Replace zed with zed2 (#3862)

Max Brunsfeld created

Change summary

.cargo/config.toml                                                                       |    3 
.github/workflows/release_nightly.yml                                                    |    2 
Cargo.lock                                                                               |  756 
Cargo.toml                                                                               |   66 
Procfile.zed2                                                                            |    4 
crates/activity_indicator/Cargo.toml                                                     |    4 
crates/activity_indicator/src/activity_indicator.rs                                      |  112 
crates/activity_indicator2/Cargo.toml                                                    |   28 
crates/activity_indicator2/src/activity_indicator.rs                                     |  331 
crates/ai/src/auth.rs                                                                    |    6 
crates/ai/src/prompts/repository_context.rs                                              |   16 
crates/ai/src/providers/open_ai/completion.rs                                            |   59 
crates/ai/src/providers/open_ai/embedding.rs                                             |   55 
crates/ai/src/test.rs                                                                    |   23 
crates/ai2/Cargo.toml                                                                    |   38 
crates/ai2/src/ai2.rs                                                                    |    8 
crates/ai2/src/auth.rs                                                                   |   15 
crates/ai2/src/completion.rs                                                             |   23 
crates/ai2/src/embedding.rs                                                              |  123 
crates/ai2/src/models.rs                                                                 |   16 
crates/ai2/src/prompts/base.rs                                                           |  330 
crates/ai2/src/prompts/file_context.rs                                                   |  164 
crates/ai2/src/prompts/generate.rs                                                       |   99 
crates/ai2/src/prompts/mod.rs                                                            |    5 
crates/ai2/src/prompts/preamble.rs                                                       |   52 
crates/ai2/src/prompts/repository_context.rs                                             |   98 
crates/ai2/src/providers/mod.rs                                                          |    1 
crates/ai2/src/providers/open_ai/completion.rs                                           |  297 
crates/ai2/src/providers/open_ai/embedding.rs                                            |  305 
crates/ai2/src/providers/open_ai/mod.rs                                                  |    9 
crates/ai2/src/providers/open_ai/model.rs                                                |   57 
crates/ai2/src/providers/open_ai/new.rs                                                  |   11 
crates/ai2/src/test.rs                                                                   |  191 
crates/assistant/Cargo.toml                                                              |    7 
crates/assistant/src/assistant.rs                                                        |   20 
crates/assistant/src/assistant_panel.rs                                                  |  808 
crates/assistant/src/assistant_settings.rs                                               |   11 
crates/assistant/src/codegen.rs                                                          |   91 
crates/assistant/src/prompts.rs                                                          |    7 
crates/assistant2/Cargo.toml                                                             |   54 
crates/assistant2/README.zmd                                                             |   63 
crates/assistant2/features.zmd                                                           |    3 
crates/assistant2/src/assistant.rs                                                       |  129 
crates/assistant2/src/assistant_panel.rs                                                 | 3540 
crates/assistant2/src/assistant_settings.rs                                              |   81 
crates/assistant2/src/codegen.rs                                                         |  688 
crates/assistant2/src/prompts.rs                                                         |  389 
crates/assistant2/src/streaming_diff.rs                                                  |  293 
crates/audio/Cargo.toml                                                                  |    5 
crates/audio/src/audio.rs                                                                |    4 
crates/audio2/Cargo.toml                                                                 |   24 
crates/audio2/audio/Cargo.toml                                                           |   23 
crates/audio2/audio/src/assets.rs                                                        |   44 
crates/audio2/audio/src/audio.rs                                                         |   81 
crates/audio2/src/assets.rs                                                              |   44 
crates/audio2/src/audio2.rs                                                              |   81 
crates/auto_update/src/auto_update.rs                                                    |  141 
crates/auto_update/src/update_notification.rs                                            |  120 
crates/auto_update2/Cargo.toml                                                           |   29 
crates/auto_update2/src/auto_update.rs                                                   |  405 
crates/auto_update2/src/update_notification.rs                                           |   56 
crates/breadcrumbs/Cargo.toml                                                            |    1 
crates/breadcrumbs/src/breadcrumbs.rs                                                    |  151 
crates/breadcrumbs2/Cargo.toml                                                           |   28 
crates/breadcrumbs2/src/breadcrumbs.rs                                                   |  111 
crates/call/Cargo.toml                                                                   |    2 
crates/call/src/call.rs                                                                  |  136 
crates/call/src/call_settings.rs                                                         |   13 
crates/call/src/participant.rs                                                           |   25 
crates/call/src/room.rs                                                                  |  336 
crates/call2/Cargo.toml                                                                  |   54 
crates/call2/src/call2.rs                                                                |  543 
crates/call2/src/call_settings.rs                                                        |   32 
crates/call2/src/participant.rs                                                          |   52 
crates/call2/src/room.rs                                                                 | 1599 
crates/channel/src/channel.rs                                                            |    4 
crates/channel/src/channel_buffer.rs                                                     |   78 
crates/channel/src/channel_chat.rs                                                       |  123 
crates/channel/src/channel_store.rs                                                      |  183 
crates/channel/src/channel_store/channel_index.rs                                        |    8 
crates/channel/src/channel_store_tests.rs                                                |   36 
crates/channel2/Cargo.toml                                                               |   54 
crates/channel2/src/channel2.rs                                                          |   23 
crates/channel2/src/channel_buffer.rs                                                    |  257 
crates/channel2/src/channel_chat.rs                                                      |  645 
crates/channel2/src/channel_store.rs                                                     | 1022 
crates/channel2/src/channel_store/channel_index.rs                                       |  184 
crates/channel2/src/channel_store_tests.rs                                               |  380 
crates/client/src/client.rs                                                              |  526 
crates/client/src/telemetry.rs                                                           |   72 
crates/client/src/test.rs                                                                |   21 
crates/client/src/user.rs                                                                |  210 
crates/client2/Cargo.toml                                                                |   53 
crates/client2/src/client2.rs                                                            | 1675 
crates/client2/src/telemetry.rs                                                          |  515 
crates/client2/src/test.rs                                                               |  214 
crates/client2/src/user.rs                                                               |  694 
crates/collab/Cargo.toml                                                                 |    4 
crates/collab/k8s                                                                        |    1 
crates/collab/k8s/collab.template.yml                                                    |    0 
crates/collab/k8s/environments/nightly.sh                                                |    0 
crates/collab/k8s/environments/preview.sh                                                |    0 
crates/collab/k8s/environments/production.sh                                             |    0 
crates/collab/k8s/environments/staging.sh                                                |    0 
crates/collab/k8s/migrate.template.yml                                                   |    0 
crates/collab/k8s/postgrest.template.yml                                                 |    0 
crates/collab/src/db/tests.rs                                                            |   17 
crates/collab/src/db/tests/channel_tests.rs                                              |    2 
crates/collab/src/db/tests/db_tests.rs                                                   |   10 
crates/collab/src/executor.rs                                                            |   13 
crates/collab/src/rpc.rs                                                                 |    9 
crates/collab/src/tests.rs                                                               |    7 
crates/collab/src/tests/channel_buffer_tests.rs                                          |  121 
crates/collab/src/tests/channel_message_tests.rs                                         |  308 
crates/collab/src/tests/channel_tests.rs                                                 |  425 
crates/collab/src/tests/editor_tests.rs                                                  |    0 
crates/collab/src/tests/following_tests.rs                                               | 3710 
crates/collab/src/tests/integration_tests.rs                                             |  487 
crates/collab/src/tests/notification_tests.rs                                            |   21 
crates/collab/src/tests/random_channel_buffer_tests.rs                                   |   40 
crates/collab/src/tests/random_project_collaboration_tests.rs                            |  108 
crates/collab/src/tests/randomized_test_helpers.rs                                       |   51 
crates/collab/src/tests/test_server.rs                                                   |  101 
crates/collab2/.env.toml                                                                 |   12 
crates/collab2/Cargo.toml                                                                |   99 
crates/collab2/README.md                                                                 |    5 
crates/collab2/admin_api.conf                                                            |    4 
crates/collab2/basic.conf                                                                |   12 
crates/collab2/migrations.sqlite/20221109000000_test_schema.sql                          |  344 
crates/collab2/migrations/20210527024318_initial_schema.sql                              |   20 
crates/collab2/migrations/20210607190313_create_access_tokens.sql                        |    7 
crates/collab2/migrations/20210805175147_create_chat_tables.sql                          |   46 
crates/collab2/migrations/20210916123647_add_nonce_to_channel_messages.sql               |    4 
crates/collab2/migrations/20210920192001_add_interests_to_signups.sql                    |    4 
crates/collab2/migrations/20220421165757_drop_signups.sql                                |    1 
crates/collab2/migrations/20220505144506_add_trigram_index_to_users.sql                  |    2 
crates/collab2/migrations/20220506130724_create_contacts.sql                             |   11 
crates/collab2/migrations/20220518151305_add_invites_to_users.sql                        |    9 
crates/collab2/migrations/20220523232954_allow_user_deletes.sql                          |    6 
crates/collab2/migrations/20220620211403_create_projects.sql                             |   24 
crates/collab2/migrations/20220913211150_create_signups.sql                              |   27 
crates/collab2/migrations/20220929182110_add_metrics_id.sql                              |    2 
crates/collab2/migrations/20221111092550_reconnection_support.sql                        |   90 
crates/collab2/migrations/20221125192125_add_added_to_mailing_list_to_signups.sql        |    2 
crates/collab2/migrations/20221207165001_add_connection_lost_to_room_participants.sql    |    7 
crates/collab2/migrations/20221213125710_index_room_participants_on_room_id.sql          |    1 
crates/collab2/migrations/20221214144346_change_epoch_from_uuid_to_integer.sql           |   30 
crates/collab2/migrations/20221219181850_project_reconnection_support.sql                |    3 
crates/collab2/migrations/20230103200902_replace_is_completed_with_completed_scan_id.sql |    3 
crates/collab2/migrations/20230202155735_followers.sql                                   |   15 
crates/collab2/migrations/20230508211523_add-repository-entries.sql                      |   13 
crates/collab2/migrations/20230511004019_add_repository_statuses.sql                     |   15 
crates/collab2/migrations/20230529164700_add_worktree_settings_files.sql                 |   10 
crates/collab2/migrations/20230605191135_remove_repository_statuses.sql                  |    2 
crates/collab2/migrations/20230616134535_add_is_external_to_worktree_entries.sql         |    2 
crates/collab2/migrations/20230727150500_add_channels.sql                                |   30 
crates/collab2/migrations/20230819154600_add_channel_buffers.sql                         |   40 
crates/collab2/migrations/20230825190322_add_server_feature_flags.sql                    |   16 
crates/collab2/migrations/20230907114200_add_channel_messages.sql                        |   19 
crates/collab2/migrations/20230925210437_add_channel_changes.sql                         |   19 
crates/collab2/migrations/20230926102500_add_participant_index_to_room_participants.sql  |    1 
crates/collab2/migrations/20231004130100_create_notifications.sql                        |   22 
crates/collab2/migrations/20231009181554_add_release_channel_to_rooms.sql                |    1 
crates/collab2/migrations/20231010114600_add_unique_index_on_rooms_channel_id.sql        |    1 
crates/collab2/migrations/20231011214412_add_guest_role.sql                              |    4 
crates/collab2/migrations/20231017185833_projects_room_id_fkey_on_delete_cascade.sql     |    8 
crates/collab2/migrations/20231018102700_create_mentions.sql                             |   11 
crates/collab2/migrations/20231024085546_move_channel_paths_to_channels_table.sql        |   12 
crates/collab2/src/api.rs                                                                |  186 
crates/collab2/src/auth.rs                                                               |  151 
crates/collab2/src/bin/dotenv2.rs                                                        |   20 
crates/collab2/src/bin/seed2.rs                                                          |  107 
crates/collab2/src/db.rs                                                                 |  672 
crates/collab2/src/db/ids.rs                                                             |  199 
crates/collab2/src/db/queries.rs                                                         |   12 
crates/collab2/src/db/queries/access_tokens.rs                                           |   54 
crates/collab2/src/db/queries/buffers.rs                                                 | 1078 
crates/collab2/src/db/queries/channels.rs                                                | 1319 
crates/collab2/src/db/queries/contacts.rs                                                |  353 
crates/collab2/src/db/queries/messages.rs                                                |  505 
crates/collab2/src/db/queries/notifications.rs                                           |  262 
crates/collab2/src/db/queries/projects.rs                                                |  960 
crates/collab2/src/db/queries/rooms.rs                                                   | 1203 
crates/collab2/src/db/queries/servers.rs                                                 |   99 
crates/collab2/src/db/queries/users.rs                                                   |  259 
crates/collab2/src/db/tables.rs                                                          |   32 
crates/collab2/src/db/tables/access_token.rs                                             |   29 
crates/collab2/src/db/tables/buffer.rs                                                   |   45 
crates/collab2/src/db/tables/buffer_operation.rs                                         |   34 
crates/collab2/src/db/tables/buffer_snapshot.rs                                          |   31 
crates/collab2/src/db/tables/channel.rs                                                  |   79 
crates/collab2/src/db/tables/channel_buffer_collaborator.rs                              |   43 
crates/collab2/src/db/tables/channel_chat_participant.rs                                 |   41 
crates/collab2/src/db/tables/channel_member.rs                                           |   59 
crates/collab2/src/db/tables/channel_message.rs                                          |   45 
crates/collab2/src/db/tables/channel_message_mention.rs                                  |   43 
crates/collab2/src/db/tables/contact.rs                                                  |   32 
crates/collab2/src/db/tables/feature_flag.rs                                             |   40 
crates/collab2/src/db/tables/follower.rs                                                 |   50 
crates/collab2/src/db/tables/language_server.rs                                          |   30 
crates/collab2/src/db/tables/notification.rs                                             |   29 
crates/collab2/src/db/tables/notification_kind.rs                                        |   15 
crates/collab2/src/db/tables/observed_buffer_edits.rs                                    |   43 
crates/collab2/src/db/tables/observed_channel_messages.rs                                |   41 
crates/collab2/src/db/tables/project.rs                                                  |   84 
crates/collab2/src/db/tables/project_collaborator.rs                                     |   43 
crates/collab2/src/db/tables/room.rs                                                     |   54 
crates/collab2/src/db/tables/room_participant.rs                                         |   61 
crates/collab2/src/db/tables/server.rs                                                   |   15 
crates/collab2/src/db/tables/signup.rs                                                   |   28 
crates/collab2/src/db/tables/user.rs                                                     |   80 
crates/collab2/src/db/tables/user_feature.rs                                             |   42 
crates/collab2/src/db/tables/worktree.rs                                                 |   36 
crates/collab2/src/db/tables/worktree_diagnostic_summary.rs                              |   21 
crates/collab2/src/db/tables/worktree_entry.rs                                           |   29 
crates/collab2/src/db/tables/worktree_repository.rs                                      |   21 
crates/collab2/src/db/tables/worktree_repository_statuses.rs                             |   23 
crates/collab2/src/db/tables/worktree_settings_file.rs                                   |   19 
crates/collab2/src/db/tests.rs                                                           |  187 
crates/collab2/src/db/tests/buffer_tests.rs                                              |  506 
crates/collab2/src/db/tests/channel_tests.rs                                             |  831 
crates/collab2/src/db/tests/db_tests.rs                                                  |  633 
crates/collab2/src/db/tests/feature_flag_tests.rs                                        |   58 
crates/collab2/src/db/tests/message_tests.rs                                             |  454 
crates/collab2/src/env.rs                                                                |   20 
crates/collab2/src/errors.rs                                                             |   29 
crates/collab2/src/executor.rs                                                           |   39 
crates/collab2/src/lib.rs                                                                |  147 
crates/collab2/src/main.rs                                                               |  139 
crates/collab2/src/rpc.rs                                                                | 3495 
crates/collab2/src/rpc/connection_pool.rs                                                |   98 
crates/collab2/src/tests.rs                                                              |   47 
crates/collab2/src/tests/channel_buffer_tests.rs                                         |  883 
crates/collab2/src/tests/channel_message_tests.rs                                        |  408 
crates/collab2/src/tests/channel_tests.rs                                                | 1543 
crates/collab2/src/tests/following_tests.rs                                              | 1890 
crates/collab2/src/tests/integration_tests.rs                                            | 5722 
crates/collab2/src/tests/notification_tests.rs                                           |  160 
crates/collab2/src/tests/random_channel_buffer_tests.rs                                  |  288 
crates/collab2/src/tests/random_project_collaboration_tests.rs                           | 1587 
crates/collab2/src/tests/randomized_test_helpers.rs                                      |  677 
crates/collab2/src/tests/test_server.rs                                                  |  616 
crates/collab_ui/Cargo.toml                                                              |   13 
crates/collab_ui/src/channel_view.rs                                                     |  210 
crates/collab_ui/src/chat_panel.rs                                                       |  724 
crates/collab_ui/src/chat_panel/message_editor.rs                                        |  109 
crates/collab_ui/src/collab_panel.rs                                                     |  892 
crates/collab_ui/src/collab_panel/channel_modal.rs                                       |  638 
crates/collab_ui/src/collab_panel/contact_finder.rs                                      |  228 
crates/collab_ui/src/collab_titlebar_item.rs                                             | 1433 
crates/collab_ui/src/collab_ui.rs                                                        |  124 
crates/collab_ui/src/face_pile.rs                                                        |  125 
crates/collab_ui/src/notification_panel.rs                                               |  653 
crates/collab_ui/src/notifications/incoming_call_notification.rs                         |  252 
crates/collab_ui/src/notifications/project_shared_notification.rs                        |  203 
crates/collab_ui/src/panel_settings.rs                                                   |   21 
crates/collab_ui2/Cargo.toml                                                             |   81 
crates/collab_ui2/src/channel_view.rs                                                    |  448 
crates/collab_ui2/src/chat_panel.rs                                                      |  704 
crates/collab_ui2/src/chat_panel/message_editor.rs                                       |  296 
crates/collab_ui2/src/collab_panel.rs                                                    | 2539 
crates/collab_ui2/src/collab_panel/channel_modal.rs                                      |  575 
crates/collab_ui2/src/collab_panel/contact_finder.rs                                     |  163 
crates/collab_ui2/src/collab_titlebar_item.rs                                            |  586 
crates/collab_ui2/src/collab_ui.rs                                                       |  167 
crates/collab_ui2/src/face_pile.rs                                                       |   30 
crates/collab_ui2/src/notification_panel.rs                                              |  755 
crates/collab_ui2/src/notifications.rs                                                   |   11 
crates/collab_ui2/src/notifications/incoming_call_notification.rs                        |  163 
crates/collab_ui2/src/notifications/project_shared_notification.rs                       |  180 
crates/collab_ui2/src/panel_settings.rs                                                  |   70 
crates/command_palette/Cargo.toml                                                        |    9 
crates/command_palette/src/command_palette.rs                                            |  451 
crates/command_palette2/Cargo.toml                                                       |   36 
crates/command_palette2/src/command_palette.rs                                           |  510 
crates/component_test/Cargo.toml                                                         |   18 
crates/component_test/src/component_test.rs                                              |  121 
crates/context_menu/Cargo.toml                                                           |   16 
crates/context_menu/src/context_menu.rs                                                  |  528 
crates/copilot/Cargo.toml                                                                |    3 
crates/copilot/src/copilot.rs                                                            |  346 
crates/copilot/src/sign_in.rs                                                            |  392 
crates/copilot2/Cargo.toml                                                               |   51 
crates/copilot2/src/copilot2.rs                                                          | 1253 
crates/copilot2/src/request.rs                                                           |  225 
crates/copilot2/src/sign_in.rs                                                           |  211 
crates/copilot_button/Cargo.toml                                                         |    4 
crates/copilot_button/src/copilot_button.rs                                              |  343 
crates/copilot_button2/Cargo.toml                                                        |   27 
crates/copilot_button2/src/copilot_button.rs                                             |  378 
crates/db/src/db.rs                                                                      |   10 
crates/db2/Cargo.toml                                                                    |   33 
crates/db2/README.md                                                                     |    5 
crates/db2/src/db2.rs                                                                    |  331 
crates/db2/src/kvp.rs                                                                    |   62 
crates/db2/src/query.rs                                                                  |  314 
crates/diagnostics/Cargo.toml                                                            |    5 
crates/diagnostics/src/diagnostics.rs                                                    |  572 
crates/diagnostics/src/items.rs                                                          |  249 
crates/diagnostics/src/project_diagnostics_settings.rs                                   |    4 
crates/diagnostics/src/toolbar_controls.rs                                               |   98 
crates/diagnostics2/Cargo.toml                                                           |   43 
crates/diagnostics2/src/diagnostics.rs                                                   | 1612 
crates/diagnostics2/src/items.rs                                                         |  187 
crates/diagnostics2/src/project_diagnostics_settings.rs                                  |   28 
crates/diagnostics2/src/toolbar_controls.rs                                              |   65 
crates/drag_and_drop/Cargo.toml                                                          |   16 
crates/drag_and_drop/src/drag_and_drop.rs                                                |  378 
crates/editor/Cargo.toml                                                                 |    7 
crates/editor/src/blink_manager.rs                                                       |   38 
crates/editor/src/display_map.rs                                                         |  410 
crates/editor/src/display_map/block_map.rs                                               |  124 
crates/editor/src/display_map/fold_map.rs                                                |  116 
crates/editor/src/display_map/inlay_map.rs                                               |    7 
crates/editor/src/display_map/wrap_map.rs                                                |  150 
crates/editor/src/editor.rs                                                              |  693 
crates/editor/src/editor_settings.rs                                                     |    8 
crates/editor/src/editor_tests.rs                                                        |  639 
crates/editor/src/element.rs                                                             |  819 
crates/editor/src/git.rs                                                                 |   16 
crates/editor/src/highlight_matching_bracket.rs                                          |    2 
crates/editor/src/hover_popover.rs                                                       |  436 
crates/editor/src/inlay_hint_cache.rs                                                    |  544 
crates/editor/src/items.rs                                                               |  404 
crates/editor/src/link_go_to_definition.rs                                               |  370 
crates/editor/src/mouse_context_menu.rs                                                  |   66 
crates/editor/src/movement.rs                                                            |  115 
crates/editor/src/rust_analyzer_ext.rs                                                   |   59 
crates/editor/src/scroll.rs                                                              |  124 
crates/editor/src/scroll/actions.rs                                                      |  111 
crates/editor/src/scroll/autoscroll.rs                                                   |   53 
crates/editor/src/selections_collection.rs                                               |   28 
crates/editor/src/test.rs                                                                |   31 
crates/editor/src/test/editor_lsp_test_context.rs                                        |   27 
crates/editor/src/test/editor_test_context.rs                                            |  150 
crates/editor2/Cargo.toml                                                                |   93 
crates/editor2/src/blink_manager.rs                                                      |  107 
crates/editor2/src/display_map.rs                                                        | 1854 
crates/editor2/src/display_map/block_map.rs                                              | 1647 
crates/editor2/src/display_map/fold_map.rs                                               | 1746 
crates/editor2/src/display_map/inlay_map.rs                                              | 1896 
crates/editor2/src/display_map/tab_map.rs                                                |  765 
crates/editor2/src/display_map/wrap_map.rs                                               | 1359 
crates/editor2/src/editor.rs                                                             | 9884 
crates/editor2/src/editor_settings.rs                                                    |   72 
crates/editor2/src/editor_tests.rs                                                       | 8268 
crates/editor2/src/element.rs                                                            | 3815 
crates/editor2/src/git.rs                                                                |  282 
crates/editor2/src/highlight_matching_bracket.rs                                         |  138 
crates/editor2/src/hover_popover.rs                                                      | 1345 
crates/editor2/src/inlay_hint_cache.rs                                                   | 3268 
crates/editor2/src/items.rs                                                              | 1339 
crates/editor2/src/link_go_to_definition.rs                                              | 1279 
crates/editor2/src/mouse_context_menu.rs                                                 |  110 
crates/editor2/src/movement.rs                                                           |  926 
crates/editor2/src/persistence.rs                                                        |   83 
crates/editor2/src/rust_analyzer_ext.rs                                                  |  119 
crates/editor2/src/scroll.rs                                                             |  460 
crates/editor2/src/scroll/actions.rs                                                     |  103 
crates/editor2/src/scroll/autoscroll.rs                                                  |  253 
crates/editor2/src/scroll/scroll_amount.rs                                               |   28 
crates/editor2/src/selections_collection.rs                                              |  888 
crates/editor2/src/test.rs                                                               |   74 
crates/editor2/src/test/editor_lsp_test_context.rs                                       |  298 
crates/editor2/src/test/editor_test_context.rs                                           |  404 
crates/feature_flags/src/feature_flags.rs                                                |   21 
crates/feature_flags2/Cargo.toml                                                         |   12 
crates/feature_flags2/src/feature_flags2.rs                                              |   80 
crates/feedback/Cargo.toml                                                               |   20 
crates/feedback/src/deploy_feedback_button.rs                                            |   98 
crates/feedback/src/feedback.rs                                                          |   91 
crates/feedback/src/feedback_editor.rs                                                   |  442 
crates/feedback/src/feedback_info_text.rs                                                |   94 
crates/feedback/src/feedback_modal.rs                                                    |    0 
crates/feedback/src/submit_feedback_button.rs                                            |  108 
crates/feedback/src/system_specs.rs                                                      |   27 
crates/feedback2/Cargo.toml                                                              |   47 
crates/feedback2/src/deploy_feedback_button.rs                                           |   49 
crates/feedback2/src/feedback2.rs                                                        |   61 
crates/feedback2/src/system_specs.rs                                                     |   68 
crates/file_finder/Cargo.toml                                                            |    4 
crates/file_finder/src/file_finder.rs                                                    |  685 
crates/file_finder2/Cargo.toml                                                           |   37 
crates/file_finder2/src/file_finder.rs                                                   | 1956 
crates/fs/src/fs.rs                                                                      |   27 
crates/fs2/Cargo.toml                                                                    |   40 
crates/fs2/src/fs2.rs                                                                    | 1278 
crates/fs2/src/repository.rs                                                             |  415 
crates/fuzzy/src/paths.rs                                                                |    9 
crates/fuzzy/src/strings.rs                                                              |   48 
crates/fuzzy2/Cargo.toml                                                                 |   13 
crates/fuzzy2/src/char_bag.rs                                                            |   63 
crates/fuzzy2/src/fuzzy2.rs                                                              |   10 
crates/fuzzy2/src/matcher.rs                                                             |  464 
crates/fuzzy2/src/paths.rs                                                               |  257 
crates/fuzzy2/src/strings.rs                                                             |  187 
crates/git3/Cargo.toml                                                                   |   30 
crates/git3/src/diff.rs                                                                  |  412 
crates/git3/src/git.rs                                                                   |   11 
crates/go_to_line/Cargo.toml                                                             |    2 
crates/go_to_line/src/go_to_line.rs                                                      |  270 
crates/go_to_line2/Cargo.toml                                                            |   25 
crates/go_to_line2/src/go_to_line.rs                                                     |  188 
crates/gpui/Cargo.toml                                                                   |   30 
crates/gpui/build.rs                                                                     |   85 
crates/gpui/docs/contexts.md                                                             |    0 
crates/gpui/docs/key_dispatch.md                                                         |    0 
crates/gpui/examples/components.rs                                                       |  237 
crates/gpui/examples/corner_radii.rs                                                     |  154 
crates/gpui/examples/text.rs                                                             |   81 
crates/gpui/src/action.rs                                                                |    0 
crates/gpui/src/app.rs                                                                   | 7536 
crates/gpui/src/app/action.rs                                                            |  120 
crates/gpui/src/app/async_context.rs                                                     |    0 
crates/gpui/src/app/callback_collection.rs                                               |  164 
crates/gpui/src/app/entity_map.rs                                                        |    0 
crates/gpui/src/app/menu.rs                                                              |   99 
crates/gpui/src/app/model_context.rs                                                     |    0 
crates/gpui/src/app/ref_counts.rs                                                        |  220 
crates/gpui/src/app/test_app_context.rs                                                  |  661 
crates/gpui/src/app/test_context.rs                                                      |    0 
crates/gpui/src/app/window.rs                                                            | 1767 
crates/gpui/src/app/window_input_handler.rs                                              |   90 
crates/gpui/src/arena.rs                                                                 |    0 
crates/gpui/src/assets.rs                                                                |   77 
crates/gpui/src/clipboard.rs                                                             |   42 
crates/gpui/src/color.rs                                                                 |  493 
crates/gpui/src/dispatch.rs                                                              |    1 
crates/gpui/src/element.rs                                                               |    0 
crates/gpui/src/elements.rs                                                              |  740 
crates/gpui/src/elements/align.rs                                                        |  115 
crates/gpui/src/elements/canvas.rs                                                       |  109 
crates/gpui/src/elements/clipped.rs                                                      |   71 
crates/gpui/src/elements/component.rs                                                    |  342 
crates/gpui/src/elements/constrained_box.rs                                              |  187 
crates/gpui/src/elements/container.rs                                                    |  684 
crates/gpui/src/elements/div.rs                                                          |    0 
crates/gpui/src/elements/empty.rs                                                        |   89 
crates/gpui/src/elements/expanded.rs                                                     |   96 
crates/gpui/src/elements/flex.rs                                                         |  512 
crates/gpui/src/elements/hook.rs                                                         |   85 
crates/gpui/src/elements/image.rs                                                        |  137 
crates/gpui/src/elements/img.rs                                                          |    0 
crates/gpui/src/elements/keystroke_label.rs                                              |  100 
crates/gpui/src/elements/label.rs                                                        |  280 
crates/gpui/src/elements/list.rs                                                         | 1086 
crates/gpui/src/elements/mod.rs                                                          |    0 
crates/gpui/src/elements/mouse_event_handler.rs                                          |  323 
crates/gpui/src/elements/overlay.rs                                                      |  401 
crates/gpui/src/elements/resizable.rs                                                    |  290 
crates/gpui/src/elements/stack.rs                                                        |  104 
crates/gpui/src/elements/svg.rs                                                          |  161 
crates/gpui/src/elements/text.rs                                                         |  733 
crates/gpui/src/elements/tooltip.rs                                                      |  244 
crates/gpui/src/elements/uniform_list.rs                                                 |  570 
crates/gpui/src/executor.rs                                                              | 1018 
crates/gpui/src/font_cache.rs                                                            |  330 
crates/gpui/src/fonts.rs                                                                 |  636 
crates/gpui/src/geometry.rs                                                              | 2637 
crates/gpui/src/gpui.rs                                                                  |  239 
crates/gpui/src/image_cache.rs                                                           |   33 
crates/gpui/src/image_data.rs                                                            |   43 
crates/gpui/src/input.rs                                                                 |    0 
crates/gpui/src/interactive.rs                                                           |    0 
crates/gpui/src/json.rs                                                                  |   15 
crates/gpui/src/key_dispatch.rs                                                          |    0 
crates/gpui/src/keymap/binding.rs                                                        |    0 
crates/gpui/src/keymap/context.rs                                                        |    0 
crates/gpui/src/keymap/keymap.rs                                                         |    0 
crates/gpui/src/keymap/matcher.rs                                                        |    0 
crates/gpui/src/keymap/mod.rs                                                            |    0 
crates/gpui/src/keymap_matcher.rs                                                        |  587 
crates/gpui/src/keymap_matcher/binding.rs                                                |  111 
crates/gpui/src/keymap_matcher/keymap.rs                                                 |  392 
crates/gpui/src/keymap_matcher/keymap_context.rs                                         |  326 
crates/gpui/src/keymap_matcher/keystroke.rs                                              |  141 
crates/gpui/src/platform.rs                                                              |  565 
crates/gpui/src/platform/app_menu.rs                                                     |    0 
crates/gpui/src/platform/event.rs                                                        |  236 
crates/gpui/src/platform/keystroke.rs                                                    |    0 
crates/gpui/src/platform/mac.rs                                                          |   93 
crates/gpui/src/platform/mac/appearance.rs                                               |   36 
crates/gpui/src/platform/mac/atlas.rs                                                    |  173 
crates/gpui/src/platform/mac/dispatcher.rs                                               |   71 
crates/gpui/src/platform/mac/display.rs                                                  |    0 
crates/gpui/src/platform/mac/display_linker.rs                                           |    0 
crates/gpui/src/platform/mac/event.rs                                                    |  359 
crates/gpui/src/platform/mac/events.rs                                                   |    0 
crates/gpui/src/platform/mac/fonts.rs                                                    |  671 
crates/gpui/src/platform/mac/fonts/open_type.rs                                          |  395 
crates/gpui/src/platform/mac/geometry.rs                                                 |   45 
crates/gpui/src/platform/mac/image_cache.rs                                              |  115 
crates/gpui/src/platform/mac/metal_atlas.rs                                              |    0 
crates/gpui/src/platform/mac/metal_renderer.rs                                           |    0 
crates/gpui/src/platform/mac/open_type.rs                                                |    0 
crates/gpui/src/platform/mac/platform.rs                                                 |  720 
crates/gpui/src/platform/mac/renderer.rs                                                 | 1277 
crates/gpui/src/platform/mac/screen.rs                                                   |  144 
crates/gpui/src/platform/mac/shaders.metal                                               |    0 
crates/gpui/src/platform/mac/shaders/shaders.h                                           |  135 
crates/gpui/src/platform/mac/shaders/shaders.metal                                       |  464 
crates/gpui/src/platform/mac/sprite_cache.rs                                             |  164 
crates/gpui/src/platform/mac/text_system.rs                                              |    0 
crates/gpui/src/platform/mac/window.rs                                                   |  604 
crates/gpui/src/platform/mac/window_appearence.rs                                        |    0 
crates/gpui/src/platform/test.rs                                                         |  443 
crates/gpui/src/platform/test/dispatcher.rs                                              |    0 
crates/gpui/src/platform/test/display.rs                                                 |    0 
crates/gpui/src/platform/test/platform.rs                                                |    0 
crates/gpui/src/platform/test/window.rs                                                  |    0 
crates/gpui/src/prelude.rs                                                               |    0 
crates/gpui/src/scene.rs                                                                 | 1097 
crates/gpui/src/scene/mouse_event.rs                                                     |  270 
crates/gpui/src/scene/mouse_region.rs                                                    |  555 
crates/gpui/src/scene/region.rs                                                          |    7 
crates/gpui/src/shared_string.rs                                                         |    0 
crates/gpui/src/style.rs                                                                 |    0 
crates/gpui/src/styled.rs                                                                |    2 
crates/gpui/src/subscription.rs                                                          |    0 
crates/gpui/src/svg_renderer.rs                                                          |    0 
crates/gpui/src/taffy.rs                                                                 |    0 
crates/gpui/src/test.rs                                                                  |  240 
crates/gpui/src/text_layout.rs                                                           |  876 
crates/gpui/src/text_system.rs                                                           |    0 
crates/gpui/src/text_system/font_features.rs                                             |    0 
crates/gpui/src/text_system/line.rs                                                      |    0 
crates/gpui/src/text_system/line_layout.rs                                               |    0 
crates/gpui/src/text_system/line_wrapper.rs                                              |    0 
crates/gpui/src/util.rs                                                                  |   15 
crates/gpui/src/view.rs                                                                  |    0 
crates/gpui/src/views.rs                                                                 |    5 
crates/gpui/src/views/select.rs                                                          |  156 
crates/gpui/src/window.rs                                                                |    0 
crates/gpui/tests/action_macros.rs                                                       |    6 
crates/gpui/tests/test.rs                                                                |   14 
crates/gpui2/Cargo.toml                                                                  |   92 
crates/gpui2/build.rs                                                                    |  137 
crates/gpui2/src/app.rs                                                                  | 1264 
crates/gpui2/src/assets.rs                                                               |   64 
crates/gpui2/src/color.rs                                                                |  457 
crates/gpui2/src/elements/canvas.rs                                                      |   54 
crates/gpui2/src/elements/list.rs                                                        |  560 
crates/gpui2/src/elements/overlay.rs                                                     |  241 
crates/gpui2/src/elements/svg.rs                                                         |   78 
crates/gpui2/src/elements/text.rs                                                        |  423 
crates/gpui2/src/elements/uniform_list.rs                                                |  316 
crates/gpui2/src/executor.rs                                                             |  402 
crates/gpui2/src/geometry.rs                                                             | 2796 
crates/gpui2/src/gpui2.rs                                                                |  215 
crates/gpui2/src/image_cache.rs                                                          |  107 
crates/gpui2/src/platform.rs                                                             |  592 
crates/gpui2/src/platform/mac.rs                                                         |  139 
crates/gpui2/src/platform/mac/dispatch.h                                                 |    1 
crates/gpui2/src/platform/mac/dispatcher.rs                                              |   96 
crates/gpui2/src/platform/mac/platform.rs                                                | 1194 
crates/gpui2/src/platform/mac/window.rs                                                  | 1791 
crates/gpui2/src/platform/test.rs                                                        |    9 
crates/gpui2/src/scene.rs                                                                |  778 
crates/gpui2/src/test.rs                                                                 |   80 
crates/gpui2/src/util.rs                                                                 |   51 
crates/gpui2_macros/Cargo.toml                                                           |   14 
crates/gpui2_macros/src/gpui2_macros.rs                                                  |   32 
crates/gpui_macros/Cargo.toml                                                            |    8 
crates/gpui_macros/src/derive_into_element.rs                                            |    2 
crates/gpui_macros/src/derive_render.rs                                                  |    2 
crates/gpui_macros/src/gpui_macros.rs                                                    |  393 
crates/gpui_macros/src/register_action.rs                                                |    0 
crates/gpui_macros/src/style_helpers.rs                                                  |    0 
crates/gpui_macros/src/test.rs                                                           |    0 
crates/install_cli/src/install_cli.rs                                                    |    5 
crates/install_cli2/Cargo.toml                                                           |   19 
crates/install_cli2/src/install_cli2.rs                                                  |   54 
crates/journal/Cargo.toml                                                                |    1 
crates/journal/src/journal.rs                                                            |   72 
crates/journal2/Cargo.toml                                                               |   27 
crates/journal2/src/journal2.rs                                                          |  181 
crates/language/Cargo.toml                                                               |    5 
crates/language/src/buffer.rs                                                            |  324 
crates/language/src/buffer_tests.rs                                                      |  239 
crates/language/src/highlight_map.rs                                                     |   22 
crates/language/src/language.rs                                                          |  138 
crates/language/src/language_settings.rs                                                 |    9 
crates/language/src/markdown.rs                                                          |   16 
crates/language/src/outline.rs                                                           |    7 
crates/language/src/syntax_map.rs                                                        |    9 
crates/language2/Cargo.toml                                                              |   86 
crates/language2/build.rs                                                                |    5 
crates/language2/src/buffer.rs                                                           | 3193 
crates/language2/src/buffer_tests.rs                                                     | 2493 
crates/language2/src/diagnostic_set.rs                                                   |  236 
crates/language2/src/highlight_map.rs                                                    |  111 
crates/language2/src/language2.rs                                                        | 2025 
crates/language2/src/language_settings.rs                                                |  431 
crates/language2/src/markdown.rs                                                         |  301 
crates/language2/src/outline.rs                                                          |  139 
crates/language2/src/proto.rs                                                            |  590 
crates/language2/src/syntax_map.rs                                                       | 1806 
crates/language2/src/syntax_map/syntax_map_tests.rs                                      | 1323 
crates/language_selector/Cargo.toml                                                      |    3 
crates/language_selector/src/active_buffer_language.rs                                   |   61 
crates/language_selector/src/language_selector.rs                                        |  152 
crates/language_selector2/Cargo.toml                                                     |   26 
crates/language_selector2/src/active_buffer_language.rs                                  |   79 
crates/language_selector2/src/language_selector.rs                                       |  232 
crates/language_tools/Cargo.toml                                                         |    1 
crates/language_tools/src/lsp_log.rs                                                     |  578 
crates/language_tools/src/lsp_log_tests.rs                                               |   25 
crates/language_tools/src/syntax_tree_view.rs                                            |  510 
crates/language_tools2/Cargo.toml                                                        |   34 
crates/language_tools2/src/language_tools.rs                                             |   15 
crates/language_tools2/src/lsp_log.rs                                                    |  895 
crates/language_tools2/src/lsp_log_tests.rs                                              |  107 
crates/language_tools2/src/syntax_tree_view.rs                                           |  533 
crates/live_kit_client/LiveKitBridge/Package.resolved                                    |    4 
crates/live_kit_client/build.rs                                                          |   12 
crates/live_kit_client/examples/test_app.rs                                              |   38 
crates/live_kit_client/src/prod.rs                                                       |  237 
crates/live_kit_client/src/test.rs                                                       |   38 
crates/live_kit_client2/.cargo/config.toml                                               |    2 
crates/live_kit_client2/Cargo.toml                                                       |   71 
crates/live_kit_client2/LiveKitBridge2/Package.resolved                                  |   52 
crates/live_kit_client2/LiveKitBridge2/Package.swift                                     |   27 
crates/live_kit_client2/LiveKitBridge2/README.md                                         |    3 
crates/live_kit_client2/LiveKitBridge2/Sources/LiveKitBridge2/LiveKitBridge2.swift       |  327 
crates/live_kit_client2/build.rs                                                         |  182 
crates/live_kit_client2/examples/test_app2.rs                                            |  176 
crates/live_kit_client2/src/live_kit_client2.rs                                          |   11 
crates/live_kit_client2/src/prod.rs                                                      |  947 
crates/live_kit_client2/src/test.rs                                                      |  651 
crates/lsp/src/lsp.rs                                                                    |   70 
crates/lsp2/Cargo.toml                                                                   |   38 
crates/lsp2/src/lsp2.rs                                                                  | 1197 
crates/menu/Cargo.toml                                                                   |    1 
crates/menu/src/menu.rs                                                                  |   13 
crates/menu2/Cargo.toml                                                                  |   13 
crates/menu2/src/menu2.rs                                                                |   24 
crates/multi_buffer/Cargo.toml                                                           |    2 
crates/multi_buffer/src/multi_buffer.rs                                                  |  188 
crates/multi_buffer2/Cargo.toml                                                          |   78 
crates/multi_buffer2/src/anchor.rs                                                       |  138 
crates/multi_buffer2/src/multi_buffer2.rs                                                | 5390 
crates/notifications/Cargo.toml                                                          |    2 
crates/notifications/src/notification_store.rs                                           |  459 
crates/notifications/src/notification_store2.rs                                          |    0 
crates/notifications2/Cargo.toml                                                         |   42 
crates/outline/Cargo.toml                                                                |    4 
crates/outline/src/outline.rs                                                            |  208 
crates/outline2/Cargo.toml                                                               |   29 
crates/outline2/src/outline.rs                                                           |  309 
crates/picker/Cargo.toml                                                                 |    4 
crates/picker/src/picker.rs                                                              |  507 
crates/picker2/Cargo.toml                                                                |   28 
crates/picker2/src/picker2.rs                                                            |  323 
crates/prettier/src/prettier.rs                                                          |  315 
crates/prettier2/Cargo.toml                                                              |   35 
crates/prettier2/src/prettier2.rs                                                        |  869 
crates/prettier2/src/prettier_server.js                                                  |  241 
crates/project/Cargo.toml                                                                |    3 
crates/project/src/lsp_command.rs                                                        |  423 
crates/project/src/lsp_ext_command.rs                                                    |   16 
crates/project/src/prettier_support.rs                                                   |  230 
crates/project/src/project.rs                                                            |  336 
crates/project/src/project_settings.rs                                                   |    7 
crates/project/src/project_tests.rs                                                      |  474 
crates/project/src/terminals.rs                                                          |   22 
crates/project/src/worktree.rs                                                           |  274 
crates/project/src/worktree_tests.rs                                                     |  110 
crates/project2/Cargo.toml                                                               |   85 
crates/project2/src/ignore.rs                                                            |   53 
crates/project2/src/lsp_command.rs                                                       | 2364 
crates/project2/src/lsp_ext_command.rs                                                   |  137 
crates/project2/src/prettier_support.rs                                                  |  772 
crates/project2/src/project2.rs                                                          | 8737 
crates/project2/src/project_settings.rs                                                  |   50 
crates/project2/src/project_tests.rs                                                     | 4317 
crates/project2/src/search.rs                                                            |  463 
crates/project2/src/terminals.rs                                                         |  128 
crates/project2/src/worktree.rs                                                          | 4576 
crates/project2/src/worktree_tests.rs                                                    | 2462 
crates/project_panel/Cargo.toml                                                          |   11 
crates/project_panel/src/file_associations.rs                                            |   71 
crates/project_panel/src/project_panel.rs                                                |  757 
crates/project_panel/src/project_panel_settings.rs                                       |    9 
crates/project_panel2/Cargo.toml                                                         |   41 
crates/project_panel2/src/file_associations.rs                                           |   87 
crates/project_panel2/src/project_panel.rs                                               | 3480 
crates/project_panel2/src/project_panel_settings.rs                                      |   48 
crates/project_symbols/Cargo.toml                                                        |    2 
crates/project_symbols/src/project_symbols.rs                                            |  166 
crates/project_symbols2/Cargo.toml                                                       |   37 
crates/project_symbols2/src/project_symbols.rs                                           |  415 
crates/quick_action_bar/Cargo.toml                                                       |    3 
crates/quick_action_bar/src/quick_action_bar.rs                                          |  238 
crates/quick_action_bar2/Cargo.toml                                                      |   22 
crates/quick_action_bar2/src/quick_action_bar.rs                                         |  191 
crates/recent_projects/Cargo.toml                                                        |    4 
crates/recent_projects/src/highlighted_workspace_location.rs                             |   14 
crates/recent_projects/src/projects.rs                                                   |    0 
crates/recent_projects/src/recent_projects.rs                                            |  226 
crates/recent_projects2/Cargo.toml                                                       |   30 
crates/recent_projects2/src/highlighted_workspace_location.rs                            |  129 
crates/recent_projects2/src/recent_projects.rs                                           |  247 
crates/rich_text/Cargo.toml                                                              |    1 
crates/rich_text/src/rich_text.rs                                                        |  300 
crates/rich_text2/Cargo.toml                                                             |   29 
crates/rich_text2/src/rich_text.rs                                                       |  353 
crates/rope/src/rope.rs                                                                  |    2 
crates/rope2/Cargo.toml                                                                  |   21 
crates/rope2/src/offset_utf16.rs                                                         |   50 
crates/rope2/src/point.rs                                                                |  128 
crates/rope2/src/point_utf16.rs                                                          |  119 
crates/rope2/src/rope2.rs                                                                | 1437 
crates/rope2/src/unclipped.rs                                                            |   57 
crates/rpc/Cargo.toml                                                                    |    3 
crates/rpc/src/conn.rs                                                                   |   16 
crates/rpc/src/peer.rs                                                                   |   55 
crates/rpc2/Cargo.toml                                                                   |   46 
crates/rpc2/build.rs                                                                     |    8 
crates/rpc2/proto/zed.proto                                                              | 1634 
crates/rpc2/src/auth.rs                                                                  |  136 
crates/rpc2/src/conn.rs                                                                  |  108 
crates/rpc2/src/macros.rs                                                                |   70 
crates/rpc2/src/notification.rs                                                          |  105 
crates/rpc2/src/peer.rs                                                                  |  934 
crates/rpc2/src/proto.rs                                                                 |  692 
crates/rpc2/src/rpc.rs                                                                   |   12 
crates/search/Cargo.toml                                                                 |    1 
crates/search/src/buffer_search.rs                                                       |  813 
crates/search/src/mode.rs                                                                |   35 
crates/search/src/project_search.rs                                                      |  684 
crates/search/src/search.rs                                                              |   94 
crates/search/src/search_bar.rs                                                          |  184 
crates/search2/Cargo.toml                                                                |   40 
crates/search2/src/buffer_search.rs                                                      | 1858 
crates/search2/src/history.rs                                                            |  184 
crates/search2/src/mode.rs                                                               |   46 
crates/search2/src/project_search.rs                                                     | 2844 
crates/search2/src/search.rs                                                             |  108 
crates/search2/src/search_bar.rs                                                         |   18 
crates/semantic_index/Cargo.toml                                                         |    3 
crates/semantic_index/src/db.rs                                                          |    4 
crates/semantic_index/src/embedding_queue.rs                                             |    9 
crates/semantic_index/src/semantic_index.rs                                              |  224 
crates/semantic_index/src/semantic_index_settings.rs                                     |    6 
crates/semantic_index/src/semantic_index_tests.rs                                        |   32 
crates/semantic_index2/Cargo.toml                                                        |   69 
crates/semantic_index2/README.md                                                         |   20 
crates/semantic_index2/eval/gpt-engineer.json                                            |  114 
crates/semantic_index2/eval/tree-sitter.json                                             |  104 
crates/semantic_index2/src/db.rs                                                         |  603 
crates/semantic_index2/src/embedding_queue.rs                                            |  169 
crates/semantic_index2/src/parsing.rs                                                    |  414 
crates/semantic_index2/src/semantic_index.rs                                             | 1280 
crates/semantic_index2/src/semantic_index_settings.rs                                    |   28 
crates/semantic_index2/src/semantic_index_tests.rs                                       | 1697 
crates/settings/src/keymap_file.rs                                                       |   14 
crates/settings/src/settings.rs                                                          |    2 
crates/settings/src/settings_file.rs                                                     |   88 
crates/settings/src/settings_store.rs                                                    |  101 
crates/settings2/Cargo.toml                                                              |   42 
crates/settings2/src/keymap_file.rs                                                      |  163 
crates/settings2/src/settings2.rs                                                        |   38 
crates/settings2/src/settings_file.rs                                                    |  134 
crates/settings2/src/settings_store.rs                                                   | 1304 
crates/story/Cargo.toml                                                                  |    2 
crates/storybook/Cargo.lock                                                              |    0 
crates/storybook/Cargo.toml                                                              |   26 
crates/storybook/build.rs                                                                |    0 
crates/storybook/docs/thoughts.md                                                        |    0 
crates/storybook/src/assets.rs                                                           |    0 
crates/storybook/src/stories.rs                                                          |    0 
crates/storybook/src/stories/auto_height_editor.rs                                       |    0 
crates/storybook/src/stories/cursor.rs                                                   |    0 
crates/storybook/src/stories/focus.rs                                                    |    0 
crates/storybook/src/stories/kitchen_sink.rs                                             |    0 
crates/storybook/src/stories/overflow_scroll.rs                                          |    0 
crates/storybook/src/stories/picker.rs                                                   |    0 
crates/storybook/src/stories/scroll.rs                                                   |    0 
crates/storybook/src/stories/text.rs                                                     |    2 
crates/storybook/src/stories/viewport_units.rs                                           |    0 
crates/storybook/src/stories/z_index.rs                                                  |    0 
crates/storybook/src/story_selector.rs                                                   |    0 
crates/storybook/src/storybook.rs                                                        |    6 
crates/terminal/src/mappings/colors.rs                                                   |  134 
crates/terminal/src/mappings/keys.rs                                                     |  287 
crates/terminal/src/mappings/mouse.rs                                                    |  187 
crates/terminal/src/terminal.rs                                                          |  435 
crates/terminal/src/terminal_settings.rs                                                 |   33 
crates/terminal2/Cargo.toml                                                              |   38 
crates/terminal2/src/mappings/colors.rs                                                  |   12 
crates/terminal2/src/mappings/keys.rs                                                    |  464 
crates/terminal2/src/mappings/mod.rs                                                     |    3 
crates/terminal2/src/mappings/mouse.rs                                                   |  277 
crates/terminal2/src/terminal2.rs                                                        | 1638 
crates/terminal2/src/terminal_settings.rs                                                |  157 
crates/terminal_view/Cargo.toml                                                          |    4 
crates/terminal_view/src/terminal_element.rs                                             | 1030 
crates/terminal_view/src/terminal_panel.rs                                               |  377 
crates/terminal_view/src/terminal_view.rs                                                |  583 
crates/terminal_view2/Cargo.toml                                                         |   46 
crates/terminal_view2/README.md                                                          |   23 
crates/terminal_view2/scripts/print256color.sh                                           |   96 
crates/terminal_view2/scripts/truecolor.sh                                               |   19 
crates/terminal_view2/src/persistence.rs                                                 |   71 
crates/terminal_view2/src/terminal_element.rs                                            | 1054 
crates/terminal_view2/src/terminal_panel.rs                                              |  437 
crates/terminal_view2/src/terminal_view.rs                                               | 1134 
crates/text/src/locator.rs                                                               |    4 
crates/text/src/selection.rs                                                             |    2 
crates/text/src/subscription.rs                                                          |    2 
crates/text/src/text.rs                                                                  |    5 
crates/text2/Cargo.toml                                                                  |   37 
crates/text2/src/anchor.rs                                                               |  144 
crates/text2/src/locator.rs                                                              |  125 
crates/text2/src/network.rs                                                              |   69 
crates/text2/src/operation_queue.rs                                                      |  153 
crates/text2/src/patch.rs                                                                |  594 
crates/text2/src/selection.rs                                                            |  123 
crates/text2/src/subscription.rs                                                         |   48 
crates/text2/src/tests.rs                                                                |  764 
crates/text2/src/text2.rs                                                                | 2679 
crates/text2/src/undo_map.rs                                                             |  112 
crates/theme/Cargo.toml                                                                  |   16 
crates/theme/src/components.rs                                                           |  480 
crates/theme/src/default_colors.rs                                                       |    0 
crates/theme/src/default_theme.rs                                                        |    0 
crates/theme/src/one_themes.rs                                                           |    0 
crates/theme/src/prelude.rs                                                              |    0 
crates/theme/src/registry.rs                                                             |    0 
crates/theme/src/scale.rs                                                                |    0 
crates/theme/src/settings.rs                                                             |    0 
crates/theme/src/styles.rs                                                               |    0 
crates/theme/src/styles/colors.rs                                                        |    0 
crates/theme/src/styles/players.rs                                                       |    0 
crates/theme/src/styles/status.rs                                                        |    0 
crates/theme/src/styles/stories/color.rs                                                 |    0 
crates/theme/src/styles/stories/mod.rs                                                   |    0 
crates/theme/src/styles/stories/players.rs                                               |    0 
crates/theme/src/styles/syntax.rs                                                        |    0 
crates/theme/src/styles/system.rs                                                        |    0 
crates/theme/src/theme.rs                                                                | 1401 
crates/theme/src/theme_registry.rs                                                       |  106 
crates/theme/src/theme_settings.rs                                                       |  214 
crates/theme/src/themes/andromeda.rs                                                     |    0 
crates/theme/src/themes/atelier.rs                                                       |    0 
crates/theme/src/themes/ayu.rs                                                           |    0 
crates/theme/src/themes/gruvbox.rs                                                       |    0 
crates/theme/src/themes/mod.rs                                                           |    0 
crates/theme/src/themes/one.rs                                                           |    0 
crates/theme/src/themes/rose_pine.rs                                                     |    0 
crates/theme/src/themes/sandcastle.rs                                                    |    0 
crates/theme/src/themes/solarized.rs                                                     |    0 
crates/theme/src/themes/summercamp.rs                                                    |    0 
crates/theme/src/ui.rs                                                                   |  244 
crates/theme/src/user_theme.rs                                                           |    0 
crates/theme/util/hex_to_hsla.py                                                         |    0 
crates/theme2/Cargo.toml                                                                 |   42 
crates/theme2/src/theme2.rs                                                              |  148 
crates/theme_importer/Cargo.toml                                                         |    4 
crates/theme_importer/src/main.rs                                                        |    4 
crates/theme_selector/Cargo.toml                                                         |    9 
crates/theme_selector/src/theme_selector.rs                                              |  178 
crates/theme_selector2/Cargo.toml                                                        |   30 
crates/theme_selector2/src/theme_selector.rs                                             |  295 
crates/ui/Cargo.toml                                                                     |   14 
crates/ui/docs/building-ui.md                                                            |    0 
crates/ui/docs/hello-world.md                                                            |    2 
crates/ui/docs/todo.md                                                                   |    0 
crates/ui/src/clickable.rs                                                               |    0 
crates/ui/src/components.rs                                                              |    0 
crates/ui/src/components/avatar.rs                                                       |    0 
crates/ui/src/components/button.rs                                                       |    0 
crates/ui/src/components/button/button.rs                                                |    0 
crates/ui/src/components/button/button_icon.rs                                           |    0 
crates/ui/src/components/button/button_like.rs                                           |    0 
crates/ui/src/components/button/icon_button.rs                                           |    0 
crates/ui/src/components/button/toggle_button.rs                                         |    0 
crates/ui/src/components/checkbox.rs                                                     |    0 
crates/ui/src/components/context_menu.rs                                                 |    0 
crates/ui/src/components/disclosure.rs                                                   |    0 
crates/ui/src/components/divider.rs                                                      |    0 
crates/ui/src/components/icon.rs                                                         |    0 
crates/ui/src/components/indicator.rs                                                    |    0 
crates/ui/src/components/keybinding.rs                                                   |    0 
crates/ui/src/components/label.rs                                                        |    0 
crates/ui/src/components/label/highlighted_label.rs                                      |    0 
crates/ui/src/components/label/label.rs                                                  |    0 
crates/ui/src/components/label/label_like.rs                                             |    0 
crates/ui/src/components/list.rs                                                         |    0 
crates/ui/src/components/list/list.rs                                                    |    0 
crates/ui/src/components/list/list_header.rs                                             |    0 
crates/ui/src/components/list/list_item.rs                                               |    0 
crates/ui/src/components/list/list_separator.rs                                          |    0 
crates/ui/src/components/list/list_sub_header.rs                                         |    0 
crates/ui/src/components/popover.rs                                                      |    8 
crates/ui/src/components/popover_menu.rs                                                 |    0 
crates/ui/src/components/right_click_menu.rs                                             |    0 
crates/ui/src/components/stack.rs                                                        |    0 
crates/ui/src/components/stories.rs                                                      |    0 
crates/ui/src/components/stories/avatar.rs                                               |    0 
crates/ui/src/components/stories/button.rs                                               |    0 
crates/ui/src/components/stories/checkbox.rs                                             |    0 
crates/ui/src/components/stories/context_menu.rs                                         |    0 
crates/ui/src/components/stories/disclosure.rs                                           |    0 
crates/ui/src/components/stories/icon.rs                                                 |    0 
crates/ui/src/components/stories/icon_button.rs                                          |    0 
crates/ui/src/components/stories/keybinding.rs                                           |    0 
crates/ui/src/components/stories/label.rs                                                |    0 
crates/ui/src/components/stories/list.rs                                                 |    0 
crates/ui/src/components/stories/list_header.rs                                          |    0 
crates/ui/src/components/stories/list_item.rs                                            |    0 
crates/ui/src/components/stories/tab.rs                                                  |    0 
crates/ui/src/components/stories/tab_bar.rs                                              |    0 
crates/ui/src/components/stories/toggle_button.rs                                        |    0 
crates/ui/src/components/tab.rs                                                          |    0 
crates/ui/src/components/tab_bar.rs                                                      |    0 
crates/ui/src/components/tooltip.rs                                                      |    0 
crates/ui/src/disableable.rs                                                             |    0 
crates/ui/src/fixed.rs                                                                   |    0 
crates/ui/src/prelude.rs                                                                 |    0 
crates/ui/src/selectable.rs                                                              |    0 
crates/ui/src/styled_ext.rs                                                              |    6 
crates/ui/src/styles.rs                                                                  |    0 
crates/ui/src/styles/color.rs                                                            |    0 
crates/ui/src/styles/docs/elevation.md                                                   |    0 
crates/ui/src/styles/elevation.rs                                                        |    0 
crates/ui/src/styles/typography.rs                                                       |    0 
crates/ui/src/styles/units.rs                                                            |    0 
crates/ui/src/ui.rs                                                                      |    0 
crates/ui/src/utils.rs                                                                   |    0 
crates/ui/src/utils/format_distance.rs                                                   |    4 
crates/ui/src/visible_on_hover.rs                                                        |    0 
crates/util/Cargo.toml                                                                   |    6 
crates/util/src/util.rs                                                                  |   20 
crates/vcs_menu/Cargo.toml                                                               |    6 
crates/vcs_menu/src/lib.rs                                                               |  293 
crates/vcs_menu2/Cargo.toml                                                              |   17 
crates/vcs_menu2/src/lib.rs                                                              |  358 
crates/vim/Cargo.toml                                                                    |    4 
crates/vim/src/command.rs                                                                |   20 
crates/vim/src/editor_events.rs                                                          |  114 
crates/vim/src/insert.rs                                                                 |   44 
crates/vim/src/mode_indicator.rs                                                         |   87 
crates/vim/src/motion.rs                                                                 |   97 
crates/vim/src/normal.rs                                                                 |   50 
crates/vim/src/normal/increment.rs                                                       |    8 
crates/vim/src/normal/paste.rs                                                           |    6 
crates/vim/src/normal/repeat.rs                                                          |  134 
crates/vim/src/normal/scroll.rs                                                          |   59 
crates/vim/src/normal/search.rs                                                          |   72 
crates/vim/src/normal/substitute.rs                                                      |    8 
crates/vim/src/object.rs                                                                 |   39 
crates/vim/src/state.rs                                                                  |   18 
crates/vim/src/test.rs                                                                   |   23 
crates/vim/src/test/neovim_backed_test_context.rs                                        |   34 
crates/vim/src/test/neovim_connection.rs                                                 |   26 
crates/vim/src/test/vim_test_context.rs                                                  |   63 
crates/vim/src/vim.rs                                                                    |  171 
crates/vim/src/visual.rs                                                                 |   53 
crates/vim2/Cargo.toml                                                                   |   53 
crates/vim2/src/command.rs                                                               |  434 
crates/vim2/src/editor_events.rs                                                         |  104 
crates/vim2/src/insert.rs                                                                |  125 
crates/vim2/src/mode_indicator.rs                                                        |   74 
crates/vim2/src/motion.rs                                                                | 1107 
crates/vim2/src/normal.rs                                                                |  910 
crates/vim2/src/normal/case.rs                                                           |  116 
crates/vim2/src/normal/change.rs                                                         |  502 
crates/vim2/src/normal/delete.rs                                                         |  475 
crates/vim2/src/normal/increment.rs                                                      |  278 
crates/vim2/src/normal/paste.rs                                                          |  476 
crates/vim2/src/normal/repeat.rs                                                         |  496 
crates/vim2/src/normal/scroll.rs                                                         |  247 
crates/vim2/src/normal/search.rs                                                         |  477 
crates/vim2/src/normal/substitute.rs                                                     |  276 
crates/vim2/src/normal/yank.rs                                                           |   50 
crates/vim2/src/object.rs                                                                | 1025 
crates/vim2/src/state.rs                                                                 |  234 
crates/vim2/src/test.rs                                                                  |  752 
crates/vim2/src/test/neovim_backed_binding_test_context.rs                               |   95 
crates/vim2/src/test/neovim_backed_test_context.rs                                       |  439 
crates/vim2/src/test/neovim_connection.rs                                                |  599 
crates/vim2/src/test/vim_test_context.rs                                                 |  177 
crates/vim2/src/utils.rs                                                                 |   50 
crates/vim2/src/vim.rs                                                                   |  607 
crates/vim2/src/visual.rs                                                                | 1034 
crates/vim2/test_data/neovim_backed_test_context_works.json                              |    3 
crates/vim2/test_data/test_a.json                                                        |    6 
crates/vim2/test_data/test_b.json                                                        |   54 
crates/vim2/test_data/test_backspace.json                                                |    9 
crates/vim2/test_data/test_capital_f_and_capital_t.json                                  |  570 
crates/vim2/test_data/test_cc.json                                                       |   24 
crates/vim2/test_data/test_change_0.json                                                 |    8 
crates/vim2/test_data/test_change_b.json                                                 |   24 
crates/vim2/test_data/test_change_backspace.json                                         |   16 
crates/vim2/test_data/test_change_case.json                                              |   23 
crates/vim2/test_data/test_change_e.json                                                 |   24 
crates/vim2/test_data/test_change_end_of_document.json                                   |   16 
crates/vim2/test_data/test_change_end_of_line.json                                       |    8 
crates/vim2/test_data/test_change_gg.json                                                |   20 
1,000 files changed, 25,422 insertions(+), 269,506 deletions(-)

Detailed changes

.cargo/config.toml 🔗

@@ -1,6 +1,3 @@
-[alias]
-xtask = "run --package xtask --"
-
 [build]
 # v0 mangling scheme provides more detailed backtraces around closures
 rustflags = ["-C", "symbol-mangling-version=v0"]

.github/workflows/release_nightly.yml 🔗

@@ -92,7 +92,7 @@ jobs:
               run: script/generate-licenses
 
             - name: Create app bundle
-              run: script/bundle -2
+              run: script/bundle
 
             - name: Upload Zed Nightly
               run: script/upload-nightly

Cargo.lock 🔗

@@ -6,6 +6,7 @@ version = 3
 name = "activity_indicator"
 version = "0.1.0"
 dependencies = [
+ "anyhow",
  "auto_update",
  "editor",
  "futures 0.3.28",
@@ -15,45 +16,18 @@ dependencies = [
  "settings",
  "smallvec",
  "theme",
+ "ui",
  "util",
  "workspace",
 ]
 
-[[package]]
-name = "activity_indicator2"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "auto_update2",
- "editor2",
- "futures 0.3.28",
- "gpui2",
- "language2",
- "project2",
- "settings2",
- "smallvec",
- "theme2",
- "ui2",
- "util",
- "workspace2",
-]
-
-[[package]]
-name = "addr2line"
-version = "0.17.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
-dependencies = [
- "gimli 0.26.2",
-]
-
 [[package]]
 name = "addr2line"
 version = "0.21.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
 dependencies = [
- "gimli 0.28.0",
+ "gimli",
 ]
 
 [[package]]
@@ -127,33 +101,6 @@ dependencies = [
  "util",
 ]
 
-[[package]]
-name = "ai2"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "async-trait",
- "bincode",
- "futures 0.3.28",
- "gpui2",
- "isahc",
- "language2",
- "lazy_static",
- "log",
- "matrixmultiply",
- "ordered-float 2.10.0",
- "parking_lot 0.11.2",
- "parse_duration",
- "postage",
- "rand 0.8.5",
- "regex",
- "rusqlite",
- "serde",
- "serde_json",
- "tiktoken-rs",
- "util",
-]
-
 [[package]]
 name = "alacritty_config"
 version = "0.1.2-dev"
@@ -237,12 +184,6 @@ dependencies = [
  "pkg-config",
 ]
 
-[[package]]
-name = "ambient-authority"
-version = "0.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec8ad6edb4840b78c5c3d88de606b22252d552b55f3a4699fbb10fc070ec3049"
-
 [[package]]
 name = "android-tzdata"
 version = "0.1.1"
@@ -368,7 +309,7 @@ dependencies = [
  "collections",
  "ctor",
  "editor",
- "env_logger 0.9.3",
+ "env_logger",
  "fs",
  "futures 0.3.28",
  "gpui",
@@ -392,52 +333,12 @@ dependencies = [
  "smol",
  "theme",
  "tiktoken-rs",
+ "ui",
  "util",
  "uuid 1.4.1",
  "workspace",
 ]
 
-[[package]]
-name = "assistant2"
-version = "0.1.0"
-dependencies = [
- "ai2",
- "anyhow",
- "chrono",
- "client2",
- "collections",
- "ctor",
- "editor2",
- "env_logger 0.9.3",
- "fs2",
- "futures 0.3.28",
- "gpui2",
- "indoc",
- "isahc",
- "language2",
- "log",
- "menu2",
- "multi_buffer2",
- "ordered-float 2.10.0",
- "parking_lot 0.11.2",
- "project2",
- "rand 0.8.5",
- "regex",
- "schemars",
- "search2",
- "semantic_index2",
- "serde",
- "serde_json",
- "settings2",
- "smol",
- "theme2",
- "tiktoken-rs",
- "ui2",
- "util",
- "uuid 1.4.1",
- "workspace2",
-]
-
 [[package]]
 name = "async-broadcast"
 version = "0.4.1"
@@ -757,24 +658,11 @@ dependencies = [
 [[package]]
 name = "audio"
 version = "0.1.0"
-dependencies = [
- "anyhow",
- "collections",
- "gpui",
- "log",
- "parking_lot 0.11.2",
- "rodio",
- "util",
-]
-
-[[package]]
-name = "audio2"
-version = "0.1.0"
 dependencies = [
  "anyhow",
  "collections",
  "futures 0.3.28",
- "gpui2",
+ "gpui",
  "log",
  "parking_lot 0.11.2",
  "rodio",
@@ -805,30 +693,6 @@ dependencies = [
  "workspace",
 ]
 
-[[package]]
-name = "auto_update2"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "client2",
- "db2",
- "gpui2",
- "isahc",
- "lazy_static",
- "log",
- "menu2",
- "project2",
- "serde",
- "serde_derive",
- "serde_json",
- "settings2",
- "smol",
- "tempdir",
- "theme2",
- "util",
- "workspace2",
-]
-
 [[package]]
 name = "autocfg"
 version = "1.1.0"
@@ -913,12 +777,12 @@ version = "0.3.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
 dependencies = [
- "addr2line 0.21.0",
+ "addr2line",
  "cc",
  "cfg-if 1.0.0",
  "libc",
  "miniz_oxide 0.7.1",
- "object 0.32.1",
+ "object",
  "rustc-demangle",
 ]
 
@@ -1154,27 +1018,10 @@ dependencies = [
  "search",
  "settings",
  "theme",
+ "ui",
  "workspace",
 ]
 
-[[package]]
-name = "breadcrumbs2"
-version = "0.1.0"
-dependencies = [
- "collections",
- "editor2",
- "gpui2",
- "itertools 0.10.5",
- "language2",
- "outline2",
- "project2",
- "search2",
- "settings2",
- "theme2",
- "ui2",
- "workspace2",
-]
-
 [[package]]
 name = "bromberg_sl2"
 version = "0.6.0"
@@ -1265,6 +1112,7 @@ dependencies = [
  "fs",
  "futures 0.3.28",
  "gpui",
+ "image",
  "language",
  "live_kit_client",
  "log",
@@ -1276,102 +1124,10 @@ dependencies = [
  "serde_derive",
  "serde_json",
  "settings",
- "util",
-]
-
-[[package]]
-name = "call2"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "async-broadcast",
- "audio2",
- "client2",
- "collections",
- "fs2",
- "futures 0.3.28",
- "gpui2",
- "image",
- "language2",
- "live_kit_client2",
- "log",
- "media",
- "postage",
- "project2",
- "schemars",
- "serde",
- "serde_derive",
- "serde_json",
- "settings2",
  "smallvec",
  "util",
 ]
 
-[[package]]
-name = "cap-fs-ext"
-version = "0.26.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b0e103ce36d217d568903ad27b14ec2238ecb5d65bad2e756a8f3c0d651506e"
-dependencies = [
- "cap-primitives",
- "cap-std",
- "io-lifetimes 0.7.5",
- "windows-sys 0.36.1",
-]
-
-[[package]]
-name = "cap-primitives"
-version = "0.26.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af3f336aa91cce16033ed3c94ac91d98956c49b420e6d6cd0dd7d0e386a57085"
-dependencies = [
- "ambient-authority",
- "fs-set-times",
- "io-extras",
- "io-lifetimes 0.7.5",
- "ipnet",
- "maybe-owned",
- "rustix 0.35.16",
- "winapi-util",
- "windows-sys 0.36.1",
- "winx",
-]
-
-[[package]]
-name = "cap-rand"
-version = "0.26.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d14b9606aa9550d34651bc481443203bc014237bdb992d201d2afa62d2ec6dea"
-dependencies = [
- "ambient-authority",
- "rand 0.8.5",
-]
-
-[[package]]
-name = "cap-std"
-version = "0.26.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9d6e70b626eceac9d6fc790fe2d72cc3f2f7bc3c35f467690c54a526b0f56db"
-dependencies = [
- "cap-primitives",
- "io-extras",
- "io-lifetimes 0.7.5",
- "ipnet",
- "rustix 0.35.16",
-]
-
-[[package]]
-name = "cap-time-ext"
-version = "0.26.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3a0524f7c4cff2ea547ae2b652bf7a348fd3e48f76556dc928d8b45ab2f1d50"
-dependencies = [
- "cap-primitives",
- "once_cell",
- "rustix 0.35.16",
- "winx",
-]
-
 [[package]]
 name = "castaway"
 version = "0.1.2"
@@ -1470,43 +1226,6 @@ dependencies = [
  "uuid 1.4.1",
 ]
 
-[[package]]
-name = "channel2"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "client2",
- "clock",
- "collections",
- "db2",
- "feature_flags2",
- "futures 0.3.28",
- "gpui2",
- "image",
- "language2",
- "lazy_static",
- "log",
- "parking_lot 0.11.2",
- "postage",
- "rand 0.8.5",
- "rpc2",
- "schemars",
- "serde",
- "serde_derive",
- "settings2",
- "smallvec",
- "smol",
- "sum_tree",
- "tempfile",
- "text2",
- "thiserror",
- "time",
- "tiny_http",
- "url",
- "util",
- "uuid 1.4.1",
-]
-
 [[package]]
 name = "chrono"
 version = "0.4.31"
@@ -1680,43 +1399,6 @@ dependencies = [
  "uuid 1.4.1",
 ]
 
-[[package]]
-name = "client2"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "async-recursion 0.3.2",
- "async-tungstenite",
- "chrono",
- "collections",
- "db2",
- "feature_flags2",
- "futures 0.3.28",
- "gpui2",
- "image",
- "lazy_static",
- "log",
- "parking_lot 0.11.2",
- "postage",
- "rand 0.8.5",
- "rpc2",
- "schemars",
- "serde",
- "serde_derive",
- "settings2",
- "smol",
- "sum_tree",
- "sysinfo",
- "tempfile",
- "text2",
- "thiserror",
- "time",
- "tiny_http",
- "url",
- "util",
- "uuid 1.4.1",
-]
-
 [[package]]
 name = "clock"
 version = "0.1.0"
@@ -1764,7 +1446,7 @@ dependencies = [
 
 [[package]]
 name = "collab"
-version = "0.32.0"
+version = "0.28.0"
 dependencies = [
  "anyhow",
  "async-trait",
@@ -1783,7 +1465,7 @@ dependencies = [
  "ctor",
  "dashmap",
  "editor",
- "env_logger 0.9.3",
+ "env_logger",
  "envy",
  "fs",
  "futures 0.3.28",
@@ -1836,111 +1518,36 @@ dependencies = [
 ]
 
 [[package]]
-name = "collab2"
-version = "0.28.0"
+name = "collab_ui"
+version = "0.1.0"
 dependencies = [
  "anyhow",
- "async-trait",
- "async-tungstenite",
- "audio2",
- "axum",
- "axum-extra",
- "base64 0.13.1",
- "call2",
- "channel2",
- "clap 3.2.25",
- "client2",
+ "auto_update",
+ "call",
+ "channel",
+ "client",
  "clock",
- "collab_ui2",
  "collections",
- "ctor",
- "dashmap",
- "editor2",
- "env_logger 0.9.3",
- "envy",
- "fs2",
+ "db",
+ "editor",
+ "feature_flags",
+ "feedback",
  "futures 0.3.28",
- "git3",
- "gpui2",
- "hyper",
- "indoc",
- "language2",
+ "fuzzy",
+ "gpui",
+ "language",
  "lazy_static",
- "lipsum",
- "live_kit_client2",
- "live_kit_server",
  "log",
- "lsp2",
- "nanoid",
- "node_runtime",
- "notifications2",
- "parking_lot 0.11.2",
+ "menu",
+ "notifications",
+ "picker",
+ "postage",
  "pretty_assertions",
- "project2",
- "prometheus",
- "prost 0.8.0",
- "rand 0.8.5",
- "reqwest",
- "rpc2",
- "scrypt",
- "sea-orm",
- "serde",
- "serde_derive",
- "serde_json",
- "settings2",
- "sha-1 0.9.8",
- "smallvec",
- "sqlx",
- "text2",
- "theme2",
- "time",
- "tokio",
- "tokio-tungstenite",
- "toml 0.5.11",
- "tonic",
- "tower",
- "tracing",
- "tracing-log",
- "tracing-subscriber",
- "unindent",
- "util",
- "uuid 1.4.1",
- "workspace2",
-]
-
-[[package]]
-name = "collab_ui"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "auto_update",
- "call",
- "channel",
- "client",
- "clock",
- "collections",
- "context_menu",
- "db",
- "drag_and_drop",
- "editor",
- "feature_flags",
- "feedback",
- "futures 0.3.28",
- "fuzzy",
- "gpui",
- "language",
- "lazy_static",
- "log",
- "menu",
- "notifications",
- "picker",
- "postage",
- "pretty_assertions",
- "project",
- "recent_projects",
- "rich_text",
- "rpc",
- "schemars",
+ "project",
+ "recent_projects",
+ "rich_text",
+ "rpc",
+ "schemars",
  "serde",
  "serde_derive",
  "settings",
@@ -1949,56 +1556,11 @@ dependencies = [
  "theme_selector",
  "time",
  "tree-sitter-markdown",
+ "ui",
  "util",
  "vcs_menu",
  "workspace",
- "zed-actions",
-]
-
-[[package]]
-name = "collab_ui2"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "auto_update2",
- "call2",
- "channel2",
- "client2",
- "clock",
- "collections",
- "db2",
- "editor2",
- "feature_flags2",
- "feedback2",
- "futures 0.3.28",
- "fuzzy2",
- "gpui2",
- "language2",
- "lazy_static",
- "log",
- "menu2",
- "notifications2",
- "picker2",
- "postage",
- "pretty_assertions",
- "project2",
- "recent_projects2",
- "rich_text2",
- "rpc2",
- "schemars",
- "serde",
- "serde_derive",
- "settings2",
- "smallvec",
- "theme2",
- "theme_selector2",
- "time",
- "tree-sitter-markdown",
- "ui2",
- "util",
- "vcs_menu2",
- "workspace2",
- "zed_actions2",
+ "zed_actions",
 ]
 
 [[package]]
@@ -2034,60 +1596,26 @@ dependencies = [
 name = "command_palette"
 version = "0.1.0"
 dependencies = [
+ "anyhow",
  "collections",
  "ctor",
  "editor",
- "env_logger 0.9.3",
+ "env_logger",
  "fuzzy",
+ "go_to_line",
  "gpui",
  "language",
+ "menu",
  "picker",
  "project",
- "serde_json",
- "settings",
- "theme",
- "util",
- "workspace",
- "zed-actions",
-]
-
-[[package]]
-name = "command_palette2"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "collections",
- "ctor",
- "editor2",
- "env_logger 0.9.3",
- "fuzzy2",
- "go_to_line2",
- "gpui2",
- "language2",
- "menu2",
- "picker2",
- "project2",
  "serde",
  "serde_json",
- "settings2",
- "theme2",
- "ui2",
- "util",
- "workspace2",
- "zed_actions2",
-]
-
-[[package]]
-name = "component_test"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "gpui",
- "project",
  "settings",
  "theme",
+ "ui",
  "util",
  "workspace",
+ "zed_actions",
 ]
 
 [[package]]
@@ -2124,17 +1652,6 @@ version = "0.9.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f"
 
-[[package]]
-name = "context_menu"
-version = "0.1.0"
-dependencies = [
- "gpui",
- "menu",
- "settings",
- "smallvec",
- "theme",
-]
-
 [[package]]
 name = "convert_case"
 version = "0.4.0"
@@ -2159,7 +1676,6 @@ dependencies = [
  "async-tar",
  "clock",
  "collections",
- "context_menu",
  "fs",
  "futures 0.3.28",
  "gpui",
@@ -2174,33 +1690,7 @@ dependencies = [
  "settings",
  "smol",
  "theme",
- "util",
-]
-
-[[package]]
-name = "copilot2"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "async-compression",
- "async-tar",
- "clock",
- "collections",
- "fs",
- "futures 0.3.28",
- "gpui2",
- "language2",
- "log",
- "lsp2",
- "node_runtime",
- "parking_lot 0.11.2",
- "rpc2",
- "serde",
- "serde_derive",
- "settings2",
- "smol",
- "theme2",
- "ui2",
+ "ui",
  "util",
 ]
 
@@ -2209,7 +1699,6 @@ name = "copilot_button"
 version = "0.1.0"
 dependencies = [
  "anyhow",
- "context_menu",
  "copilot",
  "editor",
  "fs",
@@ -2221,25 +1710,7 @@ dependencies = [
  "theme",
  "util",
  "workspace",
-]
-
-[[package]]
-name = "copilot_button2"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "copilot2",
- "editor2",
- "fs2",
- "futures 0.3.28",
- "gpui2",
- "language2",
- "settings2",
- "smol",
- "theme2",
- "util",
- "workspace2",
- "zed_actions2",
+ "zed_actions",
 ]
 
 [[package]]
@@ -2352,15 +1823,6 @@ dependencies = [
  "windows 0.46.0",
 ]
 
-[[package]]
-name = "cpp_demangle"
-version = "0.3.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eeaa953eaad386a53111e47172c2fedba671e5684c8dd601a5f474f4f118710f"
-dependencies = [
- "cfg-if 1.0.0",
-]
-
 [[package]]
 name = "cpufeatures"
 version = "0.2.9"
@@ -2370,41 +1832,12 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "cranelift-bforest"
-version = "0.89.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "593b398dd0c5b1e2e3a9c3dae8584e287894ea84e361949ad506376e99196265"
-dependencies = [
- "cranelift-entity 0.89.2",
-]
-
 [[package]]
 name = "cranelift-bforest"
 version = "0.103.0"
 source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7"
 dependencies = [
- "cranelift-entity 0.103.0",
-]
-
-[[package]]
-name = "cranelift-codegen"
-version = "0.89.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "afc0d8faabd099ea15ab33d49d150e5572c04cfeb95d675fd41286739b754629"
-dependencies = [
- "arrayvec 0.7.4",
- "bumpalo",
- "cranelift-bforest 0.89.2",
- "cranelift-codegen-meta 0.89.2",
- "cranelift-codegen-shared 0.89.2",
- "cranelift-entity 0.89.2",
- "cranelift-isle 0.89.2",
- "gimli 0.26.2",
- "log",
- "regalloc2 0.4.2",
- "smallvec",
- "target-lexicon",
+ "cranelift-entity",
 ]
 
 [[package]]
@@ -2413,43 +1846,28 @@ version = "0.103.0"
 source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7"
 dependencies = [
  "bumpalo",
- "cranelift-bforest 0.103.0",
- "cranelift-codegen-meta 0.103.0",
- "cranelift-codegen-shared 0.103.0",
+ "cranelift-bforest",
+ "cranelift-codegen-meta",
+ "cranelift-codegen-shared",
  "cranelift-control",
- "cranelift-entity 0.103.0",
- "cranelift-isle 0.103.0",
- "gimli 0.28.0",
+ "cranelift-entity",
+ "cranelift-isle",
+ "gimli",
  "hashbrown 0.14.0",
  "log",
- "regalloc2 0.9.3",
+ "regalloc2",
  "smallvec",
  "target-lexicon",
 ]
 
-[[package]]
-name = "cranelift-codegen-meta"
-version = "0.89.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ac1669e42579476f001571d6ba4b825fac686282c97b88b18f8e34242066a81"
-dependencies = [
- "cranelift-codegen-shared 0.89.2",
-]
-
 [[package]]
 name = "cranelift-codegen-meta"
 version = "0.103.0"
 source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7"
 dependencies = [
- "cranelift-codegen-shared 0.103.0",
+ "cranelift-codegen-shared",
 ]
 
-[[package]]
-name = "cranelift-codegen-shared"
-version = "0.89.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2a1b1eef9640ab72c1e7b583ac678083855a509da34b4b4378bd99954127c20"
-
 [[package]]
 name = "cranelift-codegen-shared"
 version = "0.103.0"
@@ -2463,15 +1881,6 @@ dependencies = [
  "arbitrary",
 ]
 
-[[package]]
-name = "cranelift-entity"
-version = "0.89.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eea4e17c3791fd8134640b26242a9ddbd7c67db78f0bad98cb778bf563ef81a0"
-dependencies = [
- "serde",
-]
-
 [[package]]
 name = "cranelift-entity"
 version = "0.103.0"
@@ -2481,90 +1890,45 @@ dependencies = [
  "serde_derive",
 ]
 
-[[package]]
-name = "cranelift-frontend"
-version = "0.89.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fca1474b5302348799656d43a40eacd716a3b46169405a3af812832c9edf77b4"
-dependencies = [
- "cranelift-codegen 0.89.2",
- "log",
- "smallvec",
- "target-lexicon",
-]
-
 [[package]]
 name = "cranelift-frontend"
 version = "0.103.0"
 source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7"
 dependencies = [
- "cranelift-codegen 0.103.0",
+ "cranelift-codegen",
  "log",
  "smallvec",
  "target-lexicon",
 ]
 
-[[package]]
-name = "cranelift-isle"
-version = "0.89.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77aa537f020ea43483100153278e7215d41695bdcef9eea6642d122675f64249"
-
 [[package]]
 name = "cranelift-isle"
 version = "0.103.0"
 source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7"
 
-[[package]]
-name = "cranelift-native"
-version = "0.89.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8bdc6b65241a95b7d8eafbf4e114c082e49b80162a2dcd9c6bcc5989c3310c9e"
-dependencies = [
- "cranelift-codegen 0.89.2",
- "libc",
- "target-lexicon",
-]
-
 [[package]]
 name = "cranelift-native"
 version = "0.103.0"
 source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7"
 dependencies = [
- "cranelift-codegen 0.103.0",
+ "cranelift-codegen",
  "libc",
  "target-lexicon",
 ]
 
-[[package]]
-name = "cranelift-wasm"
-version = "0.89.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4eb6359f606a1c80ccaa04fae9dbbb504615ec7a49b6c212b341080fff7a65dd"
-dependencies = [
- "cranelift-codegen 0.89.2",
- "cranelift-entity 0.89.2",
- "cranelift-frontend 0.89.2",
- "itertools 0.10.5",
- "log",
- "smallvec",
- "wasmparser 0.92.0",
- "wasmtime-types 2.0.2",
-]
-
 [[package]]
 name = "cranelift-wasm"
 version = "0.103.0"
 source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7"
 dependencies = [
- "cranelift-codegen 0.103.0",
- "cranelift-entity 0.103.0",
- "cranelift-frontend 0.103.0",
+ "cranelift-codegen",
+ "cranelift-entity",
+ "cranelift-frontend",
  "itertools 0.10.5",
  "log",
  "smallvec",
- "wasmparser 0.118.1",
- "wasmtime-types 16.0.0",
+ "wasmparser",
+ "wasmtime-types",
 ]
 
 [[package]]

Cargo.toml 🔗

@@ -1,137 +1,89 @@
 [workspace]
 members = [
     "crates/activity_indicator",
-    "crates/activity_indicator2",
     "crates/ai",
     "crates/assistant",
-    "crates/assistant2",
     "crates/audio",
-    "crates/audio2",
     "crates/auto_update",
-    "crates/auto_update2",
     "crates/breadcrumbs",
-    "crates/breadcrumbs2",
     "crates/call",
-    "crates/call2",
     "crates/channel",
-    "crates/channel2",
     "crates/cli",
     "crates/client",
-    "crates/client2",
     "crates/clock",
     "crates/collab",
-    "crates/collab2",
     "crates/collab_ui",
-    "crates/collab_ui2",
     "crates/collections",
     "crates/command_palette",
-    "crates/command_palette2",
-    "crates/component_test",
-    "crates/context_menu",
     "crates/copilot",
-    "crates/copilot2",
     "crates/copilot_button",
     "crates/db",
-    "crates/db2",
     "crates/refineable",
     "crates/refineable/derive_refineable",
     "crates/diagnostics",
-    "crates/diagnostics2",
-    "crates/drag_and_drop",
     "crates/editor",
     "crates/feature_flags",
-    "crates/feature_flags2",
     "crates/feedback",
     "crates/file_finder",
     "crates/fs",
-    "crates/fs2",
     "crates/fsevent",
     "crates/fuzzy",
-    "crates/fuzzy2",
     "crates/git",
     "crates/go_to_line",
-    "crates/go_to_line2",
     "crates/gpui",
     "crates/gpui_macros",
-    "crates/gpui2",
-    "crates/gpui2_macros",
+    "crates/gpui",
+    "crates/gpui_macros",
     "crates/install_cli",
-    "crates/install_cli2",
     "crates/journal",
-    "crates/journal2",
+    "crates/journal",
     "crates/language",
-    "crates/language2",
     "crates/language_selector",
-    "crates/language_selector2",
     "crates/language_tools",
-    "crates/language_tools2",
     "crates/live_kit_client",
     "crates/live_kit_server",
     "crates/lsp",
-    "crates/lsp2",
     "crates/media",
     "crates/menu",
-    "crates/menu2",
     "crates/multi_buffer",
-    "crates/multi_buffer2",
     "crates/node_runtime",
     "crates/notifications",
-    "crates/notifications2",
     "crates/outline",
-    "crates/outline2",
     "crates/picker",
-    "crates/picker2",
     "crates/plugin",
     "crates/plugin_macros",
     "crates/prettier",
-    "crates/prettier2",
     "crates/project",
-    "crates/project2",
     "crates/project_panel",
-    "crates/project_panel2",
     "crates/project_symbols",
-    "crates/project_symbols2",
-    "crates/quick_action_bar2",
+    "crates/quick_action_bar",
     "crates/recent_projects",
-    "crates/recent_projects2",
     "crates/rope",
     "crates/rpc",
-    "crates/rpc2",
     "crates/search",
-    "crates/search2",
     "crates/semantic_index",
-    "crates/semantic_index2",
     "crates/settings",
-    "crates/settings2",
     "crates/snippet",
     "crates/sqlez",
     "crates/sqlez_macros",
     "crates/rich_text",
-    "crates/storybook2",
+    "crates/storybook",
     "crates/sum_tree",
     "crates/terminal",
-    "crates/terminal2",
-    "crates/terminal_view2",
+    "crates/terminal_view",
     "crates/text",
     "crates/theme",
-    "crates/theme2",
     "crates/theme_importer",
     "crates/theme_selector",
-    "crates/theme_selector2",
-    "crates/ui2",
+    "crates/ui",
     "crates/util",
     "crates/story",
     "crates/vim",
     "crates/vcs_menu",
-    "crates/vcs_menu2",
-    "crates/workspace2",
+    "crates/workspace",
     "crates/welcome",
-    "crates/welcome2",
-    "crates/xtask",
     "crates/zed",
-    "crates/zed2",
-    "crates/zed-actions",
-    "crates/zed_actions2"
+    "crates/zed_actions",
 ]
 default-members = ["crates/zed"]
 resolver = "2"

Procfile.zed2 🔗

@@ -1,4 +0,0 @@
-web: cd ../zed.dev && PORT=3000 npm run dev
-collab: cd crates/collab2 && RUST_LOG=${RUST_LOG:-warn,collab=info} cargo run serve
-livekit: livekit-server --dev
-postgrest: postgrest crates/collab2/admin_api.conf

crates/activity_indicator/Cargo.toml 🔗

@@ -15,10 +15,12 @@ language = { path = "../language" }
 gpui = { path = "../gpui" }
 project = { path = "../project" }
 settings = { path = "../settings" }
+ui = { path = "../ui" }
 util = { path = "../util" }
 theme = { path = "../theme" }
-workspace = { path = "../workspace" }
+workspace = { path = "../workspace", package = "workspace" }
 
+anyhow.workspace = true
 futures.workspace = true
 smallvec.workspace = true
 

crates/activity_indicator/src/activity_indicator.rs 🔗

@@ -2,19 +2,19 @@ use auto_update::{AutoUpdateStatus, AutoUpdater, DismissErrorMessage};
 use editor::Editor;
 use futures::StreamExt;
 use gpui::{
-    actions, anyhow,
-    elements::*,
-    platform::{CursorStyle, MouseButton},
-    AppContext, Entity, ModelHandle, View, ViewContext, ViewHandle,
+    actions, svg, AppContext, CursorStyle, EventEmitter, InteractiveElement as _, Model,
+    ParentElement as _, Render, SharedString, StatefulInteractiveElement, Styled, View,
+    ViewContext, VisualContext as _,
 };
 use language::{LanguageRegistry, LanguageServerBinaryStatus};
 use project::{LanguageServerProgress, Project};
 use smallvec::SmallVec;
 use std::{cmp::Reverse, fmt::Write, sync::Arc};
+use ui::prelude::*;
 use util::ResultExt;
 use workspace::{item::ItemHandle, StatusItemView, Workspace};
 
-actions!(lsp_status, [ShowErrorMessage]);
+actions!(activity_indicator, [ShowErrorMessage]);
 
 const DOWNLOAD_ICON: &str = "icons/download.svg";
 const WARNING_ICON: &str = "icons/warning.svg";
@@ -25,8 +25,8 @@ pub enum Event {
 
 pub struct ActivityIndicator {
     statuses: Vec<LspStatus>,
-    project: ModelHandle<Project>,
-    auto_updater: Option<ModelHandle<AutoUpdater>>,
+    project: Model<Project>,
+    auto_updater: Option<Model<AutoUpdater>>,
 }
 
 struct LspStatus {
@@ -47,20 +47,15 @@ struct Content {
     on_click: Option<Arc<dyn Fn(&mut ActivityIndicator, &mut ViewContext<ActivityIndicator>)>>,
 }
 
-pub fn init(cx: &mut AppContext) {
-    cx.add_action(ActivityIndicator::show_error_message);
-    cx.add_action(ActivityIndicator::dismiss_error_message);
-}
-
 impl ActivityIndicator {
     pub fn new(
         workspace: &mut Workspace,
         languages: Arc<LanguageRegistry>,
         cx: &mut ViewContext<Workspace>,
-    ) -> ViewHandle<ActivityIndicator> {
+    ) -> View<ActivityIndicator> {
         let project = workspace.project().clone();
         let auto_updater = AutoUpdater::get(cx);
-        let this = cx.add_view(|cx: &mut ViewContext<Self>| {
+        let this = cx.new_view(|cx: &mut ViewContext<Self>| {
             let mut status_events = languages.language_server_binary_statuses();
             cx.spawn(|this, mut cx| async move {
                 while let Some((language, event)) = status_events.next().await {
@@ -77,11 +72,13 @@ impl ActivityIndicator {
             })
             .detach();
             cx.observe(&project, |_, _, cx| cx.notify()).detach();
+
             if let Some(auto_updater) = auto_updater.as_ref() {
                 cx.observe(auto_updater, |_, _, cx| cx.notify()).detach();
             }
-            cx.observe_active_labeled_tasks(|_, cx| cx.notify())
-                .detach();
+
+            // cx.observe_active_labeled_tasks(|_, cx| cx.notify())
+            //     .detach();
 
             Self {
                 statuses: Default::default(),
@@ -89,6 +86,7 @@ impl ActivityIndicator {
                 auto_updater,
             }
         });
+
         cx.subscribe(&this, move |workspace, _, event, cx| match event {
             Event::ShowError { lsp_name, error } => {
                 if let Some(buffer) = project
@@ -104,7 +102,7 @@ impl ActivityIndicator {
                     });
                     workspace.add_item(
                         Box::new(
-                            cx.add_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx)),
+                            cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx)),
                         ),
                         cx,
                     );
@@ -290,71 +288,41 @@ impl ActivityIndicator {
             };
         }
 
-        if let Some(most_recent_active_task) = cx.active_labeled_tasks().last() {
-            return Content {
-                icon: None,
-                message: most_recent_active_task.to_string(),
-                on_click: None,
-            };
-        }
+        // todo!(show active tasks)
+        // if let Some(most_recent_active_task) = cx.active_labeled_tasks().last() {
+        //     return Content {
+        //         icon: None,
+        //         message: most_recent_active_task.to_string(),
+        //         on_click: None,
+        //     };
+        // }
 
         Default::default()
     }
 }
 
-impl Entity for ActivityIndicator {
-    type Event = Event;
-}
+impl EventEmitter<Event> for ActivityIndicator {}
 
-impl View for ActivityIndicator {
-    fn ui_name() -> &'static str {
-        "ActivityIndicator"
-    }
+impl Render for ActivityIndicator {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        let content = self.content_to_render(cx);
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let Content {
-            icon,
-            message,
-            on_click,
-        } = self.content_to_render(cx);
-
-        let mut element = MouseEventHandler::new::<Self, _>(0, cx, |state, cx| {
-            let theme = &theme::current(cx).workspace.status_bar.lsp_status;
-            let style = if state.hovered() && on_click.is_some() {
-                theme.hovered.as_ref().unwrap_or(&theme.default)
-            } else {
-                &theme.default
-            };
-            Flex::row()
-                .with_children(icon.map(|path| {
-                    Svg::new(path)
-                        .with_color(style.icon_color)
-                        .constrained()
-                        .with_width(style.icon_width)
-                        .contained()
-                        .with_margin_right(style.icon_spacing)
-                        .aligned()
-                        .into_any_named("activity-icon")
-                }))
-                .with_child(
-                    Text::new(message, style.message.clone())
-                        .with_soft_wrap(false)
-                        .aligned(),
-                )
-                .constrained()
-                .with_height(style.height)
-                .contained()
-                .with_style(style.container)
-                .aligned()
-        });
+        let mut result = h_stack()
+            .id("activity-indicator")
+            .on_action(cx.listener(Self::show_error_message))
+            .on_action(cx.listener(Self::dismiss_error_message));
 
-        if let Some(on_click) = on_click.clone() {
-            element = element
-                .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, this, cx| on_click(this, cx));
+        if let Some(on_click) = content.on_click {
+            result = result
+                .cursor(CursorStyle::PointingHand)
+                .on_click(cx.listener(move |this, _, cx| {
+                    on_click(this, cx);
+                }))
         }
 
-        element.into_any()
+        result
+            .children(content.icon.map(|icon| svg().path(icon)))
+            .child(Label::new(SharedString::from(content.message)).size(LabelSize::Small))
     }
 }
 

crates/activity_indicator2/Cargo.toml 🔗

@@ -1,28 +0,0 @@
-[package]
-name = "activity_indicator2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/activity_indicator.rs"
-doctest = false
-
-[dependencies]
-auto_update = { path = "../auto_update2", package = "auto_update2" }
-editor = { path = "../editor2", package = "editor2" }
-language = { path = "../language2", package = "language2" }
-gpui = { path = "../gpui2", package = "gpui2" }
-project = { path = "../project2", package = "project2" }
-settings = { path = "../settings2", package = "settings2" }
-ui = { path = "../ui2", package = "ui2" }
-util = { path = "../util" }
-theme = { path = "../theme2", package = "theme2" }
-workspace = { path = "../workspace2", package = "workspace2" }
-
-anyhow.workspace = true
-futures.workspace = true
-smallvec.workspace = true
-
-[dev-dependencies]
-editor = { path = "../editor2", package = "editor2", features = ["test-support"] }

crates/activity_indicator2/src/activity_indicator.rs 🔗

@@ -1,331 +0,0 @@
-use auto_update::{AutoUpdateStatus, AutoUpdater, DismissErrorMessage};
-use editor::Editor;
-use futures::StreamExt;
-use gpui::{
-    actions, svg, AppContext, CursorStyle, EventEmitter, InteractiveElement as _, Model,
-    ParentElement as _, Render, SharedString, StatefulInteractiveElement, Styled, View,
-    ViewContext, VisualContext as _,
-};
-use language::{LanguageRegistry, LanguageServerBinaryStatus};
-use project::{LanguageServerProgress, Project};
-use smallvec::SmallVec;
-use std::{cmp::Reverse, fmt::Write, sync::Arc};
-use ui::prelude::*;
-use util::ResultExt;
-use workspace::{item::ItemHandle, StatusItemView, Workspace};
-
-actions!(activity_indicator, [ShowErrorMessage]);
-
-const DOWNLOAD_ICON: &str = "icons/download.svg";
-const WARNING_ICON: &str = "icons/warning.svg";
-
-pub enum Event {
-    ShowError { lsp_name: Arc<str>, error: String },
-}
-
-pub struct ActivityIndicator {
-    statuses: Vec<LspStatus>,
-    project: Model<Project>,
-    auto_updater: Option<Model<AutoUpdater>>,
-}
-
-struct LspStatus {
-    name: Arc<str>,
-    status: LanguageServerBinaryStatus,
-}
-
-struct PendingWork<'a> {
-    language_server_name: &'a str,
-    progress_token: &'a str,
-    progress: &'a LanguageServerProgress,
-}
-
-#[derive(Default)]
-struct Content {
-    icon: Option<&'static str>,
-    message: String,
-    on_click: Option<Arc<dyn Fn(&mut ActivityIndicator, &mut ViewContext<ActivityIndicator>)>>,
-}
-
-impl ActivityIndicator {
-    pub fn new(
-        workspace: &mut Workspace,
-        languages: Arc<LanguageRegistry>,
-        cx: &mut ViewContext<Workspace>,
-    ) -> View<ActivityIndicator> {
-        let project = workspace.project().clone();
-        let auto_updater = AutoUpdater::get(cx);
-        let this = cx.new_view(|cx: &mut ViewContext<Self>| {
-            let mut status_events = languages.language_server_binary_statuses();
-            cx.spawn(|this, mut cx| async move {
-                while let Some((language, event)) = status_events.next().await {
-                    this.update(&mut cx, |this, cx| {
-                        this.statuses.retain(|s| s.name != language.name());
-                        this.statuses.push(LspStatus {
-                            name: language.name(),
-                            status: event,
-                        });
-                        cx.notify();
-                    })?;
-                }
-                anyhow::Ok(())
-            })
-            .detach();
-            cx.observe(&project, |_, _, cx| cx.notify()).detach();
-
-            if let Some(auto_updater) = auto_updater.as_ref() {
-                cx.observe(auto_updater, |_, _, cx| cx.notify()).detach();
-            }
-
-            // cx.observe_active_labeled_tasks(|_, cx| cx.notify())
-            //     .detach();
-
-            Self {
-                statuses: Default::default(),
-                project: project.clone(),
-                auto_updater,
-            }
-        });
-
-        cx.subscribe(&this, move |workspace, _, event, cx| match event {
-            Event::ShowError { lsp_name, error } => {
-                if let Some(buffer) = project
-                    .update(cx, |project, cx| project.create_buffer(error, None, cx))
-                    .log_err()
-                {
-                    buffer.update(cx, |buffer, cx| {
-                        buffer.edit(
-                            [(0..0, format!("Language server error: {}\n\n", lsp_name))],
-                            None,
-                            cx,
-                        );
-                    });
-                    workspace.add_item(
-                        Box::new(
-                            cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx)),
-                        ),
-                        cx,
-                    );
-                }
-            }
-        })
-        .detach();
-        this
-    }
-
-    fn show_error_message(&mut self, _: &ShowErrorMessage, cx: &mut ViewContext<Self>) {
-        self.statuses.retain(|status| {
-            if let LanguageServerBinaryStatus::Failed { error } = &status.status {
-                cx.emit(Event::ShowError {
-                    lsp_name: status.name.clone(),
-                    error: error.clone(),
-                });
-                false
-            } else {
-                true
-            }
-        });
-
-        cx.notify();
-    }
-
-    fn dismiss_error_message(&mut self, _: &DismissErrorMessage, cx: &mut ViewContext<Self>) {
-        if let Some(updater) = &self.auto_updater {
-            updater.update(cx, |updater, cx| {
-                updater.dismiss_error(cx);
-            });
-        }
-        cx.notify();
-    }
-
-    fn pending_language_server_work<'a>(
-        &self,
-        cx: &'a AppContext,
-    ) -> impl Iterator<Item = PendingWork<'a>> {
-        self.project
-            .read(cx)
-            .language_server_statuses()
-            .rev()
-            .filter_map(|status| {
-                if status.pending_work.is_empty() {
-                    None
-                } else {
-                    let mut pending_work = status
-                        .pending_work
-                        .iter()
-                        .map(|(token, progress)| PendingWork {
-                            language_server_name: status.name.as_str(),
-                            progress_token: token.as_str(),
-                            progress,
-                        })
-                        .collect::<SmallVec<[_; 4]>>();
-                    pending_work.sort_by_key(|work| Reverse(work.progress.last_update_at));
-                    Some(pending_work)
-                }
-            })
-            .flatten()
-    }
-
-    fn content_to_render(&mut self, cx: &mut ViewContext<Self>) -> Content {
-        // Show any language server has pending activity.
-        let mut pending_work = self.pending_language_server_work(cx);
-        if let Some(PendingWork {
-            language_server_name,
-            progress_token,
-            progress,
-        }) = pending_work.next()
-        {
-            let mut message = language_server_name.to_string();
-
-            message.push_str(": ");
-            if let Some(progress_message) = progress.message.as_ref() {
-                message.push_str(progress_message);
-            } else {
-                message.push_str(progress_token);
-            }
-
-            if let Some(percentage) = progress.percentage {
-                write!(&mut message, " ({}%)", percentage).unwrap();
-            }
-
-            let additional_work_count = pending_work.count();
-            if additional_work_count > 0 {
-                write!(&mut message, " + {} more", additional_work_count).unwrap();
-            }
-
-            return Content {
-                icon: None,
-                message,
-                on_click: None,
-            };
-        }
-
-        // Show any language server installation info.
-        let mut downloading = SmallVec::<[_; 3]>::new();
-        let mut checking_for_update = SmallVec::<[_; 3]>::new();
-        let mut failed = SmallVec::<[_; 3]>::new();
-        for status in &self.statuses {
-            let name = status.name.clone();
-            match status.status {
-                LanguageServerBinaryStatus::CheckingForUpdate => checking_for_update.push(name),
-                LanguageServerBinaryStatus::Downloading => downloading.push(name),
-                LanguageServerBinaryStatus::Failed { .. } => failed.push(name),
-                LanguageServerBinaryStatus::Downloaded | LanguageServerBinaryStatus::Cached => {}
-            }
-        }
-
-        if !downloading.is_empty() {
-            return Content {
-                icon: Some(DOWNLOAD_ICON),
-                message: format!(
-                    "Downloading {} language server{}...",
-                    downloading.join(", "),
-                    if downloading.len() > 1 { "s" } else { "" }
-                ),
-                on_click: None,
-            };
-        } else if !checking_for_update.is_empty() {
-            return Content {
-                icon: Some(DOWNLOAD_ICON),
-                message: format!(
-                    "Checking for updates to {} language server{}...",
-                    checking_for_update.join(", "),
-                    if checking_for_update.len() > 1 {
-                        "s"
-                    } else {
-                        ""
-                    }
-                ),
-                on_click: None,
-            };
-        } else if !failed.is_empty() {
-            return Content {
-                icon: Some(WARNING_ICON),
-                message: format!(
-                    "Failed to download {} language server{}. Click to show error.",
-                    failed.join(", "),
-                    if failed.len() > 1 { "s" } else { "" }
-                ),
-                on_click: Some(Arc::new(|this, cx| {
-                    this.show_error_message(&Default::default(), cx)
-                })),
-            };
-        }
-
-        // Show any application auto-update info.
-        if let Some(updater) = &self.auto_updater {
-            return match &updater.read(cx).status() {
-                AutoUpdateStatus::Checking => Content {
-                    icon: Some(DOWNLOAD_ICON),
-                    message: "Checking for Zed updates…".to_string(),
-                    on_click: None,
-                },
-                AutoUpdateStatus::Downloading => Content {
-                    icon: Some(DOWNLOAD_ICON),
-                    message: "Downloading Zed update…".to_string(),
-                    on_click: None,
-                },
-                AutoUpdateStatus::Installing => Content {
-                    icon: Some(DOWNLOAD_ICON),
-                    message: "Installing Zed update…".to_string(),
-                    on_click: None,
-                },
-                AutoUpdateStatus::Updated => Content {
-                    icon: None,
-                    message: "Click to restart and update Zed".to_string(),
-                    on_click: Some(Arc::new(|_, cx| {
-                        workspace::restart(&Default::default(), cx)
-                    })),
-                },
-                AutoUpdateStatus::Errored => Content {
-                    icon: Some(WARNING_ICON),
-                    message: "Auto update failed".to_string(),
-                    on_click: Some(Arc::new(|this, cx| {
-                        this.dismiss_error_message(&Default::default(), cx)
-                    })),
-                },
-                AutoUpdateStatus::Idle => Default::default(),
-            };
-        }
-
-        // todo!(show active tasks)
-        // if let Some(most_recent_active_task) = cx.active_labeled_tasks().last() {
-        //     return Content {
-        //         icon: None,
-        //         message: most_recent_active_task.to_string(),
-        //         on_click: None,
-        //     };
-        // }
-
-        Default::default()
-    }
-}
-
-impl EventEmitter<Event> for ActivityIndicator {}
-
-impl Render for ActivityIndicator {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        let content = self.content_to_render(cx);
-
-        let mut result = h_stack()
-            .id("activity-indicator")
-            .on_action(cx.listener(Self::show_error_message))
-            .on_action(cx.listener(Self::dismiss_error_message));
-
-        if let Some(on_click) = content.on_click {
-            result = result
-                .cursor(CursorStyle::PointingHand)
-                .on_click(cx.listener(move |this, _, cx| {
-                    on_click(this, cx);
-                }))
-        }
-
-        result
-            .children(content.icon.map(|icon| svg().path(icon)))
-            .child(Label::new(SharedString::from(content.message)).size(LabelSize::Small))
-    }
-}
-
-impl StatusItemView for ActivityIndicator {
-    fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, _: &mut ViewContext<Self>) {}
-}

crates/ai/src/auth.rs 🔗

@@ -9,7 +9,7 @@ pub enum ProviderCredential {
 
 pub trait CredentialProvider: Send + Sync {
     fn has_credentials(&self) -> bool;
-    fn retrieve_credentials(&self, cx: &AppContext) -> ProviderCredential;
-    fn save_credentials(&self, cx: &AppContext, credential: ProviderCredential);
-    fn delete_credentials(&self, cx: &AppContext);
+    fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential;
+    fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential);
+    fn delete_credentials(&self, cx: &mut AppContext);
 }

crates/ai/src/prompts/repository_context.rs 🔗

@@ -2,7 +2,7 @@ use crate::prompts::base::{PromptArguments, PromptTemplate};
 use std::fmt::Write;
 use std::{ops::Range, path::PathBuf};
 
-use gpui::{AsyncAppContext, ModelHandle};
+use gpui::{AsyncAppContext, Model};
 use language::{Anchor, Buffer};
 
 #[derive(Clone)]
@@ -13,8 +13,12 @@ pub struct PromptCodeSnippet {
 }
 
 impl PromptCodeSnippet {
-    pub fn new(buffer: ModelHandle<Buffer>, range: Range<Anchor>, cx: &AsyncAppContext) -> Self {
-        let (content, language_name, file_path) = buffer.read_with(cx, |buffer, _| {
+    pub fn new(
+        buffer: Model<Buffer>,
+        range: Range<Anchor>,
+        cx: &mut AsyncAppContext,
+    ) -> anyhow::Result<Self> {
+        let (content, language_name, file_path) = buffer.update(cx, |buffer, _| {
             let snapshot = buffer.snapshot();
             let content = snapshot.text_for_range(range.clone()).collect::<String>();
 
@@ -27,13 +31,13 @@ impl PromptCodeSnippet {
                 .and_then(|file| Some(file.path().to_path_buf()));
 
             (content, language_name, file_path)
-        });
+        })?;
 
-        PromptCodeSnippet {
+        anyhow::Ok(PromptCodeSnippet {
             path: file_path,
             language_name,
             content,
-        }
+        })
     }
 }
 

crates/ai/src/providers/open_ai/completion.rs 🔗

@@ -3,7 +3,7 @@ use futures::{
     future::BoxFuture, io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, FutureExt,
     Stream, StreamExt,
 };
-use gpui::{executor::Background, AppContext};
+use gpui::{AppContext, BackgroundExecutor};
 use isahc::{http::StatusCode, Request, RequestExt};
 use parking_lot::RwLock;
 use serde::{Deserialize, Serialize};
@@ -104,7 +104,7 @@ pub struct OpenAIResponseStreamEvent {
 
 pub async fn stream_completion(
     credential: ProviderCredential,
-    executor: Arc<Background>,
+    executor: BackgroundExecutor,
     request: Box<dyn CompletionRequest>,
 ) -> Result<impl Stream<Item = Result<OpenAIResponseStreamEvent>>> {
     let api_key = match credential {
@@ -197,11 +197,11 @@ pub async fn stream_completion(
 pub struct OpenAICompletionProvider {
     model: OpenAILanguageModel,
     credential: Arc<RwLock<ProviderCredential>>,
-    executor: Arc<Background>,
+    executor: BackgroundExecutor,
 }
 
 impl OpenAICompletionProvider {
-    pub fn new(model_name: &str, executor: Arc<Background>) -> Self {
+    pub fn new(model_name: &str, executor: BackgroundExecutor) -> Self {
         let model = OpenAILanguageModel::load(model_name);
         let credential = Arc::new(RwLock::new(ProviderCredential::NoCredentials));
         Self {
@@ -219,46 +219,45 @@ impl CredentialProvider for OpenAICompletionProvider {
             _ => false,
         }
     }
-    fn retrieve_credentials(&self, cx: &AppContext) -> ProviderCredential {
-        let mut credential = self.credential.write();
-        match *credential {
-            ProviderCredential::Credentials { .. } => {
-                return credential.clone();
-            }
+
+    fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential {
+        let existing_credential = self.credential.read().clone();
+        let retrieved_credential = match existing_credential {
+            ProviderCredential::Credentials { .. } => existing_credential.clone(),
             _ => {
-                if let Ok(api_key) = env::var("OPENAI_API_KEY") {
-                    *credential = ProviderCredential::Credentials { api_key };
-                } else if let Some((_, api_key)) = cx
-                    .platform()
-                    .read_credentials(OPENAI_API_URL)
-                    .log_err()
-                    .flatten()
+                if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() {
+                    ProviderCredential::Credentials { api_key }
+                } else if let Some(Some((_, api_key))) =
+                    cx.read_credentials(OPENAI_API_URL).log_err()
                 {
                     if let Some(api_key) = String::from_utf8(api_key).log_err() {
-                        *credential = ProviderCredential::Credentials { api_key };
+                        ProviderCredential::Credentials { api_key }
+                    } else {
+                        ProviderCredential::NoCredentials
                     }
                 } else {
-                };
+                    ProviderCredential::NoCredentials
+                }
             }
-        }
-
-        credential.clone()
+        };
+        *self.credential.write() = retrieved_credential.clone();
+        retrieved_credential
     }
 
-    fn save_credentials(&self, cx: &AppContext, credential: ProviderCredential) {
-        match credential.clone() {
+    fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential) {
+        *self.credential.write() = credential.clone();
+        let credential = credential.clone();
+        match credential {
             ProviderCredential::Credentials { api_key } => {
-                cx.platform()
-                    .write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes())
+                cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes())
                     .log_err();
             }
             _ => {}
         }
-
-        *self.credential.write() = credential;
     }
-    fn delete_credentials(&self, cx: &AppContext) {
-        cx.platform().delete_credentials(OPENAI_API_URL).log_err();
+
+    fn delete_credentials(&self, cx: &mut AppContext) {
+        cx.delete_credentials(OPENAI_API_URL).log_err();
         *self.credential.write() = ProviderCredential::NoCredentials;
     }
 }

crates/ai/src/providers/open_ai/embedding.rs 🔗

@@ -1,7 +1,7 @@
 use anyhow::{anyhow, Result};
 use async_trait::async_trait;
 use futures::AsyncReadExt;
-use gpui::executor::Background;
+use gpui::BackgroundExecutor;
 use gpui::{serde_json, AppContext};
 use isahc::http::StatusCode;
 use isahc::prelude::Configurable;
@@ -35,7 +35,7 @@ pub struct OpenAIEmbeddingProvider {
     model: OpenAILanguageModel,
     credential: Arc<RwLock<ProviderCredential>>,
     pub client: Arc<dyn HttpClient>,
-    pub executor: Arc<Background>,
+    pub executor: BackgroundExecutor,
     rate_limit_count_rx: watch::Receiver<Option<Instant>>,
     rate_limit_count_tx: Arc<Mutex<watch::Sender<Option<Instant>>>>,
 }
@@ -66,7 +66,7 @@ struct OpenAIEmbeddingUsage {
 }
 
 impl OpenAIEmbeddingProvider {
-    pub fn new(client: Arc<dyn HttpClient>, executor: Arc<Background>) -> Self {
+    pub fn new(client: Arc<dyn HttpClient>, executor: BackgroundExecutor) -> Self {
         let (rate_limit_count_tx, rate_limit_count_rx) = watch::channel_with(None);
         let rate_limit_count_tx = Arc::new(Mutex::new(rate_limit_count_tx));
 
@@ -153,46 +153,45 @@ impl CredentialProvider for OpenAIEmbeddingProvider {
             _ => false,
         }
     }
-    fn retrieve_credentials(&self, cx: &AppContext) -> ProviderCredential {
-        let mut credential = self.credential.write();
-        match *credential {
-            ProviderCredential::Credentials { .. } => {
-                return credential.clone();
-            }
+    fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential {
+        let existing_credential = self.credential.read().clone();
+
+        let retrieved_credential = match existing_credential {
+            ProviderCredential::Credentials { .. } => existing_credential.clone(),
             _ => {
-                if let Ok(api_key) = env::var("OPENAI_API_KEY") {
-                    *credential = ProviderCredential::Credentials { api_key };
-                } else if let Some((_, api_key)) = cx
-                    .platform()
-                    .read_credentials(OPENAI_API_URL)
-                    .log_err()
-                    .flatten()
+                if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() {
+                    ProviderCredential::Credentials { api_key }
+                } else if let Some(Some((_, api_key))) =
+                    cx.read_credentials(OPENAI_API_URL).log_err()
                 {
                     if let Some(api_key) = String::from_utf8(api_key).log_err() {
-                        *credential = ProviderCredential::Credentials { api_key };
+                        ProviderCredential::Credentials { api_key }
+                    } else {
+                        ProviderCredential::NoCredentials
                     }
                 } else {
-                };
+                    ProviderCredential::NoCredentials
+                }
             }
-        }
+        };
 
-        credential.clone()
+        *self.credential.write() = retrieved_credential.clone();
+        retrieved_credential
     }
 
-    fn save_credentials(&self, cx: &AppContext, credential: ProviderCredential) {
-        match credential.clone() {
+    fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential) {
+        *self.credential.write() = credential.clone();
+        match credential {
             ProviderCredential::Credentials { api_key } => {
-                cx.platform()
-                    .write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes())
+                cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes())
                     .log_err();
             }
             _ => {}
         }
-
-        *self.credential.write() = credential;
     }
-    fn delete_credentials(&self, cx: &AppContext) {
-        cx.platform().delete_credentials(OPENAI_API_URL).log_err();
+
+    fn delete_credentials(&self, cx: &mut AppContext) {
+        cx.delete_credentials(OPENAI_API_URL).log_err();
         *self.credential.write() = ProviderCredential::NoCredentials;
     }
 }

crates/ai/src/test.rs 🔗

@@ -104,11 +104,11 @@ impl CredentialProvider for FakeEmbeddingProvider {
     fn has_credentials(&self) -> bool {
         true
     }
-    fn retrieve_credentials(&self, _cx: &AppContext) -> ProviderCredential {
+    fn retrieve_credentials(&self, _cx: &mut AppContext) -> ProviderCredential {
         ProviderCredential::NotNeeded
     }
-    fn save_credentials(&self, _cx: &AppContext, _credential: ProviderCredential) {}
-    fn delete_credentials(&self, _cx: &AppContext) {}
+    fn save_credentials(&self, _cx: &mut AppContext, _credential: ProviderCredential) {}
+    fn delete_credentials(&self, _cx: &mut AppContext) {}
 }
 
 #[async_trait]
@@ -153,17 +153,10 @@ impl FakeCompletionProvider {
 
     pub fn send_completion(&self, completion: impl Into<String>) {
         let mut tx = self.last_completion_tx.lock();
-
-        println!("COMPLETION TX: {:?}", &tx);
-
-        let a = tx.as_mut().unwrap();
-        a.try_send(completion.into()).unwrap();
-
-        // tx.as_mut().unwrap().try_send(completion.into()).unwrap();
+        tx.as_mut().unwrap().try_send(completion.into()).unwrap();
     }
 
     pub fn finish_completion(&self) {
-        println!("FINISHING COMPLETION");
         self.last_completion_tx.lock().take().unwrap();
     }
 }
@@ -172,11 +165,11 @@ impl CredentialProvider for FakeCompletionProvider {
     fn has_credentials(&self) -> bool {
         true
     }
-    fn retrieve_credentials(&self, _cx: &AppContext) -> ProviderCredential {
+    fn retrieve_credentials(&self, _cx: &mut AppContext) -> ProviderCredential {
         ProviderCredential::NotNeeded
     }
-    fn save_credentials(&self, _cx: &AppContext, _credential: ProviderCredential) {}
-    fn delete_credentials(&self, _cx: &AppContext) {}
+    fn save_credentials(&self, _cx: &mut AppContext, _credential: ProviderCredential) {}
+    fn delete_credentials(&self, _cx: &mut AppContext) {}
 }
 
 impl CompletionProvider for FakeCompletionProvider {
@@ -188,10 +181,8 @@ impl CompletionProvider for FakeCompletionProvider {
         &self,
         _prompt: Box<dyn CompletionRequest>,
     ) -> BoxFuture<'static, anyhow::Result<BoxStream<'static, anyhow::Result<String>>>> {
-        println!("COMPLETING");
         let (tx, rx) = mpsc::channel(1);
         *self.last_completion_tx.lock() = Some(tx);
-        println!("TX: {:?}", *self.last_completion_tx.lock());
         async move { Ok(rx.map(|rx| Ok(rx)).boxed()) }.boxed()
     }
     fn box_clone(&self) -> Box<dyn CompletionProvider> {

crates/ai2/Cargo.toml 🔗

@@ -1,38 +0,0 @@
-[package]
-name = "ai2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/ai2.rs"
-doctest = false
-
-[features]
-test-support = []
-
-[dependencies]
-gpui = { package = "gpui2", path = "../gpui2" }
-util = { path = "../util" }
-language = { package = "language2", path = "../language2" }
-async-trait.workspace = true
-anyhow.workspace = true
-futures.workspace = true
-lazy_static.workspace = true
-ordered-float.workspace = true
-parking_lot.workspace = true
-isahc.workspace = true
-regex.workspace = true
-serde.workspace = true
-serde_json.workspace = true
-postage.workspace = true
-rand.workspace = true
-log.workspace = true
-parse_duration = "2.1.1"
-tiktoken-rs.workspace = true
-matrixmultiply = "0.3.7"
-rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] }
-bincode = "1.3.3"
-
-[dev-dependencies]
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }

crates/ai2/src/ai2.rs 🔗

@@ -1,8 +0,0 @@
-pub mod auth;
-pub mod completion;
-pub mod embedding;
-pub mod models;
-pub mod prompts;
-pub mod providers;
-#[cfg(any(test, feature = "test-support"))]
-pub mod test;

crates/ai2/src/auth.rs 🔗

@@ -1,15 +0,0 @@
-use gpui::AppContext;
-
-#[derive(Clone, Debug)]
-pub enum ProviderCredential {
-    Credentials { api_key: String },
-    NoCredentials,
-    NotNeeded,
-}
-
-pub trait CredentialProvider: Send + Sync {
-    fn has_credentials(&self) -> bool;
-    fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential;
-    fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential);
-    fn delete_credentials(&self, cx: &mut AppContext);
-}

crates/ai2/src/completion.rs 🔗

@@ -1,23 +0,0 @@
-use anyhow::Result;
-use futures::{future::BoxFuture, stream::BoxStream};
-
-use crate::{auth::CredentialProvider, models::LanguageModel};
-
-pub trait CompletionRequest: Send + Sync {
-    fn data(&self) -> serde_json::Result<String>;
-}
-
-pub trait CompletionProvider: CredentialProvider {
-    fn base_model(&self) -> Box<dyn LanguageModel>;
-    fn complete(
-        &self,
-        prompt: Box<dyn CompletionRequest>,
-    ) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>>;
-    fn box_clone(&self) -> Box<dyn CompletionProvider>;
-}
-
-impl Clone for Box<dyn CompletionProvider> {
-    fn clone(&self) -> Box<dyn CompletionProvider> {
-        self.box_clone()
-    }
-}

crates/ai2/src/embedding.rs 🔗

@@ -1,123 +0,0 @@
-use std::time::Instant;
-
-use anyhow::Result;
-use async_trait::async_trait;
-use ordered_float::OrderedFloat;
-use rusqlite::types::{FromSql, FromSqlResult, ToSqlOutput, ValueRef};
-use rusqlite::ToSql;
-
-use crate::auth::CredentialProvider;
-use crate::models::LanguageModel;
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct Embedding(pub Vec<f32>);
-
-// This is needed for semantic index functionality
-// Unfortunately it has to live wherever the "Embedding" struct is created.
-// Keeping this in here though, introduces a 'rusqlite' dependency into AI
-// which is less than ideal
-impl FromSql for Embedding {
-    fn column_result(value: ValueRef) -> FromSqlResult<Self> {
-        let bytes = value.as_blob()?;
-        let embedding: Result<Vec<f32>, Box<bincode::ErrorKind>> = bincode::deserialize(bytes);
-        if embedding.is_err() {
-            return Err(rusqlite::types::FromSqlError::Other(embedding.unwrap_err()));
-        }
-        Ok(Embedding(embedding.unwrap()))
-    }
-}
-
-impl ToSql for Embedding {
-    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput> {
-        let bytes = bincode::serialize(&self.0)
-            .map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?;
-        Ok(ToSqlOutput::Owned(rusqlite::types::Value::Blob(bytes)))
-    }
-}
-impl From<Vec<f32>> for Embedding {
-    fn from(value: Vec<f32>) -> Self {
-        Embedding(value)
-    }
-}
-
-impl Embedding {
-    pub fn similarity(&self, other: &Self) -> OrderedFloat<f32> {
-        let len = self.0.len();
-        assert_eq!(len, other.0.len());
-
-        let mut result = 0.0;
-        unsafe {
-            matrixmultiply::sgemm(
-                1,
-                len,
-                1,
-                1.0,
-                self.0.as_ptr(),
-                len as isize,
-                1,
-                other.0.as_ptr(),
-                1,
-                len as isize,
-                0.0,
-                &mut result as *mut f32,
-                1,
-                1,
-            );
-        }
-        OrderedFloat(result)
-    }
-}
-
-#[async_trait]
-pub trait EmbeddingProvider: CredentialProvider {
-    fn base_model(&self) -> Box<dyn LanguageModel>;
-    async fn embed_batch(&self, spans: Vec<String>) -> Result<Vec<Embedding>>;
-    fn max_tokens_per_batch(&self) -> usize;
-    fn rate_limit_expiration(&self) -> Option<Instant>;
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use rand::prelude::*;
-
-    #[gpui::test]
-    fn test_similarity(mut rng: StdRng) {
-        assert_eq!(
-            Embedding::from(vec![1., 0., 0., 0., 0.])
-                .similarity(&Embedding::from(vec![0., 1., 0., 0., 0.])),
-            0.
-        );
-        assert_eq!(
-            Embedding::from(vec![2., 0., 0., 0., 0.])
-                .similarity(&Embedding::from(vec![3., 1., 0., 0., 0.])),
-            6.
-        );
-
-        for _ in 0..100 {
-            let size = 1536;
-            let mut a = vec![0.; size];
-            let mut b = vec![0.; size];
-            for (a, b) in a.iter_mut().zip(b.iter_mut()) {
-                *a = rng.gen();
-                *b = rng.gen();
-            }
-            let a = Embedding::from(a);
-            let b = Embedding::from(b);
-
-            assert_eq!(
-                round_to_decimals(a.similarity(&b), 1),
-                round_to_decimals(reference_dot(&a.0, &b.0), 1)
-            );
-        }
-
-        fn round_to_decimals(n: OrderedFloat<f32>, decimal_places: i32) -> f32 {
-            let factor = (10.0 as f32).powi(decimal_places);
-            (n * factor).round() / factor
-        }
-
-        fn reference_dot(a: &[f32], b: &[f32]) -> OrderedFloat<f32> {
-            OrderedFloat(a.iter().zip(b.iter()).map(|(a, b)| a * b).sum())
-        }
-    }
-}

crates/ai2/src/models.rs 🔗

@@ -1,16 +0,0 @@
-pub enum TruncationDirection {
-    Start,
-    End,
-}
-
-pub trait LanguageModel {
-    fn name(&self) -> String;
-    fn count_tokens(&self, content: &str) -> anyhow::Result<usize>;
-    fn truncate(
-        &self,
-        content: &str,
-        length: usize,
-        direction: TruncationDirection,
-    ) -> anyhow::Result<String>;
-    fn capacity(&self) -> anyhow::Result<usize>;
-}

crates/ai2/src/prompts/base.rs 🔗

@@ -1,330 +0,0 @@
-use std::cmp::Reverse;
-use std::ops::Range;
-use std::sync::Arc;
-
-use language::BufferSnapshot;
-use util::ResultExt;
-
-use crate::models::LanguageModel;
-use crate::prompts::repository_context::PromptCodeSnippet;
-
-pub(crate) enum PromptFileType {
-    Text,
-    Code,
-}
-
-// TODO: Set this up to manage for defaults well
-pub struct PromptArguments {
-    pub model: Arc<dyn LanguageModel>,
-    pub user_prompt: Option<String>,
-    pub language_name: Option<String>,
-    pub project_name: Option<String>,
-    pub snippets: Vec<PromptCodeSnippet>,
-    pub reserved_tokens: usize,
-    pub buffer: Option<BufferSnapshot>,
-    pub selected_range: Option<Range<usize>>,
-}
-
-impl PromptArguments {
-    pub(crate) fn get_file_type(&self) -> PromptFileType {
-        if self
-            .language_name
-            .as_ref()
-            .and_then(|name| Some(!["Markdown", "Plain Text"].contains(&name.as_str())))
-            .unwrap_or(true)
-        {
-            PromptFileType::Code
-        } else {
-            PromptFileType::Text
-        }
-    }
-}
-
-pub trait PromptTemplate {
-    fn generate(
-        &self,
-        args: &PromptArguments,
-        max_token_length: Option<usize>,
-    ) -> anyhow::Result<(String, usize)>;
-}
-
-#[repr(i8)]
-#[derive(PartialEq, Eq, Ord)]
-pub enum PromptPriority {
-    Mandatory,                // Ignores truncation
-    Ordered { order: usize }, // Truncates based on priority
-}
-
-impl PartialOrd for PromptPriority {
-    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
-        match (self, other) {
-            (Self::Mandatory, Self::Mandatory) => Some(std::cmp::Ordering::Equal),
-            (Self::Mandatory, Self::Ordered { .. }) => Some(std::cmp::Ordering::Greater),
-            (Self::Ordered { .. }, Self::Mandatory) => Some(std::cmp::Ordering::Less),
-            (Self::Ordered { order: a }, Self::Ordered { order: b }) => b.partial_cmp(a),
-        }
-    }
-}
-
-pub struct PromptChain {
-    args: PromptArguments,
-    templates: Vec<(PromptPriority, Box<dyn PromptTemplate>)>,
-}
-
-impl PromptChain {
-    pub fn new(
-        args: PromptArguments,
-        templates: Vec<(PromptPriority, Box<dyn PromptTemplate>)>,
-    ) -> Self {
-        PromptChain { args, templates }
-    }
-
-    pub fn generate(&self, truncate: bool) -> anyhow::Result<(String, usize)> {
-        // Argsort based on Prompt Priority
-        let seperator = "\n";
-        let seperator_tokens = self.args.model.count_tokens(seperator)?;
-        let mut sorted_indices = (0..self.templates.len()).collect::<Vec<_>>();
-        sorted_indices.sort_by_key(|&i| Reverse(&self.templates[i].0));
-
-        // If Truncate
-        let mut tokens_outstanding = if truncate {
-            Some(self.args.model.capacity()? - self.args.reserved_tokens)
-        } else {
-            None
-        };
-
-        let mut prompts = vec!["".to_string(); sorted_indices.len()];
-        for idx in sorted_indices {
-            let (_, template) = &self.templates[idx];
-
-            if let Some((template_prompt, prompt_token_count)) =
-                template.generate(&self.args, tokens_outstanding).log_err()
-            {
-                if template_prompt != "" {
-                    prompts[idx] = template_prompt;
-
-                    if let Some(remaining_tokens) = tokens_outstanding {
-                        let new_tokens = prompt_token_count + seperator_tokens;
-                        tokens_outstanding = if remaining_tokens > new_tokens {
-                            Some(remaining_tokens - new_tokens)
-                        } else {
-                            Some(0)
-                        };
-                    }
-                }
-            }
-        }
-
-        prompts.retain(|x| x != "");
-
-        let full_prompt = prompts.join(seperator);
-        let total_token_count = self.args.model.count_tokens(&full_prompt)?;
-        anyhow::Ok((prompts.join(seperator), total_token_count))
-    }
-}
-
-#[cfg(test)]
-pub(crate) mod tests {
-    use crate::models::TruncationDirection;
-    use crate::test::FakeLanguageModel;
-
-    use super::*;
-
-    #[test]
-    pub fn test_prompt_chain() {
-        struct TestPromptTemplate {}
-        impl PromptTemplate for TestPromptTemplate {
-            fn generate(
-                &self,
-                args: &PromptArguments,
-                max_token_length: Option<usize>,
-            ) -> anyhow::Result<(String, usize)> {
-                let mut content = "This is a test prompt template".to_string();
-
-                let mut token_count = args.model.count_tokens(&content)?;
-                if let Some(max_token_length) = max_token_length {
-                    if token_count > max_token_length {
-                        content = args.model.truncate(
-                            &content,
-                            max_token_length,
-                            TruncationDirection::End,
-                        )?;
-                        token_count = max_token_length;
-                    }
-                }
-
-                anyhow::Ok((content, token_count))
-            }
-        }
-
-        struct TestLowPriorityTemplate {}
-        impl PromptTemplate for TestLowPriorityTemplate {
-            fn generate(
-                &self,
-                args: &PromptArguments,
-                max_token_length: Option<usize>,
-            ) -> anyhow::Result<(String, usize)> {
-                let mut content = "This is a low priority test prompt template".to_string();
-
-                let mut token_count = args.model.count_tokens(&content)?;
-                if let Some(max_token_length) = max_token_length {
-                    if token_count > max_token_length {
-                        content = args.model.truncate(
-                            &content,
-                            max_token_length,
-                            TruncationDirection::End,
-                        )?;
-                        token_count = max_token_length;
-                    }
-                }
-
-                anyhow::Ok((content, token_count))
-            }
-        }
-
-        let model: Arc<dyn LanguageModel> = Arc::new(FakeLanguageModel { capacity: 100 });
-        let args = PromptArguments {
-            model: model.clone(),
-            language_name: None,
-            project_name: None,
-            snippets: Vec::new(),
-            reserved_tokens: 0,
-            buffer: None,
-            selected_range: None,
-            user_prompt: None,
-        };
-
-        let templates: Vec<(PromptPriority, Box<dyn PromptTemplate>)> = vec![
-            (
-                PromptPriority::Ordered { order: 0 },
-                Box::new(TestPromptTemplate {}),
-            ),
-            (
-                PromptPriority::Ordered { order: 1 },
-                Box::new(TestLowPriorityTemplate {}),
-            ),
-        ];
-        let chain = PromptChain::new(args, templates);
-
-        let (prompt, token_count) = chain.generate(false).unwrap();
-
-        assert_eq!(
-            prompt,
-            "This is a test prompt template\nThis is a low priority test prompt template"
-                .to_string()
-        );
-
-        assert_eq!(model.count_tokens(&prompt).unwrap(), token_count);
-
-        // Testing with Truncation Off
-        // Should ignore capacity and return all prompts
-        let model: Arc<dyn LanguageModel> = Arc::new(FakeLanguageModel { capacity: 20 });
-        let args = PromptArguments {
-            model: model.clone(),
-            language_name: None,
-            project_name: None,
-            snippets: Vec::new(),
-            reserved_tokens: 0,
-            buffer: None,
-            selected_range: None,
-            user_prompt: None,
-        };
-
-        let templates: Vec<(PromptPriority, Box<dyn PromptTemplate>)> = vec![
-            (
-                PromptPriority::Ordered { order: 0 },
-                Box::new(TestPromptTemplate {}),
-            ),
-            (
-                PromptPriority::Ordered { order: 1 },
-                Box::new(TestLowPriorityTemplate {}),
-            ),
-        ];
-        let chain = PromptChain::new(args, templates);
-
-        let (prompt, token_count) = chain.generate(false).unwrap();
-
-        assert_eq!(
-            prompt,
-            "This is a test prompt template\nThis is a low priority test prompt template"
-                .to_string()
-        );
-
-        assert_eq!(model.count_tokens(&prompt).unwrap(), token_count);
-
-        // Testing with Truncation Off
-        // Should ignore capacity and return all prompts
-        let capacity = 20;
-        let model: Arc<dyn LanguageModel> = Arc::new(FakeLanguageModel { capacity });
-        let args = PromptArguments {
-            model: model.clone(),
-            language_name: None,
-            project_name: None,
-            snippets: Vec::new(),
-            reserved_tokens: 0,
-            buffer: None,
-            selected_range: None,
-            user_prompt: None,
-        };
-
-        let templates: Vec<(PromptPriority, Box<dyn PromptTemplate>)> = vec![
-            (
-                PromptPriority::Ordered { order: 0 },
-                Box::new(TestPromptTemplate {}),
-            ),
-            (
-                PromptPriority::Ordered { order: 1 },
-                Box::new(TestLowPriorityTemplate {}),
-            ),
-            (
-                PromptPriority::Ordered { order: 2 },
-                Box::new(TestLowPriorityTemplate {}),
-            ),
-        ];
-        let chain = PromptChain::new(args, templates);
-
-        let (prompt, token_count) = chain.generate(true).unwrap();
-
-        assert_eq!(prompt, "This is a test promp".to_string());
-        assert_eq!(token_count, capacity);
-
-        // Change Ordering of Prompts Based on Priority
-        let capacity = 120;
-        let reserved_tokens = 10;
-        let model: Arc<dyn LanguageModel> = Arc::new(FakeLanguageModel { capacity });
-        let args = PromptArguments {
-            model: model.clone(),
-            language_name: None,
-            project_name: None,
-            snippets: Vec::new(),
-            reserved_tokens,
-            buffer: None,
-            selected_range: None,
-            user_prompt: None,
-        };
-        let templates: Vec<(PromptPriority, Box<dyn PromptTemplate>)> = vec![
-            (
-                PromptPriority::Mandatory,
-                Box::new(TestLowPriorityTemplate {}),
-            ),
-            (
-                PromptPriority::Ordered { order: 0 },
-                Box::new(TestPromptTemplate {}),
-            ),
-            (
-                PromptPriority::Ordered { order: 1 },
-                Box::new(TestLowPriorityTemplate {}),
-            ),
-        ];
-        let chain = PromptChain::new(args, templates);
-
-        let (prompt, token_count) = chain.generate(true).unwrap();
-
-        assert_eq!(
-            prompt,
-            "This is a low priority test prompt template\nThis is a test prompt template\nThis is a low priority test prompt "
-                .to_string()
-        );
-        assert_eq!(token_count, capacity - reserved_tokens);
-    }
-}

crates/ai2/src/prompts/file_context.rs 🔗

@@ -1,164 +0,0 @@
-use anyhow::anyhow;
-use language::BufferSnapshot;
-use language::ToOffset;
-
-use crate::models::LanguageModel;
-use crate::models::TruncationDirection;
-use crate::prompts::base::PromptArguments;
-use crate::prompts::base::PromptTemplate;
-use std::fmt::Write;
-use std::ops::Range;
-use std::sync::Arc;
-
-fn retrieve_context(
-    buffer: &BufferSnapshot,
-    selected_range: &Option<Range<usize>>,
-    model: Arc<dyn LanguageModel>,
-    max_token_count: Option<usize>,
-) -> anyhow::Result<(String, usize, bool)> {
-    let mut prompt = String::new();
-    let mut truncated = false;
-    if let Some(selected_range) = selected_range {
-        let start = selected_range.start.to_offset(buffer);
-        let end = selected_range.end.to_offset(buffer);
-
-        let start_window = buffer.text_for_range(0..start).collect::<String>();
-
-        let mut selected_window = String::new();
-        if start == end {
-            write!(selected_window, "<|START|>").unwrap();
-        } else {
-            write!(selected_window, "<|START|").unwrap();
-        }
-
-        write!(
-            selected_window,
-            "{}",
-            buffer.text_for_range(start..end).collect::<String>()
-        )
-        .unwrap();
-
-        if start != end {
-            write!(selected_window, "|END|>").unwrap();
-        }
-
-        let end_window = buffer.text_for_range(end..buffer.len()).collect::<String>();
-
-        if let Some(max_token_count) = max_token_count {
-            let selected_tokens = model.count_tokens(&selected_window)?;
-            if selected_tokens > max_token_count {
-                return Err(anyhow!(
-                    "selected range is greater than model context window, truncation not possible"
-                ));
-            };
-
-            let mut remaining_tokens = max_token_count - selected_tokens;
-            let start_window_tokens = model.count_tokens(&start_window)?;
-            let end_window_tokens = model.count_tokens(&end_window)?;
-            let outside_tokens = start_window_tokens + end_window_tokens;
-            if outside_tokens > remaining_tokens {
-                let (start_goal_tokens, end_goal_tokens) =
-                    if start_window_tokens < end_window_tokens {
-                        let start_goal_tokens = (remaining_tokens / 2).min(start_window_tokens);
-                        remaining_tokens -= start_goal_tokens;
-                        let end_goal_tokens = remaining_tokens.min(end_window_tokens);
-                        (start_goal_tokens, end_goal_tokens)
-                    } else {
-                        let end_goal_tokens = (remaining_tokens / 2).min(end_window_tokens);
-                        remaining_tokens -= end_goal_tokens;
-                        let start_goal_tokens = remaining_tokens.min(start_window_tokens);
-                        (start_goal_tokens, end_goal_tokens)
-                    };
-
-                let truncated_start_window =
-                    model.truncate(&start_window, start_goal_tokens, TruncationDirection::Start)?;
-                let truncated_end_window =
-                    model.truncate(&end_window, end_goal_tokens, TruncationDirection::End)?;
-                writeln!(
-                    prompt,
-                    "{truncated_start_window}{selected_window}{truncated_end_window}"
-                )
-                .unwrap();
-                truncated = true;
-            } else {
-                writeln!(prompt, "{start_window}{selected_window}{end_window}").unwrap();
-            }
-        } else {
-            // If we dont have a selected range, include entire file.
-            writeln!(prompt, "{}", &buffer.text()).unwrap();
-
-            // Dumb truncation strategy
-            if let Some(max_token_count) = max_token_count {
-                if model.count_tokens(&prompt)? > max_token_count {
-                    truncated = true;
-                    prompt = model.truncate(&prompt, max_token_count, TruncationDirection::End)?;
-                }
-            }
-        }
-    }
-
-    let token_count = model.count_tokens(&prompt)?;
-    anyhow::Ok((prompt, token_count, truncated))
-}
-
-pub struct FileContext {}
-
-impl PromptTemplate for FileContext {
-    fn generate(
-        &self,
-        args: &PromptArguments,
-        max_token_length: Option<usize>,
-    ) -> anyhow::Result<(String, usize)> {
-        if let Some(buffer) = &args.buffer {
-            let mut prompt = String::new();
-            // Add Initial Preamble
-            // TODO: Do we want to add the path in here?
-            writeln!(
-                prompt,
-                "The file you are currently working on has the following content:"
-            )
-            .unwrap();
-
-            let language_name = args
-                .language_name
-                .clone()
-                .unwrap_or("".to_string())
-                .to_lowercase();
-
-            let (context, _, truncated) = retrieve_context(
-                buffer,
-                &args.selected_range,
-                args.model.clone(),
-                max_token_length,
-            )?;
-            writeln!(prompt, "```{language_name}\n{context}\n```").unwrap();
-
-            if truncated {
-                writeln!(prompt, "Note the content has been truncated and only represents a portion of the file.").unwrap();
-            }
-
-            if let Some(selected_range) = &args.selected_range {
-                let start = selected_range.start.to_offset(buffer);
-                let end = selected_range.end.to_offset(buffer);
-
-                if start == end {
-                    writeln!(prompt, "In particular, the user's cursor is currently on the '<|START|>' span in the above content, with no text selected.").unwrap();
-                } else {
-                    writeln!(prompt, "In particular, the user has selected a section of the text between the '<|START|' and '|END|>' spans.").unwrap();
-                }
-            }
-
-            // Really dumb truncation strategy
-            if let Some(max_tokens) = max_token_length {
-                prompt = args
-                    .model
-                    .truncate(&prompt, max_tokens, TruncationDirection::End)?;
-            }
-
-            let token_count = args.model.count_tokens(&prompt)?;
-            anyhow::Ok((prompt, token_count))
-        } else {
-            Err(anyhow!("no buffer provided to retrieve file context from"))
-        }
-    }
-}

crates/ai2/src/prompts/generate.rs 🔗

@@ -1,99 +0,0 @@
-use crate::prompts::base::{PromptArguments, PromptFileType, PromptTemplate};
-use anyhow::anyhow;
-use std::fmt::Write;
-
-pub fn capitalize(s: &str) -> String {
-    let mut c = s.chars();
-    match c.next() {
-        None => String::new(),
-        Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
-    }
-}
-
-pub struct GenerateInlineContent {}
-
-impl PromptTemplate for GenerateInlineContent {
-    fn generate(
-        &self,
-        args: &PromptArguments,
-        max_token_length: Option<usize>,
-    ) -> anyhow::Result<(String, usize)> {
-        let Some(user_prompt) = &args.user_prompt else {
-            return Err(anyhow!("user prompt not provided"));
-        };
-
-        let file_type = args.get_file_type();
-        let content_type = match &file_type {
-            PromptFileType::Code => "code",
-            PromptFileType::Text => "text",
-        };
-
-        let mut prompt = String::new();
-
-        if let Some(selected_range) = &args.selected_range {
-            if selected_range.start == selected_range.end {
-                writeln!(
-                    prompt,
-                    "Assume the cursor is located where the `<|START|>` span is."
-                )
-                .unwrap();
-                writeln!(
-                    prompt,
-                    "{} can't be replaced, so assume your answer will be inserted at the cursor.",
-                    capitalize(content_type)
-                )
-                .unwrap();
-                writeln!(
-                    prompt,
-                    "Generate {content_type} based on the users prompt: {user_prompt}",
-                )
-                .unwrap();
-            } else {
-                writeln!(prompt, "Modify the user's selected {content_type} based upon the users prompt: '{user_prompt}'").unwrap();
-                writeln!(prompt, "You must reply with only the adjusted {content_type} (within the '<|START|' and '|END|>' spans) not the entire file.").unwrap();
-                writeln!(prompt, "Double check that you only return code and not the '<|START|' and '|END|'> spans").unwrap();
-            }
-        } else {
-            writeln!(
-                prompt,
-                "Generate {content_type} based on the users prompt: {user_prompt}"
-            )
-            .unwrap();
-        }
-
-        if let Some(language_name) = &args.language_name {
-            writeln!(
-                prompt,
-                "Your answer MUST always and only be valid {}.",
-                language_name
-            )
-            .unwrap();
-        }
-        writeln!(prompt, "Never make remarks about the output.").unwrap();
-        writeln!(
-            prompt,
-            "Do not return anything else, except the generated {content_type}."
-        )
-        .unwrap();
-
-        match file_type {
-            PromptFileType::Code => {
-                // writeln!(prompt, "Always wrap your code in a Markdown block.").unwrap();
-            }
-            _ => {}
-        }
-
-        // Really dumb truncation strategy
-        if let Some(max_tokens) = max_token_length {
-            prompt = args.model.truncate(
-                &prompt,
-                max_tokens,
-                crate::models::TruncationDirection::End,
-            )?;
-        }
-
-        let token_count = args.model.count_tokens(&prompt)?;
-
-        anyhow::Ok((prompt, token_count))
-    }
-}

crates/ai2/src/prompts/preamble.rs 🔗

@@ -1,52 +0,0 @@
-use crate::prompts::base::{PromptArguments, PromptFileType, PromptTemplate};
-use std::fmt::Write;
-
-pub struct EngineerPreamble {}
-
-impl PromptTemplate for EngineerPreamble {
-    fn generate(
-        &self,
-        args: &PromptArguments,
-        max_token_length: Option<usize>,
-    ) -> anyhow::Result<(String, usize)> {
-        let mut prompts = Vec::new();
-
-        match args.get_file_type() {
-            PromptFileType::Code => {
-                prompts.push(format!(
-                    "You are an expert {}engineer.",
-                    args.language_name.clone().unwrap_or("".to_string()) + " "
-                ));
-            }
-            PromptFileType::Text => {
-                prompts.push("You are an expert engineer.".to_string());
-            }
-        }
-
-        if let Some(project_name) = args.project_name.clone() {
-            prompts.push(format!(
-                "You are currently working inside the '{project_name}' project in code editor Zed."
-            ));
-        }
-
-        if let Some(mut remaining_tokens) = max_token_length {
-            let mut prompt = String::new();
-            let mut total_count = 0;
-            for prompt_piece in prompts {
-                let prompt_token_count =
-                    args.model.count_tokens(&prompt_piece)? + args.model.count_tokens("\n")?;
-                if remaining_tokens > prompt_token_count {
-                    writeln!(prompt, "{prompt_piece}").unwrap();
-                    remaining_tokens -= prompt_token_count;
-                    total_count += prompt_token_count;
-                }
-            }
-
-            anyhow::Ok((prompt, total_count))
-        } else {
-            let prompt = prompts.join("\n");
-            let token_count = args.model.count_tokens(&prompt)?;
-            anyhow::Ok((prompt, token_count))
-        }
-    }
-}

crates/ai2/src/prompts/repository_context.rs 🔗

@@ -1,98 +0,0 @@
-use crate::prompts::base::{PromptArguments, PromptTemplate};
-use std::fmt::Write;
-use std::{ops::Range, path::PathBuf};
-
-use gpui::{AsyncAppContext, Model};
-use language::{Anchor, Buffer};
-
-#[derive(Clone)]
-pub struct PromptCodeSnippet {
-    path: Option<PathBuf>,
-    language_name: Option<String>,
-    content: String,
-}
-
-impl PromptCodeSnippet {
-    pub fn new(
-        buffer: Model<Buffer>,
-        range: Range<Anchor>,
-        cx: &mut AsyncAppContext,
-    ) -> anyhow::Result<Self> {
-        let (content, language_name, file_path) = buffer.update(cx, |buffer, _| {
-            let snapshot = buffer.snapshot();
-            let content = snapshot.text_for_range(range.clone()).collect::<String>();
-
-            let language_name = buffer
-                .language()
-                .and_then(|language| Some(language.name().to_string().to_lowercase()));
-
-            let file_path = buffer
-                .file()
-                .and_then(|file| Some(file.path().to_path_buf()));
-
-            (content, language_name, file_path)
-        })?;
-
-        anyhow::Ok(PromptCodeSnippet {
-            path: file_path,
-            language_name,
-            content,
-        })
-    }
-}
-
-impl ToString for PromptCodeSnippet {
-    fn to_string(&self) -> String {
-        let path = self
-            .path
-            .as_ref()
-            .and_then(|path| Some(path.to_string_lossy().to_string()))
-            .unwrap_or("".to_string());
-        let language_name = self.language_name.clone().unwrap_or("".to_string());
-        let content = self.content.clone();
-
-        format!("The below code snippet may be relevant from file: {path}\n```{language_name}\n{content}\n```")
-    }
-}
-
-pub struct RepositoryContext {}
-
-impl PromptTemplate for RepositoryContext {
-    fn generate(
-        &self,
-        args: &PromptArguments,
-        max_token_length: Option<usize>,
-    ) -> anyhow::Result<(String, usize)> {
-        const MAXIMUM_SNIPPET_TOKEN_COUNT: usize = 500;
-        let template = "You are working inside a large repository, here are a few code snippets that may be useful.";
-        let mut prompt = String::new();
-
-        let mut remaining_tokens = max_token_length.clone();
-        let seperator_token_length = args.model.count_tokens("\n")?;
-        for snippet in &args.snippets {
-            let mut snippet_prompt = template.to_string();
-            let content = snippet.to_string();
-            writeln!(snippet_prompt, "{content}").unwrap();
-
-            let token_count = args.model.count_tokens(&snippet_prompt)?;
-            if token_count <= MAXIMUM_SNIPPET_TOKEN_COUNT {
-                if let Some(tokens_left) = remaining_tokens {
-                    if tokens_left >= token_count {
-                        writeln!(prompt, "{snippet_prompt}").unwrap();
-                        remaining_tokens = if tokens_left >= (token_count + seperator_token_length)
-                        {
-                            Some(tokens_left - token_count - seperator_token_length)
-                        } else {
-                            Some(0)
-                        };
-                    }
-                } else {
-                    writeln!(prompt, "{snippet_prompt}").unwrap();
-                }
-            }
-        }
-
-        let total_token_count = args.model.count_tokens(&prompt)?;
-        anyhow::Ok((prompt, total_token_count))
-    }
-}

crates/ai2/src/providers/open_ai/completion.rs 🔗

@@ -1,297 +0,0 @@
-use anyhow::{anyhow, Result};
-use futures::{
-    future::BoxFuture, io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, FutureExt,
-    Stream, StreamExt,
-};
-use gpui::{AppContext, BackgroundExecutor};
-use isahc::{http::StatusCode, Request, RequestExt};
-use parking_lot::RwLock;
-use serde::{Deserialize, Serialize};
-use std::{
-    env,
-    fmt::{self, Display},
-    io,
-    sync::Arc,
-};
-use util::ResultExt;
-
-use crate::{
-    auth::{CredentialProvider, ProviderCredential},
-    completion::{CompletionProvider, CompletionRequest},
-    models::LanguageModel,
-};
-
-use crate::providers::open_ai::{OpenAILanguageModel, OPENAI_API_URL};
-
-#[derive(Clone, Copy, Serialize, Deserialize, Debug, Eq, PartialEq)]
-#[serde(rename_all = "lowercase")]
-pub enum Role {
-    User,
-    Assistant,
-    System,
-}
-
-impl Role {
-    pub fn cycle(&mut self) {
-        *self = match self {
-            Role::User => Role::Assistant,
-            Role::Assistant => Role::System,
-            Role::System => Role::User,
-        }
-    }
-}
-
-impl Display for Role {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            Role::User => write!(f, "User"),
-            Role::Assistant => write!(f, "Assistant"),
-            Role::System => write!(f, "System"),
-        }
-    }
-}
-
-#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
-pub struct RequestMessage {
-    pub role: Role,
-    pub content: String,
-}
-
-#[derive(Debug, Default, Serialize)]
-pub struct OpenAIRequest {
-    pub model: String,
-    pub messages: Vec<RequestMessage>,
-    pub stream: bool,
-    pub stop: Vec<String>,
-    pub temperature: f32,
-}
-
-impl CompletionRequest for OpenAIRequest {
-    fn data(&self) -> serde_json::Result<String> {
-        serde_json::to_string(self)
-    }
-}
-
-#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
-pub struct ResponseMessage {
-    pub role: Option<Role>,
-    pub content: Option<String>,
-}
-
-#[derive(Deserialize, Debug)]
-pub struct OpenAIUsage {
-    pub prompt_tokens: u32,
-    pub completion_tokens: u32,
-    pub total_tokens: u32,
-}
-
-#[derive(Deserialize, Debug)]
-pub struct ChatChoiceDelta {
-    pub index: u32,
-    pub delta: ResponseMessage,
-    pub finish_reason: Option<String>,
-}
-
-#[derive(Deserialize, Debug)]
-pub struct OpenAIResponseStreamEvent {
-    pub id: Option<String>,
-    pub object: String,
-    pub created: u32,
-    pub model: String,
-    pub choices: Vec<ChatChoiceDelta>,
-    pub usage: Option<OpenAIUsage>,
-}
-
-pub async fn stream_completion(
-    credential: ProviderCredential,
-    executor: BackgroundExecutor,
-    request: Box<dyn CompletionRequest>,
-) -> Result<impl Stream<Item = Result<OpenAIResponseStreamEvent>>> {
-    let api_key = match credential {
-        ProviderCredential::Credentials { api_key } => api_key,
-        _ => {
-            return Err(anyhow!("no credentials provider for completion"));
-        }
-    };
-
-    let (tx, rx) = futures::channel::mpsc::unbounded::<Result<OpenAIResponseStreamEvent>>();
-
-    let json_data = request.data()?;
-    let mut response = Request::post(format!("{OPENAI_API_URL}/chat/completions"))
-        .header("Content-Type", "application/json")
-        .header("Authorization", format!("Bearer {}", api_key))
-        .body(json_data)?
-        .send_async()
-        .await?;
-
-    let status = response.status();
-    if status == StatusCode::OK {
-        executor
-            .spawn(async move {
-                let mut lines = BufReader::new(response.body_mut()).lines();
-
-                fn parse_line(
-                    line: Result<String, io::Error>,
-                ) -> Result<Option<OpenAIResponseStreamEvent>> {
-                    if let Some(data) = line?.strip_prefix("data: ") {
-                        let event = serde_json::from_str(&data)?;
-                        Ok(Some(event))
-                    } else {
-                        Ok(None)
-                    }
-                }
-
-                while let Some(line) = lines.next().await {
-                    if let Some(event) = parse_line(line).transpose() {
-                        let done = event.as_ref().map_or(false, |event| {
-                            event
-                                .choices
-                                .last()
-                                .map_or(false, |choice| choice.finish_reason.is_some())
-                        });
-                        if tx.unbounded_send(event).is_err() {
-                            break;
-                        }
-
-                        if done {
-                            break;
-                        }
-                    }
-                }
-
-                anyhow::Ok(())
-            })
-            .detach();
-
-        Ok(rx)
-    } else {
-        let mut body = String::new();
-        response.body_mut().read_to_string(&mut body).await?;
-
-        #[derive(Deserialize)]
-        struct OpenAIResponse {
-            error: OpenAIError,
-        }
-
-        #[derive(Deserialize)]
-        struct OpenAIError {
-            message: String,
-        }
-
-        match serde_json::from_str::<OpenAIResponse>(&body) {
-            Ok(response) if !response.error.message.is_empty() => Err(anyhow!(
-                "Failed to connect to OpenAI API: {}",
-                response.error.message,
-            )),
-
-            _ => Err(anyhow!(
-                "Failed to connect to OpenAI API: {} {}",
-                response.status(),
-                body,
-            )),
-        }
-    }
-}
-
-#[derive(Clone)]
-pub struct OpenAICompletionProvider {
-    model: OpenAILanguageModel,
-    credential: Arc<RwLock<ProviderCredential>>,
-    executor: BackgroundExecutor,
-}
-
-impl OpenAICompletionProvider {
-    pub fn new(model_name: &str, executor: BackgroundExecutor) -> Self {
-        let model = OpenAILanguageModel::load(model_name);
-        let credential = Arc::new(RwLock::new(ProviderCredential::NoCredentials));
-        Self {
-            model,
-            credential,
-            executor,
-        }
-    }
-}
-
-impl CredentialProvider for OpenAICompletionProvider {
-    fn has_credentials(&self) -> bool {
-        match *self.credential.read() {
-            ProviderCredential::Credentials { .. } => true,
-            _ => false,
-        }
-    }
-
-    fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential {
-        let existing_credential = self.credential.read().clone();
-        let retrieved_credential = match existing_credential {
-            ProviderCredential::Credentials { .. } => existing_credential.clone(),
-            _ => {
-                if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() {
-                    ProviderCredential::Credentials { api_key }
-                } else if let Some(Some((_, api_key))) =
-                    cx.read_credentials(OPENAI_API_URL).log_err()
-                {
-                    if let Some(api_key) = String::from_utf8(api_key).log_err() {
-                        ProviderCredential::Credentials { api_key }
-                    } else {
-                        ProviderCredential::NoCredentials
-                    }
-                } else {
-                    ProviderCredential::NoCredentials
-                }
-            }
-        };
-        *self.credential.write() = retrieved_credential.clone();
-        retrieved_credential
-    }
-
-    fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential) {
-        *self.credential.write() = credential.clone();
-        let credential = credential.clone();
-        match credential {
-            ProviderCredential::Credentials { api_key } => {
-                cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes())
-                    .log_err();
-            }
-            _ => {}
-        }
-    }
-
-    fn delete_credentials(&self, cx: &mut AppContext) {
-        cx.delete_credentials(OPENAI_API_URL).log_err();
-        *self.credential.write() = ProviderCredential::NoCredentials;
-    }
-}
-
-impl CompletionProvider for OpenAICompletionProvider {
-    fn base_model(&self) -> Box<dyn LanguageModel> {
-        let model: Box<dyn LanguageModel> = Box::new(self.model.clone());
-        model
-    }
-    fn complete(
-        &self,
-        prompt: Box<dyn CompletionRequest>,
-    ) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
-        // Currently the CompletionRequest for OpenAI, includes a 'model' parameter
-        // This means that the model is determined by the CompletionRequest and not the CompletionProvider,
-        // which is currently model based, due to the langauge model.
-        // At some point in the future we should rectify this.
-        let credential = self.credential.read().clone();
-        let request = stream_completion(credential, self.executor.clone(), prompt);
-        async move {
-            let response = request.await?;
-            let stream = response
-                .filter_map(|response| async move {
-                    match response {
-                        Ok(mut response) => Some(Ok(response.choices.pop()?.delta.content?)),
-                        Err(error) => Some(Err(error)),
-                    }
-                })
-                .boxed();
-            Ok(stream)
-        }
-        .boxed()
-    }
-    fn box_clone(&self) -> Box<dyn CompletionProvider> {
-        Box::new((*self).clone())
-    }
-}

crates/ai2/src/providers/open_ai/embedding.rs 🔗

@@ -1,305 +0,0 @@
-use anyhow::{anyhow, Result};
-use async_trait::async_trait;
-use futures::AsyncReadExt;
-use gpui::BackgroundExecutor;
-use gpui::{serde_json, AppContext};
-use isahc::http::StatusCode;
-use isahc::prelude::Configurable;
-use isahc::{AsyncBody, Response};
-use lazy_static::lazy_static;
-use parking_lot::{Mutex, RwLock};
-use parse_duration::parse;
-use postage::watch;
-use serde::{Deserialize, Serialize};
-use std::env;
-use std::ops::Add;
-use std::sync::Arc;
-use std::time::{Duration, Instant};
-use tiktoken_rs::{cl100k_base, CoreBPE};
-use util::http::{HttpClient, Request};
-use util::ResultExt;
-
-use crate::auth::{CredentialProvider, ProviderCredential};
-use crate::embedding::{Embedding, EmbeddingProvider};
-use crate::models::LanguageModel;
-use crate::providers::open_ai::OpenAILanguageModel;
-
-use crate::providers::open_ai::OPENAI_API_URL;
-
-lazy_static! {
-    static ref OPENAI_BPE_TOKENIZER: CoreBPE = cl100k_base().unwrap();
-}
-
-#[derive(Clone)]
-pub struct OpenAIEmbeddingProvider {
-    model: OpenAILanguageModel,
-    credential: Arc<RwLock<ProviderCredential>>,
-    pub client: Arc<dyn HttpClient>,
-    pub executor: BackgroundExecutor,
-    rate_limit_count_rx: watch::Receiver<Option<Instant>>,
-    rate_limit_count_tx: Arc<Mutex<watch::Sender<Option<Instant>>>>,
-}
-
-#[derive(Serialize)]
-struct OpenAIEmbeddingRequest<'a> {
-    model: &'static str,
-    input: Vec<&'a str>,
-}
-
-#[derive(Deserialize)]
-struct OpenAIEmbeddingResponse {
-    data: Vec<OpenAIEmbedding>,
-    usage: OpenAIEmbeddingUsage,
-}
-
-#[derive(Debug, Deserialize)]
-struct OpenAIEmbedding {
-    embedding: Vec<f32>,
-    index: usize,
-    object: String,
-}
-
-#[derive(Deserialize)]
-struct OpenAIEmbeddingUsage {
-    prompt_tokens: usize,
-    total_tokens: usize,
-}
-
-impl OpenAIEmbeddingProvider {
-    pub fn new(client: Arc<dyn HttpClient>, executor: BackgroundExecutor) -> Self {
-        let (rate_limit_count_tx, rate_limit_count_rx) = watch::channel_with(None);
-        let rate_limit_count_tx = Arc::new(Mutex::new(rate_limit_count_tx));
-
-        let model = OpenAILanguageModel::load("text-embedding-ada-002");
-        let credential = Arc::new(RwLock::new(ProviderCredential::NoCredentials));
-
-        OpenAIEmbeddingProvider {
-            model,
-            credential,
-            client,
-            executor,
-            rate_limit_count_rx,
-            rate_limit_count_tx,
-        }
-    }
-
-    fn get_api_key(&self) -> Result<String> {
-        match self.credential.read().clone() {
-            ProviderCredential::Credentials { api_key } => Ok(api_key),
-            _ => Err(anyhow!("api credentials not provided")),
-        }
-    }
-
-    fn resolve_rate_limit(&self) {
-        let reset_time = *self.rate_limit_count_tx.lock().borrow();
-
-        if let Some(reset_time) = reset_time {
-            if Instant::now() >= reset_time {
-                *self.rate_limit_count_tx.lock().borrow_mut() = None
-            }
-        }
-
-        log::trace!(
-            "resolving reset time: {:?}",
-            *self.rate_limit_count_tx.lock().borrow()
-        );
-    }
-
-    fn update_reset_time(&self, reset_time: Instant) {
-        let original_time = *self.rate_limit_count_tx.lock().borrow();
-
-        let updated_time = if let Some(original_time) = original_time {
-            if reset_time < original_time {
-                Some(reset_time)
-            } else {
-                Some(original_time)
-            }
-        } else {
-            Some(reset_time)
-        };
-
-        log::trace!("updating rate limit time: {:?}", updated_time);
-
-        *self.rate_limit_count_tx.lock().borrow_mut() = updated_time;
-    }
-    async fn send_request(
-        &self,
-        api_key: &str,
-        spans: Vec<&str>,
-        request_timeout: u64,
-    ) -> Result<Response<AsyncBody>> {
-        let request = Request::post("https://api.openai.com/v1/embeddings")
-            .redirect_policy(isahc::config::RedirectPolicy::Follow)
-            .timeout(Duration::from_secs(request_timeout))
-            .header("Content-Type", "application/json")
-            .header("Authorization", format!("Bearer {}", api_key))
-            .body(
-                serde_json::to_string(&OpenAIEmbeddingRequest {
-                    input: spans.clone(),
-                    model: "text-embedding-ada-002",
-                })
-                .unwrap()
-                .into(),
-            )?;
-
-        Ok(self.client.send(request).await?)
-    }
-}
-
-impl CredentialProvider for OpenAIEmbeddingProvider {
-    fn has_credentials(&self) -> bool {
-        match *self.credential.read() {
-            ProviderCredential::Credentials { .. } => true,
-            _ => false,
-        }
-    }
-    fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential {
-        let existing_credential = self.credential.read().clone();
-
-        let retrieved_credential = match existing_credential {
-            ProviderCredential::Credentials { .. } => existing_credential.clone(),
-            _ => {
-                if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() {
-                    ProviderCredential::Credentials { api_key }
-                } else if let Some(Some((_, api_key))) =
-                    cx.read_credentials(OPENAI_API_URL).log_err()
-                {
-                    if let Some(api_key) = String::from_utf8(api_key).log_err() {
-                        ProviderCredential::Credentials { api_key }
-                    } else {
-                        ProviderCredential::NoCredentials
-                    }
-                } else {
-                    ProviderCredential::NoCredentials
-                }
-            }
-        };
-
-        *self.credential.write() = retrieved_credential.clone();
-        retrieved_credential
-    }
-
-    fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential) {
-        *self.credential.write() = credential.clone();
-        match credential {
-            ProviderCredential::Credentials { api_key } => {
-                cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes())
-                    .log_err();
-            }
-            _ => {}
-        }
-    }
-
-    fn delete_credentials(&self, cx: &mut AppContext) {
-        cx.delete_credentials(OPENAI_API_URL).log_err();
-        *self.credential.write() = ProviderCredential::NoCredentials;
-    }
-}
-
-#[async_trait]
-impl EmbeddingProvider for OpenAIEmbeddingProvider {
-    fn base_model(&self) -> Box<dyn LanguageModel> {
-        let model: Box<dyn LanguageModel> = Box::new(self.model.clone());
-        model
-    }
-
-    fn max_tokens_per_batch(&self) -> usize {
-        50000
-    }
-
-    fn rate_limit_expiration(&self) -> Option<Instant> {
-        *self.rate_limit_count_rx.borrow()
-    }
-
-    async fn embed_batch(&self, spans: Vec<String>) -> Result<Vec<Embedding>> {
-        const BACKOFF_SECONDS: [usize; 4] = [3, 5, 15, 45];
-        const MAX_RETRIES: usize = 4;
-
-        let api_key = self.get_api_key()?;
-
-        let mut request_number = 0;
-        let mut rate_limiting = false;
-        let mut request_timeout: u64 = 15;
-        let mut response: Response<AsyncBody>;
-        while request_number < MAX_RETRIES {
-            response = self
-                .send_request(
-                    &api_key,
-                    spans.iter().map(|x| &**x).collect(),
-                    request_timeout,
-                )
-                .await?;
-
-            request_number += 1;
-
-            match response.status() {
-                StatusCode::REQUEST_TIMEOUT => {
-                    request_timeout += 5;
-                }
-                StatusCode::OK => {
-                    let mut body = String::new();
-                    response.body_mut().read_to_string(&mut body).await?;
-                    let response: OpenAIEmbeddingResponse = serde_json::from_str(&body)?;
-
-                    log::trace!(
-                        "openai embedding completed. tokens: {:?}",
-                        response.usage.total_tokens
-                    );
-
-                    // If we complete a request successfully that was previously rate_limited
-                    // resolve the rate limit
-                    if rate_limiting {
-                        self.resolve_rate_limit()
-                    }
-
-                    return Ok(response
-                        .data
-                        .into_iter()
-                        .map(|embedding| Embedding::from(embedding.embedding))
-                        .collect());
-                }
-                StatusCode::TOO_MANY_REQUESTS => {
-                    rate_limiting = true;
-                    let mut body = String::new();
-                    response.body_mut().read_to_string(&mut body).await?;
-
-                    let delay_duration = {
-                        let delay = Duration::from_secs(BACKOFF_SECONDS[request_number - 1] as u64);
-                        if let Some(time_to_reset) =
-                            response.headers().get("x-ratelimit-reset-tokens")
-                        {
-                            if let Ok(time_str) = time_to_reset.to_str() {
-                                parse(time_str).unwrap_or(delay)
-                            } else {
-                                delay
-                            }
-                        } else {
-                            delay
-                        }
-                    };
-
-                    // If we've previously rate limited, increment the duration but not the count
-                    let reset_time = Instant::now().add(delay_duration);
-                    self.update_reset_time(reset_time);
-
-                    log::trace!(
-                        "openai rate limiting: waiting {:?} until lifted",
-                        &delay_duration
-                    );
-
-                    self.executor.timer(delay_duration).await;
-                }
-                _ => {
-                    let mut body = String::new();
-                    response.body_mut().read_to_string(&mut body).await?;
-                    return Err(anyhow!(
-                        "open ai bad request: {:?} {:?}",
-                        &response.status(),
-                        body
-                    ));
-                }
-            }
-        }
-        Err(anyhow!("openai max retries"))
-    }
-}

crates/ai2/src/providers/open_ai/mod.rs 🔗

@@ -1,9 +0,0 @@
-pub mod completion;
-pub mod embedding;
-pub mod model;
-
-pub use completion::*;
-pub use embedding::*;
-pub use model::OpenAILanguageModel;
-
-pub const OPENAI_API_URL: &'static str = "https://api.openai.com/v1";

crates/ai2/src/providers/open_ai/model.rs 🔗

@@ -1,57 +0,0 @@
-use anyhow::anyhow;
-use tiktoken_rs::CoreBPE;
-use util::ResultExt;
-
-use crate::models::{LanguageModel, TruncationDirection};
-
-#[derive(Clone)]
-pub struct OpenAILanguageModel {
-    name: String,
-    bpe: Option<CoreBPE>,
-}
-
-impl OpenAILanguageModel {
-    pub fn load(model_name: &str) -> Self {
-        let bpe = tiktoken_rs::get_bpe_from_model(model_name).log_err();
-        OpenAILanguageModel {
-            name: model_name.to_string(),
-            bpe,
-        }
-    }
-}
-
-impl LanguageModel for OpenAILanguageModel {
-    fn name(&self) -> String {
-        self.name.clone()
-    }
-    fn count_tokens(&self, content: &str) -> anyhow::Result<usize> {
-        if let Some(bpe) = &self.bpe {
-            anyhow::Ok(bpe.encode_with_special_tokens(content).len())
-        } else {
-            Err(anyhow!("bpe for open ai model was not retrieved"))
-        }
-    }
-    fn truncate(
-        &self,
-        content: &str,
-        length: usize,
-        direction: TruncationDirection,
-    ) -> anyhow::Result<String> {
-        if let Some(bpe) = &self.bpe {
-            let tokens = bpe.encode_with_special_tokens(content);
-            if tokens.len() > length {
-                match direction {
-                    TruncationDirection::End => bpe.decode(tokens[..length].to_vec()),
-                    TruncationDirection::Start => bpe.decode(tokens[length..].to_vec()),
-                }
-            } else {
-                bpe.decode(tokens)
-            }
-        } else {
-            Err(anyhow!("bpe for open ai model was not retrieved"))
-        }
-    }
-    fn capacity(&self) -> anyhow::Result<usize> {
-        anyhow::Ok(tiktoken_rs::model::get_context_size(&self.name))
-    }
-}

crates/ai2/src/providers/open_ai/new.rs 🔗

@@ -1,11 +0,0 @@
-pub trait LanguageModel {
-    fn name(&self) -> String;
-    fn count_tokens(&self, content: &str) -> anyhow::Result<usize>;
-    fn truncate(
-        &self,
-        content: &str,
-        length: usize,
-        direction: TruncationDirection,
-    ) -> anyhow::Result<String>;
-    fn capacity(&self) -> anyhow::Result<usize>;
-}

crates/ai2/src/test.rs 🔗

@@ -1,191 +0,0 @@
-use std::{
-    sync::atomic::{self, AtomicUsize, Ordering},
-    time::Instant,
-};
-
-use async_trait::async_trait;
-use futures::{channel::mpsc, future::BoxFuture, stream::BoxStream, FutureExt, StreamExt};
-use gpui::AppContext;
-use parking_lot::Mutex;
-
-use crate::{
-    auth::{CredentialProvider, ProviderCredential},
-    completion::{CompletionProvider, CompletionRequest},
-    embedding::{Embedding, EmbeddingProvider},
-    models::{LanguageModel, TruncationDirection},
-};
-
-#[derive(Clone)]
-pub struct FakeLanguageModel {
-    pub capacity: usize,
-}
-
-impl LanguageModel for FakeLanguageModel {
-    fn name(&self) -> String {
-        "dummy".to_string()
-    }
-    fn count_tokens(&self, content: &str) -> anyhow::Result<usize> {
-        anyhow::Ok(content.chars().collect::<Vec<char>>().len())
-    }
-    fn truncate(
-        &self,
-        content: &str,
-        length: usize,
-        direction: TruncationDirection,
-    ) -> anyhow::Result<String> {
-        println!("TRYING TO TRUNCATE: {:?}", length.clone());
-
-        if length > self.count_tokens(content)? {
-            println!("NOT TRUNCATING");
-            return anyhow::Ok(content.to_string());
-        }
-
-        anyhow::Ok(match direction {
-            TruncationDirection::End => content.chars().collect::<Vec<char>>()[..length]
-                .into_iter()
-                .collect::<String>(),
-            TruncationDirection::Start => content.chars().collect::<Vec<char>>()[length..]
-                .into_iter()
-                .collect::<String>(),
-        })
-    }
-    fn capacity(&self) -> anyhow::Result<usize> {
-        anyhow::Ok(self.capacity)
-    }
-}
-
-pub struct FakeEmbeddingProvider {
-    pub embedding_count: AtomicUsize,
-}
-
-impl Clone for FakeEmbeddingProvider {
-    fn clone(&self) -> Self {
-        FakeEmbeddingProvider {
-            embedding_count: AtomicUsize::new(self.embedding_count.load(Ordering::SeqCst)),
-        }
-    }
-}
-
-impl Default for FakeEmbeddingProvider {
-    fn default() -> Self {
-        FakeEmbeddingProvider {
-            embedding_count: AtomicUsize::default(),
-        }
-    }
-}
-
-impl FakeEmbeddingProvider {
-    pub fn embedding_count(&self) -> usize {
-        self.embedding_count.load(atomic::Ordering::SeqCst)
-    }
-
-    pub fn embed_sync(&self, span: &str) -> Embedding {
-        let mut result = vec![1.0; 26];
-        for letter in span.chars() {
-            let letter = letter.to_ascii_lowercase();
-            if letter as u32 >= 'a' as u32 {
-                let ix = (letter as u32) - ('a' as u32);
-                if ix < 26 {
-                    result[ix as usize] += 1.0;
-                }
-            }
-        }
-
-        let norm = result.iter().map(|x| x * x).sum::<f32>().sqrt();
-        for x in &mut result {
-            *x /= norm;
-        }
-
-        result.into()
-    }
-}
-
-impl CredentialProvider for FakeEmbeddingProvider {
-    fn has_credentials(&self) -> bool {
-        true
-    }
-    fn retrieve_credentials(&self, _cx: &mut AppContext) -> ProviderCredential {
-        ProviderCredential::NotNeeded
-    }
-    fn save_credentials(&self, _cx: &mut AppContext, _credential: ProviderCredential) {}
-    fn delete_credentials(&self, _cx: &mut AppContext) {}
-}
-
-#[async_trait]
-impl EmbeddingProvider for FakeEmbeddingProvider {
-    fn base_model(&self) -> Box<dyn LanguageModel> {
-        Box::new(FakeLanguageModel { capacity: 1000 })
-    }
-    fn max_tokens_per_batch(&self) -> usize {
-        1000
-    }
-
-    fn rate_limit_expiration(&self) -> Option<Instant> {
-        None
-    }
-
-    async fn embed_batch(&self, spans: Vec<String>) -> anyhow::Result<Vec<Embedding>> {
-        self.embedding_count
-            .fetch_add(spans.len(), atomic::Ordering::SeqCst);
-
-        anyhow::Ok(spans.iter().map(|span| self.embed_sync(span)).collect())
-    }
-}
-
-pub struct FakeCompletionProvider {
-    last_completion_tx: Mutex<Option<mpsc::Sender<String>>>,
-}
-
-impl Clone for FakeCompletionProvider {
-    fn clone(&self) -> Self {
-        Self {
-            last_completion_tx: Mutex::new(None),
-        }
-    }
-}
-
-impl FakeCompletionProvider {
-    pub fn new() -> Self {
-        Self {
-            last_completion_tx: Mutex::new(None),
-        }
-    }
-
-    pub fn send_completion(&self, completion: impl Into<String>) {
-        let mut tx = self.last_completion_tx.lock();
-        tx.as_mut().unwrap().try_send(completion.into()).unwrap();
-    }
-
-    pub fn finish_completion(&self) {
-        self.last_completion_tx.lock().take().unwrap();
-    }
-}
-
-impl CredentialProvider for FakeCompletionProvider {
-    fn has_credentials(&self) -> bool {
-        true
-    }
-    fn retrieve_credentials(&self, _cx: &mut AppContext) -> ProviderCredential {
-        ProviderCredential::NotNeeded
-    }
-    fn save_credentials(&self, _cx: &mut AppContext, _credential: ProviderCredential) {}
-    fn delete_credentials(&self, _cx: &mut AppContext) {}
-}
-
-impl CompletionProvider for FakeCompletionProvider {
-    fn base_model(&self) -> Box<dyn LanguageModel> {
-        let model: Box<dyn LanguageModel> = Box::new(FakeLanguageModel { capacity: 8190 });
-        model
-    }
-    fn complete(
-        &self,
-        _prompt: Box<dyn CompletionRequest>,
-    ) -> BoxFuture<'static, anyhow::Result<BoxStream<'static, anyhow::Result<String>>>> {
-        let (tx, rx) = mpsc::channel(1);
-        *self.last_completion_tx.lock() = Some(tx);
-        async move { Ok(rx.map(|rx| Ok(rx)).boxed()) }.boxed()
-    }
-    fn box_clone(&self) -> Box<dyn CompletionProvider> {
-        Box::new((*self).clone())
-    }
-}

crates/assistant/Cargo.toml 🔗

@@ -18,13 +18,14 @@ gpui = { path = "../gpui" }
 language = { path = "../language" }
 menu = { path = "../menu" }
 multi_buffer = { path = "../multi_buffer" }
+project = { path = "../project" }
 search = { path = "../search" }
+semantic_index = { path = "../semantic_index" }
 settings = { path = "../settings" }
 theme = { path = "../theme" }
+ui = { path = "../ui" }
 util = { path = "../util" }
 workspace = { path = "../workspace" }
-semantic_index = { path = "../semantic_index" }
-project = { path = "../project" }
 
 uuid.workspace = true
 log.workspace = true
@@ -43,9 +44,9 @@ smol.workspace = true
 tiktoken-rs.workspace = true
 
 [dev-dependencies]
+ai = { path = "../ai", features = ["test-support"]}
 editor = { path = "../editor", features = ["test-support"] }
 project = { path = "../project", features = ["test-support"] }
-ai = { path = "../ai", features = ["test-support"]}
 
 ctor.workspace = true
 env_logger.workspace = true

crates/assistant/src/assistant.rs 🔗

@@ -12,12 +12,28 @@ use chrono::{DateTime, Local};
 use collections::HashMap;
 use fs::Fs;
 use futures::StreamExt;
-use gpui::AppContext;
+use gpui::{actions, AppContext, SharedString};
 use regex::Regex;
 use serde::{Deserialize, Serialize};
 use std::{cmp::Reverse, ffi::OsStr, path::PathBuf, sync::Arc};
 use util::paths::CONVERSATIONS_DIR;
 
+actions!(
+    assistant,
+    [
+        NewConversation,
+        Assist,
+        Split,
+        CycleMessageRole,
+        QuoteSelection,
+        ToggleFocus,
+        ResetKey,
+        InlineAssist,
+        ToggleIncludeConversation,
+        ToggleRetrieveContext,
+    ]
+);
+
 #[derive(
     Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize,
 )]
@@ -34,7 +50,7 @@ struct MessageMetadata {
 enum MessageStatus {
     Pending,
     Done,
-    Error(Arc<str>),
+    Error(SharedString),
 }
 
 #[derive(Serialize, Deserialize)]

crates/assistant/src/assistant_panel.rs 🔗

@@ -2,8 +2,9 @@ use crate::{
     assistant_settings::{AssistantDockPosition, AssistantSettings, OpenAIModel},
     codegen::{self, Codegen, CodegenKind},
     prompts::generate_content_prompt,
-    MessageId, MessageMetadata, MessageStatus, Role, SavedConversation, SavedConversationMetadata,
-    SavedMessage,
+    Assist, CycleMessageRole, InlineAssist, MessageId, MessageMetadata, MessageStatus,
+    NewConversation, QuoteSelection, ResetKey, Role, SavedConversation, SavedConversationMetadata,
+    SavedMessage, Split, ToggleFocus, ToggleIncludeConversation, ToggleRetrieveContext,
 };
 
 use ai::{
@@ -22,28 +23,24 @@ use editor::{
         BlockContext, BlockDisposition, BlockId, BlockProperties, BlockStyle, ToDisplayPoint,
     },
     scroll::autoscroll::{Autoscroll, AutoscrollStrategy},
-    Anchor, Editor, MoveDown, MoveUp, MultiBufferSnapshot, ToOffset, ToPoint,
+    Anchor, Editor, EditorElement, EditorEvent, EditorStyle, MoveDown, MoveUp, MultiBufferSnapshot,
+    ToOffset, ToPoint,
 };
 use fs::Fs;
 use futures::StreamExt;
 use gpui::{
-    actions,
-    elements::{
-        ChildView, Component, Empty, Flex, Label, MouseEventHandler, ParentElement, SafeStylable,
-        Stack, Svg, Text, UniformList, UniformListState,
-    },
-    fonts::HighlightStyle,
-    geometry::vector::{vec2f, Vector2F},
-    platform::{CursorStyle, MouseButton, PromptLevel},
-    Action, AnyElement, AppContext, AsyncAppContext, ClipboardItem, Element, Entity, ModelContext,
-    ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle,
-    WeakModelHandle, WeakViewHandle, WindowContext,
+    canvas, div, point, relative, rems, uniform_list, Action, AnyElement, AppContext,
+    AsyncWindowContext, AvailableSpace, ClipboardItem, Context, EventEmitter, FocusHandle,
+    FocusableView, FontStyle, FontWeight, HighlightStyle, InteractiveElement, IntoElement, Model,
+    ModelContext, ParentElement, Pixels, PromptLevel, Render, SharedString,
+    StatefulInteractiveElement, Styled, Subscription, Task, TextStyle, UniformListScrollHandle,
+    View, ViewContext, VisualContext, WeakModel, WeakView, WhiteSpace, WindowContext,
 };
 use language::{language_settings::SoftWrap, Buffer, LanguageRegistry, ToOffset as _};
 use project::Project;
 use search::BufferSearchBar;
 use semantic_index::{SemanticIndex, SemanticIndexStatus};
-use settings::SettingsStore;
+use settings::{Settings, SettingsStore};
 use std::{
     cell::Cell,
     cmp,
@@ -55,105 +52,60 @@ use std::{
     sync::Arc,
     time::{Duration, Instant},
 };
-use theme::{
-    components::{action_button::Button, ComponentExt},
-    AssistantStyle,
+use theme::ThemeSettings;
+use ui::{
+    prelude::*,
+    utils::{DateTimeType, FormatDistance},
+    ButtonLike, Tab, TabBar, Tooltip,
 };
 use util::{paths::CONVERSATIONS_DIR, post_inc, ResultExt, TryFutureExt};
 use uuid::Uuid;
 use workspace::{
-    dock::{DockPosition, Panel},
+    dock::{DockPosition, Panel, PanelEvent},
     searchable::Direction,
     Save, Toast, ToggleZoom, Toolbar, Workspace,
 };
 
-actions!(
-    assistant,
-    [
-        NewConversation,
-        Assist,
-        Split,
-        CycleMessageRole,
-        QuoteSelection,
-        ToggleFocus,
-        ResetKey,
-        InlineAssist,
-        ToggleIncludeConversation,
-        ToggleRetrieveContext,
-    ]
-);
-
 pub fn init(cx: &mut AppContext) {
-    settings::register::<AssistantSettings>(cx);
-    cx.add_action(
-        |this: &mut AssistantPanel,
-         _: &workspace::NewFile,
-         cx: &mut ViewContext<AssistantPanel>| {
-            this.new_conversation(cx);
-        },
-    );
-    cx.add_action(ConversationEditor::assist);
-    cx.capture_action(ConversationEditor::cancel_last_assist);
-    cx.capture_action(ConversationEditor::save);
-    cx.add_action(ConversationEditor::quote_selection);
-    cx.capture_action(ConversationEditor::copy);
-    cx.add_action(ConversationEditor::split);
-    cx.capture_action(ConversationEditor::cycle_message_role);
-    cx.add_action(AssistantPanel::save_credentials);
-    cx.add_action(AssistantPanel::reset_credentials);
-    cx.add_action(AssistantPanel::toggle_zoom);
-    cx.add_action(AssistantPanel::deploy);
-    cx.add_action(AssistantPanel::select_next_match);
-    cx.add_action(AssistantPanel::select_prev_match);
-    cx.add_action(AssistantPanel::handle_editor_cancel);
-    cx.add_action(
-        |workspace: &mut Workspace, _: &ToggleFocus, cx: &mut ViewContext<Workspace>| {
-            workspace.toggle_panel_focus::<AssistantPanel>(cx);
+    AssistantSettings::register(cx);
+    cx.observe_new_views(
+        |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
+            workspace
+                .register_action(|workspace, _: &ToggleFocus, cx| {
+                    workspace.toggle_panel_focus::<AssistantPanel>(cx);
+                })
+                .register_action(AssistantPanel::inline_assist)
+                .register_action(AssistantPanel::cancel_last_inline_assist)
+                .register_action(ConversationEditor::quote_selection);
         },
-    );
-    cx.add_action(AssistantPanel::inline_assist);
-    cx.add_action(AssistantPanel::cancel_last_inline_assist);
-    cx.add_action(InlineAssistant::confirm);
-    cx.add_action(InlineAssistant::cancel);
-    cx.add_action(InlineAssistant::toggle_include_conversation);
-    cx.add_action(InlineAssistant::toggle_retrieve_context);
-    cx.add_action(InlineAssistant::move_up);
-    cx.add_action(InlineAssistant::move_down);
-}
-
-#[derive(Debug)]
-pub enum AssistantPanelEvent {
-    ZoomIn,
-    ZoomOut,
-    Focus,
-    Close,
-    DockPositionChanged,
+    )
+    .detach();
 }
 
 pub struct AssistantPanel {
-    workspace: WeakViewHandle<Workspace>,
-    width: Option<f32>,
-    height: Option<f32>,
+    workspace: WeakView<Workspace>,
+    width: Option<Pixels>,
+    height: Option<Pixels>,
     active_editor_index: Option<usize>,
     prev_active_editor_index: Option<usize>,
-    editors: Vec<ViewHandle<ConversationEditor>>,
+    editors: Vec<View<ConversationEditor>>,
     saved_conversations: Vec<SavedConversationMetadata>,
-    saved_conversations_list_state: UniformListState,
+    saved_conversations_scroll_handle: UniformListScrollHandle,
     zoomed: bool,
-    has_focus: bool,
-    toolbar: ViewHandle<Toolbar>,
+    focus_handle: FocusHandle,
+    toolbar: View<Toolbar>,
     completion_provider: Arc<dyn CompletionProvider>,
-    api_key_editor: Option<ViewHandle<Editor>>,
+    api_key_editor: Option<View<Editor>>,
     languages: Arc<LanguageRegistry>,
     fs: Arc<dyn Fs>,
     subscriptions: Vec<Subscription>,
     next_inline_assist_id: usize,
     pending_inline_assists: HashMap<usize, PendingInlineAssist>,
-    pending_inline_assist_ids_by_editor: HashMap<WeakViewHandle<Editor>, Vec<usize>>,
+    pending_inline_assist_ids_by_editor: HashMap<WeakView<Editor>, Vec<usize>>,
     include_conversation_in_next_inline_assist: bool,
     inline_prompt_history: VecDeque<String>,
     _watch_saved_conversations: Task<Result<()>>,
-    semantic_index: Option<ModelHandle<SemanticIndex>>,
+    semantic_index: Option<Model<SemanticIndex>>,
     retrieve_context_in_next_inline_assist: bool,
 }
 
@@ -161,11 +113,11 @@ impl AssistantPanel {
     const INLINE_PROMPT_HISTORY_MAX_LEN: usize = 20;
 
     pub fn load(
-        workspace: WeakViewHandle<Workspace>,
-        cx: AsyncAppContext,
-    ) -> Task<Result<ViewHandle<Self>>> {
+        workspace: WeakView<Workspace>,
+        cx: AsyncWindowContext,
+    ) -> Task<Result<View<Self>>> {
         cx.spawn(|mut cx| async move {
-            let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
+            let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
             let saved_conversations = SavedConversationMetadata::list(fs.clone())
                 .await
                 .log_err()
@@ -174,7 +126,7 @@ impl AssistantPanel {
             // TODO: deserialize state.
             let workspace_handle = workspace.clone();
             workspace.update(&mut cx, |workspace, cx| {
-                cx.add_view::<Self, _>(|cx| {
+                cx.new_view::<Self>(|cx| {
                     const CONVERSATION_WATCH_DURATION: Duration = Duration::from_millis(100);
                     let _watch_saved_conversations = cx.spawn(move |this, mut cx| async move {
                         let mut events = fs
@@ -195,10 +147,10 @@ impl AssistantPanel {
                         anyhow::Ok(())
                     });
 
-                    let toolbar = cx.add_view(|cx| {
+                    let toolbar = cx.new_view(|cx| {
                         let mut toolbar = Toolbar::new();
                         toolbar.set_can_navigate(false, cx);
-                        toolbar.add_item(cx.add_view(|cx| BufferSearchBar::new(cx)), cx);
+                        toolbar.add_item(cx.new_view(|cx| BufferSearchBar::new(cx)), cx);
                         toolbar
                     });
 
@@ -206,18 +158,22 @@ impl AssistantPanel {
                     // Defaulting currently to GPT4, allow for this to be set via config.
                     let completion_provider = Arc::new(OpenAICompletionProvider::new(
                         "gpt-4",
-                        cx.background().clone(),
+                        cx.background_executor().clone(),
                     ));
 
+                    let focus_handle = cx.focus_handle();
+                    cx.on_focus_in(&focus_handle, Self::focus_in).detach();
+                    cx.on_focus_out(&focus_handle, Self::focus_out).detach();
+
                     let mut this = Self {
                         workspace: workspace_handle,
                         active_editor_index: Default::default(),
                         prev_active_editor_index: Default::default(),
                         editors: Default::default(),
                         saved_conversations,
-                        saved_conversations_list_state: Default::default(),
+                        saved_conversations_scroll_handle: Default::default(),
                         zoomed: false,
-                        has_focus: false,
+                        focus_handle,
                         toolbar,
                         completion_provider,
                         api_key_editor: None,
@@ -238,11 +194,11 @@ impl AssistantPanel {
 
                     let mut old_dock_position = this.position(cx);
                     this.subscriptions =
-                        vec![cx.observe_global::<SettingsStore, _>(move |this, cx| {
+                        vec![cx.observe_global::<SettingsStore>(move |this, cx| {
                             let new_dock_position = this.position(cx);
                             if new_dock_position != old_dock_position {
                                 old_dock_position = new_dock_position;
-                                cx.emit(AssistantPanelEvent::DockPositionChanged);
+                                cx.emit(PanelEvent::ChangePosition);
                             }
                             cx.notify();
                         })];
@@ -253,6 +209,25 @@ impl AssistantPanel {
         })
     }
 
+    fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
+        self.toolbar
+            .update(cx, |toolbar, cx| toolbar.focus_changed(true, cx));
+        cx.notify();
+        if self.focus_handle.is_focused(cx) {
+            if let Some(editor) = self.active_editor() {
+                cx.focus_view(editor);
+            } else if let Some(api_key_editor) = self.api_key_editor.as_ref() {
+                cx.focus_view(api_key_editor);
+            }
+        }
+    }
+
+    fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
+        self.toolbar
+            .update(cx, |toolbar, cx| toolbar.focus_changed(false, cx));
+        cx.notify();
+    }
+
     pub fn inline_assist(
         workspace: &mut Workspace,
         _: &InlineAssist,
@@ -293,9 +268,9 @@ impl AssistantPanel {
 
     fn new_inline_assist(
         &mut self,
-        editor: &ViewHandle<Editor>,
+        editor: &View<Editor>,
         cx: &mut ViewContext<Self>,
-        project: &ModelHandle<Project>,
+        project: &Model<Project>,
     ) {
         let selection = editor.read(cx).selections.newest_anchor().clone();
         if selection.start.excerpt_id != selection.end.excerpt_id {
@@ -331,7 +306,7 @@ impl AssistantPanel {
         // Retrieve Credentials Authenticates the Provider
         provider.retrieve_credentials(cx);
 
-        let codegen = cx.add_model(|cx| {
+        let codegen = cx.new_model(|cx| {
             Codegen::new(editor.read(cx).buffer().clone(), codegen_kind, provider, cx)
         });
 
@@ -341,14 +316,14 @@ impl AssistantPanel {
                 let previously_indexed = semantic_index
                     .update(&mut cx, |index, cx| {
                         index.project_previously_indexed(&project, cx)
-                    })
+                    })?
                     .await
                     .unwrap_or(false);
                 if previously_indexed {
                     let _ = semantic_index
                         .update(&mut cx, |index, cx| {
                             index.index_project(project.clone(), cx)
-                        })
+                        })?
                         .await;
                 }
                 anyhow::Ok(())
@@ -357,8 +332,8 @@ impl AssistantPanel {
         }
 
         let measurements = Rc::new(Cell::new(BlockMeasurements::default()));
-        let inline_assistant = cx.add_view(|cx| {
-            let assistant = InlineAssistant::new(
+        let inline_assistant = cx.new_view(|cx| {
+            InlineAssistant::new(
                 inline_assist_id,
                 measurements.clone(),
                 self.include_conversation_in_next_inline_assist,
@@ -369,9 +344,7 @@ impl AssistantPanel {
                 self.retrieve_context_in_next_inline_assist,
                 self.semantic_index.clone(),
                 project.clone(),
-            );
-            cx.focus_self();
-            assistant
+            )
         });
         let block_id = editor.update(cx, |editor, cx| {
             editor.change_selections(None, cx, |selections| {
@@ -389,7 +362,7 @@ impl AssistantPanel {
                                 anchor_x: cx.anchor_x,
                                 gutter_width: cx.gutter_width,
                             });
-                            ChildView::new(&inline_assistant, cx).into_any()
+                            inline_assistant.clone().into_any_element()
                         }
                     }),
                     disposition: if selection.reversed {
@@ -415,10 +388,12 @@ impl AssistantPanel {
                     cx.subscribe(editor, {
                         let inline_assistant = inline_assistant.downgrade();
                         move |_, editor, event, cx| {
-                            if let Some(inline_assistant) = inline_assistant.upgrade(cx) {
-                                if let editor::Event::SelectionsChanged { local } = event {
-                                    if *local && inline_assistant.read(cx).has_focus {
-                                        cx.focus(&editor);
+                            if let Some(inline_assistant) = inline_assistant.upgrade() {
+                                if let EditorEvent::SelectionsChanged { local } = event {
+                                    if *local
+                                        && inline_assistant.focus_handle(cx).contains_focused(cx)
+                                    {
+                                        cx.focus_view(&editor);
                                     }
                                 }
                             }
@@ -427,7 +402,7 @@ impl AssistantPanel {
                     cx.observe(&codegen, {
                         let editor = editor.downgrade();
                         move |this, _, cx| {
-                            if let Some(editor) = editor.upgrade(cx) {
+                            if let Some(editor) = editor.upgrade() {
                                 this.update_highlights_for_editor(&editor, cx);
                             }
                         }
@@ -451,7 +426,7 @@ impl AssistantPanel {
                                 .map(|error| format!("Inline assistant error: {}", error));
                             if let Some(error) = error {
                                 if pending_assist.inline_assistant.is_none() {
-                                    if let Some(workspace) = this.workspace.upgrade(cx) {
+                                    if let Some(workspace) = this.workspace.upgrade() {
                                         workspace.update(cx, |workspace, cx| {
                                             workspace.show_toast(
                                                 Toast::new(inline_assist_id, error),
@@ -479,7 +454,7 @@ impl AssistantPanel {
 
     fn handle_inline_assistant_event(
         &mut self,
-        inline_assistant: ViewHandle<InlineAssistant>,
+        inline_assistant: View<InlineAssistant>,
         event: &InlineAssistantEvent,
         cx: &mut ViewContext<Self>,
     ) {
@@ -543,7 +518,7 @@ impl AssistantPanel {
             }
         }
 
-        cx.propagate_action();
+        cx.propagate();
     }
 
     fn finish_inline_assist(&mut self, assist_id: usize, undo: bool, cx: &mut ViewContext<Self>) {
@@ -552,7 +527,7 @@ impl AssistantPanel {
         if let Some(pending_assist) = self.pending_inline_assists.remove(&assist_id) {
             if let hash_map::Entry::Occupied(mut entry) = self
                 .pending_inline_assist_ids_by_editor
-                .entry(pending_assist.editor)
+                .entry(pending_assist.editor.clone())
             {
                 entry.get_mut().retain(|id| *id != assist_id);
                 if entry.get().is_empty() {
@@ -560,7 +535,7 @@ impl AssistantPanel {
                 }
             }
 
-            if let Some(editor) = pending_assist.editor.upgrade(cx) {
+            if let Some(editor) = pending_assist.editor.upgrade() {
                 self.update_highlights_for_editor(&editor, cx);
 
                 if undo {
@@ -574,10 +549,13 @@ impl AssistantPanel {
 
     fn hide_inline_assist(&mut self, assist_id: usize, cx: &mut ViewContext<Self>) {
         if let Some(pending_assist) = self.pending_inline_assists.get_mut(&assist_id) {
-            if let Some(editor) = pending_assist.editor.upgrade(cx) {
-                if let Some((block_id, _)) = pending_assist.inline_assistant.take() {
+            if let Some(editor) = pending_assist.editor.upgrade() {
+                if let Some((block_id, inline_assistant)) = pending_assist.inline_assistant.take() {
                     editor.update(cx, |editor, cx| {
                         editor.remove_blocks(HashSet::from_iter([block_id]), None, cx);
+                        if inline_assistant.focus_handle(cx).contains_focused(cx) {
+                            editor.focus(cx);
+                        }
                     });
                 }
             }
@@ -606,7 +584,7 @@ impl AssistantPanel {
                 return;
             };
 
-        let editor = if let Some(editor) = pending_assist.editor.upgrade(cx) {
+        let editor = if let Some(editor) = pending_assist.editor.upgrade() {
             editor
         } else {
             return;
@@ -614,7 +592,7 @@ impl AssistantPanel {
 
         let project = pending_assist.project.clone();
 
-        let project_name = if let Some(project) = project.upgrade(cx) {
+        let project_name = if let Some(project) = project.upgrade() {
             Some(
                 project
                     .read(cx)
@@ -679,7 +657,7 @@ impl AssistantPanel {
         let user_prompt = user_prompt.to_string();
 
         let snippets = if retrieve_context {
-            let Some(project) = project.upgrade(cx) else {
+            let Some(project) = project.upgrade() else {
                 return;
             };
 
@@ -688,31 +666,35 @@ impl AssistantPanel {
                     this.search_project(project, user_prompt.to_string(), 10, vec![], vec![], cx)
                 });
 
-                cx.background()
+                cx.background_executor()
                     .spawn(async move { search_results.await.unwrap_or_default() })
             } else {
                 Task::ready(Vec::new())
             };
 
-            let snippets = cx.spawn(|_, cx| async move {
+            let snippets = cx.spawn(|_, mut cx| async move {
                 let mut snippets = Vec::new();
                 for result in search_results.await {
-                    snippets.push(PromptCodeSnippet::new(result.buffer, result.range, &cx));
+                    snippets.push(PromptCodeSnippet::new(
+                        result.buffer,
+                        result.range,
+                        &mut cx,
+                    )?);
                 }
-                snippets
+                anyhow::Ok(snippets)
             });
             snippets
         } else {
-            Task::ready(Vec::new())
+            Task::ready(Ok(Vec::new()))
         };
 
-        let mut model = settings::get::<AssistantSettings>(cx)
+        let mut model = AssistantSettings::get_global(cx)
             .default_open_ai_model
             .clone();
         let model_name = model.full_name();
 
-        let prompt = cx.background().spawn(async move {
-            let snippets = snippets.await;
+        let prompt = cx.background_executor().spawn(async move {
+            let snippets = snippets.await?;
 
             let language_name = language_name.as_deref();
             generate_content_prompt(
@@ -755,17 +737,13 @@ impl AssistantPanel {
                 temperature,
             });
 
-            codegen.update(&mut cx, |codegen, cx| codegen.start(request, cx));
+            codegen.update(&mut cx, |codegen, cx| codegen.start(request, cx))?;
             anyhow::Ok(())
         })
         .detach();
     }
 
-    fn update_highlights_for_editor(
-        &self,
-        editor: &ViewHandle<Editor>,
-        cx: &mut ViewContext<Self>,
-    ) {
+    fn update_highlights_for_editor(&self, editor: &View<Editor>, cx: &mut ViewContext<Self>) {
         let mut background_ranges = Vec::new();
         let mut foreground_ranges = Vec::new();
         let empty_inline_assist_ids = Vec::new();
@@ -791,7 +769,7 @@ impl AssistantPanel {
             } else {
                 editor.highlight_background::<PendingInlineAssist>(
                     background_ranges,
-                    |theme| theme.assistant.inline.pending_edit_background,
+                    |theme| theme.editor_active_line_background, // todo!("use the appropriate color")
                     cx,
                 );
             }
@@ -811,8 +789,8 @@ impl AssistantPanel {
         });
     }
 
-    fn new_conversation(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<ConversationEditor> {
-        let editor = cx.add_view(|cx| {
+    fn new_conversation(&mut self, cx: &mut ViewContext<Self>) -> View<ConversationEditor> {
+        let editor = cx.new_view(|cx| {
             ConversationEditor::new(
                 self.completion_provider.clone(),
                 self.languages.clone(),
@@ -825,11 +803,7 @@ impl AssistantPanel {
         editor
     }
 
-    fn add_conversation(
-        &mut self,
-        editor: ViewHandle<ConversationEditor>,
-        cx: &mut ViewContext<Self>,
-    ) {
+    fn add_conversation(&mut self, editor: View<ConversationEditor>, cx: &mut ViewContext<Self>) {
         self.subscriptions
             .push(cx.subscribe(&editor, Self::handle_conversation_editor_event));
 
@@ -850,8 +824,8 @@ impl AssistantPanel {
             self.toolbar.update(cx, |toolbar, cx| {
                 toolbar.set_active_item(Some(&editor), cx);
             });
-            if self.has_focus(cx) {
-                cx.focus(&editor);
+            if self.focus_handle.contains_focused(cx) {
+                cx.focus_view(&editor);
             }
         } else {
             self.toolbar.update(cx, |toolbar, cx| {
@@ -864,7 +838,7 @@ impl AssistantPanel {
 
     fn handle_conversation_editor_event(
         &mut self,
-        _: ViewHandle<ConversationEditor>,
+        _: View<ConversationEditor>,
         event: &ConversationEditorEvent,
         cx: &mut ViewContext<Self>,
     ) {
@@ -887,45 +861,46 @@ impl AssistantPanel {
                 self.completion_provider.save_credentials(cx, credential);
 
                 self.api_key_editor.take();
-                cx.focus_self();
+                self.focus_handle.focus(cx);
                 cx.notify();
             }
         } else {
-            cx.propagate_action();
+            cx.propagate();
         }
     }
 
     fn reset_credentials(&mut self, _: &ResetKey, cx: &mut ViewContext<Self>) {
         self.completion_provider.delete_credentials(cx);
         self.api_key_editor = Some(build_api_key_editor(cx));
-        cx.focus_self();
+        self.focus_handle.focus(cx);
         cx.notify();
     }
 
     fn toggle_zoom(&mut self, _: &workspace::ToggleZoom, cx: &mut ViewContext<Self>) {
         if self.zoomed {
-            cx.emit(AssistantPanelEvent::ZoomOut)
+            cx.emit(PanelEvent::ZoomOut)
         } else {
-            cx.emit(AssistantPanelEvent::ZoomIn)
+            cx.emit(PanelEvent::ZoomIn)
         }
     }
 
     fn deploy(&mut self, action: &search::buffer_search::Deploy, cx: &mut ViewContext<Self>) {
-        let mut propagate_action = true;
+        let mut propagate = true;
         if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
             search_bar.update(cx, |search_bar, cx| {
                 if search_bar.show(cx) {
                     search_bar.search_suggested(cx);
                     if action.focus {
+                        let focus_handle = search_bar.focus_handle(cx);
                         search_bar.select_query(cx);
-                        cx.focus_self();
+                        cx.focus(&focus_handle);
                     }
-                    propagate_action = false
+                    propagate = false
                 }
             });
         }
-        if propagate_action {
-            cx.propagate_action();
+        if propagate {
+            cx.propagate();
         }
     }
 
@@ -938,7 +913,7 @@ impl AssistantPanel {
                 return;
             }
         }
-        cx.propagate_action();
+        cx.propagate();
     }
 
     fn select_next_match(&mut self, _: &search::SelectNextMatch, cx: &mut ViewContext<Self>) {
@@ -953,201 +928,125 @@ impl AssistantPanel {
         }
     }
 
-    fn active_editor(&self) -> Option<&ViewHandle<ConversationEditor>> {
+    fn active_editor(&self) -> Option<&View<ConversationEditor>> {
         self.editors.get(self.active_editor_index?)
     }
 
-    fn render_hamburger_button(cx: &mut ViewContext<Self>) -> impl Element<Self> {
-        enum History {}
-        let theme = theme::current(cx);
-        let tooltip_style = theme::current(cx).tooltip.clone();
-        MouseEventHandler::new::<History, _>(0, cx, |state, _| {
-            let style = theme.assistant.hamburger_button.style_for(state);
-            Svg::for_style(style.icon.clone())
-                .contained()
-                .with_style(style.container)
-        })
-        .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, |_, this: &mut Self, cx| {
-            if this.active_editor().is_some() {
-                this.set_active_editor_index(None, cx);
-            } else {
-                this.set_active_editor_index(this.prev_active_editor_index, cx);
-            }
-        })
-        .with_tooltip::<History>(1, "History", None, tooltip_style, cx)
+    fn render_hamburger_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
+        IconButton::new("hamburger_button", Icon::Menu)
+            .on_click(cx.listener(|this, _event, cx| {
+                if this.active_editor().is_some() {
+                    this.set_active_editor_index(None, cx);
+                } else {
+                    this.set_active_editor_index(this.prev_active_editor_index, cx);
+                }
+            }))
+            .tooltip(|cx| Tooltip::text("Conversation History", cx))
     }
 
-    fn render_editor_tools(&self, cx: &mut ViewContext<Self>) -> Vec<AnyElement<Self>> {
+    fn render_editor_tools(&self, cx: &mut ViewContext<Self>) -> Vec<AnyElement> {
         if self.active_editor().is_some() {
             vec![
-                Self::render_split_button(cx).into_any(),
-                Self::render_quote_button(cx).into_any(),
-                Self::render_assist_button(cx).into_any(),
+                Self::render_split_button(cx).into_any_element(),
+                Self::render_quote_button(cx).into_any_element(),
+                Self::render_assist_button(cx).into_any_element(),
             ]
         } else {
             Default::default()
         }
     }
 
-    fn render_split_button(cx: &mut ViewContext<Self>) -> impl Element<Self> {
-        let theme = theme::current(cx);
-        let tooltip_style = theme::current(cx).tooltip.clone();
-        MouseEventHandler::new::<Split, _>(0, cx, |state, _| {
-            let style = theme.assistant.split_button.style_for(state);
-            Svg::for_style(style.icon.clone())
-                .contained()
-                .with_style(style.container)
-        })
-        .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, |_, this: &mut Self, cx| {
-            if let Some(active_editor) = this.active_editor() {
-                active_editor.update(cx, |editor, cx| editor.split(&Default::default(), cx));
-            }
-        })
-        .with_tooltip::<Split>(
-            1,
-            "Split Message",
-            Some(Box::new(Split)),
-            tooltip_style,
-            cx,
-        )
+    fn render_split_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
+        IconButton::new("split_button", Icon::Snip)
+            .on_click(cx.listener(|this, _event, cx| {
+                if let Some(active_editor) = this.active_editor() {
+                    active_editor.update(cx, |editor, cx| editor.split(&Default::default(), cx));
+                }
+            }))
+            .icon_size(IconSize::Small)
+            .tooltip(|cx| Tooltip::for_action("Split Message", &Split, cx))
     }
 
-    fn render_assist_button(cx: &mut ViewContext<Self>) -> impl Element<Self> {
-        let theme = theme::current(cx);
-        let tooltip_style = theme::current(cx).tooltip.clone();
-        MouseEventHandler::new::<Assist, _>(0, cx, |state, _| {
-            let style = theme.assistant.assist_button.style_for(state);
-            Svg::for_style(style.icon.clone())
-                .contained()
-                .with_style(style.container)
-        })
-        .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, |_, this: &mut Self, cx| {
-            if let Some(active_editor) = this.active_editor() {
-                active_editor.update(cx, |editor, cx| editor.assist(&Default::default(), cx));
-            }
-        })
-        .with_tooltip::<Assist>(1, "Assist", Some(Box::new(Assist)), tooltip_style, cx)
+    fn render_assist_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
+        IconButton::new("assist_button", Icon::MagicWand)
+            .on_click(cx.listener(|this, _event, cx| {
+                if let Some(active_editor) = this.active_editor() {
+                    active_editor.update(cx, |editor, cx| editor.assist(&Default::default(), cx));
+                }
+            }))
+            .icon_size(IconSize::Small)
+            .tooltip(|cx| Tooltip::for_action("Assist", &Assist, cx))
     }
 
-    fn render_quote_button(cx: &mut ViewContext<Self>) -> impl Element<Self> {
-        let theme = theme::current(cx);
-        let tooltip_style = theme::current(cx).tooltip.clone();
-        MouseEventHandler::new::<QuoteSelection, _>(0, cx, |state, _| {
-            let style = theme.assistant.quote_button.style_for(state);
-            Svg::for_style(style.icon.clone())
-                .contained()
-                .with_style(style.container)
-        })
-        .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, |_, this: &mut Self, cx| {
-            if let Some(workspace) = this.workspace.upgrade(cx) {
-                cx.window_context().defer(move |cx| {
-                    workspace.update(cx, |workspace, cx| {
-                        ConversationEditor::quote_selection(workspace, &Default::default(), cx)
+    fn render_quote_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
+        IconButton::new("quote_button", Icon::Quote)
+            .on_click(cx.listener(|this, _event, cx| {
+                if let Some(workspace) = this.workspace.upgrade() {
+                    cx.window_context().defer(move |cx| {
+                        workspace.update(cx, |workspace, cx| {
+                            ConversationEditor::quote_selection(workspace, &Default::default(), cx)
+                        });
                     });
-                });
-            }
-        })
-        .with_tooltip::<QuoteSelection>(
-            1,
-            "Quote Selection",
-            Some(Box::new(QuoteSelection)),
-            tooltip_style,
-            cx,
-        )
+                }
+            }))
+            .icon_size(IconSize::Small)
+            .tooltip(|cx| Tooltip::for_action("Quote Selection", &QuoteSelection, cx))
     }
 
-    fn render_plus_button(cx: &mut ViewContext<Self>) -> impl Element<Self> {
-        let theme = theme::current(cx);
-        let tooltip_style = theme::current(cx).tooltip.clone();
-        MouseEventHandler::new::<NewConversation, _>(0, cx, |state, _| {
-            let style = theme.assistant.plus_button.style_for(state);
-            Svg::for_style(style.icon.clone())
-                .contained()
-                .with_style(style.container)
-        })
-        .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, |_, this: &mut Self, cx| {
-            this.new_conversation(cx);
-        })
-        .with_tooltip::<NewConversation>(
-            1,
-            "New Conversation",
-            Some(Box::new(NewConversation)),
-            tooltip_style,
-            cx,
-        )
+    fn render_plus_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
+        IconButton::new("plus_button", Icon::Plus)
+            .on_click(cx.listener(|this, _event, cx| {
+                this.new_conversation(cx);
+            }))
+            .icon_size(IconSize::Small)
+            .tooltip(|cx| Tooltip::for_action("New Conversation", &NewConversation, cx))
     }
 
-    fn render_zoom_button(&self, cx: &mut ViewContext<Self>) -> impl Element<Self> {
-        enum ToggleZoomButton {}
-
-        let theme = theme::current(cx);
-        let tooltip_style = theme::current(cx).tooltip.clone();
-        let style = if self.zoomed {
-            &theme.assistant.zoom_out_button
-        } else {
-            &theme.assistant.zoom_in_button
-        };
-
-        MouseEventHandler::new::<ToggleZoomButton, _>(0, cx, |state, _| {
-            let style = style.style_for(state);
-            Svg::for_style(style.icon.clone())
-                .contained()
-                .with_style(style.container)
-        })
-        .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, |_, this, cx| {
-            this.toggle_zoom(&ToggleZoom, cx);
-        })
-        .with_tooltip::<ToggleZoom>(
-            0,
-            if self.zoomed { "Zoom Out" } else { "Zoom In" },
-            Some(Box::new(ToggleZoom)),
-            tooltip_style,
-            cx,
-        )
+    fn render_zoom_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        let zoomed = self.zoomed;
+        IconButton::new("zoom_button", Icon::Maximize)
+            .on_click(cx.listener(|this, _event, cx| {
+                this.toggle_zoom(&ToggleZoom, cx);
+            }))
+            .selected(zoomed)
+            .selected_icon(Icon::Minimize)
+            .icon_size(IconSize::Small)
+            .tooltip(move |cx| {
+                Tooltip::for_action(if zoomed { "Zoom Out" } else { "Zoom In" }, &ToggleZoom, cx)
+            })
     }
 
     fn render_saved_conversation(
         &mut self,
         index: usize,
         cx: &mut ViewContext<Self>,
-    ) -> impl Element<Self> {
+    ) -> impl IntoElement {
         let conversation = &self.saved_conversations[index];
         let path = conversation.path.clone();
-        MouseEventHandler::new::<SavedConversationMetadata, _>(index, cx, move |state, cx| {
-            let style = &theme::current(cx).assistant.saved_conversation;
-            Flex::row()
-                .with_child(
-                    Label::new(
-                        conversation.mtime.format("%F %I:%M%p").to_string(),
-                        style.saved_at.text.clone(),
+
+        ButtonLike::new(index)
+            .on_click(cx.listener(move |this, _, cx| {
+                this.open_conversation(path.clone(), cx)
+                    .detach_and_log_err(cx)
+            }))
+            .full_width()
+            .child(
+                div()
+                    .flex()
+                    .w_full()
+                    .gap_2()
+                    .child(
+                        Label::new(conversation.mtime.format("%F %I:%M%p").to_string())
+                            .color(Color::Muted)
+                            .size(LabelSize::Small),
                     )
-                    .aligned()
-                    .contained()
-                    .with_style(style.saved_at.container),
-                )
-                .with_child(
-                    Label::new(conversation.title.clone(), style.title.text.clone())
-                        .aligned()
-                        .contained()
-                        .with_style(style.title.container),
-                )
-                .contained()
-                .with_style(*style.container.style_for(state))
-        })
-        .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, move |_, this, cx| {
-            this.open_conversation(path.clone(), cx)
-                .detach_and_log_err(cx)
-        })
+                    .child(Label::new(conversation.title.clone()).size(LabelSize::Small)),
+            )
     }
 
     fn open_conversation(&mut self, path: PathBuf, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
+        cx.focus(&self.focus_handle);
+
         if let Some(ix) = self.editor_index_for_path(&path, cx) {
             self.set_active_editor_index(Some(ix), cx);
             return Task::ready(Ok(()));
@@ -1159,16 +1058,16 @@ impl AssistantPanel {
         cx.spawn(|this, mut cx| async move {
             let saved_conversation = fs.load(&path).await?;
             let saved_conversation = serde_json::from_str(&saved_conversation)?;
-            let conversation = cx.add_model(|cx| {
+            let conversation = cx.new_model(|cx| {
                 Conversation::deserialize(saved_conversation, path.clone(), languages, cx)
-            });
+            })?;
             this.update(&mut cx, |this, cx| {
                 // If, by the time we've loaded the conversation, the user has already opened
                 // the same conversation, we don't want to open it again.
                 if let Some(ix) = this.editor_index_for_path(&path, cx) {
                     this.set_active_editor_index(Some(ix), cx);
                 } else {
-                    let editor = cx.add_view(|cx| {
+                    let editor = cx.new_view(|cx| {
                         ConversationEditor::for_conversation(conversation, fs, workspace, cx)
                     });
                     this.add_conversation(editor, cx);
@@ -1193,168 +1092,133 @@ impl AssistantPanel {
     }
 }
 
-fn build_api_key_editor(cx: &mut ViewContext<AssistantPanel>) -> ViewHandle<Editor> {
-    cx.add_view(|cx| {
-        let mut editor = Editor::single_line(
-            Some(Arc::new(|theme| theme.assistant.api_key_editor.clone())),
-            cx,
-        );
+fn build_api_key_editor(cx: &mut ViewContext<AssistantPanel>) -> View<Editor> {
+    cx.new_view(|cx| {
+        let mut editor = Editor::single_line(cx);
         editor.set_placeholder_text("sk-000000000000000000000000000000000000000000000000", cx);
         editor
     })
 }
 
-impl Entity for AssistantPanel {
-    type Event = AssistantPanelEvent;
-}
-
-impl View for AssistantPanel {
-    fn ui_name() -> &'static str {
-        "AssistantPanel"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = &theme::current(cx);
-        let style = &theme.assistant;
-        if let Some(api_key_editor) = self.api_key_editor.as_ref() {
-            Flex::column()
-                .with_child(
-                    Text::new(
-                        "To use the assistant panel or inline assistant, you need to add your OpenAI api key.",
-                        style.api_key_prompt.text.clone(),
-                    ),
-                )
-                .with_child(
-                    Text::new(
-                        " - Having a subscription for another service like GitHub Copilot won't work.",
-                        style.api_key_prompt.text.clone(),
-                    ),
-                )
-                .with_child(
-                    Text::new(
-                        " - You can create a api key at: platform.openai.com/api-keys",
-                        style.api_key_prompt.text.clone(),
-                    ),
-                )
-                .with_child(
-                    Text::new(
-                        " ",
-                        style.api_key_prompt.text.clone(),
-                    )
-                    .aligned(),
-                )
-                .with_child(
-                    Text::new(
-                        "Paste your OpenAI API key and press Enter to use the assistant",
-                        style.api_key_prompt.text.clone(),
-                    )
-                    .aligned(),
-                )
-                .with_child(
-                    ChildView::new(api_key_editor, cx)
-                        .contained()
-                        .with_style(style.api_key_editor.container)
-                        .aligned(),
-                )
-                .with_child(
-                    Text::new(
-                        " ",
-                        style.api_key_prompt.text.clone(),
-                    )
-                    .aligned(),
-                )
-                .with_child(
-                    Text::new(
-                        "Click on the Z button in the status bar to close this panel.",
-                        style.api_key_prompt.text.clone(),
-                    )
-                    .aligned(),
-                )
-                .contained()
-                .with_style(style.api_key_prompt.container)
-                .aligned()
-                .into_any()
+impl Render for AssistantPanel {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        if let Some(api_key_editor) = self.api_key_editor.clone() {
+            v_stack()
+                .on_action(cx.listener(AssistantPanel::save_credentials))
+                .track_focus(&self.focus_handle)
+                .child(Label::new(
+                    "To use the assistant panel or inline assistant, you need to add your OpenAI api key.",
+                ))
+                .child(Label::new(
+                    " - Having a subscription for another service like GitHub Copilot won't work."
+                ))
+                .child(Label::new(
+                    " - You can create a api key at: platform.openai.com/api-keys"
+                ))
+                .child(Label::new(
+                    " "
+                ))
+                .child(Label::new(
+                    "Paste your OpenAI API key and press Enter to use the assistant"
+                ))
+                .child(api_key_editor)
+                .child(Label::new(
+                    "Click on the Z button in the status bar to close this panel."
+                ))
+                .border()
+                .border_color(gpui::red())
         } else {
-            let title = self.active_editor().map(|editor| {
-                Label::new(editor.read(cx).title(cx), style.title.text.clone())
-                    .contained()
-                    .with_style(style.title.container)
-                    .aligned()
-                    .left()
-                    .flex(1., false)
-            });
-            let mut header = Flex::row()
-                .with_child(Self::render_hamburger_button(cx).aligned())
-                .with_children(title);
-            if self.has_focus {
-                header.add_children(
-                    self.render_editor_tools(cx)
-                        .into_iter()
-                        .map(|tool| tool.aligned().flex_float()),
-                );
-                header.add_child(Self::render_plus_button(cx).aligned().flex_float());
-                header.add_child(self.render_zoom_button(cx).aligned());
-            }
-
-            Flex::column()
-                .with_child(
-                    header
-                        .contained()
-                        .with_style(theme.workspace.tab_bar.container)
-                        .expanded()
-                        .constrained()
-                        .with_height(theme.workspace.tab_bar.height),
+            let header = TabBar::new("assistant_header")
+                .start_child(
+                    h_stack().gap_1().child(Self::render_hamburger_button(cx)), // .children(title),
                 )
-                .with_children(if self.toolbar.read(cx).hidden() {
-                    None
+                .children(self.active_editor().map(|editor| {
+                    h_stack()
+                        .h(rems(Tab::HEIGHT_IN_REMS))
+                        .flex_1()
+                        .px_2()
+                        .child(Label::new(editor.read(cx).title(cx)).into_element())
+                }))
+                .end_child(if self.focus_handle.contains_focused(cx) {
+                    h_stack()
+                        .gap_2()
+                        .child(h_stack().gap_1().children(self.render_editor_tools(cx)))
+                        .child(
+                            ui::Divider::vertical()
+                                .inset()
+                                .color(ui::DividerColor::Border),
+                        )
+                        .child(
+                            h_stack()
+                                .gap_1()
+                                .child(Self::render_plus_button(cx))
+                                .child(self.render_zoom_button(cx)),
+                        )
                 } else {
-                    Some(ChildView::new(&self.toolbar, cx).expanded())
-                })
-                .with_child(if let Some(editor) = self.active_editor() {
-                    ChildView::new(editor, cx).flex(1., true).into_any()
+                    div()
+                });
+
+            v_stack()
+                .size_full()
+                .on_action(cx.listener(|this, _: &workspace::NewFile, cx| {
+                    this.new_conversation(cx);
+                }))
+                .on_action(cx.listener(AssistantPanel::reset_credentials))
+                .on_action(cx.listener(AssistantPanel::toggle_zoom))
+                .on_action(cx.listener(AssistantPanel::deploy))
+                .on_action(cx.listener(AssistantPanel::select_next_match))
+                .on_action(cx.listener(AssistantPanel::select_prev_match))
+                .on_action(cx.listener(AssistantPanel::handle_editor_cancel))
+                .track_focus(&self.focus_handle)
+                .child(header)
+                .children(if self.toolbar.read(cx).hidden() {
+                    None
                 } else {
-                    UniformList::new(
-                        self.saved_conversations_list_state.clone(),
-                        self.saved_conversations.len(),
-                        cx,
-                        |this, range, items, cx| {
-                            for ix in range {
-                                items.push(this.render_saved_conversation(ix, cx).into_any());
-                            }
-                        },
-                    )
-                    .flex(1., true)
-                    .into_any()
+                    Some(self.toolbar.clone())
                 })
-                .into_any()
-        }
-    }
-
-    fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
-        self.has_focus = true;
-        self.toolbar
-            .update(cx, |toolbar, cx| toolbar.focus_changed(true, cx));
-        cx.notify();
-        if cx.is_self_focused() {
-            if let Some(editor) = self.active_editor() {
-                cx.focus(editor);
-            } else if let Some(api_key_editor) = self.api_key_editor.as_ref() {
-                cx.focus(api_key_editor);
-            }
+                .child(
+                    div()
+                        .flex_1()
+                        .child(if let Some(editor) = self.active_editor() {
+                            editor.clone().into_any_element()
+                        } else {
+                            let view = cx.view().clone();
+                            let scroll_handle = self.saved_conversations_scroll_handle.clone();
+                            let conversation_count = self.saved_conversations.len();
+                            canvas(move |bounds, cx| {
+                                uniform_list(
+                                    view,
+                                    "saved_conversations",
+                                    conversation_count,
+                                    |this, range, cx| {
+                                        range
+                                            .map(|ix| this.render_saved_conversation(ix, cx))
+                                            .collect()
+                                    },
+                                )
+                                .track_scroll(scroll_handle)
+                                .into_any_element()
+                                .draw(
+                                    bounds.origin,
+                                    bounds.size.map(AvailableSpace::Definite),
+                                    cx,
+                                );
+                            })
+                            .size_full()
+                            .into_any_element()
+                        }),
+                )
         }
     }
-
-    fn focus_out(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
-        self.has_focus = false;
-        self.toolbar
-            .update(cx, |toolbar, cx| toolbar.focus_changed(false, cx));
-        cx.notify();
-    }
 }
 
 impl Panel for AssistantPanel {
+    fn persistent_name() -> &'static str {
+        "AssistantPanel"
+    }
+
     fn position(&self, cx: &WindowContext) -> DockPosition {
-        match settings::get::<AssistantSettings>(cx).dock {
+        match AssistantSettings::get_global(cx).dock {
             AssistantDockPosition::Left => DockPosition::Left,
             AssistantDockPosition::Bottom => DockPosition::Bottom,
             AssistantDockPosition::Right => DockPosition::Right,

crates/assistant/src/assistant_settings.rs 🔗

@@ -1,7 +1,8 @@
 use anyhow;
+use gpui::Pixels;
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
-use settings::Setting;
+use settings::Settings;
 
 #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
 pub enum OpenAIModel {
@@ -51,8 +52,8 @@ pub enum AssistantDockPosition {
 pub struct AssistantSettings {
     pub button: bool,
     pub dock: AssistantDockPosition,
-    pub default_width: f32,
-    pub default_height: f32,
+    pub default_width: Pixels,
+    pub default_height: Pixels,
     pub default_open_ai_model: OpenAIModel,
 }
 
@@ -65,7 +66,7 @@ pub struct AssistantSettingsContent {
     pub default_open_ai_model: Option<OpenAIModel>,
 }
 
-impl Setting for AssistantSettings {
+impl Settings for AssistantSettings {
     const KEY: Option<&'static str> = Some("assistant");
 
     type FileContent = AssistantSettingsContent;
@@ -73,7 +74,7 @@ impl Setting for AssistantSettings {
     fn load(
         default_value: &Self::FileContent,
         user_values: &[&Self::FileContent],
-        _: &gpui::AppContext,
+        _: &mut gpui::AppContext,
     ) -> anyhow::Result<Self> {
         Self::load_via_json_merge(default_value, user_values)
     }

crates/assistant/src/codegen.rs 🔗

@@ -3,7 +3,7 @@ use ai::completion::{CompletionProvider, CompletionRequest};
 use anyhow::Result;
 use editor::{Anchor, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint};
 use futures::{channel::mpsc, SinkExt, Stream, StreamExt};
-use gpui::{Entity, ModelContext, ModelHandle, Task};
+use gpui::{EventEmitter, Model, ModelContext, Task};
 use language::{Rope, TransactionId};
 use multi_buffer;
 use std::{cmp, future, ops::Range, sync::Arc};
@@ -21,7 +21,7 @@ pub enum CodegenKind {
 
 pub struct Codegen {
     provider: Arc<dyn CompletionProvider>,
-    buffer: ModelHandle<MultiBuffer>,
+    buffer: Model<MultiBuffer>,
     snapshot: MultiBufferSnapshot,
     kind: CodegenKind,
     last_equal_ranges: Vec<Range<Anchor>>,
@@ -32,13 +32,11 @@ pub struct Codegen {
     _subscription: gpui::Subscription,
 }
 
-impl Entity for Codegen {
-    type Event = Event;
-}
+impl EventEmitter<Event> for Codegen {}
 
 impl Codegen {
     pub fn new(
-        buffer: ModelHandle<MultiBuffer>,
+        buffer: Model<MultiBuffer>,
         kind: CodegenKind,
         provider: Arc<dyn CompletionProvider>,
         cx: &mut ModelContext<Self>,
@@ -60,7 +58,7 @@ impl Codegen {
 
     fn handle_buffer_event(
         &mut self,
-        _buffer: ModelHandle<MultiBuffer>,
+        _buffer: Model<MultiBuffer>,
         event: &multi_buffer::Event,
         cx: &mut ModelContext<Self>,
     ) {
@@ -111,13 +109,13 @@ impl Codegen {
             .unwrap_or_else(|| snapshot.indent_size_for_line(selection_start.row));
 
         let response = self.provider.complete(prompt);
-        self.generation = cx.spawn_weak(|this, mut cx| {
+        self.generation = cx.spawn(|this, mut cx| {
             async move {
                 let generate = async {
                     let mut edit_start = range.start.to_offset(&snapshot);
 
                     let (mut hunks_tx, mut hunks_rx) = mpsc::channel(1);
-                    let diff = cx.background().spawn(async move {
+                    let diff = cx.background_executor().spawn(async move {
                         let chunks = strip_invalid_spans_from_codeblock(response.await?);
                         futures::pin_mut!(chunks);
                         let mut diff = StreamingDiff::new(selected_text.to_string());
@@ -183,12 +181,6 @@ impl Codegen {
                     });
 
                     while let Some(hunks) = hunks_rx.next().await {
-                        let this = if let Some(this) = this.upgrade(&cx) {
-                            this
-                        } else {
-                            break;
-                        };
-
                         this.update(&mut cx, |this, cx| {
                             this.last_equal_ranges.clear();
 
@@ -245,7 +237,7 @@ impl Codegen {
                             }
 
                             cx.notify();
-                        });
+                        })?;
                     }
 
                     diff.await?;
@@ -253,17 +245,16 @@ impl Codegen {
                 };
 
                 let result = generate.await;
-                if let Some(this) = this.upgrade(&cx) {
-                    this.update(&mut cx, |this, cx| {
-                        this.last_equal_ranges.clear();
-                        this.idle = true;
-                        if let Err(error) = result {
-                            this.error = Some(error);
-                        }
-                        cx.emit(Event::Finished);
-                        cx.notify();
-                    });
-                }
+                this.update(&mut cx, |this, cx| {
+                    this.last_equal_ranges.clear();
+                    this.idle = true;
+                    if let Err(error) = result {
+                        this.error = Some(error);
+                    }
+                    cx.emit(Event::Finished);
+                    cx.notify();
+                })
+                .ok();
             }
         });
         self.error.take();
@@ -372,7 +363,7 @@ mod tests {
     use super::*;
     use ai::test::FakeCompletionProvider;
     use futures::stream::{self};
-    use gpui::{executor::Deterministic, TestAppContext};
+    use gpui::{Context, TestAppContext};
     use indoc::indoc;
     use language::{language_settings, tree_sitter_rust, Buffer, Language, LanguageConfig, Point};
     use rand::prelude::*;
@@ -391,12 +382,8 @@ mod tests {
     }
 
     #[gpui::test(iterations = 10)]
-    async fn test_transform_autoindent(
-        cx: &mut TestAppContext,
-        mut rng: StdRng,
-        deterministic: Arc<Deterministic>,
-    ) {
-        cx.set_global(cx.read(SettingsStore::test));
+    async fn test_transform_autoindent(cx: &mut TestAppContext, mut rng: StdRng) {
+        cx.set_global(cx.update(SettingsStore::test));
         cx.update(language_settings::init);
 
         let text = indoc! {"
@@ -408,14 +395,14 @@ mod tests {
             }
         "};
         let buffer =
-            cx.add_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
-        let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
+            cx.new_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
+        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
         let range = buffer.read_with(cx, |buffer, cx| {
             let snapshot = buffer.snapshot(cx);
             snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(4, 5))
         });
         let provider = Arc::new(FakeCompletionProvider::new());
-        let codegen = cx.add_model(|cx| {
+        let codegen = cx.new_model(|cx| {
             Codegen::new(
                 buffer.clone(),
                 CodegenKind::Transform { range },
@@ -442,10 +429,10 @@ mod tests {
             println!("CHUNK: {:?}", &chunk);
             provider.send_completion(chunk);
             new_text = suffix;
-            deterministic.run_until_parked();
+            cx.background_executor.run_until_parked();
         }
         provider.finish_completion();
-        deterministic.run_until_parked();
+        cx.background_executor.run_until_parked();
 
         assert_eq!(
             buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
@@ -464,9 +451,8 @@ mod tests {
     async fn test_autoindent_when_generating_past_indentation(
         cx: &mut TestAppContext,
         mut rng: StdRng,
-        deterministic: Arc<Deterministic>,
     ) {
-        cx.set_global(cx.read(SettingsStore::test));
+        cx.set_global(cx.update(SettingsStore::test));
         cx.update(language_settings::init);
 
         let text = indoc! {"
@@ -475,14 +461,14 @@ mod tests {
             }
         "};
         let buffer =
-            cx.add_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
-        let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
+            cx.new_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
+        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
         let position = buffer.read_with(cx, |buffer, cx| {
             let snapshot = buffer.snapshot(cx);
             snapshot.anchor_before(Point::new(1, 6))
         });
         let provider = Arc::new(FakeCompletionProvider::new());
-        let codegen = cx.add_model(|cx| {
+        let codegen = cx.new_model(|cx| {
             Codegen::new(
                 buffer.clone(),
                 CodegenKind::Generate { position },
@@ -508,10 +494,10 @@ mod tests {
             let (chunk, suffix) = new_text.split_at(len);
             provider.send_completion(chunk);
             new_text = suffix;
-            deterministic.run_until_parked();
+            cx.background_executor.run_until_parked();
         }
         provider.finish_completion();
-        deterministic.run_until_parked();
+        cx.background_executor.run_until_parked();
 
         assert_eq!(
             buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
@@ -530,9 +516,8 @@ mod tests {
     async fn test_autoindent_when_generating_before_indentation(
         cx: &mut TestAppContext,
         mut rng: StdRng,
-        deterministic: Arc<Deterministic>,
     ) {
-        cx.set_global(cx.read(SettingsStore::test));
+        cx.set_global(cx.update(SettingsStore::test));
         cx.update(language_settings::init);
 
         let text = concat!(
@@ -541,14 +526,14 @@ mod tests {
             "}\n" //
         );
         let buffer =
-            cx.add_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
-        let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
+            cx.new_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
+        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
         let position = buffer.read_with(cx, |buffer, cx| {
             let snapshot = buffer.snapshot(cx);
             snapshot.anchor_before(Point::new(1, 2))
         });
         let provider = Arc::new(FakeCompletionProvider::new());
-        let codegen = cx.add_model(|cx| {
+        let codegen = cx.new_model(|cx| {
             Codegen::new(
                 buffer.clone(),
                 CodegenKind::Generate { position },
@@ -575,10 +560,10 @@ mod tests {
             println!("{:?}", &chunk);
             provider.send_completion(chunk);
             new_text = suffix;
-            deterministic.run_until_parked();
+            cx.background_executor.run_until_parked();
         }
         provider.finish_completion();
-        deterministic.run_until_parked();
+        cx.background_executor.run_until_parked();
 
         assert_eq!(
             buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),

crates/assistant/src/prompts.rs 🔗

@@ -176,7 +176,7 @@ pub(crate) mod tests {
     use super::*;
     use std::sync::Arc;
 
-    use gpui::AppContext;
+    use gpui::{AppContext, Context};
     use indoc::indoc;
     use language::{language_settings, tree_sitter_rust, Buffer, Language, LanguageConfig, Point};
     use settings::SettingsStore;
@@ -227,7 +227,8 @@ pub(crate) mod tests {
 
     #[gpui::test]
     fn test_outline_for_prompt(cx: &mut AppContext) {
-        cx.set_global(SettingsStore::test(cx));
+        let settings_store = SettingsStore::test(cx);
+        cx.set_global(settings_store);
         language_settings::init(cx);
         let text = indoc! {"
             struct X {
@@ -253,7 +254,7 @@ pub(crate) mod tests {
             }
         "};
         let buffer =
-            cx.add_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
+            cx.new_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
         let snapshot = buffer.read(cx).snapshot();
 
         assert_eq!(

crates/assistant2/Cargo.toml 🔗

@@ -1,54 +0,0 @@
-[package]
-name = "assistant2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/assistant.rs"
-doctest = false
-
-[dependencies]
-ai = { package = "ai2", path = "../ai2" }
-client = { package = "client2", path = "../client2" }
-collections = { path = "../collections"}
-editor = { package = "editor2", path = "../editor2" }
-fs = { package = "fs2", path = "../fs2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-language = { package = "language2", path = "../language2" }
-menu = { package = "menu2", path = "../menu2" }
-multi_buffer = { package = "multi_buffer2", path = "../multi_buffer2" }
-project = { package = "project2", path = "../project2" }
-search = { package = "search2", path = "../search2" }
-semantic_index = { package = "semantic_index2", path = "../semantic_index2" }
-settings = { package = "settings2", path = "../settings2" }
-theme = { package = "theme2", path = "../theme2" }
-ui = { package = "ui2", path = "../ui2" }
-util = { path = "../util" }
-workspace = { package = "workspace2", path = "../workspace2" }
-
-uuid.workspace = true
-log.workspace = true
-anyhow.workspace = true
-chrono = { version = "0.4", features = ["serde"] }
-futures.workspace = true
-indoc.workspace = true
-isahc.workspace = true
-ordered-float.workspace = true
-parking_lot.workspace = true
-regex.workspace = true
-schemars.workspace = true
-serde.workspace = true
-serde_json.workspace = true
-smol.workspace = true
-tiktoken-rs.workspace = true
-
-[dev-dependencies]
-ai = { package = "ai2", path = "../ai2", features = ["test-support"]}
-editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
-project = { package = "project2", path = "../project2", features = ["test-support"] }
-
-ctor.workspace = true
-env_logger.workspace = true
-log.workspace = true
-rand.workspace = true

crates/assistant2/README.zmd 🔗

@@ -1,63 +0,0 @@
-> Hello, I'm Nathan Sobo.
-I'm currently your sole creator and the leader of Zed Industries.
-Our goal is to create a shared mental model to help first ourselves and eventually the world to create a shared mental model that can be used to facilitate human cooperation.
-
-< Hello, Nathan! Welcome back to Zed. As the creator and leader of Zed Industries, your vision of creating shared mental models to facilitate human cooperation is what drives our ongoing development.
-
-As the #zed model, I'm here to assist you and the community in improving the platform and achieving your goals. Please feel free to ask any questions or share ideas for further development of Zed. Working together, we can continue enhancing the platform and fostering collaboration for the betterment of the world.
-
-> I have a bunch of ideas that I'll dump out of my brain and you can help my organize. Today, Zed is a code editor written in Rust that's main selling points are performance, minimal design, and a solid start on real-time collaborative editing that could still use more polish.
-
-We've decided that our next milestone will be open-sourcing Zed on the Zed platform.
-
-< Nathan, it's exciting to hear that you have more ideas to enhance Zed! Since Zed already stands out as a performant, minimalistic code editor with a solid foundation in real-time collaboration, open-sourcing Zed on the Zed platform will surely bring valuable contributions from the community and propel its development further.
-
-Let's organize your ideas and create a plan for your next milestone. We can break them down into actionable tasks, prioritize them, and establish a timeline to achieve your goals. Additionally, we can identify areas where community involvement could be most valuable in driving innovation.
-
-Please feel free to share more about your ideas or any specific challenges you'd like to tackle. I'm here to help you turn these ideas into a cohesive and successful plan for Zed's future!
-
-@nathan> #zed, can you describe the conversation UI as you currently understand it.
-
-< Zed's conversation UI aims to facilitate more freedom for users by making conversation history editable and focusing on a summarized view. Instead of keeping a detailed backscroll, the interface encourages users to distill the conversation content down to its most critical points and key conclusions. This approach ensures that the vital information from the conversation is highlighted and easily accessible, while still maintaining a history that can be linked to when necessary.
-
-Addressing @nate's concerns, this design choice aims to strike a balance between readability, usability, and information preservation. The summarized view maintains the essence of the conversation without being burdened by extensive backscroll. Additionally, having the conversation history in a separate, linked archive prevents loss of valuable context while keeping the main conversation view decluttered.
-
-@nathan> What details around the conversation view are worth exploring?
-
-< @nathan, there are several aspects of the conversation view that are worth exploring and refining:
-
-1. Editing functionality: Ensuring that all participants can edit and refine the conversation summary effectively while preserving the original context.
-2. Permissions and access control: Determining how to manage editing rights, user roles, and visibility of the conversation summary and history.
-3. Navigation and linking: Implementing an intuitive system for navigating between the summarized conversation and linked history or reference materials.
-4. Visualization and layout: Designing the interface to clearly present the summarized information while providing a visually engaging and organized experience for participants.
-5. Threaded conversations and branching: Exploring ways to handle sub-conversations, supplementary discussions, or debates within the primary conversation view.
-
-Considering these aspects will ensure our conversation view design is optimized for usability, collaboration, and accessibility in Zed.
-
-@nathan> @nate, which of these would you like to explore?
-
-@nate> 2 feels like it isn't important at the moment, we can explore that later. Let's start with 4, which I think will lead us to discussion 3 and 5.
-
-#zed share your thoughts on the points we need to consider to design a layout and visualization for a conversation view between you (#zed) and multuple peoople, or between multiple people and multiple bots (you and other bots).
-
-@nathan> Agreed. I'm interested in threading I think more than anything. Or 4 yeah. I think we need to scope the threading conversation. Also, asking #zed to propose the solution... not sure it will be that effective but it's worth a try...
-
-#zed describe a hypothetical conversation UI that meets the description.
-
-< @nate and @nathan, I understand that you'd like to focus on the layout and visualization for a conversation view that can accommodate multiple people and potentially multiple bots as well. Here's a proposal for a conversation UI that meets the requirements:
-
-1. Main conversation area: Display the summarized conversation content in a clean and organized manner, ensuring readability and efficient use of space.
-
-2. Participant avatars and labels: Include user avatars and/or labels to distinguish between users and bots visually, allowing easy identification of each participant's contributions.
-
-3. Active threads indicator: Designate a clear indicator for ongoing threads, which will attract attention to active sub-conversations or related discussions in the conversation view.
-
-4. Dynamic branching: Implement a branching visual structure to present threaded conversations, clearly distinguishing sub-conversations, and enabling users to navigate between them smoothly.
-
-5. Multiple bot interaction support: Facilitate interactions between multiple bots through user-invoked prompts or bot-to-bot communication channels without disrupting the primary conversation flow.
-
-6. Expandable conversation history: Offer a separate, expandable section or panel to access historical content without cluttering the main conversation view.
-
-7. Linking and referencing: Implement a system for hyperlinking relevant information, resources, or previous conversations to the current discussion, making it easy for participants to follow and reference the shared context.
-
-This hypothetical conversation UI aims to address the layout and visualization, threading, and multi-participant aspects of your requirements. Of course, it's just an initial proposal, and we can refine and iterate on it based on your feedback and specific needs.

crates/assistant2/features.zmd 🔗

@@ -1,3 +0,0 @@
-Push content to a deeper layer.
-A context can have multiple sublayers.
-You can enable or disable arbitrary sublayers at arbitrary nesting depths when viewing the document.

crates/assistant2/src/assistant.rs 🔗

@@ -1,129 +0,0 @@
-pub mod assistant_panel;
-mod assistant_settings;
-mod codegen;
-mod prompts;
-mod streaming_diff;
-
-use ai::providers::open_ai::Role;
-use anyhow::Result;
-pub use assistant_panel::AssistantPanel;
-use assistant_settings::OpenAIModel;
-use chrono::{DateTime, Local};
-use collections::HashMap;
-use fs::Fs;
-use futures::StreamExt;
-use gpui::{actions, AppContext, SharedString};
-use regex::Regex;
-use serde::{Deserialize, Serialize};
-use std::{cmp::Reverse, ffi::OsStr, path::PathBuf, sync::Arc};
-use util::paths::CONVERSATIONS_DIR;
-
-actions!(
-    assistant,
-    [
-        NewConversation,
-        Assist,
-        Split,
-        CycleMessageRole,
-        QuoteSelection,
-        ToggleFocus,
-        ResetKey,
-        InlineAssist,
-        ToggleIncludeConversation,
-        ToggleRetrieveContext,
-    ]
-);
-
-#[derive(
-    Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize,
-)]
-struct MessageId(usize);
-
-#[derive(Clone, Debug, Serialize, Deserialize)]
-struct MessageMetadata {
-    role: Role,
-    sent_at: DateTime<Local>,
-    status: MessageStatus,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize)]
-enum MessageStatus {
-    Pending,
-    Done,
-    Error(SharedString),
-}
-
-#[derive(Serialize, Deserialize)]
-struct SavedMessage {
-    id: MessageId,
-    start: usize,
-}
-
-#[derive(Serialize, Deserialize)]
-struct SavedConversation {
-    id: Option<String>,
-    zed: String,
-    version: String,
-    text: String,
-    messages: Vec<SavedMessage>,
-    message_metadata: HashMap<MessageId, MessageMetadata>,
-    summary: String,
-    model: OpenAIModel,
-}
-
-impl SavedConversation {
-    const VERSION: &'static str = "0.1.0";
-}
-
-struct SavedConversationMetadata {
-    title: String,
-    path: PathBuf,
-    mtime: chrono::DateTime<chrono::Local>,
-}
-
-impl SavedConversationMetadata {
-    pub async fn list(fs: Arc<dyn Fs>) -> Result<Vec<Self>> {
-        fs.create_dir(&CONVERSATIONS_DIR).await?;
-
-        let mut paths = fs.read_dir(&CONVERSATIONS_DIR).await?;
-        let mut conversations = Vec::<SavedConversationMetadata>::new();
-        while let Some(path) = paths.next().await {
-            let path = path?;
-            if path.extension() != Some(OsStr::new("json")) {
-                continue;
-            }
-
-            let pattern = r" - \d+.zed.json$";
-            let re = Regex::new(pattern).unwrap();
-
-            let metadata = fs.metadata(&path).await?;
-            if let Some((file_name, metadata)) = path
-                .file_name()
-                .and_then(|name| name.to_str())
-                .zip(metadata)
-            {
-                let title = re.replace(file_name, "");
-                conversations.push(Self {
-                    title: title.into_owned(),
-                    path,
-                    mtime: metadata.mtime.into(),
-                });
-            }
-        }
-        conversations.sort_unstable_by_key(|conversation| Reverse(conversation.mtime));
-
-        Ok(conversations)
-    }
-}
-
-pub fn init(cx: &mut AppContext) {
-    assistant_panel::init(cx);
-}
-
-#[cfg(test)]
-#[ctor::ctor]
-fn init_logger() {
-    if std::env::var("RUST_LOG").is_ok() {
-        env_logger::init();
-    }
-}

crates/assistant2/src/assistant_panel.rs 🔗

@@ -1,3540 +0,0 @@
-use crate::{
-    assistant_settings::{AssistantDockPosition, AssistantSettings, OpenAIModel},
-    codegen::{self, Codegen, CodegenKind},
-    prompts::generate_content_prompt,
-    Assist, CycleMessageRole, InlineAssist, MessageId, MessageMetadata, MessageStatus,
-    NewConversation, QuoteSelection, ResetKey, Role, SavedConversation, SavedConversationMetadata,
-    SavedMessage, Split, ToggleFocus, ToggleIncludeConversation, ToggleRetrieveContext,
-};
-
-use ai::{
-    auth::ProviderCredential,
-    completion::{CompletionProvider, CompletionRequest},
-    providers::open_ai::{OpenAICompletionProvider, OpenAIRequest, RequestMessage},
-};
-
-use ai::prompts::repository_context::PromptCodeSnippet;
-use anyhow::{anyhow, Result};
-use chrono::{DateTime, Local};
-use client::{telemetry::AssistantKind, TelemetrySettings};
-use collections::{hash_map, HashMap, HashSet, VecDeque};
-use editor::{
-    display_map::{
-        BlockContext, BlockDisposition, BlockId, BlockProperties, BlockStyle, ToDisplayPoint,
-    },
-    scroll::autoscroll::{Autoscroll, AutoscrollStrategy},
-    Anchor, Editor, EditorElement, EditorEvent, EditorStyle, MoveDown, MoveUp, MultiBufferSnapshot,
-    ToOffset, ToPoint,
-};
-use fs::Fs;
-use futures::StreamExt;
-use gpui::{
-    canvas, div, point, relative, rems, uniform_list, Action, AnyElement, AppContext,
-    AsyncWindowContext, AvailableSpace, ClipboardItem, Context, EventEmitter, FocusHandle,
-    FocusableView, FontStyle, FontWeight, HighlightStyle, InteractiveElement, IntoElement, Model,
-    ModelContext, ParentElement, Pixels, PromptLevel, Render, SharedString,
-    StatefulInteractiveElement, Styled, Subscription, Task, TextStyle, UniformListScrollHandle,
-    View, ViewContext, VisualContext, WeakModel, WeakView, WhiteSpace, WindowContext,
-};
-use language::{language_settings::SoftWrap, Buffer, LanguageRegistry, ToOffset as _};
-use project::Project;
-use search::BufferSearchBar;
-use semantic_index::{SemanticIndex, SemanticIndexStatus};
-use settings::{Settings, SettingsStore};
-use std::{
-    cell::Cell,
-    cmp,
-    fmt::Write,
-    iter,
-    ops::Range,
-    path::{Path, PathBuf},
-    rc::Rc,
-    sync::Arc,
-    time::{Duration, Instant},
-};
-use theme::ThemeSettings;
-use ui::{
-    prelude::*,
-    utils::{DateTimeType, FormatDistance},
-    ButtonLike, Tab, TabBar, Tooltip,
-};
-use util::{paths::CONVERSATIONS_DIR, post_inc, ResultExt, TryFutureExt};
-use uuid::Uuid;
-use workspace::{
-    dock::{DockPosition, Panel, PanelEvent},
-    searchable::Direction,
-    Save, Toast, ToggleZoom, Toolbar, Workspace,
-};
-
-pub fn init(cx: &mut AppContext) {
-    AssistantSettings::register(cx);
-    cx.observe_new_views(
-        |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
-            workspace
-                .register_action(|workspace, _: &ToggleFocus, cx| {
-                    workspace.toggle_panel_focus::<AssistantPanel>(cx);
-                })
-                .register_action(AssistantPanel::inline_assist)
-                .register_action(AssistantPanel::cancel_last_inline_assist)
-                .register_action(ConversationEditor::quote_selection);
-        },
-    )
-    .detach();
-}
-
-pub struct AssistantPanel {
-    workspace: WeakView<Workspace>,
-    width: Option<Pixels>,
-    height: Option<Pixels>,
-    active_editor_index: Option<usize>,
-    prev_active_editor_index: Option<usize>,
-    editors: Vec<View<ConversationEditor>>,
-    saved_conversations: Vec<SavedConversationMetadata>,
-    saved_conversations_scroll_handle: UniformListScrollHandle,
-    zoomed: bool,
-    focus_handle: FocusHandle,
-    toolbar: View<Toolbar>,
-    completion_provider: Arc<dyn CompletionProvider>,
-    api_key_editor: Option<View<Editor>>,
-    languages: Arc<LanguageRegistry>,
-    fs: Arc<dyn Fs>,
-    subscriptions: Vec<Subscription>,
-    next_inline_assist_id: usize,
-    pending_inline_assists: HashMap<usize, PendingInlineAssist>,
-    pending_inline_assist_ids_by_editor: HashMap<WeakView<Editor>, Vec<usize>>,
-    include_conversation_in_next_inline_assist: bool,
-    inline_prompt_history: VecDeque<String>,
-    _watch_saved_conversations: Task<Result<()>>,
-    semantic_index: Option<Model<SemanticIndex>>,
-    retrieve_context_in_next_inline_assist: bool,
-}
-
-impl AssistantPanel {
-    const INLINE_PROMPT_HISTORY_MAX_LEN: usize = 20;
-
-    pub fn load(
-        workspace: WeakView<Workspace>,
-        cx: AsyncWindowContext,
-    ) -> Task<Result<View<Self>>> {
-        cx.spawn(|mut cx| async move {
-            let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
-            let saved_conversations = SavedConversationMetadata::list(fs.clone())
-                .await
-                .log_err()
-                .unwrap_or_default();
-
-            // TODO: deserialize state.
-            let workspace_handle = workspace.clone();
-            workspace.update(&mut cx, |workspace, cx| {
-                cx.new_view::<Self>(|cx| {
-                    const CONVERSATION_WATCH_DURATION: Duration = Duration::from_millis(100);
-                    let _watch_saved_conversations = cx.spawn(move |this, mut cx| async move {
-                        let mut events = fs
-                            .watch(&CONVERSATIONS_DIR, CONVERSATION_WATCH_DURATION)
-                            .await;
-                        while events.next().await.is_some() {
-                            let saved_conversations = SavedConversationMetadata::list(fs.clone())
-                                .await
-                                .log_err()
-                                .unwrap_or_default();
-                            this.update(&mut cx, |this, cx| {
-                                this.saved_conversations = saved_conversations;
-                                cx.notify();
-                            })
-                            .ok();
-                        }
-
-                        anyhow::Ok(())
-                    });
-
-                    let toolbar = cx.new_view(|cx| {
-                        let mut toolbar = Toolbar::new();
-                        toolbar.set_can_navigate(false, cx);
-                        toolbar.add_item(cx.new_view(|cx| BufferSearchBar::new(cx)), cx);
-                        toolbar
-                    });
-
-                    let semantic_index = SemanticIndex::global(cx);
-                    // Defaulting currently to GPT4, allow for this to be set via config.
-                    let completion_provider = Arc::new(OpenAICompletionProvider::new(
-                        "gpt-4",
-                        cx.background_executor().clone(),
-                    ));
-
-                    let focus_handle = cx.focus_handle();
-                    cx.on_focus_in(&focus_handle, Self::focus_in).detach();
-                    cx.on_focus_out(&focus_handle, Self::focus_out).detach();
-
-                    let mut this = Self {
-                        workspace: workspace_handle,
-                        active_editor_index: Default::default(),
-                        prev_active_editor_index: Default::default(),
-                        editors: Default::default(),
-                        saved_conversations,
-                        saved_conversations_scroll_handle: Default::default(),
-                        zoomed: false,
-                        focus_handle,
-                        toolbar,
-                        completion_provider,
-                        api_key_editor: None,
-                        languages: workspace.app_state().languages.clone(),
-                        fs: workspace.app_state().fs.clone(),
-                        width: None,
-                        height: None,
-                        subscriptions: Default::default(),
-                        next_inline_assist_id: 0,
-                        pending_inline_assists: Default::default(),
-                        pending_inline_assist_ids_by_editor: Default::default(),
-                        include_conversation_in_next_inline_assist: false,
-                        inline_prompt_history: Default::default(),
-                        _watch_saved_conversations,
-                        semantic_index,
-                        retrieve_context_in_next_inline_assist: false,
-                    };
-
-                    let mut old_dock_position = this.position(cx);
-                    this.subscriptions =
-                        vec![cx.observe_global::<SettingsStore>(move |this, cx| {
-                            let new_dock_position = this.position(cx);
-                            if new_dock_position != old_dock_position {
-                                old_dock_position = new_dock_position;
-                                cx.emit(PanelEvent::ChangePosition);
-                            }
-                            cx.notify();
-                        })];
-
-                    this
-                })
-            })
-        })
-    }
-
-    fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
-        self.toolbar
-            .update(cx, |toolbar, cx| toolbar.focus_changed(true, cx));
-        cx.notify();
-        if self.focus_handle.is_focused(cx) {
-            if let Some(editor) = self.active_editor() {
-                cx.focus_view(editor);
-            } else if let Some(api_key_editor) = self.api_key_editor.as_ref() {
-                cx.focus_view(api_key_editor);
-            }
-        }
-    }
-
-    fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
-        self.toolbar
-            .update(cx, |toolbar, cx| toolbar.focus_changed(false, cx));
-        cx.notify();
-    }
-
-    pub fn inline_assist(
-        workspace: &mut Workspace,
-        _: &InlineAssist,
-        cx: &mut ViewContext<Workspace>,
-    ) {
-        let this = if let Some(this) = workspace.panel::<AssistantPanel>(cx) {
-            if this.update(cx, |assistant, cx| {
-                if !assistant.has_credentials() {
-                    assistant.load_credentials(cx);
-                };
-
-                assistant.has_credentials()
-            }) {
-                this
-            } else {
-                workspace.focus_panel::<AssistantPanel>(cx);
-                return;
-            }
-        } else {
-            return;
-        };
-
-        let active_editor = if let Some(active_editor) = workspace
-            .active_item(cx)
-            .and_then(|item| item.act_as::<Editor>(cx))
-        {
-            active_editor
-        } else {
-            return;
-        };
-
-        let project = workspace.project();
-
-        this.update(cx, |assistant, cx| {
-            assistant.new_inline_assist(&active_editor, cx, project)
-        });
-    }
-
-    fn new_inline_assist(
-        &mut self,
-        editor: &View<Editor>,
-        cx: &mut ViewContext<Self>,
-        project: &Model<Project>,
-    ) {
-        let selection = editor.read(cx).selections.newest_anchor().clone();
-        if selection.start.excerpt_id != selection.end.excerpt_id {
-            return;
-        }
-        let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
-
-        // Extend the selection to the start and the end of the line.
-        let mut point_selection = selection.map(|selection| selection.to_point(&snapshot));
-        if point_selection.end > point_selection.start {
-            point_selection.start.column = 0;
-            // If the selection ends at the start of the line, we don't want to include it.
-            if point_selection.end.column == 0 {
-                point_selection.end.row -= 1;
-            }
-            point_selection.end.column = snapshot.line_len(point_selection.end.row);
-        }
-
-        let codegen_kind = if point_selection.start == point_selection.end {
-            CodegenKind::Generate {
-                position: snapshot.anchor_after(point_selection.start),
-            }
-        } else {
-            CodegenKind::Transform {
-                range: snapshot.anchor_before(point_selection.start)
-                    ..snapshot.anchor_after(point_selection.end),
-            }
-        };
-
-        let inline_assist_id = post_inc(&mut self.next_inline_assist_id);
-        let provider = self.completion_provider.clone();
-
-        // Retrieve Credentials Authenticates the Provider
-        provider.retrieve_credentials(cx);
-
-        let codegen = cx.new_model(|cx| {
-            Codegen::new(editor.read(cx).buffer().clone(), codegen_kind, provider, cx)
-        });
-
-        if let Some(semantic_index) = self.semantic_index.clone() {
-            let project = project.clone();
-            cx.spawn(|_, mut cx| async move {
-                let previously_indexed = semantic_index
-                    .update(&mut cx, |index, cx| {
-                        index.project_previously_indexed(&project, cx)
-                    })?
-                    .await
-                    .unwrap_or(false);
-                if previously_indexed {
-                    let _ = semantic_index
-                        .update(&mut cx, |index, cx| {
-                            index.index_project(project.clone(), cx)
-                        })?
-                        .await;
-                }
-                anyhow::Ok(())
-            })
-            .detach_and_log_err(cx);
-        }
-
-        let measurements = Rc::new(Cell::new(BlockMeasurements::default()));
-        let inline_assistant = cx.new_view(|cx| {
-            InlineAssistant::new(
-                inline_assist_id,
-                measurements.clone(),
-                self.include_conversation_in_next_inline_assist,
-                self.inline_prompt_history.clone(),
-                codegen.clone(),
-                self.workspace.clone(),
-                cx,
-                self.retrieve_context_in_next_inline_assist,
-                self.semantic_index.clone(),
-                project.clone(),
-            )
-        });
-        let block_id = editor.update(cx, |editor, cx| {
-            editor.change_selections(None, cx, |selections| {
-                selections.select_anchor_ranges([selection.head()..selection.head()])
-            });
-            editor.insert_blocks(
-                [BlockProperties {
-                    style: BlockStyle::Flex,
-                    position: snapshot.anchor_before(point_selection.head()),
-                    height: 2,
-                    render: Arc::new({
-                        let inline_assistant = inline_assistant.clone();
-                        move |cx: &mut BlockContext| {
-                            measurements.set(BlockMeasurements {
-                                anchor_x: cx.anchor_x,
-                                gutter_width: cx.gutter_width,
-                            });
-                            inline_assistant.clone().into_any_element()
-                        }
-                    }),
-                    disposition: if selection.reversed {
-                        BlockDisposition::Above
-                    } else {
-                        BlockDisposition::Below
-                    },
-                }],
-                Some(Autoscroll::Strategy(AutoscrollStrategy::Newest)),
-                cx,
-            )[0]
-        });
-
-        self.pending_inline_assists.insert(
-            inline_assist_id,
-            PendingInlineAssist {
-                editor: editor.downgrade(),
-                inline_assistant: Some((block_id, inline_assistant.clone())),
-                codegen: codegen.clone(),
-                project: project.downgrade(),
-                _subscriptions: vec![
-                    cx.subscribe(&inline_assistant, Self::handle_inline_assistant_event),
-                    cx.subscribe(editor, {
-                        let inline_assistant = inline_assistant.downgrade();
-                        move |_, editor, event, cx| {
-                            if let Some(inline_assistant) = inline_assistant.upgrade() {
-                                if let EditorEvent::SelectionsChanged { local } = event {
-                                    if *local
-                                        && inline_assistant.focus_handle(cx).contains_focused(cx)
-                                    {
-                                        cx.focus_view(&editor);
-                                    }
-                                }
-                            }
-                        }
-                    }),
-                    cx.observe(&codegen, {
-                        let editor = editor.downgrade();
-                        move |this, _, cx| {
-                            if let Some(editor) = editor.upgrade() {
-                                this.update_highlights_for_editor(&editor, cx);
-                            }
-                        }
-                    }),
-                    cx.subscribe(&codegen, move |this, codegen, event, cx| match event {
-                        codegen::Event::Undone => {
-                            this.finish_inline_assist(inline_assist_id, false, cx)
-                        }
-                        codegen::Event::Finished => {
-                            let pending_assist = if let Some(pending_assist) =
-                                this.pending_inline_assists.get(&inline_assist_id)
-                            {
-                                pending_assist
-                            } else {
-                                return;
-                            };
-
-                            let error = codegen
-                                .read(cx)
-                                .error()
-                                .map(|error| format!("Inline assistant error: {}", error));
-                            if let Some(error) = error {
-                                if pending_assist.inline_assistant.is_none() {
-                                    if let Some(workspace) = this.workspace.upgrade() {
-                                        workspace.update(cx, |workspace, cx| {
-                                            workspace.show_toast(
-                                                Toast::new(inline_assist_id, error),
-                                                cx,
-                                            );
-                                        })
-                                    }
-
-                                    this.finish_inline_assist(inline_assist_id, false, cx);
-                                }
-                            } else {
-                                this.finish_inline_assist(inline_assist_id, false, cx);
-                            }
-                        }
-                    }),
-                ],
-            },
-        );
-        self.pending_inline_assist_ids_by_editor
-            .entry(editor.downgrade())
-            .or_default()
-            .push(inline_assist_id);
-        self.update_highlights_for_editor(&editor, cx);
-    }
-
-    fn handle_inline_assistant_event(
-        &mut self,
-        inline_assistant: View<InlineAssistant>,
-        event: &InlineAssistantEvent,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let assist_id = inline_assistant.read(cx).id;
-        match event {
-            InlineAssistantEvent::Confirmed {
-                prompt,
-                include_conversation,
-                retrieve_context,
-            } => {
-                self.confirm_inline_assist(
-                    assist_id,
-                    prompt,
-                    *include_conversation,
-                    cx,
-                    *retrieve_context,
-                );
-            }
-            InlineAssistantEvent::Canceled => {
-                self.finish_inline_assist(assist_id, true, cx);
-            }
-            InlineAssistantEvent::Dismissed => {
-                self.hide_inline_assist(assist_id, cx);
-            }
-            InlineAssistantEvent::IncludeConversationToggled {
-                include_conversation,
-            } => {
-                self.include_conversation_in_next_inline_assist = *include_conversation;
-            }
-            InlineAssistantEvent::RetrieveContextToggled { retrieve_context } => {
-                self.retrieve_context_in_next_inline_assist = *retrieve_context
-            }
-        }
-    }
-
-    fn cancel_last_inline_assist(
-        workspace: &mut Workspace,
-        _: &editor::Cancel,
-        cx: &mut ViewContext<Workspace>,
-    ) {
-        if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
-            if let Some(editor) = workspace
-                .active_item(cx)
-                .and_then(|item| item.downcast::<Editor>())
-            {
-                let handled = panel.update(cx, |panel, cx| {
-                    if let Some(assist_id) = panel
-                        .pending_inline_assist_ids_by_editor
-                        .get(&editor.downgrade())
-                        .and_then(|assist_ids| assist_ids.last().copied())
-                    {
-                        panel.finish_inline_assist(assist_id, true, cx);
-                        true
-                    } else {
-                        false
-                    }
-                });
-                if handled {
-                    return;
-                }
-            }
-        }
-
-        cx.propagate();
-    }
-
-    fn finish_inline_assist(&mut self, assist_id: usize, undo: bool, cx: &mut ViewContext<Self>) {
-        self.hide_inline_assist(assist_id, cx);
-
-        if let Some(pending_assist) = self.pending_inline_assists.remove(&assist_id) {
-            if let hash_map::Entry::Occupied(mut entry) = self
-                .pending_inline_assist_ids_by_editor
-                .entry(pending_assist.editor.clone())
-            {
-                entry.get_mut().retain(|id| *id != assist_id);
-                if entry.get().is_empty() {
-                    entry.remove();
-                }
-            }
-
-            if let Some(editor) = pending_assist.editor.upgrade() {
-                self.update_highlights_for_editor(&editor, cx);
-
-                if undo {
-                    pending_assist
-                        .codegen
-                        .update(cx, |codegen, cx| codegen.undo(cx));
-                }
-            }
-        }
-    }
-
-    fn hide_inline_assist(&mut self, assist_id: usize, cx: &mut ViewContext<Self>) {
-        if let Some(pending_assist) = self.pending_inline_assists.get_mut(&assist_id) {
-            if let Some(editor) = pending_assist.editor.upgrade() {
-                if let Some((block_id, inline_assistant)) = pending_assist.inline_assistant.take() {
-                    editor.update(cx, |editor, cx| {
-                        editor.remove_blocks(HashSet::from_iter([block_id]), None, cx);
-                        if inline_assistant.focus_handle(cx).contains_focused(cx) {
-                            editor.focus(cx);
-                        }
-                    });
-                }
-            }
-        }
-    }
-
-    fn confirm_inline_assist(
-        &mut self,
-        inline_assist_id: usize,
-        user_prompt: &str,
-        include_conversation: bool,
-        cx: &mut ViewContext<Self>,
-        retrieve_context: bool,
-    ) {
-        let conversation = if include_conversation {
-            self.active_editor()
-                .map(|editor| editor.read(cx).conversation.clone())
-        } else {
-            None
-        };
-
-        let pending_assist =
-            if let Some(pending_assist) = self.pending_inline_assists.get_mut(&inline_assist_id) {
-                pending_assist
-            } else {
-                return;
-            };
-
-        let editor = if let Some(editor) = pending_assist.editor.upgrade() {
-            editor
-        } else {
-            return;
-        };
-
-        let project = pending_assist.project.clone();
-
-        let project_name = if let Some(project) = project.upgrade() {
-            Some(
-                project
-                    .read(cx)
-                    .worktree_root_names(cx)
-                    .collect::<Vec<&str>>()
-                    .join("/"),
-            )
-        } else {
-            None
-        };
-
-        self.inline_prompt_history
-            .retain(|prompt| prompt != user_prompt);
-        self.inline_prompt_history.push_back(user_prompt.into());
-        if self.inline_prompt_history.len() > Self::INLINE_PROMPT_HISTORY_MAX_LEN {
-            self.inline_prompt_history.pop_front();
-        }
-
-        let codegen = pending_assist.codegen.clone();
-        let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
-        let range = codegen.read(cx).range();
-        let start = snapshot.point_to_buffer_offset(range.start);
-        let end = snapshot.point_to_buffer_offset(range.end);
-        let (buffer, range) = if let Some((start, end)) = start.zip(end) {
-            let (start_buffer, start_buffer_offset) = start;
-            let (end_buffer, end_buffer_offset) = end;
-            if start_buffer.remote_id() == end_buffer.remote_id() {
-                (start_buffer.clone(), start_buffer_offset..end_buffer_offset)
-            } else {
-                self.finish_inline_assist(inline_assist_id, false, cx);
-                return;
-            }
-        } else {
-            self.finish_inline_assist(inline_assist_id, false, cx);
-            return;
-        };
-
-        let language = buffer.language_at(range.start);
-        let language_name = if let Some(language) = language.as_ref() {
-            if Arc::ptr_eq(language, &language::PLAIN_TEXT) {
-                None
-            } else {
-                Some(language.name())
-            }
-        } else {
-            None
-        };
-
-        // Higher Temperature increases the randomness of model outputs.
-        // If Markdown or No Language is Known, increase the randomness for more creative output
-        // If Code, decrease temperature to get more deterministic outputs
-        let temperature = if let Some(language) = language_name.clone() {
-            if language.to_string() != "Markdown".to_string() {
-                0.5
-            } else {
-                1.0
-            }
-        } else {
-            1.0
-        };
-
-        let user_prompt = user_prompt.to_string();
-
-        let snippets = if retrieve_context {
-            let Some(project) = project.upgrade() else {
-                return;
-            };
-
-            let search_results = if let Some(semantic_index) = self.semantic_index.clone() {
-                let search_results = semantic_index.update(cx, |this, cx| {
-                    this.search_project(project, user_prompt.to_string(), 10, vec![], vec![], cx)
-                });
-
-                cx.background_executor()
-                    .spawn(async move { search_results.await.unwrap_or_default() })
-            } else {
-                Task::ready(Vec::new())
-            };
-
-            let snippets = cx.spawn(|_, mut cx| async move {
-                let mut snippets = Vec::new();
-                for result in search_results.await {
-                    snippets.push(PromptCodeSnippet::new(
-                        result.buffer,
-                        result.range,
-                        &mut cx,
-                    )?);
-                }
-                anyhow::Ok(snippets)
-            });
-            snippets
-        } else {
-            Task::ready(Ok(Vec::new()))
-        };
-
-        let mut model = AssistantSettings::get_global(cx)
-            .default_open_ai_model
-            .clone();
-        let model_name = model.full_name();
-
-        let prompt = cx.background_executor().spawn(async move {
-            let snippets = snippets.await?;
-
-            let language_name = language_name.as_deref();
-            generate_content_prompt(
-                user_prompt,
-                language_name,
-                buffer,
-                range,
-                snippets,
-                model_name,
-                project_name,
-            )
-        });
-
-        let mut messages = Vec::new();
-        if let Some(conversation) = conversation {
-            let conversation = conversation.read(cx);
-            let buffer = conversation.buffer.read(cx);
-            messages.extend(
-                conversation
-                    .messages(cx)
-                    .map(|message| message.to_open_ai_message(buffer)),
-            );
-            model = conversation.model.clone();
-        }
-
-        cx.spawn(|_, mut cx| async move {
-            // I Don't know if we want to return a ? here.
-            let prompt = prompt.await?;
-
-            messages.push(RequestMessage {
-                role: Role::User,
-                content: prompt,
-            });
-
-            let request = Box::new(OpenAIRequest {
-                model: model.full_name().into(),
-                messages,
-                stream: true,
-                stop: vec!["|END|>".to_string()],
-                temperature,
-            });
-
-            codegen.update(&mut cx, |codegen, cx| codegen.start(request, cx))?;
-            anyhow::Ok(())
-        })
-        .detach();
-    }
-
-    fn update_highlights_for_editor(&self, editor: &View<Editor>, cx: &mut ViewContext<Self>) {
-        let mut background_ranges = Vec::new();
-        let mut foreground_ranges = Vec::new();
-        let empty_inline_assist_ids = Vec::new();
-        let inline_assist_ids = self
-            .pending_inline_assist_ids_by_editor
-            .get(&editor.downgrade())
-            .unwrap_or(&empty_inline_assist_ids);
-
-        for inline_assist_id in inline_assist_ids {
-            if let Some(pending_assist) = self.pending_inline_assists.get(inline_assist_id) {
-                let codegen = pending_assist.codegen.read(cx);
-                background_ranges.push(codegen.range());
-                foreground_ranges.extend(codegen.last_equal_ranges().iter().cloned());
-            }
-        }
-
-        let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
-        merge_ranges(&mut background_ranges, &snapshot);
-        merge_ranges(&mut foreground_ranges, &snapshot);
-        editor.update(cx, |editor, cx| {
-            if background_ranges.is_empty() {
-                editor.clear_background_highlights::<PendingInlineAssist>(cx);
-            } else {
-                editor.highlight_background::<PendingInlineAssist>(
-                    background_ranges,
-                    |theme| theme.editor_active_line_background, // todo!("use the appropriate color")
-                    cx,
-                );
-            }
-
-            if foreground_ranges.is_empty() {
-                editor.clear_highlights::<PendingInlineAssist>(cx);
-            } else {
-                editor.highlight_text::<PendingInlineAssist>(
-                    foreground_ranges,
-                    HighlightStyle {
-                        fade_out: Some(0.6),
-                        ..Default::default()
-                    },
-                    cx,
-                );
-            }
-        });
-    }
-
-    fn new_conversation(&mut self, cx: &mut ViewContext<Self>) -> View<ConversationEditor> {
-        let editor = cx.new_view(|cx| {
-            ConversationEditor::new(
-                self.completion_provider.clone(),
-                self.languages.clone(),
-                self.fs.clone(),
-                self.workspace.clone(),
-                cx,
-            )
-        });
-        self.add_conversation(editor.clone(), cx);
-        editor
-    }
-
-    fn add_conversation(&mut self, editor: View<ConversationEditor>, cx: &mut ViewContext<Self>) {
-        self.subscriptions
-            .push(cx.subscribe(&editor, Self::handle_conversation_editor_event));
-
-        let conversation = editor.read(cx).conversation.clone();
-        self.subscriptions
-            .push(cx.observe(&conversation, |_, _, cx| cx.notify()));
-
-        let index = self.editors.len();
-        self.editors.push(editor);
-        self.set_active_editor_index(Some(index), cx);
-    }
-
-    fn set_active_editor_index(&mut self, index: Option<usize>, cx: &mut ViewContext<Self>) {
-        self.prev_active_editor_index = self.active_editor_index;
-        self.active_editor_index = index;
-        if let Some(editor) = self.active_editor() {
-            let editor = editor.read(cx).editor.clone();
-            self.toolbar.update(cx, |toolbar, cx| {
-                toolbar.set_active_item(Some(&editor), cx);
-            });
-            if self.focus_handle.contains_focused(cx) {
-                cx.focus_view(&editor);
-            }
-        } else {
-            self.toolbar.update(cx, |toolbar, cx| {
-                toolbar.set_active_item(None, cx);
-            });
-        }
-
-        cx.notify();
-    }
-
-    fn handle_conversation_editor_event(
-        &mut self,
-        _: View<ConversationEditor>,
-        event: &ConversationEditorEvent,
-        cx: &mut ViewContext<Self>,
-    ) {
-        match event {
-            ConversationEditorEvent::TabContentChanged => cx.notify(),
-        }
-    }
-
-    fn save_credentials(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
-        if let Some(api_key) = self
-            .api_key_editor
-            .as_ref()
-            .map(|editor| editor.read(cx).text(cx))
-        {
-            if !api_key.is_empty() {
-                let credential = ProviderCredential::Credentials {
-                    api_key: api_key.clone(),
-                };
-
-                self.completion_provider.save_credentials(cx, credential);
-
-                self.api_key_editor.take();
-                self.focus_handle.focus(cx);
-                cx.notify();
-            }
-        } else {
-            cx.propagate();
-        }
-    }
-
-    fn reset_credentials(&mut self, _: &ResetKey, cx: &mut ViewContext<Self>) {
-        self.completion_provider.delete_credentials(cx);
-        self.api_key_editor = Some(build_api_key_editor(cx));
-        self.focus_handle.focus(cx);
-        cx.notify();
-    }
-
-    fn toggle_zoom(&mut self, _: &workspace::ToggleZoom, cx: &mut ViewContext<Self>) {
-        if self.zoomed {
-            cx.emit(PanelEvent::ZoomOut)
-        } else {
-            cx.emit(PanelEvent::ZoomIn)
-        }
-    }
-
-    fn deploy(&mut self, action: &search::buffer_search::Deploy, cx: &mut ViewContext<Self>) {
-        let mut propagate = true;
-        if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
-            search_bar.update(cx, |search_bar, cx| {
-                if search_bar.show(cx) {
-                    search_bar.search_suggested(cx);
-                    if action.focus {
-                        let focus_handle = search_bar.focus_handle(cx);
-                        search_bar.select_query(cx);
-                        cx.focus(&focus_handle);
-                    }
-                    propagate = false
-                }
-            });
-        }
-        if propagate {
-            cx.propagate();
-        }
-    }
-
-    fn handle_editor_cancel(&mut self, _: &editor::Cancel, cx: &mut ViewContext<Self>) {
-        if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
-            if !search_bar.read(cx).is_dismissed() {
-                search_bar.update(cx, |search_bar, cx| {
-                    search_bar.dismiss(&Default::default(), cx)
-                });
-                return;
-            }
-        }
-        cx.propagate();
-    }
-
-    fn select_next_match(&mut self, _: &search::SelectNextMatch, cx: &mut ViewContext<Self>) {
-        if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
-            search_bar.update(cx, |bar, cx| bar.select_match(Direction::Next, 1, cx));
-        }
-    }
-
-    fn select_prev_match(&mut self, _: &search::SelectPrevMatch, cx: &mut ViewContext<Self>) {
-        if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
-            search_bar.update(cx, |bar, cx| bar.select_match(Direction::Prev, 1, cx));
-        }
-    }
-
-    fn active_editor(&self) -> Option<&View<ConversationEditor>> {
-        self.editors.get(self.active_editor_index?)
-    }
-
-    fn render_hamburger_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
-        IconButton::new("hamburger_button", Icon::Menu)
-            .on_click(cx.listener(|this, _event, cx| {
-                if this.active_editor().is_some() {
-                    this.set_active_editor_index(None, cx);
-                } else {
-                    this.set_active_editor_index(this.prev_active_editor_index, cx);
-                }
-            }))
-            .tooltip(|cx| Tooltip::text("Conversation History", cx))
-    }
-
-    fn render_editor_tools(&self, cx: &mut ViewContext<Self>) -> Vec<AnyElement> {
-        if self.active_editor().is_some() {
-            vec![
-                Self::render_split_button(cx).into_any_element(),
-                Self::render_quote_button(cx).into_any_element(),
-                Self::render_assist_button(cx).into_any_element(),
-            ]
-        } else {
-            Default::default()
-        }
-    }
-
-    fn render_split_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
-        IconButton::new("split_button", Icon::Snip)
-            .on_click(cx.listener(|this, _event, cx| {
-                if let Some(active_editor) = this.active_editor() {
-                    active_editor.update(cx, |editor, cx| editor.split(&Default::default(), cx));
-                }
-            }))
-            .icon_size(IconSize::Small)
-            .tooltip(|cx| Tooltip::for_action("Split Message", &Split, cx))
-    }
-
-    fn render_assist_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
-        IconButton::new("assist_button", Icon::MagicWand)
-            .on_click(cx.listener(|this, _event, cx| {
-                if let Some(active_editor) = this.active_editor() {
-                    active_editor.update(cx, |editor, cx| editor.assist(&Default::default(), cx));
-                }
-            }))
-            .icon_size(IconSize::Small)
-            .tooltip(|cx| Tooltip::for_action("Assist", &Assist, cx))
-    }
-
-    fn render_quote_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
-        IconButton::new("quote_button", Icon::Quote)
-            .on_click(cx.listener(|this, _event, cx| {
-                if let Some(workspace) = this.workspace.upgrade() {
-                    cx.window_context().defer(move |cx| {
-                        workspace.update(cx, |workspace, cx| {
-                            ConversationEditor::quote_selection(workspace, &Default::default(), cx)
-                        });
-                    });
-                }
-            }))
-            .icon_size(IconSize::Small)
-            .tooltip(|cx| Tooltip::for_action("Quote Selection", &QuoteSelection, cx))
-    }
-
-    fn render_plus_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
-        IconButton::new("plus_button", Icon::Plus)
-            .on_click(cx.listener(|this, _event, cx| {
-                this.new_conversation(cx);
-            }))
-            .icon_size(IconSize::Small)
-            .tooltip(|cx| Tooltip::for_action("New Conversation", &NewConversation, cx))
-    }
-
-    fn render_zoom_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        let zoomed = self.zoomed;
-        IconButton::new("zoom_button", Icon::Maximize)
-            .on_click(cx.listener(|this, _event, cx| {
-                this.toggle_zoom(&ToggleZoom, cx);
-            }))
-            .selected(zoomed)
-            .selected_icon(Icon::Minimize)
-            .icon_size(IconSize::Small)
-            .tooltip(move |cx| {
-                Tooltip::for_action(if zoomed { "Zoom Out" } else { "Zoom In" }, &ToggleZoom, cx)
-            })
-    }
-
-    fn render_saved_conversation(
-        &mut self,
-        index: usize,
-        cx: &mut ViewContext<Self>,
-    ) -> impl IntoElement {
-        let conversation = &self.saved_conversations[index];
-        let path = conversation.path.clone();
-
-        ButtonLike::new(index)
-            .on_click(cx.listener(move |this, _, cx| {
-                this.open_conversation(path.clone(), cx)
-                    .detach_and_log_err(cx)
-            }))
-            .full_width()
-            .child(
-                div()
-                    .flex()
-                    .w_full()
-                    .gap_2()
-                    .child(
-                        Label::new(conversation.mtime.format("%F %I:%M%p").to_string())
-                            .color(Color::Muted)
-                            .size(LabelSize::Small),
-                    )
-                    .child(Label::new(conversation.title.clone()).size(LabelSize::Small)),
-            )
-    }
-
-    fn open_conversation(&mut self, path: PathBuf, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
-        cx.focus(&self.focus_handle);
-
-        if let Some(ix) = self.editor_index_for_path(&path, cx) {
-            self.set_active_editor_index(Some(ix), cx);
-            return Task::ready(Ok(()));
-        }
-
-        let fs = self.fs.clone();
-        let workspace = self.workspace.clone();
-        let languages = self.languages.clone();
-        cx.spawn(|this, mut cx| async move {
-            let saved_conversation = fs.load(&path).await?;
-            let saved_conversation = serde_json::from_str(&saved_conversation)?;
-            let conversation = cx.new_model(|cx| {
-                Conversation::deserialize(saved_conversation, path.clone(), languages, cx)
-            })?;
-            this.update(&mut cx, |this, cx| {
-                // If, by the time we've loaded the conversation, the user has already opened
-                // the same conversation, we don't want to open it again.
-                if let Some(ix) = this.editor_index_for_path(&path, cx) {
-                    this.set_active_editor_index(Some(ix), cx);
-                } else {
-                    let editor = cx.new_view(|cx| {
-                        ConversationEditor::for_conversation(conversation, fs, workspace, cx)
-                    });
-                    this.add_conversation(editor, cx);
-                }
-            })?;
-            Ok(())
-        })
-    }
-
-    fn editor_index_for_path(&self, path: &Path, cx: &AppContext) -> Option<usize> {
-        self.editors
-            .iter()
-            .position(|editor| editor.read(cx).conversation.read(cx).path.as_deref() == Some(path))
-    }
-
-    fn has_credentials(&mut self) -> bool {
-        self.completion_provider.has_credentials()
-    }
-
-    fn load_credentials(&mut self, cx: &mut ViewContext<Self>) {
-        self.completion_provider.retrieve_credentials(cx);
-    }
-}
-
-fn build_api_key_editor(cx: &mut ViewContext<AssistantPanel>) -> View<Editor> {
-    cx.new_view(|cx| {
-        let mut editor = Editor::single_line(cx);
-        editor.set_placeholder_text("sk-000000000000000000000000000000000000000000000000", cx);
-        editor
-    })
-}
-
-impl Render for AssistantPanel {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        if let Some(api_key_editor) = self.api_key_editor.clone() {
-            v_stack()
-                .on_action(cx.listener(AssistantPanel::save_credentials))
-                .track_focus(&self.focus_handle)
-                .child(Label::new(
-                    "To use the assistant panel or inline assistant, you need to add your OpenAI api key.",
-                ))
-                .child(Label::new(
-                    " - Having a subscription for another service like GitHub Copilot won't work."
-                ))
-                .child(Label::new(
-                    " - You can create a api key at: platform.openai.com/api-keys"
-                ))
-                .child(Label::new(
-                    " "
-                ))
-                .child(Label::new(
-                    "Paste your OpenAI API key and press Enter to use the assistant"
-                ))
-                .child(api_key_editor)
-                .child(Label::new(
-                    "Click on the Z button in the status bar to close this panel."
-                ))
-                .border()
-                .border_color(gpui::red())
-        } else {
-            let header = TabBar::new("assistant_header")
-                .start_child(
-                    h_stack().gap_1().child(Self::render_hamburger_button(cx)), // .children(title),
-                )
-                .children(self.active_editor().map(|editor| {
-                    h_stack()
-                        .h(rems(Tab::HEIGHT_IN_REMS))
-                        .flex_1()
-                        .px_2()
-                        .child(Label::new(editor.read(cx).title(cx)).into_element())
-                }))
-                .end_child(if self.focus_handle.contains_focused(cx) {
-                    h_stack()
-                        .gap_2()
-                        .child(h_stack().gap_1().children(self.render_editor_tools(cx)))
-                        .child(
-                            ui::Divider::vertical()
-                                .inset()
-                                .color(ui::DividerColor::Border),
-                        )
-                        .child(
-                            h_stack()
-                                .gap_1()
-                                .child(Self::render_plus_button(cx))
-                                .child(self.render_zoom_button(cx)),
-                        )
-                } else {
-                    div()
-                });
-
-            v_stack()
-                .size_full()
-                .on_action(cx.listener(|this, _: &workspace::NewFile, cx| {
-                    this.new_conversation(cx);
-                }))
-                .on_action(cx.listener(AssistantPanel::reset_credentials))
-                .on_action(cx.listener(AssistantPanel::toggle_zoom))
-                .on_action(cx.listener(AssistantPanel::deploy))
-                .on_action(cx.listener(AssistantPanel::select_next_match))
-                .on_action(cx.listener(AssistantPanel::select_prev_match))
-                .on_action(cx.listener(AssistantPanel::handle_editor_cancel))
-                .track_focus(&self.focus_handle)
-                .child(header)
-                .children(if self.toolbar.read(cx).hidden() {
-                    None
-                } else {
-                    Some(self.toolbar.clone())
-                })
-                .child(
-                    div()
-                        .flex_1()
-                        .child(if let Some(editor) = self.active_editor() {
-                            editor.clone().into_any_element()
-                        } else {
-                            let view = cx.view().clone();
-                            let scroll_handle = self.saved_conversations_scroll_handle.clone();
-                            let conversation_count = self.saved_conversations.len();
-                            canvas(move |bounds, cx| {
-                                uniform_list(
-                                    view,
-                                    "saved_conversations",
-                                    conversation_count,
-                                    |this, range, cx| {
-                                        range
-                                            .map(|ix| this.render_saved_conversation(ix, cx))
-                                            .collect()
-                                    },
-                                )
-                                .track_scroll(scroll_handle)
-                                .into_any_element()
-                                .draw(
-                                    bounds.origin,
-                                    bounds.size.map(AvailableSpace::Definite),
-                                    cx,
-                                );
-                            })
-                            .size_full()
-                            .into_any_element()
-                        }),
-                )
-        }
-    }
-}
-
-impl Panel for AssistantPanel {
-    fn persistent_name() -> &'static str {
-        "AssistantPanel"
-    }
-
-    fn position(&self, cx: &WindowContext) -> DockPosition {
-        match AssistantSettings::get_global(cx).dock {
-            AssistantDockPosition::Left => DockPosition::Left,
-            AssistantDockPosition::Bottom => DockPosition::Bottom,
-            AssistantDockPosition::Right => DockPosition::Right,
-        }
-    }
-
-    fn position_is_valid(&self, _: DockPosition) -> bool {
-        true
-    }
-
-    fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
-        settings::update_settings_file::<AssistantSettings>(self.fs.clone(), cx, move |settings| {
-            let dock = match position {
-                DockPosition::Left => AssistantDockPosition::Left,
-                DockPosition::Bottom => AssistantDockPosition::Bottom,
-                DockPosition::Right => AssistantDockPosition::Right,
-            };
-            settings.dock = Some(dock);
-        });
-    }
-
-    fn size(&self, cx: &WindowContext) -> Pixels {
-        let settings = AssistantSettings::get_global(cx);
-        match self.position(cx) {
-            DockPosition::Left | DockPosition::Right => {
-                self.width.unwrap_or_else(|| settings.default_width)
-            }
-            DockPosition::Bottom => self.height.unwrap_or_else(|| settings.default_height),
-        }
-    }
-
-    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
-        match self.position(cx) {
-            DockPosition::Left | DockPosition::Right => self.width = size,
-            DockPosition::Bottom => self.height = size,
-        }
-        cx.notify();
-    }
-
-    fn is_zoomed(&self, _: &WindowContext) -> bool {
-        self.zoomed
-    }
-
-    fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>) {
-        self.zoomed = zoomed;
-        cx.notify();
-    }
-
-    fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
-        if active {
-            self.load_credentials(cx);
-
-            if self.editors.is_empty() {
-                self.new_conversation(cx);
-            }
-        }
-    }
-
-    fn icon(&self, _cx: &WindowContext) -> Option<Icon> {
-        Some(Icon::Ai)
-    }
-
-    fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
-        Some("Assistant Panel")
-    }
-
-    fn toggle_action(&self) -> Box<dyn Action> {
-        Box::new(ToggleFocus)
-    }
-}
-
-impl EventEmitter<PanelEvent> for AssistantPanel {}
-
-impl FocusableView for AssistantPanel {
-    fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
-        self.focus_handle.clone()
-    }
-}
-
-enum ConversationEvent {
-    MessagesEdited,
-    SummaryChanged,
-    StreamedCompletion,
-}
-
-#[derive(Default)]
-struct Summary {
-    text: String,
-    done: bool,
-}
-
-struct Conversation {
-    id: Option<String>,
-    buffer: Model<Buffer>,
-    message_anchors: Vec<MessageAnchor>,
-    messages_metadata: HashMap<MessageId, MessageMetadata>,
-    next_message_id: MessageId,
-    summary: Option<Summary>,
-    pending_summary: Task<Option<()>>,
-    completion_count: usize,
-    pending_completions: Vec<PendingCompletion>,
-    model: OpenAIModel,
-    token_count: Option<usize>,
-    max_token_count: usize,
-    pending_token_count: Task<Option<()>>,
-    pending_save: Task<Result<()>>,
-    path: Option<PathBuf>,
-    _subscriptions: Vec<Subscription>,
-    completion_provider: Arc<dyn CompletionProvider>,
-}
-
-impl EventEmitter<ConversationEvent> for Conversation {}
-
-impl Conversation {
-    fn new(
-        language_registry: Arc<LanguageRegistry>,
-        cx: &mut ModelContext<Self>,
-        completion_provider: Arc<dyn CompletionProvider>,
-    ) -> Self {
-        let markdown = language_registry.language_for_name("Markdown");
-        let buffer = cx.new_model(|cx| {
-            let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), "");
-            buffer.set_language_registry(language_registry);
-            cx.spawn(|buffer, mut cx| async move {
-                let markdown = markdown.await?;
-                buffer.update(&mut cx, |buffer: &mut Buffer, cx| {
-                    buffer.set_language(Some(markdown), cx)
-                })?;
-                anyhow::Ok(())
-            })
-            .detach_and_log_err(cx);
-            buffer
-        });
-
-        let settings = AssistantSettings::get_global(cx);
-        let model = settings.default_open_ai_model.clone();
-
-        let mut this = Self {
-            id: Some(Uuid::new_v4().to_string()),
-            message_anchors: Default::default(),
-            messages_metadata: Default::default(),
-            next_message_id: Default::default(),
-            summary: None,
-            pending_summary: Task::ready(None),
-            completion_count: Default::default(),
-            pending_completions: Default::default(),
-            token_count: None,
-            max_token_count: tiktoken_rs::model::get_context_size(&model.full_name()),
-            pending_token_count: Task::ready(None),
-            model: model.clone(),
-            _subscriptions: vec![cx.subscribe(&buffer, Self::handle_buffer_event)],
-            pending_save: Task::ready(Ok(())),
-            path: None,
-            buffer,
-            completion_provider,
-        };
-        let message = MessageAnchor {
-            id: MessageId(post_inc(&mut this.next_message_id.0)),
-            start: language::Anchor::MIN,
-        };
-        this.message_anchors.push(message.clone());
-        this.messages_metadata.insert(
-            message.id,
-            MessageMetadata {
-                role: Role::User,
-                sent_at: Local::now(),
-                status: MessageStatus::Done,
-            },
-        );
-
-        this.count_remaining_tokens(cx);
-        this
-    }
-
-    fn serialize(&self, cx: &AppContext) -> SavedConversation {
-        SavedConversation {
-            id: self.id.clone(),
-            zed: "conversation".into(),
-            version: SavedConversation::VERSION.into(),
-            text: self.buffer.read(cx).text(),
-            message_metadata: self.messages_metadata.clone(),
-            messages: self
-                .messages(cx)
-                .map(|message| SavedMessage {
-                    id: message.id,
-                    start: message.offset_range.start,
-                })
-                .collect(),
-            summary: self
-                .summary
-                .as_ref()
-                .map(|summary| summary.text.clone())
-                .unwrap_or_default(),
-            model: self.model.clone(),
-        }
-    }
-
-    fn deserialize(
-        saved_conversation: SavedConversation,
-        path: PathBuf,
-        language_registry: Arc<LanguageRegistry>,
-        cx: &mut ModelContext<Self>,
-    ) -> Self {
-        let id = match saved_conversation.id {
-            Some(id) => Some(id),
-            None => Some(Uuid::new_v4().to_string()),
-        };
-        let model = saved_conversation.model;
-        let completion_provider: Arc<dyn CompletionProvider> = Arc::new(
-            OpenAICompletionProvider::new(model.full_name(), cx.background_executor().clone()),
-        );
-        completion_provider.retrieve_credentials(cx);
-        let markdown = language_registry.language_for_name("Markdown");
-        let mut message_anchors = Vec::new();
-        let mut next_message_id = MessageId(0);
-        let buffer = cx.new_model(|cx| {
-            let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), saved_conversation.text);
-            for message in saved_conversation.messages {
-                message_anchors.push(MessageAnchor {
-                    id: message.id,
-                    start: buffer.anchor_before(message.start),
-                });
-                next_message_id = cmp::max(next_message_id, MessageId(message.id.0 + 1));
-            }
-            buffer.set_language_registry(language_registry);
-            cx.spawn(|buffer, mut cx| async move {
-                let markdown = markdown.await?;
-                buffer.update(&mut cx, |buffer: &mut Buffer, cx| {
-                    buffer.set_language(Some(markdown), cx)
-                })?;
-                anyhow::Ok(())
-            })
-            .detach_and_log_err(cx);
-            buffer
-        });
-
-        let mut this = Self {
-            id,
-            message_anchors,
-            messages_metadata: saved_conversation.message_metadata,
-            next_message_id,
-            summary: Some(Summary {
-                text: saved_conversation.summary,
-                done: true,
-            }),
-            pending_summary: Task::ready(None),
-            completion_count: Default::default(),
-            pending_completions: Default::default(),
-            token_count: None,
-            max_token_count: tiktoken_rs::model::get_context_size(&model.full_name()),
-            pending_token_count: Task::ready(None),
-            model,
-            _subscriptions: vec![cx.subscribe(&buffer, Self::handle_buffer_event)],
-            pending_save: Task::ready(Ok(())),
-            path: Some(path),
-            buffer,
-            completion_provider,
-        };
-        this.count_remaining_tokens(cx);
-        this
-    }
-
-    fn handle_buffer_event(
-        &mut self,
-        _: Model<Buffer>,
-        event: &language::Event,
-        cx: &mut ModelContext<Self>,
-    ) {
-        match event {
-            language::Event::Edited => {
-                self.count_remaining_tokens(cx);
-                cx.emit(ConversationEvent::MessagesEdited);
-            }
-            _ => {}
-        }
-    }
-
-    fn count_remaining_tokens(&mut self, cx: &mut ModelContext<Self>) {
-        let messages = self
-            .messages(cx)
-            .into_iter()
-            .filter_map(|message| {
-                Some(tiktoken_rs::ChatCompletionRequestMessage {
-                    role: match message.role {
-                        Role::User => "user".into(),
-                        Role::Assistant => "assistant".into(),
-                        Role::System => "system".into(),
-                    },
-                    content: Some(
-                        self.buffer
-                            .read(cx)
-                            .text_for_range(message.offset_range)
-                            .collect(),
-                    ),
-                    name: None,
-                    function_call: None,
-                })
-            })
-            .collect::<Vec<_>>();
-        let model = self.model.clone();
-        self.pending_token_count = cx.spawn(|this, mut cx| {
-            async move {
-                cx.background_executor()
-                    .timer(Duration::from_millis(200))
-                    .await;
-                let token_count = cx
-                    .background_executor()
-                    .spawn(async move {
-                        tiktoken_rs::num_tokens_from_messages(&model.full_name(), &messages)
-                    })
-                    .await?;
-
-                this.update(&mut cx, |this, cx| {
-                    this.max_token_count =
-                        tiktoken_rs::model::get_context_size(&this.model.full_name());
-                    this.token_count = Some(token_count);
-                    cx.notify()
-                })?;
-                anyhow::Ok(())
-            }
-            .log_err()
-        });
-    }
-
-    fn remaining_tokens(&self) -> Option<isize> {
-        Some(self.max_token_count as isize - self.token_count? as isize)
-    }
-
-    fn set_model(&mut self, model: OpenAIModel, cx: &mut ModelContext<Self>) {
-        self.model = model;
-        self.count_remaining_tokens(cx);
-        cx.notify();
-    }
-
-    fn assist(
-        &mut self,
-        selected_messages: HashSet<MessageId>,
-        cx: &mut ModelContext<Self>,
-    ) -> Vec<MessageAnchor> {
-        let mut user_messages = Vec::new();
-
-        let last_message_id = if let Some(last_message_id) =
-            self.message_anchors.iter().rev().find_map(|message| {
-                message
-                    .start
-                    .is_valid(self.buffer.read(cx))
-                    .then_some(message.id)
-            }) {
-            last_message_id
-        } else {
-            return Default::default();
-        };
-
-        let mut should_assist = false;
-        for selected_message_id in selected_messages {
-            let selected_message_role =
-                if let Some(metadata) = self.messages_metadata.get(&selected_message_id) {
-                    metadata.role
-                } else {
-                    continue;
-                };
-
-            if selected_message_role == Role::Assistant {
-                if let Some(user_message) = self.insert_message_after(
-                    selected_message_id,
-                    Role::User,
-                    MessageStatus::Done,
-                    cx,
-                ) {
-                    user_messages.push(user_message);
-                }
-            } else {
-                should_assist = true;
-            }
-        }
-
-        if should_assist {
-            if !self.completion_provider.has_credentials() {
-                return Default::default();
-            }
-
-            let request: Box<dyn CompletionRequest> = Box::new(OpenAIRequest {
-                model: self.model.full_name().to_string(),
-                messages: self
-                    .messages(cx)
-                    .filter(|message| matches!(message.status, MessageStatus::Done))
-                    .map(|message| message.to_open_ai_message(self.buffer.read(cx)))
-                    .collect(),
-                stream: true,
-                stop: vec![],
-                temperature: 1.0,
-            });
-
-            let stream = self.completion_provider.complete(request);
-            let assistant_message = self
-                .insert_message_after(last_message_id, Role::Assistant, MessageStatus::Pending, cx)
-                .unwrap();
-
-            // Queue up the user's next reply.
-            let user_message = self
-                .insert_message_after(assistant_message.id, Role::User, MessageStatus::Done, cx)
-                .unwrap();
-            user_messages.push(user_message);
-
-            let task = cx.spawn({
-                |this, mut cx| async move {
-                    let assistant_message_id = assistant_message.id;
-                    let stream_completion = async {
-                        let mut messages = stream.await?;
-
-                        while let Some(message) = messages.next().await {
-                            let text = message?;
-
-                            this.update(&mut cx, |this, cx| {
-                                let message_ix = this
-                                    .message_anchors
-                                    .iter()
-                                    .position(|message| message.id == assistant_message_id)?;
-                                this.buffer.update(cx, |buffer, cx| {
-                                    let offset = this.message_anchors[message_ix + 1..]
-                                        .iter()
-                                        .find(|message| message.start.is_valid(buffer))
-                                        .map_or(buffer.len(), |message| {
-                                            message.start.to_offset(buffer).saturating_sub(1)
-                                        });
-                                    buffer.edit([(offset..offset, text)], None, cx);
-                                });
-                                cx.emit(ConversationEvent::StreamedCompletion);
-
-                                Some(())
-                            })?;
-                            smol::future::yield_now().await;
-                        }
-
-                        this.update(&mut cx, |this, cx| {
-                            this.pending_completions
-                                .retain(|completion| completion.id != this.completion_count);
-                            this.summarize(cx);
-                        })?;
-
-                        anyhow::Ok(())
-                    };
-
-                    let result = stream_completion.await;
-
-                    this.update(&mut cx, |this, cx| {
-                        if let Some(metadata) =
-                            this.messages_metadata.get_mut(&assistant_message.id)
-                        {
-                            match result {
-                                Ok(_) => {
-                                    metadata.status = MessageStatus::Done;
-                                }
-                                Err(error) => {
-                                    metadata.status = MessageStatus::Error(SharedString::from(
-                                        error.to_string().trim().to_string(),
-                                    ));
-                                }
-                            }
-                            cx.notify();
-                        }
-                    })
-                    .ok();
-                }
-            });
-
-            self.pending_completions.push(PendingCompletion {
-                id: post_inc(&mut self.completion_count),
-                _task: task,
-            });
-        }
-
-        user_messages
-    }
-
-    fn cancel_last_assist(&mut self) -> bool {
-        self.pending_completions.pop().is_some()
-    }
-
-    fn cycle_message_roles(&mut self, ids: HashSet<MessageId>, cx: &mut ModelContext<Self>) {
-        for id in ids {
-            if let Some(metadata) = self.messages_metadata.get_mut(&id) {
-                metadata.role.cycle();
-                cx.emit(ConversationEvent::MessagesEdited);
-                cx.notify();
-            }
-        }
-    }
-
-    fn insert_message_after(
-        &mut self,
-        message_id: MessageId,
-        role: Role,
-        status: MessageStatus,
-        cx: &mut ModelContext<Self>,
-    ) -> Option<MessageAnchor> {
-        if let Some(prev_message_ix) = self
-            .message_anchors
-            .iter()
-            .position(|message| message.id == message_id)
-        {
-            // Find the next valid message after the one we were given.
-            let mut next_message_ix = prev_message_ix + 1;
-            while let Some(next_message) = self.message_anchors.get(next_message_ix) {
-                if next_message.start.is_valid(self.buffer.read(cx)) {
-                    break;
-                }
-                next_message_ix += 1;
-            }
-
-            let start = self.buffer.update(cx, |buffer, cx| {
-                let offset = self
-                    .message_anchors
-                    .get(next_message_ix)
-                    .map_or(buffer.len(), |message| message.start.to_offset(buffer) - 1);
-                buffer.edit([(offset..offset, "\n")], None, cx);
-                buffer.anchor_before(offset + 1)
-            });
-            let message = MessageAnchor {
-                id: MessageId(post_inc(&mut self.next_message_id.0)),
-                start,
-            };
-            self.message_anchors
-                .insert(next_message_ix, message.clone());
-            self.messages_metadata.insert(
-                message.id,
-                MessageMetadata {
-                    role,
-                    sent_at: Local::now(),
-                    status,
-                },
-            );
-            cx.emit(ConversationEvent::MessagesEdited);
-            Some(message)
-        } else {
-            None
-        }
-    }
-
-    fn split_message(
-        &mut self,
-        range: Range<usize>,
-        cx: &mut ModelContext<Self>,
-    ) -> (Option<MessageAnchor>, Option<MessageAnchor>) {
-        let start_message = self.message_for_offset(range.start, cx);
-        let end_message = self.message_for_offset(range.end, cx);
-        if let Some((start_message, end_message)) = start_message.zip(end_message) {
-            // Prevent splitting when range spans multiple messages.
-            if start_message.id != end_message.id {
-                return (None, None);
-            }
-
-            let message = start_message;
-            let role = message.role;
-            let mut edited_buffer = false;
-
-            let mut suffix_start = None;
-            if range.start > message.offset_range.start && range.end < message.offset_range.end - 1
-            {
-                if self.buffer.read(cx).chars_at(range.end).next() == Some('\n') {
-                    suffix_start = Some(range.end + 1);
-                } else if self.buffer.read(cx).reversed_chars_at(range.end).next() == Some('\n') {
-                    suffix_start = Some(range.end);
-                }
-            }
-
-            let suffix = if let Some(suffix_start) = suffix_start {
-                MessageAnchor {
-                    id: MessageId(post_inc(&mut self.next_message_id.0)),
-                    start: self.buffer.read(cx).anchor_before(suffix_start),
-                }
-            } else {
-                self.buffer.update(cx, |buffer, cx| {
-                    buffer.edit([(range.end..range.end, "\n")], None, cx);
-                });
-                edited_buffer = true;
-                MessageAnchor {
-                    id: MessageId(post_inc(&mut self.next_message_id.0)),
-                    start: self.buffer.read(cx).anchor_before(range.end + 1),
-                }
-            };
-
-            self.message_anchors
-                .insert(message.index_range.end + 1, suffix.clone());
-            self.messages_metadata.insert(
-                suffix.id,
-                MessageMetadata {
-                    role,
-                    sent_at: Local::now(),
-                    status: MessageStatus::Done,
-                },
-            );
-
-            let new_messages =
-                if range.start == range.end || range.start == message.offset_range.start {
-                    (None, Some(suffix))
-                } else {
-                    let mut prefix_end = None;
-                    if range.start > message.offset_range.start
-                        && range.end < message.offset_range.end - 1
-                    {
-                        if self.buffer.read(cx).chars_at(range.start).next() == Some('\n') {
-                            prefix_end = Some(range.start + 1);
-                        } else if self.buffer.read(cx).reversed_chars_at(range.start).next()
-                            == Some('\n')
-                        {
-                            prefix_end = Some(range.start);
-                        }
-                    }
-
-                    let selection = if let Some(prefix_end) = prefix_end {
-                        cx.emit(ConversationEvent::MessagesEdited);
-                        MessageAnchor {
-                            id: MessageId(post_inc(&mut self.next_message_id.0)),
-                            start: self.buffer.read(cx).anchor_before(prefix_end),
-                        }
-                    } else {
-                        self.buffer.update(cx, |buffer, cx| {
-                            buffer.edit([(range.start..range.start, "\n")], None, cx)
-                        });
-                        edited_buffer = true;
-                        MessageAnchor {
-                            id: MessageId(post_inc(&mut self.next_message_id.0)),
-                            start: self.buffer.read(cx).anchor_before(range.end + 1),
-                        }
-                    };
-
-                    self.message_anchors
-                        .insert(message.index_range.end + 1, selection.clone());
-                    self.messages_metadata.insert(
-                        selection.id,
-                        MessageMetadata {
-                            role,
-                            sent_at: Local::now(),
-                            status: MessageStatus::Done,
-                        },
-                    );
-                    (Some(selection), Some(suffix))
-                };
-
-            if !edited_buffer {
-                cx.emit(ConversationEvent::MessagesEdited);
-            }
-            new_messages
-        } else {
-            (None, None)
-        }
-    }
-
-    fn summarize(&mut self, cx: &mut ModelContext<Self>) {
-        if self.message_anchors.len() >= 2 && self.summary.is_none() {
-            if !self.completion_provider.has_credentials() {
-                return;
-            }
-
-            let messages = self
-                .messages(cx)
-                .take(2)
-                .map(|message| message.to_open_ai_message(self.buffer.read(cx)))
-                .chain(Some(RequestMessage {
-                    role: Role::User,
-                    content: "Summarize the conversation into a short title without punctuation"
-                        .into(),
-                }));
-            let request: Box<dyn CompletionRequest> = Box::new(OpenAIRequest {
-                model: self.model.full_name().to_string(),
-                messages: messages.collect(),
-                stream: true,
-                stop: vec![],
-                temperature: 1.0,
-            });
-
-            let stream = self.completion_provider.complete(request);
-            self.pending_summary = cx.spawn(|this, mut cx| {
-                async move {
-                    let mut messages = stream.await?;
-
-                    while let Some(message) = messages.next().await {
-                        let text = message?;
-                        this.update(&mut cx, |this, cx| {
-                            this.summary
-                                .get_or_insert(Default::default())
-                                .text
-                                .push_str(&text);
-                            cx.emit(ConversationEvent::SummaryChanged);
-                        })?;
-                    }
-
-                    this.update(&mut cx, |this, cx| {
-                        if let Some(summary) = this.summary.as_mut() {
-                            summary.done = true;
-                            cx.emit(ConversationEvent::SummaryChanged);
-                        }
-                    })?;
-
-                    anyhow::Ok(())
-                }
-                .log_err()
-            });
-        }
-    }
-
-    fn message_for_offset(&self, offset: usize, cx: &AppContext) -> Option<Message> {
-        self.messages_for_offsets([offset], cx).pop()
-    }
-
-    fn messages_for_offsets(
-        &self,
-        offsets: impl IntoIterator<Item = usize>,
-        cx: &AppContext,
-    ) -> Vec<Message> {
-        let mut result = Vec::new();
-
-        let mut messages = self.messages(cx).peekable();
-        let mut offsets = offsets.into_iter().peekable();
-        let mut current_message = messages.next();
-        while let Some(offset) = offsets.next() {
-            // Locate the message that contains the offset.
-            while current_message.as_ref().map_or(false, |message| {
-                !message.offset_range.contains(&offset) && messages.peek().is_some()
-            }) {
-                current_message = messages.next();
-            }
-            let Some(message) = current_message.as_ref() else {
-                break;
-            };
-
-            // Skip offsets that are in the same message.
-            while offsets.peek().map_or(false, |offset| {
-                message.offset_range.contains(offset) || messages.peek().is_none()
-            }) {
-                offsets.next();
-            }
-
-            result.push(message.clone());
-        }
-        result
-    }
-
-    fn messages<'a>(&'a self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Message> {
-        let buffer = self.buffer.read(cx);
-        let mut message_anchors = self.message_anchors.iter().enumerate().peekable();
-        iter::from_fn(move || {
-            while let Some((start_ix, message_anchor)) = message_anchors.next() {
-                let metadata = self.messages_metadata.get(&message_anchor.id)?;
-                let message_start = message_anchor.start.to_offset(buffer);
-                let mut message_end = None;
-                let mut end_ix = start_ix;
-                while let Some((_, next_message)) = message_anchors.peek() {
-                    if next_message.start.is_valid(buffer) {
-                        message_end = Some(next_message.start);
-                        break;
-                    } else {
-                        end_ix += 1;
-                        message_anchors.next();
-                    }
-                }
-                let message_end = message_end
-                    .unwrap_or(language::Anchor::MAX)
-                    .to_offset(buffer);
-                return Some(Message {
-                    index_range: start_ix..end_ix,
-                    offset_range: message_start..message_end,
-                    id: message_anchor.id,
-                    anchor: message_anchor.start,
-                    role: metadata.role,
-                    sent_at: metadata.sent_at,
-                    status: metadata.status.clone(),
-                });
-            }
-            None
-        })
-    }
-
-    fn save(
-        &mut self,
-        debounce: Option<Duration>,
-        fs: Arc<dyn Fs>,
-        cx: &mut ModelContext<Conversation>,
-    ) {
-        self.pending_save = cx.spawn(|this, mut cx| async move {
-            if let Some(debounce) = debounce {
-                cx.background_executor().timer(debounce).await;
-            }
-
-            let (old_path, summary) = this.read_with(&cx, |this, _| {
-                let path = this.path.clone();
-                let summary = if let Some(summary) = this.summary.as_ref() {
-                    if summary.done {
-                        Some(summary.text.clone())
-                    } else {
-                        None
-                    }
-                } else {
-                    None
-                };
-                (path, summary)
-            })?;
-
-            if let Some(summary) = summary {
-                let conversation = this.read_with(&cx, |this, cx| this.serialize(cx))?;
-                let path = if let Some(old_path) = old_path {
-                    old_path
-                } else {
-                    let mut discriminant = 1;
-                    let mut new_path;
-                    loop {
-                        new_path = CONVERSATIONS_DIR.join(&format!(
-                            "{} - {}.zed.json",
-                            summary.trim(),
-                            discriminant
-                        ));
-                        if fs.is_file(&new_path).await {
-                            discriminant += 1;
-                        } else {
-                            break;
-                        }
-                    }
-                    new_path
-                };
-
-                fs.create_dir(CONVERSATIONS_DIR.as_ref()).await?;
-                fs.atomic_write(path.clone(), serde_json::to_string(&conversation).unwrap())
-                    .await?;
-                this.update(&mut cx, |this, _| this.path = Some(path))?;
-            }
-
-            Ok(())
-        });
-    }
-}
-
-struct PendingCompletion {
-    id: usize,
-    _task: Task<()>,
-}
-
-enum ConversationEditorEvent {
-    TabContentChanged,
-}
-
-#[derive(Copy, Clone, Debug, PartialEq)]
-struct ScrollPosition {
-    offset_before_cursor: gpui::Point<f32>,
-    cursor: Anchor,
-}
-
-struct ConversationEditor {
-    conversation: Model<Conversation>,
-    fs: Arc<dyn Fs>,
-    workspace: WeakView<Workspace>,
-    editor: View<Editor>,
-    blocks: HashSet<BlockId>,
-    scroll_position: Option<ScrollPosition>,
-    _subscriptions: Vec<Subscription>,
-}
-
-impl ConversationEditor {
-    fn new(
-        completion_provider: Arc<dyn CompletionProvider>,
-        language_registry: Arc<LanguageRegistry>,
-        fs: Arc<dyn Fs>,
-        workspace: WeakView<Workspace>,
-        cx: &mut ViewContext<Self>,
-    ) -> Self {
-        let conversation =
-            cx.new_model(|cx| Conversation::new(language_registry, cx, completion_provider));
-        Self::for_conversation(conversation, fs, workspace, cx)
-    }
-
-    fn for_conversation(
-        conversation: Model<Conversation>,
-        fs: Arc<dyn Fs>,
-        workspace: WeakView<Workspace>,
-        cx: &mut ViewContext<Self>,
-    ) -> Self {
-        let editor = cx.new_view(|cx| {
-            let mut editor = Editor::for_buffer(conversation.read(cx).buffer.clone(), None, cx);
-            editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
-            editor.set_show_gutter(false, cx);
-            editor.set_show_wrap_guides(false, cx);
-            editor
-        });
-
-        let _subscriptions = vec![
-            cx.observe(&conversation, |_, _, cx| cx.notify()),
-            cx.subscribe(&conversation, Self::handle_conversation_event),
-            cx.subscribe(&editor, Self::handle_editor_event),
-        ];
-
-        let mut this = Self {
-            conversation,
-            editor,
-            blocks: Default::default(),
-            scroll_position: None,
-            fs,
-            workspace,
-            _subscriptions,
-        };
-        this.update_message_headers(cx);
-        this
-    }
-
-    fn assist(&mut self, _: &Assist, cx: &mut ViewContext<Self>) {
-        report_assistant_event(
-            self.workspace.clone(),
-            self.conversation.read(cx).id.clone(),
-            AssistantKind::Panel,
-            cx,
-        );
-
-        let cursors = self.cursors(cx);
-
-        let user_messages = self.conversation.update(cx, |conversation, cx| {
-            let selected_messages = conversation
-                .messages_for_offsets(cursors, cx)
-                .into_iter()
-                .map(|message| message.id)
-                .collect();
-            conversation.assist(selected_messages, cx)
-        });
-        let new_selections = user_messages
-            .iter()
-            .map(|message| {
-                let cursor = message
-                    .start
-                    .to_offset(self.conversation.read(cx).buffer.read(cx));
-                cursor..cursor
-            })
-            .collect::<Vec<_>>();
-        if !new_selections.is_empty() {
-            self.editor.update(cx, |editor, cx| {
-                editor.change_selections(
-                    Some(Autoscroll::Strategy(AutoscrollStrategy::Fit)),
-                    cx,
-                    |selections| selections.select_ranges(new_selections),
-                );
-            });
-            // Avoid scrolling to the new cursor position so the assistant's output is stable.
-            cx.defer(|this, _| this.scroll_position = None);
-        }
-    }
-
-    fn cancel_last_assist(&mut self, _: &editor::Cancel, cx: &mut ViewContext<Self>) {
-        if !self
-            .conversation
-            .update(cx, |conversation, _| conversation.cancel_last_assist())
-        {
-            cx.propagate();
-        }
-    }
-
-    fn cycle_message_role(&mut self, _: &CycleMessageRole, cx: &mut ViewContext<Self>) {
-        let cursors = self.cursors(cx);
-        self.conversation.update(cx, |conversation, cx| {
-            let messages = conversation
-                .messages_for_offsets(cursors, cx)
-                .into_iter()
-                .map(|message| message.id)
-                .collect();
-            conversation.cycle_message_roles(messages, cx)
-        });
-    }
-
-    fn cursors(&self, cx: &AppContext) -> Vec<usize> {
-        let selections = self.editor.read(cx).selections.all::<usize>(cx);
-        selections
-            .into_iter()
-            .map(|selection| selection.head())
-            .collect()
-    }
-
-    fn handle_conversation_event(
-        &mut self,
-        _: Model<Conversation>,
-        event: &ConversationEvent,
-        cx: &mut ViewContext<Self>,
-    ) {
-        match event {
-            ConversationEvent::MessagesEdited => {
-                self.update_message_headers(cx);
-                self.conversation.update(cx, |conversation, cx| {
-                    conversation.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
-                });
-            }
-            ConversationEvent::SummaryChanged => {
-                cx.emit(ConversationEditorEvent::TabContentChanged);
-                self.conversation.update(cx, |conversation, cx| {
-                    conversation.save(None, self.fs.clone(), cx);
-                });
-            }
-            ConversationEvent::StreamedCompletion => {
-                self.editor.update(cx, |editor, cx| {
-                    if let Some(scroll_position) = self.scroll_position {
-                        let snapshot = editor.snapshot(cx);
-                        let cursor_point = scroll_position.cursor.to_display_point(&snapshot);
-                        let scroll_top =
-                            cursor_point.row() as f32 - scroll_position.offset_before_cursor.y;
-                        editor.set_scroll_position(
-                            point(scroll_position.offset_before_cursor.x, scroll_top),
-                            cx,
-                        );
-                    }
-                });
-            }
-        }
-    }
-
-    fn handle_editor_event(
-        &mut self,
-        _: View<Editor>,
-        event: &EditorEvent,
-        cx: &mut ViewContext<Self>,
-    ) {
-        match event {
-            EditorEvent::ScrollPositionChanged { autoscroll, .. } => {
-                let cursor_scroll_position = self.cursor_scroll_position(cx);
-                if *autoscroll {
-                    self.scroll_position = cursor_scroll_position;
-                } else if self.scroll_position != cursor_scroll_position {
-                    self.scroll_position = None;
-                }
-            }
-            EditorEvent::SelectionsChanged { .. } => {
-                self.scroll_position = self.cursor_scroll_position(cx);
-            }
-            _ => {}
-        }
-    }
-
-    fn cursor_scroll_position(&self, cx: &mut ViewContext<Self>) -> Option<ScrollPosition> {
-        self.editor.update(cx, |editor, cx| {
-            let snapshot = editor.snapshot(cx);
-            let cursor = editor.selections.newest_anchor().head();
-            let cursor_row = cursor.to_display_point(&snapshot.display_snapshot).row() as f32;
-            let scroll_position = editor
-                .scroll_manager
-                .anchor()
-                .scroll_position(&snapshot.display_snapshot);
-
-            let scroll_bottom = scroll_position.y + editor.visible_line_count().unwrap_or(0.);
-            if (scroll_position.y..scroll_bottom).contains(&cursor_row) {
-                Some(ScrollPosition {
-                    cursor,
-                    offset_before_cursor: point(scroll_position.x, cursor_row - scroll_position.y),
-                })
-            } else {
-                None
-            }
-        })
-    }
-
-    fn update_message_headers(&mut self, cx: &mut ViewContext<Self>) {
-        self.editor.update(cx, |editor, cx| {
-            let buffer = editor.buffer().read(cx).snapshot(cx);
-            let excerpt_id = *buffer.as_singleton().unwrap().0;
-            let old_blocks = std::mem::take(&mut self.blocks);
-            let new_blocks = self
-                .conversation
-                .read(cx)
-                .messages(cx)
-                .map(|message| BlockProperties {
-                    position: buffer.anchor_in_excerpt(excerpt_id, message.anchor),
-                    height: 2,
-                    style: BlockStyle::Sticky,
-                    render: Arc::new({
-                        let conversation = self.conversation.clone();
-                        move |_cx| {
-                            let message_id = message.id;
-                            let sender = ButtonLike::new("role")
-                                .child(match message.role {
-                                    Role::User => Label::new("You").color(Color::Default),
-                                    Role::Assistant => {
-                                        Label::new("Assistant").color(Color::Modified)
-                                    }
-                                    Role::System => Label::new("System").color(Color::Warning),
-                                })
-                                .tooltip(|cx| {
-                                    Tooltip::with_meta(
-                                        "Toggle message role",
-                                        None,
-                                        "Available roles: You (User), Assistant, System",
-                                        cx,
-                                    )
-                                })
-                                .on_click({
-                                    let conversation = conversation.clone();
-                                    move |_, cx| {
-                                        conversation.update(cx, |conversation, cx| {
-                                            conversation.cycle_message_roles(
-                                                HashSet::from_iter(Some(message_id)),
-                                                cx,
-                                            )
-                                        })
-                                    }
-                                });
-
-                            h_stack()
-                                .id(("message_header", message_id.0))
-                                .h_11()
-                                .gap_1()
-                                .p_1()
-                                .child(sender)
-                                // TODO: Only show this if the message if the message has been sent
-                                .child(
-                                    Label::new(
-                                        FormatDistance::from_now(DateTimeType::Local(
-                                            message.sent_at,
-                                        ))
-                                        .hide_prefix(true)
-                                        .add_suffix(true)
-                                        .to_string(),
-                                    )
-                                    .color(Color::Muted),
-                                )
-                                .children(
-                                    if let MessageStatus::Error(error) = message.status.clone() {
-                                        Some(
-                                            div()
-                                                .id("error")
-                                                .tooltip(move |cx| Tooltip::text(error.clone(), cx))
-                                                .child(IconElement::new(Icon::XCircle)),
-                                        )
-                                    } else {
-                                        None
-                                    },
-                                )
-                                .into_any_element()
-                        }
-                    }),
-                    disposition: BlockDisposition::Above,
-                })
-                .collect::<Vec<_>>();
-
-            editor.remove_blocks(old_blocks, None, cx);
-            let ids = editor.insert_blocks(new_blocks, None, cx);
-            self.blocks = HashSet::from_iter(ids);
-        });
-    }
-
-    fn quote_selection(
-        workspace: &mut Workspace,
-        _: &QuoteSelection,
-        cx: &mut ViewContext<Workspace>,
-    ) {
-        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
-            return;
-        };
-        let Some(editor) = workspace
-            .active_item(cx)
-            .and_then(|item| item.act_as::<Editor>(cx))
-        else {
-            return;
-        };
-
-        let editor = editor.read(cx);
-        let range = editor.selections.newest::<usize>(cx).range();
-        let buffer = editor.buffer().read(cx).snapshot(cx);
-        let start_language = buffer.language_at(range.start);
-        let end_language = buffer.language_at(range.end);
-        let language_name = if start_language == end_language {
-            start_language.map(|language| language.name())
-        } else {
-            None
-        };
-        let language_name = language_name.as_deref().unwrap_or("").to_lowercase();
-
-        let selected_text = buffer.text_for_range(range).collect::<String>();
-        let text = if selected_text.is_empty() {
-            None
-        } else {
-            Some(if language_name == "markdown" {
-                selected_text
-                    .lines()
-                    .map(|line| format!("> {}", line))
-                    .collect::<Vec<_>>()
-                    .join("\n")
-            } else {
-                format!("```{language_name}\n{selected_text}\n```")
-            })
-        };
-
-        // Activate the panel
-        if !panel.focus_handle(cx).contains_focused(cx) {
-            workspace.toggle_panel_focus::<AssistantPanel>(cx);
-        }
-
-        if let Some(text) = text {
-            panel.update(cx, |panel, cx| {
-                let conversation = panel
-                    .active_editor()
-                    .cloned()
-                    .unwrap_or_else(|| panel.new_conversation(cx));
-                conversation.update(cx, |conversation, cx| {
-                    conversation
-                        .editor
-                        .update(cx, |editor, cx| editor.insert(&text, cx))
-                });
-            });
-        }
-    }
-
-    fn copy(&mut self, _: &editor::Copy, cx: &mut ViewContext<Self>) {
-        let editor = self.editor.read(cx);
-        let conversation = self.conversation.read(cx);
-        if editor.selections.count() == 1 {
-            let selection = editor.selections.newest::<usize>(cx);
-            let mut copied_text = String::new();
-            let mut spanned_messages = 0;
-            for message in conversation.messages(cx) {
-                if message.offset_range.start >= selection.range().end {
-                    break;
-                } else if message.offset_range.end >= selection.range().start {
-                    let range = cmp::max(message.offset_range.start, selection.range().start)
-                        ..cmp::min(message.offset_range.end, selection.range().end);
-                    if !range.is_empty() {
-                        spanned_messages += 1;
-                        write!(&mut copied_text, "## {}\n\n", message.role).unwrap();
-                        for chunk in conversation.buffer.read(cx).text_for_range(range) {
-                            copied_text.push_str(&chunk);
-                        }
-                        copied_text.push('\n');
-                    }
-                }
-            }
-
-            if spanned_messages > 1 {
-                cx.write_to_clipboard(ClipboardItem::new(copied_text));
-                return;
-            }
-        }
-
-        cx.propagate();
-    }
-
-    fn split(&mut self, _: &Split, cx: &mut ViewContext<Self>) {
-        self.conversation.update(cx, |conversation, cx| {
-            let selections = self.editor.read(cx).selections.disjoint_anchors();
-            for selection in selections.into_iter() {
-                let buffer = self.editor.read(cx).buffer().read(cx).snapshot(cx);
-                let range = selection
-                    .map(|endpoint| endpoint.to_offset(&buffer))
-                    .range();
-                conversation.split_message(range, cx);
-            }
-        });
-    }
-
-    fn save(&mut self, _: &Save, cx: &mut ViewContext<Self>) {
-        self.conversation.update(cx, |conversation, cx| {
-            conversation.save(None, self.fs.clone(), cx)
-        });
-    }
-
-    fn cycle_model(&mut self, cx: &mut ViewContext<Self>) {
-        self.conversation.update(cx, |conversation, cx| {
-            let new_model = conversation.model.cycle();
-            conversation.set_model(new_model, cx);
-        });
-    }
-
-    fn title(&self, cx: &AppContext) -> String {
-        self.conversation
-            .read(cx)
-            .summary
-            .as_ref()
-            .map(|summary| summary.text.clone())
-            .unwrap_or_else(|| "New Conversation".into())
-    }
-
-    fn render_current_model(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        Button::new(
-            "current_model",
-            self.conversation.read(cx).model.short_name(),
-        )
-        .style(ButtonStyle::Filled)
-        .tooltip(move |cx| Tooltip::text("Change Model", cx))
-        .on_click(cx.listener(|this, _, cx| this.cycle_model(cx)))
-    }
-
-    fn render_remaining_tokens(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> {
-        let remaining_tokens = self.conversation.read(cx).remaining_tokens()?;
-        let remaining_tokens_color = if remaining_tokens <= 0 {
-            Color::Error
-        } else if remaining_tokens <= 500 {
-            Color::Warning
-        } else {
-            Color::Default
-        };
-        Some(Label::new(remaining_tokens.to_string()).color(remaining_tokens_color))
-    }
-}
-
-impl EventEmitter<ConversationEditorEvent> for ConversationEditor {}
-
-impl Render for ConversationEditor {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element {
-        div()
-            .key_context("ConversationEditor")
-            .capture_action(cx.listener(ConversationEditor::cancel_last_assist))
-            .capture_action(cx.listener(ConversationEditor::save))
-            .capture_action(cx.listener(ConversationEditor::copy))
-            .capture_action(cx.listener(ConversationEditor::cycle_message_role))
-            .on_action(cx.listener(ConversationEditor::assist))
-            .on_action(cx.listener(ConversationEditor::split))
-            .size_full()
-            .relative()
-            .child(
-                div()
-                    .size_full()
-                    .pl_2()
-                    .bg(cx.theme().colors().editor_background)
-                    .child(self.editor.clone()),
-            )
-            .child(
-                h_stack()
-                    .absolute()
-                    .gap_1()
-                    .top_3()
-                    .right_5()
-                    .child(self.render_current_model(cx))
-                    .children(self.render_remaining_tokens(cx)),
-            )
-    }
-}
-
-impl FocusableView for ConversationEditor {
-    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
-        self.editor.focus_handle(cx)
-    }
-}
-
-#[derive(Clone, Debug)]
-struct MessageAnchor {
-    id: MessageId,
-    start: language::Anchor,
-}
-
-#[derive(Clone, Debug)]
-pub struct Message {
-    offset_range: Range<usize>,
-    index_range: Range<usize>,
-    id: MessageId,
-    anchor: language::Anchor,
-    role: Role,
-    sent_at: DateTime<Local>,
-    status: MessageStatus,
-}
-
-impl Message {
-    fn to_open_ai_message(&self, buffer: &Buffer) -> RequestMessage {
-        let content = buffer
-            .text_for_range(self.offset_range.clone())
-            .collect::<String>();
-        RequestMessage {
-            role: self.role,
-            content: content.trim_end().into(),
-        }
-    }
-}
-
-enum InlineAssistantEvent {
-    Confirmed {
-        prompt: String,
-        include_conversation: bool,
-        retrieve_context: bool,
-    },
-    Canceled,
-    Dismissed,
-    IncludeConversationToggled {
-        include_conversation: bool,
-    },
-    RetrieveContextToggled {
-        retrieve_context: bool,
-    },
-}
-
-struct InlineAssistant {
-    id: usize,
-    prompt_editor: View<Editor>,
-    workspace: WeakView<Workspace>,
-    confirmed: bool,
-    include_conversation: bool,
-    measurements: Rc<Cell<BlockMeasurements>>,
-    prompt_history: VecDeque<String>,
-    prompt_history_ix: Option<usize>,
-    pending_prompt: String,
-    codegen: Model<Codegen>,
-    _subscriptions: Vec<Subscription>,
-    retrieve_context: bool,
-    semantic_index: Option<Model<SemanticIndex>>,
-    semantic_permissioned: Option<bool>,
-    project: WeakModel<Project>,
-    maintain_rate_limit: Option<Task<()>>,
-}
-
-impl EventEmitter<InlineAssistantEvent> for InlineAssistant {}
-
-impl Render for InlineAssistant {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element {
-        let measurements = self.measurements.get();
-        h_stack()
-            .w_full()
-            .py_2()
-            .border_y_1()
-            .border_color(cx.theme().colors().border)
-            .on_action(cx.listener(Self::confirm))
-            .on_action(cx.listener(Self::cancel))
-            .on_action(cx.listener(Self::toggle_include_conversation))
-            .on_action(cx.listener(Self::toggle_retrieve_context))
-            .on_action(cx.listener(Self::move_up))
-            .on_action(cx.listener(Self::move_down))
-            .child(
-                h_stack()
-                    .justify_center()
-                    .w(measurements.gutter_width)
-                    .child(
-                        IconButton::new("include_conversation", Icon::Ai)
-                            .on_click(cx.listener(|this, _, cx| {
-                                this.toggle_include_conversation(&ToggleIncludeConversation, cx)
-                            }))
-                            .selected(self.include_conversation)
-                            .tooltip(|cx| {
-                                Tooltip::for_action(
-                                    "Include Conversation",
-                                    &ToggleIncludeConversation,
-                                    cx,
-                                )
-                            }),
-                    )
-                    .children(if SemanticIndex::enabled(cx) {
-                        Some(
-                            IconButton::new("retrieve_context", Icon::MagnifyingGlass)
-                                .on_click(cx.listener(|this, _, cx| {
-                                    this.toggle_retrieve_context(&ToggleRetrieveContext, cx)
-                                }))
-                                .selected(self.retrieve_context)
-                                .tooltip(|cx| {
-                                    Tooltip::for_action(
-                                        "Retrieve Context",
-                                        &ToggleRetrieveContext,
-                                        cx,
-                                    )
-                                }),
-                        )
-                    } else {
-                        None
-                    })
-                    .children(if let Some(error) = self.codegen.read(cx).error() {
-                        let error_message = SharedString::from(error.to_string());
-                        Some(
-                            div()
-                                .id("error")
-                                .tooltip(move |cx| Tooltip::text(error_message.clone(), cx))
-                                .child(IconElement::new(Icon::XCircle).color(Color::Error)),
-                        )
-                    } else {
-                        None
-                    }),
-            )
-            .child(
-                h_stack()
-                    .w_full()
-                    .ml(measurements.anchor_x - measurements.gutter_width)
-                    .child(self.render_prompt_editor(cx)),
-            )
-            .children(if self.retrieve_context {
-                self.retrieve_context_status(cx)
-            } else {
-                None
-            })
-    }
-}
-
-impl FocusableView for InlineAssistant {
-    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
-        self.prompt_editor.focus_handle(cx)
-    }
-}
-
-impl InlineAssistant {
-    fn new(
-        id: usize,
-        measurements: Rc<Cell<BlockMeasurements>>,
-        include_conversation: bool,
-        prompt_history: VecDeque<String>,
-        codegen: Model<Codegen>,
-        workspace: WeakView<Workspace>,
-        cx: &mut ViewContext<Self>,
-        retrieve_context: bool,
-        semantic_index: Option<Model<SemanticIndex>>,
-        project: Model<Project>,
-    ) -> Self {
-        let prompt_editor = cx.new_view(|cx| {
-            let mut editor = Editor::single_line(cx);
-            let placeholder = match codegen.read(cx).kind() {
-                CodegenKind::Transform { .. } => "Enter transformation prompt…",
-                CodegenKind::Generate { .. } => "Enter generation prompt…",
-            };
-            editor.set_placeholder_text(placeholder, cx);
-            editor
-        });
-        cx.focus_view(&prompt_editor);
-
-        let mut subscriptions = vec![
-            cx.observe(&codegen, Self::handle_codegen_changed),
-            cx.subscribe(&prompt_editor, Self::handle_prompt_editor_events),
-        ];
-
-        if let Some(semantic_index) = semantic_index.clone() {
-            subscriptions.push(cx.observe(&semantic_index, Self::semantic_index_changed));
-        }
-
-        let assistant = Self {
-            id,
-            prompt_editor,
-            workspace,
-            confirmed: false,
-            include_conversation,
-            measurements,
-            prompt_history,
-            prompt_history_ix: None,
-            pending_prompt: String::new(),
-            codegen,
-            _subscriptions: subscriptions,
-            retrieve_context,
-            semantic_permissioned: None,
-            semantic_index,
-            project: project.downgrade(),
-            maintain_rate_limit: None,
-        };
-
-        assistant.index_project(cx).log_err();
-
-        assistant
-    }
-
-    fn semantic_permissioned(&self, cx: &mut ViewContext<Self>) -> Task<Result<bool>> {
-        if let Some(value) = self.semantic_permissioned {
-            return Task::ready(Ok(value));
-        }
-
-        let Some(project) = self.project.upgrade() else {
-            return Task::ready(Err(anyhow!("project was dropped")));
-        };
-
-        self.semantic_index
-            .as_ref()
-            .map(|semantic| {
-                semantic.update(cx, |this, cx| this.project_previously_indexed(&project, cx))
-            })
-            .unwrap_or(Task::ready(Ok(false)))
-    }
-
-    fn handle_prompt_editor_events(
-        &mut self,
-        _: View<Editor>,
-        event: &EditorEvent,
-        cx: &mut ViewContext<Self>,
-    ) {
-        if let EditorEvent::Edited = event {
-            self.pending_prompt = self.prompt_editor.read(cx).text(cx);
-            cx.notify();
-        }
-    }
-
-    fn semantic_index_changed(
-        &mut self,
-        semantic_index: Model<SemanticIndex>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let Some(project) = self.project.upgrade() else {
-            return;
-        };
-
-        let status = semantic_index.read(cx).status(&project);
-        match status {
-            SemanticIndexStatus::Indexing {
-                rate_limit_expiry: Some(_),
-                ..
-            } => {
-                if self.maintain_rate_limit.is_none() {
-                    self.maintain_rate_limit = Some(cx.spawn(|this, mut cx| async move {
-                        loop {
-                            cx.background_executor().timer(Duration::from_secs(1)).await;
-                            this.update(&mut cx, |_, cx| cx.notify()).log_err();
-                        }
-                    }));
-                }
-                return;
-            }
-            _ => {
-                self.maintain_rate_limit = None;
-            }
-        }
-    }
-
-    fn handle_codegen_changed(&mut self, _: Model<Codegen>, cx: &mut ViewContext<Self>) {
-        let is_read_only = !self.codegen.read(cx).idle();
-        self.prompt_editor.update(cx, |editor, _cx| {
-            let was_read_only = editor.read_only();
-            if was_read_only != is_read_only {
-                if is_read_only {
-                    editor.set_read_only(true);
-                } else {
-                    self.confirmed = false;
-                    editor.set_read_only(false);
-                }
-            }
-        });
-        cx.notify();
-    }
-
-    fn cancel(&mut self, _: &editor::Cancel, cx: &mut ViewContext<Self>) {
-        cx.emit(InlineAssistantEvent::Canceled);
-    }
-
-    fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
-        if self.confirmed {
-            cx.emit(InlineAssistantEvent::Dismissed);
-        } else {
-            report_assistant_event(self.workspace.clone(), None, AssistantKind::Inline, cx);
-
-            let prompt = self.prompt_editor.read(cx).text(cx);
-            self.prompt_editor
-                .update(cx, |editor, _cx| editor.set_read_only(true));
-            cx.emit(InlineAssistantEvent::Confirmed {
-                prompt,
-                include_conversation: self.include_conversation,
-                retrieve_context: self.retrieve_context,
-            });
-            self.confirmed = true;
-            cx.notify();
-        }
-    }
-
-    fn toggle_retrieve_context(&mut self, _: &ToggleRetrieveContext, cx: &mut ViewContext<Self>) {
-        let semantic_permissioned = self.semantic_permissioned(cx);
-
-        let Some(project) = self.project.upgrade() else {
-            return;
-        };
-
-        let project_name = project
-            .read(cx)
-            .worktree_root_names(cx)
-            .collect::<Vec<&str>>()
-            .join("/");
-        let is_plural = project_name.chars().filter(|letter| *letter == '/').count() > 0;
-        let prompt_text = format!("Would you like to index the '{}' project{} for context retrieval? This requires sending code to the OpenAI API", project_name,
-            if is_plural {
-                "s"
-            } else {""});
-
-        cx.spawn(|this, mut cx| async move {
-            // If Necessary prompt user
-            if !semantic_permissioned.await.unwrap_or(false) {
-                let answer = this.update(&mut cx, |_, cx| {
-                    cx.prompt(
-                        PromptLevel::Info,
-                        prompt_text.as_str(),
-                        &["Continue", "Cancel"],
-                    )
-                })?;
-
-                if answer.await? == 0 {
-                    this.update(&mut cx, |this, _| {
-                        this.semantic_permissioned = Some(true);
-                    })?;
-                } else {
-                    return anyhow::Ok(());
-                }
-            }
-
-            // If permissioned, update context appropriately
-            this.update(&mut cx, |this, cx| {
-                this.retrieve_context = !this.retrieve_context;
-
-                cx.emit(InlineAssistantEvent::RetrieveContextToggled {
-                    retrieve_context: this.retrieve_context,
-                });
-
-                if this.retrieve_context {
-                    this.index_project(cx).log_err();
-                }
-
-                cx.notify();
-            })?;
-
-            anyhow::Ok(())
-        })
-        .detach_and_log_err(cx);
-    }
-
-    fn index_project(&self, cx: &mut ViewContext<Self>) -> anyhow::Result<()> {
-        let Some(project) = self.project.upgrade() else {
-            return Err(anyhow!("project was dropped!"));
-        };
-
-        let semantic_permissioned = self.semantic_permissioned(cx);
-        if let Some(semantic_index) = SemanticIndex::global(cx) {
-            cx.spawn(|_, mut cx| async move {
-                // This has to be updated to accomodate for semantic_permissions
-                if semantic_permissioned.await.unwrap_or(false) {
-                    semantic_index
-                        .update(&mut cx, |index, cx| index.index_project(project, cx))?
-                        .await
-                } else {
-                    Err(anyhow!("project is not permissioned for semantic indexing"))
-                }
-            })
-            .detach_and_log_err(cx);
-        }
-
-        anyhow::Ok(())
-    }
-
-    fn retrieve_context_status(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
-        let Some(project) = self.project.upgrade() else {
-            return None;
-        };
-
-        let semantic_index = SemanticIndex::global(cx)?;
-        let status = semantic_index.update(cx, |index, _| index.status(&project));
-        match status {
-            SemanticIndexStatus::NotAuthenticated {} => Some(
-                div()
-                    .id("error")
-                    .tooltip(|cx| Tooltip::text("Not Authenticated. Please ensure you have a valid 'OPENAI_API_KEY' in your environment variables.", cx))
-                    .child(IconElement::new(Icon::XCircle))
-                    .into_any_element()
-            ),
-
-            SemanticIndexStatus::NotIndexed {} => Some(
-                div()
-                    .id("error")
-                    .tooltip(|cx| Tooltip::text("Not Indexed", cx))
-                    .child(IconElement::new(Icon::XCircle))
-                    .into_any_element()
-            ),
-
-            SemanticIndexStatus::Indexing {
-                remaining_files,
-                rate_limit_expiry,
-            } => {
-                let mut status_text = if remaining_files == 0 {
-                    "Indexing...".to_string()
-                } else {
-                    format!("Remaining files to index: {remaining_files}")
-                };
-
-                if let Some(rate_limit_expiry) = rate_limit_expiry {
-                    let remaining_seconds = rate_limit_expiry.duration_since(Instant::now());
-                    if remaining_seconds > Duration::from_secs(0) && remaining_files > 0 {
-                        write!(
-                            status_text,
-                            " (rate limit expires in {}s)",
-                            remaining_seconds.as_secs()
-                        )
-                        .unwrap();
-                    }
-                }
-
-                let status_text = SharedString::from(status_text);
-                Some(
-                    div()
-                        .id("update")
-                        .tooltip(move |cx| Tooltip::text(status_text.clone(), cx))
-                        .child(IconElement::new(Icon::Update).color(Color::Info))
-                        .into_any_element()
-                )
-            }
-
-            SemanticIndexStatus::Indexed {} => Some(
-                div()
-                    .id("check")
-                    .tooltip(|cx| Tooltip::text("Index up to date", cx))
-                    .child(IconElement::new(Icon::Check).color(Color::Success))
-                    .into_any_element()
-            ),
-        }
-    }
-
-    fn toggle_include_conversation(
-        &mut self,
-        _: &ToggleIncludeConversation,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.include_conversation = !self.include_conversation;
-        cx.emit(InlineAssistantEvent::IncludeConversationToggled {
-            include_conversation: self.include_conversation,
-        });
-        cx.notify();
-    }
-
-    fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
-        if let Some(ix) = self.prompt_history_ix {
-            if ix > 0 {
-                self.prompt_history_ix = Some(ix - 1);
-                let prompt = self.prompt_history[ix - 1].clone();
-                self.set_prompt(&prompt, cx);
-            }
-        } else if !self.prompt_history.is_empty() {
-            self.prompt_history_ix = Some(self.prompt_history.len() - 1);
-            let prompt = self.prompt_history[self.prompt_history.len() - 1].clone();
-            self.set_prompt(&prompt, cx);
-        }
-    }
-
-    fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
-        if let Some(ix) = self.prompt_history_ix {
-            if ix < self.prompt_history.len() - 1 {
-                self.prompt_history_ix = Some(ix + 1);
-                let prompt = self.prompt_history[ix + 1].clone();
-                self.set_prompt(&prompt, cx);
-            } else {
-                self.prompt_history_ix = None;
-                let pending_prompt = self.pending_prompt.clone();
-                self.set_prompt(&pending_prompt, cx);
-            }
-        }
-    }
-
-    fn set_prompt(&mut self, prompt: &str, cx: &mut ViewContext<Self>) {
-        self.prompt_editor.update(cx, |editor, cx| {
-            editor.buffer().update(cx, |buffer, cx| {
-                let len = buffer.len(cx);
-                buffer.edit([(0..len, prompt)], None, cx);
-            });
-        });
-    }
-
-    fn render_prompt_editor(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        let settings = ThemeSettings::get_global(cx);
-        let text_style = TextStyle {
-            color: if self.prompt_editor.read(cx).read_only() {
-                cx.theme().colors().text_disabled
-            } else {
-                cx.theme().colors().text
-            },
-            font_family: settings.ui_font.family.clone(),
-            font_features: settings.ui_font.features,
-            font_size: rems(0.875).into(),
-            font_weight: FontWeight::NORMAL,
-            font_style: FontStyle::Normal,
-            line_height: relative(1.3).into(),
-            background_color: None,
-            underline: None,
-            white_space: WhiteSpace::Normal,
-        };
-        EditorElement::new(
-            &self.prompt_editor,
-            EditorStyle {
-                background: cx.theme().colors().editor_background,
-                local_player: cx.theme().players().local(),
-                text: text_style,
-                ..Default::default()
-            },
-        )
-    }
-}
-
-// This wouldn't need to exist if we could pass parameters when rendering child views.
-#[derive(Copy, Clone, Default)]
-struct BlockMeasurements {
-    anchor_x: Pixels,
-    gutter_width: Pixels,
-}
-
-struct PendingInlineAssist {
-    editor: WeakView<Editor>,
-    inline_assistant: Option<(BlockId, View<InlineAssistant>)>,
-    codegen: Model<Codegen>,
-    _subscriptions: Vec<Subscription>,
-    project: WeakModel<Project>,
-}
-
-fn merge_ranges(ranges: &mut Vec<Range<Anchor>>, buffer: &MultiBufferSnapshot) {
-    ranges.sort_unstable_by(|a, b| {
-        a.start
-            .cmp(&b.start, buffer)
-            .then_with(|| b.end.cmp(&a.end, buffer))
-    });
-
-    let mut ix = 0;
-    while ix + 1 < ranges.len() {
-        let b = ranges[ix + 1].clone();
-        let a = &mut ranges[ix];
-        if a.end.cmp(&b.start, buffer).is_gt() {
-            if a.end.cmp(&b.end, buffer).is_lt() {
-                a.end = b.end;
-            }
-            ranges.remove(ix + 1);
-        } else {
-            ix += 1;
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::MessageId;
-    use ai::test::FakeCompletionProvider;
-    use gpui::AppContext;
-
-    #[gpui::test]
-    fn test_inserting_and_removing_messages(cx: &mut AppContext) {
-        let settings_store = SettingsStore::test(cx);
-        cx.set_global(settings_store);
-        init(cx);
-        let registry = Arc::new(LanguageRegistry::test());
-
-        let completion_provider = Arc::new(FakeCompletionProvider::new());
-        let conversation = cx.new_model(|cx| Conversation::new(registry, cx, completion_provider));
-        let buffer = conversation.read(cx).buffer.clone();
-
-        let message_1 = conversation.read(cx).message_anchors[0].clone();
-        assert_eq!(
-            messages(&conversation, cx),
-            vec![(message_1.id, Role::User, 0..0)]
-        );
-
-        let message_2 = conversation.update(cx, |conversation, cx| {
-            conversation
-                .insert_message_after(message_1.id, Role::Assistant, MessageStatus::Done, cx)
-                .unwrap()
-        });
-        assert_eq!(
-            messages(&conversation, cx),
-            vec![
-                (message_1.id, Role::User, 0..1),
-                (message_2.id, Role::Assistant, 1..1)
-            ]
-        );
-
-        buffer.update(cx, |buffer, cx| {
-            buffer.edit([(0..0, "1"), (1..1, "2")], None, cx)
-        });
-        assert_eq!(
-            messages(&conversation, cx),
-            vec![
-                (message_1.id, Role::User, 0..2),
-                (message_2.id, Role::Assistant, 2..3)
-            ]
-        );
-
-        let message_3 = conversation.update(cx, |conversation, cx| {
-            conversation
-                .insert_message_after(message_2.id, Role::User, MessageStatus::Done, cx)
-                .unwrap()
-        });
-        assert_eq!(
-            messages(&conversation, cx),
-            vec![
-                (message_1.id, Role::User, 0..2),
-                (message_2.id, Role::Assistant, 2..4),
-                (message_3.id, Role::User, 4..4)
-            ]
-        );
-
-        let message_4 = conversation.update(cx, |conversation, cx| {
-            conversation
-                .insert_message_after(message_2.id, Role::User, MessageStatus::Done, cx)
-                .unwrap()
-        });
-        assert_eq!(
-            messages(&conversation, cx),
-            vec![
-                (message_1.id, Role::User, 0..2),
-                (message_2.id, Role::Assistant, 2..4),
-                (message_4.id, Role::User, 4..5),
-                (message_3.id, Role::User, 5..5),
-            ]
-        );
-
-        buffer.update(cx, |buffer, cx| {
-            buffer.edit([(4..4, "C"), (5..5, "D")], None, cx)
-        });
-        assert_eq!(
-            messages(&conversation, cx),
-            vec![
-                (message_1.id, Role::User, 0..2),
-                (message_2.id, Role::Assistant, 2..4),
-                (message_4.id, Role::User, 4..6),
-                (message_3.id, Role::User, 6..7),
-            ]
-        );
-
-        // Deleting across message boundaries merges the messages.
-        buffer.update(cx, |buffer, cx| buffer.edit([(1..4, "")], None, cx));
-        assert_eq!(
-            messages(&conversation, cx),
-            vec![
-                (message_1.id, Role::User, 0..3),
-                (message_3.id, Role::User, 3..4),
-            ]
-        );
-
-        // Undoing the deletion should also undo the merge.
-        buffer.update(cx, |buffer, cx| buffer.undo(cx));
-        assert_eq!(
-            messages(&conversation, cx),
-            vec![
-                (message_1.id, Role::User, 0..2),
-                (message_2.id, Role::Assistant, 2..4),
-                (message_4.id, Role::User, 4..6),
-                (message_3.id, Role::User, 6..7),
-            ]
-        );
-
-        // Redoing the deletion should also redo the merge.
-        buffer.update(cx, |buffer, cx| buffer.redo(cx));
-        assert_eq!(
-            messages(&conversation, cx),
-            vec![
-                (message_1.id, Role::User, 0..3),
-                (message_3.id, Role::User, 3..4),
-            ]
-        );
-
-        // Ensure we can still insert after a merged message.
-        let message_5 = conversation.update(cx, |conversation, cx| {
-            conversation
-                .insert_message_after(message_1.id, Role::System, MessageStatus::Done, cx)
-                .unwrap()
-        });
-        assert_eq!(
-            messages(&conversation, cx),
-            vec![
-                (message_1.id, Role::User, 0..3),
-                (message_5.id, Role::System, 3..4),
-                (message_3.id, Role::User, 4..5)
-            ]
-        );
-    }
-
-    #[gpui::test]
-    fn test_message_splitting(cx: &mut AppContext) {
-        let settings_store = SettingsStore::test(cx);
-        cx.set_global(settings_store);
-        init(cx);
-        let registry = Arc::new(LanguageRegistry::test());
-        let completion_provider = Arc::new(FakeCompletionProvider::new());
-
-        let conversation = cx.new_model(|cx| Conversation::new(registry, cx, completion_provider));
-        let buffer = conversation.read(cx).buffer.clone();
-
-        let message_1 = conversation.read(cx).message_anchors[0].clone();
-        assert_eq!(
-            messages(&conversation, cx),
-            vec![(message_1.id, Role::User, 0..0)]
-        );
-
-        buffer.update(cx, |buffer, cx| {
-            buffer.edit([(0..0, "aaa\nbbb\nccc\nddd\n")], None, cx)
-        });
-
-        let (_, message_2) =
-            conversation.update(cx, |conversation, cx| conversation.split_message(3..3, cx));
-        let message_2 = message_2.unwrap();
-
-        // We recycle newlines in the middle of a split message
-        assert_eq!(buffer.read(cx).text(), "aaa\nbbb\nccc\nddd\n");
-        assert_eq!(
-            messages(&conversation, cx),
-            vec![
-                (message_1.id, Role::User, 0..4),
-                (message_2.id, Role::User, 4..16),
-            ]
-        );
-
-        let (_, message_3) =
-            conversation.update(cx, |conversation, cx| conversation.split_message(3..3, cx));
-        let message_3 = message_3.unwrap();
-
-        // We don't recycle newlines at the end of a split message
-        assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\nccc\nddd\n");
-        assert_eq!(
-            messages(&conversation, cx),
-            vec![
-                (message_1.id, Role::User, 0..4),
-                (message_3.id, Role::User, 4..5),
-                (message_2.id, Role::User, 5..17),
-            ]
-        );
-
-        let (_, message_4) =
-            conversation.update(cx, |conversation, cx| conversation.split_message(9..9, cx));
-        let message_4 = message_4.unwrap();
-        assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\nccc\nddd\n");
-        assert_eq!(
-            messages(&conversation, cx),
-            vec![
-                (message_1.id, Role::User, 0..4),
-                (message_3.id, Role::User, 4..5),
-                (message_2.id, Role::User, 5..9),
-                (message_4.id, Role::User, 9..17),
-            ]
-        );
-
-        let (_, message_5) =
-            conversation.update(cx, |conversation, cx| conversation.split_message(9..9, cx));
-        let message_5 = message_5.unwrap();
-        assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\n\nccc\nddd\n");
-        assert_eq!(
-            messages(&conversation, cx),
-            vec![
-                (message_1.id, Role::User, 0..4),
-                (message_3.id, Role::User, 4..5),
-                (message_2.id, Role::User, 5..9),
-                (message_4.id, Role::User, 9..10),
-                (message_5.id, Role::User, 10..18),
-            ]
-        );
-
-        let (message_6, message_7) = conversation.update(cx, |conversation, cx| {
-            conversation.split_message(14..16, cx)
-        });
-        let message_6 = message_6.unwrap();
-        let message_7 = message_7.unwrap();
-        assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\n\nccc\ndd\nd\n");
-        assert_eq!(
-            messages(&conversation, cx),
-            vec![
-                (message_1.id, Role::User, 0..4),
-                (message_3.id, Role::User, 4..5),
-                (message_2.id, Role::User, 5..9),
-                (message_4.id, Role::User, 9..10),
-                (message_5.id, Role::User, 10..14),
-                (message_6.id, Role::User, 14..17),
-                (message_7.id, Role::User, 17..19),
-            ]
-        );
-    }
-
-    #[gpui::test]
-    fn test_messages_for_offsets(cx: &mut AppContext) {
-        let settings_store = SettingsStore::test(cx);
-        cx.set_global(settings_store);
-        init(cx);
-        let registry = Arc::new(LanguageRegistry::test());
-        let completion_provider = Arc::new(FakeCompletionProvider::new());
-        let conversation = cx.new_model(|cx| Conversation::new(registry, cx, completion_provider));
-        let buffer = conversation.read(cx).buffer.clone();
-
-        let message_1 = conversation.read(cx).message_anchors[0].clone();
-        assert_eq!(
-            messages(&conversation, cx),
-            vec![(message_1.id, Role::User, 0..0)]
-        );
-
-        buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "aaa")], None, cx));
-        let message_2 = conversation
-            .update(cx, |conversation, cx| {
-                conversation.insert_message_after(message_1.id, Role::User, MessageStatus::Done, cx)
-            })
-            .unwrap();
-        buffer.update(cx, |buffer, cx| buffer.edit([(4..4, "bbb")], None, cx));
-
-        let message_3 = conversation
-            .update(cx, |conversation, cx| {
-                conversation.insert_message_after(message_2.id, Role::User, MessageStatus::Done, cx)
-            })
-            .unwrap();
-        buffer.update(cx, |buffer, cx| buffer.edit([(8..8, "ccc")], None, cx));
-
-        assert_eq!(buffer.read(cx).text(), "aaa\nbbb\nccc");
-        assert_eq!(
-            messages(&conversation, cx),
-            vec![
-                (message_1.id, Role::User, 0..4),
-                (message_2.id, Role::User, 4..8),
-                (message_3.id, Role::User, 8..11)
-            ]
-        );
-
-        assert_eq!(
-            message_ids_for_offsets(&conversation, &[0, 4, 9], cx),
-            [message_1.id, message_2.id, message_3.id]
-        );
-        assert_eq!(
-            message_ids_for_offsets(&conversation, &[0, 1, 11], cx),
-            [message_1.id, message_3.id]
-        );
-
-        let message_4 = conversation
-            .update(cx, |conversation, cx| {
-                conversation.insert_message_after(message_3.id, Role::User, MessageStatus::Done, cx)
-            })
-            .unwrap();
-        assert_eq!(buffer.read(cx).text(), "aaa\nbbb\nccc\n");
-        assert_eq!(
-            messages(&conversation, cx),
-            vec![
-                (message_1.id, Role::User, 0..4),
-                (message_2.id, Role::User, 4..8),
-                (message_3.id, Role::User, 8..12),
-                (message_4.id, Role::User, 12..12)
-            ]
-        );
-        assert_eq!(
-            message_ids_for_offsets(&conversation, &[0, 4, 8, 12], cx),
-            [message_1.id, message_2.id, message_3.id, message_4.id]
-        );
-
-        fn message_ids_for_offsets(
-            conversation: &Model<Conversation>,
-            offsets: &[usize],
-            cx: &AppContext,
-        ) -> Vec<MessageId> {
-            conversation
-                .read(cx)
-                .messages_for_offsets(offsets.iter().copied(), cx)
-                .into_iter()
-                .map(|message| message.id)
-                .collect()
-        }
-    }
-
-    #[gpui::test]
-    fn test_serialization(cx: &mut AppContext) {
-        let settings_store = SettingsStore::test(cx);
-        cx.set_global(settings_store);
-        init(cx);
-        let registry = Arc::new(LanguageRegistry::test());
-        let completion_provider = Arc::new(FakeCompletionProvider::new());
-        let conversation =
-            cx.new_model(|cx| Conversation::new(registry.clone(), cx, completion_provider));
-        let buffer = conversation.read(cx).buffer.clone();
-        let message_0 = conversation.read(cx).message_anchors[0].id;
-        let message_1 = conversation.update(cx, |conversation, cx| {
-            conversation
-                .insert_message_after(message_0, Role::Assistant, MessageStatus::Done, cx)
-                .unwrap()
-        });
-        let message_2 = conversation.update(cx, |conversation, cx| {
-            conversation
-                .insert_message_after(message_1.id, Role::System, MessageStatus::Done, cx)
-                .unwrap()
-        });
-        buffer.update(cx, |buffer, cx| {
-            buffer.edit([(0..0, "a"), (1..1, "b\nc")], None, cx);
-            buffer.finalize_last_transaction();
-        });
-        let _message_3 = conversation.update(cx, |conversation, cx| {
-            conversation
-                .insert_message_after(message_2.id, Role::System, MessageStatus::Done, cx)
-                .unwrap()
-        });
-        buffer.update(cx, |buffer, cx| buffer.undo(cx));
-        assert_eq!(buffer.read(cx).text(), "a\nb\nc\n");
-        assert_eq!(
-            messages(&conversation, cx),
-            [
-                (message_0, Role::User, 0..2),
-                (message_1.id, Role::Assistant, 2..6),
-                (message_2.id, Role::System, 6..6),
-            ]
-        );
-
-        let deserialized_conversation = cx.new_model(|cx| {
-            Conversation::deserialize(
-                conversation.read(cx).serialize(cx),
-                Default::default(),
-                registry.clone(),
-                cx,
-            )
-        });
-        let deserialized_buffer = deserialized_conversation.read(cx).buffer.clone();
-        assert_eq!(deserialized_buffer.read(cx).text(), "a\nb\nc\n");
-        assert_eq!(
-            messages(&deserialized_conversation, cx),
-            [
-                (message_0, Role::User, 0..2),
-                (message_1.id, Role::Assistant, 2..6),
-                (message_2.id, Role::System, 6..6),
-            ]
-        );
-    }
-
-    fn messages(
-        conversation: &Model<Conversation>,
-        cx: &AppContext,
-    ) -> Vec<(MessageId, Role, Range<usize>)> {
-        conversation
-            .read(cx)
-            .messages(cx)
-            .map(|message| (message.id, message.role, message.offset_range))
-            .collect()
-    }
-}
-
-fn report_assistant_event(
-    workspace: WeakView<Workspace>,
-    conversation_id: Option<String>,
-    assistant_kind: AssistantKind,
-    cx: &AppContext,
-) {
-    let Some(workspace) = workspace.upgrade() else {
-        return;
-    };
-
-    let client = workspace.read(cx).project().read(cx).client();
-    let telemetry = client.telemetry();
-
-    let model = AssistantSettings::get_global(cx)
-        .default_open_ai_model
-        .clone();
-
-    let telemetry_settings = TelemetrySettings::get_global(cx).clone();
-
-    telemetry.report_assistant_event(
-        telemetry_settings,
-        conversation_id,
-        assistant_kind,
-        model.full_name(),
-    )
-}

crates/assistant2/src/assistant_settings.rs 🔗

@@ -1,81 +0,0 @@
-use anyhow;
-use gpui::Pixels;
-use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
-use settings::Settings;
-
-#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
-pub enum OpenAIModel {
-    #[serde(rename = "gpt-3.5-turbo-0613")]
-    ThreePointFiveTurbo,
-    #[serde(rename = "gpt-4-0613")]
-    Four,
-    #[serde(rename = "gpt-4-1106-preview")]
-    FourTurbo,
-}
-
-impl OpenAIModel {
-    pub fn full_name(&self) -> &'static str {
-        match self {
-            OpenAIModel::ThreePointFiveTurbo => "gpt-3.5-turbo-0613",
-            OpenAIModel::Four => "gpt-4-0613",
-            OpenAIModel::FourTurbo => "gpt-4-1106-preview",
-        }
-    }
-
-    pub fn short_name(&self) -> &'static str {
-        match self {
-            OpenAIModel::ThreePointFiveTurbo => "gpt-3.5-turbo",
-            OpenAIModel::Four => "gpt-4",
-            OpenAIModel::FourTurbo => "gpt-4-turbo",
-        }
-    }
-
-    pub fn cycle(&self) -> Self {
-        match self {
-            OpenAIModel::ThreePointFiveTurbo => OpenAIModel::Four,
-            OpenAIModel::Four => OpenAIModel::FourTurbo,
-            OpenAIModel::FourTurbo => OpenAIModel::ThreePointFiveTurbo,
-        }
-    }
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum AssistantDockPosition {
-    Left,
-    Right,
-    Bottom,
-}
-
-#[derive(Deserialize, Debug)]
-pub struct AssistantSettings {
-    pub button: bool,
-    pub dock: AssistantDockPosition,
-    pub default_width: Pixels,
-    pub default_height: Pixels,
-    pub default_open_ai_model: OpenAIModel,
-}
-
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
-pub struct AssistantSettingsContent {
-    pub button: Option<bool>,
-    pub dock: Option<AssistantDockPosition>,
-    pub default_width: Option<f32>,
-    pub default_height: Option<f32>,
-    pub default_open_ai_model: Option<OpenAIModel>,
-}
-
-impl Settings for AssistantSettings {
-    const KEY: Option<&'static str> = Some("assistant");
-
-    type FileContent = AssistantSettingsContent;
-
-    fn load(
-        default_value: &Self::FileContent,
-        user_values: &[&Self::FileContent],
-        _: &mut gpui::AppContext,
-    ) -> anyhow::Result<Self> {
-        Self::load_via_json_merge(default_value, user_values)
-    }
-}

crates/assistant2/src/codegen.rs 🔗

@@ -1,688 +0,0 @@
-use crate::streaming_diff::{Hunk, StreamingDiff};
-use ai::completion::{CompletionProvider, CompletionRequest};
-use anyhow::Result;
-use editor::{Anchor, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint};
-use futures::{channel::mpsc, SinkExt, Stream, StreamExt};
-use gpui::{EventEmitter, Model, ModelContext, Task};
-use language::{Rope, TransactionId};
-use multi_buffer;
-use std::{cmp, future, ops::Range, sync::Arc};
-
-pub enum Event {
-    Finished,
-    Undone,
-}
-
-#[derive(Clone)]
-pub enum CodegenKind {
-    Transform { range: Range<Anchor> },
-    Generate { position: Anchor },
-}
-
-pub struct Codegen {
-    provider: Arc<dyn CompletionProvider>,
-    buffer: Model<MultiBuffer>,
-    snapshot: MultiBufferSnapshot,
-    kind: CodegenKind,
-    last_equal_ranges: Vec<Range<Anchor>>,
-    transaction_id: Option<TransactionId>,
-    error: Option<anyhow::Error>,
-    generation: Task<()>,
-    idle: bool,
-    _subscription: gpui::Subscription,
-}
-
-impl EventEmitter<Event> for Codegen {}
-
-impl Codegen {
-    pub fn new(
-        buffer: Model<MultiBuffer>,
-        kind: CodegenKind,
-        provider: Arc<dyn CompletionProvider>,
-        cx: &mut ModelContext<Self>,
-    ) -> Self {
-        let snapshot = buffer.read(cx).snapshot(cx);
-        Self {
-            provider,
-            buffer: buffer.clone(),
-            snapshot,
-            kind,
-            last_equal_ranges: Default::default(),
-            transaction_id: Default::default(),
-            error: Default::default(),
-            idle: true,
-            generation: Task::ready(()),
-            _subscription: cx.subscribe(&buffer, Self::handle_buffer_event),
-        }
-    }
-
-    fn handle_buffer_event(
-        &mut self,
-        _buffer: Model<MultiBuffer>,
-        event: &multi_buffer::Event,
-        cx: &mut ModelContext<Self>,
-    ) {
-        if let multi_buffer::Event::TransactionUndone { transaction_id } = event {
-            if self.transaction_id == Some(*transaction_id) {
-                self.transaction_id = None;
-                self.generation = Task::ready(());
-                cx.emit(Event::Undone);
-            }
-        }
-    }
-
-    pub fn range(&self) -> Range<Anchor> {
-        match &self.kind {
-            CodegenKind::Transform { range } => range.clone(),
-            CodegenKind::Generate { position } => position.bias_left(&self.snapshot)..*position,
-        }
-    }
-
-    pub fn kind(&self) -> &CodegenKind {
-        &self.kind
-    }
-
-    pub fn last_equal_ranges(&self) -> &[Range<Anchor>] {
-        &self.last_equal_ranges
-    }
-
-    pub fn idle(&self) -> bool {
-        self.idle
-    }
-
-    pub fn error(&self) -> Option<&anyhow::Error> {
-        self.error.as_ref()
-    }
-
-    pub fn start(&mut self, prompt: Box<dyn CompletionRequest>, cx: &mut ModelContext<Self>) {
-        let range = self.range();
-        let snapshot = self.snapshot.clone();
-        let selected_text = snapshot
-            .text_for_range(range.start..range.end)
-            .collect::<Rope>();
-
-        let selection_start = range.start.to_point(&snapshot);
-        let suggested_line_indent = snapshot
-            .suggested_indents(selection_start.row..selection_start.row + 1, cx)
-            .into_values()
-            .next()
-            .unwrap_or_else(|| snapshot.indent_size_for_line(selection_start.row));
-
-        let response = self.provider.complete(prompt);
-        self.generation = cx.spawn(|this, mut cx| {
-            async move {
-                let generate = async {
-                    let mut edit_start = range.start.to_offset(&snapshot);
-
-                    let (mut hunks_tx, mut hunks_rx) = mpsc::channel(1);
-                    let diff = cx.background_executor().spawn(async move {
-                        let chunks = strip_invalid_spans_from_codeblock(response.await?);
-                        futures::pin_mut!(chunks);
-                        let mut diff = StreamingDiff::new(selected_text.to_string());
-
-                        let mut new_text = String::new();
-                        let mut base_indent = None;
-                        let mut line_indent = None;
-                        let mut first_line = true;
-
-                        while let Some(chunk) = chunks.next().await {
-                            let chunk = chunk?;
-
-                            let mut lines = chunk.split('\n').peekable();
-                            while let Some(line) = lines.next() {
-                                new_text.push_str(line);
-                                if line_indent.is_none() {
-                                    if let Some(non_whitespace_ch_ix) =
-                                        new_text.find(|ch: char| !ch.is_whitespace())
-                                    {
-                                        line_indent = Some(non_whitespace_ch_ix);
-                                        base_indent = base_indent.or(line_indent);
-
-                                        let line_indent = line_indent.unwrap();
-                                        let base_indent = base_indent.unwrap();
-                                        let indent_delta = line_indent as i32 - base_indent as i32;
-                                        let mut corrected_indent_len = cmp::max(
-                                            0,
-                                            suggested_line_indent.len as i32 + indent_delta,
-                                        )
-                                            as usize;
-                                        if first_line {
-                                            corrected_indent_len = corrected_indent_len
-                                                .saturating_sub(selection_start.column as usize);
-                                        }
-
-                                        let indent_char = suggested_line_indent.char();
-                                        let mut indent_buffer = [0; 4];
-                                        let indent_str =
-                                            indent_char.encode_utf8(&mut indent_buffer);
-                                        new_text.replace_range(
-                                            ..line_indent,
-                                            &indent_str.repeat(corrected_indent_len),
-                                        );
-                                    }
-                                }
-
-                                if line_indent.is_some() {
-                                    hunks_tx.send(diff.push_new(&new_text)).await?;
-                                    new_text.clear();
-                                }
-
-                                if lines.peek().is_some() {
-                                    hunks_tx.send(diff.push_new("\n")).await?;
-                                    line_indent = None;
-                                    first_line = false;
-                                }
-                            }
-                        }
-                        hunks_tx.send(diff.push_new(&new_text)).await?;
-                        hunks_tx.send(diff.finish()).await?;
-
-                        anyhow::Ok(())
-                    });
-
-                    while let Some(hunks) = hunks_rx.next().await {
-                        this.update(&mut cx, |this, cx| {
-                            this.last_equal_ranges.clear();
-
-                            let transaction = this.buffer.update(cx, |buffer, cx| {
-                                // Avoid grouping assistant edits with user edits.
-                                buffer.finalize_last_transaction(cx);
-
-                                buffer.start_transaction(cx);
-                                buffer.edit(
-                                    hunks.into_iter().filter_map(|hunk| match hunk {
-                                        Hunk::Insert { text } => {
-                                            let edit_start = snapshot.anchor_after(edit_start);
-                                            Some((edit_start..edit_start, text))
-                                        }
-                                        Hunk::Remove { len } => {
-                                            let edit_end = edit_start + len;
-                                            let edit_range = snapshot.anchor_after(edit_start)
-                                                ..snapshot.anchor_before(edit_end);
-                                            edit_start = edit_end;
-                                            Some((edit_range, String::new()))
-                                        }
-                                        Hunk::Keep { len } => {
-                                            let edit_end = edit_start + len;
-                                            let edit_range = snapshot.anchor_after(edit_start)
-                                                ..snapshot.anchor_before(edit_end);
-                                            edit_start = edit_end;
-                                            this.last_equal_ranges.push(edit_range);
-                                            None
-                                        }
-                                    }),
-                                    None,
-                                    cx,
-                                );
-
-                                buffer.end_transaction(cx)
-                            });
-
-                            if let Some(transaction) = transaction {
-                                if let Some(first_transaction) = this.transaction_id {
-                                    // Group all assistant edits into the first transaction.
-                                    this.buffer.update(cx, |buffer, cx| {
-                                        buffer.merge_transactions(
-                                            transaction,
-                                            first_transaction,
-                                            cx,
-                                        )
-                                    });
-                                } else {
-                                    this.transaction_id = Some(transaction);
-                                    this.buffer.update(cx, |buffer, cx| {
-                                        buffer.finalize_last_transaction(cx)
-                                    });
-                                }
-                            }
-
-                            cx.notify();
-                        })?;
-                    }
-
-                    diff.await?;
-                    anyhow::Ok(())
-                };
-
-                let result = generate.await;
-                this.update(&mut cx, |this, cx| {
-                    this.last_equal_ranges.clear();
-                    this.idle = true;
-                    if let Err(error) = result {
-                        this.error = Some(error);
-                    }
-                    cx.emit(Event::Finished);
-                    cx.notify();
-                })
-                .ok();
-            }
-        });
-        self.error.take();
-        self.idle = false;
-        cx.notify();
-    }
-
-    pub fn undo(&mut self, cx: &mut ModelContext<Self>) {
-        if let Some(transaction_id) = self.transaction_id {
-            self.buffer
-                .update(cx, |buffer, cx| buffer.undo_transaction(transaction_id, cx));
-        }
-    }
-}
-
-fn strip_invalid_spans_from_codeblock(
-    stream: impl Stream<Item = Result<String>>,
-) -> impl Stream<Item = Result<String>> {
-    let mut first_line = true;
-    let mut buffer = String::new();
-    let mut starts_with_markdown_codeblock = false;
-    let mut includes_start_or_end_span = false;
-    stream.filter_map(move |chunk| {
-        let chunk = match chunk {
-            Ok(chunk) => chunk,
-            Err(err) => return future::ready(Some(Err(err))),
-        };
-        buffer.push_str(&chunk);
-
-        if buffer.len() > "<|S|".len() && buffer.starts_with("<|S|") {
-            includes_start_or_end_span = true;
-
-            buffer = buffer
-                .strip_prefix("<|S|>")
-                .or_else(|| buffer.strip_prefix("<|S|"))
-                .unwrap_or(&buffer)
-                .to_string();
-        } else if buffer.ends_with("|E|>") {
-            includes_start_or_end_span = true;
-        } else if buffer.starts_with("<|")
-            || buffer.starts_with("<|S")
-            || buffer.starts_with("<|S|")
-            || buffer.ends_with("|")
-            || buffer.ends_with("|E")
-            || buffer.ends_with("|E|")
-        {
-            return future::ready(None);
-        }
-
-        if first_line {
-            if buffer == "" || buffer == "`" || buffer == "``" {
-                return future::ready(None);
-            } else if buffer.starts_with("```") {
-                starts_with_markdown_codeblock = true;
-                if let Some(newline_ix) = buffer.find('\n') {
-                    buffer.replace_range(..newline_ix + 1, "");
-                    first_line = false;
-                } else {
-                    return future::ready(None);
-                }
-            }
-        }
-
-        let mut text = buffer.to_string();
-        if starts_with_markdown_codeblock {
-            text = text
-                .strip_suffix("\n```\n")
-                .or_else(|| text.strip_suffix("\n```"))
-                .or_else(|| text.strip_suffix("\n``"))
-                .or_else(|| text.strip_suffix("\n`"))
-                .or_else(|| text.strip_suffix('\n'))
-                .unwrap_or(&text)
-                .to_string();
-        }
-
-        if includes_start_or_end_span {
-            text = text
-                .strip_suffix("|E|>")
-                .or_else(|| text.strip_suffix("E|>"))
-                .or_else(|| text.strip_prefix("|>"))
-                .or_else(|| text.strip_prefix(">"))
-                .unwrap_or(&text)
-                .to_string();
-        };
-
-        if text.contains('\n') {
-            first_line = false;
-        }
-
-        let remainder = buffer.split_off(text.len());
-        let result = if buffer.is_empty() {
-            None
-        } else {
-            Some(Ok(buffer.clone()))
-        };
-
-        buffer = remainder;
-        future::ready(result)
-    })
-}
-
-#[cfg(test)]
-mod tests {
-    use std::sync::Arc;
-
-    use super::*;
-    use ai::test::FakeCompletionProvider;
-    use futures::stream::{self};
-    use gpui::{Context, TestAppContext};
-    use indoc::indoc;
-    use language::{language_settings, tree_sitter_rust, Buffer, Language, LanguageConfig, Point};
-    use rand::prelude::*;
-    use serde::Serialize;
-    use settings::SettingsStore;
-
-    #[derive(Serialize)]
-    pub struct DummyCompletionRequest {
-        pub name: String,
-    }
-
-    impl CompletionRequest for DummyCompletionRequest {
-        fn data(&self) -> serde_json::Result<String> {
-            serde_json::to_string(self)
-        }
-    }
-
-    #[gpui::test(iterations = 10)]
-    async fn test_transform_autoindent(cx: &mut TestAppContext, mut rng: StdRng) {
-        cx.set_global(cx.update(SettingsStore::test));
-        cx.update(language_settings::init);
-
-        let text = indoc! {"
-            fn main() {
-                let x = 0;
-                for _ in 0..10 {
-                    x += 1;
-                }
-            }
-        "};
-        let buffer =
-            cx.new_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
-        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
-        let range = buffer.read_with(cx, |buffer, cx| {
-            let snapshot = buffer.snapshot(cx);
-            snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(4, 5))
-        });
-        let provider = Arc::new(FakeCompletionProvider::new());
-        let codegen = cx.new_model(|cx| {
-            Codegen::new(
-                buffer.clone(),
-                CodegenKind::Transform { range },
-                provider.clone(),
-                cx,
-            )
-        });
-
-        let request = Box::new(DummyCompletionRequest {
-            name: "test".to_string(),
-        });
-        codegen.update(cx, |codegen, cx| codegen.start(request, cx));
-
-        let mut new_text = concat!(
-            "       let mut x = 0;\n",
-            "       while x < 10 {\n",
-            "           x += 1;\n",
-            "       }",
-        );
-        while !new_text.is_empty() {
-            let max_len = cmp::min(new_text.len(), 10);
-            let len = rng.gen_range(1..=max_len);
-            let (chunk, suffix) = new_text.split_at(len);
-            println!("CHUNK: {:?}", &chunk);
-            provider.send_completion(chunk);
-            new_text = suffix;
-            cx.background_executor.run_until_parked();
-        }
-        provider.finish_completion();
-        cx.background_executor.run_until_parked();
-
-        assert_eq!(
-            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
-            indoc! {"
-                fn main() {
-                    let mut x = 0;
-                    while x < 10 {
-                        x += 1;
-                    }
-                }
-            "}
-        );
-    }
-
-    #[gpui::test(iterations = 10)]
-    async fn test_autoindent_when_generating_past_indentation(
-        cx: &mut TestAppContext,
-        mut rng: StdRng,
-    ) {
-        cx.set_global(cx.update(SettingsStore::test));
-        cx.update(language_settings::init);
-
-        let text = indoc! {"
-            fn main() {
-                le
-            }
-        "};
-        let buffer =
-            cx.new_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
-        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
-        let position = buffer.read_with(cx, |buffer, cx| {
-            let snapshot = buffer.snapshot(cx);
-            snapshot.anchor_before(Point::new(1, 6))
-        });
-        let provider = Arc::new(FakeCompletionProvider::new());
-        let codegen = cx.new_model(|cx| {
-            Codegen::new(
-                buffer.clone(),
-                CodegenKind::Generate { position },
-                provider.clone(),
-                cx,
-            )
-        });
-
-        let request = Box::new(DummyCompletionRequest {
-            name: "test".to_string(),
-        });
-        codegen.update(cx, |codegen, cx| codegen.start(request, cx));
-
-        let mut new_text = concat!(
-            "t mut x = 0;\n",
-            "while x < 10 {\n",
-            "    x += 1;\n",
-            "}", //
-        );
-        while !new_text.is_empty() {
-            let max_len = cmp::min(new_text.len(), 10);
-            let len = rng.gen_range(1..=max_len);
-            let (chunk, suffix) = new_text.split_at(len);
-            provider.send_completion(chunk);
-            new_text = suffix;
-            cx.background_executor.run_until_parked();
-        }
-        provider.finish_completion();
-        cx.background_executor.run_until_parked();
-
-        assert_eq!(
-            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
-            indoc! {"
-                fn main() {
-                    let mut x = 0;
-                    while x < 10 {
-                        x += 1;
-                    }
-                }
-            "}
-        );
-    }
-
-    #[gpui::test(iterations = 10)]
-    async fn test_autoindent_when_generating_before_indentation(
-        cx: &mut TestAppContext,
-        mut rng: StdRng,
-    ) {
-        cx.set_global(cx.update(SettingsStore::test));
-        cx.update(language_settings::init);
-
-        let text = concat!(
-            "fn main() {\n",
-            "  \n",
-            "}\n" //
-        );
-        let buffer =
-            cx.new_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
-        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
-        let position = buffer.read_with(cx, |buffer, cx| {
-            let snapshot = buffer.snapshot(cx);
-            snapshot.anchor_before(Point::new(1, 2))
-        });
-        let provider = Arc::new(FakeCompletionProvider::new());
-        let codegen = cx.new_model(|cx| {
-            Codegen::new(
-                buffer.clone(),
-                CodegenKind::Generate { position },
-                provider.clone(),
-                cx,
-            )
-        });
-
-        let request = Box::new(DummyCompletionRequest {
-            name: "test".to_string(),
-        });
-        codegen.update(cx, |codegen, cx| codegen.start(request, cx));
-
-        let mut new_text = concat!(
-            "let mut x = 0;\n",
-            "while x < 10 {\n",
-            "    x += 1;\n",
-            "}", //
-        );
-        while !new_text.is_empty() {
-            let max_len = cmp::min(new_text.len(), 10);
-            let len = rng.gen_range(1..=max_len);
-            let (chunk, suffix) = new_text.split_at(len);
-            println!("{:?}", &chunk);
-            provider.send_completion(chunk);
-            new_text = suffix;
-            cx.background_executor.run_until_parked();
-        }
-        provider.finish_completion();
-        cx.background_executor.run_until_parked();
-
-        assert_eq!(
-            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
-            indoc! {"
-                fn main() {
-                    let mut x = 0;
-                    while x < 10 {
-                        x += 1;
-                    }
-                }
-            "}
-        );
-    }
-
-    #[gpui::test]
-    async fn test_strip_invalid_spans_from_codeblock() {
-        assert_eq!(
-            strip_invalid_spans_from_codeblock(chunks("Lorem ipsum dolor", 2))
-                .map(|chunk| chunk.unwrap())
-                .collect::<String>()
-                .await,
-            "Lorem ipsum dolor"
-        );
-        assert_eq!(
-            strip_invalid_spans_from_codeblock(chunks("```\nLorem ipsum dolor", 2))
-                .map(|chunk| chunk.unwrap())
-                .collect::<String>()
-                .await,
-            "Lorem ipsum dolor"
-        );
-        assert_eq!(
-            strip_invalid_spans_from_codeblock(chunks("```\nLorem ipsum dolor\n```", 2))
-                .map(|chunk| chunk.unwrap())
-                .collect::<String>()
-                .await,
-            "Lorem ipsum dolor"
-        );
-        assert_eq!(
-            strip_invalid_spans_from_codeblock(chunks("```\nLorem ipsum dolor\n```\n", 2))
-                .map(|chunk| chunk.unwrap())
-                .collect::<String>()
-                .await,
-            "Lorem ipsum dolor"
-        );
-        assert_eq!(
-            strip_invalid_spans_from_codeblock(chunks(
-                "```html\n```js\nLorem ipsum dolor\n```\n```",
-                2
-            ))
-            .map(|chunk| chunk.unwrap())
-            .collect::<String>()
-            .await,
-            "```js\nLorem ipsum dolor\n```"
-        );
-        assert_eq!(
-            strip_invalid_spans_from_codeblock(chunks("``\nLorem ipsum dolor\n```", 2))
-                .map(|chunk| chunk.unwrap())
-                .collect::<String>()
-                .await,
-            "``\nLorem ipsum dolor\n```"
-        );
-        assert_eq!(
-            strip_invalid_spans_from_codeblock(chunks("<|S|Lorem ipsum|E|>", 2))
-                .map(|chunk| chunk.unwrap())
-                .collect::<String>()
-                .await,
-            "Lorem ipsum"
-        );
-
-        assert_eq!(
-            strip_invalid_spans_from_codeblock(chunks("<|S|>Lorem ipsum", 2))
-                .map(|chunk| chunk.unwrap())
-                .collect::<String>()
-                .await,
-            "Lorem ipsum"
-        );
-
-        assert_eq!(
-            strip_invalid_spans_from_codeblock(chunks("```\n<|S|>Lorem ipsum\n```", 2))
-                .map(|chunk| chunk.unwrap())
-                .collect::<String>()
-                .await,
-            "Lorem ipsum"
-        );
-        assert_eq!(
-            strip_invalid_spans_from_codeblock(chunks("```\n<|S|Lorem ipsum|E|>\n```", 2))
-                .map(|chunk| chunk.unwrap())
-                .collect::<String>()
-                .await,
-            "Lorem ipsum"
-        );
-        fn chunks(text: &str, size: usize) -> impl Stream<Item = Result<String>> {
-            stream::iter(
-                text.chars()
-                    .collect::<Vec<_>>()
-                    .chunks(size)
-                    .map(|chunk| Ok(chunk.iter().collect::<String>()))
-                    .collect::<Vec<_>>(),
-            )
-        }
-    }
-
-    fn rust_lang() -> Language {
-        Language::new(
-            LanguageConfig {
-                name: "Rust".into(),
-                path_suffixes: vec!["rs".to_string()],
-                ..Default::default()
-            },
-            Some(tree_sitter_rust::language()),
-        )
-        .with_indents_query(
-            r#"
-            (call_expression) @indent
-            (field_expression) @indent
-            (_ "(" ")" @end) @indent
-            (_ "{" "}" @end) @indent
-            "#,
-        )
-        .unwrap()
-    }
-}

crates/assistant2/src/prompts.rs 🔗

@@ -1,389 +0,0 @@
-use ai::models::LanguageModel;
-use ai::prompts::base::{PromptArguments, PromptChain, PromptPriority, PromptTemplate};
-use ai::prompts::file_context::FileContext;
-use ai::prompts::generate::GenerateInlineContent;
-use ai::prompts::preamble::EngineerPreamble;
-use ai::prompts::repository_context::{PromptCodeSnippet, RepositoryContext};
-use ai::providers::open_ai::OpenAILanguageModel;
-use language::{BufferSnapshot, OffsetRangeExt, ToOffset};
-use std::cmp::{self, Reverse};
-use std::ops::Range;
-use std::sync::Arc;
-
-#[allow(dead_code)]
-fn summarize(buffer: &BufferSnapshot, selected_range: Range<impl ToOffset>) -> String {
-    #[derive(Debug)]
-    struct Match {
-        collapse: Range<usize>,
-        keep: Vec<Range<usize>>,
-    }
-
-    let selected_range = selected_range.to_offset(buffer);
-    let mut ts_matches = buffer.matches(0..buffer.len(), |grammar| {
-        Some(&grammar.embedding_config.as_ref()?.query)
-    });
-    let configs = ts_matches
-        .grammars()
-        .iter()
-        .map(|g| g.embedding_config.as_ref().unwrap())
-        .collect::<Vec<_>>();
-    let mut matches = Vec::new();
-    while let Some(mat) = ts_matches.peek() {
-        let config = &configs[mat.grammar_index];
-        if let Some(collapse) = mat.captures.iter().find_map(|cap| {
-            if Some(cap.index) == config.collapse_capture_ix {
-                Some(cap.node.byte_range())
-            } else {
-                None
-            }
-        }) {
-            let mut keep = Vec::new();
-            for capture in mat.captures.iter() {
-                if Some(capture.index) == config.keep_capture_ix {
-                    keep.push(capture.node.byte_range());
-                } else {
-                    continue;
-                }
-            }
-            ts_matches.advance();
-            matches.push(Match { collapse, keep });
-        } else {
-            ts_matches.advance();
-        }
-    }
-    matches.sort_unstable_by_key(|mat| (mat.collapse.start, Reverse(mat.collapse.end)));
-    let mut matches = matches.into_iter().peekable();
-
-    let mut summary = String::new();
-    let mut offset = 0;
-    let mut flushed_selection = false;
-    while let Some(mat) = matches.next() {
-        // Keep extending the collapsed range if the next match surrounds
-        // the current one.
-        while let Some(next_mat) = matches.peek() {
-            if mat.collapse.start <= next_mat.collapse.start
-                && mat.collapse.end >= next_mat.collapse.end
-            {
-                matches.next().unwrap();
-            } else {
-                break;
-            }
-        }
-
-        if offset > mat.collapse.start {
-            // Skip collapsed nodes that have already been summarized.
-            offset = cmp::max(offset, mat.collapse.end);
-            continue;
-        }
-
-        if offset <= selected_range.start && selected_range.start <= mat.collapse.end {
-            if !flushed_selection {
-                // The collapsed node ends after the selection starts, so we'll flush the selection first.
-                summary.extend(buffer.text_for_range(offset..selected_range.start));
-                summary.push_str("<|S|");
-                if selected_range.end == selected_range.start {
-                    summary.push_str(">");
-                } else {
-                    summary.extend(buffer.text_for_range(selected_range.clone()));
-                    summary.push_str("|E|>");
-                }
-                offset = selected_range.end;
-                flushed_selection = true;
-            }
-
-            // If the selection intersects the collapsed node, we won't collapse it.
-            if selected_range.end >= mat.collapse.start {
-                continue;
-            }
-        }
-
-        summary.extend(buffer.text_for_range(offset..mat.collapse.start));
-        for keep in mat.keep {
-            summary.extend(buffer.text_for_range(keep));
-        }
-        offset = mat.collapse.end;
-    }
-
-    // Flush selection if we haven't already done so.
-    if !flushed_selection && offset <= selected_range.start {
-        summary.extend(buffer.text_for_range(offset..selected_range.start));
-        summary.push_str("<|S|");
-        if selected_range.end == selected_range.start {
-            summary.push_str(">");
-        } else {
-            summary.extend(buffer.text_for_range(selected_range.clone()));
-            summary.push_str("|E|>");
-        }
-        offset = selected_range.end;
-    }
-
-    summary.extend(buffer.text_for_range(offset..buffer.len()));
-    summary
-}
-
-pub fn generate_content_prompt(
-    user_prompt: String,
-    language_name: Option<&str>,
-    buffer: BufferSnapshot,
-    range: Range<usize>,
-    search_results: Vec<PromptCodeSnippet>,
-    model: &str,
-    project_name: Option<String>,
-) -> anyhow::Result<String> {
-    // Using new Prompt Templates
-    let openai_model: Arc<dyn LanguageModel> = Arc::new(OpenAILanguageModel::load(model));
-    let lang_name = if let Some(language_name) = language_name {
-        Some(language_name.to_string())
-    } else {
-        None
-    };
-
-    let args = PromptArguments {
-        model: openai_model,
-        language_name: lang_name.clone(),
-        project_name,
-        snippets: search_results.clone(),
-        reserved_tokens: 1000,
-        buffer: Some(buffer),
-        selected_range: Some(range),
-        user_prompt: Some(user_prompt.clone()),
-    };
-
-    let templates: Vec<(PromptPriority, Box<dyn PromptTemplate>)> = vec![
-        (PromptPriority::Mandatory, Box::new(EngineerPreamble {})),
-        (
-            PromptPriority::Ordered { order: 1 },
-            Box::new(RepositoryContext {}),
-        ),
-        (
-            PromptPriority::Ordered { order: 0 },
-            Box::new(FileContext {}),
-        ),
-        (
-            PromptPriority::Mandatory,
-            Box::new(GenerateInlineContent {}),
-        ),
-    ];
-    let chain = PromptChain::new(args, templates);
-    let (prompt, _) = chain.generate(true)?;
-
-    anyhow::Ok(prompt)
-}
-
-#[cfg(test)]
-pub(crate) mod tests {
-
-    use super::*;
-    use std::sync::Arc;
-
-    use gpui::{AppContext, Context};
-    use indoc::indoc;
-    use language::{language_settings, tree_sitter_rust, Buffer, Language, LanguageConfig, Point};
-    use settings::SettingsStore;
-
-    pub(crate) fn rust_lang() -> Language {
-        Language::new(
-            LanguageConfig {
-                name: "Rust".into(),
-                path_suffixes: vec!["rs".to_string()],
-                ..Default::default()
-            },
-            Some(tree_sitter_rust::language()),
-        )
-        .with_embedding_query(
-            r#"
-            (
-                [(line_comment) (attribute_item)]* @context
-                .
-                [
-                    (struct_item
-                        name: (_) @name)
-
-                    (enum_item
-                        name: (_) @name)
-
-                    (impl_item
-                        trait: (_)? @name
-                        "for"? @name
-                        type: (_) @name)
-
-                    (trait_item
-                        name: (_) @name)
-
-                    (function_item
-                        name: (_) @name
-                        body: (block
-                            "{" @keep
-                            "}" @keep) @collapse)
-
-                    (macro_definition
-                        name: (_) @name)
-                    ] @item
-                )
-            "#,
-        )
-        .unwrap()
-    }
-
-    #[gpui::test]
-    fn test_outline_for_prompt(cx: &mut AppContext) {
-        let settings_store = SettingsStore::test(cx);
-        cx.set_global(settings_store);
-        language_settings::init(cx);
-        let text = indoc! {"
-            struct X {
-                a: usize,
-                b: usize,
-            }
-
-            impl X {
-
-                fn new() -> Self {
-                    let a = 1;
-                    let b = 2;
-                    Self { a, b }
-                }
-
-                pub fn a(&self, param: bool) -> usize {
-                    self.a
-                }
-
-                pub fn b(&self) -> usize {
-                    self.b
-                }
-            }
-        "};
-        let buffer =
-            cx.new_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
-        let snapshot = buffer.read(cx).snapshot();
-
-        assert_eq!(
-            summarize(&snapshot, Point::new(1, 4)..Point::new(1, 4)),
-            indoc! {"
-                struct X {
-                    <|S|>a: usize,
-                    b: usize,
-                }
-
-                impl X {
-
-                    fn new() -> Self {}
-
-                    pub fn a(&self, param: bool) -> usize {}
-
-                    pub fn b(&self) -> usize {}
-                }
-            "}
-        );
-
-        assert_eq!(
-            summarize(&snapshot, Point::new(8, 12)..Point::new(8, 14)),
-            indoc! {"
-                struct X {
-                    a: usize,
-                    b: usize,
-                }
-
-                impl X {
-
-                    fn new() -> Self {
-                        let <|S|a |E|>= 1;
-                        let b = 2;
-                        Self { a, b }
-                    }
-
-                    pub fn a(&self, param: bool) -> usize {}
-
-                    pub fn b(&self) -> usize {}
-                }
-            "}
-        );
-
-        assert_eq!(
-            summarize(&snapshot, Point::new(6, 0)..Point::new(6, 0)),
-            indoc! {"
-                struct X {
-                    a: usize,
-                    b: usize,
-                }
-
-                impl X {
-                <|S|>
-                    fn new() -> Self {}
-
-                    pub fn a(&self, param: bool) -> usize {}
-
-                    pub fn b(&self) -> usize {}
-                }
-            "}
-        );
-
-        assert_eq!(
-            summarize(&snapshot, Point::new(21, 0)..Point::new(21, 0)),
-            indoc! {"
-                struct X {
-                    a: usize,
-                    b: usize,
-                }
-
-                impl X {
-
-                    fn new() -> Self {}
-
-                    pub fn a(&self, param: bool) -> usize {}
-
-                    pub fn b(&self) -> usize {}
-                }
-                <|S|>"}
-        );
-
-        // Ensure nested functions get collapsed properly.
-        let text = indoc! {"
-            struct X {
-                a: usize,
-                b: usize,
-            }
-
-            impl X {
-
-                fn new() -> Self {
-                    let a = 1;
-                    let b = 2;
-                    Self { a, b }
-                }
-
-                pub fn a(&self, param: bool) -> usize {
-                    let a = 30;
-                    fn nested() -> usize {
-                        3
-                    }
-                    self.a + nested()
-                }
-
-                pub fn b(&self) -> usize {
-                    self.b
-                }
-            }
-        "};
-        buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
-        let snapshot = buffer.read(cx).snapshot();
-        assert_eq!(
-            summarize(&snapshot, Point::new(0, 0)..Point::new(0, 0)),
-            indoc! {"
-                <|S|>struct X {
-                    a: usize,
-                    b: usize,
-                }
-
-                impl X {
-
-                    fn new() -> Self {}
-
-                    pub fn a(&self, param: bool) -> usize {}
-
-                    pub fn b(&self) -> usize {}
-                }
-            "}
-        );
-    }
-}

crates/assistant2/src/streaming_diff.rs 🔗

@@ -1,293 +0,0 @@
-use collections::HashMap;
-use ordered_float::OrderedFloat;
-use std::{
-    cmp,
-    fmt::{self, Debug},
-    ops::Range,
-};
-
-struct Matrix {
-    cells: Vec<f64>,
-    rows: usize,
-    cols: usize,
-}
-
-impl Matrix {
-    fn new() -> Self {
-        Self {
-            cells: Vec::new(),
-            rows: 0,
-            cols: 0,
-        }
-    }
-
-    fn resize(&mut self, rows: usize, cols: usize) {
-        self.cells.resize(rows * cols, 0.);
-        self.rows = rows;
-        self.cols = cols;
-    }
-
-    fn get(&self, row: usize, col: usize) -> f64 {
-        if row >= self.rows {
-            panic!("row out of bounds")
-        }
-
-        if col >= self.cols {
-            panic!("col out of bounds")
-        }
-        self.cells[col * self.rows + row]
-    }
-
-    fn set(&mut self, row: usize, col: usize, value: f64) {
-        if row >= self.rows {
-            panic!("row out of bounds")
-        }
-
-        if col >= self.cols {
-            panic!("col out of bounds")
-        }
-
-        self.cells[col * self.rows + row] = value;
-    }
-}
-
-impl Debug for Matrix {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        writeln!(f)?;
-        for i in 0..self.rows {
-            for j in 0..self.cols {
-                write!(f, "{:5}", self.get(i, j))?;
-            }
-            writeln!(f)?;
-        }
-        Ok(())
-    }
-}
-
-#[derive(Debug)]
-pub enum Hunk {
-    Insert { text: String },
-    Remove { len: usize },
-    Keep { len: usize },
-}
-
-pub struct StreamingDiff {
-    old: Vec<char>,
-    new: Vec<char>,
-    scores: Matrix,
-    old_text_ix: usize,
-    new_text_ix: usize,
-    equal_runs: HashMap<(usize, usize), u32>,
-}
-
-impl StreamingDiff {
-    const INSERTION_SCORE: f64 = -1.;
-    const DELETION_SCORE: f64 = -20.;
-    const EQUALITY_BASE: f64 = 1.8;
-    const MAX_EQUALITY_EXPONENT: i32 = 16;
-
-    pub fn new(old: String) -> Self {
-        let old = old.chars().collect::<Vec<_>>();
-        let mut scores = Matrix::new();
-        scores.resize(old.len() + 1, 1);
-        for i in 0..=old.len() {
-            scores.set(i, 0, i as f64 * Self::DELETION_SCORE);
-        }
-        Self {
-            old,
-            new: Vec::new(),
-            scores,
-            old_text_ix: 0,
-            new_text_ix: 0,
-            equal_runs: Default::default(),
-        }
-    }
-
-    pub fn push_new(&mut self, text: &str) -> Vec<Hunk> {
-        self.new.extend(text.chars());
-        self.scores.resize(self.old.len() + 1, self.new.len() + 1);
-
-        for j in self.new_text_ix + 1..=self.new.len() {
-            self.scores.set(0, j, j as f64 * Self::INSERTION_SCORE);
-            for i in 1..=self.old.len() {
-                let insertion_score = self.scores.get(i, j - 1) + Self::INSERTION_SCORE;
-                let deletion_score = self.scores.get(i - 1, j) + Self::DELETION_SCORE;
-                let equality_score = if self.old[i - 1] == self.new[j - 1] {
-                    let mut equal_run = self.equal_runs.get(&(i - 1, j - 1)).copied().unwrap_or(0);
-                    equal_run += 1;
-                    self.equal_runs.insert((i, j), equal_run);
-
-                    let exponent = cmp::min(equal_run as i32 / 4, Self::MAX_EQUALITY_EXPONENT);
-                    self.scores.get(i - 1, j - 1) + Self::EQUALITY_BASE.powi(exponent)
-                } else {
-                    f64::NEG_INFINITY
-                };
-
-                let score = insertion_score.max(deletion_score).max(equality_score);
-                self.scores.set(i, j, score);
-            }
-        }
-
-        let mut max_score = f64::NEG_INFINITY;
-        let mut next_old_text_ix = self.old_text_ix;
-        let next_new_text_ix = self.new.len();
-        for i in self.old_text_ix..=self.old.len() {
-            let score = self.scores.get(i, next_new_text_ix);
-            if score > max_score {
-                max_score = score;
-                next_old_text_ix = i;
-            }
-        }
-
-        let hunks = self.backtrack(next_old_text_ix, next_new_text_ix);
-        self.old_text_ix = next_old_text_ix;
-        self.new_text_ix = next_new_text_ix;
-        hunks
-    }
-
-    fn backtrack(&self, old_text_ix: usize, new_text_ix: usize) -> Vec<Hunk> {
-        let mut pending_insert: Option<Range<usize>> = None;
-        let mut hunks = Vec::new();
-        let mut i = old_text_ix;
-        let mut j = new_text_ix;
-        while (i, j) != (self.old_text_ix, self.new_text_ix) {
-            let insertion_score = if j > self.new_text_ix {
-                Some((i, j - 1))
-            } else {
-                None
-            };
-            let deletion_score = if i > self.old_text_ix {
-                Some((i - 1, j))
-            } else {
-                None
-            };
-            let equality_score = if i > self.old_text_ix && j > self.new_text_ix {
-                if self.old[i - 1] == self.new[j - 1] {
-                    Some((i - 1, j - 1))
-                } else {
-                    None
-                }
-            } else {
-                None
-            };
-
-            let (prev_i, prev_j) = [insertion_score, deletion_score, equality_score]
-                .iter()
-                .max_by_key(|cell| cell.map(|(i, j)| OrderedFloat(self.scores.get(i, j))))
-                .unwrap()
-                .unwrap();
-
-            if prev_i == i && prev_j == j - 1 {
-                if let Some(pending_insert) = pending_insert.as_mut() {
-                    pending_insert.start = prev_j;
-                } else {
-                    pending_insert = Some(prev_j..j);
-                }
-            } else {
-                if let Some(range) = pending_insert.take() {
-                    hunks.push(Hunk::Insert {
-                        text: self.new[range].iter().collect(),
-                    });
-                }
-
-                let char_len = self.old[i - 1].len_utf8();
-                if prev_i == i - 1 && prev_j == j {
-                    if let Some(Hunk::Remove { len }) = hunks.last_mut() {
-                        *len += char_len;
-                    } else {
-                        hunks.push(Hunk::Remove { len: char_len })
-                    }
-                } else {
-                    if let Some(Hunk::Keep { len }) = hunks.last_mut() {
-                        *len += char_len;
-                    } else {
-                        hunks.push(Hunk::Keep { len: char_len })
-                    }
-                }
-            }
-
-            i = prev_i;
-            j = prev_j;
-        }
-
-        if let Some(range) = pending_insert.take() {
-            hunks.push(Hunk::Insert {
-                text: self.new[range].iter().collect(),
-            });
-        }
-
-        hunks.reverse();
-        hunks
-    }
-
-    pub fn finish(self) -> Vec<Hunk> {
-        self.backtrack(self.old.len(), self.new.len())
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use std::env;
-
-    use super::*;
-    use rand::prelude::*;
-
-    #[gpui::test(iterations = 100)]
-    fn test_random_diffs(mut rng: StdRng) {
-        let old_text_len = env::var("OLD_TEXT_LEN")
-            .map(|i| i.parse().expect("invalid `OLD_TEXT_LEN` variable"))
-            .unwrap_or(10);
-        let new_text_len = env::var("NEW_TEXT_LEN")
-            .map(|i| i.parse().expect("invalid `NEW_TEXT_LEN` variable"))
-            .unwrap_or(10);
-
-        let old = util::RandomCharIter::new(&mut rng)
-            .take(old_text_len)
-            .collect::<String>();
-        log::info!("old text: {:?}", old);
-
-        let mut diff = StreamingDiff::new(old.clone());
-        let mut hunks = Vec::new();
-        let mut new_len = 0;
-        let mut new = String::new();
-        while new_len < new_text_len {
-            let new_chunk_len = rng.gen_range(1..=new_text_len - new_len);
-            let new_chunk = util::RandomCharIter::new(&mut rng)
-                .take(new_len)
-                .collect::<String>();
-            log::info!("new chunk: {:?}", new_chunk);
-            new_len += new_chunk_len;
-            new.push_str(&new_chunk);
-            let new_hunks = diff.push_new(&new_chunk);
-            log::info!("hunks: {:?}", new_hunks);
-            hunks.extend(new_hunks);
-        }
-        let final_hunks = diff.finish();
-        log::info!("final hunks: {:?}", final_hunks);
-        hunks.extend(final_hunks);
-
-        log::info!("new text: {:?}", new);
-        let mut old_ix = 0;
-        let mut new_ix = 0;
-        let mut patched = String::new();
-        for hunk in hunks {
-            match hunk {
-                Hunk::Keep { len } => {
-                    assert_eq!(&old[old_ix..old_ix + len], &new[new_ix..new_ix + len]);
-                    patched.push_str(&old[old_ix..old_ix + len]);
-                    old_ix += len;
-                    new_ix += len;
-                }
-                Hunk::Remove { len } => {
-                    old_ix += len;
-                }
-                Hunk::Insert { text } => {
-                    assert_eq!(text, &new[new_ix..new_ix + text.len()]);
-                    patched.push_str(&text);
-                    new_ix += text.len();
-                }
-            }
-        }
-        assert_eq!(patched, new);
-    }
-}

crates/audio/Cargo.toml 🔗

@@ -13,11 +13,10 @@ gpui = { path = "../gpui" }
 collections = { path = "../collections" }
 util = { path = "../util" }
 
+
 rodio ={version = "0.17.1", default-features=false, features = ["wav"]}
 
 log.workspace = true
-
+futures.workspace = true
 anyhow.workspace = true
 parking_lot.workspace = true
-
-[dev-dependencies]

crates/audio/src/audio.rs 🔗

@@ -60,7 +60,7 @@ impl Audio {
             return;
         }
 
-        cx.update_global::<Self, _, _>(|this, cx| {
+        cx.update_global::<Self, _>(|this, cx| {
             let output_handle = this.ensure_output_exists()?;
             let source = SoundRegistry::global(cx).get(sound.file()).log_err()?;
             output_handle.play_raw(source).log_err()?;
@@ -73,7 +73,7 @@ impl Audio {
             return;
         }
 
-        cx.update_global::<Self, _, _>(|this, _| {
+        cx.update_global::<Self, _>(|this, _| {
             this._output_stream.take();
             this.output_handle.take();
         });

crates/audio2/Cargo.toml 🔗

@@ -1,24 +0,0 @@
-[package]
-name = "audio2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/audio2.rs"
-doctest = false
-
-[dependencies]
-gpui = { package = "gpui2", path = "../gpui2" }
-collections = { path = "../collections" }
-util = { path = "../util" }
-
-
-rodio ={version = "0.17.1", default-features=false, features = ["wav"]}
-
-log.workspace = true
-futures.workspace = true
-anyhow.workspace = true
-parking_lot.workspace = true
-
-[dev-dependencies]

crates/audio2/audio/Cargo.toml 🔗

@@ -1,23 +0,0 @@
-[package]
-name = "audio"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/audio.rs"
-doctest = false
-
-[dependencies]
-gpui = { path = "../gpui" }
-collections = { path = "../collections" }
-util = { path = "../util" }
-
-rodio ={version = "0.17.1", default-features=false, features = ["wav"]}
-
-log.workspace = true
-
-anyhow.workspace = true
-parking_lot.workspace = true
-
-[dev-dependencies]

crates/audio2/audio/src/assets.rs 🔗

@@ -1,44 +0,0 @@
-use std::{io::Cursor, sync::Arc};
-
-use anyhow::Result;
-use collections::HashMap;
-use gpui::{AppContext, AssetSource};
-use rodio::{
-    source::{Buffered, SamplesConverter},
-    Decoder, Source,
-};
-
-type Sound = Buffered<SamplesConverter<Decoder<Cursor<Vec<u8>>>, f32>>;
-
-pub struct SoundRegistry {
-    cache: Arc<parking_lot::Mutex<HashMap<String, Sound>>>,
-    assets: Box<dyn AssetSource>,
-}
-
-impl SoundRegistry {
-    pub fn new(source: impl AssetSource) -> Arc<Self> {
-        Arc::new(Self {
-            cache: Default::default(),
-            assets: Box::new(source),
-        })
-    }
-
-    pub fn global(cx: &AppContext) -> Arc<Self> {
-        cx.global::<Arc<Self>>().clone()
-    }
-
-    pub fn get(&self, name: &str) -> Result<impl Source<Item = f32>> {
-        if let Some(wav) = self.cache.lock().get(name) {
-            return Ok(wav.clone());
-        }
-
-        let path = format!("sounds/{}.wav", name);
-        let bytes = self.assets.load(&path)?.into_owned();
-        let cursor = Cursor::new(bytes);
-        let source = Decoder::new(cursor)?.convert_samples::<f32>().buffered();
-
-        self.cache.lock().insert(name.to_string(), source.clone());
-
-        Ok(source)
-    }
-}

crates/audio2/audio/src/audio.rs 🔗

@@ -1,81 +0,0 @@
-use assets::SoundRegistry;
-use gpui::{AppContext, AssetSource};
-use rodio::{OutputStream, OutputStreamHandle};
-use util::ResultExt;
-
-mod assets;
-
-pub fn init(source: impl AssetSource, cx: &mut AppContext) {
-    cx.set_global(SoundRegistry::new(source));
-    cx.set_global(Audio::new());
-}
-
-pub enum Sound {
-    Joined,
-    Leave,
-    Mute,
-    Unmute,
-    StartScreenshare,
-    StopScreenshare,
-}
-
-impl Sound {
-    fn file(&self) -> &'static str {
-        match self {
-            Self::Joined => "joined_call",
-            Self::Leave => "leave_call",
-            Self::Mute => "mute",
-            Self::Unmute => "unmute",
-            Self::StartScreenshare => "start_screenshare",
-            Self::StopScreenshare => "stop_screenshare",
-        }
-    }
-}
-
-pub struct Audio {
-    _output_stream: Option<OutputStream>,
-    output_handle: Option<OutputStreamHandle>,
-}
-
-impl Audio {
-    pub fn new() -> Self {
-        Self {
-            _output_stream: None,
-            output_handle: None,
-        }
-    }
-
-    fn ensure_output_exists(&mut self) -> Option<&OutputStreamHandle> {
-        if self.output_handle.is_none() {
-            let (_output_stream, output_handle) = OutputStream::try_default().log_err().unzip();
-            self.output_handle = output_handle;
-            self._output_stream = _output_stream;
-        }
-
-        self.output_handle.as_ref()
-    }
-
-    pub fn play_sound(sound: Sound, cx: &mut AppContext) {
-        if !cx.has_global::<Self>() {
-            return;
-        }
-
-        cx.update_global::<Self, _, _>(|this, cx| {
-            let output_handle = this.ensure_output_exists()?;
-            let source = SoundRegistry::global(cx).get(sound.file()).log_err()?;
-            output_handle.play_raw(source).log_err()?;
-            Some(())
-        });
-    }
-
-    pub fn end_call(cx: &mut AppContext) {
-        if !cx.has_global::<Self>() {
-            return;
-        }
-
-        cx.update_global::<Self, _, _>(|this, _| {
-            this._output_stream.take();
-            this.output_handle.take();
-        });
-    }
-}

crates/audio2/src/assets.rs 🔗

@@ -1,44 +0,0 @@
-use std::{io::Cursor, sync::Arc};
-
-use anyhow::Result;
-use collections::HashMap;
-use gpui::{AppContext, AssetSource};
-use rodio::{
-    source::{Buffered, SamplesConverter},
-    Decoder, Source,
-};
-
-type Sound = Buffered<SamplesConverter<Decoder<Cursor<Vec<u8>>>, f32>>;
-
-pub struct SoundRegistry {
-    cache: Arc<parking_lot::Mutex<HashMap<String, Sound>>>,
-    assets: Box<dyn AssetSource>,
-}
-
-impl SoundRegistry {
-    pub fn new(source: impl AssetSource) -> Arc<Self> {
-        Arc::new(Self {
-            cache: Default::default(),
-            assets: Box::new(source),
-        })
-    }
-
-    pub fn global(cx: &AppContext) -> Arc<Self> {
-        cx.global::<Arc<Self>>().clone()
-    }
-
-    pub fn get(&self, name: &str) -> Result<impl Source<Item = f32>> {
-        if let Some(wav) = self.cache.lock().get(name) {
-            return Ok(wav.clone());
-        }
-
-        let path = format!("sounds/{}.wav", name);
-        let bytes = self.assets.load(&path)?.into_owned();
-        let cursor = Cursor::new(bytes);
-        let source = Decoder::new(cursor)?.convert_samples::<f32>().buffered();
-
-        self.cache.lock().insert(name.to_string(), source.clone());
-
-        Ok(source)
-    }
-}

crates/audio2/src/audio2.rs 🔗

@@ -1,81 +0,0 @@
-use assets::SoundRegistry;
-use gpui::{AppContext, AssetSource};
-use rodio::{OutputStream, OutputStreamHandle};
-use util::ResultExt;
-
-mod assets;
-
-pub fn init(source: impl AssetSource, cx: &mut AppContext) {
-    cx.set_global(SoundRegistry::new(source));
-    cx.set_global(Audio::new());
-}
-
-pub enum Sound {
-    Joined,
-    Leave,
-    Mute,
-    Unmute,
-    StartScreenshare,
-    StopScreenshare,
-}
-
-impl Sound {
-    fn file(&self) -> &'static str {
-        match self {
-            Self::Joined => "joined_call",
-            Self::Leave => "leave_call",
-            Self::Mute => "mute",
-            Self::Unmute => "unmute",
-            Self::StartScreenshare => "start_screenshare",
-            Self::StopScreenshare => "stop_screenshare",
-        }
-    }
-}
-
-pub struct Audio {
-    _output_stream: Option<OutputStream>,
-    output_handle: Option<OutputStreamHandle>,
-}
-
-impl Audio {
-    pub fn new() -> Self {
-        Self {
-            _output_stream: None,
-            output_handle: None,
-        }
-    }
-
-    fn ensure_output_exists(&mut self) -> Option<&OutputStreamHandle> {
-        if self.output_handle.is_none() {
-            let (_output_stream, output_handle) = OutputStream::try_default().log_err().unzip();
-            self.output_handle = output_handle;
-            self._output_stream = _output_stream;
-        }
-
-        self.output_handle.as_ref()
-    }
-
-    pub fn play_sound(sound: Sound, cx: &mut AppContext) {
-        if !cx.has_global::<Self>() {
-            return;
-        }
-
-        cx.update_global::<Self, _>(|this, cx| {
-            let output_handle = this.ensure_output_exists()?;
-            let source = SoundRegistry::global(cx).get(sound.file()).log_err()?;
-            output_handle.play_raw(source).log_err()?;
-            Some(())
-        });
-    }
-
-    pub fn end_call(cx: &mut AppContext) {
-        if !cx.has_global::<Self>() {
-            return;
-        }
-
-        cx.update_global::<Self, _>(|this, _| {
-            this._output_stream.take();
-            this.output_handle.take();
-        });
-    }
-}

crates/auto_update/src/auto_update.rs 🔗

@@ -3,18 +3,23 @@ mod update_notification;
 use anyhow::{anyhow, Context, Result};
 use client::{Client, TelemetrySettings, ZED_APP_PATH, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN};
 use db::kvp::KEY_VALUE_STORE;
+use db::RELEASE_CHANNEL;
 use gpui::{
-    actions, platform::AppVersion, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle,
-    Task, WeakViewHandle,
+    actions, AppContext, AsyncAppContext, Context as _, Model, ModelContext, SemanticVersion, Task,
+    ViewContext, VisualContext, WindowContext,
 };
 use isahc::AsyncBody;
+
 use serde::Deserialize;
 use serde_derive::Serialize;
-use settings::{Setting, SettingsStore};
-use smol::{fs::File, io::AsyncReadExt, process::Command};
+use smol::io::AsyncReadExt;
+
+use settings::{Settings, SettingsStore};
+use smol::{fs::File, process::Command};
+
 use std::{ffi::OsString, sync::Arc, time::Duration};
 use update_notification::UpdateNotification;
-use util::channel::ReleaseChannel;
+use util::channel::{AppCommitSha, ReleaseChannel};
 use util::http::HttpClient;
 use workspace::Workspace;
 
@@ -42,9 +47,9 @@ pub enum AutoUpdateStatus {
 
 pub struct AutoUpdater {
     status: AutoUpdateStatus,
-    current_version: AppVersion,
+    current_version: SemanticVersion,
     http_client: Arc<dyn HttpClient>,
-    pending_poll: Option<Task<()>>,
+    pending_poll: Option<Task<Option<()>>>,
     server_url: String,
 }
 
@@ -54,13 +59,9 @@ struct JsonRelease {
     url: String,
 }
 
-impl Entity for AutoUpdater {
-    type Event = ();
-}
-
 struct AutoUpdateSetting(bool);
 
-impl Setting for AutoUpdateSetting {
+impl Settings for AutoUpdateSetting {
     const KEY: Option<&'static str> = Some("auto_update");
 
     type FileContent = Option<bool>;
@@ -68,7 +69,7 @@ impl Setting for AutoUpdateSetting {
     fn load(
         default_value: &Option<bool>,
         user_values: &[&Option<bool>],
-        _: &AppContext,
+        _: &mut AppContext,
     ) -> Result<Self> {
         Ok(Self(
             Self::json_merge(default_value, user_values)?.ok_or_else(Self::missing_default)?,
@@ -77,18 +78,31 @@ impl Setting for AutoUpdateSetting {
 }
 
 pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppContext) {
-    settings::register::<AutoUpdateSetting>(cx);
+    AutoUpdateSetting::register(cx);
+
+    cx.observe_new_views(|workspace: &mut Workspace, _cx| {
+        workspace.register_action(|_, action: &Check, cx| check(action, cx));
 
-    if let Some(version) = (*ZED_APP_VERSION).or_else(|| cx.platform().app_version().ok()) {
-        let auto_updater = cx.add_model(|cx| {
+        workspace.register_action(|_, action, cx| view_release_notes(action, cx));
+
+        // @nate - code to trigger update notification on launch
+        // todo!("remove this when Nate is done")
+        // workspace.show_notification(0, _cx, |cx| {
+        //     cx.build_view(|_| UpdateNotification::new(SemanticVersion::from_str("1.1.1").unwrap()))
+        // });
+    })
+    .detach();
+
+    if let Some(version) = ZED_APP_VERSION.or_else(|| cx.app_metadata().app_version) {
+        let auto_updater = cx.new_model(|cx| {
             let updater = AutoUpdater::new(version, http_client, server_url);
 
-            let mut update_subscription = settings::get::<AutoUpdateSetting>(cx)
+            let mut update_subscription = AutoUpdateSetting::get_global(cx)
                 .0
                 .then(|| updater.start_polling(cx));
 
-            cx.observe_global::<SettingsStore, _>(move |updater, cx| {
-                if settings::get::<AutoUpdateSetting>(cx).0 {
+            cx.observe_global::<SettingsStore>(move |updater, cx| {
+                if AutoUpdateSetting::get_global(cx).0 {
                     if update_subscription.is_none() {
                         update_subscription = Some(updater.start_polling(cx))
                     }
@@ -101,19 +115,22 @@ pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppCo
             updater
         });
         cx.set_global(Some(auto_updater));
-        cx.add_global_action(check);
-        cx.add_global_action(view_release_notes);
-        cx.add_action(UpdateNotification::dismiss);
     }
 }
 
-pub fn check(_: &Check, cx: &mut AppContext) {
+pub fn check(_: &Check, cx: &mut WindowContext) {
     if let Some(updater) = AutoUpdater::get(cx) {
         updater.update(cx, |updater, cx| updater.poll(cx));
+    } else {
+        drop(cx.prompt(
+            gpui::PromptLevel::Info,
+            "Auto-updates disabled for non-bundled app.",
+            &["Ok"],
+        ));
     }
 }
 
-fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) {
+pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) {
     if let Some(auto_updater) = AutoUpdater::get(cx) {
         let auto_updater = auto_updater.read(cx);
         let server_url = &auto_updater.server_url;
@@ -122,31 +139,28 @@ fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) {
             match cx.global::<ReleaseChannel>() {
                 ReleaseChannel::Dev => {}
                 ReleaseChannel::Nightly => {}
-                ReleaseChannel::Preview => cx
-                    .platform()
-                    .open_url(&format!("{server_url}/releases/preview/{current_version}")),
-                ReleaseChannel::Stable => cx
-                    .platform()
-                    .open_url(&format!("{server_url}/releases/stable/{current_version}")),
+                ReleaseChannel::Preview => {
+                    cx.open_url(&format!("{server_url}/releases/preview/{current_version}"))
+                }
+                ReleaseChannel::Stable => {
+                    cx.open_url(&format!("{server_url}/releases/stable/{current_version}"))
+                }
             }
         }
     }
 }
 
-pub fn notify_of_any_new_update(
-    workspace: WeakViewHandle<Workspace>,
-    cx: &mut AppContext,
-) -> Option<()> {
+pub fn notify_of_any_new_update(cx: &mut ViewContext<Workspace>) -> Option<()> {
     let updater = AutoUpdater::get(cx)?;
     let version = updater.read(cx).current_version;
     let should_show_notification = updater.read(cx).should_show_update_notification(cx);
 
-    cx.spawn(|mut cx| async move {
+    cx.spawn(|workspace, mut cx| async move {
         let should_show_notification = should_show_notification.await?;
         if should_show_notification {
             workspace.update(&mut cx, |workspace, cx| {
                 workspace.show_notification(0, cx, |cx| {
-                    cx.add_view(|_| UpdateNotification::new(version))
+                    cx.new_view(|_| UpdateNotification::new(version))
                 });
                 updater
                     .read(cx)
@@ -162,12 +176,12 @@ pub fn notify_of_any_new_update(
 }
 
 impl AutoUpdater {
-    pub fn get(cx: &mut AppContext) -> Option<ModelHandle<Self>> {
-        cx.default_global::<Option<ModelHandle<Self>>>().clone()
+    pub fn get(cx: &mut AppContext) -> Option<Model<Self>> {
+        cx.default_global::<Option<Model<Self>>>().clone()
     }
 
     fn new(
-        current_version: AppVersion,
+        current_version: SemanticVersion,
         http_client: Arc<dyn HttpClient>,
         server_url: String,
     ) -> Self {
@@ -180,11 +194,11 @@ impl AutoUpdater {
         }
     }
 
-    pub fn start_polling(&self, cx: &mut ModelContext<Self>) -> Task<()> {
+    pub fn start_polling(&self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
         cx.spawn(|this, mut cx| async move {
             loop {
-                this.update(&mut cx, |this, cx| this.poll(cx));
-                cx.background().timer(POLL_INTERVAL).await;
+                this.update(&mut cx, |this, cx| this.poll(cx))?;
+                cx.background_executor().timer(POLL_INTERVAL).await;
             }
         })
     }
@@ -198,7 +212,7 @@ impl AutoUpdater {
         cx.notify();
 
         self.pending_poll = Some(cx.spawn(|this, mut cx| async move {
-            let result = Self::update(this.clone(), cx.clone()).await;
+            let result = Self::update(this.upgrade()?, cx.clone()).await;
             this.update(&mut cx, |this, cx| {
                 this.pending_poll = None;
                 if let Err(error) = result {
@@ -206,7 +220,8 @@ impl AutoUpdater {
                     this.status = AutoUpdateStatus::Errored;
                     cx.notify();
                 }
-            });
+            })
+            .ok()
         }));
     }
 
@@ -219,26 +234,26 @@ impl AutoUpdater {
         cx.notify();
     }
 
-    async fn update(this: ModelHandle<Self>, mut cx: AsyncAppContext) -> Result<()> {
+    async fn update(this: Model<Self>, mut cx: AsyncAppContext) -> Result<()> {
         let (client, server_url, current_version) = this.read_with(&cx, |this, _| {
             (
                 this.http_client.clone(),
                 this.server_url.clone(),
                 this.current_version,
             )
-        });
+        })?;
 
         let mut url_string = format!(
             "{server_url}/api/releases/latest?token={ZED_SECRET_CLIENT_TOKEN}&asset=Zed.dmg"
         );
-        cx.read(|cx| {
+        cx.update(|cx| {
             if cx.has_global::<ReleaseChannel>() {
                 if let Some(param) = cx.global::<ReleaseChannel>().release_query_param() {
                     url_string += "&";
                     url_string += param;
                 }
             }
-        });
+        })?;
 
         let mut response = client.get(&url_string, Default::default(), true).await?;
 
@@ -251,26 +266,32 @@ impl AutoUpdater {
         let release: JsonRelease =
             serde_json::from_slice(body.as_slice()).context("error deserializing release")?;
 
-        let latest_version = release.version.parse::<AppVersion>()?;
-        if latest_version <= current_version {
+        let should_download = match *RELEASE_CHANNEL {
+            ReleaseChannel::Nightly => cx
+                .try_read_global::<AppCommitSha, _>(|sha, _| release.version != sha.0)
+                .unwrap_or(true),
+            _ => release.version.parse::<SemanticVersion>()? <= current_version,
+        };
+
+        if !should_download {
             this.update(&mut cx, |this, cx| {
                 this.status = AutoUpdateStatus::Idle;
                 cx.notify();
-            });
+            })?;
             return Ok(());
         }
 
         this.update(&mut cx, |this, cx| {
             this.status = AutoUpdateStatus::Downloading;
             cx.notify();
-        });
+        })?;
 
         let temp_dir = tempdir::TempDir::new("zed-auto-update")?;
         let dmg_path = temp_dir.path().join("Zed.dmg");
         let mount_path = temp_dir.path().join("Zed");
         let running_app_path = ZED_APP_PATH
             .clone()
-            .map_or_else(|| cx.platform().app_path(), Ok)?;
+            .map_or_else(|| cx.update(|cx| cx.app_path())?, Ok)?;
         let running_app_filename = running_app_path
             .file_name()
             .ok_or_else(|| anyhow!("invalid running app path"))?;
@@ -279,15 +300,15 @@ impl AutoUpdater {
 
         let mut dmg_file = File::create(&dmg_path).await?;
 
-        let (installation_id, release_channel, telemetry) = cx.read(|cx| {
+        let (installation_id, release_channel, telemetry) = cx.update(|cx| {
             let installation_id = cx.global::<Arc<Client>>().telemetry().installation_id();
             let release_channel = cx
                 .has_global::<ReleaseChannel>()
                 .then(|| cx.global::<ReleaseChannel>().display_name());
-            let telemetry = settings::get::<TelemetrySettings>(cx).metrics;
+            let telemetry = TelemetrySettings::get_global(cx).metrics;
 
             (installation_id, release_channel, telemetry)
-        });
+        })?;
 
         let request_body = AsyncBody::from(serde_json::to_string(&UpdateRequestBody {
             installation_id,
@@ -302,7 +323,7 @@ impl AutoUpdater {
         this.update(&mut cx, |this, cx| {
             this.status = AutoUpdateStatus::Installing;
             cx.notify();
-        });
+        })?;
 
         let output = Command::new("hdiutil")
             .args(&["attach", "-nobrowse"])
@@ -348,7 +369,7 @@ impl AutoUpdater {
                 .detach_and_log_err(cx);
             this.status = AutoUpdateStatus::Updated;
             cx.notify();
-        });
+        })?;
         Ok(())
     }
 
@@ -357,7 +378,7 @@ impl AutoUpdater {
         should_show: bool,
         cx: &AppContext,
     ) -> Task<Result<()>> {
-        cx.background().spawn(async move {
+        cx.background_executor().spawn(async move {
             if should_show {
                 KEY_VALUE_STORE
                     .write_kvp(
@@ -375,7 +396,7 @@ impl AutoUpdater {
     }
 
     fn should_show_update_notification(&self, cx: &AppContext) -> Task<Result<bool>> {
-        cx.background().spawn(async move {
+        cx.background_executor().spawn(async move {
             Ok(KEY_VALUE_STORE
                 .read_kvp(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY)?
                 .is_some())

crates/auto_update/src/update_notification.rs 🔗

@@ -1,106 +1,56 @@
-use crate::ViewReleaseNotes;
 use gpui::{
-    elements::{Flex, MouseEventHandler, Padding, ParentElement, Svg, Text},
-    platform::{AppVersion, CursorStyle, MouseButton},
-    Element, Entity, View, ViewContext,
+    div, DismissEvent, EventEmitter, InteractiveElement, IntoElement, ParentElement, Render,
+    SemanticVersion, StatefulInteractiveElement, Styled, ViewContext,
 };
 use menu::Cancel;
 use util::channel::ReleaseChannel;
-use workspace::notifications::Notification;
+use workspace::ui::{h_stack, v_stack, Icon, IconElement, Label, StyledExt};
 
 pub struct UpdateNotification {
-    version: AppVersion,
+    version: SemanticVersion,
 }
 
-pub enum Event {
-    Dismiss,
-}
-
-impl Entity for UpdateNotification {
-    type Event = Event;
-}
-
-impl View for UpdateNotification {
-    fn ui_name() -> &'static str {
-        "UpdateNotification"
-    }
-
-    fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> gpui::AnyElement<Self> {
-        let theme = theme::current(cx).clone();
-        let theme = &theme.update_notification;
+impl EventEmitter<DismissEvent> for UpdateNotification {}
 
+impl Render for UpdateNotification {
+    fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
         let app_name = cx.global::<ReleaseChannel>().display_name();
 
-        MouseEventHandler::new::<ViewReleaseNotes, _>(0, cx, |state, cx| {
-            Flex::column()
-                .with_child(
-                    Flex::row()
-                        .with_child(
-                            Text::new(
-                                format!("Updated to {app_name} {}", self.version),
-                                theme.message.text.clone(),
-                            )
-                            .contained()
-                            .with_style(theme.message.container)
-                            .aligned()
-                            .top()
-                            .left()
-                            .flex(1., true),
-                        )
-                        .with_child(
-                            MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| {
-                                let style = theme.dismiss_button.style_for(state);
-                                Svg::new("icons/x.svg")
-                                    .with_color(style.color)
-                                    .constrained()
-                                    .with_width(style.icon_width)
-                                    .aligned()
-                                    .contained()
-                                    .with_style(style.container)
-                                    .constrained()
-                                    .with_width(style.button_width)
-                                    .with_height(style.button_width)
-                            })
-                            .with_padding(Padding::uniform(5.))
-                            .on_click(MouseButton::Left, move |_, this, cx| {
-                                this.dismiss(&Default::default(), cx)
-                            })
-                            .aligned()
-                            .constrained()
-                            .with_height(cx.font_cache().line_height(theme.message.text.font_size))
-                            .aligned()
-                            .top()
-                            .flex_float(),
-                        ),
-                )
-                .with_child({
-                    let style = theme.action_message.style_for(state);
-                    Text::new("View the release notes", style.text.clone())
-                        .contained()
-                        .with_style(style.container)
-                })
-                .contained()
-        })
-        .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, |_, _, cx| {
-            crate::view_release_notes(&Default::default(), cx)
-        })
-        .into_any_named("update notification")
-    }
-}
-
-impl Notification for UpdateNotification {
-    fn should_dismiss_notification_on_event(&self, event: &<Self as Entity>::Event) -> bool {
-        matches!(event, Event::Dismiss)
+        v_stack()
+            .on_action(cx.listener(UpdateNotification::dismiss))
+            .elevation_3(cx)
+            .p_4()
+            .child(
+                h_stack()
+                    .justify_between()
+                    .child(Label::new(format!(
+                        "Updated to {app_name} {}",
+                        self.version
+                    )))
+                    .child(
+                        div()
+                            .id("cancel")
+                            .child(IconElement::new(Icon::Close))
+                            .cursor_pointer()
+                            .on_click(cx.listener(|this, _, cx| this.dismiss(&menu::Cancel, cx))),
+                    ),
+            )
+            .child(
+                div()
+                    .id("notes")
+                    .child(Label::new("View the release notes"))
+                    .cursor_pointer()
+                    .on_click(|_, cx| crate::view_release_notes(&Default::default(), cx)),
+            )
     }
 }
 
 impl UpdateNotification {
-    pub fn new(version: AppVersion) -> Self {
+    pub fn new(version: SemanticVersion) -> Self {
         Self { version }
     }
 
     pub fn dismiss(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
-        cx.emit(Event::Dismiss);
+        cx.emit(DismissEvent);
     }
 }

crates/auto_update2/Cargo.toml 🔗

@@ -1,29 +0,0 @@
-[package]
-name = "auto_update2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/auto_update.rs"
-doctest = false
-
-[dependencies]
-db = { package = "db2", path = "../db2" }
-client = { package = "client2", path = "../client2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-menu = { package = "menu2", path = "../menu2" }
-project = { package = "project2", path = "../project2" }
-settings = { package = "settings2", path = "../settings2" }
-theme = { package = "theme2", path = "../theme2" }
-workspace = { package = "workspace2", path = "../workspace2" }
-util = { path = "../util" }
-anyhow.workspace = true
-isahc.workspace = true
-lazy_static.workspace = true
-log.workspace = true
-serde.workspace = true
-serde_derive.workspace = true
-serde_json.workspace = true
-smol.workspace = true
-tempdir.workspace = true

crates/auto_update2/src/auto_update.rs 🔗

@@ -1,405 +0,0 @@
-mod update_notification;
-
-use anyhow::{anyhow, Context, Result};
-use client::{Client, TelemetrySettings, ZED_APP_PATH, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN};
-use db::kvp::KEY_VALUE_STORE;
-use db::RELEASE_CHANNEL;
-use gpui::{
-    actions, AppContext, AsyncAppContext, Context as _, Model, ModelContext, SemanticVersion, Task,
-    ViewContext, VisualContext, WindowContext,
-};
-use isahc::AsyncBody;
-
-use serde::Deserialize;
-use serde_derive::Serialize;
-use smol::io::AsyncReadExt;
-
-use settings::{Settings, SettingsStore};
-use smol::{fs::File, process::Command};
-
-use std::{ffi::OsString, sync::Arc, time::Duration};
-use update_notification::UpdateNotification;
-use util::channel::{AppCommitSha, ReleaseChannel};
-use util::http::HttpClient;
-use workspace::Workspace;
-
-const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification";
-const POLL_INTERVAL: Duration = Duration::from_secs(60 * 60);
-
-actions!(auto_update, [Check, DismissErrorMessage, ViewReleaseNotes]);
-
-#[derive(Serialize)]
-struct UpdateRequestBody {
-    installation_id: Option<Arc<str>>,
-    release_channel: Option<&'static str>,
-    telemetry: bool,
-}
-
-#[derive(Clone, Copy, PartialEq, Eq)]
-pub enum AutoUpdateStatus {
-    Idle,
-    Checking,
-    Downloading,
-    Installing,
-    Updated,
-    Errored,
-}
-
-pub struct AutoUpdater {
-    status: AutoUpdateStatus,
-    current_version: SemanticVersion,
-    http_client: Arc<dyn HttpClient>,
-    pending_poll: Option<Task<Option<()>>>,
-    server_url: String,
-}
-
-#[derive(Deserialize)]
-struct JsonRelease {
-    version: String,
-    url: String,
-}
-
-struct AutoUpdateSetting(bool);
-
-impl Settings for AutoUpdateSetting {
-    const KEY: Option<&'static str> = Some("auto_update");
-
-    type FileContent = Option<bool>;
-
-    fn load(
-        default_value: &Option<bool>,
-        user_values: &[&Option<bool>],
-        _: &mut AppContext,
-    ) -> Result<Self> {
-        Ok(Self(
-            Self::json_merge(default_value, user_values)?.ok_or_else(Self::missing_default)?,
-        ))
-    }
-}
-
-pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppContext) {
-    AutoUpdateSetting::register(cx);
-
-    cx.observe_new_views(|workspace: &mut Workspace, _cx| {
-        workspace.register_action(|_, action: &Check, cx| check(action, cx));
-
-        workspace.register_action(|_, action, cx| view_release_notes(action, cx));
-
-        // @nate - code to trigger update notification on launch
-        // todo!("remove this when Nate is done")
-        // workspace.show_notification(0, _cx, |cx| {
-        //     cx.build_view(|_| UpdateNotification::new(SemanticVersion::from_str("1.1.1").unwrap()))
-        // });
-    })
-    .detach();
-
-    if let Some(version) = ZED_APP_VERSION.or_else(|| cx.app_metadata().app_version) {
-        let auto_updater = cx.new_model(|cx| {
-            let updater = AutoUpdater::new(version, http_client, server_url);
-
-            let mut update_subscription = AutoUpdateSetting::get_global(cx)
-                .0
-                .then(|| updater.start_polling(cx));
-
-            cx.observe_global::<SettingsStore>(move |updater, cx| {
-                if AutoUpdateSetting::get_global(cx).0 {
-                    if update_subscription.is_none() {
-                        update_subscription = Some(updater.start_polling(cx))
-                    }
-                } else {
-                    update_subscription.take();
-                }
-            })
-            .detach();
-
-            updater
-        });
-        cx.set_global(Some(auto_updater));
-    }
-}
-
-pub fn check(_: &Check, cx: &mut WindowContext) {
-    if let Some(updater) = AutoUpdater::get(cx) {
-        updater.update(cx, |updater, cx| updater.poll(cx));
-    } else {
-        drop(cx.prompt(
-            gpui::PromptLevel::Info,
-            "Auto-updates disabled for non-bundled app.",
-            &["Ok"],
-        ));
-    }
-}
-
-pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) {
-    if let Some(auto_updater) = AutoUpdater::get(cx) {
-        let auto_updater = auto_updater.read(cx);
-        let server_url = &auto_updater.server_url;
-        let current_version = auto_updater.current_version;
-        if cx.has_global::<ReleaseChannel>() {
-            match cx.global::<ReleaseChannel>() {
-                ReleaseChannel::Dev => {}
-                ReleaseChannel::Nightly => {}
-                ReleaseChannel::Preview => {
-                    cx.open_url(&format!("{server_url}/releases/preview/{current_version}"))
-                }
-                ReleaseChannel::Stable => {
-                    cx.open_url(&format!("{server_url}/releases/stable/{current_version}"))
-                }
-            }
-        }
-    }
-}
-
-pub fn notify_of_any_new_update(cx: &mut ViewContext<Workspace>) -> Option<()> {
-    let updater = AutoUpdater::get(cx)?;
-    let version = updater.read(cx).current_version;
-    let should_show_notification = updater.read(cx).should_show_update_notification(cx);
-
-    cx.spawn(|workspace, mut cx| async move {
-        let should_show_notification = should_show_notification.await?;
-        if should_show_notification {
-            workspace.update(&mut cx, |workspace, cx| {
-                workspace.show_notification(0, cx, |cx| {
-                    cx.new_view(|_| UpdateNotification::new(version))
-                });
-                updater
-                    .read(cx)
-                    .set_should_show_update_notification(false, cx)
-                    .detach_and_log_err(cx);
-            })?;
-        }
-        anyhow::Ok(())
-    })
-    .detach();
-
-    None
-}
-
-impl AutoUpdater {
-    pub fn get(cx: &mut AppContext) -> Option<Model<Self>> {
-        cx.default_global::<Option<Model<Self>>>().clone()
-    }
-
-    fn new(
-        current_version: SemanticVersion,
-        http_client: Arc<dyn HttpClient>,
-        server_url: String,
-    ) -> Self {
-        Self {
-            status: AutoUpdateStatus::Idle,
-            current_version,
-            http_client,
-            server_url,
-            pending_poll: None,
-        }
-    }
-
-    pub fn start_polling(&self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
-        cx.spawn(|this, mut cx| async move {
-            loop {
-                this.update(&mut cx, |this, cx| this.poll(cx))?;
-                cx.background_executor().timer(POLL_INTERVAL).await;
-            }
-        })
-    }
-
-    pub fn poll(&mut self, cx: &mut ModelContext<Self>) {
-        if self.pending_poll.is_some() || self.status == AutoUpdateStatus::Updated {
-            return;
-        }
-
-        self.status = AutoUpdateStatus::Checking;
-        cx.notify();
-
-        self.pending_poll = Some(cx.spawn(|this, mut cx| async move {
-            let result = Self::update(this.upgrade()?, cx.clone()).await;
-            this.update(&mut cx, |this, cx| {
-                this.pending_poll = None;
-                if let Err(error) = result {
-                    log::error!("auto-update failed: error:{:?}", error);
-                    this.status = AutoUpdateStatus::Errored;
-                    cx.notify();
-                }
-            })
-            .ok()
-        }));
-    }
-
-    pub fn status(&self) -> AutoUpdateStatus {
-        self.status
-    }
-
-    pub fn dismiss_error(&mut self, cx: &mut ModelContext<Self>) {
-        self.status = AutoUpdateStatus::Idle;
-        cx.notify();
-    }
-
-    async fn update(this: Model<Self>, mut cx: AsyncAppContext) -> Result<()> {
-        let (client, server_url, current_version) = this.read_with(&cx, |this, _| {
-            (
-                this.http_client.clone(),
-                this.server_url.clone(),
-                this.current_version,
-            )
-        })?;
-
-        let mut url_string = format!(
-            "{server_url}/api/releases/latest?token={ZED_SECRET_CLIENT_TOKEN}&asset=Zed.dmg"
-        );
-        cx.update(|cx| {
-            if cx.has_global::<ReleaseChannel>() {
-                if let Some(param) = cx.global::<ReleaseChannel>().release_query_param() {
-                    url_string += "&";
-                    url_string += param;
-                }
-            }
-        })?;
-
-        let mut response = client.get(&url_string, Default::default(), true).await?;
-
-        let mut body = Vec::new();
-        response
-            .body_mut()
-            .read_to_end(&mut body)
-            .await
-            .context("error reading release")?;
-        let release: JsonRelease =
-            serde_json::from_slice(body.as_slice()).context("error deserializing release")?;
-
-        let should_download = match *RELEASE_CHANNEL {
-            ReleaseChannel::Nightly => cx
-                .try_read_global::<AppCommitSha, _>(|sha, _| release.version != sha.0)
-                .unwrap_or(true),
-            _ => release.version.parse::<SemanticVersion>()? <= current_version,
-        };
-
-        if !should_download {
-            this.update(&mut cx, |this, cx| {
-                this.status = AutoUpdateStatus::Idle;
-                cx.notify();
-            })?;
-            return Ok(());
-        }
-
-        this.update(&mut cx, |this, cx| {
-            this.status = AutoUpdateStatus::Downloading;
-            cx.notify();
-        })?;
-
-        let temp_dir = tempdir::TempDir::new("zed-auto-update")?;
-        let dmg_path = temp_dir.path().join("Zed.dmg");
-        let mount_path = temp_dir.path().join("Zed");
-        let running_app_path = ZED_APP_PATH
-            .clone()
-            .map_or_else(|| cx.update(|cx| cx.app_path())?, Ok)?;
-        let running_app_filename = running_app_path
-            .file_name()
-            .ok_or_else(|| anyhow!("invalid running app path"))?;
-        let mut mounted_app_path: OsString = mount_path.join(running_app_filename).into();
-        mounted_app_path.push("/");
-
-        let mut dmg_file = File::create(&dmg_path).await?;
-
-        let (installation_id, release_channel, telemetry) = cx.update(|cx| {
-            let installation_id = cx.global::<Arc<Client>>().telemetry().installation_id();
-            let release_channel = cx
-                .has_global::<ReleaseChannel>()
-                .then(|| cx.global::<ReleaseChannel>().display_name());
-            let telemetry = TelemetrySettings::get_global(cx).metrics;
-
-            (installation_id, release_channel, telemetry)
-        })?;
-
-        let request_body = AsyncBody::from(serde_json::to_string(&UpdateRequestBody {
-            installation_id,
-            release_channel,
-            telemetry,
-        })?);
-
-        let mut response = client.get(&release.url, request_body, true).await?;
-        smol::io::copy(response.body_mut(), &mut dmg_file).await?;
-        log::info!("downloaded update. path:{:?}", dmg_path);
-
-        this.update(&mut cx, |this, cx| {
-            this.status = AutoUpdateStatus::Installing;
-            cx.notify();
-        })?;
-
-        let output = Command::new("hdiutil")
-            .args(&["attach", "-nobrowse"])
-            .arg(&dmg_path)
-            .arg("-mountroot")
-            .arg(&temp_dir.path())
-            .output()
-            .await?;
-        if !output.status.success() {
-            Err(anyhow!(
-                "failed to mount: {:?}",
-                String::from_utf8_lossy(&output.stderr)
-            ))?;
-        }
-
-        let output = Command::new("rsync")
-            .args(&["-av", "--delete"])
-            .arg(&mounted_app_path)
-            .arg(&running_app_path)
-            .output()
-            .await?;
-        if !output.status.success() {
-            Err(anyhow!(
-                "failed to copy app: {:?}",
-                String::from_utf8_lossy(&output.stderr)
-            ))?;
-        }
-
-        let output = Command::new("hdiutil")
-            .args(&["detach"])
-            .arg(&mount_path)
-            .output()
-            .await?;
-        if !output.status.success() {
-            Err(anyhow!(
-                "failed to unmount: {:?}",
-                String::from_utf8_lossy(&output.stderr)
-            ))?;
-        }
-
-        this.update(&mut cx, |this, cx| {
-            this.set_should_show_update_notification(true, cx)
-                .detach_and_log_err(cx);
-            this.status = AutoUpdateStatus::Updated;
-            cx.notify();
-        })?;
-        Ok(())
-    }
-
-    fn set_should_show_update_notification(
-        &self,
-        should_show: bool,
-        cx: &AppContext,
-    ) -> Task<Result<()>> {
-        cx.background_executor().spawn(async move {
-            if should_show {
-                KEY_VALUE_STORE
-                    .write_kvp(
-                        SHOULD_SHOW_UPDATE_NOTIFICATION_KEY.to_string(),
-                        "".to_string(),
-                    )
-                    .await?;
-            } else {
-                KEY_VALUE_STORE
-                    .delete_kvp(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY.to_string())
-                    .await?;
-            }
-            Ok(())
-        })
-    }
-
-    fn should_show_update_notification(&self, cx: &AppContext) -> Task<Result<bool>> {
-        cx.background_executor().spawn(async move {
-            Ok(KEY_VALUE_STORE
-                .read_kvp(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY)?
-                .is_some())
-        })
-    }
-}

crates/auto_update2/src/update_notification.rs 🔗

@@ -1,56 +0,0 @@
-use gpui::{
-    div, DismissEvent, EventEmitter, InteractiveElement, IntoElement, ParentElement, Render,
-    SemanticVersion, StatefulInteractiveElement, Styled, ViewContext,
-};
-use menu::Cancel;
-use util::channel::ReleaseChannel;
-use workspace::ui::{h_stack, v_stack, Icon, IconElement, Label, StyledExt};
-
-pub struct UpdateNotification {
-    version: SemanticVersion,
-}
-
-impl EventEmitter<DismissEvent> for UpdateNotification {}
-
-impl Render for UpdateNotification {
-    fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
-        let app_name = cx.global::<ReleaseChannel>().display_name();
-
-        v_stack()
-            .on_action(cx.listener(UpdateNotification::dismiss))
-            .elevation_3(cx)
-            .p_4()
-            .child(
-                h_stack()
-                    .justify_between()
-                    .child(Label::new(format!(
-                        "Updated to {app_name} {}",
-                        self.version
-                    )))
-                    .child(
-                        div()
-                            .id("cancel")
-                            .child(IconElement::new(Icon::Close))
-                            .cursor_pointer()
-                            .on_click(cx.listener(|this, _, cx| this.dismiss(&menu::Cancel, cx))),
-                    ),
-            )
-            .child(
-                div()
-                    .id("notes")
-                    .child(Label::new("View the release notes"))
-                    .cursor_pointer()
-                    .on_click(|_, cx| crate::view_release_notes(&Default::default(), cx)),
-            )
-    }
-}
-
-impl UpdateNotification {
-    pub fn new(version: SemanticVersion) -> Self {
-        Self { version }
-    }
-
-    pub fn dismiss(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
-        cx.emit(DismissEvent);
-    }
-}

crates/breadcrumbs/Cargo.toml 🔗

@@ -12,6 +12,7 @@ doctest = false
 collections = { path = "../collections" }
 editor = { path = "../editor" }
 gpui = { path = "../gpui" }
+ui = { path = "../ui" }
 language = { path = "../language" }
 project = { path = "../project" }
 search = { path = "../search" }

crates/breadcrumbs/src/breadcrumbs.rs 🔗

@@ -1,108 +1,74 @@
+use editor::Editor;
 use gpui::{
-    elements::*, platform::MouseButton, AppContext, Entity, Subscription, View, ViewContext,
-    ViewHandle, WeakViewHandle,
+    Element, EventEmitter, IntoElement, ParentElement, Render, StyledText, Subscription,
+    ViewContext,
 };
 use itertools::Itertools;
-use search::ProjectSearchView;
+use theme::ActiveTheme;
+use ui::{prelude::*, ButtonLike, ButtonStyle, Label, Tooltip};
 use workspace::{
     item::{ItemEvent, ItemHandle},
-    ToolbarItemLocation, ToolbarItemView, Workspace,
+    ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
 };
 
-pub enum Event {
-    UpdateLocation,
-}
-
 pub struct Breadcrumbs {
     pane_focused: bool,
     active_item: Option<Box<dyn ItemHandle>>,
-    project_search: Option<ViewHandle<ProjectSearchView>>,
     subscription: Option<Subscription>,
-    workspace: WeakViewHandle<Workspace>,
 }
 
 impl Breadcrumbs {
-    pub fn new(workspace: &Workspace) -> Self {
+    pub fn new() -> Self {
         Self {
             pane_focused: false,
             active_item: Default::default(),
             subscription: Default::default(),
-            project_search: Default::default(),
-            workspace: workspace.weak_handle(),
         }
     }
 }
 
-impl Entity for Breadcrumbs {
-    type Event = Event;
-}
-
-impl View for Breadcrumbs {
-    fn ui_name() -> &'static str {
-        "Breadcrumbs"
-    }
+impl EventEmitter<ToolbarItemEvent> for Breadcrumbs {}
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let active_item = match &self.active_item {
-            Some(active_item) => active_item,
-            None => return Empty::new().into_any(),
+impl Render for Breadcrumbs {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        let element = h_stack().text_ui();
+        let Some(active_item) = self.active_item.as_ref() else {
+            return element;
+        };
+        let Some(segments) = active_item.breadcrumbs(cx.theme(), cx) else {
+            return element;
         };
-        let not_editor = active_item.downcast::<editor::Editor>().is_none();
 
-        let theme = theme::current(cx).clone();
-        let style = &theme.workspace.toolbar.breadcrumbs;
+        let highlighted_segments = segments.into_iter().map(|segment| {
+            let mut text_style = cx.text_style();
+            text_style.color = Color::Muted.color(cx);
 
-        let breadcrumbs = match active_item.breadcrumbs(&theme, cx) {
-            Some(breadcrumbs) => breadcrumbs,
-            None => return Empty::new().into_any(),
-        }
-        .into_iter()
-        .map(|breadcrumb| {
-            Text::new(
-                breadcrumb.text,
-                theme.workspace.toolbar.breadcrumbs.default.text.clone(),
-            )
-            .with_highlights(breadcrumb.highlights.unwrap_or_default())
-            .into_any()
+            StyledText::new(segment.text)
+                .with_highlights(&text_style, segment.highlights.unwrap_or_default())
+                .into_any()
+        });
+        let breadcrumbs = Itertools::intersperse_with(highlighted_segments, || {
+            Label::new("›").color(Color::Muted).into_any_element()
         });
 
-        let crumbs = Flex::row()
-            .with_children(Itertools::intersperse_with(breadcrumbs, || {
-                Label::new(" › ", style.default.text.clone()).into_any()
-            }))
-            .constrained()
-            .with_height(theme.workspace.toolbar.breadcrumb_height)
-            .contained();
-
-        if not_editor || !self.pane_focused {
-            return crumbs
-                .with_style(style.default.container)
-                .aligned()
-                .left()
-                .into_any();
+        let breadcrumbs_stack = h_stack().gap_1().children(breadcrumbs);
+        match active_item
+            .downcast::<Editor>()
+            .map(|editor| editor.downgrade())
+        {
+            Some(editor) => element.child(
+                ButtonLike::new("toggle outline view")
+                    .child(breadcrumbs_stack)
+                    .style(ButtonStyle::Subtle)
+                    .on_click(move |_, cx| {
+                        if let Some(editor) = editor.upgrade() {
+                            outline::toggle(editor, &outline::Toggle, cx)
+                        }
+                    })
+                    .tooltip(|cx| Tooltip::for_action("Show symbol outline", &outline::Toggle, cx)),
+            ),
+            None => element.child(breadcrumbs_stack),
         }
-
-        MouseEventHandler::new::<Breadcrumbs, _>(0, cx, |state, _| {
-            let style = style.style_for(state);
-            crumbs.with_style(style.container)
-        })
-        .on_click(MouseButton::Left, |_, this, cx| {
-            if let Some(workspace) = this.workspace.upgrade(cx) {
-                workspace.update(cx, |workspace, cx| {
-                    outline::toggle(workspace, &Default::default(), cx)
-                })
-            }
-        })
-        .with_tooltip::<Breadcrumbs>(
-            0,
-            "Show symbol outline".to_owned(),
-            Some(Box::new(outline::Toggle)),
-            theme.tooltip.clone(),
-            cx,
-        )
-        .aligned()
-        .left()
-        .into_any()
     }
 }
 
@@ -114,19 +80,21 @@ impl ToolbarItemView for Breadcrumbs {
     ) -> ToolbarItemLocation {
         cx.notify();
         self.active_item = None;
-        self.project_search = None;
         if let Some(item) = active_pane_item {
-            let this = cx.weak_handle();
+            let this = cx.view().downgrade();
             self.subscription = Some(item.subscribe_to_item_events(
                 cx,
                 Box::new(move |event, cx| {
-                    if let Some(this) = this.upgrade(cx) {
-                        if let ItemEvent::UpdateBreadcrumbs = event {
-                            this.update(cx, |_, cx| {
-                                cx.emit(Event::UpdateLocation);
-                                cx.notify();
-                            });
-                        }
+                    if let ItemEvent::UpdateBreadcrumbs = event {
+                        this.update(cx, |this, cx| {
+                            cx.notify();
+                            if let Some(active_item) = this.active_item.as_ref() {
+                                cx.emit(ToolbarItemEvent::ChangeLocation(
+                                    active_item.breadcrumb_location(cx),
+                                ))
+                            }
+                        })
+                        .ok();
                     }
                 }),
             ));
@@ -137,19 +105,6 @@ impl ToolbarItemView for Breadcrumbs {
         }
     }
 
-    fn location_for_event(
-        &self,
-        _: &Event,
-        current_location: ToolbarItemLocation,
-        cx: &AppContext,
-    ) -> ToolbarItemLocation {
-        if let Some(active_item) = self.active_item.as_ref() {
-            active_item.breadcrumb_location(cx)
-        } else {
-            current_location
-        }
-    }
-
     fn pane_focus_update(&mut self, pane_focused: bool, _: &mut ViewContext<Self>) {
         self.pane_focused = pane_focused;
     }

crates/breadcrumbs2/Cargo.toml 🔗

@@ -1,28 +0,0 @@
-[package]
-name = "breadcrumbs2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/breadcrumbs.rs"
-doctest = false
-
-[dependencies]
-collections = { path = "../collections" }
-editor = { package = "editor2", path = "../editor2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-ui = { package = "ui2", path = "../ui2" }
-language = { package = "language2", path = "../language2" }
-project = { package = "project2", path = "../project2" }
-search = { package = "search2", path = "../search2" }
-settings = { package = "settings2", path = "../settings2" }
-theme = { package = "theme2", path = "../theme2" }
-workspace = { package = "workspace2", path = "../workspace2" }
-outline = { package = "outline2", path = "../outline2" }
-itertools = "0.10"
-
-[dev-dependencies]
-editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }

crates/breadcrumbs2/src/breadcrumbs.rs 🔗

@@ -1,111 +0,0 @@
-use editor::Editor;
-use gpui::{
-    Element, EventEmitter, IntoElement, ParentElement, Render, StyledText, Subscription,
-    ViewContext,
-};
-use itertools::Itertools;
-use theme::ActiveTheme;
-use ui::{prelude::*, ButtonLike, ButtonStyle, Label, Tooltip};
-use workspace::{
-    item::{ItemEvent, ItemHandle},
-    ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
-};
-
-pub struct Breadcrumbs {
-    pane_focused: bool,
-    active_item: Option<Box<dyn ItemHandle>>,
-    subscription: Option<Subscription>,
-}
-
-impl Breadcrumbs {
-    pub fn new() -> Self {
-        Self {
-            pane_focused: false,
-            active_item: Default::default(),
-            subscription: Default::default(),
-        }
-    }
-}
-
-impl EventEmitter<ToolbarItemEvent> for Breadcrumbs {}
-
-impl Render for Breadcrumbs {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        let element = h_stack().text_ui();
-        let Some(active_item) = self.active_item.as_ref() else {
-            return element;
-        };
-        let Some(segments) = active_item.breadcrumbs(cx.theme(), cx) else {
-            return element;
-        };
-
-        let highlighted_segments = segments.into_iter().map(|segment| {
-            let mut text_style = cx.text_style();
-            text_style.color = Color::Muted.color(cx);
-
-            StyledText::new(segment.text)
-                .with_highlights(&text_style, segment.highlights.unwrap_or_default())
-                .into_any()
-        });
-        let breadcrumbs = Itertools::intersperse_with(highlighted_segments, || {
-            Label::new("›").color(Color::Muted).into_any_element()
-        });
-
-        let breadcrumbs_stack = h_stack().gap_1().children(breadcrumbs);
-        match active_item
-            .downcast::<Editor>()
-            .map(|editor| editor.downgrade())
-        {
-            Some(editor) => element.child(
-                ButtonLike::new("toggle outline view")
-                    .child(breadcrumbs_stack)
-                    .style(ButtonStyle::Subtle)
-                    .on_click(move |_, cx| {
-                        if let Some(editor) = editor.upgrade() {
-                            outline::toggle(editor, &outline::Toggle, cx)
-                        }
-                    })
-                    .tooltip(|cx| Tooltip::for_action("Show symbol outline", &outline::Toggle, cx)),
-            ),
-            None => element.child(breadcrumbs_stack),
-        }
-    }
-}
-
-impl ToolbarItemView for Breadcrumbs {
-    fn set_active_pane_item(
-        &mut self,
-        active_pane_item: Option<&dyn ItemHandle>,
-        cx: &mut ViewContext<Self>,
-    ) -> ToolbarItemLocation {
-        cx.notify();
-        self.active_item = None;
-        if let Some(item) = active_pane_item {
-            let this = cx.view().downgrade();
-            self.subscription = Some(item.subscribe_to_item_events(
-                cx,
-                Box::new(move |event, cx| {
-                    if let ItemEvent::UpdateBreadcrumbs = event {
-                        this.update(cx, |this, cx| {
-                            cx.notify();
-                            if let Some(active_item) = this.active_item.as_ref() {
-                                cx.emit(ToolbarItemEvent::ChangeLocation(
-                                    active_item.breadcrumb_location(cx),
-                                ))
-                            }
-                        })
-                        .ok();
-                    }
-                }),
-            ));
-            self.active_item = Some(item.boxed_clone());
-            item.breadcrumb_location(cx)
-        } else {
-            ToolbarItemLocation::Hidden
-        }
-    }
-
-    fn pane_focus_update(&mut self, pane_focused: bool, _: &mut ViewContext<Self>) {
-        self.pane_focused = pane_focused;
-    }
-}

crates/call/Cargo.toml 🔗

@@ -35,11 +35,13 @@ util = { path = "../util" }
 anyhow.workspace = true
 async-broadcast = "0.4"
 futures.workspace = true
+image = "0.23"
 postage.workspace = true
 schemars.workspace = true
 serde.workspace = true
 serde_json.workspace = true
 serde_derive.workspace = true
+smallvec.workspace = true
 
 [dev-dependencies]
 client = { path = "../client", features = ["test-support"] }

crates/call/src/call.rs 🔗

@@ -9,31 +9,25 @@ use client::{proto, Client, TelemetrySettings, TypedEnvelope, User, UserStore, Z
 use collections::HashSet;
 use futures::{channel::oneshot, future::Shared, Future, FutureExt};
 use gpui::{
-    AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Subscription, Task,
-    WeakModelHandle,
+    AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Subscription, Task,
+    WeakModel,
 };
 use postage::watch;
 use project::Project;
+use room::Event;
+use settings::Settings;
 use std::sync::Arc;
 
 pub use participant::ParticipantLocation;
 pub use room::Room;
 
-pub fn init(client: Arc<Client>, user_store: ModelHandle<UserStore>, cx: &mut AppContext) {
-    settings::register::<CallSettings>(cx);
+pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
+    CallSettings::register(cx);
 
-    let active_call = cx.add_model(|cx| ActiveCall::new(client, user_store, cx));
+    let active_call = cx.new_model(|cx| ActiveCall::new(client, user_store, cx));
     cx.set_global(active_call);
 }
 
-#[derive(Clone)]
-pub struct IncomingCall {
-    pub room_id: u64,
-    pub calling_user: Arc<User>,
-    pub participants: Vec<Arc<User>>,
-    pub initial_project: Option<proto::ParticipantProject>,
-}
-
 pub struct OneAtATime {
     cancel: Option<oneshot::Sender<()>>,
 }
@@ -65,43 +59,44 @@ impl OneAtATime {
     }
 }
 
+#[derive(Clone)]
+pub struct IncomingCall {
+    pub room_id: u64,
+    pub calling_user: Arc<User>,
+    pub participants: Vec<Arc<User>>,
+    pub initial_project: Option<proto::ParticipantProject>,
+}
+
 /// Singleton global maintaining the user's participation in a room across workspaces.
 pub struct ActiveCall {
-    room: Option<(ModelHandle<Room>, Vec<Subscription>)>,
-    pending_room_creation: Option<Shared<Task<Result<ModelHandle<Room>, Arc<anyhow::Error>>>>>,
+    room: Option<(Model<Room>, Vec<Subscription>)>,
+    pending_room_creation: Option<Shared<Task<Result<Model<Room>, Arc<anyhow::Error>>>>>,
+    location: Option<WeakModel<Project>>,
     _join_debouncer: OneAtATime,
-    location: Option<WeakModelHandle<Project>>,
     pending_invites: HashSet<u64>,
     incoming_call: (
         watch::Sender<Option<IncomingCall>>,
         watch::Receiver<Option<IncomingCall>>,
     ),
     client: Arc<Client>,
-    user_store: ModelHandle<UserStore>,
+    user_store: Model<UserStore>,
     _subscriptions: Vec<client::Subscription>,
 }
 
-impl Entity for ActiveCall {
-    type Event = room::Event;
-}
+impl EventEmitter<Event> for ActiveCall {}
 
 impl ActiveCall {
-    fn new(
-        client: Arc<Client>,
-        user_store: ModelHandle<UserStore>,
-        cx: &mut ModelContext<Self>,
-    ) -> Self {
+    fn new(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut ModelContext<Self>) -> Self {
         Self {
             room: None,
             pending_room_creation: None,
             location: None,
             pending_invites: Default::default(),
             incoming_call: watch::channel(),
-
             _join_debouncer: OneAtATime { cancel: None },
             _subscriptions: vec![
-                client.add_request_handler(cx.handle(), Self::handle_incoming_call),
-                client.add_message_handler(cx.handle(), Self::handle_call_canceled),
+                client.add_request_handler(cx.weak_model(), Self::handle_incoming_call),
+                client.add_message_handler(cx.weak_model(), Self::handle_call_canceled),
             ],
             client,
             user_store,
@@ -113,35 +108,35 @@ impl ActiveCall {
     }
 
     async fn handle_incoming_call(
-        this: ModelHandle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::IncomingCall>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
     ) -> Result<proto::Ack> {
-        let user_store = this.read_with(&cx, |this, _| this.user_store.clone());
+        let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
         let call = IncomingCall {
             room_id: envelope.payload.room_id,
             participants: user_store
                 .update(&mut cx, |user_store, cx| {
                     user_store.get_users(envelope.payload.participant_user_ids, cx)
-                })
+                })?
                 .await?,
             calling_user: user_store
                 .update(&mut cx, |user_store, cx| {
                     user_store.get_user(envelope.payload.calling_user_id, cx)
-                })
+                })?
                 .await?,
             initial_project: envelope.payload.initial_project,
         };
         this.update(&mut cx, |this, _| {
             *this.incoming_call.0.borrow_mut() = Some(call);
-        });
+        })?;
 
         Ok(proto::Ack {})
     }
 
     async fn handle_call_canceled(
-        this: ModelHandle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::CallCanceled>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -154,18 +149,18 @@ impl ActiveCall {
             {
                 incoming_call.take();
             }
-        });
+        })?;
         Ok(())
     }
 
-    pub fn global(cx: &AppContext) -> ModelHandle<Self> {
-        cx.global::<ModelHandle<Self>>().clone()
+    pub fn global(cx: &AppContext) -> Model<Self> {
+        cx.global::<Model<Self>>().clone()
     }
 
     pub fn invite(
         &mut self,
         called_user_id: u64,
-        initial_project: Option<ModelHandle<Project>>,
+        initial_project: Option<Model<Project>>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
         if !self.pending_invites.insert(called_user_id) {
@@ -184,21 +179,21 @@ impl ActiveCall {
         };
 
         let invite = if let Some(room) = room {
-            cx.spawn_weak(|_, mut cx| async move {
+            cx.spawn(move |_, mut cx| async move {
                 let room = room.await.map_err(|err| anyhow!("{:?}", err))?;
 
                 let initial_project_id = if let Some(initial_project) = initial_project {
                     Some(
-                        room.update(&mut cx, |room, cx| room.share_project(initial_project, cx))
+                        room.update(&mut cx, |room, cx| room.share_project(initial_project, cx))?
                             .await?,
                     )
                 } else {
                     None
                 };
 
-                room.update(&mut cx, |room, cx| {
+                room.update(&mut cx, move |room, cx| {
                     room.call(called_user_id, initial_project_id, cx)
-                })
+                })?
                 .await?;
 
                 anyhow::Ok(())
@@ -207,7 +202,7 @@ impl ActiveCall {
             let client = self.client.clone();
             let user_store = self.user_store.clone();
             let room = cx
-                .spawn(|this, mut cx| async move {
+                .spawn(move |this, mut cx| async move {
                     let create_room = async {
                         let room = cx
                             .update(|cx| {
@@ -218,31 +213,31 @@ impl ActiveCall {
                                     user_store,
                                     cx,
                                 )
-                            })
+                            })?
                             .await?;
 
-                        this.update(&mut cx, |this, cx| this.set_room(Some(room.clone()), cx))
+                        this.update(&mut cx, |this, cx| this.set_room(Some(room.clone()), cx))?
                             .await?;
 
                         anyhow::Ok(room)
                     };
 
                     let room = create_room.await;
-                    this.update(&mut cx, |this, _| this.pending_room_creation = None);
+                    this.update(&mut cx, |this, _| this.pending_room_creation = None)?;
                     room.map_err(Arc::new)
                 })
                 .shared();
             self.pending_room_creation = Some(room.clone());
-            cx.foreground().spawn(async move {
+            cx.background_executor().spawn(async move {
                 room.await.map_err(|err| anyhow!("{:?}", err))?;
                 anyhow::Ok(())
             })
         };
 
-        cx.spawn(|this, mut cx| async move {
+        cx.spawn(move |this, mut cx| async move {
             let result = invite.await;
             if result.is_ok() {
-                this.update(&mut cx, |this, cx| this.report_call_event("invite", cx));
+                this.update(&mut cx, |this, cx| this.report_call_event("invite", cx))?;
             } else {
                 // TODO: Resport collaboration error
             }
@@ -250,7 +245,7 @@ impl ActiveCall {
             this.update(&mut cx, |this, cx| {
                 this.pending_invites.remove(&called_user_id);
                 cx.notify();
-            });
+            })?;
             result
         })
     }
@@ -267,7 +262,7 @@ impl ActiveCall {
         };
 
         let client = self.client.clone();
-        cx.foreground().spawn(async move {
+        cx.background_executor().spawn(async move {
             client
                 .request(proto::CancelCall {
                     room_id,
@@ -306,11 +301,11 @@ impl ActiveCall {
 
         cx.spawn(|this, mut cx| async move {
             let room = join.await?;
-            this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))
+            this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))?
                 .await?;
             this.update(&mut cx, |this, cx| {
                 this.report_call_event("accept incoming", cx)
-            });
+            })?;
             Ok(())
         })
     }
@@ -333,7 +328,7 @@ impl ActiveCall {
         &mut self,
         channel_id: u64,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Option<ModelHandle<Room>>>> {
+    ) -> Task<Result<Option<Model<Room>>>> {
         if let Some(room) = self.room().cloned() {
             if room.read(cx).channel_id() == Some(channel_id) {
                 return Task::ready(Ok(Some(room)));
@@ -352,13 +347,13 @@ impl ActiveCall {
             Room::join_channel(channel_id, client, user_store, cx).await
         });
 
-        cx.spawn(move |this, mut cx| async move {
+        cx.spawn(|this, mut cx| async move {
             let room = join.await?;
-            this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))
+            this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))?
                 .await?;
             this.update(&mut cx, |this, cx| {
                 this.report_call_event("join channel", cx)
-            });
+            })?;
             Ok(room)
         })
     }
@@ -366,6 +361,7 @@ impl ActiveCall {
     pub fn hang_up(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
         cx.notify();
         self.report_call_event("hang up", cx);
+
         Audio::end_call(cx);
         if let Some((room, _)) = self.room.take() {
             room.update(cx, |room, cx| room.leave(cx))
@@ -376,7 +372,7 @@ impl ActiveCall {
 
     pub fn share_project(
         &mut self,
-        project: ModelHandle<Project>,
+        project: Model<Project>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<u64>> {
         if let Some((room, _)) = self.room.as_ref() {
@@ -389,7 +385,7 @@ impl ActiveCall {
 
     pub fn unshare_project(
         &mut self,
-        project: ModelHandle<Project>,
+        project: Model<Project>,
         cx: &mut ModelContext<Self>,
     ) -> Result<()> {
         if let Some((room, _)) = self.room.as_ref() {
@@ -400,13 +396,13 @@ impl ActiveCall {
         }
     }
 
-    pub fn location(&self) -> Option<&WeakModelHandle<Project>> {
+    pub fn location(&self) -> Option<&WeakModel<Project>> {
         self.location.as_ref()
     }
 
     pub fn set_location(
         &mut self,
-        project: Option<&ModelHandle<Project>>,
+        project: Option<&Model<Project>>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
         if project.is_some() || !*ZED_ALWAYS_ACTIVE {
@@ -420,7 +416,7 @@ impl ActiveCall {
 
     fn set_room(
         &mut self,
-        room: Option<ModelHandle<Room>>,
+        room: Option<Model<Room>>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
         if room.as_ref() != self.room.as_ref().map(|room| &room.0) {
@@ -441,7 +437,10 @@ impl ActiveCall {
                         cx.subscribe(&room, |_, _, event, cx| cx.emit(event.clone())),
                     ];
                     self.room = Some((room.clone(), subscriptions));
-                    let location = self.location.and_then(|location| location.upgrade(cx));
+                    let location = self
+                        .location
+                        .as_ref()
+                        .and_then(|location| location.upgrade());
                     room.update(cx, |room, cx| room.set_location(location.as_ref(), cx))
                 }
             } else {
@@ -453,7 +452,7 @@ impl ActiveCall {
         }
     }
 
-    pub fn room(&self) -> Option<&ModelHandle<Room>> {
+    pub fn room(&self) -> Option<&Model<Room>> {
         self.room.as_ref().map(|(room, _)| room)
     }
 
@@ -465,7 +464,7 @@ impl ActiveCall {
         &self.pending_invites
     }
 
-    pub fn report_call_event(&self, operation: &'static str, cx: &AppContext) {
+    pub fn report_call_event(&self, operation: &'static str, cx: &mut AppContext) {
         if let Some(room) = self.room() {
             let room = room.read(cx);
             report_call_event_for_room(operation, room.id(), room.channel_id(), &self.client, cx);
@@ -478,10 +477,10 @@ pub fn report_call_event_for_room(
     room_id: u64,
     channel_id: Option<u64>,
     client: &Arc<Client>,
-    cx: &AppContext,
+    cx: &mut AppContext,
 ) {
     let telemetry = client.telemetry();
-    let telemetry_settings = *settings::get::<TelemetrySettings>(cx);
+    let telemetry_settings = *TelemetrySettings::get_global(cx);
 
     telemetry.report_call_event(telemetry_settings, operation, Some(room_id), channel_id)
 }
@@ -495,7 +494,8 @@ pub fn report_call_event_for_channel(
     let room = ActiveCall::global(cx).read(cx).room();
 
     let telemetry = client.telemetry();
-    let telemetry_settings = *settings::get::<TelemetrySettings>(cx);
+
+    let telemetry_settings = *TelemetrySettings::get_global(cx);
 
     telemetry.report_call_event(
         telemetry_settings,

crates/call/src/call_settings.rs 🔗

@@ -1,6 +1,8 @@
+use anyhow::Result;
+use gpui::AppContext;
 use schemars::JsonSchema;
 use serde_derive::{Deserialize, Serialize};
-use settings::Setting;
+use settings::Settings;
 
 #[derive(Deserialize, Debug)]
 pub struct CallSettings {
@@ -12,7 +14,7 @@ pub struct CallSettingsContent {
     pub mute_on_join: Option<bool>,
 }
 
-impl Setting for CallSettings {
+impl Settings for CallSettings {
     const KEY: Option<&'static str> = Some("calls");
 
     type FileContent = CallSettingsContent;
@@ -20,8 +22,11 @@ impl Setting for CallSettings {
     fn load(
         default_value: &Self::FileContent,
         user_values: &[&Self::FileContent],
-        _: &gpui::AppContext,
-    ) -> anyhow::Result<Self> {
+        _cx: &mut AppContext,
+    ) -> Result<Self>
+    where
+        Self: Sized,
+    {
         Self::load_via_json_merge(default_value, user_values)
     }
 }

crates/call/src/participant.rs 🔗

@@ -2,11 +2,11 @@ use anyhow::{anyhow, Result};
 use client::ParticipantIndex;
 use client::{proto, User};
 use collections::HashMap;
-use gpui::WeakModelHandle;
+use gpui::WeakModel;
 pub use live_kit_client::Frame;
-use live_kit_client::RemoteAudioTrack;
+pub use live_kit_client::{RemoteAudioTrack, RemoteVideoTrack};
 use project::Project;
-use std::{fmt, sync::Arc};
+use std::sync::Arc;
 
 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
 pub enum ParticipantLocation {
@@ -35,7 +35,7 @@ impl ParticipantLocation {
 #[derive(Clone, Default)]
 pub struct LocalParticipant {
     pub projects: Vec<proto::ParticipantProject>,
-    pub active_project: Option<WeakModelHandle<Project>>,
+    pub active_project: Option<WeakModel<Project>>,
 }
 
 #[derive(Clone, Debug)]
@@ -50,20 +50,3 @@ pub struct RemoteParticipant {
     pub video_tracks: HashMap<live_kit_client::Sid, Arc<RemoteVideoTrack>>,
     pub audio_tracks: HashMap<live_kit_client::Sid, Arc<RemoteAudioTrack>>,
 }
-
-#[derive(Clone)]
-pub struct RemoteVideoTrack {
-    pub(crate) live_kit_track: Arc<live_kit_client::RemoteVideoTrack>,
-}
-
-impl fmt::Debug for RemoteVideoTrack {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_struct("RemoteVideoTrack").finish()
-    }
-}
-
-impl RemoteVideoTrack {
-    pub fn frames(&self) -> async_broadcast::Receiver<Frame> {
-        self.live_kit_track.frames()
-    }
-}

crates/call/src/room.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
     call_settings::CallSettings,
-    participant::{LocalParticipant, ParticipantLocation, RemoteParticipant, RemoteVideoTrack},
+    participant::{LocalParticipant, ParticipantLocation, RemoteParticipant},
 };
 use anyhow::{anyhow, Result};
 use audio::{Audio, Sound};
@@ -11,7 +11,9 @@ use client::{
 use collections::{BTreeMap, HashMap, HashSet};
 use fs::Fs;
 use futures::{FutureExt, StreamExt};
-use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle};
+use gpui::{
+    AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel,
+};
 use language::LanguageRegistry;
 use live_kit_client::{
     LocalAudioTrack, LocalTrackPublication, LocalVideoTrack, RemoteAudioTrackUpdate,
@@ -19,7 +21,8 @@ use live_kit_client::{
 };
 use postage::{sink::Sink, stream::Stream, watch};
 use project::Project;
-use std::{future::Future, mem, pin::Pin, sync::Arc, time::Duration};
+use settings::Settings as _;
+use std::{future::Future, mem, sync::Arc, time::Duration};
 use util::{post_inc, ResultExt, TryFutureExt};
 
 pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
@@ -54,11 +57,11 @@ pub enum Event {
 
 pub struct Room {
     id: u64,
-    pub channel_id: Option<u64>,
+    channel_id: Option<u64>,
     live_kit: Option<LiveKitRoom>,
     status: RoomStatus,
-    shared_projects: HashSet<WeakModelHandle<Project>>,
-    joined_projects: HashSet<WeakModelHandle<Project>>,
+    shared_projects: HashSet<WeakModel<Project>>,
+    joined_projects: HashSet<WeakModel<Project>>,
     local_participant: LocalParticipant,
     remote_participants: BTreeMap<u64, RemoteParticipant>,
     pending_participants: Vec<Arc<User>>,
@@ -66,39 +69,17 @@ pub struct Room {
     pending_call_count: usize,
     leave_when_empty: bool,
     client: Arc<Client>,
-    user_store: ModelHandle<UserStore>,
+    user_store: Model<UserStore>,
     follows_by_leader_id_project_id: HashMap<(PeerId, u64), Vec<PeerId>>,
-    subscriptions: Vec<client::Subscription>,
+    client_subscriptions: Vec<client::Subscription>,
+    _subscriptions: Vec<gpui::Subscription>,
     room_update_completed_tx: watch::Sender<Option<()>>,
     room_update_completed_rx: watch::Receiver<Option<()>>,
     pending_room_update: Option<Task<()>>,
     maintain_connection: Option<Task<Option<()>>>,
 }
 
-impl Entity for Room {
-    type Event = Event;
-
-    fn release(&mut self, cx: &mut AppContext) {
-        if self.status.is_online() {
-            self.leave_internal(cx).detach_and_log_err(cx);
-        }
-    }
-
-    fn app_will_quit(&mut self, cx: &mut AppContext) -> Option<Pin<Box<dyn Future<Output = ()>>>> {
-        if self.status.is_online() {
-            let leave = self.leave_internal(cx);
-            Some(
-                cx.background()
-                    .spawn(async move {
-                        leave.await.log_err();
-                    })
-                    .boxed(),
-            )
-        } else {
-            None
-        }
-    }
-}
+impl EventEmitter<Event> for Room {}
 
 impl Room {
     pub fn channel_id(&self) -> Option<u64> {
@@ -121,16 +102,12 @@ impl Room {
         }
     }
 
-    pub fn can_publish(&self) -> bool {
-        self.live_kit.as_ref().is_some_and(|room| room.can_publish)
-    }
-
     fn new(
         id: u64,
         channel_id: Option<u64>,
         live_kit_connection_info: Option<proto::LiveKitConnectionInfo>,
         client: Arc<Client>,
-        user_store: ModelHandle<UserStore>,
+        user_store: Model<UserStore>,
         cx: &mut ModelContext<Self>,
     ) -> Self {
         let live_kit_room = if let Some(connection_info) = live_kit_connection_info {
@@ -138,69 +115,75 @@ impl Room {
             let mut status = room.status();
             // Consume the initial status of the room.
             let _ = status.try_recv();
-            let _maintain_room = cx.spawn_weak(|this, mut cx| async move {
+            let _maintain_room = cx.spawn(|this, mut cx| async move {
                 while let Some(status) = status.next().await {
-                    let this = if let Some(this) = this.upgrade(&cx) {
+                    let this = if let Some(this) = this.upgrade() {
                         this
                     } else {
                         break;
                     };
 
                     if status == live_kit_client::ConnectionState::Disconnected {
-                        this.update(&mut cx, |this, cx| this.leave(cx).log_err());
+                        this.update(&mut cx, |this, cx| this.leave(cx).log_err())
+                            .ok();
                         break;
                     }
                 }
             });
 
-            let mut track_video_changes = room.remote_video_track_updates();
-            let _maintain_video_tracks = cx.spawn_weak(|this, mut cx| async move {
-                while let Some(track_change) = track_video_changes.next().await {
-                    let this = if let Some(this) = this.upgrade(&cx) {
-                        this
-                    } else {
-                        break;
-                    };
+            let _maintain_video_tracks = cx.spawn({
+                let room = room.clone();
+                move |this, mut cx| async move {
+                    let mut track_video_changes = room.remote_video_track_updates();
+                    while let Some(track_change) = track_video_changes.next().await {
+                        let this = if let Some(this) = this.upgrade() {
+                            this
+                        } else {
+                            break;
+                        };
 
-                    this.update(&mut cx, |this, cx| {
-                        this.remote_video_track_updated(track_change, cx).log_err()
-                    });
+                        this.update(&mut cx, |this, cx| {
+                            this.remote_video_track_updated(track_change, cx).log_err()
+                        })
+                        .ok();
+                    }
                 }
             });
 
-            let mut track_audio_changes = room.remote_audio_track_updates();
-            let _maintain_audio_tracks = cx.spawn_weak(|this, mut cx| async move {
-                while let Some(track_change) = track_audio_changes.next().await {
-                    let this = if let Some(this) = this.upgrade(&cx) {
-                        this
-                    } else {
-                        break;
-                    };
+            let _maintain_audio_tracks = cx.spawn({
+                let room = room.clone();
+                |this, mut cx| async move {
+                    let mut track_audio_changes = room.remote_audio_track_updates();
+                    while let Some(track_change) = track_audio_changes.next().await {
+                        let this = if let Some(this) = this.upgrade() {
+                            this
+                        } else {
+                            break;
+                        };
 
-                    this.update(&mut cx, |this, cx| {
-                        this.remote_audio_track_updated(track_change, cx).log_err()
-                    });
+                        this.update(&mut cx, |this, cx| {
+                            this.remote_audio_track_updated(track_change, cx).log_err()
+                        })
+                        .ok();
+                    }
                 }
             });
 
             let connect = room.connect(&connection_info.server_url, &connection_info.token);
-            if connection_info.can_publish {
-                cx.spawn(|this, mut cx| async move {
-                    connect.await?;
+            cx.spawn(|this, mut cx| async move {
+                connect.await?;
 
-                    if !cx.read(Self::mute_on_join) {
-                        this.update(&mut cx, |this, cx| this.share_microphone(cx))
-                            .await?;
-                    }
+                if !cx.update(|cx| Self::mute_on_join(cx))? {
+                    this.update(&mut cx, |this, cx| this.share_microphone(cx))?
+                        .await?;
+                }
 
-                    anyhow::Ok(())
-                })
-                .detach_and_log_err(cx);
-            }
+                anyhow::Ok(())
+            })
+            .detach_and_log_err(cx);
 
             Some(LiveKitRoom {
                 room,
-                can_publish: connection_info.can_publish,
                 screen_track: LocalTrack::None,
                 microphone_track: LocalTrack::None,
                 next_publish_id: 0,
@@ -214,8 +197,10 @@ impl Room {
             None
         };
 
-        let maintain_connection =
-            cx.spawn_weak(|this, cx| Self::maintain_connection(this, client.clone(), cx).log_err());
+        let maintain_connection = cx.spawn({
+            let client = client.clone();
+            move |this, cx| Self::maintain_connection(this, client.clone(), cx).log_err()
+        });
 
         Audio::play_sound(Sound::Joined, cx);
 
@@ -233,7 +218,13 @@ impl Room {
             remote_participants: Default::default(),
             pending_participants: Default::default(),
             pending_call_count: 0,
-            subscriptions: vec![client.add_message_handler(cx.handle(), Self::handle_room_updated)],
+            client_subscriptions: vec![
+                client.add_message_handler(cx.weak_model(), Self::handle_room_updated)
+            ],
+            _subscriptions: vec![
+                cx.on_release(Self::released),
+                cx.on_app_quit(Self::app_will_quit),
+            ],
             leave_when_empty: false,
             pending_room_update: None,
             client,
@@ -247,15 +238,15 @@ impl Room {
 
     pub(crate) fn create(
         called_user_id: u64,
-        initial_project: Option<ModelHandle<Project>>,
+        initial_project: Option<Model<Project>>,
         client: Arc<Client>,
-        user_store: ModelHandle<UserStore>,
+        user_store: Model<UserStore>,
         cx: &mut AppContext,
-    ) -> Task<Result<ModelHandle<Self>>> {
-        cx.spawn(|mut cx| async move {
+    ) -> Task<Result<Model<Self>>> {
+        cx.spawn(move |mut cx| async move {
             let response = client.request(proto::CreateRoom {}).await?;
             let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
-            let room = cx.add_model(|cx| {
+            let room = cx.new_model(|cx| {
                 Self::new(
                     room_proto.id,
                     None,
@@ -264,13 +255,13 @@ impl Room {
                     user_store,
                     cx,
                 )
-            });
+            })?;
 
             let initial_project_id = if let Some(initial_project) = initial_project {
                 let initial_project_id = room
                     .update(&mut cx, |room, cx| {
                         room.share_project(initial_project.clone(), cx)
-                    })
+                    })?
                     .await?;
                 Some(initial_project_id)
             } else {
@@ -281,7 +272,7 @@ impl Room {
                 .update(&mut cx, |room, cx| {
                     room.leave_when_empty = true;
                     room.call(called_user_id, initial_project_id, cx)
-                })
+                })?
                 .await
             {
                 Ok(()) => Ok(room),
@@ -293,9 +284,9 @@ impl Room {
     pub(crate) async fn join_channel(
         channel_id: u64,
         client: Arc<Client>,
-        user_store: ModelHandle<UserStore>,
+        user_store: Model<UserStore>,
         cx: AsyncAppContext,
-    ) -> Result<ModelHandle<Self>> {
+    ) -> Result<Model<Self>> {
         Self::from_join_response(
             client.request(proto::JoinChannel { channel_id }).await?,
             client,
@@ -307,9 +298,9 @@ impl Room {
     pub(crate) async fn join(
         room_id: u64,
         client: Arc<Client>,
-        user_store: ModelHandle<UserStore>,
+        user_store: Model<UserStore>,
         cx: AsyncAppContext,
-    ) -> Result<ModelHandle<Self>> {
+    ) -> Result<Model<Self>> {
         Self::from_join_response(
             client.request(proto::JoinRoom { id: room_id }).await?,
             client,
@@ -318,18 +309,41 @@ impl Room {
         )
     }
 
+    fn released(&mut self, cx: &mut AppContext) {
+        if self.status.is_online() {
+            self.leave_internal(cx).detach_and_log_err(cx);
+        }
+    }
+
+    fn app_will_quit(&mut self, cx: &mut ModelContext<Self>) -> impl Future<Output = ()> {
+        let task = if self.status.is_online() {
+            let leave = self.leave_internal(cx);
+            Some(cx.background_executor().spawn(async move {
+                leave.await.log_err();
+            }))
+        } else {
+            None
+        };
+
+        async move {
+            if let Some(task) = task {
+                task.await;
+            }
+        }
+    }
+
     pub fn mute_on_join(cx: &AppContext) -> bool {
-        settings::get::<CallSettings>(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some()
+        CallSettings::get_global(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some()
     }
 
     fn from_join_response(
         response: proto::JoinRoomResponse,
         client: Arc<Client>,
-        user_store: ModelHandle<UserStore>,
+        user_store: Model<UserStore>,
         mut cx: AsyncAppContext,
-    ) -> Result<ModelHandle<Self>> {
+    ) -> Result<Model<Self>> {
         let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
-        let room = cx.add_model(|cx| {
+        let room = cx.new_model(|cx| {
             Self::new(
                 room_proto.id,
                 response.channel_id,
@@ -338,12 +352,12 @@ impl Room {
                 user_store,
                 cx,
             )
-        });
+        })?;
         room.update(&mut cx, |room, cx| {
             room.leave_when_empty = room.channel_id.is_none();
             room.apply_room_update(room_proto, cx)?;
             anyhow::Ok(())
-        })?;
+        })??;
         Ok(room)
     }
 
@@ -372,7 +386,7 @@ impl Room {
         self.clear_state(cx);
 
         let leave_room = self.client.request(proto::LeaveRoom {});
-        cx.background().spawn(async move {
+        cx.background_executor().spawn(async move {
             leave_room.await?;
             anyhow::Ok(())
         })
@@ -380,14 +394,14 @@ impl Room {
 
     pub(crate) fn clear_state(&mut self, cx: &mut AppContext) {
         for project in self.shared_projects.drain() {
-            if let Some(project) = project.upgrade(cx) {
+            if let Some(project) = project.upgrade() {
                 project.update(cx, |project, cx| {
                     project.unshare(cx).log_err();
                 });
             }
         }
         for project in self.joined_projects.drain() {
-            if let Some(project) = project.upgrade(cx) {
+            if let Some(project) = project.upgrade() {
                 project.update(cx, |project, cx| {
                     project.disconnected_from_host(cx);
                     project.close(cx);
@@ -399,14 +413,14 @@ impl Room {
         self.remote_participants.clear();
         self.pending_participants.clear();
         self.participant_user_ids.clear();
-        self.subscriptions.clear();
+        self.client_subscriptions.clear();
         self.live_kit.take();
         self.pending_room_update.take();
         self.maintain_connection.take();
     }
 
     async fn maintain_connection(
-        this: WeakModelHandle<Self>,
+        this: WeakModel<Self>,
         client: Arc<Client>,
         mut cx: AsyncAppContext,
     ) -> Result<()> {
@@ -418,32 +432,33 @@ impl Room {
             if !is_connected || client_status.next().await.is_some() {
                 log::info!("detected client disconnection");
 
-                this.upgrade(&cx)
+                this.upgrade()
                     .ok_or_else(|| anyhow!("room was dropped"))?
                     .update(&mut cx, |this, cx| {
                         this.status = RoomStatus::Rejoining;
                         cx.notify();
-                    });
+                    })?;
 
                 // Wait for client to re-establish a connection to the server.
                 {
-                    let mut reconnection_timeout = cx.background().timer(RECONNECT_TIMEOUT).fuse();
+                    let mut reconnection_timeout =
+                        cx.background_executor().timer(RECONNECT_TIMEOUT).fuse();
                     let client_reconnection = async {
                         let mut remaining_attempts = 3;
                         while remaining_attempts > 0 {
                             if client_status.borrow().is_connected() {
                                 log::info!("client reconnected, attempting to rejoin room");
 
-                                let Some(this) = this.upgrade(&cx) else { break };
-                                if this
-                                    .update(&mut cx, |this, cx| this.rejoin(cx))
-                                    .await
-                                    .log_err()
-                                    .is_some()
-                                {
-                                    return true;
-                                } else {
-                                    remaining_attempts -= 1;
+                                let Some(this) = this.upgrade() else { break };
+                                match this.update(&mut cx, |this, cx| this.rejoin(cx)) {
+                                    Ok(task) => {
+                                        if task.await.log_err().is_some() {
+                                            return true;
+                                        } else {
+                                            remaining_attempts -= 1;
+                                        }
+                                    }
+                                    Err(_app_dropped) => return false,
                                 }
                             } else if client_status.borrow().is_signed_out() {
                                 return false;
@@ -482,9 +497,9 @@ impl Room {
         // The client failed to re-establish a connection to the server
         // or an error occurred while trying to re-join the room. Either way
         // we leave the room and return an error.
-        if let Some(this) = this.upgrade(&cx) {
+        if let Some(this) = this.upgrade() {
             log::info!("reconnection failed, leaving room");
-            let _ = this.update(&mut cx, |this, cx| this.leave(cx));
+            let _ = this.update(&mut cx, |this, cx| this.leave(cx))?;
         }
         Err(anyhow!(
             "can't reconnect to room: client failed to re-establish connection"
@@ -496,7 +511,7 @@ impl Room {
         let mut reshared_projects = Vec::new();
         let mut rejoined_projects = Vec::new();
         self.shared_projects.retain(|project| {
-            if let Some(handle) = project.upgrade(cx) {
+            if let Some(handle) = project.upgrade() {
                 let project = handle.read(cx);
                 if let Some(project_id) = project.remote_id() {
                     projects.insert(project_id, handle.clone());
@@ -510,14 +525,14 @@ impl Room {
             false
         });
         self.joined_projects.retain(|project| {
-            if let Some(handle) = project.upgrade(cx) {
+            if let Some(handle) = project.upgrade() {
                 let project = handle.read(cx);
                 if let Some(project_id) = project.remote_id() {
                     projects.insert(project_id, handle.clone());
                     rejoined_projects.push(proto::RejoinProject {
                         id: project_id,
                         worktrees: project
-                            .worktrees(cx)
+                            .worktrees()
                             .map(|worktree| {
                                 let worktree = worktree.read(cx);
                                 proto::RejoinWorktree {
@@ -565,7 +580,7 @@ impl Room {
                 }
 
                 anyhow::Ok(())
-            })
+            })?
         })
     }
 
@@ -643,7 +658,7 @@ impl Room {
     }
 
     async fn handle_room_updated(
-        this: ModelHandle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::RoomUpdated>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -652,7 +667,7 @@ impl Room {
             .payload
             .room
             .ok_or_else(|| anyhow!("invalid room"))?;
-        this.update(&mut cx, |this, cx| this.apply_room_update(room, cx))
+        this.update(&mut cx, |this, cx| this.apply_room_update(room, cx))?
     }
 
     fn apply_room_update(
@@ -733,7 +748,7 @@ impl Room {
 
                         for unshared_project_id in old_projects.difference(&new_projects) {
                             this.joined_projects.retain(|project| {
-                                if let Some(project) = project.upgrade(cx) {
+                                if let Some(project) = project.upgrade() {
                                     project.update(cx, |project, cx| {
                                         if project.remote_id() == Some(*unshared_project_id) {
                                             project.disconnected_from_host(cx);
@@ -876,7 +891,8 @@ impl Room {
                 this.check_invariants();
                 this.room_update_completed_tx.try_send(Some(())).ok();
                 cx.notify();
-            });
+            })
+            .ok();
         }));
 
         cx.notify();
@@ -907,12 +923,7 @@ impl Room {
                     .remote_participants
                     .get_mut(&user_id)
                     .ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?;
-                participant.video_tracks.insert(
-                    track_id.clone(),
-                    Arc::new(RemoteVideoTrack {
-                        live_kit_track: track,
-                    }),
-                );
+                participant.video_tracks.insert(track_id.clone(), track);
                 cx.emit(Event::RemoteVideoTracksChanged {
                     participant_id: participant.peer_id,
                 });
@@ -991,7 +1002,6 @@ impl Room {
                     .remote_participants
                     .get_mut(&user_id)
                     .ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?;
-
                 participant.audio_tracks.insert(track_id.clone(), track);
                 participant.muted = publication.is_muted();
 
@@ -1053,7 +1063,7 @@ impl Room {
         let client = self.client.clone();
         let room_id = self.id;
         self.pending_call_count += 1;
-        cx.spawn(|this, mut cx| async move {
+        cx.spawn(move |this, mut cx| async move {
             let result = client
                 .request(proto::Call {
                     room_id,
@@ -1066,7 +1076,7 @@ impl Room {
                 if this.should_leave() {
                     this.leave(cx).detach_and_log_err(cx);
                 }
-            });
+            })?;
             result?;
             Ok(())
         })
@@ -1078,31 +1088,31 @@ impl Room {
         language_registry: Arc<LanguageRegistry>,
         fs: Arc<dyn Fs>,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<ModelHandle<Project>>> {
+    ) -> Task<Result<Model<Project>>> {
         let client = self.client.clone();
         let user_store = self.user_store.clone();
         cx.emit(Event::RemoteProjectJoined { project_id: id });
-        cx.spawn(|this, mut cx| async move {
+        cx.spawn(move |this, mut cx| async move {
             let project =
                 Project::remote(id, client, user_store, language_registry, fs, cx.clone()).await?;
 
             this.update(&mut cx, |this, cx| {
                 this.joined_projects.retain(|project| {
-                    if let Some(project) = project.upgrade(cx) {
+                    if let Some(project) = project.upgrade() {
                         !project.read(cx).is_read_only()
                     } else {
                         false
                     }
                 });
                 this.joined_projects.insert(project.downgrade());
-            });
+            })?;
             Ok(project)
         })
     }
 
     pub(crate) fn share_project(
         &mut self,
-        project: ModelHandle<Project>,
+        project: Model<Project>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<u64>> {
         if let Some(project_id) = project.read(cx).remote_id() {
@@ -1118,7 +1128,7 @@ impl Room {
 
             project.update(&mut cx, |project, cx| {
                 project.shared(response.project_id, cx)
-            })?;
+            })??;
 
             // If the user's location is in this project, it changes from UnsharedProject to SharedProject.
             this.update(&mut cx, |this, cx| {
@@ -1129,7 +1139,7 @@ impl Room {
                 } else {
                     Task::ready(Ok(()))
                 }
-            })
+            })?
             .await?;
 
             Ok(response.project_id)
@@ -1138,7 +1148,7 @@ impl Room {
 
     pub(crate) fn unshare_project(
         &mut self,
-        project: ModelHandle<Project>,
+        project: Model<Project>,
         cx: &mut ModelContext<Self>,
     ) -> Result<()> {
         let project_id = match project.read(cx).remote_id() {
@@ -1152,7 +1162,7 @@ impl Room {
 
     pub(crate) fn set_location(
         &mut self,
-        project: Option<&ModelHandle<Project>>,
+        project: Option<&Model<Project>>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
         if self.status.is_offline() {
@@ -1178,7 +1188,7 @@ impl Room {
         };
 
         cx.notify();
-        cx.foreground().spawn(async move {
+        cx.background_executor().spawn(async move {
             client
                 .request(proto::UpdateParticipantLocation {
                     room_id,
@@ -1244,22 +1254,21 @@ impl Room {
             return Task::ready(Err(anyhow!("live-kit was not initialized")));
         };
 
-        cx.spawn_weak(|this, mut cx| async move {
+        cx.spawn(move |this, mut cx| async move {
             let publish_track = async {
                 let track = LocalAudioTrack::create();
-                this.upgrade(&cx)
+                this.upgrade()
                     .ok_or_else(|| anyhow!("room was dropped"))?
-                    .read_with(&cx, |this, _| {
+                    .update(&mut cx, |this, _| {
                         this.live_kit
                             .as_ref()
                             .map(|live_kit| live_kit.room.publish_audio_track(track))
-                    })
+                    })?
                     .ok_or_else(|| anyhow!("live-kit was not initialized"))?
                     .await
             };
-
             let publication = publish_track.await;
-            this.upgrade(&cx)
+            this.upgrade()
                 .ok_or_else(|| anyhow!("room was dropped"))?
                 .update(&mut cx, |this, cx| {
                     let live_kit = this
@@ -1283,7 +1292,9 @@ impl Room {
                                 live_kit.room.unpublish_track(publication);
                             } else {
                                 if muted {
-                                    cx.background().spawn(publication.set_mute(muted)).detach();
+                                    cx.background_executor()
+                                        .spawn(publication.set_mute(muted))
+                                        .detach();
                                 }
                                 live_kit.microphone_track = LocalTrack::Published {
                                     track_publication: publication,
@@ -1303,7 +1314,7 @@ impl Room {
                             }
                         }
                     }
-                })
+                })?
         })
     }
 
@@ -1326,26 +1337,26 @@ impl Room {
             return Task::ready(Err(anyhow!("live-kit was not initialized")));
         };
 
-        cx.spawn_weak(|this, mut cx| async move {
+        cx.spawn(move |this, mut cx| async move {
             let publish_track = async {
                 let displays = displays.await?;
                 let display = displays
                     .first()
                     .ok_or_else(|| anyhow!("no display found"))?;
                 let track = LocalVideoTrack::screen_share_for_display(&display);
-                this.upgrade(&cx)
+                this.upgrade()
                     .ok_or_else(|| anyhow!("room was dropped"))?
-                    .read_with(&cx, |this, _| {
+                    .update(&mut cx, |this, _| {
                         this.live_kit
                             .as_ref()
                             .map(|live_kit| live_kit.room.publish_video_track(track))
-                    })
+                    })?
                     .ok_or_else(|| anyhow!("live-kit was not initialized"))?
                     .await
             };
 
             let publication = publish_track.await;
-            this.upgrade(&cx)
+            this.upgrade()
                 .ok_or_else(|| anyhow!("room was dropped"))?
                 .update(&mut cx, |this, cx| {
                     let live_kit = this
@@ -1369,7 +1380,9 @@ impl Room {
                                 live_kit.room.unpublish_track(publication);
                             } else {
                                 if muted {
-                                    cx.background().spawn(publication.set_mute(muted)).detach();
+                                    cx.background_executor()
+                                        .spawn(publication.set_mute(muted))
+                                        .detach();
                                 }
                                 live_kit.screen_track = LocalTrack::Published {
                                     track_publication: publication,
@@ -1392,7 +1405,7 @@ impl Room {
                             }
                         }
                     }
-                })
+                })?
         })
     }
 
@@ -1435,11 +1448,12 @@ impl Room {
                     .room
                     .remote_audio_track_publications(&participant.user.id.to_string())
                 {
-                    tasks.push(cx.foreground().spawn(track.set_enabled(!live_kit.deafened)));
+                    let deafened = live_kit.deafened;
+                    tasks.push(cx.foreground_executor().spawn(track.set_enabled(!deafened)));
                 }
             }
 
-            Ok(cx.foreground().spawn(async move {
+            Ok(cx.foreground_executor().spawn(async move {
                 if let Some(mute_task) = mute_task {
                     mute_task.await?;
                 }
@@ -1499,7 +1513,6 @@ struct LiveKitRoom {
     deafened: bool,
     speaking: bool,
     next_publish_id: usize,
-    can_publish: bool,
     _maintain_room: Task<()>,
     _maintain_tracks: [Task<()>; 2],
 }
@@ -1531,7 +1544,8 @@ impl LiveKitRoom {
                 *muted = should_mute;
                 cx.notify();
                 Ok((
-                    cx.background().spawn(track_publication.set_mute(*muted)),
+                    cx.background_executor()
+                        .spawn(track_publication.set_mute(*muted)),
                     old_muted,
                 ))
             }

crates/call2/Cargo.toml 🔗

@@ -1,54 +0,0 @@
-[package]
-name = "call2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/call2.rs"
-doctest = false
-
-[features]
-test-support = [
-    "client/test-support",
-    "collections/test-support",
-    "gpui/test-support",
-    "live_kit_client/test-support",
-    "project/test-support",
-    "util/test-support"
-]
-
-[dependencies]
-audio = { package = "audio2", path = "../audio2" }
-client = { package = "client2", path = "../client2" }
-collections = { path = "../collections" }
-gpui = { package = "gpui2", path = "../gpui2" }
-log.workspace = true
-live_kit_client = { package = "live_kit_client2", path = "../live_kit_client2" }
-fs = { package = "fs2", path = "../fs2" }
-language = { package = "language2", path = "../language2" }
-media = { path = "../media" }
-project = { package = "project2", path = "../project2" }
-settings = { package = "settings2", path = "../settings2" }
-util = { path = "../util" }
-
-anyhow.workspace = true
-async-broadcast = "0.4"
-futures.workspace = true
-image = "0.23"
-postage.workspace = true
-schemars.workspace = true
-serde.workspace = true
-serde_json.workspace = true
-serde_derive.workspace = true
-smallvec.workspace = true
-
-[dev-dependencies]
-client = { package = "client2", path = "../client2", features = ["test-support"] }
-fs = { package = "fs2", path = "../fs2", features = ["test-support"] }
-language = { package = "language2", path = "../language2", features = ["test-support"] }
-collections = { path = "../collections", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-live_kit_client = { package = "live_kit_client2", path = "../live_kit_client2", features = ["test-support"] }
-project = { package = "project2", path = "../project2", features = ["test-support"] }
-util = { path = "../util", features = ["test-support"] }

crates/call2/src/call2.rs 🔗

@@ -1,543 +0,0 @@
-pub mod call_settings;
-pub mod participant;
-pub mod room;
-
-use anyhow::{anyhow, Result};
-use audio::Audio;
-use call_settings::CallSettings;
-use client::{proto, Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE};
-use collections::HashSet;
-use futures::{channel::oneshot, future::Shared, Future, FutureExt};
-use gpui::{
-    AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Subscription, Task,
-    WeakModel,
-};
-use postage::watch;
-use project::Project;
-use room::Event;
-use settings::Settings;
-use std::sync::Arc;
-
-pub use participant::ParticipantLocation;
-pub use room::Room;
-
-pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
-    CallSettings::register(cx);
-
-    let active_call = cx.new_model(|cx| ActiveCall::new(client, user_store, cx));
-    cx.set_global(active_call);
-}
-
-pub struct OneAtATime {
-    cancel: Option<oneshot::Sender<()>>,
-}
-
-impl OneAtATime {
-    /// spawn a task in the given context.
-    /// if another task is spawned before that resolves, or if the OneAtATime itself is dropped, the first task will be cancelled and return Ok(None)
-    /// otherwise you'll see the result of the task.
-    fn spawn<F, Fut, R>(&mut self, cx: &mut AppContext, f: F) -> Task<Result<Option<R>>>
-    where
-        F: 'static + FnOnce(AsyncAppContext) -> Fut,
-        Fut: Future<Output = Result<R>>,
-        R: 'static,
-    {
-        let (tx, rx) = oneshot::channel();
-        self.cancel.replace(tx);
-        cx.spawn(|cx| async move {
-            futures::select_biased! {
-                _ = rx.fuse() => Ok(None),
-                result = f(cx).fuse() => result.map(Some),
-            }
-        })
-    }
-
-    fn running(&self) -> bool {
-        self.cancel
-            .as_ref()
-            .is_some_and(|cancel| !cancel.is_canceled())
-    }
-}
-
-#[derive(Clone)]
-pub struct IncomingCall {
-    pub room_id: u64,
-    pub calling_user: Arc<User>,
-    pub participants: Vec<Arc<User>>,
-    pub initial_project: Option<proto::ParticipantProject>,
-}
-
-/// Singleton global maintaining the user's participation in a room across workspaces.
-pub struct ActiveCall {
-    room: Option<(Model<Room>, Vec<Subscription>)>,
-    pending_room_creation: Option<Shared<Task<Result<Model<Room>, Arc<anyhow::Error>>>>>,
-    location: Option<WeakModel<Project>>,
-    _join_debouncer: OneAtATime,
-    pending_invites: HashSet<u64>,
-    incoming_call: (
-        watch::Sender<Option<IncomingCall>>,
-        watch::Receiver<Option<IncomingCall>>,
-    ),
-    client: Arc<Client>,
-    user_store: Model<UserStore>,
-    _subscriptions: Vec<client::Subscription>,
-}
-
-impl EventEmitter<Event> for ActiveCall {}
-
-impl ActiveCall {
-    fn new(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut ModelContext<Self>) -> Self {
-        Self {
-            room: None,
-            pending_room_creation: None,
-            location: None,
-            pending_invites: Default::default(),
-            incoming_call: watch::channel(),
-            _join_debouncer: OneAtATime { cancel: None },
-            _subscriptions: vec![
-                client.add_request_handler(cx.weak_model(), Self::handle_incoming_call),
-                client.add_message_handler(cx.weak_model(), Self::handle_call_canceled),
-            ],
-            client,
-            user_store,
-        }
-    }
-
-    pub fn channel_id(&self, cx: &AppContext) -> Option<u64> {
-        self.room()?.read(cx).channel_id()
-    }
-
-    async fn handle_incoming_call(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::IncomingCall>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::Ack> {
-        let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
-        let call = IncomingCall {
-            room_id: envelope.payload.room_id,
-            participants: user_store
-                .update(&mut cx, |user_store, cx| {
-                    user_store.get_users(envelope.payload.participant_user_ids, cx)
-                })?
-                .await?,
-            calling_user: user_store
-                .update(&mut cx, |user_store, cx| {
-                    user_store.get_user(envelope.payload.calling_user_id, cx)
-                })?
-                .await?,
-            initial_project: envelope.payload.initial_project,
-        };
-        this.update(&mut cx, |this, _| {
-            *this.incoming_call.0.borrow_mut() = Some(call);
-        })?;
-
-        Ok(proto::Ack {})
-    }
-
-    async fn handle_call_canceled(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::CallCanceled>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        this.update(&mut cx, |this, _| {
-            let mut incoming_call = this.incoming_call.0.borrow_mut();
-            if incoming_call
-                .as_ref()
-                .map_or(false, |call| call.room_id == envelope.payload.room_id)
-            {
-                incoming_call.take();
-            }
-        })?;
-        Ok(())
-    }
-
-    pub fn global(cx: &AppContext) -> Model<Self> {
-        cx.global::<Model<Self>>().clone()
-    }
-
-    pub fn invite(
-        &mut self,
-        called_user_id: u64,
-        initial_project: Option<Model<Project>>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        if !self.pending_invites.insert(called_user_id) {
-            return Task::ready(Err(anyhow!("user was already invited")));
-        }
-        cx.notify();
-
-        if self._join_debouncer.running() {
-            return Task::ready(Ok(()));
-        }
-
-        let room = if let Some(room) = self.room().cloned() {
-            Some(Task::ready(Ok(room)).shared())
-        } else {
-            self.pending_room_creation.clone()
-        };
-
-        let invite = if let Some(room) = room {
-            cx.spawn(move |_, mut cx| async move {
-                let room = room.await.map_err(|err| anyhow!("{:?}", err))?;
-
-                let initial_project_id = if let Some(initial_project) = initial_project {
-                    Some(
-                        room.update(&mut cx, |room, cx| room.share_project(initial_project, cx))?
-                            .await?,
-                    )
-                } else {
-                    None
-                };
-
-                room.update(&mut cx, move |room, cx| {
-                    room.call(called_user_id, initial_project_id, cx)
-                })?
-                .await?;
-
-                anyhow::Ok(())
-            })
-        } else {
-            let client = self.client.clone();
-            let user_store = self.user_store.clone();
-            let room = cx
-                .spawn(move |this, mut cx| async move {
-                    let create_room = async {
-                        let room = cx
-                            .update(|cx| {
-                                Room::create(
-                                    called_user_id,
-                                    initial_project,
-                                    client,
-                                    user_store,
-                                    cx,
-                                )
-                            })?
-                            .await?;
-
-                        this.update(&mut cx, |this, cx| this.set_room(Some(room.clone()), cx))?
-                            .await?;
-
-                        anyhow::Ok(room)
-                    };
-
-                    let room = create_room.await;
-                    this.update(&mut cx, |this, _| this.pending_room_creation = None)?;
-                    room.map_err(Arc::new)
-                })
-                .shared();
-            self.pending_room_creation = Some(room.clone());
-            cx.background_executor().spawn(async move {
-                room.await.map_err(|err| anyhow!("{:?}", err))?;
-                anyhow::Ok(())
-            })
-        };
-
-        cx.spawn(move |this, mut cx| async move {
-            let result = invite.await;
-            if result.is_ok() {
-                this.update(&mut cx, |this, cx| this.report_call_event("invite", cx))?;
-            } else {
-                // TODO: Resport collaboration error
-            }
-
-            this.update(&mut cx, |this, cx| {
-                this.pending_invites.remove(&called_user_id);
-                cx.notify();
-            })?;
-            result
-        })
-    }
-
-    pub fn cancel_invite(
-        &mut self,
-        called_user_id: u64,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        let room_id = if let Some(room) = self.room() {
-            room.read(cx).id()
-        } else {
-            return Task::ready(Err(anyhow!("no active call")));
-        };
-
-        let client = self.client.clone();
-        cx.background_executor().spawn(async move {
-            client
-                .request(proto::CancelCall {
-                    room_id,
-                    called_user_id,
-                })
-                .await?;
-            anyhow::Ok(())
-        })
-    }
-
-    pub fn incoming(&self) -> watch::Receiver<Option<IncomingCall>> {
-        self.incoming_call.1.clone()
-    }
-
-    pub fn accept_incoming(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
-        if self.room.is_some() {
-            return Task::ready(Err(anyhow!("cannot join while on another call")));
-        }
-
-        let call = if let Some(call) = self.incoming_call.1.borrow().clone() {
-            call
-        } else {
-            return Task::ready(Err(anyhow!("no incoming call")));
-        };
-
-        if self.pending_room_creation.is_some() {
-            return Task::ready(Ok(()));
-        }
-
-        let room_id = call.room_id.clone();
-        let client = self.client.clone();
-        let user_store = self.user_store.clone();
-        let join = self
-            ._join_debouncer
-            .spawn(cx, move |cx| Room::join(room_id, client, user_store, cx));
-
-        cx.spawn(|this, mut cx| async move {
-            let room = join.await?;
-            this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))?
-                .await?;
-            this.update(&mut cx, |this, cx| {
-                this.report_call_event("accept incoming", cx)
-            })?;
-            Ok(())
-        })
-    }
-
-    pub fn decline_incoming(&mut self, cx: &mut ModelContext<Self>) -> Result<()> {
-        let call = self
-            .incoming_call
-            .0
-            .borrow_mut()
-            .take()
-            .ok_or_else(|| anyhow!("no incoming call"))?;
-        report_call_event_for_room("decline incoming", call.room_id, None, &self.client, cx);
-        self.client.send(proto::DeclineCall {
-            room_id: call.room_id,
-        })?;
-        Ok(())
-    }
-
-    pub fn join_channel(
-        &mut self,
-        channel_id: u64,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Option<Model<Room>>>> {
-        if let Some(room) = self.room().cloned() {
-            if room.read(cx).channel_id() == Some(channel_id) {
-                return Task::ready(Ok(Some(room)));
-            } else {
-                room.update(cx, |room, cx| room.clear_state(cx));
-            }
-        }
-
-        if self.pending_room_creation.is_some() {
-            return Task::ready(Ok(None));
-        }
-
-        let client = self.client.clone();
-        let user_store = self.user_store.clone();
-        let join = self._join_debouncer.spawn(cx, move |cx| async move {
-            Room::join_channel(channel_id, client, user_store, cx).await
-        });
-
-        cx.spawn(|this, mut cx| async move {
-            let room = join.await?;
-            this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))?
-                .await?;
-            this.update(&mut cx, |this, cx| {
-                this.report_call_event("join channel", cx)
-            })?;
-            Ok(room)
-        })
-    }
-
-    pub fn hang_up(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
-        cx.notify();
-        self.report_call_event("hang up", cx);
-
-        Audio::end_call(cx);
-        if let Some((room, _)) = self.room.take() {
-            room.update(cx, |room, cx| room.leave(cx))
-        } else {
-            Task::ready(Ok(()))
-        }
-    }
-
-    pub fn share_project(
-        &mut self,
-        project: Model<Project>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<u64>> {
-        if let Some((room, _)) = self.room.as_ref() {
-            self.report_call_event("share project", cx);
-            room.update(cx, |room, cx| room.share_project(project, cx))
-        } else {
-            Task::ready(Err(anyhow!("no active call")))
-        }
-    }
-
-    pub fn unshare_project(
-        &mut self,
-        project: Model<Project>,
-        cx: &mut ModelContext<Self>,
-    ) -> Result<()> {
-        if let Some((room, _)) = self.room.as_ref() {
-            self.report_call_event("unshare project", cx);
-            room.update(cx, |room, cx| room.unshare_project(project, cx))
-        } else {
-            Err(anyhow!("no active call"))
-        }
-    }
-
-    pub fn location(&self) -> Option<&WeakModel<Project>> {
-        self.location.as_ref()
-    }
-
-    pub fn set_location(
-        &mut self,
-        project: Option<&Model<Project>>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        if project.is_some() || !*ZED_ALWAYS_ACTIVE {
-            self.location = project.map(|project| project.downgrade());
-            if let Some((room, _)) = self.room.as_ref() {
-                return room.update(cx, |room, cx| room.set_location(project, cx));
-            }
-        }
-        Task::ready(Ok(()))
-    }
-
-    fn set_room(
-        &mut self,
-        room: Option<Model<Room>>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        if room.as_ref() != self.room.as_ref().map(|room| &room.0) {
-            cx.notify();
-            if let Some(room) = room {
-                if room.read(cx).status().is_offline() {
-                    self.room = None;
-                    Task::ready(Ok(()))
-                } else {
-                    let subscriptions = vec![
-                        cx.observe(&room, |this, room, cx| {
-                            if room.read(cx).status().is_offline() {
-                                this.set_room(None, cx).detach_and_log_err(cx);
-                            }
-
-                            cx.notify();
-                        }),
-                        cx.subscribe(&room, |_, _, event, cx| cx.emit(event.clone())),
-                    ];
-                    self.room = Some((room.clone(), subscriptions));
-                    let location = self
-                        .location
-                        .as_ref()
-                        .and_then(|location| location.upgrade());
-                    room.update(cx, |room, cx| room.set_location(location.as_ref(), cx))
-                }
-            } else {
-                self.room = None;
-                Task::ready(Ok(()))
-            }
-        } else {
-            Task::ready(Ok(()))
-        }
-    }
-
-    pub fn room(&self) -> Option<&Model<Room>> {
-        self.room.as_ref().map(|(room, _)| room)
-    }
-
-    pub fn client(&self) -> Arc<Client> {
-        self.client.clone()
-    }
-
-    pub fn pending_invites(&self) -> &HashSet<u64> {
-        &self.pending_invites
-    }
-
-    pub fn report_call_event(&self, operation: &'static str, cx: &mut AppContext) {
-        if let Some(room) = self.room() {
-            let room = room.read(cx);
-            report_call_event_for_room(operation, room.id(), room.channel_id(), &self.client, cx);
-        }
-    }
-}
-
-pub fn report_call_event_for_room(
-    operation: &'static str,
-    room_id: u64,
-    channel_id: Option<u64>,
-    client: &Arc<Client>,
-    cx: &mut AppContext,
-) {
-    let telemetry = client.telemetry();
-    let telemetry_settings = *TelemetrySettings::get_global(cx);
-
-    telemetry.report_call_event(telemetry_settings, operation, Some(room_id), channel_id)
-}
-
-pub fn report_call_event_for_channel(
-    operation: &'static str,
-    channel_id: u64,
-    client: &Arc<Client>,
-    cx: &AppContext,
-) {
-    let room = ActiveCall::global(cx).read(cx).room();
-
-    let telemetry = client.telemetry();
-
-    let telemetry_settings = *TelemetrySettings::get_global(cx);
-
-    telemetry.report_call_event(
-        telemetry_settings,
-        operation,
-        room.map(|r| r.read(cx).id()),
-        Some(channel_id),
-    )
-}
-
-#[cfg(test)]
-mod test {
-    use gpui::TestAppContext;
-
-    use crate::OneAtATime;
-
-    #[gpui::test]
-    async fn test_one_at_a_time(cx: &mut TestAppContext) {
-        let mut one_at_a_time = OneAtATime { cancel: None };
-
-        assert_eq!(
-            cx.update(|cx| one_at_a_time.spawn(cx, |_| async { Ok(1) }))
-                .await
-                .unwrap(),
-            Some(1)
-        );
-
-        let (a, b) = cx.update(|cx| {
-            (
-                one_at_a_time.spawn(cx, |_| async {
-                    assert!(false);
-                    Ok(2)
-                }),
-                one_at_a_time.spawn(cx, |_| async { Ok(3) }),
-            )
-        });
-
-        assert_eq!(a.await.unwrap(), None);
-        assert_eq!(b.await.unwrap(), Some(3));
-
-        let promise = cx.update(|cx| one_at_a_time.spawn(cx, |_| async { Ok(4) }));
-        drop(one_at_a_time);
-
-        assert_eq!(promise.await.unwrap(), None);
-    }
-}

crates/call2/src/call_settings.rs 🔗

@@ -1,32 +0,0 @@
-use anyhow::Result;
-use gpui::AppContext;
-use schemars::JsonSchema;
-use serde_derive::{Deserialize, Serialize};
-use settings::Settings;
-
-#[derive(Deserialize, Debug)]
-pub struct CallSettings {
-    pub mute_on_join: bool,
-}
-
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
-pub struct CallSettingsContent {
-    pub mute_on_join: Option<bool>,
-}
-
-impl Settings for CallSettings {
-    const KEY: Option<&'static str> = Some("calls");
-
-    type FileContent = CallSettingsContent;
-
-    fn load(
-        default_value: &Self::FileContent,
-        user_values: &[&Self::FileContent],
-        _cx: &mut AppContext,
-    ) -> Result<Self>
-    where
-        Self: Sized,
-    {
-        Self::load_via_json_merge(default_value, user_values)
-    }
-}

crates/call2/src/participant.rs 🔗

@@ -1,52 +0,0 @@
-use anyhow::{anyhow, Result};
-use client::ParticipantIndex;
-use client::{proto, User};
-use collections::HashMap;
-use gpui::WeakModel;
-pub use live_kit_client::Frame;
-pub use live_kit_client::{RemoteAudioTrack, RemoteVideoTrack};
-use project::Project;
-use std::sync::Arc;
-
-#[derive(Copy, Clone, Debug, Eq, PartialEq)]
-pub enum ParticipantLocation {
-    SharedProject { project_id: u64 },
-    UnsharedProject,
-    External,
-}
-
-impl ParticipantLocation {
-    pub fn from_proto(location: Option<proto::ParticipantLocation>) -> Result<Self> {
-        match location.and_then(|l| l.variant) {
-            Some(proto::participant_location::Variant::SharedProject(project)) => {
-                Ok(Self::SharedProject {
-                    project_id: project.id,
-                })
-            }
-            Some(proto::participant_location::Variant::UnsharedProject(_)) => {
-                Ok(Self::UnsharedProject)
-            }
-            Some(proto::participant_location::Variant::External(_)) => Ok(Self::External),
-            None => Err(anyhow!("participant location was not provided")),
-        }
-    }
-}
-
-#[derive(Clone, Default)]
-pub struct LocalParticipant {
-    pub projects: Vec<proto::ParticipantProject>,
-    pub active_project: Option<WeakModel<Project>>,
-}
-
-#[derive(Clone, Debug)]
-pub struct RemoteParticipant {
-    pub user: Arc<User>,
-    pub peer_id: proto::PeerId,
-    pub projects: Vec<proto::ParticipantProject>,
-    pub location: ParticipantLocation,
-    pub participant_index: ParticipantIndex,
-    pub muted: bool,
-    pub speaking: bool,
-    pub video_tracks: HashMap<live_kit_client::Sid, Arc<RemoteVideoTrack>>,
-    pub audio_tracks: HashMap<live_kit_client::Sid, Arc<RemoteAudioTrack>>,
-}

crates/call2/src/room.rs 🔗

@@ -1,1599 +0,0 @@
-use crate::{
-    call_settings::CallSettings,
-    participant::{LocalParticipant, ParticipantLocation, RemoteParticipant},
-};
-use anyhow::{anyhow, Result};
-use audio::{Audio, Sound};
-use client::{
-    proto::{self, PeerId},
-    Client, ParticipantIndex, TypedEnvelope, User, UserStore,
-};
-use collections::{BTreeMap, HashMap, HashSet};
-use fs::Fs;
-use futures::{FutureExt, StreamExt};
-use gpui::{
-    AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel,
-};
-use language::LanguageRegistry;
-use live_kit_client::{
-    LocalAudioTrack, LocalTrackPublication, LocalVideoTrack, RemoteAudioTrackUpdate,
-    RemoteVideoTrackUpdate,
-};
-use postage::{sink::Sink, stream::Stream, watch};
-use project::Project;
-use settings::Settings as _;
-use std::{future::Future, mem, sync::Arc, time::Duration};
-use util::{post_inc, ResultExt, TryFutureExt};
-
-pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub enum Event {
-    ParticipantLocationChanged {
-        participant_id: proto::PeerId,
-    },
-    RemoteVideoTracksChanged {
-        participant_id: proto::PeerId,
-    },
-    RemoteAudioTracksChanged {
-        participant_id: proto::PeerId,
-    },
-    RemoteProjectShared {
-        owner: Arc<User>,
-        project_id: u64,
-        worktree_root_names: Vec<String>,
-    },
-    RemoteProjectUnshared {
-        project_id: u64,
-    },
-    RemoteProjectJoined {
-        project_id: u64,
-    },
-    RemoteProjectInvitationDiscarded {
-        project_id: u64,
-    },
-    Left,
-}
-
-pub struct Room {
-    id: u64,
-    channel_id: Option<u64>,
-    live_kit: Option<LiveKitRoom>,
-    status: RoomStatus,
-    shared_projects: HashSet<WeakModel<Project>>,
-    joined_projects: HashSet<WeakModel<Project>>,
-    local_participant: LocalParticipant,
-    remote_participants: BTreeMap<u64, RemoteParticipant>,
-    pending_participants: Vec<Arc<User>>,
-    participant_user_ids: HashSet<u64>,
-    pending_call_count: usize,
-    leave_when_empty: bool,
-    client: Arc<Client>,
-    user_store: Model<UserStore>,
-    follows_by_leader_id_project_id: HashMap<(PeerId, u64), Vec<PeerId>>,
-    client_subscriptions: Vec<client::Subscription>,
-    _subscriptions: Vec<gpui::Subscription>,
-    room_update_completed_tx: watch::Sender<Option<()>>,
-    room_update_completed_rx: watch::Receiver<Option<()>>,
-    pending_room_update: Option<Task<()>>,
-    maintain_connection: Option<Task<Option<()>>>,
-}
-
-impl EventEmitter<Event> for Room {}
-
-impl Room {
-    pub fn channel_id(&self) -> Option<u64> {
-        self.channel_id
-    }
-
-    pub fn is_sharing_project(&self) -> bool {
-        !self.shared_projects.is_empty()
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn is_connected(&self) -> bool {
-        if let Some(live_kit) = self.live_kit.as_ref() {
-            matches!(
-                *live_kit.room.status().borrow(),
-                live_kit_client::ConnectionState::Connected { .. }
-            )
-        } else {
-            false
-        }
-    }
-
-    fn new(
-        id: u64,
-        channel_id: Option<u64>,
-        live_kit_connection_info: Option<proto::LiveKitConnectionInfo>,
-        client: Arc<Client>,
-        user_store: Model<UserStore>,
-        cx: &mut ModelContext<Self>,
-    ) -> Self {
-        let live_kit_room = if let Some(connection_info) = live_kit_connection_info {
-            let room = live_kit_client::Room::new();
-            let mut status = room.status();
-            // Consume the initial status of the room.
-            let _ = status.try_recv();
-            let _maintain_room = cx.spawn(|this, mut cx| async move {
-                while let Some(status) = status.next().await {
-                    let this = if let Some(this) = this.upgrade() {
-                        this
-                    } else {
-                        break;
-                    };
-
-                    if status == live_kit_client::ConnectionState::Disconnected {
-                        this.update(&mut cx, |this, cx| this.leave(cx).log_err())
-                            .ok();
-                        break;
-                    }
-                }
-            });
-
-            let _maintain_video_tracks = cx.spawn({
-                let room = room.clone();
-                move |this, mut cx| async move {
-                    let mut track_video_changes = room.remote_video_track_updates();
-                    while let Some(track_change) = track_video_changes.next().await {
-                        let this = if let Some(this) = this.upgrade() {
-                            this
-                        } else {
-                            break;
-                        };
-
-                        this.update(&mut cx, |this, cx| {
-                            this.remote_video_track_updated(track_change, cx).log_err()
-                        })
-                        .ok();
-                    }
-                }
-            });
-
-            let _maintain_audio_tracks = cx.spawn({
-                let room = room.clone();
-                |this, mut cx| async move {
-                    let mut track_audio_changes = room.remote_audio_track_updates();
-                    while let Some(track_change) = track_audio_changes.next().await {
-                        let this = if let Some(this) = this.upgrade() {
-                            this
-                        } else {
-                            break;
-                        };
-
-                        this.update(&mut cx, |this, cx| {
-                            this.remote_audio_track_updated(track_change, cx).log_err()
-                        })
-                        .ok();
-                    }
-                }
-            });
-
-            let connect = room.connect(&connection_info.server_url, &connection_info.token);
-            cx.spawn(|this, mut cx| async move {
-                connect.await?;
-
-                if !cx.update(|cx| Self::mute_on_join(cx))? {
-                    this.update(&mut cx, |this, cx| this.share_microphone(cx))?
-                        .await?;
-                }
-
-                anyhow::Ok(())
-            })
-            .detach_and_log_err(cx);
-
-            Some(LiveKitRoom {
-                room,
-                screen_track: LocalTrack::None,
-                microphone_track: LocalTrack::None,
-                next_publish_id: 0,
-                muted_by_user: false,
-                deafened: false,
-                speaking: false,
-                _maintain_room,
-                _maintain_tracks: [_maintain_video_tracks, _maintain_audio_tracks],
-            })
-        } else {
-            None
-        };
-
-        let maintain_connection = cx.spawn({
-            let client = client.clone();
-            move |this, cx| Self::maintain_connection(this, client.clone(), cx).log_err()
-        });
-
-        Audio::play_sound(Sound::Joined, cx);
-
-        let (room_update_completed_tx, room_update_completed_rx) = watch::channel();
-
-        Self {
-            id,
-            channel_id,
-            live_kit: live_kit_room,
-            status: RoomStatus::Online,
-            shared_projects: Default::default(),
-            joined_projects: Default::default(),
-            participant_user_ids: Default::default(),
-            local_participant: Default::default(),
-            remote_participants: Default::default(),
-            pending_participants: Default::default(),
-            pending_call_count: 0,
-            client_subscriptions: vec![
-                client.add_message_handler(cx.weak_model(), Self::handle_room_updated)
-            ],
-            _subscriptions: vec![
-                cx.on_release(Self::released),
-                cx.on_app_quit(Self::app_will_quit),
-            ],
-            leave_when_empty: false,
-            pending_room_update: None,
-            client,
-            user_store,
-            follows_by_leader_id_project_id: Default::default(),
-            maintain_connection: Some(maintain_connection),
-            room_update_completed_tx,
-            room_update_completed_rx,
-        }
-    }
-
-    pub(crate) fn create(
-        called_user_id: u64,
-        initial_project: Option<Model<Project>>,
-        client: Arc<Client>,
-        user_store: Model<UserStore>,
-        cx: &mut AppContext,
-    ) -> Task<Result<Model<Self>>> {
-        cx.spawn(move |mut cx| async move {
-            let response = client.request(proto::CreateRoom {}).await?;
-            let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
-            let room = cx.new_model(|cx| {
-                Self::new(
-                    room_proto.id,
-                    None,
-                    response.live_kit_connection_info,
-                    client,
-                    user_store,
-                    cx,
-                )
-            })?;
-
-            let initial_project_id = if let Some(initial_project) = initial_project {
-                let initial_project_id = room
-                    .update(&mut cx, |room, cx| {
-                        room.share_project(initial_project.clone(), cx)
-                    })?
-                    .await?;
-                Some(initial_project_id)
-            } else {
-                None
-            };
-
-            match room
-                .update(&mut cx, |room, cx| {
-                    room.leave_when_empty = true;
-                    room.call(called_user_id, initial_project_id, cx)
-                })?
-                .await
-            {
-                Ok(()) => Ok(room),
-                Err(error) => Err(anyhow!("room creation failed: {:?}", error)),
-            }
-        })
-    }
-
-    pub(crate) async fn join_channel(
-        channel_id: u64,
-        client: Arc<Client>,
-        user_store: Model<UserStore>,
-        cx: AsyncAppContext,
-    ) -> Result<Model<Self>> {
-        Self::from_join_response(
-            client.request(proto::JoinChannel { channel_id }).await?,
-            client,
-            user_store,
-            cx,
-        )
-    }
-
-    pub(crate) async fn join(
-        room_id: u64,
-        client: Arc<Client>,
-        user_store: Model<UserStore>,
-        cx: AsyncAppContext,
-    ) -> Result<Model<Self>> {
-        Self::from_join_response(
-            client.request(proto::JoinRoom { id: room_id }).await?,
-            client,
-            user_store,
-            cx,
-        )
-    }
-
-    fn released(&mut self, cx: &mut AppContext) {
-        if self.status.is_online() {
-            self.leave_internal(cx).detach_and_log_err(cx);
-        }
-    }
-
-    fn app_will_quit(&mut self, cx: &mut ModelContext<Self>) -> impl Future<Output = ()> {
-        let task = if self.status.is_online() {
-            let leave = self.leave_internal(cx);
-            Some(cx.background_executor().spawn(async move {
-                leave.await.log_err();
-            }))
-        } else {
-            None
-        };
-
-        async move {
-            if let Some(task) = task {
-                task.await;
-            }
-        }
-    }
-
-    pub fn mute_on_join(cx: &AppContext) -> bool {
-        CallSettings::get_global(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some()
-    }
-
-    fn from_join_response(
-        response: proto::JoinRoomResponse,
-        client: Arc<Client>,
-        user_store: Model<UserStore>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Model<Self>> {
-        let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
-        let room = cx.new_model(|cx| {
-            Self::new(
-                room_proto.id,
-                response.channel_id,
-                response.live_kit_connection_info,
-                client,
-                user_store,
-                cx,
-            )
-        })?;
-        room.update(&mut cx, |room, cx| {
-            room.leave_when_empty = room.channel_id.is_none();
-            room.apply_room_update(room_proto, cx)?;
-            anyhow::Ok(())
-        })??;
-        Ok(room)
-    }
-
-    fn should_leave(&self) -> bool {
-        self.leave_when_empty
-            && self.pending_room_update.is_none()
-            && self.pending_participants.is_empty()
-            && self.remote_participants.is_empty()
-            && self.pending_call_count == 0
-    }
-
-    pub(crate) fn leave(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
-        cx.notify();
-        cx.emit(Event::Left);
-        self.leave_internal(cx)
-    }
-
-    fn leave_internal(&mut self, cx: &mut AppContext) -> Task<Result<()>> {
-        if self.status.is_offline() {
-            return Task::ready(Err(anyhow!("room is offline")));
-        }
-
-        log::info!("leaving room");
-        Audio::play_sound(Sound::Leave, cx);
-
-        self.clear_state(cx);
-
-        let leave_room = self.client.request(proto::LeaveRoom {});
-        cx.background_executor().spawn(async move {
-            leave_room.await?;
-            anyhow::Ok(())
-        })
-    }
-
-    pub(crate) fn clear_state(&mut self, cx: &mut AppContext) {
-        for project in self.shared_projects.drain() {
-            if let Some(project) = project.upgrade() {
-                project.update(cx, |project, cx| {
-                    project.unshare(cx).log_err();
-                });
-            }
-        }
-        for project in self.joined_projects.drain() {
-            if let Some(project) = project.upgrade() {
-                project.update(cx, |project, cx| {
-                    project.disconnected_from_host(cx);
-                    project.close(cx);
-                });
-            }
-        }
-
-        self.status = RoomStatus::Offline;
-        self.remote_participants.clear();
-        self.pending_participants.clear();
-        self.participant_user_ids.clear();
-        self.client_subscriptions.clear();
-        self.live_kit.take();
-        self.pending_room_update.take();
-        self.maintain_connection.take();
-    }
-
-    async fn maintain_connection(
-        this: WeakModel<Self>,
-        client: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        let mut client_status = client.status();
-        loop {
-            let _ = client_status.try_recv();
-            let is_connected = client_status.borrow().is_connected();
-            // Even if we're initially connected, any future change of the status means we momentarily disconnected.
-            if !is_connected || client_status.next().await.is_some() {
-                log::info!("detected client disconnection");
-
-                this.upgrade()
-                    .ok_or_else(|| anyhow!("room was dropped"))?
-                    .update(&mut cx, |this, cx| {
-                        this.status = RoomStatus::Rejoining;
-                        cx.notify();
-                    })?;
-
-                // Wait for client to re-establish a connection to the server.
-                {
-                    let mut reconnection_timeout =
-                        cx.background_executor().timer(RECONNECT_TIMEOUT).fuse();
-                    let client_reconnection = async {
-                        let mut remaining_attempts = 3;
-                        while remaining_attempts > 0 {
-                            if client_status.borrow().is_connected() {
-                                log::info!("client reconnected, attempting to rejoin room");
-
-                                let Some(this) = this.upgrade() else { break };
-                                match this.update(&mut cx, |this, cx| this.rejoin(cx)) {
-                                    Ok(task) => {
-                                        if task.await.log_err().is_some() {
-                                            return true;
-                                        } else {
-                                            remaining_attempts -= 1;
-                                        }
-                                    }
-                                    Err(_app_dropped) => return false,
-                                }
-                            } else if client_status.borrow().is_signed_out() {
-                                return false;
-                            }
-
-                            log::info!(
-                                "waiting for client status change, remaining attempts {}",
-                                remaining_attempts
-                            );
-                            client_status.next().await;
-                        }
-                        false
-                    }
-                    .fuse();
-                    futures::pin_mut!(client_reconnection);
-
-                    futures::select_biased! {
-                        reconnected = client_reconnection => {
-                            if reconnected {
-                                log::info!("successfully reconnected to room");
-                                // If we successfully joined the room, go back around the loop
-                                // waiting for future connection status changes.
-                                continue;
-                            }
-                        }
-                        _ = reconnection_timeout => {
-                            log::info!("room reconnection timeout expired");
-                        }
-                    }
-                }
-
-                break;
-            }
-        }
-
-        // The client failed to re-establish a connection to the server
-        // or an error occurred while trying to re-join the room. Either way
-        // we leave the room and return an error.
-        if let Some(this) = this.upgrade() {
-            log::info!("reconnection failed, leaving room");
-            let _ = this.update(&mut cx, |this, cx| this.leave(cx))?;
-        }
-        Err(anyhow!(
-            "can't reconnect to room: client failed to re-establish connection"
-        ))
-    }
-
-    fn rejoin(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
-        let mut projects = HashMap::default();
-        let mut reshared_projects = Vec::new();
-        let mut rejoined_projects = Vec::new();
-        self.shared_projects.retain(|project| {
-            if let Some(handle) = project.upgrade() {
-                let project = handle.read(cx);
-                if let Some(project_id) = project.remote_id() {
-                    projects.insert(project_id, handle.clone());
-                    reshared_projects.push(proto::UpdateProject {
-                        project_id,
-                        worktrees: project.worktree_metadata_protos(cx),
-                    });
-                    return true;
-                }
-            }
-            false
-        });
-        self.joined_projects.retain(|project| {
-            if let Some(handle) = project.upgrade() {
-                let project = handle.read(cx);
-                if let Some(project_id) = project.remote_id() {
-                    projects.insert(project_id, handle.clone());
-                    rejoined_projects.push(proto::RejoinProject {
-                        id: project_id,
-                        worktrees: project
-                            .worktrees()
-                            .map(|worktree| {
-                                let worktree = worktree.read(cx);
-                                proto::RejoinWorktree {
-                                    id: worktree.id().to_proto(),
-                                    scan_id: worktree.completed_scan_id() as u64,
-                                }
-                            })
-                            .collect(),
-                    });
-                }
-                return true;
-            }
-            false
-        });
-
-        let response = self.client.request_envelope(proto::RejoinRoom {
-            id: self.id,
-            reshared_projects,
-            rejoined_projects,
-        });
-
-        cx.spawn(|this, mut cx| async move {
-            let response = response.await?;
-            let message_id = response.message_id;
-            let response = response.payload;
-            let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
-            this.update(&mut cx, |this, cx| {
-                this.status = RoomStatus::Online;
-                this.apply_room_update(room_proto, cx)?;
-
-                for reshared_project in response.reshared_projects {
-                    if let Some(project) = projects.get(&reshared_project.id) {
-                        project.update(cx, |project, cx| {
-                            project.reshared(reshared_project, cx).log_err();
-                        });
-                    }
-                }
-
-                for rejoined_project in response.rejoined_projects {
-                    if let Some(project) = projects.get(&rejoined_project.id) {
-                        project.update(cx, |project, cx| {
-                            project.rejoined(rejoined_project, message_id, cx).log_err();
-                        });
-                    }
-                }
-
-                anyhow::Ok(())
-            })?
-        })
-    }
-
-    pub fn id(&self) -> u64 {
-        self.id
-    }
-
-    pub fn status(&self) -> RoomStatus {
-        self.status
-    }
-
-    pub fn local_participant(&self) -> &LocalParticipant {
-        &self.local_participant
-    }
-
-    pub fn remote_participants(&self) -> &BTreeMap<u64, RemoteParticipant> {
-        &self.remote_participants
-    }
-
-    pub fn remote_participant_for_peer_id(&self, peer_id: PeerId) -> Option<&RemoteParticipant> {
-        self.remote_participants
-            .values()
-            .find(|p| p.peer_id == peer_id)
-    }
-
-    pub fn pending_participants(&self) -> &[Arc<User>] {
-        &self.pending_participants
-    }
-
-    pub fn contains_participant(&self, user_id: u64) -> bool {
-        self.participant_user_ids.contains(&user_id)
-    }
-
-    pub fn followers_for(&self, leader_id: PeerId, project_id: u64) -> &[PeerId] {
-        self.follows_by_leader_id_project_id
-            .get(&(leader_id, project_id))
-            .map_or(&[], |v| v.as_slice())
-    }
-
-    /// Returns the most 'active' projects, defined as most people in the project
-    pub fn most_active_project(&self, cx: &AppContext) -> Option<(u64, u64)> {
-        let mut project_hosts_and_guest_counts = HashMap::<u64, (Option<u64>, u32)>::default();
-        for participant in self.remote_participants.values() {
-            match participant.location {
-                ParticipantLocation::SharedProject { project_id } => {
-                    project_hosts_and_guest_counts
-                        .entry(project_id)
-                        .or_default()
-                        .1 += 1;
-                }
-                ParticipantLocation::External | ParticipantLocation::UnsharedProject => {}
-            }
-            for project in &participant.projects {
-                project_hosts_and_guest_counts
-                    .entry(project.id)
-                    .or_default()
-                    .0 = Some(participant.user.id);
-            }
-        }
-
-        if let Some(user) = self.user_store.read(cx).current_user() {
-            for project in &self.local_participant.projects {
-                project_hosts_and_guest_counts
-                    .entry(project.id)
-                    .or_default()
-                    .0 = Some(user.id);
-            }
-        }
-
-        project_hosts_and_guest_counts
-            .into_iter()
-            .filter_map(|(id, (host, guest_count))| Some((id, host?, guest_count)))
-            .max_by_key(|(_, _, guest_count)| *guest_count)
-            .map(|(id, host, _)| (id, host))
-    }
-
-    async fn handle_room_updated(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::RoomUpdated>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        let room = envelope
-            .payload
-            .room
-            .ok_or_else(|| anyhow!("invalid room"))?;
-        this.update(&mut cx, |this, cx| this.apply_room_update(room, cx))?
-    }
-
-    fn apply_room_update(
-        &mut self,
-        mut room: proto::Room,
-        cx: &mut ModelContext<Self>,
-    ) -> Result<()> {
-        // Filter ourselves out from the room's participants.
-        let local_participant_ix = room
-            .participants
-            .iter()
-            .position(|participant| Some(participant.user_id) == self.client.user_id());
-        let local_participant = local_participant_ix.map(|ix| room.participants.swap_remove(ix));
-
-        let pending_participant_user_ids = room
-            .pending_participants
-            .iter()
-            .map(|p| p.user_id)
-            .collect::<Vec<_>>();
-
-        let remote_participant_user_ids = room
-            .participants
-            .iter()
-            .map(|p| p.user_id)
-            .collect::<Vec<_>>();
-
-        let (remote_participants, pending_participants) =
-            self.user_store.update(cx, move |user_store, cx| {
-                (
-                    user_store.get_users(remote_participant_user_ids, cx),
-                    user_store.get_users(pending_participant_user_ids, cx),
-                )
-            });
-
-        self.pending_room_update = Some(cx.spawn(|this, mut cx| async move {
-            let (remote_participants, pending_participants) =
-                futures::join!(remote_participants, pending_participants);
-
-            this.update(&mut cx, |this, cx| {
-                this.participant_user_ids.clear();
-
-                if let Some(participant) = local_participant {
-                    this.local_participant.projects = participant.projects;
-                } else {
-                    this.local_participant.projects.clear();
-                }
-
-                if let Some(participants) = remote_participants.log_err() {
-                    for (participant, user) in room.participants.into_iter().zip(participants) {
-                        let Some(peer_id) = participant.peer_id else {
-                            continue;
-                        };
-                        let participant_index = ParticipantIndex(participant.participant_index);
-                        this.participant_user_ids.insert(participant.user_id);
-
-                        let old_projects = this
-                            .remote_participants
-                            .get(&participant.user_id)
-                            .into_iter()
-                            .flat_map(|existing| &existing.projects)
-                            .map(|project| project.id)
-                            .collect::<HashSet<_>>();
-                        let new_projects = participant
-                            .projects
-                            .iter()
-                            .map(|project| project.id)
-                            .collect::<HashSet<_>>();
-
-                        for project in &participant.projects {
-                            if !old_projects.contains(&project.id) {
-                                cx.emit(Event::RemoteProjectShared {
-                                    owner: user.clone(),
-                                    project_id: project.id,
-                                    worktree_root_names: project.worktree_root_names.clone(),
-                                });
-                            }
-                        }
-
-                        for unshared_project_id in old_projects.difference(&new_projects) {
-                            this.joined_projects.retain(|project| {
-                                if let Some(project) = project.upgrade() {
-                                    project.update(cx, |project, cx| {
-                                        if project.remote_id() == Some(*unshared_project_id) {
-                                            project.disconnected_from_host(cx);
-                                            false
-                                        } else {
-                                            true
-                                        }
-                                    })
-                                } else {
-                                    false
-                                }
-                            });
-                            cx.emit(Event::RemoteProjectUnshared {
-                                project_id: *unshared_project_id,
-                            });
-                        }
-
-                        let location = ParticipantLocation::from_proto(participant.location)
-                            .unwrap_or(ParticipantLocation::External);
-                        if let Some(remote_participant) =
-                            this.remote_participants.get_mut(&participant.user_id)
-                        {
-                            remote_participant.peer_id = peer_id;
-                            remote_participant.projects = participant.projects;
-                            remote_participant.participant_index = participant_index;
-                            if location != remote_participant.location {
-                                remote_participant.location = location;
-                                cx.emit(Event::ParticipantLocationChanged {
-                                    participant_id: peer_id,
-                                });
-                            }
-                        } else {
-                            this.remote_participants.insert(
-                                participant.user_id,
-                                RemoteParticipant {
-                                    user: user.clone(),
-                                    participant_index,
-                                    peer_id,
-                                    projects: participant.projects,
-                                    location,
-                                    muted: true,
-                                    speaking: false,
-                                    video_tracks: Default::default(),
-                                    audio_tracks: Default::default(),
-                                },
-                            );
-
-                            Audio::play_sound(Sound::Joined, cx);
-
-                            if let Some(live_kit) = this.live_kit.as_ref() {
-                                let video_tracks =
-                                    live_kit.room.remote_video_tracks(&user.id.to_string());
-                                let audio_tracks =
-                                    live_kit.room.remote_audio_tracks(&user.id.to_string());
-                                let publications = live_kit
-                                    .room
-                                    .remote_audio_track_publications(&user.id.to_string());
-
-                                for track in video_tracks {
-                                    this.remote_video_track_updated(
-                                        RemoteVideoTrackUpdate::Subscribed(track),
-                                        cx,
-                                    )
-                                    .log_err();
-                                }
-
-                                for (track, publication) in
-                                    audio_tracks.iter().zip(publications.iter())
-                                {
-                                    this.remote_audio_track_updated(
-                                        RemoteAudioTrackUpdate::Subscribed(
-                                            track.clone(),
-                                            publication.clone(),
-                                        ),
-                                        cx,
-                                    )
-                                    .log_err();
-                                }
-                            }
-                        }
-                    }
-
-                    this.remote_participants.retain(|user_id, participant| {
-                        if this.participant_user_ids.contains(user_id) {
-                            true
-                        } else {
-                            for project in &participant.projects {
-                                cx.emit(Event::RemoteProjectUnshared {
-                                    project_id: project.id,
-                                });
-                            }
-                            false
-                        }
-                    });
-                }
-
-                if let Some(pending_participants) = pending_participants.log_err() {
-                    this.pending_participants = pending_participants;
-                    for participant in &this.pending_participants {
-                        this.participant_user_ids.insert(participant.id);
-                    }
-                }
-
-                this.follows_by_leader_id_project_id.clear();
-                for follower in room.followers {
-                    let project_id = follower.project_id;
-                    let (leader, follower) = match (follower.leader_id, follower.follower_id) {
-                        (Some(leader), Some(follower)) => (leader, follower),
-
-                        _ => {
-                            log::error!("Follower message {follower:?} missing some state");
-                            continue;
-                        }
-                    };
-
-                    let list = this
-                        .follows_by_leader_id_project_id
-                        .entry((leader, project_id))
-                        .or_insert(Vec::new());
-                    if !list.contains(&follower) {
-                        list.push(follower);
-                    }
-                }
-
-                this.pending_room_update.take();
-                if this.should_leave() {
-                    log::info!("room is empty, leaving");
-                    let _ = this.leave(cx);
-                }
-
-                this.user_store.update(cx, |user_store, cx| {
-                    let participant_indices_by_user_id = this
-                        .remote_participants
-                        .iter()
-                        .map(|(user_id, participant)| (*user_id, participant.participant_index))
-                        .collect();
-                    user_store.set_participant_indices(participant_indices_by_user_id, cx);
-                });
-
-                this.check_invariants();
-                this.room_update_completed_tx.try_send(Some(())).ok();
-                cx.notify();
-            })
-            .ok();
-        }));
-
-        cx.notify();
-        Ok(())
-    }
-
-    pub fn room_update_completed(&mut self) -> impl Future<Output = ()> {
-        let mut done_rx = self.room_update_completed_rx.clone();
-        async move {
-            while let Some(result) = done_rx.next().await {
-                if result.is_some() {
-                    break;
-                }
-            }
-        }
-    }
-
-    fn remote_video_track_updated(
-        &mut self,
-        change: RemoteVideoTrackUpdate,
-        cx: &mut ModelContext<Self>,
-    ) -> Result<()> {
-        match change {
-            RemoteVideoTrackUpdate::Subscribed(track) => {
-                let user_id = track.publisher_id().parse()?;
-                let track_id = track.sid().to_string();
-                let participant = self
-                    .remote_participants
-                    .get_mut(&user_id)
-                    .ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?;
-                participant.video_tracks.insert(track_id.clone(), track);
-                cx.emit(Event::RemoteVideoTracksChanged {
-                    participant_id: participant.peer_id,
-                });
-            }
-            RemoteVideoTrackUpdate::Unsubscribed {
-                publisher_id,
-                track_id,
-            } => {
-                let user_id = publisher_id.parse()?;
-                let participant = self
-                    .remote_participants
-                    .get_mut(&user_id)
-                    .ok_or_else(|| anyhow!("unsubscribed from track by unknown participant"))?;
-                participant.video_tracks.remove(&track_id);
-                cx.emit(Event::RemoteVideoTracksChanged {
-                    participant_id: participant.peer_id,
-                });
-            }
-        }
-
-        cx.notify();
-        Ok(())
-    }
-
-    fn remote_audio_track_updated(
-        &mut self,
-        change: RemoteAudioTrackUpdate,
-        cx: &mut ModelContext<Self>,
-    ) -> Result<()> {
-        match change {
-            RemoteAudioTrackUpdate::ActiveSpeakersChanged { speakers } => {
-                let mut speaker_ids = speakers
-                    .into_iter()
-                    .filter_map(|speaker_sid| speaker_sid.parse().ok())
-                    .collect::<Vec<u64>>();
-                speaker_ids.sort_unstable();
-                for (sid, participant) in &mut self.remote_participants {
-                    if let Ok(_) = speaker_ids.binary_search(sid) {
-                        participant.speaking = true;
-                    } else {
-                        participant.speaking = false;
-                    }
-                }
-                if let Some(id) = self.client.user_id() {
-                    if let Some(room) = &mut self.live_kit {
-                        if let Ok(_) = speaker_ids.binary_search(&id) {
-                            room.speaking = true;
-                        } else {
-                            room.speaking = false;
-                        }
-                    }
-                }
-                cx.notify();
-            }
-            RemoteAudioTrackUpdate::MuteChanged { track_id, muted } => {
-                let mut found = false;
-                for participant in &mut self.remote_participants.values_mut() {
-                    for track in participant.audio_tracks.values() {
-                        if track.sid() == track_id {
-                            found = true;
-                            break;
-                        }
-                    }
-                    if found {
-                        participant.muted = muted;
-                        break;
-                    }
-                }
-
-                cx.notify();
-            }
-            RemoteAudioTrackUpdate::Subscribed(track, publication) => {
-                let user_id = track.publisher_id().parse()?;
-                let track_id = track.sid().to_string();
-                let participant = self
-                    .remote_participants
-                    .get_mut(&user_id)
-                    .ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?;
-                participant.audio_tracks.insert(track_id.clone(), track);
-                participant.muted = publication.is_muted();
-
-                cx.emit(Event::RemoteAudioTracksChanged {
-                    participant_id: participant.peer_id,
-                });
-            }
-            RemoteAudioTrackUpdate::Unsubscribed {
-                publisher_id,
-                track_id,
-            } => {
-                let user_id = publisher_id.parse()?;
-                let participant = self
-                    .remote_participants
-                    .get_mut(&user_id)
-                    .ok_or_else(|| anyhow!("unsubscribed from track by unknown participant"))?;
-                participant.audio_tracks.remove(&track_id);
-                cx.emit(Event::RemoteAudioTracksChanged {
-                    participant_id: participant.peer_id,
-                });
-            }
-        }
-
-        cx.notify();
-        Ok(())
-    }
-
-    fn check_invariants(&self) {
-        #[cfg(any(test, feature = "test-support"))]
-        {
-            for participant in self.remote_participants.values() {
-                assert!(self.participant_user_ids.contains(&participant.user.id));
-                assert_ne!(participant.user.id, self.client.user_id().unwrap());
-            }
-
-            for participant in &self.pending_participants {
-                assert!(self.participant_user_ids.contains(&participant.id));
-                assert_ne!(participant.id, self.client.user_id().unwrap());
-            }
-
-            assert_eq!(
-                self.participant_user_ids.len(),
-                self.remote_participants.len() + self.pending_participants.len()
-            );
-        }
-    }
-
-    pub(crate) fn call(
-        &mut self,
-        called_user_id: u64,
-        initial_project_id: Option<u64>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        if self.status.is_offline() {
-            return Task::ready(Err(anyhow!("room is offline")));
-        }
-
-        cx.notify();
-        let client = self.client.clone();
-        let room_id = self.id;
-        self.pending_call_count += 1;
-        cx.spawn(move |this, mut cx| async move {
-            let result = client
-                .request(proto::Call {
-                    room_id,
-                    called_user_id,
-                    initial_project_id,
-                })
-                .await;
-            this.update(&mut cx, |this, cx| {
-                this.pending_call_count -= 1;
-                if this.should_leave() {
-                    this.leave(cx).detach_and_log_err(cx);
-                }
-            })?;
-            result?;
-            Ok(())
-        })
-    }
-
-    pub fn join_project(
-        &mut self,
-        id: u64,
-        language_registry: Arc<LanguageRegistry>,
-        fs: Arc<dyn Fs>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Model<Project>>> {
-        let client = self.client.clone();
-        let user_store = self.user_store.clone();
-        cx.emit(Event::RemoteProjectJoined { project_id: id });
-        cx.spawn(move |this, mut cx| async move {
-            let project =
-                Project::remote(id, client, user_store, language_registry, fs, cx.clone()).await?;
-
-            this.update(&mut cx, |this, cx| {
-                this.joined_projects.retain(|project| {
-                    if let Some(project) = project.upgrade() {
-                        !project.read(cx).is_read_only()
-                    } else {
-                        false
-                    }
-                });
-                this.joined_projects.insert(project.downgrade());
-            })?;
-            Ok(project)
-        })
-    }
-
-    pub(crate) fn share_project(
-        &mut self,
-        project: Model<Project>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<u64>> {
-        if let Some(project_id) = project.read(cx).remote_id() {
-            return Task::ready(Ok(project_id));
-        }
-
-        let request = self.client.request(proto::ShareProject {
-            room_id: self.id(),
-            worktrees: project.read(cx).worktree_metadata_protos(cx),
-        });
-        cx.spawn(|this, mut cx| async move {
-            let response = request.await?;
-
-            project.update(&mut cx, |project, cx| {
-                project.shared(response.project_id, cx)
-            })??;
-
-            // If the user's location is in this project, it changes from UnsharedProject to SharedProject.
-            this.update(&mut cx, |this, cx| {
-                this.shared_projects.insert(project.downgrade());
-                let active_project = this.local_participant.active_project.as_ref();
-                if active_project.map_or(false, |location| *location == project) {
-                    this.set_location(Some(&project), cx)
-                } else {
-                    Task::ready(Ok(()))
-                }
-            })?
-            .await?;
-
-            Ok(response.project_id)
-        })
-    }
-
-    pub(crate) fn unshare_project(
-        &mut self,
-        project: Model<Project>,
-        cx: &mut ModelContext<Self>,
-    ) -> Result<()> {
-        let project_id = match project.read(cx).remote_id() {
-            Some(project_id) => project_id,
-            None => return Ok(()),
-        };
-
-        self.client.send(proto::UnshareProject { project_id })?;
-        project.update(cx, |this, cx| this.unshare(cx))
-    }
-
-    pub(crate) fn set_location(
-        &mut self,
-        project: Option<&Model<Project>>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        if self.status.is_offline() {
-            return Task::ready(Err(anyhow!("room is offline")));
-        }
-
-        let client = self.client.clone();
-        let room_id = self.id;
-        let location = if let Some(project) = project {
-            self.local_participant.active_project = Some(project.downgrade());
-            if let Some(project_id) = project.read(cx).remote_id() {
-                proto::participant_location::Variant::SharedProject(
-                    proto::participant_location::SharedProject { id: project_id },
-                )
-            } else {
-                proto::participant_location::Variant::UnsharedProject(
-                    proto::participant_location::UnsharedProject {},
-                )
-            }
-        } else {
-            self.local_participant.active_project = None;
-            proto::participant_location::Variant::External(proto::participant_location::External {})
-        };
-
-        cx.notify();
-        cx.background_executor().spawn(async move {
-            client
-                .request(proto::UpdateParticipantLocation {
-                    room_id,
-                    location: Some(proto::ParticipantLocation {
-                        variant: Some(location),
-                    }),
-                })
-                .await?;
-            Ok(())
-        })
-    }
-
-    pub fn is_screen_sharing(&self) -> bool {
-        self.live_kit.as_ref().map_or(false, |live_kit| {
-            !matches!(live_kit.screen_track, LocalTrack::None)
-        })
-    }
-
-    pub fn is_sharing_mic(&self) -> bool {
-        self.live_kit.as_ref().map_or(false, |live_kit| {
-            !matches!(live_kit.microphone_track, LocalTrack::None)
-        })
-    }
-
-    pub fn is_muted(&self, cx: &AppContext) -> bool {
-        self.live_kit
-            .as_ref()
-            .and_then(|live_kit| match &live_kit.microphone_track {
-                LocalTrack::None => Some(Self::mute_on_join(cx)),
-                LocalTrack::Pending { muted, .. } => Some(*muted),
-                LocalTrack::Published { muted, .. } => Some(*muted),
-            })
-            .unwrap_or(false)
-    }
-
-    pub fn is_speaking(&self) -> bool {
-        self.live_kit
-            .as_ref()
-            .map_or(false, |live_kit| live_kit.speaking)
-    }
-
-    pub fn is_deafened(&self) -> Option<bool> {
-        self.live_kit.as_ref().map(|live_kit| live_kit.deafened)
-    }
-
-    #[track_caller]
-    pub fn share_microphone(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
-        if self.status.is_offline() {
-            return Task::ready(Err(anyhow!("room is offline")));
-        } else if self.is_sharing_mic() {
-            return Task::ready(Err(anyhow!("microphone was already shared")));
-        }
-
-        let publish_id = if let Some(live_kit) = self.live_kit.as_mut() {
-            let publish_id = post_inc(&mut live_kit.next_publish_id);
-            live_kit.microphone_track = LocalTrack::Pending {
-                publish_id,
-                muted: false,
-            };
-            cx.notify();
-            publish_id
-        } else {
-            return Task::ready(Err(anyhow!("live-kit was not initialized")));
-        };
-
-        cx.spawn(move |this, mut cx| async move {
-            let publish_track = async {
-                let track = LocalAudioTrack::create();
-                this.upgrade()
-                    .ok_or_else(|| anyhow!("room was dropped"))?
-                    .update(&mut cx, |this, _| {
-                        this.live_kit
-                            .as_ref()
-                            .map(|live_kit| live_kit.room.publish_audio_track(track))
-                    })?
-                    .ok_or_else(|| anyhow!("live-kit was not initialized"))?
-                    .await
-            };
-            let publication = publish_track.await;
-            this.upgrade()
-                .ok_or_else(|| anyhow!("room was dropped"))?
-                .update(&mut cx, |this, cx| {
-                    let live_kit = this
-                        .live_kit
-                        .as_mut()
-                        .ok_or_else(|| anyhow!("live-kit was not initialized"))?;
-
-                    let (canceled, muted) = if let LocalTrack::Pending {
-                        publish_id: cur_publish_id,
-                        muted,
-                    } = &live_kit.microphone_track
-                    {
-                        (*cur_publish_id != publish_id, *muted)
-                    } else {
-                        (true, false)
-                    };
-
-                    match publication {
-                        Ok(publication) => {
-                            if canceled {
-                                live_kit.room.unpublish_track(publication);
-                            } else {
-                                if muted {
-                                    cx.background_executor()
-                                        .spawn(publication.set_mute(muted))
-                                        .detach();
-                                }
-                                live_kit.microphone_track = LocalTrack::Published {
-                                    track_publication: publication,
-                                    muted,
-                                };
-                                cx.notify();
-                            }
-                            Ok(())
-                        }
-                        Err(error) => {
-                            if canceled {
-                                Ok(())
-                            } else {
-                                live_kit.microphone_track = LocalTrack::None;
-                                cx.notify();
-                                Err(error)
-                            }
-                        }
-                    }
-                })?
-        })
-    }
-
-    pub fn share_screen(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
-        if self.status.is_offline() {
-            return Task::ready(Err(anyhow!("room is offline")));
-        } else if self.is_screen_sharing() {
-            return Task::ready(Err(anyhow!("screen was already shared")));
-        }
-
-        let (displays, publish_id) = if let Some(live_kit) = self.live_kit.as_mut() {
-            let publish_id = post_inc(&mut live_kit.next_publish_id);
-            live_kit.screen_track = LocalTrack::Pending {
-                publish_id,
-                muted: false,
-            };
-            cx.notify();
-            (live_kit.room.display_sources(), publish_id)
-        } else {
-            return Task::ready(Err(anyhow!("live-kit was not initialized")));
-        };
-
-        cx.spawn(move |this, mut cx| async move {
-            let publish_track = async {
-                let displays = displays.await?;
-                let display = displays
-                    .first()
-                    .ok_or_else(|| anyhow!("no display found"))?;
-                let track = LocalVideoTrack::screen_share_for_display(&display);
-                this.upgrade()
-                    .ok_or_else(|| anyhow!("room was dropped"))?
-                    .update(&mut cx, |this, _| {
-                        this.live_kit
-                            .as_ref()
-                            .map(|live_kit| live_kit.room.publish_video_track(track))
-                    })?
-                    .ok_or_else(|| anyhow!("live-kit was not initialized"))?
-                    .await
-            };
-
-            let publication = publish_track.await;
-            this.upgrade()
-                .ok_or_else(|| anyhow!("room was dropped"))?
-                .update(&mut cx, |this, cx| {
-                    let live_kit = this
-                        .live_kit
-                        .as_mut()
-                        .ok_or_else(|| anyhow!("live-kit was not initialized"))?;
-
-                    let (canceled, muted) = if let LocalTrack::Pending {
-                        publish_id: cur_publish_id,
-                        muted,
-                    } = &live_kit.screen_track
-                    {
-                        (*cur_publish_id != publish_id, *muted)
-                    } else {
-                        (true, false)
-                    };
-
-                    match publication {
-                        Ok(publication) => {
-                            if canceled {
-                                live_kit.room.unpublish_track(publication);
-                            } else {
-                                if muted {
-                                    cx.background_executor()
-                                        .spawn(publication.set_mute(muted))
-                                        .detach();
-                                }
-                                live_kit.screen_track = LocalTrack::Published {
-                                    track_publication: publication,
-                                    muted,
-                                };
-                                cx.notify();
-                            }
-
-                            Audio::play_sound(Sound::StartScreenshare, cx);
-
-                            Ok(())
-                        }
-                        Err(error) => {
-                            if canceled {
-                                Ok(())
-                            } else {
-                                live_kit.screen_track = LocalTrack::None;
-                                cx.notify();
-                                Err(error)
-                            }
-                        }
-                    }
-                })?
-        })
-    }
-
-    pub fn toggle_mute(&mut self, cx: &mut ModelContext<Self>) -> Result<Task<Result<()>>> {
-        let should_mute = !self.is_muted(cx);
-        if let Some(live_kit) = self.live_kit.as_mut() {
-            if matches!(live_kit.microphone_track, LocalTrack::None) {
-                return Ok(self.share_microphone(cx));
-            }
-
-            let (ret_task, old_muted) = live_kit.set_mute(should_mute, cx)?;
-            live_kit.muted_by_user = should_mute;
-
-            if old_muted == true && live_kit.deafened == true {
-                if let Some(task) = self.toggle_deafen(cx).ok() {
-                    task.detach();
-                }
-            }
-
-            Ok(ret_task)
-        } else {
-            Err(anyhow!("LiveKit not started"))
-        }
-    }
-
-    pub fn toggle_deafen(&mut self, cx: &mut ModelContext<Self>) -> Result<Task<Result<()>>> {
-        if let Some(live_kit) = self.live_kit.as_mut() {
-            (*live_kit).deafened = !live_kit.deafened;
-
-            let mut tasks = Vec::with_capacity(self.remote_participants.len());
-            // Context notification is sent within set_mute itself.
-            let mut mute_task = None;
-            // When deafening, mute user's mic as well.
-            // When undeafening, unmute user's mic unless it was manually muted prior to deafening.
-            if live_kit.deafened || !live_kit.muted_by_user {
-                mute_task = Some(live_kit.set_mute(live_kit.deafened, cx)?.0);
-            };
-            for participant in self.remote_participants.values() {
-                for track in live_kit
-                    .room
-                    .remote_audio_track_publications(&participant.user.id.to_string())
-                {
-                    let deafened = live_kit.deafened;
-                    tasks.push(cx.foreground_executor().spawn(track.set_enabled(!deafened)));
-                }
-            }
-
-            Ok(cx.foreground_executor().spawn(async move {
-                if let Some(mute_task) = mute_task {
-                    mute_task.await?;
-                }
-                for task in tasks {
-                    task.await?;
-                }
-                Ok(())
-            }))
-        } else {
-            Err(anyhow!("LiveKit not started"))
-        }
-    }
-
-    pub fn unshare_screen(&mut self, cx: &mut ModelContext<Self>) -> Result<()> {
-        if self.status.is_offline() {
-            return Err(anyhow!("room is offline"));
-        }
-
-        let live_kit = self
-            .live_kit
-            .as_mut()
-            .ok_or_else(|| anyhow!("live-kit was not initialized"))?;
-        match mem::take(&mut live_kit.screen_track) {
-            LocalTrack::None => Err(anyhow!("screen was not shared")),
-            LocalTrack::Pending { .. } => {
-                cx.notify();
-                Ok(())
-            }
-            LocalTrack::Published {
-                track_publication, ..
-            } => {
-                live_kit.room.unpublish_track(track_publication);
-                cx.notify();
-
-                Audio::play_sound(Sound::StopScreenshare, cx);
-                Ok(())
-            }
-        }
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn set_display_sources(&self, sources: Vec<live_kit_client::MacOSDisplay>) {
-        self.live_kit
-            .as_ref()
-            .unwrap()
-            .room
-            .set_display_sources(sources);
-    }
-}
-
-struct LiveKitRoom {
-    room: Arc<live_kit_client::Room>,
-    screen_track: LocalTrack,
-    microphone_track: LocalTrack,
-    /// Tracks whether we're currently in a muted state due to auto-mute from deafening or manual mute performed by user.
-    muted_by_user: bool,
-    deafened: bool,
-    speaking: bool,
-    next_publish_id: usize,
-    _maintain_room: Task<()>,
-    _maintain_tracks: [Task<()>; 2],
-}
-
-impl LiveKitRoom {
-    fn set_mute(
-        self: &mut LiveKitRoom,
-        should_mute: bool,
-        cx: &mut ModelContext<Room>,
-    ) -> Result<(Task<Result<()>>, bool)> {
-        if !should_mute {
-            // clear user muting state.
-            self.muted_by_user = false;
-        }
-
-        let (result, old_muted) = match &mut self.microphone_track {
-            LocalTrack::None => Err(anyhow!("microphone was not shared")),
-            LocalTrack::Pending { muted, .. } => {
-                let old_muted = *muted;
-                *muted = should_mute;
-                cx.notify();
-                Ok((Task::Ready(Some(Ok(()))), old_muted))
-            }
-            LocalTrack::Published {
-                track_publication,
-                muted,
-            } => {
-                let old_muted = *muted;
-                *muted = should_mute;
-                cx.notify();
-                Ok((
-                    cx.background_executor()
-                        .spawn(track_publication.set_mute(*muted)),
-                    old_muted,
-                ))
-            }
-        }?;
-
-        if old_muted != should_mute {
-            if should_mute {
-                Audio::play_sound(Sound::Mute, cx);
-            } else {
-                Audio::play_sound(Sound::Unmute, cx);
-            }
-        }
-
-        Ok((result, old_muted))
-    }
-}
-
-enum LocalTrack {
-    None,
-    Pending {
-        publish_id: usize,
-        muted: bool,
-    },
-    Published {
-        track_publication: LocalTrackPublication,
-        muted: bool,
-    },
-}
-
-impl Default for LocalTrack {
-    fn default() -> Self {
-        Self::None
-    }
-}
-
-#[derive(Copy, Clone, PartialEq, Eq)]
-pub enum RoomStatus {
-    Online,
-    Rejoining,
-    Offline,
-}
-
-impl RoomStatus {
-    pub fn is_offline(&self) -> bool {
-        matches!(self, RoomStatus::Offline)
-    }
-
-    pub fn is_online(&self) -> bool {
-        matches!(self, RoomStatus::Online)
-    }
-}

crates/channel/src/channel.rs 🔗

@@ -3,7 +3,7 @@ mod channel_chat;
 mod channel_store;
 
 use client::{Client, UserStore};
-use gpui::{AppContext, ModelHandle};
+use gpui::{AppContext, Model};
 use std::sync::Arc;
 
 pub use channel_buffer::{ChannelBuffer, ChannelBufferEvent, ACKNOWLEDGE_DEBOUNCE_INTERVAL};
@@ -16,7 +16,7 @@ pub use channel_store::{Channel, ChannelEvent, ChannelId, ChannelMembership, Cha
 #[cfg(test)]
 mod channel_store_tests;
 
-pub fn init(client: &Arc<Client>, user_store: ModelHandle<UserStore>, cx: &mut AppContext) {
+pub fn init(client: &Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
     channel_store::init(client, user_store, cx);
     channel_buffer::init(client);
     channel_chat::init(client);

crates/channel/src/channel_buffer.rs 🔗

@@ -2,7 +2,7 @@ use crate::{Channel, ChannelId, ChannelStore};
 use anyhow::Result;
 use client::{Client, Collaborator, UserStore};
 use collections::HashMap;
-use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task};
+use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task};
 use language::proto::serialize_version;
 use rpc::{
     proto::{self, PeerId},
@@ -22,9 +22,9 @@ pub struct ChannelBuffer {
     pub channel_id: ChannelId,
     connected: bool,
     collaborators: HashMap<PeerId, Collaborator>,
-    user_store: ModelHandle<UserStore>,
-    channel_store: ModelHandle<ChannelStore>,
-    buffer: ModelHandle<language::Buffer>,
+    user_store: Model<UserStore>,
+    channel_store: Model<ChannelStore>,
+    buffer: Model<language::Buffer>,
     buffer_epoch: u64,
     client: Arc<Client>,
     subscription: Option<client::Subscription>,
@@ -38,31 +38,16 @@ pub enum ChannelBufferEvent {
     ChannelChanged,
 }
 
-impl Entity for ChannelBuffer {
-    type Event = ChannelBufferEvent;
-
-    fn release(&mut self, _: &mut AppContext) {
-        if self.connected {
-            if let Some(task) = self.acknowledge_task.take() {
-                task.detach();
-            }
-            self.client
-                .send(proto::LeaveChannelBuffer {
-                    channel_id: self.channel_id,
-                })
-                .log_err();
-        }
-    }
-}
+impl EventEmitter<ChannelBufferEvent> for ChannelBuffer {}
 
 impl ChannelBuffer {
     pub(crate) async fn new(
         channel: Arc<Channel>,
         client: Arc<Client>,
-        user_store: ModelHandle<UserStore>,
-        channel_store: ModelHandle<ChannelStore>,
+        user_store: Model<UserStore>,
+        channel_store: Model<ChannelStore>,
         mut cx: AsyncAppContext,
-    ) -> Result<ModelHandle<Self>> {
+    ) -> Result<Model<Self>> {
         let response = client
             .request(proto::JoinChannelBuffer {
                 channel_id: channel.id,
@@ -76,16 +61,16 @@ impl ChannelBuffer {
             .map(language::proto::deserialize_operation)
             .collect::<Result<Vec<_>, _>>()?;
 
-        let buffer = cx.add_model(|_| {
+        let buffer = cx.new_model(|_| {
             language::Buffer::remote(response.buffer_id, response.replica_id as u16, base_text)
-        });
-        buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))?;
+        })?;
+        buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))??;
 
         let subscription = client.subscribe_to_entity(channel.id)?;
 
-        anyhow::Ok(cx.add_model(|cx| {
+        anyhow::Ok(cx.new_model(|cx| {
             cx.subscribe(&buffer, Self::on_buffer_update).detach();
-
+            cx.on_release(Self::release).detach();
             let mut this = Self {
                 buffer,
                 buffer_epoch: response.epoch,
@@ -100,14 +85,27 @@ impl ChannelBuffer {
             };
             this.replace_collaborators(response.collaborators, cx);
             this
-        }))
+        })?)
+    }
+
+    fn release(&mut self, _: &mut AppContext) {
+        if self.connected {
+            if let Some(task) = self.acknowledge_task.take() {
+                task.detach();
+            }
+            self.client
+                .send(proto::LeaveChannelBuffer {
+                    channel_id: self.channel_id,
+                })
+                .log_err();
+        }
     }
 
     pub fn remote_id(&self, cx: &AppContext) -> u64 {
         self.buffer.read(cx).remote_id()
     }
 
-    pub fn user_store(&self) -> &ModelHandle<UserStore> {
+    pub fn user_store(&self) -> &Model<UserStore> {
         &self.user_store
     }
 
@@ -136,7 +134,7 @@ impl ChannelBuffer {
     }
 
     async fn handle_update_channel_buffer(
-        this: ModelHandle<Self>,
+        this: Model<Self>,
         update_channel_buffer: TypedEnvelope<proto::UpdateChannelBuffer>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -152,13 +150,13 @@ impl ChannelBuffer {
             cx.notify();
             this.buffer
                 .update(cx, |buffer, cx| buffer.apply_ops(ops, cx))
-        })?;
+        })??;
 
         Ok(())
     }
 
     async fn handle_update_channel_buffer_collaborators(
-        this: ModelHandle<Self>,
+        this: Model<Self>,
         message: TypedEnvelope<proto::UpdateChannelBufferCollaborators>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -167,14 +165,12 @@ impl ChannelBuffer {
             this.replace_collaborators(message.payload.collaborators, cx);
             cx.emit(ChannelBufferEvent::CollaboratorsChanged);
             cx.notify();
-        });
-
-        Ok(())
+        })
     }
 
     fn on_buffer_update(
         &mut self,
-        _: ModelHandle<language::Buffer>,
+        _: Model<language::Buffer>,
         event: &language::Event,
         cx: &mut ModelContext<Self>,
     ) {
@@ -202,8 +198,10 @@ impl ChannelBuffer {
         let client = self.client.clone();
         let epoch = self.epoch();
 
-        self.acknowledge_task = Some(cx.spawn_weak(|_, cx| async move {
-            cx.background().timer(ACKNOWLEDGE_DEBOUNCE_INTERVAL).await;
+        self.acknowledge_task = Some(cx.spawn(move |_, cx| async move {
+            cx.background_executor()
+                .timer(ACKNOWLEDGE_DEBOUNCE_INTERVAL)
+                .await;
             client
                 .send(proto::AckBufferOperation {
                     buffer_id,
@@ -219,7 +217,7 @@ impl ChannelBuffer {
         self.buffer_epoch
     }
 
-    pub fn buffer(&self) -> ModelHandle<language::Buffer> {
+    pub fn buffer(&self) -> Model<language::Buffer> {
         self.buffer.clone()
     }
 

crates/channel/src/channel_chat.rs 🔗

@@ -6,7 +6,7 @@ use client::{
     Client, Subscription, TypedEnvelope, UserId,
 };
 use futures::lock::Mutex;
-use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task};
+use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task};
 use rand::prelude::*;
 use std::{
     collections::HashSet,
@@ -22,11 +22,11 @@ pub struct ChannelChat {
     pub channel_id: ChannelId,
     messages: SumTree<ChannelMessage>,
     acknowledged_message_ids: HashSet<u64>,
-    channel_store: ModelHandle<ChannelStore>,
+    channel_store: Model<ChannelStore>,
     loaded_all_messages: bool,
     last_acknowledged_id: Option<u64>,
     next_pending_message_id: usize,
-    user_store: ModelHandle<UserStore>,
+    user_store: Model<UserStore>,
     rpc: Arc<Client>,
     outgoing_messages_lock: Arc<Mutex<()>>,
     rng: StdRng,
@@ -76,31 +76,20 @@ pub enum ChannelChatEvent {
     },
 }
 
+impl EventEmitter<ChannelChatEvent> for ChannelChat {}
 pub fn init(client: &Arc<Client>) {
     client.add_model_message_handler(ChannelChat::handle_message_sent);
     client.add_model_message_handler(ChannelChat::handle_message_removed);
 }
 
-impl Entity for ChannelChat {
-    type Event = ChannelChatEvent;
-
-    fn release(&mut self, _: &mut AppContext) {
-        self.rpc
-            .send(proto::LeaveChannelChat {
-                channel_id: self.channel_id,
-            })
-            .log_err();
-    }
-}
-
 impl ChannelChat {
     pub async fn new(
         channel: Arc<Channel>,
-        channel_store: ModelHandle<ChannelStore>,
-        user_store: ModelHandle<UserStore>,
+        channel_store: Model<ChannelStore>,
+        user_store: Model<UserStore>,
         client: Arc<Client>,
         mut cx: AsyncAppContext,
-    ) -> Result<ModelHandle<Self>> {
+    ) -> Result<Model<Self>> {
         let channel_id = channel.id;
         let subscription = client.subscribe_to_entity(channel_id).unwrap();
 
@@ -110,7 +99,8 @@ impl ChannelChat {
         let messages = messages_from_proto(response.messages, &user_store, &mut cx).await?;
         let loaded_all_messages = response.done;
 
-        Ok(cx.add_model(|cx| {
+        Ok(cx.new_model(|cx| {
+            cx.on_release(Self::release).detach();
             let mut this = Self {
                 channel_id: channel.id,
                 user_store,
@@ -127,7 +117,15 @@ impl ChannelChat {
             };
             this.insert_messages(messages, cx);
             this
-        }))
+        })?)
+    }
+
+    fn release(&mut self, _: &mut AppContext) {
+        self.rpc
+            .send(proto::LeaveChannelChat {
+                channel_id: self.channel_id,
+            })
+            .log_err();
     }
 
     pub fn channel(&self, cx: &AppContext) -> Option<Arc<Channel>> {
@@ -176,7 +174,7 @@ impl ChannelChat {
         let user_store = self.user_store.clone();
         let rpc = self.rpc.clone();
         let outgoing_messages_lock = self.outgoing_messages_lock.clone();
-        Ok(cx.spawn(|this, mut cx| async move {
+        Ok(cx.spawn(move |this, mut cx| async move {
             let outgoing_message_guard = outgoing_messages_lock.lock().await;
             let request = rpc.request(proto::SendChannelMessage {
                 channel_id,
@@ -191,8 +189,8 @@ impl ChannelChat {
             let message = ChannelMessage::from_proto(response, &user_store, &mut cx).await?;
             this.update(&mut cx, |this, cx| {
                 this.insert_messages(SumTree::from_item(message, &()), cx);
-                Ok(id)
-            })
+            })?;
+            Ok(id)
         }))
     }
 
@@ -201,13 +199,12 @@ impl ChannelChat {
             channel_id: self.channel_id,
             message_id: id,
         });
-        cx.spawn(|this, mut cx| async move {
+        cx.spawn(move |this, mut cx| async move {
             response.await?;
-
             this.update(&mut cx, |this, cx| {
                 this.message_removed(id, cx);
-                Ok(())
-            })
+            })?;
+            Ok(())
         })
     }
 
@@ -220,7 +217,7 @@ impl ChannelChat {
         let user_store = self.user_store.clone();
         let channel_id = self.channel_id;
         let before_message_id = self.first_loaded_message_id()?;
-        Some(cx.spawn(|this, mut cx| {
+        Some(cx.spawn(move |this, mut cx| {
             async move {
                 let response = rpc
                     .request(proto::GetChannelMessages {
@@ -233,7 +230,7 @@ impl ChannelChat {
                 this.update(&mut cx, |this, cx| {
                     this.loaded_all_messages = loaded_all_messages;
                     this.insert_messages(messages, cx);
-                });
+                })?;
                 anyhow::Ok(())
             }
             .log_err()
@@ -251,31 +248,33 @@ impl ChannelChat {
     ///
     /// For now, we always maintain a suffix of the channel's messages.
     pub async fn load_history_since_message(
-        chat: ModelHandle<Self>,
+        chat: Model<Self>,
         message_id: u64,
         mut cx: AsyncAppContext,
     ) -> Option<usize> {
         loop {
-            let step = chat.update(&mut cx, |chat, cx| {
-                if let Some(first_id) = chat.first_loaded_message_id() {
-                    if first_id <= message_id {
-                        let mut cursor = chat.messages.cursor::<(ChannelMessageId, Count)>();
-                        let message_id = ChannelMessageId::Saved(message_id);
-                        cursor.seek(&message_id, Bias::Left, &());
-                        return ControlFlow::Break(
-                            if cursor
-                                .item()
-                                .map_or(false, |message| message.id == message_id)
-                            {
-                                Some(cursor.start().1 .0)
-                            } else {
-                                None
-                            },
-                        );
+            let step = chat
+                .update(&mut cx, |chat, cx| {
+                    if let Some(first_id) = chat.first_loaded_message_id() {
+                        if first_id <= message_id {
+                            let mut cursor = chat.messages.cursor::<(ChannelMessageId, Count)>();
+                            let message_id = ChannelMessageId::Saved(message_id);
+                            cursor.seek(&message_id, Bias::Left, &());
+                            return ControlFlow::Break(
+                                if cursor
+                                    .item()
+                                    .map_or(false, |message| message.id == message_id)
+                                {
+                                    Some(cursor.start().1 .0)
+                                } else {
+                                    None
+                                },
+                            );
+                        }
                     }
-                }
-                ControlFlow::Continue(chat.load_more_messages(cx))
-            });
+                    ControlFlow::Continue(chat.load_more_messages(cx))
+                })
+                .log_err()?;
             match step {
                 ControlFlow::Break(ix) => return ix,
                 ControlFlow::Continue(task) => task?.await?,
@@ -307,7 +306,7 @@ impl ChannelChat {
         let user_store = self.user_store.clone();
         let rpc = self.rpc.clone();
         let channel_id = self.channel_id;
-        cx.spawn(|this, mut cx| {
+        cx.spawn(move |this, mut cx| {
             async move {
                 let response = rpc.request(proto::JoinChannelChat { channel_id }).await?;
                 let messages = messages_from_proto(response.messages, &user_store, &mut cx).await?;
@@ -333,7 +332,7 @@ impl ChannelChat {
                     }
 
                     this.pending_messages().cloned().collect::<Vec<_>>()
-                });
+                })?;
 
                 for pending_message in pending_messages {
                     let request = rpc.request(proto::SendChannelMessage {
@@ -351,7 +350,7 @@ impl ChannelChat {
                     .await?;
                     this.update(&mut cx, |this, cx| {
                         this.insert_messages(SumTree::from_item(message, &()), cx);
-                    });
+                    })?;
                 }
 
                 anyhow::Ok(())
@@ -399,12 +398,12 @@ impl ChannelChat {
     }
 
     async fn handle_message_sent(
-        this: ModelHandle<Self>,
+        this: Model<Self>,
         message: TypedEnvelope<proto::ChannelMessageSent>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
     ) -> Result<()> {
-        let user_store = this.read_with(&cx, |this, _| this.user_store.clone());
+        let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
         let message = message
             .payload
             .message
@@ -418,20 +417,20 @@ impl ChannelChat {
                 channel_id: this.channel_id,
                 message_id,
             })
-        });
+        })?;
 
         Ok(())
     }
 
     async fn handle_message_removed(
-        this: ModelHandle<Self>,
+        this: Model<Self>,
         message: TypedEnvelope<proto::RemoveChannelMessage>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
     ) -> Result<()> {
         this.update(&mut cx, |this, cx| {
             this.message_removed(message.payload.message_id, cx)
-        });
+        })?;
         Ok(())
     }
 
@@ -515,7 +514,7 @@ impl ChannelChat {
 
 async fn messages_from_proto(
     proto_messages: Vec<proto::ChannelMessage>,
-    user_store: &ModelHandle<UserStore>,
+    user_store: &Model<UserStore>,
     cx: &mut AsyncAppContext,
 ) -> Result<SumTree<ChannelMessage>> {
     let messages = ChannelMessage::from_proto_vec(proto_messages, user_store, cx).await?;
@@ -527,13 +526,13 @@ async fn messages_from_proto(
 impl ChannelMessage {
     pub async fn from_proto(
         message: proto::ChannelMessage,
-        user_store: &ModelHandle<UserStore>,
+        user_store: &Model<UserStore>,
         cx: &mut AsyncAppContext,
     ) -> Result<Self> {
         let sender = user_store
             .update(cx, |user_store, cx| {
                 user_store.get_user(message.sender_id, cx)
-            })
+            })?
             .await?;
         Ok(ChannelMessage {
             id: ChannelMessageId::Saved(message.id),
@@ -561,7 +560,7 @@ impl ChannelMessage {
 
     pub async fn from_proto_vec(
         proto_messages: Vec<proto::ChannelMessage>,
-        user_store: &ModelHandle<UserStore>,
+        user_store: &Model<UserStore>,
         cx: &mut AsyncAppContext,
     ) -> Result<Vec<Self>> {
         let unique_user_ids = proto_messages
@@ -573,7 +572,7 @@ impl ChannelMessage {
         user_store
             .update(cx, |user_store, cx| {
                 user_store.get_users(unique_user_ids, cx)
-            })
+            })?
             .await?;
 
         let mut messages = Vec::with_capacity(proto_messages.len());

crates/channel/src/channel_store.rs 🔗

@@ -7,17 +7,20 @@ use client::{Client, Subscription, User, UserId, UserStore};
 use collections::{hash_map, HashMap, HashSet};
 use db::RELEASE_CHANNEL;
 use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
-use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle};
+use gpui::{
+    AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, SharedString, Task,
+    WeakModel,
+};
 use rpc::{
     proto::{self, ChannelVisibility},
     TypedEnvelope,
 };
 use std::{mem, sync::Arc, time::Duration};
-use util::ResultExt;
+use util::{async_maybe, ResultExt};
 
-pub fn init(client: &Arc<Client>, user_store: ModelHandle<UserStore>, cx: &mut AppContext) {
+pub fn init(client: &Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
     let channel_store =
-        cx.add_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
+        cx.new_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
     cx.set_global(channel_store);
 }
 
@@ -34,7 +37,7 @@ pub struct ChannelStore {
     opened_buffers: HashMap<ChannelId, OpenedModelHandle<ChannelBuffer>>,
     opened_chats: HashMap<ChannelId, OpenedModelHandle<ChannelChat>>,
     client: Arc<Client>,
-    user_store: ModelHandle<UserStore>,
+    user_store: Model<UserStore>,
     _rpc_subscription: Subscription,
     _watch_connection_status: Task<Option<()>>,
     disconnect_channel_buffers_task: Option<Task<()>>,
@@ -44,7 +47,7 @@ pub struct ChannelStore {
 #[derive(Clone, Debug, PartialEq)]
 pub struct Channel {
     pub id: ChannelId,
-    pub name: String,
+    pub name: SharedString,
     pub visibility: proto::ChannelVisibility,
     pub role: proto::ChannelRole,
     pub unseen_note_version: Option<(u64, clock::Global)>,
@@ -112,44 +115,45 @@ pub enum ChannelEvent {
     ChannelRenamed(ChannelId),
 }
 
-impl Entity for ChannelStore {
-    type Event = ChannelEvent;
-}
+impl EventEmitter<ChannelEvent> for ChannelStore {}
 
-enum OpenedModelHandle<E: Entity> {
-    Open(WeakModelHandle<E>),
-    Loading(Shared<Task<Result<ModelHandle<E>, Arc<anyhow::Error>>>>),
+enum OpenedModelHandle<E> {
+    Open(WeakModel<E>),
+    Loading(Shared<Task<Result<Model<E>, Arc<anyhow::Error>>>>),
 }
 
 impl ChannelStore {
-    pub fn global(cx: &AppContext) -> ModelHandle<Self> {
-        cx.global::<ModelHandle<Self>>().clone()
+    pub fn global(cx: &AppContext) -> Model<Self> {
+        cx.global::<Model<Self>>().clone()
     }
 
     pub fn new(
         client: Arc<Client>,
-        user_store: ModelHandle<UserStore>,
+        user_store: Model<UserStore>,
         cx: &mut ModelContext<Self>,
     ) -> Self {
         let rpc_subscription =
-            client.add_message_handler(cx.handle(), Self::handle_update_channels);
+            client.add_message_handler(cx.weak_model(), Self::handle_update_channels);
 
         let mut connection_status = client.status();
         let (update_channels_tx, mut update_channels_rx) = mpsc::unbounded();
-        let watch_connection_status = cx.spawn_weak(|this, mut cx| async move {
+        let watch_connection_status = cx.spawn(|this, mut cx| async move {
             while let Some(status) = connection_status.next().await {
-                let this = this.upgrade(&cx)?;
+                let this = this.upgrade()?;
                 match status {
                     client::Status::Connected { .. } => {
                         this.update(&mut cx, |this, cx| this.handle_connect(cx))
+                            .ok()?
                             .await
                             .log_err()?;
                     }
                     client::Status::SignedOut | client::Status::UpgradeRequired => {
-                        this.update(&mut cx, |this, cx| this.handle_disconnect(false, cx));
+                        this.update(&mut cx, |this, cx| this.handle_disconnect(false, cx))
+                            .ok();
                     }
                     _ => {
-                        this.update(&mut cx, |this, cx| this.handle_disconnect(true, cx));
+                        this.update(&mut cx, |this, cx| this.handle_disconnect(true, cx))
+                            .ok();
                     }
                 }
             }
@@ -169,17 +173,22 @@ impl ChannelStore {
             _rpc_subscription: rpc_subscription,
             _watch_connection_status: watch_connection_status,
             disconnect_channel_buffers_task: None,
-            _update_channels: cx.spawn_weak(|this, mut cx| async move {
-                while let Some(update_channels) = update_channels_rx.next().await {
-                    if let Some(this) = this.upgrade(&cx) {
-                        let update_task = this.update(&mut cx, |this, cx| {
-                            this.update_channels(update_channels, cx)
-                        });
-                        if let Some(update_task) = update_task {
-                            update_task.await.log_err();
+            _update_channels: cx.spawn(|this, mut cx| async move {
+                async_maybe!({
+                    while let Some(update_channels) = update_channels_rx.next().await {
+                        if let Some(this) = this.upgrade() {
+                            let update_task = this.update(&mut cx, |this, cx| {
+                                this.update_channels(update_channels, cx)
+                            })?;
+                            if let Some(update_task) = update_task {
+                                update_task.await.log_err();
+                            }
                         }
                     }
-                }
+                    anyhow::Ok(())
+                })
+                .await
+                .log_err();
             }),
         }
     }
@@ -240,10 +249,10 @@ impl ChannelStore {
         self.channel_index.by_id().get(&channel_id)
     }
 
-    pub fn has_open_channel_buffer(&self, channel_id: ChannelId, cx: &AppContext) -> bool {
+    pub fn has_open_channel_buffer(&self, channel_id: ChannelId, _cx: &AppContext) -> bool {
         if let Some(buffer) = self.opened_buffers.get(&channel_id) {
             if let OpenedModelHandle::Open(buffer) = buffer {
-                return buffer.upgrade(cx).is_some();
+                return buffer.upgrade().is_some();
             }
         }
         false
@@ -253,7 +262,7 @@ impl ChannelStore {
         &mut self,
         channel_id: ChannelId,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<ModelHandle<ChannelBuffer>>> {
+    ) -> Task<Result<Model<ChannelBuffer>>> {
         let client = self.client.clone();
         let user_store = self.user_store.clone();
         let channel_store = cx.handle();
@@ -278,13 +287,13 @@ impl ChannelStore {
                     .request(proto::GetChannelMessagesById { message_ids }),
             )
         };
-        cx.spawn_weak(|this, mut cx| async move {
+        cx.spawn(|this, mut cx| async move {
             if let Some(request) = request {
                 let response = request.await?;
                 let this = this
-                    .upgrade(&cx)
+                    .upgrade()
                     .ok_or_else(|| anyhow!("channel store dropped"))?;
-                let user_store = this.read_with(&cx, |this, _| this.user_store.clone());
+                let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
                 ChannelMessage::from_proto_vec(response.messages, &user_store, &mut cx).await
             } else {
                 Ok(Vec::new())
@@ -354,7 +363,7 @@ impl ChannelStore {
         &mut self,
         channel_id: ChannelId,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<ModelHandle<ChannelChat>>> {
+    ) -> Task<Result<Model<ChannelChat>>> {
         let client = self.client.clone();
         let user_store = self.user_store.clone();
         let this = cx.handle();
@@ -371,22 +380,23 @@ impl ChannelStore {
     /// Make sure that the resource is only opened once, even if this method
     /// is called multiple times with the same channel id while the first task
     /// is still running.
-    fn open_channel_resource<T: Entity, F, Fut>(
+    fn open_channel_resource<T, F, Fut>(
         &mut self,
         channel_id: ChannelId,
         get_map: fn(&mut Self) -> &mut HashMap<ChannelId, OpenedModelHandle<T>>,
         load: F,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<ModelHandle<T>>>
+    ) -> Task<Result<Model<T>>>
     where
         F: 'static + FnOnce(Arc<Channel>, AsyncAppContext) -> Fut,
-        Fut: Future<Output = Result<ModelHandle<T>>>,
+        Fut: Future<Output = Result<Model<T>>>,
+        T: 'static,
     {
         let task = loop {
             match get_map(self).entry(channel_id) {
                 hash_map::Entry::Occupied(e) => match e.get() {
                     OpenedModelHandle::Open(model) => {
-                        if let Some(model) = model.upgrade(cx) {
+                        if let Some(model) = model.upgrade() {
                             break Task::ready(Ok(model)).shared();
                         } else {
                             get_map(self).remove(&channel_id);
@@ -399,12 +409,12 @@ impl ChannelStore {
                 },
                 hash_map::Entry::Vacant(e) => {
                     let task = cx
-                        .spawn(|this, cx| async move {
-                            let channel = this.read_with(&cx, |this, _| {
+                        .spawn(move |this, mut cx| async move {
+                            let channel = this.update(&mut cx, |this, _| {
                                 this.channel_for_id(channel_id).cloned().ok_or_else(|| {
                                     Arc::new(anyhow!("no channel for id: {}", channel_id))
                                 })
-                            })?;
+                            })??;
 
                             load(channel, cx).await.map_err(Arc::new)
                         })
@@ -413,7 +423,7 @@ impl ChannelStore {
                     e.insert(OpenedModelHandle::Loading(task.clone()));
                     cx.spawn({
                         let task = task.clone();
-                        |this, mut cx| async move {
+                        move |this, mut cx| async move {
                             let result = task.await;
                             this.update(&mut cx, |this, _| match result {
                                 Ok(model) => {
@@ -425,7 +435,8 @@ impl ChannelStore {
                                 Err(_) => {
                                     get_map(this).remove(&channel_id);
                                 }
-                            });
+                            })
+                            .ok();
                         }
                     })
                     .detach();
@@ -433,7 +444,7 @@ impl ChannelStore {
                 }
             }
         };
-        cx.foreground()
+        cx.background_executor()
             .spawn(async move { task.await.map_err(|error| anyhow!("{}", error)) })
     }
 
@@ -458,7 +469,7 @@ impl ChannelStore {
     ) -> Task<Result<ChannelId>> {
         let client = self.client.clone();
         let name = name.trim_start_matches("#").to_owned();
-        cx.spawn(|this, mut cx| async move {
+        cx.spawn(move |this, mut cx| async move {
             let response = client
                 .request(proto::CreateChannel { name, parent_id })
                 .await?;
@@ -468,15 +479,6 @@ impl ChannelStore {
                 .ok_or_else(|| anyhow!("missing channel in response"))?;
             let channel_id = channel.id;
 
-            // let parent_edge = if let Some(parent_id) = parent_id {
-            //     vec![ChannelEdge {
-            //         channel_id: channel.id,
-            //         parent_id,
-            //     }]
-            // } else {
-            //     vec![]
-            // };
-
             this.update(&mut cx, |this, cx| {
                 let task = this.update_channels(
                     proto::UpdateChannels {
@@ -492,7 +494,7 @@ impl ChannelStore {
                 // will resolve before this flush_effects finishes. Synchronously emitting this event
                 // ensures that the collab panel will observe this creation before the frame completes
                 cx.emit(ChannelEvent::ChannelCreated(channel_id));
-            });
+            })?;
 
             Ok(channel_id)
         })
@@ -505,7 +507,7 @@ impl ChannelStore {
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
         let client = self.client.clone();
-        cx.spawn(|_, _| async move {
+        cx.spawn(move |_, _| async move {
             let _ = client
                 .request(proto::MoveChannel { channel_id, to })
                 .await?;
@@ -521,7 +523,7 @@ impl ChannelStore {
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
         let client = self.client.clone();
-        cx.spawn(|_, _| async move {
+        cx.spawn(move |_, _| async move {
             let _ = client
                 .request(proto::SetChannelVisibility {
                     channel_id,
@@ -546,7 +548,7 @@ impl ChannelStore {
 
         cx.notify();
         let client = self.client.clone();
-        cx.spawn(|this, mut cx| async move {
+        cx.spawn(move |this, mut cx| async move {
             let result = client
                 .request(proto::InviteChannelMember {
                     channel_id,
@@ -558,7 +560,7 @@ impl ChannelStore {
             this.update(&mut cx, |this, cx| {
                 this.outgoing_invites.remove(&(channel_id, user_id));
                 cx.notify();
-            });
+            })?;
 
             result?;
 
@@ -578,7 +580,7 @@ impl ChannelStore {
 
         cx.notify();
         let client = self.client.clone();
-        cx.spawn(|this, mut cx| async move {
+        cx.spawn(move |this, mut cx| async move {
             let result = client
                 .request(proto::RemoveChannelMember {
                     channel_id,
@@ -589,7 +591,7 @@ impl ChannelStore {
             this.update(&mut cx, |this, cx| {
                 this.outgoing_invites.remove(&(channel_id, user_id));
                 cx.notify();
-            });
+            })?;
             result?;
             Ok(())
         })
@@ -608,7 +610,7 @@ impl ChannelStore {
 
         cx.notify();
         let client = self.client.clone();
-        cx.spawn(|this, mut cx| async move {
+        cx.spawn(move |this, mut cx| async move {
             let result = client
                 .request(proto::SetChannelMemberRole {
                     channel_id,
@@ -620,7 +622,7 @@ impl ChannelStore {
             this.update(&mut cx, |this, cx| {
                 this.outgoing_invites.remove(&(channel_id, user_id));
                 cx.notify();
-            });
+            })?;
 
             result?;
             Ok(())
@@ -635,7 +637,7 @@ impl ChannelStore {
     ) -> Task<Result<()>> {
         let client = self.client.clone();
         let name = new_name.to_string();
-        cx.spawn(|this, mut cx| async move {
+        cx.spawn(move |this, mut cx| async move {
             let channel = client
                 .request(proto::RenameChannel { channel_id, name })
                 .await?
@@ -656,7 +658,7 @@ impl ChannelStore {
                 // will resolve before this flush_effects finishes. Synchronously emitting this event
                 // ensures that the collab panel will observe this creation before the frame complete
                 cx.emit(ChannelEvent::ChannelRenamed(channel_id))
-            });
+            })?;
             Ok(())
         })
     }
@@ -668,7 +670,7 @@ impl ChannelStore {
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
         let client = self.client.clone();
-        cx.background().spawn(async move {
+        cx.background_executor().spawn(async move {
             client
                 .request(proto::RespondToChannelInvite { channel_id, accept })
                 .await?;
@@ -683,17 +685,17 @@ impl ChannelStore {
     ) -> Task<Result<Vec<ChannelMembership>>> {
         let client = self.client.clone();
         let user_store = self.user_store.downgrade();
-        cx.spawn(|_, mut cx| async move {
+        cx.spawn(move |_, mut cx| async move {
             let response = client
                 .request(proto::GetChannelMembers { channel_id })
                 .await?;
 
             let user_ids = response.members.iter().map(|m| m.user_id).collect();
             let user_store = user_store
-                .upgrade(&cx)
+                .upgrade()
                 .ok_or_else(|| anyhow!("user store dropped"))?;
             let users = user_store
-                .update(&mut cx, |user_store, cx| user_store.get_users(user_ids, cx))
+                .update(&mut cx, |user_store, cx| user_store.get_users(user_ids, cx))?
                 .await?;
 
             Ok(users
@@ -727,7 +729,7 @@ impl ChannelStore {
     }
 
     async fn handle_update_channels(
-        this: ModelHandle<Self>,
+        this: Model<Self>,
         message: TypedEnvelope<proto::UpdateChannels>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -736,7 +738,7 @@ impl ChannelStore {
             this.update_channels_tx
                 .unbounded_send(message.payload)
                 .unwrap();
-        });
+        })?;
         Ok(())
     }
 
@@ -750,7 +752,7 @@ impl ChannelStore {
 
         for chat in self.opened_chats.values() {
             if let OpenedModelHandle::Open(chat) = chat {
-                if let Some(chat) = chat.upgrade(cx) {
+                if let Some(chat) = chat.upgrade() {
                     chat.update(cx, |chat, cx| {
                         chat.rejoin(cx);
                     });
@@ -761,7 +763,7 @@ impl ChannelStore {
         let mut buffer_versions = Vec::new();
         for buffer in self.opened_buffers.values() {
             if let OpenedModelHandle::Open(buffer) = buffer {
-                if let Some(buffer) = buffer.upgrade(cx) {
+                if let Some(buffer) = buffer.upgrade() {
                     let channel_buffer = buffer.read(cx);
                     let buffer = channel_buffer.buffer().read(cx);
                     buffer_versions.push(proto::ChannelBufferVersion {
@@ -787,7 +789,7 @@ impl ChannelStore {
             this.update(&mut cx, |this, cx| {
                 this.opened_buffers.retain(|_, buffer| match buffer {
                     OpenedModelHandle::Open(channel_buffer) => {
-                        let Some(channel_buffer) = channel_buffer.upgrade(cx) else {
+                        let Some(channel_buffer) = channel_buffer.upgrade() else {
                             return false;
                         };
 
@@ -824,7 +826,7 @@ impl ChannelStore {
 
                                 if let Some(operations) = operations {
                                     let client = this.client.clone();
-                                    cx.background()
+                                    cx.background_executor()
                                         .spawn(async move {
                                             let operations = operations.await;
                                             for chunk in
@@ -849,7 +851,8 @@ impl ChannelStore {
                     }
                     OpenedModelHandle::Loading(_) => true,
                 });
-            });
+            })
+            .ok();
             anyhow::Ok(())
         })
     }
@@ -858,21 +861,22 @@ impl ChannelStore {
         cx.notify();
 
         self.disconnect_channel_buffers_task.get_or_insert_with(|| {
-            cx.spawn_weak(|this, mut cx| async move {
+            cx.spawn(move |this, mut cx| async move {
                 if wait_for_reconnect {
-                    cx.background().timer(RECONNECT_TIMEOUT).await;
+                    cx.background_executor().timer(RECONNECT_TIMEOUT).await;
                 }
 
-                if let Some(this) = this.upgrade(&cx) {
+                if let Some(this) = this.upgrade() {
                     this.update(&mut cx, |this, cx| {
                         for (_, buffer) in this.opened_buffers.drain() {
                             if let OpenedModelHandle::Open(buffer) = buffer {
-                                if let Some(buffer) = buffer.upgrade(cx) {
+                                if let Some(buffer) = buffer.upgrade() {
                                     buffer.update(cx, |buffer, cx| buffer.disconnect(cx));
                                 }
                             }
                         }
-                    });
+                    })
+                    .ok();
                 }
             })
         });
@@ -892,14 +896,16 @@ impl ChannelStore {
                 .channel_invitations
                 .binary_search_by_key(&channel.id, |c| c.id)
             {
-                Ok(ix) => Arc::make_mut(&mut self.channel_invitations[ix]).name = channel.name,
+                Ok(ix) => {
+                    Arc::make_mut(&mut self.channel_invitations[ix]).name = channel.name.into()
+                }
                 Err(ix) => self.channel_invitations.insert(
                     ix,
                     Arc::new(Channel {
                         id: channel.id,
                         visibility: channel.visibility(),
                         role: channel.role(),
-                        name: channel.name,
+                        name: channel.name.into(),
                         unseen_note_version: None,
                         unseen_message_id: None,
                         parent_path: channel.parent_path,
@@ -931,7 +937,7 @@ impl ChannelStore {
                     if let Some(OpenedModelHandle::Open(buffer)) =
                         self.opened_buffers.remove(&channel_id)
                     {
-                        if let Some(buffer) = buffer.upgrade(cx) {
+                        if let Some(buffer) = buffer.upgrade() {
                             buffer.update(cx, ChannelBuffer::disconnect);
                         }
                     }
@@ -945,7 +951,7 @@ impl ChannelStore {
 
                 if channel_changed {
                     if let Some(OpenedModelHandle::Open(buffer)) = self.opened_buffers.get(&id) {
-                        if let Some(buffer) = buffer.upgrade(cx) {
+                        if let Some(buffer) = buffer.upgrade() {
                             buffer.update(cx, ChannelBuffer::channel_changed);
                         }
                     }
@@ -1010,8 +1016,7 @@ impl ChannelStore {
                 }
 
                 cx.notify();
-            });
-            anyhow::Ok(())
+            })
         }))
     }
 }

crates/channel/src/channel_store/channel_index.rs 🔗

@@ -104,7 +104,7 @@ impl<'a> ChannelPathsInsertGuard<'a> {
 
             existing_channel.visibility = channel_proto.visibility();
             existing_channel.role = channel_proto.role();
-            existing_channel.name = channel_proto.name;
+            existing_channel.name = channel_proto.name.into();
         } else {
             self.channels_by_id.insert(
                 channel_proto.id,
@@ -112,7 +112,7 @@ impl<'a> ChannelPathsInsertGuard<'a> {
                     id: channel_proto.id,
                     visibility: channel_proto.visibility(),
                     role: channel_proto.role(),
-                    name: channel_proto.name,
+                    name: channel_proto.name.into(),
                     unseen_note_version: None,
                     unseen_message_id: None,
                     parent_path: channel_proto.parent_path,
@@ -146,11 +146,11 @@ fn channel_path_sorting_key<'a>(
     let (parent_path, name) = channels_by_id
         .get(&id)
         .map_or((&[] as &[_], None), |channel| {
-            (channel.parent_path.as_slice(), Some(channel.name.as_str()))
+            (channel.parent_path.as_slice(), Some(channel.name.as_ref()))
         });
     parent_path
         .iter()
-        .filter_map(|id| Some(channels_by_id.get(id)?.name.as_str()))
+        .filter_map(|id| Some(channels_by_id.get(id)?.name.as_ref()))
         .chain(name)
 }
 

crates/channel/src/channel_store_tests.rs 🔗

@@ -2,7 +2,7 @@ use crate::channel_chat::ChannelChatEvent;
 
 use super::*;
 use client::{test::FakeServer, Client, UserStore};
-use gpui::{AppContext, ModelHandle, TestAppContext};
+use gpui::{AppContext, Context, Model, TestAppContext};
 use rpc::proto::{self};
 use settings::SettingsStore;
 use util::http::FakeHttpClient;
@@ -147,7 +147,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
     let user_id = 5;
     let channel_id = 5;
     let channel_store = cx.update(init_test);
-    let client = channel_store.read_with(cx, |s, _| s.client());
+    let client = channel_store.update(cx, |s, _| s.client());
     let server = FakeServer::for_client(user_id, &client, cx).await;
 
     // Get the available channels.
@@ -161,8 +161,8 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
         }],
         ..Default::default()
     });
-    cx.foreground().run_until_parked();
-    cx.read(|cx| {
+    cx.executor().run_until_parked();
+    cx.update(|cx| {
         assert_channels(
             &channel_store,
             &[(0, "the-channel".to_string(), proto::ChannelRole::Member)],
@@ -214,7 +214,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
         },
     );
 
-    cx.foreground().start_waiting();
+    cx.executor().start_waiting();
 
     // Client requests all users for the received messages
     let mut get_users = server.receive::<proto::GetUsers>().await.unwrap();
@@ -232,7 +232,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
     );
 
     let channel = channel.await.unwrap();
-    channel.read_with(cx, |channel, _| {
+    channel.update(cx, |channel, _| {
         assert_eq!(
             channel
                 .messages_in_range(0..2)
@@ -273,13 +273,13 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
     );
 
     assert_eq!(
-        channel.next_event(cx).await,
+        channel.next_event(cx),
         ChannelChatEvent::MessagesUpdated {
             old_range: 2..2,
             new_count: 1,
         }
     );
-    channel.read_with(cx, |channel, _| {
+    channel.update(cx, |channel, _| {
         assert_eq!(
             channel
                 .messages_in_range(2..3)
@@ -322,13 +322,13 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
     );
 
     assert_eq!(
-        channel.next_event(cx).await,
+        channel.next_event(cx),
         ChannelChatEvent::MessagesUpdated {
             old_range: 0..0,
             new_count: 2,
         }
     );
-    channel.read_with(cx, |channel, _| {
+    channel.update(cx, |channel, _| {
         assert_eq!(
             channel
                 .messages_in_range(0..2)
@@ -342,13 +342,13 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
     });
 }
 
-fn init_test(cx: &mut AppContext) -> ModelHandle<ChannelStore> {
+fn init_test(cx: &mut AppContext) -> Model<ChannelStore> {
     let http = FakeHttpClient::with_404_response();
     let client = Client::new(http.clone(), cx);
-    let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
+    let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
 
-    cx.foreground().forbid_parking();
-    cx.set_global(SettingsStore::test(cx));
+    let settings_store = SettingsStore::test(cx);
+    cx.set_global(settings_store);
     client::init(&client, cx);
     crate::init(&client, user_store, cx);
 
@@ -356,7 +356,7 @@ fn init_test(cx: &mut AppContext) -> ModelHandle<ChannelStore> {
 }
 
 fn update_channels(
-    channel_store: &ModelHandle<ChannelStore>,
+    channel_store: &Model<ChannelStore>,
     message: proto::UpdateChannels,
     cx: &mut AppContext,
 ) {
@@ -366,11 +366,11 @@ fn update_channels(
 
 #[track_caller]
 fn assert_channels(
-    channel_store: &ModelHandle<ChannelStore>,
+    channel_store: &Model<ChannelStore>,
     expected_channels: &[(usize, String, proto::ChannelRole)],
-    cx: &AppContext,
+    cx: &mut AppContext,
 ) {
-    let actual = channel_store.read_with(cx, |store, _| {
+    let actual = channel_store.update(cx, |store, _| {
         store
             .ordered_channels()
             .map(|(depth, channel)| (depth, channel.name.to_string(), channel.role))

crates/channel2/Cargo.toml 🔗

@@ -1,54 +0,0 @@
-[package]
-name = "channel2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/channel2.rs"
-doctest = false
-
-[features]
-test-support = ["collections/test-support", "gpui/test-support", "rpc/test-support"]
-
-[dependencies]
-client = { package = "client2", path = "../client2" }
-collections = { path = "../collections" }
-db = { package = "db2", path = "../db2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-util = { path = "../util" }
-rpc = { package = "rpc2", path = "../rpc2" }
-text = { package = "text2", path = "../text2" }
-language = { package = "language2", path = "../language2" }
-settings = { package = "settings2", path = "../settings2" }
-feature_flags = { package = "feature_flags2", path = "../feature_flags2" }
-sum_tree = { path = "../sum_tree" }
-clock = { path = "../clock" }
-
-anyhow.workspace = true
-futures.workspace = true
-image = "0.23"
-lazy_static.workspace = true
-smallvec.workspace = true
-log.workspace = true
-parking_lot.workspace = true
-postage.workspace = true
-rand.workspace = true
-schemars.workspace = true
-smol.workspace = true
-thiserror.workspace = true
-time.workspace = true
-tiny_http = "0.8"
-uuid.workspace = true
-url = "2.2"
-serde.workspace = true
-serde_derive.workspace = true
-tempfile = "3"
-
-[dev-dependencies]
-collections = { path = "../collections", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
-client = { package = "client2", path = "../client2", features = ["test-support"] }
-settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
-util = { path = "../util", features = ["test-support"] }

crates/channel2/src/channel2.rs 🔗

@@ -1,23 +0,0 @@
-mod channel_buffer;
-mod channel_chat;
-mod channel_store;
-
-use client::{Client, UserStore};
-use gpui::{AppContext, Model};
-use std::sync::Arc;
-
-pub use channel_buffer::{ChannelBuffer, ChannelBufferEvent, ACKNOWLEDGE_DEBOUNCE_INTERVAL};
-pub use channel_chat::{
-    mentions_to_proto, ChannelChat, ChannelChatEvent, ChannelMessage, ChannelMessageId,
-    MessageParams,
-};
-pub use channel_store::{Channel, ChannelEvent, ChannelId, ChannelMembership, ChannelStore};
-
-#[cfg(test)]
-mod channel_store_tests;
-
-pub fn init(client: &Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
-    channel_store::init(client, user_store, cx);
-    channel_buffer::init(client);
-    channel_chat::init(client);
-}

crates/channel2/src/channel_buffer.rs 🔗

@@ -1,257 +0,0 @@
-use crate::{Channel, ChannelId, ChannelStore};
-use anyhow::Result;
-use client::{Client, Collaborator, UserStore};
-use collections::HashMap;
-use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task};
-use language::proto::serialize_version;
-use rpc::{
-    proto::{self, PeerId},
-    TypedEnvelope,
-};
-use std::{sync::Arc, time::Duration};
-use util::ResultExt;
-
-pub const ACKNOWLEDGE_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(250);
-
-pub(crate) fn init(client: &Arc<Client>) {
-    client.add_model_message_handler(ChannelBuffer::handle_update_channel_buffer);
-    client.add_model_message_handler(ChannelBuffer::handle_update_channel_buffer_collaborators);
-}
-
-pub struct ChannelBuffer {
-    pub channel_id: ChannelId,
-    connected: bool,
-    collaborators: HashMap<PeerId, Collaborator>,
-    user_store: Model<UserStore>,
-    channel_store: Model<ChannelStore>,
-    buffer: Model<language::Buffer>,
-    buffer_epoch: u64,
-    client: Arc<Client>,
-    subscription: Option<client::Subscription>,
-    acknowledge_task: Option<Task<Result<()>>>,
-}
-
-pub enum ChannelBufferEvent {
-    CollaboratorsChanged,
-    Disconnected,
-    BufferEdited,
-    ChannelChanged,
-}
-
-impl EventEmitter<ChannelBufferEvent> for ChannelBuffer {}
-
-impl ChannelBuffer {
-    pub(crate) async fn new(
-        channel: Arc<Channel>,
-        client: Arc<Client>,
-        user_store: Model<UserStore>,
-        channel_store: Model<ChannelStore>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Model<Self>> {
-        let response = client
-            .request(proto::JoinChannelBuffer {
-                channel_id: channel.id,
-            })
-            .await?;
-
-        let base_text = response.base_text;
-        let operations = response
-            .operations
-            .into_iter()
-            .map(language::proto::deserialize_operation)
-            .collect::<Result<Vec<_>, _>>()?;
-
-        let buffer = cx.new_model(|_| {
-            language::Buffer::remote(response.buffer_id, response.replica_id as u16, base_text)
-        })?;
-        buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))??;
-
-        let subscription = client.subscribe_to_entity(channel.id)?;
-
-        anyhow::Ok(cx.new_model(|cx| {
-            cx.subscribe(&buffer, Self::on_buffer_update).detach();
-            cx.on_release(Self::release).detach();
-            let mut this = Self {
-                buffer,
-                buffer_epoch: response.epoch,
-                client,
-                connected: true,
-                collaborators: Default::default(),
-                acknowledge_task: None,
-                channel_id: channel.id,
-                subscription: Some(subscription.set_model(&cx.handle(), &mut cx.to_async())),
-                user_store,
-                channel_store,
-            };
-            this.replace_collaborators(response.collaborators, cx);
-            this
-        })?)
-    }
-
-    fn release(&mut self, _: &mut AppContext) {
-        if self.connected {
-            if let Some(task) = self.acknowledge_task.take() {
-                task.detach();
-            }
-            self.client
-                .send(proto::LeaveChannelBuffer {
-                    channel_id: self.channel_id,
-                })
-                .log_err();
-        }
-    }
-
-    pub fn remote_id(&self, cx: &AppContext) -> u64 {
-        self.buffer.read(cx).remote_id()
-    }
-
-    pub fn user_store(&self) -> &Model<UserStore> {
-        &self.user_store
-    }
-
-    pub(crate) fn replace_collaborators(
-        &mut self,
-        collaborators: Vec<proto::Collaborator>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        let mut new_collaborators = HashMap::default();
-        for collaborator in collaborators {
-            if let Ok(collaborator) = Collaborator::from_proto(collaborator) {
-                new_collaborators.insert(collaborator.peer_id, collaborator);
-            }
-        }
-
-        for (_, old_collaborator) in &self.collaborators {
-            if !new_collaborators.contains_key(&old_collaborator.peer_id) {
-                self.buffer.update(cx, |buffer, cx| {
-                    buffer.remove_peer(old_collaborator.replica_id as u16, cx)
-                });
-            }
-        }
-        self.collaborators = new_collaborators;
-        cx.emit(ChannelBufferEvent::CollaboratorsChanged);
-        cx.notify();
-    }
-
-    async fn handle_update_channel_buffer(
-        this: Model<Self>,
-        update_channel_buffer: TypedEnvelope<proto::UpdateChannelBuffer>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        let ops = update_channel_buffer
-            .payload
-            .operations
-            .into_iter()
-            .map(language::proto::deserialize_operation)
-            .collect::<Result<Vec<_>, _>>()?;
-
-        this.update(&mut cx, |this, cx| {
-            cx.notify();
-            this.buffer
-                .update(cx, |buffer, cx| buffer.apply_ops(ops, cx))
-        })??;
-
-        Ok(())
-    }
-
-    async fn handle_update_channel_buffer_collaborators(
-        this: Model<Self>,
-        message: TypedEnvelope<proto::UpdateChannelBufferCollaborators>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        this.update(&mut cx, |this, cx| {
-            this.replace_collaborators(message.payload.collaborators, cx);
-            cx.emit(ChannelBufferEvent::CollaboratorsChanged);
-            cx.notify();
-        })
-    }
-
-    fn on_buffer_update(
-        &mut self,
-        _: Model<language::Buffer>,
-        event: &language::Event,
-        cx: &mut ModelContext<Self>,
-    ) {
-        match event {
-            language::Event::Operation(operation) => {
-                let operation = language::proto::serialize_operation(operation);
-                self.client
-                    .send(proto::UpdateChannelBuffer {
-                        channel_id: self.channel_id,
-                        operations: vec![operation],
-                    })
-                    .log_err();
-            }
-            language::Event::Edited => {
-                cx.emit(ChannelBufferEvent::BufferEdited);
-            }
-            _ => {}
-        }
-    }
-
-    pub fn acknowledge_buffer_version(&mut self, cx: &mut ModelContext<'_, ChannelBuffer>) {
-        let buffer = self.buffer.read(cx);
-        let version = buffer.version();
-        let buffer_id = buffer.remote_id();
-        let client = self.client.clone();
-        let epoch = self.epoch();
-
-        self.acknowledge_task = Some(cx.spawn(move |_, cx| async move {
-            cx.background_executor()
-                .timer(ACKNOWLEDGE_DEBOUNCE_INTERVAL)
-                .await;
-            client
-                .send(proto::AckBufferOperation {
-                    buffer_id,
-                    epoch,
-                    version: serialize_version(&version),
-                })
-                .ok();
-            Ok(())
-        }));
-    }
-
-    pub fn epoch(&self) -> u64 {
-        self.buffer_epoch
-    }
-
-    pub fn buffer(&self) -> Model<language::Buffer> {
-        self.buffer.clone()
-    }
-
-    pub fn collaborators(&self) -> &HashMap<PeerId, Collaborator> {
-        &self.collaborators
-    }
-
-    pub fn channel(&self, cx: &AppContext) -> Option<Arc<Channel>> {
-        self.channel_store
-            .read(cx)
-            .channel_for_id(self.channel_id)
-            .cloned()
-    }
-
-    pub(crate) fn disconnect(&mut self, cx: &mut ModelContext<Self>) {
-        log::info!("channel buffer {} disconnected", self.channel_id);
-        if self.connected {
-            self.connected = false;
-            self.subscription.take();
-            cx.emit(ChannelBufferEvent::Disconnected);
-            cx.notify()
-        }
-    }
-
-    pub(crate) fn channel_changed(&mut self, cx: &mut ModelContext<Self>) {
-        cx.emit(ChannelBufferEvent::ChannelChanged);
-        cx.notify()
-    }
-
-    pub fn is_connected(&self) -> bool {
-        self.connected
-    }
-
-    pub fn replica_id(&self, cx: &AppContext) -> u16 {
-        self.buffer.read(cx).replica_id()
-    }
-}

crates/channel2/src/channel_chat.rs 🔗

@@ -1,645 +0,0 @@
-use crate::{Channel, ChannelId, ChannelStore};
-use anyhow::{anyhow, Result};
-use client::{
-    proto,
-    user::{User, UserStore},
-    Client, Subscription, TypedEnvelope, UserId,
-};
-use futures::lock::Mutex;
-use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task};
-use rand::prelude::*;
-use std::{
-    collections::HashSet,
-    mem,
-    ops::{ControlFlow, Range},
-    sync::Arc,
-};
-use sum_tree::{Bias, SumTree};
-use time::OffsetDateTime;
-use util::{post_inc, ResultExt as _, TryFutureExt};
-
-pub struct ChannelChat {
-    pub channel_id: ChannelId,
-    messages: SumTree<ChannelMessage>,
-    acknowledged_message_ids: HashSet<u64>,
-    channel_store: Model<ChannelStore>,
-    loaded_all_messages: bool,
-    last_acknowledged_id: Option<u64>,
-    next_pending_message_id: usize,
-    user_store: Model<UserStore>,
-    rpc: Arc<Client>,
-    outgoing_messages_lock: Arc<Mutex<()>>,
-    rng: StdRng,
-    _subscription: Subscription,
-}
-
-#[derive(Debug, PartialEq, Eq)]
-pub struct MessageParams {
-    pub text: String,
-    pub mentions: Vec<(Range<usize>, UserId)>,
-}
-
-#[derive(Clone, Debug)]
-pub struct ChannelMessage {
-    pub id: ChannelMessageId,
-    pub body: String,
-    pub timestamp: OffsetDateTime,
-    pub sender: Arc<User>,
-    pub nonce: u128,
-    pub mentions: Vec<(Range<usize>, UserId)>,
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub enum ChannelMessageId {
-    Saved(u64),
-    Pending(usize),
-}
-
-#[derive(Clone, Debug, Default)]
-pub struct ChannelMessageSummary {
-    max_id: ChannelMessageId,
-    count: usize,
-}
-
-#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
-struct Count(usize);
-
-#[derive(Clone, Debug, PartialEq)]
-pub enum ChannelChatEvent {
-    MessagesUpdated {
-        old_range: Range<usize>,
-        new_count: usize,
-    },
-    NewMessage {
-        channel_id: ChannelId,
-        message_id: u64,
-    },
-}
-
-impl EventEmitter<ChannelChatEvent> for ChannelChat {}
-pub fn init(client: &Arc<Client>) {
-    client.add_model_message_handler(ChannelChat::handle_message_sent);
-    client.add_model_message_handler(ChannelChat::handle_message_removed);
-}
-
-impl ChannelChat {
-    pub async fn new(
-        channel: Arc<Channel>,
-        channel_store: Model<ChannelStore>,
-        user_store: Model<UserStore>,
-        client: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Model<Self>> {
-        let channel_id = channel.id;
-        let subscription = client.subscribe_to_entity(channel_id).unwrap();
-
-        let response = client
-            .request(proto::JoinChannelChat { channel_id })
-            .await?;
-        let messages = messages_from_proto(response.messages, &user_store, &mut cx).await?;
-        let loaded_all_messages = response.done;
-
-        Ok(cx.new_model(|cx| {
-            cx.on_release(Self::release).detach();
-            let mut this = Self {
-                channel_id: channel.id,
-                user_store,
-                channel_store,
-                rpc: client,
-                outgoing_messages_lock: Default::default(),
-                messages: Default::default(),
-                acknowledged_message_ids: Default::default(),
-                loaded_all_messages,
-                next_pending_message_id: 0,
-                last_acknowledged_id: None,
-                rng: StdRng::from_entropy(),
-                _subscription: subscription.set_model(&cx.handle(), &mut cx.to_async()),
-            };
-            this.insert_messages(messages, cx);
-            this
-        })?)
-    }
-
-    fn release(&mut self, _: &mut AppContext) {
-        self.rpc
-            .send(proto::LeaveChannelChat {
-                channel_id: self.channel_id,
-            })
-            .log_err();
-    }
-
-    pub fn channel(&self, cx: &AppContext) -> Option<Arc<Channel>> {
-        self.channel_store
-            .read(cx)
-            .channel_for_id(self.channel_id)
-            .cloned()
-    }
-
-    pub fn client(&self) -> &Arc<Client> {
-        &self.rpc
-    }
-
-    pub fn send_message(
-        &mut self,
-        message: MessageParams,
-        cx: &mut ModelContext<Self>,
-    ) -> Result<Task<Result<u64>>> {
-        if message.text.is_empty() {
-            Err(anyhow!("message body can't be empty"))?;
-        }
-
-        let current_user = self
-            .user_store
-            .read(cx)
-            .current_user()
-            .ok_or_else(|| anyhow!("current_user is not present"))?;
-
-        let channel_id = self.channel_id;
-        let pending_id = ChannelMessageId::Pending(post_inc(&mut self.next_pending_message_id));
-        let nonce = self.rng.gen();
-        self.insert_messages(
-            SumTree::from_item(
-                ChannelMessage {
-                    id: pending_id,
-                    body: message.text.clone(),
-                    sender: current_user,
-                    timestamp: OffsetDateTime::now_utc(),
-                    mentions: message.mentions.clone(),
-                    nonce,
-                },
-                &(),
-            ),
-            cx,
-        );
-        let user_store = self.user_store.clone();
-        let rpc = self.rpc.clone();
-        let outgoing_messages_lock = self.outgoing_messages_lock.clone();
-        Ok(cx.spawn(move |this, mut cx| async move {
-            let outgoing_message_guard = outgoing_messages_lock.lock().await;
-            let request = rpc.request(proto::SendChannelMessage {
-                channel_id,
-                body: message.text,
-                nonce: Some(nonce.into()),
-                mentions: mentions_to_proto(&message.mentions),
-            });
-            let response = request.await?;
-            drop(outgoing_message_guard);
-            let response = response.message.ok_or_else(|| anyhow!("invalid message"))?;
-            let id = response.id;
-            let message = ChannelMessage::from_proto(response, &user_store, &mut cx).await?;
-            this.update(&mut cx, |this, cx| {
-                this.insert_messages(SumTree::from_item(message, &()), cx);
-            })?;
-            Ok(id)
-        }))
-    }
-
-    pub fn remove_message(&mut self, id: u64, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
-        let response = self.rpc.request(proto::RemoveChannelMessage {
-            channel_id: self.channel_id,
-            message_id: id,
-        });
-        cx.spawn(move |this, mut cx| async move {
-            response.await?;
-            this.update(&mut cx, |this, cx| {
-                this.message_removed(id, cx);
-            })?;
-            Ok(())
-        })
-    }
-
-    pub fn load_more_messages(&mut self, cx: &mut ModelContext<Self>) -> Option<Task<Option<()>>> {
-        if self.loaded_all_messages {
-            return None;
-        }
-
-        let rpc = self.rpc.clone();
-        let user_store = self.user_store.clone();
-        let channel_id = self.channel_id;
-        let before_message_id = self.first_loaded_message_id()?;
-        Some(cx.spawn(move |this, mut cx| {
-            async move {
-                let response = rpc
-                    .request(proto::GetChannelMessages {
-                        channel_id,
-                        before_message_id,
-                    })
-                    .await?;
-                let loaded_all_messages = response.done;
-                let messages = messages_from_proto(response.messages, &user_store, &mut cx).await?;
-                this.update(&mut cx, |this, cx| {
-                    this.loaded_all_messages = loaded_all_messages;
-                    this.insert_messages(messages, cx);
-                })?;
-                anyhow::Ok(())
-            }
-            .log_err()
-        }))
-    }
-
-    pub fn first_loaded_message_id(&mut self) -> Option<u64> {
-        self.messages.first().and_then(|message| match message.id {
-            ChannelMessageId::Saved(id) => Some(id),
-            ChannelMessageId::Pending(_) => None,
-        })
-    }
-
-    /// Load all of the chat messages since a certain message id.
-    ///
-    /// For now, we always maintain a suffix of the channel's messages.
-    pub async fn load_history_since_message(
-        chat: Model<Self>,
-        message_id: u64,
-        mut cx: AsyncAppContext,
-    ) -> Option<usize> {
-        loop {
-            let step = chat
-                .update(&mut cx, |chat, cx| {
-                    if let Some(first_id) = chat.first_loaded_message_id() {
-                        if first_id <= message_id {
-                            let mut cursor = chat.messages.cursor::<(ChannelMessageId, Count)>();
-                            let message_id = ChannelMessageId::Saved(message_id);
-                            cursor.seek(&message_id, Bias::Left, &());
-                            return ControlFlow::Break(
-                                if cursor
-                                    .item()
-                                    .map_or(false, |message| message.id == message_id)
-                                {
-                                    Some(cursor.start().1 .0)
-                                } else {
-                                    None
-                                },
-                            );
-                        }
-                    }
-                    ControlFlow::Continue(chat.load_more_messages(cx))
-                })
-                .log_err()?;
-            match step {
-                ControlFlow::Break(ix) => return ix,
-                ControlFlow::Continue(task) => task?.await?,
-            }
-        }
-    }
-
-    pub fn acknowledge_last_message(&mut self, cx: &mut ModelContext<Self>) {
-        if let ChannelMessageId::Saved(latest_message_id) = self.messages.summary().max_id {
-            if self
-                .last_acknowledged_id
-                .map_or(true, |acknowledged_id| acknowledged_id < latest_message_id)
-            {
-                self.rpc
-                    .send(proto::AckChannelMessage {
-                        channel_id: self.channel_id,
-                        message_id: latest_message_id,
-                    })
-                    .ok();
-                self.last_acknowledged_id = Some(latest_message_id);
-                self.channel_store.update(cx, |store, cx| {
-                    store.acknowledge_message_id(self.channel_id, latest_message_id, cx);
-                });
-            }
-        }
-    }
-
-    pub fn rejoin(&mut self, cx: &mut ModelContext<Self>) {
-        let user_store = self.user_store.clone();
-        let rpc = self.rpc.clone();
-        let channel_id = self.channel_id;
-        cx.spawn(move |this, mut cx| {
-            async move {
-                let response = rpc.request(proto::JoinChannelChat { channel_id }).await?;
-                let messages = messages_from_proto(response.messages, &user_store, &mut cx).await?;
-                let loaded_all_messages = response.done;
-
-                let pending_messages = this.update(&mut cx, |this, cx| {
-                    if let Some((first_new_message, last_old_message)) =
-                        messages.first().zip(this.messages.last())
-                    {
-                        if first_new_message.id > last_old_message.id {
-                            let old_messages = mem::take(&mut this.messages);
-                            cx.emit(ChannelChatEvent::MessagesUpdated {
-                                old_range: 0..old_messages.summary().count,
-                                new_count: 0,
-                            });
-                            this.loaded_all_messages = loaded_all_messages;
-                        }
-                    }
-
-                    this.insert_messages(messages, cx);
-                    if loaded_all_messages {
-                        this.loaded_all_messages = loaded_all_messages;
-                    }
-
-                    this.pending_messages().cloned().collect::<Vec<_>>()
-                })?;
-
-                for pending_message in pending_messages {
-                    let request = rpc.request(proto::SendChannelMessage {
-                        channel_id,
-                        body: pending_message.body,
-                        mentions: mentions_to_proto(&pending_message.mentions),
-                        nonce: Some(pending_message.nonce.into()),
-                    });
-                    let response = request.await?;
-                    let message = ChannelMessage::from_proto(
-                        response.message.ok_or_else(|| anyhow!("invalid message"))?,
-                        &user_store,
-                        &mut cx,
-                    )
-                    .await?;
-                    this.update(&mut cx, |this, cx| {
-                        this.insert_messages(SumTree::from_item(message, &()), cx);
-                    })?;
-                }
-
-                anyhow::Ok(())
-            }
-            .log_err()
-        })
-        .detach();
-    }
-
-    pub fn message_count(&self) -> usize {
-        self.messages.summary().count
-    }
-
-    pub fn messages(&self) -> &SumTree<ChannelMessage> {
-        &self.messages
-    }
-
-    pub fn message(&self, ix: usize) -> &ChannelMessage {
-        let mut cursor = self.messages.cursor::<Count>();
-        cursor.seek(&Count(ix), Bias::Right, &());
-        cursor.item().unwrap()
-    }
-
-    pub fn acknowledge_message(&mut self, id: u64) {
-        if self.acknowledged_message_ids.insert(id) {
-            self.rpc
-                .send(proto::AckChannelMessage {
-                    channel_id: self.channel_id,
-                    message_id: id,
-                })
-                .ok();
-        }
-    }
-
-    pub fn messages_in_range(&self, range: Range<usize>) -> impl Iterator<Item = &ChannelMessage> {
-        let mut cursor = self.messages.cursor::<Count>();
-        cursor.seek(&Count(range.start), Bias::Right, &());
-        cursor.take(range.len())
-    }
-
-    pub fn pending_messages(&self) -> impl Iterator<Item = &ChannelMessage> {
-        let mut cursor = self.messages.cursor::<ChannelMessageId>();
-        cursor.seek(&ChannelMessageId::Pending(0), Bias::Left, &());
-        cursor
-    }
-
-    async fn handle_message_sent(
-        this: Model<Self>,
-        message: TypedEnvelope<proto::ChannelMessageSent>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
-        let message = message
-            .payload
-            .message
-            .ok_or_else(|| anyhow!("empty message"))?;
-        let message_id = message.id;
-
-        let message = ChannelMessage::from_proto(message, &user_store, &mut cx).await?;
-        this.update(&mut cx, |this, cx| {
-            this.insert_messages(SumTree::from_item(message, &()), cx);
-            cx.emit(ChannelChatEvent::NewMessage {
-                channel_id: this.channel_id,
-                message_id,
-            })
-        })?;
-
-        Ok(())
-    }
-
-    async fn handle_message_removed(
-        this: Model<Self>,
-        message: TypedEnvelope<proto::RemoveChannelMessage>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        this.update(&mut cx, |this, cx| {
-            this.message_removed(message.payload.message_id, cx)
-        })?;
-        Ok(())
-    }
-
-    fn insert_messages(&mut self, messages: SumTree<ChannelMessage>, cx: &mut ModelContext<Self>) {
-        if let Some((first_message, last_message)) = messages.first().zip(messages.last()) {
-            let nonces = messages
-                .cursor::<()>()
-                .map(|m| m.nonce)
-                .collect::<HashSet<_>>();
-
-            let mut old_cursor = self.messages.cursor::<(ChannelMessageId, Count)>();
-            let mut new_messages = old_cursor.slice(&first_message.id, Bias::Left, &());
-            let start_ix = old_cursor.start().1 .0;
-            let removed_messages = old_cursor.slice(&last_message.id, Bias::Right, &());
-            let removed_count = removed_messages.summary().count;
-            let new_count = messages.summary().count;
-            let end_ix = start_ix + removed_count;
-
-            new_messages.append(messages, &());
-
-            let mut ranges = Vec::<Range<usize>>::new();
-            if new_messages.last().unwrap().is_pending() {
-                new_messages.append(old_cursor.suffix(&()), &());
-            } else {
-                new_messages.append(
-                    old_cursor.slice(&ChannelMessageId::Pending(0), Bias::Left, &()),
-                    &(),
-                );
-
-                while let Some(message) = old_cursor.item() {
-                    let message_ix = old_cursor.start().1 .0;
-                    if nonces.contains(&message.nonce) {
-                        if ranges.last().map_or(false, |r| r.end == message_ix) {
-                            ranges.last_mut().unwrap().end += 1;
-                        } else {
-                            ranges.push(message_ix..message_ix + 1);
-                        }
-                    } else {
-                        new_messages.push(message.clone(), &());
-                    }
-                    old_cursor.next(&());
-                }
-            }
-
-            drop(old_cursor);
-            self.messages = new_messages;
-
-            for range in ranges.into_iter().rev() {
-                cx.emit(ChannelChatEvent::MessagesUpdated {
-                    old_range: range,
-                    new_count: 0,
-                });
-            }
-            cx.emit(ChannelChatEvent::MessagesUpdated {
-                old_range: start_ix..end_ix,
-                new_count,
-            });
-
-            cx.notify();
-        }
-    }
-
-    fn message_removed(&mut self, id: u64, cx: &mut ModelContext<Self>) {
-        let mut cursor = self.messages.cursor::<ChannelMessageId>();
-        let mut messages = cursor.slice(&ChannelMessageId::Saved(id), Bias::Left, &());
-        if let Some(item) = cursor.item() {
-            if item.id == ChannelMessageId::Saved(id) {
-                let ix = messages.summary().count;
-                cursor.next(&());
-                messages.append(cursor.suffix(&()), &());
-                drop(cursor);
-                self.messages = messages;
-                cx.emit(ChannelChatEvent::MessagesUpdated {
-                    old_range: ix..ix + 1,
-                    new_count: 0,
-                });
-            }
-        }
-    }
-}
-
-async fn messages_from_proto(
-    proto_messages: Vec<proto::ChannelMessage>,
-    user_store: &Model<UserStore>,
-    cx: &mut AsyncAppContext,
-) -> Result<SumTree<ChannelMessage>> {
-    let messages = ChannelMessage::from_proto_vec(proto_messages, user_store, cx).await?;
-    let mut result = SumTree::new();
-    result.extend(messages, &());
-    Ok(result)
-}
-
-impl ChannelMessage {
-    pub async fn from_proto(
-        message: proto::ChannelMessage,
-        user_store: &Model<UserStore>,
-        cx: &mut AsyncAppContext,
-    ) -> Result<Self> {
-        let sender = user_store
-            .update(cx, |user_store, cx| {
-                user_store.get_user(message.sender_id, cx)
-            })?
-            .await?;
-        Ok(ChannelMessage {
-            id: ChannelMessageId::Saved(message.id),
-            body: message.body,
-            mentions: message
-                .mentions
-                .into_iter()
-                .filter_map(|mention| {
-                    let range = mention.range?;
-                    Some((range.start as usize..range.end as usize, mention.user_id))
-                })
-                .collect(),
-            timestamp: OffsetDateTime::from_unix_timestamp(message.timestamp as i64)?,
-            sender,
-            nonce: message
-                .nonce
-                .ok_or_else(|| anyhow!("nonce is required"))?
-                .into(),
-        })
-    }
-
-    pub fn is_pending(&self) -> bool {
-        matches!(self.id, ChannelMessageId::Pending(_))
-    }
-
-    pub async fn from_proto_vec(
-        proto_messages: Vec<proto::ChannelMessage>,
-        user_store: &Model<UserStore>,
-        cx: &mut AsyncAppContext,
-    ) -> Result<Vec<Self>> {
-        let unique_user_ids = proto_messages
-            .iter()
-            .map(|m| m.sender_id)
-            .collect::<HashSet<_>>()
-            .into_iter()
-            .collect();
-        user_store
-            .update(cx, |user_store, cx| {
-                user_store.get_users(unique_user_ids, cx)
-            })?
-            .await?;
-
-        let mut messages = Vec::with_capacity(proto_messages.len());
-        for message in proto_messages {
-            messages.push(ChannelMessage::from_proto(message, user_store, cx).await?);
-        }
-        Ok(messages)
-    }
-}
-
-pub fn mentions_to_proto(mentions: &[(Range<usize>, UserId)]) -> Vec<proto::ChatMention> {
-    mentions
-        .iter()
-        .map(|(range, user_id)| proto::ChatMention {
-            range: Some(proto::Range {
-                start: range.start as u64,
-                end: range.end as u64,
-            }),
-            user_id: *user_id as u64,
-        })
-        .collect()
-}
-
-impl sum_tree::Item for ChannelMessage {
-    type Summary = ChannelMessageSummary;
-
-    fn summary(&self) -> Self::Summary {
-        ChannelMessageSummary {
-            max_id: self.id,
-            count: 1,
-        }
-    }
-}
-
-impl Default for ChannelMessageId {
-    fn default() -> Self {
-        Self::Saved(0)
-    }
-}
-
-impl sum_tree::Summary for ChannelMessageSummary {
-    type Context = ();
-
-    fn add_summary(&mut self, summary: &Self, _: &()) {
-        self.max_id = summary.max_id;
-        self.count += summary.count;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, ChannelMessageSummary> for ChannelMessageId {
-    fn add_summary(&mut self, summary: &'a ChannelMessageSummary, _: &()) {
-        debug_assert!(summary.max_id > *self);
-        *self = summary.max_id;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, ChannelMessageSummary> for Count {
-    fn add_summary(&mut self, summary: &'a ChannelMessageSummary, _: &()) {
-        self.0 += summary.count;
-    }
-}
-
-impl<'a> From<&'a str> for MessageParams {
-    fn from(value: &'a str) -> Self {
-        Self {
-            text: value.into(),
-            mentions: Vec::new(),
-        }
-    }
-}

crates/channel2/src/channel_store.rs 🔗

@@ -1,1022 +0,0 @@
-mod channel_index;
-
-use crate::{channel_buffer::ChannelBuffer, channel_chat::ChannelChat, ChannelMessage};
-use anyhow::{anyhow, Result};
-use channel_index::ChannelIndex;
-use client::{Client, Subscription, User, UserId, UserStore};
-use collections::{hash_map, HashMap, HashSet};
-use db::RELEASE_CHANNEL;
-use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
-use gpui::{
-    AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, SharedString, Task,
-    WeakModel,
-};
-use rpc::{
-    proto::{self, ChannelVisibility},
-    TypedEnvelope,
-};
-use std::{mem, sync::Arc, time::Duration};
-use util::{async_maybe, ResultExt};
-
-pub fn init(client: &Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
-    let channel_store =
-        cx.new_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
-    cx.set_global(channel_store);
-}
-
-pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
-
-pub type ChannelId = u64;
-
-pub struct ChannelStore {
-    pub channel_index: ChannelIndex,
-    channel_invitations: Vec<Arc<Channel>>,
-    channel_participants: HashMap<ChannelId, Vec<Arc<User>>>,
-    outgoing_invites: HashSet<(ChannelId, UserId)>,
-    update_channels_tx: mpsc::UnboundedSender<proto::UpdateChannels>,
-    opened_buffers: HashMap<ChannelId, OpenedModelHandle<ChannelBuffer>>,
-    opened_chats: HashMap<ChannelId, OpenedModelHandle<ChannelChat>>,
-    client: Arc<Client>,
-    user_store: Model<UserStore>,
-    _rpc_subscription: Subscription,
-    _watch_connection_status: Task<Option<()>>,
-    disconnect_channel_buffers_task: Option<Task<()>>,
-    _update_channels: Task<()>,
-}
-
-#[derive(Clone, Debug, PartialEq)]
-pub struct Channel {
-    pub id: ChannelId,
-    pub name: SharedString,
-    pub visibility: proto::ChannelVisibility,
-    pub role: proto::ChannelRole,
-    pub unseen_note_version: Option<(u64, clock::Global)>,
-    pub unseen_message_id: Option<u64>,
-    pub parent_path: Vec<u64>,
-}
-
-impl Channel {
-    pub fn link(&self) -> String {
-        RELEASE_CHANNEL.link_prefix().to_owned()
-            + "channel/"
-            + &self.slug()
-            + "-"
-            + &self.id.to_string()
-    }
-
-    pub fn slug(&self) -> String {
-        let slug: String = self
-            .name
-            .chars()
-            .map(|c| if c.is_alphanumeric() { c } else { '-' })
-            .collect();
-
-        slug.trim_matches(|c| c == '-').to_string()
-    }
-
-    pub fn can_edit_notes(&self) -> bool {
-        self.role == proto::ChannelRole::Member || self.role == proto::ChannelRole::Admin
-    }
-}
-
-pub struct ChannelMembership {
-    pub user: Arc<User>,
-    pub kind: proto::channel_member::Kind,
-    pub role: proto::ChannelRole,
-}
-impl ChannelMembership {
-    pub fn sort_key(&self) -> MembershipSortKey {
-        MembershipSortKey {
-            role_order: match self.role {
-                proto::ChannelRole::Admin => 0,
-                proto::ChannelRole::Member => 1,
-                proto::ChannelRole::Banned => 2,
-                proto::ChannelRole::Guest => 3,
-            },
-            kind_order: match self.kind {
-                proto::channel_member::Kind::Member => 0,
-                proto::channel_member::Kind::AncestorMember => 1,
-                proto::channel_member::Kind::Invitee => 2,
-            },
-            username_order: self.user.github_login.as_str(),
-        }
-    }
-}
-
-#[derive(PartialOrd, Ord, PartialEq, Eq)]
-pub struct MembershipSortKey<'a> {
-    role_order: u8,
-    kind_order: u8,
-    username_order: &'a str,
-}
-
-pub enum ChannelEvent {
-    ChannelCreated(ChannelId),
-    ChannelRenamed(ChannelId),
-}
-
-impl EventEmitter<ChannelEvent> for ChannelStore {}
-
-enum OpenedModelHandle<E> {
-    Open(WeakModel<E>),
-    Loading(Shared<Task<Result<Model<E>, Arc<anyhow::Error>>>>),
-}
-
-impl ChannelStore {
-    pub fn global(cx: &AppContext) -> Model<Self> {
-        cx.global::<Model<Self>>().clone()
-    }
-
-    pub fn new(
-        client: Arc<Client>,
-        user_store: Model<UserStore>,
-        cx: &mut ModelContext<Self>,
-    ) -> Self {
-        let rpc_subscription =
-            client.add_message_handler(cx.weak_model(), Self::handle_update_channels);
-
-        let mut connection_status = client.status();
-        let (update_channels_tx, mut update_channels_rx) = mpsc::unbounded();
-        let watch_connection_status = cx.spawn(|this, mut cx| async move {
-            while let Some(status) = connection_status.next().await {
-                let this = this.upgrade()?;
-                match status {
-                    client::Status::Connected { .. } => {
-                        this.update(&mut cx, |this, cx| this.handle_connect(cx))
-                            .ok()?
-                            .await
-                            .log_err()?;
-                    }
-                    client::Status::SignedOut | client::Status::UpgradeRequired => {
-                        this.update(&mut cx, |this, cx| this.handle_disconnect(false, cx))
-                            .ok();
-                    }
-                    _ => {
-                        this.update(&mut cx, |this, cx| this.handle_disconnect(true, cx))
-                            .ok();
-                    }
-                }
-            }
-            Some(())
-        });
-
-        Self {
-            channel_invitations: Vec::default(),
-            channel_index: ChannelIndex::default(),
-            channel_participants: Default::default(),
-            outgoing_invites: Default::default(),
-            opened_buffers: Default::default(),
-            opened_chats: Default::default(),
-            update_channels_tx,
-            client,
-            user_store,
-            _rpc_subscription: rpc_subscription,
-            _watch_connection_status: watch_connection_status,
-            disconnect_channel_buffers_task: None,
-            _update_channels: cx.spawn(|this, mut cx| async move {
-                async_maybe!({
-                    while let Some(update_channels) = update_channels_rx.next().await {
-                        if let Some(this) = this.upgrade() {
-                            let update_task = this.update(&mut cx, |this, cx| {
-                                this.update_channels(update_channels, cx)
-                            })?;
-                            if let Some(update_task) = update_task {
-                                update_task.await.log_err();
-                            }
-                        }
-                    }
-                    anyhow::Ok(())
-                })
-                .await
-                .log_err();
-            }),
-        }
-    }
-
-    pub fn client(&self) -> Arc<Client> {
-        self.client.clone()
-    }
-
-    /// Returns the number of unique channels in the store
-    pub fn channel_count(&self) -> usize {
-        self.channel_index.by_id().len()
-    }
-
-    /// Returns the index of a channel ID in the list of unique channels
-    pub fn index_of_channel(&self, channel_id: ChannelId) -> Option<usize> {
-        self.channel_index
-            .by_id()
-            .keys()
-            .position(|id| *id == channel_id)
-    }
-
-    /// Returns an iterator over all unique channels
-    pub fn channels(&self) -> impl '_ + Iterator<Item = &Arc<Channel>> {
-        self.channel_index.by_id().values()
-    }
-
-    /// Iterate over all entries in the channel DAG
-    pub fn ordered_channels(&self) -> impl '_ + Iterator<Item = (usize, &Arc<Channel>)> {
-        self.channel_index
-            .ordered_channels()
-            .iter()
-            .filter_map(move |id| {
-                let channel = self.channel_index.by_id().get(id)?;
-                Some((channel.parent_path.len(), channel))
-            })
-    }
-
-    pub fn channel_at_index(&self, ix: usize) -> Option<&Arc<Channel>> {
-        let channel_id = self.channel_index.ordered_channels().get(ix)?;
-        self.channel_index.by_id().get(channel_id)
-    }
-
-    pub fn channel_at(&self, ix: usize) -> Option<&Arc<Channel>> {
-        self.channel_index.by_id().values().nth(ix)
-    }
-
-    pub fn has_channel_invitation(&self, channel_id: ChannelId) -> bool {
-        self.channel_invitations
-            .iter()
-            .any(|channel| channel.id == channel_id)
-    }
-
-    pub fn channel_invitations(&self) -> &[Arc<Channel>] {
-        &self.channel_invitations
-    }
-
-    pub fn channel_for_id(&self, channel_id: ChannelId) -> Option<&Arc<Channel>> {
-        self.channel_index.by_id().get(&channel_id)
-    }
-
-    pub fn has_open_channel_buffer(&self, channel_id: ChannelId, _cx: &AppContext) -> bool {
-        if let Some(buffer) = self.opened_buffers.get(&channel_id) {
-            if let OpenedModelHandle::Open(buffer) = buffer {
-                return buffer.upgrade().is_some();
-            }
-        }
-        false
-    }
-
-    pub fn open_channel_buffer(
-        &mut self,
-        channel_id: ChannelId,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Model<ChannelBuffer>>> {
-        let client = self.client.clone();
-        let user_store = self.user_store.clone();
-        let channel_store = cx.handle();
-        self.open_channel_resource(
-            channel_id,
-            |this| &mut this.opened_buffers,
-            |channel, cx| ChannelBuffer::new(channel, client, user_store, channel_store, cx),
-            cx,
-        )
-    }
-
-    pub fn fetch_channel_messages(
-        &self,
-        message_ids: Vec<u64>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<ChannelMessage>>> {
-        let request = if message_ids.is_empty() {
-            None
-        } else {
-            Some(
-                self.client
-                    .request(proto::GetChannelMessagesById { message_ids }),
-            )
-        };
-        cx.spawn(|this, mut cx| async move {
-            if let Some(request) = request {
-                let response = request.await?;
-                let this = this
-                    .upgrade()
-                    .ok_or_else(|| anyhow!("channel store dropped"))?;
-                let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
-                ChannelMessage::from_proto_vec(response.messages, &user_store, &mut cx).await
-            } else {
-                Ok(Vec::new())
-            }
-        })
-    }
-
-    pub fn has_channel_buffer_changed(&self, channel_id: ChannelId) -> Option<bool> {
-        self.channel_index
-            .by_id()
-            .get(&channel_id)
-            .map(|channel| channel.unseen_note_version.is_some())
-    }
-
-    pub fn has_new_messages(&self, channel_id: ChannelId) -> Option<bool> {
-        self.channel_index
-            .by_id()
-            .get(&channel_id)
-            .map(|channel| channel.unseen_message_id.is_some())
-    }
-
-    pub fn notes_changed(
-        &mut self,
-        channel_id: ChannelId,
-        epoch: u64,
-        version: &clock::Global,
-        cx: &mut ModelContext<Self>,
-    ) {
-        self.channel_index.note_changed(channel_id, epoch, version);
-        cx.notify();
-    }
-
-    pub fn new_message(
-        &mut self,
-        channel_id: ChannelId,
-        message_id: u64,
-        cx: &mut ModelContext<Self>,
-    ) {
-        self.channel_index.new_message(channel_id, message_id);
-        cx.notify();
-    }
-
-    pub fn acknowledge_message_id(
-        &mut self,
-        channel_id: ChannelId,
-        message_id: u64,
-        cx: &mut ModelContext<Self>,
-    ) {
-        self.channel_index
-            .acknowledge_message_id(channel_id, message_id);
-        cx.notify();
-    }
-
-    pub fn acknowledge_notes_version(
-        &mut self,
-        channel_id: ChannelId,
-        epoch: u64,
-        version: &clock::Global,
-        cx: &mut ModelContext<Self>,
-    ) {
-        self.channel_index
-            .acknowledge_note_version(channel_id, epoch, version);
-        cx.notify();
-    }
-
-    pub fn open_channel_chat(
-        &mut self,
-        channel_id: ChannelId,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Model<ChannelChat>>> {
-        let client = self.client.clone();
-        let user_store = self.user_store.clone();
-        let this = cx.handle();
-        self.open_channel_resource(
-            channel_id,
-            |this| &mut this.opened_chats,
-            |channel, cx| ChannelChat::new(channel, this, user_store, client, cx),
-            cx,
-        )
-    }
-
-    /// Asynchronously open a given resource associated with a channel.
-    ///
-    /// Make sure that the resource is only opened once, even if this method
-    /// is called multiple times with the same channel id while the first task
-    /// is still running.
-    fn open_channel_resource<T, F, Fut>(
-        &mut self,
-        channel_id: ChannelId,
-        get_map: fn(&mut Self) -> &mut HashMap<ChannelId, OpenedModelHandle<T>>,
-        load: F,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Model<T>>>
-    where
-        F: 'static + FnOnce(Arc<Channel>, AsyncAppContext) -> Fut,
-        Fut: Future<Output = Result<Model<T>>>,
-        T: 'static,
-    {
-        let task = loop {
-            match get_map(self).entry(channel_id) {
-                hash_map::Entry::Occupied(e) => match e.get() {
-                    OpenedModelHandle::Open(model) => {
-                        if let Some(model) = model.upgrade() {
-                            break Task::ready(Ok(model)).shared();
-                        } else {
-                            get_map(self).remove(&channel_id);
-                            continue;
-                        }
-                    }
-                    OpenedModelHandle::Loading(task) => {
-                        break task.clone();
-                    }
-                },
-                hash_map::Entry::Vacant(e) => {
-                    let task = cx
-                        .spawn(move |this, mut cx| async move {
-                            let channel = this.update(&mut cx, |this, _| {
-                                this.channel_for_id(channel_id).cloned().ok_or_else(|| {
-                                    Arc::new(anyhow!("no channel for id: {}", channel_id))
-                                })
-                            })??;
-
-                            load(channel, cx).await.map_err(Arc::new)
-                        })
-                        .shared();
-
-                    e.insert(OpenedModelHandle::Loading(task.clone()));
-                    cx.spawn({
-                        let task = task.clone();
-                        move |this, mut cx| async move {
-                            let result = task.await;
-                            this.update(&mut cx, |this, _| match result {
-                                Ok(model) => {
-                                    get_map(this).insert(
-                                        channel_id,
-                                        OpenedModelHandle::Open(model.downgrade()),
-                                    );
-                                }
-                                Err(_) => {
-                                    get_map(this).remove(&channel_id);
-                                }
-                            })
-                            .ok();
-                        }
-                    })
-                    .detach();
-                    break task;
-                }
-            }
-        };
-        cx.background_executor()
-            .spawn(async move { task.await.map_err(|error| anyhow!("{}", error)) })
-    }
-
-    pub fn is_channel_admin(&self, channel_id: ChannelId) -> bool {
-        let Some(channel) = self.channel_for_id(channel_id) else {
-            return false;
-        };
-        channel.role == proto::ChannelRole::Admin
-    }
-
-    pub fn channel_participants(&self, channel_id: ChannelId) -> &[Arc<User>] {
-        self.channel_participants
-            .get(&channel_id)
-            .map_or(&[], |v| v.as_slice())
-    }
-
-    pub fn create_channel(
-        &self,
-        name: &str,
-        parent_id: Option<ChannelId>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<ChannelId>> {
-        let client = self.client.clone();
-        let name = name.trim_start_matches("#").to_owned();
-        cx.spawn(move |this, mut cx| async move {
-            let response = client
-                .request(proto::CreateChannel { name, parent_id })
-                .await?;
-
-            let channel = response
-                .channel
-                .ok_or_else(|| anyhow!("missing channel in response"))?;
-            let channel_id = channel.id;
-
-            this.update(&mut cx, |this, cx| {
-                let task = this.update_channels(
-                    proto::UpdateChannels {
-                        channels: vec![channel],
-                        ..Default::default()
-                    },
-                    cx,
-                );
-                assert!(task.is_none());
-
-                // This event is emitted because the collab panel wants to clear the pending edit state
-                // before this frame is rendered. But we can't guarantee that the collab panel's future
-                // will resolve before this flush_effects finishes. Synchronously emitting this event
-                // ensures that the collab panel will observe this creation before the frame completes
-                cx.emit(ChannelEvent::ChannelCreated(channel_id));
-            })?;
-
-            Ok(channel_id)
-        })
-    }
-
-    pub fn move_channel(
-        &mut self,
-        channel_id: ChannelId,
-        to: Option<ChannelId>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        let client = self.client.clone();
-        cx.spawn(move |_, _| async move {
-            let _ = client
-                .request(proto::MoveChannel { channel_id, to })
-                .await?;
-
-            Ok(())
-        })
-    }
-
-    pub fn set_channel_visibility(
-        &mut self,
-        channel_id: ChannelId,
-        visibility: ChannelVisibility,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        let client = self.client.clone();
-        cx.spawn(move |_, _| async move {
-            let _ = client
-                .request(proto::SetChannelVisibility {
-                    channel_id,
-                    visibility: visibility.into(),
-                })
-                .await?;
-
-            Ok(())
-        })
-    }
-
-    pub fn invite_member(
-        &mut self,
-        channel_id: ChannelId,
-        user_id: UserId,
-        role: proto::ChannelRole,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        if !self.outgoing_invites.insert((channel_id, user_id)) {
-            return Task::ready(Err(anyhow!("invite request already in progress")));
-        }
-
-        cx.notify();
-        let client = self.client.clone();
-        cx.spawn(move |this, mut cx| async move {
-            let result = client
-                .request(proto::InviteChannelMember {
-                    channel_id,
-                    user_id,
-                    role: role.into(),
-                })
-                .await;
-
-            this.update(&mut cx, |this, cx| {
-                this.outgoing_invites.remove(&(channel_id, user_id));
-                cx.notify();
-            })?;
-
-            result?;
-
-            Ok(())
-        })
-    }
-
-    pub fn remove_member(
-        &mut self,
-        channel_id: ChannelId,
-        user_id: u64,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        if !self.outgoing_invites.insert((channel_id, user_id)) {
-            return Task::ready(Err(anyhow!("invite request already in progress")));
-        }
-
-        cx.notify();
-        let client = self.client.clone();
-        cx.spawn(move |this, mut cx| async move {
-            let result = client
-                .request(proto::RemoveChannelMember {
-                    channel_id,
-                    user_id,
-                })
-                .await;
-
-            this.update(&mut cx, |this, cx| {
-                this.outgoing_invites.remove(&(channel_id, user_id));
-                cx.notify();
-            })?;
-            result?;
-            Ok(())
-        })
-    }
-
-    pub fn set_member_role(
-        &mut self,
-        channel_id: ChannelId,
-        user_id: UserId,
-        role: proto::ChannelRole,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        if !self.outgoing_invites.insert((channel_id, user_id)) {
-            return Task::ready(Err(anyhow!("member request already in progress")));
-        }
-
-        cx.notify();
-        let client = self.client.clone();
-        cx.spawn(move |this, mut cx| async move {
-            let result = client
-                .request(proto::SetChannelMemberRole {
-                    channel_id,
-                    user_id,
-                    role: role.into(),
-                })
-                .await;
-
-            this.update(&mut cx, |this, cx| {
-                this.outgoing_invites.remove(&(channel_id, user_id));
-                cx.notify();
-            })?;
-
-            result?;
-            Ok(())
-        })
-    }
-
-    pub fn rename(
-        &mut self,
-        channel_id: ChannelId,
-        new_name: &str,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        let client = self.client.clone();
-        let name = new_name.to_string();
-        cx.spawn(move |this, mut cx| async move {
-            let channel = client
-                .request(proto::RenameChannel { channel_id, name })
-                .await?
-                .channel
-                .ok_or_else(|| anyhow!("missing channel in response"))?;
-            this.update(&mut cx, |this, cx| {
-                let task = this.update_channels(
-                    proto::UpdateChannels {
-                        channels: vec![channel],
-                        ..Default::default()
-                    },
-                    cx,
-                );
-                assert!(task.is_none());
-
-                // This event is emitted because the collab panel wants to clear the pending edit state
-                // before this frame is rendered. But we can't guarantee that the collab panel's future
-                // will resolve before this flush_effects finishes. Synchronously emitting this event
-                // ensures that the collab panel will observe this creation before the frame complete
-                cx.emit(ChannelEvent::ChannelRenamed(channel_id))
-            })?;
-            Ok(())
-        })
-    }
-
-    pub fn respond_to_channel_invite(
-        &mut self,
-        channel_id: ChannelId,
-        accept: bool,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        let client = self.client.clone();
-        cx.background_executor().spawn(async move {
-            client
-                .request(proto::RespondToChannelInvite { channel_id, accept })
-                .await?;
-            Ok(())
-        })
-    }
-
-    pub fn get_channel_member_details(
-        &self,
-        channel_id: ChannelId,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<ChannelMembership>>> {
-        let client = self.client.clone();
-        let user_store = self.user_store.downgrade();
-        cx.spawn(move |_, mut cx| async move {
-            let response = client
-                .request(proto::GetChannelMembers { channel_id })
-                .await?;
-
-            let user_ids = response.members.iter().map(|m| m.user_id).collect();
-            let user_store = user_store
-                .upgrade()
-                .ok_or_else(|| anyhow!("user store dropped"))?;
-            let users = user_store
-                .update(&mut cx, |user_store, cx| user_store.get_users(user_ids, cx))?
-                .await?;
-
-            Ok(users
-                .into_iter()
-                .zip(response.members)
-                .filter_map(|(user, member)| {
-                    Some(ChannelMembership {
-                        user,
-                        role: member.role(),
-                        kind: member.kind(),
-                    })
-                })
-                .collect())
-        })
-    }
-
-    pub fn remove_channel(&self, channel_id: ChannelId) -> impl Future<Output = Result<()>> {
-        let client = self.client.clone();
-        async move {
-            client.request(proto::DeleteChannel { channel_id }).await?;
-            Ok(())
-        }
-    }
-
-    pub fn has_pending_channel_invite_response(&self, _: &Arc<Channel>) -> bool {
-        false
-    }
-
-    pub fn has_pending_channel_invite(&self, channel_id: ChannelId, user_id: UserId) -> bool {
-        self.outgoing_invites.contains(&(channel_id, user_id))
-    }
-
-    async fn handle_update_channels(
-        this: Model<Self>,
-        message: TypedEnvelope<proto::UpdateChannels>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        this.update(&mut cx, |this, _| {
-            this.update_channels_tx
-                .unbounded_send(message.payload)
-                .unwrap();
-        })?;
-        Ok(())
-    }
-
-    fn handle_connect(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
-        self.channel_index.clear();
-        self.channel_invitations.clear();
-        self.channel_participants.clear();
-        self.channel_index.clear();
-        self.outgoing_invites.clear();
-        self.disconnect_channel_buffers_task.take();
-
-        for chat in self.opened_chats.values() {
-            if let OpenedModelHandle::Open(chat) = chat {
-                if let Some(chat) = chat.upgrade() {
-                    chat.update(cx, |chat, cx| {
-                        chat.rejoin(cx);
-                    });
-                }
-            }
-        }
-
-        let mut buffer_versions = Vec::new();
-        for buffer in self.opened_buffers.values() {
-            if let OpenedModelHandle::Open(buffer) = buffer {
-                if let Some(buffer) = buffer.upgrade() {
-                    let channel_buffer = buffer.read(cx);
-                    let buffer = channel_buffer.buffer().read(cx);
-                    buffer_versions.push(proto::ChannelBufferVersion {
-                        channel_id: channel_buffer.channel_id,
-                        epoch: channel_buffer.epoch(),
-                        version: language::proto::serialize_version(&buffer.version()),
-                    });
-                }
-            }
-        }
-
-        if buffer_versions.is_empty() {
-            return Task::ready(Ok(()));
-        }
-
-        let response = self.client.request(proto::RejoinChannelBuffers {
-            buffers: buffer_versions,
-        });
-
-        cx.spawn(|this, mut cx| async move {
-            let mut response = response.await?;
-
-            this.update(&mut cx, |this, cx| {
-                this.opened_buffers.retain(|_, buffer| match buffer {
-                    OpenedModelHandle::Open(channel_buffer) => {
-                        let Some(channel_buffer) = channel_buffer.upgrade() else {
-                            return false;
-                        };
-
-                        channel_buffer.update(cx, |channel_buffer, cx| {
-                            let channel_id = channel_buffer.channel_id;
-                            if let Some(remote_buffer) = response
-                                .buffers
-                                .iter_mut()
-                                .find(|buffer| buffer.channel_id == channel_id)
-                            {
-                                let channel_id = channel_buffer.channel_id;
-                                let remote_version =
-                                    language::proto::deserialize_version(&remote_buffer.version);
-
-                                channel_buffer.replace_collaborators(
-                                    mem::take(&mut remote_buffer.collaborators),
-                                    cx,
-                                );
-
-                                let operations = channel_buffer
-                                    .buffer()
-                                    .update(cx, |buffer, cx| {
-                                        let outgoing_operations =
-                                            buffer.serialize_ops(Some(remote_version), cx);
-                                        let incoming_operations =
-                                            mem::take(&mut remote_buffer.operations)
-                                                .into_iter()
-                                                .map(language::proto::deserialize_operation)
-                                                .collect::<Result<Vec<_>>>()?;
-                                        buffer.apply_ops(incoming_operations, cx)?;
-                                        anyhow::Ok(outgoing_operations)
-                                    })
-                                    .log_err();
-
-                                if let Some(operations) = operations {
-                                    let client = this.client.clone();
-                                    cx.background_executor()
-                                        .spawn(async move {
-                                            let operations = operations.await;
-                                            for chunk in
-                                                language::proto::split_operations(operations)
-                                            {
-                                                client
-                                                    .send(proto::UpdateChannelBuffer {
-                                                        channel_id,
-                                                        operations: chunk,
-                                                    })
-                                                    .ok();
-                                            }
-                                        })
-                                        .detach();
-                                    return true;
-                                }
-                            }
-
-                            channel_buffer.disconnect(cx);
-                            false
-                        })
-                    }
-                    OpenedModelHandle::Loading(_) => true,
-                });
-            })
-            .ok();
-            anyhow::Ok(())
-        })
-    }
-
-    fn handle_disconnect(&mut self, wait_for_reconnect: bool, cx: &mut ModelContext<Self>) {
-        cx.notify();
-
-        self.disconnect_channel_buffers_task.get_or_insert_with(|| {
-            cx.spawn(move |this, mut cx| async move {
-                if wait_for_reconnect {
-                    cx.background_executor().timer(RECONNECT_TIMEOUT).await;
-                }
-
-                if let Some(this) = this.upgrade() {
-                    this.update(&mut cx, |this, cx| {
-                        for (_, buffer) in this.opened_buffers.drain() {
-                            if let OpenedModelHandle::Open(buffer) = buffer {
-                                if let Some(buffer) = buffer.upgrade() {
-                                    buffer.update(cx, |buffer, cx| buffer.disconnect(cx));
-                                }
-                            }
-                        }
-                    })
-                    .ok();
-                }
-            })
-        });
-    }
-
-    pub(crate) fn update_channels(
-        &mut self,
-        payload: proto::UpdateChannels,
-        cx: &mut ModelContext<ChannelStore>,
-    ) -> Option<Task<Result<()>>> {
-        if !payload.remove_channel_invitations.is_empty() {
-            self.channel_invitations
-                .retain(|channel| !payload.remove_channel_invitations.contains(&channel.id));
-        }
-        for channel in payload.channel_invitations {
-            match self
-                .channel_invitations
-                .binary_search_by_key(&channel.id, |c| c.id)
-            {
-                Ok(ix) => {
-                    Arc::make_mut(&mut self.channel_invitations[ix]).name = channel.name.into()
-                }
-                Err(ix) => self.channel_invitations.insert(
-                    ix,
-                    Arc::new(Channel {
-                        id: channel.id,
-                        visibility: channel.visibility(),
-                        role: channel.role(),
-                        name: channel.name.into(),
-                        unseen_note_version: None,
-                        unseen_message_id: None,
-                        parent_path: channel.parent_path,
-                    }),
-                ),
-            }
-        }
-
-        let channels_changed = !payload.channels.is_empty()
-            || !payload.delete_channels.is_empty()
-            || !payload.unseen_channel_messages.is_empty()
-            || !payload.unseen_channel_buffer_changes.is_empty();
-
-        if channels_changed {
-            if !payload.delete_channels.is_empty() {
-                self.channel_index.delete_channels(&payload.delete_channels);
-                self.channel_participants
-                    .retain(|channel_id, _| !&payload.delete_channels.contains(channel_id));
-
-                for channel_id in &payload.delete_channels {
-                    let channel_id = *channel_id;
-                    if payload
-                        .channels
-                        .iter()
-                        .any(|channel| channel.id == channel_id)
-                    {
-                        continue;
-                    }
-                    if let Some(OpenedModelHandle::Open(buffer)) =
-                        self.opened_buffers.remove(&channel_id)
-                    {
-                        if let Some(buffer) = buffer.upgrade() {
-                            buffer.update(cx, ChannelBuffer::disconnect);
-                        }
-                    }
-                }
-            }
-
-            let mut index = self.channel_index.bulk_insert();
-            for channel in payload.channels {
-                let id = channel.id;
-                let channel_changed = index.insert(channel);
-
-                if channel_changed {
-                    if let Some(OpenedModelHandle::Open(buffer)) = self.opened_buffers.get(&id) {
-                        if let Some(buffer) = buffer.upgrade() {
-                            buffer.update(cx, ChannelBuffer::channel_changed);
-                        }
-                    }
-                }
-            }
-
-            for unseen_buffer_change in payload.unseen_channel_buffer_changes {
-                let version = language::proto::deserialize_version(&unseen_buffer_change.version);
-                index.note_changed(
-                    unseen_buffer_change.channel_id,
-                    unseen_buffer_change.epoch,
-                    &version,
-                );
-            }
-
-            for unseen_channel_message in payload.unseen_channel_messages {
-                index.new_messages(
-                    unseen_channel_message.channel_id,
-                    unseen_channel_message.message_id,
-                );
-            }
-        }
-
-        cx.notify();
-        if payload.channel_participants.is_empty() {
-            return None;
-        }
-
-        let mut all_user_ids = Vec::new();
-        let channel_participants = payload.channel_participants;
-        for entry in &channel_participants {
-            for user_id in entry.participant_user_ids.iter() {
-                if let Err(ix) = all_user_ids.binary_search(user_id) {
-                    all_user_ids.insert(ix, *user_id);
-                }
-            }
-        }
-
-        let users = self
-            .user_store
-            .update(cx, |user_store, cx| user_store.get_users(all_user_ids, cx));
-        Some(cx.spawn(|this, mut cx| async move {
-            let users = users.await?;
-
-            this.update(&mut cx, |this, cx| {
-                for entry in &channel_participants {
-                    let mut participants: Vec<_> = entry
-                        .participant_user_ids
-                        .iter()
-                        .filter_map(|user_id| {
-                            users
-                                .binary_search_by_key(&user_id, |user| &user.id)
-                                .ok()
-                                .map(|ix| users[ix].clone())
-                        })
-                        .collect();
-
-                    participants.sort_by_key(|u| u.id);
-
-                    this.channel_participants
-                        .insert(entry.channel_id, participants);
-                }
-
-                cx.notify();
-            })
-        }))
-    }
-}

crates/channel2/src/channel_store/channel_index.rs 🔗

@@ -1,184 +0,0 @@
-use crate::{Channel, ChannelId};
-use collections::BTreeMap;
-use rpc::proto;
-use std::sync::Arc;
-
-#[derive(Default, Debug)]
-pub struct ChannelIndex {
-    channels_ordered: Vec<ChannelId>,
-    channels_by_id: BTreeMap<ChannelId, Arc<Channel>>,
-}
-
-impl ChannelIndex {
-    pub fn by_id(&self) -> &BTreeMap<ChannelId, Arc<Channel>> {
-        &self.channels_by_id
-    }
-
-    pub fn ordered_channels(&self) -> &[ChannelId] {
-        &self.channels_ordered
-    }
-
-    pub fn clear(&mut self) {
-        self.channels_ordered.clear();
-        self.channels_by_id.clear();
-    }
-
-    /// Delete the given channels from this index.
-    pub fn delete_channels(&mut self, channels: &[ChannelId]) {
-        self.channels_by_id
-            .retain(|channel_id, _| !channels.contains(channel_id));
-        self.channels_ordered
-            .retain(|channel_id| !channels.contains(channel_id));
-    }
-
-    pub fn bulk_insert(&mut self) -> ChannelPathsInsertGuard {
-        ChannelPathsInsertGuard {
-            channels_ordered: &mut self.channels_ordered,
-            channels_by_id: &mut self.channels_by_id,
-        }
-    }
-
-    pub fn acknowledge_note_version(
-        &mut self,
-        channel_id: ChannelId,
-        epoch: u64,
-        version: &clock::Global,
-    ) {
-        if let Some(channel) = self.channels_by_id.get_mut(&channel_id) {
-            let channel = Arc::make_mut(channel);
-            if let Some((unseen_epoch, unseen_version)) = &channel.unseen_note_version {
-                if epoch > *unseen_epoch
-                    || epoch == *unseen_epoch && version.observed_all(unseen_version)
-                {
-                    channel.unseen_note_version = None;
-                }
-            }
-        }
-    }
-
-    pub fn acknowledge_message_id(&mut self, channel_id: ChannelId, message_id: u64) {
-        if let Some(channel) = self.channels_by_id.get_mut(&channel_id) {
-            let channel = Arc::make_mut(channel);
-            if let Some(unseen_message_id) = channel.unseen_message_id {
-                if message_id >= unseen_message_id {
-                    channel.unseen_message_id = None;
-                }
-            }
-        }
-    }
-
-    pub fn note_changed(&mut self, channel_id: ChannelId, epoch: u64, version: &clock::Global) {
-        insert_note_changed(&mut self.channels_by_id, channel_id, epoch, version);
-    }
-
-    pub fn new_message(&mut self, channel_id: ChannelId, message_id: u64) {
-        insert_new_message(&mut self.channels_by_id, channel_id, message_id)
-    }
-}
-
-/// A guard for ensuring that the paths index maintains its sort and uniqueness
-/// invariants after a series of insertions
-#[derive(Debug)]
-pub struct ChannelPathsInsertGuard<'a> {
-    channels_ordered: &'a mut Vec<ChannelId>,
-    channels_by_id: &'a mut BTreeMap<ChannelId, Arc<Channel>>,
-}
-
-impl<'a> ChannelPathsInsertGuard<'a> {
-    pub fn note_changed(&mut self, channel_id: ChannelId, epoch: u64, version: &clock::Global) {
-        insert_note_changed(&mut self.channels_by_id, channel_id, epoch, &version);
-    }
-
-    pub fn new_messages(&mut self, channel_id: ChannelId, message_id: u64) {
-        insert_new_message(&mut self.channels_by_id, channel_id, message_id)
-    }
-
-    pub fn insert(&mut self, channel_proto: proto::Channel) -> bool {
-        let mut ret = false;
-        if let Some(existing_channel) = self.channels_by_id.get_mut(&channel_proto.id) {
-            let existing_channel = Arc::make_mut(existing_channel);
-
-            ret = existing_channel.visibility != channel_proto.visibility()
-                || existing_channel.role != channel_proto.role()
-                || existing_channel.name != channel_proto.name;
-
-            existing_channel.visibility = channel_proto.visibility();
-            existing_channel.role = channel_proto.role();
-            existing_channel.name = channel_proto.name.into();
-        } else {
-            self.channels_by_id.insert(
-                channel_proto.id,
-                Arc::new(Channel {
-                    id: channel_proto.id,
-                    visibility: channel_proto.visibility(),
-                    role: channel_proto.role(),
-                    name: channel_proto.name.into(),
-                    unseen_note_version: None,
-                    unseen_message_id: None,
-                    parent_path: channel_proto.parent_path,
-                }),
-            );
-            self.insert_root(channel_proto.id);
-        }
-        ret
-    }
-
-    fn insert_root(&mut self, channel_id: ChannelId) {
-        self.channels_ordered.push(channel_id);
-    }
-}
-
-impl<'a> Drop for ChannelPathsInsertGuard<'a> {
-    fn drop(&mut self) {
-        self.channels_ordered.sort_by(|a, b| {
-            let a = channel_path_sorting_key(*a, &self.channels_by_id);
-            let b = channel_path_sorting_key(*b, &self.channels_by_id);
-            a.cmp(b)
-        });
-        self.channels_ordered.dedup();
-    }
-}
-
-fn channel_path_sorting_key<'a>(
-    id: ChannelId,
-    channels_by_id: &'a BTreeMap<ChannelId, Arc<Channel>>,
-) -> impl Iterator<Item = &str> {
-    let (parent_path, name) = channels_by_id
-        .get(&id)
-        .map_or((&[] as &[_], None), |channel| {
-            (channel.parent_path.as_slice(), Some(channel.name.as_ref()))
-        });
-    parent_path
-        .iter()
-        .filter_map(|id| Some(channels_by_id.get(id)?.name.as_ref()))
-        .chain(name)
-}
-
-fn insert_note_changed(
-    channels_by_id: &mut BTreeMap<ChannelId, Arc<Channel>>,
-    channel_id: u64,
-    epoch: u64,
-    version: &clock::Global,
-) {
-    if let Some(channel) = channels_by_id.get_mut(&channel_id) {
-        let unseen_version = Arc::make_mut(channel)
-            .unseen_note_version
-            .get_or_insert((0, clock::Global::new()));
-        if epoch > unseen_version.0 {
-            *unseen_version = (epoch, version.clone());
-        } else {
-            unseen_version.1.join(&version);
-        }
-    }
-}
-
-fn insert_new_message(
-    channels_by_id: &mut BTreeMap<ChannelId, Arc<Channel>>,
-    channel_id: u64,
-    message_id: u64,
-) {
-    if let Some(channel) = channels_by_id.get_mut(&channel_id) {
-        let unseen_message_id = Arc::make_mut(channel).unseen_message_id.get_or_insert(0);
-        *unseen_message_id = message_id.max(*unseen_message_id);
-    }
-}

crates/channel2/src/channel_store_tests.rs 🔗

@@ -1,380 +0,0 @@
-use crate::channel_chat::ChannelChatEvent;
-
-use super::*;
-use client::{test::FakeServer, Client, UserStore};
-use gpui::{AppContext, Context, Model, TestAppContext};
-use rpc::proto::{self};
-use settings::SettingsStore;
-use util::http::FakeHttpClient;
-
-#[gpui::test]
-fn test_update_channels(cx: &mut AppContext) {
-    let channel_store = init_test(cx);
-
-    update_channels(
-        &channel_store,
-        proto::UpdateChannels {
-            channels: vec![
-                proto::Channel {
-                    id: 1,
-                    name: "b".to_string(),
-                    visibility: proto::ChannelVisibility::Members as i32,
-                    role: proto::ChannelRole::Admin.into(),
-                    parent_path: Vec::new(),
-                },
-                proto::Channel {
-                    id: 2,
-                    name: "a".to_string(),
-                    visibility: proto::ChannelVisibility::Members as i32,
-                    role: proto::ChannelRole::Member.into(),
-                    parent_path: Vec::new(),
-                },
-            ],
-            ..Default::default()
-        },
-        cx,
-    );
-    assert_channels(
-        &channel_store,
-        &[
-            //
-            (0, "a".to_string(), proto::ChannelRole::Member),
-            (0, "b".to_string(), proto::ChannelRole::Admin),
-        ],
-        cx,
-    );
-
-    update_channels(
-        &channel_store,
-        proto::UpdateChannels {
-            channels: vec![
-                proto::Channel {
-                    id: 3,
-                    name: "x".to_string(),
-                    visibility: proto::ChannelVisibility::Members as i32,
-                    role: proto::ChannelRole::Admin.into(),
-                    parent_path: vec![1],
-                },
-                proto::Channel {
-                    id: 4,
-                    name: "y".to_string(),
-                    visibility: proto::ChannelVisibility::Members as i32,
-                    role: proto::ChannelRole::Member.into(),
-                    parent_path: vec![2],
-                },
-            ],
-            ..Default::default()
-        },
-        cx,
-    );
-    assert_channels(
-        &channel_store,
-        &[
-            (0, "a".to_string(), proto::ChannelRole::Member),
-            (1, "y".to_string(), proto::ChannelRole::Member),
-            (0, "b".to_string(), proto::ChannelRole::Admin),
-            (1, "x".to_string(), proto::ChannelRole::Admin),
-        ],
-        cx,
-    );
-}
-
-#[gpui::test]
-fn test_dangling_channel_paths(cx: &mut AppContext) {
-    let channel_store = init_test(cx);
-
-    update_channels(
-        &channel_store,
-        proto::UpdateChannels {
-            channels: vec![
-                proto::Channel {
-                    id: 0,
-                    name: "a".to_string(),
-                    visibility: proto::ChannelVisibility::Members as i32,
-                    role: proto::ChannelRole::Admin.into(),
-                    parent_path: vec![],
-                },
-                proto::Channel {
-                    id: 1,
-                    name: "b".to_string(),
-                    visibility: proto::ChannelVisibility::Members as i32,
-                    role: proto::ChannelRole::Admin.into(),
-                    parent_path: vec![0],
-                },
-                proto::Channel {
-                    id: 2,
-                    name: "c".to_string(),
-                    visibility: proto::ChannelVisibility::Members as i32,
-                    role: proto::ChannelRole::Admin.into(),
-                    parent_path: vec![0, 1],
-                },
-            ],
-            ..Default::default()
-        },
-        cx,
-    );
-    // Sanity check
-    assert_channels(
-        &channel_store,
-        &[
-            //
-            (0, "a".to_string(), proto::ChannelRole::Admin),
-            (1, "b".to_string(), proto::ChannelRole::Admin),
-            (2, "c".to_string(), proto::ChannelRole::Admin),
-        ],
-        cx,
-    );
-
-    update_channels(
-        &channel_store,
-        proto::UpdateChannels {
-            delete_channels: vec![1, 2],
-            ..Default::default()
-        },
-        cx,
-    );
-
-    // Make sure that the 1/2/3 path is gone
-    assert_channels(
-        &channel_store,
-        &[(0, "a".to_string(), proto::ChannelRole::Admin)],
-        cx,
-    );
-}
-
-#[gpui::test]
-async fn test_channel_messages(cx: &mut TestAppContext) {
-    let user_id = 5;
-    let channel_id = 5;
-    let channel_store = cx.update(init_test);
-    let client = channel_store.update(cx, |s, _| s.client());
-    let server = FakeServer::for_client(user_id, &client, cx).await;
-
-    // Get the available channels.
-    server.send(proto::UpdateChannels {
-        channels: vec![proto::Channel {
-            id: channel_id,
-            name: "the-channel".to_string(),
-            visibility: proto::ChannelVisibility::Members as i32,
-            role: proto::ChannelRole::Member.into(),
-            parent_path: vec![],
-        }],
-        ..Default::default()
-    });
-    cx.executor().run_until_parked();
-    cx.update(|cx| {
-        assert_channels(
-            &channel_store,
-            &[(0, "the-channel".to_string(), proto::ChannelRole::Member)],
-            cx,
-        );
-    });
-
-    let get_users = server.receive::<proto::GetUsers>().await.unwrap();
-    assert_eq!(get_users.payload.user_ids, vec![5]);
-    server.respond(
-        get_users.receipt(),
-        proto::UsersResponse {
-            users: vec![proto::User {
-                id: 5,
-                github_login: "nathansobo".into(),
-                avatar_url: "http://avatar.com/nathansobo".into(),
-            }],
-        },
-    );
-
-    // Join a channel and populate its existing messages.
-    let channel = channel_store.update(cx, |store, cx| {
-        let channel_id = store.ordered_channels().next().unwrap().1.id;
-        store.open_channel_chat(channel_id, cx)
-    });
-    let join_channel = server.receive::<proto::JoinChannelChat>().await.unwrap();
-    server.respond(
-        join_channel.receipt(),
-        proto::JoinChannelChatResponse {
-            messages: vec![
-                proto::ChannelMessage {
-                    id: 10,
-                    body: "a".into(),
-                    timestamp: 1000,
-                    sender_id: 5,
-                    mentions: vec![],
-                    nonce: Some(1.into()),
-                },
-                proto::ChannelMessage {
-                    id: 11,
-                    body: "b".into(),
-                    timestamp: 1001,
-                    sender_id: 6,
-                    mentions: vec![],
-                    nonce: Some(2.into()),
-                },
-            ],
-            done: false,
-        },
-    );
-
-    cx.executor().start_waiting();
-
-    // Client requests all users for the received messages
-    let mut get_users = server.receive::<proto::GetUsers>().await.unwrap();
-    get_users.payload.user_ids.sort();
-    assert_eq!(get_users.payload.user_ids, vec![6]);
-    server.respond(
-        get_users.receipt(),
-        proto::UsersResponse {
-            users: vec![proto::User {
-                id: 6,
-                github_login: "maxbrunsfeld".into(),
-                avatar_url: "http://avatar.com/maxbrunsfeld".into(),
-            }],
-        },
-    );
-
-    let channel = channel.await.unwrap();
-    channel.update(cx, |channel, _| {
-        assert_eq!(
-            channel
-                .messages_in_range(0..2)
-                .map(|message| (message.sender.github_login.clone(), message.body.clone()))
-                .collect::<Vec<_>>(),
-            &[
-                ("nathansobo".into(), "a".into()),
-                ("maxbrunsfeld".into(), "b".into())
-            ]
-        );
-    });
-
-    // Receive a new message.
-    server.send(proto::ChannelMessageSent {
-        channel_id,
-        message: Some(proto::ChannelMessage {
-            id: 12,
-            body: "c".into(),
-            timestamp: 1002,
-            sender_id: 7,
-            mentions: vec![],
-            nonce: Some(3.into()),
-        }),
-    });
-
-    // Client requests user for message since they haven't seen them yet
-    let get_users = server.receive::<proto::GetUsers>().await.unwrap();
-    assert_eq!(get_users.payload.user_ids, vec![7]);
-    server.respond(
-        get_users.receipt(),
-        proto::UsersResponse {
-            users: vec![proto::User {
-                id: 7,
-                github_login: "as-cii".into(),
-                avatar_url: "http://avatar.com/as-cii".into(),
-            }],
-        },
-    );
-
-    assert_eq!(
-        channel.next_event(cx),
-        ChannelChatEvent::MessagesUpdated {
-            old_range: 2..2,
-            new_count: 1,
-        }
-    );
-    channel.update(cx, |channel, _| {
-        assert_eq!(
-            channel
-                .messages_in_range(2..3)
-                .map(|message| (message.sender.github_login.clone(), message.body.clone()))
-                .collect::<Vec<_>>(),
-            &[("as-cii".into(), "c".into())]
-        )
-    });
-
-    // Scroll up to view older messages.
-    channel.update(cx, |channel, cx| {
-        channel.load_more_messages(cx).unwrap().detach();
-    });
-    let get_messages = server.receive::<proto::GetChannelMessages>().await.unwrap();
-    assert_eq!(get_messages.payload.channel_id, 5);
-    assert_eq!(get_messages.payload.before_message_id, 10);
-    server.respond(
-        get_messages.receipt(),
-        proto::GetChannelMessagesResponse {
-            done: true,
-            messages: vec![
-                proto::ChannelMessage {
-                    id: 8,
-                    body: "y".into(),
-                    timestamp: 998,
-                    sender_id: 5,
-                    nonce: Some(4.into()),
-                    mentions: vec![],
-                },
-                proto::ChannelMessage {
-                    id: 9,
-                    body: "z".into(),
-                    timestamp: 999,
-                    sender_id: 6,
-                    nonce: Some(5.into()),
-                    mentions: vec![],
-                },
-            ],
-        },
-    );
-
-    assert_eq!(
-        channel.next_event(cx),
-        ChannelChatEvent::MessagesUpdated {
-            old_range: 0..0,
-            new_count: 2,
-        }
-    );
-    channel.update(cx, |channel, _| {
-        assert_eq!(
-            channel
-                .messages_in_range(0..2)
-                .map(|message| (message.sender.github_login.clone(), message.body.clone()))
-                .collect::<Vec<_>>(),
-            &[
-                ("nathansobo".into(), "y".into()),
-                ("maxbrunsfeld".into(), "z".into())
-            ]
-        );
-    });
-}
-
-fn init_test(cx: &mut AppContext) -> Model<ChannelStore> {
-    let http = FakeHttpClient::with_404_response();
-    let client = Client::new(http.clone(), cx);
-    let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
-
-    let settings_store = SettingsStore::test(cx);
-    cx.set_global(settings_store);
-    client::init(&client, cx);
-    crate::init(&client, user_store, cx);
-
-    ChannelStore::global(cx)
-}
-
-fn update_channels(
-    channel_store: &Model<ChannelStore>,
-    message: proto::UpdateChannels,
-    cx: &mut AppContext,
-) {
-    let task = channel_store.update(cx, |store, cx| store.update_channels(message, cx));
-    assert!(task.is_none());
-}
-
-#[track_caller]
-fn assert_channels(
-    channel_store: &Model<ChannelStore>,
-    expected_channels: &[(usize, String, proto::ChannelRole)],
-    cx: &mut AppContext,
-) {
-    let actual = channel_store.update(cx, |store, _| {
-        store
-            .ordered_channels()
-            .map(|(depth, channel)| (depth, channel.name.to_string(), channel.role))
-            .collect::<Vec<_>>()
-    });
-    assert_eq!(actual, expected_channels);
-}

crates/client/src/client.rs 🔗

@@ -4,20 +4,19 @@ pub mod test;
 pub mod telemetry;
 pub mod user;
 
-use anyhow::{anyhow, Context, Result};
+use anyhow::{anyhow, Context as _, Result};
 use async_recursion::async_recursion;
 use async_tungstenite::tungstenite::{
     error::Error as WebsocketError,
     http::{Request, StatusCode},
 };
 use futures::{
-    future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt, TryFutureExt as _,
-    TryStreamExt,
+    channel::oneshot, future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt,
+    TryFutureExt as _, TryStreamExt,
 };
 use gpui::{
-    actions, platform::AppVersion, serde_json, AnyModelHandle, AnyWeakModelHandle,
-    AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, ModelHandle, Task, View, ViewContext,
-    WeakViewHandle,
+    actions, serde_json, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, Model,
+    SemanticVersion, Task, WeakModel,
 };
 use lazy_static::lazy_static;
 use parking_lot::RwLock;
@@ -26,6 +25,7 @@ use rand::prelude::*;
 use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage};
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
+use settings::Settings;
 use std::{
     any::TypeId,
     collections::HashMap,
@@ -57,7 +57,7 @@ lazy_static! {
     pub static ref ADMIN_API_TOKEN: Option<String> = std::env::var("ZED_ADMIN_API_TOKEN")
         .ok()
         .and_then(|s| if s.is_empty() { None } else { Some(s) });
-    pub static ref ZED_APP_VERSION: Option<AppVersion> = std::env::var("ZED_APP_VERSION")
+    pub static ref ZED_APP_VERSION: Option<SemanticVersion> = std::env::var("ZED_APP_VERSION")
         .ok()
         .and_then(|v| v.parse().ok());
     pub static ref ZED_APP_PATH: Option<PathBuf> =
@@ -73,14 +73,14 @@ pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5);
 actions!(client, [SignIn, SignOut, Reconnect]);
 
 pub fn init_settings(cx: &mut AppContext) {
-    settings::register::<TelemetrySettings>(cx);
+    TelemetrySettings::register(cx);
 }
 
 pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
     init_settings(cx);
 
     let client = Arc::downgrade(client);
-    cx.add_global_action({
+    cx.on_action({
         let client = client.clone();
         move |_: &SignIn, cx| {
             if let Some(client) = client.upgrade() {
@@ -91,7 +91,8 @@ pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
             }
         }
     });
-    cx.add_global_action({
+
+    cx.on_action({
         let client = client.clone();
         move |_: &SignOut, cx| {
             if let Some(client) = client.upgrade() {
@@ -102,7 +103,8 @@ pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
             }
         }
     });
-    cx.add_global_action({
+
+    cx.on_action({
         let client = client.clone();
         move |_: &Reconnect, cx| {
             if let Some(client) = client.upgrade() {
@@ -216,7 +218,7 @@ struct ClientState {
     _reconnect_task: Option<Task<()>>,
     reconnect_interval: Duration,
     entities_by_type_and_remote_id: HashMap<(TypeId, u64), WeakSubscriber>,
-    models_by_message_type: HashMap<TypeId, AnyWeakModelHandle>,
+    models_by_message_type: HashMap<TypeId, AnyWeakModel>,
     entity_types_by_message_type: HashMap<TypeId, TypeId>,
     #[allow(clippy::type_complexity)]
     message_handlers: HashMap<
@@ -225,7 +227,7 @@ struct ClientState {
             dyn Send
                 + Sync
                 + Fn(
-                    Subscriber,
+                    AnyModel,
                     Box<dyn AnyTypedEnvelope>,
                     &Arc<Client>,
                     AsyncAppContext,
@@ -235,16 +237,10 @@ struct ClientState {
 }
 
 enum WeakSubscriber {
-    Model(AnyWeakModelHandle),
-    View(AnyWeakViewHandle),
+    Entity { handle: AnyWeakModel },
     Pending(Vec<Box<dyn AnyTypedEnvelope>>),
 }
 
-enum Subscriber {
-    Model(AnyModelHandle),
-    View(AnyWeakViewHandle),
-}
-
 #[derive(Clone, Debug)]
 pub struct Credentials {
     pub user_id: u64,
@@ -298,15 +294,15 @@ impl Drop for Subscription {
     }
 }
 
-pub struct PendingEntitySubscription<T: Entity> {
+pub struct PendingEntitySubscription<T: 'static> {
     client: Arc<Client>,
     remote_id: u64,
     _entity_type: PhantomData<T>,
     consumed: bool,
 }
 
-impl<T: Entity> PendingEntitySubscription<T> {
-    pub fn set_model(mut self, model: &ModelHandle<T>, cx: &mut AsyncAppContext) -> Subscription {
+impl<T: 'static> PendingEntitySubscription<T> {
+    pub fn set_model(mut self, model: &Model<T>, cx: &mut AsyncAppContext) -> Subscription {
         self.consumed = true;
         let mut state = self.client.state.write();
         let id = (TypeId::of::<T>(), self.remote_id);
@@ -316,9 +312,12 @@ impl<T: Entity> PendingEntitySubscription<T> {
             unreachable!()
         };
 
-        state
-            .entities_by_type_and_remote_id
-            .insert(id, WeakSubscriber::Model(model.downgrade().into_any()));
+        state.entities_by_type_and_remote_id.insert(
+            id,
+            WeakSubscriber::Entity {
+                handle: model.downgrade().into(),
+            },
+        );
         drop(state);
         for message in messages {
             self.client.handle_message(message, cx);
@@ -330,7 +329,7 @@ impl<T: Entity> PendingEntitySubscription<T> {
     }
 }
 
-impl<T: Entity> Drop for PendingEntitySubscription<T> {
+impl<T: 'static> Drop for PendingEntitySubscription<T> {
     fn drop(&mut self) {
         if !self.consumed {
             let mut state = self.client.state.write();
@@ -346,7 +345,7 @@ impl<T: Entity> Drop for PendingEntitySubscription<T> {
     }
 }
 
-#[derive(Debug, Copy, Clone)]
+#[derive(Copy, Clone)]
 pub struct TelemetrySettings {
     pub diagnostics: bool,
     pub metrics: bool,
@@ -358,7 +357,7 @@ pub struct TelemetrySettingsContent {
     pub metrics: Option<bool>,
 }
 
-impl settings::Setting for TelemetrySettings {
+impl settings::Settings for TelemetrySettings {
     const KEY: Option<&'static str> = Some("telemetry");
 
     type FileContent = TelemetrySettingsContent;
@@ -366,7 +365,7 @@ impl settings::Setting for TelemetrySettings {
     fn load(
         default_value: &Self::FileContent,
         user_values: &[&Self::FileContent],
-        _: &AppContext,
+        _: &mut AppContext,
     ) -> Result<Self> {
         Ok(Self {
             diagnostics: user_values.first().and_then(|v| v.diagnostics).unwrap_or(
@@ -383,7 +382,7 @@ impl settings::Setting for TelemetrySettings {
 }
 
 impl Client {
-    pub fn new(http: Arc<dyn HttpClient>, cx: &AppContext) -> Arc<Self> {
+    pub fn new(http: Arc<dyn HttpClient>, cx: &mut AppContext) -> Arc<Self> {
         Arc::new(Self {
             id: AtomicU64::new(0),
             peer: Peer::new(0),
@@ -475,7 +474,7 @@ impl Client {
             Status::ConnectionLost => {
                 let this = self.clone();
                 let reconnect_interval = state.reconnect_interval;
-                state._reconnect_task = Some(cx.spawn(|cx| async move {
+                state._reconnect_task = Some(cx.spawn(move |cx| async move {
                     #[cfg(any(test, feature = "test-support"))]
                     let mut rng = StdRng::seed_from_u64(0);
                     #[cfg(not(any(test, feature = "test-support")))]
@@ -491,7 +490,7 @@ impl Client {
                                 },
                                 &cx,
                             );
-                            cx.background().timer(delay).await;
+                            cx.background_executor().timer(delay).await;
                             delay = delay
                                 .mul_f32(rng.gen_range(1.0..=2.0))
                                 .min(reconnect_interval);
@@ -502,33 +501,21 @@ impl Client {
                 }));
             }
             Status::SignedOut | Status::UpgradeRequired => {
-                cx.read(|cx| self.telemetry.set_authenticated_user_info(None, false, cx));
+                cx.update(|cx| self.telemetry.set_authenticated_user_info(None, false, cx))
+                    .log_err();
                 state._reconnect_task.take();
             }
             _ => {}
         }
     }
 
-    pub fn add_view_for_remote_entity<T: View>(
+    pub fn subscribe_to_entity<T>(
         self: &Arc<Self>,
         remote_id: u64,
-        cx: &mut ViewContext<T>,
-    ) -> Subscription {
-        let id = (TypeId::of::<T>(), remote_id);
-        self.state
-            .write()
-            .entities_by_type_and_remote_id
-            .insert(id, WeakSubscriber::View(cx.weak_handle().into_any()));
-        Subscription::Entity {
-            client: Arc::downgrade(self),
-            id,
-        }
-    }
-
-    pub fn subscribe_to_entity<T: Entity>(
-        self: &Arc<Self>,
-        remote_id: u64,
-    ) -> Result<PendingEntitySubscription<T>> {
+    ) -> Result<PendingEntitySubscription<T>>
+    where
+        T: 'static,
+    {
         let id = (TypeId::of::<T>(), remote_id);
 
         let mut state = self.state.write();
@@ -550,36 +537,31 @@ impl Client {
     #[track_caller]
     pub fn add_message_handler<M, E, H, F>(
         self: &Arc<Self>,
-        model: ModelHandle<E>,
+        entity: WeakModel<E>,
         handler: H,
     ) -> Subscription
     where
         M: EnvelopedMessage,
-        E: Entity,
+        E: 'static,
         H: 'static
-            + Send
             + Sync
-            + Fn(ModelHandle<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
+            + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F
+            + Send
+            + Sync,
         F: 'static + Future<Output = Result<()>>,
     {
         let message_type_id = TypeId::of::<M>();
-
         let mut state = self.state.write();
         state
             .models_by_message_type
-            .insert(message_type_id, model.downgrade().into_any());
+            .insert(message_type_id, entity.into());
 
         let prev_handler = state.message_handlers.insert(
             message_type_id,
-            Arc::new(move |handle, envelope, client, cx| {
-                let handle = if let Subscriber::Model(handle) = handle {
-                    handle
-                } else {
-                    unreachable!();
-                };
-                let model = handle.downcast::<E>().unwrap();
+            Arc::new(move |subscriber, envelope, client, cx| {
+                let subscriber = subscriber.downcast::<E>().unwrap();
                 let envelope = envelope.into_any().downcast::<TypedEnvelope<M>>().unwrap();
-                handler(model, *envelope, client.clone(), cx).boxed_local()
+                handler(subscriber, *envelope, client.clone(), cx).boxed_local()
             }),
         );
         if prev_handler.is_some() {
@@ -600,16 +582,17 @@ impl Client {
 
     pub fn add_request_handler<M, E, H, F>(
         self: &Arc<Self>,
-        model: ModelHandle<E>,
+        model: WeakModel<E>,
         handler: H,
     ) -> Subscription
     where
         M: RequestMessage,
-        E: Entity,
+        E: 'static,
         H: 'static
-            + Send
             + Sync
-            + Fn(ModelHandle<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
+            + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F
+            + Send
+            + Sync,
         F: 'static + Future<Output = Result<M::Response>>,
     {
         self.add_message_handler(model, move |handle, envelope, this, cx| {
@@ -621,52 +604,23 @@ impl Client {
         })
     }
 
-    pub fn add_view_message_handler<M, E, H, F>(self: &Arc<Self>, handler: H)
-    where
-        M: EntityMessage,
-        E: View,
-        H: 'static
-            + Send
-            + Sync
-            + Fn(WeakViewHandle<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
-        F: 'static + Future<Output = Result<()>>,
-    {
-        self.add_entity_message_handler::<M, E, _, _>(move |handle, message, client, cx| {
-            if let Subscriber::View(handle) = handle {
-                handler(handle.downcast::<E>().unwrap(), message, client, cx)
-            } else {
-                unreachable!();
-            }
-        })
-    }
-
     pub fn add_model_message_handler<M, E, H, F>(self: &Arc<Self>, handler: H)
     where
         M: EntityMessage,
-        E: Entity,
-        H: 'static
-            + Send
-            + Sync
-            + Fn(ModelHandle<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
+        E: 'static,
+        H: 'static + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F + Send + Sync,
         F: 'static + Future<Output = Result<()>>,
     {
-        self.add_entity_message_handler::<M, E, _, _>(move |handle, message, client, cx| {
-            if let Subscriber::Model(handle) = handle {
-                handler(handle.downcast::<E>().unwrap(), message, client, cx)
-            } else {
-                unreachable!();
-            }
+        self.add_entity_message_handler::<M, E, _, _>(move |subscriber, message, client, cx| {
+            handler(subscriber.downcast::<E>().unwrap(), message, client, cx)
         })
     }
 
     fn add_entity_message_handler<M, E, H, F>(self: &Arc<Self>, handler: H)
     where
         M: EntityMessage,
-        E: Entity,
-        H: 'static
-            + Send
-            + Sync
-            + Fn(Subscriber, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
+        E: 'static,
+        H: 'static + Fn(AnyModel, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F + Send + Sync,
         F: 'static + Future<Output = Result<()>>,
     {
         let model_type_id = TypeId::of::<E>();
@@ -704,11 +658,8 @@ impl Client {
     pub fn add_model_request_handler<M, E, H, F>(self: &Arc<Self>, handler: H)
     where
         M: EntityMessage + RequestMessage,
-        E: Entity,
-        H: 'static
-            + Send
-            + Sync
-            + Fn(ModelHandle<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
+        E: 'static,
+        H: 'static + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F + Send + Sync,
         F: 'static + Future<Output = Result<M::Response>>,
     {
         self.add_model_message_handler(move |entity, envelope, client, cx| {
@@ -720,25 +671,6 @@ impl Client {
         })
     }
 
-    pub fn add_view_request_handler<M, E, H, F>(self: &Arc<Self>, handler: H)
-    where
-        M: EntityMessage + RequestMessage,
-        E: View,
-        H: 'static
-            + Send
-            + Sync
-            + Fn(WeakViewHandle<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
-        F: 'static + Future<Output = Result<M::Response>>,
-    {
-        self.add_view_message_handler(move |entity, envelope, client, cx| {
-            Self::respond_to_request::<M, _>(
-                envelope.receipt(),
-                handler(entity, envelope, client.clone(), cx),
-                client,
-            )
-        })
-    }
-
     async fn respond_to_request<T: RequestMessage, F: Future<Output = Result<T::Response>>>(
         receipt: Receipt<T>,
         response: F,
@@ -823,14 +755,15 @@ impl Client {
             self.set_status(Status::Reconnecting, cx);
         }
 
-        let mut timeout = cx.background().timer(CONNECTION_TIMEOUT).fuse();
+        let mut timeout =
+            futures::FutureExt::fuse(cx.background_executor().timer(CONNECTION_TIMEOUT));
         futures::select_biased! {
             connection = self.establish_connection(&credentials, cx).fuse() => {
                 match connection {
                     Ok(conn) => {
                         self.state.write().credentials = Some(credentials.clone());
                         if !read_from_keychain && IMPERSONATE_LOGIN.is_none() {
-                            write_credentials_to_keychain(&credentials, cx).log_err();
+                            write_credentials_to_keychain(credentials, cx).log_err();
                         }
 
                         futures::select_biased! {
@@ -844,7 +777,7 @@ impl Client {
                     Err(EstablishConnectionError::Unauthorized) => {
                         self.state.write().credentials.take();
                         if read_from_keychain {
-                            cx.platform().delete_credentials(&ZED_SERVER_URL).log_err();
+                            delete_credentials_from_keychain(cx).log_err();
                             self.set_status(Status::SignedOut, cx);
                             self.authenticate_and_connect(false, cx).await
                         } else {
@@ -874,12 +807,13 @@ impl Client {
         conn: Connection,
         cx: &AsyncAppContext,
     ) -> Result<()> {
-        let executor = cx.background();
+        let executor = cx.background_executor();
         log::info!("add connection to peer");
-        let (connection_id, handle_io, mut incoming) = self
-            .peer
-            .add_connection(conn, move |duration| executor.timer(duration));
-        let handle_io = cx.background().spawn(handle_io);
+        let (connection_id, handle_io, mut incoming) = self.peer.add_connection(conn, {
+            let executor = executor.clone();
+            move |duration| executor.timer(duration)
+        });
+        let handle_io = executor.spawn(handle_io);
 
         let peer_id = async {
             log::info!("waiting for server hello");
@@ -925,10 +859,10 @@ impl Client {
             },
             cx,
         );
-        cx.foreground()
-            .spawn({
-                let cx = cx.clone();
-                let this = self.clone();
+
+        cx.spawn({
+            let this = self.clone();
+            |cx| {
                 async move {
                     while let Some(message) = incoming.next().await {
                         this.handle_message(message, &cx);
@@ -936,13 +870,13 @@ impl Client {
                         smol::future::yield_now().await;
                     }
                 }
-            })
-            .detach();
+            }
+        })
+        .detach();
 
-        let this = self.clone();
-        let cx = cx.clone();
-        cx.foreground()
-            .spawn(async move {
+        cx.spawn({
+            let this = self.clone();
+            move |cx| async move {
                 match handle_io.await {
                     Ok(()) => {
                         if this.status().borrow().clone()
@@ -959,8 +893,9 @@ impl Client {
                         this.set_status(Status::ConnectionLost, &cx);
                     }
                 }
-            })
-            .detach();
+            }
+        })
+        .detach();
 
         Ok(())
     }
@@ -1032,13 +967,7 @@ impl Client {
         credentials: &Credentials,
         cx: &AsyncAppContext,
     ) -> Task<Result<Connection, EstablishConnectionError>> {
-        let release_channel = cx.read(|cx| {
-            if cx.has_global::<ReleaseChannel>() {
-                Some(*cx.global::<ReleaseChannel>())
-            } else {
-                None
-            }
-        });
+        let release_channel = cx.try_read_global(|channel: &ReleaseChannel, _| *channel);
 
         let request = Request::builder()
             .header(
@@ -1048,7 +977,7 @@ impl Client {
             .header("x-zed-protocol-version", rpc::PROTOCOL_VERSION);
 
         let http = self.http.clone();
-        cx.background().spawn(async move {
+        cx.background_executor().spawn(async move {
             let mut rpc_url = Self::get_rpc_url(http, release_channel).await?;
             let rpc_host = rpc_url
                 .host_str()
@@ -1089,96 +1018,118 @@ impl Client {
         self: &Arc<Self>,
         cx: &AsyncAppContext,
     ) -> Task<Result<Credentials>> {
-        let platform = cx.platform();
-        let executor = cx.background();
         let http = self.http.clone();
+        cx.spawn(|cx| async move {
+            let background = cx.background_executor().clone();
+
+            let (open_url_tx, open_url_rx) = oneshot::channel::<String>();
+            cx.update(|cx| {
+                cx.spawn(move |cx| async move {
+                    let url = open_url_rx.await?;
+                    cx.update(|cx| cx.open_url(&url))
+                })
+                .detach_and_log_err(cx);
+            })
+            .log_err();
 
-        executor.clone().spawn(async move {
-            // Generate a pair of asymmetric encryption keys. The public key will be used by the
-            // zed server to encrypt the user's access token, so that it can'be intercepted by
-            // any other app running on the user's device.
-            let (public_key, private_key) =
-                rpc::auth::keypair().expect("failed to generate keypair for auth");
-            let public_key_string =
-                String::try_from(public_key).expect("failed to serialize public key for auth");
-
-            if let Some((login, token)) = IMPERSONATE_LOGIN.as_ref().zip(ADMIN_API_TOKEN.as_ref()) {
-                return Self::authenticate_as_admin(http, login.clone(), token.clone()).await;
-            }
-
-            // Start an HTTP server to receive the redirect from Zed's sign-in page.
-            let server = tiny_http::Server::http("127.0.0.1:0").expect("failed to find open port");
-            let port = server.server_addr().port();
-
-            // Open the Zed sign-in page in the user's browser, with query parameters that indicate
-            // that the user is signing in from a Zed app running on the same device.
-            let mut url = format!(
-                "{}/native_app_signin?native_app_port={}&native_app_public_key={}",
-                *ZED_SERVER_URL, port, public_key_string
-            );
-
-            if let Some(impersonate_login) = IMPERSONATE_LOGIN.as_ref() {
-                log::info!("impersonating user @{}", impersonate_login);
-                write!(&mut url, "&impersonate={}", impersonate_login).unwrap();
-            }
+            let credentials = background
+                .clone()
+                .spawn(async move {
+                    // Generate a pair of asymmetric encryption keys. The public key will be used by the
+                    // zed server to encrypt the user's access token, so that it can'be intercepted by
+                    // any other app running on the user's device.
+                    let (public_key, private_key) =
+                        rpc::auth::keypair().expect("failed to generate keypair for auth");
+                    let public_key_string = String::try_from(public_key)
+                        .expect("failed to serialize public key for auth");
+
+                    if let Some((login, token)) =
+                        IMPERSONATE_LOGIN.as_ref().zip(ADMIN_API_TOKEN.as_ref())
+                    {
+                        return Self::authenticate_as_admin(http, login.clone(), token.clone())
+                            .await;
+                    }
 
-            platform.open_url(&url);
+                    // Start an HTTP server to receive the redirect from Zed's sign-in page.
+                    let server =
+                        tiny_http::Server::http("127.0.0.1:0").expect("failed to find open port");
+                    let port = server.server_addr().port();
+
+                    // Open the Zed sign-in page in the user's browser, with query parameters that indicate
+                    // that the user is signing in from a Zed app running on the same device.
+                    let mut url = format!(
+                        "{}/native_app_signin?native_app_port={}&native_app_public_key={}",
+                        *ZED_SERVER_URL, port, public_key_string
+                    );
+
+                    if let Some(impersonate_login) = IMPERSONATE_LOGIN.as_ref() {
+                        log::info!("impersonating user @{}", impersonate_login);
+                        write!(&mut url, "&impersonate={}", impersonate_login).unwrap();
+                    }
 
-            // Receive the HTTP request from the user's browser. Retrieve the user id and encrypted
-            // access token from the query params.
-            //
-            // TODO - Avoid ever starting more than one HTTP server. Maybe switch to using a
-            // custom URL scheme instead of this local HTTP server.
-            let (user_id, access_token) = executor
-                .spawn(async move {
-                    for _ in 0..100 {
-                        if let Some(req) = server.recv_timeout(Duration::from_secs(1))? {
-                            let path = req.url();
-                            let mut user_id = None;
-                            let mut access_token = None;
-                            let url = Url::parse(&format!("http://example.com{}", path))
-                                .context("failed to parse login notification url")?;
-                            for (key, value) in url.query_pairs() {
-                                if key == "access_token" {
-                                    access_token = Some(value.to_string());
-                                } else if key == "user_id" {
-                                    user_id = Some(value.to_string());
+                    open_url_tx.send(url).log_err();
+
+                    // Receive the HTTP request from the user's browser. Retrieve the user id and encrypted
+                    // access token from the query params.
+                    //
+                    // TODO - Avoid ever starting more than one HTTP server. Maybe switch to using a
+                    // custom URL scheme instead of this local HTTP server.
+                    let (user_id, access_token) = background
+                        .spawn(async move {
+                            for _ in 0..100 {
+                                if let Some(req) = server.recv_timeout(Duration::from_secs(1))? {
+                                    let path = req.url();
+                                    let mut user_id = None;
+                                    let mut access_token = None;
+                                    let url = Url::parse(&format!("http://example.com{}", path))
+                                        .context("failed to parse login notification url")?;
+                                    for (key, value) in url.query_pairs() {
+                                        if key == "access_token" {
+                                            access_token = Some(value.to_string());
+                                        } else if key == "user_id" {
+                                            user_id = Some(value.to_string());
+                                        }
+                                    }
+
+                                    let post_auth_url =
+                                        format!("{}/native_app_signin_succeeded", *ZED_SERVER_URL);
+                                    req.respond(
+                                        tiny_http::Response::empty(302).with_header(
+                                            tiny_http::Header::from_bytes(
+                                                &b"Location"[..],
+                                                post_auth_url.as_bytes(),
+                                            )
+                                            .unwrap(),
+                                        ),
+                                    )
+                                    .context("failed to respond to login http request")?;
+                                    return Ok((
+                                        user_id
+                                            .ok_or_else(|| anyhow!("missing user_id parameter"))?,
+                                        access_token.ok_or_else(|| {
+                                            anyhow!("missing access_token parameter")
+                                        })?,
+                                    ));
                                 }
                             }
 
-                            let post_auth_url =
-                                format!("{}/native_app_signin_succeeded", *ZED_SERVER_URL);
-                            req.respond(
-                                tiny_http::Response::empty(302).with_header(
-                                    tiny_http::Header::from_bytes(
-                                        &b"Location"[..],
-                                        post_auth_url.as_bytes(),
-                                    )
-                                    .unwrap(),
-                                ),
-                            )
-                            .context("failed to respond to login http request")?;
-                            return Ok((
-                                user_id.ok_or_else(|| anyhow!("missing user_id parameter"))?,
-                                access_token
-                                    .ok_or_else(|| anyhow!("missing access_token parameter"))?,
-                            ));
-                        }
-                    }
+                            Err(anyhow!("didn't receive login redirect"))
+                        })
+                        .await?;
+
+                    let access_token = private_key
+                        .decrypt_string(&access_token)
+                        .context("failed to decrypt access token")?;
 
-                    Err(anyhow!("didn't receive login redirect"))
+                    Ok(Credentials {
+                        user_id: user_id.parse()?,
+                        access_token,
+                    })
                 })
                 .await?;
 
-            let access_token = private_key
-                .decrypt_string(&access_token)
-                .context("failed to decrypt access token")?;
-            platform.activate(true);
-
-            Ok(Credentials {
-                user_id: user_id.parse()?,
-                access_token,
-            })
+            cx.update(|cx| cx.activate(true))?;
+            Ok(credentials)
         })
     }
 
@@ -1307,12 +1258,12 @@ impl Client {
 
         let mut subscriber = None;
 
-        if let Some(message_model) = state
+        if let Some(handle) = state
             .models_by_message_type
             .get(&payload_type_id)
-            .and_then(|model| model.upgrade(cx))
+            .and_then(|handle| handle.upgrade())
         {
-            subscriber = Some(Subscriber::Model(message_model));
+            subscriber = Some(handle);
         } else if let Some((extract_entity_id, entity_type_id)) =
             state.entity_id_extractors.get(&payload_type_id).zip(
                 state
@@ -1332,12 +1283,10 @@ impl Client {
                     return;
                 }
                 Some(weak_subscriber @ _) => match weak_subscriber {
-                    WeakSubscriber::Model(handle) => {
-                        subscriber = handle.upgrade(cx).map(Subscriber::Model);
-                    }
-                    WeakSubscriber::View(handle) => {
-                        subscriber = Some(Subscriber::View(handle.clone()));
+                    WeakSubscriber::Entity { handle } => {
+                        subscriber = handle.upgrade();
                     }
+
                     WeakSubscriber::Pending(_) => {}
                 },
                 _ => {}
@@ -1367,8 +1316,7 @@ impl Client {
                 sender_id,
                 type_name
             );
-            cx.foreground()
-                .spawn(async move {
+            cx.spawn(move |_| async move {
                     match future.await {
                         Ok(()) => {
                             log::debug!(
@@ -1407,22 +1355,30 @@ fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option<Credentials> {
     }
 
     let (user_id, access_token) = cx
-        .platform()
-        .read_credentials(&ZED_SERVER_URL)
-        .log_err()
-        .flatten()?;
+        .update(|cx| cx.read_credentials(&ZED_SERVER_URL).log_err().flatten())
+        .ok()??;
+
     Some(Credentials {
         user_id: user_id.parse().ok()?,
         access_token: String::from_utf8(access_token).ok()?,
     })
 }
 
-fn write_credentials_to_keychain(credentials: &Credentials, cx: &AsyncAppContext) -> Result<()> {
-    cx.platform().write_credentials(
-        &ZED_SERVER_URL,
-        &credentials.user_id.to_string(),
-        credentials.access_token.as_bytes(),
-    )
+async fn write_credentials_to_keychain(
+    credentials: Credentials,
+    cx: &AsyncAppContext,
+) -> Result<()> {
+    cx.update(move |cx| {
+        cx.write_credentials(
+            &ZED_SERVER_URL,
+            &credentials.user_id.to_string(),
+            credentials.access_token.as_bytes(),
+        )
+    })?
+}
+
+async fn delete_credentials_from_keychain(cx: &AsyncAppContext) -> Result<()> {
+    cx.update(move |cx| cx.delete_credentials(&ZED_SERVER_URL))?
 }
 
 const WORKTREE_URL_PREFIX: &str = "zed://worktrees/";
@@ -1446,15 +1402,14 @@ pub fn decode_worktree_url(url: &str) -> Option<(u64, String)> {
 mod tests {
     use super::*;
     use crate::test::FakeServer;
-    use gpui::{executor::Deterministic, TestAppContext};
+
+    use gpui::{BackgroundExecutor, Context, TestAppContext};
     use parking_lot::Mutex;
     use std::future;
     use util::http::FakeHttpClient;
 
     #[gpui::test(iterations = 10)]
     async fn test_reconnection(cx: &mut TestAppContext) {
-        cx.foreground().forbid_parking();
-
         let user_id = 5;
         let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
         let server = FakeServer::for_client(user_id, &client, cx).await;
@@ -1470,7 +1425,7 @@ mod tests {
         while !matches!(status.next().await, Some(Status::ReconnectionError { .. })) {}
 
         server.allow_connections();
-        cx.foreground().advance_clock(Duration::from_secs(10));
+        cx.executor().advance_clock(Duration::from_secs(10));
         while !matches!(status.next().await, Some(Status::Connected { .. })) {}
         assert_eq!(server.auth_count(), 1); // Client reused the cached credentials when reconnecting
 
@@ -1481,22 +1436,21 @@ mod tests {
         // Clear cached credentials after authentication fails
         server.roll_access_token();
         server.allow_connections();
-        cx.foreground().advance_clock(Duration::from_secs(10));
+        cx.executor().run_until_parked();
+        cx.executor().advance_clock(Duration::from_secs(10));
         while !matches!(status.next().await, Some(Status::Connected { .. })) {}
         assert_eq!(server.auth_count(), 2); // Client re-authenticated due to an invalid token
     }
 
     #[gpui::test(iterations = 10)]
-    async fn test_connection_timeout(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
-        deterministic.forbid_parking();
-
+    async fn test_connection_timeout(executor: BackgroundExecutor, cx: &mut TestAppContext) {
         let user_id = 5;
         let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
         let mut status = client.status();
 
         // Time out when client tries to connect.
         client.override_authenticate(move |cx| {
-            cx.foreground().spawn(async move {
+            cx.background_executor().spawn(async move {
                 Ok(Credentials {
                     user_id,
                     access_token: "token".into(),
@@ -1504,7 +1458,7 @@ mod tests {
             })
         });
         client.override_establish_connection(|_, cx| {
-            cx.foreground().spawn(async move {
+            cx.background_executor().spawn(async move {
                 future::pending::<()>().await;
                 unreachable!()
             })
@@ -1513,10 +1467,10 @@ mod tests {
             let client = client.clone();
             |cx| async move { client.authenticate_and_connect(false, &cx).await }
         });
-        deterministic.run_until_parked();
+        executor.run_until_parked();
         assert!(matches!(status.next().await, Some(Status::Connecting)));
 
-        deterministic.advance_clock(CONNECTION_TIMEOUT);
+        executor.advance_clock(CONNECTION_TIMEOUT);
         assert!(matches!(
             status.next().await,
             Some(Status::ConnectionError { .. })
@@ -1538,18 +1492,18 @@ mod tests {
         // Time out when re-establishing the connection.
         server.allow_connections();
         client.override_establish_connection(|_, cx| {
-            cx.foreground().spawn(async move {
+            cx.background_executor().spawn(async move {
                 future::pending::<()>().await;
                 unreachable!()
             })
         });
-        deterministic.advance_clock(2 * INITIAL_RECONNECTION_DELAY);
+        executor.advance_clock(2 * INITIAL_RECONNECTION_DELAY);
         assert!(matches!(
             status.next().await,
             Some(Status::Reconnecting { .. })
         ));
 
-        deterministic.advance_clock(CONNECTION_TIMEOUT);
+        executor.advance_clock(CONNECTION_TIMEOUT);
         assert!(matches!(
             status.next().await,
             Some(Status::ReconnectionError { .. })
@@ -1559,10 +1513,8 @@ mod tests {
     #[gpui::test(iterations = 10)]
     async fn test_authenticating_more_than_once(
         cx: &mut TestAppContext,
-        deterministic: Arc<Deterministic>,
+        executor: BackgroundExecutor,
     ) {
-        cx.foreground().forbid_parking();
-
         let auth_count = Arc::new(Mutex::new(0));
         let dropped_auth_count = Arc::new(Mutex::new(0));
         let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
@@ -1572,7 +1524,7 @@ mod tests {
             move |cx| {
                 let auth_count = auth_count.clone();
                 let dropped_auth_count = dropped_auth_count.clone();
-                cx.foreground().spawn(async move {
+                cx.background_executor().spawn(async move {
                     *auth_count.lock() += 1;
                     let _drop = util::defer(move || *dropped_auth_count.lock() += 1);
                     future::pending::<()>().await;
@@ -1581,19 +1533,19 @@ mod tests {
             }
         });
 
-        let _authenticate = cx.spawn(|cx| {
+        let _authenticate = cx.spawn({
             let client = client.clone();
-            async move { client.authenticate_and_connect(false, &cx).await }
+            move |cx| async move { client.authenticate_and_connect(false, &cx).await }
         });
-        deterministic.run_until_parked();
+        executor.run_until_parked();
         assert_eq!(*auth_count.lock(), 1);
         assert_eq!(*dropped_auth_count.lock(), 0);
 
-        let _authenticate = cx.spawn(|cx| {
+        let _authenticate = cx.spawn({
             let client = client.clone();
-            async move { client.authenticate_and_connect(false, &cx).await }
+            |cx| async move { client.authenticate_and_connect(false, &cx).await }
         });
-        deterministic.run_until_parked();
+        executor.run_until_parked();
         assert_eq!(*auth_count.lock(), 2);
         assert_eq!(*dropped_auth_count.lock(), 1);
     }
@@ -1611,8 +1563,6 @@ mod tests {
 
     #[gpui::test]
     async fn test_subscribing_to_entity(cx: &mut TestAppContext) {
-        cx.foreground().forbid_parking();
-
         let user_id = 5;
         let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
         let server = FakeServer::for_client(user_id, &client, cx).await;
@@ -1620,8 +1570,8 @@ mod tests {
         let (done_tx1, mut done_rx1) = smol::channel::unbounded();
         let (done_tx2, mut done_rx2) = smol::channel::unbounded();
         client.add_model_message_handler(
-            move |model: ModelHandle<Model>, _: TypedEnvelope<proto::JoinProject>, _, cx| {
-                match model.read_with(&cx, |model, _| model.id) {
+            move |model: Model<TestModel>, _: TypedEnvelope<proto::JoinProject>, _, mut cx| {
+                match model.update(&mut cx, |model, _| model.id).unwrap() {
                     1 => done_tx1.try_send(()).unwrap(),
                     2 => done_tx2.try_send(()).unwrap(),
                     _ => unreachable!(),
@@ -1629,15 +1579,15 @@ mod tests {
                 async { Ok(()) }
             },
         );
-        let model1 = cx.add_model(|_| Model {
+        let model1 = cx.new_model(|_| TestModel {
             id: 1,
             subscription: None,
         });
-        let model2 = cx.add_model(|_| Model {
+        let model2 = cx.new_model(|_| TestModel {
             id: 2,
             subscription: None,
         });
-        let model3 = cx.add_model(|_| Model {
+        let model3 = cx.new_model(|_| TestModel {
             id: 3,
             subscription: None,
         });

crates/client/src/telemetry.rs 🔗

@@ -1,9 +1,11 @@
 use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};
 use chrono::{DateTime, Utc};
-use gpui::{executor::Background, serde_json, AppContext, Task};
+use futures::Future;
+use gpui::{serde_json, AppContext, AppMetadata, BackgroundExecutor, Task};
 use lazy_static::lazy_static;
 use parking_lot::Mutex;
 use serde::Serialize;
+use settings::Settings;
 use std::{env, io::Write, mem, path::PathBuf, sync::Arc, time::Duration};
 use sysinfo::{
     CpuRefreshKind, Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt,
@@ -14,19 +16,16 @@ use util::{channel::ReleaseChannel, TryFutureExt};
 
 pub struct Telemetry {
     http_client: Arc<dyn HttpClient>,
-    executor: Arc<Background>,
+    executor: BackgroundExecutor,
     state: Mutex<TelemetryState>,
 }
 
-#[derive(Default)]
 struct TelemetryState {
     metrics_id: Option<Arc<str>>,      // Per logged-in user
     installation_id: Option<Arc<str>>, // Per app installation (different for dev, nightly, preview, and stable)
     session_id: Option<Arc<str>>,      // Per app launch
-    app_version: Option<Arc<str>>,
     release_channel: Option<&'static str>,
-    os_name: &'static str,
-    os_version: Option<Arc<str>>,
+    app_metadata: AppMetadata,
     architecture: &'static str,
     clickhouse_events_queue: Vec<ClickhouseEventWrapper>,
     flush_clickhouse_events_task: Option<Task<()>>,
@@ -48,9 +47,9 @@ struct ClickhouseEventRequestBody {
     installation_id: Option<Arc<str>>,
     session_id: Option<Arc<str>>,
     is_staff: Option<bool>,
-    app_version: Option<Arc<str>>,
+    app_version: Option<String>,
     os_name: &'static str,
-    os_version: Option<Arc<str>>,
+    os_version: Option<String>,
     architecture: &'static str,
     release_channel: Option<&'static str>,
     events: Vec<ClickhouseEventWrapper>,
@@ -130,25 +129,23 @@ const MAX_QUEUE_LEN: usize = 50;
 const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(1);
 
 #[cfg(not(debug_assertions))]
-const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(120);
+const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(60 * 5);
 
 impl Telemetry {
-    pub fn new(client: Arc<dyn HttpClient>, cx: &AppContext) -> Arc<Self> {
-        let platform = cx.platform();
+    pub fn new(client: Arc<dyn HttpClient>, cx: &mut AppContext) -> Arc<Self> {
         let release_channel = if cx.has_global::<ReleaseChannel>() {
             Some(cx.global::<ReleaseChannel>().display_name())
         } else {
             None
         };
+
         // TODO: Replace all hardware stuff with nested SystemSpecs json
         let this = Arc::new(Self {
             http_client: client,
-            executor: cx.background().clone(),
+            executor: cx.background_executor().clone(),
             state: Mutex::new(TelemetryState {
-                os_name: platform.os_name().into(),
-                os_version: platform.os_version().ok().map(|v| v.to_string().into()),
+                app_metadata: cx.app_metadata(),
                 architecture: env::consts::ARCH,
-                app_version: platform.app_version().ok().map(|v| v.to_string().into()),
                 release_channel,
                 installation_id: None,
                 metrics_id: None,
@@ -161,9 +158,30 @@ impl Telemetry {
             }),
         });
 
+        // We should only ever have one instance of Telemetry, leak the subscription to keep it alive
+        // rather than store in TelemetryState, complicating spawn as subscriptions are not Send
+        std::mem::forget(cx.on_app_quit({
+            let this = this.clone();
+            move |cx| this.shutdown_telemetry(cx)
+        }));
+
         this
     }
 
+    #[cfg(any(test, feature = "test-support"))]
+    fn shutdown_telemetry(self: &Arc<Self>, _: &mut AppContext) -> impl Future<Output = ()> {
+        Task::ready(())
+    }
+
+    // Skip calling this function in tests.
+    // TestAppContext ends up calling this function on shutdown and it panics when trying to find the TelemetrySettings
+    #[cfg(not(any(test, feature = "test-support")))]
+    fn shutdown_telemetry(self: &Arc<Self>, cx: &mut AppContext) -> impl Future<Output = ()> {
+        let telemetry_settings = TelemetrySettings::get_global(cx).clone();
+        self.report_app_event(telemetry_settings, "close", true);
+        Task::ready(())
+    }
+
     pub fn log_file_path(&self) -> Option<PathBuf> {
         Some(self.state.lock().log_file.as_ref()?.path().to_path_buf())
     }
@@ -180,7 +198,7 @@ impl Telemetry {
         drop(state);
 
         let this = self.clone();
-        cx.spawn(|mut cx| async move {
+        cx.spawn(|cx| async move {
             // Avoiding calling `System::new_all()`, as there have been crashes related to it
             let refresh_kind = RefreshKind::new()
                 .with_memory() // For memory usage
@@ -209,7 +227,13 @@ impl Telemetry {
                     return;
                 };
 
-                let telemetry_settings = cx.update(|cx| *settings::get::<TelemetrySettings>(cx));
+                let telemetry_settings = if let Ok(telemetry_settings) =
+                    cx.update(|cx| *TelemetrySettings::get_global(cx))
+                {
+                    telemetry_settings
+                } else {
+                    break;
+                };
 
                 this.report_memory_event(
                     telemetry_settings,
@@ -232,7 +256,7 @@ impl Telemetry {
         is_staff: bool,
         cx: &AppContext,
     ) {
-        if !settings::get::<TelemetrySettings>(cx).metrics {
+        if !TelemetrySettings::get_global(cx).metrics {
             return;
         }
 
@@ -461,9 +485,15 @@ impl Telemetry {
                             installation_id: state.installation_id.clone(),
                             session_id: state.session_id.clone(),
                             is_staff: state.is_staff.clone(),
-                            app_version: state.app_version.clone(),
-                            os_name: state.os_name,
-                            os_version: state.os_version.clone(),
+                            app_version: state
+                                .app_metadata
+                                .app_version
+                                .map(|version| version.to_string()),
+                            os_name: state.app_metadata.os_name,
+                            os_version: state
+                                .app_metadata
+                                .os_version
+                                .map(|version| version.to_string()),
                             architecture: state.architecture,
 
                             release_channel: state.release_channel,

crates/client/src/test.rs 🔗

@@ -1,20 +1,19 @@
 use crate::{Client, Connection, Credentials, EstablishConnectionError, UserStore};
 use anyhow::{anyhow, Result};
 use futures::{stream::BoxStream, StreamExt};
-use gpui::{executor, ModelHandle, TestAppContext};
+use gpui::{BackgroundExecutor, Context, Model, TestAppContext};
 use parking_lot::Mutex;
 use rpc::{
     proto::{self, GetPrivateUserInfo, GetPrivateUserInfoResponse},
     ConnectionId, Peer, Receipt, TypedEnvelope,
 };
-use std::{rc::Rc, sync::Arc};
-use util::http::FakeHttpClient;
+use std::sync::Arc;
 
 pub struct FakeServer {
     peer: Arc<Peer>,
     state: Arc<Mutex<FakeServerState>>,
     user_id: u64,
-    executor: Rc<executor::Foreground>,
+    executor: BackgroundExecutor,
 }
 
 #[derive(Default)]
@@ -36,7 +35,7 @@ impl FakeServer {
             peer: Peer::new(0),
             state: Default::default(),
             user_id: client_user_id,
-            executor: cx.foreground(),
+            executor: cx.executor(),
         };
 
         client
@@ -78,10 +77,11 @@ impl FakeServer {
                             Err(EstablishConnectionError::Unauthorized)?
                         }
 
-                        let (client_conn, server_conn, _) = Connection::in_memory(cx.background());
+                        let (client_conn, server_conn, _) =
+                            Connection::in_memory(cx.background_executor().clone());
                         let (connection_id, io, incoming) =
-                            peer.add_test_connection(server_conn, cx.background());
-                        cx.background().spawn(io).detach();
+                            peer.add_test_connection(server_conn, cx.background_executor().clone());
+                        cx.background_executor().spawn(io).detach();
                         {
                             let mut state = state.lock();
                             state.connection_id = Some(connection_id);
@@ -193,9 +193,8 @@ impl FakeServer {
         &self,
         client: Arc<Client>,
         cx: &mut TestAppContext,
-    ) -> ModelHandle<UserStore> {
-        let http_client = FakeHttpClient::with_404_response();
-        let user_store = cx.add_model(|cx| UserStore::new(client, http_client, cx));
+    ) -> Model<UserStore> {
+        let user_store = cx.new_model(|cx| UserStore::new(client, cx));
         assert_eq!(
             self.receive::<proto::GetUsers>()
                 .await

crates/client/src/user.rs 🔗

@@ -2,13 +2,12 @@ use super::{proto, Client, Status, TypedEnvelope};
 use anyhow::{anyhow, Context, Result};
 use collections::{hash_map::Entry, HashMap, HashSet};
 use feature_flags::FeatureFlagAppExt;
-use futures::{channel::mpsc, future, AsyncReadExt, Future, StreamExt};
-use gpui::{AsyncAppContext, Entity, ImageData, ModelContext, ModelHandle, Task};
+use futures::{channel::mpsc, Future, StreamExt};
+use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, SharedString, Task};
 use postage::{sink::Sink, watch};
 use rpc::proto::{RequestMessage, UsersResponse};
 use std::sync::{Arc, Weak};
 use text::ReplicaId;
-use util::http::HttpClient;
 use util::TryFutureExt as _;
 
 pub type UserId = u64;
@@ -20,7 +19,7 @@ pub struct ParticipantIndex(pub u32);
 pub struct User {
     pub id: UserId,
     pub github_login: String,
-    pub avatar: Option<Arc<ImageData>>,
+    pub avatar_uri: SharedString,
 }
 
 #[derive(Clone, Debug, PartialEq, Eq)]
@@ -76,9 +75,8 @@ pub struct UserStore {
     pending_contact_requests: HashMap<u64, usize>,
     invite_info: Option<InviteInfo>,
     client: Weak<Client>,
-    http: Arc<dyn HttpClient>,
     _maintain_contacts: Task<()>,
-    _maintain_current_user: Task<()>,
+    _maintain_current_user: Task<Result<()>>,
 }
 
 #[derive(Clone)]
@@ -103,9 +101,7 @@ pub enum ContactEventKind {
     Cancelled,
 }
 
-impl Entity for UserStore {
-    type Event = Event;
-}
+impl EventEmitter<Event> for UserStore {}
 
 enum UpdateContacts {
     Update(proto::UpdateContacts),
@@ -114,17 +110,13 @@ enum UpdateContacts {
 }
 
 impl UserStore {
-    pub fn new(
-        client: Arc<Client>,
-        http: Arc<dyn HttpClient>,
-        cx: &mut ModelContext<Self>,
-    ) -> Self {
+    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
         let (mut current_user_tx, current_user_rx) = watch::channel();
         let (update_contacts_tx, mut update_contacts_rx) = mpsc::unbounded();
         let rpc_subscriptions = vec![
-            client.add_message_handler(cx.handle(), Self::handle_update_contacts),
-            client.add_message_handler(cx.handle(), Self::handle_update_invite_info),
-            client.add_message_handler(cx.handle(), Self::handle_show_contacts),
+            client.add_message_handler(cx.weak_model(), Self::handle_update_contacts),
+            client.add_message_handler(cx.weak_model(), Self::handle_update_invite_info),
+            client.add_message_handler(cx.weak_model(), Self::handle_show_contacts),
         ];
         Self {
             users: Default::default(),
@@ -136,76 +128,71 @@ impl UserStore {
             invite_info: None,
             client: Arc::downgrade(&client),
             update_contacts_tx,
-            http,
-            _maintain_contacts: cx.spawn_weak(|this, mut cx| async move {
+            _maintain_contacts: cx.spawn(|this, mut cx| async move {
                 let _subscriptions = rpc_subscriptions;
                 while let Some(message) = update_contacts_rx.next().await {
-                    if let Some(this) = this.upgrade(&cx) {
+                    if let Ok(task) =
                         this.update(&mut cx, |this, cx| this.update_contacts(message, cx))
-                            .log_err()
-                            .await;
+                    {
+                        task.log_err().await;
+                    } else {
+                        break;
                     }
                 }
             }),
-            _maintain_current_user: cx.spawn_weak(|this, mut cx| async move {
+            _maintain_current_user: cx.spawn(|this, mut cx| async move {
                 let mut status = client.status();
                 while let Some(status) = status.next().await {
                     match status {
                         Status::Connected { .. } => {
-                            if let Some((this, user_id)) = this.upgrade(&cx).zip(client.user_id()) {
-                                let fetch_user = this
-                                    .update(&mut cx, |this, cx| this.get_user(user_id, cx))
-                                    .log_err();
+                            if let Some(user_id) = client.user_id() {
+                                let fetch_user = if let Ok(fetch_user) = this
+                                    .update(&mut cx, |this, cx| {
+                                        this.get_user(user_id, cx).log_err()
+                                    }) {
+                                    fetch_user
+                                } else {
+                                    break;
+                                };
                                 let fetch_metrics_id =
                                     client.request(proto::GetPrivateUserInfo {}).log_err();
                                 let (user, info) = futures::join!(fetch_user, fetch_metrics_id);
 
-                                if let Some(info) = info {
-                                    cx.update(|cx| {
+                                cx.update(|cx| {
+                                    if let Some(info) = info {
                                         cx.update_flags(info.staff, info.flags);
                                         client.telemetry.set_authenticated_user_info(
                                             Some(info.metrics_id.clone()),
                                             info.staff,
                                             cx,
                                         )
-                                    });
-                                } else {
-                                    cx.read(|cx| {
-                                        client
-                                            .telemetry
-                                            .set_authenticated_user_info(None, false, cx)
-                                    });
-                                }
+                                    }
+                                })?;
 
                                 current_user_tx.send(user).await.ok();
 
-                                this.update(&mut cx, |_, cx| {
-                                    cx.notify();
-                                });
+                                this.update(&mut cx, |_, cx| cx.notify())?;
                             }
                         }
                         Status::SignedOut => {
                             current_user_tx.send(None).await.ok();
-                            if let Some(this) = this.upgrade(&cx) {
-                                this.update(&mut cx, |this, cx| {
-                                    cx.notify();
-                                    this.clear_contacts()
-                                })
-                                .await;
-                            }
+                            this.update(&mut cx, |this, cx| {
+                                cx.notify();
+                                this.clear_contacts()
+                            })?
+                            .await;
                         }
                         Status::ConnectionLost => {
-                            if let Some(this) = this.upgrade(&cx) {
-                                this.update(&mut cx, |this, cx| {
-                                    cx.notify();
-                                    this.clear_contacts()
-                                })
-                                .await;
-                            }
+                            this.update(&mut cx, |this, cx| {
+                                cx.notify();
+                                this.clear_contacts()
+                            })?
+                            .await;
                         }
                         _ => {}
                     }
                 }
+                Ok(())
             }),
             pending_contact_requests: Default::default(),
         }
@@ -217,7 +204,7 @@ impl UserStore {
     }
 
     async fn handle_update_invite_info(
-        this: ModelHandle<Self>,
+        this: Model<Self>,
         message: TypedEnvelope<proto::UpdateInviteInfo>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -228,17 +215,17 @@ impl UserStore {
                 count: message.payload.count,
             });
             cx.notify();
-        });
+        })?;
         Ok(())
     }
 
     async fn handle_show_contacts(
-        this: ModelHandle<Self>,
+        this: Model<Self>,
         _: TypedEnvelope<proto::ShowContacts>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
     ) -> Result<()> {
-        this.update(&mut cx, |_, cx| cx.emit(Event::ShowContacts));
+        this.update(&mut cx, |_, cx| cx.emit(Event::ShowContacts))?;
         Ok(())
     }
 
@@ -247,7 +234,7 @@ impl UserStore {
     }
 
     async fn handle_update_contacts(
-        this: ModelHandle<Self>,
+        this: Model<Self>,
         message: TypedEnvelope<proto::UpdateContacts>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -256,7 +243,7 @@ impl UserStore {
             this.update_contacts_tx
                 .unbounded_send(UpdateContacts::Update(message.payload))
                 .unwrap();
-        });
+        })?;
         Ok(())
     }
 
@@ -292,6 +279,9 @@ impl UserStore {
                     // Users are fetched in parallel above and cached in call to get_users
                     // No need to paralellize here
                     let mut updated_contacts = Vec::new();
+                    let this = this
+                        .upgrade()
+                        .ok_or_else(|| anyhow!("can't upgrade user store handle"))?;
                     for contact in message.contacts {
                         updated_contacts.push(Arc::new(
                             Contact::from_proto(contact, &this, &mut cx).await?,
@@ -300,18 +290,18 @@ impl UserStore {
 
                     let mut incoming_requests = Vec::new();
                     for request in message.incoming_requests {
-                        incoming_requests.push(
+                        incoming_requests.push({
                             this.update(&mut cx, |this, cx| {
                                 this.get_user(request.requester_id, cx)
-                            })
-                            .await?,
-                        );
+                            })?
+                            .await?
+                        });
                     }
 
                     let mut outgoing_requests = Vec::new();
                     for requested_user_id in message.outgoing_requests {
                         outgoing_requests.push(
-                            this.update(&mut cx, |this, cx| this.get_user(requested_user_id, cx))
+                            this.update(&mut cx, |this, cx| this.get_user(requested_user_id, cx))?
                                 .await?,
                         );
                     }
@@ -378,7 +368,7 @@ impl UserStore {
                         }
 
                         cx.notify();
-                    });
+                    })?;
 
                     Ok(())
                 })
@@ -400,12 +390,6 @@ impl UserStore {
         &self.incoming_contact_requests
     }
 
-    pub fn has_incoming_contact_request(&self, user_id: u64) -> bool {
-        self.incoming_contact_requests
-            .iter()
-            .any(|user| user.id == user_id)
-    }
-
     pub fn outgoing_contact_requests(&self) -> &[Arc<User>] {
         &self.outgoing_contact_requests
     }
@@ -454,6 +438,12 @@ impl UserStore {
         self.perform_contact_request(user_id, proto::RemoveContact { user_id }, cx)
     }
 
+    pub fn has_incoming_contact_request(&self, user_id: u64) -> bool {
+        self.incoming_contact_requests
+            .iter()
+            .any(|user| user.id == user_id)
+    }
+
     pub fn respond_to_contact_request(
         &mut self,
         requester_id: u64,
@@ -480,7 +470,7 @@ impl UserStore {
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
         let client = self.client.upgrade();
-        cx.spawn_weak(|_, _| async move {
+        cx.spawn(move |_, _| async move {
             client
                 .ok_or_else(|| anyhow!("can't upgrade client reference"))?
                 .request(proto::RespondToContactRequest {
@@ -502,7 +492,7 @@ impl UserStore {
         *self.pending_contact_requests.entry(user_id).or_insert(0) += 1;
         cx.notify();
 
-        cx.spawn(|this, mut cx| async move {
+        cx.spawn(move |this, mut cx| async move {
             let response = client
                 .ok_or_else(|| anyhow!("can't upgrade client reference"))?
                 .request(request)
@@ -517,7 +507,7 @@ impl UserStore {
                     }
                 }
                 cx.notify();
-            });
+            })?;
             response?;
             Ok(())
         })
@@ -560,11 +550,11 @@ impl UserStore {
                         },
                         cx,
                     )
-                })
+                })?
                 .await?;
             }
 
-            this.read_with(&cx, |this, _| {
+            this.update(&mut cx, |this, _| {
                 user_ids
                     .iter()
                     .map(|user_id| {
@@ -574,7 +564,7 @@ impl UserStore {
                             .ok_or_else(|| anyhow!("user {} not found", user_id))
                     })
                     .collect()
-            })
+            })?
         })
     }
 
@@ -596,18 +586,18 @@ impl UserStore {
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Arc<User>>> {
         if let Some(user) = self.users.get(&user_id).cloned() {
-            return cx.foreground().spawn(async move { Ok(user) });
+            return Task::ready(Ok(user));
         }
 
         let load_users = self.get_users(vec![user_id], cx);
-        cx.spawn(|this, mut cx| async move {
+        cx.spawn(move |this, mut cx| async move {
             load_users.await?;
             this.update(&mut cx, |this, _| {
                 this.users
                     .get(&user_id)
                     .cloned()
                     .ok_or_else(|| anyhow!("server responded with no users"))
-            })
+            })?
         })
     }
 
@@ -625,25 +615,22 @@ impl UserStore {
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Vec<Arc<User>>>> {
         let client = self.client.clone();
-        let http = self.http.clone();
-        cx.spawn_weak(|this, mut cx| async move {
+        cx.spawn(|this, mut cx| async move {
             if let Some(rpc) = client.upgrade() {
                 let response = rpc.request(request).await.context("error loading users")?;
-                let users = future::join_all(
-                    response
-                        .users
-                        .into_iter()
-                        .map(|user| User::new(user, http.as_ref())),
-                )
-                .await;
+                let users = response
+                    .users
+                    .into_iter()
+                    .map(|user| User::new(user))
+                    .collect::<Vec<_>>();
+
+                this.update(&mut cx, |this, _| {
+                    for user in &users {
+                        this.users.insert(user.id, user.clone());
+                    }
+                })
+                .ok();
 
-                if let Some(this) = this.upgrade(&cx) {
-                    this.update(&mut cx, |this, _| {
-                        for user in &users {
-                            this.users.insert(user.id, user.clone());
-                        }
-                    });
-                }
                 Ok(users)
             } else {
                 Ok(Vec::new())
@@ -668,11 +655,11 @@ impl UserStore {
 }
 
 impl User {
-    async fn new(message: proto::User, http: &dyn HttpClient) -> Arc<Self> {
+    fn new(message: proto::User) -> Arc<Self> {
         Arc::new(User {
             id: message.id,
             github_login: message.github_login,
-            avatar: fetch_avatar(http, &message.avatar_url).warn_on_err().await,
+            avatar_uri: message.avatar_url.into(),
         })
     }
 }
@@ -680,13 +667,13 @@ impl User {
 impl Contact {
     async fn from_proto(
         contact: proto::Contact,
-        user_store: &ModelHandle<UserStore>,
+        user_store: &Model<UserStore>,
         cx: &mut AsyncAppContext,
     ) -> Result<Self> {
         let user = user_store
             .update(cx, |user_store, cx| {
                 user_store.get_user(contact.user_id, cx)
-            })
+            })?
             .await?;
         Ok(Self {
             user,
@@ -705,24 +692,3 @@ impl Collaborator {
         })
     }
 }
-
-async fn fetch_avatar(http: &dyn HttpClient, url: &str) -> Result<Arc<ImageData>> {
-    let mut response = http
-        .get(url, Default::default(), true)
-        .await
-        .map_err(|e| anyhow!("failed to send user avatar request: {}", e))?;
-
-    if !response.status().is_success() {
-        return Err(anyhow!("avatar request failed {:?}", response.status()));
-    }
-
-    let mut body = Vec::new();
-    response
-        .body_mut()
-        .read_to_end(&mut body)
-        .await
-        .map_err(|e| anyhow!("failed to read user avatar response body: {}", e))?;
-    let format = image::guess_format(&body)?;
-    let image = image::load_from_memory_with_format(&body, format)?.into_bgra8();
-    Ok(ImageData::new(image))
-}

crates/client2/Cargo.toml 🔗

@@ -1,53 +0,0 @@
-[package]
-name = "client2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/client2.rs"
-doctest = false
-
-[features]
-test-support = ["collections/test-support", "gpui/test-support", "rpc/test-support"]
-
-[dependencies]
-chrono = { version = "0.4", features = ["serde"] }
-collections = { path = "../collections" }
-db = { package = "db2", path = "../db2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-util = { path = "../util" }
-rpc = { package = "rpc2", path = "../rpc2" }
-text = { package = "text2",  path = "../text2" }
-settings = { package = "settings2", path = "../settings2" }
-feature_flags = { package = "feature_flags2", path = "../feature_flags2" }
-sum_tree = { path = "../sum_tree" }
-
-anyhow.workspace = true
-async-recursion = "0.3"
-async-tungstenite = { version = "0.16", features = ["async-tls"] }
-futures.workspace = true
-image = "0.23"
-lazy_static.workspace = true
-log.workspace = true
-parking_lot.workspace = true
-postage.workspace = true
-rand.workspace = true
-schemars.workspace = true
-serde.workspace = true
-serde_derive.workspace = true
-smol.workspace = true
-sysinfo.workspace = true
-tempfile = "3"
-thiserror.workspace = true
-time.workspace = true
-tiny_http = "0.8"
-uuid.workspace = true
-url = "2.2"
-
-[dev-dependencies]
-collections = { path = "../collections", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
-settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
-util = { path = "../util", features = ["test-support"] }

crates/client2/src/client2.rs 🔗

@@ -1,1675 +0,0 @@
-#[cfg(any(test, feature = "test-support"))]
-pub mod test;
-
-pub mod telemetry;
-pub mod user;
-
-use anyhow::{anyhow, Context as _, Result};
-use async_recursion::async_recursion;
-use async_tungstenite::tungstenite::{
-    error::Error as WebsocketError,
-    http::{Request, StatusCode},
-};
-use futures::{
-    channel::oneshot, future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt,
-    TryFutureExt as _, TryStreamExt,
-};
-use gpui::{
-    actions, serde_json, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, Model,
-    SemanticVersion, Task, WeakModel,
-};
-use lazy_static::lazy_static;
-use parking_lot::RwLock;
-use postage::watch;
-use rand::prelude::*;
-use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage};
-use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
-use settings::Settings;
-use std::{
-    any::TypeId,
-    collections::HashMap,
-    convert::TryFrom,
-    fmt::Write as _,
-    future::Future,
-    marker::PhantomData,
-    path::PathBuf,
-    sync::{atomic::AtomicU64, Arc, Weak},
-    time::{Duration, Instant},
-};
-use telemetry::Telemetry;
-use thiserror::Error;
-use url::Url;
-use util::channel::ReleaseChannel;
-use util::http::HttpClient;
-use util::{ResultExt, TryFutureExt};
-
-pub use rpc::*;
-pub use telemetry::ClickhouseEvent;
-pub use user::*;
-
-lazy_static! {
-    pub static ref ZED_SERVER_URL: String =
-        std::env::var("ZED_SERVER_URL").unwrap_or_else(|_| "https://zed.dev".to_string());
-    pub static ref IMPERSONATE_LOGIN: Option<String> = std::env::var("ZED_IMPERSONATE")
-        .ok()
-        .and_then(|s| if s.is_empty() { None } else { Some(s) });
-    pub static ref ADMIN_API_TOKEN: Option<String> = std::env::var("ZED_ADMIN_API_TOKEN")
-        .ok()
-        .and_then(|s| if s.is_empty() { None } else { Some(s) });
-    pub static ref ZED_APP_VERSION: Option<SemanticVersion> = std::env::var("ZED_APP_VERSION")
-        .ok()
-        .and_then(|v| v.parse().ok());
-    pub static ref ZED_APP_PATH: Option<PathBuf> =
-        std::env::var("ZED_APP_PATH").ok().map(PathBuf::from);
-    pub static ref ZED_ALWAYS_ACTIVE: bool =
-        std::env::var("ZED_ALWAYS_ACTIVE").map_or(false, |e| e.len() > 0);
-}
-
-pub const ZED_SECRET_CLIENT_TOKEN: &str = "618033988749894";
-pub const INITIAL_RECONNECTION_DELAY: Duration = Duration::from_millis(100);
-pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5);
-
-actions!(client, [SignIn, SignOut, Reconnect]);
-
-pub fn init_settings(cx: &mut AppContext) {
-    TelemetrySettings::register(cx);
-}
-
-pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
-    init_settings(cx);
-
-    let client = Arc::downgrade(client);
-    cx.on_action({
-        let client = client.clone();
-        move |_: &SignIn, cx| {
-            if let Some(client) = client.upgrade() {
-                cx.spawn(
-                    |cx| async move { client.authenticate_and_connect(true, &cx).log_err().await },
-                )
-                .detach();
-            }
-        }
-    });
-
-    cx.on_action({
-        let client = client.clone();
-        move |_: &SignOut, cx| {
-            if let Some(client) = client.upgrade() {
-                cx.spawn(|cx| async move {
-                    client.disconnect(&cx);
-                })
-                .detach();
-            }
-        }
-    });
-
-    cx.on_action({
-        let client = client.clone();
-        move |_: &Reconnect, cx| {
-            if let Some(client) = client.upgrade() {
-                cx.spawn(|cx| async move {
-                    client.reconnect(&cx);
-                })
-                .detach();
-            }
-        }
-    });
-}
-
-pub struct Client {
-    id: AtomicU64,
-    peer: Arc<Peer>,
-    http: Arc<dyn HttpClient>,
-    telemetry: Arc<Telemetry>,
-    state: RwLock<ClientState>,
-
-    #[allow(clippy::type_complexity)]
-    #[cfg(any(test, feature = "test-support"))]
-    authenticate: RwLock<
-        Option<Box<dyn 'static + Send + Sync + Fn(&AsyncAppContext) -> Task<Result<Credentials>>>>,
-    >,
-
-    #[allow(clippy::type_complexity)]
-    #[cfg(any(test, feature = "test-support"))]
-    establish_connection: RwLock<
-        Option<
-            Box<
-                dyn 'static
-                    + Send
-                    + Sync
-                    + Fn(
-                        &Credentials,
-                        &AsyncAppContext,
-                    ) -> Task<Result<Connection, EstablishConnectionError>>,
-            >,
-        >,
-    >,
-}
-
-#[derive(Error, Debug)]
-pub enum EstablishConnectionError {
-    #[error("upgrade required")]
-    UpgradeRequired,
-    #[error("unauthorized")]
-    Unauthorized,
-    #[error("{0}")]
-    Other(#[from] anyhow::Error),
-    #[error("{0}")]
-    Http(#[from] util::http::Error),
-    #[error("{0}")]
-    Io(#[from] std::io::Error),
-    #[error("{0}")]
-    Websocket(#[from] async_tungstenite::tungstenite::http::Error),
-}
-
-impl From<WebsocketError> for EstablishConnectionError {
-    fn from(error: WebsocketError) -> Self {
-        if let WebsocketError::Http(response) = &error {
-            match response.status() {
-                StatusCode::UNAUTHORIZED => return EstablishConnectionError::Unauthorized,
-                StatusCode::UPGRADE_REQUIRED => return EstablishConnectionError::UpgradeRequired,
-                _ => {}
-            }
-        }
-        EstablishConnectionError::Other(error.into())
-    }
-}
-
-impl EstablishConnectionError {
-    pub fn other(error: impl Into<anyhow::Error> + Send + Sync) -> Self {
-        Self::Other(error.into())
-    }
-}
-
-#[derive(Copy, Clone, Debug, PartialEq)]
-pub enum Status {
-    SignedOut,
-    UpgradeRequired,
-    Authenticating,
-    Connecting,
-    ConnectionError,
-    Connected {
-        peer_id: PeerId,
-        connection_id: ConnectionId,
-    },
-    ConnectionLost,
-    Reauthenticating,
-    Reconnecting,
-    ReconnectionError {
-        next_reconnection: Instant,
-    },
-}
-
-impl Status {
-    pub fn is_connected(&self) -> bool {
-        matches!(self, Self::Connected { .. })
-    }
-
-    pub fn is_signed_out(&self) -> bool {
-        matches!(self, Self::SignedOut | Self::UpgradeRequired)
-    }
-}
-
-struct ClientState {
-    credentials: Option<Credentials>,
-    status: (watch::Sender<Status>, watch::Receiver<Status>),
-    entity_id_extractors: HashMap<TypeId, fn(&dyn AnyTypedEnvelope) -> u64>,
-    _reconnect_task: Option<Task<()>>,
-    reconnect_interval: Duration,
-    entities_by_type_and_remote_id: HashMap<(TypeId, u64), WeakSubscriber>,
-    models_by_message_type: HashMap<TypeId, AnyWeakModel>,
-    entity_types_by_message_type: HashMap<TypeId, TypeId>,
-    #[allow(clippy::type_complexity)]
-    message_handlers: HashMap<
-        TypeId,
-        Arc<
-            dyn Send
-                + Sync
-                + Fn(
-                    AnyModel,
-                    Box<dyn AnyTypedEnvelope>,
-                    &Arc<Client>,
-                    AsyncAppContext,
-                ) -> LocalBoxFuture<'static, Result<()>>,
-        >,
-    >,
-}
-
-enum WeakSubscriber {
-    Entity { handle: AnyWeakModel },
-    Pending(Vec<Box<dyn AnyTypedEnvelope>>),
-}
-
-#[derive(Clone, Debug)]
-pub struct Credentials {
-    pub user_id: u64,
-    pub access_token: String,
-}
-
-impl Default for ClientState {
-    fn default() -> Self {
-        Self {
-            credentials: None,
-            status: watch::channel_with(Status::SignedOut),
-            entity_id_extractors: Default::default(),
-            _reconnect_task: None,
-            reconnect_interval: Duration::from_secs(5),
-            models_by_message_type: Default::default(),
-            entities_by_type_and_remote_id: Default::default(),
-            entity_types_by_message_type: Default::default(),
-            message_handlers: Default::default(),
-        }
-    }
-}
-
-pub enum Subscription {
-    Entity {
-        client: Weak<Client>,
-        id: (TypeId, u64),
-    },
-    Message {
-        client: Weak<Client>,
-        id: TypeId,
-    },
-}
-
-impl Drop for Subscription {
-    fn drop(&mut self) {
-        match self {
-            Subscription::Entity { client, id } => {
-                if let Some(client) = client.upgrade() {
-                    let mut state = client.state.write();
-                    let _ = state.entities_by_type_and_remote_id.remove(id);
-                }
-            }
-            Subscription::Message { client, id } => {
-                if let Some(client) = client.upgrade() {
-                    let mut state = client.state.write();
-                    let _ = state.entity_types_by_message_type.remove(id);
-                    let _ = state.message_handlers.remove(id);
-                }
-            }
-        }
-    }
-}
-
-pub struct PendingEntitySubscription<T: 'static> {
-    client: Arc<Client>,
-    remote_id: u64,
-    _entity_type: PhantomData<T>,
-    consumed: bool,
-}
-
-impl<T: 'static> PendingEntitySubscription<T> {
-    pub fn set_model(mut self, model: &Model<T>, cx: &mut AsyncAppContext) -> Subscription {
-        self.consumed = true;
-        let mut state = self.client.state.write();
-        let id = (TypeId::of::<T>(), self.remote_id);
-        let Some(WeakSubscriber::Pending(messages)) =
-            state.entities_by_type_and_remote_id.remove(&id)
-        else {
-            unreachable!()
-        };
-
-        state.entities_by_type_and_remote_id.insert(
-            id,
-            WeakSubscriber::Entity {
-                handle: model.downgrade().into(),
-            },
-        );
-        drop(state);
-        for message in messages {
-            self.client.handle_message(message, cx);
-        }
-        Subscription::Entity {
-            client: Arc::downgrade(&self.client),
-            id,
-        }
-    }
-}
-
-impl<T: 'static> Drop for PendingEntitySubscription<T> {
-    fn drop(&mut self) {
-        if !self.consumed {
-            let mut state = self.client.state.write();
-            if let Some(WeakSubscriber::Pending(messages)) = state
-                .entities_by_type_and_remote_id
-                .remove(&(TypeId::of::<T>(), self.remote_id))
-            {
-                for message in messages {
-                    log::info!("unhandled message {}", message.payload_type_name());
-                }
-            }
-        }
-    }
-}
-
-#[derive(Copy, Clone)]
-pub struct TelemetrySettings {
-    pub diagnostics: bool,
-    pub metrics: bool,
-}
-
-#[derive(Default, Clone, Serialize, Deserialize, JsonSchema)]
-pub struct TelemetrySettingsContent {
-    pub diagnostics: Option<bool>,
-    pub metrics: Option<bool>,
-}
-
-impl settings::Settings for TelemetrySettings {
-    const KEY: Option<&'static str> = Some("telemetry");
-
-    type FileContent = TelemetrySettingsContent;
-
-    fn load(
-        default_value: &Self::FileContent,
-        user_values: &[&Self::FileContent],
-        _: &mut AppContext,
-    ) -> Result<Self> {
-        Ok(Self {
-            diagnostics: user_values.first().and_then(|v| v.diagnostics).unwrap_or(
-                default_value
-                    .diagnostics
-                    .ok_or_else(Self::missing_default)?,
-            ),
-            metrics: user_values
-                .first()
-                .and_then(|v| v.metrics)
-                .unwrap_or(default_value.metrics.ok_or_else(Self::missing_default)?),
-        })
-    }
-}
-
-impl Client {
-    pub fn new(http: Arc<dyn HttpClient>, cx: &mut AppContext) -> Arc<Self> {
-        Arc::new(Self {
-            id: AtomicU64::new(0),
-            peer: Peer::new(0),
-            telemetry: Telemetry::new(http.clone(), cx),
-            http,
-            state: Default::default(),
-
-            #[cfg(any(test, feature = "test-support"))]
-            authenticate: Default::default(),
-            #[cfg(any(test, feature = "test-support"))]
-            establish_connection: Default::default(),
-        })
-    }
-
-    pub fn id(&self) -> u64 {
-        self.id.load(std::sync::atomic::Ordering::SeqCst)
-    }
-
-    pub fn http_client(&self) -> Arc<dyn HttpClient> {
-        self.http.clone()
-    }
-
-    pub fn set_id(&self, id: u64) -> &Self {
-        self.id.store(id, std::sync::atomic::Ordering::SeqCst);
-        self
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn teardown(&self) {
-        let mut state = self.state.write();
-        state._reconnect_task.take();
-        state.message_handlers.clear();
-        state.models_by_message_type.clear();
-        state.entities_by_type_and_remote_id.clear();
-        state.entity_id_extractors.clear();
-        self.peer.teardown();
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn override_authenticate<F>(&self, authenticate: F) -> &Self
-    where
-        F: 'static + Send + Sync + Fn(&AsyncAppContext) -> Task<Result<Credentials>>,
-    {
-        *self.authenticate.write() = Some(Box::new(authenticate));
-        self
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn override_establish_connection<F>(&self, connect: F) -> &Self
-    where
-        F: 'static
-            + Send
-            + Sync
-            + Fn(&Credentials, &AsyncAppContext) -> Task<Result<Connection, EstablishConnectionError>>,
-    {
-        *self.establish_connection.write() = Some(Box::new(connect));
-        self
-    }
-
-    pub fn user_id(&self) -> Option<u64> {
-        self.state
-            .read()
-            .credentials
-            .as_ref()
-            .map(|credentials| credentials.user_id)
-    }
-
-    pub fn peer_id(&self) -> Option<PeerId> {
-        if let Status::Connected { peer_id, .. } = &*self.status().borrow() {
-            Some(*peer_id)
-        } else {
-            None
-        }
-    }
-
-    pub fn status(&self) -> watch::Receiver<Status> {
-        self.state.read().status.1.clone()
-    }
-
-    fn set_status(self: &Arc<Self>, status: Status, cx: &AsyncAppContext) {
-        log::info!("set status on client {}: {:?}", self.id(), status);
-        let mut state = self.state.write();
-        *state.status.0.borrow_mut() = status;
-
-        match status {
-            Status::Connected { .. } => {
-                state._reconnect_task = None;
-            }
-            Status::ConnectionLost => {
-                let this = self.clone();
-                let reconnect_interval = state.reconnect_interval;
-                state._reconnect_task = Some(cx.spawn(move |cx| async move {
-                    #[cfg(any(test, feature = "test-support"))]
-                    let mut rng = StdRng::seed_from_u64(0);
-                    #[cfg(not(any(test, feature = "test-support")))]
-                    let mut rng = StdRng::from_entropy();
-
-                    let mut delay = INITIAL_RECONNECTION_DELAY;
-                    while let Err(error) = this.authenticate_and_connect(true, &cx).await {
-                        log::error!("failed to connect {}", error);
-                        if matches!(*this.status().borrow(), Status::ConnectionError) {
-                            this.set_status(
-                                Status::ReconnectionError {
-                                    next_reconnection: Instant::now() + delay,
-                                },
-                                &cx,
-                            );
-                            cx.background_executor().timer(delay).await;
-                            delay = delay
-                                .mul_f32(rng.gen_range(1.0..=2.0))
-                                .min(reconnect_interval);
-                        } else {
-                            break;
-                        }
-                    }
-                }));
-            }
-            Status::SignedOut | Status::UpgradeRequired => {
-                cx.update(|cx| self.telemetry.set_authenticated_user_info(None, false, cx))
-                    .log_err();
-                state._reconnect_task.take();
-            }
-            _ => {}
-        }
-    }
-
-    pub fn subscribe_to_entity<T>(
-        self: &Arc<Self>,
-        remote_id: u64,
-    ) -> Result<PendingEntitySubscription<T>>
-    where
-        T: 'static,
-    {
-        let id = (TypeId::of::<T>(), remote_id);
-
-        let mut state = self.state.write();
-        if state.entities_by_type_and_remote_id.contains_key(&id) {
-            return Err(anyhow!("already subscribed to entity"));
-        } else {
-            state
-                .entities_by_type_and_remote_id
-                .insert(id, WeakSubscriber::Pending(Default::default()));
-            Ok(PendingEntitySubscription {
-                client: self.clone(),
-                remote_id,
-                consumed: false,
-                _entity_type: PhantomData,
-            })
-        }
-    }
-
-    #[track_caller]
-    pub fn add_message_handler<M, E, H, F>(
-        self: &Arc<Self>,
-        entity: WeakModel<E>,
-        handler: H,
-    ) -> Subscription
-    where
-        M: EnvelopedMessage,
-        E: 'static,
-        H: 'static
-            + Sync
-            + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F
-            + Send
-            + Sync,
-        F: 'static + Future<Output = Result<()>>,
-    {
-        let message_type_id = TypeId::of::<M>();
-        let mut state = self.state.write();
-        state
-            .models_by_message_type
-            .insert(message_type_id, entity.into());
-
-        let prev_handler = state.message_handlers.insert(
-            message_type_id,
-            Arc::new(move |subscriber, envelope, client, cx| {
-                let subscriber = subscriber.downcast::<E>().unwrap();
-                let envelope = envelope.into_any().downcast::<TypedEnvelope<M>>().unwrap();
-                handler(subscriber, *envelope, client.clone(), cx).boxed_local()
-            }),
-        );
-        if prev_handler.is_some() {
-            let location = std::panic::Location::caller();
-            panic!(
-                "{}:{} registered handler for the same message {} twice",
-                location.file(),
-                location.line(),
-                std::any::type_name::<M>()
-            );
-        }
-
-        Subscription::Message {
-            client: Arc::downgrade(self),
-            id: message_type_id,
-        }
-    }
-
-    pub fn add_request_handler<M, E, H, F>(
-        self: &Arc<Self>,
-        model: WeakModel<E>,
-        handler: H,
-    ) -> Subscription
-    where
-        M: RequestMessage,
-        E: 'static,
-        H: 'static
-            + Sync
-            + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F
-            + Send
-            + Sync,
-        F: 'static + Future<Output = Result<M::Response>>,
-    {
-        self.add_message_handler(model, move |handle, envelope, this, cx| {
-            Self::respond_to_request(
-                envelope.receipt(),
-                handler(handle, envelope, this.clone(), cx),
-                this,
-            )
-        })
-    }
-
-    pub fn add_model_message_handler<M, E, H, F>(self: &Arc<Self>, handler: H)
-    where
-        M: EntityMessage,
-        E: 'static,
-        H: 'static + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F + Send + Sync,
-        F: 'static + Future<Output = Result<()>>,
-    {
-        self.add_entity_message_handler::<M, E, _, _>(move |subscriber, message, client, cx| {
-            handler(subscriber.downcast::<E>().unwrap(), message, client, cx)
-        })
-    }
-
-    fn add_entity_message_handler<M, E, H, F>(self: &Arc<Self>, handler: H)
-    where
-        M: EntityMessage,
-        E: 'static,
-        H: 'static + Fn(AnyModel, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F + Send + Sync,
-        F: 'static + Future<Output = Result<()>>,
-    {
-        let model_type_id = TypeId::of::<E>();
-        let message_type_id = TypeId::of::<M>();
-
-        let mut state = self.state.write();
-        state
-            .entity_types_by_message_type
-            .insert(message_type_id, model_type_id);
-        state
-            .entity_id_extractors
-            .entry(message_type_id)
-            .or_insert_with(|| {
-                |envelope| {
-                    envelope
-                        .as_any()
-                        .downcast_ref::<TypedEnvelope<M>>()
-                        .unwrap()
-                        .payload
-                        .remote_entity_id()
-                }
-            });
-        let prev_handler = state.message_handlers.insert(
-            message_type_id,
-            Arc::new(move |handle, envelope, client, cx| {
-                let envelope = envelope.into_any().downcast::<TypedEnvelope<M>>().unwrap();
-                handler(handle, *envelope, client.clone(), cx).boxed_local()
-            }),
-        );
-        if prev_handler.is_some() {
-            panic!("registered handler for the same message twice");
-        }
-    }
-
-    pub fn add_model_request_handler<M, E, H, F>(self: &Arc<Self>, handler: H)
-    where
-        M: EntityMessage + RequestMessage,
-        E: 'static,
-        H: 'static + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F + Send + Sync,
-        F: 'static + Future<Output = Result<M::Response>>,
-    {
-        self.add_model_message_handler(move |entity, envelope, client, cx| {
-            Self::respond_to_request::<M, _>(
-                envelope.receipt(),
-                handler(entity, envelope, client.clone(), cx),
-                client,
-            )
-        })
-    }
-
-    async fn respond_to_request<T: RequestMessage, F: Future<Output = Result<T::Response>>>(
-        receipt: Receipt<T>,
-        response: F,
-        client: Arc<Self>,
-    ) -> Result<()> {
-        match response.await {
-            Ok(response) => {
-                client.respond(receipt, response)?;
-                Ok(())
-            }
-            Err(error) => {
-                client.respond_with_error(
-                    receipt,
-                    proto::Error {
-                        message: format!("{:?}", error),
-                    },
-                )?;
-                Err(error)
-            }
-        }
-    }
-
-    pub fn has_keychain_credentials(&self, cx: &AsyncAppContext) -> bool {
-        read_credentials_from_keychain(cx).is_some()
-    }
-
-    #[async_recursion(?Send)]
-    pub async fn authenticate_and_connect(
-        self: &Arc<Self>,
-        try_keychain: bool,
-        cx: &AsyncAppContext,
-    ) -> anyhow::Result<()> {
-        let was_disconnected = match *self.status().borrow() {
-            Status::SignedOut => true,
-            Status::ConnectionError
-            | Status::ConnectionLost
-            | Status::Authenticating { .. }
-            | Status::Reauthenticating { .. }
-            | Status::ReconnectionError { .. } => false,
-            Status::Connected { .. } | Status::Connecting { .. } | Status::Reconnecting { .. } => {
-                return Ok(())
-            }
-            Status::UpgradeRequired => return Err(EstablishConnectionError::UpgradeRequired)?,
-        };
-
-        if was_disconnected {
-            self.set_status(Status::Authenticating, cx);
-        } else {
-            self.set_status(Status::Reauthenticating, cx)
-        }
-
-        let mut read_from_keychain = false;
-        let mut credentials = self.state.read().credentials.clone();
-        if credentials.is_none() && try_keychain {
-            credentials = read_credentials_from_keychain(cx);
-            read_from_keychain = credentials.is_some();
-        }
-        if credentials.is_none() {
-            let mut status_rx = self.status();
-            let _ = status_rx.next().await;
-            futures::select_biased! {
-                authenticate = self.authenticate(cx).fuse() => {
-                    match authenticate {
-                        Ok(creds) => credentials = Some(creds),
-                        Err(err) => {
-                            self.set_status(Status::ConnectionError, cx);
-                            return Err(err);
-                        }
-                    }
-                }
-                _ = status_rx.next().fuse() => {
-                    return Err(anyhow!("authentication canceled"));
-                }
-            }
-        }
-        let credentials = credentials.unwrap();
-        self.set_id(credentials.user_id);
-
-        if was_disconnected {
-            self.set_status(Status::Connecting, cx);
-        } else {
-            self.set_status(Status::Reconnecting, cx);
-        }
-
-        let mut timeout =
-            futures::FutureExt::fuse(cx.background_executor().timer(CONNECTION_TIMEOUT));
-        futures::select_biased! {
-            connection = self.establish_connection(&credentials, cx).fuse() => {
-                match connection {
-                    Ok(conn) => {
-                        self.state.write().credentials = Some(credentials.clone());
-                        if !read_from_keychain && IMPERSONATE_LOGIN.is_none() {
-                            write_credentials_to_keychain(credentials, cx).log_err();
-                        }
-
-                        futures::select_biased! {
-                            result = self.set_connection(conn, cx).fuse() => result,
-                            _ = timeout => {
-                                self.set_status(Status::ConnectionError, cx);
-                                Err(anyhow!("timed out waiting on hello message from server"))
-                            }
-                        }
-                    }
-                    Err(EstablishConnectionError::Unauthorized) => {
-                        self.state.write().credentials.take();
-                        if read_from_keychain {
-                            delete_credentials_from_keychain(cx).log_err();
-                            self.set_status(Status::SignedOut, cx);
-                            self.authenticate_and_connect(false, cx).await
-                        } else {
-                            self.set_status(Status::ConnectionError, cx);
-                            Err(EstablishConnectionError::Unauthorized)?
-                        }
-                    }
-                    Err(EstablishConnectionError::UpgradeRequired) => {
-                        self.set_status(Status::UpgradeRequired, cx);
-                        Err(EstablishConnectionError::UpgradeRequired)?
-                    }
-                    Err(error) => {
-                        self.set_status(Status::ConnectionError, cx);
-                        Err(error)?
-                    }
-                }
-            }
-            _ = &mut timeout => {
-                self.set_status(Status::ConnectionError, cx);
-                Err(anyhow!("timed out trying to establish connection"))
-            }
-        }
-    }
-
-    async fn set_connection(
-        self: &Arc<Self>,
-        conn: Connection,
-        cx: &AsyncAppContext,
-    ) -> Result<()> {
-        let executor = cx.background_executor();
-        log::info!("add connection to peer");
-        let (connection_id, handle_io, mut incoming) = self.peer.add_connection(conn, {
-            let executor = executor.clone();
-            move |duration| executor.timer(duration)
-        });
-        let handle_io = executor.spawn(handle_io);
-
-        let peer_id = async {
-            log::info!("waiting for server hello");
-            let message = incoming
-                .next()
-                .await
-                .ok_or_else(|| anyhow!("no hello message received"))?;
-            log::info!("got server hello");
-            let hello_message_type_name = message.payload_type_name().to_string();
-            let hello = message
-                .into_any()
-                .downcast::<TypedEnvelope<proto::Hello>>()
-                .map_err(|_| {
-                    anyhow!(
-                        "invalid hello message received: {:?}",
-                        hello_message_type_name
-                    )
-                })?;
-            let peer_id = hello
-                .payload
-                .peer_id
-                .ok_or_else(|| anyhow!("invalid peer id"))?;
-            Ok(peer_id)
-        };
-
-        let peer_id = match peer_id.await {
-            Ok(peer_id) => peer_id,
-            Err(error) => {
-                self.peer.disconnect(connection_id);
-                return Err(error);
-            }
-        };
-
-        log::info!(
-            "set status to connected (connection id: {:?}, peer id: {:?})",
-            connection_id,
-            peer_id
-        );
-        self.set_status(
-            Status::Connected {
-                peer_id,
-                connection_id,
-            },
-            cx,
-        );
-
-        cx.spawn({
-            let this = self.clone();
-            |cx| {
-                async move {
-                    while let Some(message) = incoming.next().await {
-                        this.handle_message(message, &cx);
-                        // Don't starve the main thread when receiving lots of messages at once.
-                        smol::future::yield_now().await;
-                    }
-                }
-            }
-        })
-        .detach();
-
-        cx.spawn({
-            let this = self.clone();
-            move |cx| async move {
-                match handle_io.await {
-                    Ok(()) => {
-                        if this.status().borrow().clone()
-                            == (Status::Connected {
-                                connection_id,
-                                peer_id,
-                            })
-                        {
-                            this.set_status(Status::SignedOut, &cx);
-                        }
-                    }
-                    Err(err) => {
-                        log::error!("connection error: {:?}", err);
-                        this.set_status(Status::ConnectionLost, &cx);
-                    }
-                }
-            }
-        })
-        .detach();
-
-        Ok(())
-    }
-
-    fn authenticate(self: &Arc<Self>, cx: &AsyncAppContext) -> Task<Result<Credentials>> {
-        #[cfg(any(test, feature = "test-support"))]
-        if let Some(callback) = self.authenticate.read().as_ref() {
-            return callback(cx);
-        }
-
-        self.authenticate_with_browser(cx)
-    }
-
-    fn establish_connection(
-        self: &Arc<Self>,
-        credentials: &Credentials,
-        cx: &AsyncAppContext,
-    ) -> Task<Result<Connection, EstablishConnectionError>> {
-        #[cfg(any(test, feature = "test-support"))]
-        if let Some(callback) = self.establish_connection.read().as_ref() {
-            return callback(credentials, cx);
-        }
-
-        self.establish_websocket_connection(credentials, cx)
-    }
-
-    async fn get_rpc_url(
-        http: Arc<dyn HttpClient>,
-        release_channel: Option<ReleaseChannel>,
-    ) -> Result<Url> {
-        let mut url = format!("{}/rpc", *ZED_SERVER_URL);
-        if let Some(preview_param) =
-            release_channel.and_then(|channel| channel.release_query_param())
-        {
-            url += "?";
-            url += preview_param;
-        }
-        let response = http.get(&url, Default::default(), false).await?;
-
-        // Normally, ZED_SERVER_URL is set to the URL of zed.dev website.
-        // The website's /rpc endpoint redirects to a collab server's /rpc endpoint,
-        // which requires authorization via an HTTP header.
-        //
-        // For testing purposes, ZED_SERVER_URL can also set to the direct URL of
-        // of a collab server. In that case, a request to the /rpc endpoint will
-        // return an 'unauthorized' response.
-        let collab_url = if response.status().is_redirection() {
-            response
-                .headers()
-                .get("Location")
-                .ok_or_else(|| anyhow!("missing location header in /rpc response"))?
-                .to_str()
-                .map_err(EstablishConnectionError::other)?
-                .to_string()
-        } else if response.status() == StatusCode::UNAUTHORIZED {
-            url
-        } else {
-            Err(anyhow!(
-                "unexpected /rpc response status {}",
-                response.status()
-            ))?
-        };
-
-        Url::parse(&collab_url).context("invalid rpc url")
-    }
-
-    fn establish_websocket_connection(
-        self: &Arc<Self>,
-        credentials: &Credentials,
-        cx: &AsyncAppContext,
-    ) -> Task<Result<Connection, EstablishConnectionError>> {
-        let release_channel = cx.try_read_global(|channel: &ReleaseChannel, _| *channel);
-
-        let request = Request::builder()
-            .header(
-                "Authorization",
-                format!("{} {}", credentials.user_id, credentials.access_token),
-            )
-            .header("x-zed-protocol-version", rpc::PROTOCOL_VERSION);
-
-        let http = self.http.clone();
-        cx.background_executor().spawn(async move {
-            let mut rpc_url = Self::get_rpc_url(http, release_channel).await?;
-            let rpc_host = rpc_url
-                .host_str()
-                .zip(rpc_url.port_or_known_default())
-                .ok_or_else(|| anyhow!("missing host in rpc url"))?;
-            let stream = smol::net::TcpStream::connect(rpc_host).await?;
-
-            log::info!("connected to rpc endpoint {}", rpc_url);
-
-            match rpc_url.scheme() {
-                "https" => {
-                    rpc_url.set_scheme("wss").unwrap();
-                    let request = request.uri(rpc_url.as_str()).body(())?;
-                    let (stream, _) =
-                        async_tungstenite::async_tls::client_async_tls(request, stream).await?;
-                    Ok(Connection::new(
-                        stream
-                            .map_err(|error| anyhow!(error))
-                            .sink_map_err(|error| anyhow!(error)),
-                    ))
-                }
-                "http" => {
-                    rpc_url.set_scheme("ws").unwrap();
-                    let request = request.uri(rpc_url.as_str()).body(())?;
-                    let (stream, _) = async_tungstenite::client_async(request, stream).await?;
-                    Ok(Connection::new(
-                        stream
-                            .map_err(|error| anyhow!(error))
-                            .sink_map_err(|error| anyhow!(error)),
-                    ))
-                }
-                _ => Err(anyhow!("invalid rpc url: {}", rpc_url))?,
-            }
-        })
-    }
-
-    pub fn authenticate_with_browser(
-        self: &Arc<Self>,
-        cx: &AsyncAppContext,
-    ) -> Task<Result<Credentials>> {
-        let http = self.http.clone();
-        cx.spawn(|cx| async move {
-            let background = cx.background_executor().clone();
-
-            let (open_url_tx, open_url_rx) = oneshot::channel::<String>();
-            cx.update(|cx| {
-                cx.spawn(move |cx| async move {
-                    let url = open_url_rx.await?;
-                    cx.update(|cx| cx.open_url(&url))
-                })
-                .detach_and_log_err(cx);
-            })
-            .log_err();
-
-            let credentials = background
-                .clone()
-                .spawn(async move {
-                    // Generate a pair of asymmetric encryption keys. The public key will be used by the
-                    // zed server to encrypt the user's access token, so that it can'be intercepted by
-                    // any other app running on the user's device.
-                    let (public_key, private_key) =
-                        rpc::auth::keypair().expect("failed to generate keypair for auth");
-                    let public_key_string = String::try_from(public_key)
-                        .expect("failed to serialize public key for auth");
-
-                    if let Some((login, token)) =
-                        IMPERSONATE_LOGIN.as_ref().zip(ADMIN_API_TOKEN.as_ref())
-                    {
-                        return Self::authenticate_as_admin(http, login.clone(), token.clone())
-                            .await;
-                    }
-
-                    // Start an HTTP server to receive the redirect from Zed's sign-in page.
-                    let server =
-                        tiny_http::Server::http("127.0.0.1:0").expect("failed to find open port");
-                    let port = server.server_addr().port();
-
-                    // Open the Zed sign-in page in the user's browser, with query parameters that indicate
-                    // that the user is signing in from a Zed app running on the same device.
-                    let mut url = format!(
-                        "{}/native_app_signin?native_app_port={}&native_app_public_key={}",
-                        *ZED_SERVER_URL, port, public_key_string
-                    );
-
-                    if let Some(impersonate_login) = IMPERSONATE_LOGIN.as_ref() {
-                        log::info!("impersonating user @{}", impersonate_login);
-                        write!(&mut url, "&impersonate={}", impersonate_login).unwrap();
-                    }
-
-                    open_url_tx.send(url).log_err();
-
-                    // Receive the HTTP request from the user's browser. Retrieve the user id and encrypted
-                    // access token from the query params.
-                    //
-                    // TODO - Avoid ever starting more than one HTTP server. Maybe switch to using a
-                    // custom URL scheme instead of this local HTTP server.
-                    let (user_id, access_token) = background
-                        .spawn(async move {
-                            for _ in 0..100 {
-                                if let Some(req) = server.recv_timeout(Duration::from_secs(1))? {
-                                    let path = req.url();
-                                    let mut user_id = None;
-                                    let mut access_token = None;
-                                    let url = Url::parse(&format!("http://example.com{}", path))
-                                        .context("failed to parse login notification url")?;
-                                    for (key, value) in url.query_pairs() {
-                                        if key == "access_token" {
-                                            access_token = Some(value.to_string());
-                                        } else if key == "user_id" {
-                                            user_id = Some(value.to_string());
-                                        }
-                                    }
-
-                                    let post_auth_url =
-                                        format!("{}/native_app_signin_succeeded", *ZED_SERVER_URL);
-                                    req.respond(
-                                        tiny_http::Response::empty(302).with_header(
-                                            tiny_http::Header::from_bytes(
-                                                &b"Location"[..],
-                                                post_auth_url.as_bytes(),
-                                            )
-                                            .unwrap(),
-                                        ),
-                                    )
-                                    .context("failed to respond to login http request")?;
-                                    return Ok((
-                                        user_id
-                                            .ok_or_else(|| anyhow!("missing user_id parameter"))?,
-                                        access_token.ok_or_else(|| {
-                                            anyhow!("missing access_token parameter")
-                                        })?,
-                                    ));
-                                }
-                            }
-
-                            Err(anyhow!("didn't receive login redirect"))
-                        })
-                        .await?;
-
-                    let access_token = private_key
-                        .decrypt_string(&access_token)
-                        .context("failed to decrypt access token")?;
-
-                    Ok(Credentials {
-                        user_id: user_id.parse()?,
-                        access_token,
-                    })
-                })
-                .await?;
-
-            cx.update(|cx| cx.activate(true))?;
-            Ok(credentials)
-        })
-    }
-
-    async fn authenticate_as_admin(
-        http: Arc<dyn HttpClient>,
-        login: String,
-        mut api_token: String,
-    ) -> Result<Credentials> {
-        #[derive(Deserialize)]
-        struct AuthenticatedUserResponse {
-            user: User,
-        }
-
-        #[derive(Deserialize)]
-        struct User {
-            id: u64,
-        }
-
-        // Use the collab server's admin API to retrieve the id
-        // of the impersonated user.
-        let mut url = Self::get_rpc_url(http.clone(), None).await?;
-        url.set_path("/user");
-        url.set_query(Some(&format!("github_login={login}")));
-        let request = Request::get(url.as_str())
-            .header("Authorization", format!("token {api_token}"))
-            .body("".into())?;
-
-        let mut response = http.send(request).await?;
-        let mut body = String::new();
-        response.body_mut().read_to_string(&mut body).await?;
-        if !response.status().is_success() {
-            Err(anyhow!(
-                "admin user request failed {} - {}",
-                response.status().as_u16(),
-                body,
-            ))?;
-        }
-        let response: AuthenticatedUserResponse = serde_json::from_str(&body)?;
-
-        // Use the admin API token to authenticate as the impersonated user.
-        api_token.insert_str(0, "ADMIN_TOKEN:");
-        Ok(Credentials {
-            user_id: response.user.id,
-            access_token: api_token,
-        })
-    }
-
-    pub fn disconnect(self: &Arc<Self>, cx: &AsyncAppContext) {
-        self.peer.teardown();
-        self.set_status(Status::SignedOut, cx);
-    }
-
-    pub fn reconnect(self: &Arc<Self>, cx: &AsyncAppContext) {
-        self.peer.teardown();
-        self.set_status(Status::ConnectionLost, cx);
-    }
-
-    fn connection_id(&self) -> Result<ConnectionId> {
-        if let Status::Connected { connection_id, .. } = *self.status().borrow() {
-            Ok(connection_id)
-        } else {
-            Err(anyhow!("not connected"))
-        }
-    }
-
-    pub fn send<T: EnvelopedMessage>(&self, message: T) -> Result<()> {
-        log::debug!("rpc send. client_id:{}, name:{}", self.id(), T::NAME);
-        self.peer.send(self.connection_id()?, message)
-    }
-
-    pub fn request<T: RequestMessage>(
-        &self,
-        request: T,
-    ) -> impl Future<Output = Result<T::Response>> {
-        self.request_envelope(request)
-            .map_ok(|envelope| envelope.payload)
-    }
-
-    pub fn request_envelope<T: RequestMessage>(
-        &self,
-        request: T,
-    ) -> impl Future<Output = Result<TypedEnvelope<T::Response>>> {
-        let client_id = self.id();
-        log::debug!(
-            "rpc request start. client_id:{}. name:{}",
-            client_id,
-            T::NAME
-        );
-        let response = self
-            .connection_id()
-            .map(|conn_id| self.peer.request_envelope(conn_id, request));
-        async move {
-            let response = response?.await;
-            log::debug!(
-                "rpc request finish. client_id:{}. name:{}",
-                client_id,
-                T::NAME
-            );
-            response
-        }
-    }
-
-    fn respond<T: RequestMessage>(&self, receipt: Receipt<T>, response: T::Response) -> Result<()> {
-        log::debug!("rpc respond. client_id:{}. name:{}", self.id(), T::NAME);
-        self.peer.respond(receipt, response)
-    }
-
-    fn respond_with_error<T: RequestMessage>(
-        &self,
-        receipt: Receipt<T>,
-        error: proto::Error,
-    ) -> Result<()> {
-        log::debug!("rpc respond. client_id:{}. name:{}", self.id(), T::NAME);
-        self.peer.respond_with_error(receipt, error)
-    }
-
-    fn handle_message(
-        self: &Arc<Client>,
-        message: Box<dyn AnyTypedEnvelope>,
-        cx: &AsyncAppContext,
-    ) {
-        let mut state = self.state.write();
-        let type_name = message.payload_type_name();
-        let payload_type_id = message.payload_type_id();
-        let sender_id = message.original_sender_id();
-
-        let mut subscriber = None;
-
-        if let Some(handle) = state
-            .models_by_message_type
-            .get(&payload_type_id)
-            .and_then(|handle| handle.upgrade())
-        {
-            subscriber = Some(handle);
-        } else if let Some((extract_entity_id, entity_type_id)) =
-            state.entity_id_extractors.get(&payload_type_id).zip(
-                state
-                    .entity_types_by_message_type
-                    .get(&payload_type_id)
-                    .copied(),
-            )
-        {
-            let entity_id = (extract_entity_id)(message.as_ref());
-
-            match state
-                .entities_by_type_and_remote_id
-                .get_mut(&(entity_type_id, entity_id))
-            {
-                Some(WeakSubscriber::Pending(pending)) => {
-                    pending.push(message);
-                    return;
-                }
-                Some(weak_subscriber @ _) => match weak_subscriber {
-                    WeakSubscriber::Entity { handle } => {
-                        subscriber = handle.upgrade();
-                    }
-
-                    WeakSubscriber::Pending(_) => {}
-                },
-                _ => {}
-            }
-        }
-
-        let subscriber = if let Some(subscriber) = subscriber {
-            subscriber
-        } else {
-            log::info!("unhandled message {}", type_name);
-            self.peer.respond_with_unhandled_message(message).log_err();
-            return;
-        };
-
-        let handler = state.message_handlers.get(&payload_type_id).cloned();
-        // Dropping the state prevents deadlocks if the handler interacts with rpc::Client.
-        // It also ensures we don't hold the lock while yielding back to the executor, as
-        // that might cause the executor thread driving this future to block indefinitely.
-        drop(state);
-
-        if let Some(handler) = handler {
-            let future = handler(subscriber, message, &self, cx.clone());
-            let client_id = self.id();
-            log::debug!(
-                "rpc message received. client_id:{}, sender_id:{:?}, type:{}",
-                client_id,
-                sender_id,
-                type_name
-            );
-            cx.spawn(move |_| async move {
-                    match future.await {
-                        Ok(()) => {
-                            log::debug!(
-                                "rpc message handled. client_id:{}, sender_id:{:?}, type:{}",
-                                client_id,
-                                sender_id,
-                                type_name
-                            );
-                        }
-                        Err(error) => {
-                            log::error!(
-                                "error handling message. client_id:{}, sender_id:{:?}, type:{}, error:{:?}",
-                                client_id,
-                                sender_id,
-                                type_name,
-                                error
-                            );
-                        }
-                    }
-                })
-                .detach();
-        } else {
-            log::info!("unhandled message {}", type_name);
-            self.peer.respond_with_unhandled_message(message).log_err();
-        }
-    }
-
-    pub fn telemetry(&self) -> &Arc<Telemetry> {
-        &self.telemetry
-    }
-}
-
-fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option<Credentials> {
-    if IMPERSONATE_LOGIN.is_some() {
-        return None;
-    }
-
-    let (user_id, access_token) = cx
-        .update(|cx| cx.read_credentials(&ZED_SERVER_URL).log_err().flatten())
-        .ok()??;
-
-    Some(Credentials {
-        user_id: user_id.parse().ok()?,
-        access_token: String::from_utf8(access_token).ok()?,
-    })
-}
-
-async fn write_credentials_to_keychain(
-    credentials: Credentials,
-    cx: &AsyncAppContext,
-) -> Result<()> {
-    cx.update(move |cx| {
-        cx.write_credentials(
-            &ZED_SERVER_URL,
-            &credentials.user_id.to_string(),
-            credentials.access_token.as_bytes(),
-        )
-    })?
-}
-
-async fn delete_credentials_from_keychain(cx: &AsyncAppContext) -> Result<()> {
-    cx.update(move |cx| cx.delete_credentials(&ZED_SERVER_URL))?
-}
-
-const WORKTREE_URL_PREFIX: &str = "zed://worktrees/";
-
-pub fn encode_worktree_url(id: u64, access_token: &str) -> String {
-    format!("{}{}/{}", WORKTREE_URL_PREFIX, id, access_token)
-}
-
-pub fn decode_worktree_url(url: &str) -> Option<(u64, String)> {
-    let path = url.trim().strip_prefix(WORKTREE_URL_PREFIX)?;
-    let mut parts = path.split('/');
-    let id = parts.next()?.parse::<u64>().ok()?;
-    let access_token = parts.next()?;
-    if access_token.is_empty() {
-        return None;
-    }
-    Some((id, access_token.to_string()))
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::test::FakeServer;
-
-    use gpui::{BackgroundExecutor, Context, TestAppContext};
-    use parking_lot::Mutex;
-    use std::future;
-    use util::http::FakeHttpClient;
-
-    #[gpui::test(iterations = 10)]
-    async fn test_reconnection(cx: &mut TestAppContext) {
-        let user_id = 5;
-        let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
-        let server = FakeServer::for_client(user_id, &client, cx).await;
-        let mut status = client.status();
-        assert!(matches!(
-            status.next().await,
-            Some(Status::Connected { .. })
-        ));
-        assert_eq!(server.auth_count(), 1);
-
-        server.forbid_connections();
-        server.disconnect();
-        while !matches!(status.next().await, Some(Status::ReconnectionError { .. })) {}
-
-        server.allow_connections();
-        cx.executor().advance_clock(Duration::from_secs(10));
-        while !matches!(status.next().await, Some(Status::Connected { .. })) {}
-        assert_eq!(server.auth_count(), 1); // Client reused the cached credentials when reconnecting
-
-        server.forbid_connections();
-        server.disconnect();
-        while !matches!(status.next().await, Some(Status::ReconnectionError { .. })) {}
-
-        // Clear cached credentials after authentication fails
-        server.roll_access_token();
-        server.allow_connections();
-        cx.executor().run_until_parked();
-        cx.executor().advance_clock(Duration::from_secs(10));
-        while !matches!(status.next().await, Some(Status::Connected { .. })) {}
-        assert_eq!(server.auth_count(), 2); // Client re-authenticated due to an invalid token
-    }
-
-    #[gpui::test(iterations = 10)]
-    async fn test_connection_timeout(executor: BackgroundExecutor, cx: &mut TestAppContext) {
-        let user_id = 5;
-        let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
-        let mut status = client.status();
-
-        // Time out when client tries to connect.
-        client.override_authenticate(move |cx| {
-            cx.background_executor().spawn(async move {
-                Ok(Credentials {
-                    user_id,
-                    access_token: "token".into(),
-                })
-            })
-        });
-        client.override_establish_connection(|_, cx| {
-            cx.background_executor().spawn(async move {
-                future::pending::<()>().await;
-                unreachable!()
-            })
-        });
-        let auth_and_connect = cx.spawn({
-            let client = client.clone();
-            |cx| async move { client.authenticate_and_connect(false, &cx).await }
-        });
-        executor.run_until_parked();
-        assert!(matches!(status.next().await, Some(Status::Connecting)));
-
-        executor.advance_clock(CONNECTION_TIMEOUT);
-        assert!(matches!(
-            status.next().await,
-            Some(Status::ConnectionError { .. })
-        ));
-        auth_and_connect.await.unwrap_err();
-
-        // Allow the connection to be established.
-        let server = FakeServer::for_client(user_id, &client, cx).await;
-        assert!(matches!(
-            status.next().await,
-            Some(Status::Connected { .. })
-        ));
-
-        // Disconnect client.
-        server.forbid_connections();
-        server.disconnect();
-        while !matches!(status.next().await, Some(Status::ReconnectionError { .. })) {}
-
-        // Time out when re-establishing the connection.
-        server.allow_connections();
-        client.override_establish_connection(|_, cx| {
-            cx.background_executor().spawn(async move {
-                future::pending::<()>().await;
-                unreachable!()
-            })
-        });
-        executor.advance_clock(2 * INITIAL_RECONNECTION_DELAY);
-        assert!(matches!(
-            status.next().await,
-            Some(Status::Reconnecting { .. })
-        ));
-
-        executor.advance_clock(CONNECTION_TIMEOUT);
-        assert!(matches!(
-            status.next().await,
-            Some(Status::ReconnectionError { .. })
-        ));
-    }
-
-    #[gpui::test(iterations = 10)]
-    async fn test_authenticating_more_than_once(
-        cx: &mut TestAppContext,
-        executor: BackgroundExecutor,
-    ) {
-        let auth_count = Arc::new(Mutex::new(0));
-        let dropped_auth_count = Arc::new(Mutex::new(0));
-        let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
-        client.override_authenticate({
-            let auth_count = auth_count.clone();
-            let dropped_auth_count = dropped_auth_count.clone();
-            move |cx| {
-                let auth_count = auth_count.clone();
-                let dropped_auth_count = dropped_auth_count.clone();
-                cx.background_executor().spawn(async move {
-                    *auth_count.lock() += 1;
-                    let _drop = util::defer(move || *dropped_auth_count.lock() += 1);
-                    future::pending::<()>().await;
-                    unreachable!()
-                })
-            }
-        });
-
-        let _authenticate = cx.spawn({
-            let client = client.clone();
-            move |cx| async move { client.authenticate_and_connect(false, &cx).await }
-        });
-        executor.run_until_parked();
-        assert_eq!(*auth_count.lock(), 1);
-        assert_eq!(*dropped_auth_count.lock(), 0);
-
-        let _authenticate = cx.spawn({
-            let client = client.clone();
-            |cx| async move { client.authenticate_and_connect(false, &cx).await }
-        });
-        executor.run_until_parked();
-        assert_eq!(*auth_count.lock(), 2);
-        assert_eq!(*dropped_auth_count.lock(), 1);
-    }
-
-    #[test]
-    fn test_encode_and_decode_worktree_url() {
-        let url = encode_worktree_url(5, "deadbeef");
-        assert_eq!(decode_worktree_url(&url), Some((5, "deadbeef".to_string())));
-        assert_eq!(
-            decode_worktree_url(&format!("\n {}\t", url)),
-            Some((5, "deadbeef".to_string()))
-        );
-        assert_eq!(decode_worktree_url("not://the-right-format"), None);
-    }
-
-    #[gpui::test]
-    async fn test_subscribing_to_entity(cx: &mut TestAppContext) {
-        let user_id = 5;
-        let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
-        let server = FakeServer::for_client(user_id, &client, cx).await;
-
-        let (done_tx1, mut done_rx1) = smol::channel::unbounded();
-        let (done_tx2, mut done_rx2) = smol::channel::unbounded();
-        client.add_model_message_handler(
-            move |model: Model<TestModel>, _: TypedEnvelope<proto::JoinProject>, _, mut cx| {
-                match model.update(&mut cx, |model, _| model.id).unwrap() {
-                    1 => done_tx1.try_send(()).unwrap(),
-                    2 => done_tx2.try_send(()).unwrap(),
-                    _ => unreachable!(),
-                }
-                async { Ok(()) }
-            },
-        );
-        let model1 = cx.new_model(|_| TestModel {
-            id: 1,
-            subscription: None,
-        });
-        let model2 = cx.new_model(|_| TestModel {
-            id: 2,
-            subscription: None,
-        });
-        let model3 = cx.new_model(|_| TestModel {
-            id: 3,
-            subscription: None,
-        });
-
-        let _subscription1 = client
-            .subscribe_to_entity(1)
-            .unwrap()
-            .set_model(&model1, &mut cx.to_async());
-        let _subscription2 = client
-            .subscribe_to_entity(2)
-            .unwrap()
-            .set_model(&model2, &mut cx.to_async());
-        // Ensure dropping a subscription for the same entity type still allows receiving of
-        // messages for other entity IDs of the same type.
-        let subscription3 = client
-            .subscribe_to_entity(3)
-            .unwrap()
-            .set_model(&model3, &mut cx.to_async());
-        drop(subscription3);
-
-        server.send(proto::JoinProject { project_id: 1 });
-        server.send(proto::JoinProject { project_id: 2 });
-        done_rx1.next().await.unwrap();
-        done_rx2.next().await.unwrap();
-    }
-
-    #[gpui::test]
-    async fn test_subscribing_after_dropping_subscription(cx: &mut TestAppContext) {
-        let user_id = 5;
-        let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
-        let server = FakeServer::for_client(user_id, &client, cx).await;
-
-        let model = cx.new_model(|_| TestModel::default());
-        let (done_tx1, _done_rx1) = smol::channel::unbounded();
-        let (done_tx2, mut done_rx2) = smol::channel::unbounded();
-        let subscription1 = client.add_message_handler(
-            model.downgrade(),
-            move |_, _: TypedEnvelope<proto::Ping>, _, _| {
-                done_tx1.try_send(()).unwrap();
-                async { Ok(()) }
-            },
-        );
-        drop(subscription1);
-        let _subscription2 = client.add_message_handler(
-            model.downgrade(),
-            move |_, _: TypedEnvelope<proto::Ping>, _, _| {
-                done_tx2.try_send(()).unwrap();
-                async { Ok(()) }
-            },
-        );
-        server.send(proto::Ping {});
-        done_rx2.next().await.unwrap();
-    }
-
-    #[gpui::test]
-    async fn test_dropping_subscription_in_handler(cx: &mut TestAppContext) {
-        let user_id = 5;
-        let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
-        let server = FakeServer::for_client(user_id, &client, cx).await;
-
-        let model = cx.new_model(|_| TestModel::default());
-        let (done_tx, mut done_rx) = smol::channel::unbounded();
-        let subscription = client.add_message_handler(
-            model.clone().downgrade(),
-            move |model: Model<TestModel>, _: TypedEnvelope<proto::Ping>, _, mut cx| {
-                model
-                    .update(&mut cx, |model, _| model.subscription.take())
-                    .unwrap();
-                done_tx.try_send(()).unwrap();
-                async { Ok(()) }
-            },
-        );
-        model.update(cx, |model, _| {
-            model.subscription = Some(subscription);
-        });
-        server.send(proto::Ping {});
-        done_rx.next().await.unwrap();
-    }
-
-    #[derive(Default)]
-    struct TestModel {
-        id: usize,
-        subscription: Option<Subscription>,
-    }
-}

crates/client2/src/telemetry.rs 🔗

@@ -1,515 +0,0 @@
-use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};
-use chrono::{DateTime, Utc};
-use futures::Future;
-use gpui::{serde_json, AppContext, AppMetadata, BackgroundExecutor, Task};
-use lazy_static::lazy_static;
-use parking_lot::Mutex;
-use serde::Serialize;
-use settings::Settings;
-use std::{env, io::Write, mem, path::PathBuf, sync::Arc, time::Duration};
-use sysinfo::{
-    CpuRefreshKind, Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt,
-};
-use tempfile::NamedTempFile;
-use util::http::HttpClient;
-use util::{channel::ReleaseChannel, TryFutureExt};
-
-pub struct Telemetry {
-    http_client: Arc<dyn HttpClient>,
-    executor: BackgroundExecutor,
-    state: Mutex<TelemetryState>,
-}
-
-struct TelemetryState {
-    metrics_id: Option<Arc<str>>,      // Per logged-in user
-    installation_id: Option<Arc<str>>, // Per app installation (different for dev, nightly, preview, and stable)
-    session_id: Option<Arc<str>>,      // Per app launch
-    release_channel: Option<&'static str>,
-    app_metadata: AppMetadata,
-    architecture: &'static str,
-    clickhouse_events_queue: Vec<ClickhouseEventWrapper>,
-    flush_clickhouse_events_task: Option<Task<()>>,
-    log_file: Option<NamedTempFile>,
-    is_staff: Option<bool>,
-    first_event_datetime: Option<DateTime<Utc>>,
-}
-
-const CLICKHOUSE_EVENTS_URL_PATH: &'static str = "/api/events";
-
-lazy_static! {
-    static ref CLICKHOUSE_EVENTS_URL: String =
-        format!("{}{}", *ZED_SERVER_URL, CLICKHOUSE_EVENTS_URL_PATH);
-}
-
-#[derive(Serialize, Debug)]
-struct ClickhouseEventRequestBody {
-    token: &'static str,
-    installation_id: Option<Arc<str>>,
-    session_id: Option<Arc<str>>,
-    is_staff: Option<bool>,
-    app_version: Option<String>,
-    os_name: &'static str,
-    os_version: Option<String>,
-    architecture: &'static str,
-    release_channel: Option<&'static str>,
-    events: Vec<ClickhouseEventWrapper>,
-}
-
-#[derive(Serialize, Debug)]
-struct ClickhouseEventWrapper {
-    signed_in: bool,
-    #[serde(flatten)]
-    event: ClickhouseEvent,
-}
-
-#[derive(Serialize, Debug)]
-#[serde(rename_all = "snake_case")]
-pub enum AssistantKind {
-    Panel,
-    Inline,
-}
-
-#[derive(Serialize, Debug)]
-#[serde(tag = "type")]
-pub enum ClickhouseEvent {
-    Editor {
-        operation: &'static str,
-        file_extension: Option<String>,
-        vim_mode: bool,
-        copilot_enabled: bool,
-        copilot_enabled_for_language: bool,
-        milliseconds_since_first_event: i64,
-    },
-    Copilot {
-        suggestion_id: Option<String>,
-        suggestion_accepted: bool,
-        file_extension: Option<String>,
-        milliseconds_since_first_event: i64,
-    },
-    Call {
-        operation: &'static str,
-        room_id: Option<u64>,
-        channel_id: Option<u64>,
-        milliseconds_since_first_event: i64,
-    },
-    Assistant {
-        conversation_id: Option<String>,
-        kind: AssistantKind,
-        model: &'static str,
-        milliseconds_since_first_event: i64,
-    },
-    Cpu {
-        usage_as_percentage: f32,
-        core_count: u32,
-        milliseconds_since_first_event: i64,
-    },
-    Memory {
-        memory_in_bytes: u64,
-        virtual_memory_in_bytes: u64,
-        milliseconds_since_first_event: i64,
-    },
-    App {
-        operation: &'static str,
-        milliseconds_since_first_event: i64,
-    },
-    Setting {
-        setting: &'static str,
-        value: String,
-        milliseconds_since_first_event: i64,
-    },
-}
-
-#[cfg(debug_assertions)]
-const MAX_QUEUE_LEN: usize = 1;
-
-#[cfg(not(debug_assertions))]
-const MAX_QUEUE_LEN: usize = 50;
-
-#[cfg(debug_assertions)]
-const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(1);
-
-#[cfg(not(debug_assertions))]
-const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(60 * 5);
-
-impl Telemetry {
-    pub fn new(client: Arc<dyn HttpClient>, cx: &mut AppContext) -> Arc<Self> {
-        let release_channel = if cx.has_global::<ReleaseChannel>() {
-            Some(cx.global::<ReleaseChannel>().display_name())
-        } else {
-            None
-        };
-
-        // TODO: Replace all hardware stuff with nested SystemSpecs json
-        let this = Arc::new(Self {
-            http_client: client,
-            executor: cx.background_executor().clone(),
-            state: Mutex::new(TelemetryState {
-                app_metadata: cx.app_metadata(),
-                architecture: env::consts::ARCH,
-                release_channel,
-                installation_id: None,
-                metrics_id: None,
-                session_id: None,
-                clickhouse_events_queue: Default::default(),
-                flush_clickhouse_events_task: Default::default(),
-                log_file: None,
-                is_staff: None,
-                first_event_datetime: None,
-            }),
-        });
-
-        // We should only ever have one instance of Telemetry, leak the subscription to keep it alive
-        // rather than store in TelemetryState, complicating spawn as subscriptions are not Send
-        std::mem::forget(cx.on_app_quit({
-            let this = this.clone();
-            move |cx| this.shutdown_telemetry(cx)
-        }));
-
-        this
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    fn shutdown_telemetry(self: &Arc<Self>, _: &mut AppContext) -> impl Future<Output = ()> {
-        Task::ready(())
-    }
-
-    // Skip calling this function in tests.
-    // TestAppContext ends up calling this function on shutdown and it panics when trying to find the TelemetrySettings
-    #[cfg(not(any(test, feature = "test-support")))]
-    fn shutdown_telemetry(self: &Arc<Self>, cx: &mut AppContext) -> impl Future<Output = ()> {
-        let telemetry_settings = TelemetrySettings::get_global(cx).clone();
-        self.report_app_event(telemetry_settings, "close", true);
-        Task::ready(())
-    }
-
-    pub fn log_file_path(&self) -> Option<PathBuf> {
-        Some(self.state.lock().log_file.as_ref()?.path().to_path_buf())
-    }
-
-    pub fn start(
-        self: &Arc<Self>,
-        installation_id: Option<String>,
-        session_id: String,
-        cx: &mut AppContext,
-    ) {
-        let mut state = self.state.lock();
-        state.installation_id = installation_id.map(|id| id.into());
-        state.session_id = Some(session_id.into());
-        drop(state);
-
-        let this = self.clone();
-        cx.spawn(|cx| async move {
-            // Avoiding calling `System::new_all()`, as there have been crashes related to it
-            let refresh_kind = RefreshKind::new()
-                .with_memory() // For memory usage
-                .with_processes(ProcessRefreshKind::everything()) // For process usage
-                .with_cpu(CpuRefreshKind::everything()); // For core count
-
-            let mut system = System::new_with_specifics(refresh_kind);
-
-            // Avoiding calling `refresh_all()`, just update what we need
-            system.refresh_specifics(refresh_kind);
-
-            // Waiting some amount of time before the first query is important to get a reasonable value
-            // https://docs.rs/sysinfo/0.29.10/sysinfo/trait.ProcessExt.html#tymethod.cpu_usage
-            const DURATION_BETWEEN_SYSTEM_EVENTS: Duration = Duration::from_secs(4 * 60);
-
-            loop {
-                smol::Timer::after(DURATION_BETWEEN_SYSTEM_EVENTS).await;
-
-                system.refresh_specifics(refresh_kind);
-
-                let current_process = Pid::from_u32(std::process::id());
-                let Some(process) = system.processes().get(&current_process) else {
-                    let process = current_process;
-                    log::error!("Failed to find own process {process:?} in system process table");
-                    // TODO: Fire an error telemetry event
-                    return;
-                };
-
-                let telemetry_settings = if let Ok(telemetry_settings) =
-                    cx.update(|cx| *TelemetrySettings::get_global(cx))
-                {
-                    telemetry_settings
-                } else {
-                    break;
-                };
-
-                this.report_memory_event(
-                    telemetry_settings,
-                    process.memory(),
-                    process.virtual_memory(),
-                );
-                this.report_cpu_event(
-                    telemetry_settings,
-                    process.cpu_usage(),
-                    system.cpus().len() as u32,
-                );
-            }
-        })
-        .detach();
-    }
-
-    pub fn set_authenticated_user_info(
-        self: &Arc<Self>,
-        metrics_id: Option<String>,
-        is_staff: bool,
-        cx: &AppContext,
-    ) {
-        if !TelemetrySettings::get_global(cx).metrics {
-            return;
-        }
-
-        let mut state = self.state.lock();
-        let metrics_id: Option<Arc<str>> = metrics_id.map(|id| id.into());
-        state.metrics_id = metrics_id.clone();
-        state.is_staff = Some(is_staff);
-        drop(state);
-    }
-
-    pub fn report_editor_event(
-        self: &Arc<Self>,
-        telemetry_settings: TelemetrySettings,
-        file_extension: Option<String>,
-        vim_mode: bool,
-        operation: &'static str,
-        copilot_enabled: bool,
-        copilot_enabled_for_language: bool,
-    ) {
-        let event = ClickhouseEvent::Editor {
-            file_extension,
-            vim_mode,
-            operation,
-            copilot_enabled,
-            copilot_enabled_for_language,
-            milliseconds_since_first_event: self.milliseconds_since_first_event(),
-        };
-
-        self.report_clickhouse_event(event, telemetry_settings, false)
-    }
-
-    pub fn report_copilot_event(
-        self: &Arc<Self>,
-        telemetry_settings: TelemetrySettings,
-        suggestion_id: Option<String>,
-        suggestion_accepted: bool,
-        file_extension: Option<String>,
-    ) {
-        let event = ClickhouseEvent::Copilot {
-            suggestion_id,
-            suggestion_accepted,
-            file_extension,
-            milliseconds_since_first_event: self.milliseconds_since_first_event(),
-        };
-
-        self.report_clickhouse_event(event, telemetry_settings, false)
-    }
-
-    pub fn report_assistant_event(
-        self: &Arc<Self>,
-        telemetry_settings: TelemetrySettings,
-        conversation_id: Option<String>,
-        kind: AssistantKind,
-        model: &'static str,
-    ) {
-        let event = ClickhouseEvent::Assistant {
-            conversation_id,
-            kind,
-            model,
-            milliseconds_since_first_event: self.milliseconds_since_first_event(),
-        };
-
-        self.report_clickhouse_event(event, telemetry_settings, false)
-    }
-
-    pub fn report_call_event(
-        self: &Arc<Self>,
-        telemetry_settings: TelemetrySettings,
-        operation: &'static str,
-        room_id: Option<u64>,
-        channel_id: Option<u64>,
-    ) {
-        let event = ClickhouseEvent::Call {
-            operation,
-            room_id,
-            channel_id,
-            milliseconds_since_first_event: self.milliseconds_since_first_event(),
-        };
-
-        self.report_clickhouse_event(event, telemetry_settings, false)
-    }
-
-    pub fn report_cpu_event(
-        self: &Arc<Self>,
-        telemetry_settings: TelemetrySettings,
-        usage_as_percentage: f32,
-        core_count: u32,
-    ) {
-        let event = ClickhouseEvent::Cpu {
-            usage_as_percentage,
-            core_count,
-            milliseconds_since_first_event: self.milliseconds_since_first_event(),
-        };
-
-        self.report_clickhouse_event(event, telemetry_settings, false)
-    }
-
-    pub fn report_memory_event(
-        self: &Arc<Self>,
-        telemetry_settings: TelemetrySettings,
-        memory_in_bytes: u64,
-        virtual_memory_in_bytes: u64,
-    ) {
-        let event = ClickhouseEvent::Memory {
-            memory_in_bytes,
-            virtual_memory_in_bytes,
-            milliseconds_since_first_event: self.milliseconds_since_first_event(),
-        };
-
-        self.report_clickhouse_event(event, telemetry_settings, false)
-    }
-
-    pub fn report_app_event(
-        self: &Arc<Self>,
-        telemetry_settings: TelemetrySettings,
-        operation: &'static str,
-        immediate_flush: bool,
-    ) {
-        let event = ClickhouseEvent::App {
-            operation,
-            milliseconds_since_first_event: self.milliseconds_since_first_event(),
-        };
-
-        self.report_clickhouse_event(event, telemetry_settings, immediate_flush)
-    }
-
-    pub fn report_setting_event(
-        self: &Arc<Self>,
-        telemetry_settings: TelemetrySettings,
-        setting: &'static str,
-        value: String,
-    ) {
-        let event = ClickhouseEvent::Setting {
-            setting,
-            value,
-            milliseconds_since_first_event: self.milliseconds_since_first_event(),
-        };
-
-        self.report_clickhouse_event(event, telemetry_settings, false)
-    }
-
-    fn milliseconds_since_first_event(&self) -> i64 {
-        let mut state = self.state.lock();
-        match state.first_event_datetime {
-            Some(first_event_datetime) => {
-                let now: DateTime<Utc> = Utc::now();
-                now.timestamp_millis() - first_event_datetime.timestamp_millis()
-            }
-            None => {
-                state.first_event_datetime = Some(Utc::now());
-                0
-            }
-        }
-    }
-
-    fn report_clickhouse_event(
-        self: &Arc<Self>,
-        event: ClickhouseEvent,
-        telemetry_settings: TelemetrySettings,
-        immediate_flush: bool,
-    ) {
-        if !telemetry_settings.metrics {
-            return;
-        }
-
-        let mut state = self.state.lock();
-        let signed_in = state.metrics_id.is_some();
-        state
-            .clickhouse_events_queue
-            .push(ClickhouseEventWrapper { signed_in, event });
-
-        if state.installation_id.is_some() {
-            if immediate_flush || state.clickhouse_events_queue.len() >= MAX_QUEUE_LEN {
-                drop(state);
-                self.flush_clickhouse_events();
-            } else {
-                let this = self.clone();
-                let executor = self.executor.clone();
-                state.flush_clickhouse_events_task = Some(self.executor.spawn(async move {
-                    executor.timer(DEBOUNCE_INTERVAL).await;
-                    this.flush_clickhouse_events();
-                }));
-            }
-        }
-    }
-
-    pub fn metrics_id(self: &Arc<Self>) -> Option<Arc<str>> {
-        self.state.lock().metrics_id.clone()
-    }
-
-    pub fn installation_id(self: &Arc<Self>) -> Option<Arc<str>> {
-        self.state.lock().installation_id.clone()
-    }
-
-    pub fn is_staff(self: &Arc<Self>) -> Option<bool> {
-        self.state.lock().is_staff
-    }
-
-    fn flush_clickhouse_events(self: &Arc<Self>) {
-        let mut state = self.state.lock();
-        state.first_event_datetime = None;
-        let mut events = mem::take(&mut state.clickhouse_events_queue);
-        state.flush_clickhouse_events_task.take();
-        drop(state);
-
-        let this = self.clone();
-        self.executor
-            .spawn(
-                async move {
-                    let mut json_bytes = Vec::new();
-
-                    if let Some(file) = &mut this.state.lock().log_file {
-                        let file = file.as_file_mut();
-                        for event in &mut events {
-                            json_bytes.clear();
-                            serde_json::to_writer(&mut json_bytes, event)?;
-                            file.write_all(&json_bytes)?;
-                            file.write(b"\n")?;
-                        }
-                    }
-
-                    {
-                        let state = this.state.lock();
-                        let request_body = ClickhouseEventRequestBody {
-                            token: ZED_SECRET_CLIENT_TOKEN,
-                            installation_id: state.installation_id.clone(),
-                            session_id: state.session_id.clone(),
-                            is_staff: state.is_staff.clone(),
-                            app_version: state
-                                .app_metadata
-                                .app_version
-                                .map(|version| version.to_string()),
-                            os_name: state.app_metadata.os_name,
-                            os_version: state
-                                .app_metadata
-                                .os_version
-                                .map(|version| version.to_string()),
-                            architecture: state.architecture,
-
-                            release_channel: state.release_channel,
-                            events,
-                        };
-                        json_bytes.clear();
-                        serde_json::to_writer(&mut json_bytes, &request_body)?;
-                    }
-
-                    this.http_client
-                        .post_json(CLICKHOUSE_EVENTS_URL.as_str(), json_bytes.into())
-                        .await?;
-                    anyhow::Ok(())
-                }
-                .log_err(),
-            )
-            .detach();
-    }
-}

crates/client2/src/test.rs 🔗

@@ -1,214 +0,0 @@
-use crate::{Client, Connection, Credentials, EstablishConnectionError, UserStore};
-use anyhow::{anyhow, Result};
-use futures::{stream::BoxStream, StreamExt};
-use gpui::{BackgroundExecutor, Context, Model, TestAppContext};
-use parking_lot::Mutex;
-use rpc::{
-    proto::{self, GetPrivateUserInfo, GetPrivateUserInfoResponse},
-    ConnectionId, Peer, Receipt, TypedEnvelope,
-};
-use std::sync::Arc;
-
-pub struct FakeServer {
-    peer: Arc<Peer>,
-    state: Arc<Mutex<FakeServerState>>,
-    user_id: u64,
-    executor: BackgroundExecutor,
-}
-
-#[derive(Default)]
-struct FakeServerState {
-    incoming: Option<BoxStream<'static, Box<dyn proto::AnyTypedEnvelope>>>,
-    connection_id: Option<ConnectionId>,
-    forbid_connections: bool,
-    auth_count: usize,
-    access_token: usize,
-}
-
-impl FakeServer {
-    pub async fn for_client(
-        client_user_id: u64,
-        client: &Arc<Client>,
-        cx: &TestAppContext,
-    ) -> Self {
-        let server = Self {
-            peer: Peer::new(0),
-            state: Default::default(),
-            user_id: client_user_id,
-            executor: cx.executor(),
-        };
-
-        client
-            .override_authenticate({
-                let state = Arc::downgrade(&server.state);
-                move |cx| {
-                    let state = state.clone();
-                    cx.spawn(move |_| async move {
-                        let state = state.upgrade().ok_or_else(|| anyhow!("server dropped"))?;
-                        let mut state = state.lock();
-                        state.auth_count += 1;
-                        let access_token = state.access_token.to_string();
-                        Ok(Credentials {
-                            user_id: client_user_id,
-                            access_token,
-                        })
-                    })
-                }
-            })
-            .override_establish_connection({
-                let peer = Arc::downgrade(&server.peer);
-                let state = Arc::downgrade(&server.state);
-                move |credentials, cx| {
-                    let peer = peer.clone();
-                    let state = state.clone();
-                    let credentials = credentials.clone();
-                    cx.spawn(move |cx| async move {
-                        let state = state.upgrade().ok_or_else(|| anyhow!("server dropped"))?;
-                        let peer = peer.upgrade().ok_or_else(|| anyhow!("server dropped"))?;
-                        if state.lock().forbid_connections {
-                            Err(EstablishConnectionError::Other(anyhow!(
-                                "server is forbidding connections"
-                            )))?
-                        }
-
-                        assert_eq!(credentials.user_id, client_user_id);
-
-                        if credentials.access_token != state.lock().access_token.to_string() {
-                            Err(EstablishConnectionError::Unauthorized)?
-                        }
-
-                        let (client_conn, server_conn, _) =
-                            Connection::in_memory(cx.background_executor().clone());
-                        let (connection_id, io, incoming) =
-                            peer.add_test_connection(server_conn, cx.background_executor().clone());
-                        cx.background_executor().spawn(io).detach();
-                        {
-                            let mut state = state.lock();
-                            state.connection_id = Some(connection_id);
-                            state.incoming = Some(incoming);
-                        }
-                        peer.send(
-                            connection_id,
-                            proto::Hello {
-                                peer_id: Some(connection_id.into()),
-                            },
-                        )
-                        .unwrap();
-
-                        Ok(client_conn)
-                    })
-                }
-            });
-
-        client
-            .authenticate_and_connect(false, &cx.to_async())
-            .await
-            .unwrap();
-
-        server
-    }
-
-    pub fn disconnect(&self) {
-        if self.state.lock().connection_id.is_some() {
-            self.peer.disconnect(self.connection_id());
-            let mut state = self.state.lock();
-            state.connection_id.take();
-            state.incoming.take();
-        }
-    }
-
-    pub fn auth_count(&self) -> usize {
-        self.state.lock().auth_count
-    }
-
-    pub fn roll_access_token(&self) {
-        self.state.lock().access_token += 1;
-    }
-
-    pub fn forbid_connections(&self) {
-        self.state.lock().forbid_connections = true;
-    }
-
-    pub fn allow_connections(&self) {
-        self.state.lock().forbid_connections = false;
-    }
-
-    pub fn send<T: proto::EnvelopedMessage>(&self, message: T) {
-        self.peer.send(self.connection_id(), message).unwrap();
-    }
-
-    #[allow(clippy::await_holding_lock)]
-    pub async fn receive<M: proto::EnvelopedMessage>(&self) -> Result<TypedEnvelope<M>> {
-        self.executor.start_waiting();
-
-        loop {
-            let message = self
-                .state
-                .lock()
-                .incoming
-                .as_mut()
-                .expect("not connected")
-                .next()
-                .await
-                .ok_or_else(|| anyhow!("other half hung up"))?;
-            self.executor.finish_waiting();
-            let type_name = message.payload_type_name();
-            let message = message.into_any();
-
-            if message.is::<TypedEnvelope<M>>() {
-                return Ok(*message.downcast().unwrap());
-            }
-
-            if message.is::<TypedEnvelope<GetPrivateUserInfo>>() {
-                self.respond(
-                    message
-                        .downcast::<TypedEnvelope<GetPrivateUserInfo>>()
-                        .unwrap()
-                        .receipt(),
-                    GetPrivateUserInfoResponse {
-                        metrics_id: "the-metrics-id".into(),
-                        staff: false,
-                        flags: Default::default(),
-                    },
-                );
-                continue;
-            }
-
-            panic!(
-                "fake server received unexpected message type: {:?}",
-                type_name
-            );
-        }
-    }
-
-    pub fn respond<T: proto::RequestMessage>(&self, receipt: Receipt<T>, response: T::Response) {
-        self.peer.respond(receipt, response).unwrap()
-    }
-
-    fn connection_id(&self) -> ConnectionId {
-        self.state.lock().connection_id.expect("not connected")
-    }
-
-    pub async fn build_user_store(
-        &self,
-        client: Arc<Client>,
-        cx: &mut TestAppContext,
-    ) -> Model<UserStore> {
-        let user_store = cx.new_model(|cx| UserStore::new(client, cx));
-        assert_eq!(
-            self.receive::<proto::GetUsers>()
-                .await
-                .unwrap()
-                .payload
-                .user_ids,
-            &[self.user_id]
-        );
-        user_store
-    }
-}
-
-impl Drop for FakeServer {
-    fn drop(&mut self) {
-        self.disconnect();
-    }
-}

crates/client2/src/user.rs 🔗

@@ -1,694 +0,0 @@
-use super::{proto, Client, Status, TypedEnvelope};
-use anyhow::{anyhow, Context, Result};
-use collections::{hash_map::Entry, HashMap, HashSet};
-use feature_flags::FeatureFlagAppExt;
-use futures::{channel::mpsc, Future, StreamExt};
-use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, SharedString, Task};
-use postage::{sink::Sink, watch};
-use rpc::proto::{RequestMessage, UsersResponse};
-use std::sync::{Arc, Weak};
-use text::ReplicaId;
-use util::TryFutureExt as _;
-
-pub type UserId = u64;
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub struct ParticipantIndex(pub u32);
-
-#[derive(Default, Debug)]
-pub struct User {
-    pub id: UserId,
-    pub github_login: String,
-    pub avatar_uri: SharedString,
-}
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct Collaborator {
-    pub peer_id: proto::PeerId,
-    pub replica_id: ReplicaId,
-    pub user_id: UserId,
-}
-
-impl PartialOrd for User {
-    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
-        Some(self.cmp(other))
-    }
-}
-
-impl Ord for User {
-    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
-        self.github_login.cmp(&other.github_login)
-    }
-}
-
-impl PartialEq for User {
-    fn eq(&self, other: &Self) -> bool {
-        self.id == other.id && self.github_login == other.github_login
-    }
-}
-
-impl Eq for User {}
-
-#[derive(Debug, PartialEq)]
-pub struct Contact {
-    pub user: Arc<User>,
-    pub online: bool,
-    pub busy: bool,
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum ContactRequestStatus {
-    None,
-    RequestSent,
-    RequestReceived,
-    RequestAccepted,
-}
-
-pub struct UserStore {
-    users: HashMap<u64, Arc<User>>,
-    participant_indices: HashMap<u64, ParticipantIndex>,
-    update_contacts_tx: mpsc::UnboundedSender<UpdateContacts>,
-    current_user: watch::Receiver<Option<Arc<User>>>,
-    contacts: Vec<Arc<Contact>>,
-    incoming_contact_requests: Vec<Arc<User>>,
-    outgoing_contact_requests: Vec<Arc<User>>,
-    pending_contact_requests: HashMap<u64, usize>,
-    invite_info: Option<InviteInfo>,
-    client: Weak<Client>,
-    _maintain_contacts: Task<()>,
-    _maintain_current_user: Task<Result<()>>,
-}
-
-#[derive(Clone)]
-pub struct InviteInfo {
-    pub count: u32,
-    pub url: Arc<str>,
-}
-
-pub enum Event {
-    Contact {
-        user: Arc<User>,
-        kind: ContactEventKind,
-    },
-    ShowContacts,
-    ParticipantIndicesChanged,
-}
-
-#[derive(Clone, Copy)]
-pub enum ContactEventKind {
-    Requested,
-    Accepted,
-    Cancelled,
-}
-
-impl EventEmitter<Event> for UserStore {}
-
-enum UpdateContacts {
-    Update(proto::UpdateContacts),
-    Wait(postage::barrier::Sender),
-    Clear(postage::barrier::Sender),
-}
-
-impl UserStore {
-    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
-        let (mut current_user_tx, current_user_rx) = watch::channel();
-        let (update_contacts_tx, mut update_contacts_rx) = mpsc::unbounded();
-        let rpc_subscriptions = vec![
-            client.add_message_handler(cx.weak_model(), Self::handle_update_contacts),
-            client.add_message_handler(cx.weak_model(), Self::handle_update_invite_info),
-            client.add_message_handler(cx.weak_model(), Self::handle_show_contacts),
-        ];
-        Self {
-            users: Default::default(),
-            current_user: current_user_rx,
-            contacts: Default::default(),
-            incoming_contact_requests: Default::default(),
-            participant_indices: Default::default(),
-            outgoing_contact_requests: Default::default(),
-            invite_info: None,
-            client: Arc::downgrade(&client),
-            update_contacts_tx,
-            _maintain_contacts: cx.spawn(|this, mut cx| async move {
-                let _subscriptions = rpc_subscriptions;
-                while let Some(message) = update_contacts_rx.next().await {
-                    if let Ok(task) =
-                        this.update(&mut cx, |this, cx| this.update_contacts(message, cx))
-                    {
-                        task.log_err().await;
-                    } else {
-                        break;
-                    }
-                }
-            }),
-            _maintain_current_user: cx.spawn(|this, mut cx| async move {
-                let mut status = client.status();
-                while let Some(status) = status.next().await {
-                    match status {
-                        Status::Connected { .. } => {
-                            if let Some(user_id) = client.user_id() {
-                                let fetch_user = if let Ok(fetch_user) = this
-                                    .update(&mut cx, |this, cx| {
-                                        this.get_user(user_id, cx).log_err()
-                                    }) {
-                                    fetch_user
-                                } else {
-                                    break;
-                                };
-                                let fetch_metrics_id =
-                                    client.request(proto::GetPrivateUserInfo {}).log_err();
-                                let (user, info) = futures::join!(fetch_user, fetch_metrics_id);
-
-                                cx.update(|cx| {
-                                    if let Some(info) = info {
-                                        cx.update_flags(info.staff, info.flags);
-                                        client.telemetry.set_authenticated_user_info(
-                                            Some(info.metrics_id.clone()),
-                                            info.staff,
-                                            cx,
-                                        )
-                                    }
-                                })?;
-
-                                current_user_tx.send(user).await.ok();
-
-                                this.update(&mut cx, |_, cx| cx.notify())?;
-                            }
-                        }
-                        Status::SignedOut => {
-                            current_user_tx.send(None).await.ok();
-                            this.update(&mut cx, |this, cx| {
-                                cx.notify();
-                                this.clear_contacts()
-                            })?
-                            .await;
-                        }
-                        Status::ConnectionLost => {
-                            this.update(&mut cx, |this, cx| {
-                                cx.notify();
-                                this.clear_contacts()
-                            })?
-                            .await;
-                        }
-                        _ => {}
-                    }
-                }
-                Ok(())
-            }),
-            pending_contact_requests: Default::default(),
-        }
-    }
-
-    #[cfg(feature = "test-support")]
-    pub fn clear_cache(&mut self) {
-        self.users.clear();
-    }
-
-    async fn handle_update_invite_info(
-        this: Model<Self>,
-        message: TypedEnvelope<proto::UpdateInviteInfo>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        this.update(&mut cx, |this, cx| {
-            this.invite_info = Some(InviteInfo {
-                url: Arc::from(message.payload.url),
-                count: message.payload.count,
-            });
-            cx.notify();
-        })?;
-        Ok(())
-    }
-
-    async fn handle_show_contacts(
-        this: Model<Self>,
-        _: TypedEnvelope<proto::ShowContacts>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        this.update(&mut cx, |_, cx| cx.emit(Event::ShowContacts))?;
-        Ok(())
-    }
-
-    pub fn invite_info(&self) -> Option<&InviteInfo> {
-        self.invite_info.as_ref()
-    }
-
-    async fn handle_update_contacts(
-        this: Model<Self>,
-        message: TypedEnvelope<proto::UpdateContacts>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        this.update(&mut cx, |this, _| {
-            this.update_contacts_tx
-                .unbounded_send(UpdateContacts::Update(message.payload))
-                .unwrap();
-        })?;
-        Ok(())
-    }
-
-    fn update_contacts(
-        &mut self,
-        message: UpdateContacts,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        match message {
-            UpdateContacts::Wait(barrier) => {
-                drop(barrier);
-                Task::ready(Ok(()))
-            }
-            UpdateContacts::Clear(barrier) => {
-                self.contacts.clear();
-                self.incoming_contact_requests.clear();
-                self.outgoing_contact_requests.clear();
-                drop(barrier);
-                Task::ready(Ok(()))
-            }
-            UpdateContacts::Update(message) => {
-                let mut user_ids = HashSet::default();
-                for contact in &message.contacts {
-                    user_ids.insert(contact.user_id);
-                }
-                user_ids.extend(message.incoming_requests.iter().map(|req| req.requester_id));
-                user_ids.extend(message.outgoing_requests.iter());
-
-                let load_users = self.get_users(user_ids.into_iter().collect(), cx);
-                cx.spawn(|this, mut cx| async move {
-                    load_users.await?;
-
-                    // Users are fetched in parallel above and cached in call to get_users
-                    // No need to paralellize here
-                    let mut updated_contacts = Vec::new();
-                    let this = this
-                        .upgrade()
-                        .ok_or_else(|| anyhow!("can't upgrade user store handle"))?;
-                    for contact in message.contacts {
-                        updated_contacts.push(Arc::new(
-                            Contact::from_proto(contact, &this, &mut cx).await?,
-                        ));
-                    }
-
-                    let mut incoming_requests = Vec::new();
-                    for request in message.incoming_requests {
-                        incoming_requests.push({
-                            this.update(&mut cx, |this, cx| {
-                                this.get_user(request.requester_id, cx)
-                            })?
-                            .await?
-                        });
-                    }
-
-                    let mut outgoing_requests = Vec::new();
-                    for requested_user_id in message.outgoing_requests {
-                        outgoing_requests.push(
-                            this.update(&mut cx, |this, cx| this.get_user(requested_user_id, cx))?
-                                .await?,
-                        );
-                    }
-
-                    let removed_contacts =
-                        HashSet::<u64>::from_iter(message.remove_contacts.iter().copied());
-                    let removed_incoming_requests =
-                        HashSet::<u64>::from_iter(message.remove_incoming_requests.iter().copied());
-                    let removed_outgoing_requests =
-                        HashSet::<u64>::from_iter(message.remove_outgoing_requests.iter().copied());
-
-                    this.update(&mut cx, |this, cx| {
-                        // Remove contacts
-                        this.contacts
-                            .retain(|contact| !removed_contacts.contains(&contact.user.id));
-                        // Update existing contacts and insert new ones
-                        for updated_contact in updated_contacts {
-                            match this.contacts.binary_search_by_key(
-                                &&updated_contact.user.github_login,
-                                |contact| &contact.user.github_login,
-                            ) {
-                                Ok(ix) => this.contacts[ix] = updated_contact,
-                                Err(ix) => this.contacts.insert(ix, updated_contact),
-                            }
-                        }
-
-                        // Remove incoming contact requests
-                        this.incoming_contact_requests.retain(|user| {
-                            if removed_incoming_requests.contains(&user.id) {
-                                cx.emit(Event::Contact {
-                                    user: user.clone(),
-                                    kind: ContactEventKind::Cancelled,
-                                });
-                                false
-                            } else {
-                                true
-                            }
-                        });
-                        // Update existing incoming requests and insert new ones
-                        for user in incoming_requests {
-                            match this
-                                .incoming_contact_requests
-                                .binary_search_by_key(&&user.github_login, |contact| {
-                                    &contact.github_login
-                                }) {
-                                Ok(ix) => this.incoming_contact_requests[ix] = user,
-                                Err(ix) => this.incoming_contact_requests.insert(ix, user),
-                            }
-                        }
-
-                        // Remove outgoing contact requests
-                        this.outgoing_contact_requests
-                            .retain(|user| !removed_outgoing_requests.contains(&user.id));
-                        // Update existing incoming requests and insert new ones
-                        for request in outgoing_requests {
-                            match this
-                                .outgoing_contact_requests
-                                .binary_search_by_key(&&request.github_login, |contact| {
-                                    &contact.github_login
-                                }) {
-                                Ok(ix) => this.outgoing_contact_requests[ix] = request,
-                                Err(ix) => this.outgoing_contact_requests.insert(ix, request),
-                            }
-                        }
-
-                        cx.notify();
-                    })?;
-
-                    Ok(())
-                })
-            }
-        }
-    }
-
-    pub fn contacts(&self) -> &[Arc<Contact>] {
-        &self.contacts
-    }
-
-    pub fn has_contact(&self, user: &Arc<User>) -> bool {
-        self.contacts
-            .binary_search_by_key(&&user.github_login, |contact| &contact.user.github_login)
-            .is_ok()
-    }
-
-    pub fn incoming_contact_requests(&self) -> &[Arc<User>] {
-        &self.incoming_contact_requests
-    }
-
-    pub fn outgoing_contact_requests(&self) -> &[Arc<User>] {
-        &self.outgoing_contact_requests
-    }
-
-    pub fn is_contact_request_pending(&self, user: &User) -> bool {
-        self.pending_contact_requests.contains_key(&user.id)
-    }
-
-    pub fn contact_request_status(&self, user: &User) -> ContactRequestStatus {
-        if self
-            .contacts
-            .binary_search_by_key(&&user.github_login, |contact| &contact.user.github_login)
-            .is_ok()
-        {
-            ContactRequestStatus::RequestAccepted
-        } else if self
-            .outgoing_contact_requests
-            .binary_search_by_key(&&user.github_login, |user| &user.github_login)
-            .is_ok()
-        {
-            ContactRequestStatus::RequestSent
-        } else if self
-            .incoming_contact_requests
-            .binary_search_by_key(&&user.github_login, |user| &user.github_login)
-            .is_ok()
-        {
-            ContactRequestStatus::RequestReceived
-        } else {
-            ContactRequestStatus::None
-        }
-    }
-
-    pub fn request_contact(
-        &mut self,
-        responder_id: u64,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        self.perform_contact_request(responder_id, proto::RequestContact { responder_id }, cx)
-    }
-
-    pub fn remove_contact(
-        &mut self,
-        user_id: u64,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        self.perform_contact_request(user_id, proto::RemoveContact { user_id }, cx)
-    }
-
-    pub fn has_incoming_contact_request(&self, user_id: u64) -> bool {
-        self.incoming_contact_requests
-            .iter()
-            .any(|user| user.id == user_id)
-    }
-
-    pub fn respond_to_contact_request(
-        &mut self,
-        requester_id: u64,
-        accept: bool,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        self.perform_contact_request(
-            requester_id,
-            proto::RespondToContactRequest {
-                requester_id,
-                response: if accept {
-                    proto::ContactRequestResponse::Accept
-                } else {
-                    proto::ContactRequestResponse::Decline
-                } as i32,
-            },
-            cx,
-        )
-    }
-
-    pub fn dismiss_contact_request(
-        &mut self,
-        requester_id: u64,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        let client = self.client.upgrade();
-        cx.spawn(move |_, _| async move {
-            client
-                .ok_or_else(|| anyhow!("can't upgrade client reference"))?
-                .request(proto::RespondToContactRequest {
-                    requester_id,
-                    response: proto::ContactRequestResponse::Dismiss as i32,
-                })
-                .await?;
-            Ok(())
-        })
-    }
-
-    fn perform_contact_request<T: RequestMessage>(
-        &mut self,
-        user_id: u64,
-        request: T,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        let client = self.client.upgrade();
-        *self.pending_contact_requests.entry(user_id).or_insert(0) += 1;
-        cx.notify();
-
-        cx.spawn(move |this, mut cx| async move {
-            let response = client
-                .ok_or_else(|| anyhow!("can't upgrade client reference"))?
-                .request(request)
-                .await;
-            this.update(&mut cx, |this, cx| {
-                if let Entry::Occupied(mut request_count) =
-                    this.pending_contact_requests.entry(user_id)
-                {
-                    *request_count.get_mut() -= 1;
-                    if *request_count.get() == 0 {
-                        request_count.remove();
-                    }
-                }
-                cx.notify();
-            })?;
-            response?;
-            Ok(())
-        })
-    }
-
-    pub fn clear_contacts(&mut self) -> impl Future<Output = ()> {
-        let (tx, mut rx) = postage::barrier::channel();
-        self.update_contacts_tx
-            .unbounded_send(UpdateContacts::Clear(tx))
-            .unwrap();
-        async move {
-            rx.next().await;
-        }
-    }
-
-    pub fn contact_updates_done(&mut self) -> impl Future<Output = ()> {
-        let (tx, mut rx) = postage::barrier::channel();
-        self.update_contacts_tx
-            .unbounded_send(UpdateContacts::Wait(tx))
-            .unwrap();
-        async move {
-            rx.next().await;
-        }
-    }
-
-    pub fn get_users(
-        &mut self,
-        user_ids: Vec<u64>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<Arc<User>>>> {
-        let mut user_ids_to_fetch = user_ids.clone();
-        user_ids_to_fetch.retain(|id| !self.users.contains_key(id));
-
-        cx.spawn(|this, mut cx| async move {
-            if !user_ids_to_fetch.is_empty() {
-                this.update(&mut cx, |this, cx| {
-                    this.load_users(
-                        proto::GetUsers {
-                            user_ids: user_ids_to_fetch,
-                        },
-                        cx,
-                    )
-                })?
-                .await?;
-            }
-
-            this.update(&mut cx, |this, _| {
-                user_ids
-                    .iter()
-                    .map(|user_id| {
-                        this.users
-                            .get(user_id)
-                            .cloned()
-                            .ok_or_else(|| anyhow!("user {} not found", user_id))
-                    })
-                    .collect()
-            })?
-        })
-    }
-
-    pub fn fuzzy_search_users(
-        &mut self,
-        query: String,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<Arc<User>>>> {
-        self.load_users(proto::FuzzySearchUsers { query }, cx)
-    }
-
-    pub fn get_cached_user(&self, user_id: u64) -> Option<Arc<User>> {
-        self.users.get(&user_id).cloned()
-    }
-
-    pub fn get_user(
-        &mut self,
-        user_id: u64,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Arc<User>>> {
-        if let Some(user) = self.users.get(&user_id).cloned() {
-            return Task::ready(Ok(user));
-        }
-
-        let load_users = self.get_users(vec![user_id], cx);
-        cx.spawn(move |this, mut cx| async move {
-            load_users.await?;
-            this.update(&mut cx, |this, _| {
-                this.users
-                    .get(&user_id)
-                    .cloned()
-                    .ok_or_else(|| anyhow!("server responded with no users"))
-            })?
-        })
-    }
-
-    pub fn current_user(&self) -> Option<Arc<User>> {
-        self.current_user.borrow().clone()
-    }
-
-    pub fn watch_current_user(&self) -> watch::Receiver<Option<Arc<User>>> {
-        self.current_user.clone()
-    }
-
-    fn load_users(
-        &mut self,
-        request: impl RequestMessage<Response = UsersResponse>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<Arc<User>>>> {
-        let client = self.client.clone();
-        cx.spawn(|this, mut cx| async move {
-            if let Some(rpc) = client.upgrade() {
-                let response = rpc.request(request).await.context("error loading users")?;
-                let users = response
-                    .users
-                    .into_iter()
-                    .map(|user| User::new(user))
-                    .collect::<Vec<_>>();
-
-                this.update(&mut cx, |this, _| {
-                    for user in &users {
-                        this.users.insert(user.id, user.clone());
-                    }
-                })
-                .ok();
-
-                Ok(users)
-            } else {
-                Ok(Vec::new())
-            }
-        })
-    }
-
-    pub fn set_participant_indices(
-        &mut self,
-        participant_indices: HashMap<u64, ParticipantIndex>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        if participant_indices != self.participant_indices {
-            self.participant_indices = participant_indices;
-            cx.emit(Event::ParticipantIndicesChanged);
-        }
-    }
-
-    pub fn participant_indices(&self) -> &HashMap<u64, ParticipantIndex> {
-        &self.participant_indices
-    }
-}
-
-impl User {
-    fn new(message: proto::User) -> Arc<Self> {
-        Arc::new(User {
-            id: message.id,
-            github_login: message.github_login,
-            avatar_uri: message.avatar_url.into(),
-        })
-    }
-}
-
-impl Contact {
-    async fn from_proto(
-        contact: proto::Contact,
-        user_store: &Model<UserStore>,
-        cx: &mut AsyncAppContext,
-    ) -> Result<Self> {
-        let user = user_store
-            .update(cx, |user_store, cx| {
-                user_store.get_user(contact.user_id, cx)
-            })?
-            .await?;
-        Ok(Self {
-            user,
-            online: contact.online,
-            busy: contact.busy,
-        })
-    }
-}
-
-impl Collaborator {
-    pub fn from_proto(message: proto::Collaborator) -> Result<Self> {
-        Ok(Self {
-            peer_id: message.peer_id.ok_or_else(|| anyhow!("invalid peer id"))?,
-            replica_id: message.replica_id as ReplicaId,
-            user_id: message.user_id as UserId,
-        })
-    }
-}

crates/collab/Cargo.toml 🔗

@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
 default-run = "collab"
 edition = "2021"
 name = "collab"
-version = "0.32.0"
+version = "0.28.0"
 publish = false
 
 [[bin]]
@@ -74,11 +74,13 @@ live_kit_client = { path = "../live_kit_client", features = ["test-support"] }
 lsp = { path = "../lsp", features = ["test-support"] }
 node_runtime = { path = "../node_runtime" }
 notifications = { path = "../notifications", features = ["test-support"] }
+
 project = { path = "../project", features = ["test-support"] }
 rpc = { path = "../rpc", features = ["test-support"] }
 settings = { path = "../settings", features = ["test-support"] }
 theme = { path = "../theme" }
 workspace = { path = "../workspace", features = ["test-support"] }
+
 collab_ui = { path = "../collab_ui", features = ["test-support"] }
 
 async-trait.workspace = true

crates/collab/src/db/tests.rs 🔗

@@ -5,7 +5,7 @@ mod feature_flag_tests;
 mod message_tests;
 
 use super::*;
-use gpui::executor::Background;
+use gpui::BackgroundExecutor;
 use parking_lot::Mutex;
 use sea_orm::ConnectionTrait;
 use sqlx::migrate::MigrateDatabase;
@@ -22,7 +22,7 @@ pub struct TestDb {
 }
 
 impl TestDb {
-    pub fn sqlite(background: Arc<Background>) -> Self {
+    pub fn sqlite(background: BackgroundExecutor) -> Self {
         let url = format!("sqlite::memory:");
         let runtime = tokio::runtime::Builder::new_current_thread()
             .enable_io()
@@ -59,7 +59,7 @@ impl TestDb {
         }
     }
 
-    pub fn postgres(background: Arc<Background>) -> Self {
+    pub fn postgres(background: BackgroundExecutor) -> Self {
         static LOCK: Mutex<()> = Mutex::new(());
 
         let _guard = LOCK.lock();
@@ -108,17 +108,14 @@ impl TestDb {
 macro_rules! test_both_dbs {
     ($test_name:ident, $postgres_test_name:ident, $sqlite_test_name:ident) => {
         #[gpui::test]
-        async fn $postgres_test_name() {
-            let test_db = crate::db::TestDb::postgres(
-                gpui::executor::Deterministic::new(0).build_background(),
-            );
+        async fn $postgres_test_name(cx: &mut gpui::TestAppContext) {
+            let test_db = crate::db::TestDb::postgres(cx.executor().clone());
             $test_name(test_db.db()).await;
         }
 
         #[gpui::test]
-        async fn $sqlite_test_name() {
-            let test_db =
-                crate::db::TestDb::sqlite(gpui::executor::Deterministic::new(0).build_background());
+        async fn $sqlite_test_name(cx: &mut gpui::TestAppContext) {
+            let test_db = crate::db::TestDb::sqlite(cx.executor().clone());
             $test_name(test_db.db()).await;
         }
     };

crates/collab/src/db/tests/channel_tests.rs 🔗

@@ -420,8 +420,6 @@ async fn test_db_channel_moving_bugs(db: &Arc<Database>) {
         .await
         .unwrap();
 
-    // Dag is: zed - projects - livestreaming
-
     // Move to same parent should be a no-op
     assert!(db
         .move_channel(projects_id, Some(zed_id), user_id)

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

@@ -1,6 +1,6 @@
 use super::*;
 use crate::test_both_dbs;
-use gpui::executor::{Background, Deterministic};
+use gpui::TestAppContext;
 use pretty_assertions::{assert_eq, assert_ne};
 use std::sync::Arc;
 use tests::TestDb;
@@ -509,8 +509,8 @@ fn test_fuzzy_like_string() {
 }
 
 #[gpui::test]
-async fn test_fuzzy_search_users() {
-    let test_db = TestDb::postgres(build_background_executor());
+async fn test_fuzzy_search_users(cx: &mut TestAppContext) {
+    let test_db = TestDb::postgres(cx.executor());
     let db = test_db.db();
     for (i, github_login) in [
         "California",
@@ -631,7 +631,3 @@ async fn test_non_matching_release_channels(db: &Arc<Database>) {
 
     assert!(result.is_ok())
 }
-
-fn build_background_executor() -> Arc<Background> {
-    Deterministic::new(0).build_background()
-}

crates/collab/src/executor.rs 🔗

@@ -1,10 +1,13 @@
 use std::{future::Future, time::Duration};
 
+#[cfg(test)]
+use gpui::BackgroundExecutor;
+
 #[derive(Clone)]
 pub enum Executor {
     Production,
     #[cfg(test)]
-    Deterministic(std::sync::Arc<gpui::executor::Background>),
+    Deterministic(BackgroundExecutor),
 }
 
 impl Executor {
@@ -33,12 +36,4 @@ impl Executor {
             }
         }
     }
-
-    pub fn record_backtrace(&self) {
-        match self {
-            Executor::Production => {}
-            #[cfg(test)]
-            Executor::Deterministic(background) => background.record_backtrace(),
-        }
-    }
 }

crates/collab/src/rpc.rs 🔗

@@ -110,7 +110,7 @@ struct Session {
     peer: Arc<Peer>,
     connection_pool: Arc<parking_lot::Mutex<ConnectionPool>>,
     live_kit_client: Option<Arc<dyn live_kit_server::api::Client>>,
-    executor: Executor,
+    _executor: Executor,
 }
 
 impl Session {
@@ -612,7 +612,7 @@ impl Server {
                 peer: this.peer.clone(),
                 connection_pool: this.connection_pool.clone(),
                 live_kit_client: this.app_state.live_kit_client.clone(),
-                executor: executor.clone(),
+                _executor: executor.clone()
             };
             update_user_contacts(user_id, &session).await?;
 
@@ -1723,7 +1723,6 @@ async fn update_language_server(
     request: proto::UpdateLanguageServer,
     session: Session,
 ) -> Result<()> {
-    session.executor.record_backtrace();
     let project_id = ProjectId::from_proto(request.project_id);
     let project_connection_ids = session
         .db()
@@ -1750,7 +1749,6 @@ async fn forward_project_request<T>(
 where
     T: EntityMessage + RequestMessage,
 {
-    session.executor.record_backtrace();
     let project_id = ProjectId::from_proto(request.remote_entity_id());
     let host_connection_id = {
         let collaborators = session
@@ -1778,7 +1776,6 @@ async fn create_buffer_for_peer(
     request: proto::CreateBufferForPeer,
     session: Session,
 ) -> Result<()> {
-    session.executor.record_backtrace();
     let peer_id = request.peer_id.ok_or_else(|| anyhow!("invalid peer id"))?;
     session
         .peer
@@ -1791,7 +1788,6 @@ async fn update_buffer(
     response: Response<proto::UpdateBuffer>,
     session: Session,
 ) -> Result<()> {
-    session.executor.record_backtrace();
     let project_id = ProjectId::from_proto(request.project_id);
     let mut guest_connection_ids;
     let mut host_connection_id = None;
@@ -1812,7 +1808,6 @@ async fn update_buffer(
     }
     let host_connection_id = host_connection_id.ok_or_else(|| anyhow!("host not found"))?;
 
-    session.executor.record_backtrace();
     broadcast(
         Some(session.connection_id),
         guest_connection_ids,

crates/collab/src/tests.rs 🔗

@@ -1,9 +1,10 @@
 use call::Room;
-use gpui::{ModelHandle, TestAppContext};
+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;
@@ -23,7 +24,7 @@ struct RoomParticipants {
     pending: Vec<String>,
 }
 
-fn room_participants(room: &ModelHandle<Room>, cx: &mut TestAppContext) -> RoomParticipants {
+fn room_participants(room: &Model<Room>, cx: &mut TestAppContext) -> RoomParticipants {
     room.read_with(cx, |room, _| {
         let mut remote = room
             .remote_participants()
@@ -41,6 +42,6 @@ fn room_participants(room: &ModelHandle<Room>, cx: &mut TestAppContext) -> RoomP
     })
 }
 
-fn channel_id(room: &ModelHandle<Room>, cx: &mut TestAppContext) -> Option<u64> {
+fn channel_id(room: &Model<Room>, cx: &mut TestAppContext) -> Option<u64> {
     cx.read(|cx| room.read(cx).channel_id())
 }

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

@@ -4,25 +4,23 @@ use crate::{
 };
 use call::ActiveCall;
 use channel::ACKNOWLEDGE_DEBOUNCE_INTERVAL;
-use client::ParticipantIndex;
-use client::{Collaborator, UserId};
+use client::{Collaborator, ParticipantIndex, UserId};
 use collab_ui::channel_view::ChannelView;
 use collections::HashMap;
 use editor::{Anchor, Editor, ToOffset};
 use futures::future;
-use gpui::{executor::Deterministic, ModelHandle, TestAppContext, ViewContext};
+use gpui::{BackgroundExecutor, Model, TestAppContext, ViewContext};
 use rpc::{proto::PeerId, RECEIVE_TIMEOUT};
 use serde_json::json;
-use std::{ops::Range, sync::Arc};
+use std::ops::Range;
 
 #[gpui::test]
 async fn test_core_channel_buffers(
-    deterministic: Arc<Deterministic>,
+    executor: BackgroundExecutor,
     cx_a: &mut TestAppContext,
     cx_b: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(executor.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
 
@@ -50,7 +48,7 @@ async fn test_core_channel_buffers(
     });
     buffer_a.update(cx_a, |buffer, cx| buffer.undo(cx));
     assert_eq!(buffer_text(&buffer_a, cx_a), "hello, cruel world");
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
     // Client B joins the channel buffer
     let channel_buffer_b = client_b
@@ -77,13 +75,13 @@ async fn test_core_channel_buffers(
     });
 
     // Both A and B see the new edit
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert_eq!(buffer_text(&buffer_a, cx_a), "hello, beautiful world");
     assert_eq!(buffer_text(&buffer_b, cx_b), "hello, beautiful world");
 
     // Client A closes the channel buffer.
     cx_a.update(|_| drop(channel_buffer_a));
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
     // Client B sees that client A is gone from the channel buffer.
     channel_buffer_b.read_with(cx_b, |buffer, _| {
@@ -96,7 +94,7 @@ async fn test_core_channel_buffers(
         .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
         .await
         .unwrap();
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
     // Sanity test, make sure we saw A rejoining
     channel_buffer_b.read_with(cx_b, |buffer, _| {
@@ -109,7 +107,7 @@ async fn test_core_channel_buffers(
     // Client A loses connection.
     server.forbid_connections();
     server.disconnect_client(client_a.peer_id().unwrap());
-    deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
+    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
 
     // Client B observes A disconnect
     channel_buffer_b.read_with(cx_b, |buffer, _| {
@@ -123,13 +121,12 @@ async fn test_core_channel_buffers(
 
 #[gpui::test]
 async fn test_channel_notes_participant_indices(
-    deterministic: Arc<Deterministic>,
-    mut cx_a: &mut TestAppContext,
-    mut cx_b: &mut TestAppContext,
+    executor: BackgroundExecutor,
+    cx_a: &mut TestAppContext,
+    cx_b: &mut TestAppContext,
     cx_c: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(executor.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     let client_c = server.create_client(cx_c, "user_c").await;
@@ -157,9 +154,10 @@ async fn test_channel_notes_participant_indices(
     let (project_a, worktree_id_a) = client_a.build_local_project("/root", cx_a).await;
     let project_b = client_b.build_empty_local_project(cx_b);
     let project_c = client_c.build_empty_local_project(cx_c);
-    let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
-    let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
-    let workspace_c = client_c.build_workspace(&project_c, cx_c).root(cx_c);
+
+    let (workspace_a, mut cx_a) = client_a.build_workspace(&project_a, cx_a);
+    let (workspace_b, mut cx_b) = client_b.build_workspace(&project_b, cx_b);
+    let (workspace_c, cx_c) = client_c.build_workspace(&project_c, cx_c);
 
     // Clients A, B, and C open the channel notes
     let channel_view_a = cx_a
@@ -184,7 +182,7 @@ async fn test_channel_notes_participant_indices(
             });
         });
     });
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     channel_view_b.update(cx_b, |notes, cx| {
         notes.editor.update(cx, |editor, cx| {
             editor.move_down(&Default::default(), cx);
@@ -194,7 +192,7 @@ async fn test_channel_notes_participant_indices(
             });
         });
     });
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     channel_view_c.update(cx_c, |notes, cx| {
         notes.editor.update(cx, |editor, cx| {
             editor.move_down(&Default::default(), cx);
@@ -207,7 +205,7 @@ async fn test_channel_notes_participant_indices(
 
     // Client A sees clients B and C without assigned colors, because they aren't
     // in a call together.
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     channel_view_a.update(cx_a, |notes, cx| {
         notes.editor.update(cx, |editor, cx| {
             assert_remote_selections(editor, &[(None, 1..2), (None, 2..3)], cx);
@@ -223,7 +221,7 @@ async fn test_channel_notes_participant_indices(
 
     // Clients A and B see each other with two different assigned colors. Client C
     // still doesn't have a color.
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     channel_view_a.update(cx_a, |notes, cx| {
         notes.editor.update(cx, |editor, cx| {
             assert_remote_selections(
@@ -249,7 +247,7 @@ async fn test_channel_notes_participant_indices(
         .await
         .unwrap();
     let project_b = client_b.build_remote_project(project_id, cx_b).await;
-    let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
+    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
 
     // Clients A and B open the same file.
     let editor_a = workspace_a
@@ -279,7 +277,7 @@ async fn test_channel_notes_participant_indices(
             selections.select_ranges(vec![2..3]);
         });
     });
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
     // Clients A and B see each other with the same colors as in the channel notes.
     editor_a.update(cx_a, |editor, cx| {
@@ -314,11 +312,10 @@ fn assert_remote_selections(
 
 #[gpui::test]
 async fn test_multiple_handles_to_channel_buffer(
-    deterministic: Arc<Deterministic>,
+    deterministic: BackgroundExecutor,
     cx_a: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(deterministic.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
 
     let channel_id = server
@@ -340,7 +337,7 @@ async fn test_multiple_handles_to_channel_buffer(
         future::try_join3(channel_buffer_1, channel_buffer_2, channel_buffer_3)
             .await
             .unwrap();
-    let channel_buffer_model_id = channel_buffer.id();
+    let channel_buffer_model_id = channel_buffer.entity_id();
     assert_eq!(channel_buffer, channel_buffer_2);
     assert_eq!(channel_buffer, channel_buffer_3);
 
@@ -364,7 +361,7 @@ async fn test_multiple_handles_to_channel_buffer(
         .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
         .await
         .unwrap();
-    assert_ne!(channel_buffer.id(), channel_buffer_model_id);
+    assert_ne!(channel_buffer.entity_id(), channel_buffer_model_id);
     channel_buffer.update(cx_a, |buffer, cx| {
         buffer.buffer().update(cx, |buffer, _| {
             assert_eq!(buffer.text(), "hello");
@@ -374,12 +371,11 @@ async fn test_multiple_handles_to_channel_buffer(
 
 #[gpui::test]
 async fn test_channel_buffer_disconnect(
-    deterministic: Arc<Deterministic>,
+    deterministic: BackgroundExecutor,
     cx_a: &mut TestAppContext,
     cx_b: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(deterministic.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
 
@@ -397,6 +393,7 @@ async fn test_channel_buffer_disconnect(
         .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
         .await
         .unwrap();
+
     let channel_buffer_b = client_b
         .channel_store()
         .update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
@@ -437,12 +434,11 @@ async fn test_channel_buffer_disconnect(
 
 #[gpui::test]
 async fn test_rejoin_channel_buffer(
-    deterministic: Arc<Deterministic>,
+    deterministic: BackgroundExecutor,
     cx_a: &mut TestAppContext,
     cx_b: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(deterministic.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
 
@@ -518,13 +514,12 @@ async fn test_rejoin_channel_buffer(
 
 #[gpui::test]
 async fn test_channel_buffers_and_server_restarts(
-    deterministic: Arc<Deterministic>,
+    deterministic: BackgroundExecutor,
     cx_a: &mut TestAppContext,
     cx_b: &mut TestAppContext,
     cx_c: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(deterministic.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     let client_c = server.create_client(cx_c, "user_c").await;
@@ -606,13 +601,12 @@ async fn test_channel_buffers_and_server_restarts(
 
 #[gpui::test(iterations = 10)]
 async fn test_following_to_channel_notes_without_a_shared_project(
-    deterministic: Arc<Deterministic>,
+    deterministic: BackgroundExecutor,
     mut cx_a: &mut TestAppContext,
     mut cx_b: &mut TestAppContext,
     mut cx_c: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(deterministic.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
 
@@ -664,9 +658,9 @@ async fn test_following_to_channel_notes_without_a_shared_project(
     let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
     let (project_b, _) = client_b.build_local_project("/b", cx_b).await;
     let (project_c, _) = client_b.build_local_project("/c", cx_c).await;
-    let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
-    let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
-    let _workspace_c = client_c.build_workspace(&project_c, cx_c).root(cx_c);
+    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
+    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
+    let (_workspace_c, _cx_c) = client_c.build_workspace(&project_c, cx_c);
 
     active_call_a
         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
@@ -691,7 +685,9 @@ async fn test_following_to_channel_notes_without_a_shared_project(
     // Client B follows client A.
     workspace_b
         .update(cx_b, |workspace, cx| {
-            workspace.follow(client_a.peer_id().unwrap(), cx).unwrap()
+            workspace
+                .start_following(client_a.peer_id().unwrap(), cx)
+                .unwrap()
         })
         .await
         .unwrap();
@@ -699,7 +695,7 @@ async fn test_following_to_channel_notes_without_a_shared_project(
     // Client B is taken to the notes for channel 1, with the same
     // text selected as client A.
     deterministic.run_until_parked();
-    let channel_view_1_b = workspace_b.read_with(cx_b, |workspace, cx| {
+    let channel_view_1_b = workspace_b.update(cx_b, |workspace, cx| {
         assert_eq!(
             workspace.leader_for_pane(workspace.active_pane()),
             Some(client_a.peer_id().unwrap())
@@ -710,7 +706,7 @@ async fn test_following_to_channel_notes_without_a_shared_project(
             .downcast::<ChannelView>()
             .expect("active item is not a channel view")
     });
-    channel_view_1_b.read_with(cx_b, |notes, cx| {
+    channel_view_1_b.update(cx_b, |notes, cx| {
         assert_eq!(notes.channel(cx).unwrap().name, "channel-1");
         let editor = notes.editor.read(cx);
         assert_eq!(editor.text(cx), "Hello from A.");
@@ -718,17 +714,22 @@ async fn test_following_to_channel_notes_without_a_shared_project(
     });
 
     // Client A opens the notes for channel 2.
+    eprintln!("opening -------------------->");
+
     let channel_view_2_a = cx_a
         .update(|cx| ChannelView::open(channel_2_id, workspace_a.clone(), cx))
         .await
         .unwrap();
-    channel_view_2_a.read_with(cx_a, |notes, cx| {
+    channel_view_2_a.update(cx_a, |notes, cx| {
         assert_eq!(notes.channel(cx).unwrap().name, "channel-2");
     });
 
     // Client B is taken to the notes for channel 2.
     deterministic.run_until_parked();
-    let channel_view_2_b = workspace_b.read_with(cx_b, |workspace, cx| {
+
+    eprintln!("opening <--------------------");
+
+    let channel_view_2_b = workspace_b.update(cx_b, |workspace, cx| {
         assert_eq!(
             workspace.leader_for_pane(workspace.active_pane()),
             Some(client_a.peer_id().unwrap())
@@ -739,19 +740,18 @@ async fn test_following_to_channel_notes_without_a_shared_project(
             .downcast::<ChannelView>()
             .expect("active item is not a channel view")
     });
-    channel_view_2_b.read_with(cx_b, |notes, cx| {
+    channel_view_2_b.update(cx_b, |notes, cx| {
         assert_eq!(notes.channel(cx).unwrap().name, "channel-2");
     });
 }
 
 #[gpui::test]
 async fn test_channel_buffer_changes(
-    deterministic: Arc<Deterministic>,
+    deterministic: BackgroundExecutor,
     cx_a: &mut TestAppContext,
     cx_b: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(deterministic.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
 
@@ -778,7 +778,7 @@ async fn test_channel_buffer_changes(
     });
     deterministic.run_until_parked();
 
-    let has_buffer_changed = cx_b.read(|cx| {
+    let has_buffer_changed = cx_b.update(|cx| {
         client_b
             .channel_store()
             .read(cx)
@@ -789,14 +789,14 @@ async fn test_channel_buffer_changes(
 
     // Opening the buffer should clear the changed flag.
     let project_b = client_b.build_empty_local_project(cx_b);
-    let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
+    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
     let channel_view_b = cx_b
         .update(|cx| ChannelView::open(channel_id, workspace_b.clone(), cx))
         .await
         .unwrap();
     deterministic.run_until_parked();
 
-    let has_buffer_changed = cx_b.read(|cx| {
+    let has_buffer_changed = cx_b.update(|cx| {
         client_b
             .channel_store()
             .read(cx)
@@ -826,7 +826,8 @@ async fn test_channel_buffer_changes(
 
     // Test that the server is tracking things correctly, and we retain our 'not changed'
     // state across a disconnect
-    server.simulate_long_connection_interruption(client_b.peer_id().unwrap(), &deterministic);
+    server
+        .simulate_long_connection_interruption(client_b.peer_id().unwrap(), deterministic.clone());
     let has_buffer_changed = cx_b.read(|cx| {
         client_b
             .channel_store()
@@ -877,6 +878,6 @@ fn assert_collaborators(collaborators: &HashMap<PeerId, Collaborator>, ids: &[Op
     );
 }
 
-fn buffer_text(channel_buffer: &ModelHandle<language::Buffer>, cx: &mut TestAppContext) -> String {
+fn buffer_text(channel_buffer: &Model<language::Buffer>, cx: &mut TestAppContext) -> String {
     channel_buffer.read_with(cx, |buffer, _| buffer.text())
 }

crates/collab/src/tests/channel_message_tests.rs 🔗

@@ -1,20 +1,16 @@
 use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
 use channel::{ChannelChat, ChannelMessageId, MessageParams};
-use collab_ui::chat_panel::ChatPanel;
-use gpui::{executor::Deterministic, BorrowAppContext, ModelHandle, TestAppContext};
+use gpui::{BackgroundExecutor, Model, TestAppContext};
 use rpc::Notification;
-use std::sync::Arc;
-use workspace::dock::Panel;
 
 #[gpui::test]
 async fn test_basic_channel_messages(
-    deterministic: Arc<Deterministic>,
+    executor: BackgroundExecutor,
     mut cx_a: &mut TestAppContext,
     mut cx_b: &mut TestAppContext,
     mut cx_c: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(executor.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     let client_c = server.create_client(cx_c, "user_c").await;
@@ -57,13 +53,13 @@ async fn test_basic_channel_messages(
         .await
         .unwrap();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     channel_chat_b
         .update(cx_b, |c, cx| c.send_message("three".into(), cx).unwrap())
         .await
         .unwrap();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
     let channel_chat_c = client_c
         .channel_store()
@@ -117,12 +113,11 @@ async fn test_basic_channel_messages(
 
 #[gpui::test]
 async fn test_rejoin_channel_chat(
-    deterministic: Arc<Deterministic>,
+    executor: BackgroundExecutor,
     cx_a: &mut TestAppContext,
     cx_b: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(executor.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
 
@@ -178,7 +173,7 @@ async fn test_rejoin_channel_chat(
 
     // Client A reconnects.
     server.allow_connections();
-    deterministic.advance_clock(RECONNECT_TIMEOUT);
+    executor.advance_clock(RECONNECT_TIMEOUT);
 
     // Client A fetches the messages that were sent while they were disconnected
     // and resends their own messages which failed to send.
@@ -189,13 +184,12 @@ async fn test_rejoin_channel_chat(
 
 #[gpui::test]
 async fn test_remove_channel_message(
-    deterministic: Arc<Deterministic>,
+    executor: BackgroundExecutor,
     cx_a: &mut TestAppContext,
     cx_b: &mut TestAppContext,
     cx_c: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(executor.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     let client_c = server.create_client(cx_c, "user_c").await;
@@ -235,7 +229,7 @@ async fn test_remove_channel_message(
         .unwrap();
 
     // Clients A and B see all of the messages.
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     let expected_messages = &["one", "two", "three"];
     assert_messages(&channel_chat_a, expected_messages, cx_a);
     assert_messages(&channel_chat_b, expected_messages, cx_b);
@@ -252,7 +246,7 @@ async fn test_remove_channel_message(
         .unwrap();
 
     // Client B sees that the message is gone.
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     let expected_messages = &["one", "three"];
     assert_messages(&channel_chat_a, expected_messages, cx_a);
     assert_messages(&channel_chat_b, expected_messages, cx_b);
@@ -267,146 +261,148 @@ async fn test_remove_channel_message(
 }
 
 #[track_caller]
-fn assert_messages(chat: &ModelHandle<ChannelChat>, messages: &[&str], cx: &mut TestAppContext) {
+fn assert_messages(chat: &Model<ChannelChat>, messages: &[&str], cx: &mut TestAppContext) {
+    // todo!(don't directly borrow here)
     assert_eq!(
-        chat.read_with(cx, |chat, _| chat
-            .messages()
-            .iter()
-            .map(|m| m.body.clone())
-            .collect::<Vec<_>>(),),
+        chat.read_with(cx, |chat, _| {
+            chat.messages()
+                .iter()
+                .map(|m| m.body.clone())
+                .collect::<Vec<_>>()
+        }),
         messages
     );
 }
 
-#[gpui::test]
-async fn test_channel_message_changes(
-    deterministic: Arc<Deterministic>,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-
-    let channel_id = server
-        .make_channel(
-            "the-channel",
-            None,
-            (&client_a, cx_a),
-            &mut [(&client_b, cx_b)],
-        )
-        .await;
-
-    // Client A sends a message, client B should see that there is a new message.
-    let channel_chat_a = client_a
-        .channel_store()
-        .update(cx_a, |store, cx| store.open_channel_chat(channel_id, cx))
-        .await
-        .unwrap();
-
-    channel_chat_a
-        .update(cx_a, |c, cx| c.send_message("one".into(), cx).unwrap())
-        .await
-        .unwrap();
-
-    deterministic.run_until_parked();
-
-    let b_has_messages = cx_b.read_with(|cx| {
-        client_b
-            .channel_store()
-            .read(cx)
-            .has_new_messages(channel_id)
-            .unwrap()
-    });
-
-    assert!(b_has_messages);
-
-    // Opening the chat should clear the changed flag.
-    cx_b.update(|cx| {
-        collab_ui::init(&client_b.app_state, cx);
-    });
-    let project_b = client_b.build_empty_local_project(cx_b);
-    let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
-    let chat_panel_b = workspace_b.update(cx_b, |workspace, cx| ChatPanel::new(workspace, cx));
-    chat_panel_b
-        .update(cx_b, |chat_panel, cx| {
-            chat_panel.set_active(true, cx);
-            chat_panel.select_channel(channel_id, None, cx)
-        })
-        .await
-        .unwrap();
-
-    deterministic.run_until_parked();
-
-    let b_has_messages = cx_b.read_with(|cx| {
-        client_b
-            .channel_store()
-            .read(cx)
-            .has_new_messages(channel_id)
-            .unwrap()
-    });
-
-    assert!(!b_has_messages);
-
-    // Sending a message while the chat is open should not change the flag.
-    channel_chat_a
-        .update(cx_a, |c, cx| c.send_message("two".into(), cx).unwrap())
-        .await
-        .unwrap();
-
-    deterministic.run_until_parked();
-
-    let b_has_messages = cx_b.read_with(|cx| {
-        client_b
-            .channel_store()
-            .read(cx)
-            .has_new_messages(channel_id)
-            .unwrap()
-    });
-
-    assert!(!b_has_messages);
-
-    // Sending a message while the chat is closed should change the flag.
-    chat_panel_b.update(cx_b, |chat_panel, cx| {
-        chat_panel.set_active(false, cx);
-    });
-
-    // Sending a message while the chat is open should not change the flag.
-    channel_chat_a
-        .update(cx_a, |c, cx| c.send_message("three".into(), cx).unwrap())
-        .await
-        .unwrap();
-
-    deterministic.run_until_parked();
-
-    let b_has_messages = cx_b.read_with(|cx| {
-        client_b
-            .channel_store()
-            .read(cx)
-            .has_new_messages(channel_id)
-            .unwrap()
-    });
-
-    assert!(b_has_messages);
-
-    // Closing the chat should re-enable change tracking
-    cx_b.update(|_| drop(chat_panel_b));
-
-    channel_chat_a
-        .update(cx_a, |c, cx| c.send_message("four".into(), cx).unwrap())
-        .await
-        .unwrap();
-
-    deterministic.run_until_parked();
-
-    let b_has_messages = cx_b.read_with(|cx| {
-        client_b
-            .channel_store()
-            .read(cx)
-            .has_new_messages(channel_id)
-            .unwrap()
-    });
-
-    assert!(b_has_messages);
-}
+//todo!(collab_ui)
+// #[gpui::test]
+// async fn test_channel_message_changes(
+//     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;
+
+//     let channel_id = server
+//         .make_channel(
+//             "the-channel",
+//             None,
+//             (&client_a, cx_a),
+//             &mut [(&client_b, cx_b)],
+//         )
+//         .await;
+
+//     // Client A sends a message, client B should see that there is a new message.
+//     let channel_chat_a = client_a
+//         .channel_store()
+//         .update(cx_a, |store, cx| store.open_channel_chat(channel_id, cx))
+//         .await
+//         .unwrap();
+
+//     channel_chat_a
+//         .update(cx_a, |c, cx| c.send_message("one".into(), cx).unwrap())
+//         .await
+//         .unwrap();
+
+//     executor.run_until_parked();
+
+//     let b_has_messages = cx_b.read_with(|cx| {
+//         client_b
+//             .channel_store()
+//             .read(cx)
+//             .has_new_messages(channel_id)
+//             .unwrap()
+//     });
+
+//     assert!(b_has_messages);
+
+//     // Opening the chat should clear the changed flag.
+//     cx_b.update(|cx| {
+//         collab_ui::init(&client_b.app_state, cx);
+//     });
+//     let project_b = client_b.build_empty_local_project(cx_b);
+//     let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
+//     let chat_panel_b = workspace_b.update(cx_b, |workspace, cx| ChatPanel::new(workspace, cx));
+//     chat_panel_b
+//         .update(cx_b, |chat_panel, cx| {
+//             chat_panel.set_active(true, cx);
+//             chat_panel.select_channel(channel_id, None, cx)
+//         })
+//         .await
+//         .unwrap();
+
+//     executor.run_until_parked();
+
+//     let b_has_messages = cx_b.read_with(|cx| {
+//         client_b
+//             .channel_store()
+//             .read(cx)
+//             .has_new_messages(channel_id)
+//             .unwrap()
+//     });
+
+//     assert!(!b_has_messages);
+
+//     // Sending a message while the chat is open should not change the flag.
+//     channel_chat_a
+//         .update(cx_a, |c, cx| c.send_message("two".into(), cx).unwrap())
+//         .await
+//         .unwrap();
+
+//     executor.run_until_parked();
+
+//     let b_has_messages = cx_b.read_with(|cx| {
+//         client_b
+//             .channel_store()
+//             .read(cx)
+//             .has_new_messages(channel_id)
+//             .unwrap()
+//     });
+
+//     assert!(!b_has_messages);
+
+//     // Sending a message while the chat is closed should change the flag.
+//     chat_panel_b.update(cx_b, |chat_panel, cx| {
+//         chat_panel.set_active(false, cx);
+//     });
+
+//     // Sending a message while the chat is open should not change the flag.
+//     channel_chat_a
+//         .update(cx_a, |c, cx| c.send_message("three".into(), cx).unwrap())
+//         .await
+//         .unwrap();
+
+//     executor.run_until_parked();
+
+//     let b_has_messages = cx_b.read_with(|cx| {
+//         client_b
+//             .channel_store()
+//             .read(cx)
+//             .has_new_messages(channel_id)
+//             .unwrap()
+//     });
+
+//     assert!(b_has_messages);
+
+//     // Closing the chat should re-enable change tracking
+//     cx_b.update(|_| drop(chat_panel_b));
+
+//     channel_chat_a
+//         .update(cx_a, |c, cx| c.send_message("four".into(), cx).unwrap())
+//         .await
+//         .unwrap();
+
+//     executor.run_until_parked();
+
+//     let b_has_messages = cx_b.read_with(|cx| {
+//         client_b
+//             .channel_store()
+//             .read(cx)
+//             .has_new_messages(channel_id)
+//             .unwrap()
+//     });
+
+//     assert!(b_has_messages);
+// }

crates/collab/src/tests/channel_tests.rs 🔗

@@ -7,7 +7,7 @@ use call::ActiveCall;
 use channel::{ChannelId, ChannelMembership, ChannelStore};
 use client::User;
 use futures::future::try_join_all;
-use gpui::{executor::Deterministic, ModelHandle, TestAppContext};
+use gpui::{BackgroundExecutor, Model, SharedString, TestAppContext};
 use rpc::{
     proto::{self, ChannelRole},
     RECEIVE_TIMEOUT,
@@ -16,12 +16,11 @@ use std::sync::Arc;
 
 #[gpui::test]
 async fn test_core_channels(
-    deterministic: Arc<Deterministic>,
+    executor: BackgroundExecutor,
     cx_a: &mut TestAppContext,
     cx_b: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(executor.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
 
@@ -40,28 +39,30 @@ async fn test_core_channels(
         .await
         .unwrap();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert_channels(
         client_a.channel_store(),
         cx_a,
         &[
             ExpectedChannel {
                 id: channel_a_id,
-                name: "channel-a".to_string(),
+                name: "channel-a".into(),
                 depth: 0,
                 role: ChannelRole::Admin,
             },
             ExpectedChannel {
                 id: channel_b_id,
-                name: "channel-b".to_string(),
+                name: "channel-b".into(),
                 depth: 1,
                 role: ChannelRole::Admin,
             },
         ],
     );
 
-    client_b.channel_store().read_with(cx_b, |channels, _| {
-        assert!(channels.ordered_channels().collect::<Vec<_>>().is_empty())
+    cx_b.read(|cx| {
+        client_b.channel_store().read_with(cx, |channels, _| {
+            assert!(channels.ordered_channels().collect::<Vec<_>>().is_empty())
+        })
     });
 
     // Invite client B to channel A as client A.
@@ -85,13 +86,13 @@ async fn test_core_channels(
         .unwrap();
 
     // Client A sees that B has been invited.
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert_channel_invitations(
         client_b.channel_store(),
         cx_b,
         &[ExpectedChannel {
             id: channel_a_id,
-            name: "channel-a".to_string(),
+            name: "channel-a".into(),
             depth: 0,
             role: ChannelRole::Member,
         }],
@@ -129,7 +130,7 @@ async fn test_core_channels(
         })
         .await
         .unwrap();
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
     // Client B now sees that they are a member of channel A and its existing subchannels.
     assert_channel_invitations(client_b.channel_store(), cx_b, &[]);
@@ -139,13 +140,13 @@ async fn test_core_channels(
         &[
             ExpectedChannel {
                 id: channel_a_id,
-                name: "channel-a".to_string(),
+                name: "channel-a".into(),
                 role: ChannelRole::Member,
                 depth: 0,
             },
             ExpectedChannel {
                 id: channel_b_id,
-                name: "channel-b".to_string(),
+                name: "channel-b".into(),
                 role: ChannelRole::Member,
                 depth: 1,
             },
@@ -160,26 +161,26 @@ async fn test_core_channels(
         .await
         .unwrap();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert_channels(
         client_b.channel_store(),
         cx_b,
         &[
             ExpectedChannel {
                 id: channel_a_id,
-                name: "channel-a".to_string(),
+                name: "channel-a".into(),
                 role: ChannelRole::Member,
                 depth: 0,
             },
             ExpectedChannel {
                 id: channel_b_id,
-                name: "channel-b".to_string(),
+                name: "channel-b".into(),
                 role: ChannelRole::Member,
                 depth: 1,
             },
             ExpectedChannel {
                 id: channel_c_id,
-                name: "channel-c".to_string(),
+                name: "channel-c".into(),
                 role: ChannelRole::Member,
                 depth: 2,
             },
@@ -199,7 +200,7 @@ async fn test_core_channels(
         })
         .await
         .unwrap();
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
     // Observe that client B is now an admin of channel A, and that
     // their admin priveleges extend to subchannels of channel A.
@@ -210,19 +211,19 @@ async fn test_core_channels(
         &[
             ExpectedChannel {
                 id: channel_a_id,
-                name: "channel-a".to_string(),
+                name: "channel-a".into(),
                 depth: 0,
                 role: ChannelRole::Admin,
             },
             ExpectedChannel {
                 id: channel_b_id,
-                name: "channel-b".to_string(),
+                name: "channel-b".into(),
                 depth: 1,
                 role: ChannelRole::Admin,
             },
             ExpectedChannel {
                 id: channel_c_id,
-                name: "channel-c".to_string(),
+                name: "channel-c".into(),
                 depth: 2,
                 role: ChannelRole::Admin,
             },
@@ -238,13 +239,13 @@ async fn test_core_channels(
         .await
         .unwrap();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert_channels(
         client_a.channel_store(),
         cx_a,
         &[ExpectedChannel {
             id: channel_a_id,
-            name: "channel-a".to_string(),
+            name: "channel-a".into(),
             depth: 0,
             role: ChannelRole::Admin,
         }],
@@ -254,7 +255,7 @@ async fn test_core_channels(
         cx_b,
         &[ExpectedChannel {
             id: channel_a_id,
-            name: "channel-a".to_string(),
+            name: "channel-a".into(),
             depth: 0,
             role: ChannelRole::Admin,
         }],
@@ -269,7 +270,7 @@ async fn test_core_channels(
         .await
         .unwrap();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
     // Client A still has their channel
     assert_channels(
@@ -277,7 +278,7 @@ async fn test_core_channels(
         cx_a,
         &[ExpectedChannel {
             id: channel_a_id,
-            name: "channel-a".to_string(),
+            name: "channel-a".into(),
             depth: 0,
             role: ChannelRole::Admin,
         }],
@@ -288,7 +289,7 @@ async fn test_core_channels(
 
     server.forbid_connections();
     server.disconnect_client(client_a.peer_id().unwrap());
-    deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
+    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
 
     server
         .app_state
@@ -302,13 +303,13 @@ async fn test_core_channels(
         .unwrap();
 
     server.allow_connections();
-    deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
+    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
     assert_channels(
         client_a.channel_store(),
         cx_a,
         &[ExpectedChannel {
             id: channel_a_id,
-            name: "channel-a-renamed".to_string(),
+            name: "channel-a-renamed".into(),
             depth: 0,
             role: ChannelRole::Admin,
         }],
@@ -339,12 +340,11 @@ fn assert_members_eq(
 
 #[gpui::test]
 async fn test_joining_channel_ancestor_member(
-    deterministic: Arc<Deterministic>,
+    executor: BackgroundExecutor,
     cx_a: &mut TestAppContext,
     cx_b: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(executor.clone()).await;
 
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
@@ -371,13 +371,12 @@ async fn test_joining_channel_ancestor_member(
 
 #[gpui::test]
 async fn test_channel_room(
-    deterministic: Arc<Deterministic>,
+    executor: BackgroundExecutor,
     cx_a: &mut TestAppContext,
     cx_b: &mut TestAppContext,
     cx_c: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(executor.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     let client_c = server.create_client(cx_c, "user_c").await;
@@ -400,15 +399,18 @@ async fn test_channel_room(
         .unwrap();
 
     // Give everyone a chance to observe user A joining
-    deterministic.run_until_parked();
-    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
-    room_a.read_with(cx_a, |room, _| assert!(room.is_connected()));
-
-    client_a.channel_store().read_with(cx_a, |channels, _| {
-        assert_participants_eq(
-            channels.channel_participants(zed_id),
-            &[client_a.user_id().unwrap()],
-        );
+    executor.run_until_parked();
+    let room_a =
+        cx_a.read(|cx| active_call_a.read_with(cx, |call, _| call.room().unwrap().clone()));
+    cx_a.read(|cx| room_a.read_with(cx, |room, _| assert!(room.is_connected())));
+
+    cx_a.read(|cx| {
+        client_a.channel_store().read_with(cx, |channels, _| {
+            assert_participants_eq(
+                channels.channel_participants(zed_id),
+                &[client_a.user_id().unwrap()],
+            );
+        })
     });
 
     assert_channels(
@@ -416,23 +418,27 @@ async fn test_channel_room(
         cx_b,
         &[ExpectedChannel {
             id: zed_id,
-            name: "zed".to_string(),
+            name: "zed".into(),
             depth: 0,
             role: ChannelRole::Member,
         }],
     );
-    client_b.channel_store().read_with(cx_b, |channels, _| {
-        assert_participants_eq(
-            channels.channel_participants(zed_id),
-            &[client_a.user_id().unwrap()],
-        );
+    cx_b.read(|cx| {
+        client_b.channel_store().read_with(cx, |channels, _| {
+            assert_participants_eq(
+                channels.channel_participants(zed_id),
+                &[client_a.user_id().unwrap()],
+            );
+        })
     });
 
-    client_c.channel_store().read_with(cx_c, |channels, _| {
-        assert_participants_eq(
-            channels.channel_participants(zed_id),
-            &[client_a.user_id().unwrap()],
-        );
+    cx_c.read(|cx| {
+        client_c.channel_store().read_with(cx, |channels, _| {
+            assert_participants_eq(
+                channels.channel_participants(zed_id),
+                &[client_a.user_id().unwrap()],
+            );
+        })
     });
 
     active_call_b
@@ -440,31 +446,38 @@ async fn test_channel_room(
         .await
         .unwrap();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
-    client_a.channel_store().read_with(cx_a, |channels, _| {
-        assert_participants_eq(
-            channels.channel_participants(zed_id),
-            &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
-        );
+    cx_a.read(|cx| {
+        client_a.channel_store().read_with(cx, |channels, _| {
+            assert_participants_eq(
+                channels.channel_participants(zed_id),
+                &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
+            );
+        })
     });
 
-    client_b.channel_store().read_with(cx_b, |channels, _| {
-        assert_participants_eq(
-            channels.channel_participants(zed_id),
-            &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
-        );
+    cx_b.read(|cx| {
+        client_b.channel_store().read_with(cx, |channels, _| {
+            assert_participants_eq(
+                channels.channel_participants(zed_id),
+                &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
+            );
+        })
     });
 
-    client_c.channel_store().read_with(cx_c, |channels, _| {
-        assert_participants_eq(
-            channels.channel_participants(zed_id),
-            &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
-        );
+    cx_c.read(|cx| {
+        client_c.channel_store().read_with(cx, |channels, _| {
+            assert_participants_eq(
+                channels.channel_participants(zed_id),
+                &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
+            );
+        })
     });
 
-    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
-    room_a.read_with(cx_a, |room, _| assert!(room.is_connected()));
+    let room_a =
+        cx_a.read(|cx| active_call_a.read_with(cx, |call, _| call.room().unwrap().clone()));
+    cx_a.read(|cx| room_a.read_with(cx, |room, _| assert!(room.is_connected())));
     assert_eq!(
         room_participants(&room_a, cx_a),
         RoomParticipants {
@@ -473,8 +486,9 @@ async fn test_channel_room(
         }
     );
 
-    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
-    room_b.read_with(cx_b, |room, _| assert!(room.is_connected()));
+    let room_b =
+        cx_b.read(|cx| active_call_b.read_with(cx, |call, _| call.room().unwrap().clone()));
+    cx_b.read(|cx| room_b.read_with(cx, |room, _| assert!(room.is_connected())));
     assert_eq!(
         room_participants(&room_b, cx_b),
         RoomParticipants {
@@ -490,27 +504,33 @@ async fn test_channel_room(
         .await
         .unwrap();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
-    client_a.channel_store().read_with(cx_a, |channels, _| {
-        assert_participants_eq(
-            channels.channel_participants(zed_id),
-            &[client_b.user_id().unwrap()],
-        );
+    cx_a.read(|cx| {
+        client_a.channel_store().read_with(cx, |channels, _| {
+            assert_participants_eq(
+                channels.channel_participants(zed_id),
+                &[client_b.user_id().unwrap()],
+            );
+        })
     });
 
-    client_b.channel_store().read_with(cx_b, |channels, _| {
-        assert_participants_eq(
-            channels.channel_participants(zed_id),
-            &[client_b.user_id().unwrap()],
-        );
+    cx_b.read(|cx| {
+        client_b.channel_store().read_with(cx, |channels, _| {
+            assert_participants_eq(
+                channels.channel_participants(zed_id),
+                &[client_b.user_id().unwrap()],
+            );
+        })
     });
 
-    client_c.channel_store().read_with(cx_c, |channels, _| {
-        assert_participants_eq(
-            channels.channel_participants(zed_id),
-            &[client_b.user_id().unwrap()],
-        );
+    cx_c.read(|cx| {
+        client_c.channel_store().read_with(cx, |channels, _| {
+            assert_participants_eq(
+                channels.channel_participants(zed_id),
+                &[client_b.user_id().unwrap()],
+            );
+        })
     });
 
     active_call_b
@@ -518,18 +538,24 @@ async fn test_channel_room(
         .await
         .unwrap();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
-    client_a.channel_store().read_with(cx_a, |channels, _| {
-        assert_participants_eq(channels.channel_participants(zed_id), &[]);
+    cx_a.read(|cx| {
+        client_a.channel_store().read_with(cx, |channels, _| {
+            assert_participants_eq(channels.channel_participants(zed_id), &[]);
+        })
     });
 
-    client_b.channel_store().read_with(cx_b, |channels, _| {
-        assert_participants_eq(channels.channel_participants(zed_id), &[]);
+    cx_b.read(|cx| {
+        client_b.channel_store().read_with(cx, |channels, _| {
+            assert_participants_eq(channels.channel_participants(zed_id), &[]);
+        })
     });
 
-    client_c.channel_store().read_with(cx_c, |channels, _| {
-        assert_participants_eq(channels.channel_participants(zed_id), &[]);
+    cx_c.read(|cx| {
+        client_c.channel_store().read_with(cx, |channels, _| {
+            assert_participants_eq(channels.channel_participants(zed_id), &[]);
+        })
     });
 
     active_call_a
@@ -542,10 +568,11 @@ async fn test_channel_room(
         .await
         .unwrap();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
-    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
-    room_a.read_with(cx_a, |room, _| assert!(room.is_connected()));
+    let room_a =
+        cx_a.read(|cx| active_call_a.read_with(cx, |call, _| call.room().unwrap().clone()));
+    cx_a.read(|cx| room_a.read_with(cx, |room, _| assert!(room.is_connected())));
     assert_eq!(
         room_participants(&room_a, cx_a),
         RoomParticipants {
@@ -554,8 +581,9 @@ async fn test_channel_room(
         }
     );
 
-    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
-    room_b.read_with(cx_b, |room, _| assert!(room.is_connected()));
+    let room_b =
+        cx_b.read(|cx| active_call_b.read_with(cx, |call, _| call.room().unwrap().clone()));
+    cx_b.read(|cx| room_b.read_with(cx, |room, _| assert!(room.is_connected())));
     assert_eq!(
         room_participants(&room_b, cx_b),
         RoomParticipants {
@@ -566,9 +594,8 @@ async fn test_channel_room(
 }
 
 #[gpui::test]
-async fn test_channel_jumping(deterministic: Arc<Deterministic>, cx_a: &mut TestAppContext) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+async fn test_channel_jumping(executor: BackgroundExecutor, cx_a: &mut TestAppContext) {
+    let mut server = TestServer::start(executor.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
 
     let zed_id = server
@@ -586,14 +613,16 @@ async fn test_channel_jumping(deterministic: Arc<Deterministic>, cx_a: &mut Test
         .unwrap();
 
     // Give everything a chance to observe user A joining
-    deterministic.run_until_parked();
-
-    client_a.channel_store().read_with(cx_a, |channels, _| {
-        assert_participants_eq(
-            channels.channel_participants(zed_id),
-            &[client_a.user_id().unwrap()],
-        );
-        assert_participants_eq(channels.channel_participants(rust_id), &[]);
+    executor.run_until_parked();
+
+    cx_a.read(|cx| {
+        client_a.channel_store().read_with(cx, |channels, _| {
+            assert_participants_eq(
+                channels.channel_participants(zed_id),
+                &[client_a.user_id().unwrap()],
+            );
+            assert_participants_eq(channels.channel_participants(rust_id), &[]);
+        })
     });
 
     active_call_a
@@ -603,25 +632,26 @@ async fn test_channel_jumping(deterministic: Arc<Deterministic>, cx_a: &mut Test
         .await
         .unwrap();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
-    client_a.channel_store().read_with(cx_a, |channels, _| {
-        assert_participants_eq(channels.channel_participants(zed_id), &[]);
-        assert_participants_eq(
-            channels.channel_participants(rust_id),
-            &[client_a.user_id().unwrap()],
-        );
+    cx_a.read(|cx| {
+        client_a.channel_store().read_with(cx, |channels, _| {
+            assert_participants_eq(channels.channel_participants(zed_id), &[]);
+            assert_participants_eq(
+                channels.channel_participants(rust_id),
+                &[client_a.user_id().unwrap()],
+            );
+        })
     });
 }
 
 #[gpui::test]
 async fn test_permissions_update_while_invited(
-    deterministic: Arc<Deterministic>,
+    executor: BackgroundExecutor,
     cx_a: &mut TestAppContext,
     cx_b: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(executor.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
 
@@ -642,7 +672,7 @@ async fn test_permissions_update_while_invited(
         .await
         .unwrap();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
     assert_channel_invitations(
         client_b.channel_store(),
@@ -650,7 +680,7 @@ async fn test_permissions_update_while_invited(
         &[ExpectedChannel {
             depth: 0,
             id: rust_id,
-            name: "rust".to_string(),
+            name: "rust".into(),
             role: ChannelRole::Member,
         }],
     );
@@ -670,7 +700,7 @@ async fn test_permissions_update_while_invited(
         .await
         .unwrap();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
     assert_channel_invitations(
         client_b.channel_store(),
@@ -678,7 +708,7 @@ async fn test_permissions_update_while_invited(
         &[ExpectedChannel {
             depth: 0,
             id: rust_id,
-            name: "rust".to_string(),
+            name: "rust".into(),
             role: ChannelRole::Member,
         }],
     );
@@ -687,12 +717,11 @@ async fn test_permissions_update_while_invited(
 
 #[gpui::test]
 async fn test_channel_rename(
-    deterministic: Arc<Deterministic>,
+    executor: BackgroundExecutor,
     cx_a: &mut TestAppContext,
     cx_b: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(executor.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
 
@@ -709,7 +738,7 @@ async fn test_channel_rename(
         .await
         .unwrap();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
     // Client A sees the channel with its new name.
     assert_channels(
@@ -718,7 +747,7 @@ async fn test_channel_rename(
         &[ExpectedChannel {
             depth: 0,
             id: rust_id,
-            name: "rust-archive".to_string(),
+            name: "rust-archive".into(),
             role: ChannelRole::Admin,
         }],
     );
@@ -730,7 +759,7 @@ async fn test_channel_rename(
         &[ExpectedChannel {
             depth: 0,
             id: rust_id,
-            name: "rust-archive".to_string(),
+            name: "rust-archive".into(),
             role: ChannelRole::Member,
         }],
     );
@@ -738,13 +767,12 @@ async fn test_channel_rename(
 
 #[gpui::test]
 async fn test_call_from_channel(
-    deterministic: Arc<Deterministic>,
+    executor: BackgroundExecutor,
     cx_a: &mut TestAppContext,
     cx_b: &mut TestAppContext,
     cx_c: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(executor.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     let client_c = server.create_client(cx_c, "user_c").await;
@@ -778,47 +806,54 @@ async fn test_call_from_channel(
         .unwrap();
 
     // Client B accepts the call.
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     active_call_b
         .update(cx_b, |call, cx| call.accept_incoming(cx))
         .await
         .unwrap();
 
     // Client B sees that they are now in the channel
-    deterministic.run_until_parked();
-    active_call_b.read_with(cx_b, |call, cx| {
-        assert_eq!(call.channel_id(cx), Some(channel_id));
+    executor.run_until_parked();
+    cx_b.read(|cx| {
+        active_call_b.read_with(cx, |call, cx| {
+            assert_eq!(call.channel_id(cx), Some(channel_id));
+        })
     });
-    client_b.channel_store().read_with(cx_b, |channels, _| {
-        assert_participants_eq(
-            channels.channel_participants(channel_id),
-            &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
-        );
+    cx_b.read(|cx| {
+        client_b.channel_store().read_with(cx, |channels, _| {
+            assert_participants_eq(
+                channels.channel_participants(channel_id),
+                &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
+            );
+        })
     });
 
     // Clients A and C also see that client B is in the channel.
-    client_a.channel_store().read_with(cx_a, |channels, _| {
-        assert_participants_eq(
-            channels.channel_participants(channel_id),
-            &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
-        );
+    cx_a.read(|cx| {
+        client_a.channel_store().read_with(cx, |channels, _| {
+            assert_participants_eq(
+                channels.channel_participants(channel_id),
+                &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
+            );
+        })
     });
-    client_c.channel_store().read_with(cx_c, |channels, _| {
-        assert_participants_eq(
-            channels.channel_participants(channel_id),
-            &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
-        );
+    cx_c.read(|cx| {
+        client_c.channel_store().read_with(cx, |channels, _| {
+            assert_participants_eq(
+                channels.channel_participants(channel_id),
+                &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
+            );
+        })
     });
 }
 
 #[gpui::test]
 async fn test_lost_channel_creation(
-    deterministic: Arc<Deterministic>,
+    executor: BackgroundExecutor,
     cx_a: &mut TestAppContext,
     cx_b: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(executor.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
 
@@ -844,7 +879,7 @@ async fn test_lost_channel_creation(
         .await
         .unwrap();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
     // Sanity check, B has the invitation
     assert_channel_invitations(
@@ -853,7 +888,7 @@ async fn test_lost_channel_creation(
         &[ExpectedChannel {
             depth: 0,
             id: channel_id,
-            name: "x".to_string(),
+            name: "x".into(),
             role: ChannelRole::Member,
         }],
     );
@@ -867,7 +902,7 @@ async fn test_lost_channel_creation(
         .await
         .unwrap();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
     // Make sure A sees their new channel
     assert_channels(
@@ -877,13 +912,13 @@ async fn test_lost_channel_creation(
             ExpectedChannel {
                 depth: 0,
                 id: channel_id,
-                name: "x".to_string(),
+                name: "x".into(),
                 role: ChannelRole::Admin,
             },
             ExpectedChannel {
                 depth: 1,
                 id: subchannel_id,
-                name: "subchannel".to_string(),
+                name: "subchannel".into(),
                 role: ChannelRole::Admin,
             },
         ],
@@ -898,7 +933,7 @@ async fn test_lost_channel_creation(
         .await
         .unwrap();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
     // Client B should now see the channel
     assert_channels(
@@ -908,13 +943,13 @@ async fn test_lost_channel_creation(
             ExpectedChannel {
                 depth: 0,
                 id: channel_id,
-                name: "x".to_string(),
+                name: "x".into(),
                 role: ChannelRole::Member,
             },
             ExpectedChannel {
                 depth: 1,
                 id: subchannel_id,
-                name: "subchannel".to_string(),
+                name: "subchannel".into(),
                 role: ChannelRole::Member,
             },
         ],
@@ -923,14 +958,12 @@ async fn test_lost_channel_creation(
 
 #[gpui::test]
 async fn test_channel_link_notifications(
-    deterministic: Arc<Deterministic>,
+    executor: BackgroundExecutor,
     cx_a: &mut TestAppContext,
     cx_b: &mut TestAppContext,
     cx_c: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(executor.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     let client_c = server.create_client(cx_c, "user_c").await;
@@ -953,7 +986,7 @@ async fn test_channel_link_notifications(
     .await
     .unwrap();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
     client_b
         .channel_store()
@@ -971,7 +1004,7 @@ async fn test_channel_link_notifications(
         .await
         .unwrap();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
     // we have an admin (a), member (b) and guest (c) all part of the zed channel.
 
@@ -984,6 +1017,8 @@ async fn test_channel_link_notifications(
         .await
         .unwrap();
 
+    executor.run_until_parked();
+
     // the new channel shows for b and not c
     assert_channels_list_shape(
         client_a.channel_store(),
@@ -1021,7 +1056,7 @@ async fn test_channel_link_notifications(
         .await
         .unwrap();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
     // the new channel shows for b and c
     assert_channels_list_shape(
@@ -1093,6 +1128,8 @@ async fn test_channel_link_notifications(
         .await
         .unwrap();
 
+    executor.run_until_parked();
+
     // the members-only channel is still shown for c, but hidden for b
     assert_channels_list_shape(
         client_b.channel_store(),
@@ -1104,9 +1141,8 @@ async fn test_channel_link_notifications(
             (helix_channel, 3),
         ],
     );
-    client_b
-        .channel_store()
-        .read_with(cx_b, |channel_store, _| {
+    cx_b.read(|cx| {
+        client_b.channel_store().read_with(cx, |channel_store, _| {
             assert_eq!(
                 channel_store
                     .channel_for_id(vim_channel)
@@ -1114,22 +1150,19 @@ async fn test_channel_link_notifications(
                     .visibility,
                 proto::ChannelVisibility::Members
             )
-        });
+        })
+    });
 
     assert_channels_list_shape(client_c.channel_store(), cx_c, &[(zed_channel, 0)]);
 }
 
 #[gpui::test]
 async fn test_channel_membership_notifications(
-    deterministic: Arc<Deterministic>,
+    executor: BackgroundExecutor,
     cx_a: &mut TestAppContext,
     cx_b: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-
-    deterministic.forbid_parking();
-
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(executor.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_c").await;
 
@@ -1160,7 +1193,7 @@ async fn test_channel_membership_notifications(
     .await
     .unwrap();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
     client_b
         .channel_store()
@@ -1178,7 +1211,7 @@ async fn test_channel_membership_notifications(
         .await
         .unwrap();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
     // we have an admin (a), and a guest (b) with access to all of zed, and membership in vim.
     assert_channels(
@@ -1188,13 +1221,13 @@ async fn test_channel_membership_notifications(
             ExpectedChannel {
                 depth: 0,
                 id: zed_channel,
-                name: "zed".to_string(),
+                name: "zed".into(),
                 role: ChannelRole::Guest,
             },
             ExpectedChannel {
                 depth: 1,
                 id: vim_channel,
-                name: "vim".to_string(),
+                name: "vim".into(),
                 role: ChannelRole::Member,
             },
         ],
@@ -1208,7 +1241,7 @@ async fn test_channel_membership_notifications(
         .await
         .unwrap();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
     assert_channels(
         client_b.channel_store(),
@@ -1217,13 +1250,13 @@ async fn test_channel_membership_notifications(
             ExpectedChannel {
                 depth: 0,
                 id: zed_channel,
-                name: "zed".to_string(),
+                name: "zed".into(),
                 role: ChannelRole::Guest,
             },
             ExpectedChannel {
                 depth: 1,
                 id: vim_channel,
-                name: "vim".to_string(),
+                name: "vim".into(),
                 role: ChannelRole::Guest,
             },
         ],
@@ -1232,13 +1265,11 @@ async fn test_channel_membership_notifications(
 
 #[gpui::test]
 async fn test_guest_access(
-    deterministic: Arc<Deterministic>,
+    executor: BackgroundExecutor,
     cx_a: &mut TestAppContext,
     cx_b: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(executor.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
 
@@ -1281,7 +1312,7 @@ async fn test_guest_access(
         .await
         .unwrap();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert_channels_list_shape(
         client_a.channel_store(),
         cx_a,

crates/collab/src/tests/following_tests.rs 🔗

@@ -1,1820 +1,1890 @@
-use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
-use call::ActiveCall;
-use collab_ui::notifications::project_shared_notification::ProjectSharedNotification;
-use editor::{Editor, ExcerptRange, MultiBuffer};
-use gpui::{executor::Deterministic, geometry::vector::vec2f, TestAppContext, ViewHandle};
-use live_kit_client::MacOSDisplay;
-use project::project_settings::ProjectSettings;
-use rpc::proto::PeerId;
-use serde_json::json;
-use settings::SettingsStore;
-use std::{borrow::Cow, sync::Arc};
-use workspace::{
-    dock::{test::TestPanel, DockPosition},
-    item::{test::TestItem, ItemHandle as _},
-    shared_screen::SharedScreen,
-    SplitDirection, Workspace,
-};
-
-#[gpui::test(iterations = 10)]
-async fn test_basic_following(
-    deterministic: Arc<Deterministic>,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-    cx_c: &mut TestAppContext,
-    cx_d: &mut TestAppContext,
-) {
-    deterministic.forbid_parking();
-
-    let mut server = TestServer::start(&deterministic).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    let client_c = server.create_client(cx_c, "user_c").await;
-    let client_d = server.create_client(cx_d, "user_d").await;
-    server
-        .create_room(&mut [
-            (&client_a, cx_a),
-            (&client_b, cx_b),
-            (&client_c, cx_c),
-            (&client_d, cx_d),
-        ])
-        .await;
-    let active_call_a = cx_a.read(ActiveCall::global);
-    let active_call_b = cx_b.read(ActiveCall::global);
-
-    cx_a.update(editor::init);
-    cx_b.update(editor::init);
-
-    client_a
-        .fs()
-        .insert_tree(
-            "/a",
-            json!({
-                "1.txt": "one\none\none",
-                "2.txt": "two\ntwo\ntwo",
-                "3.txt": "three\nthree\nthree",
-            }),
-        )
-        .await;
-    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
-    active_call_a
-        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
-        .await
-        .unwrap();
-
-    let project_id = active_call_a
-        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-        .await
-        .unwrap();
-    let project_b = client_b.build_remote_project(project_id, cx_b).await;
-    active_call_b
-        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
-        .await
-        .unwrap();
-
-    let window_a = client_a.build_workspace(&project_a, cx_a);
-    let workspace_a = window_a.root(cx_a);
-    let window_b = client_b.build_workspace(&project_b, cx_b);
-    let workspace_b = window_b.root(cx_b);
-
-    // Client A opens some editors.
-    let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
-    let editor_a1 = workspace_a
-        .update(cx_a, |workspace, cx| {
-            workspace.open_path((worktree_id, "1.txt"), None, true, cx)
-        })
-        .await
-        .unwrap()
-        .downcast::<Editor>()
-        .unwrap();
-    let editor_a2 = workspace_a
-        .update(cx_a, |workspace, cx| {
-            workspace.open_path((worktree_id, "2.txt"), None, true, cx)
-        })
-        .await
-        .unwrap()
-        .downcast::<Editor>()
-        .unwrap();
-
-    // Client B opens an editor.
-    let editor_b1 = workspace_b
-        .update(cx_b, |workspace, cx| {
-            workspace.open_path((worktree_id, "1.txt"), None, true, cx)
-        })
-        .await
-        .unwrap()
-        .downcast::<Editor>()
-        .unwrap();
-
-    let peer_id_a = client_a.peer_id().unwrap();
-    let peer_id_b = client_b.peer_id().unwrap();
-    let peer_id_c = client_c.peer_id().unwrap();
-    let peer_id_d = client_d.peer_id().unwrap();
-
-    // Client A updates their selections in those editors
-    editor_a1.update(cx_a, |editor, cx| {
-        editor.handle_input("a", cx);
-        editor.handle_input("b", cx);
-        editor.handle_input("c", cx);
-        editor.select_left(&Default::default(), cx);
-        assert_eq!(editor.selections.ranges(cx), vec![3..2]);
-    });
-    editor_a2.update(cx_a, |editor, cx| {
-        editor.handle_input("d", cx);
-        editor.handle_input("e", cx);
-        editor.select_left(&Default::default(), cx);
-        assert_eq!(editor.selections.ranges(cx), vec![2..1]);
-    });
-
-    // When client B starts following client A, all visible view states are replicated to client B.
-    workspace_b
-        .update(cx_b, |workspace, cx| {
-            workspace.follow(peer_id_a, cx).unwrap()
-        })
-        .await
-        .unwrap();
-
-    cx_c.foreground().run_until_parked();
-    let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
-        workspace
-            .active_item(cx)
-            .unwrap()
-            .downcast::<Editor>()
-            .unwrap()
-    });
-    assert_eq!(
-        cx_b.read(|cx| editor_b2.project_path(cx)),
-        Some((worktree_id, "2.txt").into())
-    );
-    assert_eq!(
-        editor_b2.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
-        vec![2..1]
-    );
-    assert_eq!(
-        editor_b1.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
-        vec![3..2]
-    );
-
-    cx_c.foreground().run_until_parked();
-    let active_call_c = cx_c.read(ActiveCall::global);
-    let project_c = client_c.build_remote_project(project_id, cx_c).await;
-    let window_c = client_c.build_workspace(&project_c, cx_c);
-    let workspace_c = window_c.root(cx_c);
-    active_call_c
-        .update(cx_c, |call, cx| call.set_location(Some(&project_c), cx))
-        .await
-        .unwrap();
-    drop(project_c);
-
-    // Client C also follows client A.
-    workspace_c
-        .update(cx_c, |workspace, cx| {
-            workspace.follow(peer_id_a, cx).unwrap()
-        })
-        .await
-        .unwrap();
-
-    cx_d.foreground().run_until_parked();
-    let active_call_d = cx_d.read(ActiveCall::global);
-    let project_d = client_d.build_remote_project(project_id, cx_d).await;
-    let workspace_d = client_d.build_workspace(&project_d, cx_d).root(cx_d);
-    active_call_d
-        .update(cx_d, |call, cx| call.set_location(Some(&project_d), cx))
-        .await
-        .unwrap();
-    drop(project_d);
-
-    // All clients see that clients B and C are following client A.
-    cx_c.foreground().run_until_parked();
-    for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
-        assert_eq!(
-            followers_by_leader(project_id, cx),
-            &[(peer_id_a, vec![peer_id_b, peer_id_c])],
-            "followers seen by {name}"
-        );
-    }
-
-    // Client C unfollows client A.
-    workspace_c.update(cx_c, |workspace, cx| {
-        workspace.unfollow(&workspace.active_pane().clone(), cx);
-    });
-
-    // All clients see that clients B is following client A.
-    cx_c.foreground().run_until_parked();
-    for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
-        assert_eq!(
-            followers_by_leader(project_id, cx),
-            &[(peer_id_a, vec![peer_id_b])],
-            "followers seen by {name}"
-        );
-    }
-
-    // Client C re-follows client A.
-    workspace_c
-        .update(cx_c, |workspace, cx| {
-            workspace.follow(peer_id_a, cx).unwrap()
-        })
-        .await
-        .unwrap();
-
-    // All clients see that clients B and C are following client A.
-    cx_c.foreground().run_until_parked();
-    for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
-        assert_eq!(
-            followers_by_leader(project_id, cx),
-            &[(peer_id_a, vec![peer_id_b, peer_id_c])],
-            "followers seen by {name}"
-        );
-    }
-
-    // Client D follows client B, then switches to following client C.
-    workspace_d
-        .update(cx_d, |workspace, cx| {
-            workspace.follow(peer_id_b, cx).unwrap()
-        })
-        .await
-        .unwrap();
-    workspace_d
-        .update(cx_d, |workspace, cx| {
-            workspace.follow(peer_id_c, cx).unwrap()
-        })
-        .await
-        .unwrap();
-
-    // All clients see that D is following C
-    cx_d.foreground().run_until_parked();
-    for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
-        assert_eq!(
-            followers_by_leader(project_id, cx),
-            &[
-                (peer_id_a, vec![peer_id_b, peer_id_c]),
-                (peer_id_c, vec![peer_id_d])
-            ],
-            "followers seen by {name}"
-        );
-    }
-
-    // Client C closes the project.
-    window_c.remove(cx_c);
-    cx_c.drop_last(workspace_c);
-
-    // Clients A and B see that client B is following A, and client C is not present in the followers.
-    cx_c.foreground().run_until_parked();
-    for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
-        assert_eq!(
-            followers_by_leader(project_id, cx),
-            &[(peer_id_a, vec![peer_id_b]),],
-            "followers seen by {name}"
-        );
-    }
-
-    // When client A activates a different editor, client B does so as well.
-    workspace_a.update(cx_a, |workspace, cx| {
-        workspace.activate_item(&editor_a1, cx)
-    });
-    deterministic.run_until_parked();
-    workspace_b.read_with(cx_b, |workspace, cx| {
-        assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b1.id());
-    });
-
-    // When client A opens a multibuffer, client B does so as well.
-    let multibuffer_a = cx_a.add_model(|cx| {
-        let buffer_a1 = project_a.update(cx, |project, cx| {
-            project
-                .get_open_buffer(&(worktree_id, "1.txt").into(), cx)
-                .unwrap()
-        });
-        let buffer_a2 = project_a.update(cx, |project, cx| {
-            project
-                .get_open_buffer(&(worktree_id, "2.txt").into(), cx)
-                .unwrap()
-        });
-        let mut result = MultiBuffer::new(0);
-        result.push_excerpts(
-            buffer_a1,
-            [ExcerptRange {
-                context: 0..3,
-                primary: None,
-            }],
-            cx,
-        );
-        result.push_excerpts(
-            buffer_a2,
-            [ExcerptRange {
-                context: 4..7,
-                primary: None,
-            }],
-            cx,
-        );
-        result
-    });
-    let multibuffer_editor_a = workspace_a.update(cx_a, |workspace, cx| {
-        let editor =
-            cx.add_view(|cx| Editor::for_multibuffer(multibuffer_a, Some(project_a.clone()), cx));
-        workspace.add_item(Box::new(editor.clone()), cx);
-        editor
-    });
-    deterministic.run_until_parked();
-    let multibuffer_editor_b = workspace_b.read_with(cx_b, |workspace, cx| {
-        workspace
-            .active_item(cx)
-            .unwrap()
-            .downcast::<Editor>()
-            .unwrap()
-    });
-    assert_eq!(
-        multibuffer_editor_a.read_with(cx_a, |editor, cx| editor.text(cx)),
-        multibuffer_editor_b.read_with(cx_b, |editor, cx| editor.text(cx)),
-    );
-
-    // When client A navigates back and forth, client B does so as well.
-    workspace_a
-        .update(cx_a, |workspace, cx| {
-            workspace.go_back(workspace.active_pane().downgrade(), cx)
-        })
-        .await
-        .unwrap();
-    deterministic.run_until_parked();
-    workspace_b.read_with(cx_b, |workspace, cx| {
-        assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b1.id());
-    });
-
-    workspace_a
-        .update(cx_a, |workspace, cx| {
-            workspace.go_back(workspace.active_pane().downgrade(), cx)
-        })
-        .await
-        .unwrap();
-    deterministic.run_until_parked();
-    workspace_b.read_with(cx_b, |workspace, cx| {
-        assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b2.id());
-    });
-
-    workspace_a
-        .update(cx_a, |workspace, cx| {
-            workspace.go_forward(workspace.active_pane().downgrade(), cx)
-        })
-        .await
-        .unwrap();
-    deterministic.run_until_parked();
-    workspace_b.read_with(cx_b, |workspace, cx| {
-        assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b1.id());
-    });
-
-    // Changes to client A's editor are reflected on client B.
-    editor_a1.update(cx_a, |editor, cx| {
-        editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2]));
-    });
-    deterministic.run_until_parked();
-    editor_b1.read_with(cx_b, |editor, cx| {
-        assert_eq!(editor.selections.ranges(cx), &[1..1, 2..2]);
-    });
-
-    editor_a1.update(cx_a, |editor, cx| editor.set_text("TWO", cx));
-    deterministic.run_until_parked();
-    editor_b1.read_with(cx_b, |editor, cx| assert_eq!(editor.text(cx), "TWO"));
-
-    editor_a1.update(cx_a, |editor, cx| {
-        editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
-        editor.set_scroll_position(vec2f(0., 100.), cx);
-    });
-    deterministic.run_until_parked();
-    editor_b1.read_with(cx_b, |editor, cx| {
-        assert_eq!(editor.selections.ranges(cx), &[3..3]);
-    });
-
-    // After unfollowing, client B stops receiving updates from client A.
-    workspace_b.update(cx_b, |workspace, cx| {
-        workspace.unfollow(&workspace.active_pane().clone(), cx)
-    });
-    workspace_a.update(cx_a, |workspace, cx| {
-        workspace.activate_item(&editor_a2, cx)
-    });
-    deterministic.run_until_parked();
-    assert_eq!(
-        workspace_b.read_with(cx_b, |workspace, cx| workspace
-            .active_item(cx)
-            .unwrap()
-            .id()),
-        editor_b1.id()
-    );
-
-    // Client A starts following client B.
-    workspace_a
-        .update(cx_a, |workspace, cx| {
-            workspace.follow(peer_id_b, cx).unwrap()
-        })
-        .await
-        .unwrap();
-    assert_eq!(
-        workspace_a.read_with(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
-        Some(peer_id_b)
-    );
-    assert_eq!(
-        workspace_a.read_with(cx_a, |workspace, cx| workspace
-            .active_item(cx)
-            .unwrap()
-            .id()),
-        editor_a1.id()
-    );
-
-    // Client B activates an external window, which causes a new screen-sharing item to be added to the pane.
-    let display = MacOSDisplay::new();
-    active_call_b
-        .update(cx_b, |call, cx| call.set_location(None, cx))
-        .await
-        .unwrap();
-    active_call_b
-        .update(cx_b, |call, cx| {
-            call.room().unwrap().update(cx, |room, cx| {
-                room.set_display_sources(vec![display.clone()]);
-                room.share_screen(cx)
-            })
-        })
-        .await
-        .unwrap();
-    deterministic.run_until_parked();
-    let shared_screen = workspace_a.read_with(cx_a, |workspace, cx| {
-        workspace
-            .active_item(cx)
-            .expect("no active item")
-            .downcast::<SharedScreen>()
-            .expect("active item isn't a shared screen")
-    });
-
-    // Client B activates Zed again, which causes the previous editor to become focused again.
-    active_call_b
-        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
-        .await
-        .unwrap();
-    deterministic.run_until_parked();
-    workspace_a.read_with(cx_a, |workspace, cx| {
-        assert_eq!(workspace.active_item(cx).unwrap().id(), editor_a1.id())
-    });
-
-    // Client B activates a multibuffer that was created by following client A. Client A returns to that multibuffer.
-    workspace_b.update(cx_b, |workspace, cx| {
-        workspace.activate_item(&multibuffer_editor_b, cx)
-    });
-    deterministic.run_until_parked();
-    workspace_a.read_with(cx_a, |workspace, cx| {
-        assert_eq!(
-            workspace.active_item(cx).unwrap().id(),
-            multibuffer_editor_a.id()
-        )
-    });
-
-    // Client B activates a panel, and the previously-opened screen-sharing item gets activated.
-    let panel = window_b.add_view(cx_b, |_| TestPanel::new(DockPosition::Left));
-    workspace_b.update(cx_b, |workspace, cx| {
-        workspace.add_panel(panel, cx);
-        workspace.toggle_panel_focus::<TestPanel>(cx);
-    });
-    deterministic.run_until_parked();
-    assert_eq!(
-        workspace_a.read_with(cx_a, |workspace, cx| workspace
-            .active_item(cx)
-            .unwrap()
-            .id()),
-        shared_screen.id()
-    );
-
-    // Toggling the focus back to the pane causes client A to return to the multibuffer.
-    workspace_b.update(cx_b, |workspace, cx| {
-        workspace.toggle_panel_focus::<TestPanel>(cx);
-    });
-    deterministic.run_until_parked();
-    workspace_a.read_with(cx_a, |workspace, cx| {
-        assert_eq!(
-            workspace.active_item(cx).unwrap().id(),
-            multibuffer_editor_a.id()
-        )
-    });
-
-    // Client B activates an item that doesn't implement following,
-    // so the previously-opened screen-sharing item gets activated.
-    let unfollowable_item = window_b.add_view(cx_b, |_| TestItem::new());
-    workspace_b.update(cx_b, |workspace, cx| {
-        workspace.active_pane().update(cx, |pane, cx| {
-            pane.add_item(Box::new(unfollowable_item), true, true, None, cx)
-        })
-    });
-    deterministic.run_until_parked();
-    assert_eq!(
-        workspace_a.read_with(cx_a, |workspace, cx| workspace
-            .active_item(cx)
-            .unwrap()
-            .id()),
-        shared_screen.id()
-    );
-
-    // Following interrupts when client B disconnects.
-    client_b.disconnect(&cx_b.to_async());
-    deterministic.advance_clock(RECONNECT_TIMEOUT);
-    assert_eq!(
-        workspace_a.read_with(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
-        None
-    );
-}
-
-#[gpui::test]
-async fn test_following_tab_order(
-    deterministic: Arc<Deterministic>,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(&deterministic).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    server
-        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-        .await;
-    let active_call_a = cx_a.read(ActiveCall::global);
-    let active_call_b = cx_b.read(ActiveCall::global);
-
-    cx_a.update(editor::init);
-    cx_b.update(editor::init);
-
-    client_a
-        .fs()
-        .insert_tree(
-            "/a",
-            json!({
-                "1.txt": "one",
-                "2.txt": "two",
-                "3.txt": "three",
-            }),
-        )
-        .await;
-    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
-    active_call_a
-        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
-        .await
-        .unwrap();
-
-    let project_id = active_call_a
-        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-        .await
-        .unwrap();
-    let project_b = client_b.build_remote_project(project_id, cx_b).await;
-    active_call_b
-        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
-        .await
-        .unwrap();
-
-    let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
-    let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
-
-    let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
-    let pane_b = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
-
-    let client_b_id = project_a.read_with(cx_a, |project, _| {
-        project.collaborators().values().next().unwrap().peer_id
-    });
-
-    //Open 1, 3 in that order on client A
-    workspace_a
-        .update(cx_a, |workspace, cx| {
-            workspace.open_path((worktree_id, "1.txt"), None, true, cx)
-        })
-        .await
-        .unwrap();
-    workspace_a
-        .update(cx_a, |workspace, cx| {
-            workspace.open_path((worktree_id, "3.txt"), None, true, cx)
-        })
-        .await
-        .unwrap();
-
-    let pane_paths = |pane: &ViewHandle<workspace::Pane>, cx: &mut TestAppContext| {
-        pane.update(cx, |pane, cx| {
-            pane.items()
-                .map(|item| {
-                    item.project_path(cx)
-                        .unwrap()
-                        .path
-                        .to_str()
-                        .unwrap()
-                        .to_owned()
-                })
-                .collect::<Vec<_>>()
-        })
-    };
-
-    //Verify that the tabs opened in the order we expect
-    assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt"]);
-
-    //Follow client B as client A
-    workspace_a
-        .update(cx_a, |workspace, cx| {
-            workspace.follow(client_b_id, cx).unwrap()
-        })
-        .await
-        .unwrap();
-
-    //Open just 2 on client B
-    workspace_b
-        .update(cx_b, |workspace, cx| {
-            workspace.open_path((worktree_id, "2.txt"), None, true, cx)
-        })
-        .await
-        .unwrap();
-    deterministic.run_until_parked();
-
-    // Verify that newly opened followed file is at the end
-    assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt", "2.txt"]);
-
-    //Open just 1 on client B
-    workspace_b
-        .update(cx_b, |workspace, cx| {
-            workspace.open_path((worktree_id, "1.txt"), None, true, cx)
-        })
-        .await
-        .unwrap();
-    assert_eq!(&pane_paths(&pane_b, cx_b), &["2.txt", "1.txt"]);
-    deterministic.run_until_parked();
-
-    // Verify that following into 1 did not reorder
-    assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt", "2.txt"]);
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_peers_following_each_other(
-    deterministic: Arc<Deterministic>,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    server
-        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-        .await;
-    let active_call_a = cx_a.read(ActiveCall::global);
-    let active_call_b = cx_b.read(ActiveCall::global);
-
-    cx_a.update(editor::init);
-    cx_b.update(editor::init);
-
-    // Client A shares a project.
-    client_a
-        .fs()
-        .insert_tree(
-            "/a",
-            json!({
-                "1.txt": "one",
-                "2.txt": "two",
-                "3.txt": "three",
-                "4.txt": "four",
-            }),
-        )
-        .await;
-    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
-    active_call_a
-        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
-        .await
-        .unwrap();
-    let project_id = active_call_a
-        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-        .await
-        .unwrap();
-
-    // Client B joins the project.
-    let project_b = client_b.build_remote_project(project_id, cx_b).await;
-    active_call_b
-        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
-        .await
-        .unwrap();
-
-    // Client A opens a file.
-    let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
-    workspace_a
-        .update(cx_a, |workspace, cx| {
-            workspace.open_path((worktree_id, "1.txt"), None, true, cx)
-        })
-        .await
-        .unwrap()
-        .downcast::<Editor>()
-        .unwrap();
-
-    // Client B opens a different file.
-    let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
-    workspace_b
-        .update(cx_b, |workspace, cx| {
-            workspace.open_path((worktree_id, "2.txt"), None, true, cx)
-        })
-        .await
-        .unwrap()
-        .downcast::<Editor>()
-        .unwrap();
-
-    // Clients A and B follow each other in split panes
-    workspace_a.update(cx_a, |workspace, cx| {
-        workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx);
-    });
-    workspace_a
-        .update(cx_a, |workspace, cx| {
-            workspace.follow(client_b.peer_id().unwrap(), cx).unwrap()
-        })
-        .await
-        .unwrap();
-    workspace_b.update(cx_b, |workspace, cx| {
-        workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx);
-    });
-    workspace_b
-        .update(cx_b, |workspace, cx| {
-            workspace.follow(client_a.peer_id().unwrap(), cx).unwrap()
-        })
-        .await
-        .unwrap();
-
-    // Clients A and B return focus to the original files they had open
-    workspace_a.update(cx_a, |workspace, cx| workspace.activate_next_pane(cx));
-    workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
-    deterministic.run_until_parked();
-
-    // Both clients see the other client's focused file in their right pane.
-    assert_eq!(
-        pane_summaries(&workspace_a, cx_a),
-        &[
-            PaneSummary {
-                active: true,
-                leader: None,
-                items: vec![(true, "1.txt".into())]
-            },
-            PaneSummary {
-                active: false,
-                leader: client_b.peer_id(),
-                items: vec![(false, "1.txt".into()), (true, "2.txt".into())]
-            },
-        ]
-    );
-    assert_eq!(
-        pane_summaries(&workspace_b, cx_b),
-        &[
-            PaneSummary {
-                active: true,
-                leader: None,
-                items: vec![(true, "2.txt".into())]
-            },
-            PaneSummary {
-                active: false,
-                leader: client_a.peer_id(),
-                items: vec![(false, "2.txt".into()), (true, "1.txt".into())]
-            },
-        ]
-    );
-
-    // Clients A and B each open a new file.
-    workspace_a
-        .update(cx_a, |workspace, cx| {
-            workspace.open_path((worktree_id, "3.txt"), None, true, cx)
-        })
-        .await
-        .unwrap();
-
-    workspace_b
-        .update(cx_b, |workspace, cx| {
-            workspace.open_path((worktree_id, "4.txt"), None, true, cx)
-        })
-        .await
-        .unwrap();
-    deterministic.run_until_parked();
-
-    // Both client's see the other client open the new file, but keep their
-    // focus on their own active pane.
-    assert_eq!(
-        pane_summaries(&workspace_a, cx_a),
-        &[
-            PaneSummary {
-                active: true,
-                leader: None,
-                items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
-            },
-            PaneSummary {
-                active: false,
-                leader: client_b.peer_id(),
-                items: vec![
-                    (false, "1.txt".into()),
-                    (false, "2.txt".into()),
-                    (true, "4.txt".into())
-                ]
-            },
-        ]
-    );
-    assert_eq!(
-        pane_summaries(&workspace_b, cx_b),
-        &[
-            PaneSummary {
-                active: true,
-                leader: None,
-                items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
-            },
-            PaneSummary {
-                active: false,
-                leader: client_a.peer_id(),
-                items: vec![
-                    (false, "2.txt".into()),
-                    (false, "1.txt".into()),
-                    (true, "3.txt".into())
-                ]
-            },
-        ]
-    );
-
-    // Client A focuses their right pane, in which they're following client B.
-    workspace_a.update(cx_a, |workspace, cx| workspace.activate_next_pane(cx));
-    deterministic.run_until_parked();
-
-    // Client B sees that client A is now looking at the same file as them.
-    assert_eq!(
-        pane_summaries(&workspace_a, cx_a),
-        &[
-            PaneSummary {
-                active: false,
-                leader: None,
-                items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
-            },
-            PaneSummary {
-                active: true,
-                leader: client_b.peer_id(),
-                items: vec![
-                    (false, "1.txt".into()),
-                    (false, "2.txt".into()),
-                    (true, "4.txt".into())
-                ]
-            },
-        ]
-    );
-    assert_eq!(
-        pane_summaries(&workspace_b, cx_b),
-        &[
-            PaneSummary {
-                active: true,
-                leader: None,
-                items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
-            },
-            PaneSummary {
-                active: false,
-                leader: client_a.peer_id(),
-                items: vec![
-                    (false, "2.txt".into()),
-                    (false, "1.txt".into()),
-                    (false, "3.txt".into()),
-                    (true, "4.txt".into())
-                ]
-            },
-        ]
-    );
-
-    // Client B focuses their right pane, in which they're following client A,
-    // who is following them.
-    workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
-    deterministic.run_until_parked();
-
-    // Client A sees that client B is now looking at the same file as them.
-    assert_eq!(
-        pane_summaries(&workspace_b, cx_b),
-        &[
-            PaneSummary {
-                active: false,
-                leader: None,
-                items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
-            },
-            PaneSummary {
-                active: true,
-                leader: client_a.peer_id(),
-                items: vec![
-                    (false, "2.txt".into()),
-                    (false, "1.txt".into()),
-                    (false, "3.txt".into()),
-                    (true, "4.txt".into())
-                ]
-            },
-        ]
-    );
-    assert_eq!(
-        pane_summaries(&workspace_a, cx_a),
-        &[
-            PaneSummary {
-                active: false,
-                leader: None,
-                items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
-            },
-            PaneSummary {
-                active: true,
-                leader: client_b.peer_id(),
-                items: vec![
-                    (false, "1.txt".into()),
-                    (false, "2.txt".into()),
-                    (true, "4.txt".into())
-                ]
-            },
-        ]
-    );
-
-    // Client B focuses a file that they previously followed A to, breaking
-    // the follow.
-    workspace_b.update(cx_b, |workspace, cx| {
-        workspace.active_pane().update(cx, |pane, cx| {
-            pane.activate_prev_item(true, cx);
-        });
-    });
-    deterministic.run_until_parked();
-
-    // Both clients see that client B is looking at that previous file.
-    assert_eq!(
-        pane_summaries(&workspace_b, cx_b),
-        &[
-            PaneSummary {
-                active: false,
-                leader: None,
-                items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
-            },
-            PaneSummary {
-                active: true,
-                leader: None,
-                items: vec![
-                    (false, "2.txt".into()),
-                    (false, "1.txt".into()),
-                    (true, "3.txt".into()),
-                    (false, "4.txt".into())
-                ]
-            },
-        ]
-    );
-    assert_eq!(
-        pane_summaries(&workspace_a, cx_a),
-        &[
-            PaneSummary {
-                active: false,
-                leader: None,
-                items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
-            },
-            PaneSummary {
-                active: true,
-                leader: client_b.peer_id(),
-                items: vec![
-                    (false, "1.txt".into()),
-                    (false, "2.txt".into()),
-                    (false, "4.txt".into()),
-                    (true, "3.txt".into()),
-                ]
-            },
-        ]
-    );
-
-    // Client B closes tabs, some of which were originally opened by client A,
-    // and some of which were originally opened by client B.
-    workspace_b.update(cx_b, |workspace, cx| {
-        workspace.active_pane().update(cx, |pane, cx| {
-            pane.close_inactive_items(&Default::default(), cx)
-                .unwrap()
-                .detach();
-        });
-    });
-
-    deterministic.run_until_parked();
-
-    // Both clients see that Client B is looking at the previous tab.
-    assert_eq!(
-        pane_summaries(&workspace_b, cx_b),
-        &[
-            PaneSummary {
-                active: false,
-                leader: None,
-                items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
-            },
-            PaneSummary {
-                active: true,
-                leader: None,
-                items: vec![(true, "3.txt".into()),]
-            },
-        ]
-    );
-    assert_eq!(
-        pane_summaries(&workspace_a, cx_a),
-        &[
-            PaneSummary {
-                active: false,
-                leader: None,
-                items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
-            },
-            PaneSummary {
-                active: true,
-                leader: client_b.peer_id(),
-                items: vec![
-                    (false, "1.txt".into()),
-                    (false, "2.txt".into()),
-                    (false, "4.txt".into()),
-                    (true, "3.txt".into()),
-                ]
-            },
-        ]
-    );
-
-    // Client B follows client A again.
-    workspace_b
-        .update(cx_b, |workspace, cx| {
-            workspace.follow(client_a.peer_id().unwrap(), cx).unwrap()
-        })
-        .await
-        .unwrap();
-
-    // Client A cycles through some tabs.
-    workspace_a.update(cx_a, |workspace, cx| {
-        workspace.active_pane().update(cx, |pane, cx| {
-            pane.activate_prev_item(true, cx);
-        });
-    });
-    deterministic.run_until_parked();
-
-    // Client B follows client A into those tabs.
-    assert_eq!(
-        pane_summaries(&workspace_a, cx_a),
-        &[
-            PaneSummary {
-                active: false,
-                leader: None,
-                items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
-            },
-            PaneSummary {
-                active: true,
-                leader: None,
-                items: vec![
-                    (false, "1.txt".into()),
-                    (false, "2.txt".into()),
-                    (true, "4.txt".into()),
-                    (false, "3.txt".into()),
-                ]
-            },
-        ]
-    );
-    assert_eq!(
-        pane_summaries(&workspace_b, cx_b),
-        &[
-            PaneSummary {
-                active: false,
-                leader: None,
-                items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
-            },
-            PaneSummary {
-                active: true,
-                leader: client_a.peer_id(),
-                items: vec![(false, "3.txt".into()), (true, "4.txt".into())]
-            },
-        ]
-    );
-
-    workspace_a.update(cx_a, |workspace, cx| {
-        workspace.active_pane().update(cx, |pane, cx| {
-            pane.activate_prev_item(true, cx);
-        });
-    });
-    deterministic.run_until_parked();
-
-    assert_eq!(
-        pane_summaries(&workspace_a, cx_a),
-        &[
-            PaneSummary {
-                active: false,
-                leader: None,
-                items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
-            },
-            PaneSummary {
-                active: true,
-                leader: None,
-                items: vec![
-                    (false, "1.txt".into()),
-                    (true, "2.txt".into()),
-                    (false, "4.txt".into()),
-                    (false, "3.txt".into()),
-                ]
-            },
-        ]
-    );
-    assert_eq!(
-        pane_summaries(&workspace_b, cx_b),
-        &[
-            PaneSummary {
-                active: false,
-                leader: None,
-                items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
-            },
-            PaneSummary {
-                active: true,
-                leader: client_a.peer_id(),
-                items: vec![
-                    (false, "3.txt".into()),
-                    (false, "4.txt".into()),
-                    (true, "2.txt".into())
-                ]
-            },
-        ]
-    );
-
-    workspace_a.update(cx_a, |workspace, cx| {
-        workspace.active_pane().update(cx, |pane, cx| {
-            pane.activate_prev_item(true, cx);
-        });
-    });
-    deterministic.run_until_parked();
-
-    assert_eq!(
-        pane_summaries(&workspace_a, cx_a),
-        &[
-            PaneSummary {
-                active: false,
-                leader: None,
-                items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
-            },
-            PaneSummary {
-                active: true,
-                leader: None,
-                items: vec![
-                    (true, "1.txt".into()),
-                    (false, "2.txt".into()),
-                    (false, "4.txt".into()),
-                    (false, "3.txt".into()),
-                ]
-            },
-        ]
-    );
-    assert_eq!(
-        pane_summaries(&workspace_b, cx_b),
-        &[
-            PaneSummary {
-                active: false,
-                leader: None,
-                items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
-            },
-            PaneSummary {
-                active: true,
-                leader: client_a.peer_id(),
-                items: vec![
-                    (false, "3.txt".into()),
-                    (false, "4.txt".into()),
-                    (false, "2.txt".into()),
-                    (true, "1.txt".into()),
-                ]
-            },
-        ]
-    );
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_auto_unfollowing(
-    deterministic: Arc<Deterministic>,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    deterministic.forbid_parking();
-
-    // 2 clients connect to a server.
-    let mut server = TestServer::start(&deterministic).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    server
-        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-        .await;
-    let active_call_a = cx_a.read(ActiveCall::global);
-    let active_call_b = cx_b.read(ActiveCall::global);
-
-    cx_a.update(editor::init);
-    cx_b.update(editor::init);
-
-    // Client A shares a project.
-    client_a
-        .fs()
-        .insert_tree(
-            "/a",
-            json!({
-                "1.txt": "one",
-                "2.txt": "two",
-                "3.txt": "three",
-            }),
-        )
-        .await;
-    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
-    active_call_a
-        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
-        .await
-        .unwrap();
-
-    let project_id = active_call_a
-        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-        .await
-        .unwrap();
-    let project_b = client_b.build_remote_project(project_id, cx_b).await;
-    active_call_b
-        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
-        .await
-        .unwrap();
-
-    // Client A opens some editors.
-    let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
-    let _editor_a1 = workspace_a
-        .update(cx_a, |workspace, cx| {
-            workspace.open_path((worktree_id, "1.txt"), None, true, cx)
-        })
-        .await
-        .unwrap()
-        .downcast::<Editor>()
-        .unwrap();
-
-    // Client B starts following client A.
-    let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
-    let pane_b = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
-    let leader_id = project_b.read_with(cx_b, |project, _| {
-        project.collaborators().values().next().unwrap().peer_id
-    });
-    workspace_b
-        .update(cx_b, |workspace, cx| {
-            workspace.follow(leader_id, cx).unwrap()
-        })
-        .await
-        .unwrap();
-    assert_eq!(
-        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-        Some(leader_id)
-    );
-    let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
-        workspace
-            .active_item(cx)
-            .unwrap()
-            .downcast::<Editor>()
-            .unwrap()
-    });
-
-    // When client B moves, it automatically stops following client A.
-    editor_b2.update(cx_b, |editor, cx| editor.move_right(&editor::MoveRight, cx));
-    assert_eq!(
-        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-        None
-    );
-
-    workspace_b
-        .update(cx_b, |workspace, cx| {
-            workspace.follow(leader_id, cx).unwrap()
-        })
-        .await
-        .unwrap();
-    assert_eq!(
-        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-        Some(leader_id)
-    );
-
-    // When client B edits, it automatically stops following client A.
-    editor_b2.update(cx_b, |editor, cx| editor.insert("X", cx));
-    assert_eq!(
-        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-        None
-    );
-
-    workspace_b
-        .update(cx_b, |workspace, cx| {
-            workspace.follow(leader_id, cx).unwrap()
-        })
-        .await
-        .unwrap();
-    assert_eq!(
-        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-        Some(leader_id)
-    );
-
-    // When client B scrolls, it automatically stops following client A.
-    editor_b2.update(cx_b, |editor, cx| {
-        editor.set_scroll_position(vec2f(0., 3.), cx)
-    });
-    assert_eq!(
-        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-        None
-    );
-
-    workspace_b
-        .update(cx_b, |workspace, cx| {
-            workspace.follow(leader_id, cx).unwrap()
-        })
-        .await
-        .unwrap();
-    assert_eq!(
-        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-        Some(leader_id)
-    );
-
-    // When client B activates a different pane, it continues following client A in the original pane.
-    workspace_b.update(cx_b, |workspace, cx| {
-        workspace.split_and_clone(pane_b.clone(), SplitDirection::Right, cx)
-    });
-    assert_eq!(
-        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-        Some(leader_id)
-    );
-
-    workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
-    assert_eq!(
-        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-        Some(leader_id)
-    );
-
-    // When client B activates a different item in the original pane, it automatically stops following client A.
-    workspace_b
-        .update(cx_b, |workspace, cx| {
-            workspace.open_path((worktree_id, "2.txt"), None, true, cx)
-        })
-        .await
-        .unwrap();
-    assert_eq!(
-        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-        None
-    );
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_peers_simultaneously_following_each_other(
-    deterministic: Arc<Deterministic>,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    deterministic.forbid_parking();
-
-    let mut server = TestServer::start(&deterministic).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_a.update(editor::init);
-    cx_b.update(editor::init);
-
-    client_a.fs().insert_tree("/a", json!({})).await;
-    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
-    let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
-    let project_id = active_call_a
-        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-        .await
-        .unwrap();
-
-    let project_b = client_b.build_remote_project(project_id, cx_b).await;
-    let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
-
-    deterministic.run_until_parked();
-    let client_a_id = project_b.read_with(cx_b, |project, _| {
-        project.collaborators().values().next().unwrap().peer_id
-    });
-    let client_b_id = project_a.read_with(cx_a, |project, _| {
-        project.collaborators().values().next().unwrap().peer_id
-    });
-
-    let a_follow_b = workspace_a.update(cx_a, |workspace, cx| {
-        workspace.follow(client_b_id, cx).unwrap()
-    });
-    let b_follow_a = workspace_b.update(cx_b, |workspace, cx| {
-        workspace.follow(client_a_id, cx).unwrap()
-    });
-
-    futures::try_join!(a_follow_b, b_follow_a).unwrap();
-    workspace_a.read_with(cx_a, |workspace, _| {
-        assert_eq!(
-            workspace.leader_for_pane(workspace.active_pane()),
-            Some(client_b_id)
-        );
-    });
-    workspace_b.read_with(cx_b, |workspace, _| {
-        assert_eq!(
-            workspace.leader_for_pane(workspace.active_pane()),
-            Some(client_a_id)
-        );
-    });
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_following_across_workspaces(
-    deterministic: Arc<Deterministic>,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    // a and b join a channel/call
-    // a shares project 1
-    // b shares project 2
-    //
-    // b follows a: causes project 2 to be joined, and b to follow a.
-    // b opens a different file in project 2, a follows b
-    // b opens a different file in project 1, a cannot follow b
-    // b shares the project, a joins the project and follows b
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    cx_a.update(editor::init);
-    cx_b.update(editor::init);
-
-    client_a
-        .fs()
-        .insert_tree(
-            "/a",
-            json!({
-                "w.rs": "",
-                "x.rs": "",
-            }),
-        )
-        .await;
-
-    client_b
-        .fs()
-        .insert_tree(
-            "/b",
-            json!({
-                "y.rs": "",
-                "z.rs": "",
-            }),
-        )
-        .await;
-
-    server
-        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-        .await;
-    let active_call_a = cx_a.read(ActiveCall::global);
-    let active_call_b = cx_b.read(ActiveCall::global);
-
-    let (project_a, worktree_id_a) = client_a.build_local_project("/a", cx_a).await;
-    let (project_b, worktree_id_b) = client_b.build_local_project("/b", cx_b).await;
-
-    let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
-    let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
-
-    cx_a.update(|cx| collab_ui::init(&client_a.app_state, cx));
-    cx_b.update(|cx| collab_ui::init(&client_b.app_state, cx));
-
-    active_call_a
-        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-        .await
-        .unwrap();
-
-    active_call_a
-        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
-        .await
-        .unwrap();
-    active_call_b
-        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
-        .await
-        .unwrap();
-
-    workspace_a
-        .update(cx_a, |workspace, cx| {
-            workspace.open_path((worktree_id_a, "w.rs"), None, true, cx)
-        })
-        .await
-        .unwrap();
-
-    deterministic.run_until_parked();
-    assert_eq!(visible_push_notifications(cx_b).len(), 1);
-
-    workspace_b.update(cx_b, |workspace, cx| {
-        workspace
-            .follow(client_a.peer_id().unwrap(), cx)
-            .unwrap()
-            .detach()
-    });
-
-    deterministic.run_until_parked();
-    let workspace_b_project_a = cx_b
-        .windows()
-        .iter()
-        .max_by_key(|window| window.id())
-        .unwrap()
-        .downcast::<Workspace>()
-        .unwrap()
-        .root(cx_b);
-
-    // assert that b is following a in project a in w.rs
-    workspace_b_project_a.update(cx_b, |workspace, cx| {
-        assert!(workspace.is_being_followed(client_a.peer_id().unwrap()));
-        assert_eq!(
-            client_a.peer_id(),
-            workspace.leader_for_pane(workspace.active_pane())
-        );
-        let item = workspace.active_item(cx).unwrap();
-        assert_eq!(item.tab_description(0, cx).unwrap(), Cow::Borrowed("w.rs"));
-    });
-
-    // TODO: in app code, this would be done by the collab_ui.
-    active_call_b
-        .update(cx_b, |call, cx| {
-            let project = workspace_b_project_a.read(cx).project().clone();
-            call.set_location(Some(&project), cx)
-        })
-        .await
-        .unwrap();
-
-    // assert that there are no share notifications open
-    assert_eq!(visible_push_notifications(cx_b).len(), 0);
-
-    // b moves to x.rs in a's project, and a follows
-    workspace_b_project_a
-        .update(cx_b, |workspace, cx| {
-            workspace.open_path((worktree_id_a, "x.rs"), None, true, cx)
-        })
-        .await
-        .unwrap();
-
-    deterministic.run_until_parked();
-    workspace_b_project_a.update(cx_b, |workspace, cx| {
-        let item = workspace.active_item(cx).unwrap();
-        assert_eq!(item.tab_description(0, cx).unwrap(), Cow::Borrowed("x.rs"));
-    });
-
-    workspace_a.update(cx_a, |workspace, cx| {
-        workspace
-            .follow(client_b.peer_id().unwrap(), cx)
-            .unwrap()
-            .detach()
-    });
-
-    deterministic.run_until_parked();
-    workspace_a.update(cx_a, |workspace, cx| {
-        assert!(workspace.is_being_followed(client_b.peer_id().unwrap()));
-        assert_eq!(
-            client_b.peer_id(),
-            workspace.leader_for_pane(workspace.active_pane())
-        );
-        let item = workspace.active_pane().read(cx).active_item().unwrap();
-        assert_eq!(item.tab_description(0, cx).unwrap(), Cow::Borrowed("x.rs"));
-    });
-
-    // b moves to y.rs in b's project, a is still following but can't yet see
-    workspace_b
-        .update(cx_b, |workspace, cx| {
-            workspace.open_path((worktree_id_b, "y.rs"), None, true, cx)
-        })
-        .await
-        .unwrap();
-
-    // TODO: in app code, this would be done by the collab_ui.
-    active_call_b
-        .update(cx_b, |call, cx| {
-            let project = workspace_b.read(cx).project().clone();
-            call.set_location(Some(&project), cx)
-        })
-        .await
-        .unwrap();
-
-    let project_b_id = active_call_b
-        .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
-        .await
-        .unwrap();
-
-    deterministic.run_until_parked();
-    assert_eq!(visible_push_notifications(cx_a).len(), 1);
-    cx_a.update(|cx| {
-        workspace::join_remote_project(
-            project_b_id,
-            client_b.user_id().unwrap(),
-            client_a.app_state.clone(),
-            cx,
-        )
-    })
-    .await
-    .unwrap();
-
-    deterministic.run_until_parked();
-
-    assert_eq!(visible_push_notifications(cx_a).len(), 0);
-    let workspace_a_project_b = cx_a
-        .windows()
-        .iter()
-        .max_by_key(|window| window.id())
-        .unwrap()
-        .downcast::<Workspace>()
-        .unwrap()
-        .root(cx_a);
-
-    workspace_a_project_b.update(cx_a, |workspace, cx| {
-        assert_eq!(workspace.project().read(cx).remote_id(), Some(project_b_id));
-        assert!(workspace.is_being_followed(client_b.peer_id().unwrap()));
-        assert_eq!(
-            client_b.peer_id(),
-            workspace.leader_for_pane(workspace.active_pane())
-        );
-        let item = workspace.active_item(cx).unwrap();
-        assert_eq!(item.tab_description(0, cx).unwrap(), Cow::Borrowed("y.rs"));
-    });
-}
-
-#[gpui::test]
-async fn test_following_into_excluded_file(
-    deterministic: Arc<Deterministic>,
-    mut cx_a: &mut TestAppContext,
-    mut cx_b: &mut TestAppContext,
-) {
-    deterministic.forbid_parking();
-
-    let mut server = TestServer::start(&deterministic).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    for cx in [&mut cx_a, &mut cx_b] {
-        cx.update(|cx| {
-            cx.update_global::<SettingsStore, _, _>(|store, cx| {
-                store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
-                    project_settings.file_scan_exclusions = Some(vec!["**/.git".to_string()]);
-                });
-            });
-        });
-    }
-    server
-        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-        .await;
-    let active_call_a = cx_a.read(ActiveCall::global);
-    let active_call_b = cx_b.read(ActiveCall::global);
-
-    cx_a.update(editor::init);
-    cx_b.update(editor::init);
-
-    client_a
-        .fs()
-        .insert_tree(
-            "/a",
-            json!({
-                ".git": {
-                    "COMMIT_EDITMSG": "write your commit message here",
-                },
-                "1.txt": "one\none\none",
-                "2.txt": "two\ntwo\ntwo",
-                "3.txt": "three\nthree\nthree",
-            }),
-        )
-        .await;
-    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
-    active_call_a
-        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
-        .await
-        .unwrap();
-
-    let project_id = active_call_a
-        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-        .await
-        .unwrap();
-    let project_b = client_b.build_remote_project(project_id, cx_b).await;
-    active_call_b
-        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
-        .await
-        .unwrap();
-
-    let window_a = client_a.build_workspace(&project_a, cx_a);
-    let workspace_a = window_a.root(cx_a);
-    let peer_id_a = client_a.peer_id().unwrap();
-    let window_b = client_b.build_workspace(&project_b, cx_b);
-    let workspace_b = window_b.root(cx_b);
-
-    // Client A opens editors for a regular file and an excluded file.
-    let editor_for_regular = workspace_a
-        .update(cx_a, |workspace, cx| {
-            workspace.open_path((worktree_id, "1.txt"), None, true, cx)
-        })
-        .await
-        .unwrap()
-        .downcast::<Editor>()
-        .unwrap();
-    let editor_for_excluded_a = workspace_a
-        .update(cx_a, |workspace, cx| {
-            workspace.open_path((worktree_id, ".git/COMMIT_EDITMSG"), None, true, cx)
-        })
-        .await
-        .unwrap()
-        .downcast::<Editor>()
-        .unwrap();
-
-    // Client A updates their selections in those editors
-    editor_for_regular.update(cx_a, |editor, cx| {
-        editor.handle_input("a", cx);
-        editor.handle_input("b", cx);
-        editor.handle_input("c", cx);
-        editor.select_left(&Default::default(), cx);
-        assert_eq!(editor.selections.ranges(cx), vec![3..2]);
-    });
-    editor_for_excluded_a.update(cx_a, |editor, cx| {
-        editor.select_all(&Default::default(), cx);
-        editor.handle_input("new commit message", cx);
-        editor.select_left(&Default::default(), cx);
-        assert_eq!(editor.selections.ranges(cx), vec![18..17]);
-    });
-
-    // When client B starts following client A, currently visible file is replicated
-    workspace_b
-        .update(cx_b, |workspace, cx| {
-            workspace.follow(peer_id_a, cx).unwrap()
-        })
-        .await
-        .unwrap();
-
-    let editor_for_excluded_b = workspace_b.read_with(cx_b, |workspace, cx| {
-        workspace
-            .active_item(cx)
-            .unwrap()
-            .downcast::<Editor>()
-            .unwrap()
-    });
-    assert_eq!(
-        cx_b.read(|cx| editor_for_excluded_b.project_path(cx)),
-        Some((worktree_id, ".git/COMMIT_EDITMSG").into())
-    );
-    assert_eq!(
-        editor_for_excluded_b.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
-        vec![18..17]
-    );
-
-    // Changes from B to the excluded file are replicated in A's editor
-    editor_for_excluded_b.update(cx_b, |editor, cx| {
-        editor.handle_input("\nCo-Authored-By: B <b@b.b>", cx);
-    });
-    deterministic.run_until_parked();
-    editor_for_excluded_a.update(cx_a, |editor, cx| {
-        assert_eq!(
-            editor.text(cx),
-            "new commit messag\nCo-Authored-By: B <b@b.b>"
-        );
-    });
-}
-
-fn visible_push_notifications(
-    cx: &mut TestAppContext,
-) -> Vec<gpui::ViewHandle<ProjectSharedNotification>> {
-    let mut ret = Vec::new();
-    for window in cx.windows() {
-        window.read_with(cx, |window| {
-            if let Some(handle) = window
-                .root_view()
-                .clone()
-                .downcast::<ProjectSharedNotification>()
-            {
-                ret.push(handle)
-            }
-        });
-    }
-    ret
-}
-
-#[derive(Debug, PartialEq, Eq)]
-struct PaneSummary {
-    active: bool,
-    leader: Option<PeerId>,
-    items: Vec<(bool, String)>,
-}
-
-fn followers_by_leader(project_id: u64, cx: &TestAppContext) -> Vec<(PeerId, Vec<PeerId>)> {
-    cx.read(|cx| {
-        let active_call = ActiveCall::global(cx).read(cx);
-        let peer_id = active_call.client().peer_id();
-        let room = active_call.room().unwrap().read(cx);
-        let mut result = room
-            .remote_participants()
-            .values()
-            .map(|participant| participant.peer_id)
-            .chain(peer_id)
-            .filter_map(|peer_id| {
-                let followers = room.followers_for(peer_id, project_id);
-                if followers.is_empty() {
-                    None
-                } else {
-                    Some((peer_id, followers.to_vec()))
-                }
-            })
-            .collect::<Vec<_>>();
-        result.sort_by_key(|e| e.0);
-        result
-    })
-}
-
-fn pane_summaries(workspace: &ViewHandle<Workspace>, cx: &mut TestAppContext) -> Vec<PaneSummary> {
-    workspace.read_with(cx, |workspace, cx| {
-        let active_pane = workspace.active_pane();
-        workspace
-            .panes()
-            .iter()
-            .map(|pane| {
-                let leader = workspace.leader_for_pane(pane);
-                let active = pane == active_pane;
-                let pane = pane.read(cx);
-                let active_ix = pane.active_item_index();
-                PaneSummary {
-                    active,
-                    leader,
-                    items: pane
-                        .items()
-                        .enumerate()
-                        .map(|(ix, item)| {
-                            (
-                                ix == active_ix,
-                                item.tab_description(0, cx)
-                                    .map_or(String::new(), |s| s.to_string()),
-                            )
-                        })
-                        .collect(),
-                }
-            })
-            .collect()
-    })
-}
+//todo!(workspace)
+
+// use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
+// use call::ActiveCall;
+// use collab_ui::notifications::project_shared_notification::ProjectSharedNotification;
+// use editor::{Editor, ExcerptRange, MultiBuffer};
+// use gpui::{point, BackgroundExecutor, TestAppContext, View, VisualTestContext, WindowContext};
+// use live_kit_client::MacOSDisplay;
+// use project::project_settings::ProjectSettings;
+// use rpc::proto::PeerId;
+// use serde_json::json;
+// use settings::SettingsStore;
+// use std::borrow::Cow;
+// use workspace::{
+//     dock::{test::TestPanel, DockPosition},
+//     item::{test::TestItem, ItemHandle as _},
+//     shared_screen::SharedScreen,
+//     SplitDirection, Workspace,
+// };
+
+// #[gpui::test(iterations = 10)]
+// async fn test_basic_following(
+//     executor: BackgroundExecutor,
+//     cx_a: &mut TestAppContext,
+//     cx_b: &mut TestAppContext,
+//     cx_c: &mut TestAppContext,
+//     cx_d: &mut TestAppContext,
+// ) {
+//     let mut server = TestServer::start(executor.clone()).await;
+//     let client_a = server.create_client(cx_a, "user_a").await;
+//     let client_b = server.create_client(cx_b, "user_b").await;
+//     let client_c = server.create_client(cx_c, "user_c").await;
+//     let client_d = server.create_client(cx_d, "user_d").await;
+//     server
+//         .create_room(&mut [
+//             (&client_a, cx_a),
+//             (&client_b, cx_b),
+//             (&client_c, cx_c),
+//             (&client_d, cx_d),
+//         ])
+//         .await;
+//     let active_call_a = cx_a.read(ActiveCall::global);
+//     let active_call_b = cx_b.read(ActiveCall::global);
+
+//     cx_a.update(editor::init);
+//     cx_b.update(editor::init);
+
+//     client_a
+//         .fs()
+//         .insert_tree(
+//             "/a",
+//             json!({
+//                 "1.txt": "one\none\none",
+//                 "2.txt": "two\ntwo\ntwo",
+//                 "3.txt": "three\nthree\nthree",
+//             }),
+//         )
+//         .await;
+//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+//     active_call_a
+//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
+//         .await
+//         .unwrap();
+
+//     let project_id = active_call_a
+//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+//         .await
+//         .unwrap();
+//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
+//     active_call_b
+//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
+//         .await
+//         .unwrap();
+
+//     let window_a = client_a.build_workspace(&project_a, cx_a);
+//     let workspace_a = window_a.root(cx_a).unwrap();
+//     let window_b = client_b.build_workspace(&project_b, cx_b);
+//     let workspace_b = window_b.root(cx_b).unwrap();
+
+//     todo!("could be wrong")
+//     let mut cx_a = VisualTestContext::from_window(*window_a, cx_a);
+//     let cx_a = &mut cx_a;
+//     let mut cx_b = VisualTestContext::from_window(*window_b, cx_b);
+//     let cx_b = &mut cx_b;
+//     let mut cx_c = VisualTestContext::from_window(*window_c, cx_c);
+//     let cx_c = &mut cx_c;
+//     let mut cx_d = VisualTestContext::from_window(*window_d, cx_d);
+//     let cx_d = &mut cx_d;
+
+//     // Client A opens some editors.
+//     let pane_a = workspace_a.update(cx_a, |workspace, _| workspace.active_pane().clone());
+//     let editor_a1 = workspace_a
+//         .update(cx_a, |workspace, cx| {
+//             workspace.open_path((worktree_id, "1.txt"), None, true, cx)
+//         })
+//         .await
+//         .unwrap()
+//         .downcast::<Editor>()
+//         .unwrap();
+//     let editor_a2 = workspace_a
+//         .update(cx_a, |workspace, cx| {
+//             workspace.open_path((worktree_id, "2.txt"), None, true, cx)
+//         })
+//         .await
+//         .unwrap()
+//         .downcast::<Editor>()
+//         .unwrap();
+
+//     // Client B opens an editor.
+//     let editor_b1 = workspace_b
+//         .update(cx_b, |workspace, cx| {
+//             workspace.open_path((worktree_id, "1.txt"), None, true, cx)
+//         })
+//         .await
+//         .unwrap()
+//         .downcast::<Editor>()
+//         .unwrap();
+
+//     let peer_id_a = client_a.peer_id().unwrap();
+//     let peer_id_b = client_b.peer_id().unwrap();
+//     let peer_id_c = client_c.peer_id().unwrap();
+//     let peer_id_d = client_d.peer_id().unwrap();
+
+//     // Client A updates their selections in those editors
+//     editor_a1.update(cx_a, |editor, cx| {
+//         editor.handle_input("a", cx);
+//         editor.handle_input("b", cx);
+//         editor.handle_input("c", cx);
+//         editor.select_left(&Default::default(), cx);
+//         assert_eq!(editor.selections.ranges(cx), vec![3..2]);
+//     });
+//     editor_a2.update(cx_a, |editor, cx| {
+//         editor.handle_input("d", cx);
+//         editor.handle_input("e", cx);
+//         editor.select_left(&Default::default(), cx);
+//         assert_eq!(editor.selections.ranges(cx), vec![2..1]);
+//     });
+
+//     // When client B starts following client A, all visible view states are replicated to client B.
+//     workspace_b
+//         .update(cx_b, |workspace, cx| {
+//             workspace.follow(peer_id_a, cx).unwrap()
+//         })
+//         .await
+//         .unwrap();
+
+//     cx_c.executor().run_until_parked();
+//     let editor_b2 = workspace_b.update(cx_b, |workspace, cx| {
+//         workspace
+//             .active_item(cx)
+//             .unwrap()
+//             .downcast::<Editor>()
+//             .unwrap()
+//     });
+//     assert_eq!(
+//         cx_b.read(|cx| editor_b2.project_path(cx)),
+//         Some((worktree_id, "2.txt").into())
+//     );
+//     assert_eq!(
+//         editor_b2.update(cx_b, |editor, cx| editor.selections.ranges(cx)),
+//         vec![2..1]
+//     );
+//     assert_eq!(
+//         editor_b1.update(cx_b, |editor, cx| editor.selections.ranges(cx)),
+//         vec![3..2]
+//     );
+
+//     cx_c.executor().run_until_parked();
+//     let active_call_c = cx_c.read(ActiveCall::global);
+//     let project_c = client_c.build_remote_project(project_id, cx_c).await;
+//     let window_c = client_c.build_workspace(&project_c, cx_c);
+//     let workspace_c = window_c.root(cx_c).unwrap();
+//     active_call_c
+//         .update(cx_c, |call, cx| call.set_location(Some(&project_c), cx))
+//         .await
+//         .unwrap();
+//     drop(project_c);
+
+//     // Client C also follows client A.
+//     workspace_c
+//         .update(cx_c, |workspace, cx| {
+//             workspace.follow(peer_id_a, cx).unwrap()
+//         })
+//         .await
+//         .unwrap();
+
+//     cx_d.executor().run_until_parked();
+//     let active_call_d = cx_d.read(ActiveCall::global);
+//     let project_d = client_d.build_remote_project(project_id, cx_d).await;
+//     let workspace_d = client_d
+//         .build_workspace(&project_d, cx_d)
+//         .root(cx_d)
+//         .unwrap();
+//     active_call_d
+//         .update(cx_d, |call, cx| call.set_location(Some(&project_d), cx))
+//         .await
+//         .unwrap();
+//     drop(project_d);
+
+//     // All clients see that clients B and C are following client A.
+//     cx_c.executor().run_until_parked();
+//     for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
+//         assert_eq!(
+//             followers_by_leader(project_id, cx),
+//             &[(peer_id_a, vec![peer_id_b, peer_id_c])],
+//             "followers seen by {name}"
+//         );
+//     }
+
+//     // Client C unfollows client A.
+//     workspace_c.update(cx_c, |workspace, cx| {
+//         workspace.unfollow(&workspace.active_pane().clone(), cx);
+//     });
+
+//     // All clients see that clients B is following client A.
+//     cx_c.executor().run_until_parked();
+//     for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
+//         assert_eq!(
+//             followers_by_leader(project_id, cx),
+//             &[(peer_id_a, vec![peer_id_b])],
+//             "followers seen by {name}"
+//         );
+//     }
+
+//     // Client C re-follows client A.
+//     workspace_c
+//         .update(cx_c, |workspace, cx| {
+//             workspace.follow(peer_id_a, cx).unwrap()
+//         })
+//         .await
+//         .unwrap();
+
+//     // All clients see that clients B and C are following client A.
+//     cx_c.executor().run_until_parked();
+//     for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
+//         assert_eq!(
+//             followers_by_leader(project_id, cx),
+//             &[(peer_id_a, vec![peer_id_b, peer_id_c])],
+//             "followers seen by {name}"
+//         );
+//     }
+
+//     // Client D follows client B, then switches to following client C.
+//     workspace_d
+//         .update(cx_d, |workspace, cx| {
+//             workspace.follow(peer_id_b, cx).unwrap()
+//         })
+//         .await
+//         .unwrap();
+//     workspace_d
+//         .update(cx_d, |workspace, cx| {
+//             workspace.follow(peer_id_c, cx).unwrap()
+//         })
+//         .await
+//         .unwrap();
+
+//     // All clients see that D is following C
+//     cx_d.executor().run_until_parked();
+//     for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
+//         assert_eq!(
+//             followers_by_leader(project_id, cx),
+//             &[
+//                 (peer_id_a, vec![peer_id_b, peer_id_c]),
+//                 (peer_id_c, vec![peer_id_d])
+//             ],
+//             "followers seen by {name}"
+//         );
+//     }
+
+//     // Client C closes the project.
+//     window_c.remove(cx_c);
+//     cx_c.drop_last(workspace_c);
+
+//     // Clients A and B see that client B is following A, and client C is not present in the followers.
+//     cx_c.executor().run_until_parked();
+//     for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
+//         assert_eq!(
+//             followers_by_leader(project_id, cx),
+//             &[(peer_id_a, vec![peer_id_b]),],
+//             "followers seen by {name}"
+//         );
+//     }
+
+//     // When client A activates a different editor, client B does so as well.
+//     workspace_a.update(cx_a, |workspace, cx| {
+//         workspace.activate_item(&editor_a1, cx)
+//     });
+//     executor.run_until_parked();
+//     workspace_b.update(cx_b, |workspace, cx| {
+//         assert_eq!(
+//             workspace.active_item(cx).unwrap().item_id(),
+//             editor_b1.item_id()
+//         );
+//     });
+
+//     // When client A opens a multibuffer, client B does so as well.
+//     let multibuffer_a = cx_a.build_model(|cx| {
+//         let buffer_a1 = project_a.update(cx, |project, cx| {
+//             project
+//                 .get_open_buffer(&(worktree_id, "1.txt").into(), cx)
+//                 .unwrap()
+//         });
+//         let buffer_a2 = project_a.update(cx, |project, cx| {
+//             project
+//                 .get_open_buffer(&(worktree_id, "2.txt").into(), cx)
+//                 .unwrap()
+//         });
+//         let mut result = MultiBuffer::new(0);
+//         result.push_excerpts(
+//             buffer_a1,
+//             [ExcerptRange {
+//                 context: 0..3,
+//                 primary: None,
+//             }],
+//             cx,
+//         );
+//         result.push_excerpts(
+//             buffer_a2,
+//             [ExcerptRange {
+//                 context: 4..7,
+//                 primary: None,
+//             }],
+//             cx,
+//         );
+//         result
+//     });
+//     let multibuffer_editor_a = workspace_a.update(cx_a, |workspace, cx| {
+//         let editor =
+//             cx.build_view(|cx| Editor::for_multibuffer(multibuffer_a, Some(project_a.clone()), cx));
+//         workspace.add_item(Box::new(editor.clone()), cx);
+//         editor
+//     });
+//     executor.run_until_parked();
+//     let multibuffer_editor_b = workspace_b.update(cx_b, |workspace, cx| {
+//         workspace
+//             .active_item(cx)
+//             .unwrap()
+//             .downcast::<Editor>()
+//             .unwrap()
+//     });
+//     assert_eq!(
+//         multibuffer_editor_a.update(cx_a, |editor, cx| editor.text(cx)),
+//         multibuffer_editor_b.update(cx_b, |editor, cx| editor.text(cx)),
+//     );
+
+//     // When client A navigates back and forth, client B does so as well.
+//     workspace_a
+//         .update(cx_a, |workspace, cx| {
+//             workspace.go_back(workspace.active_pane().downgrade(), cx)
+//         })
+//         .await
+//         .unwrap();
+//     executor.run_until_parked();
+//     workspace_b.update(cx_b, |workspace, cx| {
+//         assert_eq!(
+//             workspace.active_item(cx).unwrap().item_id(),
+//             editor_b1.item_id()
+//         );
+//     });
+
+//     workspace_a
+//         .update(cx_a, |workspace, cx| {
+//             workspace.go_back(workspace.active_pane().downgrade(), cx)
+//         })
+//         .await
+//         .unwrap();
+//     executor.run_until_parked();
+//     workspace_b.update(cx_b, |workspace, cx| {
+//         assert_eq!(
+//             workspace.active_item(cx).unwrap().item_id(),
+//             editor_b2.item_id()
+//         );
+//     });
+
+//     workspace_a
+//         .update(cx_a, |workspace, cx| {
+//             workspace.go_forward(workspace.active_pane().downgrade(), cx)
+//         })
+//         .await
+//         .unwrap();
+//     executor.run_until_parked();
+//     workspace_b.update(cx_b, |workspace, cx| {
+//         assert_eq!(
+//             workspace.active_item(cx).unwrap().item_id(),
+//             editor_b1.item_id()
+//         );
+//     });
+
+//     // Changes to client A's editor are reflected on client B.
+//     editor_a1.update(cx_a, |editor, cx| {
+//         editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2]));
+//     });
+//     executor.run_until_parked();
+//     editor_b1.update(cx_b, |editor, cx| {
+//         assert_eq!(editor.selections.ranges(cx), &[1..1, 2..2]);
+//     });
+
+//     editor_a1.update(cx_a, |editor, cx| editor.set_text("TWO", cx));
+//     executor.run_until_parked();
+//     editor_b1.update(cx_b, |editor, cx| assert_eq!(editor.text(cx), "TWO"));
+
+//     editor_a1.update(cx_a, |editor, cx| {
+//         editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
+//         editor.set_scroll_position(point(0., 100.), cx);
+//     });
+//     executor.run_until_parked();
+//     editor_b1.update(cx_b, |editor, cx| {
+//         assert_eq!(editor.selections.ranges(cx), &[3..3]);
+//     });
+
+//     // After unfollowing, client B stops receiving updates from client A.
+//     workspace_b.update(cx_b, |workspace, cx| {
+//         workspace.unfollow(&workspace.active_pane().clone(), cx)
+//     });
+//     workspace_a.update(cx_a, |workspace, cx| {
+//         workspace.activate_item(&editor_a2, cx)
+//     });
+//     executor.run_until_parked();
+//     assert_eq!(
+//         workspace_b.update(cx_b, |workspace, cx| workspace
+//             .active_item(cx)
+//             .unwrap()
+//             .item_id()),
+//         editor_b1.item_id()
+//     );
+
+//     // Client A starts following client B.
+//     workspace_a
+//         .update(cx_a, |workspace, cx| {
+//             workspace.follow(peer_id_b, cx).unwrap()
+//         })
+//         .await
+//         .unwrap();
+//     assert_eq!(
+//         workspace_a.update(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
+//         Some(peer_id_b)
+//     );
+//     assert_eq!(
+//         workspace_a.update(cx_a, |workspace, cx| workspace
+//             .active_item(cx)
+//             .unwrap()
+//             .item_id()),
+//         editor_a1.item_id()
+//     );
+
+//     // Client B activates an external window, which causes a new screen-sharing item to be added to the pane.
+//     let display = MacOSDisplay::new();
+//     active_call_b
+//         .update(cx_b, |call, cx| call.set_location(None, cx))
+//         .await
+//         .unwrap();
+//     active_call_b
+//         .update(cx_b, |call, cx| {
+//             call.room().unwrap().update(cx, |room, cx| {
+//                 room.set_display_sources(vec![display.clone()]);
+//                 room.share_screen(cx)
+//             })
+//         })
+//         .await
+//         .unwrap();
+//     executor.run_until_parked();
+//     let shared_screen = workspace_a.update(cx_a, |workspace, cx| {
+//         workspace
+//             .active_item(cx)
+//             .expect("no active item")
+//             .downcast::<SharedScreen>()
+//             .expect("active item isn't a shared screen")
+//     });
+
+//     // Client B activates Zed again, which causes the previous editor to become focused again.
+//     active_call_b
+//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
+//         .await
+//         .unwrap();
+//     executor.run_until_parked();
+//     workspace_a.update(cx_a, |workspace, cx| {
+//         assert_eq!(
+//             workspace.active_item(cx).unwrap().item_id(),
+//             editor_a1.item_id()
+//         )
+//     });
+
+//     // Client B activates a multibuffer that was created by following client A. Client A returns to that multibuffer.
+//     workspace_b.update(cx_b, |workspace, cx| {
+//         workspace.activate_item(&multibuffer_editor_b, cx)
+//     });
+//     executor.run_until_parked();
+//     workspace_a.update(cx_a, |workspace, cx| {
+//         assert_eq!(
+//             workspace.active_item(cx).unwrap().item_id(),
+//             multibuffer_editor_a.item_id()
+//         )
+//     });
+
+//     // Client B activates a panel, and the previously-opened screen-sharing item gets activated.
+//     let panel = window_b.build_view(cx_b, |_| TestPanel::new(DockPosition::Left));
+//     workspace_b.update(cx_b, |workspace, cx| {
+//         workspace.add_panel(panel, cx);
+//         workspace.toggle_panel_focus::<TestPanel>(cx);
+//     });
+//     executor.run_until_parked();
+//     assert_eq!(
+//         workspace_a.update(cx_a, |workspace, cx| workspace
+//             .active_item(cx)
+//             .unwrap()
+//             .item_id()),
+//         shared_screen.item_id()
+//     );
+
+//     // Toggling the focus back to the pane causes client A to return to the multibuffer.
+//     workspace_b.update(cx_b, |workspace, cx| {
+//         workspace.toggle_panel_focus::<TestPanel>(cx);
+//     });
+//     executor.run_until_parked();
+//     workspace_a.update(cx_a, |workspace, cx| {
+//         assert_eq!(
+//             workspace.active_item(cx).unwrap().item_id(),
+//             multibuffer_editor_a.item_id()
+//         )
+//     });
+
+//     // Client B activates an item that doesn't implement following,
+//     // so the previously-opened screen-sharing item gets activated.
+//     let unfollowable_item = window_b.build_view(cx_b, |_| TestItem::new());
+//     workspace_b.update(cx_b, |workspace, cx| {
+//         workspace.active_pane().update(cx, |pane, cx| {
+//             pane.add_item(Box::new(unfollowable_item), true, true, None, cx)
+//         })
+//     });
+//     executor.run_until_parked();
+//     assert_eq!(
+//         workspace_a.update(cx_a, |workspace, cx| workspace
+//             .active_item(cx)
+//             .unwrap()
+//             .item_id()),
+//         shared_screen.item_id()
+//     );
+
+//     // Following interrupts when client B disconnects.
+//     client_b.disconnect(&cx_b.to_async());
+//     executor.advance_clock(RECONNECT_TIMEOUT);
+//     assert_eq!(
+//         workspace_a.update(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
+//         None
+//     );
+// }
+
+// #[gpui::test]
+// async fn test_following_tab_order(
+//     executor: BackgroundExecutor,
+//     cx_a: &mut TestAppContext,
+//     cx_b: &mut TestAppContext,
+// ) {
+//     let mut server = TestServer::start(executor.clone()).await;
+//     let client_a = server.create_client(cx_a, "user_a").await;
+//     let client_b = server.create_client(cx_b, "user_b").await;
+//     server
+//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+//         .await;
+//     let active_call_a = cx_a.read(ActiveCall::global);
+//     let active_call_b = cx_b.read(ActiveCall::global);
+
+//     cx_a.update(editor::init);
+//     cx_b.update(editor::init);
+
+//     client_a
+//         .fs()
+//         .insert_tree(
+//             "/a",
+//             json!({
+//                 "1.txt": "one",
+//                 "2.txt": "two",
+//                 "3.txt": "three",
+//             }),
+//         )
+//         .await;
+//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+//     active_call_a
+//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
+//         .await
+//         .unwrap();
+
+//     let project_id = active_call_a
+//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+//         .await
+//         .unwrap();
+//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
+//     active_call_b
+//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
+//         .await
+//         .unwrap();
+
+//     let workspace_a = client_a
+//         .build_workspace(&project_a, cx_a)
+//         .root(cx_a)
+//         .unwrap();
+//     let pane_a = workspace_a.update(cx_a, |workspace, _| workspace.active_pane().clone());
+
+//     let workspace_b = client_b
+//         .build_workspace(&project_b, cx_b)
+//         .root(cx_b)
+//         .unwrap();
+//     let pane_b = workspace_b.update(cx_b, |workspace, _| workspace.active_pane().clone());
+
+//     let client_b_id = project_a.update(cx_a, |project, _| {
+//         project.collaborators().values().next().unwrap().peer_id
+//     });
+
+//     //Open 1, 3 in that order on client A
+//     workspace_a
+//         .update(cx_a, |workspace, cx| {
+//             workspace.open_path((worktree_id, "1.txt"), None, true, cx)
+//         })
+//         .await
+//         .unwrap();
+//     workspace_a
+//         .update(cx_a, |workspace, cx| {
+//             workspace.open_path((worktree_id, "3.txt"), None, true, cx)
+//         })
+//         .await
+//         .unwrap();
+
+//     let pane_paths = |pane: &View<workspace::Pane>, cx: &mut TestAppContext| {
+//         pane.update(cx, |pane, cx| {
+//             pane.items()
+//                 .map(|item| {
+//                     item.project_path(cx)
+//                         .unwrap()
+//                         .path
+//                         .to_str()
+//                         .unwrap()
+//                         .to_owned()
+//                 })
+//                 .collect::<Vec<_>>()
+//         })
+//     };
+
+//     //Verify that the tabs opened in the order we expect
+//     assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt"]);
+
+//     //Follow client B as client A
+//     workspace_a
+//         .update(cx_a, |workspace, cx| {
+//             workspace.follow(client_b_id, cx).unwrap()
+//         })
+//         .await
+//         .unwrap();
+
+//     //Open just 2 on client B
+//     workspace_b
+//         .update(cx_b, |workspace, cx| {
+//             workspace.open_path((worktree_id, "2.txt"), None, true, cx)
+//         })
+//         .await
+//         .unwrap();
+//     executor.run_until_parked();
+
+//     // Verify that newly opened followed file is at the end
+//     assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt", "2.txt"]);
+
+//     //Open just 1 on client B
+//     workspace_b
+//         .update(cx_b, |workspace, cx| {
+//             workspace.open_path((worktree_id, "1.txt"), None, true, cx)
+//         })
+//         .await
+//         .unwrap();
+//     assert_eq!(&pane_paths(&pane_b, cx_b), &["2.txt", "1.txt"]);
+//     executor.run_until_parked();
+
+//     // Verify that following into 1 did not reorder
+//     assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt", "2.txt"]);
+// }
+
+// #[gpui::test(iterations = 10)]
+// async fn test_peers_following_each_other(
+//     executor: BackgroundExecutor,
+//     cx_a: &mut TestAppContext,
+//     cx_b: &mut TestAppContext,
+// ) {
+//     let mut server = TestServer::start(executor.clone()).await;
+//     let client_a = server.create_client(cx_a, "user_a").await;
+//     let client_b = server.create_client(cx_b, "user_b").await;
+//     server
+//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+//         .await;
+//     let active_call_a = cx_a.read(ActiveCall::global);
+//     let active_call_b = cx_b.read(ActiveCall::global);
+
+//     cx_a.update(editor::init);
+//     cx_b.update(editor::init);
+
+//     // Client A shares a project.
+//     client_a
+//         .fs()
+//         .insert_tree(
+//             "/a",
+//             json!({
+//                 "1.txt": "one",
+//                 "2.txt": "two",
+//                 "3.txt": "three",
+//                 "4.txt": "four",
+//             }),
+//         )
+//         .await;
+//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+//     active_call_a
+//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
+//         .await
+//         .unwrap();
+//     let project_id = active_call_a
+//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+//         .await
+//         .unwrap();
+
+//     // Client B joins the project.
+//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
+//     active_call_b
+//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
+//         .await
+//         .unwrap();
+
+//     // Client A opens a file.
+//     let workspace_a = client_a
+//         .build_workspace(&project_a, cx_a)
+//         .root(cx_a)
+//         .unwrap();
+//     workspace_a
+//         .update(cx_a, |workspace, cx| {
+//             workspace.open_path((worktree_id, "1.txt"), None, true, cx)
+//         })
+//         .await
+//         .unwrap()
+//         .downcast::<Editor>()
+//         .unwrap();
+
+//     // Client B opens a different file.
+//     let workspace_b = client_b
+//         .build_workspace(&project_b, cx_b)
+//         .root(cx_b)
+//         .unwrap();
+//     workspace_b
+//         .update(cx_b, |workspace, cx| {
+//             workspace.open_path((worktree_id, "2.txt"), None, true, cx)
+//         })
+//         .await
+//         .unwrap()
+//         .downcast::<Editor>()
+//         .unwrap();
+
+//     // Clients A and B follow each other in split panes
+//     workspace_a.update(cx_a, |workspace, cx| {
+//         workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx);
+//     });
+//     workspace_a
+//         .update(cx_a, |workspace, cx| {
+//             workspace.follow(client_b.peer_id().unwrap(), cx).unwrap()
+//         })
+//         .await
+//         .unwrap();
+//     workspace_b.update(cx_b, |workspace, cx| {
+//         workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx);
+//     });
+//     workspace_b
+//         .update(cx_b, |workspace, cx| {
+//             workspace.follow(client_a.peer_id().unwrap(), cx).unwrap()
+//         })
+//         .await
+//         .unwrap();
+
+//     // Clients A and B return focus to the original files they had open
+//     workspace_a.update(cx_a, |workspace, cx| workspace.activate_next_pane(cx));
+//     workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
+//     executor.run_until_parked();
+
+//     // Both clients see the other client's focused file in their right pane.
+//     assert_eq!(
+//         pane_summaries(&workspace_a, cx_a),
+//         &[
+//             PaneSummary {
+//                 active: true,
+//                 leader: None,
+//                 items: vec![(true, "1.txt".into())]
+//             },
+//             PaneSummary {
+//                 active: false,
+//                 leader: client_b.peer_id(),
+//                 items: vec![(false, "1.txt".into()), (true, "2.txt".into())]
+//             },
+//         ]
+//     );
+//     assert_eq!(
+//         pane_summaries(&workspace_b, cx_b),
+//         &[
+//             PaneSummary {
+//                 active: true,
+//                 leader: None,
+//                 items: vec![(true, "2.txt".into())]
+//             },
+//             PaneSummary {
+//                 active: false,
+//                 leader: client_a.peer_id(),
+//                 items: vec![(false, "2.txt".into()), (true, "1.txt".into())]
+//             },
+//         ]
+//     );
+
+//     // Clients A and B each open a new file.
+//     workspace_a
+//         .update(cx_a, |workspace, cx| {
+//             workspace.open_path((worktree_id, "3.txt"), None, true, cx)
+//         })
+//         .await
+//         .unwrap();
+
+//     workspace_b
+//         .update(cx_b, |workspace, cx| {
+//             workspace.open_path((worktree_id, "4.txt"), None, true, cx)
+//         })
+//         .await
+//         .unwrap();
+//     executor.run_until_parked();
+
+//     // Both client's see the other client open the new file, but keep their
+//     // focus on their own active pane.
+//     assert_eq!(
+//         pane_summaries(&workspace_a, cx_a),
+//         &[
+//             PaneSummary {
+//                 active: true,
+//                 leader: None,
+//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
+//             },
+//             PaneSummary {
+//                 active: false,
+//                 leader: client_b.peer_id(),
+//                 items: vec![
+//                     (false, "1.txt".into()),
+//                     (false, "2.txt".into()),
+//                     (true, "4.txt".into())
+//                 ]
+//             },
+//         ]
+//     );
+//     assert_eq!(
+//         pane_summaries(&workspace_b, cx_b),
+//         &[
+//             PaneSummary {
+//                 active: true,
+//                 leader: None,
+//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
+//             },
+//             PaneSummary {
+//                 active: false,
+//                 leader: client_a.peer_id(),
+//                 items: vec![
+//                     (false, "2.txt".into()),
+//                     (false, "1.txt".into()),
+//                     (true, "3.txt".into())
+//                 ]
+//             },
+//         ]
+//     );
+
+//     // Client A focuses their right pane, in which they're following client B.
+//     workspace_a.update(cx_a, |workspace, cx| workspace.activate_next_pane(cx));
+//     executor.run_until_parked();
+
+//     // Client B sees that client A is now looking at the same file as them.
+//     assert_eq!(
+//         pane_summaries(&workspace_a, cx_a),
+//         &[
+//             PaneSummary {
+//                 active: false,
+//                 leader: None,
+//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
+//             },
+//             PaneSummary {
+//                 active: true,
+//                 leader: client_b.peer_id(),
+//                 items: vec![
+//                     (false, "1.txt".into()),
+//                     (false, "2.txt".into()),
+//                     (true, "4.txt".into())
+//                 ]
+//             },
+//         ]
+//     );
+//     assert_eq!(
+//         pane_summaries(&workspace_b, cx_b),
+//         &[
+//             PaneSummary {
+//                 active: true,
+//                 leader: None,
+//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
+//             },
+//             PaneSummary {
+//                 active: false,
+//                 leader: client_a.peer_id(),
+//                 items: vec![
+//                     (false, "2.txt".into()),
+//                     (false, "1.txt".into()),
+//                     (false, "3.txt".into()),
+//                     (true, "4.txt".into())
+//                 ]
+//             },
+//         ]
+//     );
+
+//     // Client B focuses their right pane, in which they're following client A,
+//     // who is following them.
+//     workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
+//     executor.run_until_parked();
+
+//     // Client A sees that client B is now looking at the same file as them.
+//     assert_eq!(
+//         pane_summaries(&workspace_b, cx_b),
+//         &[
+//             PaneSummary {
+//                 active: false,
+//                 leader: None,
+//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
+//             },
+//             PaneSummary {
+//                 active: true,
+//                 leader: client_a.peer_id(),
+//                 items: vec![
+//                     (false, "2.txt".into()),
+//                     (false, "1.txt".into()),
+//                     (false, "3.txt".into()),
+//                     (true, "4.txt".into())
+//                 ]
+//             },
+//         ]
+//     );
+//     assert_eq!(
+//         pane_summaries(&workspace_a, cx_a),
+//         &[
+//             PaneSummary {
+//                 active: false,
+//                 leader: None,
+//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
+//             },
+//             PaneSummary {
+//                 active: true,
+//                 leader: client_b.peer_id(),
+//                 items: vec![
+//                     (false, "1.txt".into()),
+//                     (false, "2.txt".into()),
+//                     (true, "4.txt".into())
+//                 ]
+//             },
+//         ]
+//     );
+
+//     // Client B focuses a file that they previously followed A to, breaking
+//     // the follow.
+//     workspace_b.update(cx_b, |workspace, cx| {
+//         workspace.active_pane().update(cx, |pane, cx| {
+//             pane.activate_prev_item(true, cx);
+//         });
+//     });
+//     executor.run_until_parked();
+
+//     // Both clients see that client B is looking at that previous file.
+//     assert_eq!(
+//         pane_summaries(&workspace_b, cx_b),
+//         &[
+//             PaneSummary {
+//                 active: false,
+//                 leader: None,
+//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
+//             },
+//             PaneSummary {
+//                 active: true,
+//                 leader: None,
+//                 items: vec![
+//                     (false, "2.txt".into()),
+//                     (false, "1.txt".into()),
+//                     (true, "3.txt".into()),
+//                     (false, "4.txt".into())
+//                 ]
+//             },
+//         ]
+//     );
+//     assert_eq!(
+//         pane_summaries(&workspace_a, cx_a),
+//         &[
+//             PaneSummary {
+//                 active: false,
+//                 leader: None,
+//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
+//             },
+//             PaneSummary {
+//                 active: true,
+//                 leader: client_b.peer_id(),
+//                 items: vec![
+//                     (false, "1.txt".into()),
+//                     (false, "2.txt".into()),
+//                     (false, "4.txt".into()),
+//                     (true, "3.txt".into()),
+//                 ]
+//             },
+//         ]
+//     );
+
+//     // Client B closes tabs, some of which were originally opened by client A,
+//     // and some of which were originally opened by client B.
+//     workspace_b.update(cx_b, |workspace, cx| {
+//         workspace.active_pane().update(cx, |pane, cx| {
+//             pane.close_inactive_items(&Default::default(), cx)
+//                 .unwrap()
+//                 .detach();
+//         });
+//     });
+
+//     executor.run_until_parked();
+
+//     // Both clients see that Client B is looking at the previous tab.
+//     assert_eq!(
+//         pane_summaries(&workspace_b, cx_b),
+//         &[
+//             PaneSummary {
+//                 active: false,
+//                 leader: None,
+//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
+//             },
+//             PaneSummary {
+//                 active: true,
+//                 leader: None,
+//                 items: vec![(true, "3.txt".into()),]
+//             },
+//         ]
+//     );
+//     assert_eq!(
+//         pane_summaries(&workspace_a, cx_a),
+//         &[
+//             PaneSummary {
+//                 active: false,
+//                 leader: None,
+//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
+//             },
+//             PaneSummary {
+//                 active: true,
+//                 leader: client_b.peer_id(),
+//                 items: vec![
+//                     (false, "1.txt".into()),
+//                     (false, "2.txt".into()),
+//                     (false, "4.txt".into()),
+//                     (true, "3.txt".into()),
+//                 ]
+//             },
+//         ]
+//     );
+
+//     // Client B follows client A again.
+//     workspace_b
+//         .update(cx_b, |workspace, cx| {
+//             workspace.follow(client_a.peer_id().unwrap(), cx).unwrap()
+//         })
+//         .await
+//         .unwrap();
+
+//     // Client A cycles through some tabs.
+//     workspace_a.update(cx_a, |workspace, cx| {
+//         workspace.active_pane().update(cx, |pane, cx| {
+//             pane.activate_prev_item(true, cx);
+//         });
+//     });
+//     executor.run_until_parked();
+
+//     // Client B follows client A into those tabs.
+//     assert_eq!(
+//         pane_summaries(&workspace_a, cx_a),
+//         &[
+//             PaneSummary {
+//                 active: false,
+//                 leader: None,
+//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
+//             },
+//             PaneSummary {
+//                 active: true,
+//                 leader: None,
+//                 items: vec![
+//                     (false, "1.txt".into()),
+//                     (false, "2.txt".into()),
+//                     (true, "4.txt".into()),
+//                     (false, "3.txt".into()),
+//                 ]
+//             },
+//         ]
+//     );
+//     assert_eq!(
+//         pane_summaries(&workspace_b, cx_b),
+//         &[
+//             PaneSummary {
+//                 active: false,
+//                 leader: None,
+//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
+//             },
+//             PaneSummary {
+//                 active: true,
+//                 leader: client_a.peer_id(),
+//                 items: vec![(false, "3.txt".into()), (true, "4.txt".into())]
+//             },
+//         ]
+//     );
+
+//     workspace_a.update(cx_a, |workspace, cx| {
+//         workspace.active_pane().update(cx, |pane, cx| {
+//             pane.activate_prev_item(true, cx);
+//         });
+//     });
+//     executor.run_until_parked();
+
+//     assert_eq!(
+//         pane_summaries(&workspace_a, cx_a),
+//         &[
+//             PaneSummary {
+//                 active: false,
+//                 leader: None,
+//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
+//             },
+//             PaneSummary {
+//                 active: true,
+//                 leader: None,
+//                 items: vec![
+//                     (false, "1.txt".into()),
+//                     (true, "2.txt".into()),
+//                     (false, "4.txt".into()),
+//                     (false, "3.txt".into()),
+//                 ]
+//             },
+//         ]
+//     );
+//     assert_eq!(
+//         pane_summaries(&workspace_b, cx_b),
+//         &[
+//             PaneSummary {
+//                 active: false,
+//                 leader: None,
+//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
+//             },
+//             PaneSummary {
+//                 active: true,
+//                 leader: client_a.peer_id(),
+//                 items: vec![
+//                     (false, "3.txt".into()),
+//                     (false, "4.txt".into()),
+//                     (true, "2.txt".into())
+//                 ]
+//             },
+//         ]
+//     );
+
+//     workspace_a.update(cx_a, |workspace, cx| {
+//         workspace.active_pane().update(cx, |pane, cx| {
+//             pane.activate_prev_item(true, cx);
+//         });
+//     });
+//     executor.run_until_parked();
+
+//     assert_eq!(
+//         pane_summaries(&workspace_a, cx_a),
+//         &[
+//             PaneSummary {
+//                 active: false,
+//                 leader: None,
+//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
+//             },
+//             PaneSummary {
+//                 active: true,
+//                 leader: None,
+//                 items: vec![
+//                     (true, "1.txt".into()),
+//                     (false, "2.txt".into()),
+//                     (false, "4.txt".into()),
+//                     (false, "3.txt".into()),
+//                 ]
+//             },
+//         ]
+//     );
+//     assert_eq!(
+//         pane_summaries(&workspace_b, cx_b),
+//         &[
+//             PaneSummary {
+//                 active: false,
+//                 leader: None,
+//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
+//             },
+//             PaneSummary {
+//                 active: true,
+//                 leader: client_a.peer_id(),
+//                 items: vec![
+//                     (false, "3.txt".into()),
+//                     (false, "4.txt".into()),
+//                     (false, "2.txt".into()),
+//                     (true, "1.txt".into()),
+//                 ]
+//             },
+//         ]
+//     );
+// }
+
+// #[gpui::test(iterations = 10)]
+// async fn test_auto_unfollowing(
+//     executor: BackgroundExecutor,
+//     cx_a: &mut TestAppContext,
+//     cx_b: &mut TestAppContext,
+// ) {
+//     // 2 clients connect to a server.
+//     let mut server = TestServer::start(executor.clone()).await;
+//     let client_a = server.create_client(cx_a, "user_a").await;
+//     let client_b = server.create_client(cx_b, "user_b").await;
+//     server
+//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+//         .await;
+//     let active_call_a = cx_a.read(ActiveCall::global);
+//     let active_call_b = cx_b.read(ActiveCall::global);
+
+//     cx_a.update(editor::init);
+//     cx_b.update(editor::init);
+
+//     // Client A shares a project.
+//     client_a
+//         .fs()
+//         .insert_tree(
+//             "/a",
+//             json!({
+//                 "1.txt": "one",
+//                 "2.txt": "two",
+//                 "3.txt": "three",
+//             }),
+//         )
+//         .await;
+//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+//     active_call_a
+//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
+//         .await
+//         .unwrap();
+
+//     let project_id = active_call_a
+//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+//         .await
+//         .unwrap();
+//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
+//     active_call_b
+//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
+//         .await
+//         .unwrap();
+
+//     todo!("could be wrong")
+//     let mut cx_a = VisualTestContext::from_window(*window_a, cx_a);
+//     let cx_a = &mut cx_a;
+//     let mut cx_b = VisualTestContext::from_window(*window_b, cx_b);
+//     let cx_b = &mut cx_b;
+
+//     // Client A opens some editors.
+//     let workspace_a = client_a
+//         .build_workspace(&project_a, cx_a)
+//         .root(cx_a)
+//         .unwrap();
+//     let _editor_a1 = workspace_a
+//         .update(cx_a, |workspace, cx| {
+//             workspace.open_path((worktree_id, "1.txt"), None, true, cx)
+//         })
+//         .await
+//         .unwrap()
+//         .downcast::<Editor>()
+//         .unwrap();
+
+//     // Client B starts following client A.
+//     let workspace_b = client_b
+//         .build_workspace(&project_b, cx_b)
+//         .root(cx_b)
+//         .unwrap();
+//     let pane_b = workspace_b.update(cx_b, |workspace, _| workspace.active_pane().clone());
+//     let leader_id = project_b.update(cx_b, |project, _| {
+//         project.collaborators().values().next().unwrap().peer_id
+//     });
+//     workspace_b
+//         .update(cx_b, |workspace, cx| {
+//             workspace.follow(leader_id, cx).unwrap()
+//         })
+//         .await
+//         .unwrap();
+//     assert_eq!(
+//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
+//         Some(leader_id)
+//     );
+//     let editor_b2 = workspace_b.update(cx_b, |workspace, cx| {
+//         workspace
+//             .active_item(cx)
+//             .unwrap()
+//             .downcast::<Editor>()
+//             .unwrap()
+//     });
+
+//     // When client B moves, it automatically stops following client A.
+//     editor_b2.update(cx_b, |editor, cx| editor.move_right(&editor::MoveRight, cx));
+//     assert_eq!(
+//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
+//         None
+//     );
+
+//     workspace_b
+//         .update(cx_b, |workspace, cx| {
+//             workspace.follow(leader_id, cx).unwrap()
+//         })
+//         .await
+//         .unwrap();
+//     assert_eq!(
+//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
+//         Some(leader_id)
+//     );
+
+//     // When client B edits, it automatically stops following client A.
+//     editor_b2.update(cx_b, |editor, cx| editor.insert("X", cx));
+//     assert_eq!(
+//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
+//         None
+//     );
+
+//     workspace_b
+//         .update(cx_b, |workspace, cx| {
+//             workspace.follow(leader_id, cx).unwrap()
+//         })
+//         .await
+//         .unwrap();
+//     assert_eq!(
+//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
+//         Some(leader_id)
+//     );
+
+//     // When client B scrolls, it automatically stops following client A.
+//     editor_b2.update(cx_b, |editor, cx| {
+//         editor.set_scroll_position(point(0., 3.), cx)
+//     });
+//     assert_eq!(
+//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
+//         None
+//     );
+
+//     workspace_b
+//         .update(cx_b, |workspace, cx| {
+//             workspace.follow(leader_id, cx).unwrap()
+//         })
+//         .await
+//         .unwrap();
+//     assert_eq!(
+//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
+//         Some(leader_id)
+//     );
+
+//     // When client B activates a different pane, it continues following client A in the original pane.
+//     workspace_b.update(cx_b, |workspace, cx| {
+//         workspace.split_and_clone(pane_b.clone(), SplitDirection::Right, cx)
+//     });
+//     assert_eq!(
+//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
+//         Some(leader_id)
+//     );
+
+//     workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
+//     assert_eq!(
+//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
+//         Some(leader_id)
+//     );
+
+//     // When client B activates a different item in the original pane, it automatically stops following client A.
+//     workspace_b
+//         .update(cx_b, |workspace, cx| {
+//             workspace.open_path((worktree_id, "2.txt"), None, true, cx)
+//         })
+//         .await
+//         .unwrap();
+//     assert_eq!(
+//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
+//         None
+//     );
+// }
+
+// #[gpui::test(iterations = 10)]
+// async fn test_peers_simultaneously_following_each_other(
+//     executor: BackgroundExecutor,
+//     cx_a: &mut TestAppContext,
+//     cx_b: &mut TestAppContext,
+// ) {
+//     let mut server = TestServer::start(executor.clone()).await;
+//     let client_a = server.create_client(cx_a, "user_a").await;
+//     let client_b = server.create_client(cx_b, "user_b").await;
+//     server
+//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+//         .await;
+//     let active_call_a = cx_a.read(ActiveCall::global);
+
+//     cx_a.update(editor::init);
+//     cx_b.update(editor::init);
+
+//     client_a.fs().insert_tree("/a", json!({})).await;
+//     let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
+//     let workspace_a = client_a
+//         .build_workspace(&project_a, cx_a)
+//         .root(cx_a)
+//         .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;
+//     let workspace_b = client_b
+//         .build_workspace(&project_b, cx_b)
+//         .root(cx_b)
+//         .unwrap();
+
+//     executor.run_until_parked();
+//     let client_a_id = project_b.update(cx_b, |project, _| {
+//         project.collaborators().values().next().unwrap().peer_id
+//     });
+//     let client_b_id = project_a.update(cx_a, |project, _| {
+//         project.collaborators().values().next().unwrap().peer_id
+//     });
+
+//     let a_follow_b = workspace_a.update(cx_a, |workspace, cx| {
+//         workspace.follow(client_b_id, cx).unwrap()
+//     });
+//     let b_follow_a = workspace_b.update(cx_b, |workspace, cx| {
+//         workspace.follow(client_a_id, cx).unwrap()
+//     });
+
+//     futures::try_join!(a_follow_b, b_follow_a).unwrap();
+//     workspace_a.update(cx_a, |workspace, _| {
+//         assert_eq!(
+//             workspace.leader_for_pane(workspace.active_pane()),
+//             Some(client_b_id)
+//         );
+//     });
+//     workspace_b.update(cx_b, |workspace, _| {
+//         assert_eq!(
+//             workspace.leader_for_pane(workspace.active_pane()),
+//             Some(client_a_id)
+//         );
+//     });
+// }
+
+// #[gpui::test(iterations = 10)]
+// async fn test_following_across_workspaces(
+//     executor: BackgroundExecutor,
+//     cx_a: &mut TestAppContext,
+//     cx_b: &mut TestAppContext,
+// ) {
+//     // a and b join a channel/call
+//     // a shares project 1
+//     // b shares project 2
+//     //
+//     // b follows a: causes project 2 to be joined, and b to follow a.
+//     // b opens a different file in project 2, a follows b
+//     // b opens a different file in project 1, a cannot follow b
+//     // b shares the project, a joins the project and follows b
+//     let mut server = TestServer::start(executor.clone()).await;
+//     let client_a = server.create_client(cx_a, "user_a").await;
+//     let client_b = server.create_client(cx_b, "user_b").await;
+//     cx_a.update(editor::init);
+//     cx_b.update(editor::init);
+
+//     client_a
+//         .fs()
+//         .insert_tree(
+//             "/a",
+//             json!({
+//                 "w.rs": "",
+//                 "x.rs": "",
+//             }),
+//         )
+//         .await;
+
+//     client_b
+//         .fs()
+//         .insert_tree(
+//             "/b",
+//             json!({
+//                 "y.rs": "",
+//                 "z.rs": "",
+//             }),
+//         )
+//         .await;
+
+//     server
+//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+//         .await;
+//     let active_call_a = cx_a.read(ActiveCall::global);
+//     let active_call_b = cx_b.read(ActiveCall::global);
+
+//     let (project_a, worktree_id_a) = client_a.build_local_project("/a", cx_a).await;
+//     let (project_b, worktree_id_b) = client_b.build_local_project("/b", cx_b).await;
+
+//     let workspace_a = client_a
+//         .build_workspace(&project_a, cx_a)
+//         .root(cx_a)
+//         .unwrap();
+//     let workspace_b = client_b
+//         .build_workspace(&project_b, cx_b)
+//         .root(cx_b)
+//         .unwrap();
+
+//     cx_a.update(|cx| collab_ui::init(&client_a.app_state, cx));
+//     cx_b.update(|cx| collab_ui::init(&client_b.app_state, cx));
+
+//     active_call_a
+//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+//         .await
+//         .unwrap();
+
+//     active_call_a
+//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
+//         .await
+//         .unwrap();
+//     active_call_b
+//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
+//         .await
+//         .unwrap();
+
+//     todo!("could be wrong")
+//     let mut cx_a = VisualTestContext::from_window(*window_a, cx_a);
+//     let cx_a = &mut cx_a;
+//     let mut cx_b = VisualTestContext::from_window(*window_b, cx_b);
+//     let cx_b = &mut cx_b;
+
+//     workspace_a
+//         .update(cx_a, |workspace, cx| {
+//             workspace.open_path((worktree_id_a, "w.rs"), None, true, cx)
+//         })
+//         .await
+//         .unwrap();
+
+//     executor.run_until_parked();
+//     assert_eq!(visible_push_notifications(cx_b).len(), 1);
+
+//     workspace_b.update(cx_b, |workspace, cx| {
+//         workspace
+//             .follow(client_a.peer_id().unwrap(), cx)
+//             .unwrap()
+//             .detach()
+//     });
+
+//     executor.run_until_parked();
+//     let workspace_b_project_a = cx_b
+//         .windows()
+//         .iter()
+//         .max_by_key(|window| window.item_id())
+//         .unwrap()
+//         .downcast::<Workspace>()
+//         .unwrap()
+//         .root(cx_b)
+//         .unwrap();
+
+//     // assert that b is following a in project a in w.rs
+//     workspace_b_project_a.update(cx_b, |workspace, cx| {
+//         assert!(workspace.is_being_followed(client_a.peer_id().unwrap()));
+//         assert_eq!(
+//             client_a.peer_id(),
+//             workspace.leader_for_pane(workspace.active_pane())
+//         );
+//         let item = workspace.active_item(cx).unwrap();
+//         assert_eq!(item.tab_description(0, cx).unwrap(), Cow::Borrowed("w.rs"));
+//     });
+
+//     // TODO: in app code, this would be done by the collab_ui.
+//     active_call_b
+//         .update(cx_b, |call, cx| {
+//             let project = workspace_b_project_a.read(cx).project().clone();
+//             call.set_location(Some(&project), cx)
+//         })
+//         .await
+//         .unwrap();
+
+//     // assert that there are no share notifications open
+//     assert_eq!(visible_push_notifications(cx_b).len(), 0);
+
+//     // b moves to x.rs in a's project, and a follows
+//     workspace_b_project_a
+//         .update(cx_b, |workspace, cx| {
+//             workspace.open_path((worktree_id_a, "x.rs"), None, true, cx)
+//         })
+//         .await
+//         .unwrap();
+
+//     executor.run_until_parked();
+//     workspace_b_project_a.update(cx_b, |workspace, cx| {
+//         let item = workspace.active_item(cx).unwrap();
+//         assert_eq!(item.tab_description(0, cx).unwrap(), Cow::Borrowed("x.rs"));
+//     });
+
+//     workspace_a.update(cx_a, |workspace, cx| {
+//         workspace
+//             .follow(client_b.peer_id().unwrap(), cx)
+//             .unwrap()
+//             .detach()
+//     });
+
+//     executor.run_until_parked();
+//     workspace_a.update(cx_a, |workspace, cx| {
+//         assert!(workspace.is_being_followed(client_b.peer_id().unwrap()));
+//         assert_eq!(
+//             client_b.peer_id(),
+//             workspace.leader_for_pane(workspace.active_pane())
+//         );
+//         let item = workspace.active_pane().read(cx).active_item().unwrap();
+//         assert_eq!(item.tab_description(0, cx).unwrap(), "x.rs".into());
+//     });
+
+//     // b moves to y.rs in b's project, a is still following but can't yet see
+//     workspace_b
+//         .update(cx_b, |workspace, cx| {
+//             workspace.open_path((worktree_id_b, "y.rs"), None, true, cx)
+//         })
+//         .await
+//         .unwrap();
+
+//     // TODO: in app code, this would be done by the collab_ui.
+//     active_call_b
+//         .update(cx_b, |call, cx| {
+//             let project = workspace_b.read(cx).project().clone();
+//             call.set_location(Some(&project), cx)
+//         })
+//         .await
+//         .unwrap();
+
+//     let project_b_id = active_call_b
+//         .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
+//         .await
+//         .unwrap();
+
+//     executor.run_until_parked();
+//     assert_eq!(visible_push_notifications(cx_a).len(), 1);
+//     cx_a.update(|cx| {
+//         workspace::join_remote_project(
+//             project_b_id,
+//             client_b.user_id().unwrap(),
+//             client_a.app_state.clone(),
+//             cx,
+//         )
+//     })
+//     .await
+//     .unwrap();
+
+//     executor.run_until_parked();
+
+//     assert_eq!(visible_push_notifications(cx_a).len(), 0);
+//     let workspace_a_project_b = cx_a
+//         .windows()
+//         .iter()
+//         .max_by_key(|window| window.item_id())
+//         .unwrap()
+//         .downcast::<Workspace>()
+//         .unwrap()
+//         .root(cx_a)
+//         .unwrap();
+
+//     workspace_a_project_b.update(cx_a, |workspace, cx| {
+//         assert_eq!(workspace.project().read(cx).remote_id(), Some(project_b_id));
+//         assert!(workspace.is_being_followed(client_b.peer_id().unwrap()));
+//         assert_eq!(
+//             client_b.peer_id(),
+//             workspace.leader_for_pane(workspace.active_pane())
+//         );
+//         let item = workspace.active_item(cx).unwrap();
+//         assert_eq!(item.tab_description(0, cx).unwrap(), Cow::Borrowed("y.rs"));
+//     });
+// }
+
+// #[gpui::test]
+// async fn test_following_into_excluded_file(
+//     executor: BackgroundExecutor,
+//     mut cx_a: &mut TestAppContext,
+//     mut cx_b: &mut TestAppContext,
+// ) {
+//     let mut server = TestServer::start(executor.clone()).await;
+//     let client_a = server.create_client(cx_a, "user_a").await;
+//     let client_b = server.create_client(cx_b, "user_b").await;
+//     for cx in [&mut cx_a, &mut cx_b] {
+//         cx.update(|cx| {
+//             cx.update_global::<SettingsStore, _>(|store, cx| {
+//                 store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
+//                     project_settings.file_scan_exclusions = Some(vec!["**/.git".to_string()]);
+//                 });
+//             });
+//         });
+//     }
+//     server
+//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+//         .await;
+//     let active_call_a = cx_a.read(ActiveCall::global);
+//     let active_call_b = cx_b.read(ActiveCall::global);
+
+//     cx_a.update(editor::init);
+//     cx_b.update(editor::init);
+
+//     client_a
+//         .fs()
+//         .insert_tree(
+//             "/a",
+//             json!({
+//                 ".git": {
+//                     "COMMIT_EDITMSG": "write your commit message here",
+//                 },
+//                 "1.txt": "one\none\none",
+//                 "2.txt": "two\ntwo\ntwo",
+//                 "3.txt": "three\nthree\nthree",
+//             }),
+//         )
+//         .await;
+//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+//     active_call_a
+//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
+//         .await
+//         .unwrap();
+
+//     let project_id = active_call_a
+//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+//         .await
+//         .unwrap();
+//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
+//     active_call_b
+//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
+//         .await
+//         .unwrap();
+
+//     let window_a = client_a.build_workspace(&project_a, cx_a);
+//     let workspace_a = window_a.root(cx_a).unwrap();
+//     let peer_id_a = client_a.peer_id().unwrap();
+//     let window_b = client_b.build_workspace(&project_b, cx_b);
+//     let workspace_b = window_b.root(cx_b).unwrap();
+
+//     todo!("could be wrong")
+//     let mut cx_a = VisualTestContext::from_window(*window_a, cx_a);
+//     let cx_a = &mut cx_a;
+//     let mut cx_b = VisualTestContext::from_window(*window_b, cx_b);
+//     let cx_b = &mut cx_b;
+
+//     // Client A opens editors for a regular file and an excluded file.
+//     let editor_for_regular = workspace_a
+//         .update(cx_a, |workspace, cx| {
+//             workspace.open_path((worktree_id, "1.txt"), None, true, cx)
+//         })
+//         .await
+//         .unwrap()
+//         .downcast::<Editor>()
+//         .unwrap();
+//     let editor_for_excluded_a = workspace_a
+//         .update(cx_a, |workspace, cx| {
+//             workspace.open_path((worktree_id, ".git/COMMIT_EDITMSG"), None, true, cx)
+//         })
+//         .await
+//         .unwrap()
+//         .downcast::<Editor>()
+//         .unwrap();
+
+//     // Client A updates their selections in those editors
+//     editor_for_regular.update(cx_a, |editor, cx| {
+//         editor.handle_input("a", cx);
+//         editor.handle_input("b", cx);
+//         editor.handle_input("c", cx);
+//         editor.select_left(&Default::default(), cx);
+//         assert_eq!(editor.selections.ranges(cx), vec![3..2]);
+//     });
+//     editor_for_excluded_a.update(cx_a, |editor, cx| {
+//         editor.select_all(&Default::default(), cx);
+//         editor.handle_input("new commit message", cx);
+//         editor.select_left(&Default::default(), cx);
+//         assert_eq!(editor.selections.ranges(cx), vec![18..17]);
+//     });
+
+//     // When client B starts following client A, currently visible file is replicated
+//     workspace_b
+//         .update(cx_b, |workspace, cx| {
+//             workspace.follow(peer_id_a, cx).unwrap()
+//         })
+//         .await
+//         .unwrap();
+
+//     let editor_for_excluded_b = workspace_b.update(cx_b, |workspace, cx| {
+//         workspace
+//             .active_item(cx)
+//             .unwrap()
+//             .downcast::<Editor>()
+//             .unwrap()
+//     });
+//     assert_eq!(
+//         cx_b.read(|cx| editor_for_excluded_b.project_path(cx)),
+//         Some((worktree_id, ".git/COMMIT_EDITMSG").into())
+//     );
+//     assert_eq!(
+//         editor_for_excluded_b.update(cx_b, |editor, cx| editor.selections.ranges(cx)),
+//         vec![18..17]
+//     );
+
+//     // Changes from B to the excluded file are replicated in A's editor
+//     editor_for_excluded_b.update(cx_b, |editor, cx| {
+//         editor.handle_input("\nCo-Authored-By: B <b@b.b>", cx);
+//     });
+//     executor.run_until_parked();
+//     editor_for_excluded_a.update(cx_a, |editor, cx| {
+//         assert_eq!(
+//             editor.text(cx),
+//             "new commit messag\nCo-Authored-By: B <b@b.b>"
+//         );
+//     });
+// }
+
+// fn visible_push_notifications(
+//     cx: &mut TestAppContext,
+// ) -> Vec<gpui::View<ProjectSharedNotification>> {
+//     let mut ret = Vec::new();
+//     for window in cx.windows() {
+//         window.update(cx, |window| {
+//             if let Some(handle) = window
+//                 .root_view()
+//                 .clone()
+//                 .downcast::<ProjectSharedNotification>()
+//             {
+//                 ret.push(handle)
+//             }
+//         });
+//     }
+//     ret
+// }
+
+// #[derive(Debug, PartialEq, Eq)]
+// struct PaneSummary {
+//     active: bool,
+//     leader: Option<PeerId>,
+//     items: Vec<(bool, String)>,
+// }
+
+// fn followers_by_leader(project_id: u64, cx: &TestAppContext) -> Vec<(PeerId, Vec<PeerId>)> {
+//     cx.read(|cx| {
+//         let active_call = ActiveCall::global(cx).read(cx);
+//         let peer_id = active_call.client().peer_id();
+//         let room = active_call.room().unwrap().read(cx);
+//         let mut result = room
+//             .remote_participants()
+//             .values()
+//             .map(|participant| participant.peer_id)
+//             .chain(peer_id)
+//             .filter_map(|peer_id| {
+//                 let followers = room.followers_for(peer_id, project_id);
+//                 if followers.is_empty() {
+//                     None
+//                 } else {
+//                     Some((peer_id, followers.to_vec()))
+//                 }
+//             })
+//             .collect::<Vec<_>>();
+//         result.sort_by_key(|e| e.0);
+//         result
+//     })
+// }
+
+// fn pane_summaries(workspace: &View<Workspace>, cx: &mut WindowContext<'_>) -> Vec<PaneSummary> {
+//     workspace.update(cx, |workspace, cx| {
+//         let active_pane = workspace.active_pane();
+//         workspace
+//             .panes()
+//             .iter()
+//             .map(|pane| {
+//                 let leader = workspace.leader_for_pane(pane);
+//                 let active = pane == active_pane;
+//                 let pane = pane.read(cx);
+//                 let active_ix = pane.active_item_index();
+//                 PaneSummary {
+//                     active,
+//                     leader,
+//                     items: pane
+//                         .items()
+//                         .enumerate()
+//                         .map(|(ix, item)| {
+//                             (
+//                                 ix == active_ix,
+//                                 item.tab_description(0, cx)
+//                                     .map_or(String::new(), |s| s.to_string()),
+//                             )
+//                         })
+//                         .collect(),
+//                 }
+//             })
+//             .collect()
+//     })
+// }

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

@@ -5,18 +5,13 @@ use crate::{
 use call::{room, ActiveCall, ParticipantLocation, Room};
 use client::{User, RECEIVE_TIMEOUT};
 use collections::{HashMap, HashSet};
-use editor::{
-    test::editor_test_context::EditorTestContext, ConfirmCodeAction, ConfirmCompletion,
-    ConfirmRename, Editor, Redo, Rename, ToggleCodeActions, Undo,
-};
 use fs::{repository::GitFileStatus, FakeFs, Fs as _, RemoveOptions};
 use futures::StreamExt as _;
-use gpui::{executor::Deterministic, test::EmptyView, AppContext, ModelHandle, TestAppContext};
-use indoc::indoc;
+use gpui::{AppContext, BackgroundExecutor, Model, TestAppContext};
 use language::{
-    language_settings::{AllLanguageSettings, Formatter, InlayHintSettings},
-    tree_sitter_rust, Anchor, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language,
-    LanguageConfig, LineEnding, OffsetRangeExt, Point, Rope,
+    language_settings::{AllLanguageSettings, Formatter},
+    tree_sitter_rust, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language, LanguageConfig,
+    LineEnding, OffsetRangeExt, Point, Rope,
 };
 use live_kit_client::MacOSDisplay;
 use lsp::LanguageServerId;
@@ -32,12 +27,11 @@ use std::{
     path::{Path, PathBuf},
     rc::Rc,
     sync::{
-        atomic::{self, AtomicBool, AtomicUsize, Ordering::SeqCst},
+        atomic::{AtomicBool, Ordering::SeqCst},
         Arc,
     },
 };
 use unindent::Unindent as _;
-use workspace::Workspace;
 
 #[ctor::ctor]
 fn init_logger() {
@@ -48,14 +42,13 @@ fn init_logger() {
 
 #[gpui::test(iterations = 10)]
 async fn test_basic_calls(
-    deterministic: Arc<Deterministic>,
+    executor: BackgroundExecutor,
     cx_a: &mut TestAppContext,
     cx_b: &mut TestAppContext,
     cx_b2: &mut TestAppContext,
     cx_c: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(executor.clone()).await;
 
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
@@ -76,7 +69,7 @@ async fn test_basic_calls(
         .await
         .unwrap();
     let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert_eq!(
         room_participants(&room_a, cx_a),
         RoomParticipants {
@@ -86,6 +79,7 @@ async fn test_basic_calls(
     );
 
     // User B receives the call.
+
     let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
     let call_b = incoming_call_b.next().await.unwrap().unwrap();
     assert_eq!(call_b.calling_user.github_login, "user_a");
@@ -93,8 +87,9 @@ async fn test_basic_calls(
     // User B connects via another client and also receives a ring on the newly-connected client.
     let _client_b2 = server.create_client(cx_b2, "user_b").await;
     let active_call_b2 = cx_b2.read(ActiveCall::global);
+
     let mut incoming_call_b2 = active_call_b2.read_with(cx_b2, |call, _| call.incoming());
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     let call_b2 = incoming_call_b2.next().await.unwrap().unwrap();
     assert_eq!(call_b2.calling_user.github_login, "user_a");
 
@@ -103,10 +98,11 @@ async fn test_basic_calls(
         .update(cx_b, |call, cx| call.accept_incoming(cx))
         .await
         .unwrap();
+
     let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
     assert!(incoming_call_b.next().await.unwrap().is_none());
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert_eq!(
         room_participants(&room_a, cx_a),
         RoomParticipants {
@@ -123,6 +119,7 @@ async fn test_basic_calls(
     );
 
     // Call user C from client B.
+
     let mut incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
     active_call_b
         .update(cx_b, |call, cx| {
@@ -131,7 +128,7 @@ async fn test_basic_calls(
         .await
         .unwrap();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert_eq!(
         room_participants(&room_a, cx_a),
         RoomParticipants {
@@ -153,7 +150,7 @@ async fn test_basic_calls(
     active_call_c.update(cx_c, |call, cx| call.decline_incoming(cx).unwrap());
     assert!(incoming_call_c.next().await.unwrap().is_none());
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert_eq!(
         room_participants(&room_a, cx_a),
         RoomParticipants {
@@ -177,7 +174,7 @@ async fn test_basic_calls(
         .await
         .unwrap();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert_eq!(
         room_participants(&room_a, cx_a),
         RoomParticipants {
@@ -201,9 +198,10 @@ async fn test_basic_calls(
         .await
         .unwrap();
     assert!(incoming_call_c.next().await.unwrap().is_none());
+
     let room_c = active_call_c.read_with(cx_c, |call, _| call.room().unwrap().clone());
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert_eq!(
         room_participants(&room_a, cx_a),
         RoomParticipants {
@@ -240,13 +238,14 @@ async fn test_basic_calls(
         .await
         .unwrap();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
     // User B observes the remote screen sharing track.
     assert_eq!(events_b.borrow().len(), 1);
     let event_b = events_b.borrow().first().unwrap().clone();
     if let call::room::Event::RemoteVideoTracksChanged { participant_id } = event_b {
         assert_eq!(participant_id, client_a.peer_id().unwrap());
+
         room_b.read_with(cx_b, |room, _| {
             assert_eq!(
                 room.remote_participants()[&client_a.user_id().unwrap()]
@@ -264,6 +263,7 @@ async fn test_basic_calls(
     let event_c = events_c.borrow().first().unwrap().clone();
     if let call::room::Event::RemoteVideoTracksChanged { participant_id } = event_c {
         assert_eq!(participant_id, client_a.peer_id().unwrap());
+
         room_c.read_with(cx_c, |room, _| {
             assert_eq!(
                 room.remote_participants()[&client_a.user_id().unwrap()]
@@ -285,7 +285,7 @@ async fn test_basic_calls(
         })
         .await
         .unwrap();
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert_eq!(
         room_participants(&room_a, cx_a),
         RoomParticipants {
@@ -315,8 +315,10 @@ async fn test_basic_calls(
         .test_live_kit_server
         .disconnect_client(client_b.user_id().unwrap().to_string())
         .await;
-    deterministic.run_until_parked();
+    executor.run_until_parked();
+
     active_call_b.read_with(cx_b, |call, _| assert!(call.room().is_none()));
+
     active_call_c.read_with(cx_c, |call, _| assert!(call.room().is_none()));
     assert_eq!(
         room_participants(&room_a, cx_a),
@@ -343,14 +345,13 @@ async fn test_basic_calls(
 
 #[gpui::test(iterations = 10)]
 async fn test_calling_multiple_users_simultaneously(
-    deterministic: Arc<Deterministic>,
+    executor: BackgroundExecutor,
     cx_a: &mut TestAppContext,
     cx_b: &mut TestAppContext,
     cx_c: &mut TestAppContext,
     cx_d: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(executor.clone()).await;
 
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
@@ -381,7 +382,7 @@ async fn test_calling_multiple_users_simultaneously(
     c_invite.await.unwrap();
 
     let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert_eq!(
         room_participants(&room_a, cx_a),
         RoomParticipants {
@@ -397,7 +398,7 @@ async fn test_calling_multiple_users_simultaneously(
         })
         .await
         .unwrap();
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert_eq!(
         room_participants(&room_a, cx_a),
         RoomParticipants {
@@ -418,10 +419,12 @@ async fn test_calling_multiple_users_simultaneously(
     accept_c.await.unwrap();
     accept_d.await.unwrap();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
     let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
+
     let room_c = active_call_c.read_with(cx_c, |call, _| call.room().unwrap().clone());
+
     let room_d = active_call_d.read_with(cx_d, |call, _| call.room().unwrap().clone());
     assert_eq!(
         room_participants(&room_a, cx_a),
@@ -471,13 +474,12 @@ async fn test_calling_multiple_users_simultaneously(
 
 #[gpui::test(iterations = 10)]
 async fn test_joining_channels_and_calling_multiple_users_simultaneously(
-    deterministic: Arc<Deterministic>,
+    executor: BackgroundExecutor,
     cx_a: &mut TestAppContext,
     cx_b: &mut TestAppContext,
     cx_c: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(executor.clone()).await;
 
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
@@ -515,7 +517,7 @@ async fn test_joining_channels_and_calling_multiple_users_simultaneously(
     join_channel_2.await.unwrap();
 
     let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
     assert_eq!(channel_id(&room_a, cx_a), Some(channel_2));
 
@@ -543,7 +545,7 @@ async fn test_joining_channels_and_calling_multiple_users_simultaneously(
     join_channel.await.unwrap();
 
     let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
     assert_eq!(
         room_participants(&room_a, cx_a),
@@ -579,20 +581,19 @@ async fn test_joining_channels_and_calling_multiple_users_simultaneously(
     c_invite.await.unwrap();
 
     active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 }
 
 #[gpui::test(iterations = 10)]
 async fn test_room_uniqueness(
-    deterministic: Arc<Deterministic>,
+    executor: BackgroundExecutor,
     cx_a: &mut TestAppContext,
     cx_a2: &mut TestAppContext,
     cx_b: &mut TestAppContext,
     cx_b2: &mut TestAppContext,
     cx_c: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(executor.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let _client_a2 = server.create_client(cx_a2, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
@@ -623,9 +624,11 @@ async fn test_room_uniqueness(
         })
         .await
         .unwrap_err();
+
     active_call_a2.read_with(cx_a2, |call, _| assert!(call.room().is_none()));
 
     // User B receives the call from user A.
+
     let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
     let call_b1 = incoming_call_b.next().await.unwrap().unwrap();
     assert_eq!(call_b1.calling_user.github_login, "user_a");
@@ -651,6 +654,7 @@ async fn test_room_uniqueness(
         })
         .await
         .unwrap_err();
+
     active_call_b2.read_with(cx_b2, |call, _| assert!(call.room().is_none()));
 
     // User B joins the room and calling them after they've joined still fails.
@@ -672,6 +676,7 @@ async fn test_room_uniqueness(
         })
         .await
         .unwrap_err();
+
     active_call_b2.read_with(cx_b2, |call, _| assert!(call.room().is_none()));
 
     // Client C can successfully call client B after client B leaves the room.
@@ -679,26 +684,25 @@ async fn test_room_uniqueness(
         .update(cx_b, |call, cx| call.hang_up(cx))
         .await
         .unwrap();
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     active_call_c
         .update(cx_c, |call, cx| {
             call.invite(client_b.user_id().unwrap(), None, cx)
         })
         .await
         .unwrap();
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     let call_b2 = incoming_call_b.next().await.unwrap().unwrap();
     assert_eq!(call_b2.calling_user.github_login, "user_c");
 }
 
 #[gpui::test(iterations = 10)]
 async fn test_client_disconnecting_from_room(
-    deterministic: Arc<Deterministic>,
+    executor: BackgroundExecutor,
     cx_a: &mut TestAppContext,
     cx_b: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(executor.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     server
@@ -715,17 +719,20 @@ async fn test_client_disconnecting_from_room(
         })
         .await
         .unwrap();
+
     let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 
     // User B receives the call and joins the room.
+
     let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
     incoming_call_b.next().await.unwrap().unwrap();
     active_call_b
         .update(cx_b, |call, cx| call.accept_incoming(cx))
         .await
         .unwrap();
+
     let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert_eq!(
         room_participants(&room_a, cx_a),
         RoomParticipants {
@@ -743,8 +750,8 @@ async fn test_client_disconnecting_from_room(
 
     // User A automatically reconnects to the room upon disconnection.
     server.disconnect_client(client_a.peer_id().unwrap());
-    deterministic.advance_clock(RECEIVE_TIMEOUT);
-    deterministic.run_until_parked();
+    executor.advance_clock(RECEIVE_TIMEOUT);
+    executor.run_until_parked();
     assert_eq!(
         room_participants(&room_a, cx_a),
         RoomParticipants {
@@ -763,8 +770,10 @@ async fn test_client_disconnecting_from_room(
     // When user A disconnects, both client A and B clear their room on the active call.
     server.forbid_connections();
     server.disconnect_client(client_a.peer_id().unwrap());
-    deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
+    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
+
     active_call_a.read_with(cx_a, |call, _| assert!(call.room().is_none()));
+
     active_call_b.read_with(cx_b, |call, _| assert!(call.room().is_none()));
     assert_eq!(
         room_participants(&room_a, cx_a),
@@ -783,7 +792,7 @@ async fn test_client_disconnecting_from_room(
 
     // Allow user A to reconnect to the server.
     server.allow_connections();
-    deterministic.advance_clock(RECEIVE_TIMEOUT);
+    executor.advance_clock(RECEIVE_TIMEOUT);
 
     // Call user B again from client A.
     active_call_a
@@ -792,17 +801,20 @@ async fn test_client_disconnecting_from_room(
         })
         .await
         .unwrap();
+
     let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 
     // User B receives the call and joins the room.
+
     let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
     incoming_call_b.next().await.unwrap().unwrap();
     active_call_b
         .update(cx_b, |call, cx| call.accept_incoming(cx))
         .await
         .unwrap();
+
     let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert_eq!(
         room_participants(&room_a, cx_a),
         RoomParticipants {
@@ -824,7 +836,7 @@ async fn test_client_disconnecting_from_room(
         .test_live_kit_server
         .disconnect_client(client_b.user_id().unwrap().to_string())
         .await;
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     active_call_a.update(cx_a, |call, _| assert!(call.room().is_none()));
     active_call_b.update(cx_b, |call, _| assert!(call.room().is_none()));
     assert_eq!(
@@ -845,14 +857,13 @@ async fn test_client_disconnecting_from_room(
 
 #[gpui::test(iterations = 10)]
 async fn test_server_restarts(
-    deterministic: Arc<Deterministic>,
+    executor: BackgroundExecutor,
     cx_a: &mut TestAppContext,
     cx_b: &mut TestAppContext,
     cx_c: &mut TestAppContext,
     cx_d: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(executor.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     client_a
         .fs()
@@ -898,31 +909,37 @@ async fn test_server_restarts(
         })
         .await
         .unwrap();
+
     let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 
     // User B receives the call and joins the room.
+
     let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
     assert!(incoming_call_b.next().await.unwrap().is_some());
     active_call_b
         .update(cx_b, |call, cx| call.accept_incoming(cx))
         .await
         .unwrap();
+
     let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
 
     // User C receives the call and joins the room.
+
     let mut incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
     assert!(incoming_call_c.next().await.unwrap().is_some());
     active_call_c
         .update(cx_c, |call, cx| call.accept_incoming(cx))
         .await
         .unwrap();
+
     let room_c = active_call_c.read_with(cx_c, |call, _| call.room().unwrap().clone());
 
     // User D receives the call but doesn't join the room yet.
+
     let mut incoming_call_d = active_call_d.read_with(cx_d, |call, _| call.incoming());
     assert!(incoming_call_d.next().await.unwrap().is_some());
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert_eq!(
         room_participants(&room_a, cx_a),
         RoomParticipants {
@@ -950,7 +967,7 @@ async fn test_server_restarts(
 
     // Users A and B reconnect to the call. User C has troubles reconnecting, so it leaves the room.
     client_c.override_establish_connection(|_, cx| cx.spawn(|_| future::pending()));
-    deterministic.advance_clock(RECONNECT_TIMEOUT);
+    executor.advance_clock(RECONNECT_TIMEOUT);
     assert_eq!(
         room_participants(&room_a, cx_a),
         RoomParticipants {
@@ -979,7 +996,8 @@ async fn test_server_restarts(
         .update(cx_d, |call, cx| call.accept_incoming(cx))
         .await
         .unwrap();
-    deterministic.run_until_parked();
+    executor.run_until_parked();
+
     let room_d = active_call_d.read_with(cx_d, |call, _| call.room().unwrap().clone());
     assert_eq!(
         room_participants(&room_a, cx_a),
@@ -1024,7 +1042,7 @@ async fn test_server_restarts(
 
     // The server finishes restarting, cleaning up stale connections.
     server.start().await.unwrap();
-    deterministic.advance_clock(CLEANUP_TIMEOUT);
+    executor.advance_clock(CLEANUP_TIMEOUT);
     assert_eq!(
         room_participants(&room_a, cx_a),
         RoomParticipants {
@@ -1059,7 +1077,7 @@ async fn test_server_restarts(
         .update(cx_d, |call, cx| call.hang_up(cx))
         .await
         .unwrap();
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert_eq!(
         room_participants(&room_a, cx_a),
         RoomParticipants {
@@ -1098,9 +1116,10 @@ async fn test_server_restarts(
         .unwrap();
 
     // User D receives the call but doesn't join the room yet.
+
     let mut incoming_call_d = active_call_d.read_with(cx_d, |call, _| call.incoming());
     assert!(incoming_call_d.next().await.unwrap().is_some());
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert_eq!(
         room_participants(&room_a, cx_a),
         RoomParticipants {
@@ -1123,7 +1142,7 @@ async fn test_server_restarts(
     client_a.override_establish_connection(|_, cx| cx.spawn(|_| future::pending()));
     client_b.override_establish_connection(|_, cx| cx.spawn(|_| future::pending()));
     client_c.override_establish_connection(|_, cx| cx.spawn(|_| future::pending()));
-    deterministic.advance_clock(RECONNECT_TIMEOUT);
+    executor.advance_clock(RECONNECT_TIMEOUT);
     assert_eq!(
         room_participants(&room_a, cx_a),
         RoomParticipants {
@@ -1145,19 +1164,18 @@ async fn test_server_restarts(
     // The server finishes restarting, cleaning up stale connections and canceling the
     // call to user D because the room has become empty.
     server.start().await.unwrap();
-    deterministic.advance_clock(CLEANUP_TIMEOUT);
+    executor.advance_clock(CLEANUP_TIMEOUT);
     assert!(incoming_call_d.next().await.unwrap().is_none());
 }
 
 #[gpui::test(iterations = 10)]
 async fn test_calls_on_multiple_connections(
-    deterministic: Arc<Deterministic>,
+    executor: BackgroundExecutor,
     cx_a: &mut TestAppContext,
     cx_b1: &mut TestAppContext,
     cx_b2: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(executor.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b1 = server.create_client(cx_b1, "user_b").await;
     let client_b2 = server.create_client(cx_b2, "user_b").await;
@@ -1168,7 +1186,9 @@ async fn test_calls_on_multiple_connections(
     let active_call_a = cx_a.read(ActiveCall::global);
     let active_call_b1 = cx_b1.read(ActiveCall::global);
     let active_call_b2 = cx_b2.read(ActiveCall::global);
+
     let mut incoming_call_b1 = active_call_b1.read_with(cx_b1, |call, _| call.incoming());
+
     let mut incoming_call_b2 = active_call_b2.read_with(cx_b2, |call, _| call.incoming());
     assert!(incoming_call_b1.next().await.unwrap().is_none());
     assert!(incoming_call_b2.next().await.unwrap().is_none());
@@ -1180,14 +1200,14 @@ async fn test_calls_on_multiple_connections(
         })
         .await
         .unwrap();
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert!(incoming_call_b1.next().await.unwrap().is_some());
     assert!(incoming_call_b2.next().await.unwrap().is_some());
 
     // User B declines the call on one of the two connections, causing both connections
     // to stop ringing.
     active_call_b2.update(cx_b2, |call, cx| call.decline_incoming(cx).unwrap());
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert!(incoming_call_b1.next().await.unwrap().is_none());
     assert!(incoming_call_b2.next().await.unwrap().is_none());
 
@@ -1198,7 +1218,7 @@ async fn test_calls_on_multiple_connections(
         })
         .await
         .unwrap();
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert!(incoming_call_b1.next().await.unwrap().is_some());
     assert!(incoming_call_b2.next().await.unwrap().is_some());
 
@@ -1208,13 +1228,13 @@ async fn test_calls_on_multiple_connections(
         .update(cx_b2, |call, cx| call.accept_incoming(cx))
         .await
         .unwrap();
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert!(incoming_call_b1.next().await.unwrap().is_none());
     assert!(incoming_call_b2.next().await.unwrap().is_none());
 
     // User B disconnects the client that is not on the call. Everything should be fine.
     client_b1.disconnect(&cx_b1.to_async());
-    deterministic.advance_clock(RECEIVE_TIMEOUT);
+    executor.advance_clock(RECEIVE_TIMEOUT);
     client_b1
         .authenticate_and_connect(false, &cx_b1.to_async())
         .await
@@ -1225,14 +1245,14 @@ async fn test_calls_on_multiple_connections(
         .update(cx_b2, |call, cx| call.hang_up(cx))
         .await
         .unwrap();
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     active_call_a
         .update(cx_a, |call, cx| {
             call.invite(client_b1.user_id().unwrap(), None, cx)
         })
         .await
         .unwrap();
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert!(incoming_call_b1.next().await.unwrap().is_some());
     assert!(incoming_call_b2.next().await.unwrap().is_some());
 
@@ -1243,7 +1263,7 @@ async fn test_calls_on_multiple_connections(
         })
         .await
         .unwrap();
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert!(incoming_call_b1.next().await.unwrap().is_none());
     assert!(incoming_call_b2.next().await.unwrap().is_none());
 
@@ -1254,7 +1274,7 @@ async fn test_calls_on_multiple_connections(
         })
         .await
         .unwrap();
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert!(incoming_call_b1.next().await.unwrap().is_some());
     assert!(incoming_call_b2.next().await.unwrap().is_some());
 
@@ -1263,7 +1283,7 @@ async fn test_calls_on_multiple_connections(
         .update(cx_a, |call, cx| call.hang_up(cx))
         .await
         .unwrap();
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert!(incoming_call_b1.next().await.unwrap().is_none());
     assert!(incoming_call_b2.next().await.unwrap().is_none());
 
@@ -1274,27 +1294,27 @@ async fn test_calls_on_multiple_connections(
         })
         .await
         .unwrap();
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert!(incoming_call_b1.next().await.unwrap().is_some());
     assert!(incoming_call_b2.next().await.unwrap().is_some());
 
     // User A disconnects, causing both connections to stop ringing.
     server.forbid_connections();
     server.disconnect_client(client_a.peer_id().unwrap());
-    deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
+    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
     assert!(incoming_call_b1.next().await.unwrap().is_none());
     assert!(incoming_call_b2.next().await.unwrap().is_none());
 
     // User A reconnects automatically, then calls user B again.
     server.allow_connections();
-    deterministic.advance_clock(RECEIVE_TIMEOUT);
+    executor.advance_clock(RECEIVE_TIMEOUT);
     active_call_a
         .update(cx_a, |call, cx| {
             call.invite(client_b1.user_id().unwrap(), None, cx)
         })
         .await
         .unwrap();
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     assert!(incoming_call_b1.next().await.unwrap().is_some());
     assert!(incoming_call_b2.next().await.unwrap().is_some());
 
@@ -1302,187 +1322,19 @@ async fn test_calls_on_multiple_connections(
     server.forbid_connections();
     server.disconnect_client(client_b1.peer_id().unwrap());
     server.disconnect_client(client_b2.peer_id().unwrap());
-    deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
-    active_call_a.read_with(cx_a, |call, _| assert!(call.room().is_none()));
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_share_project(
-    deterministic: Arc<Deterministic>,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-    cx_c: &mut TestAppContext,
-) {
-    deterministic.forbid_parking();
-    let window_b = cx_b.add_window(|_| EmptyView);
-    let mut server = TestServer::start(&deterministic).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());
-    deterministic.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());
-
-    deterministic.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(cx).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(cx).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(cx).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
-    deterministic.run_until_parked();
-    buffer_a.read_with(cx_a, |buffer, _| {
-        buffer
-            .snapshot()
-            .remote_selections_in_range(Anchor::MIN..Anchor::MAX)
-            .count()
-            == 1
-    });
+    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
 
-    // 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));
-    deterministic.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());
-    deterministic.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));
-    deterministic.run_until_parked();
-    buffer_a.read_with(cx_a, |buffer, _| {
-        buffer
-            .snapshot()
-            .remote_selections_in_range(Anchor::MIN..Anchor::MAX)
-            .count()
-            == 0
-    });
+    active_call_a.read_with(cx_a, |call, _| assert!(call.room().is_none()));
 }
 
 #[gpui::test(iterations = 10)]
 async fn test_unshare_project(
-    deterministic: Arc<Deterministic>,
+    executor: BackgroundExecutor,
     cx_a: &mut TestAppContext,
     cx_b: &mut TestAppContext,
     cx_c: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(executor.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     let client_c = server.create_client(cx_c, "user_c").await;
@@ -1509,9 +1361,11 @@ async fn test_unshare_project(
         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
         .await
         .unwrap();
-    let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
+
+    let worktree_a = project_a.read_with(cx_a, |project, _| project.worktrees().next().unwrap());
     let project_b = client_b.build_remote_project(project_id, cx_b).await;
-    deterministic.run_until_parked();
+    executor.run_until_parked();
+
     assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
 
     project_b
@@ -1524,7 +1378,8 @@ async fn test_unshare_project(
         .update(cx_b, |call, cx| call.hang_up(cx))
         .await
         .unwrap();
-    deterministic.run_until_parked();
+    executor.run_until_parked();
+
     assert!(project_b.read_with(cx_b, |project, _| project.is_read_only()));
 
     // Client C opens the project.
@@ -1534,8 +1389,10 @@ async fn test_unshare_project(
     project_a
         .update(cx_a, |project, cx| project.unshare(cx))
         .unwrap();
-    deterministic.run_until_parked();
+    executor.run_until_parked();
+
     assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
+
     assert!(project_c.read_with(cx_c, |project, _| project.is_read_only()));
 
     // Client C can open the project again after client A re-shares.
@@ -1544,7 +1401,8 @@ async fn test_unshare_project(
         .await
         .unwrap();
     let project_c2 = client_c.build_remote_project(project_id, cx_c).await;
-    deterministic.run_until_parked();
+    executor.run_until_parked();
+
     assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
     project_c2
         .update(cx_c, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
@@ -1556,130 +1414,23 @@ async fn test_unshare_project(
         .update(cx_a, |call, cx| call.hang_up(cx))
         .await
         .unwrap();
-    deterministic.run_until_parked();
+    executor.run_until_parked();
+
     project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
+
     project_c2.read_with(cx_c, |project, _| {
         assert!(project.is_read_only());
         assert!(project.collaborators().is_empty());
     });
 }
 
-#[gpui::test(iterations = 10)]
-async fn test_host_disconnect(
-    deterministic: Arc<Deterministic>,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-    cx_c: &mut TestAppContext,
-) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).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;
-    deterministic.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());
-    deterministic.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();
-    deterministic.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();
-    deterministic.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());
-    deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
-    project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
-}
-
 #[gpui::test(iterations = 10)]
 async fn test_project_reconnect(
-    deterministic: Arc<Deterministic>,
+    executor: BackgroundExecutor,
     cx_a: &mut TestAppContext,
     cx_b: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(executor.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     server

crates/collab/src/tests/notification_tests.rs 🔗

@@ -1,18 +1,19 @@
-use crate::tests::TestServer;
-use gpui::{executor::Deterministic, TestAppContext};
+use std::sync::Arc;
+
+use gpui::{BackgroundExecutor, TestAppContext};
 use notifications::NotificationEvent;
 use parking_lot::Mutex;
 use rpc::{proto, Notification};
-use std::sync::Arc;
+
+use crate::tests::TestServer;
 
 #[gpui::test]
 async fn test_notifications(
-    deterministic: Arc<Deterministic>,
+    executor: BackgroundExecutor,
     cx_a: &mut TestAppContext,
     cx_b: &mut TestAppContext,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(executor.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
 
@@ -42,7 +43,7 @@ async fn test_notifications(
 
     // Client B receives a contact request notification and responds to the
     // request, accepting it.
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     client_b.notification_store().update(cx_b, |store, cx| {
         assert_eq!(store.notification_count(), 1);
         assert_eq!(store.unread_notification_count(), 1);
@@ -72,7 +73,7 @@ async fn test_notifications(
     });
 
     // Client B sees the notification is now read, and that they responded.
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     client_b.notification_store().read_with(cx_b, |store, _| {
         assert_eq!(store.notification_count(), 1);
         assert_eq!(store.unread_notification_count(), 0);
@@ -127,7 +128,7 @@ async fn test_notifications(
 
     // Client B receives a channel invitation notification and responds to the
     // invitation, accepting it.
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     client_b.notification_store().update(cx_b, |store, cx| {
         assert_eq!(store.notification_count(), 2);
         assert_eq!(store.unread_notification_count(), 1);
@@ -147,7 +148,7 @@ async fn test_notifications(
     });
 
     // Client B sees the notification is now read, and that they responded.
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     client_b.notification_store().read_with(cx_b, |store, _| {
         assert_eq!(store.notification_count(), 2);
         assert_eq!(store.unread_notification_count(), 0);

crates/collab/src/tests/random_channel_buffer_tests.rs 🔗

@@ -3,10 +3,14 @@ use crate::db::ChannelRole;
 use super::{run_randomized_test, RandomizedTest, TestClient, TestError, TestServer, UserTestPlan};
 use anyhow::Result;
 use async_trait::async_trait;
-use gpui::{executor::Deterministic, TestAppContext};
+use gpui::{BackgroundExecutor, SharedString, TestAppContext};
 use rand::prelude::*;
 use serde_derive::{Deserialize, Serialize};
-use std::{ops::Range, rc::Rc, sync::Arc};
+use std::{
+    ops::{Deref, DerefMut, Range},
+    rc::Rc,
+    sync::Arc,
+};
 use text::Bias;
 
 #[gpui::test(
@@ -15,10 +19,10 @@ use text::Bias;
 )]
 async fn test_random_channel_buffers(
     cx: &mut TestAppContext,
-    deterministic: Arc<Deterministic>,
+    executor: BackgroundExecutor,
     rng: StdRng,
 ) {
-    run_randomized_test::<RandomChannelBufferTest>(cx, deterministic, rng).await;
+    run_randomized_test::<RandomChannelBufferTest>(cx, executor, rng).await;
 }
 
 struct RandomChannelBufferTest;
@@ -26,13 +30,13 @@ struct RandomChannelBufferTest;
 #[derive(Clone, Serialize, Deserialize)]
 enum ChannelBufferOperation {
     JoinChannelNotes {
-        channel_name: String,
+        channel_name: SharedString,
     },
     LeaveChannelNotes {
-        channel_name: String,
+        channel_name: SharedString,
     },
     EditChannelNotes {
-        channel_name: String,
+        channel_name: SharedString,
         edits: Vec<(Range<usize>, Arc<str>)>,
     },
     Noop,
@@ -69,11 +73,11 @@ impl RandomizedTest for RandomChannelBufferTest {
         cx: &TestAppContext,
     ) -> ChannelBufferOperation {
         let channel_store = client.channel_store().clone();
-        let channel_buffers = client.channel_buffers();
+        let mut channel_buffers = client.channel_buffers();
 
         // When signed out, we can't do anything unless a channel buffer is
         // already open.
-        if channel_buffers.is_empty()
+        if channel_buffers.deref_mut().is_empty()
             && channel_store.read_with(cx, |store, _| store.channel_count() == 0)
         {
             return ChannelBufferOperation::Noop;
@@ -97,7 +101,7 @@ impl RandomizedTest for RandomChannelBufferTest {
                 }
 
                 30..=40 => {
-                    if let Some(buffer) = channel_buffers.iter().choose(rng) {
+                    if let Some(buffer) = channel_buffers.deref().iter().choose(rng) {
                         let channel_name =
                             buffer.read_with(cx, |b, cx| b.channel(cx).unwrap().name.clone());
                         break ChannelBufferOperation::LeaveChannelNotes { channel_name };
@@ -105,7 +109,7 @@ impl RandomizedTest for RandomChannelBufferTest {
                 }
 
                 _ => {
-                    if let Some(buffer) = channel_buffers.iter().choose(rng) {
+                    if let Some(buffer) = channel_buffers.deref().iter().choose(rng) {
                         break buffer.read_with(cx, |b, cx| {
                             let channel_name = b.channel(cx).unwrap().name.clone();
                             let edits = b
@@ -147,13 +151,13 @@ impl RandomizedTest for RandomChannelBufferTest {
                     "{}: opening notes for channel {channel_name}",
                     client.username
                 );
-                client.channel_buffers().insert(buffer.await?);
+                client.channel_buffers().deref_mut().insert(buffer.await?);
             }
 
             ChannelBufferOperation::LeaveChannelNotes { channel_name } => {
                 let buffer = cx.update(|cx| {
                     let mut left_buffer = Err(TestError::Inapplicable);
-                    client.channel_buffers().retain(|buffer| {
+                    client.channel_buffers().deref_mut().retain(|buffer| {
                         if buffer.read(cx).channel(cx).unwrap().name == channel_name {
                             left_buffer = Ok(buffer.clone());
                             false
@@ -179,6 +183,7 @@ impl RandomizedTest for RandomChannelBufferTest {
                     .read(|cx| {
                         client
                             .channel_buffers()
+                            .deref()
                             .iter()
                             .find(|buffer| {
                                 buffer.read(cx).channel(cx).unwrap().name == channel_name
@@ -215,13 +220,6 @@ impl RandomizedTest for RandomChannelBufferTest {
         Ok(())
     }
 
-    async fn on_client_added(client: &Rc<TestClient>, cx: &mut TestAppContext) {
-        let channel_store = client.channel_store();
-        while channel_store.read_with(cx, |store, _| store.channel_count() == 0) {
-            channel_store.next_notification(cx).await;
-        }
-    }
-
     async fn on_quiesce(server: &mut TestServer, clients: &mut [(Rc<TestClient>, TestAppContext)]) {
         let channels = server.app_state.db.all_channels().await.unwrap();
 
@@ -229,6 +227,7 @@ impl RandomizedTest for RandomChannelBufferTest {
             client_cx.update(|cx| {
                 client
                     .channel_buffers()
+                    .deref_mut()
                     .retain(|b| b.read(cx).is_connected());
             });
         }
@@ -252,6 +251,7 @@ impl RandomizedTest for RandomChannelBufferTest {
                 client_cx.read(|cx| {
                     if let Some(channel_buffer) = client
                         .channel_buffers()
+                        .deref()
                         .iter()
                         .find(|b| b.read(cx).channel_id == channel_id.to_proto())
                     {

crates/collab/src/tests/random_project_collaboration_tests.rs 🔗

@@ -1,5 +1,5 @@
-use super::{run_randomized_test, RandomizedTest, TestClient, TestError, TestServer, UserTestPlan};
-use crate::db::UserId;
+use super::{RandomizedTest, TestClient, TestError, TestServer, UserTestPlan};
+use crate::{db::UserId, tests::run_randomized_test};
 use anyhow::{anyhow, Result};
 use async_trait::async_trait;
 use call::ActiveCall;
@@ -7,7 +7,7 @@ use collections::{BTreeMap, HashMap};
 use editor::Bias;
 use fs::{repository::GitFileStatus, FakeFs, Fs as _};
 use futures::StreamExt;
-use gpui::{executor::Deterministic, ModelHandle, TestAppContext};
+use gpui::{BackgroundExecutor, Model, TestAppContext};
 use language::{range_to_lsp, FakeLspAdapter, Language, LanguageConfig, PointUtf16};
 use lsp::FakeLanguageServer;
 use pretty_assertions::assert_eq;
@@ -18,7 +18,7 @@ use rand::{
 };
 use serde::{Deserialize, Serialize};
 use std::{
-    ops::Range,
+    ops::{Deref, Range},
     path::{Path, PathBuf},
     rc::Rc,
     sync::Arc,
@@ -31,10 +31,10 @@ use util::ResultExt;
 )]
 async fn test_random_project_collaboration(
     cx: &mut TestAppContext,
-    deterministic: Arc<Deterministic>,
+    executor: BackgroundExecutor,
     rng: StdRng,
 ) {
-    run_randomized_test::<ProjectCollaborationTest>(cx, deterministic, rng).await;
+    run_randomized_test::<ProjectCollaborationTest>(cx, executor, rng).await;
 }
 
 #[derive(Clone, Debug, Serialize, Deserialize)]
@@ -295,7 +295,7 @@ impl RandomizedTest for ProjectCollaborationTest {
                             let is_local = project.read_with(cx, |project, _| project.is_local());
                             let worktree = project.read_with(cx, |project, cx| {
                                 project
-                                    .worktrees(cx)
+                                    .worktrees()
                                     .filter(|worktree| {
                                         let worktree = worktree.read(cx);
                                         worktree.is_visible()
@@ -417,7 +417,7 @@ impl RandomizedTest for ProjectCollaborationTest {
                         81.. => {
                             let worktree = project.read_with(cx, |project, cx| {
                                 project
-                                    .worktrees(cx)
+                                    .worktrees()
                                     .filter(|worktree| {
                                         let worktree = worktree.read(cx);
                                         worktree.is_visible()
@@ -624,7 +624,7 @@ impl RandomizedTest for ProjectCollaborationTest {
                             room.join_project(
                                 project_id,
                                 client.language_registry().clone(),
-                                FakeFs::new(cx.background().clone()),
+                                FakeFs::new(cx.background_executor().clone()),
                                 cx,
                             )
                         }))
@@ -782,6 +782,7 @@ impl RandomizedTest for ProjectCollaborationTest {
                         .map_err(|err| anyhow!("save request failed: {:?}", err))?;
                     assert!(buffer
                         .read_with(&cx, |buffer, _| { buffer.saved_version().to_owned() })
+                        .expect("App should not be dropped")
                         .observed_all(&requested_version));
                     anyhow::Ok(())
                 });
@@ -817,30 +818,30 @@ impl RandomizedTest for ProjectCollaborationTest {
 
                 use futures::{FutureExt as _, TryFutureExt as _};
                 let offset = buffer.read_with(cx, |b, _| b.clip_offset(offset, Bias::Left));
-                let request = cx.foreground().spawn(project.update(cx, |project, cx| {
-                    match kind {
-                        LspRequestKind::Rename => project
-                            .prepare_rename(buffer, offset, cx)
-                            .map_ok(|_| ())
-                            .boxed(),
-                        LspRequestKind::Completion => project
-                            .completions(&buffer, offset, cx)
-                            .map_ok(|_| ())
-                            .boxed(),
-                        LspRequestKind::CodeAction => project
-                            .code_actions(&buffer, offset..offset, cx)
-                            .map_ok(|_| ())
-                            .boxed(),
-                        LspRequestKind::Definition => project
-                            .definition(&buffer, offset, cx)
-                            .map_ok(|_| ())
-                            .boxed(),
-                        LspRequestKind::Highlights => project
-                            .document_highlights(&buffer, offset, cx)
-                            .map_ok(|_| ())
-                            .boxed(),
-                    }
-                }));
+
+                let process_lsp_request = project.update(cx, |project, cx| match kind {
+                    LspRequestKind::Rename => project
+                        .prepare_rename(buffer, offset, cx)
+                        .map_ok(|_| ())
+                        .boxed(),
+                    LspRequestKind::Completion => project
+                        .completions(&buffer, offset, cx)
+                        .map_ok(|_| ())
+                        .boxed(),
+                    LspRequestKind::CodeAction => project
+                        .code_actions(&buffer, offset..offset, cx)
+                        .map_ok(|_| ())
+                        .boxed(),
+                    LspRequestKind::Definition => project
+                        .definition(&buffer, offset, cx)
+                        .map_ok(|_| ())
+                        .boxed(),
+                    LspRequestKind::Highlights => project
+                        .document_highlights(&buffer, offset, cx)
+                        .map_ok(|_| ())
+                        .boxed(),
+                });
+                let request = cx.foreground_executor().spawn(process_lsp_request);
                 if detach {
                     request.detach();
                 } else {
@@ -874,7 +875,7 @@ impl RandomizedTest for ProjectCollaborationTest {
                     )
                 });
                 drop(project);
-                let search = cx.background().spawn(async move {
+                let search = cx.executor().spawn(async move {
                     let mut results = HashMap::default();
                     while let Some((buffer, ranges)) = search.next().await {
                         results.entry(buffer).or_insert(ranges);
@@ -1075,12 +1076,12 @@ impl RandomizedTest for ProjectCollaborationTest {
                         fake_server.handle_request::<lsp::request::GotoDefinition, _, _>({
                             let fs = fs.clone();
                             move |_, cx| {
-                                let background = cx.background();
+                                let background = cx.background_executor();
                                 let mut rng = background.rng();
                                 let count = rng.gen_range::<usize, _>(1..3);
                                 let files = fs.as_fake().files();
                                 let files = (0..count)
-                                    .map(|_| files.choose(&mut *rng).unwrap().clone())
+                                    .map(|_| files.choose(&mut rng).unwrap().clone())
                                     .collect::<Vec<_>>();
                                 async move {
                                     log::info!("LSP: Returning definitions in files {:?}", &files);
@@ -1100,7 +1101,7 @@ impl RandomizedTest for ProjectCollaborationTest {
                         fake_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>(
                             move |_, cx| {
                                 let mut highlights = Vec::new();
-                                let background = cx.background();
+                                let background = cx.background_executor();
                                 let mut rng = background.rng();
 
                                 let highlight_count = rng.gen_range(1..=5);
@@ -1153,7 +1154,7 @@ impl RandomizedTest for ProjectCollaborationTest {
                                 let host_worktree_snapshots =
                                     host_project.read_with(host_cx, |host_project, cx| {
                                         host_project
-                                            .worktrees(cx)
+                                            .worktrees()
                                             .map(|worktree| {
                                                 let worktree = worktree.read(cx);
                                                 (worktree.id(), worktree.snapshot())
@@ -1161,7 +1162,7 @@ impl RandomizedTest for ProjectCollaborationTest {
                                             .collect::<BTreeMap<_, _>>()
                                     });
                                 let guest_worktree_snapshots = guest_project
-                                    .worktrees(cx)
+                                    .worktrees()
                                     .map(|worktree| {
                                         let worktree = worktree.read(cx);
                                         (worktree.id(), worktree.snapshot())
@@ -1218,7 +1219,7 @@ impl RandomizedTest for ProjectCollaborationTest {
                             }
                         }
 
-                        for buffer in guest_project.opened_buffers(cx) {
+                        for buffer in guest_project.opened_buffers() {
                             let buffer = buffer.read(cx);
                             assert_eq!(
                                 buffer.deferred_ops_len(),
@@ -1268,8 +1269,8 @@ impl RandomizedTest for ProjectCollaborationTest {
                 for guest_buffer in guest_buffers {
                     let buffer_id =
                         guest_buffer.read_with(client_cx, |buffer, _| buffer.remote_id());
-                    let host_buffer = host_project.read_with(host_cx, |project, cx| {
-                        project.buffer_for_id(buffer_id, cx).unwrap_or_else(|| {
+                    let host_buffer = host_project.read_with(host_cx, |project, _| {
+                        project.buffer_for_id(buffer_id).unwrap_or_else(|| {
                             panic!(
                                 "host does not have buffer for guest:{}, peer:{:?}, id:{}",
                                 client.username,
@@ -1457,10 +1458,10 @@ fn generate_git_operation(rng: &mut StdRng, client: &TestClient) -> GitOperation
 
 fn buffer_for_full_path(
     client: &TestClient,
-    project: &ModelHandle<Project>,
+    project: &Model<Project>,
     full_path: &PathBuf,
     cx: &TestAppContext,
-) -> Option<ModelHandle<language::Buffer>> {
+) -> Option<Model<language::Buffer>> {
     client
         .buffers_for_project(project)
         .iter()
@@ -1476,18 +1477,18 @@ fn project_for_root_name(
     client: &TestClient,
     root_name: &str,
     cx: &TestAppContext,
-) -> Option<ModelHandle<Project>> {
-    if let Some(ix) = project_ix_for_root_name(&*client.local_projects(), root_name, cx) {
+) -> Option<Model<Project>> {
+    if let Some(ix) = project_ix_for_root_name(&*client.local_projects().deref(), root_name, cx) {
         return Some(client.local_projects()[ix].clone());
     }
-    if let Some(ix) = project_ix_for_root_name(&*client.remote_projects(), root_name, cx) {
+    if let Some(ix) = project_ix_for_root_name(&*client.remote_projects().deref(), root_name, cx) {
         return Some(client.remote_projects()[ix].clone());
     }
     None
 }
 
 fn project_ix_for_root_name(
-    projects: &[ModelHandle<Project>],
+    projects: &[Model<Project>],
     root_name: &str,
     cx: &TestAppContext,
 ) -> Option<usize> {
@@ -1499,7 +1500,7 @@ fn project_ix_for_root_name(
     })
 }
 
-fn root_name_for_project(project: &ModelHandle<Project>, cx: &TestAppContext) -> String {
+fn root_name_for_project(project: &Model<Project>, cx: &TestAppContext) -> String {
     project.read_with(cx, |project, cx| {
         project
             .visible_worktrees(cx)
@@ -1512,7 +1513,7 @@ fn root_name_for_project(project: &ModelHandle<Project>, cx: &TestAppContext) ->
 }
 
 fn project_path_for_full_path(
-    project: &ModelHandle<Project>,
+    project: &Model<Project>,
     full_path: &Path,
     cx: &TestAppContext,
 ) -> Option<ProjectPath> {
@@ -1520,7 +1521,7 @@ fn project_path_for_full_path(
     let root_name = components.next().unwrap().as_os_str().to_str().unwrap();
     let path = components.as_path().into();
     let worktree_id = project.read_with(cx, |project, cx| {
-        project.worktrees(cx).find_map(|worktree| {
+        project.worktrees().find_map(|worktree| {
             let worktree = worktree.read(cx);
             if worktree.root_name() == root_name {
                 Some(worktree.id())
@@ -1533,7 +1534,7 @@ fn project_path_for_full_path(
 }
 
 async fn ensure_project_shared(
-    project: &ModelHandle<Project>,
+    project: &Model<Project>,
     client: &TestClient,
     cx: &mut TestAppContext,
 ) {
@@ -1566,9 +1567,10 @@ async fn ensure_project_shared(
     }
 }
 
-fn choose_random_project(client: &TestClient, rng: &mut StdRng) -> Option<ModelHandle<Project>> {
+fn choose_random_project(client: &TestClient, rng: &mut StdRng) -> Option<Model<Project>> {
     client
         .local_projects()
+        .deref()
         .iter()
         .chain(client.remote_projects().iter())
         .choose(rng)

crates/collab/src/tests/randomized_test_helpers.rs 🔗

@@ -5,7 +5,7 @@ use crate::{
 };
 use async_trait::async_trait;
 use futures::StreamExt;
-use gpui::{executor::Deterministic, Task, TestAppContext};
+use gpui::{BackgroundExecutor, Task, TestAppContext};
 use parking_lot::Mutex;
 use rand::prelude::*;
 use rpc::RECEIVE_TIMEOUT;
@@ -115,18 +115,17 @@ pub trait RandomizedTest: 'static + Sized {
 
     async fn initialize(server: &mut TestServer, users: &[UserTestPlan]);
 
-    async fn on_client_added(client: &Rc<TestClient>, cx: &mut TestAppContext);
+    async fn on_client_added(_client: &Rc<TestClient>, _cx: &mut TestAppContext) {}
 
     async fn on_quiesce(server: &mut TestServer, client: &mut [(Rc<TestClient>, TestAppContext)]);
 }
 
 pub async fn run_randomized_test<T: RandomizedTest>(
     cx: &mut TestAppContext,
-    deterministic: Arc<Deterministic>,
+    executor: BackgroundExecutor,
     rng: StdRng,
 ) {
-    deterministic.forbid_parking();
-    let mut server = TestServer::start(&deterministic).await;
+    let mut server = TestServer::start(executor.clone()).await;
     let plan = TestPlan::<T>::new(&mut server, rng).await;
 
     LAST_PLAN.lock().replace({
@@ -144,7 +143,7 @@ pub async fn run_randomized_test<T: RandomizedTest>(
         applied.store(true, SeqCst);
         let did_apply = TestPlan::apply_server_operation(
             plan.clone(),
-            deterministic.clone(),
+            executor.clone(),
             &mut server,
             &mut clients,
             &mut client_tasks,
@@ -159,14 +158,14 @@ pub async fn run_randomized_test<T: RandomizedTest>(
     }
 
     drop(operation_channels);
-    deterministic.start_waiting();
+    executor.start_waiting();
     futures::future::join_all(client_tasks).await;
-    deterministic.finish_waiting();
+    executor.finish_waiting();
 
-    deterministic.run_until_parked();
+    executor.run_until_parked();
     T::on_quiesce(&mut server, &mut clients).await;
 
-    for (client, mut cx) in clients {
+    for (client, cx) in clients {
         cx.update(|cx| {
             let store = cx.remove_global::<SettingsStore>();
             cx.clear_globals();
@@ -174,7 +173,7 @@ pub async fn run_randomized_test<T: RandomizedTest>(
             drop(client);
         });
     }
-    deterministic.run_until_parked();
+    executor.run_until_parked();
 
     if let Some(path) = &*PLAN_SAVE_PATH {
         eprintln!("saved test plan to path {:?}", path);
@@ -450,7 +449,7 @@ impl<T: RandomizedTest> TestPlan<T> {
 
     async fn apply_server_operation(
         plan: Arc<Mutex<Self>>,
-        deterministic: Arc<Deterministic>,
+        deterministic: BackgroundExecutor,
         server: &mut TestServer,
         clients: &mut Vec<(Rc<TestClient>, TestAppContext)>,
         client_tasks: &mut Vec<Task<()>>,
@@ -471,28 +470,18 @@ impl<T: RandomizedTest> TestPlan<T> {
                     username = user.username.clone();
                 };
                 log::info!("adding new connection for {}", username);
-                let next_entity_id = (user_id.0 * 10_000) as usize;
-                let mut client_cx = TestAppContext::new(
-                    cx.foreground_platform(),
-                    cx.platform(),
-                    deterministic.build_foreground(user_id.0 as usize),
-                    deterministic.build_background(),
-                    cx.font_cache(),
-                    cx.leak_detector(),
-                    next_entity_id,
-                    cx.function_name.clone(),
-                );
+
+                let mut client_cx = cx.new_app();
 
                 let (operation_tx, operation_rx) = futures::channel::mpsc::unbounded();
                 let client = Rc::new(server.create_client(&mut client_cx, &username).await);
                 operation_channels.push(operation_tx);
                 clients.push((client.clone(), client_cx.clone()));
-                client_tasks.push(client_cx.foreground().spawn(Self::simulate_client(
-                    plan.clone(),
-                    client,
-                    operation_rx,
-                    client_cx,
-                )));
+
+                let foreground_executor = client_cx.foreground_executor().clone();
+                let simulate_client =
+                    Self::simulate_client(plan.clone(), client, operation_rx, client_cx);
+                client_tasks.push(foreground_executor.spawn(simulate_client));
 
                 log::info!("added connection for {}", username);
             }
@@ -514,7 +503,7 @@ impl<T: RandomizedTest> TestPlan<T> {
                     .collect::<Vec<_>>();
                 assert_eq!(user_connection_ids.len(), 1);
                 let removed_peer_id = user_connection_ids[0].into();
-                let (client, mut client_cx) = clients.remove(client_ix);
+                let (client, client_cx) = clients.remove(client_ix);
                 let client_task = client_tasks.remove(client_ix);
                 operation_channels.remove(client_ix);
                 server.forbid_connections();
@@ -647,7 +636,7 @@ impl<T: RandomizedTest> TestPlan<T> {
                     log::error!("{} error: {}", client.username, error);
                 }
             }
-            cx.background().simulate_random_delay().await;
+            cx.executor().simulate_random_delay().await;
         }
         log::info!("{}: done", client.username);
     }

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

@@ -13,9 +13,10 @@ use client::{
 use collections::{HashMap, HashSet};
 use fs::FakeFs;
 use futures::{channel::oneshot, StreamExt as _};
-use gpui::{executor::Deterministic, ModelHandle, Task, TestAppContext, WindowHandle};
+use gpui::{BackgroundExecutor, Context, Model, TestAppContext, View, VisualTestContext};
 use language::LanguageRegistry;
 use node_runtime::FakeNodeRuntime;
+
 use notifications::NotificationStore;
 use parking_lot::Mutex;
 use project::{Project, WorktreeId};
@@ -46,17 +47,17 @@ pub struct TestServer {
 pub struct TestClient {
     pub username: String,
     pub app_state: Arc<workspace::AppState>,
-    channel_store: ModelHandle<ChannelStore>,
-    notification_store: ModelHandle<NotificationStore>,
+    channel_store: Model<ChannelStore>,
+    notification_store: Model<NotificationStore>,
     state: RefCell<TestClientState>,
 }
 
 #[derive(Default)]
 struct TestClientState {
-    local_projects: Vec<ModelHandle<Project>>,
-    remote_projects: Vec<ModelHandle<Project>>,
-    buffers: HashMap<ModelHandle<Project>, HashSet<ModelHandle<language::Buffer>>>,
-    channel_buffers: HashSet<ModelHandle<ChannelBuffer>>,
+    local_projects: Vec<Model<Project>>,
+    remote_projects: Vec<Model<Project>>,
+    buffers: HashMap<Model<Project>, HashSet<Model<language::Buffer>>>,
+    channel_buffers: HashSet<Model<ChannelBuffer>>,
 }
 
 pub struct ContactsSummary {
@@ -66,22 +67,22 @@ pub struct ContactsSummary {
 }
 
 impl TestServer {
-    pub async fn start(deterministic: &Arc<Deterministic>) -> Self {
+    pub async fn start(deterministic: BackgroundExecutor) -> Self {
         static NEXT_LIVE_KIT_SERVER_ID: AtomicUsize = AtomicUsize::new(0);
 
         let use_postgres = env::var("USE_POSTGRES").ok();
         let use_postgres = use_postgres.as_deref();
         let test_db = if use_postgres == Some("true") || use_postgres == Some("1") {
-            TestDb::postgres(deterministic.build_background())
+            TestDb::postgres(deterministic.clone())
         } else {
-            TestDb::sqlite(deterministic.build_background())
+            TestDb::sqlite(deterministic.clone())
         };
         let live_kit_server_id = NEXT_LIVE_KIT_SERVER_ID.fetch_add(1, SeqCst);
         let live_kit_server = live_kit_client::TestServer::create(
             format!("http://livekit.{}.test", live_kit_server_id),
             format!("devkey-{}", live_kit_server_id),
             format!("secret-{}", live_kit_server_id),
-            deterministic.build_background(),
+            deterministic.clone(),
         )
         .unwrap();
         let app_state = Self::build_app_state(&test_db, &live_kit_server).await;
@@ -93,7 +94,7 @@ impl TestServer {
         let server = Server::new(
             epoch,
             app_state.clone(),
-            Executor::Deterministic(deterministic.build_background()),
+            Executor::Deterministic(deterministic.clone()),
         );
         server.start().await.unwrap();
         // Advance clock to ensure the server's cleanup task is finished.
@@ -124,8 +125,8 @@ impl TestServer {
             if cx.has_global::<SettingsStore>() {
                 panic!("Same cx used to create two test clients")
             }
-
-            cx.set_global(SettingsStore::test(cx));
+            let settings = SettingsStore::test(cx);
+            cx.set_global(settings);
         });
 
         let http = FakeHttpClient::with_404_response();
@@ -148,7 +149,7 @@ impl TestServer {
                 .user_id
         };
         let client_name = name.to_string();
-        let mut client = cx.read(|cx| Client::new(http.clone(), cx));
+        let mut client = cx.update(|cx| Client::new(http.clone(), cx));
         let server = self.server.clone();
         let db = self.app_state.db.clone();
         let connection_killers = self.connection_killers.clone();
@@ -182,20 +183,20 @@ impl TestServer {
                         )))
                     } else {
                         let (client_conn, server_conn, killed) =
-                            Connection::in_memory(cx.background());
+                            Connection::in_memory(cx.background_executor().clone());
                         let (connection_id_tx, connection_id_rx) = oneshot::channel();
                         let user = db
                             .get_user_by_id(user_id)
                             .await
                             .expect("retrieving user failed")
                             .unwrap();
-                        cx.background()
+                        cx.background_executor()
                             .spawn(server.handle_connection(
                                 server_conn,
                                 client_name,
                                 user,
                                 Some(connection_id_tx),
-                                Executor::Deterministic(cx.background()),
+                                Executor::Deterministic(cx.background_executor().clone()),
                             ))
                             .detach();
                         let connection_id = connection_id_rx.await.unwrap();
@@ -207,11 +208,11 @@ impl TestServer {
                 })
             });
 
-        let fs = FakeFs::new(cx.background());
-        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
-        let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx));
+        let fs = FakeFs::new(cx.executor());
+        let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
+        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
         let mut language_registry = LanguageRegistry::test();
-        language_registry.set_executor(cx.background());
+        language_registry.set_executor(cx.executor());
         let app_state = Arc::new(workspace::AppState {
             client: client.clone(),
             user_store: user_store.clone(),
@@ -219,13 +220,11 @@ impl TestServer {
             languages: Arc::new(language_registry),
             fs: fs.clone(),
             build_window_options: |_, _, _| Default::default(),
-            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
-            background_actions: || &[],
             node_runtime: FakeNodeRuntime::new(),
         });
 
         cx.update(|cx| {
-            theme::init((), cx);
+            theme::init(theme::LoadThemes::JustBase, cx);
             Project::init(&client, cx);
             client::init(&client, cx);
             language::init(cx);
@@ -264,7 +263,7 @@ impl TestServer {
     pub fn simulate_long_connection_interruption(
         &self,
         peer_id: PeerId,
-        deterministic: &Arc<Deterministic>,
+        deterministic: BackgroundExecutor,
     ) {
         self.forbid_connections();
         self.disconnect_client(peer_id);
@@ -295,7 +294,7 @@ impl TestServer {
                     })
                     .await
                     .unwrap();
-                cx_a.foreground().run_until_parked();
+                cx_a.executor().run_until_parked();
                 client_b
                     .app_state
                     .user_store
@@ -338,7 +337,7 @@ impl TestServer {
                 .await
                 .unwrap();
 
-            admin_cx.foreground().run_until_parked();
+            admin_cx.executor().run_until_parked();
 
             member_cx
                 .read(ChannelStore::global)
@@ -399,7 +398,7 @@ impl TestServer {
                 .await
                 .unwrap();
 
-            cx_b.foreground().run_until_parked();
+            cx_b.executor().run_until_parked();
             let active_call_b = cx_b.read(ActiveCall::global);
             active_call_b
                 .update(*cx_b, |call, cx| call.accept_incoming(cx))
@@ -448,15 +447,15 @@ impl TestClient {
         self.app_state.fs.as_fake()
     }
 
-    pub fn channel_store(&self) -> &ModelHandle<ChannelStore> {
+    pub fn channel_store(&self) -> &Model<ChannelStore> {
         &self.channel_store
     }
 
-    pub fn notification_store(&self) -> &ModelHandle<NotificationStore> {
+    pub fn notification_store(&self) -> &Model<NotificationStore> {
         &self.notification_store
     }
 
-    pub fn user_store(&self) -> &ModelHandle<UserStore> {
+    pub fn user_store(&self) -> &Model<UserStore> {
         &self.app_state.user_store
     }
 
@@ -491,30 +490,26 @@ impl TestClient {
             .await;
     }
 
-    pub fn local_projects<'a>(&'a self) -> impl Deref<Target = Vec<ModelHandle<Project>>> + 'a {
+    pub fn local_projects<'a>(&'a self) -> impl Deref<Target = Vec<Model<Project>>> + 'a {
         Ref::map(self.state.borrow(), |state| &state.local_projects)
     }
 
-    pub fn remote_projects<'a>(&'a self) -> impl Deref<Target = Vec<ModelHandle<Project>>> + 'a {
+    pub fn remote_projects<'a>(&'a self) -> impl Deref<Target = Vec<Model<Project>>> + 'a {
         Ref::map(self.state.borrow(), |state| &state.remote_projects)
     }
 
-    pub fn local_projects_mut<'a>(
-        &'a self,
-    ) -> impl DerefMut<Target = Vec<ModelHandle<Project>>> + 'a {
+    pub fn local_projects_mut<'a>(&'a self) -> impl DerefMut<Target = Vec<Model<Project>>> + 'a {
         RefMut::map(self.state.borrow_mut(), |state| &mut state.local_projects)
     }
 
-    pub fn remote_projects_mut<'a>(
-        &'a self,
-    ) -> impl DerefMut<Target = Vec<ModelHandle<Project>>> + 'a {
+    pub fn remote_projects_mut<'a>(&'a self) -> impl DerefMut<Target = Vec<Model<Project>>> + 'a {
         RefMut::map(self.state.borrow_mut(), |state| &mut state.remote_projects)
     }
 
     pub fn buffers_for_project<'a>(
         &'a self,
-        project: &ModelHandle<Project>,
-    ) -> impl DerefMut<Target = HashSet<ModelHandle<language::Buffer>>> + 'a {
+        project: &Model<Project>,
+    ) -> impl DerefMut<Target = HashSet<Model<language::Buffer>>> + 'a {
         RefMut::map(self.state.borrow_mut(), |state| {
             state.buffers.entry(project.clone()).or_default()
         })
@@ -522,14 +517,14 @@ impl TestClient {
 
     pub fn buffers<'a>(
         &'a self,
-    ) -> impl DerefMut<Target = HashMap<ModelHandle<Project>, HashSet<ModelHandle<language::Buffer>>>> + 'a
+    ) -> impl DerefMut<Target = HashMap<Model<Project>, HashSet<Model<language::Buffer>>>> + 'a
     {
         RefMut::map(self.state.borrow_mut(), |state| &mut state.buffers)
     }
 
     pub fn channel_buffers<'a>(
         &'a self,
-    ) -> impl DerefMut<Target = HashSet<ModelHandle<ChannelBuffer>>> + 'a {
+    ) -> impl DerefMut<Target = HashSet<Model<ChannelBuffer>>> + 'a {
         RefMut::map(self.state.borrow_mut(), |state| &mut state.channel_buffers)
     }
 
@@ -559,7 +554,7 @@ impl TestClient {
         &self,
         root_path: impl AsRef<Path>,
         cx: &mut TestAppContext,
-    ) -> (ModelHandle<Project>, WorktreeId) {
+    ) -> (Model<Project>, WorktreeId) {
         let project = self.build_empty_local_project(cx);
         let (worktree, _) = project
             .update(cx, |p, cx| {
@@ -573,7 +568,7 @@ impl TestClient {
         (project, worktree.read_with(cx, |tree, _| tree.id()))
     }
 
-    pub fn build_empty_local_project(&self, cx: &mut TestAppContext) -> ModelHandle<Project> {
+    pub fn build_empty_local_project(&self, cx: &mut TestAppContext) -> Model<Project> {
         cx.update(|cx| {
             Project::local(
                 self.client().clone(),
@@ -590,7 +585,7 @@ impl TestClient {
         &self,
         host_project_id: u64,
         guest_cx: &mut TestAppContext,
-    ) -> ModelHandle<Project> {
+    ) -> Model<Project> {
         let active_call = guest_cx.read(ActiveCall::global);
         let room = active_call.read_with(guest_cx, |call, _| call.room().unwrap().clone());
         room.update(guest_cx, |room, cx| {
@@ -605,12 +600,12 @@ impl TestClient {
         .unwrap()
     }
 
-    pub fn build_workspace(
-        &self,
-        project: &ModelHandle<Project>,
-        cx: &mut TestAppContext,
-    ) -> WindowHandle<Workspace> {
-        cx.add_window(|cx| Workspace::new(0, project.clone(), self.app_state.clone(), cx))
+    pub fn build_workspace<'a>(
+        &'a self,
+        project: &Model<Project>,
+        cx: &'a mut TestAppContext,
+    ) -> (View<Workspace>, &'a mut VisualTestContext) {
+        cx.add_window_view(|cx| Workspace::new(0, project.clone(), self.app_state.clone(), cx))
     }
 }
 

crates/collab2/.env.toml 🔗

@@ -1,12 +0,0 @@
-DATABASE_URL = "postgres://postgres@localhost/zed"
-DATABASE_MAX_CONNECTIONS = 5
-HTTP_PORT = 8080
-API_TOKEN = "secret"
-INVITE_LINK_PREFIX = "http://localhost:3000/invites/"
-ZED_ENVIRONMENT = "development"
-LIVE_KIT_SERVER = "http://localhost:7880"
-LIVE_KIT_KEY = "devkey"
-LIVE_KIT_SECRET = "secret"
-
-# RUST_LOG=info
-# LOG_JSON=true

crates/collab2/Cargo.toml 🔗

@@ -1,99 +0,0 @@
-[package]
-authors = ["Nathan Sobo <nathan@zed.dev>"]
-default-run = "collab2"
-edition = "2021"
-name = "collab2"
-version = "0.28.0"
-publish = false
-
-[[bin]]
-name = "collab2"
-
-[[bin]]
-name = "seed2"
-required-features = ["seed-support"]
-
-[dependencies]
-clock = { path = "../clock" }
-collections = { path = "../collections" }
-live_kit_server = { path = "../live_kit_server" }
-text = { package = "text2", path = "../text2" }
-rpc = { package = "rpc2", path = "../rpc2" }
-util = { path = "../util" }
-
-anyhow.workspace = true
-async-tungstenite = "0.16"
-axum = { version = "0.5", features = ["json", "headers", "ws"] }
-axum-extra = { version = "0.3", features = ["erased-json"] }
-base64 = "0.13"
-clap = { version = "3.1", features = ["derive"], optional = true }
-dashmap = "5.4"
-envy = "0.4.2"
-futures.workspace = true
-hyper = "0.14"
-lazy_static.workspace = true
-lipsum = { version = "0.8", optional = true }
-log.workspace = true
-nanoid = "0.4"
-parking_lot.workspace = true
-prometheus = "0.13"
-prost.workspace = true
-rand.workspace = true
-reqwest = { version = "0.11", features = ["json"], optional = true }
-scrypt = "0.7"
-smallvec.workspace = true
-sea-orm = { version = "0.12.x", features = ["sqlx-postgres", "postgres-array", "runtime-tokio-rustls", "with-uuid"] }
-serde.workspace = true
-serde_derive.workspace = true
-serde_json.workspace = true
-sha-1 = "0.9"
-sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "json", "time", "uuid", "any"] }
-time.workspace = true
-tokio = { version = "1", features = ["full"] }
-tokio-tungstenite = "0.17"
-tonic = "0.6"
-tower = "0.4"
-toml.workspace = true
-tracing = "0.1.34"
-tracing-log = "0.1.3"
-tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json"] }
-uuid.workspace = true
-
-[dev-dependencies]
-audio = { package = "audio2", path = "../audio2" }
-collections = { path = "../collections", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-call = { package = "call2", path = "../call2", features = ["test-support"] }
-client = { package = "client2", path = "../client2", features = ["test-support"] }
-channel = { package = "channel2", path = "../channel2" }
-editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
-language = { package = "language2", path = "../language2", features = ["test-support"] }
-fs = { package = "fs2", path = "../fs2", features = ["test-support"] }
-git = { package = "git3", path = "../git3", features = ["test-support"] }
-live_kit_client = { package = "live_kit_client2", path = "../live_kit_client2", features = ["test-support"] }
-lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
-node_runtime = { path = "../node_runtime" }
-notifications = { package = "notifications2", path = "../notifications2", features = ["test-support"] }
-
-project = { package = "project2", path = "../project2", features = ["test-support"] }
-rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
-settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
-theme = { package = "theme2", path = "../theme2" }
-workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
-
-collab_ui = { path = "../collab_ui2", package = "collab_ui2", features = ["test-support"] }
-
-async-trait.workspace = true
-pretty_assertions.workspace = true
-ctor.workspace = true
-env_logger.workspace = true
-indoc.workspace = true
-util = { path = "../util" }
-lazy_static.workspace = true
-sea-orm = { version = "0.12.x", features = ["sqlx-sqlite"] }
-serde_json.workspace = true
-sqlx = { version = "0.7", features = ["sqlite"] }
-unindent.workspace = true
-
-[features]
-seed-support = ["clap", "lipsum", "reqwest"]

crates/collab2/README.md 🔗

@@ -1,5 +0,0 @@
-# Zed Server
-
-This crate is what we run at https://collab.zed.dev.
-
-It contains our back-end logic for collaboration, to which we connect from the Zed client via a websocket after authenticating via https://zed.dev, which is a separate repo running on Vercel.

crates/collab2/admin_api.conf 🔗

@@ -1,4 +0,0 @@
-db-uri = "postgres://postgres@localhost/zed"
-server-port = 8081
-jwt-secret = "the-postgrest-jwt-secret-for-authorization"
-log-level = "info"

crates/collab2/basic.conf 🔗

@@ -1,12 +0,0 @@
-
-[Interface]
-PrivateKey = B5Fp/yVfP0QYlb+YJv9ea+EMI1mWODPD3akh91cVjvc=
-Address = fdaa:0:2ce3:a7b:bea:0:a:2/120
-DNS = fdaa:0:2ce3::3
-
-[Peer]
-PublicKey = RKAYPljEJiuaELNDdQIEJmQienT9+LRISfIHwH45HAw=
-AllowedIPs = fdaa:0:2ce3::/48
-Endpoint = ord1.gateway.6pn.dev:51820
-PersistentKeepalive = 15
-

crates/collab2/migrations.sqlite/20221109000000_test_schema.sql 🔗

@@ -1,344 +0,0 @@
-CREATE TABLE "users" (
-    "id" INTEGER PRIMARY KEY AUTOINCREMENT,
-    "github_login" VARCHAR,
-    "admin" BOOLEAN,
-    "email_address" VARCHAR(255) DEFAULT NULL,
-    "invite_code" VARCHAR(64),
-    "invite_count" INTEGER NOT NULL DEFAULT 0,
-    "inviter_id" INTEGER REFERENCES users (id),
-    "connected_once" BOOLEAN NOT NULL DEFAULT false,
-    "created_at" TIMESTAMP NOT NULL DEFAULT now,
-    "metrics_id" TEXT,
-    "github_user_id" INTEGER
-);
-CREATE UNIQUE INDEX "index_users_github_login" ON "users" ("github_login");
-CREATE UNIQUE INDEX "index_invite_code_users" ON "users" ("invite_code");
-CREATE INDEX "index_users_on_email_address" ON "users" ("email_address");
-CREATE INDEX "index_users_on_github_user_id" ON "users" ("github_user_id");
-
-CREATE TABLE "access_tokens" (
-    "id" INTEGER PRIMARY KEY AUTOINCREMENT,
-    "user_id" INTEGER REFERENCES users (id),
-    "hash" VARCHAR(128)
-);
-CREATE INDEX "index_access_tokens_user_id" ON "access_tokens" ("user_id");
-
-CREATE TABLE "contacts" (
-    "id" INTEGER PRIMARY KEY AUTOINCREMENT,
-    "user_id_a" INTEGER REFERENCES users (id) NOT NULL,
-    "user_id_b" INTEGER REFERENCES users (id) NOT NULL,
-    "a_to_b" BOOLEAN NOT NULL,
-    "should_notify" BOOLEAN NOT NULL,
-    "accepted" BOOLEAN NOT NULL
-);
-CREATE UNIQUE INDEX "index_contacts_user_ids" ON "contacts" ("user_id_a", "user_id_b");
-CREATE INDEX "index_contacts_user_id_b" ON "contacts" ("user_id_b");
-
-CREATE TABLE "rooms" (
-    "id" INTEGER PRIMARY KEY AUTOINCREMENT,
-    "live_kit_room" VARCHAR NOT NULL,
-    "enviroment" VARCHAR,
-    "channel_id" INTEGER REFERENCES channels (id) ON DELETE CASCADE
-);
-CREATE UNIQUE INDEX "index_rooms_on_channel_id" ON "rooms" ("channel_id");
-
-CREATE TABLE "projects" (
-    "id" INTEGER PRIMARY KEY AUTOINCREMENT,
-    "room_id" INTEGER REFERENCES rooms (id) ON DELETE CASCADE NOT NULL,
-    "host_user_id" INTEGER REFERENCES users (id) NOT NULL,
-    "host_connection_id" INTEGER,
-    "host_connection_server_id" INTEGER REFERENCES servers (id) ON DELETE CASCADE,
-    "unregistered" BOOLEAN NOT NULL DEFAULT FALSE
-);
-CREATE INDEX "index_projects_on_host_connection_server_id" ON "projects" ("host_connection_server_id");
-CREATE INDEX "index_projects_on_host_connection_id_and_host_connection_server_id" ON "projects" ("host_connection_id", "host_connection_server_id");
-
-CREATE TABLE "worktrees" (
-    "project_id" INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE,
-    "id" INTEGER NOT NULL,
-    "root_name" VARCHAR NOT NULL,
-    "abs_path" VARCHAR NOT NULL,
-    "visible" BOOL NOT NULL,
-    "scan_id" INTEGER NOT NULL,
-    "is_complete" BOOL NOT NULL DEFAULT FALSE,
-    "completed_scan_id" INTEGER NOT NULL,
-    PRIMARY KEY(project_id, id)
-);
-CREATE INDEX "index_worktrees_on_project_id" ON "worktrees" ("project_id");
-
-CREATE TABLE "worktree_entries" (
-    "project_id" INTEGER NOT NULL,
-    "worktree_id" INTEGER NOT NULL,
-    "scan_id" INTEGER NOT NULL,
-    "id" INTEGER NOT NULL,
-    "is_dir" BOOL NOT NULL,
-    "path" VARCHAR NOT NULL,
-    "inode" INTEGER NOT NULL,
-    "mtime_seconds" INTEGER NOT NULL,
-    "mtime_nanos" INTEGER NOT NULL,
-    "is_symlink" BOOL NOT NULL,
-    "is_external" BOOL NOT NULL,
-    "is_ignored" BOOL NOT NULL,
-    "is_deleted" BOOL NOT NULL,
-    "git_status" INTEGER,
-    PRIMARY KEY(project_id, worktree_id, id),
-    FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE
-);
-CREATE INDEX "index_worktree_entries_on_project_id" ON "worktree_entries" ("project_id");
-CREATE INDEX "index_worktree_entries_on_project_id_and_worktree_id" ON "worktree_entries" ("project_id", "worktree_id");
-
-CREATE TABLE "worktree_repositories" (
-    "project_id" INTEGER NOT NULL,
-    "worktree_id" INTEGER NOT NULL,
-    "work_directory_id" INTEGER NOT NULL,
-    "branch" VARCHAR,
-    "scan_id" INTEGER NOT NULL,
-    "is_deleted" BOOL NOT NULL,
-    PRIMARY KEY(project_id, worktree_id, work_directory_id),
-    FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE,
-    FOREIGN KEY(project_id, worktree_id, work_directory_id) REFERENCES worktree_entries (project_id, worktree_id, id) ON DELETE CASCADE
-);
-CREATE INDEX "index_worktree_repositories_on_project_id" ON "worktree_repositories" ("project_id");
-CREATE INDEX "index_worktree_repositories_on_project_id_and_worktree_id" ON "worktree_repositories" ("project_id", "worktree_id");
-
-CREATE TABLE "worktree_settings_files" (
-    "project_id" INTEGER NOT NULL,
-    "worktree_id" INTEGER NOT NULL,
-    "path" VARCHAR NOT NULL,
-    "content" TEXT,
-    PRIMARY KEY(project_id, worktree_id, path),
-    FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE
-);
-CREATE INDEX "index_worktree_settings_files_on_project_id" ON "worktree_settings_files" ("project_id");
-CREATE INDEX "index_worktree_settings_files_on_project_id_and_worktree_id" ON "worktree_settings_files" ("project_id", "worktree_id");
-
-CREATE TABLE "worktree_diagnostic_summaries" (
-    "project_id" INTEGER NOT NULL,
-    "worktree_id" INTEGER NOT NULL,
-    "path" VARCHAR NOT NULL,
-    "language_server_id" INTEGER NOT NULL,
-    "error_count" INTEGER NOT NULL,
-    "warning_count" INTEGER NOT NULL,
-    PRIMARY KEY(project_id, worktree_id, path),
-    FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE
-);
-CREATE INDEX "index_worktree_diagnostic_summaries_on_project_id" ON "worktree_diagnostic_summaries" ("project_id");
-CREATE INDEX "index_worktree_diagnostic_summaries_on_project_id_and_worktree_id" ON "worktree_diagnostic_summaries" ("project_id", "worktree_id");
-
-CREATE TABLE "language_servers" (
-    "id" INTEGER NOT NULL,
-    "project_id" INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE,
-    "name" VARCHAR NOT NULL,
-    PRIMARY KEY(project_id, id)
-);
-CREATE INDEX "index_language_servers_on_project_id" ON "language_servers" ("project_id");
-
-CREATE TABLE "project_collaborators" (
-    "id" INTEGER PRIMARY KEY AUTOINCREMENT,
-    "project_id" INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE,
-    "connection_id" INTEGER NOT NULL,
-    "connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE,
-    "user_id" INTEGER NOT NULL,
-    "replica_id" INTEGER NOT NULL,
-    "is_host" BOOLEAN NOT NULL
-);
-CREATE INDEX "index_project_collaborators_on_project_id" ON "project_collaborators" ("project_id");
-CREATE UNIQUE INDEX "index_project_collaborators_on_project_id_and_replica_id" ON "project_collaborators" ("project_id", "replica_id");
-CREATE INDEX "index_project_collaborators_on_connection_server_id" ON "project_collaborators" ("connection_server_id");
-CREATE INDEX "index_project_collaborators_on_connection_id" ON "project_collaborators" ("connection_id");
-CREATE UNIQUE INDEX "index_project_collaborators_on_project_id_connection_id_and_server_id" ON "project_collaborators" ("project_id", "connection_id", "connection_server_id");
-
-CREATE TABLE "room_participants" (
-    "id" INTEGER PRIMARY KEY AUTOINCREMENT,
-    "room_id" INTEGER NOT NULL REFERENCES rooms (id),
-    "user_id" INTEGER NOT NULL REFERENCES users (id),
-    "answering_connection_id" INTEGER,
-    "answering_connection_server_id" INTEGER REFERENCES servers (id) ON DELETE CASCADE,
-    "answering_connection_lost" BOOLEAN NOT NULL,
-    "location_kind" INTEGER,
-    "location_project_id" INTEGER,
-    "initial_project_id" INTEGER,
-    "calling_user_id" INTEGER NOT NULL REFERENCES users (id),
-    "calling_connection_id" INTEGER NOT NULL,
-    "calling_connection_server_id" INTEGER REFERENCES servers (id) ON DELETE SET NULL,
-    "participant_index" INTEGER
-);
-CREATE UNIQUE INDEX "index_room_participants_on_user_id" ON "room_participants" ("user_id");
-CREATE INDEX "index_room_participants_on_room_id" ON "room_participants" ("room_id");
-CREATE INDEX "index_room_participants_on_answering_connection_server_id" ON "room_participants" ("answering_connection_server_id");
-CREATE INDEX "index_room_participants_on_calling_connection_server_id" ON "room_participants" ("calling_connection_server_id");
-CREATE INDEX "index_room_participants_on_answering_connection_id" ON "room_participants" ("answering_connection_id");
-CREATE UNIQUE INDEX "index_room_participants_on_answering_connection_id_and_answering_connection_server_id" ON "room_participants" ("answering_connection_id", "answering_connection_server_id");
-
-CREATE TABLE "servers" (
-    "id" INTEGER PRIMARY KEY AUTOINCREMENT,
-    "environment" VARCHAR NOT NULL
-);
-
-CREATE TABLE "followers" (
-    "id" INTEGER PRIMARY KEY AUTOINCREMENT,
-    "room_id" INTEGER NOT NULL REFERENCES rooms (id) ON DELETE CASCADE,
-    "project_id" INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE,
-    "leader_connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE,
-    "leader_connection_id" INTEGER NOT NULL,
-    "follower_connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE,
-    "follower_connection_id" INTEGER NOT NULL
-);
-CREATE UNIQUE INDEX
-    "index_followers_on_project_id_and_leader_connection_server_id_and_leader_connection_id_and_follower_connection_server_id_and_follower_connection_id"
-ON "followers" ("project_id", "leader_connection_server_id", "leader_connection_id", "follower_connection_server_id", "follower_connection_id");
-CREATE INDEX "index_followers_on_room_id" ON "followers" ("room_id");
-
-CREATE TABLE "channels" (
-    "id" INTEGER PRIMARY KEY AUTOINCREMENT,
-    "name" VARCHAR NOT NULL,
-    "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-    "visibility" VARCHAR NOT NULL,
-    "parent_path" TEXT
-);
-
-CREATE INDEX "index_channels_on_parent_path" ON "channels" ("parent_path");
-
-CREATE TABLE IF NOT EXISTS "channel_chat_participants" (
-    "id" INTEGER PRIMARY KEY AUTOINCREMENT,
-    "user_id" INTEGER NOT NULL REFERENCES users (id),
-    "channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
-    "connection_id" INTEGER NOT NULL,
-    "connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE
-);
-CREATE INDEX "index_channel_chat_participants_on_channel_id" ON "channel_chat_participants" ("channel_id");
-
-CREATE TABLE IF NOT EXISTS "channel_messages" (
-    "id" INTEGER PRIMARY KEY AUTOINCREMENT,
-    "channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
-    "sender_id" INTEGER NOT NULL REFERENCES users (id),
-    "body" TEXT NOT NULL,
-    "sent_at" TIMESTAMP,
-    "nonce" BLOB NOT NULL
-);
-CREATE INDEX "index_channel_messages_on_channel_id" ON "channel_messages" ("channel_id");
-CREATE UNIQUE INDEX "index_channel_messages_on_sender_id_nonce" ON "channel_messages" ("sender_id", "nonce");
-
-CREATE TABLE "channel_message_mentions" (
-    "message_id" INTEGER NOT NULL REFERENCES channel_messages (id) ON DELETE CASCADE,
-    "start_offset" INTEGER NOT NULL,
-    "end_offset" INTEGER NOT NULL,
-    "user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
-    PRIMARY KEY(message_id, start_offset)
-);
-
-CREATE TABLE "channel_members" (
-    "id" INTEGER PRIMARY KEY AUTOINCREMENT,
-    "channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
-    "user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
-    "admin" BOOLEAN NOT NULL DEFAULT false,
-    "role" VARCHAR,
-    "accepted" BOOLEAN NOT NULL DEFAULT false,
-    "updated_at" TIMESTAMP NOT NULL DEFAULT now
-);
-
-CREATE UNIQUE INDEX "index_channel_members_on_channel_id_and_user_id" ON "channel_members" ("channel_id", "user_id");
-
-CREATE TABLE "buffers" (
-    "id" INTEGER PRIMARY KEY AUTOINCREMENT,
-    "channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
-    "epoch" INTEGER NOT NULL DEFAULT 0
-);
-
-CREATE INDEX "index_buffers_on_channel_id" ON "buffers" ("channel_id");
-
-CREATE TABLE "buffer_operations" (
-    "buffer_id" INTEGER NOT NULL REFERENCES buffers (id) ON DELETE CASCADE,
-    "epoch" INTEGER NOT NULL,
-    "replica_id" INTEGER NOT NULL,
-    "lamport_timestamp" INTEGER NOT NULL,
-    "value" BLOB NOT NULL,
-    PRIMARY KEY(buffer_id, epoch, lamport_timestamp, replica_id)
-);
-
-CREATE TABLE "buffer_snapshots" (
-    "buffer_id" INTEGER NOT NULL REFERENCES buffers (id) ON DELETE CASCADE,
-    "epoch" INTEGER NOT NULL,
-    "text" TEXT NOT NULL,
-    "operation_serialization_version" INTEGER NOT NULL,
-    PRIMARY KEY(buffer_id, epoch)
-);
-
-CREATE TABLE "channel_buffer_collaborators" (
-    "id" INTEGER PRIMARY KEY AUTOINCREMENT,
-    "channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
-    "connection_id" INTEGER NOT NULL,
-    "connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE,
-    "connection_lost" BOOLEAN NOT NULL DEFAULT false,
-    "user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
-    "replica_id" INTEGER NOT NULL
-);
-
-CREATE INDEX "index_channel_buffer_collaborators_on_channel_id" ON "channel_buffer_collaborators" ("channel_id");
-CREATE UNIQUE INDEX "index_channel_buffer_collaborators_on_channel_id_and_replica_id" ON "channel_buffer_collaborators" ("channel_id", "replica_id");
-CREATE INDEX "index_channel_buffer_collaborators_on_connection_server_id" ON "channel_buffer_collaborators" ("connection_server_id");
-CREATE INDEX "index_channel_buffer_collaborators_on_connection_id" ON "channel_buffer_collaborators" ("connection_id");
-CREATE UNIQUE INDEX "index_channel_buffer_collaborators_on_channel_id_connection_id_and_server_id" ON "channel_buffer_collaborators" ("channel_id", "connection_id", "connection_server_id");
-
-
-CREATE TABLE "feature_flags" (
-    "id" INTEGER PRIMARY KEY AUTOINCREMENT,
-    "flag" TEXT NOT NULL UNIQUE
-);
-
-CREATE INDEX "index_feature_flags" ON "feature_flags" ("id");
-
-
-CREATE TABLE "user_features" (
-    "user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
-    "feature_id" INTEGER NOT NULL REFERENCES feature_flags (id) ON DELETE CASCADE,
-    PRIMARY KEY (user_id, feature_id)
-);
-
-CREATE UNIQUE INDEX "index_user_features_user_id_and_feature_id" ON "user_features" ("user_id", "feature_id");
-CREATE INDEX "index_user_features_on_user_id" ON "user_features" ("user_id");
-CREATE INDEX "index_user_features_on_feature_id" ON "user_features" ("feature_id");
-
-
-CREATE TABLE "observed_buffer_edits" (
-    "user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
-    "buffer_id" INTEGER NOT NULL REFERENCES buffers (id) ON DELETE CASCADE,
-    "epoch" INTEGER NOT NULL,
-    "lamport_timestamp" INTEGER NOT NULL,
-    "replica_id" INTEGER NOT NULL,
-    PRIMARY KEY (user_id, buffer_id)
-);
-
-CREATE UNIQUE INDEX "index_observed_buffers_user_and_buffer_id" ON "observed_buffer_edits" ("user_id", "buffer_id");
-
-CREATE TABLE IF NOT EXISTS "observed_channel_messages" (
-    "user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
-    "channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
-    "channel_message_id" INTEGER NOT NULL,
-    PRIMARY KEY (user_id, channel_id)
-);
-
-CREATE UNIQUE INDEX "index_observed_channel_messages_user_and_channel_id" ON "observed_channel_messages" ("user_id", "channel_id");
-
-CREATE TABLE "notification_kinds" (
-    "id" INTEGER PRIMARY KEY AUTOINCREMENT,
-    "name" VARCHAR NOT NULL
-);
-
-CREATE UNIQUE INDEX "index_notification_kinds_on_name" ON "notification_kinds" ("name");
-
-CREATE TABLE "notifications" (
-    "id" INTEGER PRIMARY KEY AUTOINCREMENT,
-    "created_at" TIMESTAMP NOT NULL default CURRENT_TIMESTAMP,
-    "recipient_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
-    "kind" INTEGER NOT NULL REFERENCES notification_kinds (id),
-    "entity_id" INTEGER,
-    "content" TEXT,
-    "is_read" BOOLEAN NOT NULL DEFAULT FALSE,
-    "response" BOOLEAN
-);
-
-CREATE INDEX
-    "index_notifications_on_recipient_id_is_read_kind_entity_id"
-    ON "notifications"
-    ("recipient_id", "is_read", "kind", "entity_id");

crates/collab2/migrations/20210527024318_initial_schema.sql 🔗

@@ -1,20 +0,0 @@
-CREATE TABLE IF NOT EXISTS "sessions" (
-    "id" VARCHAR NOT NULL PRIMARY KEY,
-    "expires" TIMESTAMP WITH TIME ZONE NULL,
-    "session" TEXT NOT NULL
-);
-
-CREATE TABLE IF NOT EXISTS "users" (
-    "id" SERIAL PRIMARY KEY,
-    "github_login" VARCHAR,
-    "admin" BOOLEAN
-);
-
-CREATE UNIQUE INDEX "index_users_github_login" ON "users" ("github_login");
-
-CREATE TABLE IF NOT EXISTS "signups" (
-    "id" SERIAL PRIMARY KEY,
-    "github_login" VARCHAR,
-    "email_address" VARCHAR,
-    "about" TEXT
-);

crates/collab2/migrations/20210805175147_create_chat_tables.sql 🔗

@@ -1,46 +0,0 @@
-CREATE TABLE IF NOT EXISTS "orgs" (
-    "id" SERIAL PRIMARY KEY,
-    "name" VARCHAR NOT NULL,
-    "slug" VARCHAR NOT NULL
-);
-
-CREATE UNIQUE INDEX "index_orgs_slug" ON "orgs" ("slug");
-
-CREATE TABLE IF NOT EXISTS "org_memberships" (
-    "id" SERIAL PRIMARY KEY,
-    "org_id" INTEGER REFERENCES orgs (id) NOT NULL,
-    "user_id" INTEGER REFERENCES users (id) NOT NULL,
-    "admin" BOOLEAN NOT NULL
-);
-
-CREATE INDEX "index_org_memberships_user_id" ON "org_memberships" ("user_id");
-CREATE UNIQUE INDEX "index_org_memberships_org_id_and_user_id" ON "org_memberships" ("org_id", "user_id");
-
-CREATE TABLE IF NOT EXISTS "channels" (
-    "id" SERIAL PRIMARY KEY,
-    "owner_id" INTEGER NOT NULL,
-    "owner_is_user" BOOLEAN NOT NULL,
-    "name" VARCHAR NOT NULL
-);
-
-CREATE UNIQUE INDEX "index_channels_owner_and_name" ON "channels" ("owner_is_user", "owner_id", "name");
-
-CREATE TABLE IF NOT EXISTS "channel_memberships" (
-    "id" SERIAL PRIMARY KEY,
-    "channel_id" INTEGER REFERENCES channels (id) NOT NULL,
-    "user_id" INTEGER REFERENCES users (id) NOT NULL,
-    "admin" BOOLEAN NOT NULL
-);
-
-CREATE INDEX "index_channel_memberships_user_id" ON "channel_memberships" ("user_id");
-CREATE UNIQUE INDEX "index_channel_memberships_channel_id_and_user_id" ON "channel_memberships" ("channel_id", "user_id");
-
-CREATE TABLE IF NOT EXISTS "channel_messages" (
-    "id" SERIAL PRIMARY KEY,
-    "channel_id" INTEGER REFERENCES channels (id) NOT NULL,
-    "sender_id" INTEGER REFERENCES users (id) NOT NULL,
-    "body" TEXT NOT NULL,
-    "sent_at" TIMESTAMP
-);
-
-CREATE INDEX "index_channel_messages_channel_id" ON "channel_messages" ("channel_id");

crates/collab2/migrations/20220506130724_create_contacts.sql 🔗

@@ -1,11 +0,0 @@
-CREATE TABLE IF NOT EXISTS "contacts" (
-    "id" SERIAL PRIMARY KEY,
-    "user_id_a" INTEGER REFERENCES users (id) NOT NULL,
-    "user_id_b" INTEGER REFERENCES users (id) NOT NULL,
-    "a_to_b" BOOLEAN NOT NULL,
-    "should_notify" BOOLEAN NOT NULL,
-    "accepted" BOOLEAN NOT NULL
-);
-
-CREATE UNIQUE INDEX "index_contacts_user_ids" ON "contacts" ("user_id_a", "user_id_b");
-CREATE INDEX "index_contacts_user_id_b" ON "contacts" ("user_id_b");

crates/collab2/migrations/20220518151305_add_invites_to_users.sql 🔗

@@ -1,9 +0,0 @@
-ALTER TABLE users
-ADD email_address VARCHAR(255) DEFAULT NULL,
-ADD invite_code VARCHAR(64),
-ADD invite_count INTEGER NOT NULL DEFAULT 0,
-ADD inviter_id INTEGER REFERENCES users (id),
-ADD connected_once BOOLEAN NOT NULL DEFAULT false,
-ADD created_at TIMESTAMP NOT NULL DEFAULT NOW();
-
-CREATE UNIQUE INDEX "index_invite_code_users" ON "users" ("invite_code");

crates/collab2/migrations/20220523232954_allow_user_deletes.sql 🔗

@@ -1,6 +0,0 @@
-ALTER TABLE contacts DROP CONSTRAINT contacts_user_id_a_fkey;
-ALTER TABLE contacts DROP CONSTRAINT contacts_user_id_b_fkey;
-ALTER TABLE contacts ADD CONSTRAINT contacts_user_id_a_fkey FOREIGN KEY (user_id_a) REFERENCES users(id) ON DELETE CASCADE;
-ALTER TABLE contacts ADD CONSTRAINT contacts_user_id_b_fkey FOREIGN KEY (user_id_b) REFERENCES users(id) ON DELETE CASCADE;
-ALTER TABLE users DROP CONSTRAINT users_inviter_id_fkey;
-ALTER TABLE users ADD CONSTRAINT users_inviter_id_fkey FOREIGN KEY (inviter_id) REFERENCES users(id) ON DELETE SET NULL;

crates/collab2/migrations/20220620211403_create_projects.sql 🔗

@@ -1,24 +0,0 @@
-CREATE TABLE IF NOT EXISTS "projects" (
-    "id" SERIAL PRIMARY KEY,
-    "host_user_id" INTEGER REFERENCES users (id) NOT NULL,
-    "unregistered" BOOLEAN NOT NULL DEFAULT false
-);
-
-CREATE TABLE IF NOT EXISTS "worktree_extensions" (
-    "id" SERIAL PRIMARY KEY,
-    "project_id" INTEGER REFERENCES projects (id) NOT NULL,
-    "worktree_id" INTEGER NOT NULL,
-    "extension" VARCHAR(255),
-    "count" INTEGER NOT NULL
-);
-
-CREATE TABLE IF NOT EXISTS "project_activity_periods" (
-    "id" SERIAL PRIMARY KEY,
-    "duration_millis" INTEGER NOT NULL,
-    "ended_at" TIMESTAMP NOT NULL,
-    "user_id" INTEGER REFERENCES users (id) NOT NULL,
-    "project_id" INTEGER REFERENCES projects (id) NOT NULL
-);
-
-CREATE INDEX "index_project_activity_periods_on_ended_at" ON "project_activity_periods" ("ended_at");
-CREATE UNIQUE INDEX "index_worktree_extensions_on_project_id_and_worktree_id_and_extension" ON "worktree_extensions" ("project_id", "worktree_id", "extension");

crates/collab2/migrations/20220913211150_create_signups.sql 🔗

@@ -1,27 +0,0 @@
-CREATE TABLE IF NOT EXISTS "signups" (
-    "id" SERIAL PRIMARY KEY,
-    "email_address" VARCHAR NOT NULL,
-    "email_confirmation_code" VARCHAR(64) NOT NULL,
-    "email_confirmation_sent" BOOLEAN NOT NULL,
-    "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-    "device_id" VARCHAR,
-    "user_id" INTEGER REFERENCES users (id) ON DELETE CASCADE,
-    "inviting_user_id" INTEGER REFERENCES users (id) ON DELETE SET NULL,
-
-    "platform_mac" BOOLEAN NOT NULL,
-    "platform_linux" BOOLEAN NOT NULL,
-    "platform_windows" BOOLEAN NOT NULL,
-    "platform_unknown" BOOLEAN NOT NULL,
-
-    "editor_features" VARCHAR[],
-    "programming_languages" VARCHAR[]
-);
-
-CREATE UNIQUE INDEX "index_signups_on_email_address" ON "signups" ("email_address");
-CREATE INDEX "index_signups_on_email_confirmation_sent" ON "signups" ("email_confirmation_sent");
-
-ALTER TABLE "users"
-    ADD "github_user_id" INTEGER;
-
-CREATE INDEX "index_users_on_email_address" ON "users" ("email_address");
-CREATE INDEX "index_users_on_github_user_id" ON "users" ("github_user_id");

crates/collab2/migrations/20221111092550_reconnection_support.sql 🔗

@@ -1,90 +0,0 @@
-CREATE TABLE IF NOT EXISTS "rooms" (
-    "id" SERIAL PRIMARY KEY,
-    "live_kit_room" VARCHAR NOT NULL
-);
-
-ALTER TABLE "projects"
-    ADD "room_id" INTEGER REFERENCES rooms (id),
-    ADD "host_connection_id" INTEGER,
-    ADD "host_connection_epoch" UUID;
-CREATE INDEX "index_projects_on_host_connection_epoch" ON "projects" ("host_connection_epoch");
-
-CREATE TABLE "worktrees" (
-    "project_id" INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE,
-    "id" INT8 NOT NULL,
-    "root_name" VARCHAR NOT NULL,
-    "abs_path" VARCHAR NOT NULL,
-    "visible" BOOL NOT NULL,
-    "scan_id" INT8 NOT NULL,
-    "is_complete" BOOL NOT NULL,
-    PRIMARY KEY(project_id, id)
-);
-CREATE INDEX "index_worktrees_on_project_id" ON "worktrees" ("project_id");
-
-CREATE TABLE "worktree_entries" (
-    "project_id" INTEGER NOT NULL,
-    "worktree_id" INT8 NOT NULL,
-    "id" INT8 NOT NULL,
-    "is_dir" BOOL NOT NULL,
-    "path" VARCHAR NOT NULL,
-    "inode" INT8 NOT NULL,
-    "mtime_seconds" INT8 NOT NULL,
-    "mtime_nanos" INTEGER NOT NULL,
-    "is_symlink" BOOL NOT NULL,
-    "is_ignored" BOOL NOT NULL,
-    PRIMARY KEY(project_id, worktree_id, id),
-    FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE
-);
-CREATE INDEX "index_worktree_entries_on_project_id" ON "worktree_entries" ("project_id");
-CREATE INDEX "index_worktree_entries_on_project_id_and_worktree_id" ON "worktree_entries" ("project_id", "worktree_id");
-
-CREATE TABLE "worktree_diagnostic_summaries" (
-    "project_id" INTEGER NOT NULL,
-    "worktree_id" INT8 NOT NULL,
-    "path" VARCHAR NOT NULL,
-    "language_server_id" INT8 NOT NULL,
-    "error_count" INTEGER NOT NULL,
-    "warning_count" INTEGER NOT NULL,
-    PRIMARY KEY(project_id, worktree_id, path),
-    FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE
-);
-CREATE INDEX "index_worktree_diagnostic_summaries_on_project_id" ON "worktree_diagnostic_summaries" ("project_id");
-CREATE INDEX "index_worktree_diagnostic_summaries_on_project_id_and_worktree_id" ON "worktree_diagnostic_summaries" ("project_id", "worktree_id");
-
-CREATE TABLE "language_servers" (
-    "project_id" INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE,
-    "id" INT8 NOT NULL,
-    "name" VARCHAR NOT NULL,
-    PRIMARY KEY(project_id, id)
-);
-CREATE INDEX "index_language_servers_on_project_id" ON "language_servers" ("project_id");
-
-CREATE TABLE "project_collaborators" (
-    "id" SERIAL PRIMARY KEY,
-    "project_id" INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE,
-    "connection_id" INTEGER NOT NULL,
-    "connection_epoch" UUID NOT NULL,
-    "user_id" INTEGER NOT NULL,
-    "replica_id" INTEGER NOT NULL,
-    "is_host" BOOLEAN NOT NULL
-);
-CREATE INDEX "index_project_collaborators_on_project_id" ON "project_collaborators" ("project_id");
-CREATE UNIQUE INDEX "index_project_collaborators_on_project_id_and_replica_id" ON "project_collaborators" ("project_id", "replica_id");
-CREATE INDEX "index_project_collaborators_on_connection_epoch" ON "project_collaborators" ("connection_epoch");
-
-CREATE TABLE "room_participants" (
-    "id" SERIAL PRIMARY KEY,
-    "room_id" INTEGER NOT NULL REFERENCES rooms (id),
-    "user_id" INTEGER NOT NULL REFERENCES users (id),
-    "answering_connection_id" INTEGER,
-    "answering_connection_epoch" UUID,
-    "location_kind" INTEGER,
-    "location_project_id" INTEGER,
-    "initial_project_id" INTEGER,
-    "calling_user_id" INTEGER NOT NULL REFERENCES users (id),
-    "calling_connection_id" INTEGER NOT NULL,
-    "calling_connection_epoch" UUID NOT NULL
-);
-CREATE UNIQUE INDEX "index_room_participants_on_user_id" ON "room_participants" ("user_id");
-CREATE INDEX "index_room_participants_on_answering_connection_epoch" ON "room_participants" ("answering_connection_epoch");
-CREATE INDEX "index_room_participants_on_calling_connection_epoch" ON "room_participants" ("calling_connection_epoch");

crates/collab2/migrations/20221207165001_add_connection_lost_to_room_participants.sql 🔗

@@ -1,7 +0,0 @@
-ALTER TABLE "room_participants"
-    ADD "answering_connection_lost" BOOLEAN NOT NULL DEFAULT FALSE;
-
-CREATE INDEX "index_project_collaborators_on_connection_id" ON "project_collaborators" ("connection_id");
-CREATE UNIQUE INDEX "index_project_collaborators_on_project_id_connection_id_and_epoch" ON "project_collaborators" ("project_id", "connection_id", "connection_epoch");
-CREATE INDEX "index_room_participants_on_answering_connection_id" ON "room_participants" ("answering_connection_id");
-CREATE UNIQUE INDEX "index_room_participants_on_answering_connection_id_and_answering_connection_epoch" ON "room_participants" ("answering_connection_id", "answering_connection_epoch");

crates/collab2/migrations/20221214144346_change_epoch_from_uuid_to_integer.sql 🔗

@@ -1,30 +0,0 @@
-CREATE TABLE servers (
-    id SERIAL PRIMARY KEY,
-    environment VARCHAR NOT NULL
-);
-
-DROP TABLE worktree_extensions;
-DROP TABLE project_activity_periods;
-DELETE from projects;
-ALTER TABLE projects
-    DROP COLUMN host_connection_epoch,
-    ADD COLUMN host_connection_server_id INTEGER REFERENCES servers (id) ON DELETE CASCADE;
-CREATE INDEX "index_projects_on_host_connection_server_id" ON "projects" ("host_connection_server_id");
-CREATE INDEX "index_projects_on_host_connection_id_and_host_connection_server_id" ON "projects" ("host_connection_id", "host_connection_server_id");
-
-DELETE FROM project_collaborators;
-ALTER TABLE project_collaborators
-    DROP COLUMN connection_epoch,
-    ADD COLUMN connection_server_id INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE;
-CREATE INDEX "index_project_collaborators_on_connection_server_id" ON "project_collaborators" ("connection_server_id");
-CREATE UNIQUE INDEX "index_project_collaborators_on_project_id_connection_id_and_server_id" ON "project_collaborators" ("project_id", "connection_id", "connection_server_id");
-
-DELETE FROM room_participants;
-ALTER TABLE room_participants
-    DROP COLUMN answering_connection_epoch,
-    DROP COLUMN calling_connection_epoch,
-    ADD COLUMN answering_connection_server_id INTEGER REFERENCES servers (id) ON DELETE CASCADE,
-    ADD COLUMN calling_connection_server_id INTEGER REFERENCES servers (id) ON DELETE SET NULL;
-CREATE INDEX "index_room_participants_on_answering_connection_server_id" ON "room_participants" ("answering_connection_server_id");
-CREATE INDEX "index_room_participants_on_calling_connection_server_id" ON "room_participants" ("calling_connection_server_id");
-CREATE UNIQUE INDEX "index_room_participants_on_answering_connection_id_and_answering_connection_server_id" ON "room_participants" ("answering_connection_id", "answering_connection_server_id");

crates/collab2/migrations/20230202155735_followers.sql 🔗

@@ -1,15 +0,0 @@
-CREATE TABLE IF NOT EXISTS "followers" (
-    "id" SERIAL PRIMARY KEY,
-    "room_id" INTEGER NOT NULL REFERENCES rooms (id) ON DELETE CASCADE,
-    "project_id" INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE,
-    "leader_connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE,
-    "leader_connection_id" INTEGER NOT NULL,
-    "follower_connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE,
-    "follower_connection_id" INTEGER NOT NULL
-);
-
-CREATE UNIQUE INDEX 
-    "index_followers_on_project_id_and_leader_connection_server_id_and_leader_connection_id_and_follower_connection_server_id_and_follower_connection_id"
-ON "followers" ("project_id", "leader_connection_server_id", "leader_connection_id", "follower_connection_server_id", "follower_connection_id");
-
-CREATE INDEX "index_followers_on_room_id" ON "followers" ("room_id");

crates/collab2/migrations/20230508211523_add-repository-entries.sql 🔗

@@ -1,13 +0,0 @@
-CREATE TABLE "worktree_repositories" (
-    "project_id" INTEGER NOT NULL,
-    "worktree_id" INT8 NOT NULL,
-    "work_directory_id" INT8 NOT NULL,
-    "scan_id" INT8 NOT NULL,
-    "branch" VARCHAR,
-    "is_deleted" BOOL NOT NULL,
-    PRIMARY KEY(project_id, worktree_id, work_directory_id),
-    FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE,
-    FOREIGN KEY(project_id, worktree_id, work_directory_id) REFERENCES worktree_entries (project_id, worktree_id, id) ON DELETE CASCADE
-);
-CREATE INDEX "index_worktree_repositories_on_project_id" ON "worktree_repositories" ("project_id");
-CREATE INDEX "index_worktree_repositories_on_project_id_and_worktree_id" ON "worktree_repositories" ("project_id", "worktree_id");

crates/collab2/migrations/20230511004019_add_repository_statuses.sql 🔗

@@ -1,15 +0,0 @@
-CREATE TABLE "worktree_repository_statuses" (
-    "project_id" INTEGER NOT NULL,
-    "worktree_id" INT8 NOT NULL,
-    "work_directory_id" INT8 NOT NULL,
-    "repo_path" VARCHAR NOT NULL,
-    "status" INT8 NOT NULL,
-    "scan_id" INT8 NOT NULL,
-    "is_deleted" BOOL NOT NULL,
-    PRIMARY KEY(project_id, worktree_id, work_directory_id, repo_path),
-    FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE,
-    FOREIGN KEY(project_id, worktree_id, work_directory_id) REFERENCES worktree_entries (project_id, worktree_id, id) ON DELETE CASCADE
-);
-CREATE INDEX "index_wt_repos_statuses_on_project_id" ON "worktree_repository_statuses" ("project_id");
-CREATE INDEX "index_wt_repos_statuses_on_project_id_and_wt_id" ON "worktree_repository_statuses" ("project_id", "worktree_id");
-CREATE INDEX "index_wt_repos_statuses_on_project_id_and_wt_id_and_wd_id" ON "worktree_repository_statuses" ("project_id", "worktree_id", "work_directory_id");

crates/collab2/migrations/20230529164700_add_worktree_settings_files.sql 🔗

@@ -1,10 +0,0 @@
-CREATE TABLE "worktree_settings_files" (
-    "project_id" INTEGER NOT NULL,
-    "worktree_id" INT8 NOT NULL,
-    "path" VARCHAR NOT NULL,
-    "content" TEXT NOT NULL,
-    PRIMARY KEY(project_id, worktree_id, path),
-    FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE
-);
-CREATE INDEX "index_settings_files_on_project_id" ON "worktree_settings_files" ("project_id");
-CREATE INDEX "index_settings_files_on_project_id_and_wt_id" ON "worktree_settings_files" ("project_id", "worktree_id");

crates/collab2/migrations/20230727150500_add_channels.sql 🔗

@@ -1,30 +0,0 @@
-DROP TABLE "channel_messages";
-DROP TABLE "channel_memberships";
-DROP TABLE "org_memberships";
-DROP TABLE "orgs";
-DROP TABLE "channels";
-
-CREATE TABLE "channels" (
-    "id" SERIAL PRIMARY KEY,
-    "name" VARCHAR NOT NULL,
-    "created_at" TIMESTAMP NOT NULL DEFAULT now()
-);
-
-CREATE TABLE "channel_paths" (
-    "id_path" VARCHAR NOT NULL PRIMARY KEY,
-    "channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE
-);
-CREATE INDEX "index_channel_paths_on_channel_id" ON "channel_paths" ("channel_id");
-
-CREATE TABLE "channel_members" (
-    "id" SERIAL PRIMARY KEY,
-    "channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
-    "user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
-    "admin" BOOLEAN NOT NULL DEFAULT false,
-    "accepted" BOOLEAN NOT NULL DEFAULT false,
-    "updated_at" TIMESTAMP NOT NULL DEFAULT now()
-);
-
-CREATE UNIQUE INDEX "index_channel_members_on_channel_id_and_user_id" ON "channel_members" ("channel_id", "user_id");
-
-ALTER TABLE rooms ADD COLUMN "channel_id" INTEGER REFERENCES channels (id) ON DELETE CASCADE;

crates/collab2/migrations/20230819154600_add_channel_buffers.sql 🔗

@@ -1,40 +0,0 @@
-CREATE TABLE "buffers" (
-    "id" SERIAL PRIMARY KEY,
-    "channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
-    "epoch" INTEGER NOT NULL DEFAULT 0
-);
-
-CREATE INDEX "index_buffers_on_channel_id" ON "buffers" ("channel_id");
-
-CREATE TABLE "buffer_operations" (
-    "buffer_id" INTEGER NOT NULL REFERENCES buffers (id) ON DELETE CASCADE,
-    "epoch" INTEGER NOT NULL,
-    "replica_id" INTEGER NOT NULL,
-    "lamport_timestamp" INTEGER NOT NULL,
-    "value" BYTEA NOT NULL,
-    PRIMARY KEY(buffer_id, epoch, lamport_timestamp, replica_id)
-);
-
-CREATE TABLE "buffer_snapshots" (
-    "buffer_id" INTEGER NOT NULL REFERENCES buffers (id) ON DELETE CASCADE,
-    "epoch" INTEGER NOT NULL,
-    "text" TEXT NOT NULL,
-    "operation_serialization_version" INTEGER NOT NULL,
-    PRIMARY KEY(buffer_id, epoch)
-);
-
-CREATE TABLE "channel_buffer_collaborators" (
-    "id" SERIAL PRIMARY KEY,
-    "channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
-    "connection_id" INTEGER NOT NULL,
-    "connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE,
-    "connection_lost" BOOLEAN NOT NULL DEFAULT FALSE,
-    "user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
-    "replica_id" INTEGER NOT NULL
-);
-
-CREATE INDEX "index_channel_buffer_collaborators_on_channel_id" ON "channel_buffer_collaborators" ("channel_id");
-CREATE UNIQUE INDEX "index_channel_buffer_collaborators_on_channel_id_and_replica_id" ON "channel_buffer_collaborators" ("channel_id", "replica_id");
-CREATE INDEX "index_channel_buffer_collaborators_on_connection_server_id" ON "channel_buffer_collaborators" ("connection_server_id");
-CREATE INDEX "index_channel_buffer_collaborators_on_connection_id" ON "channel_buffer_collaborators" ("connection_id");
-CREATE UNIQUE INDEX "index_channel_buffer_collaborators_on_channel_id_connection_id_and_server_id" ON "channel_buffer_collaborators" ("channel_id", "connection_id", "connection_server_id");

crates/collab2/migrations/20230825190322_add_server_feature_flags.sql 🔗

@@ -1,16 +0,0 @@
-CREATE TABLE "feature_flags" (
-    "id" SERIAL PRIMARY KEY,
-    "flag" VARCHAR(255) NOT NULL UNIQUE
-);
-
-CREATE UNIQUE INDEX "index_feature_flags" ON "feature_flags" ("id");
-
-CREATE TABLE "user_features" (
-    "user_id" INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
-    "feature_id" INTEGER NOT NULL REFERENCES feature_flags(id) ON DELETE CASCADE,
-    PRIMARY KEY (user_id, feature_id)
-);
-
-CREATE UNIQUE INDEX "index_user_features_user_id_and_feature_id" ON "user_features" ("user_id", "feature_id");
-CREATE INDEX "index_user_features_on_user_id" ON "user_features" ("user_id");
-CREATE INDEX "index_user_features_on_feature_id" ON "user_features" ("feature_id");

crates/collab2/migrations/20230907114200_add_channel_messages.sql 🔗

@@ -1,19 +0,0 @@
-CREATE TABLE IF NOT EXISTS "channel_messages" (
-    "id" SERIAL PRIMARY KEY,
-    "channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
-    "sender_id" INTEGER NOT NULL REFERENCES users (id),
-    "body" TEXT NOT NULL,
-    "sent_at" TIMESTAMP,
-    "nonce" UUID NOT NULL
-);
-CREATE INDEX "index_channel_messages_on_channel_id" ON "channel_messages" ("channel_id");
-CREATE UNIQUE INDEX "index_channel_messages_on_nonce" ON "channel_messages" ("nonce");
-
-CREATE TABLE IF NOT EXISTS "channel_chat_participants" (
-    "id" SERIAL PRIMARY KEY,
-    "user_id" INTEGER NOT NULL REFERENCES users (id),
-    "channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
-    "connection_id" INTEGER NOT NULL,
-    "connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE
-);
-CREATE INDEX "index_channel_chat_participants_on_channel_id" ON "channel_chat_participants" ("channel_id");

crates/collab2/migrations/20230925210437_add_channel_changes.sql 🔗

@@ -1,19 +0,0 @@
-CREATE TABLE IF NOT EXISTS "observed_buffer_edits" (
-    "user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
-    "buffer_id" INTEGER NOT NULL REFERENCES buffers (id) ON DELETE CASCADE,
-    "epoch" INTEGER NOT NULL,
-    "lamport_timestamp" INTEGER NOT NULL,
-    "replica_id" INTEGER NOT NULL,
-    PRIMARY KEY (user_id, buffer_id)
-);
-
-CREATE UNIQUE INDEX "index_observed_buffer_user_and_buffer_id" ON "observed_buffer_edits" ("user_id", "buffer_id");
-
-CREATE TABLE IF NOT EXISTS "observed_channel_messages" (
-    "user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
-    "channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
-    "channel_message_id" INTEGER NOT NULL,
-    PRIMARY KEY (user_id, channel_id)
-);
-
-CREATE UNIQUE INDEX "index_observed_channel_messages_user_and_channel_id" ON "observed_channel_messages" ("user_id", "channel_id");

crates/collab2/migrations/20231004130100_create_notifications.sql 🔗

@@ -1,22 +0,0 @@
-CREATE TABLE "notification_kinds" (
-    "id" SERIAL PRIMARY KEY,
-    "name" VARCHAR NOT NULL
-);
-
-CREATE UNIQUE INDEX "index_notification_kinds_on_name" ON "notification_kinds" ("name");
-
-CREATE TABLE notifications (
-    "id" SERIAL PRIMARY KEY,
-    "created_at" TIMESTAMP NOT NULL DEFAULT now(),
-    "recipient_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
-    "kind" INTEGER NOT NULL REFERENCES notification_kinds (id),
-    "entity_id" INTEGER,
-    "content" TEXT,
-    "is_read" BOOLEAN NOT NULL DEFAULT FALSE,
-    "response" BOOLEAN
-);
-
-CREATE INDEX
-    "index_notifications_on_recipient_id_is_read_kind_entity_id"
-    ON "notifications"
-    ("recipient_id", "is_read", "kind", "entity_id");

crates/collab2/migrations/20231018102700_create_mentions.sql 🔗

@@ -1,11 +0,0 @@
-CREATE TABLE "channel_message_mentions" (
-    "message_id" INTEGER NOT NULL REFERENCES channel_messages (id) ON DELETE CASCADE,
-    "start_offset" INTEGER NOT NULL,
-    "end_offset" INTEGER NOT NULL,
-    "user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
-    PRIMARY KEY(message_id, start_offset)
-);
-
--- We use 'on conflict update' with this index, so it should be per-user.
-CREATE UNIQUE INDEX "index_channel_messages_on_sender_id_nonce" ON "channel_messages" ("sender_id", "nonce");
-DROP INDEX "index_channel_messages_on_nonce";

crates/collab2/migrations/20231024085546_move_channel_paths_to_channels_table.sql 🔗

@@ -1,12 +0,0 @@
-ALTER TABLE channels ADD COLUMN parent_path TEXT;
-
-UPDATE channels
-SET parent_path = substr(
-    channel_paths.id_path,
-    2,
-    length(channel_paths.id_path) - length('/' || channel_paths.channel_id::text || '/')
-)
-FROM channel_paths
-WHERE channel_paths.channel_id = channels.id;
-
-CREATE INDEX "index_channels_on_parent_path" ON "channels" ("parent_path");

crates/collab2/src/api.rs 🔗

@@ -1,186 +0,0 @@
-use crate::{
-    auth,
-    db::{User, UserId},
-    rpc, AppState, Error, Result,
-};
-use anyhow::anyhow;
-use axum::{
-    body::Body,
-    extract::{Path, Query},
-    http::{self, Request, StatusCode},
-    middleware::{self, Next},
-    response::IntoResponse,
-    routing::{get, post},
-    Extension, Json, Router,
-};
-use axum_extra::response::ErasedJson;
-use serde::{Deserialize, Serialize};
-use std::sync::Arc;
-use tower::ServiceBuilder;
-use tracing::instrument;
-
-pub fn routes(rpc_server: Arc<rpc::Server>, state: Arc<AppState>) -> Router<Body> {
-    Router::new()
-        .route("/user", get(get_authenticated_user))
-        .route("/users/:id/access_tokens", post(create_access_token))
-        .route("/panic", post(trace_panic))
-        .route("/rpc_server_snapshot", get(get_rpc_server_snapshot))
-        .layer(
-            ServiceBuilder::new()
-                .layer(Extension(state))
-                .layer(Extension(rpc_server))
-                .layer(middleware::from_fn(validate_api_token)),
-        )
-}
-
-pub async fn validate_api_token<B>(req: Request<B>, next: Next<B>) -> impl IntoResponse {
-    let token = req
-        .headers()
-        .get(http::header::AUTHORIZATION)
-        .and_then(|header| header.to_str().ok())
-        .ok_or_else(|| {
-            Error::Http(
-                StatusCode::BAD_REQUEST,
-                "missing authorization header".to_string(),
-            )
-        })?
-        .strip_prefix("token ")
-        .ok_or_else(|| {
-            Error::Http(
-                StatusCode::BAD_REQUEST,
-                "invalid authorization header".to_string(),
-            )
-        })?;
-
-    let state = req.extensions().get::<Arc<AppState>>().unwrap();
-
-    if token != state.config.api_token {
-        Err(Error::Http(
-            StatusCode::UNAUTHORIZED,
-            "invalid authorization token".to_string(),
-        ))?
-    }
-
-    Ok::<_, Error>(next.run(req).await)
-}
-
-#[derive(Debug, Deserialize)]
-struct AuthenticatedUserParams {
-    github_user_id: Option<i32>,
-    github_login: String,
-    github_email: Option<String>,
-}
-
-#[derive(Debug, Serialize)]
-struct AuthenticatedUserResponse {
-    user: User,
-    metrics_id: String,
-}
-
-async fn get_authenticated_user(
-    Query(params): Query<AuthenticatedUserParams>,
-    Extension(app): Extension<Arc<AppState>>,
-) -> Result<Json<AuthenticatedUserResponse>> {
-    let user = app
-        .db
-        .get_or_create_user_by_github_account(
-            &params.github_login,
-            params.github_user_id,
-            params.github_email.as_deref(),
-        )
-        .await?
-        .ok_or_else(|| Error::Http(StatusCode::NOT_FOUND, "user not found".into()))?;
-    let metrics_id = app.db.get_user_metrics_id(user.id).await?;
-    return Ok(Json(AuthenticatedUserResponse { user, metrics_id }));
-}
-
-#[derive(Deserialize, Debug)]
-struct CreateUserParams {
-    github_user_id: i32,
-    github_login: String,
-    email_address: String,
-    email_confirmation_code: Option<String>,
-    #[serde(default)]
-    admin: bool,
-    #[serde(default)]
-    invite_count: i32,
-}
-
-#[derive(Serialize, Debug)]
-struct CreateUserResponse {
-    user: User,
-    signup_device_id: Option<String>,
-    metrics_id: String,
-}
-
-#[derive(Debug, Deserialize)]
-struct Panic {
-    version: String,
-    release_channel: String,
-    backtrace_hash: String,
-    text: String,
-}
-
-#[instrument(skip(panic))]
-async fn trace_panic(panic: Json<Panic>) -> Result<()> {
-    tracing::error!(version = %panic.version, release_channel = %panic.release_channel, backtrace_hash = %panic.backtrace_hash, text = %panic.text, "panic report");
-    Ok(())
-}
-
-async fn get_rpc_server_snapshot(
-    Extension(rpc_server): Extension<Arc<rpc::Server>>,
-) -> Result<ErasedJson> {
-    Ok(ErasedJson::pretty(rpc_server.snapshot().await))
-}
-
-#[derive(Deserialize)]
-struct CreateAccessTokenQueryParams {
-    public_key: String,
-    impersonate: Option<String>,
-}
-
-#[derive(Serialize)]
-struct CreateAccessTokenResponse {
-    user_id: UserId,
-    encrypted_access_token: String,
-}
-
-async fn create_access_token(
-    Path(user_id): Path<UserId>,
-    Query(params): Query<CreateAccessTokenQueryParams>,
-    Extension(app): Extension<Arc<AppState>>,
-) -> Result<Json<CreateAccessTokenResponse>> {
-    let user = app
-        .db
-        .get_user_by_id(user_id)
-        .await?
-        .ok_or_else(|| anyhow!("user not found"))?;
-
-    let mut user_id = user.id;
-    if let Some(impersonate) = params.impersonate {
-        if user.admin {
-            if let Some(impersonated_user) = app.db.get_user_by_github_login(&impersonate).await? {
-                user_id = impersonated_user.id;
-            } else {
-                return Err(Error::Http(
-                    StatusCode::UNPROCESSABLE_ENTITY,
-                    format!("user {impersonate} does not exist"),
-                ));
-            }
-        } else {
-            return Err(Error::Http(
-                StatusCode::UNAUTHORIZED,
-                "you do not have permission to impersonate other users".to_string(),
-            ));
-        }
-    }
-
-    let access_token = auth::create_access_token(app.db.as_ref(), user_id).await?;
-    let encrypted_access_token =
-        auth::encrypt_access_token(&access_token, params.public_key.clone())?;
-
-    Ok(Json(CreateAccessTokenResponse {
-        user_id,
-        encrypted_access_token,
-    }))
-}

crates/collab2/src/auth.rs 🔗

@@ -1,151 +0,0 @@
-use crate::{
-    db::{self, AccessTokenId, Database, UserId},
-    AppState, Error, Result,
-};
-use anyhow::{anyhow, Context};
-use axum::{
-    http::{self, Request, StatusCode},
-    middleware::Next,
-    response::IntoResponse,
-};
-use lazy_static::lazy_static;
-use prometheus::{exponential_buckets, register_histogram, Histogram};
-use rand::thread_rng;
-use scrypt::{
-    password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
-    Scrypt,
-};
-use serde::{Deserialize, Serialize};
-use std::{sync::Arc, time::Instant};
-
-lazy_static! {
-    static ref METRIC_ACCESS_TOKEN_HASHING_TIME: Histogram = register_histogram!(
-        "access_token_hashing_time",
-        "time spent hashing access tokens",
-        exponential_buckets(10.0, 2.0, 10).unwrap(),
-    )
-    .unwrap();
-}
-
-pub async fn validate_header<B>(mut req: Request<B>, next: Next<B>) -> impl IntoResponse {
-    let mut auth_header = req
-        .headers()
-        .get(http::header::AUTHORIZATION)
-        .and_then(|header| header.to_str().ok())
-        .ok_or_else(|| {
-            Error::Http(
-                StatusCode::UNAUTHORIZED,
-                "missing authorization header".to_string(),
-            )
-        })?
-        .split_whitespace();
-
-    let user_id = UserId(auth_header.next().unwrap_or("").parse().map_err(|_| {
-        Error::Http(
-            StatusCode::BAD_REQUEST,
-            "missing user id in authorization header".to_string(),
-        )
-    })?);
-
-    let access_token = auth_header.next().ok_or_else(|| {
-        Error::Http(
-            StatusCode::BAD_REQUEST,
-            "missing access token in authorization header".to_string(),
-        )
-    })?;
-
-    let state = req.extensions().get::<Arc<AppState>>().unwrap();
-    let credentials_valid = if let Some(admin_token) = access_token.strip_prefix("ADMIN_TOKEN:") {
-        state.config.api_token == admin_token
-    } else {
-        verify_access_token(&access_token, user_id, &state.db)
-            .await
-            .unwrap_or(false)
-    };
-
-    if credentials_valid {
-        let user = state
-            .db
-            .get_user_by_id(user_id)
-            .await?
-            .ok_or_else(|| anyhow!("user {} not found", user_id))?;
-        req.extensions_mut().insert(user);
-        Ok::<_, Error>(next.run(req).await)
-    } else {
-        Err(Error::Http(
-            StatusCode::UNAUTHORIZED,
-            "invalid credentials".to_string(),
-        ))
-    }
-}
-
-const MAX_ACCESS_TOKENS_TO_STORE: usize = 8;
-
-#[derive(Serialize, Deserialize)]
-struct AccessTokenJson {
-    version: usize,
-    id: AccessTokenId,
-    token: String,
-}
-
-pub async fn create_access_token(db: &db::Database, user_id: UserId) -> Result<String> {
-    const VERSION: usize = 1;
-    let access_token = rpc::auth::random_token();
-    let access_token_hash =
-        hash_access_token(&access_token).context("failed to hash access token")?;
-    let id = db
-        .create_access_token(user_id, &access_token_hash, MAX_ACCESS_TOKENS_TO_STORE)
-        .await?;
-    Ok(serde_json::to_string(&AccessTokenJson {
-        version: VERSION,
-        id,
-        token: access_token,
-    })?)
-}
-
-fn hash_access_token(token: &str) -> Result<String> {
-    // Avoid slow hashing in debug mode.
-    let params = if cfg!(debug_assertions) {
-        scrypt::Params::new(1, 1, 1).unwrap()
-    } else {
-        scrypt::Params::new(14, 8, 1).unwrap()
-    };
-
-    Ok(Scrypt
-        .hash_password(
-            token.as_bytes(),
-            None,
-            params,
-            &SaltString::generate(thread_rng()),
-        )
-        .map_err(anyhow::Error::new)?
-        .to_string())
-}
-
-pub fn encrypt_access_token(access_token: &str, public_key: String) -> Result<String> {
-    let native_app_public_key =
-        rpc::auth::PublicKey::try_from(public_key).context("failed to parse app public key")?;
-    let encrypted_access_token = native_app_public_key
-        .encrypt_string(access_token)
-        .context("failed to encrypt access token with public key")?;
-    Ok(encrypted_access_token)
-}
-
-pub async fn verify_access_token(token: &str, user_id: UserId, db: &Arc<Database>) -> Result<bool> {
-    let token: AccessTokenJson = serde_json::from_str(&token)?;
-
-    let db_token = db.get_access_token(token.id).await?;
-    if db_token.user_id != user_id {
-        return Err(anyhow!("no such access token"))?;
-    }
-
-    let db_hash = PasswordHash::new(&db_token.hash).map_err(anyhow::Error::new)?;
-    let t0 = Instant::now();
-    let is_valid = Scrypt
-        .verify_password(token.token.as_bytes(), &db_hash)
-        .is_ok();
-    let duration = t0.elapsed();
-    log::info!("hashed access token in {:?}", duration);
-    METRIC_ACCESS_TOKEN_HASHING_TIME.observe(duration.as_millis() as f64);
-    Ok(is_valid)
-}

crates/collab2/src/bin/dotenv2.rs 🔗

@@ -1,20 +0,0 @@
-use anyhow::anyhow;
-use std::fs;
-
-fn main() -> anyhow::Result<()> {
-    let env: toml::map::Map<String, toml::Value> = toml::de::from_str(
-        &fs::read_to_string("./.env.toml").map_err(|_| anyhow!("no .env.toml file found"))?,
-    )?;
-
-    for (key, value) in env {
-        let value = match value {
-            toml::Value::String(value) => value,
-            toml::Value::Integer(value) => value.to_string(),
-            toml::Value::Float(value) => value.to_string(),
-            _ => panic!("unsupported TOML value in .env.toml for key {}", key),
-        };
-        println!("export {}=\"{}\"", key, value);
-    }
-
-    Ok(())
-}

crates/collab2/src/bin/seed2.rs 🔗

@@ -1,107 +0,0 @@
-use collab2::{db, executor::Executor};
-use db::{ConnectOptions, Database};
-use serde::{de::DeserializeOwned, Deserialize};
-use std::fmt::Write;
-
-#[derive(Debug, Deserialize)]
-struct GitHubUser {
-    id: i32,
-    login: String,
-    email: Option<String>,
-}
-
-#[tokio::main]
-async fn main() {
-    let database_url = std::env::var("DATABASE_URL").expect("missing DATABASE_URL env var");
-    let db = Database::new(ConnectOptions::new(database_url), Executor::Production)
-        .await
-        .expect("failed to connect to postgres database");
-    let github_token = std::env::var("GITHUB_TOKEN").expect("missing GITHUB_TOKEN env var");
-    let client = reqwest::Client::new();
-
-    let mut current_user =
-        fetch_github::<GitHubUser>(&client, &github_token, "https://api.github.com/user").await;
-    current_user
-        .email
-        .get_or_insert_with(|| "placeholder@example.com".to_string());
-    let staff_users = fetch_github::<Vec<GitHubUser>>(
-        &client,
-        &github_token,
-        "https://api.github.com/orgs/zed-industries/teams/staff/members",
-    )
-    .await;
-
-    let mut zed_users = Vec::new();
-    zed_users.push((current_user, true));
-    zed_users.extend(staff_users.into_iter().map(|user| (user, true)));
-
-    let user_count = db
-        .get_all_users(0, 200)
-        .await
-        .expect("failed to load users from db")
-        .len();
-    if user_count < 100 {
-        let mut last_user_id = None;
-        for _ in 0..10 {
-            let mut uri = "https://api.github.com/users?per_page=100".to_string();
-            if let Some(last_user_id) = last_user_id {
-                write!(&mut uri, "&since={}", last_user_id).unwrap();
-            }
-            let users = fetch_github::<Vec<GitHubUser>>(&client, &github_token, &uri).await;
-            if let Some(last_user) = users.last() {
-                last_user_id = Some(last_user.id);
-                zed_users.extend(users.into_iter().map(|user| (user, false)));
-            } else {
-                break;
-            }
-        }
-    }
-
-    for (github_user, admin) in zed_users {
-        if db
-            .get_user_by_github_login(&github_user.login)
-            .await
-            .expect("failed to fetch user")
-            .is_none()
-        {
-            if admin {
-                db.create_user(
-                    &format!("{}@zed.dev", github_user.login),
-                    admin,
-                    db::NewUserParams {
-                        github_login: github_user.login,
-                        github_user_id: github_user.id,
-                    },
-                )
-                .await
-                .expect("failed to insert user");
-            } else {
-                db.get_or_create_user_by_github_account(
-                    &github_user.login,
-                    Some(github_user.id),
-                    github_user.email.as_deref(),
-                )
-                .await
-                .expect("failed to insert user");
-            }
-        }
-    }
-}
-
-async fn fetch_github<T: DeserializeOwned>(
-    client: &reqwest::Client,
-    access_token: &str,
-    url: &str,
-) -> T {
-    let response = client
-        .get(url)
-        .bearer_auth(&access_token)
-        .header("user-agent", "zed")
-        .send()
-        .await
-        .expect(&format!("failed to fetch '{}'", url));
-    response
-        .json()
-        .await
-        .expect(&format!("failed to deserialize github user from '{}'", url))
-}

crates/collab2/src/db.rs 🔗

@@ -1,672 +0,0 @@
-#[cfg(test)]
-pub mod tests;
-
-#[cfg(test)]
-pub use tests::TestDb;
-
-mod ids;
-mod queries;
-mod tables;
-
-use crate::{executor::Executor, Error, Result};
-use anyhow::anyhow;
-use collections::{BTreeMap, HashMap, HashSet};
-use dashmap::DashMap;
-use futures::StreamExt;
-use rand::{prelude::StdRng, Rng, SeedableRng};
-use rpc::{
-    proto::{self},
-    ConnectionId,
-};
-use sea_orm::{
-    entity::prelude::*,
-    sea_query::{Alias, Expr, OnConflict},
-    ActiveValue, Condition, ConnectionTrait, DatabaseConnection, DatabaseTransaction, DbErr,
-    FromQueryResult, IntoActiveModel, IsolationLevel, JoinType, QueryOrder, QuerySelect, Statement,
-    TransactionTrait,
-};
-use serde::{Deserialize, Serialize};
-use sqlx::{
-    migrate::{Migrate, Migration, MigrationSource},
-    Connection,
-};
-use std::{
-    fmt::Write as _,
-    future::Future,
-    marker::PhantomData,
-    ops::{Deref, DerefMut},
-    path::Path,
-    rc::Rc,
-    sync::Arc,
-    time::Duration,
-};
-use tables::*;
-use tokio::sync::{Mutex, OwnedMutexGuard};
-
-pub use ids::*;
-pub use sea_orm::ConnectOptions;
-pub use tables::user::Model as User;
-
-pub struct Database {
-    options: ConnectOptions,
-    pool: DatabaseConnection,
-    rooms: DashMap<RoomId, Arc<Mutex<()>>>,
-    rng: Mutex<StdRng>,
-    executor: Executor,
-    notification_kinds_by_id: HashMap<NotificationKindId, &'static str>,
-    notification_kinds_by_name: HashMap<String, NotificationKindId>,
-    #[cfg(test)]
-    runtime: Option<tokio::runtime::Runtime>,
-}
-
-// The `Database` type has so many methods that its impl blocks are split into
-// separate files in the `queries` folder.
-impl Database {
-    pub async fn new(options: ConnectOptions, executor: Executor) -> Result<Self> {
-        sqlx::any::install_default_drivers();
-        Ok(Self {
-            options: options.clone(),
-            pool: sea_orm::Database::connect(options).await?,
-            rooms: DashMap::with_capacity(16384),
-            rng: Mutex::new(StdRng::seed_from_u64(0)),
-            notification_kinds_by_id: HashMap::default(),
-            notification_kinds_by_name: HashMap::default(),
-            executor,
-            #[cfg(test)]
-            runtime: None,
-        })
-    }
-
-    #[cfg(test)]
-    pub fn reset(&self) {
-        self.rooms.clear();
-    }
-
-    pub async fn migrate(
-        &self,
-        migrations_path: &Path,
-        ignore_checksum_mismatch: bool,
-    ) -> anyhow::Result<Vec<(Migration, Duration)>> {
-        let migrations = MigrationSource::resolve(migrations_path)
-            .await
-            .map_err(|err| anyhow!("failed to load migrations: {err:?}"))?;
-
-        let mut connection = sqlx::AnyConnection::connect(self.options.get_url()).await?;
-
-        connection.ensure_migrations_table().await?;
-        let applied_migrations: HashMap<_, _> = connection
-            .list_applied_migrations()
-            .await?
-            .into_iter()
-            .map(|m| (m.version, m))
-            .collect();
-
-        let mut new_migrations = Vec::new();
-        for migration in migrations {
-            match applied_migrations.get(&migration.version) {
-                Some(applied_migration) => {
-                    if migration.checksum != applied_migration.checksum && !ignore_checksum_mismatch
-                    {
-                        Err(anyhow!(
-                            "checksum mismatch for applied migration {}",
-                            migration.description
-                        ))?;
-                    }
-                }
-                None => {
-                    let elapsed = connection.apply(&migration).await?;
-                    new_migrations.push((migration, elapsed));
-                }
-            }
-        }
-
-        Ok(new_migrations)
-    }
-
-    pub async fn initialize_static_data(&mut self) -> Result<()> {
-        self.initialize_notification_kinds().await?;
-        Ok(())
-    }
-
-    pub async fn transaction<F, Fut, T>(&self, f: F) -> Result<T>
-    where
-        F: Send + Fn(TransactionHandle) -> Fut,
-        Fut: Send + Future<Output = Result<T>>,
-    {
-        let body = async {
-            let mut i = 0;
-            loop {
-                let (tx, result) = self.with_transaction(&f).await?;
-                match result {
-                    Ok(result) => match tx.commit().await.map_err(Into::into) {
-                        Ok(()) => return Ok(result),
-                        Err(error) => {
-                            if !self.retry_on_serialization_error(&error, i).await {
-                                return Err(error);
-                            }
-                        }
-                    },
-                    Err(error) => {
-                        tx.rollback().await?;
-                        if !self.retry_on_serialization_error(&error, i).await {
-                            return Err(error);
-                        }
-                    }
-                }
-                i += 1;
-            }
-        };
-
-        self.run(body).await
-    }
-
-    async fn optional_room_transaction<F, Fut, T>(&self, f: F) -> Result<Option<RoomGuard<T>>>
-    where
-        F: Send + Fn(TransactionHandle) -> Fut,
-        Fut: Send + Future<Output = Result<Option<(RoomId, T)>>>,
-    {
-        let body = async {
-            let mut i = 0;
-            loop {
-                let (tx, result) = self.with_transaction(&f).await?;
-                match result {
-                    Ok(Some((room_id, data))) => {
-                        let lock = self.rooms.entry(room_id).or_default().clone();
-                        let _guard = lock.lock_owned().await;
-                        match tx.commit().await.map_err(Into::into) {
-                            Ok(()) => {
-                                return Ok(Some(RoomGuard {
-                                    data,
-                                    _guard,
-                                    _not_send: PhantomData,
-                                }));
-                            }
-                            Err(error) => {
-                                if !self.retry_on_serialization_error(&error, i).await {
-                                    return Err(error);
-                                }
-                            }
-                        }
-                    }
-                    Ok(None) => match tx.commit().await.map_err(Into::into) {
-                        Ok(()) => return Ok(None),
-                        Err(error) => {
-                            if !self.retry_on_serialization_error(&error, i).await {
-                                return Err(error);
-                            }
-                        }
-                    },
-                    Err(error) => {
-                        tx.rollback().await?;
-                        if !self.retry_on_serialization_error(&error, i).await {
-                            return Err(error);
-                        }
-                    }
-                }
-                i += 1;
-            }
-        };
-
-        self.run(body).await
-    }
-
-    async fn room_transaction<F, Fut, T>(&self, room_id: RoomId, f: F) -> Result<RoomGuard<T>>
-    where
-        F: Send + Fn(TransactionHandle) -> Fut,
-        Fut: Send + Future<Output = Result<T>>,
-    {
-        let body = async {
-            let mut i = 0;
-            loop {
-                let lock = self.rooms.entry(room_id).or_default().clone();
-                let _guard = lock.lock_owned().await;
-                let (tx, result) = self.with_transaction(&f).await?;
-                match result {
-                    Ok(data) => match tx.commit().await.map_err(Into::into) {
-                        Ok(()) => {
-                            return Ok(RoomGuard {
-                                data,
-                                _guard,
-                                _not_send: PhantomData,
-                            });
-                        }
-                        Err(error) => {
-                            if !self.retry_on_serialization_error(&error, i).await {
-                                return Err(error);
-                            }
-                        }
-                    },
-                    Err(error) => {
-                        tx.rollback().await?;
-                        if !self.retry_on_serialization_error(&error, i).await {
-                            return Err(error);
-                        }
-                    }
-                }
-                i += 1;
-            }
-        };
-
-        self.run(body).await
-    }
-
-    async fn with_transaction<F, Fut, T>(&self, f: &F) -> Result<(DatabaseTransaction, Result<T>)>
-    where
-        F: Send + Fn(TransactionHandle) -> Fut,
-        Fut: Send + Future<Output = Result<T>>,
-    {
-        let tx = self
-            .pool
-            .begin_with_config(Some(IsolationLevel::Serializable), None)
-            .await?;
-
-        let mut tx = Arc::new(Some(tx));
-        let result = f(TransactionHandle(tx.clone())).await;
-        let Some(tx) = Arc::get_mut(&mut tx).and_then(|tx| tx.take()) else {
-            return Err(anyhow!(
-                "couldn't complete transaction because it's still in use"
-            ))?;
-        };
-
-        Ok((tx, result))
-    }
-
-    async fn run<F, T>(&self, future: F) -> Result<T>
-    where
-        F: Future<Output = Result<T>>,
-    {
-        #[cfg(test)]
-        {
-            if let Executor::Deterministic(executor) = &self.executor {
-                executor.simulate_random_delay().await;
-            }
-
-            self.runtime.as_ref().unwrap().block_on(future)
-        }
-
-        #[cfg(not(test))]
-        {
-            future.await
-        }
-    }
-
-    async fn retry_on_serialization_error(&self, error: &Error, prev_attempt_count: u32) -> bool {
-        // If the error is due to a failure to serialize concurrent transactions, then retry
-        // this transaction after a delay. With each subsequent retry, double the delay duration.
-        // Also vary the delay randomly in order to ensure different database connections retry
-        // at different times.
-        if is_serialization_error(error) {
-            let base_delay = 4_u64 << prev_attempt_count.min(16);
-            let randomized_delay = base_delay as f32 * self.rng.lock().await.gen_range(0.5..=2.0);
-            log::info!(
-                "retrying transaction after serialization error. delay: {} ms.",
-                randomized_delay
-            );
-            self.executor
-                .sleep(Duration::from_millis(randomized_delay as u64))
-                .await;
-            true
-        } else {
-            false
-        }
-    }
-}
-
-fn is_serialization_error(error: &Error) -> bool {
-    const SERIALIZATION_FAILURE_CODE: &'static str = "40001";
-    match error {
-        Error::Database(
-            DbErr::Exec(sea_orm::RuntimeErr::SqlxError(error))
-            | DbErr::Query(sea_orm::RuntimeErr::SqlxError(error)),
-        ) if error
-            .as_database_error()
-            .and_then(|error| error.code())
-            .as_deref()
-            == Some(SERIALIZATION_FAILURE_CODE) =>
-        {
-            true
-        }
-        _ => false,
-    }
-}
-
-pub struct TransactionHandle(Arc<Option<DatabaseTransaction>>);
-
-impl Deref for TransactionHandle {
-    type Target = DatabaseTransaction;
-
-    fn deref(&self) -> &Self::Target {
-        self.0.as_ref().as_ref().unwrap()
-    }
-}
-
-pub struct RoomGuard<T> {
-    data: T,
-    _guard: OwnedMutexGuard<()>,
-    _not_send: PhantomData<Rc<()>>,
-}
-
-impl<T> Deref for RoomGuard<T> {
-    type Target = T;
-
-    fn deref(&self) -> &T {
-        &self.data
-    }
-}
-
-impl<T> DerefMut for RoomGuard<T> {
-    fn deref_mut(&mut self) -> &mut T {
-        &mut self.data
-    }
-}
-
-impl<T> RoomGuard<T> {
-    pub fn into_inner(self) -> T {
-        self.data
-    }
-}
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub enum Contact {
-    Accepted { user_id: UserId, busy: bool },
-    Outgoing { user_id: UserId },
-    Incoming { user_id: UserId },
-}
-
-impl Contact {
-    pub fn user_id(&self) -> UserId {
-        match self {
-            Contact::Accepted { user_id, .. } => *user_id,
-            Contact::Outgoing { user_id } => *user_id,
-            Contact::Incoming { user_id, .. } => *user_id,
-        }
-    }
-}
-
-pub type NotificationBatch = Vec<(UserId, proto::Notification)>;
-
-pub struct CreatedChannelMessage {
-    pub message_id: MessageId,
-    pub participant_connection_ids: Vec<ConnectionId>,
-    pub channel_members: Vec<UserId>,
-    pub notifications: NotificationBatch,
-}
-
-#[derive(Clone, Debug, PartialEq, Eq, FromQueryResult, Serialize, Deserialize)]
-pub struct Invite {
-    pub email_address: String,
-    pub email_confirmation_code: String,
-}
-
-#[derive(Clone, Debug, Deserialize)]
-pub struct NewSignup {
-    pub email_address: String,
-    pub platform_mac: bool,
-    pub platform_windows: bool,
-    pub platform_linux: bool,
-    pub editor_features: Vec<String>,
-    pub programming_languages: Vec<String>,
-    pub device_id: Option<String>,
-    pub added_to_mailing_list: bool,
-    pub created_at: Option<DateTime>,
-}
-
-#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, FromQueryResult)]
-pub struct WaitlistSummary {
-    pub count: i64,
-    pub linux_count: i64,
-    pub mac_count: i64,
-    pub windows_count: i64,
-    pub unknown_count: i64,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub struct NewUserParams {
-    pub github_login: String,
-    pub github_user_id: i32,
-}
-
-#[derive(Debug)]
-pub struct NewUserResult {
-    pub user_id: UserId,
-    pub metrics_id: String,
-    pub inviting_user_id: Option<UserId>,
-    pub signup_device_id: Option<String>,
-}
-
-#[derive(Debug)]
-pub struct MoveChannelResult {
-    pub participants_to_update: HashMap<UserId, ChannelsForUser>,
-    pub participants_to_remove: HashSet<UserId>,
-    pub moved_channels: HashSet<ChannelId>,
-}
-
-#[derive(Debug)]
-pub struct RenameChannelResult {
-    pub channel: Channel,
-    pub participants_to_update: HashMap<UserId, Channel>,
-}
-
-#[derive(Debug)]
-pub struct CreateChannelResult {
-    pub channel: Channel,
-    pub participants_to_update: Vec<(UserId, ChannelsForUser)>,
-}
-
-#[derive(Debug)]
-pub struct SetChannelVisibilityResult {
-    pub participants_to_update: HashMap<UserId, ChannelsForUser>,
-    pub participants_to_remove: HashSet<UserId>,
-    pub channels_to_remove: Vec<ChannelId>,
-}
-
-#[derive(Debug)]
-pub struct MembershipUpdated {
-    pub channel_id: ChannelId,
-    pub new_channels: ChannelsForUser,
-    pub removed_channels: Vec<ChannelId>,
-}
-
-#[derive(Debug)]
-pub enum SetMemberRoleResult {
-    InviteUpdated(Channel),
-    MembershipUpdated(MembershipUpdated),
-}
-
-#[derive(Debug)]
-pub struct InviteMemberResult {
-    pub channel: Channel,
-    pub notifications: NotificationBatch,
-}
-
-#[derive(Debug)]
-pub struct RespondToChannelInvite {
-    pub membership_update: Option<MembershipUpdated>,
-    pub notifications: NotificationBatch,
-}
-
-#[derive(Debug)]
-pub struct RemoveChannelMemberResult {
-    pub membership_update: MembershipUpdated,
-    pub notification_id: Option<NotificationId>,
-}
-
-#[derive(Debug, PartialEq, Eq, Hash)]
-pub struct Channel {
-    pub id: ChannelId,
-    pub name: String,
-    pub visibility: ChannelVisibility,
-    pub role: ChannelRole,
-    pub parent_path: Vec<ChannelId>,
-}
-
-impl Channel {
-    fn from_model(value: channel::Model, role: ChannelRole) -> Self {
-        Channel {
-            id: value.id,
-            visibility: value.visibility,
-            name: value.clone().name,
-            role,
-            parent_path: value.ancestors().collect(),
-        }
-    }
-
-    pub fn to_proto(&self) -> proto::Channel {
-        proto::Channel {
-            id: self.id.to_proto(),
-            name: self.name.clone(),
-            visibility: self.visibility.into(),
-            role: self.role.into(),
-            parent_path: self.parent_path.iter().map(|c| c.to_proto()).collect(),
-        }
-    }
-}
-
-#[derive(Debug, PartialEq, Eq, Hash)]
-pub struct ChannelMember {
-    pub role: ChannelRole,
-    pub user_id: UserId,
-    pub kind: proto::channel_member::Kind,
-}
-
-impl ChannelMember {
-    pub fn to_proto(&self) -> proto::ChannelMember {
-        proto::ChannelMember {
-            role: self.role.into(),
-            user_id: self.user_id.to_proto(),
-            kind: self.kind.into(),
-        }
-    }
-}
-
-#[derive(Debug, PartialEq)]
-pub struct ChannelsForUser {
-    pub channels: Vec<Channel>,
-    pub channel_participants: HashMap<ChannelId, Vec<UserId>>,
-    pub unseen_buffer_changes: Vec<proto::UnseenChannelBufferChange>,
-    pub channel_messages: Vec<proto::UnseenChannelMessage>,
-}
-
-#[derive(Debug)]
-pub struct RejoinedChannelBuffer {
-    pub buffer: proto::RejoinedChannelBuffer,
-    pub old_connection_id: ConnectionId,
-}
-
-#[derive(Clone)]
-pub struct JoinRoom {
-    pub room: proto::Room,
-    pub channel_id: Option<ChannelId>,
-    pub channel_members: Vec<UserId>,
-}
-
-pub struct RejoinedRoom {
-    pub room: proto::Room,
-    pub rejoined_projects: Vec<RejoinedProject>,
-    pub reshared_projects: Vec<ResharedProject>,
-    pub channel_id: Option<ChannelId>,
-    pub channel_members: Vec<UserId>,
-}
-
-pub struct ResharedProject {
-    pub id: ProjectId,
-    pub old_connection_id: ConnectionId,
-    pub collaborators: Vec<ProjectCollaborator>,
-    pub worktrees: Vec<proto::WorktreeMetadata>,
-}
-
-pub struct RejoinedProject {
-    pub id: ProjectId,
-    pub old_connection_id: ConnectionId,
-    pub collaborators: Vec<ProjectCollaborator>,
-    pub worktrees: Vec<RejoinedWorktree>,
-    pub language_servers: Vec<proto::LanguageServer>,
-}
-
-#[derive(Debug)]
-pub struct RejoinedWorktree {
-    pub id: u64,
-    pub abs_path: String,
-    pub root_name: String,
-    pub visible: bool,
-    pub updated_entries: Vec<proto::Entry>,
-    pub removed_entries: Vec<u64>,
-    pub updated_repositories: Vec<proto::RepositoryEntry>,
-    pub removed_repositories: Vec<u64>,
-    pub diagnostic_summaries: Vec<proto::DiagnosticSummary>,
-    pub settings_files: Vec<WorktreeSettingsFile>,
-    pub scan_id: u64,
-    pub completed_scan_id: u64,
-}
-
-pub struct LeftRoom {
-    pub room: proto::Room,
-    pub channel_id: Option<ChannelId>,
-    pub channel_members: Vec<UserId>,
-    pub left_projects: HashMap<ProjectId, LeftProject>,
-    pub canceled_calls_to_user_ids: Vec<UserId>,
-    pub deleted: bool,
-}
-
-pub struct RefreshedRoom {
-    pub room: proto::Room,
-    pub channel_id: Option<ChannelId>,
-    pub channel_members: Vec<UserId>,
-    pub stale_participant_user_ids: Vec<UserId>,
-    pub canceled_calls_to_user_ids: Vec<UserId>,
-}
-
-pub struct RefreshedChannelBuffer {
-    pub connection_ids: Vec<ConnectionId>,
-    pub collaborators: Vec<proto::Collaborator>,
-}
-
-pub struct Project {
-    pub collaborators: Vec<ProjectCollaborator>,
-    pub worktrees: BTreeMap<u64, Worktree>,
-    pub language_servers: Vec<proto::LanguageServer>,
-}
-
-pub struct ProjectCollaborator {
-    pub connection_id: ConnectionId,
-    pub user_id: UserId,
-    pub replica_id: ReplicaId,
-    pub is_host: bool,
-}
-
-impl ProjectCollaborator {
-    pub fn to_proto(&self) -> proto::Collaborator {
-        proto::Collaborator {
-            peer_id: Some(self.connection_id.into()),
-            replica_id: self.replica_id.0 as u32,
-            user_id: self.user_id.to_proto(),
-        }
-    }
-}
-
-#[derive(Debug)]
-pub struct LeftProject {
-    pub id: ProjectId,
-    pub host_user_id: UserId,
-    pub host_connection_id: ConnectionId,
-    pub connection_ids: Vec<ConnectionId>,
-}
-
-pub struct Worktree {
-    pub id: u64,
-    pub abs_path: String,
-    pub root_name: String,
-    pub visible: bool,
-    pub entries: Vec<proto::Entry>,
-    pub repository_entries: BTreeMap<u64, proto::RepositoryEntry>,
-    pub diagnostic_summaries: Vec<proto::DiagnosticSummary>,
-    pub settings_files: Vec<WorktreeSettingsFile>,
-    pub scan_id: u64,
-    pub completed_scan_id: u64,
-}
-
-#[derive(Debug)]
-pub struct WorktreeSettingsFile {
-    pub path: String,
-    pub content: String,
-}

crates/collab2/src/db/ids.rs 🔗

@@ -1,199 +0,0 @@
-use crate::Result;
-use rpc::proto;
-use sea_orm::{entity::prelude::*, DbErr};
-use serde::{Deserialize, Serialize};
-
-macro_rules! id_type {
-    ($name:ident) => {
-        #[derive(
-            Clone,
-            Copy,
-            Debug,
-            Default,
-            PartialEq,
-            Eq,
-            PartialOrd,
-            Ord,
-            Hash,
-            Serialize,
-            Deserialize,
-            DeriveValueType,
-        )]
-        #[serde(transparent)]
-        pub struct $name(pub i32);
-
-        impl $name {
-            #[allow(unused)]
-            pub const MAX: Self = Self(i32::MAX);
-
-            #[allow(unused)]
-            pub fn from_proto(value: u64) -> Self {
-                Self(value as i32)
-            }
-
-            #[allow(unused)]
-            pub fn to_proto(self) -> u64 {
-                self.0 as u64
-            }
-        }
-
-        impl std::fmt::Display for $name {
-            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
-                self.0.fmt(f)
-            }
-        }
-
-        impl sea_orm::TryFromU64 for $name {
-            fn try_from_u64(n: u64) -> Result<Self, DbErr> {
-                Ok(Self(n.try_into().map_err(|_| {
-                    DbErr::ConvertFromU64(concat!(
-                        "error converting ",
-                        stringify!($name),
-                        " to u64"
-                    ))
-                })?))
-            }
-        }
-
-        impl sea_orm::sea_query::Nullable for $name {
-            fn null() -> Value {
-                Value::Int(None)
-            }
-        }
-    };
-}
-
-id_type!(BufferId);
-id_type!(AccessTokenId);
-id_type!(ChannelChatParticipantId);
-id_type!(ChannelId);
-id_type!(ChannelMemberId);
-id_type!(MessageId);
-id_type!(ContactId);
-id_type!(FollowerId);
-id_type!(RoomId);
-id_type!(RoomParticipantId);
-id_type!(ProjectId);
-id_type!(ProjectCollaboratorId);
-id_type!(ReplicaId);
-id_type!(ServerId);
-id_type!(SignupId);
-id_type!(UserId);
-id_type!(ChannelBufferCollaboratorId);
-id_type!(FlagId);
-id_type!(NotificationId);
-id_type!(NotificationKindId);
-
-#[derive(Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum, Default, Hash)]
-#[sea_orm(rs_type = "String", db_type = "String(None)")]
-pub enum ChannelRole {
-    #[sea_orm(string_value = "admin")]
-    Admin,
-    #[sea_orm(string_value = "member")]
-    #[default]
-    Member,
-    #[sea_orm(string_value = "guest")]
-    Guest,
-    #[sea_orm(string_value = "banned")]
-    Banned,
-}
-
-impl ChannelRole {
-    pub fn should_override(&self, other: Self) -> bool {
-        use ChannelRole::*;
-        match self {
-            Admin => matches!(other, Member | Banned | Guest),
-            Member => matches!(other, Banned | Guest),
-            Banned => matches!(other, Guest),
-            Guest => false,
-        }
-    }
-
-    pub fn max(&self, other: Self) -> Self {
-        if self.should_override(other) {
-            *self
-        } else {
-            other
-        }
-    }
-
-    pub fn can_see_all_descendants(&self) -> bool {
-        use ChannelRole::*;
-        match self {
-            Admin | Member => true,
-            Guest | Banned => false,
-        }
-    }
-
-    pub fn can_only_see_public_descendants(&self) -> bool {
-        use ChannelRole::*;
-        match self {
-            Guest => true,
-            Admin | Member | Banned => false,
-        }
-    }
-}
-
-impl From<proto::ChannelRole> for ChannelRole {
-    fn from(value: proto::ChannelRole) -> Self {
-        match value {
-            proto::ChannelRole::Admin => ChannelRole::Admin,
-            proto::ChannelRole::Member => ChannelRole::Member,
-            proto::ChannelRole::Guest => ChannelRole::Guest,
-            proto::ChannelRole::Banned => ChannelRole::Banned,
-        }
-    }
-}
-
-impl Into<proto::ChannelRole> for ChannelRole {
-    fn into(self) -> proto::ChannelRole {
-        match self {
-            ChannelRole::Admin => proto::ChannelRole::Admin,
-            ChannelRole::Member => proto::ChannelRole::Member,
-            ChannelRole::Guest => proto::ChannelRole::Guest,
-            ChannelRole::Banned => proto::ChannelRole::Banned,
-        }
-    }
-}
-
-impl Into<i32> for ChannelRole {
-    fn into(self) -> i32 {
-        let proto: proto::ChannelRole = self.into();
-        proto.into()
-    }
-}
-
-#[derive(Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum, Default, Hash)]
-#[sea_orm(rs_type = "String", db_type = "String(None)")]
-pub enum ChannelVisibility {
-    #[sea_orm(string_value = "public")]
-    Public,
-    #[sea_orm(string_value = "members")]
-    #[default]
-    Members,
-}
-
-impl From<proto::ChannelVisibility> for ChannelVisibility {
-    fn from(value: proto::ChannelVisibility) -> Self {
-        match value {
-            proto::ChannelVisibility::Public => ChannelVisibility::Public,
-            proto::ChannelVisibility::Members => ChannelVisibility::Members,
-        }
-    }
-}
-
-impl Into<proto::ChannelVisibility> for ChannelVisibility {
-    fn into(self) -> proto::ChannelVisibility {
-        match self {
-            ChannelVisibility::Public => proto::ChannelVisibility::Public,
-            ChannelVisibility::Members => proto::ChannelVisibility::Members,
-        }
-    }
-}
-
-impl Into<i32> for ChannelVisibility {
-    fn into(self) -> i32 {
-        let proto: proto::ChannelVisibility = self.into();
-        proto.into()
-    }
-}

crates/collab2/src/db/queries.rs 🔗

@@ -1,12 +0,0 @@
-use super::*;
-
-pub mod access_tokens;
-pub mod buffers;
-pub mod channels;
-pub mod contacts;
-pub mod messages;
-pub mod notifications;
-pub mod projects;
-pub mod rooms;
-pub mod servers;
-pub mod users;

crates/collab2/src/db/queries/access_tokens.rs 🔗

@@ -1,54 +0,0 @@
-use super::*;
-use sea_orm::sea_query::Query;
-
-impl Database {
-    pub async fn create_access_token(
-        &self,
-        user_id: UserId,
-        access_token_hash: &str,
-        max_access_token_count: usize,
-    ) -> Result<AccessTokenId> {
-        self.transaction(|tx| async {
-            let tx = tx;
-
-            let token = access_token::ActiveModel {
-                user_id: ActiveValue::set(user_id),
-                hash: ActiveValue::set(access_token_hash.into()),
-                ..Default::default()
-            }
-            .insert(&*tx)
-            .await?;
-
-            access_token::Entity::delete_many()
-                .filter(
-                    access_token::Column::Id.in_subquery(
-                        Query::select()
-                            .column(access_token::Column::Id)
-                            .from(access_token::Entity)
-                            .and_where(access_token::Column::UserId.eq(user_id))
-                            .order_by(access_token::Column::Id, sea_orm::Order::Desc)
-                            .limit(10000)
-                            .offset(max_access_token_count as u64)
-                            .to_owned(),
-                    ),
-                )
-                .exec(&*tx)
-                .await?;
-            Ok(token.id)
-        })
-        .await
-    }
-
-    pub async fn get_access_token(
-        &self,
-        access_token_id: AccessTokenId,
-    ) -> Result<access_token::Model> {
-        self.transaction(|tx| async move {
-            Ok(access_token::Entity::find_by_id(access_token_id)
-                .one(&*tx)
-                .await?
-                .ok_or_else(|| anyhow!("no such access token"))?)
-        })
-        .await
-    }
-}

crates/collab2/src/db/queries/buffers.rs 🔗

@@ -1,1078 +0,0 @@
-use super::*;
-use prost::Message;
-use text::{EditOperation, UndoOperation};
-
-pub struct LeftChannelBuffer {
-    pub channel_id: ChannelId,
-    pub collaborators: Vec<proto::Collaborator>,
-    pub connections: Vec<ConnectionId>,
-}
-
-impl Database {
-    pub async fn join_channel_buffer(
-        &self,
-        channel_id: ChannelId,
-        user_id: UserId,
-        connection: ConnectionId,
-    ) -> Result<proto::JoinChannelBufferResponse> {
-        self.transaction(|tx| async move {
-            let channel = self.get_channel_internal(channel_id, &*tx).await?;
-            self.check_user_is_channel_participant(&channel, user_id, &tx)
-                .await?;
-
-            let buffer = channel::Model {
-                id: channel_id,
-                ..Default::default()
-            }
-            .find_related(buffer::Entity)
-            .one(&*tx)
-            .await?;
-
-            let buffer = if let Some(buffer) = buffer {
-                buffer
-            } else {
-                let buffer = buffer::ActiveModel {
-                    channel_id: ActiveValue::Set(channel_id),
-                    ..Default::default()
-                }
-                .insert(&*tx)
-                .await?;
-                buffer_snapshot::ActiveModel {
-                    buffer_id: ActiveValue::Set(buffer.id),
-                    epoch: ActiveValue::Set(0),
-                    text: ActiveValue::Set(String::new()),
-                    operation_serialization_version: ActiveValue::Set(
-                        storage::SERIALIZATION_VERSION,
-                    ),
-                }
-                .insert(&*tx)
-                .await?;
-                buffer
-            };
-
-            // Join the collaborators
-            let mut collaborators = channel_buffer_collaborator::Entity::find()
-                .filter(channel_buffer_collaborator::Column::ChannelId.eq(channel_id))
-                .all(&*tx)
-                .await?;
-            let replica_ids = collaborators
-                .iter()
-                .map(|c| c.replica_id)
-                .collect::<HashSet<_>>();
-            let mut replica_id = ReplicaId(0);
-            while replica_ids.contains(&replica_id) {
-                replica_id.0 += 1;
-            }
-            let collaborator = channel_buffer_collaborator::ActiveModel {
-                channel_id: ActiveValue::Set(channel_id),
-                connection_id: ActiveValue::Set(connection.id as i32),
-                connection_server_id: ActiveValue::Set(ServerId(connection.owner_id as i32)),
-                user_id: ActiveValue::Set(user_id),
-                replica_id: ActiveValue::Set(replica_id),
-                ..Default::default()
-            }
-            .insert(&*tx)
-            .await?;
-            collaborators.push(collaborator);
-
-            let (base_text, operations, max_operation) =
-                self.get_buffer_state(&buffer, &tx).await?;
-
-            // Save the last observed operation
-            if let Some(op) = max_operation {
-                observed_buffer_edits::Entity::insert(observed_buffer_edits::ActiveModel {
-                    user_id: ActiveValue::Set(user_id),
-                    buffer_id: ActiveValue::Set(buffer.id),
-                    epoch: ActiveValue::Set(op.epoch),
-                    lamport_timestamp: ActiveValue::Set(op.lamport_timestamp),
-                    replica_id: ActiveValue::Set(op.replica_id),
-                })
-                .on_conflict(
-                    OnConflict::columns([
-                        observed_buffer_edits::Column::UserId,
-                        observed_buffer_edits::Column::BufferId,
-                    ])
-                    .update_columns([
-                        observed_buffer_edits::Column::Epoch,
-                        observed_buffer_edits::Column::LamportTimestamp,
-                    ])
-                    .to_owned(),
-                )
-                .exec(&*tx)
-                .await?;
-            }
-
-            Ok(proto::JoinChannelBufferResponse {
-                buffer_id: buffer.id.to_proto(),
-                replica_id: replica_id.to_proto() as u32,
-                base_text,
-                operations,
-                epoch: buffer.epoch as u64,
-                collaborators: collaborators
-                    .into_iter()
-                    .map(|collaborator| proto::Collaborator {
-                        peer_id: Some(collaborator.connection().into()),
-                        user_id: collaborator.user_id.to_proto(),
-                        replica_id: collaborator.replica_id.0 as u32,
-                    })
-                    .collect(),
-            })
-        })
-        .await
-    }
-
-    pub async fn rejoin_channel_buffers(
-        &self,
-        buffers: &[proto::ChannelBufferVersion],
-        user_id: UserId,
-        connection_id: ConnectionId,
-    ) -> Result<Vec<RejoinedChannelBuffer>> {
-        self.transaction(|tx| async move {
-            let mut results = Vec::new();
-            for client_buffer in buffers {
-                let channel = self
-                    .get_channel_internal(ChannelId::from_proto(client_buffer.channel_id), &*tx)
-                    .await?;
-                if self
-                    .check_user_is_channel_participant(&channel, user_id, &*tx)
-                    .await
-                    .is_err()
-                {
-                    log::info!("user is not a member of channel");
-                    continue;
-                }
-
-                let buffer = self.get_channel_buffer(channel.id, &*tx).await?;
-                let mut collaborators = channel_buffer_collaborator::Entity::find()
-                    .filter(channel_buffer_collaborator::Column::ChannelId.eq(channel.id))
-                    .all(&*tx)
-                    .await?;
-
-                // If the buffer epoch hasn't changed since the client lost
-                // connection, then the client's buffer can be syncronized with
-                // the server's buffer.
-                if buffer.epoch as u64 != client_buffer.epoch {
-                    log::info!("can't rejoin buffer, epoch has changed");
-                    continue;
-                }
-
-                // Find the collaborator record for this user's previous lost
-                // connection. Update it with the new connection id.
-                let server_id = ServerId(connection_id.owner_id as i32);
-                let Some(self_collaborator) = collaborators.iter_mut().find(|c| {
-                    c.user_id == user_id
-                        && (c.connection_lost || c.connection_server_id != server_id)
-                }) else {
-                    log::info!("can't rejoin buffer, no previous collaborator found");
-                    continue;
-                };
-                let old_connection_id = self_collaborator.connection();
-                *self_collaborator = channel_buffer_collaborator::ActiveModel {
-                    id: ActiveValue::Unchanged(self_collaborator.id),
-                    connection_id: ActiveValue::Set(connection_id.id as i32),
-                    connection_server_id: ActiveValue::Set(ServerId(connection_id.owner_id as i32)),
-                    connection_lost: ActiveValue::Set(false),
-                    ..Default::default()
-                }
-                .update(&*tx)
-                .await?;
-
-                let client_version = version_from_wire(&client_buffer.version);
-                let serialization_version = self
-                    .get_buffer_operation_serialization_version(buffer.id, buffer.epoch, &*tx)
-                    .await?;
-
-                let mut rows = buffer_operation::Entity::find()
-                    .filter(
-                        buffer_operation::Column::BufferId
-                            .eq(buffer.id)
-                            .and(buffer_operation::Column::Epoch.eq(buffer.epoch)),
-                    )
-                    .stream(&*tx)
-                    .await?;
-
-                // Find the server's version vector and any operations
-                // that the client has not seen.
-                let mut server_version = clock::Global::new();
-                let mut operations = Vec::new();
-                while let Some(row) = rows.next().await {
-                    let row = row?;
-                    let timestamp = clock::Lamport {
-                        replica_id: row.replica_id as u16,
-                        value: row.lamport_timestamp as u32,
-                    };
-                    server_version.observe(timestamp);
-                    if !client_version.observed(timestamp) {
-                        operations.push(proto::Operation {
-                            variant: Some(operation_from_storage(row, serialization_version)?),
-                        })
-                    }
-                }
-
-                results.push(RejoinedChannelBuffer {
-                    old_connection_id,
-                    buffer: proto::RejoinedChannelBuffer {
-                        channel_id: client_buffer.channel_id,
-                        version: version_to_wire(&server_version),
-                        operations,
-                        collaborators: collaborators
-                            .into_iter()
-                            .map(|collaborator| proto::Collaborator {
-                                peer_id: Some(collaborator.connection().into()),
-                                user_id: collaborator.user_id.to_proto(),
-                                replica_id: collaborator.replica_id.0 as u32,
-                            })
-                            .collect(),
-                    },
-                });
-            }
-
-            Ok(results)
-        })
-        .await
-    }
-
-    pub async fn clear_stale_channel_buffer_collaborators(
-        &self,
-        channel_id: ChannelId,
-        server_id: ServerId,
-    ) -> Result<RefreshedChannelBuffer> {
-        self.transaction(|tx| async move {
-            let db_collaborators = channel_buffer_collaborator::Entity::find()
-                .filter(channel_buffer_collaborator::Column::ChannelId.eq(channel_id))
-                .all(&*tx)
-                .await?;
-
-            let mut connection_ids = Vec::new();
-            let mut collaborators = Vec::new();
-            let mut collaborator_ids_to_remove = Vec::new();
-            for db_collaborator in &db_collaborators {
-                if !db_collaborator.connection_lost
-                    && db_collaborator.connection_server_id == server_id
-                {
-                    connection_ids.push(db_collaborator.connection());
-                    collaborators.push(proto::Collaborator {
-                        peer_id: Some(db_collaborator.connection().into()),
-                        replica_id: db_collaborator.replica_id.0 as u32,
-                        user_id: db_collaborator.user_id.to_proto(),
-                    })
-                } else {
-                    collaborator_ids_to_remove.push(db_collaborator.id);
-                }
-            }
-
-            channel_buffer_collaborator::Entity::delete_many()
-                .filter(channel_buffer_collaborator::Column::Id.is_in(collaborator_ids_to_remove))
-                .exec(&*tx)
-                .await?;
-
-            Ok(RefreshedChannelBuffer {
-                connection_ids,
-                collaborators,
-            })
-        })
-        .await
-    }
-
-    pub async fn leave_channel_buffer(
-        &self,
-        channel_id: ChannelId,
-        connection: ConnectionId,
-    ) -> Result<LeftChannelBuffer> {
-        self.transaction(|tx| async move {
-            self.leave_channel_buffer_internal(channel_id, connection, &*tx)
-                .await
-        })
-        .await
-    }
-
-    pub async fn channel_buffer_connection_lost(
-        &self,
-        connection: ConnectionId,
-        tx: &DatabaseTransaction,
-    ) -> Result<()> {
-        channel_buffer_collaborator::Entity::update_many()
-            .filter(
-                Condition::all()
-                    .add(channel_buffer_collaborator::Column::ConnectionId.eq(connection.id as i32))
-                    .add(
-                        channel_buffer_collaborator::Column::ConnectionServerId
-                            .eq(connection.owner_id as i32),
-                    ),
-            )
-            .set(channel_buffer_collaborator::ActiveModel {
-                connection_lost: ActiveValue::set(true),
-                ..Default::default()
-            })
-            .exec(&*tx)
-            .await?;
-        Ok(())
-    }
-
-    pub async fn leave_channel_buffers(
-        &self,
-        connection: ConnectionId,
-    ) -> Result<Vec<LeftChannelBuffer>> {
-        self.transaction(|tx| async move {
-            #[derive(Debug, Clone, Copy, EnumIter, DeriveColumn)]
-            enum QueryChannelIds {
-                ChannelId,
-            }
-
-            let channel_ids: Vec<ChannelId> = channel_buffer_collaborator::Entity::find()
-                .select_only()
-                .column(channel_buffer_collaborator::Column::ChannelId)
-                .filter(Condition::all().add(
-                    channel_buffer_collaborator::Column::ConnectionId.eq(connection.id as i32),
-                ))
-                .into_values::<_, QueryChannelIds>()
-                .all(&*tx)
-                .await?;
-
-            let mut result = Vec::new();
-            for channel_id in channel_ids {
-                let left_channel_buffer = self
-                    .leave_channel_buffer_internal(channel_id, connection, &*tx)
-                    .await?;
-                result.push(left_channel_buffer);
-            }
-
-            Ok(result)
-        })
-        .await
-    }
-
-    pub async fn leave_channel_buffer_internal(
-        &self,
-        channel_id: ChannelId,
-        connection: ConnectionId,
-        tx: &DatabaseTransaction,
-    ) -> Result<LeftChannelBuffer> {
-        let result = channel_buffer_collaborator::Entity::delete_many()
-            .filter(
-                Condition::all()
-                    .add(channel_buffer_collaborator::Column::ChannelId.eq(channel_id))
-                    .add(channel_buffer_collaborator::Column::ConnectionId.eq(connection.id as i32))
-                    .add(
-                        channel_buffer_collaborator::Column::ConnectionServerId
-                            .eq(connection.owner_id as i32),
-                    ),
-            )
-            .exec(&*tx)
-            .await?;
-        if result.rows_affected == 0 {
-            Err(anyhow!("not a collaborator on this project"))?;
-        }
-
-        let mut collaborators = Vec::new();
-        let mut connections = Vec::new();
-        let mut rows = channel_buffer_collaborator::Entity::find()
-            .filter(
-                Condition::all().add(channel_buffer_collaborator::Column::ChannelId.eq(channel_id)),
-            )
-            .stream(&*tx)
-            .await?;
-        while let Some(row) = rows.next().await {
-            let row = row?;
-            let connection = row.connection();
-            connections.push(connection);
-            collaborators.push(proto::Collaborator {
-                peer_id: Some(connection.into()),
-                replica_id: row.replica_id.0 as u32,
-                user_id: row.user_id.to_proto(),
-            });
-        }
-
-        drop(rows);
-
-        if collaborators.is_empty() {
-            self.snapshot_channel_buffer(channel_id, &tx).await?;
-        }
-
-        Ok(LeftChannelBuffer {
-            channel_id,
-            collaborators,
-            connections,
-        })
-    }
-
-    pub async fn get_channel_buffer_collaborators(
-        &self,
-        channel_id: ChannelId,
-    ) -> Result<Vec<UserId>> {
-        self.transaction(|tx| async move {
-            self.get_channel_buffer_collaborators_internal(channel_id, &*tx)
-                .await
-        })
-        .await
-    }
-
-    async fn get_channel_buffer_collaborators_internal(
-        &self,
-        channel_id: ChannelId,
-        tx: &DatabaseTransaction,
-    ) -> Result<Vec<UserId>> {
-        #[derive(Debug, Clone, Copy, EnumIter, DeriveColumn)]
-        enum QueryUserIds {
-            UserId,
-        }
-
-        let users: Vec<UserId> = channel_buffer_collaborator::Entity::find()
-            .select_only()
-            .column(channel_buffer_collaborator::Column::UserId)
-            .filter(
-                Condition::all().add(channel_buffer_collaborator::Column::ChannelId.eq(channel_id)),
-            )
-            .into_values::<_, QueryUserIds>()
-            .all(&*tx)
-            .await?;
-
-        Ok(users)
-    }
-
-    pub async fn update_channel_buffer(
-        &self,
-        channel_id: ChannelId,
-        user: UserId,
-        operations: &[proto::Operation],
-    ) -> Result<(
-        Vec<ConnectionId>,
-        Vec<UserId>,
-        i32,
-        Vec<proto::VectorClockEntry>,
-    )> {
-        self.transaction(move |tx| async move {
-            let channel = self.get_channel_internal(channel_id, &*tx).await?;
-            self.check_user_is_channel_member(&channel, user, &*tx)
-                .await?;
-
-            let buffer = buffer::Entity::find()
-                .filter(buffer::Column::ChannelId.eq(channel_id))
-                .one(&*tx)
-                .await?
-                .ok_or_else(|| anyhow!("no such buffer"))?;
-
-            let serialization_version = self
-                .get_buffer_operation_serialization_version(buffer.id, buffer.epoch, &*tx)
-                .await?;
-
-            let operations = operations
-                .iter()
-                .filter_map(|op| operation_to_storage(op, &buffer, serialization_version))
-                .collect::<Vec<_>>();
-
-            let mut channel_members;
-            let max_version;
-
-            if !operations.is_empty() {
-                let max_operation = operations
-                    .iter()
-                    .max_by_key(|op| (op.lamport_timestamp.as_ref(), op.replica_id.as_ref()))
-                    .unwrap();
-
-                max_version = vec![proto::VectorClockEntry {
-                    replica_id: *max_operation.replica_id.as_ref() as u32,
-                    timestamp: *max_operation.lamport_timestamp.as_ref() as u32,
-                }];
-
-                // get current channel participants and save the max operation above
-                self.save_max_operation(
-                    user,
-                    buffer.id,
-                    buffer.epoch,
-                    *max_operation.replica_id.as_ref(),
-                    *max_operation.lamport_timestamp.as_ref(),
-                    &*tx,
-                )
-                .await?;
-
-                channel_members = self.get_channel_participants(&channel, &*tx).await?;
-                let collaborators = self
-                    .get_channel_buffer_collaborators_internal(channel_id, &*tx)
-                    .await?;
-                channel_members.retain(|member| !collaborators.contains(member));
-
-                buffer_operation::Entity::insert_many(operations)
-                    .on_conflict(
-                        OnConflict::columns([
-                            buffer_operation::Column::BufferId,
-                            buffer_operation::Column::Epoch,
-                            buffer_operation::Column::LamportTimestamp,
-                            buffer_operation::Column::ReplicaId,
-                        ])
-                        .do_nothing()
-                        .to_owned(),
-                    )
-                    .exec(&*tx)
-                    .await?;
-            } else {
-                channel_members = Vec::new();
-                max_version = Vec::new();
-            }
-
-            let mut connections = Vec::new();
-            let mut rows = channel_buffer_collaborator::Entity::find()
-                .filter(
-                    Condition::all()
-                        .add(channel_buffer_collaborator::Column::ChannelId.eq(channel_id)),
-                )
-                .stream(&*tx)
-                .await?;
-            while let Some(row) = rows.next().await {
-                let row = row?;
-                connections.push(ConnectionId {
-                    id: row.connection_id as u32,
-                    owner_id: row.connection_server_id.0 as u32,
-                });
-            }
-
-            Ok((connections, channel_members, buffer.epoch, max_version))
-        })
-        .await
-    }
-
-    async fn save_max_operation(
-        &self,
-        user_id: UserId,
-        buffer_id: BufferId,
-        epoch: i32,
-        replica_id: i32,
-        lamport_timestamp: i32,
-        tx: &DatabaseTransaction,
-    ) -> Result<()> {
-        use observed_buffer_edits::Column;
-
-        observed_buffer_edits::Entity::insert(observed_buffer_edits::ActiveModel {
-            user_id: ActiveValue::Set(user_id),
-            buffer_id: ActiveValue::Set(buffer_id),
-            epoch: ActiveValue::Set(epoch),
-            replica_id: ActiveValue::Set(replica_id),
-            lamport_timestamp: ActiveValue::Set(lamport_timestamp),
-        })
-        .on_conflict(
-            OnConflict::columns([Column::UserId, Column::BufferId])
-                .update_columns([Column::Epoch, Column::LamportTimestamp, Column::ReplicaId])
-                .action_cond_where(
-                    Condition::any().add(Column::Epoch.lt(epoch)).add(
-                        Condition::all().add(Column::Epoch.eq(epoch)).add(
-                            Condition::any()
-                                .add(Column::LamportTimestamp.lt(lamport_timestamp))
-                                .add(
-                                    Column::LamportTimestamp
-                                        .eq(lamport_timestamp)
-                                        .and(Column::ReplicaId.lt(replica_id)),
-                                ),
-                        ),
-                    ),
-                )
-                .to_owned(),
-        )
-        .exec_without_returning(tx)
-        .await?;
-
-        Ok(())
-    }
-
-    async fn get_buffer_operation_serialization_version(
-        &self,
-        buffer_id: BufferId,
-        epoch: i32,
-        tx: &DatabaseTransaction,
-    ) -> Result<i32> {
-        Ok(buffer_snapshot::Entity::find()
-            .filter(buffer_snapshot::Column::BufferId.eq(buffer_id))
-            .filter(buffer_snapshot::Column::Epoch.eq(epoch))
-            .select_only()
-            .column(buffer_snapshot::Column::OperationSerializationVersion)
-            .into_values::<_, QueryOperationSerializationVersion>()
-            .one(&*tx)
-            .await?
-            .ok_or_else(|| anyhow!("missing buffer snapshot"))?)
-    }
-
-    pub async fn get_channel_buffer(
-        &self,
-        channel_id: ChannelId,
-        tx: &DatabaseTransaction,
-    ) -> Result<buffer::Model> {
-        Ok(channel::Model {
-            id: channel_id,
-            ..Default::default()
-        }
-        .find_related(buffer::Entity)
-        .one(&*tx)
-        .await?
-        .ok_or_else(|| anyhow!("no such buffer"))?)
-    }
-
-    async fn get_buffer_state(
-        &self,
-        buffer: &buffer::Model,
-        tx: &DatabaseTransaction,
-    ) -> Result<(
-        String,
-        Vec<proto::Operation>,
-        Option<buffer_operation::Model>,
-    )> {
-        let id = buffer.id;
-        let (base_text, version) = if buffer.epoch > 0 {
-            let snapshot = buffer_snapshot::Entity::find()
-                .filter(
-                    buffer_snapshot::Column::BufferId
-                        .eq(id)
-                        .and(buffer_snapshot::Column::Epoch.eq(buffer.epoch)),
-                )
-                .one(&*tx)
-                .await?
-                .ok_or_else(|| anyhow!("no such snapshot"))?;
-
-            let version = snapshot.operation_serialization_version;
-            (snapshot.text, version)
-        } else {
-            (String::new(), storage::SERIALIZATION_VERSION)
-        };
-
-        let mut rows = buffer_operation::Entity::find()
-            .filter(
-                buffer_operation::Column::BufferId
-                    .eq(id)
-                    .and(buffer_operation::Column::Epoch.eq(buffer.epoch)),
-            )
-            .order_by_asc(buffer_operation::Column::LamportTimestamp)
-            .order_by_asc(buffer_operation::Column::ReplicaId)
-            .stream(&*tx)
-            .await?;
-
-        let mut operations = Vec::new();
-        let mut last_row = None;
-        while let Some(row) = rows.next().await {
-            let row = row?;
-            last_row = Some(buffer_operation::Model {
-                buffer_id: row.buffer_id,
-                epoch: row.epoch,
-                lamport_timestamp: row.lamport_timestamp,
-                replica_id: row.lamport_timestamp,
-                value: Default::default(),
-            });
-            operations.push(proto::Operation {
-                variant: Some(operation_from_storage(row, version)?),
-            });
-        }
-
-        Ok((base_text, operations, last_row))
-    }
-
-    async fn snapshot_channel_buffer(
-        &self,
-        channel_id: ChannelId,
-        tx: &DatabaseTransaction,
-    ) -> Result<()> {
-        let buffer = self.get_channel_buffer(channel_id, tx).await?;
-        let (base_text, operations, _) = self.get_buffer_state(&buffer, tx).await?;
-        if operations.is_empty() {
-            return Ok(());
-        }
-
-        let mut text_buffer = text::Buffer::new(0, 0, base_text);
-        text_buffer
-            .apply_ops(operations.into_iter().filter_map(operation_from_wire))
-            .unwrap();
-
-        let base_text = text_buffer.text();
-        let epoch = buffer.epoch + 1;
-
-        buffer_snapshot::Model {
-            buffer_id: buffer.id,
-            epoch,
-            text: base_text,
-            operation_serialization_version: storage::SERIALIZATION_VERSION,
-        }
-        .into_active_model()
-        .insert(tx)
-        .await?;
-
-        buffer::ActiveModel {
-            id: ActiveValue::Unchanged(buffer.id),
-            epoch: ActiveValue::Set(epoch),
-            ..Default::default()
-        }
-        .save(tx)
-        .await?;
-
-        Ok(())
-    }
-
-    pub async fn observe_buffer_version(
-        &self,
-        buffer_id: BufferId,
-        user_id: UserId,
-        epoch: i32,
-        version: &[proto::VectorClockEntry],
-    ) -> Result<()> {
-        self.transaction(|tx| async move {
-            // For now, combine concurrent operations.
-            let Some(component) = version.iter().max_by_key(|version| version.timestamp) else {
-                return Ok(());
-            };
-            self.save_max_operation(
-                user_id,
-                buffer_id,
-                epoch,
-                component.replica_id as i32,
-                component.timestamp as i32,
-                &*tx,
-            )
-            .await?;
-            Ok(())
-        })
-        .await
-    }
-
-    pub async fn unseen_channel_buffer_changes(
-        &self,
-        user_id: UserId,
-        channel_ids: &[ChannelId],
-        tx: &DatabaseTransaction,
-    ) -> Result<Vec<proto::UnseenChannelBufferChange>> {
-        #[derive(Debug, Clone, Copy, EnumIter, DeriveColumn)]
-        enum QueryIds {
-            ChannelId,
-            Id,
-        }
-
-        let mut channel_ids_by_buffer_id = HashMap::default();
-        let mut rows = buffer::Entity::find()
-            .filter(buffer::Column::ChannelId.is_in(channel_ids.iter().copied()))
-            .stream(&*tx)
-            .await?;
-        while let Some(row) = rows.next().await {
-            let row = row?;
-            channel_ids_by_buffer_id.insert(row.id, row.channel_id);
-        }
-        drop(rows);
-
-        let mut observed_edits_by_buffer_id = HashMap::default();
-        let mut rows = observed_buffer_edits::Entity::find()
-            .filter(observed_buffer_edits::Column::UserId.eq(user_id))
-            .filter(
-                observed_buffer_edits::Column::BufferId
-                    .is_in(channel_ids_by_buffer_id.keys().copied()),
-            )
-            .stream(&*tx)
-            .await?;
-        while let Some(row) = rows.next().await {
-            let row = row?;
-            observed_edits_by_buffer_id.insert(row.buffer_id, row);
-        }
-        drop(rows);
-
-        let latest_operations = self
-            .get_latest_operations_for_buffers(channel_ids_by_buffer_id.keys().copied(), &*tx)
-            .await?;
-
-        let mut changes = Vec::default();
-        for latest in latest_operations {
-            if let Some(observed) = observed_edits_by_buffer_id.get(&latest.buffer_id) {
-                if (
-                    observed.epoch,
-                    observed.lamport_timestamp,
-                    observed.replica_id,
-                ) >= (latest.epoch, latest.lamport_timestamp, latest.replica_id)
-                {
-                    continue;
-                }
-            }
-
-            if let Some(channel_id) = channel_ids_by_buffer_id.get(&latest.buffer_id) {
-                changes.push(proto::UnseenChannelBufferChange {
-                    channel_id: channel_id.to_proto(),
-                    epoch: latest.epoch as u64,
-                    version: vec![proto::VectorClockEntry {
-                        replica_id: latest.replica_id as u32,
-                        timestamp: latest.lamport_timestamp as u32,
-                    }],
-                });
-            }
-        }
-
-        Ok(changes)
-    }
-
-    pub async fn get_latest_operations_for_buffers(
-        &self,
-        buffer_ids: impl IntoIterator<Item = BufferId>,
-        tx: &DatabaseTransaction,
-    ) -> Result<Vec<buffer_operation::Model>> {
-        let mut values = String::new();
-        for id in buffer_ids {
-            if !values.is_empty() {
-                values.push_str(", ");
-            }
-            write!(&mut values, "({})", id).unwrap();
-        }
-
-        if values.is_empty() {
-            return Ok(Vec::default());
-        }
-
-        let sql = format!(
-            r#"
-            SELECT
-                *
-            FROM
-            (
-                SELECT
-                    *,
-                    row_number() OVER (
-                        PARTITION BY buffer_id
-                        ORDER BY
-                            epoch DESC,
-                            lamport_timestamp DESC,
-                            replica_id DESC
-                    ) as row_number
-                FROM buffer_operations
-                WHERE
-                    buffer_id in ({values})
-            ) AS last_operations
-            WHERE
-                row_number = 1
-            "#,
-        );
-
-        let stmt = Statement::from_string(self.pool.get_database_backend(), sql);
-        Ok(buffer_operation::Entity::find()
-            .from_raw_sql(stmt)
-            .all(&*tx)
-            .await?)
-    }
-}
-
-fn operation_to_storage(
-    operation: &proto::Operation,
-    buffer: &buffer::Model,
-    _format: i32,
-) -> Option<buffer_operation::ActiveModel> {
-    let (replica_id, lamport_timestamp, value) = match operation.variant.as_ref()? {
-        proto::operation::Variant::Edit(operation) => (
-            operation.replica_id,
-            operation.lamport_timestamp,
-            storage::Operation {
-                version: version_to_storage(&operation.version),
-                is_undo: false,
-                edit_ranges: operation
-                    .ranges
-                    .iter()
-                    .map(|range| storage::Range {
-                        start: range.start,
-                        end: range.end,
-                    })
-                    .collect(),
-                edit_texts: operation.new_text.clone(),
-                undo_counts: Vec::new(),
-            },
-        ),
-        proto::operation::Variant::Undo(operation) => (
-            operation.replica_id,
-            operation.lamport_timestamp,
-            storage::Operation {
-                version: version_to_storage(&operation.version),
-                is_undo: true,
-                edit_ranges: Vec::new(),
-                edit_texts: Vec::new(),
-                undo_counts: operation
-                    .counts
-                    .iter()
-                    .map(|entry| storage::UndoCount {
-                        replica_id: entry.replica_id,
-                        lamport_timestamp: entry.lamport_timestamp,
-                        count: entry.count,
-                    })
-                    .collect(),
-            },
-        ),
-        _ => None?,
-    };
-
-    Some(buffer_operation::ActiveModel {
-        buffer_id: ActiveValue::Set(buffer.id),
-        epoch: ActiveValue::Set(buffer.epoch),
-        replica_id: ActiveValue::Set(replica_id as i32),
-        lamport_timestamp: ActiveValue::Set(lamport_timestamp as i32),
-        value: ActiveValue::Set(value.encode_to_vec()),
-    })
-}
-
-fn operation_from_storage(
-    row: buffer_operation::Model,
-    _format_version: i32,
-) -> Result<proto::operation::Variant, Error> {
-    let operation =
-        storage::Operation::decode(row.value.as_slice()).map_err(|error| anyhow!("{}", error))?;
-    let version = version_from_storage(&operation.version);
-    Ok(if operation.is_undo {
-        proto::operation::Variant::Undo(proto::operation::Undo {
-            replica_id: row.replica_id as u32,
-            lamport_timestamp: row.lamport_timestamp as u32,
-            version,
-            counts: operation
-                .undo_counts
-                .iter()
-                .map(|entry| proto::UndoCount {
-                    replica_id: entry.replica_id,
-                    lamport_timestamp: entry.lamport_timestamp,
-                    count: entry.count,
-                })
-                .collect(),
-        })
-    } else {
-        proto::operation::Variant::Edit(proto::operation::Edit {
-            replica_id: row.replica_id as u32,
-            lamport_timestamp: row.lamport_timestamp as u32,
-            version,
-            ranges: operation
-                .edit_ranges
-                .into_iter()
-                .map(|range| proto::Range {
-                    start: range.start,
-                    end: range.end,
-                })
-                .collect(),
-            new_text: operation.edit_texts,
-        })
-    })
-}
-
-fn version_to_storage(version: &Vec<proto::VectorClockEntry>) -> Vec<storage::VectorClockEntry> {
-    version
-        .iter()
-        .map(|entry| storage::VectorClockEntry {
-            replica_id: entry.replica_id,
-            timestamp: entry.timestamp,
-        })
-        .collect()
-}
-
-fn version_from_storage(version: &Vec<storage::VectorClockEntry>) -> Vec<proto::VectorClockEntry> {
-    version
-        .iter()
-        .map(|entry| proto::VectorClockEntry {
-            replica_id: entry.replica_id,
-            timestamp: entry.timestamp,
-        })
-        .collect()
-}
-
-// This is currently a manual copy of the deserialization code in the client's langauge crate
-pub fn operation_from_wire(operation: proto::Operation) -> Option<text::Operation> {
-    match operation.variant? {
-        proto::operation::Variant::Edit(edit) => Some(text::Operation::Edit(EditOperation {
-            timestamp: clock::Lamport {
-                replica_id: edit.replica_id as text::ReplicaId,
-                value: edit.lamport_timestamp,
-            },
-            version: version_from_wire(&edit.version),
-            ranges: edit
-                .ranges
-                .into_iter()
-                .map(|range| {
-                    text::FullOffset(range.start as usize)..text::FullOffset(range.end as usize)
-                })
-                .collect(),
-            new_text: edit.new_text.into_iter().map(Arc::from).collect(),
-        })),
-        proto::operation::Variant::Undo(undo) => Some(text::Operation::Undo(UndoOperation {
-            timestamp: clock::Lamport {
-                replica_id: undo.replica_id as text::ReplicaId,
-                value: undo.lamport_timestamp,
-            },
-            version: version_from_wire(&undo.version),
-            counts: undo
-                .counts
-                .into_iter()
-                .map(|c| {
-                    (
-                        clock::Lamport {
-                            replica_id: c.replica_id as text::ReplicaId,
-                            value: c.lamport_timestamp,
-                        },
-                        c.count,
-                    )
-                })
-                .collect(),
-        })),
-        _ => None,
-    }
-}
-
-fn version_from_wire(message: &[proto::VectorClockEntry]) -> clock::Global {
-    let mut version = clock::Global::new();
-    for entry in message {
-        version.observe(clock::Lamport {
-            replica_id: entry.replica_id as text::ReplicaId,
-            value: entry.timestamp,
-        });
-    }
-    version
-}
-
-fn version_to_wire(version: &clock::Global) -> Vec<proto::VectorClockEntry> {
-    let mut message = Vec::new();
-    for entry in version.iter() {
-        message.push(proto::VectorClockEntry {
-            replica_id: entry.replica_id as u32,
-            timestamp: entry.value,
-        });
-    }
-    message
-}
-
-#[derive(Debug, Clone, Copy, EnumIter, DeriveColumn)]
-enum QueryOperationSerializationVersion {
-    OperationSerializationVersion,
-}
-
-mod storage {
-    #![allow(non_snake_case)]
-    use prost::Message;
-    pub const SERIALIZATION_VERSION: i32 = 1;
-
-    #[derive(Message)]
-    pub struct Operation {
-        #[prost(message, repeated, tag = "2")]
-        pub version: Vec<VectorClockEntry>,
-        #[prost(bool, tag = "3")]
-        pub is_undo: bool,
-        #[prost(message, repeated, tag = "4")]
-        pub edit_ranges: Vec<Range>,
-        #[prost(string, repeated, tag = "5")]
-        pub edit_texts: Vec<String>,
-        #[prost(message, repeated, tag = "6")]
-        pub undo_counts: Vec<UndoCount>,
-    }
-
-    #[derive(Message)]
-    pub struct VectorClockEntry {
-        #[prost(uint32, tag = "1")]
-        pub replica_id: u32,
-        #[prost(uint32, tag = "2")]
-        pub timestamp: u32,
-    }
-
-    #[derive(Message)]
-    pub struct Range {
-        #[prost(uint64, tag = "1")]
-        pub start: u64,
-        #[prost(uint64, tag = "2")]
-        pub end: u64,
-    }
-
-    #[derive(Message)]
-    pub struct UndoCount {
-        #[prost(uint32, tag = "1")]
-        pub replica_id: u32,
-        #[prost(uint32, tag = "2")]
-        pub lamport_timestamp: u32,
-        #[prost(uint32, tag = "3")]
-        pub count: u32,
-    }
-}

crates/collab2/src/db/queries/channels.rs 🔗

@@ -1,1319 +0,0 @@
-use super::*;
-use rpc::proto::channel_member::Kind;
-use sea_orm::TryGetableMany;
-
-impl Database {
-    #[cfg(test)]
-    pub async fn all_channels(&self) -> Result<Vec<(ChannelId, String)>> {
-        self.transaction(move |tx| async move {
-            let mut channels = Vec::new();
-            let mut rows = channel::Entity::find().stream(&*tx).await?;
-            while let Some(row) = rows.next().await {
-                let row = row?;
-                channels.push((row.id, row.name));
-            }
-            Ok(channels)
-        })
-        .await
-    }
-
-    #[cfg(test)]
-    pub async fn create_root_channel(&self, name: &str, creator_id: UserId) -> Result<ChannelId> {
-        Ok(self
-            .create_channel(name, None, creator_id)
-            .await?
-            .channel
-            .id)
-    }
-
-    #[cfg(test)]
-    pub async fn create_sub_channel(
-        &self,
-        name: &str,
-        parent: ChannelId,
-        creator_id: UserId,
-    ) -> Result<ChannelId> {
-        Ok(self
-            .create_channel(name, Some(parent), creator_id)
-            .await?
-            .channel
-            .id)
-    }
-
-    pub async fn create_channel(
-        &self,
-        name: &str,
-        parent_channel_id: Option<ChannelId>,
-        admin_id: UserId,
-    ) -> Result<CreateChannelResult> {
-        let name = Self::sanitize_channel_name(name)?;
-        self.transaction(move |tx| async move {
-            let mut parent = None;
-
-            if let Some(parent_channel_id) = parent_channel_id {
-                let parent_channel = self.get_channel_internal(parent_channel_id, &*tx).await?;
-                self.check_user_is_channel_admin(&parent_channel, admin_id, &*tx)
-                    .await?;
-                parent = Some(parent_channel);
-            }
-
-            let channel = channel::ActiveModel {
-                id: ActiveValue::NotSet,
-                name: ActiveValue::Set(name.to_string()),
-                visibility: ActiveValue::Set(ChannelVisibility::Members),
-                parent_path: ActiveValue::Set(
-                    parent
-                        .as_ref()
-                        .map_or(String::new(), |parent| parent.path()),
-                ),
-            }
-            .insert(&*tx)
-            .await?;
-
-            let participants_to_update;
-            if let Some(parent) = &parent {
-                participants_to_update = self
-                    .participants_to_notify_for_channel_change(parent, &*tx)
-                    .await?;
-            } else {
-                participants_to_update = vec![];
-
-                channel_member::ActiveModel {
-                    id: ActiveValue::NotSet,
-                    channel_id: ActiveValue::Set(channel.id),
-                    user_id: ActiveValue::Set(admin_id),
-                    accepted: ActiveValue::Set(true),
-                    role: ActiveValue::Set(ChannelRole::Admin),
-                }
-                .insert(&*tx)
-                .await?;
-            };
-
-            Ok(CreateChannelResult {
-                channel: Channel::from_model(channel, ChannelRole::Admin),
-                participants_to_update,
-            })
-        })
-        .await
-    }
-
-    pub async fn join_channel(
-        &self,
-        channel_id: ChannelId,
-        user_id: UserId,
-        connection: ConnectionId,
-        environment: &str,
-    ) -> Result<(JoinRoom, Option<MembershipUpdated>, ChannelRole)> {
-        self.transaction(move |tx| async move {
-            let channel = self.get_channel_internal(channel_id, &*tx).await?;
-            let mut role = self.channel_role_for_user(&channel, user_id, &*tx).await?;
-
-            let mut accept_invite_result = None;
-
-            if role.is_none() {
-                if let Some(invitation) = self
-                    .pending_invite_for_channel(&channel, user_id, &*tx)
-                    .await?
-                {
-                    // note, this may be a parent channel
-                    role = Some(invitation.role);
-                    channel_member::Entity::update(channel_member::ActiveModel {
-                        accepted: ActiveValue::Set(true),
-                        ..invitation.into_active_model()
-                    })
-                    .exec(&*tx)
-                    .await?;
-
-                    accept_invite_result = Some(
-                        self.calculate_membership_updated(&channel, user_id, &*tx)
-                            .await?,
-                    );
-
-                    debug_assert!(
-                        self.channel_role_for_user(&channel, user_id, &*tx).await? == role
-                    );
-                }
-            }
-
-            if channel.visibility == ChannelVisibility::Public {
-                role = Some(ChannelRole::Guest);
-                let channel_to_join = self
-                    .public_ancestors_including_self(&channel, &*tx)
-                    .await?
-                    .first()
-                    .cloned()
-                    .unwrap_or(channel.clone());
-
-                channel_member::Entity::insert(channel_member::ActiveModel {
-                    id: ActiveValue::NotSet,
-                    channel_id: ActiveValue::Set(channel_to_join.id),
-                    user_id: ActiveValue::Set(user_id),
-                    accepted: ActiveValue::Set(true),
-                    role: ActiveValue::Set(ChannelRole::Guest),
-                })
-                .exec(&*tx)
-                .await?;
-
-                accept_invite_result = Some(
-                    self.calculate_membership_updated(&channel_to_join, user_id, &*tx)
-                        .await?,
-                );
-
-                debug_assert!(self.channel_role_for_user(&channel, user_id, &*tx).await? == role);
-            }
-
-            if role.is_none() || role == Some(ChannelRole::Banned) {
-                Err(anyhow!("not allowed"))?
-            }
-
-            let live_kit_room = format!("channel-{}", nanoid::nanoid!(30));
-            let room_id = self
-                .get_or_create_channel_room(channel_id, &live_kit_room, environment, &*tx)
-                .await?;
-
-            self.join_channel_room_internal(room_id, user_id, connection, &*tx)
-                .await
-                .map(|jr| (jr, accept_invite_result, role.unwrap()))
-        })
-        .await
-    }
-
-    pub async fn set_channel_visibility(
-        &self,
-        channel_id: ChannelId,
-        visibility: ChannelVisibility,
-        admin_id: UserId,
-    ) -> Result<SetChannelVisibilityResult> {
-        self.transaction(move |tx| async move {
-            let channel = self.get_channel_internal(channel_id, &*tx).await?;
-
-            self.check_user_is_channel_admin(&channel, admin_id, &*tx)
-                .await?;
-
-            let previous_members = self
-                .get_channel_participant_details_internal(&channel, &*tx)
-                .await?;
-
-            let mut model = channel.into_active_model();
-            model.visibility = ActiveValue::Set(visibility);
-            let channel = model.update(&*tx).await?;
-
-            let mut participants_to_update: HashMap<UserId, ChannelsForUser> = self
-                .participants_to_notify_for_channel_change(&channel, &*tx)
-                .await?
-                .into_iter()
-                .collect();
-
-            let mut channels_to_remove: Vec<ChannelId> = vec![];
-            let mut participants_to_remove: HashSet<UserId> = HashSet::default();
-            match visibility {
-                ChannelVisibility::Members => {
-                    let all_descendents: Vec<ChannelId> = self
-                        .get_channel_descendants_including_self(vec![channel_id], &*tx)
-                        .await?
-                        .into_iter()
-                        .map(|channel| channel.id)
-                        .collect();
-
-                    channels_to_remove = channel::Entity::find()
-                        .filter(
-                            channel::Column::Id
-                                .is_in(all_descendents)
-                                .and(channel::Column::Visibility.eq(ChannelVisibility::Public)),
-                        )
-                        .all(&*tx)
-                        .await?
-                        .into_iter()
-                        .map(|channel| channel.id)
-                        .collect();
-
-                    channels_to_remove.push(channel_id);
-
-                    for member in previous_members {
-                        if member.role.can_only_see_public_descendants() {
-                            participants_to_remove.insert(member.user_id);
-                        }
-                    }
-                }
-                ChannelVisibility::Public => {
-                    if let Some(public_parent) = self.public_parent_channel(&channel, &*tx).await? {
-                        let parent_updates = self
-                            .participants_to_notify_for_channel_change(&public_parent, &*tx)
-                            .await?;
-
-                        for (user_id, channels) in parent_updates {
-                            participants_to_update.insert(user_id, channels);
-                        }
-                    }
-                }
-            }
-
-            Ok(SetChannelVisibilityResult {
-                participants_to_update,
-                participants_to_remove,
-                channels_to_remove,
-            })
-        })
-        .await
-    }
-
-    pub async fn delete_channel(
-        &self,
-        channel_id: ChannelId,
-        user_id: UserId,
-    ) -> Result<(Vec<ChannelId>, Vec<UserId>)> {
-        self.transaction(move |tx| async move {
-            let channel = self.get_channel_internal(channel_id, &*tx).await?;
-            self.check_user_is_channel_admin(&channel, user_id, &*tx)
-                .await?;
-
-            let members_to_notify: Vec<UserId> = channel_member::Entity::find()
-                .filter(channel_member::Column::ChannelId.is_in(channel.ancestors_including_self()))
-                .select_only()
-                .column(channel_member::Column::UserId)
-                .distinct()
-                .into_values::<_, QueryUserIds>()
-                .all(&*tx)
-                .await?;
-
-            let channels_to_remove = self
-                .get_channel_descendants_including_self(vec![channel.id], &*tx)
-                .await?
-                .into_iter()
-                .map(|channel| channel.id)
-                .collect::<Vec<_>>();
-
-            channel::Entity::delete_many()
-                .filter(channel::Column::Id.is_in(channels_to_remove.iter().copied()))
-                .exec(&*tx)
-                .await?;
-
-            Ok((channels_to_remove, members_to_notify))
-        })
-        .await
-    }
-
-    pub async fn invite_channel_member(
-        &self,
-        channel_id: ChannelId,
-        invitee_id: UserId,
-        inviter_id: UserId,
-        role: ChannelRole,
-    ) -> Result<InviteMemberResult> {
-        self.transaction(move |tx| async move {
-            let channel = self.get_channel_internal(channel_id, &*tx).await?;
-            self.check_user_is_channel_admin(&channel, inviter_id, &*tx)
-                .await?;
-
-            channel_member::ActiveModel {
-                id: ActiveValue::NotSet,
-                channel_id: ActiveValue::Set(channel_id),
-                user_id: ActiveValue::Set(invitee_id),
-                accepted: ActiveValue::Set(false),
-                role: ActiveValue::Set(role),
-            }
-            .insert(&*tx)
-            .await?;
-
-            let channel = Channel::from_model(channel, role);
-
-            let notifications = self
-                .create_notification(
-                    invitee_id,
-                    rpc::Notification::ChannelInvitation {
-                        channel_id: channel_id.to_proto(),
-                        channel_name: channel.name.clone(),
-                        inviter_id: inviter_id.to_proto(),
-                    },
-                    true,
-                    &*tx,
-                )
-                .await?
-                .into_iter()
-                .collect();
-
-            Ok(InviteMemberResult {
-                channel,
-                notifications,
-            })
-        })
-        .await
-    }
-
-    fn sanitize_channel_name(name: &str) -> Result<&str> {
-        let new_name = name.trim().trim_start_matches('#');
-        if new_name == "" {
-            Err(anyhow!("channel name can't be blank"))?;
-        }
-        Ok(new_name)
-    }
-
-    pub async fn rename_channel(
-        &self,
-        channel_id: ChannelId,
-        admin_id: UserId,
-        new_name: &str,
-    ) -> Result<RenameChannelResult> {
-        self.transaction(move |tx| async move {
-            let new_name = Self::sanitize_channel_name(new_name)?.to_string();
-
-            let channel = self.get_channel_internal(channel_id, &*tx).await?;
-            let role = self
-                .check_user_is_channel_admin(&channel, admin_id, &*tx)
-                .await?;
-
-            let mut model = channel.into_active_model();
-            model.name = ActiveValue::Set(new_name.clone());
-            let channel = model.update(&*tx).await?;
-
-            let participants = self
-                .get_channel_participant_details_internal(&channel, &*tx)
-                .await?;
-
-            Ok(RenameChannelResult {
-                channel: Channel::from_model(channel.clone(), role),
-                participants_to_update: participants
-                    .iter()
-                    .map(|participant| {
-                        (
-                            participant.user_id,
-                            Channel::from_model(channel.clone(), participant.role),
-                        )
-                    })
-                    .collect(),
-            })
-        })
-        .await
-    }
-
-    pub async fn respond_to_channel_invite(
-        &self,
-        channel_id: ChannelId,
-        user_id: UserId,
-        accept: bool,
-    ) -> Result<RespondToChannelInvite> {
-        self.transaction(move |tx| async move {
-            let channel = self.get_channel_internal(channel_id, &*tx).await?;
-
-            let membership_update = if accept {
-                let rows_affected = channel_member::Entity::update_many()
-                    .set(channel_member::ActiveModel {
-                        accepted: ActiveValue::Set(accept),
-                        ..Default::default()
-                    })
-                    .filter(
-                        channel_member::Column::ChannelId
-                            .eq(channel_id)
-                            .and(channel_member::Column::UserId.eq(user_id))
-                            .and(channel_member::Column::Accepted.eq(false)),
-                    )
-                    .exec(&*tx)
-                    .await?
-                    .rows_affected;
-
-                if rows_affected == 0 {
-                    Err(anyhow!("no such invitation"))?;
-                }
-
-                Some(
-                    self.calculate_membership_updated(&channel, user_id, &*tx)
-                        .await?,
-                )
-            } else {
-                let rows_affected = channel_member::Entity::delete_many()
-                    .filter(
-                        channel_member::Column::ChannelId
-                            .eq(channel_id)
-                            .and(channel_member::Column::UserId.eq(user_id))
-                            .and(channel_member::Column::Accepted.eq(false)),
-                    )
-                    .exec(&*tx)
-                    .await?
-                    .rows_affected;
-                if rows_affected == 0 {
-                    Err(anyhow!("no such invitation"))?;
-                }
-
-                None
-            };
-
-            Ok(RespondToChannelInvite {
-                membership_update,
-                notifications: self
-                    .mark_notification_as_read_with_response(
-                        user_id,
-                        &rpc::Notification::ChannelInvitation {
-                            channel_id: channel_id.to_proto(),
-                            channel_name: Default::default(),
-                            inviter_id: Default::default(),
-                        },
-                        accept,
-                        &*tx,
-                    )
-                    .await?
-                    .into_iter()
-                    .collect(),
-            })
-        })
-        .await
-    }
-
-    async fn calculate_membership_updated(
-        &self,
-        channel: &channel::Model,
-        user_id: UserId,
-        tx: &DatabaseTransaction,
-    ) -> Result<MembershipUpdated> {
-        let new_channels = self.get_user_channels(user_id, Some(channel), &*tx).await?;
-        let removed_channels = self
-            .get_channel_descendants_including_self(vec![channel.id], &*tx)
-            .await?
-            .into_iter()
-            .filter_map(|channel| {
-                if !new_channels.channels.iter().any(|c| c.id == channel.id) {
-                    Some(channel.id)
-                } else {
-                    None
-                }
-            })
-            .collect::<Vec<_>>();
-
-        Ok(MembershipUpdated {
-            channel_id: channel.id,
-            new_channels,
-            removed_channels,
-        })
-    }
-
-    pub async fn remove_channel_member(
-        &self,
-        channel_id: ChannelId,
-        member_id: UserId,
-        admin_id: UserId,
-    ) -> Result<RemoveChannelMemberResult> {
-        self.transaction(|tx| async move {
-            let channel = self.get_channel_internal(channel_id, &*tx).await?;
-            self.check_user_is_channel_admin(&channel, admin_id, &*tx)
-                .await?;
-
-            let result = channel_member::Entity::delete_many()
-                .filter(
-                    channel_member::Column::ChannelId
-                        .eq(channel_id)
-                        .and(channel_member::Column::UserId.eq(member_id)),
-                )
-                .exec(&*tx)
-                .await?;
-
-            if result.rows_affected == 0 {
-                Err(anyhow!("no such member"))?;
-            }
-
-            Ok(RemoveChannelMemberResult {
-                membership_update: self
-                    .calculate_membership_updated(&channel, member_id, &*tx)
-                    .await?,
-                notification_id: self
-                    .remove_notification(
-                        member_id,
-                        rpc::Notification::ChannelInvitation {
-                            channel_id: channel_id.to_proto(),
-                            channel_name: Default::default(),
-                            inviter_id: Default::default(),
-                        },
-                        &*tx,
-                    )
-                    .await?,
-            })
-        })
-        .await
-    }
-
-    pub async fn get_channel_invites_for_user(&self, user_id: UserId) -> Result<Vec<Channel>> {
-        self.transaction(|tx| async move {
-            let mut role_for_channel: HashMap<ChannelId, ChannelRole> = HashMap::default();
-
-            let channel_invites = channel_member::Entity::find()
-                .filter(
-                    channel_member::Column::UserId
-                        .eq(user_id)
-                        .and(channel_member::Column::Accepted.eq(false)),
-                )
-                .all(&*tx)
-                .await?;
-
-            for invite in channel_invites {
-                role_for_channel.insert(invite.channel_id, invite.role);
-            }
-
-            let channels = channel::Entity::find()
-                .filter(channel::Column::Id.is_in(role_for_channel.keys().copied()))
-                .all(&*tx)
-                .await?;
-
-            let channels = channels
-                .into_iter()
-                .filter_map(|channel| {
-                    let role = *role_for_channel.get(&channel.id)?;
-                    Some(Channel::from_model(channel, role))
-                })
-                .collect();
-
-            Ok(channels)
-        })
-        .await
-    }
-
-    pub async fn get_channels_for_user(&self, user_id: UserId) -> Result<ChannelsForUser> {
-        self.transaction(|tx| async move {
-            let tx = tx;
-
-            self.get_user_channels(user_id, None, &tx).await
-        })
-        .await
-    }
-
-    pub async fn get_user_channels(
-        &self,
-        user_id: UserId,
-        ancestor_channel: Option<&channel::Model>,
-        tx: &DatabaseTransaction,
-    ) -> Result<ChannelsForUser> {
-        let channel_memberships = channel_member::Entity::find()
-            .filter(
-                channel_member::Column::UserId
-                    .eq(user_id)
-                    .and(channel_member::Column::Accepted.eq(true)),
-            )
-            .all(&*tx)
-            .await?;
-
-        let descendants = self
-            .get_channel_descendants_including_self(
-                channel_memberships.iter().map(|m| m.channel_id),
-                &*tx,
-            )
-            .await?;
-
-        let mut roles_by_channel_id: HashMap<ChannelId, ChannelRole> = HashMap::default();
-        for membership in channel_memberships.iter() {
-            roles_by_channel_id.insert(membership.channel_id, membership.role);
-        }
-
-        let mut visible_channel_ids: HashSet<ChannelId> = HashSet::default();
-
-        let channels: Vec<Channel> = descendants
-            .into_iter()
-            .filter_map(|channel| {
-                let parent_role = channel
-                    .parent_id()
-                    .and_then(|parent_id| roles_by_channel_id.get(&parent_id));
-
-                let role = if let Some(parent_role) = parent_role {
-                    let role = if let Some(existing_role) = roles_by_channel_id.get(&channel.id) {
-                        existing_role.max(*parent_role)
-                    } else {
-                        *parent_role
-                    };
-                    roles_by_channel_id.insert(channel.id, role);
-                    role
-                } else {
-                    *roles_by_channel_id.get(&channel.id)?
-                };
-
-                let can_see_parent_paths = role.can_see_all_descendants()
-                    || role.can_only_see_public_descendants()
-                        && channel.visibility == ChannelVisibility::Public;
-                if !can_see_parent_paths {
-                    return None;
-                }
-
-                visible_channel_ids.insert(channel.id);
-
-                if let Some(ancestor) = ancestor_channel {
-                    if !channel
-                        .ancestors_including_self()
-                        .any(|id| id == ancestor.id)
-                    {
-                        return None;
-                    }
-                }
-
-                let mut channel = Channel::from_model(channel, role);
-                channel
-                    .parent_path
-                    .retain(|id| visible_channel_ids.contains(&id));
-
-                Some(channel)
-            })
-            .collect();
-
-        #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
-        enum QueryUserIdsAndChannelIds {
-            ChannelId,
-            UserId,
-        }
-
-        let mut channel_participants: HashMap<ChannelId, Vec<UserId>> = HashMap::default();
-        {
-            let mut rows = room_participant::Entity::find()
-                .inner_join(room::Entity)
-                .filter(room::Column::ChannelId.is_in(channels.iter().map(|c| c.id)))
-                .select_only()
-                .column(room::Column::ChannelId)
-                .column(room_participant::Column::UserId)
-                .into_values::<_, QueryUserIdsAndChannelIds>()
-                .stream(&*tx)
-                .await?;
-            while let Some(row) = rows.next().await {
-                let row: (ChannelId, UserId) = row?;
-                channel_participants.entry(row.0).or_default().push(row.1)
-            }
-        }
-
-        let channel_ids = channels.iter().map(|c| c.id).collect::<Vec<_>>();
-        let channel_buffer_changes = self
-            .unseen_channel_buffer_changes(user_id, &channel_ids, &*tx)
-            .await?;
-
-        let unseen_messages = self
-            .unseen_channel_messages(user_id, &channel_ids, &*tx)
-            .await?;
-
-        Ok(ChannelsForUser {
-            channels,
-            channel_participants,
-            unseen_buffer_changes: channel_buffer_changes,
-            channel_messages: unseen_messages,
-        })
-    }
-
-    async fn participants_to_notify_for_channel_change(
-        &self,
-        new_parent: &channel::Model,
-        tx: &DatabaseTransaction,
-    ) -> Result<Vec<(UserId, ChannelsForUser)>> {
-        let mut results: Vec<(UserId, ChannelsForUser)> = Vec::new();
-
-        let members = self
-            .get_channel_participant_details_internal(new_parent, &*tx)
-            .await?;
-
-        for member in members.iter() {
-            if !member.role.can_see_all_descendants() {
-                continue;
-            }
-            results.push((
-                member.user_id,
-                self.get_user_channels(member.user_id, Some(new_parent), &*tx)
-                    .await?,
-            ))
-        }
-
-        let public_parents = self
-            .public_ancestors_including_self(new_parent, &*tx)
-            .await?;
-        let public_parent = public_parents.last();
-
-        let Some(public_parent) = public_parent else {
-            return Ok(results);
-        };
-
-        // could save some time in the common case by skipping this if the
-        // new channel is not public and has no public descendants.
-        let public_members = if public_parent == new_parent {
-            members
-        } else {
-            self.get_channel_participant_details_internal(public_parent, &*tx)
-                .await?
-        };
-
-        for member in public_members {
-            if !member.role.can_only_see_public_descendants() {
-                continue;
-            };
-            results.push((
-                member.user_id,
-                self.get_user_channels(member.user_id, Some(public_parent), &*tx)
-                    .await?,
-            ))
-        }
-
-        Ok(results)
-    }
-
-    pub async fn set_channel_member_role(
-        &self,
-        channel_id: ChannelId,
-        admin_id: UserId,
-        for_user: UserId,
-        role: ChannelRole,
-    ) -> Result<SetMemberRoleResult> {
-        self.transaction(|tx| async move {
-            let channel = self.get_channel_internal(channel_id, &*tx).await?;
-            self.check_user_is_channel_admin(&channel, admin_id, &*tx)
-                .await?;
-
-            let membership = channel_member::Entity::find()
-                .filter(
-                    channel_member::Column::ChannelId
-                        .eq(channel_id)
-                        .and(channel_member::Column::UserId.eq(for_user)),
-                )
-                .one(&*tx)
-                .await?;
-
-            let Some(membership) = membership else {
-                Err(anyhow!("no such member"))?
-            };
-
-            let mut update = membership.into_active_model();
-            update.role = ActiveValue::Set(role);
-            let updated = channel_member::Entity::update(update).exec(&*tx).await?;
-
-            if updated.accepted {
-                Ok(SetMemberRoleResult::MembershipUpdated(
-                    self.calculate_membership_updated(&channel, for_user, &*tx)
-                        .await?,
-                ))
-            } else {
-                Ok(SetMemberRoleResult::InviteUpdated(Channel::from_model(
-                    channel, role,
-                )))
-            }
-        })
-        .await
-    }
-
-    pub async fn get_channel_participant_details(
-        &self,
-        channel_id: ChannelId,
-        user_id: UserId,
-    ) -> Result<Vec<proto::ChannelMember>> {
-        let (role, members) = self
-            .transaction(move |tx| async move {
-                let channel = self.get_channel_internal(channel_id, &*tx).await?;
-                let role = self
-                    .check_user_is_channel_participant(&channel, user_id, &*tx)
-                    .await?;
-                Ok((
-                    role,
-                    self.get_channel_participant_details_internal(&channel, &*tx)
-                        .await?,
-                ))
-            })
-            .await?;
-
-        if role == ChannelRole::Admin {
-            Ok(members
-                .into_iter()
-                .map(|channel_member| channel_member.to_proto())
-                .collect())
-        } else {
-            return Ok(members
-                .into_iter()
-                .filter_map(|member| {
-                    if member.kind == proto::channel_member::Kind::Invitee {
-                        return None;
-                    }
-                    Some(ChannelMember {
-                        role: member.role,
-                        user_id: member.user_id,
-                        kind: proto::channel_member::Kind::Member,
-                    })
-                })
-                .map(|channel_member| channel_member.to_proto())
-                .collect());
-        }
-    }
-
-    async fn get_channel_participant_details_internal(
-        &self,
-        channel: &channel::Model,
-        tx: &DatabaseTransaction,
-    ) -> Result<Vec<ChannelMember>> {
-        #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
-        enum QueryMemberDetails {
-            UserId,
-            Role,
-            IsDirectMember,
-            Accepted,
-            Visibility,
-        }
-
-        let mut stream = channel_member::Entity::find()
-            .left_join(channel::Entity)
-            .filter(channel_member::Column::ChannelId.is_in(channel.ancestors_including_self()))
-            .select_only()
-            .column(channel_member::Column::UserId)
-            .column(channel_member::Column::Role)
-            .column_as(
-                channel_member::Column::ChannelId.eq(channel.id),
-                QueryMemberDetails::IsDirectMember,
-            )
-            .column(channel_member::Column::Accepted)
-            .column(channel::Column::Visibility)
-            .into_values::<_, QueryMemberDetails>()
-            .stream(&*tx)
-            .await?;
-
-        let mut user_details: HashMap<UserId, ChannelMember> = HashMap::default();
-
-        while let Some(user_membership) = stream.next().await {
-            let (user_id, channel_role, is_direct_member, is_invite_accepted, visibility): (
-                UserId,
-                ChannelRole,
-                bool,
-                bool,
-                ChannelVisibility,
-            ) = user_membership?;
-            let kind = match (is_direct_member, is_invite_accepted) {
-                (true, true) => proto::channel_member::Kind::Member,
-                (true, false) => proto::channel_member::Kind::Invitee,
-                (false, true) => proto::channel_member::Kind::AncestorMember,
-                (false, false) => continue,
-            };
-
-            if channel_role == ChannelRole::Guest
-                && visibility != ChannelVisibility::Public
-                && channel.visibility != ChannelVisibility::Public
-            {
-                continue;
-            }
-
-            if let Some(details_mut) = user_details.get_mut(&user_id) {
-                if channel_role.should_override(details_mut.role) {
-                    details_mut.role = channel_role;
-                }
-                if kind == Kind::Member {
-                    details_mut.kind = kind;
-                // the UI is going to be a bit confusing if you already have permissions
-                // that are greater than or equal to the ones you're being invited to.
-                } else if kind == Kind::Invitee && details_mut.kind == Kind::AncestorMember {
-                    details_mut.kind = kind;
-                }
-            } else {
-                user_details.insert(
-                    user_id,
-                    ChannelMember {
-                        user_id,
-                        kind,
-                        role: channel_role,
-                    },
-                );
-            }
-        }
-
-        Ok(user_details
-            .into_iter()
-            .map(|(_, details)| details)
-            .collect())
-    }
-
-    pub async fn get_channel_participants(
-        &self,
-        channel: &channel::Model,
-        tx: &DatabaseTransaction,
-    ) -> Result<Vec<UserId>> {
-        let participants = self
-            .get_channel_participant_details_internal(channel, &*tx)
-            .await?;
-        Ok(participants
-            .into_iter()
-            .map(|member| member.user_id)
-            .collect())
-    }
-
-    pub async fn check_user_is_channel_admin(
-        &self,
-        channel: &channel::Model,
-        user_id: UserId,
-        tx: &DatabaseTransaction,
-    ) -> Result<ChannelRole> {
-        let role = self.channel_role_for_user(channel, user_id, tx).await?;
-        match role {
-            Some(ChannelRole::Admin) => Ok(role.unwrap()),
-            Some(ChannelRole::Member)
-            | Some(ChannelRole::Banned)
-            | Some(ChannelRole::Guest)
-            | None => Err(anyhow!(
-                "user is not a channel admin or channel does not exist"
-            ))?,
-        }
-    }
-
-    pub async fn check_user_is_channel_member(
-        &self,
-        channel: &channel::Model,
-        user_id: UserId,
-        tx: &DatabaseTransaction,
-    ) -> Result<ChannelRole> {
-        let channel_role = self.channel_role_for_user(channel, user_id, tx).await?;
-        match channel_role {
-            Some(ChannelRole::Admin) | Some(ChannelRole::Member) => Ok(channel_role.unwrap()),
-            Some(ChannelRole::Banned) | Some(ChannelRole::Guest) | None => Err(anyhow!(
-                "user is not a channel member or channel does not exist"
-            ))?,
-        }
-    }
-
-    pub async fn check_user_is_channel_participant(
-        &self,
-        channel: &channel::Model,
-        user_id: UserId,
-        tx: &DatabaseTransaction,
-    ) -> Result<ChannelRole> {
-        let role = self.channel_role_for_user(channel, user_id, tx).await?;
-        match role {
-            Some(ChannelRole::Admin) | Some(ChannelRole::Member) | Some(ChannelRole::Guest) => {
-                Ok(role.unwrap())
-            }
-            Some(ChannelRole::Banned) | None => Err(anyhow!(
-                "user is not a channel participant or channel does not exist"
-            ))?,
-        }
-    }
-
-    pub async fn pending_invite_for_channel(
-        &self,
-        channel: &channel::Model,
-        user_id: UserId,
-        tx: &DatabaseTransaction,
-    ) -> Result<Option<channel_member::Model>> {
-        let row = channel_member::Entity::find()
-            .filter(channel_member::Column::ChannelId.is_in(channel.ancestors_including_self()))
-            .filter(channel_member::Column::UserId.eq(user_id))
-            .filter(channel_member::Column::Accepted.eq(false))
-            .one(&*tx)
-            .await?;
-
-        Ok(row)
-    }
-
-    pub async fn public_parent_channel(
-        &self,
-        channel: &channel::Model,
-        tx: &DatabaseTransaction,
-    ) -> Result<Option<channel::Model>> {
-        let mut path = self.public_ancestors_including_self(channel, &*tx).await?;
-        if path.last().unwrap().id == channel.id {
-            path.pop();
-        }
-        Ok(path.pop())
-    }
-
-    pub async fn public_ancestors_including_self(
-        &self,
-        channel: &channel::Model,
-        tx: &DatabaseTransaction,
-    ) -> Result<Vec<channel::Model>> {
-        let visible_channels = channel::Entity::find()
-            .filter(channel::Column::Id.is_in(channel.ancestors_including_self()))
-            .filter(channel::Column::Visibility.eq(ChannelVisibility::Public))
-            .order_by_asc(channel::Column::ParentPath)
-            .all(&*tx)
-            .await?;
-
-        Ok(visible_channels)
-    }
-
-    pub async fn channel_role_for_user(
-        &self,
-        channel: &channel::Model,
-        user_id: UserId,
-        tx: &DatabaseTransaction,
-    ) -> Result<Option<ChannelRole>> {
-        #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
-        enum QueryChannelMembership {
-            ChannelId,
-            Role,
-            Visibility,
-        }
-
-        let mut rows = channel_member::Entity::find()
-            .left_join(channel::Entity)
-            .filter(
-                channel_member::Column::ChannelId
-                    .is_in(channel.ancestors_including_self())
-                    .and(channel_member::Column::UserId.eq(user_id))
-                    .and(channel_member::Column::Accepted.eq(true)),
-            )
-            .select_only()
-            .column(channel_member::Column::ChannelId)
-            .column(channel_member::Column::Role)
-            .column(channel::Column::Visibility)
-            .into_values::<_, QueryChannelMembership>()
-            .stream(&*tx)
-            .await?;
-
-        let mut user_role: Option<ChannelRole> = None;
-
-        let mut is_participant = false;
-        let mut current_channel_visibility = None;
-
-        // note these channels are not iterated in any particular order,
-        // our current logic takes the highest permission available.
-        while let Some(row) = rows.next().await {
-            let (membership_channel, role, visibility): (
-                ChannelId,
-                ChannelRole,
-                ChannelVisibility,
-            ) = row?;
-
-            match role {
-                ChannelRole::Admin | ChannelRole::Member | ChannelRole::Banned => {
-                    if let Some(users_role) = user_role {
-                        user_role = Some(users_role.max(role));
-                    } else {
-                        user_role = Some(role)
-                    }
-                }
-                ChannelRole::Guest if visibility == ChannelVisibility::Public => {
-                    is_participant = true
-                }
-                ChannelRole::Guest => {}
-            }
-            if channel.id == membership_channel {
-                current_channel_visibility = Some(visibility);
-            }
-        }
-        // free up database connection
-        drop(rows);
-
-        if is_participant && user_role.is_none() {
-            if current_channel_visibility.is_none() {
-                current_channel_visibility = channel::Entity::find()
-                    .filter(channel::Column::Id.eq(channel.id))
-                    .one(&*tx)
-                    .await?
-                    .map(|channel| channel.visibility);
-            }
-            if current_channel_visibility == Some(ChannelVisibility::Public) {
-                user_role = Some(ChannelRole::Guest);
-            }
-        }
-
-        Ok(user_role)
-    }
-
-    // Get the descendants of the given set if channels, ordered by their
-    // path.
-    async fn get_channel_descendants_including_self(
-        &self,
-        channel_ids: impl IntoIterator<Item = ChannelId>,
-        tx: &DatabaseTransaction,
-    ) -> Result<Vec<channel::Model>> {
-        let mut values = String::new();
-        for id in channel_ids {
-            if !values.is_empty() {
-                values.push_str(", ");
-            }
-            write!(&mut values, "({})", id).unwrap();
-        }
-
-        if values.is_empty() {
-            return Ok(vec![]);
-        }
-
-        let sql = format!(
-            r#"
-            SELECT DISTINCT
-                descendant_channels.*,
-                descendant_channels.parent_path || descendant_channels.id as full_path
-            FROM
-                channels parent_channels, channels descendant_channels
-            WHERE
-                descendant_channels.id IN ({values}) OR
-                (
-                    parent_channels.id IN ({values}) AND
-                    descendant_channels.parent_path LIKE (parent_channels.parent_path || parent_channels.id || '/%')
-                )
-            ORDER BY
-                full_path ASC
-            "#
-        );
-
-        Ok(channel::Entity::find()
-            .from_raw_sql(Statement::from_string(
-                self.pool.get_database_backend(),
-                sql,
-            ))
-            .all(tx)
-            .await?)
-    }
-
-    /// Returns the channel with the given ID
-    pub async fn get_channel(&self, channel_id: ChannelId, user_id: UserId) -> Result<Channel> {
-        self.transaction(|tx| async move {
-            let channel = self.get_channel_internal(channel_id, &*tx).await?;
-            let role = self
-                .check_user_is_channel_participant(&channel, user_id, &*tx)
-                .await?;
-
-            Ok(Channel::from_model(channel, role))
-        })
-        .await
-    }
-
-    pub async fn get_channel_internal(
-        &self,
-        channel_id: ChannelId,
-        tx: &DatabaseTransaction,
-    ) -> Result<channel::Model> {
-        Ok(channel::Entity::find_by_id(channel_id)
-            .one(&*tx)
-            .await?
-            .ok_or_else(|| anyhow!("no such channel"))?)
-    }
-
-    pub(crate) async fn get_or_create_channel_room(
-        &self,
-        channel_id: ChannelId,
-        live_kit_room: &str,
-        environment: &str,
-        tx: &DatabaseTransaction,
-    ) -> Result<RoomId> {
-        let room = room::Entity::find()
-            .filter(room::Column::ChannelId.eq(channel_id))
-            .one(&*tx)
-            .await?;
-
-        let room_id = if let Some(room) = room {
-            if let Some(env) = room.enviroment {
-                if &env != environment {
-                    Err(anyhow!("must join using the {} release", env))?;
-                }
-            }
-            room.id
-        } else {
-            let result = room::Entity::insert(room::ActiveModel {
-                channel_id: ActiveValue::Set(Some(channel_id)),
-                live_kit_room: ActiveValue::Set(live_kit_room.to_string()),
-                enviroment: ActiveValue::Set(Some(environment.to_string())),
-                ..Default::default()
-            })
-            .exec(&*tx)
-            .await?;
-
-            result.last_insert_id
-        };
-
-        Ok(room_id)
-    }
-
-    /// Move a channel from one parent to another
-    pub async fn move_channel(
-        &self,
-        channel_id: ChannelId,
-        new_parent_id: Option<ChannelId>,
-        admin_id: UserId,
-    ) -> Result<Option<MoveChannelResult>> {
-        self.transaction(|tx| async move {
-            let channel = self.get_channel_internal(channel_id, &*tx).await?;
-            self.check_user_is_channel_admin(&channel, admin_id, &*tx)
-                .await?;
-
-            let new_parent_path;
-            let new_parent_channel;
-            if let Some(new_parent_id) = new_parent_id {
-                let new_parent = self.get_channel_internal(new_parent_id, &*tx).await?;
-                self.check_user_is_channel_admin(&new_parent, admin_id, &*tx)
-                    .await?;
-
-                if new_parent
-                    .ancestors_including_self()
-                    .any(|id| id == channel.id)
-                {
-                    Err(anyhow!("cannot move a channel into one of its descendants"))?;
-                }
-
-                new_parent_path = new_parent.path();
-                new_parent_channel = Some(new_parent);
-            } else {
-                new_parent_path = String::new();
-                new_parent_channel = None;
-            };
-
-            let previous_participants = self
-                .get_channel_participant_details_internal(&channel, &*tx)
-                .await?;
-
-            let old_path = format!("{}{}/", channel.parent_path, channel.id);
-            let new_path = format!("{}{}/", new_parent_path, channel.id);
-
-            if old_path == new_path {
-                return Ok(None);
-            }
-
-            let mut model = channel.into_active_model();
-            model.parent_path = ActiveValue::Set(new_parent_path);
-            let channel = model.update(&*tx).await?;
-
-            if new_parent_channel.is_none() {
-                channel_member::ActiveModel {
-                    id: ActiveValue::NotSet,
-                    channel_id: ActiveValue::Set(channel_id),
-                    user_id: ActiveValue::Set(admin_id),
-                    accepted: ActiveValue::Set(true),
-                    role: ActiveValue::Set(ChannelRole::Admin),
-                }
-                .insert(&*tx)
-                .await?;
-            }
-
-            let descendent_ids =
-                ChannelId::find_by_statement::<QueryIds>(Statement::from_sql_and_values(
-                    self.pool.get_database_backend(),
-                    "
-                    UPDATE channels SET parent_path = REPLACE(parent_path, $1, $2)
-                    WHERE parent_path LIKE $3 || '%'
-                    RETURNING id
-                ",
-                    [old_path.clone().into(), new_path.into(), old_path.into()],
-                ))
-                .all(&*tx)
-                .await?;
-
-            let participants_to_update: HashMap<_, _> = self
-                .participants_to_notify_for_channel_change(
-                    new_parent_channel.as_ref().unwrap_or(&channel),
-                    &*tx,
-                )
-                .await?
-                .into_iter()
-                .collect();
-
-            let mut moved_channels: HashSet<ChannelId> = HashSet::default();
-            for id in descendent_ids {
-                moved_channels.insert(id);
-            }
-            moved_channels.insert(channel_id);
-
-            let mut participants_to_remove: HashSet<UserId> = HashSet::default();
-            for participant in previous_participants {
-                if participant.kind == proto::channel_member::Kind::AncestorMember {
-                    if !participants_to_update.contains_key(&participant.user_id) {
-                        participants_to_remove.insert(participant.user_id);
-                    }
-                }
-            }
-
-            Ok(Some(MoveChannelResult {
-                participants_to_remove,
-                participants_to_update,
-                moved_channels,
-            }))
-        })
-        .await
-    }
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
-enum QueryIds {
-    Id,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
-enum QueryUserIds {
-    UserId,
-}

crates/collab2/src/db/queries/contacts.rs 🔗

@@ -1,353 +0,0 @@
-use super::*;
-
-impl Database {
-    pub async fn get_contacts(&self, user_id: UserId) -> Result<Vec<Contact>> {
-        #[derive(Debug, FromQueryResult)]
-        struct ContactWithUserBusyStatuses {
-            user_id_a: UserId,
-            user_id_b: UserId,
-            a_to_b: bool,
-            accepted: bool,
-            user_a_busy: bool,
-            user_b_busy: bool,
-        }
-
-        self.transaction(|tx| async move {
-            let user_a_participant = Alias::new("user_a_participant");
-            let user_b_participant = Alias::new("user_b_participant");
-            let mut db_contacts = contact::Entity::find()
-                .column_as(
-                    Expr::col((user_a_participant.clone(), room_participant::Column::Id))
-                        .is_not_null(),
-                    "user_a_busy",
-                )
-                .column_as(
-                    Expr::col((user_b_participant.clone(), room_participant::Column::Id))
-                        .is_not_null(),
-                    "user_b_busy",
-                )
-                .filter(
-                    contact::Column::UserIdA
-                        .eq(user_id)
-                        .or(contact::Column::UserIdB.eq(user_id)),
-                )
-                .join_as(
-                    JoinType::LeftJoin,
-                    contact::Relation::UserARoomParticipant.def(),
-                    user_a_participant,
-                )
-                .join_as(
-                    JoinType::LeftJoin,
-                    contact::Relation::UserBRoomParticipant.def(),
-                    user_b_participant,
-                )
-                .into_model::<ContactWithUserBusyStatuses>()
-                .stream(&*tx)
-                .await?;
-
-            let mut contacts = Vec::new();
-            while let Some(db_contact) = db_contacts.next().await {
-                let db_contact = db_contact?;
-                if db_contact.user_id_a == user_id {
-                    if db_contact.accepted {
-                        contacts.push(Contact::Accepted {
-                            user_id: db_contact.user_id_b,
-                            busy: db_contact.user_b_busy,
-                        });
-                    } else if db_contact.a_to_b {
-                        contacts.push(Contact::Outgoing {
-                            user_id: db_contact.user_id_b,
-                        })
-                    } else {
-                        contacts.push(Contact::Incoming {
-                            user_id: db_contact.user_id_b,
-                        });
-                    }
-                } else if db_contact.accepted {
-                    contacts.push(Contact::Accepted {
-                        user_id: db_contact.user_id_a,
-                        busy: db_contact.user_a_busy,
-                    });
-                } else if db_contact.a_to_b {
-                    contacts.push(Contact::Incoming {
-                        user_id: db_contact.user_id_a,
-                    });
-                } else {
-                    contacts.push(Contact::Outgoing {
-                        user_id: db_contact.user_id_a,
-                    });
-                }
-            }
-
-            contacts.sort_unstable_by_key(|contact| contact.user_id());
-
-            Ok(contacts)
-        })
-        .await
-    }
-
-    pub async fn is_user_busy(&self, user_id: UserId) -> Result<bool> {
-        self.transaction(|tx| async move {
-            let participant = room_participant::Entity::find()
-                .filter(room_participant::Column::UserId.eq(user_id))
-                .one(&*tx)
-                .await?;
-            Ok(participant.is_some())
-        })
-        .await
-    }
-
-    pub async fn has_contact(&self, user_id_1: UserId, user_id_2: UserId) -> Result<bool> {
-        self.transaction(|tx| async move {
-            let (id_a, id_b) = if user_id_1 < user_id_2 {
-                (user_id_1, user_id_2)
-            } else {
-                (user_id_2, user_id_1)
-            };
-
-            Ok(contact::Entity::find()
-                .filter(
-                    contact::Column::UserIdA
-                        .eq(id_a)
-                        .and(contact::Column::UserIdB.eq(id_b))
-                        .and(contact::Column::Accepted.eq(true)),
-                )
-                .one(&*tx)
-                .await?
-                .is_some())
-        })
-        .await
-    }
-
-    pub async fn send_contact_request(
-        &self,
-        sender_id: UserId,
-        receiver_id: UserId,
-    ) -> Result<NotificationBatch> {
-        self.transaction(|tx| async move {
-            let (id_a, id_b, a_to_b) = if sender_id < receiver_id {
-                (sender_id, receiver_id, true)
-            } else {
-                (receiver_id, sender_id, false)
-            };
-
-            let rows_affected = contact::Entity::insert(contact::ActiveModel {
-                user_id_a: ActiveValue::set(id_a),
-                user_id_b: ActiveValue::set(id_b),
-                a_to_b: ActiveValue::set(a_to_b),
-                accepted: ActiveValue::set(false),
-                should_notify: ActiveValue::set(true),
-                ..Default::default()
-            })
-            .on_conflict(
-                OnConflict::columns([contact::Column::UserIdA, contact::Column::UserIdB])
-                    .values([
-                        (contact::Column::Accepted, true.into()),
-                        (contact::Column::ShouldNotify, false.into()),
-                    ])
-                    .action_and_where(
-                        contact::Column::Accepted.eq(false).and(
-                            contact::Column::AToB
-                                .eq(a_to_b)
-                                .and(contact::Column::UserIdA.eq(id_b))
-                                .or(contact::Column::AToB
-                                    .ne(a_to_b)
-                                    .and(contact::Column::UserIdA.eq(id_a))),
-                        ),
-                    )
-                    .to_owned(),
-            )
-            .exec_without_returning(&*tx)
-            .await?;
-
-            if rows_affected == 0 {
-                Err(anyhow!("contact already requested"))?;
-            }
-
-            Ok(self
-                .create_notification(
-                    receiver_id,
-                    rpc::Notification::ContactRequest {
-                        sender_id: sender_id.to_proto(),
-                    },
-                    true,
-                    &*tx,
-                )
-                .await?
-                .into_iter()
-                .collect())
-        })
-        .await
-    }
-
-    /// Returns a bool indicating whether the removed contact had originally accepted or not
-    ///
-    /// Deletes the contact identified by the requester and responder ids, and then returns
-    /// whether the deleted contact had originally accepted or was a pending contact request.
-    ///
-    /// # Arguments
-    ///
-    /// * `requester_id` - The user that initiates this request
-    /// * `responder_id` - The user that will be removed
-    pub async fn remove_contact(
-        &self,
-        requester_id: UserId,
-        responder_id: UserId,
-    ) -> Result<(bool, Option<NotificationId>)> {
-        self.transaction(|tx| async move {
-            let (id_a, id_b) = if responder_id < requester_id {
-                (responder_id, requester_id)
-            } else {
-                (requester_id, responder_id)
-            };
-
-            let contact = contact::Entity::find()
-                .filter(
-                    contact::Column::UserIdA
-                        .eq(id_a)
-                        .and(contact::Column::UserIdB.eq(id_b)),
-                )
-                .one(&*tx)
-                .await?
-                .ok_or_else(|| anyhow!("no such contact"))?;
-
-            contact::Entity::delete_by_id(contact.id).exec(&*tx).await?;
-
-            let mut deleted_notification_id = None;
-            if !contact.accepted {
-                deleted_notification_id = self
-                    .remove_notification(
-                        responder_id,
-                        rpc::Notification::ContactRequest {
-                            sender_id: requester_id.to_proto(),
-                        },
-                        &*tx,
-                    )
-                    .await?;
-            }
-
-            Ok((contact.accepted, deleted_notification_id))
-        })
-        .await
-    }
-
-    pub async fn dismiss_contact_notification(
-        &self,
-        user_id: UserId,
-        contact_user_id: UserId,
-    ) -> Result<()> {
-        self.transaction(|tx| async move {
-            let (id_a, id_b, a_to_b) = if user_id < contact_user_id {
-                (user_id, contact_user_id, true)
-            } else {
-                (contact_user_id, user_id, false)
-            };
-
-            let result = contact::Entity::update_many()
-                .set(contact::ActiveModel {
-                    should_notify: ActiveValue::set(false),
-                    ..Default::default()
-                })
-                .filter(
-                    contact::Column::UserIdA
-                        .eq(id_a)
-                        .and(contact::Column::UserIdB.eq(id_b))
-                        .and(
-                            contact::Column::AToB
-                                .eq(a_to_b)
-                                .and(contact::Column::Accepted.eq(true))
-                                .or(contact::Column::AToB
-                                    .ne(a_to_b)
-                                    .and(contact::Column::Accepted.eq(false))),
-                        ),
-                )
-                .exec(&*tx)
-                .await?;
-            if result.rows_affected == 0 {
-                Err(anyhow!("no such contact request"))?
-            } else {
-                Ok(())
-            }
-        })
-        .await
-    }
-
-    pub async fn respond_to_contact_request(
-        &self,
-        responder_id: UserId,
-        requester_id: UserId,
-        accept: bool,
-    ) -> Result<NotificationBatch> {
-        self.transaction(|tx| async move {
-            let (id_a, id_b, a_to_b) = if responder_id < requester_id {
-                (responder_id, requester_id, false)
-            } else {
-                (requester_id, responder_id, true)
-            };
-            let rows_affected = if accept {
-                let result = contact::Entity::update_many()
-                    .set(contact::ActiveModel {
-                        accepted: ActiveValue::set(true),
-                        should_notify: ActiveValue::set(true),
-                        ..Default::default()
-                    })
-                    .filter(
-                        contact::Column::UserIdA
-                            .eq(id_a)
-                            .and(contact::Column::UserIdB.eq(id_b))
-                            .and(contact::Column::AToB.eq(a_to_b)),
-                    )
-                    .exec(&*tx)
-                    .await?;
-                result.rows_affected
-            } else {
-                let result = contact::Entity::delete_many()
-                    .filter(
-                        contact::Column::UserIdA
-                            .eq(id_a)
-                            .and(contact::Column::UserIdB.eq(id_b))
-                            .and(contact::Column::AToB.eq(a_to_b))
-                            .and(contact::Column::Accepted.eq(false)),
-                    )
-                    .exec(&*tx)
-                    .await?;
-
-                result.rows_affected
-            };
-
-            if rows_affected == 0 {
-                Err(anyhow!("no such contact request"))?
-            }
-
-            let mut notifications = Vec::new();
-            notifications.extend(
-                self.mark_notification_as_read_with_response(
-                    responder_id,
-                    &rpc::Notification::ContactRequest {
-                        sender_id: requester_id.to_proto(),
-                    },
-                    accept,
-                    &*tx,
-                )
-                .await?,
-            );
-
-            if accept {
-                notifications.extend(
-                    self.create_notification(
-                        requester_id,
-                        rpc::Notification::ContactRequestAccepted {
-                            responder_id: responder_id.to_proto(),
-                        },
-                        true,
-                        &*tx,
-                    )
-                    .await?,
-                );
-            }
-
-            Ok(notifications)
-        })
-        .await
-    }
-}

crates/collab2/src/db/queries/messages.rs 🔗

@@ -1,505 +0,0 @@
-use super::*;
-use rpc::Notification;
-use sea_orm::TryInsertResult;
-use time::OffsetDateTime;
-
-impl Database {
-    pub async fn join_channel_chat(
-        &self,
-        channel_id: ChannelId,
-        connection_id: ConnectionId,
-        user_id: UserId,
-    ) -> Result<()> {
-        self.transaction(|tx| async move {
-            let channel = self.get_channel_internal(channel_id, &*tx).await?;
-            self.check_user_is_channel_participant(&channel, user_id, &*tx)
-                .await?;
-            channel_chat_participant::ActiveModel {
-                id: ActiveValue::NotSet,
-                channel_id: ActiveValue::Set(channel_id),
-                user_id: ActiveValue::Set(user_id),
-                connection_id: ActiveValue::Set(connection_id.id as i32),
-                connection_server_id: ActiveValue::Set(ServerId(connection_id.owner_id as i32)),
-            }
-            .insert(&*tx)
-            .await?;
-            Ok(())
-        })
-        .await
-    }
-
-    pub async fn channel_chat_connection_lost(
-        &self,
-        connection_id: ConnectionId,
-        tx: &DatabaseTransaction,
-    ) -> Result<()> {
-        channel_chat_participant::Entity::delete_many()
-            .filter(
-                Condition::all()
-                    .add(
-                        channel_chat_participant::Column::ConnectionServerId
-                            .eq(connection_id.owner_id),
-                    )
-                    .add(channel_chat_participant::Column::ConnectionId.eq(connection_id.id)),
-            )
-            .exec(tx)
-            .await?;
-        Ok(())
-    }
-
-    pub async fn leave_channel_chat(
-        &self,
-        channel_id: ChannelId,
-        connection_id: ConnectionId,
-        _user_id: UserId,
-    ) -> Result<()> {
-        self.transaction(|tx| async move {
-            channel_chat_participant::Entity::delete_many()
-                .filter(
-                    Condition::all()
-                        .add(
-                            channel_chat_participant::Column::ConnectionServerId
-                                .eq(connection_id.owner_id),
-                        )
-                        .add(channel_chat_participant::Column::ConnectionId.eq(connection_id.id))
-                        .add(channel_chat_participant::Column::ChannelId.eq(channel_id)),
-                )
-                .exec(&*tx)
-                .await?;
-
-            Ok(())
-        })
-        .await
-    }
-
-    pub async fn get_channel_messages(
-        &self,
-        channel_id: ChannelId,
-        user_id: UserId,
-        count: usize,
-        before_message_id: Option<MessageId>,
-    ) -> Result<Vec<proto::ChannelMessage>> {
-        self.transaction(|tx| async move {
-            let channel = self.get_channel_internal(channel_id, &*tx).await?;
-            self.check_user_is_channel_participant(&channel, user_id, &*tx)
-                .await?;
-
-            let mut condition =
-                Condition::all().add(channel_message::Column::ChannelId.eq(channel_id));
-
-            if let Some(before_message_id) = before_message_id {
-                condition = condition.add(channel_message::Column::Id.lt(before_message_id));
-            }
-
-            let rows = channel_message::Entity::find()
-                .filter(condition)
-                .order_by_desc(channel_message::Column::Id)
-                .limit(count as u64)
-                .all(&*tx)
-                .await?;
-
-            self.load_channel_messages(rows, &*tx).await
-        })
-        .await
-    }
-
-    pub async fn get_channel_messages_by_id(
-        &self,
-        user_id: UserId,
-        message_ids: &[MessageId],
-    ) -> Result<Vec<proto::ChannelMessage>> {
-        self.transaction(|tx| async move {
-            let rows = channel_message::Entity::find()
-                .filter(channel_message::Column::Id.is_in(message_ids.iter().copied()))
-                .order_by_desc(channel_message::Column::Id)
-                .all(&*tx)
-                .await?;
-
-            let mut channels = HashMap::<ChannelId, channel::Model>::default();
-            for row in &rows {
-                channels.insert(
-                    row.channel_id,
-                    self.get_channel_internal(row.channel_id, &*tx).await?,
-                );
-            }
-
-            for (_, channel) in channels {
-                self.check_user_is_channel_participant(&channel, user_id, &*tx)
-                    .await?;
-            }
-
-            let messages = self.load_channel_messages(rows, &*tx).await?;
-            Ok(messages)
-        })
-        .await
-    }
-
-    async fn load_channel_messages(
-        &self,
-        rows: Vec<channel_message::Model>,
-        tx: &DatabaseTransaction,
-    ) -> Result<Vec<proto::ChannelMessage>> {
-        let mut messages = rows
-            .into_iter()
-            .map(|row| {
-                let nonce = row.nonce.as_u64_pair();
-                proto::ChannelMessage {
-                    id: row.id.to_proto(),
-                    sender_id: row.sender_id.to_proto(),
-                    body: row.body,
-                    timestamp: row.sent_at.assume_utc().unix_timestamp() as u64,
-                    mentions: vec![],
-                    nonce: Some(proto::Nonce {
-                        upper_half: nonce.0,
-                        lower_half: nonce.1,
-                    }),
-                }
-            })
-            .collect::<Vec<_>>();
-        messages.reverse();
-
-        let mut mentions = channel_message_mention::Entity::find()
-            .filter(channel_message_mention::Column::MessageId.is_in(messages.iter().map(|m| m.id)))
-            .order_by_asc(channel_message_mention::Column::MessageId)
-            .order_by_asc(channel_message_mention::Column::StartOffset)
-            .stream(&*tx)
-            .await?;
-
-        let mut message_ix = 0;
-        while let Some(mention) = mentions.next().await {
-            let mention = mention?;
-            let message_id = mention.message_id.to_proto();
-            while let Some(message) = messages.get_mut(message_ix) {
-                if message.id < message_id {
-                    message_ix += 1;
-                } else {
-                    if message.id == message_id {
-                        message.mentions.push(proto::ChatMention {
-                            range: Some(proto::Range {
-                                start: mention.start_offset as u64,
-                                end: mention.end_offset as u64,
-                            }),
-                            user_id: mention.user_id.to_proto(),
-                        });
-                    }
-                    break;
-                }
-            }
-        }
-
-        Ok(messages)
-    }
-
-    pub async fn create_channel_message(
-        &self,
-        channel_id: ChannelId,
-        user_id: UserId,
-        body: &str,
-        mentions: &[proto::ChatMention],
-        timestamp: OffsetDateTime,
-        nonce: u128,
-    ) -> Result<CreatedChannelMessage> {
-        self.transaction(|tx| async move {
-            let channel = self.get_channel_internal(channel_id, &*tx).await?;
-            self.check_user_is_channel_participant(&channel, user_id, &*tx)
-                .await?;
-
-            let mut rows = channel_chat_participant::Entity::find()
-                .filter(channel_chat_participant::Column::ChannelId.eq(channel_id))
-                .stream(&*tx)
-                .await?;
-
-            let mut is_participant = false;
-            let mut participant_connection_ids = Vec::new();
-            let mut participant_user_ids = Vec::new();
-            while let Some(row) = rows.next().await {
-                let row = row?;
-                if row.user_id == user_id {
-                    is_participant = true;
-                }
-                participant_user_ids.push(row.user_id);
-                participant_connection_ids.push(row.connection());
-            }
-            drop(rows);
-
-            if !is_participant {
-                Err(anyhow!("not a chat participant"))?;
-            }
-
-            let timestamp = timestamp.to_offset(time::UtcOffset::UTC);
-            let timestamp = time::PrimitiveDateTime::new(timestamp.date(), timestamp.time());
-
-            let result = channel_message::Entity::insert(channel_message::ActiveModel {
-                channel_id: ActiveValue::Set(channel_id),
-                sender_id: ActiveValue::Set(user_id),
-                body: ActiveValue::Set(body.to_string()),
-                sent_at: ActiveValue::Set(timestamp),
-                nonce: ActiveValue::Set(Uuid::from_u128(nonce)),
-                id: ActiveValue::NotSet,
-            })
-            .on_conflict(
-                OnConflict::columns([
-                    channel_message::Column::SenderId,
-                    channel_message::Column::Nonce,
-                ])
-                .do_nothing()
-                .to_owned(),
-            )
-            .do_nothing()
-            .exec(&*tx)
-            .await?;
-
-            let message_id;
-            let mut notifications = Vec::new();
-            match result {
-                TryInsertResult::Inserted(result) => {
-                    message_id = result.last_insert_id;
-                    let mentioned_user_ids =
-                        mentions.iter().map(|m| m.user_id).collect::<HashSet<_>>();
-                    let mentions = mentions
-                        .iter()
-                        .filter_map(|mention| {
-                            let range = mention.range.as_ref()?;
-                            if !body.is_char_boundary(range.start as usize)
-                                || !body.is_char_boundary(range.end as usize)
-                            {
-                                return None;
-                            }
-                            Some(channel_message_mention::ActiveModel {
-                                message_id: ActiveValue::Set(message_id),
-                                start_offset: ActiveValue::Set(range.start as i32),
-                                end_offset: ActiveValue::Set(range.end as i32),
-                                user_id: ActiveValue::Set(UserId::from_proto(mention.user_id)),
-                            })
-                        })
-                        .collect::<Vec<_>>();
-                    if !mentions.is_empty() {
-                        channel_message_mention::Entity::insert_many(mentions)
-                            .exec(&*tx)
-                            .await?;
-                    }
-
-                    for mentioned_user in mentioned_user_ids {
-                        notifications.extend(
-                            self.create_notification(
-                                UserId::from_proto(mentioned_user),
-                                rpc::Notification::ChannelMessageMention {
-                                    message_id: message_id.to_proto(),
-                                    sender_id: user_id.to_proto(),
-                                    channel_id: channel_id.to_proto(),
-                                },
-                                false,
-                                &*tx,
-                            )
-                            .await?,
-                        );
-                    }
-
-                    self.observe_channel_message_internal(channel_id, user_id, message_id, &*tx)
-                        .await?;
-                }
-                _ => {
-                    message_id = channel_message::Entity::find()
-                        .filter(channel_message::Column::Nonce.eq(Uuid::from_u128(nonce)))
-                        .one(&*tx)
-                        .await?
-                        .ok_or_else(|| anyhow!("failed to insert message"))?
-                        .id;
-                }
-            }
-
-            let mut channel_members = self.get_channel_participants(&channel, &*tx).await?;
-            channel_members.retain(|member| !participant_user_ids.contains(member));
-
-            Ok(CreatedChannelMessage {
-                message_id,
-                participant_connection_ids,
-                channel_members,
-                notifications,
-            })
-        })
-        .await
-    }
-
-    pub async fn observe_channel_message(
-        &self,
-        channel_id: ChannelId,
-        user_id: UserId,
-        message_id: MessageId,
-    ) -> Result<NotificationBatch> {
-        self.transaction(|tx| async move {
-            self.observe_channel_message_internal(channel_id, user_id, message_id, &*tx)
-                .await?;
-            let mut batch = NotificationBatch::default();
-            batch.extend(
-                self.mark_notification_as_read(
-                    user_id,
-                    &Notification::ChannelMessageMention {
-                        message_id: message_id.to_proto(),
-                        sender_id: Default::default(),
-                        channel_id: Default::default(),
-                    },
-                    &*tx,
-                )
-                .await?,
-            );
-            Ok(batch)
-        })
-        .await
-    }
-
-    async fn observe_channel_message_internal(
-        &self,
-        channel_id: ChannelId,
-        user_id: UserId,
-        message_id: MessageId,
-        tx: &DatabaseTransaction,
-    ) -> Result<()> {
-        observed_channel_messages::Entity::insert(observed_channel_messages::ActiveModel {
-            user_id: ActiveValue::Set(user_id),
-            channel_id: ActiveValue::Set(channel_id),
-            channel_message_id: ActiveValue::Set(message_id),
-        })
-        .on_conflict(
-            OnConflict::columns([
-                observed_channel_messages::Column::ChannelId,
-                observed_channel_messages::Column::UserId,
-            ])
-            .update_column(observed_channel_messages::Column::ChannelMessageId)
-            .action_cond_where(observed_channel_messages::Column::ChannelMessageId.lt(message_id))
-            .to_owned(),
-        )
-        // TODO: Try to upgrade SeaORM so we don't have to do this hack around their bug
-        .exec_without_returning(&*tx)
-        .await?;
-        Ok(())
-    }
-
-    pub async fn unseen_channel_messages(
-        &self,
-        user_id: UserId,
-        channel_ids: &[ChannelId],
-        tx: &DatabaseTransaction,
-    ) -> Result<Vec<proto::UnseenChannelMessage>> {
-        let mut observed_messages_by_channel_id = HashMap::default();
-        let mut rows = observed_channel_messages::Entity::find()
-            .filter(observed_channel_messages::Column::UserId.eq(user_id))
-            .filter(observed_channel_messages::Column::ChannelId.is_in(channel_ids.iter().copied()))
-            .stream(&*tx)
-            .await?;
-
-        while let Some(row) = rows.next().await {
-            let row = row?;
-            observed_messages_by_channel_id.insert(row.channel_id, row);
-        }
-        drop(rows);
-        let mut values = String::new();
-        for id in channel_ids {
-            if !values.is_empty() {
-                values.push_str(", ");
-            }
-            write!(&mut values, "({})", id).unwrap();
-        }
-
-        if values.is_empty() {
-            return Ok(Default::default());
-        }
-
-        let sql = format!(
-            r#"
-            SELECT
-                *
-            FROM (
-                SELECT
-                    *,
-                    row_number() OVER (
-                        PARTITION BY channel_id
-                        ORDER BY id DESC
-                    ) as row_number
-                FROM channel_messages
-                WHERE
-                    channel_id in ({values})
-            ) AS messages
-            WHERE
-                row_number = 1
-            "#,
-        );
-
-        let stmt = Statement::from_string(self.pool.get_database_backend(), sql);
-        let last_messages = channel_message::Model::find_by_statement(stmt)
-            .all(&*tx)
-            .await?;
-
-        let mut changes = Vec::new();
-        for last_message in last_messages {
-            if let Some(observed_message) =
-                observed_messages_by_channel_id.get(&last_message.channel_id)
-            {
-                if observed_message.channel_message_id == last_message.id {
-                    continue;
-                }
-            }
-            changes.push(proto::UnseenChannelMessage {
-                channel_id: last_message.channel_id.to_proto(),
-                message_id: last_message.id.to_proto(),
-            });
-        }
-
-        Ok(changes)
-    }
-
-    pub async fn remove_channel_message(
-        &self,
-        channel_id: ChannelId,
-        message_id: MessageId,
-        user_id: UserId,
-    ) -> Result<Vec<ConnectionId>> {
-        self.transaction(|tx| async move {
-            let mut rows = channel_chat_participant::Entity::find()
-                .filter(channel_chat_participant::Column::ChannelId.eq(channel_id))
-                .stream(&*tx)
-                .await?;
-
-            let mut is_participant = false;
-            let mut participant_connection_ids = Vec::new();
-            while let Some(row) = rows.next().await {
-                let row = row?;
-                if row.user_id == user_id {
-                    is_participant = true;
-                }
-                participant_connection_ids.push(row.connection());
-            }
-            drop(rows);
-
-            if !is_participant {
-                Err(anyhow!("not a chat participant"))?;
-            }
-
-            let result = channel_message::Entity::delete_by_id(message_id)
-                .filter(channel_message::Column::SenderId.eq(user_id))
-                .exec(&*tx)
-                .await?;
-
-            if result.rows_affected == 0 {
-                let channel = self.get_channel_internal(channel_id, &*tx).await?;
-                if self
-                    .check_user_is_channel_admin(&channel, user_id, &*tx)
-                    .await
-                    .is_ok()
-                {
-                    let result = channel_message::Entity::delete_by_id(message_id)
-                        .exec(&*tx)
-                        .await?;
-                    if result.rows_affected == 0 {
-                        Err(anyhow!("no such message"))?;
-                    }
-                } else {
-                    Err(anyhow!("operation could not be completed"))?;
-                }
-            }
-
-            Ok(participant_connection_ids)
-        })
-        .await
-    }
-}

crates/collab2/src/db/queries/notifications.rs 🔗

@@ -1,262 +0,0 @@
-use super::*;
-use rpc::Notification;
-
-impl Database {
-    pub async fn initialize_notification_kinds(&mut self) -> Result<()> {
-        notification_kind::Entity::insert_many(Notification::all_variant_names().iter().map(
-            |kind| notification_kind::ActiveModel {
-                name: ActiveValue::Set(kind.to_string()),
-                ..Default::default()
-            },
-        ))
-        .on_conflict(OnConflict::new().do_nothing().to_owned())
-        .exec_without_returning(&self.pool)
-        .await?;
-
-        let mut rows = notification_kind::Entity::find().stream(&self.pool).await?;
-        while let Some(row) = rows.next().await {
-            let row = row?;
-            self.notification_kinds_by_name.insert(row.name, row.id);
-        }
-
-        for name in Notification::all_variant_names() {
-            if let Some(id) = self.notification_kinds_by_name.get(*name).copied() {
-                self.notification_kinds_by_id.insert(id, name);
-            }
-        }
-
-        Ok(())
-    }
-
-    pub async fn get_notifications(
-        &self,
-        recipient_id: UserId,
-        limit: usize,
-        before_id: Option<NotificationId>,
-    ) -> Result<Vec<proto::Notification>> {
-        self.transaction(|tx| async move {
-            let mut result = Vec::new();
-            let mut condition =
-                Condition::all().add(notification::Column::RecipientId.eq(recipient_id));
-
-            if let Some(before_id) = before_id {
-                condition = condition.add(notification::Column::Id.lt(before_id));
-            }
-
-            let mut rows = notification::Entity::find()
-                .filter(condition)
-                .order_by_desc(notification::Column::Id)
-                .limit(limit as u64)
-                .stream(&*tx)
-                .await?;
-            while let Some(row) = rows.next().await {
-                let row = row?;
-                let kind = row.kind;
-                if let Some(proto) = model_to_proto(self, row) {
-                    result.push(proto);
-                } else {
-                    log::warn!("unknown notification kind {:?}", kind);
-                }
-            }
-            result.reverse();
-            Ok(result)
-        })
-        .await
-    }
-
-    /// Create a notification. If `avoid_duplicates` is set to true, then avoid
-    /// creating a new notification if the given recipient already has an
-    /// unread notification with the given kind and entity id.
-    pub async fn create_notification(
-        &self,
-        recipient_id: UserId,
-        notification: Notification,
-        avoid_duplicates: bool,
-        tx: &DatabaseTransaction,
-    ) -> Result<Option<(UserId, proto::Notification)>> {
-        if avoid_duplicates {
-            if self
-                .find_notification(recipient_id, &notification, tx)
-                .await?
-                .is_some()
-            {
-                return Ok(None);
-            }
-        }
-
-        let proto = notification.to_proto();
-        let kind = notification_kind_from_proto(self, &proto)?;
-        let model = notification::ActiveModel {
-            recipient_id: ActiveValue::Set(recipient_id),
-            kind: ActiveValue::Set(kind),
-            entity_id: ActiveValue::Set(proto.entity_id.map(|id| id as i32)),
-            content: ActiveValue::Set(proto.content.clone()),
-            ..Default::default()
-        }
-        .save(&*tx)
-        .await?;
-
-        Ok(Some((
-            recipient_id,
-            proto::Notification {
-                id: model.id.as_ref().to_proto(),
-                kind: proto.kind,
-                timestamp: model.created_at.as_ref().assume_utc().unix_timestamp() as u64,
-                is_read: false,
-                response: None,
-                content: proto.content,
-                entity_id: proto.entity_id,
-            },
-        )))
-    }
-
-    /// Remove an unread notification with the given recipient, kind and
-    /// entity id.
-    pub async fn remove_notification(
-        &self,
-        recipient_id: UserId,
-        notification: Notification,
-        tx: &DatabaseTransaction,
-    ) -> Result<Option<NotificationId>> {
-        let id = self
-            .find_notification(recipient_id, &notification, tx)
-            .await?;
-        if let Some(id) = id {
-            notification::Entity::delete_by_id(id).exec(tx).await?;
-        }
-        Ok(id)
-    }
-
-    /// Populate the response for the notification with the given kind and
-    /// entity id.
-    pub async fn mark_notification_as_read_with_response(
-        &self,
-        recipient_id: UserId,
-        notification: &Notification,
-        response: bool,
-        tx: &DatabaseTransaction,
-    ) -> Result<Option<(UserId, proto::Notification)>> {
-        self.mark_notification_as_read_internal(recipient_id, notification, Some(response), tx)
-            .await
-    }
-
-    pub async fn mark_notification_as_read(
-        &self,
-        recipient_id: UserId,
-        notification: &Notification,
-        tx: &DatabaseTransaction,
-    ) -> Result<Option<(UserId, proto::Notification)>> {
-        self.mark_notification_as_read_internal(recipient_id, notification, None, tx)
-            .await
-    }
-
-    pub async fn mark_notification_as_read_by_id(
-        &self,
-        recipient_id: UserId,
-        notification_id: NotificationId,
-    ) -> Result<NotificationBatch> {
-        self.transaction(|tx| async move {
-            let row = notification::Entity::update(notification::ActiveModel {
-                id: ActiveValue::Unchanged(notification_id),
-                recipient_id: ActiveValue::Unchanged(recipient_id),
-                is_read: ActiveValue::Set(true),
-                ..Default::default()
-            })
-            .exec(&*tx)
-            .await?;
-            Ok(model_to_proto(self, row)
-                .map(|notification| (recipient_id, notification))
-                .into_iter()
-                .collect())
-        })
-        .await
-    }
-
-    async fn mark_notification_as_read_internal(
-        &self,
-        recipient_id: UserId,
-        notification: &Notification,
-        response: Option<bool>,
-        tx: &DatabaseTransaction,
-    ) -> Result<Option<(UserId, proto::Notification)>> {
-        if let Some(id) = self
-            .find_notification(recipient_id, notification, &*tx)
-            .await?
-        {
-            let row = notification::Entity::update(notification::ActiveModel {
-                id: ActiveValue::Unchanged(id),
-                recipient_id: ActiveValue::Unchanged(recipient_id),
-                is_read: ActiveValue::Set(true),
-                response: if let Some(response) = response {
-                    ActiveValue::Set(Some(response))
-                } else {
-                    ActiveValue::NotSet
-                },
-                ..Default::default()
-            })
-            .exec(tx)
-            .await?;
-            Ok(model_to_proto(self, row).map(|notification| (recipient_id, notification)))
-        } else {
-            Ok(None)
-        }
-    }
-
-    /// Find an unread notification by its recipient, kind and entity id.
-    async fn find_notification(
-        &self,
-        recipient_id: UserId,
-        notification: &Notification,
-        tx: &DatabaseTransaction,
-    ) -> Result<Option<NotificationId>> {
-        let proto = notification.to_proto();
-        let kind = notification_kind_from_proto(self, &proto)?;
-
-        #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
-        enum QueryIds {
-            Id,
-        }
-
-        Ok(notification::Entity::find()
-            .select_only()
-            .column(notification::Column::Id)
-            .filter(
-                Condition::all()
-                    .add(notification::Column::RecipientId.eq(recipient_id))
-                    .add(notification::Column::IsRead.eq(false))
-                    .add(notification::Column::Kind.eq(kind))
-                    .add(if proto.entity_id.is_some() {
-                        notification::Column::EntityId.eq(proto.entity_id)
-                    } else {
-                        notification::Column::EntityId.is_null()
-                    }),
-            )
-            .into_values::<_, QueryIds>()
-            .one(&*tx)
-            .await?)
-    }
-}
-
-fn model_to_proto(this: &Database, row: notification::Model) -> Option<proto::Notification> {
-    let kind = this.notification_kinds_by_id.get(&row.kind)?;
-    Some(proto::Notification {
-        id: row.id.to_proto(),
-        kind: kind.to_string(),
-        timestamp: row.created_at.assume_utc().unix_timestamp() as u64,
-        is_read: row.is_read,
-        response: row.response,
-        content: row.content,
-        entity_id: row.entity_id.map(|id| id as u64),
-    })
-}
-
-fn notification_kind_from_proto(
-    this: &Database,
-    proto: &proto::Notification,
-) -> Result<NotificationKindId> {
-    Ok(this
-        .notification_kinds_by_name
-        .get(&proto.kind)
-        .copied()
-        .ok_or_else(|| anyhow!("invalid notification kind {:?}", proto.kind))?)
-}

crates/collab2/src/db/queries/projects.rs 🔗

@@ -1,960 +0,0 @@
-use super::*;
-
-impl Database {
-    pub async fn project_count_excluding_admins(&self) -> Result<usize> {
-        #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
-        enum QueryAs {
-            Count,
-        }
-
-        self.transaction(|tx| async move {
-            Ok(project::Entity::find()
-                .select_only()
-                .column_as(project::Column::Id.count(), QueryAs::Count)
-                .inner_join(user::Entity)
-                .filter(user::Column::Admin.eq(false))
-                .into_values::<_, QueryAs>()
-                .one(&*tx)
-                .await?
-                .unwrap_or(0i64) as usize)
-        })
-        .await
-    }
-
-    pub async fn share_project(
-        &self,
-        room_id: RoomId,
-        connection: ConnectionId,
-        worktrees: &[proto::WorktreeMetadata],
-    ) -> Result<RoomGuard<(ProjectId, proto::Room)>> {
-        self.room_transaction(room_id, |tx| async move {
-            let participant = room_participant::Entity::find()
-                .filter(
-                    Condition::all()
-                        .add(
-                            room_participant::Column::AnsweringConnectionId
-                                .eq(connection.id as i32),
-                        )
-                        .add(
-                            room_participant::Column::AnsweringConnectionServerId
-                                .eq(connection.owner_id as i32),
-                        ),
-                )
-                .one(&*tx)
-                .await?
-                .ok_or_else(|| anyhow!("could not find participant"))?;
-            if participant.room_id != room_id {
-                return Err(anyhow!("shared project on unexpected room"))?;
-            }
-
-            let project = project::ActiveModel {
-                room_id: ActiveValue::set(participant.room_id),
-                host_user_id: ActiveValue::set(participant.user_id),
-                host_connection_id: ActiveValue::set(Some(connection.id as i32)),
-                host_connection_server_id: ActiveValue::set(Some(ServerId(
-                    connection.owner_id as i32,
-                ))),
-                ..Default::default()
-            }
-            .insert(&*tx)
-            .await?;
-
-            if !worktrees.is_empty() {
-                worktree::Entity::insert_many(worktrees.iter().map(|worktree| {
-                    worktree::ActiveModel {
-                        id: ActiveValue::set(worktree.id as i64),
-                        project_id: ActiveValue::set(project.id),
-                        abs_path: ActiveValue::set(worktree.abs_path.clone()),
-                        root_name: ActiveValue::set(worktree.root_name.clone()),
-                        visible: ActiveValue::set(worktree.visible),
-                        scan_id: ActiveValue::set(0),
-                        completed_scan_id: ActiveValue::set(0),
-                    }
-                }))
-                .exec(&*tx)
-                .await?;
-            }
-
-            project_collaborator::ActiveModel {
-                project_id: ActiveValue::set(project.id),
-                connection_id: ActiveValue::set(connection.id as i32),
-                connection_server_id: ActiveValue::set(ServerId(connection.owner_id as i32)),
-                user_id: ActiveValue::set(participant.user_id),
-                replica_id: ActiveValue::set(ReplicaId(0)),
-                is_host: ActiveValue::set(true),
-                ..Default::default()
-            }
-            .insert(&*tx)
-            .await?;
-
-            let room = self.get_room(room_id, &tx).await?;
-            Ok((project.id, room))
-        })
-        .await
-    }
-
-    pub async fn unshare_project(
-        &self,
-        project_id: ProjectId,
-        connection: ConnectionId,
-    ) -> Result<RoomGuard<(proto::Room, Vec<ConnectionId>)>> {
-        let room_id = self.room_id_for_project(project_id).await?;
-        self.room_transaction(room_id, |tx| async move {
-            let guest_connection_ids = self.project_guest_connection_ids(project_id, &tx).await?;
-
-            let project = project::Entity::find_by_id(project_id)
-                .one(&*tx)
-                .await?
-                .ok_or_else(|| anyhow!("project not found"))?;
-            if project.host_connection()? == connection {
-                project::Entity::delete(project.into_active_model())
-                    .exec(&*tx)
-                    .await?;
-                let room = self.get_room(room_id, &tx).await?;
-                Ok((room, guest_connection_ids))
-            } else {
-                Err(anyhow!("cannot unshare a project hosted by another user"))?
-            }
-        })
-        .await
-    }
-
-    pub async fn update_project(
-        &self,
-        project_id: ProjectId,
-        connection: ConnectionId,
-        worktrees: &[proto::WorktreeMetadata],
-    ) -> Result<RoomGuard<(proto::Room, Vec<ConnectionId>)>> {
-        let room_id = self.room_id_for_project(project_id).await?;
-        self.room_transaction(room_id, |tx| async move {
-            let project = project::Entity::find_by_id(project_id)
-                .filter(
-                    Condition::all()
-                        .add(project::Column::HostConnectionId.eq(connection.id as i32))
-                        .add(
-                            project::Column::HostConnectionServerId.eq(connection.owner_id as i32),
-                        ),
-                )
-                .one(&*tx)
-                .await?
-                .ok_or_else(|| anyhow!("no such project"))?;
-
-            self.update_project_worktrees(project.id, worktrees, &tx)
-                .await?;
-
-            let guest_connection_ids = self.project_guest_connection_ids(project.id, &tx).await?;
-            let room = self.get_room(project.room_id, &tx).await?;
-            Ok((room, guest_connection_ids))
-        })
-        .await
-    }
-
-    pub(in crate::db) async fn update_project_worktrees(
-        &self,
-        project_id: ProjectId,
-        worktrees: &[proto::WorktreeMetadata],
-        tx: &DatabaseTransaction,
-    ) -> Result<()> {
-        if !worktrees.is_empty() {
-            worktree::Entity::insert_many(worktrees.iter().map(|worktree| worktree::ActiveModel {
-                id: ActiveValue::set(worktree.id as i64),
-                project_id: ActiveValue::set(project_id),
-                abs_path: ActiveValue::set(worktree.abs_path.clone()),
-                root_name: ActiveValue::set(worktree.root_name.clone()),
-                visible: ActiveValue::set(worktree.visible),
-                scan_id: ActiveValue::set(0),
-                completed_scan_id: ActiveValue::set(0),
-            }))
-            .on_conflict(
-                OnConflict::columns([worktree::Column::ProjectId, worktree::Column::Id])
-                    .update_column(worktree::Column::RootName)
-                    .to_owned(),
-            )
-            .exec(&*tx)
-            .await?;
-        }
-
-        worktree::Entity::delete_many()
-            .filter(worktree::Column::ProjectId.eq(project_id).and(
-                worktree::Column::Id.is_not_in(worktrees.iter().map(|worktree| worktree.id as i64)),
-            ))
-            .exec(&*tx)
-            .await?;
-
-        Ok(())
-    }
-
-    pub async fn update_worktree(
-        &self,
-        update: &proto::UpdateWorktree,
-        connection: ConnectionId,
-    ) -> Result<RoomGuard<Vec<ConnectionId>>> {
-        let project_id = ProjectId::from_proto(update.project_id);
-        let worktree_id = update.worktree_id as i64;
-        let room_id = self.room_id_for_project(project_id).await?;
-        self.room_transaction(room_id, |tx| async move {
-            // Ensure the update comes from the host.
-            let _project = project::Entity::find_by_id(project_id)
-                .filter(
-                    Condition::all()
-                        .add(project::Column::HostConnectionId.eq(connection.id as i32))
-                        .add(
-                            project::Column::HostConnectionServerId.eq(connection.owner_id as i32),
-                        ),
-                )
-                .one(&*tx)
-                .await?
-                .ok_or_else(|| anyhow!("no such project"))?;
-
-            // Update metadata.
-            worktree::Entity::update(worktree::ActiveModel {
-                id: ActiveValue::set(worktree_id),
-                project_id: ActiveValue::set(project_id),
-                root_name: ActiveValue::set(update.root_name.clone()),
-                scan_id: ActiveValue::set(update.scan_id as i64),
-                completed_scan_id: if update.is_last_update {
-                    ActiveValue::set(update.scan_id as i64)
-                } else {
-                    ActiveValue::default()
-                },
-                abs_path: ActiveValue::set(update.abs_path.clone()),
-                ..Default::default()
-            })
-            .exec(&*tx)
-            .await?;
-
-            if !update.updated_entries.is_empty() {
-                worktree_entry::Entity::insert_many(update.updated_entries.iter().map(|entry| {
-                    let mtime = entry.mtime.clone().unwrap_or_default();
-                    worktree_entry::ActiveModel {
-                        project_id: ActiveValue::set(project_id),
-                        worktree_id: ActiveValue::set(worktree_id),
-                        id: ActiveValue::set(entry.id as i64),
-                        is_dir: ActiveValue::set(entry.is_dir),
-                        path: ActiveValue::set(entry.path.clone()),
-                        inode: ActiveValue::set(entry.inode as i64),
-                        mtime_seconds: ActiveValue::set(mtime.seconds as i64),
-                        mtime_nanos: ActiveValue::set(mtime.nanos as i32),
-                        is_symlink: ActiveValue::set(entry.is_symlink),
-                        is_ignored: ActiveValue::set(entry.is_ignored),
-                        is_external: ActiveValue::set(entry.is_external),
-                        git_status: ActiveValue::set(entry.git_status.map(|status| status as i64)),
-                        is_deleted: ActiveValue::set(false),
-                        scan_id: ActiveValue::set(update.scan_id as i64),
-                    }
-                }))
-                .on_conflict(
-                    OnConflict::columns([
-                        worktree_entry::Column::ProjectId,
-                        worktree_entry::Column::WorktreeId,
-                        worktree_entry::Column::Id,
-                    ])
-                    .update_columns([
-                        worktree_entry::Column::IsDir,
-                        worktree_entry::Column::Path,
-                        worktree_entry::Column::Inode,
-                        worktree_entry::Column::MtimeSeconds,
-                        worktree_entry::Column::MtimeNanos,
-                        worktree_entry::Column::IsSymlink,
-                        worktree_entry::Column::IsIgnored,
-                        worktree_entry::Column::GitStatus,
-                        worktree_entry::Column::ScanId,
-                    ])
-                    .to_owned(),
-                )
-                .exec(&*tx)
-                .await?;
-            }
-
-            if !update.removed_entries.is_empty() {
-                worktree_entry::Entity::update_many()
-                    .filter(
-                        worktree_entry::Column::ProjectId
-                            .eq(project_id)
-                            .and(worktree_entry::Column::WorktreeId.eq(worktree_id))
-                            .and(
-                                worktree_entry::Column::Id
-                                    .is_in(update.removed_entries.iter().map(|id| *id as i64)),
-                            ),
-                    )
-                    .set(worktree_entry::ActiveModel {
-                        is_deleted: ActiveValue::Set(true),
-                        scan_id: ActiveValue::Set(update.scan_id as i64),
-                        ..Default::default()
-                    })
-                    .exec(&*tx)
-                    .await?;
-            }
-
-            if !update.updated_repositories.is_empty() {
-                worktree_repository::Entity::insert_many(update.updated_repositories.iter().map(
-                    |repository| worktree_repository::ActiveModel {
-                        project_id: ActiveValue::set(project_id),
-                        worktree_id: ActiveValue::set(worktree_id),
-                        work_directory_id: ActiveValue::set(repository.work_directory_id as i64),
-                        scan_id: ActiveValue::set(update.scan_id as i64),
-                        branch: ActiveValue::set(repository.branch.clone()),
-                        is_deleted: ActiveValue::set(false),
-                    },
-                ))
-                .on_conflict(
-                    OnConflict::columns([
-                        worktree_repository::Column::ProjectId,
-                        worktree_repository::Column::WorktreeId,
-                        worktree_repository::Column::WorkDirectoryId,
-                    ])
-                    .update_columns([
-                        worktree_repository::Column::ScanId,
-                        worktree_repository::Column::Branch,
-                    ])
-                    .to_owned(),
-                )
-                .exec(&*tx)
-                .await?;
-            }
-
-            if !update.removed_repositories.is_empty() {
-                worktree_repository::Entity::update_many()
-                    .filter(
-                        worktree_repository::Column::ProjectId
-                            .eq(project_id)
-                            .and(worktree_repository::Column::WorktreeId.eq(worktree_id))
-                            .and(
-                                worktree_repository::Column::WorkDirectoryId
-                                    .is_in(update.removed_repositories.iter().map(|id| *id as i64)),
-                            ),
-                    )
-                    .set(worktree_repository::ActiveModel {
-                        is_deleted: ActiveValue::Set(true),
-                        scan_id: ActiveValue::Set(update.scan_id as i64),
-                        ..Default::default()
-                    })
-                    .exec(&*tx)
-                    .await?;
-            }
-
-            let connection_ids = self.project_guest_connection_ids(project_id, &tx).await?;
-            Ok(connection_ids)
-        })
-        .await
-    }
-
-    pub async fn update_diagnostic_summary(
-        &self,
-        update: &proto::UpdateDiagnosticSummary,
-        connection: ConnectionId,
-    ) -> Result<RoomGuard<Vec<ConnectionId>>> {
-        let project_id = ProjectId::from_proto(update.project_id);
-        let worktree_id = update.worktree_id as i64;
-        let room_id = self.room_id_for_project(project_id).await?;
-        self.room_transaction(room_id, |tx| async move {
-            let summary = update
-                .summary
-                .as_ref()
-                .ok_or_else(|| anyhow!("invalid summary"))?;
-
-            // Ensure the update comes from the host.
-            let project = project::Entity::find_by_id(project_id)
-                .one(&*tx)
-                .await?
-                .ok_or_else(|| anyhow!("no such project"))?;
-            if project.host_connection()? != connection {
-                return Err(anyhow!("can't update a project hosted by someone else"))?;
-            }
-
-            // Update summary.
-            worktree_diagnostic_summary::Entity::insert(worktree_diagnostic_summary::ActiveModel {
-                project_id: ActiveValue::set(project_id),
-                worktree_id: ActiveValue::set(worktree_id),
-                path: ActiveValue::set(summary.path.clone()),
-                language_server_id: ActiveValue::set(summary.language_server_id as i64),
-                error_count: ActiveValue::set(summary.error_count as i32),
-                warning_count: ActiveValue::set(summary.warning_count as i32),
-                ..Default::default()
-            })
-            .on_conflict(
-                OnConflict::columns([
-                    worktree_diagnostic_summary::Column::ProjectId,
-                    worktree_diagnostic_summary::Column::WorktreeId,
-                    worktree_diagnostic_summary::Column::Path,
-                ])
-                .update_columns([
-                    worktree_diagnostic_summary::Column::LanguageServerId,
-                    worktree_diagnostic_summary::Column::ErrorCount,
-                    worktree_diagnostic_summary::Column::WarningCount,
-                ])
-                .to_owned(),
-            )
-            .exec(&*tx)
-            .await?;
-
-            let connection_ids = self.project_guest_connection_ids(project_id, &tx).await?;
-            Ok(connection_ids)
-        })
-        .await
-    }
-
-    pub async fn start_language_server(
-        &self,
-        update: &proto::StartLanguageServer,
-        connection: ConnectionId,
-    ) -> Result<RoomGuard<Vec<ConnectionId>>> {
-        let project_id = ProjectId::from_proto(update.project_id);
-        let room_id = self.room_id_for_project(project_id).await?;
-        self.room_transaction(room_id, |tx| async move {
-            let server = update
-                .server
-                .as_ref()
-                .ok_or_else(|| anyhow!("invalid language server"))?;
-
-            // Ensure the update comes from the host.
-            let project = project::Entity::find_by_id(project_id)
-                .one(&*tx)
-                .await?
-                .ok_or_else(|| anyhow!("no such project"))?;
-            if project.host_connection()? != connection {
-                return Err(anyhow!("can't update a project hosted by someone else"))?;
-            }
-
-            // Add the newly-started language server.
-            language_server::Entity::insert(language_server::ActiveModel {
-                project_id: ActiveValue::set(project_id),
-                id: ActiveValue::set(server.id as i64),
-                name: ActiveValue::set(server.name.clone()),
-                ..Default::default()
-            })
-            .on_conflict(
-                OnConflict::columns([
-                    language_server::Column::ProjectId,
-                    language_server::Column::Id,
-                ])
-                .update_column(language_server::Column::Name)
-                .to_owned(),
-            )
-            .exec(&*tx)
-            .await?;
-
-            let connection_ids = self.project_guest_connection_ids(project_id, &tx).await?;
-            Ok(connection_ids)
-        })
-        .await
-    }
-
-    pub async fn update_worktree_settings(
-        &self,
-        update: &proto::UpdateWorktreeSettings,
-        connection: ConnectionId,
-    ) -> Result<RoomGuard<Vec<ConnectionId>>> {
-        let project_id = ProjectId::from_proto(update.project_id);
-        let room_id = self.room_id_for_project(project_id).await?;
-        self.room_transaction(room_id, |tx| async move {
-            // Ensure the update comes from the host.
-            let project = project::Entity::find_by_id(project_id)
-                .one(&*tx)
-                .await?
-                .ok_or_else(|| anyhow!("no such project"))?;
-            if project.host_connection()? != connection {
-                return Err(anyhow!("can't update a project hosted by someone else"))?;
-            }
-
-            if let Some(content) = &update.content {
-                worktree_settings_file::Entity::insert(worktree_settings_file::ActiveModel {
-                    project_id: ActiveValue::Set(project_id),
-                    worktree_id: ActiveValue::Set(update.worktree_id as i64),
-                    path: ActiveValue::Set(update.path.clone()),
-                    content: ActiveValue::Set(content.clone()),
-                })
-                .on_conflict(
-                    OnConflict::columns([
-                        worktree_settings_file::Column::ProjectId,
-                        worktree_settings_file::Column::WorktreeId,
-                        worktree_settings_file::Column::Path,
-                    ])
-                    .update_column(worktree_settings_file::Column::Content)
-                    .to_owned(),
-                )
-                .exec(&*tx)
-                .await?;
-            } else {
-                worktree_settings_file::Entity::delete(worktree_settings_file::ActiveModel {
-                    project_id: ActiveValue::Set(project_id),
-                    worktree_id: ActiveValue::Set(update.worktree_id as i64),
-                    path: ActiveValue::Set(update.path.clone()),
-                    ..Default::default()
-                })
-                .exec(&*tx)
-                .await?;
-            }
-
-            let connection_ids = self.project_guest_connection_ids(project_id, &tx).await?;
-            Ok(connection_ids)
-        })
-        .await
-    }
-
-    pub async fn join_project(
-        &self,
-        project_id: ProjectId,
-        connection: ConnectionId,
-    ) -> Result<RoomGuard<(Project, ReplicaId)>> {
-        let room_id = self.room_id_for_project(project_id).await?;
-        self.room_transaction(room_id, |tx| async move {
-            let participant = room_participant::Entity::find()
-                .filter(
-                    Condition::all()
-                        .add(
-                            room_participant::Column::AnsweringConnectionId
-                                .eq(connection.id as i32),
-                        )
-                        .add(
-                            room_participant::Column::AnsweringConnectionServerId
-                                .eq(connection.owner_id as i32),
-                        ),
-                )
-                .one(&*tx)
-                .await?
-                .ok_or_else(|| anyhow!("must join a room first"))?;
-
-            let project = project::Entity::find_by_id(project_id)
-                .one(&*tx)
-                .await?
-                .ok_or_else(|| anyhow!("no such project"))?;
-            if project.room_id != participant.room_id {
-                return Err(anyhow!("no such project"))?;
-            }
-
-            let mut collaborators = project
-                .find_related(project_collaborator::Entity)
-                .all(&*tx)
-                .await?;
-            let replica_ids = collaborators
-                .iter()
-                .map(|c| c.replica_id)
-                .collect::<HashSet<_>>();
-            let mut replica_id = ReplicaId(1);
-            while replica_ids.contains(&replica_id) {
-                replica_id.0 += 1;
-            }
-            let new_collaborator = project_collaborator::ActiveModel {
-                project_id: ActiveValue::set(project_id),
-                connection_id: ActiveValue::set(connection.id as i32),
-                connection_server_id: ActiveValue::set(ServerId(connection.owner_id as i32)),
-                user_id: ActiveValue::set(participant.user_id),
-                replica_id: ActiveValue::set(replica_id),
-                is_host: ActiveValue::set(false),
-                ..Default::default()
-            }
-            .insert(&*tx)
-            .await?;
-            collaborators.push(new_collaborator);
-
-            let db_worktrees = project.find_related(worktree::Entity).all(&*tx).await?;
-            let mut worktrees = db_worktrees
-                .into_iter()
-                .map(|db_worktree| {
-                    (
-                        db_worktree.id as u64,
-                        Worktree {
-                            id: db_worktree.id as u64,
-                            abs_path: db_worktree.abs_path,
-                            root_name: db_worktree.root_name,
-                            visible: db_worktree.visible,
-                            entries: Default::default(),
-                            repository_entries: Default::default(),
-                            diagnostic_summaries: Default::default(),
-                            settings_files: Default::default(),
-                            scan_id: db_worktree.scan_id as u64,
-                            completed_scan_id: db_worktree.completed_scan_id as u64,
-                        },
-                    )
-                })
-                .collect::<BTreeMap<_, _>>();
-
-            // Populate worktree entries.
-            {
-                let mut db_entries = worktree_entry::Entity::find()
-                    .filter(
-                        Condition::all()
-                            .add(worktree_entry::Column::ProjectId.eq(project_id))
-                            .add(worktree_entry::Column::IsDeleted.eq(false)),
-                    )
-                    .stream(&*tx)
-                    .await?;
-                while let Some(db_entry) = db_entries.next().await {
-                    let db_entry = db_entry?;
-                    if let Some(worktree) = worktrees.get_mut(&(db_entry.worktree_id as u64)) {
-                        worktree.entries.push(proto::Entry {
-                            id: db_entry.id as u64,
-                            is_dir: db_entry.is_dir,
-                            path: db_entry.path,
-                            inode: db_entry.inode as u64,
-                            mtime: Some(proto::Timestamp {
-                                seconds: db_entry.mtime_seconds as u64,
-                                nanos: db_entry.mtime_nanos as u32,
-                            }),
-                            is_symlink: db_entry.is_symlink,
-                            is_ignored: db_entry.is_ignored,
-                            is_external: db_entry.is_external,
-                            git_status: db_entry.git_status.map(|status| status as i32),
-                        });
-                    }
-                }
-            }
-
-            // Populate repository entries.
-            {
-                let mut db_repository_entries = worktree_repository::Entity::find()
-                    .filter(
-                        Condition::all()
-                            .add(worktree_repository::Column::ProjectId.eq(project_id))
-                            .add(worktree_repository::Column::IsDeleted.eq(false)),
-                    )
-                    .stream(&*tx)
-                    .await?;
-                while let Some(db_repository_entry) = db_repository_entries.next().await {
-                    let db_repository_entry = db_repository_entry?;
-                    if let Some(worktree) =
-                        worktrees.get_mut(&(db_repository_entry.worktree_id as u64))
-                    {
-                        worktree.repository_entries.insert(
-                            db_repository_entry.work_directory_id as u64,
-                            proto::RepositoryEntry {
-                                work_directory_id: db_repository_entry.work_directory_id as u64,
-                                branch: db_repository_entry.branch,
-                            },
-                        );
-                    }
-                }
-            }
-
-            // Populate worktree diagnostic summaries.
-            {
-                let mut db_summaries = worktree_diagnostic_summary::Entity::find()
-                    .filter(worktree_diagnostic_summary::Column::ProjectId.eq(project_id))
-                    .stream(&*tx)
-                    .await?;
-                while let Some(db_summary) = db_summaries.next().await {
-                    let db_summary = db_summary?;
-                    if let Some(worktree) = worktrees.get_mut(&(db_summary.worktree_id as u64)) {
-                        worktree
-                            .diagnostic_summaries
-                            .push(proto::DiagnosticSummary {
-                                path: db_summary.path,
-                                language_server_id: db_summary.language_server_id as u64,
-                                error_count: db_summary.error_count as u32,
-                                warning_count: db_summary.warning_count as u32,
-                            });
-                    }
-                }
-            }
-
-            // Populate worktree settings files
-            {
-                let mut db_settings_files = worktree_settings_file::Entity::find()
-                    .filter(worktree_settings_file::Column::ProjectId.eq(project_id))
-                    .stream(&*tx)
-                    .await?;
-                while let Some(db_settings_file) = db_settings_files.next().await {
-                    let db_settings_file = db_settings_file?;
-                    if let Some(worktree) =
-                        worktrees.get_mut(&(db_settings_file.worktree_id as u64))
-                    {
-                        worktree.settings_files.push(WorktreeSettingsFile {
-                            path: db_settings_file.path,
-                            content: db_settings_file.content,
-                        });
-                    }
-                }
-            }
-
-            // Populate language servers.
-            let language_servers = project
-                .find_related(language_server::Entity)
-                .all(&*tx)
-                .await?;
-
-            let project = Project {
-                collaborators: collaborators
-                    .into_iter()
-                    .map(|collaborator| ProjectCollaborator {
-                        connection_id: collaborator.connection(),
-                        user_id: collaborator.user_id,
-                        replica_id: collaborator.replica_id,
-                        is_host: collaborator.is_host,
-                    })
-                    .collect(),
-                worktrees,
-                language_servers: language_servers
-                    .into_iter()
-                    .map(|language_server| proto::LanguageServer {
-                        id: language_server.id as u64,
-                        name: language_server.name,
-                    })
-                    .collect(),
-            };
-            Ok((project, replica_id as ReplicaId))
-        })
-        .await
-    }
-
-    pub async fn leave_project(
-        &self,
-        project_id: ProjectId,
-        connection: ConnectionId,
-    ) -> Result<RoomGuard<(proto::Room, LeftProject)>> {
-        let room_id = self.room_id_for_project(project_id).await?;
-        self.room_transaction(room_id, |tx| async move {
-            let result = project_collaborator::Entity::delete_many()
-                .filter(
-                    Condition::all()
-                        .add(project_collaborator::Column::ProjectId.eq(project_id))
-                        .add(project_collaborator::Column::ConnectionId.eq(connection.id as i32))
-                        .add(
-                            project_collaborator::Column::ConnectionServerId
-                                .eq(connection.owner_id as i32),
-                        ),
-                )
-                .exec(&*tx)
-                .await?;
-            if result.rows_affected == 0 {
-                Err(anyhow!("not a collaborator on this project"))?;
-            }
-
-            let project = project::Entity::find_by_id(project_id)
-                .one(&*tx)
-                .await?
-                .ok_or_else(|| anyhow!("no such project"))?;
-            let collaborators = project
-                .find_related(project_collaborator::Entity)
-                .all(&*tx)
-                .await?;
-            let connection_ids = collaborators
-                .into_iter()
-                .map(|collaborator| collaborator.connection())
-                .collect();
-
-            follower::Entity::delete_many()
-                .filter(
-                    Condition::any()
-                        .add(
-                            Condition::all()
-                                .add(follower::Column::ProjectId.eq(Some(project_id)))
-                                .add(
-                                    follower::Column::LeaderConnectionServerId
-                                        .eq(connection.owner_id),
-                                )
-                                .add(follower::Column::LeaderConnectionId.eq(connection.id)),
-                        )
-                        .add(
-                            Condition::all()
-                                .add(follower::Column::ProjectId.eq(Some(project_id)))
-                                .add(
-                                    follower::Column::FollowerConnectionServerId
-                                        .eq(connection.owner_id),
-                                )
-                                .add(follower::Column::FollowerConnectionId.eq(connection.id)),
-                        ),
-                )
-                .exec(&*tx)
-                .await?;
-
-            let room = self.get_room(project.room_id, &tx).await?;
-            let left_project = LeftProject {
-                id: project_id,
-                host_user_id: project.host_user_id,
-                host_connection_id: project.host_connection()?,
-                connection_ids,
-            };
-            Ok((room, left_project))
-        })
-        .await
-    }
-
-    pub async fn project_collaborators(
-        &self,
-        project_id: ProjectId,
-        connection_id: ConnectionId,
-    ) -> Result<RoomGuard<Vec<ProjectCollaborator>>> {
-        let room_id = self.room_id_for_project(project_id).await?;
-        self.room_transaction(room_id, |tx| async move {
-            let collaborators = project_collaborator::Entity::find()
-                .filter(project_collaborator::Column::ProjectId.eq(project_id))
-                .all(&*tx)
-                .await?
-                .into_iter()
-                .map(|collaborator| ProjectCollaborator {
-                    connection_id: collaborator.connection(),
-                    user_id: collaborator.user_id,
-                    replica_id: collaborator.replica_id,
-                    is_host: collaborator.is_host,
-                })
-                .collect::<Vec<_>>();
-
-            if collaborators
-                .iter()
-                .any(|collaborator| collaborator.connection_id == connection_id)
-            {
-                Ok(collaborators)
-            } else {
-                Err(anyhow!("no such project"))?
-            }
-        })
-        .await
-    }
-
-    pub async fn project_connection_ids(
-        &self,
-        project_id: ProjectId,
-        connection_id: ConnectionId,
-    ) -> Result<RoomGuard<HashSet<ConnectionId>>> {
-        let room_id = self.room_id_for_project(project_id).await?;
-        self.room_transaction(room_id, |tx| async move {
-            let mut collaborators = project_collaborator::Entity::find()
-                .filter(project_collaborator::Column::ProjectId.eq(project_id))
-                .stream(&*tx)
-                .await?;
-
-            let mut connection_ids = HashSet::default();
-            while let Some(collaborator) = collaborators.next().await {
-                let collaborator = collaborator?;
-                connection_ids.insert(collaborator.connection());
-            }
-
-            if connection_ids.contains(&connection_id) {
-                Ok(connection_ids)
-            } else {
-                Err(anyhow!("no such project"))?
-            }
-        })
-        .await
-    }
-
-    async fn project_guest_connection_ids(
-        &self,
-        project_id: ProjectId,
-        tx: &DatabaseTransaction,
-    ) -> Result<Vec<ConnectionId>> {
-        let mut collaborators = project_collaborator::Entity::find()
-            .filter(
-                project_collaborator::Column::ProjectId
-                    .eq(project_id)
-                    .and(project_collaborator::Column::IsHost.eq(false)),
-            )
-            .stream(tx)
-            .await?;
-
-        let mut guest_connection_ids = Vec::new();
-        while let Some(collaborator) = collaborators.next().await {
-            let collaborator = collaborator?;
-            guest_connection_ids.push(collaborator.connection());
-        }
-        Ok(guest_connection_ids)
-    }
-
-    pub async fn room_id_for_project(&self, project_id: ProjectId) -> Result<RoomId> {
-        self.transaction(|tx| async move {
-            let project = project::Entity::find_by_id(project_id)
-                .one(&*tx)
-                .await?
-                .ok_or_else(|| anyhow!("project {} not found", project_id))?;
-            Ok(project.room_id)
-        })
-        .await
-    }
-
-    pub async fn check_room_participants(
-        &self,
-        room_id: RoomId,
-        leader_id: ConnectionId,
-        follower_id: ConnectionId,
-    ) -> Result<()> {
-        self.transaction(|tx| async move {
-            use room_participant::Column;
-
-            let count = room_participant::Entity::find()
-                .filter(
-                    Condition::all().add(Column::RoomId.eq(room_id)).add(
-                        Condition::any()
-                            .add(Column::AnsweringConnectionId.eq(leader_id.id as i32).and(
-                                Column::AnsweringConnectionServerId.eq(leader_id.owner_id as i32),
-                            ))
-                            .add(Column::AnsweringConnectionId.eq(follower_id.id as i32).and(
-                                Column::AnsweringConnectionServerId.eq(follower_id.owner_id as i32),
-                            )),
-                    ),
-                )
-                .count(&*tx)
-                .await?;
-
-            if count < 2 {
-                Err(anyhow!("not room participants"))?;
-            }
-
-            Ok(())
-        })
-        .await
-    }
-
-    pub async fn follow(
-        &self,
-        room_id: RoomId,
-        project_id: ProjectId,
-        leader_connection: ConnectionId,
-        follower_connection: ConnectionId,
-    ) -> Result<RoomGuard<proto::Room>> {
-        self.room_transaction(room_id, |tx| async move {
-            follower::ActiveModel {
-                room_id: ActiveValue::set(room_id),
-                project_id: ActiveValue::set(project_id),
-                leader_connection_server_id: ActiveValue::set(ServerId(
-                    leader_connection.owner_id as i32,
-                )),
-                leader_connection_id: ActiveValue::set(leader_connection.id as i32),
-                follower_connection_server_id: ActiveValue::set(ServerId(
-                    follower_connection.owner_id as i32,
-                )),
-                follower_connection_id: ActiveValue::set(follower_connection.id as i32),
-                ..Default::default()
-            }
-            .insert(&*tx)
-            .await?;
-
-            let room = self.get_room(room_id, &*tx).await?;
-            Ok(room)
-        })
-        .await
-    }
-
-    pub async fn unfollow(
-        &self,
-        room_id: RoomId,
-        project_id: ProjectId,
-        leader_connection: ConnectionId,
-        follower_connection: ConnectionId,
-    ) -> Result<RoomGuard<proto::Room>> {
-        self.room_transaction(room_id, |tx| async move {
-            follower::Entity::delete_many()
-                .filter(
-                    Condition::all()
-                        .add(follower::Column::RoomId.eq(room_id))
-                        .add(follower::Column::ProjectId.eq(project_id))
-                        .add(
-                            follower::Column::LeaderConnectionServerId
-                                .eq(leader_connection.owner_id),
-                        )
-                        .add(follower::Column::LeaderConnectionId.eq(leader_connection.id))
-                        .add(
-                            follower::Column::FollowerConnectionServerId
-                                .eq(follower_connection.owner_id),
-                        )
-                        .add(follower::Column::FollowerConnectionId.eq(follower_connection.id)),
-                )
-                .exec(&*tx)
-                .await?;
-
-            let room = self.get_room(room_id, &*tx).await?;
-            Ok(room)
-        })
-        .await
-    }
-}

crates/collab2/src/db/queries/rooms.rs 🔗

@@ -1,1203 +0,0 @@
-use super::*;
-
-impl Database {
-    pub async fn clear_stale_room_participants(
-        &self,
-        room_id: RoomId,
-        new_server_id: ServerId,
-    ) -> Result<RoomGuard<RefreshedRoom>> {
-        self.room_transaction(room_id, |tx| async move {
-            let stale_participant_filter = Condition::all()
-                .add(room_participant::Column::RoomId.eq(room_id))
-                .add(room_participant::Column::AnsweringConnectionId.is_not_null())
-                .add(room_participant::Column::AnsweringConnectionServerId.ne(new_server_id));
-
-            let stale_participant_user_ids = room_participant::Entity::find()
-                .filter(stale_participant_filter.clone())
-                .all(&*tx)
-                .await?
-                .into_iter()
-                .map(|participant| participant.user_id)
-                .collect::<Vec<_>>();
-
-            // Delete participants who failed to reconnect and cancel their calls.
-            let mut canceled_calls_to_user_ids = Vec::new();
-            room_participant::Entity::delete_many()
-                .filter(stale_participant_filter)
-                .exec(&*tx)
-                .await?;
-            let called_participants = room_participant::Entity::find()
-                .filter(
-                    Condition::all()
-                        .add(
-                            room_participant::Column::CallingUserId
-                                .is_in(stale_participant_user_ids.iter().copied()),
-                        )
-                        .add(room_participant::Column::AnsweringConnectionId.is_null()),
-                )
-                .all(&*tx)
-                .await?;
-            room_participant::Entity::delete_many()
-                .filter(
-                    room_participant::Column::Id
-                        .is_in(called_participants.iter().map(|participant| participant.id)),
-                )
-                .exec(&*tx)
-                .await?;
-            canceled_calls_to_user_ids.extend(
-                called_participants
-                    .into_iter()
-                    .map(|participant| participant.user_id),
-            );
-
-            let (channel, room) = self.get_channel_room(room_id, &tx).await?;
-            let channel_members;
-            if let Some(channel) = &channel {
-                channel_members = self.get_channel_participants(channel, &tx).await?;
-            } else {
-                channel_members = Vec::new();
-
-                // Delete the room if it becomes empty.
-                if room.participants.is_empty() {
-                    project::Entity::delete_many()
-                        .filter(project::Column::RoomId.eq(room_id))
-                        .exec(&*tx)
-                        .await?;
-                    room::Entity::delete_by_id(room_id).exec(&*tx).await?;
-                }
-            };
-
-            Ok(RefreshedRoom {
-                room,
-                channel_id: channel.map(|channel| channel.id),
-                channel_members,
-                stale_participant_user_ids,
-                canceled_calls_to_user_ids,
-            })
-        })
-        .await
-    }
-
-    pub async fn incoming_call_for_user(
-        &self,
-        user_id: UserId,
-    ) -> Result<Option<proto::IncomingCall>> {
-        self.transaction(|tx| async move {
-            let pending_participant = room_participant::Entity::find()
-                .filter(
-                    room_participant::Column::UserId
-                        .eq(user_id)
-                        .and(room_participant::Column::AnsweringConnectionId.is_null()),
-                )
-                .one(&*tx)
-                .await?;
-
-            if let Some(pending_participant) = pending_participant {
-                let room = self.get_room(pending_participant.room_id, &tx).await?;
-                Ok(Self::build_incoming_call(&room, user_id))
-            } else {
-                Ok(None)
-            }
-        })
-        .await
-    }
-
-    pub async fn create_room(
-        &self,
-        user_id: UserId,
-        connection: ConnectionId,
-        live_kit_room: &str,
-        release_channel: &str,
-    ) -> Result<proto::Room> {
-        self.transaction(|tx| async move {
-            let room = room::ActiveModel {
-                live_kit_room: ActiveValue::set(live_kit_room.into()),
-                enviroment: ActiveValue::set(Some(release_channel.to_string())),
-                ..Default::default()
-            }
-            .insert(&*tx)
-            .await?;
-            room_participant::ActiveModel {
-                room_id: ActiveValue::set(room.id),
-                user_id: ActiveValue::set(user_id),
-                answering_connection_id: ActiveValue::set(Some(connection.id as i32)),
-                answering_connection_server_id: ActiveValue::set(Some(ServerId(
-                    connection.owner_id as i32,
-                ))),
-                answering_connection_lost: ActiveValue::set(false),
-                calling_user_id: ActiveValue::set(user_id),
-                calling_connection_id: ActiveValue::set(connection.id as i32),
-                calling_connection_server_id: ActiveValue::set(Some(ServerId(
-                    connection.owner_id as i32,
-                ))),
-                participant_index: ActiveValue::set(Some(0)),
-                ..Default::default()
-            }
-            .insert(&*tx)
-            .await?;
-
-            let room = self.get_room(room.id, &tx).await?;
-            Ok(room)
-        })
-        .await
-    }
-
-    pub async fn call(
-        &self,
-        room_id: RoomId,
-        calling_user_id: UserId,
-        calling_connection: ConnectionId,
-        called_user_id: UserId,
-        initial_project_id: Option<ProjectId>,
-    ) -> Result<RoomGuard<(proto::Room, proto::IncomingCall)>> {
-        self.room_transaction(room_id, |tx| async move {
-            room_participant::ActiveModel {
-                room_id: ActiveValue::set(room_id),
-                user_id: ActiveValue::set(called_user_id),
-                answering_connection_lost: ActiveValue::set(false),
-                participant_index: ActiveValue::NotSet,
-                calling_user_id: ActiveValue::set(calling_user_id),
-                calling_connection_id: ActiveValue::set(calling_connection.id as i32),
-                calling_connection_server_id: ActiveValue::set(Some(ServerId(
-                    calling_connection.owner_id as i32,
-                ))),
-                initial_project_id: ActiveValue::set(initial_project_id),
-                ..Default::default()
-            }
-            .insert(&*tx)
-            .await?;
-
-            let room = self.get_room(room_id, &tx).await?;
-            let incoming_call = Self::build_incoming_call(&room, called_user_id)
-                .ok_or_else(|| anyhow!("failed to build incoming call"))?;
-            Ok((room, incoming_call))
-        })
-        .await
-    }
-
-    pub async fn call_failed(
-        &self,
-        room_id: RoomId,
-        called_user_id: UserId,
-    ) -> Result<RoomGuard<proto::Room>> {
-        self.room_transaction(room_id, |tx| async move {
-            room_participant::Entity::delete_many()
-                .filter(
-                    room_participant::Column::RoomId
-                        .eq(room_id)
-                        .and(room_participant::Column::UserId.eq(called_user_id)),
-                )
-                .exec(&*tx)
-                .await?;
-            let room = self.get_room(room_id, &tx).await?;
-            Ok(room)
-        })
-        .await
-    }
-
-    pub async fn decline_call(
-        &self,
-        expected_room_id: Option<RoomId>,
-        user_id: UserId,
-    ) -> Result<Option<RoomGuard<proto::Room>>> {
-        self.optional_room_transaction(|tx| async move {
-            let mut filter = Condition::all()
-                .add(room_participant::Column::UserId.eq(user_id))
-                .add(room_participant::Column::AnsweringConnectionId.is_null());
-            if let Some(room_id) = expected_room_id {
-                filter = filter.add(room_participant::Column::RoomId.eq(room_id));
-            }
-            let participant = room_participant::Entity::find()
-                .filter(filter)
-                .one(&*tx)
-                .await?;
-
-            let participant = if let Some(participant) = participant {
-                participant
-            } else if expected_room_id.is_some() {
-                return Err(anyhow!("could not find call to decline"))?;
-            } else {
-                return Ok(None);
-            };
-
-            let room_id = participant.room_id;
-            room_participant::Entity::delete(participant.into_active_model())
-                .exec(&*tx)
-                .await?;
-
-            let room = self.get_room(room_id, &tx).await?;
-            Ok(Some((room_id, room)))
-        })
-        .await
-    }
-
-    pub async fn cancel_call(
-        &self,
-        room_id: RoomId,
-        calling_connection: ConnectionId,
-        called_user_id: UserId,
-    ) -> Result<RoomGuard<proto::Room>> {
-        self.room_transaction(room_id, |tx| async move {
-            let participant = room_participant::Entity::find()
-                .filter(
-                    Condition::all()
-                        .add(room_participant::Column::UserId.eq(called_user_id))
-                        .add(room_participant::Column::RoomId.eq(room_id))
-                        .add(
-                            room_participant::Column::CallingConnectionId
-                                .eq(calling_connection.id as i32),
-                        )
-                        .add(
-                            room_participant::Column::CallingConnectionServerId
-                                .eq(calling_connection.owner_id as i32),
-                        )
-                        .add(room_participant::Column::AnsweringConnectionId.is_null()),
-                )
-                .one(&*tx)
-                .await?
-                .ok_or_else(|| anyhow!("no call to cancel"))?;
-
-            room_participant::Entity::delete(participant.into_active_model())
-                .exec(&*tx)
-                .await?;
-
-            let room = self.get_room(room_id, &tx).await?;
-            Ok(room)
-        })
-        .await
-    }
-
-    pub async fn join_room(
-        &self,
-        room_id: RoomId,
-        user_id: UserId,
-        connection: ConnectionId,
-        enviroment: &str,
-    ) -> Result<RoomGuard<JoinRoom>> {
-        self.room_transaction(room_id, |tx| async move {
-            #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
-            enum QueryChannelIdAndEnviroment {
-                ChannelId,
-                Enviroment,
-            }
-
-            let (channel_id, release_channel): (Option<ChannelId>, Option<String>) =
-                room::Entity::find()
-                    .select_only()
-                    .column(room::Column::ChannelId)
-                    .column(room::Column::Enviroment)
-                    .filter(room::Column::Id.eq(room_id))
-                    .into_values::<_, QueryChannelIdAndEnviroment>()
-                    .one(&*tx)
-                    .await?
-                    .ok_or_else(|| anyhow!("no such room"))?;
-
-            if let Some(release_channel) = release_channel {
-                if &release_channel != enviroment {
-                    Err(anyhow!("must join using the {} release", release_channel))?;
-                }
-            }
-
-            if channel_id.is_some() {
-                Err(anyhow!("tried to join channel call directly"))?
-            }
-
-            let participant_index = self
-                .get_next_participant_index_internal(room_id, &*tx)
-                .await?;
-
-            let result = room_participant::Entity::update_many()
-                .filter(
-                    Condition::all()
-                        .add(room_participant::Column::RoomId.eq(room_id))
-                        .add(room_participant::Column::UserId.eq(user_id))
-                        .add(room_participant::Column::AnsweringConnectionId.is_null()),
-                )
-                .set(room_participant::ActiveModel {
-                    participant_index: ActiveValue::Set(Some(participant_index)),
-                    answering_connection_id: ActiveValue::set(Some(connection.id as i32)),
-                    answering_connection_server_id: ActiveValue::set(Some(ServerId(
-                        connection.owner_id as i32,
-                    ))),
-                    answering_connection_lost: ActiveValue::set(false),
-                    ..Default::default()
-                })
-                .exec(&*tx)
-                .await?;
-            if result.rows_affected == 0 {
-                Err(anyhow!("room does not exist or was already joined"))?;
-            }
-
-            let room = self.get_room(room_id, &tx).await?;
-            Ok(JoinRoom {
-                room,
-                channel_id: None,
-                channel_members: vec![],
-            })
-        })
-        .await
-    }
-
-    async fn get_next_participant_index_internal(
-        &self,
-        room_id: RoomId,
-        tx: &DatabaseTransaction,
-    ) -> Result<i32> {
-        #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
-        enum QueryParticipantIndices {
-            ParticipantIndex,
-        }
-        let existing_participant_indices: Vec<i32> = room_participant::Entity::find()
-            .filter(
-                room_participant::Column::RoomId
-                    .eq(room_id)
-                    .and(room_participant::Column::ParticipantIndex.is_not_null()),
-            )
-            .select_only()
-            .column(room_participant::Column::ParticipantIndex)
-            .into_values::<_, QueryParticipantIndices>()
-            .all(&*tx)
-            .await?;
-
-        let mut participant_index = 0;
-        while existing_participant_indices.contains(&participant_index) {
-            participant_index += 1;
-        }
-
-        Ok(participant_index)
-    }
-
-    pub async fn channel_id_for_room(&self, room_id: RoomId) -> Result<Option<ChannelId>> {
-        self.transaction(|tx| async move {
-            let room: Option<room::Model> = room::Entity::find()
-                .filter(room::Column::Id.eq(room_id))
-                .one(&*tx)
-                .await?;
-
-            Ok(room.and_then(|room| room.channel_id))
-        })
-        .await
-    }
-
-    pub(crate) async fn join_channel_room_internal(
-        &self,
-        room_id: RoomId,
-        user_id: UserId,
-        connection: ConnectionId,
-        tx: &DatabaseTransaction,
-    ) -> Result<JoinRoom> {
-        let participant_index = self
-            .get_next_participant_index_internal(room_id, &*tx)
-            .await?;
-
-        room_participant::Entity::insert_many([room_participant::ActiveModel {
-            room_id: ActiveValue::set(room_id),
-            user_id: ActiveValue::set(user_id),
-            answering_connection_id: ActiveValue::set(Some(connection.id as i32)),
-            answering_connection_server_id: ActiveValue::set(Some(ServerId(
-                connection.owner_id as i32,
-            ))),
-            answering_connection_lost: ActiveValue::set(false),
-            calling_user_id: ActiveValue::set(user_id),
-            calling_connection_id: ActiveValue::set(connection.id as i32),
-            calling_connection_server_id: ActiveValue::set(Some(ServerId(
-                connection.owner_id as i32,
-            ))),
-            participant_index: ActiveValue::Set(Some(participant_index)),
-            ..Default::default()
-        }])
-        .on_conflict(
-            OnConflict::columns([room_participant::Column::UserId])
-                .update_columns([
-                    room_participant::Column::AnsweringConnectionId,
-                    room_participant::Column::AnsweringConnectionServerId,
-                    room_participant::Column::AnsweringConnectionLost,
-                    room_participant::Column::ParticipantIndex,
-                ])
-                .to_owned(),
-        )
-        .exec(&*tx)
-        .await?;
-
-        let (channel, room) = self.get_channel_room(room_id, &tx).await?;
-        let channel = channel.ok_or_else(|| anyhow!("no channel for room"))?;
-        let channel_members = self.get_channel_participants(&channel, &*tx).await?;
-        Ok(JoinRoom {
-            room,
-            channel_id: Some(channel.id),
-            channel_members,
-        })
-    }
-
-    pub async fn rejoin_room(
-        &self,
-        rejoin_room: proto::RejoinRoom,
-        user_id: UserId,
-        connection: ConnectionId,
-    ) -> Result<RoomGuard<RejoinedRoom>> {
-        let room_id = RoomId::from_proto(rejoin_room.id);
-        self.room_transaction(room_id, |tx| async {
-            let tx = tx;
-            let participant_update = room_participant::Entity::update_many()
-                .filter(
-                    Condition::all()
-                        .add(room_participant::Column::RoomId.eq(room_id))
-                        .add(room_participant::Column::UserId.eq(user_id))
-                        .add(room_participant::Column::AnsweringConnectionId.is_not_null())
-                        .add(
-                            Condition::any()
-                                .add(room_participant::Column::AnsweringConnectionLost.eq(true))
-                                .add(
-                                    room_participant::Column::AnsweringConnectionServerId
-                                        .ne(connection.owner_id as i32),
-                                ),
-                        ),
-                )
-                .set(room_participant::ActiveModel {
-                    answering_connection_id: ActiveValue::set(Some(connection.id as i32)),
-                    answering_connection_server_id: ActiveValue::set(Some(ServerId(
-                        connection.owner_id as i32,
-                    ))),
-                    answering_connection_lost: ActiveValue::set(false),
-                    ..Default::default()
-                })
-                .exec(&*tx)
-                .await?;
-            if participant_update.rows_affected == 0 {
-                return Err(anyhow!("room does not exist or was already joined"))?;
-            }
-
-            let mut reshared_projects = Vec::new();
-            for reshared_project in &rejoin_room.reshared_projects {
-                let project_id = ProjectId::from_proto(reshared_project.project_id);
-                let project = project::Entity::find_by_id(project_id)
-                    .one(&*tx)
-                    .await?
-                    .ok_or_else(|| anyhow!("project does not exist"))?;
-                if project.host_user_id != user_id {
-                    return Err(anyhow!("no such project"))?;
-                }
-
-                let mut collaborators = project
-                    .find_related(project_collaborator::Entity)
-                    .all(&*tx)
-                    .await?;
-                let host_ix = collaborators
-                    .iter()
-                    .position(|collaborator| {
-                        collaborator.user_id == user_id && collaborator.is_host
-                    })
-                    .ok_or_else(|| anyhow!("host not found among collaborators"))?;
-                let host = collaborators.swap_remove(host_ix);
-                let old_connection_id = host.connection();
-
-                project::Entity::update(project::ActiveModel {
-                    host_connection_id: ActiveValue::set(Some(connection.id as i32)),
-                    host_connection_server_id: ActiveValue::set(Some(ServerId(
-                        connection.owner_id as i32,
-                    ))),
-                    ..project.into_active_model()
-                })
-                .exec(&*tx)
-                .await?;
-                project_collaborator::Entity::update(project_collaborator::ActiveModel {
-                    connection_id: ActiveValue::set(connection.id as i32),
-                    connection_server_id: ActiveValue::set(ServerId(connection.owner_id as i32)),
-                    ..host.into_active_model()
-                })
-                .exec(&*tx)
-                .await?;
-
-                self.update_project_worktrees(project_id, &reshared_project.worktrees, &tx)
-                    .await?;
-
-                reshared_projects.push(ResharedProject {
-                    id: project_id,
-                    old_connection_id,
-                    collaborators: collaborators
-                        .iter()
-                        .map(|collaborator| ProjectCollaborator {
-                            connection_id: collaborator.connection(),
-                            user_id: collaborator.user_id,
-                            replica_id: collaborator.replica_id,
-                            is_host: collaborator.is_host,
-                        })
-                        .collect(),
-                    worktrees: reshared_project.worktrees.clone(),
-                });
-            }
-
-            project::Entity::delete_many()
-                .filter(
-                    Condition::all()
-                        .add(project::Column::RoomId.eq(room_id))
-                        .add(project::Column::HostUserId.eq(user_id))
-                        .add(
-                            project::Column::Id
-                                .is_not_in(reshared_projects.iter().map(|project| project.id)),
-                        ),
-                )
-                .exec(&*tx)
-                .await?;
-
-            let mut rejoined_projects = Vec::new();
-            for rejoined_project in &rejoin_room.rejoined_projects {
-                let project_id = ProjectId::from_proto(rejoined_project.id);
-                let Some(project) = project::Entity::find_by_id(project_id).one(&*tx).await? else {
-                    continue;
-                };
-
-                let mut worktrees = Vec::new();
-                let db_worktrees = project.find_related(worktree::Entity).all(&*tx).await?;
-                for db_worktree in db_worktrees {
-                    let mut worktree = RejoinedWorktree {
-                        id: db_worktree.id as u64,
-                        abs_path: db_worktree.abs_path,
-                        root_name: db_worktree.root_name,
-                        visible: db_worktree.visible,
-                        updated_entries: Default::default(),
-                        removed_entries: Default::default(),
-                        updated_repositories: Default::default(),
-                        removed_repositories: Default::default(),
-                        diagnostic_summaries: Default::default(),
-                        settings_files: Default::default(),
-                        scan_id: db_worktree.scan_id as u64,
-                        completed_scan_id: db_worktree.completed_scan_id as u64,
-                    };
-
-                    let rejoined_worktree = rejoined_project
-                        .worktrees
-                        .iter()
-                        .find(|worktree| worktree.id == db_worktree.id as u64);
-
-                    // File entries
-                    {
-                        let entry_filter = if let Some(rejoined_worktree) = rejoined_worktree {
-                            worktree_entry::Column::ScanId.gt(rejoined_worktree.scan_id)
-                        } else {
-                            worktree_entry::Column::IsDeleted.eq(false)
-                        };
-
-                        let mut db_entries = worktree_entry::Entity::find()
-                            .filter(
-                                Condition::all()
-                                    .add(worktree_entry::Column::ProjectId.eq(project.id))
-                                    .add(worktree_entry::Column::WorktreeId.eq(worktree.id))
-                                    .add(entry_filter),
-                            )
-                            .stream(&*tx)
-                            .await?;
-
-                        while let Some(db_entry) = db_entries.next().await {
-                            let db_entry = db_entry?;
-                            if db_entry.is_deleted {
-                                worktree.removed_entries.push(db_entry.id as u64);
-                            } else {
-                                worktree.updated_entries.push(proto::Entry {
-                                    id: db_entry.id as u64,
-                                    is_dir: db_entry.is_dir,
-                                    path: db_entry.path,
-                                    inode: db_entry.inode as u64,
-                                    mtime: Some(proto::Timestamp {
-                                        seconds: db_entry.mtime_seconds as u64,
-                                        nanos: db_entry.mtime_nanos as u32,
-                                    }),
-                                    is_symlink: db_entry.is_symlink,
-                                    is_ignored: db_entry.is_ignored,
-                                    is_external: db_entry.is_external,
-                                    git_status: db_entry.git_status.map(|status| status as i32),
-                                });
-                            }
-                        }
-                    }
-
-                    // Repository Entries
-                    {
-                        let repository_entry_filter =
-                            if let Some(rejoined_worktree) = rejoined_worktree {
-                                worktree_repository::Column::ScanId.gt(rejoined_worktree.scan_id)
-                            } else {
-                                worktree_repository::Column::IsDeleted.eq(false)
-                            };
-
-                        let mut db_repositories = worktree_repository::Entity::find()
-                            .filter(
-                                Condition::all()
-                                    .add(worktree_repository::Column::ProjectId.eq(project.id))
-                                    .add(worktree_repository::Column::WorktreeId.eq(worktree.id))
-                                    .add(repository_entry_filter),
-                            )
-                            .stream(&*tx)
-                            .await?;
-
-                        while let Some(db_repository) = db_repositories.next().await {
-                            let db_repository = db_repository?;
-                            if db_repository.is_deleted {
-                                worktree
-                                    .removed_repositories
-                                    .push(db_repository.work_directory_id as u64);
-                            } else {
-                                worktree.updated_repositories.push(proto::RepositoryEntry {
-                                    work_directory_id: db_repository.work_directory_id as u64,
-                                    branch: db_repository.branch,
-                                });
-                            }
-                        }
-                    }
-
-                    worktrees.push(worktree);
-                }
-
-                let language_servers = project
-                    .find_related(language_server::Entity)
-                    .all(&*tx)
-                    .await?
-                    .into_iter()
-                    .map(|language_server| proto::LanguageServer {
-                        id: language_server.id as u64,
-                        name: language_server.name,
-                    })
-                    .collect::<Vec<_>>();
-
-                {
-                    let mut db_settings_files = worktree_settings_file::Entity::find()
-                        .filter(worktree_settings_file::Column::ProjectId.eq(project_id))
-                        .stream(&*tx)
-                        .await?;
-                    while let Some(db_settings_file) = db_settings_files.next().await {
-                        let db_settings_file = db_settings_file?;
-                        if let Some(worktree) = worktrees
-                            .iter_mut()
-                            .find(|w| w.id == db_settings_file.worktree_id as u64)
-                        {
-                            worktree.settings_files.push(WorktreeSettingsFile {
-                                path: db_settings_file.path,
-                                content: db_settings_file.content,
-                            });
-                        }
-                    }
-                }
-
-                let mut collaborators = project
-                    .find_related(project_collaborator::Entity)
-                    .all(&*tx)
-                    .await?;
-                let self_collaborator = if let Some(self_collaborator_ix) = collaborators
-                    .iter()
-                    .position(|collaborator| collaborator.user_id == user_id)
-                {
-                    collaborators.swap_remove(self_collaborator_ix)
-                } else {
-                    continue;
-                };
-                let old_connection_id = self_collaborator.connection();
-                project_collaborator::Entity::update(project_collaborator::ActiveModel {
-                    connection_id: ActiveValue::set(connection.id as i32),
-                    connection_server_id: ActiveValue::set(ServerId(connection.owner_id as i32)),
-                    ..self_collaborator.into_active_model()
-                })
-                .exec(&*tx)
-                .await?;
-
-                let collaborators = collaborators
-                    .into_iter()
-                    .map(|collaborator| ProjectCollaborator {
-                        connection_id: collaborator.connection(),
-                        user_id: collaborator.user_id,
-                        replica_id: collaborator.replica_id,
-                        is_host: collaborator.is_host,
-                    })
-                    .collect::<Vec<_>>();
-
-                rejoined_projects.push(RejoinedProject {
-                    id: project_id,
-                    old_connection_id,
-                    collaborators,
-                    worktrees,
-                    language_servers,
-                });
-            }
-
-            let (channel, room) = self.get_channel_room(room_id, &tx).await?;
-            let channel_members = if let Some(channel) = &channel {
-                self.get_channel_participants(&channel, &tx).await?
-            } else {
-                Vec::new()
-            };
-
-            Ok(RejoinedRoom {
-                room,
-                channel_id: channel.map(|channel| channel.id),
-                channel_members,
-                rejoined_projects,
-                reshared_projects,
-            })
-        })
-        .await
-    }
-
-    pub async fn leave_room(
-        &self,
-        connection: ConnectionId,
-    ) -> Result<Option<RoomGuard<LeftRoom>>> {
-        self.optional_room_transaction(|tx| async move {
-            let leaving_participant = room_participant::Entity::find()
-                .filter(
-                    Condition::all()
-                        .add(
-                            room_participant::Column::AnsweringConnectionId
-                                .eq(connection.id as i32),
-                        )
-                        .add(
-                            room_participant::Column::AnsweringConnectionServerId
-                                .eq(connection.owner_id as i32),
-                        ),
-                )
-                .one(&*tx)
-                .await?;
-
-            if let Some(leaving_participant) = leaving_participant {
-                // Leave room.
-                let room_id = leaving_participant.room_id;
-                room_participant::Entity::delete_by_id(leaving_participant.id)
-                    .exec(&*tx)
-                    .await?;
-
-                // Cancel pending calls initiated by the leaving user.
-                let called_participants = room_participant::Entity::find()
-                    .filter(
-                        Condition::all()
-                            .add(
-                                room_participant::Column::CallingUserId
-                                    .eq(leaving_participant.user_id),
-                            )
-                            .add(room_participant::Column::AnsweringConnectionId.is_null()),
-                    )
-                    .all(&*tx)
-                    .await?;
-                room_participant::Entity::delete_many()
-                    .filter(
-                        room_participant::Column::Id
-                            .is_in(called_participants.iter().map(|participant| participant.id)),
-                    )
-                    .exec(&*tx)
-                    .await?;
-                let canceled_calls_to_user_ids = called_participants
-                    .into_iter()
-                    .map(|participant| participant.user_id)
-                    .collect();
-
-                // Detect left projects.
-                #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
-                enum QueryProjectIds {
-                    ProjectId,
-                }
-                let project_ids: Vec<ProjectId> = project_collaborator::Entity::find()
-                    .select_only()
-                    .column_as(
-                        project_collaborator::Column::ProjectId,
-                        QueryProjectIds::ProjectId,
-                    )
-                    .filter(
-                        Condition::all()
-                            .add(
-                                project_collaborator::Column::ConnectionId.eq(connection.id as i32),
-                            )
-                            .add(
-                                project_collaborator::Column::ConnectionServerId
-                                    .eq(connection.owner_id as i32),
-                            ),
-                    )
-                    .into_values::<_, QueryProjectIds>()
-                    .all(&*tx)
-                    .await?;
-                let mut left_projects = HashMap::default();
-                let mut collaborators = project_collaborator::Entity::find()
-                    .filter(project_collaborator::Column::ProjectId.is_in(project_ids))
-                    .stream(&*tx)
-                    .await?;
-                while let Some(collaborator) = collaborators.next().await {
-                    let collaborator = collaborator?;
-                    let left_project =
-                        left_projects
-                            .entry(collaborator.project_id)
-                            .or_insert(LeftProject {
-                                id: collaborator.project_id,
-                                host_user_id: Default::default(),
-                                connection_ids: Default::default(),
-                                host_connection_id: Default::default(),
-                            });
-
-                    let collaborator_connection_id = collaborator.connection();
-                    if collaborator_connection_id != connection {
-                        left_project.connection_ids.push(collaborator_connection_id);
-                    }
-
-                    if collaborator.is_host {
-                        left_project.host_user_id = collaborator.user_id;
-                        left_project.host_connection_id = collaborator_connection_id;
-                    }
-                }
-                drop(collaborators);
-
-                // Leave projects.
-                project_collaborator::Entity::delete_many()
-                    .filter(
-                        Condition::all()
-                            .add(
-                                project_collaborator::Column::ConnectionId.eq(connection.id as i32),
-                            )
-                            .add(
-                                project_collaborator::Column::ConnectionServerId
-                                    .eq(connection.owner_id as i32),
-                            ),
-                    )
-                    .exec(&*tx)
-                    .await?;
-
-                // Unshare projects.
-                project::Entity::delete_many()
-                    .filter(
-                        Condition::all()
-                            .add(project::Column::RoomId.eq(room_id))
-                            .add(project::Column::HostConnectionId.eq(connection.id as i32))
-                            .add(
-                                project::Column::HostConnectionServerId
-                                    .eq(connection.owner_id as i32),
-                            ),
-                    )
-                    .exec(&*tx)
-                    .await?;
-
-                let (channel, room) = self.get_channel_room(room_id, &tx).await?;
-                let deleted = if room.participants.is_empty() {
-                    let result = room::Entity::delete_by_id(room_id).exec(&*tx).await?;
-                    result.rows_affected > 0
-                } else {
-                    false
-                };
-
-                let channel_members = if let Some(channel) = &channel {
-                    self.get_channel_participants(channel, &tx).await?
-                } else {
-                    Vec::new()
-                };
-                let left_room = LeftRoom {
-                    room,
-                    channel_id: channel.map(|channel| channel.id),
-                    channel_members,
-                    left_projects,
-                    canceled_calls_to_user_ids,
-                    deleted,
-                };
-
-                if left_room.room.participants.is_empty() {
-                    self.rooms.remove(&room_id);
-                }
-
-                Ok(Some((room_id, left_room)))
-            } else {
-                Ok(None)
-            }
-        })
-        .await
-    }
-
-    pub async fn update_room_participant_location(
-        &self,
-        room_id: RoomId,
-        connection: ConnectionId,
-        location: proto::ParticipantLocation,
-    ) -> Result<RoomGuard<proto::Room>> {
-        self.room_transaction(room_id, |tx| async {
-            let tx = tx;
-            let location_kind;
-            let location_project_id;
-            match location
-                .variant
-                .as_ref()
-                .ok_or_else(|| anyhow!("invalid location"))?
-            {
-                proto::participant_location::Variant::SharedProject(project) => {
-                    location_kind = 0;
-                    location_project_id = Some(ProjectId::from_proto(project.id));
-                }
-                proto::participant_location::Variant::UnsharedProject(_) => {
-                    location_kind = 1;
-                    location_project_id = None;
-                }
-                proto::participant_location::Variant::External(_) => {
-                    location_kind = 2;
-                    location_project_id = None;
-                }
-            }
-
-            let result = room_participant::Entity::update_many()
-                .filter(
-                    Condition::all()
-                        .add(room_participant::Column::RoomId.eq(room_id))
-                        .add(
-                            room_participant::Column::AnsweringConnectionId
-                                .eq(connection.id as i32),
-                        )
-                        .add(
-                            room_participant::Column::AnsweringConnectionServerId
-                                .eq(connection.owner_id as i32),
-                        ),
-                )
-                .set(room_participant::ActiveModel {
-                    location_kind: ActiveValue::set(Some(location_kind)),
-                    location_project_id: ActiveValue::set(location_project_id),
-                    ..Default::default()
-                })
-                .exec(&*tx)
-                .await?;
-
-            if result.rows_affected == 1 {
-                let room = self.get_room(room_id, &tx).await?;
-                Ok(room)
-            } else {
-                Err(anyhow!("could not update room participant location"))?
-            }
-        })
-        .await
-    }
-
-    pub async fn connection_lost(&self, connection: ConnectionId) -> Result<()> {
-        self.transaction(|tx| async move {
-            self.room_connection_lost(connection, &*tx).await?;
-            self.channel_buffer_connection_lost(connection, &*tx)
-                .await?;
-            self.channel_chat_connection_lost(connection, &*tx).await?;
-            Ok(())
-        })
-        .await
-    }
-
-    pub async fn room_connection_lost(
-        &self,
-        connection: ConnectionId,
-        tx: &DatabaseTransaction,
-    ) -> Result<()> {
-        let participant = room_participant::Entity::find()
-            .filter(
-                Condition::all()
-                    .add(room_participant::Column::AnsweringConnectionId.eq(connection.id as i32))
-                    .add(
-                        room_participant::Column::AnsweringConnectionServerId
-                            .eq(connection.owner_id as i32),
-                    ),
-            )
-            .one(&*tx)
-            .await?;
-
-        if let Some(participant) = participant {
-            room_participant::Entity::update(room_participant::ActiveModel {
-                answering_connection_lost: ActiveValue::set(true),
-                ..participant.into_active_model()
-            })
-            .exec(&*tx)
-            .await?;
-        }
-        Ok(())
-    }
-
-    fn build_incoming_call(
-        room: &proto::Room,
-        called_user_id: UserId,
-    ) -> Option<proto::IncomingCall> {
-        let pending_participant = room
-            .pending_participants
-            .iter()
-            .find(|participant| participant.user_id == called_user_id.to_proto())?;
-
-        Some(proto::IncomingCall {
-            room_id: room.id,
-            calling_user_id: pending_participant.calling_user_id,
-            participant_user_ids: room
-                .participants
-                .iter()
-                .map(|participant| participant.user_id)
-                .collect(),
-            initial_project: room.participants.iter().find_map(|participant| {
-                let initial_project_id = pending_participant.initial_project_id?;
-                participant
-                    .projects
-                    .iter()
-                    .find(|project| project.id == initial_project_id)
-                    .cloned()
-            }),
-        })
-    }
-
-    pub async fn get_room(&self, room_id: RoomId, tx: &DatabaseTransaction) -> Result<proto::Room> {
-        let (_, room) = self.get_channel_room(room_id, tx).await?;
-        Ok(room)
-    }
-
-    pub async fn room_connection_ids(
-        &self,
-        room_id: RoomId,
-        connection_id: ConnectionId,
-    ) -> Result<RoomGuard<HashSet<ConnectionId>>> {
-        self.room_transaction(room_id, |tx| async move {
-            let mut participants = room_participant::Entity::find()
-                .filter(room_participant::Column::RoomId.eq(room_id))
-                .stream(&*tx)
-                .await?;
-
-            let mut is_participant = false;
-            let mut connection_ids = HashSet::default();
-            while let Some(participant) = participants.next().await {
-                let participant = participant?;
-                if let Some(answering_connection) = participant.answering_connection() {
-                    if answering_connection == connection_id {
-                        is_participant = true;
-                    } else {
-                        connection_ids.insert(answering_connection);
-                    }
-                }
-            }
-
-            if !is_participant {
-                Err(anyhow!("not a room participant"))?;
-            }
-
-            Ok(connection_ids)
-        })
-        .await
-    }
-
-    async fn get_channel_room(
-        &self,
-        room_id: RoomId,
-        tx: &DatabaseTransaction,
-    ) -> Result<(Option<channel::Model>, proto::Room)> {
-        let db_room = room::Entity::find_by_id(room_id)
-            .one(tx)
-            .await?
-            .ok_or_else(|| anyhow!("could not find room"))?;
-
-        let mut db_participants = db_room
-            .find_related(room_participant::Entity)
-            .stream(tx)
-            .await?;
-        let mut participants = HashMap::default();
-        let mut pending_participants = Vec::new();
-        while let Some(db_participant) = db_participants.next().await {
-            let db_participant = db_participant?;
-            if let (
-                Some(answering_connection_id),
-                Some(answering_connection_server_id),
-                Some(participant_index),
-            ) = (
-                db_participant.answering_connection_id,
-                db_participant.answering_connection_server_id,
-                db_participant.participant_index,
-            ) {
-                let location = match (
-                    db_participant.location_kind,
-                    db_participant.location_project_id,
-                ) {
-                    (Some(0), Some(project_id)) => {
-                        Some(proto::participant_location::Variant::SharedProject(
-                            proto::participant_location::SharedProject {
-                                id: project_id.to_proto(),
-                            },
-                        ))
-                    }
-                    (Some(1), _) => Some(proto::participant_location::Variant::UnsharedProject(
-                        Default::default(),
-                    )),
-                    _ => Some(proto::participant_location::Variant::External(
-                        Default::default(),
-                    )),
-                };
-
-                let answering_connection = ConnectionId {
-                    owner_id: answering_connection_server_id.0 as u32,
-                    id: answering_connection_id as u32,
-                };
-                participants.insert(
-                    answering_connection,
-                    proto::Participant {
-                        user_id: db_participant.user_id.to_proto(),
-                        peer_id: Some(answering_connection.into()),
-                        projects: Default::default(),
-                        location: Some(proto::ParticipantLocation { variant: location }),
-                        participant_index: participant_index as u32,
-                    },
-                );
-            } else {
-                pending_participants.push(proto::PendingParticipant {
-                    user_id: db_participant.user_id.to_proto(),
-                    calling_user_id: db_participant.calling_user_id.to_proto(),
-                    initial_project_id: db_participant.initial_project_id.map(|id| id.to_proto()),
-                });
-            }
-        }
-        drop(db_participants);
-
-        let mut db_projects = db_room
-            .find_related(project::Entity)
-            .find_with_related(worktree::Entity)
-            .stream(tx)
-            .await?;
-
-        while let Some(row) = db_projects.next().await {
-            let (db_project, db_worktree) = row?;
-            let host_connection = db_project.host_connection()?;
-            if let Some(participant) = participants.get_mut(&host_connection) {
-                let project = if let Some(project) = participant
-                    .projects
-                    .iter_mut()
-                    .find(|project| project.id == db_project.id.to_proto())
-                {
-                    project
-                } else {
-                    participant.projects.push(proto::ParticipantProject {
-                        id: db_project.id.to_proto(),
-                        worktree_root_names: Default::default(),
-                    });
-                    participant.projects.last_mut().unwrap()
-                };
-
-                if let Some(db_worktree) = db_worktree {
-                    if db_worktree.visible {
-                        project.worktree_root_names.push(db_worktree.root_name);
-                    }
-                }
-            }
-        }
-        drop(db_projects);
-
-        let mut db_followers = db_room.find_related(follower::Entity).stream(tx).await?;
-        let mut followers = Vec::new();
-        while let Some(db_follower) = db_followers.next().await {
-            let db_follower = db_follower?;
-            followers.push(proto::Follower {
-                leader_id: Some(db_follower.leader_connection().into()),
-                follower_id: Some(db_follower.follower_connection().into()),
-                project_id: db_follower.project_id.to_proto(),
-            });
-        }
-        drop(db_followers);
-
-        let channel = if let Some(channel_id) = db_room.channel_id {
-            Some(self.get_channel_internal(channel_id, &*tx).await?)
-        } else {
-            None
-        };
-
-        Ok((
-            channel,
-            proto::Room {
-                id: db_room.id.to_proto(),
-                live_kit_room: db_room.live_kit_room,
-                participants: participants.into_values().collect(),
-                pending_participants,
-                followers,
-            },
-        ))
-    }
-}

crates/collab2/src/db/queries/servers.rs 🔗

@@ -1,99 +0,0 @@
-use super::*;
-
-impl Database {
-    pub async fn create_server(&self, environment: &str) -> Result<ServerId> {
-        self.transaction(|tx| async move {
-            let server = server::ActiveModel {
-                environment: ActiveValue::set(environment.into()),
-                ..Default::default()
-            }
-            .insert(&*tx)
-            .await?;
-            Ok(server.id)
-        })
-        .await
-    }
-
-    pub async fn stale_server_resource_ids(
-        &self,
-        environment: &str,
-        new_server_id: ServerId,
-    ) -> Result<(Vec<RoomId>, Vec<ChannelId>)> {
-        self.transaction(|tx| async move {
-            #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
-            enum QueryRoomIds {
-                RoomId,
-            }
-
-            #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
-            enum QueryChannelIds {
-                ChannelId,
-            }
-
-            let stale_server_epochs = self
-                .stale_server_ids(environment, new_server_id, &tx)
-                .await?;
-            let room_ids = room_participant::Entity::find()
-                .select_only()
-                .column(room_participant::Column::RoomId)
-                .distinct()
-                .filter(
-                    room_participant::Column::AnsweringConnectionServerId
-                        .is_in(stale_server_epochs.iter().copied()),
-                )
-                .into_values::<_, QueryRoomIds>()
-                .all(&*tx)
-                .await?;
-            let channel_ids = channel_buffer_collaborator::Entity::find()
-                .select_only()
-                .column(channel_buffer_collaborator::Column::ChannelId)
-                .distinct()
-                .filter(
-                    channel_buffer_collaborator::Column::ConnectionServerId
-                        .is_in(stale_server_epochs.iter().copied()),
-                )
-                .into_values::<_, QueryChannelIds>()
-                .all(&*tx)
-                .await?;
-
-            Ok((room_ids, channel_ids))
-        })
-        .await
-    }
-
-    pub async fn delete_stale_servers(
-        &self,
-        environment: &str,
-        new_server_id: ServerId,
-    ) -> Result<()> {
-        self.transaction(|tx| async move {
-            server::Entity::delete_many()
-                .filter(
-                    Condition::all()
-                        .add(server::Column::Environment.eq(environment))
-                        .add(server::Column::Id.ne(new_server_id)),
-                )
-                .exec(&*tx)
-                .await?;
-            Ok(())
-        })
-        .await
-    }
-
-    async fn stale_server_ids(
-        &self,
-        environment: &str,
-        new_server_id: ServerId,
-        tx: &DatabaseTransaction,
-    ) -> Result<Vec<ServerId>> {
-        let stale_servers = server::Entity::find()
-            .filter(
-                Condition::all()
-                    .add(server::Column::Environment.eq(environment))
-                    .add(server::Column::Id.ne(new_server_id)),
-            )
-            .all(&*tx)
-            .await?;
-        Ok(stale_servers.into_iter().map(|server| server.id).collect())
-    }
-}

crates/collab2/src/db/queries/users.rs 🔗

@@ -1,259 +0,0 @@
-use super::*;
-
-impl Database {
-    pub async fn create_user(
-        &self,
-        email_address: &str,
-        admin: bool,
-        params: NewUserParams,
-    ) -> Result<NewUserResult> {
-        self.transaction(|tx| async {
-            let tx = tx;
-            let user = user::Entity::insert(user::ActiveModel {
-                email_address: ActiveValue::set(Some(email_address.into())),
-                github_login: ActiveValue::set(params.github_login.clone()),
-                github_user_id: ActiveValue::set(Some(params.github_user_id)),
-                admin: ActiveValue::set(admin),
-                metrics_id: ActiveValue::set(Uuid::new_v4()),
-                ..Default::default()
-            })
-            .on_conflict(
-                OnConflict::column(user::Column::GithubLogin)
-                    .update_column(user::Column::GithubLogin)
-                    .to_owned(),
-            )
-            .exec_with_returning(&*tx)
-            .await?;
-
-            Ok(NewUserResult {
-                user_id: user.id,
-                metrics_id: user.metrics_id.to_string(),
-                signup_device_id: None,
-                inviting_user_id: None,
-            })
-        })
-        .await
-    }
-
-    pub async fn get_user_by_id(&self, id: UserId) -> Result<Option<user::Model>> {
-        self.transaction(|tx| async move { Ok(user::Entity::find_by_id(id).one(&*tx).await?) })
-            .await
-    }
-
-    pub async fn get_users_by_ids(&self, ids: Vec<UserId>) -> Result<Vec<user::Model>> {
-        self.transaction(|tx| async {
-            let tx = tx;
-            Ok(user::Entity::find()
-                .filter(user::Column::Id.is_in(ids.iter().copied()))
-                .all(&*tx)
-                .await?)
-        })
-        .await
-    }
-
-    pub async fn get_user_by_github_login(&self, github_login: &str) -> Result<Option<User>> {
-        self.transaction(|tx| async move {
-            Ok(user::Entity::find()
-                .filter(user::Column::GithubLogin.eq(github_login))
-                .one(&*tx)
-                .await?)
-        })
-        .await
-    }
-
-    pub async fn get_or_create_user_by_github_account(
-        &self,
-        github_login: &str,
-        github_user_id: Option<i32>,
-        github_email: Option<&str>,
-    ) -> Result<Option<User>> {
-        self.transaction(|tx| async move {
-            let tx = &*tx;
-            if let Some(github_user_id) = github_user_id {
-                if let Some(user_by_github_user_id) = user::Entity::find()
-                    .filter(user::Column::GithubUserId.eq(github_user_id))
-                    .one(tx)
-                    .await?
-                {
-                    let mut user_by_github_user_id = user_by_github_user_id.into_active_model();
-                    user_by_github_user_id.github_login = ActiveValue::set(github_login.into());
-                    Ok(Some(user_by_github_user_id.update(tx).await?))
-                } else if let Some(user_by_github_login) = user::Entity::find()
-                    .filter(user::Column::GithubLogin.eq(github_login))
-                    .one(tx)
-                    .await?
-                {
-                    let mut user_by_github_login = user_by_github_login.into_active_model();
-                    user_by_github_login.github_user_id = ActiveValue::set(Some(github_user_id));
-                    Ok(Some(user_by_github_login.update(tx).await?))
-                } else {
-                    let user = user::Entity::insert(user::ActiveModel {
-                        email_address: ActiveValue::set(github_email.map(|email| email.into())),
-                        github_login: ActiveValue::set(github_login.into()),
-                        github_user_id: ActiveValue::set(Some(github_user_id)),
-                        admin: ActiveValue::set(false),
-                        invite_count: ActiveValue::set(0),
-                        invite_code: ActiveValue::set(None),
-                        metrics_id: ActiveValue::set(Uuid::new_v4()),
-                        ..Default::default()
-                    })
-                    .exec_with_returning(&*tx)
-                    .await?;
-                    Ok(Some(user))
-                }
-            } else {
-                Ok(user::Entity::find()
-                    .filter(user::Column::GithubLogin.eq(github_login))
-                    .one(tx)
-                    .await?)
-            }
-        })
-        .await
-    }
-
-    pub async fn get_all_users(&self, page: u32, limit: u32) -> Result<Vec<User>> {
-        self.transaction(|tx| async move {
-            Ok(user::Entity::find()
-                .order_by_asc(user::Column::GithubLogin)
-                .limit(limit as u64)
-                .offset(page as u64 * limit as u64)
-                .all(&*tx)
-                .await?)
-        })
-        .await
-    }
-
-    pub async fn get_user_metrics_id(&self, id: UserId) -> Result<String> {
-        #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
-        enum QueryAs {
-            MetricsId,
-        }
-
-        self.transaction(|tx| async move {
-            let metrics_id: Uuid = user::Entity::find_by_id(id)
-                .select_only()
-                .column(user::Column::MetricsId)
-                .into_values::<_, QueryAs>()
-                .one(&*tx)
-                .await?
-                .ok_or_else(|| anyhow!("could not find user"))?;
-            Ok(metrics_id.to_string())
-        })
-        .await
-    }
-
-    pub async fn set_user_connected_once(&self, id: UserId, connected_once: bool) -> Result<()> {
-        self.transaction(|tx| async move {
-            user::Entity::update_many()
-                .filter(user::Column::Id.eq(id))
-                .set(user::ActiveModel {
-                    connected_once: ActiveValue::set(connected_once),
-                    ..Default::default()
-                })
-                .exec(&*tx)
-                .await?;
-            Ok(())
-        })
-        .await
-    }
-
-    pub async fn destroy_user(&self, id: UserId) -> Result<()> {
-        self.transaction(|tx| async move {
-            access_token::Entity::delete_many()
-                .filter(access_token::Column::UserId.eq(id))
-                .exec(&*tx)
-                .await?;
-            user::Entity::delete_by_id(id).exec(&*tx).await?;
-            Ok(())
-        })
-        .await
-    }
-
-    pub async fn fuzzy_search_users(&self, name_query: &str, limit: u32) -> Result<Vec<User>> {
-        self.transaction(|tx| async {
-            let tx = tx;
-            let like_string = Self::fuzzy_like_string(name_query);
-            let query = "
-                SELECT users.*
-                FROM users
-                WHERE github_login ILIKE $1
-                ORDER BY github_login <-> $2
-                LIMIT $3
-            ";
-
-            Ok(user::Entity::find()
-                .from_raw_sql(Statement::from_sql_and_values(
-                    self.pool.get_database_backend(),
-                    query,
-                    vec![like_string.into(), name_query.into(), limit.into()],
-                ))
-                .all(&*tx)
-                .await?)
-        })
-        .await
-    }
-
-    pub fn fuzzy_like_string(string: &str) -> String {
-        let mut result = String::with_capacity(string.len() * 2 + 1);
-        for c in string.chars() {
-            if c.is_alphanumeric() {
-                result.push('%');
-                result.push(c);
-            }
-        }
-        result.push('%');
-        result
-    }
-
-    pub async fn create_user_flag(&self, flag: &str) -> Result<FlagId> {
-        self.transaction(|tx| async move {
-            let flag = feature_flag::Entity::insert(feature_flag::ActiveModel {
-                flag: ActiveValue::set(flag.to_string()),
-                ..Default::default()
-            })
-            .exec(&*tx)
-            .await?
-            .last_insert_id;
-
-            Ok(flag)
-        })
-        .await
-    }
-
-    pub async fn add_user_flag(&self, user: UserId, flag: FlagId) -> Result<()> {
-        self.transaction(|tx| async move {
-            user_feature::Entity::insert(user_feature::ActiveModel {
-                user_id: ActiveValue::set(user),
-                feature_id: ActiveValue::set(flag),
-            })
-            .exec(&*tx)
-            .await?;
-
-            Ok(())
-        })
-        .await
-    }
-
-    pub async fn get_user_flags(&self, user: UserId) -> Result<Vec<String>> {
-        self.transaction(|tx| async move {
-            #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
-            enum QueryAs {
-                Flag,
-            }
-
-            let flags = user::Model {
-                id: user,
-                ..Default::default()
-            }
-            .find_linked(user::UserFlags)
-            .select_only()
-            .column(feature_flag::Column::Flag)
-            .into_values::<_, QueryAs>()
-            .all(&*tx)
-            .await?;
-
-            Ok(flags)
-        })
-        .await
-    }
-}

crates/collab2/src/db/tables.rs 🔗

@@ -1,32 +0,0 @@
-pub mod access_token;
-pub mod buffer;
-pub mod buffer_operation;
-pub mod buffer_snapshot;
-pub mod channel;
-pub mod channel_buffer_collaborator;
-pub mod channel_chat_participant;
-pub mod channel_member;
-pub mod channel_message;
-pub mod channel_message_mention;
-pub mod contact;
-pub mod feature_flag;
-pub mod follower;
-pub mod language_server;
-pub mod notification;
-pub mod notification_kind;
-pub mod observed_buffer_edits;
-pub mod observed_channel_messages;
-pub mod project;
-pub mod project_collaborator;
-pub mod room;
-pub mod room_participant;
-pub mod server;
-pub mod signup;
-pub mod user;
-pub mod user_feature;
-pub mod worktree;
-pub mod worktree_diagnostic_summary;
-pub mod worktree_entry;
-pub mod worktree_repository;
-pub mod worktree_repository_statuses;
-pub mod worktree_settings_file;

crates/collab2/src/db/tables/access_token.rs 🔗

@@ -1,29 +0,0 @@
-use crate::db::{AccessTokenId, UserId};
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "access_tokens")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub id: AccessTokenId,
-    pub user_id: UserId,
-    pub hash: String,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
-    #[sea_orm(
-        belongs_to = "super::user::Entity",
-        from = "Column::UserId",
-        to = "super::user::Column::Id"
-    )]
-    User,
-}
-
-impl Related<super::user::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::User.def()
-    }
-}
-
-impl ActiveModelBehavior for ActiveModel {}

crates/collab2/src/db/tables/buffer.rs 🔗

@@ -1,45 +0,0 @@
-use crate::db::{BufferId, ChannelId};
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "buffers")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub id: BufferId,
-    pub epoch: i32,
-    pub channel_id: ChannelId,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
-    #[sea_orm(has_many = "super::buffer_operation::Entity")]
-    Operations,
-    #[sea_orm(has_many = "super::buffer_snapshot::Entity")]
-    Snapshots,
-    #[sea_orm(
-        belongs_to = "super::channel::Entity",
-        from = "Column::ChannelId",
-        to = "super::channel::Column::Id"
-    )]
-    Channel,
-}
-
-impl Related<super::buffer_operation::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Operations.def()
-    }
-}
-
-impl Related<super::buffer_snapshot::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Snapshots.def()
-    }
-}
-
-impl Related<super::channel::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Channel.def()
-    }
-}
-
-impl ActiveModelBehavior for ActiveModel {}

crates/collab2/src/db/tables/buffer_operation.rs 🔗

@@ -1,34 +0,0 @@
-use crate::db::BufferId;
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "buffer_operations")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub buffer_id: BufferId,
-    #[sea_orm(primary_key)]
-    pub epoch: i32,
-    #[sea_orm(primary_key)]
-    pub lamport_timestamp: i32,
-    #[sea_orm(primary_key)]
-    pub replica_id: i32,
-    pub value: Vec<u8>,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
-    #[sea_orm(
-        belongs_to = "super::buffer::Entity",
-        from = "Column::BufferId",
-        to = "super::buffer::Column::Id"
-    )]
-    Buffer,
-}
-
-impl Related<super::buffer::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Buffer.def()
-    }
-}
-
-impl ActiveModelBehavior for ActiveModel {}

crates/collab2/src/db/tables/buffer_snapshot.rs 🔗

@@ -1,31 +0,0 @@
-use crate::db::BufferId;
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "buffer_snapshots")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub buffer_id: BufferId,
-    #[sea_orm(primary_key)]
-    pub epoch: i32,
-    pub text: String,
-    pub operation_serialization_version: i32,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
-    #[sea_orm(
-        belongs_to = "super::buffer::Entity",
-        from = "Column::BufferId",
-        to = "super::buffer::Column::Id"
-    )]
-    Buffer,
-}
-
-impl Related<super::buffer::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Buffer.def()
-    }
-}
-
-impl ActiveModelBehavior for ActiveModel {}

crates/collab2/src/db/tables/channel.rs 🔗

@@ -1,79 +0,0 @@
-use crate::db::{ChannelId, ChannelVisibility};
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, Default, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "channels")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub id: ChannelId,
-    pub name: String,
-    pub visibility: ChannelVisibility,
-    pub parent_path: String,
-}
-
-impl Model {
-    pub fn parent_id(&self) -> Option<ChannelId> {
-        self.ancestors().last()
-    }
-
-    pub fn ancestors(&self) -> impl Iterator<Item = ChannelId> + '_ {
-        self.parent_path
-            .trim_end_matches('/')
-            .split('/')
-            .filter_map(|id| Some(ChannelId::from_proto(id.parse().ok()?)))
-    }
-
-    pub fn ancestors_including_self(&self) -> impl Iterator<Item = ChannelId> + '_ {
-        self.ancestors().chain(Some(self.id))
-    }
-
-    pub fn path(&self) -> String {
-        format!("{}{}/", self.parent_path, self.id)
-    }
-}
-
-impl ActiveModelBehavior for ActiveModel {}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
-    #[sea_orm(has_one = "super::room::Entity")]
-    Room,
-    #[sea_orm(has_one = "super::buffer::Entity")]
-    Buffer,
-    #[sea_orm(has_many = "super::channel_member::Entity")]
-    Member,
-    #[sea_orm(has_many = "super::channel_buffer_collaborator::Entity")]
-    BufferCollaborators,
-    #[sea_orm(has_many = "super::channel_chat_participant::Entity")]
-    ChatParticipants,
-}
-
-impl Related<super::channel_member::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Member.def()
-    }
-}
-
-impl Related<super::room::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Room.def()
-    }
-}
-
-impl Related<super::buffer::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Buffer.def()
-    }
-}
-
-impl Related<super::channel_buffer_collaborator::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::BufferCollaborators.def()
-    }
-}
-
-impl Related<super::channel_chat_participant::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::ChatParticipants.def()
-    }
-}

crates/collab2/src/db/tables/channel_buffer_collaborator.rs 🔗

@@ -1,43 +0,0 @@
-use crate::db::{ChannelBufferCollaboratorId, ChannelId, ReplicaId, ServerId, UserId};
-use rpc::ConnectionId;
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "channel_buffer_collaborators")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub id: ChannelBufferCollaboratorId,
-    pub channel_id: ChannelId,
-    pub connection_id: i32,
-    pub connection_server_id: ServerId,
-    pub connection_lost: bool,
-    pub user_id: UserId,
-    pub replica_id: ReplicaId,
-}
-
-impl Model {
-    pub fn connection(&self) -> ConnectionId {
-        ConnectionId {
-            owner_id: self.connection_server_id.0 as u32,
-            id: self.connection_id as u32,
-        }
-    }
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
-    #[sea_orm(
-        belongs_to = "super::channel::Entity",
-        from = "Column::ChannelId",
-        to = "super::channel::Column::Id"
-    )]
-    Channel,
-}
-
-impl Related<super::channel::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Channel.def()
-    }
-}
-
-impl ActiveModelBehavior for ActiveModel {}

crates/collab2/src/db/tables/channel_chat_participant.rs 🔗

@@ -1,41 +0,0 @@
-use crate::db::{ChannelChatParticipantId, ChannelId, ServerId, UserId};
-use rpc::ConnectionId;
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "channel_chat_participants")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub id: ChannelChatParticipantId,
-    pub channel_id: ChannelId,
-    pub user_id: UserId,
-    pub connection_id: i32,
-    pub connection_server_id: ServerId,
-}
-
-impl Model {
-    pub fn connection(&self) -> ConnectionId {
-        ConnectionId {
-            owner_id: self.connection_server_id.0 as u32,
-            id: self.connection_id as u32,
-        }
-    }
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
-    #[sea_orm(
-        belongs_to = "super::channel::Entity",
-        from = "Column::ChannelId",
-        to = "super::channel::Column::Id"
-    )]
-    Channel,
-}
-
-impl Related<super::channel::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Channel.def()
-    }
-}
-
-impl ActiveModelBehavior for ActiveModel {}

crates/collab2/src/db/tables/channel_member.rs 🔗

@@ -1,59 +0,0 @@
-use crate::db::{channel_member, ChannelId, ChannelMemberId, ChannelRole, UserId};
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "channel_members")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub id: ChannelMemberId,
-    pub channel_id: ChannelId,
-    pub user_id: UserId,
-    pub accepted: bool,
-    pub role: ChannelRole,
-}
-
-impl ActiveModelBehavior for ActiveModel {}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
-    #[sea_orm(
-        belongs_to = "super::channel::Entity",
-        from = "Column::ChannelId",
-        to = "super::channel::Column::Id"
-    )]
-    Channel,
-    #[sea_orm(
-        belongs_to = "super::user::Entity",
-        from = "Column::UserId",
-        to = "super::user::Column::Id"
-    )]
-    User,
-}
-
-impl Related<super::channel::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Channel.def()
-    }
-}
-
-impl Related<super::user::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::User.def()
-    }
-}
-
-#[derive(Debug)]
-pub struct UserToChannel;
-
-impl Linked for UserToChannel {
-    type FromEntity = super::user::Entity;
-
-    type ToEntity = super::channel::Entity;
-
-    fn link(&self) -> Vec<RelationDef> {
-        vec![
-            channel_member::Relation::User.def().rev(),
-            channel_member::Relation::Channel.def(),
-        ]
-    }
-}

crates/collab2/src/db/tables/channel_message.rs 🔗

@@ -1,45 +0,0 @@
-use crate::db::{ChannelId, MessageId, UserId};
-use sea_orm::entity::prelude::*;
-use time::PrimitiveDateTime;
-
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "channel_messages")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub id: MessageId,
-    pub channel_id: ChannelId,
-    pub sender_id: UserId,
-    pub body: String,
-    pub sent_at: PrimitiveDateTime,
-    pub nonce: Uuid,
-}
-
-impl ActiveModelBehavior for ActiveModel {}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
-    #[sea_orm(
-        belongs_to = "super::channel::Entity",
-        from = "Column::ChannelId",
-        to = "super::channel::Column::Id"
-    )]
-    Channel,
-    #[sea_orm(
-        belongs_to = "super::user::Entity",
-        from = "Column::SenderId",
-        to = "super::user::Column::Id"
-    )]
-    Sender,
-}
-
-impl Related<super::channel::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Channel.def()
-    }
-}
-
-impl Related<super::user::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Sender.def()
-    }
-}

crates/collab2/src/db/tables/channel_message_mention.rs 🔗

@@ -1,43 +0,0 @@
-use crate::db::{MessageId, UserId};
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "channel_message_mentions")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub message_id: MessageId,
-    #[sea_orm(primary_key)]
-    pub start_offset: i32,
-    pub end_offset: i32,
-    pub user_id: UserId,
-}
-
-impl ActiveModelBehavior for ActiveModel {}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
-    #[sea_orm(
-        belongs_to = "super::channel_message::Entity",
-        from = "Column::MessageId",
-        to = "super::channel_message::Column::Id"
-    )]
-    Message,
-    #[sea_orm(
-        belongs_to = "super::user::Entity",
-        from = "Column::UserId",
-        to = "super::user::Column::Id"
-    )]
-    MentionedUser,
-}
-
-impl Related<super::channel::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Message.def()
-    }
-}
-
-impl Related<super::user::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::MentionedUser.def()
-    }
-}

crates/collab2/src/db/tables/contact.rs 🔗

@@ -1,32 +0,0 @@
-use crate::db::{ContactId, UserId};
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, Default, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "contacts")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub id: ContactId,
-    pub user_id_a: UserId,
-    pub user_id_b: UserId,
-    pub a_to_b: bool,
-    pub should_notify: bool,
-    pub accepted: bool,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
-    #[sea_orm(
-        belongs_to = "super::room_participant::Entity",
-        from = "Column::UserIdA",
-        to = "super::room_participant::Column::UserId"
-    )]
-    UserARoomParticipant,
-    #[sea_orm(
-        belongs_to = "super::room_participant::Entity",
-        from = "Column::UserIdB",
-        to = "super::room_participant::Column::UserId"
-    )]
-    UserBRoomParticipant,
-}
-
-impl ActiveModelBehavior for ActiveModel {}

crates/collab2/src/db/tables/feature_flag.rs 🔗

@@ -1,40 +0,0 @@
-use sea_orm::entity::prelude::*;
-
-use crate::db::FlagId;
-
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "feature_flags")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub id: FlagId,
-    pub flag: String,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
-    #[sea_orm(has_many = "super::user_feature::Entity")]
-    UserFeature,
-}
-
-impl Related<super::user_feature::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::UserFeature.def()
-    }
-}
-
-impl ActiveModelBehavior for ActiveModel {}
-
-pub struct FlaggedUsers;
-
-impl Linked for FlaggedUsers {
-    type FromEntity = Entity;
-
-    type ToEntity = super::user::Entity;
-
-    fn link(&self) -> Vec<RelationDef> {
-        vec![
-            super::user_feature::Relation::Flag.def().rev(),
-            super::user_feature::Relation::User.def(),
-        ]
-    }
-}

crates/collab2/src/db/tables/follower.rs 🔗

@@ -1,50 +0,0 @@
-use crate::db::{FollowerId, ProjectId, RoomId, ServerId};
-use rpc::ConnectionId;
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, Default, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "followers")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub id: FollowerId,
-    pub room_id: RoomId,
-    pub project_id: ProjectId,
-    pub leader_connection_server_id: ServerId,
-    pub leader_connection_id: i32,
-    pub follower_connection_server_id: ServerId,
-    pub follower_connection_id: i32,
-}
-
-impl Model {
-    pub fn leader_connection(&self) -> ConnectionId {
-        ConnectionId {
-            owner_id: self.leader_connection_server_id.0 as u32,
-            id: self.leader_connection_id as u32,
-        }
-    }
-
-    pub fn follower_connection(&self) -> ConnectionId {
-        ConnectionId {
-            owner_id: self.follower_connection_server_id.0 as u32,
-            id: self.follower_connection_id as u32,
-        }
-    }
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
-    #[sea_orm(
-        belongs_to = "super::room::Entity",
-        from = "Column::RoomId",
-        to = "super::room::Column::Id"
-    )]
-    Room,
-}
-
-impl Related<super::room::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Room.def()
-    }
-}
-
-impl ActiveModelBehavior for ActiveModel {}

crates/collab2/src/db/tables/language_server.rs 🔗

@@ -1,30 +0,0 @@
-use crate::db::ProjectId;
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "language_servers")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub project_id: ProjectId,
-    #[sea_orm(primary_key)]
-    pub id: i64,
-    pub name: String,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
-    #[sea_orm(
-        belongs_to = "super::project::Entity",
-        from = "Column::ProjectId",
-        to = "super::project::Column::Id"
-    )]
-    Project,
-}
-
-impl Related<super::project::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Project.def()
-    }
-}
-
-impl ActiveModelBehavior for ActiveModel {}

crates/collab2/src/db/tables/notification.rs 🔗

@@ -1,29 +0,0 @@
-use crate::db::{NotificationId, NotificationKindId, UserId};
-use sea_orm::entity::prelude::*;
-use time::PrimitiveDateTime;
-
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "notifications")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub id: NotificationId,
-    pub created_at: PrimitiveDateTime,
-    pub recipient_id: UserId,
-    pub kind: NotificationKindId,
-    pub entity_id: Option<i32>,
-    pub content: String,
-    pub is_read: bool,
-    pub response: Option<bool>,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
-    #[sea_orm(
-        belongs_to = "super::user::Entity",
-        from = "Column::RecipientId",
-        to = "super::user::Column::Id"
-    )]
-    Recipient,
-}
-
-impl ActiveModelBehavior for ActiveModel {}

crates/collab2/src/db/tables/notification_kind.rs 🔗

@@ -1,15 +0,0 @@
-use crate::db::NotificationKindId;
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "notification_kinds")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub id: NotificationKindId,
-    pub name: String,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {}
-
-impl ActiveModelBehavior for ActiveModel {}

crates/collab2/src/db/tables/observed_buffer_edits.rs 🔗

@@ -1,43 +0,0 @@
-use crate::db::{BufferId, UserId};
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "observed_buffer_edits")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub user_id: UserId,
-    pub buffer_id: BufferId,
-    pub epoch: i32,
-    pub lamport_timestamp: i32,
-    pub replica_id: i32,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
-    #[sea_orm(
-        belongs_to = "super::buffer::Entity",
-        from = "Column::BufferId",
-        to = "super::buffer::Column::Id"
-    )]
-    Buffer,
-    #[sea_orm(
-        belongs_to = "super::user::Entity",
-        from = "Column::UserId",
-        to = "super::user::Column::Id"
-    )]
-    User,
-}
-
-impl Related<super::buffer::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Buffer.def()
-    }
-}
-
-impl Related<super::user::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::User.def()
-    }
-}
-
-impl ActiveModelBehavior for ActiveModel {}

crates/collab2/src/db/tables/observed_channel_messages.rs 🔗

@@ -1,41 +0,0 @@
-use crate::db::{ChannelId, MessageId, UserId};
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "observed_channel_messages")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub user_id: UserId,
-    pub channel_id: ChannelId,
-    pub channel_message_id: MessageId,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
-    #[sea_orm(
-        belongs_to = "super::channel::Entity",
-        from = "Column::ChannelId",
-        to = "super::channel::Column::Id"
-    )]
-    Channel,
-    #[sea_orm(
-        belongs_to = "super::user::Entity",
-        from = "Column::UserId",
-        to = "super::user::Column::Id"
-    )]
-    User,
-}
-
-impl Related<super::channel::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Channel.def()
-    }
-}
-
-impl Related<super::user::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::User.def()
-    }
-}
-
-impl ActiveModelBehavior for ActiveModel {}

crates/collab2/src/db/tables/project.rs 🔗

@@ -1,84 +0,0 @@
-use crate::db::{ProjectId, Result, RoomId, ServerId, UserId};
-use anyhow::anyhow;
-use rpc::ConnectionId;
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "projects")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub id: ProjectId,
-    pub room_id: RoomId,
-    pub host_user_id: UserId,
-    pub host_connection_id: Option<i32>,
-    pub host_connection_server_id: Option<ServerId>,
-}
-
-impl Model {
-    pub fn host_connection(&self) -> Result<ConnectionId> {
-        let host_connection_server_id = self
-            .host_connection_server_id
-            .ok_or_else(|| anyhow!("empty host_connection_server_id"))?;
-        let host_connection_id = self
-            .host_connection_id
-            .ok_or_else(|| anyhow!("empty host_connection_id"))?;
-        Ok(ConnectionId {
-            owner_id: host_connection_server_id.0 as u32,
-            id: host_connection_id as u32,
-        })
-    }
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
-    #[sea_orm(
-        belongs_to = "super::user::Entity",
-        from = "Column::HostUserId",
-        to = "super::user::Column::Id"
-    )]
-    HostUser,
-    #[sea_orm(
-        belongs_to = "super::room::Entity",
-        from = "Column::RoomId",
-        to = "super::room::Column::Id"
-    )]
-    Room,
-    #[sea_orm(has_many = "super::worktree::Entity")]
-    Worktrees,
-    #[sea_orm(has_many = "super::project_collaborator::Entity")]
-    Collaborators,
-    #[sea_orm(has_many = "super::language_server::Entity")]
-    LanguageServers,
-}
-
-impl Related<super::user::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::HostUser.def()
-    }
-}
-
-impl Related<super::room::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Room.def()
-    }
-}
-
-impl Related<super::worktree::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Worktrees.def()
-    }
-}
-
-impl Related<super::project_collaborator::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Collaborators.def()
-    }
-}
-
-impl Related<super::language_server::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::LanguageServers.def()
-    }
-}
-
-impl ActiveModelBehavior for ActiveModel {}

crates/collab2/src/db/tables/project_collaborator.rs 🔗

@@ -1,43 +0,0 @@
-use crate::db::{ProjectCollaboratorId, ProjectId, ReplicaId, ServerId, UserId};
-use rpc::ConnectionId;
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "project_collaborators")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub id: ProjectCollaboratorId,
-    pub project_id: ProjectId,
-    pub connection_id: i32,
-    pub connection_server_id: ServerId,
-    pub user_id: UserId,
-    pub replica_id: ReplicaId,
-    pub is_host: bool,
-}
-
-impl Model {
-    pub fn connection(&self) -> ConnectionId {
-        ConnectionId {
-            owner_id: self.connection_server_id.0 as u32,
-            id: self.connection_id as u32,
-        }
-    }
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
-    #[sea_orm(
-        belongs_to = "super::project::Entity",
-        from = "Column::ProjectId",
-        to = "super::project::Column::Id"
-    )]
-    Project,
-}
-
-impl Related<super::project::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Project.def()
-    }
-}
-
-impl ActiveModelBehavior for ActiveModel {}

crates/collab2/src/db/tables/room.rs 🔗

@@ -1,54 +0,0 @@
-use crate::db::{ChannelId, RoomId};
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Default, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "rooms")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub id: RoomId,
-    pub live_kit_room: String,
-    pub channel_id: Option<ChannelId>,
-    pub enviroment: Option<String>,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
-    #[sea_orm(has_many = "super::room_participant::Entity")]
-    RoomParticipant,
-    #[sea_orm(has_many = "super::project::Entity")]
-    Project,
-    #[sea_orm(has_many = "super::follower::Entity")]
-    Follower,
-    #[sea_orm(
-        belongs_to = "super::channel::Entity",
-        from = "Column::ChannelId",
-        to = "super::channel::Column::Id"
-    )]
-    Channel,
-}
-
-impl Related<super::room_participant::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::RoomParticipant.def()
-    }
-}
-
-impl Related<super::project::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Project.def()
-    }
-}
-
-impl Related<super::follower::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Follower.def()
-    }
-}
-
-impl Related<super::channel::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Channel.def()
-    }
-}
-
-impl ActiveModelBehavior for ActiveModel {}

crates/collab2/src/db/tables/room_participant.rs 🔗

@@ -1,61 +0,0 @@
-use crate::db::{ProjectId, RoomId, RoomParticipantId, ServerId, UserId};
-use rpc::ConnectionId;
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "room_participants")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub id: RoomParticipantId,
-    pub room_id: RoomId,
-    pub user_id: UserId,
-    pub answering_connection_id: Option<i32>,
-    pub answering_connection_server_id: Option<ServerId>,
-    pub answering_connection_lost: bool,
-    pub location_kind: Option<i32>,
-    pub location_project_id: Option<ProjectId>,
-    pub initial_project_id: Option<ProjectId>,
-    pub calling_user_id: UserId,
-    pub calling_connection_id: i32,
-    pub calling_connection_server_id: Option<ServerId>,
-    pub participant_index: Option<i32>,
-}
-
-impl Model {
-    pub fn answering_connection(&self) -> Option<ConnectionId> {
-        Some(ConnectionId {
-            owner_id: self.answering_connection_server_id?.0 as u32,
-            id: self.answering_connection_id? as u32,
-        })
-    }
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
-    #[sea_orm(
-        belongs_to = "super::user::Entity",
-        from = "Column::UserId",
-        to = "super::user::Column::Id"
-    )]
-    User,
-    #[sea_orm(
-        belongs_to = "super::room::Entity",
-        from = "Column::RoomId",
-        to = "super::room::Column::Id"
-    )]
-    Room,
-}
-
-impl Related<super::user::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::User.def()
-    }
-}
-
-impl Related<super::room::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Room.def()
-    }
-}
-
-impl ActiveModelBehavior for ActiveModel {}

crates/collab2/src/db/tables/server.rs 🔗

@@ -1,15 +0,0 @@
-use crate::db::ServerId;
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "servers")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub id: ServerId,
-    pub environment: String,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {}
-
-impl ActiveModelBehavior for ActiveModel {}

crates/collab2/src/db/tables/signup.rs 🔗

@@ -1,28 +0,0 @@
-use crate::db::{SignupId, UserId};
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "signups")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub id: SignupId,
-    pub email_address: String,
-    pub email_confirmation_code: String,
-    pub email_confirmation_sent: bool,
-    pub created_at: DateTime,
-    pub device_id: Option<String>,
-    pub user_id: Option<UserId>,
-    pub inviting_user_id: Option<UserId>,
-    pub platform_mac: bool,
-    pub platform_linux: bool,
-    pub platform_windows: bool,
-    pub platform_unknown: bool,
-    pub editor_features: Option<Vec<String>>,
-    pub programming_languages: Option<Vec<String>>,
-    pub added_to_mailing_list: bool,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {}
-
-impl ActiveModelBehavior for ActiveModel {}

crates/collab2/src/db/tables/user.rs 🔗

@@ -1,80 +0,0 @@
-use crate::db::UserId;
-use sea_orm::entity::prelude::*;
-use serde::Serialize;
-
-#[derive(Clone, Debug, Default, PartialEq, Eq, DeriveEntityModel, Serialize)]
-#[sea_orm(table_name = "users")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub id: UserId,
-    pub github_login: String,
-    pub github_user_id: Option<i32>,
-    pub email_address: Option<String>,
-    pub admin: bool,
-    pub invite_code: Option<String>,
-    pub invite_count: i32,
-    pub inviter_id: Option<UserId>,
-    pub connected_once: bool,
-    pub metrics_id: Uuid,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
-    #[sea_orm(has_many = "super::access_token::Entity")]
-    AccessToken,
-    #[sea_orm(has_one = "super::room_participant::Entity")]
-    RoomParticipant,
-    #[sea_orm(has_many = "super::project::Entity")]
-    HostedProjects,
-    #[sea_orm(has_many = "super::channel_member::Entity")]
-    ChannelMemberships,
-    #[sea_orm(has_many = "super::user_feature::Entity")]
-    UserFeatures,
-}
-
-impl Related<super::access_token::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::AccessToken.def()
-    }
-}
-
-impl Related<super::room_participant::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::RoomParticipant.def()
-    }
-}
-
-impl Related<super::project::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::HostedProjects.def()
-    }
-}
-
-impl Related<super::channel_member::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::ChannelMemberships.def()
-    }
-}
-
-impl Related<super::user_feature::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::UserFeatures.def()
-    }
-}
-
-impl ActiveModelBehavior for ActiveModel {}
-
-pub struct UserFlags;
-
-impl Linked for UserFlags {
-    type FromEntity = Entity;
-
-    type ToEntity = super::feature_flag::Entity;
-
-    fn link(&self) -> Vec<RelationDef> {
-        vec![
-            super::user_feature::Relation::User.def().rev(),
-            super::user_feature::Relation::Flag.def(),
-        ]
-    }
-}

crates/collab2/src/db/tables/user_feature.rs 🔗

@@ -1,42 +0,0 @@
-use sea_orm::entity::prelude::*;
-
-use crate::db::{FlagId, UserId};
-
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "user_features")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub user_id: UserId,
-    #[sea_orm(primary_key)]
-    pub feature_id: FlagId,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
-    #[sea_orm(
-        belongs_to = "super::feature_flag::Entity",
-        from = "Column::FeatureId",
-        to = "super::feature_flag::Column::Id"
-    )]
-    Flag,
-    #[sea_orm(
-        belongs_to = "super::user::Entity",
-        from = "Column::UserId",
-        to = "super::user::Column::Id"
-    )]
-    User,
-}
-
-impl Related<super::feature_flag::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Flag.def()
-    }
-}
-
-impl Related<super::user::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::User.def()
-    }
-}
-
-impl ActiveModelBehavior for ActiveModel {}

crates/collab2/src/db/tables/worktree.rs 🔗

@@ -1,36 +0,0 @@
-use crate::db::ProjectId;
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "worktrees")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub id: i64,
-    #[sea_orm(primary_key)]
-    pub project_id: ProjectId,
-    pub abs_path: String,
-    pub root_name: String,
-    pub visible: bool,
-    /// The last scan for which we've observed entries. It may be in progress.
-    pub scan_id: i64,
-    /// The last scan that fully completed.
-    pub completed_scan_id: i64,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
-    #[sea_orm(
-        belongs_to = "super::project::Entity",
-        from = "Column::ProjectId",
-        to = "super::project::Column::Id"
-    )]
-    Project,
-}
-
-impl Related<super::project::Entity> for Entity {
-    fn to() -> RelationDef {
-        Relation::Project.def()
-    }
-}
-
-impl ActiveModelBehavior for ActiveModel {}

crates/collab2/src/db/tables/worktree_diagnostic_summary.rs 🔗

@@ -1,21 +0,0 @@
-use crate::db::ProjectId;
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "worktree_diagnostic_summaries")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub project_id: ProjectId,
-    #[sea_orm(primary_key)]
-    pub worktree_id: i64,
-    #[sea_orm(primary_key)]
-    pub path: String,
-    pub language_server_id: i64,
-    pub error_count: i32,
-    pub warning_count: i32,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {}
-
-impl ActiveModelBehavior for ActiveModel {}

crates/collab2/src/db/tables/worktree_entry.rs 🔗

@@ -1,29 +0,0 @@
-use crate::db::ProjectId;
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "worktree_entries")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub project_id: ProjectId,
-    #[sea_orm(primary_key)]
-    pub worktree_id: i64,
-    #[sea_orm(primary_key)]
-    pub id: i64,
-    pub is_dir: bool,
-    pub path: String,
-    pub inode: i64,
-    pub mtime_seconds: i64,
-    pub mtime_nanos: i32,
-    pub git_status: Option<i64>,
-    pub is_symlink: bool,
-    pub is_ignored: bool,
-    pub is_external: bool,
-    pub is_deleted: bool,
-    pub scan_id: i64,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {}
-
-impl ActiveModelBehavior for ActiveModel {}

crates/collab2/src/db/tables/worktree_repository.rs 🔗

@@ -1,21 +0,0 @@
-use crate::db::ProjectId;
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "worktree_repositories")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub project_id: ProjectId,
-    #[sea_orm(primary_key)]
-    pub worktree_id: i64,
-    #[sea_orm(primary_key)]
-    pub work_directory_id: i64,
-    pub scan_id: i64,
-    pub branch: Option<String>,
-    pub is_deleted: bool,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {}
-
-impl ActiveModelBehavior for ActiveModel {}

crates/collab2/src/db/tables/worktree_repository_statuses.rs 🔗

@@ -1,23 +0,0 @@
-use crate::db::ProjectId;
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "worktree_repository_statuses")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub project_id: ProjectId,
-    #[sea_orm(primary_key)]
-    pub worktree_id: i64,
-    #[sea_orm(primary_key)]
-    pub work_directory_id: i64,
-    #[sea_orm(primary_key)]
-    pub repo_path: String,
-    pub status: i64,
-    pub scan_id: i64,
-    pub is_deleted: bool,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {}
-
-impl ActiveModelBehavior for ActiveModel {}

crates/collab2/src/db/tables/worktree_settings_file.rs 🔗

@@ -1,19 +0,0 @@
-use crate::db::ProjectId;
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "worktree_settings_files")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub project_id: ProjectId,
-    #[sea_orm(primary_key)]
-    pub worktree_id: i64,
-    #[sea_orm(primary_key)]
-    pub path: String,
-    pub content: String,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {}
-
-impl ActiveModelBehavior for ActiveModel {}

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

@@ -1,187 +0,0 @@
-mod buffer_tests;
-mod channel_tests;
-mod db_tests;
-mod feature_flag_tests;
-mod message_tests;
-
-use super::*;
-use gpui::BackgroundExecutor;
-use parking_lot::Mutex;
-use sea_orm::ConnectionTrait;
-use sqlx::migrate::MigrateDatabase;
-use std::sync::{
-    atomic::{AtomicI32, AtomicU32, Ordering::SeqCst},
-    Arc,
-};
-
-const TEST_RELEASE_CHANNEL: &'static str = "test";
-
-pub struct TestDb {
-    pub db: Option<Arc<Database>>,
-    pub connection: Option<sqlx::AnyConnection>,
-}
-
-impl TestDb {
-    pub fn sqlite(background: BackgroundExecutor) -> Self {
-        let url = format!("sqlite::memory:");
-        let runtime = tokio::runtime::Builder::new_current_thread()
-            .enable_io()
-            .enable_time()
-            .build()
-            .unwrap();
-
-        let mut db = runtime.block_on(async {
-            let mut options = ConnectOptions::new(url);
-            options.max_connections(5);
-            let mut db = Database::new(options, Executor::Deterministic(background))
-                .await
-                .unwrap();
-            let sql = include_str!(concat!(
-                env!("CARGO_MANIFEST_DIR"),
-                "/migrations.sqlite/20221109000000_test_schema.sql"
-            ));
-            db.pool
-                .execute(sea_orm::Statement::from_string(
-                    db.pool.get_database_backend(),
-                    sql,
-                ))
-                .await
-                .unwrap();
-            db.initialize_notification_kinds().await.unwrap();
-            db
-        });
-
-        db.runtime = Some(runtime);
-
-        Self {
-            db: Some(Arc::new(db)),
-            connection: None,
-        }
-    }
-
-    pub fn postgres(background: BackgroundExecutor) -> Self {
-        static LOCK: Mutex<()> = Mutex::new(());
-
-        let _guard = LOCK.lock();
-        let mut rng = StdRng::from_entropy();
-        let url = format!(
-            "postgres://postgres@localhost/zed-test-{}",
-            rng.gen::<u128>()
-        );
-        let runtime = tokio::runtime::Builder::new_current_thread()
-            .enable_io()
-            .enable_time()
-            .build()
-            .unwrap();
-
-        let mut db = runtime.block_on(async {
-            sqlx::Postgres::create_database(&url)
-                .await
-                .expect("failed to create test db");
-            let mut options = ConnectOptions::new(url);
-            options
-                .max_connections(5)
-                .idle_timeout(Duration::from_secs(0));
-            let mut db = Database::new(options, Executor::Deterministic(background))
-                .await
-                .unwrap();
-            let migrations_path = concat!(env!("CARGO_MANIFEST_DIR"), "/migrations");
-            db.migrate(Path::new(migrations_path), false).await.unwrap();
-            db.initialize_notification_kinds().await.unwrap();
-            db
-        });
-
-        db.runtime = Some(runtime);
-
-        Self {
-            db: Some(Arc::new(db)),
-            connection: None,
-        }
-    }
-
-    pub fn db(&self) -> &Arc<Database> {
-        self.db.as_ref().unwrap()
-    }
-}
-
-#[macro_export]
-macro_rules! test_both_dbs {
-    ($test_name:ident, $postgres_test_name:ident, $sqlite_test_name:ident) => {
-        #[gpui::test]
-        async fn $postgres_test_name(cx: &mut gpui::TestAppContext) {
-            let test_db = crate::db::TestDb::postgres(cx.executor().clone());
-            $test_name(test_db.db()).await;
-        }
-
-        #[gpui::test]
-        async fn $sqlite_test_name(cx: &mut gpui::TestAppContext) {
-            let test_db = crate::db::TestDb::sqlite(cx.executor().clone());
-            $test_name(test_db.db()).await;
-        }
-    };
-}
-
-impl Drop for TestDb {
-    fn drop(&mut self) {
-        let db = self.db.take().unwrap();
-        if let sea_orm::DatabaseBackend::Postgres = db.pool.get_database_backend() {
-            db.runtime.as_ref().unwrap().block_on(async {
-                use util::ResultExt;
-                let query = "
-                        SELECT pg_terminate_backend(pg_stat_activity.pid)
-                        FROM pg_stat_activity
-                        WHERE
-                            pg_stat_activity.datname = current_database() AND
-                            pid <> pg_backend_pid();
-                    ";
-                db.pool
-                    .execute(sea_orm::Statement::from_string(
-                        db.pool.get_database_backend(),
-                        query,
-                    ))
-                    .await
-                    .log_err();
-                sqlx::Postgres::drop_database(db.options.get_url())
-                    .await
-                    .log_err();
-            })
-        }
-    }
-}
-
-fn channel_tree(channels: &[(ChannelId, &[ChannelId], &'static str, ChannelRole)]) -> Vec<Channel> {
-    channels
-        .iter()
-        .map(|(id, parent_path, name, role)| Channel {
-            id: *id,
-            name: name.to_string(),
-            visibility: ChannelVisibility::Members,
-            role: *role,
-            parent_path: parent_path.to_vec(),
-        })
-        .collect()
-}
-
-static GITHUB_USER_ID: AtomicI32 = AtomicI32::new(5);
-
-async fn new_test_user(db: &Arc<Database>, email: &str) -> UserId {
-    db.create_user(
-        email,
-        false,
-        NewUserParams {
-            github_login: email[0..email.find("@").unwrap()].to_string(),
-            github_user_id: GITHUB_USER_ID.fetch_add(1, SeqCst),
-        },
-    )
-    .await
-    .unwrap()
-    .user_id
-}
-
-static TEST_CONNECTION_ID: AtomicU32 = AtomicU32::new(1);
-fn new_test_connection(server: ServerId) -> ConnectionId {
-    ConnectionId {
-        id: TEST_CONNECTION_ID.fetch_add(1, SeqCst),
-        owner_id: server.0 as u32,
-    }
-}

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

@@ -1,506 +0,0 @@
-use super::*;
-use crate::test_both_dbs;
-use language::proto::{self, serialize_version};
-use text::Buffer;
-
-test_both_dbs!(
-    test_channel_buffers,
-    test_channel_buffers_postgres,
-    test_channel_buffers_sqlite
-);
-
-async fn test_channel_buffers(db: &Arc<Database>) {
-    let a_id = db
-        .create_user(
-            "user_a@example.com",
-            false,
-            NewUserParams {
-                github_login: "user_a".into(),
-                github_user_id: 101,
-            },
-        )
-        .await
-        .unwrap()
-        .user_id;
-    let b_id = db
-        .create_user(
-            "user_b@example.com",
-            false,
-            NewUserParams {
-                github_login: "user_b".into(),
-                github_user_id: 102,
-            },
-        )
-        .await
-        .unwrap()
-        .user_id;
-
-    // This user will not be a part of the channel
-    let c_id = db
-        .create_user(
-            "user_c@example.com",
-            false,
-            NewUserParams {
-                github_login: "user_c".into(),
-                github_user_id: 102,
-            },
-        )
-        .await
-        .unwrap()
-        .user_id;
-
-    let owner_id = db.create_server("production").await.unwrap().0 as u32;
-
-    let zed_id = db.create_root_channel("zed", a_id).await.unwrap();
-
-    db.invite_channel_member(zed_id, b_id, a_id, ChannelRole::Member)
-        .await
-        .unwrap();
-
-    db.respond_to_channel_invite(zed_id, b_id, true)
-        .await
-        .unwrap();
-
-    let connection_id_a = ConnectionId { owner_id, id: 1 };
-    let _ = db
-        .join_channel_buffer(zed_id, a_id, connection_id_a)
-        .await
-        .unwrap();
-
-    let mut buffer_a = Buffer::new(0, 0, "".to_string());
-    let mut operations = Vec::new();
-    operations.push(buffer_a.edit([(0..0, "hello world")]));
-    operations.push(buffer_a.edit([(5..5, ", cruel")]));
-    operations.push(buffer_a.edit([(0..5, "goodbye")]));
-    operations.push(buffer_a.undo().unwrap().1);
-    assert_eq!(buffer_a.text(), "hello, cruel world");
-
-    let operations = operations
-        .into_iter()
-        .map(|op| proto::serialize_operation(&language::Operation::Buffer(op)))
-        .collect::<Vec<_>>();
-
-    db.update_channel_buffer(zed_id, a_id, &operations)
-        .await
-        .unwrap();
-
-    let connection_id_b = ConnectionId { owner_id, id: 2 };
-    let buffer_response_b = db
-        .join_channel_buffer(zed_id, b_id, connection_id_b)
-        .await
-        .unwrap();
-
-    let mut buffer_b = Buffer::new(0, 0, buffer_response_b.base_text);
-    buffer_b
-        .apply_ops(buffer_response_b.operations.into_iter().map(|operation| {
-            let operation = proto::deserialize_operation(operation).unwrap();
-            if let language::Operation::Buffer(operation) = operation {
-                operation
-            } else {
-                unreachable!()
-            }
-        }))
-        .unwrap();
-
-    assert_eq!(buffer_b.text(), "hello, cruel world");
-
-    // Ensure that C fails to open the buffer
-    assert!(db
-        .join_channel_buffer(zed_id, c_id, ConnectionId { owner_id, id: 3 })
-        .await
-        .is_err());
-
-    // Ensure that both collaborators have shown up
-    assert_eq!(
-        buffer_response_b.collaborators,
-        &[
-            rpc::proto::Collaborator {
-                user_id: a_id.to_proto(),
-                peer_id: Some(rpc::proto::PeerId { id: 1, owner_id }),
-                replica_id: 0,
-            },
-            rpc::proto::Collaborator {
-                user_id: b_id.to_proto(),
-                peer_id: Some(rpc::proto::PeerId { id: 2, owner_id }),
-                replica_id: 1,
-            }
-        ]
-    );
-
-    // Ensure that get_channel_buffer_collaborators works
-    let zed_collaborats = db.get_channel_buffer_collaborators(zed_id).await.unwrap();
-    assert_eq!(zed_collaborats, &[a_id, b_id]);
-
-    let left_buffer = db
-        .leave_channel_buffer(zed_id, connection_id_b)
-        .await
-        .unwrap();
-
-    assert_eq!(left_buffer.connections, &[connection_id_a],);
-
-    let cargo_id = db.create_root_channel("cargo", a_id).await.unwrap();
-    let _ = db
-        .join_channel_buffer(cargo_id, a_id, connection_id_a)
-        .await
-        .unwrap();
-
-    db.leave_channel_buffers(connection_id_a).await.unwrap();
-
-    let zed_collaborators = db.get_channel_buffer_collaborators(zed_id).await.unwrap();
-    let cargo_collaborators = db.get_channel_buffer_collaborators(cargo_id).await.unwrap();
-    assert_eq!(zed_collaborators, &[]);
-    assert_eq!(cargo_collaborators, &[]);
-
-    // When everyone has left the channel, the operations are collapsed into
-    // a new base text.
-    let buffer_response_b = db
-        .join_channel_buffer(zed_id, b_id, connection_id_b)
-        .await
-        .unwrap();
-    assert_eq!(buffer_response_b.base_text, "hello, cruel world");
-    assert_eq!(buffer_response_b.operations, &[]);
-}
-
-test_both_dbs!(
-    test_channel_buffers_last_operations,
-    test_channel_buffers_last_operations_postgres,
-    test_channel_buffers_last_operations_sqlite
-);
-
-async fn test_channel_buffers_last_operations(db: &Database) {
-    let user_id = db
-        .create_user(
-            "user_a@example.com",
-            false,
-            NewUserParams {
-                github_login: "user_a".into(),
-                github_user_id: 101,
-            },
-        )
-        .await
-        .unwrap()
-        .user_id;
-    let observer_id = db
-        .create_user(
-            "user_b@example.com",
-            false,
-            NewUserParams {
-                github_login: "user_b".into(),
-                github_user_id: 102,
-            },
-        )
-        .await
-        .unwrap()
-        .user_id;
-    let owner_id = db.create_server("production").await.unwrap().0 as u32;
-    let connection_id = ConnectionId {
-        owner_id,
-        id: user_id.0 as u32,
-    };
-
-    let mut buffers = Vec::new();
-    let mut text_buffers = Vec::new();
-    for i in 0..3 {
-        let channel = db
-            .create_root_channel(&format!("channel-{i}"), user_id)
-            .await
-            .unwrap();
-
-        db.invite_channel_member(channel, observer_id, user_id, ChannelRole::Member)
-            .await
-            .unwrap();
-        db.respond_to_channel_invite(channel, observer_id, true)
-            .await
-            .unwrap();
-
-        db.join_channel_buffer(channel, user_id, connection_id)
-            .await
-            .unwrap();
-
-        buffers.push(
-            db.transaction(|tx| async move { db.get_channel_buffer(channel, &*tx).await })
-                .await
-                .unwrap(),
-        );
-
-        text_buffers.push(Buffer::new(0, 0, "".to_string()));
-    }
-
-    let operations = db
-        .transaction(|tx| {
-            let buffers = &buffers;
-            async move {
-                db.get_latest_operations_for_buffers([buffers[0].id, buffers[2].id], &*tx)
-                    .await
-            }
-        })
-        .await
-        .unwrap();
-
-    assert!(operations.is_empty());
-
-    update_buffer(
-        buffers[0].channel_id,
-        user_id,
-        db,
-        vec![
-            text_buffers[0].edit([(0..0, "a")]),
-            text_buffers[0].edit([(0..0, "b")]),
-            text_buffers[0].edit([(0..0, "c")]),
-        ],
-    )
-    .await;
-
-    update_buffer(
-        buffers[1].channel_id,
-        user_id,
-        db,
-        vec![
-            text_buffers[1].edit([(0..0, "d")]),
-            text_buffers[1].edit([(1..1, "e")]),
-            text_buffers[1].edit([(2..2, "f")]),
-        ],
-    )
-    .await;
-
-    // cause buffer 1's epoch to increment.
-    db.leave_channel_buffer(buffers[1].channel_id, connection_id)
-        .await
-        .unwrap();
-    db.join_channel_buffer(buffers[1].channel_id, user_id, connection_id)
-        .await
-        .unwrap();
-    text_buffers[1] = Buffer::new(1, 0, "def".to_string());
-    update_buffer(
-        buffers[1].channel_id,
-        user_id,
-        db,
-        vec![
-            text_buffers[1].edit([(0..0, "g")]),
-            text_buffers[1].edit([(0..0, "h")]),
-        ],
-    )
-    .await;
-
-    update_buffer(
-        buffers[2].channel_id,
-        user_id,
-        db,
-        vec![text_buffers[2].edit([(0..0, "i")])],
-    )
-    .await;
-
-    let operations = db
-        .transaction(|tx| {
-            let buffers = &buffers;
-            async move {
-                db.get_latest_operations_for_buffers([buffers[1].id, buffers[2].id], &*tx)
-                    .await
-            }
-        })
-        .await
-        .unwrap();
-    assert_operations(
-        &operations,
-        &[
-            (buffers[1].id, 1, &text_buffers[1]),
-            (buffers[2].id, 0, &text_buffers[2]),
-        ],
-    );
-
-    let operations = db
-        .transaction(|tx| {
-            let buffers = &buffers;
-            async move {
-                db.get_latest_operations_for_buffers([buffers[0].id, buffers[1].id], &*tx)
-                    .await
-            }
-        })
-        .await
-        .unwrap();
-    assert_operations(
-        &operations,
-        &[
-            (buffers[0].id, 0, &text_buffers[0]),
-            (buffers[1].id, 1, &text_buffers[1]),
-        ],
-    );
-
-    let buffer_changes = db
-        .transaction(|tx| {
-            let buffers = &buffers;
-            async move {
-                db.unseen_channel_buffer_changes(
-                    observer_id,
-                    &[
-                        buffers[0].channel_id,
-                        buffers[1].channel_id,
-                        buffers[2].channel_id,
-                    ],
-                    &*tx,
-                )
-                .await
-            }
-        })
-        .await
-        .unwrap();
-
-    pretty_assertions::assert_eq!(
-        buffer_changes,
-        [
-            rpc::proto::UnseenChannelBufferChange {
-                channel_id: buffers[0].channel_id.to_proto(),
-                epoch: 0,
-                version: serialize_version(&text_buffers[0].version()),
-            },
-            rpc::proto::UnseenChannelBufferChange {
-                channel_id: buffers[1].channel_id.to_proto(),
-                epoch: 1,
-                version: serialize_version(&text_buffers[1].version())
-                    .into_iter()
-                    .filter(|vector| vector.replica_id
-                        == buffer_changes[1].version.first().unwrap().replica_id)
-                    .collect::<Vec<_>>(),
-            },
-            rpc::proto::UnseenChannelBufferChange {
-                channel_id: buffers[2].channel_id.to_proto(),
-                epoch: 0,
-                version: serialize_version(&text_buffers[2].version()),
-            },
-        ]
-    );
-
-    db.observe_buffer_version(
-        buffers[1].id,
-        observer_id,
-        1,
-        serialize_version(&text_buffers[1].version()).as_slice(),
-    )
-    .await
-    .unwrap();
-
-    let buffer_changes = db
-        .transaction(|tx| {
-            let buffers = &buffers;
-            async move {
-                db.unseen_channel_buffer_changes(
-                    observer_id,
-                    &[
-                        buffers[0].channel_id,
-                        buffers[1].channel_id,
-                        buffers[2].channel_id,
-                    ],
-                    &*tx,
-                )
-                .await
-            }
-        })
-        .await
-        .unwrap();
-
-    assert_eq!(
-        buffer_changes,
-        [
-            rpc::proto::UnseenChannelBufferChange {
-                channel_id: buffers[0].channel_id.to_proto(),
-                epoch: 0,
-                version: serialize_version(&text_buffers[0].version()),
-            },
-            rpc::proto::UnseenChannelBufferChange {
-                channel_id: buffers[2].channel_id.to_proto(),
-                epoch: 0,
-                version: serialize_version(&text_buffers[2].version()),
-            },
-        ]
-    );
-
-    // Observe an earlier version of the buffer.
-    db.observe_buffer_version(
-        buffers[1].id,
-        observer_id,
-        1,
-        &[rpc::proto::VectorClockEntry {
-            replica_id: 0,
-            timestamp: 0,
-        }],
-    )
-    .await
-    .unwrap();
-
-    let buffer_changes = db
-        .transaction(|tx| {
-            let buffers = &buffers;
-            async move {
-                db.unseen_channel_buffer_changes(
-                    observer_id,
-                    &[
-                        buffers[0].channel_id,
-                        buffers[1].channel_id,
-                        buffers[2].channel_id,
-                    ],
-                    &*tx,
-                )
-                .await
-            }
-        })
-        .await
-        .unwrap();
-
-    assert_eq!(
-        buffer_changes,
-        [
-            rpc::proto::UnseenChannelBufferChange {
-                channel_id: buffers[0].channel_id.to_proto(),
-                epoch: 0,
-                version: serialize_version(&text_buffers[0].version()),
-            },
-            rpc::proto::UnseenChannelBufferChange {
-                channel_id: buffers[2].channel_id.to_proto(),
-                epoch: 0,
-                version: serialize_version(&text_buffers[2].version()),
-            },
-        ]
-    );
-}
-
-async fn update_buffer(
-    channel_id: ChannelId,
-    user_id: UserId,
-    db: &Database,
-    operations: Vec<text::Operation>,
-) {
-    let operations = operations
-        .into_iter()
-        .map(|op| proto::serialize_operation(&language::Operation::Buffer(op)))
-        .collect::<Vec<_>>();
-    db.update_channel_buffer(channel_id, user_id, &operations)
-        .await
-        .unwrap();
-}
-
-fn assert_operations(
-    operations: &[buffer_operation::Model],
-    expected: &[(BufferId, i32, &text::Buffer)],
-) {
-    let actual = operations
-        .iter()
-        .map(|op| buffer_operation::Model {
-            buffer_id: op.buffer_id,
-            epoch: op.epoch,
-            lamport_timestamp: op.lamport_timestamp,
-            replica_id: op.replica_id,
-            value: vec![],
-        })
-        .collect::<Vec<_>>();
-    let expected = expected
-        .iter()
-        .map(|(buffer_id, epoch, buffer)| buffer_operation::Model {
-            buffer_id: *buffer_id,
-            epoch: *epoch,
-            lamport_timestamp: buffer.lamport_clock.value as i32 - 1,
-            replica_id: buffer.replica_id() as i32,
-            value: vec![],
-        })
-        .collect::<Vec<_>>();
-    assert_eq!(actual, expected, "unexpected operations")
-}

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

@@ -1,831 +0,0 @@
-use crate::{
-    db::{
-        tests::{channel_tree, new_test_connection, new_test_user, TEST_RELEASE_CHANNEL},
-        Channel, ChannelId, ChannelRole, Database, NewUserParams, RoomId,
-    },
-    test_both_dbs,
-};
-use rpc::{
-    proto::{self},
-    ConnectionId,
-};
-use std::sync::Arc;
-
-test_both_dbs!(test_channels, test_channels_postgres, test_channels_sqlite);
-
-async fn test_channels(db: &Arc<Database>) {
-    let a_id = new_test_user(db, "user1@example.com").await;
-    let b_id = new_test_user(db, "user2@example.com").await;
-
-    let zed_id = db.create_root_channel("zed", a_id).await.unwrap();
-
-    // Make sure that people cannot read channels they haven't been invited to
-    assert!(db.get_channel(zed_id, b_id).await.is_err());
-
-    db.invite_channel_member(zed_id, b_id, a_id, ChannelRole::Member)
-        .await
-        .unwrap();
-
-    db.respond_to_channel_invite(zed_id, b_id, true)
-        .await
-        .unwrap();
-
-    let crdb_id = db.create_sub_channel("crdb", zed_id, a_id).await.unwrap();
-    let livestreaming_id = db
-        .create_sub_channel("livestreaming", zed_id, a_id)
-        .await
-        .unwrap();
-    let replace_id = db
-        .create_sub_channel("replace", zed_id, a_id)
-        .await
-        .unwrap();
-
-    let mut members = db
-        .transaction(|tx| async move {
-            let channel = db.get_channel_internal(replace_id, &*tx).await?;
-            Ok(db.get_channel_participants(&channel, &*tx).await?)
-        })
-        .await
-        .unwrap();
-    members.sort();
-    assert_eq!(members, &[a_id, b_id]);
-
-    let rust_id = db.create_root_channel("rust", a_id).await.unwrap();
-    let cargo_id = db.create_sub_channel("cargo", rust_id, a_id).await.unwrap();
-
-    let cargo_ra_id = db
-        .create_sub_channel("cargo-ra", cargo_id, a_id)
-        .await
-        .unwrap();
-
-    let result = db.get_channels_for_user(a_id).await.unwrap();
-    assert_eq!(
-        result.channels,
-        channel_tree(&[
-            (zed_id, &[], "zed", ChannelRole::Admin),
-            (crdb_id, &[zed_id], "crdb", ChannelRole::Admin),
-            (
-                livestreaming_id,
-                &[zed_id],
-                "livestreaming",
-                ChannelRole::Admin
-            ),
-            (replace_id, &[zed_id], "replace", ChannelRole::Admin),
-            (rust_id, &[], "rust", ChannelRole::Admin),
-            (cargo_id, &[rust_id], "cargo", ChannelRole::Admin),
-            (
-                cargo_ra_id,
-                &[rust_id, cargo_id],
-                "cargo-ra",
-                ChannelRole::Admin
-            )
-        ],)
-    );
-
-    let result = db.get_channels_for_user(b_id).await.unwrap();
-    assert_eq!(
-        result.channels,
-        channel_tree(&[
-            (zed_id, &[], "zed", ChannelRole::Member),
-            (crdb_id, &[zed_id], "crdb", ChannelRole::Member),
-            (
-                livestreaming_id,
-                &[zed_id],
-                "livestreaming",
-                ChannelRole::Member
-            ),
-            (replace_id, &[zed_id], "replace", ChannelRole::Member)
-        ],)
-    );
-
-    // Update member permissions
-    let set_subchannel_admin = db
-        .set_channel_member_role(crdb_id, a_id, b_id, ChannelRole::Admin)
-        .await;
-    assert!(set_subchannel_admin.is_err());
-    let set_channel_admin = db
-        .set_channel_member_role(zed_id, a_id, b_id, ChannelRole::Admin)
-        .await;
-    assert!(set_channel_admin.is_ok());
-
-    let result = db.get_channels_for_user(b_id).await.unwrap();
-    assert_eq!(
-        result.channels,
-        channel_tree(&[
-            (zed_id, &[], "zed", ChannelRole::Admin),
-            (crdb_id, &[zed_id], "crdb", ChannelRole::Admin),
-            (
-                livestreaming_id,
-                &[zed_id],
-                "livestreaming",
-                ChannelRole::Admin
-            ),
-            (replace_id, &[zed_id], "replace", ChannelRole::Admin)
-        ],)
-    );
-
-    // Remove a single channel
-    db.delete_channel(crdb_id, a_id).await.unwrap();
-    assert!(db.get_channel(crdb_id, a_id).await.is_err());
-
-    // Remove a channel tree
-    let (mut channel_ids, user_ids) = db.delete_channel(rust_id, a_id).await.unwrap();
-    channel_ids.sort();
-    assert_eq!(channel_ids, &[rust_id, cargo_id, cargo_ra_id]);
-    assert_eq!(user_ids, &[a_id]);
-
-    assert!(db.get_channel(rust_id, a_id).await.is_err());
-    assert!(db.get_channel(cargo_id, a_id).await.is_err());
-    assert!(db.get_channel(cargo_ra_id, a_id).await.is_err());
-}
-
-test_both_dbs!(
-    test_joining_channels,
-    test_joining_channels_postgres,
-    test_joining_channels_sqlite
-);
-
-async fn test_joining_channels(db: &Arc<Database>) {
-    let owner_id = db.create_server("test").await.unwrap().0 as u32;
-
-    let user_1 = new_test_user(db, "user1@example.com").await;
-    let user_2 = new_test_user(db, "user2@example.com").await;
-
-    let channel_1 = db.create_root_channel("channel_1", user_1).await.unwrap();
-
-    // can join a room with membership to its channel
-    let (joined_room, _, _) = db
-        .join_channel(
-            channel_1,
-            user_1,
-            ConnectionId { owner_id, id: 1 },
-            TEST_RELEASE_CHANNEL,
-        )
-        .await
-        .unwrap();
-    assert_eq!(joined_room.room.participants.len(), 1);
-
-    let room_id = RoomId::from_proto(joined_room.room.id);
-    drop(joined_room);
-    // cannot join a room without membership to its channel
-    assert!(db
-        .join_room(
-            room_id,
-            user_2,
-            ConnectionId { owner_id, id: 1 },
-            TEST_RELEASE_CHANNEL
-        )
-        .await
-        .is_err());
-}
-
-test_both_dbs!(
-    test_channel_invites,
-    test_channel_invites_postgres,
-    test_channel_invites_sqlite
-);
-
-async fn test_channel_invites(db: &Arc<Database>) {
-    db.create_server("test").await.unwrap();
-
-    let user_1 = new_test_user(db, "user1@example.com").await;
-    let user_2 = new_test_user(db, "user2@example.com").await;
-    let user_3 = new_test_user(db, "user3@example.com").await;
-
-    let channel_1_1 = db.create_root_channel("channel_1", user_1).await.unwrap();
-
-    let channel_1_2 = db.create_root_channel("channel_2", user_1).await.unwrap();
-
-    db.invite_channel_member(channel_1_1, user_2, user_1, ChannelRole::Member)
-        .await
-        .unwrap();
-    db.invite_channel_member(channel_1_2, user_2, user_1, ChannelRole::Member)
-        .await
-        .unwrap();
-    db.invite_channel_member(channel_1_1, user_3, user_1, ChannelRole::Admin)
-        .await
-        .unwrap();
-
-    let user_2_invites = db
-        .get_channel_invites_for_user(user_2) // -> [channel_1_1, channel_1_2]
-        .await
-        .unwrap()
-        .into_iter()
-        .map(|channel| channel.id)
-        .collect::<Vec<_>>();
-
-    assert_eq!(user_2_invites, &[channel_1_1, channel_1_2]);
-
-    let user_3_invites = db
-        .get_channel_invites_for_user(user_3) // -> [channel_1_1]
-        .await
-        .unwrap()
-        .into_iter()
-        .map(|channel| channel.id)
-        .collect::<Vec<_>>();
-
-    assert_eq!(user_3_invites, &[channel_1_1]);
-
-    let mut members = db
-        .get_channel_participant_details(channel_1_1, user_1)
-        .await
-        .unwrap();
-
-    members.sort_by_key(|member| member.user_id);
-    assert_eq!(
-        members,
-        &[
-            proto::ChannelMember {
-                user_id: user_1.to_proto(),
-                kind: proto::channel_member::Kind::Member.into(),
-                role: proto::ChannelRole::Admin.into(),
-            },
-            proto::ChannelMember {
-                user_id: user_2.to_proto(),
-                kind: proto::channel_member::Kind::Invitee.into(),
-                role: proto::ChannelRole::Member.into(),
-            },
-            proto::ChannelMember {
-                user_id: user_3.to_proto(),
-                kind: proto::channel_member::Kind::Invitee.into(),
-                role: proto::ChannelRole::Admin.into(),
-            },
-        ]
-    );
-
-    db.respond_to_channel_invite(channel_1_1, user_2, true)
-        .await
-        .unwrap();
-
-    let channel_1_3 = db
-        .create_sub_channel("channel_3", channel_1_1, user_1)
-        .await
-        .unwrap();
-
-    let members = db
-        .get_channel_participant_details(channel_1_3, user_1)
-        .await
-        .unwrap();
-    assert_eq!(
-        members,
-        &[
-            proto::ChannelMember {
-                user_id: user_1.to_proto(),
-                kind: proto::channel_member::Kind::AncestorMember.into(),
-                role: proto::ChannelRole::Admin.into(),
-            },
-            proto::ChannelMember {
-                user_id: user_2.to_proto(),
-                kind: proto::channel_member::Kind::AncestorMember.into(),
-                role: proto::ChannelRole::Member.into(),
-            },
-        ]
-    );
-}
-
-test_both_dbs!(
-    test_channel_renames,
-    test_channel_renames_postgres,
-    test_channel_renames_sqlite
-);
-
-async fn test_channel_renames(db: &Arc<Database>) {
-    db.create_server("test").await.unwrap();
-
-    let user_1 = db
-        .create_user(
-            "user1@example.com",
-            false,
-            NewUserParams {
-                github_login: "user1".into(),
-                github_user_id: 5,
-            },
-        )
-        .await
-        .unwrap()
-        .user_id;
-
-    let user_2 = db
-        .create_user(
-            "user2@example.com",
-            false,
-            NewUserParams {
-                github_login: "user2".into(),
-                github_user_id: 6,
-            },
-        )
-        .await
-        .unwrap()
-        .user_id;
-
-    let zed_id = db.create_root_channel("zed", user_1).await.unwrap();
-
-    db.rename_channel(zed_id, user_1, "#zed-archive")
-        .await
-        .unwrap();
-
-    let channel = db.get_channel(zed_id, user_1).await.unwrap();
-    assert_eq!(channel.name, "zed-archive");
-
-    let non_permissioned_rename = db.rename_channel(zed_id, user_2, "hacked-lol").await;
-    assert!(non_permissioned_rename.is_err());
-
-    let bad_name_rename = db.rename_channel(zed_id, user_1, "#").await;
-    assert!(bad_name_rename.is_err())
-}
-
-test_both_dbs!(
-    test_db_channel_moving,
-    test_channels_moving_postgres,
-    test_channels_moving_sqlite
-);
-
-async fn test_db_channel_moving(db: &Arc<Database>) {
-    let a_id = db
-        .create_user(
-            "user1@example.com",
-            false,
-            NewUserParams {
-                github_login: "user1".into(),
-                github_user_id: 5,
-            },
-        )
-        .await
-        .unwrap()
-        .user_id;
-
-    let zed_id = db.create_root_channel("zed", a_id).await.unwrap();
-
-    let crdb_id = db.create_sub_channel("crdb", zed_id, a_id).await.unwrap();
-
-    let gpui2_id = db.create_sub_channel("gpui2", zed_id, a_id).await.unwrap();
-
-    let livestreaming_id = db
-        .create_sub_channel("livestreaming", crdb_id, a_id)
-        .await
-        .unwrap();
-
-    let livestreaming_dag_id = db
-        .create_sub_channel("livestreaming_dag", livestreaming_id, a_id)
-        .await
-        .unwrap();
-
-    // ========================================================================
-    // sanity check
-    // Initial DAG:
-    //     /- gpui2
-    // zed -- crdb - livestreaming - livestreaming_dag
-    let result = db.get_channels_for_user(a_id).await.unwrap();
-    assert_channel_tree(
-        result.channels,
-        &[
-            (zed_id, &[]),
-            (crdb_id, &[zed_id]),
-            (livestreaming_id, &[zed_id, crdb_id]),
-            (livestreaming_dag_id, &[zed_id, crdb_id, livestreaming_id]),
-            (gpui2_id, &[zed_id]),
-        ],
-    );
-}
-
-test_both_dbs!(
-    test_db_channel_moving_bugs,
-    test_db_channel_moving_bugs_postgres,
-    test_db_channel_moving_bugs_sqlite
-);
-
-async fn test_db_channel_moving_bugs(db: &Arc<Database>) {
-    let user_id = db
-        .create_user(
-            "user1@example.com",
-            false,
-            NewUserParams {
-                github_login: "user1".into(),
-                github_user_id: 5,
-            },
-        )
-        .await
-        .unwrap()
-        .user_id;
-
-    let zed_id = db.create_root_channel("zed", user_id).await.unwrap();
-
-    let projects_id = db
-        .create_sub_channel("projects", zed_id, user_id)
-        .await
-        .unwrap();
-
-    let livestreaming_id = db
-        .create_sub_channel("livestreaming", projects_id, user_id)
-        .await
-        .unwrap();
-
-    // Move to same parent should be a no-op
-    assert!(db
-        .move_channel(projects_id, Some(zed_id), user_id)
-        .await
-        .unwrap()
-        .is_none());
-
-    let result = db.get_channels_for_user(user_id).await.unwrap();
-    assert_channel_tree(
-        result.channels,
-        &[
-            (zed_id, &[]),
-            (projects_id, &[zed_id]),
-            (livestreaming_id, &[zed_id, projects_id]),
-        ],
-    );
-
-    // Move the project channel to the root
-    db.move_channel(projects_id, None, user_id).await.unwrap();
-    let result = db.get_channels_for_user(user_id).await.unwrap();
-    assert_channel_tree(
-        result.channels,
-        &[
-            (zed_id, &[]),
-            (projects_id, &[]),
-            (livestreaming_id, &[projects_id]),
-        ],
-    );
-
-    // Can't move a channel into its ancestor
-    db.move_channel(projects_id, Some(livestreaming_id), user_id)
-        .await
-        .unwrap_err();
-    let result = db.get_channels_for_user(user_id).await.unwrap();
-    assert_channel_tree(
-        result.channels,
-        &[
-            (zed_id, &[]),
-            (projects_id, &[]),
-            (livestreaming_id, &[projects_id]),
-        ],
-    );
-}
-
-test_both_dbs!(
-    test_user_is_channel_participant,
-    test_user_is_channel_participant_postgres,
-    test_user_is_channel_participant_sqlite
-);
-
-async fn test_user_is_channel_participant(db: &Arc<Database>) {
-    let admin = new_test_user(db, "admin@example.com").await;
-    let member = new_test_user(db, "member@example.com").await;
-    let guest = new_test_user(db, "guest@example.com").await;
-
-    let zed_channel = db.create_root_channel("zed", admin).await.unwrap();
-    let active_channel_id = db
-        .create_sub_channel("active", zed_channel, admin)
-        .await
-        .unwrap();
-    let vim_channel_id = db
-        .create_sub_channel("vim", active_channel_id, admin)
-        .await
-        .unwrap();
-
-    db.set_channel_visibility(vim_channel_id, crate::db::ChannelVisibility::Public, admin)
-        .await
-        .unwrap();
-    db.invite_channel_member(active_channel_id, member, admin, ChannelRole::Member)
-        .await
-        .unwrap();
-    db.invite_channel_member(vim_channel_id, guest, admin, ChannelRole::Guest)
-        .await
-        .unwrap();
-
-    db.respond_to_channel_invite(active_channel_id, member, true)
-        .await
-        .unwrap();
-
-    db.transaction(|tx| async move {
-        db.check_user_is_channel_participant(
-            &db.get_channel_internal(vim_channel_id, &*tx).await?,
-            admin,
-            &*tx,
-        )
-        .await
-    })
-    .await
-    .unwrap();
-    db.transaction(|tx| async move {
-        db.check_user_is_channel_participant(
-            &db.get_channel_internal(vim_channel_id, &*tx).await?,
-            member,
-            &*tx,
-        )
-        .await
-    })
-    .await
-    .unwrap();
-
-    let mut members = db
-        .get_channel_participant_details(vim_channel_id, admin)
-        .await
-        .unwrap();
-
-    members.sort_by_key(|member| member.user_id);
-
-    assert_eq!(
-        members,
-        &[
-            proto::ChannelMember {
-                user_id: admin.to_proto(),
-                kind: proto::channel_member::Kind::AncestorMember.into(),
-                role: proto::ChannelRole::Admin.into(),
-            },
-            proto::ChannelMember {
-                user_id: member.to_proto(),
-                kind: proto::channel_member::Kind::AncestorMember.into(),
-                role: proto::ChannelRole::Member.into(),
-            },
-            proto::ChannelMember {
-                user_id: guest.to_proto(),
-                kind: proto::channel_member::Kind::Invitee.into(),
-                role: proto::ChannelRole::Guest.into(),
-            },
-        ]
-    );
-
-    db.respond_to_channel_invite(vim_channel_id, guest, true)
-        .await
-        .unwrap();
-
-    db.transaction(|tx| async move {
-        db.check_user_is_channel_participant(
-            &db.get_channel_internal(vim_channel_id, &*tx).await?,
-            guest,
-            &*tx,
-        )
-        .await
-    })
-    .await
-    .unwrap();
-
-    let channels = db.get_channels_for_user(guest).await.unwrap().channels;
-    assert_channel_tree(channels, &[(vim_channel_id, &[])]);
-    let channels = db.get_channels_for_user(member).await.unwrap().channels;
-    assert_channel_tree(
-        channels,
-        &[
-            (active_channel_id, &[]),
-            (vim_channel_id, &[active_channel_id]),
-        ],
-    );
-
-    db.set_channel_member_role(vim_channel_id, admin, guest, ChannelRole::Banned)
-        .await
-        .unwrap();
-    assert!(db
-        .transaction(|tx| async move {
-            db.check_user_is_channel_participant(
-                &db.get_channel_internal(vim_channel_id, &*tx).await.unwrap(),
-                guest,
-                &*tx,
-            )
-            .await
-        })
-        .await
-        .is_err());
-
-    let mut members = db
-        .get_channel_participant_details(vim_channel_id, admin)
-        .await
-        .unwrap();
-
-    members.sort_by_key(|member| member.user_id);
-
-    assert_eq!(
-        members,
-        &[
-            proto::ChannelMember {
-                user_id: admin.to_proto(),
-                kind: proto::channel_member::Kind::AncestorMember.into(),
-                role: proto::ChannelRole::Admin.into(),
-            },
-            proto::ChannelMember {
-                user_id: member.to_proto(),
-                kind: proto::channel_member::Kind::AncestorMember.into(),
-                role: proto::ChannelRole::Member.into(),
-            },
-            proto::ChannelMember {
-                user_id: guest.to_proto(),
-                kind: proto::channel_member::Kind::Member.into(),
-                role: proto::ChannelRole::Banned.into(),
-            },
-        ]
-    );
-
-    db.remove_channel_member(vim_channel_id, guest, admin)
-        .await
-        .unwrap();
-
-    db.set_channel_visibility(zed_channel, crate::db::ChannelVisibility::Public, admin)
-        .await
-        .unwrap();
-
-    db.invite_channel_member(zed_channel, guest, admin, ChannelRole::Guest)
-        .await
-        .unwrap();
-
-    // currently people invited to parent channels are not shown here
-    let mut members = db
-        .get_channel_participant_details(vim_channel_id, admin)
-        .await
-        .unwrap();
-
-    members.sort_by_key(|member| member.user_id);
-
-    assert_eq!(
-        members,
-        &[
-            proto::ChannelMember {
-                user_id: admin.to_proto(),
-                kind: proto::channel_member::Kind::AncestorMember.into(),
-                role: proto::ChannelRole::Admin.into(),
-            },
-            proto::ChannelMember {
-                user_id: member.to_proto(),
-                kind: proto::channel_member::Kind::AncestorMember.into(),
-                role: proto::ChannelRole::Member.into(),
-            },
-        ]
-    );
-
-    db.respond_to_channel_invite(zed_channel, guest, true)
-        .await
-        .unwrap();
-
-    db.transaction(|tx| async move {
-        db.check_user_is_channel_participant(
-            &db.get_channel_internal(zed_channel, &*tx).await.unwrap(),
-            guest,
-            &*tx,
-        )
-        .await
-    })
-    .await
-    .unwrap();
-    assert!(db
-        .transaction(|tx| async move {
-            db.check_user_is_channel_participant(
-                &db.get_channel_internal(active_channel_id, &*tx)
-                    .await
-                    .unwrap(),
-                guest,
-                &*tx,
-            )
-            .await
-        })
-        .await
-        .is_err(),);
-
-    db.transaction(|tx| async move {
-        db.check_user_is_channel_participant(
-            &db.get_channel_internal(vim_channel_id, &*tx).await.unwrap(),
-            guest,
-            &*tx,
-        )
-        .await
-    })
-    .await
-    .unwrap();
-
-    let mut members = db
-        .get_channel_participant_details(vim_channel_id, admin)
-        .await
-        .unwrap();
-
-    members.sort_by_key(|member| member.user_id);
-
-    assert_eq!(
-        members,
-        &[
-            proto::ChannelMember {
-                user_id: admin.to_proto(),
-                kind: proto::channel_member::Kind::AncestorMember.into(),
-                role: proto::ChannelRole::Admin.into(),
-            },
-            proto::ChannelMember {
-                user_id: member.to_proto(),
-                kind: proto::channel_member::Kind::AncestorMember.into(),
-                role: proto::ChannelRole::Member.into(),
-            },
-            proto::ChannelMember {
-                user_id: guest.to_proto(),
-                kind: proto::channel_member::Kind::AncestorMember.into(),
-                role: proto::ChannelRole::Guest.into(),
-            },
-        ]
-    );
-
-    let channels = db.get_channels_for_user(guest).await.unwrap().channels;
-    assert_channel_tree(
-        channels,
-        &[(zed_channel, &[]), (vim_channel_id, &[zed_channel])],
-    )
-}
-
-test_both_dbs!(
-    test_user_joins_correct_channel,
-    test_user_joins_correct_channel_postgres,
-    test_user_joins_correct_channel_sqlite
-);
-
-async fn test_user_joins_correct_channel(db: &Arc<Database>) {
-    let admin = new_test_user(db, "admin@example.com").await;
-
-    let zed_channel = db.create_root_channel("zed", admin).await.unwrap();
-
-    let active_channel = db
-        .create_sub_channel("active", zed_channel, admin)
-        .await
-        .unwrap();
-
-    let vim_channel = db
-        .create_sub_channel("vim", active_channel, admin)
-        .await
-        .unwrap();
-
-    let vim2_channel = db
-        .create_sub_channel("vim2", vim_channel, admin)
-        .await
-        .unwrap();
-
-    db.set_channel_visibility(zed_channel, crate::db::ChannelVisibility::Public, admin)
-        .await
-        .unwrap();
-
-    db.set_channel_visibility(vim_channel, crate::db::ChannelVisibility::Public, admin)
-        .await
-        .unwrap();
-
-    db.set_channel_visibility(vim2_channel, crate::db::ChannelVisibility::Public, admin)
-        .await
-        .unwrap();
-
-    let most_public = db
-        .transaction(|tx| async move {
-            Ok(db
-                .public_ancestors_including_self(
-                    &db.get_channel_internal(vim_channel, &*tx).await.unwrap(),
-                    &tx,
-                )
-                .await?
-                .first()
-                .cloned())
-        })
-        .await
-        .unwrap()
-        .unwrap()
-        .id;
-
-    assert_eq!(most_public, zed_channel)
-}
-
-test_both_dbs!(
-    test_guest_access,
-    test_guest_access_postgres,
-    test_guest_access_sqlite
-);
-
-async fn test_guest_access(db: &Arc<Database>) {
-    let server = db.create_server("test").await.unwrap();
-
-    let admin = new_test_user(db, "admin@example.com").await;
-    let guest = new_test_user(db, "guest@example.com").await;
-    let guest_connection = new_test_connection(server);
-
-    let zed_channel = db.create_root_channel("zed", admin).await.unwrap();
-    db.set_channel_visibility(zed_channel, crate::db::ChannelVisibility::Public, admin)
-        .await
-        .unwrap();
-
-    assert!(db
-        .join_channel_chat(zed_channel, guest_connection, guest)
-        .await
-        .is_err());
-
-    db.join_channel(zed_channel, guest, guest_connection, TEST_RELEASE_CHANNEL)
-        .await
-        .unwrap();
-
-    assert!(db
-        .join_channel_chat(zed_channel, guest_connection, guest)
-        .await
-        .is_ok())
-}
-
-#[track_caller]
-fn assert_channel_tree(actual: Vec<Channel>, expected: &[(ChannelId, &[ChannelId])]) {
-    let actual = actual
-        .iter()
-        .map(|channel| (channel.id, channel.parent_path.as_slice()))
-        .collect::<Vec<_>>();
-    pretty_assertions::assert_eq!(
-        actual,
-        expected.to_vec(),
-        "wrong channel ids and parent paths"
-    );
-}

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

@@ -1,633 +0,0 @@
-use super::*;
-use crate::test_both_dbs;
-use gpui::TestAppContext;
-use pretty_assertions::{assert_eq, assert_ne};
-use std::sync::Arc;
-use tests::TestDb;
-
-test_both_dbs!(
-    test_get_users,
-    test_get_users_by_ids_postgres,
-    test_get_users_by_ids_sqlite
-);
-
-async fn test_get_users(db: &Arc<Database>) {
-    let mut user_ids = Vec::new();
-    let mut user_metric_ids = Vec::new();
-    for i in 1..=4 {
-        let user = db
-            .create_user(
-                &format!("user{i}@example.com"),
-                false,
-                NewUserParams {
-                    github_login: format!("user{i}"),
-                    github_user_id: i,
-                },
-            )
-            .await
-            .unwrap();
-        user_ids.push(user.user_id);
-        user_metric_ids.push(user.metrics_id);
-    }
-
-    assert_eq!(
-        db.get_users_by_ids(user_ids.clone()).await.unwrap(),
-        vec![
-            User {
-                id: user_ids[0],
-                github_login: "user1".to_string(),
-                github_user_id: Some(1),
-                email_address: Some("user1@example.com".to_string()),
-                admin: false,
-                metrics_id: user_metric_ids[0].parse().unwrap(),
-                ..Default::default()
-            },
-            User {
-                id: user_ids[1],
-                github_login: "user2".to_string(),
-                github_user_id: Some(2),
-                email_address: Some("user2@example.com".to_string()),
-                admin: false,
-                metrics_id: user_metric_ids[1].parse().unwrap(),
-                ..Default::default()
-            },
-            User {
-                id: user_ids[2],
-                github_login: "user3".to_string(),
-                github_user_id: Some(3),
-                email_address: Some("user3@example.com".to_string()),
-                admin: false,
-                metrics_id: user_metric_ids[2].parse().unwrap(),
-                ..Default::default()
-            },
-            User {
-                id: user_ids[3],
-                github_login: "user4".to_string(),
-                github_user_id: Some(4),
-                email_address: Some("user4@example.com".to_string()),
-                admin: false,
-                metrics_id: user_metric_ids[3].parse().unwrap(),
-                ..Default::default()
-            }
-        ]
-    );
-}
-
-test_both_dbs!(
-    test_get_or_create_user_by_github_account,
-    test_get_or_create_user_by_github_account_postgres,
-    test_get_or_create_user_by_github_account_sqlite
-);
-
-async fn test_get_or_create_user_by_github_account(db: &Arc<Database>) {
-    let user_id1 = db
-        .create_user(
-            "user1@example.com",
-            false,
-            NewUserParams {
-                github_login: "login1".into(),
-                github_user_id: 101,
-            },
-        )
-        .await
-        .unwrap()
-        .user_id;
-    let user_id2 = db
-        .create_user(
-            "user2@example.com",
-            false,
-            NewUserParams {
-                github_login: "login2".into(),
-                github_user_id: 102,
-            },
-        )
-        .await
-        .unwrap()
-        .user_id;
-
-    let user = db
-        .get_or_create_user_by_github_account("login1", None, None)
-        .await
-        .unwrap()
-        .unwrap();
-    assert_eq!(user.id, user_id1);
-    assert_eq!(&user.github_login, "login1");
-    assert_eq!(user.github_user_id, Some(101));
-
-    assert!(db
-        .get_or_create_user_by_github_account("non-existent-login", None, None)
-        .await
-        .unwrap()
-        .is_none());
-
-    let user = db
-        .get_or_create_user_by_github_account("the-new-login2", Some(102), None)
-        .await
-        .unwrap()
-        .unwrap();
-    assert_eq!(user.id, user_id2);
-    assert_eq!(&user.github_login, "the-new-login2");
-    assert_eq!(user.github_user_id, Some(102));
-
-    let user = db
-        .get_or_create_user_by_github_account("login3", Some(103), Some("user3@example.com"))
-        .await
-        .unwrap()
-        .unwrap();
-    assert_eq!(&user.github_login, "login3");
-    assert_eq!(user.github_user_id, Some(103));
-    assert_eq!(user.email_address, Some("user3@example.com".into()));
-}
-
-test_both_dbs!(
-    test_create_access_tokens,
-    test_create_access_tokens_postgres,
-    test_create_access_tokens_sqlite
-);
-
-async fn test_create_access_tokens(db: &Arc<Database>) {
-    let user = db
-        .create_user(
-            "u1@example.com",
-            false,
-            NewUserParams {
-                github_login: "u1".into(),
-                github_user_id: 1,
-            },
-        )
-        .await
-        .unwrap()
-        .user_id;
-
-    let token_1 = db.create_access_token(user, "h1", 2).await.unwrap();
-    let token_2 = db.create_access_token(user, "h2", 2).await.unwrap();
-    assert_eq!(
-        db.get_access_token(token_1).await.unwrap(),
-        access_token::Model {
-            id: token_1,
-            user_id: user,
-            hash: "h1".into(),
-        }
-    );
-    assert_eq!(
-        db.get_access_token(token_2).await.unwrap(),
-        access_token::Model {
-            id: token_2,
-            user_id: user,
-            hash: "h2".into()
-        }
-    );
-
-    let token_3 = db.create_access_token(user, "h3", 2).await.unwrap();
-    assert_eq!(
-        db.get_access_token(token_3).await.unwrap(),
-        access_token::Model {
-            id: token_3,
-            user_id: user,
-            hash: "h3".into()
-        }
-    );
-    assert_eq!(
-        db.get_access_token(token_2).await.unwrap(),
-        access_token::Model {
-            id: token_2,
-            user_id: user,
-            hash: "h2".into()
-        }
-    );
-    assert!(db.get_access_token(token_1).await.is_err());
-
-    let token_4 = db.create_access_token(user, "h4", 2).await.unwrap();
-    assert_eq!(
-        db.get_access_token(token_4).await.unwrap(),
-        access_token::Model {
-            id: token_4,
-            user_id: user,
-            hash: "h4".into()
-        }
-    );
-    assert_eq!(
-        db.get_access_token(token_3).await.unwrap(),
-        access_token::Model {
-            id: token_3,
-            user_id: user,
-            hash: "h3".into()
-        }
-    );
-    assert!(db.get_access_token(token_2).await.is_err());
-    assert!(db.get_access_token(token_1).await.is_err());
-}
-
-test_both_dbs!(
-    test_add_contacts,
-    test_add_contacts_postgres,
-    test_add_contacts_sqlite
-);
-
-async fn test_add_contacts(db: &Arc<Database>) {
-    let mut user_ids = Vec::new();
-    for i in 0..3 {
-        user_ids.push(
-            db.create_user(
-                &format!("user{i}@example.com"),
-                false,
-                NewUserParams {
-                    github_login: format!("user{i}"),
-                    github_user_id: i,
-                },
-            )
-            .await
-            .unwrap()
-            .user_id,
-        );
-    }
-
-    let user_1 = user_ids[0];
-    let user_2 = user_ids[1];
-    let user_3 = user_ids[2];
-
-    // User starts with no contacts
-    assert_eq!(db.get_contacts(user_1).await.unwrap(), &[]);
-
-    // User requests a contact. Both users see the pending request.
-    db.send_contact_request(user_1, user_2).await.unwrap();
-    assert!(!db.has_contact(user_1, user_2).await.unwrap());
-    assert!(!db.has_contact(user_2, user_1).await.unwrap());
-    assert_eq!(
-        db.get_contacts(user_1).await.unwrap(),
-        &[Contact::Outgoing { user_id: user_2 }],
-    );
-    assert_eq!(
-        db.get_contacts(user_2).await.unwrap(),
-        &[Contact::Incoming { user_id: user_1 }]
-    );
-
-    // User 2 dismisses the contact request notification without accepting or rejecting.
-    // We shouldn't notify them again.
-    db.dismiss_contact_notification(user_1, user_2)
-        .await
-        .unwrap_err();
-    db.dismiss_contact_notification(user_2, user_1)
-        .await
-        .unwrap();
-    assert_eq!(
-        db.get_contacts(user_2).await.unwrap(),
-        &[Contact::Incoming { user_id: user_1 }]
-    );
-
-    // User can't accept their own contact request
-    db.respond_to_contact_request(user_1, user_2, true)
-        .await
-        .unwrap_err();
-
-    // User accepts a contact request. Both users see the contact.
-    db.respond_to_contact_request(user_2, user_1, true)
-        .await
-        .unwrap();
-    assert_eq!(
-        db.get_contacts(user_1).await.unwrap(),
-        &[Contact::Accepted {
-            user_id: user_2,
-            busy: false,
-        }],
-    );
-    assert!(db.has_contact(user_1, user_2).await.unwrap());
-    assert!(db.has_contact(user_2, user_1).await.unwrap());
-    assert_eq!(
-        db.get_contacts(user_2).await.unwrap(),
-        &[Contact::Accepted {
-            user_id: user_1,
-            busy: false,
-        }]
-    );
-
-    // Users cannot re-request existing contacts.
-    db.send_contact_request(user_1, user_2).await.unwrap_err();
-    db.send_contact_request(user_2, user_1).await.unwrap_err();
-
-    // Users can't dismiss notifications of them accepting other users' requests.
-    db.dismiss_contact_notification(user_2, user_1)
-        .await
-        .unwrap_err();
-    assert_eq!(
-        db.get_contacts(user_1).await.unwrap(),
-        &[Contact::Accepted {
-            user_id: user_2,
-            busy: false,
-        }]
-    );
-
-    // Users can dismiss notifications of other users accepting their requests.
-    db.dismiss_contact_notification(user_1, user_2)
-        .await
-        .unwrap();
-    assert_eq!(
-        db.get_contacts(user_1).await.unwrap(),
-        &[Contact::Accepted {
-            user_id: user_2,
-            busy: false,
-        }]
-    );
-
-    // Users send each other concurrent contact requests and
-    // see that they are immediately accepted.
-    db.send_contact_request(user_1, user_3).await.unwrap();
-    db.send_contact_request(user_3, user_1).await.unwrap();
-    assert_eq!(
-        db.get_contacts(user_1).await.unwrap(),
-        &[
-            Contact::Accepted {
-                user_id: user_2,
-                busy: false,
-            },
-            Contact::Accepted {
-                user_id: user_3,
-                busy: false,
-            }
-        ]
-    );
-    assert_eq!(
-        db.get_contacts(user_3).await.unwrap(),
-        &[Contact::Accepted {
-            user_id: user_1,
-            busy: false,
-        }],
-    );
-
-    // User declines a contact request. Both users see that it is gone.
-    db.send_contact_request(user_2, user_3).await.unwrap();
-    db.respond_to_contact_request(user_3, user_2, false)
-        .await
-        .unwrap();
-    assert!(!db.has_contact(user_2, user_3).await.unwrap());
-    assert!(!db.has_contact(user_3, user_2).await.unwrap());
-    assert_eq!(
-        db.get_contacts(user_2).await.unwrap(),
-        &[Contact::Accepted {
-            user_id: user_1,
-            busy: false,
-        }]
-    );
-    assert_eq!(
-        db.get_contacts(user_3).await.unwrap(),
-        &[Contact::Accepted {
-            user_id: user_1,
-            busy: false,
-        }],
-    );
-}
-
-test_both_dbs!(
-    test_metrics_id,
-    test_metrics_id_postgres,
-    test_metrics_id_sqlite
-);
-
-async fn test_metrics_id(db: &Arc<Database>) {
-    let NewUserResult {
-        user_id: user1,
-        metrics_id: metrics_id1,
-        ..
-    } = db
-        .create_user(
-            "person1@example.com",
-            false,
-            NewUserParams {
-                github_login: "person1".into(),
-                github_user_id: 101,
-            },
-        )
-        .await
-        .unwrap();
-    let NewUserResult {
-        user_id: user2,
-        metrics_id: metrics_id2,
-        ..
-    } = db
-        .create_user(
-            "person2@example.com",
-            false,
-            NewUserParams {
-                github_login: "person2".into(),
-                github_user_id: 102,
-            },
-        )
-        .await
-        .unwrap();
-
-    assert_eq!(db.get_user_metrics_id(user1).await.unwrap(), metrics_id1);
-    assert_eq!(db.get_user_metrics_id(user2).await.unwrap(), metrics_id2);
-    assert_eq!(metrics_id1.len(), 36);
-    assert_eq!(metrics_id2.len(), 36);
-    assert_ne!(metrics_id1, metrics_id2);
-}
-
-test_both_dbs!(
-    test_project_count,
-    test_project_count_postgres,
-    test_project_count_sqlite
-);
-
-async fn test_project_count(db: &Arc<Database>) {
-    let owner_id = db.create_server("test").await.unwrap().0 as u32;
-
-    let user1 = db
-        .create_user(
-            &format!("admin@example.com"),
-            true,
-            NewUserParams {
-                github_login: "admin".into(),
-                github_user_id: 0,
-            },
-        )
-        .await
-        .unwrap();
-    let user2 = db
-        .create_user(
-            &format!("user@example.com"),
-            false,
-            NewUserParams {
-                github_login: "user".into(),
-                github_user_id: 1,
-            },
-        )
-        .await
-        .unwrap();
-
-    let room_id = RoomId::from_proto(
-        db.create_room(user1.user_id, ConnectionId { owner_id, id: 0 }, "", "dev")
-            .await
-            .unwrap()
-            .id,
-    );
-    db.call(
-        room_id,
-        user1.user_id,
-        ConnectionId { owner_id, id: 0 },
-        user2.user_id,
-        None,
-    )
-    .await
-    .unwrap();
-    db.join_room(
-        room_id,
-        user2.user_id,
-        ConnectionId { owner_id, id: 1 },
-        "dev",
-    )
-    .await
-    .unwrap();
-    assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
-
-    db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[])
-        .await
-        .unwrap();
-    assert_eq!(db.project_count_excluding_admins().await.unwrap(), 1);
-
-    db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[])
-        .await
-        .unwrap();
-    assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
-
-    // Projects shared by admins aren't counted.
-    db.share_project(room_id, ConnectionId { owner_id, id: 0 }, &[])
-        .await
-        .unwrap();
-    assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
-
-    db.leave_room(ConnectionId { owner_id, id: 1 })
-        .await
-        .unwrap();
-    assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
-}
-
-#[test]
-fn test_fuzzy_like_string() {
-    assert_eq!(Database::fuzzy_like_string("abcd"), "%a%b%c%d%");
-    assert_eq!(Database::fuzzy_like_string("x y"), "%x%y%");
-    assert_eq!(Database::fuzzy_like_string(" z  "), "%z%");
-}
-
-#[gpui::test]
-async fn test_fuzzy_search_users(cx: &mut TestAppContext) {
-    let test_db = TestDb::postgres(cx.executor());
-    let db = test_db.db();
-    for (i, github_login) in [
-        "California",
-        "colorado",
-        "oregon",
-        "washington",
-        "florida",
-        "delaware",
-        "rhode-island",
-    ]
-    .into_iter()
-    .enumerate()
-    {
-        db.create_user(
-            &format!("{github_login}@example.com"),
-            false,
-            NewUserParams {
-                github_login: github_login.into(),
-                github_user_id: i as i32,
-            },
-        )
-        .await
-        .unwrap();
-    }
-
-    assert_eq!(
-        fuzzy_search_user_names(db, "clr").await,
-        &["colorado", "California"]
-    );
-    assert_eq!(
-        fuzzy_search_user_names(db, "ro").await,
-        &["rhode-island", "colorado", "oregon"],
-    );
-
-    async fn fuzzy_search_user_names(db: &Database, query: &str) -> Vec<String> {
-        db.fuzzy_search_users(query, 10)
-            .await
-            .unwrap()
-            .into_iter()
-            .map(|user| user.github_login)
-            .collect::<Vec<_>>()
-    }
-}
-
-test_both_dbs!(
-    test_non_matching_release_channels,
-    test_non_matching_release_channels_postgres,
-    test_non_matching_release_channels_sqlite
-);
-
-async fn test_non_matching_release_channels(db: &Arc<Database>) {
-    let owner_id = db.create_server("test").await.unwrap().0 as u32;
-
-    let user1 = db
-        .create_user(
-            &format!("admin@example.com"),
-            true,
-            NewUserParams {
-                github_login: "admin".into(),
-                github_user_id: 0,
-            },
-        )
-        .await
-        .unwrap();
-    let user2 = db
-        .create_user(
-            &format!("user@example.com"),
-            false,
-            NewUserParams {
-                github_login: "user".into(),
-                github_user_id: 1,
-            },
-        )
-        .await
-        .unwrap();
-
-    let room = db
-        .create_room(
-            user1.user_id,
-            ConnectionId { owner_id, id: 0 },
-            "",
-            "stable",
-        )
-        .await
-        .unwrap();
-
-    db.call(
-        RoomId::from_proto(room.id),
-        user1.user_id,
-        ConnectionId { owner_id, id: 0 },
-        user2.user_id,
-        None,
-    )
-    .await
-    .unwrap();
-
-    // User attempts to join from preview
-    let result = db
-        .join_room(
-            RoomId::from_proto(room.id),
-            user2.user_id,
-            ConnectionId { owner_id, id: 1 },
-            "preview",
-        )
-        .await;
-
-    assert!(result.is_err());
-
-    // User switches to stable
-    let result = db
-        .join_room(
-            RoomId::from_proto(room.id),
-            user2.user_id,
-            ConnectionId { owner_id, id: 1 },
-            "stable",
-        )
-        .await;
-
-    assert!(result.is_ok())
-}

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

@@ -1,58 +0,0 @@
-use crate::{
-    db::{Database, NewUserParams},
-    test_both_dbs,
-};
-use std::sync::Arc;
-
-test_both_dbs!(
-    test_get_user_flags,
-    test_get_user_flags_postgres,
-    test_get_user_flags_sqlite
-);
-
-async fn test_get_user_flags(db: &Arc<Database>) {
-    let user_1 = db
-        .create_user(
-            &format!("user1@example.com"),
-            false,
-            NewUserParams {
-                github_login: format!("user1"),
-                github_user_id: 1,
-            },
-        )
-        .await
-        .unwrap()
-        .user_id;
-
-    let user_2 = db
-        .create_user(
-            &format!("user2@example.com"),
-            false,
-            NewUserParams {
-                github_login: format!("user2"),
-                github_user_id: 2,
-            },
-        )
-        .await
-        .unwrap()
-        .user_id;
-
-    const CHANNELS_ALPHA: &'static str = "channels-alpha";
-    const NEW_SEARCH: &'static str = "new-search";
-
-    let channels_flag = db.create_user_flag(CHANNELS_ALPHA).await.unwrap();
-    let search_flag = db.create_user_flag(NEW_SEARCH).await.unwrap();
-
-    db.add_user_flag(user_1, channels_flag).await.unwrap();
-    db.add_user_flag(user_1, search_flag).await.unwrap();
-
-    db.add_user_flag(user_2, channels_flag).await.unwrap();
-
-    let mut user_1_flags = db.get_user_flags(user_1).await.unwrap();
-    user_1_flags.sort();
-    assert_eq!(user_1_flags, &[CHANNELS_ALPHA, NEW_SEARCH]);
-
-    let mut user_2_flags = db.get_user_flags(user_2).await.unwrap();
-    user_2_flags.sort();
-    assert_eq!(user_2_flags, &[CHANNELS_ALPHA]);
-}

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

@@ -1,454 +0,0 @@
-use super::new_test_user;
-use crate::{
-    db::{ChannelRole, Database, MessageId},
-    test_both_dbs,
-};
-use channel::mentions_to_proto;
-use std::sync::Arc;
-use time::OffsetDateTime;
-
-test_both_dbs!(
-    test_channel_message_retrieval,
-    test_channel_message_retrieval_postgres,
-    test_channel_message_retrieval_sqlite
-);
-
-async fn test_channel_message_retrieval(db: &Arc<Database>) {
-    let user = new_test_user(db, "user@example.com").await;
-    let result = db.create_channel("channel", None, user).await.unwrap();
-
-    let owner_id = db.create_server("test").await.unwrap().0 as u32;
-    db.join_channel_chat(
-        result.channel.id,
-        rpc::ConnectionId { owner_id, id: 0 },
-        user,
-    )
-    .await
-    .unwrap();
-
-    let mut all_messages = Vec::new();
-    for i in 0..10 {
-        all_messages.push(
-            db.create_channel_message(
-                result.channel.id,
-                user,
-                &i.to_string(),
-                &[],
-                OffsetDateTime::now_utc(),
-                i,
-            )
-            .await
-            .unwrap()
-            .message_id
-            .to_proto(),
-        );
-    }
-
-    let messages = db
-        .get_channel_messages(result.channel.id, user, 3, None)
-        .await
-        .unwrap()
-        .into_iter()
-        .map(|message| message.id)
-        .collect::<Vec<_>>();
-    assert_eq!(messages, &all_messages[7..10]);
-
-    let messages = db
-        .get_channel_messages(
-            result.channel.id,
-            user,
-            4,
-            Some(MessageId::from_proto(all_messages[6])),
-        )
-        .await
-        .unwrap()
-        .into_iter()
-        .map(|message| message.id)
-        .collect::<Vec<_>>();
-    assert_eq!(messages, &all_messages[2..6]);
-}
-
-test_both_dbs!(
-    test_channel_message_nonces,
-    test_channel_message_nonces_postgres,
-    test_channel_message_nonces_sqlite
-);
-
-async fn test_channel_message_nonces(db: &Arc<Database>) {
-    let user_a = new_test_user(db, "user_a@example.com").await;
-    let user_b = new_test_user(db, "user_b@example.com").await;
-    let user_c = new_test_user(db, "user_c@example.com").await;
-    let channel = db.create_root_channel("channel", user_a).await.unwrap();
-    db.invite_channel_member(channel, user_b, user_a, ChannelRole::Member)
-        .await
-        .unwrap();
-    db.invite_channel_member(channel, user_c, user_a, ChannelRole::Member)
-        .await
-        .unwrap();
-    db.respond_to_channel_invite(channel, user_b, true)
-        .await
-        .unwrap();
-    db.respond_to_channel_invite(channel, user_c, true)
-        .await
-        .unwrap();
-
-    let owner_id = db.create_server("test").await.unwrap().0 as u32;
-    db.join_channel_chat(channel, rpc::ConnectionId { owner_id, id: 0 }, user_a)
-        .await
-        .unwrap();
-    db.join_channel_chat(channel, rpc::ConnectionId { owner_id, id: 1 }, user_b)
-        .await
-        .unwrap();
-
-    // As user A, create messages that re-use the same nonces. The requests
-    // succeed, but return the same ids.
-    let id1 = db
-        .create_channel_message(
-            channel,
-            user_a,
-            "hi @user_b",
-            &mentions_to_proto(&[(3..10, user_b.to_proto())]),
-            OffsetDateTime::now_utc(),
-            100,
-        )
-        .await
-        .unwrap()
-        .message_id;
-    let id2 = db
-        .create_channel_message(
-            channel,
-            user_a,
-            "hello, fellow users",
-            &mentions_to_proto(&[]),
-            OffsetDateTime::now_utc(),
-            200,
-        )
-        .await
-        .unwrap()
-        .message_id;
-    let id3 = db
-        .create_channel_message(
-            channel,
-            user_a,
-            "bye @user_c (same nonce as first message)",
-            &mentions_to_proto(&[(4..11, user_c.to_proto())]),
-            OffsetDateTime::now_utc(),
-            100,
-        )
-        .await
-        .unwrap()
-        .message_id;
-    let id4 = db
-        .create_channel_message(
-            channel,
-            user_a,
-            "omg (same nonce as second message)",
-            &mentions_to_proto(&[]),
-            OffsetDateTime::now_utc(),
-            200,
-        )
-        .await
-        .unwrap()
-        .message_id;
-
-    // As a different user, reuse one of the same nonces. This request succeeds
-    // and returns a different id.
-    let id5 = db
-        .create_channel_message(
-            channel,
-            user_b,
-            "omg @user_a (same nonce as user_a's first message)",
-            &mentions_to_proto(&[(4..11, user_a.to_proto())]),
-            OffsetDateTime::now_utc(),
-            100,
-        )
-        .await
-        .unwrap()
-        .message_id;
-
-    assert_ne!(id1, id2);
-    assert_eq!(id1, id3);
-    assert_eq!(id2, id4);
-    assert_ne!(id5, id1);
-
-    let messages = db
-        .get_channel_messages(channel, user_a, 5, None)
-        .await
-        .unwrap()
-        .into_iter()
-        .map(|m| (m.id, m.body, m.mentions))
-        .collect::<Vec<_>>();
-    assert_eq!(
-        messages,
-        &[
-            (
-                id1.to_proto(),
-                "hi @user_b".into(),
-                mentions_to_proto(&[(3..10, user_b.to_proto())]),
-            ),
-            (
-                id2.to_proto(),
-                "hello, fellow users".into(),
-                mentions_to_proto(&[])
-            ),
-            (
-                id5.to_proto(),
-                "omg @user_a (same nonce as user_a's first message)".into(),
-                mentions_to_proto(&[(4..11, user_a.to_proto())]),
-            ),
-        ]
-    );
-}
-
-test_both_dbs!(
-    test_unseen_channel_messages,
-    test_unseen_channel_messages_postgres,
-    test_unseen_channel_messages_sqlite
-);
-
-async fn test_unseen_channel_messages(db: &Arc<Database>) {
-    let user = new_test_user(db, "user_a@example.com").await;
-    let observer = new_test_user(db, "user_b@example.com").await;
-
-    let channel_1 = db.create_root_channel("channel", user).await.unwrap();
-    let channel_2 = db.create_root_channel("channel-2", user).await.unwrap();
-
-    db.invite_channel_member(channel_1, observer, user, ChannelRole::Member)
-        .await
-        .unwrap();
-    db.invite_channel_member(channel_2, observer, user, ChannelRole::Member)
-        .await
-        .unwrap();
-
-    db.respond_to_channel_invite(channel_1, observer, true)
-        .await
-        .unwrap();
-    db.respond_to_channel_invite(channel_2, observer, true)
-        .await
-        .unwrap();
-
-    let owner_id = db.create_server("test").await.unwrap().0 as u32;
-    let user_connection_id = rpc::ConnectionId { owner_id, id: 0 };
-
-    db.join_channel_chat(channel_1, user_connection_id, user)
-        .await
-        .unwrap();
-
-    let _ = db
-        .create_channel_message(channel_1, user, "1_1", &[], OffsetDateTime::now_utc(), 1)
-        .await
-        .unwrap();
-
-    let second_message = db
-        .create_channel_message(channel_1, user, "1_2", &[], OffsetDateTime::now_utc(), 2)
-        .await
-        .unwrap()
-        .message_id;
-
-    let third_message = db
-        .create_channel_message(channel_1, user, "1_3", &[], OffsetDateTime::now_utc(), 3)
-        .await
-        .unwrap()
-        .message_id;
-
-    db.join_channel_chat(channel_2, user_connection_id, user)
-        .await
-        .unwrap();
-
-    let fourth_message = db
-        .create_channel_message(channel_2, user, "2_1", &[], OffsetDateTime::now_utc(), 4)
-        .await
-        .unwrap()
-        .message_id;
-
-    // Check that observer has new messages
-    let unseen_messages = db
-        .transaction(|tx| async move {
-            db.unseen_channel_messages(observer, &[channel_1, channel_2], &*tx)
-                .await
-        })
-        .await
-        .unwrap();
-
-    assert_eq!(
-        unseen_messages,
-        [
-            rpc::proto::UnseenChannelMessage {
-                channel_id: channel_1.to_proto(),
-                message_id: third_message.to_proto(),
-            },
-            rpc::proto::UnseenChannelMessage {
-                channel_id: channel_2.to_proto(),
-                message_id: fourth_message.to_proto(),
-            },
-        ]
-    );
-
-    // Observe the second message
-    db.observe_channel_message(channel_1, observer, second_message)
-        .await
-        .unwrap();
-
-    // Make sure the observer still has a new message
-    let unseen_messages = db
-        .transaction(|tx| async move {
-            db.unseen_channel_messages(observer, &[channel_1, channel_2], &*tx)
-                .await
-        })
-        .await
-        .unwrap();
-    assert_eq!(
-        unseen_messages,
-        [
-            rpc::proto::UnseenChannelMessage {
-                channel_id: channel_1.to_proto(),
-                message_id: third_message.to_proto(),
-            },
-            rpc::proto::UnseenChannelMessage {
-                channel_id: channel_2.to_proto(),
-                message_id: fourth_message.to_proto(),
-            },
-        ]
-    );
-
-    // Observe the third message,
-    db.observe_channel_message(channel_1, observer, third_message)
-        .await
-        .unwrap();
-
-    // Make sure the observer does not have a new method
-    let unseen_messages = db
-        .transaction(|tx| async move {
-            db.unseen_channel_messages(observer, &[channel_1, channel_2], &*tx)
-                .await
-        })
-        .await
-        .unwrap();
-
-    assert_eq!(
-        unseen_messages,
-        [rpc::proto::UnseenChannelMessage {
-            channel_id: channel_2.to_proto(),
-            message_id: fourth_message.to_proto(),
-        }]
-    );
-
-    // Observe the second message again, should not regress our observed state
-    db.observe_channel_message(channel_1, observer, second_message)
-        .await
-        .unwrap();
-
-    // Make sure the observer does not have a new message
-    let unseen_messages = db
-        .transaction(|tx| async move {
-            db.unseen_channel_messages(observer, &[channel_1, channel_2], &*tx)
-                .await
-        })
-        .await
-        .unwrap();
-    assert_eq!(
-        unseen_messages,
-        [rpc::proto::UnseenChannelMessage {
-            channel_id: channel_2.to_proto(),
-            message_id: fourth_message.to_proto(),
-        }]
-    );
-}
-
-test_both_dbs!(
-    test_channel_message_mentions,
-    test_channel_message_mentions_postgres,
-    test_channel_message_mentions_sqlite
-);
-
-async fn test_channel_message_mentions(db: &Arc<Database>) {
-    let user_a = new_test_user(db, "user_a@example.com").await;
-    let user_b = new_test_user(db, "user_b@example.com").await;
-    let user_c = new_test_user(db, "user_c@example.com").await;
-
-    let channel = db
-        .create_channel("channel", None, user_a)
-        .await
-        .unwrap()
-        .channel
-        .id;
-    db.invite_channel_member(channel, user_b, user_a, ChannelRole::Member)
-        .await
-        .unwrap();
-    db.respond_to_channel_invite(channel, user_b, true)
-        .await
-        .unwrap();
-
-    let owner_id = db.create_server("test").await.unwrap().0 as u32;
-    let connection_id = rpc::ConnectionId { owner_id, id: 0 };
-    db.join_channel_chat(channel, connection_id, user_a)
-        .await
-        .unwrap();
-
-    db.create_channel_message(
-        channel,
-        user_a,
-        "hi @user_b and @user_c",
-        &mentions_to_proto(&[(3..10, user_b.to_proto()), (15..22, user_c.to_proto())]),
-        OffsetDateTime::now_utc(),
-        1,
-    )
-    .await
-    .unwrap();
-    db.create_channel_message(
-        channel,
-        user_a,
-        "bye @user_c",
-        &mentions_to_proto(&[(4..11, user_c.to_proto())]),
-        OffsetDateTime::now_utc(),
-        2,
-    )
-    .await
-    .unwrap();
-    db.create_channel_message(
-        channel,
-        user_a,
-        "umm",
-        &mentions_to_proto(&[]),
-        OffsetDateTime::now_utc(),
-        3,
-    )
-    .await
-    .unwrap();
-    db.create_channel_message(
-        channel,
-        user_a,
-        "@user_b, stop.",
-        &mentions_to_proto(&[(0..7, user_b.to_proto())]),
-        OffsetDateTime::now_utc(),
-        4,
-    )
-    .await
-    .unwrap();
-
-    let messages = db
-        .get_channel_messages(channel, user_b, 5, None)
-        .await
-        .unwrap()
-        .into_iter()
-        .map(|m| (m.body, m.mentions))
-        .collect::<Vec<_>>();
-    assert_eq!(
-        &messages,
-        &[
-            (
-                "hi @user_b and @user_c".into(),
-                mentions_to_proto(&[(3..10, user_b.to_proto()), (15..22, user_c.to_proto())]),
-            ),
-            (
-                "bye @user_c".into(),
-                mentions_to_proto(&[(4..11, user_c.to_proto())]),
-            ),
-            ("umm".into(), mentions_to_proto(&[]),),
-            (
-                "@user_b, stop.".into(),
-                mentions_to_proto(&[(0..7, user_b.to_proto())]),
-            ),
-        ]
-    );
-}

crates/collab2/src/env.rs 🔗

@@ -1,20 +0,0 @@
-use anyhow::anyhow;
-use std::fs;
-
-pub fn load_dotenv() -> anyhow::Result<()> {
-    let env: toml::map::Map<String, toml::Value> = toml::de::from_str(
-        &fs::read_to_string("./.env.toml").map_err(|_| anyhow!("no .env.toml file found"))?,
-    )?;
-
-    for (key, value) in env {
-        let value = match value {
-            toml::Value::String(value) => value,
-            toml::Value::Integer(value) => value.to_string(),
-            toml::Value::Float(value) => value.to_string(),
-            _ => panic!("unsupported TOML value in .env.toml for key {}", key),
-        };
-        std::env::set_var(key, value);
-    }
-
-    Ok(())
-}

crates/collab2/src/errors.rs 🔗

@@ -1,29 +0,0 @@
-// Allow tide Results to accept context like other Results do when
-// using anyhow.
-pub trait TideResultExt {
-    fn context<C>(self, cx: C) -> Self
-    where
-        C: std::fmt::Display + Send + Sync + 'static;
-
-    fn with_context<C, F>(self, f: F) -> Self
-    where
-        C: std::fmt::Display + Send + Sync + 'static,
-        F: FnOnce() -> C;
-}
-
-impl<T> TideResultExt for tide::Result<T> {
-    fn context<C>(self, cx: C) -> Self
-    where
-        C: std::fmt::Display + Send + Sync + 'static,
-    {
-        self.map_err(|e| tide::Error::new(e.status(), e.into_inner().context(cx)))
-    }
-
-    fn with_context<C, F>(self, f: F) -> Self
-    where
-        C: std::fmt::Display + Send + Sync + 'static,
-        F: FnOnce() -> C,
-    {
-        self.map_err(|e| tide::Error::new(e.status(), e.into_inner().context(f())))
-    }
-}

crates/collab2/src/executor.rs 🔗

@@ -1,39 +0,0 @@
-use std::{future::Future, time::Duration};
-
-#[cfg(test)]
-use gpui::BackgroundExecutor;
-
-#[derive(Clone)]
-pub enum Executor {
-    Production,
-    #[cfg(test)]
-    Deterministic(BackgroundExecutor),
-}
-
-impl Executor {
-    pub fn spawn_detached<F>(&self, future: F)
-    where
-        F: 'static + Send + Future<Output = ()>,
-    {
-        match self {
-            Executor::Production => {
-                tokio::spawn(future);
-            }
-            #[cfg(test)]
-            Executor::Deterministic(background) => {
-                background.spawn(future).detach();
-            }
-        }
-    }
-
-    pub fn sleep(&self, duration: Duration) -> impl Future<Output = ()> {
-        let this = self.clone();
-        async move {
-            match this {
-                Executor::Production => tokio::time::sleep(duration).await,
-                #[cfg(test)]
-                Executor::Deterministic(background) => background.timer(duration).await,
-            }
-        }
-    }
-}

crates/collab2/src/lib.rs 🔗

@@ -1,147 +0,0 @@
-pub mod api;
-pub mod auth;
-pub mod db;
-pub mod env;
-pub mod executor;
-pub mod rpc;
-
-#[cfg(test)]
-mod tests;
-
-use axum::{http::StatusCode, response::IntoResponse};
-use db::Database;
-use executor::Executor;
-use serde::Deserialize;
-use std::{path::PathBuf, sync::Arc};
-
-pub type Result<T, E = Error> = std::result::Result<T, E>;
-
-pub enum Error {
-    Http(StatusCode, String),
-    Database(sea_orm::error::DbErr),
-    Internal(anyhow::Error),
-}
-
-impl From<anyhow::Error> for Error {
-    fn from(error: anyhow::Error) -> Self {
-        Self::Internal(error)
-    }
-}
-
-impl From<sea_orm::error::DbErr> for Error {
-    fn from(error: sea_orm::error::DbErr) -> Self {
-        Self::Database(error)
-    }
-}
-
-impl From<axum::Error> for Error {
-    fn from(error: axum::Error) -> Self {
-        Self::Internal(error.into())
-    }
-}
-
-impl From<hyper::Error> for Error {
-    fn from(error: hyper::Error) -> Self {
-        Self::Internal(error.into())
-    }
-}
-
-impl From<serde_json::Error> for Error {
-    fn from(error: serde_json::Error) -> Self {
-        Self::Internal(error.into())
-    }
-}
-
-impl IntoResponse for Error {
-    fn into_response(self) -> axum::response::Response {
-        match self {
-            Error::Http(code, message) => (code, message).into_response(),
-            Error::Database(error) => {
-                (StatusCode::INTERNAL_SERVER_ERROR, format!("{}", &error)).into_response()
-            }
-            Error::Internal(error) => {
-                (StatusCode::INTERNAL_SERVER_ERROR, format!("{}", &error)).into_response()
-            }
-        }
-    }
-}
-
-impl std::fmt::Debug for Error {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            Error::Http(code, message) => (code, message).fmt(f),
-            Error::Database(error) => error.fmt(f),
-            Error::Internal(error) => error.fmt(f),
-        }
-    }
-}
-
-impl std::fmt::Display for Error {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            Error::Http(code, message) => write!(f, "{code}: {message}"),
-            Error::Database(error) => error.fmt(f),
-            Error::Internal(error) => error.fmt(f),
-        }
-    }
-}
-
-impl std::error::Error for Error {}
-
-#[derive(Default, Deserialize)]
-pub struct Config {
-    pub http_port: u16,
-    pub database_url: String,
-    pub database_max_connections: u32,
-    pub api_token: String,
-    pub invite_link_prefix: String,
-    pub live_kit_server: Option<String>,
-    pub live_kit_key: Option<String>,
-    pub live_kit_secret: Option<String>,
-    pub rust_log: Option<String>,
-    pub log_json: Option<bool>,
-    pub zed_environment: String,
-}
-
-#[derive(Default, Deserialize)]
-pub struct MigrateConfig {
-    pub database_url: String,
-    pub migrations_path: Option<PathBuf>,
-}
-
-pub struct AppState {
-    pub db: Arc<Database>,
-    pub live_kit_client: Option<Arc<dyn live_kit_server::api::Client>>,
-    pub config: Config,
-}
-
-impl AppState {
-    pub async fn new(config: Config) -> Result<Arc<Self>> {
-        let mut db_options = db::ConnectOptions::new(config.database_url.clone());
-        db_options.max_connections(config.database_max_connections);
-        let mut db = Database::new(db_options, Executor::Production).await?;
-        db.initialize_notification_kinds().await?;
-
-        let live_kit_client = if let Some(((server, key), secret)) = config
-            .live_kit_server
-            .as_ref()
-            .zip(config.live_kit_key.as_ref())
-            .zip(config.live_kit_secret.as_ref())
-        {
-            Some(Arc::new(live_kit_server::api::LiveKitClient::new(
-                server.clone(),
-                key.clone(),
-                secret.clone(),
-            )) as Arc<dyn live_kit_server::api::Client>)
-        } else {
-            None
-        };
-
-        let this = Self {
-            db: Arc::new(db),
-            live_kit_client,
-            config,
-        };
-        Ok(Arc::new(this))
-    }
-}

crates/collab2/src/main.rs 🔗

@@ -1,139 +0,0 @@
-use anyhow::anyhow;
-use axum::{routing::get, Extension, Router};
-use collab2::{db, env, executor::Executor, AppState, Config, MigrateConfig, Result};
-use db::Database;
-use std::{
-    env::args,
-    net::{SocketAddr, TcpListener},
-    path::Path,
-    sync::Arc,
-};
-use tokio::signal::unix::SignalKind;
-use tracing_log::LogTracer;
-use tracing_subscriber::{filter::EnvFilter, fmt::format::JsonFields, Layer};
-use util::ResultExt;
-
-const VERSION: &'static str = env!("CARGO_PKG_VERSION");
-
-#[tokio::main]
-async fn main() -> Result<()> {
-    if let Err(error) = env::load_dotenv() {
-        eprintln!(
-            "error loading .env.toml (this is expected in production): {}",
-            error
-        );
-    }
-
-    match args().skip(1).next().as_deref() {
-        Some("version") => {
-            println!("collab v{VERSION}");
-        }
-        Some("migrate") => {
-            let config = envy::from_env::<MigrateConfig>().expect("error loading config");
-            let mut db_options = db::ConnectOptions::new(config.database_url.clone());
-            db_options.max_connections(5);
-            let db = Database::new(db_options, Executor::Production).await?;
-
-            let migrations_path = config
-                .migrations_path
-                .as_deref()
-                .unwrap_or_else(|| Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/migrations")));
-
-            let migrations = db.migrate(&migrations_path, false).await?;
-            for (migration, duration) in migrations {
-                println!(
-                    "Ran {} {} {:?}",
-                    migration.version, migration.description, duration
-                );
-            }
-
-            return Ok(());
-        }
-        Some("serve") => {
-            let config = envy::from_env::<Config>().expect("error loading config");
-            init_tracing(&config);
-
-            let state = AppState::new(config).await?;
-
-            let listener = TcpListener::bind(&format!("0.0.0.0:{}", state.config.http_port))
-                .expect("failed to bind TCP listener");
-
-            let epoch = state
-                .db
-                .create_server(&state.config.zed_environment)
-                .await?;
-            let rpc_server = collab2::rpc::Server::new(epoch, state.clone(), Executor::Production);
-            rpc_server.start().await?;
-
-            let app = collab2::api::routes(rpc_server.clone(), state.clone())
-                .merge(collab2::rpc::routes(rpc_server.clone()))
-                .merge(
-                    Router::new()
-                        .route("/", get(handle_root))
-                        .route("/healthz", get(handle_liveness_probe))
-                        .layer(Extension(state.clone())),
-                );
-
-            axum::Server::from_tcp(listener)?
-                .serve(app.into_make_service_with_connect_info::<SocketAddr>())
-                .with_graceful_shutdown(async move {
-                    let mut sigterm = tokio::signal::unix::signal(SignalKind::terminate())
-                        .expect("failed to listen for interrupt signal");
-                    let mut sigint = tokio::signal::unix::signal(SignalKind::interrupt())
-                        .expect("failed to listen for interrupt signal");
-                    let sigterm = sigterm.recv();
-                    let sigint = sigint.recv();
-                    futures::pin_mut!(sigterm, sigint);
-                    futures::future::select(sigterm, sigint).await;
-                    tracing::info!("Received interrupt signal");
-                    rpc_server.teardown();
-                })
-                .await?;
-        }
-        _ => {
-            Err(anyhow!("usage: collab <version | migrate | serve>"))?;
-        }
-    }
-    Ok(())
-}
-
-async fn handle_root() -> String {
-    format!("collab v{VERSION}")
-}
-
-async fn handle_liveness_probe(Extension(state): Extension<Arc<AppState>>) -> Result<String> {
-    state.db.get_all_users(0, 1).await?;
-    Ok("ok".to_string())
-}
-
-pub fn init_tracing(config: &Config) -> Option<()> {
-    use std::str::FromStr;
-    use tracing_subscriber::layer::SubscriberExt;
-    let rust_log = config.rust_log.clone()?;
-
-    LogTracer::init().log_err()?;
-
-    let subscriber = tracing_subscriber::Registry::default()
-        .with(if config.log_json.unwrap_or(false) {
-            Box::new(
-                tracing_subscriber::fmt::layer()
-                    .fmt_fields(JsonFields::default())
-                    .event_format(
-                        tracing_subscriber::fmt::format()
-                            .json()
-                            .flatten_event(true)
-                            .with_span_list(true),
-                    ),
-            ) as Box<dyn Layer<_> + Send + Sync>
-        } else {
-            Box::new(
-                tracing_subscriber::fmt::layer()
-                    .event_format(tracing_subscriber::fmt::format().pretty()),
-            )
-        })
-        .with(EnvFilter::from_str(rust_log.as_str()).log_err()?);
-
-    tracing::subscriber::set_global_default(subscriber).unwrap();
-
-    None
-}

crates/collab2/src/rpc.rs 🔗

@@ -1,3495 +0,0 @@
-mod connection_pool;
-
-use crate::{
-    auth,
-    db::{
-        self, BufferId, ChannelId, ChannelRole, ChannelsForUser, CreateChannelResult,
-        CreatedChannelMessage, Database, InviteMemberResult, MembershipUpdated, MessageId,
-        MoveChannelResult, NotificationId, ProjectId, RemoveChannelMemberResult,
-        RenameChannelResult, RespondToChannelInvite, RoomId, ServerId, SetChannelVisibilityResult,
-        User, UserId,
-    },
-    executor::Executor,
-    AppState, Result,
-};
-use anyhow::anyhow;
-use async_tungstenite::tungstenite::{
-    protocol::CloseFrame as TungsteniteCloseFrame, Message as TungsteniteMessage,
-};
-use axum::{
-    body::Body,
-    extract::{
-        ws::{CloseFrame as AxumCloseFrame, Message as AxumMessage},
-        ConnectInfo, WebSocketUpgrade,
-    },
-    headers::{Header, HeaderName},
-    http::StatusCode,
-    middleware,
-    response::IntoResponse,
-    routing::get,
-    Extension, Router, TypedHeader,
-};
-use collections::{HashMap, HashSet};
-pub use connection_pool::ConnectionPool;
-use futures::{
-    channel::oneshot,
-    future::{self, BoxFuture},
-    stream::FuturesUnordered,
-    FutureExt, SinkExt, StreamExt, TryStreamExt,
-};
-use lazy_static::lazy_static;
-use prometheus::{register_int_gauge, IntGauge};
-use rpc::{
-    proto::{
-        self, Ack, AnyTypedEnvelope, EntityMessage, EnvelopedMessage, LiveKitConnectionInfo,
-        RequestMessage, UpdateChannelBufferCollaborators,
-    },
-    Connection, ConnectionId, Peer, Receipt, TypedEnvelope,
-};
-use serde::{Serialize, Serializer};
-use std::{
-    any::TypeId,
-    fmt,
-    future::Future,
-    marker::PhantomData,
-    mem,
-    net::SocketAddr,
-    ops::{Deref, DerefMut},
-    rc::Rc,
-    sync::{
-        atomic::{AtomicBool, Ordering::SeqCst},
-        Arc,
-    },
-    time::{Duration, Instant},
-};
-use time::OffsetDateTime;
-use tokio::sync::{watch, Semaphore};
-use tower::ServiceBuilder;
-use tracing::{info_span, instrument, Instrument};
-use util::channel::RELEASE_CHANNEL_NAME;
-
-pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
-pub const CLEANUP_TIMEOUT: Duration = Duration::from_secs(10);
-
-const MESSAGE_COUNT_PER_PAGE: usize = 100;
-const MAX_MESSAGE_LEN: usize = 1024;
-const NOTIFICATION_COUNT_PER_PAGE: usize = 50;
-
-lazy_static! {
-    static ref METRIC_CONNECTIONS: IntGauge =
-        register_int_gauge!("connections", "number of connections").unwrap();
-    static ref METRIC_SHARED_PROJECTS: IntGauge = register_int_gauge!(
-        "shared_projects",
-        "number of open projects with one or more guests"
-    )
-    .unwrap();
-}
-
-type MessageHandler =
-    Box<dyn Send + Sync + Fn(Box<dyn AnyTypedEnvelope>, Session) -> BoxFuture<'static, ()>>;
-
-struct Response<R> {
-    peer: Arc<Peer>,
-    receipt: Receipt<R>,
-    responded: Arc<AtomicBool>,
-}
-
-impl<R: RequestMessage> Response<R> {
-    fn send(self, payload: R::Response) -> Result<()> {
-        self.responded.store(true, SeqCst);
-        self.peer.respond(self.receipt, payload)?;
-        Ok(())
-    }
-}
-
-#[derive(Clone)]
-struct Session {
-    user_id: UserId,
-    connection_id: ConnectionId,
-    db: Arc<tokio::sync::Mutex<DbHandle>>,
-    peer: Arc<Peer>,
-    connection_pool: Arc<parking_lot::Mutex<ConnectionPool>>,
-    live_kit_client: Option<Arc<dyn live_kit_server::api::Client>>,
-    _executor: Executor,
-}
-
-impl Session {
-    async fn db(&self) -> tokio::sync::MutexGuard<DbHandle> {
-        #[cfg(test)]
-        tokio::task::yield_now().await;
-        let guard = self.db.lock().await;
-        #[cfg(test)]
-        tokio::task::yield_now().await;
-        guard
-    }
-
-    async fn connection_pool(&self) -> ConnectionPoolGuard<'_> {
-        #[cfg(test)]
-        tokio::task::yield_now().await;
-        let guard = self.connection_pool.lock();
-        ConnectionPoolGuard {
-            guard,
-            _not_send: PhantomData,
-        }
-    }
-}
-
-impl fmt::Debug for Session {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_struct("Session")
-            .field("user_id", &self.user_id)
-            .field("connection_id", &self.connection_id)
-            .finish()
-    }
-}
-
-struct DbHandle(Arc<Database>);
-
-impl Deref for DbHandle {
-    type Target = Database;
-
-    fn deref(&self) -> &Self::Target {
-        self.0.as_ref()
-    }
-}
-
-pub struct Server {
-    id: parking_lot::Mutex<ServerId>,
-    peer: Arc<Peer>,
-    pub(crate) connection_pool: Arc<parking_lot::Mutex<ConnectionPool>>,
-    app_state: Arc<AppState>,
-    executor: Executor,
-    handlers: HashMap<TypeId, MessageHandler>,
-    teardown: watch::Sender<()>,
-}
-
-pub(crate) struct ConnectionPoolGuard<'a> {
-    guard: parking_lot::MutexGuard<'a, ConnectionPool>,
-    _not_send: PhantomData<Rc<()>>,
-}
-
-#[derive(Serialize)]
-pub struct ServerSnapshot<'a> {
-    peer: &'a Peer,
-    #[serde(serialize_with = "serialize_deref")]
-    connection_pool: ConnectionPoolGuard<'a>,
-}
-
-pub fn serialize_deref<S, T, U>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
-where
-    S: Serializer,
-    T: Deref<Target = U>,
-    U: Serialize,
-{
-    Serialize::serialize(value.deref(), serializer)
-}
-
-impl Server {
-    pub fn new(id: ServerId, app_state: Arc<AppState>, executor: Executor) -> Arc<Self> {
-        let mut server = Self {
-            id: parking_lot::Mutex::new(id),
-            peer: Peer::new(id.0 as u32),
-            app_state,
-            executor,
-            connection_pool: Default::default(),
-            handlers: Default::default(),
-            teardown: watch::channel(()).0,
-        };
-
-        server
-            .add_request_handler(ping)
-            .add_request_handler(create_room)
-            .add_request_handler(join_room)
-            .add_request_handler(rejoin_room)
-            .add_request_handler(leave_room)
-            .add_request_handler(call)
-            .add_request_handler(cancel_call)
-            .add_message_handler(decline_call)
-            .add_request_handler(update_participant_location)
-            .add_request_handler(share_project)
-            .add_message_handler(unshare_project)
-            .add_request_handler(join_project)
-            .add_message_handler(leave_project)
-            .add_request_handler(update_project)
-            .add_request_handler(update_worktree)
-            .add_message_handler(start_language_server)
-            .add_message_handler(update_language_server)
-            .add_message_handler(update_diagnostic_summary)
-            .add_message_handler(update_worktree_settings)
-            .add_message_handler(refresh_inlay_hints)
-            .add_request_handler(forward_project_request::<proto::GetHover>)
-            .add_request_handler(forward_project_request::<proto::GetDefinition>)
-            .add_request_handler(forward_project_request::<proto::GetTypeDefinition>)
-            .add_request_handler(forward_project_request::<proto::GetReferences>)
-            .add_request_handler(forward_project_request::<proto::SearchProject>)
-            .add_request_handler(forward_project_request::<proto::GetDocumentHighlights>)
-            .add_request_handler(forward_project_request::<proto::GetProjectSymbols>)
-            .add_request_handler(forward_project_request::<proto::OpenBufferForSymbol>)
-            .add_request_handler(forward_project_request::<proto::OpenBufferById>)
-            .add_request_handler(forward_project_request::<proto::OpenBufferByPath>)
-            .add_request_handler(forward_project_request::<proto::GetCompletions>)
-            .add_request_handler(forward_project_request::<proto::ApplyCompletionAdditionalEdits>)
-            .add_request_handler(forward_project_request::<proto::ResolveCompletionDocumentation>)
-            .add_request_handler(forward_project_request::<proto::GetCodeActions>)
-            .add_request_handler(forward_project_request::<proto::ApplyCodeAction>)
-            .add_request_handler(forward_project_request::<proto::PrepareRename>)
-            .add_request_handler(forward_project_request::<proto::PerformRename>)
-            .add_request_handler(forward_project_request::<proto::ReloadBuffers>)
-            .add_request_handler(forward_project_request::<proto::SynchronizeBuffers>)
-            .add_request_handler(forward_project_request::<proto::FormatBuffers>)
-            .add_request_handler(forward_project_request::<proto::CreateProjectEntry>)
-            .add_request_handler(forward_project_request::<proto::RenameProjectEntry>)
-            .add_request_handler(forward_project_request::<proto::CopyProjectEntry>)
-            .add_request_handler(forward_project_request::<proto::DeleteProjectEntry>)
-            .add_request_handler(forward_project_request::<proto::ExpandProjectEntry>)
-            .add_request_handler(forward_project_request::<proto::OnTypeFormatting>)
-            .add_request_handler(forward_project_request::<proto::InlayHints>)
-            .add_message_handler(create_buffer_for_peer)
-            .add_request_handler(update_buffer)
-            .add_message_handler(update_buffer_file)
-            .add_message_handler(buffer_reloaded)
-            .add_message_handler(buffer_saved)
-            .add_request_handler(forward_project_request::<proto::SaveBuffer>)
-            .add_request_handler(get_users)
-            .add_request_handler(fuzzy_search_users)
-            .add_request_handler(request_contact)
-            .add_request_handler(remove_contact)
-            .add_request_handler(respond_to_contact_request)
-            .add_request_handler(create_channel)
-            .add_request_handler(delete_channel)
-            .add_request_handler(invite_channel_member)
-            .add_request_handler(remove_channel_member)
-            .add_request_handler(set_channel_member_role)
-            .add_request_handler(set_channel_visibility)
-            .add_request_handler(rename_channel)
-            .add_request_handler(join_channel_buffer)
-            .add_request_handler(leave_channel_buffer)
-            .add_message_handler(update_channel_buffer)
-            .add_request_handler(rejoin_channel_buffers)
-            .add_request_handler(get_channel_members)
-            .add_request_handler(respond_to_channel_invite)
-            .add_request_handler(join_channel)
-            .add_request_handler(join_channel_chat)
-            .add_message_handler(leave_channel_chat)
-            .add_request_handler(send_channel_message)
-            .add_request_handler(remove_channel_message)
-            .add_request_handler(get_channel_messages)
-            .add_request_handler(get_channel_messages_by_id)
-            .add_request_handler(get_notifications)
-            .add_request_handler(mark_notification_as_read)
-            .add_request_handler(move_channel)
-            .add_request_handler(follow)
-            .add_message_handler(unfollow)
-            .add_message_handler(update_followers)
-            .add_message_handler(update_diff_base)
-            .add_request_handler(get_private_user_info)
-            .add_message_handler(acknowledge_channel_message)
-            .add_message_handler(acknowledge_buffer_version);
-
-        Arc::new(server)
-    }
-
-    pub async fn start(&self) -> Result<()> {
-        let server_id = *self.id.lock();
-        let app_state = self.app_state.clone();
-        let peer = self.peer.clone();
-        let timeout = self.executor.sleep(CLEANUP_TIMEOUT);
-        let pool = self.connection_pool.clone();
-        let live_kit_client = self.app_state.live_kit_client.clone();
-
-        let span = info_span!("start server");
-        self.executor.spawn_detached(
-            async move {
-                tracing::info!("waiting for cleanup timeout");
-                timeout.await;
-                tracing::info!("cleanup timeout expired, retrieving stale rooms");
-                if let Some((room_ids, channel_ids)) = app_state
-                    .db
-                    .stale_server_resource_ids(&app_state.config.zed_environment, server_id)
-                    .await
-                    .trace_err()
-                {
-                    tracing::info!(stale_room_count = room_ids.len(), "retrieved stale rooms");
-                    tracing::info!(
-                        stale_channel_buffer_count = channel_ids.len(),
-                        "retrieved stale channel buffers"
-                    );
-
-                    for channel_id in channel_ids {
-                        if let Some(refreshed_channel_buffer) = app_state
-                            .db
-                            .clear_stale_channel_buffer_collaborators(channel_id, server_id)
-                            .await
-                            .trace_err()
-                        {
-                            for connection_id in refreshed_channel_buffer.connection_ids {
-                                peer.send(
-                                    connection_id,
-                                    proto::UpdateChannelBufferCollaborators {
-                                        channel_id: channel_id.to_proto(),
-                                        collaborators: refreshed_channel_buffer
-                                            .collaborators
-                                            .clone(),
-                                    },
-                                )
-                                .trace_err();
-                            }
-                        }
-                    }
-
-                    for room_id in room_ids {
-                        let mut contacts_to_update = HashSet::default();
-                        let mut canceled_calls_to_user_ids = Vec::new();
-                        let mut live_kit_room = String::new();
-                        let mut delete_live_kit_room = false;
-
-                        if let Some(mut refreshed_room) = app_state
-                            .db
-                            .clear_stale_room_participants(room_id, server_id)
-                            .await
-                            .trace_err()
-                        {
-                            tracing::info!(
-                                room_id = room_id.0,
-                                new_participant_count = refreshed_room.room.participants.len(),
-                                "refreshed room"
-                            );
-                            room_updated(&refreshed_room.room, &peer);
-                            if let Some(channel_id) = refreshed_room.channel_id {
-                                channel_updated(
-                                    channel_id,
-                                    &refreshed_room.room,
-                                    &refreshed_room.channel_members,
-                                    &peer,
-                                    &*pool.lock(),
-                                );
-                            }
-                            contacts_to_update
-                                .extend(refreshed_room.stale_participant_user_ids.iter().copied());
-                            contacts_to_update
-                                .extend(refreshed_room.canceled_calls_to_user_ids.iter().copied());
-                            canceled_calls_to_user_ids =
-                                mem::take(&mut refreshed_room.canceled_calls_to_user_ids);
-                            live_kit_room = mem::take(&mut refreshed_room.room.live_kit_room);
-                            delete_live_kit_room = refreshed_room.room.participants.is_empty();
-                        }
-
-                        {
-                            let pool = pool.lock();
-                            for canceled_user_id in canceled_calls_to_user_ids {
-                                for connection_id in pool.user_connection_ids(canceled_user_id) {
-                                    peer.send(
-                                        connection_id,
-                                        proto::CallCanceled {
-                                            room_id: room_id.to_proto(),
-                                        },
-                                    )
-                                    .trace_err();
-                                }
-                            }
-                        }
-
-                        for user_id in contacts_to_update {
-                            let busy = app_state.db.is_user_busy(user_id).await.trace_err();
-                            let contacts = app_state.db.get_contacts(user_id).await.trace_err();
-                            if let Some((busy, contacts)) = busy.zip(contacts) {
-                                let pool = pool.lock();
-                                let updated_contact = contact_for_user(user_id, busy, &pool);
-                                for contact in contacts {
-                                    if let db::Contact::Accepted {
-                                        user_id: contact_user_id,
-                                        ..
-                                    } = contact
-                                    {
-                                        for contact_conn_id in
-                                            pool.user_connection_ids(contact_user_id)
-                                        {
-                                            peer.send(
-                                                contact_conn_id,
-                                                proto::UpdateContacts {
-                                                    contacts: vec![updated_contact.clone()],
-                                                    remove_contacts: Default::default(),
-                                                    incoming_requests: Default::default(),
-                                                    remove_incoming_requests: Default::default(),
-                                                    outgoing_requests: Default::default(),
-                                                    remove_outgoing_requests: Default::default(),
-                                                },
-                                            )
-                                            .trace_err();
-                                        }
-                                    }
-                                }
-                            }
-                        }
-
-                        if let Some(live_kit) = live_kit_client.as_ref() {
-                            if delete_live_kit_room {
-                                live_kit.delete_room(live_kit_room).await.trace_err();
-                            }
-                        }
-                    }
-                }
-
-                app_state
-                    .db
-                    .delete_stale_servers(&app_state.config.zed_environment, server_id)
-                    .await
-                    .trace_err();
-            }
-            .instrument(span),
-        );
-        Ok(())
-    }
-
-    pub fn teardown(&self) {
-        self.peer.teardown();
-        self.connection_pool.lock().reset();
-        let _ = self.teardown.send(());
-    }
-
-    #[cfg(test)]
-    pub fn reset(&self, id: ServerId) {
-        self.teardown();
-        *self.id.lock() = id;
-        self.peer.reset(id.0 as u32);
-    }
-
-    #[cfg(test)]
-    pub fn id(&self) -> ServerId {
-        *self.id.lock()
-    }
-
-    fn add_handler<F, Fut, M>(&mut self, handler: F) -> &mut Self
-    where
-        F: 'static + Send + Sync + Fn(TypedEnvelope<M>, Session) -> Fut,
-        Fut: 'static + Send + Future<Output = Result<()>>,
-        M: EnvelopedMessage,
-    {
-        let prev_handler = self.handlers.insert(
-            TypeId::of::<M>(),
-            Box::new(move |envelope, session| {
-                let envelope = envelope.into_any().downcast::<TypedEnvelope<M>>().unwrap();
-                let span = info_span!(
-                    "handle message",
-                    payload_type = envelope.payload_type_name()
-                );
-                span.in_scope(|| {
-                    tracing::info!(
-                        payload_type = envelope.payload_type_name(),
-                        "message received"
-                    );
-                });
-                let start_time = Instant::now();
-                let future = (handler)(*envelope, session);
-                async move {
-                    let result = future.await;
-                    let duration_ms = start_time.elapsed().as_micros() as f64 / 1000.0;
-                    match result {
-                        Err(error) => {
-                            tracing::error!(%error, ?duration_ms, "error handling message")
-                        }
-                        Ok(()) => tracing::info!(?duration_ms, "finished handling message"),
-                    }
-                }
-                .instrument(span)
-                .boxed()
-            }),
-        );
-        if prev_handler.is_some() {
-            panic!("registered a handler for the same message twice");
-        }
-        self
-    }
-
-    fn add_message_handler<F, Fut, M>(&mut self, handler: F) -> &mut Self
-    where
-        F: 'static + Send + Sync + Fn(M, Session) -> Fut,
-        Fut: 'static + Send + Future<Output = Result<()>>,
-        M: EnvelopedMessage,
-    {
-        self.add_handler(move |envelope, session| handler(envelope.payload, session));
-        self
-    }
-
-    fn add_request_handler<F, Fut, M>(&mut self, handler: F) -> &mut Self
-    where
-        F: 'static + Send + Sync + Fn(M, Response<M>, Session) -> Fut,
-        Fut: Send + Future<Output = Result<()>>,
-        M: RequestMessage,
-    {
-        let handler = Arc::new(handler);
-        self.add_handler(move |envelope, session| {
-            let receipt = envelope.receipt();
-            let handler = handler.clone();
-            async move {
-                let peer = session.peer.clone();
-                let responded = Arc::new(AtomicBool::default());
-                let response = Response {
-                    peer: peer.clone(),
-                    responded: responded.clone(),
-                    receipt,
-                };
-                match (handler)(envelope.payload, response, session).await {
-                    Ok(()) => {
-                        if responded.load(std::sync::atomic::Ordering::SeqCst) {
-                            Ok(())
-                        } else {
-                            Err(anyhow!("handler did not send a response"))?
-                        }
-                    }
-                    Err(error) => {
-                        peer.respond_with_error(
-                            receipt,
-                            proto::Error {
-                                message: error.to_string(),
-                            },
-                        )?;
-                        Err(error)
-                    }
-                }
-            }
-        })
-    }
-
-    pub fn handle_connection(
-        self: &Arc<Self>,
-        connection: Connection,
-        address: String,
-        user: User,
-        mut send_connection_id: Option<oneshot::Sender<ConnectionId>>,
-        executor: Executor,
-    ) -> impl Future<Output = Result<()>> {
-        let this = self.clone();
-        let user_id = user.id;
-        let login = user.github_login;
-        let span = info_span!("handle connection", %user_id, %login, %address);
-        let mut teardown = self.teardown.subscribe();
-        async move {
-            let (connection_id, handle_io, mut incoming_rx) = this
-                .peer
-                .add_connection(connection, {
-                    let executor = executor.clone();
-                    move |duration| executor.sleep(duration)
-                });
-
-            tracing::info!(%user_id, %login, %connection_id, %address, "connection opened");
-            this.peer.send(connection_id, proto::Hello { peer_id: Some(connection_id.into()) })?;
-            tracing::info!(%user_id, %login, %connection_id, %address, "sent hello message");
-
-            if let Some(send_connection_id) = send_connection_id.take() {
-                let _ = send_connection_id.send(connection_id);
-            }
-
-            if !user.connected_once {
-                this.peer.send(connection_id, proto::ShowContacts {})?;
-                this.app_state.db.set_user_connected_once(user_id, true).await?;
-            }
-
-            let (contacts, channels_for_user, channel_invites) = future::try_join3(
-                this.app_state.db.get_contacts(user_id),
-                this.app_state.db.get_channels_for_user(user_id),
-                this.app_state.db.get_channel_invites_for_user(user_id),
-            ).await?;
-
-            {
-                let mut pool = this.connection_pool.lock();
-                pool.add_connection(connection_id, user_id, user.admin);
-                this.peer.send(connection_id, build_initial_contacts_update(contacts, &pool))?;
-                this.peer.send(connection_id, build_channels_update(
-                    channels_for_user,
-                    channel_invites
-                ))?;
-            }
-
-            if let Some(incoming_call) = this.app_state.db.incoming_call_for_user(user_id).await? {
-                this.peer.send(connection_id, incoming_call)?;
-            }
-
-            let session = Session {
-                user_id,
-                connection_id,
-                db: Arc::new(tokio::sync::Mutex::new(DbHandle(this.app_state.db.clone()))),
-                peer: this.peer.clone(),
-                connection_pool: this.connection_pool.clone(),
-                live_kit_client: this.app_state.live_kit_client.clone(),
-                _executor: executor.clone()
-            };
-            update_user_contacts(user_id, &session).await?;
-
-            let handle_io = handle_io.fuse();
-            futures::pin_mut!(handle_io);
-
-            // Handlers for foreground messages are pushed into the following `FuturesUnordered`.
-            // This prevents deadlocks when e.g., client A performs a request to client B and
-            // client B performs a request to client A. If both clients stop processing further
-            // messages until their respective request completes, they won't have a chance to
-            // respond to the other client's request and cause a deadlock.
-            //
-            // This arrangement ensures we will attempt to process earlier messages first, but fall
-            // back to processing messages arrived later in the spirit of making progress.
-            let mut foreground_message_handlers = FuturesUnordered::new();
-            let concurrent_handlers = Arc::new(Semaphore::new(256));
-            loop {
-                let next_message = async {
-                    let permit = concurrent_handlers.clone().acquire_owned().await.unwrap();
-                    let message = incoming_rx.next().await;
-                    (permit, message)
-                }.fuse();
-                futures::pin_mut!(next_message);
-                futures::select_biased! {
-                    _ = teardown.changed().fuse() => return Ok(()),
-                    result = handle_io => {
-                        if let Err(error) = result {
-                            tracing::error!(?error, %user_id, %login, %connection_id, %address, "error handling I/O");
-                        }
-                        break;
-                    }
-                    _ = foreground_message_handlers.next() => {}
-                    next_message = next_message => {
-                        let (permit, message) = next_message;
-                        if let Some(message) = message {
-                            let type_name = message.payload_type_name();
-                            let span = tracing::info_span!("receive message", %user_id, %login, %connection_id, %address, type_name);
-                            let span_enter = span.enter();
-                            if let Some(handler) = this.handlers.get(&message.payload_type_id()) {
-                                let is_background = message.is_background();
-                                let handle_message = (handler)(message, session.clone());
-                                drop(span_enter);
-
-                                let handle_message = async move {
-                                    handle_message.await;
-                                    drop(permit);
-                                }.instrument(span);
-                                if is_background {
-                                    executor.spawn_detached(handle_message);
-                                } else {
-                                    foreground_message_handlers.push(handle_message);
-                                }
-                            } else {
-                                tracing::error!(%user_id, %login, %connection_id, %address, "no message handler");
-                            }
-                        } else {
-                            tracing::info!(%user_id, %login, %connection_id, %address, "connection closed");
-                            break;
-                        }
-                    }
-                }
-            }
-
-            drop(foreground_message_handlers);
-            tracing::info!(%user_id, %login, %connection_id, %address, "signing out");
-            if let Err(error) = connection_lost(session, teardown, executor).await {
-                tracing::error!(%user_id, %login, %connection_id, %address, ?error, "error signing out");
-            }
-
-            Ok(())
-        }.instrument(span)
-    }
-
-    pub async fn invite_code_redeemed(
-        self: &Arc<Self>,
-        inviter_id: UserId,
-        invitee_id: UserId,
-    ) -> Result<()> {
-        if let Some(user) = self.app_state.db.get_user_by_id(inviter_id).await? {
-            if let Some(code) = &user.invite_code {
-                let pool = self.connection_pool.lock();
-                let invitee_contact = contact_for_user(invitee_id, false, &pool);
-                for connection_id in pool.user_connection_ids(inviter_id) {
-                    self.peer.send(
-                        connection_id,
-                        proto::UpdateContacts {
-                            contacts: vec![invitee_contact.clone()],
-                            ..Default::default()
-                        },
-                    )?;
-                    self.peer.send(
-                        connection_id,
-                        proto::UpdateInviteInfo {
-                            url: format!("{}{}", self.app_state.config.invite_link_prefix, &code),
-                            count: user.invite_count as u32,
-                        },
-                    )?;
-                }
-            }
-        }
-        Ok(())
-    }
-
-    pub async fn invite_count_updated(self: &Arc<Self>, user_id: UserId) -> Result<()> {
-        if let Some(user) = self.app_state.db.get_user_by_id(user_id).await? {
-            if let Some(invite_code) = &user.invite_code {
-                let pool = self.connection_pool.lock();
-                for connection_id in pool.user_connection_ids(user_id) {
-                    self.peer.send(
-                        connection_id,
-                        proto::UpdateInviteInfo {
-                            url: format!(
-                                "{}{}",
-                                self.app_state.config.invite_link_prefix, invite_code
-                            ),
-                            count: user.invite_count as u32,
-                        },
-                    )?;
-                }
-            }
-        }
-        Ok(())
-    }
-
-    pub async fn snapshot<'a>(self: &'a Arc<Self>) -> ServerSnapshot<'a> {
-        ServerSnapshot {
-            connection_pool: ConnectionPoolGuard {
-                guard: self.connection_pool.lock(),
-                _not_send: PhantomData,
-            },
-            peer: &self.peer,
-        }
-    }
-}
-
-impl<'a> Deref for ConnectionPoolGuard<'a> {
-    type Target = ConnectionPool;
-
-    fn deref(&self) -> &Self::Target {
-        &*self.guard
-    }
-}
-
-impl<'a> DerefMut for ConnectionPoolGuard<'a> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut *self.guard
-    }
-}
-
-impl<'a> Drop for ConnectionPoolGuard<'a> {
-    fn drop(&mut self) {
-        #[cfg(test)]
-        self.check_invariants();
-    }
-}
-
-fn broadcast<F>(
-    sender_id: Option<ConnectionId>,
-    receiver_ids: impl IntoIterator<Item = ConnectionId>,
-    mut f: F,
-) where
-    F: FnMut(ConnectionId) -> anyhow::Result<()>,
-{
-    for receiver_id in receiver_ids {
-        if Some(receiver_id) != sender_id {
-            if let Err(error) = f(receiver_id) {
-                tracing::error!("failed to send to {:?} {}", receiver_id, error);
-            }
-        }
-    }
-}
-
-lazy_static! {
-    static ref ZED_PROTOCOL_VERSION: HeaderName = HeaderName::from_static("x-zed-protocol-version");
-}
-
-pub struct ProtocolVersion(u32);
-
-impl Header for ProtocolVersion {
-    fn name() -> &'static HeaderName {
-        &ZED_PROTOCOL_VERSION
-    }
-
-    fn decode<'i, I>(values: &mut I) -> Result<Self, axum::headers::Error>
-    where
-        Self: Sized,
-        I: Iterator<Item = &'i axum::http::HeaderValue>,
-    {
-        let version = values
-            .next()
-            .ok_or_else(axum::headers::Error::invalid)?
-            .to_str()
-            .map_err(|_| axum::headers::Error::invalid())?
-            .parse()
-            .map_err(|_| axum::headers::Error::invalid())?;
-        Ok(Self(version))
-    }
-
-    fn encode<E: Extend<axum::http::HeaderValue>>(&self, values: &mut E) {
-        values.extend([self.0.to_string().parse().unwrap()]);
-    }
-}
-
-pub fn routes(server: Arc<Server>) -> Router<Body> {
-    Router::new()
-        .route("/rpc", get(handle_websocket_request))
-        .layer(
-            ServiceBuilder::new()
-                .layer(Extension(server.app_state.clone()))
-                .layer(middleware::from_fn(auth::validate_header)),
-        )
-        .route("/metrics", get(handle_metrics))
-        .layer(Extension(server))
-}
-
-pub async fn handle_websocket_request(
-    TypedHeader(ProtocolVersion(protocol_version)): TypedHeader<ProtocolVersion>,
-    ConnectInfo(socket_address): ConnectInfo<SocketAddr>,
-    Extension(server): Extension<Arc<Server>>,
-    Extension(user): Extension<User>,
-    ws: WebSocketUpgrade,
-) -> axum::response::Response {
-    if protocol_version != rpc::PROTOCOL_VERSION {
-        return (
-            StatusCode::UPGRADE_REQUIRED,
-            "client must be upgraded".to_string(),
-        )
-            .into_response();
-    }
-    let socket_address = socket_address.to_string();
-    ws.on_upgrade(move |socket| {
-        use util::ResultExt;
-        let socket = socket
-            .map_ok(to_tungstenite_message)
-            .err_into()
-            .with(|message| async move { Ok(to_axum_message(message)) });
-        let connection = Connection::new(Box::pin(socket));
-        async move {
-            server
-                .handle_connection(connection, socket_address, user, None, Executor::Production)
-                .await
-                .log_err();
-        }
-    })
-}
-
-pub async fn handle_metrics(Extension(server): Extension<Arc<Server>>) -> Result<String> {
-    let connections = server
-        .connection_pool
-        .lock()
-        .connections()
-        .filter(|connection| !connection.admin)
-        .count();
-
-    METRIC_CONNECTIONS.set(connections as _);
-
-    let shared_projects = server.app_state.db.project_count_excluding_admins().await?;
-    METRIC_SHARED_PROJECTS.set(shared_projects as _);
-
-    let encoder = prometheus::TextEncoder::new();
-    let metric_families = prometheus::gather();
-    let encoded_metrics = encoder
-        .encode_to_string(&metric_families)
-        .map_err(|err| anyhow!("{}", err))?;
-    Ok(encoded_metrics)
-}
-
-#[instrument(err, skip(executor))]
-async fn connection_lost(
-    session: Session,
-    mut teardown: watch::Receiver<()>,
-    executor: Executor,
-) -> Result<()> {
-    session.peer.disconnect(session.connection_id);
-    session
-        .connection_pool()
-        .await
-        .remove_connection(session.connection_id)?;
-
-    session
-        .db()
-        .await
-        .connection_lost(session.connection_id)
-        .await
-        .trace_err();
-
-    futures::select_biased! {
-        _ = executor.sleep(RECONNECT_TIMEOUT).fuse() => {
-            log::info!("connection lost, removing all resources for user:{}, connection:{:?}", session.user_id, session.connection_id);
-            leave_room_for_session(&session).await.trace_err();
-            leave_channel_buffers_for_session(&session)
-                .await
-                .trace_err();
-
-            if !session
-                .connection_pool()
-                .await
-                .is_user_online(session.user_id)
-            {
-                let db = session.db().await;
-                if let Some(room) = db.decline_call(None, session.user_id).await.trace_err().flatten() {
-                    room_updated(&room, &session.peer);
-                }
-            }
-
-            update_user_contacts(session.user_id, &session).await?;
-        }
-        _ = teardown.changed().fuse() => {}
-    }
-
-    Ok(())
-}
-
-async fn ping(_: proto::Ping, response: Response<proto::Ping>, _session: Session) -> Result<()> {
-    response.send(proto::Ack {})?;
-    Ok(())
-}
-
-async fn create_room(
-    _request: proto::CreateRoom,
-    response: Response<proto::CreateRoom>,
-    session: Session,
-) -> Result<()> {
-    let live_kit_room = nanoid::nanoid!(30);
-
-    let live_kit_connection_info = {
-        let live_kit_room = live_kit_room.clone();
-        let live_kit = session.live_kit_client.as_ref();
-
-        util::async_maybe!({
-            let live_kit = live_kit?;
-
-            let token = live_kit
-                .room_token(&live_kit_room, &session.user_id.to_string())
-                .trace_err()?;
-
-            Some(proto::LiveKitConnectionInfo {
-                server_url: live_kit.url().into(),
-                token,
-                can_publish: true,
-            })
-        })
-    }
-    .await;
-
-    let room = session
-        .db()
-        .await
-        .create_room(
-            session.user_id,
-            session.connection_id,
-            &live_kit_room,
-            RELEASE_CHANNEL_NAME.as_str(),
-        )
-        .await?;
-
-    response.send(proto::CreateRoomResponse {
-        room: Some(room.clone()),
-        live_kit_connection_info,
-    })?;
-
-    update_user_contacts(session.user_id, &session).await?;
-    Ok(())
-}
-
-async fn join_room(
-    request: proto::JoinRoom,
-    response: Response<proto::JoinRoom>,
-    session: Session,
-) -> Result<()> {
-    let room_id = RoomId::from_proto(request.id);
-
-    let channel_id = session.db().await.channel_id_for_room(room_id).await?;
-
-    if let Some(channel_id) = channel_id {
-        return join_channel_internal(channel_id, Box::new(response), session).await;
-    }
-
-    let joined_room = {
-        let room = session
-            .db()
-            .await
-            .join_room(
-                room_id,
-                session.user_id,
-                session.connection_id,
-                RELEASE_CHANNEL_NAME.as_str(),
-            )
-            .await?;
-        room_updated(&room.room, &session.peer);
-        room.into_inner()
-    };
-
-    for connection_id in session
-        .connection_pool()
-        .await
-        .user_connection_ids(session.user_id)
-    {
-        session
-            .peer
-            .send(
-                connection_id,
-                proto::CallCanceled {
-                    room_id: room_id.to_proto(),
-                },
-            )
-            .trace_err();
-    }
-
-    let live_kit_connection_info = if let Some(live_kit) = session.live_kit_client.as_ref() {
-        if let Some(token) = live_kit
-            .room_token(
-                &joined_room.room.live_kit_room,
-                &session.user_id.to_string(),
-            )
-            .trace_err()
-        {
-            Some(proto::LiveKitConnectionInfo {
-                server_url: live_kit.url().into(),
-                token,
-                can_publish: true,
-            })
-        } else {
-            None
-        }
-    } else {
-        None
-    };
-
-    response.send(proto::JoinRoomResponse {
-        room: Some(joined_room.room),
-        channel_id: None,
-        live_kit_connection_info,
-    })?;
-
-    update_user_contacts(session.user_id, &session).await?;
-    Ok(())
-}
-
-async fn rejoin_room(
-    request: proto::RejoinRoom,
-    response: Response<proto::RejoinRoom>,
-    session: Session,
-) -> Result<()> {
-    let room;
-    let channel_id;
-    let channel_members;
-    {
-        let mut rejoined_room = session
-            .db()
-            .await
-            .rejoin_room(request, session.user_id, session.connection_id)
-            .await?;
-
-        response.send(proto::RejoinRoomResponse {
-            room: Some(rejoined_room.room.clone()),
-            reshared_projects: rejoined_room
-                .reshared_projects
-                .iter()
-                .map(|project| proto::ResharedProject {
-                    id: project.id.to_proto(),
-                    collaborators: project
-                        .collaborators
-                        .iter()
-                        .map(|collaborator| collaborator.to_proto())
-                        .collect(),
-                })
-                .collect(),
-            rejoined_projects: rejoined_room
-                .rejoined_projects
-                .iter()
-                .map(|rejoined_project| proto::RejoinedProject {
-                    id: rejoined_project.id.to_proto(),
-                    worktrees: rejoined_project
-                        .worktrees
-                        .iter()
-                        .map(|worktree| proto::WorktreeMetadata {
-                            id: worktree.id,
-                            root_name: worktree.root_name.clone(),
-                            visible: worktree.visible,
-                            abs_path: worktree.abs_path.clone(),
-                        })
-                        .collect(),
-                    collaborators: rejoined_project
-                        .collaborators
-                        .iter()
-                        .map(|collaborator| collaborator.to_proto())
-                        .collect(),
-                    language_servers: rejoined_project.language_servers.clone(),
-                })
-                .collect(),
-        })?;
-        room_updated(&rejoined_room.room, &session.peer);
-
-        for project in &rejoined_room.reshared_projects {
-            for collaborator in &project.collaborators {
-                session
-                    .peer
-                    .send(
-                        collaborator.connection_id,
-                        proto::UpdateProjectCollaborator {
-                            project_id: project.id.to_proto(),
-                            old_peer_id: Some(project.old_connection_id.into()),
-                            new_peer_id: Some(session.connection_id.into()),
-                        },
-                    )
-                    .trace_err();
-            }
-
-            broadcast(
-                Some(session.connection_id),
-                project
-                    .collaborators
-                    .iter()
-                    .map(|collaborator| collaborator.connection_id),
-                |connection_id| {
-                    session.peer.forward_send(
-                        session.connection_id,
-                        connection_id,
-                        proto::UpdateProject {
-                            project_id: project.id.to_proto(),
-                            worktrees: project.worktrees.clone(),
-                        },
-                    )
-                },
-            );
-        }
-
-        for project in &rejoined_room.rejoined_projects {
-            for collaborator in &project.collaborators {
-                session
-                    .peer
-                    .send(
-                        collaborator.connection_id,
-                        proto::UpdateProjectCollaborator {
-                            project_id: project.id.to_proto(),
-                            old_peer_id: Some(project.old_connection_id.into()),
-                            new_peer_id: Some(session.connection_id.into()),
-                        },
-                    )
-                    .trace_err();
-            }
-        }
-
-        for project in &mut rejoined_room.rejoined_projects {
-            for worktree in mem::take(&mut project.worktrees) {
-                #[cfg(any(test, feature = "test-support"))]
-                const MAX_CHUNK_SIZE: usize = 2;
-                #[cfg(not(any(test, feature = "test-support")))]
-                const MAX_CHUNK_SIZE: usize = 256;
-
-                // Stream this worktree's entries.
-                let message = proto::UpdateWorktree {
-                    project_id: project.id.to_proto(),
-                    worktree_id: worktree.id,
-                    abs_path: worktree.abs_path.clone(),
-                    root_name: worktree.root_name,
-                    updated_entries: worktree.updated_entries,
-                    removed_entries: worktree.removed_entries,
-                    scan_id: worktree.scan_id,
-                    is_last_update: worktree.completed_scan_id == worktree.scan_id,
-                    updated_repositories: worktree.updated_repositories,
-                    removed_repositories: worktree.removed_repositories,
-                };
-                for update in proto::split_worktree_update(message, MAX_CHUNK_SIZE) {
-                    session.peer.send(session.connection_id, update.clone())?;
-                }
-
-                // Stream this worktree's diagnostics.
-                for summary in worktree.diagnostic_summaries {
-                    session.peer.send(
-                        session.connection_id,
-                        proto::UpdateDiagnosticSummary {
-                            project_id: project.id.to_proto(),
-                            worktree_id: worktree.id,
-                            summary: Some(summary),
-                        },
-                    )?;
-                }
-
-                for settings_file in worktree.settings_files {
-                    session.peer.send(
-                        session.connection_id,
-                        proto::UpdateWorktreeSettings {
-                            project_id: project.id.to_proto(),
-                            worktree_id: worktree.id,
-                            path: settings_file.path,
-                            content: Some(settings_file.content),
-                        },
-                    )?;
-                }
-            }
-
-            for language_server in &project.language_servers {
-                session.peer.send(
-                    session.connection_id,
-                    proto::UpdateLanguageServer {
-                        project_id: project.id.to_proto(),
-                        language_server_id: language_server.id,
-                        variant: Some(
-                            proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(
-                                proto::LspDiskBasedDiagnosticsUpdated {},
-                            ),
-                        ),
-                    },
-                )?;
-            }
-        }
-
-        let rejoined_room = rejoined_room.into_inner();
-
-        room = rejoined_room.room;
-        channel_id = rejoined_room.channel_id;
-        channel_members = rejoined_room.channel_members;
-    }
-
-    if let Some(channel_id) = channel_id {
-        channel_updated(
-            channel_id,
-            &room,
-            &channel_members,
-            &session.peer,
-            &*session.connection_pool().await,
-        );
-    }
-
-    update_user_contacts(session.user_id, &session).await?;
-    Ok(())
-}
-
-async fn leave_room(
-    _: proto::LeaveRoom,
-    response: Response<proto::LeaveRoom>,
-    session: Session,
-) -> Result<()> {
-    leave_room_for_session(&session).await?;
-    response.send(proto::Ack {})?;
-    Ok(())
-}
-
-async fn call(
-    request: proto::Call,
-    response: Response<proto::Call>,
-    session: Session,
-) -> Result<()> {
-    let room_id = RoomId::from_proto(request.room_id);
-    let calling_user_id = session.user_id;
-    let calling_connection_id = session.connection_id;
-    let called_user_id = UserId::from_proto(request.called_user_id);
-    let initial_project_id = request.initial_project_id.map(ProjectId::from_proto);
-    if !session
-        .db()
-        .await
-        .has_contact(calling_user_id, called_user_id)
-        .await?
-    {
-        return Err(anyhow!("cannot call a user who isn't a contact"))?;
-    }
-
-    let incoming_call = {
-        let (room, incoming_call) = &mut *session
-            .db()
-            .await
-            .call(
-                room_id,
-                calling_user_id,
-                calling_connection_id,
-                called_user_id,
-                initial_project_id,
-            )
-            .await?;
-        room_updated(&room, &session.peer);
-        mem::take(incoming_call)
-    };
-    update_user_contacts(called_user_id, &session).await?;
-
-    let mut calls = session
-        .connection_pool()
-        .await
-        .user_connection_ids(called_user_id)
-        .map(|connection_id| session.peer.request(connection_id, incoming_call.clone()))
-        .collect::<FuturesUnordered<_>>();
-
-    while let Some(call_response) = calls.next().await {
-        match call_response.as_ref() {
-            Ok(_) => {
-                response.send(proto::Ack {})?;
-                return Ok(());
-            }
-            Err(_) => {
-                call_response.trace_err();
-            }
-        }
-    }
-
-    {
-        let room = session
-            .db()
-            .await
-            .call_failed(room_id, called_user_id)
-            .await?;
-        room_updated(&room, &session.peer);
-    }
-    update_user_contacts(called_user_id, &session).await?;
-
-    Err(anyhow!("failed to ring user"))?
-}
-
-async fn cancel_call(
-    request: proto::CancelCall,
-    response: Response<proto::CancelCall>,
-    session: Session,
-) -> Result<()> {
-    let called_user_id = UserId::from_proto(request.called_user_id);
-    let room_id = RoomId::from_proto(request.room_id);
-    {
-        let room = session
-            .db()
-            .await
-            .cancel_call(room_id, session.connection_id, called_user_id)
-            .await?;
-        room_updated(&room, &session.peer);
-    }
-
-    for connection_id in session
-        .connection_pool()
-        .await
-        .user_connection_ids(called_user_id)
-    {
-        session
-            .peer
-            .send(
-                connection_id,
-                proto::CallCanceled {
-                    room_id: room_id.to_proto(),
-                },
-            )
-            .trace_err();
-    }
-    response.send(proto::Ack {})?;
-
-    update_user_contacts(called_user_id, &session).await?;
-    Ok(())
-}
-
-async fn decline_call(message: proto::DeclineCall, session: Session) -> Result<()> {
-    let room_id = RoomId::from_proto(message.room_id);
-    {
-        let room = session
-            .db()
-            .await
-            .decline_call(Some(room_id), session.user_id)
-            .await?
-            .ok_or_else(|| anyhow!("failed to decline call"))?;
-        room_updated(&room, &session.peer);
-    }
-
-    for connection_id in session
-        .connection_pool()
-        .await
-        .user_connection_ids(session.user_id)
-    {
-        session
-            .peer
-            .send(
-                connection_id,
-                proto::CallCanceled {
-                    room_id: room_id.to_proto(),
-                },
-            )
-            .trace_err();
-    }
-    update_user_contacts(session.user_id, &session).await?;
-    Ok(())
-}
-
-async fn update_participant_location(
-    request: proto::UpdateParticipantLocation,
-    response: Response<proto::UpdateParticipantLocation>,
-    session: Session,
-) -> Result<()> {
-    let room_id = RoomId::from_proto(request.room_id);
-    let location = request
-        .location
-        .ok_or_else(|| anyhow!("invalid location"))?;
-
-    let db = session.db().await;
-    let room = db
-        .update_room_participant_location(room_id, session.connection_id, location)
-        .await?;
-
-    room_updated(&room, &session.peer);
-    response.send(proto::Ack {})?;
-    Ok(())
-}
-
-async fn share_project(
-    request: proto::ShareProject,
-    response: Response<proto::ShareProject>,
-    session: Session,
-) -> Result<()> {
-    let (project_id, room) = &*session
-        .db()
-        .await
-        .share_project(
-            RoomId::from_proto(request.room_id),
-            session.connection_id,
-            &request.worktrees,
-        )
-        .await?;
-    response.send(proto::ShareProjectResponse {
-        project_id: project_id.to_proto(),
-    })?;
-    room_updated(&room, &session.peer);
-
-    Ok(())
-}
-
-async fn unshare_project(message: proto::UnshareProject, session: Session) -> Result<()> {
-    let project_id = ProjectId::from_proto(message.project_id);
-
-    let (room, guest_connection_ids) = &*session
-        .db()
-        .await
-        .unshare_project(project_id, session.connection_id)
-        .await?;
-
-    broadcast(
-        Some(session.connection_id),
-        guest_connection_ids.iter().copied(),
-        |conn_id| session.peer.send(conn_id, message.clone()),
-    );
-    room_updated(&room, &session.peer);
-
-    Ok(())
-}
-
-async fn join_project(
-    request: proto::JoinProject,
-    response: Response<proto::JoinProject>,
-    session: Session,
-) -> Result<()> {
-    let project_id = ProjectId::from_proto(request.project_id);
-    let guest_user_id = session.user_id;
-
-    tracing::info!(%project_id, "join project");
-
-    let (project, replica_id) = &mut *session
-        .db()
-        .await
-        .join_project(project_id, session.connection_id)
-        .await?;
-
-    let collaborators = project
-        .collaborators
-        .iter()
-        .filter(|collaborator| collaborator.connection_id != session.connection_id)
-        .map(|collaborator| collaborator.to_proto())
-        .collect::<Vec<_>>();
-
-    let worktrees = project
-        .worktrees
-        .iter()
-        .map(|(id, worktree)| proto::WorktreeMetadata {
-            id: *id,
-            root_name: worktree.root_name.clone(),
-            visible: worktree.visible,
-            abs_path: worktree.abs_path.clone(),
-        })
-        .collect::<Vec<_>>();
-
-    for collaborator in &collaborators {
-        session
-            .peer
-            .send(
-                collaborator.peer_id.unwrap().into(),
-                proto::AddProjectCollaborator {
-                    project_id: project_id.to_proto(),
-                    collaborator: Some(proto::Collaborator {
-                        peer_id: Some(session.connection_id.into()),
-                        replica_id: replica_id.0 as u32,
-                        user_id: guest_user_id.to_proto(),
-                    }),
-                },
-            )
-            .trace_err();
-    }
-
-    // First, we send the metadata associated with each worktree.
-    response.send(proto::JoinProjectResponse {
-        worktrees: worktrees.clone(),
-        replica_id: replica_id.0 as u32,
-        collaborators: collaborators.clone(),
-        language_servers: project.language_servers.clone(),
-    })?;
-
-    for (worktree_id, worktree) in mem::take(&mut project.worktrees) {
-        #[cfg(any(test, feature = "test-support"))]
-        const MAX_CHUNK_SIZE: usize = 2;
-        #[cfg(not(any(test, feature = "test-support")))]
-        const MAX_CHUNK_SIZE: usize = 256;
-
-        // Stream this worktree's entries.
-        let message = proto::UpdateWorktree {
-            project_id: project_id.to_proto(),
-            worktree_id,
-            abs_path: worktree.abs_path.clone(),
-            root_name: worktree.root_name,
-            updated_entries: worktree.entries,
-            removed_entries: Default::default(),
-            scan_id: worktree.scan_id,
-            is_last_update: worktree.scan_id == worktree.completed_scan_id,
-            updated_repositories: worktree.repository_entries.into_values().collect(),
-            removed_repositories: Default::default(),
-        };
-        for update in proto::split_worktree_update(message, MAX_CHUNK_SIZE) {
-            session.peer.send(session.connection_id, update.clone())?;
-        }
-
-        // Stream this worktree's diagnostics.
-        for summary in worktree.diagnostic_summaries {
-            session.peer.send(
-                session.connection_id,
-                proto::UpdateDiagnosticSummary {
-                    project_id: project_id.to_proto(),
-                    worktree_id: worktree.id,
-                    summary: Some(summary),
-                },
-            )?;
-        }
-
-        for settings_file in worktree.settings_files {
-            session.peer.send(
-                session.connection_id,
-                proto::UpdateWorktreeSettings {
-                    project_id: project_id.to_proto(),
-                    worktree_id: worktree.id,
-                    path: settings_file.path,
-                    content: Some(settings_file.content),
-                },
-            )?;
-        }
-    }
-
-    for language_server in &project.language_servers {
-        session.peer.send(
-            session.connection_id,
-            proto::UpdateLanguageServer {
-                project_id: project_id.to_proto(),
-                language_server_id: language_server.id,
-                variant: Some(
-                    proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(
-                        proto::LspDiskBasedDiagnosticsUpdated {},
-                    ),
-                ),
-            },
-        )?;
-    }
-
-    Ok(())
-}
-
-async fn leave_project(request: proto::LeaveProject, session: Session) -> Result<()> {
-    let sender_id = session.connection_id;
-    let project_id = ProjectId::from_proto(request.project_id);
-
-    let (room, project) = &*session
-        .db()
-        .await
-        .leave_project(project_id, sender_id)
-        .await?;
-    tracing::info!(
-        %project_id,
-        host_user_id = %project.host_user_id,
-        host_connection_id = %project.host_connection_id,
-        "leave project"
-    );
-
-    project_left(&project, &session);
-    room_updated(&room, &session.peer);
-
-    Ok(())
-}
-
-async fn update_project(
-    request: proto::UpdateProject,
-    response: Response<proto::UpdateProject>,
-    session: Session,
-) -> Result<()> {
-    let project_id = ProjectId::from_proto(request.project_id);
-    let (room, guest_connection_ids) = &*session
-        .db()
-        .await
-        .update_project(project_id, session.connection_id, &request.worktrees)
-        .await?;
-    broadcast(
-        Some(session.connection_id),
-        guest_connection_ids.iter().copied(),
-        |connection_id| {
-            session
-                .peer
-                .forward_send(session.connection_id, connection_id, request.clone())
-        },
-    );
-    room_updated(&room, &session.peer);
-    response.send(proto::Ack {})?;
-
-    Ok(())
-}
-
-async fn update_worktree(
-    request: proto::UpdateWorktree,
-    response: Response<proto::UpdateWorktree>,
-    session: Session,
-) -> Result<()> {
-    let guest_connection_ids = session
-        .db()
-        .await
-        .update_worktree(&request, session.connection_id)
-        .await?;
-
-    broadcast(
-        Some(session.connection_id),
-        guest_connection_ids.iter().copied(),
-        |connection_id| {
-            session
-                .peer
-                .forward_send(session.connection_id, connection_id, request.clone())
-        },
-    );
-    response.send(proto::Ack {})?;
-    Ok(())
-}
-
-async fn update_diagnostic_summary(
-    message: proto::UpdateDiagnosticSummary,
-    session: Session,
-) -> Result<()> {
-    let guest_connection_ids = session
-        .db()
-        .await
-        .update_diagnostic_summary(&message, session.connection_id)
-        .await?;
-
-    broadcast(
-        Some(session.connection_id),
-        guest_connection_ids.iter().copied(),
-        |connection_id| {
-            session
-                .peer
-                .forward_send(session.connection_id, connection_id, message.clone())
-        },
-    );
-
-    Ok(())
-}
-
-async fn update_worktree_settings(
-    message: proto::UpdateWorktreeSettings,
-    session: Session,
-) -> Result<()> {
-    let guest_connection_ids = session
-        .db()
-        .await
-        .update_worktree_settings(&message, session.connection_id)
-        .await?;
-
-    broadcast(
-        Some(session.connection_id),
-        guest_connection_ids.iter().copied(),
-        |connection_id| {
-            session
-                .peer
-                .forward_send(session.connection_id, connection_id, message.clone())
-        },
-    );
-
-    Ok(())
-}
-
-async fn refresh_inlay_hints(request: proto::RefreshInlayHints, session: Session) -> Result<()> {
-    broadcast_project_message(request.project_id, request, session).await
-}
-
-async fn start_language_server(
-    request: proto::StartLanguageServer,
-    session: Session,
-) -> Result<()> {
-    let guest_connection_ids = session
-        .db()
-        .await
-        .start_language_server(&request, session.connection_id)
-        .await?;
-
-    broadcast(
-        Some(session.connection_id),
-        guest_connection_ids.iter().copied(),
-        |connection_id| {
-            session
-                .peer
-                .forward_send(session.connection_id, connection_id, request.clone())
-        },
-    );
-    Ok(())
-}
-
-async fn update_language_server(
-    request: proto::UpdateLanguageServer,
-    session: Session,
-) -> Result<()> {
-    let project_id = ProjectId::from_proto(request.project_id);
-    let project_connection_ids = session
-        .db()
-        .await
-        .project_connection_ids(project_id, session.connection_id)
-        .await?;
-    broadcast(
-        Some(session.connection_id),
-        project_connection_ids.iter().copied(),
-        |connection_id| {
-            session
-                .peer
-                .forward_send(session.connection_id, connection_id, request.clone())
-        },
-    );
-    Ok(())
-}
-
-async fn forward_project_request<T>(
-    request: T,
-    response: Response<T>,
-    session: Session,
-) -> Result<()>
-where
-    T: EntityMessage + RequestMessage,
-{
-    let project_id = ProjectId::from_proto(request.remote_entity_id());
-    let host_connection_id = {
-        let collaborators = session
-            .db()
-            .await
-            .project_collaborators(project_id, session.connection_id)
-            .await?;
-        collaborators
-            .iter()
-            .find(|collaborator| collaborator.is_host)
-            .ok_or_else(|| anyhow!("host not found"))?
-            .connection_id
-    };
-
-    let payload = session
-        .peer
-        .forward_request(session.connection_id, host_connection_id, request)
-        .await?;
-
-    response.send(payload)?;
-    Ok(())
-}
-
-async fn create_buffer_for_peer(
-    request: proto::CreateBufferForPeer,
-    session: Session,
-) -> Result<()> {
-    let peer_id = request.peer_id.ok_or_else(|| anyhow!("invalid peer id"))?;
-    session
-        .peer
-        .forward_send(session.connection_id, peer_id.into(), request)?;
-    Ok(())
-}
-
-async fn update_buffer(
-    request: proto::UpdateBuffer,
-    response: Response<proto::UpdateBuffer>,
-    session: Session,
-) -> Result<()> {
-    let project_id = ProjectId::from_proto(request.project_id);
-    let mut guest_connection_ids;
-    let mut host_connection_id = None;
-    {
-        let collaborators = session
-            .db()
-            .await
-            .project_collaborators(project_id, session.connection_id)
-            .await?;
-        guest_connection_ids = Vec::with_capacity(collaborators.len() - 1);
-        for collaborator in collaborators.iter() {
-            if collaborator.is_host {
-                host_connection_id = Some(collaborator.connection_id);
-            } else {
-                guest_connection_ids.push(collaborator.connection_id);
-            }
-        }
-    }
-    let host_connection_id = host_connection_id.ok_or_else(|| anyhow!("host not found"))?;
-
-    broadcast(
-        Some(session.connection_id),
-        guest_connection_ids,
-        |connection_id| {
-            session
-                .peer
-                .forward_send(session.connection_id, connection_id, request.clone())
-        },
-    );
-    if host_connection_id != session.connection_id {
-        session
-            .peer
-            .forward_request(session.connection_id, host_connection_id, request.clone())
-            .await?;
-    }
-
-    response.send(proto::Ack {})?;
-    Ok(())
-}
-
-async fn update_buffer_file(request: proto::UpdateBufferFile, session: Session) -> Result<()> {
-    let project_id = ProjectId::from_proto(request.project_id);
-    let project_connection_ids = session
-        .db()
-        .await
-        .project_connection_ids(project_id, session.connection_id)
-        .await?;
-
-    broadcast(
-        Some(session.connection_id),
-        project_connection_ids.iter().copied(),
-        |connection_id| {
-            session
-                .peer
-                .forward_send(session.connection_id, connection_id, request.clone())
-        },
-    );
-    Ok(())
-}
-
-async fn buffer_reloaded(request: proto::BufferReloaded, session: Session) -> Result<()> {
-    let project_id = ProjectId::from_proto(request.project_id);
-    let project_connection_ids = session
-        .db()
-        .await
-        .project_connection_ids(project_id, session.connection_id)
-        .await?;
-    broadcast(
-        Some(session.connection_id),
-        project_connection_ids.iter().copied(),
-        |connection_id| {
-            session
-                .peer
-                .forward_send(session.connection_id, connection_id, request.clone())
-        },
-    );
-    Ok(())
-}
-
-async fn buffer_saved(request: proto::BufferSaved, session: Session) -> Result<()> {
-    broadcast_project_message(request.project_id, request, session).await
-}
-
-async fn broadcast_project_message<T: EnvelopedMessage>(
-    project_id: u64,
-    request: T,
-    session: Session,
-) -> Result<()> {
-    let project_id = ProjectId::from_proto(project_id);
-    let project_connection_ids = session
-        .db()
-        .await
-        .project_connection_ids(project_id, session.connection_id)
-        .await?;
-    broadcast(
-        Some(session.connection_id),
-        project_connection_ids.iter().copied(),
-        |connection_id| {
-            session
-                .peer
-                .forward_send(session.connection_id, connection_id, request.clone())
-        },
-    );
-    Ok(())
-}
-
-async fn follow(
-    request: proto::Follow,
-    response: Response<proto::Follow>,
-    session: Session,
-) -> Result<()> {
-    let room_id = RoomId::from_proto(request.room_id);
-    let project_id = request.project_id.map(ProjectId::from_proto);
-    let leader_id = request
-        .leader_id
-        .ok_or_else(|| anyhow!("invalid leader id"))?
-        .into();
-    let follower_id = session.connection_id;
-
-    session
-        .db()
-        .await
-        .check_room_participants(room_id, leader_id, session.connection_id)
-        .await?;
-
-    let response_payload = session
-        .peer
-        .forward_request(session.connection_id, leader_id, request)
-        .await?;
-    response.send(response_payload)?;
-
-    if let Some(project_id) = project_id {
-        let room = session
-            .db()
-            .await
-            .follow(room_id, project_id, leader_id, follower_id)
-            .await?;
-        room_updated(&room, &session.peer);
-    }
-
-    Ok(())
-}
-
-async fn unfollow(request: proto::Unfollow, session: Session) -> Result<()> {
-    let room_id = RoomId::from_proto(request.room_id);
-    let project_id = request.project_id.map(ProjectId::from_proto);
-    let leader_id = request
-        .leader_id
-        .ok_or_else(|| anyhow!("invalid leader id"))?
-        .into();
-    let follower_id = session.connection_id;
-
-    session
-        .db()
-        .await
-        .check_room_participants(room_id, leader_id, session.connection_id)
-        .await?;
-
-    session
-        .peer
-        .forward_send(session.connection_id, leader_id, request)?;
-
-    if let Some(project_id) = project_id {
-        let room = session
-            .db()
-            .await
-            .unfollow(room_id, project_id, leader_id, follower_id)
-            .await?;
-        room_updated(&room, &session.peer);
-    }
-
-    Ok(())
-}
-
-async fn update_followers(request: proto::UpdateFollowers, session: Session) -> Result<()> {
-    let room_id = RoomId::from_proto(request.room_id);
-    let database = session.db.lock().await;
-
-    let connection_ids = if let Some(project_id) = request.project_id {
-        let project_id = ProjectId::from_proto(project_id);
-        database
-            .project_connection_ids(project_id, session.connection_id)
-            .await?
-    } else {
-        database
-            .room_connection_ids(room_id, session.connection_id)
-            .await?
-    };
-
-    // For now, don't send view update messages back to that view's current leader.
-    let connection_id_to_omit = request.variant.as_ref().and_then(|variant| match variant {
-        proto::update_followers::Variant::UpdateView(payload) => payload.leader_id,
-        _ => None,
-    });
-
-    for follower_peer_id in request.follower_ids.iter().copied() {
-        let follower_connection_id = follower_peer_id.into();
-        if Some(follower_peer_id) != connection_id_to_omit
-            && connection_ids.contains(&follower_connection_id)
-        {
-            session.peer.forward_send(
-                session.connection_id,
-                follower_connection_id,
-                request.clone(),
-            )?;
-        }
-    }
-    Ok(())
-}
-
-async fn get_users(
-    request: proto::GetUsers,
-    response: Response<proto::GetUsers>,
-    session: Session,
-) -> Result<()> {
-    let user_ids = request
-        .user_ids
-        .into_iter()
-        .map(UserId::from_proto)
-        .collect();
-    let users = session
-        .db()
-        .await
-        .get_users_by_ids(user_ids)
-        .await?
-        .into_iter()
-        .map(|user| proto::User {
-            id: user.id.to_proto(),
-            avatar_url: format!("https://github.com/{}.png?size=128", user.github_login),
-            github_login: user.github_login,
-        })
-        .collect();
-    response.send(proto::UsersResponse { users })?;
-    Ok(())
-}
-
-async fn fuzzy_search_users(
-    request: proto::FuzzySearchUsers,
-    response: Response<proto::FuzzySearchUsers>,
-    session: Session,
-) -> Result<()> {
-    let query = request.query;
-    let users = match query.len() {
-        0 => vec![],
-        1 | 2 => session
-            .db()
-            .await
-            .get_user_by_github_login(&query)
-            .await?
-            .into_iter()
-            .collect(),
-        _ => session.db().await.fuzzy_search_users(&query, 10).await?,
-    };
-    let users = users
-        .into_iter()
-        .filter(|user| user.id != session.user_id)
-        .map(|user| proto::User {
-            id: user.id.to_proto(),
-            avatar_url: format!("https://github.com/{}.png?size=128", user.github_login),
-            github_login: user.github_login,
-        })
-        .collect();
-    response.send(proto::UsersResponse { users })?;
-    Ok(())
-}
-
-async fn request_contact(
-    request: proto::RequestContact,
-    response: Response<proto::RequestContact>,
-    session: Session,
-) -> Result<()> {
-    let requester_id = session.user_id;
-    let responder_id = UserId::from_proto(request.responder_id);
-    if requester_id == responder_id {
-        return Err(anyhow!("cannot add yourself as a contact"))?;
-    }
-
-    let notifications = session
-        .db()
-        .await
-        .send_contact_request(requester_id, responder_id)
-        .await?;
-
-    // Update outgoing contact requests of requester
-    let mut update = proto::UpdateContacts::default();
-    update.outgoing_requests.push(responder_id.to_proto());
-    for connection_id in session
-        .connection_pool()
-        .await
-        .user_connection_ids(requester_id)
-    {
-        session.peer.send(connection_id, update.clone())?;
-    }
-
-    // Update incoming contact requests of responder
-    let mut update = proto::UpdateContacts::default();
-    update
-        .incoming_requests
-        .push(proto::IncomingContactRequest {
-            requester_id: requester_id.to_proto(),
-        });
-    let connection_pool = session.connection_pool().await;
-    for connection_id in connection_pool.user_connection_ids(responder_id) {
-        session.peer.send(connection_id, update.clone())?;
-    }
-
-    send_notifications(&*connection_pool, &session.peer, notifications);
-
-    response.send(proto::Ack {})?;
-    Ok(())
-}
-
-async fn respond_to_contact_request(
-    request: proto::RespondToContactRequest,
-    response: Response<proto::RespondToContactRequest>,
-    session: Session,
-) -> Result<()> {
-    let responder_id = session.user_id;
-    let requester_id = UserId::from_proto(request.requester_id);
-    let db = session.db().await;
-    if request.response == proto::ContactRequestResponse::Dismiss as i32 {
-        db.dismiss_contact_notification(responder_id, requester_id)
-            .await?;
-    } else {
-        let accept = request.response == proto::ContactRequestResponse::Accept as i32;
-
-        let notifications = db
-            .respond_to_contact_request(responder_id, requester_id, accept)
-            .await?;
-        let requester_busy = db.is_user_busy(requester_id).await?;
-        let responder_busy = db.is_user_busy(responder_id).await?;
-
-        let pool = session.connection_pool().await;
-        // Update responder with new contact
-        let mut update = proto::UpdateContacts::default();
-        if accept {
-            update
-                .contacts
-                .push(contact_for_user(requester_id, requester_busy, &pool));
-        }
-        update
-            .remove_incoming_requests
-            .push(requester_id.to_proto());
-        for connection_id in pool.user_connection_ids(responder_id) {
-            session.peer.send(connection_id, update.clone())?;
-        }
-
-        // Update requester with new contact
-        let mut update = proto::UpdateContacts::default();
-        if accept {
-            update
-                .contacts
-                .push(contact_for_user(responder_id, responder_busy, &pool));
-        }
-        update
-            .remove_outgoing_requests
-            .push(responder_id.to_proto());
-
-        for connection_id in pool.user_connection_ids(requester_id) {
-            session.peer.send(connection_id, update.clone())?;
-        }
-
-        send_notifications(&*pool, &session.peer, notifications);
-    }
-
-    response.send(proto::Ack {})?;
-    Ok(())
-}
-
-async fn remove_contact(
-    request: proto::RemoveContact,
-    response: Response<proto::RemoveContact>,
-    session: Session,
-) -> Result<()> {
-    let requester_id = session.user_id;
-    let responder_id = UserId::from_proto(request.user_id);
-    let db = session.db().await;
-    let (contact_accepted, deleted_notification_id) =
-        db.remove_contact(requester_id, responder_id).await?;
-
-    let pool = session.connection_pool().await;
-    // Update outgoing contact requests of requester
-    let mut update = proto::UpdateContacts::default();
-    if contact_accepted {
-        update.remove_contacts.push(responder_id.to_proto());
-    } else {
-        update
-            .remove_outgoing_requests
-            .push(responder_id.to_proto());
-    }
-    for connection_id in pool.user_connection_ids(requester_id) {
-        session.peer.send(connection_id, update.clone())?;
-    }
-
-    // Update incoming contact requests of responder
-    let mut update = proto::UpdateContacts::default();
-    if contact_accepted {
-        update.remove_contacts.push(requester_id.to_proto());
-    } else {
-        update
-            .remove_incoming_requests
-            .push(requester_id.to_proto());
-    }
-    for connection_id in pool.user_connection_ids(responder_id) {
-        session.peer.send(connection_id, update.clone())?;
-        if let Some(notification_id) = deleted_notification_id {
-            session.peer.send(
-                connection_id,
-                proto::DeleteNotification {
-                    notification_id: notification_id.to_proto(),
-                },
-            )?;
-        }
-    }
-
-    response.send(proto::Ack {})?;
-    Ok(())
-}
-
-async fn create_channel(
-    request: proto::CreateChannel,
-    response: Response<proto::CreateChannel>,
-    session: Session,
-) -> Result<()> {
-    let db = session.db().await;
-
-    let parent_id = request.parent_id.map(|id| ChannelId::from_proto(id));
-    let CreateChannelResult {
-        channel,
-        participants_to_update,
-    } = db
-        .create_channel(&request.name, parent_id, session.user_id)
-        .await?;
-
-    response.send(proto::CreateChannelResponse {
-        channel: Some(channel.to_proto()),
-        parent_id: request.parent_id,
-    })?;
-
-    let connection_pool = session.connection_pool().await;
-    for (user_id, channels) in participants_to_update {
-        let update = build_channels_update(channels, vec![]);
-        for connection_id in connection_pool.user_connection_ids(user_id) {
-            if user_id == session.user_id {
-                continue;
-            }
-            session.peer.send(connection_id, update.clone())?;
-        }
-    }
-
-    Ok(())
-}
-
-async fn delete_channel(
-    request: proto::DeleteChannel,
-    response: Response<proto::DeleteChannel>,
-    session: Session,
-) -> Result<()> {
-    let db = session.db().await;
-
-    let channel_id = request.channel_id;
-    let (removed_channels, member_ids) = db
-        .delete_channel(ChannelId::from_proto(channel_id), session.user_id)
-        .await?;
-    response.send(proto::Ack {})?;
-
-    // Notify members of removed channels
-    let mut update = proto::UpdateChannels::default();
-    update
-        .delete_channels
-        .extend(removed_channels.into_iter().map(|id| id.to_proto()));
-
-    let connection_pool = session.connection_pool().await;
-    for member_id in member_ids {
-        for connection_id in connection_pool.user_connection_ids(member_id) {
-            session.peer.send(connection_id, update.clone())?;
-        }
-    }
-
-    Ok(())
-}
-
-async fn invite_channel_member(
-    request: proto::InviteChannelMember,
-    response: Response<proto::InviteChannelMember>,
-    session: Session,
-) -> Result<()> {
-    let db = session.db().await;
-    let channel_id = ChannelId::from_proto(request.channel_id);
-    let invitee_id = UserId::from_proto(request.user_id);
-    let InviteMemberResult {
-        channel,
-        notifications,
-    } = db
-        .invite_channel_member(
-            channel_id,
-            invitee_id,
-            session.user_id,
-            request.role().into(),
-        )
-        .await?;
-
-    let update = proto::UpdateChannels {
-        channel_invitations: vec![channel.to_proto()],
-        ..Default::default()
-    };
-
-    let connection_pool = session.connection_pool().await;
-    for connection_id in connection_pool.user_connection_ids(invitee_id) {
-        session.peer.send(connection_id, update.clone())?;
-    }
-
-    send_notifications(&*connection_pool, &session.peer, notifications);
-
-    response.send(proto::Ack {})?;
-    Ok(())
-}
-
-async fn remove_channel_member(
-    request: proto::RemoveChannelMember,
-    response: Response<proto::RemoveChannelMember>,
-    session: Session,
-) -> Result<()> {
-    let db = session.db().await;
-    let channel_id = ChannelId::from_proto(request.channel_id);
-    let member_id = UserId::from_proto(request.user_id);
-
-    let RemoveChannelMemberResult {
-        membership_update,
-        notification_id,
-    } = db
-        .remove_channel_member(channel_id, member_id, session.user_id)
-        .await?;
-
-    let connection_pool = &session.connection_pool().await;
-    notify_membership_updated(
-        &connection_pool,
-        membership_update,
-        member_id,
-        &session.peer,
-    );
-    for connection_id in connection_pool.user_connection_ids(member_id) {
-        if let Some(notification_id) = notification_id {
-            session
-                .peer
-                .send(
-                    connection_id,
-                    proto::DeleteNotification {
-                        notification_id: notification_id.to_proto(),
-                    },
-                )
-                .trace_err();
-        }
-    }
-
-    response.send(proto::Ack {})?;
-    Ok(())
-}
-
-async fn set_channel_visibility(
-    request: proto::SetChannelVisibility,
-    response: Response<proto::SetChannelVisibility>,
-    session: Session,
-) -> Result<()> {
-    let db = session.db().await;
-    let channel_id = ChannelId::from_proto(request.channel_id);
-    let visibility = request.visibility().into();
-
-    let SetChannelVisibilityResult {
-        participants_to_update,
-        participants_to_remove,
-        channels_to_remove,
-    } = db
-        .set_channel_visibility(channel_id, visibility, session.user_id)
-        .await?;
-
-    let connection_pool = session.connection_pool().await;
-    for (user_id, channels) in participants_to_update {
-        let update = build_channels_update(channels, vec![]);
-        for connection_id in connection_pool.user_connection_ids(user_id) {
-            session.peer.send(connection_id, update.clone())?;
-        }
-    }
-    for user_id in participants_to_remove {
-        let update = proto::UpdateChannels {
-            delete_channels: channels_to_remove.iter().map(|id| id.to_proto()).collect(),
-            ..Default::default()
-        };
-        for connection_id in connection_pool.user_connection_ids(user_id) {
-            session.peer.send(connection_id, update.clone())?;
-        }
-    }
-
-    response.send(proto::Ack {})?;
-    Ok(())
-}
-
-async fn set_channel_member_role(
-    request: proto::SetChannelMemberRole,
-    response: Response<proto::SetChannelMemberRole>,
-    session: Session,
-) -> Result<()> {
-    let db = session.db().await;
-    let channel_id = ChannelId::from_proto(request.channel_id);
-    let member_id = UserId::from_proto(request.user_id);
-    let result = db
-        .set_channel_member_role(
-            channel_id,
-            session.user_id,
-            member_id,
-            request.role().into(),
-        )
-        .await?;
-
-    match result {
-        db::SetMemberRoleResult::MembershipUpdated(membership_update) => {
-            let connection_pool = session.connection_pool().await;
-            notify_membership_updated(
-                &connection_pool,
-                membership_update,
-                member_id,
-                &session.peer,
-            )
-        }
-        db::SetMemberRoleResult::InviteUpdated(channel) => {
-            let update = proto::UpdateChannels {
-                channel_invitations: vec![channel.to_proto()],
-                ..Default::default()
-            };
-
-            for connection_id in session
-                .connection_pool()
-                .await
-                .user_connection_ids(member_id)
-            {
-                session.peer.send(connection_id, update.clone())?;
-            }
-        }
-    }
-
-    response.send(proto::Ack {})?;
-    Ok(())
-}
-
-async fn rename_channel(
-    request: proto::RenameChannel,
-    response: Response<proto::RenameChannel>,
-    session: Session,
-) -> Result<()> {
-    let db = session.db().await;
-    let channel_id = ChannelId::from_proto(request.channel_id);
-    let RenameChannelResult {
-        channel,
-        participants_to_update,
-    } = db
-        .rename_channel(channel_id, session.user_id, &request.name)
-        .await?;
-
-    response.send(proto::RenameChannelResponse {
-        channel: Some(channel.to_proto()),
-    })?;
-
-    let connection_pool = session.connection_pool().await;
-    for (user_id, channel) in participants_to_update {
-        for connection_id in connection_pool.user_connection_ids(user_id) {
-            let update = proto::UpdateChannels {
-                channels: vec![channel.to_proto()],
-                ..Default::default()
-            };
-
-            session.peer.send(connection_id, update.clone())?;
-        }
-    }
-
-    Ok(())
-}
-
-async fn move_channel(
-    request: proto::MoveChannel,
-    response: Response<proto::MoveChannel>,
-    session: Session,
-) -> Result<()> {
-    let channel_id = ChannelId::from_proto(request.channel_id);
-    let to = request.to.map(ChannelId::from_proto);
-
-    let result = session
-        .db()
-        .await
-        .move_channel(channel_id, to, session.user_id)
-        .await?;
-
-    notify_channel_moved(result, session).await?;
-
-    response.send(Ack {})?;
-    Ok(())
-}
-
-async fn notify_channel_moved(result: Option<MoveChannelResult>, session: Session) -> Result<()> {
-    let Some(MoveChannelResult {
-        participants_to_remove,
-        participants_to_update,
-        moved_channels,
-    }) = result
-    else {
-        return Ok(());
-    };
-    let moved_channels: Vec<u64> = moved_channels.iter().map(|id| id.to_proto()).collect();
-
-    let connection_pool = session.connection_pool().await;
-    for (user_id, channels) in participants_to_update {
-        let mut update = build_channels_update(channels, vec![]);
-        update.delete_channels = moved_channels.clone();
-        for connection_id in connection_pool.user_connection_ids(user_id) {
-            session.peer.send(connection_id, update.clone())?;
-        }
-    }
-
-    for user_id in participants_to_remove {
-        let update = proto::UpdateChannels {
-            delete_channels: moved_channels.clone(),
-            ..Default::default()
-        };
-        for connection_id in connection_pool.user_connection_ids(user_id) {
-            session.peer.send(connection_id, update.clone())?;
-        }
-    }
-    Ok(())
-}
-
-async fn get_channel_members(
-    request: proto::GetChannelMembers,
-    response: Response<proto::GetChannelMembers>,
-    session: Session,
-) -> Result<()> {
-    let db = session.db().await;
-    let channel_id = ChannelId::from_proto(request.channel_id);
-    let members = db
-        .get_channel_participant_details(channel_id, session.user_id)
-        .await?;
-    response.send(proto::GetChannelMembersResponse { members })?;
-    Ok(())
-}
-
-async fn respond_to_channel_invite(
-    request: proto::RespondToChannelInvite,
-    response: Response<proto::RespondToChannelInvite>,
-    session: Session,
-) -> Result<()> {
-    let db = session.db().await;
-    let channel_id = ChannelId::from_proto(request.channel_id);
-    let RespondToChannelInvite {
-        membership_update,
-        notifications,
-    } = db
-        .respond_to_channel_invite(channel_id, session.user_id, request.accept)
-        .await?;
-
-    let connection_pool = session.connection_pool().await;
-    if let Some(membership_update) = membership_update {
-        notify_membership_updated(
-            &connection_pool,
-            membership_update,
-            session.user_id,
-            &session.peer,
-        );
-    } else {
-        let update = proto::UpdateChannels {
-            remove_channel_invitations: vec![channel_id.to_proto()],
-            ..Default::default()
-        };
-
-        for connection_id in connection_pool.user_connection_ids(session.user_id) {
-            session.peer.send(connection_id, update.clone())?;
-        }
-    };
-
-    send_notifications(&*connection_pool, &session.peer, notifications);
-
-    response.send(proto::Ack {})?;
-
-    Ok(())
-}
-
-async fn join_channel(
-    request: proto::JoinChannel,
-    response: Response<proto::JoinChannel>,
-    session: Session,
-) -> Result<()> {
-    let channel_id = ChannelId::from_proto(request.channel_id);
-    join_channel_internal(channel_id, Box::new(response), session).await
-}
-
-trait JoinChannelInternalResponse {
-    fn send(self, result: proto::JoinRoomResponse) -> Result<()>;
-}
-impl JoinChannelInternalResponse for Response<proto::JoinChannel> {
-    fn send(self, result: proto::JoinRoomResponse) -> Result<()> {
-        Response::<proto::JoinChannel>::send(self, result)
-    }
-}
-impl JoinChannelInternalResponse for Response<proto::JoinRoom> {
-    fn send(self, result: proto::JoinRoomResponse) -> Result<()> {
-        Response::<proto::JoinRoom>::send(self, result)
-    }
-}
-
-async fn join_channel_internal(
-    channel_id: ChannelId,
-    response: Box<impl JoinChannelInternalResponse>,
-    session: Session,
-) -> Result<()> {
-    let joined_room = {
-        leave_room_for_session(&session).await?;
-        let db = session.db().await;
-
-        let (joined_room, membership_updated, role) = db
-            .join_channel(
-                channel_id,
-                session.user_id,
-                session.connection_id,
-                RELEASE_CHANNEL_NAME.as_str(),
-            )
-            .await?;
-
-        let live_kit_connection_info = session.live_kit_client.as_ref().and_then(|live_kit| {
-            let (can_publish, token) = if role == ChannelRole::Guest {
-                (
-                    false,
-                    live_kit
-                        .guest_token(
-                            &joined_room.room.live_kit_room,
-                            &session.user_id.to_string(),
-                        )
-                        .trace_err()?,
-                )
-            } else {
-                (
-                    true,
-                    live_kit
-                        .room_token(
-                            &joined_room.room.live_kit_room,
-                            &session.user_id.to_string(),
-                        )
-                        .trace_err()?,
-                )
-            };
-
-            Some(LiveKitConnectionInfo {
-                server_url: live_kit.url().into(),
-                token,
-                can_publish,
-            })
-        });
-
-        response.send(proto::JoinRoomResponse {
-            room: Some(joined_room.room.clone()),
-            channel_id: joined_room.channel_id.map(|id| id.to_proto()),
-            live_kit_connection_info,
-        })?;
-
-        let connection_pool = session.connection_pool().await;
-        if let Some(membership_updated) = membership_updated {
-            notify_membership_updated(
-                &connection_pool,
-                membership_updated,
-                session.user_id,
-                &session.peer,
-            );
-        }
-
-        room_updated(&joined_room.room, &session.peer);
-
-        joined_room
-    };
-
-    channel_updated(
-        channel_id,
-        &joined_room.room,
-        &joined_room.channel_members,
-        &session.peer,
-        &*session.connection_pool().await,
-    );
-
-    update_user_contacts(session.user_id, &session).await?;
-    Ok(())
-}
-
-async fn join_channel_buffer(
-    request: proto::JoinChannelBuffer,
-    response: Response<proto::JoinChannelBuffer>,
-    session: Session,
-) -> Result<()> {
-    let db = session.db().await;
-    let channel_id = ChannelId::from_proto(request.channel_id);
-
-    let open_response = db
-        .join_channel_buffer(channel_id, session.user_id, session.connection_id)
-        .await?;
-
-    let collaborators = open_response.collaborators.clone();
-    response.send(open_response)?;
-
-    let update = UpdateChannelBufferCollaborators {
-        channel_id: channel_id.to_proto(),
-        collaborators: collaborators.clone(),
-    };
-    channel_buffer_updated(
-        session.connection_id,
-        collaborators
-            .iter()
-            .filter_map(|collaborator| Some(collaborator.peer_id?.into())),
-        &update,
-        &session.peer,
-    );
-
-    Ok(())
-}
-
-async fn update_channel_buffer(
-    request: proto::UpdateChannelBuffer,
-    session: Session,
-) -> Result<()> {
-    let db = session.db().await;
-    let channel_id = ChannelId::from_proto(request.channel_id);
-
-    let (collaborators, non_collaborators, epoch, version) = db
-        .update_channel_buffer(channel_id, session.user_id, &request.operations)
-        .await?;
-
-    channel_buffer_updated(
-        session.connection_id,
-        collaborators,
-        &proto::UpdateChannelBuffer {
-            channel_id: channel_id.to_proto(),
-            operations: request.operations,
-        },
-        &session.peer,
-    );
-
-    let pool = &*session.connection_pool().await;
-
-    broadcast(
-        None,
-        non_collaborators
-            .iter()
-            .flat_map(|user_id| pool.user_connection_ids(*user_id)),
-        |peer_id| {
-            session.peer.send(
-                peer_id.into(),
-                proto::UpdateChannels {
-                    unseen_channel_buffer_changes: vec![proto::UnseenChannelBufferChange {
-                        channel_id: channel_id.to_proto(),
-                        epoch: epoch as u64,
-                        version: version.clone(),
-                    }],
-                    ..Default::default()
-                },
-            )
-        },
-    );
-
-    Ok(())
-}
-
-async fn rejoin_channel_buffers(
-    request: proto::RejoinChannelBuffers,
-    response: Response<proto::RejoinChannelBuffers>,
-    session: Session,
-) -> Result<()> {
-    let db = session.db().await;
-    let buffers = db
-        .rejoin_channel_buffers(&request.buffers, session.user_id, session.connection_id)
-        .await?;
-
-    for rejoined_buffer in &buffers {
-        let collaborators_to_notify = rejoined_buffer
-            .buffer
-            .collaborators
-            .iter()
-            .filter_map(|c| Some(c.peer_id?.into()));
-        channel_buffer_updated(
-            session.connection_id,
-            collaborators_to_notify,
-            &proto::UpdateChannelBufferCollaborators {
-                channel_id: rejoined_buffer.buffer.channel_id,
-                collaborators: rejoined_buffer.buffer.collaborators.clone(),
-            },
-            &session.peer,
-        );
-    }
-
-    response.send(proto::RejoinChannelBuffersResponse {
-        buffers: buffers.into_iter().map(|b| b.buffer).collect(),
-    })?;
-
-    Ok(())
-}
-
-async fn leave_channel_buffer(
-    request: proto::LeaveChannelBuffer,
-    response: Response<proto::LeaveChannelBuffer>,
-    session: Session,
-) -> Result<()> {
-    let db = session.db().await;
-    let channel_id = ChannelId::from_proto(request.channel_id);
-
-    let left_buffer = db
-        .leave_channel_buffer(channel_id, session.connection_id)
-        .await?;
-
-    response.send(Ack {})?;
-
-    channel_buffer_updated(
-        session.connection_id,
-        left_buffer.connections,
-        &proto::UpdateChannelBufferCollaborators {
-            channel_id: channel_id.to_proto(),
-            collaborators: left_buffer.collaborators,
-        },
-        &session.peer,
-    );
-
-    Ok(())
-}
-
-fn channel_buffer_updated<T: EnvelopedMessage>(
-    sender_id: ConnectionId,
-    collaborators: impl IntoIterator<Item = ConnectionId>,
-    message: &T,
-    peer: &Peer,
-) {
-    broadcast(Some(sender_id), collaborators.into_iter(), |peer_id| {
-        peer.send(peer_id.into(), message.clone())
-    });
-}
-
-fn send_notifications(
-    connection_pool: &ConnectionPool,
-    peer: &Peer,
-    notifications: db::NotificationBatch,
-) {
-    for (user_id, notification) in notifications {
-        for connection_id in connection_pool.user_connection_ids(user_id) {
-            if let Err(error) = peer.send(
-                connection_id,
-                proto::AddNotification {
-                    notification: Some(notification.clone()),
-                },
-            ) {
-                tracing::error!(
-                    "failed to send notification to {:?} {}",
-                    connection_id,
-                    error
-                );
-            }
-        }
-    }
-}
-
-async fn send_channel_message(
-    request: proto::SendChannelMessage,
-    response: Response<proto::SendChannelMessage>,
-    session: Session,
-) -> Result<()> {
-    // Validate the message body.
-    let body = request.body.trim().to_string();
-    if body.len() > MAX_MESSAGE_LEN {
-        return Err(anyhow!("message is too long"))?;
-    }
-    if body.is_empty() {
-        return Err(anyhow!("message can't be blank"))?;
-    }
-
-    // TODO: adjust mentions if body is trimmed
-
-    let timestamp = OffsetDateTime::now_utc();
-    let nonce = request
-        .nonce
-        .ok_or_else(|| anyhow!("nonce can't be blank"))?;
-
-    let channel_id = ChannelId::from_proto(request.channel_id);
-    let CreatedChannelMessage {
-        message_id,
-        participant_connection_ids,
-        channel_members,
-        notifications,
-    } = session
-        .db()
-        .await
-        .create_channel_message(
-            channel_id,
-            session.user_id,
-            &body,
-            &request.mentions,
-            timestamp,
-            nonce.clone().into(),
-        )
-        .await?;
-    let message = proto::ChannelMessage {
-        sender_id: session.user_id.to_proto(),
-        id: message_id.to_proto(),
-        body,
-        mentions: request.mentions,
-        timestamp: timestamp.unix_timestamp() as u64,
-        nonce: Some(nonce),
-    };
-    broadcast(
-        Some(session.connection_id),
-        participant_connection_ids,
-        |connection| {
-            session.peer.send(
-                connection,
-                proto::ChannelMessageSent {
-                    channel_id: channel_id.to_proto(),
-                    message: Some(message.clone()),
-                },
-            )
-        },
-    );
-    response.send(proto::SendChannelMessageResponse {
-        message: Some(message),
-    })?;
-
-    let pool = &*session.connection_pool().await;
-    broadcast(
-        None,
-        channel_members
-            .iter()
-            .flat_map(|user_id| pool.user_connection_ids(*user_id)),
-        |peer_id| {
-            session.peer.send(
-                peer_id.into(),
-                proto::UpdateChannels {
-                    unseen_channel_messages: vec![proto::UnseenChannelMessage {
-                        channel_id: channel_id.to_proto(),
-                        message_id: message_id.to_proto(),
-                    }],
-                    ..Default::default()
-                },
-            )
-        },
-    );
-    send_notifications(pool, &session.peer, notifications);
-
-    Ok(())
-}
-
-async fn remove_channel_message(
-    request: proto::RemoveChannelMessage,
-    response: Response<proto::RemoveChannelMessage>,
-    session: Session,
-) -> Result<()> {
-    let channel_id = ChannelId::from_proto(request.channel_id);
-    let message_id = MessageId::from_proto(request.message_id);
-    let connection_ids = session
-        .db()
-        .await
-        .remove_channel_message(channel_id, message_id, session.user_id)
-        .await?;
-    broadcast(Some(session.connection_id), connection_ids, |connection| {
-        session.peer.send(connection, request.clone())
-    });
-    response.send(proto::Ack {})?;
-    Ok(())
-}
-
-async fn acknowledge_channel_message(
-    request: proto::AckChannelMessage,
-    session: Session,
-) -> Result<()> {
-    let channel_id = ChannelId::from_proto(request.channel_id);
-    let message_id = MessageId::from_proto(request.message_id);
-    let notifications = session
-        .db()
-        .await
-        .observe_channel_message(channel_id, session.user_id, message_id)
-        .await?;
-    send_notifications(
-        &*session.connection_pool().await,
-        &session.peer,
-        notifications,
-    );
-    Ok(())
-}
-
-async fn acknowledge_buffer_version(
-    request: proto::AckBufferOperation,
-    session: Session,
-) -> Result<()> {
-    let buffer_id = BufferId::from_proto(request.buffer_id);
-    session
-        .db()
-        .await
-        .observe_buffer_version(
-            buffer_id,
-            session.user_id,
-            request.epoch as i32,
-            &request.version,
-        )
-        .await?;
-    Ok(())
-}
-
-async fn join_channel_chat(
-    request: proto::JoinChannelChat,
-    response: Response<proto::JoinChannelChat>,
-    session: Session,
-) -> Result<()> {
-    let channel_id = ChannelId::from_proto(request.channel_id);
-
-    let db = session.db().await;
-    db.join_channel_chat(channel_id, session.connection_id, session.user_id)
-        .await?;
-    let messages = db
-        .get_channel_messages(channel_id, session.user_id, MESSAGE_COUNT_PER_PAGE, None)
-        .await?;
-    response.send(proto::JoinChannelChatResponse {
-        done: messages.len() < MESSAGE_COUNT_PER_PAGE,
-        messages,
-    })?;
-    Ok(())
-}
-
-async fn leave_channel_chat(request: proto::LeaveChannelChat, session: Session) -> Result<()> {
-    let channel_id = ChannelId::from_proto(request.channel_id);
-    session
-        .db()
-        .await
-        .leave_channel_chat(channel_id, session.connection_id, session.user_id)
-        .await?;
-    Ok(())
-}
-
-async fn get_channel_messages(
-    request: proto::GetChannelMessages,
-    response: Response<proto::GetChannelMessages>,
-    session: Session,
-) -> Result<()> {
-    let channel_id = ChannelId::from_proto(request.channel_id);
-    let messages = session
-        .db()
-        .await
-        .get_channel_messages(
-            channel_id,
-            session.user_id,
-            MESSAGE_COUNT_PER_PAGE,
-            Some(MessageId::from_proto(request.before_message_id)),
-        )
-        .await?;
-    response.send(proto::GetChannelMessagesResponse {
-        done: messages.len() < MESSAGE_COUNT_PER_PAGE,
-        messages,
-    })?;
-    Ok(())
-}
-
-async fn get_channel_messages_by_id(
-    request: proto::GetChannelMessagesById,
-    response: Response<proto::GetChannelMessagesById>,
-    session: Session,
-) -> Result<()> {
-    let message_ids = request
-        .message_ids
-        .iter()
-        .map(|id| MessageId::from_proto(*id))
-        .collect::<Vec<_>>();
-    let messages = session
-        .db()
-        .await
-        .get_channel_messages_by_id(session.user_id, &message_ids)
-        .await?;
-    response.send(proto::GetChannelMessagesResponse {
-        done: messages.len() < MESSAGE_COUNT_PER_PAGE,
-        messages,
-    })?;
-    Ok(())
-}
-
-async fn get_notifications(
-    request: proto::GetNotifications,
-    response: Response<proto::GetNotifications>,
-    session: Session,
-) -> Result<()> {
-    let notifications = session
-        .db()
-        .await
-        .get_notifications(
-            session.user_id,
-            NOTIFICATION_COUNT_PER_PAGE,
-            request
-                .before_id
-                .map(|id| db::NotificationId::from_proto(id)),
-        )
-        .await?;
-    response.send(proto::GetNotificationsResponse {
-        done: notifications.len() < NOTIFICATION_COUNT_PER_PAGE,
-        notifications,
-    })?;
-    Ok(())
-}
-
-async fn mark_notification_as_read(
-    request: proto::MarkNotificationRead,
-    response: Response<proto::MarkNotificationRead>,
-    session: Session,
-) -> Result<()> {
-    let database = &session.db().await;
-    let notifications = database
-        .mark_notification_as_read_by_id(
-            session.user_id,
-            NotificationId::from_proto(request.notification_id),
-        )
-        .await?;
-    send_notifications(
-        &*session.connection_pool().await,
-        &session.peer,
-        notifications,
-    );
-    response.send(proto::Ack {})?;
-    Ok(())
-}
-
-async fn update_diff_base(request: proto::UpdateDiffBase, session: Session) -> Result<()> {
-    let project_id = ProjectId::from_proto(request.project_id);
-    let project_connection_ids = session
-        .db()
-        .await
-        .project_connection_ids(project_id, session.connection_id)
-        .await?;
-    broadcast(
-        Some(session.connection_id),
-        project_connection_ids.iter().copied(),
-        |connection_id| {
-            session
-                .peer
-                .forward_send(session.connection_id, connection_id, request.clone())
-        },
-    );
-    Ok(())
-}
-
-async fn get_private_user_info(
-    _request: proto::GetPrivateUserInfo,
-    response: Response<proto::GetPrivateUserInfo>,
-    session: Session,
-) -> Result<()> {
-    let db = session.db().await;
-
-    let metrics_id = db.get_user_metrics_id(session.user_id).await?;
-    let user = db
-        .get_user_by_id(session.user_id)
-        .await?
-        .ok_or_else(|| anyhow!("user not found"))?;
-    let flags = db.get_user_flags(session.user_id).await?;
-
-    response.send(proto::GetPrivateUserInfoResponse {
-        metrics_id,
-        staff: user.admin,
-        flags,
-    })?;
-    Ok(())
-}
-
-fn to_axum_message(message: TungsteniteMessage) -> AxumMessage {
-    match message {
-        TungsteniteMessage::Text(payload) => AxumMessage::Text(payload),
-        TungsteniteMessage::Binary(payload) => AxumMessage::Binary(payload),
-        TungsteniteMessage::Ping(payload) => AxumMessage::Ping(payload),
-        TungsteniteMessage::Pong(payload) => AxumMessage::Pong(payload),
-        TungsteniteMessage::Close(frame) => AxumMessage::Close(frame.map(|frame| AxumCloseFrame {
-            code: frame.code.into(),
-            reason: frame.reason,
-        })),
-    }
-}
-
-fn to_tungstenite_message(message: AxumMessage) -> TungsteniteMessage {
-    match message {
-        AxumMessage::Text(payload) => TungsteniteMessage::Text(payload),
-        AxumMessage::Binary(payload) => TungsteniteMessage::Binary(payload),
-        AxumMessage::Ping(payload) => TungsteniteMessage::Ping(payload),
-        AxumMessage::Pong(payload) => TungsteniteMessage::Pong(payload),
-        AxumMessage::Close(frame) => {
-            TungsteniteMessage::Close(frame.map(|frame| TungsteniteCloseFrame {
-                code: frame.code.into(),
-                reason: frame.reason,
-            }))
-        }
-    }
-}
-
-fn notify_membership_updated(
-    connection_pool: &ConnectionPool,
-    result: MembershipUpdated,
-    user_id: UserId,
-    peer: &Peer,
-) {
-    let mut update = build_channels_update(result.new_channels, vec![]);
-    update.delete_channels = result
-        .removed_channels
-        .into_iter()
-        .map(|id| id.to_proto())
-        .collect();
-    update.remove_channel_invitations = vec![result.channel_id.to_proto()];
-
-    for connection_id in connection_pool.user_connection_ids(user_id) {
-        peer.send(connection_id, update.clone()).trace_err();
-    }
-}
-
-fn build_channels_update(
-    channels: ChannelsForUser,
-    channel_invites: Vec<db::Channel>,
-) -> proto::UpdateChannels {
-    let mut update = proto::UpdateChannels::default();
-
-    for channel in channels.channels {
-        update.channels.push(channel.to_proto());
-    }
-
-    update.unseen_channel_buffer_changes = channels.unseen_buffer_changes;
-    update.unseen_channel_messages = channels.channel_messages;
-
-    for (channel_id, participants) in channels.channel_participants {
-        update
-            .channel_participants
-            .push(proto::ChannelParticipants {
-                channel_id: channel_id.to_proto(),
-                participant_user_ids: participants.into_iter().map(|id| id.to_proto()).collect(),
-            });
-    }
-
-    for channel in channel_invites {
-        update.channel_invitations.push(channel.to_proto());
-    }
-
-    update
-}
-
-fn build_initial_contacts_update(
-    contacts: Vec<db::Contact>,
-    pool: &ConnectionPool,
-) -> proto::UpdateContacts {
-    let mut update = proto::UpdateContacts::default();
-
-    for contact in contacts {
-        match contact {
-            db::Contact::Accepted { user_id, busy } => {
-                update.contacts.push(contact_for_user(user_id, busy, &pool));
-            }
-            db::Contact::Outgoing { user_id } => update.outgoing_requests.push(user_id.to_proto()),
-            db::Contact::Incoming { user_id } => {
-                update
-                    .incoming_requests
-                    .push(proto::IncomingContactRequest {
-                        requester_id: user_id.to_proto(),
-                    })
-            }
-        }
-    }
-
-    update
-}
-
-fn contact_for_user(user_id: UserId, busy: bool, pool: &ConnectionPool) -> proto::Contact {
-    proto::Contact {
-        user_id: user_id.to_proto(),
-        online: pool.is_user_online(user_id),
-        busy,
-    }
-}
-
-fn room_updated(room: &proto::Room, peer: &Peer) {
-    broadcast(
-        None,
-        room.participants
-            .iter()
-            .filter_map(|participant| Some(participant.peer_id?.into())),
-        |peer_id| {
-            peer.send(
-                peer_id.into(),
-                proto::RoomUpdated {
-                    room: Some(room.clone()),
-                },
-            )
-        },
-    );
-}
-
-fn channel_updated(
-    channel_id: ChannelId,
-    room: &proto::Room,
-    channel_members: &[UserId],
-    peer: &Peer,
-    pool: &ConnectionPool,
-) {
-    let participants = room
-        .participants
-        .iter()
-        .map(|p| p.user_id)
-        .collect::<Vec<_>>();
-
-    broadcast(
-        None,
-        channel_members
-            .iter()
-            .flat_map(|user_id| pool.user_connection_ids(*user_id)),
-        |peer_id| {
-            peer.send(
-                peer_id.into(),
-                proto::UpdateChannels {
-                    channel_participants: vec![proto::ChannelParticipants {
-                        channel_id: channel_id.to_proto(),
-                        participant_user_ids: participants.clone(),
-                    }],
-                    ..Default::default()
-                },
-            )
-        },
-    );
-}
-
-async fn update_user_contacts(user_id: UserId, session: &Session) -> Result<()> {
-    let db = session.db().await;
-
-    let contacts = db.get_contacts(user_id).await?;
-    let busy = db.is_user_busy(user_id).await?;
-
-    let pool = session.connection_pool().await;
-    let updated_contact = contact_for_user(user_id, busy, &pool);
-    for contact in contacts {
-        if let db::Contact::Accepted {
-            user_id: contact_user_id,
-            ..
-        } = contact
-        {
-            for contact_conn_id in pool.user_connection_ids(contact_user_id) {
-                session
-                    .peer
-                    .send(
-                        contact_conn_id,
-                        proto::UpdateContacts {
-                            contacts: vec![updated_contact.clone()],
-                            remove_contacts: Default::default(),
-                            incoming_requests: Default::default(),
-                            remove_incoming_requests: Default::default(),
-                            outgoing_requests: Default::default(),
-                            remove_outgoing_requests: Default::default(),
-                        },
-                    )
-                    .trace_err();
-            }
-        }
-    }
-    Ok(())
-}
-
-async fn leave_room_for_session(session: &Session) -> Result<()> {
-    let mut contacts_to_update = HashSet::default();
-
-    let room_id;
-    let canceled_calls_to_user_ids;
-    let live_kit_room;
-    let delete_live_kit_room;
-    let room;
-    let channel_members;
-    let channel_id;
-
-    if let Some(mut left_room) = session.db().await.leave_room(session.connection_id).await? {
-        contacts_to_update.insert(session.user_id);
-
-        for project in left_room.left_projects.values() {
-            project_left(project, session);
-        }
-
-        room_id = RoomId::from_proto(left_room.room.id);
-        canceled_calls_to_user_ids = mem::take(&mut left_room.canceled_calls_to_user_ids);
-        live_kit_room = mem::take(&mut left_room.room.live_kit_room);
-        delete_live_kit_room = left_room.deleted;
-        room = mem::take(&mut left_room.room);
-        channel_members = mem::take(&mut left_room.channel_members);
-        channel_id = left_room.channel_id;
-
-        room_updated(&room, &session.peer);
-    } else {
-        return Ok(());
-    }
-
-    if let Some(channel_id) = channel_id {
-        channel_updated(
-            channel_id,
-            &room,
-            &channel_members,
-            &session.peer,
-            &*session.connection_pool().await,
-        );
-    }
-
-    {
-        let pool = session.connection_pool().await;
-        for canceled_user_id in canceled_calls_to_user_ids {
-            for connection_id in pool.user_connection_ids(canceled_user_id) {
-                session
-                    .peer
-                    .send(
-                        connection_id,
-                        proto::CallCanceled {
-                            room_id: room_id.to_proto(),
-                        },
-                    )
-                    .trace_err();
-            }
-            contacts_to_update.insert(canceled_user_id);
-        }
-    }
-
-    for contact_user_id in contacts_to_update {
-        update_user_contacts(contact_user_id, &session).await?;
-    }
-
-    if let Some(live_kit) = session.live_kit_client.as_ref() {
-        live_kit
-            .remove_participant(live_kit_room.clone(), session.user_id.to_string())
-            .await
-            .trace_err();
-
-        if delete_live_kit_room {
-            live_kit.delete_room(live_kit_room).await.trace_err();
-        }
-    }
-
-    Ok(())
-}
-
-async fn leave_channel_buffers_for_session(session: &Session) -> Result<()> {
-    let left_channel_buffers = session
-        .db()
-        .await
-        .leave_channel_buffers(session.connection_id)
-        .await?;
-
-    for left_buffer in left_channel_buffers {
-        channel_buffer_updated(
-            session.connection_id,
-            left_buffer.connections,
-            &proto::UpdateChannelBufferCollaborators {
-                channel_id: left_buffer.channel_id.to_proto(),
-                collaborators: left_buffer.collaborators,
-            },
-            &session.peer,
-        );
-    }
-
-    Ok(())
-}
-
-fn project_left(project: &db::LeftProject, session: &Session) {
-    for connection_id in &project.connection_ids {
-        if project.host_user_id == session.user_id {
-            session
-                .peer
-                .send(
-                    *connection_id,
-                    proto::UnshareProject {
-                        project_id: project.id.to_proto(),
-                    },
-                )
-                .trace_err();
-        } else {
-            session
-                .peer
-                .send(
-                    *connection_id,
-                    proto::RemoveProjectCollaborator {
-                        project_id: project.id.to_proto(),
-                        peer_id: Some(session.connection_id.into()),
-                    },
-                )
-                .trace_err();
-        }
-    }
-}
-
-pub trait ResultExt {
-    type Ok;
-
-    fn trace_err(self) -> Option<Self::Ok>;
-}
-
-impl<T, E> ResultExt for Result<T, E>
-where
-    E: std::fmt::Debug,
-{
-    type Ok = T;
-
-    fn trace_err(self) -> Option<T> {
-        match self {
-            Ok(value) => Some(value),
-            Err(error) => {
-                tracing::error!("{:?}", error);
-                None
-            }
-        }
-    }
-}

crates/collab2/src/rpc/connection_pool.rs 🔗

@@ -1,98 +0,0 @@
-use crate::db::UserId;
-use anyhow::{anyhow, Result};
-use collections::{BTreeMap, HashSet};
-use rpc::ConnectionId;
-use serde::Serialize;
-use tracing::instrument;
-
-#[derive(Default, Serialize)]
-pub struct ConnectionPool {
-    connections: BTreeMap<ConnectionId, Connection>,
-    connected_users: BTreeMap<UserId, ConnectedUser>,
-}
-
-#[derive(Default, Serialize)]
-struct ConnectedUser {
-    connection_ids: HashSet<ConnectionId>,
-}
-
-#[derive(Serialize)]
-pub struct Connection {
-    pub user_id: UserId,
-    pub admin: bool,
-}
-
-impl ConnectionPool {
-    pub fn reset(&mut self) {
-        self.connections.clear();
-        self.connected_users.clear();
-    }
-
-    #[instrument(skip(self))]
-    pub fn add_connection(&mut self, connection_id: ConnectionId, user_id: UserId, admin: bool) {
-        self.connections
-            .insert(connection_id, Connection { user_id, admin });
-        let connected_user = self.connected_users.entry(user_id).or_default();
-        connected_user.connection_ids.insert(connection_id);
-    }
-
-    #[instrument(skip(self))]
-    pub fn remove_connection(&mut self, connection_id: ConnectionId) -> Result<()> {
-        let connection = self
-            .connections
-            .get_mut(&connection_id)
-            .ok_or_else(|| anyhow!("no such connection"))?;
-
-        let user_id = connection.user_id;
-        let connected_user = self.connected_users.get_mut(&user_id).unwrap();
-        connected_user.connection_ids.remove(&connection_id);
-        if connected_user.connection_ids.is_empty() {
-            self.connected_users.remove(&user_id);
-        }
-        self.connections.remove(&connection_id).unwrap();
-        Ok(())
-    }
-
-    pub fn connections(&self) -> impl Iterator<Item = &Connection> {
-        self.connections.values()
-    }
-
-    pub fn user_connection_ids(&self, user_id: UserId) -> impl Iterator<Item = ConnectionId> + '_ {
-        self.connected_users
-            .get(&user_id)
-            .into_iter()
-            .map(|state| &state.connection_ids)
-            .flatten()
-            .copied()
-    }
-
-    pub fn is_user_online(&self, user_id: UserId) -> bool {
-        !self
-            .connected_users
-            .get(&user_id)
-            .unwrap_or(&Default::default())
-            .connection_ids
-            .is_empty()
-    }
-
-    #[cfg(test)]
-    pub fn check_invariants(&self) {
-        for (connection_id, connection) in &self.connections {
-            assert!(self
-                .connected_users
-                .get(&connection.user_id)
-                .unwrap()
-                .connection_ids
-                .contains(connection_id));
-        }
-
-        for (user_id, state) in &self.connected_users {
-            for connection_id in &state.connection_ids {
-                assert_eq!(
-                    self.connections.get(connection_id).unwrap().user_id,
-                    *user_id
-                );
-            }
-        }
-    }
-}

crates/collab2/src/tests.rs 🔗

@@ -1,47 +0,0 @@
-use call::Room;
-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;
-mod random_channel_buffer_tests;
-mod random_project_collaboration_tests;
-mod randomized_test_helpers;
-mod test_server;
-
-pub use randomized_test_helpers::{
-    run_randomized_test, save_randomized_test_plan, RandomizedTest, TestError, UserTestPlan,
-};
-pub use test_server::{TestClient, TestServer};
-
-#[derive(Debug, Eq, PartialEq)]
-struct RoomParticipants {
-    remote: Vec<String>,
-    pending: Vec<String>,
-}
-
-fn room_participants(room: &Model<Room>, cx: &mut TestAppContext) -> RoomParticipants {
-    room.read_with(cx, |room, _| {
-        let mut remote = room
-            .remote_participants()
-            .iter()
-            .map(|(_, participant)| participant.user.github_login.clone())
-            .collect::<Vec<_>>();
-        let mut pending = room
-            .pending_participants()
-            .iter()
-            .map(|user| user.github_login.clone())
-            .collect::<Vec<_>>();
-        remote.sort();
-        pending.sort();
-        RoomParticipants { remote, pending }
-    })
-}
-
-fn channel_id(room: &Model<Room>, cx: &mut TestAppContext) -> Option<u64> {
-    cx.read(|cx| room.read(cx).channel_id())
-}

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

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

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

@@ -1,408 +0,0 @@
-use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
-use channel::{ChannelChat, ChannelMessageId, MessageParams};
-use gpui::{BackgroundExecutor, Model, TestAppContext};
-use rpc::Notification;
-
-#[gpui::test]
-async fn test_basic_channel_messages(
-    executor: BackgroundExecutor,
-    mut cx_a: &mut TestAppContext,
-    mut cx_b: &mut TestAppContext,
-    mut cx_c: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    let client_c = server.create_client(cx_c, "user_c").await;
-
-    let channel_id = server
-        .make_channel(
-            "the-channel",
-            None,
-            (&client_a, cx_a),
-            &mut [(&client_b, cx_b), (&client_c, cx_c)],
-        )
-        .await;
-
-    let channel_chat_a = client_a
-        .channel_store()
-        .update(cx_a, |store, cx| store.open_channel_chat(channel_id, cx))
-        .await
-        .unwrap();
-    let channel_chat_b = client_b
-        .channel_store()
-        .update(cx_b, |store, cx| store.open_channel_chat(channel_id, cx))
-        .await
-        .unwrap();
-
-    let message_id = channel_chat_a
-        .update(cx_a, |c, cx| {
-            c.send_message(
-                MessageParams {
-                    text: "hi @user_c!".into(),
-                    mentions: vec![(3..10, client_c.id())],
-                },
-                cx,
-            )
-            .unwrap()
-        })
-        .await
-        .unwrap();
-    channel_chat_a
-        .update(cx_a, |c, cx| c.send_message("two".into(), cx).unwrap())
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-    channel_chat_b
-        .update(cx_b, |c, cx| c.send_message("three".into(), cx).unwrap())
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-
-    let channel_chat_c = client_c
-        .channel_store()
-        .update(cx_c, |store, cx| store.open_channel_chat(channel_id, cx))
-        .await
-        .unwrap();
-
-    for (chat, cx) in [
-        (&channel_chat_a, &mut cx_a),
-        (&channel_chat_b, &mut cx_b),
-        (&channel_chat_c, &mut cx_c),
-    ] {
-        chat.update(*cx, |c, _| {
-            assert_eq!(
-                c.messages()
-                    .iter()
-                    .map(|m| (m.body.as_str(), m.mentions.as_slice()))
-                    .collect::<Vec<_>>(),
-                vec![
-                    ("hi @user_c!", [(3..10, client_c.id())].as_slice()),
-                    ("two", &[]),
-                    ("three", &[])
-                ],
-                "results for user {}",
-                c.client().id(),
-            );
-        });
-    }
-
-    client_c.notification_store().update(cx_c, |store, _| {
-        assert_eq!(store.notification_count(), 2);
-        assert_eq!(store.unread_notification_count(), 1);
-        assert_eq!(
-            store.notification_at(0).unwrap().notification,
-            Notification::ChannelMessageMention {
-                message_id,
-                sender_id: client_a.id(),
-                channel_id,
-            }
-        );
-        assert_eq!(
-            store.notification_at(1).unwrap().notification,
-            Notification::ChannelInvitation {
-                channel_id,
-                channel_name: "the-channel".to_string(),
-                inviter_id: client_a.id()
-            }
-        );
-    });
-}
-
-#[gpui::test]
-async fn test_rejoin_channel_chat(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-
-    let channel_id = server
-        .make_channel(
-            "the-channel",
-            None,
-            (&client_a, cx_a),
-            &mut [(&client_b, cx_b)],
-        )
-        .await;
-
-    let channel_chat_a = client_a
-        .channel_store()
-        .update(cx_a, |store, cx| store.open_channel_chat(channel_id, cx))
-        .await
-        .unwrap();
-    let channel_chat_b = client_b
-        .channel_store()
-        .update(cx_b, |store, cx| store.open_channel_chat(channel_id, cx))
-        .await
-        .unwrap();
-
-    channel_chat_a
-        .update(cx_a, |c, cx| c.send_message("one".into(), cx).unwrap())
-        .await
-        .unwrap();
-    channel_chat_b
-        .update(cx_b, |c, cx| c.send_message("two".into(), cx).unwrap())
-        .await
-        .unwrap();
-
-    server.forbid_connections();
-    server.disconnect_client(client_a.peer_id().unwrap());
-
-    // While client A is disconnected, clients A and B both send new messages.
-    channel_chat_a
-        .update(cx_a, |c, cx| c.send_message("three".into(), cx).unwrap())
-        .await
-        .unwrap_err();
-    channel_chat_a
-        .update(cx_a, |c, cx| c.send_message("four".into(), cx).unwrap())
-        .await
-        .unwrap_err();
-    channel_chat_b
-        .update(cx_b, |c, cx| c.send_message("five".into(), cx).unwrap())
-        .await
-        .unwrap();
-    channel_chat_b
-        .update(cx_b, |c, cx| c.send_message("six".into(), cx).unwrap())
-        .await
-        .unwrap();
-
-    // Client A reconnects.
-    server.allow_connections();
-    executor.advance_clock(RECONNECT_TIMEOUT);
-
-    // Client A fetches the messages that were sent while they were disconnected
-    // and resends their own messages which failed to send.
-    let expected_messages = &["one", "two", "five", "six", "three", "four"];
-    assert_messages(&channel_chat_a, expected_messages, cx_a);
-    assert_messages(&channel_chat_b, expected_messages, cx_b);
-}
-
-#[gpui::test]
-async fn test_remove_channel_message(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-    cx_c: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    let client_c = server.create_client(cx_c, "user_c").await;
-
-    let channel_id = server
-        .make_channel(
-            "the-channel",
-            None,
-            (&client_a, cx_a),
-            &mut [(&client_b, cx_b), (&client_c, cx_c)],
-        )
-        .await;
-
-    let channel_chat_a = client_a
-        .channel_store()
-        .update(cx_a, |store, cx| store.open_channel_chat(channel_id, cx))
-        .await
-        .unwrap();
-    let channel_chat_b = client_b
-        .channel_store()
-        .update(cx_b, |store, cx| store.open_channel_chat(channel_id, cx))
-        .await
-        .unwrap();
-
-    // Client A sends some messages.
-    channel_chat_a
-        .update(cx_a, |c, cx| c.send_message("one".into(), cx).unwrap())
-        .await
-        .unwrap();
-    channel_chat_a
-        .update(cx_a, |c, cx| c.send_message("two".into(), cx).unwrap())
-        .await
-        .unwrap();
-    channel_chat_a
-        .update(cx_a, |c, cx| c.send_message("three".into(), cx).unwrap())
-        .await
-        .unwrap();
-
-    // Clients A and B see all of the messages.
-    executor.run_until_parked();
-    let expected_messages = &["one", "two", "three"];
-    assert_messages(&channel_chat_a, expected_messages, cx_a);
-    assert_messages(&channel_chat_b, expected_messages, cx_b);
-
-    // Client A deletes one of their messages.
-    channel_chat_a
-        .update(cx_a, |c, cx| {
-            let ChannelMessageId::Saved(id) = c.message(1).id else {
-                panic!("message not saved")
-            };
-            c.remove_message(id, cx)
-        })
-        .await
-        .unwrap();
-
-    // Client B sees that the message is gone.
-    executor.run_until_parked();
-    let expected_messages = &["one", "three"];
-    assert_messages(&channel_chat_a, expected_messages, cx_a);
-    assert_messages(&channel_chat_b, expected_messages, cx_b);
-
-    // Client C joins the channel chat, and does not see the deleted message.
-    let channel_chat_c = client_c
-        .channel_store()
-        .update(cx_c, |store, cx| store.open_channel_chat(channel_id, cx))
-        .await
-        .unwrap();
-    assert_messages(&channel_chat_c, expected_messages, cx_c);
-}
-
-#[track_caller]
-fn assert_messages(chat: &Model<ChannelChat>, messages: &[&str], cx: &mut TestAppContext) {
-    // todo!(don't directly borrow here)
-    assert_eq!(
-        chat.read_with(cx, |chat, _| {
-            chat.messages()
-                .iter()
-                .map(|m| m.body.clone())
-                .collect::<Vec<_>>()
-        }),
-        messages
-    );
-}
-
-//todo!(collab_ui)
-// #[gpui::test]
-// async fn test_channel_message_changes(
-//     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;
-
-//     let channel_id = server
-//         .make_channel(
-//             "the-channel",
-//             None,
-//             (&client_a, cx_a),
-//             &mut [(&client_b, cx_b)],
-//         )
-//         .await;
-
-//     // Client A sends a message, client B should see that there is a new message.
-//     let channel_chat_a = client_a
-//         .channel_store()
-//         .update(cx_a, |store, cx| store.open_channel_chat(channel_id, cx))
-//         .await
-//         .unwrap();
-
-//     channel_chat_a
-//         .update(cx_a, |c, cx| c.send_message("one".into(), cx).unwrap())
-//         .await
-//         .unwrap();
-
-//     executor.run_until_parked();
-
-//     let b_has_messages = cx_b.read_with(|cx| {
-//         client_b
-//             .channel_store()
-//             .read(cx)
-//             .has_new_messages(channel_id)
-//             .unwrap()
-//     });
-
-//     assert!(b_has_messages);
-
-//     // Opening the chat should clear the changed flag.
-//     cx_b.update(|cx| {
-//         collab_ui::init(&client_b.app_state, cx);
-//     });
-//     let project_b = client_b.build_empty_local_project(cx_b);
-//     let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
-//     let chat_panel_b = workspace_b.update(cx_b, |workspace, cx| ChatPanel::new(workspace, cx));
-//     chat_panel_b
-//         .update(cx_b, |chat_panel, cx| {
-//             chat_panel.set_active(true, cx);
-//             chat_panel.select_channel(channel_id, None, cx)
-//         })
-//         .await
-//         .unwrap();
-
-//     executor.run_until_parked();
-
-//     let b_has_messages = cx_b.read_with(|cx| {
-//         client_b
-//             .channel_store()
-//             .read(cx)
-//             .has_new_messages(channel_id)
-//             .unwrap()
-//     });
-
-//     assert!(!b_has_messages);
-
-//     // Sending a message while the chat is open should not change the flag.
-//     channel_chat_a
-//         .update(cx_a, |c, cx| c.send_message("two".into(), cx).unwrap())
-//         .await
-//         .unwrap();
-
-//     executor.run_until_parked();
-
-//     let b_has_messages = cx_b.read_with(|cx| {
-//         client_b
-//             .channel_store()
-//             .read(cx)
-//             .has_new_messages(channel_id)
-//             .unwrap()
-//     });
-
-//     assert!(!b_has_messages);
-
-//     // Sending a message while the chat is closed should change the flag.
-//     chat_panel_b.update(cx_b, |chat_panel, cx| {
-//         chat_panel.set_active(false, cx);
-//     });
-
-//     // Sending a message while the chat is open should not change the flag.
-//     channel_chat_a
-//         .update(cx_a, |c, cx| c.send_message("three".into(), cx).unwrap())
-//         .await
-//         .unwrap();
-
-//     executor.run_until_parked();
-
-//     let b_has_messages = cx_b.read_with(|cx| {
-//         client_b
-//             .channel_store()
-//             .read(cx)
-//             .has_new_messages(channel_id)
-//             .unwrap()
-//     });
-
-//     assert!(b_has_messages);
-
-//     // Closing the chat should re-enable change tracking
-//     cx_b.update(|_| drop(chat_panel_b));
-
-//     channel_chat_a
-//         .update(cx_a, |c, cx| c.send_message("four".into(), cx).unwrap())
-//         .await
-//         .unwrap();
-
-//     executor.run_until_parked();
-
-//     let b_has_messages = cx_b.read_with(|cx| {
-//         client_b
-//             .channel_store()
-//             .read(cx)
-//             .has_new_messages(channel_id)
-//             .unwrap()
-//     });
-
-//     assert!(b_has_messages);
-// }

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

@@ -1,1543 +0,0 @@
-use crate::{
-    db::{self, UserId},
-    rpc::RECONNECT_TIMEOUT,
-    tests::{room_participants, RoomParticipants, TestServer},
-};
-use call::ActiveCall;
-use channel::{ChannelId, ChannelMembership, ChannelStore};
-use client::User;
-use futures::future::try_join_all;
-use gpui::{BackgroundExecutor, Model, SharedString, TestAppContext};
-use rpc::{
-    proto::{self, ChannelRole},
-    RECEIVE_TIMEOUT,
-};
-use std::sync::Arc;
-
-#[gpui::test]
-async fn test_core_channels(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-
-    let channel_a_id = client_a
-        .channel_store()
-        .update(cx_a, |channel_store, cx| {
-            channel_store.create_channel("channel-a", None, cx)
-        })
-        .await
-        .unwrap();
-    let channel_b_id = client_a
-        .channel_store()
-        .update(cx_a, |channel_store, cx| {
-            channel_store.create_channel("channel-b", Some(channel_a_id), cx)
-        })
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-    assert_channels(
-        client_a.channel_store(),
-        cx_a,
-        &[
-            ExpectedChannel {
-                id: channel_a_id,
-                name: "channel-a".into(),
-                depth: 0,
-                role: ChannelRole::Admin,
-            },
-            ExpectedChannel {
-                id: channel_b_id,
-                name: "channel-b".into(),
-                depth: 1,
-                role: ChannelRole::Admin,
-            },
-        ],
-    );
-
-    cx_b.read(|cx| {
-        client_b.channel_store().read_with(cx, |channels, _| {
-            assert!(channels.ordered_channels().collect::<Vec<_>>().is_empty())
-        })
-    });
-
-    // Invite client B to channel A as client A.
-    client_a
-        .channel_store()
-        .update(cx_a, |store, cx| {
-            assert!(!store.has_pending_channel_invite(channel_a_id, client_b.user_id().unwrap()));
-
-            let invite = store.invite_member(
-                channel_a_id,
-                client_b.user_id().unwrap(),
-                proto::ChannelRole::Member,
-                cx,
-            );
-
-            // Make sure we're synchronously storing the pending invite
-            assert!(store.has_pending_channel_invite(channel_a_id, client_b.user_id().unwrap()));
-            invite
-        })
-        .await
-        .unwrap();
-
-    // Client A sees that B has been invited.
-    executor.run_until_parked();
-    assert_channel_invitations(
-        client_b.channel_store(),
-        cx_b,
-        &[ExpectedChannel {
-            id: channel_a_id,
-            name: "channel-a".into(),
-            depth: 0,
-            role: ChannelRole::Member,
-        }],
-    );
-
-    let members = client_a
-        .channel_store()
-        .update(cx_a, |store, cx| {
-            assert!(!store.has_pending_channel_invite(channel_a_id, client_b.user_id().unwrap()));
-            store.get_channel_member_details(channel_a_id, cx)
-        })
-        .await
-        .unwrap();
-    assert_members_eq(
-        &members,
-        &[
-            (
-                client_a.user_id().unwrap(),
-                proto::ChannelRole::Admin,
-                proto::channel_member::Kind::Member,
-            ),
-            (
-                client_b.user_id().unwrap(),
-                proto::ChannelRole::Member,
-                proto::channel_member::Kind::Invitee,
-            ),
-        ],
-    );
-
-    // Client B accepts the invitation.
-    client_b
-        .channel_store()
-        .update(cx_b, |channels, cx| {
-            channels.respond_to_channel_invite(channel_a_id, true, cx)
-        })
-        .await
-        .unwrap();
-    executor.run_until_parked();
-
-    // Client B now sees that they are a member of channel A and its existing subchannels.
-    assert_channel_invitations(client_b.channel_store(), cx_b, &[]);
-    assert_channels(
-        client_b.channel_store(),
-        cx_b,
-        &[
-            ExpectedChannel {
-                id: channel_a_id,
-                name: "channel-a".into(),
-                role: ChannelRole::Member,
-                depth: 0,
-            },
-            ExpectedChannel {
-                id: channel_b_id,
-                name: "channel-b".into(),
-                role: ChannelRole::Member,
-                depth: 1,
-            },
-        ],
-    );
-
-    let channel_c_id = client_a
-        .channel_store()
-        .update(cx_a, |channel_store, cx| {
-            channel_store.create_channel("channel-c", Some(channel_b_id), cx)
-        })
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-    assert_channels(
-        client_b.channel_store(),
-        cx_b,
-        &[
-            ExpectedChannel {
-                id: channel_a_id,
-                name: "channel-a".into(),
-                role: ChannelRole::Member,
-                depth: 0,
-            },
-            ExpectedChannel {
-                id: channel_b_id,
-                name: "channel-b".into(),
-                role: ChannelRole::Member,
-                depth: 1,
-            },
-            ExpectedChannel {
-                id: channel_c_id,
-                name: "channel-c".into(),
-                role: ChannelRole::Member,
-                depth: 2,
-            },
-        ],
-    );
-
-    // Update client B's membership to channel A to be an admin.
-    client_a
-        .channel_store()
-        .update(cx_a, |store, cx| {
-            store.set_member_role(
-                channel_a_id,
-                client_b.user_id().unwrap(),
-                proto::ChannelRole::Admin,
-                cx,
-            )
-        })
-        .await
-        .unwrap();
-    executor.run_until_parked();
-
-    // Observe that client B is now an admin of channel A, and that
-    // their admin priveleges extend to subchannels of channel A.
-    assert_channel_invitations(client_b.channel_store(), cx_b, &[]);
-    assert_channels(
-        client_b.channel_store(),
-        cx_b,
-        &[
-            ExpectedChannel {
-                id: channel_a_id,
-                name: "channel-a".into(),
-                depth: 0,
-                role: ChannelRole::Admin,
-            },
-            ExpectedChannel {
-                id: channel_b_id,
-                name: "channel-b".into(),
-                depth: 1,
-                role: ChannelRole::Admin,
-            },
-            ExpectedChannel {
-                id: channel_c_id,
-                name: "channel-c".into(),
-                depth: 2,
-                role: ChannelRole::Admin,
-            },
-        ],
-    );
-
-    // Client A deletes the channel, deletion also deletes subchannels.
-    client_a
-        .channel_store()
-        .update(cx_a, |channel_store, _| {
-            channel_store.remove_channel(channel_b_id)
-        })
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-    assert_channels(
-        client_a.channel_store(),
-        cx_a,
-        &[ExpectedChannel {
-            id: channel_a_id,
-            name: "channel-a".into(),
-            depth: 0,
-            role: ChannelRole::Admin,
-        }],
-    );
-    assert_channels(
-        client_b.channel_store(),
-        cx_b,
-        &[ExpectedChannel {
-            id: channel_a_id,
-            name: "channel-a".into(),
-            depth: 0,
-            role: ChannelRole::Admin,
-        }],
-    );
-
-    // Remove client B
-    client_a
-        .channel_store()
-        .update(cx_a, |channel_store, cx| {
-            channel_store.remove_member(channel_a_id, client_b.user_id().unwrap(), cx)
-        })
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-
-    // Client A still has their channel
-    assert_channels(
-        client_a.channel_store(),
-        cx_a,
-        &[ExpectedChannel {
-            id: channel_a_id,
-            name: "channel-a".into(),
-            depth: 0,
-            role: ChannelRole::Admin,
-        }],
-    );
-
-    // Client B no longer has access to the channel
-    assert_channels(client_b.channel_store(), cx_b, &[]);
-
-    server.forbid_connections();
-    server.disconnect_client(client_a.peer_id().unwrap());
-    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
-
-    server
-        .app_state
-        .db
-        .rename_channel(
-            db::ChannelId::from_proto(channel_a_id),
-            UserId::from_proto(client_a.id()),
-            "channel-a-renamed",
-        )
-        .await
-        .unwrap();
-
-    server.allow_connections();
-    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
-    assert_channels(
-        client_a.channel_store(),
-        cx_a,
-        &[ExpectedChannel {
-            id: channel_a_id,
-            name: "channel-a-renamed".into(),
-            depth: 0,
-            role: ChannelRole::Admin,
-        }],
-    );
-}
-
-#[track_caller]
-fn assert_participants_eq(participants: &[Arc<User>], expected_partitipants: &[u64]) {
-    assert_eq!(
-        participants.iter().map(|p| p.id).collect::<Vec<_>>(),
-        expected_partitipants
-    );
-}
-
-#[track_caller]
-fn assert_members_eq(
-    members: &[ChannelMembership],
-    expected_members: &[(u64, proto::ChannelRole, proto::channel_member::Kind)],
-) {
-    assert_eq!(
-        members
-            .iter()
-            .map(|member| (member.user.id, member.role, member.kind))
-            .collect::<Vec<_>>(),
-        expected_members
-    );
-}
-
-#[gpui::test]
-async fn test_joining_channel_ancestor_member(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-
-    let parent_id = server
-        .make_channel("parent", None, (&client_a, cx_a), &mut [(&client_b, cx_b)])
-        .await;
-
-    let sub_id = client_a
-        .channel_store()
-        .update(cx_a, |channel_store, cx| {
-            channel_store.create_channel("sub_channel", Some(parent_id), cx)
-        })
-        .await
-        .unwrap();
-
-    let active_call_b = cx_b.read(ActiveCall::global);
-
-    assert!(active_call_b
-        .update(cx_b, |active_call, cx| active_call.join_channel(sub_id, cx))
-        .await
-        .is_ok());
-}
-
-#[gpui::test]
-async fn test_channel_room(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-    cx_c: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    let client_c = server.create_client(cx_c, "user_c").await;
-
-    let zed_id = server
-        .make_channel(
-            "zed",
-            None,
-            (&client_a, cx_a),
-            &mut [(&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);
-
-    active_call_a
-        .update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx))
-        .await
-        .unwrap();
-
-    // Give everyone a chance to observe user A joining
-    executor.run_until_parked();
-    let room_a =
-        cx_a.read(|cx| active_call_a.read_with(cx, |call, _| call.room().unwrap().clone()));
-    cx_a.read(|cx| room_a.read_with(cx, |room, _| assert!(room.is_connected())));
-
-    cx_a.read(|cx| {
-        client_a.channel_store().read_with(cx, |channels, _| {
-            assert_participants_eq(
-                channels.channel_participants(zed_id),
-                &[client_a.user_id().unwrap()],
-            );
-        })
-    });
-
-    assert_channels(
-        client_b.channel_store(),
-        cx_b,
-        &[ExpectedChannel {
-            id: zed_id,
-            name: "zed".into(),
-            depth: 0,
-            role: ChannelRole::Member,
-        }],
-    );
-    cx_b.read(|cx| {
-        client_b.channel_store().read_with(cx, |channels, _| {
-            assert_participants_eq(
-                channels.channel_participants(zed_id),
-                &[client_a.user_id().unwrap()],
-            );
-        })
-    });
-
-    cx_c.read(|cx| {
-        client_c.channel_store().read_with(cx, |channels, _| {
-            assert_participants_eq(
-                channels.channel_participants(zed_id),
-                &[client_a.user_id().unwrap()],
-            );
-        })
-    });
-
-    active_call_b
-        .update(cx_b, |active_call, cx| active_call.join_channel(zed_id, cx))
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-
-    cx_a.read(|cx| {
-        client_a.channel_store().read_with(cx, |channels, _| {
-            assert_participants_eq(
-                channels.channel_participants(zed_id),
-                &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
-            );
-        })
-    });
-
-    cx_b.read(|cx| {
-        client_b.channel_store().read_with(cx, |channels, _| {
-            assert_participants_eq(
-                channels.channel_participants(zed_id),
-                &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
-            );
-        })
-    });
-
-    cx_c.read(|cx| {
-        client_c.channel_store().read_with(cx, |channels, _| {
-            assert_participants_eq(
-                channels.channel_participants(zed_id),
-                &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
-            );
-        })
-    });
-
-    let room_a =
-        cx_a.read(|cx| active_call_a.read_with(cx, |call, _| call.room().unwrap().clone()));
-    cx_a.read(|cx| room_a.read_with(cx, |room, _| assert!(room.is_connected())));
-    assert_eq!(
-        room_participants(&room_a, cx_a),
-        RoomParticipants {
-            remote: vec!["user_b".to_string()],
-            pending: vec![]
-        }
-    );
-
-    let room_b =
-        cx_b.read(|cx| active_call_b.read_with(cx, |call, _| call.room().unwrap().clone()));
-    cx_b.read(|cx| room_b.read_with(cx, |room, _| assert!(room.is_connected())));
-    assert_eq!(
-        room_participants(&room_b, cx_b),
-        RoomParticipants {
-            remote: vec!["user_a".to_string()],
-            pending: vec![]
-        }
-    );
-
-    // Make sure that leaving and rejoining works
-
-    active_call_a
-        .update(cx_a, |active_call, cx| active_call.hang_up(cx))
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-
-    cx_a.read(|cx| {
-        client_a.channel_store().read_with(cx, |channels, _| {
-            assert_participants_eq(
-                channels.channel_participants(zed_id),
-                &[client_b.user_id().unwrap()],
-            );
-        })
-    });
-
-    cx_b.read(|cx| {
-        client_b.channel_store().read_with(cx, |channels, _| {
-            assert_participants_eq(
-                channels.channel_participants(zed_id),
-                &[client_b.user_id().unwrap()],
-            );
-        })
-    });
-
-    cx_c.read(|cx| {
-        client_c.channel_store().read_with(cx, |channels, _| {
-            assert_participants_eq(
-                channels.channel_participants(zed_id),
-                &[client_b.user_id().unwrap()],
-            );
-        })
-    });
-
-    active_call_b
-        .update(cx_b, |active_call, cx| active_call.hang_up(cx))
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-
-    cx_a.read(|cx| {
-        client_a.channel_store().read_with(cx, |channels, _| {
-            assert_participants_eq(channels.channel_participants(zed_id), &[]);
-        })
-    });
-
-    cx_b.read(|cx| {
-        client_b.channel_store().read_with(cx, |channels, _| {
-            assert_participants_eq(channels.channel_participants(zed_id), &[]);
-        })
-    });
-
-    cx_c.read(|cx| {
-        client_c.channel_store().read_with(cx, |channels, _| {
-            assert_participants_eq(channels.channel_participants(zed_id), &[]);
-        })
-    });
-
-    active_call_a
-        .update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx))
-        .await
-        .unwrap();
-
-    active_call_b
-        .update(cx_b, |active_call, cx| active_call.join_channel(zed_id, cx))
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-
-    let room_a =
-        cx_a.read(|cx| active_call_a.read_with(cx, |call, _| call.room().unwrap().clone()));
-    cx_a.read(|cx| room_a.read_with(cx, |room, _| assert!(room.is_connected())));
-    assert_eq!(
-        room_participants(&room_a, cx_a),
-        RoomParticipants {
-            remote: vec!["user_b".to_string()],
-            pending: vec![]
-        }
-    );
-
-    let room_b =
-        cx_b.read(|cx| active_call_b.read_with(cx, |call, _| call.room().unwrap().clone()));
-    cx_b.read(|cx| room_b.read_with(cx, |room, _| assert!(room.is_connected())));
-    assert_eq!(
-        room_participants(&room_b, cx_b),
-        RoomParticipants {
-            remote: vec!["user_a".to_string()],
-            pending: vec![]
-        }
-    );
-}
-
-#[gpui::test]
-async fn test_channel_jumping(executor: BackgroundExecutor, cx_a: &mut TestAppContext) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-
-    let zed_id = server
-        .make_channel("zed", None, (&client_a, cx_a), &mut [])
-        .await;
-    let rust_id = server
-        .make_channel("rust", None, (&client_a, cx_a), &mut [])
-        .await;
-
-    let active_call_a = cx_a.read(ActiveCall::global);
-
-    active_call_a
-        .update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx))
-        .await
-        .unwrap();
-
-    // Give everything a chance to observe user A joining
-    executor.run_until_parked();
-
-    cx_a.read(|cx| {
-        client_a.channel_store().read_with(cx, |channels, _| {
-            assert_participants_eq(
-                channels.channel_participants(zed_id),
-                &[client_a.user_id().unwrap()],
-            );
-            assert_participants_eq(channels.channel_participants(rust_id), &[]);
-        })
-    });
-
-    active_call_a
-        .update(cx_a, |active_call, cx| {
-            active_call.join_channel(rust_id, cx)
-        })
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-
-    cx_a.read(|cx| {
-        client_a.channel_store().read_with(cx, |channels, _| {
-            assert_participants_eq(channels.channel_participants(zed_id), &[]);
-            assert_participants_eq(
-                channels.channel_participants(rust_id),
-                &[client_a.user_id().unwrap()],
-            );
-        })
-    });
-}
-
-#[gpui::test]
-async fn test_permissions_update_while_invited(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-
-    let rust_id = server
-        .make_channel("rust", None, (&client_a, cx_a), &mut [])
-        .await;
-
-    client_a
-        .channel_store()
-        .update(cx_a, |channel_store, cx| {
-            channel_store.invite_member(
-                rust_id,
-                client_b.user_id().unwrap(),
-                proto::ChannelRole::Member,
-                cx,
-            )
-        })
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-
-    assert_channel_invitations(
-        client_b.channel_store(),
-        cx_b,
-        &[ExpectedChannel {
-            depth: 0,
-            id: rust_id,
-            name: "rust".into(),
-            role: ChannelRole::Member,
-        }],
-    );
-    assert_channels(client_b.channel_store(), cx_b, &[]);
-
-    // Update B's invite before they've accepted it
-    client_a
-        .channel_store()
-        .update(cx_a, |channel_store, cx| {
-            channel_store.set_member_role(
-                rust_id,
-                client_b.user_id().unwrap(),
-                proto::ChannelRole::Admin,
-                cx,
-            )
-        })
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-
-    assert_channel_invitations(
-        client_b.channel_store(),
-        cx_b,
-        &[ExpectedChannel {
-            depth: 0,
-            id: rust_id,
-            name: "rust".into(),
-            role: ChannelRole::Member,
-        }],
-    );
-    assert_channels(client_b.channel_store(), cx_b, &[]);
-}
-
-#[gpui::test]
-async fn test_channel_rename(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-
-    let rust_id = server
-        .make_channel("rust", None, (&client_a, cx_a), &mut [(&client_b, cx_b)])
-        .await;
-
-    // Rename the channel
-    client_a
-        .channel_store()
-        .update(cx_a, |channel_store, cx| {
-            channel_store.rename(rust_id, "#rust-archive", cx)
-        })
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-
-    // Client A sees the channel with its new name.
-    assert_channels(
-        client_a.channel_store(),
-        cx_a,
-        &[ExpectedChannel {
-            depth: 0,
-            id: rust_id,
-            name: "rust-archive".into(),
-            role: ChannelRole::Admin,
-        }],
-    );
-
-    // Client B sees the channel with its new name.
-    assert_channels(
-        client_b.channel_store(),
-        cx_b,
-        &[ExpectedChannel {
-            depth: 0,
-            id: rust_id,
-            name: "rust-archive".into(),
-            role: ChannelRole::Member,
-        }],
-    );
-}
-
-#[gpui::test]
-async fn test_call_from_channel(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-    cx_c: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    let client_c = server.create_client(cx_c, "user_c").await;
-    server
-        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-        .await;
-
-    let channel_id = server
-        .make_channel(
-            "x",
-            None,
-            (&client_a, cx_a),
-            &mut [(&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);
-
-    active_call_a
-        .update(cx_a, |call, cx| call.join_channel(channel_id, cx))
-        .await
-        .unwrap();
-
-    // Client A calls client B while in the channel.
-    active_call_a
-        .update(cx_a, |call, cx| {
-            call.invite(client_b.user_id().unwrap(), None, cx)
-        })
-        .await
-        .unwrap();
-
-    // Client B accepts the call.
-    executor.run_until_parked();
-    active_call_b
-        .update(cx_b, |call, cx| call.accept_incoming(cx))
-        .await
-        .unwrap();
-
-    // Client B sees that they are now in the channel
-    executor.run_until_parked();
-    cx_b.read(|cx| {
-        active_call_b.read_with(cx, |call, cx| {
-            assert_eq!(call.channel_id(cx), Some(channel_id));
-        })
-    });
-    cx_b.read(|cx| {
-        client_b.channel_store().read_with(cx, |channels, _| {
-            assert_participants_eq(
-                channels.channel_participants(channel_id),
-                &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
-            );
-        })
-    });
-
-    // Clients A and C also see that client B is in the channel.
-    cx_a.read(|cx| {
-        client_a.channel_store().read_with(cx, |channels, _| {
-            assert_participants_eq(
-                channels.channel_participants(channel_id),
-                &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
-            );
-        })
-    });
-    cx_c.read(|cx| {
-        client_c.channel_store().read_with(cx, |channels, _| {
-            assert_participants_eq(
-                channels.channel_participants(channel_id),
-                &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
-            );
-        })
-    });
-}
-
-#[gpui::test]
-async fn test_lost_channel_creation(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-
-    server
-        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-        .await;
-
-    let channel_id = server
-        .make_channel("x", None, (&client_a, cx_a), &mut [])
-        .await;
-
-    // Invite a member
-    client_a
-        .channel_store()
-        .update(cx_a, |channel_store, cx| {
-            channel_store.invite_member(
-                channel_id,
-                client_b.user_id().unwrap(),
-                proto::ChannelRole::Member,
-                cx,
-            )
-        })
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-
-    // Sanity check, B has the invitation
-    assert_channel_invitations(
-        client_b.channel_store(),
-        cx_b,
-        &[ExpectedChannel {
-            depth: 0,
-            id: channel_id,
-            name: "x".into(),
-            role: ChannelRole::Member,
-        }],
-    );
-
-    // A creates a subchannel while the invite is still pending.
-    let subchannel_id = client_a
-        .channel_store()
-        .update(cx_a, |channel_store, cx| {
-            channel_store.create_channel("subchannel", Some(channel_id), cx)
-        })
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-
-    // Make sure A sees their new channel
-    assert_channels(
-        client_a.channel_store(),
-        cx_a,
-        &[
-            ExpectedChannel {
-                depth: 0,
-                id: channel_id,
-                name: "x".into(),
-                role: ChannelRole::Admin,
-            },
-            ExpectedChannel {
-                depth: 1,
-                id: subchannel_id,
-                name: "subchannel".into(),
-                role: ChannelRole::Admin,
-            },
-        ],
-    );
-
-    // Client B accepts the invite
-    client_b
-        .channel_store()
-        .update(cx_b, |channel_store, cx| {
-            channel_store.respond_to_channel_invite(channel_id, true, cx)
-        })
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-
-    // Client B should now see the channel
-    assert_channels(
-        client_b.channel_store(),
-        cx_b,
-        &[
-            ExpectedChannel {
-                depth: 0,
-                id: channel_id,
-                name: "x".into(),
-                role: ChannelRole::Member,
-            },
-            ExpectedChannel {
-                depth: 1,
-                id: subchannel_id,
-                name: "subchannel".into(),
-                role: ChannelRole::Member,
-            },
-        ],
-    );
-}
-
-#[gpui::test]
-async fn test_channel_link_notifications(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-    cx_c: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    let client_c = server.create_client(cx_c, "user_c").await;
-
-    let user_b = client_b.user_id().unwrap();
-    let user_c = client_c.user_id().unwrap();
-
-    let channels = server
-        .make_channel_tree(&[("zed", None)], (&client_a, cx_a))
-        .await;
-    let zed_channel = channels[0];
-
-    try_join_all(client_a.channel_store().update(cx_a, |channel_store, cx| {
-        [
-            channel_store.set_channel_visibility(zed_channel, proto::ChannelVisibility::Public, cx),
-            channel_store.invite_member(zed_channel, user_b, proto::ChannelRole::Member, cx),
-            channel_store.invite_member(zed_channel, user_c, proto::ChannelRole::Guest, cx),
-        ]
-    }))
-    .await
-    .unwrap();
-
-    executor.run_until_parked();
-
-    client_b
-        .channel_store()
-        .update(cx_b, |channel_store, cx| {
-            channel_store.respond_to_channel_invite(zed_channel, true, cx)
-        })
-        .await
-        .unwrap();
-
-    client_c
-        .channel_store()
-        .update(cx_c, |channel_store, cx| {
-            channel_store.respond_to_channel_invite(zed_channel, true, cx)
-        })
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-
-    // we have an admin (a), member (b) and guest (c) all part of the zed channel.
-
-    // create a new private channel, make it public, and move it under the previous one, and verify it shows for b and not c
-    let active_channel = client_a
-        .channel_store()
-        .update(cx_a, |channel_store, cx| {
-            channel_store.create_channel("active", Some(zed_channel), cx)
-        })
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-
-    // the new channel shows for b and not c
-    assert_channels_list_shape(
-        client_a.channel_store(),
-        cx_a,
-        &[(zed_channel, 0), (active_channel, 1)],
-    );
-    assert_channels_list_shape(
-        client_b.channel_store(),
-        cx_b,
-        &[(zed_channel, 0), (active_channel, 1)],
-    );
-    assert_channels_list_shape(client_c.channel_store(), cx_c, &[(zed_channel, 0)]);
-
-    let vim_channel = client_a
-        .channel_store()
-        .update(cx_a, |channel_store, cx| {
-            channel_store.create_channel("vim", None, cx)
-        })
-        .await
-        .unwrap();
-
-    client_a
-        .channel_store()
-        .update(cx_a, |channel_store, cx| {
-            channel_store.set_channel_visibility(vim_channel, proto::ChannelVisibility::Public, cx)
-        })
-        .await
-        .unwrap();
-
-    client_a
-        .channel_store()
-        .update(cx_a, |channel_store, cx| {
-            channel_store.move_channel(vim_channel, Some(active_channel), cx)
-        })
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-
-    // the new channel shows for b and c
-    assert_channels_list_shape(
-        client_a.channel_store(),
-        cx_a,
-        &[(zed_channel, 0), (active_channel, 1), (vim_channel, 2)],
-    );
-    assert_channels_list_shape(
-        client_b.channel_store(),
-        cx_b,
-        &[(zed_channel, 0), (active_channel, 1), (vim_channel, 2)],
-    );
-    assert_channels_list_shape(
-        client_c.channel_store(),
-        cx_c,
-        &[(zed_channel, 0), (vim_channel, 1)],
-    );
-
-    let helix_channel = client_a
-        .channel_store()
-        .update(cx_a, |channel_store, cx| {
-            channel_store.create_channel("helix", None, cx)
-        })
-        .await
-        .unwrap();
-
-    client_a
-        .channel_store()
-        .update(cx_a, |channel_store, cx| {
-            channel_store.move_channel(helix_channel, Some(vim_channel), cx)
-        })
-        .await
-        .unwrap();
-
-    client_a
-        .channel_store()
-        .update(cx_a, |channel_store, cx| {
-            channel_store.set_channel_visibility(
-                helix_channel,
-                proto::ChannelVisibility::Public,
-                cx,
-            )
-        })
-        .await
-        .unwrap();
-
-    // the new channel shows for b and c
-    assert_channels_list_shape(
-        client_b.channel_store(),
-        cx_b,
-        &[
-            (zed_channel, 0),
-            (active_channel, 1),
-            (vim_channel, 2),
-            (helix_channel, 3),
-        ],
-    );
-    assert_channels_list_shape(
-        client_c.channel_store(),
-        cx_c,
-        &[(zed_channel, 0), (vim_channel, 1), (helix_channel, 2)],
-    );
-
-    client_a
-        .channel_store()
-        .update(cx_a, |channel_store, cx| {
-            channel_store.set_channel_visibility(vim_channel, proto::ChannelVisibility::Members, cx)
-        })
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-
-    // the members-only channel is still shown for c, but hidden for b
-    assert_channels_list_shape(
-        client_b.channel_store(),
-        cx_b,
-        &[
-            (zed_channel, 0),
-            (active_channel, 1),
-            (vim_channel, 2),
-            (helix_channel, 3),
-        ],
-    );
-    cx_b.read(|cx| {
-        client_b.channel_store().read_with(cx, |channel_store, _| {
-            assert_eq!(
-                channel_store
-                    .channel_for_id(vim_channel)
-                    .unwrap()
-                    .visibility,
-                proto::ChannelVisibility::Members
-            )
-        })
-    });
-
-    assert_channels_list_shape(client_c.channel_store(), cx_c, &[(zed_channel, 0)]);
-}
-
-#[gpui::test]
-async fn test_channel_membership_notifications(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_c").await;
-
-    let user_b = client_b.user_id().unwrap();
-
-    let channels = server
-        .make_channel_tree(
-            &[
-                ("zed", None),
-                ("active", Some("zed")),
-                ("vim", Some("active")),
-            ],
-            (&client_a, cx_a),
-        )
-        .await;
-    let zed_channel = channels[0];
-    let _active_channel = channels[1];
-    let vim_channel = channels[2];
-
-    try_join_all(client_a.channel_store().update(cx_a, |channel_store, cx| {
-        [
-            channel_store.set_channel_visibility(zed_channel, proto::ChannelVisibility::Public, cx),
-            channel_store.set_channel_visibility(vim_channel, proto::ChannelVisibility::Public, cx),
-            channel_store.invite_member(vim_channel, user_b, proto::ChannelRole::Member, cx),
-            channel_store.invite_member(zed_channel, user_b, proto::ChannelRole::Guest, cx),
-        ]
-    }))
-    .await
-    .unwrap();
-
-    executor.run_until_parked();
-
-    client_b
-        .channel_store()
-        .update(cx_b, |channel_store, cx| {
-            channel_store.respond_to_channel_invite(zed_channel, true, cx)
-        })
-        .await
-        .unwrap();
-
-    client_b
-        .channel_store()
-        .update(cx_b, |channel_store, cx| {
-            channel_store.respond_to_channel_invite(vim_channel, true, cx)
-        })
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-
-    // we have an admin (a), and a guest (b) with access to all of zed, and membership in vim.
-    assert_channels(
-        client_b.channel_store(),
-        cx_b,
-        &[
-            ExpectedChannel {
-                depth: 0,
-                id: zed_channel,
-                name: "zed".into(),
-                role: ChannelRole::Guest,
-            },
-            ExpectedChannel {
-                depth: 1,
-                id: vim_channel,
-                name: "vim".into(),
-                role: ChannelRole::Member,
-            },
-        ],
-    );
-
-    client_a
-        .channel_store()
-        .update(cx_a, |channel_store, cx| {
-            channel_store.remove_member(vim_channel, user_b, cx)
-        })
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-
-    assert_channels(
-        client_b.channel_store(),
-        cx_b,
-        &[
-            ExpectedChannel {
-                depth: 0,
-                id: zed_channel,
-                name: "zed".into(),
-                role: ChannelRole::Guest,
-            },
-            ExpectedChannel {
-                depth: 1,
-                id: vim_channel,
-                name: "vim".into(),
-                role: ChannelRole::Guest,
-            },
-        ],
-    )
-}
-
-#[gpui::test]
-async fn test_guest_access(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-
-    let channels = server
-        .make_channel_tree(
-            &[("channel-a", None), ("channel-b", Some("channel-a"))],
-            (&client_a, cx_a),
-        )
-        .await;
-    let channel_a = channels[0];
-    let channel_b = channels[1];
-
-    let active_call_b = cx_b.read(ActiveCall::global);
-
-    // Non-members should not be allowed to join
-    assert!(active_call_b
-        .update(cx_b, |call, cx| call.join_channel(channel_a, cx))
-        .await
-        .is_err());
-
-    // Make channels A and B public
-    client_a
-        .channel_store()
-        .update(cx_a, |channel_store, cx| {
-            channel_store.set_channel_visibility(channel_a, proto::ChannelVisibility::Public, cx)
-        })
-        .await
-        .unwrap();
-    client_a
-        .channel_store()
-        .update(cx_a, |channel_store, cx| {
-            channel_store.set_channel_visibility(channel_b, proto::ChannelVisibility::Public, cx)
-        })
-        .await
-        .unwrap();
-
-    // Client B joins channel A as a guest
-    active_call_b
-        .update(cx_b, |call, cx| call.join_channel(channel_a, cx))
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-    assert_channels_list_shape(
-        client_a.channel_store(),
-        cx_a,
-        &[(channel_a, 0), (channel_b, 1)],
-    );
-    assert_channels_list_shape(
-        client_b.channel_store(),
-        cx_b,
-        &[(channel_a, 0), (channel_b, 1)],
-    );
-
-    client_a.channel_store().update(cx_a, |channel_store, _| {
-        let participants = channel_store.channel_participants(channel_a);
-        assert_eq!(participants.len(), 1);
-        assert_eq!(participants[0].id, client_b.user_id().unwrap());
-    });
-
-    client_a
-        .channel_store()
-        .update(cx_a, |channel_store, cx| {
-            channel_store.set_channel_visibility(channel_a, proto::ChannelVisibility::Members, cx)
-        })
-        .await
-        .unwrap();
-
-    assert_channels_list_shape(client_b.channel_store(), cx_b, &[]);
-
-    active_call_b
-        .update(cx_b, |call, cx| call.join_channel(channel_b, cx))
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-    assert_channels_list_shape(client_b.channel_store(), cx_b, &[(channel_b, 0)]);
-}
-
-#[gpui::test]
-async fn test_invite_access(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-
-    let channels = server
-        .make_channel_tree(
-            &[("channel-a", None), ("channel-b", Some("channel-a"))],
-            (&client_a, cx_a),
-        )
-        .await;
-    let channel_a_id = channels[0];
-    let channel_b_id = channels[0];
-
-    let active_call_b = cx_b.read(ActiveCall::global);
-
-    // should not be allowed to join
-    assert!(active_call_b
-        .update(cx_b, |call, cx| call.join_channel(channel_b_id, cx))
-        .await
-        .is_err());
-
-    client_a
-        .channel_store()
-        .update(cx_a, |channel_store, cx| {
-            channel_store.invite_member(
-                channel_a_id,
-                client_b.user_id().unwrap(),
-                ChannelRole::Member,
-                cx,
-            )
-        })
-        .await
-        .unwrap();
-
-    active_call_b
-        .update(cx_b, |call, cx| call.join_channel(channel_b_id, cx))
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-
-    client_b.channel_store().update(cx_b, |channel_store, _| {
-        assert!(channel_store.channel_for_id(channel_b_id).is_some());
-        assert!(channel_store.channel_for_id(channel_a_id).is_some());
-    });
-
-    client_a.channel_store().update(cx_a, |channel_store, _| {
-        let participants = channel_store.channel_participants(channel_b_id);
-        assert_eq!(participants.len(), 1);
-        assert_eq!(participants[0].id, client_b.user_id().unwrap());
-    })
-}
-
-#[gpui::test]
-async fn test_channel_moving(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    _cx_b: &mut TestAppContext,
-    _cx_c: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    // let client_b = server.create_client(cx_b, "user_b").await;
-    // let client_c = server.create_client(cx_c, "user_c").await;
-
-    let channels = server
-        .make_channel_tree(
-            &[
-                ("channel-a", None),
-                ("channel-b", Some("channel-a")),
-                ("channel-c", Some("channel-b")),
-                ("channel-d", Some("channel-c")),
-            ],
-            (&client_a, cx_a),
-        )
-        .await;
-    let channel_a_id = channels[0];
-    let channel_b_id = channels[1];
-    let channel_c_id = channels[2];
-    let channel_d_id = channels[3];
-
-    // Current shape:
-    // a - b - c - d
-    assert_channels_list_shape(
-        client_a.channel_store(),
-        cx_a,
-        &[
-            (channel_a_id, 0),
-            (channel_b_id, 1),
-            (channel_c_id, 2),
-            (channel_d_id, 3),
-        ],
-    );
-
-    client_a
-        .channel_store()
-        .update(cx_a, |channel_store, cx| {
-            channel_store.move_channel(channel_d_id, Some(channel_b_id), cx)
-        })
-        .await
-        .unwrap();
-
-    // Current shape:
-    //       /- d
-    // a - b -- c
-    assert_channels_list_shape(
-        client_a.channel_store(),
-        cx_a,
-        &[
-            (channel_a_id, 0),
-            (channel_b_id, 1),
-            (channel_c_id, 2),
-            (channel_d_id, 2),
-        ],
-    );
-}
-
-#[derive(Debug, PartialEq)]
-struct ExpectedChannel {
-    depth: usize,
-    id: ChannelId,
-    name: SharedString,
-    role: ChannelRole,
-}
-
-#[track_caller]
-fn assert_channel_invitations(
-    channel_store: &Model<ChannelStore>,
-    cx: &TestAppContext,
-    expected_channels: &[ExpectedChannel],
-) {
-    let actual = cx.read(|cx| {
-        channel_store.read_with(cx, |store, _| {
-            store
-                .channel_invitations()
-                .iter()
-                .map(|channel| ExpectedChannel {
-                    depth: 0,
-                    name: channel.name.clone(),
-                    id: channel.id,
-                    role: channel.role,
-                })
-                .collect::<Vec<_>>()
-        })
-    });
-    assert_eq!(actual, expected_channels);
-}
-
-#[track_caller]
-fn assert_channels(
-    channel_store: &Model<ChannelStore>,
-    cx: &TestAppContext,
-    expected_channels: &[ExpectedChannel],
-) {
-    let actual = cx.read(|cx| {
-        channel_store.read_with(cx, |store, _| {
-            store
-                .ordered_channels()
-                .map(|(depth, channel)| ExpectedChannel {
-                    depth,
-                    name: channel.name.clone().into(),
-                    id: channel.id,
-                    role: channel.role,
-                })
-                .collect::<Vec<_>>()
-        })
-    });
-    pretty_assertions::assert_eq!(actual, expected_channels);
-}
-
-#[track_caller]
-fn assert_channels_list_shape(
-    channel_store: &Model<ChannelStore>,
-    cx: &TestAppContext,
-    expected_channels: &[(u64, usize)],
-) {
-    let actual = cx.read(|cx| {
-        channel_store.read_with(cx, |store, _| {
-            store
-                .ordered_channels()
-                .map(|(depth, channel)| (channel.id, depth))
-                .collect::<Vec<_>>()
-        })
-    });
-    pretty_assertions::assert_eq!(actual, expected_channels);
-}

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

@@ -1,1890 +0,0 @@
-//todo!(workspace)
-
-// use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
-// use call::ActiveCall;
-// use collab_ui::notifications::project_shared_notification::ProjectSharedNotification;
-// use editor::{Editor, ExcerptRange, MultiBuffer};
-// use gpui::{point, BackgroundExecutor, TestAppContext, View, VisualTestContext, WindowContext};
-// use live_kit_client::MacOSDisplay;
-// use project::project_settings::ProjectSettings;
-// use rpc::proto::PeerId;
-// use serde_json::json;
-// use settings::SettingsStore;
-// use std::borrow::Cow;
-// use workspace::{
-//     dock::{test::TestPanel, DockPosition},
-//     item::{test::TestItem, ItemHandle as _},
-//     shared_screen::SharedScreen,
-//     SplitDirection, Workspace,
-// };
-
-// #[gpui::test(iterations = 10)]
-// async fn test_basic_following(
-//     executor: BackgroundExecutor,
-//     cx_a: &mut TestAppContext,
-//     cx_b: &mut TestAppContext,
-//     cx_c: &mut TestAppContext,
-//     cx_d: &mut TestAppContext,
-// ) {
-//     let mut server = TestServer::start(executor.clone()).await;
-//     let client_a = server.create_client(cx_a, "user_a").await;
-//     let client_b = server.create_client(cx_b, "user_b").await;
-//     let client_c = server.create_client(cx_c, "user_c").await;
-//     let client_d = server.create_client(cx_d, "user_d").await;
-//     server
-//         .create_room(&mut [
-//             (&client_a, cx_a),
-//             (&client_b, cx_b),
-//             (&client_c, cx_c),
-//             (&client_d, cx_d),
-//         ])
-//         .await;
-//     let active_call_a = cx_a.read(ActiveCall::global);
-//     let active_call_b = cx_b.read(ActiveCall::global);
-
-//     cx_a.update(editor::init);
-//     cx_b.update(editor::init);
-
-//     client_a
-//         .fs()
-//         .insert_tree(
-//             "/a",
-//             json!({
-//                 "1.txt": "one\none\none",
-//                 "2.txt": "two\ntwo\ntwo",
-//                 "3.txt": "three\nthree\nthree",
-//             }),
-//         )
-//         .await;
-//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
-//     active_call_a
-//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
-//         .await
-//         .unwrap();
-
-//     let project_id = active_call_a
-//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-//         .await
-//         .unwrap();
-//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
-//     active_call_b
-//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
-//         .await
-//         .unwrap();
-
-//     let window_a = client_a.build_workspace(&project_a, cx_a);
-//     let workspace_a = window_a.root(cx_a).unwrap();
-//     let window_b = client_b.build_workspace(&project_b, cx_b);
-//     let workspace_b = window_b.root(cx_b).unwrap();
-
-//     todo!("could be wrong")
-//     let mut cx_a = VisualTestContext::from_window(*window_a, cx_a);
-//     let cx_a = &mut cx_a;
-//     let mut cx_b = VisualTestContext::from_window(*window_b, cx_b);
-//     let cx_b = &mut cx_b;
-//     let mut cx_c = VisualTestContext::from_window(*window_c, cx_c);
-//     let cx_c = &mut cx_c;
-//     let mut cx_d = VisualTestContext::from_window(*window_d, cx_d);
-//     let cx_d = &mut cx_d;
-
-//     // Client A opens some editors.
-//     let pane_a = workspace_a.update(cx_a, |workspace, _| workspace.active_pane().clone());
-//     let editor_a1 = workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.open_path((worktree_id, "1.txt"), None, true, cx)
-//         })
-//         .await
-//         .unwrap()
-//         .downcast::<Editor>()
-//         .unwrap();
-//     let editor_a2 = workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.open_path((worktree_id, "2.txt"), None, true, cx)
-//         })
-//         .await
-//         .unwrap()
-//         .downcast::<Editor>()
-//         .unwrap();
-
-//     // Client B opens an editor.
-//     let editor_b1 = workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.open_path((worktree_id, "1.txt"), None, true, cx)
-//         })
-//         .await
-//         .unwrap()
-//         .downcast::<Editor>()
-//         .unwrap();
-
-//     let peer_id_a = client_a.peer_id().unwrap();
-//     let peer_id_b = client_b.peer_id().unwrap();
-//     let peer_id_c = client_c.peer_id().unwrap();
-//     let peer_id_d = client_d.peer_id().unwrap();
-
-//     // Client A updates their selections in those editors
-//     editor_a1.update(cx_a, |editor, cx| {
-//         editor.handle_input("a", cx);
-//         editor.handle_input("b", cx);
-//         editor.handle_input("c", cx);
-//         editor.select_left(&Default::default(), cx);
-//         assert_eq!(editor.selections.ranges(cx), vec![3..2]);
-//     });
-//     editor_a2.update(cx_a, |editor, cx| {
-//         editor.handle_input("d", cx);
-//         editor.handle_input("e", cx);
-//         editor.select_left(&Default::default(), cx);
-//         assert_eq!(editor.selections.ranges(cx), vec![2..1]);
-//     });
-
-//     // When client B starts following client A, all visible view states are replicated to client B.
-//     workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.follow(peer_id_a, cx).unwrap()
-//         })
-//         .await
-//         .unwrap();
-
-//     cx_c.executor().run_until_parked();
-//     let editor_b2 = workspace_b.update(cx_b, |workspace, cx| {
-//         workspace
-//             .active_item(cx)
-//             .unwrap()
-//             .downcast::<Editor>()
-//             .unwrap()
-//     });
-//     assert_eq!(
-//         cx_b.read(|cx| editor_b2.project_path(cx)),
-//         Some((worktree_id, "2.txt").into())
-//     );
-//     assert_eq!(
-//         editor_b2.update(cx_b, |editor, cx| editor.selections.ranges(cx)),
-//         vec![2..1]
-//     );
-//     assert_eq!(
-//         editor_b1.update(cx_b, |editor, cx| editor.selections.ranges(cx)),
-//         vec![3..2]
-//     );
-
-//     cx_c.executor().run_until_parked();
-//     let active_call_c = cx_c.read(ActiveCall::global);
-//     let project_c = client_c.build_remote_project(project_id, cx_c).await;
-//     let window_c = client_c.build_workspace(&project_c, cx_c);
-//     let workspace_c = window_c.root(cx_c).unwrap();
-//     active_call_c
-//         .update(cx_c, |call, cx| call.set_location(Some(&project_c), cx))
-//         .await
-//         .unwrap();
-//     drop(project_c);
-
-//     // Client C also follows client A.
-//     workspace_c
-//         .update(cx_c, |workspace, cx| {
-//             workspace.follow(peer_id_a, cx).unwrap()
-//         })
-//         .await
-//         .unwrap();
-
-//     cx_d.executor().run_until_parked();
-//     let active_call_d = cx_d.read(ActiveCall::global);
-//     let project_d = client_d.build_remote_project(project_id, cx_d).await;
-//     let workspace_d = client_d
-//         .build_workspace(&project_d, cx_d)
-//         .root(cx_d)
-//         .unwrap();
-//     active_call_d
-//         .update(cx_d, |call, cx| call.set_location(Some(&project_d), cx))
-//         .await
-//         .unwrap();
-//     drop(project_d);
-
-//     // All clients see that clients B and C are following client A.
-//     cx_c.executor().run_until_parked();
-//     for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
-//         assert_eq!(
-//             followers_by_leader(project_id, cx),
-//             &[(peer_id_a, vec![peer_id_b, peer_id_c])],
-//             "followers seen by {name}"
-//         );
-//     }
-
-//     // Client C unfollows client A.
-//     workspace_c.update(cx_c, |workspace, cx| {
-//         workspace.unfollow(&workspace.active_pane().clone(), cx);
-//     });
-
-//     // All clients see that clients B is following client A.
-//     cx_c.executor().run_until_parked();
-//     for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
-//         assert_eq!(
-//             followers_by_leader(project_id, cx),
-//             &[(peer_id_a, vec![peer_id_b])],
-//             "followers seen by {name}"
-//         );
-//     }
-
-//     // Client C re-follows client A.
-//     workspace_c
-//         .update(cx_c, |workspace, cx| {
-//             workspace.follow(peer_id_a, cx).unwrap()
-//         })
-//         .await
-//         .unwrap();
-
-//     // All clients see that clients B and C are following client A.
-//     cx_c.executor().run_until_parked();
-//     for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
-//         assert_eq!(
-//             followers_by_leader(project_id, cx),
-//             &[(peer_id_a, vec![peer_id_b, peer_id_c])],
-//             "followers seen by {name}"
-//         );
-//     }
-
-//     // Client D follows client B, then switches to following client C.
-//     workspace_d
-//         .update(cx_d, |workspace, cx| {
-//             workspace.follow(peer_id_b, cx).unwrap()
-//         })
-//         .await
-//         .unwrap();
-//     workspace_d
-//         .update(cx_d, |workspace, cx| {
-//             workspace.follow(peer_id_c, cx).unwrap()
-//         })
-//         .await
-//         .unwrap();
-
-//     // All clients see that D is following C
-//     cx_d.executor().run_until_parked();
-//     for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
-//         assert_eq!(
-//             followers_by_leader(project_id, cx),
-//             &[
-//                 (peer_id_a, vec![peer_id_b, peer_id_c]),
-//                 (peer_id_c, vec![peer_id_d])
-//             ],
-//             "followers seen by {name}"
-//         );
-//     }
-
-//     // Client C closes the project.
-//     window_c.remove(cx_c);
-//     cx_c.drop_last(workspace_c);
-
-//     // Clients A and B see that client B is following A, and client C is not present in the followers.
-//     cx_c.executor().run_until_parked();
-//     for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
-//         assert_eq!(
-//             followers_by_leader(project_id, cx),
-//             &[(peer_id_a, vec![peer_id_b]),],
-//             "followers seen by {name}"
-//         );
-//     }
-
-//     // When client A activates a different editor, client B does so as well.
-//     workspace_a.update(cx_a, |workspace, cx| {
-//         workspace.activate_item(&editor_a1, cx)
-//     });
-//     executor.run_until_parked();
-//     workspace_b.update(cx_b, |workspace, cx| {
-//         assert_eq!(
-//             workspace.active_item(cx).unwrap().item_id(),
-//             editor_b1.item_id()
-//         );
-//     });
-
-//     // When client A opens a multibuffer, client B does so as well.
-//     let multibuffer_a = cx_a.build_model(|cx| {
-//         let buffer_a1 = project_a.update(cx, |project, cx| {
-//             project
-//                 .get_open_buffer(&(worktree_id, "1.txt").into(), cx)
-//                 .unwrap()
-//         });
-//         let buffer_a2 = project_a.update(cx, |project, cx| {
-//             project
-//                 .get_open_buffer(&(worktree_id, "2.txt").into(), cx)
-//                 .unwrap()
-//         });
-//         let mut result = MultiBuffer::new(0);
-//         result.push_excerpts(
-//             buffer_a1,
-//             [ExcerptRange {
-//                 context: 0..3,
-//                 primary: None,
-//             }],
-//             cx,
-//         );
-//         result.push_excerpts(
-//             buffer_a2,
-//             [ExcerptRange {
-//                 context: 4..7,
-//                 primary: None,
-//             }],
-//             cx,
-//         );
-//         result
-//     });
-//     let multibuffer_editor_a = workspace_a.update(cx_a, |workspace, cx| {
-//         let editor =
-//             cx.build_view(|cx| Editor::for_multibuffer(multibuffer_a, Some(project_a.clone()), cx));
-//         workspace.add_item(Box::new(editor.clone()), cx);
-//         editor
-//     });
-//     executor.run_until_parked();
-//     let multibuffer_editor_b = workspace_b.update(cx_b, |workspace, cx| {
-//         workspace
-//             .active_item(cx)
-//             .unwrap()
-//             .downcast::<Editor>()
-//             .unwrap()
-//     });
-//     assert_eq!(
-//         multibuffer_editor_a.update(cx_a, |editor, cx| editor.text(cx)),
-//         multibuffer_editor_b.update(cx_b, |editor, cx| editor.text(cx)),
-//     );
-
-//     // When client A navigates back and forth, client B does so as well.
-//     workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.go_back(workspace.active_pane().downgrade(), cx)
-//         })
-//         .await
-//         .unwrap();
-//     executor.run_until_parked();
-//     workspace_b.update(cx_b, |workspace, cx| {
-//         assert_eq!(
-//             workspace.active_item(cx).unwrap().item_id(),
-//             editor_b1.item_id()
-//         );
-//     });
-
-//     workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.go_back(workspace.active_pane().downgrade(), cx)
-//         })
-//         .await
-//         .unwrap();
-//     executor.run_until_parked();
-//     workspace_b.update(cx_b, |workspace, cx| {
-//         assert_eq!(
-//             workspace.active_item(cx).unwrap().item_id(),
-//             editor_b2.item_id()
-//         );
-//     });
-
-//     workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.go_forward(workspace.active_pane().downgrade(), cx)
-//         })
-//         .await
-//         .unwrap();
-//     executor.run_until_parked();
-//     workspace_b.update(cx_b, |workspace, cx| {
-//         assert_eq!(
-//             workspace.active_item(cx).unwrap().item_id(),
-//             editor_b1.item_id()
-//         );
-//     });
-
-//     // Changes to client A's editor are reflected on client B.
-//     editor_a1.update(cx_a, |editor, cx| {
-//         editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2]));
-//     });
-//     executor.run_until_parked();
-//     editor_b1.update(cx_b, |editor, cx| {
-//         assert_eq!(editor.selections.ranges(cx), &[1..1, 2..2]);
-//     });
-
-//     editor_a1.update(cx_a, |editor, cx| editor.set_text("TWO", cx));
-//     executor.run_until_parked();
-//     editor_b1.update(cx_b, |editor, cx| assert_eq!(editor.text(cx), "TWO"));
-
-//     editor_a1.update(cx_a, |editor, cx| {
-//         editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
-//         editor.set_scroll_position(point(0., 100.), cx);
-//     });
-//     executor.run_until_parked();
-//     editor_b1.update(cx_b, |editor, cx| {
-//         assert_eq!(editor.selections.ranges(cx), &[3..3]);
-//     });
-
-//     // After unfollowing, client B stops receiving updates from client A.
-//     workspace_b.update(cx_b, |workspace, cx| {
-//         workspace.unfollow(&workspace.active_pane().clone(), cx)
-//     });
-//     workspace_a.update(cx_a, |workspace, cx| {
-//         workspace.activate_item(&editor_a2, cx)
-//     });
-//     executor.run_until_parked();
-//     assert_eq!(
-//         workspace_b.update(cx_b, |workspace, cx| workspace
-//             .active_item(cx)
-//             .unwrap()
-//             .item_id()),
-//         editor_b1.item_id()
-//     );
-
-//     // Client A starts following client B.
-//     workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.follow(peer_id_b, cx).unwrap()
-//         })
-//         .await
-//         .unwrap();
-//     assert_eq!(
-//         workspace_a.update(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
-//         Some(peer_id_b)
-//     );
-//     assert_eq!(
-//         workspace_a.update(cx_a, |workspace, cx| workspace
-//             .active_item(cx)
-//             .unwrap()
-//             .item_id()),
-//         editor_a1.item_id()
-//     );
-
-//     // Client B activates an external window, which causes a new screen-sharing item to be added to the pane.
-//     let display = MacOSDisplay::new();
-//     active_call_b
-//         .update(cx_b, |call, cx| call.set_location(None, cx))
-//         .await
-//         .unwrap();
-//     active_call_b
-//         .update(cx_b, |call, cx| {
-//             call.room().unwrap().update(cx, |room, cx| {
-//                 room.set_display_sources(vec![display.clone()]);
-//                 room.share_screen(cx)
-//             })
-//         })
-//         .await
-//         .unwrap();
-//     executor.run_until_parked();
-//     let shared_screen = workspace_a.update(cx_a, |workspace, cx| {
-//         workspace
-//             .active_item(cx)
-//             .expect("no active item")
-//             .downcast::<SharedScreen>()
-//             .expect("active item isn't a shared screen")
-//     });
-
-//     // Client B activates Zed again, which causes the previous editor to become focused again.
-//     active_call_b
-//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
-//         .await
-//         .unwrap();
-//     executor.run_until_parked();
-//     workspace_a.update(cx_a, |workspace, cx| {
-//         assert_eq!(
-//             workspace.active_item(cx).unwrap().item_id(),
-//             editor_a1.item_id()
-//         )
-//     });
-
-//     // Client B activates a multibuffer that was created by following client A. Client A returns to that multibuffer.
-//     workspace_b.update(cx_b, |workspace, cx| {
-//         workspace.activate_item(&multibuffer_editor_b, cx)
-//     });
-//     executor.run_until_parked();
-//     workspace_a.update(cx_a, |workspace, cx| {
-//         assert_eq!(
-//             workspace.active_item(cx).unwrap().item_id(),
-//             multibuffer_editor_a.item_id()
-//         )
-//     });
-
-//     // Client B activates a panel, and the previously-opened screen-sharing item gets activated.
-//     let panel = window_b.build_view(cx_b, |_| TestPanel::new(DockPosition::Left));
-//     workspace_b.update(cx_b, |workspace, cx| {
-//         workspace.add_panel(panel, cx);
-//         workspace.toggle_panel_focus::<TestPanel>(cx);
-//     });
-//     executor.run_until_parked();
-//     assert_eq!(
-//         workspace_a.update(cx_a, |workspace, cx| workspace
-//             .active_item(cx)
-//             .unwrap()
-//             .item_id()),
-//         shared_screen.item_id()
-//     );
-
-//     // Toggling the focus back to the pane causes client A to return to the multibuffer.
-//     workspace_b.update(cx_b, |workspace, cx| {
-//         workspace.toggle_panel_focus::<TestPanel>(cx);
-//     });
-//     executor.run_until_parked();
-//     workspace_a.update(cx_a, |workspace, cx| {
-//         assert_eq!(
-//             workspace.active_item(cx).unwrap().item_id(),
-//             multibuffer_editor_a.item_id()
-//         )
-//     });
-
-//     // Client B activates an item that doesn't implement following,
-//     // so the previously-opened screen-sharing item gets activated.
-//     let unfollowable_item = window_b.build_view(cx_b, |_| TestItem::new());
-//     workspace_b.update(cx_b, |workspace, cx| {
-//         workspace.active_pane().update(cx, |pane, cx| {
-//             pane.add_item(Box::new(unfollowable_item), true, true, None, cx)
-//         })
-//     });
-//     executor.run_until_parked();
-//     assert_eq!(
-//         workspace_a.update(cx_a, |workspace, cx| workspace
-//             .active_item(cx)
-//             .unwrap()
-//             .item_id()),
-//         shared_screen.item_id()
-//     );
-
-//     // Following interrupts when client B disconnects.
-//     client_b.disconnect(&cx_b.to_async());
-//     executor.advance_clock(RECONNECT_TIMEOUT);
-//     assert_eq!(
-//         workspace_a.update(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
-//         None
-//     );
-// }
-
-// #[gpui::test]
-// async fn test_following_tab_order(
-//     executor: BackgroundExecutor,
-//     cx_a: &mut TestAppContext,
-//     cx_b: &mut TestAppContext,
-// ) {
-//     let mut server = TestServer::start(executor.clone()).await;
-//     let client_a = server.create_client(cx_a, "user_a").await;
-//     let client_b = server.create_client(cx_b, "user_b").await;
-//     server
-//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-//         .await;
-//     let active_call_a = cx_a.read(ActiveCall::global);
-//     let active_call_b = cx_b.read(ActiveCall::global);
-
-//     cx_a.update(editor::init);
-//     cx_b.update(editor::init);
-
-//     client_a
-//         .fs()
-//         .insert_tree(
-//             "/a",
-//             json!({
-//                 "1.txt": "one",
-//                 "2.txt": "two",
-//                 "3.txt": "three",
-//             }),
-//         )
-//         .await;
-//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
-//     active_call_a
-//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
-//         .await
-//         .unwrap();
-
-//     let project_id = active_call_a
-//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-//         .await
-//         .unwrap();
-//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
-//     active_call_b
-//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
-//         .await
-//         .unwrap();
-
-//     let workspace_a = client_a
-//         .build_workspace(&project_a, cx_a)
-//         .root(cx_a)
-//         .unwrap();
-//     let pane_a = workspace_a.update(cx_a, |workspace, _| workspace.active_pane().clone());
-
-//     let workspace_b = client_b
-//         .build_workspace(&project_b, cx_b)
-//         .root(cx_b)
-//         .unwrap();
-//     let pane_b = workspace_b.update(cx_b, |workspace, _| workspace.active_pane().clone());
-
-//     let client_b_id = project_a.update(cx_a, |project, _| {
-//         project.collaborators().values().next().unwrap().peer_id
-//     });
-
-//     //Open 1, 3 in that order on client A
-//     workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.open_path((worktree_id, "1.txt"), None, true, cx)
-//         })
-//         .await
-//         .unwrap();
-//     workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.open_path((worktree_id, "3.txt"), None, true, cx)
-//         })
-//         .await
-//         .unwrap();
-
-//     let pane_paths = |pane: &View<workspace::Pane>, cx: &mut TestAppContext| {
-//         pane.update(cx, |pane, cx| {
-//             pane.items()
-//                 .map(|item| {
-//                     item.project_path(cx)
-//                         .unwrap()
-//                         .path
-//                         .to_str()
-//                         .unwrap()
-//                         .to_owned()
-//                 })
-//                 .collect::<Vec<_>>()
-//         })
-//     };
-
-//     //Verify that the tabs opened in the order we expect
-//     assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt"]);
-
-//     //Follow client B as client A
-//     workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.follow(client_b_id, cx).unwrap()
-//         })
-//         .await
-//         .unwrap();
-
-//     //Open just 2 on client B
-//     workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.open_path((worktree_id, "2.txt"), None, true, cx)
-//         })
-//         .await
-//         .unwrap();
-//     executor.run_until_parked();
-
-//     // Verify that newly opened followed file is at the end
-//     assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt", "2.txt"]);
-
-//     //Open just 1 on client B
-//     workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.open_path((worktree_id, "1.txt"), None, true, cx)
-//         })
-//         .await
-//         .unwrap();
-//     assert_eq!(&pane_paths(&pane_b, cx_b), &["2.txt", "1.txt"]);
-//     executor.run_until_parked();
-
-//     // Verify that following into 1 did not reorder
-//     assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt", "2.txt"]);
-// }
-
-// #[gpui::test(iterations = 10)]
-// async fn test_peers_following_each_other(
-//     executor: BackgroundExecutor,
-//     cx_a: &mut TestAppContext,
-//     cx_b: &mut TestAppContext,
-// ) {
-//     let mut server = TestServer::start(executor.clone()).await;
-//     let client_a = server.create_client(cx_a, "user_a").await;
-//     let client_b = server.create_client(cx_b, "user_b").await;
-//     server
-//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-//         .await;
-//     let active_call_a = cx_a.read(ActiveCall::global);
-//     let active_call_b = cx_b.read(ActiveCall::global);
-
-//     cx_a.update(editor::init);
-//     cx_b.update(editor::init);
-
-//     // Client A shares a project.
-//     client_a
-//         .fs()
-//         .insert_tree(
-//             "/a",
-//             json!({
-//                 "1.txt": "one",
-//                 "2.txt": "two",
-//                 "3.txt": "three",
-//                 "4.txt": "four",
-//             }),
-//         )
-//         .await;
-//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
-//     active_call_a
-//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
-//         .await
-//         .unwrap();
-//     let project_id = active_call_a
-//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-//         .await
-//         .unwrap();
-
-//     // Client B joins the project.
-//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
-//     active_call_b
-//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
-//         .await
-//         .unwrap();
-
-//     // Client A opens a file.
-//     let workspace_a = client_a
-//         .build_workspace(&project_a, cx_a)
-//         .root(cx_a)
-//         .unwrap();
-//     workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.open_path((worktree_id, "1.txt"), None, true, cx)
-//         })
-//         .await
-//         .unwrap()
-//         .downcast::<Editor>()
-//         .unwrap();
-
-//     // Client B opens a different file.
-//     let workspace_b = client_b
-//         .build_workspace(&project_b, cx_b)
-//         .root(cx_b)
-//         .unwrap();
-//     workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.open_path((worktree_id, "2.txt"), None, true, cx)
-//         })
-//         .await
-//         .unwrap()
-//         .downcast::<Editor>()
-//         .unwrap();
-
-//     // Clients A and B follow each other in split panes
-//     workspace_a.update(cx_a, |workspace, cx| {
-//         workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx);
-//     });
-//     workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.follow(client_b.peer_id().unwrap(), cx).unwrap()
-//         })
-//         .await
-//         .unwrap();
-//     workspace_b.update(cx_b, |workspace, cx| {
-//         workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx);
-//     });
-//     workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.follow(client_a.peer_id().unwrap(), cx).unwrap()
-//         })
-//         .await
-//         .unwrap();
-
-//     // Clients A and B return focus to the original files they had open
-//     workspace_a.update(cx_a, |workspace, cx| workspace.activate_next_pane(cx));
-//     workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
-//     executor.run_until_parked();
-
-//     // Both clients see the other client's focused file in their right pane.
-//     assert_eq!(
-//         pane_summaries(&workspace_a, cx_a),
-//         &[
-//             PaneSummary {
-//                 active: true,
-//                 leader: None,
-//                 items: vec![(true, "1.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: false,
-//                 leader: client_b.peer_id(),
-//                 items: vec![(false, "1.txt".into()), (true, "2.txt".into())]
-//             },
-//         ]
-//     );
-//     assert_eq!(
-//         pane_summaries(&workspace_b, cx_b),
-//         &[
-//             PaneSummary {
-//                 active: true,
-//                 leader: None,
-//                 items: vec![(true, "2.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: false,
-//                 leader: client_a.peer_id(),
-//                 items: vec![(false, "2.txt".into()), (true, "1.txt".into())]
-//             },
-//         ]
-//     );
-
-//     // Clients A and B each open a new file.
-//     workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.open_path((worktree_id, "3.txt"), None, true, cx)
-//         })
-//         .await
-//         .unwrap();
-
-//     workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.open_path((worktree_id, "4.txt"), None, true, cx)
-//         })
-//         .await
-//         .unwrap();
-//     executor.run_until_parked();
-
-//     // Both client's see the other client open the new file, but keep their
-//     // focus on their own active pane.
-//     assert_eq!(
-//         pane_summaries(&workspace_a, cx_a),
-//         &[
-//             PaneSummary {
-//                 active: true,
-//                 leader: None,
-//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: false,
-//                 leader: client_b.peer_id(),
-//                 items: vec![
-//                     (false, "1.txt".into()),
-//                     (false, "2.txt".into()),
-//                     (true, "4.txt".into())
-//                 ]
-//             },
-//         ]
-//     );
-//     assert_eq!(
-//         pane_summaries(&workspace_b, cx_b),
-//         &[
-//             PaneSummary {
-//                 active: true,
-//                 leader: None,
-//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: false,
-//                 leader: client_a.peer_id(),
-//                 items: vec![
-//                     (false, "2.txt".into()),
-//                     (false, "1.txt".into()),
-//                     (true, "3.txt".into())
-//                 ]
-//             },
-//         ]
-//     );
-
-//     // Client A focuses their right pane, in which they're following client B.
-//     workspace_a.update(cx_a, |workspace, cx| workspace.activate_next_pane(cx));
-//     executor.run_until_parked();
-
-//     // Client B sees that client A is now looking at the same file as them.
-//     assert_eq!(
-//         pane_summaries(&workspace_a, cx_a),
-//         &[
-//             PaneSummary {
-//                 active: false,
-//                 leader: None,
-//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: true,
-//                 leader: client_b.peer_id(),
-//                 items: vec![
-//                     (false, "1.txt".into()),
-//                     (false, "2.txt".into()),
-//                     (true, "4.txt".into())
-//                 ]
-//             },
-//         ]
-//     );
-//     assert_eq!(
-//         pane_summaries(&workspace_b, cx_b),
-//         &[
-//             PaneSummary {
-//                 active: true,
-//                 leader: None,
-//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: false,
-//                 leader: client_a.peer_id(),
-//                 items: vec![
-//                     (false, "2.txt".into()),
-//                     (false, "1.txt".into()),
-//                     (false, "3.txt".into()),
-//                     (true, "4.txt".into())
-//                 ]
-//             },
-//         ]
-//     );
-
-//     // Client B focuses their right pane, in which they're following client A,
-//     // who is following them.
-//     workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
-//     executor.run_until_parked();
-
-//     // Client A sees that client B is now looking at the same file as them.
-//     assert_eq!(
-//         pane_summaries(&workspace_b, cx_b),
-//         &[
-//             PaneSummary {
-//                 active: false,
-//                 leader: None,
-//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: true,
-//                 leader: client_a.peer_id(),
-//                 items: vec![
-//                     (false, "2.txt".into()),
-//                     (false, "1.txt".into()),
-//                     (false, "3.txt".into()),
-//                     (true, "4.txt".into())
-//                 ]
-//             },
-//         ]
-//     );
-//     assert_eq!(
-//         pane_summaries(&workspace_a, cx_a),
-//         &[
-//             PaneSummary {
-//                 active: false,
-//                 leader: None,
-//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: true,
-//                 leader: client_b.peer_id(),
-//                 items: vec![
-//                     (false, "1.txt".into()),
-//                     (false, "2.txt".into()),
-//                     (true, "4.txt".into())
-//                 ]
-//             },
-//         ]
-//     );
-
-//     // Client B focuses a file that they previously followed A to, breaking
-//     // the follow.
-//     workspace_b.update(cx_b, |workspace, cx| {
-//         workspace.active_pane().update(cx, |pane, cx| {
-//             pane.activate_prev_item(true, cx);
-//         });
-//     });
-//     executor.run_until_parked();
-
-//     // Both clients see that client B is looking at that previous file.
-//     assert_eq!(
-//         pane_summaries(&workspace_b, cx_b),
-//         &[
-//             PaneSummary {
-//                 active: false,
-//                 leader: None,
-//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: true,
-//                 leader: None,
-//                 items: vec![
-//                     (false, "2.txt".into()),
-//                     (false, "1.txt".into()),
-//                     (true, "3.txt".into()),
-//                     (false, "4.txt".into())
-//                 ]
-//             },
-//         ]
-//     );
-//     assert_eq!(
-//         pane_summaries(&workspace_a, cx_a),
-//         &[
-//             PaneSummary {
-//                 active: false,
-//                 leader: None,
-//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: true,
-//                 leader: client_b.peer_id(),
-//                 items: vec![
-//                     (false, "1.txt".into()),
-//                     (false, "2.txt".into()),
-//                     (false, "4.txt".into()),
-//                     (true, "3.txt".into()),
-//                 ]
-//             },
-//         ]
-//     );
-
-//     // Client B closes tabs, some of which were originally opened by client A,
-//     // and some of which were originally opened by client B.
-//     workspace_b.update(cx_b, |workspace, cx| {
-//         workspace.active_pane().update(cx, |pane, cx| {
-//             pane.close_inactive_items(&Default::default(), cx)
-//                 .unwrap()
-//                 .detach();
-//         });
-//     });
-
-//     executor.run_until_parked();
-
-//     // Both clients see that Client B is looking at the previous tab.
-//     assert_eq!(
-//         pane_summaries(&workspace_b, cx_b),
-//         &[
-//             PaneSummary {
-//                 active: false,
-//                 leader: None,
-//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: true,
-//                 leader: None,
-//                 items: vec![(true, "3.txt".into()),]
-//             },
-//         ]
-//     );
-//     assert_eq!(
-//         pane_summaries(&workspace_a, cx_a),
-//         &[
-//             PaneSummary {
-//                 active: false,
-//                 leader: None,
-//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: true,
-//                 leader: client_b.peer_id(),
-//                 items: vec![
-//                     (false, "1.txt".into()),
-//                     (false, "2.txt".into()),
-//                     (false, "4.txt".into()),
-//                     (true, "3.txt".into()),
-//                 ]
-//             },
-//         ]
-//     );
-
-//     // Client B follows client A again.
-//     workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.follow(client_a.peer_id().unwrap(), cx).unwrap()
-//         })
-//         .await
-//         .unwrap();
-
-//     // Client A cycles through some tabs.
-//     workspace_a.update(cx_a, |workspace, cx| {
-//         workspace.active_pane().update(cx, |pane, cx| {
-//             pane.activate_prev_item(true, cx);
-//         });
-//     });
-//     executor.run_until_parked();
-
-//     // Client B follows client A into those tabs.
-//     assert_eq!(
-//         pane_summaries(&workspace_a, cx_a),
-//         &[
-//             PaneSummary {
-//                 active: false,
-//                 leader: None,
-//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: true,
-//                 leader: None,
-//                 items: vec![
-//                     (false, "1.txt".into()),
-//                     (false, "2.txt".into()),
-//                     (true, "4.txt".into()),
-//                     (false, "3.txt".into()),
-//                 ]
-//             },
-//         ]
-//     );
-//     assert_eq!(
-//         pane_summaries(&workspace_b, cx_b),
-//         &[
-//             PaneSummary {
-//                 active: false,
-//                 leader: None,
-//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: true,
-//                 leader: client_a.peer_id(),
-//                 items: vec![(false, "3.txt".into()), (true, "4.txt".into())]
-//             },
-//         ]
-//     );
-
-//     workspace_a.update(cx_a, |workspace, cx| {
-//         workspace.active_pane().update(cx, |pane, cx| {
-//             pane.activate_prev_item(true, cx);
-//         });
-//     });
-//     executor.run_until_parked();
-
-//     assert_eq!(
-//         pane_summaries(&workspace_a, cx_a),
-//         &[
-//             PaneSummary {
-//                 active: false,
-//                 leader: None,
-//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: true,
-//                 leader: None,
-//                 items: vec![
-//                     (false, "1.txt".into()),
-//                     (true, "2.txt".into()),
-//                     (false, "4.txt".into()),
-//                     (false, "3.txt".into()),
-//                 ]
-//             },
-//         ]
-//     );
-//     assert_eq!(
-//         pane_summaries(&workspace_b, cx_b),
-//         &[
-//             PaneSummary {
-//                 active: false,
-//                 leader: None,
-//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: true,
-//                 leader: client_a.peer_id(),
-//                 items: vec![
-//                     (false, "3.txt".into()),
-//                     (false, "4.txt".into()),
-//                     (true, "2.txt".into())
-//                 ]
-//             },
-//         ]
-//     );
-
-//     workspace_a.update(cx_a, |workspace, cx| {
-//         workspace.active_pane().update(cx, |pane, cx| {
-//             pane.activate_prev_item(true, cx);
-//         });
-//     });
-//     executor.run_until_parked();
-
-//     assert_eq!(
-//         pane_summaries(&workspace_a, cx_a),
-//         &[
-//             PaneSummary {
-//                 active: false,
-//                 leader: None,
-//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: true,
-//                 leader: None,
-//                 items: vec![
-//                     (true, "1.txt".into()),
-//                     (false, "2.txt".into()),
-//                     (false, "4.txt".into()),
-//                     (false, "3.txt".into()),
-//                 ]
-//             },
-//         ]
-//     );
-//     assert_eq!(
-//         pane_summaries(&workspace_b, cx_b),
-//         &[
-//             PaneSummary {
-//                 active: false,
-//                 leader: None,
-//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: true,
-//                 leader: client_a.peer_id(),
-//                 items: vec![
-//                     (false, "3.txt".into()),
-//                     (false, "4.txt".into()),
-//                     (false, "2.txt".into()),
-//                     (true, "1.txt".into()),
-//                 ]
-//             },
-//         ]
-//     );
-// }
-
-// #[gpui::test(iterations = 10)]
-// async fn test_auto_unfollowing(
-//     executor: BackgroundExecutor,
-//     cx_a: &mut TestAppContext,
-//     cx_b: &mut TestAppContext,
-// ) {
-//     // 2 clients connect to a server.
-//     let mut server = TestServer::start(executor.clone()).await;
-//     let client_a = server.create_client(cx_a, "user_a").await;
-//     let client_b = server.create_client(cx_b, "user_b").await;
-//     server
-//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-//         .await;
-//     let active_call_a = cx_a.read(ActiveCall::global);
-//     let active_call_b = cx_b.read(ActiveCall::global);
-
-//     cx_a.update(editor::init);
-//     cx_b.update(editor::init);
-
-//     // Client A shares a project.
-//     client_a
-//         .fs()
-//         .insert_tree(
-//             "/a",
-//             json!({
-//                 "1.txt": "one",
-//                 "2.txt": "two",
-//                 "3.txt": "three",
-//             }),
-//         )
-//         .await;
-//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
-//     active_call_a
-//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
-//         .await
-//         .unwrap();
-
-//     let project_id = active_call_a
-//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-//         .await
-//         .unwrap();
-//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
-//     active_call_b
-//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
-//         .await
-//         .unwrap();
-
-//     todo!("could be wrong")
-//     let mut cx_a = VisualTestContext::from_window(*window_a, cx_a);
-//     let cx_a = &mut cx_a;
-//     let mut cx_b = VisualTestContext::from_window(*window_b, cx_b);
-//     let cx_b = &mut cx_b;
-
-//     // Client A opens some editors.
-//     let workspace_a = client_a
-//         .build_workspace(&project_a, cx_a)
-//         .root(cx_a)
-//         .unwrap();
-//     let _editor_a1 = workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.open_path((worktree_id, "1.txt"), None, true, cx)
-//         })
-//         .await
-//         .unwrap()
-//         .downcast::<Editor>()
-//         .unwrap();
-
-//     // Client B starts following client A.
-//     let workspace_b = client_b
-//         .build_workspace(&project_b, cx_b)
-//         .root(cx_b)
-//         .unwrap();
-//     let pane_b = workspace_b.update(cx_b, |workspace, _| workspace.active_pane().clone());
-//     let leader_id = project_b.update(cx_b, |project, _| {
-//         project.collaborators().values().next().unwrap().peer_id
-//     });
-//     workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.follow(leader_id, cx).unwrap()
-//         })
-//         .await
-//         .unwrap();
-//     assert_eq!(
-//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-//         Some(leader_id)
-//     );
-//     let editor_b2 = workspace_b.update(cx_b, |workspace, cx| {
-//         workspace
-//             .active_item(cx)
-//             .unwrap()
-//             .downcast::<Editor>()
-//             .unwrap()
-//     });
-
-//     // When client B moves, it automatically stops following client A.
-//     editor_b2.update(cx_b, |editor, cx| editor.move_right(&editor::MoveRight, cx));
-//     assert_eq!(
-//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-//         None
-//     );
-
-//     workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.follow(leader_id, cx).unwrap()
-//         })
-//         .await
-//         .unwrap();
-//     assert_eq!(
-//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-//         Some(leader_id)
-//     );
-
-//     // When client B edits, it automatically stops following client A.
-//     editor_b2.update(cx_b, |editor, cx| editor.insert("X", cx));
-//     assert_eq!(
-//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-//         None
-//     );
-
-//     workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.follow(leader_id, cx).unwrap()
-//         })
-//         .await
-//         .unwrap();
-//     assert_eq!(
-//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-//         Some(leader_id)
-//     );
-
-//     // When client B scrolls, it automatically stops following client A.
-//     editor_b2.update(cx_b, |editor, cx| {
-//         editor.set_scroll_position(point(0., 3.), cx)
-//     });
-//     assert_eq!(
-//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-//         None
-//     );
-
-//     workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.follow(leader_id, cx).unwrap()
-//         })
-//         .await
-//         .unwrap();
-//     assert_eq!(
-//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-//         Some(leader_id)
-//     );
-
-//     // When client B activates a different pane, it continues following client A in the original pane.
-//     workspace_b.update(cx_b, |workspace, cx| {
-//         workspace.split_and_clone(pane_b.clone(), SplitDirection::Right, cx)
-//     });
-//     assert_eq!(
-//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-//         Some(leader_id)
-//     );
-
-//     workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
-//     assert_eq!(
-//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-//         Some(leader_id)
-//     );
-
-//     // When client B activates a different item in the original pane, it automatically stops following client A.
-//     workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.open_path((worktree_id, "2.txt"), None, true, cx)
-//         })
-//         .await
-//         .unwrap();
-//     assert_eq!(
-//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-//         None
-//     );
-// }
-
-// #[gpui::test(iterations = 10)]
-// async fn test_peers_simultaneously_following_each_other(
-//     executor: BackgroundExecutor,
-//     cx_a: &mut TestAppContext,
-//     cx_b: &mut TestAppContext,
-// ) {
-//     let mut server = TestServer::start(executor.clone()).await;
-//     let client_a = server.create_client(cx_a, "user_a").await;
-//     let client_b = server.create_client(cx_b, "user_b").await;
-//     server
-//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-//         .await;
-//     let active_call_a = cx_a.read(ActiveCall::global);
-
-//     cx_a.update(editor::init);
-//     cx_b.update(editor::init);
-
-//     client_a.fs().insert_tree("/a", json!({})).await;
-//     let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
-//     let workspace_a = client_a
-//         .build_workspace(&project_a, cx_a)
-//         .root(cx_a)
-//         .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;
-//     let workspace_b = client_b
-//         .build_workspace(&project_b, cx_b)
-//         .root(cx_b)
-//         .unwrap();
-
-//     executor.run_until_parked();
-//     let client_a_id = project_b.update(cx_b, |project, _| {
-//         project.collaborators().values().next().unwrap().peer_id
-//     });
-//     let client_b_id = project_a.update(cx_a, |project, _| {
-//         project.collaborators().values().next().unwrap().peer_id
-//     });
-
-//     let a_follow_b = workspace_a.update(cx_a, |workspace, cx| {
-//         workspace.follow(client_b_id, cx).unwrap()
-//     });
-//     let b_follow_a = workspace_b.update(cx_b, |workspace, cx| {
-//         workspace.follow(client_a_id, cx).unwrap()
-//     });
-
-//     futures::try_join!(a_follow_b, b_follow_a).unwrap();
-//     workspace_a.update(cx_a, |workspace, _| {
-//         assert_eq!(
-//             workspace.leader_for_pane(workspace.active_pane()),
-//             Some(client_b_id)
-//         );
-//     });
-//     workspace_b.update(cx_b, |workspace, _| {
-//         assert_eq!(
-//             workspace.leader_for_pane(workspace.active_pane()),
-//             Some(client_a_id)
-//         );
-//     });
-// }
-
-// #[gpui::test(iterations = 10)]
-// async fn test_following_across_workspaces(
-//     executor: BackgroundExecutor,
-//     cx_a: &mut TestAppContext,
-//     cx_b: &mut TestAppContext,
-// ) {
-//     // a and b join a channel/call
-//     // a shares project 1
-//     // b shares project 2
-//     //
-//     // b follows a: causes project 2 to be joined, and b to follow a.
-//     // b opens a different file in project 2, a follows b
-//     // b opens a different file in project 1, a cannot follow b
-//     // b shares the project, a joins the project and follows b
-//     let mut server = TestServer::start(executor.clone()).await;
-//     let client_a = server.create_client(cx_a, "user_a").await;
-//     let client_b = server.create_client(cx_b, "user_b").await;
-//     cx_a.update(editor::init);
-//     cx_b.update(editor::init);
-
-//     client_a
-//         .fs()
-//         .insert_tree(
-//             "/a",
-//             json!({
-//                 "w.rs": "",
-//                 "x.rs": "",
-//             }),
-//         )
-//         .await;
-
-//     client_b
-//         .fs()
-//         .insert_tree(
-//             "/b",
-//             json!({
-//                 "y.rs": "",
-//                 "z.rs": "",
-//             }),
-//         )
-//         .await;
-
-//     server
-//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-//         .await;
-//     let active_call_a = cx_a.read(ActiveCall::global);
-//     let active_call_b = cx_b.read(ActiveCall::global);
-
-//     let (project_a, worktree_id_a) = client_a.build_local_project("/a", cx_a).await;
-//     let (project_b, worktree_id_b) = client_b.build_local_project("/b", cx_b).await;
-
-//     let workspace_a = client_a
-//         .build_workspace(&project_a, cx_a)
-//         .root(cx_a)
-//         .unwrap();
-//     let workspace_b = client_b
-//         .build_workspace(&project_b, cx_b)
-//         .root(cx_b)
-//         .unwrap();
-
-//     cx_a.update(|cx| collab_ui::init(&client_a.app_state, cx));
-//     cx_b.update(|cx| collab_ui::init(&client_b.app_state, cx));
-
-//     active_call_a
-//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-//         .await
-//         .unwrap();
-
-//     active_call_a
-//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
-//         .await
-//         .unwrap();
-//     active_call_b
-//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
-//         .await
-//         .unwrap();
-
-//     todo!("could be wrong")
-//     let mut cx_a = VisualTestContext::from_window(*window_a, cx_a);
-//     let cx_a = &mut cx_a;
-//     let mut cx_b = VisualTestContext::from_window(*window_b, cx_b);
-//     let cx_b = &mut cx_b;
-
-//     workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.open_path((worktree_id_a, "w.rs"), None, true, cx)
-//         })
-//         .await
-//         .unwrap();
-
-//     executor.run_until_parked();
-//     assert_eq!(visible_push_notifications(cx_b).len(), 1);
-
-//     workspace_b.update(cx_b, |workspace, cx| {
-//         workspace
-//             .follow(client_a.peer_id().unwrap(), cx)
-//             .unwrap()
-//             .detach()
-//     });
-
-//     executor.run_until_parked();
-//     let workspace_b_project_a = cx_b
-//         .windows()
-//         .iter()
-//         .max_by_key(|window| window.item_id())
-//         .unwrap()
-//         .downcast::<Workspace>()
-//         .unwrap()
-//         .root(cx_b)
-//         .unwrap();
-
-//     // assert that b is following a in project a in w.rs
-//     workspace_b_project_a.update(cx_b, |workspace, cx| {
-//         assert!(workspace.is_being_followed(client_a.peer_id().unwrap()));
-//         assert_eq!(
-//             client_a.peer_id(),
-//             workspace.leader_for_pane(workspace.active_pane())
-//         );
-//         let item = workspace.active_item(cx).unwrap();
-//         assert_eq!(item.tab_description(0, cx).unwrap(), Cow::Borrowed("w.rs"));
-//     });
-
-//     // TODO: in app code, this would be done by the collab_ui.
-//     active_call_b
-//         .update(cx_b, |call, cx| {
-//             let project = workspace_b_project_a.read(cx).project().clone();
-//             call.set_location(Some(&project), cx)
-//         })
-//         .await
-//         .unwrap();
-
-//     // assert that there are no share notifications open
-//     assert_eq!(visible_push_notifications(cx_b).len(), 0);
-
-//     // b moves to x.rs in a's project, and a follows
-//     workspace_b_project_a
-//         .update(cx_b, |workspace, cx| {
-//             workspace.open_path((worktree_id_a, "x.rs"), None, true, cx)
-//         })
-//         .await
-//         .unwrap();
-
-//     executor.run_until_parked();
-//     workspace_b_project_a.update(cx_b, |workspace, cx| {
-//         let item = workspace.active_item(cx).unwrap();
-//         assert_eq!(item.tab_description(0, cx).unwrap(), Cow::Borrowed("x.rs"));
-//     });
-
-//     workspace_a.update(cx_a, |workspace, cx| {
-//         workspace
-//             .follow(client_b.peer_id().unwrap(), cx)
-//             .unwrap()
-//             .detach()
-//     });
-
-//     executor.run_until_parked();
-//     workspace_a.update(cx_a, |workspace, cx| {
-//         assert!(workspace.is_being_followed(client_b.peer_id().unwrap()));
-//         assert_eq!(
-//             client_b.peer_id(),
-//             workspace.leader_for_pane(workspace.active_pane())
-//         );
-//         let item = workspace.active_pane().read(cx).active_item().unwrap();
-//         assert_eq!(item.tab_description(0, cx).unwrap(), "x.rs".into());
-//     });
-
-//     // b moves to y.rs in b's project, a is still following but can't yet see
-//     workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.open_path((worktree_id_b, "y.rs"), None, true, cx)
-//         })
-//         .await
-//         .unwrap();
-
-//     // TODO: in app code, this would be done by the collab_ui.
-//     active_call_b
-//         .update(cx_b, |call, cx| {
-//             let project = workspace_b.read(cx).project().clone();
-//             call.set_location(Some(&project), cx)
-//         })
-//         .await
-//         .unwrap();
-
-//     let project_b_id = active_call_b
-//         .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
-//         .await
-//         .unwrap();
-
-//     executor.run_until_parked();
-//     assert_eq!(visible_push_notifications(cx_a).len(), 1);
-//     cx_a.update(|cx| {
-//         workspace::join_remote_project(
-//             project_b_id,
-//             client_b.user_id().unwrap(),
-//             client_a.app_state.clone(),
-//             cx,
-//         )
-//     })
-//     .await
-//     .unwrap();
-
-//     executor.run_until_parked();
-
-//     assert_eq!(visible_push_notifications(cx_a).len(), 0);
-//     let workspace_a_project_b = cx_a
-//         .windows()
-//         .iter()
-//         .max_by_key(|window| window.item_id())
-//         .unwrap()
-//         .downcast::<Workspace>()
-//         .unwrap()
-//         .root(cx_a)
-//         .unwrap();
-
-//     workspace_a_project_b.update(cx_a, |workspace, cx| {
-//         assert_eq!(workspace.project().read(cx).remote_id(), Some(project_b_id));
-//         assert!(workspace.is_being_followed(client_b.peer_id().unwrap()));
-//         assert_eq!(
-//             client_b.peer_id(),
-//             workspace.leader_for_pane(workspace.active_pane())
-//         );
-//         let item = workspace.active_item(cx).unwrap();
-//         assert_eq!(item.tab_description(0, cx).unwrap(), Cow::Borrowed("y.rs"));
-//     });
-// }
-
-// #[gpui::test]
-// async fn test_following_into_excluded_file(
-//     executor: BackgroundExecutor,
-//     mut cx_a: &mut TestAppContext,
-//     mut cx_b: &mut TestAppContext,
-// ) {
-//     let mut server = TestServer::start(executor.clone()).await;
-//     let client_a = server.create_client(cx_a, "user_a").await;
-//     let client_b = server.create_client(cx_b, "user_b").await;
-//     for cx in [&mut cx_a, &mut cx_b] {
-//         cx.update(|cx| {
-//             cx.update_global::<SettingsStore, _>(|store, cx| {
-//                 store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
-//                     project_settings.file_scan_exclusions = Some(vec!["**/.git".to_string()]);
-//                 });
-//             });
-//         });
-//     }
-//     server
-//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-//         .await;
-//     let active_call_a = cx_a.read(ActiveCall::global);
-//     let active_call_b = cx_b.read(ActiveCall::global);
-
-//     cx_a.update(editor::init);
-//     cx_b.update(editor::init);
-
-//     client_a
-//         .fs()
-//         .insert_tree(
-//             "/a",
-//             json!({
-//                 ".git": {
-//                     "COMMIT_EDITMSG": "write your commit message here",
-//                 },
-//                 "1.txt": "one\none\none",
-//                 "2.txt": "two\ntwo\ntwo",
-//                 "3.txt": "three\nthree\nthree",
-//             }),
-//         )
-//         .await;
-//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
-//     active_call_a
-//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
-//         .await
-//         .unwrap();
-
-//     let project_id = active_call_a
-//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-//         .await
-//         .unwrap();
-//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
-//     active_call_b
-//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
-//         .await
-//         .unwrap();
-
-//     let window_a = client_a.build_workspace(&project_a, cx_a);
-//     let workspace_a = window_a.root(cx_a).unwrap();
-//     let peer_id_a = client_a.peer_id().unwrap();
-//     let window_b = client_b.build_workspace(&project_b, cx_b);
-//     let workspace_b = window_b.root(cx_b).unwrap();
-
-//     todo!("could be wrong")
-//     let mut cx_a = VisualTestContext::from_window(*window_a, cx_a);
-//     let cx_a = &mut cx_a;
-//     let mut cx_b = VisualTestContext::from_window(*window_b, cx_b);
-//     let cx_b = &mut cx_b;
-
-//     // Client A opens editors for a regular file and an excluded file.
-//     let editor_for_regular = workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.open_path((worktree_id, "1.txt"), None, true, cx)
-//         })
-//         .await
-//         .unwrap()
-//         .downcast::<Editor>()
-//         .unwrap();
-//     let editor_for_excluded_a = workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.open_path((worktree_id, ".git/COMMIT_EDITMSG"), None, true, cx)
-//         })
-//         .await
-//         .unwrap()
-//         .downcast::<Editor>()
-//         .unwrap();
-
-//     // Client A updates their selections in those editors
-//     editor_for_regular.update(cx_a, |editor, cx| {
-//         editor.handle_input("a", cx);
-//         editor.handle_input("b", cx);
-//         editor.handle_input("c", cx);
-//         editor.select_left(&Default::default(), cx);
-//         assert_eq!(editor.selections.ranges(cx), vec![3..2]);
-//     });
-//     editor_for_excluded_a.update(cx_a, |editor, cx| {
-//         editor.select_all(&Default::default(), cx);
-//         editor.handle_input("new commit message", cx);
-//         editor.select_left(&Default::default(), cx);
-//         assert_eq!(editor.selections.ranges(cx), vec![18..17]);
-//     });
-
-//     // When client B starts following client A, currently visible file is replicated
-//     workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.follow(peer_id_a, cx).unwrap()
-//         })
-//         .await
-//         .unwrap();
-
-//     let editor_for_excluded_b = workspace_b.update(cx_b, |workspace, cx| {
-//         workspace
-//             .active_item(cx)
-//             .unwrap()
-//             .downcast::<Editor>()
-//             .unwrap()
-//     });
-//     assert_eq!(
-//         cx_b.read(|cx| editor_for_excluded_b.project_path(cx)),
-//         Some((worktree_id, ".git/COMMIT_EDITMSG").into())
-//     );
-//     assert_eq!(
-//         editor_for_excluded_b.update(cx_b, |editor, cx| editor.selections.ranges(cx)),
-//         vec![18..17]
-//     );
-
-//     // Changes from B to the excluded file are replicated in A's editor
-//     editor_for_excluded_b.update(cx_b, |editor, cx| {
-//         editor.handle_input("\nCo-Authored-By: B <b@b.b>", cx);
-//     });
-//     executor.run_until_parked();
-//     editor_for_excluded_a.update(cx_a, |editor, cx| {
-//         assert_eq!(
-//             editor.text(cx),
-//             "new commit messag\nCo-Authored-By: B <b@b.b>"
-//         );
-//     });
-// }
-
-// fn visible_push_notifications(
-//     cx: &mut TestAppContext,
-// ) -> Vec<gpui::View<ProjectSharedNotification>> {
-//     let mut ret = Vec::new();
-//     for window in cx.windows() {
-//         window.update(cx, |window| {
-//             if let Some(handle) = window
-//                 .root_view()
-//                 .clone()
-//                 .downcast::<ProjectSharedNotification>()
-//             {
-//                 ret.push(handle)
-//             }
-//         });
-//     }
-//     ret
-// }
-
-// #[derive(Debug, PartialEq, Eq)]
-// struct PaneSummary {
-//     active: bool,
-//     leader: Option<PeerId>,
-//     items: Vec<(bool, String)>,
-// }
-
-// fn followers_by_leader(project_id: u64, cx: &TestAppContext) -> Vec<(PeerId, Vec<PeerId>)> {
-//     cx.read(|cx| {
-//         let active_call = ActiveCall::global(cx).read(cx);
-//         let peer_id = active_call.client().peer_id();
-//         let room = active_call.room().unwrap().read(cx);
-//         let mut result = room
-//             .remote_participants()
-//             .values()
-//             .map(|participant| participant.peer_id)
-//             .chain(peer_id)
-//             .filter_map(|peer_id| {
-//                 let followers = room.followers_for(peer_id, project_id);
-//                 if followers.is_empty() {
-//                     None
-//                 } else {
-//                     Some((peer_id, followers.to_vec()))
-//                 }
-//             })
-//             .collect::<Vec<_>>();
-//         result.sort_by_key(|e| e.0);
-//         result
-//     })
-// }
-
-// fn pane_summaries(workspace: &View<Workspace>, cx: &mut WindowContext<'_>) -> Vec<PaneSummary> {
-//     workspace.update(cx, |workspace, cx| {
-//         let active_pane = workspace.active_pane();
-//         workspace
-//             .panes()
-//             .iter()
-//             .map(|pane| {
-//                 let leader = workspace.leader_for_pane(pane);
-//                 let active = pane == active_pane;
-//                 let pane = pane.read(cx);
-//                 let active_ix = pane.active_item_index();
-//                 PaneSummary {
-//                     active,
-//                     leader,
-//                     items: pane
-//                         .items()
-//                         .enumerate()
-//                         .map(|(ix, item)| {
-//                             (
-//                                 ix == active_ix,
-//                                 item.tab_description(0, cx)
-//                                     .map_or(String::new(), |s| s.to_string()),
-//                             )
-//                         })
-//                         .collect(),
-//                 }
-//             })
-//             .collect()
-//     })
-// }

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

@@ -1,5722 +0,0 @@
-use crate::{
-    rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
-    tests::{channel_id, room_participants, RoomParticipants, TestClient, TestServer},
-};
-use call::{room, ActiveCall, ParticipantLocation, Room};
-use client::{User, RECEIVE_TIMEOUT};
-use collections::{HashMap, HashSet};
-use fs::{repository::GitFileStatus, FakeFs, Fs as _, RemoveOptions};
-use futures::StreamExt as _;
-use gpui::{AppContext, BackgroundExecutor, Model, TestAppContext};
-use language::{
-    language_settings::{AllLanguageSettings, Formatter},
-    tree_sitter_rust, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language, LanguageConfig,
-    LineEnding, OffsetRangeExt, Point, Rope,
-};
-use live_kit_client::MacOSDisplay;
-use lsp::LanguageServerId;
-use project::{
-    search::SearchQuery, DiagnosticSummary, FormatTrigger, HoverBlockKind, Project, ProjectPath,
-};
-use rand::prelude::*;
-use serde_json::json;
-use settings::SettingsStore;
-use std::{
-    cell::{Cell, RefCell},
-    env, future, mem,
-    path::{Path, PathBuf},
-    rc::Rc,
-    sync::{
-        atomic::{AtomicBool, Ordering::SeqCst},
-        Arc,
-    },
-};
-use unindent::Unindent as _;
-
-#[ctor::ctor]
-fn init_logger() {
-    if std::env::var("RUST_LOG").is_ok() {
-        env_logger::init();
-    }
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_basic_calls(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-    cx_b2: &mut TestAppContext,
-    cx_c: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    let 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);
-
-    // Call user B from client A.
-    active_call_a
-        .update(cx_a, |call, cx| {
-            call.invite(client_b.user_id().unwrap(), None, cx)
-        })
-        .await
-        .unwrap();
-    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
-    executor.run_until_parked();
-    assert_eq!(
-        room_participants(&room_a, cx_a),
-        RoomParticipants {
-            remote: Default::default(),
-            pending: vec!["user_b".to_string()]
-        }
-    );
-
-    // User B receives the call.
-
-    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
-    let call_b = incoming_call_b.next().await.unwrap().unwrap();
-    assert_eq!(call_b.calling_user.github_login, "user_a");
-
-    // User B connects via another client and also receives a ring on the newly-connected client.
-    let _client_b2 = server.create_client(cx_b2, "user_b").await;
-    let active_call_b2 = cx_b2.read(ActiveCall::global);
-
-    let mut incoming_call_b2 = active_call_b2.read_with(cx_b2, |call, _| call.incoming());
-    executor.run_until_parked();
-    let call_b2 = incoming_call_b2.next().await.unwrap().unwrap();
-    assert_eq!(call_b2.calling_user.github_login, "user_a");
-
-    // User B joins the room using the first client.
-    active_call_b
-        .update(cx_b, |call, cx| call.accept_incoming(cx))
-        .await
-        .unwrap();
-
-    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
-    assert!(incoming_call_b.next().await.unwrap().is_none());
-
-    executor.run_until_parked();
-    assert_eq!(
-        room_participants(&room_a, cx_a),
-        RoomParticipants {
-            remote: vec!["user_b".to_string()],
-            pending: Default::default()
-        }
-    );
-    assert_eq!(
-        room_participants(&room_b, cx_b),
-        RoomParticipants {
-            remote: vec!["user_a".to_string()],
-            pending: Default::default()
-        }
-    );
-
-    // Call user C from client B.
-
-    let mut incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
-    active_call_b
-        .update(cx_b, |call, cx| {
-            call.invite(client_c.user_id().unwrap(), None, cx)
-        })
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-    assert_eq!(
-        room_participants(&room_a, cx_a),
-        RoomParticipants {
-            remote: vec!["user_b".to_string()],
-            pending: vec!["user_c".to_string()]
-        }
-    );
-    assert_eq!(
-        room_participants(&room_b, cx_b),
-        RoomParticipants {
-            remote: vec!["user_a".to_string()],
-            pending: vec!["user_c".to_string()]
-        }
-    );
-
-    // User C receives the call, but declines it.
-    let call_c = incoming_call_c.next().await.unwrap().unwrap();
-    assert_eq!(call_c.calling_user.github_login, "user_b");
-    active_call_c.update(cx_c, |call, cx| call.decline_incoming(cx).unwrap());
-    assert!(incoming_call_c.next().await.unwrap().is_none());
-
-    executor.run_until_parked();
-    assert_eq!(
-        room_participants(&room_a, cx_a),
-        RoomParticipants {
-            remote: vec!["user_b".to_string()],
-            pending: Default::default()
-        }
-    );
-    assert_eq!(
-        room_participants(&room_b, cx_b),
-        RoomParticipants {
-            remote: vec!["user_a".to_string()],
-            pending: Default::default()
-        }
-    );
-
-    // Call user C again from user A.
-    active_call_a
-        .update(cx_a, |call, cx| {
-            call.invite(client_c.user_id().unwrap(), None, cx)
-        })
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-    assert_eq!(
-        room_participants(&room_a, cx_a),
-        RoomParticipants {
-            remote: vec!["user_b".to_string()],
-            pending: vec!["user_c".to_string()]
-        }
-    );
-    assert_eq!(
-        room_participants(&room_b, cx_b),
-        RoomParticipants {
-            remote: vec!["user_a".to_string()],
-            pending: vec!["user_c".to_string()]
-        }
-    );
-
-    // User C accepts the call.
-    let call_c = incoming_call_c.next().await.unwrap().unwrap();
-    assert_eq!(call_c.calling_user.github_login, "user_a");
-    active_call_c
-        .update(cx_c, |call, cx| call.accept_incoming(cx))
-        .await
-        .unwrap();
-    assert!(incoming_call_c.next().await.unwrap().is_none());
-
-    let room_c = active_call_c.read_with(cx_c, |call, _| call.room().unwrap().clone());
-
-    executor.run_until_parked();
-    assert_eq!(
-        room_participants(&room_a, cx_a),
-        RoomParticipants {
-            remote: vec!["user_b".to_string(), "user_c".to_string()],
-            pending: Default::default()
-        }
-    );
-    assert_eq!(
-        room_participants(&room_b, cx_b),
-        RoomParticipants {
-            remote: vec!["user_a".to_string(), "user_c".to_string()],
-            pending: Default::default()
-        }
-    );
-    assert_eq!(
-        room_participants(&room_c, cx_c),
-        RoomParticipants {
-            remote: vec!["user_a".to_string(), "user_b".to_string()],
-            pending: Default::default()
-        }
-    );
-
-    // User A shares their screen
-    let display = MacOSDisplay::new();
-    let events_b = active_call_events(cx_b);
-    let events_c = active_call_events(cx_c);
-    active_call_a
-        .update(cx_a, |call, cx| {
-            call.room().unwrap().update(cx, |room, cx| {
-                room.set_display_sources(vec![display.clone()]);
-                room.share_screen(cx)
-            })
-        })
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-
-    // User B observes the remote screen sharing track.
-    assert_eq!(events_b.borrow().len(), 1);
-    let event_b = events_b.borrow().first().unwrap().clone();
-    if let call::room::Event::RemoteVideoTracksChanged { participant_id } = event_b {
-        assert_eq!(participant_id, client_a.peer_id().unwrap());
-
-        room_b.read_with(cx_b, |room, _| {
-            assert_eq!(
-                room.remote_participants()[&client_a.user_id().unwrap()]
-                    .video_tracks
-                    .len(),
-                1
-            );
-        });
-    } else {
-        panic!("unexpected event")
-    }
-
-    // User C observes the remote screen sharing track.
-    assert_eq!(events_c.borrow().len(), 1);
-    let event_c = events_c.borrow().first().unwrap().clone();
-    if let call::room::Event::RemoteVideoTracksChanged { participant_id } = event_c {
-        assert_eq!(participant_id, client_a.peer_id().unwrap());
-
-        room_c.read_with(cx_c, |room, _| {
-            assert_eq!(
-                room.remote_participants()[&client_a.user_id().unwrap()]
-                    .video_tracks
-                    .len(),
-                1
-            );
-        });
-    } else {
-        panic!("unexpected event")
-    }
-
-    // User A leaves the room.
-    active_call_a
-        .update(cx_a, |call, cx| {
-            let hang_up = call.hang_up(cx);
-            assert!(call.room().is_none());
-            hang_up
-        })
-        .await
-        .unwrap();
-    executor.run_until_parked();
-    assert_eq!(
-        room_participants(&room_a, cx_a),
-        RoomParticipants {
-            remote: Default::default(),
-            pending: Default::default()
-        }
-    );
-    assert_eq!(
-        room_participants(&room_b, cx_b),
-        RoomParticipants {
-            remote: vec!["user_c".to_string()],
-            pending: Default::default()
-        }
-    );
-    assert_eq!(
-        room_participants(&room_c, cx_c),
-        RoomParticipants {
-            remote: vec!["user_b".to_string()],
-            pending: Default::default()
-        }
-    );
-
-    // User B gets disconnected from the LiveKit server, which causes them
-    // to automatically leave the room. User C leaves the room as well because
-    // nobody else is in there.
-    server
-        .test_live_kit_server
-        .disconnect_client(client_b.user_id().unwrap().to_string())
-        .await;
-    executor.run_until_parked();
-
-    active_call_b.read_with(cx_b, |call, _| assert!(call.room().is_none()));
-
-    active_call_c.read_with(cx_c, |call, _| assert!(call.room().is_none()));
-    assert_eq!(
-        room_participants(&room_a, cx_a),
-        RoomParticipants {
-            remote: Default::default(),
-            pending: Default::default()
-        }
-    );
-    assert_eq!(
-        room_participants(&room_b, cx_b),
-        RoomParticipants {
-            remote: Default::default(),
-            pending: Default::default()
-        }
-    );
-    assert_eq!(
-        room_participants(&room_c, cx_c),
-        RoomParticipants {
-            remote: Default::default(),
-            pending: Default::default()
-        }
-    );
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_calling_multiple_users_simultaneously(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-    cx_c: &mut TestAppContext,
-    cx_d: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    let client_c = server.create_client(cx_c, "user_c").await;
-    let client_d = server.create_client(cx_d, "user_d").await;
-    server
-        .make_contacts(&mut [
-            (&client_a, cx_a),
-            (&client_b, cx_b),
-            (&client_c, cx_c),
-            (&client_d, cx_d),
-        ])
-        .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);
-    let active_call_d = cx_d.read(ActiveCall::global);
-
-    // Simultaneously call user B and user C from client A.
-    let b_invite = active_call_a.update(cx_a, |call, cx| {
-        call.invite(client_b.user_id().unwrap(), None, cx)
-    });
-    let c_invite = active_call_a.update(cx_a, |call, cx| {
-        call.invite(client_c.user_id().unwrap(), None, cx)
-    });
-    b_invite.await.unwrap();
-    c_invite.await.unwrap();
-
-    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
-    executor.run_until_parked();
-    assert_eq!(
-        room_participants(&room_a, cx_a),
-        RoomParticipants {
-            remote: Default::default(),
-            pending: vec!["user_b".to_string(), "user_c".to_string()]
-        }
-    );
-
-    // Call client D from client A.
-    active_call_a
-        .update(cx_a, |call, cx| {
-            call.invite(client_d.user_id().unwrap(), None, cx)
-        })
-        .await
-        .unwrap();
-    executor.run_until_parked();
-    assert_eq!(
-        room_participants(&room_a, cx_a),
-        RoomParticipants {
-            remote: Default::default(),
-            pending: vec![
-                "user_b".to_string(),
-                "user_c".to_string(),
-                "user_d".to_string()
-            ]
-        }
-    );
-
-    // Accept the call on all clients simultaneously.
-    let accept_b = active_call_b.update(cx_b, |call, cx| call.accept_incoming(cx));
-    let accept_c = active_call_c.update(cx_c, |call, cx| call.accept_incoming(cx));
-    let accept_d = active_call_d.update(cx_d, |call, cx| call.accept_incoming(cx));
-    accept_b.await.unwrap();
-    accept_c.await.unwrap();
-    accept_d.await.unwrap();
-
-    executor.run_until_parked();
-
-    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
-
-    let room_c = active_call_c.read_with(cx_c, |call, _| call.room().unwrap().clone());
-
-    let room_d = active_call_d.read_with(cx_d, |call, _| call.room().unwrap().clone());
-    assert_eq!(
-        room_participants(&room_a, cx_a),
-        RoomParticipants {
-            remote: vec![
-                "user_b".to_string(),
-                "user_c".to_string(),
-                "user_d".to_string(),
-            ],
-            pending: Default::default()
-        }
-    );
-    assert_eq!(
-        room_participants(&room_b, cx_b),
-        RoomParticipants {
-            remote: vec![
-                "user_a".to_string(),
-                "user_c".to_string(),
-                "user_d".to_string(),
-            ],
-            pending: Default::default()
-        }
-    );
-    assert_eq!(
-        room_participants(&room_c, cx_c),
-        RoomParticipants {
-            remote: vec![
-                "user_a".to_string(),
-                "user_b".to_string(),
-                "user_d".to_string(),
-            ],
-            pending: Default::default()
-        }
-    );
-    assert_eq!(
-        room_participants(&room_d, cx_d),
-        RoomParticipants {
-            remote: vec![
-                "user_a".to_string(),
-                "user_b".to_string(),
-                "user_c".to_string(),
-            ],
-            pending: Default::default()
-        }
-    );
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_joining_channels_and_calling_multiple_users_simultaneously(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-    cx_c: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    let 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 channel_1 = server
-        .make_channel(
-            "channel1",
-            None,
-            (&client_a, cx_a),
-            &mut [(&client_b, cx_b), (&client_c, cx_c)],
-        )
-        .await;
-
-    let channel_2 = server
-        .make_channel(
-            "channel2",
-            None,
-            (&client_a, cx_a),
-            &mut [(&client_b, cx_b), (&client_c, cx_c)],
-        )
-        .await;
-
-    let active_call_a = cx_a.read(ActiveCall::global);
-
-    // Simultaneously join channel 1 and then channel 2
-    active_call_a
-        .update(cx_a, |call, cx| call.join_channel(channel_1, cx))
-        .detach();
-    let join_channel_2 = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_2, cx));
-
-    join_channel_2.await.unwrap();
-
-    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
-    executor.run_until_parked();
-
-    assert_eq!(channel_id(&room_a, cx_a), Some(channel_2));
-
-    // Leave the room
-    active_call_a
-        .update(cx_a, |call, cx| {
-            let hang_up = call.hang_up(cx);
-            hang_up
-        })
-        .await
-        .unwrap();
-
-    // Initiating invites and then joining a channel should fail gracefully
-    let b_invite = active_call_a.update(cx_a, |call, cx| {
-        call.invite(client_b.user_id().unwrap(), None, cx)
-    });
-    let c_invite = active_call_a.update(cx_a, |call, cx| {
-        call.invite(client_c.user_id().unwrap(), None, cx)
-    });
-
-    let join_channel = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, cx));
-
-    b_invite.await.unwrap();
-    c_invite.await.unwrap();
-    join_channel.await.unwrap();
-
-    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
-    executor.run_until_parked();
-
-    assert_eq!(
-        room_participants(&room_a, cx_a),
-        RoomParticipants {
-            remote: Default::default(),
-            pending: vec!["user_b".to_string(), "user_c".to_string()]
-        }
-    );
-
-    assert_eq!(channel_id(&room_a, cx_a), None);
-
-    // Leave the room
-    active_call_a
-        .update(cx_a, |call, cx| {
-            let hang_up = call.hang_up(cx);
-            hang_up
-        })
-        .await
-        .unwrap();
-
-    // Simultaneously join channel 1 and call user B and user C from client A.
-    let join_channel = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, cx));
-
-    let b_invite = active_call_a.update(cx_a, |call, cx| {
-        call.invite(client_b.user_id().unwrap(), None, cx)
-    });
-    let c_invite = active_call_a.update(cx_a, |call, cx| {
-        call.invite(client_c.user_id().unwrap(), None, cx)
-    });
-
-    join_channel.await.unwrap();
-    b_invite.await.unwrap();
-    c_invite.await.unwrap();
-
-    active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
-    executor.run_until_parked();
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_room_uniqueness(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_a2: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-    cx_b2: &mut TestAppContext,
-    cx_c: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let _client_a2 = server.create_client(cx_a2, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    let _client_b2 = server.create_client(cx_b2, "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_a2 = cx_a2.read(ActiveCall::global);
-    let active_call_b = cx_b.read(ActiveCall::global);
-    let active_call_b2 = cx_b2.read(ActiveCall::global);
-    let active_call_c = cx_c.read(ActiveCall::global);
-
-    // Call user B from client A.
-    active_call_a
-        .update(cx_a, |call, cx| {
-            call.invite(client_b.user_id().unwrap(), None, cx)
-        })
-        .await
-        .unwrap();
-
-    // Ensure a new room can't be created given user A just created one.
-    active_call_a2
-        .update(cx_a2, |call, cx| {
-            call.invite(client_c.user_id().unwrap(), None, cx)
-        })
-        .await
-        .unwrap_err();
-
-    active_call_a2.read_with(cx_a2, |call, _| assert!(call.room().is_none()));
-
-    // User B receives the call from user A.
-
-    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
-    let call_b1 = incoming_call_b.next().await.unwrap().unwrap();
-    assert_eq!(call_b1.calling_user.github_login, "user_a");
-
-    // Ensure calling users A and B from client C fails.
-    active_call_c
-        .update(cx_c, |call, cx| {
-            call.invite(client_a.user_id().unwrap(), None, cx)
-        })
-        .await
-        .unwrap_err();
-    active_call_c
-        .update(cx_c, |call, cx| {
-            call.invite(client_b.user_id().unwrap(), None, cx)
-        })
-        .await
-        .unwrap_err();
-
-    // Ensure User B can't create a room while they still have an incoming call.
-    active_call_b2
-        .update(cx_b2, |call, cx| {
-            call.invite(client_c.user_id().unwrap(), None, cx)
-        })
-        .await
-        .unwrap_err();
-
-    active_call_b2.read_with(cx_b2, |call, _| assert!(call.room().is_none()));
-
-    // User B joins the room and calling them after they've joined still fails.
-    active_call_b
-        .update(cx_b, |call, cx| call.accept_incoming(cx))
-        .await
-        .unwrap();
-    active_call_c
-        .update(cx_c, |call, cx| {
-            call.invite(client_b.user_id().unwrap(), None, cx)
-        })
-        .await
-        .unwrap_err();
-
-    // Ensure User B can't create a room while they belong to another room.
-    active_call_b2
-        .update(cx_b2, |call, cx| {
-            call.invite(client_c.user_id().unwrap(), None, cx)
-        })
-        .await
-        .unwrap_err();
-
-    active_call_b2.read_with(cx_b2, |call, _| assert!(call.room().is_none()));
-
-    // Client C can successfully call client B after client B leaves the room.
-    active_call_b
-        .update(cx_b, |call, cx| call.hang_up(cx))
-        .await
-        .unwrap();
-    executor.run_until_parked();
-    active_call_c
-        .update(cx_c, |call, cx| {
-            call.invite(client_b.user_id().unwrap(), None, cx)
-        })
-        .await
-        .unwrap();
-    executor.run_until_parked();
-    let call_b2 = incoming_call_b.next().await.unwrap().unwrap();
-    assert_eq!(call_b2.calling_user.github_login, "user_c");
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_client_disconnecting_from_room(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    server
-        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-        .await;
-
-    let active_call_a = cx_a.read(ActiveCall::global);
-    let active_call_b = cx_b.read(ActiveCall::global);
-
-    // Call user B from client A.
-    active_call_a
-        .update(cx_a, |call, cx| {
-            call.invite(client_b.user_id().unwrap(), None, cx)
-        })
-        .await
-        .unwrap();
-
-    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
-
-    // User B receives the call and joins the room.
-
-    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
-    incoming_call_b.next().await.unwrap().unwrap();
-    active_call_b
-        .update(cx_b, |call, cx| call.accept_incoming(cx))
-        .await
-        .unwrap();
-
-    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
-    executor.run_until_parked();
-    assert_eq!(
-        room_participants(&room_a, cx_a),
-        RoomParticipants {
-            remote: vec!["user_b".to_string()],
-            pending: Default::default()
-        }
-    );
-    assert_eq!(
-        room_participants(&room_b, cx_b),
-        RoomParticipants {
-            remote: vec!["user_a".to_string()],
-            pending: Default::default()
-        }
-    );
-
-    // User A automatically reconnects to the room upon disconnection.
-    server.disconnect_client(client_a.peer_id().unwrap());
-    executor.advance_clock(RECEIVE_TIMEOUT);
-    executor.run_until_parked();
-    assert_eq!(
-        room_participants(&room_a, cx_a),
-        RoomParticipants {
-            remote: vec!["user_b".to_string()],
-            pending: Default::default()
-        }
-    );
-    assert_eq!(
-        room_participants(&room_b, cx_b),
-        RoomParticipants {
-            remote: vec!["user_a".to_string()],
-            pending: Default::default()
-        }
-    );
-
-    // When user A disconnects, both client A and B clear their room on the active call.
-    server.forbid_connections();
-    server.disconnect_client(client_a.peer_id().unwrap());
-    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
-
-    active_call_a.read_with(cx_a, |call, _| assert!(call.room().is_none()));
-
-    active_call_b.read_with(cx_b, |call, _| assert!(call.room().is_none()));
-    assert_eq!(
-        room_participants(&room_a, cx_a),
-        RoomParticipants {
-            remote: Default::default(),
-            pending: Default::default()
-        }
-    );
-    assert_eq!(
-        room_participants(&room_b, cx_b),
-        RoomParticipants {
-            remote: Default::default(),
-            pending: Default::default()
-        }
-    );
-
-    // Allow user A to reconnect to the server.
-    server.allow_connections();
-    executor.advance_clock(RECEIVE_TIMEOUT);
-
-    // Call user B again from client A.
-    active_call_a
-        .update(cx_a, |call, cx| {
-            call.invite(client_b.user_id().unwrap(), None, cx)
-        })
-        .await
-        .unwrap();
-
-    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
-
-    // User B receives the call and joins the room.
-
-    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
-    incoming_call_b.next().await.unwrap().unwrap();
-    active_call_b
-        .update(cx_b, |call, cx| call.accept_incoming(cx))
-        .await
-        .unwrap();
-
-    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
-    executor.run_until_parked();
-    assert_eq!(
-        room_participants(&room_a, cx_a),
-        RoomParticipants {
-            remote: vec!["user_b".to_string()],
-            pending: Default::default()
-        }
-    );
-    assert_eq!(
-        room_participants(&room_b, cx_b),
-        RoomParticipants {
-            remote: vec!["user_a".to_string()],
-            pending: Default::default()
-        }
-    );
-
-    // User B gets disconnected from the LiveKit server, which causes it
-    // to automatically leave the room.
-    server
-        .test_live_kit_server
-        .disconnect_client(client_b.user_id().unwrap().to_string())
-        .await;
-    executor.run_until_parked();
-    active_call_a.update(cx_a, |call, _| assert!(call.room().is_none()));
-    active_call_b.update(cx_b, |call, _| assert!(call.room().is_none()));
-    assert_eq!(
-        room_participants(&room_a, cx_a),
-        RoomParticipants {
-            remote: Default::default(),
-            pending: Default::default()
-        }
-    );
-    assert_eq!(
-        room_participants(&room_b, cx_b),
-        RoomParticipants {
-            remote: Default::default(),
-            pending: Default::default()
-        }
-    );
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_server_restarts(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-    cx_c: &mut TestAppContext,
-    cx_d: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    client_a
-        .fs()
-        .insert_tree("/a", json!({ "a.txt": "a-contents" }))
-        .await;
-
-    // Invite client B to collaborate on a project
-    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
-
-    let client_b = server.create_client(cx_b, "user_b").await;
-    let client_c = server.create_client(cx_c, "user_c").await;
-    let client_d = server.create_client(cx_d, "user_d").await;
-    server
-        .make_contacts(&mut [
-            (&client_a, cx_a),
-            (&client_b, cx_b),
-            (&client_c, cx_c),
-            (&client_d, cx_d),
-        ])
-        .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);
-    let active_call_d = cx_d.read(ActiveCall::global);
-
-    // User A calls users B, C, and D.
-    active_call_a
-        .update(cx_a, |call, cx| {
-            call.invite(client_b.user_id().unwrap(), Some(project_a.clone()), cx)
-        })
-        .await
-        .unwrap();
-    active_call_a
-        .update(cx_a, |call, cx| {
-            call.invite(client_c.user_id().unwrap(), Some(project_a.clone()), cx)
-        })
-        .await
-        .unwrap();
-    active_call_a
-        .update(cx_a, |call, cx| {
-            call.invite(client_d.user_id().unwrap(), Some(project_a.clone()), cx)
-        })
-        .await
-        .unwrap();
-
-    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
-
-    // User B receives the call and joins the room.
-
-    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
-    assert!(incoming_call_b.next().await.unwrap().is_some());
-    active_call_b
-        .update(cx_b, |call, cx| call.accept_incoming(cx))
-        .await
-        .unwrap();
-
-    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
-
-    // User C receives the call and joins the room.
-
-    let mut incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
-    assert!(incoming_call_c.next().await.unwrap().is_some());
-    active_call_c
-        .update(cx_c, |call, cx| call.accept_incoming(cx))
-        .await
-        .unwrap();
-
-    let room_c = active_call_c.read_with(cx_c, |call, _| call.room().unwrap().clone());
-
-    // User D receives the call but doesn't join the room yet.
-
-    let mut incoming_call_d = active_call_d.read_with(cx_d, |call, _| call.incoming());
-    assert!(incoming_call_d.next().await.unwrap().is_some());
-
-    executor.run_until_parked();
-    assert_eq!(
-        room_participants(&room_a, cx_a),
-        RoomParticipants {
-            remote: vec!["user_b".to_string(), "user_c".to_string()],
-            pending: vec!["user_d".to_string()]
-        }
-    );
-    assert_eq!(
-        room_participants(&room_b, cx_b),
-        RoomParticipants {
-            remote: vec!["user_a".to_string(), "user_c".to_string()],
-            pending: vec!["user_d".to_string()]
-        }
-    );
-    assert_eq!(
-        room_participants(&room_c, cx_c),
-        RoomParticipants {
-            remote: vec!["user_a".to_string(), "user_b".to_string()],
-            pending: vec!["user_d".to_string()]
-        }
-    );
-
-    // The server is torn down.
-    server.reset().await;
-
-    // Users A and B reconnect to the call. User C has troubles reconnecting, so it leaves the room.
-    client_c.override_establish_connection(|_, cx| cx.spawn(|_| future::pending()));
-    executor.advance_clock(RECONNECT_TIMEOUT);
-    assert_eq!(
-        room_participants(&room_a, cx_a),
-        RoomParticipants {
-            remote: vec!["user_b".to_string(), "user_c".to_string()],
-            pending: vec!["user_d".to_string()]
-        }
-    );
-    assert_eq!(
-        room_participants(&room_b, cx_b),
-        RoomParticipants {
-            remote: vec!["user_a".to_string(), "user_c".to_string()],
-            pending: vec!["user_d".to_string()]
-        }
-    );
-    assert_eq!(
-        room_participants(&room_c, cx_c),
-        RoomParticipants {
-            remote: vec![],
-            pending: vec![]
-        }
-    );
-
-    // User D is notified again of the incoming call and accepts it.
-    assert!(incoming_call_d.next().await.unwrap().is_some());
-    active_call_d
-        .update(cx_d, |call, cx| call.accept_incoming(cx))
-        .await
-        .unwrap();
-    executor.run_until_parked();
-
-    let room_d = active_call_d.read_with(cx_d, |call, _| call.room().unwrap().clone());
-    assert_eq!(
-        room_participants(&room_a, cx_a),
-        RoomParticipants {
-            remote: vec![
-                "user_b".to_string(),
-                "user_c".to_string(),
-                "user_d".to_string(),
-            ],
-            pending: vec![]
-        }
-    );
-    assert_eq!(
-        room_participants(&room_b, cx_b),
-        RoomParticipants {
-            remote: vec![
-                "user_a".to_string(),
-                "user_c".to_string(),
-                "user_d".to_string(),
-            ],
-            pending: vec![]
-        }
-    );
-    assert_eq!(
-        room_participants(&room_c, cx_c),
-        RoomParticipants {
-            remote: vec![],
-            pending: vec![]
-        }
-    );
-    assert_eq!(
-        room_participants(&room_d, cx_d),
-        RoomParticipants {
-            remote: vec![
-                "user_a".to_string(),
-                "user_b".to_string(),
-                "user_c".to_string(),
-            ],
-            pending: vec![]
-        }
-    );
-
-    // The server finishes restarting, cleaning up stale connections.
-    server.start().await.unwrap();
-    executor.advance_clock(CLEANUP_TIMEOUT);
-    assert_eq!(
-        room_participants(&room_a, cx_a),
-        RoomParticipants {
-            remote: vec!["user_b".to_string(), "user_d".to_string()],
-            pending: vec![]
-        }
-    );
-    assert_eq!(
-        room_participants(&room_b, cx_b),
-        RoomParticipants {
-            remote: vec!["user_a".to_string(), "user_d".to_string()],
-            pending: vec![]
-        }
-    );
-    assert_eq!(
-        room_participants(&room_c, cx_c),
-        RoomParticipants {
-            remote: vec![],
-            pending: vec![]
-        }
-    );
-    assert_eq!(
-        room_participants(&room_d, cx_d),
-        RoomParticipants {
-            remote: vec!["user_a".to_string(), "user_b".to_string()],
-            pending: vec![]
-        }
-    );
-
-    // User D hangs up.
-    active_call_d
-        .update(cx_d, |call, cx| call.hang_up(cx))
-        .await
-        .unwrap();
-    executor.run_until_parked();
-    assert_eq!(
-        room_participants(&room_a, cx_a),
-        RoomParticipants {
-            remote: vec!["user_b".to_string()],
-            pending: vec![]
-        }
-    );
-    assert_eq!(
-        room_participants(&room_b, cx_b),
-        RoomParticipants {
-            remote: vec!["user_a".to_string()],
-            pending: vec![]
-        }
-    );
-    assert_eq!(
-        room_participants(&room_c, cx_c),
-        RoomParticipants {
-            remote: vec![],
-            pending: vec![]
-        }
-    );
-    assert_eq!(
-        room_participants(&room_d, cx_d),
-        RoomParticipants {
-            remote: vec![],
-            pending: vec![]
-        }
-    );
-
-    // User B calls user D again.
-    active_call_b
-        .update(cx_b, |call, cx| {
-            call.invite(client_d.user_id().unwrap(), None, cx)
-        })
-        .await
-        .unwrap();
-
-    // User D receives the call but doesn't join the room yet.
-
-    let mut incoming_call_d = active_call_d.read_with(cx_d, |call, _| call.incoming());
-    assert!(incoming_call_d.next().await.unwrap().is_some());
-    executor.run_until_parked();
-    assert_eq!(
-        room_participants(&room_a, cx_a),
-        RoomParticipants {
-            remote: vec!["user_b".to_string()],
-            pending: vec!["user_d".to_string()]
-        }
-    );
-    assert_eq!(
-        room_participants(&room_b, cx_b),
-        RoomParticipants {
-            remote: vec!["user_a".to_string()],
-            pending: vec!["user_d".to_string()]
-        }
-    );
-
-    // The server is torn down.
-    server.reset().await;
-
-    // Users A and B have troubles reconnecting, so they leave the room.
-    client_a.override_establish_connection(|_, cx| cx.spawn(|_| future::pending()));
-    client_b.override_establish_connection(|_, cx| cx.spawn(|_| future::pending()));
-    client_c.override_establish_connection(|_, cx| cx.spawn(|_| future::pending()));
-    executor.advance_clock(RECONNECT_TIMEOUT);
-    assert_eq!(
-        room_participants(&room_a, cx_a),
-        RoomParticipants {
-            remote: vec![],
-            pending: vec![]
-        }
-    );
-    assert_eq!(
-        room_participants(&room_b, cx_b),
-        RoomParticipants {
-            remote: vec![],
-            pending: vec![]
-        }
-    );
-
-    // User D is notified again of the incoming call but doesn't accept it.
-    assert!(incoming_call_d.next().await.unwrap().is_some());
-
-    // The server finishes restarting, cleaning up stale connections and canceling the
-    // call to user D because the room has become empty.
-    server.start().await.unwrap();
-    executor.advance_clock(CLEANUP_TIMEOUT);
-    assert!(incoming_call_d.next().await.unwrap().is_none());
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_calls_on_multiple_connections(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b1: &mut TestAppContext,
-    cx_b2: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b1 = server.create_client(cx_b1, "user_b").await;
-    let client_b2 = server.create_client(cx_b2, "user_b").await;
-    server
-        .make_contacts(&mut [(&client_a, cx_a), (&client_b1, cx_b1)])
-        .await;
-
-    let active_call_a = cx_a.read(ActiveCall::global);
-    let active_call_b1 = cx_b1.read(ActiveCall::global);
-    let active_call_b2 = cx_b2.read(ActiveCall::global);
-
-    let mut incoming_call_b1 = active_call_b1.read_with(cx_b1, |call, _| call.incoming());
-
-    let mut incoming_call_b2 = active_call_b2.read_with(cx_b2, |call, _| call.incoming());
-    assert!(incoming_call_b1.next().await.unwrap().is_none());
-    assert!(incoming_call_b2.next().await.unwrap().is_none());
-
-    // Call user B from client A, ensuring both clients for user B ring.
-    active_call_a
-        .update(cx_a, |call, cx| {
-            call.invite(client_b1.user_id().unwrap(), None, cx)
-        })
-        .await
-        .unwrap();
-    executor.run_until_parked();
-    assert!(incoming_call_b1.next().await.unwrap().is_some());
-    assert!(incoming_call_b2.next().await.unwrap().is_some());
-
-    // User B declines the call on one of the two connections, causing both connections
-    // to stop ringing.
-    active_call_b2.update(cx_b2, |call, cx| call.decline_incoming(cx).unwrap());
-    executor.run_until_parked();
-    assert!(incoming_call_b1.next().await.unwrap().is_none());
-    assert!(incoming_call_b2.next().await.unwrap().is_none());
-
-    // Call user B again from client A.
-    active_call_a
-        .update(cx_a, |call, cx| {
-            call.invite(client_b1.user_id().unwrap(), None, cx)
-        })
-        .await
-        .unwrap();
-    executor.run_until_parked();
-    assert!(incoming_call_b1.next().await.unwrap().is_some());
-    assert!(incoming_call_b2.next().await.unwrap().is_some());
-
-    // User B accepts the call on one of the two connections, causing both connections
-    // to stop ringing.
-    active_call_b2
-        .update(cx_b2, |call, cx| call.accept_incoming(cx))
-        .await
-        .unwrap();
-    executor.run_until_parked();
-    assert!(incoming_call_b1.next().await.unwrap().is_none());
-    assert!(incoming_call_b2.next().await.unwrap().is_none());
-
-    // User B disconnects the client that is not on the call. Everything should be fine.
-    client_b1.disconnect(&cx_b1.to_async());
-    executor.advance_clock(RECEIVE_TIMEOUT);
-    client_b1
-        .authenticate_and_connect(false, &cx_b1.to_async())
-        .await
-        .unwrap();
-
-    // User B hangs up, and user A calls them again.
-    active_call_b2
-        .update(cx_b2, |call, cx| call.hang_up(cx))
-        .await
-        .unwrap();
-    executor.run_until_parked();
-    active_call_a
-        .update(cx_a, |call, cx| {
-            call.invite(client_b1.user_id().unwrap(), None, cx)
-        })
-        .await
-        .unwrap();
-    executor.run_until_parked();
-    assert!(incoming_call_b1.next().await.unwrap().is_some());
-    assert!(incoming_call_b2.next().await.unwrap().is_some());
-
-    // User A cancels the call, causing both connections to stop ringing.
-    active_call_a
-        .update(cx_a, |call, cx| {
-            call.cancel_invite(client_b1.user_id().unwrap(), cx)
-        })
-        .await
-        .unwrap();
-    executor.run_until_parked();
-    assert!(incoming_call_b1.next().await.unwrap().is_none());
-    assert!(incoming_call_b2.next().await.unwrap().is_none());
-
-    // User A calls user B again.
-    active_call_a
-        .update(cx_a, |call, cx| {
-            call.invite(client_b1.user_id().unwrap(), None, cx)
-        })
-        .await
-        .unwrap();
-    executor.run_until_parked();
-    assert!(incoming_call_b1.next().await.unwrap().is_some());
-    assert!(incoming_call_b2.next().await.unwrap().is_some());
-
-    // User A hangs up, causing both connections to stop ringing.
-    active_call_a
-        .update(cx_a, |call, cx| call.hang_up(cx))
-        .await
-        .unwrap();
-    executor.run_until_parked();
-    assert!(incoming_call_b1.next().await.unwrap().is_none());
-    assert!(incoming_call_b2.next().await.unwrap().is_none());
-
-    // User A calls user B again.
-    active_call_a
-        .update(cx_a, |call, cx| {
-            call.invite(client_b1.user_id().unwrap(), None, cx)
-        })
-        .await
-        .unwrap();
-    executor.run_until_parked();
-    assert!(incoming_call_b1.next().await.unwrap().is_some());
-    assert!(incoming_call_b2.next().await.unwrap().is_some());
-
-    // User A disconnects, causing both connections to stop ringing.
-    server.forbid_connections();
-    server.disconnect_client(client_a.peer_id().unwrap());
-    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
-    assert!(incoming_call_b1.next().await.unwrap().is_none());
-    assert!(incoming_call_b2.next().await.unwrap().is_none());
-
-    // User A reconnects automatically, then calls user B again.
-    server.allow_connections();
-    executor.advance_clock(RECEIVE_TIMEOUT);
-    active_call_a
-        .update(cx_a, |call, cx| {
-            call.invite(client_b1.user_id().unwrap(), None, cx)
-        })
-        .await
-        .unwrap();
-    executor.run_until_parked();
-    assert!(incoming_call_b1.next().await.unwrap().is_some());
-    assert!(incoming_call_b2.next().await.unwrap().is_some());
-
-    // User B disconnects all clients, causing user A to no longer see a pending call for them.
-    server.forbid_connections();
-    server.disconnect_client(client_b1.peer_id().unwrap());
-    server.disconnect_client(client_b2.peer_id().unwrap());
-    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
-
-    active_call_a.read_with(cx_a, |call, _| assert!(call.room().is_none()));
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_unshare_project(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-    cx_c: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    let 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;
-
-    let active_call_a = cx_a.read(ActiveCall::global);
-    let active_call_b = cx_b.read(ActiveCall::global);
-
-    client_a
-        .fs()
-        .insert_tree(
-            "/a",
-            json!({
-                "a.txt": "a-contents",
-                "b.txt": "b-contents",
-            }),
-        )
-        .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 worktree_a = project_a.read_with(cx_a, |project, _| project.worktrees().next().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()));
-
-    project_b
-        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
-        .await
-        .unwrap();
-
-    // When client B leaves the room, the project becomes read-only.
-    active_call_b
-        .update(cx_b, |call, cx| call.hang_up(cx))
-        .await
-        .unwrap();
-    executor.run_until_parked();
-
-    assert!(project_b.read_with(cx_b, |project, _| project.is_read_only()));
-
-    // Client C opens the project.
-    let project_c = client_c.build_remote_project(project_id, cx_c).await;
-
-    // When client A unshares the project, client C's project becomes read-only.
-    project_a
-        .update(cx_a, |project, cx| project.unshare(cx))
-        .unwrap();
-    executor.run_until_parked();
-
-    assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
-
-    assert!(project_c.read_with(cx_c, |project, _| project.is_read_only()));
-
-    // Client C can open the project again after client A re-shares.
-    let project_id = active_call_a
-        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-        .await
-        .unwrap();
-    let project_c2 = client_c.build_remote_project(project_id, cx_c).await;
-    executor.run_until_parked();
-
-    assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
-    project_c2
-        .update(cx_c, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
-        .await
-        .unwrap();
-
-    // When client A (the host) leaves the room, the project gets unshared and guests are notified.
-    active_call_a
-        .update(cx_a, |call, cx| call.hang_up(cx))
-        .await
-        .unwrap();
-    executor.run_until_parked();
-
-    project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
-
-    project_c2.read_with(cx_c, |project, _| {
-        assert!(project.is_read_only());
-        assert!(project.collaborators().is_empty());
-    });
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_project_reconnect(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    server
-        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-        .await;
-
-    cx_b.update(editor::init);
-
-    client_a
-        .fs()
-        .insert_tree(
-            "/root-1",
-            json!({
-                "dir1": {
-                    "a.txt": "a",
-                    "b.txt": "b",
-                    "subdir1": {
-                        "c.txt": "c",
-                        "d.txt": "d",
-                        "e.txt": "e",
-                    }
-                },
-                "dir2": {
-                    "v.txt": "v",
-                },
-                "dir3": {
-                    "w.txt": "w",
-                    "x.txt": "x",
-                    "y.txt": "y",
-                },
-                "dir4": {
-                    "z.txt": "z",
-                },
-            }),
-        )
-        .await;
-    client_a
-        .fs()
-        .insert_tree(
-            "/root-2",
-            json!({
-                "2.txt": "2",
-            }),
-        )
-        .await;
-    client_a
-        .fs()
-        .insert_tree(
-            "/root-3",
-            json!({
-                "3.txt": "3",
-            }),
-        )
-        .await;
-
-    let active_call_a = cx_a.read(ActiveCall::global);
-    let (project_a1, _) = client_a.build_local_project("/root-1/dir1", cx_a).await;
-    let (project_a2, _) = client_a.build_local_project("/root-2", cx_a).await;
-    let (project_a3, _) = client_a.build_local_project("/root-3", cx_a).await;
-    let worktree_a1 = project_a1.read_with(cx_a, |project, _| project.worktrees().next().unwrap());
-    let project1_id = active_call_a
-        .update(cx_a, |call, cx| call.share_project(project_a1.clone(), cx))
-        .await
-        .unwrap();
-    let project2_id = active_call_a
-        .update(cx_a, |call, cx| call.share_project(project_a2.clone(), cx))
-        .await
-        .unwrap();
-    let project3_id = active_call_a
-        .update(cx_a, |call, cx| call.share_project(project_a3.clone(), cx))
-        .await
-        .unwrap();
-
-    let project_b1 = client_b.build_remote_project(project1_id, cx_b).await;
-    let project_b2 = client_b.build_remote_project(project2_id, cx_b).await;
-    let project_b3 = client_b.build_remote_project(project3_id, cx_b).await;
-    executor.run_until_parked();
-
-    let worktree1_id = worktree_a1.read_with(cx_a, |worktree, _| {
-        assert!(worktree.as_local().unwrap().is_shared());
-        worktree.id()
-    });
-    let (worktree_a2, _) = project_a1
-        .update(cx_a, |p, cx| {
-            p.find_or_create_local_worktree("/root-1/dir2", true, cx)
-        })
-        .await
-        .unwrap();
-    executor.run_until_parked();
-
-    let worktree2_id = worktree_a2.read_with(cx_a, |tree, _| {
-        assert!(tree.as_local().unwrap().is_shared());
-        tree.id()
-    });
-    executor.run_until_parked();
-
-    project_b1.read_with(cx_b, |project, cx| {
-        assert!(project.worktree_for_id(worktree2_id, cx).is_some())
-    });
-
-    let buffer_a1 = project_a1
-        .update(cx_a, |p, cx| p.open_buffer((worktree1_id, "a.txt"), cx))
-        .await
-        .unwrap();
-    let buffer_b1 = project_b1
-        .update(cx_b, |p, cx| p.open_buffer((worktree1_id, "a.txt"), cx))
-        .await
-        .unwrap();
-
-    // Drop client A's connection.
-    server.forbid_connections();
-    server.disconnect_client(client_a.peer_id().unwrap());
-    executor.advance_clock(RECEIVE_TIMEOUT);
-
-    project_a1.read_with(cx_a, |project, _| {
-        assert!(project.is_shared());
-        assert_eq!(project.collaborators().len(), 1);
-    });
-
-    project_b1.read_with(cx_b, |project, _| {
-        assert!(!project.is_read_only());
-        assert_eq!(project.collaborators().len(), 1);
-    });
-
-    worktree_a1.read_with(cx_a, |tree, _| {
-        assert!(tree.as_local().unwrap().is_shared())
-    });
-
-    // While client A is disconnected, add and remove files from client A's project.
-    client_a
-        .fs()
-        .insert_tree(
-            "/root-1/dir1/subdir2",
-            json!({
-                "f.txt": "f-contents",
-                "g.txt": "g-contents",
-                "h.txt": "h-contents",
-                "i.txt": "i-contents",
-            }),
-        )
-        .await;
-    client_a
-        .fs()
-        .remove_dir(
-            "/root-1/dir1/subdir1".as_ref(),
-            RemoveOptions {
-                recursive: true,
-                ..Default::default()
-            },
-        )
-        .await
-        .unwrap();
-
-    // While client A is disconnected, add and remove worktrees from client A's project.
-    project_a1.update(cx_a, |project, cx| {
-        project.remove_worktree(worktree2_id, cx)
-    });
-    let (worktree_a3, _) = project_a1
-        .update(cx_a, |p, cx| {
-            p.find_or_create_local_worktree("/root-1/dir3", true, cx)
-        })
-        .await
-        .unwrap();
-    worktree_a3
-        .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
-        .await;
-
-    let worktree3_id = worktree_a3.read_with(cx_a, |tree, _| {
-        assert!(!tree.as_local().unwrap().is_shared());
-        tree.id()
-    });
-    executor.run_until_parked();
-
-    // While client A is disconnected, close project 2
-    cx_a.update(|_| drop(project_a2));
-
-    // While client A is disconnected, mutate a buffer on both the host and the guest.
-    buffer_a1.update(cx_a, |buf, cx| buf.edit([(0..0, "W")], None, cx));
-    buffer_b1.update(cx_b, |buf, cx| buf.edit([(1..1, "Z")], None, cx));
-    executor.run_until_parked();
-
-    // Client A reconnects. Their project is re-shared, and client B re-joins it.
-    server.allow_connections();
-    client_a
-        .authenticate_and_connect(false, &cx_a.to_async())
-        .await
-        .unwrap();
-    executor.run_until_parked();
-
-    project_a1.read_with(cx_a, |project, cx| {
-        assert!(project.is_shared());
-        assert!(worktree_a1.read(cx).as_local().unwrap().is_shared());
-        assert_eq!(
-            worktree_a1
-                .read(cx)
-                .snapshot()
-                .paths()
-                .map(|p| p.to_str().unwrap())
-                .collect::<Vec<_>>(),
-            vec![
-                "a.txt",
-                "b.txt",
-                "subdir2",
-                "subdir2/f.txt",
-                "subdir2/g.txt",
-                "subdir2/h.txt",
-                "subdir2/i.txt"
-            ]
-        );
-        assert!(worktree_a3.read(cx).as_local().unwrap().is_shared());
-        assert_eq!(
-            worktree_a3
-                .read(cx)
-                .snapshot()
-                .paths()
-                .map(|p| p.to_str().unwrap())
-                .collect::<Vec<_>>(),
-            vec!["w.txt", "x.txt", "y.txt"]
-        );
-    });
-
-    project_b1.read_with(cx_b, |project, cx| {
-        assert!(!project.is_read_only());
-        assert_eq!(
-            project
-                .worktree_for_id(worktree1_id, cx)
-                .unwrap()
-                .read(cx)
-                .snapshot()
-                .paths()
-                .map(|p| p.to_str().unwrap())
-                .collect::<Vec<_>>(),
-            vec![
-                "a.txt",
-                "b.txt",
-                "subdir2",
-                "subdir2/f.txt",
-                "subdir2/g.txt",
-                "subdir2/h.txt",
-                "subdir2/i.txt"
-            ]
-        );
-        assert!(project.worktree_for_id(worktree2_id, cx).is_none());
-        assert_eq!(
-            project
-                .worktree_for_id(worktree3_id, cx)
-                .unwrap()
-                .read(cx)
-                .snapshot()
-                .paths()
-                .map(|p| p.to_str().unwrap())
-                .collect::<Vec<_>>(),
-            vec!["w.txt", "x.txt", "y.txt"]
-        );
-    });
-
-    project_b2.read_with(cx_b, |project, _| assert!(project.is_read_only()));
-
-    project_b3.read_with(cx_b, |project, _| assert!(!project.is_read_only()));
-
-    buffer_a1.read_with(cx_a, |buffer, _| assert_eq!(buffer.text(), "WaZ"));
-
-    buffer_b1.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "WaZ"));
-
-    // Drop client B's connection.
-    server.forbid_connections();
-    server.disconnect_client(client_b.peer_id().unwrap());
-    executor.advance_clock(RECEIVE_TIMEOUT);
-
-    // While client B is disconnected, add and remove files from client A's project
-    client_a
-        .fs()
-        .insert_file("/root-1/dir1/subdir2/j.txt", "j-contents".into())
-        .await;
-    client_a
-        .fs()
-        .remove_file("/root-1/dir1/subdir2/i.txt".as_ref(), Default::default())
-        .await
-        .unwrap();
-
-    // While client B is disconnected, add and remove worktrees from client A's project.
-    let (worktree_a4, _) = project_a1
-        .update(cx_a, |p, cx| {
-            p.find_or_create_local_worktree("/root-1/dir4", true, cx)
-        })
-        .await
-        .unwrap();
-    executor.run_until_parked();
-
-    let worktree4_id = worktree_a4.read_with(cx_a, |tree, _| {
-        assert!(tree.as_local().unwrap().is_shared());
-        tree.id()
-    });
-    project_a1.update(cx_a, |project, cx| {
-        project.remove_worktree(worktree3_id, cx)
-    });
-    executor.run_until_parked();
-
-    // While client B is disconnected, mutate a buffer on both the host and the guest.
-    buffer_a1.update(cx_a, |buf, cx| buf.edit([(1..1, "X")], None, cx));
-    buffer_b1.update(cx_b, |buf, cx| buf.edit([(2..2, "Y")], None, cx));
-    executor.run_until_parked();
-
-    // While disconnected, close project 3
-    cx_a.update(|_| drop(project_a3));
-
-    // Client B reconnects. They re-join the room and the remaining shared project.
-    server.allow_connections();
-    client_b
-        .authenticate_and_connect(false, &cx_b.to_async())
-        .await
-        .unwrap();
-    executor.run_until_parked();
-
-    project_b1.read_with(cx_b, |project, cx| {
-        assert!(!project.is_read_only());
-        assert_eq!(
-            project
-                .worktree_for_id(worktree1_id, cx)
-                .unwrap()
-                .read(cx)
-                .snapshot()
-                .paths()
-                .map(|p| p.to_str().unwrap())
-                .collect::<Vec<_>>(),
-            vec![
-                "a.txt",
-                "b.txt",
-                "subdir2",
-                "subdir2/f.txt",
-                "subdir2/g.txt",
-                "subdir2/h.txt",
-                "subdir2/j.txt"
-            ]
-        );
-        assert!(project.worktree_for_id(worktree2_id, cx).is_none());
-        assert_eq!(
-            project
-                .worktree_for_id(worktree4_id, cx)
-                .unwrap()
-                .read(cx)
-                .snapshot()
-                .paths()
-                .map(|p| p.to_str().unwrap())
-                .collect::<Vec<_>>(),
-            vec!["z.txt"]
-        );
-    });
-
-    project_b3.read_with(cx_b, |project, _| assert!(project.is_read_only()));
-
-    buffer_a1.read_with(cx_a, |buffer, _| assert_eq!(buffer.text(), "WXaYZ"));
-
-    buffer_b1.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "WXaYZ"));
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_active_call_events(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    client_a.fs().insert_tree("/a", json!({})).await;
-    client_b.fs().insert_tree("/b", json!({})).await;
-
-    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
-    let (project_b, _) = client_b.build_local_project("/b", cx_b).await;
-
-    server
-        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-        .await;
-    let active_call_a = cx_a.read(ActiveCall::global);
-    let active_call_b = cx_b.read(ActiveCall::global);
-
-    let events_a = active_call_events(cx_a);
-    let events_b = active_call_events(cx_b);
-
-    let project_a_id = active_call_a
-        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-        .await
-        .unwrap();
-    executor.run_until_parked();
-    assert_eq!(mem::take(&mut *events_a.borrow_mut()), vec![]);
-    assert_eq!(
-        mem::take(&mut *events_b.borrow_mut()),
-        vec![room::Event::RemoteProjectShared {
-            owner: Arc::new(User {
-                id: client_a.user_id().unwrap(),
-                github_login: "user_a".to_string(),
-                avatar_uri: "avatar_a".into(),
-            }),
-            project_id: project_a_id,
-            worktree_root_names: vec!["a".to_string()],
-        }]
-    );
-
-    let project_b_id = active_call_b
-        .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
-        .await
-        .unwrap();
-    executor.run_until_parked();
-    assert_eq!(
-        mem::take(&mut *events_a.borrow_mut()),
-        vec![room::Event::RemoteProjectShared {
-            owner: Arc::new(User {
-                id: client_b.user_id().unwrap(),
-                github_login: "user_b".to_string(),
-                avatar_uri: "avatar_b".into(),
-            }),
-            project_id: project_b_id,
-            worktree_root_names: vec!["b".to_string()]
-        }]
-    );
-    assert_eq!(mem::take(&mut *events_b.borrow_mut()), vec![]);
-
-    // Sharing a project twice is idempotent.
-    let project_b_id_2 = active_call_b
-        .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
-        .await
-        .unwrap();
-    assert_eq!(project_b_id_2, project_b_id);
-    executor.run_until_parked();
-    assert_eq!(mem::take(&mut *events_a.borrow_mut()), vec![]);
-    assert_eq!(mem::take(&mut *events_b.borrow_mut()), vec![]);
-}
-
-fn active_call_events(cx: &mut TestAppContext) -> Rc<RefCell<Vec<room::Event>>> {
-    let events = Rc::new(RefCell::new(Vec::new()));
-    let active_call = cx.read(ActiveCall::global);
-    cx.update({
-        let events = events.clone();
-        |cx| {
-            cx.subscribe(&active_call, move |_, event, _| {
-                events.borrow_mut().push(event.clone())
-            })
-            .detach()
-        }
-    });
-    events
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_room_location(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    client_a.fs().insert_tree("/a", json!({})).await;
-    client_b.fs().insert_tree("/b", json!({})).await;
-
-    let active_call_a = cx_a.read(ActiveCall::global);
-    let active_call_b = cx_b.read(ActiveCall::global);
-
-    let a_notified = Rc::new(Cell::new(false));
-    cx_a.update({
-        let notified = a_notified.clone();
-        |cx| {
-            cx.observe(&active_call_a, move |_, _| notified.set(true))
-                .detach()
-        }
-    });
-
-    let b_notified = Rc::new(Cell::new(false));
-    cx_b.update({
-        let b_notified = b_notified.clone();
-        |cx| {
-            cx.observe(&active_call_b, move |_, _| b_notified.set(true))
-                .detach()
-        }
-    });
-
-    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
-    active_call_a
-        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
-        .await
-        .unwrap();
-    let (project_b, _) = client_b.build_local_project("/b", cx_b).await;
-
-    server
-        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-        .await;
-
-    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
-
-    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
-    executor.run_until_parked();
-    assert!(a_notified.take());
-    assert_eq!(
-        participant_locations(&room_a, cx_a),
-        vec![("user_b".to_string(), ParticipantLocation::External)]
-    );
-    assert!(b_notified.take());
-    assert_eq!(
-        participant_locations(&room_b, cx_b),
-        vec![("user_a".to_string(), ParticipantLocation::UnsharedProject)]
-    );
-
-    let project_a_id = active_call_a
-        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-        .await
-        .unwrap();
-    executor.run_until_parked();
-    assert!(a_notified.take());
-    assert_eq!(
-        participant_locations(&room_a, cx_a),
-        vec![("user_b".to_string(), ParticipantLocation::External)]
-    );
-    assert!(b_notified.take());
-    assert_eq!(
-        participant_locations(&room_b, cx_b),
-        vec![(
-            "user_a".to_string(),
-            ParticipantLocation::SharedProject {
-                project_id: project_a_id
-            }
-        )]
-    );
-
-    let project_b_id = active_call_b
-        .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
-        .await
-        .unwrap();
-    executor.run_until_parked();
-    assert!(a_notified.take());
-    assert_eq!(
-        participant_locations(&room_a, cx_a),
-        vec![("user_b".to_string(), ParticipantLocation::External)]
-    );
-    assert!(b_notified.take());
-    assert_eq!(
-        participant_locations(&room_b, cx_b),
-        vec![(
-            "user_a".to_string(),
-            ParticipantLocation::SharedProject {
-                project_id: project_a_id
-            }
-        )]
-    );
-
-    active_call_b
-        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
-        .await
-        .unwrap();
-    executor.run_until_parked();
-    assert!(a_notified.take());
-    assert_eq!(
-        participant_locations(&room_a, cx_a),
-        vec![(
-            "user_b".to_string(),
-            ParticipantLocation::SharedProject {
-                project_id: project_b_id
-            }
-        )]
-    );
-    assert!(b_notified.take());
-    assert_eq!(
-        participant_locations(&room_b, cx_b),
-        vec![(
-            "user_a".to_string(),
-            ParticipantLocation::SharedProject {
-                project_id: project_a_id
-            }
-        )]
-    );
-
-    active_call_b
-        .update(cx_b, |call, cx| call.set_location(None, cx))
-        .await
-        .unwrap();
-    executor.run_until_parked();
-    assert!(a_notified.take());
-    assert_eq!(
-        participant_locations(&room_a, cx_a),
-        vec![("user_b".to_string(), ParticipantLocation::External)]
-    );
-    assert!(b_notified.take());
-    assert_eq!(
-        participant_locations(&room_b, cx_b),
-        vec![(
-            "user_a".to_string(),
-            ParticipantLocation::SharedProject {
-                project_id: project_a_id
-            }
-        )]
-    );
-
-    fn participant_locations(
-        room: &Model<Room>,
-        cx: &TestAppContext,
-    ) -> Vec<(String, ParticipantLocation)> {
-        room.read_with(cx, |room, _| {
-            room.remote_participants()
-                .values()
-                .map(|participant| {
-                    (
-                        participant.user.github_login.to_string(),
-                        participant.location,
-                    )
-                })
-                .collect()
-        })
-    }
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_propagate_saves_and_fs_changes(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-    cx_c: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    let 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;
-    let active_call_a = cx_a.read(ActiveCall::global);
-
-    let rust = Arc::new(Language::new(
-        LanguageConfig {
-            name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
-            ..Default::default()
-        },
-        Some(tree_sitter_rust::language()),
-    ));
-    let javascript = Arc::new(Language::new(
-        LanguageConfig {
-            name: "JavaScript".into(),
-            path_suffixes: vec!["js".to_string()],
-            ..Default::default()
-        },
-        Some(tree_sitter_rust::language()),
-    ));
-    for client in [&client_a, &client_b, &client_c] {
-        client.language_registry().add(rust.clone());
-        client.language_registry().add(javascript.clone());
-    }
-
-    client_a
-        .fs()
-        .insert_tree(
-            "/a",
-            json!({
-                "file1.rs": "",
-                "file2": ""
-            }),
-        )
-        .await;
-    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
-
-    let worktree_a = project_a.read_with(cx_a, |p, _| p.worktrees().next().unwrap());
-    let project_id = active_call_a
-        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-        .await
-        .unwrap();
-
-    // Join that worktree as clients B and C.
-    let project_b = client_b.build_remote_project(project_id, cx_b).await;
-    let project_c = client_c.build_remote_project(project_id, cx_c).await;
-
-    let worktree_b = project_b.read_with(cx_b, |p, _| p.worktrees().next().unwrap());
-
-    let worktree_c = project_c.read_with(cx_c, |p, _| p.worktrees().next().unwrap());
-
-    // Open and edit a buffer as both guests B and C.
-    let buffer_b = project_b
-        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "file1.rs"), cx))
-        .await
-        .unwrap();
-    let buffer_c = project_c
-        .update(cx_c, |p, cx| p.open_buffer((worktree_id, "file1.rs"), cx))
-        .await
-        .unwrap();
-
-    buffer_b.read_with(cx_b, |buffer, _| {
-        assert_eq!(&*buffer.language().unwrap().name(), "Rust");
-    });
-
-    buffer_c.read_with(cx_c, |buffer, _| {
-        assert_eq!(&*buffer.language().unwrap().name(), "Rust");
-    });
-    buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "i-am-b, ")], None, cx));
-    buffer_c.update(cx_c, |buf, cx| buf.edit([(0..0, "i-am-c, ")], None, cx));
-
-    // Open and edit that buffer as the host.
-    let buffer_a = project_a
-        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "file1.rs"), cx))
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-
-    buffer_a.read_with(cx_a, |buf, _| assert_eq!(buf.text(), "i-am-c, i-am-b, "));
-    buffer_a.update(cx_a, |buf, cx| {
-        buf.edit([(buf.len()..buf.len(), "i-am-a")], None, cx)
-    });
-
-    executor.run_until_parked();
-
-    buffer_a.read_with(cx_a, |buf, _| {
-        assert_eq!(buf.text(), "i-am-c, i-am-b, i-am-a");
-    });
-
-    buffer_b.read_with(cx_b, |buf, _| {
-        assert_eq!(buf.text(), "i-am-c, i-am-b, i-am-a");
-    });
-
-    buffer_c.read_with(cx_c, |buf, _| {
-        assert_eq!(buf.text(), "i-am-c, i-am-b, i-am-a");
-    });
-
-    // Edit the buffer as the host and concurrently save as guest B.
-    let save_b = project_b.update(cx_b, |project, cx| {
-        project.save_buffer(buffer_b.clone(), cx)
-    });
-    buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "hi-a, ")], None, cx));
-    save_b.await.unwrap();
-    assert_eq!(
-        client_a.fs().load("/a/file1.rs".as_ref()).await.unwrap(),
-        "hi-a, i-am-c, i-am-b, i-am-a"
-    );
-
-    executor.run_until_parked();
-
-    buffer_a.read_with(cx_a, |buf, _| assert!(!buf.is_dirty()));
-
-    buffer_b.read_with(cx_b, |buf, _| assert!(!buf.is_dirty()));
-
-    buffer_c.read_with(cx_c, |buf, _| assert!(!buf.is_dirty()));
-
-    // Make changes on host's file system, see those changes on guest worktrees.
-    client_a
-        .fs()
-        .rename(
-            "/a/file1.rs".as_ref(),
-            "/a/file1.js".as_ref(),
-            Default::default(),
-        )
-        .await
-        .unwrap();
-    client_a
-        .fs()
-        .rename("/a/file2".as_ref(), "/a/file3".as_ref(), Default::default())
-        .await
-        .unwrap();
-    client_a.fs().insert_file("/a/file4", "4".into()).await;
-    executor.run_until_parked();
-
-    worktree_a.read_with(cx_a, |tree, _| {
-        assert_eq!(
-            tree.paths()
-                .map(|p| p.to_string_lossy())
-                .collect::<Vec<_>>(),
-            ["file1.js", "file3", "file4"]
-        )
-    });
-
-    worktree_b.read_with(cx_b, |tree, _| {
-        assert_eq!(
-            tree.paths()
-                .map(|p| p.to_string_lossy())
-                .collect::<Vec<_>>(),
-            ["file1.js", "file3", "file4"]
-        )
-    });
-
-    worktree_c.read_with(cx_c, |tree, _| {
-        assert_eq!(
-            tree.paths()
-                .map(|p| p.to_string_lossy())
-                .collect::<Vec<_>>(),
-            ["file1.js", "file3", "file4"]
-        )
-    });
-
-    // Ensure buffer files are updated as well.
-
-    buffer_a.read_with(cx_a, |buffer, _| {
-        assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
-        assert_eq!(&*buffer.language().unwrap().name(), "JavaScript");
-    });
-
-    buffer_b.read_with(cx_b, |buffer, _| {
-        assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
-        assert_eq!(&*buffer.language().unwrap().name(), "JavaScript");
-    });
-
-    buffer_c.read_with(cx_c, |buffer, _| {
-        assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
-        assert_eq!(&*buffer.language().unwrap().name(), "JavaScript");
-    });
-
-    let new_buffer_a = project_a
-        .update(cx_a, |p, cx| p.create_buffer("", None, cx))
-        .unwrap();
-
-    let new_buffer_id = new_buffer_a.read_with(cx_a, |buffer, _| buffer.remote_id());
-    let new_buffer_b = project_b
-        .update(cx_b, |p, cx| p.open_buffer_by_id(new_buffer_id, cx))
-        .await
-        .unwrap();
-
-    new_buffer_b.read_with(cx_b, |buffer, _| {
-        assert!(buffer.file().is_none());
-    });
-
-    new_buffer_a.update(cx_a, |buffer, cx| {
-        buffer.edit([(0..0, "ok")], None, cx);
-    });
-    project_a
-        .update(cx_a, |project, cx| {
-            project.save_buffer_as(new_buffer_a.clone(), "/a/file3.rs".into(), cx)
-        })
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-
-    new_buffer_b.read_with(cx_b, |buffer_b, _| {
-        assert_eq!(
-            buffer_b.file().unwrap().path().as_ref(),
-            Path::new("file3.rs")
-        );
-
-        new_buffer_a.read_with(cx_a, |buffer_a, _| {
-            assert_eq!(buffer_b.saved_mtime(), buffer_a.saved_mtime());
-            assert_eq!(buffer_b.saved_version(), buffer_a.saved_version());
-        });
-    });
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_git_diff_base_change(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    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!({
-            ".git": {},
-            "sub": {
-                ".git": {},
-                "b.txt": "
-                    one
-                    two
-                    three
-                ".unindent(),
-            },
-            "a.txt": "
-                    one
-                    two
-                    three
-                ".unindent(),
-            }),
-        )
-        .await;
-
-    let (project_local, 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_local.clone(), cx)
-        })
-        .await
-        .unwrap();
-
-    let project_remote = client_b.build_remote_project(project_id, cx_b).await;
-
-    let diff_base = "
-        one
-        three
-    "
-    .unindent();
-
-    let new_diff_base = "
-        one
-        two
-    "
-    .unindent();
-
-    client_a.fs().set_index_for_repo(
-        Path::new("/dir/.git"),
-        &[(Path::new("a.txt"), diff_base.clone())],
-    );
-
-    // Create the buffer
-    let buffer_local_a = project_local
-        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
-        .await
-        .unwrap();
-
-    // Wait for it to catch up to the new diff
-    executor.run_until_parked();
-
-    // Smoke test diffing
-
-    buffer_local_a.read_with(cx_a, |buffer, _| {
-        assert_eq!(buffer.diff_base(), Some(diff_base.as_ref()));
-        git::diff::assert_hunks(
-            buffer.snapshot().git_diff_hunks_in_row_range(0..4),
-            &buffer,
-            &diff_base,
-            &[(1..2, "", "two\n")],
-        );
-    });
-
-    // Create remote buffer
-    let buffer_remote_a = project_remote
-        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
-        .await
-        .unwrap();
-
-    // Wait remote buffer to catch up to the new diff
-    executor.run_until_parked();
-
-    // Smoke test diffing
-
-    buffer_remote_a.read_with(cx_b, |buffer, _| {
-        assert_eq!(buffer.diff_base(), Some(diff_base.as_ref()));
-        git::diff::assert_hunks(
-            buffer.snapshot().git_diff_hunks_in_row_range(0..4),
-            &buffer,
-            &diff_base,
-            &[(1..2, "", "two\n")],
-        );
-    });
-
-    client_a.fs().set_index_for_repo(
-        Path::new("/dir/.git"),
-        &[(Path::new("a.txt"), new_diff_base.clone())],
-    );
-
-    // Wait for buffer_local_a to receive it
-    executor.run_until_parked();
-
-    // Smoke test new diffing
-
-    buffer_local_a.read_with(cx_a, |buffer, _| {
-        assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref()));
-
-        git::diff::assert_hunks(
-            buffer.snapshot().git_diff_hunks_in_row_range(0..4),
-            &buffer,
-            &diff_base,
-            &[(2..3, "", "three\n")],
-        );
-    });
-
-    // Smoke test B
-
-    buffer_remote_a.read_with(cx_b, |buffer, _| {
-        assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref()));
-        git::diff::assert_hunks(
-            buffer.snapshot().git_diff_hunks_in_row_range(0..4),
-            &buffer,
-            &diff_base,
-            &[(2..3, "", "three\n")],
-        );
-    });
-
-    //Nested git dir
-
-    let diff_base = "
-        one
-        three
-    "
-    .unindent();
-
-    let new_diff_base = "
-        one
-        two
-    "
-    .unindent();
-
-    client_a.fs().set_index_for_repo(
-        Path::new("/dir/sub/.git"),
-        &[(Path::new("b.txt"), diff_base.clone())],
-    );
-
-    // Create the buffer
-    let buffer_local_b = project_local
-        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "sub/b.txt"), cx))
-        .await
-        .unwrap();
-
-    // Wait for it to catch up to the new diff
-    executor.run_until_parked();
-
-    // Smoke test diffing
-
-    buffer_local_b.read_with(cx_a, |buffer, _| {
-        assert_eq!(buffer.diff_base(), Some(diff_base.as_ref()));
-        git::diff::assert_hunks(
-            buffer.snapshot().git_diff_hunks_in_row_range(0..4),
-            &buffer,
-            &diff_base,
-            &[(1..2, "", "two\n")],
-        );
-    });
-
-    // Create remote buffer
-    let buffer_remote_b = project_remote
-        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "sub/b.txt"), cx))
-        .await
-        .unwrap();
-
-    // Wait remote buffer to catch up to the new diff
-    executor.run_until_parked();
-
-    // Smoke test diffing
-
-    buffer_remote_b.read_with(cx_b, |buffer, _| {
-        assert_eq!(buffer.diff_base(), Some(diff_base.as_ref()));
-        git::diff::assert_hunks(
-            buffer.snapshot().git_diff_hunks_in_row_range(0..4),
-            &buffer,
-            &diff_base,
-            &[(1..2, "", "two\n")],
-        );
-    });
-
-    client_a.fs().set_index_for_repo(
-        Path::new("/dir/sub/.git"),
-        &[(Path::new("b.txt"), new_diff_base.clone())],
-    );
-
-    // Wait for buffer_local_b to receive it
-    executor.run_until_parked();
-
-    // Smoke test new diffing
-
-    buffer_local_b.read_with(cx_a, |buffer, _| {
-        assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref()));
-        println!("{:?}", buffer.as_rope().to_string());
-        println!("{:?}", buffer.diff_base());
-        println!(
-            "{:?}",
-            buffer
-                .snapshot()
-                .git_diff_hunks_in_row_range(0..4)
-                .collect::<Vec<_>>()
-        );
-
-        git::diff::assert_hunks(
-            buffer.snapshot().git_diff_hunks_in_row_range(0..4),
-            &buffer,
-            &diff_base,
-            &[(2..3, "", "three\n")],
-        );
-    });
-
-    // Smoke test B
-
-    buffer_remote_b.read_with(cx_b, |buffer, _| {
-        assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref()));
-        git::diff::assert_hunks(
-            buffer.snapshot().git_diff_hunks_in_row_range(0..4),
-            &buffer,
-            &diff_base,
-            &[(2..3, "", "three\n")],
-        );
-    });
-}
-
-#[gpui::test]
-async fn test_git_branch_name(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-    cx_c: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    let 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;
-    let active_call_a = cx_a.read(ActiveCall::global);
-
-    client_a
-        .fs()
-        .insert_tree(
-            "/dir",
-            json!({
-            ".git": {},
-            }),
-        )
-        .await;
-
-    let (project_local, _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_local.clone(), cx)
-        })
-        .await
-        .unwrap();
-
-    let project_remote = client_b.build_remote_project(project_id, cx_b).await;
-    client_a
-        .fs()
-        .set_branch_name(Path::new("/dir/.git"), Some("branch-1"));
-
-    // Wait for it to catch up to the new branch
-    executor.run_until_parked();
-
-    #[track_caller]
-    fn assert_branch(branch_name: Option<impl Into<String>>, project: &Project, cx: &AppContext) {
-        let branch_name = branch_name.map(Into::into);
-        let worktrees = project.visible_worktrees(cx).collect::<Vec<_>>();
-        assert_eq!(worktrees.len(), 1);
-        let worktree = worktrees[0].clone();
-        let root_entry = worktree.read(cx).snapshot().root_git_entry().unwrap();
-        assert_eq!(root_entry.branch(), branch_name.map(Into::into));
-    }
-
-    // Smoke test branch reading
-
-    project_local.read_with(cx_a, |project, cx| {
-        assert_branch(Some("branch-1"), project, cx)
-    });
-
-    project_remote.read_with(cx_b, |project, cx| {
-        assert_branch(Some("branch-1"), project, cx)
-    });
-
-    client_a
-        .fs()
-        .set_branch_name(Path::new("/dir/.git"), Some("branch-2"));
-
-    // Wait for buffer_local_a to receive it
-    executor.run_until_parked();
-
-    // Smoke test branch reading
-
-    project_local.read_with(cx_a, |project, cx| {
-        assert_branch(Some("branch-2"), project, cx)
-    });
-
-    project_remote.read_with(cx_b, |project, cx| {
-        assert_branch(Some("branch-2"), project, cx)
-    });
-
-    let project_remote_c = client_c.build_remote_project(project_id, cx_c).await;
-    executor.run_until_parked();
-
-    project_remote_c.read_with(cx_c, |project, cx| {
-        assert_branch(Some("branch-2"), project, cx)
-    });
-}
-
-#[gpui::test]
-async fn test_git_status_sync(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-    cx_c: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    let 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;
-    let active_call_a = cx_a.read(ActiveCall::global);
-
-    client_a
-        .fs()
-        .insert_tree(
-            "/dir",
-            json!({
-            ".git": {},
-            "a.txt": "a",
-            "b.txt": "b",
-            }),
-        )
-        .await;
-
-    const A_TXT: &'static str = "a.txt";
-    const B_TXT: &'static str = "b.txt";
-
-    client_a.fs().set_status_for_repo_via_git_operation(
-        Path::new("/dir/.git"),
-        &[
-            (&Path::new(A_TXT), GitFileStatus::Added),
-            (&Path::new(B_TXT), GitFileStatus::Added),
-        ],
-    );
-
-    let (project_local, _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_local.clone(), cx)
-        })
-        .await
-        .unwrap();
-
-    let project_remote = client_b.build_remote_project(project_id, cx_b).await;
-
-    // Wait for it to catch up to the new status
-    executor.run_until_parked();
-
-    #[track_caller]
-    fn assert_status(
-        file: &impl AsRef<Path>,
-        status: Option<GitFileStatus>,
-        project: &Project,
-        cx: &AppContext,
-    ) {
-        let file = file.as_ref();
-        let worktrees = project.visible_worktrees(cx).collect::<Vec<_>>();
-        assert_eq!(worktrees.len(), 1);
-        let worktree = worktrees[0].clone();
-        let snapshot = worktree.read(cx).snapshot();
-        assert_eq!(snapshot.status_for_file(file), status);
-    }
-
-    // Smoke test status reading
-
-    project_local.read_with(cx_a, |project, cx| {
-        assert_status(&Path::new(A_TXT), Some(GitFileStatus::Added), project, cx);
-        assert_status(&Path::new(B_TXT), Some(GitFileStatus::Added), project, cx);
-    });
-
-    project_remote.read_with(cx_b, |project, cx| {
-        assert_status(&Path::new(A_TXT), Some(GitFileStatus::Added), project, cx);
-        assert_status(&Path::new(B_TXT), Some(GitFileStatus::Added), project, cx);
-    });
-
-    client_a.fs().set_status_for_repo_via_working_copy_change(
-        Path::new("/dir/.git"),
-        &[
-            (&Path::new(A_TXT), GitFileStatus::Modified),
-            (&Path::new(B_TXT), GitFileStatus::Modified),
-        ],
-    );
-
-    // Wait for buffer_local_a to receive it
-    executor.run_until_parked();
-
-    // Smoke test status reading
-
-    project_local.read_with(cx_a, |project, cx| {
-        assert_status(
-            &Path::new(A_TXT),
-            Some(GitFileStatus::Modified),
-            project,
-            cx,
-        );
-        assert_status(
-            &Path::new(B_TXT),
-            Some(GitFileStatus::Modified),
-            project,
-            cx,
-        );
-    });
-
-    project_remote.read_with(cx_b, |project, cx| {
-        assert_status(
-            &Path::new(A_TXT),
-            Some(GitFileStatus::Modified),
-            project,
-            cx,
-        );
-        assert_status(
-            &Path::new(B_TXT),
-            Some(GitFileStatus::Modified),
-            project,
-            cx,
-        );
-    });
-
-    // And synchronization while joining
-    let project_remote_c = client_c.build_remote_project(project_id, cx_c).await;
-    executor.run_until_parked();
-
-    project_remote_c.read_with(cx_c, |project, cx| {
-        assert_status(
-            &Path::new(A_TXT),
-            Some(GitFileStatus::Modified),
-            project,
-            cx,
-        );
-        assert_status(
-            &Path::new(B_TXT),
-            Some(GitFileStatus::Modified),
-            project,
-            cx,
-        );
-    });
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_fs_operations(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    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": "a-contents",
-                "b.txt": "b-contents",
-            }),
-        )
-        .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 worktree_a = project_a.read_with(cx_a, |project, _| project.worktrees().next().unwrap());
-
-    let worktree_b = project_b.read_with(cx_b, |project, _| project.worktrees().next().unwrap());
-
-    let entry = project_b
-        .update(cx_b, |project, cx| {
-            project.create_entry((worktree_id, "c.txt"), false, cx)
-        })
-        .await
-        .unwrap()
-        .unwrap();
-
-    worktree_a.read_with(cx_a, |worktree, _| {
-        assert_eq!(
-            worktree
-                .paths()
-                .map(|p| p.to_string_lossy())
-                .collect::<Vec<_>>(),
-            ["a.txt", "b.txt", "c.txt"]
-        );
-    });
-
-    worktree_b.read_with(cx_b, |worktree, _| {
-        assert_eq!(
-            worktree
-                .paths()
-                .map(|p| p.to_string_lossy())
-                .collect::<Vec<_>>(),
-            ["a.txt", "b.txt", "c.txt"]
-        );
-    });
-
-    project_b
-        .update(cx_b, |project, cx| {
-            project.rename_entry(entry.id, Path::new("d.txt"), cx)
-        })
-        .await
-        .unwrap()
-        .unwrap();
-
-    worktree_a.read_with(cx_a, |worktree, _| {
-        assert_eq!(
-            worktree
-                .paths()
-                .map(|p| p.to_string_lossy())
-                .collect::<Vec<_>>(),
-            ["a.txt", "b.txt", "d.txt"]
-        );
-    });
-
-    worktree_b.read_with(cx_b, |worktree, _| {
-        assert_eq!(
-            worktree
-                .paths()
-                .map(|p| p.to_string_lossy())
-                .collect::<Vec<_>>(),
-            ["a.txt", "b.txt", "d.txt"]
-        );
-    });
-
-    let dir_entry = project_b
-        .update(cx_b, |project, cx| {
-            project.create_entry((worktree_id, "DIR"), true, cx)
-        })
-        .await
-        .unwrap()
-        .unwrap();
-
-    worktree_a.read_with(cx_a, |worktree, _| {
-        assert_eq!(
-            worktree
-                .paths()
-                .map(|p| p.to_string_lossy())
-                .collect::<Vec<_>>(),
-            ["DIR", "a.txt", "b.txt", "d.txt"]
-        );
-    });
-
-    worktree_b.read_with(cx_b, |worktree, _| {
-        assert_eq!(
-            worktree
-                .paths()
-                .map(|p| p.to_string_lossy())
-                .collect::<Vec<_>>(),
-            ["DIR", "a.txt", "b.txt", "d.txt"]
-        );
-    });
-
-    project_b
-        .update(cx_b, |project, cx| {
-            project.create_entry((worktree_id, "DIR/e.txt"), false, cx)
-        })
-        .await
-        .unwrap()
-        .unwrap();
-    project_b
-        .update(cx_b, |project, cx| {
-            project.create_entry((worktree_id, "DIR/SUBDIR"), true, cx)
-        })
-        .await
-        .unwrap()
-        .unwrap();
-    project_b
-        .update(cx_b, |project, cx| {
-            project.create_entry((worktree_id, "DIR/SUBDIR/f.txt"), false, cx)
-        })
-        .await
-        .unwrap()
-        .unwrap();
-
-    worktree_a.read_with(cx_a, |worktree, _| {
-        assert_eq!(
-            worktree
-                .paths()
-                .map(|p| p.to_string_lossy())
-                .collect::<Vec<_>>(),
-            [
-                "DIR",
-                "DIR/SUBDIR",
-                "DIR/SUBDIR/f.txt",
-                "DIR/e.txt",
-                "a.txt",
-                "b.txt",
-                "d.txt"
-            ]
-        );
-    });
-
-    worktree_b.read_with(cx_b, |worktree, _| {
-        assert_eq!(
-            worktree
-                .paths()
-                .map(|p| p.to_string_lossy())
-                .collect::<Vec<_>>(),
-            [
-                "DIR",
-                "DIR/SUBDIR",
-                "DIR/SUBDIR/f.txt",
-                "DIR/e.txt",
-                "a.txt",
-                "b.txt",
-                "d.txt"
-            ]
-        );
-    });
-
-    project_b
-        .update(cx_b, |project, cx| {
-            project.copy_entry(entry.id, Path::new("f.txt"), cx)
-        })
-        .await
-        .unwrap()
-        .unwrap();
-
-    worktree_a.read_with(cx_a, |worktree, _| {
-        assert_eq!(
-            worktree
-                .paths()
-                .map(|p| p.to_string_lossy())
-                .collect::<Vec<_>>(),
-            [
-                "DIR",
-                "DIR/SUBDIR",
-                "DIR/SUBDIR/f.txt",
-                "DIR/e.txt",
-                "a.txt",
-                "b.txt",
-                "d.txt",
-                "f.txt"
-            ]
-        );
-    });
-
-    worktree_b.read_with(cx_b, |worktree, _| {
-        assert_eq!(
-            worktree
-                .paths()
-                .map(|p| p.to_string_lossy())
-                .collect::<Vec<_>>(),
-            [
-                "DIR",
-                "DIR/SUBDIR",
-                "DIR/SUBDIR/f.txt",
-                "DIR/e.txt",
-                "a.txt",
-                "b.txt",
-                "d.txt",
-                "f.txt"
-            ]
-        );
-    });
-
-    project_b
-        .update(cx_b, |project, cx| {
-            project.delete_entry(dir_entry.id, cx).unwrap()
-        })
-        .await
-        .unwrap();
-    executor.run_until_parked();
-
-    worktree_a.read_with(cx_a, |worktree, _| {
-        assert_eq!(
-            worktree
-                .paths()
-                .map(|p| p.to_string_lossy())
-                .collect::<Vec<_>>(),
-            ["a.txt", "b.txt", "d.txt", "f.txt"]
-        );
-    });
-
-    worktree_b.read_with(cx_b, |worktree, _| {
-        assert_eq!(
-            worktree
-                .paths()
-                .map(|p| p.to_string_lossy())
-                .collect::<Vec<_>>(),
-            ["a.txt", "b.txt", "d.txt", "f.txt"]
-        );
-    });
-
-    project_b
-        .update(cx_b, |project, cx| {
-            project.delete_entry(entry.id, cx).unwrap()
-        })
-        .await
-        .unwrap();
-
-    worktree_a.read_with(cx_a, |worktree, _| {
-        assert_eq!(
-            worktree
-                .paths()
-                .map(|p| p.to_string_lossy())
-                .collect::<Vec<_>>(),
-            ["a.txt", "b.txt", "f.txt"]
-        );
-    });
-
-    worktree_b.read_with(cx_b, |worktree, _| {
-        assert_eq!(
-            worktree
-                .paths()
-                .map(|p| p.to_string_lossy())
-                .collect::<Vec<_>>(),
-            ["a.txt", "b.txt", "f.txt"]
-        );
-    });
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_local_settings(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    server
-        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-        .await;
-    let active_call_a = cx_a.read(ActiveCall::global);
-
-    // As client A, open a project that contains some local settings files
-    client_a
-        .fs()
-        .insert_tree(
-            "/dir",
-            json!({
-                ".zed": {
-                    "settings.json": r#"{ "tab_size": 2 }"#
-                },
-                "a": {
-                    ".zed": {
-                        "settings.json": r#"{ "tab_size": 8 }"#
-                    },
-                    "a.txt": "a-contents",
-                },
-                "b": {
-                    "b.txt": "b-contents",
-                }
-            }),
-        )
-        .await;
-    let (project_a, _) = client_a.build_local_project("/dir", cx_a).await;
-    executor.run_until_parked();
-    let project_id = active_call_a
-        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-        .await
-        .unwrap();
-
-    // As client B, join that project and observe the local settings.
-    let project_b = client_b.build_remote_project(project_id, cx_b).await;
-
-    let worktree_b = project_b.read_with(cx_b, |project, _| project.worktrees().next().unwrap());
-    executor.run_until_parked();
-    cx_b.read(|cx| {
-        let store = cx.global::<SettingsStore>();
-        assert_eq!(
-            store
-                .local_settings(worktree_b.read(cx).id().to_usize())
-                .collect::<Vec<_>>(),
-            &[
-                (Path::new("").into(), r#"{"tab_size":2}"#.to_string()),
-                (Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
-            ]
-        )
-    });
-
-    // As client A, update a settings file. As Client B, see the changed settings.
-    client_a
-        .fs()
-        .insert_file("/dir/.zed/settings.json", r#"{}"#.into())
-        .await;
-    executor.run_until_parked();
-    cx_b.read(|cx| {
-        let store = cx.global::<SettingsStore>();
-        assert_eq!(
-            store
-                .local_settings(worktree_b.read(cx).id().to_usize())
-                .collect::<Vec<_>>(),
-            &[
-                (Path::new("").into(), r#"{}"#.to_string()),
-                (Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
-            ]
-        )
-    });
-
-    // As client A, create and remove some settings files. As client B, see the changed settings.
-    client_a
-        .fs()
-        .remove_file("/dir/.zed/settings.json".as_ref(), Default::default())
-        .await
-        .unwrap();
-    client_a
-        .fs()
-        .create_dir("/dir/b/.zed".as_ref())
-        .await
-        .unwrap();
-    client_a
-        .fs()
-        .insert_file("/dir/b/.zed/settings.json", r#"{"tab_size": 4}"#.into())
-        .await;
-    executor.run_until_parked();
-    cx_b.read(|cx| {
-        let store = cx.global::<SettingsStore>();
-        assert_eq!(
-            store
-                .local_settings(worktree_b.read(cx).id().to_usize())
-                .collect::<Vec<_>>(),
-            &[
-                (Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
-                (Path::new("b").into(), r#"{"tab_size":4}"#.to_string()),
-            ]
-        )
-    });
-
-    // As client B, disconnect.
-    server.forbid_connections();
-    server.disconnect_client(client_b.peer_id().unwrap());
-
-    // As client A, change and remove settings files while client B is disconnected.
-    client_a
-        .fs()
-        .insert_file("/dir/a/.zed/settings.json", r#"{"hard_tabs":true}"#.into())
-        .await;
-    client_a
-        .fs()
-        .remove_file("/dir/b/.zed/settings.json".as_ref(), Default::default())
-        .await
-        .unwrap();
-    executor.run_until_parked();
-
-    // As client B, reconnect and see the changed settings.
-    server.allow_connections();
-    executor.advance_clock(RECEIVE_TIMEOUT);
-    cx_b.read(|cx| {
-        let store = cx.global::<SettingsStore>();
-        assert_eq!(
-            store
-                .local_settings(worktree_b.read(cx).id().to_usize())
-                .collect::<Vec<_>>(),
-            &[(Path::new("a").into(), r#"{"hard_tabs":true}"#.to_string()),]
-        )
-    });
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_buffer_conflict_after_save(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    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": "a-contents",
-            }),
-        )
-        .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 B
-    let buffer_b = project_b
-        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
-        .await
-        .unwrap();
-
-    buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "world ")], None, cx));
-
-    buffer_b.read_with(cx_b, |buf, _| {
-        assert!(buf.is_dirty());
-        assert!(!buf.has_conflict());
-    });
-
-    project_b
-        .update(cx_b, |project, cx| {
-            project.save_buffer(buffer_b.clone(), cx)
-        })
-        .await
-        .unwrap();
-
-    buffer_b.read_with(cx_b, |buffer_b, _| assert!(!buffer_b.is_dirty()));
-
-    buffer_b.read_with(cx_b, |buf, _| {
-        assert!(!buf.has_conflict());
-    });
-
-    buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "hello ")], None, cx));
-
-    buffer_b.read_with(cx_b, |buf, _| {
-        assert!(buf.is_dirty());
-        assert!(!buf.has_conflict());
-    });
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_buffer_reloading(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    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": "a\nb\nc",
-            }),
-        )
-        .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 B
-    let buffer_b = project_b
-        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
-        .await
-        .unwrap();
-
-    buffer_b.read_with(cx_b, |buf, _| {
-        assert!(!buf.is_dirty());
-        assert!(!buf.has_conflict());
-        assert_eq!(buf.line_ending(), LineEnding::Unix);
-    });
-
-    let new_contents = Rope::from("d\ne\nf");
-    client_a
-        .fs()
-        .save("/dir/a.txt".as_ref(), &new_contents, LineEnding::Windows)
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-
-    buffer_b.read_with(cx_b, |buf, _| {
-        assert_eq!(buf.text(), new_contents.to_string());
-        assert!(!buf.is_dirty());
-        assert!(!buf.has_conflict());
-        assert_eq!(buf.line_ending(), LineEnding::Windows);
-    });
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_editing_while_guest_opens_buffer(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    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": "a-contents" }))
-        .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();
-
-    // Start opening the same buffer as client B
-    let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx));
-    let buffer_b = cx_b.executor().spawn(open_buffer);
-
-    // Edit the buffer as client A while client B is still opening it.
-    cx_b.executor().simulate_random_delay().await;
-    buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "X")], None, cx));
-    cx_b.executor().simulate_random_delay().await;
-    buffer_a.update(cx_a, |buf, cx| buf.edit([(1..1, "Y")], None, cx));
-
-    let text = buffer_a.read_with(cx_a, |buf, _| buf.text());
-    let buffer_b = buffer_b.await.unwrap();
-    executor.run_until_parked();
-
-    buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), text));
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_leaving_worktree_while_opening_buffer(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    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": "a-contents" }))
-        .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;
-
-    // See that a guest has joined as client A.
-    executor.run_until_parked();
-
-    project_a.read_with(cx_a, |p, _| assert_eq!(p.collaborators().len(), 1));
-
-    // Begin opening a buffer as client B, but leave the project before the open completes.
-    let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx));
-    let buffer_b = cx_b.executor().spawn(open_buffer);
-    cx_b.update(|_| drop(project_b));
-    drop(buffer_b);
-
-    // See that the guest has left.
-    executor.run_until_parked();
-
-    project_a.read_with(cx_a, |p, _| assert!(p.collaborators().is_empty()));
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_canceling_buffer_opening(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    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": "abc",
-            }),
-        )
-        .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 buffer_a = project_a
-        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
-        .await
-        .unwrap();
-
-    // Open a buffer as client B but cancel after a random amount of time.
-    let buffer_b = project_b.update(cx_b, |p, cx| {
-        p.open_buffer_by_id(buffer_a.read_with(cx_a, |a, _| a.remote_id()), cx)
-    });
-    executor.simulate_random_delay().await;
-    drop(buffer_b);
-
-    // Try opening the same buffer again as client B, and ensure we can
-    // still do it despite the cancellation above.
-    let buffer_b = project_b
-        .update(cx_b, |p, cx| {
-            p.open_buffer_by_id(buffer_a.read_with(cx_a, |a, _| a.remote_id()), cx)
-        })
-        .await
-        .unwrap();
-
-    buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "abc"));
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_leaving_project(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-    cx_c: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    let 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;
-    let active_call_a = cx_a.read(ActiveCall::global);
-
-    client_a
-        .fs()
-        .insert_tree(
-            "/a",
-            json!({
-                "a.txt": "a-contents",
-                "b.txt": "b-contents",
-            }),
-        )
-        .await;
-    let (project_a, _) = 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_b1 = client_b.build_remote_project(project_id, cx_b).await;
-    let project_c = client_c.build_remote_project(project_id, cx_c).await;
-
-    // Client A sees that a guest has joined.
-    executor.run_until_parked();
-
-    project_a.read_with(cx_a, |project, _| {
-        assert_eq!(project.collaborators().len(), 2);
-    });
-
-    project_b1.read_with(cx_b, |project, _| {
-        assert_eq!(project.collaborators().len(), 2);
-    });
-
-    project_c.read_with(cx_c, |project, _| {
-        assert_eq!(project.collaborators().len(), 2);
-    });
-
-    // Client B opens a buffer.
-    let buffer_b1 = project_b1
-        .update(cx_b, |project, cx| {
-            let worktree_id = project.worktrees().next().unwrap().read(cx).id();
-            project.open_buffer((worktree_id, "a.txt"), cx)
-        })
-        .await
-        .unwrap();
-
-    buffer_b1.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "a-contents"));
-
-    // Drop client B's project and ensure client A and client C observe client B leaving.
-    cx_b.update(|_| drop(project_b1));
-    executor.run_until_parked();
-
-    project_a.read_with(cx_a, |project, _| {
-        assert_eq!(project.collaborators().len(), 1);
-    });
-
-    project_c.read_with(cx_c, |project, _| {
-        assert_eq!(project.collaborators().len(), 1);
-    });
-
-    // Client B re-joins the project and can open buffers as before.
-    let project_b2 = client_b.build_remote_project(project_id, cx_b).await;
-    executor.run_until_parked();
-
-    project_a.read_with(cx_a, |project, _| {
-        assert_eq!(project.collaborators().len(), 2);
-    });
-
-    project_b2.read_with(cx_b, |project, _| {
-        assert_eq!(project.collaborators().len(), 2);
-    });
-
-    project_c.read_with(cx_c, |project, _| {
-        assert_eq!(project.collaborators().len(), 2);
-    });
-
-    let buffer_b2 = project_b2
-        .update(cx_b, |project, cx| {
-            let worktree_id = project.worktrees().next().unwrap().read(cx).id();
-            project.open_buffer((worktree_id, "a.txt"), cx)
-        })
-        .await
-        .unwrap();
-
-    buffer_b2.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "a-contents"));
-
-    // Drop client B's connection and ensure client A and client C observe client B leaving.
-    client_b.disconnect(&cx_b.to_async());
-    executor.advance_clock(RECONNECT_TIMEOUT);
-
-    project_a.read_with(cx_a, |project, _| {
-        assert_eq!(project.collaborators().len(), 1);
-    });
-
-    project_b2.read_with(cx_b, |project, _| {
-        assert!(project.is_read_only());
-    });
-
-    project_c.read_with(cx_c, |project, _| {
-        assert_eq!(project.collaborators().len(), 1);
-    });
-
-    // Client B can't join the project, unless they re-join the room.
-    cx_b.spawn(|cx| {
-        Project::remote(
-            project_id,
-            client_b.app_state.client.clone(),
-            client_b.user_store().clone(),
-            client_b.language_registry().clone(),
-            FakeFs::new(cx.background_executor().clone()),
-            cx,
-        )
-    })
-    .await
-    .unwrap_err();
-
-    // Simulate connection loss for client C and ensure client A observes client C leaving the project.
-    client_c.wait_for_current_user(cx_c).await;
-    server.forbid_connections();
-    server.disconnect_client(client_c.peer_id().unwrap());
-    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
-    executor.run_until_parked();
-
-    project_a.read_with(cx_a, |project, _| {
-        assert_eq!(project.collaborators().len(), 0);
-    });
-
-    project_b2.read_with(cx_b, |project, _| {
-        assert!(project.is_read_only());
-    });
-
-    project_c.read_with(cx_c, |project, _| {
-        assert!(project.is_read_only());
-    });
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_collaborating_with_diagnostics(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-    cx_c: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    let 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;
-    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(Default::default()).await;
-    client_a.language_registry().add(Arc::new(language));
-
-    // Share a project as client A
-    client_a
-        .fs()
-        .insert_tree(
-            "/a",
-            json!({
-                "a.rs": "let one = two",
-                "other.rs": "",
-            }),
-        )
-        .await;
-    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
-
-    // Cause the language server to start.
-    let _buffer = project_a
-        .update(cx_a, |project, cx| {
-            project.open_buffer(
-                ProjectPath {
-                    worktree_id,
-                    path: Path::new("other.rs").into(),
-                },
-                cx,
-            )
-        })
-        .await
-        .unwrap();
-
-    // Simulate a language server reporting errors for a file.
-    let mut fake_language_server = fake_language_servers.next().await.unwrap();
-    fake_language_server
-        .receive_notification::<lsp::notification::DidOpenTextDocument>()
-        .await;
-    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
-        lsp::PublishDiagnosticsParams {
-            uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
-            version: None,
-            diagnostics: vec![lsp::Diagnostic {
-                severity: Some(lsp::DiagnosticSeverity::WARNING),
-                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
-                message: "message 0".to_string(),
-                ..Default::default()
-            }],
-        },
-    );
-
-    // Client A shares the project and, simultaneously, the language server
-    // publishes a diagnostic. This is done to ensure that the server always
-    // observes the latest diagnostics for a worktree.
-    let project_id = active_call_a
-        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-        .await
-        .unwrap();
-    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
-        lsp::PublishDiagnosticsParams {
-            uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
-            version: None,
-            diagnostics: vec![lsp::Diagnostic {
-                severity: Some(lsp::DiagnosticSeverity::ERROR),
-                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
-                message: "message 1".to_string(),
-                ..Default::default()
-            }],
-        },
-    );
-
-    // Join the worktree as client B.
-    let project_b = client_b.build_remote_project(project_id, cx_b).await;
-
-    // Wait for server to see the diagnostics update.
-    executor.run_until_parked();
-
-    // Ensure client B observes the new diagnostics.
-
-    project_b.read_with(cx_b, |project, cx| {
-        assert_eq!(
-            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
-            &[(
-                ProjectPath {
-                    worktree_id,
-                    path: Arc::from(Path::new("a.rs")),
-                },
-                LanguageServerId(0),
-                DiagnosticSummary {
-                    error_count: 1,
-                    warning_count: 0,
-                    ..Default::default()
-                },
-            )]
-        )
-    });
-
-    // Join project as client C and observe the diagnostics.
-    let project_c = client_c.build_remote_project(project_id, cx_c).await;
-    let project_c_diagnostic_summaries =
-        Rc::new(RefCell::new(project_c.read_with(cx_c, |project, cx| {
-            project.diagnostic_summaries(false, cx).collect::<Vec<_>>()
-        })));
-    project_c.update(cx_c, |_, cx| {
-        let summaries = project_c_diagnostic_summaries.clone();
-        cx.subscribe(&project_c, {
-            move |p, _, event, cx| {
-                if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
-                    *summaries.borrow_mut() = p.diagnostic_summaries(false, cx).collect();
-                }
-            }
-        })
-        .detach();
-    });
-
-    executor.run_until_parked();
-    assert_eq!(
-        project_c_diagnostic_summaries.borrow().as_slice(),
-        &[(
-            ProjectPath {
-                worktree_id,
-                path: Arc::from(Path::new("a.rs")),
-            },
-            LanguageServerId(0),
-            DiagnosticSummary {
-                error_count: 1,
-                warning_count: 0,
-                ..Default::default()
-            },
-        )]
-    );
-
-    // Simulate a language server reporting more errors for a file.
-    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
-        lsp::PublishDiagnosticsParams {
-            uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
-            version: None,
-            diagnostics: vec![
-                lsp::Diagnostic {
-                    severity: Some(lsp::DiagnosticSeverity::ERROR),
-                    range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
-                    message: "message 1".to_string(),
-                    ..Default::default()
-                },
-                lsp::Diagnostic {
-                    severity: Some(lsp::DiagnosticSeverity::WARNING),
-                    range: lsp::Range::new(lsp::Position::new(0, 10), lsp::Position::new(0, 13)),
-                    message: "message 2".to_string(),
-                    ..Default::default()
-                },
-            ],
-        },
-    );
-
-    // Clients B and C get the updated summaries
-    executor.run_until_parked();
-
-    project_b.read_with(cx_b, |project, cx| {
-        assert_eq!(
-            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
-            [(
-                ProjectPath {
-                    worktree_id,
-                    path: Arc::from(Path::new("a.rs")),
-                },
-                LanguageServerId(0),
-                DiagnosticSummary {
-                    error_count: 1,
-                    warning_count: 1,
-                },
-            )]
-        );
-    });
-
-    project_c.read_with(cx_c, |project, cx| {
-        assert_eq!(
-            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
-            [(
-                ProjectPath {
-                    worktree_id,
-                    path: Arc::from(Path::new("a.rs")),
-                },
-                LanguageServerId(0),
-                DiagnosticSummary {
-                    error_count: 1,
-                    warning_count: 1,
-                },
-            )]
-        );
-    });
-
-    // Open the file with the errors on client B. They should be present.
-    let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
-    let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
-
-    buffer_b.read_with(cx_b, |buffer, _| {
-        assert_eq!(
-            buffer
-                .snapshot()
-                .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
-                .collect::<Vec<_>>(),
-            &[
-                DiagnosticEntry {
-                    range: Point::new(0, 4)..Point::new(0, 7),
-                    diagnostic: Diagnostic {
-                        group_id: 2,
-                        message: "message 1".to_string(),
-                        severity: lsp::DiagnosticSeverity::ERROR,
-                        is_primary: true,
-                        ..Default::default()
-                    }
-                },
-                DiagnosticEntry {
-                    range: Point::new(0, 10)..Point::new(0, 13),
-                    diagnostic: Diagnostic {
-                        group_id: 3,
-                        severity: lsp::DiagnosticSeverity::WARNING,
-                        message: "message 2".to_string(),
-                        is_primary: true,
-                        ..Default::default()
-                    }
-                }
-            ]
-        );
-    });
-
-    // Simulate a language server reporting no errors for a file.
-    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
-        lsp::PublishDiagnosticsParams {
-            uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
-            version: None,
-            diagnostics: vec![],
-        },
-    );
-    executor.run_until_parked();
-
-    project_a.read_with(cx_a, |project, cx| {
-        assert_eq!(
-            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
-            []
-        )
-    });
-
-    project_b.read_with(cx_b, |project, cx| {
-        assert_eq!(
-            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
-            []
-        )
-    });
-
-    project_c.read_with(cx_c, |project, cx| {
-        assert_eq!(
-            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
-            []
-        )
-    });
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    server
-        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-        .await;
-
-    // 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 {
-            disk_based_diagnostics_progress_token: Some("the-disk-based-token".into()),
-            disk_based_diagnostics_sources: vec!["the-disk-based-diagnostics-source".into()],
-            ..Default::default()
-        }))
-        .await;
-    client_a.language_registry().add(Arc::new(language));
-
-    let file_names = &["one.rs", "two.rs", "three.rs", "four.rs", "five.rs"];
-    client_a
-        .fs()
-        .insert_tree(
-            "/test",
-            json!({
-                "one.rs": "const ONE: usize = 1;",
-                "two.rs": "const TWO: usize = 2;",
-                "three.rs": "const THREE: usize = 3;",
-                "four.rs": "const FOUR: usize = 3;",
-                "five.rs": "const FIVE: usize = 3;",
-            }),
-        )
-        .await;
-
-    let (project_a, worktree_id) = client_a.build_local_project("/test", cx_a).await;
-
-    // Share a project as client A
-    let active_call_a = cx_a.read(ActiveCall::global);
-    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 and open all three files.
-    let project_b = client_b.build_remote_project(project_id, cx_b).await;
-    let guest_buffers = futures::future::try_join_all(file_names.iter().map(|file_name| {
-        project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, file_name), cx))
-    }))
-    .await
-    .unwrap();
-
-    // Simulate a language server reporting errors for a file.
-    let fake_language_server = fake_language_servers.next().await.unwrap();
-    fake_language_server
-        .request::<lsp::request::WorkDoneProgressCreate>(lsp::WorkDoneProgressCreateParams {
-            token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
-        })
-        .await
-        .unwrap();
-    fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
-        token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
-        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Begin(
-            lsp::WorkDoneProgressBegin {
-                title: "Progress Began".into(),
-                ..Default::default()
-            },
-        )),
-    });
-    for file_name in file_names {
-        fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
-            lsp::PublishDiagnosticsParams {
-                uri: lsp::Url::from_file_path(Path::new("/test").join(file_name)).unwrap(),
-                version: None,
-                diagnostics: vec![lsp::Diagnostic {
-                    severity: Some(lsp::DiagnosticSeverity::WARNING),
-                    source: Some("the-disk-based-diagnostics-source".into()),
-                    range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
-                    message: "message one".to_string(),
-                    ..Default::default()
-                }],
-            },
-        );
-    }
-    fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
-        token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
-        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::End(
-            lsp::WorkDoneProgressEnd { message: None },
-        )),
-    });
-
-    // When the "disk base diagnostics finished" message is received, the buffers'
-    // diagnostics are expected to be present.
-    let disk_based_diagnostics_finished = Arc::new(AtomicBool::new(false));
-    project_b.update(cx_b, {
-        let project_b = project_b.clone();
-        let disk_based_diagnostics_finished = disk_based_diagnostics_finished.clone();
-        move |_, cx| {
-            cx.subscribe(&project_b, move |_, _, event, cx| {
-                if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
-                    disk_based_diagnostics_finished.store(true, SeqCst);
-                    for buffer in &guest_buffers {
-                        assert_eq!(
-                            buffer
-                                .read(cx)
-                                .snapshot()
-                                .diagnostics_in_range::<_, usize>(0..5, false)
-                                .count(),
-                            1,
-                            "expected a diagnostic for buffer {:?}",
-                            buffer.read(cx).file().unwrap().path(),
-                        );
-                    }
-                }
-            })
-            .detach();
-        }
-    });
-
-    executor.run_until_parked();
-    assert!(disk_based_diagnostics_finished.load(SeqCst));
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_reloading_buffer_manually(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    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("/a", json!({ "a.rs": "let one = 1;" }))
-        .await;
-    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
-    let buffer_a = project_a
-        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
-        .await
-        .unwrap();
-    let project_id = active_call_a
-        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-        .await
-        .unwrap();
-
-    let project_b = client_b.build_remote_project(project_id, cx_b).await;
-
-    let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
-    let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
-    buffer_b.update(cx_b, |buffer, cx| {
-        buffer.edit([(4..7, "six")], None, cx);
-        buffer.edit([(10..11, "6")], None, cx);
-        assert_eq!(buffer.text(), "let six = 6;");
-        assert!(buffer.is_dirty());
-        assert!(!buffer.has_conflict());
-    });
-    executor.run_until_parked();
-
-    buffer_a.read_with(cx_a, |buffer, _| assert_eq!(buffer.text(), "let six = 6;"));
-
-    client_a
-        .fs()
-        .save(
-            "/a/a.rs".as_ref(),
-            &Rope::from("let seven = 7;"),
-            LineEnding::Unix,
-        )
-        .await
-        .unwrap();
-    executor.run_until_parked();
-
-    buffer_a.read_with(cx_a, |buffer, _| assert!(buffer.has_conflict()));
-
-    buffer_b.read_with(cx_b, |buffer, _| assert!(buffer.has_conflict()));
-
-    project_b
-        .update(cx_b, |project, cx| {
-            project.reload_buffers(HashSet::from_iter([buffer_b.clone()]), true, cx)
-        })
-        .await
-        .unwrap();
-
-    buffer_a.read_with(cx_a, |buffer, _| {
-        assert_eq!(buffer.text(), "let seven = 7;");
-        assert!(!buffer.is_dirty());
-        assert!(!buffer.has_conflict());
-    });
-
-    buffer_b.read_with(cx_b, |buffer, _| {
-        assert_eq!(buffer.text(), "let seven = 7;");
-        assert!(!buffer.is_dirty());
-        assert!(!buffer.has_conflict());
-    });
-
-    buffer_a.update(cx_a, |buffer, cx| {
-        // Undoing on the host is a no-op when the reload was initiated by the guest.
-        buffer.undo(cx);
-        assert_eq!(buffer.text(), "let seven = 7;");
-        assert!(!buffer.is_dirty());
-        assert!(!buffer.has_conflict());
-    });
-    buffer_b.update(cx_b, |buffer, cx| {
-        // Undoing on the guest rolls back the buffer to before it was reloaded but the conflict gets cleared.
-        buffer.undo(cx);
-        assert_eq!(buffer.text(), "let six = 6;");
-        assert!(buffer.is_dirty());
-        assert!(!buffer.has_conflict());
-    });
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_formatting_buffer(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    executor.allow_parking();
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    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(Default::default()).await;
-    client_a.language_registry().add(Arc::new(language));
-
-    // Here we insert a fake tree with a directory that exists on disk. This is needed
-    // because later we'll invoke a command, which requires passing a working directory
-    // that points to a valid location on disk.
-    let directory = env::current_dir().unwrap();
-    client_a
-        .fs()
-        .insert_tree(&directory, json!({ "a.rs": "let one = \"two\"" }))
-        .await;
-    let (project_a, worktree_id) = client_a.build_local_project(&directory, 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 open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
-    let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
-
-    let fake_language_server = fake_language_servers.next().await.unwrap();
-    fake_language_server.handle_request::<lsp::request::Formatting, _, _>(|_, _| async move {
-        Ok(Some(vec![
-            lsp::TextEdit {
-                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 4)),
-                new_text: "h".to_string(),
-            },
-            lsp::TextEdit {
-                range: lsp::Range::new(lsp::Position::new(0, 7), lsp::Position::new(0, 7)),
-                new_text: "y".to_string(),
-            },
-        ]))
-    });
-
-    project_b
-        .update(cx_b, |project, cx| {
-            project.format(
-                HashSet::from_iter([buffer_b.clone()]),
-                true,
-                FormatTrigger::Save,
-                cx,
-            )
-        })
-        .await
-        .unwrap();
-
-    // The edits from the LSP are applied, and a final newline is added.
-    assert_eq!(
-        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
-        "let honey = \"two\"\n"
-    );
-
-    // Ensure buffer can be formatted using an external command. Notice how the
-    // host's configuration is honored as opposed to using the guest's settings.
-    cx_a.update(|cx| {
-        cx.update_global(|store: &mut SettingsStore, cx| {
-            store.update_user_settings::<AllLanguageSettings>(cx, |file| {
-                file.defaults.formatter = Some(Formatter::External {
-                    command: "awk".into(),
-                    arguments: vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()].into(),
-                });
-            });
-        });
-    });
-    project_b
-        .update(cx_b, |project, cx| {
-            project.format(
-                HashSet::from_iter([buffer_b.clone()]),
-                true,
-                FormatTrigger::Save,
-                cx,
-            )
-        })
-        .await
-        .unwrap();
-    assert_eq!(
-        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
-        format!("let honey = \"{}/a.rs\"\n", directory.to_str().unwrap())
-    );
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_prettier_formatting_buffer(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    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()],
-            prettier_parser_name: Some("test_parser".to_string()),
-            ..Default::default()
-        },
-        Some(tree_sitter_rust::language()),
-    );
-    let test_plugin = "test_plugin";
-    let mut fake_language_servers = language
-        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-            prettier_plugins: vec![test_plugin],
-            ..Default::default()
-        }))
-        .await;
-    let language = Arc::new(language);
-    client_a.language_registry().add(Arc::clone(&language));
-
-    // Here we insert a fake tree with a directory that exists on disk. This is needed
-    // because later we'll invoke a command, which requires passing a working directory
-    // that points to a valid location on disk.
-    let directory = env::current_dir().unwrap();
-    let buffer_text = "let one = \"two\"";
-    client_a
-        .fs()
-        .insert_tree(&directory, json!({ "a.rs": buffer_text }))
-        .await;
-    let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await;
-    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
-    let open_buffer = project_a.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
-    let buffer_a = cx_a.executor().spawn(open_buffer).await.unwrap();
-
-    let project_id = active_call_a
-        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-        .await
-        .unwrap();
-    let project_b = client_b.build_remote_project(project_id, cx_b).await;
-    let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
-    let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
-
-    cx_a.update(|cx| {
-        cx.update_global(|store: &mut SettingsStore, cx| {
-            store.update_user_settings::<AllLanguageSettings>(cx, |file| {
-                file.defaults.formatter = Some(Formatter::Auto);
-            });
-        });
-    });
-    cx_b.update(|cx| {
-        cx.update_global(|store: &mut SettingsStore, cx| {
-            store.update_user_settings::<AllLanguageSettings>(cx, |file| {
-                file.defaults.formatter = Some(Formatter::LanguageServer);
-            });
-        });
-    });
-    let fake_language_server = fake_language_servers.next().await.unwrap();
-    fake_language_server.handle_request::<lsp::request::Formatting, _, _>(|_, _| async move {
-        panic!(
-            "Unexpected: prettier should be preferred since it's enabled and language supports it"
-        )
-    });
-
-    project_b
-        .update(cx_b, |project, cx| {
-            project.format(
-                HashSet::from_iter([buffer_b.clone()]),
-                true,
-                FormatTrigger::Save,
-                cx,
-            )
-        })
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-    assert_eq!(
-        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
-        buffer_text.to_string() + "\n" + prettier_format_suffix,
-        "Prettier formatting was not applied to client buffer after client's request"
-    );
-
-    project_a
-        .update(cx_a, |project, cx| {
-            project.format(
-                HashSet::from_iter([buffer_a.clone()]),
-                true,
-                FormatTrigger::Manual,
-                cx,
-            )
-        })
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-    assert_eq!(
-        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
-        buffer_text.to_string() + "\n" + prettier_format_suffix + "\n" + prettier_format_suffix,
-        "Prettier formatting was not applied to client buffer after host's request"
-    );
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_definition(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    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(Default::default()).await;
-    client_a.language_registry().add(Arc::new(language));
-
-    client_a
-        .fs()
-        .insert_tree(
-            "/root",
-            json!({
-                "dir-1": {
-                    "a.rs": "const ONE: usize = b::TWO + b::THREE;",
-                },
-                "dir-2": {
-                    "b.rs": "const TWO: c::T2 = 2;\nconst THREE: usize = 3;",
-                    "c.rs": "type T2 = usize;",
-                }
-            }),
-        )
-        .await;
-    let (project_a, worktree_id) = client_a.build_local_project("/root/dir-1", 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 the file on client B.
-    let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
-    let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
-
-    // Request the definition of a symbol as the guest.
-    let fake_language_server = fake_language_servers.next().await.unwrap();
-    fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
-        Ok(Some(lsp::GotoDefinitionResponse::Scalar(
-            lsp::Location::new(
-                lsp::Url::from_file_path("/root/dir-2/b.rs").unwrap(),
-                lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
-            ),
-        )))
-    });
-
-    let definitions_1 = project_b
-        .update(cx_b, |p, cx| p.definition(&buffer_b, 23, cx))
-        .await
-        .unwrap();
-    cx_b.read(|cx| {
-        assert_eq!(definitions_1.len(), 1);
-        assert_eq!(project_b.read(cx).worktrees().count(), 2);
-        let target_buffer = definitions_1[0].target.buffer.read(cx);
-        assert_eq!(
-            target_buffer.text(),
-            "const TWO: c::T2 = 2;\nconst THREE: usize = 3;"
-        );
-        assert_eq!(
-            definitions_1[0].target.range.to_point(target_buffer),
-            Point::new(0, 6)..Point::new(0, 9)
-        );
-    });
-
-    // Try getting more definitions for the same buffer, ensuring the buffer gets reused from
-    // the previous call to `definition`.
-    fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
-        Ok(Some(lsp::GotoDefinitionResponse::Scalar(
-            lsp::Location::new(
-                lsp::Url::from_file_path("/root/dir-2/b.rs").unwrap(),
-                lsp::Range::new(lsp::Position::new(1, 6), lsp::Position::new(1, 11)),
-            ),
-        )))
-    });
-
-    let definitions_2 = project_b
-        .update(cx_b, |p, cx| p.definition(&buffer_b, 33, cx))
-        .await
-        .unwrap();
-    cx_b.read(|cx| {
-        assert_eq!(definitions_2.len(), 1);
-        assert_eq!(project_b.read(cx).worktrees().count(), 2);
-        let target_buffer = definitions_2[0].target.buffer.read(cx);
-        assert_eq!(
-            target_buffer.text(),
-            "const TWO: c::T2 = 2;\nconst THREE: usize = 3;"
-        );
-        assert_eq!(
-            definitions_2[0].target.range.to_point(target_buffer),
-            Point::new(1, 6)..Point::new(1, 11)
-        );
-    });
-    assert_eq!(
-        definitions_1[0].target.buffer,
-        definitions_2[0].target.buffer
-    );
-
-    fake_language_server.handle_request::<lsp::request::GotoTypeDefinition, _, _>(
-        |req, _| async move {
-            assert_eq!(
-                req.text_document_position_params.position,
-                lsp::Position::new(0, 7)
-            );
-            Ok(Some(lsp::GotoDefinitionResponse::Scalar(
-                lsp::Location::new(
-                    lsp::Url::from_file_path("/root/dir-2/c.rs").unwrap(),
-                    lsp::Range::new(lsp::Position::new(0, 5), lsp::Position::new(0, 7)),
-                ),
-            )))
-        },
-    );
-
-    let type_definitions = project_b
-        .update(cx_b, |p, cx| p.type_definition(&buffer_b, 7, cx))
-        .await
-        .unwrap();
-    cx_b.read(|cx| {
-        assert_eq!(type_definitions.len(), 1);
-        let target_buffer = type_definitions[0].target.buffer.read(cx);
-        assert_eq!(target_buffer.text(), "type T2 = usize;");
-        assert_eq!(
-            type_definitions[0].target.range.to_point(target_buffer),
-            Point::new(0, 5)..Point::new(0, 7)
-        );
-    });
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_references(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    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(Default::default()).await;
-    client_a.language_registry().add(Arc::new(language));
-
-    client_a
-        .fs()
-        .insert_tree(
-            "/root",
-            json!({
-                "dir-1": {
-                    "one.rs": "const ONE: usize = 1;",
-                    "two.rs": "const TWO: usize = one::ONE + one::ONE;",
-                },
-                "dir-2": {
-                    "three.rs": "const THREE: usize = two::TWO + one::ONE;",
-                }
-            }),
-        )
-        .await;
-    let (project_a, worktree_id) = client_a.build_local_project("/root/dir-1", 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 the file on client B.
-    let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx));
-    let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
-
-    // Request references to a symbol as the guest.
-    let fake_language_server = fake_language_servers.next().await.unwrap();
-    fake_language_server.handle_request::<lsp::request::References, _, _>(|params, _| async move {
-        assert_eq!(
-            params.text_document_position.text_document.uri.as_str(),
-            "file:///root/dir-1/one.rs"
-        );
-        Ok(Some(vec![
-            lsp::Location {
-                uri: lsp::Url::from_file_path("/root/dir-1/two.rs").unwrap(),
-                range: lsp::Range::new(lsp::Position::new(0, 24), lsp::Position::new(0, 27)),
-            },
-            lsp::Location {
-                uri: lsp::Url::from_file_path("/root/dir-1/two.rs").unwrap(),
-                range: lsp::Range::new(lsp::Position::new(0, 35), lsp::Position::new(0, 38)),
-            },
-            lsp::Location {
-                uri: lsp::Url::from_file_path("/root/dir-2/three.rs").unwrap(),
-                range: lsp::Range::new(lsp::Position::new(0, 37), lsp::Position::new(0, 40)),
-            },
-        ]))
-    });
-
-    let references = project_b
-        .update(cx_b, |p, cx| p.references(&buffer_b, 7, cx))
-        .await
-        .unwrap();
-    cx_b.read(|cx| {
-        assert_eq!(references.len(), 3);
-        assert_eq!(project_b.read(cx).worktrees().count(), 2);
-
-        let two_buffer = references[0].buffer.read(cx);
-        let three_buffer = references[2].buffer.read(cx);
-        assert_eq!(
-            two_buffer.file().unwrap().path().as_ref(),
-            Path::new("two.rs")
-        );
-        assert_eq!(references[1].buffer, references[0].buffer);
-        assert_eq!(
-            three_buffer.file().unwrap().full_path(cx),
-            Path::new("/root/dir-2/three.rs")
-        );
-
-        assert_eq!(references[0].range.to_offset(two_buffer), 24..27);
-        assert_eq!(references[1].range.to_offset(two_buffer), 35..38);
-        assert_eq!(references[2].range.to_offset(three_buffer), 37..40);
-    });
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_project_search(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    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(
-            "/root",
-            json!({
-                "dir-1": {
-                    "a": "hello world",
-                    "b": "goodnight moon",
-                    "c": "a world of goo",
-                    "d": "world champion of clown world",
-                },
-                "dir-2": {
-                    "e": "disney world is fun",
-                }
-            }),
-        )
-        .await;
-    let (project_a, _) = client_a.build_local_project("/root/dir-1", cx_a).await;
-    let (worktree_2, _) = project_a
-        .update(cx_a, |p, cx| {
-            p.find_or_create_local_worktree("/root/dir-2", true, cx)
-        })
-        .await
-        .unwrap();
-    worktree_2
-        .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
-        .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;
-
-    // Perform a search as the guest.
-    let mut results = HashMap::default();
-    let mut search_rx = project_b.update(cx_b, |project, cx| {
-        project.search(
-            SearchQuery::text("world", false, false, false, Vec::new(), Vec::new()).unwrap(),
-            cx,
-        )
-    });
-    while let Some((buffer, ranges)) = search_rx.next().await {
-        results.entry(buffer).or_insert(ranges);
-    }
-
-    let mut ranges_by_path = results
-        .into_iter()
-        .map(|(buffer, ranges)| {
-            buffer.read_with(cx_b, |buffer, cx| {
-                let path = buffer.file().unwrap().full_path(cx);
-                let offset_ranges = ranges
-                    .into_iter()
-                    .map(|range| range.to_offset(buffer))
-                    .collect::<Vec<_>>();
-                (path, offset_ranges)
-            })
-        })
-        .collect::<Vec<_>>();
-    ranges_by_path.sort_by_key(|(path, _)| path.clone());
-
-    assert_eq!(
-        ranges_by_path,
-        &[
-            (PathBuf::from("dir-1/a"), vec![6..11]),
-            (PathBuf::from("dir-1/c"), vec![2..7]),
-            (PathBuf::from("dir-1/d"), vec![0..5, 24..29]),
-            (PathBuf::from("dir-2/e"), vec![7..12]),
-        ]
-    );
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_document_highlights(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    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(
-            "/root-1",
-            json!({
-                "main.rs": "fn double(number: i32) -> i32 { number + number }",
-            }),
-        )
-        .await;
-
-    // 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));
-
-    let (project_a, worktree_id) = client_a.build_local_project("/root-1", 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 the file on client B.
-    let open_b = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx));
-    let buffer_b = cx_b.executor().spawn(open_b).await.unwrap();
-
-    // Request document highlights as the guest.
-    let fake_language_server = fake_language_servers.next().await.unwrap();
-    fake_language_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>(
-        |params, _| async move {
-            assert_eq!(
-                params
-                    .text_document_position_params
-                    .text_document
-                    .uri
-                    .as_str(),
-                "file:///root-1/main.rs"
-            );
-            assert_eq!(
-                params.text_document_position_params.position,
-                lsp::Position::new(0, 34)
-            );
-            Ok(Some(vec![
-                lsp::DocumentHighlight {
-                    kind: Some(lsp::DocumentHighlightKind::WRITE),
-                    range: lsp::Range::new(lsp::Position::new(0, 10), lsp::Position::new(0, 16)),
-                },
-                lsp::DocumentHighlight {
-                    kind: Some(lsp::DocumentHighlightKind::READ),
-                    range: lsp::Range::new(lsp::Position::new(0, 32), lsp::Position::new(0, 38)),
-                },
-                lsp::DocumentHighlight {
-                    kind: Some(lsp::DocumentHighlightKind::READ),
-                    range: lsp::Range::new(lsp::Position::new(0, 41), lsp::Position::new(0, 47)),
-                },
-            ]))
-        },
-    );
-
-    let highlights = project_b
-        .update(cx_b, |p, cx| p.document_highlights(&buffer_b, 34, cx))
-        .await
-        .unwrap();
-
-    buffer_b.read_with(cx_b, |buffer, _| {
-        let snapshot = buffer.snapshot();
-
-        let highlights = highlights
-            .into_iter()
-            .map(|highlight| (highlight.kind, highlight.range.to_offset(&snapshot)))
-            .collect::<Vec<_>>();
-        assert_eq!(
-            highlights,
-            &[
-                (lsp::DocumentHighlightKind::WRITE, 10..16),
-                (lsp::DocumentHighlightKind::READ, 32..38),
-                (lsp::DocumentHighlightKind::READ, 41..47)
-            ]
-        )
-    });
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_lsp_hover(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    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(
-            "/root-1",
-            json!({
-                "main.rs": "use std::collections::HashMap;",
-            }),
-        )
-        .await;
-
-    // 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));
-
-    let (project_a, worktree_id) = client_a.build_local_project("/root-1", 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 the file as the guest
-    let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx));
-    let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
-
-    // Request hover information as the guest.
-    let fake_language_server = fake_language_servers.next().await.unwrap();
-    fake_language_server.handle_request::<lsp::request::HoverRequest, _, _>(
-        |params, _| async move {
-            assert_eq!(
-                params
-                    .text_document_position_params
-                    .text_document
-                    .uri
-                    .as_str(),
-                "file:///root-1/main.rs"
-            );
-            assert_eq!(
-                params.text_document_position_params.position,
-                lsp::Position::new(0, 22)
-            );
-            Ok(Some(lsp::Hover {
-                contents: lsp::HoverContents::Array(vec![
-                    lsp::MarkedString::String("Test hover content.".to_string()),
-                    lsp::MarkedString::LanguageString(lsp::LanguageString {
-                        language: "Rust".to_string(),
-                        value: "let foo = 42;".to_string(),
-                    }),
-                ]),
-                range: Some(lsp::Range::new(
-                    lsp::Position::new(0, 22),
-                    lsp::Position::new(0, 29),
-                )),
-            }))
-        },
-    );
-
-    let hover_info = project_b
-        .update(cx_b, |p, cx| p.hover(&buffer_b, 22, cx))
-        .await
-        .unwrap()
-        .unwrap();
-
-    buffer_b.read_with(cx_b, |buffer, _| {
-        let snapshot = buffer.snapshot();
-        assert_eq!(hover_info.range.unwrap().to_offset(&snapshot), 22..29);
-        assert_eq!(
-            hover_info.contents,
-            vec![
-                project::HoverBlock {
-                    text: "Test hover content.".to_string(),
-                    kind: HoverBlockKind::Markdown,
-                },
-                project::HoverBlock {
-                    text: "let foo = 42;".to_string(),
-                    kind: HoverBlockKind::Code {
-                        language: "Rust".to_string()
-                    },
-                }
-            ]
-        );
-    });
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_project_symbols(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    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(Default::default()).await;
-    client_a.language_registry().add(Arc::new(language));
-
-    client_a
-        .fs()
-        .insert_tree(
-            "/code",
-            json!({
-                "crate-1": {
-                    "one.rs": "const ONE: usize = 1;",
-                },
-                "crate-2": {
-                    "two.rs": "const TWO: usize = 2; const THREE: usize = 3;",
-                },
-                "private": {
-                    "passwords.txt": "the-password",
-                }
-            }),
-        )
-        .await;
-    let (project_a, worktree_id) = client_a.build_local_project("/code/crate-1", 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;
-
-    // Cause the language server to start.
-    let open_buffer_task =
-        project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx));
-    let _buffer = cx_b.executor().spawn(open_buffer_task).await.unwrap();
-
-    let fake_language_server = fake_language_servers.next().await.unwrap();
-    fake_language_server.handle_request::<lsp::WorkspaceSymbolRequest, _, _>(|_, _| async move {
-        Ok(Some(lsp::WorkspaceSymbolResponse::Flat(vec![
-            #[allow(deprecated)]
-            lsp::SymbolInformation {
-                name: "TWO".into(),
-                location: lsp::Location {
-                    uri: lsp::Url::from_file_path("/code/crate-2/two.rs").unwrap(),
-                    range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
-                },
-                kind: lsp::SymbolKind::CONSTANT,
-                tags: None,
-                container_name: None,
-                deprecated: None,
-            },
-        ])))
-    });
-
-    // Request the definition of a symbol as the guest.
-    let symbols = project_b
-        .update(cx_b, |p, cx| p.symbols("two", cx))
-        .await
-        .unwrap();
-    assert_eq!(symbols.len(), 1);
-    assert_eq!(symbols[0].name, "TWO");
-
-    // Open one of the returned symbols.
-    let buffer_b_2 = project_b
-        .update(cx_b, |project, cx| {
-            project.open_buffer_for_symbol(&symbols[0], cx)
-        })
-        .await
-        .unwrap();
-
-    buffer_b_2.read_with(cx_b, |buffer, _| {
-        assert_eq!(
-            buffer.file().unwrap().path().as_ref(),
-            Path::new("../crate-2/two.rs")
-        );
-    });
-
-    // Attempt to craft a symbol and violate host's privacy by opening an arbitrary file.
-    let mut fake_symbol = symbols[0].clone();
-    fake_symbol.path.path = Path::new("/code/secrets").into();
-    let error = project_b
-        .update(cx_b, |project, cx| {
-            project.open_buffer_for_symbol(&fake_symbol, cx)
-        })
-        .await
-        .unwrap_err();
-    assert!(error.to_string().contains("invalid symbol signature"));
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_open_buffer_while_getting_definition_pointing_to_it(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-    mut rng: StdRng,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    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(Default::default()).await;
-    client_a.language_registry().add(Arc::new(language));
-
-    client_a
-        .fs()
-        .insert_tree(
-            "/root",
-            json!({
-                "a.rs": "const ONE: usize = b::TWO;",
-                "b.rs": "const TWO: usize = 2",
-            }),
-        )
-        .await;
-    let (project_a, worktree_id) = client_a.build_local_project("/root", 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 open_buffer_task = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
-    let buffer_b1 = cx_b.executor().spawn(open_buffer_task).await.unwrap();
-
-    let fake_language_server = fake_language_servers.next().await.unwrap();
-    fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
-        Ok(Some(lsp::GotoDefinitionResponse::Scalar(
-            lsp::Location::new(
-                lsp::Url::from_file_path("/root/b.rs").unwrap(),
-                lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
-            ),
-        )))
-    });
-
-    let definitions;
-    let buffer_b2;
-    if rng.gen() {
-        definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
-        buffer_b2 = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.rs"), cx));
-    } else {
-        buffer_b2 = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.rs"), cx));
-        definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
-    }
-
-    let buffer_b2 = buffer_b2.await.unwrap();
-    let definitions = definitions.await.unwrap();
-    assert_eq!(definitions.len(), 1);
-    assert_eq!(definitions[0].target.buffer, buffer_b2);
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_contacts(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-    cx_c: &mut TestAppContext,
-    cx_d: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    let client_c = server.create_client(cx_c, "user_c").await;
-    let client_d = server.create_client(cx_d, "user_d").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);
-    let _active_call_d = cx_d.read(ActiveCall::global);
-
-    executor.run_until_parked();
-    assert_eq!(
-        contacts(&client_a, cx_a),
-        [
-            ("user_b".to_string(), "online", "free"),
-            ("user_c".to_string(), "online", "free")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_b, cx_b),
-        [
-            ("user_a".to_string(), "online", "free"),
-            ("user_c".to_string(), "online", "free")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_c, cx_c),
-        [
-            ("user_a".to_string(), "online", "free"),
-            ("user_b".to_string(), "online", "free")
-        ]
-    );
-    assert_eq!(contacts(&client_d, cx_d), []);
-
-    server.disconnect_client(client_c.peer_id().unwrap());
-    server.forbid_connections();
-    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
-    assert_eq!(
-        contacts(&client_a, cx_a),
-        [
-            ("user_b".to_string(), "online", "free"),
-            ("user_c".to_string(), "offline", "free")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_b, cx_b),
-        [
-            ("user_a".to_string(), "online", "free"),
-            ("user_c".to_string(), "offline", "free")
-        ]
-    );
-    assert_eq!(contacts(&client_c, cx_c), []);
-    assert_eq!(contacts(&client_d, cx_d), []);
-
-    server.allow_connections();
-    client_c
-        .authenticate_and_connect(false, &cx_c.to_async())
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-    assert_eq!(
-        contacts(&client_a, cx_a),
-        [
-            ("user_b".to_string(), "online", "free"),
-            ("user_c".to_string(), "online", "free")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_b, cx_b),
-        [
-            ("user_a".to_string(), "online", "free"),
-            ("user_c".to_string(), "online", "free")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_c, cx_c),
-        [
-            ("user_a".to_string(), "online", "free"),
-            ("user_b".to_string(), "online", "free")
-        ]
-    );
-    assert_eq!(contacts(&client_d, cx_d), []);
-
-    active_call_a
-        .update(cx_a, |call, cx| {
-            call.invite(client_b.user_id().unwrap(), None, cx)
-        })
-        .await
-        .unwrap();
-    executor.run_until_parked();
-    assert_eq!(
-        contacts(&client_a, cx_a),
-        [
-            ("user_b".to_string(), "online", "busy"),
-            ("user_c".to_string(), "online", "free")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_b, cx_b),
-        [
-            ("user_a".to_string(), "online", "busy"),
-            ("user_c".to_string(), "online", "free")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_c, cx_c),
-        [
-            ("user_a".to_string(), "online", "busy"),
-            ("user_b".to_string(), "online", "busy")
-        ]
-    );
-    assert_eq!(contacts(&client_d, cx_d), []);
-
-    // Client B and client D become contacts while client B is being called.
-    server
-        .make_contacts(&mut [(&client_b, cx_b), (&client_d, cx_d)])
-        .await;
-    executor.run_until_parked();
-    assert_eq!(
-        contacts(&client_a, cx_a),
-        [
-            ("user_b".to_string(), "online", "busy"),
-            ("user_c".to_string(), "online", "free")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_b, cx_b),
-        [
-            ("user_a".to_string(), "online", "busy"),
-            ("user_c".to_string(), "online", "free"),
-            ("user_d".to_string(), "online", "free"),
-        ]
-    );
-    assert_eq!(
-        contacts(&client_c, cx_c),
-        [
-            ("user_a".to_string(), "online", "busy"),
-            ("user_b".to_string(), "online", "busy")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_d, cx_d),
-        [("user_b".to_string(), "online", "busy")]
-    );
-
-    active_call_b.update(cx_b, |call, cx| call.decline_incoming(cx).unwrap());
-    executor.run_until_parked();
-    assert_eq!(
-        contacts(&client_a, cx_a),
-        [
-            ("user_b".to_string(), "online", "free"),
-            ("user_c".to_string(), "online", "free")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_b, cx_b),
-        [
-            ("user_a".to_string(), "online", "free"),
-            ("user_c".to_string(), "online", "free"),
-            ("user_d".to_string(), "online", "free")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_c, cx_c),
-        [
-            ("user_a".to_string(), "online", "free"),
-            ("user_b".to_string(), "online", "free")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_d, cx_d),
-        [("user_b".to_string(), "online", "free")]
-    );
-
-    active_call_c
-        .update(cx_c, |call, cx| {
-            call.invite(client_a.user_id().unwrap(), None, cx)
-        })
-        .await
-        .unwrap();
-    executor.run_until_parked();
-    assert_eq!(
-        contacts(&client_a, cx_a),
-        [
-            ("user_b".to_string(), "online", "free"),
-            ("user_c".to_string(), "online", "busy")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_b, cx_b),
-        [
-            ("user_a".to_string(), "online", "busy"),
-            ("user_c".to_string(), "online", "busy"),
-            ("user_d".to_string(), "online", "free")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_c, cx_c),
-        [
-            ("user_a".to_string(), "online", "busy"),
-            ("user_b".to_string(), "online", "free")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_d, cx_d),
-        [("user_b".to_string(), "online", "free")]
-    );
-
-    active_call_a
-        .update(cx_a, |call, cx| call.accept_incoming(cx))
-        .await
-        .unwrap();
-    executor.run_until_parked();
-    assert_eq!(
-        contacts(&client_a, cx_a),
-        [
-            ("user_b".to_string(), "online", "free"),
-            ("user_c".to_string(), "online", "busy")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_b, cx_b),
-        [
-            ("user_a".to_string(), "online", "busy"),
-            ("user_c".to_string(), "online", "busy"),
-            ("user_d".to_string(), "online", "free")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_c, cx_c),
-        [
-            ("user_a".to_string(), "online", "busy"),
-            ("user_b".to_string(), "online", "free")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_d, cx_d),
-        [("user_b".to_string(), "online", "free")]
-    );
-
-    active_call_a
-        .update(cx_a, |call, cx| {
-            call.invite(client_b.user_id().unwrap(), None, cx)
-        })
-        .await
-        .unwrap();
-    executor.run_until_parked();
-    assert_eq!(
-        contacts(&client_a, cx_a),
-        [
-            ("user_b".to_string(), "online", "busy"),
-            ("user_c".to_string(), "online", "busy")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_b, cx_b),
-        [
-            ("user_a".to_string(), "online", "busy"),
-            ("user_c".to_string(), "online", "busy"),
-            ("user_d".to_string(), "online", "free")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_c, cx_c),
-        [
-            ("user_a".to_string(), "online", "busy"),
-            ("user_b".to_string(), "online", "busy")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_d, cx_d),
-        [("user_b".to_string(), "online", "busy")]
-    );
-
-    active_call_a
-        .update(cx_a, |call, cx| call.hang_up(cx))
-        .await
-        .unwrap();
-    executor.run_until_parked();
-    assert_eq!(
-        contacts(&client_a, cx_a),
-        [
-            ("user_b".to_string(), "online", "free"),
-            ("user_c".to_string(), "online", "free")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_b, cx_b),
-        [
-            ("user_a".to_string(), "online", "free"),
-            ("user_c".to_string(), "online", "free"),
-            ("user_d".to_string(), "online", "free")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_c, cx_c),
-        [
-            ("user_a".to_string(), "online", "free"),
-            ("user_b".to_string(), "online", "free")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_d, cx_d),
-        [("user_b".to_string(), "online", "free")]
-    );
-
-    active_call_a
-        .update(cx_a, |call, cx| {
-            call.invite(client_b.user_id().unwrap(), None, cx)
-        })
-        .await
-        .unwrap();
-    executor.run_until_parked();
-    assert_eq!(
-        contacts(&client_a, cx_a),
-        [
-            ("user_b".to_string(), "online", "busy"),
-            ("user_c".to_string(), "online", "free")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_b, cx_b),
-        [
-            ("user_a".to_string(), "online", "busy"),
-            ("user_c".to_string(), "online", "free"),
-            ("user_d".to_string(), "online", "free")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_c, cx_c),
-        [
-            ("user_a".to_string(), "online", "busy"),
-            ("user_b".to_string(), "online", "busy")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_d, cx_d),
-        [("user_b".to_string(), "online", "busy")]
-    );
-
-    server.forbid_connections();
-    server.disconnect_client(client_a.peer_id().unwrap());
-    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
-    assert_eq!(contacts(&client_a, cx_a), []);
-    assert_eq!(
-        contacts(&client_b, cx_b),
-        [
-            ("user_a".to_string(), "offline", "free"),
-            ("user_c".to_string(), "online", "free"),
-            ("user_d".to_string(), "online", "free")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_c, cx_c),
-        [
-            ("user_a".to_string(), "offline", "free"),
-            ("user_b".to_string(), "online", "free")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_d, cx_d),
-        [("user_b".to_string(), "online", "free")]
-    );
-
-    // Test removing a contact
-    client_b
-        .user_store()
-        .update(cx_b, |store, cx| {
-            store.remove_contact(client_c.user_id().unwrap(), cx)
-        })
-        .await
-        .unwrap();
-    executor.run_until_parked();
-    assert_eq!(
-        contacts(&client_b, cx_b),
-        [
-            ("user_a".to_string(), "offline", "free"),
-            ("user_d".to_string(), "online", "free")
-        ]
-    );
-    assert_eq!(
-        contacts(&client_c, cx_c),
-        [("user_a".to_string(), "offline", "free"),]
-    );
-
-    fn contacts(
-        client: &TestClient,
-        cx: &TestAppContext,
-    ) -> Vec<(String, &'static str, &'static str)> {
-        client.user_store().read_with(cx, |store, _| {
-            store
-                .contacts()
-                .iter()
-                .map(|contact| {
-                    (
-                        contact.user.github_login.clone(),
-                        if contact.online { "online" } else { "offline" },
-                        if contact.busy { "busy" } else { "free" },
-                    )
-                })
-                .collect()
-        })
-    }
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_contact_requests(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_a2: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-    cx_b2: &mut TestAppContext,
-    cx_c: &mut TestAppContext,
-    cx_c2: &mut TestAppContext,
-) {
-    // Connect to a server as 3 clients.
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_a2 = server.create_client(cx_a2, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    let client_b2 = server.create_client(cx_b2, "user_b").await;
-    let client_c = server.create_client(cx_c, "user_c").await;
-    let client_c2 = server.create_client(cx_c2, "user_c").await;
-
-    assert_eq!(client_a.user_id().unwrap(), client_a2.user_id().unwrap());
-    assert_eq!(client_b.user_id().unwrap(), client_b2.user_id().unwrap());
-    assert_eq!(client_c.user_id().unwrap(), client_c2.user_id().unwrap());
-
-    // User A and User C request that user B become their contact.
-    client_a
-        .user_store()
-        .update(cx_a, |store, cx| {
-            store.request_contact(client_b.user_id().unwrap(), cx)
-        })
-        .await
-        .unwrap();
-    client_c
-        .user_store()
-        .update(cx_c, |store, cx| {
-            store.request_contact(client_b.user_id().unwrap(), cx)
-        })
-        .await
-        .unwrap();
-    executor.run_until_parked();
-
-    // All users see the pending request appear in all their clients.
-    assert_eq!(
-        client_a.summarize_contacts(cx_a).outgoing_requests,
-        &["user_b"]
-    );
-    assert_eq!(
-        client_a2.summarize_contacts(cx_a2).outgoing_requests,
-        &["user_b"]
-    );
-    assert_eq!(
-        client_b.summarize_contacts(cx_b).incoming_requests,
-        &["user_a", "user_c"]
-    );
-    assert_eq!(
-        client_b2.summarize_contacts(cx_b2).incoming_requests,
-        &["user_a", "user_c"]
-    );
-    assert_eq!(
-        client_c.summarize_contacts(cx_c).outgoing_requests,
-        &["user_b"]
-    );
-    assert_eq!(
-        client_c2.summarize_contacts(cx_c2).outgoing_requests,
-        &["user_b"]
-    );
-
-    // Contact requests are present upon connecting (tested here via disconnect/reconnect)
-    disconnect_and_reconnect(&client_a, cx_a).await;
-    disconnect_and_reconnect(&client_b, cx_b).await;
-    disconnect_and_reconnect(&client_c, cx_c).await;
-    executor.run_until_parked();
-    assert_eq!(
-        client_a.summarize_contacts(cx_a).outgoing_requests,
-        &["user_b"]
-    );
-    assert_eq!(
-        client_b.summarize_contacts(cx_b).incoming_requests,
-        &["user_a", "user_c"]
-    );
-    assert_eq!(
-        client_c.summarize_contacts(cx_c).outgoing_requests,
-        &["user_b"]
-    );
-
-    // User B accepts the request from user A.
-    client_b
-        .user_store()
-        .update(cx_b, |store, cx| {
-            store.respond_to_contact_request(client_a.user_id().unwrap(), true, cx)
-        })
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-
-    // User B sees user A as their contact now in all client, and the incoming request from them is removed.
-    let contacts_b = client_b.summarize_contacts(cx_b);
-    assert_eq!(contacts_b.current, &["user_a"]);
-    assert_eq!(contacts_b.incoming_requests, &["user_c"]);
-    let contacts_b2 = client_b2.summarize_contacts(cx_b2);
-    assert_eq!(contacts_b2.current, &["user_a"]);
-    assert_eq!(contacts_b2.incoming_requests, &["user_c"]);
-
-    // User A sees user B as their contact now in all clients, and the outgoing request to them is removed.
-    let contacts_a = client_a.summarize_contacts(cx_a);
-    assert_eq!(contacts_a.current, &["user_b"]);
-    assert!(contacts_a.outgoing_requests.is_empty());
-    let contacts_a2 = client_a2.summarize_contacts(cx_a2);
-    assert_eq!(contacts_a2.current, &["user_b"]);
-    assert!(contacts_a2.outgoing_requests.is_empty());
-
-    // Contacts are present upon connecting (tested here via disconnect/reconnect)
-    disconnect_and_reconnect(&client_a, cx_a).await;
-    disconnect_and_reconnect(&client_b, cx_b).await;
-    disconnect_and_reconnect(&client_c, cx_c).await;
-    executor.run_until_parked();
-    assert_eq!(client_a.summarize_contacts(cx_a).current, &["user_b"]);
-    assert_eq!(client_b.summarize_contacts(cx_b).current, &["user_a"]);
-    assert_eq!(
-        client_b.summarize_contacts(cx_b).incoming_requests,
-        &["user_c"]
-    );
-    assert!(client_c.summarize_contacts(cx_c).current.is_empty());
-    assert_eq!(
-        client_c.summarize_contacts(cx_c).outgoing_requests,
-        &["user_b"]
-    );
-
-    // User B rejects the request from user C.
-    client_b
-        .user_store()
-        .update(cx_b, |store, cx| {
-            store.respond_to_contact_request(client_c.user_id().unwrap(), false, cx)
-        })
-        .await
-        .unwrap();
-
-    executor.run_until_parked();
-
-    // User B doesn't see user C as their contact, and the incoming request from them is removed.
-    let contacts_b = client_b.summarize_contacts(cx_b);
-    assert_eq!(contacts_b.current, &["user_a"]);
-    assert!(contacts_b.incoming_requests.is_empty());
-    let contacts_b2 = client_b2.summarize_contacts(cx_b2);
-    assert_eq!(contacts_b2.current, &["user_a"]);
-    assert!(contacts_b2.incoming_requests.is_empty());
-
-    // User C doesn't see user B as their contact, and the outgoing request to them is removed.
-    let contacts_c = client_c.summarize_contacts(cx_c);
-    assert!(contacts_c.current.is_empty());
-    assert!(contacts_c.outgoing_requests.is_empty());
-    let contacts_c2 = client_c2.summarize_contacts(cx_c2);
-    assert!(contacts_c2.current.is_empty());
-    assert!(contacts_c2.outgoing_requests.is_empty());
-
-    // Incoming/outgoing requests are not present upon connecting (tested here via disconnect/reconnect)
-    disconnect_and_reconnect(&client_a, cx_a).await;
-    disconnect_and_reconnect(&client_b, cx_b).await;
-    disconnect_and_reconnect(&client_c, cx_c).await;
-    executor.run_until_parked();
-    assert_eq!(client_a.summarize_contacts(cx_a).current, &["user_b"]);
-    assert_eq!(client_b.summarize_contacts(cx_b).current, &["user_a"]);
-    assert!(client_b
-        .summarize_contacts(cx_b)
-        .incoming_requests
-        .is_empty());
-    assert!(client_c.summarize_contacts(cx_c).current.is_empty());
-    assert!(client_c
-        .summarize_contacts(cx_c)
-        .outgoing_requests
-        .is_empty());
-
-    async fn disconnect_and_reconnect(client: &TestClient, cx: &mut TestAppContext) {
-        client.disconnect(&cx.to_async());
-        client.clear_contacts(cx).await;
-        client
-            .authenticate_and_connect(false, &cx.to_async())
-            .await
-            .unwrap();
-    }
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_join_call_after_screen_was_shared(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    server
-        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-        .await;
-
-    let active_call_a = cx_a.read(ActiveCall::global);
-    let active_call_b = cx_b.read(ActiveCall::global);
-
-    // Call users B and C from client A.
-    active_call_a
-        .update(cx_a, |call, cx| {
-            call.invite(client_b.user_id().unwrap(), None, cx)
-        })
-        .await
-        .unwrap();
-
-    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
-    executor.run_until_parked();
-    assert_eq!(
-        room_participants(&room_a, cx_a),
-        RoomParticipants {
-            remote: Default::default(),
-            pending: vec!["user_b".to_string()]
-        }
-    );
-
-    // User B receives the call.
-
-    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
-    let call_b = incoming_call_b.next().await.unwrap().unwrap();
-    assert_eq!(call_b.calling_user.github_login, "user_a");
-
-    // User A shares their screen
-    let display = MacOSDisplay::new();
-    active_call_a
-        .update(cx_a, |call, cx| {
-            call.room().unwrap().update(cx, |room, cx| {
-                room.set_display_sources(vec![display.clone()]);
-                room.share_screen(cx)
-            })
-        })
-        .await
-        .unwrap();
-
-    client_b.user_store().update(cx_b, |user_store, _| {
-        user_store.clear_cache();
-    });
-
-    // User B joins the room
-    active_call_b
-        .update(cx_b, |call, cx| call.accept_incoming(cx))
-        .await
-        .unwrap();
-
-    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
-    assert!(incoming_call_b.next().await.unwrap().is_none());
-
-    executor.run_until_parked();
-    assert_eq!(
-        room_participants(&room_a, cx_a),
-        RoomParticipants {
-            remote: vec!["user_b".to_string()],
-            pending: vec![],
-        }
-    );
-    assert_eq!(
-        room_participants(&room_b, cx_b),
-        RoomParticipants {
-            remote: vec!["user_a".to_string()],
-            pending: vec![],
-        }
-    );
-
-    // Ensure User B sees User A's screenshare.
-
-    room_b.read_with(cx_b, |room, _| {
-        assert_eq!(
-            room.remote_participants()
-                .get(&client_a.user_id().unwrap())
-                .unwrap()
-                .video_tracks
-                .len(),
-            1
-        );
-    });
-}

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

@@ -1,160 +0,0 @@
-use std::sync::Arc;
-
-use gpui::{BackgroundExecutor, TestAppContext};
-use notifications::NotificationEvent;
-use parking_lot::Mutex;
-use rpc::{proto, Notification};
-
-use crate::tests::TestServer;
-
-#[gpui::test]
-async fn test_notifications(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-
-    let notification_events_a = Arc::new(Mutex::new(Vec::new()));
-    let notification_events_b = Arc::new(Mutex::new(Vec::new()));
-    client_a.notification_store().update(cx_a, |_, cx| {
-        let events = notification_events_a.clone();
-        cx.subscribe(&cx.handle(), move |_, _, event, _| {
-            events.lock().push(event.clone());
-        })
-        .detach()
-    });
-    client_b.notification_store().update(cx_b, |_, cx| {
-        let events = notification_events_b.clone();
-        cx.subscribe(&cx.handle(), move |_, _, event, _| {
-            events.lock().push(event.clone());
-        })
-        .detach()
-    });
-
-    // Client A sends a contact request to client B.
-    client_a
-        .user_store()
-        .update(cx_a, |store, cx| store.request_contact(client_b.id(), cx))
-        .await
-        .unwrap();
-
-    // Client B receives a contact request notification and responds to the
-    // request, accepting it.
-    executor.run_until_parked();
-    client_b.notification_store().update(cx_b, |store, cx| {
-        assert_eq!(store.notification_count(), 1);
-        assert_eq!(store.unread_notification_count(), 1);
-
-        let entry = store.notification_at(0).unwrap();
-        assert_eq!(
-            entry.notification,
-            Notification::ContactRequest {
-                sender_id: client_a.id()
-            }
-        );
-        assert!(!entry.is_read);
-        assert_eq!(
-            &notification_events_b.lock()[0..],
-            &[
-                NotificationEvent::NewNotification {
-                    entry: entry.clone(),
-                },
-                NotificationEvent::NotificationsUpdated {
-                    old_range: 0..0,
-                    new_count: 1
-                }
-            ]
-        );
-
-        store.respond_to_notification(entry.notification.clone(), true, cx);
-    });
-
-    // Client B sees the notification is now read, and that they responded.
-    executor.run_until_parked();
-    client_b.notification_store().read_with(cx_b, |store, _| {
-        assert_eq!(store.notification_count(), 1);
-        assert_eq!(store.unread_notification_count(), 0);
-
-        let entry = store.notification_at(0).unwrap();
-        assert!(entry.is_read);
-        assert_eq!(entry.response, Some(true));
-        assert_eq!(
-            &notification_events_b.lock()[2..],
-            &[
-                NotificationEvent::NotificationRead {
-                    entry: entry.clone(),
-                },
-                NotificationEvent::NotificationsUpdated {
-                    old_range: 0..1,
-                    new_count: 1
-                }
-            ]
-        );
-    });
-
-    // Client A receives a notification that client B accepted their request.
-    client_a.notification_store().read_with(cx_a, |store, _| {
-        assert_eq!(store.notification_count(), 1);
-        assert_eq!(store.unread_notification_count(), 1);
-
-        let entry = store.notification_at(0).unwrap();
-        assert_eq!(
-            entry.notification,
-            Notification::ContactRequestAccepted {
-                responder_id: client_b.id()
-            }
-        );
-        assert!(!entry.is_read);
-    });
-
-    // Client A creates a channel and invites client B to be a member.
-    let channel_id = client_a
-        .channel_store()
-        .update(cx_a, |store, cx| {
-            store.create_channel("the-channel", None, cx)
-        })
-        .await
-        .unwrap();
-    client_a
-        .channel_store()
-        .update(cx_a, |store, cx| {
-            store.invite_member(channel_id, client_b.id(), proto::ChannelRole::Member, cx)
-        })
-        .await
-        .unwrap();
-
-    // Client B receives a channel invitation notification and responds to the
-    // invitation, accepting it.
-    executor.run_until_parked();
-    client_b.notification_store().update(cx_b, |store, cx| {
-        assert_eq!(store.notification_count(), 2);
-        assert_eq!(store.unread_notification_count(), 1);
-
-        let entry = store.notification_at(0).unwrap();
-        assert_eq!(
-            entry.notification,
-            Notification::ChannelInvitation {
-                channel_id,
-                channel_name: "the-channel".to_string(),
-                inviter_id: client_a.id()
-            }
-        );
-        assert!(!entry.is_read);
-
-        store.respond_to_notification(entry.notification.clone(), true, cx);
-    });
-
-    // Client B sees the notification is now read, and that they responded.
-    executor.run_until_parked();
-    client_b.notification_store().read_with(cx_b, |store, _| {
-        assert_eq!(store.notification_count(), 2);
-        assert_eq!(store.unread_notification_count(), 0);
-
-        let entry = store.notification_at(0).unwrap();
-        assert!(entry.is_read);
-        assert_eq!(entry.response, Some(true));
-    });
-}

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

@@ -1,288 +0,0 @@
-use crate::db::ChannelRole;
-
-use super::{run_randomized_test, RandomizedTest, TestClient, TestError, TestServer, UserTestPlan};
-use anyhow::Result;
-use async_trait::async_trait;
-use gpui::{BackgroundExecutor, SharedString, TestAppContext};
-use rand::prelude::*;
-use serde_derive::{Deserialize, Serialize};
-use std::{
-    ops::{Deref, DerefMut, Range},
-    rc::Rc,
-    sync::Arc,
-};
-use text::Bias;
-
-#[gpui::test(
-    iterations = 100,
-    on_failure = "crate::tests::save_randomized_test_plan"
-)]
-async fn test_random_channel_buffers(
-    cx: &mut TestAppContext,
-    executor: BackgroundExecutor,
-    rng: StdRng,
-) {
-    run_randomized_test::<RandomChannelBufferTest>(cx, executor, rng).await;
-}
-
-struct RandomChannelBufferTest;
-
-#[derive(Clone, Serialize, Deserialize)]
-enum ChannelBufferOperation {
-    JoinChannelNotes {
-        channel_name: SharedString,
-    },
-    LeaveChannelNotes {
-        channel_name: SharedString,
-    },
-    EditChannelNotes {
-        channel_name: SharedString,
-        edits: Vec<(Range<usize>, Arc<str>)>,
-    },
-    Noop,
-}
-
-const CHANNEL_COUNT: usize = 3;
-
-#[async_trait(?Send)]
-impl RandomizedTest for RandomChannelBufferTest {
-    type Operation = ChannelBufferOperation;
-
-    async fn initialize(server: &mut TestServer, users: &[UserTestPlan]) {
-        let db = &server.app_state.db;
-        for ix in 0..CHANNEL_COUNT {
-            let id = db
-                .create_root_channel(&format!("channel-{ix}"), users[0].user_id)
-                .await
-                .unwrap();
-            for user in &users[1..] {
-                db.invite_channel_member(id, user.user_id, users[0].user_id, ChannelRole::Member)
-                    .await
-                    .unwrap();
-                db.respond_to_channel_invite(id, user.user_id, true)
-                    .await
-                    .unwrap();
-            }
-        }
-    }
-
-    fn generate_operation(
-        client: &TestClient,
-        rng: &mut StdRng,
-        _: &mut UserTestPlan,
-        cx: &TestAppContext,
-    ) -> ChannelBufferOperation {
-        let channel_store = client.channel_store().clone();
-        let mut channel_buffers = client.channel_buffers();
-
-        // When signed out, we can't do anything unless a channel buffer is
-        // already open.
-        if channel_buffers.deref_mut().is_empty()
-            && channel_store.read_with(cx, |store, _| store.channel_count() == 0)
-        {
-            return ChannelBufferOperation::Noop;
-        }
-
-        loop {
-            match rng.gen_range(0..100_u32) {
-                0..=29 => {
-                    let channel_name = client.channel_store().read_with(cx, |store, cx| {
-                        store.ordered_channels().find_map(|(_, channel)| {
-                            if store.has_open_channel_buffer(channel.id, cx) {
-                                None
-                            } else {
-                                Some(channel.name.clone())
-                            }
-                        })
-                    });
-                    if let Some(channel_name) = channel_name {
-                        break ChannelBufferOperation::JoinChannelNotes { channel_name };
-                    }
-                }
-
-                30..=40 => {
-                    if let Some(buffer) = channel_buffers.deref().iter().choose(rng) {
-                        let channel_name =
-                            buffer.read_with(cx, |b, cx| b.channel(cx).unwrap().name.clone());
-                        break ChannelBufferOperation::LeaveChannelNotes { channel_name };
-                    }
-                }
-
-                _ => {
-                    if let Some(buffer) = channel_buffers.deref().iter().choose(rng) {
-                        break buffer.read_with(cx, |b, cx| {
-                            let channel_name = b.channel(cx).unwrap().name.clone();
-                            let edits = b
-                                .buffer()
-                                .read_with(cx, |buffer, _| buffer.get_random_edits(rng, 3));
-                            ChannelBufferOperation::EditChannelNotes {
-                                channel_name,
-                                edits,
-                            }
-                        });
-                    }
-                }
-            }
-        }
-    }
-
-    async fn apply_operation(
-        client: &TestClient,
-        operation: ChannelBufferOperation,
-        cx: &mut TestAppContext,
-    ) -> Result<(), TestError> {
-        match operation {
-            ChannelBufferOperation::JoinChannelNotes { channel_name } => {
-                let buffer = client.channel_store().update(cx, |store, cx| {
-                    let channel_id = store
-                        .ordered_channels()
-                        .find(|(_, c)| c.name == channel_name)
-                        .unwrap()
-                        .1
-                        .id;
-                    if store.has_open_channel_buffer(channel_id, cx) {
-                        Err(TestError::Inapplicable)
-                    } else {
-                        Ok(store.open_channel_buffer(channel_id, cx))
-                    }
-                })?;
-
-                log::info!(
-                    "{}: opening notes for channel {channel_name}",
-                    client.username
-                );
-                client.channel_buffers().deref_mut().insert(buffer.await?);
-            }
-
-            ChannelBufferOperation::LeaveChannelNotes { channel_name } => {
-                let buffer = cx.update(|cx| {
-                    let mut left_buffer = Err(TestError::Inapplicable);
-                    client.channel_buffers().deref_mut().retain(|buffer| {
-                        if buffer.read(cx).channel(cx).unwrap().name == channel_name {
-                            left_buffer = Ok(buffer.clone());
-                            false
-                        } else {
-                            true
-                        }
-                    });
-                    left_buffer
-                })?;
-
-                log::info!(
-                    "{}: closing notes for channel {channel_name}",
-                    client.username
-                );
-                cx.update(|_| drop(buffer));
-            }
-
-            ChannelBufferOperation::EditChannelNotes {
-                channel_name,
-                edits,
-            } => {
-                let channel_buffer = cx
-                    .read(|cx| {
-                        client
-                            .channel_buffers()
-                            .deref()
-                            .iter()
-                            .find(|buffer| {
-                                buffer.read(cx).channel(cx).unwrap().name == channel_name
-                            })
-                            .cloned()
-                    })
-                    .ok_or_else(|| TestError::Inapplicable)?;
-
-                log::info!(
-                    "{}: editing notes for channel {channel_name} with {:?}",
-                    client.username,
-                    edits
-                );
-
-                channel_buffer.update(cx, |buffer, cx| {
-                    let buffer = buffer.buffer();
-                    buffer.update(cx, |buffer, cx| {
-                        let snapshot = buffer.snapshot();
-                        buffer.edit(
-                            edits.into_iter().map(|(range, text)| {
-                                let start = snapshot.clip_offset(range.start, Bias::Left);
-                                let end = snapshot.clip_offset(range.end, Bias::Right);
-                                (start..end, text)
-                            }),
-                            None,
-                            cx,
-                        );
-                    });
-                });
-            }
-
-            ChannelBufferOperation::Noop => Err(TestError::Inapplicable)?,
-        }
-        Ok(())
-    }
-
-    async fn on_quiesce(server: &mut TestServer, clients: &mut [(Rc<TestClient>, TestAppContext)]) {
-        let channels = server.app_state.db.all_channels().await.unwrap();
-
-        for (client, client_cx) in clients.iter_mut() {
-            client_cx.update(|cx| {
-                client
-                    .channel_buffers()
-                    .deref_mut()
-                    .retain(|b| b.read(cx).is_connected());
-            });
-        }
-
-        for (channel_id, channel_name) in channels {
-            let mut prev_text: Option<(u64, String)> = None;
-
-            let mut collaborator_user_ids = server
-                .app_state
-                .db
-                .get_channel_buffer_collaborators(channel_id)
-                .await
-                .unwrap()
-                .into_iter()
-                .map(|id| id.to_proto())
-                .collect::<Vec<_>>();
-            collaborator_user_ids.sort();
-
-            for (client, client_cx) in clients.iter() {
-                let user_id = client.user_id().unwrap();
-                client_cx.read(|cx| {
-                    if let Some(channel_buffer) = client
-                        .channel_buffers()
-                        .deref()
-                        .iter()
-                        .find(|b| b.read(cx).channel_id == channel_id.to_proto())
-                    {
-                        let channel_buffer = channel_buffer.read(cx);
-
-                        // Assert that channel buffer's text matches other clients' copies.
-                        let text = channel_buffer.buffer().read(cx).text();
-                        if let Some((prev_user_id, prev_text)) = &prev_text {
-                            assert_eq!(
-                                &text,
-                                prev_text,
-                                "client {user_id} has different text than client {prev_user_id} for channel {channel_name}",
-                            );
-                        } else {
-                            prev_text = Some((user_id, text.clone()));
-                        }
-
-                        // Assert that all clients and the server agree about who is present in the
-                        // channel buffer.
-                        let collaborators = channel_buffer.collaborators();
-                        let mut user_ids =
-                            collaborators.values().map(|c| c.user_id).collect::<Vec<_>>();
-                        user_ids.sort();
-                        assert_eq!(
-                            user_ids,
-                            collaborator_user_ids,
-                            "client {user_id} has different user ids for channel {channel_name} than the server",
-                        );
-                    }
-                });
-            }
-        }
-    }
-}

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

@@ -1,1587 +0,0 @@
-use super::{RandomizedTest, TestClient, TestError, TestServer, UserTestPlan};
-use crate::{db::UserId, tests::run_randomized_test};
-use anyhow::{anyhow, Result};
-use async_trait::async_trait;
-use call::ActiveCall;
-use collections::{BTreeMap, HashMap};
-use editor::Bias;
-use fs::{repository::GitFileStatus, FakeFs, Fs as _};
-use futures::StreamExt;
-use gpui::{BackgroundExecutor, Model, TestAppContext};
-use language::{range_to_lsp, FakeLspAdapter, Language, LanguageConfig, PointUtf16};
-use lsp::FakeLanguageServer;
-use pretty_assertions::assert_eq;
-use project::{search::SearchQuery, Project, ProjectPath};
-use rand::{
-    distributions::{Alphanumeric, DistString},
-    prelude::*,
-};
-use serde::{Deserialize, Serialize};
-use std::{
-    ops::{Deref, Range},
-    path::{Path, PathBuf},
-    rc::Rc,
-    sync::Arc,
-};
-use util::ResultExt;
-
-#[gpui::test(
-    iterations = 100,
-    on_failure = "crate::tests::save_randomized_test_plan"
-)]
-async fn test_random_project_collaboration(
-    cx: &mut TestAppContext,
-    executor: BackgroundExecutor,
-    rng: StdRng,
-) {
-    run_randomized_test::<ProjectCollaborationTest>(cx, executor, rng).await;
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize)]
-enum ClientOperation {
-    AcceptIncomingCall,
-    RejectIncomingCall,
-    LeaveCall,
-    InviteContactToCall {
-        user_id: UserId,
-    },
-    OpenLocalProject {
-        first_root_name: String,
-    },
-    OpenRemoteProject {
-        host_id: UserId,
-        first_root_name: String,
-    },
-    AddWorktreeToProject {
-        project_root_name: String,
-        new_root_path: PathBuf,
-    },
-    CloseRemoteProject {
-        project_root_name: String,
-    },
-    OpenBuffer {
-        project_root_name: String,
-        is_local: bool,
-        full_path: PathBuf,
-    },
-    SearchProject {
-        project_root_name: String,
-        is_local: bool,
-        query: String,
-        detach: bool,
-    },
-    EditBuffer {
-        project_root_name: String,
-        is_local: bool,
-        full_path: PathBuf,
-        edits: Vec<(Range<usize>, Arc<str>)>,
-    },
-    CloseBuffer {
-        project_root_name: String,
-        is_local: bool,
-        full_path: PathBuf,
-    },
-    SaveBuffer {
-        project_root_name: String,
-        is_local: bool,
-        full_path: PathBuf,
-        detach: bool,
-    },
-    RequestLspDataInBuffer {
-        project_root_name: String,
-        is_local: bool,
-        full_path: PathBuf,
-        offset: usize,
-        kind: LspRequestKind,
-        detach: bool,
-    },
-    CreateWorktreeEntry {
-        project_root_name: String,
-        is_local: bool,
-        full_path: PathBuf,
-        is_dir: bool,
-    },
-    WriteFsEntry {
-        path: PathBuf,
-        is_dir: bool,
-        content: String,
-    },
-    GitOperation {
-        operation: GitOperation,
-    },
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize)]
-enum GitOperation {
-    WriteGitIndex {
-        repo_path: PathBuf,
-        contents: Vec<(PathBuf, String)>,
-    },
-    WriteGitBranch {
-        repo_path: PathBuf,
-        new_branch: Option<String>,
-    },
-    WriteGitStatuses {
-        repo_path: PathBuf,
-        statuses: Vec<(PathBuf, GitFileStatus)>,
-        git_operation: bool,
-    },
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize)]
-enum LspRequestKind {
-    Rename,
-    Completion,
-    CodeAction,
-    Definition,
-    Highlights,
-}
-
-struct ProjectCollaborationTest;
-
-#[async_trait(?Send)]
-impl RandomizedTest for ProjectCollaborationTest {
-    type Operation = ClientOperation;
-
-    async fn initialize(server: &mut TestServer, users: &[UserTestPlan]) {
-        let db = &server.app_state.db;
-        for (ix, user_a) in users.iter().enumerate() {
-            for user_b in &users[ix + 1..] {
-                db.send_contact_request(user_a.user_id, user_b.user_id)
-                    .await
-                    .unwrap();
-                db.respond_to_contact_request(user_b.user_id, user_a.user_id, true)
-                    .await
-                    .unwrap();
-            }
-        }
-    }
-
-    fn generate_operation(
-        client: &TestClient,
-        rng: &mut StdRng,
-        plan: &mut UserTestPlan,
-        cx: &TestAppContext,
-    ) -> ClientOperation {
-        let call = cx.read(ActiveCall::global);
-        loop {
-            match rng.gen_range(0..100_u32) {
-                // Mutate the call
-                0..=29 => {
-                    // Respond to an incoming call
-                    if call.read_with(cx, |call, _| call.incoming().borrow().is_some()) {
-                        break if rng.gen_bool(0.7) {
-                            ClientOperation::AcceptIncomingCall
-                        } else {
-                            ClientOperation::RejectIncomingCall
-                        };
-                    }
-
-                    match rng.gen_range(0..100_u32) {
-                        // Invite a contact to the current call
-                        0..=70 => {
-                            let available_contacts =
-                                client.user_store().read_with(cx, |user_store, _| {
-                                    user_store
-                                        .contacts()
-                                        .iter()
-                                        .filter(|contact| contact.online && !contact.busy)
-                                        .cloned()
-                                        .collect::<Vec<_>>()
-                                });
-                            if !available_contacts.is_empty() {
-                                let contact = available_contacts.choose(rng).unwrap();
-                                break ClientOperation::InviteContactToCall {
-                                    user_id: UserId(contact.user.id as i32),
-                                };
-                            }
-                        }
-
-                        // Leave the current call
-                        71.. => {
-                            if plan.allow_client_disconnection
-                                && call.read_with(cx, |call, _| call.room().is_some())
-                            {
-                                break ClientOperation::LeaveCall;
-                            }
-                        }
-                    }
-                }
-
-                // Mutate projects
-                30..=59 => match rng.gen_range(0..100_u32) {
-                    // Open a new project
-                    0..=70 => {
-                        // Open a remote project
-                        if let Some(room) = call.read_with(cx, |call, _| call.room().cloned()) {
-                            let existing_remote_project_ids = cx.read(|cx| {
-                                client
-                                    .remote_projects()
-                                    .iter()
-                                    .map(|p| p.read(cx).remote_id().unwrap())
-                                    .collect::<Vec<_>>()
-                            });
-                            let new_remote_projects = room.read_with(cx, |room, _| {
-                                room.remote_participants()
-                                    .values()
-                                    .flat_map(|participant| {
-                                        participant.projects.iter().filter_map(|project| {
-                                            if existing_remote_project_ids.contains(&project.id) {
-                                                None
-                                            } else {
-                                                Some((
-                                                    UserId::from_proto(participant.user.id),
-                                                    project.worktree_root_names[0].clone(),
-                                                ))
-                                            }
-                                        })
-                                    })
-                                    .collect::<Vec<_>>()
-                            });
-                            if !new_remote_projects.is_empty() {
-                                let (host_id, first_root_name) =
-                                    new_remote_projects.choose(rng).unwrap().clone();
-                                break ClientOperation::OpenRemoteProject {
-                                    host_id,
-                                    first_root_name,
-                                };
-                            }
-                        }
-                        // Open a local project
-                        else {
-                            let first_root_name = plan.next_root_dir_name();
-                            break ClientOperation::OpenLocalProject { first_root_name };
-                        }
-                    }
-
-                    // Close a remote project
-                    71..=80 => {
-                        if !client.remote_projects().is_empty() {
-                            let project = client.remote_projects().choose(rng).unwrap().clone();
-                            let first_root_name = root_name_for_project(&project, cx);
-                            break ClientOperation::CloseRemoteProject {
-                                project_root_name: first_root_name,
-                            };
-                        }
-                    }
-
-                    // Mutate project worktrees
-                    81.. => match rng.gen_range(0..100_u32) {
-                        // Add a worktree to a local project
-                        0..=50 => {
-                            let Some(project) = client.local_projects().choose(rng).cloned() else {
-                                continue;
-                            };
-                            let project_root_name = root_name_for_project(&project, cx);
-                            let mut paths = client.fs().paths(false);
-                            paths.remove(0);
-                            let new_root_path = if paths.is_empty() || rng.gen() {
-                                Path::new("/").join(&plan.next_root_dir_name())
-                            } else {
-                                paths.choose(rng).unwrap().clone()
-                            };
-                            break ClientOperation::AddWorktreeToProject {
-                                project_root_name,
-                                new_root_path,
-                            };
-                        }
-
-                        // Add an entry to a worktree
-                        _ => {
-                            let Some(project) = choose_random_project(client, rng) else {
-                                continue;
-                            };
-                            let project_root_name = root_name_for_project(&project, cx);
-                            let is_local = project.read_with(cx, |project, _| project.is_local());
-                            let worktree = project.read_with(cx, |project, cx| {
-                                project
-                                    .worktrees()
-                                    .filter(|worktree| {
-                                        let worktree = worktree.read(cx);
-                                        worktree.is_visible()
-                                            && worktree.entries(false).any(|e| e.is_file())
-                                            && worktree.root_entry().map_or(false, |e| e.is_dir())
-                                    })
-                                    .choose(rng)
-                            });
-                            let Some(worktree) = worktree else { continue };
-                            let is_dir = rng.gen::<bool>();
-                            let mut full_path =
-                                worktree.read_with(cx, |w, _| PathBuf::from(w.root_name()));
-                            full_path.push(gen_file_name(rng));
-                            if !is_dir {
-                                full_path.set_extension("rs");
-                            }
-                            break ClientOperation::CreateWorktreeEntry {
-                                project_root_name,
-                                is_local,
-                                full_path,
-                                is_dir,
-                            };
-                        }
-                    },
-                },
-
-                // Query and mutate buffers
-                60..=90 => {
-                    let Some(project) = choose_random_project(client, rng) else {
-                        continue;
-                    };
-                    let project_root_name = root_name_for_project(&project, cx);
-                    let is_local = project.read_with(cx, |project, _| project.is_local());
-
-                    match rng.gen_range(0..100_u32) {
-                        // Manipulate an existing buffer
-                        0..=70 => {
-                            let Some(buffer) = client
-                                .buffers_for_project(&project)
-                                .iter()
-                                .choose(rng)
-                                .cloned()
-                            else {
-                                continue;
-                            };
-
-                            let full_path = buffer
-                                .read_with(cx, |buffer, cx| buffer.file().unwrap().full_path(cx));
-
-                            match rng.gen_range(0..100_u32) {
-                                // Close the buffer
-                                0..=15 => {
-                                    break ClientOperation::CloseBuffer {
-                                        project_root_name,
-                                        is_local,
-                                        full_path,
-                                    };
-                                }
-                                // Save the buffer
-                                16..=29 if buffer.read_with(cx, |b, _| b.is_dirty()) => {
-                                    let detach = rng.gen_bool(0.3);
-                                    break ClientOperation::SaveBuffer {
-                                        project_root_name,
-                                        is_local,
-                                        full_path,
-                                        detach,
-                                    };
-                                }
-                                // Edit the buffer
-                                30..=69 => {
-                                    let edits = buffer
-                                        .read_with(cx, |buffer, _| buffer.get_random_edits(rng, 3));
-                                    break ClientOperation::EditBuffer {
-                                        project_root_name,
-                                        is_local,
-                                        full_path,
-                                        edits,
-                                    };
-                                }
-                                // Make an LSP request
-                                _ => {
-                                    let offset = buffer.read_with(cx, |buffer, _| {
-                                        buffer.clip_offset(
-                                            rng.gen_range(0..=buffer.len()),
-                                            language::Bias::Left,
-                                        )
-                                    });
-                                    let detach = rng.gen();
-                                    break ClientOperation::RequestLspDataInBuffer {
-                                        project_root_name,
-                                        full_path,
-                                        offset,
-                                        is_local,
-                                        kind: match rng.gen_range(0..5_u32) {
-                                            0 => LspRequestKind::Rename,
-                                            1 => LspRequestKind::Highlights,
-                                            2 => LspRequestKind::Definition,
-                                            3 => LspRequestKind::CodeAction,
-                                            4.. => LspRequestKind::Completion,
-                                        },
-                                        detach,
-                                    };
-                                }
-                            }
-                        }
-
-                        71..=80 => {
-                            let query = rng.gen_range('a'..='z').to_string();
-                            let detach = rng.gen_bool(0.3);
-                            break ClientOperation::SearchProject {
-                                project_root_name,
-                                is_local,
-                                query,
-                                detach,
-                            };
-                        }
-
-                        // Open a buffer
-                        81.. => {
-                            let worktree = project.read_with(cx, |project, cx| {
-                                project
-                                    .worktrees()
-                                    .filter(|worktree| {
-                                        let worktree = worktree.read(cx);
-                                        worktree.is_visible()
-                                            && worktree.entries(false).any(|e| e.is_file())
-                                    })
-                                    .choose(rng)
-                            });
-                            let Some(worktree) = worktree else { continue };
-                            let full_path = worktree.read_with(cx, |worktree, _| {
-                                let entry = worktree
-                                    .entries(false)
-                                    .filter(|e| e.is_file())
-                                    .choose(rng)
-                                    .unwrap();
-                                if entry.path.as_ref() == Path::new("") {
-                                    Path::new(worktree.root_name()).into()
-                                } else {
-                                    Path::new(worktree.root_name()).join(&entry.path)
-                                }
-                            });
-                            break ClientOperation::OpenBuffer {
-                                project_root_name,
-                                is_local,
-                                full_path,
-                            };
-                        }
-                    }
-                }
-
-                // Update a git related action
-                91..=95 => {
-                    break ClientOperation::GitOperation {
-                        operation: generate_git_operation(rng, client),
-                    };
-                }
-
-                // Create or update a file or directory
-                96.. => {
-                    let is_dir = rng.gen::<bool>();
-                    let content;
-                    let mut path;
-                    let dir_paths = client.fs().directories(false);
-
-                    if is_dir {
-                        content = String::new();
-                        path = dir_paths.choose(rng).unwrap().clone();
-                        path.push(gen_file_name(rng));
-                    } else {
-                        content = Alphanumeric.sample_string(rng, 16);
-
-                        // Create a new file or overwrite an existing file
-                        let file_paths = client.fs().files();
-                        if file_paths.is_empty() || rng.gen_bool(0.5) {
-                            path = dir_paths.choose(rng).unwrap().clone();
-                            path.push(gen_file_name(rng));
-                            path.set_extension("rs");
-                        } else {
-                            path = file_paths.choose(rng).unwrap().clone()
-                        };
-                    }
-                    break ClientOperation::WriteFsEntry {
-                        path,
-                        is_dir,
-                        content,
-                    };
-                }
-            }
-        }
-    }
-
-    async fn apply_operation(
-        client: &TestClient,
-        operation: ClientOperation,
-        cx: &mut TestAppContext,
-    ) -> Result<(), TestError> {
-        match operation {
-            ClientOperation::AcceptIncomingCall => {
-                let active_call = cx.read(ActiveCall::global);
-                if active_call.read_with(cx, |call, _| call.incoming().borrow().is_none()) {
-                    Err(TestError::Inapplicable)?;
-                }
-
-                log::info!("{}: accepting incoming call", client.username);
-                active_call
-                    .update(cx, |call, cx| call.accept_incoming(cx))
-                    .await?;
-            }
-
-            ClientOperation::RejectIncomingCall => {
-                let active_call = cx.read(ActiveCall::global);
-                if active_call.read_with(cx, |call, _| call.incoming().borrow().is_none()) {
-                    Err(TestError::Inapplicable)?;
-                }
-
-                log::info!("{}: declining incoming call", client.username);
-                active_call.update(cx, |call, cx| call.decline_incoming(cx))?;
-            }
-
-            ClientOperation::LeaveCall => {
-                let active_call = cx.read(ActiveCall::global);
-                if active_call.read_with(cx, |call, _| call.room().is_none()) {
-                    Err(TestError::Inapplicable)?;
-                }
-
-                log::info!("{}: hanging up", client.username);
-                active_call.update(cx, |call, cx| call.hang_up(cx)).await?;
-            }
-
-            ClientOperation::InviteContactToCall { user_id } => {
-                let active_call = cx.read(ActiveCall::global);
-
-                log::info!("{}: inviting {}", client.username, user_id,);
-                active_call
-                    .update(cx, |call, cx| call.invite(user_id.to_proto(), None, cx))
-                    .await
-                    .log_err();
-            }
-
-            ClientOperation::OpenLocalProject { first_root_name } => {
-                log::info!(
-                    "{}: opening local project at {:?}",
-                    client.username,
-                    first_root_name
-                );
-
-                let root_path = Path::new("/").join(&first_root_name);
-                client.fs().create_dir(&root_path).await.unwrap();
-                client
-                    .fs()
-                    .create_file(&root_path.join("main.rs"), Default::default())
-                    .await
-                    .unwrap();
-                let project = client.build_local_project(root_path, cx).await.0;
-                ensure_project_shared(&project, client, cx).await;
-                client.local_projects_mut().push(project.clone());
-            }
-
-            ClientOperation::AddWorktreeToProject {
-                project_root_name,
-                new_root_path,
-            } => {
-                let project = project_for_root_name(client, &project_root_name, cx)
-                    .ok_or(TestError::Inapplicable)?;
-
-                log::info!(
-                    "{}: finding/creating local worktree at {:?} to project with root path {}",
-                    client.username,
-                    new_root_path,
-                    project_root_name
-                );
-
-                ensure_project_shared(&project, client, cx).await;
-                if !client.fs().paths(false).contains(&new_root_path) {
-                    client.fs().create_dir(&new_root_path).await.unwrap();
-                }
-                project
-                    .update(cx, |project, cx| {
-                        project.find_or_create_local_worktree(&new_root_path, true, cx)
-                    })
-                    .await
-                    .unwrap();
-            }
-
-            ClientOperation::CloseRemoteProject { project_root_name } => {
-                let project = project_for_root_name(client, &project_root_name, cx)
-                    .ok_or(TestError::Inapplicable)?;
-
-                log::info!(
-                    "{}: closing remote project with root path {}",
-                    client.username,
-                    project_root_name,
-                );
-
-                let ix = client
-                    .remote_projects()
-                    .iter()
-                    .position(|p| p == &project)
-                    .unwrap();
-                cx.update(|_| {
-                    client.remote_projects_mut().remove(ix);
-                    client.buffers().retain(|p, _| *p != project);
-                    drop(project);
-                });
-            }
-
-            ClientOperation::OpenRemoteProject {
-                host_id,
-                first_root_name,
-            } => {
-                let active_call = cx.read(ActiveCall::global);
-                let project = active_call
-                    .update(cx, |call, cx| {
-                        let room = call.room().cloned()?;
-                        let participant = room
-                            .read(cx)
-                            .remote_participants()
-                            .get(&host_id.to_proto())?;
-                        let project_id = participant
-                            .projects
-                            .iter()
-                            .find(|project| project.worktree_root_names[0] == first_root_name)?
-                            .id;
-                        Some(room.update(cx, |room, cx| {
-                            room.join_project(
-                                project_id,
-                                client.language_registry().clone(),
-                                FakeFs::new(cx.background_executor().clone()),
-                                cx,
-                            )
-                        }))
-                    })
-                    .ok_or(TestError::Inapplicable)?;
-
-                log::info!(
-                    "{}: joining remote project of user {}, root name {}",
-                    client.username,
-                    host_id,
-                    first_root_name,
-                );
-
-                let project = project.await?;
-                client.remote_projects_mut().push(project.clone());
-            }
-
-            ClientOperation::CreateWorktreeEntry {
-                project_root_name,
-                is_local,
-                full_path,
-                is_dir,
-            } => {
-                let project = project_for_root_name(client, &project_root_name, cx)
-                    .ok_or(TestError::Inapplicable)?;
-                let project_path = project_path_for_full_path(&project, &full_path, cx)
-                    .ok_or(TestError::Inapplicable)?;
-
-                log::info!(
-                    "{}: creating {} at path {:?} in {} project {}",
-                    client.username,
-                    if is_dir { "dir" } else { "file" },
-                    full_path,
-                    if is_local { "local" } else { "remote" },
-                    project_root_name,
-                );
-
-                ensure_project_shared(&project, client, cx).await;
-                project
-                    .update(cx, |p, cx| p.create_entry(project_path, is_dir, cx))
-                    .await?;
-            }
-
-            ClientOperation::OpenBuffer {
-                project_root_name,
-                is_local,
-                full_path,
-            } => {
-                let project = project_for_root_name(client, &project_root_name, cx)
-                    .ok_or(TestError::Inapplicable)?;
-                let project_path = project_path_for_full_path(&project, &full_path, cx)
-                    .ok_or(TestError::Inapplicable)?;
-
-                log::info!(
-                    "{}: opening buffer {:?} in {} project {}",
-                    client.username,
-                    full_path,
-                    if is_local { "local" } else { "remote" },
-                    project_root_name,
-                );
-
-                ensure_project_shared(&project, client, cx).await;
-                let buffer = project
-                    .update(cx, |project, cx| project.open_buffer(project_path, cx))
-                    .await?;
-                client.buffers_for_project(&project).insert(buffer);
-            }
-
-            ClientOperation::EditBuffer {
-                project_root_name,
-                is_local,
-                full_path,
-                edits,
-            } => {
-                let project = project_for_root_name(client, &project_root_name, cx)
-                    .ok_or(TestError::Inapplicable)?;
-                let buffer = buffer_for_full_path(client, &project, &full_path, cx)
-                    .ok_or(TestError::Inapplicable)?;
-
-                log::info!(
-                    "{}: editing buffer {:?} in {} project {} with {:?}",
-                    client.username,
-                    full_path,
-                    if is_local { "local" } else { "remote" },
-                    project_root_name,
-                    edits
-                );
-
-                ensure_project_shared(&project, client, cx).await;
-                buffer.update(cx, |buffer, cx| {
-                    let snapshot = buffer.snapshot();
-                    buffer.edit(
-                        edits.into_iter().map(|(range, text)| {
-                            let start = snapshot.clip_offset(range.start, Bias::Left);
-                            let end = snapshot.clip_offset(range.end, Bias::Right);
-                            (start..end, text)
-                        }),
-                        None,
-                        cx,
-                    );
-                });
-            }
-
-            ClientOperation::CloseBuffer {
-                project_root_name,
-                is_local,
-                full_path,
-            } => {
-                let project = project_for_root_name(client, &project_root_name, cx)
-                    .ok_or(TestError::Inapplicable)?;
-                let buffer = buffer_for_full_path(client, &project, &full_path, cx)
-                    .ok_or(TestError::Inapplicable)?;
-
-                log::info!(
-                    "{}: closing buffer {:?} in {} project {}",
-                    client.username,
-                    full_path,
-                    if is_local { "local" } else { "remote" },
-                    project_root_name
-                );
-
-                ensure_project_shared(&project, client, cx).await;
-                cx.update(|_| {
-                    client.buffers_for_project(&project).remove(&buffer);
-                    drop(buffer);
-                });
-            }
-
-            ClientOperation::SaveBuffer {
-                project_root_name,
-                is_local,
-                full_path,
-                detach,
-            } => {
-                let project = project_for_root_name(client, &project_root_name, cx)
-                    .ok_or(TestError::Inapplicable)?;
-                let buffer = buffer_for_full_path(client, &project, &full_path, cx)
-                    .ok_or(TestError::Inapplicable)?;
-
-                log::info!(
-                    "{}: saving buffer {:?} in {} project {}, {}",
-                    client.username,
-                    full_path,
-                    if is_local { "local" } else { "remote" },
-                    project_root_name,
-                    if detach { "detaching" } else { "awaiting" }
-                );
-
-                ensure_project_shared(&project, client, cx).await;
-                let requested_version = buffer.read_with(cx, |buffer, _| buffer.version());
-                let save =
-                    project.update(cx, |project, cx| project.save_buffer(buffer.clone(), cx));
-                let save = cx.spawn(|cx| async move {
-                    save.await
-                        .map_err(|err| anyhow!("save request failed: {:?}", err))?;
-                    assert!(buffer
-                        .read_with(&cx, |buffer, _| { buffer.saved_version().to_owned() })
-                        .expect("App should not be dropped")
-                        .observed_all(&requested_version));
-                    anyhow::Ok(())
-                });
-                if detach {
-                    cx.update(|cx| save.detach_and_log_err(cx));
-                } else {
-                    save.await?;
-                }
-            }
-
-            ClientOperation::RequestLspDataInBuffer {
-                project_root_name,
-                is_local,
-                full_path,
-                offset,
-                kind,
-                detach,
-            } => {
-                let project = project_for_root_name(client, &project_root_name, cx)
-                    .ok_or(TestError::Inapplicable)?;
-                let buffer = buffer_for_full_path(client, &project, &full_path, cx)
-                    .ok_or(TestError::Inapplicable)?;
-
-                log::info!(
-                    "{}: request LSP {:?} for buffer {:?} in {} project {}, {}",
-                    client.username,
-                    kind,
-                    full_path,
-                    if is_local { "local" } else { "remote" },
-                    project_root_name,
-                    if detach { "detaching" } else { "awaiting" }
-                );
-
-                use futures::{FutureExt as _, TryFutureExt as _};
-                let offset = buffer.read_with(cx, |b, _| b.clip_offset(offset, Bias::Left));
-
-                let process_lsp_request = project.update(cx, |project, cx| match kind {
-                    LspRequestKind::Rename => project
-                        .prepare_rename(buffer, offset, cx)
-                        .map_ok(|_| ())
-                        .boxed(),
-                    LspRequestKind::Completion => project
-                        .completions(&buffer, offset, cx)
-                        .map_ok(|_| ())
-                        .boxed(),
-                    LspRequestKind::CodeAction => project
-                        .code_actions(&buffer, offset..offset, cx)
-                        .map_ok(|_| ())
-                        .boxed(),
-                    LspRequestKind::Definition => project
-                        .definition(&buffer, offset, cx)
-                        .map_ok(|_| ())
-                        .boxed(),
-                    LspRequestKind::Highlights => project
-                        .document_highlights(&buffer, offset, cx)
-                        .map_ok(|_| ())
-                        .boxed(),
-                });
-                let request = cx.foreground_executor().spawn(process_lsp_request);
-                if detach {
-                    request.detach();
-                } else {
-                    request.await?;
-                }
-            }
-
-            ClientOperation::SearchProject {
-                project_root_name,
-                is_local,
-                query,
-                detach,
-            } => {
-                let project = project_for_root_name(client, &project_root_name, cx)
-                    .ok_or(TestError::Inapplicable)?;
-
-                log::info!(
-                    "{}: search {} project {} for {:?}, {}",
-                    client.username,
-                    if is_local { "local" } else { "remote" },
-                    project_root_name,
-                    query,
-                    if detach { "detaching" } else { "awaiting" }
-                );
-
-                let mut search = project.update(cx, |project, cx| {
-                    project.search(
-                        SearchQuery::text(query, false, false, false, Vec::new(), Vec::new())
-                            .unwrap(),
-                        cx,
-                    )
-                });
-                drop(project);
-                let search = cx.executor().spawn(async move {
-                    let mut results = HashMap::default();
-                    while let Some((buffer, ranges)) = search.next().await {
-                        results.entry(buffer).or_insert(ranges);
-                    }
-                    results
-                });
-                search.await;
-            }
-
-            ClientOperation::WriteFsEntry {
-                path,
-                is_dir,
-                content,
-            } => {
-                if !client
-                    .fs()
-                    .directories(false)
-                    .contains(&path.parent().unwrap().to_owned())
-                {
-                    return Err(TestError::Inapplicable);
-                }
-
-                if is_dir {
-                    log::info!("{}: creating dir at {:?}", client.username, path);
-                    client.fs().create_dir(&path).await.unwrap();
-                } else {
-                    let exists = client.fs().metadata(&path).await?.is_some();
-                    let verb = if exists { "updating" } else { "creating" };
-                    log::info!("{}: {} file at {:?}", verb, client.username, path);
-
-                    client
-                        .fs()
-                        .save(&path, &content.as_str().into(), text::LineEnding::Unix)
-                        .await
-                        .unwrap();
-                }
-            }
-
-            ClientOperation::GitOperation { operation } => match operation {
-                GitOperation::WriteGitIndex {
-                    repo_path,
-                    contents,
-                } => {
-                    if !client.fs().directories(false).contains(&repo_path) {
-                        return Err(TestError::Inapplicable);
-                    }
-
-                    for (path, _) in contents.iter() {
-                        if !client.fs().files().contains(&repo_path.join(path)) {
-                            return Err(TestError::Inapplicable);
-                        }
-                    }
-
-                    log::info!(
-                        "{}: writing git index for repo {:?}: {:?}",
-                        client.username,
-                        repo_path,
-                        contents
-                    );
-
-                    let dot_git_dir = repo_path.join(".git");
-                    let contents = contents
-                        .iter()
-                        .map(|(path, contents)| (path.as_path(), contents.clone()))
-                        .collect::<Vec<_>>();
-                    if client.fs().metadata(&dot_git_dir).await?.is_none() {
-                        client.fs().create_dir(&dot_git_dir).await?;
-                    }
-                    client.fs().set_index_for_repo(&dot_git_dir, &contents);
-                }
-                GitOperation::WriteGitBranch {
-                    repo_path,
-                    new_branch,
-                } => {
-                    if !client.fs().directories(false).contains(&repo_path) {
-                        return Err(TestError::Inapplicable);
-                    }
-
-                    log::info!(
-                        "{}: writing git branch for repo {:?}: {:?}",
-                        client.username,
-                        repo_path,
-                        new_branch
-                    );
-
-                    let dot_git_dir = repo_path.join(".git");
-                    if client.fs().metadata(&dot_git_dir).await?.is_none() {
-                        client.fs().create_dir(&dot_git_dir).await?;
-                    }
-                    client
-                        .fs()
-                        .set_branch_name(&dot_git_dir, new_branch.clone());
-                }
-                GitOperation::WriteGitStatuses {
-                    repo_path,
-                    statuses,
-                    git_operation,
-                } => {
-                    if !client.fs().directories(false).contains(&repo_path) {
-                        return Err(TestError::Inapplicable);
-                    }
-                    for (path, _) in statuses.iter() {
-                        if !client.fs().files().contains(&repo_path.join(path)) {
-                            return Err(TestError::Inapplicable);
-                        }
-                    }
-
-                    log::info!(
-                        "{}: writing git statuses for repo {:?}: {:?}",
-                        client.username,
-                        repo_path,
-                        statuses
-                    );
-
-                    let dot_git_dir = repo_path.join(".git");
-
-                    let statuses = statuses
-                        .iter()
-                        .map(|(path, val)| (path.as_path(), val.clone()))
-                        .collect::<Vec<_>>();
-
-                    if client.fs().metadata(&dot_git_dir).await?.is_none() {
-                        client.fs().create_dir(&dot_git_dir).await?;
-                    }
-
-                    if git_operation {
-                        client.fs().set_status_for_repo_via_git_operation(
-                            &dot_git_dir,
-                            statuses.as_slice(),
-                        );
-                    } else {
-                        client.fs().set_status_for_repo_via_working_copy_change(
-                            &dot_git_dir,
-                            statuses.as_slice(),
-                        );
-                    }
-                }
-            },
-        }
-        Ok(())
-    }
-
-    async fn on_client_added(client: &Rc<TestClient>, _: &mut TestAppContext) {
-        let mut language = Language::new(
-            LanguageConfig {
-                name: "Rust".into(),
-                path_suffixes: vec!["rs".to_string()],
-                ..Default::default()
-            },
-            None,
-        );
-        language
-            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-                name: "the-fake-language-server",
-                capabilities: lsp::LanguageServer::full_capabilities(),
-                initializer: Some(Box::new({
-                    let fs = client.app_state.fs.clone();
-                    move |fake_server: &mut FakeLanguageServer| {
-                        fake_server.handle_request::<lsp::request::Completion, _, _>(
-                            |_, _| async move {
-                                Ok(Some(lsp::CompletionResponse::Array(vec![
-                                    lsp::CompletionItem {
-                                        text_edit: Some(lsp::CompletionTextEdit::Edit(
-                                            lsp::TextEdit {
-                                                range: lsp::Range::new(
-                                                    lsp::Position::new(0, 0),
-                                                    lsp::Position::new(0, 0),
-                                                ),
-                                                new_text: "the-new-text".to_string(),
-                                            },
-                                        )),
-                                        ..Default::default()
-                                    },
-                                ])))
-                            },
-                        );
-
-                        fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
-                            |_, _| async move {
-                                Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
-                                    lsp::CodeAction {
-                                        title: "the-code-action".to_string(),
-                                        ..Default::default()
-                                    },
-                                )]))
-                            },
-                        );
-
-                        fake_server.handle_request::<lsp::request::PrepareRenameRequest, _, _>(
-                            |params, _| async move {
-                                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
-                                    params.position,
-                                    params.position,
-                                ))))
-                            },
-                        );
-
-                        fake_server.handle_request::<lsp::request::GotoDefinition, _, _>({
-                            let fs = fs.clone();
-                            move |_, cx| {
-                                let background = cx.background_executor();
-                                let mut rng = background.rng();
-                                let count = rng.gen_range::<usize, _>(1..3);
-                                let files = fs.as_fake().files();
-                                let files = (0..count)
-                                    .map(|_| files.choose(&mut rng).unwrap().clone())
-                                    .collect::<Vec<_>>();
-                                async move {
-                                    log::info!("LSP: Returning definitions in files {:?}", &files);
-                                    Ok(Some(lsp::GotoDefinitionResponse::Array(
-                                        files
-                                            .into_iter()
-                                            .map(|file| lsp::Location {
-                                                uri: lsp::Url::from_file_path(file).unwrap(),
-                                                range: Default::default(),
-                                            })
-                                            .collect(),
-                                    )))
-                                }
-                            }
-                        });
-
-                        fake_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>(
-                            move |_, cx| {
-                                let mut highlights = Vec::new();
-                                let background = cx.background_executor();
-                                let mut rng = background.rng();
-
-                                let highlight_count = rng.gen_range(1..=5);
-                                for _ in 0..highlight_count {
-                                    let start_row = rng.gen_range(0..100);
-                                    let start_column = rng.gen_range(0..100);
-                                    let end_row = rng.gen_range(0..100);
-                                    let end_column = rng.gen_range(0..100);
-                                    let start = PointUtf16::new(start_row, start_column);
-                                    let end = PointUtf16::new(end_row, end_column);
-                                    let range = if start > end { end..start } else { start..end };
-                                    highlights.push(lsp::DocumentHighlight {
-                                        range: range_to_lsp(range.clone()),
-                                        kind: Some(lsp::DocumentHighlightKind::READ),
-                                    });
-                                }
-                                highlights.sort_unstable_by_key(|highlight| {
-                                    (highlight.range.start, highlight.range.end)
-                                });
-                                async move { Ok(Some(highlights)) }
-                            },
-                        );
-                    }
-                })),
-                ..Default::default()
-            }))
-            .await;
-        client.app_state.languages.add(Arc::new(language));
-    }
-
-    async fn on_quiesce(_: &mut TestServer, clients: &mut [(Rc<TestClient>, TestAppContext)]) {
-        for (client, client_cx) in clients.iter() {
-            for guest_project in client.remote_projects().iter() {
-                guest_project.read_with(client_cx, |guest_project, cx| {
-                        let host_project = clients.iter().find_map(|(client, cx)| {
-                            let project = client
-                                .local_projects()
-                                .iter()
-                                .find(|host_project| {
-                                    host_project.read_with(cx, |host_project, _| {
-                                        host_project.remote_id() == guest_project.remote_id()
-                                    })
-                                })?
-                                .clone();
-                            Some((project, cx))
-                        });
-
-                        if !guest_project.is_read_only() {
-                            if let Some((host_project, host_cx)) = host_project {
-                                let host_worktree_snapshots =
-                                    host_project.read_with(host_cx, |host_project, cx| {
-                                        host_project
-                                            .worktrees()
-                                            .map(|worktree| {
-                                                let worktree = worktree.read(cx);
-                                                (worktree.id(), worktree.snapshot())
-                                            })
-                                            .collect::<BTreeMap<_, _>>()
-                                    });
-                                let guest_worktree_snapshots = guest_project
-                                    .worktrees()
-                                    .map(|worktree| {
-                                        let worktree = worktree.read(cx);
-                                        (worktree.id(), worktree.snapshot())
-                                    })
-                                    .collect::<BTreeMap<_, _>>();
-
-                                assert_eq!(
-                                    guest_worktree_snapshots.values().map(|w| w.abs_path()).collect::<Vec<_>>(),
-                                    host_worktree_snapshots.values().map(|w| w.abs_path()).collect::<Vec<_>>(),
-                                    "{} has different worktrees than the host for project {:?}",
-                                    client.username, guest_project.remote_id(),
-                                );
-
-                                for (id, host_snapshot) in &host_worktree_snapshots {
-                                    let guest_snapshot = &guest_worktree_snapshots[id];
-                                    assert_eq!(
-                                        guest_snapshot.root_name(),
-                                        host_snapshot.root_name(),
-                                        "{} has different root name than the host for worktree {}, project {:?}",
-                                        client.username,
-                                        id,
-                                        guest_project.remote_id(),
-                                    );
-                                    assert_eq!(
-                                        guest_snapshot.abs_path(),
-                                        host_snapshot.abs_path(),
-                                        "{} has different abs path than the host for worktree {}, project: {:?}",
-                                        client.username,
-                                        id,
-                                        guest_project.remote_id(),
-                                    );
-                                    assert_eq!(
-                                        guest_snapshot.entries(false).collect::<Vec<_>>(),
-                                        host_snapshot.entries(false).collect::<Vec<_>>(),
-                                        "{} has different snapshot than the host for worktree {:?} ({:?}) and project {:?}",
-                                        client.username,
-                                        host_snapshot.abs_path(),
-                                        id,
-                                        guest_project.remote_id(),
-                                    );
-                                    assert_eq!(guest_snapshot.repositories().collect::<Vec<_>>(), host_snapshot.repositories().collect::<Vec<_>>(),
-                                        "{} has different repositories than the host for worktree {:?} and project {:?}",
-                                        client.username,
-                                        host_snapshot.abs_path(),
-                                        guest_project.remote_id(),
-                                    );
-                                    assert_eq!(guest_snapshot.scan_id(), host_snapshot.scan_id(),
-                                        "{} has different scan id than the host for worktree {:?} and project {:?}",
-                                        client.username,
-                                        host_snapshot.abs_path(),
-                                        guest_project.remote_id(),
-                                    );
-                                }
-                            }
-                        }
-
-                        for buffer in guest_project.opened_buffers() {
-                            let buffer = buffer.read(cx);
-                            assert_eq!(
-                                buffer.deferred_ops_len(),
-                                0,
-                                "{} has deferred operations for buffer {:?} in project {:?}",
-                                client.username,
-                                buffer.file().unwrap().full_path(cx),
-                                guest_project.remote_id(),
-                            );
-                        }
-                    });
-            }
-
-            let buffers = client.buffers().clone();
-            for (guest_project, guest_buffers) in &buffers {
-                let project_id = if guest_project.read_with(client_cx, |project, _| {
-                    project.is_local() || project.is_read_only()
-                }) {
-                    continue;
-                } else {
-                    guest_project
-                        .read_with(client_cx, |project, _| project.remote_id())
-                        .unwrap()
-                };
-                let guest_user_id = client.user_id().unwrap();
-
-                let host_project = clients.iter().find_map(|(client, cx)| {
-                    let project = client
-                        .local_projects()
-                        .iter()
-                        .find(|host_project| {
-                            host_project.read_with(cx, |host_project, _| {
-                                host_project.remote_id() == Some(project_id)
-                            })
-                        })?
-                        .clone();
-                    Some((client.user_id().unwrap(), project, cx))
-                });
-
-                let (host_user_id, host_project, host_cx) =
-                    if let Some((host_user_id, host_project, host_cx)) = host_project {
-                        (host_user_id, host_project, host_cx)
-                    } else {
-                        continue;
-                    };
-
-                for guest_buffer in guest_buffers {
-                    let buffer_id =
-                        guest_buffer.read_with(client_cx, |buffer, _| buffer.remote_id());
-                    let host_buffer = host_project.read_with(host_cx, |project, _| {
-                        project.buffer_for_id(buffer_id).unwrap_or_else(|| {
-                            panic!(
-                                "host does not have buffer for guest:{}, peer:{:?}, id:{}",
-                                client.username,
-                                client.peer_id(),
-                                buffer_id
-                            )
-                        })
-                    });
-                    let path = host_buffer
-                        .read_with(host_cx, |buffer, cx| buffer.file().unwrap().full_path(cx));
-
-                    assert_eq!(
-                        guest_buffer.read_with(client_cx, |buffer, _| buffer.deferred_ops_len()),
-                        0,
-                        "{}, buffer {}, path {:?} has deferred operations",
-                        client.username,
-                        buffer_id,
-                        path,
-                    );
-                    assert_eq!(
-                        guest_buffer.read_with(client_cx, |buffer, _| buffer.text()),
-                        host_buffer.read_with(host_cx, |buffer, _| buffer.text()),
-                        "{}, buffer {}, path {:?}, differs from the host's buffer",
-                        client.username,
-                        buffer_id,
-                        path
-                    );
-
-                    let host_file = host_buffer.read_with(host_cx, |b, _| b.file().cloned());
-                    let guest_file = guest_buffer.read_with(client_cx, |b, _| b.file().cloned());
-                    match (host_file, guest_file) {
-                        (Some(host_file), Some(guest_file)) => {
-                            assert_eq!(guest_file.path(), host_file.path());
-                            assert_eq!(guest_file.is_deleted(), host_file.is_deleted());
-                            assert_eq!(
-                                guest_file.mtime(),
-                                host_file.mtime(),
-                                "guest {} mtime does not match host {} for path {:?} in project {}",
-                                guest_user_id,
-                                host_user_id,
-                                guest_file.path(),
-                                project_id,
-                            );
-                        }
-                        (None, None) => {}
-                        (None, _) => panic!("host's file is None, guest's isn't"),
-                        (_, None) => panic!("guest's file is None, hosts's isn't"),
-                    }
-
-                    let host_diff_base = host_buffer
-                        .read_with(host_cx, |b, _| b.diff_base().map(ToString::to_string));
-                    let guest_diff_base = guest_buffer
-                        .read_with(client_cx, |b, _| b.diff_base().map(ToString::to_string));
-                    assert_eq!(
-                            guest_diff_base, host_diff_base,
-                            "guest {} diff base does not match host's for path {path:?} in project {project_id}",
-                            client.username
-                        );
-
-                    let host_saved_version =
-                        host_buffer.read_with(host_cx, |b, _| b.saved_version().clone());
-                    let guest_saved_version =
-                        guest_buffer.read_with(client_cx, |b, _| b.saved_version().clone());
-                    assert_eq!(
-                            guest_saved_version, host_saved_version,
-                            "guest {} saved version does not match host's for path {path:?} in project {project_id}",
-                            client.username
-                        );
-
-                    let host_saved_version_fingerprint =
-                        host_buffer.read_with(host_cx, |b, _| b.saved_version_fingerprint());
-                    let guest_saved_version_fingerprint =
-                        guest_buffer.read_with(client_cx, |b, _| b.saved_version_fingerprint());
-                    assert_eq!(
-                            guest_saved_version_fingerprint, host_saved_version_fingerprint,
-                            "guest {} saved fingerprint does not match host's for path {path:?} in project {project_id}",
-                            client.username
-                        );
-
-                    let host_saved_mtime = host_buffer.read_with(host_cx, |b, _| b.saved_mtime());
-                    let guest_saved_mtime =
-                        guest_buffer.read_with(client_cx, |b, _| b.saved_mtime());
-                    assert_eq!(
-                            guest_saved_mtime, host_saved_mtime,
-                            "guest {} saved mtime does not match host's for path {path:?} in project {project_id}",
-                            client.username
-                        );
-
-                    let host_is_dirty = host_buffer.read_with(host_cx, |b, _| b.is_dirty());
-                    let guest_is_dirty = guest_buffer.read_with(client_cx, |b, _| b.is_dirty());
-                    assert_eq!(guest_is_dirty, host_is_dirty,
-                            "guest {} dirty status does not match host's for path {path:?} in project {project_id}",
-                            client.username
-                        );
-
-                    let host_has_conflict = host_buffer.read_with(host_cx, |b, _| b.has_conflict());
-                    let guest_has_conflict =
-                        guest_buffer.read_with(client_cx, |b, _| b.has_conflict());
-                    assert_eq!(guest_has_conflict, host_has_conflict,
-                            "guest {} conflict status does not match host's for path {path:?} in project {project_id}",
-                            client.username
-                        );
-                }
-            }
-        }
-    }
-}
-
-fn generate_git_operation(rng: &mut StdRng, client: &TestClient) -> GitOperation {
-    fn generate_file_paths(
-        repo_path: &Path,
-        rng: &mut StdRng,
-        client: &TestClient,
-    ) -> Vec<PathBuf> {
-        let mut paths = client
-            .fs()
-            .files()
-            .into_iter()
-            .filter(|path| path.starts_with(repo_path))
-            .collect::<Vec<_>>();
-
-        let count = rng.gen_range(0..=paths.len());
-        paths.shuffle(rng);
-        paths.truncate(count);
-
-        paths
-            .iter()
-            .map(|path| path.strip_prefix(repo_path).unwrap().to_path_buf())
-            .collect::<Vec<_>>()
-    }
-
-    let repo_path = client.fs().directories(false).choose(rng).unwrap().clone();
-
-    match rng.gen_range(0..100_u32) {
-        0..=25 => {
-            let file_paths = generate_file_paths(&repo_path, rng, client);
-
-            let contents = file_paths
-                .into_iter()
-                .map(|path| (path, Alphanumeric.sample_string(rng, 16)))
-                .collect();
-
-            GitOperation::WriteGitIndex {
-                repo_path,
-                contents,
-            }
-        }
-        26..=63 => {
-            let new_branch = (rng.gen_range(0..10) > 3).then(|| Alphanumeric.sample_string(rng, 8));
-
-            GitOperation::WriteGitBranch {
-                repo_path,
-                new_branch,
-            }
-        }
-        64..=100 => {
-            let file_paths = generate_file_paths(&repo_path, rng, client);
-
-            let statuses = file_paths
-                .into_iter()
-                .map(|paths| {
-                    (
-                        paths,
-                        match rng.gen_range(0..3_u32) {
-                            0 => GitFileStatus::Added,
-                            1 => GitFileStatus::Modified,
-                            2 => GitFileStatus::Conflict,
-                            _ => unreachable!(),
-                        },
-                    )
-                })
-                .collect::<Vec<_>>();
-
-            let git_operation = rng.gen::<bool>();
-
-            GitOperation::WriteGitStatuses {
-                repo_path,
-                statuses,
-                git_operation,
-            }
-        }
-        _ => unreachable!(),
-    }
-}
-
-fn buffer_for_full_path(
-    client: &TestClient,
-    project: &Model<Project>,
-    full_path: &PathBuf,
-    cx: &TestAppContext,
-) -> Option<Model<language::Buffer>> {
-    client
-        .buffers_for_project(project)
-        .iter()
-        .find(|buffer| {
-            buffer.read_with(cx, |buffer, cx| {
-                buffer.file().unwrap().full_path(cx) == *full_path
-            })
-        })
-        .cloned()
-}
-
-fn project_for_root_name(
-    client: &TestClient,
-    root_name: &str,
-    cx: &TestAppContext,
-) -> Option<Model<Project>> {
-    if let Some(ix) = project_ix_for_root_name(&*client.local_projects().deref(), root_name, cx) {
-        return Some(client.local_projects()[ix].clone());
-    }
-    if let Some(ix) = project_ix_for_root_name(&*client.remote_projects().deref(), root_name, cx) {
-        return Some(client.remote_projects()[ix].clone());
-    }
-    None
-}
-
-fn project_ix_for_root_name(
-    projects: &[Model<Project>],
-    root_name: &str,
-    cx: &TestAppContext,
-) -> Option<usize> {
-    projects.iter().position(|project| {
-        project.read_with(cx, |project, cx| {
-            let worktree = project.visible_worktrees(cx).next().unwrap();
-            worktree.read(cx).root_name() == root_name
-        })
-    })
-}
-
-fn root_name_for_project(project: &Model<Project>, cx: &TestAppContext) -> String {
-    project.read_with(cx, |project, cx| {
-        project
-            .visible_worktrees(cx)
-            .next()
-            .unwrap()
-            .read(cx)
-            .root_name()
-            .to_string()
-    })
-}
-
-fn project_path_for_full_path(
-    project: &Model<Project>,
-    full_path: &Path,
-    cx: &TestAppContext,
-) -> Option<ProjectPath> {
-    let mut components = full_path.components();
-    let root_name = components.next().unwrap().as_os_str().to_str().unwrap();
-    let path = components.as_path().into();
-    let worktree_id = project.read_with(cx, |project, cx| {
-        project.worktrees().find_map(|worktree| {
-            let worktree = worktree.read(cx);
-            if worktree.root_name() == root_name {
-                Some(worktree.id())
-            } else {
-                None
-            }
-        })
-    })?;
-    Some(ProjectPath { worktree_id, path })
-}
-
-async fn ensure_project_shared(
-    project: &Model<Project>,
-    client: &TestClient,
-    cx: &mut TestAppContext,
-) {
-    let first_root_name = root_name_for_project(project, cx);
-    let active_call = cx.read(ActiveCall::global);
-    if active_call.read_with(cx, |call, _| call.room().is_some())
-        && project.read_with(cx, |project, _| project.is_local() && !project.is_shared())
-    {
-        match active_call
-            .update(cx, |call, cx| call.share_project(project.clone(), cx))
-            .await
-        {
-            Ok(project_id) => {
-                log::info!(
-                    "{}: shared project {} with id {}",
-                    client.username,
-                    first_root_name,
-                    project_id
-                );
-            }
-            Err(error) => {
-                log::error!(
-                    "{}: error sharing project {}: {:?}",
-                    client.username,
-                    first_root_name,
-                    error
-                );
-            }
-        }
-    }
-}
-
-fn choose_random_project(client: &TestClient, rng: &mut StdRng) -> Option<Model<Project>> {
-    client
-        .local_projects()
-        .deref()
-        .iter()
-        .chain(client.remote_projects().iter())
-        .choose(rng)
-        .cloned()
-}
-
-fn gen_file_name(rng: &mut StdRng) -> String {
-    let mut name = String::new();
-    for _ in 0..10 {
-        let letter = rng.gen_range('a'..='z');
-        name.push(letter);
-    }
-    name
-}

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

@@ -1,677 +0,0 @@
-use crate::{
-    db::{self, NewUserParams, UserId},
-    rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
-    tests::{TestClient, TestServer},
-};
-use async_trait::async_trait;
-use futures::StreamExt;
-use gpui::{BackgroundExecutor, Task, TestAppContext};
-use parking_lot::Mutex;
-use rand::prelude::*;
-use rpc::RECEIVE_TIMEOUT;
-use serde::{de::DeserializeOwned, Deserialize, Serialize};
-use settings::SettingsStore;
-use std::{
-    env,
-    path::PathBuf,
-    rc::Rc,
-    sync::{
-        atomic::{AtomicBool, Ordering::SeqCst},
-        Arc,
-    },
-};
-
-lazy_static::lazy_static! {
-    static ref PLAN_LOAD_PATH: Option<PathBuf> = path_env_var("LOAD_PLAN");
-    static ref PLAN_SAVE_PATH: Option<PathBuf> = path_env_var("SAVE_PLAN");
-    static ref MAX_PEERS: usize = env::var("MAX_PEERS")
-        .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
-        .unwrap_or(3);
-    static ref MAX_OPERATIONS: usize = env::var("OPERATIONS")
-        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
-        .unwrap_or(10);
-
-}
-
-static LOADED_PLAN_JSON: Mutex<Option<Vec<u8>>> = Mutex::new(None);
-static LAST_PLAN: Mutex<Option<Box<dyn Send + FnOnce() -> Vec<u8>>>> = Mutex::new(None);
-
-struct TestPlan<T: RandomizedTest> {
-    rng: StdRng,
-    replay: bool,
-    stored_operations: Vec<(StoredOperation<T::Operation>, Arc<AtomicBool>)>,
-    max_operations: usize,
-    operation_ix: usize,
-    users: Vec<UserTestPlan>,
-    next_batch_id: usize,
-    allow_server_restarts: bool,
-    allow_client_reconnection: bool,
-    allow_client_disconnection: bool,
-}
-
-pub struct UserTestPlan {
-    pub user_id: UserId,
-    pub username: String,
-    pub allow_client_reconnection: bool,
-    pub allow_client_disconnection: bool,
-    next_root_id: usize,
-    operation_ix: usize,
-    online: bool,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize)]
-#[serde(untagged)]
-enum StoredOperation<T> {
-    Server(ServerOperation),
-    Client {
-        user_id: UserId,
-        batch_id: usize,
-        operation: T,
-    },
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize)]
-enum ServerOperation {
-    AddConnection {
-        user_id: UserId,
-    },
-    RemoveConnection {
-        user_id: UserId,
-    },
-    BounceConnection {
-        user_id: UserId,
-    },
-    RestartServer,
-    MutateClients {
-        batch_id: usize,
-        #[serde(skip_serializing)]
-        #[serde(skip_deserializing)]
-        user_ids: Vec<UserId>,
-        quiesce: bool,
-    },
-}
-
-pub enum TestError {
-    Inapplicable,
-    Other(anyhow::Error),
-}
-
-#[async_trait(?Send)]
-pub trait RandomizedTest: 'static + Sized {
-    type Operation: Send + Clone + Serialize + DeserializeOwned;
-
-    fn generate_operation(
-        client: &TestClient,
-        rng: &mut StdRng,
-        plan: &mut UserTestPlan,
-        cx: &TestAppContext,
-    ) -> Self::Operation;
-
-    async fn apply_operation(
-        client: &TestClient,
-        operation: Self::Operation,
-        cx: &mut TestAppContext,
-    ) -> Result<(), TestError>;
-
-    async fn initialize(server: &mut TestServer, users: &[UserTestPlan]);
-
-    async fn on_client_added(_client: &Rc<TestClient>, _cx: &mut TestAppContext) {}
-
-    async fn on_quiesce(server: &mut TestServer, client: &mut [(Rc<TestClient>, TestAppContext)]);
-}
-
-pub async fn run_randomized_test<T: RandomizedTest>(
-    cx: &mut TestAppContext,
-    executor: BackgroundExecutor,
-    rng: StdRng,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let plan = TestPlan::<T>::new(&mut server, rng).await;
-
-    LAST_PLAN.lock().replace({
-        let plan = plan.clone();
-        Box::new(move || plan.lock().serialize())
-    });
-
-    let mut clients = Vec::new();
-    let mut client_tasks = Vec::new();
-    let mut operation_channels = Vec::new();
-    loop {
-        let Some((next_operation, applied)) = plan.lock().next_server_operation(&clients) else {
-            break;
-        };
-        applied.store(true, SeqCst);
-        let did_apply = TestPlan::apply_server_operation(
-            plan.clone(),
-            executor.clone(),
-            &mut server,
-            &mut clients,
-            &mut client_tasks,
-            &mut operation_channels,
-            next_operation,
-            cx,
-        )
-        .await;
-        if !did_apply {
-            applied.store(false, SeqCst);
-        }
-    }
-
-    drop(operation_channels);
-    executor.start_waiting();
-    futures::future::join_all(client_tasks).await;
-    executor.finish_waiting();
-
-    executor.run_until_parked();
-    T::on_quiesce(&mut server, &mut clients).await;
-
-    for (client, cx) in clients {
-        cx.update(|cx| {
-            let store = cx.remove_global::<SettingsStore>();
-            cx.clear_globals();
-            cx.set_global(store);
-            drop(client);
-        });
-    }
-    executor.run_until_parked();
-
-    if let Some(path) = &*PLAN_SAVE_PATH {
-        eprintln!("saved test plan to path {:?}", path);
-        std::fs::write(path, plan.lock().serialize()).unwrap();
-    }
-}
-
-pub fn save_randomized_test_plan() {
-    if let Some(serialize_plan) = LAST_PLAN.lock().take() {
-        if let Some(path) = &*PLAN_SAVE_PATH {
-            eprintln!("saved test plan to path {:?}", path);
-            std::fs::write(path, serialize_plan()).unwrap();
-        }
-    }
-}
-
-impl<T: RandomizedTest> TestPlan<T> {
-    pub async fn new(server: &mut TestServer, mut rng: StdRng) -> Arc<Mutex<Self>> {
-        let allow_server_restarts = rng.gen_bool(0.7);
-        let allow_client_reconnection = rng.gen_bool(0.7);
-        let allow_client_disconnection = rng.gen_bool(0.1);
-
-        let mut users = Vec::new();
-        for ix in 0..*MAX_PEERS {
-            let username = format!("user-{}", ix + 1);
-            let user_id = server
-                .app_state
-                .db
-                .create_user(
-                    &format!("{username}@example.com"),
-                    false,
-                    NewUserParams {
-                        github_login: username.clone(),
-                        github_user_id: ix as i32,
-                    },
-                )
-                .await
-                .unwrap()
-                .user_id;
-            users.push(UserTestPlan {
-                user_id,
-                username,
-                online: false,
-                next_root_id: 0,
-                operation_ix: 0,
-                allow_client_disconnection,
-                allow_client_reconnection,
-            });
-        }
-
-        T::initialize(server, &users).await;
-
-        let plan = Arc::new(Mutex::new(Self {
-            replay: false,
-            allow_server_restarts,
-            allow_client_reconnection,
-            allow_client_disconnection,
-            stored_operations: Vec::new(),
-            operation_ix: 0,
-            next_batch_id: 0,
-            max_operations: *MAX_OPERATIONS,
-            users,
-            rng,
-        }));
-
-        if let Some(path) = &*PLAN_LOAD_PATH {
-            let json = LOADED_PLAN_JSON
-                .lock()
-                .get_or_insert_with(|| {
-                    eprintln!("loaded test plan from path {:?}", path);
-                    std::fs::read(path).unwrap()
-                })
-                .clone();
-            plan.lock().deserialize(json);
-        }
-
-        plan
-    }
-
-    fn deserialize(&mut self, json: Vec<u8>) {
-        let stored_operations: Vec<StoredOperation<T::Operation>> =
-            serde_json::from_slice(&json).unwrap();
-        self.replay = true;
-        self.stored_operations = stored_operations
-            .iter()
-            .cloned()
-            .enumerate()
-            .map(|(i, mut operation)| {
-                let did_apply = Arc::new(AtomicBool::new(false));
-                if let StoredOperation::Server(ServerOperation::MutateClients {
-                    batch_id: current_batch_id,
-                    user_ids,
-                    ..
-                }) = &mut operation
-                {
-                    assert!(user_ids.is_empty());
-                    user_ids.extend(stored_operations[i + 1..].iter().filter_map(|operation| {
-                        if let StoredOperation::Client {
-                            user_id, batch_id, ..
-                        } = operation
-                        {
-                            if batch_id == current_batch_id {
-                                return Some(user_id);
-                            }
-                        }
-                        None
-                    }));
-                    user_ids.sort_unstable();
-                }
-                (operation, did_apply)
-            })
-            .collect()
-    }
-
-    fn serialize(&mut self) -> Vec<u8> {
-        // Format each operation as one line
-        let mut json = Vec::new();
-        json.push(b'[');
-        for (operation, applied) in &self.stored_operations {
-            if !applied.load(SeqCst) {
-                continue;
-            }
-            if json.len() > 1 {
-                json.push(b',');
-            }
-            json.extend_from_slice(b"\n  ");
-            serde_json::to_writer(&mut json, operation).unwrap();
-        }
-        json.extend_from_slice(b"\n]\n");
-        json
-    }
-
-    fn next_server_operation(
-        &mut self,
-        clients: &[(Rc<TestClient>, TestAppContext)],
-    ) -> Option<(ServerOperation, Arc<AtomicBool>)> {
-        if self.replay {
-            while let Some(stored_operation) = self.stored_operations.get(self.operation_ix) {
-                self.operation_ix += 1;
-                if let (StoredOperation::Server(operation), applied) = stored_operation {
-                    return Some((operation.clone(), applied.clone()));
-                }
-            }
-            None
-        } else {
-            let operation = self.generate_server_operation(clients)?;
-            let applied = Arc::new(AtomicBool::new(false));
-            self.stored_operations
-                .push((StoredOperation::Server(operation.clone()), applied.clone()));
-            Some((operation, applied))
-        }
-    }
-
-    fn next_client_operation(
-        &mut self,
-        client: &TestClient,
-        current_batch_id: usize,
-        cx: &TestAppContext,
-    ) -> Option<(T::Operation, Arc<AtomicBool>)> {
-        let current_user_id = client.current_user_id(cx);
-        let user_ix = self
-            .users
-            .iter()
-            .position(|user| user.user_id == current_user_id)
-            .unwrap();
-        let user_plan = &mut self.users[user_ix];
-
-        if self.replay {
-            while let Some(stored_operation) = self.stored_operations.get(user_plan.operation_ix) {
-                user_plan.operation_ix += 1;
-                if let (
-                    StoredOperation::Client {
-                        user_id, operation, ..
-                    },
-                    applied,
-                ) = stored_operation
-                {
-                    if user_id == &current_user_id {
-                        return Some((operation.clone(), applied.clone()));
-                    }
-                }
-            }
-            None
-        } else {
-            if self.operation_ix == self.max_operations {
-                return None;
-            }
-            self.operation_ix += 1;
-            let operation = T::generate_operation(
-                client,
-                &mut self.rng,
-                self.users
-                    .iter_mut()
-                    .find(|user| user.user_id == current_user_id)
-                    .unwrap(),
-                cx,
-            );
-            let applied = Arc::new(AtomicBool::new(false));
-            self.stored_operations.push((
-                StoredOperation::Client {
-                    user_id: current_user_id,
-                    batch_id: current_batch_id,
-                    operation: operation.clone(),
-                },
-                applied.clone(),
-            ));
-            Some((operation, applied))
-        }
-    }
-
-    fn generate_server_operation(
-        &mut self,
-        clients: &[(Rc<TestClient>, TestAppContext)],
-    ) -> Option<ServerOperation> {
-        if self.operation_ix == self.max_operations {
-            return None;
-        }
-
-        Some(loop {
-            break match self.rng.gen_range(0..100) {
-                0..=29 if clients.len() < self.users.len() => {
-                    let user = self
-                        .users
-                        .iter()
-                        .filter(|u| !u.online)
-                        .choose(&mut self.rng)
-                        .unwrap();
-                    self.operation_ix += 1;
-                    ServerOperation::AddConnection {
-                        user_id: user.user_id,
-                    }
-                }
-                30..=34 if clients.len() > 1 && self.allow_client_disconnection => {
-                    let (client, cx) = &clients[self.rng.gen_range(0..clients.len())];
-                    let user_id = client.current_user_id(cx);
-                    self.operation_ix += 1;
-                    ServerOperation::RemoveConnection { user_id }
-                }
-                35..=39 if clients.len() > 1 && self.allow_client_reconnection => {
-                    let (client, cx) = &clients[self.rng.gen_range(0..clients.len())];
-                    let user_id = client.current_user_id(cx);
-                    self.operation_ix += 1;
-                    ServerOperation::BounceConnection { user_id }
-                }
-                40..=44 if self.allow_server_restarts && clients.len() > 1 => {
-                    self.operation_ix += 1;
-                    ServerOperation::RestartServer
-                }
-                _ if !clients.is_empty() => {
-                    let count = self
-                        .rng
-                        .gen_range(1..10)
-                        .min(self.max_operations - self.operation_ix);
-                    let batch_id = util::post_inc(&mut self.next_batch_id);
-                    let mut user_ids = (0..count)
-                        .map(|_| {
-                            let ix = self.rng.gen_range(0..clients.len());
-                            let (client, cx) = &clients[ix];
-                            client.current_user_id(cx)
-                        })
-                        .collect::<Vec<_>>();
-                    user_ids.sort_unstable();
-                    ServerOperation::MutateClients {
-                        user_ids,
-                        batch_id,
-                        quiesce: self.rng.gen_bool(0.7),
-                    }
-                }
-                _ => continue,
-            };
-        })
-    }
-
-    async fn apply_server_operation(
-        plan: Arc<Mutex<Self>>,
-        deterministic: BackgroundExecutor,
-        server: &mut TestServer,
-        clients: &mut Vec<(Rc<TestClient>, TestAppContext)>,
-        client_tasks: &mut Vec<Task<()>>,
-        operation_channels: &mut Vec<futures::channel::mpsc::UnboundedSender<usize>>,
-        operation: ServerOperation,
-        cx: &mut TestAppContext,
-    ) -> bool {
-        match operation {
-            ServerOperation::AddConnection { user_id } => {
-                let username;
-                {
-                    let mut plan = plan.lock();
-                    let user = plan.user(user_id);
-                    if user.online {
-                        return false;
-                    }
-                    user.online = true;
-                    username = user.username.clone();
-                };
-                log::info!("adding new connection for {}", username);
-
-                let mut client_cx = cx.new_app();
-
-                let (operation_tx, operation_rx) = futures::channel::mpsc::unbounded();
-                let client = Rc::new(server.create_client(&mut client_cx, &username).await);
-                operation_channels.push(operation_tx);
-                clients.push((client.clone(), client_cx.clone()));
-
-                let foreground_executor = client_cx.foreground_executor().clone();
-                let simulate_client =
-                    Self::simulate_client(plan.clone(), client, operation_rx, client_cx);
-                client_tasks.push(foreground_executor.spawn(simulate_client));
-
-                log::info!("added connection for {}", username);
-            }
-
-            ServerOperation::RemoveConnection {
-                user_id: removed_user_id,
-            } => {
-                log::info!("simulating full disconnection of user {}", removed_user_id);
-                let client_ix = clients
-                    .iter()
-                    .position(|(client, cx)| client.current_user_id(cx) == removed_user_id);
-                let Some(client_ix) = client_ix else {
-                    return false;
-                };
-                let user_connection_ids = server
-                    .connection_pool
-                    .lock()
-                    .user_connection_ids(removed_user_id)
-                    .collect::<Vec<_>>();
-                assert_eq!(user_connection_ids.len(), 1);
-                let removed_peer_id = user_connection_ids[0].into();
-                let (client, client_cx) = clients.remove(client_ix);
-                let client_task = client_tasks.remove(client_ix);
-                operation_channels.remove(client_ix);
-                server.forbid_connections();
-                server.disconnect_client(removed_peer_id);
-                deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
-                deterministic.start_waiting();
-                log::info!("waiting for user {} to exit...", removed_user_id);
-                client_task.await;
-                deterministic.finish_waiting();
-                server.allow_connections();
-
-                for project in client.remote_projects().iter() {
-                    project.read_with(&client_cx, |project, _| {
-                        assert!(
-                            project.is_read_only(),
-                            "project {:?} should be read only",
-                            project.remote_id()
-                        )
-                    });
-                }
-
-                for (client, cx) in clients {
-                    let contacts = server
-                        .app_state
-                        .db
-                        .get_contacts(client.current_user_id(cx))
-                        .await
-                        .unwrap();
-                    let pool = server.connection_pool.lock();
-                    for contact in contacts {
-                        if let db::Contact::Accepted { user_id, busy, .. } = contact {
-                            if user_id == removed_user_id {
-                                assert!(!pool.is_user_online(user_id));
-                                assert!(!busy);
-                            }
-                        }
-                    }
-                }
-
-                log::info!("{} removed", client.username);
-                plan.lock().user(removed_user_id).online = false;
-                client_cx.update(|cx| {
-                    cx.clear_globals();
-                    drop(client);
-                });
-            }
-
-            ServerOperation::BounceConnection { user_id } => {
-                log::info!("simulating temporary disconnection of user {}", user_id);
-                let user_connection_ids = server
-                    .connection_pool
-                    .lock()
-                    .user_connection_ids(user_id)
-                    .collect::<Vec<_>>();
-                if user_connection_ids.is_empty() {
-                    return false;
-                }
-                assert_eq!(user_connection_ids.len(), 1);
-                let peer_id = user_connection_ids[0].into();
-                server.disconnect_client(peer_id);
-                deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
-            }
-
-            ServerOperation::RestartServer => {
-                log::info!("simulating server restart");
-                server.reset().await;
-                deterministic.advance_clock(RECEIVE_TIMEOUT);
-                server.start().await.unwrap();
-                deterministic.advance_clock(CLEANUP_TIMEOUT);
-                let environment = &server.app_state.config.zed_environment;
-                let (stale_room_ids, _) = server
-                    .app_state
-                    .db
-                    .stale_server_resource_ids(environment, server.id())
-                    .await
-                    .unwrap();
-                assert_eq!(stale_room_ids, vec![]);
-            }
-
-            ServerOperation::MutateClients {
-                user_ids,
-                batch_id,
-                quiesce,
-            } => {
-                let mut applied = false;
-                for user_id in user_ids {
-                    let client_ix = clients
-                        .iter()
-                        .position(|(client, cx)| client.current_user_id(cx) == user_id);
-                    let Some(client_ix) = client_ix else { continue };
-                    applied = true;
-                    if let Err(err) = operation_channels[client_ix].unbounded_send(batch_id) {
-                        log::error!("error signaling user {user_id}: {err}");
-                    }
-                }
-
-                if quiesce && applied {
-                    deterministic.run_until_parked();
-                    T::on_quiesce(server, clients).await;
-                }
-
-                return applied;
-            }
-        }
-        true
-    }
-
-    async fn simulate_client(
-        plan: Arc<Mutex<Self>>,
-        client: Rc<TestClient>,
-        mut operation_rx: futures::channel::mpsc::UnboundedReceiver<usize>,
-        mut cx: TestAppContext,
-    ) {
-        T::on_client_added(&client, &mut cx).await;
-
-        while let Some(batch_id) = operation_rx.next().await {
-            let Some((operation, applied)) =
-                plan.lock().next_client_operation(&client, batch_id, &cx)
-            else {
-                break;
-            };
-            applied.store(true, SeqCst);
-            match T::apply_operation(&client, operation, &mut cx).await {
-                Ok(()) => {}
-                Err(TestError::Inapplicable) => {
-                    applied.store(false, SeqCst);
-                    log::info!("skipped operation");
-                }
-                Err(TestError::Other(error)) => {
-                    log::error!("{} error: {}", client.username, error);
-                }
-            }
-            cx.executor().simulate_random_delay().await;
-        }
-        log::info!("{}: done", client.username);
-    }
-
-    fn user(&mut self, user_id: UserId) -> &mut UserTestPlan {
-        self.users
-            .iter_mut()
-            .find(|user| user.user_id == user_id)
-            .unwrap()
-    }
-}
-
-impl UserTestPlan {
-    pub fn next_root_dir_name(&mut self) -> String {
-        let user_id = self.user_id;
-        let root_id = util::post_inc(&mut self.next_root_id);
-        format!("dir-{user_id}-{root_id}")
-    }
-}
-
-impl From<anyhow::Error> for TestError {
-    fn from(value: anyhow::Error) -> Self {
-        Self::Other(value)
-    }
-}
-
-fn path_env_var(name: &str) -> Option<PathBuf> {
-    let value = env::var(name).ok()?;
-    let mut path = PathBuf::from(value);
-    if path.is_relative() {
-        let mut abs_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
-        abs_path.pop();
-        abs_path.pop();
-        abs_path.push(path);
-        path = abs_path
-    }
-    Some(path)
-}

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

@@ -1,616 +0,0 @@
-use crate::{
-    db::{tests::TestDb, NewUserParams, UserId},
-    executor::Executor,
-    rpc::{Server, CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
-    AppState,
-};
-use anyhow::anyhow;
-use call::ActiveCall;
-use channel::{ChannelBuffer, ChannelStore};
-use client::{
-    self, proto::PeerId, Client, Connection, Credentials, EstablishConnectionError, UserStore,
-};
-use collections::{HashMap, HashSet};
-use fs::FakeFs;
-use futures::{channel::oneshot, StreamExt as _};
-use gpui::{BackgroundExecutor, Context, Model, TestAppContext, View, VisualTestContext};
-use language::LanguageRegistry;
-use node_runtime::FakeNodeRuntime;
-
-use notifications::NotificationStore;
-use parking_lot::Mutex;
-use project::{Project, WorktreeId};
-use rpc::{proto::ChannelRole, RECEIVE_TIMEOUT};
-use settings::SettingsStore;
-use std::{
-    cell::{Ref, RefCell, RefMut},
-    env,
-    ops::{Deref, DerefMut},
-    path::Path,
-    sync::{
-        atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst},
-        Arc,
-    },
-};
-use util::http::FakeHttpClient;
-use workspace::{Workspace, WorkspaceStore};
-
-pub struct TestServer {
-    pub app_state: Arc<AppState>,
-    pub test_live_kit_server: Arc<live_kit_client::TestServer>,
-    server: Arc<Server>,
-    connection_killers: Arc<Mutex<HashMap<PeerId, Arc<AtomicBool>>>>,
-    forbid_connections: Arc<AtomicBool>,
-    _test_db: TestDb,
-}
-
-pub struct TestClient {
-    pub username: String,
-    pub app_state: Arc<workspace::AppState>,
-    channel_store: Model<ChannelStore>,
-    notification_store: Model<NotificationStore>,
-    state: RefCell<TestClientState>,
-}
-
-#[derive(Default)]
-struct TestClientState {
-    local_projects: Vec<Model<Project>>,
-    remote_projects: Vec<Model<Project>>,
-    buffers: HashMap<Model<Project>, HashSet<Model<language::Buffer>>>,
-    channel_buffers: HashSet<Model<ChannelBuffer>>,
-}
-
-pub struct ContactsSummary {
-    pub current: Vec<String>,
-    pub outgoing_requests: Vec<String>,
-    pub incoming_requests: Vec<String>,
-}
-
-impl TestServer {
-    pub async fn start(deterministic: BackgroundExecutor) -> Self {
-        static NEXT_LIVE_KIT_SERVER_ID: AtomicUsize = AtomicUsize::new(0);
-
-        let use_postgres = env::var("USE_POSTGRES").ok();
-        let use_postgres = use_postgres.as_deref();
-        let test_db = if use_postgres == Some("true") || use_postgres == Some("1") {
-            TestDb::postgres(deterministic.clone())
-        } else {
-            TestDb::sqlite(deterministic.clone())
-        };
-        let live_kit_server_id = NEXT_LIVE_KIT_SERVER_ID.fetch_add(1, SeqCst);
-        let live_kit_server = live_kit_client::TestServer::create(
-            format!("http://livekit.{}.test", live_kit_server_id),
-            format!("devkey-{}", live_kit_server_id),
-            format!("secret-{}", live_kit_server_id),
-            deterministic.clone(),
-        )
-        .unwrap();
-        let app_state = Self::build_app_state(&test_db, &live_kit_server).await;
-        let epoch = app_state
-            .db
-            .create_server(&app_state.config.zed_environment)
-            .await
-            .unwrap();
-        let server = Server::new(
-            epoch,
-            app_state.clone(),
-            Executor::Deterministic(deterministic.clone()),
-        );
-        server.start().await.unwrap();
-        // Advance clock to ensure the server's cleanup task is finished.
-        deterministic.advance_clock(CLEANUP_TIMEOUT);
-        Self {
-            app_state,
-            server,
-            connection_killers: Default::default(),
-            forbid_connections: Default::default(),
-            _test_db: test_db,
-            test_live_kit_server: live_kit_server,
-        }
-    }
-
-    pub async fn reset(&self) {
-        self.app_state.db.reset();
-        let epoch = self
-            .app_state
-            .db
-            .create_server(&self.app_state.config.zed_environment)
-            .await
-            .unwrap();
-        self.server.reset(epoch);
-    }
-
-    pub async fn create_client(&mut self, cx: &mut TestAppContext, name: &str) -> TestClient {
-        cx.update(|cx| {
-            if cx.has_global::<SettingsStore>() {
-                panic!("Same cx used to create two test clients")
-            }
-            let settings = SettingsStore::test(cx);
-            cx.set_global(settings);
-        });
-
-        let http = FakeHttpClient::with_404_response();
-        let user_id = if let Ok(Some(user)) = self.app_state.db.get_user_by_github_login(name).await
-        {
-            user.id
-        } else {
-            self.app_state
-                .db
-                .create_user(
-                    &format!("{name}@example.com"),
-                    false,
-                    NewUserParams {
-                        github_login: name.into(),
-                        github_user_id: 0,
-                    },
-                )
-                .await
-                .expect("creating user failed")
-                .user_id
-        };
-        let client_name = name.to_string();
-        let mut client = cx.update(|cx| Client::new(http.clone(), cx));
-        let server = self.server.clone();
-        let db = self.app_state.db.clone();
-        let connection_killers = self.connection_killers.clone();
-        let forbid_connections = self.forbid_connections.clone();
-
-        Arc::get_mut(&mut client)
-            .unwrap()
-            .set_id(user_id.to_proto())
-            .override_authenticate(move |cx| {
-                cx.spawn(|_| async move {
-                    let access_token = "the-token".to_string();
-                    Ok(Credentials {
-                        user_id: user_id.to_proto(),
-                        access_token,
-                    })
-                })
-            })
-            .override_establish_connection(move |credentials, cx| {
-                assert_eq!(credentials.user_id, user_id.0 as u64);
-                assert_eq!(credentials.access_token, "the-token");
-
-                let server = server.clone();
-                let db = db.clone();
-                let connection_killers = connection_killers.clone();
-                let forbid_connections = forbid_connections.clone();
-                let client_name = client_name.clone();
-                cx.spawn(move |cx| async move {
-                    if forbid_connections.load(SeqCst) {
-                        Err(EstablishConnectionError::other(anyhow!(
-                            "server is forbidding connections"
-                        )))
-                    } else {
-                        let (client_conn, server_conn, killed) =
-                            Connection::in_memory(cx.background_executor().clone());
-                        let (connection_id_tx, connection_id_rx) = oneshot::channel();
-                        let user = db
-                            .get_user_by_id(user_id)
-                            .await
-                            .expect("retrieving user failed")
-                            .unwrap();
-                        cx.background_executor()
-                            .spawn(server.handle_connection(
-                                server_conn,
-                                client_name,
-                                user,
-                                Some(connection_id_tx),
-                                Executor::Deterministic(cx.background_executor().clone()),
-                            ))
-                            .detach();
-                        let connection_id = connection_id_rx.await.unwrap();
-                        connection_killers
-                            .lock()
-                            .insert(connection_id.into(), killed);
-                        Ok(client_conn)
-                    }
-                })
-            });
-
-        let fs = FakeFs::new(cx.executor());
-        let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
-        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
-        let mut language_registry = LanguageRegistry::test();
-        language_registry.set_executor(cx.executor());
-        let app_state = Arc::new(workspace::AppState {
-            client: client.clone(),
-            user_store: user_store.clone(),
-            workspace_store,
-            languages: Arc::new(language_registry),
-            fs: fs.clone(),
-            build_window_options: |_, _, _| Default::default(),
-            node_runtime: FakeNodeRuntime::new(),
-        });
-
-        cx.update(|cx| {
-            theme::init(theme::LoadThemes::JustBase, cx);
-            Project::init(&client, cx);
-            client::init(&client, cx);
-            language::init(cx);
-            editor::init_settings(cx);
-            workspace::init(app_state.clone(), cx);
-            audio::init((), cx);
-            call::init(client.clone(), user_store.clone(), cx);
-            channel::init(&client, user_store.clone(), cx);
-            notifications::init(client.clone(), user_store, cx);
-        });
-
-        client
-            .authenticate_and_connect(false, &cx.to_async())
-            .await
-            .unwrap();
-
-        let client = TestClient {
-            app_state,
-            username: name.to_string(),
-            channel_store: cx.read(ChannelStore::global).clone(),
-            notification_store: cx.read(NotificationStore::global).clone(),
-            state: Default::default(),
-        };
-        client.wait_for_current_user(cx).await;
-        client
-    }
-
-    pub fn disconnect_client(&self, peer_id: PeerId) {
-        self.connection_killers
-            .lock()
-            .remove(&peer_id)
-            .unwrap()
-            .store(true, SeqCst);
-    }
-
-    pub fn simulate_long_connection_interruption(
-        &self,
-        peer_id: PeerId,
-        deterministic: BackgroundExecutor,
-    ) {
-        self.forbid_connections();
-        self.disconnect_client(peer_id);
-        deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
-        self.allow_connections();
-        deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
-        deterministic.run_until_parked();
-    }
-
-    pub fn forbid_connections(&self) {
-        self.forbid_connections.store(true, SeqCst);
-    }
-
-    pub fn allow_connections(&self) {
-        self.forbid_connections.store(false, SeqCst);
-    }
-
-    pub async fn make_contacts(&self, clients: &mut [(&TestClient, &mut TestAppContext)]) {
-        for ix in 1..clients.len() {
-            let (left, right) = clients.split_at_mut(ix);
-            let (client_a, cx_a) = left.last_mut().unwrap();
-            for (client_b, cx_b) in right {
-                client_a
-                    .app_state
-                    .user_store
-                    .update(*cx_a, |store, cx| {
-                        store.request_contact(client_b.user_id().unwrap(), cx)
-                    })
-                    .await
-                    .unwrap();
-                cx_a.executor().run_until_parked();
-                client_b
-                    .app_state
-                    .user_store
-                    .update(*cx_b, |store, cx| {
-                        store.respond_to_contact_request(client_a.user_id().unwrap(), true, cx)
-                    })
-                    .await
-                    .unwrap();
-            }
-        }
-    }
-
-    pub async fn make_channel(
-        &self,
-        channel: &str,
-        parent: Option<u64>,
-        admin: (&TestClient, &mut TestAppContext),
-        members: &mut [(&TestClient, &mut TestAppContext)],
-    ) -> u64 {
-        let (_, admin_cx) = admin;
-        let channel_id = admin_cx
-            .read(ChannelStore::global)
-            .update(admin_cx, |channel_store, cx| {
-                channel_store.create_channel(channel, parent, cx)
-            })
-            .await
-            .unwrap();
-
-        for (member_client, member_cx) in members {
-            admin_cx
-                .read(ChannelStore::global)
-                .update(admin_cx, |channel_store, cx| {
-                    channel_store.invite_member(
-                        channel_id,
-                        member_client.user_id().unwrap(),
-                        ChannelRole::Member,
-                        cx,
-                    )
-                })
-                .await
-                .unwrap();
-
-            admin_cx.executor().run_until_parked();
-
-            member_cx
-                .read(ChannelStore::global)
-                .update(*member_cx, |channels, cx| {
-                    channels.respond_to_channel_invite(channel_id, true, cx)
-                })
-                .await
-                .unwrap();
-        }
-
-        channel_id
-    }
-
-    pub async fn make_channel_tree(
-        &self,
-        channels: &[(&str, Option<&str>)],
-        creator: (&TestClient, &mut TestAppContext),
-    ) -> Vec<u64> {
-        let mut observed_channels = HashMap::default();
-        let mut result = Vec::new();
-        for (channel, parent) in channels {
-            let id;
-            if let Some(parent) = parent {
-                if let Some(parent_id) = observed_channels.get(parent) {
-                    id = self
-                        .make_channel(channel, Some(*parent_id), (creator.0, creator.1), &mut [])
-                        .await;
-                } else {
-                    panic!(
-                        "Edge {}->{} referenced before {} was created",
-                        parent, channel, parent
-                    )
-                }
-            } else {
-                id = self
-                    .make_channel(channel, None, (creator.0, creator.1), &mut [])
-                    .await;
-            }
-
-            observed_channels.insert(channel, id);
-            result.push(id);
-        }
-
-        result
-    }
-
-    pub async fn create_room(&self, clients: &mut [(&TestClient, &mut TestAppContext)]) {
-        self.make_contacts(clients).await;
-
-        let (left, right) = clients.split_at_mut(1);
-        let (_client_a, cx_a) = &mut left[0];
-        let active_call_a = cx_a.read(ActiveCall::global);
-
-        for (client_b, cx_b) in right {
-            let user_id_b = client_b.current_user_id(*cx_b).to_proto();
-            active_call_a
-                .update(*cx_a, |call, cx| call.invite(user_id_b, None, cx))
-                .await
-                .unwrap();
-
-            cx_b.executor().run_until_parked();
-            let active_call_b = cx_b.read(ActiveCall::global);
-            active_call_b
-                .update(*cx_b, |call, cx| call.accept_incoming(cx))
-                .await
-                .unwrap();
-        }
-    }
-
-    pub async fn build_app_state(
-        test_db: &TestDb,
-        fake_server: &live_kit_client::TestServer,
-    ) -> Arc<AppState> {
-        Arc::new(AppState {
-            db: test_db.db().clone(),
-            live_kit_client: Some(Arc::new(fake_server.create_api_client())),
-            config: Default::default(),
-        })
-    }
-}
-
-impl Deref for TestServer {
-    type Target = Server;
-
-    fn deref(&self) -> &Self::Target {
-        &self.server
-    }
-}
-
-impl Drop for TestServer {
-    fn drop(&mut self) {
-        self.server.teardown();
-        self.test_live_kit_server.teardown().unwrap();
-    }
-}
-
-impl Deref for TestClient {
-    type Target = Arc<Client>;
-
-    fn deref(&self) -> &Self::Target {
-        &self.app_state.client
-    }
-}
-
-impl TestClient {
-    pub fn fs(&self) -> &FakeFs {
-        self.app_state.fs.as_fake()
-    }
-
-    pub fn channel_store(&self) -> &Model<ChannelStore> {
-        &self.channel_store
-    }
-
-    pub fn notification_store(&self) -> &Model<NotificationStore> {
-        &self.notification_store
-    }
-
-    pub fn user_store(&self) -> &Model<UserStore> {
-        &self.app_state.user_store
-    }
-
-    pub fn language_registry(&self) -> &Arc<LanguageRegistry> {
-        &self.app_state.languages
-    }
-
-    pub fn client(&self) -> &Arc<Client> {
-        &self.app_state.client
-    }
-
-    pub fn current_user_id(&self, cx: &TestAppContext) -> UserId {
-        UserId::from_proto(
-            self.app_state
-                .user_store
-                .read_with(cx, |user_store, _| user_store.current_user().unwrap().id),
-        )
-    }
-
-    pub async fn wait_for_current_user(&self, cx: &TestAppContext) {
-        let mut authed_user = self
-            .app_state
-            .user_store
-            .read_with(cx, |user_store, _| user_store.watch_current_user());
-        while authed_user.next().await.unwrap().is_none() {}
-    }
-
-    pub async fn clear_contacts(&self, cx: &mut TestAppContext) {
-        self.app_state
-            .user_store
-            .update(cx, |store, _| store.clear_contacts())
-            .await;
-    }
-
-    pub fn local_projects<'a>(&'a self) -> impl Deref<Target = Vec<Model<Project>>> + 'a {
-        Ref::map(self.state.borrow(), |state| &state.local_projects)
-    }
-
-    pub fn remote_projects<'a>(&'a self) -> impl Deref<Target = Vec<Model<Project>>> + 'a {
-        Ref::map(self.state.borrow(), |state| &state.remote_projects)
-    }
-
-    pub fn local_projects_mut<'a>(&'a self) -> impl DerefMut<Target = Vec<Model<Project>>> + 'a {
-        RefMut::map(self.state.borrow_mut(), |state| &mut state.local_projects)
-    }
-
-    pub fn remote_projects_mut<'a>(&'a self) -> impl DerefMut<Target = Vec<Model<Project>>> + 'a {
-        RefMut::map(self.state.borrow_mut(), |state| &mut state.remote_projects)
-    }
-
-    pub fn buffers_for_project<'a>(
-        &'a self,
-        project: &Model<Project>,
-    ) -> impl DerefMut<Target = HashSet<Model<language::Buffer>>> + 'a {
-        RefMut::map(self.state.borrow_mut(), |state| {
-            state.buffers.entry(project.clone()).or_default()
-        })
-    }
-
-    pub fn buffers<'a>(
-        &'a self,
-    ) -> impl DerefMut<Target = HashMap<Model<Project>, HashSet<Model<language::Buffer>>>> + 'a
-    {
-        RefMut::map(self.state.borrow_mut(), |state| &mut state.buffers)
-    }
-
-    pub fn channel_buffers<'a>(
-        &'a self,
-    ) -> impl DerefMut<Target = HashSet<Model<ChannelBuffer>>> + 'a {
-        RefMut::map(self.state.borrow_mut(), |state| &mut state.channel_buffers)
-    }
-
-    pub fn summarize_contacts(&self, cx: &TestAppContext) -> ContactsSummary {
-        self.app_state
-            .user_store
-            .read_with(cx, |store, _| ContactsSummary {
-                current: store
-                    .contacts()
-                    .iter()
-                    .map(|contact| contact.user.github_login.clone())
-                    .collect(),
-                outgoing_requests: store
-                    .outgoing_contact_requests()
-                    .iter()
-                    .map(|user| user.github_login.clone())
-                    .collect(),
-                incoming_requests: store
-                    .incoming_contact_requests()
-                    .iter()
-                    .map(|user| user.github_login.clone())
-                    .collect(),
-            })
-    }
-
-    pub async fn build_local_project(
-        &self,
-        root_path: impl AsRef<Path>,
-        cx: &mut TestAppContext,
-    ) -> (Model<Project>, WorktreeId) {
-        let project = self.build_empty_local_project(cx);
-        let (worktree, _) = project
-            .update(cx, |p, cx| {
-                p.find_or_create_local_worktree(root_path, true, cx)
-            })
-            .await
-            .unwrap();
-        worktree
-            .read_with(cx, |tree, _| tree.as_local().unwrap().scan_complete())
-            .await;
-        (project, worktree.read_with(cx, |tree, _| tree.id()))
-    }
-
-    pub fn build_empty_local_project(&self, cx: &mut TestAppContext) -> Model<Project> {
-        cx.update(|cx| {
-            Project::local(
-                self.client().clone(),
-                self.app_state.node_runtime.clone(),
-                self.app_state.user_store.clone(),
-                self.app_state.languages.clone(),
-                self.app_state.fs.clone(),
-                cx,
-            )
-        })
-    }
-
-    pub async fn build_remote_project(
-        &self,
-        host_project_id: u64,
-        guest_cx: &mut TestAppContext,
-    ) -> Model<Project> {
-        let active_call = guest_cx.read(ActiveCall::global);
-        let room = active_call.read_with(guest_cx, |call, _| call.room().unwrap().clone());
-        room.update(guest_cx, |room, cx| {
-            room.join_project(
-                host_project_id,
-                self.app_state.languages.clone(),
-                self.app_state.fs.clone(),
-                cx,
-            )
-        })
-        .await
-        .unwrap()
-    }
-
-    pub fn build_workspace<'a>(
-        &'a self,
-        project: &Model<Project>,
-        cx: &'a mut TestAppContext,
-    ) -> (View<Workspace>, &'a mut VisualTestContext) {
-        cx.add_window_view(|cx| Workspace::new(0, project.clone(), self.app_state.clone(), cx))
-    }
-}
-
-impl Drop for TestClient {
-    fn drop(&mut self) {
-        self.app_state.client.teardown();
-    }
-}

crates/collab_ui/Cargo.toml 🔗

@@ -29,14 +29,14 @@ client = { path = "../client" }
 channel = { path = "../channel" }
 clock = { path = "../clock" }
 collections = { path = "../collections" }
-context_menu = { path = "../context_menu" }
-drag_and_drop = { path = "../drag_and_drop" }
+# context_menu = { path = "../context_menu" }
+# drag_and_drop = { path = "../drag_and_drop" }
 editor = { path = "../editor" }
 feedback = { path = "../feedback" }
-fuzzy = { path = "../fuzzy" }
+fuzzy = {  path = "../fuzzy" }
 gpui = { path = "../gpui" }
 language = { path = "../language" }
-menu = { path = "../menu" }
+menu = {  path = "../menu" }
 notifications = { path = "../notifications" }
 rich_text = { path = "../rich_text" }
 picker = { path = "../picker" }
@@ -44,13 +44,14 @@ project = { path = "../project" }
 recent_projects = { path = "../recent_projects" }
 rpc = { path = "../rpc" }
 settings = { path = "../settings" }
-feature_flags = {path = "../feature_flags"}
+feature_flags = { path = "../feature_flags"}
 theme = { path = "../theme" }
 theme_selector = { path = "../theme_selector" }
 vcs_menu = { path = "../vcs_menu" }
+ui = { path = "../ui" }
 util = { path = "../util" }
 workspace = { path = "../workspace" }
-zed-actions = {path = "../zed-actions"}
+zed_actions = { path = "../zed_actions"}
 
 anyhow.workspace = true
 futures.workspace = true

crates/collab_ui/src/channel_view.rs 🔗

@@ -1,4 +1,4 @@
-use anyhow::{anyhow, Result};
+use anyhow::Result;
 use call::report_call_event_for_channel;
 use channel::{Channel, ChannelBuffer, ChannelBufferEvent, ChannelId, ChannelStore};
 use client::{
@@ -6,20 +6,18 @@ use client::{
     Collaborator, ParticipantIndex,
 };
 use collections::HashMap;
-use editor::{CollaborationHub, Editor};
+use editor::{CollaborationHub, Editor, EditorEvent};
 use gpui::{
-    actions,
-    elements::{ChildView, Label},
-    geometry::vector::Vector2F,
-    AnyElement, AnyViewHandle, AppContext, Element, Entity, ModelHandle, Subscription, Task, View,
-    ViewContext, ViewHandle,
+    actions, AnyElement, AnyView, AppContext, Entity as _, EventEmitter, FocusableView,
+    IntoElement as _, Model, Pixels, Point, Render, Subscription, Task, View, ViewContext,
+    VisualContext as _, WindowContext,
 };
 use project::Project;
-use smallvec::SmallVec;
 use std::{
     any::{Any, TypeId},
     sync::Arc,
 };
+use ui::{prelude::*, Label};
 use util::ResultExt;
 use workspace::{
     item::{FollowableItem, Item, ItemEvent, ItemHandle},
@@ -28,17 +26,17 @@ use workspace::{
     ItemNavHistory, Pane, SaveIntent, ViewId, Workspace, WorkspaceId,
 };
 
-actions!(channel_view, [Deploy]);
+actions!(collab, [Deploy]);
 
 pub fn init(cx: &mut AppContext) {
     register_followable_item::<ChannelView>(cx)
 }
 
 pub struct ChannelView {
-    pub editor: ViewHandle<Editor>,
-    project: ModelHandle<Project>,
-    channel_store: ModelHandle<ChannelStore>,
-    channel_buffer: ModelHandle<ChannelBuffer>,
+    pub editor: View<Editor>,
+    project: Model<Project>,
+    channel_store: Model<ChannelStore>,
+    channel_buffer: Model<ChannelBuffer>,
     remote_id: Option<ViewId>,
     _editor_event_subscription: Subscription,
 }
@@ -46,9 +44,9 @@ pub struct ChannelView {
 impl ChannelView {
     pub fn open(
         channel_id: ChannelId,
-        workspace: ViewHandle<Workspace>,
-        cx: &mut AppContext,
-    ) -> Task<Result<ViewHandle<Self>>> {
+        workspace: View<Workspace>,
+        cx: &mut WindowContext,
+    ) -> Task<Result<View<Self>>> {
         let pane = workspace.read(cx).active_pane().clone();
         let channel_view = Self::open_in_pane(channel_id, pane.clone(), workspace.clone(), cx);
         cx.spawn(|mut cx| async move {
@@ -61,17 +59,17 @@ impl ChannelView {
                     cx,
                 );
                 pane.add_item(Box::new(channel_view.clone()), true, true, None, cx);
-            });
+            })?;
             anyhow::Ok(channel_view)
         })
     }
 
     pub fn open_in_pane(
         channel_id: ChannelId,
-        pane: ViewHandle<Pane>,
-        workspace: ViewHandle<Workspace>,
-        cx: &mut AppContext,
-    ) -> Task<Result<ViewHandle<Self>>> {
+        pane: View<Pane>,
+        workspace: View<Workspace>,
+        cx: &mut WindowContext,
+    ) -> Task<Result<View<Self>>> {
         let workspace = workspace.read(cx);
         let project = workspace.project().to_owned();
         let channel_store = ChannelStore::global(cx);
@@ -91,7 +89,7 @@ impl ChannelView {
                         buffer.set_language(Some(markdown), cx);
                     }
                 })
-            });
+            })?;
 
             pane.update(&mut cx, |pane, cx| {
                 let buffer_id = channel_buffer.read(cx).remote_id(cx);
@@ -107,7 +105,7 @@ impl ChannelView {
                     }
                 }
 
-                let view = cx.add_view(|cx| {
+                let view = cx.new_view(|cx| {
                     let mut this = Self::new(project, channel_store, channel_buffer, cx);
                     this.acknowledge_buffer_version(cx);
                     this
@@ -117,7 +115,7 @@ impl ChannelView {
                 // replace that.
                 if let Some(existing_item) = existing_view {
                     if let Some(ix) = pane.index_for_item(&existing_item) {
-                        pane.close_item_by_id(existing_item.id(), SaveIntent::Skip, cx)
+                        pane.close_item_by_id(existing_item.entity_id(), SaveIntent::Skip, cx)
                             .detach();
                         pane.add_item(Box::new(view.clone()), true, true, Some(ix), cx);
                     }
@@ -125,18 +123,17 @@ impl ChannelView {
 
                 view
             })
-            .ok_or_else(|| anyhow!("pane was dropped"))
         })
     }
 
     pub fn new(
-        project: ModelHandle<Project>,
-        channel_store: ModelHandle<ChannelStore>,
-        channel_buffer: ModelHandle<ChannelBuffer>,
+        project: Model<Project>,
+        channel_store: Model<ChannelStore>,
+        channel_buffer: Model<ChannelBuffer>,
         cx: &mut ViewContext<Self>,
     ) -> Self {
         let buffer = channel_buffer.read(cx).buffer();
-        let editor = cx.add_view(|cx| {
+        let editor = cx.new_view(|cx| {
             let mut editor = Editor::for_buffer(buffer, None, cx);
             editor.set_collaboration_hub(Box::new(ChannelBufferCollaborationHub(
                 channel_buffer.clone(),
@@ -149,7 +146,8 @@ impl ChannelView {
             );
             editor
         });
-        let _editor_event_subscription = cx.subscribe(&editor, |_, _, e, cx| cx.emit(e.clone()));
+        let _editor_event_subscription =
+            cx.subscribe(&editor, |_, _, e: &EditorEvent, cx| cx.emit(e.clone()));
 
         cx.subscribe(&channel_buffer, Self::handle_channel_buffer_event)
             .detach();
@@ -170,7 +168,7 @@ impl ChannelView {
 
     fn handle_channel_buffer_event(
         &mut self,
-        _: ModelHandle<ChannelBuffer>,
+        _: Model<ChannelBuffer>,
         event: &ChannelBufferEvent,
         cx: &mut ViewContext<Self>,
     ) {
@@ -182,12 +180,12 @@ impl ChannelView {
             ChannelBufferEvent::ChannelChanged => {
                 self.editor.update(cx, |editor, cx| {
                     editor.set_read_only(!self.channel(cx).is_some_and(|c| c.can_edit_notes()));
-                    cx.emit(editor::Event::TitleChanged);
+                    cx.emit(editor::EditorEvent::TitleChanged);
                     cx.notify()
                 });
             }
             ChannelBufferEvent::BufferEdited => {
-                if cx.is_self_focused() || self.editor.is_focused(cx) {
+                if self.editor.read(cx).is_focused(cx) {
                     self.acknowledge_buffer_version(cx);
                 } else {
                     self.channel_store.update(cx, |store, cx| {
@@ -205,7 +203,7 @@ impl ChannelView {
         }
     }
 
-    fn acknowledge_buffer_version(&mut self, cx: &mut ViewContext<'_, '_, ChannelView>) {
+    fn acknowledge_buffer_version(&mut self, cx: &mut ViewContext<ChannelView>) {
         self.channel_store.update(cx, |store, cx| {
             let channel_buffer = self.channel_buffer.read(cx);
             store.acknowledge_notes_version(
@@ -221,49 +219,39 @@ impl ChannelView {
     }
 }
 
-impl Entity for ChannelView {
-    type Event = editor::Event;
-}
-
-impl View for ChannelView {
-    fn ui_name() -> &'static str {
-        "ChannelView"
-    }
+impl EventEmitter<EditorEvent> for ChannelView {}
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        ChildView::new(self.editor.as_any(), cx).into_any()
+impl Render for ChannelView {
+    fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
+        self.editor.clone()
     }
+}
 
-    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
-        if cx.is_self_focused() {
-            self.acknowledge_buffer_version(cx);
-            cx.focus(self.editor.as_any())
-        }
+impl FocusableView for ChannelView {
+    fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
+        self.editor.read(cx).focus_handle(cx)
     }
 }
 
 impl Item for ChannelView {
+    type Event = EditorEvent;
+
     fn act_as_type<'a>(
         &'a self,
         type_id: TypeId,
-        self_handle: &'a ViewHandle<Self>,
+        self_handle: &'a View<Self>,
         _: &'a AppContext,
-    ) -> Option<&'a AnyViewHandle> {
+    ) -> Option<AnyView> {
         if type_id == TypeId::of::<Self>() {
-            Some(self_handle)
+            Some(self_handle.to_any())
         } else if type_id == TypeId::of::<Editor>() {
-            Some(&self.editor)
+            Some(self.editor.to_any())
         } else {
             None
         }
     }
 
-    fn tab_content<V: 'static>(
-        &self,
-        _: Option<usize>,
-        style: &theme::Tab,
-        cx: &gpui::AppContext,
-    ) -> AnyElement<V> {
+    fn tab_content(&self, _: Option<usize>, selected: bool, cx: &WindowContext) -> AnyElement {
         let label = if let Some(channel) = self.channel(cx) {
             match (
                 channel.can_edit_notes(),
@@ -276,16 +264,24 @@ impl Item for ChannelView {
         } else {
             format!("channel notes (disconnected)")
         };
-        Label::new(label, style.label.to_owned()).into_any()
+        Label::new(label)
+            .color(if selected {
+                Color::Default
+            } else {
+                Color::Muted
+            })
+            .into_any_element()
     }
 
-    fn clone_on_split(&self, _: WorkspaceId, cx: &mut ViewContext<Self>) -> Option<Self> {
-        Some(Self::new(
-            self.project.clone(),
-            self.channel_store.clone(),
-            self.channel_buffer.clone(),
-            cx,
-        ))
+    fn clone_on_split(&self, _: WorkspaceId, cx: &mut ViewContext<Self>) -> Option<View<Self>> {
+        Some(cx.new_view(|cx| {
+            Self::new(
+                self.project.clone(),
+                self.channel_store.clone(),
+                self.channel_buffer.clone(),
+                cx,
+            )
+        }))
     }
 
     fn is_singleton(&self, _cx: &AppContext) -> bool {
@@ -307,7 +303,7 @@ impl Item for ChannelView {
             .update(cx, |editor, cx| Item::set_nav_history(editor, history, cx))
     }
 
-    fn as_searchable(&self, _: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
+    fn as_searchable(&self, _: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
         Some(Box::new(self.editor.clone()))
     }
 
@@ -315,12 +311,12 @@ impl Item for ChannelView {
         true
     }
 
-    fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Vector2F> {
+    fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>> {
         self.editor.read(cx).pixel_position_of_cursor(cx)
     }
 
-    fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
-        editor::Editor::to_item_events(event)
+    fn to_item_events(event: &EditorEvent, f: impl FnMut(ItemEvent)) {
+        Editor::to_item_events(event, f)
     }
 }
 
@@ -329,7 +325,7 @@ impl FollowableItem for ChannelView {
         self.remote_id
     }
 
-    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
+    fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
         let channel_buffer = self.channel_buffer.read(cx);
         if !channel_buffer.is_connected() {
             return None;
@@ -350,12 +346,12 @@ impl FollowableItem for ChannelView {
     }
 
     fn from_state_proto(
-        pane: ViewHandle<workspace::Pane>,
-        workspace: ViewHandle<workspace::Workspace>,
+        pane: View<workspace::Pane>,
+        workspace: View<workspace::Workspace>,
         remote_id: workspace::ViewId,
         state: &mut Option<proto::view::Variant>,
-        cx: &mut AppContext,
-    ) -> Option<gpui::Task<anyhow::Result<ViewHandle<Self>>>> {
+        cx: &mut WindowContext,
+    ) -> Option<gpui::Task<anyhow::Result<View<Self>>>> {
         let Some(proto::view::Variant::ChannelView(_)) = state else {
             return None;
         };
@@ -368,30 +364,28 @@ impl FollowableItem for ChannelView {
         Some(cx.spawn(|mut cx| async move {
             let this = open.await?;
 
-            let task = this
-                .update(&mut cx, |this, cx| {
-                    this.remote_id = Some(remote_id);
-
-                    if let Some(state) = state.editor {
-                        Some(this.editor.update(cx, |editor, cx| {
-                            editor.apply_update_proto(
-                                &this.project,
-                                proto::update_view::Variant::Editor(proto::update_view::Editor {
-                                    selections: state.selections,
-                                    pending_selection: state.pending_selection,
-                                    scroll_top_anchor: state.scroll_top_anchor,
-                                    scroll_x: state.scroll_x,
-                                    scroll_y: state.scroll_y,
-                                    ..Default::default()
-                                }),
-                                cx,
-                            )
-                        }))
-                    } else {
-                        None
-                    }
-                })
-                .ok_or_else(|| anyhow!("window was closed"))?;
+            let task = this.update(&mut cx, |this, cx| {
+                this.remote_id = Some(remote_id);
+
+                if let Some(state) = state.editor {
+                    Some(this.editor.update(cx, |editor, cx| {
+                        editor.apply_update_proto(
+                            &this.project,
+                            proto::update_view::Variant::Editor(proto::update_view::Editor {
+                                selections: state.selections,
+                                pending_selection: state.pending_selection,
+                                scroll_top_anchor: state.scroll_top_anchor,
+                                scroll_x: state.scroll_x,
+                                scroll_y: state.scroll_y,
+                                ..Default::default()
+                            }),
+                            cx,
+                        )
+                    }))
+                } else {
+                    None
+                }
+            })?;
 
             if let Some(task) = task {
                 task.await?;
@@ -403,9 +397,9 @@ impl FollowableItem for ChannelView {
 
     fn add_event_to_update_proto(
         &self,
-        event: &Self::Event,
+        event: &EditorEvent,
         update: &mut Option<proto::update_view::Variant>,
-        cx: &AppContext,
+        cx: &WindowContext,
     ) -> bool {
         self.editor
             .read(cx)
@@ -414,7 +408,7 @@ impl FollowableItem for ChannelView {
 
     fn apply_update_proto(
         &mut self,
-        project: &ModelHandle<Project>,
+        project: &Model<Project>,
         message: proto::update_view::Variant,
         cx: &mut ViewContext<Self>,
     ) -> gpui::Task<anyhow::Result<()>> {
@@ -429,16 +423,16 @@ impl FollowableItem for ChannelView {
         })
     }
 
-    fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool {
-        Editor::should_unfollow_on_event(event, cx)
+    fn is_project_item(&self, _cx: &WindowContext) -> bool {
+        false
     }
 
-    fn is_project_item(&self, _cx: &AppContext) -> bool {
-        false
+    fn to_follow_event(event: &Self::Event) -> Option<workspace::item::FollowEvent> {
+        Editor::to_follow_event(event)
     }
 }
 
-struct ChannelBufferCollaborationHub(ModelHandle<ChannelBuffer>);
+struct ChannelBufferCollaborationHub(Model<ChannelBuffer>);
 
 impl CollaborationHub for ChannelBufferCollaborationHub {
     fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator> {

crates/collab_ui/src/chat_panel.rs 🔗

@@ -1,6 +1,4 @@
-use crate::{
-    channel_view::ChannelView, is_channels_feature_enabled, render_avatar, ChatPanelSettings,
-};
+use crate::{channel_view::ChannelView, is_channels_feature_enabled, ChatPanelSettings};
 use anyhow::Result;
 use call::ActiveCall;
 use channel::{ChannelChat, ChannelChatEvent, ChannelMessageId, ChannelStore};
@@ -9,13 +7,9 @@ use collections::HashMap;
 use db::kvp::KEY_VALUE_STORE;
 use editor::Editor;
 use gpui::{
-    actions,
-    elements::*,
-    platform::{CursorStyle, MouseButton},
-    serde_json,
-    views::{ItemType, Select, SelectStyle},
-    AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelHandle, Subscription, Task, View,
-    ViewContext, ViewHandle, WeakViewHandle,
+    actions, div, list, prelude::*, px, serde_json, AnyElement, AppContext, AsyncWindowContext,
+    ClickEvent, ElementId, EventEmitter, FocusableView, ListOffset, ListScrollEvent, ListState,
+    Model, Render, Subscription, Task, View, ViewContext, VisualContext, WeakView,
 };
 use language::LanguageRegistry;
 use menu::Confirm;
@@ -23,13 +17,14 @@ use message_editor::MessageEditor;
 use project::Fs;
 use rich_text::RichText;
 use serde::{Deserialize, Serialize};
-use settings::SettingsStore;
+use settings::{Settings, SettingsStore};
 use std::sync::Arc;
-use theme::{IconButton, Theme};
+use theme::ActiveTheme as _;
 use time::{OffsetDateTime, UtcOffset};
+use ui::{prelude::*, Avatar, Button, Icon, IconButton, Label, TabBar, Tooltip};
 use util::{ResultExt, TryFutureExt};
 use workspace::{
-    dock::{DockPosition, Panel},
+    dock::{DockPosition, Panel, PanelEvent},
     Workspace,
 };
 
@@ -38,29 +33,36 @@ mod message_editor;
 const MESSAGE_LOADING_THRESHOLD: usize = 50;
 const CHAT_PANEL_KEY: &'static str = "ChatPanel";
 
+pub fn init(cx: &mut AppContext) {
+    cx.observe_new_views(|workspace: &mut Workspace, _| {
+        workspace.register_action(|workspace, _: &ToggleFocus, cx| {
+            workspace.toggle_panel_focus::<ChatPanel>(cx);
+        });
+    })
+    .detach();
+}
+
 pub struct ChatPanel {
     client: Arc<Client>,
-    channel_store: ModelHandle<ChannelStore>,
+    channel_store: Model<ChannelStore>,
     languages: Arc<LanguageRegistry>,
-    active_chat: Option<(ModelHandle<ChannelChat>, Subscription)>,
-    message_list: ListState<ChatPanel>,
-    input_editor: ViewHandle<MessageEditor>,
-    channel_select: ViewHandle<Select>,
+    message_list: ListState,
+    active_chat: Option<(Model<ChannelChat>, Subscription)>,
+    input_editor: View<MessageEditor>,
     local_timezone: UtcOffset,
     fs: Arc<dyn Fs>,
-    width: Option<f32>,
+    width: Option<Pixels>,
     active: bool,
     pending_serialization: Task<Option<()>>,
     subscriptions: Vec<gpui::Subscription>,
-    workspace: WeakViewHandle<Workspace>,
+    workspace: WeakView<Workspace>,
     is_scrolled_to_bottom: bool,
-    has_focus: bool,
     markdown_data: HashMap<ChannelMessageId, RichText>,
 }
 
 #[derive(Serialize, Deserialize)]
 struct SerializedChatPanel {
-    width: Option<f32>,
+    width: Option<Pixels>,
 }
 
 #[derive(Debug)]
@@ -70,90 +72,56 @@ pub enum Event {
     Dismissed,
 }
 
-actions!(
-    chat_panel,
-    [LoadMoreMessages, ToggleFocus, OpenChannelNotes, JoinCall]
-);
-
-pub fn init(cx: &mut AppContext) {
-    cx.add_action(ChatPanel::send);
-    cx.add_action(ChatPanel::load_more_messages);
-    cx.add_action(ChatPanel::open_notes);
-    cx.add_action(ChatPanel::join_call);
-}
+actions!(chat_panel, [ToggleFocus]);
 
 impl ChatPanel {
-    pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> ViewHandle<Self> {
+    pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
         let fs = workspace.app_state().fs.clone();
         let client = workspace.app_state().client.clone();
         let channel_store = ChannelStore::global(cx);
         let languages = workspace.app_state().languages.clone();
 
-        let input_editor = cx.add_view(|cx| {
+        let input_editor = cx.new_view(|cx| {
             MessageEditor::new(
                 languages.clone(),
                 channel_store.clone(),
-                cx.add_view(|cx| {
-                    Editor::auto_height(
-                        4,
-                        Some(Arc::new(|theme| theme.chat_panel.input_editor.clone())),
-                        cx,
-                    )
-                }),
+                cx.new_view(|cx| Editor::auto_height(4, cx)),
                 cx,
             )
         });
 
         let workspace_handle = workspace.weak_handle();
 
-        let channel_select = cx.add_view(|cx| {
-            let channel_store = channel_store.clone();
-            let workspace = workspace_handle.clone();
-            Select::new(0, cx, {
-                move |ix, item_type, is_hovered, cx| {
-                    Self::render_channel_name(
-                        &channel_store,
-                        ix,
-                        item_type,
-                        is_hovered,
-                        workspace,
-                        cx,
-                    )
-                }
-            })
-            .with_style(move |cx| {
-                let style = &theme::current(cx).chat_panel.channel_select;
-                SelectStyle {
-                    header: Default::default(),
-                    menu: style.menu,
-                }
-            })
-        });
+        cx.new_view(|cx: &mut ViewContext<Self>| {
+            let view = cx.view().downgrade();
+            let message_list =
+                ListState::new(0, gpui::ListAlignment::Bottom, px(1000.), move |ix, cx| {
+                    if let Some(view) = view.upgrade() {
+                        view.update(cx, |view, cx| {
+                            view.render_message(ix, cx).into_any_element()
+                        })
+                    } else {
+                        div().into_any()
+                    }
+                });
 
-        let mut message_list =
-            ListState::<Self>::new(0, Orientation::Bottom, 10., move |this, ix, cx| {
-                this.render_message(ix, cx)
-            });
-        message_list.set_scroll_handler(|visible_range, count, this, cx| {
-            if visible_range.start < MESSAGE_LOADING_THRESHOLD {
-                this.load_more_messages(&LoadMoreMessages, cx);
-            }
-            this.is_scrolled_to_bottom = visible_range.end == count;
-        });
+            message_list.set_scroll_handler(cx.listener(|this, event: &ListScrollEvent, cx| {
+                if event.visible_range.start < MESSAGE_LOADING_THRESHOLD {
+                    this.load_more_messages(cx);
+                }
+                this.is_scrolled_to_bottom = event.visible_range.end == event.count;
+            }));
 
-        cx.add_view(|cx| {
             let mut this = Self {
                 fs,
                 client,
                 channel_store,
                 languages,
+                message_list,
                 active_chat: Default::default(),
                 pending_serialization: Task::ready(None),
-                message_list,
                 input_editor,
-                channel_select,
-                local_timezone: cx.platform().local_timezone(),
-                has_focus: false,
+                local_timezone: cx.local_timezone(),
                 subscriptions: Vec::new(),
                 workspace: workspace_handle,
                 is_scrolled_to_bottom: true,
@@ -163,38 +131,16 @@ impl ChatPanel {
             };
 
             let mut old_dock_position = this.position(cx);
-            this.subscriptions
-                .push(
-                    cx.observe_global::<SettingsStore, _>(move |this: &mut Self, cx| {
-                        let new_dock_position = this.position(cx);
-                        if new_dock_position != old_dock_position {
-                            old_dock_position = new_dock_position;
-                            cx.emit(Event::DockPositionChanged);
-                        }
-                        cx.notify();
-                    }),
-                );
-
-            this.update_channel_count(cx);
-            cx.observe(&this.channel_store, |this, _, cx| {
-                this.update_channel_count(cx)
-            })
-            .detach();
-
-            cx.observe(&this.channel_select, |this, channel_select, cx| {
-                let selected_ix = channel_select.read(cx).selected_index();
-
-                let selected_channel_id = this
-                    .channel_store
-                    .read(cx)
-                    .channel_at(selected_ix)
-                    .map(|e| e.id);
-                if let Some(selected_channel_id) = selected_channel_id {
-                    this.select_channel(selected_channel_id, None, cx)
-                        .detach_and_log_err(cx);
-                }
-            })
-            .detach();
+            this.subscriptions.push(cx.observe_global::<SettingsStore>(
+                move |this: &mut Self, cx| {
+                    let new_dock_position = this.position(cx);
+                    if new_dock_position != old_dock_position {
+                        old_dock_position = new_dock_position;
+                        cx.emit(Event::DockPositionChanged);
+                    }
+                    cx.notify();
+                },
+            ));
 
             this
         })
@@ -204,17 +150,17 @@ impl ChatPanel {
         self.is_scrolled_to_bottom
     }
 
-    pub fn active_chat(&self) -> Option<ModelHandle<ChannelChat>> {
+    pub fn active_chat(&self) -> Option<Model<ChannelChat>> {
         self.active_chat.as_ref().map(|(chat, _)| chat.clone())
     }
 
     pub fn load(
-        workspace: WeakViewHandle<Workspace>,
-        cx: AsyncAppContext,
-    ) -> Task<Result<ViewHandle<Self>>> {
+        workspace: WeakView<Workspace>,
+        cx: AsyncWindowContext,
+    ) -> Task<Result<View<Self>>> {
         cx.spawn(|mut cx| async move {
             let serialized_panel = if let Some(panel) = cx
-                .background()
+                .background_executor()
                 .spawn(async move { KEY_VALUE_STORE.read_kvp(CHAT_PANEL_KEY) })
                 .await
                 .log_err()
@@ -240,7 +186,7 @@ impl ChatPanel {
 
     fn serialize(&mut self, cx: &mut ViewContext<Self>) {
         let width = self.width;
-        self.pending_serialization = cx.background().spawn(
+        self.pending_serialization = cx.background_executor().spawn(
             async move {
                 KEY_VALUE_STORE
                     .write_kvp(
@@ -254,14 +200,7 @@ impl ChatPanel {
         );
     }
 
-    fn update_channel_count(&mut self, cx: &mut ViewContext<Self>) {
-        let channel_count = self.channel_store.read(cx).channel_count();
-        self.channel_select.update(cx, |select, cx| {
-            select.set_item_count(channel_count, cx);
-        });
-    }
-
-    fn set_active_chat(&mut self, chat: ModelHandle<ChannelChat>, cx: &mut ViewContext<Self>) {
+    fn set_active_chat(&mut self, chat: Model<ChannelChat>, cx: &mut ViewContext<Self>) {
         if self.active_chat.as_ref().map(|e| &e.0) != Some(&chat) {
             let channel_id = chat.read(cx).channel_id;
             {
@@ -277,18 +216,13 @@ impl ChatPanel {
             let subscription = cx.subscribe(&chat, Self::channel_did_change);
             self.active_chat = Some((chat, subscription));
             self.acknowledge_last_message(cx);
-            self.channel_select.update(cx, |select, cx| {
-                if let Some(ix) = self.channel_store.read(cx).index_of_channel(channel_id) {
-                    select.set_selected_index(ix, cx);
-                }
-            });
             cx.notify();
         }
     }
 
     fn channel_did_change(
         &mut self,
-        _: ModelHandle<ChannelChat>,
+        _: Model<ChannelChat>,
         event: &ChannelChatEvent,
         cx: &mut ViewContext<Self>,
     ) {
@@ -316,7 +250,7 @@ impl ChatPanel {
         cx.notify();
     }
 
-    fn acknowledge_last_message(&mut self, cx: &mut ViewContext<'_, '_, ChatPanel>) {
+    fn acknowledge_last_message(&mut self, cx: &mut ViewContext<Self>) {
         if self.active && self.is_scrolled_to_bottom {
             if let Some((chat, _)) = &self.active_chat {
                 chat.update(cx, |chat, cx| {
@@ -326,39 +260,60 @@ impl ChatPanel {
         }
     }
 
-    fn render_channel(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = theme::current(cx);
-        Flex::column()
-            .with_child(
-                ChildView::new(&self.channel_select, cx)
-                    .contained()
-                    .with_style(theme.chat_panel.channel_select.container),
+    fn render_channel(&self, cx: &mut ViewContext<Self>) -> AnyElement {
+        v_stack()
+            .full()
+            .on_action(cx.listener(Self::send))
+            .child(
+                h_stack().z_index(1).child(
+                    TabBar::new("chat_header")
+                        .child(
+                            h_stack()
+                                .w_full()
+                                .h(rems(ui::Tab::HEIGHT_IN_REMS))
+                                .px_2()
+                                .child(Label::new(
+                                    self.active_chat
+                                        .as_ref()
+                                        .and_then(|c| {
+                                            Some(format!("#{}", c.0.read(cx).channel(cx)?.name))
+                                        })
+                                        .unwrap_or_default(),
+                                )),
+                        )
+                        .end_child(
+                            IconButton::new("notes", Icon::File)
+                                .on_click(cx.listener(Self::open_notes))
+                                .tooltip(|cx| Tooltip::text("Open notes", cx)),
+                        )
+                        .end_child(
+                            IconButton::new("call", Icon::AudioOn)
+                                .on_click(cx.listener(Self::join_call))
+                                .tooltip(|cx| Tooltip::text("Join call", cx)),
+                        ),
+                ),
+            )
+            .child(div().flex_grow().px_2().py_1().map(|this| {
+                if self.active_chat.is_some() {
+                    this.child(list(self.message_list.clone()).full())
+                } else {
+                    this
+                }
+            }))
+            .child(
+                div()
+                    .z_index(1)
+                    .p_2()
+                    .bg(cx.theme().colors().background)
+                    .child(self.input_editor.clone()),
             )
-            .with_child(self.render_active_channel_messages(&theme))
-            .with_child(self.render_input_box(&theme, cx))
             .into_any()
     }
 
-    fn render_active_channel_messages(&self, theme: &Arc<Theme>) -> AnyElement<Self> {
-        let messages = if self.active_chat.is_some() {
-            List::new(self.message_list.clone())
-                .contained()
-                .with_style(theme.chat_panel.list)
-                .into_any()
-        } else {
-            Empty::new().into_any()
-        };
-
-        messages.flex(1., true).into_any()
-    }
-
-    fn render_message(&mut self, ix: usize, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let (message, is_continuation, is_last, is_admin) = self
-            .active_chat
-            .as_ref()
-            .unwrap()
-            .0
-            .update(cx, |active_chat, cx| {
+    fn render_message(&mut self, ix: usize, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        let active_chat = &self.active_chat.as_ref().unwrap().0;
+        let (message, is_continuation_from_previous, is_continuation_to_next, is_admin) =
+            active_chat.update(cx, |active_chat, cx| {
                 let is_admin = self
                     .channel_store
                     .read(cx)
@@ -366,8 +321,13 @@ impl ChatPanel {
 
                 let last_message = active_chat.message(ix.saturating_sub(1));
                 let this_message = active_chat.message(ix).clone();
-                let is_continuation = last_message.id != this_message.id
-                    && this_message.sender.id == last_message.sender.id;
+                let next_message =
+                    active_chat.message(ix.saturating_add(1).min(active_chat.message_count() - 1));
+
+                let is_continuation_from_previous = last_message.id != this_message.id
+                    && last_message.sender.id == this_message.sender.id;
+                let is_continuation_to_next = this_message.id != next_message.id
+                    && this_message.sender.id == next_message.sender.id;
 
                 if let ChannelMessageId::Saved(id) = this_message.id {
                     if this_message
@@ -381,28 +341,19 @@ impl ChatPanel {
 
                 (
                     this_message,
-                    is_continuation,
-                    active_chat.message_count() == ix + 1,
+                    is_continuation_from_previous,
+                    is_continuation_to_next,
                     is_admin,
                 )
             });
 
-        let is_pending = message.is_pending();
-        let theme = theme::current(cx);
+        let _is_pending = message.is_pending();
         let text = self.markdown_data.entry(message.id).or_insert_with(|| {
             Self::render_markdown_with_mentions(&self.languages, self.client.id(), &message)
         });
 
         let now = OffsetDateTime::now_utc();
 
-        let style = if is_pending {
-            &theme.chat_panel.pending_message
-        } else if is_continuation {
-            &theme.chat_panel.continuation_message
-        } else {
-            &theme.chat_panel.message
-        };
-
         let belongs_to_user = Some(message.sender.id) == self.client.user_id();
         let message_id_to_remove = if let (ChannelMessageId::Saved(id), true) =
             (message.id, belongs_to_user || is_admin)
@@ -412,89 +363,52 @@ impl ChatPanel {
             None
         };
 
-        enum MessageBackgroundHighlight {}
-        MouseEventHandler::new::<MessageBackgroundHighlight, _>(ix, cx, |state, cx| {
-            let container = style.style_for(state);
-            if is_continuation {
-                Flex::row()
-                    .with_child(
-                        text.element(
-                            theme.editor.syntax.clone(),
-                            theme.chat_panel.rich_text.clone(),
-                            cx,
+        let element_id: ElementId = match message.id {
+            ChannelMessageId::Saved(id) => ("saved-message", id).into(),
+            ChannelMessageId::Pending(id) => ("pending-message", id).into(),
+        };
+
+        v_stack()
+            .w_full()
+            .id(element_id)
+            .relative()
+            .overflow_hidden()
+            .group("")
+            .when(!is_continuation_from_previous, |this| {
+                this.child(
+                    h_stack()
+                        .gap_2()
+                        .child(Avatar::new(message.sender.avatar_uri.clone()))
+                        .child(Label::new(message.sender.github_login.clone()))
+                        .child(
+                            Label::new(format_timestamp(
+                                message.timestamp,
+                                now,
+                                self.local_timezone,
+                            ))
+                            .color(Color::Muted),
+                        ),
+                )
+            })
+            .when(!is_continuation_to_next, |this|
+                // HACK: This should really be a margin, but margins seem to get collapsed.
+                this.pb_2())
+            .child(text.element("body".into(), cx))
+            .child(
+                div()
+                    .absolute()
+                    .top_1()
+                    .right_2()
+                    .w_8()
+                    .visible_on_hover("")
+                    .children(message_id_to_remove.map(|message_id| {
+                        IconButton::new(("remove", message_id), Icon::XCircle).on_click(
+                            cx.listener(move |this, _, cx| {
+                                this.remove_message(message_id, cx);
+                            }),
                         )
-                        .flex(1., true),
-                    )
-                    .with_child(render_remove(message_id_to_remove, cx, &theme))
-                    .contained()
-                    .with_style(*container)
-                    .with_margin_bottom(if is_last {
-                        theme.chat_panel.last_message_bottom_spacing
-                    } else {
-                        0.
-                    })
-                    .into_any()
-            } else {
-                Flex::column()
-                    .with_child(
-                        Flex::row()
-                            .with_child(
-                                Flex::row()
-                                    .with_child(render_avatar(
-                                        message.sender.avatar.clone(),
-                                        &theme.chat_panel.avatar,
-                                        theme.chat_panel.avatar_container,
-                                    ))
-                                    .with_child(
-                                        Label::new(
-                                            message.sender.github_login.clone(),
-                                            theme.chat_panel.message_sender.text.clone(),
-                                        )
-                                        .contained()
-                                        .with_style(theme.chat_panel.message_sender.container),
-                                    )
-                                    .with_child(
-                                        Label::new(
-                                            format_timestamp(
-                                                message.timestamp,
-                                                now,
-                                                self.local_timezone,
-                                            ),
-                                            theme.chat_panel.message_timestamp.text.clone(),
-                                        )
-                                        .contained()
-                                        .with_style(theme.chat_panel.message_timestamp.container),
-                                    )
-                                    .align_children_center()
-                                    .flex(1., true),
-                            )
-                            .with_child(render_remove(message_id_to_remove, cx, &theme))
-                            .align_children_center(),
-                    )
-                    .with_child(
-                        Flex::row()
-                            .with_child(
-                                text.element(
-                                    theme.editor.syntax.clone(),
-                                    theme.chat_panel.rich_text.clone(),
-                                    cx,
-                                )
-                                .flex(1., true),
-                            )
-                            // Add a spacer to make everything line up
-                            .with_child(render_remove(None, cx, &theme)),
-                    )
-                    .contained()
-                    .with_style(*container)
-                    .with_margin_bottom(if is_last {
-                        theme.chat_panel.last_message_bottom_spacing
-                    } else {
-                        0.
-                    })
-                    .into_any()
-            }
-        })
-        .into_any()
+                    })),
+            )
     }
 
     fn render_markdown_with_mentions(
@@ -514,127 +428,26 @@ impl ChatPanel {
         rich_text::render_markdown(message.body.clone(), &mentions, language_registry, None)
     }
 
-    fn render_input_box(&self, theme: &Arc<Theme>, cx: &AppContext) -> AnyElement<Self> {
-        ChildView::new(&self.input_editor, cx)
-            .contained()
-            .with_style(theme.chat_panel.input_editor.container)
-            .into_any()
-    }
-
-    fn render_channel_name(
-        channel_store: &ModelHandle<ChannelStore>,
-        ix: usize,
-        item_type: ItemType,
-        is_hovered: bool,
-        workspace: WeakViewHandle<Workspace>,
-        cx: &mut ViewContext<Select>,
-    ) -> AnyElement<Select> {
-        let theme = theme::current(cx);
-        let tooltip_style = &theme.tooltip;
-        let theme = &theme.chat_panel;
-        let style = match (&item_type, is_hovered) {
-            (ItemType::Header, _) => &theme.channel_select.header,
-            (ItemType::Selected, _) => &theme.channel_select.active_item,
-            (ItemType::Unselected, false) => &theme.channel_select.item,
-            (ItemType::Unselected, true) => &theme.channel_select.hovered_item,
-        };
-
-        let channel = &channel_store.read(cx).channel_at(ix).unwrap();
-        let channel_id = channel.id;
-
-        let mut row = Flex::row()
-            .with_child(
-                Label::new("#".to_string(), style.hash.text.clone())
-                    .contained()
-                    .with_style(style.hash.container),
-            )
-            .with_child(Label::new(channel.name.clone(), style.name.clone()));
-
-        if matches!(item_type, ItemType::Header) {
-            row.add_children([
-                MouseEventHandler::new::<OpenChannelNotes, _>(0, cx, |mouse_state, _| {
-                    render_icon_button(theme.icon_button.style_for(mouse_state), "icons/file.svg")
-                })
-                .on_click(MouseButton::Left, move |_, _, cx| {
-                    if let Some(workspace) = workspace.upgrade(cx) {
-                        ChannelView::open(channel_id, workspace, cx).detach();
+    fn render_sign_in_prompt(&self, cx: &mut ViewContext<Self>) -> AnyElement {
+        Button::new("sign-in", "Sign in to use chat")
+            .on_click(cx.listener(move |this, _, cx| {
+                let client = this.client.clone();
+                cx.spawn(|this, mut cx| async move {
+                    if client
+                        .authenticate_and_connect(true, &cx)
+                        .log_err()
+                        .await
+                        .is_some()
+                    {
+                        this.update(&mut cx, |_, cx| {
+                            cx.focus_self();
+                        })
+                        .ok();
                     }
                 })
-                .with_tooltip::<OpenChannelNotes>(
-                    channel_id as usize,
-                    "Open Notes",
-                    Some(Box::new(OpenChannelNotes)),
-                    tooltip_style.clone(),
-                    cx,
-                )
-                .flex_float(),
-                MouseEventHandler::new::<ActiveCall, _>(0, cx, |mouse_state, _| {
-                    render_icon_button(
-                        theme.icon_button.style_for(mouse_state),
-                        "icons/speaker-loud.svg",
-                    )
-                })
-                .on_click(MouseButton::Left, move |_, _, cx| {
-                    ActiveCall::global(cx)
-                        .update(cx, |call, cx| call.join_channel(channel_id, cx))
-                        .detach_and_log_err(cx);
-                })
-                .with_tooltip::<ActiveCall>(
-                    channel_id as usize,
-                    "Join Call",
-                    Some(Box::new(JoinCall)),
-                    tooltip_style.clone(),
-                    cx,
-                )
-                .flex_float(),
-            ]);
-        }
-
-        row.align_children_center()
-            .contained()
-            .with_style(style.container)
-            .into_any()
-    }
-
-    fn render_sign_in_prompt(
-        &self,
-        theme: &Arc<Theme>,
-        cx: &mut ViewContext<Self>,
-    ) -> AnyElement<Self> {
-        enum SignInPromptLabel {}
-
-        MouseEventHandler::new::<SignInPromptLabel, _>(0, cx, |mouse_state, _| {
-            Label::new(
-                "Sign in to use chat".to_string(),
-                theme
-                    .chat_panel
-                    .sign_in_prompt
-                    .style_for(mouse_state)
-                    .clone(),
-            )
-        })
-        .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, move |_, this, cx| {
-            let client = this.client.clone();
-            cx.spawn(|this, mut cx| async move {
-                if client
-                    .authenticate_and_connect(true, &cx)
-                    .log_err()
-                    .await
-                    .is_some()
-                {
-                    this.update(&mut cx, |this, cx| {
-                        if cx.handle().is_focused(cx) {
-                            cx.focus(&this.input_editor);
-                        }
-                    })
-                    .ok();
-                }
-            })
-            .detach();
-        })
-        .aligned()
-        .into_any()
+                .detach();
+            }))
+            .into_any_element()
     }
 
     fn send(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
@@ -658,7 +471,7 @@ impl ChatPanel {
         }
     }
 
-    fn load_more_messages(&mut self, _: &LoadMoreMessages, cx: &mut ViewContext<Self>) {
+    fn load_more_messages(&mut self, cx: &mut ViewContext<Self>) {
         if let Some((chat, _)) = self.active_chat.as_ref() {
             chat.update(cx, |channel, cx| {
                 if let Some(task) = channel.load_more_messages(cx) {
@@ -695,14 +508,14 @@ impl ChatPanel {
 
             if let Some(message_id) = scroll_to_message_id {
                 if let Some(item_ix) =
-                    ChannelChat::load_history_since_message(chat.clone(), message_id, cx.clone())
+                    ChannelChat::load_history_since_message(chat.clone(), message_id, (*cx).clone())
                         .await
                 {
                     this.update(&mut cx, |this, cx| {
                         if this.active_chat.as_ref().map_or(false, |(c, _)| *c == chat) {
                             this.message_list.scroll_to(ListOffset {
                                 item_ix,
-                                offset_in_item: 0.,
+                                offset_in_item: px(0.0),
                             });
                             cx.notify();
                         }
@@ -714,16 +527,16 @@ impl ChatPanel {
         })
     }
 
-    fn open_notes(&mut self, _: &OpenChannelNotes, cx: &mut ViewContext<Self>) {
+    fn open_notes(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
         if let Some((chat, _)) = &self.active_chat {
             let channel_id = chat.read(cx).channel_id;
-            if let Some(workspace) = self.workspace.upgrade(cx) {
+            if let Some(workspace) = self.workspace.upgrade() {
                 ChannelView::open(channel_id, workspace, cx).detach();
             }
         }
     }
 
-    fn join_call(&mut self, _: &JoinCall, cx: &mut ViewContext<Self>) {
+    fn join_call(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
         if let Some((chat, _)) = &self.active_chat {
             let channel_id = chat.read(cx).channel_id;
             ActiveCall::global(cx)
@@ -733,89 +546,30 @@ impl ChatPanel {
     }
 }
 
-fn render_remove(
-    message_id_to_remove: Option<u64>,
-    cx: &mut ViewContext<'_, '_, ChatPanel>,
-    theme: &Arc<Theme>,
-) -> AnyElement<ChatPanel> {
-    enum DeleteMessage {}
-
-    message_id_to_remove
-        .map(|id| {
-            MouseEventHandler::new::<DeleteMessage, _>(id as usize, cx, |mouse_state, _| {
-                let button_style = theme.chat_panel.icon_button.style_for(mouse_state);
-                render_icon_button(button_style, "icons/x.svg")
-                    .aligned()
-                    .into_any()
-            })
-            .with_padding(Padding::uniform(2.))
-            .with_cursor_style(CursorStyle::PointingHand)
-            .on_click(MouseButton::Left, move |_, this, cx| {
-                this.remove_message(id, cx);
-            })
-            .flex_float()
-            .into_any()
-        })
-        .unwrap_or_else(|| {
-            let style = theme.chat_panel.icon_button.default;
-
-            Empty::new()
-                .constrained()
-                .with_width(style.icon_width)
-                .aligned()
-                .constrained()
-                .with_width(style.button_width)
-                .with_height(style.button_width)
-                .contained()
-                .with_uniform_padding(2.)
-                .flex_float()
-                .into_any()
-        })
-}
-
-impl Entity for ChatPanel {
-    type Event = Event;
-}
+impl EventEmitter<Event> for ChatPanel {}
 
-impl View for ChatPanel {
-    fn ui_name() -> &'static str {
-        "ChatPanel"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = theme::current(cx);
-        let element = if self.client.user_id().is_some() {
-            self.render_channel(cx)
-        } else {
-            self.render_sign_in_prompt(&theme, cx)
-        };
-        element
-            .contained()
-            .with_style(theme.chat_panel.container)
-            .constrained()
-            .with_min_width(150.)
-            .into_any()
-    }
-
-    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
-        self.has_focus = true;
-        if matches!(
-            *self.client.status().borrow(),
-            client::Status::Connected { .. }
-        ) {
-            let editor = self.input_editor.read(cx).editor.clone();
-            cx.focus(&editor);
-        }
+impl Render for ChatPanel {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        div()
+            .full()
+            .child(if self.client.user_id().is_some() {
+                self.render_channel(cx)
+            } else {
+                self.render_sign_in_prompt(cx)
+            })
+            .min_w(px(150.))
     }
+}
 
-    fn focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext<Self>) {
-        self.has_focus = false;
+impl FocusableView for ChatPanel {
+    fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
+        self.input_editor.read(cx).focus_handle(cx)
     }
 }
 
 impl Panel for ChatPanel {
     fn position(&self, cx: &gpui::WindowContext) -> DockPosition {
-        settings::get::<ChatPanelSettings>(cx).dock
+        ChatPanelSettings::get_global(cx).dock
     }
 
     fn position_is_valid(&self, position: DockPosition) -> bool {
@@ -828,12 +582,12 @@ impl Panel for ChatPanel {
         });
     }
 
-    fn size(&self, cx: &gpui::WindowContext) -> f32 {
+    fn size(&self, cx: &gpui::WindowContext) -> Pixels {
         self.width
-            .unwrap_or_else(|| settings::get::<ChatPanelSettings>(cx).default_width)
+            .unwrap_or_else(|| ChatPanelSettings::get_global(cx).default_width)
     }
 
-    fn set_size(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>) {
+    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
         self.width = size;
         self.serialize(cx);
         cx.notify();
@@ -849,32 +603,25 @@ impl Panel for ChatPanel {
         }
     }
 
-    fn icon_path(&self, cx: &gpui::WindowContext) -> Option<&'static str> {
-        (settings::get::<ChatPanelSettings>(cx).button && is_channels_feature_enabled(cx))
-            .then(|| "icons/conversations.svg")
-    }
-
-    fn icon_tooltip(&self) -> (String, Option<Box<dyn gpui::Action>>) {
-        ("Chat Panel".to_string(), Some(Box::new(ToggleFocus)))
-    }
-
-    fn should_change_position_on_event(event: &Self::Event) -> bool {
-        matches!(event, Event::DockPositionChanged)
+    fn persistent_name() -> &'static str {
+        "ChatPanel"
     }
 
-    fn should_close_on_event(event: &Self::Event) -> bool {
-        matches!(event, Event::Dismissed)
+    fn icon(&self, _cx: &WindowContext) -> Option<ui::Icon> {
+        Some(ui::Icon::MessageBubbles)
     }
 
-    fn has_focus(&self, _cx: &gpui::WindowContext) -> bool {
-        self.has_focus
+    fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
+        Some("Chat Panel")
     }
 
-    fn is_focus_event(event: &Self::Event) -> bool {
-        matches!(event, Event::Focus)
+    fn toggle_action(&self) -> Box<dyn gpui::Action> {
+        Box::new(ToggleFocus)
     }
 }
 
+impl EventEmitter<PanelEvent> for ChatPanel {}
+
 fn format_timestamp(
     mut timestamp: OffsetDateTime,
     mut now: OffsetDateTime,
@@ -900,25 +647,12 @@ fn format_timestamp(
     }
 }
 
-fn render_icon_button<V: View>(style: &IconButton, svg_path: &'static str) -> impl Element<V> {
-    Svg::new(svg_path)
-        .with_color(style.color)
-        .constrained()
-        .with_width(style.icon_width)
-        .aligned()
-        .constrained()
-        .with_width(style.button_width)
-        .with_height(style.button_width)
-        .contained()
-        .with_style(style.container)
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;
-    use gpui::fonts::HighlightStyle;
+    use gpui::HighlightStyle;
     use pretty_assertions::assert_eq;
-    use rich_text::{BackgroundKind, Highlight, RenderedRegion};
+    use rich_text::Highlight;
     use util::test::marked_text_ranges;
 
     #[gpui::test]
@@ -931,7 +665,7 @@ mod tests {
             timestamp: OffsetDateTime::now_utc(),
             sender: Arc::new(client::User {
                 github_login: "fgh".into(),
-                avatar: None,
+                avatar_uri: "avatar_fgh".into(),
                 id: 103,
             }),
             nonce: 5,
@@ -949,7 +683,7 @@ mod tests {
                 (
                     ranges[0].clone(),
                     HighlightStyle {
-                        italic: Some(true),
+                        font_style: Some(gpui::FontStyle::Italic),
                         ..Default::default()
                     }
                     .into()

crates/collab_ui/src/chat_panel/message_editor.rs 🔗

@@ -3,13 +3,14 @@ use client::UserId;
 use collections::HashMap;
 use editor::{AnchorRangeExt, Editor};
 use gpui::{
-    elements::ChildView, AnyElement, AsyncAppContext, Element, Entity, ModelHandle, Task, View,
-    ViewContext, ViewHandle, WeakViewHandle,
+    AsyncWindowContext, FocusableView, IntoElement, Model, Render, SharedString, Task, View,
+    ViewContext, WeakView,
 };
 use language::{language_settings::SoftWrap, Buffer, BufferSnapshot, LanguageRegistry};
 use lazy_static::lazy_static;
 use project::search::SearchQuery;
 use std::{sync::Arc, time::Duration};
+use workspace::item::ItemHandle;
 
 const MENTIONS_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(50);
 
@@ -19,8 +20,8 @@ lazy_static! {
 }
 
 pub struct MessageEditor {
-    pub editor: ViewHandle<Editor>,
-    channel_store: ModelHandle<ChannelStore>,
+    pub editor: View<Editor>,
+    channel_store: Model<ChannelStore>,
     users: HashMap<String, UserId>,
     mentions: Vec<UserId>,
     mentions_task: Option<Task<()>>,
@@ -30,8 +31,8 @@ pub struct MessageEditor {
 impl MessageEditor {
     pub fn new(
         language_registry: Arc<LanguageRegistry>,
-        channel_store: ModelHandle<ChannelStore>,
-        editor: ViewHandle<Editor>,
+        channel_store: Model<ChannelStore>,
+        editor: View<Editor>,
         cx: &mut ViewContext<Self>,
     ) -> Self {
         editor.update(cx, |editor, cx| {
@@ -48,15 +49,13 @@ impl MessageEditor {
         cx.subscribe(&buffer, Self::on_buffer_event).detach();
 
         let markdown = language_registry.language_for_name("Markdown");
-        cx.app_context()
-            .spawn(|mut cx| async move {
-                let markdown = markdown.await?;
-                buffer.update(&mut cx, |buffer, cx| {
-                    buffer.set_language(Some(markdown), cx)
-                });
-                anyhow::Ok(())
+        cx.spawn(|_, mut cx| async move {
+            let markdown = markdown.await?;
+            buffer.update(&mut cx, |buffer, cx| {
+                buffer.set_language(Some(markdown), cx)
             })
-            .detach_and_log_err(cx);
+        })
+        .detach_and_log_err(cx);
 
         Self {
             editor,
@@ -71,7 +70,7 @@ impl MessageEditor {
     pub fn set_channel(
         &mut self,
         channel_id: u64,
-        channel_name: Option<String>,
+        channel_name: Option<SharedString>,
         cx: &mut ViewContext<Self>,
     ) {
         self.editor.update(cx, |editor, cx| {
@@ -132,26 +131,28 @@ impl MessageEditor {
 
     fn on_buffer_event(
         &mut self,
-        buffer: ModelHandle<Buffer>,
+        buffer: Model<Buffer>,
         event: &language::Event,
         cx: &mut ViewContext<Self>,
     ) {
         if let language::Event::Reparsed | language::Event::Edited = event {
             let buffer = buffer.read(cx).snapshot();
             self.mentions_task = Some(cx.spawn(|this, cx| async move {
-                cx.background().timer(MENTIONS_DEBOUNCE_INTERVAL).await;
+                cx.background_executor()
+                    .timer(MENTIONS_DEBOUNCE_INTERVAL)
+                    .await;
                 Self::find_mentions(this, buffer, cx).await;
             }));
         }
     }
 
     async fn find_mentions(
-        this: WeakViewHandle<MessageEditor>,
+        this: WeakView<MessageEditor>,
         buffer: BufferSnapshot,
-        mut cx: AsyncAppContext,
+        mut cx: AsyncWindowContext,
     ) {
         let (buffer, ranges) = cx
-            .background()
+            .background_executor()
             .spawn(async move {
                 let ranges = MENTIONS_SEARCH.search(&buffer, None).await;
                 (buffer, ranges)
@@ -180,11 +181,7 @@ impl MessageEditor {
                 }
 
                 editor.clear_highlights::<Self>(cx);
-                editor.highlight_text::<Self>(
-                    anchor_ranges,
-                    theme::current(cx).chat_panel.rich_text.mention_highlight,
-                    cx,
-                )
+                editor.highlight_text::<Self>(anchor_ranges, gpui::red().into(), cx)
             });
 
             this.mentions = mentioned_user_ids;
@@ -192,21 +189,15 @@ impl MessageEditor {
         })
         .ok();
     }
-}
-
-impl Entity for MessageEditor {
-    type Event = ();
-}
 
-impl View for MessageEditor {
-    fn render(&mut self, cx: &mut ViewContext<'_, '_, Self>) -> AnyElement<Self> {
-        ChildView::new(&self.editor, cx).into_any()
+    pub(crate) fn focus_handle(&self, cx: &gpui::AppContext) -> gpui::FocusHandle {
+        self.editor.read(cx).focus_handle(cx)
     }
+}
 
-    fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
-        if cx.is_self_focused() {
-            cx.focus(&self.editor);
-        }
+impl Render for MessageEditor {
+    fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
+        self.editor.to_any()
     }
 }
 
@@ -214,7 +205,7 @@ impl View for MessageEditor {
 mod tests {
     use super::*;
     use client::{Client, User, UserStore};
-    use gpui::{TestAppContext, WindowHandle};
+    use gpui::{Context as _, TestAppContext, VisualContext as _};
     use language::{Language, LanguageConfig};
     use rpc::proto;
     use settings::SettingsStore;
@@ -222,8 +213,17 @@ mod tests {
 
     #[gpui::test]
     async fn test_message_editor(cx: &mut TestAppContext) {
-        let editor = init_test(cx);
-        let editor = editor.root(cx);
+        let language_registry = init_test(cx);
+
+        let (editor, cx) = cx.add_window_view(|cx| {
+            MessageEditor::new(
+                language_registry,
+                ChannelStore::global(cx),
+                cx.new_view(|cx| Editor::auto_height(4, cx)),
+                cx,
+            )
+        });
+        cx.executor().run_until_parked();
 
         editor.update(cx, |editor, cx| {
             editor.set_members(
@@ -232,7 +232,7 @@ mod tests {
                         user: Arc::new(User {
                             github_login: "a-b".into(),
                             id: 101,
-                            avatar: None,
+                            avatar_uri: "avatar_a-b".into(),
                         }),
                         kind: proto::channel_member::Kind::Member,
                         role: proto::ChannelRole::Member,
@@ -241,7 +241,7 @@ mod tests {
                         user: Arc::new(User {
                             github_login: "C_D".into(),
                             id: 102,
-                            avatar: None,
+                            avatar_uri: "avatar_C_D".into(),
                         }),
                         kind: proto::channel_member::Kind::Member,
                         role: proto::ChannelRole::Member,
@@ -255,7 +255,7 @@ mod tests {
             });
         });
 
-        cx.foreground().advance_clock(MENTIONS_DEBOUNCE_INTERVAL);
+        cx.executor().advance_clock(MENTIONS_DEBOUNCE_INTERVAL);
 
         editor.update(cx, |editor, cx| {
             let (text, ranges) = marked_text_ranges("Hello, «@a-b»! Have you met «@C_D»?", false);
@@ -269,15 +269,14 @@ mod tests {
         });
     }
 
-    fn init_test(cx: &mut TestAppContext) -> WindowHandle<MessageEditor> {
-        cx.foreground().forbid_parking();
-
+    fn init_test(cx: &mut TestAppContext) -> Arc<LanguageRegistry> {
         cx.update(|cx| {
             let http = FakeHttpClient::with_404_response();
             let client = Client::new(http.clone(), cx);
-            let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
-            cx.set_global(SettingsStore::test(cx));
-            theme::init((), cx);
+            let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
+            let settings = SettingsStore::test(cx);
+            cx.set_global(settings);
+            theme::init(theme::LoadThemes::JustBase, cx);
             language::init(cx);
             editor::init(cx);
             client::init(&client, cx);
@@ -292,16 +291,6 @@ mod tests {
             },
             Some(tree_sitter_markdown::language()),
         )));
-
-        let editor = cx.add_window(|cx| {
-            MessageEditor::new(
-                language_registry,
-                ChannelStore::global(cx),
-                cx.add_view(|cx| Editor::auto_height(4, None, cx)),
-                cx,
-            )
-        });
-        cx.foreground().run_until_parked();
-        editor
+        language_registry
     }
 }

crates/collab_ui/src/collab_panel.rs 🔗

@@ -1,123 +1,46 @@
 mod channel_modal;
 mod contact_finder;
 
+use self::channel_modal::ChannelModal;
 use crate::{
-    channel_view::{self, ChannelView},
-    chat_panel::ChatPanel,
-    face_pile::FacePile,
-    panel_settings, CollaborationPanelSettings,
+    channel_view::ChannelView, chat_panel::ChatPanel, face_pile::FacePile,
+    CollaborationPanelSettings,
 };
-use anyhow::Result;
 use call::ActiveCall;
 use channel::{Channel, ChannelEvent, ChannelId, ChannelStore};
-use channel_modal::ChannelModal;
-use client::{
-    proto::{self, PeerId},
-    Client, Contact, User, UserStore,
-};
+use client::{Client, Contact, User, UserStore};
 use contact_finder::ContactFinder;
-use context_menu::{ContextMenu, ContextMenuItem};
 use db::kvp::KEY_VALUE_STORE;
-use drag_and_drop::{DragAndDrop, Draggable};
-use editor::{Cancel, Editor};
+use editor::Editor;
 use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt};
-use futures::StreamExt;
 use fuzzy::{match_strings, StringMatchCandidate};
 use gpui::{
-    actions,
-    elements::{
-        Canvas, ChildView, Component, ContainerStyle, Empty, Flex, Image, Label, List, ListOffset,
-        ListState, MouseEventHandler, Orientation, OverlayPositionMode, Padding, ParentElement,
-        SafeStylable, Stack, Svg,
-    },
-    fonts::TextStyle,
-    geometry::{
-        rect::RectF,
-        vector::{vec2f, Vector2F},
-    },
-    impl_actions,
-    platform::{CursorStyle, MouseButton, PromptLevel},
-    serde_json, AnyElement, AppContext, AsyncAppContext, ClipboardItem, Element, Entity, FontCache,
-    ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
+    actions, canvas, div, fill, list, overlay, point, prelude::*, px, serde_json, AnyElement,
+    AppContext, AsyncWindowContext, Bounds, ClipboardItem, DismissEvent, Div, EventEmitter,
+    FocusHandle, FocusableView, InteractiveElement, IntoElement, ListOffset, ListState, Model,
+    MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, RenderOnce, SharedString,
+    Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView,
 };
-use menu::{Confirm, SelectNext, SelectPrev};
+use menu::{Cancel, Confirm, SelectNext, SelectPrev};
 use project::{Fs, Project};
+use rpc::proto::{self, PeerId};
 use serde_derive::{Deserialize, Serialize};
-use settings::SettingsStore;
-use std::{borrow::Cow, hash::Hash, mem, sync::Arc};
-use theme::{components::ComponentExt, IconButton, Interactive};
+use settings::{Settings, SettingsStore};
+use smallvec::SmallVec;
+use std::{mem, sync::Arc};
+use theme::{ActiveTheme, ThemeSettings};
+use ui::prelude::*;
+use ui::{
+    h_stack, v_stack, Avatar, Button, Color, ContextMenu, Icon, IconButton, IconElement, IconSize,
+    Label, ListHeader, ListItem, Tooltip,
+};
 use util::{maybe, ResultExt, TryFutureExt};
 use workspace::{
-    dock::{DockPosition, Panel},
-    item::ItemHandle,
-    FollowNextCollaborator, Workspace,
+    dock::{DockPosition, Panel, PanelEvent},
+    notifications::NotifyResultExt,
+    Workspace,
 };
 
-#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
-struct ToggleCollapse {
-    location: ChannelId,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
-struct NewChannel {
-    location: ChannelId,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
-struct RenameChannel {
-    channel_id: ChannelId,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
-struct ToggleSelectedIx {
-    ix: usize,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
-struct RemoveChannel {
-    channel_id: ChannelId,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
-struct InviteMembers {
-    channel_id: ChannelId,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
-struct ManageMembers {
-    channel_id: ChannelId,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
-pub struct OpenChannelNotes {
-    pub channel_id: ChannelId,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
-pub struct JoinChannelCall {
-    pub channel_id: u64,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
-pub struct JoinChannelChat {
-    pub channel_id: u64,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
-pub struct CopyChannelLink {
-    pub channel_id: u64,
-}
-
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
-struct StartMoveChannelFor {
-    channel_id: ChannelId,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
-struct MoveChannel {
-    to: ChannelId,
-}
-
 actions!(
     collab_panel,
     [
@@ -132,25 +55,6 @@ actions!(
     ]
 );
 
-impl_actions!(
-    collab_panel,
-    [
-        RemoveChannel,
-        NewChannel,
-        InviteMembers,
-        ManageMembers,
-        RenameChannel,
-        ToggleCollapse,
-        OpenChannelNotes,
-        JoinChannelCall,
-        JoinChannelChat,
-        CopyChannelLink,
-        StartMoveChannelFor,
-        MoveChannel,
-        ToggleSelectedIx
-    ]
-);
-
 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
 struct ChannelMoveClipboard {
     channel_id: ChannelId,
@@ -159,90 +63,12 @@ struct ChannelMoveClipboard {
 const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel";
 
 pub fn init(cx: &mut AppContext) {
-    settings::register::<panel_settings::CollaborationPanelSettings>(cx);
-    contact_finder::init(cx);
-    channel_modal::init(cx);
-    channel_view::init(cx);
-
-    cx.add_action(CollabPanel::cancel);
-    cx.add_action(CollabPanel::select_next);
-    cx.add_action(CollabPanel::select_prev);
-    cx.add_action(CollabPanel::confirm);
-    cx.add_action(CollabPanel::insert_space);
-    cx.add_action(CollabPanel::remove);
-    cx.add_action(CollabPanel::remove_selected_channel);
-    cx.add_action(CollabPanel::show_inline_context_menu);
-    cx.add_action(CollabPanel::new_subchannel);
-    cx.add_action(CollabPanel::invite_members);
-    cx.add_action(CollabPanel::manage_members);
-    cx.add_action(CollabPanel::rename_selected_channel);
-    cx.add_action(CollabPanel::rename_channel);
-    cx.add_action(CollabPanel::toggle_channel_collapsed_action);
-    cx.add_action(CollabPanel::collapse_selected_channel);
-    cx.add_action(CollabPanel::expand_selected_channel);
-    cx.add_action(CollabPanel::open_channel_notes);
-    cx.add_action(CollabPanel::join_channel_chat);
-    cx.add_action(CollabPanel::copy_channel_link);
-
-    cx.add_action(
-        |panel: &mut CollabPanel, action: &ToggleSelectedIx, cx: &mut ViewContext<CollabPanel>| {
-            if panel.selection.take() != Some(action.ix) {
-                panel.selection = Some(action.ix)
-            }
-
-            cx.notify();
-        },
-    );
-
-    cx.add_action(
-        |panel: &mut CollabPanel,
-         action: &StartMoveChannelFor,
-         _: &mut ViewContext<CollabPanel>| {
-            panel.channel_clipboard = Some(ChannelMoveClipboard {
-                channel_id: action.channel_id,
-            });
-        },
-    );
-
-    cx.add_action(
-        |panel: &mut CollabPanel, _: &StartMoveChannel, _: &mut ViewContext<CollabPanel>| {
-            if let Some(channel) = panel.selected_channel() {
-                panel.channel_clipboard = Some(ChannelMoveClipboard {
-                    channel_id: channel.id,
-                })
-            }
-        },
-    );
-
-    cx.add_action(
-        |panel: &mut CollabPanel, _: &MoveSelected, cx: &mut ViewContext<CollabPanel>| {
-            let Some(clipboard) = panel.channel_clipboard.take() else {
-                return;
-            };
-            let Some(selected_channel) = panel.selected_channel() else {
-                return;
-            };
-
-            panel
-                .channel_store
-                .update(cx, |channel_store, cx| {
-                    channel_store.move_channel(clipboard.channel_id, Some(selected_channel.id), cx)
-                })
-                .detach_and_log_err(cx)
-        },
-    );
-
-    cx.add_action(
-        |panel: &mut CollabPanel, action: &MoveChannel, cx: &mut ViewContext<CollabPanel>| {
-            if let Some(clipboard) = panel.channel_clipboard.take() {
-                panel.channel_store.update(cx, |channel_store, cx| {
-                    channel_store
-                        .move_channel(clipboard.channel_id, Some(action.to), cx)
-                        .detach_and_log_err(cx)
-                })
-            }
-        },
-    );
+    cx.observe_new_views(|workspace: &mut Workspace, _| {
+        workspace.register_action(|workspace, _: &ToggleFocus, cx| {
+            workspace.toggle_panel_focus::<CollabPanel>(cx);
+        });
+    })
+    .detach();
 }
 
 #[derive(Debug)]
@@ -258,58 +84,42 @@ pub enum ChannelEditingState {
 }
 
 impl ChannelEditingState {
-    fn pending_name(&self) -> Option<&str> {
+    fn pending_name(&self) -> Option<String> {
         match self {
-            ChannelEditingState::Create { pending_name, .. } => pending_name.as_deref(),
-            ChannelEditingState::Rename { pending_name, .. } => pending_name.as_deref(),
+            ChannelEditingState::Create { pending_name, .. } => pending_name.clone(),
+            ChannelEditingState::Rename { pending_name, .. } => pending_name.clone(),
         }
     }
 }
 
 pub struct CollabPanel {
-    width: Option<f32>,
+    width: Option<Pixels>,
     fs: Arc<dyn Fs>,
-    has_focus: bool,
+    focus_handle: FocusHandle,
     channel_clipboard: Option<ChannelMoveClipboard>,
     pending_serialization: Task<Option<()>>,
-    context_menu: ViewHandle<ContextMenu>,
-    filter_editor: ViewHandle<Editor>,
-    channel_name_editor: ViewHandle<Editor>,
+    context_menu: Option<(View<ContextMenu>, Point<Pixels>, Subscription)>,
+    list_state: ListState,
+    filter_editor: View<Editor>,
+    channel_name_editor: View<Editor>,
     channel_editing_state: Option<ChannelEditingState>,
     entries: Vec<ListEntry>,
     selection: Option<usize>,
-    user_store: ModelHandle<UserStore>,
+    channel_store: Model<ChannelStore>,
+    user_store: Model<UserStore>,
     client: Arc<Client>,
-    channel_store: ModelHandle<ChannelStore>,
-    project: ModelHandle<Project>,
+    project: Model<Project>,
     match_candidates: Vec<StringMatchCandidate>,
-    list_state: ListState<Self>,
     subscriptions: Vec<Subscription>,
     collapsed_sections: Vec<Section>,
     collapsed_channels: Vec<ChannelId>,
-    drag_target_channel: ChannelDragTarget,
-    workspace: WeakViewHandle<Workspace>,
-    context_menu_on_selected: bool,
-}
-
-#[derive(PartialEq, Eq)]
-enum ChannelDragTarget {
-    None,
-    Root,
-    Channel(ChannelId),
+    workspace: WeakView<Workspace>,
 }
 
 #[derive(Serialize, Deserialize)]
 struct SerializedCollabPanel {
-    width: Option<f32>,
-    collapsed_channels: Option<Vec<ChannelId>>,
-}
-
-#[derive(Debug)]
-pub enum Event {
-    DockPositionChanged,
-    Focus,
-    Dismissed,
+    width: Option<Pixels>,
+    collapsed_channels: Option<Vec<u64>>,
 }
 
 #[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)]
@@ -365,28 +175,17 @@ enum ListEntry {
     ContactPlaceholder,
 }
 
-impl Entity for CollabPanel {
-    type Event = Event;
-}
-
 impl CollabPanel {
-    pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> ViewHandle<Self> {
-        cx.add_view::<Self, _>(|cx| {
-            let view_id = cx.view_id();
-
-            let filter_editor = cx.add_view(|cx| {
-                let mut editor = Editor::single_line(
-                    Some(Arc::new(|theme| {
-                        theme.collab_panel.user_query_editor.clone()
-                    })),
-                    cx,
-                );
-                editor.set_placeholder_text("Filter channels, contacts", cx);
+    pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
+        cx.new_view(|cx| {
+            let filter_editor = cx.new_view(|cx| {
+                let mut editor = Editor::single_line(cx);
+                editor.set_placeholder_text("Filter...", cx);
                 editor
             });
 
-            cx.subscribe(&filter_editor, |this, _, event, cx| {
-                if let editor::Event::BufferEdited = event {
+            cx.subscribe(&filter_editor, |this: &mut Self, _, event, cx| {
+                if let editor::EditorEvent::BufferEdited = event {
                     let query = this.filter_editor.read(cx).text(cx);
                     if !query.is_empty() {
                         this.selection.take();
@@ -398,27 +197,14 @@ impl CollabPanel {
                             .iter()
                             .position(|entry| !matches!(entry, ListEntry::Header(_)));
                     }
-                } else if let editor::Event::Blurred = event {
-                    let query = this.filter_editor.read(cx).text(cx);
-                    if query.is_empty() {
-                        this.selection.take();
-                        this.update_entries(true, cx);
-                    }
                 }
             })
             .detach();
 
-            let channel_name_editor = cx.add_view(|cx| {
-                Editor::single_line(
-                    Some(Arc::new(|theme| {
-                        theme.collab_panel.user_query_editor.clone()
-                    })),
-                    cx,
-                )
-            });
+            let channel_name_editor = cx.new_view(|cx| Editor::single_line(cx));
 
-            cx.subscribe(&channel_name_editor, |this, _, event, cx| {
-                if let editor::Event::Blurred = event {
+            cx.subscribe(&channel_name_editor, |this: &mut Self, _, event, cx| {
+                if let editor::EditorEvent::Blurred = event {
                     if let Some(state) = &this.channel_editing_state {
                         if state.pending_name().is_some() {
                             return;
@@ -431,151 +217,31 @@ impl CollabPanel {
             })
             .detach();
 
+            let view = cx.view().downgrade();
             let list_state =
-                ListState::<Self>::new(0, Orientation::Top, 1000., move |this, ix, cx| {
-                    let theme = theme::current(cx).clone();
-                    let is_selected = this.selection == Some(ix);
-                    let current_project_id = this.project.read(cx).remote_id();
-
-                    match &this.entries[ix] {
-                        ListEntry::Header(section) => {
-                            let is_collapsed = this.collapsed_sections.contains(section);
-                            this.render_header(*section, &theme, is_selected, is_collapsed, cx)
-                        }
-                        ListEntry::CallParticipant {
-                            user,
-                            peer_id,
-                            is_pending,
-                        } => Self::render_call_participant(
-                            user,
-                            *peer_id,
-                            this.user_store.clone(),
-                            *is_pending,
-                            is_selected,
-                            &theme,
-                            cx,
-                        ),
-                        ListEntry::ParticipantProject {
-                            project_id,
-                            worktree_root_names,
-                            host_user_id,
-                            is_last,
-                        } => Self::render_participant_project(
-                            *project_id,
-                            worktree_root_names,
-                            *host_user_id,
-                            Some(*project_id) == current_project_id,
-                            *is_last,
-                            is_selected,
-                            &theme,
-                            cx,
-                        ),
-                        ListEntry::ParticipantScreen { peer_id, is_last } => {
-                            Self::render_participant_screen(
-                                *peer_id,
-                                *is_last,
-                                is_selected,
-                                &theme.collab_panel,
-                                cx,
-                            )
-                        }
-                        ListEntry::Channel {
-                            channel,
-                            depth,
-                            has_children,
-                        } => {
-                            let channel_row = this.render_channel(
-                                &*channel,
-                                *depth,
-                                &theme,
-                                is_selected,
-                                *has_children,
-                                ix,
-                                cx,
-                            );
-
-                            if is_selected && this.context_menu_on_selected {
-                                Stack::new()
-                                    .with_child(channel_row)
-                                    .with_child(
-                                        ChildView::new(&this.context_menu, cx)
-                                            .aligned()
-                                            .bottom()
-                                            .right(),
-                                    )
-                                    .into_any()
-                            } else {
-                                return channel_row;
-                            }
-                        }
-                        ListEntry::ChannelNotes { channel_id } => this.render_channel_notes(
-                            *channel_id,
-                            &theme.collab_panel,
-                            is_selected,
-                            ix,
-                            cx,
-                        ),
-                        ListEntry::ChannelChat { channel_id } => this.render_channel_chat(
-                            *channel_id,
-                            &theme.collab_panel,
-                            is_selected,
-                            ix,
-                            cx,
-                        ),
-                        ListEntry::ChannelInvite(channel) => Self::render_channel_invite(
-                            channel.clone(),
-                            this.channel_store.clone(),
-                            &theme.collab_panel,
-                            is_selected,
-                            cx,
-                        ),
-                        ListEntry::IncomingRequest(user) => Self::render_contact_request(
-                            user.clone(),
-                            this.user_store.clone(),
-                            &theme.collab_panel,
-                            true,
-                            is_selected,
-                            cx,
-                        ),
-                        ListEntry::OutgoingRequest(user) => Self::render_contact_request(
-                            user.clone(),
-                            this.user_store.clone(),
-                            &theme.collab_panel,
-                            false,
-                            is_selected,
-                            cx,
-                        ),
-                        ListEntry::Contact { contact, calling } => Self::render_contact(
-                            contact,
-                            *calling,
-                            &this.project,
-                            &theme,
-                            is_selected,
-                            cx,
-                        ),
-                        ListEntry::ChannelEditor { depth } => {
-                            this.render_channel_editor(&theme, *depth, cx)
-                        }
-                        ListEntry::ContactPlaceholder => {
-                            this.render_contact_placeholder(&theme.collab_panel, is_selected, cx)
-                        }
+                ListState::new(0, gpui::ListAlignment::Top, px(1000.), move |ix, cx| {
+                    if let Some(view) = view.upgrade() {
+                        view.update(cx, |view, cx| view.render_list_entry(ix, cx))
+                    } else {
+                        div().into_any()
                     }
                 });
 
             let mut this = Self {
                 width: None,
-                has_focus: false,
+                focus_handle: cx.focus_handle(),
                 channel_clipboard: None,
                 fs: workspace.app_state().fs.clone(),
                 pending_serialization: Task::ready(None),
-                context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)),
+                context_menu: None,
+                list_state,
                 channel_name_editor,
                 filter_editor,
                 entries: Vec::default(),
                 channel_editing_state: None,
                 selection: None,
-                user_store: workspace.user_store().clone(),
                 channel_store: ChannelStore::global(cx),
+                user_store: workspace.user_store().clone(),
                 project: workspace.project().clone(),
                 subscriptions: Vec::default(),
                 match_candidates: Vec::default(),
@@ -583,26 +249,22 @@ impl CollabPanel {
                 collapsed_channels: Vec::default(),
                 workspace: workspace.weak_handle(),
                 client: workspace.app_state().client.clone(),
-                context_menu_on_selected: true,
-                drag_target_channel: ChannelDragTarget::None,
-                list_state,
             };
 
             this.update_entries(false, cx);
 
             // Update the dock position when the setting changes.
             let mut old_dock_position = this.position(cx);
-            this.subscriptions
-                .push(
-                    cx.observe_global::<SettingsStore, _>(move |this: &mut Self, cx| {
-                        let new_dock_position = this.position(cx);
-                        if new_dock_position != old_dock_position {
-                            old_dock_position = new_dock_position;
-                            cx.emit(Event::DockPositionChanged);
-                        }
-                        cx.notify();
-                    }),
-                );
+            this.subscriptions.push(cx.observe_global::<SettingsStore>(
+                move |this: &mut Self, cx| {
+                    let new_dock_position = this.position(cx);
+                    if new_dock_position != old_dock_position {
+                        old_dock_position = new_dock_position;
+                        cx.emit(PanelEvent::ChangePosition);
+                    }
+                    cx.notify();
+                },
+            ));
 
             let active_call = ActiveCall::global(cx);
             this.subscriptions
@@ -642,49 +304,41 @@ impl CollabPanel {
         })
     }
 
-    pub fn load(
-        workspace: WeakViewHandle<Workspace>,
-        cx: AsyncAppContext,
-    ) -> Task<Result<ViewHandle<Self>>> {
-        cx.spawn(|mut cx| async move {
-            let serialized_panel = if let Some(panel) = cx
-                .background()
-                .spawn(async move { KEY_VALUE_STORE.read_kvp(COLLABORATION_PANEL_KEY) })
-                .await
-                .log_err()
-                .flatten()
-            {
-                match serde_json::from_str::<SerializedCollabPanel>(&panel) {
-                    Ok(panel) => Some(panel),
-                    Err(err) => {
-                        log::error!("Failed to deserialize collaboration panel: {}", err);
-                        None
-                    }
-                }
-            } else {
-                None
-            };
-
-            workspace.update(&mut cx, |workspace, cx| {
-                let panel = CollabPanel::new(workspace, cx);
-                if let Some(serialized_panel) = serialized_panel {
-                    panel.update(cx, |panel, cx| {
-                        panel.width = serialized_panel.width;
-                        panel.collapsed_channels = serialized_panel
-                            .collapsed_channels
-                            .unwrap_or_else(|| Vec::new());
-                        cx.notify();
-                    });
-                }
-                panel
-            })
+    pub async fn load(
+        workspace: WeakView<Workspace>,
+        mut cx: AsyncWindowContext,
+    ) -> anyhow::Result<View<Self>> {
+        let serialized_panel = cx
+            .background_executor()
+            .spawn(async move { KEY_VALUE_STORE.read_kvp(COLLABORATION_PANEL_KEY) })
+            .await
+            .map_err(|_| anyhow::anyhow!("Failed to read collaboration panel from key value store"))
+            .log_err()
+            .flatten()
+            .map(|panel| serde_json::from_str::<SerializedCollabPanel>(&panel))
+            .transpose()
+            .log_err()
+            .flatten();
+
+        workspace.update(&mut cx, |workspace, cx| {
+            let panel = CollabPanel::new(workspace, cx);
+            if let Some(serialized_panel) = serialized_panel {
+                panel.update(cx, |panel, cx| {
+                    panel.width = serialized_panel.width;
+                    panel.collapsed_channels = serialized_panel
+                        .collapsed_channels
+                        .unwrap_or_else(|| Vec::new());
+                    cx.notify();
+                });
+            }
+            panel
         })
     }
 
     fn serialize(&mut self, cx: &mut ViewContext<Self>) {
         let width = self.width;
         let collapsed_channels = self.collapsed_channels.clone();
-        self.pending_serialization = cx.background().spawn(
+        self.pending_serialization = cx.background_executor().spawn(
             async move {
                 KEY_VALUE_STORE
                     .write_kvp(
@@ -701,11 +355,15 @@ impl CollabPanel {
         );
     }
 
+    fn scroll_to_item(&mut self, ix: usize) {
+        self.list_state.scroll_to_reveal_item(ix)
+    }
+
     fn update_entries(&mut self, select_same_item: bool, cx: &mut ViewContext<Self>) {
         let channel_store = self.channel_store.read(cx);
         let user_store = self.user_store.read(cx);
         let query = self.filter_editor.read(cx).text(cx);
-        let executor = cx.background().clone();
+        let executor = cx.background_executor().clone();
 
         let prev_selected_entry = self.selection.and_then(|ix| self.entries.get(ix).cloned());
         let old_entries = mem::take(&mut self.entries);
@@ -851,7 +509,7 @@ impl CollabPanel {
                     .extend(channel_store.ordered_channels().enumerate().map(
                         |(ix, (_, channel))| StringMatchCandidate {
                             id: ix,
-                            string: channel.name.clone(),
+                            string: channel.name.clone().into(),
                             char_bag: channel.name.chars().collect(),
                         },
                     ));
@@ -929,7 +587,7 @@ impl CollabPanel {
                     .extend(channel_invites.iter().enumerate().map(|(ix, channel)| {
                         StringMatchCandidate {
                             id: ix,
-                            string: channel.name.clone(),
+                            string: channel.name.clone().into(),
                             char_bag: channel.name.chars().collect(),
                         }
                     }));
@@ -1097,7 +755,6 @@ impl CollabPanel {
         }
 
         let old_scroll_top = self.list_state.logical_scroll_top();
-
         self.list_state.reset(self.entries.len());
 
         if scroll_to_top {
@@ -1121,7 +778,7 @@ impl CollabPanel {
                             .position(|entry| entry == entry_after_old_top)?;
                         Some(ListOffset {
                             item_ix,
-                            offset_in_item: 0.,
+                            offset_in_item: Pixels::ZERO,
                         })
                     })
                     .or_else(|| {
@@ -1133,7 +790,7 @@ impl CollabPanel {
                             .position(|entry| entry == entry_before_old_top)?;
                         Some(ListOffset {
                             item_ix,
-                            offset_in_item: 0.,
+                            offset_in_item: Pixels::ZERO,
                         })
                     });
 
@@ -1146,288 +803,107 @@ impl CollabPanel {
     }
 
     fn render_call_participant(
-        user: &User,
+        &self,
+        user: &Arc<User>,
         peer_id: Option<PeerId>,
-        user_store: ModelHandle<UserStore>,
         is_pending: bool,
         is_selected: bool,
-        theme: &theme::Theme,
         cx: &mut ViewContext<Self>,
-    ) -> AnyElement<Self> {
-        enum CallParticipant {}
-        enum CallParticipantTooltip {}
-        enum LeaveCallButton {}
-        enum LeaveCallTooltip {}
-
-        let collab_theme = &theme.collab_panel;
-
+    ) -> ListItem {
         let is_current_user =
-            user_store.read(cx).current_user().map(|user| user.id) == Some(user.id);
-
-        let content = MouseEventHandler::new::<CallParticipant, _>(
-            user.id as usize,
-            cx,
-            |mouse_state, cx| {
-                let style = if is_current_user {
-                    *collab_theme
-                        .contact_row
-                        .in_state(is_selected)
-                        .style_for(&mut Default::default())
-                } else {
-                    *collab_theme
-                        .contact_row
-                        .in_state(is_selected)
-                        .style_for(mouse_state)
-                };
-
-                Flex::row()
-                    .with_children(user.avatar.clone().map(|avatar| {
-                        Image::from_data(avatar)
-                            .with_style(collab_theme.contact_avatar)
-                            .aligned()
-                            .left()
-                    }))
-                    .with_child(
-                        Label::new(
-                            user.github_login.clone(),
-                            collab_theme.contact_username.text.clone(),
-                        )
-                        .contained()
-                        .with_style(collab_theme.contact_username.container)
-                        .aligned()
-                        .left()
-                        .flex(1., true),
-                    )
-                    .with_children(if is_pending {
-                        Some(
-                            Label::new("Calling", collab_theme.calling_indicator.text.clone())
-                                .contained()
-                                .with_style(collab_theme.calling_indicator.container)
-                                .aligned()
-                                .into_any(),
-                        )
-                    } else if is_current_user {
-                        Some(
-                            MouseEventHandler::new::<LeaveCallButton, _>(0, cx, |state, _| {
-                                render_icon_button(
-                                    theme
-                                        .collab_panel
-                                        .leave_call_button
-                                        .style_for(is_selected, state),
-                                    "icons/exit.svg",
-                                )
-                            })
-                            .with_cursor_style(CursorStyle::PointingHand)
-                            .on_click(MouseButton::Left, |_, _, cx| {
-                                Self::leave_call(cx);
-                            })
-                            .with_tooltip::<LeaveCallTooltip>(
-                                0,
-                                "Leave call",
-                                None,
-                                theme.tooltip.clone(),
-                                cx,
-                            )
-                            .into_any(),
-                        )
-                    } else {
-                        None
-                    })
-                    .constrained()
-                    .with_height(collab_theme.row_height)
-                    .contained()
-                    .with_style(style)
-            },
-        );
-
-        if is_current_user || is_pending || peer_id.is_none() {
-            return content.into_any();
-        }
-
+            self.user_store.read(cx).current_user().map(|user| user.id) == Some(user.id);
         let tooltip = format!("Follow {}", user.github_login);
 
-        content
-            .on_click(MouseButton::Left, move |_, this, cx| {
-                if let Some(workspace) = this.workspace.upgrade(cx) {
-                    workspace
-                        .update(cx, |workspace, cx| workspace.follow(peer_id.unwrap(), cx))
-                        .map(|task| task.detach_and_log_err(cx));
-                }
+        ListItem::new(SharedString::from(user.github_login.clone()))
+            .start_slot(Avatar::new(user.avatar_uri.clone()))
+            .child(Label::new(user.github_login.clone()))
+            .selected(is_selected)
+            .end_slot(if is_pending {
+                Label::new("Calling").color(Color::Muted).into_any_element()
+            } else if is_current_user {
+                IconButton::new("leave-call", Icon::Exit)
+                    .style(ButtonStyle::Subtle)
+                    .on_click(move |_, cx| Self::leave_call(cx))
+                    .tooltip(|cx| Tooltip::text("Leave Call", cx))
+                    .into_any_element()
+            } else {
+                div().into_any_element()
+            })
+            .when_some(peer_id, |this, peer_id| {
+                this.tooltip(move |cx| Tooltip::text(tooltip.clone(), cx))
+                    .on_click(cx.listener(move |this, _, cx| {
+                        this.workspace
+                            .update(cx, |workspace, cx| workspace.follow(peer_id, cx))
+                            .ok();
+                    }))
             })
-            .with_cursor_style(CursorStyle::PointingHand)
-            .with_tooltip::<CallParticipantTooltip>(
-                user.id as usize,
-                tooltip,
-                Some(Box::new(FollowNextCollaborator)),
-                theme.tooltip.clone(),
-                cx,
-            )
-            .into_any()
     }
 
     fn render_participant_project(
+        &self,
         project_id: u64,
         worktree_root_names: &[String],
         host_user_id: u64,
-        is_current: bool,
         is_last: bool,
         is_selected: bool,
-        theme: &theme::Theme,
         cx: &mut ViewContext<Self>,
-    ) -> AnyElement<Self> {
-        enum JoinProject {}
-        enum JoinProjectTooltip {}
-
-        let collab_theme = &theme.collab_panel;
-        let host_avatar_width = collab_theme
-            .contact_avatar
-            .width
-            .or(collab_theme.contact_avatar.height)
-            .unwrap_or(0.);
-        let tree_branch = collab_theme.tree_branch;
-        let project_name = if worktree_root_names.is_empty() {
+    ) -> impl IntoElement {
+        let project_name: SharedString = if worktree_root_names.is_empty() {
             "untitled".to_string()
         } else {
             worktree_root_names.join(", ")
-        };
-
-        let content =
-            MouseEventHandler::new::<JoinProject, _>(project_id as usize, cx, |mouse_state, cx| {
-                let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
-                let row = if is_current {
-                    collab_theme
-                        .project_row
-                        .in_state(true)
-                        .style_for(&mut Default::default())
-                } else {
-                    collab_theme
-                        .project_row
-                        .in_state(is_selected)
-                        .style_for(mouse_state)
-                };
-
-                Flex::row()
-                    .with_child(render_tree_branch(
-                        tree_branch,
-                        &row.name.text,
-                        is_last,
-                        vec2f(host_avatar_width, collab_theme.row_height),
-                        cx.font_cache(),
-                    ))
-                    .with_child(
-                        Svg::new("icons/file_icons/folder.svg")
-                            .with_color(collab_theme.channel_hash.color)
-                            .constrained()
-                            .with_width(collab_theme.channel_hash.width)
-                            .aligned()
-                            .left(),
-                    )
-                    .with_child(
-                        Label::new(project_name.clone(), row.name.text.clone())
-                            .aligned()
-                            .left()
-                            .contained()
-                            .with_style(row.name.container)
-                            .flex(1., false),
-                    )
-                    .constrained()
-                    .with_height(collab_theme.row_height)
-                    .contained()
-                    .with_style(row.container)
-            });
-
-        if is_current {
-            return content.into_any();
         }
-
-        content
-            .with_cursor_style(CursorStyle::PointingHand)
-            .on_click(MouseButton::Left, move |_, this, cx| {
-                if let Some(workspace) = this.workspace.upgrade(cx) {
-                    let app_state = workspace.read(cx).app_state().clone();
-                    workspace::join_remote_project(project_id, host_user_id, app_state, cx)
-                        .detach_and_log_err(cx);
-                }
-            })
-            .with_tooltip::<JoinProjectTooltip>(
-                project_id as usize,
-                format!("Open {}", project_name),
-                None,
-                theme.tooltip.clone(),
-                cx,
+        .into();
+
+        ListItem::new(project_id as usize)
+            .selected(is_selected)
+            .on_click(cx.listener(move |this, _, cx| {
+                this.workspace
+                    .update(cx, |workspace, cx| {
+                        let app_state = workspace.app_state().clone();
+                        workspace::join_remote_project(project_id, host_user_id, app_state, cx)
+                            .detach_and_log_err(cx);
+                    })
+                    .ok();
+            }))
+            .start_slot(
+                h_stack()
+                    .gap_1()
+                    .child(render_tree_branch(is_last, cx))
+                    .child(IconButton::new(0, Icon::Folder)),
             )
-            .into_any()
+            .child(Label::new(project_name.clone()))
+            .tooltip(move |cx| Tooltip::text(format!("Open {}", project_name), cx))
     }
 
     fn render_participant_screen(
+        &self,
         peer_id: Option<PeerId>,
         is_last: bool,
         is_selected: bool,
-        theme: &theme::CollabPanel,
         cx: &mut ViewContext<Self>,
-    ) -> AnyElement<Self> {
-        enum OpenSharedScreen {}
-
-        let host_avatar_width = theme
-            .contact_avatar
-            .width
-            .or(theme.contact_avatar.height)
-            .unwrap_or(0.);
-        let tree_branch = theme.tree_branch;
-
-        let handler = MouseEventHandler::new::<OpenSharedScreen, _>(
-            peer_id.map(|id| id.as_u64()).unwrap_or(0) as usize,
-            cx,
-            |mouse_state, cx| {
-                let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
-                let row = theme
-                    .project_row
-                    .in_state(is_selected)
-                    .style_for(mouse_state);
-
-                Flex::row()
-                    .with_child(render_tree_branch(
-                        tree_branch,
-                        &row.name.text,
-                        is_last,
-                        vec2f(host_avatar_width, theme.row_height),
-                        cx.font_cache(),
-                    ))
-                    .with_child(
-                        Svg::new("icons/desktop.svg")
-                            .with_color(theme.channel_hash.color)
-                            .constrained()
-                            .with_width(theme.channel_hash.width)
-                            .aligned()
-                            .left(),
-                    )
-                    .with_child(
-                        Label::new("Screen", row.name.text.clone())
-                            .aligned()
-                            .left()
-                            .contained()
-                            .with_style(row.name.container)
-                            .flex(1., false),
-                    )
-                    .constrained()
-                    .with_height(theme.row_height)
-                    .contained()
-                    .with_style(row.container)
-            },
-        );
-        if peer_id.is_none() {
-            return handler.into_any();
-        }
-        handler
-            .with_cursor_style(CursorStyle::PointingHand)
-            .on_click(MouseButton::Left, move |_, this, cx| {
-                if let Some(workspace) = this.workspace.upgrade(cx) {
-                    workspace.update(cx, |workspace, cx| {
-                        workspace.open_shared_screen(peer_id.unwrap(), cx)
-                    });
-                }
+    ) -> impl IntoElement {
+        let id = peer_id.map_or(usize::MAX, |id| id.as_u64() as usize);
+
+        ListItem::new(("screen", id))
+            .selected(is_selected)
+            .start_slot(
+                h_stack()
+                    .gap_1()
+                    .child(render_tree_branch(is_last, cx))
+                    .child(IconButton::new(0, Icon::Screen)),
+            )
+            .child(Label::new("Screen"))
+            .when_some(peer_id, |this, _| {
+                this.on_click(cx.listener(move |this, _, cx| {
+                    this.workspace
+                        .update(cx, |workspace, cx| {
+                            workspace.open_shared_screen(peer_id.unwrap(), cx)
+                        })
+                        .ok();
+                }))
+                .tooltip(move |cx| Tooltip::text(format!("Open shared screen"), cx))
             })
-            .into_any()
     }
 
     fn take_editing_state(&mut self, cx: &mut ViewContext<Self>) -> bool {

crates/collab_ui/src/collab_panel/channel_modal.rs 🔗

@@ -3,19 +3,17 @@ use client::{
     proto::{self, ChannelRole, ChannelVisibility},
     User, UserId, UserStore,
 };
-use context_menu::{ContextMenu, ContextMenuItem};
 use fuzzy::{match_strings, StringMatchCandidate};
 use gpui::{
-    actions,
-    elements::*,
-    platform::{CursorStyle, MouseButton},
-    AppContext, ClipboardItem, Entity, ModelHandle, MouseState, Task, View, ViewContext,
-    ViewHandle,
+    actions, div, overlay, AppContext, ClipboardItem, DismissEvent, EventEmitter, FocusableView,
+    Model, ParentElement, Render, Styled, Subscription, Task, View, ViewContext, VisualContext,
+    WeakView,
 };
-use picker::{Picker, PickerDelegate, PickerEvent};
+use picker::{Picker, PickerDelegate};
 use std::sync::Arc;
+use ui::{prelude::*, Avatar, Checkbox, ContextMenu, ListItem, ListItemSpacing};
 use util::TryFutureExt;
-use workspace::Modal;
+use workspace::ModalView;
 
 actions!(
     channel_modal,
@@ -27,34 +25,27 @@ actions!(
     ]
 );
 
-pub fn init(cx: &mut AppContext) {
-    Picker::<ChannelModalDelegate>::init(cx);
-    cx.add_action(ChannelModal::toggle_mode);
-    cx.add_action(ChannelModal::toggle_member_admin);
-    cx.add_action(ChannelModal::remove_member);
-    cx.add_action(ChannelModal::dismiss);
-}
-
 pub struct ChannelModal {
-    picker: ViewHandle<Picker<ChannelModalDelegate>>,
-    channel_store: ModelHandle<ChannelStore>,
+    picker: View<Picker<ChannelModalDelegate>>,
+    channel_store: Model<ChannelStore>,
     channel_id: ChannelId,
-    has_focus: bool,
 }
 
 impl ChannelModal {
     pub fn new(
-        user_store: ModelHandle<UserStore>,
-        channel_store: ModelHandle<ChannelStore>,
+        user_store: Model<UserStore>,
+        channel_store: Model<ChannelStore>,
         channel_id: ChannelId,
         mode: Mode,
         members: Vec<ChannelMembership>,
         cx: &mut ViewContext<Self>,
     ) -> Self {
         cx.observe(&channel_store, |_, _, cx| cx.notify()).detach();
-        let picker = cx.add_view(|cx| {
+        let channel_modal = cx.view().downgrade();
+        let picker = cx.new_view(|cx| {
             Picker::new(
                 ChannelModalDelegate {
+                    channel_modal,
                     matching_users: Vec::new(),
                     matching_member_indices: Vec::new(),
                     selected_index: 0,
@@ -62,33 +53,24 @@ impl ChannelModal {
                     channel_store: channel_store.clone(),
                     channel_id,
                     match_candidates: Vec::new(),
+                    context_menu: None,
                     members,
                     mode,
-                    context_menu: cx.add_view(|cx| {
-                        let mut menu = ContextMenu::new(cx.view_id(), cx);
-                        menu.set_position_mode(OverlayPositionMode::Local);
-                        menu
-                    }),
                 },
                 cx,
             )
-            .with_theme(|theme| theme.collab_panel.tabbed_modal.picker.clone())
+            .modal(false)
         });
 
-        cx.subscribe(&picker, |_, _, e, cx| cx.emit(*e)).detach();
-
-        let has_focus = picker.read(cx).has_focus();
-
         Self {
             picker,
             channel_store,
             channel_id,
-            has_focus,
         }
     }
 
     fn toggle_mode(&mut self, _: &ToggleMode, cx: &mut ViewContext<Self>) {
-        let mode = match self.picker.read(cx).delegate().mode {
+        let mode = match self.picker.read(cx).delegate.mode {
             Mode::ManageMembers => Mode::InviteMembers,
             Mode::InviteMembers => Mode::ManageMembers,
         };
@@ -103,20 +85,20 @@ impl ChannelModal {
                 let mut members = channel_store
                     .update(&mut cx, |channel_store, cx| {
                         channel_store.get_channel_member_details(channel_id, cx)
-                    })
+                    })?
                     .await?;
 
                 members.sort_by(|a, b| a.sort_key().cmp(&b.sort_key()));
 
                 this.update(&mut cx, |this, cx| {
                     this.picker
-                        .update(cx, |picker, _| picker.delegate_mut().members = members);
+                        .update(cx, |picker, _| picker.delegate.members = members);
                 })?;
             }
 
             this.update(&mut cx, |this, cx| {
                 this.picker.update(cx, |picker, cx| {
-                    let delegate = picker.delegate_mut();
+                    let delegate = &mut picker.delegate;
                     delegate.mode = mode;
                     delegate.selected_index = 0;
                     picker.set_query("", cx);
@@ -129,204 +111,118 @@ impl ChannelModal {
         .detach();
     }
 
-    fn toggle_member_admin(&mut self, _: &ToggleMemberAdmin, cx: &mut ViewContext<Self>) {
-        self.picker.update(cx, |picker, cx| {
-            picker.delegate_mut().toggle_selected_member_admin(cx);
-        })
-    }
-
-    fn remove_member(&mut self, _: &RemoveMember, cx: &mut ViewContext<Self>) {
-        self.picker.update(cx, |picker, cx| {
-            picker.delegate_mut().remove_selected_member(cx);
+    fn set_channel_visiblity(&mut self, selection: &Selection, cx: &mut ViewContext<Self>) {
+        self.channel_store.update(cx, |channel_store, cx| {
+            channel_store
+                .set_channel_visibility(
+                    self.channel_id,
+                    match selection {
+                        Selection::Unselected => ChannelVisibility::Members,
+                        Selection::Selected => ChannelVisibility::Public,
+                        Selection::Indeterminate => return,
+                    },
+                    cx,
+                )
+                .detach_and_log_err(cx)
         });
     }
 
     fn dismiss(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
-        cx.emit(PickerEvent::Dismiss);
+        cx.emit(DismissEvent);
     }
 }
 
-impl Entity for ChannelModal {
-    type Event = PickerEvent;
-}
+impl EventEmitter<DismissEvent> for ChannelModal {}
+impl ModalView for ChannelModal {}
 
-impl View for ChannelModal {
-    fn ui_name() -> &'static str {
-        "ChannelModal"
+impl FocusableView for ChannelModal {
+    fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
+        self.picker.focus_handle(cx)
     }
+}
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = &theme::current(cx).collab_panel.tabbed_modal;
-
-        let mode = self.picker.read(cx).delegate().mode;
-        let Some(channel) = self.channel_store.read(cx).channel_for_id(self.channel_id) else {
-            return Empty::new().into_any();
+impl Render for ChannelModal {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        let channel_store = self.channel_store.read(cx);
+        let Some(channel) = channel_store.channel_for_id(self.channel_id) else {
+            return div();
         };
-
-        enum InviteMembers {}
-        enum ManageMembers {}
-
-        fn render_mode_button<T: 'static>(
-            mode: Mode,
-            text: &'static str,
-            current_mode: Mode,
-            theme: &theme::TabbedModal,
-            cx: &mut ViewContext<ChannelModal>,
-        ) -> AnyElement<ChannelModal> {
-            let active = mode == current_mode;
-            MouseEventHandler::new::<T, _>(0, cx, move |state, _| {
-                let contained_text = theme.tab_button.style_for(active, state);
-                Label::new(text, contained_text.text.clone())
-                    .contained()
-                    .with_style(contained_text.container.clone())
-            })
-            .on_click(MouseButton::Left, move |_, this, cx| {
-                if !active {
-                    this.set_mode(mode, cx);
-                }
-            })
-            .with_cursor_style(CursorStyle::PointingHand)
-            .into_any()
-        }
-
-        fn render_visibility(
-            channel_id: ChannelId,
-            visibility: ChannelVisibility,
-            theme: &theme::TabbedModal,
-            cx: &mut ViewContext<ChannelModal>,
-        ) -> AnyElement<ChannelModal> {
-            enum TogglePublic {}
-
-            if visibility == ChannelVisibility::Members {
-                return Flex::row()
-                    .with_child(
-                        MouseEventHandler::new::<TogglePublic, _>(0, cx, move |state, _| {
-                            let style = theme.visibility_toggle.style_for(state);
-                            Label::new(format!("{}", "Public access: OFF"), style.text.clone())
-                                .contained()
-                                .with_style(style.container.clone())
-                        })
-                        .on_click(MouseButton::Left, move |_, this, cx| {
-                            this.channel_store
-                                .update(cx, |channel_store, cx| {
-                                    channel_store.set_channel_visibility(
-                                        channel_id,
-                                        ChannelVisibility::Public,
-                                        cx,
+        let channel_name = channel.name.clone();
+        let channel_id = channel.id;
+        let visibility = channel.visibility;
+        let mode = self.picker.read(cx).delegate.mode;
+
+        v_stack()
+            .key_context("ChannelModal")
+            .on_action(cx.listener(Self::toggle_mode))
+            .on_action(cx.listener(Self::dismiss))
+            .elevation_3(cx)
+            .w(rems(34.))
+            .child(
+                v_stack()
+                    .px_2()
+                    .py_1()
+                    .rounded_t(px(8.))
+                    .bg(cx.theme().colors().element_background)
+                    .child(IconElement::new(Icon::Hash).size(IconSize::Medium))
+                    .child(Label::new(channel_name))
+                    .child(
+                        h_stack()
+                            .w_full()
+                            .justify_between()
+                            .child(
+                                h_stack()
+                                    .gap_2()
+                                    .child(
+                                        Checkbox::new(
+                                            "is-public",
+                                            if visibility == ChannelVisibility::Public {
+                                                ui::Selection::Selected
+                                            } else {
+                                                ui::Selection::Unselected
+                                            },
+                                        )
+                                        .on_click(cx.listener(Self::set_channel_visiblity)),
                                     )
-                                })
-                                .detach_and_log_err(cx);
-                        })
-                        .with_cursor_style(CursorStyle::PointingHand),
-                    )
-                    .into_any();
-            }
-
-            Flex::row()
-                .with_child(
-                    MouseEventHandler::new::<TogglePublic, _>(0, cx, move |state, _| {
-                        let style = theme.visibility_toggle.style_for(state);
-                        Label::new(format!("{}", "Public access: ON"), style.text.clone())
-                            .contained()
-                            .with_style(style.container.clone())
-                    })
-                    .on_click(MouseButton::Left, move |_, this, cx| {
-                        this.channel_store
-                            .update(cx, |channel_store, cx| {
-                                channel_store.set_channel_visibility(
-                                    channel_id,
-                                    ChannelVisibility::Members,
-                                    cx,
-                                )
-                            })
-                            .detach_and_log_err(cx);
-                    })
-                    .with_cursor_style(CursorStyle::PointingHand),
-                )
-                .with_spacing(14.0)
-                .with_child(
-                    MouseEventHandler::new::<TogglePublic, _>(1, cx, move |state, _| {
-                        let style = theme.channel_link.style_for(state);
-                        Label::new(format!("{}", "copy link"), style.text.clone())
-                            .contained()
-                            .with_style(style.container.clone())
-                    })
-                    .on_click(MouseButton::Left, move |_, this, cx| {
-                        if let Some(channel) =
-                            this.channel_store.read(cx).channel_for_id(channel_id)
-                        {
-                            let item = ClipboardItem::new(channel.link());
-                            cx.write_to_clipboard(item);
-                        }
-                    })
-                    .with_cursor_style(CursorStyle::PointingHand),
-                )
-                .into_any()
-        }
-
-        Flex::column()
-            .with_child(
-                Flex::column()
-                    .with_child(
-                        Label::new(format!("#{}", channel.name), theme.title.text.clone())
-                            .contained()
-                            .with_style(theme.title.container.clone()),
+                                    .child(Label::new("Public")),
+                            )
+                            .children(if visibility == ChannelVisibility::Public {
+                                Some(Button::new("copy-link", "Copy Link").on_click(cx.listener(
+                                    move |this, _, cx| {
+                                        if let Some(channel) =
+                                            this.channel_store.read(cx).channel_for_id(channel_id)
+                                        {
+                                            let item = ClipboardItem::new(channel.link());
+                                            cx.write_to_clipboard(item);
+                                        }
+                                    },
+                                )))
+                            } else {
+                                None
+                            }),
                     )
-                    .with_child(render_visibility(channel.id, channel.visibility, theme, cx))
-                    .with_child(Flex::row().with_children([
-                        render_mode_button::<InviteMembers>(
-                            Mode::InviteMembers,
-                            "Invite members",
-                            mode,
-                            theme,
-                            cx,
-                        ),
-                        render_mode_button::<ManageMembers>(
-                            Mode::ManageMembers,
-                            "Manage members",
-                            mode,
-                            theme,
-                            cx,
-                        ),
-                    ]))
-                    .expanded()
-                    .contained()
-                    .with_style(theme.header),
-            )
-            .with_child(
-                ChildView::new(&self.picker, cx)
-                    .contained()
-                    .with_style(theme.body),
+                    .child(
+                        div()
+                            .w_full()
+                            .flex()
+                            .flex_row()
+                            .child(
+                                Button::new("manage-members", "Manage Members")
+                                    .selected(mode == Mode::ManageMembers)
+                                    .on_click(cx.listener(|this, _, cx| {
+                                        this.set_mode(Mode::ManageMembers, cx);
+                                    })),
+                            )
+                            .child(
+                                Button::new("invite-members", "Invite Members")
+                                    .selected(mode == Mode::InviteMembers)
+                                    .on_click(cx.listener(|this, _, cx| {
+                                        this.set_mode(Mode::InviteMembers, cx);
+                                    })),
+                            ),
+                    ),
             )
-            .constrained()
-            .with_max_height(theme.max_height)
-            .with_max_width(theme.max_width)
-            .contained()
-            .with_style(theme.modal)
-            .into_any()
-    }
-
-    fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
-        self.has_focus = true;
-        if cx.is_self_focused() {
-            cx.focus(&self.picker)
-        }
-    }
-
-    fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
-        self.has_focus = false;
-    }
-}
-
-impl Modal for ChannelModal {
-    fn has_focus(&self) -> bool {
-        self.has_focus
-    }
-
-    fn dismiss_on_event(event: &Self::Event) -> bool {
-        match event {
-            PickerEvent::Dismiss => true,
-        }
+            .child(self.picker.clone())
     }
 }
 
@@ -337,19 +233,22 @@ pub enum Mode {
 }
 
 pub struct ChannelModalDelegate {
+    channel_modal: WeakView<ChannelModal>,
     matching_users: Vec<Arc<User>>,
     matching_member_indices: Vec<usize>,
-    user_store: ModelHandle<UserStore>,
-    channel_store: ModelHandle<ChannelStore>,
+    user_store: Model<UserStore>,
+    channel_store: Model<ChannelStore>,
     channel_id: ChannelId,
     selected_index: usize,
     mode: Mode,
     match_candidates: Vec<StringMatchCandidate>,
     members: Vec<ChannelMembership>,
-    context_menu: ViewHandle<ContextMenu>,
+    context_menu: Option<(View<ContextMenu>, Subscription)>,
 }
 
 impl PickerDelegate for ChannelModalDelegate {
+    type ListItem = ListItem;
+
     fn placeholder_text(&self) -> Arc<str> {
         "Search collaborator by username...".into()
     }
@@ -382,19 +281,19 @@ impl PickerDelegate for ChannelModalDelegate {
                         }
                     }));
 
-                let matches = cx.background().block(match_strings(
+                let matches = cx.background_executor().block(match_strings(
                     &self.match_candidates,
                     &query,
                     true,
                     usize::MAX,
                     &Default::default(),
-                    cx.background().clone(),
+                    cx.background_executor().clone(),
                 ));
 
                 cx.spawn(|picker, mut cx| async move {
                     picker
                         .update(&mut cx, |picker, cx| {
-                            let delegate = picker.delegate_mut();
+                            let delegate = &mut picker.delegate;
                             delegate.matching_member_indices.clear();
                             delegate
                                 .matching_member_indices
@@ -412,8 +311,7 @@ impl PickerDelegate for ChannelModalDelegate {
                     async {
                         let users = search_users.await?;
                         picker.update(&mut cx, |picker, cx| {
-                            let delegate = picker.delegate_mut();
-                            delegate.matching_users = users;
+                            picker.delegate.matching_users = users;
                             cx.notify();
                         })?;
                         anyhow::Ok(())
@@ -429,11 +327,11 @@ impl PickerDelegate for ChannelModalDelegate {
         if let Some((selected_user, role)) = self.user_at_index(self.selected_index) {
             match self.mode {
                 Mode::ManageMembers => {
-                    self.show_context_menu(role.unwrap_or(ChannelRole::Member), cx)
+                    self.show_context_menu(selected_user, role.unwrap_or(ChannelRole::Member), cx)
                 }
                 Mode::InviteMembers => match self.member_status(selected_user.id, cx) {
                     Some(proto::channel_member::Kind::Invitee) => {
-                        self.remove_selected_member(cx);
+                        self.remove_member(selected_user.id, cx);
                     }
                     Some(proto::channel_member::Kind::AncestorMember) | None => {
                         self.invite_member(selected_user, cx)
@@ -445,138 +343,70 @@ impl PickerDelegate for ChannelModalDelegate {
     }
 
     fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
-        cx.emit(PickerEvent::Dismiss);
+        if self.context_menu.is_none() {
+            self.channel_modal
+                .update(cx, |_, cx| {
+                    cx.emit(DismissEvent);
+                })
+                .ok();
+        }
     }
 
     fn render_match(
         &self,
         ix: usize,
-        mouse_state: &mut MouseState,
         selected: bool,
-        cx: &gpui::AppContext,
-    ) -> AnyElement<Picker<Self>> {
-        let full_theme = &theme::current(cx);
-        let theme = &full_theme.collab_panel.channel_modal;
-        let tabbed_modal = &full_theme.collab_panel.tabbed_modal;
-        let (user, role) = self.user_at_index(ix).unwrap();
+        cx: &mut ViewContext<Picker<Self>>,
+    ) -> Option<Self::ListItem> {
+        let (user, role) = self.user_at_index(ix)?;
         let request_status = self.member_status(user.id, cx);
 
-        let style = tabbed_modal
-            .picker
-            .item
-            .in_state(selected)
-            .style_for(mouse_state);
-
-        let in_manage = matches!(self.mode, Mode::ManageMembers);
-
-        let mut result = Flex::row()
-            .with_children(user.avatar.clone().map(|avatar| {
-                Image::from_data(avatar)
-                    .with_style(theme.contact_avatar)
-                    .aligned()
-                    .left()
-            }))
-            .with_child(
-                Label::new(user.github_login.clone(), style.label.clone())
-                    .contained()
-                    .with_style(theme.contact_username)
-                    .aligned()
-                    .left(),
-            )
-            .with_children({
-                (in_manage && request_status == Some(proto::channel_member::Kind::Invitee)).then(
-                    || {
-                        Label::new("Invited", theme.member_tag.text.clone())
-                            .contained()
-                            .with_style(theme.member_tag.container)
-                            .aligned()
-                            .left()
-                    },
-                )
-            })
-            .with_children(if in_manage && role == Some(ChannelRole::Admin) {
-                Some(
-                    Label::new("Admin", theme.member_tag.text.clone())
-                        .contained()
-                        .with_style(theme.member_tag.container)
-                        .aligned()
-                        .left(),
-                )
-            } else if in_manage && role == Some(ChannelRole::Guest) {
-                Some(
-                    Label::new("Guest", theme.member_tag.text.clone())
-                        .contained()
-                        .with_style(theme.member_tag.container)
-                        .aligned()
-                        .left(),
-                )
-            } else {
-                None
-            })
-            .with_children({
-                let svg = match self.mode {
-                    Mode::ManageMembers => Some(
-                        Svg::new("icons/ellipsis.svg")
-                            .with_color(theme.member_icon.color)
-                            .constrained()
-                            .with_width(theme.member_icon.icon_width)
-                            .aligned()
-                            .constrained()
-                            .with_width(theme.member_icon.button_width)
-                            .with_height(theme.member_icon.button_width)
-                            .contained()
-                            .with_style(theme.member_icon.container),
-                    ),
-                    Mode::InviteMembers => match request_status {
-                        Some(proto::channel_member::Kind::Member) => Some(
-                            Svg::new("icons/check.svg")
-                                .with_color(theme.member_icon.color)
-                                .constrained()
-                                .with_width(theme.member_icon.icon_width)
-                                .aligned()
-                                .constrained()
-                                .with_width(theme.member_icon.button_width)
-                                .with_height(theme.member_icon.button_width)
-                                .contained()
-                                .with_style(theme.member_icon.container),
-                        ),
-                        Some(proto::channel_member::Kind::Invitee) => Some(
-                            Svg::new("icons/check.svg")
-                                .with_color(theme.invitee_icon.color)
-                                .constrained()
-                                .with_width(theme.invitee_icon.icon_width)
-                                .aligned()
-                                .constrained()
-                                .with_width(theme.invitee_icon.button_width)
-                                .with_height(theme.invitee_icon.button_width)
-                                .contained()
-                                .with_style(theme.invitee_icon.container),
-                        ),
-                        Some(proto::channel_member::Kind::AncestorMember) | None => None,
-                    },
-                };
-
-                svg.map(|svg| svg.aligned().flex_float().into_any())
-            })
-            .contained()
-            .with_style(style.container)
-            .constrained()
-            .with_height(tabbed_modal.row_height)
-            .into_any();
-
-        if selected {
-            result = Stack::new()
-                .with_child(result)
-                .with_child(
-                    ChildView::new(&self.context_menu, cx)
-                        .aligned()
-                        .top()
-                        .right(),
-                )
-                .into_any();
-        }
-
-        result
+        Some(
+            ListItem::new(ix)
+                .inset(true)
+                .spacing(ListItemSpacing::Sparse)
+                .selected(selected)
+                .start_slot(Avatar::new(user.avatar_uri.clone()))
+                .child(Label::new(user.github_login.clone()))
+                .end_slot(h_stack().gap_2().map(|slot| {
+                    match self.mode {
+                        Mode::ManageMembers => slot
+                            .children(
+                                if request_status == Some(proto::channel_member::Kind::Invitee) {
+                                    Some(Label::new("Invited"))
+                                } else {
+                                    None
+                                },
+                            )
+                            .children(match role {
+                                Some(ChannelRole::Admin) => Some(Label::new("Admin")),
+                                Some(ChannelRole::Guest) => Some(Label::new("Guest")),
+                                _ => None,
+                            })
+                            .child(IconButton::new("ellipsis", Icon::Ellipsis))
+                            .children(
+                                if let (Some((menu, _)), true) = (&self.context_menu, selected) {
+                                    Some(
+                                        overlay()
+                                            .anchor(gpui::AnchorCorner::TopLeft)
+                                            .child(menu.clone()),
+                                    )
+                                } else {
+                                    None
+                                },
+                            ),
+                        Mode::InviteMembers => match request_status {
+                            Some(proto::channel_member::Kind::Invitee) => {
+                                slot.children(Some(Label::new("Invited")))
+                            }
+                            Some(proto::channel_member::Kind::Member) => {
+                                slot.children(Some(Label::new("Member")))
+                            }
+                            _ => slot,
+                        },
+                    }
+                })),
+        )
     }
 }
 
@@ -610,21 +440,20 @@ impl ChannelModalDelegate {
         }
     }
 
-    fn toggle_selected_member_admin(&mut self, cx: &mut ViewContext<Picker<Self>>) -> Option<()> {
-        let (user, role) = self.user_at_index(self.selected_index)?;
-        let new_role = if role == Some(ChannelRole::Admin) {
-            ChannelRole::Member
-        } else {
-            ChannelRole::Admin
-        };
+    fn set_user_role(
+        &mut self,
+        user_id: UserId,
+        new_role: ChannelRole,
+        cx: &mut ViewContext<Picker<Self>>,
+    ) -> Option<()> {
         let update = self.channel_store.update(cx, |store, cx| {
-            store.set_member_role(self.channel_id, user.id, new_role, cx)
+            store.set_member_role(self.channel_id, user_id, new_role, cx)
         });
         cx.spawn(|picker, mut cx| async move {
             update.await?;
             picker.update(&mut cx, |picker, cx| {
-                let this = picker.delegate_mut();
-                if let Some(member) = this.members.iter_mut().find(|m| m.user.id == user.id) {
+                let this = &mut picker.delegate;
+                if let Some(member) = this.members.iter_mut().find(|m| m.user.id == user_id) {
                     member.role = new_role;
                 }
                 cx.focus_self();
@@ -635,16 +464,14 @@ impl ChannelModalDelegate {
         Some(())
     }
 
-    fn remove_selected_member(&mut self, cx: &mut ViewContext<Picker<Self>>) -> Option<()> {
-        let (user, _) = self.user_at_index(self.selected_index)?;
-        let user_id = user.id;
+    fn remove_member(&mut self, user_id: UserId, cx: &mut ViewContext<Picker<Self>>) -> Option<()> {
         let update = self.channel_store.update(cx, |store, cx| {
             store.remove_member(self.channel_id, user_id, cx)
         });
         cx.spawn(|picker, mut cx| async move {
             update.await?;
             picker.update(&mut cx, |picker, cx| {
-                let this = picker.delegate_mut();
+                let this = &mut picker.delegate;
                 if let Some(ix) = this.members.iter_mut().position(|m| m.user.id == user_id) {
                     this.members.remove(ix);
                     this.matching_member_indices.retain_mut(|member_ix| {
@@ -661,7 +488,7 @@ impl ChannelModalDelegate {
                     .selected_index
                     .min(this.matching_member_indices.len().saturating_sub(1));
 
-                cx.focus_self();
+                picker.focus(cx);
                 cx.notify();
             })
         })
@@ -683,7 +510,7 @@ impl ChannelModalDelegate {
                     kind: proto::channel_member::Kind::Invitee,
                     role: ChannelRole::Member,
                 };
-                let members = &mut this.delegate_mut().members;
+                let members = &mut this.delegate.members;
                 match members.binary_search_by_key(&new_member.sort_key(), |k| k.sort_key()) {
                     Ok(ix) | Err(ix) => members.insert(ix, new_member),
                 }
@@ -694,24 +521,55 @@ impl ChannelModalDelegate {
         .detach_and_log_err(cx);
     }
 
-    fn show_context_menu(&mut self, role: ChannelRole, cx: &mut ViewContext<Picker<Self>>) {
-        self.context_menu.update(cx, |context_menu, cx| {
-            context_menu.show(
-                Default::default(),
-                AnchorCorner::TopRight,
-                vec![
-                    ContextMenuItem::action("Remove", RemoveMember),
-                    ContextMenuItem::action(
-                        if role == ChannelRole::Admin {
-                            "Make non-admin"
-                        } else {
-                            "Make admin"
-                        },
-                        ToggleMemberAdmin,
-                    ),
-                ],
-                cx,
-            )
-        })
+    fn show_context_menu(
+        &mut self,
+        user: Arc<User>,
+        role: ChannelRole,
+        cx: &mut ViewContext<Picker<Self>>,
+    ) {
+        let user_id = user.id;
+        let picker = cx.view().clone();
+        let context_menu = ContextMenu::build(cx, |mut menu, _cx| {
+            menu = menu.entry("Remove Member", None, {
+                let picker = picker.clone();
+                move |cx| {
+                    picker.update(cx, |picker, cx| {
+                        picker.delegate.remove_member(user_id, cx);
+                    })
+                }
+            });
+
+            let picker = picker.clone();
+            match role {
+                ChannelRole::Admin => {
+                    menu = menu.entry("Revoke Admin", None, move |cx| {
+                        picker.update(cx, |picker, cx| {
+                            picker
+                                .delegate
+                                .set_user_role(user_id, ChannelRole::Member, cx);
+                        })
+                    });
+                }
+                ChannelRole::Member => {
+                    menu = menu.entry("Make Admin", None, move |cx| {
+                        picker.update(cx, |picker, cx| {
+                            picker
+                                .delegate
+                                .set_user_role(user_id, ChannelRole::Admin, cx);
+                        })
+                    });
+                }
+                _ => {}
+            };
+
+            menu
+        });
+        cx.focus_view(&context_menu);
+        let subscription = cx.subscribe(&context_menu, |picker, _, _: &DismissEvent, cx| {
+            picker.delegate.context_menu = None;
+            picker.focus(cx);
+            cx.notify();
+        });
+        self.context_menu = Some((context_menu, subscription));
     }
 }

crates/collab_ui/src/collab_panel/contact_finder.rs 🔗

@@ -1,42 +1,30 @@
 use client::{ContactRequestStatus, User, UserStore};
 use gpui::{
-    elements::*, AppContext, Entity, ModelHandle, MouseState, Task, View, ViewContext, ViewHandle,
+    AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model, ParentElement as _,
+    Render, Styled, Task, View, ViewContext, VisualContext, WeakView,
 };
-use picker::{Picker, PickerDelegate, PickerEvent};
+use picker::{Picker, PickerDelegate};
 use std::sync::Arc;
-use util::TryFutureExt;
-use workspace::Modal;
-
-pub fn init(cx: &mut AppContext) {
-    Picker::<ContactFinderDelegate>::init(cx);
-    cx.add_action(ContactFinder::dismiss)
-}
+use theme::ActiveTheme as _;
+use ui::{prelude::*, Avatar, ListItem, ListItemSpacing};
+use util::{ResultExt as _, TryFutureExt};
+use workspace::ModalView;
 
 pub struct ContactFinder {
-    picker: ViewHandle<Picker<ContactFinderDelegate>>,
-    has_focus: bool,
+    picker: View<Picker<ContactFinderDelegate>>,
 }
 
 impl ContactFinder {
-    pub fn new(user_store: ModelHandle<UserStore>, cx: &mut ViewContext<Self>) -> Self {
-        let picker = cx.add_view(|cx| {
-            Picker::new(
-                ContactFinderDelegate {
-                    user_store,
-                    potential_contacts: Arc::from([]),
-                    selected_index: 0,
-                },
-                cx,
-            )
-            .with_theme(|theme| theme.collab_panel.tabbed_modal.picker.clone())
-        });
-
-        cx.subscribe(&picker, |_, _, e, cx| cx.emit(*e)).detach();
+    pub fn new(user_store: Model<UserStore>, cx: &mut ViewContext<Self>) -> Self {
+        let delegate = ContactFinderDelegate {
+            parent: cx.view().downgrade(),
+            user_store,
+            potential_contacts: Arc::from([]),
+            selected_index: 0,
+        };
+        let picker = cx.new_view(|cx| Picker::new(delegate, cx).modal(false));
 
-        Self {
-            picker,
-            has_focus: false,
-        }
+        Self { picker }
     }
 
     pub fn set_query(&mut self, query: String, cx: &mut ViewContext<Self>) {
@@ -44,101 +32,45 @@ impl ContactFinder {
             picker.set_query(query, cx);
         });
     }
-
-    fn dismiss(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
-        cx.emit(PickerEvent::Dismiss);
-    }
-}
-
-impl Entity for ContactFinder {
-    type Event = PickerEvent;
 }
 
-impl View for ContactFinder {
-    fn ui_name() -> &'static str {
-        "ContactFinder"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let full_theme = &theme::current(cx);
-        let theme = &full_theme.collab_panel.tabbed_modal;
-
-        fn render_mode_button(
-            text: &'static str,
-            theme: &theme::TabbedModal,
-            _cx: &mut ViewContext<ContactFinder>,
-        ) -> AnyElement<ContactFinder> {
-            let contained_text = &theme.tab_button.active_state().default;
-            Label::new(text, contained_text.text.clone())
-                .contained()
-                .with_style(contained_text.container.clone())
-                .into_any()
-        }
-
-        Flex::column()
-            .with_child(
-                Flex::column()
-                    .with_child(
-                        Label::new("Contacts", theme.title.text.clone())
-                            .contained()
-                            .with_style(theme.title.container.clone()),
-                    )
-                    .with_child(Flex::row().with_children([render_mode_button(
-                        "Invite new contacts",
-                        &theme,
-                        cx,
-                    )]))
-                    .expanded()
-                    .contained()
-                    .with_style(theme.header),
+impl Render for ContactFinder {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        v_stack()
+            .elevation_3(cx)
+            .child(
+                v_stack()
+                    .px_2()
+                    .py_1()
+                    .bg(cx.theme().colors().element_background)
+                    // HACK: Prevent the background color from overflowing the parent container.
+                    .rounded_t(px(8.))
+                    .child(Label::new("Contacts"))
+                    .child(h_stack().child(Label::new("Invite new contacts"))),
             )
-            .with_child(
-                ChildView::new(&self.picker, cx)
-                    .contained()
-                    .with_style(theme.body),
-            )
-            .constrained()
-            .with_max_height(theme.max_height)
-            .with_max_width(theme.max_width)
-            .contained()
-            .with_style(theme.modal)
-            .into_any()
-    }
-
-    fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
-        self.has_focus = true;
-        if cx.is_self_focused() {
-            cx.focus(&self.picker)
-        }
-    }
-
-    fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
-        self.has_focus = false;
-    }
-}
-
-impl Modal for ContactFinder {
-    fn has_focus(&self) -> bool {
-        self.has_focus
-    }
-
-    fn dismiss_on_event(event: &Self::Event) -> bool {
-        match event {
-            PickerEvent::Dismiss => true,
-        }
+            .child(self.picker.clone())
+            .w(rems(34.))
     }
 }
 
 pub struct ContactFinderDelegate {
+    parent: WeakView<ContactFinder>,
     potential_contacts: Arc<[Arc<User>]>,
-    user_store: ModelHandle<UserStore>,
+    user_store: Model<UserStore>,
     selected_index: usize,
 }
 
-impl PickerDelegate for ContactFinderDelegate {
-    fn placeholder_text(&self) -> Arc<str> {
-        "Search collaborator by username...".into()
+impl EventEmitter<DismissEvent> for ContactFinder {}
+impl ModalView for ContactFinder {}
+
+impl FocusableView for ContactFinder {
+    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
+        self.picker.focus_handle(cx)
     }
+}
+
+impl PickerDelegate for ContactFinderDelegate {
+    type ListItem = ListItem;
 
     fn match_count(&self) -> usize {
         self.potential_contacts.len()
@@ -152,6 +84,10 @@ impl PickerDelegate for ContactFinderDelegate {
         self.selected_index = ix;
     }
 
+    fn placeholder_text(&self) -> Arc<str> {
+        "Search collaborator by username...".into()
+    }
+
     fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
         let search_users = self
             .user_store
@@ -161,7 +97,7 @@ impl PickerDelegate for ContactFinderDelegate {
             async {
                 let potential_contacts = search_users.await?;
                 picker.update(&mut cx, |picker, cx| {
-                    picker.delegate_mut().potential_contacts = potential_contacts.into();
+                    picker.delegate.potential_contacts = potential_contacts.into();
                     cx.notify();
                 })?;
                 anyhow::Ok(())
@@ -191,19 +127,17 @@ impl PickerDelegate for ContactFinderDelegate {
     }
 
     fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
-        cx.emit(PickerEvent::Dismiss);
+        self.parent
+            .update(cx, |_, cx| cx.emit(DismissEvent))
+            .log_err();
     }
 
     fn render_match(
         &self,
         ix: usize,
-        mouse_state: &mut MouseState,
         selected: bool,
-        cx: &gpui::AppContext,
-    ) -> AnyElement<Picker<Self>> {
-        let full_theme = &theme::current(cx);
-        let theme = &full_theme.collab_panel.contact_finder;
-        let tabbed_modal = &full_theme.collab_panel.tabbed_modal;
+        cx: &mut ViewContext<Picker<Self>>,
+    ) -> Option<Self::ListItem> {
         let user = &self.potential_contacts[ix];
         let request_status = self.user_store.read(cx).contact_request_status(user);
 
@@ -214,48 +148,16 @@ impl PickerDelegate for ContactFinderDelegate {
             ContactRequestStatus::RequestSent => Some("icons/x.svg"),
             ContactRequestStatus::RequestAccepted => None,
         };
-        let button_style = if self.user_store.read(cx).is_contact_request_pending(user) {
-            &theme.disabled_contact_button
-        } else {
-            &theme.contact_button
-        };
-        let style = tabbed_modal
-            .picker
-            .item
-            .in_state(selected)
-            .style_for(mouse_state);
-        Flex::row()
-            .with_children(user.avatar.clone().map(|avatar| {
-                Image::from_data(avatar)
-                    .with_style(theme.contact_avatar)
-                    .aligned()
-                    .left()
-            }))
-            .with_child(
-                Label::new(user.github_login.clone(), style.label.clone())
-                    .contained()
-                    .with_style(theme.contact_username)
-                    .aligned()
-                    .left(),
-            )
-            .with_children(icon_path.map(|icon_path| {
-                Svg::new(icon_path)
-                    .with_color(button_style.color)
-                    .constrained()
-                    .with_width(button_style.icon_width)
-                    .aligned()
-                    .contained()
-                    .with_style(button_style.container)
-                    .constrained()
-                    .with_width(button_style.button_width)
-                    .with_height(button_style.button_width)
-                    .aligned()
-                    .flex_float()
-            }))
-            .contained()
-            .with_style(style.container)
-            .constrained()
-            .with_height(tabbed_modal.row_height)
-            .into_any()
+        Some(
+            ListItem::new(ix)
+                .inset(true)
+                .spacing(ListItemSpacing::Sparse)
+                .selected(selected)
+                .start_slot(Avatar::new(user.avatar_uri.clone()))
+                .child(Label::new(user.github_login.clone()))
+                .end_slot::<IconElement>(
+                    icon_path.map(|icon_path| IconElement::from_path(icon_path)),
+                ),
+        )
     }
 }

crates/collab_ui/src/collab_titlebar_item.rs 🔗

@@ -1,30 +1,24 @@
-use crate::{
-    face_pile::FacePile, toggle_deafen, toggle_mute, toggle_screen_sharing, LeaveCall,
-    ToggleDeafen, ToggleMute, ToggleScreenSharing,
-};
+use crate::face_pile::FacePile;
 use auto_update::AutoUpdateStatus;
 use call::{ActiveCall, ParticipantLocation, Room};
-use client::{proto::PeerId, Client, SignIn, SignOut, User, UserStore};
-use clock::ReplicaId;
-use context_menu::{ContextMenu, ContextMenuItem};
+use client::{proto::PeerId, Client, ParticipantIndex, User, UserStore};
 use gpui::{
-    actions,
-    color::Color,
-    elements::*,
-    geometry::{rect::RectF, vector::vec2f, PathBuilder},
-    json::{self, ToJson},
-    platform::{CursorStyle, MouseButton},
-    AppContext, Entity, ImageData, ModelHandle, Subscription, View, ViewContext, ViewHandle,
-    WeakViewHandle,
+    actions, canvas, div, point, px, rems, Action, AnyElement, AppContext, Element, Hsla,
+    InteractiveElement, IntoElement, Model, ParentElement, Path, Render,
+    StatefulInteractiveElement, Styled, Subscription, View, ViewContext, VisualContext, WeakView,
+    WindowBounds,
 };
-use picker::PickerEvent;
 use project::{Project, RepositoryEntry};
-use recent_projects::{build_recent_projects, RecentProjects};
-use std::{ops::Range, sync::Arc};
-use theme::{AvatarStyle, Theme};
+use recent_projects::RecentProjects;
+use std::sync::Arc;
+use theme::{ActiveTheme, PlayerColors};
+use ui::{
+    h_stack, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon,
+    IconButton, IconElement, Tooltip,
+};
 use util::ResultExt;
 use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu};
-use workspace::{FollowNextCollaborator, Workspace, WORKSPACE_DB};
+use workspace::{notifications::NotifyResultExt, Workspace};
 
 const MAX_PROJECT_NAME_LENGTH: usize = 40;
 const MAX_BRANCH_NAME_LENGTH: usize = 40;
@@ -32,131 +26,269 @@ const MAX_BRANCH_NAME_LENGTH: usize = 40;
 actions!(
     collab,
     [
-        ToggleUserMenu,
-        ToggleProjectMenu,
-        SwitchBranch,
         ShareProject,
         UnshareProject,
+        ToggleUserMenu,
+        ToggleProjectMenu,
+        SwitchBranch
     ]
 );
 
 pub fn init(cx: &mut AppContext) {
-    cx.add_action(CollabTitlebarItem::share_project);
-    cx.add_action(CollabTitlebarItem::unshare_project);
-    cx.add_action(CollabTitlebarItem::toggle_user_menu);
-    cx.add_action(CollabTitlebarItem::toggle_vcs_menu);
-    cx.add_action(CollabTitlebarItem::toggle_project_menu);
+    cx.observe_new_views(|workspace: &mut Workspace, cx| {
+        let titlebar_item = cx.new_view(|cx| CollabTitlebarItem::new(workspace, cx));
+        workspace.set_titlebar_item(titlebar_item.into(), cx)
+    })
+    .detach();
+    // cx.add_action(CollabTitlebarItem::share_project);
+    // cx.add_action(CollabTitlebarItem::unshare_project);
+    // cx.add_action(CollabTitlebarItem::toggle_user_menu);
+    // cx.add_action(CollabTitlebarItem::toggle_vcs_menu);
+    // cx.add_action(CollabTitlebarItem::toggle_project_menu);
 }
 
 pub struct CollabTitlebarItem {
-    project: ModelHandle<Project>,
-    user_store: ModelHandle<UserStore>,
+    project: Model<Project>,
+    user_store: Model<UserStore>,
     client: Arc<Client>,
-    workspace: WeakViewHandle<Workspace>,
-    branch_popover: Option<ViewHandle<BranchList>>,
-    project_popover: Option<ViewHandle<recent_projects::RecentProjects>>,
-    user_menu: ViewHandle<ContextMenu>,
+    workspace: WeakView<Workspace>,
     _subscriptions: Vec<Subscription>,
 }
 
-impl Entity for CollabTitlebarItem {
-    type Event = ();
-}
-
-impl View for CollabTitlebarItem {
-    fn ui_name() -> &'static str {
-        "CollabTitlebarItem"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let workspace = if let Some(workspace) = self.workspace.upgrade(cx) {
-            workspace
-        } else {
-            return Empty::new().into_any();
-        };
-
-        let theme = theme::current(cx).clone();
-        let mut left_container = Flex::row();
-        let mut right_container = Flex::row().align_children_center();
-
-        left_container.add_child(self.collect_title_root_names(theme.clone(), cx));
-
-        let user = self.user_store.read(cx).current_user();
-        let peer_id = self.client.peer_id();
-        if let Some(((user, peer_id), room)) = user
-            .as_ref()
-            .zip(peer_id)
-            .zip(ActiveCall::global(cx).read(cx).room().cloned())
-        {
-            if room.read(cx).can_publish() {
-                right_container
-                    .add_children(self.render_in_call_share_unshare_button(&workspace, &theme, cx));
-            }
-            right_container.add_child(self.render_leave_call(&theme, cx));
-            let muted = room.read(cx).is_muted(cx);
-            let speaking = room.read(cx).is_speaking();
-            left_container.add_child(
-                self.render_current_user(&workspace, &theme, &user, peer_id, muted, speaking, cx),
-            );
-            left_container.add_children(self.render_collaborators(&workspace, &theme, &room, cx));
-            if room.read(cx).can_publish() {
-                right_container.add_child(self.render_toggle_mute(&theme, &room, cx));
-            }
-            right_container.add_child(self.render_toggle_deafen(&theme, &room, cx));
-            if room.read(cx).can_publish() {
-                right_container
-                    .add_child(self.render_toggle_screen_sharing_button(&theme, &room, cx));
-            }
-        }
-
-        let status = workspace.read(cx).client().status();
-        let status = &*status.borrow();
-        if matches!(status, client::Status::Connected { .. }) {
-            let avatar = user.as_ref().and_then(|user| user.avatar.clone());
-            right_container.add_child(self.render_user_menu_button(&theme, avatar, cx));
-        } else {
-            right_container.add_children(self.render_connection_status(status, cx));
-            right_container.add_child(self.render_sign_in_button(&theme, cx));
-            right_container.add_child(self.render_user_menu_button(&theme, None, cx));
-        }
-
-        Stack::new()
-            .with_child(left_container)
-            .with_child(
-                Flex::row()
-                    .with_child(
-                        right_container.contained().with_background_color(
-                            theme
-                                .titlebar
-                                .container
-                                .background_color
-                                .unwrap_or_else(|| Color::transparent_black()),
-                        ),
-                    )
-                    .aligned()
-                    .right(),
+impl Render for CollabTitlebarItem {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        let room = ActiveCall::global(cx).read(cx).room().cloned();
+        let current_user = self.user_store.read(cx).current_user();
+        let client = self.client.clone();
+        let project_id = self.project.read(cx).remote_id();
+
+        h_stack()
+            .id("titlebar")
+            .justify_between()
+            .w_full()
+            .h(rems(1.75))
+            // Set a non-scaling min-height here to ensure the titlebar is
+            // always at least the height of the traffic lights.
+            .min_h(px(32.))
+            .map(|this| {
+                if matches!(cx.window_bounds(), WindowBounds::Fullscreen) {
+                    this.pl_2()
+                } else {
+                    // Use pixels here instead of a rem-based size because the macOS traffic
+                    // lights are a static size, and don't scale with the rest of the UI.
+                    this.pl(px(80.))
+                }
+            })
+            .bg(cx.theme().colors().title_bar_background)
+            .on_click(|event, cx| {
+                if event.up.click_count == 2 {
+                    cx.zoom_window();
+                }
+            })
+            // left side
+            .child(
+                h_stack()
+                    .gap_1()
+                    .children(self.render_project_host(cx))
+                    .child(self.render_project_name(cx))
+                    .children(self.render_project_branch(cx))
+                    .when_some(
+                        current_user.clone().zip(client.peer_id()).zip(room.clone()),
+                        |this, ((current_user, peer_id), room)| {
+                            let player_colors = cx.theme().players();
+                            let room = room.read(cx);
+                            let mut remote_participants =
+                                room.remote_participants().values().collect::<Vec<_>>();
+                            remote_participants.sort_by_key(|p| p.participant_index.0);
+
+                            this.children(self.render_collaborator(
+                                &current_user,
+                                peer_id,
+                                true,
+                                room.is_speaking(),
+                                room.is_muted(cx),
+                                &room,
+                                project_id,
+                                &current_user,
+                            ))
+                            .children(
+                                remote_participants.iter().filter_map(|collaborator| {
+                                    let is_present = project_id.map_or(false, |project_id| {
+                                        collaborator.location
+                                            == ParticipantLocation::SharedProject { project_id }
+                                    });
+
+                                    let face_pile = self.render_collaborator(
+                                        &collaborator.user,
+                                        collaborator.peer_id,
+                                        is_present,
+                                        collaborator.speaking,
+                                        collaborator.muted,
+                                        &room,
+                                        project_id,
+                                        &current_user,
+                                    )?;
+
+                                    Some(
+                                        v_stack()
+                                            .id(("collaborator", collaborator.user.id))
+                                            .child(face_pile)
+                                            .child(render_color_ribbon(
+                                                collaborator.participant_index,
+                                                player_colors,
+                                            ))
+                                            .cursor_pointer()
+                                            .on_click({
+                                                let peer_id = collaborator.peer_id;
+                                                cx.listener(move |this, _, cx| {
+                                                    this.workspace
+                                                        .update(cx, |workspace, cx| {
+                                                            workspace.follow(peer_id, cx);
+                                                        })
+                                                        .ok();
+                                                })
+                                            })
+                                            .tooltip({
+                                                let login = collaborator.user.github_login.clone();
+                                                move |cx| {
+                                                    Tooltip::text(format!("Follow {login}"), cx)
+                                                }
+                                            }),
+                                    )
+                                }),
+                            )
+                        },
+                    ),
+            )
+            // right side
+            .child(
+                h_stack()
+                    .gap_1()
+                    .pr_1()
+                    .when_some(room, |this, room| {
+                        let room = room.read(cx);
+                        let project = self.project.read(cx);
+                        let is_local = project.is_local();
+                        let is_shared = is_local && project.is_shared();
+                        let is_muted = room.is_muted(cx);
+                        let is_deafened = room.is_deafened().unwrap_or(false);
+                        let is_screen_sharing = room.is_screen_sharing();
+
+                        this.when(is_local, |this| {
+                            this.child(
+                                Button::new(
+                                    "toggle_sharing",
+                                    if is_shared { "Unshare" } else { "Share" },
+                                )
+                                .style(ButtonStyle::Subtle)
+                                .label_size(LabelSize::Small)
+                                .on_click(cx.listener(
+                                    move |this, _, cx| {
+                                        if is_shared {
+                                            this.unshare_project(&Default::default(), cx);
+                                        } else {
+                                            this.share_project(&Default::default(), cx);
+                                        }
+                                    },
+                                )),
+                            )
+                        })
+                        .child(
+                            IconButton::new("leave-call", ui::Icon::Exit)
+                                .style(ButtonStyle::Subtle)
+                                .icon_size(IconSize::Small)
+                                .on_click(move |_, cx| {
+                                    ActiveCall::global(cx)
+                                        .update(cx, |call, cx| call.hang_up(cx))
+                                        .detach_and_log_err(cx);
+                                }),
+                        )
+                        .child(
+                            IconButton::new(
+                                "mute-microphone",
+                                if is_muted {
+                                    ui::Icon::MicMute
+                                } else {
+                                    ui::Icon::Mic
+                                },
+                            )
+                            .style(ButtonStyle::Subtle)
+                            .icon_size(IconSize::Small)
+                            .selected(is_muted)
+                            .on_click(move |_, cx| crate::toggle_mute(&Default::default(), cx)),
+                        )
+                        .child(
+                            IconButton::new(
+                                "mute-sound",
+                                if is_deafened {
+                                    ui::Icon::AudioOff
+                                } else {
+                                    ui::Icon::AudioOn
+                                },
+                            )
+                            .style(ButtonStyle::Subtle)
+                            .icon_size(IconSize::Small)
+                            .selected(is_deafened)
+                            .tooltip(move |cx| {
+                                Tooltip::with_meta("Deafen Audio", None, "Mic will be muted", cx)
+                            })
+                            .on_click(move |_, cx| crate::toggle_mute(&Default::default(), cx)),
+                        )
+                        .child(
+                            IconButton::new("screen-share", ui::Icon::Screen)
+                                .style(ButtonStyle::Subtle)
+                                .icon_size(IconSize::Small)
+                                .selected(is_screen_sharing)
+                                .on_click(move |_, cx| {
+                                    crate::toggle_screen_sharing(&Default::default(), cx)
+                                }),
+                        )
+                    })
+                    .map(|el| {
+                        let status = self.client.status();
+                        let status = &*status.borrow();
+                        if matches!(status, client::Status::Connected { .. }) {
+                            el.child(self.render_user_menu_button(cx))
+                        } else {
+                            el.children(self.render_connection_status(status, cx))
+                                .child(self.render_sign_in_button(cx))
+                                .child(self.render_user_menu_button(cx))
+                        }
+                    }),
             )
-            .into_any()
     }
 }
 
+fn render_color_ribbon(participant_index: ParticipantIndex, colors: &PlayerColors) -> gpui::Canvas {
+    let color = colors.color_for_participant(participant_index.0).cursor;
+    canvas(move |bounds, cx| {
+        let mut path = Path::new(bounds.lower_left());
+        let height = bounds.size.height;
+        path.curve_to(bounds.origin + point(height, px(0.)), bounds.origin);
+        path.line_to(bounds.upper_right() - point(height, px(0.)));
+        path.curve_to(bounds.lower_right(), bounds.upper_right());
+        path.line_to(bounds.lower_left());
+        cx.paint_path(path, color);
+    })
+    .h_1()
+    .w_full()
+}
+
 impl CollabTitlebarItem {
-    pub fn new(
-        workspace: &Workspace,
-        workspace_handle: &ViewHandle<Workspace>,
-        cx: &mut ViewContext<Self>,
-    ) -> Self {
+    pub fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
         let project = workspace.project().clone();
         let user_store = workspace.app_state().user_store.clone();
         let client = workspace.app_state().client.clone();
         let active_call = ActiveCall::global(cx);
         let mut subscriptions = Vec::new();
-        subscriptions.push(cx.observe(workspace_handle, |_, _, cx| cx.notify()));
+        subscriptions.push(
+            cx.observe(&workspace.weak_handle().upgrade().unwrap(), |_, _, cx| {
+                cx.notify()
+            }),
+        );
         subscriptions.push(cx.observe(&project, |_, _, cx| cx.notify()));
         subscriptions.push(cx.observe(&active_call, |this, _, cx| this.active_call_changed(cx)));
-        subscriptions.push(cx.observe_window_activation(|this, active, cx| {
-            this.window_activation_changed(active, cx)
-        }));
+        subscriptions.push(cx.observe_window_activation(Self::window_activation_changed));
         subscriptions.push(cx.observe(&user_store, |_, _, cx| cx.notify()));
 
         Self {
@@ -164,184 +296,132 @@ impl CollabTitlebarItem {
             project,
             user_store,
             client,
-            user_menu: cx.add_view(|cx| {
-                let view_id = cx.view_id();
-                let mut menu = ContextMenu::new(view_id, cx);
-                menu.set_position_mode(OverlayPositionMode::Local);
-                menu
-            }),
-            branch_popover: None,
-            project_popover: None,
             _subscriptions: subscriptions,
         }
     }
 
-    fn collect_title_root_names(
-        &self,
-        theme: Arc<Theme>,
-        cx: &mut ViewContext<Self>,
-    ) -> AnyElement<Self> {
-        let project = self.project.read(cx);
+    // resolve if you are in a room -> render_project_owner
+    // render_project_owner -> resolve if you are in a room -> Option<foo>
 
-        let (name, entry) = {
-            let mut names_and_branches = project.visible_worktrees(cx).map(|worktree| {
+    pub fn render_project_host(&self, cx: &mut ViewContext<Self>) -> Option<impl Element> {
+        let host = self.project.read(cx).host()?;
+        let host = self.user_store.read(cx).get_cached_user(host.user_id)?;
+        let participant_index = self
+            .user_store
+            .read(cx)
+            .participant_indices()
+            .get(&host.id)?;
+        Some(
+            div().border().border_color(gpui::red()).child(
+                Button::new("project_owner_trigger", host.github_login.clone())
+                    .color(Color::Player(participant_index.0))
+                    .style(ButtonStyle::Subtle)
+                    .label_size(LabelSize::Small)
+                    .tooltip(move |cx| Tooltip::text("Toggle following", cx)),
+            ),
+        )
+    }
+
+    pub fn render_project_name(&self, cx: &mut ViewContext<Self>) -> impl Element {
+        let name = {
+            let mut names = self.project.read(cx).visible_worktrees(cx).map(|worktree| {
                 let worktree = worktree.read(cx);
-                (worktree.root_name(), worktree.root_git_entry())
+                worktree.root_name()
             });
 
-            names_and_branches.next().unwrap_or(("", None))
+            names.next().unwrap_or("")
         };
 
         let name = util::truncate_and_trailoff(name, MAX_PROJECT_NAME_LENGTH);
-        let branch_prepended = entry
-            .as_ref()
-            .and_then(RepositoryEntry::branch)
-            .map(|branch| util::truncate_and_trailoff(&branch, MAX_BRANCH_NAME_LENGTH));
-        let project_style = theme.titlebar.project_menu_button.clone();
-        let git_style = theme.titlebar.git_menu_button.clone();
-        let item_spacing = theme.titlebar.item_spacing;
-
-        let mut ret = Flex::row();
+        let workspace = self.workspace.clone();
+        popover_menu("project_name_trigger")
+            .trigger(
+                Button::new("project_name_trigger", name)
+                    .style(ButtonStyle::Subtle)
+                    .label_size(LabelSize::Small)
+                    .tooltip(move |cx| Tooltip::text("Recent Projects", cx)),
+            )
+            .menu(move |cx| Some(Self::render_project_popover(workspace.clone(), cx)))
+    }
 
-        if let Some(project_host) = self.collect_project_host(theme.clone(), cx) {
-            ret = ret.with_child(project_host)
-        }
+    pub fn render_project_branch(&self, cx: &mut ViewContext<Self>) -> Option<impl Element> {
+        let entry = {
+            let mut names_and_branches =
+                self.project.read(cx).visible_worktrees(cx).map(|worktree| {
+                    let worktree = worktree.read(cx);
+                    worktree.root_git_entry()
+                });
 
-        ret = ret.with_child(
-            Stack::new()
-                .with_child(
-                    MouseEventHandler::new::<ToggleProjectMenu, _>(0, cx, |mouse_state, cx| {
-                        let style = project_style
-                            .in_state(self.project_popover.is_some())
-                            .style_for(mouse_state);
-                        enum RecentProjectsTooltip {}
-                        Label::new(name, style.text.clone())
-                            .contained()
-                            .with_style(style.container)
-                            .aligned()
-                            .left()
-                            .with_tooltip::<RecentProjectsTooltip>(
-                                0,
-                                "Recent projects",
-                                Some(Box::new(recent_projects::OpenRecent)),
-                                theme.tooltip.clone(),
+            names_and_branches.next().flatten()
+        };
+        let workspace = self.workspace.upgrade()?;
+        let branch_name = entry
+            .as_ref()
+            .and_then(RepositoryEntry::branch)
+            .map(|branch| util::truncate_and_trailoff(&branch, MAX_BRANCH_NAME_LENGTH))?;
+        Some(
+            popover_menu("project_branch_trigger")
+                .trigger(
+                    Button::new("project_branch_trigger", branch_name)
+                        .color(Color::Muted)
+                        .style(ButtonStyle::Subtle)
+                        .label_size(LabelSize::Small)
+                        .tooltip(move |cx| {
+                            Tooltip::with_meta(
+                                "Recent Branches",
+                                Some(&ToggleVcsMenu),
+                                "Local branches only",
                                 cx,
                             )
-                            .into_any_named("title-project-name")
-                    })
-                    .with_cursor_style(CursorStyle::PointingHand)
-                    .on_down(MouseButton::Left, move |_, this, cx| {
-                        this.toggle_project_menu(&Default::default(), cx)
-                    })
-                    .on_click(MouseButton::Left, move |_, _, _| {}),
+                        }),
                 )
-                .with_children(self.render_project_popover_host(&theme.titlebar, cx)),
-        );
-        if let Some(git_branch) = branch_prepended {
-            ret = ret.with_child(
-                Flex::row().with_child(
-                    Stack::new()
-                        .with_child(
-                            MouseEventHandler::new::<ToggleVcsMenu, _>(0, cx, |mouse_state, cx| {
-                                enum BranchPopoverTooltip {}
-                                let style = git_style
-                                    .in_state(self.branch_popover.is_some())
-                                    .style_for(mouse_state);
-                                Label::new(git_branch, style.text.clone())
-                                    .contained()
-                                    .with_style(style.container.clone())
-                                    .with_margin_right(item_spacing)
-                                    .aligned()
-                                    .left()
-                                    .with_tooltip::<BranchPopoverTooltip>(
-                                        0,
-                                        "Recent branches",
-                                        Some(Box::new(ToggleVcsMenu)),
-                                        theme.tooltip.clone(),
-                                        cx,
-                                    )
-                                    .into_any_named("title-project-branch")
-                            })
-                            .with_cursor_style(CursorStyle::PointingHand)
-                            .on_down(MouseButton::Left, move |_, this, cx| {
-                                this.toggle_vcs_menu(&Default::default(), cx)
-                            })
-                            .on_click(MouseButton::Left, move |_, _, _| {}),
-                        )
-                        .with_children(self.render_branches_popover_host(&theme.titlebar, cx)),
-                ),
-            )
-        }
-        ret.into_any()
+                .menu(move |cx| Self::render_vcs_popover(workspace.clone(), cx)),
+        )
     }
 
-    fn collect_project_host(
+    fn render_collaborator(
         &self,
-        theme: Arc<Theme>,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<AnyElement<Self>> {
-        if ActiveCall::global(cx).read(cx).room().is_none() {
-            return None;
-        }
-        let project = self.project.read(cx);
-        let user_store = self.user_store.read(cx);
-
-        if project.is_local() {
-            return None;
-        }
-
-        let Some(host) = project.host() else {
-            return None;
-        };
-        let (Some(host_user), Some(participant_index)) = (
-            user_store.get_cached_user(host.user_id),
-            user_store.participant_indices().get(&host.user_id),
-        ) else {
-            return None;
-        };
-
-        enum ProjectHost {}
-        enum ProjectHostTooltip {}
+        user: &Arc<User>,
+        peer_id: PeerId,
+        is_present: bool,
+        is_speaking: bool,
+        is_muted: bool,
+        room: &Room,
+        project_id: Option<u64>,
+        current_user: &Arc<User>,
+    ) -> Option<FacePile> {
+        let followers = project_id.map_or(&[] as &[_], |id| room.followers_for(peer_id, id));
+
+        let pile = FacePile::default()
+            .child(
+                Avatar::new(user.avatar_uri.clone())
+                    .grayscale(!is_present)
+                    .border_color(if is_speaking {
+                        gpui::blue()
+                    } else if is_muted {
+                        gpui::red()
+                    } else {
+                        Hsla::default()
+                    }),
+            )
+            .children(followers.iter().filter_map(|follower_peer_id| {
+                let follower = room
+                    .remote_participants()
+                    .values()
+                    .find_map(|p| (p.peer_id == *follower_peer_id).then_some(&p.user))
+                    .or_else(|| {
+                        (self.client.peer_id() == Some(*follower_peer_id)).then_some(current_user)
+                    })?
+                    .clone();
 
-        let host_style = theme.titlebar.project_host.clone();
-        let selection_style = theme
-            .editor
-            .selection_style_for_room_participant(participant_index.0);
-        let peer_id = host.peer_id.clone();
+                Some(Avatar::new(follower.avatar_uri.clone()))
+            }));
 
-        Some(
-            MouseEventHandler::new::<ProjectHost, _>(0, cx, |mouse_state, _| {
-                let mut host_style = host_style.style_for(mouse_state).clone();
-                host_style.text.color = selection_style.cursor;
-                Label::new(host_user.github_login.clone(), host_style.text)
-                    .contained()
-                    .with_style(host_style.container)
-                    .aligned()
-                    .left()
-            })
-            .with_cursor_style(CursorStyle::PointingHand)
-            .on_click(MouseButton::Left, move |_, this, cx| {
-                if let Some(workspace) = this.workspace.upgrade(cx) {
-                    if let Some(task) =
-                        workspace.update(cx, |workspace, cx| workspace.follow(peer_id, cx))
-                    {
-                        task.detach_and_log_err(cx);
-                    }
-                }
-            })
-            .with_tooltip::<ProjectHostTooltip>(
-                0,
-                host_user.github_login.clone() + " is sharing this project. Click to follow.",
-                None,
-                theme.tooltip.clone(),
-                cx,
-            )
-            .into_any_named("project-host"),
-        )
+        Some(pile)
     }
 
-    fn window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
-        let project = if active {
+    fn window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
+        let project = if cx.is_window_active() {
             Some(self.project.clone())
         } else {
             None
@@ -371,801 +451,44 @@ impl CollabTitlebarItem {
             .log_err();
     }
 
-    pub fn toggle_user_menu(&mut self, _: &ToggleUserMenu, cx: &mut ViewContext<Self>) {
-        self.user_menu.update(cx, |user_menu, cx| {
-            let items = if let Some(_) = self.user_store.read(cx).current_user() {
-                vec![
-                    ContextMenuItem::action("Settings", zed_actions::OpenSettings),
-                    ContextMenuItem::action("Theme", theme_selector::Toggle),
-                    ContextMenuItem::separator(),
-                    ContextMenuItem::action(
-                        "Share Feedback",
-                        feedback::feedback_editor::GiveFeedback,
-                    ),
-                    ContextMenuItem::action("Sign Out", SignOut),
-                ]
-            } else {
-                vec![
-                    ContextMenuItem::action("Settings", zed_actions::OpenSettings),
-                    ContextMenuItem::action("Theme", theme_selector::Toggle),
-                    ContextMenuItem::separator(),
-                    ContextMenuItem::action(
-                        "Share Feedback",
-                        feedback::feedback_editor::GiveFeedback,
-                    ),
-                ]
-            };
-            user_menu.toggle(Default::default(), AnchorCorner::TopRight, items, cx);
-        });
-    }
-
-    fn render_branches_popover_host<'a>(
-        &'a self,
-        _theme: &'a theme::Titlebar,
-        cx: &'a mut ViewContext<Self>,
-    ) -> Option<AnyElement<Self>> {
-        self.branch_popover.as_ref().map(|child| {
-            let theme = theme::current(cx).clone();
-            let child = ChildView::new(child, cx);
-            let child = MouseEventHandler::new::<BranchList, _>(0, cx, |_, _| {
-                child
-                    .flex(1., true)
-                    .contained()
-                    .constrained()
-                    .with_width(theme.titlebar.menu.width)
-                    .with_height(theme.titlebar.menu.height)
-            })
-            .on_click(MouseButton::Left, |_, _, _| {})
-            .on_down_out(MouseButton::Left, move |_, this, cx| {
-                this.branch_popover.take();
-                cx.emit(());
-                cx.notify();
-            })
-            .contained()
-            .into_any();
-
-            Overlay::new(child)
-                .with_fit_mode(OverlayFitMode::SwitchAnchor)
-                .with_anchor_corner(AnchorCorner::TopLeft)
-                .with_z_index(999)
-                .aligned()
-                .bottom()
-                .left()
-                .into_any()
-        })
-    }
-
-    fn render_project_popover_host<'a>(
-        &'a self,
-        _theme: &'a theme::Titlebar,
-        cx: &'a mut ViewContext<Self>,
-    ) -> Option<AnyElement<Self>> {
-        self.project_popover.as_ref().map(|child| {
-            let theme = theme::current(cx).clone();
-            let child = ChildView::new(child, cx);
-            let child = MouseEventHandler::new::<RecentProjects, _>(0, cx, |_, _| {
-                child
-                    .flex(1., true)
-                    .contained()
-                    .constrained()
-                    .with_width(theme.titlebar.menu.width)
-                    .with_height(theme.titlebar.menu.height)
-            })
-            .on_click(MouseButton::Left, |_, _, _| {})
-            .on_down_out(MouseButton::Left, move |_, this, cx| {
-                this.project_popover.take();
-                cx.emit(());
-                cx.notify();
-            })
-            .into_any();
-
-            Overlay::new(child)
-                .with_fit_mode(OverlayFitMode::SwitchAnchor)
-                .with_anchor_corner(AnchorCorner::TopLeft)
-                .with_z_index(999)
-                .aligned()
-                .bottom()
-                .left()
-                .into_any()
-        })
-    }
-
-    pub fn toggle_vcs_menu(&mut self, _: &ToggleVcsMenu, cx: &mut ViewContext<Self>) {
-        if self.branch_popover.take().is_none() {
-            if let Some(workspace) = self.workspace.upgrade(cx) {
-                let Some(view) =
-                    cx.add_option_view(|cx| build_branch_list(workspace, cx).log_err())
-                else {
-                    return;
-                };
-                cx.subscribe(&view, |this, _, event, cx| {
-                    match event {
-                        PickerEvent::Dismiss => {
-                            this.branch_popover = None;
-                        }
-                    }
-
-                    cx.notify();
-                })
-                .detach();
-                self.project_popover.take();
-                cx.focus(&view);
-                self.branch_popover = Some(view);
-            }
-        }
-
-        cx.notify();
+    pub fn render_vcs_popover(
+        workspace: View<Workspace>,
+        cx: &mut WindowContext<'_>,
+    ) -> Option<View<BranchList>> {
+        let view = build_branch_list(workspace, cx).log_err()?;
+        let focus_handle = view.focus_handle(cx);
+        cx.focus(&focus_handle);
+        Some(view)
     }
 
-    pub fn toggle_project_menu(&mut self, _: &ToggleProjectMenu, cx: &mut ViewContext<Self>) {
-        let workspace = self.workspace.clone();
-        if self.project_popover.take().is_none() {
-            cx.spawn(|this, mut cx| async move {
-                let workspaces = WORKSPACE_DB
-                    .recent_workspaces_on_disk()
-                    .await
-                    .unwrap_or_default()
-                    .into_iter()
-                    .map(|(_, location)| location)
-                    .collect();
-
-                let workspace = workspace.clone();
-                this.update(&mut cx, move |this, cx| {
-                    let view = cx.add_view(|cx| build_recent_projects(workspace, workspaces, cx));
-
-                    cx.subscribe(&view, |this, _, event, cx| {
-                        match event {
-                            PickerEvent::Dismiss => {
-                                this.project_popover = None;
-                            }
-                        }
+    pub fn render_project_popover(
+        workspace: WeakView<Workspace>,
+        cx: &mut WindowContext<'_>,
+    ) -> View<RecentProjects> {
+        let view = RecentProjects::open_popover(workspace, cx);
 
-                        cx.notify();
-                    })
-                    .detach();
-                    cx.focus(&view);
-                    this.branch_popover.take();
-                    this.project_popover = Some(view);
-                    cx.notify();
-                })
-                .log_err();
-            })
-            .detach();
-        }
-        cx.notify();
-    }
-
-    fn render_toggle_screen_sharing_button(
-        &self,
-        theme: &Theme,
-        room: &ModelHandle<Room>,
-        cx: &mut ViewContext<Self>,
-    ) -> AnyElement<Self> {
-        let icon;
-        let tooltip;
-        if room.read(cx).is_screen_sharing() {
-            icon = "icons/desktop.svg";
-            tooltip = "Stop Sharing Screen"
-        } else {
-            icon = "icons/desktop.svg";
-            tooltip = "Share Screen";
-        }
-
-        let active = room.read(cx).is_screen_sharing();
-        let titlebar = &theme.titlebar;
-        MouseEventHandler::new::<ToggleScreenSharing, _>(0, cx, |state, _| {
-            let style = titlebar
-                .screen_share_button
-                .in_state(active)
-                .style_for(state);
-
-            Svg::new(icon)
-                .with_color(style.color)
-                .constrained()
-                .with_width(style.icon_width)
-                .aligned()
-                .constrained()
-                .with_width(style.button_width)
-                .with_height(style.button_width)
-                .contained()
-                .with_style(style.container)
-        })
-        .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, move |_, _, cx| {
-            toggle_screen_sharing(&Default::default(), cx)
-        })
-        .with_tooltip::<ToggleScreenSharing>(
-            0,
-            tooltip,
-            Some(Box::new(ToggleScreenSharing)),
-            theme.tooltip.clone(),
-            cx,
-        )
-        .aligned()
-        .into_any()
-    }
-    fn render_toggle_mute(
-        &self,
-        theme: &Theme,
-        room: &ModelHandle<Room>,
-        cx: &mut ViewContext<Self>,
-    ) -> AnyElement<Self> {
-        let icon;
-        let tooltip;
-        let is_muted = room.read(cx).is_muted(cx);
-        if is_muted {
-            icon = "icons/mic-mute.svg";
-            tooltip = "Unmute microphone";
-        } else {
-            icon = "icons/mic.svg";
-            tooltip = "Mute microphone";
-        }
-
-        let titlebar = &theme.titlebar;
-        MouseEventHandler::new::<ToggleMute, _>(0, cx, |state, _| {
-            let style = titlebar
-                .toggle_microphone_button
-                .in_state(is_muted)
-                .style_for(state);
-            let image = Svg::new(icon)
-                .with_color(style.color)
-                .constrained()
-                .with_width(style.icon_width)
-                .aligned()
-                .constrained()
-                .with_width(style.button_width)
-                .with_height(style.button_width)
-                .contained()
-                .with_style(style.container);
-            if let Some(color) = style.container.background_color {
-                image.with_background_color(color)
-            } else {
-                image
-            }
-        })
-        .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, move |_, _, cx| {
-            toggle_mute(&Default::default(), cx)
-        })
-        .with_tooltip::<ToggleMute>(
-            0,
-            tooltip,
-            Some(Box::new(ToggleMute)),
-            theme.tooltip.clone(),
-            cx,
-        )
-        .aligned()
-        .into_any()
-    }
-    fn render_toggle_deafen(
-        &self,
-        theme: &Theme,
-        room: &ModelHandle<Room>,
-        cx: &mut ViewContext<Self>,
-    ) -> AnyElement<Self> {
-        let icon;
-        let tooltip;
-        let is_deafened = room.read(cx).is_deafened().unwrap_or(false);
-        if is_deafened {
-            icon = "icons/speaker-off.svg";
-            tooltip = "Unmute speakers";
-        } else {
-            icon = "icons/speaker-loud.svg";
-            tooltip = "Mute speakers";
-        }
-
-        let titlebar = &theme.titlebar;
-        MouseEventHandler::new::<ToggleDeafen, _>(0, cx, |state, _| {
-            let style = titlebar
-                .toggle_speakers_button
-                .in_state(is_deafened)
-                .style_for(state);
-            Svg::new(icon)
-                .with_color(style.color)
-                .constrained()
-                .with_width(style.icon_width)
-                .aligned()
-                .constrained()
-                .with_width(style.button_width)
-                .with_height(style.button_width)
-                .contained()
-                .with_style(style.container)
-        })
-        .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, move |_, _, cx| {
-            toggle_deafen(&Default::default(), cx)
-        })
-        .with_tooltip::<ToggleDeafen>(
-            0,
-            tooltip,
-            Some(Box::new(ToggleDeafen)),
-            theme.tooltip.clone(),
-            cx,
-        )
-        .aligned()
-        .into_any()
-    }
-    fn render_leave_call(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let icon = "icons/exit.svg";
-        let tooltip = "Leave call";
-
-        let titlebar = &theme.titlebar;
-        MouseEventHandler::new::<LeaveCall, _>(0, cx, |state, _| {
-            let style = titlebar.leave_call_button.style_for(state);
-            Svg::new(icon)
-                .with_color(style.color)
-                .constrained()
-                .with_width(style.icon_width)
-                .aligned()
-                .constrained()
-                .with_width(style.button_width)
-                .with_height(style.button_width)
-                .contained()
-                .with_style(style.container)
-        })
-        .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, move |_, _, cx| {
-            ActiveCall::global(cx)
-                .update(cx, |call, cx| call.hang_up(cx))
-                .detach_and_log_err(cx);
-        })
-        .with_tooltip::<LeaveCall>(
-            0,
-            tooltip,
-            Some(Box::new(LeaveCall)),
-            theme.tooltip.clone(),
-            cx,
-        )
-        .aligned()
-        .into_any()
-    }
-    fn render_in_call_share_unshare_button(
-        &self,
-        workspace: &ViewHandle<Workspace>,
-        theme: &Theme,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<AnyElement<Self>> {
-        let project = workspace.read(cx).project();
-        if project.read(cx).is_remote() {
-            return None;
-        }
-
-        let is_shared = project.read(cx).is_shared();
-        let label = if is_shared { "Stop Sharing" } else { "Share" };
-        let tooltip = if is_shared {
-            "Stop sharing project with call participants"
-        } else {
-            "Share project with call participants"
-        };
-
-        let titlebar = &theme.titlebar;
-
-        enum ShareUnshare {}
-        Some(
-            Stack::new()
-                .with_child(
-                    MouseEventHandler::new::<ShareUnshare, _>(0, cx, |state, _| {
-                        //TODO: Ensure this button has consistent width for both text variations
-                        let style = titlebar.share_button.inactive_state().style_for(state);
-                        Label::new(label, style.text.clone())
-                            .contained()
-                            .with_style(style.container)
-                    })
-                    .with_cursor_style(CursorStyle::PointingHand)
-                    .on_click(MouseButton::Left, move |_, this, cx| {
-                        if is_shared {
-                            this.unshare_project(&Default::default(), cx);
-                        } else {
-                            this.share_project(&Default::default(), cx);
-                        }
-                    })
-                    .with_tooltip::<ShareUnshare>(
-                        0,
-                        tooltip.to_owned(),
-                        None,
-                        theme.tooltip.clone(),
-                        cx,
-                    ),
-                )
-                .aligned()
-                .contained()
-                .with_margin_left(theme.titlebar.item_spacing)
-                .into_any(),
-        )
-    }
-
-    fn render_user_menu_button(
-        &self,
-        theme: &Theme,
-        avatar: Option<Arc<ImageData>>,
-        cx: &mut ViewContext<Self>,
-    ) -> AnyElement<Self> {
-        let tooltip = theme.tooltip.clone();
-        let user_menu_button_style = if avatar.is_some() {
-            &theme.titlebar.user_menu.user_menu_button_online
-        } else {
-            &theme.titlebar.user_menu.user_menu_button_offline
-        };
-
-        let avatar_style = &user_menu_button_style.avatar;
-        Stack::new()
-            .with_child(
-                MouseEventHandler::new::<ToggleUserMenu, _>(0, cx, |state, _| {
-                    let style = user_menu_button_style
-                        .user_menu
-                        .inactive_state()
-                        .style_for(state);
-
-                    let mut dropdown = Flex::row().align_children_center();
-
-                    if let Some(avatar_img) = avatar {
-                        dropdown = dropdown.with_child(Self::render_face(
-                            avatar_img,
-                            *avatar_style,
-                            Color::transparent_black(),
-                            None,
-                        ));
-                    };
-
-                    dropdown
-                        .with_child(
-                            Svg::new("icons/caret_down.svg")
-                                .with_color(user_menu_button_style.icon.color)
-                                .constrained()
-                                .with_width(user_menu_button_style.icon.width)
-                                .contained()
-                                .into_any(),
-                        )
-                        .aligned()
-                        .constrained()
-                        .with_height(style.width)
-                        .contained()
-                        .with_style(style.container)
-                        .into_any()
-                })
-                .with_cursor_style(CursorStyle::PointingHand)
-                .on_down(MouseButton::Left, move |_, this, cx| {
-                    this.user_menu.update(cx, |menu, _| menu.delay_cancel());
-                })
-                .on_click(MouseButton::Left, move |_, this, cx| {
-                    this.toggle_user_menu(&Default::default(), cx)
-                })
-                .with_tooltip::<ToggleUserMenu>(
-                    0,
-                    "Toggle User Menu".to_owned(),
-                    Some(Box::new(ToggleUserMenu)),
-                    tooltip,
-                    cx,
-                )
-                .contained(),
-            )
-            .with_child(
-                ChildView::new(&self.user_menu, cx)
-                    .aligned()
-                    .bottom()
-                    .right(),
-            )
-            .into_any()
-    }
-
-    fn render_sign_in_button(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let titlebar = &theme.titlebar;
-        MouseEventHandler::new::<SignIn, _>(0, cx, |state, _| {
-            let style = titlebar.sign_in_button.inactive_state().style_for(state);
-            Label::new("Sign In", style.text.clone())
-                .contained()
-                .with_style(style.container)
-        })
-        .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, move |_, this, cx| {
-            let client = this.client.clone();
-            cx.app_context()
-                .spawn(|cx| async move { client.authenticate_and_connect(true, &cx).await })
-                .detach_and_log_err(cx);
-        })
-        .into_any()
-    }
-
-    fn render_collaborators(
-        &self,
-        workspace: &ViewHandle<Workspace>,
-        theme: &Theme,
-        room: &ModelHandle<Room>,
-        cx: &mut ViewContext<Self>,
-    ) -> Vec<Container<Self>> {
-        let mut participants = room
-            .read(cx)
-            .remote_participants()
-            .values()
-            .cloned()
-            .collect::<Vec<_>>();
-        participants.sort_by_cached_key(|p| p.user.github_login.clone());
-
-        participants
-            .into_iter()
-            .filter_map(|participant| {
-                let project = workspace.read(cx).project().read(cx);
-                let replica_id = project
-                    .collaborators()
-                    .get(&participant.peer_id)
-                    .map(|collaborator| collaborator.replica_id);
-                let user = participant.user.clone();
-                Some(
-                    Container::new(self.render_face_pile(
-                        &user,
-                        replica_id,
-                        participant.peer_id,
-                        Some(participant.location),
-                        participant.muted,
-                        participant.speaking,
-                        workspace,
-                        theme,
-                        cx,
-                    ))
-                    .with_margin_right(theme.titlebar.face_pile_spacing),
-                )
-            })
-            .collect()
-    }
-
-    fn render_current_user(
-        &self,
-        workspace: &ViewHandle<Workspace>,
-        theme: &Theme,
-        user: &Arc<User>,
-        peer_id: PeerId,
-        muted: bool,
-        speaking: bool,
-        cx: &mut ViewContext<Self>,
-    ) -> AnyElement<Self> {
-        let replica_id = workspace.read(cx).project().read(cx).replica_id();
-
-        Container::new(self.render_face_pile(
-            user,
-            Some(replica_id),
-            peer_id,
-            None,
-            muted,
-            speaking,
-            workspace,
-            theme,
-            cx,
-        ))
-        .with_margin_right(theme.titlebar.item_spacing)
-        .into_any()
-    }
-
-    fn render_face_pile(
-        &self,
-        user: &User,
-        _replica_id: Option<ReplicaId>,
-        peer_id: PeerId,
-        location: Option<ParticipantLocation>,
-        muted: bool,
-        speaking: bool,
-        workspace: &ViewHandle<Workspace>,
-        theme: &Theme,
-        cx: &mut ViewContext<Self>,
-    ) -> AnyElement<Self> {
-        let user_id = user.id;
-        let project_id = workspace.read(cx).project().read(cx).remote_id();
-        let room = ActiveCall::global(cx).read(cx).room().cloned();
-        let self_peer_id = workspace.read(cx).client().peer_id();
-        let self_following = workspace.read(cx).is_being_followed(peer_id);
-        let self_following_initialized = self_following
-            && room.as_ref().map_or(false, |room| match project_id {
-                None => true,
-                Some(project_id) => room
-                    .read(cx)
-                    .followers_for(peer_id, project_id)
-                    .iter()
-                    .any(|&follower| Some(follower) == self_peer_id),
-            });
-
-        let leader_style = theme.titlebar.leader_avatar;
-        let follower_style = theme.titlebar.follower_avatar;
-
-        let microphone_state = if muted {
-            Some(theme.titlebar.muted)
-        } else if speaking {
-            Some(theme.titlebar.speaking)
-        } else {
-            None
-        };
-
-        let mut background_color = theme
-            .titlebar
-            .container
-            .background_color
-            .unwrap_or_default();
-
-        let participant_index = self
-            .user_store
-            .read(cx)
-            .participant_indices()
-            .get(&user_id)
-            .copied();
-        if let Some(participant_index) = participant_index {
-            if self_following_initialized {
-                let selection = theme
-                    .editor
-                    .selection_style_for_room_participant(participant_index.0)
-                    .selection;
-                background_color = Color::blend(selection, background_color);
-                background_color.a = 255;
-            }
-        }
-
-        enum TitlebarParticipant {}
-
-        let content = MouseEventHandler::new::<TitlebarParticipant, _>(
-            peer_id.as_u64() as usize,
-            cx,
-            move |_, cx| {
-                Stack::new()
-                    .with_children(user.avatar.as_ref().map(|avatar| {
-                        let face_pile = FacePile::new(theme.titlebar.follower_avatar_overlap)
-                            .with_child(Self::render_face(
-                                avatar.clone(),
-                                Self::location_style(workspace, location, leader_style, cx),
-                                background_color,
-                                microphone_state,
-                            ))
-                            .with_children(
-                                (|| {
-                                    let project_id = project_id?;
-                                    let room = room?.read(cx);
-                                    let followers = room.followers_for(peer_id, project_id);
-                                    Some(followers.into_iter().filter_map(|&follower| {
-                                        if Some(follower) == self_peer_id {
-                                            return None;
-                                        }
-                                        let participant =
-                                            room.remote_participant_for_peer_id(follower)?;
-                                        Some(Self::render_face(
-                                            participant.user.avatar.clone()?,
-                                            follower_style,
-                                            background_color,
-                                            None,
-                                        ))
-                                    }))
-                                })()
-                                .into_iter()
-                                .flatten(),
-                            )
-                            .with_children(
-                                self_following_initialized
-                                    .then(|| self.user_store.read(cx).current_user())
-                                    .and_then(|user| {
-                                        Some(Self::render_face(
-                                            user?.avatar.clone()?,
-                                            follower_style,
-                                            background_color,
-                                            None,
-                                        ))
-                                    }),
-                            );
-
-                        let mut container = face_pile
-                            .contained()
-                            .with_style(theme.titlebar.leader_selection);
-
-                        if let Some(participant_index) = participant_index {
-                            if self_following_initialized {
-                                let color = theme
-                                    .editor
-                                    .selection_style_for_room_participant(participant_index.0)
-                                    .selection;
-                                container = container.with_background_color(color);
-                            }
-                        }
-
-                        container
-                    }))
-                    .with_children((|| {
-                        let participant_index = participant_index?;
-                        let color = theme
-                            .editor
-                            .selection_style_for_room_participant(participant_index.0)
-                            .cursor;
-                        Some(
-                            AvatarRibbon::new(color)
-                                .constrained()
-                                .with_width(theme.titlebar.avatar_ribbon.width)
-                                .with_height(theme.titlebar.avatar_ribbon.height)
-                                .aligned()
-                                .bottom(),
-                        )
-                    })())
-            },
-        );
-
-        if Some(peer_id) == self_peer_id {
-            return content.into_any();
-        }
-
-        content
-            .with_cursor_style(CursorStyle::PointingHand)
-            .on_click(MouseButton::Left, move |_, this, cx| {
-                let Some(workspace) = this.workspace.upgrade(cx) else {
-                    return;
-                };
-                if let Some(task) =
-                    workspace.update(cx, |workspace, cx| workspace.follow(peer_id, cx))
-                {
-                    task.detach_and_log_err(cx);
-                }
-            })
-            .with_tooltip::<TitlebarParticipant>(
-                peer_id.as_u64() as usize,
-                format!("Follow {}", user.github_login),
-                Some(Box::new(FollowNextCollaborator)),
-                theme.tooltip.clone(),
-                cx,
-            )
-            .into_any()
-    }
-
-    fn location_style(
-        workspace: &ViewHandle<Workspace>,
-        location: Option<ParticipantLocation>,
-        mut style: AvatarStyle,
-        cx: &ViewContext<Self>,
-    ) -> AvatarStyle {
-        if let Some(location) = location {
-            if let ParticipantLocation::SharedProject { project_id } = location {
-                if Some(project_id) != workspace.read(cx).project().read(cx).remote_id() {
-                    style.image.grayscale = true;
-                }
-            } else {
-                style.image.grayscale = true;
-            }
-        }
-
-        style
-    }
-
-    fn render_face<V: 'static>(
-        avatar: Arc<ImageData>,
-        avatar_style: AvatarStyle,
-        background_color: Color,
-        microphone_state: Option<Color>,
-    ) -> AnyElement<V> {
-        Image::from_data(avatar)
-            .with_style(avatar_style.image)
-            .aligned()
-            .contained()
-            .with_background_color(microphone_state.unwrap_or(background_color))
-            .with_corner_radius(avatar_style.outer_corner_radius)
-            .constrained()
-            .with_width(avatar_style.outer_width)
-            .with_height(avatar_style.outer_width)
-            .aligned()
-            .into_any()
+        let focus_handle = view.focus_handle(cx);
+        cx.focus(&focus_handle);
+        view
     }
 
     fn render_connection_status(
         &self,
         status: &client::Status,
         cx: &mut ViewContext<Self>,
-    ) -> Option<AnyElement<Self>> {
-        enum ConnectionStatusButton {}
-
-        let theme = &theme::current(cx).clone();
+    ) -> Option<AnyElement> {
         match status {
             client::Status::ConnectionError
             | client::Status::ConnectionLost
             | client::Status::Reauthenticating { .. }
             | client::Status::Reconnecting { .. }
             | client::Status::ReconnectionError { .. } => Some(
-                Svg::new("icons/disconnected.svg")
-                    .with_color(theme.titlebar.offline_icon.color)
-                    .constrained()
-                    .with_width(theme.titlebar.offline_icon.width)
-                    .aligned()
-                    .contained()
-                    .with_style(theme.titlebar.offline_icon.container)
-                    .into_any(),
+                div()
+                    .id("disconnected")
+                    .bg(gpui::red()) // todo!() @nate
+                    .child(IconElement::new(Icon::Disconnected))
+                    .tooltip(|cx| Tooltip::text("Disconnected", cx))
+                    .into_any_element(),
             ),
             client::Status::UpgradeRequired => {
                 let auto_updater = auto_update::AutoUpdater::get(cx);

crates/collab_ui/src/collab_ui.rs 🔗

@@ -7,27 +7,22 @@ pub mod notification_panel;
 pub mod notifications;
 mod panel_settings;
 
+use std::{rc::Rc, sync::Arc};
+
 use call::{report_call_event_for_room, ActiveCall, Room};
+pub use collab_panel::CollabPanel;
+pub use collab_titlebar_item::CollabTitlebarItem;
 use feature_flags::{ChannelsAlpha, FeatureFlagAppExt};
 use gpui::{
-    actions,
-    elements::{ContainerStyle, Empty, Image},
-    geometry::{
-        rect::RectF,
-        vector::{vec2f, Vector2F},
-    },
-    platform::{Screen, WindowBounds, WindowKind, WindowOptions},
-    AnyElement, AppContext, Element, ImageData, Task,
+    actions, point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, Task, WindowBounds,
+    WindowKind, WindowOptions,
 };
-use std::{rc::Rc, sync::Arc};
-use theme::AvatarStyle;
-use util::ResultExt;
-use workspace::AppState;
-
-pub use collab_titlebar_item::CollabTitlebarItem;
 pub use panel_settings::{
     ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings,
 };
+use settings::Settings;
+use util::ResultExt;
+use workspace::AppState;
 
 actions!(
     collab,
@@ -35,19 +30,21 @@ actions!(
 );
 
 pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
-    settings::register::<CollaborationPanelSettings>(cx);
-    settings::register::<ChatPanelSettings>(cx);
-    settings::register::<NotificationPanelSettings>(cx);
+    CollaborationPanelSettings::register(cx);
+    ChatPanelSettings::register(cx);
+    NotificationPanelSettings::register(cx);
 
     vcs_menu::init(cx);
     collab_titlebar_item::init(cx);
     collab_panel::init(cx);
+    channel_view::init(cx);
     chat_panel::init(cx);
+    notification_panel::init(cx);
     notifications::init(&app_state, cx);
 
-    cx.add_global_action(toggle_screen_sharing);
-    cx.add_global_action(toggle_mute);
-    cx.add_global_action(toggle_deafen);
+    // cx.add_global_action(toggle_screen_sharing);
+    // cx.add_global_action(toggle_mute);
+    // cx.add_global_action(toggle_deafen);
 }
 
 pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
@@ -107,58 +104,63 @@ pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) {
 }
 
 fn notification_window_options(
-    screen: Rc<dyn Screen>,
-    window_size: Vector2F,
-) -> WindowOptions<'static> {
-    const NOTIFICATION_PADDING: f32 = 16.;
+    screen: Rc<dyn PlatformDisplay>,
+    window_size: Size<Pixels>,
+) -> WindowOptions {
+    let notification_margin_width = GlobalPixels::from(16.);
+    let notification_margin_height = GlobalPixels::from(-0.) - GlobalPixels::from(48.);
+
+    let screen_bounds = screen.bounds();
+    let size: Size<GlobalPixels> = window_size.into();
 
-    let screen_bounds = screen.content_bounds();
+    // todo!() use content bounds instead of screen.bounds and get rid of magics in point's 2nd argument.
+    let bounds = gpui::Bounds::<GlobalPixels> {
+        origin: screen_bounds.upper_right()
+            - point(
+                size.width + notification_margin_width,
+                notification_margin_height,
+            ),
+        size: window_size.into(),
+    };
     WindowOptions {
-        bounds: WindowBounds::Fixed(RectF::new(
-            screen_bounds.upper_right()
-                + vec2f(
-                    -NOTIFICATION_PADDING - window_size.x(),
-                    NOTIFICATION_PADDING,
-                ),
-            window_size,
-        )),
+        bounds: WindowBounds::Fixed(bounds),
         titlebar: None,
         center: false,
         focus: false,
         show: true,
         kind: WindowKind::PopUp,
         is_movable: false,
-        screen: Some(screen),
+        display_id: Some(screen.id()),
     }
 }
 
-fn render_avatar<T: 'static>(
-    avatar: Option<Arc<ImageData>>,
-    avatar_style: &AvatarStyle,
-    container: ContainerStyle,
-) -> AnyElement<T> {
-    avatar
-        .map(|avatar| {
-            Image::from_data(avatar)
-                .with_style(avatar_style.image)
-                .aligned()
-                .contained()
-                .with_corner_radius(avatar_style.outer_corner_radius)
-                .constrained()
-                .with_width(avatar_style.outer_width)
-                .with_height(avatar_style.outer_width)
-                .into_any()
-        })
-        .unwrap_or_else(|| {
-            Empty::new()
-                .constrained()
-                .with_width(avatar_style.outer_width)
-                .into_any()
-        })
-        .contained()
-        .with_style(container)
-        .into_any()
-}
+// fn render_avatar<T: 'static>(
+//     avatar: Option<Arc<ImageData>>,
+//     avatar_style: &AvatarStyle,
+//     container: ContainerStyle,
+// ) -> AnyElement<T> {
+//     avatar
+//         .map(|avatar| {
+//             Image::from_data(avatar)
+//                 .with_style(avatar_style.image)
+//                 .aligned()
+//                 .contained()
+//                 .with_corner_radius(avatar_style.outer_corner_radius)
+//                 .constrained()
+//                 .with_width(avatar_style.outer_width)
+//                 .with_height(avatar_style.outer_width)
+//                 .into_any()
+//         })
+//         .unwrap_or_else(|| {
+//             Empty::new()
+//                 .constrained()
+//                 .with_width(avatar_style.outer_width)
+//                 .into_any()
+//         })
+//         .contained()
+//         .with_style(container)
+//         .into_any()
+// }
 
 fn is_channels_feature_enabled(cx: &gpui::WindowContext<'_>) -> bool {
     cx.is_staff() || cx.has_flag::<ChannelsAlpha>()

crates/collab_ui/src/face_pile.rs 🔗

@@ -1,113 +1,28 @@
-use std::ops::Range;
+use gpui::{div, AnyElement, IntoElement, ParentElement, RenderOnce, Styled, WindowContext};
+use smallvec::SmallVec;
 
-use gpui::{
-    geometry::{
-        rect::RectF,
-        vector::{vec2f, Vector2F},
-    },
-    json::ToJson,
-    serde_json::{self, json},
-    AnyElement, Axis, Element, View, ViewContext,
-};
-
-pub(crate) struct FacePile<V: View> {
-    overlap: f32,
-    faces: Vec<AnyElement<V>>,
+#[derive(Default, IntoElement)]
+pub struct FacePile {
+    pub faces: SmallVec<[AnyElement; 2]>,
 }
 
-impl<V: View> FacePile<V> {
-    pub fn new(overlap: f32) -> Self {
-        Self {
-            overlap,
-            faces: Vec::new(),
-        }
-    }
-}
-
-impl<V: View> Element<V> for FacePile<V> {
-    type LayoutState = ();
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        constraint: gpui::SizeConstraint,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> (Vector2F, Self::LayoutState) {
-        debug_assert!(constraint.max_along(Axis::Horizontal) == f32::INFINITY);
-
-        let mut width = 0.;
-        let mut max_height = 0.;
-        for face in &mut self.faces {
-            let layout = face.layout(constraint, view, cx);
-            width += layout.x();
-            max_height = f32::max(max_height, layout.y());
-        }
-        width -= self.overlap * self.faces.len().saturating_sub(1) as f32;
-
-        (
-            Vector2F::new(width, max_height.clamp(1., constraint.max.y())),
-            (),
-        )
-    }
-
-    fn paint(
-        &mut self,
-        bounds: RectF,
-        visible_bounds: RectF,
-        _layout: &mut Self::LayoutState,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> Self::PaintState {
-        let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
-
-        let origin_y = bounds.upper_right().y();
-        let mut origin_x = bounds.upper_right().x();
-
-        for face in self.faces.iter_mut().rev() {
-            let size = face.size();
-            origin_x -= size.x();
-            let origin_y = origin_y + (bounds.height() - size.y()) / 2.0;
-
-            cx.scene().push_layer(None);
-            face.paint(vec2f(origin_x, origin_y), visible_bounds, view, cx);
-            cx.scene().pop_layer();
-            origin_x += self.overlap;
-        }
-
-        ()
-    }
-
-    fn rect_for_text_range(
-        &self,
-        _: Range<usize>,
-        _: RectF,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        _: &V,
-        _: &ViewContext<V>,
-    ) -> Option<RectF> {
-        None
-    }
-
-    fn debug(
-        &self,
-        bounds: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        _: &V,
-        _: &ViewContext<V>,
-    ) -> serde_json::Value {
-        json!({
-            "type": "FacePile",
-            "bounds": bounds.to_json()
-        })
+impl RenderOnce for FacePile {
+    fn render(self, _: &mut WindowContext) -> impl IntoElement {
+        let player_count = self.faces.len();
+        let player_list = self.faces.into_iter().enumerate().map(|(ix, player)| {
+            let isnt_last = ix < player_count - 1;
+
+            div()
+                .z_index((player_count - ix) as u8)
+                .when(isnt_last, |div| div.neg_mr_1())
+                .child(player)
+        });
+        div().p_1().flex().items_center().children(player_list)
     }
 }
 
-impl<V: View> Extend<AnyElement<V>> for FacePile<V> {
-    fn extend<T: IntoIterator<Item = AnyElement<V>>>(&mut self, children: T) {
-        self.faces.extend(children);
+impl ParentElement for FacePile {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
+        &mut self.faces
     }
 }

crates/collab_ui/src/notification_panel.rs 🔗

@@ -1,4 +1,4 @@
-use crate::{chat_panel::ChatPanel, render_avatar, NotificationPanelSettings};
+use crate::{chat_panel::ChatPanel, NotificationPanelSettings};
 use anyhow::Result;
 use channel::ChannelStore;
 use client::{Client, Notification, User, UserStore};
@@ -6,23 +6,23 @@ use collections::HashMap;
 use db::kvp::KEY_VALUE_STORE;
 use futures::StreamExt;
 use gpui::{
-    actions,
-    elements::*,
-    platform::{CursorStyle, MouseButton},
-    serde_json, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelHandle, Task, View,
-    ViewContext, ViewHandle, WeakViewHandle, WindowContext,
+    actions, div, img, list, px, serde_json, AnyElement, AppContext, AsyncWindowContext,
+    CursorStyle, DismissEvent, Element, EventEmitter, FocusHandle, FocusableView,
+    InteractiveElement, IntoElement, ListAlignment, ListScrollEvent, ListState, Model,
+    ParentElement, Render, StatefulInteractiveElement, Styled, Task, View, ViewContext,
+    VisualContext, WeakView, WindowContext,
 };
 use notifications::{NotificationEntry, NotificationEvent, NotificationStore};
 use project::Fs;
 use rpc::proto;
 use serde::{Deserialize, Serialize};
-use settings::SettingsStore;
+use settings::{Settings, SettingsStore};
 use std::{sync::Arc, time::Duration};
-use theme::{ui, Theme};
 use time::{OffsetDateTime, UtcOffset};
+use ui::{h_stack, prelude::*, v_stack, Avatar, Button, Icon, IconButton, IconElement, Label};
 use util::{ResultExt, TryFutureExt};
 use workspace::{
-    dock::{DockPosition, Panel},
+    dock::{DockPosition, Panel, PanelEvent},
     Workspace,
 };
 
@@ -33,25 +33,25 @@ const NOTIFICATION_PANEL_KEY: &'static str = "NotificationPanel";
 
 pub struct NotificationPanel {
     client: Arc<Client>,
-    user_store: ModelHandle<UserStore>,
-    channel_store: ModelHandle<ChannelStore>,
-    notification_store: ModelHandle<NotificationStore>,
+    user_store: Model<UserStore>,
+    channel_store: Model<ChannelStore>,
+    notification_store: Model<NotificationStore>,
     fs: Arc<dyn Fs>,
-    width: Option<f32>,
+    width: Option<Pixels>,
     active: bool,
-    notification_list: ListState<Self>,
+    notification_list: ListState,
     pending_serialization: Task<Option<()>>,
     subscriptions: Vec<gpui::Subscription>,
-    workspace: WeakViewHandle<Workspace>,
+    workspace: WeakView<Workspace>,
     current_notification_toast: Option<(u64, Task<()>)>,
     local_timezone: UtcOffset,
-    has_focus: bool,
+    focus_handle: FocusHandle,
     mark_as_read_tasks: HashMap<u64, Task<Result<()>>>,
 }
 
 #[derive(Serialize, Deserialize)]
 struct SerializedNotificationPanel {
-    width: Option<f32>,
+    width: Option<Pixels>,
 }
 
 #[derive(Debug)]
@@ -71,16 +71,23 @@ pub struct NotificationPresenter {
 
 actions!(notification_panel, [ToggleFocus]);
 
-pub fn init(_cx: &mut AppContext) {}
+pub fn init(cx: &mut AppContext) {
+    cx.observe_new_views(|workspace: &mut Workspace, _| {
+        workspace.register_action(|workspace, _: &ToggleFocus, cx| {
+            workspace.toggle_panel_focus::<NotificationPanel>(cx);
+        });
+    })
+    .detach();
+}
 
 impl NotificationPanel {
-    pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> ViewHandle<Self> {
+    pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
         let fs = workspace.app_state().fs.clone();
         let client = workspace.app_state().client.clone();
         let user_store = workspace.app_state().user_store.clone();
         let workspace_handle = workspace.weak_handle();
 
-        cx.add_view(|cx| {
+        cx.new_view(|cx: &mut ViewContext<Self>| {
             let mut status = client.status();
             cx.spawn(|this, mut cx| async move {
                 while let Some(_) = status.next().await {
@@ -96,33 +103,39 @@ impl NotificationPanel {
             })
             .detach();
 
-            let mut notification_list =
-                ListState::<Self>::new(0, Orientation::Top, 1000., move |this, ix, cx| {
-                    this.render_notification(ix, cx)
-                        .unwrap_or_else(|| Empty::new().into_any())
+            let view = cx.view().downgrade();
+            let notification_list =
+                ListState::new(0, ListAlignment::Top, px(1000.), move |ix, cx| {
+                    view.upgrade()
+                        .and_then(|view| {
+                            view.update(cx, |this, cx| this.render_notification(ix, cx))
+                        })
+                        .unwrap_or_else(|| div().into_any())
                 });
-            notification_list.set_scroll_handler(|visible_range, count, this, cx| {
-                if count.saturating_sub(visible_range.end) < LOADING_THRESHOLD {
-                    if let Some(task) = this
-                        .notification_store
-                        .update(cx, |store, cx| store.load_more_notifications(false, cx))
-                    {
-                        task.detach();
+            notification_list.set_scroll_handler(cx.listener(
+                |this, event: &ListScrollEvent, cx| {
+                    if event.count.saturating_sub(event.visible_range.end) < LOADING_THRESHOLD {
+                        if let Some(task) = this
+                            .notification_store
+                            .update(cx, |store, cx| store.load_more_notifications(false, cx))
+                        {
+                            task.detach();
+                        }
                     }
-                }
-            });
+                },
+            ));
 
             let mut this = Self {
                 fs,
                 client,
                 user_store,
-                local_timezone: cx.platform().local_timezone(),
+                local_timezone: cx.local_timezone(),
                 channel_store: ChannelStore::global(cx),
                 notification_store: NotificationStore::global(cx),
                 notification_list,
                 pending_serialization: Task::ready(None),
                 workspace: workspace_handle,
-                has_focus: false,
+                focus_handle: cx.focus_handle(),
                 current_notification_toast: None,
                 subscriptions: Vec::new(),
                 active: false,
@@ -134,7 +147,7 @@ impl NotificationPanel {
             this.subscriptions.extend([
                 cx.observe(&this.notification_store, |_, _, cx| cx.notify()),
                 cx.subscribe(&this.notification_store, Self::on_notification_event),
-                cx.observe_global::<SettingsStore, _>(move |this: &mut Self, cx| {
+                cx.observe_global::<SettingsStore>(move |this: &mut Self, cx| {
                     let new_dock_position = this.position(cx);
                     if new_dock_position != old_dock_position {
                         old_dock_position = new_dock_position;
@@ -148,12 +161,12 @@ impl NotificationPanel {
     }
 
     pub fn load(
-        workspace: WeakViewHandle<Workspace>,
-        cx: AsyncAppContext,
-    ) -> Task<Result<ViewHandle<Self>>> {
+        workspace: WeakView<Workspace>,
+        cx: AsyncWindowContext,
+    ) -> Task<Result<View<Self>>> {
         cx.spawn(|mut cx| async move {
             let serialized_panel = if let Some(panel) = cx
-                .background()
+                .background_executor()
                 .spawn(async move { KEY_VALUE_STORE.read_kvp(NOTIFICATION_PANEL_KEY) })
                 .await
                 .log_err()
@@ -179,7 +192,7 @@ impl NotificationPanel {
 
     fn serialize(&mut self, cx: &mut ViewContext<Self>) {
         let width = self.width;
-        self.pending_serialization = cx.background().spawn(
+        self.pending_serialization = cx.background_executor().spawn(
             async move {
                 KEY_VALUE_STORE
                     .write_kvp(
@@ -193,11 +206,7 @@ impl NotificationPanel {
         );
     }
 
-    fn render_notification(
-        &mut self,
-        ix: usize,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<AnyElement<Self>> {
+    fn render_notification(&mut self, ix: usize, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
         let entry = self.notification_store.read(cx).notification_at(ix)?;
         let notification_id = entry.id;
         let now = OffsetDateTime::now_utc();
@@ -210,136 +219,99 @@ impl NotificationPanel {
             ..
         } = self.present_notification(entry, cx)?;
 
-        let theme = theme::current(cx);
-        let style = &theme.notification_panel;
         let response = entry.response;
         let notification = entry.notification.clone();
 
-        let message_style = if entry.is_read {
-            style.read_text.clone()
-        } else {
-            style.unread_text.clone()
-        };
-
         if self.active && !entry.is_read {
             self.did_render_notification(notification_id, &notification, cx);
         }
 
-        enum Decline {}
-        enum Accept {}
-
         Some(
-            MouseEventHandler::new::<NotificationEntry, _>(ix, cx, |_, cx| {
-                let container = message_style.container;
-
-                Flex::row()
-                    .with_children(actor.map(|actor| {
-                        render_avatar(actor.avatar.clone(), &style.avatar, style.avatar_container)
-                    }))
-                    .with_child(
-                        Flex::column()
-                            .with_child(Text::new(text, message_style.text.clone()))
-                            .with_child(
-                                Flex::row()
-                                    .with_child(
-                                        Label::new(
-                                            format_timestamp(timestamp, now, self.local_timezone),
-                                            style.timestamp.text.clone(),
-                                        )
-                                        .contained()
-                                        .with_style(style.timestamp.container),
+            div()
+                .id(ix)
+                .flex()
+                .flex_row()
+                .size_full()
+                .px_2()
+                .py_1()
+                .gap_2()
+                .when(can_navigate, |el| {
+                    el.cursor(CursorStyle::PointingHand).on_click({
+                        let notification = notification.clone();
+                        cx.listener(move |this, _, cx| {
+                            this.did_click_notification(&notification, cx)
+                        })
+                    })
+                })
+                .children(actor.map(|actor| {
+                    img(actor.avatar_uri.clone())
+                        .flex_none()
+                        .w_8()
+                        .h_8()
+                        .rounded_full()
+                }))
+                .child(
+                    v_stack()
+                        .gap_1()
+                        .size_full()
+                        .overflow_hidden()
+                        .child(Label::new(text.clone()))
+                        .child(
+                            h_stack()
+                                .child(
+                                    Label::new(format_timestamp(
+                                        timestamp,
+                                        now,
+                                        self.local_timezone,
+                                    ))
+                                    .color(Color::Muted),
+                                )
+                                .children(if let Some(is_accepted) = response {
+                                    Some(div().flex().flex_grow().justify_end().child(Label::new(
+                                        if is_accepted {
+                                            "You accepted"
+                                        } else {
+                                            "You declined"
+                                        },
+                                    )))
+                                } else if needs_response {
+                                    Some(
+                                        h_stack()
+                                            .flex_grow()
+                                            .justify_end()
+                                            .child(Button::new("decline", "Decline").on_click({
+                                                let notification = notification.clone();
+                                                let view = cx.view().clone();
+                                                move |_, cx| {
+                                                    view.update(cx, |this, cx| {
+                                                        this.respond_to_notification(
+                                                            notification.clone(),
+                                                            false,
+                                                            cx,
+                                                        )
+                                                    });
+                                                }
+                                            }))
+                                            .child(Button::new("accept", "Accept").on_click({
+                                                let notification = notification.clone();
+                                                let view = cx.view().clone();
+                                                move |_, cx| {
+                                                    view.update(cx, |this, cx| {
+                                                        this.respond_to_notification(
+                                                            notification.clone(),
+                                                            true,
+                                                            cx,
+                                                        )
+                                                    });
+                                                }
+                                            })),
                                     )
-                                    .with_children(if let Some(is_accepted) = response {
-                                        Some(
-                                            Label::new(
-                                                if is_accepted {
-                                                    "You accepted"
-                                                } else {
-                                                    "You declined"
-                                                },
-                                                style.read_text.text.clone(),
-                                            )
-                                            .flex_float()
-                                            .into_any(),
-                                        )
-                                    } else if needs_response {
-                                        Some(
-                                            Flex::row()
-                                                .with_children([
-                                                    MouseEventHandler::new::<Decline, _>(
-                                                        ix,
-                                                        cx,
-                                                        |state, _| {
-                                                            let button =
-                                                                style.button.style_for(state);
-                                                            Label::new(
-                                                                "Decline",
-                                                                button.text.clone(),
-                                                            )
-                                                            .contained()
-                                                            .with_style(button.container)
-                                                        },
-                                                    )
-                                                    .with_cursor_style(CursorStyle::PointingHand)
-                                                    .on_click(MouseButton::Left, {
-                                                        let notification = notification.clone();
-                                                        move |_, view, cx| {
-                                                            view.respond_to_notification(
-                                                                notification.clone(),
-                                                                false,
-                                                                cx,
-                                                            );
-                                                        }
-                                                    }),
-                                                    MouseEventHandler::new::<Accept, _>(
-                                                        ix,
-                                                        cx,
-                                                        |state, _| {
-                                                            let button =
-                                                                style.button.style_for(state);
-                                                            Label::new(
-                                                                "Accept",
-                                                                button.text.clone(),
-                                                            )
-                                                            .contained()
-                                                            .with_style(button.container)
-                                                        },
-                                                    )
-                                                    .with_cursor_style(CursorStyle::PointingHand)
-                                                    .on_click(MouseButton::Left, {
-                                                        let notification = notification.clone();
-                                                        move |_, view, cx| {
-                                                            view.respond_to_notification(
-                                                                notification.clone(),
-                                                                true,
-                                                                cx,
-                                                            );
-                                                        }
-                                                    }),
-                                                ])
-                                                .flex_float()
-                                                .into_any(),
-                                        )
-                                    } else {
-                                        None
-                                    }),
-                            )
-                            .flex(1.0, true),
-                    )
-                    .contained()
-                    .with_style(container)
-                    .into_any()
-            })
-            .with_cursor_style(if can_navigate {
-                CursorStyle::PointingHand
-            } else {
-                CursorStyle::default()
-            })
-            .on_click(MouseButton::Left, {
-                let notification = notification.clone();
-                move |_, this, cx| this.did_click_notification(&notification, cx)
-            })
-            .into_any(),
+                                } else {
+                                    None
+                                }),
+                        ),
+                )
+                .into_any(),
         )
     }
 
@@ -432,7 +404,7 @@ impl NotificationPanel {
                 .or_insert_with(|| {
                     let client = self.client.clone();
                     cx.spawn(|this, mut cx| async move {
-                        cx.background().timer(MARK_AS_READ_DELAY).await;
+                        cx.background_executor().timer(MARK_AS_READ_DELAY).await;
                         client
                             .request(proto::MarkNotificationRead { notification_id })
                             .await?;
@@ -452,8 +424,8 @@ impl NotificationPanel {
             ..
         } = notification.clone()
         {
-            if let Some(workspace) = self.workspace.upgrade(cx) {
-                cx.app_context().defer(move |cx| {
+            if let Some(workspace) = self.workspace.upgrade() {
+                cx.window_context().defer(move |cx| {
                     workspace.update(cx, |workspace, cx| {
                         if let Some(panel) = workspace.focus_panel::<ChatPanel>(cx) {
                             panel.update(cx, |panel, cx| {
@@ -468,73 +440,27 @@ impl NotificationPanel {
         }
     }
 
-    fn is_showing_notification(&self, notification: &Notification, cx: &AppContext) -> bool {
+    fn is_showing_notification(&self, notification: &Notification, cx: &ViewContext<Self>) -> bool {
         if let Notification::ChannelMessageMention { channel_id, .. } = &notification {
-            if let Some(workspace) = self.workspace.upgrade(cx) {
-                return workspace
-                    .read_with(cx, |workspace, cx| {
-                        if let Some(panel) = workspace.panel::<ChatPanel>(cx) {
-                            return panel.read_with(cx, |panel, cx| {
-                                panel.is_scrolled_to_bottom()
-                                    && panel.active_chat().map_or(false, |chat| {
-                                        chat.read(cx).channel_id == *channel_id
-                                    })
-                            });
-                        }
-                        false
-                    })
-                    .unwrap_or_default();
+            if let Some(workspace) = self.workspace.upgrade() {
+                return if let Some(panel) = workspace.read(cx).panel::<ChatPanel>(cx) {
+                    let panel = panel.read(cx);
+                    panel.is_scrolled_to_bottom()
+                        && panel
+                            .active_chat()
+                            .map_or(false, |chat| chat.read(cx).channel_id == *channel_id)
+                } else {
+                    false
+                };
             }
         }
 
         false
     }
 
-    fn render_sign_in_prompt(
-        &self,
-        theme: &Arc<Theme>,
-        cx: &mut ViewContext<Self>,
-    ) -> AnyElement<Self> {
-        enum SignInPromptLabel {}
-
-        MouseEventHandler::new::<SignInPromptLabel, _>(0, cx, |mouse_state, _| {
-            Label::new(
-                "Sign in to view your notifications".to_string(),
-                theme
-                    .chat_panel
-                    .sign_in_prompt
-                    .style_for(mouse_state)
-                    .clone(),
-            )
-        })
-        .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, move |_, this, cx| {
-            let client = this.client.clone();
-            cx.spawn(|_, cx| async move {
-                client.authenticate_and_connect(true, &cx).log_err().await;
-            })
-            .detach();
-        })
-        .aligned()
-        .into_any()
-    }
-
-    fn render_empty_state(
-        &self,
-        theme: &Arc<Theme>,
-        _cx: &mut ViewContext<Self>,
-    ) -> AnyElement<Self> {
-        Label::new(
-            "You have no notifications".to_string(),
-            theme.chat_panel.sign_in_prompt.default.clone(),
-        )
-        .aligned()
-        .into_any()
-    }
-
     fn on_notification_event(
         &mut self,
-        _: ModelHandle<NotificationStore>,
+        _: Model<NotificationStore>,
         event: &NotificationEvent,
         cx: &mut ViewContext<Self>,
     ) {
@@ -566,7 +492,7 @@ impl NotificationPanel {
         self.current_notification_toast = Some((
             notification_id,
             cx.spawn(|this, mut cx| async move {
-                cx.background().timer(TOAST_DURATION).await;
+                cx.background_executor().timer(TOAST_DURATION).await;
                 this.update(&mut cx, |this, cx| this.remove_toast(notification_id, cx))
                     .ok();
             }),
@@ -576,8 +502,8 @@ impl NotificationPanel {
             .update(cx, |workspace, cx| {
                 workspace.dismiss_notification::<NotificationToast>(0, cx);
                 workspace.show_notification(0, cx, |cx| {
-                    let workspace = cx.weak_handle();
-                    cx.add_view(|_| NotificationToast {
+                    let workspace = cx.view().downgrade();
+                    cx.new_view(|_| NotificationToast {
                         notification_id,
                         actor,
                         text,
@@ -613,62 +539,90 @@ impl NotificationPanel {
     }
 }
 
-impl Entity for NotificationPanel {
-    type Event = Event;
-}
-
-impl View for NotificationPanel {
-    fn ui_name() -> &'static str {
-        "NotificationPanel"
+impl Render for NotificationPanel {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        v_stack()
+            .size_full()
+            .child(
+                h_stack()
+                    .justify_between()
+                    .px_2()
+                    .py_1()
+                    // Match the height of the tab bar so they line up.
+                    .h(rems(ui::Tab::HEIGHT_IN_REMS))
+                    .border_b_1()
+                    .border_color(cx.theme().colors().border)
+                    .child(Label::new("Notifications"))
+                    .child(IconElement::new(Icon::Envelope)),
+            )
+            .map(|this| {
+                if self.client.user_id().is_none() {
+                    this.child(
+                        v_stack()
+                            .gap_2()
+                            .p_4()
+                            .child(
+                                Button::new("sign_in_prompt_button", "Sign in")
+                                    .icon_color(Color::Muted)
+                                    .icon(Icon::Github)
+                                    .icon_position(IconPosition::Start)
+                                    .style(ButtonStyle::Filled)
+                                    .full_width()
+                                    .on_click({
+                                        let client = self.client.clone();
+                                        move |_, cx| {
+                                            let client = client.clone();
+                                            cx.spawn(move |cx| async move {
+                                                client
+                                                    .authenticate_and_connect(true, &cx)
+                                                    .log_err()
+                                                    .await;
+                                            })
+                                            .detach()
+                                        }
+                                    }),
+                            )
+                            .child(
+                                div().flex().w_full().items_center().child(
+                                    Label::new("Sign in to view notifications.")
+                                        .color(Color::Muted)
+                                        .size(LabelSize::Small),
+                                ),
+                            ),
+                    )
+                } else if self.notification_list.item_count() == 0 {
+                    this.child(
+                        v_stack().p_4().child(
+                            div().flex().w_full().items_center().child(
+                                Label::new("You have no notifications.")
+                                    .color(Color::Muted)
+                                    .size(LabelSize::Small),
+                            ),
+                        ),
+                    )
+                } else {
+                    this.child(list(self.notification_list.clone()).size_full())
+                }
+            })
     }
+}
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = theme::current(cx);
-        let style = &theme.notification_panel;
-        let element = if self.client.user_id().is_none() {
-            self.render_sign_in_prompt(&theme, cx)
-        } else if self.notification_list.item_count() == 0 {
-            self.render_empty_state(&theme, cx)
-        } else {
-            Flex::column()
-                .with_child(
-                    Flex::row()
-                        .with_child(Label::new("Notifications", style.title.text.clone()))
-                        .with_child(ui::svg(&style.title_icon).flex_float())
-                        .align_children_center()
-                        .contained()
-                        .with_style(style.title.container)
-                        .constrained()
-                        .with_height(style.title_height),
-                )
-                .with_child(
-                    List::new(self.notification_list.clone())
-                        .contained()
-                        .with_style(style.list)
-                        .flex(1., true),
-                )
-                .into_any()
-        };
-        element
-            .contained()
-            .with_style(style.container)
-            .constrained()
-            .with_min_width(150.)
-            .into_any()
+impl FocusableView for NotificationPanel {
+    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
+        self.focus_handle.clone()
     }
+}
 
-    fn focus_in(&mut self, _: AnyViewHandle, _: &mut ViewContext<Self>) {
-        self.has_focus = true;
-    }
+impl EventEmitter<Event> for NotificationPanel {}
+impl EventEmitter<PanelEvent> for NotificationPanel {}
 
-    fn focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext<Self>) {
-        self.has_focus = false;
+impl Panel for NotificationPanel {
+    fn persistent_name() -> &'static str {
+        "NotificationPanel"
     }
-}
 
-impl Panel for NotificationPanel {
     fn position(&self, cx: &gpui::WindowContext) -> DockPosition {
-        settings::get::<NotificationPanelSettings>(cx).dock
+        NotificationPanelSettings::get_global(cx).dock
     }
 
     fn position_is_valid(&self, position: DockPosition) -> bool {
@@ -683,12 +637,12 @@ impl Panel for NotificationPanel {
         );
     }
 
-    fn size(&self, cx: &gpui::WindowContext) -> f32 {
+    fn size(&self, cx: &gpui::WindowContext) -> Pixels {
         self.width
-            .unwrap_or_else(|| settings::get::<NotificationPanelSettings>(cx).default_width)
+            .unwrap_or_else(|| NotificationPanelSettings::get_global(cx).default_width)
     }
 
-    fn set_size(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>) {
+    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
         self.width = size;
         self.serialize(cx);
         cx.notify();
@@ -701,17 +655,14 @@ impl Panel for NotificationPanel {
         }
     }
 
-    fn icon_path(&self, cx: &gpui::WindowContext) -> Option<&'static str> {
-        (settings::get::<NotificationPanelSettings>(cx).button
+    fn icon(&self, cx: &gpui::WindowContext) -> Option<Icon> {
+        (NotificationPanelSettings::get_global(cx).button
             && self.notification_store.read(cx).notification_count() > 0)
-            .then(|| "icons/bell.svg")
+            .then(|| Icon::Bell)
     }
 
-    fn icon_tooltip(&self) -> (String, Option<Box<dyn gpui::Action>>) {
-        (
-            "Notification Panel".to_string(),
-            Some(Box::new(ToggleFocus)),
-        )
+    fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
+        Some("Notification Panel")
     }
 
     fn icon_label(&self, cx: &WindowContext) -> Option<String> {
@@ -723,20 +674,8 @@ impl Panel for NotificationPanel {
         }
     }
 
-    fn should_change_position_on_event(event: &Self::Event) -> bool {
-        matches!(event, Event::DockPositionChanged)
-    }
-
-    fn should_close_on_event(event: &Self::Event) -> bool {
-        matches!(event, Event::Dismissed)
-    }
-
-    fn has_focus(&self, _cx: &gpui::WindowContext) -> bool {
-        self.has_focus
-    }
-
-    fn is_focus_event(event: &Self::Event) -> bool {
-        matches!(event, Event::Focus)
+    fn toggle_action(&self) -> Box<dyn gpui::Action> {
+        Box::new(ToggleFocus)
     }
 }
 
@@ -744,18 +683,14 @@ pub struct NotificationToast {
     notification_id: u64,
     actor: Option<Arc<User>>,
     text: String,
-    workspace: WeakViewHandle<Workspace>,
-}
-
-pub enum ToastEvent {
-    Dismiss,
+    workspace: WeakView<Workspace>,
 }
 
 impl NotificationToast {
-    fn focus_notification_panel(&self, cx: &mut AppContext) {
+    fn focus_notification_panel(&self, cx: &mut ViewContext<Self>) {
         let workspace = self.workspace.clone();
         let notification_id = self.notification_id;
-        cx.defer(move |cx| {
+        cx.window_context().defer(move |cx| {
             workspace
                 .update(cx, |workspace, cx| {
                     if let Some(panel) = workspace.focus_panel::<NotificationPanel>(cx) {
@@ -772,91 +707,27 @@ impl NotificationToast {
     }
 }
 
-impl Entity for NotificationToast {
-    type Event = ToastEvent;
-}
-
-impl View for NotificationToast {
-    fn ui_name() -> &'static str {
-        "ContactNotification"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+impl Render for NotificationToast {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
         let user = self.actor.clone();
-        let theme = theme::current(cx).clone();
-        let theme = &theme.contact_notification;
-
-        MouseEventHandler::new::<Self, _>(0, cx, |_, cx| {
-            Flex::row()
-                .with_children(user.and_then(|user| {
-                    Some(
-                        Image::from_data(user.avatar.clone()?)
-                            .with_style(theme.header_avatar)
-                            .aligned()
-                            .constrained()
-                            .with_height(
-                                cx.font_cache()
-                                    .line_height(theme.header_message.text.font_size),
-                            )
-                            .aligned()
-                            .top(),
-                    )
-                }))
-                .with_child(
-                    Text::new(self.text.clone(), theme.header_message.text.clone())
-                        .contained()
-                        .with_style(theme.header_message.container)
-                        .aligned()
-                        .top()
-                        .left()
-                        .flex(1., true),
-                )
-                .with_child(
-                    MouseEventHandler::new::<ToastEvent, _>(0, cx, |state, _| {
-                        let style = theme.dismiss_button.style_for(state);
-                        Svg::new("icons/x.svg")
-                            .with_color(style.color)
-                            .constrained()
-                            .with_width(style.icon_width)
-                            .aligned()
-                            .contained()
-                            .with_style(style.container)
-                            .constrained()
-                            .with_width(style.button_width)
-                            .with_height(style.button_width)
-                    })
-                    .with_cursor_style(CursorStyle::PointingHand)
-                    .with_padding(Padding::uniform(5.))
-                    .on_click(MouseButton::Left, move |_, _, cx| {
-                        cx.emit(ToastEvent::Dismiss)
-                    })
-                    .aligned()
-                    .constrained()
-                    .with_height(
-                        cx.font_cache()
-                            .line_height(theme.header_message.text.font_size),
-                    )
-                    .aligned()
-                    .top()
-                    .flex_float(),
-                )
-                .contained()
-        })
-        .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, move |_, this, cx| {
-            this.focus_notification_panel(cx);
-            cx.emit(ToastEvent::Dismiss);
-        })
-        .into_any()
-    }
-}
 
-impl workspace::notifications::Notification for NotificationToast {
-    fn should_dismiss_notification_on_event(&self, event: &<Self as Entity>::Event) -> bool {
-        matches!(event, ToastEvent::Dismiss)
+        h_stack()
+            .id("notification_panel_toast")
+            .children(user.map(|user| Avatar::new(user.avatar_uri.clone())))
+            .child(Label::new(self.text.clone()))
+            .child(
+                IconButton::new("close", Icon::Close)
+                    .on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))),
+            )
+            .on_click(cx.listener(|this, _, cx| {
+                this.focus_notification_panel(cx);
+                cx.emit(DismissEvent);
+            }))
     }
 }
 
+impl EventEmitter<DismissEvent> for NotificationToast {}
+
 fn format_timestamp(
     mut timestamp: OffsetDateTime,
     mut now: OffsetDateTime,

crates/collab_ui/src/notifications/incoming_call_notification.rs 🔗

@@ -1,14 +1,15 @@
 use crate::notification_window_options;
 use call::{ActiveCall, IncomingCall};
-use client::proto;
 use futures::StreamExt;
 use gpui::{
-    elements::*,
-    geometry::vector::vec2f,
-    platform::{CursorStyle, MouseButton},
-    AnyElement, AppContext, Entity, View, ViewContext, WindowHandle,
+    img, px, AppContext, ParentElement, Render, RenderOnce, Styled, ViewContext,
+    VisualContext as _, WindowHandle,
 };
+use settings::Settings;
 use std::sync::{Arc, Weak};
+use theme::ThemeSettings;
+use ui::prelude::*;
+use ui::{h_stack, v_stack, Button, Label};
 use util::ResultExt;
 use workspace::AppState;
 
@@ -19,21 +20,33 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
         let mut notification_windows: Vec<WindowHandle<IncomingCallNotification>> = Vec::new();
         while let Some(incoming_call) = incoming_call.next().await {
             for window in notification_windows.drain(..) {
-                window.remove(&mut cx);
+                window
+                    .update(&mut cx, |_, cx| {
+                        // todo!()
+                        cx.remove_window();
+                    })
+                    .log_err();
             }
 
             if let Some(incoming_call) = incoming_call {
-                let window_size = cx.read(|cx| {
-                    let theme = &theme::current(cx).incoming_call_notification;
-                    vec2f(theme.window_width, theme.window_height)
-                });
+                let unique_screens = cx.update(|cx| cx.displays()).unwrap();
+                let window_size = gpui::Size {
+                    width: px(380.),
+                    height: px(64.),
+                };
 
-                for screen in cx.platform().screens() {
+                for screen in unique_screens {
+                    let options = notification_window_options(screen, window_size);
                     let window = cx
-                        .add_window(notification_window_options(screen, window_size), |_| {
-                            IncomingCallNotification::new(incoming_call.clone(), app_state.clone())
-                        });
-
+                        .open_window(options, |cx| {
+                            cx.new_view(|_| {
+                                IncomingCallNotification::new(
+                                    incoming_call.clone(),
+                                    app_state.clone(),
+                                )
+                            })
+                        })
+                        .unwrap();
                     notification_windows.push(window);
                 }
             }
@@ -47,167 +60,104 @@ struct RespondToCall {
     accept: bool,
 }
 
-pub struct IncomingCallNotification {
+struct IncomingCallNotificationState {
     call: IncomingCall,
     app_state: Weak<AppState>,
 }
 
-impl IncomingCallNotification {
+pub struct IncomingCallNotification {
+    state: Arc<IncomingCallNotificationState>,
+}
+impl IncomingCallNotificationState {
     pub fn new(call: IncomingCall, app_state: Weak<AppState>) -> Self {
         Self { call, app_state }
     }
 
-    fn respond(&mut self, accept: bool, cx: &mut ViewContext<Self>) {
+    fn respond(&self, accept: bool, cx: &mut AppContext) {
         let active_call = ActiveCall::global(cx);
         if accept {
             let join = active_call.update(cx, |active_call, cx| active_call.accept_incoming(cx));
             let caller_user_id = self.call.calling_user.id;
             let initial_project_id = self.call.initial_project.as_ref().map(|project| project.id);
             let app_state = self.app_state.clone();
-            cx.app_context()
-                .spawn(|mut cx| async move {
-                    join.await?;
-                    if let Some(project_id) = initial_project_id {
-                        cx.update(|cx| {
-                            if let Some(app_state) = app_state.upgrade() {
-                                workspace::join_remote_project(
-                                    project_id,
-                                    caller_user_id,
-                                    app_state,
-                                    cx,
-                                )
-                                .detach_and_log_err(cx);
-                            }
-                        });
-                    }
-                    anyhow::Ok(())
-                })
-                .detach_and_log_err(cx);
+            let cx: &mut AppContext = cx;
+            cx.spawn(|cx| async move {
+                join.await?;
+                if let Some(project_id) = initial_project_id {
+                    cx.update(|cx| {
+                        if let Some(app_state) = app_state.upgrade() {
+                            workspace::join_remote_project(
+                                project_id,
+                                caller_user_id,
+                                app_state,
+                                cx,
+                            )
+                            .detach_and_log_err(cx);
+                        }
+                    })
+                    .log_err();
+                }
+                anyhow::Ok(())
+            })
+            .detach_and_log_err(cx);
         } else {
             active_call.update(cx, |active_call, cx| {
                 active_call.decline_incoming(cx).log_err();
             });
         }
     }
+}
 
-    fn render_caller(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = &theme::current(cx).incoming_call_notification;
-        let default_project = proto::ParticipantProject::default();
-        let initial_project = self
-            .call
-            .initial_project
-            .as_ref()
-            .unwrap_or(&default_project);
-        Flex::row()
-            .with_children(self.call.calling_user.avatar.clone().map(|avatar| {
-                Image::from_data(avatar)
-                    .with_style(theme.caller_avatar)
-                    .aligned()
-            }))
-            .with_child(
-                Flex::column()
-                    .with_child(
-                        Label::new(
-                            self.call.calling_user.github_login.clone(),
-                            theme.caller_username.text.clone(),
-                        )
-                        .contained()
-                        .with_style(theme.caller_username.container),
-                    )
-                    .with_child(
-                        Label::new(
-                            format!(
-                                "is sharing a project in Zed{}",
-                                if initial_project.worktree_root_names.is_empty() {
-                                    ""
-                                } else {
-                                    ":"
-                                }
-                            ),
-                            theme.caller_message.text.clone(),
-                        )
-                        .contained()
-                        .with_style(theme.caller_message.container),
-                    )
-                    .with_children(if initial_project.worktree_root_names.is_empty() {
-                        None
-                    } else {
-                        Some(
-                            Label::new(
-                                initial_project.worktree_root_names.join(", "),
-                                theme.worktree_roots.text.clone(),
-                            )
-                            .contained()
-                            .with_style(theme.worktree_roots.container),
-                        )
-                    })
-                    .contained()
-                    .with_style(theme.caller_metadata)
-                    .aligned(),
-            )
-            .contained()
-            .with_style(theme.caller_container)
-            .flex(1., true)
-            .into_any()
-    }
-
-    fn render_buttons(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        enum Accept {}
-        enum Decline {}
-
-        let theme = theme::current(cx);
-        Flex::column()
-            .with_child(
-                MouseEventHandler::new::<Accept, _>(0, cx, |_, _| {
-                    let theme = &theme.incoming_call_notification;
-                    Label::new("Accept", theme.accept_button.text.clone())
-                        .aligned()
-                        .contained()
-                        .with_style(theme.accept_button.container)
-                })
-                .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, |_, this, cx| {
-                    this.respond(true, cx);
-                })
-                .flex(1., true),
-            )
-            .with_child(
-                MouseEventHandler::new::<Decline, _>(0, cx, |_, _| {
-                    let theme = &theme.incoming_call_notification;
-                    Label::new("Decline", theme.decline_button.text.clone())
-                        .aligned()
-                        .contained()
-                        .with_style(theme.decline_button.container)
-                })
-                .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, |_, this, cx| {
-                    this.respond(false, cx);
-                })
-                .flex(1., true),
-            )
-            .constrained()
-            .with_width(theme.incoming_call_notification.button_width)
-            .into_any()
+impl IncomingCallNotification {
+    pub fn new(call: IncomingCall, app_state: Weak<AppState>) -> Self {
+        Self {
+            state: Arc::new(IncomingCallNotificationState::new(call, app_state)),
+        }
     }
 }
 
-impl Entity for IncomingCallNotification {
-    type Event = ();
-}
+impl Render for IncomingCallNotification {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        // TODO: Is there a better place for us to initialize the font?
+        let (ui_font, ui_font_size) = {
+            let theme_settings = ThemeSettings::get_global(cx);
+            (
+                theme_settings.ui_font.family.clone(),
+                theme_settings.ui_font_size.clone(),
+            )
+        };
 
-impl View for IncomingCallNotification {
-    fn ui_name() -> &'static str {
-        "IncomingCallNotification"
-    }
+        cx.set_rem_size(ui_font_size);
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let background = theme::current(cx).incoming_call_notification.background;
-        Flex::row()
-            .with_child(self.render_caller(cx))
-            .with_child(self.render_buttons(cx))
-            .contained()
-            .with_background_color(background)
-            .expanded()
-            .into_any()
+        h_stack()
+            .font(ui_font)
+            .text_ui()
+            .justify_between()
+            .size_full()
+            .overflow_hidden()
+            .elevation_3(cx)
+            .p_2()
+            .gap_2()
+            .child(
+                img(self.state.call.calling_user.avatar_uri.clone())
+                    .w_12()
+                    .h_12()
+                    .rounded_full(),
+            )
+            .child(v_stack().overflow_hidden().child(Label::new(format!(
+                "{} is sharing a project in Zed",
+                self.state.call.calling_user.github_login
+            ))))
+            .child(
+                v_stack()
+                    .child(Button::new("accept", "Accept").render(cx).on_click({
+                        let state = self.state.clone();
+                        move |_, cx| state.respond(true, cx)
+                    }))
+                    .child(Button::new("decline", "Decline").render(cx).on_click({
+                        let state = self.state.clone();
+                        move |_, cx| state.respond(false, cx)
+                    })),
+            )
     }
 }

crates/collab_ui/src/notifications/project_shared_notification.rs 🔗

@@ -2,13 +2,11 @@ use crate::notification_window_options;
 use call::{room, ActiveCall};
 use client::User;
 use collections::HashMap;
-use gpui::{
-    elements::*,
-    geometry::vector::vec2f,
-    platform::{CursorStyle, MouseButton},
-    AppContext, Entity, View, ViewContext,
-};
+use gpui::{img, px, AppContext, ParentElement, Render, Size, Styled, ViewContext, VisualContext};
+use settings::Settings;
 use std::sync::{Arc, Weak};
+use theme::ThemeSettings;
+use ui::{h_stack, prelude::*, v_stack, Button, Label};
 use workspace::AppState;
 
 pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
@@ -21,38 +19,54 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
             project_id,
             worktree_root_names,
         } => {
-            let theme = &theme::current(cx).project_shared_notification;
-            let window_size = vec2f(theme.window_width, theme.window_height);
+            let window_size = Size {
+                width: px(400.),
+                height: px(72.),
+            };
 
-            for screen in cx.platform().screens() {
-                let window =
-                    cx.add_window(notification_window_options(screen, window_size), |_| {
+            for screen in cx.displays() {
+                let options = notification_window_options(screen, window_size);
+                let window = cx.open_window(options, |cx| {
+                    cx.new_view(|_| {
                         ProjectSharedNotification::new(
                             owner.clone(),
                             *project_id,
                             worktree_root_names.clone(),
                             app_state.clone(),
                         )
-                    });
+                    })
+                });
                 notification_windows
                     .entry(*project_id)
                     .or_insert(Vec::new())
                     .push(window);
             }
         }
+
         room::Event::RemoteProjectUnshared { project_id }
         | room::Event::RemoteProjectJoined { project_id }
         | room::Event::RemoteProjectInvitationDiscarded { project_id } => {
             if let Some(windows) = notification_windows.remove(&project_id) {
                 for window in windows {
-                    window.remove(cx);
+                    window
+                        .update(cx, |_, cx| {
+                            // todo!()
+                            cx.remove_window();
+                        })
+                        .ok();
                 }
             }
         }
+
         room::Event::Left => {
             for (_, windows) in notification_windows.drain() {
                 for window in windows {
-                    window.remove(cx);
+                    window
+                        .update(cx, |_, cx| {
+                            // todo!()
+                            cx.remove_window();
+                        })
+                        .ok();
                 }
             }
         }
@@ -101,117 +115,66 @@ impl ProjectSharedNotification {
             });
         }
     }
+}
 
-    fn render_owner(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = &theme::current(cx).project_shared_notification;
-        Flex::row()
-            .with_children(self.owner.avatar.clone().map(|avatar| {
-                Image::from_data(avatar)
-                    .with_style(theme.owner_avatar)
-                    .aligned()
-            }))
-            .with_child(
-                Flex::column()
-                    .with_child(
-                        Label::new(
-                            self.owner.github_login.clone(),
-                            theme.owner_username.text.clone(),
-                        )
-                        .contained()
-                        .with_style(theme.owner_username.container),
-                    )
-                    .with_child(
-                        Label::new(
-                            format!(
-                                "is sharing a project in Zed{}",
-                                if self.worktree_root_names.is_empty() {
-                                    ""
-                                } else {
-                                    ":"
-                                }
-                            ),
-                            theme.message.text.clone(),
-                        )
-                        .contained()
-                        .with_style(theme.message.container),
-                    )
-                    .with_children(if self.worktree_root_names.is_empty() {
-                        None
-                    } else {
-                        Some(
-                            Label::new(
-                                self.worktree_root_names.join(", "),
-                                theme.worktree_roots.text.clone(),
-                            )
-                            .contained()
-                            .with_style(theme.worktree_roots.container),
-                        )
-                    })
-                    .contained()
-                    .with_style(theme.owner_metadata)
-                    .aligned(),
+impl Render for ProjectSharedNotification {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        // TODO: Is there a better place for us to initialize the font?
+        let (ui_font, ui_font_size) = {
+            let theme_settings = ThemeSettings::get_global(cx);
+            (
+                theme_settings.ui_font.family.clone(),
+                theme_settings.ui_font_size.clone(),
             )
-            .contained()
-            .with_style(theme.owner_container)
-            .flex(1., true)
-            .into_any()
-    }
+        };
 
-    fn render_buttons(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        enum Open {}
-        enum Dismiss {}
+        cx.set_rem_size(ui_font_size);
 
-        let theme = theme::current(cx);
-        Flex::column()
-            .with_child(
-                MouseEventHandler::new::<Open, _>(0, cx, |_, _| {
-                    let theme = &theme.project_shared_notification;
-                    Label::new("Open", theme.open_button.text.clone())
-                        .aligned()
-                        .contained()
-                        .with_style(theme.open_button.container)
-                })
-                .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, this, cx| this.join(cx))
-                .flex(1., true),
+        h_stack()
+            .font(ui_font)
+            .text_ui()
+            .justify_between()
+            .size_full()
+            .overflow_hidden()
+            .elevation_3(cx)
+            .p_2()
+            .gap_2()
+            .child(
+                img(self.owner.avatar_uri.clone())
+                    .w_12()
+                    .h_12()
+                    .rounded_full(),
             )
-            .with_child(
-                MouseEventHandler::new::<Dismiss, _>(0, cx, |_, _| {
-                    let theme = &theme.project_shared_notification;
-                    Label::new("Dismiss", theme.dismiss_button.text.clone())
-                        .aligned()
-                        .contained()
-                        .with_style(theme.dismiss_button.container)
-                })
-                .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, |_, this, cx| {
-                    this.dismiss(cx);
-                })
-                .flex(1., true),
+            .child(
+                v_stack()
+                    .overflow_hidden()
+                    .child(Label::new(self.owner.github_login.clone()))
+                    .child(Label::new(format!(
+                        "is sharing a project in Zed{}",
+                        if self.worktree_root_names.is_empty() {
+                            ""
+                        } else {
+                            ":"
+                        }
+                    )))
+                    .children(if self.worktree_root_names.is_empty() {
+                        None
+                    } else {
+                        Some(Label::new(self.worktree_root_names.join(", ")))
+                    }),
+            )
+            .child(
+                v_stack()
+                    .child(Button::new("open", "Open").on_click(cx.listener(
+                        move |this, _event, cx| {
+                            this.join(cx);
+                        },
+                    )))
+                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
+                        move |this, _event, cx| {
+                            this.dismiss(cx);
+                        },
+                    ))),
             )
-            .constrained()
-            .with_width(theme.project_shared_notification.button_width)
-            .into_any()
-    }
-}
-
-impl Entity for ProjectSharedNotification {
-    type Event = ();
-}
-
-impl View for ProjectSharedNotification {
-    fn ui_name() -> &'static str {
-        "ProjectSharedNotification"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> gpui::AnyElement<Self> {
-        let background = theme::current(cx).project_shared_notification.background;
-        Flex::row()
-            .with_child(self.render_owner(cx))
-            .with_child(self.render_buttons(cx))
-            .contained()
-            .with_background_color(background)
-            .expanded()
-            .into_any()
     }
 }

crates/collab_ui/src/panel_settings.rs 🔗

@@ -1,28 +1,29 @@
 use anyhow;
+use gpui::Pixels;
 use schemars::JsonSchema;
 use serde_derive::{Deserialize, Serialize};
-use settings::Setting;
+use settings::Settings;
 use workspace::dock::DockPosition;
 
 #[derive(Deserialize, Debug)]
 pub struct CollaborationPanelSettings {
     pub button: bool,
     pub dock: DockPosition,
-    pub default_width: f32,
+    pub default_width: Pixels,
 }
 
 #[derive(Deserialize, Debug)]
 pub struct ChatPanelSettings {
     pub button: bool,
     pub dock: DockPosition,
-    pub default_width: f32,
+    pub default_width: Pixels,
 }
 
 #[derive(Deserialize, Debug)]
 pub struct NotificationPanelSettings {
     pub button: bool,
     pub dock: DockPosition,
-    pub default_width: f32,
+    pub default_width: Pixels,
 }
 
 #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
@@ -32,37 +33,37 @@ pub struct PanelSettingsContent {
     pub default_width: Option<f32>,
 }
 
-impl Setting for CollaborationPanelSettings {
+impl Settings for CollaborationPanelSettings {
     const KEY: Option<&'static str> = Some("collaboration_panel");
     type FileContent = PanelSettingsContent;
     fn load(
         default_value: &Self::FileContent,
         user_values: &[&Self::FileContent],
-        _: &gpui::AppContext,
+        _: &mut gpui::AppContext,
     ) -> anyhow::Result<Self> {
         Self::load_via_json_merge(default_value, user_values)
     }
 }
 
-impl Setting for ChatPanelSettings {
+impl Settings for ChatPanelSettings {
     const KEY: Option<&'static str> = Some("chat_panel");
     type FileContent = PanelSettingsContent;
     fn load(
         default_value: &Self::FileContent,
         user_values: &[&Self::FileContent],
-        _: &gpui::AppContext,
+        _: &mut gpui::AppContext,
     ) -> anyhow::Result<Self> {
         Self::load_via_json_merge(default_value, user_values)
     }
 }
 
-impl Setting for NotificationPanelSettings {
+impl Settings for NotificationPanelSettings {
     const KEY: Option<&'static str> = Some("notification_panel");
     type FileContent = PanelSettingsContent;
     fn load(
         default_value: &Self::FileContent,
         user_values: &[&Self::FileContent],
-        _: &gpui::AppContext,
+        _: &mut gpui::AppContext,
     ) -> anyhow::Result<Self> {
         Self::load_via_json_merge(default_value, user_values)
     }

crates/collab_ui2/Cargo.toml 🔗

@@ -1,81 +0,0 @@
-[package]
-name = "collab_ui2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/collab_ui.rs"
-doctest = false
-
-[features]
-test-support = [
-    "call/test-support",
-    "client/test-support",
-    "collections/test-support",
-    "editor/test-support",
-    "gpui/test-support",
-    "project/test-support",
-    "settings/test-support",
-    "util/test-support",
-    "workspace/test-support",
-]
-
-[dependencies]
-auto_update = { package = "auto_update2", path = "../auto_update2" }
-db = { package = "db2", path = "../db2" }
-call = { package = "call2", path = "../call2" }
-client = { package = "client2", path = "../client2" }
-channel = { package = "channel2", path = "../channel2" }
-clock = { path = "../clock" }
-collections = { path = "../collections" }
-# context_menu = { path = "../context_menu" }
-# drag_and_drop = { path = "../drag_and_drop" }
-editor = { package="editor2", path = "../editor2" }
-feedback = { package = "feedback2", path = "../feedback2" }
-fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-language = { package = "language2", path = "../language2" }
-menu = { package = "menu2",  path = "../menu2" }
-notifications = { package = "notifications2",  path = "../notifications2" }
-rich_text = { package = "rich_text2", path = "../rich_text2" }
-picker = { package = "picker2", path = "../picker2" }
-project = { package = "project2", path = "../project2" }
-recent_projects = { package = "recent_projects2", path = "../recent_projects2" }
-rpc = { package ="rpc2",  path = "../rpc2" }
-settings = { package = "settings2", path = "../settings2" }
-feature_flags = { package = "feature_flags2", path = "../feature_flags2"}
-theme = { package = "theme2", path = "../theme2" }
-theme_selector = { package = "theme_selector2", path = "../theme_selector2" }
-vcs_menu = { package = "vcs_menu2", path = "../vcs_menu2" }
-ui = { package = "ui2", path = "../ui2" }
-util = { path = "../util" }
-workspace = { package = "workspace2", path = "../workspace2" }
-zed-actions = { package="zed_actions2", path = "../zed_actions2"}
-
-anyhow.workspace = true
-futures.workspace = true
-lazy_static.workspace = true
-log.workspace = true
-schemars.workspace = true
-postage.workspace = true
-serde.workspace = true
-serde_derive.workspace = true
-time.workspace = true
-smallvec.workspace = true
-
-[dev-dependencies]
-call = { package = "call2", path = "../call2", features = ["test-support"] }
-client = { package = "client2", path = "../client2", features = ["test-support"] }
-collections = { path = "../collections", features = ["test-support"] }
-editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-notifications = { package = "notifications2", path = "../notifications2", features = ["test-support"] }
-project = { package = "project2", path = "../project2", features = ["test-support"] }
-rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
-settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
-util = { path = "../util", features = ["test-support"] }
-workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
-
-pretty_assertions.workspace = true
-tree-sitter-markdown.workspace = true

crates/collab_ui2/src/channel_view.rs 🔗

@@ -1,448 +0,0 @@
-use anyhow::Result;
-use call::report_call_event_for_channel;
-use channel::{Channel, ChannelBuffer, ChannelBufferEvent, ChannelId, ChannelStore};
-use client::{
-    proto::{self, PeerId},
-    Collaborator, ParticipantIndex,
-};
-use collections::HashMap;
-use editor::{CollaborationHub, Editor, EditorEvent};
-use gpui::{
-    actions, AnyElement, AnyView, AppContext, Entity as _, EventEmitter, FocusableView,
-    IntoElement as _, Model, Pixels, Point, Render, Subscription, Task, View, ViewContext,
-    VisualContext as _, WindowContext,
-};
-use project::Project;
-use std::{
-    any::{Any, TypeId},
-    sync::Arc,
-};
-use ui::{prelude::*, Label};
-use util::ResultExt;
-use workspace::{
-    item::{FollowableItem, Item, ItemEvent, ItemHandle},
-    register_followable_item,
-    searchable::SearchableItemHandle,
-    ItemNavHistory, Pane, SaveIntent, ViewId, Workspace, WorkspaceId,
-};
-
-actions!(collab, [Deploy]);
-
-pub fn init(cx: &mut AppContext) {
-    register_followable_item::<ChannelView>(cx)
-}
-
-pub struct ChannelView {
-    pub editor: View<Editor>,
-    project: Model<Project>,
-    channel_store: Model<ChannelStore>,
-    channel_buffer: Model<ChannelBuffer>,
-    remote_id: Option<ViewId>,
-    _editor_event_subscription: Subscription,
-}
-
-impl ChannelView {
-    pub fn open(
-        channel_id: ChannelId,
-        workspace: View<Workspace>,
-        cx: &mut WindowContext,
-    ) -> Task<Result<View<Self>>> {
-        let pane = workspace.read(cx).active_pane().clone();
-        let channel_view = Self::open_in_pane(channel_id, pane.clone(), workspace.clone(), cx);
-        cx.spawn(|mut cx| async move {
-            let channel_view = channel_view.await?;
-            pane.update(&mut cx, |pane, cx| {
-                report_call_event_for_channel(
-                    "open channel notes",
-                    channel_id,
-                    &workspace.read(cx).app_state().client,
-                    cx,
-                );
-                pane.add_item(Box::new(channel_view.clone()), true, true, None, cx);
-            })?;
-            anyhow::Ok(channel_view)
-        })
-    }
-
-    pub fn open_in_pane(
-        channel_id: ChannelId,
-        pane: View<Pane>,
-        workspace: View<Workspace>,
-        cx: &mut WindowContext,
-    ) -> Task<Result<View<Self>>> {
-        let workspace = workspace.read(cx);
-        let project = workspace.project().to_owned();
-        let channel_store = ChannelStore::global(cx);
-        let language_registry = workspace.app_state().languages.clone();
-        let markdown = language_registry.language_for_name("Markdown");
-        let channel_buffer =
-            channel_store.update(cx, |store, cx| store.open_channel_buffer(channel_id, cx));
-
-        cx.spawn(|mut cx| async move {
-            let channel_buffer = channel_buffer.await?;
-            let markdown = markdown.await.log_err();
-
-            channel_buffer.update(&mut cx, |buffer, cx| {
-                buffer.buffer().update(cx, |buffer, cx| {
-                    buffer.set_language_registry(language_registry);
-                    if let Some(markdown) = markdown {
-                        buffer.set_language(Some(markdown), cx);
-                    }
-                })
-            })?;
-
-            pane.update(&mut cx, |pane, cx| {
-                let buffer_id = channel_buffer.read(cx).remote_id(cx);
-
-                let existing_view = pane
-                    .items_of_type::<Self>()
-                    .find(|view| view.read(cx).channel_buffer.read(cx).remote_id(cx) == buffer_id);
-
-                // If this channel buffer is already open in this pane, just return it.
-                if let Some(existing_view) = existing_view.clone() {
-                    if existing_view.read(cx).channel_buffer == channel_buffer {
-                        return existing_view;
-                    }
-                }
-
-                let view = cx.new_view(|cx| {
-                    let mut this = Self::new(project, channel_store, channel_buffer, cx);
-                    this.acknowledge_buffer_version(cx);
-                    this
-                });
-
-                // If the pane contained a disconnected view for this channel buffer,
-                // replace that.
-                if let Some(existing_item) = existing_view {
-                    if let Some(ix) = pane.index_for_item(&existing_item) {
-                        pane.close_item_by_id(existing_item.entity_id(), SaveIntent::Skip, cx)
-                            .detach();
-                        pane.add_item(Box::new(view.clone()), true, true, Some(ix), cx);
-                    }
-                }
-
-                view
-            })
-        })
-    }
-
-    pub fn new(
-        project: Model<Project>,
-        channel_store: Model<ChannelStore>,
-        channel_buffer: Model<ChannelBuffer>,
-        cx: &mut ViewContext<Self>,
-    ) -> Self {
-        let buffer = channel_buffer.read(cx).buffer();
-        let editor = cx.new_view(|cx| {
-            let mut editor = Editor::for_buffer(buffer, None, cx);
-            editor.set_collaboration_hub(Box::new(ChannelBufferCollaborationHub(
-                channel_buffer.clone(),
-            )));
-            editor.set_read_only(
-                !channel_buffer
-                    .read(cx)
-                    .channel(cx)
-                    .is_some_and(|c| c.can_edit_notes()),
-            );
-            editor
-        });
-        let _editor_event_subscription =
-            cx.subscribe(&editor, |_, _, e: &EditorEvent, cx| cx.emit(e.clone()));
-
-        cx.subscribe(&channel_buffer, Self::handle_channel_buffer_event)
-            .detach();
-
-        Self {
-            editor,
-            project,
-            channel_store,
-            channel_buffer,
-            remote_id: None,
-            _editor_event_subscription,
-        }
-    }
-
-    pub fn channel(&self, cx: &AppContext) -> Option<Arc<Channel>> {
-        self.channel_buffer.read(cx).channel(cx)
-    }
-
-    fn handle_channel_buffer_event(
-        &mut self,
-        _: Model<ChannelBuffer>,
-        event: &ChannelBufferEvent,
-        cx: &mut ViewContext<Self>,
-    ) {
-        match event {
-            ChannelBufferEvent::Disconnected => self.editor.update(cx, |editor, cx| {
-                editor.set_read_only(true);
-                cx.notify();
-            }),
-            ChannelBufferEvent::ChannelChanged => {
-                self.editor.update(cx, |editor, cx| {
-                    editor.set_read_only(!self.channel(cx).is_some_and(|c| c.can_edit_notes()));
-                    cx.emit(editor::EditorEvent::TitleChanged);
-                    cx.notify()
-                });
-            }
-            ChannelBufferEvent::BufferEdited => {
-                if self.editor.read(cx).is_focused(cx) {
-                    self.acknowledge_buffer_version(cx);
-                } else {
-                    self.channel_store.update(cx, |store, cx| {
-                        let channel_buffer = self.channel_buffer.read(cx);
-                        store.notes_changed(
-                            channel_buffer.channel_id,
-                            channel_buffer.epoch(),
-                            &channel_buffer.buffer().read(cx).version(),
-                            cx,
-                        )
-                    });
-                }
-            }
-            ChannelBufferEvent::CollaboratorsChanged => {}
-        }
-    }
-
-    fn acknowledge_buffer_version(&mut self, cx: &mut ViewContext<ChannelView>) {
-        self.channel_store.update(cx, |store, cx| {
-            let channel_buffer = self.channel_buffer.read(cx);
-            store.acknowledge_notes_version(
-                channel_buffer.channel_id,
-                channel_buffer.epoch(),
-                &channel_buffer.buffer().read(cx).version(),
-                cx,
-            )
-        });
-        self.channel_buffer.update(cx, |buffer, cx| {
-            buffer.acknowledge_buffer_version(cx);
-        });
-    }
-}
-
-impl EventEmitter<EditorEvent> for ChannelView {}
-
-impl Render for ChannelView {
-    fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
-        self.editor.clone()
-    }
-}
-
-impl FocusableView for ChannelView {
-    fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
-        self.editor.read(cx).focus_handle(cx)
-    }
-}
-
-impl Item for ChannelView {
-    type Event = EditorEvent;
-
-    fn act_as_type<'a>(
-        &'a self,
-        type_id: TypeId,
-        self_handle: &'a View<Self>,
-        _: &'a AppContext,
-    ) -> Option<AnyView> {
-        if type_id == TypeId::of::<Self>() {
-            Some(self_handle.to_any())
-        } else if type_id == TypeId::of::<Editor>() {
-            Some(self.editor.to_any())
-        } else {
-            None
-        }
-    }
-
-    fn tab_content(&self, _: Option<usize>, selected: bool, cx: &WindowContext) -> AnyElement {
-        let label = if let Some(channel) = self.channel(cx) {
-            match (
-                channel.can_edit_notes(),
-                self.channel_buffer.read(cx).is_connected(),
-            ) {
-                (true, true) => format!("#{}", channel.name),
-                (false, true) => format!("#{} (read-only)", channel.name),
-                (_, false) => format!("#{} (disconnected)", channel.name),
-            }
-        } else {
-            format!("channel notes (disconnected)")
-        };
-        Label::new(label)
-            .color(if selected {
-                Color::Default
-            } else {
-                Color::Muted
-            })
-            .into_any_element()
-    }
-
-    fn clone_on_split(&self, _: WorkspaceId, cx: &mut ViewContext<Self>) -> Option<View<Self>> {
-        Some(cx.new_view(|cx| {
-            Self::new(
-                self.project.clone(),
-                self.channel_store.clone(),
-                self.channel_buffer.clone(),
-                cx,
-            )
-        }))
-    }
-
-    fn is_singleton(&self, _cx: &AppContext) -> bool {
-        false
-    }
-
-    fn navigate(&mut self, data: Box<dyn Any>, cx: &mut ViewContext<Self>) -> bool {
-        self.editor
-            .update(cx, |editor, cx| editor.navigate(data, cx))
-    }
-
-    fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
-        self.editor
-            .update(cx, |editor, cx| Item::deactivated(editor, cx))
-    }
-
-    fn set_nav_history(&mut self, history: ItemNavHistory, cx: &mut ViewContext<Self>) {
-        self.editor
-            .update(cx, |editor, cx| Item::set_nav_history(editor, history, cx))
-    }
-
-    fn as_searchable(&self, _: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
-        Some(Box::new(self.editor.clone()))
-    }
-
-    fn show_toolbar(&self) -> bool {
-        true
-    }
-
-    fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>> {
-        self.editor.read(cx).pixel_position_of_cursor(cx)
-    }
-
-    fn to_item_events(event: &EditorEvent, f: impl FnMut(ItemEvent)) {
-        Editor::to_item_events(event, f)
-    }
-}
-
-impl FollowableItem for ChannelView {
-    fn remote_id(&self) -> Option<workspace::ViewId> {
-        self.remote_id
-    }
-
-    fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
-        let channel_buffer = self.channel_buffer.read(cx);
-        if !channel_buffer.is_connected() {
-            return None;
-        }
-
-        Some(proto::view::Variant::ChannelView(
-            proto::view::ChannelView {
-                channel_id: channel_buffer.channel_id,
-                editor: if let Some(proto::view::Variant::Editor(proto)) =
-                    self.editor.read(cx).to_state_proto(cx)
-                {
-                    Some(proto)
-                } else {
-                    None
-                },
-            },
-        ))
-    }
-
-    fn from_state_proto(
-        pane: View<workspace::Pane>,
-        workspace: View<workspace::Workspace>,
-        remote_id: workspace::ViewId,
-        state: &mut Option<proto::view::Variant>,
-        cx: &mut WindowContext,
-    ) -> Option<gpui::Task<anyhow::Result<View<Self>>>> {
-        let Some(proto::view::Variant::ChannelView(_)) = state else {
-            return None;
-        };
-        let Some(proto::view::Variant::ChannelView(state)) = state.take() else {
-            unreachable!()
-        };
-
-        let open = ChannelView::open_in_pane(state.channel_id, pane, workspace, cx);
-
-        Some(cx.spawn(|mut cx| async move {
-            let this = open.await?;
-
-            let task = this.update(&mut cx, |this, cx| {
-                this.remote_id = Some(remote_id);
-
-                if let Some(state) = state.editor {
-                    Some(this.editor.update(cx, |editor, cx| {
-                        editor.apply_update_proto(
-                            &this.project,
-                            proto::update_view::Variant::Editor(proto::update_view::Editor {
-                                selections: state.selections,
-                                pending_selection: state.pending_selection,
-                                scroll_top_anchor: state.scroll_top_anchor,
-                                scroll_x: state.scroll_x,
-                                scroll_y: state.scroll_y,
-                                ..Default::default()
-                            }),
-                            cx,
-                        )
-                    }))
-                } else {
-                    None
-                }
-            })?;
-
-            if let Some(task) = task {
-                task.await?;
-            }
-
-            Ok(this)
-        }))
-    }
-
-    fn add_event_to_update_proto(
-        &self,
-        event: &EditorEvent,
-        update: &mut Option<proto::update_view::Variant>,
-        cx: &WindowContext,
-    ) -> bool {
-        self.editor
-            .read(cx)
-            .add_event_to_update_proto(event, update, cx)
-    }
-
-    fn apply_update_proto(
-        &mut self,
-        project: &Model<Project>,
-        message: proto::update_view::Variant,
-        cx: &mut ViewContext<Self>,
-    ) -> gpui::Task<anyhow::Result<()>> {
-        self.editor.update(cx, |editor, cx| {
-            editor.apply_update_proto(project, message, cx)
-        })
-    }
-
-    fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>) {
-        self.editor.update(cx, |editor, cx| {
-            editor.set_leader_peer_id(leader_peer_id, cx)
-        })
-    }
-
-    fn is_project_item(&self, _cx: &WindowContext) -> bool {
-        false
-    }
-
-    fn to_follow_event(event: &Self::Event) -> Option<workspace::item::FollowEvent> {
-        Editor::to_follow_event(event)
-    }
-}
-
-struct ChannelBufferCollaborationHub(Model<ChannelBuffer>);
-
-impl CollaborationHub for ChannelBufferCollaborationHub {
-    fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator> {
-        self.0.read(cx).collaborators()
-    }
-
-    fn user_participant_indices<'a>(
-        &self,
-        cx: &'a AppContext,
-    ) -> &'a HashMap<u64, ParticipantIndex> {
-        self.0.read(cx).user_store().read(cx).participant_indices()
-    }
-}

crates/collab_ui2/src/chat_panel.rs 🔗

@@ -1,704 +0,0 @@
-use crate::{channel_view::ChannelView, is_channels_feature_enabled, ChatPanelSettings};
-use anyhow::Result;
-use call::ActiveCall;
-use channel::{ChannelChat, ChannelChatEvent, ChannelMessageId, ChannelStore};
-use client::Client;
-use collections::HashMap;
-use db::kvp::KEY_VALUE_STORE;
-use editor::Editor;
-use gpui::{
-    actions, div, list, prelude::*, px, serde_json, AnyElement, AppContext, AsyncWindowContext,
-    ClickEvent, ElementId, EventEmitter, FocusableView, ListOffset, ListScrollEvent, ListState,
-    Model, Render, Subscription, Task, View, ViewContext, VisualContext, WeakView,
-};
-use language::LanguageRegistry;
-use menu::Confirm;
-use message_editor::MessageEditor;
-use project::Fs;
-use rich_text::RichText;
-use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsStore};
-use std::sync::Arc;
-use theme::ActiveTheme as _;
-use time::{OffsetDateTime, UtcOffset};
-use ui::{prelude::*, Avatar, Button, Icon, IconButton, Label, TabBar, Tooltip};
-use util::{ResultExt, TryFutureExt};
-use workspace::{
-    dock::{DockPosition, Panel, PanelEvent},
-    Workspace,
-};
-
-mod message_editor;
-
-const MESSAGE_LOADING_THRESHOLD: usize = 50;
-const CHAT_PANEL_KEY: &'static str = "ChatPanel";
-
-pub fn init(cx: &mut AppContext) {
-    cx.observe_new_views(|workspace: &mut Workspace, _| {
-        workspace.register_action(|workspace, _: &ToggleFocus, cx| {
-            workspace.toggle_panel_focus::<ChatPanel>(cx);
-        });
-    })
-    .detach();
-}
-
-pub struct ChatPanel {
-    client: Arc<Client>,
-    channel_store: Model<ChannelStore>,
-    languages: Arc<LanguageRegistry>,
-    message_list: ListState,
-    active_chat: Option<(Model<ChannelChat>, Subscription)>,
-    input_editor: View<MessageEditor>,
-    local_timezone: UtcOffset,
-    fs: Arc<dyn Fs>,
-    width: Option<Pixels>,
-    active: bool,
-    pending_serialization: Task<Option<()>>,
-    subscriptions: Vec<gpui::Subscription>,
-    workspace: WeakView<Workspace>,
-    is_scrolled_to_bottom: bool,
-    markdown_data: HashMap<ChannelMessageId, RichText>,
-}
-
-#[derive(Serialize, Deserialize)]
-struct SerializedChatPanel {
-    width: Option<Pixels>,
-}
-
-#[derive(Debug)]
-pub enum Event {
-    DockPositionChanged,
-    Focus,
-    Dismissed,
-}
-
-actions!(chat_panel, [ToggleFocus]);
-
-impl ChatPanel {
-    pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
-        let fs = workspace.app_state().fs.clone();
-        let client = workspace.app_state().client.clone();
-        let channel_store = ChannelStore::global(cx);
-        let languages = workspace.app_state().languages.clone();
-
-        let input_editor = cx.new_view(|cx| {
-            MessageEditor::new(
-                languages.clone(),
-                channel_store.clone(),
-                cx.new_view(|cx| Editor::auto_height(4, cx)),
-                cx,
-            )
-        });
-
-        let workspace_handle = workspace.weak_handle();
-
-        cx.new_view(|cx: &mut ViewContext<Self>| {
-            let view = cx.view().downgrade();
-            let message_list =
-                ListState::new(0, gpui::ListAlignment::Bottom, px(1000.), move |ix, cx| {
-                    if let Some(view) = view.upgrade() {
-                        view.update(cx, |view, cx| {
-                            view.render_message(ix, cx).into_any_element()
-                        })
-                    } else {
-                        div().into_any()
-                    }
-                });
-
-            message_list.set_scroll_handler(cx.listener(|this, event: &ListScrollEvent, cx| {
-                if event.visible_range.start < MESSAGE_LOADING_THRESHOLD {
-                    this.load_more_messages(cx);
-                }
-                this.is_scrolled_to_bottom = event.visible_range.end == event.count;
-            }));
-
-            let mut this = Self {
-                fs,
-                client,
-                channel_store,
-                languages,
-                message_list,
-                active_chat: Default::default(),
-                pending_serialization: Task::ready(None),
-                input_editor,
-                local_timezone: cx.local_timezone(),
-                subscriptions: Vec::new(),
-                workspace: workspace_handle,
-                is_scrolled_to_bottom: true,
-                active: false,
-                width: None,
-                markdown_data: Default::default(),
-            };
-
-            let mut old_dock_position = this.position(cx);
-            this.subscriptions.push(cx.observe_global::<SettingsStore>(
-                move |this: &mut Self, cx| {
-                    let new_dock_position = this.position(cx);
-                    if new_dock_position != old_dock_position {
-                        old_dock_position = new_dock_position;
-                        cx.emit(Event::DockPositionChanged);
-                    }
-                    cx.notify();
-                },
-            ));
-
-            this
-        })
-    }
-
-    pub fn is_scrolled_to_bottom(&self) -> bool {
-        self.is_scrolled_to_bottom
-    }
-
-    pub fn active_chat(&self) -> Option<Model<ChannelChat>> {
-        self.active_chat.as_ref().map(|(chat, _)| chat.clone())
-    }
-
-    pub fn load(
-        workspace: WeakView<Workspace>,
-        cx: AsyncWindowContext,
-    ) -> Task<Result<View<Self>>> {
-        cx.spawn(|mut cx| async move {
-            let serialized_panel = if let Some(panel) = cx
-                .background_executor()
-                .spawn(async move { KEY_VALUE_STORE.read_kvp(CHAT_PANEL_KEY) })
-                .await
-                .log_err()
-                .flatten()
-            {
-                Some(serde_json::from_str::<SerializedChatPanel>(&panel)?)
-            } else {
-                None
-            };
-
-            workspace.update(&mut cx, |workspace, cx| {
-                let panel = Self::new(workspace, cx);
-                if let Some(serialized_panel) = serialized_panel {
-                    panel.update(cx, |panel, cx| {
-                        panel.width = serialized_panel.width;
-                        cx.notify();
-                    });
-                }
-                panel
-            })
-        })
-    }
-
-    fn serialize(&mut self, cx: &mut ViewContext<Self>) {
-        let width = self.width;
-        self.pending_serialization = cx.background_executor().spawn(
-            async move {
-                KEY_VALUE_STORE
-                    .write_kvp(
-                        CHAT_PANEL_KEY.into(),
-                        serde_json::to_string(&SerializedChatPanel { width })?,
-                    )
-                    .await?;
-                anyhow::Ok(())
-            }
-            .log_err(),
-        );
-    }
-
-    fn set_active_chat(&mut self, chat: Model<ChannelChat>, cx: &mut ViewContext<Self>) {
-        if self.active_chat.as_ref().map(|e| &e.0) != Some(&chat) {
-            let channel_id = chat.read(cx).channel_id;
-            {
-                self.markdown_data.clear();
-                let chat = chat.read(cx);
-                self.message_list.reset(chat.message_count());
-
-                let channel_name = chat.channel(cx).map(|channel| channel.name.clone());
-                self.input_editor.update(cx, |editor, cx| {
-                    editor.set_channel(channel_id, channel_name, cx);
-                });
-            };
-            let subscription = cx.subscribe(&chat, Self::channel_did_change);
-            self.active_chat = Some((chat, subscription));
-            self.acknowledge_last_message(cx);
-            cx.notify();
-        }
-    }
-
-    fn channel_did_change(
-        &mut self,
-        _: Model<ChannelChat>,
-        event: &ChannelChatEvent,
-        cx: &mut ViewContext<Self>,
-    ) {
-        match event {
-            ChannelChatEvent::MessagesUpdated {
-                old_range,
-                new_count,
-            } => {
-                self.message_list.splice(old_range.clone(), *new_count);
-                if self.active {
-                    self.acknowledge_last_message(cx);
-                }
-            }
-            ChannelChatEvent::NewMessage {
-                channel_id,
-                message_id,
-            } => {
-                if !self.active {
-                    self.channel_store.update(cx, |store, cx| {
-                        store.new_message(*channel_id, *message_id, cx)
-                    })
-                }
-            }
-        }
-        cx.notify();
-    }
-
-    fn acknowledge_last_message(&mut self, cx: &mut ViewContext<Self>) {
-        if self.active && self.is_scrolled_to_bottom {
-            if let Some((chat, _)) = &self.active_chat {
-                chat.update(cx, |chat, cx| {
-                    chat.acknowledge_last_message(cx);
-                });
-            }
-        }
-    }
-
-    fn render_channel(&self, cx: &mut ViewContext<Self>) -> AnyElement {
-        v_stack()
-            .full()
-            .on_action(cx.listener(Self::send))
-            .child(
-                h_stack().z_index(1).child(
-                    TabBar::new("chat_header")
-                        .child(
-                            h_stack()
-                                .w_full()
-                                .h(rems(ui::Tab::HEIGHT_IN_REMS))
-                                .px_2()
-                                .child(Label::new(
-                                    self.active_chat
-                                        .as_ref()
-                                        .and_then(|c| {
-                                            Some(format!("#{}", c.0.read(cx).channel(cx)?.name))
-                                        })
-                                        .unwrap_or_default(),
-                                )),
-                        )
-                        .end_child(
-                            IconButton::new("notes", Icon::File)
-                                .on_click(cx.listener(Self::open_notes))
-                                .tooltip(|cx| Tooltip::text("Open notes", cx)),
-                        )
-                        .end_child(
-                            IconButton::new("call", Icon::AudioOn)
-                                .on_click(cx.listener(Self::join_call))
-                                .tooltip(|cx| Tooltip::text("Join call", cx)),
-                        ),
-                ),
-            )
-            .child(div().flex_grow().px_2().py_1().map(|this| {
-                if self.active_chat.is_some() {
-                    this.child(list(self.message_list.clone()).full())
-                } else {
-                    this
-                }
-            }))
-            .child(
-                div()
-                    .z_index(1)
-                    .p_2()
-                    .bg(cx.theme().colors().background)
-                    .child(self.input_editor.clone()),
-            )
-            .into_any()
-    }
-
-    fn render_message(&mut self, ix: usize, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        let active_chat = &self.active_chat.as_ref().unwrap().0;
-        let (message, is_continuation_from_previous, is_continuation_to_next, is_admin) =
-            active_chat.update(cx, |active_chat, cx| {
-                let is_admin = self
-                    .channel_store
-                    .read(cx)
-                    .is_channel_admin(active_chat.channel_id);
-
-                let last_message = active_chat.message(ix.saturating_sub(1));
-                let this_message = active_chat.message(ix).clone();
-                let next_message =
-                    active_chat.message(ix.saturating_add(1).min(active_chat.message_count() - 1));
-
-                let is_continuation_from_previous = last_message.id != this_message.id
-                    && last_message.sender.id == this_message.sender.id;
-                let is_continuation_to_next = this_message.id != next_message.id
-                    && this_message.sender.id == next_message.sender.id;
-
-                if let ChannelMessageId::Saved(id) = this_message.id {
-                    if this_message
-                        .mentions
-                        .iter()
-                        .any(|(_, user_id)| Some(*user_id) == self.client.user_id())
-                    {
-                        active_chat.acknowledge_message(id);
-                    }
-                }
-
-                (
-                    this_message,
-                    is_continuation_from_previous,
-                    is_continuation_to_next,
-                    is_admin,
-                )
-            });
-
-        let _is_pending = message.is_pending();
-        let text = self.markdown_data.entry(message.id).or_insert_with(|| {
-            Self::render_markdown_with_mentions(&self.languages, self.client.id(), &message)
-        });
-
-        let now = OffsetDateTime::now_utc();
-
-        let belongs_to_user = Some(message.sender.id) == self.client.user_id();
-        let message_id_to_remove = if let (ChannelMessageId::Saved(id), true) =
-            (message.id, belongs_to_user || is_admin)
-        {
-            Some(id)
-        } else {
-            None
-        };
-
-        let element_id: ElementId = match message.id {
-            ChannelMessageId::Saved(id) => ("saved-message", id).into(),
-            ChannelMessageId::Pending(id) => ("pending-message", id).into(),
-        };
-
-        v_stack()
-            .w_full()
-            .id(element_id)
-            .relative()
-            .overflow_hidden()
-            .group("")
-            .when(!is_continuation_from_previous, |this| {
-                this.child(
-                    h_stack()
-                        .gap_2()
-                        .child(Avatar::new(message.sender.avatar_uri.clone()))
-                        .child(Label::new(message.sender.github_login.clone()))
-                        .child(
-                            Label::new(format_timestamp(
-                                message.timestamp,
-                                now,
-                                self.local_timezone,
-                            ))
-                            .color(Color::Muted),
-                        ),
-                )
-            })
-            .when(!is_continuation_to_next, |this|
-                // HACK: This should really be a margin, but margins seem to get collapsed.
-                this.pb_2())
-            .child(text.element("body".into(), cx))
-            .child(
-                div()
-                    .absolute()
-                    .top_1()
-                    .right_2()
-                    .w_8()
-                    .visible_on_hover("")
-                    .children(message_id_to_remove.map(|message_id| {
-                        IconButton::new(("remove", message_id), Icon::XCircle).on_click(
-                            cx.listener(move |this, _, cx| {
-                                this.remove_message(message_id, cx);
-                            }),
-                        )
-                    })),
-            )
-    }
-
-    fn render_markdown_with_mentions(
-        language_registry: &Arc<LanguageRegistry>,
-        current_user_id: u64,
-        message: &channel::ChannelMessage,
-    ) -> RichText {
-        let mentions = message
-            .mentions
-            .iter()
-            .map(|(range, user_id)| rich_text::Mention {
-                range: range.clone(),
-                is_self_mention: *user_id == current_user_id,
-            })
-            .collect::<Vec<_>>();
-
-        rich_text::render_markdown(message.body.clone(), &mentions, language_registry, None)
-    }
-
-    fn render_sign_in_prompt(&self, cx: &mut ViewContext<Self>) -> AnyElement {
-        Button::new("sign-in", "Sign in to use chat")
-            .on_click(cx.listener(move |this, _, cx| {
-                let client = this.client.clone();
-                cx.spawn(|this, mut cx| async move {
-                    if client
-                        .authenticate_and_connect(true, &cx)
-                        .log_err()
-                        .await
-                        .is_some()
-                    {
-                        this.update(&mut cx, |_, cx| {
-                            cx.focus_self();
-                        })
-                        .ok();
-                    }
-                })
-                .detach();
-            }))
-            .into_any_element()
-    }
-
-    fn send(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
-        if let Some((chat, _)) = self.active_chat.as_ref() {
-            let message = self
-                .input_editor
-                .update(cx, |editor, cx| editor.take_message(cx));
-
-            if let Some(task) = chat
-                .update(cx, |chat, cx| chat.send_message(message, cx))
-                .log_err()
-            {
-                task.detach();
-            }
-        }
-    }
-
-    fn remove_message(&mut self, id: u64, cx: &mut ViewContext<Self>) {
-        if let Some((chat, _)) = self.active_chat.as_ref() {
-            chat.update(cx, |chat, cx| chat.remove_message(id, cx).detach())
-        }
-    }
-
-    fn load_more_messages(&mut self, cx: &mut ViewContext<Self>) {
-        if let Some((chat, _)) = self.active_chat.as_ref() {
-            chat.update(cx, |channel, cx| {
-                if let Some(task) = channel.load_more_messages(cx) {
-                    task.detach();
-                }
-            })
-        }
-    }
-
-    pub fn select_channel(
-        &mut self,
-        selected_channel_id: u64,
-        scroll_to_message_id: Option<u64>,
-        cx: &mut ViewContext<ChatPanel>,
-    ) -> Task<Result<()>> {
-        let open_chat = self
-            .active_chat
-            .as_ref()
-            .and_then(|(chat, _)| {
-                (chat.read(cx).channel_id == selected_channel_id)
-                    .then(|| Task::ready(anyhow::Ok(chat.clone())))
-            })
-            .unwrap_or_else(|| {
-                self.channel_store.update(cx, |store, cx| {
-                    store.open_channel_chat(selected_channel_id, cx)
-                })
-            });
-
-        cx.spawn(|this, mut cx| async move {
-            let chat = open_chat.await?;
-            this.update(&mut cx, |this, cx| {
-                this.set_active_chat(chat.clone(), cx);
-            })?;
-
-            if let Some(message_id) = scroll_to_message_id {
-                if let Some(item_ix) =
-                    ChannelChat::load_history_since_message(chat.clone(), message_id, (*cx).clone())
-                        .await
-                {
-                    this.update(&mut cx, |this, cx| {
-                        if this.active_chat.as_ref().map_or(false, |(c, _)| *c == chat) {
-                            this.message_list.scroll_to(ListOffset {
-                                item_ix,
-                                offset_in_item: px(0.0),
-                            });
-                            cx.notify();
-                        }
-                    })?;
-                }
-            }
-
-            Ok(())
-        })
-    }
-
-    fn open_notes(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
-        if let Some((chat, _)) = &self.active_chat {
-            let channel_id = chat.read(cx).channel_id;
-            if let Some(workspace) = self.workspace.upgrade() {
-                ChannelView::open(channel_id, workspace, cx).detach();
-            }
-        }
-    }
-
-    fn join_call(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
-        if let Some((chat, _)) = &self.active_chat {
-            let channel_id = chat.read(cx).channel_id;
-            ActiveCall::global(cx)
-                .update(cx, |call, cx| call.join_channel(channel_id, cx))
-                .detach_and_log_err(cx);
-        }
-    }
-}
-
-impl EventEmitter<Event> for ChatPanel {}
-
-impl Render for ChatPanel {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        div()
-            .full()
-            .child(if self.client.user_id().is_some() {
-                self.render_channel(cx)
-            } else {
-                self.render_sign_in_prompt(cx)
-            })
-            .min_w(px(150.))
-    }
-}
-
-impl FocusableView for ChatPanel {
-    fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
-        self.input_editor.read(cx).focus_handle(cx)
-    }
-}
-
-impl Panel for ChatPanel {
-    fn position(&self, cx: &gpui::WindowContext) -> DockPosition {
-        ChatPanelSettings::get_global(cx).dock
-    }
-
-    fn position_is_valid(&self, position: DockPosition) -> bool {
-        matches!(position, DockPosition::Left | DockPosition::Right)
-    }
-
-    fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
-        settings::update_settings_file::<ChatPanelSettings>(self.fs.clone(), cx, move |settings| {
-            settings.dock = Some(position)
-        });
-    }
-
-    fn size(&self, cx: &gpui::WindowContext) -> Pixels {
-        self.width
-            .unwrap_or_else(|| ChatPanelSettings::get_global(cx).default_width)
-    }
-
-    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
-        self.width = size;
-        self.serialize(cx);
-        cx.notify();
-    }
-
-    fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
-        self.active = active;
-        if active {
-            self.acknowledge_last_message(cx);
-            if !is_channels_feature_enabled(cx) {
-                cx.emit(Event::Dismissed);
-            }
-        }
-    }
-
-    fn persistent_name() -> &'static str {
-        "ChatPanel"
-    }
-
-    fn icon(&self, _cx: &WindowContext) -> Option<ui::Icon> {
-        Some(ui::Icon::MessageBubbles)
-    }
-
-    fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
-        Some("Chat Panel")
-    }
-
-    fn toggle_action(&self) -> Box<dyn gpui::Action> {
-        Box::new(ToggleFocus)
-    }
-}
-
-impl EventEmitter<PanelEvent> for ChatPanel {}
-
-fn format_timestamp(
-    mut timestamp: OffsetDateTime,
-    mut now: OffsetDateTime,
-    local_timezone: UtcOffset,
-) -> String {
-    timestamp = timestamp.to_offset(local_timezone);
-    now = now.to_offset(local_timezone);
-
-    let today = now.date();
-    let date = timestamp.date();
-    let mut hour = timestamp.hour();
-    let mut part = "am";
-    if hour > 12 {
-        hour -= 12;
-        part = "pm";
-    }
-    if date == today {
-        format!("{:02}:{:02}{}", hour, timestamp.minute(), part)
-    } else if date.next_day() == Some(today) {
-        format!("yesterday at {:02}:{:02}{}", hour, timestamp.minute(), part)
-    } else {
-        format!("{:02}/{}/{}", date.month() as u32, date.day(), date.year())
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use gpui::HighlightStyle;
-    use pretty_assertions::assert_eq;
-    use rich_text::Highlight;
-    use util::test::marked_text_ranges;
-
-    #[gpui::test]
-    fn test_render_markdown_with_mentions() {
-        let language_registry = Arc::new(LanguageRegistry::test());
-        let (body, ranges) = marked_text_ranges("*hi*, «@abc», let's **call** «@fgh»", false);
-        let message = channel::ChannelMessage {
-            id: ChannelMessageId::Saved(0),
-            body,
-            timestamp: OffsetDateTime::now_utc(),
-            sender: Arc::new(client::User {
-                github_login: "fgh".into(),
-                avatar_uri: "avatar_fgh".into(),
-                id: 103,
-            }),
-            nonce: 5,
-            mentions: vec![(ranges[0].clone(), 101), (ranges[1].clone(), 102)],
-        };
-
-        let message = ChatPanel::render_markdown_with_mentions(&language_registry, 102, &message);
-
-        // Note that the "'" was replaced with ’ due to smart punctuation.
-        let (body, ranges) = marked_text_ranges("«hi», «@abc», let’s «call» «@fgh»", false);
-        assert_eq!(message.text, body);
-        assert_eq!(
-            message.highlights,
-            vec![
-                (
-                    ranges[0].clone(),
-                    HighlightStyle {
-                        font_style: Some(gpui::FontStyle::Italic),
-                        ..Default::default()
-                    }
-                    .into()
-                ),
-                (ranges[1].clone(), Highlight::Mention),
-                (
-                    ranges[2].clone(),
-                    HighlightStyle {
-                        font_weight: Some(gpui::FontWeight::BOLD),
-                        ..Default::default()
-                    }
-                    .into()
-                ),
-                (ranges[3].clone(), Highlight::SelfMention)
-            ]
-        );
-    }
-}

crates/collab_ui2/src/chat_panel/message_editor.rs 🔗

@@ -1,296 +0,0 @@
-use channel::{ChannelId, ChannelMembership, ChannelStore, MessageParams};
-use client::UserId;
-use collections::HashMap;
-use editor::{AnchorRangeExt, Editor};
-use gpui::{
-    AsyncWindowContext, FocusableView, IntoElement, Model, Render, SharedString, Task, View,
-    ViewContext, WeakView,
-};
-use language::{language_settings::SoftWrap, Buffer, BufferSnapshot, LanguageRegistry};
-use lazy_static::lazy_static;
-use project::search::SearchQuery;
-use std::{sync::Arc, time::Duration};
-use workspace::item::ItemHandle;
-
-const MENTIONS_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(50);
-
-lazy_static! {
-    static ref MENTIONS_SEARCH: SearchQuery =
-        SearchQuery::regex("@[-_\\w]+", false, false, false, Vec::new(), Vec::new()).unwrap();
-}
-
-pub struct MessageEditor {
-    pub editor: View<Editor>,
-    channel_store: Model<ChannelStore>,
-    users: HashMap<String, UserId>,
-    mentions: Vec<UserId>,
-    mentions_task: Option<Task<()>>,
-    channel_id: Option<ChannelId>,
-}
-
-impl MessageEditor {
-    pub fn new(
-        language_registry: Arc<LanguageRegistry>,
-        channel_store: Model<ChannelStore>,
-        editor: View<Editor>,
-        cx: &mut ViewContext<Self>,
-    ) -> Self {
-        editor.update(cx, |editor, cx| {
-            editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
-        });
-
-        let buffer = editor
-            .read(cx)
-            .buffer()
-            .read(cx)
-            .as_singleton()
-            .expect("message editor must be singleton");
-
-        cx.subscribe(&buffer, Self::on_buffer_event).detach();
-
-        let markdown = language_registry.language_for_name("Markdown");
-        cx.spawn(|_, mut cx| async move {
-            let markdown = markdown.await?;
-            buffer.update(&mut cx, |buffer, cx| {
-                buffer.set_language(Some(markdown), cx)
-            })
-        })
-        .detach_and_log_err(cx);
-
-        Self {
-            editor,
-            channel_store,
-            users: HashMap::default(),
-            channel_id: None,
-            mentions: Vec::new(),
-            mentions_task: None,
-        }
-    }
-
-    pub fn set_channel(
-        &mut self,
-        channel_id: u64,
-        channel_name: Option<SharedString>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.editor.update(cx, |editor, cx| {
-            if let Some(channel_name) = channel_name {
-                editor.set_placeholder_text(format!("Message #{}", channel_name), cx);
-            } else {
-                editor.set_placeholder_text(format!("Message Channel"), cx);
-            }
-        });
-        self.channel_id = Some(channel_id);
-        self.refresh_users(cx);
-    }
-
-    pub fn refresh_users(&mut self, cx: &mut ViewContext<Self>) {
-        if let Some(channel_id) = self.channel_id {
-            let members = self.channel_store.update(cx, |store, cx| {
-                store.get_channel_member_details(channel_id, cx)
-            });
-            cx.spawn(|this, mut cx| async move {
-                let members = members.await?;
-                this.update(&mut cx, |this, cx| this.set_members(members, cx))?;
-                anyhow::Ok(())
-            })
-            .detach_and_log_err(cx);
-        }
-    }
-
-    pub fn set_members(&mut self, members: Vec<ChannelMembership>, _: &mut ViewContext<Self>) {
-        self.users.clear();
-        self.users.extend(
-            members
-                .into_iter()
-                .map(|member| (member.user.github_login.clone(), member.user.id)),
-        );
-    }
-
-    pub fn take_message(&mut self, cx: &mut ViewContext<Self>) -> MessageParams {
-        self.editor.update(cx, |editor, cx| {
-            let highlights = editor.text_highlights::<Self>(cx);
-            let text = editor.text(cx);
-            let snapshot = editor.buffer().read(cx).snapshot(cx);
-            let mentions = if let Some((_, ranges)) = highlights {
-                ranges
-                    .iter()
-                    .map(|range| range.to_offset(&snapshot))
-                    .zip(self.mentions.iter().copied())
-                    .collect()
-            } else {
-                Vec::new()
-            };
-
-            editor.clear(cx);
-            self.mentions.clear();
-
-            MessageParams { text, mentions }
-        })
-    }
-
-    fn on_buffer_event(
-        &mut self,
-        buffer: Model<Buffer>,
-        event: &language::Event,
-        cx: &mut ViewContext<Self>,
-    ) {
-        if let language::Event::Reparsed | language::Event::Edited = event {
-            let buffer = buffer.read(cx).snapshot();
-            self.mentions_task = Some(cx.spawn(|this, cx| async move {
-                cx.background_executor()
-                    .timer(MENTIONS_DEBOUNCE_INTERVAL)
-                    .await;
-                Self::find_mentions(this, buffer, cx).await;
-            }));
-        }
-    }
-
-    async fn find_mentions(
-        this: WeakView<MessageEditor>,
-        buffer: BufferSnapshot,
-        mut cx: AsyncWindowContext,
-    ) {
-        let (buffer, ranges) = cx
-            .background_executor()
-            .spawn(async move {
-                let ranges = MENTIONS_SEARCH.search(&buffer, None).await;
-                (buffer, ranges)
-            })
-            .await;
-
-        this.update(&mut cx, |this, cx| {
-            let mut anchor_ranges = Vec::new();
-            let mut mentioned_user_ids = Vec::new();
-            let mut text = String::new();
-
-            this.editor.update(cx, |editor, cx| {
-                let multi_buffer = editor.buffer().read(cx).snapshot(cx);
-                for range in ranges {
-                    text.clear();
-                    text.extend(buffer.text_for_range(range.clone()));
-                    if let Some(username) = text.strip_prefix("@") {
-                        if let Some(user_id) = this.users.get(username) {
-                            let start = multi_buffer.anchor_after(range.start);
-                            let end = multi_buffer.anchor_after(range.end);
-
-                            mentioned_user_ids.push(*user_id);
-                            anchor_ranges.push(start..end);
-                        }
-                    }
-                }
-
-                editor.clear_highlights::<Self>(cx);
-                editor.highlight_text::<Self>(anchor_ranges, gpui::red().into(), cx)
-            });
-
-            this.mentions = mentioned_user_ids;
-            this.mentions_task.take();
-        })
-        .ok();
-    }
-
-    pub(crate) fn focus_handle(&self, cx: &gpui::AppContext) -> gpui::FocusHandle {
-        self.editor.read(cx).focus_handle(cx)
-    }
-}
-
-impl Render for MessageEditor {
-    fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
-        self.editor.to_any()
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use client::{Client, User, UserStore};
-    use gpui::{Context as _, TestAppContext, VisualContext as _};
-    use language::{Language, LanguageConfig};
-    use rpc::proto;
-    use settings::SettingsStore;
-    use util::{http::FakeHttpClient, test::marked_text_ranges};
-
-    #[gpui::test]
-    async fn test_message_editor(cx: &mut TestAppContext) {
-        let language_registry = init_test(cx);
-
-        let (editor, cx) = cx.add_window_view(|cx| {
-            MessageEditor::new(
-                language_registry,
-                ChannelStore::global(cx),
-                cx.new_view(|cx| Editor::auto_height(4, cx)),
-                cx,
-            )
-        });
-        cx.executor().run_until_parked();
-
-        editor.update(cx, |editor, cx| {
-            editor.set_members(
-                vec![
-                    ChannelMembership {
-                        user: Arc::new(User {
-                            github_login: "a-b".into(),
-                            id: 101,
-                            avatar_uri: "avatar_a-b".into(),
-                        }),
-                        kind: proto::channel_member::Kind::Member,
-                        role: proto::ChannelRole::Member,
-                    },
-                    ChannelMembership {
-                        user: Arc::new(User {
-                            github_login: "C_D".into(),
-                            id: 102,
-                            avatar_uri: "avatar_C_D".into(),
-                        }),
-                        kind: proto::channel_member::Kind::Member,
-                        role: proto::ChannelRole::Member,
-                    },
-                ],
-                cx,
-            );
-
-            editor.editor.update(cx, |editor, cx| {
-                editor.set_text("Hello, @a-b! Have you met @C_D?", cx)
-            });
-        });
-
-        cx.executor().advance_clock(MENTIONS_DEBOUNCE_INTERVAL);
-
-        editor.update(cx, |editor, cx| {
-            let (text, ranges) = marked_text_ranges("Hello, «@a-b»! Have you met «@C_D»?", false);
-            assert_eq!(
-                editor.take_message(cx),
-                MessageParams {
-                    text,
-                    mentions: vec![(ranges[0].clone(), 101), (ranges[1].clone(), 102)],
-                }
-            );
-        });
-    }
-
-    fn init_test(cx: &mut TestAppContext) -> Arc<LanguageRegistry> {
-        cx.update(|cx| {
-            let http = FakeHttpClient::with_404_response();
-            let client = Client::new(http.clone(), cx);
-            let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
-            let settings = SettingsStore::test(cx);
-            cx.set_global(settings);
-            theme::init(theme::LoadThemes::JustBase, cx);
-            language::init(cx);
-            editor::init(cx);
-            client::init(&client, cx);
-            channel::init(&client, user_store, cx);
-        });
-
-        let language_registry = Arc::new(LanguageRegistry::test());
-        language_registry.add(Arc::new(Language::new(
-            LanguageConfig {
-                name: "Markdown".into(),
-                ..Default::default()
-            },
-            Some(tree_sitter_markdown::language()),
-        )));
-        language_registry
-    }
-}

crates/collab_ui2/src/collab_panel.rs 🔗

@@ -1,2539 +0,0 @@
-mod channel_modal;
-mod contact_finder;
-
-use self::channel_modal::ChannelModal;
-use crate::{
-    channel_view::ChannelView, chat_panel::ChatPanel, face_pile::FacePile,
-    CollaborationPanelSettings,
-};
-use call::ActiveCall;
-use channel::{Channel, ChannelEvent, ChannelId, ChannelStore};
-use client::{Client, Contact, User, UserStore};
-use contact_finder::ContactFinder;
-use db::kvp::KEY_VALUE_STORE;
-use editor::Editor;
-use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt};
-use fuzzy::{match_strings, StringMatchCandidate};
-use gpui::{
-    actions, canvas, div, fill, list, overlay, point, prelude::*, px, serde_json, AnyElement,
-    AppContext, AsyncWindowContext, Bounds, ClipboardItem, DismissEvent, Div, EventEmitter,
-    FocusHandle, FocusableView, InteractiveElement, IntoElement, ListOffset, ListState, Model,
-    MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, RenderOnce, SharedString,
-    Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView,
-};
-use menu::{Cancel, Confirm, SelectNext, SelectPrev};
-use project::{Fs, Project};
-use rpc::proto::{self, PeerId};
-use serde_derive::{Deserialize, Serialize};
-use settings::{Settings, SettingsStore};
-use smallvec::SmallVec;
-use std::{mem, sync::Arc};
-use theme::{ActiveTheme, ThemeSettings};
-use ui::prelude::*;
-use ui::{
-    h_stack, v_stack, Avatar, Button, Color, ContextMenu, Icon, IconButton, IconElement, IconSize,
-    Label, ListHeader, ListItem, Tooltip,
-};
-use util::{maybe, ResultExt, TryFutureExt};
-use workspace::{
-    dock::{DockPosition, Panel, PanelEvent},
-    notifications::NotifyResultExt,
-    Workspace,
-};
-
-actions!(
-    collab_panel,
-    [
-        ToggleFocus,
-        Remove,
-        Secondary,
-        CollapseSelectedChannel,
-        ExpandSelectedChannel,
-        StartMoveChannel,
-        MoveSelected,
-        InsertSpace,
-    ]
-);
-
-#[derive(Debug, Copy, Clone, PartialEq, Eq)]
-struct ChannelMoveClipboard {
-    channel_id: ChannelId,
-}
-
-const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel";
-
-pub fn init(cx: &mut AppContext) {
-    cx.observe_new_views(|workspace: &mut Workspace, _| {
-        workspace.register_action(|workspace, _: &ToggleFocus, cx| {
-            workspace.toggle_panel_focus::<CollabPanel>(cx);
-        });
-    })
-    .detach();
-}
-
-#[derive(Debug)]
-pub enum ChannelEditingState {
-    Create {
-        location: Option<ChannelId>,
-        pending_name: Option<String>,
-    },
-    Rename {
-        location: ChannelId,
-        pending_name: Option<String>,
-    },
-}
-
-impl ChannelEditingState {
-    fn pending_name(&self) -> Option<String> {
-        match self {
-            ChannelEditingState::Create { pending_name, .. } => pending_name.clone(),
-            ChannelEditingState::Rename { pending_name, .. } => pending_name.clone(),
-        }
-    }
-}
-
-pub struct CollabPanel {
-    width: Option<Pixels>,
-    fs: Arc<dyn Fs>,
-    focus_handle: FocusHandle,
-    channel_clipboard: Option<ChannelMoveClipboard>,
-    pending_serialization: Task<Option<()>>,
-    context_menu: Option<(View<ContextMenu>, Point<Pixels>, Subscription)>,
-    list_state: ListState,
-    filter_editor: View<Editor>,
-    channel_name_editor: View<Editor>,
-    channel_editing_state: Option<ChannelEditingState>,
-    entries: Vec<ListEntry>,
-    selection: Option<usize>,
-    channel_store: Model<ChannelStore>,
-    user_store: Model<UserStore>,
-    client: Arc<Client>,
-    project: Model<Project>,
-    match_candidates: Vec<StringMatchCandidate>,
-    subscriptions: Vec<Subscription>,
-    collapsed_sections: Vec<Section>,
-    collapsed_channels: Vec<ChannelId>,
-    workspace: WeakView<Workspace>,
-}
-
-#[derive(Serialize, Deserialize)]
-struct SerializedCollabPanel {
-    width: Option<Pixels>,
-    collapsed_channels: Option<Vec<u64>>,
-}
-
-#[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)]
-enum Section {
-    ActiveCall,
-    Channels,
-    ChannelInvites,
-    ContactRequests,
-    Contacts,
-    Online,
-    Offline,
-}
-
-#[derive(Clone, Debug)]
-enum ListEntry {
-    Header(Section),
-    CallParticipant {
-        user: Arc<User>,
-        peer_id: Option<PeerId>,
-        is_pending: bool,
-    },
-    ParticipantProject {
-        project_id: u64,
-        worktree_root_names: Vec<String>,
-        host_user_id: u64,
-        is_last: bool,
-    },
-    ParticipantScreen {
-        peer_id: Option<PeerId>,
-        is_last: bool,
-    },
-    IncomingRequest(Arc<User>),
-    OutgoingRequest(Arc<User>),
-    ChannelInvite(Arc<Channel>),
-    Channel {
-        channel: Arc<Channel>,
-        depth: usize,
-        has_children: bool,
-    },
-    ChannelNotes {
-        channel_id: ChannelId,
-    },
-    ChannelChat {
-        channel_id: ChannelId,
-    },
-    ChannelEditor {
-        depth: usize,
-    },
-    Contact {
-        contact: Arc<Contact>,
-        calling: bool,
-    },
-    ContactPlaceholder,
-}
-
-impl CollabPanel {
-    pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
-        cx.new_view(|cx| {
-            let filter_editor = cx.new_view(|cx| {
-                let mut editor = Editor::single_line(cx);
-                editor.set_placeholder_text("Filter...", cx);
-                editor
-            });
-
-            cx.subscribe(&filter_editor, |this: &mut Self, _, event, cx| {
-                if let editor::EditorEvent::BufferEdited = event {
-                    let query = this.filter_editor.read(cx).text(cx);
-                    if !query.is_empty() {
-                        this.selection.take();
-                    }
-                    this.update_entries(true, cx);
-                    if !query.is_empty() {
-                        this.selection = this
-                            .entries
-                            .iter()
-                            .position(|entry| !matches!(entry, ListEntry::Header(_)));
-                    }
-                }
-            })
-            .detach();
-
-            let channel_name_editor = cx.new_view(|cx| Editor::single_line(cx));
-
-            cx.subscribe(&channel_name_editor, |this: &mut Self, _, event, cx| {
-                if let editor::EditorEvent::Blurred = event {
-                    if let Some(state) = &this.channel_editing_state {
-                        if state.pending_name().is_some() {
-                            return;
-                        }
-                    }
-                    this.take_editing_state(cx);
-                    this.update_entries(false, cx);
-                    cx.notify();
-                }
-            })
-            .detach();
-
-            let view = cx.view().downgrade();
-            let list_state =
-                ListState::new(0, gpui::ListAlignment::Top, px(1000.), move |ix, cx| {
-                    if let Some(view) = view.upgrade() {
-                        view.update(cx, |view, cx| view.render_list_entry(ix, cx))
-                    } else {
-                        div().into_any()
-                    }
-                });
-
-            let mut this = Self {
-                width: None,
-                focus_handle: cx.focus_handle(),
-                channel_clipboard: None,
-                fs: workspace.app_state().fs.clone(),
-                pending_serialization: Task::ready(None),
-                context_menu: None,
-                list_state,
-                channel_name_editor,
-                filter_editor,
-                entries: Vec::default(),
-                channel_editing_state: None,
-                selection: None,
-                channel_store: ChannelStore::global(cx),
-                user_store: workspace.user_store().clone(),
-                project: workspace.project().clone(),
-                subscriptions: Vec::default(),
-                match_candidates: Vec::default(),
-                collapsed_sections: vec![Section::Offline],
-                collapsed_channels: Vec::default(),
-                workspace: workspace.weak_handle(),
-                client: workspace.app_state().client.clone(),
-            };
-
-            this.update_entries(false, cx);
-
-            // Update the dock position when the setting changes.
-            let mut old_dock_position = this.position(cx);
-            this.subscriptions.push(cx.observe_global::<SettingsStore>(
-                move |this: &mut Self, cx| {
-                    let new_dock_position = this.position(cx);
-                    if new_dock_position != old_dock_position {
-                        old_dock_position = new_dock_position;
-                        cx.emit(PanelEvent::ChangePosition);
-                    }
-                    cx.notify();
-                },
-            ));
-
-            let active_call = ActiveCall::global(cx);
-            this.subscriptions
-                .push(cx.observe(&this.user_store, |this, _, cx| {
-                    this.update_entries(true, cx)
-                }));
-            this.subscriptions
-                .push(cx.observe(&this.channel_store, |this, _, cx| {
-                    this.update_entries(true, cx)
-                }));
-            this.subscriptions
-                .push(cx.observe(&active_call, |this, _, cx| this.update_entries(true, cx)));
-            this.subscriptions
-                .push(cx.observe_flag::<ChannelsAlpha, _>(move |_, this, cx| {
-                    this.update_entries(true, cx)
-                }));
-            this.subscriptions.push(cx.subscribe(
-                &this.channel_store,
-                |this, _channel_store, e, cx| match e {
-                    ChannelEvent::ChannelCreated(channel_id)
-                    | ChannelEvent::ChannelRenamed(channel_id) => {
-                        if this.take_editing_state(cx) {
-                            this.update_entries(false, cx);
-                            this.selection = this.entries.iter().position(|entry| {
-                                if let ListEntry::Channel { channel, .. } = entry {
-                                    channel.id == *channel_id
-                                } else {
-                                    false
-                                }
-                            });
-                        }
-                    }
-                },
-            ));
-
-            this
-        })
-    }
-
-    pub async fn load(
-        workspace: WeakView<Workspace>,
-        mut cx: AsyncWindowContext,
-    ) -> anyhow::Result<View<Self>> {
-        let serialized_panel = cx
-            .background_executor()
-            .spawn(async move { KEY_VALUE_STORE.read_kvp(COLLABORATION_PANEL_KEY) })
-            .await
-            .map_err(|_| anyhow::anyhow!("Failed to read collaboration panel from key value store"))
-            .log_err()
-            .flatten()
-            .map(|panel| serde_json::from_str::<SerializedCollabPanel>(&panel))
-            .transpose()
-            .log_err()
-            .flatten();
-
-        workspace.update(&mut cx, |workspace, cx| {
-            let panel = CollabPanel::new(workspace, cx);
-            if let Some(serialized_panel) = serialized_panel {
-                panel.update(cx, |panel, cx| {
-                    panel.width = serialized_panel.width;
-                    panel.collapsed_channels = serialized_panel
-                        .collapsed_channels
-                        .unwrap_or_else(|| Vec::new());
-                    cx.notify();
-                });
-            }
-            panel
-        })
-    }
-
-    fn serialize(&mut self, cx: &mut ViewContext<Self>) {
-        let width = self.width;
-        let collapsed_channels = self.collapsed_channels.clone();
-        self.pending_serialization = cx.background_executor().spawn(
-            async move {
-                KEY_VALUE_STORE
-                    .write_kvp(
-                        COLLABORATION_PANEL_KEY.into(),
-                        serde_json::to_string(&SerializedCollabPanel {
-                            width,
-                            collapsed_channels: Some(collapsed_channels),
-                        })?,
-                    )
-                    .await?;
-                anyhow::Ok(())
-            }
-            .log_err(),
-        );
-    }
-
-    fn scroll_to_item(&mut self, ix: usize) {
-        self.list_state.scroll_to_reveal_item(ix)
-    }
-
-    fn update_entries(&mut self, select_same_item: bool, cx: &mut ViewContext<Self>) {
-        let channel_store = self.channel_store.read(cx);
-        let user_store = self.user_store.read(cx);
-        let query = self.filter_editor.read(cx).text(cx);
-        let executor = cx.background_executor().clone();
-
-        let prev_selected_entry = self.selection.and_then(|ix| self.entries.get(ix).cloned());
-        let old_entries = mem::take(&mut self.entries);
-        let mut scroll_to_top = false;
-
-        if let Some(room) = ActiveCall::global(cx).read(cx).room() {
-            self.entries.push(ListEntry::Header(Section::ActiveCall));
-            if !old_entries
-                .iter()
-                .any(|entry| matches!(entry, ListEntry::Header(Section::ActiveCall)))
-            {
-                scroll_to_top = true;
-            }
-
-            if !self.collapsed_sections.contains(&Section::ActiveCall) {
-                let room = room.read(cx);
-
-                if let Some(channel_id) = room.channel_id() {
-                    self.entries.push(ListEntry::ChannelNotes { channel_id });
-                    self.entries.push(ListEntry::ChannelChat { channel_id })
-                }
-
-                // Populate the active user.
-                if let Some(user) = user_store.current_user() {
-                    self.match_candidates.clear();
-                    self.match_candidates.push(StringMatchCandidate {
-                        id: 0,
-                        string: user.github_login.clone(),
-                        char_bag: user.github_login.chars().collect(),
-                    });
-                    let matches = executor.block(match_strings(
-                        &self.match_candidates,
-                        &query,
-                        true,
-                        usize::MAX,
-                        &Default::default(),
-                        executor.clone(),
-                    ));
-                    if !matches.is_empty() {
-                        let user_id = user.id;
-                        self.entries.push(ListEntry::CallParticipant {
-                            user,
-                            peer_id: None,
-                            is_pending: false,
-                        });
-                        let mut projects = room.local_participant().projects.iter().peekable();
-                        while let Some(project) = projects.next() {
-                            self.entries.push(ListEntry::ParticipantProject {
-                                project_id: project.id,
-                                worktree_root_names: project.worktree_root_names.clone(),
-                                host_user_id: user_id,
-                                is_last: projects.peek().is_none() && !room.is_screen_sharing(),
-                            });
-                        }
-                        if room.is_screen_sharing() {
-                            self.entries.push(ListEntry::ParticipantScreen {
-                                peer_id: None,
-                                is_last: true,
-                            });
-                        }
-                    }
-                }
-
-                // Populate remote participants.
-                self.match_candidates.clear();
-                self.match_candidates
-                    .extend(room.remote_participants().iter().map(|(_, participant)| {
-                        StringMatchCandidate {
-                            id: participant.user.id as usize,
-                            string: participant.user.github_login.clone(),
-                            char_bag: participant.user.github_login.chars().collect(),
-                        }
-                    }));
-                let matches = executor.block(match_strings(
-                    &self.match_candidates,
-                    &query,
-                    true,
-                    usize::MAX,
-                    &Default::default(),
-                    executor.clone(),
-                ));
-                for mat in matches {
-                    let user_id = mat.candidate_id as u64;
-                    let participant = &room.remote_participants()[&user_id];
-                    self.entries.push(ListEntry::CallParticipant {
-                        user: participant.user.clone(),
-                        peer_id: Some(participant.peer_id),
-                        is_pending: false,
-                    });
-                    let mut projects = participant.projects.iter().peekable();
-                    while let Some(project) = projects.next() {
-                        self.entries.push(ListEntry::ParticipantProject {
-                            project_id: project.id,
-                            worktree_root_names: project.worktree_root_names.clone(),
-                            host_user_id: participant.user.id,
-                            is_last: projects.peek().is_none()
-                                && participant.video_tracks.is_empty(),
-                        });
-                    }
-                    if !participant.video_tracks.is_empty() {
-                        self.entries.push(ListEntry::ParticipantScreen {
-                            peer_id: Some(participant.peer_id),
-                            is_last: true,
-                        });
-                    }
-                }
-
-                // Populate pending participants.
-                self.match_candidates.clear();
-                self.match_candidates
-                    .extend(room.pending_participants().iter().enumerate().map(
-                        |(id, participant)| StringMatchCandidate {
-                            id,
-                            string: participant.github_login.clone(),
-                            char_bag: participant.github_login.chars().collect(),
-                        },
-                    ));
-                let matches = executor.block(match_strings(
-                    &self.match_candidates,
-                    &query,
-                    true,
-                    usize::MAX,
-                    &Default::default(),
-                    executor.clone(),
-                ));
-                self.entries
-                    .extend(matches.iter().map(|mat| ListEntry::CallParticipant {
-                        user: room.pending_participants()[mat.candidate_id].clone(),
-                        peer_id: None,
-                        is_pending: true,
-                    }));
-            }
-        }
-
-        let mut request_entries = Vec::new();
-
-        if cx.has_flag::<ChannelsAlpha>() {
-            self.entries.push(ListEntry::Header(Section::Channels));
-
-            if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() {
-                self.match_candidates.clear();
-                self.match_candidates
-                    .extend(channel_store.ordered_channels().enumerate().map(
-                        |(ix, (_, channel))| StringMatchCandidate {
-                            id: ix,
-                            string: channel.name.clone().into(),
-                            char_bag: channel.name.chars().collect(),
-                        },
-                    ));
-                let matches = executor.block(match_strings(
-                    &self.match_candidates,
-                    &query,
-                    true,
-                    usize::MAX,
-                    &Default::default(),
-                    executor.clone(),
-                ));
-                if let Some(state) = &self.channel_editing_state {
-                    if matches!(state, ChannelEditingState::Create { location: None, .. }) {
-                        self.entries.push(ListEntry::ChannelEditor { depth: 0 });
-                    }
-                }
-                let mut collapse_depth = None;
-                for mat in matches {
-                    let channel = channel_store.channel_at_index(mat.candidate_id).unwrap();
-                    let depth = channel.parent_path.len();
-
-                    if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) {
-                        collapse_depth = Some(depth);
-                    } else if let Some(collapsed_depth) = collapse_depth {
-                        if depth > collapsed_depth {
-                            continue;
-                        }
-                        if self.is_channel_collapsed(channel.id) {
-                            collapse_depth = Some(depth);
-                        } else {
-                            collapse_depth = None;
-                        }
-                    }
-
-                    let has_children = channel_store
-                        .channel_at_index(mat.candidate_id + 1)
-                        .map_or(false, |next_channel| {
-                            next_channel.parent_path.ends_with(&[channel.id])
-                        });
-
-                    match &self.channel_editing_state {
-                        Some(ChannelEditingState::Create {
-                            location: parent_id,
-                            ..
-                        }) if *parent_id == Some(channel.id) => {
-                            self.entries.push(ListEntry::Channel {
-                                channel: channel.clone(),
-                                depth,
-                                has_children: false,
-                            });
-                            self.entries
-                                .push(ListEntry::ChannelEditor { depth: depth + 1 });
-                        }
-                        Some(ChannelEditingState::Rename {
-                            location: parent_id,
-                            ..
-                        }) if parent_id == &channel.id => {
-                            self.entries.push(ListEntry::ChannelEditor { depth });
-                        }
-                        _ => {
-                            self.entries.push(ListEntry::Channel {
-                                channel: channel.clone(),
-                                depth,
-                                has_children,
-                            });
-                        }
-                    }
-                }
-            }
-
-            let channel_invites = channel_store.channel_invitations();
-            if !channel_invites.is_empty() {
-                self.match_candidates.clear();
-                self.match_candidates
-                    .extend(channel_invites.iter().enumerate().map(|(ix, channel)| {
-                        StringMatchCandidate {
-                            id: ix,
-                            string: channel.name.clone().into(),
-                            char_bag: channel.name.chars().collect(),
-                        }
-                    }));
-                let matches = executor.block(match_strings(
-                    &self.match_candidates,
-                    &query,
-                    true,
-                    usize::MAX,
-                    &Default::default(),
-                    executor.clone(),
-                ));
-                request_entries.extend(matches.iter().map(|mat| {
-                    ListEntry::ChannelInvite(channel_invites[mat.candidate_id].clone())
-                }));
-
-                if !request_entries.is_empty() {
-                    self.entries
-                        .push(ListEntry::Header(Section::ChannelInvites));
-                    if !self.collapsed_sections.contains(&Section::ChannelInvites) {
-                        self.entries.append(&mut request_entries);
-                    }
-                }
-            }
-        }
-
-        self.entries.push(ListEntry::Header(Section::Contacts));
-
-        request_entries.clear();
-        let incoming = user_store.incoming_contact_requests();
-        if !incoming.is_empty() {
-            self.match_candidates.clear();
-            self.match_candidates
-                .extend(
-                    incoming
-                        .iter()
-                        .enumerate()
-                        .map(|(ix, user)| StringMatchCandidate {
-                            id: ix,
-                            string: user.github_login.clone(),
-                            char_bag: user.github_login.chars().collect(),
-                        }),
-                );
-            let matches = executor.block(match_strings(
-                &self.match_candidates,
-                &query,
-                true,
-                usize::MAX,
-                &Default::default(),
-                executor.clone(),
-            ));
-            request_entries.extend(
-                matches
-                    .iter()
-                    .map(|mat| ListEntry::IncomingRequest(incoming[mat.candidate_id].clone())),
-            );
-        }
-
-        let outgoing = user_store.outgoing_contact_requests();
-        if !outgoing.is_empty() {
-            self.match_candidates.clear();
-            self.match_candidates
-                .extend(
-                    outgoing
-                        .iter()
-                        .enumerate()
-                        .map(|(ix, user)| StringMatchCandidate {
-                            id: ix,
-                            string: user.github_login.clone(),
-                            char_bag: user.github_login.chars().collect(),
-                        }),
-                );
-            let matches = executor.block(match_strings(
-                &self.match_candidates,
-                &query,
-                true,
-                usize::MAX,
-                &Default::default(),
-                executor.clone(),
-            ));
-            request_entries.extend(
-                matches
-                    .iter()
-                    .map(|mat| ListEntry::OutgoingRequest(outgoing[mat.candidate_id].clone())),
-            );
-        }
-
-        if !request_entries.is_empty() {
-            self.entries
-                .push(ListEntry::Header(Section::ContactRequests));
-            if !self.collapsed_sections.contains(&Section::ContactRequests) {
-                self.entries.append(&mut request_entries);
-            }
-        }
-
-        let contacts = user_store.contacts();
-        if !contacts.is_empty() {
-            self.match_candidates.clear();
-            self.match_candidates
-                .extend(
-                    contacts
-                        .iter()
-                        .enumerate()
-                        .map(|(ix, contact)| StringMatchCandidate {
-                            id: ix,
-                            string: contact.user.github_login.clone(),
-                            char_bag: contact.user.github_login.chars().collect(),
-                        }),
-                );
-
-            let matches = executor.block(match_strings(
-                &self.match_candidates,
-                &query,
-                true,
-                usize::MAX,
-                &Default::default(),
-                executor.clone(),
-            ));
-
-            let (online_contacts, offline_contacts) = matches
-                .iter()
-                .partition::<Vec<_>, _>(|mat| contacts[mat.candidate_id].online);
-
-            for (matches, section) in [
-                (online_contacts, Section::Online),
-                (offline_contacts, Section::Offline),
-            ] {
-                if !matches.is_empty() {
-                    self.entries.push(ListEntry::Header(section));
-                    if !self.collapsed_sections.contains(&section) {
-                        let active_call = &ActiveCall::global(cx).read(cx);
-                        for mat in matches {
-                            let contact = &contacts[mat.candidate_id];
-                            self.entries.push(ListEntry::Contact {
-                                contact: contact.clone(),
-                                calling: active_call.pending_invites().contains(&contact.user.id),
-                            });
-                        }
-                    }
-                }
-            }
-        }
-
-        if incoming.is_empty() && outgoing.is_empty() && contacts.is_empty() {
-            self.entries.push(ListEntry::ContactPlaceholder);
-        }
-
-        if select_same_item {
-            if let Some(prev_selected_entry) = prev_selected_entry {
-                self.selection.take();
-                for (ix, entry) in self.entries.iter().enumerate() {
-                    if *entry == prev_selected_entry {
-                        self.selection = Some(ix);
-                        break;
-                    }
-                }
-            }
-        } else {
-            self.selection = self.selection.and_then(|prev_selection| {
-                if self.entries.is_empty() {
-                    None
-                } else {
-                    Some(prev_selection.min(self.entries.len() - 1))
-                }
-            });
-        }
-
-        let old_scroll_top = self.list_state.logical_scroll_top();
-        self.list_state.reset(self.entries.len());
-
-        if scroll_to_top {
-            self.list_state.scroll_to(ListOffset::default());
-        } else {
-            // Attempt to maintain the same scroll position.
-            if let Some(old_top_entry) = old_entries.get(old_scroll_top.item_ix) {
-                let new_scroll_top = self
-                    .entries
-                    .iter()
-                    .position(|entry| entry == old_top_entry)
-                    .map(|item_ix| ListOffset {
-                        item_ix,
-                        offset_in_item: old_scroll_top.offset_in_item,
-                    })
-                    .or_else(|| {
-                        let entry_after_old_top = old_entries.get(old_scroll_top.item_ix + 1)?;
-                        let item_ix = self
-                            .entries
-                            .iter()
-                            .position(|entry| entry == entry_after_old_top)?;
-                        Some(ListOffset {
-                            item_ix,
-                            offset_in_item: Pixels::ZERO,
-                        })
-                    })
-                    .or_else(|| {
-                        let entry_before_old_top =
-                            old_entries.get(old_scroll_top.item_ix.saturating_sub(1))?;
-                        let item_ix = self
-                            .entries
-                            .iter()
-                            .position(|entry| entry == entry_before_old_top)?;
-                        Some(ListOffset {
-                            item_ix,
-                            offset_in_item: Pixels::ZERO,
-                        })
-                    });
-
-                self.list_state
-                    .scroll_to(new_scroll_top.unwrap_or(old_scroll_top));
-            }
-        }
-
-        cx.notify();
-    }
-
-    fn render_call_participant(
-        &self,
-        user: &Arc<User>,
-        peer_id: Option<PeerId>,
-        is_pending: bool,
-        is_selected: bool,
-        cx: &mut ViewContext<Self>,
-    ) -> ListItem {
-        let is_current_user =
-            self.user_store.read(cx).current_user().map(|user| user.id) == Some(user.id);
-        let tooltip = format!("Follow {}", user.github_login);
-
-        ListItem::new(SharedString::from(user.github_login.clone()))
-            .start_slot(Avatar::new(user.avatar_uri.clone()))
-            .child(Label::new(user.github_login.clone()))
-            .selected(is_selected)
-            .end_slot(if is_pending {
-                Label::new("Calling").color(Color::Muted).into_any_element()
-            } else if is_current_user {
-                IconButton::new("leave-call", Icon::Exit)
-                    .style(ButtonStyle::Subtle)
-                    .on_click(move |_, cx| Self::leave_call(cx))
-                    .tooltip(|cx| Tooltip::text("Leave Call", cx))
-                    .into_any_element()
-            } else {
-                div().into_any_element()
-            })
-            .when_some(peer_id, |this, peer_id| {
-                this.tooltip(move |cx| Tooltip::text(tooltip.clone(), cx))
-                    .on_click(cx.listener(move |this, _, cx| {
-                        this.workspace
-                            .update(cx, |workspace, cx| workspace.follow(peer_id, cx))
-                            .ok();
-                    }))
-            })
-    }
-
-    fn render_participant_project(
-        &self,
-        project_id: u64,
-        worktree_root_names: &[String],
-        host_user_id: u64,
-        is_last: bool,
-        is_selected: bool,
-        cx: &mut ViewContext<Self>,
-    ) -> impl IntoElement {
-        let project_name: SharedString = if worktree_root_names.is_empty() {
-            "untitled".to_string()
-        } else {
-            worktree_root_names.join(", ")
-        }
-        .into();
-
-        ListItem::new(project_id as usize)
-            .selected(is_selected)
-            .on_click(cx.listener(move |this, _, cx| {
-                this.workspace
-                    .update(cx, |workspace, cx| {
-                        let app_state = workspace.app_state().clone();
-                        workspace::join_remote_project(project_id, host_user_id, app_state, cx)
-                            .detach_and_log_err(cx);
-                    })
-                    .ok();
-            }))
-            .start_slot(
-                h_stack()
-                    .gap_1()
-                    .child(render_tree_branch(is_last, cx))
-                    .child(IconButton::new(0, Icon::Folder)),
-            )
-            .child(Label::new(project_name.clone()))
-            .tooltip(move |cx| Tooltip::text(format!("Open {}", project_name), cx))
-    }
-
-    fn render_participant_screen(
-        &self,
-        peer_id: Option<PeerId>,
-        is_last: bool,
-        is_selected: bool,
-        cx: &mut ViewContext<Self>,
-    ) -> impl IntoElement {
-        let id = peer_id.map_or(usize::MAX, |id| id.as_u64() as usize);
-
-        ListItem::new(("screen", id))
-            .selected(is_selected)
-            .start_slot(
-                h_stack()
-                    .gap_1()
-                    .child(render_tree_branch(is_last, cx))
-                    .child(IconButton::new(0, Icon::Screen)),
-            )
-            .child(Label::new("Screen"))
-            .when_some(peer_id, |this, _| {
-                this.on_click(cx.listener(move |this, _, cx| {
-                    this.workspace
-                        .update(cx, |workspace, cx| {
-                            workspace.open_shared_screen(peer_id.unwrap(), cx)
-                        })
-                        .ok();
-                }))
-                .tooltip(move |cx| Tooltip::text(format!("Open shared screen"), cx))
-            })
-    }
-
-    fn take_editing_state(&mut self, cx: &mut ViewContext<Self>) -> bool {
-        if let Some(_) = self.channel_editing_state.take() {
-            self.channel_name_editor.update(cx, |editor, cx| {
-                editor.set_text("", cx);
-            });
-            true
-        } else {
-            false
-        }
-    }
-
-    fn render_channel_notes(
-        &self,
-        channel_id: ChannelId,
-        is_selected: bool,
-        cx: &mut ViewContext<Self>,
-    ) -> impl IntoElement {
-        ListItem::new("channel-notes")
-            .selected(is_selected)
-            .on_click(cx.listener(move |this, _, cx| {
-                this.open_channel_notes(channel_id, cx);
-            }))
-            .start_slot(
-                h_stack()
-                    .gap_1()
-                    .child(render_tree_branch(false, cx))
-                    .child(IconButton::new(0, Icon::File)),
-            )
-            .child(div().h_7().w_full().child(Label::new("notes")))
-            .tooltip(move |cx| Tooltip::text("Open Channel Notes", cx))
-    }
-
-    fn render_channel_chat(
-        &self,
-        channel_id: ChannelId,
-        is_selected: bool,
-        cx: &mut ViewContext<Self>,
-    ) -> impl IntoElement {
-        ListItem::new("channel-chat")
-            .selected(is_selected)
-            .on_click(cx.listener(move |this, _, cx| {
-                this.join_channel_chat(channel_id, cx);
-            }))
-            .start_slot(
-                h_stack()
-                    .gap_1()
-                    .child(render_tree_branch(false, cx))
-                    .child(IconButton::new(0, Icon::MessageBubbles)),
-            )
-            .child(Label::new("chat"))
-            .tooltip(move |cx| Tooltip::text("Open Chat", cx))
-    }
-
-    fn has_subchannels(&self, ix: usize) -> bool {
-        self.entries.get(ix).map_or(false, |entry| {
-            if let ListEntry::Channel { has_children, .. } = entry {
-                *has_children
-            } else {
-                false
-            }
-        })
-    }
-
-    fn deploy_channel_context_menu(
-        &mut self,
-        position: Point<Pixels>,
-        channel_id: ChannelId,
-        ix: usize,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let clipboard_channel_name = self.channel_clipboard.as_ref().and_then(|clipboard| {
-            self.channel_store
-                .read(cx)
-                .channel_for_id(clipboard.channel_id)
-                .map(|channel| channel.name.clone())
-        });
-        let this = cx.view().clone();
-
-        let context_menu = ContextMenu::build(cx, |mut context_menu, cx| {
-            if self.has_subchannels(ix) {
-                let expand_action_name = if self.is_channel_collapsed(channel_id) {
-                    "Expand Subchannels"
-                } else {
-                    "Collapse Subchannels"
-                };
-                context_menu = context_menu.entry(
-                    expand_action_name,
-                    None,
-                    cx.handler_for(&this, move |this, cx| {
-                        this.toggle_channel_collapsed(channel_id, cx)
-                    }),
-                );
-            }
-
-            context_menu = context_menu
-                .entry(
-                    "Open Notes",
-                    None,
-                    cx.handler_for(&this, move |this, cx| {
-                        this.open_channel_notes(channel_id, cx)
-                    }),
-                )
-                .entry(
-                    "Open Chat",
-                    None,
-                    cx.handler_for(&this, move |this, cx| {
-                        this.join_channel_chat(channel_id, cx)
-                    }),
-                )
-                .entry(
-                    "Copy Channel Link",
-                    None,
-                    cx.handler_for(&this, move |this, cx| {
-                        this.copy_channel_link(channel_id, cx)
-                    }),
-                );
-
-            if self.channel_store.read(cx).is_channel_admin(channel_id) {
-                context_menu = context_menu
-                    .separator()
-                    .entry(
-                        "New Subchannel",
-                        None,
-                        cx.handler_for(&this, move |this, cx| this.new_subchannel(channel_id, cx)),
-                    )
-                    .entry(
-                        "Rename",
-                        None,
-                        cx.handler_for(&this, move |this, cx| this.rename_channel(channel_id, cx)),
-                    )
-                    .entry(
-                        "Move this channel",
-                        None,
-                        cx.handler_for(&this, move |this, cx| {
-                            this.start_move_channel(channel_id, cx)
-                        }),
-                    );
-
-                if let Some(channel_name) = clipboard_channel_name {
-                    context_menu = context_menu.separator().entry(
-                        format!("Move '#{}' here", channel_name),
-                        None,
-                        cx.handler_for(&this, move |this, cx| {
-                            this.move_channel_on_clipboard(channel_id, cx)
-                        }),
-                    );
-                }
-
-                context_menu = context_menu
-                    .separator()
-                    .entry(
-                        "Invite Members",
-                        None,
-                        cx.handler_for(&this, move |this, cx| this.invite_members(channel_id, cx)),
-                    )
-                    .entry(
-                        "Manage Members",
-                        None,
-                        cx.handler_for(&this, move |this, cx| this.manage_members(channel_id, cx)),
-                    )
-                    .entry(
-                        "Delete",
-                        None,
-                        cx.handler_for(&this, move |this, cx| this.remove_channel(channel_id, cx)),
-                    );
-            }
-
-            context_menu
-        });
-
-        cx.focus_view(&context_menu);
-        let subscription =
-            cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| {
-                if this.context_menu.as_ref().is_some_and(|context_menu| {
-                    context_menu.0.focus_handle(cx).contains_focused(cx)
-                }) {
-                    cx.focus_self();
-                }
-                this.context_menu.take();
-                cx.notify();
-            });
-        self.context_menu = Some((context_menu, position, subscription));
-
-        cx.notify();
-    }
-
-    fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
-        if self.take_editing_state(cx) {
-            cx.focus_view(&self.filter_editor);
-        } else {
-            self.filter_editor.update(cx, |editor, cx| {
-                if editor.buffer().read(cx).len(cx) > 0 {
-                    editor.set_text("", cx);
-                }
-            });
-        }
-
-        self.update_entries(false, cx);
-    }
-
-    fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
-        let ix = self.selection.map_or(0, |ix| ix + 1);
-        if ix < self.entries.len() {
-            self.selection = Some(ix);
-        }
-
-        if let Some(ix) = self.selection {
-            self.scroll_to_item(ix)
-        }
-        cx.notify();
-    }
-
-    fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
-        let ix = self.selection.take().unwrap_or(0);
-        if ix > 0 {
-            self.selection = Some(ix - 1);
-        }
-
-        if let Some(ix) = self.selection {
-            self.scroll_to_item(ix)
-        }
-        cx.notify();
-    }
-
-    fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
-        if self.confirm_channel_edit(cx) {
-            return;
-        }
-
-        if let Some(selection) = self.selection {
-            if let Some(entry) = self.entries.get(selection) {
-                match entry {
-                    ListEntry::Header(section) => match section {
-                        Section::ActiveCall => Self::leave_call(cx),
-                        Section::Channels => self.new_root_channel(cx),
-                        Section::Contacts => self.toggle_contact_finder(cx),
-                        Section::ContactRequests
-                        | Section::Online
-                        | Section::Offline
-                        | Section::ChannelInvites => {
-                            self.toggle_section_expanded(*section, cx);
-                        }
-                    },
-                    ListEntry::Contact { contact, calling } => {
-                        if contact.online && !contact.busy && !calling {
-                            self.call(contact.user.id, cx);
-                        }
-                    }
-                    ListEntry::ParticipantProject {
-                        project_id,
-                        host_user_id,
-                        ..
-                    } => {
-                        if let Some(workspace) = self.workspace.upgrade() {
-                            let app_state = workspace.read(cx).app_state().clone();
-                            workspace::join_remote_project(
-                                *project_id,
-                                *host_user_id,
-                                app_state,
-                                cx,
-                            )
-                            .detach_and_log_err(cx);
-                        }
-                    }
-                    ListEntry::ParticipantScreen { peer_id, .. } => {
-                        let Some(peer_id) = peer_id else {
-                            return;
-                        };
-                        if let Some(workspace) = self.workspace.upgrade() {
-                            workspace.update(cx, |workspace, cx| {
-                                workspace.open_shared_screen(*peer_id, cx)
-                            });
-                        }
-                    }
-                    ListEntry::Channel { channel, .. } => {
-                        let is_active = maybe!({
-                            let call_channel = ActiveCall::global(cx)
-                                .read(cx)
-                                .room()?
-                                .read(cx)
-                                .channel_id()?;
-
-                            Some(call_channel == channel.id)
-                        })
-                        .unwrap_or(false);
-                        if is_active {
-                            self.open_channel_notes(channel.id, cx)
-                        } else {
-                            self.join_channel(channel.id, cx)
-                        }
-                    }
-                    ListEntry::ContactPlaceholder => self.toggle_contact_finder(cx),
-                    ListEntry::CallParticipant { user, peer_id, .. } => {
-                        if Some(user) == self.user_store.read(cx).current_user().as_ref() {
-                            Self::leave_call(cx);
-                        } else if let Some(peer_id) = peer_id {
-                            self.workspace
-                                .update(cx, |workspace, cx| workspace.follow(*peer_id, cx))
-                                .ok();
-                        }
-                    }
-                    ListEntry::IncomingRequest(user) => {
-                        self.respond_to_contact_request(user.id, true, cx)
-                    }
-                    ListEntry::ChannelInvite(channel) => {
-                        self.respond_to_channel_invite(channel.id, true, cx)
-                    }
-                    ListEntry::ChannelNotes { channel_id } => {
-                        self.open_channel_notes(*channel_id, cx)
-                    }
-                    ListEntry::ChannelChat { channel_id } => {
-                        self.join_channel_chat(*channel_id, cx)
-                    }
-
-                    ListEntry::OutgoingRequest(_) => {}
-                    ListEntry::ChannelEditor { .. } => {}
-                }
-            }
-        }
-    }
-
-    fn insert_space(&mut self, _: &InsertSpace, cx: &mut ViewContext<Self>) {
-        if self.channel_editing_state.is_some() {
-            self.channel_name_editor.update(cx, |editor, cx| {
-                editor.insert(" ", cx);
-            });
-        }
-    }
-
-    fn confirm_channel_edit(&mut self, cx: &mut ViewContext<CollabPanel>) -> bool {
-        if let Some(editing_state) = &mut self.channel_editing_state {
-            match editing_state {
-                ChannelEditingState::Create {
-                    location,
-                    pending_name,
-                    ..
-                } => {
-                    if pending_name.is_some() {
-                        return false;
-                    }
-                    let channel_name = self.channel_name_editor.read(cx).text(cx);
-
-                    *pending_name = Some(channel_name.clone());
-
-                    self.channel_store
-                        .update(cx, |channel_store, cx| {
-                            channel_store.create_channel(&channel_name, *location, cx)
-                        })
-                        .detach();
-                    cx.notify();
-                }
-                ChannelEditingState::Rename {
-                    location,
-                    pending_name,
-                } => {
-                    if pending_name.is_some() {
-                        return false;
-                    }
-                    let channel_name = self.channel_name_editor.read(cx).text(cx);
-                    *pending_name = Some(channel_name.clone());
-
-                    self.channel_store
-                        .update(cx, |channel_store, cx| {
-                            channel_store.rename(*location, &channel_name, cx)
-                        })
-                        .detach();
-                    cx.notify();
-                }
-            }
-            cx.focus_self();
-            true
-        } else {
-            false
-        }
-    }
-
-    fn toggle_section_expanded(&mut self, section: Section, cx: &mut ViewContext<Self>) {
-        if let Some(ix) = self.collapsed_sections.iter().position(|s| *s == section) {
-            self.collapsed_sections.remove(ix);
-        } else {
-            self.collapsed_sections.push(section);
-        }
-        self.update_entries(false, cx);
-    }
-
-    fn collapse_selected_channel(
-        &mut self,
-        _: &CollapseSelectedChannel,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let Some(channel_id) = self.selected_channel().map(|channel| channel.id) else {
-            return;
-        };
-
-        if self.is_channel_collapsed(channel_id) {
-            return;
-        }
-
-        self.toggle_channel_collapsed(channel_id, cx);
-    }
-
-    fn expand_selected_channel(&mut self, _: &ExpandSelectedChannel, cx: &mut ViewContext<Self>) {
-        let Some(id) = self.selected_channel().map(|channel| channel.id) else {
-            return;
-        };
-
-        if !self.is_channel_collapsed(id) {
-            return;
-        }
-
-        self.toggle_channel_collapsed(id, cx)
-    }
-
-    //     fn toggle_channel_collapsed_action(
-    //         &mut self,
-    //         action: &ToggleCollapse,
-    //         cx: &mut ViewContext<Self>,
-    //     ) {
-    //         self.toggle_channel_collapsed(action.location, cx);
-    //     }
-
-    fn toggle_channel_collapsed<'a>(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
-        match self.collapsed_channels.binary_search(&channel_id) {
-            Ok(ix) => {
-                self.collapsed_channels.remove(ix);
-            }
-            Err(ix) => {
-                self.collapsed_channels.insert(ix, channel_id);
-            }
-        };
-        self.serialize(cx);
-        self.update_entries(true, cx);
-        cx.notify();
-        cx.focus_self();
-    }
-
-    fn is_channel_collapsed(&self, channel_id: ChannelId) -> bool {
-        self.collapsed_channels.binary_search(&channel_id).is_ok()
-    }
-
-    fn leave_call(cx: &mut WindowContext) {
-        ActiveCall::global(cx)
-            .update(cx, |call, cx| call.hang_up(cx))
-            .detach_and_log_err(cx);
-    }
-
-    fn toggle_contact_finder(&mut self, cx: &mut ViewContext<Self>) {
-        if let Some(workspace) = self.workspace.upgrade() {
-            workspace.update(cx, |workspace, cx| {
-                workspace.toggle_modal(cx, |cx| {
-                    let mut finder = ContactFinder::new(self.user_store.clone(), cx);
-                    finder.set_query(self.filter_editor.read(cx).text(cx), cx);
-                    finder
-                });
-            });
-        }
-    }
-
-    fn new_root_channel(&mut self, cx: &mut ViewContext<Self>) {
-        self.channel_editing_state = Some(ChannelEditingState::Create {
-            location: None,
-            pending_name: None,
-        });
-        self.update_entries(false, cx);
-        self.select_channel_editor();
-        cx.focus_view(&self.channel_name_editor);
-        cx.notify();
-    }
-
-    fn select_channel_editor(&mut self) {
-        self.selection = self.entries.iter().position(|entry| match entry {
-            ListEntry::ChannelEditor { .. } => true,
-            _ => false,
-        });
-    }
-
-    fn new_subchannel(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
-        self.collapsed_channels
-            .retain(|channel| *channel != channel_id);
-        self.channel_editing_state = Some(ChannelEditingState::Create {
-            location: Some(channel_id),
-            pending_name: None,
-        });
-        self.update_entries(false, cx);
-        self.select_channel_editor();
-        cx.focus_view(&self.channel_name_editor);
-        cx.notify();
-    }
-
-    fn invite_members(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
-        self.show_channel_modal(channel_id, channel_modal::Mode::InviteMembers, cx);
-    }
-
-    fn manage_members(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
-        self.show_channel_modal(channel_id, channel_modal::Mode::ManageMembers, cx);
-    }
-
-    fn remove_selected_channel(&mut self, _: &Remove, cx: &mut ViewContext<Self>) {
-        if let Some(channel) = self.selected_channel() {
-            self.remove_channel(channel.id, cx)
-        }
-    }
-
-    fn rename_selected_channel(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
-        if let Some(channel) = self.selected_channel() {
-            self.rename_channel(channel.id, cx);
-        }
-    }
-
-    fn rename_channel(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
-        let channel_store = self.channel_store.read(cx);
-        if !channel_store.is_channel_admin(channel_id) {
-            return;
-        }
-        if let Some(channel) = channel_store.channel_for_id(channel_id).cloned() {
-            self.channel_editing_state = Some(ChannelEditingState::Rename {
-                location: channel_id,
-                pending_name: None,
-            });
-            self.channel_name_editor.update(cx, |editor, cx| {
-                editor.set_text(channel.name.clone(), cx);
-                editor.select_all(&Default::default(), cx);
-            });
-            cx.focus_view(&self.channel_name_editor);
-            self.update_entries(false, cx);
-            self.select_channel_editor();
-        }
-    }
-
-    fn start_move_channel(&mut self, channel_id: ChannelId, _cx: &mut ViewContext<Self>) {
-        self.channel_clipboard = Some(ChannelMoveClipboard { channel_id });
-    }
-
-    fn start_move_selected_channel(&mut self, _: &StartMoveChannel, cx: &mut ViewContext<Self>) {
-        if let Some(channel) = self.selected_channel() {
-            self.start_move_channel(channel.id, cx);
-        }
-    }
-
-    fn move_channel_on_clipboard(
-        &mut self,
-        to_channel_id: ChannelId,
-        cx: &mut ViewContext<CollabPanel>,
-    ) {
-        if let Some(clipboard) = self.channel_clipboard.take() {
-            self.channel_store.update(cx, |channel_store, cx| {
-                channel_store
-                    .move_channel(clipboard.channel_id, Some(to_channel_id), cx)
-                    .detach_and_log_err(cx)
-            })
-        }
-    }
-
-    fn open_channel_notes(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
-        if let Some(workspace) = self.workspace.upgrade() {
-            ChannelView::open(channel_id, workspace, cx).detach();
-        }
-    }
-
-    fn show_inline_context_menu(&mut self, _: &menu::ShowContextMenu, cx: &mut ViewContext<Self>) {
-        let Some(channel) = self.selected_channel() else {
-            return;
-        };
-        let Some(bounds) = self
-            .selection
-            .and_then(|ix| self.list_state.bounds_for_item(ix))
-        else {
-            return;
-        };
-
-        self.deploy_channel_context_menu(bounds.center(), channel.id, self.selection.unwrap(), cx);
-        cx.stop_propagation();
-    }
-
-    fn selected_channel(&self) -> Option<&Arc<Channel>> {
-        self.selection
-            .and_then(|ix| self.entries.get(ix))
-            .and_then(|entry| match entry {
-                ListEntry::Channel { channel, .. } => Some(channel),
-                _ => None,
-            })
-    }
-
-    fn show_channel_modal(
-        &mut self,
-        channel_id: ChannelId,
-        mode: channel_modal::Mode,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let workspace = self.workspace.clone();
-        let user_store = self.user_store.clone();
-        let channel_store = self.channel_store.clone();
-        let members = self.channel_store.update(cx, |channel_store, cx| {
-            channel_store.get_channel_member_details(channel_id, cx)
-        });
-
-        cx.spawn(|_, mut cx| async move {
-            let members = members.await?;
-            workspace.update(&mut cx, |workspace, cx| {
-                workspace.toggle_modal(cx, |cx| {
-                    ChannelModal::new(
-                        user_store.clone(),
-                        channel_store.clone(),
-                        channel_id,
-                        mode,
-                        members,
-                        cx,
-                    )
-                });
-            })
-        })
-        .detach();
-    }
-
-    fn remove_channel(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
-        let channel_store = self.channel_store.clone();
-        if let Some(channel) = channel_store.read(cx).channel_for_id(channel_id) {
-            let prompt_message = format!(
-                "Are you sure you want to remove the channel \"{}\"?",
-                channel.name
-            );
-            let answer = cx.prompt(PromptLevel::Warning, &prompt_message, &["Remove", "Cancel"]);
-            cx.spawn(|this, mut cx| async move {
-                if answer.await? == 0 {
-                    channel_store
-                        .update(&mut cx, |channels, _| channels.remove_channel(channel_id))?
-                        .await
-                        .notify_async_err(&mut cx);
-                    this.update(&mut cx, |_, cx| cx.focus_self()).ok();
-                }
-                anyhow::Ok(())
-            })
-            .detach();
-        }
-    }
-
-    fn remove_contact(&mut self, user_id: u64, github_login: &str, cx: &mut ViewContext<Self>) {
-        let user_store = self.user_store.clone();
-        let prompt_message = format!(
-            "Are you sure you want to remove \"{}\" from your contacts?",
-            github_login
-        );
-        let answer = cx.prompt(PromptLevel::Warning, &prompt_message, &["Remove", "Cancel"]);
-        cx.spawn(|_, mut cx| async move {
-            if answer.await? == 0 {
-                user_store
-                    .update(&mut cx, |store, cx| store.remove_contact(user_id, cx))?
-                    .await
-                    .notify_async_err(&mut cx);
-            }
-            anyhow::Ok(())
-        })
-        .detach_and_log_err(cx);
-    }
-
-    fn respond_to_contact_request(
-        &mut self,
-        user_id: u64,
-        accept: bool,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.user_store
-            .update(cx, |store, cx| {
-                store.respond_to_contact_request(user_id, accept, cx)
-            })
-            .detach_and_log_err(cx);
-    }
-
-    fn respond_to_channel_invite(
-        &mut self,
-        channel_id: u64,
-        accept: bool,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.channel_store
-            .update(cx, |store, cx| {
-                store.respond_to_channel_invite(channel_id, accept, cx)
-            })
-            .detach();
-    }
-
-    fn call(&mut self, recipient_user_id: u64, cx: &mut ViewContext<Self>) {
-        ActiveCall::global(cx)
-            .update(cx, |call, cx| {
-                call.invite(recipient_user_id, Some(self.project.clone()), cx)
-            })
-            .detach_and_log_err(cx);
-    }
-
-    fn join_channel(&self, channel_id: u64, cx: &mut ViewContext<Self>) {
-        let Some(workspace) = self.workspace.upgrade() else {
-            return;
-        };
-        let Some(handle) = cx.window_handle().downcast::<Workspace>() else {
-            return;
-        };
-        workspace::join_channel(
-            channel_id,
-            workspace.read(cx).app_state().clone(),
-            Some(handle),
-            cx,
-        )
-        .detach_and_log_err(cx)
-    }
-
-    fn join_channel_chat(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
-        let Some(workspace) = self.workspace.upgrade() else {
-            return;
-        };
-        cx.window_context().defer(move |cx| {
-            workspace.update(cx, |workspace, cx| {
-                if let Some(panel) = workspace.focus_panel::<ChatPanel>(cx) {
-                    panel.update(cx, |panel, cx| {
-                        panel
-                            .select_channel(channel_id, None, cx)
-                            .detach_and_log_err(cx);
-                    });
-                }
-            });
-        });
-    }
-
-    fn copy_channel_link(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
-        let channel_store = self.channel_store.read(cx);
-        let Some(channel) = channel_store.channel_for_id(channel_id) else {
-            return;
-        };
-        let item = ClipboardItem::new(channel.link());
-        cx.write_to_clipboard(item)
-    }
-
-    fn render_signed_out(&mut self, cx: &mut ViewContext<Self>) -> Div {
-        let collab_blurb = "Work with your team in realtime with collaborative editing, voice, shared notes and more.";
-
-        v_stack()
-            .gap_6()
-            .p_4()
-            .child(Label::new(collab_blurb))
-            .child(
-                v_stack()
-                    .gap_2()
-                    .child(
-                        Button::new("sign_in", "Sign in")
-                            .icon_color(Color::Muted)
-                            .icon(Icon::Github)
-                            .icon_position(IconPosition::Start)
-                            .style(ButtonStyle::Filled)
-                            .full_width()
-                            .on_click(cx.listener(|this, _, cx| {
-                                let client = this.client.clone();
-                                cx.spawn(|_, mut cx| async move {
-                                    client
-                                        .authenticate_and_connect(true, &cx)
-                                        .await
-                                        .notify_async_err(&mut cx);
-                                })
-                                .detach()
-                            })),
-                    )
-                    .child(
-                        div().flex().w_full().items_center().child(
-                            Label::new("Sign in to enable collaboration.")
-                                .color(Color::Muted)
-                                .size(LabelSize::Small),
-                        ),
-                    ),
-            )
-    }
-
-    fn render_list_entry(&mut self, ix: usize, cx: &mut ViewContext<Self>) -> AnyElement {
-        let entry = &self.entries[ix];
-
-        let is_selected = self.selection == Some(ix);
-        match entry {
-            ListEntry::Header(section) => {
-                let is_collapsed = self.collapsed_sections.contains(section);
-                self.render_header(*section, is_selected, is_collapsed, cx)
-                    .into_any_element()
-            }
-            ListEntry::Contact { contact, calling } => self
-                .render_contact(contact, *calling, is_selected, cx)
-                .into_any_element(),
-            ListEntry::ContactPlaceholder => self
-                .render_contact_placeholder(is_selected, cx)
-                .into_any_element(),
-            ListEntry::IncomingRequest(user) => self
-                .render_contact_request(user, true, is_selected, cx)
-                .into_any_element(),
-            ListEntry::OutgoingRequest(user) => self
-                .render_contact_request(user, false, is_selected, cx)
-                .into_any_element(),
-            ListEntry::Channel {
-                channel,
-                depth,
-                has_children,
-            } => self
-                .render_channel(channel, *depth, *has_children, is_selected, ix, cx)
-                .into_any_element(),
-            ListEntry::ChannelEditor { depth } => {
-                self.render_channel_editor(*depth, cx).into_any_element()
-            }
-            ListEntry::ChannelInvite(channel) => self
-                .render_channel_invite(channel, is_selected, cx)
-                .into_any_element(),
-            ListEntry::CallParticipant {
-                user,
-                peer_id,
-                is_pending,
-            } => self
-                .render_call_participant(user, *peer_id, *is_pending, is_selected, cx)
-                .into_any_element(),
-            ListEntry::ParticipantProject {
-                project_id,
-                worktree_root_names,
-                host_user_id,
-                is_last,
-            } => self
-                .render_participant_project(
-                    *project_id,
-                    &worktree_root_names,
-                    *host_user_id,
-                    *is_last,
-                    is_selected,
-                    cx,
-                )
-                .into_any_element(),
-            ListEntry::ParticipantScreen { peer_id, is_last } => self
-                .render_participant_screen(*peer_id, *is_last, is_selected, cx)
-                .into_any_element(),
-            ListEntry::ChannelNotes { channel_id } => self
-                .render_channel_notes(*channel_id, is_selected, cx)
-                .into_any_element(),
-            ListEntry::ChannelChat { channel_id } => self
-                .render_channel_chat(*channel_id, is_selected, cx)
-                .into_any_element(),
-        }
-    }
-
-    fn render_signed_in(&mut self, cx: &mut ViewContext<Self>) -> Div {
-        v_stack()
-            .size_full()
-            .child(list(self.list_state.clone()).full())
-            .child(
-                v_stack().p_2().child(
-                    v_stack()
-                        .border_primary(cx)
-                        .border_t()
-                        .child(self.filter_editor.clone()),
-                ),
-            )
-    }
-
-    fn render_header(
-        &self,
-        section: Section,
-        is_selected: bool,
-        is_collapsed: bool,
-        cx: &ViewContext<Self>,
-    ) -> impl IntoElement {
-        let mut channel_link = None;
-        let mut channel_tooltip_text = None;
-        let mut channel_icon = None;
-        // let mut is_dragged_over = false;
-
-        let text = match section {
-            Section::ActiveCall => {
-                let channel_name = maybe!({
-                    let channel_id = ActiveCall::global(cx).read(cx).channel_id(cx)?;
-
-                    let channel = self.channel_store.read(cx).channel_for_id(channel_id)?;
-
-                    channel_link = Some(channel.link());
-                    (channel_icon, channel_tooltip_text) = match channel.visibility {
-                        proto::ChannelVisibility::Public => {
-                            (Some("icons/public.svg"), Some("Copy public channel link."))
-                        }
-                        proto::ChannelVisibility::Members => {
-                            (Some("icons/hash.svg"), Some("Copy private channel link."))
-                        }
-                    };
-
-                    Some(channel.name.as_ref())
-                });
-
-                if let Some(name) = channel_name {
-                    SharedString::from(format!("{}", name))
-                } else {
-                    SharedString::from("Current Call")
-                }
-            }
-            Section::ContactRequests => SharedString::from("Requests"),
-            Section::Contacts => SharedString::from("Contacts"),
-            Section::Channels => SharedString::from("Channels"),
-            Section::ChannelInvites => SharedString::from("Invites"),
-            Section::Online => SharedString::from("Online"),
-            Section::Offline => SharedString::from("Offline"),
-        };
-
-        let button = match section {
-            Section::ActiveCall => channel_link.map(|channel_link| {
-                let channel_link_copy = channel_link.clone();
-                IconButton::new("channel-link", Icon::Copy)
-                    .icon_size(IconSize::Small)
-                    .size(ButtonSize::None)
-                    .visible_on_hover("section-header")
-                    .on_click(move |_, cx| {
-                        let item = ClipboardItem::new(channel_link_copy.clone());
-                        cx.write_to_clipboard(item)
-                    })
-                    .tooltip(|cx| Tooltip::text("Copy channel link", cx))
-                    .into_any_element()
-            }),
-            Section::Contacts => Some(
-                IconButton::new("add-contact", Icon::Plus)
-                    .on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx)))
-                    .tooltip(|cx| Tooltip::text("Search for new contact", cx))
-                    .into_any_element(),
-            ),
-            Section::Channels => Some(
-                IconButton::new("add-channel", Icon::Plus)
-                    .on_click(cx.listener(|this, _, cx| this.new_root_channel(cx)))
-                    .tooltip(|cx| Tooltip::text("Create a channel", cx))
-                    .into_any_element(),
-            ),
-            _ => None,
-        };
-
-        let can_collapse = match section {
-            Section::ActiveCall | Section::Channels | Section::Contacts => false,
-            Section::ChannelInvites
-            | Section::ContactRequests
-            | Section::Online
-            | Section::Offline => true,
-        };
-
-        h_stack()
-            .w_full()
-            .group("section-header")
-            .child(
-                ListHeader::new(text)
-                    .when(can_collapse, |header| {
-                        header.toggle(Some(!is_collapsed)).on_toggle(cx.listener(
-                            move |this, _, cx| {
-                                this.toggle_section_expanded(section, cx);
-                            },
-                        ))
-                    })
-                    .inset(true)
-                    .end_slot::<AnyElement>(button)
-                    .selected(is_selected),
-            )
-            .when(section == Section::Channels, |el| {
-                el.drag_over::<Channel>(|style| style.bg(cx.theme().colors().ghost_element_hover))
-                    .on_drop(cx.listener(move |this, dragged_channel: &Channel, cx| {
-                        this.channel_store
-                            .update(cx, |channel_store, cx| {
-                                channel_store.move_channel(dragged_channel.id, None, cx)
-                            })
-                            .detach_and_log_err(cx)
-                    }))
-            })
-    }
-
-    fn render_contact(
-        &self,
-        contact: &Contact,
-        calling: bool,
-        is_selected: bool,
-        cx: &mut ViewContext<Self>,
-    ) -> impl IntoElement {
-        let online = contact.online;
-        let busy = contact.busy || calling;
-        let user_id = contact.user.id;
-        let github_login = SharedString::from(contact.user.github_login.clone());
-        let item =
-            ListItem::new(github_login.clone())
-                .indent_level(1)
-                .indent_step_size(px(20.))
-                .selected(is_selected)
-                .on_click(cx.listener(move |this, _, cx| this.call(user_id, cx)))
-                .child(
-                    h_stack()
-                        .w_full()
-                        .justify_between()
-                        .child(Label::new(github_login.clone()))
-                        .when(calling, |el| {
-                            el.child(Label::new("Calling").color(Color::Muted))
-                        })
-                        .when(!calling, |el| {
-                            el.child(
-                                IconButton::new("remove_contact", Icon::Close)
-                                    .icon_color(Color::Muted)
-                                    .visible_on_hover("")
-                                    .tooltip(|cx| Tooltip::text("Remove Contact", cx))
-                                    .on_click(cx.listener({
-                                        let github_login = github_login.clone();
-                                        move |this, _, cx| {
-                                            this.remove_contact(user_id, &github_login, cx);
-                                        }
-                                    })),
-                            )
-                        }),
-                )
-                .start_slot(
-                    // todo!() handle contacts with no avatar
-                    Avatar::new(contact.user.avatar_uri.clone())
-                        .availability_indicator(if online { Some(!busy) } else { None }),
-                )
-                .when(online && !busy, |el| {
-                    el.on_click(cx.listener(move |this, _, cx| this.call(user_id, cx)))
-                });
-
-        div()
-            .id(github_login.clone())
-            .group("")
-            .child(item)
-            .tooltip(move |cx| {
-                let text = if !online {
-                    format!(" {} is offline", &github_login)
-                } else if busy {
-                    format!(" {} is on a call", &github_login)
-                } else {
-                    let room = ActiveCall::global(cx).read(cx).room();
-                    if room.is_some() {
-                        format!("Invite {} to join call", &github_login)
-                    } else {
-                        format!("Call {}", &github_login)
-                    }
-                };
-                Tooltip::text(text, cx)
-            })
-    }
-
-    fn render_contact_request(
-        &self,
-        user: &Arc<User>,
-        is_incoming: bool,
-        is_selected: bool,
-        cx: &mut ViewContext<Self>,
-    ) -> impl IntoElement {
-        let github_login = SharedString::from(user.github_login.clone());
-        let user_id = user.id;
-        let is_response_pending = self.user_store.read(cx).is_contact_request_pending(&user);
-        let color = if is_response_pending {
-            Color::Muted
-        } else {
-            Color::Default
-        };
-
-        let controls = if is_incoming {
-            vec![
-                IconButton::new("decline-contact", Icon::Close)
-                    .on_click(cx.listener(move |this, _, cx| {
-                        this.respond_to_contact_request(user_id, false, cx);
-                    }))
-                    .icon_color(color)
-                    .tooltip(|cx| Tooltip::text("Decline invite", cx)),
-                IconButton::new("accept-contact", Icon::Check)
-                    .on_click(cx.listener(move |this, _, cx| {
-                        this.respond_to_contact_request(user_id, true, cx);
-                    }))
-                    .icon_color(color)
-                    .tooltip(|cx| Tooltip::text("Accept invite", cx)),
-            ]
-        } else {
-            let github_login = github_login.clone();
-            vec![IconButton::new("remove_contact", Icon::Close)
-                .on_click(cx.listener(move |this, _, cx| {
-                    this.remove_contact(user_id, &github_login, cx);
-                }))
-                .icon_color(color)
-                .tooltip(|cx| Tooltip::text("Cancel invite", cx))]
-        };
-
-        ListItem::new(github_login.clone())
-            .indent_level(1)
-            .indent_step_size(px(20.))
-            .selected(is_selected)
-            .child(
-                h_stack()
-                    .w_full()
-                    .justify_between()
-                    .child(Label::new(github_login.clone()))
-                    .child(h_stack().children(controls)),
-            )
-            .start_slot(Avatar::new(user.avatar_uri.clone()))
-    }
-
-    fn render_channel_invite(
-        &self,
-        channel: &Arc<Channel>,
-        is_selected: bool,
-        cx: &mut ViewContext<Self>,
-    ) -> ListItem {
-        let channel_id = channel.id;
-        let response_is_pending = self
-            .channel_store
-            .read(cx)
-            .has_pending_channel_invite_response(&channel);
-        let color = if response_is_pending {
-            Color::Muted
-        } else {
-            Color::Default
-        };
-
-        let controls = [
-            IconButton::new("reject-invite", Icon::Close)
-                .on_click(cx.listener(move |this, _, cx| {
-                    this.respond_to_channel_invite(channel_id, false, cx);
-                }))
-                .icon_color(color)
-                .tooltip(|cx| Tooltip::text("Decline invite", cx)),
-            IconButton::new("accept-invite", Icon::Check)
-                .on_click(cx.listener(move |this, _, cx| {
-                    this.respond_to_channel_invite(channel_id, true, cx);
-                }))
-                .icon_color(color)
-                .tooltip(|cx| Tooltip::text("Accept invite", cx)),
-        ];
-
-        ListItem::new(("channel-invite", channel.id as usize))
-            .selected(is_selected)
-            .child(
-                h_stack()
-                    .w_full()
-                    .justify_between()
-                    .child(Label::new(channel.name.clone()))
-                    .child(h_stack().children(controls)),
-            )
-            .start_slot(
-                IconElement::new(Icon::Hash)
-                    .size(IconSize::Small)
-                    .color(Color::Muted),
-            )
-    }
-
-    fn render_contact_placeholder(
-        &self,
-        is_selected: bool,
-        cx: &mut ViewContext<Self>,
-    ) -> ListItem {
-        ListItem::new("contact-placeholder")
-            .child(IconElement::new(Icon::Plus))
-            .child(Label::new("Add a Contact"))
-            .selected(is_selected)
-            .on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx)))
-    }
-
-    fn render_channel(
-        &self,
-        channel: &Channel,
-        depth: usize,
-        has_children: bool,
-        is_selected: bool,
-        ix: usize,
-        cx: &mut ViewContext<Self>,
-    ) -> impl IntoElement {
-        let channel_id = channel.id;
-
-        let is_active = maybe!({
-            let call_channel = ActiveCall::global(cx)
-                .read(cx)
-                .room()?
-                .read(cx)
-                .channel_id()?;
-            Some(call_channel == channel_id)
-        })
-        .unwrap_or(false);
-        let is_public = self
-            .channel_store
-            .read(cx)
-            .channel_for_id(channel_id)
-            .map(|channel| channel.visibility)
-            == Some(proto::ChannelVisibility::Public);
-        let disclosed =
-            has_children.then(|| !self.collapsed_channels.binary_search(&channel.id).is_ok());
-
-        let has_messages_notification = channel.unseen_message_id.is_some();
-        let has_notes_notification = channel.unseen_note_version.is_some();
-
-        const FACEPILE_LIMIT: usize = 3;
-        let participants = self.channel_store.read(cx).channel_participants(channel_id);
-
-        let face_pile = if !participants.is_empty() {
-            let extra_count = participants.len().saturating_sub(FACEPILE_LIMIT);
-            let result = FacePile {
-                faces: participants
-                    .iter()
-                    .map(|user| Avatar::new(user.avatar_uri.clone()).into_any_element())
-                    .take(FACEPILE_LIMIT)
-                    .chain(if extra_count > 0 {
-                        // todo!() @nate - this label looks wrong.
-                        Some(Label::new(format!("+{}", extra_count)).into_any_element())
-                    } else {
-                        None
-                    })
-                    .collect::<SmallVec<_>>(),
-            };
-
-            Some(result)
-        } else {
-            None
-        };
-
-        let button_container = |cx: &mut ViewContext<Self>| {
-            h_stack()
-                .absolute()
-                // We're using a negative coordinate for the right anchor to
-                // counteract the padding of the `ListItem`.
-                //
-                // This prevents a gap from showing up between the background
-                // of this element and the edge of the collab panel.
-                .right(rems(-0.5))
-                // HACK: Without this the channel name clips on top of the icons, but I'm not sure why.
-                .z_index(10)
-                .bg(cx.theme().colors().panel_background)
-                .when(is_selected || is_active, |this| {
-                    this.bg(cx.theme().colors().ghost_element_selected)
-                })
-        };
-
-        let messages_button = |cx: &mut ViewContext<Self>| {
-            IconButton::new("channel_chat", Icon::MessageBubbles)
-                .icon_size(IconSize::Small)
-                .icon_color(if has_messages_notification {
-                    Color::Default
-                } else {
-                    Color::Muted
-                })
-                .on_click(cx.listener(move |this, _, cx| this.join_channel_chat(channel_id, cx)))
-                .tooltip(|cx| Tooltip::text("Open channel chat", cx))
-        };
-
-        let notes_button = |cx: &mut ViewContext<Self>| {
-            IconButton::new("channel_notes", Icon::File)
-                .icon_size(IconSize::Small)
-                .icon_color(if has_notes_notification {
-                    Color::Default
-                } else {
-                    Color::Muted
-                })
-                .on_click(cx.listener(move |this, _, cx| this.open_channel_notes(channel_id, cx)))
-                .tooltip(|cx| Tooltip::text("Open channel notes", cx))
-        };
-
-        let width = self.width.unwrap_or(px(240.));
-
-        div()
-            .id(channel_id as usize)
-            .group("")
-            .flex()
-            .w_full()
-            .on_drag(channel.clone(), move |channel, cx| {
-                cx.new_view(|_| DraggedChannelView {
-                    channel: channel.clone(),
-                    width,
-                })
-            })
-            .drag_over::<Channel>(|style| style.bg(cx.theme().colors().ghost_element_hover))
-            .on_drop(cx.listener(move |this, dragged_channel: &Channel, cx| {
-                this.channel_store
-                    .update(cx, |channel_store, cx| {
-                        channel_store.move_channel(dragged_channel.id, Some(channel_id), cx)
-                    })
-                    .detach_and_log_err(cx)
-            }))
-            .child(
-                ListItem::new(channel_id as usize)
-                    // Add one level of depth for the disclosure arrow.
-                    .indent_level(depth + 1)
-                    .indent_step_size(px(20.))
-                    .selected(is_selected || is_active)
-                    .toggle(disclosed)
-                    .on_toggle(
-                        cx.listener(move |this, _, cx| {
-                            this.toggle_channel_collapsed(channel_id, cx)
-                        }),
-                    )
-                    .on_click(cx.listener(move |this, _, cx| {
-                        if is_active {
-                            this.open_channel_notes(channel_id, cx)
-                        } else {
-                            this.join_channel(channel_id, cx)
-                        }
-                    }))
-                    .on_secondary_mouse_down(cx.listener(
-                        move |this, event: &MouseDownEvent, cx| {
-                            this.deploy_channel_context_menu(event.position, channel_id, ix, cx)
-                        },
-                    ))
-                    .start_slot(
-                        IconElement::new(if is_public { Icon::Public } else { Icon::Hash })
-                            .size(IconSize::Small)
-                            .color(Color::Muted),
-                    )
-                    .child(
-                        h_stack()
-                            .id(channel_id as usize)
-                            // HACK: This is a dirty hack to help with the positioning of the button container.
-                            //
-                            // We're using a pixel width for the elements but then allowing the contents to
-                            // overflow. This means that the label and facepile will be shown, but will not
-                            // push the button container off the edge of the panel.
-                            .w_px()
-                            .child(Label::new(channel.name.clone()))
-                            .children(face_pile.map(|face_pile| face_pile.render(cx))),
-                    )
-                    .end_slot::<Div>(
-                        // If we have a notification for either button, we want to show the corresponding
-                        // button(s) as indicators.
-                        if has_messages_notification || has_notes_notification {
-                            Some(
-                                button_container(cx).child(
-                                    h_stack()
-                                        .px_1()
-                                        .children(
-                                            // We only want to render the messages button if there are unseen messages.
-                                            // This way we don't take up any space that might overlap the channel name
-                                            // when there are no notifications.
-                                            has_messages_notification.then(|| messages_button(cx)),
-                                        )
-                                        .child(
-                                            // We always want the notes button to take up space to prevent layout
-                                            // shift when hovering over the channel.
-                                            // However, if there are is no notes notification we just show an empty slot.
-                                            notes_button(cx)
-                                                .when(!has_notes_notification, |this| {
-                                                    this.visible_on_hover("")
-                                                }),
-                                        ),
-                                ),
-                            )
-                        } else {
-                            None
-                        },
-                    )
-                    .end_hover_slot(
-                        // When we hover the channel entry we want to always show both buttons.
-                        button_container(cx).child(
-                            h_stack()
-                                .px_1()
-                                // The element hover background has a slight transparency to it, so we
-                                // need to apply it to the inner element so that it blends with the solid
-                                // background color of the absolutely-positioned element.
-                                .group_hover("", |style| {
-                                    style.bg(cx.theme().colors().ghost_element_hover)
-                                })
-                                .child(messages_button(cx))
-                                .child(notes_button(cx)),
-                        ),
-                    ),
-            )
-            .tooltip(|cx| Tooltip::text("Join channel", cx))
-    }
-
-    fn render_channel_editor(&self, depth: usize, _cx: &mut ViewContext<Self>) -> impl IntoElement {
-        let item = ListItem::new("channel-editor")
-            .inset(false)
-            // Add one level of depth for the disclosure arrow.
-            .indent_level(depth + 1)
-            .indent_step_size(px(20.))
-            .start_slot(
-                IconElement::new(Icon::Hash)
-                    .size(IconSize::Small)
-                    .color(Color::Muted),
-            );
-
-        if let Some(pending_name) = self
-            .channel_editing_state
-            .as_ref()
-            .and_then(|state| state.pending_name())
-        {
-            item.child(Label::new(pending_name))
-        } else {
-            item.child(
-                div()
-                    .w_full()
-                    .py_1() // todo!() @nate this is a px off at the default font size.
-                    .child(self.channel_name_editor.clone()),
-            )
-        }
-    }
-}
-
-fn render_tree_branch(is_last: bool, cx: &mut WindowContext) -> impl IntoElement {
-    let rem_size = cx.rem_size();
-    let line_height = cx.text_style().line_height_in_pixels(rem_size);
-    let width = rem_size * 1.5;
-    let thickness = px(2.);
-    let color = cx.theme().colors().text;
-
-    canvas(move |bounds, cx| {
-        let start_x = (bounds.left() + bounds.right() - thickness) / 2.;
-        let start_y = (bounds.top() + bounds.bottom() - thickness) / 2.;
-        let right = bounds.right();
-        let top = bounds.top();
-
-        cx.paint_quad(fill(
-            Bounds::from_corners(
-                point(start_x, top),
-                point(
-                    start_x + thickness,
-                    if is_last { start_y } else { bounds.bottom() },
-                ),
-            ),
-            color,
-        ));
-        cx.paint_quad(fill(
-            Bounds::from_corners(point(start_x, start_y), point(right, start_y + thickness)),
-            color,
-        ));
-    })
-    .w(width)
-    .h(line_height)
-}
-
-impl Render for CollabPanel {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        v_stack()
-            .key_context("CollabPanel")
-            .on_action(cx.listener(CollabPanel::cancel))
-            .on_action(cx.listener(CollabPanel::select_next))
-            .on_action(cx.listener(CollabPanel::select_prev))
-            .on_action(cx.listener(CollabPanel::confirm))
-            .on_action(cx.listener(CollabPanel::insert_space))
-            .on_action(cx.listener(CollabPanel::remove_selected_channel))
-            .on_action(cx.listener(CollabPanel::show_inline_context_menu))
-            .on_action(cx.listener(CollabPanel::rename_selected_channel))
-            .on_action(cx.listener(CollabPanel::collapse_selected_channel))
-            .on_action(cx.listener(CollabPanel::expand_selected_channel))
-            .on_action(cx.listener(CollabPanel::start_move_selected_channel))
-            .track_focus(&self.focus_handle)
-            .size_full()
-            .child(if self.user_store.read(cx).current_user().is_none() {
-                self.render_signed_out(cx)
-            } else {
-                self.render_signed_in(cx)
-            })
-            .children(self.context_menu.as_ref().map(|(menu, position, _)| {
-                overlay()
-                    .position(*position)
-                    .anchor(gpui::AnchorCorner::TopLeft)
-                    .child(menu.clone())
-            }))
-    }
-}
-
-impl EventEmitter<PanelEvent> for CollabPanel {}
-
-impl Panel for CollabPanel {
-    fn position(&self, cx: &gpui::WindowContext) -> DockPosition {
-        CollaborationPanelSettings::get_global(cx).dock
-    }
-
-    fn position_is_valid(&self, position: DockPosition) -> bool {
-        matches!(position, DockPosition::Left | DockPosition::Right)
-    }
-
-    fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
-        settings::update_settings_file::<CollaborationPanelSettings>(
-            self.fs.clone(),
-            cx,
-            move |settings| settings.dock = Some(position),
-        );
-    }
-
-    fn size(&self, cx: &gpui::WindowContext) -> Pixels {
-        self.width
-            .unwrap_or_else(|| CollaborationPanelSettings::get_global(cx).default_width)
-    }
-
-    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
-        self.width = size;
-        self.serialize(cx);
-        cx.notify();
-    }
-
-    fn icon(&self, cx: &gpui::WindowContext) -> Option<ui::Icon> {
-        CollaborationPanelSettings::get_global(cx)
-            .button
-            .then(|| ui::Icon::Collab)
-    }
-
-    fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
-        Some("Collab Panel")
-    }
-
-    fn toggle_action(&self) -> Box<dyn gpui::Action> {
-        Box::new(ToggleFocus)
-    }
-
-    fn persistent_name() -> &'static str {
-        "CollabPanel"
-    }
-}
-
-impl FocusableView for CollabPanel {
-    fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
-        self.filter_editor.focus_handle(cx).clone()
-    }
-}
-
-impl PartialEq for ListEntry {
-    fn eq(&self, other: &Self) -> bool {
-        match self {
-            ListEntry::Header(section_1) => {
-                if let ListEntry::Header(section_2) = other {
-                    return section_1 == section_2;
-                }
-            }
-            ListEntry::CallParticipant { user: user_1, .. } => {
-                if let ListEntry::CallParticipant { user: user_2, .. } = other {
-                    return user_1.id == user_2.id;
-                }
-            }
-            ListEntry::ParticipantProject {
-                project_id: project_id_1,
-                ..
-            } => {
-                if let ListEntry::ParticipantProject {
-                    project_id: project_id_2,
-                    ..
-                } = other
-                {
-                    return project_id_1 == project_id_2;
-                }
-            }
-            ListEntry::ParticipantScreen {
-                peer_id: peer_id_1, ..
-            } => {
-                if let ListEntry::ParticipantScreen {
-                    peer_id: peer_id_2, ..
-                } = other
-                {
-                    return peer_id_1 == peer_id_2;
-                }
-            }
-            ListEntry::Channel {
-                channel: channel_1, ..
-            } => {
-                if let ListEntry::Channel {
-                    channel: channel_2, ..
-                } = other
-                {
-                    return channel_1.id == channel_2.id;
-                }
-            }
-            ListEntry::ChannelNotes { channel_id } => {
-                if let ListEntry::ChannelNotes {
-                    channel_id: other_id,
-                } = other
-                {
-                    return channel_id == other_id;
-                }
-            }
-            ListEntry::ChannelChat { channel_id } => {
-                if let ListEntry::ChannelChat {
-                    channel_id: other_id,
-                } = other
-                {
-                    return channel_id == other_id;
-                }
-            }
-            ListEntry::ChannelInvite(channel_1) => {
-                if let ListEntry::ChannelInvite(channel_2) = other {
-                    return channel_1.id == channel_2.id;
-                }
-            }
-            ListEntry::IncomingRequest(user_1) => {
-                if let ListEntry::IncomingRequest(user_2) = other {
-                    return user_1.id == user_2.id;
-                }
-            }
-            ListEntry::OutgoingRequest(user_1) => {
-                if let ListEntry::OutgoingRequest(user_2) = other {
-                    return user_1.id == user_2.id;
-                }
-            }
-            ListEntry::Contact {
-                contact: contact_1, ..
-            } => {
-                if let ListEntry::Contact {
-                    contact: contact_2, ..
-                } = other
-                {
-                    return contact_1.user.id == contact_2.user.id;
-                }
-            }
-            ListEntry::ChannelEditor { depth } => {
-                if let ListEntry::ChannelEditor { depth: other_depth } = other {
-                    return depth == other_depth;
-                }
-            }
-            ListEntry::ContactPlaceholder => {
-                if let ListEntry::ContactPlaceholder = other {
-                    return true;
-                }
-            }
-        }
-        false
-    }
-}
-
-struct DraggedChannelView {
-    channel: Channel,
-    width: Pixels,
-}
-
-impl Render for DraggedChannelView {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element {
-        let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone();
-        h_stack()
-            .font(ui_font)
-            .bg(cx.theme().colors().background)
-            .w(self.width)
-            .p_1()
-            .gap_1()
-            .child(
-                IconElement::new(
-                    if self.channel.visibility == proto::ChannelVisibility::Public {
-                        Icon::Public
-                    } else {
-                        Icon::Hash
-                    },
-                )
-                .size(IconSize::Small)
-                .color(Color::Muted),
-            )
-            .child(Label::new(self.channel.name.clone()))
-    }
-}

crates/collab_ui2/src/collab_panel/channel_modal.rs 🔗

@@ -1,575 +0,0 @@
-use channel::{ChannelId, ChannelMembership, ChannelStore};
-use client::{
-    proto::{self, ChannelRole, ChannelVisibility},
-    User, UserId, UserStore,
-};
-use fuzzy::{match_strings, StringMatchCandidate};
-use gpui::{
-    actions, div, overlay, AppContext, ClipboardItem, DismissEvent, EventEmitter, FocusableView,
-    Model, ParentElement, Render, Styled, Subscription, Task, View, ViewContext, VisualContext,
-    WeakView,
-};
-use picker::{Picker, PickerDelegate};
-use std::sync::Arc;
-use ui::{prelude::*, Avatar, Checkbox, ContextMenu, ListItem, ListItemSpacing};
-use util::TryFutureExt;
-use workspace::ModalView;
-
-actions!(
-    channel_modal,
-    [
-        SelectNextControl,
-        ToggleMode,
-        ToggleMemberAdmin,
-        RemoveMember
-    ]
-);
-
-pub struct ChannelModal {
-    picker: View<Picker<ChannelModalDelegate>>,
-    channel_store: Model<ChannelStore>,
-    channel_id: ChannelId,
-}
-
-impl ChannelModal {
-    pub fn new(
-        user_store: Model<UserStore>,
-        channel_store: Model<ChannelStore>,
-        channel_id: ChannelId,
-        mode: Mode,
-        members: Vec<ChannelMembership>,
-        cx: &mut ViewContext<Self>,
-    ) -> Self {
-        cx.observe(&channel_store, |_, _, cx| cx.notify()).detach();
-        let channel_modal = cx.view().downgrade();
-        let picker = cx.new_view(|cx| {
-            Picker::new(
-                ChannelModalDelegate {
-                    channel_modal,
-                    matching_users: Vec::new(),
-                    matching_member_indices: Vec::new(),
-                    selected_index: 0,
-                    user_store: user_store.clone(),
-                    channel_store: channel_store.clone(),
-                    channel_id,
-                    match_candidates: Vec::new(),
-                    context_menu: None,
-                    members,
-                    mode,
-                },
-                cx,
-            )
-            .modal(false)
-        });
-
-        Self {
-            picker,
-            channel_store,
-            channel_id,
-        }
-    }
-
-    fn toggle_mode(&mut self, _: &ToggleMode, cx: &mut ViewContext<Self>) {
-        let mode = match self.picker.read(cx).delegate.mode {
-            Mode::ManageMembers => Mode::InviteMembers,
-            Mode::InviteMembers => Mode::ManageMembers,
-        };
-        self.set_mode(mode, cx);
-    }
-
-    fn set_mode(&mut self, mode: Mode, cx: &mut ViewContext<Self>) {
-        let channel_store = self.channel_store.clone();
-        let channel_id = self.channel_id;
-        cx.spawn(|this, mut cx| async move {
-            if mode == Mode::ManageMembers {
-                let mut members = channel_store
-                    .update(&mut cx, |channel_store, cx| {
-                        channel_store.get_channel_member_details(channel_id, cx)
-                    })?
-                    .await?;
-
-                members.sort_by(|a, b| a.sort_key().cmp(&b.sort_key()));
-
-                this.update(&mut cx, |this, cx| {
-                    this.picker
-                        .update(cx, |picker, _| picker.delegate.members = members);
-                })?;
-            }
-
-            this.update(&mut cx, |this, cx| {
-                this.picker.update(cx, |picker, cx| {
-                    let delegate = &mut picker.delegate;
-                    delegate.mode = mode;
-                    delegate.selected_index = 0;
-                    picker.set_query("", cx);
-                    picker.update_matches(picker.query(cx), cx);
-                    cx.notify()
-                });
-                cx.notify()
-            })
-        })
-        .detach();
-    }
-
-    fn set_channel_visiblity(&mut self, selection: &Selection, cx: &mut ViewContext<Self>) {
-        self.channel_store.update(cx, |channel_store, cx| {
-            channel_store
-                .set_channel_visibility(
-                    self.channel_id,
-                    match selection {
-                        Selection::Unselected => ChannelVisibility::Members,
-                        Selection::Selected => ChannelVisibility::Public,
-                        Selection::Indeterminate => return,
-                    },
-                    cx,
-                )
-                .detach_and_log_err(cx)
-        });
-    }
-
-    fn dismiss(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
-        cx.emit(DismissEvent);
-    }
-}
-
-impl EventEmitter<DismissEvent> for ChannelModal {}
-impl ModalView for ChannelModal {}
-
-impl FocusableView for ChannelModal {
-    fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
-        self.picker.focus_handle(cx)
-    }
-}
-
-impl Render for ChannelModal {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        let channel_store = self.channel_store.read(cx);
-        let Some(channel) = channel_store.channel_for_id(self.channel_id) else {
-            return div();
-        };
-        let channel_name = channel.name.clone();
-        let channel_id = channel.id;
-        let visibility = channel.visibility;
-        let mode = self.picker.read(cx).delegate.mode;
-
-        v_stack()
-            .key_context("ChannelModal")
-            .on_action(cx.listener(Self::toggle_mode))
-            .on_action(cx.listener(Self::dismiss))
-            .elevation_3(cx)
-            .w(rems(34.))
-            .child(
-                v_stack()
-                    .px_2()
-                    .py_1()
-                    .rounded_t(px(8.))
-                    .bg(cx.theme().colors().element_background)
-                    .child(IconElement::new(Icon::Hash).size(IconSize::Medium))
-                    .child(Label::new(channel_name))
-                    .child(
-                        h_stack()
-                            .w_full()
-                            .justify_between()
-                            .child(
-                                h_stack()
-                                    .gap_2()
-                                    .child(
-                                        Checkbox::new(
-                                            "is-public",
-                                            if visibility == ChannelVisibility::Public {
-                                                ui::Selection::Selected
-                                            } else {
-                                                ui::Selection::Unselected
-                                            },
-                                        )
-                                        .on_click(cx.listener(Self::set_channel_visiblity)),
-                                    )
-                                    .child(Label::new("Public")),
-                            )
-                            .children(if visibility == ChannelVisibility::Public {
-                                Some(Button::new("copy-link", "Copy Link").on_click(cx.listener(
-                                    move |this, _, cx| {
-                                        if let Some(channel) =
-                                            this.channel_store.read(cx).channel_for_id(channel_id)
-                                        {
-                                            let item = ClipboardItem::new(channel.link());
-                                            cx.write_to_clipboard(item);
-                                        }
-                                    },
-                                )))
-                            } else {
-                                None
-                            }),
-                    )
-                    .child(
-                        div()
-                            .w_full()
-                            .flex()
-                            .flex_row()
-                            .child(
-                                Button::new("manage-members", "Manage Members")
-                                    .selected(mode == Mode::ManageMembers)
-                                    .on_click(cx.listener(|this, _, cx| {
-                                        this.set_mode(Mode::ManageMembers, cx);
-                                    })),
-                            )
-                            .child(
-                                Button::new("invite-members", "Invite Members")
-                                    .selected(mode == Mode::InviteMembers)
-                                    .on_click(cx.listener(|this, _, cx| {
-                                        this.set_mode(Mode::InviteMembers, cx);
-                                    })),
-                            ),
-                    ),
-            )
-            .child(self.picker.clone())
-    }
-}
-
-#[derive(Copy, Clone, PartialEq)]
-pub enum Mode {
-    ManageMembers,
-    InviteMembers,
-}
-
-pub struct ChannelModalDelegate {
-    channel_modal: WeakView<ChannelModal>,
-    matching_users: Vec<Arc<User>>,
-    matching_member_indices: Vec<usize>,
-    user_store: Model<UserStore>,
-    channel_store: Model<ChannelStore>,
-    channel_id: ChannelId,
-    selected_index: usize,
-    mode: Mode,
-    match_candidates: Vec<StringMatchCandidate>,
-    members: Vec<ChannelMembership>,
-    context_menu: Option<(View<ContextMenu>, Subscription)>,
-}
-
-impl PickerDelegate for ChannelModalDelegate {
-    type ListItem = ListItem;
-
-    fn placeholder_text(&self) -> Arc<str> {
-        "Search collaborator by username...".into()
-    }
-
-    fn match_count(&self) -> usize {
-        match self.mode {
-            Mode::ManageMembers => self.matching_member_indices.len(),
-            Mode::InviteMembers => self.matching_users.len(),
-        }
-    }
-
-    fn selected_index(&self) -> usize {
-        self.selected_index
-    }
-
-    fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
-        self.selected_index = ix;
-    }
-
-    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
-        match self.mode {
-            Mode::ManageMembers => {
-                self.match_candidates.clear();
-                self.match_candidates
-                    .extend(self.members.iter().enumerate().map(|(id, member)| {
-                        StringMatchCandidate {
-                            id,
-                            string: member.user.github_login.clone(),
-                            char_bag: member.user.github_login.chars().collect(),
-                        }
-                    }));
-
-                let matches = cx.background_executor().block(match_strings(
-                    &self.match_candidates,
-                    &query,
-                    true,
-                    usize::MAX,
-                    &Default::default(),
-                    cx.background_executor().clone(),
-                ));
-
-                cx.spawn(|picker, mut cx| async move {
-                    picker
-                        .update(&mut cx, |picker, cx| {
-                            let delegate = &mut picker.delegate;
-                            delegate.matching_member_indices.clear();
-                            delegate
-                                .matching_member_indices
-                                .extend(matches.into_iter().map(|m| m.candidate_id));
-                            cx.notify();
-                        })
-                        .ok();
-                })
-            }
-            Mode::InviteMembers => {
-                let search_users = self
-                    .user_store
-                    .update(cx, |store, cx| store.fuzzy_search_users(query, cx));
-                cx.spawn(|picker, mut cx| async move {
-                    async {
-                        let users = search_users.await?;
-                        picker.update(&mut cx, |picker, cx| {
-                            picker.delegate.matching_users = users;
-                            cx.notify();
-                        })?;
-                        anyhow::Ok(())
-                    }
-                    .log_err()
-                    .await;
-                })
-            }
-        }
-    }
-
-    fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
-        if let Some((selected_user, role)) = self.user_at_index(self.selected_index) {
-            match self.mode {
-                Mode::ManageMembers => {
-                    self.show_context_menu(selected_user, role.unwrap_or(ChannelRole::Member), cx)
-                }
-                Mode::InviteMembers => match self.member_status(selected_user.id, cx) {
-                    Some(proto::channel_member::Kind::Invitee) => {
-                        self.remove_member(selected_user.id, cx);
-                    }
-                    Some(proto::channel_member::Kind::AncestorMember) | None => {
-                        self.invite_member(selected_user, cx)
-                    }
-                    Some(proto::channel_member::Kind::Member) => {}
-                },
-            }
-        }
-    }
-
-    fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
-        if self.context_menu.is_none() {
-            self.channel_modal
-                .update(cx, |_, cx| {
-                    cx.emit(DismissEvent);
-                })
-                .ok();
-        }
-    }
-
-    fn render_match(
-        &self,
-        ix: usize,
-        selected: bool,
-        cx: &mut ViewContext<Picker<Self>>,
-    ) -> Option<Self::ListItem> {
-        let (user, role) = self.user_at_index(ix)?;
-        let request_status = self.member_status(user.id, cx);
-
-        Some(
-            ListItem::new(ix)
-                .inset(true)
-                .spacing(ListItemSpacing::Sparse)
-                .selected(selected)
-                .start_slot(Avatar::new(user.avatar_uri.clone()))
-                .child(Label::new(user.github_login.clone()))
-                .end_slot(h_stack().gap_2().map(|slot| {
-                    match self.mode {
-                        Mode::ManageMembers => slot
-                            .children(
-                                if request_status == Some(proto::channel_member::Kind::Invitee) {
-                                    Some(Label::new("Invited"))
-                                } else {
-                                    None
-                                },
-                            )
-                            .children(match role {
-                                Some(ChannelRole::Admin) => Some(Label::new("Admin")),
-                                Some(ChannelRole::Guest) => Some(Label::new("Guest")),
-                                _ => None,
-                            })
-                            .child(IconButton::new("ellipsis", Icon::Ellipsis))
-                            .children(
-                                if let (Some((menu, _)), true) = (&self.context_menu, selected) {
-                                    Some(
-                                        overlay()
-                                            .anchor(gpui::AnchorCorner::TopLeft)
-                                            .child(menu.clone()),
-                                    )
-                                } else {
-                                    None
-                                },
-                            ),
-                        Mode::InviteMembers => match request_status {
-                            Some(proto::channel_member::Kind::Invitee) => {
-                                slot.children(Some(Label::new("Invited")))
-                            }
-                            Some(proto::channel_member::Kind::Member) => {
-                                slot.children(Some(Label::new("Member")))
-                            }
-                            _ => slot,
-                        },
-                    }
-                })),
-        )
-    }
-}
-
-impl ChannelModalDelegate {
-    fn member_status(
-        &self,
-        user_id: UserId,
-        cx: &AppContext,
-    ) -> Option<proto::channel_member::Kind> {
-        self.members
-            .iter()
-            .find_map(|membership| (membership.user.id == user_id).then_some(membership.kind))
-            .or_else(|| {
-                self.channel_store
-                    .read(cx)
-                    .has_pending_channel_invite(self.channel_id, user_id)
-                    .then_some(proto::channel_member::Kind::Invitee)
-            })
-    }
-
-    fn user_at_index(&self, ix: usize) -> Option<(Arc<User>, Option<ChannelRole>)> {
-        match self.mode {
-            Mode::ManageMembers => self.matching_member_indices.get(ix).and_then(|ix| {
-                let channel_membership = self.members.get(*ix)?;
-                Some((
-                    channel_membership.user.clone(),
-                    Some(channel_membership.role),
-                ))
-            }),
-            Mode::InviteMembers => Some((self.matching_users.get(ix).cloned()?, None)),
-        }
-    }
-
-    fn set_user_role(
-        &mut self,
-        user_id: UserId,
-        new_role: ChannelRole,
-        cx: &mut ViewContext<Picker<Self>>,
-    ) -> Option<()> {
-        let update = self.channel_store.update(cx, |store, cx| {
-            store.set_member_role(self.channel_id, user_id, new_role, cx)
-        });
-        cx.spawn(|picker, mut cx| async move {
-            update.await?;
-            picker.update(&mut cx, |picker, cx| {
-                let this = &mut picker.delegate;
-                if let Some(member) = this.members.iter_mut().find(|m| m.user.id == user_id) {
-                    member.role = new_role;
-                }
-                cx.focus_self();
-                cx.notify();
-            })
-        })
-        .detach_and_log_err(cx);
-        Some(())
-    }
-
-    fn remove_member(&mut self, user_id: UserId, cx: &mut ViewContext<Picker<Self>>) -> Option<()> {
-        let update = self.channel_store.update(cx, |store, cx| {
-            store.remove_member(self.channel_id, user_id, cx)
-        });
-        cx.spawn(|picker, mut cx| async move {
-            update.await?;
-            picker.update(&mut cx, |picker, cx| {
-                let this = &mut picker.delegate;
-                if let Some(ix) = this.members.iter_mut().position(|m| m.user.id == user_id) {
-                    this.members.remove(ix);
-                    this.matching_member_indices.retain_mut(|member_ix| {
-                        if *member_ix == ix {
-                            return false;
-                        } else if *member_ix > ix {
-                            *member_ix -= 1;
-                        }
-                        true
-                    })
-                }
-
-                this.selected_index = this
-                    .selected_index
-                    .min(this.matching_member_indices.len().saturating_sub(1));
-
-                picker.focus(cx);
-                cx.notify();
-            })
-        })
-        .detach_and_log_err(cx);
-        Some(())
-    }
-
-    fn invite_member(&mut self, user: Arc<User>, cx: &mut ViewContext<Picker<Self>>) {
-        let invite_member = self.channel_store.update(cx, |store, cx| {
-            store.invite_member(self.channel_id, user.id, ChannelRole::Member, cx)
-        });
-
-        cx.spawn(|this, mut cx| async move {
-            invite_member.await?;
-
-            this.update(&mut cx, |this, cx| {
-                let new_member = ChannelMembership {
-                    user,
-                    kind: proto::channel_member::Kind::Invitee,
-                    role: ChannelRole::Member,
-                };
-                let members = &mut this.delegate.members;
-                match members.binary_search_by_key(&new_member.sort_key(), |k| k.sort_key()) {
-                    Ok(ix) | Err(ix) => members.insert(ix, new_member),
-                }
-
-                cx.notify();
-            })
-        })
-        .detach_and_log_err(cx);
-    }
-
-    fn show_context_menu(
-        &mut self,
-        user: Arc<User>,
-        role: ChannelRole,
-        cx: &mut ViewContext<Picker<Self>>,
-    ) {
-        let user_id = user.id;
-        let picker = cx.view().clone();
-        let context_menu = ContextMenu::build(cx, |mut menu, _cx| {
-            menu = menu.entry("Remove Member", None, {
-                let picker = picker.clone();
-                move |cx| {
-                    picker.update(cx, |picker, cx| {
-                        picker.delegate.remove_member(user_id, cx);
-                    })
-                }
-            });
-
-            let picker = picker.clone();
-            match role {
-                ChannelRole::Admin => {
-                    menu = menu.entry("Revoke Admin", None, move |cx| {
-                        picker.update(cx, |picker, cx| {
-                            picker
-                                .delegate
-                                .set_user_role(user_id, ChannelRole::Member, cx);
-                        })
-                    });
-                }
-                ChannelRole::Member => {
-                    menu = menu.entry("Make Admin", None, move |cx| {
-                        picker.update(cx, |picker, cx| {
-                            picker
-                                .delegate
-                                .set_user_role(user_id, ChannelRole::Admin, cx);
-                        })
-                    });
-                }
-                _ => {}
-            };
-
-            menu
-        });
-        cx.focus_view(&context_menu);
-        let subscription = cx.subscribe(&context_menu, |picker, _, _: &DismissEvent, cx| {
-            picker.delegate.context_menu = None;
-            picker.focus(cx);
-            cx.notify();
-        });
-        self.context_menu = Some((context_menu, subscription));
-    }
-}

crates/collab_ui2/src/collab_panel/contact_finder.rs 🔗

@@ -1,163 +0,0 @@
-use client::{ContactRequestStatus, User, UserStore};
-use gpui::{
-    AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model, ParentElement as _,
-    Render, Styled, Task, View, ViewContext, VisualContext, WeakView,
-};
-use picker::{Picker, PickerDelegate};
-use std::sync::Arc;
-use theme::ActiveTheme as _;
-use ui::{prelude::*, Avatar, ListItem, ListItemSpacing};
-use util::{ResultExt as _, TryFutureExt};
-use workspace::ModalView;
-
-pub struct ContactFinder {
-    picker: View<Picker<ContactFinderDelegate>>,
-}
-
-impl ContactFinder {
-    pub fn new(user_store: Model<UserStore>, cx: &mut ViewContext<Self>) -> Self {
-        let delegate = ContactFinderDelegate {
-            parent: cx.view().downgrade(),
-            user_store,
-            potential_contacts: Arc::from([]),
-            selected_index: 0,
-        };
-        let picker = cx.new_view(|cx| Picker::new(delegate, cx).modal(false));
-
-        Self { picker }
-    }
-
-    pub fn set_query(&mut self, query: String, cx: &mut ViewContext<Self>) {
-        self.picker.update(cx, |picker, cx| {
-            picker.set_query(query, cx);
-        });
-    }
-}
-
-impl Render for ContactFinder {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        v_stack()
-            .elevation_3(cx)
-            .child(
-                v_stack()
-                    .px_2()
-                    .py_1()
-                    .bg(cx.theme().colors().element_background)
-                    // HACK: Prevent the background color from overflowing the parent container.
-                    .rounded_t(px(8.))
-                    .child(Label::new("Contacts"))
-                    .child(h_stack().child(Label::new("Invite new contacts"))),
-            )
-            .child(self.picker.clone())
-            .w(rems(34.))
-    }
-}
-
-pub struct ContactFinderDelegate {
-    parent: WeakView<ContactFinder>,
-    potential_contacts: Arc<[Arc<User>]>,
-    user_store: Model<UserStore>,
-    selected_index: usize,
-}
-
-impl EventEmitter<DismissEvent> for ContactFinder {}
-impl ModalView for ContactFinder {}
-
-impl FocusableView for ContactFinder {
-    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
-        self.picker.focus_handle(cx)
-    }
-}
-
-impl PickerDelegate for ContactFinderDelegate {
-    type ListItem = ListItem;
-
-    fn match_count(&self) -> usize {
-        self.potential_contacts.len()
-    }
-
-    fn selected_index(&self) -> usize {
-        self.selected_index
-    }
-
-    fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
-        self.selected_index = ix;
-    }
-
-    fn placeholder_text(&self) -> Arc<str> {
-        "Search collaborator by username...".into()
-    }
-
-    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
-        let search_users = self
-            .user_store
-            .update(cx, |store, cx| store.fuzzy_search_users(query, cx));
-
-        cx.spawn(|picker, mut cx| async move {
-            async {
-                let potential_contacts = search_users.await?;
-                picker.update(&mut cx, |picker, cx| {
-                    picker.delegate.potential_contacts = potential_contacts.into();
-                    cx.notify();
-                })?;
-                anyhow::Ok(())
-            }
-            .log_err()
-            .await;
-        })
-    }
-
-    fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
-        if let Some(user) = self.potential_contacts.get(self.selected_index) {
-            let user_store = self.user_store.read(cx);
-            match user_store.contact_request_status(user) {
-                ContactRequestStatus::None | ContactRequestStatus::RequestReceived => {
-                    self.user_store
-                        .update(cx, |store, cx| store.request_contact(user.id, cx))
-                        .detach();
-                }
-                ContactRequestStatus::RequestSent => {
-                    self.user_store
-                        .update(cx, |store, cx| store.remove_contact(user.id, cx))
-                        .detach();
-                }
-                _ => {}
-            }
-        }
-    }
-
-    fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
-        self.parent
-            .update(cx, |_, cx| cx.emit(DismissEvent))
-            .log_err();
-    }
-
-    fn render_match(
-        &self,
-        ix: usize,
-        selected: bool,
-        cx: &mut ViewContext<Picker<Self>>,
-    ) -> Option<Self::ListItem> {
-        let user = &self.potential_contacts[ix];
-        let request_status = self.user_store.read(cx).contact_request_status(user);
-
-        let icon_path = match request_status {
-            ContactRequestStatus::None | ContactRequestStatus::RequestReceived => {
-                Some("icons/check.svg")
-            }
-            ContactRequestStatus::RequestSent => Some("icons/x.svg"),
-            ContactRequestStatus::RequestAccepted => None,
-        };
-        Some(
-            ListItem::new(ix)
-                .inset(true)
-                .spacing(ListItemSpacing::Sparse)
-                .selected(selected)
-                .start_slot(Avatar::new(user.avatar_uri.clone()))
-                .child(Label::new(user.github_login.clone()))
-                .end_slot::<IconElement>(
-                    icon_path.map(|icon_path| IconElement::from_path(icon_path)),
-                ),
-        )
-    }
-}

crates/collab_ui2/src/collab_titlebar_item.rs 🔗

@@ -1,586 +0,0 @@
-use crate::face_pile::FacePile;
-use auto_update::AutoUpdateStatus;
-use call::{ActiveCall, ParticipantLocation, Room};
-use client::{proto::PeerId, Client, ParticipantIndex, User, UserStore};
-use gpui::{
-    actions, canvas, div, point, px, rems, Action, AnyElement, AppContext, Element, Hsla,
-    InteractiveElement, IntoElement, Model, ParentElement, Path, Render,
-    StatefulInteractiveElement, Styled, Subscription, View, ViewContext, VisualContext, WeakView,
-    WindowBounds,
-};
-use project::{Project, RepositoryEntry};
-use recent_projects::RecentProjects;
-use std::sync::Arc;
-use theme::{ActiveTheme, PlayerColors};
-use ui::{
-    h_stack, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon,
-    IconButton, IconElement, Tooltip,
-};
-use util::ResultExt;
-use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu};
-use workspace::{notifications::NotifyResultExt, Workspace};
-
-const MAX_PROJECT_NAME_LENGTH: usize = 40;
-const MAX_BRANCH_NAME_LENGTH: usize = 40;
-
-actions!(
-    collab,
-    [
-        ShareProject,
-        UnshareProject,
-        ToggleUserMenu,
-        ToggleProjectMenu,
-        SwitchBranch
-    ]
-);
-
-pub fn init(cx: &mut AppContext) {
-    cx.observe_new_views(|workspace: &mut Workspace, cx| {
-        let titlebar_item = cx.new_view(|cx| CollabTitlebarItem::new(workspace, cx));
-        workspace.set_titlebar_item(titlebar_item.into(), cx)
-    })
-    .detach();
-    // cx.add_action(CollabTitlebarItem::share_project);
-    // cx.add_action(CollabTitlebarItem::unshare_project);
-    // cx.add_action(CollabTitlebarItem::toggle_user_menu);
-    // cx.add_action(CollabTitlebarItem::toggle_vcs_menu);
-    // cx.add_action(CollabTitlebarItem::toggle_project_menu);
-}
-
-pub struct CollabTitlebarItem {
-    project: Model<Project>,
-    user_store: Model<UserStore>,
-    client: Arc<Client>,
-    workspace: WeakView<Workspace>,
-    _subscriptions: Vec<Subscription>,
-}
-
-impl Render for CollabTitlebarItem {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        let room = ActiveCall::global(cx).read(cx).room().cloned();
-        let current_user = self.user_store.read(cx).current_user();
-        let client = self.client.clone();
-        let project_id = self.project.read(cx).remote_id();
-
-        h_stack()
-            .id("titlebar")
-            .justify_between()
-            .w_full()
-            .h(rems(1.75))
-            // Set a non-scaling min-height here to ensure the titlebar is
-            // always at least the height of the traffic lights.
-            .min_h(px(32.))
-            .map(|this| {
-                if matches!(cx.window_bounds(), WindowBounds::Fullscreen) {
-                    this.pl_2()
-                } else {
-                    // Use pixels here instead of a rem-based size because the macOS traffic
-                    // lights are a static size, and don't scale with the rest of the UI.
-                    this.pl(px(80.))
-                }
-            })
-            .bg(cx.theme().colors().title_bar_background)
-            .on_click(|event, cx| {
-                if event.up.click_count == 2 {
-                    cx.zoom_window();
-                }
-            })
-            // left side
-            .child(
-                h_stack()
-                    .gap_1()
-                    .children(self.render_project_host(cx))
-                    .child(self.render_project_name(cx))
-                    .children(self.render_project_branch(cx))
-                    .when_some(
-                        current_user.clone().zip(client.peer_id()).zip(room.clone()),
-                        |this, ((current_user, peer_id), room)| {
-                            let player_colors = cx.theme().players();
-                            let room = room.read(cx);
-                            let mut remote_participants =
-                                room.remote_participants().values().collect::<Vec<_>>();
-                            remote_participants.sort_by_key(|p| p.participant_index.0);
-
-                            this.children(self.render_collaborator(
-                                &current_user,
-                                peer_id,
-                                true,
-                                room.is_speaking(),
-                                room.is_muted(cx),
-                                &room,
-                                project_id,
-                                &current_user,
-                            ))
-                            .children(
-                                remote_participants.iter().filter_map(|collaborator| {
-                                    let is_present = project_id.map_or(false, |project_id| {
-                                        collaborator.location
-                                            == ParticipantLocation::SharedProject { project_id }
-                                    });
-
-                                    let face_pile = self.render_collaborator(
-                                        &collaborator.user,
-                                        collaborator.peer_id,
-                                        is_present,
-                                        collaborator.speaking,
-                                        collaborator.muted,
-                                        &room,
-                                        project_id,
-                                        &current_user,
-                                    )?;
-
-                                    Some(
-                                        v_stack()
-                                            .id(("collaborator", collaborator.user.id))
-                                            .child(face_pile)
-                                            .child(render_color_ribbon(
-                                                collaborator.participant_index,
-                                                player_colors,
-                                            ))
-                                            .cursor_pointer()
-                                            .on_click({
-                                                let peer_id = collaborator.peer_id;
-                                                cx.listener(move |this, _, cx| {
-                                                    this.workspace
-                                                        .update(cx, |workspace, cx| {
-                                                            workspace.follow(peer_id, cx);
-                                                        })
-                                                        .ok();
-                                                })
-                                            })
-                                            .tooltip({
-                                                let login = collaborator.user.github_login.clone();
-                                                move |cx| {
-                                                    Tooltip::text(format!("Follow {login}"), cx)
-                                                }
-                                            }),
-                                    )
-                                }),
-                            )
-                        },
-                    ),
-            )
-            // right side
-            .child(
-                h_stack()
-                    .gap_1()
-                    .pr_1()
-                    .when_some(room, |this, room| {
-                        let room = room.read(cx);
-                        let project = self.project.read(cx);
-                        let is_local = project.is_local();
-                        let is_shared = is_local && project.is_shared();
-                        let is_muted = room.is_muted(cx);
-                        let is_deafened = room.is_deafened().unwrap_or(false);
-                        let is_screen_sharing = room.is_screen_sharing();
-
-                        this.when(is_local, |this| {
-                            this.child(
-                                Button::new(
-                                    "toggle_sharing",
-                                    if is_shared { "Unshare" } else { "Share" },
-                                )
-                                .style(ButtonStyle::Subtle)
-                                .label_size(LabelSize::Small)
-                                .on_click(cx.listener(
-                                    move |this, _, cx| {
-                                        if is_shared {
-                                            this.unshare_project(&Default::default(), cx);
-                                        } else {
-                                            this.share_project(&Default::default(), cx);
-                                        }
-                                    },
-                                )),
-                            )
-                        })
-                        .child(
-                            IconButton::new("leave-call", ui::Icon::Exit)
-                                .style(ButtonStyle::Subtle)
-                                .icon_size(IconSize::Small)
-                                .on_click(move |_, cx| {
-                                    ActiveCall::global(cx)
-                                        .update(cx, |call, cx| call.hang_up(cx))
-                                        .detach_and_log_err(cx);
-                                }),
-                        )
-                        .child(
-                            IconButton::new(
-                                "mute-microphone",
-                                if is_muted {
-                                    ui::Icon::MicMute
-                                } else {
-                                    ui::Icon::Mic
-                                },
-                            )
-                            .style(ButtonStyle::Subtle)
-                            .icon_size(IconSize::Small)
-                            .selected(is_muted)
-                            .on_click(move |_, cx| crate::toggle_mute(&Default::default(), cx)),
-                        )
-                        .child(
-                            IconButton::new(
-                                "mute-sound",
-                                if is_deafened {
-                                    ui::Icon::AudioOff
-                                } else {
-                                    ui::Icon::AudioOn
-                                },
-                            )
-                            .style(ButtonStyle::Subtle)
-                            .icon_size(IconSize::Small)
-                            .selected(is_deafened)
-                            .tooltip(move |cx| {
-                                Tooltip::with_meta("Deafen Audio", None, "Mic will be muted", cx)
-                            })
-                            .on_click(move |_, cx| crate::toggle_mute(&Default::default(), cx)),
-                        )
-                        .child(
-                            IconButton::new("screen-share", ui::Icon::Screen)
-                                .style(ButtonStyle::Subtle)
-                                .icon_size(IconSize::Small)
-                                .selected(is_screen_sharing)
-                                .on_click(move |_, cx| {
-                                    crate::toggle_screen_sharing(&Default::default(), cx)
-                                }),
-                        )
-                    })
-                    .map(|el| {
-                        let status = self.client.status();
-                        let status = &*status.borrow();
-                        if matches!(status, client::Status::Connected { .. }) {
-                            el.child(self.render_user_menu_button(cx))
-                        } else {
-                            el.children(self.render_connection_status(status, cx))
-                                .child(self.render_sign_in_button(cx))
-                                .child(self.render_user_menu_button(cx))
-                        }
-                    }),
-            )
-    }
-}
-
-fn render_color_ribbon(participant_index: ParticipantIndex, colors: &PlayerColors) -> gpui::Canvas {
-    let color = colors.color_for_participant(participant_index.0).cursor;
-    canvas(move |bounds, cx| {
-        let mut path = Path::new(bounds.lower_left());
-        let height = bounds.size.height;
-        path.curve_to(bounds.origin + point(height, px(0.)), bounds.origin);
-        path.line_to(bounds.upper_right() - point(height, px(0.)));
-        path.curve_to(bounds.lower_right(), bounds.upper_right());
-        path.line_to(bounds.lower_left());
-        cx.paint_path(path, color);
-    })
-    .h_1()
-    .w_full()
-}
-
-impl CollabTitlebarItem {
-    pub fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
-        let project = workspace.project().clone();
-        let user_store = workspace.app_state().user_store.clone();
-        let client = workspace.app_state().client.clone();
-        let active_call = ActiveCall::global(cx);
-        let mut subscriptions = Vec::new();
-        subscriptions.push(
-            cx.observe(&workspace.weak_handle().upgrade().unwrap(), |_, _, cx| {
-                cx.notify()
-            }),
-        );
-        subscriptions.push(cx.observe(&project, |_, _, cx| cx.notify()));
-        subscriptions.push(cx.observe(&active_call, |this, _, cx| this.active_call_changed(cx)));
-        subscriptions.push(cx.observe_window_activation(Self::window_activation_changed));
-        subscriptions.push(cx.observe(&user_store, |_, _, cx| cx.notify()));
-
-        Self {
-            workspace: workspace.weak_handle(),
-            project,
-            user_store,
-            client,
-            _subscriptions: subscriptions,
-        }
-    }
-
-    // resolve if you are in a room -> render_project_owner
-    // render_project_owner -> resolve if you are in a room -> Option<foo>
-
-    pub fn render_project_host(&self, cx: &mut ViewContext<Self>) -> Option<impl Element> {
-        let host = self.project.read(cx).host()?;
-        let host = self.user_store.read(cx).get_cached_user(host.user_id)?;
-        let participant_index = self
-            .user_store
-            .read(cx)
-            .participant_indices()
-            .get(&host.id)?;
-        Some(
-            div().border().border_color(gpui::red()).child(
-                Button::new("project_owner_trigger", host.github_login.clone())
-                    .color(Color::Player(participant_index.0))
-                    .style(ButtonStyle::Subtle)
-                    .label_size(LabelSize::Small)
-                    .tooltip(move |cx| Tooltip::text("Toggle following", cx)),
-            ),
-        )
-    }
-
-    pub fn render_project_name(&self, cx: &mut ViewContext<Self>) -> impl Element {
-        let name = {
-            let mut names = self.project.read(cx).visible_worktrees(cx).map(|worktree| {
-                let worktree = worktree.read(cx);
-                worktree.root_name()
-            });
-
-            names.next().unwrap_or("")
-        };
-
-        let name = util::truncate_and_trailoff(name, MAX_PROJECT_NAME_LENGTH);
-        let workspace = self.workspace.clone();
-        popover_menu("project_name_trigger")
-            .trigger(
-                Button::new("project_name_trigger", name)
-                    .style(ButtonStyle::Subtle)
-                    .label_size(LabelSize::Small)
-                    .tooltip(move |cx| Tooltip::text("Recent Projects", cx)),
-            )
-            .menu(move |cx| Some(Self::render_project_popover(workspace.clone(), cx)))
-    }
-
-    pub fn render_project_branch(&self, cx: &mut ViewContext<Self>) -> Option<impl Element> {
-        let entry = {
-            let mut names_and_branches =
-                self.project.read(cx).visible_worktrees(cx).map(|worktree| {
-                    let worktree = worktree.read(cx);
-                    worktree.root_git_entry()
-                });
-
-            names_and_branches.next().flatten()
-        };
-        let workspace = self.workspace.upgrade()?;
-        let branch_name = entry
-            .as_ref()
-            .and_then(RepositoryEntry::branch)
-            .map(|branch| util::truncate_and_trailoff(&branch, MAX_BRANCH_NAME_LENGTH))?;
-        Some(
-            popover_menu("project_branch_trigger")
-                .trigger(
-                    Button::new("project_branch_trigger", branch_name)
-                        .color(Color::Muted)
-                        .style(ButtonStyle::Subtle)
-                        .label_size(LabelSize::Small)
-                        .tooltip(move |cx| {
-                            Tooltip::with_meta(
-                                "Recent Branches",
-                                Some(&ToggleVcsMenu),
-                                "Local branches only",
-                                cx,
-                            )
-                        }),
-                )
-                .menu(move |cx| Self::render_vcs_popover(workspace.clone(), cx)),
-        )
-    }
-
-    fn render_collaborator(
-        &self,
-        user: &Arc<User>,
-        peer_id: PeerId,
-        is_present: bool,
-        is_speaking: bool,
-        is_muted: bool,
-        room: &Room,
-        project_id: Option<u64>,
-        current_user: &Arc<User>,
-    ) -> Option<FacePile> {
-        let followers = project_id.map_or(&[] as &[_], |id| room.followers_for(peer_id, id));
-
-        let pile = FacePile::default()
-            .child(
-                Avatar::new(user.avatar_uri.clone())
-                    .grayscale(!is_present)
-                    .border_color(if is_speaking {
-                        gpui::blue()
-                    } else if is_muted {
-                        gpui::red()
-                    } else {
-                        Hsla::default()
-                    }),
-            )
-            .children(followers.iter().filter_map(|follower_peer_id| {
-                let follower = room
-                    .remote_participants()
-                    .values()
-                    .find_map(|p| (p.peer_id == *follower_peer_id).then_some(&p.user))
-                    .or_else(|| {
-                        (self.client.peer_id() == Some(*follower_peer_id)).then_some(current_user)
-                    })?
-                    .clone();
-
-                Some(Avatar::new(follower.avatar_uri.clone()))
-            }));
-
-        Some(pile)
-    }
-
-    fn window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
-        let project = if cx.is_window_active() {
-            Some(self.project.clone())
-        } else {
-            None
-        };
-        ActiveCall::global(cx)
-            .update(cx, |call, cx| call.set_location(project.as_ref(), cx))
-            .detach_and_log_err(cx);
-    }
-
-    fn active_call_changed(&mut self, cx: &mut ViewContext<Self>) {
-        cx.notify();
-    }
-
-    fn share_project(&mut self, _: &ShareProject, cx: &mut ViewContext<Self>) {
-        let active_call = ActiveCall::global(cx);
-        let project = self.project.clone();
-        active_call
-            .update(cx, |call, cx| call.share_project(project, cx))
-            .detach_and_log_err(cx);
-    }
-
-    fn unshare_project(&mut self, _: &UnshareProject, cx: &mut ViewContext<Self>) {
-        let active_call = ActiveCall::global(cx);
-        let project = self.project.clone();
-        active_call
-            .update(cx, |call, cx| call.unshare_project(project, cx))
-            .log_err();
-    }
-
-    pub fn render_vcs_popover(
-        workspace: View<Workspace>,
-        cx: &mut WindowContext<'_>,
-    ) -> Option<View<BranchList>> {
-        let view = build_branch_list(workspace, cx).log_err()?;
-        let focus_handle = view.focus_handle(cx);
-        cx.focus(&focus_handle);
-        Some(view)
-    }
-
-    pub fn render_project_popover(
-        workspace: WeakView<Workspace>,
-        cx: &mut WindowContext<'_>,
-    ) -> View<RecentProjects> {
-        let view = RecentProjects::open_popover(workspace, cx);
-
-        let focus_handle = view.focus_handle(cx);
-        cx.focus(&focus_handle);
-        view
-    }
-
-    fn render_connection_status(
-        &self,
-        status: &client::Status,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<AnyElement> {
-        match status {
-            client::Status::ConnectionError
-            | client::Status::ConnectionLost
-            | client::Status::Reauthenticating { .. }
-            | client::Status::Reconnecting { .. }
-            | client::Status::ReconnectionError { .. } => Some(
-                div()
-                    .id("disconnected")
-                    .bg(gpui::red()) // todo!() @nate
-                    .child(IconElement::new(Icon::Disconnected))
-                    .tooltip(|cx| Tooltip::text("Disconnected", cx))
-                    .into_any_element(),
-            ),
-            client::Status::UpgradeRequired => {
-                let auto_updater = auto_update::AutoUpdater::get(cx);
-                let label = match auto_updater.map(|auto_update| auto_update.read(cx).status()) {
-                    Some(AutoUpdateStatus::Updated) => "Please restart Zed to Collaborate",
-                    Some(AutoUpdateStatus::Installing)
-                    | Some(AutoUpdateStatus::Downloading)
-                    | Some(AutoUpdateStatus::Checking) => "Updating...",
-                    Some(AutoUpdateStatus::Idle) | Some(AutoUpdateStatus::Errored) | None => {
-                        "Please update Zed to Collaborate"
-                    }
-                };
-
-                Some(
-                    div()
-                        .bg(gpui::red()) // todo!() @nate
-                        .child(Button::new("connection-status", label).on_click(|_, cx| {
-                            if let Some(auto_updater) = auto_update::AutoUpdater::get(cx) {
-                                if auto_updater.read(cx).status() == AutoUpdateStatus::Updated {
-                                    workspace::restart(&Default::default(), cx);
-                                    return;
-                                }
-                            }
-                            auto_update::check(&Default::default(), cx);
-                        }))
-                        .into_any_element(),
-                )
-            }
-            _ => None,
-        }
-    }
-
-    pub fn render_sign_in_button(&mut self, _: &mut ViewContext<Self>) -> Button {
-        let client = self.client.clone();
-        Button::new("sign_in", "Sign in").on_click(move |_, cx| {
-            let client = client.clone();
-            cx.spawn(move |mut cx| async move {
-                client
-                    .authenticate_and_connect(true, &cx)
-                    .await
-                    .notify_async_err(&mut cx);
-            })
-            .detach();
-        })
-    }
-
-    pub fn render_user_menu_button(&mut self, cx: &mut ViewContext<Self>) -> impl Element {
-        if let Some(user) = self.user_store.read(cx).current_user() {
-            popover_menu("user-menu")
-                .menu(|cx| {
-                    ContextMenu::build(cx, |menu, _| {
-                        menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
-                            .action("Theme", theme_selector::Toggle.boxed_clone())
-                            .separator()
-                            .action("Share Feedback", feedback::GiveFeedback.boxed_clone())
-                            .action("Sign Out", client::SignOut.boxed_clone())
-                    })
-                    .into()
-                })
-                .trigger(
-                    ButtonLike::new("user-menu")
-                        .child(
-                            h_stack()
-                                .gap_0p5()
-                                .child(Avatar::new(user.avatar_uri.clone()))
-                                .child(IconElement::new(Icon::ChevronDown).color(Color::Muted)),
-                        )
-                        .style(ButtonStyle::Subtle)
-                        .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)),
-                )
-                .anchor(gpui::AnchorCorner::TopRight)
-        } else {
-            popover_menu("user-menu")
-                .menu(|cx| {
-                    ContextMenu::build(cx, |menu, _| {
-                        menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
-                            .action("Theme", theme_selector::Toggle.boxed_clone())
-                            .separator()
-                            .action("Share Feedback", feedback::GiveFeedback.boxed_clone())
-                    })
-                    .into()
-                })
-                .trigger(
-                    ButtonLike::new("user-menu")
-                        .child(
-                            h_stack()
-                                .gap_0p5()
-                                .child(IconElement::new(Icon::ChevronDown).color(Color::Muted)),
-                        )
-                        .style(ButtonStyle::Subtle)
-                        .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)),
-                )
-        }
-    }
-}

crates/collab_ui2/src/collab_ui.rs 🔗

@@ -1,167 +0,0 @@
-pub mod channel_view;
-pub mod chat_panel;
-pub mod collab_panel;
-mod collab_titlebar_item;
-mod face_pile;
-pub mod notification_panel;
-pub mod notifications;
-mod panel_settings;
-
-use std::{rc::Rc, sync::Arc};
-
-use call::{report_call_event_for_room, ActiveCall, Room};
-pub use collab_panel::CollabPanel;
-pub use collab_titlebar_item::CollabTitlebarItem;
-use feature_flags::{ChannelsAlpha, FeatureFlagAppExt};
-use gpui::{
-    actions, point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, Task, WindowBounds,
-    WindowKind, WindowOptions,
-};
-pub use panel_settings::{
-    ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings,
-};
-use settings::Settings;
-use util::ResultExt;
-use workspace::AppState;
-
-actions!(
-    collab,
-    [ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall]
-);
-
-pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
-    CollaborationPanelSettings::register(cx);
-    ChatPanelSettings::register(cx);
-    NotificationPanelSettings::register(cx);
-
-    vcs_menu::init(cx);
-    collab_titlebar_item::init(cx);
-    collab_panel::init(cx);
-    channel_view::init(cx);
-    chat_panel::init(cx);
-    notification_panel::init(cx);
-    notifications::init(&app_state, cx);
-
-    // cx.add_global_action(toggle_screen_sharing);
-    // cx.add_global_action(toggle_mute);
-    // cx.add_global_action(toggle_deafen);
-}
-
-pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
-    let call = ActiveCall::global(cx).read(cx);
-    if let Some(room) = call.room().cloned() {
-        let client = call.client();
-        let toggle_screen_sharing = room.update(cx, |room, cx| {
-            if room.is_screen_sharing() {
-                report_call_event_for_room(
-                    "disable screen share",
-                    room.id(),
-                    room.channel_id(),
-                    &client,
-                    cx,
-                );
-                Task::ready(room.unshare_screen(cx))
-            } else {
-                report_call_event_for_room(
-                    "enable screen share",
-                    room.id(),
-                    room.channel_id(),
-                    &client,
-                    cx,
-                );
-                room.share_screen(cx)
-            }
-        });
-        toggle_screen_sharing.detach_and_log_err(cx);
-    }
-}
-
-pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) {
-    let call = ActiveCall::global(cx).read(cx);
-    if let Some(room) = call.room().cloned() {
-        let client = call.client();
-        room.update(cx, |room, cx| {
-            let operation = if room.is_muted(cx) {
-                "enable microphone"
-            } else {
-                "disable microphone"
-            };
-            report_call_event_for_room(operation, room.id(), room.channel_id(), &client, cx);
-
-            room.toggle_mute(cx)
-        })
-        .map(|task| task.detach_and_log_err(cx))
-        .log_err();
-    }
-}
-
-pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) {
-    if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
-        room.update(cx, Room::toggle_deafen)
-            .map(|task| task.detach_and_log_err(cx))
-            .log_err();
-    }
-}
-
-fn notification_window_options(
-    screen: Rc<dyn PlatformDisplay>,
-    window_size: Size<Pixels>,
-) -> WindowOptions {
-    let notification_margin_width = GlobalPixels::from(16.);
-    let notification_margin_height = GlobalPixels::from(-0.) - GlobalPixels::from(48.);
-
-    let screen_bounds = screen.bounds();
-    let size: Size<GlobalPixels> = window_size.into();
-
-    // todo!() use content bounds instead of screen.bounds and get rid of magics in point's 2nd argument.
-    let bounds = gpui::Bounds::<GlobalPixels> {
-        origin: screen_bounds.upper_right()
-            - point(
-                size.width + notification_margin_width,
-                notification_margin_height,
-            ),
-        size: window_size.into(),
-    };
-    WindowOptions {
-        bounds: WindowBounds::Fixed(bounds),
-        titlebar: None,
-        center: false,
-        focus: false,
-        show: true,
-        kind: WindowKind::PopUp,
-        is_movable: false,
-        display_id: Some(screen.id()),
-    }
-}
-
-// fn render_avatar<T: 'static>(
-//     avatar: Option<Arc<ImageData>>,
-//     avatar_style: &AvatarStyle,
-//     container: ContainerStyle,
-// ) -> AnyElement<T> {
-//     avatar
-//         .map(|avatar| {
-//             Image::from_data(avatar)
-//                 .with_style(avatar_style.image)
-//                 .aligned()
-//                 .contained()
-//                 .with_corner_radius(avatar_style.outer_corner_radius)
-//                 .constrained()
-//                 .with_width(avatar_style.outer_width)
-//                 .with_height(avatar_style.outer_width)
-//                 .into_any()
-//         })
-//         .unwrap_or_else(|| {
-//             Empty::new()
-//                 .constrained()
-//                 .with_width(avatar_style.outer_width)
-//                 .into_any()
-//         })
-//         .contained()
-//         .with_style(container)
-//         .into_any()
-// }
-
-fn is_channels_feature_enabled(cx: &gpui::WindowContext<'_>) -> bool {
-    cx.is_staff() || cx.has_flag::<ChannelsAlpha>()
-}

crates/collab_ui2/src/face_pile.rs 🔗

@@ -1,30 +0,0 @@
-use gpui::{
-    div, AnyElement, ElementId, IntoElement, ParentElement, RenderOnce, Styled, WindowContext,
-};
-use smallvec::SmallVec;
-
-#[derive(Default, IntoElement)]
-pub struct FacePile {
-    pub faces: SmallVec<[AnyElement; 2]>,
-}
-
-impl RenderOnce for FacePile {
-    fn render(self, _: &mut WindowContext) -> impl IntoElement {
-        let player_count = self.faces.len();
-        let player_list = self.faces.into_iter().enumerate().map(|(ix, player)| {
-            let isnt_last = ix < player_count - 1;
-
-            div()
-                .z_index((player_count - ix) as u8)
-                .when(isnt_last, |div| div.neg_mr_1())
-                .child(player)
-        });
-        div().p_1().flex().items_center().children(player_list)
-    }
-}
-
-impl ParentElement for FacePile {
-    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
-        &mut self.faces
-    }
-}

crates/collab_ui2/src/notification_panel.rs 🔗

@@ -1,755 +0,0 @@
-use crate::{chat_panel::ChatPanel, NotificationPanelSettings};
-use anyhow::Result;
-use channel::ChannelStore;
-use client::{Client, Notification, User, UserStore};
-use collections::HashMap;
-use db::kvp::KEY_VALUE_STORE;
-use futures::StreamExt;
-use gpui::{
-    actions, div, img, list, px, serde_json, AnyElement, AppContext, AsyncWindowContext,
-    CursorStyle, DismissEvent, Element, EventEmitter, FocusHandle, FocusableView,
-    InteractiveElement, IntoElement, ListAlignment, ListScrollEvent, ListState, Model,
-    ParentElement, Render, StatefulInteractiveElement, Styled, Task, View, ViewContext,
-    VisualContext, WeakView, WindowContext,
-};
-use notifications::{NotificationEntry, NotificationEvent, NotificationStore};
-use project::Fs;
-use rpc::proto;
-use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsStore};
-use std::{sync::Arc, time::Duration};
-use time::{OffsetDateTime, UtcOffset};
-use ui::{h_stack, prelude::*, v_stack, Avatar, Button, Icon, IconButton, IconElement, Label};
-use util::{ResultExt, TryFutureExt};
-use workspace::{
-    dock::{DockPosition, Panel, PanelEvent},
-    Workspace,
-};
-
-const LOADING_THRESHOLD: usize = 30;
-const MARK_AS_READ_DELAY: Duration = Duration::from_secs(1);
-const TOAST_DURATION: Duration = Duration::from_secs(5);
-const NOTIFICATION_PANEL_KEY: &'static str = "NotificationPanel";
-
-pub struct NotificationPanel {
-    client: Arc<Client>,
-    user_store: Model<UserStore>,
-    channel_store: Model<ChannelStore>,
-    notification_store: Model<NotificationStore>,
-    fs: Arc<dyn Fs>,
-    width: Option<Pixels>,
-    active: bool,
-    notification_list: ListState,
-    pending_serialization: Task<Option<()>>,
-    subscriptions: Vec<gpui::Subscription>,
-    workspace: WeakView<Workspace>,
-    current_notification_toast: Option<(u64, Task<()>)>,
-    local_timezone: UtcOffset,
-    focus_handle: FocusHandle,
-    mark_as_read_tasks: HashMap<u64, Task<Result<()>>>,
-}
-
-#[derive(Serialize, Deserialize)]
-struct SerializedNotificationPanel {
-    width: Option<Pixels>,
-}
-
-#[derive(Debug)]
-pub enum Event {
-    DockPositionChanged,
-    Focus,
-    Dismissed,
-}
-
-pub struct NotificationPresenter {
-    pub actor: Option<Arc<client::User>>,
-    pub text: String,
-    pub icon: &'static str,
-    pub needs_response: bool,
-    pub can_navigate: bool,
-}
-
-actions!(notification_panel, [ToggleFocus]);
-
-pub fn init(cx: &mut AppContext) {
-    cx.observe_new_views(|workspace: &mut Workspace, _| {
-        workspace.register_action(|workspace, _: &ToggleFocus, cx| {
-            workspace.toggle_panel_focus::<NotificationPanel>(cx);
-        });
-    })
-    .detach();
-}
-
-impl NotificationPanel {
-    pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
-        let fs = workspace.app_state().fs.clone();
-        let client = workspace.app_state().client.clone();
-        let user_store = workspace.app_state().user_store.clone();
-        let workspace_handle = workspace.weak_handle();
-
-        cx.new_view(|cx: &mut ViewContext<Self>| {
-            let mut status = client.status();
-            cx.spawn(|this, mut cx| async move {
-                while let Some(_) = status.next().await {
-                    if this
-                        .update(&mut cx, |_, cx| {
-                            cx.notify();
-                        })
-                        .is_err()
-                    {
-                        break;
-                    }
-                }
-            })
-            .detach();
-
-            let view = cx.view().downgrade();
-            let notification_list =
-                ListState::new(0, ListAlignment::Top, px(1000.), move |ix, cx| {
-                    view.upgrade()
-                        .and_then(|view| {
-                            view.update(cx, |this, cx| this.render_notification(ix, cx))
-                        })
-                        .unwrap_or_else(|| div().into_any())
-                });
-            notification_list.set_scroll_handler(cx.listener(
-                |this, event: &ListScrollEvent, cx| {
-                    if event.count.saturating_sub(event.visible_range.end) < LOADING_THRESHOLD {
-                        if let Some(task) = this
-                            .notification_store
-                            .update(cx, |store, cx| store.load_more_notifications(false, cx))
-                        {
-                            task.detach();
-                        }
-                    }
-                },
-            ));
-
-            let mut this = Self {
-                fs,
-                client,
-                user_store,
-                local_timezone: cx.local_timezone(),
-                channel_store: ChannelStore::global(cx),
-                notification_store: NotificationStore::global(cx),
-                notification_list,
-                pending_serialization: Task::ready(None),
-                workspace: workspace_handle,
-                focus_handle: cx.focus_handle(),
-                current_notification_toast: None,
-                subscriptions: Vec::new(),
-                active: false,
-                mark_as_read_tasks: HashMap::default(),
-                width: None,
-            };
-
-            let mut old_dock_position = this.position(cx);
-            this.subscriptions.extend([
-                cx.observe(&this.notification_store, |_, _, cx| cx.notify()),
-                cx.subscribe(&this.notification_store, Self::on_notification_event),
-                cx.observe_global::<SettingsStore>(move |this: &mut Self, cx| {
-                    let new_dock_position = this.position(cx);
-                    if new_dock_position != old_dock_position {
-                        old_dock_position = new_dock_position;
-                        cx.emit(Event::DockPositionChanged);
-                    }
-                    cx.notify();
-                }),
-            ]);
-            this
-        })
-    }
-
-    pub fn load(
-        workspace: WeakView<Workspace>,
-        cx: AsyncWindowContext,
-    ) -> Task<Result<View<Self>>> {
-        cx.spawn(|mut cx| async move {
-            let serialized_panel = if let Some(panel) = cx
-                .background_executor()
-                .spawn(async move { KEY_VALUE_STORE.read_kvp(NOTIFICATION_PANEL_KEY) })
-                .await
-                .log_err()
-                .flatten()
-            {
-                Some(serde_json::from_str::<SerializedNotificationPanel>(&panel)?)
-            } else {
-                None
-            };
-
-            workspace.update(&mut cx, |workspace, cx| {
-                let panel = Self::new(workspace, cx);
-                if let Some(serialized_panel) = serialized_panel {
-                    panel.update(cx, |panel, cx| {
-                        panel.width = serialized_panel.width;
-                        cx.notify();
-                    });
-                }
-                panel
-            })
-        })
-    }
-
-    fn serialize(&mut self, cx: &mut ViewContext<Self>) {
-        let width = self.width;
-        self.pending_serialization = cx.background_executor().spawn(
-            async move {
-                KEY_VALUE_STORE
-                    .write_kvp(
-                        NOTIFICATION_PANEL_KEY.into(),
-                        serde_json::to_string(&SerializedNotificationPanel { width })?,
-                    )
-                    .await?;
-                anyhow::Ok(())
-            }
-            .log_err(),
-        );
-    }
-
-    fn render_notification(&mut self, ix: usize, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
-        let entry = self.notification_store.read(cx).notification_at(ix)?;
-        let notification_id = entry.id;
-        let now = OffsetDateTime::now_utc();
-        let timestamp = entry.timestamp;
-        let NotificationPresenter {
-            actor,
-            text,
-            needs_response,
-            can_navigate,
-            ..
-        } = self.present_notification(entry, cx)?;
-
-        let response = entry.response;
-        let notification = entry.notification.clone();
-
-        if self.active && !entry.is_read {
-            self.did_render_notification(notification_id, &notification, cx);
-        }
-
-        Some(
-            div()
-                .id(ix)
-                .flex()
-                .flex_row()
-                .size_full()
-                .px_2()
-                .py_1()
-                .gap_2()
-                .when(can_navigate, |el| {
-                    el.cursor(CursorStyle::PointingHand).on_click({
-                        let notification = notification.clone();
-                        cx.listener(move |this, _, cx| {
-                            this.did_click_notification(&notification, cx)
-                        })
-                    })
-                })
-                .children(actor.map(|actor| {
-                    img(actor.avatar_uri.clone())
-                        .flex_none()
-                        .w_8()
-                        .h_8()
-                        .rounded_full()
-                }))
-                .child(
-                    v_stack()
-                        .gap_1()
-                        .size_full()
-                        .overflow_hidden()
-                        .child(Label::new(text.clone()))
-                        .child(
-                            h_stack()
-                                .child(
-                                    Label::new(format_timestamp(
-                                        timestamp,
-                                        now,
-                                        self.local_timezone,
-                                    ))
-                                    .color(Color::Muted),
-                                )
-                                .children(if let Some(is_accepted) = response {
-                                    Some(div().flex().flex_grow().justify_end().child(Label::new(
-                                        if is_accepted {
-                                            "You accepted"
-                                        } else {
-                                            "You declined"
-                                        },
-                                    )))
-                                } else if needs_response {
-                                    Some(
-                                        h_stack()
-                                            .flex_grow()
-                                            .justify_end()
-                                            .child(Button::new("decline", "Decline").on_click({
-                                                let notification = notification.clone();
-                                                let view = cx.view().clone();
-                                                move |_, cx| {
-                                                    view.update(cx, |this, cx| {
-                                                        this.respond_to_notification(
-                                                            notification.clone(),
-                                                            false,
-                                                            cx,
-                                                        )
-                                                    });
-                                                }
-                                            }))
-                                            .child(Button::new("accept", "Accept").on_click({
-                                                let notification = notification.clone();
-                                                let view = cx.view().clone();
-                                                move |_, cx| {
-                                                    view.update(cx, |this, cx| {
-                                                        this.respond_to_notification(
-                                                            notification.clone(),
-                                                            true,
-                                                            cx,
-                                                        )
-                                                    });
-                                                }
-                                            })),
-                                    )
-                                } else {
-                                    None
-                                }),
-                        ),
-                )
-                .into_any(),
-        )
-    }
-
-    fn present_notification(
-        &self,
-        entry: &NotificationEntry,
-        cx: &AppContext,
-    ) -> Option<NotificationPresenter> {
-        let user_store = self.user_store.read(cx);
-        let channel_store = self.channel_store.read(cx);
-        match entry.notification {
-            Notification::ContactRequest { sender_id } => {
-                let requester = user_store.get_cached_user(sender_id)?;
-                Some(NotificationPresenter {
-                    icon: "icons/plus.svg",
-                    text: format!("{} wants to add you as a contact", requester.github_login),
-                    needs_response: user_store.has_incoming_contact_request(requester.id),
-                    actor: Some(requester),
-                    can_navigate: false,
-                })
-            }
-            Notification::ContactRequestAccepted { responder_id } => {
-                let responder = user_store.get_cached_user(responder_id)?;
-                Some(NotificationPresenter {
-                    icon: "icons/plus.svg",
-                    text: format!("{} accepted your contact invite", responder.github_login),
-                    needs_response: false,
-                    actor: Some(responder),
-                    can_navigate: false,
-                })
-            }
-            Notification::ChannelInvitation {
-                ref channel_name,
-                channel_id,
-                inviter_id,
-            } => {
-                let inviter = user_store.get_cached_user(inviter_id)?;
-                Some(NotificationPresenter {
-                    icon: "icons/hash.svg",
-                    text: format!(
-                        "{} invited you to join the #{channel_name} channel",
-                        inviter.github_login
-                    ),
-                    needs_response: channel_store.has_channel_invitation(channel_id),
-                    actor: Some(inviter),
-                    can_navigate: false,
-                })
-            }
-            Notification::ChannelMessageMention {
-                sender_id,
-                channel_id,
-                message_id,
-            } => {
-                let sender = user_store.get_cached_user(sender_id)?;
-                let channel = channel_store.channel_for_id(channel_id)?;
-                let message = self
-                    .notification_store
-                    .read(cx)
-                    .channel_message_for_id(message_id)?;
-                Some(NotificationPresenter {
-                    icon: "icons/conversations.svg",
-                    text: format!(
-                        "{} mentioned you in #{}:\n{}",
-                        sender.github_login, channel.name, message.body,
-                    ),
-                    needs_response: false,
-                    actor: Some(sender),
-                    can_navigate: true,
-                })
-            }
-        }
-    }
-
-    fn did_render_notification(
-        &mut self,
-        notification_id: u64,
-        notification: &Notification,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let should_mark_as_read = match notification {
-            Notification::ContactRequestAccepted { .. } => true,
-            Notification::ContactRequest { .. }
-            | Notification::ChannelInvitation { .. }
-            | Notification::ChannelMessageMention { .. } => false,
-        };
-
-        if should_mark_as_read {
-            self.mark_as_read_tasks
-                .entry(notification_id)
-                .or_insert_with(|| {
-                    let client = self.client.clone();
-                    cx.spawn(|this, mut cx| async move {
-                        cx.background_executor().timer(MARK_AS_READ_DELAY).await;
-                        client
-                            .request(proto::MarkNotificationRead { notification_id })
-                            .await?;
-                        this.update(&mut cx, |this, _| {
-                            this.mark_as_read_tasks.remove(&notification_id);
-                        })?;
-                        Ok(())
-                    })
-                });
-        }
-    }
-
-    fn did_click_notification(&mut self, notification: &Notification, cx: &mut ViewContext<Self>) {
-        if let Notification::ChannelMessageMention {
-            message_id,
-            channel_id,
-            ..
-        } = notification.clone()
-        {
-            if let Some(workspace) = self.workspace.upgrade() {
-                cx.window_context().defer(move |cx| {
-                    workspace.update(cx, |workspace, cx| {
-                        if let Some(panel) = workspace.focus_panel::<ChatPanel>(cx) {
-                            panel.update(cx, |panel, cx| {
-                                panel
-                                    .select_channel(channel_id, Some(message_id), cx)
-                                    .detach_and_log_err(cx);
-                            });
-                        }
-                    });
-                });
-            }
-        }
-    }
-
-    fn is_showing_notification(&self, notification: &Notification, cx: &ViewContext<Self>) -> bool {
-        if let Notification::ChannelMessageMention { channel_id, .. } = &notification {
-            if let Some(workspace) = self.workspace.upgrade() {
-                return if let Some(panel) = workspace.read(cx).panel::<ChatPanel>(cx) {
-                    let panel = panel.read(cx);
-                    panel.is_scrolled_to_bottom()
-                        && panel
-                            .active_chat()
-                            .map_or(false, |chat| chat.read(cx).channel_id == *channel_id)
-                } else {
-                    false
-                };
-            }
-        }
-
-        false
-    }
-
-    fn on_notification_event(
-        &mut self,
-        _: Model<NotificationStore>,
-        event: &NotificationEvent,
-        cx: &mut ViewContext<Self>,
-    ) {
-        match event {
-            NotificationEvent::NewNotification { entry } => self.add_toast(entry, cx),
-            NotificationEvent::NotificationRemoved { entry }
-            | NotificationEvent::NotificationRead { entry } => self.remove_toast(entry.id, cx),
-            NotificationEvent::NotificationsUpdated {
-                old_range,
-                new_count,
-            } => {
-                self.notification_list.splice(old_range.clone(), *new_count);
-                cx.notify();
-            }
-        }
-    }
-
-    fn add_toast(&mut self, entry: &NotificationEntry, cx: &mut ViewContext<Self>) {
-        if self.is_showing_notification(&entry.notification, cx) {
-            return;
-        }
-
-        let Some(NotificationPresenter { actor, text, .. }) = self.present_notification(entry, cx)
-        else {
-            return;
-        };
-
-        let notification_id = entry.id;
-        self.current_notification_toast = Some((
-            notification_id,
-            cx.spawn(|this, mut cx| async move {
-                cx.background_executor().timer(TOAST_DURATION).await;
-                this.update(&mut cx, |this, cx| this.remove_toast(notification_id, cx))
-                    .ok();
-            }),
-        ));
-
-        self.workspace
-            .update(cx, |workspace, cx| {
-                workspace.dismiss_notification::<NotificationToast>(0, cx);
-                workspace.show_notification(0, cx, |cx| {
-                    let workspace = cx.view().downgrade();
-                    cx.new_view(|_| NotificationToast {
-                        notification_id,
-                        actor,
-                        text,
-                        workspace,
-                    })
-                })
-            })
-            .ok();
-    }
-
-    fn remove_toast(&mut self, notification_id: u64, cx: &mut ViewContext<Self>) {
-        if let Some((current_id, _)) = &self.current_notification_toast {
-            if *current_id == notification_id {
-                self.current_notification_toast.take();
-                self.workspace
-                    .update(cx, |workspace, cx| {
-                        workspace.dismiss_notification::<NotificationToast>(0, cx)
-                    })
-                    .ok();
-            }
-        }
-    }
-
-    fn respond_to_notification(
-        &mut self,
-        notification: Notification,
-        response: bool,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.notification_store.update(cx, |store, cx| {
-            store.respond_to_notification(notification, response, cx);
-        });
-    }
-}
-
-impl Render for NotificationPanel {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        v_stack()
-            .size_full()
-            .child(
-                h_stack()
-                    .justify_between()
-                    .px_2()
-                    .py_1()
-                    // Match the height of the tab bar so they line up.
-                    .h(rems(ui::Tab::HEIGHT_IN_REMS))
-                    .border_b_1()
-                    .border_color(cx.theme().colors().border)
-                    .child(Label::new("Notifications"))
-                    .child(IconElement::new(Icon::Envelope)),
-            )
-            .map(|this| {
-                if self.client.user_id().is_none() {
-                    this.child(
-                        v_stack()
-                            .gap_2()
-                            .p_4()
-                            .child(
-                                Button::new("sign_in_prompt_button", "Sign in")
-                                    .icon_color(Color::Muted)
-                                    .icon(Icon::Github)
-                                    .icon_position(IconPosition::Start)
-                                    .style(ButtonStyle::Filled)
-                                    .full_width()
-                                    .on_click({
-                                        let client = self.client.clone();
-                                        move |_, cx| {
-                                            let client = client.clone();
-                                            cx.spawn(move |cx| async move {
-                                                client
-                                                    .authenticate_and_connect(true, &cx)
-                                                    .log_err()
-                                                    .await;
-                                            })
-                                            .detach()
-                                        }
-                                    }),
-                            )
-                            .child(
-                                div().flex().w_full().items_center().child(
-                                    Label::new("Sign in to view notifications.")
-                                        .color(Color::Muted)
-                                        .size(LabelSize::Small),
-                                ),
-                            ),
-                    )
-                } else if self.notification_list.item_count() == 0 {
-                    this.child(
-                        v_stack().p_4().child(
-                            div().flex().w_full().items_center().child(
-                                Label::new("You have no notifications.")
-                                    .color(Color::Muted)
-                                    .size(LabelSize::Small),
-                            ),
-                        ),
-                    )
-                } else {
-                    this.child(list(self.notification_list.clone()).size_full())
-                }
-            })
-    }
-}
-
-impl FocusableView for NotificationPanel {
-    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
-        self.focus_handle.clone()
-    }
-}
-
-impl EventEmitter<Event> for NotificationPanel {}
-impl EventEmitter<PanelEvent> for NotificationPanel {}
-
-impl Panel for NotificationPanel {
-    fn persistent_name() -> &'static str {
-        "NotificationPanel"
-    }
-
-    fn position(&self, cx: &gpui::WindowContext) -> DockPosition {
-        NotificationPanelSettings::get_global(cx).dock
-    }
-
-    fn position_is_valid(&self, position: DockPosition) -> bool {
-        matches!(position, DockPosition::Left | DockPosition::Right)
-    }
-
-    fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
-        settings::update_settings_file::<NotificationPanelSettings>(
-            self.fs.clone(),
-            cx,
-            move |settings| settings.dock = Some(position),
-        );
-    }
-
-    fn size(&self, cx: &gpui::WindowContext) -> Pixels {
-        self.width
-            .unwrap_or_else(|| NotificationPanelSettings::get_global(cx).default_width)
-    }
-
-    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
-        self.width = size;
-        self.serialize(cx);
-        cx.notify();
-    }
-
-    fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
-        self.active = active;
-        if self.notification_store.read(cx).notification_count() == 0 {
-            cx.emit(Event::Dismissed);
-        }
-    }
-
-    fn icon(&self, cx: &gpui::WindowContext) -> Option<Icon> {
-        (NotificationPanelSettings::get_global(cx).button
-            && self.notification_store.read(cx).notification_count() > 0)
-            .then(|| Icon::Bell)
-    }
-
-    fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
-        Some("Notification Panel")
-    }
-
-    fn icon_label(&self, cx: &WindowContext) -> Option<String> {
-        let count = self.notification_store.read(cx).unread_notification_count();
-        if count == 0 {
-            None
-        } else {
-            Some(count.to_string())
-        }
-    }
-
-    fn toggle_action(&self) -> Box<dyn gpui::Action> {
-        Box::new(ToggleFocus)
-    }
-}
-
-pub struct NotificationToast {
-    notification_id: u64,
-    actor: Option<Arc<User>>,
-    text: String,
-    workspace: WeakView<Workspace>,
-}
-
-impl NotificationToast {
-    fn focus_notification_panel(&self, cx: &mut ViewContext<Self>) {
-        let workspace = self.workspace.clone();
-        let notification_id = self.notification_id;
-        cx.window_context().defer(move |cx| {
-            workspace
-                .update(cx, |workspace, cx| {
-                    if let Some(panel) = workspace.focus_panel::<NotificationPanel>(cx) {
-                        panel.update(cx, |panel, cx| {
-                            let store = panel.notification_store.read(cx);
-                            if let Some(entry) = store.notification_for_id(notification_id) {
-                                panel.did_click_notification(&entry.clone().notification, cx);
-                            }
-                        });
-                    }
-                })
-                .ok();
-        })
-    }
-}
-
-impl Render for NotificationToast {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        let user = self.actor.clone();
-
-        h_stack()
-            .id("notification_panel_toast")
-            .children(user.map(|user| Avatar::new(user.avatar_uri.clone())))
-            .child(Label::new(self.text.clone()))
-            .child(
-                IconButton::new("close", Icon::Close)
-                    .on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))),
-            )
-            .on_click(cx.listener(|this, _, cx| {
-                this.focus_notification_panel(cx);
-                cx.emit(DismissEvent);
-            }))
-    }
-}
-
-impl EventEmitter<DismissEvent> for NotificationToast {}
-
-fn format_timestamp(
-    mut timestamp: OffsetDateTime,
-    mut now: OffsetDateTime,
-    local_timezone: UtcOffset,
-) -> String {
-    timestamp = timestamp.to_offset(local_timezone);
-    now = now.to_offset(local_timezone);
-
-    let today = now.date();
-    let date = timestamp.date();
-    if date == today {
-        let difference = now - timestamp;
-        if difference >= Duration::from_secs(3600) {
-            format!("{}h", difference.whole_seconds() / 3600)
-        } else if difference >= Duration::from_secs(60) {
-            format!("{}m", difference.whole_seconds() / 60)
-        } else {
-            "just now".to_string()
-        }
-    } else if date.next_day() == Some(today) {
-        format!("yesterday")
-    } else {
-        format!("{:02}/{}/{}", date.month() as u32, date.day(), date.year())
-    }
-}

crates/collab_ui2/src/notifications.rs 🔗

@@ -1,11 +0,0 @@
-use gpui::AppContext;
-use std::sync::Arc;
-use workspace::AppState;
-
-pub mod incoming_call_notification;
-pub mod project_shared_notification;
-
-pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
-    incoming_call_notification::init(app_state, cx);
-    project_shared_notification::init(app_state, cx);
-}

crates/collab_ui2/src/notifications/incoming_call_notification.rs 🔗

@@ -1,163 +0,0 @@
-use crate::notification_window_options;
-use call::{ActiveCall, IncomingCall};
-use futures::StreamExt;
-use gpui::{
-    img, px, AppContext, ParentElement, Render, RenderOnce, Styled, ViewContext,
-    VisualContext as _, WindowHandle,
-};
-use settings::Settings;
-use std::sync::{Arc, Weak};
-use theme::ThemeSettings;
-use ui::prelude::*;
-use ui::{h_stack, v_stack, Button, Label};
-use util::ResultExt;
-use workspace::AppState;
-
-pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
-    let app_state = Arc::downgrade(app_state);
-    let mut incoming_call = ActiveCall::global(cx).read(cx).incoming();
-    cx.spawn(|mut cx| async move {
-        let mut notification_windows: Vec<WindowHandle<IncomingCallNotification>> = Vec::new();
-        while let Some(incoming_call) = incoming_call.next().await {
-            for window in notification_windows.drain(..) {
-                window
-                    .update(&mut cx, |_, cx| {
-                        // todo!()
-                        cx.remove_window();
-                    })
-                    .log_err();
-            }
-
-            if let Some(incoming_call) = incoming_call {
-                let unique_screens = cx.update(|cx| cx.displays()).unwrap();
-                let window_size = gpui::Size {
-                    width: px(380.),
-                    height: px(64.),
-                };
-
-                for screen in unique_screens {
-                    let options = notification_window_options(screen, window_size);
-                    let window = cx
-                        .open_window(options, |cx| {
-                            cx.new_view(|_| {
-                                IncomingCallNotification::new(
-                                    incoming_call.clone(),
-                                    app_state.clone(),
-                                )
-                            })
-                        })
-                        .unwrap();
-                    notification_windows.push(window);
-                }
-            }
-        }
-    })
-    .detach();
-}
-
-#[derive(Clone, PartialEq)]
-struct RespondToCall {
-    accept: bool,
-}
-
-struct IncomingCallNotificationState {
-    call: IncomingCall,
-    app_state: Weak<AppState>,
-}
-
-pub struct IncomingCallNotification {
-    state: Arc<IncomingCallNotificationState>,
-}
-impl IncomingCallNotificationState {
-    pub fn new(call: IncomingCall, app_state: Weak<AppState>) -> Self {
-        Self { call, app_state }
-    }
-
-    fn respond(&self, accept: bool, cx: &mut AppContext) {
-        let active_call = ActiveCall::global(cx);
-        if accept {
-            let join = active_call.update(cx, |active_call, cx| active_call.accept_incoming(cx));
-            let caller_user_id = self.call.calling_user.id;
-            let initial_project_id = self.call.initial_project.as_ref().map(|project| project.id);
-            let app_state = self.app_state.clone();
-            let cx: &mut AppContext = cx;
-            cx.spawn(|cx| async move {
-                join.await?;
-                if let Some(project_id) = initial_project_id {
-                    cx.update(|cx| {
-                        if let Some(app_state) = app_state.upgrade() {
-                            workspace::join_remote_project(
-                                project_id,
-                                caller_user_id,
-                                app_state,
-                                cx,
-                            )
-                            .detach_and_log_err(cx);
-                        }
-                    })
-                    .log_err();
-                }
-                anyhow::Ok(())
-            })
-            .detach_and_log_err(cx);
-        } else {
-            active_call.update(cx, |active_call, cx| {
-                active_call.decline_incoming(cx).log_err();
-            });
-        }
-    }
-}
-
-impl IncomingCallNotification {
-    pub fn new(call: IncomingCall, app_state: Weak<AppState>) -> Self {
-        Self {
-            state: Arc::new(IncomingCallNotificationState::new(call, app_state)),
-        }
-    }
-}
-
-impl Render for IncomingCallNotification {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        // TODO: Is there a better place for us to initialize the font?
-        let (ui_font, ui_font_size) = {
-            let theme_settings = ThemeSettings::get_global(cx);
-            (
-                theme_settings.ui_font.family.clone(),
-                theme_settings.ui_font_size.clone(),
-            )
-        };
-
-        cx.set_rem_size(ui_font_size);
-
-        h_stack()
-            .font(ui_font)
-            .text_ui()
-            .justify_between()
-            .size_full()
-            .overflow_hidden()
-            .elevation_3(cx)
-            .p_2()
-            .gap_2()
-            .child(
-                img(self.state.call.calling_user.avatar_uri.clone())
-                    .w_12()
-                    .h_12()
-                    .rounded_full(),
-            )
-            .child(v_stack().overflow_hidden().child(Label::new(format!(
-                "{} is sharing a project in Zed",
-                self.state.call.calling_user.github_login
-            ))))
-            .child(
-                v_stack()
-                    .child(Button::new("accept", "Accept").render(cx).on_click({
-                        let state = self.state.clone();
-                        move |_, cx| state.respond(true, cx)
-                    }))
-                    .child(Button::new("decline", "Decline").render(cx).on_click({
-                        let state = self.state.clone();
-                        move |_, cx| state.respond(false, cx)
-                    })),
-            )
-    }
-}

crates/collab_ui2/src/notifications/project_shared_notification.rs 🔗

@@ -1,180 +0,0 @@
-use crate::notification_window_options;
-use call::{room, ActiveCall};
-use client::User;
-use collections::HashMap;
-use gpui::{img, px, AppContext, ParentElement, Render, Size, Styled, ViewContext, VisualContext};
-use settings::Settings;
-use std::sync::{Arc, Weak};
-use theme::ThemeSettings;
-use ui::{h_stack, prelude::*, v_stack, Button, Label};
-use workspace::AppState;
-
-pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
-    let app_state = Arc::downgrade(app_state);
-    let active_call = ActiveCall::global(cx);
-    let mut notification_windows = HashMap::default();
-    cx.subscribe(&active_call, move |_, event, cx| match event {
-        room::Event::RemoteProjectShared {
-            owner,
-            project_id,
-            worktree_root_names,
-        } => {
-            let window_size = Size {
-                width: px(400.),
-                height: px(72.),
-            };
-
-            for screen in cx.displays() {
-                let options = notification_window_options(screen, window_size);
-                let window = cx.open_window(options, |cx| {
-                    cx.new_view(|_| {
-                        ProjectSharedNotification::new(
-                            owner.clone(),
-                            *project_id,
-                            worktree_root_names.clone(),
-                            app_state.clone(),
-                        )
-                    })
-                });
-                notification_windows
-                    .entry(*project_id)
-                    .or_insert(Vec::new())
-                    .push(window);
-            }
-        }
-
-        room::Event::RemoteProjectUnshared { project_id }
-        | room::Event::RemoteProjectJoined { project_id }
-        | room::Event::RemoteProjectInvitationDiscarded { project_id } => {
-            if let Some(windows) = notification_windows.remove(&project_id) {
-                for window in windows {
-                    window
-                        .update(cx, |_, cx| {
-                            // todo!()
-                            cx.remove_window();
-                        })
-                        .ok();
-                }
-            }
-        }
-
-        room::Event::Left => {
-            for (_, windows) in notification_windows.drain() {
-                for window in windows {
-                    window
-                        .update(cx, |_, cx| {
-                            // todo!()
-                            cx.remove_window();
-                        })
-                        .ok();
-                }
-            }
-        }
-        _ => {}
-    })
-    .detach();
-}
-
-pub struct ProjectSharedNotification {
-    project_id: u64,
-    worktree_root_names: Vec<String>,
-    owner: Arc<User>,
-    app_state: Weak<AppState>,
-}
-
-impl ProjectSharedNotification {
-    fn new(
-        owner: Arc<User>,
-        project_id: u64,
-        worktree_root_names: Vec<String>,
-        app_state: Weak<AppState>,
-    ) -> Self {
-        Self {
-            project_id,
-            worktree_root_names,
-            owner,
-            app_state,
-        }
-    }
-
-    fn join(&mut self, cx: &mut ViewContext<Self>) {
-        if let Some(app_state) = self.app_state.upgrade() {
-            workspace::join_remote_project(self.project_id, self.owner.id, app_state, cx)
-                .detach_and_log_err(cx);
-        }
-    }
-
-    fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
-        if let Some(active_room) =
-            ActiveCall::global(cx).read_with(cx, |call, _| call.room().cloned())
-        {
-            active_room.update(cx, |_, cx| {
-                cx.emit(room::Event::RemoteProjectInvitationDiscarded {
-                    project_id: self.project_id,
-                });
-            });
-        }
-    }
-}
-
-impl Render for ProjectSharedNotification {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        // TODO: Is there a better place for us to initialize the font?
-        let (ui_font, ui_font_size) = {
-            let theme_settings = ThemeSettings::get_global(cx);
-            (
-                theme_settings.ui_font.family.clone(),
-                theme_settings.ui_font_size.clone(),
-            )
-        };
-
-        cx.set_rem_size(ui_font_size);
-
-        h_stack()
-            .font(ui_font)
-            .text_ui()
-            .justify_between()
-            .size_full()
-            .overflow_hidden()
-            .elevation_3(cx)
-            .p_2()
-            .gap_2()
-            .child(
-                img(self.owner.avatar_uri.clone())
-                    .w_12()
-                    .h_12()
-                    .rounded_full(),
-            )
-            .child(
-                v_stack()
-                    .overflow_hidden()
-                    .child(Label::new(self.owner.github_login.clone()))
-                    .child(Label::new(format!(
-                        "is sharing a project in Zed{}",
-                        if self.worktree_root_names.is_empty() {
-                            ""
-                        } else {
-                            ":"
-                        }
-                    )))
-                    .children(if self.worktree_root_names.is_empty() {
-                        None
-                    } else {
-                        Some(Label::new(self.worktree_root_names.join(", ")))
-                    }),
-            )
-            .child(
-                v_stack()
-                    .child(Button::new("open", "Open").on_click(cx.listener(
-                        move |this, _event, cx| {
-                            this.join(cx);
-                        },
-                    )))
-                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
-                        move |this, _event, cx| {
-                            this.dismiss(cx);
-                        },
-                    ))),
-            )
-    }
-}

crates/collab_ui2/src/panel_settings.rs 🔗

@@ -1,70 +0,0 @@
-use anyhow;
-use gpui::Pixels;
-use schemars::JsonSchema;
-use serde_derive::{Deserialize, Serialize};
-use settings::Settings;
-use workspace::dock::DockPosition;
-
-#[derive(Deserialize, Debug)]
-pub struct CollaborationPanelSettings {
-    pub button: bool,
-    pub dock: DockPosition,
-    pub default_width: Pixels,
-}
-
-#[derive(Deserialize, Debug)]
-pub struct ChatPanelSettings {
-    pub button: bool,
-    pub dock: DockPosition,
-    pub default_width: Pixels,
-}
-
-#[derive(Deserialize, Debug)]
-pub struct NotificationPanelSettings {
-    pub button: bool,
-    pub dock: DockPosition,
-    pub default_width: Pixels,
-}
-
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
-pub struct PanelSettingsContent {
-    pub button: Option<bool>,
-    pub dock: Option<DockPosition>,
-    pub default_width: Option<f32>,
-}
-
-impl Settings for CollaborationPanelSettings {
-    const KEY: Option<&'static str> = Some("collaboration_panel");
-    type FileContent = PanelSettingsContent;
-    fn load(
-        default_value: &Self::FileContent,
-        user_values: &[&Self::FileContent],
-        _: &mut gpui::AppContext,
-    ) -> anyhow::Result<Self> {
-        Self::load_via_json_merge(default_value, user_values)
-    }
-}
-
-impl Settings for ChatPanelSettings {
-    const KEY: Option<&'static str> = Some("chat_panel");
-    type FileContent = PanelSettingsContent;
-    fn load(
-        default_value: &Self::FileContent,
-        user_values: &[&Self::FileContent],
-        _: &mut gpui::AppContext,
-    ) -> anyhow::Result<Self> {
-        Self::load_via_json_merge(default_value, user_values)
-    }
-}
-
-impl Settings for NotificationPanelSettings {
-    const KEY: Option<&'static str> = Some("notification_panel");
-    type FileContent = PanelSettingsContent;
-    fn load(
-        default_value: &Self::FileContent,
-        user_values: &[&Self::FileContent],
-        _: &mut gpui::AppContext,
-    ) -> anyhow::Result<Self> {
-        Self::load_via_json_merge(default_value, user_values)
-    }
-}

crates/command_palette/Cargo.toml 🔗

@@ -11,21 +11,26 @@ doctest = false
 [dependencies]
 collections = { path = "../collections" }
 editor = { path = "../editor" }
-fuzzy = { path = "../fuzzy" }
+fuzzy = {  path = "../fuzzy" }
 gpui = { path = "../gpui" }
 picker = { path = "../picker" }
 project = { path = "../project" }
 settings = { path = "../settings" }
+ui = { path = "../ui" }
 util = { path = "../util" }
 theme = { path = "../theme" }
 workspace = { path = "../workspace" }
-zed-actions = { path = "../zed-actions" }
+zed_actions = { path = "../zed_actions" }
+anyhow.workspace = true
+serde.workspace = true
 
 [dev-dependencies]
 gpui = { path = "../gpui", features = ["test-support"] }
 editor = { path = "../editor", features = ["test-support"] }
 language = { path = "../language", features = ["test-support"] }
 project = { path = "../project", features = ["test-support"] }
+menu = { path = "../menu" }
+go_to_line = { path = "../go_to_line" }
 serde_json.workspace = true
 workspace = { path = "../workspace", features = ["test-support"] }
 ctor.workspace = true

crates/command_palette/src/command_palette.rs 🔗

@@ -1,26 +1,92 @@
+use std::{
+    cmp::{self, Reverse},
+    sync::Arc,
+};
+
 use collections::{CommandPaletteFilter, HashMap};
 use fuzzy::{StringMatch, StringMatchCandidate};
 use gpui::{
-    actions, anyhow::anyhow, elements::*, keymap_matcher::Keystroke, Action, AnyWindowHandle,
-    AppContext, Element, MouseState, ViewContext,
+    actions, Action, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView,
+    ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
 };
-use picker::{Picker, PickerDelegate, PickerEvent};
-use std::cmp::{self, Reverse};
+use picker::{Picker, PickerDelegate};
+
+use ui::{h_stack, prelude::*, v_stack, HighlightedLabel, KeyBinding, ListItem, ListItemSpacing};
 use util::{
     channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
     ResultExt,
 };
-use workspace::Workspace;
+use workspace::{ModalView, Workspace};
 use zed_actions::OpenZedURL;
 
+actions!(command_palette, [Toggle]);
+
 pub fn init(cx: &mut AppContext) {
-    cx.add_action(toggle_command_palette);
-    CommandPalette::init(cx);
+    cx.set_global(HitCounts::default());
+    cx.set_global(CommandPaletteFilter::default());
+    cx.observe_new_views(CommandPalette::register).detach();
 }
 
-actions!(command_palette, [Toggle]);
+impl ModalView for CommandPalette {}
+
+pub struct CommandPalette {
+    picker: View<Picker<CommandPaletteDelegate>>,
+}
+
+impl CommandPalette {
+    fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+        workspace.register_action(|workspace, _: &Toggle, cx| {
+            let Some(previous_focus_handle) = cx.focused() else {
+                return;
+            };
+            workspace.toggle_modal(cx, move |cx| CommandPalette::new(previous_focus_handle, cx));
+        });
+    }
+
+    fn new(previous_focus_handle: FocusHandle, cx: &mut ViewContext<Self>) -> Self {
+        let filter = cx.try_global::<CommandPaletteFilter>();
+
+        let commands = cx
+            .available_actions()
+            .into_iter()
+            .filter_map(|action| {
+                let name = action.name();
+                let namespace = name.split("::").next().unwrap_or("malformed action name");
+                if filter.is_some_and(|f| {
+                    f.hidden_namespaces.contains(namespace)
+                        || f.hidden_action_types.contains(&action.type_id())
+                }) {
+                    return None;
+                }
+
+                Some(Command {
+                    name: humanize_action_name(&name),
+                    action,
+                })
+            })
+            .collect();
+
+        let delegate =
+            CommandPaletteDelegate::new(cx.view().downgrade(), commands, previous_focus_handle);
+
+        let picker = cx.new_view(|cx| Picker::new(delegate, cx));
+        Self { picker }
+    }
+}
+
+impl EventEmitter<DismissEvent> for CommandPalette {}
 
-pub type CommandPalette = Picker<CommandPaletteDelegate>;
+impl FocusableView for CommandPalette {
+    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
+        self.picker.focus_handle(cx)
+    }
+}
+
+impl Render for CommandPalette {
+    fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
+        v_stack().w(rems(34.)).child(self.picker.clone())
+    }
+}
 
 pub type CommandPaletteInterceptor =
     Box<dyn Fn(&str, &AppContext) -> Option<CommandInterceptResult>>;
@@ -32,24 +98,26 @@ pub struct CommandInterceptResult {
 }
 
 pub struct CommandPaletteDelegate {
-    actions: Vec<Command>,
+    command_palette: WeakView<CommandPalette>,
+    all_commands: Vec<Command>,
+    commands: Vec<Command>,
     matches: Vec<StringMatch>,
     selected_ix: usize,
-    focused_view_id: usize,
+    previous_focus_handle: FocusHandle,
 }
 
-pub enum Event {
-    Dismissed,
-    Confirmed {
-        window: AnyWindowHandle,
-        focused_view_id: usize,
-        action: Box<dyn Action>,
-    },
-}
 struct Command {
     name: String,
     action: Box<dyn Action>,
-    keystrokes: Vec<Keystroke>,
+}
+
+impl Clone for Command {
+    fn clone(&self) -> Self {
+        Self {
+            name: self.name.clone(),
+            action: self.action.boxed_clone(),
+        }
+    }
 }
 
 /// Hit count for each command in the palette.
@@ -58,26 +126,27 @@ struct Command {
 #[derive(Default)]
 struct HitCounts(HashMap<String, usize>);
 
-fn toggle_command_palette(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
-    let focused_view_id = cx.focused_view_id().unwrap_or_else(|| cx.view_id());
-    workspace.toggle_modal(cx, |_, cx| {
-        cx.add_view(|cx| Picker::new(CommandPaletteDelegate::new(focused_view_id), cx))
-    });
-}
-
 impl CommandPaletteDelegate {
-    pub fn new(focused_view_id: usize) -> Self {
+    fn new(
+        command_palette: WeakView<CommandPalette>,
+        commands: Vec<Command>,
+        previous_focus_handle: FocusHandle,
+    ) -> Self {
         Self {
-            actions: Default::default(),
+            command_palette,
+            all_commands: commands.clone(),
             matches: vec![],
+            commands,
             selected_ix: 0,
-            focused_view_id,
+            previous_focus_handle,
         }
     }
 }
 
 impl PickerDelegate for CommandPaletteDelegate {
-    fn placeholder_text(&self) -> std::sync::Arc<str> {
+    type ListItem = ListItem;
+
+    fn placeholder_text(&self) -> Arc<str> {
         "Execute a command...".into()
     }
 
@@ -98,49 +167,20 @@ impl PickerDelegate for CommandPaletteDelegate {
         query: String,
         cx: &mut ViewContext<Picker<Self>>,
     ) -> gpui::Task<()> {
-        let view_id = self.focused_view_id;
-        let window = cx.window();
+        let mut commands = self.all_commands.clone();
+
         cx.spawn(move |picker, mut cx| async move {
-            let mut actions = window
-                .available_actions(view_id, &cx)
-                .into_iter()
-                .flatten()
-                .filter_map(|(name, action, bindings)| {
-                    let filtered = cx.read(|cx| {
-                        if cx.has_global::<CommandPaletteFilter>() {
-                            let filter = cx.global::<CommandPaletteFilter>();
-                            filter.hidden_namespaces.contains(action.namespace())
-                        } else {
-                            false
-                        }
-                    });
-
-                    if filtered {
-                        None
-                    } else {
-                        Some(Command {
-                            name: humanize_action_name(name),
-                            action,
-                            keystrokes: bindings
-                                .iter()
-                                .map(|binding| binding.keystrokes())
-                                .last()
-                                .map_or(Vec::new(), |keystrokes| keystrokes.to_vec()),
-                        })
-                    }
-                })
-                .collect::<Vec<_>>();
-            let mut actions = cx.read(move |cx| {
-                let hit_counts = cx.optional_global::<HitCounts>();
-                actions.sort_by_key(|action| {
+            cx.read_global::<HitCounts, _>(|hit_counts, _| {
+                commands.sort_by_key(|action| {
                     (
-                        Reverse(hit_counts.and_then(|map| map.0.get(&action.name)).cloned()),
+                        Reverse(hit_counts.0.get(&action.name).cloned()),
                         action.name.clone(),
                     )
                 });
-                actions
-            });
-            let candidates = actions
+            })
+            .ok();
+
+            let candidates = commands
                 .iter()
                 .enumerate()
                 .map(|(ix, command)| StringMatchCandidate {
@@ -167,17 +207,17 @@ impl PickerDelegate for CommandPaletteDelegate {
                     true,
                     10000,
                     &Default::default(),
-                    cx.background(),
+                    cx.background_executor().clone(),
                 )
                 .await
             };
-            let mut intercept_result = cx.read(|cx| {
-                if cx.has_global::<CommandPaletteInterceptor>() {
-                    cx.global::<CommandPaletteInterceptor>()(&query, cx)
-                } else {
-                    None
-                }
-            });
+
+            let mut intercept_result = cx
+                .try_read_global(|interceptor: &CommandPaletteInterceptor, cx| {
+                    (interceptor)(&query, cx)
+                })
+                .flatten();
+
             if *RELEASE_CHANNEL == ReleaseChannel::Dev {
                 if parse_zed_link(&query).is_some() {
                     intercept_result = Some(CommandInterceptResult {
@@ -187,6 +227,7 @@ impl PickerDelegate for CommandPaletteDelegate {
                     })
                 }
             }
+
             if let Some(CommandInterceptResult {
                 action,
                 string,
@@ -195,29 +236,29 @@ impl PickerDelegate for CommandPaletteDelegate {
             {
                 if let Some(idx) = matches
                     .iter()
-                    .position(|m| actions[m.candidate_id].action.id() == action.id())
+                    .position(|m| commands[m.candidate_id].action.type_id() == action.type_id())
                 {
                     matches.remove(idx);
                 }
-                actions.push(Command {
+                commands.push(Command {
                     name: string.clone(),
                     action,
-                    keystrokes: vec![],
                 });
                 matches.insert(
                     0,
                     StringMatch {
-                        candidate_id: actions.len() - 1,
+                        candidate_id: commands.len() - 1,
                         string,
                         positions,
                         score: 0.0,
                     },
                 )
             }
+
             picker
                 .update(&mut cx, |picker, _| {
-                    let delegate = picker.delegate_mut();
-                    delegate.actions = actions;
+                    let delegate = &mut picker.delegate;
+                    delegate.commands = commands;
                     delegate.matches = matches;
                     if delegate.matches.is_empty() {
                         delegate.selected_ix = 0;
@@ -230,83 +271,60 @@ impl PickerDelegate for CommandPaletteDelegate {
         })
     }
 
-    fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
+    fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
+        self.command_palette
+            .update(cx, |_, cx| cx.emit(DismissEvent))
+            .log_err();
+    }
 
     fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
-        if !self.matches.is_empty() {
-            let window = cx.window();
-            let focused_view_id = self.focused_view_id;
-            let action_ix = self.matches[self.selected_ix].candidate_id;
-            let command = self.actions.remove(action_ix);
-            cx.update_default_global(|hit_counts: &mut HitCounts, _| {
-                *hit_counts.0.entry(command.name).or_default() += 1;
-            });
-            let action = command.action;
-
-            cx.app_context()
-                .spawn(move |mut cx| async move {
-                    window
-                        .dispatch_action(focused_view_id, action.as_ref(), &mut cx)
-                        .ok_or_else(|| anyhow!("window was closed"))
-                })
-                .detach_and_log_err(cx);
+        if self.matches.is_empty() {
+            self.dismissed(cx);
+            return;
         }
-        cx.emit(PickerEvent::Dismiss);
+        let action_ix = self.matches[self.selected_ix].candidate_id;
+        let command = self.commands.swap_remove(action_ix);
+        self.matches.clear();
+        self.commands.clear();
+        cx.update_global(|hit_counts: &mut HitCounts, _| {
+            *hit_counts.0.entry(command.name).or_default() += 1;
+        });
+        let action = command.action;
+        cx.focus(&self.previous_focus_handle);
+        cx.window_context()
+            .spawn(move |mut cx| async move { cx.update(|_, cx| cx.dispatch_action(action)) })
+            .detach_and_log_err(cx);
+        self.dismissed(cx);
     }
 
     fn render_match(
         &self,
         ix: usize,
-        mouse_state: &mut MouseState,
         selected: bool,
-        cx: &gpui::AppContext,
-    ) -> AnyElement<Picker<Self>> {
-        let mat = &self.matches[ix];
-        let command = &self.actions[mat.candidate_id];
-        let theme = theme::current(cx);
-        let style = theme.picker.item.in_state(selected).style_for(mouse_state);
-        let key_style = &theme.command_palette.key.in_state(selected);
-        let keystroke_spacing = theme.command_palette.keystroke_spacing;
-
-        Flex::row()
-            .with_child(
-                Label::new(mat.string.clone(), style.label.clone())
-                    .with_highlights(mat.positions.clone()),
-            )
-            .with_children(command.keystrokes.iter().map(|keystroke| {
-                Flex::row()
-                    .with_children(
-                        [
-                            (keystroke.ctrl, "^"),
-                            (keystroke.alt, "⌥"),
-                            (keystroke.cmd, "⌘"),
-                            (keystroke.shift, "⇧"),
-                        ]
-                        .into_iter()
-                        .filter_map(|(modifier, label)| {
-                            if modifier {
-                                Some(
-                                    Label::new(label, key_style.label.clone())
-                                        .contained()
-                                        .with_style(key_style.container),
-                                )
-                            } else {
-                                None
-                            }
-                        }),
-                    )
-                    .with_child(
-                        Label::new(keystroke.key.clone(), key_style.label.clone())
-                            .contained()
-                            .with_style(key_style.container),
-                    )
-                    .contained()
-                    .with_margin_left(keystroke_spacing)
-                    .flex_float()
-            }))
-            .contained()
-            .with_style(style.container)
-            .into_any()
+        cx: &mut ViewContext<Picker<Self>>,
+    ) -> Option<Self::ListItem> {
+        let r#match = self.matches.get(ix)?;
+        let command = self.commands.get(r#match.candidate_id)?;
+        Some(
+            ListItem::new(ix)
+                .inset(true)
+                .spacing(ListItemSpacing::Sparse)
+                .selected(selected)
+                .child(
+                    h_stack()
+                        .w_full()
+                        .justify_between()
+                        .child(HighlightedLabel::new(
+                            command.name.clone(),
+                            r#match.positions.clone(),
+                        ))
+                        .children(KeyBinding::for_action_in(
+                            &*command.action,
+                            &self.previous_focus_handle,
+                            cx,
+                        )),
+                ),
+        )
     }
 }
 
@@ -338,8 +356,7 @@ impl std::fmt::Debug for Command {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         f.debug_struct("Command")
             .field("name", &self.name)
-            .field("keystrokes", &self.keystrokes)
-            .finish()
+            .finish_non_exhaustive()
     }
 }
 
@@ -349,7 +366,9 @@ mod tests {
 
     use super::*;
     use editor::Editor;
-    use gpui::{executor::Deterministic, TestAppContext};
+    use go_to_line::GoToLine;
+    use gpui::TestAppContext;
+    use language::Point;
     use project::Project;
     use workspace::{AppState, Workspace};
 
@@ -370,101 +389,121 @@ mod tests {
     }
 
     #[gpui::test]
-    async fn test_command_palette(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
+    async fn test_command_palette(cx: &mut TestAppContext) {
         let app_state = init_test(cx);
-
         let project = Project::test(app_state.fs.clone(), [], cx).await;
-        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let workspace = window.root(cx);
-        let editor = window.add_view(cx, |cx| {
-            let mut editor = Editor::single_line(None, cx);
+        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
+
+        let editor = cx.new_view(|cx| {
+            let mut editor = Editor::single_line(cx);
             editor.set_text("abc", cx);
             editor
         });
 
         workspace.update(cx, |workspace, cx| {
-            cx.focus(&editor);
-            workspace.add_item(Box::new(editor.clone()), cx)
+            workspace.add_item(Box::new(editor.clone()), cx);
+            editor.update(cx, |editor, cx| editor.focus(cx))
         });
 
-        workspace.update(cx, |workspace, cx| {
-            toggle_command_palette(workspace, &Toggle, cx);
-        });
+        cx.simulate_keystrokes("cmd-shift-p");
 
-        let palette = workspace.read_with(cx, |workspace, _| {
-            workspace.modal::<CommandPalette>().unwrap()
+        let palette = workspace.update(cx, |workspace, cx| {
+            workspace
+                .active_modal::<CommandPalette>(cx)
+                .unwrap()
+                .read(cx)
+                .picker
+                .clone()
         });
 
-        palette
-            .update(cx, |palette, cx| {
-                // Fill up palette's command list by running an empty query;
-                // we only need it to subsequently assert that the palette is initially
-                // sorted by command's name.
-                palette.delegate_mut().update_matches("".to_string(), cx)
-            })
-            .await;
-
         palette.update(cx, |palette, _| {
+            assert!(palette.delegate.commands.len() > 5);
             let is_sorted =
                 |actions: &[Command]| actions.windows(2).all(|pair| pair[0].name <= pair[1].name);
-            assert!(is_sorted(&palette.delegate().actions));
+            assert!(is_sorted(&palette.delegate.commands));
         });
 
-        palette
-            .update(cx, |palette, cx| {
-                palette
-                    .delegate_mut()
-                    .update_matches("bcksp".to_string(), cx)
-            })
-            .await;
+        cx.simulate_input("bcksp");
 
-        palette.update(cx, |palette, cx| {
-            assert_eq!(palette.delegate().matches[0].string, "editor: backspace");
-            palette.confirm(&Default::default(), cx);
+        palette.update(cx, |palette, _| {
+            assert_eq!(palette.delegate.matches[0].string, "editor: backspace");
         });
-        deterministic.run_until_parked();
-        editor.read_with(cx, |editor, cx| {
-            assert_eq!(editor.text(cx), "ab");
+
+        cx.simulate_keystrokes("enter");
+
+        workspace.update(cx, |workspace, cx| {
+            assert!(workspace.active_modal::<CommandPalette>(cx).is_none());
+            assert_eq!(editor.read(cx).text(cx), "ab")
         });
 
         // Add namespace filter, and redeploy the palette
         cx.update(|cx| {
-            cx.update_default_global::<CommandPaletteFilter, _, _>(|filter, _| {
+            cx.set_global(CommandPaletteFilter::default());
+            cx.update_global::<CommandPaletteFilter, _>(|filter, _| {
                 filter.hidden_namespaces.insert("editor");
             })
         });
 
-        workspace.update(cx, |workspace, cx| {
-            toggle_command_palette(workspace, &Toggle, cx);
+        cx.simulate_keystrokes("cmd-shift-p");
+        cx.simulate_input("bcksp");
+
+        let palette = workspace.update(cx, |workspace, cx| {
+            workspace
+                .active_modal::<CommandPalette>(cx)
+                .unwrap()
+                .read(cx)
+                .picker
+                .clone()
+        });
+        palette.update(cx, |palette, _| {
+            assert!(palette.delegate.matches.is_empty())
+        });
+    }
+
+    #[gpui::test]
+    async fn test_go_to_line(cx: &mut TestAppContext) {
+        let app_state = init_test(cx);
+        let project = Project::test(app_state.fs.clone(), [], cx).await;
+        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
+
+        cx.simulate_keystrokes("cmd-n");
+
+        let editor = workspace.update(cx, |workspace, cx| {
+            workspace.active_item_as::<Editor>(cx).unwrap()
         });
+        editor.update(cx, |editor, cx| editor.set_text("1\n2\n3\n4\n5\n6\n", cx));
+
+        cx.simulate_keystrokes("cmd-shift-p");
+        cx.simulate_input("go to line: Toggle");
+        cx.simulate_keystrokes("enter");
 
-        // Assert editor command not present
-        let palette = workspace.read_with(cx, |workspace, _| {
-            workspace.modal::<CommandPalette>().unwrap()
+        workspace.update(cx, |workspace, cx| {
+            assert!(workspace.active_modal::<GoToLine>(cx).is_some())
         });
 
-        palette
-            .update(cx, |palette, cx| {
-                palette
-                    .delegate_mut()
-                    .update_matches("bcksp".to_string(), cx)
-            })
-            .await;
+        cx.simulate_keystrokes("3 enter");
 
-        palette.update(cx, |palette, _| {
-            assert!(palette.delegate().matches.is_empty())
+        editor.update(cx, |editor, cx| {
+            assert!(editor.focus_handle(cx).is_focused(cx));
+            assert_eq!(
+                editor.selections.last::<Point>(cx).range().start,
+                Point::new(2, 0)
+            );
         });
     }
 
     fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
         cx.update(|cx| {
             let app_state = AppState::test(cx);
-            theme::init((), cx);
+            theme::init(theme::LoadThemes::JustBase, cx);
             language::init(cx);
             editor::init(cx);
+            menu::init();
+            go_to_line::init(cx);
             workspace::init(app_state.clone(), cx);
             init(cx);
             Project::init_settings(cx);
+            settings::load_default_keymap(cx);
             app_state
         })
     }

crates/command_palette2/Cargo.toml 🔗

@@ -1,36 +0,0 @@
-[package]
-name = "command_palette2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/command_palette.rs"
-doctest = false
-
-[dependencies]
-collections = { path = "../collections" }
-editor = { package = "editor2", path = "../editor2" }
-fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-picker = { package = "picker2", path = "../picker2" }
-project = { package = "project2", path = "../project2" }
-settings = { package = "settings2", path = "../settings2" }
-ui = { package = "ui2", path = "../ui2" }
-util = { path = "../util" }
-theme = { package = "theme2", path = "../theme2" }
-workspace = { package="workspace2", path = "../workspace2" }
-zed_actions = { package = "zed_actions2", path = "../zed_actions2" }
-anyhow.workspace = true
-serde.workspace = true
-[dev-dependencies]
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
-language = { package="language2", path = "../language2", features = ["test-support"] }
-project = { package="project2", path = "../project2", features = ["test-support"] }
-menu = { package = "menu2", path = "../menu2" }
-go_to_line = { package = "go_to_line2", path = "../go_to_line2" }
-serde_json.workspace = true
-workspace = { package="workspace2", path = "../workspace2", features = ["test-support"] }
-ctor.workspace = true
-env_logger.workspace = true

crates/command_palette2/src/command_palette.rs 🔗

@@ -1,510 +0,0 @@
-use std::{
-    cmp::{self, Reverse},
-    sync::Arc,
-};
-
-use collections::{CommandPaletteFilter, HashMap};
-use fuzzy::{StringMatch, StringMatchCandidate};
-use gpui::{
-    actions, Action, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView,
-    ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
-};
-use picker::{Picker, PickerDelegate};
-
-use ui::{h_stack, prelude::*, v_stack, HighlightedLabel, KeyBinding, ListItem, ListItemSpacing};
-use util::{
-    channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
-    ResultExt,
-};
-use workspace::{ModalView, Workspace};
-use zed_actions::OpenZedURL;
-
-actions!(command_palette, [Toggle]);
-
-pub fn init(cx: &mut AppContext) {
-    cx.set_global(HitCounts::default());
-    cx.set_global(CommandPaletteFilter::default());
-    cx.observe_new_views(CommandPalette::register).detach();
-}
-
-impl ModalView for CommandPalette {}
-
-pub struct CommandPalette {
-    picker: View<Picker<CommandPaletteDelegate>>,
-}
-
-impl CommandPalette {
-    fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
-        workspace.register_action(|workspace, _: &Toggle, cx| {
-            let Some(previous_focus_handle) = cx.focused() else {
-                return;
-            };
-            workspace.toggle_modal(cx, move |cx| CommandPalette::new(previous_focus_handle, cx));
-        });
-    }
-
-    fn new(previous_focus_handle: FocusHandle, cx: &mut ViewContext<Self>) -> Self {
-        let filter = cx.try_global::<CommandPaletteFilter>();
-
-        let commands = cx
-            .available_actions()
-            .into_iter()
-            .filter_map(|action| {
-                let name = action.name();
-                let namespace = name.split("::").next().unwrap_or("malformed action name");
-                if filter.is_some_and(|f| {
-                    f.hidden_namespaces.contains(namespace)
-                        || f.hidden_action_types.contains(&action.type_id())
-                }) {
-                    return None;
-                }
-
-                Some(Command {
-                    name: humanize_action_name(&name),
-                    action,
-                })
-            })
-            .collect();
-
-        let delegate =
-            CommandPaletteDelegate::new(cx.view().downgrade(), commands, previous_focus_handle);
-
-        let picker = cx.new_view(|cx| Picker::new(delegate, cx));
-        Self { picker }
-    }
-}
-
-impl EventEmitter<DismissEvent> for CommandPalette {}
-
-impl FocusableView for CommandPalette {
-    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
-        self.picker.focus_handle(cx)
-    }
-}
-
-impl Render for CommandPalette {
-    fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
-        v_stack().w(rems(34.)).child(self.picker.clone())
-    }
-}
-
-pub type CommandPaletteInterceptor =
-    Box<dyn Fn(&str, &AppContext) -> Option<CommandInterceptResult>>;
-
-pub struct CommandInterceptResult {
-    pub action: Box<dyn Action>,
-    pub string: String,
-    pub positions: Vec<usize>,
-}
-
-pub struct CommandPaletteDelegate {
-    command_palette: WeakView<CommandPalette>,
-    all_commands: Vec<Command>,
-    commands: Vec<Command>,
-    matches: Vec<StringMatch>,
-    selected_ix: usize,
-    previous_focus_handle: FocusHandle,
-}
-
-struct Command {
-    name: String,
-    action: Box<dyn Action>,
-}
-
-impl Clone for Command {
-    fn clone(&self) -> Self {
-        Self {
-            name: self.name.clone(),
-            action: self.action.boxed_clone(),
-        }
-    }
-}
-
-/// Hit count for each command in the palette.
-/// We only account for commands triggered directly via command palette and not by e.g. keystrokes because
-/// if an user already knows a keystroke for a command, they are unlikely to use a command palette to look for it.
-#[derive(Default)]
-struct HitCounts(HashMap<String, usize>);
-
-impl CommandPaletteDelegate {
-    fn new(
-        command_palette: WeakView<CommandPalette>,
-        commands: Vec<Command>,
-        previous_focus_handle: FocusHandle,
-    ) -> Self {
-        Self {
-            command_palette,
-            all_commands: commands.clone(),
-            matches: vec![],
-            commands,
-            selected_ix: 0,
-            previous_focus_handle,
-        }
-    }
-}
-
-impl PickerDelegate for CommandPaletteDelegate {
-    type ListItem = ListItem;
-
-    fn placeholder_text(&self) -> Arc<str> {
-        "Execute a command...".into()
-    }
-
-    fn match_count(&self) -> usize {
-        self.matches.len()
-    }
-
-    fn selected_index(&self) -> usize {
-        self.selected_ix
-    }
-
-    fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
-        self.selected_ix = ix;
-    }
-
-    fn update_matches(
-        &mut self,
-        query: String,
-        cx: &mut ViewContext<Picker<Self>>,
-    ) -> gpui::Task<()> {
-        let mut commands = self.all_commands.clone();
-
-        cx.spawn(move |picker, mut cx| async move {
-            cx.read_global::<HitCounts, _>(|hit_counts, _| {
-                commands.sort_by_key(|action| {
-                    (
-                        Reverse(hit_counts.0.get(&action.name).cloned()),
-                        action.name.clone(),
-                    )
-                });
-            })
-            .ok();
-
-            let candidates = commands
-                .iter()
-                .enumerate()
-                .map(|(ix, command)| StringMatchCandidate {
-                    id: ix,
-                    string: command.name.to_string(),
-                    char_bag: command.name.chars().collect(),
-                })
-                .collect::<Vec<_>>();
-            let mut matches = if query.is_empty() {
-                candidates
-                    .into_iter()
-                    .enumerate()
-                    .map(|(index, candidate)| StringMatch {
-                        candidate_id: index,
-                        string: candidate.string,
-                        positions: Vec::new(),
-                        score: 0.0,
-                    })
-                    .collect()
-            } else {
-                fuzzy::match_strings(
-                    &candidates,
-                    &query,
-                    true,
-                    10000,
-                    &Default::default(),
-                    cx.background_executor().clone(),
-                )
-                .await
-            };
-
-            let mut intercept_result = cx
-                .try_read_global(|interceptor: &CommandPaletteInterceptor, cx| {
-                    (interceptor)(&query, cx)
-                })
-                .flatten();
-
-            if *RELEASE_CHANNEL == ReleaseChannel::Dev {
-                if parse_zed_link(&query).is_some() {
-                    intercept_result = Some(CommandInterceptResult {
-                        action: OpenZedURL { url: query.clone() }.boxed_clone(),
-                        string: query.clone(),
-                        positions: vec![],
-                    })
-                }
-            }
-
-            if let Some(CommandInterceptResult {
-                action,
-                string,
-                positions,
-            }) = intercept_result
-            {
-                if let Some(idx) = matches
-                    .iter()
-                    .position(|m| commands[m.candidate_id].action.type_id() == action.type_id())
-                {
-                    matches.remove(idx);
-                }
-                commands.push(Command {
-                    name: string.clone(),
-                    action,
-                });
-                matches.insert(
-                    0,
-                    StringMatch {
-                        candidate_id: commands.len() - 1,
-                        string,
-                        positions,
-                        score: 0.0,
-                    },
-                )
-            }
-
-            picker
-                .update(&mut cx, |picker, _| {
-                    let delegate = &mut picker.delegate;
-                    delegate.commands = commands;
-                    delegate.matches = matches;
-                    if delegate.matches.is_empty() {
-                        delegate.selected_ix = 0;
-                    } else {
-                        delegate.selected_ix =
-                            cmp::min(delegate.selected_ix, delegate.matches.len() - 1);
-                    }
-                })
-                .log_err();
-        })
-    }
-
-    fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
-        self.command_palette
-            .update(cx, |_, cx| cx.emit(DismissEvent))
-            .log_err();
-    }
-
-    fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
-        if self.matches.is_empty() {
-            self.dismissed(cx);
-            return;
-        }
-        let action_ix = self.matches[self.selected_ix].candidate_id;
-        let command = self.commands.swap_remove(action_ix);
-        self.matches.clear();
-        self.commands.clear();
-        cx.update_global(|hit_counts: &mut HitCounts, _| {
-            *hit_counts.0.entry(command.name).or_default() += 1;
-        });
-        let action = command.action;
-        cx.focus(&self.previous_focus_handle);
-        cx.window_context()
-            .spawn(move |mut cx| async move { cx.update(|_, cx| cx.dispatch_action(action)) })
-            .detach_and_log_err(cx);
-        self.dismissed(cx);
-    }
-
-    fn render_match(
-        &self,
-        ix: usize,
-        selected: bool,
-        cx: &mut ViewContext<Picker<Self>>,
-    ) -> Option<Self::ListItem> {
-        let r#match = self.matches.get(ix)?;
-        let command = self.commands.get(r#match.candidate_id)?;
-        Some(
-            ListItem::new(ix)
-                .inset(true)
-                .spacing(ListItemSpacing::Sparse)
-                .selected(selected)
-                .child(
-                    h_stack()
-                        .w_full()
-                        .justify_between()
-                        .child(HighlightedLabel::new(
-                            command.name.clone(),
-                            r#match.positions.clone(),
-                        ))
-                        .children(KeyBinding::for_action_in(
-                            &*command.action,
-                            &self.previous_focus_handle,
-                            cx,
-                        )),
-                ),
-        )
-    }
-}
-
-fn humanize_action_name(name: &str) -> String {
-    let capacity = name.len() + name.chars().filter(|c| c.is_uppercase()).count();
-    let mut result = String::with_capacity(capacity);
-    for char in name.chars() {
-        if char == ':' {
-            if result.ends_with(':') {
-                result.push(' ');
-            } else {
-                result.push(':');
-            }
-        } else if char == '_' {
-            result.push(' ');
-        } else if char.is_uppercase() {
-            if !result.ends_with(' ') {
-                result.push(' ');
-            }
-            result.extend(char.to_lowercase());
-        } else {
-            result.push(char);
-        }
-    }
-    result
-}
-
-impl std::fmt::Debug for Command {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("Command")
-            .field("name", &self.name)
-            .finish_non_exhaustive()
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use std::sync::Arc;
-
-    use super::*;
-    use editor::Editor;
-    use go_to_line::GoToLine;
-    use gpui::TestAppContext;
-    use language::Point;
-    use project::Project;
-    use workspace::{AppState, Workspace};
-
-    #[test]
-    fn test_humanize_action_name() {
-        assert_eq!(
-            humanize_action_name("editor::GoToDefinition"),
-            "editor: go to definition"
-        );
-        assert_eq!(
-            humanize_action_name("editor::Backspace"),
-            "editor: backspace"
-        );
-        assert_eq!(
-            humanize_action_name("go_to_line::Deploy"),
-            "go to line: deploy"
-        );
-    }
-
-    #[gpui::test]
-    async fn test_command_palette(cx: &mut TestAppContext) {
-        let app_state = init_test(cx);
-        let project = Project::test(app_state.fs.clone(), [], cx).await;
-        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
-
-        let editor = cx.new_view(|cx| {
-            let mut editor = Editor::single_line(cx);
-            editor.set_text("abc", cx);
-            editor
-        });
-
-        workspace.update(cx, |workspace, cx| {
-            workspace.add_item(Box::new(editor.clone()), cx);
-            editor.update(cx, |editor, cx| editor.focus(cx))
-        });
-
-        cx.simulate_keystrokes("cmd-shift-p");
-
-        let palette = workspace.update(cx, |workspace, cx| {
-            workspace
-                .active_modal::<CommandPalette>(cx)
-                .unwrap()
-                .read(cx)
-                .picker
-                .clone()
-        });
-
-        palette.update(cx, |palette, _| {
-            assert!(palette.delegate.commands.len() > 5);
-            let is_sorted =
-                |actions: &[Command]| actions.windows(2).all(|pair| pair[0].name <= pair[1].name);
-            assert!(is_sorted(&palette.delegate.commands));
-        });
-
-        cx.simulate_input("bcksp");
-
-        palette.update(cx, |palette, _| {
-            assert_eq!(palette.delegate.matches[0].string, "editor: backspace");
-        });
-
-        cx.simulate_keystrokes("enter");
-
-        workspace.update(cx, |workspace, cx| {
-            assert!(workspace.active_modal::<CommandPalette>(cx).is_none());
-            assert_eq!(editor.read(cx).text(cx), "ab")
-        });
-
-        // Add namespace filter, and redeploy the palette
-        cx.update(|cx| {
-            cx.set_global(CommandPaletteFilter::default());
-            cx.update_global::<CommandPaletteFilter, _>(|filter, _| {
-                filter.hidden_namespaces.insert("editor");
-            })
-        });
-
-        cx.simulate_keystrokes("cmd-shift-p");
-        cx.simulate_input("bcksp");
-
-        let palette = workspace.update(cx, |workspace, cx| {
-            workspace
-                .active_modal::<CommandPalette>(cx)
-                .unwrap()
-                .read(cx)
-                .picker
-                .clone()
-        });
-        palette.update(cx, |palette, _| {
-            assert!(palette.delegate.matches.is_empty())
-        });
-    }
-
-    #[gpui::test]
-    async fn test_go_to_line(cx: &mut TestAppContext) {
-        let app_state = init_test(cx);
-        let project = Project::test(app_state.fs.clone(), [], cx).await;
-        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
-
-        cx.simulate_keystrokes("cmd-n");
-
-        let editor = workspace.update(cx, |workspace, cx| {
-            workspace.active_item_as::<Editor>(cx).unwrap()
-        });
-        editor.update(cx, |editor, cx| editor.set_text("1\n2\n3\n4\n5\n6\n", cx));
-
-        cx.simulate_keystrokes("cmd-shift-p");
-        cx.simulate_input("go to line: Toggle");
-        cx.simulate_keystrokes("enter");
-
-        workspace.update(cx, |workspace, cx| {
-            assert!(workspace.active_modal::<GoToLine>(cx).is_some())
-        });
-
-        cx.simulate_keystrokes("3 enter");
-
-        editor.update(cx, |editor, cx| {
-            assert!(editor.focus_handle(cx).is_focused(cx));
-            assert_eq!(
-                editor.selections.last::<Point>(cx).range().start,
-                Point::new(2, 0)
-            );
-        });
-    }
-
-    fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
-        cx.update(|cx| {
-            let app_state = AppState::test(cx);
-            theme::init(theme::LoadThemes::JustBase, cx);
-            language::init(cx);
-            editor::init(cx);
-            menu::init();
-            go_to_line::init(cx);
-            workspace::init(app_state.clone(), cx);
-            init(cx);
-            Project::init_settings(cx);
-            settings::load_default_keymap(cx);
-            app_state
-        })
-    }
-}

crates/component_test/Cargo.toml 🔗

@@ -1,18 +0,0 @@
-[package]
-name = "component_test"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/component_test.rs"
-doctest = false
-
-[dependencies]
-anyhow.workspace = true
-gpui = { path = "../gpui" }
-settings = { path = "../settings" }
-util = { path = "../util" }
-theme = { path = "../theme" }
-workspace = { path = "../workspace" }
-project = { path = "../project" }

crates/component_test/src/component_test.rs 🔗

@@ -1,121 +0,0 @@
-use gpui::{
-    actions,
-    elements::{Component, Flex, ParentElement, SafeStylable},
-    AppContext, Element, Entity, ModelHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle,
-};
-use project::Project;
-use theme::components::{action_button::Button, label::Label, ComponentExt};
-use workspace::{
-    item::Item, register_deserializable_item, ItemId, Pane, PaneBackdrop, Workspace, WorkspaceId,
-};
-
-pub fn init(cx: &mut AppContext) {
-    cx.add_action(ComponentTest::toggle_disclosure);
-    cx.add_action(ComponentTest::toggle_toggle);
-    cx.add_action(ComponentTest::deploy);
-    register_deserializable_item::<ComponentTest>(cx);
-}
-
-actions!(
-    test,
-    [NoAction, ToggleDisclosure, ToggleToggle, NewComponentTest]
-);
-
-struct ComponentTest {
-    disclosed: bool,
-    toggled: bool,
-}
-
-impl ComponentTest {
-    fn new() -> Self {
-        Self {
-            disclosed: false,
-            toggled: false,
-        }
-    }
-
-    fn deploy(workspace: &mut Workspace, _: &NewComponentTest, cx: &mut ViewContext<Workspace>) {
-        workspace.add_item(Box::new(cx.add_view(|_| ComponentTest::new())), cx);
-    }
-
-    fn toggle_disclosure(&mut self, _: &ToggleDisclosure, cx: &mut ViewContext<Self>) {
-        self.disclosed = !self.disclosed;
-        cx.notify();
-    }
-
-    fn toggle_toggle(&mut self, _: &ToggleToggle, cx: &mut ViewContext<Self>) {
-        self.toggled = !self.toggled;
-        cx.notify();
-    }
-}
-
-impl Entity for ComponentTest {
-    type Event = ();
-}
-
-impl View for ComponentTest {
-    fn ui_name() -> &'static str {
-        "Component Test"
-    }
-
-    fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> gpui::AnyElement<Self> {
-        let theme = theme::current(cx);
-
-        PaneBackdrop::new(
-            cx.view_id(),
-            Flex::column()
-                .with_spacing(10.)
-                .with_child(
-                    Button::action(NoAction)
-                        .with_tooltip("Here's what a tooltip looks like", theme.tooltip.clone())
-                        .with_contents(Label::new("Click me!"))
-                        .with_style(theme.component_test.button.clone())
-                        .element(),
-                )
-                .with_child(
-                    Button::action(ToggleToggle)
-                        .with_tooltip("Here's what a tooltip looks like", theme.tooltip.clone())
-                        .with_contents(Label::new("Toggle me!"))
-                        .toggleable(self.toggled)
-                        .with_style(theme.component_test.toggle.clone())
-                        .element(),
-                )
-                .with_child(
-                    Label::new("A disclosure")
-                        .disclosable(Some(self.disclosed), Box::new(ToggleDisclosure))
-                        .with_style(theme.component_test.disclosure.clone())
-                        .element(),
-                )
-                .constrained()
-                .with_width(200.)
-                .aligned()
-                .into_any(),
-        )
-        .into_any()
-    }
-}
-
-impl Item for ComponentTest {
-    fn tab_content<V: 'static>(
-        &self,
-        _: Option<usize>,
-        style: &theme::Tab,
-        _: &AppContext,
-    ) -> gpui::AnyElement<V> {
-        gpui::elements::Label::new("Component test", style.label.clone()).into_any()
-    }
-
-    fn serialized_item_kind() -> Option<&'static str> {
-        Some("ComponentTest")
-    }
-
-    fn deserialize(
-        _project: ModelHandle<Project>,
-        _workspace: WeakViewHandle<Workspace>,
-        _workspace_id: WorkspaceId,
-        _item_id: ItemId,
-        cx: &mut ViewContext<Pane>,
-    ) -> Task<anyhow::Result<ViewHandle<Self>>> {
-        Task::ready(Ok(cx.add_view(|_| Self::new())))
-    }
-}

crates/context_menu/Cargo.toml 🔗

@@ -1,16 +0,0 @@
-[package]
-name = "context_menu"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/context_menu.rs"
-doctest = false
-
-[dependencies]
-gpui = { path = "../gpui" }
-menu = { path = "../menu" }
-settings = { path = "../settings" }
-theme = { path = "../theme" }
-smallvec.workspace = true

crates/context_menu/src/context_menu.rs 🔗

@@ -1,528 +0,0 @@
-use gpui::{
-    anyhow::{self, anyhow},
-    elements::*,
-    geometry::vector::Vector2F,
-    keymap_matcher::KeymapContext,
-    platform::{CursorStyle, MouseButton},
-    Action, AnyViewHandle, AppContext, Axis, Entity, MouseState, SizeConstraint, Subscription,
-    View, ViewContext,
-};
-use menu::*;
-use std::{any::TypeId, borrow::Cow, sync::Arc, time::Duration};
-
-pub fn init(cx: &mut AppContext) {
-    cx.add_action(ContextMenu::select_first);
-    cx.add_action(ContextMenu::select_last);
-    cx.add_action(ContextMenu::select_next);
-    cx.add_action(ContextMenu::select_prev);
-    cx.add_action(ContextMenu::confirm);
-    cx.add_action(ContextMenu::cancel);
-}
-
-pub type StaticItem = Box<dyn Fn(&mut AppContext) -> AnyElement<ContextMenu>>;
-
-type ContextMenuItemBuilder =
-    Box<dyn Fn(&mut MouseState, &theme::ContextMenuItem) -> AnyElement<ContextMenu>>;
-
-pub enum ContextMenuItemLabel {
-    String(Cow<'static, str>),
-    Element(ContextMenuItemBuilder),
-}
-
-impl From<Cow<'static, str>> for ContextMenuItemLabel {
-    fn from(s: Cow<'static, str>) -> Self {
-        Self::String(s)
-    }
-}
-
-impl From<&'static str> for ContextMenuItemLabel {
-    fn from(s: &'static str) -> Self {
-        Self::String(s.into())
-    }
-}
-
-impl From<String> for ContextMenuItemLabel {
-    fn from(s: String) -> Self {
-        Self::String(s.into())
-    }
-}
-
-impl<T> From<T> for ContextMenuItemLabel
-where
-    T: 'static + Fn(&mut MouseState, &theme::ContextMenuItem) -> AnyElement<ContextMenu>,
-{
-    fn from(f: T) -> Self {
-        Self::Element(Box::new(f))
-    }
-}
-
-pub enum ContextMenuItemAction {
-    Action(Box<dyn Action>),
-    Handler(Arc<dyn Fn(&mut ViewContext<ContextMenu>)>),
-}
-
-impl Clone for ContextMenuItemAction {
-    fn clone(&self) -> Self {
-        match self {
-            Self::Action(action) => Self::Action(action.boxed_clone()),
-            Self::Handler(handler) => Self::Handler(handler.clone()),
-        }
-    }
-}
-
-pub enum ContextMenuItem {
-    Item {
-        label: ContextMenuItemLabel,
-        action: ContextMenuItemAction,
-    },
-    Static(StaticItem),
-    Separator,
-}
-
-impl ContextMenuItem {
-    pub fn action(label: impl Into<ContextMenuItemLabel>, action: impl 'static + Action) -> Self {
-        Self::Item {
-            label: label.into(),
-            action: ContextMenuItemAction::Action(Box::new(action)),
-        }
-    }
-
-    pub fn handler(
-        label: impl Into<ContextMenuItemLabel>,
-        handler: impl 'static + Fn(&mut ViewContext<ContextMenu>),
-    ) -> Self {
-        Self::Item {
-            label: label.into(),
-            action: ContextMenuItemAction::Handler(Arc::new(handler)),
-        }
-    }
-
-    pub fn separator() -> Self {
-        Self::Separator
-    }
-
-    fn is_action(&self) -> bool {
-        matches!(self, Self::Item { .. })
-    }
-
-    fn action_id(&self) -> Option<TypeId> {
-        match self {
-            ContextMenuItem::Item { action, .. } => match action {
-                ContextMenuItemAction::Action(action) => Some(action.id()),
-                ContextMenuItemAction::Handler(_) => None,
-            },
-            ContextMenuItem::Static(..) | ContextMenuItem::Separator => None,
-        }
-    }
-}
-
-pub struct ContextMenu {
-    show_count: usize,
-    anchor_position: Vector2F,
-    anchor_corner: AnchorCorner,
-    position_mode: OverlayPositionMode,
-    items: Vec<ContextMenuItem>,
-    selected_index: Option<usize>,
-    visible: bool,
-    delay_cancel: bool,
-    previously_focused_view_id: Option<usize>,
-    parent_view_id: usize,
-    _actions_observation: Subscription,
-}
-
-impl Entity for ContextMenu {
-    type Event = ();
-}
-
-impl View for ContextMenu {
-    fn ui_name() -> &'static str {
-        "ContextMenu"
-    }
-
-    fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
-        Self::reset_to_default_keymap_context(keymap);
-        keymap.add_identifier("menu");
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        if !self.visible {
-            return Empty::new().into_any();
-        }
-
-        // Render the menu once at minimum width.
-        let mut collapsed_menu = self.render_menu_for_measurement(cx);
-        let expanded_menu =
-            self.render_menu(cx)
-                .constrained()
-                .dynamically(move |constraint, view, cx| {
-                    SizeConstraint::strict_along(
-                        Axis::Horizontal,
-                        collapsed_menu.layout(constraint, view, cx).0.x(),
-                    )
-                });
-
-        Overlay::new(expanded_menu)
-            .with_hoverable(true)
-            .with_fit_mode(OverlayFitMode::SnapToWindow)
-            .with_anchor_position(self.anchor_position)
-            .with_anchor_corner(self.anchor_corner)
-            .with_position_mode(self.position_mode)
-            .into_any()
-    }
-
-    fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
-        self.reset(cx);
-    }
-}
-
-impl ContextMenu {
-    pub fn new(parent_view_id: usize, cx: &mut ViewContext<Self>) -> Self {
-        Self {
-            show_count: 0,
-            delay_cancel: false,
-            anchor_position: Default::default(),
-            anchor_corner: AnchorCorner::TopLeft,
-            position_mode: OverlayPositionMode::Window,
-            items: Default::default(),
-            selected_index: Default::default(),
-            visible: Default::default(),
-            previously_focused_view_id: Default::default(),
-            parent_view_id,
-            _actions_observation: cx.observe_actions(Self::action_dispatched),
-        }
-    }
-
-    pub fn visible(&self) -> bool {
-        self.visible
-    }
-
-    fn action_dispatched(&mut self, action_id: TypeId, cx: &mut ViewContext<Self>) {
-        if let Some(ix) = self
-            .items
-            .iter()
-            .position(|item| item.action_id() == Some(action_id))
-        {
-            self.selected_index = Some(ix);
-            cx.notify();
-            cx.spawn(|this, mut cx| async move {
-                cx.background().timer(Duration::from_millis(50)).await;
-                this.update(&mut cx, |this, cx| this.cancel(&Default::default(), cx))?;
-                anyhow::Ok(())
-            })
-            .detach_and_log_err(cx);
-        }
-    }
-
-    fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
-        if let Some(ix) = self.selected_index {
-            if let Some(ContextMenuItem::Item { action, .. }) = self.items.get(ix) {
-                match action {
-                    ContextMenuItemAction::Action(action) => {
-                        let window = cx.window();
-                        let view_id = self.parent_view_id;
-                        let action = action.boxed_clone();
-                        cx.app_context()
-                            .spawn(|mut cx| async move {
-                                window
-                                    .dispatch_action(view_id, action.as_ref(), &mut cx)
-                                    .ok_or_else(|| anyhow!("window was closed"))
-                            })
-                            .detach_and_log_err(cx);
-                    }
-                    ContextMenuItemAction::Handler(handler) => handler(cx),
-                }
-                self.reset(cx);
-            }
-        }
-    }
-
-    pub fn delay_cancel(&mut self) {
-        self.delay_cancel = true;
-    }
-
-    fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
-        if !self.delay_cancel {
-            self.reset(cx);
-            let show_count = self.show_count;
-            cx.defer(move |this, cx| {
-                if cx.handle().is_focused(cx) && this.show_count == show_count {
-                    (**cx).focus(this.previously_focused_view_id.take());
-                }
-            });
-        } else {
-            self.delay_cancel = false;
-        }
-    }
-
-    fn reset(&mut self, cx: &mut ViewContext<Self>) {
-        self.items.clear();
-        self.visible = false;
-        self.selected_index.take();
-        cx.notify();
-    }
-
-    fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext<Self>) {
-        self.selected_index = self.items.iter().position(|item| item.is_action());
-        cx.notify();
-    }
-
-    fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext<Self>) {
-        for (ix, item) in self.items.iter().enumerate().rev() {
-            if item.is_action() {
-                self.selected_index = Some(ix);
-                cx.notify();
-                break;
-            }
-        }
-    }
-
-    fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
-        if let Some(ix) = self.selected_index {
-            for (ix, item) in self.items.iter().enumerate().skip(ix + 1) {
-                if item.is_action() {
-                    self.selected_index = Some(ix);
-                    cx.notify();
-                    break;
-                }
-            }
-        } else {
-            self.select_first(&Default::default(), cx);
-        }
-    }
-
-    fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
-        if let Some(ix) = self.selected_index {
-            for (ix, item) in self.items.iter().enumerate().take(ix).rev() {
-                if item.is_action() {
-                    self.selected_index = Some(ix);
-                    cx.notify();
-                    break;
-                }
-            }
-        } else {
-            self.select_last(&Default::default(), cx);
-        }
-    }
-
-    pub fn toggle(
-        &mut self,
-        anchor_position: Vector2F,
-        anchor_corner: AnchorCorner,
-        items: Vec<ContextMenuItem>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        if self.visible() {
-            self.cancel(&Cancel, cx);
-        } else {
-            let mut items = items.into_iter().peekable();
-            if items.peek().is_some() {
-                self.items = items.collect();
-                self.anchor_position = anchor_position;
-                self.anchor_corner = anchor_corner;
-                self.visible = true;
-                self.show_count += 1;
-                if !cx.is_self_focused() {
-                    self.previously_focused_view_id = cx.focused_view_id();
-                }
-                cx.focus_self();
-            } else {
-                self.visible = false;
-            }
-        }
-        cx.notify();
-    }
-
-    pub fn show(
-        &mut self,
-        anchor_position: Vector2F,
-        anchor_corner: AnchorCorner,
-        items: Vec<ContextMenuItem>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let mut items = items.into_iter().peekable();
-        if items.peek().is_some() {
-            self.items = items.collect();
-            self.anchor_position = anchor_position;
-            self.anchor_corner = anchor_corner;
-            self.visible = true;
-            self.show_count += 1;
-            if !cx.is_self_focused() {
-                self.previously_focused_view_id = cx.focused_view_id();
-            }
-            cx.focus_self();
-        } else {
-            self.visible = false;
-        }
-        cx.notify();
-    }
-
-    pub fn set_position_mode(&mut self, mode: OverlayPositionMode) {
-        self.position_mode = mode;
-    }
-
-    fn render_menu_for_measurement(&self, cx: &mut ViewContext<Self>) -> impl Element<ContextMenu> {
-        let style = theme::current(cx).context_menu.clone();
-        Flex::row()
-            .with_child(
-                Flex::column().with_children(self.items.iter().enumerate().map(|(ix, item)| {
-                    match item {
-                        ContextMenuItem::Item { label, .. } => {
-                            let style = style.item.in_state(self.selected_index == Some(ix));
-                            let style = style.style_for(&mut Default::default());
-
-                            match label {
-                                ContextMenuItemLabel::String(label) => {
-                                    Label::new(label.to_string(), style.label.clone())
-                                        .contained()
-                                        .with_style(style.container)
-                                        .into_any()
-                                }
-                                ContextMenuItemLabel::Element(element) => {
-                                    element(&mut Default::default(), style)
-                                }
-                            }
-                        }
-
-                        ContextMenuItem::Static(f) => f(cx),
-
-                        ContextMenuItem::Separator => Empty::new()
-                            .collapsed()
-                            .contained()
-                            .with_style(style.separator)
-                            .constrained()
-                            .with_height(1.)
-                            .into_any(),
-                    }
-                })),
-            )
-            .with_child(
-                Flex::column()
-                    .with_children(self.items.iter().enumerate().map(|(ix, item)| {
-                        match item {
-                            ContextMenuItem::Item { action, .. } => {
-                                let style = style.item.in_state(self.selected_index == Some(ix));
-                                let style = style.style_for(&mut Default::default());
-
-                                match action {
-                                    ContextMenuItemAction::Action(action) => KeystrokeLabel::new(
-                                        self.parent_view_id,
-                                        action.boxed_clone(),
-                                        style.keystroke.container,
-                                        style.keystroke.text.clone(),
-                                    )
-                                    .into_any(),
-                                    ContextMenuItemAction::Handler(_) => Empty::new().into_any(),
-                                }
-                            }
-
-                            ContextMenuItem::Static(_) => Empty::new().into_any(),
-
-                            ContextMenuItem::Separator => Empty::new()
-                                .collapsed()
-                                .constrained()
-                                .with_height(1.)
-                                .contained()
-                                .with_style(style.separator)
-                                .into_any(),
-                        }
-                    }))
-                    .contained()
-                    .with_margin_left(style.keystroke_margin),
-            )
-            .contained()
-            .with_style(style.container)
-    }
-
-    fn render_menu(&self, cx: &mut ViewContext<Self>) -> impl Element<ContextMenu> {
-        enum Menu {}
-        enum MenuItem {}
-
-        let style = theme::current(cx).context_menu.clone();
-
-        MouseEventHandler::new::<Menu, _>(0, cx, |_, cx| {
-            Flex::column()
-                .with_children(self.items.iter().enumerate().map(|(ix, item)| {
-                    match item {
-                        ContextMenuItem::Item { label, action } => {
-                            let action = action.clone();
-                            let view_id = self.parent_view_id;
-                            MouseEventHandler::new::<MenuItem, _>(ix, cx, |state, _| {
-                                let style = style.item.in_state(self.selected_index == Some(ix));
-                                let style = style.style_for(state);
-                                let keystroke = match &action {
-                                    ContextMenuItemAction::Action(action) => Some(
-                                        KeystrokeLabel::new(
-                                            view_id,
-                                            action.boxed_clone(),
-                                            style.keystroke.container,
-                                            style.keystroke.text.clone(),
-                                        )
-                                        .flex_float(),
-                                    ),
-                                    ContextMenuItemAction::Handler(_) => None,
-                                };
-
-                                Flex::row()
-                                    .with_child(match label {
-                                        ContextMenuItemLabel::String(label) => {
-                                            Label::new(label.clone(), style.label.clone())
-                                                .contained()
-                                                .into_any()
-                                        }
-                                        ContextMenuItemLabel::Element(element) => {
-                                            element(state, style)
-                                        }
-                                    })
-                                    .with_children(keystroke)
-                                    .contained()
-                                    .with_style(style.container)
-                            })
-                            .with_cursor_style(CursorStyle::PointingHand)
-                            .on_up(MouseButton::Left, |_, _, _| {}) // Capture these events
-                            .on_down(MouseButton::Left, |_, _, _| {}) // Capture these events
-                            .on_click(MouseButton::Left, move |_, menu, cx| {
-                                menu.cancel(&Default::default(), cx);
-                                let window = cx.window();
-                                match &action {
-                                    ContextMenuItemAction::Action(action) => {
-                                        let action = action.boxed_clone();
-                                        cx.app_context()
-                                            .spawn(|mut cx| async move {
-                                                window
-                                                    .dispatch_action(
-                                                        view_id,
-                                                        action.as_ref(),
-                                                        &mut cx,
-                                                    )
-                                                    .ok_or_else(|| anyhow!("window was closed"))
-                                            })
-                                            .detach_and_log_err(cx);
-                                    }
-                                    ContextMenuItemAction::Handler(handler) => handler(cx),
-                                }
-                            })
-                            .on_drag(MouseButton::Left, |_, _, _| {})
-                            .into_any()
-                        }
-
-                        ContextMenuItem::Static(f) => f(cx),
-
-                        ContextMenuItem::Separator => Empty::new()
-                            .constrained()
-                            .with_height(1.)
-                            .contained()
-                            .with_style(style.separator)
-                            .into_any(),
-                    }
-                }))
-                .contained()
-                .with_style(style.container)
-        })
-        .on_down_out(MouseButton::Left, |_, this, cx| {
-            this.cancel(&Default::default(), cx);
-        })
-        .on_down_out(MouseButton::Right, |_, this, cx| {
-            this.cancel(&Default::default(), cx);
-        })
-    }
-}

crates/copilot/Cargo.toml 🔗

@@ -20,7 +20,7 @@ test-support = [
 
 [dependencies]
 collections = { path = "../collections" }
-context_menu = { path = "../context_menu" }
+# context_menu = { path = "../context_menu" }
 gpui = { path = "../gpui" }
 language = { path = "../language" }
 settings = { path = "../settings" }
@@ -28,6 +28,7 @@ theme = { path = "../theme" }
 lsp = { path = "../lsp" }
 node_runtime = { path = "../node_runtime"}
 util = { path = "../util" }
+ui = { path = "../ui" }
 async-compression.workspace = true
 async-tar = "0.4.2"
 anyhow.workspace = true

crates/copilot/src/copilot.rs 🔗

@@ -1,13 +1,14 @@
 pub mod request;
 mod sign_in;
 
-use anyhow::{anyhow, Context, Result};
+use anyhow::{anyhow, Context as _, Result};
 use async_compression::futures::bufread::GzipDecoder;
 use async_tar::Archive;
 use collections::{HashMap, HashSet};
 use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt};
 use gpui::{
-    actions, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle,
+    actions, AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter, Model,
+    ModelContext, Task, WeakModel,
 };
 use language::{
     language_settings::{all_language_settings, language_settings},
@@ -21,24 +22,27 @@ use request::StatusNotification;
 use settings::SettingsStore;
 use smol::{fs, io::BufReader, stream::StreamExt};
 use std::{
+    any::TypeId,
     ffi::OsString,
     mem,
     ops::Range,
     path::{Path, PathBuf},
-    pin::Pin,
     sync::Arc,
 };
 use util::{
     fs::remove_matching, github::latest_github_release, http::HttpClient, paths, ResultExt,
 };
 
-const COPILOT_AUTH_NAMESPACE: &'static str = "copilot_auth";
-actions!(copilot_auth, [SignIn, SignOut]);
-
-const COPILOT_NAMESPACE: &'static str = "copilot";
 actions!(
     copilot,
-    [Suggest, NextSuggestion, PreviousSuggestion, Reinstall]
+    [
+        Suggest,
+        NextSuggestion,
+        PreviousSuggestion,
+        Reinstall,
+        SignIn,
+        SignOut
+    ]
 );
 
 pub fn init(
@@ -47,50 +51,69 @@ pub fn init(
     node_runtime: Arc<dyn NodeRuntime>,
     cx: &mut AppContext,
 ) {
-    let copilot = cx.add_model({
+    let copilot = cx.new_model({
         let node_runtime = node_runtime.clone();
         move |cx| Copilot::start(new_server_id, http, node_runtime, cx)
     });
     cx.set_global(copilot.clone());
-
     cx.observe(&copilot, |handle, cx| {
+        let copilot_action_types = [
+            TypeId::of::<Suggest>(),
+            TypeId::of::<NextSuggestion>(),
+            TypeId::of::<PreviousSuggestion>(),
+            TypeId::of::<Reinstall>(),
+        ];
+        let copilot_auth_action_types = [TypeId::of::<SignOut>()];
+        let copilot_no_auth_action_types = [TypeId::of::<SignIn>()];
         let status = handle.read(cx).status();
-        cx.update_default_global::<collections::CommandPaletteFilter, _, _>(move |filter, _cx| {
-            match status {
-                Status::Disabled => {
-                    filter.hidden_namespaces.insert(COPILOT_NAMESPACE);
-                    filter.hidden_namespaces.insert(COPILOT_AUTH_NAMESPACE);
-                }
-                Status::Authorized => {
-                    filter.hidden_namespaces.remove(COPILOT_NAMESPACE);
-                    filter.hidden_namespaces.remove(COPILOT_AUTH_NAMESPACE);
+        let filter = cx.default_global::<collections::CommandPaletteFilter>();
+
+        match status {
+            Status::Disabled => {
+                filter.hidden_action_types.extend(copilot_action_types);
+                filter.hidden_action_types.extend(copilot_auth_action_types);
+                filter
+                    .hidden_action_types
+                    .extend(copilot_no_auth_action_types);
+            }
+            Status::Authorized => {
+                filter
+                    .hidden_action_types
+                    .extend(copilot_no_auth_action_types);
+                for type_id in copilot_action_types
+                    .iter()
+                    .chain(&copilot_auth_action_types)
+                {
+                    filter.hidden_action_types.remove(type_id);
                 }
-                _ => {
-                    filter.hidden_namespaces.insert(COPILOT_NAMESPACE);
-                    filter.hidden_namespaces.remove(COPILOT_AUTH_NAMESPACE);
+            }
+            _ => {
+                filter.hidden_action_types.extend(copilot_action_types);
+                filter.hidden_action_types.extend(copilot_auth_action_types);
+                for type_id in &copilot_no_auth_action_types {
+                    filter.hidden_action_types.remove(type_id);
                 }
             }
-        });
+        }
     })
     .detach();
 
     sign_in::init(cx);
-    cx.add_global_action(|_: &SignIn, cx| {
+    cx.on_action(|_: &SignIn, cx| {
         if let Some(copilot) = Copilot::global(cx) {
             copilot
                 .update(cx, |copilot, cx| copilot.sign_in(cx))
                 .detach_and_log_err(cx);
         }
     });
-    cx.add_global_action(|_: &SignOut, cx| {
+    cx.on_action(|_: &SignOut, cx| {
         if let Some(copilot) = Copilot::global(cx) {
             copilot
                 .update(cx, |copilot, cx| copilot.sign_out(cx))
                 .detach_and_log_err(cx);
         }
     });
-
-    cx.add_global_action(|_: &Reinstall, cx| {
+    cx.on_action(|_: &Reinstall, cx| {
         if let Some(copilot) = Copilot::global(cx) {
             copilot
                 .update(cx, |copilot, cx| copilot.reinstall(cx))
@@ -133,7 +156,7 @@ struct RunningCopilotServer {
     name: LanguageServerName,
     lsp: Arc<LanguageServer>,
     sign_in_status: SignInStatus,
-    registered_buffers: HashMap<usize, RegisteredBuffer>,
+    registered_buffers: HashMap<EntityId, RegisteredBuffer>,
 }
 
 #[derive(Clone, Debug)]
@@ -180,7 +203,7 @@ struct RegisteredBuffer {
 impl RegisteredBuffer {
     fn report_changes(
         &mut self,
-        buffer: &ModelHandle<Buffer>,
+        buffer: &Model<Buffer>,
         cx: &mut ModelContext<Copilot>,
     ) -> oneshot::Receiver<(i32, BufferSnapshot)> {
         let (done_tx, done_rx) = oneshot::channel();
@@ -189,23 +212,23 @@ impl RegisteredBuffer {
             let _ = done_tx.send((self.snapshot_version, self.snapshot.clone()));
         } else {
             let buffer = buffer.downgrade();
-            let id = buffer.id();
+            let id = buffer.entity_id();
             let prev_pending_change =
                 mem::replace(&mut self.pending_buffer_change, Task::ready(None));
-            self.pending_buffer_change = cx.spawn_weak(|copilot, mut cx| async move {
+            self.pending_buffer_change = cx.spawn(move |copilot, mut cx| async move {
                 prev_pending_change.await;
 
-                let old_version = copilot.upgrade(&cx)?.update(&mut cx, |copilot, _| {
-                    let server = copilot.server.as_authenticated().log_err()?;
-                    let buffer = server.registered_buffers.get_mut(&id)?;
-                    Some(buffer.snapshot.version.clone())
-                })?;
-                let new_snapshot = buffer
-                    .upgrade(&cx)?
-                    .read_with(&cx, |buffer, _| buffer.snapshot());
+                let old_version = copilot
+                    .update(&mut cx, |copilot, _| {
+                        let server = copilot.server.as_authenticated().log_err()?;
+                        let buffer = server.registered_buffers.get_mut(&id)?;
+                        Some(buffer.snapshot.version.clone())
+                    })
+                    .ok()??;
+                let new_snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot()).ok()?;
 
                 let content_changes = cx
-                    .background()
+                    .background_executor()
                     .spawn({
                         let new_snapshot = new_snapshot.clone();
                         async move {
@@ -231,28 +254,30 @@ impl RegisteredBuffer {
                     })
                     .await;
 
-                copilot.upgrade(&cx)?.update(&mut cx, |copilot, _| {
-                    let server = copilot.server.as_authenticated().log_err()?;
-                    let buffer = server.registered_buffers.get_mut(&id)?;
-                    if !content_changes.is_empty() {
-                        buffer.snapshot_version += 1;
-                        buffer.snapshot = new_snapshot;
-                        server
-                            .lsp
-                            .notify::<lsp::notification::DidChangeTextDocument>(
-                                lsp::DidChangeTextDocumentParams {
-                                    text_document: lsp::VersionedTextDocumentIdentifier::new(
-                                        buffer.uri.clone(),
-                                        buffer.snapshot_version,
-                                    ),
-                                    content_changes,
-                                },
-                            )
-                            .log_err();
-                    }
-                    let _ = done_tx.send((buffer.snapshot_version, buffer.snapshot.clone()));
-                    Some(())
-                })?;
+                copilot
+                    .update(&mut cx, |copilot, _| {
+                        let server = copilot.server.as_authenticated().log_err()?;
+                        let buffer = server.registered_buffers.get_mut(&id)?;
+                        if !content_changes.is_empty() {
+                            buffer.snapshot_version += 1;
+                            buffer.snapshot = new_snapshot;
+                            server
+                                .lsp
+                                .notify::<lsp::notification::DidChangeTextDocument>(
+                                    lsp::DidChangeTextDocumentParams {
+                                        text_document: lsp::VersionedTextDocumentIdentifier::new(
+                                            buffer.uri.clone(),
+                                            buffer.snapshot_version,
+                                        ),
+                                        content_changes,
+                                    },
+                                )
+                                .log_err();
+                        }
+                        let _ = done_tx.send((buffer.snapshot_version, buffer.snapshot.clone()));
+                        Some(())
+                    })
+                    .ok()?;
 
                 Some(())
             });
@@ -273,36 +298,21 @@ pub struct Copilot {
     http: Arc<dyn HttpClient>,
     node_runtime: Arc<dyn NodeRuntime>,
     server: CopilotServer,
-    buffers: HashSet<WeakModelHandle<Buffer>>,
+    buffers: HashSet<WeakModel<Buffer>>,
     server_id: LanguageServerId,
+    _subscription: gpui::Subscription,
 }
 
 pub enum Event {
     CopilotLanguageServerStarted,
 }
 
-impl Entity for Copilot {
-    type Event = Event;
-
-    fn app_will_quit(
-        &mut self,
-        _: &mut AppContext,
-    ) -> Option<Pin<Box<dyn 'static + Future<Output = ()>>>> {
-        match mem::replace(&mut self.server, CopilotServer::Disabled) {
-            CopilotServer::Running(server) => Some(Box::pin(async move {
-                if let Some(shutdown) = server.lsp.shutdown() {
-                    shutdown.await;
-                }
-            })),
-            _ => None,
-        }
-    }
-}
+impl EventEmitter<Event> for Copilot {}
 
 impl Copilot {
-    pub fn global(cx: &AppContext) -> Option<ModelHandle<Self>> {
-        if cx.has_global::<ModelHandle<Self>>() {
-            Some(cx.global::<ModelHandle<Self>>().clone())
+    pub fn global(cx: &AppContext) -> Option<Model<Self>> {
+        if cx.has_global::<Model<Self>>() {
+            Some(cx.global::<Model<Self>>().clone())
         } else {
             None
         }
@@ -320,24 +330,39 @@ impl Copilot {
             node_runtime,
             server: CopilotServer::Disabled,
             buffers: Default::default(),
+            _subscription: cx.on_app_quit(Self::shutdown_language_server),
         };
         this.enable_or_disable_copilot(cx);
-        cx.observe_global::<SettingsStore, _>(move |this, cx| this.enable_or_disable_copilot(cx))
+        cx.observe_global::<SettingsStore>(move |this, cx| this.enable_or_disable_copilot(cx))
             .detach();
         this
     }
 
-    fn enable_or_disable_copilot(&mut self, cx: &mut ModelContext<Copilot>) {
+    fn shutdown_language_server(
+        &mut self,
+        _cx: &mut ModelContext<Self>,
+    ) -> impl Future<Output = ()> {
+        let shutdown = match mem::replace(&mut self.server, CopilotServer::Disabled) {
+            CopilotServer::Running(server) => Some(Box::pin(async move { server.lsp.shutdown() })),
+            _ => None,
+        };
+
+        async move {
+            if let Some(shutdown) = shutdown {
+                shutdown.await;
+            }
+        }
+    }
+
+    fn enable_or_disable_copilot(&mut self, cx: &mut ModelContext<Self>) {
         let server_id = self.server_id;
         let http = self.http.clone();
         let node_runtime = self.node_runtime.clone();
         if all_language_settings(None, cx).copilot_enabled(None, None) {
             if matches!(self.server, CopilotServer::Disabled) {
                 let start_task = cx
-                    .spawn({
-                        move |this, cx| {
-                            Self::start_language_server(server_id, http, node_runtime, this, cx)
-                        }
+                    .spawn(move |this, cx| {
+                        Self::start_language_server(server_id, http, node_runtime, this, cx)
                     })
                     .shared();
                 self.server = CopilotServer::Starting { task: start_task };
@@ -350,14 +375,14 @@ impl Copilot {
     }
 
     #[cfg(any(test, feature = "test-support"))]
-    pub fn fake(cx: &mut gpui::TestAppContext) -> (ModelHandle<Self>, lsp::FakeLanguageServer) {
+    pub fn fake(cx: &mut gpui::TestAppContext) -> (Model<Self>, lsp::FakeLanguageServer) {
         use node_runtime::FakeNodeRuntime;
 
         let (server, fake_server) =
             LanguageServer::fake("copilot".into(), Default::default(), cx.to_async());
         let http = util::http::FakeHttpClient::create(|_| async { unreachable!() });
         let node_runtime = FakeNodeRuntime::new();
-        let this = cx.add_model(|_| Self {
+        let this = cx.new_model(|cx| Self {
             server_id: LanguageServerId(0),
             http: http.clone(),
             node_runtime,
@@ -367,6 +392,7 @@ impl Copilot {
                 sign_in_status: SignInStatus::Authorized,
                 registered_buffers: Default::default(),
             }),
+            _subscription: cx.on_app_quit(Self::shutdown_language_server),
             buffers: Default::default(),
         });
         (this, fake_server)
@@ -376,7 +402,7 @@ impl Copilot {
         new_server_id: LanguageServerId,
         http: Arc<dyn HttpClient>,
         node_runtime: Arc<dyn NodeRuntime>,
-        this: ModelHandle<Self>,
+        this: WeakModel<Self>,
         mut cx: AsyncAppContext,
     ) -> impl Future<Output = ()> {
         async move {
@@ -448,6 +474,7 @@ impl Copilot {
                     }
                 }
             })
+            .ok();
         }
     }
 
@@ -489,7 +516,7 @@ impl Copilot {
                                                     cx.notify();
                                                 }
                                             }
-                                        });
+                                        })?;
                                         let response = lsp
                                             .request::<request::SignInConfirm>(
                                                 request::SignInConfirmParams {
@@ -515,7 +542,7 @@ impl Copilot {
                                     );
                                     Err(Arc::new(error))
                                 }
-                            })
+                            })?
                         })
                         .shared();
                     server.sign_in_status = SignInStatus::SigningIn {
@@ -527,7 +554,7 @@ impl Copilot {
                 }
             };
 
-            cx.foreground()
+            cx.background_executor()
                 .spawn(task.map_err(|err| anyhow!("{:?}", err)))
         } else {
             // If we're downloading, wait until download is finished
@@ -540,7 +567,7 @@ impl Copilot {
         self.update_sign_in_status(request::SignInStatus::NotSignedIn, cx);
         if let CopilotServer::Running(RunningCopilotServer { lsp: server, .. }) = &self.server {
             let server = server.clone();
-            cx.background().spawn(async move {
+            cx.background_executor().spawn(async move {
                 server
                     .request::<request::SignOut>(request::SignOutParams {})
                     .await?;
@@ -570,7 +597,7 @@ impl Copilot {
 
         cx.notify();
 
-        cx.foreground().spawn(start_task)
+        cx.background_executor().spawn(start_task)
     }
 
     pub fn language_server(&self) -> Option<(&LanguageServerName, &Arc<LanguageServer>)> {
@@ -581,7 +608,7 @@ impl Copilot {
         }
     }
 
-    pub fn register_buffer(&mut self, buffer: &ModelHandle<Buffer>, cx: &mut ModelContext<Self>) {
+    pub fn register_buffer(&mut self, buffer: &Model<Buffer>, cx: &mut ModelContext<Self>) {
         let weak_buffer = buffer.downgrade();
         self.buffers.insert(weak_buffer.clone());
 
@@ -596,51 +623,54 @@ impl Copilot {
                 return;
             }
 
-            registered_buffers.entry(buffer.id()).or_insert_with(|| {
-                let uri: lsp::Url = uri_for_buffer(buffer, cx);
-                let language_id = id_for_language(buffer.read(cx).language());
-                let snapshot = buffer.read(cx).snapshot();
-                server
-                    .notify::<lsp::notification::DidOpenTextDocument>(
-                        lsp::DidOpenTextDocumentParams {
-                            text_document: lsp::TextDocumentItem {
-                                uri: uri.clone(),
-                                language_id: language_id.clone(),
-                                version: 0,
-                                text: snapshot.text(),
+            registered_buffers
+                .entry(buffer.entity_id())
+                .or_insert_with(|| {
+                    let uri: lsp::Url = uri_for_buffer(buffer, cx);
+                    let language_id = id_for_language(buffer.read(cx).language());
+                    let snapshot = buffer.read(cx).snapshot();
+                    server
+                        .notify::<lsp::notification::DidOpenTextDocument>(
+                            lsp::DidOpenTextDocumentParams {
+                                text_document: lsp::TextDocumentItem {
+                                    uri: uri.clone(),
+                                    language_id: language_id.clone(),
+                                    version: 0,
+                                    text: snapshot.text(),
+                                },
                             },
-                        },
-                    )
-                    .log_err();
+                        )
+                        .log_err();
 
-                RegisteredBuffer {
-                    uri,
-                    language_id,
-                    snapshot,
-                    snapshot_version: 0,
-                    pending_buffer_change: Task::ready(Some(())),
-                    _subscriptions: [
-                        cx.subscribe(buffer, |this, buffer, event, cx| {
-                            this.handle_buffer_event(buffer, event, cx).log_err();
-                        }),
-                        cx.observe_release(buffer, move |this, _buffer, _cx| {
-                            this.buffers.remove(&weak_buffer);
-                            this.unregister_buffer(&weak_buffer);
-                        }),
-                    ],
-                }
-            });
+                    RegisteredBuffer {
+                        uri,
+                        language_id,
+                        snapshot,
+                        snapshot_version: 0,
+                        pending_buffer_change: Task::ready(Some(())),
+                        _subscriptions: [
+                            cx.subscribe(buffer, |this, buffer, event, cx| {
+                                this.handle_buffer_event(buffer, event, cx).log_err();
+                            }),
+                            cx.observe_release(buffer, move |this, _buffer, _cx| {
+                                this.buffers.remove(&weak_buffer);
+                                this.unregister_buffer(&weak_buffer);
+                            }),
+                        ],
+                    }
+                });
         }
     }
 
     fn handle_buffer_event(
         &mut self,
-        buffer: ModelHandle<Buffer>,
+        buffer: Model<Buffer>,
         event: &language::Event,
         cx: &mut ModelContext<Self>,
     ) -> Result<()> {
         if let Ok(server) = self.server.as_running() {
-            if let Some(registered_buffer) = server.registered_buffers.get_mut(&buffer.id()) {
+            if let Some(registered_buffer) = server.registered_buffers.get_mut(&buffer.entity_id())
+            {
                 match event {
                     language::Event::Edited => {
                         let _ = registered_buffer.report_changes(&buffer, cx);
@@ -694,9 +724,9 @@ impl Copilot {
         Ok(())
     }
 
-    fn unregister_buffer(&mut self, buffer: &WeakModelHandle<Buffer>) {
+    fn unregister_buffer(&mut self, buffer: &WeakModel<Buffer>) {
         if let Ok(server) = self.server.as_running() {
-            if let Some(buffer) = server.registered_buffers.remove(&buffer.id()) {
+            if let Some(buffer) = server.registered_buffers.remove(&buffer.entity_id()) {
                 server
                     .lsp
                     .notify::<lsp::notification::DidCloseTextDocument>(
@@ -711,7 +741,7 @@ impl Copilot {
 
     pub fn completions<T>(
         &mut self,
-        buffer: &ModelHandle<Buffer>,
+        buffer: &Model<Buffer>,
         position: T,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Vec<Completion>>>
@@ -723,7 +753,7 @@ impl Copilot {
 
     pub fn completions_cycling<T>(
         &mut self,
-        buffer: &ModelHandle<Buffer>,
+        buffer: &Model<Buffer>,
         position: T,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Vec<Completion>>>
@@ -748,7 +778,7 @@ impl Copilot {
                 .request::<request::NotifyAccepted>(request::NotifyAcceptedParams {
                     uuid: completion.uuid.clone(),
                 });
-        cx.background().spawn(async move {
+        cx.background_executor().spawn(async move {
             request.await?;
             Ok(())
         })
@@ -772,7 +802,7 @@ impl Copilot {
                         .map(|completion| completion.uuid.clone())
                         .collect(),
                 });
-        cx.background().spawn(async move {
+        cx.background_executor().spawn(async move {
             request.await?;
             Ok(())
         })
@@ -780,7 +810,7 @@ impl Copilot {
 
     fn request_completions<R, T>(
         &mut self,
-        buffer: &ModelHandle<Buffer>,
+        buffer: &Model<Buffer>,
         position: T,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Vec<Completion>>>
@@ -799,7 +829,10 @@ impl Copilot {
             Err(error) => return Task::ready(Err(error)),
         };
         let lsp = server.lsp.clone();
-        let registered_buffer = server.registered_buffers.get_mut(&buffer.id()).unwrap();
+        let registered_buffer = server
+            .registered_buffers
+            .get_mut(&buffer.entity_id())
+            .unwrap();
         let snapshot = registered_buffer.report_changes(buffer, cx);
         let buffer = buffer.read(cx);
         let uri = registered_buffer.uri.clone();
@@ -812,7 +845,7 @@ impl Copilot {
             .map(|file| file.path().to_path_buf())
             .unwrap_or_default();
 
-        cx.foreground().spawn(async move {
+        cx.background_executor().spawn(async move {
             let (version, snapshot) = snapshot.await?;
             let result = lsp
                 .request::<R>(request::GetCompletionsParams {
@@ -869,7 +902,7 @@ impl Copilot {
         lsp_status: request::SignInStatus,
         cx: &mut ModelContext<Self>,
     ) {
-        self.buffers.retain(|buffer| buffer.is_upgradable(cx));
+        self.buffers.retain(|buffer| buffer.is_upgradable());
 
         if let Ok(server) = self.server.as_running() {
             match lsp_status {
@@ -878,20 +911,20 @@ impl Copilot {
                 | request::SignInStatus::AlreadySignedIn { .. } => {
                     server.sign_in_status = SignInStatus::Authorized;
                     for buffer in self.buffers.iter().cloned().collect::<Vec<_>>() {
-                        if let Some(buffer) = buffer.upgrade(cx) {
+                        if let Some(buffer) = buffer.upgrade() {
                             self.register_buffer(&buffer, cx);
                         }
                     }
                 }
                 request::SignInStatus::NotAuthorized { .. } => {
                     server.sign_in_status = SignInStatus::Unauthorized;
-                    for buffer in self.buffers.iter().copied().collect::<Vec<_>>() {
+                    for buffer in self.buffers.iter().cloned().collect::<Vec<_>>() {
                         self.unregister_buffer(&buffer);
                     }
                 }
                 request::SignInStatus::NotSignedIn => {
                     server.sign_in_status = SignInStatus::SignedOut;
-                    for buffer in self.buffers.iter().copied().collect::<Vec<_>>() {
+                    for buffer in self.buffers.iter().cloned().collect::<Vec<_>>() {
                         self.unregister_buffer(&buffer);
                     }
                 }
@@ -911,11 +944,11 @@ fn id_for_language(language: Option<&Arc<Language>>) -> String {
     }
 }
 
-fn uri_for_buffer(buffer: &ModelHandle<Buffer>, cx: &AppContext) -> lsp::Url {
+fn uri_for_buffer(buffer: &Model<Buffer>, cx: &AppContext) -> lsp::Url {
     if let Some(file) = buffer.read(cx).file().and_then(|file| file.as_local()) {
         lsp::Url::from_file_path(file.abs_path(cx)).unwrap()
     } else {
-        format!("buffer://{}", buffer.id()).parse().unwrap()
+        format!("buffer://{}", buffer.entity_id()).parse().unwrap()
     }
 }
 
@@ -994,15 +1027,16 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use gpui::{executor::Deterministic, TestAppContext};
+    use gpui::TestAppContext;
 
     #[gpui::test(iterations = 10)]
-    async fn test_buffer_management(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
-        deterministic.forbid_parking();
+    async fn test_buffer_management(cx: &mut TestAppContext) {
         let (copilot, mut lsp) = Copilot::fake(cx);
 
-        let buffer_1 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "Hello"));
-        let buffer_1_uri: lsp::Url = format!("buffer://{}", buffer_1.id()).parse().unwrap();
+        let buffer_1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "Hello"));
+        let buffer_1_uri: lsp::Url = format!("buffer://{}", buffer_1.entity_id().as_u64())
+            .parse()
+            .unwrap();
         copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_1, cx));
         assert_eq!(
             lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
@@ -1017,8 +1051,10 @@ mod tests {
             }
         );
 
-        let buffer_2 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "Goodbye"));
-        let buffer_2_uri: lsp::Url = format!("buffer://{}", buffer_2.id()).parse().unwrap();
+        let buffer_2 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "Goodbye"));
+        let buffer_2_uri: lsp::Url = format!("buffer://{}", buffer_2.entity_id().as_u64())
+            .parse()
+            .unwrap();
         copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_2, cx));
         assert_eq!(
             lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
@@ -1114,6 +1150,7 @@ mod tests {
             .update(cx, |copilot, cx| copilot.sign_in(cx))
             .await
             .unwrap();
+
         assert_eq!(
             lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
                 .await,
@@ -1138,7 +1175,6 @@ mod tests {
                 ),
             }
         );
-
         // Dropping a buffer causes it to be closed on the LSP side as well.
         cx.update(|_| drop(buffer_2));
         assert_eq!(

crates/copilot/src/sign_in.rs 🔗

@@ -1,18 +1,11 @@
 use crate::{request::PromptUserDeviceFlow, Copilot, Status};
 use gpui::{
-    elements::*,
-    geometry::rect::RectF,
-    platform::{WindowBounds, WindowKind, WindowOptions},
-    AnyElement, AnyViewHandle, AppContext, ClipboardItem, Element, Entity, View, ViewContext,
-    WindowHandle,
+    div, size, AppContext, Bounds, ClipboardItem, Element, GlobalPixels, InteractiveElement,
+    IntoElement, ParentElement, Point, Render, Styled, ViewContext, VisualContext, WindowBounds,
+    WindowHandle, WindowKind, WindowOptions,
 };
-use theme::ui::modal;
-
-#[derive(PartialEq, Eq, Debug, Clone)]
-struct CopyUserCode;
-
-#[derive(PartialEq, Eq, Debug, Clone)]
-struct OpenGithub;
+use theme::ActiveTheme;
+use ui::{prelude::*, Button, Icon, IconElement, Label};
 
 const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot";
 
@@ -26,14 +19,11 @@ pub fn init(cx: &mut AppContext) {
                 crate::Status::SigningIn { prompt } => {
                     if let Some(window) = verification_window.as_mut() {
                         let updated = window
-                            .root(cx)
-                            .map(|root| {
-                                root.update(cx, |verification, cx| {
-                                    verification.set_status(status.clone(), cx);
-                                    cx.activate_window();
-                                })
+                            .update(cx, |verification, cx| {
+                                verification.set_status(status.clone(), cx);
+                                cx.activate_window();
                             })
-                            .is_some();
+                            .is_ok();
                         if !updated {
                             verification_window = Some(create_copilot_auth_window(cx, &status));
                         }
@@ -43,18 +33,20 @@ pub fn init(cx: &mut AppContext) {
                 }
                 Status::Authorized | Status::Unauthorized => {
                     if let Some(window) = verification_window.as_ref() {
-                        if let Some(verification) = window.root(cx) {
-                            verification.update(cx, |verification, cx| {
+                        window
+                            .update(cx, |verification, cx| {
                                 verification.set_status(status, cx);
-                                cx.platform().activate(true);
+                                cx.activate(true);
                                 cx.activate_window();
-                            });
-                        }
+                            })
+                            .ok();
                     }
                 }
                 _ => {
                     if let Some(code_verification) = verification_window.take() {
-                        code_verification.update(cx, |cx| cx.remove_window());
+                        code_verification
+                            .update(cx, |_, cx| cx.remove_window())
+                            .ok();
                     }
                 }
             }
@@ -67,20 +59,21 @@ fn create_copilot_auth_window(
     cx: &mut AppContext,
     status: &Status,
 ) -> WindowHandle<CopilotCodeVerification> {
-    let window_size = theme::current(cx).copilot.modal.dimensions();
+    let window_size = size(GlobalPixels::from(280.), GlobalPixels::from(280.));
     let window_options = WindowOptions {
-        bounds: WindowBounds::Fixed(RectF::new(Default::default(), window_size)),
+        bounds: WindowBounds::Fixed(Bounds::new(Point::default(), window_size)),
         titlebar: None,
         center: true,
         focus: true,
         show: true,
-        kind: WindowKind::Normal,
+        kind: WindowKind::PopUp,
         is_movable: true,
-        screen: None,
+        display_id: None,
     };
-    cx.add_window(window_options, |_cx| {
-        CopilotCodeVerification::new(status.clone())
-    })
+    let window = cx.open_window(window_options, |cx| {
+        cx.new_view(|_| CopilotCodeVerification::new(status.clone()))
+    });
+    window
 }
 
 pub struct CopilotCodeVerification {
@@ -103,273 +96,116 @@ impl CopilotCodeVerification {
 
     fn render_device_code(
         data: &PromptUserDeviceFlow,
-        style: &theme::Copilot,
         cx: &mut ViewContext<Self>,
-    ) -> impl Element<Self> {
+    ) -> impl IntoElement {
         let copied = cx
             .read_from_clipboard()
             .map(|item| item.text() == &data.user_code)
             .unwrap_or(false);
-
-        let device_code_style = &style.auth.prompting.device_code;
-
-        MouseEventHandler::new::<Self, _>(0, cx, |state, _cx| {
-            Flex::row()
-                .with_child(
-                    Label::new(data.user_code.clone(), device_code_style.text.clone())
-                        .aligned()
-                        .contained()
-                        .with_style(device_code_style.left_container)
-                        .constrained()
-                        .with_width(device_code_style.left),
-                )
-                .with_child(
-                    Label::new(
-                        if copied { "Copied!" } else { "Copy" },
-                        device_code_style.cta.style_for(state).text.clone(),
-                    )
-                    .aligned()
-                    .contained()
-                    .with_style(*device_code_style.right_container.style_for(state))
-                    .constrained()
-                    .with_width(device_code_style.right),
-                )
-                .contained()
-                .with_style(device_code_style.cta.style_for(state).container)
-        })
-        .on_click(gpui::platform::MouseButton::Left, {
-            let user_code = data.user_code.clone();
-            move |_, _, cx| {
-                cx.platform()
-                    .write_to_clipboard(ClipboardItem::new(user_code.clone()));
-                cx.notify();
-            }
-        })
-        .with_cursor_style(gpui::platform::CursorStyle::PointingHand)
+        h_stack()
+            .cursor_pointer()
+            .justify_between()
+            .on_mouse_down(gpui::MouseButton::Left, {
+                let user_code = data.user_code.clone();
+                move |_, cx| {
+                    cx.write_to_clipboard(ClipboardItem::new(user_code.clone()));
+                    cx.notify();
+                }
+            })
+            .child(Label::new(data.user_code.clone()))
+            .child(div())
+            .child(Label::new(if copied { "Copied!" } else { "Copy" }))
     }
 
     fn render_prompting_modal(
         connect_clicked: bool,
         data: &PromptUserDeviceFlow,
-        style: &theme::Copilot,
         cx: &mut ViewContext<Self>,
-    ) -> AnyElement<Self> {
-        enum ConnectButton {}
-
-        Flex::column()
-            .with_child(
-                Flex::column()
-                    .with_children([
-                        Label::new(
-                            "Enable Copilot by connecting",
-                            style.auth.prompting.subheading.text.clone(),
-                        )
-                        .aligned(),
-                        Label::new(
-                            "your existing license.",
-                            style.auth.prompting.subheading.text.clone(),
-                        )
-                        .aligned(),
-                    ])
-                    .align_children_center()
-                    .contained()
-                    .with_style(style.auth.prompting.subheading.container),
-            )
-            .with_child(Self::render_device_code(data, &style, cx))
-            .with_child(
-                Flex::column()
-                    .with_children([
-                        Label::new(
-                            "Paste this code into GitHub after",
-                            style.auth.prompting.hint.text.clone(),
-                        )
-                        .aligned(),
-                        Label::new(
-                            "clicking the button below.",
-                            style.auth.prompting.hint.text.clone(),
-                        )
-                        .aligned(),
-                    ])
-                    .align_children_center()
-                    .contained()
-                    .with_style(style.auth.prompting.hint.container.clone()),
-            )
-            .with_child(theme::ui::cta_button::<ConnectButton, _, _, _>(
-                if connect_clicked {
-                    "Waiting for connection..."
-                } else {
-                    "Connect to GitHub"
-                },
-                style.auth.content_width,
-                &style.auth.cta_button,
-                cx,
-                {
-                    let verification_uri = data.verification_uri.clone();
-                    move |_, verification, cx| {
-                        cx.platform().open_url(&verification_uri);
-                        verification.connect_clicked = true;
-                    }
-                },
+    ) -> impl Element {
+        let connect_button_label = if connect_clicked {
+            "Waiting for connection..."
+        } else {
+            "Connect to Github"
+        };
+        v_stack()
+            .flex_1()
+            .items_center()
+            .justify_between()
+            .w_full()
+            .child(Label::new(
+                "Enable Copilot by connecting your existing license",
             ))
-            .align_children_center()
-            .into_any()
-    }
-
-    fn render_enabled_modal(
-        style: &theme::Copilot,
-        cx: &mut ViewContext<Self>,
-    ) -> AnyElement<Self> {
-        enum DoneButton {}
-
-        let enabled_style = &style.auth.authorized;
-        Flex::column()
-            .with_child(
-                Label::new("Copilot Enabled!", enabled_style.subheading.text.clone())
-                    .contained()
-                    .with_style(enabled_style.subheading.container)
-                    .aligned(),
+            .child(Self::render_device_code(data, cx))
+            .child(
+                Label::new("Paste this code into GitHub after clicking the button below.")
+                    .size(ui::LabelSize::Small),
             )
-            .with_child(
-                Flex::column()
-                    .with_children([
-                        Label::new(
-                            "You can update your settings or",
-                            enabled_style.hint.text.clone(),
-                        )
-                        .aligned(),
-                        Label::new(
-                            "sign out from the Copilot menu in",
-                            enabled_style.hint.text.clone(),
-                        )
-                        .aligned(),
-                        Label::new("the status bar.", enabled_style.hint.text.clone()).aligned(),
-                    ])
-                    .align_children_center()
-                    .contained()
-                    .with_style(enabled_style.hint.container),
+            .child(
+                Button::new("connect-button", connect_button_label).on_click({
+                    let verification_uri = data.verification_uri.clone();
+                    cx.listener(move |this, _, cx| {
+                        cx.open_url(&verification_uri);
+                        this.connect_clicked = true;
+                    })
+                }),
             )
-            .with_child(theme::ui::cta_button::<DoneButton, _, _, _>(
-                "Done",
-                style.auth.content_width,
-                &style.auth.cta_button,
-                cx,
-                |_, _, cx| cx.remove_window(),
+    }
+    fn render_enabled_modal() -> impl Element {
+        v_stack()
+            .child(Label::new("Copilot Enabled!"))
+            .child(Label::new(
+                "You can update your settings or sign out from the Copilot menu in the status bar.",
             ))
-            .align_children_center()
-            .into_any()
+            .child(
+                Button::new("copilot-enabled-done-button", "Done")
+                    .on_click(|_, cx| cx.remove_window()),
+            )
     }
 
-    fn render_unauthorized_modal(
-        style: &theme::Copilot,
-        cx: &mut ViewContext<Self>,
-    ) -> AnyElement<Self> {
-        let unauthorized_style = &style.auth.not_authorized;
-
-        Flex::column()
-            .with_child(
-                Flex::column()
-                    .with_children([
-                        Label::new(
-                            "Enable Copilot by connecting",
-                            unauthorized_style.subheading.text.clone(),
-                        )
-                        .aligned(),
-                        Label::new(
-                            "your existing license.",
-                            unauthorized_style.subheading.text.clone(),
-                        )
-                        .aligned(),
-                    ])
-                    .align_children_center()
-                    .contained()
-                    .with_style(unauthorized_style.subheading.container),
-            )
-            .with_child(
-                Flex::column()
-                    .with_children([
-                        Label::new(
-                            "You must have an active copilot",
-                            unauthorized_style.warning.text.clone(),
-                        )
-                        .aligned(),
-                        Label::new(
-                            "license to use it in Zed.",
-                            unauthorized_style.warning.text.clone(),
-                        )
-                        .aligned(),
-                    ])
-                    .align_children_center()
-                    .contained()
-                    .with_style(unauthorized_style.warning.container),
+    fn render_unauthorized_modal() -> impl Element {
+        v_stack()
+            .child(Label::new(
+                "Enable Copilot by connecting your existing license.",
+            ))
+            .child(
+                Label::new("You must have an active Copilot license to use it in Zed.")
+                    .color(Color::Warning),
             )
-            .with_child(theme::ui::cta_button::<Self, _, _, _>(
-                "Subscribe on GitHub",
-                style.auth.content_width,
-                &style.auth.cta_button,
-                cx,
-                |_, _, cx| {
+            .child(
+                Button::new("copilot-subscribe-button", "Subscibe on Github").on_click(|_, cx| {
                     cx.remove_window();
-                    cx.platform().open_url(COPILOT_SIGN_UP_URL)
-                },
-            ))
-            .align_children_center()
-            .into_any()
+                    cx.open_url(COPILOT_SIGN_UP_URL)
+                }),
+            )
     }
 }
 
-impl Entity for CopilotCodeVerification {
-    type Event = ();
-}
-
-impl View for CopilotCodeVerification {
-    fn ui_name() -> &'static str {
-        "CopilotCodeVerification"
-    }
-
-    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
-        cx.notify()
-    }
-
-    fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
-        cx.notify()
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        enum ConnectModal {}
-
-        let style = theme::current(cx).clone();
-
-        modal::<ConnectModal, _, _, _, _>(
-            "Connect Copilot to Zed",
-            &style.copilot.modal,
-            cx,
-            |cx| {
-                Flex::column()
-                    .with_children([
-                        theme::ui::icon(&style.copilot.auth.header).into_any(),
-                        match &self.status {
-                            Status::SigningIn {
-                                prompt: Some(prompt),
-                            } => Self::render_prompting_modal(
-                                self.connect_clicked,
-                                &prompt,
-                                &style.copilot,
-                                cx,
-                            ),
-                            Status::Unauthorized => {
-                                self.connect_clicked = false;
-                                Self::render_unauthorized_modal(&style.copilot, cx)
-                            }
-                            Status::Authorized => {
-                                self.connect_clicked = false;
-                                Self::render_enabled_modal(&style.copilot, cx)
-                            }
-                            _ => Empty::new().into_any(),
-                        },
-                    ])
-                    .align_children_center()
-            },
-        )
-        .into_any()
+impl Render for CopilotCodeVerification {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        let prompt = match &self.status {
+            Status::SigningIn {
+                prompt: Some(prompt),
+            } => Self::render_prompting_modal(self.connect_clicked, &prompt, cx).into_any_element(),
+            Status::Unauthorized => {
+                self.connect_clicked = false;
+                Self::render_unauthorized_modal().into_any_element()
+            }
+            Status::Authorized => {
+                self.connect_clicked = false;
+                Self::render_enabled_modal().into_any_element()
+            }
+            _ => div().into_any_element(),
+        };
+        div()
+            .id("copilot code verification")
+            .flex()
+            .flex_col()
+            .size_full()
+            .items_center()
+            .p_10()
+            .bg(cx.theme().colors().element_background)
+            .child(ui::Label::new("Connect Copilot to Zed"))
+            .child(IconElement::new(Icon::ZedXCopilot))
+            .child(prompt)
     }
 }

crates/copilot2/Cargo.toml 🔗

@@ -1,51 +0,0 @@
-[package]
-name = "copilot2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/copilot2.rs"
-doctest = false
-
-[features]
-test-support = [
-    "collections/test-support",
-    "gpui/test-support",
-    "language/test-support",
-    "lsp/test-support",
-    "settings/test-support",
-    "util/test-support",
-]
-
-[dependencies]
-collections = { path = "../collections" }
-# context_menu = { path = "../context_menu" }
-gpui = { package = "gpui2", path = "../gpui2" }
-language = { package = "language2", path = "../language2" }
-settings = { package = "settings2", path = "../settings2" }
-theme = { package = "theme2", path = "../theme2" }
-lsp = { package = "lsp2", path = "../lsp2" }
-node_runtime = { path = "../node_runtime"}
-util = { path = "../util" }
-ui = { package = "ui2", path = "../ui2" }
-async-compression.workspace = true
-async-tar = "0.4.2"
-anyhow.workspace = true
-log.workspace = true
-serde.workspace = true
-serde_derive.workspace = true
-smol.workspace = true
-futures.workspace = true
-parking_lot.workspace = true
-
-[dev-dependencies]
-clock = { path = "../clock" }
-collections = { path = "../collections", features = ["test-support"] }
-fs = { path = "../fs", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-language = { package = "language2", path = "../language2", features = ["test-support"] }
-lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
-rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
-settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
-util = { path = "../util", features = ["test-support"] }

crates/copilot2/src/copilot2.rs 🔗

@@ -1,1253 +0,0 @@
-pub mod request;
-mod sign_in;
-
-use anyhow::{anyhow, Context as _, Result};
-use async_compression::futures::bufread::GzipDecoder;
-use async_tar::Archive;
-use collections::{HashMap, HashSet};
-use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt};
-use gpui::{
-    actions, AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter, Model,
-    ModelContext, Task, WeakModel,
-};
-use language::{
-    language_settings::{all_language_settings, language_settings},
-    point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, Language,
-    LanguageServerName, PointUtf16, ToPointUtf16,
-};
-use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId};
-use node_runtime::NodeRuntime;
-use parking_lot::Mutex;
-use request::StatusNotification;
-use settings::SettingsStore;
-use smol::{fs, io::BufReader, stream::StreamExt};
-use std::{
-    any::TypeId,
-    ffi::OsString,
-    mem,
-    ops::Range,
-    path::{Path, PathBuf},
-    sync::Arc,
-};
-use util::{
-    fs::remove_matching, github::latest_github_release, http::HttpClient, paths, ResultExt,
-};
-
-actions!(
-    copilot,
-    [
-        Suggest,
-        NextSuggestion,
-        PreviousSuggestion,
-        Reinstall,
-        SignIn,
-        SignOut
-    ]
-);
-
-pub fn init(
-    new_server_id: LanguageServerId,
-    http: Arc<dyn HttpClient>,
-    node_runtime: Arc<dyn NodeRuntime>,
-    cx: &mut AppContext,
-) {
-    let copilot = cx.new_model({
-        let node_runtime = node_runtime.clone();
-        move |cx| Copilot::start(new_server_id, http, node_runtime, cx)
-    });
-    cx.set_global(copilot.clone());
-    cx.observe(&copilot, |handle, cx| {
-        let copilot_action_types = [
-            TypeId::of::<Suggest>(),
-            TypeId::of::<NextSuggestion>(),
-            TypeId::of::<PreviousSuggestion>(),
-            TypeId::of::<Reinstall>(),
-        ];
-        let copilot_auth_action_types = [TypeId::of::<SignOut>()];
-        let copilot_no_auth_action_types = [TypeId::of::<SignIn>()];
-        let status = handle.read(cx).status();
-        let filter = cx.default_global::<collections::CommandPaletteFilter>();
-
-        match status {
-            Status::Disabled => {
-                filter.hidden_action_types.extend(copilot_action_types);
-                filter.hidden_action_types.extend(copilot_auth_action_types);
-                filter
-                    .hidden_action_types
-                    .extend(copilot_no_auth_action_types);
-            }
-            Status::Authorized => {
-                filter
-                    .hidden_action_types
-                    .extend(copilot_no_auth_action_types);
-                for type_id in copilot_action_types
-                    .iter()
-                    .chain(&copilot_auth_action_types)
-                {
-                    filter.hidden_action_types.remove(type_id);
-                }
-            }
-            _ => {
-                filter.hidden_action_types.extend(copilot_action_types);
-                filter.hidden_action_types.extend(copilot_auth_action_types);
-                for type_id in &copilot_no_auth_action_types {
-                    filter.hidden_action_types.remove(type_id);
-                }
-            }
-        }
-    })
-    .detach();
-
-    sign_in::init(cx);
-    cx.on_action(|_: &SignIn, cx| {
-        if let Some(copilot) = Copilot::global(cx) {
-            copilot
-                .update(cx, |copilot, cx| copilot.sign_in(cx))
-                .detach_and_log_err(cx);
-        }
-    });
-    cx.on_action(|_: &SignOut, cx| {
-        if let Some(copilot) = Copilot::global(cx) {
-            copilot
-                .update(cx, |copilot, cx| copilot.sign_out(cx))
-                .detach_and_log_err(cx);
-        }
-    });
-    cx.on_action(|_: &Reinstall, cx| {
-        if let Some(copilot) = Copilot::global(cx) {
-            copilot
-                .update(cx, |copilot, cx| copilot.reinstall(cx))
-                .detach();
-        }
-    });
-}
-
-enum CopilotServer {
-    Disabled,
-    Starting { task: Shared<Task<()>> },
-    Error(Arc<str>),
-    Running(RunningCopilotServer),
-}
-
-impl CopilotServer {
-    fn as_authenticated(&mut self) -> Result<&mut RunningCopilotServer> {
-        let server = self.as_running()?;
-        if matches!(server.sign_in_status, SignInStatus::Authorized { .. }) {
-            Ok(server)
-        } else {
-            Err(anyhow!("must sign in before using copilot"))
-        }
-    }
-
-    fn as_running(&mut self) -> Result<&mut RunningCopilotServer> {
-        match self {
-            CopilotServer::Starting { .. } => Err(anyhow!("copilot is still starting")),
-            CopilotServer::Disabled => Err(anyhow!("copilot is disabled")),
-            CopilotServer::Error(error) => Err(anyhow!(
-                "copilot was not started because of an error: {}",
-                error
-            )),
-            CopilotServer::Running(server) => Ok(server),
-        }
-    }
-}
-
-struct RunningCopilotServer {
-    name: LanguageServerName,
-    lsp: Arc<LanguageServer>,
-    sign_in_status: SignInStatus,
-    registered_buffers: HashMap<EntityId, RegisteredBuffer>,
-}
-
-#[derive(Clone, Debug)]
-enum SignInStatus {
-    Authorized,
-    Unauthorized,
-    SigningIn {
-        prompt: Option<request::PromptUserDeviceFlow>,
-        task: Shared<Task<Result<(), Arc<anyhow::Error>>>>,
-    },
-    SignedOut,
-}
-
-#[derive(Debug, Clone)]
-pub enum Status {
-    Starting {
-        task: Shared<Task<()>>,
-    },
-    Error(Arc<str>),
-    Disabled,
-    SignedOut,
-    SigningIn {
-        prompt: Option<request::PromptUserDeviceFlow>,
-    },
-    Unauthorized,
-    Authorized,
-}
-
-impl Status {
-    pub fn is_authorized(&self) -> bool {
-        matches!(self, Status::Authorized)
-    }
-}
-
-struct RegisteredBuffer {
-    uri: lsp::Url,
-    language_id: String,
-    snapshot: BufferSnapshot,
-    snapshot_version: i32,
-    _subscriptions: [gpui::Subscription; 2],
-    pending_buffer_change: Task<Option<()>>,
-}
-
-impl RegisteredBuffer {
-    fn report_changes(
-        &mut self,
-        buffer: &Model<Buffer>,
-        cx: &mut ModelContext<Copilot>,
-    ) -> oneshot::Receiver<(i32, BufferSnapshot)> {
-        let (done_tx, done_rx) = oneshot::channel();
-
-        if buffer.read(cx).version() == self.snapshot.version {
-            let _ = done_tx.send((self.snapshot_version, self.snapshot.clone()));
-        } else {
-            let buffer = buffer.downgrade();
-            let id = buffer.entity_id();
-            let prev_pending_change =
-                mem::replace(&mut self.pending_buffer_change, Task::ready(None));
-            self.pending_buffer_change = cx.spawn(move |copilot, mut cx| async move {
-                prev_pending_change.await;
-
-                let old_version = copilot
-                    .update(&mut cx, |copilot, _| {
-                        let server = copilot.server.as_authenticated().log_err()?;
-                        let buffer = server.registered_buffers.get_mut(&id)?;
-                        Some(buffer.snapshot.version.clone())
-                    })
-                    .ok()??;
-                let new_snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot()).ok()?;
-
-                let content_changes = cx
-                    .background_executor()
-                    .spawn({
-                        let new_snapshot = new_snapshot.clone();
-                        async move {
-                            new_snapshot
-                                .edits_since::<(PointUtf16, usize)>(&old_version)
-                                .map(|edit| {
-                                    let edit_start = edit.new.start.0;
-                                    let edit_end = edit_start + (edit.old.end.0 - edit.old.start.0);
-                                    let new_text = new_snapshot
-                                        .text_for_range(edit.new.start.1..edit.new.end.1)
-                                        .collect();
-                                    lsp::TextDocumentContentChangeEvent {
-                                        range: Some(lsp::Range::new(
-                                            point_to_lsp(edit_start),
-                                            point_to_lsp(edit_end),
-                                        )),
-                                        range_length: None,
-                                        text: new_text,
-                                    }
-                                })
-                                .collect::<Vec<_>>()
-                        }
-                    })
-                    .await;
-
-                copilot
-                    .update(&mut cx, |copilot, _| {
-                        let server = copilot.server.as_authenticated().log_err()?;
-                        let buffer = server.registered_buffers.get_mut(&id)?;
-                        if !content_changes.is_empty() {
-                            buffer.snapshot_version += 1;
-                            buffer.snapshot = new_snapshot;
-                            server
-                                .lsp
-                                .notify::<lsp::notification::DidChangeTextDocument>(
-                                    lsp::DidChangeTextDocumentParams {
-                                        text_document: lsp::VersionedTextDocumentIdentifier::new(
-                                            buffer.uri.clone(),
-                                            buffer.snapshot_version,
-                                        ),
-                                        content_changes,
-                                    },
-                                )
-                                .log_err();
-                        }
-                        let _ = done_tx.send((buffer.snapshot_version, buffer.snapshot.clone()));
-                        Some(())
-                    })
-                    .ok()?;
-
-                Some(())
-            });
-        }
-
-        done_rx
-    }
-}
-
-#[derive(Debug)]
-pub struct Completion {
-    pub uuid: String,
-    pub range: Range<Anchor>,
-    pub text: String,
-}
-
-pub struct Copilot {
-    http: Arc<dyn HttpClient>,
-    node_runtime: Arc<dyn NodeRuntime>,
-    server: CopilotServer,
-    buffers: HashSet<WeakModel<Buffer>>,
-    server_id: LanguageServerId,
-    _subscription: gpui::Subscription,
-}
-
-pub enum Event {
-    CopilotLanguageServerStarted,
-}
-
-impl EventEmitter<Event> for Copilot {}
-
-impl Copilot {
-    pub fn global(cx: &AppContext) -> Option<Model<Self>> {
-        if cx.has_global::<Model<Self>>() {
-            Some(cx.global::<Model<Self>>().clone())
-        } else {
-            None
-        }
-    }
-
-    fn start(
-        new_server_id: LanguageServerId,
-        http: Arc<dyn HttpClient>,
-        node_runtime: Arc<dyn NodeRuntime>,
-        cx: &mut ModelContext<Self>,
-    ) -> Self {
-        let mut this = Self {
-            server_id: new_server_id,
-            http,
-            node_runtime,
-            server: CopilotServer::Disabled,
-            buffers: Default::default(),
-            _subscription: cx.on_app_quit(Self::shutdown_language_server),
-        };
-        this.enable_or_disable_copilot(cx);
-        cx.observe_global::<SettingsStore>(move |this, cx| this.enable_or_disable_copilot(cx))
-            .detach();
-        this
-    }
-
-    fn shutdown_language_server(
-        &mut self,
-        _cx: &mut ModelContext<Self>,
-    ) -> impl Future<Output = ()> {
-        let shutdown = match mem::replace(&mut self.server, CopilotServer::Disabled) {
-            CopilotServer::Running(server) => Some(Box::pin(async move { server.lsp.shutdown() })),
-            _ => None,
-        };
-
-        async move {
-            if let Some(shutdown) = shutdown {
-                shutdown.await;
-            }
-        }
-    }
-
-    fn enable_or_disable_copilot(&mut self, cx: &mut ModelContext<Self>) {
-        let server_id = self.server_id;
-        let http = self.http.clone();
-        let node_runtime = self.node_runtime.clone();
-        if all_language_settings(None, cx).copilot_enabled(None, None) {
-            if matches!(self.server, CopilotServer::Disabled) {
-                let start_task = cx
-                    .spawn(move |this, cx| {
-                        Self::start_language_server(server_id, http, node_runtime, this, cx)
-                    })
-                    .shared();
-                self.server = CopilotServer::Starting { task: start_task };
-                cx.notify();
-            }
-        } else {
-            self.server = CopilotServer::Disabled;
-            cx.notify();
-        }
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn fake(cx: &mut gpui::TestAppContext) -> (Model<Self>, lsp::FakeLanguageServer) {
-        use node_runtime::FakeNodeRuntime;
-
-        let (server, fake_server) =
-            LanguageServer::fake("copilot".into(), Default::default(), cx.to_async());
-        let http = util::http::FakeHttpClient::create(|_| async { unreachable!() });
-        let node_runtime = FakeNodeRuntime::new();
-        let this = cx.new_model(|cx| Self {
-            server_id: LanguageServerId(0),
-            http: http.clone(),
-            node_runtime,
-            server: CopilotServer::Running(RunningCopilotServer {
-                name: LanguageServerName(Arc::from("copilot")),
-                lsp: Arc::new(server),
-                sign_in_status: SignInStatus::Authorized,
-                registered_buffers: Default::default(),
-            }),
-            _subscription: cx.on_app_quit(Self::shutdown_language_server),
-            buffers: Default::default(),
-        });
-        (this, fake_server)
-    }
-
-    fn start_language_server(
-        new_server_id: LanguageServerId,
-        http: Arc<dyn HttpClient>,
-        node_runtime: Arc<dyn NodeRuntime>,
-        this: WeakModel<Self>,
-        mut cx: AsyncAppContext,
-    ) -> impl Future<Output = ()> {
-        async move {
-            let start_language_server = async {
-                let server_path = get_copilot_lsp(http).await?;
-                let node_path = node_runtime.binary_path().await?;
-                let arguments: Vec<OsString> = vec![server_path.into(), "--stdio".into()];
-                let binary = LanguageServerBinary {
-                    path: node_path,
-                    arguments,
-                };
-
-                let server = LanguageServer::new(
-                    Arc::new(Mutex::new(None)),
-                    new_server_id,
-                    binary,
-                    Path::new("/"),
-                    None,
-                    cx.clone(),
-                )?;
-
-                server
-                    .on_notification::<StatusNotification, _>(
-                        |_, _| { /* Silence the notification */ },
-                    )
-                    .detach();
-
-                let server = server.initialize(Default::default()).await?;
-
-                let status = server
-                    .request::<request::CheckStatus>(request::CheckStatusParams {
-                        local_checks_only: false,
-                    })
-                    .await?;
-
-                server
-                    .request::<request::SetEditorInfo>(request::SetEditorInfoParams {
-                        editor_info: request::EditorInfo {
-                            name: "zed".into(),
-                            version: env!("CARGO_PKG_VERSION").into(),
-                        },
-                        editor_plugin_info: request::EditorPluginInfo {
-                            name: "zed-copilot".into(),
-                            version: "0.0.1".into(),
-                        },
-                    })
-                    .await?;
-
-                anyhow::Ok((server, status))
-            };
-
-            let server = start_language_server.await;
-            this.update(&mut cx, |this, cx| {
-                cx.notify();
-                match server {
-                    Ok((server, status)) => {
-                        this.server = CopilotServer::Running(RunningCopilotServer {
-                            name: LanguageServerName(Arc::from("copilot")),
-                            lsp: server,
-                            sign_in_status: SignInStatus::SignedOut,
-                            registered_buffers: Default::default(),
-                        });
-                        cx.emit(Event::CopilotLanguageServerStarted);
-                        this.update_sign_in_status(status, cx);
-                    }
-                    Err(error) => {
-                        this.server = CopilotServer::Error(error.to_string().into());
-                        cx.notify()
-                    }
-                }
-            })
-            .ok();
-        }
-    }
-
-    pub fn sign_in(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
-        if let CopilotServer::Running(server) = &mut self.server {
-            let task = match &server.sign_in_status {
-                SignInStatus::Authorized { .. } => Task::ready(Ok(())).shared(),
-                SignInStatus::SigningIn { task, .. } => {
-                    cx.notify();
-                    task.clone()
-                }
-                SignInStatus::SignedOut | SignInStatus::Unauthorized { .. } => {
-                    let lsp = server.lsp.clone();
-                    let task = cx
-                        .spawn(|this, mut cx| async move {
-                            let sign_in = async {
-                                let sign_in = lsp
-                                    .request::<request::SignInInitiate>(
-                                        request::SignInInitiateParams {},
-                                    )
-                                    .await?;
-                                match sign_in {
-                                    request::SignInInitiateResult::AlreadySignedIn { user } => {
-                                        Ok(request::SignInStatus::Ok { user })
-                                    }
-                                    request::SignInInitiateResult::PromptUserDeviceFlow(flow) => {
-                                        this.update(&mut cx, |this, cx| {
-                                            if let CopilotServer::Running(RunningCopilotServer {
-                                                sign_in_status: status,
-                                                ..
-                                            }) = &mut this.server
-                                            {
-                                                if let SignInStatus::SigningIn {
-                                                    prompt: prompt_flow,
-                                                    ..
-                                                } = status
-                                                {
-                                                    *prompt_flow = Some(flow.clone());
-                                                    cx.notify();
-                                                }
-                                            }
-                                        })?;
-                                        let response = lsp
-                                            .request::<request::SignInConfirm>(
-                                                request::SignInConfirmParams {
-                                                    user_code: flow.user_code,
-                                                },
-                                            )
-                                            .await?;
-                                        Ok(response)
-                                    }
-                                }
-                            };
-
-                            let sign_in = sign_in.await;
-                            this.update(&mut cx, |this, cx| match sign_in {
-                                Ok(status) => {
-                                    this.update_sign_in_status(status, cx);
-                                    Ok(())
-                                }
-                                Err(error) => {
-                                    this.update_sign_in_status(
-                                        request::SignInStatus::NotSignedIn,
-                                        cx,
-                                    );
-                                    Err(Arc::new(error))
-                                }
-                            })?
-                        })
-                        .shared();
-                    server.sign_in_status = SignInStatus::SigningIn {
-                        prompt: None,
-                        task: task.clone(),
-                    };
-                    cx.notify();
-                    task
-                }
-            };
-
-            cx.background_executor()
-                .spawn(task.map_err(|err| anyhow!("{:?}", err)))
-        } else {
-            // If we're downloading, wait until download is finished
-            // If we're in a stuck state, display to the user
-            Task::ready(Err(anyhow!("copilot hasn't started yet")))
-        }
-    }
-
-    fn sign_out(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
-        self.update_sign_in_status(request::SignInStatus::NotSignedIn, cx);
-        if let CopilotServer::Running(RunningCopilotServer { lsp: server, .. }) = &self.server {
-            let server = server.clone();
-            cx.background_executor().spawn(async move {
-                server
-                    .request::<request::SignOut>(request::SignOutParams {})
-                    .await?;
-                anyhow::Ok(())
-            })
-        } else {
-            Task::ready(Err(anyhow!("copilot hasn't started yet")))
-        }
-    }
-
-    pub fn reinstall(&mut self, cx: &mut ModelContext<Self>) -> Task<()> {
-        let start_task = cx
-            .spawn({
-                let http = self.http.clone();
-                let node_runtime = self.node_runtime.clone();
-                let server_id = self.server_id;
-                move |this, cx| async move {
-                    clear_copilot_dir().await;
-                    Self::start_language_server(server_id, http, node_runtime, this, cx).await
-                }
-            })
-            .shared();
-
-        self.server = CopilotServer::Starting {
-            task: start_task.clone(),
-        };
-
-        cx.notify();
-
-        cx.background_executor().spawn(start_task)
-    }
-
-    pub fn language_server(&self) -> Option<(&LanguageServerName, &Arc<LanguageServer>)> {
-        if let CopilotServer::Running(server) = &self.server {
-            Some((&server.name, &server.lsp))
-        } else {
-            None
-        }
-    }
-
-    pub fn register_buffer(&mut self, buffer: &Model<Buffer>, cx: &mut ModelContext<Self>) {
-        let weak_buffer = buffer.downgrade();
-        self.buffers.insert(weak_buffer.clone());
-
-        if let CopilotServer::Running(RunningCopilotServer {
-            lsp: server,
-            sign_in_status: status,
-            registered_buffers,
-            ..
-        }) = &mut self.server
-        {
-            if !matches!(status, SignInStatus::Authorized { .. }) {
-                return;
-            }
-
-            registered_buffers
-                .entry(buffer.entity_id())
-                .or_insert_with(|| {
-                    let uri: lsp::Url = uri_for_buffer(buffer, cx);
-                    let language_id = id_for_language(buffer.read(cx).language());
-                    let snapshot = buffer.read(cx).snapshot();
-                    server
-                        .notify::<lsp::notification::DidOpenTextDocument>(
-                            lsp::DidOpenTextDocumentParams {
-                                text_document: lsp::TextDocumentItem {
-                                    uri: uri.clone(),
-                                    language_id: language_id.clone(),
-                                    version: 0,
-                                    text: snapshot.text(),
-                                },
-                            },
-                        )
-                        .log_err();
-
-                    RegisteredBuffer {
-                        uri,
-                        language_id,
-                        snapshot,
-                        snapshot_version: 0,
-                        pending_buffer_change: Task::ready(Some(())),
-                        _subscriptions: [
-                            cx.subscribe(buffer, |this, buffer, event, cx| {
-                                this.handle_buffer_event(buffer, event, cx).log_err();
-                            }),
-                            cx.observe_release(buffer, move |this, _buffer, _cx| {
-                                this.buffers.remove(&weak_buffer);
-                                this.unregister_buffer(&weak_buffer);
-                            }),
-                        ],
-                    }
-                });
-        }
-    }
-
-    fn handle_buffer_event(
-        &mut self,
-        buffer: Model<Buffer>,
-        event: &language::Event,
-        cx: &mut ModelContext<Self>,
-    ) -> Result<()> {
-        if let Ok(server) = self.server.as_running() {
-            if let Some(registered_buffer) = server.registered_buffers.get_mut(&buffer.entity_id())
-            {
-                match event {
-                    language::Event::Edited => {
-                        let _ = registered_buffer.report_changes(&buffer, cx);
-                    }
-                    language::Event::Saved => {
-                        server
-                            .lsp
-                            .notify::<lsp::notification::DidSaveTextDocument>(
-                                lsp::DidSaveTextDocumentParams {
-                                    text_document: lsp::TextDocumentIdentifier::new(
-                                        registered_buffer.uri.clone(),
-                                    ),
-                                    text: None,
-                                },
-                            )?;
-                    }
-                    language::Event::FileHandleChanged | language::Event::LanguageChanged => {
-                        let new_language_id = id_for_language(buffer.read(cx).language());
-                        let new_uri = uri_for_buffer(&buffer, cx);
-                        if new_uri != registered_buffer.uri
-                            || new_language_id != registered_buffer.language_id
-                        {
-                            let old_uri = mem::replace(&mut registered_buffer.uri, new_uri);
-                            registered_buffer.language_id = new_language_id;
-                            server
-                                .lsp
-                                .notify::<lsp::notification::DidCloseTextDocument>(
-                                    lsp::DidCloseTextDocumentParams {
-                                        text_document: lsp::TextDocumentIdentifier::new(old_uri),
-                                    },
-                                )?;
-                            server
-                                .lsp
-                                .notify::<lsp::notification::DidOpenTextDocument>(
-                                    lsp::DidOpenTextDocumentParams {
-                                        text_document: lsp::TextDocumentItem::new(
-                                            registered_buffer.uri.clone(),
-                                            registered_buffer.language_id.clone(),
-                                            registered_buffer.snapshot_version,
-                                            registered_buffer.snapshot.text(),
-                                        ),
-                                    },
-                                )?;
-                        }
-                    }
-                    _ => {}
-                }
-            }
-        }
-
-        Ok(())
-    }
-
-    fn unregister_buffer(&mut self, buffer: &WeakModel<Buffer>) {
-        if let Ok(server) = self.server.as_running() {
-            if let Some(buffer) = server.registered_buffers.remove(&buffer.entity_id()) {
-                server
-                    .lsp
-                    .notify::<lsp::notification::DidCloseTextDocument>(
-                        lsp::DidCloseTextDocumentParams {
-                            text_document: lsp::TextDocumentIdentifier::new(buffer.uri),
-                        },
-                    )
-                    .log_err();
-            }
-        }
-    }
-
-    pub fn completions<T>(
-        &mut self,
-        buffer: &Model<Buffer>,
-        position: T,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<Completion>>>
-    where
-        T: ToPointUtf16,
-    {
-        self.request_completions::<request::GetCompletions, _>(buffer, position, cx)
-    }
-
-    pub fn completions_cycling<T>(
-        &mut self,
-        buffer: &Model<Buffer>,
-        position: T,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<Completion>>>
-    where
-        T: ToPointUtf16,
-    {
-        self.request_completions::<request::GetCompletionsCycling, _>(buffer, position, cx)
-    }
-
-    pub fn accept_completion(
-        &mut self,
-        completion: &Completion,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        let server = match self.server.as_authenticated() {
-            Ok(server) => server,
-            Err(error) => return Task::ready(Err(error)),
-        };
-        let request =
-            server
-                .lsp
-                .request::<request::NotifyAccepted>(request::NotifyAcceptedParams {
-                    uuid: completion.uuid.clone(),
-                });
-        cx.background_executor().spawn(async move {
-            request.await?;
-            Ok(())
-        })
-    }
-
-    pub fn discard_completions(
-        &mut self,
-        completions: &[Completion],
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        let server = match self.server.as_authenticated() {
-            Ok(server) => server,
-            Err(error) => return Task::ready(Err(error)),
-        };
-        let request =
-            server
-                .lsp
-                .request::<request::NotifyRejected>(request::NotifyRejectedParams {
-                    uuids: completions
-                        .iter()
-                        .map(|completion| completion.uuid.clone())
-                        .collect(),
-                });
-        cx.background_executor().spawn(async move {
-            request.await?;
-            Ok(())
-        })
-    }
-
-    fn request_completions<R, T>(
-        &mut self,
-        buffer: &Model<Buffer>,
-        position: T,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<Completion>>>
-    where
-        R: 'static
-            + lsp::request::Request<
-                Params = request::GetCompletionsParams,
-                Result = request::GetCompletionsResult,
-            >,
-        T: ToPointUtf16,
-    {
-        self.register_buffer(buffer, cx);
-
-        let server = match self.server.as_authenticated() {
-            Ok(server) => server,
-            Err(error) => return Task::ready(Err(error)),
-        };
-        let lsp = server.lsp.clone();
-        let registered_buffer = server
-            .registered_buffers
-            .get_mut(&buffer.entity_id())
-            .unwrap();
-        let snapshot = registered_buffer.report_changes(buffer, cx);
-        let buffer = buffer.read(cx);
-        let uri = registered_buffer.uri.clone();
-        let position = position.to_point_utf16(buffer);
-        let settings = language_settings(buffer.language_at(position).as_ref(), buffer.file(), cx);
-        let tab_size = settings.tab_size;
-        let hard_tabs = settings.hard_tabs;
-        let relative_path = buffer
-            .file()
-            .map(|file| file.path().to_path_buf())
-            .unwrap_or_default();
-
-        cx.background_executor().spawn(async move {
-            let (version, snapshot) = snapshot.await?;
-            let result = lsp
-                .request::<R>(request::GetCompletionsParams {
-                    doc: request::GetCompletionsDocument {
-                        uri,
-                        tab_size: tab_size.into(),
-                        indent_size: 1,
-                        insert_spaces: !hard_tabs,
-                        relative_path: relative_path.to_string_lossy().into(),
-                        position: point_to_lsp(position),
-                        version: version.try_into().unwrap(),
-                    },
-                })
-                .await?;
-            let completions = result
-                .completions
-                .into_iter()
-                .map(|completion| {
-                    let start = snapshot
-                        .clip_point_utf16(point_from_lsp(completion.range.start), Bias::Left);
-                    let end =
-                        snapshot.clip_point_utf16(point_from_lsp(completion.range.end), Bias::Left);
-                    Completion {
-                        uuid: completion.uuid,
-                        range: snapshot.anchor_before(start)..snapshot.anchor_after(end),
-                        text: completion.text,
-                    }
-                })
-                .collect();
-            anyhow::Ok(completions)
-        })
-    }
-
-    pub fn status(&self) -> Status {
-        match &self.server {
-            CopilotServer::Starting { task } => Status::Starting { task: task.clone() },
-            CopilotServer::Disabled => Status::Disabled,
-            CopilotServer::Error(error) => Status::Error(error.clone()),
-            CopilotServer::Running(RunningCopilotServer { sign_in_status, .. }) => {
-                match sign_in_status {
-                    SignInStatus::Authorized { .. } => Status::Authorized,
-                    SignInStatus::Unauthorized { .. } => Status::Unauthorized,
-                    SignInStatus::SigningIn { prompt, .. } => Status::SigningIn {
-                        prompt: prompt.clone(),
-                    },
-                    SignInStatus::SignedOut => Status::SignedOut,
-                }
-            }
-        }
-    }
-
-    fn update_sign_in_status(
-        &mut self,
-        lsp_status: request::SignInStatus,
-        cx: &mut ModelContext<Self>,
-    ) {
-        self.buffers.retain(|buffer| buffer.is_upgradable());
-
-        if let Ok(server) = self.server.as_running() {
-            match lsp_status {
-                request::SignInStatus::Ok { .. }
-                | request::SignInStatus::MaybeOk { .. }
-                | request::SignInStatus::AlreadySignedIn { .. } => {
-                    server.sign_in_status = SignInStatus::Authorized;
-                    for buffer in self.buffers.iter().cloned().collect::<Vec<_>>() {
-                        if let Some(buffer) = buffer.upgrade() {
-                            self.register_buffer(&buffer, cx);
-                        }
-                    }
-                }
-                request::SignInStatus::NotAuthorized { .. } => {
-                    server.sign_in_status = SignInStatus::Unauthorized;
-                    for buffer in self.buffers.iter().cloned().collect::<Vec<_>>() {
-                        self.unregister_buffer(&buffer);
-                    }
-                }
-                request::SignInStatus::NotSignedIn => {
-                    server.sign_in_status = SignInStatus::SignedOut;
-                    for buffer in self.buffers.iter().cloned().collect::<Vec<_>>() {
-                        self.unregister_buffer(&buffer);
-                    }
-                }
-            }
-
-            cx.notify();
-        }
-    }
-}
-
-fn id_for_language(language: Option<&Arc<Language>>) -> String {
-    let language_name = language.map(|language| language.name());
-    match language_name.as_deref() {
-        Some("Plain Text") => "plaintext".to_string(),
-        Some(language_name) => language_name.to_lowercase(),
-        None => "plaintext".to_string(),
-    }
-}
-
-fn uri_for_buffer(buffer: &Model<Buffer>, cx: &AppContext) -> lsp::Url {
-    if let Some(file) = buffer.read(cx).file().and_then(|file| file.as_local()) {
-        lsp::Url::from_file_path(file.abs_path(cx)).unwrap()
-    } else {
-        format!("buffer://{}", buffer.entity_id()).parse().unwrap()
-    }
-}
-
-async fn clear_copilot_dir() {
-    remove_matching(&paths::COPILOT_DIR, |_| true).await
-}
-
-async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
-    const SERVER_PATH: &'static str = "dist/agent.js";
-
-    ///Check for the latest copilot language server and download it if we haven't already
-    async fn fetch_latest(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
-        let release = latest_github_release("zed-industries/copilot", false, http.clone()).await?;
-
-        let version_dir = &*paths::COPILOT_DIR.join(format!("copilot-{}", release.name));
-
-        fs::create_dir_all(version_dir).await?;
-        let server_path = version_dir.join(SERVER_PATH);
-
-        if fs::metadata(&server_path).await.is_err() {
-            // Copilot LSP looks for this dist dir specifcially, so lets add it in.
-            let dist_dir = version_dir.join("dist");
-            fs::create_dir_all(dist_dir.as_path()).await?;
-
-            let url = &release
-                .assets
-                .get(0)
-                .context("Github release for copilot contained no assets")?
-                .browser_download_url;
-
-            let mut response = http
-                .get(&url, Default::default(), true)
-                .await
-                .map_err(|err| anyhow!("error downloading copilot release: {}", err))?;
-            let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
-            let archive = Archive::new(decompressed_bytes);
-            archive.unpack(dist_dir).await?;
-
-            remove_matching(&paths::COPILOT_DIR, |entry| entry != version_dir).await;
-        }
-
-        Ok(server_path)
-    }
-
-    match fetch_latest(http).await {
-        ok @ Result::Ok(..) => ok,
-        e @ Err(..) => {
-            e.log_err();
-            // Fetch a cached binary, if it exists
-            (|| async move {
-                let mut last_version_dir = None;
-                let mut entries = fs::read_dir(paths::COPILOT_DIR.as_path()).await?;
-                while let Some(entry) = entries.next().await {
-                    let entry = entry?;
-                    if entry.file_type().await?.is_dir() {
-                        last_version_dir = Some(entry.path());
-                    }
-                }
-                let last_version_dir =
-                    last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
-                let server_path = last_version_dir.join(SERVER_PATH);
-                if server_path.exists() {
-                    Ok(server_path)
-                } else {
-                    Err(anyhow!(
-                        "missing executable in directory {:?}",
-                        last_version_dir
-                    ))
-                }
-            })()
-            .await
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use gpui::TestAppContext;
-
-    #[gpui::test(iterations = 10)]
-    async fn test_buffer_management(cx: &mut TestAppContext) {
-        let (copilot, mut lsp) = Copilot::fake(cx);
-
-        let buffer_1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "Hello"));
-        let buffer_1_uri: lsp::Url = format!("buffer://{}", buffer_1.entity_id().as_u64())
-            .parse()
-            .unwrap();
-        copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_1, cx));
-        assert_eq!(
-            lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
-                .await,
-            lsp::DidOpenTextDocumentParams {
-                text_document: lsp::TextDocumentItem::new(
-                    buffer_1_uri.clone(),
-                    "plaintext".into(),
-                    0,
-                    "Hello".into()
-                ),
-            }
-        );
-
-        let buffer_2 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "Goodbye"));
-        let buffer_2_uri: lsp::Url = format!("buffer://{}", buffer_2.entity_id().as_u64())
-            .parse()
-            .unwrap();
-        copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_2, cx));
-        assert_eq!(
-            lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
-                .await,
-            lsp::DidOpenTextDocumentParams {
-                text_document: lsp::TextDocumentItem::new(
-                    buffer_2_uri.clone(),
-                    "plaintext".into(),
-                    0,
-                    "Goodbye".into()
-                ),
-            }
-        );
-
-        buffer_1.update(cx, |buffer, cx| buffer.edit([(5..5, " world")], None, cx));
-        assert_eq!(
-            lsp.receive_notification::<lsp::notification::DidChangeTextDocument>()
-                .await,
-            lsp::DidChangeTextDocumentParams {
-                text_document: lsp::VersionedTextDocumentIdentifier::new(buffer_1_uri.clone(), 1),
-                content_changes: vec![lsp::TextDocumentContentChangeEvent {
-                    range: Some(lsp::Range::new(
-                        lsp::Position::new(0, 5),
-                        lsp::Position::new(0, 5)
-                    )),
-                    range_length: None,
-                    text: " world".into(),
-                }],
-            }
-        );
-
-        // Ensure updates to the file are reflected in the LSP.
-        buffer_1.update(cx, |buffer, cx| {
-            buffer.file_updated(
-                Arc::new(File {
-                    abs_path: "/root/child/buffer-1".into(),
-                    path: Path::new("child/buffer-1").into(),
-                }),
-                cx,
-            )
-        });
-        assert_eq!(
-            lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
-                .await,
-            lsp::DidCloseTextDocumentParams {
-                text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri),
-            }
-        );
-        let buffer_1_uri = lsp::Url::from_file_path("/root/child/buffer-1").unwrap();
-        assert_eq!(
-            lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
-                .await,
-            lsp::DidOpenTextDocumentParams {
-                text_document: lsp::TextDocumentItem::new(
-                    buffer_1_uri.clone(),
-                    "plaintext".into(),
-                    1,
-                    "Hello world".into()
-                ),
-            }
-        );
-
-        // Ensure all previously-registered buffers are closed when signing out.
-        lsp.handle_request::<request::SignOut, _, _>(|_, _| async {
-            Ok(request::SignOutResult {})
-        });
-        copilot
-            .update(cx, |copilot, cx| copilot.sign_out(cx))
-            .await
-            .unwrap();
-        assert_eq!(
-            lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
-                .await,
-            lsp::DidCloseTextDocumentParams {
-                text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri.clone()),
-            }
-        );
-        assert_eq!(
-            lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
-                .await,
-            lsp::DidCloseTextDocumentParams {
-                text_document: lsp::TextDocumentIdentifier::new(buffer_2_uri.clone()),
-            }
-        );
-
-        // Ensure all previously-registered buffers are re-opened when signing in.
-        lsp.handle_request::<request::SignInInitiate, _, _>(|_, _| async {
-            Ok(request::SignInInitiateResult::AlreadySignedIn {
-                user: "user-1".into(),
-            })
-        });
-        copilot
-            .update(cx, |copilot, cx| copilot.sign_in(cx))
-            .await
-            .unwrap();
-
-        assert_eq!(
-            lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
-                .await,
-            lsp::DidOpenTextDocumentParams {
-                text_document: lsp::TextDocumentItem::new(
-                    buffer_1_uri.clone(),
-                    "plaintext".into(),
-                    0,
-                    "Hello world".into()
-                ),
-            }
-        );
-        assert_eq!(
-            lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
-                .await,
-            lsp::DidOpenTextDocumentParams {
-                text_document: lsp::TextDocumentItem::new(
-                    buffer_2_uri.clone(),
-                    "plaintext".into(),
-                    0,
-                    "Goodbye".into()
-                ),
-            }
-        );
-        // Dropping a buffer causes it to be closed on the LSP side as well.
-        cx.update(|_| drop(buffer_2));
-        assert_eq!(
-            lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
-                .await,
-            lsp::DidCloseTextDocumentParams {
-                text_document: lsp::TextDocumentIdentifier::new(buffer_2_uri),
-            }
-        );
-    }
-
-    struct File {
-        abs_path: PathBuf,
-        path: Arc<Path>,
-    }
-
-    impl language::File for File {
-        fn as_local(&self) -> Option<&dyn language::LocalFile> {
-            Some(self)
-        }
-
-        fn mtime(&self) -> std::time::SystemTime {
-            unimplemented!()
-        }
-
-        fn path(&self) -> &Arc<Path> {
-            &self.path
-        }
-
-        fn full_path(&self, _: &AppContext) -> PathBuf {
-            unimplemented!()
-        }
-
-        fn file_name<'a>(&'a self, _: &'a AppContext) -> &'a std::ffi::OsStr {
-            unimplemented!()
-        }
-
-        fn is_deleted(&self) -> bool {
-            unimplemented!()
-        }
-
-        fn as_any(&self) -> &dyn std::any::Any {
-            unimplemented!()
-        }
-
-        fn to_proto(&self) -> rpc::proto::File {
-            unimplemented!()
-        }
-
-        fn worktree_id(&self) -> usize {
-            0
-        }
-    }
-
-    impl language::LocalFile for File {
-        fn abs_path(&self, _: &AppContext) -> PathBuf {
-            self.abs_path.clone()
-        }
-
-        fn load(&self, _: &AppContext) -> Task<Result<String>> {
-            unimplemented!()
-        }
-
-        fn buffer_reloaded(
-            &self,
-            _: u64,
-            _: &clock::Global,
-            _: language::RopeFingerprint,
-            _: language::LineEnding,
-            _: std::time::SystemTime,
-            _: &mut AppContext,
-        ) {
-            unimplemented!()
-        }
-    }
-}

crates/copilot2/src/request.rs 🔗

@@ -1,225 +0,0 @@
-use serde::{Deserialize, Serialize};
-
-pub enum CheckStatus {}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct CheckStatusParams {
-    pub local_checks_only: bool,
-}
-
-impl lsp::request::Request for CheckStatus {
-    type Params = CheckStatusParams;
-    type Result = SignInStatus;
-    const METHOD: &'static str = "checkStatus";
-}
-
-pub enum SignInInitiate {}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub struct SignInInitiateParams {}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(tag = "status")]
-pub enum SignInInitiateResult {
-    AlreadySignedIn { user: String },
-    PromptUserDeviceFlow(PromptUserDeviceFlow),
-}
-
-#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct PromptUserDeviceFlow {
-    pub user_code: String,
-    pub verification_uri: String,
-}
-
-impl lsp::request::Request for SignInInitiate {
-    type Params = SignInInitiateParams;
-    type Result = SignInInitiateResult;
-    const METHOD: &'static str = "signInInitiate";
-}
-
-pub enum SignInConfirm {}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct SignInConfirmParams {
-    pub user_code: String,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(tag = "status")]
-pub enum SignInStatus {
-    #[serde(rename = "OK")]
-    Ok {
-        user: String,
-    },
-    MaybeOk {
-        user: String,
-    },
-    AlreadySignedIn {
-        user: String,
-    },
-    NotAuthorized {
-        user: String,
-    },
-    NotSignedIn,
-}
-
-impl lsp::request::Request for SignInConfirm {
-    type Params = SignInConfirmParams;
-    type Result = SignInStatus;
-    const METHOD: &'static str = "signInConfirm";
-}
-
-pub enum SignOut {}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct SignOutParams {}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct SignOutResult {}
-
-impl lsp::request::Request for SignOut {
-    type Params = SignOutParams;
-    type Result = SignOutResult;
-    const METHOD: &'static str = "signOut";
-}
-
-pub enum GetCompletions {}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct GetCompletionsParams {
-    pub doc: GetCompletionsDocument,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct GetCompletionsDocument {
-    pub tab_size: u32,
-    pub indent_size: u32,
-    pub insert_spaces: bool,
-    pub uri: lsp::Url,
-    pub relative_path: String,
-    pub position: lsp::Position,
-    pub version: usize,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct GetCompletionsResult {
-    pub completions: Vec<Completion>,
-}
-
-#[derive(Clone, Debug, Default, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct Completion {
-    pub text: String,
-    pub position: lsp::Position,
-    pub uuid: String,
-    pub range: lsp::Range,
-    pub display_text: String,
-}
-
-impl lsp::request::Request for GetCompletions {
-    type Params = GetCompletionsParams;
-    type Result = GetCompletionsResult;
-    const METHOD: &'static str = "getCompletions";
-}
-
-pub enum GetCompletionsCycling {}
-
-impl lsp::request::Request for GetCompletionsCycling {
-    type Params = GetCompletionsParams;
-    type Result = GetCompletionsResult;
-    const METHOD: &'static str = "getCompletionsCycling";
-}
-
-pub enum LogMessage {}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct LogMessageParams {
-    pub level: u8,
-    pub message: String,
-    pub metadata_str: String,
-    pub extra: Vec<String>,
-}
-
-impl lsp::notification::Notification for LogMessage {
-    type Params = LogMessageParams;
-    const METHOD: &'static str = "LogMessage";
-}
-
-pub enum StatusNotification {}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub struct StatusNotificationParams {
-    pub message: String,
-    pub status: String, // One of Normal/InProgress
-}
-
-impl lsp::notification::Notification for StatusNotification {
-    type Params = StatusNotificationParams;
-    const METHOD: &'static str = "statusNotification";
-}
-
-pub enum SetEditorInfo {}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct SetEditorInfoParams {
-    pub editor_info: EditorInfo,
-    pub editor_plugin_info: EditorPluginInfo,
-}
-
-impl lsp::request::Request for SetEditorInfo {
-    type Params = SetEditorInfoParams;
-    type Result = String;
-    const METHOD: &'static str = "setEditorInfo";
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct EditorInfo {
-    pub name: String,
-    pub version: String,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct EditorPluginInfo {
-    pub name: String,
-    pub version: String,
-}
-
-pub enum NotifyAccepted {}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct NotifyAcceptedParams {
-    pub uuid: String,
-}
-
-impl lsp::request::Request for NotifyAccepted {
-    type Params = NotifyAcceptedParams;
-    type Result = String;
-    const METHOD: &'static str = "notifyAccepted";
-}
-
-pub enum NotifyRejected {}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct NotifyRejectedParams {
-    pub uuids: Vec<String>,
-}
-
-impl lsp::request::Request for NotifyRejected {
-    type Params = NotifyRejectedParams;
-    type Result = String;
-    const METHOD: &'static str = "notifyRejected";
-}

crates/copilot2/src/sign_in.rs 🔗

@@ -1,211 +0,0 @@
-use crate::{request::PromptUserDeviceFlow, Copilot, Status};
-use gpui::{
-    div, size, AppContext, Bounds, ClipboardItem, Element, GlobalPixels, InteractiveElement,
-    IntoElement, ParentElement, Point, Render, Styled, ViewContext, VisualContext, WindowBounds,
-    WindowHandle, WindowKind, WindowOptions,
-};
-use theme::ActiveTheme;
-use ui::{prelude::*, Button, Icon, IconElement, Label};
-
-const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot";
-
-pub fn init(cx: &mut AppContext) {
-    if let Some(copilot) = Copilot::global(cx) {
-        let mut verification_window: Option<WindowHandle<CopilotCodeVerification>> = None;
-        cx.observe(&copilot, move |copilot, cx| {
-            let status = copilot.read(cx).status();
-
-            match &status {
-                crate::Status::SigningIn { prompt } => {
-                    if let Some(window) = verification_window.as_mut() {
-                        let updated = window
-                            .update(cx, |verification, cx| {
-                                verification.set_status(status.clone(), cx);
-                                cx.activate_window();
-                            })
-                            .is_ok();
-                        if !updated {
-                            verification_window = Some(create_copilot_auth_window(cx, &status));
-                        }
-                    } else if let Some(_prompt) = prompt {
-                        verification_window = Some(create_copilot_auth_window(cx, &status));
-                    }
-                }
-                Status::Authorized | Status::Unauthorized => {
-                    if let Some(window) = verification_window.as_ref() {
-                        window
-                            .update(cx, |verification, cx| {
-                                verification.set_status(status, cx);
-                                cx.activate(true);
-                                cx.activate_window();
-                            })
-                            .ok();
-                    }
-                }
-                _ => {
-                    if let Some(code_verification) = verification_window.take() {
-                        code_verification
-                            .update(cx, |_, cx| cx.remove_window())
-                            .ok();
-                    }
-                }
-            }
-        })
-        .detach();
-    }
-}
-
-fn create_copilot_auth_window(
-    cx: &mut AppContext,
-    status: &Status,
-) -> WindowHandle<CopilotCodeVerification> {
-    let window_size = size(GlobalPixels::from(280.), GlobalPixels::from(280.));
-    let window_options = WindowOptions {
-        bounds: WindowBounds::Fixed(Bounds::new(Point::default(), window_size)),
-        titlebar: None,
-        center: true,
-        focus: true,
-        show: true,
-        kind: WindowKind::PopUp,
-        is_movable: true,
-        display_id: None,
-    };
-    let window = cx.open_window(window_options, |cx| {
-        cx.new_view(|_| CopilotCodeVerification::new(status.clone()))
-    });
-    window
-}
-
-pub struct CopilotCodeVerification {
-    status: Status,
-    connect_clicked: bool,
-}
-
-impl CopilotCodeVerification {
-    pub fn new(status: Status) -> Self {
-        Self {
-            status,
-            connect_clicked: false,
-        }
-    }
-
-    pub fn set_status(&mut self, status: Status, cx: &mut ViewContext<Self>) {
-        self.status = status;
-        cx.notify();
-    }
-
-    fn render_device_code(
-        data: &PromptUserDeviceFlow,
-        cx: &mut ViewContext<Self>,
-    ) -> impl IntoElement {
-        let copied = cx
-            .read_from_clipboard()
-            .map(|item| item.text() == &data.user_code)
-            .unwrap_or(false);
-        h_stack()
-            .cursor_pointer()
-            .justify_between()
-            .on_mouse_down(gpui::MouseButton::Left, {
-                let user_code = data.user_code.clone();
-                move |_, cx| {
-                    cx.write_to_clipboard(ClipboardItem::new(user_code.clone()));
-                    cx.notify();
-                }
-            })
-            .child(Label::new(data.user_code.clone()))
-            .child(div())
-            .child(Label::new(if copied { "Copied!" } else { "Copy" }))
-    }
-
-    fn render_prompting_modal(
-        connect_clicked: bool,
-        data: &PromptUserDeviceFlow,
-        cx: &mut ViewContext<Self>,
-    ) -> impl Element {
-        let connect_button_label = if connect_clicked {
-            "Waiting for connection..."
-        } else {
-            "Connect to Github"
-        };
-        v_stack()
-            .flex_1()
-            .items_center()
-            .justify_between()
-            .w_full()
-            .child(Label::new(
-                "Enable Copilot by connecting your existing license",
-            ))
-            .child(Self::render_device_code(data, cx))
-            .child(
-                Label::new("Paste this code into GitHub after clicking the button below.")
-                    .size(ui::LabelSize::Small),
-            )
-            .child(
-                Button::new("connect-button", connect_button_label).on_click({
-                    let verification_uri = data.verification_uri.clone();
-                    cx.listener(move |this, _, cx| {
-                        cx.open_url(&verification_uri);
-                        this.connect_clicked = true;
-                    })
-                }),
-            )
-    }
-    fn render_enabled_modal() -> impl Element {
-        v_stack()
-            .child(Label::new("Copilot Enabled!"))
-            .child(Label::new(
-                "You can update your settings or sign out from the Copilot menu in the status bar.",
-            ))
-            .child(
-                Button::new("copilot-enabled-done-button", "Done")
-                    .on_click(|_, cx| cx.remove_window()),
-            )
-    }
-
-    fn render_unauthorized_modal() -> impl Element {
-        v_stack()
-            .child(Label::new(
-                "Enable Copilot by connecting your existing license.",
-            ))
-            .child(
-                Label::new("You must have an active Copilot license to use it in Zed.")
-                    .color(Color::Warning),
-            )
-            .child(
-                Button::new("copilot-subscribe-button", "Subscibe on Github").on_click(|_, cx| {
-                    cx.remove_window();
-                    cx.open_url(COPILOT_SIGN_UP_URL)
-                }),
-            )
-    }
-}
-
-impl Render for CopilotCodeVerification {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        let prompt = match &self.status {
-            Status::SigningIn {
-                prompt: Some(prompt),
-            } => Self::render_prompting_modal(self.connect_clicked, &prompt, cx).into_any_element(),
-            Status::Unauthorized => {
-                self.connect_clicked = false;
-                Self::render_unauthorized_modal().into_any_element()
-            }
-            Status::Authorized => {
-                self.connect_clicked = false;
-                Self::render_enabled_modal().into_any_element()
-            }
-            _ => div().into_any_element(),
-        };
-        div()
-            .id("copilot code verification")
-            .flex()
-            .flex_col()
-            .size_full()
-            .items_center()
-            .p_10()
-            .bg(cx.theme().colors().element_background)
-            .child(ui::Label::new("Connect Copilot to Zed"))
-            .child(IconElement::new(Icon::ZedXCopilot))
-            .child(prompt)
-    }
-}

crates/copilot_button/Cargo.toml 🔗

@@ -12,13 +12,13 @@ doctest = false
 copilot = { path = "../copilot" }
 editor = { path = "../editor" }
 fs = { path = "../fs" }
-context_menu = { path = "../context_menu" }
+zed_actions = { path = "../zed_actions"}
 gpui = { path = "../gpui" }
 language = { path = "../language" }
 settings = { path = "../settings" }
 theme = { path = "../theme" }
 util = { path = "../util" }
-workspace = { path = "../workspace" }
+workspace = {path = "../workspace" }
 anyhow.workspace = true
 smol.workspace = true
 futures.workspace = true

crates/copilot_button/src/copilot_button.rs 🔗

@@ -1,32 +1,31 @@
 use anyhow::Result;
-use context_menu::{ContextMenu, ContextMenuItem};
 use copilot::{Copilot, SignOut, Status};
 use editor::{scroll::autoscroll::Autoscroll, Editor};
 use fs::Fs;
 use gpui::{
-    elements::*,
-    platform::{CursorStyle, MouseButton},
-    AnyElement, AppContext, AsyncAppContext, Element, Entity, MouseState, Subscription, View,
-    ViewContext, ViewHandle, WeakViewHandle, WindowContext,
+    div, Action, AnchorCorner, AppContext, AsyncWindowContext, Entity, IntoElement, ParentElement,
+    Render, Subscription, View, ViewContext, WeakView, WindowContext,
 };
 use language::{
     language_settings::{self, all_language_settings, AllLanguageSettings},
     File, Language,
 };
-use settings::{update_settings_file, SettingsStore};
+use settings::{update_settings_file, Settings, SettingsStore};
 use std::{path::Path, sync::Arc};
 use util::{paths, ResultExt};
 use workspace::{
-    create_and_open_local_file, item::ItemHandle,
-    notifications::simple_message_notification::OsOpen, StatusItemView, Toast, Workspace,
+    create_and_open_local_file,
+    item::ItemHandle,
+    ui::{popover_menu, ButtonCommon, Clickable, ContextMenu, Icon, IconButton, IconSize, Tooltip},
+    StatusItemView, Toast, Workspace,
 };
+use zed_actions::OpenBrowser;
 
 const COPILOT_SETTINGS_URL: &str = "https://github.com/settings/copilot";
 const COPILOT_STARTING_TOAST_ID: usize = 1337;
 const COPILOT_ERROR_TOAST_ID: usize = 1338;
 
 pub struct CopilotButton {
-    popup_menu: ViewHandle<ContextMenu>,
     editor_subscription: Option<(Subscription, usize)>,
     editor_enabled: Option<bool>,
     language: Option<Arc<Language>>,
@@ -34,25 +33,15 @@ pub struct CopilotButton {
     fs: Arc<dyn Fs>,
 }
 
-impl Entity for CopilotButton {
-    type Event = ();
-}
-
-impl View for CopilotButton {
-    fn ui_name() -> &'static str {
-        "CopilotButton"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+impl Render for CopilotButton {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
         let all_language_settings = all_language_settings(None, cx);
         if !all_language_settings.copilot.feature_enabled {
-            return Empty::new().into_any();
+            return div();
         }
 
-        let theme = theme::current(cx).clone();
-        let active = self.popup_menu.read(cx).visible();
         let Some(copilot) = Copilot::global(cx) else {
-            return Empty::new().into_any();
+            return div();
         };
         let status = copilot.read(cx).status();
 
@@ -60,59 +49,26 @@ impl View for CopilotButton {
             .editor_enabled
             .unwrap_or_else(|| all_language_settings.copilot_enabled(None, None));
 
-        Stack::new()
-            .with_child(
-                MouseEventHandler::new::<Self, _>(0, cx, {
-                    let theme = theme.clone();
-                    let status = status.clone();
-                    move |state, _cx| {
-                        let style = theme
-                            .workspace
-                            .status_bar
-                            .panel_buttons
-                            .button
-                            .in_state(active)
-                            .style_for(state);
-
-                        Flex::row()
-                            .with_child(
-                                Svg::new({
-                                    match status {
-                                        Status::Error(_) => "icons/copilot_error.svg",
-                                        Status::Authorized => {
-                                            if enabled {
-                                                "icons/copilot.svg"
-                                            } else {
-                                                "icons/copilot_disabled.svg"
-                                            }
-                                        }
-                                        _ => "icons/copilot_init.svg",
-                                    }
-                                })
-                                .with_color(style.icon_color)
-                                .constrained()
-                                .with_width(style.icon_size)
-                                .aligned()
-                                .into_any_named("copilot-icon"),
-                            )
-                            .constrained()
-                            .with_height(style.icon_size)
-                            .contained()
-                            .with_style(style.container)
-                    }
-                })
-                .with_cursor_style(CursorStyle::PointingHand)
-                .on_down(MouseButton::Left, |_, this, cx| {
-                    this.popup_menu.update(cx, |menu, _| menu.delay_cancel());
-                })
-                .on_click(MouseButton::Left, {
-                    let status = status.clone();
-                    move |_, this, cx| match status {
-                        Status::Authorized => this.deploy_copilot_menu(cx),
-                        Status::Error(ref e) => {
-                            if let Some(workspace) = cx.root_view().clone().downcast::<Workspace>()
-                            {
-                                workspace.update(cx, |workspace, cx| {
+        let icon = match status {
+            Status::Error(_) => Icon::CopilotError,
+            Status::Authorized => {
+                if enabled {
+                    Icon::Copilot
+                } else {
+                    Icon::CopilotDisabled
+                }
+            }
+            _ => Icon::CopilotInit,
+        };
+
+        if let Status::Error(e) = status {
+            return div().child(
+                IconButton::new("copilot-error", icon)
+                    .icon_size(IconSize::Small)
+                    .on_click(cx.listener(move |_, _, cx| {
+                        if let Some(workspace) = cx.window_handle().downcast::<Workspace>() {
+                            workspace
+                                .update(cx, |workspace, cx| {
                                     workspace.show_toast(
                                         Toast::new(
                                             COPILOT_ERROR_TOAST_ID,
@@ -132,43 +88,40 @@ impl View for CopilotButton {
                                         ),
                                         cx,
                                     );
-                                });
-                            }
+                                })
+                                .ok();
                         }
-                        _ => this.deploy_copilot_start_menu(cx),
+                    }))
+                    .tooltip(|cx| Tooltip::text("GitHub Copilot", cx)),
+            );
+        }
+        let this = cx.view().clone();
+
+        div().child(
+            popover_menu("copilot")
+                .menu(move |cx| match status {
+                    Status::Authorized => {
+                        Some(this.update(cx, |this, cx| this.build_copilot_menu(cx)))
                     }
+                    _ => Some(this.update(cx, |this, cx| this.build_copilot_start_menu(cx))),
                 })
-                .with_tooltip::<Self>(
-                    0,
-                    "GitHub Copilot",
-                    None,
-                    theme.tooltip.clone(),
-                    cx,
+                .anchor(AnchorCorner::BottomRight)
+                .trigger(
+                    IconButton::new("copilot-icon", icon)
+                        .tooltip(|cx| Tooltip::text("GitHub Copilot", cx)),
                 ),
-            )
-            .with_child(ChildView::new(&self.popup_menu, cx).aligned().top().right())
-            .into_any()
+        )
     }
 }
 
 impl CopilotButton {
     pub fn new(fs: Arc<dyn Fs>, cx: &mut ViewContext<Self>) -> Self {
-        let button_view_id = cx.view_id();
-        let menu = cx.add_view(|cx| {
-            let mut menu = ContextMenu::new(button_view_id, cx);
-            menu.set_position_mode(OverlayPositionMode::Local);
-            menu
-        });
-
-        cx.observe(&menu, |_, _, cx| cx.notify()).detach();
-
         Copilot::global(cx).map(|copilot| cx.observe(&copilot, |_, _, cx| cx.notify()).detach());
 
-        cx.observe_global::<SettingsStore, _>(move |_, cx| cx.notify())
+        cx.observe_global::<SettingsStore>(move |_, cx| cx.notify())
             .detach();
 
         Self {
-            popup_menu: menu,
             editor_subscription: None,
             editor_enabled: None,
             language: None,
@@ -177,108 +130,91 @@ impl CopilotButton {
         }
     }
 
-    pub fn deploy_copilot_start_menu(&mut self, cx: &mut ViewContext<Self>) {
-        let mut menu_options = Vec::with_capacity(2);
+    pub fn build_copilot_start_menu(&mut self, cx: &mut ViewContext<Self>) -> View<ContextMenu> {
         let fs = self.fs.clone();
-
-        menu_options.push(ContextMenuItem::handler("Sign In", |cx| {
-            initiate_sign_in(cx)
-        }));
-        menu_options.push(ContextMenuItem::handler("Disable Copilot", move |cx| {
-            hide_copilot(fs.clone(), cx)
-        }));
-
-        self.popup_menu.update(cx, |menu, cx| {
-            menu.toggle(
-                Default::default(),
-                AnchorCorner::BottomRight,
-                menu_options,
-                cx,
-            );
-        });
+        ContextMenu::build(cx, |menu, _| {
+            menu.entry("Sign In", None, initiate_sign_in).entry(
+                "Disable Copilot",
+                None,
+                move |cx| hide_copilot(fs.clone(), cx),
+            )
+        })
     }
 
-    pub fn deploy_copilot_menu(&mut self, cx: &mut ViewContext<Self>) {
+    pub fn build_copilot_menu(&mut self, cx: &mut ViewContext<Self>) -> View<ContextMenu> {
         let fs = self.fs.clone();
-        let mut menu_options = Vec::with_capacity(8);
-
-        if let Some(language) = self.language.clone() {
-            let fs = fs.clone();
-            let language_enabled = language_settings::language_settings(Some(&language), None, cx)
-                .show_copilot_suggestions;
-            menu_options.push(ContextMenuItem::handler(
-                format!(
-                    "{} Suggestions for {}",
-                    if language_enabled { "Hide" } else { "Show" },
-                    language.name()
-                ),
-                move |cx| toggle_copilot_for_language(language.clone(), fs.clone(), cx),
-            ));
-        }
 
-        let settings = settings::get::<AllLanguageSettings>(cx);
+        return ContextMenu::build(cx, move |mut menu, cx| {
+            if let Some(language) = self.language.clone() {
+                let fs = fs.clone();
+                let language_enabled =
+                    language_settings::language_settings(Some(&language), None, cx)
+                        .show_copilot_suggestions;
+
+                menu = menu.entry(
+                    format!(
+                        "{} Suggestions for {}",
+                        if language_enabled { "Hide" } else { "Show" },
+                        language.name()
+                    ),
+                    None,
+                    move |cx| toggle_copilot_for_language(language.clone(), fs.clone(), cx),
+                );
+            }
 
-        if let Some(file) = &self.file {
-            let path = file.path().clone();
-            let path_enabled = settings.copilot_enabled_for_path(&path);
-            menu_options.push(ContextMenuItem::handler(
-                format!(
-                    "{} Suggestions for This Path",
-                    if path_enabled { "Hide" } else { "Show" }
-                ),
-                move |cx| {
-                    if let Some(workspace) = cx.root_view().clone().downcast::<Workspace>() {
-                        let workspace = workspace.downgrade();
-                        cx.spawn(|_, cx| {
-                            configure_disabled_globs(
-                                workspace,
-                                path_enabled.then_some(path.clone()),
-                                cx,
-                            )
-                        })
-                        .detach_and_log_err(cx);
-                    }
-                },
-            ));
-        }
+            let settings = AllLanguageSettings::get_global(cx);
 
-        let globally_enabled = settings.copilot_enabled(None, None);
-        menu_options.push(ContextMenuItem::handler(
-            if globally_enabled {
-                "Hide Suggestions for All Files"
-            } else {
-                "Show Suggestions for All Files"
-            },
-            move |cx| toggle_copilot_globally(fs.clone(), cx),
-        ));
-
-        menu_options.push(ContextMenuItem::Separator);
-
-        let icon_style = theme::current(cx).copilot.out_link_icon.clone();
-        menu_options.push(ContextMenuItem::action(
-            move |state: &mut MouseState, style: &theme::ContextMenuItem| {
-                Flex::row()
-                    .with_child(Label::new("Copilot Settings", style.label.clone()))
-                    .with_child(theme::ui::icon(icon_style.style_for(state)))
-                    .align_children_center()
-                    .into_any()
-            },
-            OsOpen::new(COPILOT_SETTINGS_URL),
-        ));
-
-        menu_options.push(ContextMenuItem::action("Sign Out", SignOut));
-
-        self.popup_menu.update(cx, |menu, cx| {
-            menu.toggle(
-                Default::default(),
-                AnchorCorner::BottomRight,
-                menu_options,
-                cx,
-            );
+            if let Some(file) = &self.file {
+                let path = file.path().clone();
+                let path_enabled = settings.copilot_enabled_for_path(&path);
+
+                menu = menu.entry(
+                    format!(
+                        "{} Suggestions for This Path",
+                        if path_enabled { "Hide" } else { "Show" }
+                    ),
+                    None,
+                    move |cx| {
+                        if let Some(workspace) = cx.window_handle().downcast::<Workspace>() {
+                            if let Ok(workspace) = workspace.root_view(cx) {
+                                let workspace = workspace.downgrade();
+                                cx.spawn(|cx| {
+                                    configure_disabled_globs(
+                                        workspace,
+                                        path_enabled.then_some(path.clone()),
+                                        cx,
+                                    )
+                                })
+                                .detach_and_log_err(cx);
+                            }
+                        }
+                    },
+                );
+            }
+
+            let globally_enabled = settings.copilot_enabled(None, None);
+            menu.entry(
+                if globally_enabled {
+                    "Hide Suggestions for All Files"
+                } else {
+                    "Show Suggestions for All Files"
+                },
+                None,
+                move |cx| toggle_copilot_globally(fs.clone(), cx),
+            )
+            .separator()
+            .link(
+                "Copilot Settings",
+                OpenBrowser {
+                    url: COPILOT_SETTINGS_URL.to_string(),
+                }
+                .boxed_clone(),
+            )
+            .action("Sign Out", SignOut.boxed_clone())
         });
     }
 
-    pub fn update_enabled(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
+    pub fn update_enabled(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
         let editor = editor.read(cx);
         let snapshot = editor.buffer().read(cx).snapshot(cx);
         let suggestion_anchor = editor.selections.newest_anchor().start;
@@ -299,8 +235,10 @@ impl CopilotButton {
 impl StatusItemView for CopilotButton {
     fn set_active_pane_item(&mut self, item: Option<&dyn ItemHandle>, cx: &mut ViewContext<Self>) {
         if let Some(editor) = item.map(|item| item.act_as::<Editor>(cx)).flatten() {
-            self.editor_subscription =
-                Some((cx.observe(&editor, Self::update_enabled), editor.id()));
+            self.editor_subscription = Some((
+                cx.observe(&editor, Self::update_enabled),
+                editor.entity_id().as_u64() as usize,
+            ));
             self.update_enabled(editor, cx);
         } else {
             self.language = None;
@@ -312,9 +250,9 @@ impl StatusItemView for CopilotButton {
 }
 
 async fn configure_disabled_globs(
-    workspace: WeakViewHandle<Workspace>,
+    workspace: WeakView<Workspace>,
     path_to_disable: Option<Arc<Path>>,
-    mut cx: AsyncAppContext,
+    mut cx: AsyncWindowContext,
 ) -> Result<()> {
     let settings_editor = workspace
         .update(&mut cx, |_, cx| {
@@ -396,20 +334,23 @@ fn initiate_sign_in(cx: &mut WindowContext) {
 
     match status {
         Status::Starting { task } => {
-            let Some(workspace) = cx.root_view().clone().downcast::<Workspace>() else {
+            let Some(workspace) = cx.window_handle().downcast::<Workspace>() else {
                 return;
             };
 
-            workspace.update(cx, |workspace, cx| {
+            let Ok(workspace) = workspace.update(cx, |workspace, cx| {
                 workspace.show_toast(
                     Toast::new(COPILOT_STARTING_TOAST_ID, "Copilot is starting..."),
                     cx,
-                )
-            });
-            let workspace = workspace.downgrade();
+                );
+                workspace.weak_handle()
+            }) else {
+                return;
+            };
+
             cx.spawn(|mut cx| async move {
                 task.await;
-                if let Some(copilot) = cx.read(Copilot::global) {
+                if let Some(copilot) = cx.update(|_, cx| Copilot::global(cx)).ok().flatten() {
                     workspace
                         .update(&mut cx, |workspace, cx| match copilot.read(cx).status() {
                             Status::Authorized => workspace.show_toast(

crates/copilot_button2/Cargo.toml 🔗

@@ -1,27 +0,0 @@
-[package]
-name = "copilot_button2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/copilot_button.rs"
-doctest = false
-
-[dependencies]
-copilot = { package = "copilot2", path = "../copilot2" }
-editor = { package = "editor2", path = "../editor2" }
-fs = { package = "fs2", path = "../fs2" }
-zed-actions = { package="zed_actions2", path = "../zed_actions2"}
-gpui = { package = "gpui2", path = "../gpui2" }
-language = { package = "language2", path = "../language2" }
-settings = { package = "settings2", path = "../settings2" }
-theme = { package = "theme2", path = "../theme2" }
-util = { path = "../util" }
-workspace = { package = "workspace2", path = "../workspace2" }
-anyhow.workspace = true
-smol.workspace = true
-futures.workspace = true
-
-[dev-dependencies]
-editor = { package = "editor2", path = "../editor2", features = ["test-support"] }

crates/copilot_button2/src/copilot_button.rs 🔗

@@ -1,378 +0,0 @@
-use anyhow::Result;
-use copilot::{Copilot, SignOut, Status};
-use editor::{scroll::autoscroll::Autoscroll, Editor};
-use fs::Fs;
-use gpui::{
-    div, Action, AnchorCorner, AppContext, AsyncWindowContext, Entity, IntoElement, ParentElement,
-    Render, Subscription, View, ViewContext, WeakView, WindowContext,
-};
-use language::{
-    language_settings::{self, all_language_settings, AllLanguageSettings},
-    File, Language,
-};
-use settings::{update_settings_file, Settings, SettingsStore};
-use std::{path::Path, sync::Arc};
-use util::{paths, ResultExt};
-use workspace::{
-    create_and_open_local_file,
-    item::ItemHandle,
-    ui::{popover_menu, ButtonCommon, Clickable, ContextMenu, Icon, IconButton, IconSize, Tooltip},
-    StatusItemView, Toast, Workspace,
-};
-use zed_actions::OpenBrowser;
-
-const COPILOT_SETTINGS_URL: &str = "https://github.com/settings/copilot";
-const COPILOT_STARTING_TOAST_ID: usize = 1337;
-const COPILOT_ERROR_TOAST_ID: usize = 1338;
-
-pub struct CopilotButton {
-    editor_subscription: Option<(Subscription, usize)>,
-    editor_enabled: Option<bool>,
-    language: Option<Arc<Language>>,
-    file: Option<Arc<dyn File>>,
-    fs: Arc<dyn Fs>,
-}
-
-impl Render for CopilotButton {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        let all_language_settings = all_language_settings(None, cx);
-        if !all_language_settings.copilot.feature_enabled {
-            return div();
-        }
-
-        let Some(copilot) = Copilot::global(cx) else {
-            return div();
-        };
-        let status = copilot.read(cx).status();
-
-        let enabled = self
-            .editor_enabled
-            .unwrap_or_else(|| all_language_settings.copilot_enabled(None, None));
-
-        let icon = match status {
-            Status::Error(_) => Icon::CopilotError,
-            Status::Authorized => {
-                if enabled {
-                    Icon::Copilot
-                } else {
-                    Icon::CopilotDisabled
-                }
-            }
-            _ => Icon::CopilotInit,
-        };
-
-        if let Status::Error(e) = status {
-            return div().child(
-                IconButton::new("copilot-error", icon)
-                    .icon_size(IconSize::Small)
-                    .on_click(cx.listener(move |_, _, cx| {
-                        if let Some(workspace) = cx.window_handle().downcast::<Workspace>() {
-                            workspace
-                                .update(cx, |workspace, cx| {
-                                    workspace.show_toast(
-                                        Toast::new(
-                                            COPILOT_ERROR_TOAST_ID,
-                                            format!("Copilot can't be started: {}", e),
-                                        )
-                                        .on_click(
-                                            "Reinstall Copilot",
-                                            |cx| {
-                                                if let Some(copilot) = Copilot::global(cx) {
-                                                    copilot
-                                                        .update(cx, |copilot, cx| {
-                                                            copilot.reinstall(cx)
-                                                        })
-                                                        .detach();
-                                                }
-                                            },
-                                        ),
-                                        cx,
-                                    );
-                                })
-                                .ok();
-                        }
-                    }))
-                    .tooltip(|cx| Tooltip::text("GitHub Copilot", cx)),
-            );
-        }
-        let this = cx.view().clone();
-
-        div().child(
-            popover_menu("copilot")
-                .menu(move |cx| match status {
-                    Status::Authorized => {
-                        Some(this.update(cx, |this, cx| this.build_copilot_menu(cx)))
-                    }
-                    _ => Some(this.update(cx, |this, cx| this.build_copilot_start_menu(cx))),
-                })
-                .anchor(AnchorCorner::BottomRight)
-                .trigger(
-                    IconButton::new("copilot-icon", icon)
-                        .tooltip(|cx| Tooltip::text("GitHub Copilot", cx)),
-                ),
-        )
-    }
-}
-
-impl CopilotButton {
-    pub fn new(fs: Arc<dyn Fs>, cx: &mut ViewContext<Self>) -> Self {
-        Copilot::global(cx).map(|copilot| cx.observe(&copilot, |_, _, cx| cx.notify()).detach());
-
-        cx.observe_global::<SettingsStore>(move |_, cx| cx.notify())
-            .detach();
-
-        Self {
-            editor_subscription: None,
-            editor_enabled: None,
-            language: None,
-            file: None,
-            fs,
-        }
-    }
-
-    pub fn build_copilot_start_menu(&mut self, cx: &mut ViewContext<Self>) -> View<ContextMenu> {
-        let fs = self.fs.clone();
-        ContextMenu::build(cx, |menu, _| {
-            menu.entry("Sign In", None, initiate_sign_in).entry(
-                "Disable Copilot",
-                None,
-                move |cx| hide_copilot(fs.clone(), cx),
-            )
-        })
-    }
-
-    pub fn build_copilot_menu(&mut self, cx: &mut ViewContext<Self>) -> View<ContextMenu> {
-        let fs = self.fs.clone();
-
-        return ContextMenu::build(cx, move |mut menu, cx| {
-            if let Some(language) = self.language.clone() {
-                let fs = fs.clone();
-                let language_enabled =
-                    language_settings::language_settings(Some(&language), None, cx)
-                        .show_copilot_suggestions;
-
-                menu = menu.entry(
-                    format!(
-                        "{} Suggestions for {}",
-                        if language_enabled { "Hide" } else { "Show" },
-                        language.name()
-                    ),
-                    None,
-                    move |cx| toggle_copilot_for_language(language.clone(), fs.clone(), cx),
-                );
-            }
-
-            let settings = AllLanguageSettings::get_global(cx);
-
-            if let Some(file) = &self.file {
-                let path = file.path().clone();
-                let path_enabled = settings.copilot_enabled_for_path(&path);
-
-                menu = menu.entry(
-                    format!(
-                        "{} Suggestions for This Path",
-                        if path_enabled { "Hide" } else { "Show" }
-                    ),
-                    None,
-                    move |cx| {
-                        if let Some(workspace) = cx.window_handle().downcast::<Workspace>() {
-                            if let Ok(workspace) = workspace.root_view(cx) {
-                                let workspace = workspace.downgrade();
-                                cx.spawn(|cx| {
-                                    configure_disabled_globs(
-                                        workspace,
-                                        path_enabled.then_some(path.clone()),
-                                        cx,
-                                    )
-                                })
-                                .detach_and_log_err(cx);
-                            }
-                        }
-                    },
-                );
-            }
-
-            let globally_enabled = settings.copilot_enabled(None, None);
-            menu.entry(
-                if globally_enabled {
-                    "Hide Suggestions for All Files"
-                } else {
-                    "Show Suggestions for All Files"
-                },
-                None,
-                move |cx| toggle_copilot_globally(fs.clone(), cx),
-            )
-            .separator()
-            .link(
-                "Copilot Settings",
-                OpenBrowser {
-                    url: COPILOT_SETTINGS_URL.to_string(),
-                }
-                .boxed_clone(),
-            )
-            .action("Sign Out", SignOut.boxed_clone())
-        });
-    }
-
-    pub fn update_enabled(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
-        let editor = editor.read(cx);
-        let snapshot = editor.buffer().read(cx).snapshot(cx);
-        let suggestion_anchor = editor.selections.newest_anchor().start;
-        let language = snapshot.language_at(suggestion_anchor);
-        let file = snapshot.file_at(suggestion_anchor).cloned();
-
-        self.editor_enabled = Some(
-            all_language_settings(self.file.as_ref(), cx)
-                .copilot_enabled(language, file.as_ref().map(|file| file.path().as_ref())),
-        );
-        self.language = language.cloned();
-        self.file = file;
-
-        cx.notify()
-    }
-}
-
-impl StatusItemView for CopilotButton {
-    fn set_active_pane_item(&mut self, item: Option<&dyn ItemHandle>, cx: &mut ViewContext<Self>) {
-        if let Some(editor) = item.map(|item| item.act_as::<Editor>(cx)).flatten() {
-            self.editor_subscription = Some((
-                cx.observe(&editor, Self::update_enabled),
-                editor.entity_id().as_u64() as usize,
-            ));
-            self.update_enabled(editor, cx);
-        } else {
-            self.language = None;
-            self.editor_subscription = None;
-            self.editor_enabled = None;
-        }
-        cx.notify();
-    }
-}
-
-async fn configure_disabled_globs(
-    workspace: WeakView<Workspace>,
-    path_to_disable: Option<Arc<Path>>,
-    mut cx: AsyncWindowContext,
-) -> Result<()> {
-    let settings_editor = workspace
-        .update(&mut cx, |_, cx| {
-            create_and_open_local_file(&paths::SETTINGS, cx, || {
-                settings::initial_user_settings_content().as_ref().into()
-            })
-        })?
-        .await?
-        .downcast::<Editor>()
-        .unwrap();
-
-    settings_editor.downgrade().update(&mut cx, |item, cx| {
-        let text = item.buffer().read(cx).snapshot(cx).text();
-
-        let settings = cx.global::<SettingsStore>();
-        let edits = settings.edits_for_update::<AllLanguageSettings>(&text, |file| {
-            let copilot = file.copilot.get_or_insert_with(Default::default);
-            let globs = copilot.disabled_globs.get_or_insert_with(|| {
-                settings
-                    .get::<AllLanguageSettings>(None)
-                    .copilot
-                    .disabled_globs
-                    .iter()
-                    .map(|glob| glob.glob().to_string())
-                    .collect()
-            });
-
-            if let Some(path_to_disable) = &path_to_disable {
-                globs.push(path_to_disable.to_string_lossy().into_owned());
-            } else {
-                globs.clear();
-            }
-        });
-
-        if !edits.is_empty() {
-            item.change_selections(Some(Autoscroll::newest()), cx, |selections| {
-                selections.select_ranges(edits.iter().map(|e| e.0.clone()));
-            });
-
-            // When *enabling* a path, don't actually perform an edit, just select the range.
-            if path_to_disable.is_some() {
-                item.edit(edits.iter().cloned(), cx);
-            }
-        }
-    })?;
-
-    anyhow::Ok(())
-}
-
-fn toggle_copilot_globally(fs: Arc<dyn Fs>, cx: &mut AppContext) {
-    let show_copilot_suggestions = all_language_settings(None, cx).copilot_enabled(None, None);
-    update_settings_file::<AllLanguageSettings>(fs, cx, move |file| {
-        file.defaults.show_copilot_suggestions = Some((!show_copilot_suggestions).into())
-    });
-}
-
-fn toggle_copilot_for_language(language: Arc<Language>, fs: Arc<dyn Fs>, cx: &mut AppContext) {
-    let show_copilot_suggestions =
-        all_language_settings(None, cx).copilot_enabled(Some(&language), None);
-    update_settings_file::<AllLanguageSettings>(fs, cx, move |file| {
-        file.languages
-            .entry(language.name())
-            .or_default()
-            .show_copilot_suggestions = Some(!show_copilot_suggestions);
-    });
-}
-
-fn hide_copilot(fs: Arc<dyn Fs>, cx: &mut AppContext) {
-    update_settings_file::<AllLanguageSettings>(fs, cx, move |file| {
-        file.features.get_or_insert(Default::default()).copilot = Some(false);
-    });
-}
-
-fn initiate_sign_in(cx: &mut WindowContext) {
-    let Some(copilot) = Copilot::global(cx) else {
-        return;
-    };
-    let status = copilot.read(cx).status();
-
-    match status {
-        Status::Starting { task } => {
-            let Some(workspace) = cx.window_handle().downcast::<Workspace>() else {
-                return;
-            };
-
-            let Ok(workspace) = workspace.update(cx, |workspace, cx| {
-                workspace.show_toast(
-                    Toast::new(COPILOT_STARTING_TOAST_ID, "Copilot is starting..."),
-                    cx,
-                );
-                workspace.weak_handle()
-            }) else {
-                return;
-            };
-
-            cx.spawn(|mut cx| async move {
-                task.await;
-                if let Some(copilot) = cx.update(|_, cx| Copilot::global(cx)).ok().flatten() {
-                    workspace
-                        .update(&mut cx, |workspace, cx| match copilot.read(cx).status() {
-                            Status::Authorized => workspace.show_toast(
-                                Toast::new(COPILOT_STARTING_TOAST_ID, "Copilot has started!"),
-                                cx,
-                            ),
-                            _ => {
-                                workspace.dismiss_toast(COPILOT_STARTING_TOAST_ID, cx);
-                                copilot
-                                    .update(cx, |copilot, cx| copilot.sign_in(cx))
-                                    .detach_and_log_err(cx);
-                            }
-                        })
-                        .log_err();
-                }
-            })
-            .detach();
-        }
-        _ => {
-            copilot
-                .update(cx, |copilot, cx| copilot.sign_in(cx))
-                .detach_and_log_err(cx);
-        }
-    }
-}

crates/db/src/db.rs 🔗

@@ -185,7 +185,7 @@ pub fn write_and_log<F>(cx: &mut AppContext, db_write: impl FnOnce() -> F + Send
 where
     F: Future<Output = anyhow::Result<()>> + Send,
 {
-    cx.background()
+    cx.background_executor()
         .spawn(async move { db_write().await.log_err() })
         .detach()
 }
@@ -226,7 +226,9 @@ mod tests {
 
     /// Test that DB exists but corrupted (causing recreate)
     #[gpui::test]
-    async fn test_db_corruption() {
+    async fn test_db_corruption(cx: &mut gpui::TestAppContext) {
+        cx.executor().allow_parking();
+
         enum CorruptedDB {}
 
         impl Domain for CorruptedDB {
@@ -268,7 +270,9 @@ mod tests {
 
     /// Test that DB exists but corrupted (causing recreate)
     #[gpui::test(iterations = 30)]
-    async fn test_simultaneous_db_corruption() {
+    async fn test_simultaneous_db_corruption(cx: &mut gpui::TestAppContext) {
+        cx.executor().allow_parking();
+
         enum CorruptedDB {}
 
         impl Domain for CorruptedDB {

crates/db2/Cargo.toml 🔗

@@ -1,33 +0,0 @@
-[package]
-name = "db2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/db2.rs"
-doctest = false
-
-[features]
-test-support = []
-
-[dependencies]
-collections = { path = "../collections" }
-gpui = { package = "gpui2", path = "../gpui2" }
-sqlez = { path = "../sqlez" }
-sqlez_macros = { path = "../sqlez_macros" }
-util = { path = "../util" }
-anyhow.workspace = true
-indoc.workspace = true
-async-trait.workspace = true
-lazy_static.workspace = true
-log.workspace = true
-parking_lot.workspace = true
-serde.workspace = true
-serde_derive.workspace = true
-smol.workspace = true
-
-[dev-dependencies]
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-env_logger.workspace = true
-tempdir.workspace = true

crates/db2/README.md 🔗

@@ -1,5 +0,0 @@
-# Building Queries
-
-First, craft your test data. The examples folder shows a template for building a test-db, and can be ran with `cargo run --example [your-example]`.
-
-To actually use and test your queries, import the generated DB file into https://sqliteonline.com/

crates/db2/src/db2.rs 🔗

@@ -1,331 +0,0 @@
-pub mod kvp;
-pub mod query;
-
-// Re-export
-pub use anyhow;
-use anyhow::Context;
-use gpui::AppContext;
-pub use indoc::indoc;
-pub use lazy_static;
-pub use smol;
-pub use sqlez;
-pub use sqlez_macros;
-pub use util::channel::{RELEASE_CHANNEL, RELEASE_CHANNEL_NAME};
-pub use util::paths::DB_DIR;
-
-use sqlez::domain::Migrator;
-use sqlez::thread_safe_connection::ThreadSafeConnection;
-use sqlez_macros::sql;
-use std::future::Future;
-use std::path::{Path, PathBuf};
-use std::sync::atomic::{AtomicBool, Ordering};
-use util::channel::ReleaseChannel;
-use util::{async_maybe, ResultExt};
-
-const CONNECTION_INITIALIZE_QUERY: &'static str = sql!(
-    PRAGMA foreign_keys=TRUE;
-);
-
-const DB_INITIALIZE_QUERY: &'static str = sql!(
-    PRAGMA journal_mode=WAL;
-    PRAGMA busy_timeout=1;
-    PRAGMA case_sensitive_like=TRUE;
-    PRAGMA synchronous=NORMAL;
-);
-
-const FALLBACK_DB_NAME: &'static str = "FALLBACK_MEMORY_DB";
-
-const DB_FILE_NAME: &'static str = "db.sqlite";
-
-lazy_static::lazy_static! {
-    pub static ref ZED_STATELESS: bool = std::env::var("ZED_STATELESS").map_or(false, |v| !v.is_empty());
-    pub static ref ALL_FILE_DB_FAILED: AtomicBool = AtomicBool::new(false);
-}
-
-/// Open or create a database at the given directory path.
-/// This will retry a couple times if there are failures. If opening fails once, the db directory
-/// is moved to a backup folder and a new one is created. If that fails, a shared in memory db is created.
-/// In either case, static variables are set so that the user can be notified.
-pub async fn open_db<M: Migrator + 'static>(
-    db_dir: &Path,
-    release_channel: &ReleaseChannel,
-) -> ThreadSafeConnection<M> {
-    if *ZED_STATELESS {
-        return open_fallback_db().await;
-    }
-
-    let release_channel_name = release_channel.dev_name();
-    let main_db_dir = db_dir.join(Path::new(&format!("0-{}", release_channel_name)));
-
-    let connection = async_maybe!({
-        smol::fs::create_dir_all(&main_db_dir)
-            .await
-            .context("Could not create db directory")
-            .log_err()?;
-        let db_path = main_db_dir.join(Path::new(DB_FILE_NAME));
-        open_main_db(&db_path).await
-    })
-    .await;
-
-    if let Some(connection) = connection {
-        return connection;
-    }
-
-    // Set another static ref so that we can escalate the notification
-    ALL_FILE_DB_FAILED.store(true, Ordering::Release);
-
-    // If still failed, create an in memory db with a known name
-    open_fallback_db().await
-}
-
-async fn open_main_db<M: Migrator>(db_path: &PathBuf) -> Option<ThreadSafeConnection<M>> {
-    log::info!("Opening main db");
-    ThreadSafeConnection::<M>::builder(db_path.to_string_lossy().as_ref(), true)
-        .with_db_initialization_query(DB_INITIALIZE_QUERY)
-        .with_connection_initialize_query(CONNECTION_INITIALIZE_QUERY)
-        .build()
-        .await
-        .log_err()
-}
-
-async fn open_fallback_db<M: Migrator>() -> ThreadSafeConnection<M> {
-    log::info!("Opening fallback db");
-    ThreadSafeConnection::<M>::builder(FALLBACK_DB_NAME, false)
-        .with_db_initialization_query(DB_INITIALIZE_QUERY)
-        .with_connection_initialize_query(CONNECTION_INITIALIZE_QUERY)
-        .build()
-        .await
-        .expect(
-            "Fallback in memory database failed. Likely initialization queries or migrations have fundamental errors",
-        )
-}
-
-#[cfg(any(test, feature = "test-support"))]
-pub async fn open_test_db<M: Migrator>(db_name: &str) -> ThreadSafeConnection<M> {
-    use sqlez::thread_safe_connection::locking_queue;
-
-    ThreadSafeConnection::<M>::builder(db_name, false)
-        .with_db_initialization_query(DB_INITIALIZE_QUERY)
-        .with_connection_initialize_query(CONNECTION_INITIALIZE_QUERY)
-        // Serialize queued writes via a mutex and run them synchronously
-        .with_write_queue_constructor(locking_queue())
-        .build()
-        .await
-        .unwrap()
-}
-
-/// Implements a basic DB wrapper for a given domain
-#[macro_export]
-macro_rules! define_connection {
-    (pub static ref $id:ident: $t:ident<()> = $migrations:expr;) => {
-        pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection<$t>);
-
-        impl ::std::ops::Deref for $t {
-            type Target = $crate::sqlez::thread_safe_connection::ThreadSafeConnection<$t>;
-
-            fn deref(&self) -> &Self::Target {
-                &self.0
-            }
-        }
-
-        impl $crate::sqlez::domain::Domain for $t {
-            fn name() -> &'static str {
-                stringify!($t)
-            }
-
-            fn migrations() -> &'static [&'static str] {
-                $migrations
-            }
-        }
-
-        #[cfg(any(test, feature = "test-support"))]
-        $crate::lazy_static::lazy_static! {
-            pub static ref $id: $t = $t($crate::smol::block_on($crate::open_test_db(stringify!($id))));
-        }
-
-        #[cfg(not(any(test, feature = "test-support")))]
-        $crate::lazy_static::lazy_static! {
-            pub static ref $id: $t = $t($crate::smol::block_on($crate::open_db(&$crate::DB_DIR, &$crate::RELEASE_CHANNEL)));
-        }
-    };
-    (pub static ref $id:ident: $t:ident<$($d:ty),+> = $migrations:expr;) => {
-        pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection<( $($d),+, $t )>);
-
-        impl ::std::ops::Deref for $t {
-            type Target = $crate::sqlez::thread_safe_connection::ThreadSafeConnection<($($d),+, $t)>;
-
-            fn deref(&self) -> &Self::Target {
-                &self.0
-            }
-        }
-
-        impl $crate::sqlez::domain::Domain for $t {
-            fn name() -> &'static str {
-                stringify!($t)
-            }
-
-            fn migrations() -> &'static [&'static str] {
-                $migrations
-            }
-        }
-
-        #[cfg(any(test, feature = "test-support"))]
-        $crate::lazy_static::lazy_static! {
-            pub static ref $id: $t = $t($crate::smol::block_on($crate::open_test_db(stringify!($id))));
-        }
-
-        #[cfg(not(any(test, feature = "test-support")))]
-        $crate::lazy_static::lazy_static! {
-            pub static ref $id: $t = $t($crate::smol::block_on($crate::open_db(&$crate::DB_DIR, &$crate::RELEASE_CHANNEL)));
-        }
-    };
-}
-
-pub fn write_and_log<F>(cx: &mut AppContext, db_write: impl FnOnce() -> F + Send + 'static)
-where
-    F: Future<Output = anyhow::Result<()>> + Send,
-{
-    cx.background_executor()
-        .spawn(async move { db_write().await.log_err() })
-        .detach()
-}
-
-#[cfg(test)]
-mod tests {
-    use std::thread;
-
-    use sqlez::domain::Domain;
-    use sqlez_macros::sql;
-    use tempdir::TempDir;
-
-    use crate::open_db;
-
-    // Test bad migration panics
-    #[gpui::test]
-    #[should_panic]
-    async fn test_bad_migration_panics() {
-        enum BadDB {}
-
-        impl Domain for BadDB {
-            fn name() -> &'static str {
-                "db_tests"
-            }
-
-            fn migrations() -> &'static [&'static str] {
-                &[
-                    sql!(CREATE TABLE test(value);),
-                    // failure because test already exists
-                    sql!(CREATE TABLE test(value);),
-                ]
-            }
-        }
-
-        let tempdir = TempDir::new("DbTests").unwrap();
-        let _bad_db = open_db::<BadDB>(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
-    }
-
-    /// Test that DB exists but corrupted (causing recreate)
-    #[gpui::test]
-    async fn test_db_corruption(cx: &mut gpui::TestAppContext) {
-        cx.executor().allow_parking();
-
-        enum CorruptedDB {}
-
-        impl Domain for CorruptedDB {
-            fn name() -> &'static str {
-                "db_tests"
-            }
-
-            fn migrations() -> &'static [&'static str] {
-                &[sql!(CREATE TABLE test(value);)]
-            }
-        }
-
-        enum GoodDB {}
-
-        impl Domain for GoodDB {
-            fn name() -> &'static str {
-                "db_tests" //Notice same name
-            }
-
-            fn migrations() -> &'static [&'static str] {
-                &[sql!(CREATE TABLE test2(value);)] //But different migration
-            }
-        }
-
-        let tempdir = TempDir::new("DbTests").unwrap();
-        {
-            let corrupt_db =
-                open_db::<CorruptedDB>(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
-            assert!(corrupt_db.persistent());
-        }
-
-        let good_db = open_db::<GoodDB>(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
-        assert!(
-            good_db.select_row::<usize>("SELECT * FROM test2").unwrap()()
-                .unwrap()
-                .is_none()
-        );
-    }
-
-    /// Test that DB exists but corrupted (causing recreate)
-    #[gpui::test(iterations = 30)]
-    async fn test_simultaneous_db_corruption(cx: &mut gpui::TestAppContext) {
-        cx.executor().allow_parking();
-
-        enum CorruptedDB {}
-
-        impl Domain for CorruptedDB {
-            fn name() -> &'static str {
-                "db_tests"
-            }
-
-            fn migrations() -> &'static [&'static str] {
-                &[sql!(CREATE TABLE test(value);)]
-            }
-        }
-
-        enum GoodDB {}
-
-        impl Domain for GoodDB {
-            fn name() -> &'static str {
-                "db_tests" //Notice same name
-            }
-
-            fn migrations() -> &'static [&'static str] {
-                &[sql!(CREATE TABLE test2(value);)] //But different migration
-            }
-        }
-
-        let tempdir = TempDir::new("DbTests").unwrap();
-        {
-            // Setup the bad database
-            let corrupt_db =
-                open_db::<CorruptedDB>(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
-            assert!(corrupt_db.persistent());
-        }
-
-        // Try to connect to it a bunch of times at once
-        let mut guards = vec![];
-        for _ in 0..10 {
-            let tmp_path = tempdir.path().to_path_buf();
-            let guard = thread::spawn(move || {
-                let good_db = smol::block_on(open_db::<GoodDB>(
-                    tmp_path.as_path(),
-                    &util::channel::ReleaseChannel::Dev,
-                ));
-                assert!(
-                    good_db.select_row::<usize>("SELECT * FROM test2").unwrap()()
-                        .unwrap()
-                        .is_none()
-                );
-            });
-
-            guards.push(guard);
-        }
-
-        for guard in guards.into_iter() {
-            assert!(guard.join().is_ok());
-        }
-    }
-}

crates/db2/src/kvp.rs 🔗

@@ -1,62 +0,0 @@
-use sqlez_macros::sql;
-
-use crate::{define_connection, query};
-
-define_connection!(pub static ref KEY_VALUE_STORE: KeyValueStore<()> =
-    &[sql!(
-        CREATE TABLE IF NOT EXISTS kv_store(
-            key TEXT PRIMARY KEY,
-            value TEXT NOT NULL
-        ) STRICT;
-    )];
-);
-
-impl KeyValueStore {
-    query! {
-        pub fn read_kvp(key: &str) -> Result<Option<String>> {
-            SELECT value FROM kv_store WHERE key = (?)
-        }
-    }
-
-    query! {
-        pub async fn write_kvp(key: String, value: String) -> Result<()> {
-            INSERT OR REPLACE INTO kv_store(key, value) VALUES ((?), (?))
-        }
-    }
-
-    query! {
-        pub async fn delete_kvp(key: String) -> Result<()> {
-            DELETE FROM kv_store WHERE key = (?)
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::kvp::KeyValueStore;
-
-    #[gpui::test]
-    async fn test_kvp() {
-        let db = KeyValueStore(crate::open_test_db("test_kvp").await);
-
-        assert_eq!(db.read_kvp("key-1").unwrap(), None);
-
-        db.write_kvp("key-1".to_string(), "one".to_string())
-            .await
-            .unwrap();
-        assert_eq!(db.read_kvp("key-1").unwrap(), Some("one".to_string()));
-
-        db.write_kvp("key-1".to_string(), "one-2".to_string())
-            .await
-            .unwrap();
-        assert_eq!(db.read_kvp("key-1").unwrap(), Some("one-2".to_string()));
-
-        db.write_kvp("key-2".to_string(), "two".to_string())
-            .await
-            .unwrap();
-        assert_eq!(db.read_kvp("key-2").unwrap(), Some("two".to_string()));
-
-        db.delete_kvp("key-1".to_string()).await.unwrap();
-        assert_eq!(db.read_kvp("key-1").unwrap(), None);
-    }
-}

crates/db2/src/query.rs 🔗

@@ -1,314 +0,0 @@
-#[macro_export]
-macro_rules! query {
-    ($vis:vis fn $id:ident() -> Result<()> { $($sql:tt)+ }) => {
-        $vis fn $id(&self) -> $crate::anyhow::Result<()> {
-            use $crate::anyhow::Context;
-
-            let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
-
-            self.exec(sql_stmt)?().context(::std::format!(
-                "Error in {}, exec failed to execute or parse for: {}",
-                ::std::stringify!($id),
-                sql_stmt,
-            ))
-        }
-    };
-    ($vis:vis async fn $id:ident() -> Result<()> { $($sql:tt)+ }) => {
-        $vis async fn $id(&self) -> $crate::anyhow::Result<()> {
-            use $crate::anyhow::Context;
-
-            self.write(|connection| {
-                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
-
-                connection.exec(sql_stmt)?().context(::std::format!(
-                    "Error in {}, exec failed to execute or parse for: {}",
-                    ::std::stringify!($id),
-                    sql_stmt
-                ))
-            }).await
-        }
-    };
-    ($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<()> { $($sql:tt)+ }) => {
-        $vis fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<()> {
-            use $crate::anyhow::Context;
-
-            let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
-
-            self.exec_bound::<($($arg_type),+)>(sql_stmt)?(($($arg),+))
-                .context(::std::format!(
-                    "Error in {}, exec_bound failed to execute or parse for: {}",
-                    ::std::stringify!($id),
-                    sql_stmt
-                ))
-        }
-    };
-    ($vis:vis async fn $id:ident($arg:ident: $arg_type:ty) -> Result<()> { $($sql:tt)+ }) => {
-        $vis async fn $id(&self, $arg: $arg_type) -> $crate::anyhow::Result<()> {
-            use $crate::anyhow::Context;
-
-            self.write(move |connection| {
-                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
-
-                connection.exec_bound::<$arg_type>(sql_stmt)?($arg)
-                    .context(::std::format!(
-                        "Error in {}, exec_bound failed to execute or parse for: {}",
-                        ::std::stringify!($id),
-                        sql_stmt
-                    ))
-            }).await
-        }
-    };
-    ($vis:vis async fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<()> { $($sql:tt)+ }) => {
-        $vis async fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<()> {
-            use $crate::anyhow::Context;
-
-            self.write(move |connection| {
-                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
-
-                connection.exec_bound::<($($arg_type),+)>(sql_stmt)?(($($arg),+))
-                    .context(::std::format!(
-                        "Error in {}, exec_bound failed to execute or parse for: {}",
-                        ::std::stringify!($id),
-                        sql_stmt
-                    ))
-            }).await
-        }
-    };
-    ($vis:vis fn $id:ident() ->  Result<Vec<$return_type:ty>> { $($sql:tt)+ }) => {
-        $vis fn $id(&self) -> $crate::anyhow::Result<Vec<$return_type>> {
-            use $crate::anyhow::Context;
-
-            let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
-
-            self.select::<$return_type>(sql_stmt)?()
-                .context(::std::format!(
-                    "Error in {}, select_row failed to execute or parse for: {}",
-                    ::std::stringify!($id),
-                    sql_stmt
-                ))
-        }
-    };
-    ($vis:vis async fn $id:ident() -> Result<Vec<$return_type:ty>> { $($sql:tt)+ }) => {
-        pub async fn $id(&self) -> $crate::anyhow::Result<Vec<$return_type>> {
-            use $crate::anyhow::Context;
-
-            self.write(|connection| {
-                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
-
-                connection.select::<$return_type>(sql_stmt)?()
-                    .context(::std::format!(
-                        "Error in {}, select_row failed to execute or parse for: {}",
-                        ::std::stringify!($id),
-                        sql_stmt
-                    ))
-            }).await
-        }
-    };
-    ($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<Vec<$return_type:ty>> { $($sql:tt)+ }) => {
-        $vis fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<Vec<$return_type>> {
-            use $crate::anyhow::Context;
-
-            let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
-
-            self.select_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
-                .context(::std::format!(
-                    "Error in {}, exec_bound failed to execute or parse for: {}",
-                    ::std::stringify!($id),
-                    sql_stmt
-                ))
-        }
-    };
-    ($vis:vis async fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<Vec<$return_type:ty>> { $($sql:tt)+ }) => {
-        $vis async fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<Vec<$return_type>> {
-            use $crate::anyhow::Context;
-
-            self.write(|connection| {
-                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
-
-                connection.select_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
-                    .context(::std::format!(
-                        "Error in {}, exec_bound failed to execute or parse for: {}",
-                        ::std::stringify!($id),
-                        sql_stmt
-                    ))
-            }).await
-        }
-    };
-    ($vis:vis fn $id:ident() ->  Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
-        $vis fn $id(&self) -> $crate::anyhow::Result<Option<$return_type>> {
-            use $crate::anyhow::Context;
-
-            let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
-
-            self.select_row::<$return_type>(sql_stmt)?()
-                .context(::std::format!(
-                    "Error in {}, select_row failed to execute or parse for: {}",
-                    ::std::stringify!($id),
-                    sql_stmt
-                ))
-        }
-    };
-    ($vis:vis async fn $id:ident() ->  Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
-        $vis async fn $id(&self) -> $crate::anyhow::Result<Option<$return_type>> {
-            use $crate::anyhow::Context;
-
-            self.write(|connection| {
-                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
-
-                connection.select_row::<$return_type>(sql_stmt)?()
-                    .context(::std::format!(
-                        "Error in {}, select_row failed to execute or parse for: {}",
-                        ::std::stringify!($id),
-                        sql_stmt
-                    ))
-            }).await
-        }
-    };
-    ($vis:vis fn $id:ident($arg:ident: $arg_type:ty) ->  Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
-        $vis fn $id(&self, $arg: $arg_type) -> $crate::anyhow::Result<Option<$return_type>>  {
-            use $crate::anyhow::Context;
-
-            let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
-
-            self.select_row_bound::<$arg_type, $return_type>(sql_stmt)?($arg)
-                .context(::std::format!(
-                    "Error in {}, select_row_bound failed to execute or parse for: {}",
-                    ::std::stringify!($id),
-                    sql_stmt
-                ))
-
-        }
-    };
-    ($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) ->  Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
-        $vis fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<Option<$return_type>>  {
-            use $crate::anyhow::Context;
-
-            let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
-
-            self.select_row_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
-                .context(::std::format!(
-                    "Error in {}, select_row_bound failed to execute or parse for: {}",
-                    ::std::stringify!($id),
-                    sql_stmt
-                ))
-
-        }
-    };
-    ($vis:vis async fn $id:ident($($arg:ident: $arg_type:ty),+) ->  Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
-        $vis async fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<Option<$return_type>>  {
-            use $crate::anyhow::Context;
-
-
-            self.write(move |connection| {
-                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
-
-                connection.select_row_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
-                    .context(::std::format!(
-                        "Error in {}, select_row_bound failed to execute or parse for: {}",
-                        ::std::stringify!($id),
-                        sql_stmt
-                    ))
-            }).await
-        }
-    };
-    ($vis:vis fn $id:ident() ->  Result<$return_type:ty> { $($sql:tt)+ }) => {
-        $vis fn $id(&self) ->  $crate::anyhow::Result<$return_type>  {
-            use $crate::anyhow::Context;
-
-            let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
-
-            self.select_row::<$return_type>(indoc! { $sql })?()
-                .context(::std::format!(
-                    "Error in {}, select_row_bound failed to execute or parse for: {}",
-                    ::std::stringify!($id),
-                    sql_stmt
-                ))?
-                .context(::std::format!(
-                    "Error in {}, select_row_bound expected single row result but found none for: {}",
-                    ::std::stringify!($id),
-                    sql_stmt
-                ))
-        }
-    };
-    ($vis:vis async fn $id:ident() ->  Result<$return_type:ty> { $($sql:tt)+ }) => {
-        $vis async fn $id(&self) ->  $crate::anyhow::Result<$return_type>  {
-            use $crate::anyhow::Context;
-
-            self.write(|connection| {
-                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
-
-                connection.select_row::<$return_type>(sql_stmt)?()
-                    .context(::std::format!(
-                        "Error in {}, select_row_bound failed to execute or parse for: {}",
-                        ::std::stringify!($id),
-                        sql_stmt
-                    ))?
-                    .context(::std::format!(
-                        "Error in {}, select_row_bound expected single row result but found none for: {}",
-                        ::std::stringify!($id),
-                        sql_stmt
-                    ))
-            }).await
-        }
-    };
-    ($vis:vis fn $id:ident($arg:ident: $arg_type:ty) ->  Result<$return_type:ty> { $($sql:tt)+ }) => {
-        pub fn $id(&self, $arg: $arg_type) ->  $crate::anyhow::Result<$return_type>  {
-            use $crate::anyhow::Context;
-
-            let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
-
-            self.select_row_bound::<$arg_type, $return_type>(sql_stmt)?($arg)
-                .context(::std::format!(
-                    "Error in {}, select_row_bound failed to execute or parse for: {}",
-                    ::std::stringify!($id),
-                    sql_stmt
-                ))?
-                .context(::std::format!(
-                    "Error in {}, select_row_bound expected single row result but found none for: {}",
-                    ::std::stringify!($id),
-                    sql_stmt
-                ))
-        }
-    };
-    ($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) ->  Result<$return_type:ty> { $($sql:tt)+ }) => {
-        $vis fn $id(&self, $($arg: $arg_type),+) ->  $crate::anyhow::Result<$return_type>  {
-            use $crate::anyhow::Context;
-
-            let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
-
-            self.select_row_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
-                .context(::std::format!(
-                    "Error in {}, select_row_bound failed to execute or parse for: {}",
-                    ::std::stringify!($id),
-                    sql_stmt
-                ))?
-                .context(::std::format!(
-                    "Error in {}, select_row_bound expected single row result but found none for: {}",
-                    ::std::stringify!($id),
-                    sql_stmt
-                ))
-        }
-    };
-    ($vis:vis fn async $id:ident($($arg:ident: $arg_type:ty),+) ->  Result<$return_type:ty> { $($sql:tt)+ }) => {
-        $vis async fn $id(&self, $($arg: $arg_type),+) ->  $crate::anyhow::Result<$return_type>  {
-            use $crate::anyhow::Context;
-
-
-            self.write(|connection| {
-                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
-
-                connection.select_row_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
-                    .context(::std::format!(
-                        "Error in {}, select_row_bound failed to execute or parse for: {}",
-                        ::std::stringify!($id),
-                        sql_stmt
-                    ))?
-                    .context(::std::format!(
-                        "Error in {}, select_row_bound expected single row result but found none for: {}",
-                        ::std::stringify!($id),
-                        sql_stmt
-                    ))
-            }).await
-        }
-    };
-}

crates/diagnostics/Cargo.toml 🔗

@@ -12,13 +12,14 @@ doctest = false
 collections = { path = "../collections" }
 editor = { path = "../editor" }
 gpui = { path = "../gpui" }
+ui = { path = "../ui" }
 language = { path = "../language" }
 lsp = { path = "../lsp" }
 project = { path = "../project" }
 settings = { path = "../settings" }
 theme = { path = "../theme" }
 util = { path = "../util" }
-workspace = { path = "../workspace" }
+workspace = {path = "../workspace" }
 
 log.workspace = true
 anyhow.workspace = true
@@ -35,7 +36,7 @@ editor = { path = "../editor", features = ["test-support"] }
 language = { path = "../language", features = ["test-support"] }
 lsp = { path = "../lsp", features = ["test-support"] }
 gpui = { path = "../gpui", features = ["test-support"] }
-workspace = { path = "../workspace", features = ["test-support"] }
+workspace = {path = "../workspace", features = ["test-support"] }
 theme = { path = "../theme", features = ["test-support"] }
 
 serde_json.workspace = true

crates/diagnostics/src/diagnostics.rs 🔗

@@ -2,19 +2,21 @@ pub mod items;
 mod project_diagnostics_settings;
 mod toolbar_controls;
 
-use anyhow::{Context, Result};
+use anyhow::{Context as _, Result};
 use collections::{HashMap, HashSet};
 use editor::{
     diagnostic_block_renderer,
     display_map::{BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock},
     highlight_diagnostic_message,
     scroll::autoscroll::Autoscroll,
-    Editor, ExcerptId, ExcerptRange, MultiBuffer, ToOffset,
+    Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, ToOffset,
 };
 use futures::future::try_join_all;
 use gpui::{
-    actions, elements::*, fonts::TextStyle, serde_json, AnyViewHandle, AppContext, Entity,
-    ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
+    actions, div, svg, AnyElement, AnyView, AppContext, Context, EventEmitter, FocusHandle,
+    FocusableView, HighlightStyle, InteractiveElement, IntoElement, Model, ParentElement, Render,
+    SharedString, Styled, StyledText, Subscription, Task, View, ViewContext, VisualContext,
+    WeakView, WindowContext,
 };
 use language::{
     Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection,
@@ -23,23 +25,22 @@ use language::{
 use lsp::LanguageServerId;
 use project::{DiagnosticSummary, Project, ProjectPath};
 use project_diagnostics_settings::ProjectDiagnosticsSettings;
-use serde_json::json;
-use smallvec::SmallVec;
+use settings::Settings;
 use std::{
     any::{Any, TypeId},
-    borrow::Cow,
     cmp::Ordering,
     mem,
     ops::Range,
     path::PathBuf,
     sync::Arc,
 };
-use theme::ThemeSettings;
+use theme::ActiveTheme;
 pub use toolbar_controls::ToolbarControls;
+use ui::{h_stack, prelude::*, Icon, IconElement, Label};
 use util::TryFutureExt;
 use workspace::{
     item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
-    ItemNavHistory, Pane, PaneBackdrop, ToolbarItemLocation, Workspace,
+    ItemNavHistory, Pane, ToolbarItemLocation, Workspace,
 };
 
 actions!(diagnostics, [Deploy, ToggleWarnings]);
@@ -47,20 +48,18 @@ actions!(diagnostics, [Deploy, ToggleWarnings]);
 const CONTEXT_LINE_COUNT: u32 = 1;
 
 pub fn init(cx: &mut AppContext) {
-    settings::register::<ProjectDiagnosticsSettings>(cx);
-    cx.add_action(ProjectDiagnosticsEditor::deploy);
-    cx.add_action(ProjectDiagnosticsEditor::toggle_warnings);
-    items::init(cx);
+    ProjectDiagnosticsSettings::register(cx);
+    cx.observe_new_views(ProjectDiagnosticsEditor::register)
+        .detach();
 }
 
-type Event = editor::Event;
-
 struct ProjectDiagnosticsEditor {
-    project: ModelHandle<Project>,
-    workspace: WeakViewHandle<Workspace>,
-    editor: ViewHandle<Editor>,
+    project: Model<Project>,
+    workspace: WeakView<Workspace>,
+    focus_handle: FocusHandle,
+    editor: View<Editor>,
     summary: DiagnosticSummary,
-    excerpts: ModelHandle<MultiBuffer>,
+    excerpts: Model<MultiBuffer>,
     path_states: Vec<PathState>,
     paths_to_update: HashMap<LanguageServerId, HashSet<ProjectPath>>,
     current_diagnostics: HashMap<LanguageServerId, HashSet<ProjectPath>>,
@@ -89,71 +88,38 @@ struct DiagnosticGroupState {
     block_count: usize,
 }
 
-impl Entity for ProjectDiagnosticsEditor {
-    type Event = Event;
-}
-
-impl View for ProjectDiagnosticsEditor {
-    fn ui_name() -> &'static str {
-        "ProjectDiagnosticsEditor"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        if self.path_states.is_empty() {
-            let theme = &theme::current(cx).project_diagnostics;
-            PaneBackdrop::new(
-                cx.view_id(),
-                Label::new("No problems in workspace", theme.empty_message.clone())
-                    .aligned()
-                    .contained()
-                    .with_style(theme.container)
-                    .into_any(),
-            )
-            .into_any()
+impl EventEmitter<EditorEvent> for ProjectDiagnosticsEditor {}
+
+impl Render for ProjectDiagnosticsEditor {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element {
+        let child = if self.path_states.is_empty() {
+            div()
+                .bg(cx.theme().colors().editor_background)
+                .flex()
+                .items_center()
+                .justify_center()
+                .size_full()
+                .child(Label::new("No problems in workspace"))
         } else {
-            ChildView::new(&self.editor, cx).into_any()
-        }
-    }
-
-    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
-        if cx.is_self_focused() && !self.path_states.is_empty() {
-            cx.focus(&self.editor);
-        }
-    }
+            div().size_full().child(self.editor.clone())
+        };
 
-    fn debug_json(&self, cx: &AppContext) -> serde_json::Value {
-        let project = self.project.read(cx);
-        json!({
-            "project": json!({
-                "language_servers": project.language_server_statuses().collect::<Vec<_>>(),
-                "summary": project.diagnostic_summary(false, cx),
-            }),
-            "summary": self.summary,
-            "paths_to_update": self.paths_to_update.iter().map(|(server_id, paths)|
-                (server_id.0, paths.into_iter().map(|path| path.path.to_string_lossy()).collect::<Vec<_>>())
-            ).collect::<HashMap<_, _>>(),
-            "current_diagnostics": self.current_diagnostics.iter().map(|(server_id, paths)|
-                (server_id.0, paths.into_iter().map(|path| path.path.to_string_lossy()).collect::<Vec<_>>())
-            ).collect::<HashMap<_, _>>(),
-            "paths_states": self.path_states.iter().map(|state|
-                json!({
-                    "path": state.path.path.to_string_lossy(),
-                    "groups": state.diagnostic_groups.iter().map(|group|
-                        json!({
-                            "block_count": group.blocks.len(),
-                            "excerpt_count": group.excerpts.len(),
-                        })
-                    ).collect::<Vec<_>>(),
-                })
-            ).collect::<Vec<_>>(),
-        })
+        div()
+            .track_focus(&self.focus_handle)
+            .size_full()
+            .on_action(cx.listener(Self::toggle_warnings))
+            .child(child)
     }
 }
 
 impl ProjectDiagnosticsEditor {
+    fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+        workspace.register_action(Self::deploy);
+    }
+
     fn new(
-        project_handle: ModelHandle<Project>,
-        workspace: WeakViewHandle<Workspace>,
+        project_handle: Model<Project>,
+        workspace: WeakView<Workspace>,
         cx: &mut ViewContext<Self>,
     ) -> Self {
         let project_event_subscription =
@@ -180,19 +146,25 @@ impl ProjectDiagnosticsEditor {
                 _ => {}
             });
 
-        let excerpts = cx.add_model(|cx| MultiBuffer::new(project_handle.read(cx).replica_id()));
-        let editor = cx.add_view(|cx| {
+        let focus_handle = cx.focus_handle();
+
+        let focus_in_subscription =
+            cx.on_focus_in(&focus_handle, |diagnostics, cx| diagnostics.focus_in(cx));
+
+        let excerpts = cx.new_model(|cx| MultiBuffer::new(project_handle.read(cx).replica_id()));
+        let editor = cx.new_view(|cx| {
             let mut editor =
                 Editor::for_multibuffer(excerpts.clone(), Some(project_handle.clone()), cx);
             editor.set_vertical_scroll_margin(5, cx);
             editor
         });
-        let editor_event_subscription = cx.subscribe(&editor, |this, _, event, cx| {
-            cx.emit(event.clone());
-            if event == &editor::Event::Focused && this.path_states.is_empty() {
-                cx.focus_self()
-            }
-        });
+        let editor_event_subscription =
+            cx.subscribe(&editor, |this, _editor, event: &EditorEvent, cx| {
+                cx.emit(event.clone());
+                if event == &EditorEvent::Focused && this.path_states.is_empty() {
+                    cx.focus(&this.focus_handle);
+                }
+            });
 
         let project = project_handle.read(cx);
         let summary = project.diagnostic_summary(false, cx);
@@ -201,12 +173,17 @@ impl ProjectDiagnosticsEditor {
             summary,
             workspace,
             excerpts,
+            focus_handle,
             editor,
             path_states: Default::default(),
             paths_to_update: HashMap::default(),
-            include_warnings: settings::get::<ProjectDiagnosticsSettings>(cx).include_warnings,
+            include_warnings: ProjectDiagnosticsSettings::get_global(cx).include_warnings,
             current_diagnostics: HashMap::default(),
-            _subscriptions: vec![project_event_subscription, editor_event_subscription],
+            _subscriptions: vec![
+                project_event_subscription,
+                editor_event_subscription,
+                focus_in_subscription,
+            ],
         };
         this.update_excerpts(None, cx);
         this
@@ -216,8 +193,8 @@ impl ProjectDiagnosticsEditor {
         if let Some(existing) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) {
             workspace.activate_item(&existing, cx);
         } else {
-            let workspace_handle = cx.weak_handle();
-            let diagnostics = cx.add_view(|cx| {
+            let workspace_handle = cx.view().downgrade();
+            let diagnostics = cx.new_view(|cx| {
                 ProjectDiagnosticsEditor::new(workspace.project().clone(), workspace_handle, cx)
             });
             workspace.add_item(Box::new(diagnostics), cx);
@@ -231,6 +208,12 @@ impl ProjectDiagnosticsEditor {
         cx.notify();
     }
 
+    fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
+        if self.focus_handle.is_focused(cx) && !self.path_states.is_empty() {
+            self.editor.focus_handle(cx).focus(cx)
+        }
+    }
+
     fn update_excerpts(
         &mut self,
         language_server_id: Option<LanguageServerId>,
@@ -304,9 +287,10 @@ impl ProjectDiagnosticsEditor {
                 let _: Vec<()> = try_join_all(paths_to_recheck.into_iter().map(|path| {
                     let mut cx = cx.clone();
                     let project = project.clone();
+                    let this = this.clone();
                     async move {
                         let buffer = project
-                            .update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx))
+                            .update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx))?
                             .await
                             .with_context(|| format!("opening buffer for path {path:?}"))?;
                         this.update(&mut cx, |this, cx| {
@@ -321,7 +305,7 @@ impl ProjectDiagnosticsEditor {
 
                 this.update(&mut cx, |this, cx| {
                     this.summary = this.project.read(cx).diagnostic_summary(false, cx);
-                    cx.emit(Event::TitleChanged);
+                    cx.emit(EditorEvent::TitleChanged);
                 })?;
                 anyhow::Ok(())
             }
@@ -334,7 +318,7 @@ impl ProjectDiagnosticsEditor {
         &mut self,
         path: ProjectPath,
         language_server_id: Option<LanguageServerId>,
-        buffer: ModelHandle<Buffer>,
+        buffer: Model<Buffer>,
         cx: &mut ViewContext<Self>,
     ) {
         let was_empty = self.path_states.is_empty();
@@ -618,41 +602,32 @@ impl ProjectDiagnosticsEditor {
         });
 
         if self.path_states.is_empty() {
-            if self.editor.is_focused(cx) {
-                cx.focus_self();
+            if self.editor.focus_handle(cx).is_focused(cx) {
+                cx.focus(&self.focus_handle);
             }
-        } else if cx.handle().is_focused(cx) {
-            cx.focus(&self.editor);
+        } else if self.focus_handle.is_focused(cx) {
+            let focus_handle = self.editor.focus_handle(cx);
+            cx.focus(&focus_handle);
         }
         cx.notify();
     }
 }
 
-impl Item for ProjectDiagnosticsEditor {
-    fn tab_content<T: 'static>(
-        &self,
-        _detail: Option<usize>,
-        style: &theme::Tab,
-        cx: &AppContext,
-    ) -> AnyElement<T> {
-        render_summary(
-            &self.summary,
-            &style.label.text,
-            &theme::current(cx).project_diagnostics,
-        )
+impl FocusableView for ProjectDiagnosticsEditor {
+    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
+        self.focus_handle.clone()
     }
+}
 
-    fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) {
-        self.editor.for_each_project_item(cx, f)
-    }
+impl Item for ProjectDiagnosticsEditor {
+    type Event = EditorEvent;
 
-    fn is_singleton(&self, _: &AppContext) -> bool {
-        false
+    fn to_item_events(event: &EditorEvent, f: impl FnMut(ItemEvent)) {
+        Editor::to_item_events(event, f)
     }
 
-    fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
-        self.editor
-            .update(cx, |editor, cx| editor.added_to_workspace(workspace, cx));
+    fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
+        self.editor.update(cx, |editor, cx| editor.deactivated(cx));
     }
 
     fn navigate(&mut self, data: Box<dyn Any>, cx: &mut ViewContext<Self>) -> bool {
@@ -660,10 +635,82 @@ impl Item for ProjectDiagnosticsEditor {
             .update(cx, |editor, cx| editor.navigate(data, cx))
     }
 
-    fn tab_tooltip_text(&self, _: &AppContext) -> Option<Cow<str>> {
+    fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> {
         Some("Project Diagnostics".into())
     }
 
+    fn tab_content(&self, _detail: Option<usize>, selected: bool, _: &WindowContext) -> AnyElement {
+        if self.summary.error_count == 0 && self.summary.warning_count == 0 {
+            let label = Label::new("No problems");
+            label.into_any_element()
+        } else {
+            h_stack()
+                .gap_1()
+                .when(self.summary.error_count > 0, |then| {
+                    then.child(
+                        h_stack()
+                            .gap_1()
+                            .child(IconElement::new(Icon::XCircle).color(Color::Error))
+                            .child(Label::new(self.summary.error_count.to_string()).color(
+                                if selected {
+                                    Color::Default
+                                } else {
+                                    Color::Muted
+                                },
+                            )),
+                    )
+                })
+                .when(self.summary.warning_count > 0, |then| {
+                    then.child(
+                        h_stack()
+                            .gap_1()
+                            .child(
+                                IconElement::new(Icon::ExclamationTriangle).color(Color::Warning),
+                            )
+                            .child(Label::new(self.summary.warning_count.to_string()).color(
+                                if selected {
+                                    Color::Default
+                                } else {
+                                    Color::Muted
+                                },
+                            )),
+                    )
+                })
+                .into_any_element()
+        }
+    }
+
+    fn for_each_project_item(
+        &self,
+        cx: &AppContext,
+        f: &mut dyn FnMut(gpui::EntityId, &dyn project::Item),
+    ) {
+        self.editor.for_each_project_item(cx, f)
+    }
+
+    fn is_singleton(&self, _: &AppContext) -> bool {
+        false
+    }
+
+    fn set_nav_history(&mut self, nav_history: ItemNavHistory, cx: &mut ViewContext<Self>) {
+        self.editor.update(cx, |editor, _| {
+            editor.set_nav_history(Some(nav_history));
+        });
+    }
+
+    fn clone_on_split(
+        &self,
+        _workspace_id: workspace::WorkspaceId,
+        cx: &mut ViewContext<Self>,
+    ) -> Option<View<Self>>
+    where
+        Self: Sized,
+    {
+        Some(cx.new_view(|cx| {
+            ProjectDiagnosticsEditor::new(self.project.clone(), self.workspace.clone(), cx)
+        }))
+    }
+
     fn is_dirty(&self, cx: &AppContext) -> bool {
         self.excerpts.read(cx).is_dirty(cx)
     }
@@ -676,209 +723,133 @@ impl Item for ProjectDiagnosticsEditor {
         true
     }
 
-    fn save(
-        &mut self,
-        project: ModelHandle<Project>,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<Result<()>> {
+    fn save(&mut self, project: Model<Project>, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
         self.editor.save(project, cx)
     }
 
-    fn reload(
-        &mut self,
-        project: ModelHandle<Project>,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<Result<()>> {
-        self.editor.reload(project, cx)
-    }
-
     fn save_as(
         &mut self,
-        _: ModelHandle<Project>,
+        _: Model<Project>,
         _: PathBuf,
         _: &mut ViewContext<Self>,
     ) -> Task<Result<()>> {
         unreachable!()
     }
 
-    fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
-        Editor::to_item_events(event)
-    }
-
-    fn set_nav_history(&mut self, nav_history: ItemNavHistory, cx: &mut ViewContext<Self>) {
-        self.editor.update(cx, |editor, _| {
-            editor.set_nav_history(Some(nav_history));
-        });
-    }
-
-    fn clone_on_split(
-        &self,
-        _workspace_id: workspace::WorkspaceId,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<Self>
-    where
-        Self: Sized,
-    {
-        Some(ProjectDiagnosticsEditor::new(
-            self.project.clone(),
-            self.workspace.clone(),
-            cx,
-        ))
+    fn reload(&mut self, project: Model<Project>, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
+        self.editor.reload(project, cx)
     }
 
     fn act_as_type<'a>(
         &'a self,
         type_id: TypeId,
-        self_handle: &'a ViewHandle<Self>,
+        self_handle: &'a View<Self>,
         _: &'a AppContext,
-    ) -> Option<&AnyViewHandle> {
+    ) -> Option<AnyView> {
         if type_id == TypeId::of::<Self>() {
-            Some(self_handle)
+            Some(self_handle.to_any())
         } else if type_id == TypeId::of::<Editor>() {
-            Some(&self.editor)
+            Some(self.editor.to_any())
         } else {
             None
         }
     }
 
-    fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
-        self.editor.update(cx, |editor, cx| editor.deactivated(cx));
-    }
-
-    fn serialized_item_kind() -> Option<&'static str> {
-        Some("diagnostics")
+    fn breadcrumb_location(&self) -> ToolbarItemLocation {
+        ToolbarItemLocation::PrimaryLeft
     }
 
     fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
         self.editor.breadcrumbs(theme, cx)
     }
 
-    fn breadcrumb_location(&self) -> ToolbarItemLocation {
-        ToolbarItemLocation::PrimaryLeft { flex: None }
+    fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
+        self.editor
+            .update(cx, |editor, cx| editor.added_to_workspace(workspace, cx));
+    }
+
+    fn serialized_item_kind() -> Option<&'static str> {
+        Some("diagnostics")
     }
 
     fn deserialize(
-        project: ModelHandle<Project>,
-        workspace: WeakViewHandle<Workspace>,
+        project: Model<Project>,
+        workspace: WeakView<Workspace>,
         _workspace_id: workspace::WorkspaceId,
         _item_id: workspace::ItemId,
         cx: &mut ViewContext<Pane>,
-    ) -> Task<Result<ViewHandle<Self>>> {
-        Task::ready(Ok(cx.add_view(|cx| Self::new(project, workspace, cx))))
+    ) -> Task<Result<View<Self>>> {
+        Task::ready(Ok(cx.new_view(|cx| Self::new(project, workspace, cx))))
     }
 }
 
 fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
-    let (message, highlights) = highlight_diagnostic_message(Vec::new(), &diagnostic.message);
+    let (message, code_ranges) = highlight_diagnostic_message(&diagnostic);
+    let message: SharedString = message.into();
     Arc::new(move |cx| {
-        let settings = settings::get::<ThemeSettings>(cx);
-        let theme = &settings.theme.editor;
-        let style = theme.diagnostic_header.clone();
-        let font_size = (style.text_scale_factor * settings.buffer_font_size(cx)).round();
-        let icon_width = cx.em_width * style.icon_width_factor;
-        let icon = if diagnostic.severity == DiagnosticSeverity::ERROR {
-            Svg::new("icons/error.svg").with_color(theme.error_diagnostic.message.text.color)
-        } else {
-            Svg::new("icons/warning.svg").with_color(theme.warning_diagnostic.message.text.color)
-        };
-
-        Flex::row()
-            .with_child(
-                icon.constrained()
-                    .with_width(icon_width)
-                    .aligned()
-                    .contained()
-                    .with_margin_right(cx.gutter_padding),
+        let highlight_style: HighlightStyle = cx.theme().colors().text_accent.into();
+        h_stack()
+            .id("diagnostic header")
+            .py_2()
+            .pl_10()
+            .pr_5()
+            .w_full()
+            .justify_between()
+            .gap_2()
+            .child(
+                h_stack()
+                    .gap_3()
+                    .map(|stack| {
+                        stack.child(
+                            svg()
+                                .size(cx.text_style().font_size)
+                                .flex_none()
+                                .map(|icon| {
+                                    if diagnostic.severity == DiagnosticSeverity::ERROR {
+                                        icon.path(Icon::XCircle.path())
+                                            .text_color(Color::Error.color(cx))
+                                    } else {
+                                        icon.path(Icon::ExclamationTriangle.path())
+                                            .text_color(Color::Warning.color(cx))
+                                    }
+                                }),
+                        )
+                    })
+                    .child(
+                        h_stack()
+                            .gap_1()
+                            .child(
+                                StyledText::new(message.clone()).with_highlights(
+                                    &cx.text_style(),
+                                    code_ranges
+                                        .iter()
+                                        .map(|range| (range.clone(), highlight_style)),
+                                ),
+                            )
+                            .when_some(diagnostic.code.as_ref(), |stack, code| {
+                                stack.child(
+                                    div()
+                                        .child(SharedString::from(format!("({code})")))
+                                        .text_color(cx.theme().colors().text_muted),
+                                )
+                            }),
+                    ),
             )
-            .with_children(diagnostic.source.as_ref().map(|source| {
-                Label::new(
-                    format!("{source}: "),
-                    style.source.label.clone().with_font_size(font_size),
-                )
-                .contained()
-                .with_style(style.message.container)
-                .aligned()
-            }))
-            .with_child(
-                Label::new(
-                    message.clone(),
-                    style.message.label.clone().with_font_size(font_size),
-                )
-                .with_highlights(highlights.clone())
-                .contained()
-                .with_style(style.message.container)
-                .aligned(),
+            .child(
+                h_stack()
+                    .gap_1()
+                    .when_some(diagnostic.source.as_ref(), |stack, source| {
+                        stack.child(
+                            div()
+                                .child(SharedString::from(source.clone()))
+                                .text_color(cx.theme().colors().text_muted),
+                        )
+                    }),
             )
-            .with_children(diagnostic.code.clone().map(|code| {
-                Label::new(code, style.code.text.clone().with_font_size(font_size))
-                    .contained()
-                    .with_style(style.code.container)
-                    .aligned()
-            }))
-            .contained()
-            .with_style(style.container)
-            .with_padding_left(cx.gutter_padding)
-            .with_padding_right(cx.gutter_padding)
-            .expanded()
-            .into_any_named("diagnostic header")
+            .into_any_element()
     })
 }
 
-pub(crate) fn render_summary<T: 'static>(
-    summary: &DiagnosticSummary,
-    text_style: &TextStyle,
-    theme: &theme::ProjectDiagnostics,
-) -> AnyElement<T> {
-    if summary.error_count == 0 && summary.warning_count == 0 {
-        Label::new("No problems", text_style.clone()).into_any()
-    } else {
-        let icon_width = theme.tab_icon_width;
-        let icon_spacing = theme.tab_icon_spacing;
-        let summary_spacing = theme.tab_summary_spacing;
-        Flex::row()
-            .with_child(
-                Svg::new("icons/error.svg")
-                    .with_color(text_style.color)
-                    .constrained()
-                    .with_width(icon_width)
-                    .aligned()
-                    .contained()
-                    .with_margin_right(icon_spacing),
-            )
-            .with_child(
-                Label::new(
-                    summary.error_count.to_string(),
-                    LabelStyle {
-                        text: text_style.clone(),
-                        highlight_text: None,
-                    },
-                )
-                .aligned(),
-            )
-            .with_child(
-                Svg::new("icons/warning.svg")
-                    .with_color(text_style.color)
-                    .constrained()
-                    .with_width(icon_width)
-                    .aligned()
-                    .contained()
-                    .with_margin_left(summary_spacing)
-                    .with_margin_right(icon_spacing),
-            )
-            .with_child(
-                Label::new(
-                    summary.warning_count.to_string(),
-                    LabelStyle {
-                        text: text_style.clone(),
-                        highlight_text: None,
-                    },
-                )
-                .aligned(),
-            )
-            .into_any()
-    }
-}
-
 fn compare_diagnostics<L: language::ToOffset, R: language::ToOffset>(
     lhs: &DiagnosticEntry<L>,
     rhs: &DiagnosticEntry<R>,
@@ -904,7 +875,7 @@ mod tests {
         display_map::{BlockContext, TransformBlock},
         DisplayPoint,
     };
-    use gpui::{TestAppContext, WindowContext};
+    use gpui::{px, TestAppContext, VisualTestContext, WindowContext};
     use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16, Unclipped};
     use project::FakeFs;
     use serde_json::json;
@@ -915,7 +886,7 @@ mod tests {
     async fn test_diagnostics(cx: &mut TestAppContext) {
         init_test(cx);
 
-        let fs = FakeFs::new(cx.background());
+        let fs = FakeFs::new(cx.executor());
         fs.insert_tree(
             "/test",
             json!({
@@ -945,7 +916,8 @@ mod tests {
         let language_server_id = LanguageServerId(0);
         let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let workspace = window.root(cx);
+        let cx = &mut VisualTestContext::from_window(*window, cx);
+        let workspace = window.root(cx).unwrap();
 
         // Create some diagnostics
         project.update(cx, |project, cx| {
@@ -1032,7 +1004,7 @@ mod tests {
         });
 
         // Open the project diagnostics view while there are already diagnostics.
-        let view = window.add_view(cx, |cx| {
+        let view = window.build_view(cx, |cx| {
             ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)
         });
 
@@ -1320,7 +1292,7 @@ mod tests {
     async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
         init_test(cx);
 
-        let fs = FakeFs::new(cx.background());
+        let fs = FakeFs::new(cx.executor());
         fs.insert_tree(
             "/test",
             json!({
@@ -1339,9 +1311,10 @@ mod tests {
         let server_id_2 = LanguageServerId(101);
         let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let workspace = window.root(cx);
+        let cx = &mut VisualTestContext::from_window(*window, cx);
+        let workspace = window.root(cx).unwrap();
 
-        let view = window.add_view(cx, |cx| {
+        let view = window.build_view(cx, |cx| {
             ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)
         });
 
@@ -1376,7 +1349,7 @@ mod tests {
         });
 
         // Only the first language server's diagnostics are shown.
-        cx.foreground().run_until_parked();
+        cx.executor().run_until_parked();
         view.update(cx, |view, cx| {
             assert_eq!(
                 editor_blocks(&view.editor, cx),
@@ -1424,7 +1397,7 @@ mod tests {
         });
 
         // Both language server's diagnostics are shown.
-        cx.foreground().run_until_parked();
+        cx.executor().run_until_parked();
         view.update(cx, |view, cx| {
             assert_eq!(
                 editor_blocks(&view.editor, cx),
@@ -1492,7 +1465,7 @@ mod tests {
         });
 
         // Only the first language server's diagnostics are updated.
-        cx.foreground().run_until_parked();
+        cx.executor().run_until_parked();
         view.update(cx, |view, cx| {
             assert_eq!(
                 editor_blocks(&view.editor, cx),
@@ -1550,7 +1523,7 @@ mod tests {
         });
 
         // Both language servers' diagnostics are updated.
-        cx.foreground().run_until_parked();
+        cx.executor().run_until_parked();
         view.update(cx, |view, cx| {
             assert_eq!(
                 editor_blocks(&view.editor, cx),
@@ -1586,8 +1559,9 @@ mod tests {
 
     fn init_test(cx: &mut TestAppContext) {
         cx.update(|cx| {
-            cx.set_global(SettingsStore::test(cx));
-            theme::init((), cx);
+            let settings = SettingsStore::test(cx);
+            cx.set_global(settings);
+            theme::init(theme::LoadThemes::JustBase, cx);
             language::init(cx);
             client::init_settings(cx);
             workspace::init_settings(cx);
@@ -1596,7 +1570,7 @@ mod tests {
         });
     }
 
-    fn editor_blocks(editor: &ViewHandle<Editor>, cx: &mut WindowContext) -> Vec<(u32, String)> {
+    fn editor_blocks(editor: &View<Editor>, cx: &mut WindowContext) -> Vec<(u32, SharedString)> {
         editor.update(cx, |editor, cx| {
             let snapshot = editor.snapshot(cx);
             snapshot
@@ -1607,23 +1581,25 @@ mod tests {
                         TransformBlock::Custom(block) => block
                             .render(&mut BlockContext {
                                 view_context: cx,
-                                anchor_x: 0.,
-                                scroll_x: 0.,
-                                gutter_padding: 0.,
-                                gutter_width: 0.,
-                                line_height: 0.,
-                                em_width: 0.,
+                                anchor_x: px(0.),
+                                gutter_padding: px(0.),
+                                gutter_width: px(0.),
+                                line_height: px(0.),
+                                em_width: px(0.),
                                 block_id: ix,
+                                editor_style: &editor::EditorStyle::default(),
                             })
-                            .name()?
-                            .to_string(),
+                            .inner_id()?
+                            .try_into()
+                            .ok()?,
+
                         TransformBlock::ExcerptHeader {
                             starts_new_buffer, ..
                         } => {
                             if *starts_new_buffer {
-                                "path header block".to_string()
+                                "path header block".into()
                             } else {
-                                "collapsed context".to_string()
+                                "collapsed context".into()
                             }
                         }
                     };

crates/diagnostics/src/items.rs 🔗

@@ -1,27 +1,105 @@
 use collections::HashSet;
-use editor::{Editor, GoToDiagnostic};
+use editor::Editor;
 use gpui::{
-    elements::*,
-    platform::{CursorStyle, MouseButton},
-    serde_json, AppContext, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
+    rems, EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, View,
+    ViewContext, WeakView,
 };
 use language::Diagnostic;
 use lsp::LanguageServerId;
-use workspace::{item::ItemHandle, StatusItemView, Workspace};
+use ui::{h_stack, prelude::*, Button, ButtonLike, Color, Icon, IconElement, Label, Tooltip};
+use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace};
 
-use crate::ProjectDiagnosticsEditor;
+use crate::{Deploy, ProjectDiagnosticsEditor};
 
 pub struct DiagnosticIndicator {
     summary: project::DiagnosticSummary,
-    active_editor: Option<WeakViewHandle<Editor>>,
-    workspace: WeakViewHandle<Workspace>,
+    active_editor: Option<WeakView<Editor>>,
+    workspace: WeakView<Workspace>,
     current_diagnostic: Option<Diagnostic>,
     in_progress_checks: HashSet<LanguageServerId>,
     _observe_active_editor: Option<Subscription>,
 }
 
-pub fn init(cx: &mut AppContext) {
-    cx.add_action(DiagnosticIndicator::go_to_next_diagnostic);
+impl Render for DiagnosticIndicator {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        let diagnostic_indicator = match (self.summary.error_count, self.summary.warning_count) {
+            (0, 0) => h_stack().child(
+                IconElement::new(Icon::Check)
+                    .size(IconSize::Small)
+                    .color(Color::Success),
+            ),
+            (0, warning_count) => h_stack()
+                .gap_1()
+                .child(
+                    IconElement::new(Icon::ExclamationTriangle)
+                        .size(IconSize::Small)
+                        .color(Color::Warning),
+                )
+                .child(Label::new(warning_count.to_string()).size(LabelSize::Small)),
+            (error_count, 0) => h_stack()
+                .gap_1()
+                .child(
+                    IconElement::new(Icon::XCircle)
+                        .size(IconSize::Small)
+                        .color(Color::Error),
+                )
+                .child(Label::new(error_count.to_string()).size(LabelSize::Small)),
+            (error_count, warning_count) => h_stack()
+                .gap_1()
+                .child(
+                    IconElement::new(Icon::XCircle)
+                        .size(IconSize::Small)
+                        .color(Color::Error),
+                )
+                .child(Label::new(error_count.to_string()).size(LabelSize::Small))
+                .child(
+                    IconElement::new(Icon::ExclamationTriangle)
+                        .size(IconSize::Small)
+                        .color(Color::Warning),
+                )
+                .child(Label::new(warning_count.to_string()).size(LabelSize::Small)),
+        };
+
+        let status = if !self.in_progress_checks.is_empty() {
+            Some(
+                Label::new("Checking…")
+                    .size(LabelSize::Small)
+                    .into_any_element(),
+            )
+        } else if let Some(diagnostic) = &self.current_diagnostic {
+            let message = diagnostic.message.split('\n').next().unwrap().to_string();
+            Some(
+                Button::new("diagnostic_message", message)
+                    .label_size(LabelSize::Small)
+                    .tooltip(|cx| {
+                        Tooltip::for_action("Next Diagnostic", &editor::GoToDiagnostic, cx)
+                    })
+                    .on_click(cx.listener(|this, _, cx| {
+                        this.go_to_next_diagnostic(cx);
+                    }))
+                    .into_any_element(),
+            )
+        } else {
+            None
+        };
+
+        h_stack()
+            .h(rems(1.375))
+            .gap_2()
+            .child(
+                ButtonLike::new("diagnostic-indicator")
+                    .child(diagnostic_indicator)
+                    .tooltip(|cx| Tooltip::for_action("Project Diagnostics", &Deploy, cx))
+                    .on_click(cx.listener(|this, _, cx| {
+                        if let Some(workspace) = this.workspace.upgrade() {
+                            workspace.update(cx, |workspace, cx| {
+                                ProjectDiagnosticsEditor::deploy(workspace, &Default::default(), cx)
+                            })
+                        }
+                    })),
+            )
+            .children(status)
+    }
 }
 
 impl DiagnosticIndicator {
@@ -32,19 +110,23 @@ impl DiagnosticIndicator {
                 this.in_progress_checks.insert(*language_server_id);
                 cx.notify();
             }
+
             project::Event::DiskBasedDiagnosticsFinished { language_server_id }
             | project::Event::LanguageServerRemoved(language_server_id) => {
                 this.summary = project.read(cx).diagnostic_summary(false, cx);
                 this.in_progress_checks.remove(language_server_id);
                 cx.notify();
             }
+
             project::Event::DiagnosticsUpdated { .. } => {
                 this.summary = project.read(cx).diagnostic_summary(false, cx);
                 cx.notify();
             }
+
             _ => {}
         })
         .detach();
+
         Self {
             summary: project.read(cx).diagnostic_summary(false, cx),
             in_progress_checks: project
@@ -58,15 +140,15 @@ impl DiagnosticIndicator {
         }
     }
 
-    fn go_to_next_diagnostic(&mut self, _: &GoToDiagnostic, cx: &mut ViewContext<Self>) {
-        if let Some(editor) = self.active_editor.as_ref().and_then(|e| e.upgrade(cx)) {
+    fn go_to_next_diagnostic(&mut self, cx: &mut ViewContext<Self>) {
+        if let Some(editor) = self.active_editor.as_ref().and_then(|e| e.upgrade()) {
             editor.update(cx, |editor, cx| {
                 editor.go_to_diagnostic_impl(editor::Direction::Next, cx);
             })
         }
     }
 
-    fn update(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
+    fn update(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
         let editor = editor.read(cx);
         let buffer = editor.buffer().read(cx);
         let cursor_position = editor.selections.newest::<usize>(cx).head();
@@ -83,146 +165,7 @@ impl DiagnosticIndicator {
     }
 }
 
-impl Entity for DiagnosticIndicator {
-    type Event = ();
-}
-
-impl View for DiagnosticIndicator {
-    fn ui_name() -> &'static str {
-        "DiagnosticIndicator"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        enum Summary {}
-        enum Message {}
-
-        let tooltip_style = theme::current(cx).tooltip.clone();
-        let in_progress = !self.in_progress_checks.is_empty();
-        let mut element = Flex::row().with_child(
-            MouseEventHandler::new::<Summary, _>(0, cx, |state, cx| {
-                let theme = theme::current(cx);
-                let style = theme
-                    .workspace
-                    .status_bar
-                    .diagnostic_summary
-                    .style_for(state);
-
-                let mut summary_row = Flex::row();
-                if self.summary.error_count > 0 {
-                    summary_row.add_child(
-                        Svg::new("icons/error.svg")
-                            .with_color(style.icon_color_error)
-                            .constrained()
-                            .with_width(style.icon_width)
-                            .aligned()
-                            .contained()
-                            .with_margin_right(style.icon_spacing),
-                    );
-                    summary_row.add_child(
-                        Label::new(self.summary.error_count.to_string(), style.text.clone())
-                            .aligned(),
-                    );
-                }
-
-                if self.summary.warning_count > 0 {
-                    summary_row.add_child(
-                        Svg::new("icons/warning.svg")
-                            .with_color(style.icon_color_warning)
-                            .constrained()
-                            .with_width(style.icon_width)
-                            .aligned()
-                            .contained()
-                            .with_margin_right(style.icon_spacing)
-                            .with_margin_left(if self.summary.error_count > 0 {
-                                style.summary_spacing
-                            } else {
-                                0.
-                            }),
-                    );
-                    summary_row.add_child(
-                        Label::new(self.summary.warning_count.to_string(), style.text.clone())
-                            .aligned(),
-                    );
-                }
-
-                if self.summary.error_count == 0 && self.summary.warning_count == 0 {
-                    summary_row.add_child(
-                        Svg::new("icons/check_circle.svg")
-                            .with_color(style.icon_color_ok)
-                            .constrained()
-                            .with_width(style.icon_width)
-                            .aligned()
-                            .into_any_named("ok-icon"),
-                    );
-                }
-
-                summary_row
-                    .constrained()
-                    .with_height(style.height)
-                    .contained()
-                    .with_style(if self.summary.error_count > 0 {
-                        style.container_error
-                    } else if self.summary.warning_count > 0 {
-                        style.container_warning
-                    } else {
-                        style.container_ok
-                    })
-            })
-            .with_cursor_style(CursorStyle::PointingHand)
-            .on_click(MouseButton::Left, |_, this, cx| {
-                if let Some(workspace) = this.workspace.upgrade(cx) {
-                    workspace.update(cx, |workspace, cx| {
-                        ProjectDiagnosticsEditor::deploy(workspace, &Default::default(), cx)
-                    })
-                }
-            })
-            .with_tooltip::<Summary>(
-                0,
-                "Project Diagnostics",
-                Some(Box::new(crate::Deploy)),
-                tooltip_style,
-                cx,
-            )
-            .aligned()
-            .into_any(),
-        );
-
-        let style = &theme::current(cx).workspace.status_bar;
-        let item_spacing = style.item_spacing;
-
-        if in_progress {
-            element.add_child(
-                Label::new("Checking…", style.diagnostic_message.default.text.clone())
-                    .aligned()
-                    .contained()
-                    .with_margin_left(item_spacing),
-            );
-        } else if let Some(diagnostic) = &self.current_diagnostic {
-            let message_style = style.diagnostic_message.clone();
-            element.add_child(
-                MouseEventHandler::new::<Message, _>(1, cx, |state, _| {
-                    Label::new(
-                        diagnostic.message.split('\n').next().unwrap().to_string(),
-                        message_style.style_for(state).text.clone(),
-                    )
-                    .aligned()
-                    .contained()
-                    .with_margin_left(item_spacing)
-                })
-                .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, |_, this, cx| {
-                    this.go_to_next_diagnostic(&Default::default(), cx)
-                }),
-            );
-        }
-
-        element.into_any_named("diagnostic indicator")
-    }
-
-    fn debug_json(&self, _: &gpui::AppContext) -> serde_json::Value {
-        serde_json::json!({ "summary": self.summary })
-    }
-}
+impl EventEmitter<ToolbarItemEvent> for DiagnosticIndicator {}
 
 impl StatusItemView for DiagnosticIndicator {
     fn set_active_pane_item(

crates/diagnostics/src/project_diagnostics_settings.rs 🔗

@@ -11,14 +11,14 @@ pub struct ProjectDiagnosticsSettingsContent {
     include_warnings: Option<bool>,
 }
 
-impl settings::Setting for ProjectDiagnosticsSettings {
+impl settings::Settings for ProjectDiagnosticsSettings {
     const KEY: Option<&'static str> = Some("diagnostics");
     type FileContent = ProjectDiagnosticsSettingsContent;
 
     fn load(
         default_value: &Self::FileContent,
         user_values: &[&Self::FileContent],
-        _cx: &gpui::AppContext,
+        _cx: &mut gpui::AppContext,
     ) -> anyhow::Result<Self>
     where
         Self: Sized,

crates/diagnostics/src/toolbar_controls.rs 🔗

@@ -1,55 +1,44 @@
-use crate::{ProjectDiagnosticsEditor, ToggleWarnings};
-use gpui::{
-    elements::*,
-    platform::{CursorStyle, MouseButton},
-    Action, Entity, EventContext, View, ViewContext, WeakViewHandle,
-};
-use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView};
+use crate::ProjectDiagnosticsEditor;
+use gpui::{div, EventEmitter, ParentElement, Render, ViewContext, WeakView};
+use ui::prelude::*;
+use ui::{Icon, IconButton, Tooltip};
+use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
 
 pub struct ToolbarControls {
-    editor: Option<WeakViewHandle<ProjectDiagnosticsEditor>>,
+    editor: Option<WeakView<ProjectDiagnosticsEditor>>,
 }
 
-impl Entity for ToolbarControls {
-    type Event = ();
-}
-
-impl View for ToolbarControls {
-    fn ui_name() -> &'static str {
-        "ToolbarControls"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+impl Render for ToolbarControls {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
         let include_warnings = self
             .editor
             .as_ref()
-            .and_then(|editor| editor.upgrade(cx))
+            .and_then(|editor| editor.upgrade())
             .map(|editor| editor.read(cx).include_warnings)
             .unwrap_or(false);
+
         let tooltip = if include_warnings {
-            "Exclude Warnings".into()
+            "Exclude Warnings"
         } else {
-            "Include Warnings".into()
+            "Include Warnings"
         };
-        Flex::row()
-            .with_child(render_toggle_button(
-                0,
-                "icons/warning.svg",
-                include_warnings,
-                (tooltip, Some(Box::new(ToggleWarnings))),
-                cx,
-                move |this, cx| {
-                    if let Some(editor) = this.editor.and_then(|editor| editor.upgrade(cx)) {
+
+        div().child(
+            IconButton::new("toggle-warnings", Icon::ExclamationTriangle)
+                .tooltip(move |cx| Tooltip::text(tooltip, cx))
+                .on_click(cx.listener(|this, _, cx| {
+                    if let Some(editor) = this.editor.as_ref().and_then(|editor| editor.upgrade()) {
                         editor.update(cx, |editor, cx| {
-                            editor.toggle_warnings(&Default::default(), cx)
+                            editor.toggle_warnings(&Default::default(), cx);
                         });
                     }
-                },
-            ))
-            .into_any()
+                })),
+        )
     }
 }
 
+impl EventEmitter<ToolbarItemEvent> for ToolbarControls {}
+
 impl ToolbarItemView for ToolbarControls {
     fn set_active_pane_item(
         &mut self,
@@ -59,7 +48,7 @@ impl ToolbarItemView for ToolbarControls {
         if let Some(pane_item) = active_pane_item.as_ref() {
             if let Some(editor) = pane_item.downcast::<ProjectDiagnosticsEditor>() {
                 self.editor = Some(editor.downgrade());
-                ToolbarItemLocation::PrimaryRight { flex: None }
+                ToolbarItemLocation::PrimaryRight
             } else {
                 ToolbarItemLocation::Hidden
             }
@@ -74,42 +63,3 @@ impl ToolbarControls {
         ToolbarControls { editor: None }
     }
 }
-
-fn render_toggle_button<
-    F: 'static + Fn(&mut ToolbarControls, &mut EventContext<ToolbarControls>),
->(
-    index: usize,
-    icon: &'static str,
-    toggled: bool,
-    tooltip: (String, Option<Box<dyn Action>>),
-    cx: &mut ViewContext<ToolbarControls>,
-    on_click: F,
-) -> AnyElement<ToolbarControls> {
-    enum Button {}
-
-    let theme = theme::current(cx);
-    let (tooltip_text, action) = tooltip;
-
-    MouseEventHandler::new::<Button, _>(index, cx, |mouse_state, _| {
-        let style = theme
-            .workspace
-            .toolbar
-            .toggleable_tool
-            .in_state(toggled)
-            .style_for(mouse_state);
-        Svg::new(icon)
-            .with_color(style.color)
-            .constrained()
-            .with_width(style.icon_width)
-            .aligned()
-            .constrained()
-            .with_width(style.button_width)
-            .with_height(style.button_width)
-            .contained()
-            .with_style(style.container)
-    })
-    .with_cursor_style(CursorStyle::PointingHand)
-    .on_click(MouseButton::Left, move |_, view, cx| on_click(view, cx))
-    .with_tooltip::<Button>(index, tooltip_text, action, theme.tooltip.clone(), cx)
-    .into_any_named("quick action bar button")
-}

crates/diagnostics2/Cargo.toml 🔗

@@ -1,43 +0,0 @@
-[package]
-name = "diagnostics2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/diagnostics.rs"
-doctest = false
-
-[dependencies]
-collections = { path = "../collections" }
-editor = { package = "editor2", path = "../editor2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-ui = { package = "ui2", path = "../ui2" }
-language = { package = "language2", path = "../language2" }
-lsp = { package = "lsp2", path = "../lsp2" }
-project = { package = "project2", path = "../project2" }
-settings = { package = "settings2", path = "../settings2" }
-theme = { package = "theme2", path = "../theme2" }
-util = { path = "../util" }
-workspace = { package = "workspace2", path = "../workspace2" }
-
-log.workspace = true
-anyhow.workspace = true
-futures.workspace = true
-schemars.workspace = true
-serde.workspace = true
-serde_derive.workspace = true
-smallvec.workspace = true
-postage.workspace = true
-
-[dev-dependencies]
-client = { package = "client2", path = "../client2", features = ["test-support"] }
-editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
-language = { package = "language2", path = "../language2", features = ["test-support"] }
-lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
-theme = { package = "theme2", path = "../theme2", features = ["test-support"] }
-
-serde_json.workspace = true
-unindent.workspace = true

crates/diagnostics2/src/diagnostics.rs 🔗

@@ -1,1612 +0,0 @@
-pub mod items;
-mod project_diagnostics_settings;
-mod toolbar_controls;
-
-use anyhow::{Context as _, Result};
-use collections::{HashMap, HashSet};
-use editor::{
-    diagnostic_block_renderer,
-    display_map::{BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock},
-    highlight_diagnostic_message,
-    scroll::autoscroll::Autoscroll,
-    Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, ToOffset,
-};
-use futures::future::try_join_all;
-use gpui::{
-    actions, div, svg, AnyElement, AnyView, AppContext, Context, EventEmitter, FocusHandle,
-    FocusableView, HighlightStyle, InteractiveElement, IntoElement, Model, ParentElement, Render,
-    SharedString, Styled, StyledText, Subscription, Task, View, ViewContext, VisualContext,
-    WeakView, WindowContext,
-};
-use language::{
-    Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection,
-    SelectionGoal,
-};
-use lsp::LanguageServerId;
-use project::{DiagnosticSummary, Project, ProjectPath};
-use project_diagnostics_settings::ProjectDiagnosticsSettings;
-use settings::Settings;
-use std::{
-    any::{Any, TypeId},
-    cmp::Ordering,
-    mem,
-    ops::Range,
-    path::PathBuf,
-    sync::Arc,
-};
-use theme::ActiveTheme;
-pub use toolbar_controls::ToolbarControls;
-use ui::{h_stack, prelude::*, Icon, IconElement, Label};
-use util::TryFutureExt;
-use workspace::{
-    item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
-    ItemNavHistory, Pane, ToolbarItemLocation, Workspace,
-};
-
-actions!(diagnostics, [Deploy, ToggleWarnings]);
-
-const CONTEXT_LINE_COUNT: u32 = 1;
-
-pub fn init(cx: &mut AppContext) {
-    ProjectDiagnosticsSettings::register(cx);
-    cx.observe_new_views(ProjectDiagnosticsEditor::register)
-        .detach();
-}
-
-struct ProjectDiagnosticsEditor {
-    project: Model<Project>,
-    workspace: WeakView<Workspace>,
-    focus_handle: FocusHandle,
-    editor: View<Editor>,
-    summary: DiagnosticSummary,
-    excerpts: Model<MultiBuffer>,
-    path_states: Vec<PathState>,
-    paths_to_update: HashMap<LanguageServerId, HashSet<ProjectPath>>,
-    current_diagnostics: HashMap<LanguageServerId, HashSet<ProjectPath>>,
-    include_warnings: bool,
-    _subscriptions: Vec<Subscription>,
-}
-
-struct PathState {
-    path: ProjectPath,
-    diagnostic_groups: Vec<DiagnosticGroupState>,
-}
-
-#[derive(Clone, Debug, PartialEq)]
-struct Jump {
-    path: ProjectPath,
-    position: Point,
-    anchor: Anchor,
-}
-
-struct DiagnosticGroupState {
-    language_server_id: LanguageServerId,
-    primary_diagnostic: DiagnosticEntry<language::Anchor>,
-    primary_excerpt_ix: usize,
-    excerpts: Vec<ExcerptId>,
-    blocks: HashSet<BlockId>,
-    block_count: usize,
-}
-
-impl EventEmitter<EditorEvent> for ProjectDiagnosticsEditor {}
-
-impl Render for ProjectDiagnosticsEditor {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element {
-        let child = if self.path_states.is_empty() {
-            div()
-                .bg(cx.theme().colors().editor_background)
-                .flex()
-                .items_center()
-                .justify_center()
-                .size_full()
-                .child(Label::new("No problems in workspace"))
-        } else {
-            div().size_full().child(self.editor.clone())
-        };
-
-        div()
-            .track_focus(&self.focus_handle)
-            .size_full()
-            .on_action(cx.listener(Self::toggle_warnings))
-            .child(child)
-    }
-}
-
-impl ProjectDiagnosticsEditor {
-    fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
-        workspace.register_action(Self::deploy);
-    }
-
-    fn new(
-        project_handle: Model<Project>,
-        workspace: WeakView<Workspace>,
-        cx: &mut ViewContext<Self>,
-    ) -> Self {
-        let project_event_subscription =
-            cx.subscribe(&project_handle, |this, _, event, cx| match event {
-                project::Event::DiskBasedDiagnosticsFinished { language_server_id } => {
-                    log::debug!("Disk based diagnostics finished for server {language_server_id}");
-                    this.update_excerpts(Some(*language_server_id), cx);
-                }
-                project::Event::DiagnosticsUpdated {
-                    language_server_id,
-                    path,
-                } => {
-                    log::debug!("Adding path {path:?} to update for server {language_server_id}");
-                    this.paths_to_update
-                        .entry(*language_server_id)
-                        .or_default()
-                        .insert(path.clone());
-                    if this.editor.read(cx).selections.all::<usize>(cx).is_empty()
-                        && !this.is_dirty(cx)
-                    {
-                        this.update_excerpts(Some(*language_server_id), cx);
-                    }
-                }
-                _ => {}
-            });
-
-        let focus_handle = cx.focus_handle();
-
-        let focus_in_subscription =
-            cx.on_focus_in(&focus_handle, |diagnostics, cx| diagnostics.focus_in(cx));
-
-        let excerpts = cx.new_model(|cx| MultiBuffer::new(project_handle.read(cx).replica_id()));
-        let editor = cx.new_view(|cx| {
-            let mut editor =
-                Editor::for_multibuffer(excerpts.clone(), Some(project_handle.clone()), cx);
-            editor.set_vertical_scroll_margin(5, cx);
-            editor
-        });
-        let editor_event_subscription =
-            cx.subscribe(&editor, |this, _editor, event: &EditorEvent, cx| {
-                cx.emit(event.clone());
-                if event == &EditorEvent::Focused && this.path_states.is_empty() {
-                    cx.focus(&this.focus_handle);
-                }
-            });
-
-        let project = project_handle.read(cx);
-        let summary = project.diagnostic_summary(false, cx);
-        let mut this = Self {
-            project: project_handle,
-            summary,
-            workspace,
-            excerpts,
-            focus_handle,
-            editor,
-            path_states: Default::default(),
-            paths_to_update: HashMap::default(),
-            include_warnings: ProjectDiagnosticsSettings::get_global(cx).include_warnings,
-            current_diagnostics: HashMap::default(),
-            _subscriptions: vec![
-                project_event_subscription,
-                editor_event_subscription,
-                focus_in_subscription,
-            ],
-        };
-        this.update_excerpts(None, cx);
-        this
-    }
-
-    fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
-        if let Some(existing) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) {
-            workspace.activate_item(&existing, cx);
-        } else {
-            let workspace_handle = cx.view().downgrade();
-            let diagnostics = cx.new_view(|cx| {
-                ProjectDiagnosticsEditor::new(workspace.project().clone(), workspace_handle, cx)
-            });
-            workspace.add_item(Box::new(diagnostics), cx);
-        }
-    }
-
-    fn toggle_warnings(&mut self, _: &ToggleWarnings, cx: &mut ViewContext<Self>) {
-        self.include_warnings = !self.include_warnings;
-        self.paths_to_update = self.current_diagnostics.clone();
-        self.update_excerpts(None, cx);
-        cx.notify();
-    }
-
-    fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
-        if self.focus_handle.is_focused(cx) && !self.path_states.is_empty() {
-            self.editor.focus_handle(cx).focus(cx)
-        }
-    }
-
-    fn update_excerpts(
-        &mut self,
-        language_server_id: Option<LanguageServerId>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        log::debug!("Updating excerpts for server {language_server_id:?}");
-        let mut paths_to_recheck = HashSet::default();
-        let mut new_summaries: HashMap<LanguageServerId, HashSet<ProjectPath>> = self
-            .project
-            .read(cx)
-            .diagnostic_summaries(false, cx)
-            .fold(HashMap::default(), |mut summaries, (path, server_id, _)| {
-                summaries.entry(server_id).or_default().insert(path);
-                summaries
-            });
-        let mut old_diagnostics = if let Some(language_server_id) = language_server_id {
-            new_summaries.retain(|server_id, _| server_id == &language_server_id);
-            self.paths_to_update.retain(|server_id, paths| {
-                if server_id == &language_server_id {
-                    paths_to_recheck.extend(paths.drain());
-                    false
-                } else {
-                    true
-                }
-            });
-            let mut old_diagnostics = HashMap::default();
-            if let Some(new_paths) = new_summaries.get(&language_server_id) {
-                if let Some(old_paths) = self
-                    .current_diagnostics
-                    .insert(language_server_id, new_paths.clone())
-                {
-                    old_diagnostics.insert(language_server_id, old_paths);
-                }
-            } else {
-                if let Some(old_paths) = self.current_diagnostics.remove(&language_server_id) {
-                    old_diagnostics.insert(language_server_id, old_paths);
-                }
-            }
-            old_diagnostics
-        } else {
-            paths_to_recheck.extend(self.paths_to_update.drain().flat_map(|(_, paths)| paths));
-            mem::replace(&mut self.current_diagnostics, new_summaries.clone())
-        };
-        for (server_id, new_paths) in new_summaries {
-            match old_diagnostics.remove(&server_id) {
-                Some(mut old_paths) => {
-                    paths_to_recheck.extend(
-                        new_paths
-                            .into_iter()
-                            .filter(|new_path| !old_paths.remove(new_path)),
-                    );
-                    paths_to_recheck.extend(old_paths);
-                }
-                None => paths_to_recheck.extend(new_paths),
-            }
-        }
-        paths_to_recheck.extend(old_diagnostics.into_iter().flat_map(|(_, paths)| paths));
-
-        if paths_to_recheck.is_empty() {
-            log::debug!("No paths to recheck for language server {language_server_id:?}");
-            return;
-        }
-        log::debug!(
-            "Rechecking {} paths for language server {:?}",
-            paths_to_recheck.len(),
-            language_server_id
-        );
-        let project = self.project.clone();
-        cx.spawn(|this, mut cx| {
-            async move {
-                let _: Vec<()> = try_join_all(paths_to_recheck.into_iter().map(|path| {
-                    let mut cx = cx.clone();
-                    let project = project.clone();
-                    let this = this.clone();
-                    async move {
-                        let buffer = project
-                            .update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx))?
-                            .await
-                            .with_context(|| format!("opening buffer for path {path:?}"))?;
-                        this.update(&mut cx, |this, cx| {
-                            this.populate_excerpts(path, language_server_id, buffer, cx);
-                        })
-                        .context("missing project")?;
-                        anyhow::Ok(())
-                    }
-                }))
-                .await
-                .context("rechecking diagnostics for paths")?;
-
-                this.update(&mut cx, |this, cx| {
-                    this.summary = this.project.read(cx).diagnostic_summary(false, cx);
-                    cx.emit(EditorEvent::TitleChanged);
-                })?;
-                anyhow::Ok(())
-            }
-            .log_err()
-        })
-        .detach();
-    }
-
-    fn populate_excerpts(
-        &mut self,
-        path: ProjectPath,
-        language_server_id: Option<LanguageServerId>,
-        buffer: Model<Buffer>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let was_empty = self.path_states.is_empty();
-        let snapshot = buffer.read(cx).snapshot();
-        let path_ix = match self.path_states.binary_search_by_key(&&path, |e| &e.path) {
-            Ok(ix) => ix,
-            Err(ix) => {
-                self.path_states.insert(
-                    ix,
-                    PathState {
-                        path: path.clone(),
-                        diagnostic_groups: Default::default(),
-                    },
-                );
-                ix
-            }
-        };
-
-        let mut prev_excerpt_id = if path_ix > 0 {
-            let prev_path_last_group = &self.path_states[path_ix - 1]
-                .diagnostic_groups
-                .last()
-                .unwrap();
-            prev_path_last_group.excerpts.last().unwrap().clone()
-        } else {
-            ExcerptId::min()
-        };
-
-        let path_state = &mut self.path_states[path_ix];
-        let mut groups_to_add = Vec::new();
-        let mut group_ixs_to_remove = Vec::new();
-        let mut blocks_to_add = Vec::new();
-        let mut blocks_to_remove = HashSet::default();
-        let mut first_excerpt_id = None;
-        let max_severity = if self.include_warnings {
-            DiagnosticSeverity::WARNING
-        } else {
-            DiagnosticSeverity::ERROR
-        };
-        let excerpts_snapshot = self.excerpts.update(cx, |excerpts, excerpts_cx| {
-            let mut old_groups = path_state.diagnostic_groups.iter().enumerate().peekable();
-            let mut new_groups = snapshot
-                .diagnostic_groups(language_server_id)
-                .into_iter()
-                .filter(|(_, group)| {
-                    group.entries[group.primary_ix].diagnostic.severity <= max_severity
-                })
-                .peekable();
-            loop {
-                let mut to_insert = None;
-                let mut to_remove = None;
-                let mut to_keep = None;
-                match (old_groups.peek(), new_groups.peek()) {
-                    (None, None) => break,
-                    (None, Some(_)) => to_insert = new_groups.next(),
-                    (Some((_, old_group)), None) => {
-                        if language_server_id.map_or(true, |id| id == old_group.language_server_id)
-                        {
-                            to_remove = old_groups.next();
-                        } else {
-                            to_keep = old_groups.next();
-                        }
-                    }
-                    (Some((_, old_group)), Some((_, new_group))) => {
-                        let old_primary = &old_group.primary_diagnostic;
-                        let new_primary = &new_group.entries[new_group.primary_ix];
-                        match compare_diagnostics(old_primary, new_primary, &snapshot) {
-                            Ordering::Less => {
-                                if language_server_id
-                                    .map_or(true, |id| id == old_group.language_server_id)
-                                {
-                                    to_remove = old_groups.next();
-                                } else {
-                                    to_keep = old_groups.next();
-                                }
-                            }
-                            Ordering::Equal => {
-                                to_keep = old_groups.next();
-                                new_groups.next();
-                            }
-                            Ordering::Greater => to_insert = new_groups.next(),
-                        }
-                    }
-                }
-
-                if let Some((language_server_id, group)) = to_insert {
-                    let mut group_state = DiagnosticGroupState {
-                        language_server_id,
-                        primary_diagnostic: group.entries[group.primary_ix].clone(),
-                        primary_excerpt_ix: 0,
-                        excerpts: Default::default(),
-                        blocks: Default::default(),
-                        block_count: 0,
-                    };
-                    let mut pending_range: Option<(Range<Point>, usize)> = None;
-                    let mut is_first_excerpt_for_group = true;
-                    for (ix, entry) in group.entries.iter().map(Some).chain([None]).enumerate() {
-                        let resolved_entry = entry.map(|e| e.resolve::<Point>(&snapshot));
-                        if let Some((range, start_ix)) = &mut pending_range {
-                            if let Some(entry) = resolved_entry.as_ref() {
-                                if entry.range.start.row
-                                    <= range.end.row + 1 + CONTEXT_LINE_COUNT * 2
-                                {
-                                    range.end = range.end.max(entry.range.end);
-                                    continue;
-                                }
-                            }
-
-                            let excerpt_start =
-                                Point::new(range.start.row.saturating_sub(CONTEXT_LINE_COUNT), 0);
-                            let excerpt_end = snapshot.clip_point(
-                                Point::new(range.end.row + CONTEXT_LINE_COUNT, u32::MAX),
-                                Bias::Left,
-                            );
-                            let excerpt_id = excerpts
-                                .insert_excerpts_after(
-                                    prev_excerpt_id,
-                                    buffer.clone(),
-                                    [ExcerptRange {
-                                        context: excerpt_start..excerpt_end,
-                                        primary: Some(range.clone()),
-                                    }],
-                                    excerpts_cx,
-                                )
-                                .pop()
-                                .unwrap();
-
-                            prev_excerpt_id = excerpt_id.clone();
-                            first_excerpt_id.get_or_insert_with(|| prev_excerpt_id.clone());
-                            group_state.excerpts.push(excerpt_id.clone());
-                            let header_position = (excerpt_id.clone(), language::Anchor::MIN);
-
-                            if is_first_excerpt_for_group {
-                                is_first_excerpt_for_group = false;
-                                let mut primary =
-                                    group.entries[group.primary_ix].diagnostic.clone();
-                                primary.message =
-                                    primary.message.split('\n').next().unwrap().to_string();
-                                group_state.block_count += 1;
-                                blocks_to_add.push(BlockProperties {
-                                    position: header_position,
-                                    height: 2,
-                                    style: BlockStyle::Sticky,
-                                    render: diagnostic_header_renderer(primary),
-                                    disposition: BlockDisposition::Above,
-                                });
-                            }
-
-                            for entry in &group.entries[*start_ix..ix] {
-                                let mut diagnostic = entry.diagnostic.clone();
-                                if diagnostic.is_primary {
-                                    group_state.primary_excerpt_ix = group_state.excerpts.len() - 1;
-                                    diagnostic.message =
-                                        entry.diagnostic.message.split('\n').skip(1).collect();
-                                }
-
-                                if !diagnostic.message.is_empty() {
-                                    group_state.block_count += 1;
-                                    blocks_to_add.push(BlockProperties {
-                                        position: (excerpt_id.clone(), entry.range.start),
-                                        height: diagnostic.message.matches('\n').count() as u8 + 1,
-                                        style: BlockStyle::Fixed,
-                                        render: diagnostic_block_renderer(diagnostic, true),
-                                        disposition: BlockDisposition::Below,
-                                    });
-                                }
-                            }
-
-                            pending_range.take();
-                        }
-
-                        if let Some(entry) = resolved_entry {
-                            pending_range = Some((entry.range.clone(), ix));
-                        }
-                    }
-
-                    groups_to_add.push(group_state);
-                } else if let Some((group_ix, group_state)) = to_remove {
-                    excerpts.remove_excerpts(group_state.excerpts.iter().copied(), excerpts_cx);
-                    group_ixs_to_remove.push(group_ix);
-                    blocks_to_remove.extend(group_state.blocks.iter().copied());
-                } else if let Some((_, group)) = to_keep {
-                    prev_excerpt_id = group.excerpts.last().unwrap().clone();
-                    first_excerpt_id.get_or_insert_with(|| prev_excerpt_id.clone());
-                }
-            }
-
-            excerpts.snapshot(excerpts_cx)
-        });
-
-        self.editor.update(cx, |editor, cx| {
-            editor.remove_blocks(blocks_to_remove, None, cx);
-            let block_ids = editor.insert_blocks(
-                blocks_to_add.into_iter().map(|block| {
-                    let (excerpt_id, text_anchor) = block.position;
-                    BlockProperties {
-                        position: excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor),
-                        height: block.height,
-                        style: block.style,
-                        render: block.render,
-                        disposition: block.disposition,
-                    }
-                }),
-                Some(Autoscroll::fit()),
-                cx,
-            );
-
-            let mut block_ids = block_ids.into_iter();
-            for group_state in &mut groups_to_add {
-                group_state.blocks = block_ids.by_ref().take(group_state.block_count).collect();
-            }
-        });
-
-        for ix in group_ixs_to_remove.into_iter().rev() {
-            path_state.diagnostic_groups.remove(ix);
-        }
-        path_state.diagnostic_groups.extend(groups_to_add);
-        path_state.diagnostic_groups.sort_unstable_by(|a, b| {
-            let range_a = &a.primary_diagnostic.range;
-            let range_b = &b.primary_diagnostic.range;
-            range_a
-                .start
-                .cmp(&range_b.start, &snapshot)
-                .then_with(|| range_a.end.cmp(&range_b.end, &snapshot))
-        });
-
-        if path_state.diagnostic_groups.is_empty() {
-            self.path_states.remove(path_ix);
-        }
-
-        self.editor.update(cx, |editor, cx| {
-            let groups;
-            let mut selections;
-            let new_excerpt_ids_by_selection_id;
-            if was_empty {
-                groups = self.path_states.first()?.diagnostic_groups.as_slice();
-                new_excerpt_ids_by_selection_id = [(0, ExcerptId::min())].into_iter().collect();
-                selections = vec![Selection {
-                    id: 0,
-                    start: 0,
-                    end: 0,
-                    reversed: false,
-                    goal: SelectionGoal::None,
-                }];
-            } else {
-                groups = self.path_states.get(path_ix)?.diagnostic_groups.as_slice();
-                new_excerpt_ids_by_selection_id =
-                    editor.change_selections(Some(Autoscroll::fit()), cx, |s| s.refresh());
-                selections = editor.selections.all::<usize>(cx);
-            }
-
-            // If any selection has lost its position, move it to start of the next primary diagnostic.
-            let snapshot = editor.snapshot(cx);
-            for selection in &mut selections {
-                if let Some(new_excerpt_id) = new_excerpt_ids_by_selection_id.get(&selection.id) {
-                    let group_ix = match groups.binary_search_by(|probe| {
-                        probe
-                            .excerpts
-                            .last()
-                            .unwrap()
-                            .cmp(new_excerpt_id, &snapshot.buffer_snapshot)
-                    }) {
-                        Ok(ix) | Err(ix) => ix,
-                    };
-                    if let Some(group) = groups.get(group_ix) {
-                        let offset = excerpts_snapshot
-                            .anchor_in_excerpt(
-                                group.excerpts[group.primary_excerpt_ix].clone(),
-                                group.primary_diagnostic.range.start,
-                            )
-                            .to_offset(&excerpts_snapshot);
-                        selection.start = offset;
-                        selection.end = offset;
-                    }
-                }
-            }
-            editor.change_selections(None, cx, |s| {
-                s.select(selections);
-            });
-            Some(())
-        });
-
-        if self.path_states.is_empty() {
-            if self.editor.focus_handle(cx).is_focused(cx) {
-                cx.focus(&self.focus_handle);
-            }
-        } else if self.focus_handle.is_focused(cx) {
-            let focus_handle = self.editor.focus_handle(cx);
-            cx.focus(&focus_handle);
-        }
-        cx.notify();
-    }
-}
-
-impl FocusableView for ProjectDiagnosticsEditor {
-    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
-        self.focus_handle.clone()
-    }
-}
-
-impl Item for ProjectDiagnosticsEditor {
-    type Event = EditorEvent;
-
-    fn to_item_events(event: &EditorEvent, f: impl FnMut(ItemEvent)) {
-        Editor::to_item_events(event, f)
-    }
-
-    fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
-        self.editor.update(cx, |editor, cx| editor.deactivated(cx));
-    }
-
-    fn navigate(&mut self, data: Box<dyn Any>, cx: &mut ViewContext<Self>) -> bool {
-        self.editor
-            .update(cx, |editor, cx| editor.navigate(data, cx))
-    }
-
-    fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> {
-        Some("Project Diagnostics".into())
-    }
-
-    fn tab_content(&self, _detail: Option<usize>, selected: bool, _: &WindowContext) -> AnyElement {
-        if self.summary.error_count == 0 && self.summary.warning_count == 0 {
-            let label = Label::new("No problems");
-            label.into_any_element()
-        } else {
-            h_stack()
-                .gap_1()
-                .when(self.summary.error_count > 0, |then| {
-                    then.child(
-                        h_stack()
-                            .gap_1()
-                            .child(IconElement::new(Icon::XCircle).color(Color::Error))
-                            .child(Label::new(self.summary.error_count.to_string()).color(
-                                if selected {
-                                    Color::Default
-                                } else {
-                                    Color::Muted
-                                },
-                            )),
-                    )
-                })
-                .when(self.summary.warning_count > 0, |then| {
-                    then.child(
-                        h_stack()
-                            .gap_1()
-                            .child(
-                                IconElement::new(Icon::ExclamationTriangle).color(Color::Warning),
-                            )
-                            .child(Label::new(self.summary.warning_count.to_string()).color(
-                                if selected {
-                                    Color::Default
-                                } else {
-                                    Color::Muted
-                                },
-                            )),
-                    )
-                })
-                .into_any_element()
-        }
-    }
-
-    fn for_each_project_item(
-        &self,
-        cx: &AppContext,
-        f: &mut dyn FnMut(gpui::EntityId, &dyn project::Item),
-    ) {
-        self.editor.for_each_project_item(cx, f)
-    }
-
-    fn is_singleton(&self, _: &AppContext) -> bool {
-        false
-    }
-
-    fn set_nav_history(&mut self, nav_history: ItemNavHistory, cx: &mut ViewContext<Self>) {
-        self.editor.update(cx, |editor, _| {
-            editor.set_nav_history(Some(nav_history));
-        });
-    }
-
-    fn clone_on_split(
-        &self,
-        _workspace_id: workspace::WorkspaceId,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<View<Self>>
-    where
-        Self: Sized,
-    {
-        Some(cx.new_view(|cx| {
-            ProjectDiagnosticsEditor::new(self.project.clone(), self.workspace.clone(), cx)
-        }))
-    }
-
-    fn is_dirty(&self, cx: &AppContext) -> bool {
-        self.excerpts.read(cx).is_dirty(cx)
-    }
-
-    fn has_conflict(&self, cx: &AppContext) -> bool {
-        self.excerpts.read(cx).has_conflict(cx)
-    }
-
-    fn can_save(&self, _: &AppContext) -> bool {
-        true
-    }
-
-    fn save(&mut self, project: Model<Project>, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
-        self.editor.save(project, cx)
-    }
-
-    fn save_as(
-        &mut self,
-        _: Model<Project>,
-        _: PathBuf,
-        _: &mut ViewContext<Self>,
-    ) -> Task<Result<()>> {
-        unreachable!()
-    }
-
-    fn reload(&mut self, project: Model<Project>, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
-        self.editor.reload(project, cx)
-    }
-
-    fn act_as_type<'a>(
-        &'a self,
-        type_id: TypeId,
-        self_handle: &'a View<Self>,
-        _: &'a AppContext,
-    ) -> Option<AnyView> {
-        if type_id == TypeId::of::<Self>() {
-            Some(self_handle.to_any())
-        } else if type_id == TypeId::of::<Editor>() {
-            Some(self.editor.to_any())
-        } else {
-            None
-        }
-    }
-
-    fn breadcrumb_location(&self) -> ToolbarItemLocation {
-        ToolbarItemLocation::PrimaryLeft
-    }
-
-    fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
-        self.editor.breadcrumbs(theme, cx)
-    }
-
-    fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
-        self.editor
-            .update(cx, |editor, cx| editor.added_to_workspace(workspace, cx));
-    }
-
-    fn serialized_item_kind() -> Option<&'static str> {
-        Some("diagnostics")
-    }
-
-    fn deserialize(
-        project: Model<Project>,
-        workspace: WeakView<Workspace>,
-        _workspace_id: workspace::WorkspaceId,
-        _item_id: workspace::ItemId,
-        cx: &mut ViewContext<Pane>,
-    ) -> Task<Result<View<Self>>> {
-        Task::ready(Ok(cx.new_view(|cx| Self::new(project, workspace, cx))))
-    }
-}
-
-fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
-    let (message, code_ranges) = highlight_diagnostic_message(&diagnostic);
-    let message: SharedString = message.into();
-    Arc::new(move |cx| {
-        let highlight_style: HighlightStyle = cx.theme().colors().text_accent.into();
-        h_stack()
-            .id("diagnostic header")
-            .py_2()
-            .pl_10()
-            .pr_5()
-            .w_full()
-            .justify_between()
-            .gap_2()
-            .child(
-                h_stack()
-                    .gap_3()
-                    .map(|stack| {
-                        stack.child(
-                            svg()
-                                .size(cx.text_style().font_size)
-                                .flex_none()
-                                .map(|icon| {
-                                    if diagnostic.severity == DiagnosticSeverity::ERROR {
-                                        icon.path(Icon::XCircle.path())
-                                            .text_color(Color::Error.color(cx))
-                                    } else {
-                                        icon.path(Icon::ExclamationTriangle.path())
-                                            .text_color(Color::Warning.color(cx))
-                                    }
-                                }),
-                        )
-                    })
-                    .child(
-                        h_stack()
-                            .gap_1()
-                            .child(
-                                StyledText::new(message.clone()).with_highlights(
-                                    &cx.text_style(),
-                                    code_ranges
-                                        .iter()
-                                        .map(|range| (range.clone(), highlight_style)),
-                                ),
-                            )
-                            .when_some(diagnostic.code.as_ref(), |stack, code| {
-                                stack.child(
-                                    div()
-                                        .child(SharedString::from(format!("({code})")))
-                                        .text_color(cx.theme().colors().text_muted),
-                                )
-                            }),
-                    ),
-            )
-            .child(
-                h_stack()
-                    .gap_1()
-                    .when_some(diagnostic.source.as_ref(), |stack, source| {
-                        stack.child(
-                            div()
-                                .child(SharedString::from(source.clone()))
-                                .text_color(cx.theme().colors().text_muted),
-                        )
-                    }),
-            )
-            .into_any_element()
-    })
-}
-
-fn compare_diagnostics<L: language::ToOffset, R: language::ToOffset>(
-    lhs: &DiagnosticEntry<L>,
-    rhs: &DiagnosticEntry<R>,
-    snapshot: &language::BufferSnapshot,
-) -> Ordering {
-    lhs.range
-        .start
-        .to_offset(snapshot)
-        .cmp(&rhs.range.start.to_offset(snapshot))
-        .then_with(|| {
-            lhs.range
-                .end
-                .to_offset(snapshot)
-                .cmp(&rhs.range.end.to_offset(snapshot))
-        })
-        .then_with(|| lhs.diagnostic.message.cmp(&rhs.diagnostic.message))
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use editor::{
-        display_map::{BlockContext, TransformBlock},
-        DisplayPoint,
-    };
-    use gpui::{px, TestAppContext, VisualTestContext, WindowContext};
-    use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16, Unclipped};
-    use project::FakeFs;
-    use serde_json::json;
-    use settings::SettingsStore;
-    use unindent::Unindent as _;
-
-    #[gpui::test]
-    async fn test_diagnostics(cx: &mut TestAppContext) {
-        init_test(cx);
-
-        let fs = FakeFs::new(cx.executor());
-        fs.insert_tree(
-            "/test",
-            json!({
-                "consts.rs": "
-                    const a: i32 = 'a';
-                    const b: i32 = c;
-                "
-                .unindent(),
-
-                "main.rs": "
-                    fn main() {
-                        let x = vec![];
-                        let y = vec![];
-                        a(x);
-                        b(y);
-                        // comment 1
-                        // comment 2
-                        c(y);
-                        d(x);
-                    }
-                "
-                .unindent(),
-            }),
-        )
-        .await;
-
-        let language_server_id = LanguageServerId(0);
-        let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
-        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let cx = &mut VisualTestContext::from_window(*window, cx);
-        let workspace = window.root(cx).unwrap();
-
-        // Create some diagnostics
-        project.update(cx, |project, cx| {
-            project
-                .update_diagnostic_entries(
-                    language_server_id,
-                    PathBuf::from("/test/main.rs"),
-                    None,
-                    vec![
-                        DiagnosticEntry {
-                            range: Unclipped(PointUtf16::new(1, 8))..Unclipped(PointUtf16::new(1, 9)),
-                            diagnostic: Diagnostic {
-                                message:
-                                    "move occurs because `x` has type `Vec<char>`, which does not implement the `Copy` trait"
-                                        .to_string(),
-                                severity: DiagnosticSeverity::INFORMATION,
-                                is_primary: false,
-                                is_disk_based: true,
-                                group_id: 1,
-                                ..Default::default()
-                            },
-                        },
-                        DiagnosticEntry {
-                            range: Unclipped(PointUtf16::new(2, 8))..Unclipped(PointUtf16::new(2, 9)),
-                            diagnostic: Diagnostic {
-                                message:
-                                    "move occurs because `y` has type `Vec<char>`, which does not implement the `Copy` trait"
-                                        .to_string(),
-                                severity: DiagnosticSeverity::INFORMATION,
-                                is_primary: false,
-                                is_disk_based: true,
-                                group_id: 0,
-                                ..Default::default()
-                            },
-                        },
-                        DiagnosticEntry {
-                            range: Unclipped(PointUtf16::new(3, 6))..Unclipped(PointUtf16::new(3, 7)),
-                            diagnostic: Diagnostic {
-                                message: "value moved here".to_string(),
-                                severity: DiagnosticSeverity::INFORMATION,
-                                is_primary: false,
-                                is_disk_based: true,
-                                group_id: 1,
-                                ..Default::default()
-                            },
-                        },
-                        DiagnosticEntry {
-                            range: Unclipped(PointUtf16::new(4, 6))..Unclipped(PointUtf16::new(4, 7)),
-                            diagnostic: Diagnostic {
-                                message: "value moved here".to_string(),
-                                severity: DiagnosticSeverity::INFORMATION,
-                                is_primary: false,
-                                is_disk_based: true,
-                                group_id: 0,
-                                ..Default::default()
-                            },
-                        },
-                        DiagnosticEntry {
-                            range: Unclipped(PointUtf16::new(7, 6))..Unclipped(PointUtf16::new(7, 7)),
-                            diagnostic: Diagnostic {
-                                message: "use of moved value\nvalue used here after move".to_string(),
-                                severity: DiagnosticSeverity::ERROR,
-                                is_primary: true,
-                                is_disk_based: true,
-                                group_id: 0,
-                                ..Default::default()
-                            },
-                        },
-                        DiagnosticEntry {
-                            range: Unclipped(PointUtf16::new(8, 6))..Unclipped(PointUtf16::new(8, 7)),
-                            diagnostic: Diagnostic {
-                                message: "use of moved value\nvalue used here after move".to_string(),
-                                severity: DiagnosticSeverity::ERROR,
-                                is_primary: true,
-                                is_disk_based: true,
-                                group_id: 1,
-                                ..Default::default()
-                            },
-                        },
-                    ],
-                    cx,
-                )
-                .unwrap();
-        });
-
-        // Open the project diagnostics view while there are already diagnostics.
-        let view = window.build_view(cx, |cx| {
-            ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)
-        });
-
-        view.next_notification(cx).await;
-        view.update(cx, |view, cx| {
-            assert_eq!(
-                editor_blocks(&view.editor, cx),
-                [
-                    (0, "path header block".into()),
-                    (2, "diagnostic header".into()),
-                    (15, "collapsed context".into()),
-                    (16, "diagnostic header".into()),
-                    (25, "collapsed context".into()),
-                ]
-            );
-            assert_eq!(
-                view.editor.update(cx, |editor, cx| editor.display_text(cx)),
-                concat!(
-                    //
-                    // main.rs
-                    //
-                    "\n", // filename
-                    "\n", // padding
-                    // diagnostic group 1
-                    "\n", // primary message
-                    "\n", // padding
-                    "    let x = vec![];\n",
-                    "    let y = vec![];\n",
-                    "\n", // supporting diagnostic
-                    "    a(x);\n",
-                    "    b(y);\n",
-                    "\n", // supporting diagnostic
-                    "    // comment 1\n",
-                    "    // comment 2\n",
-                    "    c(y);\n",
-                    "\n", // supporting diagnostic
-                    "    d(x);\n",
-                    "\n", // context ellipsis
-                    // diagnostic group 2
-                    "\n", // primary message
-                    "\n", // padding
-                    "fn main() {\n",
-                    "    let x = vec![];\n",
-                    "\n", // supporting diagnostic
-                    "    let y = vec![];\n",
-                    "    a(x);\n",
-                    "\n", // supporting diagnostic
-                    "    b(y);\n",
-                    "\n", // context ellipsis
-                    "    c(y);\n",
-                    "    d(x);\n",
-                    "\n", // supporting diagnostic
-                    "}"
-                )
-            );
-
-            // Cursor is at the first diagnostic
-            view.editor.update(cx, |editor, cx| {
-                assert_eq!(
-                    editor.selections.display_ranges(cx),
-                    [DisplayPoint::new(12, 6)..DisplayPoint::new(12, 6)]
-                );
-            });
-        });
-
-        // Diagnostics are added for another earlier path.
-        project.update(cx, |project, cx| {
-            project.disk_based_diagnostics_started(language_server_id, cx);
-            project
-                .update_diagnostic_entries(
-                    language_server_id,
-                    PathBuf::from("/test/consts.rs"),
-                    None,
-                    vec![DiagnosticEntry {
-                        range: Unclipped(PointUtf16::new(0, 15))..Unclipped(PointUtf16::new(0, 15)),
-                        diagnostic: Diagnostic {
-                            message: "mismatched types\nexpected `usize`, found `char`".to_string(),
-                            severity: DiagnosticSeverity::ERROR,
-                            is_primary: true,
-                            is_disk_based: true,
-                            group_id: 0,
-                            ..Default::default()
-                        },
-                    }],
-                    cx,
-                )
-                .unwrap();
-            project.disk_based_diagnostics_finished(language_server_id, cx);
-        });
-
-        view.next_notification(cx).await;
-        view.update(cx, |view, cx| {
-            assert_eq!(
-                editor_blocks(&view.editor, cx),
-                [
-                    (0, "path header block".into()),
-                    (2, "diagnostic header".into()),
-                    (7, "path header block".into()),
-                    (9, "diagnostic header".into()),
-                    (22, "collapsed context".into()),
-                    (23, "diagnostic header".into()),
-                    (32, "collapsed context".into()),
-                ]
-            );
-            assert_eq!(
-                view.editor.update(cx, |editor, cx| editor.display_text(cx)),
-                concat!(
-                    //
-                    // consts.rs
-                    //
-                    "\n", // filename
-                    "\n", // padding
-                    // diagnostic group 1
-                    "\n", // primary message
-                    "\n", // padding
-                    "const a: i32 = 'a';\n",
-                    "\n", // supporting diagnostic
-                    "const b: i32 = c;\n",
-                    //
-                    // main.rs
-                    //
-                    "\n", // filename
-                    "\n", // padding
-                    // diagnostic group 1
-                    "\n", // primary message
-                    "\n", // padding
-                    "    let x = vec![];\n",
-                    "    let y = vec![];\n",
-                    "\n", // supporting diagnostic
-                    "    a(x);\n",
-                    "    b(y);\n",
-                    "\n", // supporting diagnostic
-                    "    // comment 1\n",
-                    "    // comment 2\n",
-                    "    c(y);\n",
-                    "\n", // supporting diagnostic
-                    "    d(x);\n",
-                    "\n", // collapsed context
-                    // diagnostic group 2
-                    "\n", // primary message
-                    "\n", // filename
-                    "fn main() {\n",
-                    "    let x = vec![];\n",
-                    "\n", // supporting diagnostic
-                    "    let y = vec![];\n",
-                    "    a(x);\n",
-                    "\n", // supporting diagnostic
-                    "    b(y);\n",
-                    "\n", // context ellipsis
-                    "    c(y);\n",
-                    "    d(x);\n",
-                    "\n", // supporting diagnostic
-                    "}"
-                )
-            );
-
-            // Cursor keeps its position.
-            view.editor.update(cx, |editor, cx| {
-                assert_eq!(
-                    editor.selections.display_ranges(cx),
-                    [DisplayPoint::new(19, 6)..DisplayPoint::new(19, 6)]
-                );
-            });
-        });
-
-        // Diagnostics are added to the first path
-        project.update(cx, |project, cx| {
-            project.disk_based_diagnostics_started(language_server_id, cx);
-            project
-                .update_diagnostic_entries(
-                    language_server_id,
-                    PathBuf::from("/test/consts.rs"),
-                    None,
-                    vec![
-                        DiagnosticEntry {
-                            range: Unclipped(PointUtf16::new(0, 15))
-                                ..Unclipped(PointUtf16::new(0, 15)),
-                            diagnostic: Diagnostic {
-                                message: "mismatched types\nexpected `usize`, found `char`"
-                                    .to_string(),
-                                severity: DiagnosticSeverity::ERROR,
-                                is_primary: true,
-                                is_disk_based: true,
-                                group_id: 0,
-                                ..Default::default()
-                            },
-                        },
-                        DiagnosticEntry {
-                            range: Unclipped(PointUtf16::new(1, 15))
-                                ..Unclipped(PointUtf16::new(1, 15)),
-                            diagnostic: Diagnostic {
-                                message: "unresolved name `c`".to_string(),
-                                severity: DiagnosticSeverity::ERROR,
-                                is_primary: true,
-                                is_disk_based: true,
-                                group_id: 1,
-                                ..Default::default()
-                            },
-                        },
-                    ],
-                    cx,
-                )
-                .unwrap();
-            project.disk_based_diagnostics_finished(language_server_id, cx);
-        });
-
-        view.next_notification(cx).await;
-        view.update(cx, |view, cx| {
-            assert_eq!(
-                editor_blocks(&view.editor, cx),
-                [
-                    (0, "path header block".into()),
-                    (2, "diagnostic header".into()),
-                    (7, "collapsed context".into()),
-                    (8, "diagnostic header".into()),
-                    (13, "path header block".into()),
-                    (15, "diagnostic header".into()),
-                    (28, "collapsed context".into()),
-                    (29, "diagnostic header".into()),
-                    (38, "collapsed context".into()),
-                ]
-            );
-            assert_eq!(
-                view.editor.update(cx, |editor, cx| editor.display_text(cx)),
-                concat!(
-                    //
-                    // consts.rs
-                    //
-                    "\n", // filename
-                    "\n", // padding
-                    // diagnostic group 1
-                    "\n", // primary message
-                    "\n", // padding
-                    "const a: i32 = 'a';\n",
-                    "\n", // supporting diagnostic
-                    "const b: i32 = c;\n",
-                    "\n", // context ellipsis
-                    // diagnostic group 2
-                    "\n", // primary message
-                    "\n", // padding
-                    "const a: i32 = 'a';\n",
-                    "const b: i32 = c;\n",
-                    "\n", // supporting diagnostic
-                    //
-                    // main.rs
-                    //
-                    "\n", // filename
-                    "\n", // padding
-                    // diagnostic group 1
-                    "\n", // primary message
-                    "\n", // padding
-                    "    let x = vec![];\n",
-                    "    let y = vec![];\n",
-                    "\n", // supporting diagnostic
-                    "    a(x);\n",
-                    "    b(y);\n",
-                    "\n", // supporting diagnostic
-                    "    // comment 1\n",
-                    "    // comment 2\n",
-                    "    c(y);\n",
-                    "\n", // supporting diagnostic
-                    "    d(x);\n",
-                    "\n", // context ellipsis
-                    // diagnostic group 2
-                    "\n", // primary message
-                    "\n", // filename
-                    "fn main() {\n",
-                    "    let x = vec![];\n",
-                    "\n", // supporting diagnostic
-                    "    let y = vec![];\n",
-                    "    a(x);\n",
-                    "\n", // supporting diagnostic
-                    "    b(y);\n",
-                    "\n", // context ellipsis
-                    "    c(y);\n",
-                    "    d(x);\n",
-                    "\n", // supporting diagnostic
-                    "}"
-                )
-            );
-        });
-    }
-
-    #[gpui::test]
-    async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
-        init_test(cx);
-
-        let fs = FakeFs::new(cx.executor());
-        fs.insert_tree(
-            "/test",
-            json!({
-                "main.js": "
-                    a();
-                    b();
-                    c();
-                    d();
-                    e();
-                ".unindent()
-            }),
-        )
-        .await;
-
-        let server_id_1 = LanguageServerId(100);
-        let server_id_2 = LanguageServerId(101);
-        let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
-        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let cx = &mut VisualTestContext::from_window(*window, cx);
-        let workspace = window.root(cx).unwrap();
-
-        let view = window.build_view(cx, |cx| {
-            ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)
-        });
-
-        // Two language servers start updating diagnostics
-        project.update(cx, |project, cx| {
-            project.disk_based_diagnostics_started(server_id_1, cx);
-            project.disk_based_diagnostics_started(server_id_2, cx);
-            project
-                .update_diagnostic_entries(
-                    server_id_1,
-                    PathBuf::from("/test/main.js"),
-                    None,
-                    vec![DiagnosticEntry {
-                        range: Unclipped(PointUtf16::new(0, 0))..Unclipped(PointUtf16::new(0, 1)),
-                        diagnostic: Diagnostic {
-                            message: "error 1".to_string(),
-                            severity: DiagnosticSeverity::WARNING,
-                            is_primary: true,
-                            is_disk_based: true,
-                            group_id: 1,
-                            ..Default::default()
-                        },
-                    }],
-                    cx,
-                )
-                .unwrap();
-        });
-
-        // The first language server finishes
-        project.update(cx, |project, cx| {
-            project.disk_based_diagnostics_finished(server_id_1, cx);
-        });
-
-        // Only the first language server's diagnostics are shown.
-        cx.executor().run_until_parked();
-        view.update(cx, |view, cx| {
-            assert_eq!(
-                editor_blocks(&view.editor, cx),
-                [
-                    (0, "path header block".into()),
-                    (2, "diagnostic header".into()),
-                ]
-            );
-            assert_eq!(
-                view.editor.update(cx, |editor, cx| editor.display_text(cx)),
-                concat!(
-                    "\n", // filename
-                    "\n", // padding
-                    // diagnostic group 1
-                    "\n",     // primary message
-                    "\n",     // padding
-                    "a();\n", //
-                    "b();",
-                )
-            );
-        });
-
-        // The second language server finishes
-        project.update(cx, |project, cx| {
-            project
-                .update_diagnostic_entries(
-                    server_id_2,
-                    PathBuf::from("/test/main.js"),
-                    None,
-                    vec![DiagnosticEntry {
-                        range: Unclipped(PointUtf16::new(1, 0))..Unclipped(PointUtf16::new(1, 1)),
-                        diagnostic: Diagnostic {
-                            message: "warning 1".to_string(),
-                            severity: DiagnosticSeverity::ERROR,
-                            is_primary: true,
-                            is_disk_based: true,
-                            group_id: 2,
-                            ..Default::default()
-                        },
-                    }],
-                    cx,
-                )
-                .unwrap();
-            project.disk_based_diagnostics_finished(server_id_2, cx);
-        });
-
-        // Both language server's diagnostics are shown.
-        cx.executor().run_until_parked();
-        view.update(cx, |view, cx| {
-            assert_eq!(
-                editor_blocks(&view.editor, cx),
-                [
-                    (0, "path header block".into()),
-                    (2, "diagnostic header".into()),
-                    (6, "collapsed context".into()),
-                    (7, "diagnostic header".into()),
-                ]
-            );
-            assert_eq!(
-                view.editor.update(cx, |editor, cx| editor.display_text(cx)),
-                concat!(
-                    "\n", // filename
-                    "\n", // padding
-                    // diagnostic group 1
-                    "\n",     // primary message
-                    "\n",     // padding
-                    "a();\n", // location
-                    "b();\n", //
-                    "\n",     // collapsed context
-                    // diagnostic group 2
-                    "\n",     // primary message
-                    "\n",     // padding
-                    "a();\n", // context
-                    "b();\n", //
-                    "c();",   // context
-                )
-            );
-        });
-
-        // Both language servers start updating diagnostics, and the first server finishes.
-        project.update(cx, |project, cx| {
-            project.disk_based_diagnostics_started(server_id_1, cx);
-            project.disk_based_diagnostics_started(server_id_2, cx);
-            project
-                .update_diagnostic_entries(
-                    server_id_1,
-                    PathBuf::from("/test/main.js"),
-                    None,
-                    vec![DiagnosticEntry {
-                        range: Unclipped(PointUtf16::new(2, 0))..Unclipped(PointUtf16::new(2, 1)),
-                        diagnostic: Diagnostic {
-                            message: "warning 2".to_string(),
-                            severity: DiagnosticSeverity::WARNING,
-                            is_primary: true,
-                            is_disk_based: true,
-                            group_id: 1,
-                            ..Default::default()
-                        },
-                    }],
-                    cx,
-                )
-                .unwrap();
-            project
-                .update_diagnostic_entries(
-                    server_id_2,
-                    PathBuf::from("/test/main.rs"),
-                    None,
-                    vec![],
-                    cx,
-                )
-                .unwrap();
-            project.disk_based_diagnostics_finished(server_id_1, cx);
-        });
-
-        // Only the first language server's diagnostics are updated.
-        cx.executor().run_until_parked();
-        view.update(cx, |view, cx| {
-            assert_eq!(
-                editor_blocks(&view.editor, cx),
-                [
-                    (0, "path header block".into()),
-                    (2, "diagnostic header".into()),
-                    (7, "collapsed context".into()),
-                    (8, "diagnostic header".into()),
-                ]
-            );
-            assert_eq!(
-                view.editor.update(cx, |editor, cx| editor.display_text(cx)),
-                concat!(
-                    "\n", // filename
-                    "\n", // padding
-                    // diagnostic group 1
-                    "\n",     // primary message
-                    "\n",     // padding
-                    "a();\n", // location
-                    "b();\n", //
-                    "c();\n", // context
-                    "\n",     // collapsed context
-                    // diagnostic group 2
-                    "\n",     // primary message
-                    "\n",     // padding
-                    "b();\n", // context
-                    "c();\n", //
-                    "d();",   // context
-                )
-            );
-        });
-
-        // The second language server finishes.
-        project.update(cx, |project, cx| {
-            project
-                .update_diagnostic_entries(
-                    server_id_2,
-                    PathBuf::from("/test/main.js"),
-                    None,
-                    vec![DiagnosticEntry {
-                        range: Unclipped(PointUtf16::new(3, 0))..Unclipped(PointUtf16::new(3, 1)),
-                        diagnostic: Diagnostic {
-                            message: "warning 2".to_string(),
-                            severity: DiagnosticSeverity::WARNING,
-                            is_primary: true,
-                            is_disk_based: true,
-                            group_id: 1,
-                            ..Default::default()
-                        },
-                    }],
-                    cx,
-                )
-                .unwrap();
-            project.disk_based_diagnostics_finished(server_id_2, cx);
-        });
-
-        // Both language servers' diagnostics are updated.
-        cx.executor().run_until_parked();
-        view.update(cx, |view, cx| {
-            assert_eq!(
-                editor_blocks(&view.editor, cx),
-                [
-                    (0, "path header block".into()),
-                    (2, "diagnostic header".into()),
-                    (7, "collapsed context".into()),
-                    (8, "diagnostic header".into()),
-                ]
-            );
-            assert_eq!(
-                view.editor.update(cx, |editor, cx| editor.display_text(cx)),
-                concat!(
-                    "\n", // filename
-                    "\n", // padding
-                    // diagnostic group 1
-                    "\n",     // primary message
-                    "\n",     // padding
-                    "b();\n", // location
-                    "c();\n", //
-                    "d();\n", // context
-                    "\n",     // collapsed context
-                    // diagnostic group 2
-                    "\n",     // primary message
-                    "\n",     // padding
-                    "c();\n", // context
-                    "d();\n", //
-                    "e();",   // context
-                )
-            );
-        });
-    }
-
-    fn init_test(cx: &mut TestAppContext) {
-        cx.update(|cx| {
-            let settings = SettingsStore::test(cx);
-            cx.set_global(settings);
-            theme::init(theme::LoadThemes::JustBase, cx);
-            language::init(cx);
-            client::init_settings(cx);
-            workspace::init_settings(cx);
-            Project::init_settings(cx);
-            crate::init(cx);
-        });
-    }
-
-    fn editor_blocks(editor: &View<Editor>, cx: &mut WindowContext) -> Vec<(u32, SharedString)> {
-        editor.update(cx, |editor, cx| {
-            let snapshot = editor.snapshot(cx);
-            snapshot
-                .blocks_in_range(0..snapshot.max_point().row())
-                .enumerate()
-                .filter_map(|(ix, (row, block))| {
-                    let name = match block {
-                        TransformBlock::Custom(block) => block
-                            .render(&mut BlockContext {
-                                view_context: cx,
-                                anchor_x: px(0.),
-                                gutter_padding: px(0.),
-                                gutter_width: px(0.),
-                                line_height: px(0.),
-                                em_width: px(0.),
-                                block_id: ix,
-                                editor_style: &editor::EditorStyle::default(),
-                            })
-                            .inner_id()?
-                            .try_into()
-                            .ok()?,
-
-                        TransformBlock::ExcerptHeader {
-                            starts_new_buffer, ..
-                        } => {
-                            if *starts_new_buffer {
-                                "path header block".into()
-                            } else {
-                                "collapsed context".into()
-                            }
-                        }
-                    };
-
-                    Some((row, name))
-                })
-                .collect()
-        })
-    }
-}

crates/diagnostics2/src/items.rs 🔗

@@ -1,187 +0,0 @@
-use collections::HashSet;
-use editor::Editor;
-use gpui::{
-    rems, EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, View,
-    ViewContext, WeakView,
-};
-use language::Diagnostic;
-use lsp::LanguageServerId;
-use ui::{h_stack, prelude::*, Button, ButtonLike, Color, Icon, IconElement, Label, Tooltip};
-use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace};
-
-use crate::{Deploy, ProjectDiagnosticsEditor};
-
-pub struct DiagnosticIndicator {
-    summary: project::DiagnosticSummary,
-    active_editor: Option<WeakView<Editor>>,
-    workspace: WeakView<Workspace>,
-    current_diagnostic: Option<Diagnostic>,
-    in_progress_checks: HashSet<LanguageServerId>,
-    _observe_active_editor: Option<Subscription>,
-}
-
-impl Render for DiagnosticIndicator {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        let diagnostic_indicator = match (self.summary.error_count, self.summary.warning_count) {
-            (0, 0) => h_stack().child(
-                IconElement::new(Icon::Check)
-                    .size(IconSize::Small)
-                    .color(Color::Success),
-            ),
-            (0, warning_count) => h_stack()
-                .gap_1()
-                .child(
-                    IconElement::new(Icon::ExclamationTriangle)
-                        .size(IconSize::Small)
-                        .color(Color::Warning),
-                )
-                .child(Label::new(warning_count.to_string()).size(LabelSize::Small)),
-            (error_count, 0) => h_stack()
-                .gap_1()
-                .child(
-                    IconElement::new(Icon::XCircle)
-                        .size(IconSize::Small)
-                        .color(Color::Error),
-                )
-                .child(Label::new(error_count.to_string()).size(LabelSize::Small)),
-            (error_count, warning_count) => h_stack()
-                .gap_1()
-                .child(
-                    IconElement::new(Icon::XCircle)
-                        .size(IconSize::Small)
-                        .color(Color::Error),
-                )
-                .child(Label::new(error_count.to_string()).size(LabelSize::Small))
-                .child(
-                    IconElement::new(Icon::ExclamationTriangle)
-                        .size(IconSize::Small)
-                        .color(Color::Warning),
-                )
-                .child(Label::new(warning_count.to_string()).size(LabelSize::Small)),
-        };
-
-        let status = if !self.in_progress_checks.is_empty() {
-            Some(
-                Label::new("Checking…")
-                    .size(LabelSize::Small)
-                    .into_any_element(),
-            )
-        } else if let Some(diagnostic) = &self.current_diagnostic {
-            let message = diagnostic.message.split('\n').next().unwrap().to_string();
-            Some(
-                Button::new("diagnostic_message", message)
-                    .label_size(LabelSize::Small)
-                    .tooltip(|cx| {
-                        Tooltip::for_action("Next Diagnostic", &editor::GoToDiagnostic, cx)
-                    })
-                    .on_click(cx.listener(|this, _, cx| {
-                        this.go_to_next_diagnostic(cx);
-                    }))
-                    .into_any_element(),
-            )
-        } else {
-            None
-        };
-
-        h_stack()
-            .h(rems(1.375))
-            .gap_2()
-            .child(
-                ButtonLike::new("diagnostic-indicator")
-                    .child(diagnostic_indicator)
-                    .tooltip(|cx| Tooltip::for_action("Project Diagnostics", &Deploy, cx))
-                    .on_click(cx.listener(|this, _, cx| {
-                        if let Some(workspace) = this.workspace.upgrade() {
-                            workspace.update(cx, |workspace, cx| {
-                                ProjectDiagnosticsEditor::deploy(workspace, &Default::default(), cx)
-                            })
-                        }
-                    })),
-            )
-            .children(status)
-    }
-}
-
-impl DiagnosticIndicator {
-    pub fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
-        let project = workspace.project();
-        cx.subscribe(project, |this, project, event, cx| match event {
-            project::Event::DiskBasedDiagnosticsStarted { language_server_id } => {
-                this.in_progress_checks.insert(*language_server_id);
-                cx.notify();
-            }
-
-            project::Event::DiskBasedDiagnosticsFinished { language_server_id }
-            | project::Event::LanguageServerRemoved(language_server_id) => {
-                this.summary = project.read(cx).diagnostic_summary(false, cx);
-                this.in_progress_checks.remove(language_server_id);
-                cx.notify();
-            }
-
-            project::Event::DiagnosticsUpdated { .. } => {
-                this.summary = project.read(cx).diagnostic_summary(false, cx);
-                cx.notify();
-            }
-
-            _ => {}
-        })
-        .detach();
-
-        Self {
-            summary: project.read(cx).diagnostic_summary(false, cx),
-            in_progress_checks: project
-                .read(cx)
-                .language_servers_running_disk_based_diagnostics()
-                .collect(),
-            active_editor: None,
-            workspace: workspace.weak_handle(),
-            current_diagnostic: None,
-            _observe_active_editor: None,
-        }
-    }
-
-    fn go_to_next_diagnostic(&mut self, cx: &mut ViewContext<Self>) {
-        if let Some(editor) = self.active_editor.as_ref().and_then(|e| e.upgrade()) {
-            editor.update(cx, |editor, cx| {
-                editor.go_to_diagnostic_impl(editor::Direction::Next, cx);
-            })
-        }
-    }
-
-    fn update(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
-        let editor = editor.read(cx);
-        let buffer = editor.buffer().read(cx);
-        let cursor_position = editor.selections.newest::<usize>(cx).head();
-        let new_diagnostic = buffer
-            .snapshot(cx)
-            .diagnostics_in_range::<_, usize>(cursor_position..cursor_position, false)
-            .filter(|entry| !entry.range.is_empty())
-            .min_by_key(|entry| (entry.diagnostic.severity, entry.range.len()))
-            .map(|entry| entry.diagnostic);
-        if new_diagnostic != self.current_diagnostic {
-            self.current_diagnostic = new_diagnostic;
-            cx.notify();
-        }
-    }
-}
-
-impl EventEmitter<ToolbarItemEvent> for DiagnosticIndicator {}
-
-impl StatusItemView for DiagnosticIndicator {
-    fn set_active_pane_item(
-        &mut self,
-        active_pane_item: Option<&dyn ItemHandle>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
-            self.active_editor = Some(editor.downgrade());
-            self._observe_active_editor = Some(cx.observe(&editor, Self::update));
-            self.update(editor, cx);
-        } else {
-            self.active_editor = None;
-            self.current_diagnostic = None;
-            self._observe_active_editor = None;
-        }
-        cx.notify();
-    }
-}

crates/diagnostics2/src/project_diagnostics_settings.rs 🔗

@@ -1,28 +0,0 @@
-use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
-
-#[derive(Deserialize, Debug)]
-pub struct ProjectDiagnosticsSettings {
-    pub include_warnings: bool,
-}
-
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
-pub struct ProjectDiagnosticsSettingsContent {
-    include_warnings: Option<bool>,
-}
-
-impl settings::Settings for ProjectDiagnosticsSettings {
-    const KEY: Option<&'static str> = Some("diagnostics");
-    type FileContent = ProjectDiagnosticsSettingsContent;
-
-    fn load(
-        default_value: &Self::FileContent,
-        user_values: &[&Self::FileContent],
-        _cx: &mut gpui::AppContext,
-    ) -> anyhow::Result<Self>
-    where
-        Self: Sized,
-    {
-        Self::load_via_json_merge(default_value, user_values)
-    }
-}

crates/diagnostics2/src/toolbar_controls.rs 🔗

@@ -1,65 +0,0 @@
-use crate::ProjectDiagnosticsEditor;
-use gpui::{div, EventEmitter, ParentElement, Render, ViewContext, WeakView};
-use ui::prelude::*;
-use ui::{Icon, IconButton, Tooltip};
-use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
-
-pub struct ToolbarControls {
-    editor: Option<WeakView<ProjectDiagnosticsEditor>>,
-}
-
-impl Render for ToolbarControls {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        let include_warnings = self
-            .editor
-            .as_ref()
-            .and_then(|editor| editor.upgrade())
-            .map(|editor| editor.read(cx).include_warnings)
-            .unwrap_or(false);
-
-        let tooltip = if include_warnings {
-            "Exclude Warnings"
-        } else {
-            "Include Warnings"
-        };
-
-        div().child(
-            IconButton::new("toggle-warnings", Icon::ExclamationTriangle)
-                .tooltip(move |cx| Tooltip::text(tooltip, cx))
-                .on_click(cx.listener(|this, _, cx| {
-                    if let Some(editor) = this.editor.as_ref().and_then(|editor| editor.upgrade()) {
-                        editor.update(cx, |editor, cx| {
-                            editor.toggle_warnings(&Default::default(), cx);
-                        });
-                    }
-                })),
-        )
-    }
-}
-
-impl EventEmitter<ToolbarItemEvent> for ToolbarControls {}
-
-impl ToolbarItemView for ToolbarControls {
-    fn set_active_pane_item(
-        &mut self,
-        active_pane_item: Option<&dyn ItemHandle>,
-        _: &mut ViewContext<Self>,
-    ) -> ToolbarItemLocation {
-        if let Some(pane_item) = active_pane_item.as_ref() {
-            if let Some(editor) = pane_item.downcast::<ProjectDiagnosticsEditor>() {
-                self.editor = Some(editor.downgrade());
-                ToolbarItemLocation::PrimaryRight
-            } else {
-                ToolbarItemLocation::Hidden
-            }
-        } else {
-            ToolbarItemLocation::Hidden
-        }
-    }
-}
-
-impl ToolbarControls {
-    pub fn new() -> Self {
-        ToolbarControls { editor: None }
-    }
-}

crates/drag_and_drop/Cargo.toml 🔗

@@ -1,16 +0,0 @@
-[package]
-name = "drag_and_drop"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/drag_and_drop.rs"
-doctest = false
-
-[dependencies]
-collections = { path = "../collections" }
-gpui = { path = "../gpui" }
-
-[dev-dependencies]
-gpui = { path = "../gpui", features = ["test-support"] }

crates/drag_and_drop/src/drag_and_drop.rs 🔗

@@ -1,378 +0,0 @@
-use std::{any::Any, rc::Rc};
-
-use collections::HashSet;
-use gpui::{
-    elements::{Empty, MouseEventHandler, Overlay},
-    geometry::{rect::RectF, vector::Vector2F},
-    platform::{CursorStyle, Modifiers, MouseButton},
-    scene::{MouseDown, MouseDrag},
-    AnyElement, AnyWindowHandle, Element, View, ViewContext, WeakViewHandle, WindowContext,
-};
-
-const DEAD_ZONE: f32 = 4.;
-
-enum State<V> {
-    Down {
-        region_offset: Vector2F,
-        region: RectF,
-    },
-    DeadZone {
-        region_offset: Vector2F,
-        region: RectF,
-    },
-    Dragging {
-        modifiers: Modifiers,
-        window: AnyWindowHandle,
-        position: Vector2F,
-        region_offset: Vector2F,
-        region: RectF,
-        payload: Rc<dyn Any + 'static>,
-        render: Rc<dyn Fn(&Modifiers, Rc<dyn Any>, &mut ViewContext<V>) -> AnyElement<V>>,
-    },
-    Canceled,
-}
-
-impl<V> Clone for State<V> {
-    fn clone(&self) -> Self {
-        match self {
-            &State::Down {
-                region_offset,
-                region,
-            } => State::Down {
-                region_offset,
-                region,
-            },
-            &State::DeadZone {
-                region_offset,
-                region,
-            } => State::DeadZone {
-                region_offset,
-                region,
-            },
-            State::Dragging {
-                modifiers,
-                window,
-                position,
-                region_offset,
-                region,
-                payload,
-                render,
-            } => Self::Dragging {
-                window: window.clone(),
-                position: position.clone(),
-                region_offset: region_offset.clone(),
-                region: region.clone(),
-                payload: payload.clone(),
-                render: render.clone(),
-                modifiers: modifiers.clone(),
-            },
-            State::Canceled => State::Canceled,
-        }
-    }
-}
-
-pub struct DragAndDrop<V> {
-    containers: HashSet<WeakViewHandle<V>>,
-    currently_dragged: Option<State<V>>,
-}
-
-impl<V> Default for DragAndDrop<V> {
-    fn default() -> Self {
-        Self {
-            containers: Default::default(),
-            currently_dragged: Default::default(),
-        }
-    }
-}
-
-impl<V: 'static> DragAndDrop<V> {
-    pub fn register_container(&mut self, handle: WeakViewHandle<V>) {
-        self.containers.insert(handle);
-    }
-
-    pub fn currently_dragged<T: Any>(&self, window: AnyWindowHandle) -> Option<(Vector2F, Rc<T>)> {
-        self.currently_dragged.as_ref().and_then(|state| {
-            if let State::Dragging {
-                position,
-                payload,
-                window: window_dragged_from,
-                ..
-            } = state
-            {
-                if &window != window_dragged_from {
-                    return None;
-                }
-
-                payload
-                    .is::<T>()
-                    .then(|| payload.clone().downcast::<T>().ok())
-                    .flatten()
-                    .map(|payload| (position.clone(), payload))
-            } else {
-                None
-            }
-        })
-    }
-
-    pub fn any_currently_dragged(&self, window: AnyWindowHandle) -> bool {
-        self.currently_dragged
-            .as_ref()
-            .map(|state| {
-                if let State::Dragging {
-                    window: window_dragged_from,
-                    ..
-                } = state
-                {
-                    if &window != window_dragged_from {
-                        return false;
-                    }
-
-                    true
-                } else {
-                    false
-                }
-            })
-            .unwrap_or(false)
-    }
-
-    pub fn drag_started(event: MouseDown, cx: &mut WindowContext) {
-        cx.update_global(|this: &mut Self, _| {
-            this.currently_dragged = Some(State::Down {
-                region_offset: event.position - event.region.origin(),
-                region: event.region,
-            });
-        })
-    }
-
-    pub fn dragging<T: Any>(
-        event: MouseDrag,
-        payload: Rc<T>,
-        cx: &mut WindowContext,
-        render: Rc<impl 'static + Fn(&Modifiers, &T, &mut ViewContext<V>) -> AnyElement<V>>,
-    ) {
-        let window = cx.window();
-        cx.update_global(|this: &mut Self, cx| {
-            this.notify_containers_for_window(window, cx);
-
-            match this.currently_dragged.as_ref() {
-                Some(&State::Down {
-                    region_offset,
-                    region,
-                })
-                | Some(&State::DeadZone {
-                    region_offset,
-                    region,
-                }) => {
-                    if (event.position - (region.origin() + region_offset)).length() > DEAD_ZONE {
-                        this.currently_dragged = Some(State::Dragging {
-                            modifiers: event.modifiers,
-                            window,
-                            region_offset,
-                            region,
-                            position: event.position,
-                            payload,
-                            render: Rc::new(move |modifiers, payload, cx| {
-                                render(modifiers, payload.downcast_ref::<T>().unwrap(), cx)
-                            }),
-                        });
-                    } else {
-                        this.currently_dragged = Some(State::DeadZone {
-                            region_offset,
-                            region,
-                        })
-                    }
-                }
-                Some(&State::Dragging {
-                    region_offset,
-                    region,
-                    modifiers,
-                    ..
-                }) => {
-                    this.currently_dragged = Some(State::Dragging {
-                        modifiers,
-                        window,
-                        region_offset,
-                        region,
-                        position: event.position,
-                        payload,
-                        render: Rc::new(move |modifiers, payload, cx| {
-                            render(modifiers, payload.downcast_ref::<T>().unwrap(), cx)
-                        }),
-                    });
-                }
-                _ => {}
-            }
-        });
-    }
-
-    pub fn update_modifiers(new_modifiers: Modifiers, cx: &mut ViewContext<V>) -> bool {
-        let result = cx.update_global(|this: &mut Self, _| match &mut this.currently_dragged {
-            Some(state) => match state {
-                State::Dragging { modifiers, .. } => {
-                    *modifiers = new_modifiers;
-                    true
-                }
-                _ => false,
-            },
-            None => false,
-        });
-
-        if result {
-            cx.notify();
-        }
-
-        result
-    }
-
-    pub fn render(cx: &mut ViewContext<V>) -> Option<AnyElement<V>> {
-        enum DraggedElementHandler {}
-        cx.global::<Self>()
-            .currently_dragged
-            .clone()
-            .and_then(|state| {
-                match state {
-                    State::Down { .. } => None,
-                    State::DeadZone { .. } => None,
-                    State::Dragging {
-                        modifiers,
-                        window,
-                        region_offset,
-                        position,
-                        region,
-                        payload,
-                        render,
-                    } => {
-                        if cx.window() != window {
-                            return None;
-                        }
-
-                        let position = (position - region_offset).round();
-                        Some(
-                            Overlay::new(
-                                MouseEventHandler::new::<DraggedElementHandler, _>(
-                                    0,
-                                    cx,
-                                    |_, cx| render(&modifiers, payload, cx),
-                                )
-                                .with_cursor_style(CursorStyle::Arrow)
-                                .on_up(MouseButton::Left, |_, _, cx| {
-                                    cx.window_context().defer(|cx| {
-                                        cx.update_global::<Self, _, _>(|this, cx| {
-                                            this.finish_dragging(cx)
-                                        });
-                                    });
-                                    cx.propagate_event();
-                                })
-                                .on_up_out(MouseButton::Left, |_, _, cx| {
-                                    cx.window_context().defer(|cx| {
-                                        cx.update_global::<Self, _, _>(|this, cx| {
-                                            this.finish_dragging(cx)
-                                        });
-                                    });
-                                })
-                                // Don't block hover events or invalidations
-                                .with_hoverable(false)
-                                .constrained()
-                                .with_width(region.width())
-                                .with_height(region.height()),
-                            )
-                            .with_anchor_position(position)
-                            .into_any(),
-                        )
-                    }
-
-                    State::Canceled => Some(
-                        MouseEventHandler::new::<DraggedElementHandler, _>(0, cx, |_, _| {
-                            Empty::new().constrained().with_width(0.).with_height(0.)
-                        })
-                        .on_up(MouseButton::Left, |_, _, cx| {
-                            cx.window_context().defer(|cx| {
-                                cx.update_global::<Self, _, _>(|this, _| {
-                                    this.currently_dragged = None;
-                                });
-                            });
-                        })
-                        .on_up_out(MouseButton::Left, |_, _, cx| {
-                            cx.window_context().defer(|cx| {
-                                cx.update_global::<Self, _, _>(|this, _| {
-                                    this.currently_dragged = None;
-                                });
-                            });
-                        })
-                        .into_any(),
-                    ),
-                }
-            })
-    }
-
-    pub fn cancel_dragging<P: Any>(&mut self, cx: &mut WindowContext) {
-        if let Some(State::Dragging {
-            payload, window, ..
-        }) = &self.currently_dragged
-        {
-            if payload.is::<P>() {
-                let window = *window;
-                self.currently_dragged = Some(State::Canceled);
-                self.notify_containers_for_window(window, cx);
-            }
-        }
-    }
-
-    fn finish_dragging(&mut self, cx: &mut WindowContext) {
-        if let Some(State::Dragging { window, .. }) = self.currently_dragged.take() {
-            self.notify_containers_for_window(window, cx);
-        }
-    }
-
-    fn notify_containers_for_window(&mut self, window: AnyWindowHandle, cx: &mut WindowContext) {
-        self.containers.retain(|container| {
-            if let Some(container) = container.upgrade(cx) {
-                if container.window() == window {
-                    container.update(cx, |_, cx| cx.notify());
-                }
-                true
-            } else {
-                false
-            }
-        });
-    }
-}
-
-pub trait Draggable<V> {
-    fn as_draggable<D: View, P: Any>(
-        self,
-        payload: P,
-        render: impl 'static + Fn(&Modifiers, &P, &mut ViewContext<D>) -> AnyElement<D>,
-    ) -> Self
-    where
-        Self: Sized;
-}
-
-impl<V: 'static> Draggable<V> for MouseEventHandler<V> {
-    fn as_draggable<D: View, P: Any>(
-        self,
-        payload: P,
-        render: impl 'static + Fn(&Modifiers, &P, &mut ViewContext<D>) -> AnyElement<D>,
-    ) -> Self
-    where
-        Self: Sized,
-    {
-        let payload = Rc::new(payload);
-        let render = Rc::new(render);
-        self.on_down(MouseButton::Left, move |e, _, cx| {
-            cx.propagate_event();
-            DragAndDrop::<D>::drag_started(e, cx);
-        })
-        .on_drag(MouseButton::Left, move |e, _, cx| {
-            if e.end {
-                cx.update_global::<DragAndDrop<D>, _, _>(|drag_and_drop, cx| {
-                    drag_and_drop.finish_dragging(cx)
-                })
-            } else {
-                let payload = payload.clone();
-                let render = render.clone();
-                DragAndDrop::<D>::dragging(e, payload, cx, render)
-            }
-        })
-    }
-}

crates/editor/Cargo.toml 🔗

@@ -27,10 +27,9 @@ client = { path = "../client" }
 clock = { path = "../clock" }
 copilot = { path = "../copilot" }
 db = { path = "../db" }
-drag_and_drop = { path = "../drag_and_drop" }
 collections = { path = "../collections" }
-context_menu = { path = "../context_menu" }
-fuzzy = { path = "../fuzzy" }
+# context_menu = { path = "../context_menu" }
+fuzzy = {  path = "../fuzzy" }
 git = { path = "../git" }
 gpui = { path = "../gpui" }
 language = { path = "../language" }
@@ -44,6 +43,7 @@ snippet = { path = "../snippet" }
 sum_tree = { path = "../sum_tree" }
 text = { path = "../text" }
 theme = { path = "../theme" }
+ui = { path = "../ui" }
 util = { path = "../util" }
 sqlez = { path = "../sqlez" }
 workspace = { path = "../workspace" }
@@ -62,6 +62,7 @@ postage.workspace = true
 rand.workspace = true
 schemars.workspace = true
 serde.workspace = true
+serde_json.workspace = true
 serde_derive.workspace = true
 smallvec.workspace = true
 smol.workspace = true
@@ -1,5 +1,6 @@
 use crate::EditorSettings;
-use gpui::{Entity, ModelContext};
+use gpui::ModelContext;
+use settings::Settings;
 use settings::SettingsStore;
 use smol::Timer;
 use std::time::Duration;
@@ -16,7 +17,7 @@ pub struct BlinkManager {
 impl BlinkManager {
     pub fn new(blink_interval: Duration, cx: &mut ModelContext<Self>) -> Self {
         // Make sure we blink the cursors if the setting is re-enabled
-        cx.observe_global::<SettingsStore, _>(move |this, cx| {
+        cx.observe_global::<SettingsStore>(move |this, cx| {
             this.blink_cursors(this.blink_epoch, cx)
         })
         .detach();
@@ -41,14 +42,9 @@ impl BlinkManager {
 
         let epoch = self.next_blink_epoch();
         let interval = self.blink_interval;
-        cx.spawn(|this, mut cx| {
-            let this = this.downgrade();
-            async move {
-                Timer::after(interval).await;
-                if let Some(this) = this.upgrade(&cx) {
-                    this.update(&mut cx, |this, cx| this.resume_cursor_blinking(epoch, cx))
-                }
-            }
+        cx.spawn(|this, mut cx| async move {
+            Timer::after(interval).await;
+            this.update(&mut cx, |this, cx| this.resume_cursor_blinking(epoch, cx))
         })
         .detach();
     }
@@ -61,20 +57,18 @@ impl BlinkManager {
     }
 
     fn blink_cursors(&mut self, epoch: usize, cx: &mut ModelContext<Self>) {
-        if settings::get::<EditorSettings>(cx).cursor_blink {
+        if EditorSettings::get_global(cx).cursor_blink {
             if epoch == self.blink_epoch && self.enabled && !self.blinking_paused {
                 self.visible = !self.visible;
                 cx.notify();
 
                 let epoch = self.next_blink_epoch();
                 let interval = self.blink_interval;
-                cx.spawn(|this, mut cx| {
-                    let this = this.downgrade();
-                    async move {
-                        Timer::after(interval).await;
-                        if let Some(this) = this.upgrade(&cx) {
-                            this.update(&mut cx, |this, cx| this.blink_cursors(epoch, cx));
-                        }
+                cx.spawn(|this, mut cx| async move {
+                    Timer::after(interval).await;
+                    if let Some(this) = this.upgrade() {
+                        this.update(&mut cx, |this, cx| this.blink_cursors(epoch, cx))
+                            .ok();
                     }
                 })
                 .detach();
@@ -92,6 +86,10 @@ impl BlinkManager {
     }
 
     pub fn enable(&mut self, cx: &mut ModelContext<Self>) {
+        if self.enabled {
+            return;
+        }
+
         self.enabled = true;
         // Set cursors as invisible and start blinking: this causes cursors
         // to be visible during the next render.
@@ -107,7 +105,3 @@ impl BlinkManager {
         self.visible
     }
 }
-
-impl Entity for BlinkManager {
-    type Event = ();
-}

crates/editor/src/display_map.rs 🔗

@@ -4,19 +4,15 @@ mod inlay_map;
 mod tab_map;
 mod wrap_map;
 
+use crate::EditorStyle;
 use crate::{
     link_go_to_definition::InlayHighlight, movement::TextLayoutDetails, Anchor, AnchorRangeExt,
-    EditorStyle, InlayId, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint,
+    InlayId, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint,
 };
 pub use block_map::{BlockMap, BlockPoint};
 use collections::{BTreeMap, HashMap, HashSet};
 use fold_map::FoldMap;
-use gpui::{
-    color::Color,
-    fonts::{FontId, HighlightStyle, Underline},
-    text_layout::{Line, RunStyle},
-    Entity, ModelContext, ModelHandle,
-};
+use gpui::{Font, HighlightStyle, Hsla, LineLayout, Model, ModelContext, Pixels, UnderlineStyle};
 use inlay_map::InlayMap;
 use language::{
     language_settings::language_settings, OffsetUtf16, Point, Subscription as BufferSubscription,
@@ -25,6 +21,7 @@ use lsp::DiagnosticSeverity;
 use std::{any::TypeId, borrow::Cow, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc};
 use sum_tree::{Bias, TreeMap};
 use tab_map::TabMap;
+
 use wrap_map::WrapMap;
 
 pub use block_map::{
@@ -32,7 +29,7 @@ pub use block_map::{
     BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, TransformBlock,
 };
 
-pub use self::fold_map::FoldPoint;
+pub use self::fold_map::{Fold, FoldPoint};
 pub use self::inlay_map::{Inlay, InlayOffset, InlayPoint};
 
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -41,6 +38,8 @@ pub enum FoldStatus {
     Foldable,
 }
 
+const UNNECESSARY_CODE_FADE: f32 = 0.3;
+
 pub trait ToDisplayPoint {
     fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint;
 }
@@ -49,28 +48,24 @@ type TextHighlights = TreeMap<Option<TypeId>, Arc<(HighlightStyle, Vec<Range<Anc
 type InlayHighlights = BTreeMap<TypeId, HashMap<InlayId, (HighlightStyle, InlayHighlight)>>;
 
 pub struct DisplayMap {
-    buffer: ModelHandle<MultiBuffer>,
+    buffer: Model<MultiBuffer>,
     buffer_subscription: BufferSubscription,
     fold_map: FoldMap,
     inlay_map: InlayMap,
     tab_map: TabMap,
-    wrap_map: ModelHandle<WrapMap>,
+    wrap_map: Model<WrapMap>,
     block_map: BlockMap,
     text_highlights: TextHighlights,
     inlay_highlights: InlayHighlights,
     pub clip_at_line_ends: bool,
 }
 
-impl Entity for DisplayMap {
-    type Event = ();
-}
-
 impl DisplayMap {
     pub fn new(
-        buffer: ModelHandle<MultiBuffer>,
-        font_id: FontId,
-        font_size: f32,
-        wrap_width: Option<f32>,
+        buffer: Model<MultiBuffer>,
+        font: Font,
+        font_size: Pixels,
+        wrap_width: Option<Pixels>,
         buffer_header_height: u8,
         excerpt_header_height: u8,
         cx: &mut ModelContext<Self>,
@@ -81,7 +76,7 @@ impl DisplayMap {
         let (inlay_map, snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
         let (fold_map, snapshot) = FoldMap::new(snapshot);
         let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
-        let (wrap_map, snapshot) = WrapMap::new(snapshot, font_id, font_size, wrap_width, cx);
+        let (wrap_map, snapshot) = WrapMap::new(snapshot, font, font_size, wrap_width, cx);
         let block_map = BlockMap::new(snapshot, buffer_header_height, excerpt_header_height);
         cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
         DisplayMap {
@@ -127,7 +122,7 @@ impl DisplayMap {
         self.fold(
             other
                 .folds_in_range(0..other.buffer_snapshot.len())
-                .map(|fold| fold.to_offset(&other.buffer_snapshot)),
+                .map(|fold| fold.range.to_offset(&other.buffer_snapshot)),
             cx,
         );
     }
@@ -249,16 +244,16 @@ impl DisplayMap {
         cleared
     }
 
-    pub fn set_font(&self, font_id: FontId, font_size: f32, cx: &mut ModelContext<Self>) -> bool {
+    pub fn set_font(&self, font: Font, font_size: Pixels, cx: &mut ModelContext<Self>) -> bool {
         self.wrap_map
-            .update(cx, |map, cx| map.set_font(font_id, font_size, cx))
+            .update(cx, |map, cx| map.set_font_with_size(font, font_size, cx))
     }
 
-    pub fn set_fold_ellipses_color(&mut self, color: Color) -> bool {
+    pub fn set_fold_ellipses_color(&mut self, color: Hsla) -> bool {
         self.fold_map.set_ellipses_color(color)
     }
 
-    pub fn set_wrap_width(&self, width: Option<f32>, cx: &mut ModelContext<Self>) -> bool {
+    pub fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut ModelContext<Self>) -> bool {
         self.wrap_map
             .update(cx, |map, cx| map.set_wrap_width(width, cx))
     }
@@ -296,7 +291,7 @@ impl DisplayMap {
         self.block_map.read(snapshot, edits);
     }
 
-    fn tab_size(buffer: &ModelHandle<MultiBuffer>, cx: &mut ModelContext<Self>) -> NonZeroU32 {
+    fn tab_size(buffer: &Model<MultiBuffer>, cx: &mut ModelContext<Self>) -> NonZeroU32 {
         let language = buffer
             .read(cx)
             .as_singleton()
@@ -510,18 +505,18 @@ impl DisplaySnapshot {
         &'a self,
         display_rows: Range<u32>,
         language_aware: bool,
-        style: &'a EditorStyle,
+        editor_style: &'a EditorStyle,
     ) -> impl Iterator<Item = HighlightedChunk<'a>> {
         self.chunks(
             display_rows,
             language_aware,
-            Some(style.theme.hint),
-            Some(style.theme.suggestion),
+            Some(editor_style.inlays_style),
+            Some(editor_style.suggestions_style),
         )
         .map(|chunk| {
             let mut highlight_style = chunk
                 .syntax_highlight_id
-                .and_then(|id| id.style(&style.syntax));
+                .and_then(|id| id.style(&editor_style.syntax));
 
             if let Some(chunk_highlight) = chunk.highlight_style {
                 if let Some(highlight_style) = highlight_style.as_mut() {
@@ -534,17 +529,18 @@ impl DisplaySnapshot {
             let mut diagnostic_highlight = HighlightStyle::default();
 
             if chunk.is_unnecessary {
-                diagnostic_highlight.fade_out = Some(style.unnecessary_code_fade);
+                diagnostic_highlight.fade_out = Some(UNNECESSARY_CODE_FADE);
             }
 
             if let Some(severity) = chunk.diagnostic_severity {
                 // Omit underlines for HINT/INFO diagnostics on 'unnecessary' code.
                 if severity <= DiagnosticSeverity::WARNING || !chunk.is_unnecessary {
-                    let diagnostic_style = super::diagnostic_style(severity, true, style);
-                    diagnostic_highlight.underline = Some(Underline {
-                        color: Some(diagnostic_style.message.text.color),
+                    let diagnostic_color =
+                        super::diagnostic_style(severity, true, &editor_style.status);
+                    diagnostic_highlight.underline = Some(UnderlineStyle {
+                        color: Some(diagnostic_color),
                         thickness: 1.0.into(),
-                        squiggly: true,
+                        wavy: true,
                     });
                 }
             }
@@ -563,81 +559,64 @@ impl DisplaySnapshot {
         })
     }
 
-    pub fn lay_out_line_for_row(
+    pub fn layout_row(
         &self,
         display_row: u32,
         TextLayoutDetails {
-            font_cache,
-            text_layout_cache,
+            text_system,
             editor_style,
+            rem_size,
         }: &TextLayoutDetails,
-    ) -> Line {
-        let mut styles = Vec::new();
+    ) -> Arc<LineLayout> {
+        let mut runs = Vec::new();
         let mut line = String::new();
-        let mut ended_in_newline = false;
 
         let range = display_row..display_row + 1;
-        for chunk in self.highlighted_chunks(range, false, editor_style) {
+        for chunk in self.highlighted_chunks(range, false, &editor_style) {
             line.push_str(chunk.chunk);
 
             let text_style = if let Some(style) = chunk.style {
-                editor_style
-                    .text
-                    .clone()
-                    .highlight(style, font_cache)
-                    .map(Cow::Owned)
-                    .unwrap_or_else(|_| Cow::Borrowed(&editor_style.text))
+                Cow::Owned(editor_style.text.clone().highlight(style))
             } else {
                 Cow::Borrowed(&editor_style.text)
             };
-            ended_in_newline = chunk.chunk.ends_with("\n");
-
-            styles.push((
-                chunk.chunk.len(),
-                RunStyle {
-                    font_id: text_style.font_id,
-                    color: text_style.color,
-                    underline: text_style.underline,
-                },
-            ));
+
+            runs.push(text_style.to_run(chunk.chunk.len()))
         }
 
-        // our pixel positioning logic assumes each line ends in \n,
-        // this is almost always true except for the last line which
-        // may have no trailing newline.
-        if !ended_in_newline && display_row == self.max_point().row() {
-            line.push_str("\n");
-
-            styles.push((
-                "\n".len(),
-                RunStyle {
-                    font_id: editor_style.text.font_id,
-                    color: editor_style.text_color,
-                    underline: editor_style.text.underline,
-                },
-            ));
+        if line.ends_with('\n') {
+            line.pop();
+            if let Some(last_run) = runs.last_mut() {
+                last_run.len -= 1;
+                if last_run.len == 0 {
+                    runs.pop();
+                }
+            }
         }
 
-        text_layout_cache.layout_str(&line, editor_style.text.font_size, &styles)
+        let font_size = editor_style.text.font_size.to_pixels(*rem_size);
+        text_system
+            .layout_line(&line, font_size, &runs)
+            .expect("we expect the font to be loaded because it's rendered by the editor")
     }
 
-    pub fn x_for_point(
+    pub fn x_for_display_point(
         &self,
         display_point: DisplayPoint,
         text_layout_details: &TextLayoutDetails,
-    ) -> f32 {
-        let layout_line = self.lay_out_line_for_row(display_point.row(), text_layout_details);
-        layout_line.x_for_index(display_point.column() as usize)
+    ) -> Pixels {
+        let line = self.layout_row(display_point.row(), text_layout_details);
+        line.x_for_index(display_point.column() as usize)
     }
 
-    pub fn column_for_x(
+    pub fn display_column_for_x(
         &self,
         display_row: u32,
-        x_coordinate: f32,
-        text_layout_details: &TextLayoutDetails,
+        x: Pixels,
+        details: &TextLayoutDetails,
     ) -> u32 {
-        let layout_line = self.lay_out_line_for_row(display_row, text_layout_details);
-        layout_line.closest_index_for_x(x_coordinate) as u32
+        let layout_line = self.layout_row(display_row, details);
+        layout_line.closest_index_for_x(x) as u32
     }
 
     pub fn chars_at(
@@ -740,7 +719,7 @@ impl DisplaySnapshot {
         DisplayPoint(point)
     }
 
-    pub fn folds_in_range<T>(&self, range: Range<T>) -> impl Iterator<Item = &Range<Anchor>>
+    pub fn folds_in_range<T>(&self, range: Range<T>) -> impl Iterator<Item = &Fold>
     where
         T: ToOffset,
     {
@@ -1015,7 +994,7 @@ pub mod tests {
         movement,
         test::{editor_test_context::EditorTestContext, marked_display_snapshot},
     };
-    use gpui::{color::Color, elements::*, test::observe, AppContext};
+    use gpui::{div, font, observe, px, AppContext, Context, Element, Hsla};
     use language::{
         language_settings::{AllLanguageSettings, AllLanguageSettingsContent},
         Buffer, Language, LanguageConfig, SelectionGoal,
@@ -1025,34 +1004,27 @@ pub mod tests {
     use settings::SettingsStore;
     use smol::stream::StreamExt;
     use std::{env, sync::Arc};
-    use theme::SyntaxTheme;
+    use theme::{LoadThemes, SyntaxTheme};
     use util::test::{marked_text_ranges, sample_text};
     use Bias::*;
 
     #[gpui::test(iterations = 100)]
     async fn test_random_display_map(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
-        cx.foreground().set_block_on_ticks(0..=50);
-        cx.foreground().forbid_parking();
+        cx.background_executor.set_block_on_ticks(0..=50);
         let operations = env::var("OPERATIONS")
             .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
             .unwrap_or(10);
 
-        let font_cache = cx.font_cache().clone();
+        let _test_platform = &cx.test_platform;
         let mut tab_size = rng.gen_range(1..=4);
         let buffer_start_excerpt_header_height = rng.gen_range(1..=5);
         let excerpt_header_height = rng.gen_range(1..=5);
-        let family_id = font_cache
-            .load_family(&["Helvetica"], &Default::default())
-            .unwrap();
-        let font_id = font_cache
-            .select_font(family_id, &Default::default())
-            .unwrap();
-        let font_size = 14.0;
+        let font_size = px(14.0);
         let max_wrap_width = 300.0;
         let mut wrap_width = if rng.gen_bool(0.1) {
             None
         } else {
-            Some(rng.gen_range(0.0..=max_wrap_width))
+            Some(px(rng.gen_range(0.0..=max_wrap_width)))
         };
 
         log::info!("tab size: {}", tab_size);
@@ -1074,10 +1046,10 @@ pub mod tests {
             }
         });
 
-        let map = cx.add_model(|cx| {
+        let map = cx.new_model(|cx| {
             DisplayMap::new(
                 buffer.clone(),
-                font_id,
+                font("Helvetica"),
                 font_size,
                 wrap_width,
                 buffer_start_excerpt_header_height,
@@ -1103,7 +1075,7 @@ pub mod tests {
                     wrap_width = if rng.gen_bool(0.2) {
                         None
                     } else {
-                        Some(rng.gen_range(0.0..=max_wrap_width))
+                        Some(px(rng.gen_range(0.0..=max_wrap_width)))
                     };
                     log::info!("setting wrap width to {:?}", wrap_width);
                     map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
@@ -1114,7 +1086,7 @@ pub mod tests {
                     tab_size = *tab_sizes.choose(&mut rng).unwrap();
                     log::info!("setting tab size to {:?}", tab_size);
                     cx.update(|cx| {
-                        cx.update_global::<SettingsStore, _, _>(|store, cx| {
+                        cx.update_global::<SettingsStore, _>(|store, cx| {
                             store.update_user_settings::<AllLanguageSettings>(cx, |s| {
                                 s.defaults.tab_size = NonZeroU32::new(tab_size);
                             });
@@ -1150,7 +1122,7 @@ pub mod tests {
                                         position,
                                         height,
                                         disposition,
-                                        render: Arc::new(|_| Empty::new().into_any()),
+                                        render: Arc::new(|_| div().into_any()),
                                     }
                                 })
                                 .collect::<Vec<_>>();
@@ -1295,7 +1267,8 @@ pub mod tests {
 
     #[gpui::test(retries = 5)]
     async fn test_soft_wraps(cx: &mut gpui::TestAppContext) {
-        cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
+        cx.background_executor
+            .set_block_on_ticks(usize::MAX..=usize::MAX);
         cx.update(|cx| {
             init_test(cx, |_| {});
         });
@@ -1304,25 +1277,25 @@ pub mod tests {
         let editor = cx.editor.clone();
         let window = cx.window.clone();
 
-        cx.update_window(window, |cx| {
+        _ = cx.update_window(window, |_, cx| {
             let text_layout_details =
-                editor.read_with(cx, |editor, cx| editor.text_layout_details(cx));
-
-            let font_cache = cx.font_cache().clone();
+                editor.update(cx, |editor, cx| editor.text_layout_details(cx));
 
-            let family_id = font_cache
-                .load_family(&["Helvetica"], &Default::default())
-                .unwrap();
-            let font_id = font_cache
-                .select_font(family_id, &Default::default())
-                .unwrap();
-            let font_size = 12.0;
-            let wrap_width = Some(64.);
+            let font_size = px(12.0);
+            let wrap_width = Some(px(64.));
 
             let text = "one two three four five\nsix seven eight";
             let buffer = MultiBuffer::build_simple(text, cx);
-            let map = cx.add_model(|cx| {
-                DisplayMap::new(buffer.clone(), font_id, font_size, wrap_width, 1, 1, cx)
+            let map = cx.new_model(|cx| {
+                DisplayMap::new(
+                    buffer.clone(),
+                    font("Helvetica"),
+                    font_size,
+                    wrap_width,
+                    1,
+                    1,
+                    cx,
+                )
             });
 
             let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
@@ -1347,7 +1320,7 @@ pub mod tests {
                 DisplayPoint::new(0, 7)
             );
 
-            let x = snapshot.x_for_point(DisplayPoint::new(1, 10), &text_layout_details);
+            let x = snapshot.x_for_display_point(DisplayPoint::new(1, 10), &text_layout_details);
             assert_eq!(
                 movement::up(
                     &snapshot,
@@ -1358,33 +1331,33 @@ pub mod tests {
                 ),
                 (
                     DisplayPoint::new(0, 7),
-                    SelectionGoal::HorizontalPosition(x)
+                    SelectionGoal::HorizontalPosition(x.0)
                 )
             );
             assert_eq!(
                 movement::down(
                     &snapshot,
                     DisplayPoint::new(0, 7),
-                    SelectionGoal::HorizontalPosition(x),
+                    SelectionGoal::HorizontalPosition(x.0),
                     false,
                     &text_layout_details
                 ),
                 (
                     DisplayPoint::new(1, 10),
-                    SelectionGoal::HorizontalPosition(x)
+                    SelectionGoal::HorizontalPosition(x.0)
                 )
             );
             assert_eq!(
                 movement::down(
                     &snapshot,
                     DisplayPoint::new(1, 10),
-                    SelectionGoal::HorizontalPosition(x),
+                    SelectionGoal::HorizontalPosition(x.0),
                     false,
                     &text_layout_details
                 ),
                 (
                     DisplayPoint::new(2, 4),
-                    SelectionGoal::HorizontalPosition(x)
+                    SelectionGoal::HorizontalPosition(x.0)
                 )
             );
 
@@ -1400,7 +1373,9 @@ pub mod tests {
             );
 
             // Re-wrap on font size changes
-            map.update(cx, |map, cx| map.set_font(font_id, font_size + 3., cx));
+            map.update(cx, |map, cx| {
+                map.set_font(font("Helvetica"), px(font_size.0 + 3.), cx)
+            });
 
             let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
             assert_eq!(
@@ -1416,17 +1391,11 @@ pub mod tests {
 
         let text = sample_text(6, 6, 'a');
         let buffer = MultiBuffer::build_simple(&text, cx);
-        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 map =
-            cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx));
+
+        let font_size = px(14.0);
+        let map = cx.new_model(|cx| {
+            DisplayMap::new(buffer.clone(), font("Helvetica"), font_size, None, 1, 1, cx)
+        });
 
         buffer.update(cx, |buffer, cx| {
             buffer.edit(
@@ -1470,9 +1439,9 @@ pub mod tests {
             }"#
         .unindent();
 
-        let theme = SyntaxTheme::new(vec![
-            ("mod.body".to_string(), Color::red().into()),
-            ("fn.name".to_string(), Color::blue().into()),
+        let theme = SyntaxTheme::new_test(vec![
+            ("mod.body", Hsla::red().into()),
+            ("fn.name", Hsla::blue().into()),
         ]);
         let language = Arc::new(
             Language::new(
@@ -1495,38 +1464,33 @@ pub mod tests {
 
         cx.update(|cx| init_test(cx, |s| s.defaults.tab_size = Some(2.try_into().unwrap())));
 
-        let buffer = cx
-            .add_model(|cx| Buffer::new(0, cx.model_id() as u64, text).with_language(language, cx));
-        buffer.condition(cx, |buf, _| !buf.is_parsing()).await;
-        let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
+        let buffer = cx.new_model(|cx| {
+            Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
+        });
+        cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
+        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 
-        let font_cache = cx.font_cache();
-        let family_id = font_cache
-            .load_family(&["Helvetica"], &Default::default())
-            .unwrap();
-        let font_id = font_cache
-            .select_font(family_id, &Default::default())
-            .unwrap();
-        let font_size = 14.0;
+        let font_size = px(14.0);
 
-        let map = cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx));
+        let map = cx
+            .new_model(|cx| DisplayMap::new(buffer, font("Helvetica"), font_size, None, 1, 1, cx));
         assert_eq!(
             cx.update(|cx| syntax_chunks(0..5, &map, &theme, cx)),
             vec![
                 ("fn ".to_string(), None),
-                ("outer".to_string(), Some(Color::blue())),
+                ("outer".to_string(), Some(Hsla::blue())),
                 ("() {}\n\nmod module ".to_string(), None),
-                ("{\n    fn ".to_string(), Some(Color::red())),
-                ("inner".to_string(), Some(Color::blue())),
-                ("() {}\n}".to_string(), Some(Color::red())),
+                ("{\n    fn ".to_string(), Some(Hsla::red())),
+                ("inner".to_string(), Some(Hsla::blue())),
+                ("() {}\n}".to_string(), Some(Hsla::red())),
             ]
         );
         assert_eq!(
             cx.update(|cx| syntax_chunks(3..5, &map, &theme, cx)),
             vec![
-                ("    fn ".to_string(), Some(Color::red())),
-                ("inner".to_string(), Some(Color::blue())),
-                ("() {}\n}".to_string(), Some(Color::red())),
+                ("    fn ".to_string(), Some(Hsla::red())),
+                ("inner".to_string(), Some(Hsla::blue())),
+                ("() {}\n}".to_string(), Some(Hsla::red())),
             ]
         );
 
@@ -1537,11 +1501,11 @@ pub mod tests {
             cx.update(|cx| syntax_chunks(0..2, &map, &theme, cx)),
             vec![
                 ("fn ".to_string(), None),
-                ("out".to_string(), Some(Color::blue())),
+                ("out".to_string(), Some(Hsla::blue())),
                 ("⋯".to_string(), None),
-                ("  fn ".to_string(), Some(Color::red())),
-                ("inner".to_string(), Some(Color::blue())),
-                ("() {}\n}".to_string(), Some(Color::red())),
+                ("  fn ".to_string(), Some(Hsla::red())),
+                ("inner".to_string(), Some(Hsla::blue())),
+                ("() {}\n}".to_string(), Some(Hsla::red())),
             ]
         );
     }
@@ -1550,7 +1514,8 @@ pub mod tests {
     async fn test_chunks_with_soft_wrapping(cx: &mut gpui::TestAppContext) {
         use unindent::Unindent as _;
 
-        cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
+        cx.background_executor
+            .set_block_on_ticks(usize::MAX..=usize::MAX);
 
         let text = r#"
             fn outer() {}
@@ -1560,9 +1525,9 @@ pub mod tests {
             }"#
         .unindent();
 
-        let theme = SyntaxTheme::new(vec![
-            ("mod.body".to_string(), Color::red().into()),
-            ("fn.name".to_string(), Color::blue().into()),
+        let theme = SyntaxTheme::new_test(vec![
+            ("mod.body", Hsla::red().into()),
+            ("fn.name", Hsla::blue().into()),
         ]);
         let language = Arc::new(
             Language::new(
@@ -1585,28 +1550,22 @@ pub mod tests {
 
         cx.update(|cx| init_test(cx, |_| {}));
 
-        let buffer = cx
-            .add_model(|cx| Buffer::new(0, cx.model_id() as u64, text).with_language(language, cx));
-        buffer.condition(cx, |buf, _| !buf.is_parsing()).await;
-        let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
+        let buffer = cx.new_model(|cx| {
+            Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
+        });
+        cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
+        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 
-        let font_cache = cx.font_cache();
+        let font_size = px(16.0);
 
-        let family_id = font_cache
-            .load_family(&["Courier"], &Default::default())
-            .unwrap();
-        let font_id = font_cache
-            .select_font(family_id, &Default::default())
-            .unwrap();
-        let font_size = 16.0;
-
-        let map =
-            cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, Some(40.0), 1, 1, cx));
+        let map = cx.new_model(|cx| {
+            DisplayMap::new(buffer, font("Courier"), font_size, Some(px(40.0)), 1, 1, cx)
+        });
         assert_eq!(
             cx.update(|cx| syntax_chunks(0..5, &map, &theme, cx)),
             [
                 ("fn \n".to_string(), None),
-                ("oute\nr".to_string(), Some(Color::blue())),
+                ("oute\nr".to_string(), Some(Hsla::blue())),
                 ("() \n{}\n\n".to_string(), None),
             ]
         );
@@ -1621,10 +1580,10 @@ pub mod tests {
         assert_eq!(
             cx.update(|cx| syntax_chunks(1..4, &map, &theme, cx)),
             [
-                ("out".to_string(), Some(Color::blue())),
+                ("out".to_string(), Some(Hsla::blue())),
                 ("⋯\n".to_string(), None),
-                ("  \nfn ".to_string(), Some(Color::red())),
-                ("i\n".to_string(), Some(Color::blue()))
+                ("  \nfn ".to_string(), Some(Hsla::red())),
+                ("i\n".to_string(), Some(Hsla::blue()))
             ]
         );
     }
@@ -1633,9 +1592,9 @@ pub mod tests {
     async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) {
         cx.update(|cx| init_test(cx, |_| {}));
 
-        let theme = SyntaxTheme::new(vec![
-            ("operator".to_string(), Color::red().into()),
-            ("string".to_string(), Color::green().into()),
+        let theme = SyntaxTheme::new_test(vec![
+            ("operator", Hsla::red().into()),
+            ("string", Hsla::green().into()),
         ]);
         let language = Arc::new(
             Language::new(
@@ -1658,27 +1617,22 @@ pub mod tests {
 
         let (text, highlighted_ranges) = marked_text_ranges(r#"constˇ «a»: B = "c «d»""#, false);
 
-        let buffer = cx
-            .add_model(|cx| Buffer::new(0, cx.model_id() as u64, text).with_language(language, cx));
-        buffer.condition(cx, |buf, _| !buf.is_parsing()).await;
+        let buffer = cx.new_model(|cx| {
+            Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
+        });
+        cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
 
-        let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
+        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
         let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
 
-        let font_cache = cx.font_cache();
-        let family_id = font_cache
-            .load_family(&["Courier"], &Default::default())
-            .unwrap();
-        let font_id = font_cache
-            .select_font(family_id, &Default::default())
-            .unwrap();
-        let font_size = 16.0;
-        let map = cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx));
+        let font_size = px(16.0);
+        let map =
+            cx.new_model(|cx| DisplayMap::new(buffer, font("Courier"), font_size, None, 1, 1, cx));
 
         enum MyType {}
 
         let style = HighlightStyle {
-            color: Some(Color::blue()),
+            color: Some(Hsla::blue()),
             ..Default::default()
         };
 
@@ -1700,12 +1654,12 @@ pub mod tests {
             cx.update(|cx| chunks(0..10, &map, &theme, cx)),
             [
                 ("const ".to_string(), None, None),
-                ("a".to_string(), None, Some(Color::blue())),
-                (":".to_string(), Some(Color::red()), None),
+                ("a".to_string(), None, Some(Hsla::blue())),
+                (":".to_string(), Some(Hsla::red()), None),
                 (" B = ".to_string(), None, None),
-                ("\"c ".to_string(), Some(Color::green()), None),
-                ("d".to_string(), Some(Color::green()), Some(Color::blue())),
-                ("\"".to_string(), Some(Color::green()), None),
+                ("\"c ".to_string(), Some(Hsla::green()), None),
+                ("d".to_string(), Some(Hsla::green()), Some(Hsla::blue())),
+                ("\"".to_string(), Some(Hsla::green()), None),
             ]
         );
     }
@@ -1785,17 +1739,11 @@ pub mod tests {
 
         let text = "✅\t\tα\nβ\t\n🏀β\t\tγ";
         let buffer = MultiBuffer::build_simple(text, cx);
-        let font_cache = cx.font_cache();
-        let family_id = font_cache
-            .load_family(&["Helvetica"], &Default::default())
-            .unwrap();
-        let font_id = font_cache
-            .select_font(family_id, &Default::default())
-            .unwrap();
-        let font_size = 14.0;
+        let font_size = px(14.0);
 
-        let map =
-            cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx));
+        let map = cx.new_model(|cx| {
+            DisplayMap::new(buffer.clone(), font("Helvetica"), font_size, None, 1, 1, cx)
+        });
         let map = map.update(cx, |map, cx| map.snapshot(cx));
         assert_eq!(map.text(), "✅       α\nβ   \n🏀β      γ");
         assert_eq!(
@@ -1846,16 +1794,10 @@ pub mod tests {
         init_test(cx, |_| {});
 
         let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
-        let font_cache = cx.font_cache();
-        let family_id = font_cache
-            .load_family(&["Helvetica"], &Default::default())
-            .unwrap();
-        let font_id = font_cache
-            .select_font(family_id, &Default::default())
-            .unwrap();
-        let font_size = 14.0;
-        let map =
-            cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx));
+        let font_size = px(14.0);
+        let map = cx.new_model(|cx| {
+            DisplayMap::new(buffer.clone(), font("Helvetica"), font_size, None, 1, 1, cx)
+        });
         assert_eq!(
             map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
             DisplayPoint::new(1, 11)
@@ -1864,10 +1806,10 @@ pub mod tests {
 
     fn syntax_chunks<'a>(
         rows: Range<u32>,
-        map: &ModelHandle<DisplayMap>,
+        map: &Model<DisplayMap>,
         theme: &'a SyntaxTheme,
         cx: &mut AppContext,
-    ) -> Vec<(String, Option<Color>)> {
+    ) -> Vec<(String, Option<Hsla>)> {
         chunks(rows, map, theme, cx)
             .into_iter()
             .map(|(text, color, _)| (text, color))
@@ -1876,12 +1818,12 @@ pub mod tests {
 
     fn chunks<'a>(
         rows: Range<u32>,
-        map: &ModelHandle<DisplayMap>,
+        map: &Model<DisplayMap>,
         theme: &'a SyntaxTheme,
         cx: &mut AppContext,
-    ) -> Vec<(String, Option<Color>, Option<Color>)> {
+    ) -> Vec<(String, Option<Hsla>, Option<Hsla>)> {
         let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
-        let mut chunks: Vec<(String, Option<Color>, Option<Color>)> = Vec::new();
+        let mut chunks: Vec<(String, Option<Hsla>, Option<Hsla>)> = Vec::new();
         for chunk in snapshot.chunks(rows, true, None, None) {
             let syntax_color = chunk
                 .syntax_highlight_id
@@ -1899,13 +1841,13 @@ pub mod tests {
     }
 
     fn init_test(cx: &mut AppContext, f: impl Fn(&mut AllLanguageSettingsContent)) {
-        cx.foreground().forbid_parking();
-        cx.set_global(SettingsStore::test(cx));
+        let settings = SettingsStore::test(cx);
+        cx.set_global(settings);
         language::init(cx);
         crate::init(cx);
         Project::init_settings(cx);
-        theme::init((), cx);
-        cx.update_global::<SettingsStore, _, _>(|store, cx| {
+        theme::init(LoadThemes::JustBase, cx);
+        cx.update_global::<SettingsStore, _>(|store, cx| {
             store.update_user_settings::<AllLanguageSettings>(cx, f);
         });
     }

crates/editor/src/display_map/block_map.rs 🔗

@@ -2,9 +2,9 @@ use super::{
     wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot},
     Highlights,
 };
-use crate::{Anchor, Editor, ExcerptId, ExcerptRange, ToPoint as _};
+use crate::{Anchor, Editor, EditorStyle, ExcerptId, ExcerptRange, ToPoint as _};
 use collections::{Bound, HashMap, HashSet};
-use gpui::{AnyElement, ViewContext};
+use gpui::{AnyElement, Pixels, ViewContext};
 use language::{BufferSnapshot, Chunk, Patch, Point};
 use parking_lot::Mutex;
 use std::{
@@ -50,7 +50,7 @@ struct BlockRow(u32);
 #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
 struct WrapRow(u32);
 
-pub type RenderBlock = Arc<dyn Fn(&mut BlockContext) -> AnyElement<Editor>>;
+pub type RenderBlock = Arc<dyn Fn(&mut BlockContext) -> AnyElement>;
 
 pub struct Block {
     id: BlockId,
@@ -69,7 +69,7 @@ where
     pub position: P,
     pub height: u8,
     pub style: BlockStyle,
-    pub render: Arc<dyn Fn(&mut BlockContext) -> AnyElement<Editor>>,
+    pub render: Arc<dyn Fn(&mut BlockContext) -> AnyElement>,
     pub disposition: BlockDisposition,
 }
 
@@ -80,15 +80,15 @@ pub enum BlockStyle {
     Sticky,
 }
 
-pub struct BlockContext<'a, 'b, 'c> {
-    pub view_context: &'c mut ViewContext<'a, 'b, Editor>,
-    pub anchor_x: f32,
-    pub scroll_x: f32,
-    pub gutter_width: f32,
-    pub gutter_padding: f32,
-    pub em_width: f32,
-    pub line_height: f32,
+pub struct BlockContext<'a, 'b> {
+    pub view_context: &'b mut ViewContext<'a, Editor>,
+    pub anchor_x: Pixels,
+    pub gutter_width: Pixels,
+    pub gutter_padding: Pixels,
+    pub em_width: Pixels,
+    pub line_height: Pixels,
     pub block_id: usize,
+    pub editor_style: &'b EditorStyle,
 }
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
@@ -932,22 +932,22 @@ impl BlockDisposition {
     }
 }
 
-impl<'a, 'b, 'c> Deref for BlockContext<'a, 'b, 'c> {
-    type Target = ViewContext<'a, 'b, Editor>;
+impl<'a> Deref for BlockContext<'a, '_> {
+    type Target = ViewContext<'a, Editor>;
 
     fn deref(&self) -> &Self::Target {
         self.view_context
     }
 }
 
-impl DerefMut for BlockContext<'_, '_, '_> {
+impl DerefMut for BlockContext<'_, '_> {
     fn deref_mut(&mut self) -> &mut Self::Target {
         self.view_context
     }
 }
 
 impl Block {
-    pub fn render(&self, cx: &mut BlockContext) -> AnyElement<Editor> {
+    pub fn render(&self, cx: &mut BlockContext) -> AnyElement {
         self.render.lock()(cx)
     }
 
@@ -993,7 +993,7 @@ mod tests {
     use super::*;
     use crate::display_map::inlay_map::InlayMap;
     use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap};
-    use gpui::{elements::Empty, Element};
+    use gpui::{div, font, px, Element};
     use multi_buffer::MultiBuffer;
     use rand::prelude::*;
     use settings::SettingsStore;
@@ -1015,27 +1015,19 @@ mod tests {
     }
 
     #[gpui::test]
-    fn test_basic_blocks(cx: &mut gpui::AppContext) {
-        init_test(cx);
-
-        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();
+    fn test_basic_blocks(cx: &mut gpui::TestAppContext) {
+        cx.update(|cx| init_test(cx));
 
         let text = "aaa\nbbb\nccc\nddd";
 
-        let buffer = MultiBuffer::build_simple(text, cx);
-        let buffer_snapshot = buffer.read(cx).snapshot(cx);
+        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
+        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
         let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
         let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
         let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
         let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
-        let (wrap_map, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, 14.0, None, cx);
+        let (wrap_map, wraps_snapshot) =
+            cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
         let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
 
         let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
@@ -1045,21 +1037,21 @@ mod tests {
                 position: buffer_snapshot.anchor_after(Point::new(1, 0)),
                 height: 1,
                 disposition: BlockDisposition::Above,
-                render: Arc::new(|_| Empty::new().into_any_named("block 1")),
+                render: Arc::new(|_| div().into_any()),
             },
             BlockProperties {
                 style: BlockStyle::Fixed,
                 position: buffer_snapshot.anchor_after(Point::new(1, 2)),
                 height: 2,
                 disposition: BlockDisposition::Above,
-                render: Arc::new(|_| Empty::new().into_any_named("block 2")),
+                render: Arc::new(|_| div().into_any()),
             },
             BlockProperties {
                 style: BlockStyle::Fixed,
                 position: buffer_snapshot.anchor_after(Point::new(3, 3)),
                 height: 3,
                 disposition: BlockDisposition::Below,
-                render: Arc::new(|_| Empty::new().into_any_named("block 3")),
+                render: Arc::new(|_| div().into_any()),
             },
         ]);
 
@@ -1190,26 +1182,21 @@ mod tests {
     }
 
     #[gpui::test]
-    fn test_blocks_on_wrapped_lines(cx: &mut gpui::AppContext) {
-        init_test(cx);
-
-        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();
+    fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) {
+        cx.update(|cx| init_test(cx));
+
+        let _font_id = cx.text_system().font_id(&font("Helvetica")).unwrap();
 
         let text = "one two three\nfour five six\nseven eight";
 
-        let buffer = MultiBuffer::build_simple(text, cx);
-        let buffer_snapshot = buffer.read(cx).snapshot(cx);
+        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
+        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
         let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
         let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
         let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
-        let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, 14.0, Some(60.), cx);
+        let (_, wraps_snapshot) = cx.update(|cx| {
+            WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(60.)), cx)
+        });
         let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
 
         let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
@@ -1218,14 +1205,14 @@ mod tests {
                 style: BlockStyle::Fixed,
                 position: buffer_snapshot.anchor_after(Point::new(1, 12)),
                 disposition: BlockDisposition::Above,
-                render: Arc::new(|_| Empty::new().into_any_named("block 1")),
+                render: Arc::new(|_| div().into_any()),
                 height: 1,
             },
             BlockProperties {
                 style: BlockStyle::Fixed,
                 position: buffer_snapshot.anchor_after(Point::new(1, 1)),
                 disposition: BlockDisposition::Below,
-                render: Arc::new(|_| Empty::new().into_any_named("block 2")),
+                render: Arc::new(|_| div().into_any()),
                 height: 1,
             },
         ]);
@@ -1240,8 +1227,8 @@ mod tests {
     }
 
     #[gpui::test(iterations = 100)]
-    fn test_random_blocks(cx: &mut gpui::AppContext, mut rng: StdRng) {
-        init_test(cx);
+    fn test_random_blocks(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
+        cx.update(|cx| init_test(cx));
 
         let operations = env::var("OPERATIONS")
             .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
@@ -1250,18 +1237,10 @@ mod tests {
         let wrap_width = if rng.gen_bool(0.2) {
             None
         } else {
-            Some(rng.gen_range(0.0..=100.0))
+            Some(px(rng.gen_range(0.0..=100.0)))
         };
         let tab_size = 1.try_into().unwrap();
-        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_size = px(14.0);
         let buffer_start_header_height = rng.gen_range(1..=5);
         let excerpt_header_height = rng.gen_range(1..=5);
 
@@ -1272,17 +1251,17 @@ mod tests {
             let len = rng.gen_range(0..10);
             let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
             log::info!("initial buffer text: {:?}", text);
-            MultiBuffer::build_simple(&text, cx)
+            cx.update(|cx| MultiBuffer::build_simple(&text, cx))
         } else {
-            MultiBuffer::build_random(&mut rng, cx)
+            cx.update(|cx| MultiBuffer::build_random(&mut rng, cx))
         };
 
-        let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
+        let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
         let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
         let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
         let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
-        let (wrap_map, wraps_snapshot) =
-            WrapMap::new(tab_snapshot, font_id, font_size, wrap_width, cx);
+        let (wrap_map, wraps_snapshot) = cx
+            .update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), font_size, wrap_width, cx));
         let mut block_map = BlockMap::new(
             wraps_snapshot,
             buffer_start_header_height,
@@ -1297,7 +1276,7 @@ mod tests {
                     let wrap_width = if rng.gen_bool(0.2) {
                         None
                     } else {
-                        Some(rng.gen_range(0.0..=100.0))
+                        Some(px(rng.gen_range(0.0..=100.0)))
                     };
                     log::info!("Setting wrap width to {:?}", wrap_width);
                     wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
@@ -1306,7 +1285,7 @@ mod tests {
                     let block_count = rng.gen_range(1..=5);
                     let block_properties = (0..block_count)
                         .map(|_| {
-                            let buffer = buffer.read(cx).read(cx);
+                            let buffer = cx.update(|cx| buffer.read(cx).read(cx).clone());
                             let position = buffer.anchor_after(
                                 buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Left),
                             );
@@ -1328,7 +1307,7 @@ mod tests {
                                 position,
                                 height,
                                 disposition,
-                                render: Arc::new(|_| Empty::new().into_any()),
+                                render: Arc::new(|_| div().into_any()),
                             }
                         })
                         .collect::<Vec<_>>();
@@ -1646,8 +1625,9 @@ mod tests {
     }
 
     fn init_test(cx: &mut gpui::AppContext) {
-        cx.set_global(SettingsStore::test(cx));
-        theme::init((), cx);
+        let settings = SettingsStore::test(cx);
+        cx.set_global(settings);
+        theme::init(theme::LoadThemes::JustBase, cx);
     }
 
     impl TransformBlock {

crates/editor/src/display_map/fold_map.rs 🔗

@@ -3,15 +3,16 @@ use super::{
     Highlights,
 };
 use crate::{Anchor, AnchorRangeExt, MultiBufferSnapshot, ToOffset};
-use gpui::{color::Color, fonts::HighlightStyle};
+use gpui::{ElementId, HighlightStyle, Hsla};
 use language::{Chunk, Edit, Point, TextSummary};
 use std::{
     any::TypeId,
     cmp::{self, Ordering},
     iter,
-    ops::{Add, AddAssign, Range, Sub},
+    ops::{Add, AddAssign, Deref, DerefMut, Range, Sub},
 };
 use sum_tree::{Bias, Cursor, FilterCursor, SumTree};
+use util::post_inc;
 
 #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
 pub struct FoldPoint(pub Point);
@@ -90,12 +91,16 @@ impl<'a> FoldMapWriter<'a> {
             }
 
             // For now, ignore any ranges that span an excerpt boundary.
-            let fold = Fold(buffer.anchor_after(range.start)..buffer.anchor_before(range.end));
-            if fold.0.start.excerpt_id != fold.0.end.excerpt_id {
+            let fold_range =
+                FoldRange(buffer.anchor_after(range.start)..buffer.anchor_before(range.end));
+            if fold_range.0.start.excerpt_id != fold_range.0.end.excerpt_id {
                 continue;
             }
 
-            folds.push(fold);
+            folds.push(Fold {
+                id: FoldId(post_inc(&mut self.0.next_fold_id.0)),
+                range: fold_range,
+            });
 
             let inlay_range =
                 snapshot.to_inlay_offset(range.start)..snapshot.to_inlay_offset(range.end);
@@ -106,13 +111,13 @@ impl<'a> FoldMapWriter<'a> {
         }
 
         let buffer = &snapshot.buffer;
-        folds.sort_unstable_by(|a, b| sum_tree::SeekTarget::cmp(a, b, buffer));
+        folds.sort_unstable_by(|a, b| sum_tree::SeekTarget::cmp(&a.range, &b.range, buffer));
 
         self.0.snapshot.folds = {
             let mut new_tree = SumTree::new();
-            let mut cursor = self.0.snapshot.folds.cursor::<Fold>();
+            let mut cursor = self.0.snapshot.folds.cursor::<FoldRange>();
             for fold in folds {
-                new_tree.append(cursor.slice(&fold, Bias::Right, buffer), buffer);
+                new_tree.append(cursor.slice(&fold.range, Bias::Right, buffer), buffer);
                 new_tree.push(fold, buffer);
             }
             new_tree.append(cursor.suffix(buffer), buffer);
@@ -138,7 +143,8 @@ impl<'a> FoldMapWriter<'a> {
             let mut folds_cursor =
                 intersecting_folds(&snapshot, &self.0.snapshot.folds, range, inclusive);
             while let Some(fold) = folds_cursor.item() {
-                let offset_range = fold.0.start.to_offset(buffer)..fold.0.end.to_offset(buffer);
+                let offset_range =
+                    fold.range.start.to_offset(buffer)..fold.range.end.to_offset(buffer);
                 if offset_range.end > offset_range.start {
                     let inlay_range = snapshot.to_inlay_offset(offset_range.start)
                         ..snapshot.to_inlay_offset(offset_range.end);
@@ -174,7 +180,8 @@ impl<'a> FoldMapWriter<'a> {
 
 pub struct FoldMap {
     snapshot: FoldSnapshot,
-    ellipses_color: Option<Color>,
+    ellipses_color: Option<Hsla>,
+    next_fold_id: FoldId,
 }
 
 impl FoldMap {
@@ -197,6 +204,7 @@ impl FoldMap {
                 ellipses_color: None,
             },
             ellipses_color: None,
+            next_fold_id: FoldId::default(),
         };
         let snapshot = this.snapshot.clone();
         (this, snapshot)
@@ -221,7 +229,7 @@ impl FoldMap {
         (FoldMapWriter(self), snapshot, edits)
     }
 
-    pub fn set_ellipses_color(&mut self, color: Color) -> bool {
+    pub fn set_ellipses_color(&mut self, color: Hsla) -> bool {
         if self.ellipses_color != Some(color) {
             self.ellipses_color = Some(color);
             true
@@ -242,8 +250,8 @@ impl FoldMap {
             while let Some(fold) = folds.next() {
                 if let Some(next_fold) = folds.peek() {
                     let comparison = fold
-                        .0
-                        .cmp(&next_fold.0, &self.snapshot.inlay_snapshot.buffer);
+                        .range
+                        .cmp(&next_fold.range, &self.snapshot.inlay_snapshot.buffer);
                     assert!(comparison.is_le());
                 }
             }
@@ -304,9 +312,9 @@ impl FoldMap {
                 let anchor = inlay_snapshot
                     .buffer
                     .anchor_before(inlay_snapshot.to_buffer_offset(edit.new.start));
-                let mut folds_cursor = self.snapshot.folds.cursor::<Fold>();
+                let mut folds_cursor = self.snapshot.folds.cursor::<FoldRange>();
                 folds_cursor.seek(
-                    &Fold(anchor..Anchor::max()),
+                    &FoldRange(anchor..Anchor::max()),
                     Bias::Left,
                     &inlay_snapshot.buffer,
                 );
@@ -315,8 +323,8 @@ impl FoldMap {
                     let inlay_snapshot = &inlay_snapshot;
                     move || {
                         let item = folds_cursor.item().map(|f| {
-                            let buffer_start = f.0.start.to_offset(&inlay_snapshot.buffer);
-                            let buffer_end = f.0.end.to_offset(&inlay_snapshot.buffer);
+                            let buffer_start = f.range.start.to_offset(&inlay_snapshot.buffer);
+                            let buffer_end = f.range.end.to_offset(&inlay_snapshot.buffer);
                             inlay_snapshot.to_inlay_offset(buffer_start)
                                 ..inlay_snapshot.to_inlay_offset(buffer_end)
                         });
@@ -469,7 +477,7 @@ pub struct FoldSnapshot {
     folds: SumTree<Fold>,
     pub inlay_snapshot: InlaySnapshot,
     pub version: usize,
-    pub ellipses_color: Option<Color>,
+    pub ellipses_color: Option<Hsla>,
 }
 
 impl FoldSnapshot {
@@ -596,13 +604,13 @@ impl FoldSnapshot {
         self.transforms.summary().output.longest_row
     }
 
-    pub fn folds_in_range<T>(&self, range: Range<T>) -> impl Iterator<Item = &Range<Anchor>>
+    pub fn folds_in_range<T>(&self, range: Range<T>) -> impl Iterator<Item = &Fold>
     where
         T: ToOffset,
     {
         let mut folds = intersecting_folds(&self.inlay_snapshot, &self.folds, range, false);
         iter::from_fn(move || {
-            let item = folds.item().map(|f| &f.0);
+            let item = folds.item();
             folds.next(&self.inlay_snapshot.buffer);
             item
         })
@@ -830,10 +838,39 @@ impl sum_tree::Summary for TransformSummary {
     }
 }
 
-#[derive(Clone, Debug)]
-struct Fold(Range<Anchor>);
+#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
+pub struct FoldId(usize);
+
+impl Into<ElementId> for FoldId {
+    fn into(self) -> ElementId {
+        ElementId::Integer(self.0)
+    }
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct Fold {
+    pub id: FoldId,
+    pub range: FoldRange,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct FoldRange(Range<Anchor>);
+
+impl Deref for FoldRange {
+    type Target = Range<Anchor>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl DerefMut for FoldRange {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.0
+    }
+}
 
-impl Default for Fold {
+impl Default for FoldRange {
     fn default() -> Self {
         Self(Anchor::min()..Anchor::max())
     }
@@ -844,17 +881,17 @@ impl sum_tree::Item for Fold {
 
     fn summary(&self) -> Self::Summary {
         FoldSummary {
-            start: self.0.start.clone(),
-            end: self.0.end.clone(),
-            min_start: self.0.start.clone(),
-            max_end: self.0.end.clone(),
+            start: self.range.start.clone(),
+            end: self.range.end.clone(),
+            min_start: self.range.start.clone(),
+            max_end: self.range.end.clone(),
             count: 1,
         }
     }
 }
 
 #[derive(Clone, Debug)]
-struct FoldSummary {
+pub struct FoldSummary {
     start: Anchor,
     end: Anchor,
     min_start: Anchor,
@@ -900,14 +937,14 @@ impl sum_tree::Summary for FoldSummary {
     }
 }
 
-impl<'a> sum_tree::Dimension<'a, FoldSummary> for Fold {
+impl<'a> sum_tree::Dimension<'a, FoldSummary> for FoldRange {
     fn add_summary(&mut self, summary: &'a FoldSummary, _: &MultiBufferSnapshot) {
         self.0.start = summary.start.clone();
         self.0.end = summary.end.clone();
     }
 }
 
-impl<'a> sum_tree::SeekTarget<'a, FoldSummary, Fold> for Fold {
+impl<'a> sum_tree::SeekTarget<'a, FoldSummary, FoldRange> for FoldRange {
     fn cmp(&self, other: &Self, buffer: &MultiBufferSnapshot) -> Ordering {
         self.0.cmp(&other.0, buffer)
     }
@@ -959,7 +996,7 @@ pub struct FoldChunks<'a> {
     inlay_offset: InlayOffset,
     output_offset: usize,
     max_output_offset: usize,
-    ellipses_color: Option<Color>,
+    ellipses_color: Option<Hsla>,
 }
 
 impl<'a> Iterator for FoldChunks<'a> {
@@ -1321,7 +1358,10 @@ mod tests {
         let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
         let fold_ranges = snapshot
             .folds_in_range(Point::new(1, 0)..Point::new(1, 3))
-            .map(|fold| fold.start.to_point(&buffer_snapshot)..fold.end.to_point(&buffer_snapshot))
+            .map(|fold| {
+                fold.range.start.to_point(&buffer_snapshot)
+                    ..fold.range.end.to_point(&buffer_snapshot)
+            })
             .collect::<Vec<_>>();
         assert_eq!(
             fold_ranges,
@@ -1553,10 +1593,9 @@ mod tests {
                     .filter(|fold| {
                         let start = buffer_snapshot.anchor_before(start);
                         let end = buffer_snapshot.anchor_after(end);
-                        start.cmp(&fold.0.end, &buffer_snapshot) == Ordering::Less
-                            && end.cmp(&fold.0.start, &buffer_snapshot) == Ordering::Greater
+                        start.cmp(&fold.range.end, &buffer_snapshot) == Ordering::Less
+                            && end.cmp(&fold.range.start, &buffer_snapshot) == Ordering::Greater
                     })
-                    .map(|fold| fold.0)
                     .collect::<Vec<_>>();
 
                 assert_eq!(
@@ -1629,7 +1668,8 @@ mod tests {
     }
 
     fn init_test(cx: &mut gpui::AppContext) {
-        cx.set_global(SettingsStore::test(cx));
+        let store = SettingsStore::test(cx);
+        cx.set_global(store);
     }
 
     impl FoldMap {
@@ -1638,10 +1678,10 @@ mod tests {
             let buffer = &inlay_snapshot.buffer;
             let mut folds = self.snapshot.folds.items(buffer);
             // Ensure sorting doesn't change how folds get merged and displayed.
-            folds.sort_by(|a, b| a.0.cmp(&b.0, buffer));
+            folds.sort_by(|a, b| a.range.cmp(&b.range, buffer));
             let mut fold_ranges = folds
                 .iter()
-                .map(|fold| fold.0.start.to_offset(buffer)..fold.0.end.to_offset(buffer))
+                .map(|fold| fold.range.start.to_offset(buffer)..fold.range.end.to_offset(buffer))
                 .peekable();
 
             let mut merged_ranges = Vec::new();

crates/editor/src/display_map/inlay_map.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{Anchor, InlayId, MultiBufferSnapshot, ToOffset};
 use collections::{BTreeMap, BTreeSet};
-use gpui::fonts::HighlightStyle;
+use gpui::HighlightStyle;
 use language::{Chunk, Edit, Point, TextSummary};
 use multi_buffer::{MultiBufferChunks, MultiBufferRows};
 use std::{
@@ -1889,7 +1889,8 @@ mod tests {
     }
 
     fn init_test(cx: &mut AppContext) {
-        cx.set_global(SettingsStore::test(cx));
-        theme::init((), cx);
+        let store = SettingsStore::test(cx);
+        cx.set_global(store);
+        theme::init(theme::LoadThemes::JustBase, cx);
     }
 }

crates/editor/src/display_map/wrap_map.rs 🔗

@@ -4,15 +4,14 @@ use super::{
     Highlights,
 };
 use crate::MultiBufferSnapshot;
-use gpui::{
-    fonts::FontId, text_layout::LineWrapper, AppContext, Entity, ModelContext, ModelHandle, Task,
-};
+use gpui::{AppContext, Context, Font, LineWrapper, Model, ModelContext, Pixels, Task};
 use language::{Chunk, Point};
 use lazy_static::lazy_static;
 use smol::future::yield_now;
 use std::{cmp, collections::VecDeque, mem, ops::Range, time::Duration};
 use sum_tree::{Bias, Cursor, SumTree};
 use text::Patch;
+use util::ResultExt;
 
 pub use super::tab_map::TextSummary;
 pub type WrapEdit = text::Edit<u32>;
@@ -22,13 +21,9 @@ pub struct WrapMap {
     pending_edits: VecDeque<(TabSnapshot, Vec<TabEdit>)>,
     interpolated_edits: Patch<u32>,
     edits_since_sync: Patch<u32>,
-    wrap_width: Option<f32>,
+    wrap_width: Option<Pixels>,
     background_task: Option<Task<()>>,
-    font: (FontId, f32),
-}
-
-impl Entity for WrapMap {
-    type Event = ();
+    font_with_size: (Font, Pixels),
 }
 
 #[derive(Clone)]
@@ -74,14 +69,14 @@ pub struct WrapBufferRows<'a> {
 impl WrapMap {
     pub fn new(
         tab_snapshot: TabSnapshot,
-        font_id: FontId,
-        font_size: f32,
-        wrap_width: Option<f32>,
+        font: Font,
+        font_size: Pixels,
+        wrap_width: Option<Pixels>,
         cx: &mut AppContext,
-    ) -> (ModelHandle<Self>, WrapSnapshot) {
-        let handle = cx.add_model(|cx| {
+    ) -> (Model<Self>, WrapSnapshot) {
+        let handle = cx.new_model(|cx| {
             let mut this = Self {
-                font: (font_id, font_size),
+                font_with_size: (font, font_size),
                 wrap_width: None,
                 pending_edits: Default::default(),
                 interpolated_edits: Default::default(),
@@ -121,14 +116,16 @@ impl WrapMap {
         (self.snapshot.clone(), mem::take(&mut self.edits_since_sync))
     }
 
-    pub fn set_font(
+    pub fn set_font_with_size(
         &mut self,
-        font_id: FontId,
-        font_size: f32,
+        font: Font,
+        font_size: Pixels,
         cx: &mut ModelContext<Self>,
     ) -> bool {
-        if (font_id, font_size) != self.font {
-            self.font = (font_id, font_size);
+        let font_with_size = (font, font_size);
+
+        if font_with_size != self.font_with_size {
+            self.font_with_size = font_with_size;
             self.rewrap(cx);
             true
         } else {
@@ -136,7 +133,11 @@ impl WrapMap {
         }
     }
 
-    pub fn set_wrap_width(&mut self, wrap_width: Option<f32>, cx: &mut ModelContext<Self>) -> bool {
+    pub fn set_wrap_width(
+        &mut self,
+        wrap_width: Option<Pixels>,
+        cx: &mut ModelContext<Self>,
+    ) -> bool {
         if wrap_width == self.wrap_width {
             return false;
         }
@@ -153,34 +154,36 @@ impl WrapMap {
 
         if let Some(wrap_width) = self.wrap_width {
             let mut new_snapshot = self.snapshot.clone();
-            let font_cache = cx.font_cache().clone();
-            let (font_id, font_size) = self.font;
-            let task = cx.background().spawn(async move {
-                let mut line_wrapper = font_cache.line_wrapper(font_id, font_size);
-                let tab_snapshot = new_snapshot.tab_snapshot.clone();
-                let range = TabPoint::zero()..tab_snapshot.max_point();
-                let edits = new_snapshot
-                    .update(
-                        tab_snapshot,
-                        &[TabEdit {
-                            old: range.clone(),
-                            new: range.clone(),
-                        }],
-                        wrap_width,
-                        &mut line_wrapper,
-                    )
-                    .await;
+            let mut edits = Patch::default();
+            let text_system = cx.text_system().clone();
+            let (font, font_size) = self.font_with_size.clone();
+            let task = cx.background_executor().spawn(async move {
+                if let Some(mut line_wrapper) = text_system.line_wrapper(font, font_size).log_err()
+                {
+                    let tab_snapshot = new_snapshot.tab_snapshot.clone();
+                    let range = TabPoint::zero()..tab_snapshot.max_point();
+                    edits = new_snapshot
+                        .update(
+                            tab_snapshot,
+                            &[TabEdit {
+                                old: range.clone(),
+                                new: range.clone(),
+                            }],
+                            wrap_width,
+                            &mut line_wrapper,
+                        )
+                        .await;
+                }
                 (new_snapshot, edits)
             });
 
             match cx
-                .background()
+                .background_executor()
                 .block_with_timeout(Duration::from_millis(5), task)
             {
                 Ok((snapshot, edits)) => {
                     self.snapshot = snapshot;
                     self.edits_since_sync = self.edits_since_sync.compose(&edits);
-                    cx.notify();
                 }
                 Err(wrap_task) => {
                     self.background_task = Some(cx.spawn(|this, mut cx| async move {
@@ -194,7 +197,8 @@ impl WrapMap {
                             this.background_task = None;
                             this.flush_edits(cx);
                             cx.notify();
-                        });
+                        })
+                        .ok();
                     }));
                 }
             }
@@ -237,23 +241,25 @@ impl WrapMap {
             if self.background_task.is_none() {
                 let pending_edits = self.pending_edits.clone();
                 let mut snapshot = self.snapshot.clone();
-                let font_cache = cx.font_cache().clone();
-                let (font_id, font_size) = self.font;
-                let update_task = cx.background().spawn(async move {
-                    let mut line_wrapper = font_cache.line_wrapper(font_id, font_size);
-
+                let text_system = cx.text_system().clone();
+                let (font, font_size) = self.font_with_size.clone();
+                let update_task = cx.background_executor().spawn(async move {
                     let mut edits = Patch::default();
-                    for (tab_snapshot, tab_edits) in pending_edits {
-                        let wrap_edits = snapshot
-                            .update(tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper)
-                            .await;
-                        edits = edits.compose(&wrap_edits);
+                    if let Some(mut line_wrapper) =
+                        text_system.line_wrapper(font, font_size).log_err()
+                    {
+                        for (tab_snapshot, tab_edits) in pending_edits {
+                            let wrap_edits = snapshot
+                                .update(tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper)
+                                .await;
+                            edits = edits.compose(&wrap_edits);
+                        }
                     }
                     (snapshot, edits)
                 });
 
                 match cx
-                    .background()
+                    .background_executor()
                     .block_with_timeout(Duration::from_millis(1), update_task)
                 {
                     Ok((snapshot, output_edits)) => {
@@ -272,7 +278,8 @@ impl WrapMap {
                                 this.background_task = None;
                                 this.flush_edits(cx);
                                 cx.notify();
-                            });
+                            })
+                            .ok();
                         }));
                     }
                 }
@@ -385,7 +392,7 @@ impl WrapSnapshot {
         &mut self,
         new_tab_snapshot: TabSnapshot,
         tab_edits: &[TabEdit],
-        wrap_width: f32,
+        wrap_width: Pixels,
         line_wrapper: &mut LineWrapper,
     ) -> Patch<u32> {
         #[derive(Debug)]
@@ -1026,37 +1033,34 @@ mod tests {
         display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap},
         MultiBuffer,
     };
-    use gpui::test::observe;
+    use gpui::{font, px, test::observe};
     use rand::prelude::*;
     use settings::SettingsStore;
     use smol::stream::StreamExt;
     use std::{cmp, env, num::NonZeroU32};
     use text::Rope;
+    use theme::LoadThemes;
 
     #[gpui::test(iterations = 100)]
     async fn test_random_wraps(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
+        // todo!() this test is flaky
         init_test(cx);
 
-        cx.foreground().set_block_on_ticks(0..=50);
+        cx.background_executor.set_block_on_ticks(0..=50);
         let operations = env::var("OPERATIONS")
             .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
             .unwrap_or(10);
 
-        let font_cache = cx.font_cache().clone();
-        let font_system = cx.platform().fonts();
+        let text_system = cx.read(|cx| cx.text_system().clone());
         let mut wrap_width = if rng.gen_bool(0.1) {
             None
         } else {
-            Some(rng.gen_range(0.0..=1000.0))
+            Some(px(rng.gen_range(0.0..=1000.0)))
         };
         let tab_size = NonZeroU32::new(rng.gen_range(1..=4)).unwrap();
-        let family_id = font_cache
-            .load_family(&["Helvetica"], &Default::default())
-            .unwrap();
-        let font_id = font_cache
-            .select_font(family_id, &Default::default())
-            .unwrap();
-        let font_size = 14.0;
+        let font = font("Helvetica");
+        let _font_id = text_system.font_id(&font).unwrap();
+        let font_size = px(14.0);
 
         log::info!("Tab size: {}", tab_size);
         log::info!("Wrap width: {:?}", wrap_width);
@@ -1082,12 +1086,12 @@ mod tests {
         let tabs_snapshot = tab_map.set_max_expansion_column(32);
         log::info!("TabMap text: {:?}", tabs_snapshot.text());
 
-        let mut line_wrapper = LineWrapper::new(font_id, font_size, font_system);
+        let mut line_wrapper = text_system.line_wrapper(font.clone(), font_size).unwrap();
         let unwrapped_text = tabs_snapshot.text();
         let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper);
 
         let (wrap_map, _) =
-            cx.update(|cx| WrapMap::new(tabs_snapshot.clone(), font_id, font_size, wrap_width, cx));
+            cx.update(|cx| WrapMap::new(tabs_snapshot.clone(), font, font_size, wrap_width, cx));
         let mut notifications = observe(&wrap_map, cx);
 
         if wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
@@ -1118,7 +1122,7 @@ mod tests {
                     wrap_width = if rng.gen_bool(0.2) {
                         None
                     } else {
-                        Some(rng.gen_range(0.0..=1000.0))
+                        Some(px(rng.gen_range(0.0..=1000.0)))
                     };
                     log::info!("Setting wrap width to {:?}", wrap_width);
                     wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
@@ -1272,16 +1276,16 @@ mod tests {
     }
 
     fn init_test(cx: &mut gpui::TestAppContext) {
-        cx.foreground().forbid_parking();
         cx.update(|cx| {
-            cx.set_global(SettingsStore::test(cx));
-            theme::init((), cx);
+            let settings = SettingsStore::test(cx);
+            cx.set_global(settings);
+            theme::init(LoadThemes::JustBase, cx);
         });
     }
 
     fn wrap_text(
         unwrapped_text: &str,
-        wrap_width: Option<f32>,
+        wrap_width: Option<Pixels>,
         line_wrapper: &mut LineWrapper,
     ) -> String {
         if let Some(wrap_width) = wrap_width {

crates/editor/src/editor.rs 🔗

@@ -20,13 +20,12 @@ pub mod selections_collection;
 mod editor_tests;
 #[cfg(any(test, feature = "test-support"))]
 pub mod test;
-
 use ::git::diff::DiffHunk;
 use aho_corasick::AhoCorasick;
-use anyhow::{anyhow, Context, Result};
+use anyhow::{anyhow, Context as _, Result};
 use blink_manager::BlinkManager;
 use client::{Client, Collaborator, ParticipantIndex, TelemetrySettings};
-use clock::{Global, ReplicaId};
+use clock::ReplicaId;
 use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
 use convert_case::{Case, Casing};
 use copilot::Copilot;
@@ -38,19 +37,14 @@ pub use element::{
 };
 use futures::FutureExt;
 use fuzzy::{StringMatch, StringMatchCandidate};
+use git::diff_hunk_to_display;
 use gpui::{
-    actions,
-    color::Color,
-    elements::*,
-    executor,
-    fonts::{self, HighlightStyle, TextStyle},
-    geometry::vector::{vec2f, Vector2F},
-    impl_actions,
-    keymap_matcher::KeymapContext,
-    platform::{CursorStyle, MouseButton},
-    serde_json, AnyElement, AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem,
-    CursorRegion, Element, Entity, ModelHandle, MouseRegion, Subscription, Task, View, ViewContext,
-    ViewHandle, WeakViewHandle, WindowContext,
+    actions, div, impl_actions, point, prelude::*, px, relative, rems, size, uniform_list, Action,
+    AnyElement, AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Context,
+    DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight,
+    HighlightStyle, Hsla, InputHandler, InteractiveText, KeyContext, Model, MouseButton,
+    ParentElement, Pixels, Render, SharedString, Styled, StyledText, Subscription, Task, TextStyle,
+    UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WhiteSpace, WindowContext,
 };
 use highlight_matching_bracket::refresh_matching_bracket_highlights;
 use hover_popover::{hide_hover, HoverState};
@@ -61,16 +55,14 @@ pub use language::{char_kind, CharKind};
 use language::{
     language_settings::{self, all_language_settings, InlayHintSettings},
     markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel,
-    Completion, CursorShape, Diagnostic, DiagnosticSeverity, Documentation, File, IndentKind,
-    IndentSize, Language, LanguageRegistry, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point,
-    Selection, SelectionGoal, TransactionId,
-};
-use link_go_to_definition::{
-    hide_link_definition, show_link_definition, GoToDefinitionLink, InlayHighlight,
-    LinkGoToDefinitionState,
+    Completion, CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language,
+    LanguageRegistry, LanguageServerName, OffsetRangeExt, Point, Selection, SelectionGoal,
+    TransactionId,
 };
-use log::error;
-use lsp::LanguageServerId;
+
+use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState};
+use lsp::{DiagnosticSeverity, LanguageServerId};
+use mouse_context_menu::MouseContextMenu;
 use movement::TextLayoutDetails;
 use multi_buffer::ToOffsetUtf16;
 pub use multi_buffer::{
@@ -80,14 +72,14 @@ pub use multi_buffer::{
 use ordered_float::OrderedFloat;
 use parking_lot::RwLock;
 use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction};
-use rand::{seq::SliceRandom, thread_rng};
-use rpc::proto::{self, PeerId};
+use rand::prelude::*;
+use rpc::proto::{self, *};
 use scroll::{
     autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide,
 };
 use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
 use serde::{Deserialize, Serialize};
-use settings::SettingsStore;
+use settings::{Settings, SettingsStore};
 use smallvec::SmallVec;
 use snippet::Snippet;
 use std::{
@@ -99,16 +91,19 @@ use std::{
     ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
     path::Path,
     sync::Arc,
+    sync::Weak,
     time::{Duration, Instant},
 };
 pub use sum_tree::Bias;
 use sum_tree::TreeMap;
-use text::Rope;
-use theme::{DiagnosticStyle, Theme, ThemeSettings};
+use text::{OffsetUtf16, Rope};
+use theme::{ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings};
+use ui::{
+    h_stack, ButtonSize, ButtonStyle, Icon, IconButton, ListItem, ListItemSpacing, Popover, Tooltip,
+};
+use ui::{prelude::*, IconSize};
 use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
-use workspace::{ItemNavHistory, SplitDirection, ViewId, Workspace};
-
-use crate::git::diff_hunk_to_display;
+use workspace::{searchable::SearchEvent, ItemNavHistory, Pane, SplitDirection, ViewId, Workspace};
 
 const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
 const MAX_LINE_LEN: usize = 1024;
@@ -120,147 +115,163 @@ pub const DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis
 
 pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
 
-pub fn render_parsed_markdown<Tag: 'static>(
+pub fn render_parsed_markdown(
+    element_id: impl Into<ElementId>,
     parsed: &language::ParsedMarkdown,
     editor_style: &EditorStyle,
-    workspace: Option<WeakViewHandle<Workspace>>,
+    workspace: Option<WeakView<Workspace>>,
     cx: &mut ViewContext<Editor>,
-) -> Text {
-    enum RenderedMarkdown {}
-
-    let parsed = parsed.clone();
-    let view_id = cx.view_id();
-    let code_span_background_color = editor_style.document_highlight_read_background;
-
-    let mut region_id = 0;
-
-    Text::new(parsed.text, editor_style.text.clone())
-        .with_highlights(
-            parsed
-                .highlights
-                .iter()
-                .filter_map(|(range, highlight)| {
-                    let highlight = highlight.to_highlight_style(&editor_style.syntax)?;
-                    Some((range.clone(), highlight))
-                })
-                .collect::<Vec<_>>(),
-        )
-        .with_custom_runs(parsed.region_ranges, move |ix, bounds, cx| {
-            region_id += 1;
-            let region = parsed.regions[ix].clone();
-
-            if let Some(link) = region.link {
-                cx.scene().push_cursor_region(CursorRegion {
-                    bounds,
-                    style: CursorStyle::PointingHand,
-                });
-                cx.scene().push_mouse_region(
-                    MouseRegion::new::<(RenderedMarkdown, Tag)>(view_id, region_id, bounds)
-                        .on_down::<Editor, _>(MouseButton::Left, move |_, _, cx| match &link {
-                            markdown::Link::Web { url } => cx.platform().open_url(url),
-                            markdown::Link::Path { path } => {
-                                if let Some(workspace) = &workspace {
-                                    _ = workspace.update(cx, |workspace, cx| {
-                                        workspace.open_abs_path(path.clone(), false, cx).detach();
-                                    });
-                                }
-                            }
-                        }),
-                );
-            }
-
-            if region.code {
-                cx.scene().push_quad(gpui::Quad {
-                    bounds,
-                    background: Some(code_span_background_color),
-                    border: Default::default(),
-                    corner_radii: (2.0).into(),
-                });
+) -> InteractiveText {
+    let code_span_background_color = cx
+        .theme()
+        .colors()
+        .editor_document_highlight_read_background;
+
+    let highlights = gpui::combine_highlights(
+        parsed.highlights.iter().filter_map(|(range, highlight)| {
+            let highlight = highlight.to_highlight_style(&editor_style.syntax)?;
+            Some((range.clone(), highlight))
+        }),
+        parsed
+            .regions
+            .iter()
+            .zip(&parsed.region_ranges)
+            .filter_map(|(region, range)| {
+                if region.code {
+                    Some((
+                        range.clone(),
+                        HighlightStyle {
+                            background_color: Some(code_span_background_color),
+                            ..Default::default()
+                        },
+                    ))
+                } else {
+                    None
+                }
+            }),
+    );
+
+    let mut links = Vec::new();
+    let mut link_ranges = Vec::new();
+    for (range, region) in parsed.region_ranges.iter().zip(&parsed.regions) {
+        if let Some(link) = region.link.clone() {
+            links.push(link);
+            link_ranges.push(range.clone());
+        }
+    }
+
+    InteractiveText::new(
+        element_id,
+        StyledText::new(parsed.text.clone()).with_highlights(&editor_style.text, highlights),
+    )
+    .on_click(link_ranges, move |clicked_range_ix, cx| {
+        match &links[clicked_range_ix] {
+            markdown::Link::Web { url } => cx.open_url(url),
+            markdown::Link::Path { path } => {
+                if let Some(workspace) = &workspace {
+                    _ = workspace.update(cx, |workspace, cx| {
+                        workspace.open_abs_path(path.clone(), false, cx).detach();
+                    });
+                }
             }
-        })
-        .with_soft_wrap(true)
+        }
+    })
 }
 
-#[derive(Clone, Deserialize, PartialEq, Default)]
+#[derive(PartialEq, Clone, Deserialize, Default)]
 pub struct SelectNext {
     #[serde(default)]
     pub replace_newest: bool,
 }
 
-#[derive(Clone, Deserialize, PartialEq, Default)]
+#[derive(PartialEq, Clone, Deserialize, Default)]
 pub struct SelectPrevious {
     #[serde(default)]
     pub replace_newest: bool,
 }
 
-#[derive(Clone, Deserialize, PartialEq, Default)]
+#[derive(PartialEq, Clone, Deserialize, Default)]
 pub struct SelectAllMatches {
     #[serde(default)]
     pub replace_newest: bool,
 }
 
-#[derive(Clone, Deserialize, PartialEq)]
+#[derive(PartialEq, Clone, Deserialize, Default)]
 pub struct SelectToBeginningOfLine {
     #[serde(default)]
     stop_at_soft_wraps: bool,
 }
 
-#[derive(Clone, Default, Deserialize, PartialEq)]
+#[derive(PartialEq, Clone, Deserialize, Default)]
 pub struct MovePageUp {
     #[serde(default)]
     center_cursor: bool,
 }
 
-#[derive(Clone, Default, Deserialize, PartialEq)]
+#[derive(PartialEq, Clone, Deserialize, Default)]
 pub struct MovePageDown {
     #[serde(default)]
     center_cursor: bool,
 }
 
-#[derive(Clone, Deserialize, PartialEq)]
+#[derive(PartialEq, Clone, Deserialize, Default)]
 pub struct SelectToEndOfLine {
     #[serde(default)]
     stop_at_soft_wraps: bool,
 }
 
-#[derive(Clone, Deserialize, PartialEq)]
+#[derive(PartialEq, Clone, Deserialize, Default)]
 pub struct ToggleCodeActions {
     #[serde(default)]
     pub deployed_from_indicator: bool,
 }
 
-#[derive(Clone, Default, Deserialize, PartialEq)]
+#[derive(PartialEq, Clone, Deserialize, Default)]
 pub struct ConfirmCompletion {
     #[serde(default)]
     pub item_ix: Option<usize>,
 }
 
-#[derive(Clone, Default, Deserialize, PartialEq)]
+#[derive(PartialEq, Clone, Deserialize, Default)]
 pub struct ConfirmCodeAction {
     #[serde(default)]
     pub item_ix: Option<usize>,
 }
 
-#[derive(Clone, Default, Deserialize, PartialEq)]
+#[derive(PartialEq, Clone, Deserialize, Default)]
 pub struct ToggleComments {
     #[serde(default)]
     pub advance_downwards: bool,
 }
 
-#[derive(Clone, Default, Deserialize, PartialEq)]
+#[derive(PartialEq, Clone, Deserialize, Default)]
 pub struct FoldAt {
     pub buffer_row: u32,
 }
 
-#[derive(Clone, Default, Deserialize, PartialEq)]
+#[derive(PartialEq, Clone, Deserialize, Default)]
 pub struct UnfoldAt {
     pub buffer_row: u32,
 }
 
-#[derive(Clone, Default, Deserialize, PartialEq)]
-pub struct GutterHover {
-    pub hovered: bool,
-}
+impl_actions!(
+    editor,
+    [
+        SelectNext,
+        SelectPrevious,
+        SelectAllMatches,
+        SelectToBeginningOfLine,
+        MovePageUp,
+        MovePageDown,
+        SelectToEndOfLine,
+        ToggleCodeActions,
+        ConfirmCompletion,
+        ConfirmCodeAction,
+        ToggleComments,
+        FoldAt,
+        UnfoldAt
+    ]
+);
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub enum InlayId {
@@ -280,134 +291,122 @@ impl InlayId {
 actions!(
     editor,
     [
-        Cancel,
+        AddSelectionAbove,
+        AddSelectionBelow,
         Backspace,
+        Cancel,
+        ConfirmRename,
+        ContextMenuFirst,
+        ContextMenuLast,
+        ContextMenuNext,
+        ContextMenuPrev,
+        ConvertToKebabCase,
+        ConvertToLowerCamelCase,
+        ConvertToLowerCase,
+        ConvertToSnakeCase,
+        ConvertToTitleCase,
+        ConvertToUpperCamelCase,
+        ConvertToUpperCase,
+        Copy,
+        CopyHighlightJson,
+        CopyPath,
+        CopyRelativePath,
+        Cut,
+        CutToEndOfLine,
         Delete,
-        Newline,
-        NewlineAbove,
-        NewlineBelow,
-        GoToDiagnostic,
-        GoToPrevDiagnostic,
-        GoToHunk,
-        GoToPrevHunk,
-        Indent,
-        Outdent,
         DeleteLine,
-        DeleteToPreviousWordStart,
-        DeleteToPreviousSubwordStart,
-        DeleteToNextWordEnd,
-        DeleteToNextSubwordEnd,
         DeleteToBeginningOfLine,
         DeleteToEndOfLine,
-        CutToEndOfLine,
+        DeleteToNextSubwordEnd,
+        DeleteToNextWordEnd,
+        DeleteToPreviousSubwordStart,
+        DeleteToPreviousWordStart,
         DuplicateLine,
         ExpandMacroRecursively,
-        MoveLineUp,
-        MoveLineDown,
+        FindAllReferences,
+        Fold,
+        FoldSelectedRanges,
+        Format,
+        GoToDefinition,
+        GoToDefinitionSplit,
+        GoToDiagnostic,
+        GoToHunk,
+        GoToPrevDiagnostic,
+        GoToPrevHunk,
+        GoToTypeDefinition,
+        GoToTypeDefinitionSplit,
+        HalfPageDown,
+        HalfPageUp,
+        Hover,
+        Indent,
         JoinLines,
-        SortLinesCaseSensitive,
-        SortLinesCaseInsensitive,
-        ReverseLines,
-        ShuffleLines,
-        ConvertToUpperCase,
-        ConvertToLowerCase,
-        ConvertToTitleCase,
-        ConvertToSnakeCase,
-        ConvertToKebabCase,
-        ConvertToUpperCamelCase,
-        ConvertToLowerCamelCase,
-        Transpose,
-        Cut,
-        Copy,
-        Paste,
-        Undo,
-        Redo,
-        MoveUp,
-        PageUp,
+        LineDown,
+        LineUp,
         MoveDown,
-        PageDown,
         MoveLeft,
+        MoveLineDown,
+        MoveLineUp,
         MoveRight,
-        MoveToPreviousWordStart,
-        MoveToPreviousSubwordStart,
-        MoveToNextWordEnd,
-        MoveToNextSubwordEnd,
+        MoveToBeginning,
         MoveToBeginningOfLine,
+        MoveToEnclosingBracket,
+        MoveToEnd,
         MoveToEndOfLine,
-        MoveToStartOfParagraph,
         MoveToEndOfParagraph,
-        MoveToBeginning,
-        MoveToEnd,
-        SelectUp,
+        MoveToNextSubwordEnd,
+        MoveToNextWordEnd,
+        MoveToPreviousSubwordStart,
+        MoveToPreviousWordStart,
+        MoveToStartOfParagraph,
+        MoveUp,
+        Newline,
+        NewlineAbove,
+        NewlineBelow,
+        NextScreen,
+        OpenExcerpts,
+        Outdent,
+        PageDown,
+        PageUp,
+        Paste,
+        Redo,
+        RedoSelection,
+        Rename,
+        RestartLanguageServer,
+        RevealInFinder,
+        ReverseLines,
+        ScrollCursorBottom,
+        ScrollCursorCenter,
+        ScrollCursorTop,
+        SelectAll,
         SelectDown,
+        SelectLargerSyntaxNode,
         SelectLeft,
+        SelectLine,
         SelectRight,
-        SelectToPreviousWordStart,
-        SelectToPreviousSubwordStart,
-        SelectToNextWordEnd,
-        SelectToNextSubwordEnd,
-        SelectToStartOfParagraph,
-        SelectToEndOfParagraph,
+        SelectSmallerSyntaxNode,
         SelectToBeginning,
         SelectToEnd,
-        SelectAll,
-        SelectLine,
+        SelectToEndOfParagraph,
+        SelectToNextSubwordEnd,
+        SelectToNextWordEnd,
+        SelectToPreviousSubwordStart,
+        SelectToPreviousWordStart,
+        SelectToStartOfParagraph,
+        SelectUp,
+        ShowCharacterPalette,
+        ShowCompletions,
+        ShuffleLines,
+        SortLinesCaseInsensitive,
+        SortLinesCaseSensitive,
         SplitSelectionIntoLines,
-        AddSelectionAbove,
-        AddSelectionBelow,
         Tab,
         TabPrev,
-        ShowCharacterPalette,
-        SelectLargerSyntaxNode,
-        SelectSmallerSyntaxNode,
-        GoToDefinition,
-        GoToDefinitionSplit,
-        GoToTypeDefinition,
-        GoToTypeDefinitionSplit,
-        MoveToEnclosingBracket,
+        ToggleInlayHints,
+        ToggleSoftWrap,
+        Transpose,
+        Undo,
         UndoSelection,
-        RedoSelection,
-        FindAllReferences,
-        Rename,
-        ConfirmRename,
-        Fold,
         UnfoldLines,
-        FoldSelectedRanges,
-        ShowCompletions,
-        OpenExcerpts,
-        RestartLanguageServer,
-        Hover,
-        Format,
-        ToggleSoftWrap,
-        ToggleInlayHints,
-        RevealInFinder,
-        CopyPath,
-        CopyRelativePath,
-        CopyHighlightJson,
-        ContextMenuFirst,
-        ContextMenuPrev,
-        ContextMenuNext,
-        ContextMenuLast,
-    ]
-);
-
-impl_actions!(
-    editor,
-    [
-        SelectNext,
-        SelectPrevious,
-        SelectAllMatches,
-        SelectToBeginningOfLine,
-        SelectToEndOfLine,
-        ToggleCodeActions,
-        MovePageUp,
-        MovePageDown,
-        ConfirmCompletion,
-        ConfirmCodeAction,
-        ToggleComments,
-        FoldAt,
-        UnfoldAt,
-        GutterHover
     ]
 );
 
@@ -422,144 +421,41 @@ pub enum Direction {
 }
 
 pub fn init_settings(cx: &mut AppContext) {
-    settings::register::<EditorSettings>(cx);
+    EditorSettings::register(cx);
 }
 
 pub fn init(cx: &mut AppContext) {
     init_settings(cx);
 
-    rust_analyzer_ext::apply_related_actions(cx);
-    cx.add_action(Editor::new_file);
-    cx.add_action(Editor::new_file_in_direction);
-    cx.add_action(Editor::cancel);
-    cx.add_action(Editor::newline);
-    cx.add_action(Editor::newline_above);
-    cx.add_action(Editor::newline_below);
-    cx.add_action(Editor::backspace);
-    cx.add_action(Editor::delete);
-    cx.add_action(Editor::tab);
-    cx.add_action(Editor::tab_prev);
-    cx.add_action(Editor::indent);
-    cx.add_action(Editor::outdent);
-    cx.add_action(Editor::delete_line);
-    cx.add_action(Editor::join_lines);
-    cx.add_action(Editor::sort_lines_case_sensitive);
-    cx.add_action(Editor::sort_lines_case_insensitive);
-    cx.add_action(Editor::reverse_lines);
-    cx.add_action(Editor::shuffle_lines);
-    cx.add_action(Editor::convert_to_upper_case);
-    cx.add_action(Editor::convert_to_lower_case);
-    cx.add_action(Editor::convert_to_title_case);
-    cx.add_action(Editor::convert_to_snake_case);
-    cx.add_action(Editor::convert_to_kebab_case);
-    cx.add_action(Editor::convert_to_upper_camel_case);
-    cx.add_action(Editor::convert_to_lower_camel_case);
-    cx.add_action(Editor::delete_to_previous_word_start);
-    cx.add_action(Editor::delete_to_previous_subword_start);
-    cx.add_action(Editor::delete_to_next_word_end);
-    cx.add_action(Editor::delete_to_next_subword_end);
-    cx.add_action(Editor::delete_to_beginning_of_line);
-    cx.add_action(Editor::delete_to_end_of_line);
-    cx.add_action(Editor::cut_to_end_of_line);
-    cx.add_action(Editor::duplicate_line);
-    cx.add_action(Editor::move_line_up);
-    cx.add_action(Editor::move_line_down);
-    cx.add_action(Editor::transpose);
-    cx.add_action(Editor::cut);
-    cx.add_action(Editor::copy);
-    cx.add_action(Editor::paste);
-    cx.add_action(Editor::undo);
-    cx.add_action(Editor::redo);
-    cx.add_action(Editor::move_up);
-    cx.add_action(Editor::move_page_up);
-    cx.add_action(Editor::move_down);
-    cx.add_action(Editor::move_page_down);
-    cx.add_action(Editor::next_screen);
-    cx.add_action(Editor::move_left);
-    cx.add_action(Editor::move_right);
-    cx.add_action(Editor::move_to_previous_word_start);
-    cx.add_action(Editor::move_to_previous_subword_start);
-    cx.add_action(Editor::move_to_next_word_end);
-    cx.add_action(Editor::move_to_next_subword_end);
-    cx.add_action(Editor::move_to_beginning_of_line);
-    cx.add_action(Editor::move_to_end_of_line);
-    cx.add_action(Editor::move_to_start_of_paragraph);
-    cx.add_action(Editor::move_to_end_of_paragraph);
-    cx.add_action(Editor::move_to_beginning);
-    cx.add_action(Editor::move_to_end);
-    cx.add_action(Editor::select_up);
-    cx.add_action(Editor::select_down);
-    cx.add_action(Editor::select_left);
-    cx.add_action(Editor::select_right);
-    cx.add_action(Editor::select_to_previous_word_start);
-    cx.add_action(Editor::select_to_previous_subword_start);
-    cx.add_action(Editor::select_to_next_word_end);
-    cx.add_action(Editor::select_to_next_subword_end);
-    cx.add_action(Editor::select_to_beginning_of_line);
-    cx.add_action(Editor::select_to_end_of_line);
-    cx.add_action(Editor::select_to_start_of_paragraph);
-    cx.add_action(Editor::select_to_end_of_paragraph);
-    cx.add_action(Editor::select_to_beginning);
-    cx.add_action(Editor::select_to_end);
-    cx.add_action(Editor::select_all);
-    cx.add_action(Editor::select_all_matches);
-    cx.add_action(Editor::select_line);
-    cx.add_action(Editor::split_selection_into_lines);
-    cx.add_action(Editor::add_selection_above);
-    cx.add_action(Editor::add_selection_below);
-    cx.add_action(Editor::select_next);
-    cx.add_action(Editor::select_previous);
-    cx.add_action(Editor::toggle_comments);
-    cx.add_action(Editor::select_larger_syntax_node);
-    cx.add_action(Editor::select_smaller_syntax_node);
-    cx.add_action(Editor::move_to_enclosing_bracket);
-    cx.add_action(Editor::undo_selection);
-    cx.add_action(Editor::redo_selection);
-    cx.add_action(Editor::go_to_diagnostic);
-    cx.add_action(Editor::go_to_prev_diagnostic);
-    cx.add_action(Editor::go_to_hunk);
-    cx.add_action(Editor::go_to_prev_hunk);
-    cx.add_action(Editor::go_to_definition);
-    cx.add_action(Editor::go_to_definition_split);
-    cx.add_action(Editor::go_to_type_definition);
-    cx.add_action(Editor::go_to_type_definition_split);
-    cx.add_action(Editor::fold);
-    cx.add_action(Editor::fold_at);
-    cx.add_action(Editor::unfold_lines);
-    cx.add_action(Editor::unfold_at);
-    cx.add_action(Editor::gutter_hover);
-    cx.add_action(Editor::fold_selected_ranges);
-    cx.add_action(Editor::show_completions);
-    cx.add_action(Editor::toggle_code_actions);
-    cx.add_action(Editor::open_excerpts);
-    cx.add_action(Editor::toggle_soft_wrap);
-    cx.add_action(Editor::toggle_inlay_hints);
-    cx.add_action(Editor::reveal_in_finder);
-    cx.add_action(Editor::copy_path);
-    cx.add_action(Editor::copy_relative_path);
-    cx.add_action(Editor::copy_highlight_json);
-    cx.add_async_action(Editor::format);
-    cx.add_action(Editor::restart_language_server);
-    cx.add_action(Editor::show_character_palette);
-    cx.add_async_action(Editor::confirm_completion);
-    cx.add_async_action(Editor::confirm_code_action);
-    cx.add_async_action(Editor::rename);
-    cx.add_async_action(Editor::confirm_rename);
-    cx.add_async_action(Editor::find_all_references);
-    cx.add_action(Editor::next_copilot_suggestion);
-    cx.add_action(Editor::previous_copilot_suggestion);
-    cx.add_action(Editor::copilot_suggest);
-    cx.add_action(Editor::context_menu_first);
-    cx.add_action(Editor::context_menu_prev);
-    cx.add_action(Editor::context_menu_next);
-    cx.add_action(Editor::context_menu_last);
-
-    hover_popover::init(cx);
-    scroll::actions::init(cx);
-
     workspace::register_project_item::<Editor>(cx);
     workspace::register_followable_item::<Editor>(cx);
     workspace::register_deserializable_item::<Editor>(cx);
+    cx.observe_new_views(
+        |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
+            workspace.register_action(Editor::new_file);
+            workspace.register_action(Editor::new_file_in_direction);
+        },
+    )
+    .detach();
+
+    cx.on_action(move |_: &workspace::NewFile, cx| {
+        let app_state = cx.global::<Weak<workspace::AppState>>();
+        if let Some(app_state) = app_state.upgrade() {
+            workspace::open_new(&app_state, cx, |workspace, cx| {
+                Editor::new_file(workspace, &Default::default(), cx)
+            })
+            .detach();
+        }
+    });
+    cx.on_action(move |_: &workspace::NewWindow, cx| {
+        let app_state = cx.global::<Weak<workspace::AppState>>();
+        if let Some(app_state) = app_state.upgrade() {
+            workspace::open_new(&app_state, cx, |workspace, cx| {
+                Editor::new_file(workspace, &Default::default(), cx)
+            })
+            .detach();
+        }
+    });
 }
 
 trait InvalidationRegion {
@@ -584,7 +480,7 @@ pub enum SelectPhase {
     Update {
         position: DisplayPoint,
         goal_column: u32,
-        scroll_position: Vector2F,
+        scroll_position: gpui::Point<f32>,
     },
     End,
 }
@@ -611,27 +507,31 @@ pub enum SoftWrap {
     Column(u32),
 }
 
-#[derive(Clone)]
+#[derive(Clone, Default)]
 pub struct EditorStyle {
+    pub background: Hsla,
+    pub local_player: PlayerColor,
     pub text: TextStyle,
-    pub line_height_scalar: f32,
-    pub placeholder_text: Option<TextStyle>,
-    pub theme: theme::Editor,
-    pub theme_id: usize,
+    pub scrollbar_width: Pixels,
+    pub syntax: Arc<SyntaxTheme>,
+    pub status: StatusColors,
+    pub inlays_style: HighlightStyle,
+    pub suggestions_style: HighlightStyle,
 }
 
 type CompletionId = usize;
 
-type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
-type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
+// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
+// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
 
-type BackgroundHighlight = (fn(&Theme) -> Color, Vec<Range<Anchor>>);
-type InlayBackgroundHighlight = (fn(&Theme) -> Color, Vec<InlayHighlight>);
+type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Vec<Range<Anchor>>);
+type InlayBackgroundHighlight = (fn(&ThemeColors) -> Hsla, Vec<InlayHighlight>);
 
 pub struct Editor {
-    handle: WeakViewHandle<Self>,
-    buffer: ModelHandle<MultiBuffer>,
-    display_map: ModelHandle<DisplayMap>,
+    handle: WeakView<Self>,
+    focus_handle: FocusHandle,
+    buffer: Model<MultiBuffer>,
+    display_map: Model<DisplayMap>,
     pub selections: SelectionsCollection,
     pub scroll_manager: ScrollManager,
     columnar_selection_tail: Option<Anchor>,
@@ -645,12 +545,9 @@ pub struct Editor {
     ime_transaction: Option<TransactionId>,
     active_diagnostics: Option<ActiveDiagnosticGroup>,
     soft_wrap_mode_override: Option<language_settings::SoftWrap>,
-    get_field_editor_theme: Option<Arc<GetFieldEditorTheme>>,
-    override_text_style: Option<Box<OverrideTextStyle>>,
-    project: Option<ModelHandle<Project>>,
+    project: Option<Model<Project>>,
     collaboration_hub: Option<Box<dyn CollaborationHub>>,
-    focused: bool,
-    blink_manager: ModelHandle<BlinkManager>,
+    blink_manager: Model<BlinkManager>,
     pub show_local_selections: bool,
     mode: EditorMode,
     show_gutter: bool,
@@ -661,10 +558,10 @@ pub struct Editor {
     inlay_background_highlights: TreeMap<Option<TypeId>, InlayBackgroundHighlight>,
     nav_history: Option<ItemNavHistory>,
     context_menu: RwLock<Option<ContextMenu>>,
-    mouse_context_menu: ViewHandle<context_menu::ContextMenu>,
+    mouse_context_menu: Option<MouseContextMenu>,
     completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
     next_completion_id: CompletionId,
-    available_code_actions: Option<(ModelHandle<Buffer>, Arc<[CodeAction]>)>,
+    available_code_actions: Option<(Model<Buffer>, Arc<[CodeAction]>)>,
     code_actions_task: Option<Task<()>>,
     document_highlights_task: Option<Task<()>>,
     pending_rename: Option<RenameState>,
@@ -672,8 +569,8 @@ pub struct Editor {
     cursor_shape: CursorShape,
     collapse_matches: bool,
     autoindent_mode: Option<AutoindentMode>,
-    workspace: Option<(WeakViewHandle<Workspace>, i64)>,
-    keymap_context_layers: BTreeMap<TypeId, KeymapContext>,
+    workspace: Option<(WeakView<Workspace>, i64)>,
+    keymap_context_layers: BTreeMap<TypeId, KeyContext>,
     input_enabled: bool,
     read_only: bool,
     leader_peer_id: Option<PeerId>,
@@ -685,7 +582,10 @@ pub struct Editor {
     inlay_hint_cache: InlayHintCache,
     next_inlay_id: usize,
     _subscriptions: Vec<Subscription>,
-    pixel_position_of_newest_cursor: Option<Vector2F>,
+    pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
+    gutter_width: Pixels,
+    style: Option<EditorStyle>,
+    editor_actions: Vec<Box<dyn Fn(&mut ViewContext<Self>)>>,
 }
 
 pub struct EditorSnapshot {
@@ -841,7 +741,7 @@ struct SnippetState {
 pub struct RenameState {
     pub range: Range<Anchor>,
     pub old_name: Arc<str>,
-    pub editor: ViewHandle<Editor>,
+    pub editor: View<Editor>,
     block_id: BlockId,
 }
 
@@ -855,7 +755,7 @@ enum ContextMenu {
 impl ContextMenu {
     fn select_first(
         &mut self,
-        project: Option<&ModelHandle<Project>>,
+        project: Option<&Model<Project>>,
         cx: &mut ViewContext<Editor>,
     ) -> bool {
         if self.visible() {
@@ -871,7 +771,7 @@ impl ContextMenu {
 
     fn select_prev(
         &mut self,
-        project: Option<&ModelHandle<Project>>,
+        project: Option<&Model<Project>>,
         cx: &mut ViewContext<Editor>,
     ) -> bool {
         if self.visible() {
@@ -887,7 +787,7 @@ impl ContextMenu {
 
     fn select_next(
         &mut self,
-        project: Option<&ModelHandle<Project>>,
+        project: Option<&Model<Project>>,
         cx: &mut ViewContext<Editor>,
     ) -> bool {
         if self.visible() {
@@ -903,7 +803,7 @@ impl ContextMenu {
 
     fn select_last(
         &mut self,
-        project: Option<&ModelHandle<Project>>,
+        project: Option<&Model<Project>>,
         cx: &mut ViewContext<Editor>,
     ) -> bool {
         if self.visible() {
@@ -927,13 +827,17 @@ impl ContextMenu {
     fn render(
         &self,
         cursor_position: DisplayPoint,
-        style: EditorStyle,
-        workspace: Option<WeakViewHandle<Workspace>>,
+        style: &EditorStyle,
+        max_height: Pixels,
+        workspace: Option<WeakView<Workspace>>,
         cx: &mut ViewContext<Editor>,
-    ) -> (DisplayPoint, AnyElement<Editor>) {
+    ) -> (DisplayPoint, AnyElement) {
         match self {
-            ContextMenu::Completions(menu) => (cursor_position, menu.render(style, workspace, cx)),
-            ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx),
+            ContextMenu::Completions(menu) => (
+                cursor_position,
+                menu.render(style, max_height, workspace, cx),
+            ),
+            ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, max_height, cx),
         }
     }
 }
@@ -942,63 +846,47 @@ impl ContextMenu {
 struct CompletionsMenu {
     id: CompletionId,
     initial_position: Anchor,
-    buffer: ModelHandle<Buffer>,
+    buffer: Model<Buffer>,
     completions: Arc<RwLock<Box<[Completion]>>>,
     match_candidates: Arc<[StringMatchCandidate]>,
     matches: Arc<[StringMatch]>,
     selected_item: usize,
-    list: UniformListState,
+    scroll_handle: UniformListScrollHandle,
 }
 
 impl CompletionsMenu {
-    fn select_first(
-        &mut self,
-        project: Option<&ModelHandle<Project>>,
-        cx: &mut ViewContext<Editor>,
-    ) {
+    fn select_first(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
         self.selected_item = 0;
-        self.list.scroll_to(ScrollTarget::Show(self.selected_item));
+        self.scroll_handle.scroll_to_item(self.selected_item);
         self.attempt_resolve_selected_completion_documentation(project, cx);
         cx.notify();
     }
 
-    fn select_prev(
-        &mut self,
-        project: Option<&ModelHandle<Project>>,
-        cx: &mut ViewContext<Editor>,
-    ) {
+    fn select_prev(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
         if self.selected_item > 0 {
             self.selected_item -= 1;
         } else {
             self.selected_item = self.matches.len() - 1;
         }
-        self.list.scroll_to(ScrollTarget::Show(self.selected_item));
+        self.scroll_handle.scroll_to_item(self.selected_item);
         self.attempt_resolve_selected_completion_documentation(project, cx);
         cx.notify();
     }
 
-    fn select_next(
-        &mut self,
-        project: Option<&ModelHandle<Project>>,
-        cx: &mut ViewContext<Editor>,
-    ) {
+    fn select_next(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
         if self.selected_item + 1 < self.matches.len() {
             self.selected_item += 1;
         } else {
             self.selected_item = 0;
         }
-        self.list.scroll_to(ScrollTarget::Show(self.selected_item));
+        self.scroll_handle.scroll_to_item(self.selected_item);
         self.attempt_resolve_selected_completion_documentation(project, cx);
         cx.notify();
     }
 
-    fn select_last(
-        &mut self,
-        project: Option<&ModelHandle<Project>>,
-        cx: &mut ViewContext<Editor>,
-    ) {
+    fn select_last(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
         self.selected_item = self.matches.len() - 1;
-        self.list.scroll_to(ScrollTarget::Show(self.selected_item));
+        self.scroll_handle.scroll_to_item(self.selected_item);
         self.attempt_resolve_selected_completion_documentation(project, cx);
         cx.notify();
     }
@@ -1008,7 +896,7 @@ impl CompletionsMenu {
         editor: &Editor,
         cx: &mut ViewContext<Editor>,
     ) -> Option<Task<()>> {
-        let settings = settings::get::<EditorSettings>(cx);
+        let settings = EditorSettings::get_global(cx);
         if !settings.show_completion_documentation {
             return None;
         }
@@ -1069,9 +957,12 @@ impl CompletionsMenu {
                     let completion = completion.lsp_completion.clone();
                     drop(completions_guard);
 
-                    let server = project.read_with(&mut cx, |project, _| {
-                        project.language_server_for_id(server_id)
-                    });
+                    let server = project
+                        .read_with(&mut cx, |project, _| {
+                            project.language_server_for_id(server_id)
+                        })
+                        .ok()
+                        .flatten();
                     let Some(server) = server else {
                         return;
                     };

crates/editor/src/editor_settings.rs 🔗

@@ -1,8 +1,8 @@
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
-use settings::Setting;
+use settings::Settings;
 
-#[derive(Clone, Deserialize)]
+#[derive(Deserialize)]
 pub struct EditorSettings {
     pub cursor_blink: bool,
     pub hover_popover_enabled: bool,
@@ -57,7 +57,7 @@ pub struct ScrollbarContent {
     pub selections: Option<bool>,
 }
 
-impl Setting for EditorSettings {
+impl Settings for EditorSettings {
     const KEY: Option<&'static str> = None;
 
     type FileContent = EditorSettingsContent;
@@ -65,7 +65,7 @@ impl Setting for EditorSettings {
     fn load(
         default_value: &Self::FileContent,
         user_values: &[&Self::FileContent],
-        _: &gpui::AppContext,
+        _: &mut gpui::AppContext,
     ) -> anyhow::Result<Self> {
         Self::load_via_json_merge(default_value, user_values)
     }

crates/editor/src/editor_tests.rs 🔗

@@ -7,14 +7,12 @@ use crate::{
     },
     JoinLines,
 };
-use drag_and_drop::DragAndDrop;
+
 use futures::StreamExt;
 use gpui::{
-    executor::Deterministic,
-    geometry::{rect::RectF, vector::vec2f},
-    platform::{WindowBounds, WindowOptions},
+    div,
     serde_json::{self, json},
-    TestAppContext,
+    TestAppContext, VisualTestContext, WindowBounds, WindowOptions,
 };
 use indoc::indoc;
 use language::{
@@ -34,7 +32,7 @@ use util::{
     test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
 };
 use workspace::{
-    item::{FollowableItem, Item, ItemHandle},
+    item::{FollowEvent, FollowableItem, Item, ItemHandle},
     NavigationEntry, ViewId,
 };
 
@@ -42,127 +40,110 @@ use workspace::{
 fn test_edit_events(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    let buffer = cx.add_model(|cx| {
-        let mut buffer = language::Buffer::new(0, cx.model_id() as u64, "123456");
+    let buffer = cx.new_model(|cx| {
+        let mut buffer = language::Buffer::new(0, cx.entity_id().as_u64(), "123456");
         buffer.set_group_interval(Duration::from_secs(1));
         buffer
     });
 
     let events = Rc::new(RefCell::new(Vec::new()));
-    let editor1 = cx
-        .add_window({
-            let events = events.clone();
-            |cx| {
-                cx.subscribe(&cx.handle(), move |_, _, event, _| {
-                    if matches!(
-                        event,
-                        Event::Edited | Event::BufferEdited | Event::DirtyChanged
-                    ) {
-                        events.borrow_mut().push(("editor1", event.clone()));
-                    }
-                })
-                .detach();
-                Editor::for_buffer(buffer.clone(), None, cx)
-            }
-        })
-        .root(cx);
-    let editor2 = cx
-        .add_window({
-            let events = events.clone();
-            |cx| {
-                cx.subscribe(&cx.handle(), move |_, _, event, _| {
-                    if matches!(
-                        event,
-                        Event::Edited | Event::BufferEdited | Event::DirtyChanged
-                    ) {
-                        events.borrow_mut().push(("editor2", event.clone()));
-                    }
-                })
-                .detach();
-                Editor::for_buffer(buffer.clone(), None, cx)
-            }
-        })
-        .root(cx);
+    let editor1 = cx.add_window({
+        let events = events.clone();
+        |cx| {
+            let view = cx.view().clone();
+            cx.subscribe(&view, move |_, _, event: &EditorEvent, _| {
+                if matches!(event, EditorEvent::Edited | EditorEvent::BufferEdited) {
+                    events.borrow_mut().push(("editor1", event.clone()));
+                }
+            })
+            .detach();
+            Editor::for_buffer(buffer.clone(), None, cx)
+        }
+    });
+
+    let editor2 = cx.add_window({
+        let events = events.clone();
+        |cx| {
+            cx.subscribe(&cx.view().clone(), move |_, _, event: &EditorEvent, _| {
+                if matches!(event, EditorEvent::Edited | EditorEvent::BufferEdited) {
+                    events.borrow_mut().push(("editor2", event.clone()));
+                }
+            })
+            .detach();
+            Editor::for_buffer(buffer.clone(), None, cx)
+        }
+    });
+
     assert_eq!(mem::take(&mut *events.borrow_mut()), []);
 
     // Mutating editor 1 will emit an `Edited` event only for that editor.
-    editor1.update(cx, |editor, cx| editor.insert("X", cx));
+    _ = 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)
+            ("editor1", EditorEvent::Edited),
+            ("editor1", EditorEvent::BufferEdited),
+            ("editor2", EditorEvent::BufferEdited),
         ]
     );
 
     // Mutating editor 2 will emit an `Edited` event only for that editor.
-    editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
+    _ = 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),
+            ("editor2", EditorEvent::Edited),
+            ("editor1", EditorEvent::BufferEdited),
+            ("editor2", EditorEvent::BufferEdited),
         ]
     );
 
     // Undoing on editor 1 will emit an `Edited` event only for that editor.
-    editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
+    _ = 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),
+            ("editor1", EditorEvent::Edited),
+            ("editor1", EditorEvent::BufferEdited),
+            ("editor2", EditorEvent::BufferEdited),
         ]
     );
 
     // Redoing on editor 1 will emit an `Edited` event only for that editor.
-    editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
+    _ = 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),
+            ("editor1", EditorEvent::Edited),
+            ("editor1", EditorEvent::BufferEdited),
+            ("editor2", EditorEvent::BufferEdited),
         ]
     );
 
     // Undoing on editor 2 will emit an `Edited` event only for that editor.
-    editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
+    _ = 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),
+            ("editor2", EditorEvent::Edited),
+            ("editor1", EditorEvent::BufferEdited),
+            ("editor2", EditorEvent::BufferEdited),
         ]
     );
 
     // Redoing on editor 2 will emit an `Edited` event only for that editor.
-    editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
+    _ = 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),
+            ("editor2", EditorEvent::Edited),
+            ("editor1", EditorEvent::BufferEdited),
+            ("editor2", EditorEvent::BufferEdited),
         ]
     );
 
     // No event is emitted when the mutation is a no-op.
-    editor2.update(cx, |editor, cx| {
+    _ = editor2.update(cx, |editor, cx| {
         editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
 
         editor.backspace(&Backspace, cx);
@@ -175,14 +156,12 @@ 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| {
+    let buffer = cx.new_model(|cx| language::Buffer::new(0, cx.entity_id().as_u64(), "123456"));
+    let group_interval = buffer.update(cx, |buffer, _| buffer.transaction_group_interval());
+    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
+    let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
+
+    _ = editor.update(cx, |editor, cx| {
         editor.start_transaction_at(now, cx);
         editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
 
@@ -202,7 +181,7 @@ fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
         editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
 
         // Simulate an edit in another editor
-        buffer.update(cx, |buffer, cx| {
+        _ = 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);
@@ -247,14 +226,14 @@ fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
 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");
+    let buffer = cx.new_model(|cx| {
+        let mut buffer = language::Buffer::new(0, cx.entity_id().as_u64(), "abcde");
         // Ensure automatic grouping doesn't occur.
         buffer.set_group_interval(Duration::ZERO);
         buffer
     });
 
-    let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
+    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
     cx.add_window(|cx| {
         let mut editor = build_editor(buffer.clone(), cx);
 
@@ -350,67 +329,98 @@ fn test_ime_composition(cx: &mut TestAppContext) {
 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| {
+    let editor = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
+        build_editor(buffer, cx)
+    });
+
+    _ = editor.update(cx, |view, cx| {
         view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
     });
     assert_eq!(
-        editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        editor
+            .update(cx, |view, cx| view.selections.display_ranges(cx))
+            .unwrap(),
         [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
     );
 
-    editor.update(cx, |view, cx| {
-        view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx);
+    _ = editor.update(cx, |view, cx| {
+        view.update_selection(
+            DisplayPoint::new(3, 3),
+            0,
+            gpui::Point::<f32>::default(),
+            cx,
+        );
     });
 
     assert_eq!(
-        editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        editor
+            .update(cx, |view, cx| view.selections.display_ranges(cx))
+            .unwrap(),
         [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
     );
 
-    editor.update(cx, |view, cx| {
-        view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx);
+    _ = editor.update(cx, |view, cx| {
+        view.update_selection(
+            DisplayPoint::new(1, 1),
+            0,
+            gpui::Point::<f32>::default(),
+            cx,
+        );
     });
 
     assert_eq!(
-        editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        editor
+            .update(cx, |view, cx| view.selections.display_ranges(cx))
+            .unwrap(),
         [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
     );
 
-    editor.update(cx, |view, cx| {
+    _ = editor.update(cx, |view, cx| {
         view.end_selection(cx);
-        view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx);
+        view.update_selection(
+            DisplayPoint::new(3, 3),
+            0,
+            gpui::Point::<f32>::default(),
+            cx,
+        );
     });
 
     assert_eq!(
-        editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        editor
+            .update(cx, |view, cx| view.selections.display_ranges(cx))
+            .unwrap(),
         [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
     );
 
-    editor.update(cx, |view, cx| {
+    _ = editor.update(cx, |view, cx| {
         view.begin_selection(DisplayPoint::new(3, 3), true, 1, cx);
-        view.update_selection(DisplayPoint::new(0, 0), 0, Vector2F::zero(), cx);
+        view.update_selection(
+            DisplayPoint::new(0, 0),
+            0,
+            gpui::Point::<f32>::default(),
+            cx,
+        );
     });
 
     assert_eq!(
-        editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        editor
+            .update(cx, |view, cx| view.selections.display_ranges(cx))
+            .unwrap(),
         [
             DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1),
             DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)
         ]
     );
 
-    editor.update(cx, |view, cx| {
+    _ = editor.update(cx, |view, cx| {
         view.end_selection(cx);
     });
 
     assert_eq!(
-        editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        editor
+            .update(cx, |view, cx| view.selections.display_ranges(cx))
+            .unwrap(),
         [DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)]
     );
 }
@@ -419,14 +429,12 @@ fn test_selection_with_mouse(cx: &mut TestAppContext) {
 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);
+    let view = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
+        build_editor(buffer, cx)
+    });
 
-    view.update(cx, |view, cx| {
+    _ = view.update(cx, |view, cx| {
         view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
         assert_eq!(
             view.selections.display_ranges(cx),
@@ -434,17 +442,27 @@ fn test_canceling_pending_selection(cx: &mut TestAppContext) {
         );
     });
 
-    view.update(cx, |view, cx| {
-        view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx);
+    _ = view.update(cx, |view, cx| {
+        view.update_selection(
+            DisplayPoint::new(3, 3),
+            0,
+            gpui::Point::<f32>::default(),
+            cx,
+        );
         assert_eq!(
             view.selections.display_ranges(cx),
             [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
         );
     });
 
-    view.update(cx, |view, cx| {
+    _ = view.update(cx, |view, cx| {
         view.cancel(&Cancel, cx);
-        view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx);
+        view.update_selection(
+            DisplayPoint::new(1, 1),
+            0,
+            gpui::Point::<f32>::default(),
+            cx,
+        );
         assert_eq!(
             view.selections.display_ranges(cx),
             [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
@@ -467,14 +485,12 @@ fn test_clone(cx: &mut TestAppContext) {
         true,
     );
 
-    let editor = cx
-        .add_window(|cx| {
-            let buffer = MultiBuffer::build_simple(&text, cx);
-            build_editor(buffer, cx)
-        })
-        .root(cx);
+    let editor = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple(&text, cx);
+        build_editor(buffer, cx)
+    });
 
-    editor.update(cx, |editor, cx| {
+    _ = editor.update(cx, |editor, cx| {
         editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
         editor.fold_ranges(
             [
@@ -488,16 +504,18 @@ fn test_clone(cx: &mut TestAppContext) {
 
     let cloned_editor = editor
         .update(cx, |editor, cx| {
-            cx.add_window(Default::default(), |cx| editor.clone(cx))
+            cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
         })
-        .root(cx);
+        .unwrap();
 
-    let snapshot = editor.update(cx, |e, cx| e.snapshot(cx));
-    let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx));
+    let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
+    let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
 
     assert_eq!(
-        cloned_editor.update(cx, |e, cx| e.display_text(cx)),
-        editor.update(cx, |e, cx| e.display_text(cx))
+        cloned_editor
+            .update(cx, |e, cx| e.display_text(cx))
+            .unwrap(),
+        editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
     );
     assert_eq!(
         cloned_snapshot
@@ -506,127 +524,139 @@ fn test_clone(cx: &mut TestAppContext) {
         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))
+        cloned_editor
+            .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
+            .unwrap(),
+        editor
+            .update(cx, |editor, cx| editor.selections.ranges(cx))
+            .unwrap()
     );
     assert_set_eq!(
-        cloned_editor.update(cx, |e, cx| e.selections.display_ranges(cx)),
-        editor.update(cx, |e, cx| e.selections.display_ranges(cx))
+        cloned_editor
+            .update(cx, |e, cx| e.selections.display_ranges(cx))
+            .unwrap(),
+        editor
+            .update(cx, |e, cx| e.selections.display_ranges(cx))
+            .unwrap()
     );
 }
 
+//todo!(editor navigate)
 #[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 fs = FakeFs::new(cx.executor());
     let project = Project::test(fs, [], cx).await;
-    let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-    let workspace = window.root(cx);
-    let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-    window.add_view(cx, |cx| {
-        let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
-        let mut editor = build_editor(buffer.clone(), cx);
-        let handle = cx.handle();
-        editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
+    let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
+    let pane = workspace
+        .update(cx, |workspace, _| workspace.active_pane().clone())
+        .unwrap();
 
-        fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
-            editor.nav_history.as_mut().unwrap().pop_backward(cx)
-        }
+    _ = workspace.update(cx, |_v, cx| {
+        cx.new_view(|cx| {
+            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
+            let mut editor = build_editor(buffer.clone(), cx);
+            let handle = cx.view();
+            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
 
-        // 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());
+            fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
+                editor.nav_history.as_mut().unwrap().pop_backward(cx)
+            }
 
-        // Move the cursor a large distance.
-        // The history can jump back to the previous position.
-        editor.change_selections(None, cx, |s| {
-            s.select_display_ranges([DisplayPoint::new(13, 0)..DisplayPoint::new(13, 3)])
-        });
-        let nav_entry = pop_history(&mut editor, cx).unwrap();
-        editor.navigate(nav_entry.data.unwrap(), cx);
-        assert_eq!(nav_entry.item.id(), cx.view_id());
-        assert_eq!(
-            editor.selections.display_ranges(cx),
-            &[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)]
-        );
-        assert!(pop_history(&mut editor, cx).is_none());
-
-        // Move the cursor a small distance via the mouse.
-        // Nothing is added to the navigation history.
-        editor.begin_selection(DisplayPoint::new(5, 0), false, 1, cx);
-        editor.end_selection(cx);
-        assert_eq!(
-            editor.selections.display_ranges(cx),
-            &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
-        );
-        assert!(pop_history(&mut editor, cx).is_none());
-
-        // Move the cursor a large distance via the mouse.
-        // The history can jump back to the previous position.
-        editor.begin_selection(DisplayPoint::new(15, 0), false, 1, cx);
-        editor.end_selection(cx);
-        assert_eq!(
-            editor.selections.display_ranges(cx),
-            &[DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)]
-        );
-        let nav_entry = pop_history(&mut editor, cx).unwrap();
-        editor.navigate(nav_entry.data.unwrap(), cx);
-        assert_eq!(nav_entry.item.id(), cx.view_id());
-        assert_eq!(
-            editor.selections.display_ranges(cx),
-            &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
-        );
-        assert!(pop_history(&mut editor, cx).is_none());
-
-        // Set scroll position to check later
-        editor.set_scroll_position(Vector2F::new(5.5, 5.5), cx);
-        let original_scroll_position = editor.scroll_manager.anchor();
-
-        // Jump to the end of the document and adjust scroll
-        editor.move_to_end(&MoveToEnd, cx);
-        editor.set_scroll_position(Vector2F::new(-2.5, -0.5), cx);
-        assert_ne!(editor.scroll_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)
-        );
+            // 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());
 
-        editor
+            // Move the cursor a large distance.
+            // The history can jump back to the previous position.
+            editor.change_selections(None, cx, |s| {
+                s.select_display_ranges([DisplayPoint::new(13, 0)..DisplayPoint::new(13, 3)])
+            });
+            let nav_entry = pop_history(&mut editor, cx).unwrap();
+            editor.navigate(nav_entry.data.unwrap(), cx);
+            assert_eq!(nav_entry.item.id(), cx.entity_id());
+            assert_eq!(
+                editor.selections.display_ranges(cx),
+                &[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)]
+            );
+            assert!(pop_history(&mut editor, cx).is_none());
+
+            // 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.entity_id());
+            assert_eq!(
+                editor.selections.display_ranges(cx),
+                &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
+            );
+            assert!(pop_history(&mut editor, cx).is_none());
+
+            // Set scroll position to check later
+            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
+            let original_scroll_position = editor.scroll_manager.anchor();
+
+            // Jump to the end of the document and adjust scroll
+            editor.move_to_end(&MoveToEnd, cx);
+            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
+            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
+
+            let nav_entry = pop_history(&mut editor, cx).unwrap();
+            editor.navigate(nav_entry.data.unwrap(), cx);
+            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
+
+            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
+            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
+            invalid_anchor.text_anchor.buffer_id = Some(999);
+            let invalid_point = Point::new(9999, 0);
+            editor.navigate(
+                Box::new(NavigationData {
+                    cursor_anchor: invalid_anchor,
+                    cursor_position: invalid_point,
+                    scroll_anchor: ScrollAnchor {
+                        anchor: invalid_anchor,
+                        offset: Default::default(),
+                    },
+                    scroll_top_row: invalid_point.row,
+                }),
+                cx,
+            );
+            assert_eq!(
+                editor.selections.display_ranges(cx),
+                &[editor.max_point(cx)..editor.max_point(cx)]
+            );
+            assert_eq!(
+                editor.scroll_position(cx),
+                gpui::Point::new(0., editor.max_point(cx).row() as f32)
+            );
+
+            editor
+        })
     });
 }
 
@@ -634,20 +664,28 @@ async fn test_navigation_history(cx: &mut TestAppContext) {
 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);
+    let view = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
+        build_editor(buffer, cx)
+    });
 
-    view.update(cx, |view, cx| {
+    _ = view.update(cx, |view, cx| {
         view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx);
-        view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx);
+        view.update_selection(
+            DisplayPoint::new(1, 1),
+            0,
+            gpui::Point::<f32>::default(),
+            cx,
+        );
         view.end_selection(cx);
 
         view.begin_selection(DisplayPoint::new(0, 1), true, 1, cx);
-        view.update_selection(DisplayPoint::new(0, 3), 0, Vector2F::zero(), cx);
+        view.update_selection(
+            DisplayPoint::new(0, 3),
+            0,
+            gpui::Point::<f32>::default(),
+            cx,
+        );
         view.end_selection(cx);
         assert_eq!(
             view.selections.display_ranges(cx),
@@ -658,7 +696,7 @@ fn test_cancel(cx: &mut TestAppContext) {
         );
     });
 
-    view.update(cx, |view, cx| {
+    _ = view.update(cx, |view, cx| {
         view.cancel(&Cancel, cx);
         assert_eq!(
             view.selections.display_ranges(cx),
@@ -666,7 +704,7 @@ fn test_cancel(cx: &mut TestAppContext) {
         );
     });
 
-    view.update(cx, |view, cx| {
+    _ = view.update(cx, |view, cx| {
         view.cancel(&Cancel, cx);
         assert_eq!(
             view.selections.display_ranges(cx),
@@ -679,10 +717,9 @@ fn test_cancel(cx: &mut TestAppContext) {
 fn test_fold_action(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    let view = cx
-        .add_window(|cx| {
-            let buffer = MultiBuffer::build_simple(
-                &"
+    let view = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple(
+            &"
                 impl Foo {
                     // Hello!
 
@@ -699,14 +736,13 @@ fn test_fold_action(cx: &mut TestAppContext) {
                     }
                 }
             "
-                .unindent(),
-                cx,
-            );
-            build_editor(buffer.clone(), cx)
-        })
-        .root(cx);
+            .unindent(),
+            cx,
+        );
+        build_editor(buffer.clone(), cx)
+    });
 
-    view.update(cx, |view, cx| {
+    _ = view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)]);
         });
@@ -772,11 +808,9 @@ 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);
+    let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
 
-    buffer.update(cx, |buffer, cx| {
+    _ = buffer.update(cx, |buffer, cx| {
         buffer.edit(
             vec![
                 (Point::new(1, 0)..Point::new(1, 0), "\t"),
@@ -786,7 +820,7 @@ fn test_move_cursor(cx: &mut TestAppContext) {
             cx,
         );
     });
-    view.update(cx, |view, cx| {
+    _ = view.update(cx, |view, cx| {
         assert_eq!(
             view.selections.display_ranges(cx),
             &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
@@ -849,17 +883,15 @@ fn test_move_cursor(cx: &mut TestAppContext) {
 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);
+    let view = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
+        build_editor(buffer.clone(), cx)
+    });
 
     assert_eq!('ⓐ'.len_utf8(), 3);
     assert_eq!('α'.len_utf8(), 2);
 
-    view.update(cx, |view, cx| {
+    _ = view.update(cx, |view, cx| {
         view.fold_ranges(
             vec![
                 Point::new(0, 6)..Point::new(0, 12),
@@ -963,17 +995,16 @@ fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
     });
 }
 
+//todo!(finish editor tests)
 #[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| {
+    let view = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
+        build_editor(buffer.clone(), cx)
+    });
+    _ = view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
         });
@@ -1019,13 +1050,11 @@ fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 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| {
+    let view = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
+        build_editor(buffer, cx)
+    });
+    _ = view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
                 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
@@ -1034,7 +1063,7 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
         });
     });
 
-    view.update(cx, |view, cx| {
+    _ = view.update(cx, |view, cx| {
         view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
         assert_eq!(
             view.selections.display_ranges(cx),
@@ -1045,7 +1074,7 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
         );
     });
 
-    view.update(cx, |view, cx| {
+    _ = view.update(cx, |view, cx| {
         view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
         assert_eq!(
             view.selections.display_ranges(cx),
@@ -1056,7 +1085,7 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
         );
     });
 
-    view.update(cx, |view, cx| {
+    _ = view.update(cx, |view, cx| {
         view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
         assert_eq!(
             view.selections.display_ranges(cx),
@@ -1067,7 +1096,7 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
         );
     });
 
-    view.update(cx, |view, cx| {
+    _ = view.update(cx, |view, cx| {
         view.move_to_end_of_line(&MoveToEndOfLine, cx);
         assert_eq!(
             view.selections.display_ranges(cx),
@@ -1079,7 +1108,7 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
     });
 
     // Moving to the end of line again is a no-op.
-    view.update(cx, |view, cx| {
+    _ = view.update(cx, |view, cx| {
         view.move_to_end_of_line(&MoveToEndOfLine, cx);
         assert_eq!(
             view.selections.display_ranges(cx),
@@ -1090,7 +1119,7 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
         );
     });
 
-    view.update(cx, |view, cx| {
+    _ = view.update(cx, |view, cx| {
         view.move_left(&MoveLeft, cx);
         view.select_to_beginning_of_line(
             &SelectToBeginningOfLine {
@@ -1107,7 +1136,7 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
         );
     });
 
-    view.update(cx, |view, cx| {
+    _ = view.update(cx, |view, cx| {
         view.select_to_beginning_of_line(
             &SelectToBeginningOfLine {
                 stop_at_soft_wraps: true,
@@ -1123,7 +1152,7 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
         );
     });
 
-    view.update(cx, |view, cx| {
+    _ = view.update(cx, |view, cx| {
         view.select_to_beginning_of_line(
             &SelectToBeginningOfLine {
                 stop_at_soft_wraps: true,
@@ -1139,7 +1168,7 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
         );
     });
 
-    view.update(cx, |view, cx| {
+    _ = view.update(cx, |view, cx| {
         view.select_to_end_of_line(
             &SelectToEndOfLine {
                 stop_at_soft_wraps: true,
@@ -1155,7 +1184,7 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
         );
     });
 
-    view.update(cx, |view, cx| {
+    _ = view.update(cx, |view, cx| {
         view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
         assert_eq!(view.display_text(cx), "ab\n  de");
         assert_eq!(
@@ -1167,7 +1196,7 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
         );
     });
 
-    view.update(cx, |view, cx| {
+    _ = view.update(cx, |view, cx| {
         view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
         assert_eq!(view.display_text(cx), "\n");
         assert_eq!(
@@ -1184,13 +1213,11 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 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| {
+    let view = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
+        build_editor(buffer, cx)
+    });
+    _ = view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
                 DisplayPoint::new(0, 11)..DisplayPoint::new(0, 11),

crates/editor/src/element.rs 🔗

@@ -1,50 +1,47 @@
-use super::{
-    display_map::{BlockContext, ToDisplayPoint},
-    Anchor, DisplayPoint, Editor, EditorMode, EditorSnapshot, SelectPhase, SoftWrap, ToPoint,
-    MAX_LINE_LEN,
-};
 use crate::{
-    display_map::{BlockStyle, DisplaySnapshot, FoldStatus, HighlightedChunk, TransformBlock},
+    display_map::{
+        BlockContext, BlockStyle, DisplaySnapshot, FoldStatus, HighlightedChunk, ToDisplayPoint,
+        TransformBlock,
+    },
     editor_settings::ShowScrollbar,
     git::{diff_hunk_to_display, DisplayDiffHunk},
     hover_popover::{
-        hide_hover, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH,
-        MIN_POPOVER_LINE_HEIGHT,
+        self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
     },
     link_go_to_definition::{
-        go_to_fetched_definition, go_to_fetched_type_definition, update_go_to_definition_link,
-        update_inlay_link_and_hover_points, GoToDefinitionTrigger,
+        go_to_fetched_definition, go_to_fetched_type_definition, show_link_definition,
+        update_go_to_definition_link, update_inlay_link_and_hover_points, GoToDefinitionTrigger,
+        LinkGoToDefinitionState,
     },
-    mouse_context_menu, EditorSettings, EditorStyle, GutterHover, UnfoldAt,
+    mouse_context_menu,
+    scroll::scroll_amount::ScrollAmount,
+    CursorShape, DisplayPoint, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle,
+    HalfPageDown, HalfPageUp, LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point, SelectPhase,
+    Selection, SoftWrap, ToPoint, MAX_LINE_LEN,
 };
+use anyhow::Result;
 use collections::{BTreeMap, HashMap};
 use git::diff::DiffHunkStatus;
 use gpui::{
-    color::Color,
-    elements::*,
-    fonts::TextStyle,
-    geometry::{
-        rect::RectF,
-        vector::{vec2f, Vector2F},
-        PathBuilder,
-    },
-    json::{self, ToJson},
-    platform::{CursorStyle, Modifiers, MouseButton, MouseButtonEvent, MouseMovedEvent},
-    text_layout::{self, Line, RunStyle, TextLayoutCache},
-    AnyElement, Axis, CursorRegion, Element, EventContext, FontCache, MouseRegion, Quad,
-    SizeConstraint, ViewContext, WindowContext,
+    div, fill, outline, overlay, point, px, quad, relative, size, transparent_black, Action,
+    AnchorCorner, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners,
+    CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Hsla, InteractiveBounds,
+    InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, MouseDownEvent,
+    MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollWheelEvent, ShapedLine,
+    SharedString, Size, StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun,
+    TextStyle, View, ViewContext, WindowContext,
 };
 use itertools::Itertools;
-use json::json;
-use language::{
-    language_settings::ShowWhitespaceSetting, Bias, CursorShape, OffsetUtf16, Selection,
-};
+use language::language_settings::ShowWhitespaceSetting;
+use multi_buffer::Anchor;
 use project::{
     project_settings::{GitGutterSetting, ProjectSettings},
     ProjectPath,
 };
+use settings::Settings;
 use smallvec::SmallVec;
 use std::{
+    any::TypeId,
     borrow::Cow,
     cmp::{self, Ordering},
     fmt::Write,
@@ -52,12 +49,13 @@ use std::{
     ops::Range,
     sync::Arc,
 };
-use text::Point;
-use theme::SelectionStyle;
+use sum_tree::Bias;
+use theme::{ActiveTheme, PlayerColor};
+use ui::prelude::*;
+use ui::{h_stack, ButtonLike, ButtonStyle, IconButton, Tooltip};
+use util::ResultExt;
 use workspace::item::Item;
 
-enum FoldMarkers {}
-
 struct SelectionLayout {
     head: DisplayPoint,
     cursor_shape: CursorShape,
@@ -119,176 +117,288 @@ impl SelectionLayout {
 }
 
 pub struct EditorElement {
-    style: Arc<EditorStyle>,
+    editor: View<Editor>,
+    style: EditorStyle,
 }
 
 impl EditorElement {
-    pub fn new(style: EditorStyle) -> Self {
+    pub fn new(editor: &View<Editor>, style: EditorStyle) -> Self {
         Self {
-            style: Arc::new(style),
+            editor: editor.clone(),
+            style,
         }
     }
 
-    fn attach_mouse_handlers(
-        position_map: &Arc<PositionMap>,
-        has_popovers: bool,
-        visible_bounds: RectF,
-        text_bounds: RectF,
-        gutter_bounds: RectF,
-        bounds: RectF,
+    fn register_actions(&self, cx: &mut WindowContext) {
+        let view = &self.editor;
+        view.update(cx, |editor, cx| {
+            for action in editor.editor_actions.iter() {
+                (action)(cx)
+            }
+        });
+
+        crate::rust_analyzer_ext::apply_related_actions(view, cx);
+        register_action(view, cx, Editor::move_left);
+        register_action(view, cx, Editor::move_right);
+        register_action(view, cx, Editor::move_down);
+        register_action(view, cx, Editor::move_up);
+        register_action(view, cx, Editor::cancel);
+        register_action(view, cx, Editor::newline);
+        register_action(view, cx, Editor::newline_above);
+        register_action(view, cx, Editor::newline_below);
+        register_action(view, cx, Editor::backspace);
+        register_action(view, cx, Editor::delete);
+        register_action(view, cx, Editor::tab);
+        register_action(view, cx, Editor::tab_prev);
+        register_action(view, cx, Editor::indent);
+        register_action(view, cx, Editor::outdent);
+        register_action(view, cx, Editor::delete_line);
+        register_action(view, cx, Editor::join_lines);
+        register_action(view, cx, Editor::sort_lines_case_sensitive);
+        register_action(view, cx, Editor::sort_lines_case_insensitive);
+        register_action(view, cx, Editor::reverse_lines);
+        register_action(view, cx, Editor::shuffle_lines);
+        register_action(view, cx, Editor::convert_to_upper_case);
+        register_action(view, cx, Editor::convert_to_lower_case);
+        register_action(view, cx, Editor::convert_to_title_case);
+        register_action(view, cx, Editor::convert_to_snake_case);
+        register_action(view, cx, Editor::convert_to_kebab_case);
+        register_action(view, cx, Editor::convert_to_upper_camel_case);
+        register_action(view, cx, Editor::convert_to_lower_camel_case);
+        register_action(view, cx, Editor::delete_to_previous_word_start);
+        register_action(view, cx, Editor::delete_to_previous_subword_start);
+        register_action(view, cx, Editor::delete_to_next_word_end);
+        register_action(view, cx, Editor::delete_to_next_subword_end);
+        register_action(view, cx, Editor::delete_to_beginning_of_line);
+        register_action(view, cx, Editor::delete_to_end_of_line);
+        register_action(view, cx, Editor::cut_to_end_of_line);
+        register_action(view, cx, Editor::duplicate_line);
+        register_action(view, cx, Editor::move_line_up);
+        register_action(view, cx, Editor::move_line_down);
+        register_action(view, cx, Editor::transpose);
+        register_action(view, cx, Editor::cut);
+        register_action(view, cx, Editor::copy);
+        register_action(view, cx, Editor::paste);
+        register_action(view, cx, Editor::undo);
+        register_action(view, cx, Editor::redo);
+        register_action(view, cx, Editor::move_page_up);
+        register_action(view, cx, Editor::move_page_down);
+        register_action(view, cx, Editor::next_screen);
+        register_action(view, cx, Editor::scroll_cursor_top);
+        register_action(view, cx, Editor::scroll_cursor_center);
+        register_action(view, cx, Editor::scroll_cursor_bottom);
+        register_action(view, cx, |editor, _: &LineDown, cx| {
+            editor.scroll_screen(&ScrollAmount::Line(1.), cx)
+        });
+        register_action(view, cx, |editor, _: &LineUp, cx| {
+            editor.scroll_screen(&ScrollAmount::Line(-1.), cx)
+        });
+        register_action(view, cx, |editor, _: &HalfPageDown, cx| {
+            editor.scroll_screen(&ScrollAmount::Page(0.5), cx)
+        });
+        register_action(view, cx, |editor, _: &HalfPageUp, cx| {
+            editor.scroll_screen(&ScrollAmount::Page(-0.5), cx)
+        });
+        register_action(view, cx, |editor, _: &PageDown, cx| {
+            editor.scroll_screen(&ScrollAmount::Page(1.), cx)
+        });
+        register_action(view, cx, |editor, _: &PageUp, cx| {
+            editor.scroll_screen(&ScrollAmount::Page(-1.), cx)
+        });
+        register_action(view, cx, Editor::move_to_previous_word_start);
+        register_action(view, cx, Editor::move_to_previous_subword_start);
+        register_action(view, cx, Editor::move_to_next_word_end);
+        register_action(view, cx, Editor::move_to_next_subword_end);
+        register_action(view, cx, Editor::move_to_beginning_of_line);
+        register_action(view, cx, Editor::move_to_end_of_line);
+        register_action(view, cx, Editor::move_to_start_of_paragraph);
+        register_action(view, cx, Editor::move_to_end_of_paragraph);
+        register_action(view, cx, Editor::move_to_beginning);
+        register_action(view, cx, Editor::move_to_end);
+        register_action(view, cx, Editor::select_up);
+        register_action(view, cx, Editor::select_down);
+        register_action(view, cx, Editor::select_left);
+        register_action(view, cx, Editor::select_right);
+        register_action(view, cx, Editor::select_to_previous_word_start);
+        register_action(view, cx, Editor::select_to_previous_subword_start);
+        register_action(view, cx, Editor::select_to_next_word_end);
+        register_action(view, cx, Editor::select_to_next_subword_end);
+        register_action(view, cx, Editor::select_to_beginning_of_line);
+        register_action(view, cx, Editor::select_to_end_of_line);
+        register_action(view, cx, Editor::select_to_start_of_paragraph);
+        register_action(view, cx, Editor::select_to_end_of_paragraph);
+        register_action(view, cx, Editor::select_to_beginning);
+        register_action(view, cx, Editor::select_to_end);
+        register_action(view, cx, Editor::select_all);
+        register_action(view, cx, |editor, action, cx| {
+            editor.select_all_matches(action, cx).log_err();
+        });
+        register_action(view, cx, Editor::select_line);
+        register_action(view, cx, Editor::split_selection_into_lines);
+        register_action(view, cx, Editor::add_selection_above);
+        register_action(view, cx, Editor::add_selection_below);
+        register_action(view, cx, |editor, action, cx| {
+            editor.select_next(action, cx).log_err();
+        });
+        register_action(view, cx, |editor, action, cx| {
+            editor.select_previous(action, cx).log_err();
+        });
+        register_action(view, cx, Editor::toggle_comments);
+        register_action(view, cx, Editor::select_larger_syntax_node);
+        register_action(view, cx, Editor::select_smaller_syntax_node);
+        register_action(view, cx, Editor::move_to_enclosing_bracket);
+        register_action(view, cx, Editor::undo_selection);
+        register_action(view, cx, Editor::redo_selection);
+        register_action(view, cx, Editor::go_to_diagnostic);
+        register_action(view, cx, Editor::go_to_prev_diagnostic);
+        register_action(view, cx, Editor::go_to_hunk);
+        register_action(view, cx, Editor::go_to_prev_hunk);
+        register_action(view, cx, Editor::go_to_definition);
+        register_action(view, cx, Editor::go_to_definition_split);
+        register_action(view, cx, Editor::go_to_type_definition);
+        register_action(view, cx, Editor::go_to_type_definition_split);
+        register_action(view, cx, Editor::fold);
+        register_action(view, cx, Editor::fold_at);
+        register_action(view, cx, Editor::unfold_lines);
+        register_action(view, cx, Editor::unfold_at);
+        register_action(view, cx, Editor::fold_selected_ranges);
+        register_action(view, cx, Editor::show_completions);
+        register_action(view, cx, Editor::toggle_code_actions);
+        register_action(view, cx, Editor::open_excerpts);
+        register_action(view, cx, Editor::toggle_soft_wrap);
+        register_action(view, cx, Editor::toggle_inlay_hints);
+        register_action(view, cx, hover_popover::hover);
+        register_action(view, cx, Editor::reveal_in_finder);
+        register_action(view, cx, Editor::copy_path);
+        register_action(view, cx, Editor::copy_relative_path);
+        register_action(view, cx, Editor::copy_highlight_json);
+        register_action(view, cx, |editor, action, cx| {
+            if let Some(task) = editor.format(action, cx) {
+                task.detach_and_log_err(cx);
+            } else {
+                cx.propagate();
+            }
+        });
+        register_action(view, cx, Editor::restart_language_server);
+        register_action(view, cx, Editor::show_character_palette);
+        register_action(view, cx, |editor, action, cx| {
+            if let Some(task) = editor.confirm_completion(action, cx) {
+                task.detach_and_log_err(cx);
+            } else {
+                cx.propagate();
+            }
+        });
+        register_action(view, cx, |editor, action, cx| {
+            if let Some(task) = editor.confirm_code_action(action, cx) {
+                task.detach_and_log_err(cx);
+            } else {
+                cx.propagate();
+            }
+        });
+        register_action(view, cx, |editor, action, cx| {
+            if let Some(task) = editor.rename(action, cx) {
+                task.detach_and_log_err(cx);
+            } else {
+                cx.propagate();
+            }
+        });
+        register_action(view, cx, |editor, action, cx| {
+            if let Some(task) = editor.confirm_rename(action, cx) {
+                task.detach_and_log_err(cx);
+            } else {
+                cx.propagate();
+            }
+        });
+        register_action(view, cx, |editor, action, cx| {
+            if let Some(task) = editor.find_all_references(action, cx) {
+                task.detach_and_log_err(cx);
+            } else {
+                cx.propagate();
+            }
+        });
+        register_action(view, cx, Editor::next_copilot_suggestion);
+        register_action(view, cx, Editor::previous_copilot_suggestion);
+        register_action(view, cx, Editor::copilot_suggest);
+        register_action(view, cx, Editor::context_menu_first);
+        register_action(view, cx, Editor::context_menu_prev);
+        register_action(view, cx, Editor::context_menu_next);
+        register_action(view, cx, Editor::context_menu_last);
+    }
+
+    fn register_key_listeners(&self, cx: &mut WindowContext) {
+        cx.on_key_event({
+            let editor = self.editor.clone();
+            move |event: &ModifiersChangedEvent, phase, cx| {
+                if phase != DispatchPhase::Bubble {
+                    return;
+                }
+
+                if editor.update(cx, |editor, cx| Self::modifiers_changed(editor, event, cx)) {
+                    cx.stop_propagation();
+                }
+            }
+        });
+    }
+
+    pub(crate) fn modifiers_changed(
+        editor: &mut Editor,
+        event: &ModifiersChangedEvent,
         cx: &mut ViewContext<Editor>,
-    ) {
-        enum EditorElementMouseHandlers {}
-        let view_id = cx.view_id();
-        cx.scene().push_mouse_region(
-            MouseRegion::new::<EditorElementMouseHandlers>(view_id, view_id, visible_bounds)
-                .on_down(MouseButton::Left, {
-                    let position_map = position_map.clone();
-                    move |event, editor, cx| {
-                        if !Self::mouse_down(
-                            editor,
-                            event.platform_event,
-                            position_map.as_ref(),
-                            text_bounds,
-                            gutter_bounds,
-                            cx,
-                        ) {
-                            cx.propagate_event();
-                        }
-                    }
-                })
-                .on_down(MouseButton::Right, {
-                    let position_map = position_map.clone();
-                    move |event, editor, cx| {
-                        if !Self::mouse_right_down(
-                            editor,
-                            event.position,
-                            position_map.as_ref(),
-                            text_bounds,
-                            cx,
-                        ) {
-                            cx.propagate_event();
-                        }
-                    }
-                })
-                .on_up(MouseButton::Left, {
-                    let position_map = position_map.clone();
-                    move |event, editor, cx| {
-                        if !Self::mouse_up(
-                            editor,
-                            event.position,
-                            event.cmd,
-                            event.shift,
-                            event.alt,
-                            position_map.as_ref(),
-                            text_bounds,
-                            cx,
-                        ) {
-                            cx.propagate_event()
-                        }
-                    }
-                })
-                .on_drag(MouseButton::Left, {
-                    let position_map = position_map.clone();
-                    move |event, editor, cx| {
-                        if event.end {
-                            return;
-                        }
+    ) -> bool {
+        let pending_selection = editor.has_pending_selection();
 
-                        if !Self::mouse_dragged(
-                            editor,
-                            event.platform_event,
-                            position_map.as_ref(),
-                            text_bounds,
-                            cx,
-                        ) {
-                            cx.propagate_event()
-                        }
-                    }
-                })
-                .on_move({
-                    let position_map = position_map.clone();
-                    move |event, editor, cx| {
-                        if !Self::mouse_moved(
-                            editor,
-                            event.platform_event,
-                            &position_map,
-                            text_bounds,
-                            cx,
-                        ) {
-                            cx.propagate_event()
-                        }
-                    }
-                })
-                .on_move_out(move |_, editor: &mut Editor, cx| {
-                    if has_popovers {
-                        hide_hover(editor, cx);
-                    }
-                })
-                .on_scroll({
-                    let position_map = position_map.clone();
-                    move |event, editor, cx| {
-                        if !Self::scroll(
-                            editor,
-                            event.position,
-                            *event.delta.raw(),
-                            event.delta.precise(),
-                            &position_map,
-                            bounds,
-                            cx,
-                        ) {
-                            cx.propagate_event()
-                        }
-                    }
-                }),
-        );
+        if let Some(point) = &editor.link_go_to_definition_state.last_trigger_point {
+            if event.command && !pending_selection {
+                let point = point.clone();
+                let snapshot = editor.snapshot(cx);
+                let kind = point.definition_kind(event.shift);
 
-        enum GutterHandlers {}
-        let view_id = cx.view_id();
-        let region_id = cx.view_id() + 1;
-        cx.scene().push_mouse_region(
-            MouseRegion::new::<GutterHandlers>(view_id, region_id, gutter_bounds).on_hover(
-                |hover, editor: &mut Editor, cx| {
-                    editor.gutter_hover(
-                        &GutterHover {
-                            hovered: hover.started,
-                        },
-                        cx,
-                    );
-                },
-            ),
-        )
+                show_link_definition(kind, editor, point, snapshot, cx);
+                return false;
+            }
+        }
+
+        {
+            if editor.link_go_to_definition_state.symbol_range.is_some()
+                || !editor.link_go_to_definition_state.definitions.is_empty()
+            {
+                editor.link_go_to_definition_state.symbol_range.take();
+                editor.link_go_to_definition_state.definitions.clear();
+                cx.notify();
+            }
+
+            editor.link_go_to_definition_state.task = None;
+
+            editor.clear_highlights::<LinkGoToDefinitionState>(cx);
+        }
+
+        false
     }
 
-    fn mouse_down(
+    fn mouse_left_down(
         editor: &mut Editor,
-        MouseButtonEvent {
-            position,
-            modifiers:
-                Modifiers {
-                    shift,
-                    ctrl,
-                    alt,
-                    cmd,
-                    ..
-                },
-            mut click_count,
-            ..
-        }: MouseButtonEvent,
+        event: &MouseDownEvent,
         position_map: &PositionMap,
-        text_bounds: RectF,
-        gutter_bounds: RectF,
-        cx: &mut EventContext<Editor>,
-    ) -> bool {
-        if gutter_bounds.contains_point(position) {
+        text_bounds: Bounds<Pixels>,
+        gutter_bounds: Bounds<Pixels>,
+        stacking_order: &StackingOrder,
+        cx: &mut ViewContext<Editor>,
+    ) {
+        let mut click_count = event.click_count;
+        let modifiers = event.modifiers;
+
+        if gutter_bounds.contains(&event.position) {
             click_count = 3; // Simulate triple-click when clicking the gutter to select lines
-        } else if !text_bounds.contains_point(position) {
-            return false;
+        } else if !text_bounds.contains(&event.position) {
+            return;
+        }
+        if !cx.was_top_layer(&event.position, stacking_order) {
+            return;
         }
 
-        let point_for_position = position_map.point_for_position(text_bounds, position);
+        let point_for_position = position_map.point_for_position(text_bounds, event.position);
         let position = point_for_position.previous_valid;
-        if shift && alt {
+        if modifiers.shift && modifiers.alt {
             editor.select(
                 SelectPhase::BeginColumnar {
                     position,
@@ -296,7 +406,7 @@ impl EditorElement {
                 },
                 cx,
             );
-        } else if shift && !ctrl && !alt && !cmd {
+        } else if modifiers.shift && !modifiers.control && !modifiers.alt && !modifiers.command {
             editor.select(
                 SelectPhase::Extend {
                     position,
@@ -308,46 +418,44 @@ impl EditorElement {
             editor.select(
                 SelectPhase::Begin {
                     position,
-                    add: alt,
+                    add: modifiers.alt,
                     click_count,
                 },
                 cx,
             );
         }
 
-        true
+        cx.stop_propagation();
     }
 
     fn mouse_right_down(
         editor: &mut Editor,
-        position: Vector2F,
+        event: &MouseDownEvent,
         position_map: &PositionMap,
-        text_bounds: RectF,
-        cx: &mut EventContext<Editor>,
-    ) -> bool {
-        if !text_bounds.contains_point(position) {
-            return false;
+        text_bounds: Bounds<Pixels>,
+        cx: &mut ViewContext<Editor>,
+    ) {
+        if !text_bounds.contains(&event.position) {
+            return;
         }
-        let point_for_position = position_map.point_for_position(text_bounds, position);
+        let point_for_position = position_map.point_for_position(text_bounds, event.position);
         mouse_context_menu::deploy_context_menu(
             editor,
-            position,
+            event.position,
             point_for_position.previous_valid,
             cx,
         );
-        true
+        cx.stop_propagation();
     }
 
     fn mouse_up(
         editor: &mut Editor,
-        position: Vector2F,
-        cmd: bool,
-        shift: bool,
-        alt: bool,
+        event: &MouseUpEvent,
         position_map: &PositionMap,
-        text_bounds: RectF,
-        cx: &mut EventContext<Editor>,
-    ) -> bool {
+        text_bounds: Bounds<Pixels>,
+        stacking_order: &StackingOrder,
+        cx: &mut ViewContext<Editor>,
+    ) {
         let end_selection = editor.has_pending_selection();
         let pending_nonempty_selections = editor.has_pending_nonempty_selection();
 
@@ -355,118 +463,99 @@ impl EditorElement {
             editor.select(SelectPhase::End, cx);
         }
 
-        if !pending_nonempty_selections && cmd && text_bounds.contains_point(position) {
-            let point = position_map.point_for_position(text_bounds, position);
+        if !pending_nonempty_selections
+            && event.modifiers.command
+            && text_bounds.contains(&event.position)
+            && cx.was_top_layer(&event.position, stacking_order)
+        {
+            let point = position_map.point_for_position(text_bounds, event.position);
             let could_be_inlay = point.as_valid().is_none();
-            if shift || could_be_inlay {
-                go_to_fetched_type_definition(editor, point, alt, cx);
+            let split = event.modifiers.alt;
+            if event.modifiers.shift || could_be_inlay {
+                go_to_fetched_type_definition(editor, point, split, cx);
             } else {
-                go_to_fetched_definition(editor, point, alt, cx);
+                go_to_fetched_definition(editor, point, split, cx);
             }
 
-            return true;
+            cx.stop_propagation();
+        } else if end_selection {
+            cx.stop_propagation();
         }
-
-        end_selection
     }
 
     fn mouse_dragged(
         editor: &mut Editor,
-        MouseMovedEvent {
-            modifiers: Modifiers { cmd, shift, .. },
-            position,
-            ..
-        }: MouseMovedEvent,
+        event: &MouseMoveEvent,
         position_map: &PositionMap,
-        text_bounds: RectF,
-        cx: &mut EventContext<Editor>,
-    ) -> bool {
-        // This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed
-        // Don't trigger hover popover if mouse is hovering over context menu
-        let point = if text_bounds.contains_point(position) {
-            position_map
-                .point_for_position(text_bounds, position)
-                .as_valid()
-        } else {
-            None
-        };
-
-        update_go_to_definition_link(
-            editor,
-            point.map(GoToDefinitionTrigger::Text),
-            cmd,
-            shift,
-            cx,
-        );
-
-        if editor.has_pending_selection() {
-            let mut scroll_delta = Vector2F::zero();
-
-            let vertical_margin = position_map.line_height.min(text_bounds.height() / 3.0);
-            let top = text_bounds.origin_y() + vertical_margin;
-            let bottom = text_bounds.lower_left().y() - vertical_margin;
-            if position.y() < top {
-                scroll_delta.set_y(-scale_vertical_mouse_autoscroll_delta(top - position.y()))
-            }
-            if position.y() > bottom {
-                scroll_delta.set_y(scale_vertical_mouse_autoscroll_delta(position.y() - bottom))
-            }
-
-            let horizontal_margin = position_map.line_height.min(text_bounds.width() / 3.0);
-            let left = text_bounds.origin_x() + horizontal_margin;
-            let right = text_bounds.upper_right().x() - horizontal_margin;
-            if position.x() < left {
-                scroll_delta.set_x(-scale_horizontal_mouse_autoscroll_delta(
-                    left - position.x(),
-                ))
-            }
-            if position.x() > right {
-                scroll_delta.set_x(scale_horizontal_mouse_autoscroll_delta(
-                    position.x() - right,
-                ))
-            }
+        text_bounds: Bounds<Pixels>,
+        _gutter_bounds: Bounds<Pixels>,
+        _stacking_order: &StackingOrder,
+        cx: &mut ViewContext<Editor>,
+    ) {
+        if !editor.has_pending_selection() {
+            return;
+        }
 
-            let point_for_position = position_map.point_for_position(text_bounds, position);
+        let point_for_position = position_map.point_for_position(text_bounds, event.position);
+        let mut scroll_delta = gpui::Point::<f32>::default();
+        let vertical_margin = position_map.line_height.min(text_bounds.size.height / 3.0);
+        let top = text_bounds.origin.y + vertical_margin;
+        let bottom = text_bounds.lower_left().y - vertical_margin;
+        if event.position.y < top {
+            scroll_delta.y = -scale_vertical_mouse_autoscroll_delta(top - event.position.y);
+        }
+        if event.position.y > bottom {
+            scroll_delta.y = scale_vertical_mouse_autoscroll_delta(event.position.y - bottom);
+        }
 
-            editor.select(
-                SelectPhase::Update {
-                    position: point_for_position.previous_valid,
-                    goal_column: point_for_position.exact_unclipped.column(),
-                    scroll_position: (position_map.snapshot.scroll_position() + scroll_delta)
-                        .clamp(Vector2F::zero(), position_map.scroll_max),
-                },
-                cx,
-            );
-            hover_at(editor, point, cx);
-            true
-        } else {
-            hover_at(editor, point, cx);
-            false
+        let horizontal_margin = position_map.line_height.min(text_bounds.size.width / 3.0);
+        let left = text_bounds.origin.x + horizontal_margin;
+        let right = text_bounds.upper_right().x - horizontal_margin;
+        if event.position.x < left {
+            scroll_delta.x = -scale_horizontal_mouse_autoscroll_delta(left - event.position.x);
         }
+        if event.position.x > right {
+            scroll_delta.x = scale_horizontal_mouse_autoscroll_delta(event.position.x - right);
+        }
+
+        editor.select(
+            SelectPhase::Update {
+                position: point_for_position.previous_valid,
+                goal_column: point_for_position.exact_unclipped.column(),
+                scroll_position: (position_map.snapshot.scroll_position() + scroll_delta)
+                    .clamp(&gpui::Point::default(), &position_map.scroll_max),
+            },
+            cx,
+        );
     }
 
     fn mouse_moved(
         editor: &mut Editor,
-        MouseMovedEvent {
-            modifiers: Modifiers { shift, cmd, .. },
-            position,
-            ..
-        }: MouseMovedEvent,
+        event: &MouseMoveEvent,
         position_map: &PositionMap,
-        text_bounds: RectF,
+        text_bounds: Bounds<Pixels>,
+        gutter_bounds: Bounds<Pixels>,
+        stacking_order: &StackingOrder,
         cx: &mut ViewContext<Editor>,
-    ) -> bool {
-        // This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed
+    ) {
+        let modifiers = event.modifiers;
+        let text_hovered = text_bounds.contains(&event.position);
+        let gutter_hovered = gutter_bounds.contains(&event.position);
+        let was_top = cx.was_top_layer(&event.position, stacking_order);
+
+        editor.set_gutter_hovered(gutter_hovered, cx);
+
         // Don't trigger hover popover if mouse is hovering over context menu
-        if text_bounds.contains_point(position) {
-            let point_for_position = position_map.point_for_position(text_bounds, position);
+        if text_hovered && was_top {
+            let point_for_position = position_map.point_for_position(text_bounds, event.position);
+
             match point_for_position.as_valid() {
                 Some(point) => {
                     update_go_to_definition_link(
                         editor,
                         Some(GoToDefinitionTrigger::Text(point)),
-                        cmd,
-                        shift,
+                        modifiers.command,
+                        modifiers.shift,
                         cx,
                     );
                     hover_at(editor, Some(point), cx);
@@ -476,76 +565,69 @@ impl EditorElement {
                         &position_map.snapshot,
                         point_for_position,
                         editor,
-                        cmd,
-                        shift,
+                        modifiers.command,
+                        modifiers.shift,
                         cx,
                     );
                 }
             }
         } else {
-            update_go_to_definition_link(editor, None, cmd, shift, cx);
+            update_go_to_definition_link(editor, None, modifiers.command, modifiers.shift, cx);
             hover_at(editor, None, cx);
+            if gutter_hovered && was_top {
+                cx.stop_propagation();
+            }
         }
-
-        true
     }
 
     fn scroll(
         editor: &mut Editor,
-        position: Vector2F,
-        mut delta: Vector2F,
-        precise: bool,
+        event: &ScrollWheelEvent,
         position_map: &PositionMap,
-        bounds: RectF,
+        bounds: &InteractiveBounds,
         cx: &mut ViewContext<Editor>,
-    ) -> bool {
-        if !bounds.contains_point(position) {
-            return false;
+    ) {
+        if !bounds.visibly_contains(&event.position, cx) {
+            return;
         }
 
         let line_height = position_map.line_height;
         let max_glyph_width = position_map.em_width;
+        let (delta, axis) = match event.delta {
+            gpui::ScrollDelta::Pixels(mut pixels) => {
+                //Trackpad
+                let axis = position_map.snapshot.ongoing_scroll.filter(&mut pixels);
+                (pixels, axis)
+            }
 
-        let axis = if precise {
-            //Trackpad
-            position_map.snapshot.ongoing_scroll.filter(&mut delta)
-        } else {
-            //Not trackpad
-            delta *= vec2f(max_glyph_width, line_height);
-            None //Resets ongoing scroll
+            gpui::ScrollDelta::Lines(lines) => {
+                //Not trackpad
+                let pixels = point(lines.x * max_glyph_width, lines.y * line_height);
+                (pixels, None)
+            }
         };
 
         let scroll_position = position_map.snapshot.scroll_position();
-        let x = (scroll_position.x() * max_glyph_width - delta.x()) / max_glyph_width;
-        let y = (scroll_position.y() * line_height - delta.y()) / line_height;
-        let scroll_position = vec2f(x, y).clamp(Vector2F::zero(), position_map.scroll_max);
+        let x = f32::from((scroll_position.x * max_glyph_width - delta.x) / max_glyph_width);
+        let y = f32::from((scroll_position.y * line_height - delta.y) / line_height);
+        let scroll_position = point(x, y).clamp(&point(0., 0.), &position_map.scroll_max);
         editor.scroll(scroll_position, axis, cx);
-
-        true
+        cx.stop_propagation();
     }
 
     fn paint_background(
         &self,
-        gutter_bounds: RectF,
-        text_bounds: RectF,
+        gutter_bounds: Bounds<Pixels>,
+        text_bounds: Bounds<Pixels>,
         layout: &LayoutState,
-        cx: &mut ViewContext<Editor>,
+        cx: &mut WindowContext,
     ) {
-        let bounds = gutter_bounds.union_rect(text_bounds);
+        let bounds = gutter_bounds.union(&text_bounds);
         let scroll_top =
-            layout.position_map.snapshot.scroll_position().y() * layout.position_map.line_height;
-        cx.scene().push_quad(Quad {
-            bounds: gutter_bounds,
-            background: Some(self.style.gutter_background),
-            border: Border::new(0., Color::transparent_black()).into(),
-            corner_radii: Default::default(),
-        });
-        cx.scene().push_quad(Quad {
-            bounds: text_bounds,
-            background: Some(self.style.background),
-            border: Border::new(0., Color::transparent_black()).into(),
-            corner_radii: Default::default(),
-        });
+            layout.position_map.snapshot.scroll_position().y * layout.position_map.line_height;
+        let gutter_bg = cx.theme().colors().editor_gutter_background;
+        cx.paint_quad(fill(gutter_bounds, gutter_bg));
+        cx.paint_quad(fill(text_bounds, self.style.background));
 
         if let EditorMode::Full = layout.mode {
             let mut active_rows = layout.active_rows.iter().peekable();
@@ -559,90 +641,77 @@ impl EditorElement {
                 }
 
                 if !contains_non_empty_selection {
-                    let origin = vec2f(
-                        bounds.origin_x(),
-                        bounds.origin_y() + (layout.position_map.line_height * *start_row as f32)
+                    let origin = point(
+                        bounds.origin.x,
+                        bounds.origin.y + (layout.position_map.line_height * *start_row as f32)
                             - scroll_top,
                     );
-                    let size = vec2f(
-                        bounds.width(),
+                    let size = size(
+                        bounds.size.width,
                         layout.position_map.line_height * (end_row - start_row + 1) as f32,
                     );
-                    cx.scene().push_quad(Quad {
-                        bounds: RectF::new(origin, size),
-                        background: Some(self.style.active_line_background),
-                        border: Border::default().into(),
-                        corner_radii: Default::default(),
-                    });
+                    let active_line_bg = cx.theme().colors().editor_active_line_background;
+                    cx.paint_quad(fill(Bounds { origin, size }, active_line_bg));
                 }
             }
 
             if let Some(highlighted_rows) = &layout.highlighted_rows {
-                let origin = vec2f(
-                    bounds.origin_x(),
-                    bounds.origin_y()
+                let origin = point(
+                    bounds.origin.x,
+                    bounds.origin.y
                         + (layout.position_map.line_height * highlighted_rows.start as f32)
                         - scroll_top,
                 );
-                let size = vec2f(
-                    bounds.width(),
+                let size = size(
+                    bounds.size.width,
                     layout.position_map.line_height * highlighted_rows.len() as f32,
                 );
-                cx.scene().push_quad(Quad {
-                    bounds: RectF::new(origin, size),
-                    background: Some(self.style.highlighted_line_background),
-                    border: Border::default().into(),
-                    corner_radii: Default::default(),
-                });
+                let highlighted_line_bg = cx.theme().colors().editor_highlighted_line_background;
+                cx.paint_quad(fill(Bounds { origin, size }, highlighted_line_bg));
             }
 
             let scroll_left =
-                layout.position_map.snapshot.scroll_position().x() * layout.position_map.em_width;
+                layout.position_map.snapshot.scroll_position().x * layout.position_map.em_width;
 
             for (wrap_position, active) in layout.wrap_guides.iter() {
-                let x =
-                    (text_bounds.origin_x() + wrap_position + layout.position_map.em_width / 2.)
-                        - scroll_left;
+                let x = (text_bounds.origin.x + *wrap_position + layout.position_map.em_width / 2.)
+                    - scroll_left;
 
-                if x < text_bounds.origin_x()
+                if x < text_bounds.origin.x
                     || (layout.show_scrollbars && x > self.scrollbar_left(&bounds))
                 {
                     continue;
                 }
 
                 let color = if *active {
-                    self.style.active_wrap_guide
+                    cx.theme().colors().editor_active_wrap_guide
                 } else {
-                    self.style.wrap_guide
+                    cx.theme().colors().editor_wrap_guide
                 };
-                cx.scene().push_quad(Quad {
-                    bounds: RectF::new(
-                        vec2f(x, text_bounds.origin_y()),
-                        vec2f(1., text_bounds.height()),
-                    ),
-                    background: Some(color),
-                    border: Border::new(0., Color::transparent_black()).into(),
-                    corner_radii: Default::default(),
-                });
+                cx.paint_quad(fill(
+                    Bounds {
+                        origin: point(x, text_bounds.origin.y),
+                        size: size(px(1.), text_bounds.size.height),
+                    },
+                    color,
+                ));
             }
         }
     }
 
     fn paint_gutter(
         &mut self,
-        bounds: RectF,
-        visible_bounds: RectF,
+        bounds: Bounds<Pixels>,
         layout: &mut LayoutState,
-        editor: &mut Editor,
-        cx: &mut ViewContext<Editor>,
+        cx: &mut WindowContext,
     ) {
         let line_height = layout.position_map.line_height;
 
         let scroll_position = layout.position_map.snapshot.scroll_position();
-        let scroll_top = scroll_position.y() * line_height;
+        let scroll_top = scroll_position.y * line_height;
 
         let show_gutter = matches!(
-            settings::get::<ProjectSettings>(cx).git.git_gutter,
+            ProjectSettings::get_global(cx).git.git_gutter,
             Some(GitGutterSetting::TrackedFiles)
         );
 

crates/editor/src/git.rs 🔗

@@ -60,8 +60,8 @@ pub fn diff_hunk_to_display(hunk: DiffHunk<u32>, snapshot: &DisplaySnapshot) ->
     let folds_end = Point::new(hunk.buffer_range.end + 2, 0);
     let folds_range = folds_start..folds_end;
 
-    let containing_fold = snapshot.folds_in_range(folds_range).find(|fold_range| {
-        let fold_point_range = fold_range.to_point(&snapshot.buffer_snapshot);
+    let containing_fold = snapshot.folds_in_range(folds_range).find(|fold| {
+        let fold_point_range = fold.range.to_point(&snapshot.buffer_snapshot);
         let fold_point_range = fold_point_range.start..=fold_point_range.end;
 
         let folded_start = fold_point_range.contains(&hunk_start_point);
@@ -72,7 +72,7 @@ pub fn diff_hunk_to_display(hunk: DiffHunk<u32>, snapshot: &DisplaySnapshot) ->
     });
 
     if let Some(fold) = containing_fold {
-        let row = fold.start.to_display_point(snapshot).row();
+        let row = fold.range.start.to_display_point(snapshot).row();
         DisplayDiffHunk::Folded { display_row: row }
     } else {
         let start = hunk_start_point.to_display_point(snapshot).row();
@@ -88,11 +88,11 @@ pub fn diff_hunk_to_display(hunk: DiffHunk<u32>, snapshot: &DisplaySnapshot) ->
     }
 }
 
-#[cfg(any(test, feature = "test_support"))]
+#[cfg(test)]
 mod tests {
     use crate::editor_tests::init_test;
     use crate::Point;
-    use gpui::TestAppContext;
+    use gpui::{Context, TestAppContext};
     use multi_buffer::{ExcerptRange, MultiBuffer};
     use project::{FakeFs, Project};
     use unindent::Unindent;
@@ -101,7 +101,7 @@ mod tests {
         use git::diff::DiffHunkStatus;
         init_test(cx, |_| {});
 
-        let fs = FakeFs::new(cx.background());
+        let fs = FakeFs::new(cx.background_executor.clone());
         let project = Project::test(fs, [], cx).await;
 
         // buffer has two modified hunks with two rows each
@@ -180,9 +180,9 @@ mod tests {
             );
         });
 
-        cx.foreground().run_until_parked();
+        cx.background_executor.run_until_parked();
 
-        let multibuffer = cx.add_model(|cx| {
+        let multibuffer = cx.new_model(|cx| {
             let mut multibuffer = MultiBuffer::new(0);
             multibuffer.push_excerpts(
                 buffer_1.clone(),

crates/editor/src/highlight_matching_bracket.rs 🔗

@@ -24,7 +24,7 @@ pub fn refresh_matching_bracket_highlights(editor: &mut Editor, cx: &mut ViewCon
                 opening_range.to_anchors(&snapshot.buffer_snapshot),
                 closing_range.to_anchors(&snapshot.buffer_snapshot),
             ],
-            |theme| theme.editor.document_highlight_read_background,
+            |theme| theme.editor_document_highlight_read_background,
             cx,
         )
     }

crates/editor/src/hover_popover.rs 🔗

@@ -6,16 +6,17 @@ use crate::{
 };
 use futures::FutureExt;
 use gpui::{
-    actions,
-    elements::{Flex, MouseEventHandler, Padding, ParentElement, Text},
-    platform::{CursorStyle, MouseButton},
-    AnyElement, AppContext, Element, ModelHandle, Task, ViewContext, WeakViewHandle,
-};
-use language::{
-    markdown, Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry, ParsedMarkdown,
+    actions, div, px, AnyElement, CursorStyle, Hsla, InteractiveElement, IntoElement, Model,
+    MouseButton, ParentElement, Pixels, SharedString, Size, StatefulInteractiveElement, Styled,
+    Task, ViewContext, WeakView,
 };
+use language::{markdown, Bias, DiagnosticEntry, Language, LanguageRegistry, ParsedMarkdown};
+
+use lsp::DiagnosticSeverity;
 use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project};
+use settings::Settings;
 use std::{ops::Range, sync::Arc, time::Duration};
+use ui::{StyledExt, Tooltip};
 use util::TryFutureExt;
 use workspace::Workspace;
 
@@ -23,15 +24,11 @@ pub const HOVER_DELAY_MILLIS: u64 = 350;
 pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200;
 
 pub const MIN_POPOVER_CHARACTER_WIDTH: f32 = 20.;
-pub const MIN_POPOVER_LINE_HEIGHT: f32 = 4.;
-pub const HOVER_POPOVER_GAP: f32 = 10.;
+pub const MIN_POPOVER_LINE_HEIGHT: Pixels = px(4.);
+pub const HOVER_POPOVER_GAP: Pixels = px(10.);
 
 actions!(editor, [Hover]);
 
-pub fn init(cx: &mut AppContext) {
-    cx.add_action(hover);
-}
-
 /// Bindable action which uses the most recent selection head to trigger a hover
 pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext<Editor>) {
     let head = editor.selections.newest_display(cx).head();
@@ -41,7 +38,7 @@ pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext<Editor>) {
 /// The internal hover action dispatches between `show_hover` or `hide_hover`
 /// depending on whether a point to hover over is provided.
 pub fn hover_at(editor: &mut Editor, point: Option<DisplayPoint>, cx: &mut ViewContext<Editor>) {
-    if settings::get::<EditorSettings>(cx).hover_popover_enabled {
+    if EditorSettings::get_global(cx).hover_popover_enabled {
         if let Some(point) = point {
             show_hover(editor, point, false, cx);
         } else {
@@ -79,7 +76,7 @@ pub fn find_hovered_hint_part(
 }
 
 pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut ViewContext<Editor>) {
-    if settings::get::<EditorSettings>(cx).hover_popover_enabled {
+    if EditorSettings::get_global(cx).hover_popover_enabled {
         if editor.pending_rename.is_some() {
             return;
         }
@@ -100,14 +97,14 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie
 
         let task = cx.spawn(|this, mut cx| {
             async move {
-                cx.background()
+                cx.background_executor()
                     .timer(Duration::from_millis(HOVER_DELAY_MILLIS))
                     .await;
                 this.update(&mut cx, |this, _| {
                     this.hover_state.diagnostic_popover = None;
                 })?;
 
-                let language_registry = project.update(&mut cx, |p, _| p.languages().clone());
+                let language_registry = project.update(&mut cx, |p, _| p.languages().clone())?;
                 let blocks = vec![inlay_hover.tooltip];
                 let parsed_content = parse_blocks(&blocks, &language_registry, None).await;
 
@@ -122,7 +119,7 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie
                     // Highlight the selected symbol using a background highlight
                     this.highlight_inlay_background::<HoverState>(
                         vec![inlay_hover.range],
-                        |theme| theme.editor.hover_popover.highlight,
+                        |theme| theme.element_hover, // todo!("use a proper background here")
                         cx,
                     );
                     this.hover_state.info_popover = Some(hover_popover);
@@ -239,11 +236,11 @@ fn show_hover(
             let delay = if !ignore_timeout {
                 // Construct delay task to wait for later
                 let total_delay = Some(
-                    cx.background()
+                    cx.background_executor()
                         .timer(Duration::from_millis(HOVER_DELAY_MILLIS)),
                 );
 
-                cx.background()
+                cx.background_executor()
                     .timer(Duration::from_millis(HOVER_REQUEST_DELAY_MILLIS))
                     .await;
                 total_delay
@@ -252,11 +249,11 @@ fn show_hover(
             };
 
             // query the LSP for hover info
-            let hover_request = cx.update(|cx| {
+            let hover_request = cx.update(|_, cx| {
                 project.update(cx, |project, cx| {
                     project.hover(&buffer, buffer_position, cx)
                 })
-            });
+            })?;
 
             if let Some(delay) = delay {
                 delay.await;
@@ -310,7 +307,8 @@ fn show_hover(
                         anchor..anchor
                     };
 
-                    let language_registry = project.update(&mut cx, |p, _| p.languages().clone());
+                    let language_registry =
+                        project.update(&mut cx, |p, _| p.languages().clone())?;
                     let blocks = hover_result.contents;
                     let language = hover_result.language;
                     let parsed_content = parse_blocks(&blocks, &language_registry, language).await;
@@ -334,7 +332,7 @@ fn show_hover(
                     // Highlight the selected symbol using a background highlight
                     this.highlight_background::<HoverState>(
                         vec![symbol_range],
-                        |theme| theme.editor.hover_popover.highlight,
+                        |theme| theme.element_hover, // todo! update theme
                         cx,
                     );
                 } else {
@@ -423,9 +421,10 @@ impl HoverState {
         snapshot: &EditorSnapshot,
         style: &EditorStyle,
         visible_rows: Range<u32>,
-        workspace: Option<WeakViewHandle<Workspace>>,
+        max_size: Size<Pixels>,
+        workspace: Option<WeakView<Workspace>>,
         cx: &mut ViewContext<Editor>,
-    ) -> Option<(DisplayPoint, Vec<AnyElement<Editor>>)> {
+    ) -> Option<(DisplayPoint, Vec<AnyElement>)> {
         // If there is a diagnostic, position the popovers based on that.
         // Otherwise use the start of the hover range
         let anchor = self
@@ -450,10 +449,10 @@ impl HoverState {
         let mut elements = Vec::new();
 
         if let Some(diagnostic_popover) = self.diagnostic_popover.as_ref() {
-            elements.push(diagnostic_popover.render(style, cx));
+            elements.push(diagnostic_popover.render(style, max_size, cx));
         }
         if let Some(info_popover) = self.info_popover.as_mut() {
-            elements.push(info_popover.render(style, workspace, cx));
+            elements.push(info_popover.render(style, max_size, workspace, cx));
         }
 
         Some((point, elements))
@@ -462,7 +461,7 @@ impl HoverState {
 
 #[derive(Debug, Clone)]
 pub struct InfoPopover {
-    pub project: ModelHandle<Project>,
+    pub project: Model<Project>,
     symbol_range: RangeInEditor,
     pub blocks: Vec<HoverBlock>,
     parsed_content: ParsedMarkdown,
@@ -472,29 +471,28 @@ impl InfoPopover {
     pub fn render(
         &mut self,
         style: &EditorStyle,
-        workspace: Option<WeakViewHandle<Workspace>>,
+        max_size: Size<Pixels>,
+        workspace: Option<WeakView<Workspace>>,
         cx: &mut ViewContext<Editor>,
-    ) -> AnyElement<Editor> {
-        MouseEventHandler::new::<InfoPopover, _>(0, cx, |_, cx| {
-            Flex::column()
-                .scrollable::<HoverBlock>(0, None, cx)
-                .with_child(crate::render_parsed_markdown::<HoverBlock>(
-                    &self.parsed_content,
-                    style,
-                    workspace,
-                    cx,
-                ))
-                .contained()
-                .with_style(style.hover_popover.container)
-        })
-        .on_move(|_, _, _| {}) // Consume move events so they don't reach regions underneath.
-        .with_cursor_style(CursorStyle::Arrow)
-        .with_padding(Padding {
-            bottom: HOVER_POPOVER_GAP,
-            top: HOVER_POPOVER_GAP,
-            ..Default::default()
-        })
-        .into_any()
+    ) -> AnyElement {
+        div()
+            .id("info_popover")
+            .elevation_2(cx)
+            .p_2()
+            .overflow_y_scroll()
+            .max_w(max_size.width)
+            .max_h(max_size.height)
+            // Prevent a mouse move on the popover from being propagated to the editor,
+            // because that would dismiss the popover.
+            .on_mouse_move(|_, cx| cx.stop_propagation())
+            .child(crate::render_parsed_markdown(
+                "content",
+                &self.parsed_content,
+                style,
+                workspace,
+                cx,
+            ))
+            .into_any_element()
     }
 }
 
@@ -505,56 +503,74 @@ pub struct DiagnosticPopover {
 }
 
 impl DiagnosticPopover {
-    pub fn render(&self, style: &EditorStyle, cx: &mut ViewContext<Editor>) -> AnyElement<Editor> {
-        enum PrimaryDiagnostic {}
-
-        let mut text_style = style.hover_popover.prose.clone();
-        text_style.font_size = style.text.font_size;
-        let diagnostic_source_style = style.hover_popover.diagnostic_source_highlight.clone();
-
+    pub fn render(
+        &self,
+        style: &EditorStyle,
+        max_size: Size<Pixels>,
+        cx: &mut ViewContext<Editor>,
+    ) -> AnyElement {
         let text = match &self.local_diagnostic.diagnostic.source {
-            Some(source) => Text::new(
-                format!("{source}: {}", self.local_diagnostic.diagnostic.message),
-                text_style,
-            )
-            .with_highlights(vec![(0..source.len(), diagnostic_source_style)]),
-
-            None => Text::new(self.local_diagnostic.diagnostic.message.clone(), text_style),
+            Some(source) => format!("{source}: {}", self.local_diagnostic.diagnostic.message),
+            None => self.local_diagnostic.diagnostic.message.clone(),
         };
 
-        let container_style = match self.local_diagnostic.diagnostic.severity {
-            DiagnosticSeverity::HINT => style.hover_popover.info_container,
-            DiagnosticSeverity::INFORMATION => style.hover_popover.info_container,
-            DiagnosticSeverity::WARNING => style.hover_popover.warning_container,
-            DiagnosticSeverity::ERROR => style.hover_popover.error_container,
-            _ => style.hover_popover.container,
-        };
+        struct DiagnosticColors {
+            pub text: Hsla,
+            pub background: Hsla,
+            pub border: Hsla,
+        }
 
-        let tooltip_style = theme::current(cx).tooltip.clone();
+        let diagnostic_colors = match self.local_diagnostic.diagnostic.severity {
+            DiagnosticSeverity::ERROR => DiagnosticColors {
+                text: style.status.error,
+                background: style.status.error_background,
+                border: style.status.error_border,
+            },
+            DiagnosticSeverity::WARNING => DiagnosticColors {
+                text: style.status.warning,
+                background: style.status.warning_background,
+                border: style.status.warning_border,
+            },
+            DiagnosticSeverity::INFORMATION => DiagnosticColors {
+                text: style.status.info,
+                background: style.status.info_background,
+                border: style.status.info_border,
+            },
+            DiagnosticSeverity::HINT => DiagnosticColors {
+                text: style.status.hint,
+                background: style.status.hint_background,
+                border: style.status.hint_border,
+            },
+            _ => DiagnosticColors {
+                text: style.status.ignored,
+                background: style.status.ignored_background,
+                border: style.status.ignored_border,
+            },
+        };
 
-        MouseEventHandler::new::<DiagnosticPopover, _>(0, cx, |_, _| {
-            text.with_soft_wrap(true)
-                .contained()
-                .with_style(container_style)
-        })
-        .with_padding(Padding {
-            top: HOVER_POPOVER_GAP,
-            bottom: HOVER_POPOVER_GAP,
-            ..Default::default()
-        })
-        .on_move(|_, _, _| {}) // Consume move events so they don't reach regions underneath.
-        .on_click(MouseButton::Left, |_, this, cx| {
-            this.go_to_diagnostic(&Default::default(), cx)
-        })
-        .with_cursor_style(CursorStyle::PointingHand)
-        .with_tooltip::<PrimaryDiagnostic>(
-            0,
-            "Go To Diagnostic".to_string(),
-            Some(Box::new(crate::GoToDiagnostic)),
-            tooltip_style,
-            cx,
-        )
-        .into_any()
+        div()
+            .id("diagnostic")
+            .overflow_y_scroll()
+            .px_2()
+            .py_1()
+            .bg(diagnostic_colors.background)
+            .text_color(diagnostic_colors.text)
+            .border_1()
+            .border_color(diagnostic_colors.border)
+            .rounded_md()
+            .max_w(max_size.width)
+            .max_h(max_size.height)
+            .cursor(CursorStyle::PointingHand)
+            .tooltip(move |cx| Tooltip::for_action("Go To Diagnostic", &crate::GoToDiagnostic, cx))
+            // Prevent a mouse move on the popover from being propagated to the editor,
+            // because that would dismiss the popover.
+            .on_mouse_move(|_, cx| cx.stop_propagation())
+            // Prevent a mouse down on the popover from being propagated to the editor,
+            // because that would move the cursor.
+            .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
+            .on_click(cx.listener(|editor, _, cx| editor.go_to_diagnostic(&Default::default(), cx)))
+            .child(SharedString::from(text))
+            .into_any_element()
     }
 
     pub fn activation_info(&self) -> (usize, Anchor) {
@@ -579,7 +595,7 @@ mod tests {
         InlayId,
     };
     use collections::BTreeSet;
-    use gpui::fonts::{HighlightStyle, Underline, Weight};
+    use gpui::{FontWeight, HighlightStyle, UnderlineStyle};
     use indoc::indoc;
     use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet};
     use lsp::LanguageServerId;
@@ -626,7 +642,7 @@ mod tests {
                     range: Some(symbol_range),
                 }))
             });
-        cx.foreground()
+        cx.background_executor
             .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
         requests.next().await;
 
@@ -649,7 +665,7 @@ mod tests {
             .lsp
             .handle_request::<lsp::request::HoverRequest, _, _>(|_, _| async move { Ok(None) });
         cx.update_editor(|editor, cx| hover_at(editor, Some(hover_point), cx));
-        cx.foreground()
+        cx.background_executor
             .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
         request.next().await;
         cx.editor(|editor, _| {
@@ -853,7 +869,7 @@ mod tests {
 
         // Hover pops diagnostic immediately
         cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
-        cx.foreground().run_until_parked();
+        cx.background_executor.run_until_parked();
 
         cx.editor(|Editor { hover_state, .. }, _| {
             assert!(hover_state.diagnostic_popover.is_some() && hover_state.info_popover.is_none())
@@ -872,10 +888,10 @@ mod tests {
                 range: Some(range),
             }))
         });
-        cx.foreground()
+        cx.background_executor
             .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
 
-        cx.foreground().run_until_parked();
+        cx.background_executor.run_until_parked();
         cx.editor(|Editor { hover_state, .. }, _| {
             hover_state.diagnostic_popover.is_some() && hover_state.info_task.is_some()
         });
@@ -885,48 +901,49 @@ mod tests {
     fn test_render_blocks(cx: &mut gpui::TestAppContext) {
         init_test(cx, |_| {});
 
-        cx.add_window(|cx| {
-            let editor = Editor::single_line(None, cx);
-            let style = editor.style(cx);
+        let editor = cx.add_window(|cx| Editor::single_line(cx));
+        editor
+            .update(cx, |editor, _cx| {
+                let style = editor.style.clone().unwrap();
 
-            struct Row {
-                blocks: Vec<HoverBlock>,
-                expected_marked_text: String,
-                expected_styles: Vec<HighlightStyle>,
-            }
+                struct Row {
+                    blocks: Vec<HoverBlock>,
+                    expected_marked_text: String,
+                    expected_styles: Vec<HighlightStyle>,
+                }
 
-            let rows = &[
-                // Strong emphasis
-                Row {
-                    blocks: vec![HoverBlock {
-                        text: "one **two** three".to_string(),
-                        kind: HoverBlockKind::Markdown,
-                    }],
-                    expected_marked_text: "one «two» three".to_string(),
-                    expected_styles: vec![HighlightStyle {
-                        weight: Some(Weight::BOLD),
-                        ..Default::default()
-                    }],
-                },
-                // Links
-                Row {
-                    blocks: vec![HoverBlock {
-                        text: "one [two](https://the-url) three".to_string(),
-                        kind: HoverBlockKind::Markdown,
-                    }],
-                    expected_marked_text: "one «two» three".to_string(),
-                    expected_styles: vec![HighlightStyle {
-                        underline: Some(Underline {
-                            thickness: 1.0.into(),
+                let rows = &[
+                    // Strong emphasis
+                    Row {
+                        blocks: vec![HoverBlock {
+                            text: "one **two** three".to_string(),
+                            kind: HoverBlockKind::Markdown,
+                        }],
+                        expected_marked_text: "one «two» three".to_string(),
+                        expected_styles: vec![HighlightStyle {
+                            font_weight: Some(FontWeight::BOLD),
                             ..Default::default()
-                        }),
-                        ..Default::default()
-                    }],
-                },
-                // Lists
-                Row {
-                    blocks: vec![HoverBlock {
-                        text: "
+                        }],
+                    },
+                    // Links
+                    Row {
+                        blocks: vec![HoverBlock {
+                            text: "one [two](https://the-url) three".to_string(),
+                            kind: HoverBlockKind::Markdown,
+                        }],
+                        expected_marked_text: "one «two» three".to_string(),
+                        expected_styles: vec![HighlightStyle {
+                            underline: Some(UnderlineStyle {
+                                thickness: 1.0.into(),
+                                ..Default::default()
+                            }),
+                            ..Default::default()
+                        }],
+                    },
+                    // Lists
+                    Row {
+                        blocks: vec![HoverBlock {
+                            text: "
                             lists:
                             * one
                                 - a
@@ -934,10 +951,10 @@ mod tests {
                             * two
                                 - [c](https://the-url)
                                 - d"
-                        .unindent(),
-                        kind: HoverBlockKind::Markdown,
-                    }],
-                    expected_marked_text: "
+                            .unindent(),
+                            kind: HoverBlockKind::Markdown,
+                        }],
+                        expected_marked_text: "
                         lists:
                         - one
                           - a
@@ -945,19 +962,19 @@ mod tests {
                         - two
                           - «c»
                           - d"
-                    .unindent(),
-                    expected_styles: vec![HighlightStyle {
-                        underline: Some(Underline {
-                            thickness: 1.0.into(),
+                        .unindent(),
+                        expected_styles: vec![HighlightStyle {
+                            underline: Some(UnderlineStyle {
+                                thickness: 1.0.into(),
+                                ..Default::default()
+                            }),
                             ..Default::default()
-                        }),
-                        ..Default::default()
-                    }],
-                },
-                // Multi-paragraph list items
-                Row {
-                    blocks: vec![HoverBlock {
-                        text: "
+                        }],
+                    },
+                    // Multi-paragraph list items
+                    Row {
+                        blocks: vec![HoverBlock {
+                            text: "
                             * one two
                               three
 
@@ -968,10 +985,10 @@ mod tests {
                                   nine
                                 * ten
                             * six"
-                            .unindent(),
-                        kind: HoverBlockKind::Markdown,
-                    }],
-                    expected_marked_text: "
+                                .unindent(),
+                            kind: HoverBlockKind::Markdown,
+                        }],
+                        expected_marked_text: "
                         - one two three
                         - four five
                           - six seven eight
@@ -979,52 +996,51 @@ mod tests {
                             nine
                           - ten
                         - six"
-                        .unindent(),
-                    expected_styles: vec![HighlightStyle {
-                        underline: Some(Underline {
-                            thickness: 1.0.into(),
+                            .unindent(),
+                        expected_styles: vec![HighlightStyle {
+                            underline: Some(UnderlineStyle {
+                                thickness: 1.0.into(),
+                                ..Default::default()
+                            }),
                             ..Default::default()
-                        }),
-                        ..Default::default()
-                    }],
-                },
-            ];
-
-            for Row {
-                blocks,
-                expected_marked_text,
-                expected_styles,
-            } in &rows[0..]
-            {
-                let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None));
-
-                let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false);
-                let expected_highlights = ranges
-                    .into_iter()
-                    .zip(expected_styles.iter().cloned())
-                    .collect::<Vec<_>>();
-                assert_eq!(
-                    rendered.text, expected_text,
-                    "wrong text for input {blocks:?}"
-                );
-
-                let rendered_highlights: Vec<_> = rendered
-                    .highlights
-                    .iter()
-                    .filter_map(|(range, highlight)| {
-                        let highlight = highlight.to_highlight_style(&style.syntax)?;
-                        Some((range.clone(), highlight))
-                    })
-                    .collect();
+                        }],
+                    },
+                ];
 
-                assert_eq!(
-                    rendered_highlights, expected_highlights,
-                    "wrong highlights for input {blocks:?}"
-                );
-            }
+                for Row {
+                    blocks,
+                    expected_marked_text,
+                    expected_styles,
+                } in &rows[0..]
+                {
+                    let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None));
+
+                    let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false);
+                    let expected_highlights = ranges
+                        .into_iter()
+                        .zip(expected_styles.iter().cloned())
+                        .collect::<Vec<_>>();
+                    assert_eq!(
+                        rendered.text, expected_text,
+                        "wrong text for input {blocks:?}"
+                    );
 
-            editor
-        });
+                    let rendered_highlights: Vec<_> = rendered
+                        .highlights
+                        .iter()
+                        .filter_map(|(range, highlight)| {
+                            let highlight = highlight.to_highlight_style(&style.syntax)?;
+                            Some((range.clone(), highlight))
+                        })
+                        .collect();
+
+                    assert_eq!(
+                        rendered_highlights, expected_highlights,
+                        "wrong highlights for input {blocks:?}"
+                    );
+                }
+            })
+            .unwrap();
     }
 
     #[gpui::test]
@@ -1127,7 +1143,7 @@ mod tests {
             })
             .next()
             .await;
-        cx.foreground().run_until_parked();
+        cx.background_executor.run_until_parked();
         cx.update_editor(|editor, cx| {
             let expected_layers = vec![entire_hint_label.to_string()];
             assert_eq!(expected_layers, cached_hint_labels(editor));
@@ -1236,7 +1252,7 @@ mod tests {
             )
             .next()
             .await;
-        cx.foreground().run_until_parked();
+        cx.background_executor.run_until_parked();
 
         cx.update_editor(|editor, cx| {
             update_inlay_link_and_hover_points(
@@ -1248,9 +1264,9 @@ mod tests {
                 cx,
             );
         });
-        cx.foreground()
+        cx.background_executor
             .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
-        cx.foreground().run_until_parked();
+        cx.background_executor.run_until_parked();
         cx.update_editor(|editor, cx| {
             let hover_state = &editor.hover_state;
             assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some());
@@ -1301,9 +1317,9 @@ mod tests {
                 cx,
             );
         });
-        cx.foreground()
+        cx.background_executor
             .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
-        cx.foreground().run_until_parked();
+        cx.background_executor.run_until_parked();
         cx.update_editor(|editor, cx| {
             let hover_state = &editor.hover_state;
             assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some());

crates/editor/src/inlay_hint_cache.rs 🔗

@@ -11,7 +11,7 @@ use crate::{
 use anyhow::Context;
 use clock::Global;
 use futures::future;
-use gpui::{ModelContext, ModelHandle, Task, ViewContext};
+use gpui::{Model, ModelContext, Task, ViewContext};
 use language::{language_settings::InlayHintKind, Buffer, BufferSnapshot};
 use parking_lot::RwLock;
 use project::{InlayHint, ResolveState};
@@ -250,7 +250,7 @@ impl InlayHintCache {
 
     pub fn update_settings(
         &mut self,
-        multi_buffer: &ModelHandle<MultiBuffer>,
+        multi_buffer: &Model<MultiBuffer>,
         new_hint_settings: InlayHintSettings,
         visible_hints: Vec<Inlay>,
         cx: &mut ViewContext<Editor>,
@@ -302,7 +302,7 @@ impl InlayHintCache {
     pub fn spawn_hint_refresh(
         &mut self,
         reason: &'static str,
-        excerpts_to_query: HashMap<ExcerptId, (ModelHandle<Buffer>, Global, Range<usize>)>,
+        excerpts_to_query: HashMap<ExcerptId, (Model<Buffer>, Global, Range<usize>)>,
         invalidate: InvalidationStrategy,
         cx: &mut ViewContext<Editor>,
     ) -> Option<InlaySplice> {
@@ -355,7 +355,7 @@ impl InlayHintCache {
 
     fn new_allowed_hint_kinds_splice(
         &self,
-        multi_buffer: &ModelHandle<MultiBuffer>,
+        multi_buffer: &Model<MultiBuffer>,
         visible_hints: &[Inlay],
         new_kinds: &HashSet<Option<InlayHintKind>>,
         cx: &mut ViewContext<Editor>,
@@ -521,7 +521,7 @@ impl InlayHintCache {
         buffer_id: u64,
         excerpt_id: ExcerptId,
         id: InlayId,
-        cx: &mut ViewContext<'_, '_, Editor>,
+        cx: &mut ViewContext<'_, Editor>,
     ) {
         if let Some(excerpt_hints) = self.hints.get(&excerpt_id) {
             let mut guard = excerpt_hints.write();
@@ -579,10 +579,10 @@ impl InlayHintCache {
 fn spawn_new_update_tasks(
     editor: &mut Editor,
     reason: &'static str,
-    excerpts_to_query: HashMap<ExcerptId, (ModelHandle<Buffer>, Global, Range<usize>)>,
+    excerpts_to_query: HashMap<ExcerptId, (Model<Buffer>, Global, Range<usize>)>,
     invalidate: InvalidationStrategy,
     update_cache_version: usize,
-    cx: &mut ViewContext<'_, '_, Editor>,
+    cx: &mut ViewContext<'_, Editor>,
 ) {
     let visible_hints = Arc::new(editor.visible_inlay_hints(cx));
     for (excerpt_id, (excerpt_buffer, new_task_buffer_version, excerpt_visible_range)) in
@@ -684,7 +684,7 @@ impl QueryRanges {
 fn determine_query_ranges(
     multi_buffer: &mut MultiBuffer,
     excerpt_id: ExcerptId,
-    excerpt_buffer: &ModelHandle<Buffer>,
+    excerpt_buffer: &Model<Buffer>,
     excerpt_visible_range: Range<usize>,
     cx: &mut ModelContext<'_, MultiBuffer>,
 ) -> Option<QueryRanges> {
@@ -760,7 +760,7 @@ fn new_update_task(
     visible_hints: Arc<Vec<Inlay>>,
     cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
     lsp_request_limiter: Arc<Semaphore>,
-    cx: &mut ViewContext<'_, '_, Editor>,
+    cx: &mut ViewContext<'_, Editor>,
 ) -> Task<()> {
     cx.spawn(|editor, mut cx| async move {
         let closure_cx = cx.clone();
@@ -789,7 +789,7 @@ fn new_update_task(
         ))
         .await;
 
-        let hint_delay = cx.background().timer(Duration::from_millis(
+        let hint_delay = cx.background_executor().timer(Duration::from_millis(
             INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS,
         ));
 
@@ -837,7 +837,7 @@ fn new_update_task(
 }
 
 async fn fetch_and_update_hints(
-    editor: gpui::WeakViewHandle<Editor>,
+    editor: gpui::WeakView<Editor>,
     multi_buffer_snapshot: MultiBufferSnapshot,
     buffer_snapshot: BufferSnapshot,
     visible_hints: Arc<Vec<Inlay>>,
@@ -846,7 +846,7 @@ async fn fetch_and_update_hints(
     invalidate: bool,
     fetch_range: Range<language::Anchor>,
     lsp_request_limiter: Arc<Semaphore>,
-    mut cx: gpui::AsyncAppContext,
+    mut cx: gpui::AsyncWindowContext,
 ) -> anyhow::Result<()> {
     let (lsp_request_guard, got_throttled) = if query.invalidate.should_invalidate() {
         (None, false)
@@ -927,7 +927,7 @@ async fn fetch_and_update_hints(
     let background_task_buffer_snapshot = buffer_snapshot.clone();
     let backround_fetch_range = fetch_range.clone();
     let new_update = cx
-        .background()
+        .background_executor()
         .spawn(async move {
             calculate_hint_updates(
                 query.excerpt_id,
@@ -1071,7 +1071,7 @@ fn apply_hint_update(
     invalidate: bool,
     buffer_snapshot: BufferSnapshot,
     multi_buffer_snapshot: MultiBufferSnapshot,
-    cx: &mut ViewContext<'_, '_, Editor>,
+    cx: &mut ViewContext<'_, Editor>,
 ) {
     let cached_excerpt_hints = editor
         .inlay_hint_cache
@@ -1200,11 +1200,10 @@ pub mod tests {
 
     use crate::{
         scroll::{autoscroll::Autoscroll, scroll_amount::ScrollAmount},
-        serde_json::json,
         ExcerptRange,
     };
     use futures::StreamExt;
-    use gpui::{executor::Deterministic, TestAppContext, ViewHandle};
+    use gpui::{Context, TestAppContext, WindowHandle};
     use itertools::Itertools;
     use language::{
         language_settings::AllLanguageSettingsContent, FakeLspAdapter, Language, LanguageConfig,
@@ -1212,9 +1211,9 @@ pub mod tests {
     use lsp::FakeLanguageServer;
     use parking_lot::Mutex;
     use project::{FakeFs, Project};
+    use serde_json::json;
     use settings::SettingsStore;
     use text::{Point, ToPoint};
-    use workspace::Workspace;
 
     use crate::editor_tests::update_test_language_settings;
 
@@ -1270,10 +1269,10 @@ pub mod tests {
             })
             .next()
             .await;
-        cx.foreground().run_until_parked();
+        cx.executor().run_until_parked();
 
         let mut edits_made = 1;
-        editor.update(cx, |editor, cx| {
+        _ = editor.update(cx, |editor, cx| {
             let expected_hints = vec!["0".to_string()];
             assert_eq!(
                 expected_hints,
@@ -1292,13 +1291,13 @@ pub mod tests {
             );
         });
 
-        editor.update(cx, |editor, cx| {
+        _ = editor.update(cx, |editor, cx| {
             editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
             editor.handle_input("some change", cx);
             edits_made += 1;
         });
-        cx.foreground().run_until_parked();
-        editor.update(cx, |editor, cx| {
+        cx.executor().run_until_parked();
+        _ = editor.update(cx, |editor, cx| {
             let expected_hints = vec!["0".to_string(), "1".to_string()];
             assert_eq!(
                 expected_hints,
@@ -1322,8 +1321,8 @@ pub mod tests {
             .await
             .expect("inlay refresh request failed");
         edits_made += 1;
-        cx.foreground().run_until_parked();
-        editor.update(cx, |editor, cx| {
+        cx.executor().run_until_parked();
+        _ = editor.update(cx, |editor, cx| {
             let expected_hints = vec!["0".to_string(), "1".to_string(), "2".to_string()];
             assert_eq!(
                 expected_hints,
@@ -1380,10 +1379,10 @@ pub mod tests {
             })
             .next()
             .await;
-        cx.foreground().run_until_parked();
+        cx.executor().run_until_parked();
 
         let mut edits_made = 1;
-        editor.update(cx, |editor, cx| {
+        _ = editor.update(cx, |editor, cx| {
             let expected_hints = vec!["0".to_string()];
             assert_eq!(
                 expected_hints,
@@ -1405,16 +1404,16 @@ pub mod tests {
             })
             .await
             .expect("work done progress create request failed");
-        cx.foreground().run_until_parked();
+        cx.executor().run_until_parked();
         fake_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
             token: lsp::ProgressToken::String(progress_token.to_string()),
             value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Begin(
                 lsp::WorkDoneProgressBegin::default(),
             )),
         });
-        cx.foreground().run_until_parked();
+        cx.executor().run_until_parked();
 
-        editor.update(cx, |editor, cx| {
+        _ = editor.update(cx, |editor, cx| {
             let expected_hints = vec!["0".to_string()];
             assert_eq!(
                 expected_hints,
@@ -1435,10 +1434,10 @@ pub mod tests {
                 lsp::WorkDoneProgressEnd::default(),
             )),
         });
-        cx.foreground().run_until_parked();
+        cx.executor().run_until_parked();
 
         edits_made += 1;
-        editor.update(cx, |editor, cx| {
+        _ = editor.update(cx, |editor, cx| {
             let expected_hints = vec!["1".to_string()];
             assert_eq!(
                 expected_hints,
@@ -1465,7 +1464,7 @@ pub mod tests {
             })
         });
 
-        let fs = FakeFs::new(cx.background());
+        let fs = FakeFs::new(cx.background_executor.clone());
         fs.insert_tree(
                     "/a",
                     json!({
@@ -1475,14 +1474,6 @@ pub mod tests {
                 )
                 .await;
         let project = Project::test(fs, ["/a".as_ref()], cx).await;
-        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 mut rs_fake_servers = None;
         let mut md_fake_servers = None;
@@ -1515,23 +1506,17 @@ pub mod tests {
             });
         }
 
-        let _rs_buffer = project
+        let rs_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();
+        cx.executor().run_until_parked();
+        cx.executor().start_waiting();
         let rs_fake_server = rs_fake_servers.unwrap().next().await.unwrap();
-        let rs_editor = workspace
-            .update(cx, |workspace, cx| {
-                workspace.open_path((worktree_id, "main.rs"), None, true, cx)
-            })
-            .await
-            .unwrap()
-            .downcast::<Editor>()
-            .unwrap();
+        let rs_editor =
+            cx.add_window(|cx| Editor::for_buffer(rs_buffer, Some(project.clone()), cx));
         let rs_lsp_request_count = Arc::new(AtomicU32::new(0));
         rs_fake_server
             .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
@@ -1556,8 +1541,8 @@ pub mod tests {
             })
             .next()
             .await;
-        cx.foreground().run_until_parked();
-        rs_editor.update(cx, |editor, cx| {
+        cx.executor().run_until_parked();
+        _ = rs_editor.update(cx, |editor, cx| {
             let expected_hints = vec!["0".to_string()];
             assert_eq!(
                 expected_hints,
@@ -1572,24 +1557,17 @@ pub mod tests {
             );
         });
 
-        cx.foreground().run_until_parked();
-        let _md_buffer = project
+        cx.executor().run_until_parked();
+        let md_buffer = project
             .update(cx, |project, cx| {
                 project.open_local_buffer("/a/other.md", cx)
             })
             .await
             .unwrap();
-        cx.foreground().run_until_parked();
-        cx.foreground().start_waiting();
+        cx.executor().run_until_parked();
+        cx.executor().start_waiting();
         let md_fake_server = md_fake_servers.unwrap().next().await.unwrap();
-        let md_editor = workspace
-            .update(cx, |workspace, cx| {
-                workspace.open_path((worktree_id, "other.md"), None, true, cx)
-            })
-            .await
-            .unwrap()
-            .downcast::<Editor>()
-            .unwrap();
+        let md_editor = cx.add_window(|cx| Editor::for_buffer(md_buffer, Some(project), cx));
         let md_lsp_request_count = Arc::new(AtomicU32::new(0));
         md_fake_server
             .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
@@ -1614,8 +1592,8 @@ pub mod tests {
             })
             .next()
             .await;
-        cx.foreground().run_until_parked();
-        md_editor.update(cx, |editor, cx| {
+        cx.executor().run_until_parked();
+        _ = md_editor.update(cx, |editor, cx| {
             let expected_hints = vec!["0".to_string()];
             assert_eq!(
                 expected_hints,
@@ -1626,12 +1604,12 @@ pub mod tests {
             assert_eq!(editor.inlay_hint_cache().version, 1);
         });
 
-        rs_editor.update(cx, |editor, cx| {
+        _ = rs_editor.update(cx, |editor, cx| {
             editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
             editor.handle_input("some rs change", cx);
         });
-        cx.foreground().run_until_parked();
-        rs_editor.update(cx, |editor, cx| {
+        cx.executor().run_until_parked();
+        _ = rs_editor.update(cx, |editor, cx| {
             let expected_hints = vec!["1".to_string()];
             assert_eq!(
                 expected_hints,
@@ -1645,7 +1623,7 @@ pub mod tests {
                 "Every time hint cache changes, cache version should be incremented"
             );
         });
-        md_editor.update(cx, |editor, cx| {
+        _ = md_editor.update(cx, |editor, cx| {
             let expected_hints = vec!["0".to_string()];
             assert_eq!(
                 expected_hints,
@@ -1656,12 +1634,12 @@ pub mod tests {
             assert_eq!(editor.inlay_hint_cache().version, 1);
         });
 
-        md_editor.update(cx, |editor, cx| {
+        _ = md_editor.update(cx, |editor, cx| {
             editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
             editor.handle_input("some md change", cx);
         });
-        cx.foreground().run_until_parked();
-        md_editor.update(cx, |editor, cx| {
+        cx.executor().run_until_parked();
+        _ = md_editor.update(cx, |editor, cx| {
             let expected_hints = vec!["1".to_string()];
             assert_eq!(
                 expected_hints,
@@ -1671,7 +1649,7 @@ pub mod tests {
             assert_eq!(expected_hints, visible_hint_labels(editor, cx));
             assert_eq!(editor.inlay_hint_cache().version, 2);
         });
-        rs_editor.update(cx, |editor, cx| {
+        _ = rs_editor.update(cx, |editor, cx| {
             let expected_hints = vec!["1".to_string()];
             assert_eq!(
                 expected_hints,
@@ -1743,10 +1721,10 @@ pub mod tests {
             })
             .next()
             .await;
-        cx.foreground().run_until_parked();
+        cx.executor().run_until_parked();
 
         let mut edits_made = 1;
-        editor.update(cx, |editor, cx| {
+        _ = editor.update(cx, |editor, cx| {
             assert_eq!(
                 lsp_request_count.load(Ordering::Relaxed),
                 1,
@@ -1780,8 +1758,8 @@ pub mod tests {
             .request::<lsp::request::InlayHintRefreshRequest>(())
             .await
             .expect("inlay refresh request failed");
-        cx.foreground().run_until_parked();
-        editor.update(cx, |editor, cx| {
+        cx.executor().run_until_parked();
+        _ = editor.update(cx, |editor, cx| {
             assert_eq!(
                 lsp_request_count.load(Ordering::Relaxed),
                 2,
@@ -1852,8 +1830,8 @@ pub mod tests {
                     show_other_hints: new_allowed_hint_kinds.contains(&None),
                 })
             });
-            cx.foreground().run_until_parked();
-            editor.update(cx, |editor, cx| {
+            cx.executor().run_until_parked();
+            _ = editor.update(cx, |editor, cx| {
                 assert_eq!(
                     lsp_request_count.load(Ordering::Relaxed),
                     2,
@@ -1896,8 +1874,8 @@ pub mod tests {
                 show_other_hints: another_allowed_hint_kinds.contains(&None),
             })
         });
-        cx.foreground().run_until_parked();
-        editor.update(cx, |editor, cx| {
+        cx.executor().run_until_parked();
+        _ = editor.update(cx, |editor, cx| {
             assert_eq!(
                 lsp_request_count.load(Ordering::Relaxed),
                 2,
@@ -1926,8 +1904,8 @@ pub mod tests {
             .request::<lsp::request::InlayHintRefreshRequest>(())
             .await
             .expect("inlay refresh request failed");
-        cx.foreground().run_until_parked();
-        editor.update(cx, |editor, cx| {
+        cx.executor().run_until_parked();
+        _ = editor.update(cx, |editor, cx| {
             assert_eq!(
                 lsp_request_count.load(Ordering::Relaxed),
                 2,
@@ -1952,8 +1930,8 @@ pub mod tests {
                 show_other_hints: final_allowed_hint_kinds.contains(&None),
             })
         });
-        cx.foreground().run_until_parked();
-        editor.update(cx, |editor, cx| {
+        cx.executor().run_until_parked();
+        _ = editor.update(cx, |editor, cx| {
             assert_eq!(
                 lsp_request_count.load(Ordering::Relaxed),
                 3,
@@ -1988,8 +1966,8 @@ pub mod tests {
             .request::<lsp::request::InlayHintRefreshRequest>(())
             .await
             .expect("inlay refresh request failed");
-        cx.foreground().run_until_parked();
-        editor.update(cx, |editor, cx| {
+        cx.executor().run_until_parked();
+        _ = editor.update(cx, |editor, cx| {
             assert_eq!(
                 lsp_request_count.load(Ordering::Relaxed),
                 4,
@@ -2056,16 +2034,16 @@ pub mod tests {
             "initial change #2",
             "initial change #3",
         ] {
-            editor.update(cx, |editor, cx| {
+            _ = editor.update(cx, |editor, cx| {
                 editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
                 editor.handle_input(change_after_opening, cx);
             });
             expected_changes.push(change_after_opening);
         }
 
-        cx.foreground().run_until_parked();
+        cx.executor().run_until_parked();
 
-        editor.update(cx, |editor, cx| {
+        _ = editor.update(cx, |editor, cx| {
             let current_text = editor.text(cx);
             for change in &expected_changes {
                 assert!(
@@ -2099,18 +2077,17 @@ pub mod tests {
         ] {
             expected_changes.push(async_later_change);
             let task_editor = editor.clone();
-            let mut task_cx = cx.clone();
-            edits.push(cx.foreground().spawn(async move {
-                task_editor.update(&mut task_cx, |editor, cx| {
+            edits.push(cx.spawn(|mut cx| async move {
+                _ = task_editor.update(&mut cx, |editor, cx| {
                     editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
                     editor.handle_input(async_later_change, cx);
                 });
             }));
         }
         let _ = future::join_all(edits).await;
-        cx.foreground().run_until_parked();
+        cx.executor().run_until_parked();
 
-        editor.update(cx, |editor, cx| {
+        _ = editor.update(cx, |editor, cx| {
             let current_text = editor.text(cx);
             for change in &expected_changes {
                 assert!(
@@ -2166,7 +2143,7 @@ pub mod tests {
                 ..Default::default()
             }))
             .await;
-        let fs = FakeFs::new(cx.background());
+        let fs = FakeFs::new(cx.background_executor.clone());
         fs.insert_tree(
             "/a",
             json!({
@@ -2177,32 +2154,16 @@ pub mod tests {
         .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
+        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();
+        cx.executor().run_until_parked();
+        cx.executor().start_waiting();
         let fake_server = fake_servers.next().await.unwrap();
-        let editor = workspace
-            .update(cx, |workspace, cx| {
-                workspace.open_path((worktree_id, "main.rs"), None, true, cx)
-            })
-            .await
-            .unwrap()
-            .downcast::<Editor>()
-            .unwrap();
+        let editor = cx.add_window(|cx| Editor::for_buffer(buffer, Some(project), cx));
         let lsp_request_ranges = Arc::new(Mutex::new(Vec::new()));
         let lsp_request_count = Arc::new(AtomicUsize::new(0));
         let closure_lsp_request_ranges = Arc::clone(&lsp_request_ranges);
@@ -2233,13 +2194,16 @@ pub mod tests {
             })
             .next()
             .await;
+
         fn editor_visible_range(
-            editor: &ViewHandle<Editor>,
+            editor: &WindowHandle<Editor>,
             cx: &mut gpui::TestAppContext,
         ) -> Range<Point> {
-            let ranges = editor.update(cx, |editor, cx| {
-                editor.excerpts_for_inlay_hints_query(None, cx)
-            });
+            let ranges = editor
+                .update(cx, |editor, cx| {
+                    editor.excerpts_for_inlay_hints_query(None, cx)
+                })
+                .unwrap();
             assert_eq!(
                 ranges.len(),
                 1,
@@ -2262,10 +2226,10 @@ pub mod tests {
         // in large buffers, requests are made for more than visible range of a buffer.
         // invisible parts are queried later, to avoid excessive requests on quick typing.
         // wait the timeout needed to get all requests.
-        cx.foreground().advance_clock(Duration::from_millis(
+        cx.executor().advance_clock(Duration::from_millis(
             INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
         ));
-        cx.foreground().run_until_parked();
+        cx.executor().run_until_parked();
         let initial_visible_range = editor_visible_range(&editor, cx);
         let lsp_initial_visible_range = lsp::Range::new(
             lsp::Position::new(
@@ -2281,7 +2245,7 @@ pub mod tests {
             lsp::Position::new(initial_visible_range.end.row * 2, 2);
         let mut expected_invisible_query_start = lsp_initial_visible_range.end;
         expected_invisible_query_start.character += 1;
-        editor.update(cx, |editor, cx| {
+        _ = editor.update(cx, |editor, cx| {
             let ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
             assert_eq!(ranges.len(), 2,
                 "When scroll is at the edge of a big document, its visible part and the same range further should be queried in order, but got: {ranges:?}");
@@ -2308,39 +2272,41 @@ pub mod tests {
             );
         });
 
-        editor.update(cx, |editor, cx| {
+        _ = editor.update(cx, |editor, cx| {
             editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
             editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
         });
-        cx.foreground().advance_clock(Duration::from_millis(
+        cx.executor().advance_clock(Duration::from_millis(
             INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
         ));
-        cx.foreground().run_until_parked();
+        cx.executor().run_until_parked();
         let visible_range_after_scrolls = editor_visible_range(&editor, cx);
-        let visible_line_count =
-            editor.update(cx, |editor, _| editor.visible_line_count().unwrap());
-        let selection_in_cached_range = editor.update(cx, |editor, cx| {
-            let ranges = lsp_request_ranges
-                .lock()
-                .drain(..)
-                .sorted_by_key(|r| r.start)
-                .collect::<Vec<_>>();
-            assert_eq!(
-                ranges.len(),
-                2,
-                "Should query 2 ranges after both scrolls, but got: {ranges:?}"
-            );
-            let first_scroll = &ranges[0];
-            let second_scroll = &ranges[1];
-            assert_eq!(
-                first_scroll.end, second_scroll.start,
-                "Should query 2 adjacent ranges after the scrolls, but got: {ranges:?}"
-            );
-            assert_eq!(
+        let visible_line_count = editor
+            .update(cx, |editor, _| editor.visible_line_count().unwrap())
+            .unwrap();
+        let selection_in_cached_range = editor
+            .update(cx, |editor, cx| {
+                let ranges = lsp_request_ranges
+                    .lock()
+                    .drain(..)
+                    .sorted_by_key(|r| r.start)
+                    .collect::<Vec<_>>();
+                assert_eq!(
+                    ranges.len(),
+                    2,
+                    "Should query 2 ranges after both scrolls, but got: {ranges:?}"
+                );
+                let first_scroll = &ranges[0];
+                let second_scroll = &ranges[1];
+                assert_eq!(
+                    first_scroll.end, second_scroll.start,
+                    "Should query 2 adjacent ranges after the scrolls, but got: {ranges:?}"
+                );
+                assert_eq!(
                 first_scroll.start, expected_initial_query_range_end,
                 "First scroll should start the query right after the end of the original scroll",
             );
-            assert_eq!(
+                assert_eq!(
                 second_scroll.end,
                 lsp::Position::new(
                     visible_range_after_scrolls.end.row
@@ -2350,41 +2316,42 @@ pub mod tests {
                 "Second scroll should query one more screen down after the end of the visible range"
             );
 
-            let lsp_requests = lsp_request_count.load(Ordering::Acquire);
-            assert_eq!(lsp_requests, 4, "Should query for hints after every scroll");
-            let expected_hints = vec![
-                "1".to_string(),
-                "2".to_string(),
-                "3".to_string(),
-                "4".to_string(),
-            ];
-            assert_eq!(
-                expected_hints,
-                cached_hint_labels(editor),
-                "Should have hints from the new LSP response after the edit"
-            );
-            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-            assert_eq!(
-                editor.inlay_hint_cache().version,
-                lsp_requests,
-                "Should update the cache for every LSP response with hints added"
-            );
+                let lsp_requests = lsp_request_count.load(Ordering::Acquire);
+                assert_eq!(lsp_requests, 4, "Should query for hints after every scroll");
+                let expected_hints = vec![
+                    "1".to_string(),
+                    "2".to_string(),
+                    "3".to_string(),
+                    "4".to_string(),
+                ];
+                assert_eq!(
+                    expected_hints,
+                    cached_hint_labels(editor),
+                    "Should have hints from the new LSP response after the edit"
+                );
+                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
+                assert_eq!(
+                    editor.inlay_hint_cache().version,
+                    lsp_requests,
+                    "Should update the cache for every LSP response with hints added"
+                );
 
-            let mut selection_in_cached_range = visible_range_after_scrolls.end;
-            selection_in_cached_range.row -= visible_line_count.ceil() as u32;
-            selection_in_cached_range
-        });
+                let mut selection_in_cached_range = visible_range_after_scrolls.end;
+                selection_in_cached_range.row -= visible_line_count.ceil() as u32;
+                selection_in_cached_range
+            })
+            .unwrap();
 
-        editor.update(cx, |editor, cx| {
+        _ = editor.update(cx, |editor, cx| {
             editor.change_selections(Some(Autoscroll::center()), cx, |s| {
                 s.select_ranges([selection_in_cached_range..selection_in_cached_range])
             });
         });
-        cx.foreground().advance_clock(Duration::from_millis(
+        cx.executor().advance_clock(Duration::from_millis(
             INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
         ));
-        cx.foreground().run_until_parked();
-        editor.update(cx, |_, _| {
+        cx.executor().run_until_parked();
+        _ = editor.update(cx, |_, _| {
             let ranges = lsp_request_ranges
                 .lock()
                 .drain(..)
@@ -2394,14 +2361,14 @@ pub mod tests {
             assert_eq!(lsp_request_count.load(Ordering::Acquire), 4);
         });
 
-        editor.update(cx, |editor, cx| {
+        _ = editor.update(cx, |editor, cx| {
             editor.handle_input("++++more text++++", cx);
         });
-        cx.foreground().advance_clock(Duration::from_millis(
+        cx.executor().advance_clock(Duration::from_millis(
             INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
         ));
-        cx.foreground().run_until_parked();
-        editor.update(cx, |editor, cx| {
+        cx.executor().run_until_parked();
+        _ = editor.update(cx, |editor, cx| {
             let mut ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
             ranges.sort_by_key(|r| r.start);
 
@@ -2434,10 +2401,7 @@ pub mod tests {
     }
 
     #[gpui::test(iterations = 10)]
-    async fn test_multiple_excerpts_large_multibuffer(
-        deterministic: Arc<Deterministic>,
-        cx: &mut gpui::TestAppContext,
-    ) {
+    async fn test_multiple_excerpts_large_multibuffer(cx: &mut gpui::TestAppContext) {
         init_test(cx, |settings| {
             settings.defaults.inlay_hints = Some(InlayHintSettings {
                 enabled: true,
@@ -2465,26 +2429,21 @@ pub mod tests {
             }))
             .await;
         let language = Arc::new(language);
-        let fs = FakeFs::new(cx.background());
+        let fs = FakeFs::new(cx.background_executor.clone());
         fs.insert_tree(
-            "/a",
-            json!({
-                "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
-                "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
-            }),
-        )
-        .await;
+                "/a",
+                json!({
+                    "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
+                    "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
+                }),
+            )
+            .await;
         let project = Project::test(fs, ["/a".as_ref()], cx).await;
         project.update(cx, |project, _| {
             project.languages().add(Arc::clone(&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 worktree_id = project.update(cx, |project, cx| {
+            project.worktrees().next().unwrap().read(cx).id()
         });
 
         let buffer_1 = project
@@ -2499,7 +2458,7 @@ pub mod tests {
             })
             .await
             .unwrap();
-        let multibuffer = cx.add_model(|cx| {
+        let multibuffer = cx.new_model(|cx| {
             let mut multibuffer = MultiBuffer::new(0);
             multibuffer.push_excerpts(
                 buffer_1.clone(),
@@ -2564,11 +2523,9 @@ pub mod tests {
             multibuffer
         });
 
-        deterministic.run_until_parked();
-        cx.foreground().run_until_parked();
-        let editor = cx
-            .add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx))
-            .root(cx);
+        cx.executor().run_until_parked();
+        let editor =
+            cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
         let editor_edited = Arc::new(AtomicBool::new(false));
         let fake_server = fake_servers.next().await.unwrap();
         let closure_editor_edited = Arc::clone(&editor_edited);
@@ -2637,25 +2594,27 @@ pub mod tests {
             })
             .next()
             .await;
-        cx.foreground().run_until_parked();
-
-        editor.update(cx, |editor, cx| {
-            let expected_hints = vec![
-                "main hint #0".to_string(),
-                "main hint #1".to_string(),
-                "main hint #2".to_string(),
-                "main hint #3".to_string(),
-            ];
-            assert_eq!(
-                expected_hints,
-                cached_hint_labels(editor),
-                "When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
-            );
-            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-            assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(), "Every visible excerpt hints should bump the verison");
-        });
+        cx.executor().run_until_parked();
+
+        _ = editor.update(cx, |editor, cx| {
+                let expected_hints = vec![
+                    "main hint #0".to_string(),
+                    "main hint #1".to_string(),
+                    "main hint #2".to_string(),
+                    "main hint #3".to_string(),
+                    "main hint #4".to_string(),
+                    "main hint #5".to_string(),
+                ];
+                assert_eq!(
+                    expected_hints,
+                    cached_hint_labels(editor),
+                    "When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
+                );
+                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
+                assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(), "Every visible excerpt hints should bump the verison");
+            });
 
-        editor.update(cx, |editor, cx| {
+        _ = editor.update(cx, |editor, cx| {
             editor.change_selections(Some(Autoscroll::Next), cx, |s| {
                 s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
             });
@@ -2666,93 +2625,94 @@ pub mod tests {
                 s.select_ranges([Point::new(50, 0)..Point::new(50, 0)])
             });
         });
-        cx.foreground().run_until_parked();
-        editor.update(cx, |editor, cx| {
-            let expected_hints = vec![
-                "main hint #0".to_string(),
-                "main hint #1".to_string(),
-                "main hint #2".to_string(),
-                "main hint #3".to_string(),
-                "main hint #4".to_string(),
-                "main hint #5".to_string(),
-                "other hint #0".to_string(),
-                "other hint #1".to_string(),
-                "other hint #2".to_string(),
-            ];
-            assert_eq!(expected_hints, cached_hint_labels(editor),
-                "With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits");
-            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-            assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(),
-                "Due to every excerpt having one hint, we update cache per new excerpt scrolled");
-        });
+        cx.executor().run_until_parked();
+        _ = editor.update(cx, |editor, cx| {
+                let expected_hints = vec![
+                    "main hint #0".to_string(),
+                    "main hint #1".to_string(),
+                    "main hint #2".to_string(),
+                    "main hint #3".to_string(),
+                    "main hint #4".to_string(),
+                    "main hint #5".to_string(),
+                    "other hint #0".to_string(),
+                    "other hint #1".to_string(),
+                    "other hint #2".to_string(),
+                ];
+                assert_eq!(expected_hints, cached_hint_labels(editor),
+                    "With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits");
+                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
+                assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(),
+                    "Due to every excerpt having one hint, we update cache per new excerpt scrolled");
+            });
 
-        editor.update(cx, |editor, cx| {
+        _ = editor.update(cx, |editor, cx| {
             editor.change_selections(Some(Autoscroll::Next), cx, |s| {
                 s.select_ranges([Point::new(100, 0)..Point::new(100, 0)])
             });
         });
-        cx.foreground().advance_clock(Duration::from_millis(
+        cx.executor().advance_clock(Duration::from_millis(
             INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
         ));
-        cx.foreground().run_until_parked();
+        cx.executor().run_until_parked();
         let last_scroll_update_version = editor.update(cx, |editor, cx| {
-            let expected_hints = vec![
-                "main hint #0".to_string(),
-                "main hint #1".to_string(),
-                "main hint #2".to_string(),
-                "main hint #3".to_string(),
-                "main hint #4".to_string(),
-                "main hint #5".to_string(),
-                "other hint #0".to_string(),
-                "other hint #1".to_string(),
-                "other hint #2".to_string(),
-                "other hint #3".to_string(),
-                "other hint #4".to_string(),
-                "other hint #5".to_string(),
-            ];
-            assert_eq!(expected_hints, cached_hint_labels(editor),
-                "After multibuffer was scrolled to the end, all hints for all excerpts should be fetched");
-            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-            assert_eq!(editor.inlay_hint_cache().version, expected_hints.len());
-            expected_hints.len()
-        });
-
-        editor.update(cx, |editor, cx| {
+                let expected_hints = vec![
+                    "main hint #0".to_string(),
+                    "main hint #1".to_string(),
+                    "main hint #2".to_string(),
+                    "main hint #3".to_string(),
+                    "main hint #4".to_string(),
+                    "main hint #5".to_string(),
+                    "other hint #0".to_string(),
+                    "other hint #1".to_string(),
+                    "other hint #2".to_string(),
+                    "other hint #3".to_string(),
+                    "other hint #4".to_string(),
+                    "other hint #5".to_string(),
+                ];
+                assert_eq!(expected_hints, cached_hint_labels(editor),
+                    "After multibuffer was scrolled to the end, all hints for all excerpts should be fetched");
+                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
+                assert_eq!(editor.inlay_hint_cache().version, expected_hints.len());
+                expected_hints.len()
+            }).unwrap();
+
+        _ = editor.update(cx, |editor, cx| {
             editor.change_selections(Some(Autoscroll::Next), cx, |s| {
                 s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
             });
         });
-        cx.foreground().run_until_parked();
-        editor.update(cx, |editor, cx| {
-            let expected_hints = vec![
-                "main hint #0".to_string(),
-                "main hint #1".to_string(),
-                "main hint #2".to_string(),
-                "main hint #3".to_string(),
-                "main hint #4".to_string(),
-                "main hint #5".to_string(),
-                "other hint #0".to_string(),
-                "other hint #1".to_string(),
-                "other hint #2".to_string(),
-                "other hint #3".to_string(),
-                "other hint #4".to_string(),
-                "other hint #5".to_string(),
-            ];
-            assert_eq!(expected_hints, cached_hint_labels(editor),
-                "After multibuffer was scrolled to the end, further scrolls up should not bring more hints");
-            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-            assert_eq!(editor.inlay_hint_cache().version, last_scroll_update_version, "No updates should happen during scrolling already scolled buffer");
-        });
+        cx.executor().run_until_parked();
+        _ = editor.update(cx, |editor, cx| {
+                let expected_hints = vec![
+                    "main hint #0".to_string(),
+                    "main hint #1".to_string(),
+                    "main hint #2".to_string(),
+                    "main hint #3".to_string(),
+                    "main hint #4".to_string(),
+                    "main hint #5".to_string(),
+                    "other hint #0".to_string(),
+                    "other hint #1".to_string(),
+                    "other hint #2".to_string(),
+                    "other hint #3".to_string(),
+                    "other hint #4".to_string(),
+                    "other hint #5".to_string(),
+                ];
+                assert_eq!(expected_hints, cached_hint_labels(editor),
+                    "After multibuffer was scrolled to the end, further scrolls up should not bring more hints");
+                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
+                assert_eq!(editor.inlay_hint_cache().version, last_scroll_update_version, "No updates should happen during scrolling already scolled buffer");
+            });
 
         editor_edited.store(true, Ordering::Release);
-        editor.update(cx, |editor, cx| {
+        _ = editor.update(cx, |editor, cx| {
             editor.change_selections(None, cx, |s| {
-                s.select_ranges([Point::new(56, 0)..Point::new(56, 0)])
+                // TODO if this gets set to hint boundary (e.g. 56) we sometimes get an extra cache version bump, why?
+                s.select_ranges([Point::new(57, 0)..Point::new(57, 0)])
             });
             editor.handle_input("++++more text++++", cx);
         });
-        cx.foreground().run_until_parked();
-        editor.update(cx, |editor, cx| {
+        cx.executor().run_until_parked();
+        _ = editor.update(cx, |editor, cx| {
             let expected_hints = vec![
                 "main hint(edited) #0".to_string(),
                 "main hint(edited) #1".to_string(),

crates/editor/src/items.rs 🔗

@@ -1,16 +1,15 @@
 use crate::{
     editor_settings::SeedQuerySetting, link_go_to_definition::hide_link_definition,
-    persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor, EditorSettings, Event,
+    persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor, EditorEvent, EditorSettings,
     ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _,
 };
-use anyhow::{Context, Result};
+use anyhow::{anyhow, Context as _, Result};
 use collections::HashSet;
 use futures::future::try_join_all;
 use gpui::{
-    elements::*,
-    geometry::vector::{vec2f, Vector2F},
-    AppContext, AsyncAppContext, Entity, ModelHandle, Subscription, Task, View, ViewContext,
-    ViewHandle, WeakViewHandle,
+    div, point, AnyElement, AppContext, AsyncWindowContext, Context, Entity, EntityId,
+    EventEmitter, IntoElement, Model, ParentElement, Pixels, Render, SharedString, Styled,
+    Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
 };
 use language::{
     proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, OffsetRangeExt,
@@ -18,27 +17,29 @@ use language::{
 };
 use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPath};
 use rpc::proto::{self, update_view, PeerId};
-use smallvec::SmallVec;
+use settings::Settings;
+
+use std::fmt::Write;
 use std::{
     borrow::Cow,
     cmp::{self, Ordering},
-    fmt::Write,
     iter,
     ops::Range,
     path::{Path, PathBuf},
     sync::Arc,
 };
 use text::Selection;
-use util::{
-    paths::{PathExt, FILE_ROW_COLUMN_DELIMITER},
-    ResultExt, TryFutureExt,
+use theme::{ActiveTheme, Theme};
+use ui::{h_stack, prelude::*, Label};
+use util::{paths::PathExt, paths::FILE_ROW_COLUMN_DELIMITER, ResultExt, TryFutureExt};
+use workspace::{
+    item::{BreadcrumbText, FollowEvent, FollowableItemHandle},
+    StatusItemView,
 };
-use workspace::item::{BreadcrumbText, FollowableItemHandle, ItemHandle};
 use workspace::{
-    item::{FollowableItem, Item, ItemEvent, ProjectItem},
+    item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem},
     searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
-    ItemId, ItemNavHistory, Pane, StatusItemView, ToolbarItemLocation, ViewId, Workspace,
-    WorkspaceId,
+    ItemId, ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
 };
 
 pub const MAX_TAB_TITLE_LEN: usize = 24;
@@ -49,12 +50,12 @@ impl FollowableItem for Editor {
     }
 
     fn from_state_proto(
-        pane: ViewHandle<workspace::Pane>,
-        workspace: ViewHandle<Workspace>,
+        pane: View<workspace::Pane>,
+        workspace: View<Workspace>,
         remote_id: ViewId,
         state: &mut Option<proto::view::Variant>,
-        cx: &mut AppContext,
-    ) -> Option<Task<Result<ViewHandle<Self>>>> {
+        cx: &mut WindowContext,
+    ) -> Option<Task<Result<View<Self>>>> {
         let project = workspace.read(cx).project().to_owned();
         let Some(proto::view::Variant::Editor(_)) = state else {
             return None;
@@ -80,7 +81,7 @@ impl FollowableItem for Editor {
         let pane = pane.downgrade();
         Some(cx.spawn(|mut cx| async move {
             let mut buffers = futures::future::try_join_all(buffers).await?;
-            let editor = pane.read_with(&cx, |pane, cx| {
+            let editor = pane.update(&mut cx, |pane, cx| {
                 let mut editors = pane.items_of_type::<Self>();
                 editors.find(|editor| {
                     let ids_match = editor.remote_id(&client, cx) == Some(remote_id);
@@ -95,7 +96,7 @@ impl FollowableItem for Editor {
                 editor
             } else {
                 pane.update(&mut cx, |_, cx| {
-                    let multibuffer = cx.add_model(|cx| {
+                    let multibuffer = cx.new_model(|cx| {
                         let mut multibuffer;
                         if state.singleton && buffers.len() == 1 {
                             multibuffer = MultiBuffer::singleton(buffers.pop().unwrap(), cx)
@@ -128,7 +129,7 @@ impl FollowableItem for Editor {
                         multibuffer
                     });
 
-                    cx.add_view(|cx| {
+                    cx.new_view(|cx| {
                         let mut editor =
                             Editor::for_multibuffer(multibuffer, Some(project.clone()), cx);
                         editor.remote_id = Some(remote_id);
@@ -162,22 +163,20 @@ impl FollowableItem for Editor {
             self.buffer.update(cx, |buffer, cx| {
                 buffer.remove_active_selections(cx);
             });
-        } else {
+        } else if self.focus_handle.is_focused(cx) {
             self.buffer.update(cx, |buffer, cx| {
-                if self.focused {
-                    buffer.set_active_selections(
-                        &self.selections.disjoint_anchors(),
-                        self.selections.line_mode,
-                        self.cursor_shape,
-                        cx,
-                    );
-                }
+                buffer.set_active_selections(
+                    &self.selections.disjoint_anchors(),
+                    self.selections.line_mode,
+                    self.cursor_shape,
+                    cx,
+                );
             });
         }
         cx.notify();
     }
 
-    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
+    fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
         let buffer = self.buffer.read(cx);
         let scroll_anchor = self.scroll_manager.anchor();
         let excerpts = buffer
@@ -204,8 +203,8 @@ impl FollowableItem for Editor {
             title: (!buffer.is_singleton()).then(|| buffer.title(cx).into()),
             excerpts,
             scroll_top_anchor: Some(serialize_anchor(&scroll_anchor.anchor)),
-            scroll_x: scroll_anchor.offset.x(),
-            scroll_y: scroll_anchor.offset.y(),
+            scroll_x: scroll_anchor.offset.x,
+            scroll_y: scroll_anchor.offset.y,
             selections: self
                 .selections
                 .disjoint_anchors()
@@ -220,18 +219,33 @@ impl FollowableItem for Editor {
         }))
     }
 
+    fn to_follow_event(event: &EditorEvent) -> Option<workspace::item::FollowEvent> {
+        match event {
+            EditorEvent::Edited => Some(FollowEvent::Unfollow),
+            EditorEvent::SelectionsChanged { local }
+            | EditorEvent::ScrollPositionChanged { local, .. } => {
+                if *local {
+                    Some(FollowEvent::Unfollow)
+                } else {
+                    None
+                }
+            }
+            _ => None,
+        }
+    }
+
     fn add_event_to_update_proto(
         &self,
-        event: &Self::Event,
+        event: &EditorEvent,
         update: &mut Option<proto::update_view::Variant>,
-        cx: &AppContext,
+        cx: &WindowContext,
     ) -> bool {
         let update =
             update.get_or_insert_with(|| proto::update_view::Variant::Editor(Default::default()));
 
         match update {
             proto::update_view::Variant::Editor(update) => match event {
-                Event::ExcerptsAdded {
+                EditorEvent::ExcerptsAdded {
                     buffer,
                     predecessor,
                     excerpts,
@@ -252,20 +266,20 @@ impl FollowableItem for Editor {
                     }
                     true
                 }
-                Event::ExcerptsRemoved { ids } => {
+                EditorEvent::ExcerptsRemoved { ids } => {
                     update
                         .deleted_excerpts
                         .extend(ids.iter().map(ExcerptId::to_proto));
                     true
                 }
-                Event::ScrollPositionChanged { .. } => {
+                EditorEvent::ScrollPositionChanged { .. } => {
                     let scroll_anchor = self.scroll_manager.anchor();
                     update.scroll_top_anchor = Some(serialize_anchor(&scroll_anchor.anchor));
-                    update.scroll_x = scroll_anchor.offset.x();
-                    update.scroll_y = scroll_anchor.offset.y();
+                    update.scroll_x = scroll_anchor.offset.x;
+                    update.scroll_y = scroll_anchor.offset.y;
                     true
                 }
-                Event::SelectionsChanged { .. } => {
+                EditorEvent::SelectionsChanged { .. } => {
                     update.selections = self
                         .selections
                         .disjoint_anchors()
@@ -286,7 +300,7 @@ impl FollowableItem for Editor {
 
     fn apply_update_proto(
         &mut self,
-        project: &ModelHandle<Project>,
+        project: &Model<Project>,
         message: update_view::Variant,
         cx: &mut ViewContext<Self>,
     ) -> Task<Result<()>> {
@@ -297,25 +311,16 @@ impl FollowableItem for Editor {
         })
     }
 
-    fn should_unfollow_on_event(event: &Self::Event, _: &AppContext) -> bool {
-        match event {
-            Event::Edited => true,
-            Event::SelectionsChanged { local } => *local,
-            Event::ScrollPositionChanged { local, .. } => *local,
-            _ => false,
-        }
-    }
-
-    fn is_project_item(&self, _cx: &AppContext) -> bool {
+    fn is_project_item(&self, _cx: &WindowContext) -> bool {
         true
     }
 }
 
 async fn update_editor_from_message(
-    this: WeakViewHandle<Editor>,
-    project: ModelHandle<Project>,
+    this: WeakView<Editor>,
+    project: Model<Project>,
     message: proto::update_view::Editor,
-    cx: &mut AsyncAppContext,
+    cx: &mut AsyncWindowContext,
 ) -> Result<()> {
     // Open all of the buffers of which excerpts were added to the editor.
     let inserted_excerpt_buffer_ids = message
@@ -328,7 +333,7 @@ async fn update_editor_from_message(
             .into_iter()
             .map(|id| project.open_buffer_by_id(id, cx))
             .collect::<Vec<_>>()
-    });
+    })?;
     let _inserted_excerpt_buffers = try_join_all(inserted_excerpt_buffers).await?;
 
     // Update the editor's excerpts.
@@ -353,7 +358,7 @@ async fn update_editor_from_message(
                     continue;
                 };
                 let buffer_id = excerpt.buffer_id;
-                let Some(buffer) = project.read(cx).buffer_for_id(buffer_id, cx) else {
+                let Some(buffer) = project.read(cx).buffer_for_id(buffer_id) else {
                     continue;
                 };
 
@@ -430,7 +435,7 @@ async fn update_editor_from_message(
             editor.set_scroll_anchor_remote(
                 ScrollAnchor {
                     anchor: scroll_top_anchor,
-                    offset: vec2f(message.scroll_x, message.scroll_y),
+                    offset: point(message.scroll_x, message.scroll_y),
                 },
                 cx,
             );
@@ -516,6 +521,8 @@ fn deserialize_anchor(buffer: &MultiBufferSnapshot, anchor: proto::EditorAnchor)
 }
 
 impl Item for Editor {
+    type Event = EditorEvent;
+
     fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) -> bool {
         if let Ok(data) = data.downcast::<NavigationData>() {
             let newest_selection = self.selections.newest::<Point>(cx);
@@ -551,7 +558,7 @@ impl Item for Editor {
         }
     }
 
-    fn tab_tooltip_text(&self, cx: &AppContext) -> Option<Cow<str>> {
+    fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
         let file_path = self
             .buffer()
             .read(cx)
@@ -566,53 +573,66 @@ impl Item for Editor {
         Some(file_path.into())
     }
 
-    fn tab_description<'a>(&'a self, detail: usize, cx: &'a AppContext) -> Option<Cow<str>> {
-        match path_for_buffer(&self.buffer, detail, true, cx)? {
-            Cow::Borrowed(path) => Some(path.to_string_lossy()),
-            Cow::Owned(path) => Some(path.to_string_lossy().to_string().into()),
-        }
+    fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option<SharedString> {
+        let path = path_for_buffer(&self.buffer, detail, true, cx)?;
+        Some(path.to_string_lossy().to_string().into())
     }
 
-    fn tab_content<T: 'static>(
-        &self,
-        detail: Option<usize>,
-        style: &theme::Tab,
-        cx: &AppContext,
-    ) -> AnyElement<T> {
-        Flex::row()
-            .with_child(Label::new(self.title(cx).to_string(), style.label.clone()).into_any())
-            .with_children(detail.and_then(|detail| {
-                let path = path_for_buffer(&self.buffer, detail, false, cx)?;
-                let description = path.to_string_lossy();
-                Some(
-                    Label::new(
-                        util::truncate_and_trailoff(&description, MAX_TAB_TITLE_LEN),
-                        style.description.text.clone(),
-                    )
-                    .contained()
-                    .with_style(style.description.container)
-                    .aligned(),
-                )
+    fn tab_content(&self, detail: Option<usize>, selected: bool, cx: &WindowContext) -> AnyElement {
+        let _theme = cx.theme();
+
+        let description = detail.and_then(|detail| {
+            let path = path_for_buffer(&self.buffer, detail, false, cx)?;
+            let description = path.to_string_lossy();
+            let description = description.trim();
+
+            if description.is_empty() {
+                return None;
+            }
+
+            Some(util::truncate_and_trailoff(&description, MAX_TAB_TITLE_LEN))
+        });
+
+        h_stack()
+            .gap_2()
+            .child(Label::new(self.title(cx).to_string()).color(if selected {
+                Color::Default
+            } else {
+                Color::Muted
             }))
-            .align_children_center()
-            .into_any()
+            .when_some(description, |this, description| {
+                this.child(
+                    Label::new(description)
+                        .size(LabelSize::XSmall)
+                        .color(Color::Muted),
+                )
+            })
+            .into_any_element()
     }
 
-    fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) {
+    fn for_each_project_item(
+        &self,
+        cx: &AppContext,
+        f: &mut dyn FnMut(EntityId, &dyn project::Item),
+    ) {
         self.buffer
             .read(cx)
-            .for_each_buffer(|buffer| f(buffer.id(), buffer.read(cx)));
+            .for_each_buffer(|buffer| f(buffer.entity_id(), buffer.read(cx)));
     }
 
     fn is_singleton(&self, cx: &AppContext) -> bool {
         self.buffer.read(cx).is_singleton()
     }
 
-    fn clone_on_split(&self, _workspace_id: WorkspaceId, cx: &mut ViewContext<Self>) -> Option<Self>
+    fn clone_on_split(
+        &self,
+        _workspace_id: WorkspaceId,
+        cx: &mut ViewContext<Self>,
+    ) -> Option<View<Editor>>
     where
         Self: Sized,
     {
-        Some(self.clone(cx))
+        Some(cx.new_view(|cx| self.clone(cx)))
     }
 
     fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
@@ -646,11 +666,7 @@ impl Item for Editor {
         }
     }
 
-    fn save(
-        &mut self,
-        project: ModelHandle<Project>,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<Result<()>> {
+    fn save(&mut self, project: Model<Project>, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
         self.report_editor_event("save", None, cx);
         let format = self.perform_format(project.clone(), FormatTrigger::Save, cx);
         let buffers = self.buffer().clone().read(cx).all_buffers();
@@ -659,28 +675,34 @@ impl Item for Editor {
 
             if buffers.len() == 1 {
                 project
-                    .update(&mut cx, |project, cx| project.save_buffers(buffers, cx))
+                    .update(&mut cx, |project, cx| project.save_buffers(buffers, cx))?
                     .await?;
             } else {
                 // For multi-buffers, only save those ones that contain changes. For clean buffers
                 // we simulate saving by calling `Buffer::did_save`, so that language servers or
                 // other downstream listeners of save events get notified.
                 let (dirty_buffers, clean_buffers) = buffers.into_iter().partition(|buffer| {
-                    buffer.read_with(&cx, |buffer, _| buffer.is_dirty() || buffer.has_conflict())
+                    buffer
+                        .update(&mut cx, |buffer, _| {
+                            buffer.is_dirty() || buffer.has_conflict()
+                        })
+                        .unwrap_or(false)
                 });
 
                 project
                     .update(&mut cx, |project, cx| {
                         project.save_buffers(dirty_buffers, cx)
-                    })
+                    })?
                     .await?;
                 for buffer in clean_buffers {
-                    buffer.update(&mut cx, |buffer, cx| {
-                        let version = buffer.saved_version().clone();
-                        let fingerprint = buffer.saved_version_fingerprint();
-                        let mtime = buffer.saved_mtime();
-                        buffer.did_save(version, fingerprint, mtime, cx);
-                    });
+                    buffer
+                        .update(&mut cx, |buffer, cx| {
+                            let version = buffer.saved_version().clone();
+                            let fingerprint = buffer.saved_version_fingerprint();
+                            let mtime = buffer.saved_mtime();
+                            buffer.did_save(version, fingerprint, mtime, cx);
+                        })
+                        .ok();
                 }
             }
 
@@ -690,7 +712,7 @@ impl Item for Editor {
 
     fn save_as(
         &mut self,
-        project: ModelHandle<Project>,
+        project: Model<Project>,
         abs_path: PathBuf,
         cx: &mut ViewContext<Self>,
     ) -> Task<Result<()>> {
@@ -710,11 +732,7 @@ impl Item for Editor {
         })
     }
 
-    fn reload(
-        &mut self,
-        project: ModelHandle<Project>,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<Result<()>> {
+    fn reload(&mut self, project: Model<Project>, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
         let buffer = self.buffer().clone();
         let buffers = self.buffer.read(cx).all_buffers();
         let reload_buffers =
@@ -724,60 +742,36 @@ impl Item for Editor {
             this.update(&mut cx, |editor, cx| {
                 editor.request_autoscroll(Autoscroll::fit(), cx)
             })?;
-            buffer.update(&mut cx, |buffer, cx| {
-                if let Some(transaction) = transaction {
-                    if !buffer.is_singleton() {
-                        buffer.push_transaction(&transaction.0, cx);
+            buffer
+                .update(&mut cx, |buffer, cx| {
+                    if let Some(transaction) = transaction {
+                        if !buffer.is_singleton() {
+                            buffer.push_transaction(&transaction.0, cx);
+                        }
                     }
-                }
-            });
+                })
+                .ok();
             Ok(())
         })
     }
 
-    fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
-        let mut result = SmallVec::new();
-        match event {
-            Event::Closed => result.push(ItemEvent::CloseItem),
-            Event::Saved | Event::TitleChanged => {
-                result.push(ItemEvent::UpdateTab);
-                result.push(ItemEvent::UpdateBreadcrumbs);
-            }
-            Event::Reparsed => {
-                result.push(ItemEvent::UpdateBreadcrumbs);
-            }
-            Event::SelectionsChanged { local } if *local => {
-                result.push(ItemEvent::UpdateBreadcrumbs);
-            }
-            Event::DirtyChanged => {
-                result.push(ItemEvent::UpdateTab);
-            }
-            Event::BufferEdited => {
-                result.push(ItemEvent::Edit);
-                result.push(ItemEvent::UpdateBreadcrumbs);
-            }
-            _ => {}
-        }
-        result
-    }
-
-    fn as_searchable(&self, handle: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
+    fn as_searchable(&self, handle: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
         Some(Box::new(handle.clone()))
     }
 
-    fn pixel_position_of_cursor(&self, _: &AppContext) -> Option<Vector2F> {
+    fn pixel_position_of_cursor(&self, _: &AppContext) -> Option<gpui::Point<Pixels>> {
         self.pixel_position_of_newest_cursor
     }
 
     fn breadcrumb_location(&self) -> ToolbarItemLocation {
-        ToolbarItemLocation::PrimaryLeft { flex: None }
+        ToolbarItemLocation::PrimaryLeft
     }
 
-    fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
+    fn breadcrumbs(&self, variant: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
         let cursor = self.selections.newest_anchor().head();
         let multibuffer = &self.buffer().read(cx);
         let (buffer_id, symbols) =
-            multibuffer.symbols_containing(cursor, Some(&theme.editor.syntax), cx)?;
+            multibuffer.symbols_containing(cursor, Some(&variant.syntax()), cx)?;
         let buffer = multibuffer.buffer(buffer_id)?;
 
         let buffer = buffer.read(cx);
@@ -806,11 +800,11 @@ impl Item for Editor {
 
     fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
         let workspace_id = workspace.database_id();
-        let item_id = cx.view_id();
+        let item_id = cx.view().item_id().as_u64() as ItemId;
         self.workspace = Some((workspace.weak_handle(), workspace.database_id()));
 
         fn serialize(
-            buffer: ModelHandle<Buffer>,
+            buffer: Model<Buffer>,
             workspace_id: WorkspaceId,
             item_id: ItemId,
             cx: &mut AppContext,
@@ -818,7 +812,7 @@ impl Item for Editor {
             if let Some(file) = buffer.read(cx).file().and_then(|file| file.as_local()) {
                 let path = file.abs_path(cx);
 
-                cx.background()
+                cx.background_executor()
                     .spawn(async move {
                         DB.save_path(item_id, workspace_id, path.clone())
                             .await
@@ -834,7 +828,12 @@ impl Item for Editor {
             cx.subscribe(&buffer, |this, buffer, event, cx| {
                 if let Some((_, workspace_id)) = this.workspace.as_ref() {
                     if let language::Event::FileHandleChanged = event {
-                        serialize(buffer, *workspace_id, cx.view_id(), cx);
+                        serialize(
+                            buffer,
+                            *workspace_id,
+                            cx.view().item_id().as_u64() as ItemId,
+                            cx,
+                        );
                     }
                 }
             })
@@ -846,13 +845,47 @@ impl Item for Editor {
         Some("Editor")
     }
 
+    fn to_item_events(event: &EditorEvent, mut f: impl FnMut(ItemEvent)) {
+        match event {
+            EditorEvent::Closed => f(ItemEvent::CloseItem),
+
+            EditorEvent::Saved | EditorEvent::TitleChanged => {
+                f(ItemEvent::UpdateTab);
+                f(ItemEvent::UpdateBreadcrumbs);
+            }
+
+            EditorEvent::Reparsed => {
+                f(ItemEvent::UpdateBreadcrumbs);
+            }
+
+            EditorEvent::SelectionsChanged { local } if *local => {
+                f(ItemEvent::UpdateBreadcrumbs);
+            }
+
+            EditorEvent::DirtyChanged => {
+                f(ItemEvent::UpdateTab);
+            }
+
+            EditorEvent::BufferEdited => {
+                f(ItemEvent::Edit);
+                f(ItemEvent::UpdateBreadcrumbs);
+            }
+
+            EditorEvent::ExcerptsAdded { .. } | EditorEvent::ExcerptsRemoved { .. } => {
+                f(ItemEvent::Edit);
+            }
+
+            _ => {}
+        }
+    }
+
     fn deserialize(
-        project: ModelHandle<Project>,
-        _workspace: WeakViewHandle<Workspace>,
+        project: Model<Project>,
+        _workspace: WeakView<Workspace>,
         workspace_id: workspace::WorkspaceId,
         item_id: ItemId,
         cx: &mut ViewContext<Pane>,
-    ) -> Task<Result<ViewHandle<Self>>> {
+    ) -> Task<Result<View<Self>>> {
         let project_item: Result<_> = project.update(cx, |project, cx| {
             // Look up the path with this key associated, create a self with that path
             let path = DB
@@ -876,10 +909,11 @@ impl Item for Editor {
                     let (_, project_item) = project_item.await?;
                     let buffer = project_item
                         .downcast::<Buffer>()
-                        .context("Project item at stored path was not a buffer")?;
+                        .map_err(|_| anyhow!("Project item at stored path was not a buffer"))?;
                     Ok(pane.update(&mut cx, |_, cx| {
-                        cx.add_view(|cx| {
+                        cx.new_view(|cx| {
                             let mut editor = Editor::for_buffer(buffer, Some(project), cx);
+
                             editor.read_scroll_position_from_db(item_id, workspace_id, cx);
                             editor
                         })
@@ -894,36 +928,20 @@ impl ProjectItem for Editor {
     type Item = Buffer;
 
     fn for_project_item(
-        project: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         cx: &mut ViewContext<Self>,
     ) -> Self {
         Self::for_buffer(buffer, Some(project), cx)
     }
 }
 
+impl EventEmitter<SearchEvent> for Editor {}
+
 pub(crate) enum BufferSearchHighlights {}
 impl SearchableItem for Editor {
     type Match = Range<Anchor>;
 
-    fn to_search_event(
-        &mut self,
-        event: &Self::Event,
-        _: &mut ViewContext<Self>,
-    ) -> Option<SearchEvent> {
-        match event {
-            Event::BufferEdited => Some(SearchEvent::MatchesInvalidated),
-            Event::SelectionsChanged { .. } => {
-                if self.selections.disjoint_anchors().len() == 1 {
-                    Some(SearchEvent::ActiveMatchChanged)
-                } else {
-                    None
-                }
-            }
-            _ => None,
-        }
-    }
-
     fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
         self.clear_background_highlights::<BufferSearchHighlights>(cx);
     }
@@ -931,13 +949,13 @@ impl SearchableItem for Editor {
     fn update_matches(&mut self, matches: Vec<Range<Anchor>>, cx: &mut ViewContext<Self>) {
         self.highlight_background::<BufferSearchHighlights>(
             matches,
-            |theme| theme.search.match_background,
+            |theme| theme.search_match_background,
             cx,
         );
     }
 
     fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
-        let setting = settings::get::<EditorSettings>(cx).seed_search_query_from_cursor;
+        let setting = EditorSettings::get_global(cx).seed_search_query_from_cursor;
         let snapshot = &self.snapshot(cx).buffer_snapshot;
         let selection = self.selections.newest::<usize>(cx);
 
@@ -1060,7 +1078,7 @@ impl SearchableItem for Editor {
         cx: &mut ViewContext<Self>,
     ) -> Task<Vec<Range<Anchor>>> {
         let buffer = self.buffer().read(cx).snapshot(cx);
-        cx.background().spawn(async move {
+        cx.background_executor().spawn(async move {
             let mut ranges = Vec::new();
             if let Some((_, _, excerpt_buffer)) = buffer.as_singleton() {
                 ranges.extend(
@@ -1153,7 +1171,7 @@ impl CursorPosition {
         }
     }
 
-    fn update_position(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
+    fn update_position(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
         let editor = editor.read(cx);
         let buffer = editor.buffer().read(cx).snapshot(cx);
 
@@ -1174,18 +1192,9 @@ impl CursorPosition {
     }
 }
 
-impl Entity for CursorPosition {
-    type Event = ();
-}
-
-impl View for CursorPosition {
-    fn ui_name() -> &'static str {
-        "CursorPosition"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        if let Some(position) = self.position {
-            let theme = &theme::current(cx).workspace.status_bar;
+impl Render for CursorPosition {
+    fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
+        div().when_some(self.position, |el, position| {
             let mut text = format!(
                 "{}{FILE_ROW_COLUMN_DELIMITER}{}",
                 position.row + 1,
@@ -1194,10 +1203,9 @@ impl View for CursorPosition {
             if self.selected_count > 0 {
                 write!(text, " ({} selected)", self.selected_count).unwrap();
             }
-            Label::new(text, theme.cursor_position.clone()).into_any()
-        } else {
-            Empty::new().into_any()
-        }
+
+            el.child(Label::new(text).size(LabelSize::Small))
+        })
     }
 }
 
@@ -1220,7 +1228,7 @@ impl StatusItemView for CursorPosition {
 }
 
 fn path_for_buffer<'a>(
-    buffer: &ModelHandle<MultiBuffer>,
+    buffer: &Model<MultiBuffer>,
     height: usize,
     include_filename: bool,
     cx: &'a AppContext,
@@ -2,9 +2,10 @@ use crate::{
     display_map::DisplaySnapshot,
     element::PointForPosition,
     hover_popover::{self, InlayHover},
-    Anchor, DisplayPoint, Editor, EditorSnapshot, InlayId, SelectPhase,
+    Anchor, DisplayPoint, Editor, EditorSnapshot, GoToDefinition, GoToTypeDefinition, InlayId,
+    SelectPhase,
 };
-use gpui::{Task, ViewContext};
+use gpui::{px, Task, ViewContext};
 use language::{Bias, ToOffset};
 use lsp::LanguageServerId;
 use project::{
@@ -12,6 +13,7 @@ use project::{
     ResolveState,
 };
 use std::ops::Range;
+use theme::ActiveTheme as _;
 use util::TryFutureExt;
 
 #[derive(Debug, Default)]
@@ -168,7 +170,7 @@ pub fn update_inlay_link_and_hover_points(
     editor: &mut Editor,
     cmd_held: bool,
     shift_held: bool,
-    cx: &mut ViewContext<'_, '_, Editor>,
+    cx: &mut ViewContext<'_, Editor>,
 ) {
     let hovered_offset = if point_for_position.column_overshoot_after_line_end == 0 {
         Some(snapshot.display_point_to_inlay_offset(point_for_position.exact_unclipped, Bias::Left))
@@ -396,8 +398,8 @@ pub fn show_link_definition(
             let result = match &trigger_point {
                 TriggerPoint::Text(_) => {
                     // query the LSP for definition info
-                    cx.update(|cx| {
-                        project.update(cx, |project, cx| match definition_kind {
+                    project
+                        .update(&mut cx, |project, cx| match definition_kind {
                             LinkDefinitionKind::Symbol => {
                                 project.definition(&buffer, buffer_position, cx)
                             }
@@ -405,29 +407,30 @@ pub fn show_link_definition(
                             LinkDefinitionKind::Type => {
                                 project.type_definition(&buffer, buffer_position, cx)
                             }
+                        })?
+                        .await
+                        .ok()
+                        .map(|definition_result| {
+                            (
+                                definition_result.iter().find_map(|link| {
+                                    link.origin.as_ref().map(|origin| {
+                                        let start = snapshot.buffer_snapshot.anchor_in_excerpt(
+                                            excerpt_id.clone(),
+                                            origin.range.start,
+                                        );
+                                        let end = snapshot.buffer_snapshot.anchor_in_excerpt(
+                                            excerpt_id.clone(),
+                                            origin.range.end,
+                                        );
+                                        RangeInEditor::Text(start..end)
+                                    })
+                                }),
+                                definition_result
+                                    .into_iter()
+                                    .map(GoToDefinitionLink::Text)
+                                    .collect(),
+                            )
                         })
-                    })
-                    .await
-                    .ok()
-                    .map(|definition_result| {
-                        (
-                            definition_result.iter().find_map(|link| {
-                                link.origin.as_ref().map(|origin| {
-                                    let start = snapshot
-                                        .buffer_snapshot
-                                        .anchor_in_excerpt(excerpt_id.clone(), origin.range.start);
-                                    let end = snapshot
-                                        .buffer_snapshot
-                                        .anchor_in_excerpt(excerpt_id.clone(), origin.range.end);
-                                    RangeInEditor::Text(start..end)
-                                })
-                            }),
-                            definition_result
-                                .into_iter()
-                                .map(GoToDefinitionLink::Text)
-                                .collect(),
-                        )
-                    })
                 }
                 TriggerPoint::InlayHint(highlight, lsp_location, server_id) => Some((
                     Some(RangeInEditor::Inlay(highlight.clone())),
@@ -483,8 +486,14 @@ pub fn show_link_definition(
                         });
 
                     if any_definition_does_not_contain_current_location {
-                        // Highlight symbol using theme link definition highlight style
-                        let style = theme::current(cx).editor.link_definition;
+                        let style = gpui::HighlightStyle {
+                            underline: Some(gpui::UnderlineStyle {
+                                thickness: px(1.),
+                                ..Default::default()
+                            }),
+                            color: Some(cx.theme().colors().link_text_hover),
+                            ..Default::default()
+                        };
                         let highlight_range =
                             symbol_range.unwrap_or_else(|| match &trigger_point {
                                 TriggerPoint::Text(trigger_anchor) => {
@@ -575,8 +584,8 @@ fn go_to_fetched_definition_of_kind(
 
     let is_correct_kind = cached_definitions_kind == Some(kind);
     if !cached_definitions.is_empty() && is_correct_kind {
-        if !editor.focused {
-            cx.focus_self();
+        if !editor.focus_handle.is_focused(cx) {
+            cx.focus(&editor.focus_handle);
         }
 
         editor.navigate_to_definitions(cached_definitions, split, cx);
@@ -592,8 +601,8 @@ fn go_to_fetched_definition_of_kind(
 
         if point.as_valid().is_some() {
             match kind {
-                LinkDefinitionKind::Symbol => editor.go_to_definition(&Default::default(), cx),
-                LinkDefinitionKind::Type => editor.go_to_type_definition(&Default::default(), cx),
+                LinkDefinitionKind::Symbol => editor.go_to_definition(&GoToDefinition, cx),
+                LinkDefinitionKind::Type => editor.go_to_type_definition(&GoToTypeDefinition, cx),
             }
         }
     }
@@ -609,10 +618,7 @@ mod tests {
         test::editor_lsp_test_context::EditorLspTestContext,
     };
     use futures::StreamExt;
-    use gpui::{
-        platform::{self, Modifiers, ModifiersChangedEvent},
-        View,
-    };
+    use gpui::{Modifiers, ModifiersChangedEvent};
     use indoc::indoc;
     use language::language_settings::InlayHintSettings;
     use lsp::request::{GotoDefinition, GotoTypeDefinition};
@@ -674,7 +680,7 @@ mod tests {
             );
         });
         requests.next().await;
-        cx.foreground().run_until_parked();
+        cx.background_executor.run_until_parked();
         cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
             struct A;
             let «variable» = A;
@@ -682,10 +688,11 @@ mod tests {
 
         // Unpress shift causes highlight to go away (normal goto-definition is not valid here)
         cx.update_editor(|editor, cx| {
-            editor.modifiers_changed(
-                &platform::ModifiersChangedEvent {
+            crate::element::EditorElement::modifiers_changed(
+                editor,
+                &ModifiersChangedEvent {
                     modifiers: Modifiers {
-                        cmd: true,
+                        command: true,
                         ..Default::default()
                     },
                     ..Default::default()
@@ -725,7 +732,7 @@ mod tests {
             go_to_fetched_type_definition(editor, PointForPosition::valid(hover_point), false, cx);
         });
         requests.next().await;
-        cx.foreground().run_until_parked();
+        cx.background_executor.run_until_parked();
 
         cx.assert_editor_state(indoc! {"
             struct «Aˇ»;
@@ -747,23 +754,23 @@ mod tests {
         .await;
 
         cx.set_state(indoc! {"
-            fn ˇtest() { do_work(); }
-            fn do_work() { test(); }
-        "});
+                fn ˇtest() { do_work(); }
+                fn do_work() { test(); }
+            "});
 
         // Basic hold cmd, expect highlight in region if response contains definition
         let hover_point = cx.display_point(indoc! {"
-            fn test() { do_wˇork(); }
-            fn do_work() { test(); }
-        "});
+                fn test() { do_wˇork(); }
+                fn do_work() { test(); }
+            "});
         let symbol_range = cx.lsp_range(indoc! {"
-            fn test() { «do_work»(); }
-            fn do_work() { test(); }
-        "});
+                fn test() { «do_work»(); }
+                fn do_work() { test(); }
+            "});
         let target_range = cx.lsp_range(indoc! {"
-            fn test() { do_work(); }
-            fn «do_work»() { test(); }
-        "});
+                fn test() { do_work(); }
+                fn «do_work»() { test(); }
+            "});
 
         let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
             Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
@@ -786,22 +793,22 @@ mod tests {
             );
         });
         requests.next().await;
-        cx.foreground().run_until_parked();
+        cx.background_executor.run_until_parked();
         cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
-            fn test() { «do_work»(); }
-            fn do_work() { test(); }
-        "});
+                fn test() { «do_work»(); }
+                fn do_work() { test(); }
+            "});
 
         // Unpress cmd causes highlight to go away
         cx.update_editor(|editor, cx| {
-            editor.modifiers_changed(&Default::default(), cx);
+            crate::element::EditorElement::modifiers_changed(editor, &Default::default(), cx);
         });
 
         // Assert no link highlights
         cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
-            fn test() { do_work(); }
-            fn do_work() { test(); }
-        "});
+                fn test() { do_work(); }
+                fn do_work() { test(); }
+            "});
 
         // Response without source range still highlights word
         cx.update_editor(|editor, _| editor.link_go_to_definition_state.last_trigger_point = None);
@@ -826,18 +833,18 @@ mod tests {
             );
         });
         requests.next().await;
-        cx.foreground().run_until_parked();
+        cx.background_executor.run_until_parked();
 
         cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
-            fn test() { «do_work»(); }
-            fn do_work() { test(); }
-        "});
+                fn test() { «do_work»(); }
+                fn do_work() { test(); }
+            "});
 
         // Moving mouse to location with no response dismisses highlight
         let hover_point = cx.display_point(indoc! {"
-            fˇn test() { do_work(); }
-            fn do_work() { test(); }
-        "});
+                fˇn test() { do_work(); }
+                fn do_work() { test(); }
+            "});
         let mut requests = cx
             .lsp
             .handle_request::<GotoDefinition, _, _>(move |_, _| async move {
@@ -854,19 +861,19 @@ mod tests {
             );
         });
         requests.next().await;
-        cx.foreground().run_until_parked();
+        cx.background_executor.run_until_parked();
 
         // Assert no link highlights
         cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
-            fn test() { do_work(); }
-            fn do_work() { test(); }
-        "});
+                fn test() { do_work(); }
+                fn do_work() { test(); }
+            "});
 
         // Move mouse without cmd and then pressing cmd triggers highlight
         let hover_point = cx.display_point(indoc! {"
-            fn test() { do_work(); }
-            fn do_work() { teˇst(); }
-        "});
+                fn test() { do_work(); }
+                fn do_work() { teˇst(); }
+            "});
         cx.update_editor(|editor, cx| {
             update_go_to_definition_link(
                 editor,
@@ -876,22 +883,22 @@ mod tests {
                 cx,
             );
         });
-        cx.foreground().run_until_parked();
+        cx.background_executor.run_until_parked();
 
         // Assert no link highlights
         cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
-            fn test() { do_work(); }
-            fn do_work() { test(); }
-        "});
+                fn test() { do_work(); }
+                fn do_work() { test(); }
+            "});
 
         let symbol_range = cx.lsp_range(indoc! {"
-            fn test() { do_work(); }
-            fn do_work() { «test»(); }
-        "});
+                fn test() { do_work(); }
+                fn do_work() { «test»(); }
+            "});
         let target_range = cx.lsp_range(indoc! {"
-            fn «test»() { do_work(); }
-            fn do_work() { test(); }
-        "});
+                fn «test»() { do_work(); }
+                fn do_work() { test(); }
+            "});
 
         let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
             Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
@@ -904,10 +911,11 @@ mod tests {
             ])))
         });
         cx.update_editor(|editor, cx| {
-            editor.modifiers_changed(
+            crate::element::EditorElement::modifiers_changed(
+                editor,
                 &ModifiersChangedEvent {
                     modifiers: Modifiers {
-                        cmd: true,
+                        command: true,
                         ..Default::default()
                     },
                 },
@@ -915,21 +923,21 @@ mod tests {
             );
         });
         requests.next().await;
-        cx.foreground().run_until_parked();
+        cx.background_executor.run_until_parked();
 
         cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
-            fn test() { do_work(); }
-            fn do_work() { «test»(); }
-        "});
+                fn test() { do_work(); }
+                fn do_work() { «test»(); }
+            "});
 
         // Deactivating the window dismisses the highlight
         cx.update_workspace(|workspace, cx| {
-            workspace.on_window_activation_changed(false, cx);
+            workspace.on_window_activation_changed(cx);
         });
         cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
-            fn test() { do_work(); }
-            fn do_work() { test(); }
-        "});
+                fn test() { do_work(); }
+                fn do_work() { test(); }
+            "});
 
         // Moving the mouse restores the highlights.
         cx.update_editor(|editor, cx| {
@@ -941,17 +949,17 @@ mod tests {
                 cx,
             );
         });
-        cx.foreground().run_until_parked();
+        cx.background_executor.run_until_parked();
         cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
-            fn test() { do_work(); }
-            fn do_work() { «test»(); }
-        "});
+                fn test() { do_work(); }
+                fn do_work() { «test»(); }
+            "});
 
         // Moving again within the same symbol range doesn't re-request
         let hover_point = cx.display_point(indoc! {"
-            fn test() { do_work(); }
-            fn do_work() { tesˇt(); }
-        "});
+                fn test() { do_work(); }
+                fn do_work() { tesˇt(); }
+            "});
         cx.update_editor(|editor, cx| {
             update_go_to_definition_link(
                 editor,
@@ -961,11 +969,11 @@ mod tests {
                 cx,
             );
         });
-        cx.foreground().run_until_parked();
+        cx.background_executor.run_until_parked();
         cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
-            fn test() { do_work(); }
-            fn do_work() { «test»(); }
-        "});
+                fn test() { do_work(); }
+                fn do_work() { «test»(); }
+            "});
 
         // Cmd click with existing definition doesn't re-request and dismisses highlight
         cx.update_editor(|editor, cx| {
@@ -978,27 +986,27 @@ mod tests {
                 // the cached location instead
                 Ok(Some(lsp::GotoDefinitionResponse::Link(vec![])))
             });
-        cx.foreground().run_until_parked();
+        cx.background_executor.run_until_parked();
         cx.assert_editor_state(indoc! {"
-            fn «testˇ»() { do_work(); }
-            fn do_work() { test(); }
-        "});
+                fn «testˇ»() { do_work(); }
+                fn do_work() { test(); }
+            "});
 
         // Assert no link highlights after jump
         cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
-            fn test() { do_work(); }
-            fn do_work() { test(); }
-        "});
+                fn test() { do_work(); }
+                fn do_work() { test(); }
+            "});
 
         // Cmd click without existing definition requests and jumps
         let hover_point = cx.display_point(indoc! {"
-            fn test() { do_wˇork(); }
-            fn do_work() { test(); }
-        "});
+                fn test() { do_wˇork(); }
+                fn do_work() { test(); }
+            "});
         let target_range = cx.lsp_range(indoc! {"
-            fn test() { do_work(); }
-            fn «do_work»() { test(); }
-        "});
+                fn test() { do_work(); }
+                fn «do_work»() { test(); }
+            "});
 
         let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
             Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
@@ -1014,22 +1022,22 @@ mod tests {
             go_to_fetched_definition(editor, PointForPosition::valid(hover_point), false, cx);
         });
         requests.next().await;
-        cx.foreground().run_until_parked();
+        cx.background_executor.run_until_parked();
         cx.assert_editor_state(indoc! {"
-            fn test() { do_work(); }
-            fn «do_workˇ»() { test(); }
-        "});
+                fn test() { do_work(); }
+                fn «do_workˇ»() { test(); }
+            "});
 
         // 1. We have a pending selection, mouse point is over a symbol that we have a response for, hitting cmd and nothing happens
         // 2. Selection is completed, hovering
         let hover_point = cx.display_point(indoc! {"
-            fn test() { do_wˇork(); }
-            fn do_work() { test(); }
-        "});
+                fn test() { do_wˇork(); }
+                fn do_work() { test(); }
+            "});
         let target_range = cx.lsp_range(indoc! {"
-            fn test() { do_work(); }
-            fn «do_work»() { test(); }
-        "});
+                fn test() { do_work(); }
+                fn «do_work»() { test(); }
+            "});
         let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
             Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
                 lsp::LocationLink {
@@ -1043,9 +1051,9 @@ mod tests {
 
         // create a pending selection
         let selection_range = cx.ranges(indoc! {"
-            fn «test() { do_w»ork(); }
-            fn do_work() { test(); }
-        "})[0]
+                fn «test() { do_w»ork(); }
+                fn do_work() { test(); }
+            "})[0]
             .clone();
         cx.update_editor(|editor, cx| {
             let snapshot = editor.buffer().read(cx).snapshot(cx);
@@ -1064,13 +1072,13 @@ mod tests {
                 cx,
             );
         });
-        cx.foreground().run_until_parked();
+        cx.background_executor.run_until_parked();
         assert!(requests.try_next().is_err());
         cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
-            fn test() { do_work(); }
-            fn do_work() { test(); }
-        "});
-        cx.foreground().run_until_parked();
+                fn test() { do_work(); }
+                fn do_work() { test(); }
+            "});
+        cx.background_executor.run_until_parked();
     }
 
     #[gpui::test]
@@ -1093,28 +1101,28 @@ mod tests {
         )
         .await;
         cx.set_state(indoc! {"
-            struct TestStruct;
+                struct TestStruct;
 
-            fn main() {
-                let variableˇ = TestStruct;
-            }
-        "});
+                fn main() {
+                    let variableˇ = TestStruct;
+                }
+            "});
         let hint_start_offset = cx.ranges(indoc! {"
-            struct TestStruct;
+                struct TestStruct;
 
-            fn main() {
-                let variableˇ = TestStruct;
-            }
-        "})[0]
+                fn main() {
+                    let variableˇ = TestStruct;
+                }
+            "})[0]
             .start;
         let hint_position = cx.to_lsp(hint_start_offset);
         let target_range = cx.lsp_range(indoc! {"
-            struct «TestStruct»;
+                struct «TestStruct»;
 
-            fn main() {
-                let variable = TestStruct;
-            }
-        "});
+                fn main() {
+                    let variable = TestStruct;
+                }
+            "});
 
         let expected_uri = cx.buffer_lsp_url.clone();
         let hint_label = ": TestStruct";
@@ -1144,7 +1152,7 @@ mod tests {
             })
             .next()
             .await;
-        cx.foreground().run_until_parked();
+        cx.background_executor.run_until_parked();
         cx.update_editor(|editor, cx| {
             let expected_layers = vec![hint_label.to_string()];
             assert_eq!(expected_layers, cached_hint_labels(editor));
@@ -1153,12 +1161,12 @@ mod tests {
 
         let inlay_range = cx
             .ranges(indoc! {"
-            struct TestStruct;
+                struct TestStruct;
 
-            fn main() {
-                let variable« »= TestStruct;
-            }
-        "})
+                fn main() {
+                    let variable« »= TestStruct;
+                }
+            "})
             .get(0)
             .cloned()
             .unwrap();
@@ -1190,7 +1198,7 @@ mod tests {
                 cx,
             );
         });
-        cx.foreground().run_until_parked();
+        cx.background_executor.run_until_parked();
         cx.update_editor(|editor, cx| {
             let snapshot = editor.snapshot(cx);
             let actual_highlights = snapshot
@@ -1210,10 +1218,11 @@ mod tests {
 
         // Unpress cmd causes highlight to go away
         cx.update_editor(|editor, cx| {
-            editor.modifiers_changed(
-                &platform::ModifiersChangedEvent {
+            crate::element::EditorElement::modifiers_changed(
+                editor,
+                &ModifiersChangedEvent {
                     modifiers: Modifiers {
-                        cmd: false,
+                        command: false,
                         ..Default::default()
                     },
                     ..Default::default()
@@ -1223,21 +1232,22 @@ mod tests {
         });
         // Assert no link highlights
         cx.update_editor(|editor, cx| {
-            let snapshot = editor.snapshot(cx);
-            let actual_ranges = snapshot
-                .text_highlight_ranges::<LinkGoToDefinitionState>()
-                .map(|ranges| ranges.as_ref().clone().1)
-                .unwrap_or_default();
+                let snapshot = editor.snapshot(cx);
+                let actual_ranges = snapshot
+                    .text_highlight_ranges::<LinkGoToDefinitionState>()
+                    .map(|ranges| ranges.as_ref().clone().1)
+                    .unwrap_or_default();
 
-            assert!(actual_ranges.is_empty(), "When no cmd is pressed, should have no hint label selected, but got: {actual_ranges:?}");
-        });
+                assert!(actual_ranges.is_empty(), "When no cmd is pressed, should have no hint label selected, but got: {actual_ranges:?}");
+            });
 
         // Cmd+click without existing definition requests and jumps
         cx.update_editor(|editor, cx| {
-            editor.modifiers_changed(
-                &platform::ModifiersChangedEvent {
+            crate::element::EditorElement::modifiers_changed(
+                editor,
+                &ModifiersChangedEvent {
                     modifiers: Modifiers {
-                        cmd: true,
+                        command: true,
                         ..Default::default()
                     },
                     ..Default::default()
@@ -1253,17 +1263,17 @@ mod tests {
                 cx,
             );
         });
-        cx.foreground().run_until_parked();
+        cx.background_executor.run_until_parked();
         cx.update_editor(|editor, cx| {
             go_to_fetched_type_definition(editor, hint_hover_position, false, cx);
         });
-        cx.foreground().run_until_parked();
+        cx.background_executor.run_until_parked();
         cx.assert_editor_state(indoc! {"
-            struct «TestStructˇ»;
+                struct «TestStructˇ»;
 
-            fn main() {
-                let variable = TestStruct;
-            }
-        "});
+                fn main() {
+                    let variable = TestStruct;
+                }
+            "});
     }
 }

crates/editor/src/mouse_context_menu.rs 🔗

@@ -2,17 +2,22 @@ use crate::{
     DisplayPoint, Editor, EditorMode, FindAllReferences, GoToDefinition, GoToTypeDefinition,
     Rename, RevealInFinder, SelectMode, ToggleCodeActions,
 };
-use context_menu::ContextMenuItem;
-use gpui::{elements::AnchorCorner, geometry::vector::Vector2F, ViewContext};
+use gpui::{DismissEvent, Pixels, Point, Subscription, View, ViewContext};
+
+pub struct MouseContextMenu {
+    pub(crate) position: Point<Pixels>,
+    pub(crate) context_menu: View<ui::ContextMenu>,
+    _subscription: Subscription,
+}
 
 pub fn deploy_context_menu(
     editor: &mut Editor,
-    position: Vector2F,
+    position: Point<Pixels>,
     point: DisplayPoint,
     cx: &mut ViewContext<Editor>,
 ) {
-    if !editor.focused {
-        cx.focus_self();
+    if !editor.is_focused(cx) {
+        editor.focus(cx);
     }
 
     // Don't show context menu for inline editors
@@ -31,26 +36,34 @@ pub fn deploy_context_menu(
         s.set_pending_display_range(point..point, SelectMode::Character);
     });
 
-    editor.mouse_context_menu.update(cx, |menu, cx| {
-        menu.show(
-            position,
-            AnchorCorner::TopLeft,
-            vec![
-                ContextMenuItem::action("Rename Symbol", Rename),
-                ContextMenuItem::action("Go to Definition", GoToDefinition),
-                ContextMenuItem::action("Go to Type Definition", GoToTypeDefinition),
-                ContextMenuItem::action("Find All References", FindAllReferences),
-                ContextMenuItem::action(
-                    "Code Actions",
-                    ToggleCodeActions {
-                        deployed_from_indicator: false,
-                    },
-                ),
-                ContextMenuItem::Separator,
-                ContextMenuItem::action("Reveal in Finder", RevealInFinder),
-            ],
-            cx,
-        );
+    let context_menu = ui::ContextMenu::build(cx, |menu, _cx| {
+        menu.action("Rename Symbol", Box::new(Rename))
+            .action("Go to Definition", Box::new(GoToDefinition))
+            .action("Go to Type Definition", Box::new(GoToTypeDefinition))
+            .action("Find All References", Box::new(FindAllReferences))
+            .action(
+                "Code Actions",
+                Box::new(ToggleCodeActions {
+                    deployed_from_indicator: false,
+                }),
+            )
+            .separator()
+            .action("Reveal in Finder", Box::new(RevealInFinder))
+    });
+    let context_menu_focus = context_menu.focus_handle(cx);
+    cx.focus(&context_menu_focus);
+
+    let _subscription = cx.subscribe(&context_menu, move |this, _, _event: &DismissEvent, cx| {
+        this.mouse_context_menu.take();
+        if context_menu_focus.contains_focused(cx) {
+            this.focus(cx);
+        }
+    });
+
+    editor.mouse_context_menu = Some(MouseContextMenu {
+        position,
+        context_menu,
+        _subscription,
     });
     cx.notify();
 }
@@ -84,6 +97,7 @@ mod tests {
                 do_wˇork();
             }
         "});
+        cx.editor(|editor, _app| assert!(editor.mouse_context_menu.is_none()));
         cx.update_editor(|editor, cx| deploy_context_menu(editor, Default::default(), point, cx));
 
         cx.assert_editor_state(indoc! {"
@@ -91,6 +105,6 @@ mod tests {
                 do_wˇork();
             }
         "});
-        cx.editor(|editor, app| assert!(editor.mouse_context_menu.read(app).visible()));
+        cx.editor(|editor, _app| assert!(editor.mouse_context_menu.is_some()));
     }
 }

crates/editor/src/movement.rs 🔗

@@ -1,7 +1,8 @@
 use super::{Bias, DisplayPoint, DisplaySnapshot, SelectionGoal, ToDisplayPoint};
 use crate::{char_kind, CharKind, EditorStyle, ToOffset, ToPoint};
-use gpui::{FontCache, TextLayoutCache};
+use gpui::{px, Pixels, TextSystem};
 use language::Point;
+
 use std::{ops::Range, sync::Arc};
 
 #[derive(Debug, PartialEq)]
@@ -13,9 +14,9 @@ pub enum FindRange {
 /// TextLayoutDetails encompasses everything we need to move vertically
 /// taking into account variable width characters.
 pub struct TextLayoutDetails {
-    pub font_cache: Arc<FontCache>,
-    pub text_layout_cache: Arc<TextLayoutCache>,
+    pub text_system: Arc<TextSystem>,
     pub editor_style: EditorStyle,
+    pub rem_size: Pixels,
 }
 
 pub fn left(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
@@ -94,10 +95,10 @@ pub fn up_by_rows(
     text_layout_details: &TextLayoutDetails,
 ) -> (DisplayPoint, SelectionGoal) {
     let mut goal_x = match goal {
-        SelectionGoal::HorizontalPosition(x) => x,
-        SelectionGoal::WrappedHorizontalPosition((_, x)) => x,
-        SelectionGoal::HorizontalRange { end, .. } => end,
-        _ => map.x_for_point(start, text_layout_details),
+        SelectionGoal::HorizontalPosition(x) => x.into(), // todo!("Can the fields in SelectionGoal by Pixels? We should extract a geometry crate and depend on that.")
+        SelectionGoal::WrappedHorizontalPosition((_, x)) => x.into(),
+        SelectionGoal::HorizontalRange { end, .. } => end.into(),
+        _ => map.x_for_display_point(start, text_layout_details),
     };
 
     let prev_row = start.row().saturating_sub(row_count);
@@ -106,19 +107,22 @@ pub fn up_by_rows(
         Bias::Left,
     );
     if point.row() < start.row() {
-        *point.column_mut() = map.column_for_x(point.row(), goal_x, text_layout_details)
+        *point.column_mut() = map.display_column_for_x(point.row(), goal_x, text_layout_details)
     } else if preserve_column_at_start {
         return (start, goal);
     } else {
         point = DisplayPoint::new(0, 0);
-        goal_x = 0.0;
+        goal_x = px(0.);
     }
 
     let mut clipped_point = map.clip_point(point, Bias::Left);
     if clipped_point.row() < point.row() {
         clipped_point = map.clip_point(point, Bias::Right);
     }
-    (clipped_point, SelectionGoal::HorizontalPosition(goal_x))
+    (
+        clipped_point,
+        SelectionGoal::HorizontalPosition(goal_x.into()),
+    )
 }
 
 pub fn down_by_rows(
@@ -130,28 +134,31 @@ pub fn down_by_rows(
     text_layout_details: &TextLayoutDetails,
 ) -> (DisplayPoint, SelectionGoal) {
     let mut goal_x = match goal {
-        SelectionGoal::HorizontalPosition(x) => x,
-        SelectionGoal::WrappedHorizontalPosition((_, x)) => x,
-        SelectionGoal::HorizontalRange { end, .. } => end,
-        _ => map.x_for_point(start, text_layout_details),
+        SelectionGoal::HorizontalPosition(x) => x.into(),
+        SelectionGoal::WrappedHorizontalPosition((_, x)) => x.into(),
+        SelectionGoal::HorizontalRange { end, .. } => end.into(),
+        _ => map.x_for_display_point(start, text_layout_details),
     };
 
     let new_row = start.row() + row_count;
     let mut point = map.clip_point(DisplayPoint::new(new_row, 0), Bias::Right);
     if point.row() > start.row() {
-        *point.column_mut() = map.column_for_x(point.row(), goal_x, text_layout_details)
+        *point.column_mut() = map.display_column_for_x(point.row(), goal_x, text_layout_details)
     } else if preserve_column_at_end {
         return (start, goal);
     } else {
         point = map.max_point();
-        goal_x = map.x_for_point(point, text_layout_details)
+        goal_x = map.x_for_display_point(point, text_layout_details)
     }
 
     let mut clipped_point = map.clip_point(point, Bias::Right);
     if clipped_point.row() > point.row() {
         clipped_point = map.clip_point(point, Bias::Left);
     }
-    (clipped_point, SelectionGoal::HorizontalPosition(goal_x))
+    (
+        clipped_point,
+        SelectionGoal::HorizontalPosition(goal_x.into()),
+    )
 }
 
 pub fn line_beginning(
@@ -453,6 +460,7 @@ mod tests {
         test::{editor_test_context::EditorTestContext, marked_display_snapshot},
         Buffer, DisplayMap, ExcerptRange, InlayId, MultiBuffer,
     };
+    use gpui::{font, Context as _};
     use project::Project;
     use settings::SettingsStore;
     use util::post_inc;
@@ -563,19 +571,12 @@ mod tests {
         init_test(cx);
 
         let input_text = "abcdefghijklmnopqrstuvwxys";
-        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 = font("Helvetica");
+        let font_size = px(14.0);
         let buffer = MultiBuffer::build_simple(input_text, cx);
         let buffer_snapshot = buffer.read(cx).snapshot(cx);
         let display_map =
-            cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx));
+            cx.new_model(|cx| DisplayMap::new(buffer, font, font_size, None, 1, 1, cx));
 
         // add all kinds of inlays between two word boundaries: we should be able to cross them all, when looking for another boundary
         let mut id = 0;
@@ -756,22 +757,15 @@ mod tests {
         let mut cx = EditorTestContext::new(cx).await;
         let editor = cx.editor.clone();
         let window = cx.window.clone();
-        cx.update_window(window, |cx| {
+        _ = cx.update_window(window, |_, cx| {
             let text_layout_details =
-                editor.read_with(cx, |editor, cx| editor.text_layout_details(cx));
+                editor.update(cx, |editor, cx| editor.text_layout_details(cx));
 
-            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 = font("Helvetica");
 
             let buffer =
-                cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "abc\ndefg\nhijkl\nmn"));
-            let multibuffer = cx.add_model(|cx| {
+                cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abc\ndefg\nhijkl\nmn"));
+            let multibuffer = cx.new_model(|cx| {
                 let mut multibuffer = MultiBuffer::new(0);
                 multibuffer.push_excerpts(
                     buffer.clone(),
@@ -790,19 +784,20 @@ mod tests {
                 multibuffer
             });
             let display_map =
-                cx.add_model(|cx| DisplayMap::new(multibuffer, font_id, 14.0, None, 2, 2, cx));
+                cx.new_model(|cx| DisplayMap::new(multibuffer, font, px(14.0), None, 2, 2, cx));
             let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
 
             assert_eq!(snapshot.text(), "\n\nabc\ndefg\n\n\nhijkl\nmn");
 
-            let col_2_x = snapshot.x_for_point(DisplayPoint::new(2, 2), &text_layout_details);
+            let col_2_x =
+                snapshot.x_for_display_point(DisplayPoint::new(2, 2), &text_layout_details);
 
             // Can't move up into the first excerpt's header
             assert_eq!(
                 up(
                     &snapshot,
                     DisplayPoint::new(2, 2),
-                    SelectionGoal::HorizontalPosition(col_2_x),
+                    SelectionGoal::HorizontalPosition(col_2_x.0),
                     false,
                     &text_layout_details
                 ),
@@ -825,67 +820,70 @@ mod tests {
                 ),
             );
 
-            let col_4_x = snapshot.x_for_point(DisplayPoint::new(3, 4), &text_layout_details);
+            let col_4_x =
+                snapshot.x_for_display_point(DisplayPoint::new(3, 4), &text_layout_details);
 
             // Move up and down within first excerpt
             assert_eq!(
                 up(
                     &snapshot,
                     DisplayPoint::new(3, 4),
-                    SelectionGoal::HorizontalPosition(col_4_x),
+                    SelectionGoal::HorizontalPosition(col_4_x.0),
                     false,
                     &text_layout_details
                 ),
                 (
                     DisplayPoint::new(2, 3),
-                    SelectionGoal::HorizontalPosition(col_4_x)
+                    SelectionGoal::HorizontalPosition(col_4_x.0)
                 ),
             );
             assert_eq!(
                 down(
                     &snapshot,
                     DisplayPoint::new(2, 3),
-                    SelectionGoal::HorizontalPosition(col_4_x),
+                    SelectionGoal::HorizontalPosition(col_4_x.0),
                     false,
                     &text_layout_details
                 ),
                 (
                     DisplayPoint::new(3, 4),
-                    SelectionGoal::HorizontalPosition(col_4_x)
+                    SelectionGoal::HorizontalPosition(col_4_x.0)
                 ),
             );
 
-            let col_5_x = snapshot.x_for_point(DisplayPoint::new(6, 5), &text_layout_details);
+            let col_5_x =
+                snapshot.x_for_display_point(DisplayPoint::new(6, 5), &text_layout_details);
 
             // Move up and down across second excerpt's header
             assert_eq!(
                 up(
                     &snapshot,
                     DisplayPoint::new(6, 5),
-                    SelectionGoal::HorizontalPosition(col_5_x),
+                    SelectionGoal::HorizontalPosition(col_5_x.0),
                     false,
                     &text_layout_details
                 ),
                 (
                     DisplayPoint::new(3, 4),
-                    SelectionGoal::HorizontalPosition(col_5_x)
+                    SelectionGoal::HorizontalPosition(col_5_x.0)
                 ),
             );
             assert_eq!(
                 down(
                     &snapshot,
                     DisplayPoint::new(3, 4),
-                    SelectionGoal::HorizontalPosition(col_5_x),
+                    SelectionGoal::HorizontalPosition(col_5_x.0),
                     false,
                     &text_layout_details
                 ),
                 (
                     DisplayPoint::new(6, 5),
-                    SelectionGoal::HorizontalPosition(col_5_x)
+                    SelectionGoal::HorizontalPosition(col_5_x.0)
                 ),
             );
 
-            let max_point_x = snapshot.x_for_point(DisplayPoint::new(7, 2), &text_layout_details);
+            let max_point_x =
+                snapshot.x_for_display_point(DisplayPoint::new(7, 2), &text_layout_details);
 
             // Can't move down off the end
             assert_eq!(
@@ -898,28 +896,29 @@ mod tests {
                 ),
                 (
                     DisplayPoint::new(7, 2),
-                    SelectionGoal::HorizontalPosition(max_point_x)
+                    SelectionGoal::HorizontalPosition(max_point_x.0)
                 ),
             );
             assert_eq!(
                 down(
                     &snapshot,
                     DisplayPoint::new(7, 2),
-                    SelectionGoal::HorizontalPosition(max_point_x),
+                    SelectionGoal::HorizontalPosition(max_point_x.0),
                     false,
                     &text_layout_details
                 ),
                 (
                     DisplayPoint::new(7, 2),
-                    SelectionGoal::HorizontalPosition(max_point_x)
+                    SelectionGoal::HorizontalPosition(max_point_x.0)
                 ),
             );
         });
     }
 
     fn init_test(cx: &mut gpui::AppContext) {
-        cx.set_global(SettingsStore::test(cx));
-        theme::init((), cx);
+        let settings_store = SettingsStore::test(cx);
+        cx.set_global(settings_store);
+        theme::init(theme::LoadThemes::JustBase, cx);
         language::init(cx);
         crate::init(cx);
         Project::init_settings(cx);

crates/editor/src/rust_analyzer_ext.rs 🔗

@@ -1,31 +1,50 @@
 use std::sync::Arc;
 
 use anyhow::Context as _;
-use gpui::{AppContext, Task, ViewContext};
+use gpui::{Context, View, ViewContext, VisualContext, WindowContext};
 use language::Language;
 use multi_buffer::MultiBuffer;
 use project::lsp_ext_command::ExpandMacro;
 use text::ToPointUtf16;
 
-use crate::{Editor, ExpandMacroRecursively};
+use crate::{element::register_action, Editor, ExpandMacroRecursively};
 
-pub fn apply_related_actions(cx: &mut AppContext) {
-    cx.add_async_action(expand_macro_recursively);
+pub fn apply_related_actions(editor: &View<Editor>, cx: &mut WindowContext) {
+    let is_rust_related = editor.update(cx, |editor, cx| {
+        editor
+            .buffer()
+            .read(cx)
+            .all_buffers()
+            .iter()
+            .any(|b| match b.read(cx).language() {
+                Some(l) => is_rust_language(l),
+                None => false,
+            })
+    });
+
+    if is_rust_related {
+        register_action(editor, cx, expand_macro_recursively);
+    }
 }
 
 pub fn expand_macro_recursively(
     editor: &mut Editor,
     _: &ExpandMacroRecursively,
-    cx: &mut ViewContext<'_, '_, Editor>,
-) -> Option<Task<anyhow::Result<()>>> {
+    cx: &mut ViewContext<'_, Editor>,
+) {
     if editor.selections.count() == 0 {
-        return None;
+        return;
     }
-    let project = editor.project.as_ref()?;
-    let workspace = editor.workspace(cx)?;
+    let Some(project) = &editor.project else {
+        return;
+    };
+    let Some(workspace) = editor.workspace() else {
+        return;
+    };
+
     let multibuffer = editor.buffer().read(cx);
 
-    let (trigger_anchor, rust_language, server_to_query, buffer) = editor
+    let Some((trigger_anchor, rust_language, server_to_query, buffer)) = editor
         .selections
         .disjoint_anchors()
         .into_iter()
@@ -56,7 +75,10 @@ pub fn expand_macro_recursively(
                         None
                     }
                 })
-        })?;
+        })
+    else {
+        return;
+    };
 
     let project = project.clone();
     let buffer_snapshot = buffer.read(cx).snapshot();
@@ -69,7 +91,7 @@ pub fn expand_macro_recursively(
             cx,
         )
     });
-    Some(cx.spawn(|_, mut cx| async move {
+    cx.spawn(|_editor, mut cx| async move {
         let macro_expansion = expand_macro_task.await.context("expand macro")?;
         if macro_expansion.is_empty() {
             log::info!("Empty macro expansion for position {position:?}");
@@ -78,19 +100,18 @@ pub fn expand_macro_recursively(
 
         let buffer = project.update(&mut cx, |project, cx| {
             project.create_buffer(&macro_expansion.expansion, Some(rust_language), cx)
-        })?;
+        })??;
         workspace.update(&mut cx, |workspace, cx| {
-            let buffer = cx.add_model(|cx| {
+            let buffer = cx.new_model(|cx| {
                 MultiBuffer::singleton(buffer, cx).with_title(macro_expansion.name)
             });
             workspace.add_item(
-                Box::new(cx.add_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx))),
+                Box::new(cx.new_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx))),
                 cx,
             );
-        });
-
-        anyhow::Ok(())
-    }))
+        })
+    })
+    .detach_and_log_err(cx);
 }
 
 fn is_rust_language(language: &Language) -> bool {

crates/editor/src/scroll.rs 🔗

@@ -2,26 +2,21 @@ pub mod actions;
 pub mod autoscroll;
 pub mod scroll_amount;
 
-use std::{
-    cmp::Ordering,
-    time::{Duration, Instant},
-};
-
-use gpui::{
-    geometry::vector::{vec2f, Vector2F},
-    AppContext, Axis, Task, ViewContext,
-};
-use language::{Bias, Point};
-use util::ResultExt;
-use workspace::WorkspaceId;
-
 use crate::{
     display_map::{DisplaySnapshot, ToDisplayPoint},
     hover_popover::hide_hover,
     persistence::DB,
-    Anchor, DisplayPoint, Editor, EditorMode, Event, InlayHintRefreshReason, MultiBufferSnapshot,
-    ToPoint,
+    Anchor, DisplayPoint, Editor, EditorEvent, EditorMode, InlayHintRefreshReason,
+    MultiBufferSnapshot, ToPoint,
+};
+use gpui::{point, px, AppContext, Entity, Pixels, Task, ViewContext};
+use language::{Bias, Point};
+use std::{
+    cmp::Ordering,
+    time::{Duration, Instant},
 };
+use util::ResultExt;
+use workspace::{ItemId, WorkspaceId};
 
 use self::{
     autoscroll::{Autoscroll, AutoscrollStrategy},
@@ -37,25 +32,25 @@ pub struct ScrollbarAutoHide(pub bool);
 
 #[derive(Clone, Copy, Debug, PartialEq)]
 pub struct ScrollAnchor {
-    pub offset: Vector2F,
+    pub offset: gpui::Point<f32>,
     pub anchor: Anchor,
 }
 
 impl ScrollAnchor {
     fn new() -> Self {
         Self {
-            offset: Vector2F::zero(),
+            offset: gpui::Point::default(),
             anchor: Anchor::min(),
         }
     }
 
-    pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> Vector2F {
+    pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<f32> {
         let mut scroll_position = self.offset;
         if self.anchor != Anchor::min() {
             let scroll_top = self.anchor.to_display_point(snapshot).row() as f32;
-            scroll_position.set_y(scroll_top + scroll_position.y());
+            scroll_position.y = scroll_top + scroll_position.y;
         } else {
-            scroll_position.set_y(0.);
+            scroll_position.y = 0.;
         }
         scroll_position
     }
@@ -65,6 +60,12 @@ impl ScrollAnchor {
     }
 }
 
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub enum Axis {
+    Vertical,
+    Horizontal,
+}
+
 #[derive(Clone, Copy, Debug)]
 pub struct OngoingScroll {
     last_event: Instant,
@@ -79,13 +80,13 @@ impl OngoingScroll {
         }
     }
 
-    pub fn filter(&self, delta: &mut Vector2F) -> Option<Axis> {
+    pub fn filter(&self, delta: &mut gpui::Point<Pixels>) -> Option<Axis> {
         const UNLOCK_PERCENT: f32 = 1.9;
-        const UNLOCK_LOWER_BOUND: f32 = 6.;
+        const UNLOCK_LOWER_BOUND: Pixels = px(6.);
         let mut axis = self.axis;
 
-        let x = delta.x().abs();
-        let y = delta.y().abs();
+        let x = delta.x.abs();
+        let y = delta.y.abs();
         let duration = Instant::now().duration_since(self.last_event);
         if duration > SCROLL_EVENT_SEPARATION {
             //New ongoing scroll will start, determine axis
@@ -114,8 +115,12 @@ impl OngoingScroll {
         }
 
         match axis {
-            Some(Axis::Vertical) => *delta = vec2f(0., delta.y()),
-            Some(Axis::Horizontal) => *delta = vec2f(delta.x(), 0.),
+            Some(Axis::Vertical) => {
+                *delta = point(px(0.), delta.y);
+            }
+            Some(Axis::Horizontal) => {
+                *delta = point(delta.x, px(0.));
+            }
             None => {}
         }
 
@@ -128,9 +133,10 @@ pub struct ScrollManager {
     anchor: ScrollAnchor,
     ongoing: OngoingScroll,
     autoscroll_request: Option<(Autoscroll, bool)>,
-    last_autoscroll: Option<(Vector2F, f32, f32, AutoscrollStrategy)>,
+    last_autoscroll: Option<(gpui::Point<f32>, f32, f32, AutoscrollStrategy)>,
     show_scrollbars: bool,
     hide_scrollbar_task: Option<Task<()>>,
+    dragging_scrollbar: bool,
     visible_line_count: Option<f32>,
 }
 
@@ -143,6 +149,7 @@ impl ScrollManager {
             autoscroll_request: None,
             show_scrollbars: true,
             hide_scrollbar_task: None,
+            dragging_scrollbar: false,
             last_autoscroll: None,
             visible_line_count: None,
         }
@@ -166,30 +173,30 @@ impl ScrollManager {
         self.ongoing.axis = axis;
     }
 
-    pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> Vector2F {
+    pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<f32> {
         self.anchor.scroll_position(snapshot)
     }
 
     fn set_scroll_position(
         &mut self,
-        scroll_position: Vector2F,
+        scroll_position: gpui::Point<f32>,
         map: &DisplaySnapshot,
         local: bool,
         autoscroll: bool,
         workspace_id: Option<i64>,
         cx: &mut ViewContext<Editor>,
     ) {
-        let (new_anchor, top_row) = if scroll_position.y() <= 0. {
+        let (new_anchor, top_row) = if scroll_position.y <= 0. {
             (
                 ScrollAnchor {
                     anchor: Anchor::min(),
-                    offset: scroll_position.max(vec2f(0., 0.)),
+                    offset: scroll_position.max(&gpui::Point::default()),
                 },
                 0,
             )
         } else {
             let scroll_top_buffer_point =
-                DisplayPoint::new(scroll_position.y() as u32, 0).to_point(&map);
+                DisplayPoint::new(scroll_position.y as u32, 0).to_point(&map);
             let top_anchor = map
                 .buffer_snapshot
                 .anchor_at(scroll_top_buffer_point, Bias::Right);
@@ -197,9 +204,9 @@ impl ScrollManager {
             (
                 ScrollAnchor {
                     anchor: top_anchor,
-                    offset: vec2f(
-                        scroll_position.x(),
-                        scroll_position.y() - top_anchor.to_display_point(&map).row() as f32,
+                    offset: point(
+                        scroll_position.x,
+                        scroll_position.y - top_anchor.to_display_point(&map).row() as f32,
                     ),
                 },
                 scroll_top_buffer_point.row,
@@ -219,20 +226,20 @@ impl ScrollManager {
         cx: &mut ViewContext<Editor>,
     ) {
         self.anchor = anchor;
-        cx.emit(Event::ScrollPositionChanged { local, autoscroll });
+        cx.emit(EditorEvent::ScrollPositionChanged { local, autoscroll });
         self.show_scrollbar(cx);
         self.autoscroll_request.take();
         if let Some(workspace_id) = workspace_id {
-            let item_id = cx.view_id();
+            let item_id = cx.view().entity_id().as_u64() as ItemId;
 
-            cx.background()
+            cx.foreground_executor()
                 .spawn(async move {
                     DB.save_scroll_position(
                         item_id,
                         workspace_id,
                         top_row,
-                        anchor.offset.x(),
-                        anchor.offset.y(),
+                        anchor.offset.x,
+                        anchor.offset.y,
                     )
                     .await
                     .log_err()
@@ -250,7 +257,9 @@ impl ScrollManager {
 
         if cx.default_global::<ScrollbarAutoHide>().0 {
             self.hide_scrollbar_task = Some(cx.spawn(|editor, mut cx| async move {
-                cx.background().timer(SCROLLBAR_SHOW_INTERVAL).await;
+                cx.background_executor()
+                    .timer(SCROLLBAR_SHOW_INTERVAL)
+                    .await;
                 editor
                     .update(&mut cx, |editor, cx| {
                         editor.scroll_manager.show_scrollbars = false;
@@ -271,9 +280,20 @@ impl ScrollManager {
         self.autoscroll_request.is_some()
     }
 
+    pub fn is_dragging_scrollbar(&self) -> bool {
+        self.dragging_scrollbar
+    }
+
+    pub fn set_is_dragging_scrollbar(&mut self, dragging: bool, cx: &mut ViewContext<Editor>) {
+        if dragging != self.dragging_scrollbar {
+            self.dragging_scrollbar = dragging;
+            cx.notify();
+        }
+    }
+
     pub fn clamp_scroll_left(&mut self, max: f32) -> bool {
-        if max < self.anchor.offset.x() {
-            self.anchor.offset.set_x(max);
+        if max < self.anchor.offset.x {
+            self.anchor.offset.x = max;
             true
         } else {
             false
@@ -310,13 +330,17 @@ impl Editor {
         }
     }
 
-    pub fn set_scroll_position(&mut self, scroll_position: Vector2F, cx: &mut ViewContext<Self>) {
+    pub fn set_scroll_position(
+        &mut self,
+        scroll_position: gpui::Point<f32>,
+        cx: &mut ViewContext<Self>,
+    ) {
         self.set_scroll_position_internal(scroll_position, true, false, cx);
     }
 
     pub(crate) fn set_scroll_position_internal(
         &mut self,
-        scroll_position: Vector2F,
+        scroll_position: gpui::Point<f32>,
         local: bool,
         autoscroll: bool,
         cx: &mut ViewContext<Self>,
@@ -337,7 +361,7 @@ impl Editor {
         self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
     }
 
-    pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> Vector2F {
+    pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> gpui::Point<f32> {
         let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         self.scroll_manager.anchor.scroll_position(&display_map)
     }
@@ -370,7 +394,7 @@ impl Editor {
 
     pub fn scroll_screen(&mut self, amount: &ScrollAmount, cx: &mut ViewContext<Self>) {
         if matches!(self.mode, EditorMode::SingleLine) {
-            cx.propagate_action();
+            cx.propagate();
             return;
         }
 
@@ -379,7 +403,7 @@ impl Editor {
         }
 
         let cur_position = self.scroll_position(cx);
-        let new_pos = cur_position + vec2f(0., amount.lines(self));
+        let new_pos = cur_position + point(0., amount.lines(self));
         self.set_scroll_position(new_pos, cx);
     }
 
@@ -415,7 +439,7 @@ impl Editor {
 
     pub fn read_scroll_position_from_db(
         &mut self,
-        item_id: usize,
+        item_id: u64,
         workspace_id: WorkspaceId,
         cx: &mut ViewContext<Editor>,
     ) {
@@ -427,7 +451,7 @@ impl Editor {
                 .snapshot(cx)
                 .anchor_at(Point::new(top_row as u32, 0), Bias::Left);
             let scroll_anchor = ScrollAnchor {
-                offset: Vector2F::new(x, y),
+                offset: gpui::Point::new(x, y),
                 anchor: top_anchor,
             };
             self.set_scroll_anchor(scroll_anchor, cx);

crates/editor/src/scroll/actions.rs 🔗

@@ -1,72 +1,31 @@
-use gpui::{actions, geometry::vector::Vector2F, AppContext, Axis, ViewContext};
-use language::Bias;
-
-use crate::{Editor, EditorMode};
-
-use super::{autoscroll::Autoscroll, scroll_amount::ScrollAmount, ScrollAnchor};
-
-actions!(
-    editor,
-    [
-        LineDown,
-        LineUp,
-        HalfPageDown,
-        HalfPageUp,
-        PageDown,
-        PageUp,
-        NextScreen,
-        ScrollCursorTop,
-        ScrollCursorCenter,
-        ScrollCursorBottom,
-    ]
-);
-
-pub fn init(cx: &mut AppContext) {
-    cx.add_action(Editor::next_screen);
-    cx.add_action(Editor::scroll_cursor_top);
-    cx.add_action(Editor::scroll_cursor_center);
-    cx.add_action(Editor::scroll_cursor_bottom);
-    cx.add_action(|this: &mut Editor, _: &LineDown, cx| {
-        this.scroll_screen(&ScrollAmount::Line(1.), cx)
-    });
-    cx.add_action(|this: &mut Editor, _: &LineUp, cx| {
-        this.scroll_screen(&ScrollAmount::Line(-1.), cx)
-    });
-    cx.add_action(|this: &mut Editor, _: &HalfPageDown, cx| {
-        this.scroll_screen(&ScrollAmount::Page(0.5), cx)
-    });
-    cx.add_action(|this: &mut Editor, _: &HalfPageUp, cx| {
-        this.scroll_screen(&ScrollAmount::Page(-0.5), cx)
-    });
-    cx.add_action(|this: &mut Editor, _: &PageDown, cx| {
-        this.scroll_screen(&ScrollAmount::Page(1.), cx)
-    });
-    cx.add_action(|this: &mut Editor, _: &PageUp, cx| {
-        this.scroll_screen(&ScrollAmount::Page(-1.), cx)
-    });
-}
+use super::Axis;
+use crate::{
+    Autoscroll, Bias, Editor, EditorMode, NextScreen, ScrollAnchor, ScrollCursorBottom,
+    ScrollCursorCenter, ScrollCursorTop,
+};
+use gpui::{Point, ViewContext};
 
 impl Editor {
-    pub fn next_screen(&mut self, _: &NextScreen, cx: &mut ViewContext<Editor>) -> Option<()> {
+    pub fn next_screen(&mut self, _: &NextScreen, cx: &mut ViewContext<Editor>) {
         if self.take_rename(true, cx).is_some() {
-            return None;
+            return;
         }
 
-        if self.mouse_context_menu.read(cx).visible() {
-            return None;
-        }
+        // todo!()
+        // if self.mouse_context_menu.read(cx).visible() {
+        //     return None;
+        // }
 
         if matches!(self.mode, EditorMode::SingleLine) {
-            cx.propagate_action();
-            return None;
+            cx.propagate();
+            return;
         }
         self.request_autoscroll(Autoscroll::Next, cx);
-        Some(())
     }
 
     pub fn scroll(
         &mut self,
-        scroll_position: Vector2F,
+        scroll_position: Point<f32>,
         axis: Option<Axis>,
         cx: &mut ViewContext<Self>,
     ) {
@@ -74,17 +33,17 @@ impl Editor {
         self.set_scroll_position(scroll_position, cx);
     }
 
-    fn scroll_cursor_top(editor: &mut Editor, _: &ScrollCursorTop, cx: &mut ViewContext<Editor>) {
-        let snapshot = editor.snapshot(cx).display_snapshot;
-        let scroll_margin_rows = editor.vertical_scroll_margin() as u32;
+    pub fn scroll_cursor_top(&mut self, _: &ScrollCursorTop, cx: &mut ViewContext<Editor>) {
+        let snapshot = self.snapshot(cx).display_snapshot;
+        let scroll_margin_rows = self.vertical_scroll_margin() as u32;
 
-        let mut new_screen_top = editor.selections.newest_display(cx).head();
+        let mut new_screen_top = self.selections.newest_display(cx).head();
         *new_screen_top.row_mut() = new_screen_top.row().saturating_sub(scroll_margin_rows);
         *new_screen_top.column_mut() = 0;
         let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
         let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top);
 
-        editor.set_scroll_anchor(
+        self.set_scroll_anchor(
             ScrollAnchor {
                 anchor: new_anchor,
                 offset: Default::default(),
@@ -93,25 +52,21 @@ impl Editor {
         )
     }
 
-    fn scroll_cursor_center(
-        editor: &mut Editor,
-        _: &ScrollCursorCenter,
-        cx: &mut ViewContext<Editor>,
-    ) {
-        let snapshot = editor.snapshot(cx).display_snapshot;
-        let visible_rows = if let Some(visible_rows) = editor.visible_line_count() {
+    pub fn scroll_cursor_center(&mut self, _: &ScrollCursorCenter, cx: &mut ViewContext<Editor>) {
+        let snapshot = self.snapshot(cx).display_snapshot;
+        let visible_rows = if let Some(visible_rows) = self.visible_line_count() {
             visible_rows as u32
         } else {
             return;
         };
 
-        let mut new_screen_top = editor.selections.newest_display(cx).head();
+        let mut new_screen_top = self.selections.newest_display(cx).head();
         *new_screen_top.row_mut() = new_screen_top.row().saturating_sub(visible_rows / 2);
         *new_screen_top.column_mut() = 0;
         let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
         let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top);
 
-        editor.set_scroll_anchor(
+        self.set_scroll_anchor(
             ScrollAnchor {
                 anchor: new_anchor,
                 offset: Default::default(),
@@ -120,20 +75,16 @@ impl Editor {
         )
     }
 
-    fn scroll_cursor_bottom(
-        editor: &mut Editor,
-        _: &ScrollCursorBottom,
-        cx: &mut ViewContext<Editor>,
-    ) {
-        let snapshot = editor.snapshot(cx).display_snapshot;
-        let scroll_margin_rows = editor.vertical_scroll_margin() as u32;
-        let visible_rows = if let Some(visible_rows) = editor.visible_line_count() {
+    pub fn scroll_cursor_bottom(&mut self, _: &ScrollCursorBottom, cx: &mut ViewContext<Editor>) {
+        let snapshot = self.snapshot(cx).display_snapshot;
+        let scroll_margin_rows = self.vertical_scroll_margin() as u32;
+        let visible_rows = if let Some(visible_rows) = self.visible_line_count() {
             visible_rows as u32
         } else {
             return;
         };
 
-        let mut new_screen_top = editor.selections.newest_display(cx).head();
+        let mut new_screen_top = self.selections.newest_display(cx).head();
         *new_screen_top.row_mut() = new_screen_top
             .row()
             .saturating_sub(visible_rows.saturating_sub(scroll_margin_rows));
@@ -141,7 +92,7 @@ impl Editor {
         let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
         let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top);
 
-        editor.set_scroll_anchor(
+        self.set_scroll_anchor(
             ScrollAnchor {
                 anchor: new_anchor,
                 offset: Default::default(),

crates/editor/src/scroll/autoscroll.rs 🔗

@@ -1,6 +1,6 @@
-use std::cmp;
+use std::{cmp, f32};
 
-use gpui::ViewContext;
+use gpui::{px, Pixels, ViewContext};
 use language::Point;
 
 use crate::{display_map::ToDisplayPoint, Editor, EditorMode, LineWithInvisibles};
@@ -48,11 +48,11 @@ impl AutoscrollStrategy {
 impl Editor {
     pub fn autoscroll_vertically(
         &mut self,
-        viewport_height: f32,
-        line_height: f32,
+        viewport_height: Pixels,
+        line_height: Pixels,
         cx: &mut ViewContext<Editor>,
     ) -> bool {
-        let visible_lines = viewport_height / line_height;
+        let visible_lines = f32::from(viewport_height / line_height);
         let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let mut scroll_position = self.scroll_manager.scroll_position(&display_map);
         let max_scroll_top = if matches!(self.mode, EditorMode::AutoHeight { .. }) {
@@ -60,8 +60,8 @@ impl Editor {
         } else {
             display_map.max_point().row() as f32
         };
-        if scroll_position.y() > max_scroll_top {
-            scroll_position.set_y(max_scroll_top);
+        if scroll_position.y > max_scroll_top {
+            scroll_position.y = max_scroll_top;
             self.set_scroll_position(scroll_position, cx);
         }
 
@@ -136,31 +136,31 @@ impl Editor {
                 let margin = margin.min(self.scroll_manager.vertical_scroll_margin);
                 let target_top = (target_top - margin).max(0.0);
                 let target_bottom = target_bottom + margin;
-                let start_row = scroll_position.y();
+                let start_row = scroll_position.y;
                 let end_row = start_row + visible_lines;
 
                 let needs_scroll_up = target_top < start_row;
                 let needs_scroll_down = target_bottom >= end_row;
 
                 if needs_scroll_up && !needs_scroll_down {
-                    scroll_position.set_y(target_top);
+                    scroll_position.y = target_top;
                     self.set_scroll_position_internal(scroll_position, local, true, cx);
                 }
                 if !needs_scroll_up && needs_scroll_down {
-                    scroll_position.set_y(target_bottom - visible_lines);
+                    scroll_position.y = target_bottom - visible_lines;
                     self.set_scroll_position_internal(scroll_position, local, true, cx);
                 }
             }
             AutoscrollStrategy::Center => {
-                scroll_position.set_y((target_top - margin).max(0.0));
+                scroll_position.y = (target_top - margin).max(0.0);
                 self.set_scroll_position_internal(scroll_position, local, true, cx);
             }
             AutoscrollStrategy::Top => {
-                scroll_position.set_y((target_top).max(0.0));
+                scroll_position.y = (target_top).max(0.0);
                 self.set_scroll_position_internal(scroll_position, local, true, cx);
             }
             AutoscrollStrategy::Bottom => {
-                scroll_position.set_y((target_bottom - visible_lines).max(0.0));
+                scroll_position.y = (target_bottom - visible_lines).max(0.0);
                 self.set_scroll_position_internal(scroll_position, local, true, cx);
             }
         }
@@ -178,9 +178,9 @@ impl Editor {
     pub fn autoscroll_horizontally(
         &mut self,
         start_row: u32,
-        viewport_width: f32,
-        scroll_width: f32,
-        max_glyph_width: f32,
+        viewport_width: Pixels,
+        scroll_width: Pixels,
+        max_glyph_width: Pixels,
         layouts: &[LineWithInvisibles],
         cx: &mut ViewContext<Self>,
     ) -> bool {
@@ -191,11 +191,11 @@ impl Editor {
         let mut target_right;
 
         if self.highlighted_rows.is_some() {
-            target_left = 0.0_f32;
-            target_right = 0.0_f32;
+            target_left = px(0.);
+            target_right = px(0.);
         } else {
-            target_left = std::f32::INFINITY;
-            target_right = 0.0_f32;
+            target_left = px(f32::INFINITY);
+            target_right = px(0.);
             for selection in selections {
                 let head = selection.head().to_display_point(&display_map);
                 if head.row() >= start_row && head.row() < start_row + layouts.len() as u32 {
@@ -222,20 +222,15 @@ impl Editor {
             return false;
         }
 
-        let scroll_left = self.scroll_manager.anchor.offset.x() * max_glyph_width;
+        let scroll_left = self.scroll_manager.anchor.offset.x * max_glyph_width;
         let scroll_right = scroll_left + viewport_width;
 
         if target_left < scroll_left {
-            self.scroll_manager
-                .anchor
-                .offset
-                .set_x(target_left / max_glyph_width);
+            self.scroll_manager.anchor.offset.x = (target_left / max_glyph_width).into();
             true
         } else if target_right > scroll_right {
-            self.scroll_manager
-                .anchor
-                .offset
-                .set_x((target_right - viewport_width) / max_glyph_width);
+            self.scroll_manager.anchor.offset.x =
+                ((target_right - viewport_width) / max_glyph_width).into();
             true
         } else {
             false

crates/editor/src/selections_collection.rs 🔗

@@ -6,7 +6,7 @@ use std::{
 };
 
 use collections::HashMap;
-use gpui::{AppContext, ModelHandle};
+use gpui::{AppContext, Model, Pixels};
 use itertools::Itertools;
 use language::{Bias, Point, Selection, SelectionGoal, TextDimension, ToPoint};
 use util::post_inc;
@@ -25,8 +25,8 @@ pub struct PendingSelection {
 
 #[derive(Debug, Clone)]
 pub struct SelectionsCollection {
-    display_map: ModelHandle<DisplayMap>,
-    buffer: ModelHandle<MultiBuffer>,
+    display_map: Model<DisplayMap>,
+    buffer: Model<MultiBuffer>,
     pub next_selection_id: usize,
     pub line_mode: bool,
     disjoint: Arc<[Selection<Anchor>]>,
@@ -34,7 +34,7 @@ pub struct SelectionsCollection {
 }
 
 impl SelectionsCollection {
-    pub fn new(display_map: ModelHandle<DisplayMap>, buffer: ModelHandle<MultiBuffer>) -> Self {
+    pub fn new(display_map: Model<DisplayMap>, buffer: Model<MultiBuffer>) -> Self {
         Self {
             display_map,
             buffer,
@@ -306,19 +306,19 @@ impl SelectionsCollection {
         &mut self,
         display_map: &DisplaySnapshot,
         row: u32,
-        positions: &Range<f32>,
+        positions: &Range<Pixels>,
         reversed: bool,
         text_layout_details: &TextLayoutDetails,
     ) -> Option<Selection<Point>> {
         let is_empty = positions.start == positions.end;
         let line_len = display_map.line_len(row);
 
-        let layed_out_line = display_map.lay_out_line_for_row(row, &text_layout_details);
+        let line = display_map.layout_row(row, &text_layout_details);
 
-        let start_col = layed_out_line.closest_index_for_x(positions.start) as u32;
-        if start_col < line_len || (is_empty && positions.start == layed_out_line.width()) {
+        let start_col = line.closest_index_for_x(positions.start) as u32;
+        if start_col < line_len || (is_empty && positions.start == line.width) {
             let start = DisplayPoint::new(row, start_col);
-            let end_col = layed_out_line.closest_index_for_x(positions.end) as u32;
+            let end_col = line.closest_index_for_x(positions.end) as u32;
             let end = DisplayPoint::new(row, end_col);
 
             Some(Selection {
@@ -327,8 +327,8 @@ impl SelectionsCollection {
                 end: end.to_point(display_map),
                 reversed,
                 goal: SelectionGoal::HorizontalRange {
-                    start: positions.start,
-                    end: positions.end,
+                    start: positions.start.into(),
+                    end: positions.end.into(),
                 },
             })
         } else {
@@ -592,7 +592,10 @@ impl<'a> MutableSelectionsCollection<'a> {
         self.select(selections)
     }
 
-    pub fn select_anchor_ranges<I: IntoIterator<Item = Range<Anchor>>>(&mut self, ranges: I) {
+    pub fn select_anchor_ranges<I>(&mut self, ranges: I)
+    where
+        I: IntoIterator<Item = Range<Anchor>>,
+    {
         let buffer = self.buffer.read(self.cx).snapshot(self.cx);
         let selections = ranges
             .into_iter()
@@ -614,7 +617,6 @@ impl<'a> MutableSelectionsCollection<'a> {
                 }
             })
             .collect::<Vec<_>>();
-
         self.select_anchors(selections)
     }
 

crates/editor/src/test.rs 🔗

@@ -6,7 +6,7 @@ use crate::{
     DisplayPoint, Editor, EditorMode, MultiBuffer,
 };
 
-use gpui::{ModelHandle, ViewContext};
+use gpui::{Context, Model, Pixels, ViewContext};
 
 use project::Project;
 use util::test::{marked_text_offsets, marked_text_ranges};
@@ -26,19 +26,11 @@ pub fn marked_display_snapshot(
 ) -> (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 = 14usize.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 display_map = cx.new_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()
@@ -67,17 +59,16 @@ pub fn assert_text_with_selections(
 // 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: ModelHandle<MultiBuffer>,
-    cx: &mut ViewContext<Editor>,
-) -> Editor {
-    Editor::new(EditorMode::Full, buffer, None, None, cx)
+pub(crate) fn build_editor(buffer: Model<MultiBuffer>, cx: &mut ViewContext<Editor>) -> Editor {
+    // todo!()
+    Editor::new(EditorMode::Full, buffer, None, /*None,*/ cx)
 }
 
 pub(crate) fn build_editor_with_project(
-    project: ModelHandle<Project>,
-    buffer: ModelHandle<MultiBuffer>,
+    project: Model<Project>,
+    buffer: Model<MultiBuffer>,
     cx: &mut ViewContext<Editor>,
 ) -> Editor {
-    Editor::new(EditorMode::Full, buffer, Some(project), None, cx)
+    // todo!()
+    Editor::new(EditorMode::Full, buffer, Some(project), /*None,*/ cx)
 }

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

@@ -5,11 +5,12 @@ use std::{
 };
 
 use anyhow::Result;
+use serde_json::json;
 
 use crate::{Editor, ToPoint};
 use collections::HashSet;
 use futures::Future;
-use gpui::{json, ViewContext, ViewHandle};
+use gpui::{View, ViewContext, VisualTestContext};
 use indoc::indoc;
 use language::{point_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageQueries};
 use lsp::{notification, request};
@@ -18,12 +19,12 @@ use project::Project;
 use smol::stream::StreamExt;
 use workspace::{AppState, Workspace, WorkspaceHandle};
 
-use super::editor_test_context::EditorTestContext;
+use super::editor_test_context::{AssertionContextManager, EditorTestContext};
 
 pub struct EditorLspTestContext<'a> {
     pub cx: EditorTestContext<'a>,
     pub lsp: lsp::FakeLanguageServer,
-    pub workspace: ViewHandle<Workspace>,
+    pub workspace: View<Workspace>,
     pub buffer_lsp_url: lsp::Url,
 }
 
@@ -33,8 +34,6 @@ impl<'a> EditorLspTestContext<'a> {
         capabilities: lsp::ServerCapabilities,
         cx: &'a mut gpui::TestAppContext,
     ) -> EditorLspTestContext<'a> {
-        use json::json;
-
         let app_state = cx.update(AppState::test);
 
         cx.update(|cx| {
@@ -60,6 +59,7 @@ impl<'a> EditorLspTestContext<'a> {
             .await;
 
         let project = Project::test(app_state.fs.clone(), [], cx).await;
+
         project.update(cx, |project, _| project.languages().add(Arc::new(language)));
 
         app_state
@@ -69,37 +69,38 @@ impl<'a> EditorLspTestContext<'a> {
             .await;
 
         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let workspace = window.root(cx);
+
+        let workspace = window.root_view(cx).unwrap();
+
+        let mut cx = VisualTestContext::from_window(*window.deref(), cx);
         project
-            .update(cx, |project, cx| {
+            .update(&mut cx, |project, cx| {
                 project.find_or_create_local_worktree("/root", true, cx)
             })
             .await
             .unwrap();
         cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
             .await;
-
         let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
         let item = workspace
-            .update(cx, |workspace, cx| {
+            .update(&mut cx, |workspace, cx| {
                 workspace.open_path(file, None, true, cx)
             })
             .await
             .expect("Could not open test file");
-
         let editor = cx.update(|cx| {
             item.act_as::<Editor>(cx)
                 .expect("Opened test file wasn't an editor")
         });
-        editor.update(cx, |_, cx| cx.focus_self());
+        editor.update(&mut cx, |editor, cx| editor.focus(cx));
 
         let lsp = fake_servers.next().await.unwrap();
-
         Self {
             cx: EditorTestContext {
                 cx,
                 window: window.into(),
                 editor,
+                assertion_cx: AssertionContextManager::new(),
             },
             lsp,
             workspace,
@@ -257,7 +258,7 @@ impl<'a> EditorLspTestContext<'a> {
     where
         F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
     {
-        self.workspace.update(self.cx.cx, update)
+        self.workspace.update(&mut self.cx.cx, update)
     }
 
     pub fn handle_request<T, F, Fut>(

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

@@ -1,17 +1,23 @@
 use crate::{
     display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer,
 };
+use collections::BTreeMap;
 use futures::Future;
 use gpui::{
-    executor::Foreground, keymap_matcher::Keystroke, AnyWindowHandle, AppContext, ContextHandle,
-    ModelContext, ViewContext, ViewHandle,
+    AnyWindowHandle, AppContext, Keystroke, ModelContext, View, ViewContext, VisualTestContext,
 };
 use indoc::indoc;
+use itertools::Itertools;
 use language::{Buffer, BufferSnapshot};
+use parking_lot::RwLock;
 use project::{FakeFs, Project};
 use std::{
     any::TypeId,
     ops::{Deref, DerefMut, Range},
+    sync::{
+        atomic::{AtomicUsize, Ordering},
+        Arc,
+    },
 };
 use util::{
     assert_set_eq,
@@ -21,14 +27,15 @@ use util::{
 use super::build_editor_with_project;
 
 pub struct EditorTestContext<'a> {
-    pub cx: &'a mut gpui::TestAppContext,
+    pub cx: gpui::VisualTestContext<'a>,
     pub window: AnyWindowHandle,
-    pub editor: ViewHandle<Editor>,
+    pub editor: View<Editor>,
+    pub assertion_cx: AssertionContextManager,
 }
 
 impl<'a> EditorTestContext<'a> {
     pub async fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> {
-        let fs = FakeFs::new(cx.background());
+        let fs = FakeFs::new(cx.executor());
         // fs.insert_file("/file", "".to_owned()).await;
         fs.insert_tree(
             "/root",
@@ -44,15 +51,18 @@ impl<'a> EditorTestContext<'a> {
             })
             .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 = cx.add_window(|cx| {
+            let editor =
+                build_editor_with_project(project, MultiBuffer::build_from_buffer(buffer, cx), cx);
+            editor.focus(cx);
+            editor
         });
-        let editor = window.root(cx);
+        let editor_view = editor.root_view(cx).unwrap();
         Self {
-            cx,
-            window: window.into(),
-            editor,
+            cx: VisualTestContext::from_window(*editor.deref(), cx),
+            window: editor.into(),
+            editor: editor_view,
+            assertion_cx: AssertionContextManager::new(),
         }
     }
 
@@ -60,24 +70,28 @@ impl<'a> EditorTestContext<'a> {
         &self,
         predicate: impl FnMut(&Editor, &AppContext) -> bool,
     ) -> impl Future<Output = ()> {
-        self.editor.condition(self.cx, predicate)
+        self.editor
+            .condition::<crate::EditorEvent>(&self.cx, predicate)
     }
 
-    pub fn editor<F, T>(&self, read: F) -> T
+    #[track_caller]
+    pub fn editor<F, T>(&mut self, read: F) -> T
     where
         F: FnOnce(&Editor, &ViewContext<Editor>) -> T,
     {
-        self.editor.read_with(self.cx, read)
+        self.editor
+            .update(&mut self.cx, |this, cx| read(&this, &cx))
     }
 
+    #[track_caller]
     pub fn update_editor<F, T>(&mut self, update: F) -> T
     where
         F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
     {
-        self.editor.update(self.cx, update)
+        self.editor.update(&mut self.cx, update)
     }
 
-    pub fn multibuffer<F, T>(&self, read: F) -> T
+    pub fn multibuffer<F, T>(&mut self, read: F) -> T
     where
         F: FnOnce(&MultiBuffer, &AppContext) -> T,
     {
@@ -91,11 +105,11 @@ impl<'a> EditorTestContext<'a> {
         self.update_editor(|editor, cx| editor.buffer().update(cx, update))
     }
 
-    pub fn buffer_text(&self) -> String {
+    pub fn buffer_text(&mut self) -> String {
         self.multibuffer(|buffer, cx| buffer.snapshot(cx).text())
     }
 
-    pub fn buffer<F, T>(&self, read: F) -> T
+    pub fn buffer<F, T>(&mut self, read: F) -> T
     where
         F: FnOnce(&Buffer, &AppContext) -> T,
     {
@@ -115,10 +129,18 @@ impl<'a> EditorTestContext<'a> {
         })
     }
 
-    pub fn buffer_snapshot(&self) -> BufferSnapshot {
+    pub fn buffer_snapshot(&mut self) -> BufferSnapshot {
         self.buffer(|buffer, _| buffer.snapshot())
     }
 
+    pub fn add_assertion_context(&self, context: String) -> ContextHandle {
+        self.assertion_cx.add_context(context)
+    }
+
+    pub fn assertion_context(&self) -> String {
+        self.assertion_cx.context()
+    }
+
     pub fn simulate_keystroke(&mut self, keystroke_text: &str) -> ContextHandle {
         let keystroke_under_test_handle =
             self.add_assertion_context(format!("Simulated Keystroke: {:?}", keystroke_text));
@@ -142,16 +164,16 @@ impl<'a> EditorTestContext<'a> {
         // 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!();
-        }
+        self.cx.background_executor.run_until_parked();
 
         keystrokes_under_test_handle
     }
 
-    pub fn ranges(&self, marked_text: &str) -> Vec<Range<usize>> {
+    pub fn run_until_parked(&mut self) {
+        self.cx.background_executor.run_until_parked();
+    }
+
+    pub fn ranges(&mut self, marked_text: &str) -> Vec<Range<usize>> {
         let (unmarked_text, ranges) = marked_text_ranges(marked_text, false);
         assert_eq!(self.buffer_text(), unmarked_text);
         ranges
@@ -161,12 +183,12 @@ impl<'a> EditorTestContext<'a> {
         let ranges = self.ranges(marked_text);
         let snapshot = self
             .editor
-            .update(self.cx, |editor, cx| editor.snapshot(cx));
+            .update(&mut self.cx, |editor, cx| editor.snapshot(cx));
         ranges[0].start.to_display_point(&snapshot)
     }
 
     // Returns anchors for the current buffer using `«` and `»`
-    pub fn text_anchor_range(&self, marked_text: &str) -> Range<language::Anchor> {
+    pub fn text_anchor_range(&mut self, marked_text: &str) -> Range<language::Anchor> {
         let ranges = self.ranges(marked_text);
         let snapshot = self.buffer_snapshot();
         snapshot.anchor_before(ranges[0].start)..snapshot.anchor_after(ranges[0].end)
@@ -191,7 +213,7 @@ impl<'a> EditorTestContext<'a> {
             marked_text.escape_debug().to_string()
         ));
         let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
-        self.editor.update(self.cx, |editor, cx| {
+        self.editor.update(&mut self.cx, |editor, cx| {
             editor.set_text(unmarked_text, cx);
             editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
                 s.select_ranges(selection_ranges)
@@ -207,7 +229,7 @@ impl<'a> EditorTestContext<'a> {
             marked_text.escape_debug().to_string()
         ));
         let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
-        self.editor.update(self.cx, |editor, cx| {
+        self.editor.update(&mut self.cx, |editor, cx| {
             assert_eq!(editor.text(cx), unmarked_text);
             editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
                 s.select_ranges(selection_ranges)
@@ -274,9 +296,12 @@ impl<'a> EditorTestContext<'a> {
         self.assert_selections(expected_selections, expected_marked_text)
     }
 
-    fn editor_selections(&self) -> Vec<Range<usize>> {
+    #[track_caller]
+    fn editor_selections(&mut self) -> Vec<Range<usize>> {
         self.editor
-            .read_with(self.cx, |editor, cx| editor.selections.all::<usize>(cx))
+            .update(&mut self.cx, |editor, cx| {
+                editor.selections.all::<usize>(cx)
+            })
             .into_iter()
             .map(|s| {
                 if s.reversed {
@@ -301,14 +326,14 @@ impl<'a> EditorTestContext<'a> {
             panic!(
                 indoc! {"
 
-                    {}Editor has unexpected selections.
+                {}Editor has unexpected selections.
 
-                    Expected selections:
-                    {}
+                Expected selections:
+                {}
 
-                    Actual selections:
-                    {}
-                "},
+                Actual selections:
+                {}
+            "},
                 self.assertion_context(),
                 expected_marked_text,
                 actual_marked_text,
@@ -321,7 +346,7 @@ impl<'a> Deref for EditorTestContext<'a> {
     type Target = gpui::TestAppContext;
 
     fn deref(&self) -> &Self::Target {
-        self.cx
+        &self.cx
     }
 }
 
@@ -330,3 +355,50 @@ impl<'a> DerefMut for EditorTestContext<'a> {
         &mut self.cx
     }
 }
+
+/// Tracks string context to be printed when assertions fail.
+/// Often this is done by storing a context string in the manager and returning the handle.
+#[derive(Clone)]
+pub struct AssertionContextManager {
+    id: Arc<AtomicUsize>,
+    contexts: Arc<RwLock<BTreeMap<usize, String>>>,
+}
+
+impl AssertionContextManager {
+    pub fn new() -> Self {
+        Self {
+            id: Arc::new(AtomicUsize::new(0)),
+            contexts: Arc::new(RwLock::new(BTreeMap::new())),
+        }
+    }
+
+    pub fn add_context(&self, context: String) -> ContextHandle {
+        let id = self.id.fetch_add(1, Ordering::Relaxed);
+        let mut contexts = self.contexts.write();
+        contexts.insert(id, context);
+        ContextHandle {
+            id,
+            manager: self.clone(),
+        }
+    }
+
+    pub fn context(&self) -> String {
+        let contexts = self.contexts.read();
+        format!("\n{}\n", contexts.values().join("\n"))
+    }
+}
+
+/// Used to track the lifetime of a piece of context so that it can be provided when an assertion fails.
+/// For example, in the EditorTestContext, `set_state` returns a context handle so that if an assertion fails,
+/// the state that was set initially for the failure can be printed in the error message
+pub struct ContextHandle {
+    id: usize,
+    manager: AssertionContextManager,
+}
+
+impl Drop for ContextHandle {
+    fn drop(&mut self) {
+        let mut contexts = self.manager.contexts.write();
+        contexts.remove(&self.id);
+    }
+}

crates/editor2/Cargo.toml 🔗

@@ -1,93 +0,0 @@
-[package]
-name = "editor2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/editor.rs"
-doctest = false
-
-[features]
-test-support = [
-    "copilot/test-support",
-    "text/test-support",
-    "language/test-support",
-    "gpui/test-support",
-    "multi_buffer/test-support",
-    "project/test-support",
-    "util/test-support",
-    "workspace/test-support",
-    "tree-sitter-rust",
-    "tree-sitter-typescript"
-]
-
-[dependencies]
-client = { package = "client2", path = "../client2" }
-clock = { path = "../clock" }
-copilot = { package="copilot2", path = "../copilot2" }
-db = { package="db2", path = "../db2" }
-collections = { path = "../collections" }
-# context_menu = { path = "../context_menu" }
-fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
-git = { package = "git3", path = "../git3" }
-gpui = { package = "gpui2", path = "../gpui2" }
-language = { package = "language2", path = "../language2" }
-lsp = { package = "lsp2", path = "../lsp2" }
-multi_buffer = { package = "multi_buffer2", path = "../multi_buffer2" }
-project = { package = "project2", path = "../project2" }
-rpc = { package = "rpc2", path = "../rpc2" }
-rich_text = { package = "rich_text2", path = "../rich_text2" }
-settings = { package="settings2", path = "../settings2" }
-snippet = { path = "../snippet" }
-sum_tree = { path = "../sum_tree" }
-text = { package="text2", path = "../text2" }
-theme = { package="theme2", path = "../theme2" }
-ui = { package = "ui2", path = "../ui2" }
-util = { path = "../util" }
-sqlez = { path = "../sqlez" }
-workspace = { package = "workspace2", path = "../workspace2" }
-
-aho-corasick = "1.1"
-anyhow.workspace = true
-convert_case = "0.6.0"
-futures.workspace = true
-indoc = "1.0.4"
-itertools = "0.10"
-lazy_static.workspace = true
-log.workspace = true
-ordered-float.workspace = true
-parking_lot.workspace = true
-postage.workspace = true
-rand.workspace = true
-schemars.workspace = true
-serde.workspace = true
-serde_json.workspace = true
-serde_derive.workspace = true
-smallvec.workspace = true
-smol.workspace = true
-
-tree-sitter-rust = { workspace = true, optional = true }
-tree-sitter-html = { workspace = true, optional = true }
-tree-sitter-typescript = { workspace = true, optional = true }
-
-[dev-dependencies]
-copilot = { package="copilot2", path = "../copilot2", features = ["test-support"] }
-text = { package="text2", path = "../text2", features = ["test-support"] }
-language = { package="language2", path = "../language2", features = ["test-support"] }
-lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-util = { path = "../util", features = ["test-support"] }
-project = { package = "project2", path = "../project2", features = ["test-support"] }
-settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
-workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
-multi_buffer = { package = "multi_buffer2", path = "../multi_buffer2", features = ["test-support"] }
-
-ctor.workspace = true
-env_logger.workspace = true
-rand.workspace = true
-unindent.workspace = true
-tree-sitter.workspace = true
-tree-sitter-rust.workspace = true
-tree-sitter-html.workspace = true
-tree-sitter-typescript.workspace = true
@@ -1,107 +0,0 @@
-use crate::EditorSettings;
-use gpui::ModelContext;
-use settings::Settings;
-use settings::SettingsStore;
-use smol::Timer;
-use std::time::Duration;
-
-pub struct BlinkManager {
-    blink_interval: Duration,
-
-    blink_epoch: usize,
-    blinking_paused: bool,
-    visible: bool,
-    enabled: bool,
-}
-
-impl BlinkManager {
-    pub fn new(blink_interval: Duration, cx: &mut ModelContext<Self>) -> Self {
-        // Make sure we blink the cursors if the setting is re-enabled
-        cx.observe_global::<SettingsStore>(move |this, cx| {
-            this.blink_cursors(this.blink_epoch, cx)
-        })
-        .detach();
-
-        Self {
-            blink_interval,
-
-            blink_epoch: 0,
-            blinking_paused: false,
-            visible: true,
-            enabled: false,
-        }
-    }
-
-    fn next_blink_epoch(&mut self) -> usize {
-        self.blink_epoch += 1;
-        self.blink_epoch
-    }
-
-    pub fn pause_blinking(&mut self, cx: &mut ModelContext<Self>) {
-        self.show_cursor(cx);
-
-        let epoch = self.next_blink_epoch();
-        let interval = self.blink_interval;
-        cx.spawn(|this, mut cx| async move {
-            Timer::after(interval).await;
-            this.update(&mut cx, |this, cx| this.resume_cursor_blinking(epoch, cx))
-        })
-        .detach();
-    }
-
-    fn resume_cursor_blinking(&mut self, epoch: usize, cx: &mut ModelContext<Self>) {
-        if epoch == self.blink_epoch {
-            self.blinking_paused = false;
-            self.blink_cursors(epoch, cx);
-        }
-    }
-
-    fn blink_cursors(&mut self, epoch: usize, cx: &mut ModelContext<Self>) {
-        if EditorSettings::get_global(cx).cursor_blink {
-            if epoch == self.blink_epoch && self.enabled && !self.blinking_paused {
-                self.visible = !self.visible;
-                cx.notify();
-
-                let epoch = self.next_blink_epoch();
-                let interval = self.blink_interval;
-                cx.spawn(|this, mut cx| async move {
-                    Timer::after(interval).await;
-                    if let Some(this) = this.upgrade() {
-                        this.update(&mut cx, |this, cx| this.blink_cursors(epoch, cx))
-                            .ok();
-                    }
-                })
-                .detach();
-            }
-        } else {
-            self.show_cursor(cx);
-        }
-    }
-
-    pub fn show_cursor(&mut self, cx: &mut ModelContext<'_, BlinkManager>) {
-        if !self.visible {
-            self.visible = true;
-            cx.notify();
-        }
-    }
-
-    pub fn enable(&mut self, cx: &mut ModelContext<Self>) {
-        if self.enabled {
-            return;
-        }
-
-        self.enabled = true;
-        // Set cursors as invisible and start blinking: this causes cursors
-        // to be visible during the next render.
-        self.visible = false;
-        self.blink_cursors(self.blink_epoch, cx);
-    }
-
-    pub fn disable(&mut self, _cx: &mut ModelContext<Self>) {
-        self.enabled = false;
-    }
-
-    pub fn visible(&self) -> bool {
-        self.visible
-    }
-}

crates/editor2/src/display_map.rs 🔗

@@ -1,1854 +0,0 @@
-mod block_map;
-mod fold_map;
-mod inlay_map;
-mod tab_map;
-mod wrap_map;
-
-use crate::EditorStyle;
-use crate::{
-    link_go_to_definition::InlayHighlight, movement::TextLayoutDetails, Anchor, AnchorRangeExt,
-    InlayId, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint,
-};
-pub use block_map::{BlockMap, BlockPoint};
-use collections::{BTreeMap, HashMap, HashSet};
-use fold_map::FoldMap;
-use gpui::{Font, HighlightStyle, Hsla, LineLayout, Model, ModelContext, Pixels, UnderlineStyle};
-use inlay_map::InlayMap;
-use language::{
-    language_settings::language_settings, OffsetUtf16, Point, Subscription as BufferSubscription,
-};
-use lsp::DiagnosticSeverity;
-use std::{any::TypeId, borrow::Cow, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc};
-use sum_tree::{Bias, TreeMap};
-use tab_map::TabMap;
-
-use wrap_map::WrapMap;
-
-pub use block_map::{
-    BlockBufferRows as DisplayBufferRows, BlockChunks as DisplayChunks, BlockContext,
-    BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, TransformBlock,
-};
-
-pub use self::fold_map::{Fold, FoldPoint};
-pub use self::inlay_map::{Inlay, InlayOffset, InlayPoint};
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub enum FoldStatus {
-    Folded,
-    Foldable,
-}
-
-const UNNECESSARY_CODE_FADE: f32 = 0.3;
-
-pub trait ToDisplayPoint {
-    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint;
-}
-
-type TextHighlights = TreeMap<Option<TypeId>, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>;
-type InlayHighlights = BTreeMap<TypeId, HashMap<InlayId, (HighlightStyle, InlayHighlight)>>;
-
-pub struct DisplayMap {
-    buffer: Model<MultiBuffer>,
-    buffer_subscription: BufferSubscription,
-    fold_map: FoldMap,
-    inlay_map: InlayMap,
-    tab_map: TabMap,
-    wrap_map: Model<WrapMap>,
-    block_map: BlockMap,
-    text_highlights: TextHighlights,
-    inlay_highlights: InlayHighlights,
-    pub clip_at_line_ends: bool,
-}
-
-impl DisplayMap {
-    pub fn new(
-        buffer: Model<MultiBuffer>,
-        font: Font,
-        font_size: Pixels,
-        wrap_width: Option<Pixels>,
-        buffer_header_height: u8,
-        excerpt_header_height: u8,
-        cx: &mut ModelContext<Self>,
-    ) -> Self {
-        let buffer_subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
-
-        let tab_size = Self::tab_size(&buffer, cx);
-        let (inlay_map, snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
-        let (fold_map, snapshot) = FoldMap::new(snapshot);
-        let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
-        let (wrap_map, snapshot) = WrapMap::new(snapshot, font, font_size, wrap_width, cx);
-        let block_map = BlockMap::new(snapshot, buffer_header_height, excerpt_header_height);
-        cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
-        DisplayMap {
-            buffer,
-            buffer_subscription,
-            fold_map,
-            inlay_map,
-            tab_map,
-            wrap_map,
-            block_map,
-            text_highlights: Default::default(),
-            inlay_highlights: Default::default(),
-            clip_at_line_ends: false,
-        }
-    }
-
-    pub fn snapshot(&mut self, cx: &mut ModelContext<Self>) -> DisplaySnapshot {
-        let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
-        let edits = self.buffer_subscription.consume().into_inner();
-        let (inlay_snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
-        let (fold_snapshot, edits) = self.fold_map.read(inlay_snapshot.clone(), edits);
-        let tab_size = Self::tab_size(&self.buffer, cx);
-        let (tab_snapshot, edits) = self.tab_map.sync(fold_snapshot.clone(), edits, tab_size);
-        let (wrap_snapshot, edits) = self
-            .wrap_map
-            .update(cx, |map, cx| map.sync(tab_snapshot.clone(), edits, cx));
-        let block_snapshot = self.block_map.read(wrap_snapshot.clone(), edits);
-
-        DisplaySnapshot {
-            buffer_snapshot: self.buffer.read(cx).snapshot(cx),
-            fold_snapshot,
-            inlay_snapshot,
-            tab_snapshot,
-            wrap_snapshot,
-            block_snapshot,
-            text_highlights: self.text_highlights.clone(),
-            inlay_highlights: self.inlay_highlights.clone(),
-            clip_at_line_ends: self.clip_at_line_ends,
-        }
-    }
-
-    pub fn set_state(&mut self, other: &DisplaySnapshot, cx: &mut ModelContext<Self>) {
-        self.fold(
-            other
-                .folds_in_range(0..other.buffer_snapshot.len())
-                .map(|fold| fold.range.to_offset(&other.buffer_snapshot)),
-            cx,
-        );
-    }
-
-    pub fn fold<T: ToOffset>(
-        &mut self,
-        ranges: impl IntoIterator<Item = Range<T>>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        let snapshot = self.buffer.read(cx).snapshot(cx);
-        let edits = self.buffer_subscription.consume().into_inner();
-        let tab_size = Self::tab_size(&self.buffer, cx);
-        let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
-        let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
-        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
-        let (snapshot, edits) = self
-            .wrap_map
-            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
-        self.block_map.read(snapshot, edits);
-        let (snapshot, edits) = fold_map.fold(ranges);
-        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
-        let (snapshot, edits) = self
-            .wrap_map
-            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
-        self.block_map.read(snapshot, edits);
-    }
-
-    pub fn unfold<T: ToOffset>(
-        &mut self,
-        ranges: impl IntoIterator<Item = Range<T>>,
-        inclusive: bool,
-        cx: &mut ModelContext<Self>,
-    ) {
-        let snapshot = self.buffer.read(cx).snapshot(cx);
-        let edits = self.buffer_subscription.consume().into_inner();
-        let tab_size = Self::tab_size(&self.buffer, cx);
-        let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
-        let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
-        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
-        let (snapshot, edits) = self
-            .wrap_map
-            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
-        self.block_map.read(snapshot, edits);
-        let (snapshot, edits) = fold_map.unfold(ranges, inclusive);
-        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
-        let (snapshot, edits) = self
-            .wrap_map
-            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
-        self.block_map.read(snapshot, edits);
-    }
-
-    pub fn insert_blocks(
-        &mut self,
-        blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
-        cx: &mut ModelContext<Self>,
-    ) -> Vec<BlockId> {
-        let snapshot = self.buffer.read(cx).snapshot(cx);
-        let edits = self.buffer_subscription.consume().into_inner();
-        let tab_size = Self::tab_size(&self.buffer, cx);
-        let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
-        let (snapshot, edits) = self.fold_map.read(snapshot, edits);
-        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
-        let (snapshot, edits) = self
-            .wrap_map
-            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
-        let mut block_map = self.block_map.write(snapshot, edits);
-        block_map.insert(blocks)
-    }
-
-    pub fn replace_blocks(&mut self, styles: HashMap<BlockId, RenderBlock>) {
-        self.block_map.replace(styles);
-    }
-
-    pub fn remove_blocks(&mut self, ids: HashSet<BlockId>, cx: &mut ModelContext<Self>) {
-        let snapshot = self.buffer.read(cx).snapshot(cx);
-        let edits = self.buffer_subscription.consume().into_inner();
-        let tab_size = Self::tab_size(&self.buffer, cx);
-        let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
-        let (snapshot, edits) = self.fold_map.read(snapshot, edits);
-        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
-        let (snapshot, edits) = self
-            .wrap_map
-            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
-        let mut block_map = self.block_map.write(snapshot, edits);
-        block_map.remove(ids);
-    }
-
-    pub fn highlight_text(
-        &mut self,
-        type_id: TypeId,
-        ranges: Vec<Range<Anchor>>,
-        style: HighlightStyle,
-    ) {
-        self.text_highlights
-            .insert(Some(type_id), Arc::new((style, ranges)));
-    }
-
-    pub fn highlight_inlays(
-        &mut self,
-        type_id: TypeId,
-        highlights: Vec<InlayHighlight>,
-        style: HighlightStyle,
-    ) {
-        for highlight in highlights {
-            self.inlay_highlights
-                .entry(type_id)
-                .or_default()
-                .insert(highlight.inlay, (style, highlight));
-        }
-    }
-
-    pub fn text_highlights(&self, type_id: TypeId) -> Option<(HighlightStyle, &[Range<Anchor>])> {
-        let highlights = self.text_highlights.get(&Some(type_id))?;
-        Some((highlights.0, &highlights.1))
-    }
-    pub fn clear_highlights(&mut self, type_id: TypeId) -> bool {
-        let mut cleared = self.text_highlights.remove(&Some(type_id)).is_some();
-        cleared |= self.inlay_highlights.remove(&type_id).is_none();
-        cleared
-    }
-
-    pub fn set_font(&self, font: Font, font_size: Pixels, cx: &mut ModelContext<Self>) -> bool {
-        self.wrap_map
-            .update(cx, |map, cx| map.set_font_with_size(font, font_size, cx))
-    }
-
-    pub fn set_fold_ellipses_color(&mut self, color: Hsla) -> bool {
-        self.fold_map.set_ellipses_color(color)
-    }
-
-    pub fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut ModelContext<Self>) -> bool {
-        self.wrap_map
-            .update(cx, |map, cx| map.set_wrap_width(width, cx))
-    }
-
-    pub fn current_inlays(&self) -> impl Iterator<Item = &Inlay> {
-        self.inlay_map.current_inlays()
-    }
-
-    pub fn splice_inlays(
-        &mut self,
-        to_remove: Vec<InlayId>,
-        to_insert: Vec<Inlay>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        if to_remove.is_empty() && to_insert.is_empty() {
-            return;
-        }
-        let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
-        let edits = self.buffer_subscription.consume().into_inner();
-        let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
-        let (snapshot, edits) = self.fold_map.read(snapshot, edits);
-        let tab_size = Self::tab_size(&self.buffer, cx);
-        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
-        let (snapshot, edits) = self
-            .wrap_map
-            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
-        self.block_map.read(snapshot, edits);
-
-        let (snapshot, edits) = self.inlay_map.splice(to_remove, to_insert);
-        let (snapshot, edits) = self.fold_map.read(snapshot, edits);
-        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
-        let (snapshot, edits) = self
-            .wrap_map
-            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
-        self.block_map.read(snapshot, edits);
-    }
-
-    fn tab_size(buffer: &Model<MultiBuffer>, cx: &mut ModelContext<Self>) -> NonZeroU32 {
-        let language = buffer
-            .read(cx)
-            .as_singleton()
-            .and_then(|buffer| buffer.read(cx).language());
-        language_settings(language.as_deref(), None, cx).tab_size
-    }
-
-    #[cfg(test)]
-    pub fn is_rewrapping(&self, cx: &gpui::AppContext) -> bool {
-        self.wrap_map.read(cx).is_rewrapping()
-    }
-}
-
-#[derive(Debug, Default)]
-pub struct Highlights<'a> {
-    pub text_highlights: Option<&'a TextHighlights>,
-    pub inlay_highlights: Option<&'a InlayHighlights>,
-    pub inlay_highlight_style: Option<HighlightStyle>,
-    pub suggestion_highlight_style: Option<HighlightStyle>,
-}
-
-pub struct HighlightedChunk<'a> {
-    pub chunk: &'a str,
-    pub style: Option<HighlightStyle>,
-    pub is_tab: bool,
-}
-
-pub struct DisplaySnapshot {
-    pub buffer_snapshot: MultiBufferSnapshot,
-    pub fold_snapshot: fold_map::FoldSnapshot,
-    inlay_snapshot: inlay_map::InlaySnapshot,
-    tab_snapshot: tab_map::TabSnapshot,
-    wrap_snapshot: wrap_map::WrapSnapshot,
-    block_snapshot: block_map::BlockSnapshot,
-    text_highlights: TextHighlights,
-    inlay_highlights: InlayHighlights,
-    clip_at_line_ends: bool,
-}
-
-impl DisplaySnapshot {
-    #[cfg(test)]
-    pub fn fold_count(&self) -> usize {
-        self.fold_snapshot.fold_count()
-    }
-
-    pub fn is_empty(&self) -> bool {
-        self.buffer_snapshot.len() == 0
-    }
-
-    pub fn buffer_rows(&self, start_row: u32) -> DisplayBufferRows {
-        self.block_snapshot.buffer_rows(start_row)
-    }
-
-    pub fn max_buffer_row(&self) -> u32 {
-        self.buffer_snapshot.max_buffer_row()
-    }
-
-    pub fn prev_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) {
-        loop {
-            let mut inlay_point = self.inlay_snapshot.to_inlay_point(point);
-            let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Left);
-            fold_point.0.column = 0;
-            inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);
-            point = self.inlay_snapshot.to_buffer_point(inlay_point);
-
-            let mut display_point = self.point_to_display_point(point, Bias::Left);
-            *display_point.column_mut() = 0;
-            let next_point = self.display_point_to_point(display_point, Bias::Left);
-            if next_point == point {
-                return (point, display_point);
-            }
-            point = next_point;
-        }
-    }
-
-    pub fn next_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) {
-        loop {
-            let mut inlay_point = self.inlay_snapshot.to_inlay_point(point);
-            let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Right);
-            fold_point.0.column = self.fold_snapshot.line_len(fold_point.row());
-            inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);
-            point = self.inlay_snapshot.to_buffer_point(inlay_point);
-
-            let mut display_point = self.point_to_display_point(point, Bias::Right);
-            *display_point.column_mut() = self.line_len(display_point.row());
-            let next_point = self.display_point_to_point(display_point, Bias::Right);
-            if next_point == point {
-                return (point, display_point);
-            }
-            point = next_point;
-        }
-    }
-
-    // used by line_mode selections and tries to match vim behaviour
-    pub fn expand_to_line(&self, range: Range<Point>) -> Range<Point> {
-        let new_start = if range.start.row == 0 {
-            Point::new(0, 0)
-        } else if range.start.row == self.max_buffer_row()
-            || (range.end.column > 0 && range.end.row == self.max_buffer_row())
-        {
-            Point::new(range.start.row - 1, self.line_len(range.start.row - 1))
-        } else {
-            self.prev_line_boundary(range.start).0
-        };
-
-        let new_end = if range.end.column == 0 {
-            range.end
-        } else if range.end.row < self.max_buffer_row() {
-            self.buffer_snapshot
-                .clip_point(Point::new(range.end.row + 1, 0), Bias::Left)
-        } else {
-            self.buffer_snapshot.max_point()
-        };
-
-        new_start..new_end
-    }
-
-    fn point_to_display_point(&self, point: Point, bias: Bias) -> DisplayPoint {
-        let inlay_point = self.inlay_snapshot.to_inlay_point(point);
-        let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);
-        let tab_point = self.tab_snapshot.to_tab_point(fold_point);
-        let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point);
-        let block_point = self.block_snapshot.to_block_point(wrap_point);
-        DisplayPoint(block_point)
-    }
-
-    fn display_point_to_point(&self, point: DisplayPoint, bias: Bias) -> Point {
-        self.inlay_snapshot
-            .to_buffer_point(self.display_point_to_inlay_point(point, bias))
-    }
-
-    pub fn display_point_to_inlay_offset(&self, point: DisplayPoint, bias: Bias) -> InlayOffset {
-        self.inlay_snapshot
-            .to_offset(self.display_point_to_inlay_point(point, bias))
-    }
-
-    pub fn anchor_to_inlay_offset(&self, anchor: Anchor) -> InlayOffset {
-        self.inlay_snapshot
-            .to_inlay_offset(anchor.to_offset(&self.buffer_snapshot))
-    }
-
-    fn display_point_to_inlay_point(&self, point: DisplayPoint, bias: Bias) -> InlayPoint {
-        let block_point = point.0;
-        let wrap_point = self.block_snapshot.to_wrap_point(block_point);
-        let tab_point = self.wrap_snapshot.to_tab_point(wrap_point);
-        let fold_point = self.tab_snapshot.to_fold_point(tab_point, bias).0;
-        fold_point.to_inlay_point(&self.fold_snapshot)
-    }
-
-    pub fn display_point_to_fold_point(&self, point: DisplayPoint, bias: Bias) -> FoldPoint {
-        let block_point = point.0;
-        let wrap_point = self.block_snapshot.to_wrap_point(block_point);
-        let tab_point = self.wrap_snapshot.to_tab_point(wrap_point);
-        self.tab_snapshot.to_fold_point(tab_point, bias).0
-    }
-
-    pub fn fold_point_to_display_point(&self, fold_point: FoldPoint) -> DisplayPoint {
-        let tab_point = self.tab_snapshot.to_tab_point(fold_point);
-        let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point);
-        let block_point = self.block_snapshot.to_block_point(wrap_point);
-        DisplayPoint(block_point)
-    }
-
-    pub fn max_point(&self) -> DisplayPoint {
-        DisplayPoint(self.block_snapshot.max_point())
-    }
-
-    /// Returns text chunks starting at the given display row until the end of the file
-    pub fn text_chunks(&self, display_row: u32) -> impl Iterator<Item = &str> {
-        self.block_snapshot
-            .chunks(
-                display_row..self.max_point().row() + 1,
-                false,
-                Highlights::default(),
-            )
-            .map(|h| h.text)
-    }
-
-    /// Returns text chunks starting at the end of the given display row in reverse until the start of the file
-    pub fn reverse_text_chunks(&self, display_row: u32) -> impl Iterator<Item = &str> {
-        (0..=display_row).into_iter().rev().flat_map(|row| {
-            self.block_snapshot
-                .chunks(row..row + 1, false, Highlights::default())
-                .map(|h| h.text)
-                .collect::<Vec<_>>()
-                .into_iter()
-                .rev()
-        })
-    }
-
-    pub fn chunks<'a>(
-        &'a self,
-        display_rows: Range<u32>,
-        language_aware: bool,
-        inlay_highlight_style: Option<HighlightStyle>,
-        suggestion_highlight_style: Option<HighlightStyle>,
-    ) -> DisplayChunks<'a> {
-        self.block_snapshot.chunks(
-            display_rows,
-            language_aware,
-            Highlights {
-                text_highlights: Some(&self.text_highlights),
-                inlay_highlights: Some(&self.inlay_highlights),
-                inlay_highlight_style,
-                suggestion_highlight_style,
-            },
-        )
-    }
-
-    pub fn highlighted_chunks<'a>(
-        &'a self,
-        display_rows: Range<u32>,
-        language_aware: bool,
-        editor_style: &'a EditorStyle,
-    ) -> impl Iterator<Item = HighlightedChunk<'a>> {
-        self.chunks(
-            display_rows,
-            language_aware,
-            Some(editor_style.inlays_style),
-            Some(editor_style.suggestions_style),
-        )
-        .map(|chunk| {
-            let mut highlight_style = chunk
-                .syntax_highlight_id
-                .and_then(|id| id.style(&editor_style.syntax));
-
-            if let Some(chunk_highlight) = chunk.highlight_style {
-                if let Some(highlight_style) = highlight_style.as_mut() {
-                    highlight_style.highlight(chunk_highlight);
-                } else {
-                    highlight_style = Some(chunk_highlight);
-                }
-            }
-
-            let mut diagnostic_highlight = HighlightStyle::default();
-
-            if chunk.is_unnecessary {
-                diagnostic_highlight.fade_out = Some(UNNECESSARY_CODE_FADE);
-            }
-
-            if let Some(severity) = chunk.diagnostic_severity {
-                // Omit underlines for HINT/INFO diagnostics on 'unnecessary' code.
-                if severity <= DiagnosticSeverity::WARNING || !chunk.is_unnecessary {
-                    let diagnostic_color =
-                        super::diagnostic_style(severity, true, &editor_style.status);
-                    diagnostic_highlight.underline = Some(UnderlineStyle {
-                        color: Some(diagnostic_color),
-                        thickness: 1.0.into(),
-                        wavy: true,
-                    });
-                }
-            }
-
-            if let Some(highlight_style) = highlight_style.as_mut() {
-                highlight_style.highlight(diagnostic_highlight);
-            } else {
-                highlight_style = Some(diagnostic_highlight);
-            }
-
-            HighlightedChunk {
-                chunk: chunk.text,
-                style: highlight_style,
-                is_tab: chunk.is_tab,
-            }
-        })
-    }
-
-    pub fn layout_row(
-        &self,
-        display_row: u32,
-        TextLayoutDetails {
-            text_system,
-            editor_style,
-            rem_size,
-        }: &TextLayoutDetails,
-    ) -> Arc<LineLayout> {
-        let mut runs = Vec::new();
-        let mut line = String::new();
-
-        let range = display_row..display_row + 1;
-        for chunk in self.highlighted_chunks(range, false, &editor_style) {
-            line.push_str(chunk.chunk);
-
-            let text_style = if let Some(style) = chunk.style {
-                Cow::Owned(editor_style.text.clone().highlight(style))
-            } else {
-                Cow::Borrowed(&editor_style.text)
-            };
-
-            runs.push(text_style.to_run(chunk.chunk.len()))
-        }
-
-        if line.ends_with('\n') {
-            line.pop();
-            if let Some(last_run) = runs.last_mut() {
-                last_run.len -= 1;
-                if last_run.len == 0 {
-                    runs.pop();
-                }
-            }
-        }
-
-        let font_size = editor_style.text.font_size.to_pixels(*rem_size);
-        text_system
-            .layout_line(&line, font_size, &runs)
-            .expect("we expect the font to be loaded because it's rendered by the editor")
-    }
-
-    pub fn x_for_display_point(
-        &self,
-        display_point: DisplayPoint,
-        text_layout_details: &TextLayoutDetails,
-    ) -> Pixels {
-        let line = self.layout_row(display_point.row(), text_layout_details);
-        line.x_for_index(display_point.column() as usize)
-    }
-
-    pub fn display_column_for_x(
-        &self,
-        display_row: u32,
-        x: Pixels,
-        details: &TextLayoutDetails,
-    ) -> u32 {
-        let layout_line = self.layout_row(display_row, details);
-        layout_line.closest_index_for_x(x) as u32
-    }
-
-    pub fn chars_at(
-        &self,
-        mut point: DisplayPoint,
-    ) -> impl Iterator<Item = (char, DisplayPoint)> + '_ {
-        point = DisplayPoint(self.block_snapshot.clip_point(point.0, Bias::Left));
-        self.text_chunks(point.row())
-            .flat_map(str::chars)
-            .skip_while({
-                let mut column = 0;
-                move |char| {
-                    let at_point = column >= point.column();
-                    column += char.len_utf8() as u32;
-                    !at_point
-                }
-            })
-            .map(move |ch| {
-                let result = (ch, point);
-                if ch == '\n' {
-                    *point.row_mut() += 1;
-                    *point.column_mut() = 0;
-                } else {
-                    *point.column_mut() += ch.len_utf8() as u32;
-                }
-                result
-            })
-    }
-
-    pub fn reverse_chars_at(
-        &self,
-        mut point: DisplayPoint,
-    ) -> impl Iterator<Item = (char, DisplayPoint)> + '_ {
-        point = DisplayPoint(self.block_snapshot.clip_point(point.0, Bias::Left));
-        self.reverse_text_chunks(point.row())
-            .flat_map(|chunk| chunk.chars().rev())
-            .skip_while({
-                let mut column = self.line_len(point.row());
-                if self.max_point().row() > point.row() {
-                    column += 1;
-                }
-
-                move |char| {
-                    let at_point = column <= point.column();
-                    column = column.saturating_sub(char.len_utf8() as u32);
-                    !at_point
-                }
-            })
-            .map(move |ch| {
-                if ch == '\n' {
-                    *point.row_mut() -= 1;
-                    *point.column_mut() = self.line_len(point.row());
-                } else {
-                    *point.column_mut() = point.column().saturating_sub(ch.len_utf8() as u32);
-                }
-                (ch, point)
-            })
-    }
-
-    pub fn column_to_chars(&self, display_row: u32, target: u32) -> u32 {
-        let mut count = 0;
-        let mut column = 0;
-        for (c, _) in self.chars_at(DisplayPoint::new(display_row, 0)) {
-            if column >= target {
-                break;
-            }
-            count += 1;
-            column += c.len_utf8() as u32;
-        }
-        count
-    }
-
-    pub fn column_from_chars(&self, display_row: u32, char_count: u32) -> u32 {
-        let mut column = 0;
-
-        for (count, (c, _)) in self.chars_at(DisplayPoint::new(display_row, 0)).enumerate() {
-            if c == '\n' || count >= char_count as usize {
-                break;
-            }
-            column += c.len_utf8() as u32;
-        }
-
-        column
-    }
-
-    pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
-        let mut clipped = self.block_snapshot.clip_point(point.0, bias);
-        if self.clip_at_line_ends {
-            clipped = self.clip_at_line_end(DisplayPoint(clipped)).0
-        }
-        DisplayPoint(clipped)
-    }
-
-    pub fn clip_at_line_end(&self, point: DisplayPoint) -> DisplayPoint {
-        let mut point = point.0;
-        if point.column == self.line_len(point.row) {
-            point.column = point.column.saturating_sub(1);
-            point = self.block_snapshot.clip_point(point, Bias::Left);
-        }
-        DisplayPoint(point)
-    }
-
-    pub fn folds_in_range<T>(&self, range: Range<T>) -> impl Iterator<Item = &Fold>
-    where
-        T: ToOffset,
-    {
-        self.fold_snapshot.folds_in_range(range)
-    }
-
-    pub fn blocks_in_range(
-        &self,
-        rows: Range<u32>,
-    ) -> impl Iterator<Item = (u32, &TransformBlock)> {
-        self.block_snapshot.blocks_in_range(rows)
-    }
-
-    pub fn intersects_fold<T: ToOffset>(&self, offset: T) -> bool {
-        self.fold_snapshot.intersects_fold(offset)
-    }
-
-    pub fn is_line_folded(&self, buffer_row: u32) -> bool {
-        self.fold_snapshot.is_line_folded(buffer_row)
-    }
-
-    pub fn is_block_line(&self, display_row: u32) -> bool {
-        self.block_snapshot.is_block_line(display_row)
-    }
-
-    pub fn soft_wrap_indent(&self, display_row: u32) -> Option<u32> {
-        let wrap_row = self
-            .block_snapshot
-            .to_wrap_point(BlockPoint::new(display_row, 0))
-            .row();
-        self.wrap_snapshot.soft_wrap_indent(wrap_row)
-    }
-
-    pub fn text(&self) -> String {
-        self.text_chunks(0).collect()
-    }
-
-    pub fn line(&self, display_row: u32) -> String {
-        let mut result = String::new();
-        for chunk in self.text_chunks(display_row) {
-            if let Some(ix) = chunk.find('\n') {
-                result.push_str(&chunk[0..ix]);
-                break;
-            } else {
-                result.push_str(chunk);
-            }
-        }
-        result
-    }
-
-    pub fn line_indent(&self, display_row: u32) -> (u32, bool) {
-        let mut indent = 0;
-        let mut is_blank = true;
-        for (c, _) in self.chars_at(DisplayPoint::new(display_row, 0)) {
-            if c == ' ' {
-                indent += 1;
-            } else {
-                is_blank = c == '\n';
-                break;
-            }
-        }
-        (indent, is_blank)
-    }
-
-    pub fn line_indent_for_buffer_row(&self, buffer_row: u32) -> (u32, bool) {
-        let (buffer, range) = self
-            .buffer_snapshot
-            .buffer_line_for_row(buffer_row)
-            .unwrap();
-
-        let mut indent_size = 0;
-        let mut is_blank = false;
-        for c in buffer.chars_at(Point::new(range.start.row, 0)) {
-            if c == ' ' || c == '\t' {
-                indent_size += 1;
-            } else {
-                if c == '\n' {
-                    is_blank = true;
-                }
-                break;
-            }
-        }
-
-        (indent_size, is_blank)
-    }
-
-    pub fn line_len(&self, row: u32) -> u32 {
-        self.block_snapshot.line_len(row)
-    }
-
-    pub fn longest_row(&self) -> u32 {
-        self.block_snapshot.longest_row()
-    }
-
-    pub fn fold_for_line(self: &Self, buffer_row: u32) -> Option<FoldStatus> {
-        if self.is_line_folded(buffer_row) {
-            Some(FoldStatus::Folded)
-        } else if self.is_foldable(buffer_row) {
-            Some(FoldStatus::Foldable)
-        } else {
-            None
-        }
-    }
-
-    pub fn is_foldable(self: &Self, buffer_row: u32) -> bool {
-        let max_row = self.buffer_snapshot.max_buffer_row();
-        if buffer_row >= max_row {
-            return false;
-        }
-
-        let (indent_size, is_blank) = self.line_indent_for_buffer_row(buffer_row);
-        if is_blank {
-            return false;
-        }
-
-        for next_row in (buffer_row + 1)..=max_row {
-            let (next_indent_size, next_line_is_blank) = self.line_indent_for_buffer_row(next_row);
-            if next_indent_size > indent_size {
-                return true;
-            } else if !next_line_is_blank {
-                break;
-            }
-        }
-
-        false
-    }
-
-    pub fn foldable_range(self: &Self, buffer_row: u32) -> Option<Range<Point>> {
-        let start = Point::new(buffer_row, self.buffer_snapshot.line_len(buffer_row));
-        if self.is_foldable(start.row) && !self.is_line_folded(start.row) {
-            let (start_indent, _) = self.line_indent_for_buffer_row(buffer_row);
-            let max_point = self.buffer_snapshot.max_point();
-            let mut end = None;
-
-            for row in (buffer_row + 1)..=max_point.row {
-                let (indent, is_blank) = self.line_indent_for_buffer_row(row);
-                if !is_blank && indent <= start_indent {
-                    let prev_row = row - 1;
-                    end = Some(Point::new(
-                        prev_row,
-                        self.buffer_snapshot.line_len(prev_row),
-                    ));
-                    break;
-                }
-            }
-            let end = end.unwrap_or(max_point);
-            Some(start..end)
-        } else {
-            None
-        }
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn text_highlight_ranges<Tag: ?Sized + 'static>(
-        &self,
-    ) -> Option<Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
-        let type_id = TypeId::of::<Tag>();
-        self.text_highlights.get(&Some(type_id)).cloned()
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn inlay_highlights<Tag: ?Sized + 'static>(
-        &self,
-    ) -> Option<&HashMap<InlayId, (HighlightStyle, InlayHighlight)>> {
-        let type_id = TypeId::of::<Tag>();
-        self.inlay_highlights.get(&type_id)
-    }
-}
-
-#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)]
-pub struct DisplayPoint(BlockPoint);
-
-impl Debug for DisplayPoint {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.write_fmt(format_args!(
-            "DisplayPoint({}, {})",
-            self.row(),
-            self.column()
-        ))
-    }
-}
-
-impl DisplayPoint {
-    pub fn new(row: u32, column: u32) -> Self {
-        Self(BlockPoint(Point::new(row, column)))
-    }
-
-    pub fn zero() -> Self {
-        Self::new(0, 0)
-    }
-
-    pub fn is_zero(&self) -> bool {
-        self.0.is_zero()
-    }
-
-    pub fn row(self) -> u32 {
-        self.0.row
-    }
-
-    pub fn column(self) -> u32 {
-        self.0.column
-    }
-
-    pub fn row_mut(&mut self) -> &mut u32 {
-        &mut self.0.row
-    }
-
-    pub fn column_mut(&mut self) -> &mut u32 {
-        &mut self.0.column
-    }
-
-    pub fn to_point(self, map: &DisplaySnapshot) -> Point {
-        map.display_point_to_point(self, Bias::Left)
-    }
-
-    pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize {
-        let wrap_point = map.block_snapshot.to_wrap_point(self.0);
-        let tab_point = map.wrap_snapshot.to_tab_point(wrap_point);
-        let fold_point = map.tab_snapshot.to_fold_point(tab_point, bias).0;
-        let inlay_point = fold_point.to_inlay_point(&map.fold_snapshot);
-        map.inlay_snapshot
-            .to_buffer_offset(map.inlay_snapshot.to_offset(inlay_point))
-    }
-}
-
-impl ToDisplayPoint for usize {
-    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
-        map.point_to_display_point(self.to_point(&map.buffer_snapshot), Bias::Left)
-    }
-}
-
-impl ToDisplayPoint for OffsetUtf16 {
-    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
-        self.to_offset(&map.buffer_snapshot).to_display_point(map)
-    }
-}
-
-impl ToDisplayPoint for Point {
-    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
-        map.point_to_display_point(*self, Bias::Left)
-    }
-}
-
-impl ToDisplayPoint for Anchor {
-    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
-        self.to_point(&map.buffer_snapshot).to_display_point(map)
-    }
-}
-
-pub fn next_rows(display_row: u32, display_map: &DisplaySnapshot) -> impl Iterator<Item = u32> {
-    let max_row = display_map.max_point().row();
-    let start_row = display_row + 1;
-    let mut current = None;
-    std::iter::from_fn(move || {
-        if current == None {
-            current = Some(start_row);
-        } else {
-            current = Some(current.unwrap() + 1)
-        }
-        if current.unwrap() > max_row {
-            None
-        } else {
-            current
-        }
-    })
-}
-
-#[cfg(test)]
-pub mod tests {
-    use super::*;
-    use crate::{
-        movement,
-        test::{editor_test_context::EditorTestContext, marked_display_snapshot},
-    };
-    use gpui::{div, font, observe, px, AppContext, Context, Element, Hsla};
-    use language::{
-        language_settings::{AllLanguageSettings, AllLanguageSettingsContent},
-        Buffer, Language, LanguageConfig, SelectionGoal,
-    };
-    use project::Project;
-    use rand::{prelude::*, Rng};
-    use settings::SettingsStore;
-    use smol::stream::StreamExt;
-    use std::{env, sync::Arc};
-    use theme::{LoadThemes, SyntaxTheme};
-    use util::test::{marked_text_ranges, sample_text};
-    use Bias::*;
-
-    #[gpui::test(iterations = 100)]
-    async fn test_random_display_map(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
-        cx.background_executor.set_block_on_ticks(0..=50);
-        let operations = env::var("OPERATIONS")
-            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
-            .unwrap_or(10);
-
-        let _test_platform = &cx.test_platform;
-        let mut tab_size = rng.gen_range(1..=4);
-        let buffer_start_excerpt_header_height = rng.gen_range(1..=5);
-        let excerpt_header_height = rng.gen_range(1..=5);
-        let font_size = px(14.0);
-        let max_wrap_width = 300.0;
-        let mut wrap_width = if rng.gen_bool(0.1) {
-            None
-        } else {
-            Some(px(rng.gen_range(0.0..=max_wrap_width)))
-        };
-
-        log::info!("tab size: {}", tab_size);
-        log::info!("wrap width: {:?}", wrap_width);
-
-        cx.update(|cx| {
-            init_test(cx, |s| s.defaults.tab_size = NonZeroU32::new(tab_size));
-        });
-
-        let buffer = cx.update(|cx| {
-            if rng.gen() {
-                let len = rng.gen_range(0..10);
-                let text = util::RandomCharIter::new(&mut rng)
-                    .take(len)
-                    .collect::<String>();
-                MultiBuffer::build_simple(&text, cx)
-            } else {
-                MultiBuffer::build_random(&mut rng, cx)
-            }
-        });
-
-        let map = cx.new_model(|cx| {
-            DisplayMap::new(
-                buffer.clone(),
-                font("Helvetica"),
-                font_size,
-                wrap_width,
-                buffer_start_excerpt_header_height,
-                excerpt_header_height,
-                cx,
-            )
-        });
-        let mut notifications = observe(&map, cx);
-        let mut fold_count = 0;
-        let mut blocks = Vec::new();
-
-        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
-        log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
-        log::info!("fold text: {:?}", snapshot.fold_snapshot.text());
-        log::info!("tab text: {:?}", snapshot.tab_snapshot.text());
-        log::info!("wrap text: {:?}", snapshot.wrap_snapshot.text());
-        log::info!("block text: {:?}", snapshot.block_snapshot.text());
-        log::info!("display text: {:?}", snapshot.text());
-
-        for _i in 0..operations {
-            match rng.gen_range(0..100) {
-                0..=19 => {
-                    wrap_width = if rng.gen_bool(0.2) {
-                        None
-                    } else {
-                        Some(px(rng.gen_range(0.0..=max_wrap_width)))
-                    };
-                    log::info!("setting wrap width to {:?}", wrap_width);
-                    map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
-                }
-                20..=29 => {
-                    let mut tab_sizes = vec![1, 2, 3, 4];
-                    tab_sizes.remove((tab_size - 1) as usize);
-                    tab_size = *tab_sizes.choose(&mut rng).unwrap();
-                    log::info!("setting tab size to {:?}", tab_size);
-                    cx.update(|cx| {
-                        cx.update_global::<SettingsStore, _>(|store, cx| {
-                            store.update_user_settings::<AllLanguageSettings>(cx, |s| {
-                                s.defaults.tab_size = NonZeroU32::new(tab_size);
-                            });
-                        });
-                    });
-                }
-                30..=44 => {
-                    map.update(cx, |map, cx| {
-                        if rng.gen() || blocks.is_empty() {
-                            let buffer = map.snapshot(cx).buffer_snapshot;
-                            let block_properties = (0..rng.gen_range(1..=1))
-                                .map(|_| {
-                                    let position =
-                                        buffer.anchor_after(buffer.clip_offset(
-                                            rng.gen_range(0..=buffer.len()),
-                                            Bias::Left,
-                                        ));
-
-                                    let disposition = if rng.gen() {
-                                        BlockDisposition::Above
-                                    } else {
-                                        BlockDisposition::Below
-                                    };
-                                    let height = rng.gen_range(1..5);
-                                    log::info!(
-                                        "inserting block {:?} {:?} with height {}",
-                                        disposition,
-                                        position.to_point(&buffer),
-                                        height
-                                    );
-                                    BlockProperties {
-                                        style: BlockStyle::Fixed,
-                                        position,
-                                        height,
-                                        disposition,
-                                        render: Arc::new(|_| div().into_any()),
-                                    }
-                                })
-                                .collect::<Vec<_>>();
-                            blocks.extend(map.insert_blocks(block_properties, cx));
-                        } else {
-                            blocks.shuffle(&mut rng);
-                            let remove_count = rng.gen_range(1..=4.min(blocks.len()));
-                            let block_ids_to_remove = (0..remove_count)
-                                .map(|_| blocks.remove(rng.gen_range(0..blocks.len())))
-                                .collect();
-                            log::info!("removing block ids {:?}", block_ids_to_remove);
-                            map.remove_blocks(block_ids_to_remove, cx);
-                        }
-                    });
-                }
-                45..=79 => {
-                    let mut ranges = Vec::new();
-                    for _ in 0..rng.gen_range(1..=3) {
-                        buffer.read_with(cx, |buffer, cx| {
-                            let buffer = buffer.read(cx);
-                            let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
-                            let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
-                            ranges.push(start..end);
-                        });
-                    }
-
-                    if rng.gen() && fold_count > 0 {
-                        log::info!("unfolding ranges: {:?}", ranges);
-                        map.update(cx, |map, cx| {
-                            map.unfold(ranges, true, cx);
-                        });
-                    } else {
-                        log::info!("folding ranges: {:?}", ranges);
-                        map.update(cx, |map, cx| {
-                            map.fold(ranges, cx);
-                        });
-                    }
-                }
-                _ => {
-                    buffer.update(cx, |buffer, cx| buffer.randomly_mutate(&mut rng, 5, cx));
-                }
-            }
-
-            if map.read_with(cx, |map, cx| map.is_rewrapping(cx)) {
-                notifications.next().await.unwrap();
-            }
-
-            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
-            fold_count = snapshot.fold_count();
-            log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
-            log::info!("fold text: {:?}", snapshot.fold_snapshot.text());
-            log::info!("tab text: {:?}", snapshot.tab_snapshot.text());
-            log::info!("wrap text: {:?}", snapshot.wrap_snapshot.text());
-            log::info!("block text: {:?}", snapshot.block_snapshot.text());
-            log::info!("display text: {:?}", snapshot.text());
-
-            // Line boundaries
-            let buffer = &snapshot.buffer_snapshot;
-            for _ in 0..5 {
-                let row = rng.gen_range(0..=buffer.max_point().row);
-                let column = rng.gen_range(0..=buffer.line_len(row));
-                let point = buffer.clip_point(Point::new(row, column), Left);
-
-                let (prev_buffer_bound, prev_display_bound) = snapshot.prev_line_boundary(point);
-                let (next_buffer_bound, next_display_bound) = snapshot.next_line_boundary(point);
-
-                assert!(prev_buffer_bound <= point);
-                assert!(next_buffer_bound >= point);
-                assert_eq!(prev_buffer_bound.column, 0);
-                assert_eq!(prev_display_bound.column(), 0);
-                if next_buffer_bound < buffer.max_point() {
-                    assert_eq!(buffer.chars_at(next_buffer_bound).next(), Some('\n'));
-                }
-
-                assert_eq!(
-                    prev_display_bound,
-                    prev_buffer_bound.to_display_point(&snapshot),
-                    "row boundary before {:?}. reported buffer row boundary: {:?}",
-                    point,
-                    prev_buffer_bound
-                );
-                assert_eq!(
-                    next_display_bound,
-                    next_buffer_bound.to_display_point(&snapshot),
-                    "display row boundary after {:?}. reported buffer row boundary: {:?}",
-                    point,
-                    next_buffer_bound
-                );
-                assert_eq!(
-                    prev_buffer_bound,
-                    prev_display_bound.to_point(&snapshot),
-                    "row boundary before {:?}. reported display row boundary: {:?}",
-                    point,
-                    prev_display_bound
-                );
-                assert_eq!(
-                    next_buffer_bound,
-                    next_display_bound.to_point(&snapshot),
-                    "row boundary after {:?}. reported display row boundary: {:?}",
-                    point,
-                    next_display_bound
-                );
-            }
-
-            // Movement
-            let min_point = snapshot.clip_point(DisplayPoint::new(0, 0), Left);
-            let max_point = snapshot.clip_point(snapshot.max_point(), Right);
-            for _ in 0..5 {
-                let row = rng.gen_range(0..=snapshot.max_point().row());
-                let column = rng.gen_range(0..=snapshot.line_len(row));
-                let point = snapshot.clip_point(DisplayPoint::new(row, column), Left);
-
-                log::info!("Moving from point {:?}", point);
-
-                let moved_right = movement::right(&snapshot, point);
-                log::info!("Right {:?}", moved_right);
-                if point < max_point {
-                    assert!(moved_right > point);
-                    if point.column() == snapshot.line_len(point.row())
-                        || snapshot.soft_wrap_indent(point.row()).is_some()
-                            && point.column() == snapshot.line_len(point.row()) - 1
-                    {
-                        assert!(moved_right.row() > point.row());
-                    }
-                } else {
-                    assert_eq!(moved_right, point);
-                }
-
-                let moved_left = movement::left(&snapshot, point);
-                log::info!("Left {:?}", moved_left);
-                if point > min_point {
-                    assert!(moved_left < point);
-                    if point.column() == 0 {
-                        assert!(moved_left.row() < point.row());
-                    }
-                } else {
-                    assert_eq!(moved_left, point);
-                }
-            }
-        }
-    }
-
-    #[gpui::test(retries = 5)]
-    async fn test_soft_wraps(cx: &mut gpui::TestAppContext) {
-        cx.background_executor
-            .set_block_on_ticks(usize::MAX..=usize::MAX);
-        cx.update(|cx| {
-            init_test(cx, |_| {});
-        });
-
-        let mut cx = EditorTestContext::new(cx).await;
-        let editor = cx.editor.clone();
-        let window = cx.window.clone();
-
-        _ = cx.update_window(window, |_, cx| {
-            let text_layout_details =
-                editor.update(cx, |editor, cx| editor.text_layout_details(cx));
-
-            let font_size = px(12.0);
-            let wrap_width = Some(px(64.));
-
-            let text = "one two three four five\nsix seven eight";
-            let buffer = MultiBuffer::build_simple(text, cx);
-            let map = cx.new_model(|cx| {
-                DisplayMap::new(
-                    buffer.clone(),
-                    font("Helvetica"),
-                    font_size,
-                    wrap_width,
-                    1,
-                    1,
-                    cx,
-                )
-            });
-
-            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
-            assert_eq!(
-                snapshot.text_chunks(0).collect::<String>(),
-                "one two \nthree four \nfive\nsix seven \neight"
-            );
-            assert_eq!(
-                snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Left),
-                DisplayPoint::new(0, 7)
-            );
-            assert_eq!(
-                snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Right),
-                DisplayPoint::new(1, 0)
-            );
-            assert_eq!(
-                movement::right(&snapshot, DisplayPoint::new(0, 7)),
-                DisplayPoint::new(1, 0)
-            );
-            assert_eq!(
-                movement::left(&snapshot, DisplayPoint::new(1, 0)),
-                DisplayPoint::new(0, 7)
-            );
-
-            let x = snapshot.x_for_display_point(DisplayPoint::new(1, 10), &text_layout_details);
-            assert_eq!(
-                movement::up(
-                    &snapshot,
-                    DisplayPoint::new(1, 10),
-                    SelectionGoal::None,
-                    false,
-                    &text_layout_details,
-                ),
-                (
-                    DisplayPoint::new(0, 7),
-                    SelectionGoal::HorizontalPosition(x.0)
-                )
-            );
-            assert_eq!(
-                movement::down(
-                    &snapshot,
-                    DisplayPoint::new(0, 7),
-                    SelectionGoal::HorizontalPosition(x.0),
-                    false,
-                    &text_layout_details
-                ),
-                (
-                    DisplayPoint::new(1, 10),
-                    SelectionGoal::HorizontalPosition(x.0)
-                )
-            );
-            assert_eq!(
-                movement::down(
-                    &snapshot,
-                    DisplayPoint::new(1, 10),
-                    SelectionGoal::HorizontalPosition(x.0),
-                    false,
-                    &text_layout_details
-                ),
-                (
-                    DisplayPoint::new(2, 4),
-                    SelectionGoal::HorizontalPosition(x.0)
-                )
-            );
-
-            let ix = snapshot.buffer_snapshot.text().find("seven").unwrap();
-            buffer.update(cx, |buffer, cx| {
-                buffer.edit([(ix..ix, "and ")], None, cx);
-            });
-
-            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
-            assert_eq!(
-                snapshot.text_chunks(1).collect::<String>(),
-                "three four \nfive\nsix and \nseven eight"
-            );
-
-            // Re-wrap on font size changes
-            map.update(cx, |map, cx| {
-                map.set_font(font("Helvetica"), px(font_size.0 + 3.), cx)
-            });
-
-            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
-            assert_eq!(
-                snapshot.text_chunks(1).collect::<String>(),
-                "three \nfour five\nsix and \nseven \neight"
-            )
-        });
-    }
-
-    #[gpui::test]
-    fn test_text_chunks(cx: &mut gpui::AppContext) {
-        init_test(cx, |_| {});
-
-        let text = sample_text(6, 6, 'a');
-        let buffer = MultiBuffer::build_simple(&text, cx);
-
-        let font_size = px(14.0);
-        let map = cx.new_model(|cx| {
-            DisplayMap::new(buffer.clone(), font("Helvetica"), font_size, None, 1, 1, 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"),
-                    (Point::new(2, 1)..Point::new(2, 1), "\t"),
-                ],
-                None,
-                cx,
-            )
-        });
-
-        assert_eq!(
-            map.update(cx, |map, cx| map.snapshot(cx))
-                .text_chunks(1)
-                .collect::<String>()
-                .lines()
-                .next(),
-            Some("    b   bbbbb")
-        );
-        assert_eq!(
-            map.update(cx, |map, cx| map.snapshot(cx))
-                .text_chunks(2)
-                .collect::<String>()
-                .lines()
-                .next(),
-            Some("c   ccccc")
-        );
-    }
-
-    #[gpui::test]
-    async fn test_chunks(cx: &mut gpui::TestAppContext) {
-        use unindent::Unindent as _;
-
-        let text = r#"
-            fn outer() {}
-
-            mod module {
-                fn inner() {}
-            }"#
-        .unindent();
-
-        let theme = SyntaxTheme::new_test(vec![
-            ("mod.body", Hsla::red().into()),
-            ("fn.name", Hsla::blue().into()),
-        ]);
-        let language = Arc::new(
-            Language::new(
-                LanguageConfig {
-                    name: "Test".into(),
-                    path_suffixes: vec![".test".to_string()],
-                    ..Default::default()
-                },
-                Some(tree_sitter_rust::language()),
-            )
-            .with_highlights_query(
-                r#"
-                (mod_item name: (identifier) body: _ @mod.body)
-                (function_item name: (identifier) @fn.name)
-                "#,
-            )
-            .unwrap(),
-        );
-        language.set_theme(&theme);
-
-        cx.update(|cx| init_test(cx, |s| s.defaults.tab_size = Some(2.try_into().unwrap())));
-
-        let buffer = cx.new_model(|cx| {
-            Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
-        });
-        cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
-        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
-
-        let font_size = px(14.0);
-
-        let map = cx
-            .new_model(|cx| DisplayMap::new(buffer, font("Helvetica"), font_size, None, 1, 1, cx));
-        assert_eq!(
-            cx.update(|cx| syntax_chunks(0..5, &map, &theme, cx)),
-            vec![
-                ("fn ".to_string(), None),
-                ("outer".to_string(), Some(Hsla::blue())),
-                ("() {}\n\nmod module ".to_string(), None),
-                ("{\n    fn ".to_string(), Some(Hsla::red())),
-                ("inner".to_string(), Some(Hsla::blue())),
-                ("() {}\n}".to_string(), Some(Hsla::red())),
-            ]
-        );
-        assert_eq!(
-            cx.update(|cx| syntax_chunks(3..5, &map, &theme, cx)),
-            vec![
-                ("    fn ".to_string(), Some(Hsla::red())),
-                ("inner".to_string(), Some(Hsla::blue())),
-                ("() {}\n}".to_string(), Some(Hsla::red())),
-            ]
-        );
-
-        map.update(cx, |map, cx| {
-            map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
-        });
-        assert_eq!(
-            cx.update(|cx| syntax_chunks(0..2, &map, &theme, cx)),
-            vec![
-                ("fn ".to_string(), None),
-                ("out".to_string(), Some(Hsla::blue())),
-                ("⋯".to_string(), None),
-                ("  fn ".to_string(), Some(Hsla::red())),
-                ("inner".to_string(), Some(Hsla::blue())),
-                ("() {}\n}".to_string(), Some(Hsla::red())),
-            ]
-        );
-    }
-
-    #[gpui::test]
-    async fn test_chunks_with_soft_wrapping(cx: &mut gpui::TestAppContext) {
-        use unindent::Unindent as _;
-
-        cx.background_executor
-            .set_block_on_ticks(usize::MAX..=usize::MAX);
-
-        let text = r#"
-            fn outer() {}
-
-            mod module {
-                fn inner() {}
-            }"#
-        .unindent();
-
-        let theme = SyntaxTheme::new_test(vec![
-            ("mod.body", Hsla::red().into()),
-            ("fn.name", Hsla::blue().into()),
-        ]);
-        let language = Arc::new(
-            Language::new(
-                LanguageConfig {
-                    name: "Test".into(),
-                    path_suffixes: vec![".test".to_string()],
-                    ..Default::default()
-                },
-                Some(tree_sitter_rust::language()),
-            )
-            .with_highlights_query(
-                r#"
-                (mod_item name: (identifier) body: _ @mod.body)
-                (function_item name: (identifier) @fn.name)
-                "#,
-            )
-            .unwrap(),
-        );
-        language.set_theme(&theme);
-
-        cx.update(|cx| init_test(cx, |_| {}));
-
-        let buffer = cx.new_model(|cx| {
-            Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
-        });
-        cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
-        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
-
-        let font_size = px(16.0);
-
-        let map = cx.new_model(|cx| {
-            DisplayMap::new(buffer, font("Courier"), font_size, Some(px(40.0)), 1, 1, cx)
-        });
-        assert_eq!(
-            cx.update(|cx| syntax_chunks(0..5, &map, &theme, cx)),
-            [
-                ("fn \n".to_string(), None),
-                ("oute\nr".to_string(), Some(Hsla::blue())),
-                ("() \n{}\n\n".to_string(), None),
-            ]
-        );
-        assert_eq!(
-            cx.update(|cx| syntax_chunks(3..5, &map, &theme, cx)),
-            [("{}\n\n".to_string(), None)]
-        );
-
-        map.update(cx, |map, cx| {
-            map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
-        });
-        assert_eq!(
-            cx.update(|cx| syntax_chunks(1..4, &map, &theme, cx)),
-            [
-                ("out".to_string(), Some(Hsla::blue())),
-                ("⋯\n".to_string(), None),
-                ("  \nfn ".to_string(), Some(Hsla::red())),
-                ("i\n".to_string(), Some(Hsla::blue()))
-            ]
-        );
-    }
-
-    #[gpui::test]
-    async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) {
-        cx.update(|cx| init_test(cx, |_| {}));
-
-        let theme = SyntaxTheme::new_test(vec![
-            ("operator", Hsla::red().into()),
-            ("string", Hsla::green().into()),
-        ]);
-        let language = Arc::new(
-            Language::new(
-                LanguageConfig {
-                    name: "Test".into(),
-                    path_suffixes: vec![".test".to_string()],
-                    ..Default::default()
-                },
-                Some(tree_sitter_rust::language()),
-            )
-            .with_highlights_query(
-                r#"
-                ":" @operator
-                (string_literal) @string
-                "#,
-            )
-            .unwrap(),
-        );
-        language.set_theme(&theme);
-
-        let (text, highlighted_ranges) = marked_text_ranges(r#"constˇ «a»: B = "c «d»""#, false);
-
-        let buffer = cx.new_model(|cx| {
-            Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
-        });
-        cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
-
-        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
-        let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
-
-        let font_size = px(16.0);
-        let map =
-            cx.new_model(|cx| DisplayMap::new(buffer, font("Courier"), font_size, None, 1, 1, cx));
-
-        enum MyType {}
-
-        let style = HighlightStyle {
-            color: Some(Hsla::blue()),
-            ..Default::default()
-        };
-
-        map.update(cx, |map, _cx| {
-            map.highlight_text(
-                TypeId::of::<MyType>(),
-                highlighted_ranges
-                    .into_iter()
-                    .map(|range| {
-                        buffer_snapshot.anchor_before(range.start)
-                            ..buffer_snapshot.anchor_before(range.end)
-                    })
-                    .collect(),
-                style,
-            );
-        });
-
-        assert_eq!(
-            cx.update(|cx| chunks(0..10, &map, &theme, cx)),
-            [
-                ("const ".to_string(), None, None),
-                ("a".to_string(), None, Some(Hsla::blue())),
-                (":".to_string(), Some(Hsla::red()), None),
-                (" B = ".to_string(), None, None),
-                ("\"c ".to_string(), Some(Hsla::green()), None),
-                ("d".to_string(), Some(Hsla::green()), Some(Hsla::blue())),
-                ("\"".to_string(), Some(Hsla::green()), None),
-            ]
-        );
-    }
-
-    #[gpui::test]
-    fn test_clip_point(cx: &mut gpui::AppContext) {
-        init_test(cx, |_| {});
-
-        fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::AppContext) {
-            let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx);
-
-            match bias {
-                Bias::Left => {
-                    if shift_right {
-                        *markers[1].column_mut() += 1;
-                    }
-
-                    assert_eq!(unmarked_snapshot.clip_point(markers[1], bias), markers[0])
-                }
-                Bias::Right => {
-                    if shift_right {
-                        *markers[0].column_mut() += 1;
-                    }
-
-                    assert_eq!(unmarked_snapshot.clip_point(markers[0], bias), markers[1])
-                }
-            };
-        }
-
-        use Bias::{Left, Right};
-        assert("ˇˇα", false, Left, cx);
-        assert("ˇˇα", true, Left, cx);
-        assert("ˇˇα", false, Right, cx);
-        assert("ˇαˇ", true, Right, cx);
-        assert("ˇˇ✋", false, Left, cx);
-        assert("ˇˇ✋", true, Left, cx);
-        assert("ˇˇ✋", false, Right, cx);
-        assert("ˇ✋ˇ", true, Right, cx);
-        assert("ˇˇ🍐", false, Left, cx);
-        assert("ˇˇ🍐", true, Left, cx);
-        assert("ˇˇ🍐", false, Right, cx);
-        assert("ˇ🍐ˇ", true, Right, cx);
-        assert("ˇˇ\t", false, Left, cx);
-        assert("ˇˇ\t", true, Left, cx);
-        assert("ˇˇ\t", false, Right, cx);
-        assert("ˇ\tˇ", true, Right, cx);
-        assert(" ˇˇ\t", false, Left, cx);
-        assert(" ˇˇ\t", true, Left, cx);
-        assert(" ˇˇ\t", false, Right, cx);
-        assert(" ˇ\tˇ", true, Right, cx);
-        assert("   ˇˇ\t", false, Left, cx);
-        assert("   ˇˇ\t", false, Right, cx);
-    }
-
-    #[gpui::test]
-    fn test_clip_at_line_ends(cx: &mut gpui::AppContext) {
-        init_test(cx, |_| {});
-
-        fn assert(text: &str, cx: &mut gpui::AppContext) {
-            let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
-            unmarked_snapshot.clip_at_line_ends = true;
-            assert_eq!(
-                unmarked_snapshot.clip_point(markers[1], Bias::Left),
-                markers[0]
-            );
-        }
-
-        assert("ˇˇ", cx);
-        assert("ˇaˇ", cx);
-        assert("aˇbˇ", cx);
-        assert("aˇαˇ", cx);
-    }
-
-    #[gpui::test]
-    fn test_tabs_with_multibyte_chars(cx: &mut gpui::AppContext) {
-        init_test(cx, |_| {});
-
-        let text = "✅\t\tα\nβ\t\n🏀β\t\tγ";
-        let buffer = MultiBuffer::build_simple(text, cx);
-        let font_size = px(14.0);
-
-        let map = cx.new_model(|cx| {
-            DisplayMap::new(buffer.clone(), font("Helvetica"), font_size, None, 1, 1, cx)
-        });
-        let map = map.update(cx, |map, cx| map.snapshot(cx));
-        assert_eq!(map.text(), "✅       α\nβ   \n🏀β      γ");
-        assert_eq!(
-            map.text_chunks(0).collect::<String>(),
-            "✅       α\nβ   \n🏀β      γ"
-        );
-        assert_eq!(map.text_chunks(1).collect::<String>(), "β   \n🏀β      γ");
-        assert_eq!(map.text_chunks(2).collect::<String>(), "🏀β      γ");
-
-        let point = Point::new(0, "✅\t\t".len() as u32);
-        let display_point = DisplayPoint::new(0, "✅       ".len() as u32);
-        assert_eq!(point.to_display_point(&map), display_point);
-        assert_eq!(display_point.to_point(&map), point);
-
-        let point = Point::new(1, "β\t".len() as u32);
-        let display_point = DisplayPoint::new(1, "β   ".len() as u32);
-        assert_eq!(point.to_display_point(&map), display_point);
-        assert_eq!(display_point.to_point(&map), point,);
-
-        let point = Point::new(2, "🏀β\t\t".len() as u32);
-        let display_point = DisplayPoint::new(2, "🏀β      ".len() as u32);
-        assert_eq!(point.to_display_point(&map), display_point);
-        assert_eq!(display_point.to_point(&map), point,);
-
-        // Display points inside of expanded tabs
-        assert_eq!(
-            DisplayPoint::new(0, "✅      ".len() as u32).to_point(&map),
-            Point::new(0, "✅\t".len() as u32),
-        );
-        assert_eq!(
-            DisplayPoint::new(0, "✅ ".len() as u32).to_point(&map),
-            Point::new(0, "✅".len() as u32),
-        );
-
-        // Clipping display points inside of multi-byte characters
-        assert_eq!(
-            map.clip_point(DisplayPoint::new(0, "✅".len() as u32 - 1), Left),
-            DisplayPoint::new(0, 0)
-        );
-        assert_eq!(
-            map.clip_point(DisplayPoint::new(0, "✅".len() as u32 - 1), Bias::Right),
-            DisplayPoint::new(0, "✅".len() as u32)
-        );
-    }
-
-    #[gpui::test]
-    fn test_max_point(cx: &mut gpui::AppContext) {
-        init_test(cx, |_| {});
-
-        let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
-        let font_size = px(14.0);
-        let map = cx.new_model(|cx| {
-            DisplayMap::new(buffer.clone(), font("Helvetica"), font_size, None, 1, 1, cx)
-        });
-        assert_eq!(
-            map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
-            DisplayPoint::new(1, 11)
-        )
-    }
-
-    fn syntax_chunks<'a>(
-        rows: Range<u32>,
-        map: &Model<DisplayMap>,
-        theme: &'a SyntaxTheme,
-        cx: &mut AppContext,
-    ) -> Vec<(String, Option<Hsla>)> {
-        chunks(rows, map, theme, cx)
-            .into_iter()
-            .map(|(text, color, _)| (text, color))
-            .collect()
-    }
-
-    fn chunks<'a>(
-        rows: Range<u32>,
-        map: &Model<DisplayMap>,
-        theme: &'a SyntaxTheme,
-        cx: &mut AppContext,
-    ) -> Vec<(String, Option<Hsla>, Option<Hsla>)> {
-        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
-        let mut chunks: Vec<(String, Option<Hsla>, Option<Hsla>)> = Vec::new();
-        for chunk in snapshot.chunks(rows, true, None, None) {
-            let syntax_color = chunk
-                .syntax_highlight_id
-                .and_then(|id| id.style(theme)?.color);
-            let highlight_color = chunk.highlight_style.and_then(|style| style.color);
-            if let Some((last_chunk, last_syntax_color, last_highlight_color)) = chunks.last_mut() {
-                if syntax_color == *last_syntax_color && highlight_color == *last_highlight_color {
-                    last_chunk.push_str(chunk.text);
-                    continue;
-                }
-            }
-            chunks.push((chunk.text.to_string(), syntax_color, highlight_color));
-        }
-        chunks
-    }
-
-    fn init_test(cx: &mut AppContext, f: impl Fn(&mut AllLanguageSettingsContent)) {
-        let settings = SettingsStore::test(cx);
-        cx.set_global(settings);
-        language::init(cx);
-        crate::init(cx);
-        Project::init_settings(cx);
-        theme::init(LoadThemes::JustBase, cx);
-        cx.update_global::<SettingsStore, _>(|store, cx| {
-            store.update_user_settings::<AllLanguageSettings>(cx, f);
-        });
-    }
-}

crates/editor2/src/display_map/block_map.rs 🔗

@@ -1,1647 +0,0 @@
-use super::{
-    wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot},
-    Highlights,
-};
-use crate::{Anchor, Editor, EditorStyle, ExcerptId, ExcerptRange, ToPoint as _};
-use collections::{Bound, HashMap, HashSet};
-use gpui::{AnyElement, Pixels, ViewContext};
-use language::{BufferSnapshot, Chunk, Patch, Point};
-use parking_lot::Mutex;
-use std::{
-    cell::RefCell,
-    cmp::{self, Ordering},
-    fmt::Debug,
-    ops::{Deref, DerefMut, Range},
-    sync::{
-        atomic::{AtomicUsize, Ordering::SeqCst},
-        Arc,
-    },
-};
-use sum_tree::{Bias, SumTree};
-use text::Edit;
-
-const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize];
-
-pub struct BlockMap {
-    next_block_id: AtomicUsize,
-    wrap_snapshot: RefCell<WrapSnapshot>,
-    blocks: Vec<Arc<Block>>,
-    transforms: RefCell<SumTree<Transform>>,
-    buffer_header_height: u8,
-    excerpt_header_height: u8,
-}
-
-pub struct BlockMapWriter<'a>(&'a mut BlockMap);
-
-pub struct BlockSnapshot {
-    wrap_snapshot: WrapSnapshot,
-    transforms: SumTree<Transform>,
-}
-
-#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub struct BlockId(usize);
-
-#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
-pub struct BlockPoint(pub Point);
-
-#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
-struct BlockRow(u32);
-
-#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
-struct WrapRow(u32);
-
-pub type RenderBlock = Arc<dyn Fn(&mut BlockContext) -> AnyElement>;
-
-pub struct Block {
-    id: BlockId,
-    position: Anchor,
-    height: u8,
-    style: BlockStyle,
-    render: Mutex<RenderBlock>,
-    disposition: BlockDisposition,
-}
-
-#[derive(Clone)]
-pub struct BlockProperties<P>
-where
-    P: Clone,
-{
-    pub position: P,
-    pub height: u8,
-    pub style: BlockStyle,
-    pub render: Arc<dyn Fn(&mut BlockContext) -> AnyElement>,
-    pub disposition: BlockDisposition,
-}
-
-#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
-pub enum BlockStyle {
-    Fixed,
-    Flex,
-    Sticky,
-}
-
-pub struct BlockContext<'a, 'b> {
-    pub view_context: &'b mut ViewContext<'a, Editor>,
-    pub anchor_x: Pixels,
-    pub gutter_width: Pixels,
-    pub gutter_padding: Pixels,
-    pub em_width: Pixels,
-    pub line_height: Pixels,
-    pub block_id: usize,
-    pub editor_style: &'b EditorStyle,
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
-pub enum BlockDisposition {
-    Above,
-    Below,
-}
-
-#[derive(Clone, Debug)]
-struct Transform {
-    summary: TransformSummary,
-    block: Option<TransformBlock>,
-}
-
-#[allow(clippy::large_enum_variant)]
-#[derive(Clone)]
-pub enum TransformBlock {
-    Custom(Arc<Block>),
-    ExcerptHeader {
-        id: ExcerptId,
-        buffer: BufferSnapshot,
-        range: ExcerptRange<text::Anchor>,
-        height: u8,
-        starts_new_buffer: bool,
-    },
-}
-
-impl TransformBlock {
-    fn disposition(&self) -> BlockDisposition {
-        match self {
-            TransformBlock::Custom(block) => block.disposition,
-            TransformBlock::ExcerptHeader { .. } => BlockDisposition::Above,
-        }
-    }
-
-    pub fn height(&self) -> u8 {
-        match self {
-            TransformBlock::Custom(block) => block.height,
-            TransformBlock::ExcerptHeader { height, .. } => *height,
-        }
-    }
-}
-
-impl Debug for TransformBlock {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            Self::Custom(block) => f.debug_struct("Custom").field("block", block).finish(),
-            Self::ExcerptHeader { buffer, .. } => f
-                .debug_struct("ExcerptHeader")
-                .field("path", &buffer.file().map(|f| f.path()))
-                .finish(),
-        }
-    }
-}
-
-#[derive(Clone, Debug, Default)]
-struct TransformSummary {
-    input_rows: u32,
-    output_rows: u32,
-}
-
-pub struct BlockChunks<'a> {
-    transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>,
-    input_chunks: wrap_map::WrapChunks<'a>,
-    input_chunk: Chunk<'a>,
-    output_row: u32,
-    max_output_row: u32,
-}
-
-#[derive(Clone)]
-pub struct BlockBufferRows<'a> {
-    transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>,
-    input_buffer_rows: wrap_map::WrapBufferRows<'a>,
-    output_row: u32,
-    started: bool,
-}
-
-impl BlockMap {
-    pub fn new(
-        wrap_snapshot: WrapSnapshot,
-        buffer_header_height: u8,
-        excerpt_header_height: u8,
-    ) -> Self {
-        let row_count = wrap_snapshot.max_point().row() + 1;
-        let map = Self {
-            next_block_id: AtomicUsize::new(0),
-            blocks: Vec::new(),
-            transforms: RefCell::new(SumTree::from_item(Transform::isomorphic(row_count), &())),
-            wrap_snapshot: RefCell::new(wrap_snapshot.clone()),
-            buffer_header_height,
-            excerpt_header_height,
-        };
-        map.sync(
-            &wrap_snapshot,
-            Patch::new(vec![Edit {
-                old: 0..row_count,
-                new: 0..row_count,
-            }]),
-        );
-        map
-    }
-
-    pub fn read(&self, wrap_snapshot: WrapSnapshot, edits: Patch<u32>) -> BlockSnapshot {
-        self.sync(&wrap_snapshot, edits);
-        *self.wrap_snapshot.borrow_mut() = wrap_snapshot.clone();
-        BlockSnapshot {
-            wrap_snapshot,
-            transforms: self.transforms.borrow().clone(),
-        }
-    }
-
-    pub fn write(&mut self, wrap_snapshot: WrapSnapshot, edits: Patch<u32>) -> BlockMapWriter {
-        self.sync(&wrap_snapshot, edits);
-        *self.wrap_snapshot.borrow_mut() = wrap_snapshot;
-        BlockMapWriter(self)
-    }
-
-    fn sync(&self, wrap_snapshot: &WrapSnapshot, mut edits: Patch<u32>) {
-        let buffer = wrap_snapshot.buffer_snapshot();
-
-        // Handle changing the last excerpt if it is empty.
-        if buffer.trailing_excerpt_update_count()
-            != self
-                .wrap_snapshot
-                .borrow()
-                .buffer_snapshot()
-                .trailing_excerpt_update_count()
-        {
-            let max_point = wrap_snapshot.max_point();
-            let edit_start = wrap_snapshot.prev_row_boundary(max_point);
-            let edit_end = max_point.row() + 1;
-            edits = edits.compose([WrapEdit {
-                old: edit_start..edit_end,
-                new: edit_start..edit_end,
-            }]);
-        }
-
-        let edits = edits.into_inner();
-        if edits.is_empty() {
-            return;
-        }
-
-        let mut transforms = self.transforms.borrow_mut();
-        let mut new_transforms = SumTree::new();
-        let old_row_count = transforms.summary().input_rows;
-        let new_row_count = wrap_snapshot.max_point().row() + 1;
-        let mut cursor = transforms.cursor::<WrapRow>();
-        let mut last_block_ix = 0;
-        let mut blocks_in_edit = Vec::new();
-        let mut edits = edits.into_iter().peekable();
-
-        while let Some(edit) = edits.next() {
-            // Preserve any old transforms that precede this edit.
-            let old_start = WrapRow(edit.old.start);
-            let new_start = WrapRow(edit.new.start);
-            new_transforms.append(cursor.slice(&old_start, Bias::Left, &()), &());
-            if let Some(transform) = cursor.item() {
-                if transform.is_isomorphic() && old_start == cursor.end(&()) {
-                    new_transforms.push(transform.clone(), &());
-                    cursor.next(&());
-                    while let Some(transform) = cursor.item() {
-                        if transform
-                            .block
-                            .as_ref()
-                            .map_or(false, |b| b.disposition().is_below())
-                        {
-                            new_transforms.push(transform.clone(), &());
-                            cursor.next(&());
-                        } else {
-                            break;
-                        }
-                    }
-                }
-            }
-
-            // Preserve any portion of an old transform that precedes this edit.
-            let extent_before_edit = old_start.0 - cursor.start().0;
-            push_isomorphic(&mut new_transforms, extent_before_edit);
-
-            // Skip over any old transforms that intersect this edit.
-            let mut old_end = WrapRow(edit.old.end);
-            let mut new_end = WrapRow(edit.new.end);
-            cursor.seek(&old_end, Bias::Left, &());
-            cursor.next(&());
-            if old_end == *cursor.start() {
-                while let Some(transform) = cursor.item() {
-                    if transform
-                        .block
-                        .as_ref()
-                        .map_or(false, |b| b.disposition().is_below())
-                    {
-                        cursor.next(&());
-                    } else {
-                        break;
-                    }
-                }
-            }
-
-            // Combine this edit with any subsequent edits that intersect the same transform.
-            while let Some(next_edit) = edits.peek() {
-                if next_edit.old.start <= cursor.start().0 {
-                    old_end = WrapRow(next_edit.old.end);
-                    new_end = WrapRow(next_edit.new.end);
-                    cursor.seek(&old_end, Bias::Left, &());
-                    cursor.next(&());
-                    if old_end == *cursor.start() {
-                        while let Some(transform) = cursor.item() {
-                            if transform
-                                .block
-                                .as_ref()
-                                .map_or(false, |b| b.disposition().is_below())
-                            {
-                                cursor.next(&());
-                            } else {
-                                break;
-                            }
-                        }
-                    }
-                    edits.next();
-                } else {
-                    break;
-                }
-            }
-
-            // Find the blocks within this edited region.
-            let new_buffer_start =
-                wrap_snapshot.to_point(WrapPoint::new(new_start.0, 0), Bias::Left);
-            let start_bound = Bound::Included(new_buffer_start);
-            let start_block_ix = match self.blocks[last_block_ix..].binary_search_by(|probe| {
-                probe
-                    .position
-                    .to_point(buffer)
-                    .cmp(&new_buffer_start)
-                    .then(Ordering::Greater)
-            }) {
-                Ok(ix) | Err(ix) => last_block_ix + ix,
-            };
-
-            let end_bound;
-            let end_block_ix = if new_end.0 > wrap_snapshot.max_point().row() {
-                end_bound = Bound::Unbounded;
-                self.blocks.len()
-            } else {
-                let new_buffer_end =
-                    wrap_snapshot.to_point(WrapPoint::new(new_end.0, 0), Bias::Left);
-                end_bound = Bound::Excluded(new_buffer_end);
-                match self.blocks[start_block_ix..].binary_search_by(|probe| {
-                    probe
-                        .position
-                        .to_point(buffer)
-                        .cmp(&new_buffer_end)
-                        .then(Ordering::Greater)
-                }) {
-                    Ok(ix) | Err(ix) => start_block_ix + ix,
-                }
-            };
-            last_block_ix = end_block_ix;
-
-            debug_assert!(blocks_in_edit.is_empty());
-            blocks_in_edit.extend(
-                self.blocks[start_block_ix..end_block_ix]
-                    .iter()
-                    .map(|block| {
-                        let mut position = block.position.to_point(buffer);
-                        match block.disposition {
-                            BlockDisposition::Above => position.column = 0,
-                            BlockDisposition::Below => {
-                                position.column = buffer.line_len(position.row)
-                            }
-                        }
-                        let position = wrap_snapshot.make_wrap_point(position, Bias::Left);
-                        (position.row(), TransformBlock::Custom(block.clone()))
-                    }),
-            );
-            blocks_in_edit.extend(
-                buffer
-                    .excerpt_boundaries_in_range((start_bound, end_bound))
-                    .map(|excerpt_boundary| {
-                        (
-                            wrap_snapshot
-                                .make_wrap_point(Point::new(excerpt_boundary.row, 0), Bias::Left)
-                                .row(),
-                            TransformBlock::ExcerptHeader {
-                                id: excerpt_boundary.id,
-                                buffer: excerpt_boundary.buffer,
-                                range: excerpt_boundary.range,
-                                height: if excerpt_boundary.starts_new_buffer {
-                                    self.buffer_header_height
-                                } else {
-                                    self.excerpt_header_height
-                                },
-                                starts_new_buffer: excerpt_boundary.starts_new_buffer,
-                            },
-                        )
-                    }),
-            );
-
-            // Place excerpt headers above custom blocks on the same row.
-            blocks_in_edit.sort_unstable_by(|(row_a, block_a), (row_b, block_b)| {
-                row_a.cmp(row_b).then_with(|| match (block_a, block_b) {
-                    (
-                        TransformBlock::ExcerptHeader { .. },
-                        TransformBlock::ExcerptHeader { .. },
-                    ) => Ordering::Equal,
-                    (TransformBlock::ExcerptHeader { .. }, _) => Ordering::Less,
-                    (_, TransformBlock::ExcerptHeader { .. }) => Ordering::Greater,
-                    (TransformBlock::Custom(block_a), TransformBlock::Custom(block_b)) => block_a
-                        .disposition
-                        .cmp(&block_b.disposition)
-                        .then_with(|| block_a.id.cmp(&block_b.id)),
-                })
-            });
-
-            // For each of these blocks, insert a new isomorphic transform preceding the block,
-            // and then insert the block itself.
-            for (block_row, block) in blocks_in_edit.drain(..) {
-                let insertion_row = match block.disposition() {
-                    BlockDisposition::Above => block_row,
-                    BlockDisposition::Below => block_row + 1,
-                };
-                let extent_before_block = insertion_row - new_transforms.summary().input_rows;
-                push_isomorphic(&mut new_transforms, extent_before_block);
-                new_transforms.push(Transform::block(block), &());
-            }
-
-            old_end = WrapRow(old_end.0.min(old_row_count));
-            new_end = WrapRow(new_end.0.min(new_row_count));
-
-            // Insert an isomorphic transform after the final block.
-            let extent_after_last_block = new_end.0 - new_transforms.summary().input_rows;
-            push_isomorphic(&mut new_transforms, extent_after_last_block);
-
-            // Preserve any portion of the old transform after this edit.
-            let extent_after_edit = cursor.start().0 - old_end.0;
-            push_isomorphic(&mut new_transforms, extent_after_edit);
-        }
-
-        new_transforms.append(cursor.suffix(&()), &());
-        debug_assert_eq!(
-            new_transforms.summary().input_rows,
-            wrap_snapshot.max_point().row() + 1
-        );
-
-        drop(cursor);
-        *transforms = new_transforms;
-    }
-
-    pub fn replace(&mut self, mut renderers: HashMap<BlockId, RenderBlock>) {
-        for block in &self.blocks {
-            if let Some(render) = renderers.remove(&block.id) {
-                *block.render.lock() = render;
-            }
-        }
-    }
-}
-
-fn push_isomorphic(tree: &mut SumTree<Transform>, rows: u32) {
-    if rows == 0 {
-        return;
-    }
-
-    let mut extent = Some(rows);
-    tree.update_last(
-        |last_transform| {
-            if last_transform.is_isomorphic() {
-                let extent = extent.take().unwrap();
-                last_transform.summary.input_rows += extent;
-                last_transform.summary.output_rows += extent;
-            }
-        },
-        &(),
-    );
-    if let Some(extent) = extent {
-        tree.push(Transform::isomorphic(extent), &());
-    }
-}
-
-impl BlockPoint {
-    pub fn new(row: u32, column: u32) -> Self {
-        Self(Point::new(row, column))
-    }
-}
-
-impl Deref for BlockPoint {
-    type Target = Point;
-
-    fn deref(&self) -> &Self::Target {
-        &self.0
-    }
-}
-
-impl std::ops::DerefMut for BlockPoint {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.0
-    }
-}
-
-impl<'a> BlockMapWriter<'a> {
-    pub fn insert(
-        &mut self,
-        blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
-    ) -> Vec<BlockId> {
-        let mut ids = Vec::new();
-        let mut edits = Patch::default();
-        let wrap_snapshot = &*self.0.wrap_snapshot.borrow();
-        let buffer = wrap_snapshot.buffer_snapshot();
-
-        for block in blocks {
-            let id = BlockId(self.0.next_block_id.fetch_add(1, SeqCst));
-            ids.push(id);
-
-            let position = block.position;
-            let point = position.to_point(buffer);
-            let wrap_row = wrap_snapshot
-                .make_wrap_point(Point::new(point.row, 0), Bias::Left)
-                .row();
-            let start_row = wrap_snapshot.prev_row_boundary(WrapPoint::new(wrap_row, 0));
-            let end_row = wrap_snapshot
-                .next_row_boundary(WrapPoint::new(wrap_row, 0))
-                .unwrap_or(wrap_snapshot.max_point().row() + 1);
-
-            let block_ix = match self
-                .0
-                .blocks
-                .binary_search_by(|probe| probe.position.cmp(&position, buffer))
-            {
-                Ok(ix) | Err(ix) => ix,
-            };
-            self.0.blocks.insert(
-                block_ix,
-                Arc::new(Block {
-                    id,
-                    position,
-                    height: block.height,
-                    render: Mutex::new(block.render),
-                    disposition: block.disposition,
-                    style: block.style,
-                }),
-            );
-
-            edits = edits.compose([Edit {
-                old: start_row..end_row,
-                new: start_row..end_row,
-            }]);
-        }
-
-        self.0.sync(wrap_snapshot, edits);
-        ids
-    }
-
-    pub fn remove(&mut self, block_ids: HashSet<BlockId>) {
-        let wrap_snapshot = &*self.0.wrap_snapshot.borrow();
-        let buffer = wrap_snapshot.buffer_snapshot();
-        let mut edits = Patch::default();
-        let mut last_block_buffer_row = None;
-        self.0.blocks.retain(|block| {
-            if block_ids.contains(&block.id) {
-                let buffer_row = block.position.to_point(buffer).row;
-                if last_block_buffer_row != Some(buffer_row) {
-                    last_block_buffer_row = Some(buffer_row);
-                    let wrap_row = wrap_snapshot
-                        .make_wrap_point(Point::new(buffer_row, 0), Bias::Left)
-                        .row();
-                    let start_row = wrap_snapshot.prev_row_boundary(WrapPoint::new(wrap_row, 0));
-                    let end_row = wrap_snapshot
-                        .next_row_boundary(WrapPoint::new(wrap_row, 0))
-                        .unwrap_or(wrap_snapshot.max_point().row() + 1);
-                    edits.push(Edit {
-                        old: start_row..end_row,
-                        new: start_row..end_row,
-                    })
-                }
-                false
-            } else {
-                true
-            }
-        });
-        self.0.sync(wrap_snapshot, edits);
-    }
-}
-
-impl BlockSnapshot {
-    #[cfg(test)]
-    pub fn text(&self) -> String {
-        self.chunks(
-            0..self.transforms.summary().output_rows,
-            false,
-            Highlights::default(),
-        )
-        .map(|chunk| chunk.text)
-        .collect()
-    }
-
-    pub fn chunks<'a>(
-        &'a self,
-        rows: Range<u32>,
-        language_aware: bool,
-        highlights: Highlights<'a>,
-    ) -> BlockChunks<'a> {
-        let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
-        let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
-        let input_end = {
-            cursor.seek(&BlockRow(rows.end), Bias::Right, &());
-            let overshoot = if cursor
-                .item()
-                .map_or(false, |transform| transform.is_isomorphic())
-            {
-                rows.end - cursor.start().0 .0
-            } else {
-                0
-            };
-            cursor.start().1 .0 + overshoot
-        };
-        let input_start = {
-            cursor.seek(&BlockRow(rows.start), Bias::Right, &());
-            let overshoot = if cursor
-                .item()
-                .map_or(false, |transform| transform.is_isomorphic())
-            {
-                rows.start - cursor.start().0 .0
-            } else {
-                0
-            };
-            cursor.start().1 .0 + overshoot
-        };
-        BlockChunks {
-            input_chunks: self.wrap_snapshot.chunks(
-                input_start..input_end,
-                language_aware,
-                highlights,
-            ),
-            input_chunk: Default::default(),
-            transforms: cursor,
-            output_row: rows.start,
-            max_output_row,
-        }
-    }
-
-    pub fn buffer_rows(&self, start_row: u32) -> BlockBufferRows {
-        let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
-        cursor.seek(&BlockRow(start_row), Bias::Right, &());
-        let (output_start, input_start) = cursor.start();
-        let overshoot = if cursor.item().map_or(false, |t| t.is_isomorphic()) {
-            start_row - output_start.0
-        } else {
-            0
-        };
-        let input_start_row = input_start.0 + overshoot;
-        BlockBufferRows {
-            transforms: cursor,
-            input_buffer_rows: self.wrap_snapshot.buffer_rows(input_start_row),
-            output_row: start_row,
-            started: false,
-        }
-    }
-
-    pub fn blocks_in_range(
-        &self,
-        rows: Range<u32>,
-    ) -> impl Iterator<Item = (u32, &TransformBlock)> {
-        let mut cursor = self.transforms.cursor::<BlockRow>();
-        cursor.seek(&BlockRow(rows.start), Bias::Right, &());
-        std::iter::from_fn(move || {
-            while let Some(transform) = cursor.item() {
-                let start_row = cursor.start().0;
-                if start_row >= rows.end {
-                    break;
-                }
-                if let Some(block) = &transform.block {
-                    cursor.next(&());
-                    return Some((start_row, block));
-                } else {
-                    cursor.next(&());
-                }
-            }
-            None
-        })
-    }
-
-    pub fn max_point(&self) -> BlockPoint {
-        let row = self.transforms.summary().output_rows - 1;
-        BlockPoint::new(row, self.line_len(row))
-    }
-
-    pub fn longest_row(&self) -> u32 {
-        let input_row = self.wrap_snapshot.longest_row();
-        self.to_block_point(WrapPoint::new(input_row, 0)).row
-    }
-
-    pub fn line_len(&self, row: u32) -> u32 {
-        let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
-        cursor.seek(&BlockRow(row), Bias::Right, &());
-        if let Some(transform) = cursor.item() {
-            let (output_start, input_start) = cursor.start();
-            let overshoot = row - output_start.0;
-            if transform.block.is_some() {
-                0
-            } else {
-                self.wrap_snapshot.line_len(input_start.0 + overshoot)
-            }
-        } else {
-            panic!("row out of range");
-        }
-    }
-
-    pub fn is_block_line(&self, row: u32) -> bool {
-        let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
-        cursor.seek(&BlockRow(row), Bias::Right, &());
-        cursor.item().map_or(false, |t| t.block.is_some())
-    }
-
-    pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
-        let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
-        cursor.seek(&BlockRow(point.row), Bias::Right, &());
-
-        let max_input_row = WrapRow(self.transforms.summary().input_rows);
-        let mut search_left =
-            (bias == Bias::Left && cursor.start().1 .0 > 0) || cursor.end(&()).1 == max_input_row;
-        let mut reversed = false;
-
-        loop {
-            if let Some(transform) = cursor.item() {
-                if transform.is_isomorphic() {
-                    let (output_start_row, input_start_row) = cursor.start();
-                    let (output_end_row, input_end_row) = cursor.end(&());
-                    let output_start = Point::new(output_start_row.0, 0);
-                    let input_start = Point::new(input_start_row.0, 0);
-                    let input_end = Point::new(input_end_row.0, 0);
-                    let input_point = if point.row >= output_end_row.0 {
-                        let line_len = self.wrap_snapshot.line_len(input_end_row.0 - 1);
-                        self.wrap_snapshot
-                            .clip_point(WrapPoint::new(input_end_row.0 - 1, line_len), bias)
-                    } else {
-                        let output_overshoot = point.0.saturating_sub(output_start);
-                        self.wrap_snapshot
-                            .clip_point(WrapPoint(input_start + output_overshoot), bias)
-                    };
-
-                    if (input_start..input_end).contains(&input_point.0) {
-                        let input_overshoot = input_point.0.saturating_sub(input_start);
-                        return BlockPoint(output_start + input_overshoot);
-                    }
-                }
-
-                if search_left {
-                    cursor.prev(&());
-                } else {
-                    cursor.next(&());
-                }
-            } else if reversed {
-                return self.max_point();
-            } else {
-                reversed = true;
-                search_left = !search_left;
-                cursor.seek(&BlockRow(point.row), Bias::Right, &());
-            }
-        }
-    }
-
-    pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
-        let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
-        cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &());
-        if let Some(transform) = cursor.item() {
-            debug_assert!(transform.is_isomorphic());
-        } else {
-            return self.max_point();
-        }
-
-        let (input_start_row, output_start_row) = cursor.start();
-        let input_start = Point::new(input_start_row.0, 0);
-        let output_start = Point::new(output_start_row.0, 0);
-        let input_overshoot = wrap_point.0 - input_start;
-        BlockPoint(output_start + input_overshoot)
-    }
-
-    pub fn to_wrap_point(&self, block_point: BlockPoint) -> WrapPoint {
-        let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
-        cursor.seek(&BlockRow(block_point.row), Bias::Right, &());
-        if let Some(transform) = cursor.item() {
-            match transform.block.as_ref().map(|b| b.disposition()) {
-                Some(BlockDisposition::Above) => WrapPoint::new(cursor.start().1 .0, 0),
-                Some(BlockDisposition::Below) => {
-                    let wrap_row = cursor.start().1 .0 - 1;
-                    WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
-                }
-                None => {
-                    let overshoot = block_point.row - cursor.start().0 .0;
-                    let wrap_row = cursor.start().1 .0 + overshoot;
-                    WrapPoint::new(wrap_row, block_point.column)
-                }
-            }
-        } else {
-            self.wrap_snapshot.max_point()
-        }
-    }
-}
-
-impl Transform {
-    fn isomorphic(rows: u32) -> Self {
-        Self {
-            summary: TransformSummary {
-                input_rows: rows,
-                output_rows: rows,
-            },
-            block: None,
-        }
-    }
-
-    fn block(block: TransformBlock) -> Self {
-        Self {
-            summary: TransformSummary {
-                input_rows: 0,
-                output_rows: block.height() as u32,
-            },
-            block: Some(block),
-        }
-    }
-
-    fn is_isomorphic(&self) -> bool {
-        self.block.is_none()
-    }
-}
-
-impl<'a> Iterator for BlockChunks<'a> {
-    type Item = Chunk<'a>;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if self.output_row >= self.max_output_row {
-            return None;
-        }
-
-        let transform = self.transforms.item()?;
-        if transform.block.is_some() {
-            let block_start = self.transforms.start().0 .0;
-            let mut block_end = self.transforms.end(&()).0 .0;
-            self.transforms.next(&());
-            if self.transforms.item().is_none() {
-                block_end -= 1;
-            }
-
-            let start_in_block = self.output_row - block_start;
-            let end_in_block = cmp::min(self.max_output_row, block_end) - block_start;
-            let line_count = end_in_block - start_in_block;
-            self.output_row += line_count;
-
-            return Some(Chunk {
-                text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..line_count as usize]) },
-                ..Default::default()
-            });
-        }
-
-        if self.input_chunk.text.is_empty() {
-            if let Some(input_chunk) = self.input_chunks.next() {
-                self.input_chunk = input_chunk;
-            } else {
-                self.output_row += 1;
-                if self.output_row < self.max_output_row {
-                    self.transforms.next(&());
-                    return Some(Chunk {
-                        text: "\n",
-                        ..Default::default()
-                    });
-                } else {
-                    return None;
-                }
-            }
-        }
-
-        let transform_end = self.transforms.end(&()).0 .0;
-        let (prefix_rows, prefix_bytes) =
-            offset_for_row(self.input_chunk.text, transform_end - self.output_row);
-        self.output_row += prefix_rows;
-        let (prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes);
-        self.input_chunk.text = suffix;
-        if self.output_row == transform_end {
-            self.transforms.next(&());
-        }
-
-        Some(Chunk {
-            text: prefix,
-            ..self.input_chunk
-        })
-    }
-}
-
-impl<'a> Iterator for BlockBufferRows<'a> {
-    type Item = Option<u32>;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if self.started {
-            self.output_row += 1;
-        } else {
-            self.started = true;
-        }
-
-        if self.output_row >= self.transforms.end(&()).0 .0 {
-            self.transforms.next(&());
-        }
-
-        let transform = self.transforms.item()?;
-        if transform.block.is_some() {
-            Some(None)
-        } else {
-            Some(self.input_buffer_rows.next().unwrap())
-        }
-    }
-}
-
-impl sum_tree::Item for Transform {
-    type Summary = TransformSummary;
-
-    fn summary(&self) -> Self::Summary {
-        self.summary.clone()
-    }
-}
-
-impl sum_tree::Summary for TransformSummary {
-    type Context = ();
-
-    fn add_summary(&mut self, summary: &Self, _: &()) {
-        self.input_rows += summary.input_rows;
-        self.output_rows += summary.output_rows;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow {
-    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
-        self.0 += summary.input_rows;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
-    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
-        self.0 += summary.output_rows;
-    }
-}
-
-impl BlockDisposition {
-    fn is_below(&self) -> bool {
-        matches!(self, BlockDisposition::Below)
-    }
-}
-
-impl<'a> Deref for BlockContext<'a, '_> {
-    type Target = ViewContext<'a, Editor>;
-
-    fn deref(&self) -> &Self::Target {
-        self.view_context
-    }
-}
-
-impl DerefMut for BlockContext<'_, '_> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        self.view_context
-    }
-}
-
-impl Block {
-    pub fn render(&self, cx: &mut BlockContext) -> AnyElement {
-        self.render.lock()(cx)
-    }
-
-    pub fn position(&self) -> &Anchor {
-        &self.position
-    }
-
-    pub fn style(&self) -> BlockStyle {
-        self.style
-    }
-}
-
-impl Debug for Block {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("Block")
-            .field("id", &self.id)
-            .field("position", &self.position)
-            .field("disposition", &self.disposition)
-            .finish()
-    }
-}
-
-// Count the number of bytes prior to a target point. If the string doesn't contain the target
-// point, return its total extent. Otherwise return the target point itself.
-fn offset_for_row(s: &str, target: u32) -> (u32, usize) {
-    let mut row = 0;
-    let mut offset = 0;
-    for (ix, line) in s.split('\n').enumerate() {
-        if ix > 0 {
-            row += 1;
-            offset += 1;
-        }
-        if row >= target {
-            break;
-        }
-        offset += line.len() as usize;
-    }
-    (row, offset)
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::display_map::inlay_map::InlayMap;
-    use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap};
-    use gpui::{div, font, px, Element};
-    use multi_buffer::MultiBuffer;
-    use rand::prelude::*;
-    use settings::SettingsStore;
-    use std::env;
-    use util::RandomCharIter;
-
-    #[gpui::test]
-    fn test_offset_for_row() {
-        assert_eq!(offset_for_row("", 0), (0, 0));
-        assert_eq!(offset_for_row("", 1), (0, 0));
-        assert_eq!(offset_for_row("abcd", 0), (0, 0));
-        assert_eq!(offset_for_row("abcd", 1), (0, 4));
-        assert_eq!(offset_for_row("\n", 0), (0, 0));
-        assert_eq!(offset_for_row("\n", 1), (1, 1));
-        assert_eq!(offset_for_row("abc\ndef\nghi", 0), (0, 0));
-        assert_eq!(offset_for_row("abc\ndef\nghi", 1), (1, 4));
-        assert_eq!(offset_for_row("abc\ndef\nghi", 2), (2, 8));
-        assert_eq!(offset_for_row("abc\ndef\nghi", 3), (2, 11));
-    }
-
-    #[gpui::test]
-    fn test_basic_blocks(cx: &mut gpui::TestAppContext) {
-        cx.update(|cx| init_test(cx));
-
-        let text = "aaa\nbbb\nccc\nddd";
-
-        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
-        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
-        let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
-        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
-        let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
-        let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
-        let (wrap_map, wraps_snapshot) =
-            cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
-        let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
-
-        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
-        let block_ids = writer.insert(vec![
-            BlockProperties {
-                style: BlockStyle::Fixed,
-                position: buffer_snapshot.anchor_after(Point::new(1, 0)),
-                height: 1,
-                disposition: BlockDisposition::Above,
-                render: Arc::new(|_| div().into_any()),
-            },
-            BlockProperties {
-                style: BlockStyle::Fixed,
-                position: buffer_snapshot.anchor_after(Point::new(1, 2)),
-                height: 2,
-                disposition: BlockDisposition::Above,
-                render: Arc::new(|_| div().into_any()),
-            },
-            BlockProperties {
-                style: BlockStyle::Fixed,
-                position: buffer_snapshot.anchor_after(Point::new(3, 3)),
-                height: 3,
-                disposition: BlockDisposition::Below,
-                render: Arc::new(|_| div().into_any()),
-            },
-        ]);
-
-        let snapshot = block_map.read(wraps_snapshot, Default::default());
-        assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
-
-        let blocks = snapshot
-            .blocks_in_range(0..8)
-            .map(|(start_row, block)| {
-                let block = block.as_custom().unwrap();
-                (start_row..start_row + block.height as u32, block.id)
-            })
-            .collect::<Vec<_>>();
-
-        // When multiple blocks are on the same line, the newer blocks appear first.
-        assert_eq!(
-            blocks,
-            &[
-                (1..2, block_ids[0]),
-                (2..4, block_ids[1]),
-                (7..10, block_ids[2]),
-            ]
-        );
-
-        assert_eq!(
-            snapshot.to_block_point(WrapPoint::new(0, 3)),
-            BlockPoint::new(0, 3)
-        );
-        assert_eq!(
-            snapshot.to_block_point(WrapPoint::new(1, 0)),
-            BlockPoint::new(4, 0)
-        );
-        assert_eq!(
-            snapshot.to_block_point(WrapPoint::new(3, 3)),
-            BlockPoint::new(6, 3)
-        );
-
-        assert_eq!(
-            snapshot.to_wrap_point(BlockPoint::new(0, 3)),
-            WrapPoint::new(0, 3)
-        );
-        assert_eq!(
-            snapshot.to_wrap_point(BlockPoint::new(1, 0)),
-            WrapPoint::new(1, 0)
-        );
-        assert_eq!(
-            snapshot.to_wrap_point(BlockPoint::new(3, 0)),
-            WrapPoint::new(1, 0)
-        );
-        assert_eq!(
-            snapshot.to_wrap_point(BlockPoint::new(7, 0)),
-            WrapPoint::new(3, 3)
-        );
-
-        assert_eq!(
-            snapshot.clip_point(BlockPoint::new(1, 0), Bias::Left),
-            BlockPoint::new(0, 3)
-        );
-        assert_eq!(
-            snapshot.clip_point(BlockPoint::new(1, 0), Bias::Right),
-            BlockPoint::new(4, 0)
-        );
-        assert_eq!(
-            snapshot.clip_point(BlockPoint::new(1, 1), Bias::Left),
-            BlockPoint::new(0, 3)
-        );
-        assert_eq!(
-            snapshot.clip_point(BlockPoint::new(1, 1), Bias::Right),
-            BlockPoint::new(4, 0)
-        );
-        assert_eq!(
-            snapshot.clip_point(BlockPoint::new(4, 0), Bias::Left),
-            BlockPoint::new(4, 0)
-        );
-        assert_eq!(
-            snapshot.clip_point(BlockPoint::new(4, 0), Bias::Right),
-            BlockPoint::new(4, 0)
-        );
-        assert_eq!(
-            snapshot.clip_point(BlockPoint::new(6, 3), Bias::Left),
-            BlockPoint::new(6, 3)
-        );
-        assert_eq!(
-            snapshot.clip_point(BlockPoint::new(6, 3), Bias::Right),
-            BlockPoint::new(6, 3)
-        );
-        assert_eq!(
-            snapshot.clip_point(BlockPoint::new(7, 0), Bias::Left),
-            BlockPoint::new(6, 3)
-        );
-        assert_eq!(
-            snapshot.clip_point(BlockPoint::new(7, 0), Bias::Right),
-            BlockPoint::new(6, 3)
-        );
-
-        assert_eq!(
-            snapshot.buffer_rows(0).collect::<Vec<_>>(),
-            &[
-                Some(0),
-                None,
-                None,
-                None,
-                Some(1),
-                Some(2),
-                Some(3),
-                None,
-                None,
-                None
-            ]
-        );
-
-        // Insert a line break, separating two block decorations into separate lines.
-        let buffer_snapshot = buffer.update(cx, |buffer, cx| {
-            buffer.edit([(Point::new(1, 1)..Point::new(1, 1), "!!!\n")], None, cx);
-            buffer.snapshot(cx)
-        });
-
-        let (inlay_snapshot, inlay_edits) =
-            inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
-        let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
-        let (tab_snapshot, tab_edits) =
-            tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap());
-        let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
-            wrap_map.sync(tab_snapshot, tab_edits, cx)
-        });
-        let snapshot = block_map.read(wraps_snapshot, wrap_edits);
-        assert_eq!(snapshot.text(), "aaa\n\nb!!!\n\n\nbb\nccc\nddd\n\n\n");
-    }
-
-    #[gpui::test]
-    fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) {
-        cx.update(|cx| init_test(cx));
-
-        let _font_id = cx.text_system().font_id(&font("Helvetica")).unwrap();
-
-        let text = "one two three\nfour five six\nseven eight";
-
-        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
-        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
-        let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
-        let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
-        let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
-        let (_, wraps_snapshot) = cx.update(|cx| {
-            WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(60.)), cx)
-        });
-        let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
-
-        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
-        writer.insert(vec![
-            BlockProperties {
-                style: BlockStyle::Fixed,
-                position: buffer_snapshot.anchor_after(Point::new(1, 12)),
-                disposition: BlockDisposition::Above,
-                render: Arc::new(|_| div().into_any()),
-                height: 1,
-            },
-            BlockProperties {
-                style: BlockStyle::Fixed,
-                position: buffer_snapshot.anchor_after(Point::new(1, 1)),
-                disposition: BlockDisposition::Below,
-                render: Arc::new(|_| div().into_any()),
-                height: 1,
-            },
-        ]);
-
-        // Blocks with an 'above' disposition go above their corresponding buffer line.
-        // Blocks with a 'below' disposition go below their corresponding buffer line.
-        let snapshot = block_map.read(wraps_snapshot, Default::default());
-        assert_eq!(
-            snapshot.text(),
-            "one two \nthree\n\nfour five \nsix\n\nseven \neight"
-        );
-    }
-
-    #[gpui::test(iterations = 100)]
-    fn test_random_blocks(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
-        cx.update(|cx| init_test(cx));
-
-        let operations = env::var("OPERATIONS")
-            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
-            .unwrap_or(10);
-
-        let wrap_width = if rng.gen_bool(0.2) {
-            None
-        } else {
-            Some(px(rng.gen_range(0.0..=100.0)))
-        };
-        let tab_size = 1.try_into().unwrap();
-        let font_size = px(14.0);
-        let buffer_start_header_height = rng.gen_range(1..=5);
-        let excerpt_header_height = rng.gen_range(1..=5);
-
-        log::info!("Wrap width: {:?}", wrap_width);
-        log::info!("Excerpt Header Height: {:?}", excerpt_header_height);
-
-        let buffer = if rng.gen() {
-            let len = rng.gen_range(0..10);
-            let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
-            log::info!("initial buffer text: {:?}", text);
-            cx.update(|cx| MultiBuffer::build_simple(&text, cx))
-        } else {
-            cx.update(|cx| MultiBuffer::build_random(&mut rng, cx))
-        };
-
-        let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
-        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
-        let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
-        let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
-        let (wrap_map, wraps_snapshot) = cx
-            .update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), font_size, wrap_width, cx));
-        let mut block_map = BlockMap::new(
-            wraps_snapshot,
-            buffer_start_header_height,
-            excerpt_header_height,
-        );
-        let mut custom_blocks = Vec::new();
-
-        for _ in 0..operations {
-            let mut buffer_edits = Vec::new();
-            match rng.gen_range(0..=100) {
-                0..=19 => {
-                    let wrap_width = if rng.gen_bool(0.2) {
-                        None
-                    } else {
-                        Some(px(rng.gen_range(0.0..=100.0)))
-                    };
-                    log::info!("Setting wrap width to {:?}", wrap_width);
-                    wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
-                }
-                20..=39 => {
-                    let block_count = rng.gen_range(1..=5);
-                    let block_properties = (0..block_count)
-                        .map(|_| {
-                            let buffer = cx.update(|cx| buffer.read(cx).read(cx).clone());
-                            let position = buffer.anchor_after(
-                                buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Left),
-                            );
-
-                            let disposition = if rng.gen() {
-                                BlockDisposition::Above
-                            } else {
-                                BlockDisposition::Below
-                            };
-                            let height = rng.gen_range(1..5);
-                            log::info!(
-                                "inserting block {:?} {:?} with height {}",
-                                disposition,
-                                position.to_point(&buffer),
-                                height
-                            );
-                            BlockProperties {
-                                style: BlockStyle::Fixed,
-                                position,
-                                height,
-                                disposition,
-                                render: Arc::new(|_| div().into_any()),
-                            }
-                        })
-                        .collect::<Vec<_>>();
-
-                    let (inlay_snapshot, inlay_edits) =
-                        inlay_map.sync(buffer_snapshot.clone(), vec![]);
-                    let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
-                    let (tab_snapshot, tab_edits) =
-                        tab_map.sync(fold_snapshot, fold_edits, tab_size);
-                    let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
-                        wrap_map.sync(tab_snapshot, tab_edits, cx)
-                    });
-                    let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
-                    let block_ids = block_map.insert(block_properties.clone());
-                    for (block_id, props) in block_ids.into_iter().zip(block_properties) {
-                        custom_blocks.push((block_id, props));
-                    }
-                }
-                40..=59 if !custom_blocks.is_empty() => {
-                    let block_count = rng.gen_range(1..=4.min(custom_blocks.len()));
-                    let block_ids_to_remove = (0..block_count)
-                        .map(|_| {
-                            custom_blocks
-                                .remove(rng.gen_range(0..custom_blocks.len()))
-                                .0
-                        })
-                        .collect();
-
-                    let (inlay_snapshot, inlay_edits) =
-                        inlay_map.sync(buffer_snapshot.clone(), vec![]);
-                    let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
-                    let (tab_snapshot, tab_edits) =
-                        tab_map.sync(fold_snapshot, fold_edits, tab_size);
-                    let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
-                        wrap_map.sync(tab_snapshot, tab_edits, cx)
-                    });
-                    let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
-                    block_map.remove(block_ids_to_remove);
-                }
-                _ => {
-                    buffer.update(cx, |buffer, cx| {
-                        let mutation_count = rng.gen_range(1..=5);
-                        let subscription = buffer.subscribe();
-                        buffer.randomly_mutate(&mut rng, mutation_count, cx);
-                        buffer_snapshot = buffer.snapshot(cx);
-                        buffer_edits.extend(subscription.consume());
-                        log::info!("buffer text: {:?}", buffer_snapshot.text());
-                    });
-                }
-            }
-
-            let (inlay_snapshot, inlay_edits) =
-                inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
-            let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
-            let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
-            let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
-                wrap_map.sync(tab_snapshot, tab_edits, cx)
-            });
-            let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
-            assert_eq!(
-                blocks_snapshot.transforms.summary().input_rows,
-                wraps_snapshot.max_point().row() + 1
-            );
-            log::info!("blocks text: {:?}", blocks_snapshot.text());
-
-            let mut expected_blocks = Vec::new();
-            expected_blocks.extend(custom_blocks.iter().map(|(id, block)| {
-                let mut position = block.position.to_point(&buffer_snapshot);
-                match block.disposition {
-                    BlockDisposition::Above => {
-                        position.column = 0;
-                    }
-                    BlockDisposition::Below => {
-                        position.column = buffer_snapshot.line_len(position.row);
-                    }
-                };
-                let row = wraps_snapshot.make_wrap_point(position, Bias::Left).row();
-                (
-                    row,
-                    ExpectedBlock::Custom {
-                        disposition: block.disposition,
-                        id: *id,
-                        height: block.height,
-                    },
-                )
-            }));
-            expected_blocks.extend(buffer_snapshot.excerpt_boundaries_in_range(0..).map(
-                |boundary| {
-                    let position =
-                        wraps_snapshot.make_wrap_point(Point::new(boundary.row, 0), Bias::Left);
-                    (
-                        position.row(),
-                        ExpectedBlock::ExcerptHeader {
-                            height: if boundary.starts_new_buffer {
-                                buffer_start_header_height
-                            } else {
-                                excerpt_header_height
-                            },
-                            starts_new_buffer: boundary.starts_new_buffer,
-                        },
-                    )
-                },
-            ));
-            expected_blocks.sort_unstable();
-            let mut sorted_blocks_iter = expected_blocks.into_iter().peekable();
-
-            let input_buffer_rows = buffer_snapshot.buffer_rows(0).collect::<Vec<_>>();
-            let mut expected_buffer_rows = Vec::new();
-            let mut expected_text = String::new();
-            let mut expected_block_positions = Vec::new();
-            let input_text = wraps_snapshot.text();
-            for (row, input_line) in input_text.split('\n').enumerate() {
-                let row = row as u32;
-                if row > 0 {
-                    expected_text.push('\n');
-                }
-
-                let buffer_row = input_buffer_rows[wraps_snapshot
-                    .to_point(WrapPoint::new(row, 0), Bias::Left)
-                    .row as usize];
-
-                while let Some((block_row, block)) = sorted_blocks_iter.peek() {
-                    if *block_row == row && block.disposition() == BlockDisposition::Above {
-                        let (_, block) = sorted_blocks_iter.next().unwrap();
-                        let height = block.height() as usize;
-                        expected_block_positions
-                            .push((expected_text.matches('\n').count() as u32, block));
-                        let text = "\n".repeat(height);
-                        expected_text.push_str(&text);
-                        for _ in 0..height {
-                            expected_buffer_rows.push(None);
-                        }
-                    } else {
-                        break;
-                    }
-                }
-
-                let soft_wrapped = wraps_snapshot.to_tab_point(WrapPoint::new(row, 0)).column() > 0;
-                expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row });
-                expected_text.push_str(input_line);
-
-                while let Some((block_row, block)) = sorted_blocks_iter.peek() {
-                    if *block_row == row && block.disposition() == BlockDisposition::Below {
-                        let (_, block) = sorted_blocks_iter.next().unwrap();
-                        let height = block.height() as usize;
-                        expected_block_positions
-                            .push((expected_text.matches('\n').count() as u32 + 1, block));
-                        let text = "\n".repeat(height);
-                        expected_text.push_str(&text);
-                        for _ in 0..height {
-                            expected_buffer_rows.push(None);
-                        }
-                    } else {
-                        break;
-                    }
-                }
-            }
-
-            let expected_lines = expected_text.split('\n').collect::<Vec<_>>();
-            let expected_row_count = expected_lines.len();
-            for start_row in 0..expected_row_count {
-                let expected_text = expected_lines[start_row..].join("\n");
-                let actual_text = blocks_snapshot
-                    .chunks(
-                        start_row as u32..blocks_snapshot.max_point().row + 1,
-                        false,
-                        Highlights::default(),
-                    )
-                    .map(|chunk| chunk.text)
-                    .collect::<String>();
-                assert_eq!(
-                    actual_text, expected_text,
-                    "incorrect text starting from row {}",
-                    start_row
-                );
-                assert_eq!(
-                    blocks_snapshot
-                        .buffer_rows(start_row as u32)
-                        .collect::<Vec<_>>(),
-                    &expected_buffer_rows[start_row..]
-                );
-            }
-
-            assert_eq!(
-                blocks_snapshot
-                    .blocks_in_range(0..(expected_row_count as u32))
-                    .map(|(row, block)| (row, block.clone().into()))
-                    .collect::<Vec<_>>(),
-                expected_block_positions
-            );
-
-            let mut expected_longest_rows = Vec::new();
-            let mut longest_line_len = -1_isize;
-            for (row, line) in expected_lines.iter().enumerate() {
-                let row = row as u32;
-
-                assert_eq!(
-                    blocks_snapshot.line_len(row),
-                    line.len() as u32,
-                    "invalid line len for row {}",
-                    row
-                );
-
-                let line_char_count = line.chars().count() as isize;
-                match line_char_count.cmp(&longest_line_len) {
-                    Ordering::Less => {}
-                    Ordering::Equal => expected_longest_rows.push(row),
-                    Ordering::Greater => {
-                        longest_line_len = line_char_count;
-                        expected_longest_rows.clear();
-                        expected_longest_rows.push(row);
-                    }
-                }
-            }
-
-            let longest_row = blocks_snapshot.longest_row();
-            assert!(
-                expected_longest_rows.contains(&longest_row),
-                "incorrect longest row {}. expected {:?} with length {}",
-                longest_row,
-                expected_longest_rows,
-                longest_line_len,
-            );
-
-            for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() {
-                let wrap_point = WrapPoint::new(row, 0);
-                let block_point = blocks_snapshot.to_block_point(wrap_point);
-                assert_eq!(blocks_snapshot.to_wrap_point(block_point), wrap_point);
-            }
-
-            let mut block_point = BlockPoint::new(0, 0);
-            for c in expected_text.chars() {
-                let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
-                let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left);
-                assert_eq!(
-                    blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(left_point)),
-                    left_point
-                );
-                assert_eq!(
-                    left_buffer_point,
-                    buffer_snapshot.clip_point(left_buffer_point, Bias::Right),
-                    "{:?} is not valid in buffer coordinates",
-                    left_point
-                );
-
-                let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
-                let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right);
-                assert_eq!(
-                    blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(right_point)),
-                    right_point
-                );
-                assert_eq!(
-                    right_buffer_point,
-                    buffer_snapshot.clip_point(right_buffer_point, Bias::Left),
-                    "{:?} is not valid in buffer coordinates",
-                    right_point
-                );
-
-                if c == '\n' {
-                    block_point.0 += Point::new(1, 0);
-                } else {
-                    block_point.column += c.len_utf8() as u32;
-                }
-            }
-        }
-
-        #[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
-        enum ExpectedBlock {
-            ExcerptHeader {
-                height: u8,
-                starts_new_buffer: bool,
-            },
-            Custom {
-                disposition: BlockDisposition,
-                id: BlockId,
-                height: u8,
-            },
-        }
-
-        impl ExpectedBlock {
-            fn height(&self) -> u8 {
-                match self {
-                    ExpectedBlock::ExcerptHeader { height, .. } => *height,
-                    ExpectedBlock::Custom { height, .. } => *height,
-                }
-            }
-
-            fn disposition(&self) -> BlockDisposition {
-                match self {
-                    ExpectedBlock::ExcerptHeader { .. } => BlockDisposition::Above,
-                    ExpectedBlock::Custom { disposition, .. } => *disposition,
-                }
-            }
-        }
-
-        impl From<TransformBlock> for ExpectedBlock {
-            fn from(block: TransformBlock) -> Self {
-                match block {
-                    TransformBlock::Custom(block) => ExpectedBlock::Custom {
-                        id: block.id,
-                        disposition: block.disposition,
-                        height: block.height,
-                    },
-                    TransformBlock::ExcerptHeader {
-                        height,
-                        starts_new_buffer,
-                        ..
-                    } => ExpectedBlock::ExcerptHeader {
-                        height,
-                        starts_new_buffer,
-                    },
-                }
-            }
-        }
-    }
-
-    fn init_test(cx: &mut gpui::AppContext) {
-        let settings = SettingsStore::test(cx);
-        cx.set_global(settings);
-        theme::init(theme::LoadThemes::JustBase, cx);
-    }
-
-    impl TransformBlock {
-        fn as_custom(&self) -> Option<&Block> {
-            match self {
-                TransformBlock::Custom(block) => Some(block),
-                TransformBlock::ExcerptHeader { .. } => None,
-            }
-        }
-    }
-
-    impl BlockSnapshot {
-        fn to_point(&self, point: BlockPoint, bias: Bias) -> Point {
-            self.wrap_snapshot.to_point(self.to_wrap_point(point), bias)
-        }
-    }
-}

crates/editor2/src/display_map/fold_map.rs 🔗

@@ -1,1746 +0,0 @@
-use super::{
-    inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot},
-    Highlights,
-};
-use crate::{Anchor, AnchorRangeExt, MultiBufferSnapshot, ToOffset};
-use gpui::{ElementId, HighlightStyle, Hsla};
-use language::{Chunk, Edit, Point, TextSummary};
-use std::{
-    any::TypeId,
-    cmp::{self, Ordering},
-    iter,
-    ops::{Add, AddAssign, Deref, DerefMut, Range, Sub},
-};
-use sum_tree::{Bias, Cursor, FilterCursor, SumTree};
-use util::post_inc;
-
-#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
-pub struct FoldPoint(pub Point);
-
-impl FoldPoint {
-    pub fn new(row: u32, column: u32) -> Self {
-        Self(Point::new(row, column))
-    }
-
-    pub fn row(self) -> u32 {
-        self.0.row
-    }
-
-    pub fn column(self) -> u32 {
-        self.0.column
-    }
-
-    pub fn row_mut(&mut self) -> &mut u32 {
-        &mut self.0.row
-    }
-
-    #[cfg(test)]
-    pub fn column_mut(&mut self) -> &mut u32 {
-        &mut self.0.column
-    }
-
-    pub fn to_inlay_point(self, snapshot: &FoldSnapshot) -> InlayPoint {
-        let mut cursor = snapshot.transforms.cursor::<(FoldPoint, InlayPoint)>();
-        cursor.seek(&self, Bias::Right, &());
-        let overshoot = self.0 - cursor.start().0 .0;
-        InlayPoint(cursor.start().1 .0 + overshoot)
-    }
-
-    pub fn to_offset(self, snapshot: &FoldSnapshot) -> FoldOffset {
-        let mut cursor = snapshot
-            .transforms
-            .cursor::<(FoldPoint, TransformSummary)>();
-        cursor.seek(&self, Bias::Right, &());
-        let overshoot = self.0 - cursor.start().1.output.lines;
-        let mut offset = cursor.start().1.output.len;
-        if !overshoot.is_zero() {
-            let transform = cursor.item().expect("display point out of range");
-            assert!(transform.output_text.is_none());
-            let end_inlay_offset = snapshot
-                .inlay_snapshot
-                .to_offset(InlayPoint(cursor.start().1.input.lines + overshoot));
-            offset += end_inlay_offset.0 - cursor.start().1.input.len;
-        }
-        FoldOffset(offset)
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldPoint {
-    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
-        self.0 += &summary.output.lines;
-    }
-}
-
-pub struct FoldMapWriter<'a>(&'a mut FoldMap);
-
-impl<'a> FoldMapWriter<'a> {
-    pub fn fold<T: ToOffset>(
-        &mut self,
-        ranges: impl IntoIterator<Item = Range<T>>,
-    ) -> (FoldSnapshot, Vec<FoldEdit>) {
-        let mut edits = Vec::new();
-        let mut folds = Vec::new();
-        let snapshot = self.0.snapshot.inlay_snapshot.clone();
-        for range in ranges.into_iter() {
-            let buffer = &snapshot.buffer;
-            let range = range.start.to_offset(&buffer)..range.end.to_offset(&buffer);
-
-            // Ignore any empty ranges.
-            if range.start == range.end {
-                continue;
-            }
-
-            // For now, ignore any ranges that span an excerpt boundary.
-            let fold_range =
-                FoldRange(buffer.anchor_after(range.start)..buffer.anchor_before(range.end));
-            if fold_range.0.start.excerpt_id != fold_range.0.end.excerpt_id {
-                continue;
-            }
-
-            folds.push(Fold {
-                id: FoldId(post_inc(&mut self.0.next_fold_id.0)),
-                range: fold_range,
-            });
-
-            let inlay_range =
-                snapshot.to_inlay_offset(range.start)..snapshot.to_inlay_offset(range.end);
-            edits.push(InlayEdit {
-                old: inlay_range.clone(),
-                new: inlay_range,
-            });
-        }
-
-        let buffer = &snapshot.buffer;
-        folds.sort_unstable_by(|a, b| sum_tree::SeekTarget::cmp(&a.range, &b.range, buffer));
-
-        self.0.snapshot.folds = {
-            let mut new_tree = SumTree::new();
-            let mut cursor = self.0.snapshot.folds.cursor::<FoldRange>();
-            for fold in folds {
-                new_tree.append(cursor.slice(&fold.range, Bias::Right, buffer), buffer);
-                new_tree.push(fold, buffer);
-            }
-            new_tree.append(cursor.suffix(buffer), buffer);
-            new_tree
-        };
-
-        consolidate_inlay_edits(&mut edits);
-        let edits = self.0.sync(snapshot.clone(), edits);
-        (self.0.snapshot.clone(), edits)
-    }
-
-    pub fn unfold<T: ToOffset>(
-        &mut self,
-        ranges: impl IntoIterator<Item = Range<T>>,
-        inclusive: bool,
-    ) -> (FoldSnapshot, Vec<FoldEdit>) {
-        let mut edits = Vec::new();
-        let mut fold_ixs_to_delete = Vec::new();
-        let snapshot = self.0.snapshot.inlay_snapshot.clone();
-        let buffer = &snapshot.buffer;
-        for range in ranges.into_iter() {
-            // Remove intersecting folds and add their ranges to edits that are passed to sync.
-            let mut folds_cursor =
-                intersecting_folds(&snapshot, &self.0.snapshot.folds, range, inclusive);
-            while let Some(fold) = folds_cursor.item() {
-                let offset_range =
-                    fold.range.start.to_offset(buffer)..fold.range.end.to_offset(buffer);
-                if offset_range.end > offset_range.start {
-                    let inlay_range = snapshot.to_inlay_offset(offset_range.start)
-                        ..snapshot.to_inlay_offset(offset_range.end);
-                    edits.push(InlayEdit {
-                        old: inlay_range.clone(),
-                        new: inlay_range,
-                    });
-                }
-                fold_ixs_to_delete.push(*folds_cursor.start());
-                folds_cursor.next(buffer);
-            }
-        }
-
-        fold_ixs_to_delete.sort_unstable();
-        fold_ixs_to_delete.dedup();
-
-        self.0.snapshot.folds = {
-            let mut cursor = self.0.snapshot.folds.cursor::<usize>();
-            let mut folds = SumTree::new();
-            for fold_ix in fold_ixs_to_delete {
-                folds.append(cursor.slice(&fold_ix, Bias::Right, buffer), buffer);
-                cursor.next(buffer);
-            }
-            folds.append(cursor.suffix(buffer), buffer);
-            folds
-        };
-
-        consolidate_inlay_edits(&mut edits);
-        let edits = self.0.sync(snapshot.clone(), edits);
-        (self.0.snapshot.clone(), edits)
-    }
-}
-
-pub struct FoldMap {
-    snapshot: FoldSnapshot,
-    ellipses_color: Option<Hsla>,
-    next_fold_id: FoldId,
-}
-
-impl FoldMap {
-    pub fn new(inlay_snapshot: InlaySnapshot) -> (Self, FoldSnapshot) {
-        let this = Self {
-            snapshot: FoldSnapshot {
-                folds: Default::default(),
-                transforms: SumTree::from_item(
-                    Transform {
-                        summary: TransformSummary {
-                            input: inlay_snapshot.text_summary(),
-                            output: inlay_snapshot.text_summary(),
-                        },
-                        output_text: None,
-                    },
-                    &(),
-                ),
-                inlay_snapshot: inlay_snapshot.clone(),
-                version: 0,
-                ellipses_color: None,
-            },
-            ellipses_color: None,
-            next_fold_id: FoldId::default(),
-        };
-        let snapshot = this.snapshot.clone();
-        (this, snapshot)
-    }
-
-    pub fn read(
-        &mut self,
-        inlay_snapshot: InlaySnapshot,
-        edits: Vec<InlayEdit>,
-    ) -> (FoldSnapshot, Vec<FoldEdit>) {
-        let edits = self.sync(inlay_snapshot, edits);
-        self.check_invariants();
-        (self.snapshot.clone(), edits)
-    }
-
-    pub fn write(
-        &mut self,
-        inlay_snapshot: InlaySnapshot,
-        edits: Vec<InlayEdit>,
-    ) -> (FoldMapWriter, FoldSnapshot, Vec<FoldEdit>) {
-        let (snapshot, edits) = self.read(inlay_snapshot, edits);
-        (FoldMapWriter(self), snapshot, edits)
-    }
-
-    pub fn set_ellipses_color(&mut self, color: Hsla) -> bool {
-        if self.ellipses_color != Some(color) {
-            self.ellipses_color = Some(color);
-            true
-        } else {
-            false
-        }
-    }
-
-    fn check_invariants(&self) {
-        if cfg!(test) {
-            assert_eq!(
-                self.snapshot.transforms.summary().input.len,
-                self.snapshot.inlay_snapshot.len().0,
-                "transform tree does not match inlay snapshot's length"
-            );
-
-            let mut folds = self.snapshot.folds.iter().peekable();
-            while let Some(fold) = folds.next() {
-                if let Some(next_fold) = folds.peek() {
-                    let comparison = fold
-                        .range
-                        .cmp(&next_fold.range, &self.snapshot.inlay_snapshot.buffer);
-                    assert!(comparison.is_le());
-                }
-            }
-        }
-    }
-
-    fn sync(
-        &mut self,
-        inlay_snapshot: InlaySnapshot,
-        inlay_edits: Vec<InlayEdit>,
-    ) -> Vec<FoldEdit> {
-        if inlay_edits.is_empty() {
-            if self.snapshot.inlay_snapshot.version != inlay_snapshot.version {
-                self.snapshot.version += 1;
-            }
-            self.snapshot.inlay_snapshot = inlay_snapshot;
-            Vec::new()
-        } else {
-            let mut inlay_edits_iter = inlay_edits.iter().cloned().peekable();
-
-            let mut new_transforms = SumTree::new();
-            let mut cursor = self.snapshot.transforms.cursor::<InlayOffset>();
-            cursor.seek(&InlayOffset(0), Bias::Right, &());
-
-            while let Some(mut edit) = inlay_edits_iter.next() {
-                new_transforms.append(cursor.slice(&edit.old.start, Bias::Left, &()), &());
-                edit.new.start -= edit.old.start - *cursor.start();
-                edit.old.start = *cursor.start();
-
-                cursor.seek(&edit.old.end, Bias::Right, &());
-                cursor.next(&());
-
-                let mut delta = edit.new_len().0 as isize - edit.old_len().0 as isize;
-                loop {
-                    edit.old.end = *cursor.start();
-
-                    if let Some(next_edit) = inlay_edits_iter.peek() {
-                        if next_edit.old.start > edit.old.end {
-                            break;
-                        }
-
-                        let next_edit = inlay_edits_iter.next().unwrap();
-                        delta += next_edit.new_len().0 as isize - next_edit.old_len().0 as isize;
-
-                        if next_edit.old.end >= edit.old.end {
-                            edit.old.end = next_edit.old.end;
-                            cursor.seek(&edit.old.end, Bias::Right, &());
-                            cursor.next(&());
-                        }
-                    } else {
-                        break;
-                    }
-                }
-
-                edit.new.end =
-                    InlayOffset(((edit.new.start + edit.old_len()).0 as isize + delta) as usize);
-
-                let anchor = inlay_snapshot
-                    .buffer
-                    .anchor_before(inlay_snapshot.to_buffer_offset(edit.new.start));
-                let mut folds_cursor = self.snapshot.folds.cursor::<FoldRange>();
-                folds_cursor.seek(
-                    &FoldRange(anchor..Anchor::max()),
-                    Bias::Left,
-                    &inlay_snapshot.buffer,
-                );
-
-                let mut folds = iter::from_fn({
-                    let inlay_snapshot = &inlay_snapshot;
-                    move || {
-                        let item = folds_cursor.item().map(|f| {
-                            let buffer_start = f.range.start.to_offset(&inlay_snapshot.buffer);
-                            let buffer_end = f.range.end.to_offset(&inlay_snapshot.buffer);
-                            inlay_snapshot.to_inlay_offset(buffer_start)
-                                ..inlay_snapshot.to_inlay_offset(buffer_end)
-                        });
-                        folds_cursor.next(&inlay_snapshot.buffer);
-                        item
-                    }
-                })
-                .peekable();
-
-                while folds.peek().map_or(false, |fold| fold.start < edit.new.end) {
-                    let mut fold = folds.next().unwrap();
-                    let sum = new_transforms.summary();
-
-                    assert!(fold.start.0 >= sum.input.len);
-
-                    while folds
-                        .peek()
-                        .map_or(false, |next_fold| next_fold.start <= fold.end)
-                    {
-                        let next_fold = folds.next().unwrap();
-                        if next_fold.end > fold.end {
-                            fold.end = next_fold.end;
-                        }
-                    }
-
-                    if fold.start.0 > sum.input.len {
-                        let text_summary = inlay_snapshot
-                            .text_summary_for_range(InlayOffset(sum.input.len)..fold.start);
-                        new_transforms.push(
-                            Transform {
-                                summary: TransformSummary {
-                                    output: text_summary.clone(),
-                                    input: text_summary,
-                                },
-                                output_text: None,
-                            },
-                            &(),
-                        );
-                    }
-
-                    if fold.end > fold.start {
-                        let output_text = "⋯";
-                        new_transforms.push(
-                            Transform {
-                                summary: TransformSummary {
-                                    output: TextSummary::from(output_text),
-                                    input: inlay_snapshot
-                                        .text_summary_for_range(fold.start..fold.end),
-                                },
-                                output_text: Some(output_text),
-                            },
-                            &(),
-                        );
-                    }
-                }
-
-                let sum = new_transforms.summary();
-                if sum.input.len < edit.new.end.0 {
-                    let text_summary = inlay_snapshot
-                        .text_summary_for_range(InlayOffset(sum.input.len)..edit.new.end);
-                    new_transforms.push(
-                        Transform {
-                            summary: TransformSummary {
-                                output: text_summary.clone(),
-                                input: text_summary,
-                            },
-                            output_text: None,
-                        },
-                        &(),
-                    );
-                }
-            }
-
-            new_transforms.append(cursor.suffix(&()), &());
-            if new_transforms.is_empty() {
-                let text_summary = inlay_snapshot.text_summary();
-                new_transforms.push(
-                    Transform {
-                        summary: TransformSummary {
-                            output: text_summary.clone(),
-                            input: text_summary,
-                        },
-                        output_text: None,
-                    },
-                    &(),
-                );
-            }
-
-            drop(cursor);
-
-            let mut fold_edits = Vec::with_capacity(inlay_edits.len());
-            {
-                let mut old_transforms = self
-                    .snapshot
-                    .transforms
-                    .cursor::<(InlayOffset, FoldOffset)>();
-                let mut new_transforms = new_transforms.cursor::<(InlayOffset, FoldOffset)>();
-
-                for mut edit in inlay_edits {
-                    old_transforms.seek(&edit.old.start, Bias::Left, &());
-                    if old_transforms.item().map_or(false, |t| t.is_fold()) {
-                        edit.old.start = old_transforms.start().0;
-                    }
-                    let old_start =
-                        old_transforms.start().1 .0 + (edit.old.start - old_transforms.start().0).0;
-
-                    old_transforms.seek_forward(&edit.old.end, Bias::Right, &());
-                    if old_transforms.item().map_or(false, |t| t.is_fold()) {
-                        old_transforms.next(&());
-                        edit.old.end = old_transforms.start().0;
-                    }
-                    let old_end =
-                        old_transforms.start().1 .0 + (edit.old.end - old_transforms.start().0).0;
-
-                    new_transforms.seek(&edit.new.start, Bias::Left, &());
-                    if new_transforms.item().map_or(false, |t| t.is_fold()) {
-                        edit.new.start = new_transforms.start().0;
-                    }
-                    let new_start =
-                        new_transforms.start().1 .0 + (edit.new.start - new_transforms.start().0).0;
-
-                    new_transforms.seek_forward(&edit.new.end, Bias::Right, &());
-                    if new_transforms.item().map_or(false, |t| t.is_fold()) {
-                        new_transforms.next(&());
-                        edit.new.end = new_transforms.start().0;
-                    }
-                    let new_end =
-                        new_transforms.start().1 .0 + (edit.new.end - new_transforms.start().0).0;
-
-                    fold_edits.push(FoldEdit {
-                        old: FoldOffset(old_start)..FoldOffset(old_end),
-                        new: FoldOffset(new_start)..FoldOffset(new_end),
-                    });
-                }
-
-                consolidate_fold_edits(&mut fold_edits);
-            }
-
-            self.snapshot.transforms = new_transforms;
-            self.snapshot.inlay_snapshot = inlay_snapshot;
-            self.snapshot.version += 1;
-            fold_edits
-        }
-    }
-}
-
-#[derive(Clone)]
-pub struct FoldSnapshot {
-    transforms: SumTree<Transform>,
-    folds: SumTree<Fold>,
-    pub inlay_snapshot: InlaySnapshot,
-    pub version: usize,
-    pub ellipses_color: Option<Hsla>,
-}
-
-impl FoldSnapshot {
-    #[cfg(test)]
-    pub fn text(&self) -> String {
-        self.chunks(FoldOffset(0)..self.len(), false, Highlights::default())
-            .map(|c| c.text)
-            .collect()
-    }
-
-    #[cfg(test)]
-    pub fn fold_count(&self) -> usize {
-        self.folds.items(&self.inlay_snapshot.buffer).len()
-    }
-
-    pub fn text_summary_for_range(&self, range: Range<FoldPoint>) -> TextSummary {
-        let mut summary = TextSummary::default();
-
-        let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>();
-        cursor.seek(&range.start, Bias::Right, &());
-        if let Some(transform) = cursor.item() {
-            let start_in_transform = range.start.0 - cursor.start().0 .0;
-            let end_in_transform = cmp::min(range.end, cursor.end(&()).0).0 - cursor.start().0 .0;
-            if let Some(output_text) = transform.output_text {
-                summary = TextSummary::from(
-                    &output_text
-                        [start_in_transform.column as usize..end_in_transform.column as usize],
-                );
-            } else {
-                let inlay_start = self
-                    .inlay_snapshot
-                    .to_offset(InlayPoint(cursor.start().1 .0 + start_in_transform));
-                let inlay_end = self
-                    .inlay_snapshot
-                    .to_offset(InlayPoint(cursor.start().1 .0 + end_in_transform));
-                summary = self
-                    .inlay_snapshot
-                    .text_summary_for_range(inlay_start..inlay_end);
-            }
-        }
-
-        if range.end > cursor.end(&()).0 {
-            cursor.next(&());
-            summary += &cursor
-                .summary::<_, TransformSummary>(&range.end, Bias::Right, &())
-                .output;
-            if let Some(transform) = cursor.item() {
-                let end_in_transform = range.end.0 - cursor.start().0 .0;
-                if let Some(output_text) = transform.output_text {
-                    summary += TextSummary::from(&output_text[..end_in_transform.column as usize]);
-                } else {
-                    let inlay_start = self.inlay_snapshot.to_offset(cursor.start().1);
-                    let inlay_end = self
-                        .inlay_snapshot
-                        .to_offset(InlayPoint(cursor.start().1 .0 + end_in_transform));
-                    summary += self
-                        .inlay_snapshot
-                        .text_summary_for_range(inlay_start..inlay_end);
-                }
-            }
-        }
-
-        summary
-    }
-
-    pub fn to_fold_point(&self, point: InlayPoint, bias: Bias) -> FoldPoint {
-        let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>();
-        cursor.seek(&point, Bias::Right, &());
-        if cursor.item().map_or(false, |t| t.is_fold()) {
-            if bias == Bias::Left || point == cursor.start().0 {
-                cursor.start().1
-            } else {
-                cursor.end(&()).1
-            }
-        } else {
-            let overshoot = point.0 - cursor.start().0 .0;
-            FoldPoint(cmp::min(
-                cursor.start().1 .0 + overshoot,
-                cursor.end(&()).1 .0,
-            ))
-        }
-    }
-
-    pub fn len(&self) -> FoldOffset {
-        FoldOffset(self.transforms.summary().output.len)
-    }
-
-    pub fn line_len(&self, row: u32) -> u32 {
-        let line_start = FoldPoint::new(row, 0).to_offset(self).0;
-        let line_end = if row >= self.max_point().row() {
-            self.len().0
-        } else {
-            FoldPoint::new(row + 1, 0).to_offset(self).0 - 1
-        };
-        (line_end - line_start) as u32
-    }
-
-    pub fn buffer_rows(&self, start_row: u32) -> FoldBufferRows {
-        if start_row > self.transforms.summary().output.lines.row {
-            panic!("invalid display row {}", start_row);
-        }
-
-        let fold_point = FoldPoint::new(start_row, 0);
-        let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>();
-        cursor.seek(&fold_point, Bias::Left, &());
-
-        let overshoot = fold_point.0 - cursor.start().0 .0;
-        let inlay_point = InlayPoint(cursor.start().1 .0 + overshoot);
-        let input_buffer_rows = self.inlay_snapshot.buffer_rows(inlay_point.row());
-
-        FoldBufferRows {
-            fold_point,
-            input_buffer_rows,
-            cursor,
-        }
-    }
-
-    pub fn max_point(&self) -> FoldPoint {
-        FoldPoint(self.transforms.summary().output.lines)
-    }
-
-    #[cfg(test)]
-    pub fn longest_row(&self) -> u32 {
-        self.transforms.summary().output.longest_row
-    }
-
-    pub fn folds_in_range<T>(&self, range: Range<T>) -> impl Iterator<Item = &Fold>
-    where
-        T: ToOffset,
-    {
-        let mut folds = intersecting_folds(&self.inlay_snapshot, &self.folds, range, false);
-        iter::from_fn(move || {
-            let item = folds.item();
-            folds.next(&self.inlay_snapshot.buffer);
-            item
-        })
-    }
-
-    pub fn intersects_fold<T>(&self, offset: T) -> bool
-    where
-        T: ToOffset,
-    {
-        let buffer_offset = offset.to_offset(&self.inlay_snapshot.buffer);
-        let inlay_offset = self.inlay_snapshot.to_inlay_offset(buffer_offset);
-        let mut cursor = self.transforms.cursor::<InlayOffset>();
-        cursor.seek(&inlay_offset, Bias::Right, &());
-        cursor.item().map_or(false, |t| t.output_text.is_some())
-    }
-
-    pub fn is_line_folded(&self, buffer_row: u32) -> bool {
-        let mut inlay_point = self
-            .inlay_snapshot
-            .to_inlay_point(Point::new(buffer_row, 0));
-        let mut cursor = self.transforms.cursor::<InlayPoint>();
-        cursor.seek(&inlay_point, Bias::Right, &());
-        loop {
-            match cursor.item() {
-                Some(transform) => {
-                    let buffer_point = self.inlay_snapshot.to_buffer_point(inlay_point);
-                    if buffer_point.row != buffer_row {
-                        return false;
-                    } else if transform.output_text.is_some() {
-                        return true;
-                    }
-                }
-                None => return false,
-            }
-
-            if cursor.end(&()).row() == inlay_point.row() {
-                cursor.next(&());
-            } else {
-                inlay_point.0 += Point::new(1, 0);
-                cursor.seek(&inlay_point, Bias::Right, &());
-            }
-        }
-    }
-
-    pub fn chunks<'a>(
-        &'a self,
-        range: Range<FoldOffset>,
-        language_aware: bool,
-        highlights: Highlights<'a>,
-    ) -> FoldChunks<'a> {
-        let mut transform_cursor = self.transforms.cursor::<(FoldOffset, InlayOffset)>();
-
-        let inlay_end = {
-            transform_cursor.seek(&range.end, Bias::Right, &());
-            let overshoot = range.end.0 - transform_cursor.start().0 .0;
-            transform_cursor.start().1 + InlayOffset(overshoot)
-        };
-
-        let inlay_start = {
-            transform_cursor.seek(&range.start, Bias::Right, &());
-            let overshoot = range.start.0 - transform_cursor.start().0 .0;
-            transform_cursor.start().1 + InlayOffset(overshoot)
-        };
-
-        FoldChunks {
-            transform_cursor,
-            inlay_chunks: self.inlay_snapshot.chunks(
-                inlay_start..inlay_end,
-                language_aware,
-                highlights,
-            ),
-            inlay_chunk: None,
-            inlay_offset: inlay_start,
-            output_offset: range.start.0,
-            max_output_offset: range.end.0,
-            ellipses_color: self.ellipses_color,
-        }
-    }
-
-    pub fn chars_at(&self, start: FoldPoint) -> impl '_ + Iterator<Item = char> {
-        self.chunks(
-            start.to_offset(self)..self.len(),
-            false,
-            Highlights::default(),
-        )
-        .flat_map(|chunk| chunk.text.chars())
-    }
-
-    #[cfg(test)]
-    pub fn clip_offset(&self, offset: FoldOffset, bias: Bias) -> FoldOffset {
-        if offset > self.len() {
-            self.len()
-        } else {
-            self.clip_point(offset.to_point(self), bias).to_offset(self)
-        }
-    }
-
-    pub fn clip_point(&self, point: FoldPoint, bias: Bias) -> FoldPoint {
-        let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>();
-        cursor.seek(&point, Bias::Right, &());
-        if let Some(transform) = cursor.item() {
-            let transform_start = cursor.start().0 .0;
-            if transform.output_text.is_some() {
-                if point.0 == transform_start || matches!(bias, Bias::Left) {
-                    FoldPoint(transform_start)
-                } else {
-                    FoldPoint(cursor.end(&()).0 .0)
-                }
-            } else {
-                let overshoot = InlayPoint(point.0 - transform_start);
-                let inlay_point = cursor.start().1 + overshoot;
-                let clipped_inlay_point = self.inlay_snapshot.clip_point(inlay_point, bias);
-                FoldPoint(cursor.start().0 .0 + (clipped_inlay_point - cursor.start().1).0)
-            }
-        } else {
-            FoldPoint(self.transforms.summary().output.lines)
-        }
-    }
-}
-
-fn intersecting_folds<'a, T>(
-    inlay_snapshot: &'a InlaySnapshot,
-    folds: &'a SumTree<Fold>,
-    range: Range<T>,
-    inclusive: bool,
-) -> FilterCursor<'a, impl 'a + FnMut(&FoldSummary) -> bool, Fold, usize>
-where
-    T: ToOffset,
-{
-    let buffer = &inlay_snapshot.buffer;
-    let start = buffer.anchor_before(range.start.to_offset(buffer));
-    let end = buffer.anchor_after(range.end.to_offset(buffer));
-    let mut cursor = folds.filter::<_, usize>(move |summary| {
-        let start_cmp = start.cmp(&summary.max_end, buffer);
-        let end_cmp = end.cmp(&summary.min_start, buffer);
-
-        if inclusive {
-            start_cmp <= Ordering::Equal && end_cmp >= Ordering::Equal
-        } else {
-            start_cmp == Ordering::Less && end_cmp == Ordering::Greater
-        }
-    });
-    cursor.next(buffer);
-    cursor
-}
-
-fn consolidate_inlay_edits(edits: &mut Vec<InlayEdit>) {
-    edits.sort_unstable_by(|a, b| {
-        a.old
-            .start
-            .cmp(&b.old.start)
-            .then_with(|| b.old.end.cmp(&a.old.end))
-    });
-
-    let mut i = 1;
-    while i < edits.len() {
-        let edit = edits[i].clone();
-        let prev_edit = &mut edits[i - 1];
-        if prev_edit.old.end >= edit.old.start {
-            prev_edit.old.end = prev_edit.old.end.max(edit.old.end);
-            prev_edit.new.start = prev_edit.new.start.min(edit.new.start);
-            prev_edit.new.end = prev_edit.new.end.max(edit.new.end);
-            edits.remove(i);
-            continue;
-        }
-        i += 1;
-    }
-}
-
-fn consolidate_fold_edits(edits: &mut Vec<FoldEdit>) {
-    edits.sort_unstable_by(|a, b| {
-        a.old
-            .start
-            .cmp(&b.old.start)
-            .then_with(|| b.old.end.cmp(&a.old.end))
-    });
-
-    let mut i = 1;
-    while i < edits.len() {
-        let edit = edits[i].clone();
-        let prev_edit = &mut edits[i - 1];
-        if prev_edit.old.end >= edit.old.start {
-            prev_edit.old.end = prev_edit.old.end.max(edit.old.end);
-            prev_edit.new.start = prev_edit.new.start.min(edit.new.start);
-            prev_edit.new.end = prev_edit.new.end.max(edit.new.end);
-            edits.remove(i);
-            continue;
-        }
-        i += 1;
-    }
-}
-
-#[derive(Clone, Debug, Default, Eq, PartialEq)]
-struct Transform {
-    summary: TransformSummary,
-    output_text: Option<&'static str>,
-}
-
-impl Transform {
-    fn is_fold(&self) -> bool {
-        self.output_text.is_some()
-    }
-}
-
-#[derive(Clone, Debug, Default, Eq, PartialEq)]
-struct TransformSummary {
-    output: TextSummary,
-    input: TextSummary,
-}
-
-impl sum_tree::Item for Transform {
-    type Summary = TransformSummary;
-
-    fn summary(&self) -> Self::Summary {
-        self.summary.clone()
-    }
-}
-
-impl sum_tree::Summary for TransformSummary {
-    type Context = ();
-
-    fn add_summary(&mut self, other: &Self, _: &()) {
-        self.input += &other.input;
-        self.output += &other.output;
-    }
-}
-
-#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
-pub struct FoldId(usize);
-
-impl Into<ElementId> for FoldId {
-    fn into(self) -> ElementId {
-        ElementId::Integer(self.0)
-    }
-}
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct Fold {
-    pub id: FoldId,
-    pub range: FoldRange,
-}
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct FoldRange(Range<Anchor>);
-
-impl Deref for FoldRange {
-    type Target = Range<Anchor>;
-
-    fn deref(&self) -> &Self::Target {
-        &self.0
-    }
-}
-
-impl DerefMut for FoldRange {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.0
-    }
-}
-
-impl Default for FoldRange {
-    fn default() -> Self {
-        Self(Anchor::min()..Anchor::max())
-    }
-}
-
-impl sum_tree::Item for Fold {
-    type Summary = FoldSummary;
-
-    fn summary(&self) -> Self::Summary {
-        FoldSummary {
-            start: self.range.start.clone(),
-            end: self.range.end.clone(),
-            min_start: self.range.start.clone(),
-            max_end: self.range.end.clone(),
-            count: 1,
-        }
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct FoldSummary {
-    start: Anchor,
-    end: Anchor,
-    min_start: Anchor,
-    max_end: Anchor,
-    count: usize,
-}
-
-impl Default for FoldSummary {
-    fn default() -> Self {
-        Self {
-            start: Anchor::min(),
-            end: Anchor::max(),
-            min_start: Anchor::max(),
-            max_end: Anchor::min(),
-            count: 0,
-        }
-    }
-}
-
-impl sum_tree::Summary for FoldSummary {
-    type Context = MultiBufferSnapshot;
-
-    fn add_summary(&mut self, other: &Self, buffer: &Self::Context) {
-        if other.min_start.cmp(&self.min_start, buffer) == Ordering::Less {
-            self.min_start = other.min_start.clone();
-        }
-        if other.max_end.cmp(&self.max_end, buffer) == Ordering::Greater {
-            self.max_end = other.max_end.clone();
-        }
-
-        #[cfg(debug_assertions)]
-        {
-            let start_comparison = self.start.cmp(&other.start, buffer);
-            assert!(start_comparison <= Ordering::Equal);
-            if start_comparison == Ordering::Equal {
-                assert!(self.end.cmp(&other.end, buffer) >= Ordering::Equal);
-            }
-        }
-
-        self.start = other.start.clone();
-        self.end = other.end.clone();
-        self.count += other.count;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, FoldSummary> for FoldRange {
-    fn add_summary(&mut self, summary: &'a FoldSummary, _: &MultiBufferSnapshot) {
-        self.0.start = summary.start.clone();
-        self.0.end = summary.end.clone();
-    }
-}
-
-impl<'a> sum_tree::SeekTarget<'a, FoldSummary, FoldRange> for FoldRange {
-    fn cmp(&self, other: &Self, buffer: &MultiBufferSnapshot) -> Ordering {
-        self.0.cmp(&other.0, buffer)
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize {
-    fn add_summary(&mut self, summary: &'a FoldSummary, _: &MultiBufferSnapshot) {
-        *self += summary.count;
-    }
-}
-
-#[derive(Clone)]
-pub struct FoldBufferRows<'a> {
-    cursor: Cursor<'a, Transform, (FoldPoint, InlayPoint)>,
-    input_buffer_rows: InlayBufferRows<'a>,
-    fold_point: FoldPoint,
-}
-
-impl<'a> Iterator for FoldBufferRows<'a> {
-    type Item = Option<u32>;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        let mut traversed_fold = false;
-        while self.fold_point > self.cursor.end(&()).0 {
-            self.cursor.next(&());
-            traversed_fold = true;
-            if self.cursor.item().is_none() {
-                break;
-            }
-        }
-
-        if self.cursor.item().is_some() {
-            if traversed_fold {
-                self.input_buffer_rows.seek(self.cursor.start().1.row());
-                self.input_buffer_rows.next();
-            }
-            *self.fold_point.row_mut() += 1;
-            self.input_buffer_rows.next()
-        } else {
-            None
-        }
-    }
-}
-
-pub struct FoldChunks<'a> {
-    transform_cursor: Cursor<'a, Transform, (FoldOffset, InlayOffset)>,
-    inlay_chunks: InlayChunks<'a>,
-    inlay_chunk: Option<(InlayOffset, Chunk<'a>)>,
-    inlay_offset: InlayOffset,
-    output_offset: usize,
-    max_output_offset: usize,
-    ellipses_color: Option<Hsla>,
-}
-
-impl<'a> Iterator for FoldChunks<'a> {
-    type Item = Chunk<'a>;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if self.output_offset >= self.max_output_offset {
-            return None;
-        }
-
-        let transform = self.transform_cursor.item()?;
-
-        // If we're in a fold, then return the fold's display text and
-        // advance the transform and buffer cursors to the end of the fold.
-        if let Some(output_text) = transform.output_text {
-            self.inlay_chunk.take();
-            self.inlay_offset += InlayOffset(transform.summary.input.len);
-            self.inlay_chunks.seek(self.inlay_offset);
-
-            while self.inlay_offset >= self.transform_cursor.end(&()).1
-                && self.transform_cursor.item().is_some()
-            {
-                self.transform_cursor.next(&());
-            }
-
-            self.output_offset += output_text.len();
-            return Some(Chunk {
-                text: output_text,
-                highlight_style: self.ellipses_color.map(|color| HighlightStyle {
-                    color: Some(color),
-                    ..Default::default()
-                }),
-                ..Default::default()
-            });
-        }
-
-        // Retrieve a chunk from the current location in the buffer.
-        if self.inlay_chunk.is_none() {
-            let chunk_offset = self.inlay_chunks.offset();
-            self.inlay_chunk = self.inlay_chunks.next().map(|chunk| (chunk_offset, chunk));
-        }
-
-        // Otherwise, take a chunk from the buffer's text.
-        if let Some((buffer_chunk_start, mut chunk)) = self.inlay_chunk {
-            let buffer_chunk_end = buffer_chunk_start + InlayOffset(chunk.text.len());
-            let transform_end = self.transform_cursor.end(&()).1;
-            let chunk_end = buffer_chunk_end.min(transform_end);
-
-            chunk.text = &chunk.text
-                [(self.inlay_offset - buffer_chunk_start).0..(chunk_end - buffer_chunk_start).0];
-
-            if chunk_end == transform_end {
-                self.transform_cursor.next(&());
-            } else if chunk_end == buffer_chunk_end {
-                self.inlay_chunk.take();
-            }
-
-            self.inlay_offset = chunk_end;
-            self.output_offset += chunk.text.len();
-            return Some(chunk);
-        }
-
-        None
-    }
-}
-
-#[derive(Copy, Clone, Eq, PartialEq)]
-struct HighlightEndpoint {
-    offset: InlayOffset,
-    is_start: bool,
-    tag: Option<TypeId>,
-    style: HighlightStyle,
-}
-
-impl PartialOrd for HighlightEndpoint {
-    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
-        Some(self.cmp(other))
-    }
-}
-
-impl Ord for HighlightEndpoint {
-    fn cmp(&self, other: &Self) -> Ordering {
-        self.offset
-            .cmp(&other.offset)
-            .then_with(|| other.is_start.cmp(&self.is_start))
-    }
-}
-
-#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
-pub struct FoldOffset(pub usize);
-
-impl FoldOffset {
-    pub fn to_point(self, snapshot: &FoldSnapshot) -> FoldPoint {
-        let mut cursor = snapshot
-            .transforms
-            .cursor::<(FoldOffset, TransformSummary)>();
-        cursor.seek(&self, Bias::Right, &());
-        let overshoot = if cursor.item().map_or(true, |t| t.is_fold()) {
-            Point::new(0, (self.0 - cursor.start().0 .0) as u32)
-        } else {
-            let inlay_offset = cursor.start().1.input.len + self.0 - cursor.start().0 .0;
-            let inlay_point = snapshot.inlay_snapshot.to_point(InlayOffset(inlay_offset));
-            inlay_point.0 - cursor.start().1.input.lines
-        };
-        FoldPoint(cursor.start().1.output.lines + overshoot)
-    }
-
-    #[cfg(test)]
-    pub fn to_inlay_offset(self, snapshot: &FoldSnapshot) -> InlayOffset {
-        let mut cursor = snapshot.transforms.cursor::<(FoldOffset, InlayOffset)>();
-        cursor.seek(&self, Bias::Right, &());
-        let overshoot = self.0 - cursor.start().0 .0;
-        InlayOffset(cursor.start().1 .0 + overshoot)
-    }
-}
-
-impl Add for FoldOffset {
-    type Output = Self;
-
-    fn add(self, rhs: Self) -> Self::Output {
-        Self(self.0 + rhs.0)
-    }
-}
-
-impl AddAssign for FoldOffset {
-    fn add_assign(&mut self, rhs: Self) {
-        self.0 += rhs.0;
-    }
-}
-
-impl Sub for FoldOffset {
-    type Output = Self;
-
-    fn sub(self, rhs: Self) -> Self::Output {
-        Self(self.0 - rhs.0)
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldOffset {
-    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
-        self.0 += &summary.output.len;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
-    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
-        self.0 += &summary.input.lines;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
-    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
-        self.0 += &summary.input.len;
-    }
-}
-
-pub type FoldEdit = Edit<FoldOffset>;
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::{display_map::inlay_map::InlayMap, MultiBuffer, ToPoint};
-    use collections::HashSet;
-    use rand::prelude::*;
-    use settings::SettingsStore;
-    use std::{env, mem};
-    use text::Patch;
-    use util::test::sample_text;
-    use util::RandomCharIter;
-    use Bias::{Left, Right};
-
-    #[gpui::test]
-    fn test_basic_folds(cx: &mut gpui::AppContext) {
-        init_test(cx);
-        let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
-        let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
-        let buffer_snapshot = buffer.read(cx).snapshot(cx);
-        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
-        let mut map = FoldMap::new(inlay_snapshot.clone()).0;
-
-        let (mut writer, _, _) = map.write(inlay_snapshot, vec![]);
-        let (snapshot2, edits) = writer.fold(vec![
-            Point::new(0, 2)..Point::new(2, 2),
-            Point::new(2, 4)..Point::new(4, 1),
-        ]);
-        assert_eq!(snapshot2.text(), "aa⋯cc⋯eeeee");
-        assert_eq!(
-            edits,
-            &[
-                FoldEdit {
-                    old: FoldOffset(2)..FoldOffset(16),
-                    new: FoldOffset(2)..FoldOffset(5),
-                },
-                FoldEdit {
-                    old: FoldOffset(18)..FoldOffset(29),
-                    new: FoldOffset(7)..FoldOffset(10)
-                },
-            ]
-        );
-
-        let buffer_snapshot = buffer.update(cx, |buffer, cx| {
-            buffer.edit(
-                vec![
-                    (Point::new(0, 0)..Point::new(0, 1), "123"),
-                    (Point::new(2, 3)..Point::new(2, 3), "123"),
-                ],
-                None,
-                cx,
-            );
-            buffer.snapshot(cx)
-        });
-
-        let (inlay_snapshot, inlay_edits) =
-            inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
-        let (snapshot3, edits) = map.read(inlay_snapshot, inlay_edits);
-        assert_eq!(snapshot3.text(), "123a⋯c123c⋯eeeee");
-        assert_eq!(
-            edits,
-            &[
-                FoldEdit {
-                    old: FoldOffset(0)..FoldOffset(1),
-                    new: FoldOffset(0)..FoldOffset(3),
-                },
-                FoldEdit {
-                    old: FoldOffset(6)..FoldOffset(6),
-                    new: FoldOffset(8)..FoldOffset(11),
-                },
-            ]
-        );
-
-        let buffer_snapshot = buffer.update(cx, |buffer, cx| {
-            buffer.edit([(Point::new(2, 6)..Point::new(4, 3), "456")], None, cx);
-            buffer.snapshot(cx)
-        });
-        let (inlay_snapshot, inlay_edits) =
-            inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
-        let (snapshot4, _) = map.read(inlay_snapshot.clone(), inlay_edits);
-        assert_eq!(snapshot4.text(), "123a⋯c123456eee");
-
-        let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
-        writer.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), false);
-        let (snapshot5, _) = map.read(inlay_snapshot.clone(), vec![]);
-        assert_eq!(snapshot5.text(), "123a⋯c123456eee");
-
-        let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
-        writer.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), true);
-        let (snapshot6, _) = map.read(inlay_snapshot, vec![]);
-        assert_eq!(snapshot6.text(), "123aaaaa\nbbbbbb\nccc123456eee");
-    }
-
-    #[gpui::test]
-    fn test_adjacent_folds(cx: &mut gpui::AppContext) {
-        init_test(cx);
-        let buffer = MultiBuffer::build_simple("abcdefghijkl", cx);
-        let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
-        let buffer_snapshot = buffer.read(cx).snapshot(cx);
-        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
-
-        {
-            let mut map = FoldMap::new(inlay_snapshot.clone()).0;
-
-            let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
-            writer.fold(vec![5..8]);
-            let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
-            assert_eq!(snapshot.text(), "abcde⋯ijkl");
-
-            // Create an fold adjacent to the start of the first fold.
-            let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
-            writer.fold(vec![0..1, 2..5]);
-            let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
-            assert_eq!(snapshot.text(), "⋯b⋯ijkl");
-
-            // Create an fold adjacent to the end of the first fold.
-            let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
-            writer.fold(vec![11..11, 8..10]);
-            let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
-            assert_eq!(snapshot.text(), "⋯b⋯kl");
-        }
-
-        {
-            let mut map = FoldMap::new(inlay_snapshot.clone()).0;
-
-            // Create two adjacent folds.
-            let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
-            writer.fold(vec![0..2, 2..5]);
-            let (snapshot, _) = map.read(inlay_snapshot, vec![]);
-            assert_eq!(snapshot.text(), "⋯fghijkl");
-
-            // Edit within one of the folds.
-            let buffer_snapshot = buffer.update(cx, |buffer, cx| {
-                buffer.edit([(0..1, "12345")], None, cx);
-                buffer.snapshot(cx)
-            });
-            let (inlay_snapshot, inlay_edits) =
-                inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
-            let (snapshot, _) = map.read(inlay_snapshot, inlay_edits);
-            assert_eq!(snapshot.text(), "12345⋯fghijkl");
-        }
-    }
-
-    #[gpui::test]
-    fn test_overlapping_folds(cx: &mut gpui::AppContext) {
-        let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
-        let buffer_snapshot = buffer.read(cx).snapshot(cx);
-        let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
-        let mut map = FoldMap::new(inlay_snapshot.clone()).0;
-        let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
-        writer.fold(vec![
-            Point::new(0, 2)..Point::new(2, 2),
-            Point::new(0, 4)..Point::new(1, 0),
-            Point::new(1, 2)..Point::new(3, 2),
-            Point::new(3, 1)..Point::new(4, 1),
-        ]);
-        let (snapshot, _) = map.read(inlay_snapshot, vec![]);
-        assert_eq!(snapshot.text(), "aa⋯eeeee");
-    }
-
-    #[gpui::test]
-    fn test_merging_folds_via_edit(cx: &mut gpui::AppContext) {
-        init_test(cx);
-        let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
-        let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
-        let buffer_snapshot = buffer.read(cx).snapshot(cx);
-        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
-        let mut map = FoldMap::new(inlay_snapshot.clone()).0;
-
-        let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
-        writer.fold(vec![
-            Point::new(0, 2)..Point::new(2, 2),
-            Point::new(3, 1)..Point::new(4, 1),
-        ]);
-        let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
-        assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee");
-
-        let buffer_snapshot = buffer.update(cx, |buffer, cx| {
-            buffer.edit([(Point::new(2, 2)..Point::new(3, 1), "")], None, cx);
-            buffer.snapshot(cx)
-        });
-        let (inlay_snapshot, inlay_edits) =
-            inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
-        let (snapshot, _) = map.read(inlay_snapshot, inlay_edits);
-        assert_eq!(snapshot.text(), "aa⋯eeeee");
-    }
-
-    #[gpui::test]
-    fn test_folds_in_range(cx: &mut gpui::AppContext) {
-        let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
-        let buffer_snapshot = buffer.read(cx).snapshot(cx);
-        let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
-        let mut map = FoldMap::new(inlay_snapshot.clone()).0;
-
-        let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
-        writer.fold(vec![
-            Point::new(0, 2)..Point::new(2, 2),
-            Point::new(0, 4)..Point::new(1, 0),
-            Point::new(1, 2)..Point::new(3, 2),
-            Point::new(3, 1)..Point::new(4, 1),
-        ]);
-        let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
-        let fold_ranges = snapshot
-            .folds_in_range(Point::new(1, 0)..Point::new(1, 3))
-            .map(|fold| {
-                fold.range.start.to_point(&buffer_snapshot)
-                    ..fold.range.end.to_point(&buffer_snapshot)
-            })
-            .collect::<Vec<_>>();
-        assert_eq!(
-            fold_ranges,
-            vec![
-                Point::new(0, 2)..Point::new(2, 2),
-                Point::new(1, 2)..Point::new(3, 2)
-            ]
-        );
-    }
-
-    #[gpui::test(iterations = 100)]
-    fn test_random_folds(cx: &mut gpui::AppContext, mut rng: StdRng) {
-        init_test(cx);
-        let operations = env::var("OPERATIONS")
-            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
-            .unwrap_or(10);
-
-        let len = rng.gen_range(0..10);
-        let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
-        let buffer = if rng.gen() {
-            MultiBuffer::build_simple(&text, cx)
-        } else {
-            MultiBuffer::build_random(&mut rng, cx)
-        };
-        let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
-        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
-        let mut map = FoldMap::new(inlay_snapshot.clone()).0;
-
-        let (mut initial_snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
-        let mut snapshot_edits = Vec::new();
-
-        let mut next_inlay_id = 0;
-        for _ in 0..operations {
-            log::info!("text: {:?}", buffer_snapshot.text());
-            let mut buffer_edits = Vec::new();
-            let mut inlay_edits = Vec::new();
-            match rng.gen_range(0..=100) {
-                0..=39 => {
-                    snapshot_edits.extend(map.randomly_mutate(&mut rng));
-                }
-                40..=59 => {
-                    let (_, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
-                    inlay_edits = edits;
-                }
-                _ => buffer.update(cx, |buffer, cx| {
-                    let subscription = buffer.subscribe();
-                    let edit_count = rng.gen_range(1..=5);
-                    buffer.randomly_mutate(&mut rng, edit_count, cx);
-                    buffer_snapshot = buffer.snapshot(cx);
-                    let edits = subscription.consume().into_inner();
-                    log::info!("editing {:?}", edits);
-                    buffer_edits.extend(edits);
-                }),
-            };
-
-            let (inlay_snapshot, new_inlay_edits) =
-                inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
-            log::info!("inlay text {:?}", inlay_snapshot.text());
-
-            let inlay_edits = Patch::new(inlay_edits)
-                .compose(new_inlay_edits)
-                .into_inner();
-            let (snapshot, edits) = map.read(inlay_snapshot.clone(), inlay_edits);
-            snapshot_edits.push((snapshot.clone(), edits));
-
-            let mut expected_text: String = inlay_snapshot.text().to_string();
-            for fold_range in map.merged_fold_ranges().into_iter().rev() {
-                let fold_inlay_start = inlay_snapshot.to_inlay_offset(fold_range.start);
-                let fold_inlay_end = inlay_snapshot.to_inlay_offset(fold_range.end);
-                expected_text.replace_range(fold_inlay_start.0..fold_inlay_end.0, "⋯");
-            }
-
-            assert_eq!(snapshot.text(), expected_text);
-            log::info!(
-                "fold text {:?} ({} lines)",
-                expected_text,
-                expected_text.matches('\n').count() + 1
-            );
-
-            let mut prev_row = 0;
-            let mut expected_buffer_rows = Vec::new();
-            for fold_range in map.merged_fold_ranges().into_iter() {
-                let fold_start = inlay_snapshot
-                    .to_point(inlay_snapshot.to_inlay_offset(fold_range.start))
-                    .row();
-                let fold_end = inlay_snapshot
-                    .to_point(inlay_snapshot.to_inlay_offset(fold_range.end))
-                    .row();
-                expected_buffer_rows.extend(
-                    inlay_snapshot
-                        .buffer_rows(prev_row)
-                        .take((1 + fold_start - prev_row) as usize),
-                );
-                prev_row = 1 + fold_end;
-            }
-            expected_buffer_rows.extend(inlay_snapshot.buffer_rows(prev_row));
-
-            assert_eq!(
-                expected_buffer_rows.len(),
-                expected_text.matches('\n').count() + 1,
-                "wrong expected buffer rows {:?}. text: {:?}",
-                expected_buffer_rows,
-                expected_text
-            );
-
-            for (output_row, line) in expected_text.lines().enumerate() {
-                let line_len = snapshot.line_len(output_row as u32);
-                assert_eq!(line_len, line.len() as u32);
-            }
-
-            let longest_row = snapshot.longest_row();
-            let longest_char_column = expected_text
-                .split('\n')
-                .nth(longest_row as usize)
-                .unwrap()
-                .chars()
-                .count();
-            let mut fold_point = FoldPoint::new(0, 0);
-            let mut fold_offset = FoldOffset(0);
-            let mut char_column = 0;
-            for c in expected_text.chars() {
-                let inlay_point = fold_point.to_inlay_point(&snapshot);
-                let inlay_offset = fold_offset.to_inlay_offset(&snapshot);
-                assert_eq!(
-                    snapshot.to_fold_point(inlay_point, Right),
-                    fold_point,
-                    "{:?} -> fold point",
-                    inlay_point,
-                );
-                assert_eq!(
-                    inlay_snapshot.to_offset(inlay_point),
-                    inlay_offset,
-                    "inlay_snapshot.to_offset({:?})",
-                    inlay_point,
-                );
-                assert_eq!(
-                    fold_point.to_offset(&snapshot),
-                    fold_offset,
-                    "fold_point.to_offset({:?})",
-                    fold_point,
-                );
-
-                if c == '\n' {
-                    *fold_point.row_mut() += 1;
-                    *fold_point.column_mut() = 0;
-                    char_column = 0;
-                } else {
-                    *fold_point.column_mut() += c.len_utf8() as u32;
-                    char_column += 1;
-                }
-                fold_offset.0 += c.len_utf8();
-                if char_column > longest_char_column {
-                    panic!(
-                        "invalid longest row {:?} (chars {}), found row {:?} (chars: {})",
-                        longest_row,
-                        longest_char_column,
-                        fold_point.row(),
-                        char_column
-                    );
-                }
-            }
-
-            for _ in 0..5 {
-                let mut start = snapshot
-                    .clip_offset(FoldOffset(rng.gen_range(0..=snapshot.len().0)), Bias::Left);
-                let mut end = snapshot
-                    .clip_offset(FoldOffset(rng.gen_range(0..=snapshot.len().0)), Bias::Right);
-                if start > end {
-                    mem::swap(&mut start, &mut end);
-                }
-
-                let text = &expected_text[start.0..end.0];
-                assert_eq!(
-                    snapshot
-                        .chunks(start..end, false, Highlights::default())
-                        .map(|c| c.text)
-                        .collect::<String>(),
-                    text,
-                );
-            }
-
-            let mut fold_row = 0;
-            while fold_row < expected_buffer_rows.len() as u32 {
-                assert_eq!(
-                    snapshot.buffer_rows(fold_row).collect::<Vec<_>>(),
-                    expected_buffer_rows[(fold_row as usize)..],
-                    "wrong buffer rows starting at fold row {}",
-                    fold_row,
-                );
-                fold_row += 1;
-            }
-
-            let folded_buffer_rows = map
-                .merged_fold_ranges()
-                .iter()
-                .flat_map(|range| {
-                    let start_row = range.start.to_point(&buffer_snapshot).row;
-                    let end = range.end.to_point(&buffer_snapshot);
-                    if end.column == 0 {
-                        start_row..end.row
-                    } else {
-                        start_row..end.row + 1
-                    }
-                })
-                .collect::<HashSet<_>>();
-            for row in 0..=buffer_snapshot.max_point().row {
-                assert_eq!(
-                    snapshot.is_line_folded(row),
-                    folded_buffer_rows.contains(&row),
-                    "expected buffer row {}{} to be folded",
-                    row,
-                    if folded_buffer_rows.contains(&row) {
-                        ""
-                    } else {
-                        " not"
-                    }
-                );
-            }
-
-            for _ in 0..5 {
-                let end =
-                    buffer_snapshot.clip_offset(rng.gen_range(0..=buffer_snapshot.len()), Right);
-                let start = buffer_snapshot.clip_offset(rng.gen_range(0..=end), Left);
-                let expected_folds = map
-                    .snapshot
-                    .folds
-                    .items(&buffer_snapshot)
-                    .into_iter()
-                    .filter(|fold| {
-                        let start = buffer_snapshot.anchor_before(start);
-                        let end = buffer_snapshot.anchor_after(end);
-                        start.cmp(&fold.range.end, &buffer_snapshot) == Ordering::Less
-                            && end.cmp(&fold.range.start, &buffer_snapshot) == Ordering::Greater
-                    })
-                    .collect::<Vec<_>>();
-
-                assert_eq!(
-                    snapshot
-                        .folds_in_range(start..end)
-                        .cloned()
-                        .collect::<Vec<_>>(),
-                    expected_folds
-                );
-            }
-
-            let text = snapshot.text();
-            for _ in 0..5 {
-                let start_row = rng.gen_range(0..=snapshot.max_point().row());
-                let start_column = rng.gen_range(0..=snapshot.line_len(start_row));
-                let end_row = rng.gen_range(0..=snapshot.max_point().row());
-                let end_column = rng.gen_range(0..=snapshot.line_len(end_row));
-                let mut start =
-                    snapshot.clip_point(FoldPoint::new(start_row, start_column), Bias::Left);
-                let mut end = snapshot.clip_point(FoldPoint::new(end_row, end_column), Bias::Right);
-                if start > end {
-                    mem::swap(&mut start, &mut end);
-                }
-
-                let lines = start..end;
-                let bytes = start.to_offset(&snapshot)..end.to_offset(&snapshot);
-                assert_eq!(
-                    snapshot.text_summary_for_range(lines),
-                    TextSummary::from(&text[bytes.start.0..bytes.end.0])
-                )
-            }
-
-            let mut text = initial_snapshot.text();
-            for (snapshot, edits) in snapshot_edits.drain(..) {
-                let new_text = snapshot.text();
-                for edit in edits {
-                    let old_bytes = edit.new.start.0..edit.new.start.0 + edit.old_len().0;
-                    let new_bytes = edit.new.start.0..edit.new.end.0;
-                    text.replace_range(old_bytes, &new_text[new_bytes]);
-                }
-
-                assert_eq!(text, new_text);
-                initial_snapshot = snapshot;
-            }
-        }
-    }
-
-    #[gpui::test]
-    fn test_buffer_rows(cx: &mut gpui::AppContext) {
-        let text = sample_text(6, 6, 'a') + "\n";
-        let buffer = MultiBuffer::build_simple(&text, cx);
-
-        let buffer_snapshot = buffer.read(cx).snapshot(cx);
-        let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
-        let mut map = FoldMap::new(inlay_snapshot.clone()).0;
-
-        let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
-        writer.fold(vec![
-            Point::new(0, 2)..Point::new(2, 2),
-            Point::new(3, 1)..Point::new(4, 1),
-        ]);
-
-        let (snapshot, _) = map.read(inlay_snapshot, vec![]);
-        assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee\nffffff\n");
-        assert_eq!(
-            snapshot.buffer_rows(0).collect::<Vec<_>>(),
-            [Some(0), Some(3), Some(5), Some(6)]
-        );
-        assert_eq!(snapshot.buffer_rows(3).collect::<Vec<_>>(), [Some(6)]);
-    }
-
-    fn init_test(cx: &mut gpui::AppContext) {
-        let store = SettingsStore::test(cx);
-        cx.set_global(store);
-    }
-
-    impl FoldMap {
-        fn merged_fold_ranges(&self) -> Vec<Range<usize>> {
-            let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
-            let buffer = &inlay_snapshot.buffer;
-            let mut folds = self.snapshot.folds.items(buffer);
-            // Ensure sorting doesn't change how folds get merged and displayed.
-            folds.sort_by(|a, b| a.range.cmp(&b.range, buffer));
-            let mut fold_ranges = folds
-                .iter()
-                .map(|fold| fold.range.start.to_offset(buffer)..fold.range.end.to_offset(buffer))
-                .peekable();
-
-            let mut merged_ranges = Vec::new();
-            while let Some(mut fold_range) = fold_ranges.next() {
-                while let Some(next_range) = fold_ranges.peek() {
-                    if fold_range.end >= next_range.start {
-                        if next_range.end > fold_range.end {
-                            fold_range.end = next_range.end;
-                        }
-                        fold_ranges.next();
-                    } else {
-                        break;
-                    }
-                }
-                if fold_range.end > fold_range.start {
-                    merged_ranges.push(fold_range);
-                }
-            }
-            merged_ranges
-        }
-
-        pub fn randomly_mutate(
-            &mut self,
-            rng: &mut impl Rng,
-        ) -> Vec<(FoldSnapshot, Vec<FoldEdit>)> {
-            let mut snapshot_edits = Vec::new();
-            match rng.gen_range(0..=100) {
-                0..=39 if !self.snapshot.folds.is_empty() => {
-                    let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
-                    let buffer = &inlay_snapshot.buffer;
-                    let mut to_unfold = Vec::new();
-                    for _ in 0..rng.gen_range(1..=3) {
-                        let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
-                        let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
-                        to_unfold.push(start..end);
-                    }
-                    log::info!("unfolding {:?}", to_unfold);
-                    let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]);
-                    snapshot_edits.push((snapshot, edits));
-                    let (snapshot, edits) = writer.fold(to_unfold);
-                    snapshot_edits.push((snapshot, edits));
-                }
-                _ => {
-                    let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
-                    let buffer = &inlay_snapshot.buffer;
-                    let mut to_fold = Vec::new();
-                    for _ in 0..rng.gen_range(1..=2) {
-                        let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
-                        let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
-                        to_fold.push(start..end);
-                    }
-                    log::info!("folding {:?}", to_fold);
-                    let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]);
-                    snapshot_edits.push((snapshot, edits));
-                    let (snapshot, edits) = writer.fold(to_fold);
-                    snapshot_edits.push((snapshot, edits));
-                }
-            }
-            snapshot_edits
-        }
-    }
-}

crates/editor2/src/display_map/inlay_map.rs 🔗

@@ -1,1896 +0,0 @@
-use crate::{Anchor, InlayId, MultiBufferSnapshot, ToOffset};
-use collections::{BTreeMap, BTreeSet};
-use gpui::HighlightStyle;
-use language::{Chunk, Edit, Point, TextSummary};
-use multi_buffer::{MultiBufferChunks, MultiBufferRows};
-use std::{
-    any::TypeId,
-    cmp,
-    iter::Peekable,
-    ops::{Add, AddAssign, Range, Sub, SubAssign},
-    sync::Arc,
-    vec,
-};
-use sum_tree::{Bias, Cursor, SumTree, TreeMap};
-use text::{Patch, Rope};
-
-use super::Highlights;
-
-pub struct InlayMap {
-    snapshot: InlaySnapshot,
-    inlays: Vec<Inlay>,
-}
-
-#[derive(Clone)]
-pub struct InlaySnapshot {
-    pub buffer: MultiBufferSnapshot,
-    transforms: SumTree<Transform>,
-    pub version: usize,
-}
-
-#[derive(Clone, Debug)]
-enum Transform {
-    Isomorphic(TextSummary),
-    Inlay(Inlay),
-}
-
-#[derive(Debug, Clone)]
-pub struct Inlay {
-    pub id: InlayId,
-    pub position: Anchor,
-    pub text: text::Rope,
-}
-
-impl Inlay {
-    pub fn hint(id: usize, position: Anchor, hint: &project::InlayHint) -> Self {
-        let mut text = hint.text();
-        if hint.padding_right && !text.ends_with(' ') {
-            text.push(' ');
-        }
-        if hint.padding_left && !text.starts_with(' ') {
-            text.insert(0, ' ');
-        }
-        Self {
-            id: InlayId::Hint(id),
-            position,
-            text: text.into(),
-        }
-    }
-
-    pub fn suggestion<T: Into<Rope>>(id: usize, position: Anchor, text: T) -> Self {
-        Self {
-            id: InlayId::Suggestion(id),
-            position,
-            text: text.into(),
-        }
-    }
-}
-
-impl sum_tree::Item for Transform {
-    type Summary = TransformSummary;
-
-    fn summary(&self) -> Self::Summary {
-        match self {
-            Transform::Isomorphic(summary) => TransformSummary {
-                input: summary.clone(),
-                output: summary.clone(),
-            },
-            Transform::Inlay(inlay) => TransformSummary {
-                input: TextSummary::default(),
-                output: inlay.text.summary(),
-            },
-        }
-    }
-}
-
-#[derive(Clone, Debug, Default)]
-struct TransformSummary {
-    input: TextSummary,
-    output: TextSummary,
-}
-
-impl sum_tree::Summary for TransformSummary {
-    type Context = ();
-
-    fn add_summary(&mut self, other: &Self, _: &()) {
-        self.input += &other.input;
-        self.output += &other.output;
-    }
-}
-
-pub type InlayEdit = Edit<InlayOffset>;
-
-#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
-pub struct InlayOffset(pub usize);
-
-impl Add for InlayOffset {
-    type Output = Self;
-
-    fn add(self, rhs: Self) -> Self::Output {
-        Self(self.0 + rhs.0)
-    }
-}
-
-impl Sub for InlayOffset {
-    type Output = Self;
-
-    fn sub(self, rhs: Self) -> Self::Output {
-        Self(self.0 - rhs.0)
-    }
-}
-
-impl AddAssign for InlayOffset {
-    fn add_assign(&mut self, rhs: Self) {
-        self.0 += rhs.0;
-    }
-}
-
-impl SubAssign for InlayOffset {
-    fn sub_assign(&mut self, rhs: Self) {
-        self.0 -= rhs.0;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
-    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
-        self.0 += &summary.output.len;
-    }
-}
-
-#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
-pub struct InlayPoint(pub Point);
-
-impl Add for InlayPoint {
-    type Output = Self;
-
-    fn add(self, rhs: Self) -> Self::Output {
-        Self(self.0 + rhs.0)
-    }
-}
-
-impl Sub for InlayPoint {
-    type Output = Self;
-
-    fn sub(self, rhs: Self) -> Self::Output {
-        Self(self.0 - rhs.0)
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
-    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
-        self.0 += &summary.output.lines;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize {
-    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
-        *self += &summary.input.len;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point {
-    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
-        *self += &summary.input.lines;
-    }
-}
-
-#[derive(Clone)]
-pub struct InlayBufferRows<'a> {
-    transforms: Cursor<'a, Transform, (InlayPoint, Point)>,
-    buffer_rows: MultiBufferRows<'a>,
-    inlay_row: u32,
-    max_buffer_row: u32,
-}
-
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-struct HighlightEndpoint {
-    offset: InlayOffset,
-    is_start: bool,
-    tag: Option<TypeId>,
-    style: HighlightStyle,
-}
-
-impl PartialOrd for HighlightEndpoint {
-    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
-        Some(self.cmp(other))
-    }
-}
-
-impl Ord for HighlightEndpoint {
-    fn cmp(&self, other: &Self) -> cmp::Ordering {
-        self.offset
-            .cmp(&other.offset)
-            .then_with(|| other.is_start.cmp(&self.is_start))
-    }
-}
-
-pub struct InlayChunks<'a> {
-    transforms: Cursor<'a, Transform, (InlayOffset, usize)>,
-    buffer_chunks: MultiBufferChunks<'a>,
-    buffer_chunk: Option<Chunk<'a>>,
-    inlay_chunks: Option<text::Chunks<'a>>,
-    inlay_chunk: Option<&'a str>,
-    output_offset: InlayOffset,
-    max_output_offset: InlayOffset,
-    inlay_highlight_style: Option<HighlightStyle>,
-    suggestion_highlight_style: Option<HighlightStyle>,
-    highlight_endpoints: Peekable<vec::IntoIter<HighlightEndpoint>>,
-    active_highlights: BTreeMap<Option<TypeId>, HighlightStyle>,
-    highlights: Highlights<'a>,
-    snapshot: &'a InlaySnapshot,
-}
-
-impl<'a> InlayChunks<'a> {
-    pub fn seek(&mut self, offset: InlayOffset) {
-        self.transforms.seek(&offset, Bias::Right, &());
-
-        let buffer_offset = self.snapshot.to_buffer_offset(offset);
-        self.buffer_chunks.seek(buffer_offset);
-        self.inlay_chunks = None;
-        self.buffer_chunk = None;
-        self.output_offset = offset;
-    }
-
-    pub fn offset(&self) -> InlayOffset {
-        self.output_offset
-    }
-}
-
-impl<'a> Iterator for InlayChunks<'a> {
-    type Item = Chunk<'a>;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if self.output_offset == self.max_output_offset {
-            return None;
-        }
-
-        let mut next_highlight_endpoint = InlayOffset(usize::MAX);
-        while let Some(endpoint) = self.highlight_endpoints.peek().copied() {
-            if endpoint.offset <= self.output_offset {
-                if endpoint.is_start {
-                    self.active_highlights.insert(endpoint.tag, endpoint.style);
-                } else {
-                    self.active_highlights.remove(&endpoint.tag);
-                }
-                self.highlight_endpoints.next();
-            } else {
-                next_highlight_endpoint = endpoint.offset;
-                break;
-            }
-        }
-
-        let chunk = match self.transforms.item()? {
-            Transform::Isomorphic(_) => {
-                let chunk = self
-                    .buffer_chunk
-                    .get_or_insert_with(|| self.buffer_chunks.next().unwrap());
-                if chunk.text.is_empty() {
-                    *chunk = self.buffer_chunks.next().unwrap();
-                }
-
-                let (prefix, suffix) = chunk.text.split_at(
-                    chunk
-                        .text
-                        .len()
-                        .min(self.transforms.end(&()).0 .0 - self.output_offset.0)
-                        .min(next_highlight_endpoint.0 - self.output_offset.0),
-                );
-
-                chunk.text = suffix;
-                self.output_offset.0 += prefix.len();
-                let mut prefix = Chunk {
-                    text: prefix,
-                    ..chunk.clone()
-                };
-                if !self.active_highlights.is_empty() {
-                    let mut highlight_style = HighlightStyle::default();
-                    for active_highlight in self.active_highlights.values() {
-                        highlight_style.highlight(*active_highlight);
-                    }
-                    prefix.highlight_style = Some(highlight_style);
-                }
-                prefix
-            }
-            Transform::Inlay(inlay) => {
-                let mut inlay_style_and_highlight = None;
-                if let Some(inlay_highlights) = self.highlights.inlay_highlights {
-                    for (_, inlay_id_to_data) in inlay_highlights.iter() {
-                        let style_and_highlight = inlay_id_to_data.get(&inlay.id);
-                        if style_and_highlight.is_some() {
-                            inlay_style_and_highlight = style_and_highlight;
-                            break;
-                        }
-                    }
-                }
-
-                let mut highlight_style = match inlay.id {
-                    InlayId::Suggestion(_) => self.suggestion_highlight_style,
-                    InlayId::Hint(_) => self.inlay_highlight_style,
-                };
-                let next_inlay_highlight_endpoint;
-                let offset_in_inlay = self.output_offset - self.transforms.start().0;
-                if let Some((style, highlight)) = inlay_style_and_highlight {
-                    let range = &highlight.range;
-                    if offset_in_inlay.0 < range.start {
-                        next_inlay_highlight_endpoint = range.start - offset_in_inlay.0;
-                    } else if offset_in_inlay.0 >= range.end {
-                        next_inlay_highlight_endpoint = usize::MAX;
-                    } else {
-                        next_inlay_highlight_endpoint = range.end - offset_in_inlay.0;
-                        highlight_style
-                            .get_or_insert_with(|| Default::default())
-                            .highlight(style.clone());
-                    }
-                } else {
-                    next_inlay_highlight_endpoint = usize::MAX;
-                }
-
-                let inlay_chunks = self.inlay_chunks.get_or_insert_with(|| {
-                    let start = offset_in_inlay;
-                    let end = cmp::min(self.max_output_offset, self.transforms.end(&()).0)
-                        - self.transforms.start().0;
-                    inlay.text.chunks_in_range(start.0..end.0)
-                });
-                let inlay_chunk = self
-                    .inlay_chunk
-                    .get_or_insert_with(|| inlay_chunks.next().unwrap());
-                let (chunk, remainder) =
-                    inlay_chunk.split_at(inlay_chunk.len().min(next_inlay_highlight_endpoint));
-                *inlay_chunk = remainder;
-                if inlay_chunk.is_empty() {
-                    self.inlay_chunk = None;
-                }
-
-                self.output_offset.0 += chunk.len();
-
-                if !self.active_highlights.is_empty() {
-                    for active_highlight in self.active_highlights.values() {
-                        highlight_style
-                            .get_or_insert(Default::default())
-                            .highlight(*active_highlight);
-                    }
-                }
-                Chunk {
-                    text: chunk,
-                    highlight_style,
-                    ..Default::default()
-                }
-            }
-        };
-
-        if self.output_offset == self.transforms.end(&()).0 {
-            self.inlay_chunks = None;
-            self.transforms.next(&());
-        }
-
-        Some(chunk)
-    }
-}
-
-impl<'a> InlayBufferRows<'a> {
-    pub fn seek(&mut self, row: u32) {
-        let inlay_point = InlayPoint::new(row, 0);
-        self.transforms.seek(&inlay_point, Bias::Left, &());
-
-        let mut buffer_point = self.transforms.start().1;
-        let buffer_row = if row == 0 {
-            0
-        } else {
-            match self.transforms.item() {
-                Some(Transform::Isomorphic(_)) => {
-                    buffer_point += inlay_point.0 - self.transforms.start().0 .0;
-                    buffer_point.row
-                }
-                _ => cmp::min(buffer_point.row + 1, self.max_buffer_row),
-            }
-        };
-        self.inlay_row = inlay_point.row();
-        self.buffer_rows.seek(buffer_row);
-    }
-}
-
-impl<'a> Iterator for InlayBufferRows<'a> {
-    type Item = Option<u32>;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        let buffer_row = if self.inlay_row == 0 {
-            self.buffer_rows.next().unwrap()
-        } else {
-            match self.transforms.item()? {
-                Transform::Inlay(_) => None,
-                Transform::Isomorphic(_) => self.buffer_rows.next().unwrap(),
-            }
-        };
-
-        self.inlay_row += 1;
-        self.transforms
-            .seek_forward(&InlayPoint::new(self.inlay_row, 0), Bias::Left, &());
-
-        Some(buffer_row)
-    }
-}
-
-impl InlayPoint {
-    pub fn new(row: u32, column: u32) -> Self {
-        Self(Point::new(row, column))
-    }
-
-    pub fn row(self) -> u32 {
-        self.0.row
-    }
-}
-
-impl InlayMap {
-    pub fn new(buffer: MultiBufferSnapshot) -> (Self, InlaySnapshot) {
-        let version = 0;
-        let snapshot = InlaySnapshot {
-            buffer: buffer.clone(),
-            transforms: SumTree::from_iter(Some(Transform::Isomorphic(buffer.text_summary())), &()),
-            version,
-        };
-
-        (
-            Self {
-                snapshot: snapshot.clone(),
-                inlays: Vec::new(),
-            },
-            snapshot,
-        )
-    }
-
-    pub fn sync(
-        &mut self,
-        buffer_snapshot: MultiBufferSnapshot,
-        mut buffer_edits: Vec<text::Edit<usize>>,
-    ) -> (InlaySnapshot, Vec<InlayEdit>) {
-        let snapshot = &mut self.snapshot;
-
-        if buffer_edits.is_empty() {
-            if snapshot.buffer.trailing_excerpt_update_count()
-                != buffer_snapshot.trailing_excerpt_update_count()
-            {
-                buffer_edits.push(Edit {
-                    old: snapshot.buffer.len()..snapshot.buffer.len(),
-                    new: buffer_snapshot.len()..buffer_snapshot.len(),
-                });
-            }
-        }
-
-        if buffer_edits.is_empty() {
-            if snapshot.buffer.edit_count() != buffer_snapshot.edit_count()
-                || snapshot.buffer.parse_count() != buffer_snapshot.parse_count()
-                || snapshot.buffer.diagnostics_update_count()
-                    != buffer_snapshot.diagnostics_update_count()
-                || snapshot.buffer.git_diff_update_count()
-                    != buffer_snapshot.git_diff_update_count()
-                || snapshot.buffer.trailing_excerpt_update_count()
-                    != buffer_snapshot.trailing_excerpt_update_count()
-            {
-                snapshot.version += 1;
-            }
-
-            snapshot.buffer = buffer_snapshot;
-            (snapshot.clone(), Vec::new())
-        } else {
-            let mut inlay_edits = Patch::default();
-            let mut new_transforms = SumTree::new();
-            let mut cursor = snapshot.transforms.cursor::<(usize, InlayOffset)>();
-            let mut buffer_edits_iter = buffer_edits.iter().peekable();
-            while let Some(buffer_edit) = buffer_edits_iter.next() {
-                new_transforms.append(cursor.slice(&buffer_edit.old.start, Bias::Left, &()), &());
-                if let Some(Transform::Isomorphic(transform)) = cursor.item() {
-                    if cursor.end(&()).0 == buffer_edit.old.start {
-                        push_isomorphic(&mut new_transforms, transform.clone());
-                        cursor.next(&());
-                    }
-                }
-
-                // Remove all the inlays and transforms contained by the edit.
-                let old_start =
-                    cursor.start().1 + InlayOffset(buffer_edit.old.start - cursor.start().0);
-                cursor.seek(&buffer_edit.old.end, Bias::Right, &());
-                let old_end =
-                    cursor.start().1 + InlayOffset(buffer_edit.old.end - cursor.start().0);
-
-                // Push the unchanged prefix.
-                let prefix_start = new_transforms.summary().input.len;
-                let prefix_end = buffer_edit.new.start;
-                push_isomorphic(
-                    &mut new_transforms,
-                    buffer_snapshot.text_summary_for_range(prefix_start..prefix_end),
-                );
-                let new_start = InlayOffset(new_transforms.summary().output.len);
-
-                let start_ix = match self.inlays.binary_search_by(|probe| {
-                    probe
-                        .position
-                        .to_offset(&buffer_snapshot)
-                        .cmp(&buffer_edit.new.start)
-                        .then(std::cmp::Ordering::Greater)
-                }) {
-                    Ok(ix) | Err(ix) => ix,
-                };
-
-                for inlay in &self.inlays[start_ix..] {
-                    let buffer_offset = inlay.position.to_offset(&buffer_snapshot);
-                    if buffer_offset > buffer_edit.new.end {
-                        break;
-                    }
-
-                    let prefix_start = new_transforms.summary().input.len;
-                    let prefix_end = buffer_offset;
-                    push_isomorphic(
-                        &mut new_transforms,
-                        buffer_snapshot.text_summary_for_range(prefix_start..prefix_end),
-                    );
-
-                    if inlay.position.is_valid(&buffer_snapshot) {
-                        new_transforms.push(Transform::Inlay(inlay.clone()), &());
-                    }
-                }
-
-                // Apply the rest of the edit.
-                let transform_start = new_transforms.summary().input.len;
-                push_isomorphic(
-                    &mut new_transforms,
-                    buffer_snapshot.text_summary_for_range(transform_start..buffer_edit.new.end),
-                );
-                let new_end = InlayOffset(new_transforms.summary().output.len);
-                inlay_edits.push(Edit {
-                    old: old_start..old_end,
-                    new: new_start..new_end,
-                });
-
-                // If the next edit doesn't intersect the current isomorphic transform, then
-                // we can push its remainder.
-                if buffer_edits_iter
-                    .peek()
-                    .map_or(true, |edit| edit.old.start >= cursor.end(&()).0)
-                {
-                    let transform_start = new_transforms.summary().input.len;
-                    let transform_end =
-                        buffer_edit.new.end + (cursor.end(&()).0 - buffer_edit.old.end);
-                    push_isomorphic(
-                        &mut new_transforms,
-                        buffer_snapshot.text_summary_for_range(transform_start..transform_end),
-                    );
-                    cursor.next(&());
-                }
-            }
-
-            new_transforms.append(cursor.suffix(&()), &());
-            if new_transforms.is_empty() {
-                new_transforms.push(Transform::Isomorphic(Default::default()), &());
-            }
-
-            drop(cursor);
-            snapshot.transforms = new_transforms;
-            snapshot.version += 1;
-            snapshot.buffer = buffer_snapshot;
-            snapshot.check_invariants();
-
-            (snapshot.clone(), inlay_edits.into_inner())
-        }
-    }
-
-    pub fn splice(
-        &mut self,
-        to_remove: Vec<InlayId>,
-        to_insert: Vec<Inlay>,
-    ) -> (InlaySnapshot, Vec<InlayEdit>) {
-        let snapshot = &mut self.snapshot;
-        let mut edits = BTreeSet::new();
-
-        self.inlays.retain(|inlay| {
-            let retain = !to_remove.contains(&inlay.id);
-            if !retain {
-                let offset = inlay.position.to_offset(&snapshot.buffer);
-                edits.insert(offset);
-            }
-            retain
-        });
-
-        for inlay_to_insert in to_insert {
-            // Avoid inserting empty inlays.
-            if inlay_to_insert.text.is_empty() {
-                continue;
-            }
-
-            let offset = inlay_to_insert.position.to_offset(&snapshot.buffer);
-            match self.inlays.binary_search_by(|probe| {
-                probe
-                    .position
-                    .cmp(&inlay_to_insert.position, &snapshot.buffer)
-            }) {
-                Ok(ix) | Err(ix) => {
-                    self.inlays.insert(ix, inlay_to_insert);
-                }
-            }
-
-            edits.insert(offset);
-        }
-
-        let buffer_edits = edits
-            .into_iter()
-            .map(|offset| Edit {
-                old: offset..offset,
-                new: offset..offset,
-            })
-            .collect();
-        let buffer_snapshot = snapshot.buffer.clone();
-        let (snapshot, edits) = self.sync(buffer_snapshot, buffer_edits);
-        (snapshot, edits)
-    }
-
-    pub fn current_inlays(&self) -> impl Iterator<Item = &Inlay> {
-        self.inlays.iter()
-    }
-
-    #[cfg(test)]
-    pub(crate) fn randomly_mutate(
-        &mut self,
-        next_inlay_id: &mut usize,
-        rng: &mut rand::rngs::StdRng,
-    ) -> (InlaySnapshot, Vec<InlayEdit>) {
-        use rand::prelude::*;
-        use util::post_inc;
-
-        let mut to_remove = Vec::new();
-        let mut to_insert = Vec::new();
-        let snapshot = &mut self.snapshot;
-        for i in 0..rng.gen_range(1..=5) {
-            if self.inlays.is_empty() || rng.gen() {
-                let position = snapshot.buffer.random_byte_range(0, rng).start;
-                let bias = if rng.gen() { Bias::Left } else { Bias::Right };
-                let len = if rng.gen_bool(0.01) {
-                    0
-                } else {
-                    rng.gen_range(1..=5)
-                };
-                let text = util::RandomCharIter::new(&mut *rng)
-                    .filter(|ch| *ch != '\r')
-                    .take(len)
-                    .collect::<String>();
-
-                let inlay_id = if i % 2 == 0 {
-                    InlayId::Hint(post_inc(next_inlay_id))
-                } else {
-                    InlayId::Suggestion(post_inc(next_inlay_id))
-                };
-                log::info!(
-                    "creating inlay {:?} at buffer offset {} with bias {:?} and text {:?}",
-                    inlay_id,
-                    position,
-                    bias,
-                    text
-                );
-
-                to_insert.push(Inlay {
-                    id: inlay_id,
-                    position: snapshot.buffer.anchor_at(position, bias),
-                    text: text.into(),
-                });
-            } else {
-                to_remove.push(
-                    self.inlays
-                        .iter()
-                        .choose(rng)
-                        .map(|inlay| inlay.id)
-                        .unwrap(),
-                );
-            }
-        }
-        log::info!("removing inlays: {:?}", to_remove);
-
-        let (snapshot, edits) = self.splice(to_remove, to_insert);
-        (snapshot, edits)
-    }
-}
-
-impl InlaySnapshot {
-    pub fn to_point(&self, offset: InlayOffset) -> InlayPoint {
-        let mut cursor = self
-            .transforms
-            .cursor::<(InlayOffset, (InlayPoint, usize))>();
-        cursor.seek(&offset, Bias::Right, &());
-        let overshoot = offset.0 - cursor.start().0 .0;
-        match cursor.item() {
-            Some(Transform::Isomorphic(_)) => {
-                let buffer_offset_start = cursor.start().1 .1;
-                let buffer_offset_end = buffer_offset_start + overshoot;
-                let buffer_start = self.buffer.offset_to_point(buffer_offset_start);
-                let buffer_end = self.buffer.offset_to_point(buffer_offset_end);
-                InlayPoint(cursor.start().1 .0 .0 + (buffer_end - buffer_start))
-            }
-            Some(Transform::Inlay(inlay)) => {
-                let overshoot = inlay.text.offset_to_point(overshoot);
-                InlayPoint(cursor.start().1 .0 .0 + overshoot)
-            }
-            None => self.max_point(),
-        }
-    }
-
-    pub fn len(&self) -> InlayOffset {
-        InlayOffset(self.transforms.summary().output.len)
-    }
-
-    pub fn max_point(&self) -> InlayPoint {
-        InlayPoint(self.transforms.summary().output.lines)
-    }
-
-    pub fn to_offset(&self, point: InlayPoint) -> InlayOffset {
-        let mut cursor = self
-            .transforms
-            .cursor::<(InlayPoint, (InlayOffset, Point))>();
-        cursor.seek(&point, Bias::Right, &());
-        let overshoot = point.0 - cursor.start().0 .0;
-        match cursor.item() {
-            Some(Transform::Isomorphic(_)) => {
-                let buffer_point_start = cursor.start().1 .1;
-                let buffer_point_end = buffer_point_start + overshoot;
-                let buffer_offset_start = self.buffer.point_to_offset(buffer_point_start);
-                let buffer_offset_end = self.buffer.point_to_offset(buffer_point_end);
-                InlayOffset(cursor.start().1 .0 .0 + (buffer_offset_end - buffer_offset_start))
-            }
-            Some(Transform::Inlay(inlay)) => {
-                let overshoot = inlay.text.point_to_offset(overshoot);
-                InlayOffset(cursor.start().1 .0 .0 + overshoot)
-            }
-            None => self.len(),
-        }
-    }
-
-    pub fn to_buffer_point(&self, point: InlayPoint) -> Point {
-        let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>();
-        cursor.seek(&point, Bias::Right, &());
-        match cursor.item() {
-            Some(Transform::Isomorphic(_)) => {
-                let overshoot = point.0 - cursor.start().0 .0;
-                cursor.start().1 + overshoot
-            }
-            Some(Transform::Inlay(_)) => cursor.start().1,
-            None => self.buffer.max_point(),
-        }
-    }
-
-    pub fn to_buffer_offset(&self, offset: InlayOffset) -> usize {
-        let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>();
-        cursor.seek(&offset, Bias::Right, &());
-        match cursor.item() {
-            Some(Transform::Isomorphic(_)) => {
-                let overshoot = offset - cursor.start().0;
-                cursor.start().1 + overshoot.0
-            }
-            Some(Transform::Inlay(_)) => cursor.start().1,
-            None => self.buffer.len(),
-        }
-    }
-
-    pub fn to_inlay_offset(&self, offset: usize) -> InlayOffset {
-        let mut cursor = self.transforms.cursor::<(usize, InlayOffset)>();
-        cursor.seek(&offset, Bias::Left, &());
-        loop {
-            match cursor.item() {
-                Some(Transform::Isomorphic(_)) => {
-                    if offset == cursor.end(&()).0 {
-                        while let Some(Transform::Inlay(inlay)) = cursor.next_item() {
-                            if inlay.position.bias() == Bias::Right {
-                                break;
-                            } else {
-                                cursor.next(&());
-                            }
-                        }
-                        return cursor.end(&()).1;
-                    } else {
-                        let overshoot = offset - cursor.start().0;
-                        return InlayOffset(cursor.start().1 .0 + overshoot);
-                    }
-                }
-                Some(Transform::Inlay(inlay)) => {
-                    if inlay.position.bias() == Bias::Left {
-                        cursor.next(&());
-                    } else {
-                        return cursor.start().1;
-                    }
-                }
-                None => {
-                    return self.len();
-                }
-            }
-        }
-    }
-
-    pub fn to_inlay_point(&self, point: Point) -> InlayPoint {
-        let mut cursor = self.transforms.cursor::<(Point, InlayPoint)>();
-        cursor.seek(&point, Bias::Left, &());
-        loop {
-            match cursor.item() {
-                Some(Transform::Isomorphic(_)) => {
-                    if point == cursor.end(&()).0 {
-                        while let Some(Transform::Inlay(inlay)) = cursor.next_item() {
-                            if inlay.position.bias() == Bias::Right {
-                                break;
-                            } else {
-                                cursor.next(&());
-                            }
-                        }
-                        return cursor.end(&()).1;
-                    } else {
-                        let overshoot = point - cursor.start().0;
-                        return InlayPoint(cursor.start().1 .0 + overshoot);
-                    }
-                }
-                Some(Transform::Inlay(inlay)) => {
-                    if inlay.position.bias() == Bias::Left {
-                        cursor.next(&());
-                    } else {
-                        return cursor.start().1;
-                    }
-                }
-                None => {
-                    return self.max_point();
-                }
-            }
-        }
-    }
-
-    pub fn clip_point(&self, mut point: InlayPoint, mut bias: Bias) -> InlayPoint {
-        let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>();
-        cursor.seek(&point, Bias::Left, &());
-        loop {
-            match cursor.item() {
-                Some(Transform::Isomorphic(transform)) => {
-                    if cursor.start().0 == point {
-                        if let Some(Transform::Inlay(inlay)) = cursor.prev_item() {
-                            if inlay.position.bias() == Bias::Left {
-                                return point;
-                            } else if bias == Bias::Left {
-                                cursor.prev(&());
-                            } else if transform.first_line_chars == 0 {
-                                point.0 += Point::new(1, 0);
-                            } else {
-                                point.0 += Point::new(0, 1);
-                            }
-                        } else {
-                            return point;
-                        }
-                    } else if cursor.end(&()).0 == point {
-                        if let Some(Transform::Inlay(inlay)) = cursor.next_item() {
-                            if inlay.position.bias() == Bias::Right {
-                                return point;
-                            } else if bias == Bias::Right {
-                                cursor.next(&());
-                            } else if point.0.column == 0 {
-                                point.0.row -= 1;
-                                point.0.column = self.line_len(point.0.row);
-                            } else {
-                                point.0.column -= 1;
-                            }
-                        } else {
-                            return point;
-                        }
-                    } else {
-                        let overshoot = point.0 - cursor.start().0 .0;
-                        let buffer_point = cursor.start().1 + overshoot;
-                        let clipped_buffer_point = self.buffer.clip_point(buffer_point, bias);
-                        let clipped_overshoot = clipped_buffer_point - cursor.start().1;
-                        let clipped_point = InlayPoint(cursor.start().0 .0 + clipped_overshoot);
-                        if clipped_point == point {
-                            return clipped_point;
-                        } else {
-                            point = clipped_point;
-                        }
-                    }
-                }
-                Some(Transform::Inlay(inlay)) => {
-                    if point == cursor.start().0 && inlay.position.bias() == Bias::Right {
-                        match cursor.prev_item() {
-                            Some(Transform::Inlay(inlay)) => {
-                                if inlay.position.bias() == Bias::Left {
-                                    return point;
-                                }
-                            }
-                            _ => return point,
-                        }
-                    } else if point == cursor.end(&()).0 && inlay.position.bias() == Bias::Left {
-                        match cursor.next_item() {
-                            Some(Transform::Inlay(inlay)) => {
-                                if inlay.position.bias() == Bias::Right {
-                                    return point;
-                                }
-                            }
-                            _ => return point,
-                        }
-                    }
-
-                    if bias == Bias::Left {
-                        point = cursor.start().0;
-                        cursor.prev(&());
-                    } else {
-                        cursor.next(&());
-                        point = cursor.start().0;
-                    }
-                }
-                None => {
-                    bias = bias.invert();
-                    if bias == Bias::Left {
-                        point = cursor.start().0;
-                        cursor.prev(&());
-                    } else {
-                        cursor.next(&());
-                        point = cursor.start().0;
-                    }
-                }
-            }
-        }
-    }
-
-    pub fn text_summary(&self) -> TextSummary {
-        self.transforms.summary().output.clone()
-    }
-
-    pub fn text_summary_for_range(&self, range: Range<InlayOffset>) -> TextSummary {
-        let mut summary = TextSummary::default();
-
-        let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>();
-        cursor.seek(&range.start, Bias::Right, &());
-
-        let overshoot = range.start.0 - cursor.start().0 .0;
-        match cursor.item() {
-            Some(Transform::Isomorphic(_)) => {
-                let buffer_start = cursor.start().1;
-                let suffix_start = buffer_start + overshoot;
-                let suffix_end =
-                    buffer_start + (cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0);
-                summary = self.buffer.text_summary_for_range(suffix_start..suffix_end);
-                cursor.next(&());
-            }
-            Some(Transform::Inlay(inlay)) => {
-                let suffix_start = overshoot;
-                let suffix_end = cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0;
-                summary = inlay.text.cursor(suffix_start).summary(suffix_end);
-                cursor.next(&());
-            }
-            None => {}
-        }
-
-        if range.end > cursor.start().0 {
-            summary += cursor
-                .summary::<_, TransformSummary>(&range.end, Bias::Right, &())
-                .output;
-
-            let overshoot = range.end.0 - cursor.start().0 .0;
-            match cursor.item() {
-                Some(Transform::Isomorphic(_)) => {
-                    let prefix_start = cursor.start().1;
-                    let prefix_end = prefix_start + overshoot;
-                    summary += self
-                        .buffer
-                        .text_summary_for_range::<TextSummary, _>(prefix_start..prefix_end);
-                }
-                Some(Transform::Inlay(inlay)) => {
-                    let prefix_end = overshoot;
-                    summary += inlay.text.cursor(0).summary::<TextSummary>(prefix_end);
-                }
-                None => {}
-            }
-        }
-
-        summary
-    }
-
-    pub fn buffer_rows<'a>(&'a self, row: u32) -> InlayBufferRows<'a> {
-        let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>();
-        let inlay_point = InlayPoint::new(row, 0);
-        cursor.seek(&inlay_point, Bias::Left, &());
-
-        let max_buffer_row = self.buffer.max_point().row;
-        let mut buffer_point = cursor.start().1;
-        let buffer_row = if row == 0 {
-            0
-        } else {
-            match cursor.item() {
-                Some(Transform::Isomorphic(_)) => {
-                    buffer_point += inlay_point.0 - cursor.start().0 .0;
-                    buffer_point.row
-                }
-                _ => cmp::min(buffer_point.row + 1, max_buffer_row),
-            }
-        };
-
-        InlayBufferRows {
-            transforms: cursor,
-            inlay_row: inlay_point.row(),
-            buffer_rows: self.buffer.buffer_rows(buffer_row),
-            max_buffer_row,
-        }
-    }
-
-    pub fn line_len(&self, row: u32) -> u32 {
-        let line_start = self.to_offset(InlayPoint::new(row, 0)).0;
-        let line_end = if row >= self.max_point().row() {
-            self.len().0
-        } else {
-            self.to_offset(InlayPoint::new(row + 1, 0)).0 - 1
-        };
-        (line_end - line_start) as u32
-    }
-
-    pub fn chunks<'a>(
-        &'a self,
-        range: Range<InlayOffset>,
-        language_aware: bool,
-        highlights: Highlights<'a>,
-    ) -> InlayChunks<'a> {
-        let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>();
-        cursor.seek(&range.start, Bias::Right, &());
-
-        let mut highlight_endpoints = Vec::new();
-        if let Some(text_highlights) = highlights.text_highlights {
-            if !text_highlights.is_empty() {
-                self.apply_text_highlights(
-                    &mut cursor,
-                    &range,
-                    text_highlights,
-                    &mut highlight_endpoints,
-                );
-                cursor.seek(&range.start, Bias::Right, &());
-            }
-        }
-        highlight_endpoints.sort();
-        let buffer_range = self.to_buffer_offset(range.start)..self.to_buffer_offset(range.end);
-        let buffer_chunks = self.buffer.chunks(buffer_range, language_aware);
-
-        InlayChunks {
-            transforms: cursor,
-            buffer_chunks,
-            inlay_chunks: None,
-            inlay_chunk: None,
-            buffer_chunk: None,
-            output_offset: range.start,
-            max_output_offset: range.end,
-            inlay_highlight_style: highlights.inlay_highlight_style,
-            suggestion_highlight_style: highlights.suggestion_highlight_style,
-            highlight_endpoints: highlight_endpoints.into_iter().peekable(),
-            active_highlights: Default::default(),
-            highlights,
-            snapshot: self,
-        }
-    }
-
-    fn apply_text_highlights(
-        &self,
-        cursor: &mut Cursor<'_, Transform, (InlayOffset, usize)>,
-        range: &Range<InlayOffset>,
-        text_highlights: &TreeMap<Option<TypeId>, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>,
-        highlight_endpoints: &mut Vec<HighlightEndpoint>,
-    ) {
-        while cursor.start().0 < range.end {
-            let transform_start = self
-                .buffer
-                .anchor_after(self.to_buffer_offset(cmp::max(range.start, cursor.start().0)));
-            let transform_end =
-                {
-                    let overshoot = InlayOffset(range.end.0 - cursor.start().0 .0);
-                    self.buffer.anchor_before(self.to_buffer_offset(cmp::min(
-                        cursor.end(&()).0,
-                        cursor.start().0 + overshoot,
-                    )))
-                };
-
-            for (tag, text_highlights) in text_highlights.iter() {
-                let style = text_highlights.0;
-                let ranges = &text_highlights.1;
-
-                let start_ix = match ranges.binary_search_by(|probe| {
-                    let cmp = probe.end.cmp(&transform_start, &self.buffer);
-                    if cmp.is_gt() {
-                        cmp::Ordering::Greater
-                    } else {
-                        cmp::Ordering::Less
-                    }
-                }) {
-                    Ok(i) | Err(i) => i,
-                };
-                for range in &ranges[start_ix..] {
-                    if range.start.cmp(&transform_end, &self.buffer).is_ge() {
-                        break;
-                    }
-
-                    highlight_endpoints.push(HighlightEndpoint {
-                        offset: self.to_inlay_offset(range.start.to_offset(&self.buffer)),
-                        is_start: true,
-                        tag: *tag,
-                        style,
-                    });
-                    highlight_endpoints.push(HighlightEndpoint {
-                        offset: self.to_inlay_offset(range.end.to_offset(&self.buffer)),
-                        is_start: false,
-                        tag: *tag,
-                        style,
-                    });
-                }
-            }
-
-            cursor.next(&());
-        }
-    }
-
-    #[cfg(test)]
-    pub fn text(&self) -> String {
-        self.chunks(Default::default()..self.len(), false, Highlights::default())
-            .map(|chunk| chunk.text)
-            .collect()
-    }
-
-    fn check_invariants(&self) {
-        #[cfg(any(debug_assertions, feature = "test-support"))]
-        {
-            assert_eq!(self.transforms.summary().input, self.buffer.text_summary());
-            let mut transforms = self.transforms.iter().peekable();
-            while let Some(transform) = transforms.next() {
-                let transform_is_isomorphic = matches!(transform, Transform::Isomorphic(_));
-                if let Some(next_transform) = transforms.peek() {
-                    let next_transform_is_isomorphic =
-                        matches!(next_transform, Transform::Isomorphic(_));
-                    assert!(
-                        !transform_is_isomorphic || !next_transform_is_isomorphic,
-                        "two adjacent isomorphic transforms"
-                    );
-                }
-            }
-        }
-    }
-}
-
-fn push_isomorphic(sum_tree: &mut SumTree<Transform>, summary: TextSummary) {
-    if summary.len == 0 {
-        return;
-    }
-
-    let mut summary = Some(summary);
-    sum_tree.update_last(
-        |transform| {
-            if let Transform::Isomorphic(transform) = transform {
-                *transform += summary.take().unwrap();
-            }
-        },
-        &(),
-    );
-
-    if let Some(summary) = summary {
-        sum_tree.push(Transform::Isomorphic(summary), &());
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::{
-        display_map::{InlayHighlights, TextHighlights},
-        link_go_to_definition::InlayHighlight,
-        InlayId, MultiBuffer,
-    };
-    use gpui::AppContext;
-    use project::{InlayHint, InlayHintLabel, ResolveState};
-    use rand::prelude::*;
-    use settings::SettingsStore;
-    use std::{cmp::Reverse, env, sync::Arc};
-    use text::Patch;
-    use util::post_inc;
-
-    #[test]
-    fn test_inlay_properties_label_padding() {
-        assert_eq!(
-            Inlay::hint(
-                0,
-                Anchor::min(),
-                &InlayHint {
-                    label: InlayHintLabel::String("a".to_string()),
-                    position: text::Anchor::default(),
-                    padding_left: false,
-                    padding_right: false,
-                    tooltip: None,
-                    kind: None,
-                    resolve_state: ResolveState::Resolved,
-                },
-            )
-            .text
-            .to_string(),
-            "a",
-            "Should not pad label if not requested"
-        );
-
-        assert_eq!(
-            Inlay::hint(
-                0,
-                Anchor::min(),
-                &InlayHint {
-                    label: InlayHintLabel::String("a".to_string()),
-                    position: text::Anchor::default(),
-                    padding_left: true,
-                    padding_right: true,
-                    tooltip: None,
-                    kind: None,
-                    resolve_state: ResolveState::Resolved,
-                },
-            )
-            .text
-            .to_string(),
-            " a ",
-            "Should pad label for every side requested"
-        );
-
-        assert_eq!(
-            Inlay::hint(
-                0,
-                Anchor::min(),
-                &InlayHint {
-                    label: InlayHintLabel::String(" a ".to_string()),
-                    position: text::Anchor::default(),
-                    padding_left: false,
-                    padding_right: false,
-                    tooltip: None,
-                    kind: None,
-                    resolve_state: ResolveState::Resolved,
-                },
-            )
-            .text
-            .to_string(),
-            " a ",
-            "Should not change already padded label"
-        );
-
-        assert_eq!(
-            Inlay::hint(
-                0,
-                Anchor::min(),
-                &InlayHint {
-                    label: InlayHintLabel::String(" a ".to_string()),
-                    position: text::Anchor::default(),
-                    padding_left: true,
-                    padding_right: true,
-                    tooltip: None,
-                    kind: None,
-                    resolve_state: ResolveState::Resolved,
-                },
-            )
-            .text
-            .to_string(),
-            " a ",
-            "Should not change already padded label"
-        );
-    }
-
-    #[gpui::test]
-    fn test_basic_inlays(cx: &mut AppContext) {
-        let buffer = MultiBuffer::build_simple("abcdefghi", cx);
-        let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
-        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
-        assert_eq!(inlay_snapshot.text(), "abcdefghi");
-        let mut next_inlay_id = 0;
-
-        let (inlay_snapshot, _) = inlay_map.splice(
-            Vec::new(),
-            vec![Inlay {
-                id: InlayId::Hint(post_inc(&mut next_inlay_id)),
-                position: buffer.read(cx).snapshot(cx).anchor_after(3),
-                text: "|123|".into(),
-            }],
-        );
-        assert_eq!(inlay_snapshot.text(), "abc|123|defghi");
-        assert_eq!(
-            inlay_snapshot.to_inlay_point(Point::new(0, 0)),
-            InlayPoint::new(0, 0)
-        );
-        assert_eq!(
-            inlay_snapshot.to_inlay_point(Point::new(0, 1)),
-            InlayPoint::new(0, 1)
-        );
-        assert_eq!(
-            inlay_snapshot.to_inlay_point(Point::new(0, 2)),
-            InlayPoint::new(0, 2)
-        );
-        assert_eq!(
-            inlay_snapshot.to_inlay_point(Point::new(0, 3)),
-            InlayPoint::new(0, 3)
-        );
-        assert_eq!(
-            inlay_snapshot.to_inlay_point(Point::new(0, 4)),
-            InlayPoint::new(0, 9)
-        );
-        assert_eq!(
-            inlay_snapshot.to_inlay_point(Point::new(0, 5)),
-            InlayPoint::new(0, 10)
-        );
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
-            InlayPoint::new(0, 0)
-        );
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
-            InlayPoint::new(0, 0)
-        );
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
-            InlayPoint::new(0, 3)
-        );
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
-            InlayPoint::new(0, 3)
-        );
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
-            InlayPoint::new(0, 3)
-        );
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
-            InlayPoint::new(0, 9)
-        );
-
-        // Edits before or after the inlay should not affect it.
-        buffer.update(cx, |buffer, cx| {
-            buffer.edit([(2..3, "x"), (3..3, "y"), (4..4, "z")], None, cx)
-        });
-        let (inlay_snapshot, _) = inlay_map.sync(
-            buffer.read(cx).snapshot(cx),
-            buffer_edits.consume().into_inner(),
-        );
-        assert_eq!(inlay_snapshot.text(), "abxy|123|dzefghi");
-
-        // An edit surrounding the inlay should invalidate it.
-        buffer.update(cx, |buffer, cx| buffer.edit([(4..5, "D")], None, cx));
-        let (inlay_snapshot, _) = inlay_map.sync(
-            buffer.read(cx).snapshot(cx),
-            buffer_edits.consume().into_inner(),
-        );
-        assert_eq!(inlay_snapshot.text(), "abxyDzefghi");
-
-        let (inlay_snapshot, _) = inlay_map.splice(
-            Vec::new(),
-            vec![
-                Inlay {
-                    id: InlayId::Hint(post_inc(&mut next_inlay_id)),
-                    position: buffer.read(cx).snapshot(cx).anchor_before(3),
-                    text: "|123|".into(),
-                },
-                Inlay {
-                    id: InlayId::Suggestion(post_inc(&mut next_inlay_id)),
-                    position: buffer.read(cx).snapshot(cx).anchor_after(3),
-                    text: "|456|".into(),
-                },
-            ],
-        );
-        assert_eq!(inlay_snapshot.text(), "abx|123||456|yDzefghi");
-
-        // Edits ending where the inlay starts should not move it if it has a left bias.
-        buffer.update(cx, |buffer, cx| buffer.edit([(3..3, "JKL")], None, cx));
-        let (inlay_snapshot, _) = inlay_map.sync(
-            buffer.read(cx).snapshot(cx),
-            buffer_edits.consume().into_inner(),
-        );
-        assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi");
-
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
-            InlayPoint::new(0, 0)
-        );
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
-            InlayPoint::new(0, 0)
-        );
-
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Left),
-            InlayPoint::new(0, 1)
-        );
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Right),
-            InlayPoint::new(0, 1)
-        );
-
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Left),
-            InlayPoint::new(0, 2)
-        );
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Right),
-            InlayPoint::new(0, 2)
-        );
-
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
-            InlayPoint::new(0, 2)
-        );
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
-            InlayPoint::new(0, 8)
-        );
-
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
-            InlayPoint::new(0, 2)
-        );
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
-            InlayPoint::new(0, 8)
-        );
-
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Left),
-            InlayPoint::new(0, 2)
-        );
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Right),
-            InlayPoint::new(0, 8)
-        );
-
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Left),
-            InlayPoint::new(0, 2)
-        );
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Right),
-            InlayPoint::new(0, 8)
-        );
-
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Left),
-            InlayPoint::new(0, 2)
-        );
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Right),
-            InlayPoint::new(0, 8)
-        );
-
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Left),
-            InlayPoint::new(0, 8)
-        );
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Right),
-            InlayPoint::new(0, 8)
-        );
-
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Left),
-            InlayPoint::new(0, 9)
-        );
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Right),
-            InlayPoint::new(0, 9)
-        );
-
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Left),
-            InlayPoint::new(0, 10)
-        );
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Right),
-            InlayPoint::new(0, 10)
-        );
-
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Left),
-            InlayPoint::new(0, 11)
-        );
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Right),
-            InlayPoint::new(0, 11)
-        );
-
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Left),
-            InlayPoint::new(0, 11)
-        );
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Right),
-            InlayPoint::new(0, 17)
-        );
-
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Left),
-            InlayPoint::new(0, 11)
-        );
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Right),
-            InlayPoint::new(0, 17)
-        );
-
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Left),
-            InlayPoint::new(0, 11)
-        );
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Right),
-            InlayPoint::new(0, 17)
-        );
-
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Left),
-            InlayPoint::new(0, 11)
-        );
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Right),
-            InlayPoint::new(0, 17)
-        );
-
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Left),
-            InlayPoint::new(0, 11)
-        );
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Right),
-            InlayPoint::new(0, 17)
-        );
-
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Left),
-            InlayPoint::new(0, 17)
-        );
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Right),
-            InlayPoint::new(0, 17)
-        );
-
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Left),
-            InlayPoint::new(0, 18)
-        );
-        assert_eq!(
-            inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Right),
-            InlayPoint::new(0, 18)
-        );
-
-        // The inlays can be manually removed.
-        let (inlay_snapshot, _) = inlay_map.splice(
-            inlay_map.inlays.iter().map(|inlay| inlay.id).collect(),
-            Vec::new(),
-        );
-        assert_eq!(inlay_snapshot.text(), "abxJKLyDzefghi");
-    }
-
-    #[gpui::test]
-    fn test_inlay_buffer_rows(cx: &mut AppContext) {
-        let buffer = MultiBuffer::build_simple("abc\ndef\nghi", cx);
-        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
-        assert_eq!(inlay_snapshot.text(), "abc\ndef\nghi");
-        let mut next_inlay_id = 0;
-
-        let (inlay_snapshot, _) = inlay_map.splice(
-            Vec::new(),
-            vec![
-                Inlay {
-                    id: InlayId::Hint(post_inc(&mut next_inlay_id)),
-                    position: buffer.read(cx).snapshot(cx).anchor_before(0),
-                    text: "|123|\n".into(),
-                },
-                Inlay {
-                    id: InlayId::Hint(post_inc(&mut next_inlay_id)),
-                    position: buffer.read(cx).snapshot(cx).anchor_before(4),
-                    text: "|456|".into(),
-                },
-                Inlay {
-                    id: InlayId::Suggestion(post_inc(&mut next_inlay_id)),
-                    position: buffer.read(cx).snapshot(cx).anchor_before(7),
-                    text: "\n|567|\n".into(),
-                },
-            ],
-        );
-        assert_eq!(inlay_snapshot.text(), "|123|\nabc\n|456|def\n|567|\n\nghi");
-        assert_eq!(
-            inlay_snapshot.buffer_rows(0).collect::<Vec<_>>(),
-            vec![Some(0), None, Some(1), None, None, Some(2)]
-        );
-    }
-
-    #[gpui::test(iterations = 100)]
-    fn test_random_inlays(cx: &mut AppContext, mut rng: StdRng) {
-        init_test(cx);
-
-        let operations = env::var("OPERATIONS")
-            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
-            .unwrap_or(10);
-
-        let len = rng.gen_range(0..30);
-        let buffer = if rng.gen() {
-            let text = util::RandomCharIter::new(&mut rng)
-                .take(len)
-                .collect::<String>();
-            MultiBuffer::build_simple(&text, cx)
-        } else {
-            MultiBuffer::build_random(&mut rng, cx)
-        };
-        let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
-        let mut next_inlay_id = 0;
-        log::info!("buffer text: {:?}", buffer_snapshot.text());
-        let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
-        for _ in 0..operations {
-            let mut inlay_edits = Patch::default();
-
-            let mut prev_inlay_text = inlay_snapshot.text();
-            let mut buffer_edits = Vec::new();
-            match rng.gen_range(0..=100) {
-                0..=50 => {
-                    let (snapshot, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
-                    log::info!("mutated text: {:?}", snapshot.text());
-                    inlay_edits = Patch::new(edits);
-                }
-                _ => buffer.update(cx, |buffer, cx| {
-                    let subscription = buffer.subscribe();
-                    let edit_count = rng.gen_range(1..=5);
-                    buffer.randomly_mutate(&mut rng, edit_count, cx);
-                    buffer_snapshot = buffer.snapshot(cx);
-                    let edits = subscription.consume().into_inner();
-                    log::info!("editing {:?}", edits);
-                    buffer_edits.extend(edits);
-                }),
-            };
-
-            let (new_inlay_snapshot, new_inlay_edits) =
-                inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
-            inlay_snapshot = new_inlay_snapshot;
-            inlay_edits = inlay_edits.compose(new_inlay_edits);
-
-            log::info!("buffer text: {:?}", buffer_snapshot.text());
-            log::info!("inlay text: {:?}", inlay_snapshot.text());
-
-            let inlays = inlay_map
-                .inlays
-                .iter()
-                .filter(|inlay| inlay.position.is_valid(&buffer_snapshot))
-                .map(|inlay| {
-                    let offset = inlay.position.to_offset(&buffer_snapshot);
-                    (offset, inlay.clone())
-                })
-                .collect::<Vec<_>>();
-            let mut expected_text = Rope::from(buffer_snapshot.text());
-            for (offset, inlay) in inlays.iter().rev() {
-                expected_text.replace(*offset..*offset, &inlay.text.to_string());
-            }
-            assert_eq!(inlay_snapshot.text(), expected_text.to_string());
-
-            let expected_buffer_rows = inlay_snapshot.buffer_rows(0).collect::<Vec<_>>();
-            assert_eq!(
-                expected_buffer_rows.len() as u32,
-                expected_text.max_point().row + 1
-            );
-            for row_start in 0..expected_buffer_rows.len() {
-                assert_eq!(
-                    inlay_snapshot
-                        .buffer_rows(row_start as u32)
-                        .collect::<Vec<_>>(),
-                    &expected_buffer_rows[row_start..],
-                    "incorrect buffer rows starting at {}",
-                    row_start
-                );
-            }
-
-            let mut text_highlights = TextHighlights::default();
-            let text_highlight_count = rng.gen_range(0_usize..10);
-            let mut text_highlight_ranges = (0..text_highlight_count)
-                .map(|_| buffer_snapshot.random_byte_range(0, &mut rng))
-                .collect::<Vec<_>>();
-            text_highlight_ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
-            log::info!("highlighting text ranges {text_highlight_ranges:?}");
-            text_highlights.insert(
-                Some(TypeId::of::<()>()),
-                Arc::new((
-                    HighlightStyle::default(),
-                    text_highlight_ranges
-                        .into_iter()
-                        .map(|range| {
-                            buffer_snapshot.anchor_before(range.start)
-                                ..buffer_snapshot.anchor_after(range.end)
-                        })
-                        .collect(),
-                )),
-            );
-
-            let mut inlay_highlights = InlayHighlights::default();
-            if !inlays.is_empty() {
-                let inlay_highlight_count = rng.gen_range(0..inlays.len());
-                let mut inlay_indices = BTreeSet::default();
-                while inlay_indices.len() < inlay_highlight_count {
-                    inlay_indices.insert(rng.gen_range(0..inlays.len()));
-                }
-                let new_highlights = inlay_indices
-                    .into_iter()
-                    .filter_map(|i| {
-                        let (_, inlay) = &inlays[i];
-                        let inlay_text_len = inlay.text.len();
-                        match inlay_text_len {
-                            0 => None,
-                            1 => Some(InlayHighlight {
-                                inlay: inlay.id,
-                                inlay_position: inlay.position,
-                                range: 0..1,
-                            }),
-                            n => {
-                                let inlay_text = inlay.text.to_string();
-                                let mut highlight_end = rng.gen_range(1..n);
-                                let mut highlight_start = rng.gen_range(0..highlight_end);
-                                while !inlay_text.is_char_boundary(highlight_end) {
-                                    highlight_end += 1;
-                                }
-                                while !inlay_text.is_char_boundary(highlight_start) {
-                                    highlight_start -= 1;
-                                }
-                                Some(InlayHighlight {
-                                    inlay: inlay.id,
-                                    inlay_position: inlay.position,
-                                    range: highlight_start..highlight_end,
-                                })
-                            }
-                        }
-                    })
-                    .map(|highlight| (highlight.inlay, (HighlightStyle::default(), highlight)))
-                    .collect();
-                log::info!("highlighting inlay ranges {new_highlights:?}");
-                inlay_highlights.insert(TypeId::of::<()>(), new_highlights);
-            }
-
-            for _ in 0..5 {
-                let mut end = rng.gen_range(0..=inlay_snapshot.len().0);
-                end = expected_text.clip_offset(end, Bias::Right);
-                let mut start = rng.gen_range(0..=end);
-                start = expected_text.clip_offset(start, Bias::Right);
-
-                let range = InlayOffset(start)..InlayOffset(end);
-                log::info!("calling inlay_snapshot.chunks({range:?})");
-                let actual_text = inlay_snapshot
-                    .chunks(
-                        range,
-                        false,
-                        Highlights {
-                            text_highlights: Some(&text_highlights),
-                            inlay_highlights: Some(&inlay_highlights),
-                            ..Highlights::default()
-                        },
-                    )
-                    .map(|chunk| chunk.text)
-                    .collect::<String>();
-                assert_eq!(
-                    actual_text,
-                    expected_text.slice(start..end).to_string(),
-                    "incorrect text in range {:?}",
-                    start..end
-                );
-
-                assert_eq!(
-                    inlay_snapshot.text_summary_for_range(InlayOffset(start)..InlayOffset(end)),
-                    expected_text.slice(start..end).summary()
-                );
-            }
-
-            for edit in inlay_edits {
-                prev_inlay_text.replace_range(
-                    edit.new.start.0..edit.new.start.0 + edit.old_len().0,
-                    &inlay_snapshot.text()[edit.new.start.0..edit.new.end.0],
-                );
-            }
-            assert_eq!(prev_inlay_text, inlay_snapshot.text());
-
-            assert_eq!(expected_text.max_point(), inlay_snapshot.max_point().0);
-            assert_eq!(expected_text.len(), inlay_snapshot.len().0);
-
-            let mut buffer_point = Point::default();
-            let mut inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
-            let mut buffer_chars = buffer_snapshot.chars_at(0);
-            loop {
-                // Ensure conversion from buffer coordinates to inlay coordinates
-                // is consistent.
-                let buffer_offset = buffer_snapshot.point_to_offset(buffer_point);
-                assert_eq!(
-                    inlay_snapshot.to_point(inlay_snapshot.to_inlay_offset(buffer_offset)),
-                    inlay_point
-                );
-
-                // No matter which bias we clip an inlay point with, it doesn't move
-                // because it was constructed from a buffer point.
-                assert_eq!(
-                    inlay_snapshot.clip_point(inlay_point, Bias::Left),
-                    inlay_point,
-                    "invalid inlay point for buffer point {:?} when clipped left",
-                    buffer_point
-                );
-                assert_eq!(
-                    inlay_snapshot.clip_point(inlay_point, Bias::Right),
-                    inlay_point,
-                    "invalid inlay point for buffer point {:?} when clipped right",
-                    buffer_point
-                );
-
-                if let Some(ch) = buffer_chars.next() {
-                    if ch == '\n' {
-                        buffer_point += Point::new(1, 0);
-                    } else {
-                        buffer_point += Point::new(0, ch.len_utf8() as u32);
-                    }
-
-                    // Ensure that moving forward in the buffer always moves the inlay point forward as well.
-                    let new_inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
-                    assert!(new_inlay_point > inlay_point);
-                    inlay_point = new_inlay_point;
-                } else {
-                    break;
-                }
-            }
-
-            let mut inlay_point = InlayPoint::default();
-            let mut inlay_offset = InlayOffset::default();
-            for ch in expected_text.chars() {
-                assert_eq!(
-                    inlay_snapshot.to_offset(inlay_point),
-                    inlay_offset,
-                    "invalid to_offset({:?})",
-                    inlay_point
-                );
-                assert_eq!(
-                    inlay_snapshot.to_point(inlay_offset),
-                    inlay_point,
-                    "invalid to_point({:?})",
-                    inlay_offset
-                );
-
-                let mut bytes = [0; 4];
-                for byte in ch.encode_utf8(&mut bytes).as_bytes() {
-                    inlay_offset.0 += 1;
-                    if *byte == b'\n' {
-                        inlay_point.0 += Point::new(1, 0);
-                    } else {
-                        inlay_point.0 += Point::new(0, 1);
-                    }
-
-                    let clipped_left_point = inlay_snapshot.clip_point(inlay_point, Bias::Left);
-                    let clipped_right_point = inlay_snapshot.clip_point(inlay_point, Bias::Right);
-                    assert!(
-                        clipped_left_point <= clipped_right_point,
-                        "inlay point {:?} when clipped left is greater than when clipped right ({:?} > {:?})",
-                        inlay_point,
-                        clipped_left_point,
-                        clipped_right_point
-                    );
-
-                    // Ensure the clipped points are at valid text locations.
-                    assert_eq!(
-                        clipped_left_point.0,
-                        expected_text.clip_point(clipped_left_point.0, Bias::Left)
-                    );
-                    assert_eq!(
-                        clipped_right_point.0,
-                        expected_text.clip_point(clipped_right_point.0, Bias::Right)
-                    );
-
-                    // Ensure the clipped points never overshoot the end of the map.
-                    assert!(clipped_left_point <= inlay_snapshot.max_point());
-                    assert!(clipped_right_point <= inlay_snapshot.max_point());
-
-                    // Ensure the clipped points are at valid buffer locations.
-                    assert_eq!(
-                        inlay_snapshot
-                            .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_left_point)),
-                        clipped_left_point,
-                        "to_buffer_point({:?}) = {:?}",
-                        clipped_left_point,
-                        inlay_snapshot.to_buffer_point(clipped_left_point),
-                    );
-                    assert_eq!(
-                        inlay_snapshot
-                            .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_right_point)),
-                        clipped_right_point,
-                        "to_buffer_point({:?}) = {:?}",
-                        clipped_right_point,
-                        inlay_snapshot.to_buffer_point(clipped_right_point),
-                    );
-                }
-            }
-        }
-    }
-
-    fn init_test(cx: &mut AppContext) {
-        let store = SettingsStore::test(cx);
-        cx.set_global(store);
-        theme::init(theme::LoadThemes::JustBase, cx);
-    }
-}

crates/editor2/src/display_map/tab_map.rs 🔗

@@ -1,765 +0,0 @@
-use super::{
-    fold_map::{self, FoldChunks, FoldEdit, FoldPoint, FoldSnapshot},
-    Highlights,
-};
-use crate::MultiBufferSnapshot;
-use language::{Chunk, Point};
-use std::{cmp, mem, num::NonZeroU32, ops::Range};
-use sum_tree::Bias;
-
-const MAX_EXPANSION_COLUMN: u32 = 256;
-
-pub struct TabMap(TabSnapshot);
-
-impl TabMap {
-    pub fn new(fold_snapshot: FoldSnapshot, tab_size: NonZeroU32) -> (Self, TabSnapshot) {
-        let snapshot = TabSnapshot {
-            fold_snapshot,
-            tab_size,
-            max_expansion_column: MAX_EXPANSION_COLUMN,
-            version: 0,
-        };
-        (Self(snapshot.clone()), snapshot)
-    }
-
-    #[cfg(test)]
-    pub fn set_max_expansion_column(&mut self, column: u32) -> TabSnapshot {
-        self.0.max_expansion_column = column;
-        self.0.clone()
-    }
-
-    pub fn sync(
-        &mut self,
-        fold_snapshot: FoldSnapshot,
-        mut fold_edits: Vec<FoldEdit>,
-        tab_size: NonZeroU32,
-    ) -> (TabSnapshot, Vec<TabEdit>) {
-        let old_snapshot = &mut self.0;
-        let mut new_snapshot = TabSnapshot {
-            fold_snapshot,
-            tab_size,
-            max_expansion_column: old_snapshot.max_expansion_column,
-            version: old_snapshot.version,
-        };
-
-        if old_snapshot.fold_snapshot.version != new_snapshot.fold_snapshot.version {
-            new_snapshot.version += 1;
-        }
-
-        let mut tab_edits = Vec::with_capacity(fold_edits.len());
-
-        if old_snapshot.tab_size == new_snapshot.tab_size {
-            // Expand each edit to include the next tab on the same line as the edit,
-            // and any subsequent tabs on that line that moved across the tab expansion
-            // boundary.
-            for fold_edit in &mut fold_edits {
-                let old_end = fold_edit.old.end.to_point(&old_snapshot.fold_snapshot);
-                let old_end_row_successor_offset = cmp::min(
-                    FoldPoint::new(old_end.row() + 1, 0),
-                    old_snapshot.fold_snapshot.max_point(),
-                )
-                .to_offset(&old_snapshot.fold_snapshot);
-                let new_end = fold_edit.new.end.to_point(&new_snapshot.fold_snapshot);
-
-                let mut offset_from_edit = 0;
-                let mut first_tab_offset = None;
-                let mut last_tab_with_changed_expansion_offset = None;
-                'outer: for chunk in old_snapshot.fold_snapshot.chunks(
-                    fold_edit.old.end..old_end_row_successor_offset,
-                    false,
-                    Highlights::default(),
-                ) {
-                    for (ix, _) in chunk.text.match_indices('\t') {
-                        let offset_from_edit = offset_from_edit + (ix as u32);
-                        if first_tab_offset.is_none() {
-                            first_tab_offset = Some(offset_from_edit);
-                        }
-
-                        let old_column = old_end.column() + offset_from_edit;
-                        let new_column = new_end.column() + offset_from_edit;
-                        let was_expanded = old_column < old_snapshot.max_expansion_column;
-                        let is_expanded = new_column < new_snapshot.max_expansion_column;
-                        if was_expanded != is_expanded {
-                            last_tab_with_changed_expansion_offset = Some(offset_from_edit);
-                        } else if !was_expanded && !is_expanded {
-                            break 'outer;
-                        }
-                    }
-
-                    offset_from_edit += chunk.text.len() as u32;
-                    if old_end.column() + offset_from_edit >= old_snapshot.max_expansion_column
-                        && new_end.column() + offset_from_edit >= new_snapshot.max_expansion_column
-                    {
-                        break;
-                    }
-                }
-
-                if let Some(offset) = last_tab_with_changed_expansion_offset.or(first_tab_offset) {
-                    fold_edit.old.end.0 += offset as usize + 1;
-                    fold_edit.new.end.0 += offset as usize + 1;
-                }
-            }
-
-            // Combine any edits that overlap due to the expansion.
-            let mut ix = 1;
-            while ix < fold_edits.len() {
-                let (prev_edits, next_edits) = fold_edits.split_at_mut(ix);
-                let prev_edit = prev_edits.last_mut().unwrap();
-                let edit = &next_edits[0];
-                if prev_edit.old.end >= edit.old.start {
-                    prev_edit.old.end = edit.old.end;
-                    prev_edit.new.end = edit.new.end;
-                    fold_edits.remove(ix);
-                } else {
-                    ix += 1;
-                }
-            }
-
-            for fold_edit in fold_edits {
-                let old_start = fold_edit.old.start.to_point(&old_snapshot.fold_snapshot);
-                let old_end = fold_edit.old.end.to_point(&old_snapshot.fold_snapshot);
-                let new_start = fold_edit.new.start.to_point(&new_snapshot.fold_snapshot);
-                let new_end = fold_edit.new.end.to_point(&new_snapshot.fold_snapshot);
-                tab_edits.push(TabEdit {
-                    old: old_snapshot.to_tab_point(old_start)..old_snapshot.to_tab_point(old_end),
-                    new: new_snapshot.to_tab_point(new_start)..new_snapshot.to_tab_point(new_end),
-                });
-            }
-        } else {
-            new_snapshot.version += 1;
-            tab_edits.push(TabEdit {
-                old: TabPoint::zero()..old_snapshot.max_point(),
-                new: TabPoint::zero()..new_snapshot.max_point(),
-            });
-        }
-
-        *old_snapshot = new_snapshot;
-        (old_snapshot.clone(), tab_edits)
-    }
-}
-
-#[derive(Clone)]
-pub struct TabSnapshot {
-    pub fold_snapshot: FoldSnapshot,
-    pub tab_size: NonZeroU32,
-    pub max_expansion_column: u32,
-    pub version: usize,
-}
-
-impl TabSnapshot {
-    pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot {
-        &self.fold_snapshot.inlay_snapshot.buffer
-    }
-
-    pub fn line_len(&self, row: u32) -> u32 {
-        let max_point = self.max_point();
-        if row < max_point.row() {
-            self.to_tab_point(FoldPoint::new(row, self.fold_snapshot.line_len(row)))
-                .0
-                .column
-        } else {
-            max_point.column()
-        }
-    }
-
-    pub fn text_summary(&self) -> TextSummary {
-        self.text_summary_for_range(TabPoint::zero()..self.max_point())
-    }
-
-    pub fn text_summary_for_range(&self, range: Range<TabPoint>) -> TextSummary {
-        let input_start = self.to_fold_point(range.start, Bias::Left).0;
-        let input_end = self.to_fold_point(range.end, Bias::Right).0;
-        let input_summary = self
-            .fold_snapshot
-            .text_summary_for_range(input_start..input_end);
-
-        let mut first_line_chars = 0;
-        let line_end = if range.start.row() == range.end.row() {
-            range.end
-        } else {
-            self.max_point()
-        };
-        for c in self
-            .chunks(range.start..line_end, false, Highlights::default())
-            .flat_map(|chunk| chunk.text.chars())
-        {
-            if c == '\n' {
-                break;
-            }
-            first_line_chars += 1;
-        }
-
-        let mut last_line_chars = 0;
-        if range.start.row() == range.end.row() {
-            last_line_chars = first_line_chars;
-        } else {
-            for _ in self
-                .chunks(
-                    TabPoint::new(range.end.row(), 0)..range.end,
-                    false,
-                    Highlights::default(),
-                )
-                .flat_map(|chunk| chunk.text.chars())
-            {
-                last_line_chars += 1;
-            }
-        }
-
-        TextSummary {
-            lines: range.end.0 - range.start.0,
-            first_line_chars,
-            last_line_chars,
-            longest_row: input_summary.longest_row,
-            longest_row_chars: input_summary.longest_row_chars,
-        }
-    }
-
-    pub fn chunks<'a>(
-        &'a self,
-        range: Range<TabPoint>,
-        language_aware: bool,
-        highlights: Highlights<'a>,
-    ) -> TabChunks<'a> {
-        let (input_start, expanded_char_column, to_next_stop) =
-            self.to_fold_point(range.start, Bias::Left);
-        let input_column = input_start.column();
-        let input_start = input_start.to_offset(&self.fold_snapshot);
-        let input_end = self
-            .to_fold_point(range.end, Bias::Right)
-            .0
-            .to_offset(&self.fold_snapshot);
-        let to_next_stop = if range.start.0 + Point::new(0, to_next_stop) > range.end.0 {
-            range.end.column() - range.start.column()
-        } else {
-            to_next_stop
-        };
-
-        TabChunks {
-            fold_chunks: self.fold_snapshot.chunks(
-                input_start..input_end,
-                language_aware,
-                highlights,
-            ),
-            input_column,
-            column: expanded_char_column,
-            max_expansion_column: self.max_expansion_column,
-            output_position: range.start.0,
-            max_output_position: range.end.0,
-            tab_size: self.tab_size,
-            chunk: Chunk {
-                text: &SPACES[0..(to_next_stop as usize)],
-                is_tab: true,
-                ..Default::default()
-            },
-            inside_leading_tab: to_next_stop > 0,
-        }
-    }
-
-    pub fn buffer_rows(&self, row: u32) -> fold_map::FoldBufferRows<'_> {
-        self.fold_snapshot.buffer_rows(row)
-    }
-
-    #[cfg(test)]
-    pub fn text(&self) -> String {
-        self.chunks(
-            TabPoint::zero()..self.max_point(),
-            false,
-            Highlights::default(),
-        )
-        .map(|chunk| chunk.text)
-        .collect()
-    }
-
-    pub fn max_point(&self) -> TabPoint {
-        self.to_tab_point(self.fold_snapshot.max_point())
-    }
-
-    pub fn clip_point(&self, point: TabPoint, bias: Bias) -> TabPoint {
-        self.to_tab_point(
-            self.fold_snapshot
-                .clip_point(self.to_fold_point(point, bias).0, bias),
-        )
-    }
-
-    pub fn to_tab_point(&self, input: FoldPoint) -> TabPoint {
-        let chars = self.fold_snapshot.chars_at(FoldPoint::new(input.row(), 0));
-        let expanded = self.expand_tabs(chars, input.column());
-        TabPoint::new(input.row(), expanded)
-    }
-
-    pub fn to_fold_point(&self, output: TabPoint, bias: Bias) -> (FoldPoint, u32, u32) {
-        let chars = self.fold_snapshot.chars_at(FoldPoint::new(output.row(), 0));
-        let expanded = output.column();
-        let (collapsed, expanded_char_column, to_next_stop) =
-            self.collapse_tabs(chars, expanded, bias);
-        (
-            FoldPoint::new(output.row(), collapsed as u32),
-            expanded_char_column,
-            to_next_stop,
-        )
-    }
-
-    pub fn make_tab_point(&self, point: Point, bias: Bias) -> TabPoint {
-        let inlay_point = self.fold_snapshot.inlay_snapshot.to_inlay_point(point);
-        let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);
-        self.to_tab_point(fold_point)
-    }
-
-    pub fn to_point(&self, point: TabPoint, bias: Bias) -> Point {
-        let fold_point = self.to_fold_point(point, bias).0;
-        let inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);
-        self.fold_snapshot
-            .inlay_snapshot
-            .to_buffer_point(inlay_point)
-    }
-
-    fn expand_tabs(&self, chars: impl Iterator<Item = char>, column: u32) -> u32 {
-        let tab_size = self.tab_size.get();
-
-        let mut expanded_chars = 0;
-        let mut expanded_bytes = 0;
-        let mut collapsed_bytes = 0;
-        let end_column = column.min(self.max_expansion_column);
-        for c in chars {
-            if collapsed_bytes >= end_column {
-                break;
-            }
-            if c == '\t' {
-                let tab_len = tab_size - expanded_chars % tab_size;
-                expanded_bytes += tab_len;
-                expanded_chars += tab_len;
-            } else {
-                expanded_bytes += c.len_utf8() as u32;
-                expanded_chars += 1;
-            }
-            collapsed_bytes += c.len_utf8() as u32;
-        }
-        expanded_bytes + column.saturating_sub(collapsed_bytes)
-    }
-
-    fn collapse_tabs(
-        &self,
-        chars: impl Iterator<Item = char>,
-        column: u32,
-        bias: Bias,
-    ) -> (u32, u32, u32) {
-        let tab_size = self.tab_size.get();
-
-        let mut expanded_bytes = 0;
-        let mut expanded_chars = 0;
-        let mut collapsed_bytes = 0;
-        for c in chars {
-            if expanded_bytes >= column {
-                break;
-            }
-            if collapsed_bytes >= self.max_expansion_column {
-                break;
-            }
-
-            if c == '\t' {
-                let tab_len = tab_size - (expanded_chars % tab_size);
-                expanded_chars += tab_len;
-                expanded_bytes += tab_len;
-                if expanded_bytes > column {
-                    expanded_chars -= expanded_bytes - column;
-                    return match bias {
-                        Bias::Left => (collapsed_bytes, expanded_chars, expanded_bytes - column),
-                        Bias::Right => (collapsed_bytes + 1, expanded_chars, 0),
-                    };
-                }
-            } else {
-                expanded_chars += 1;
-                expanded_bytes += c.len_utf8() as u32;
-            }
-
-            if expanded_bytes > column && matches!(bias, Bias::Left) {
-                expanded_chars -= 1;
-                break;
-            }
-
-            collapsed_bytes += c.len_utf8() as u32;
-        }
-        (
-            collapsed_bytes + column.saturating_sub(expanded_bytes),
-            expanded_chars,
-            0,
-        )
-    }
-}
-
-#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
-pub struct TabPoint(pub Point);
-
-impl TabPoint {
-    pub fn new(row: u32, column: u32) -> Self {
-        Self(Point::new(row, column))
-    }
-
-    pub fn zero() -> Self {
-        Self::new(0, 0)
-    }
-
-    pub fn row(self) -> u32 {
-        self.0.row
-    }
-
-    pub fn column(self) -> u32 {
-        self.0.column
-    }
-}
-
-impl From<Point> for TabPoint {
-    fn from(point: Point) -> Self {
-        Self(point)
-    }
-}
-
-pub type TabEdit = text::Edit<TabPoint>;
-
-#[derive(Clone, Debug, Default, Eq, PartialEq)]
-pub struct TextSummary {
-    pub lines: Point,
-    pub first_line_chars: u32,
-    pub last_line_chars: u32,
-    pub longest_row: u32,
-    pub longest_row_chars: u32,
-}
-
-impl<'a> From<&'a str> for TextSummary {
-    fn from(text: &'a str) -> Self {
-        let sum = text::TextSummary::from(text);
-
-        TextSummary {
-            lines: sum.lines,
-            first_line_chars: sum.first_line_chars,
-            last_line_chars: sum.last_line_chars,
-            longest_row: sum.longest_row,
-            longest_row_chars: sum.longest_row_chars,
-        }
-    }
-}
-
-impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
-    fn add_assign(&mut self, other: &'a Self) {
-        let joined_chars = self.last_line_chars + other.first_line_chars;
-        if joined_chars > self.longest_row_chars {
-            self.longest_row = self.lines.row;
-            self.longest_row_chars = joined_chars;
-        }
-        if other.longest_row_chars > self.longest_row_chars {
-            self.longest_row = self.lines.row + other.longest_row;
-            self.longest_row_chars = other.longest_row_chars;
-        }
-
-        if self.lines.row == 0 {
-            self.first_line_chars += other.first_line_chars;
-        }
-
-        if other.lines.row == 0 {
-            self.last_line_chars += other.first_line_chars;
-        } else {
-            self.last_line_chars = other.last_line_chars;
-        }
-
-        self.lines += &other.lines;
-    }
-}
-
-// Handles a tab width <= 16
-const SPACES: &str = "                ";
-
-pub struct TabChunks<'a> {
-    fold_chunks: FoldChunks<'a>,
-    chunk: Chunk<'a>,
-    column: u32,
-    max_expansion_column: u32,
-    output_position: Point,
-    input_column: u32,
-    max_output_position: Point,
-    tab_size: NonZeroU32,
-    inside_leading_tab: bool,
-}
-
-impl<'a> Iterator for TabChunks<'a> {
-    type Item = Chunk<'a>;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if self.chunk.text.is_empty() {
-            if let Some(chunk) = self.fold_chunks.next() {
-                self.chunk = chunk;
-                if self.inside_leading_tab {
-                    self.chunk.text = &self.chunk.text[1..];
-                    self.inside_leading_tab = false;
-                    self.input_column += 1;
-                }
-            } else {
-                return None;
-            }
-        }
-
-        for (ix, c) in self.chunk.text.char_indices() {
-            match c {
-                '\t' => {
-                    if ix > 0 {
-                        let (prefix, suffix) = self.chunk.text.split_at(ix);
-                        self.chunk.text = suffix;
-                        return Some(Chunk {
-                            text: prefix,
-                            ..self.chunk
-                        });
-                    } else {
-                        self.chunk.text = &self.chunk.text[1..];
-                        let tab_size = if self.input_column < self.max_expansion_column {
-                            self.tab_size.get() as u32
-                        } else {
-                            1
-                        };
-                        let mut len = tab_size - self.column % tab_size;
-                        let next_output_position = cmp::min(
-                            self.output_position + Point::new(0, len),
-                            self.max_output_position,
-                        );
-                        len = next_output_position.column - self.output_position.column;
-                        self.column += len;
-                        self.input_column += 1;
-                        self.output_position = next_output_position;
-                        return Some(Chunk {
-                            text: &SPACES[..len as usize],
-                            is_tab: true,
-                            ..self.chunk
-                        });
-                    }
-                }
-                '\n' => {
-                    self.column = 0;
-                    self.input_column = 0;
-                    self.output_position += Point::new(1, 0);
-                }
-                _ => {
-                    self.column += 1;
-                    if !self.inside_leading_tab {
-                        self.input_column += c.len_utf8() as u32;
-                    }
-                    self.output_position.column += c.len_utf8() as u32;
-                }
-            }
-        }
-
-        Some(mem::take(&mut self.chunk))
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::{
-        display_map::{fold_map::FoldMap, inlay_map::InlayMap},
-        MultiBuffer,
-    };
-    use rand::{prelude::StdRng, Rng};
-
-    #[gpui::test]
-    fn test_expand_tabs(cx: &mut gpui::AppContext) {
-        let buffer = MultiBuffer::build_simple("", cx);
-        let buffer_snapshot = buffer.read(cx).snapshot(cx);
-        let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
-        let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
-        let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
-
-        assert_eq!(tab_snapshot.expand_tabs("\t".chars(), 0), 0);
-        assert_eq!(tab_snapshot.expand_tabs("\t".chars(), 1), 4);
-        assert_eq!(tab_snapshot.expand_tabs("\ta".chars(), 2), 5);
-    }
-
-    #[gpui::test]
-    fn test_long_lines(cx: &mut gpui::AppContext) {
-        let max_expansion_column = 12;
-        let input = "A\tBC\tDEF\tG\tHI\tJ\tK\tL\tM";
-        let output = "A   BC  DEF G   HI J K L M";
-
-        let buffer = MultiBuffer::build_simple(input, cx);
-        let buffer_snapshot = buffer.read(cx).snapshot(cx);
-        let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
-        let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
-        let (_, mut tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
-
-        tab_snapshot.max_expansion_column = max_expansion_column;
-        assert_eq!(tab_snapshot.text(), output);
-
-        for (ix, c) in input.char_indices() {
-            assert_eq!(
-                tab_snapshot
-                    .chunks(
-                        TabPoint::new(0, ix as u32)..tab_snapshot.max_point(),
-                        false,
-                        Highlights::default(),
-                    )
-                    .map(|c| c.text)
-                    .collect::<String>(),
-                &output[ix..],
-                "text from index {ix}"
-            );
-
-            if c != '\t' {
-                let input_point = Point::new(0, ix as u32);
-                let output_point = Point::new(0, output.find(c).unwrap() as u32);
-                assert_eq!(
-                    tab_snapshot.to_tab_point(FoldPoint(input_point)),
-                    TabPoint(output_point),
-                    "to_tab_point({input_point:?})"
-                );
-                assert_eq!(
-                    tab_snapshot
-                        .to_fold_point(TabPoint(output_point), Bias::Left)
-                        .0,
-                    FoldPoint(input_point),
-                    "to_fold_point({output_point:?})"
-                );
-            }
-        }
-    }
-
-    #[gpui::test]
-    fn test_long_lines_with_character_spanning_max_expansion_column(cx: &mut gpui::AppContext) {
-        let max_expansion_column = 8;
-        let input = "abcdefg⋯hij";
-
-        let buffer = MultiBuffer::build_simple(input, cx);
-        let buffer_snapshot = buffer.read(cx).snapshot(cx);
-        let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
-        let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
-        let (_, mut tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
-
-        tab_snapshot.max_expansion_column = max_expansion_column;
-        assert_eq!(tab_snapshot.text(), input);
-    }
-
-    #[gpui::test]
-    fn test_marking_tabs(cx: &mut gpui::AppContext) {
-        let input = "\t \thello";
-
-        let buffer = MultiBuffer::build_simple(&input, cx);
-        let buffer_snapshot = buffer.read(cx).snapshot(cx);
-        let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
-        let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
-        let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
-
-        assert_eq!(
-            chunks(&tab_snapshot, TabPoint::zero()),
-            vec![
-                ("    ".to_string(), true),
-                (" ".to_string(), false),
-                ("   ".to_string(), true),
-                ("hello".to_string(), false),
-            ]
-        );
-        assert_eq!(
-            chunks(&tab_snapshot, TabPoint::new(0, 2)),
-            vec![
-                ("  ".to_string(), true),
-                (" ".to_string(), false),
-                ("   ".to_string(), true),
-                ("hello".to_string(), false),
-            ]
-        );
-
-        fn chunks(snapshot: &TabSnapshot, start: TabPoint) -> Vec<(String, bool)> {
-            let mut chunks = Vec::new();
-            let mut was_tab = false;
-            let mut text = String::new();
-            for chunk in snapshot.chunks(start..snapshot.max_point(), false, Highlights::default())
-            {
-                if chunk.is_tab != was_tab {
-                    if !text.is_empty() {
-                        chunks.push((mem::take(&mut text), was_tab));
-                    }
-                    was_tab = chunk.is_tab;
-                }
-                text.push_str(chunk.text);
-            }
-
-            if !text.is_empty() {
-                chunks.push((text, was_tab));
-            }
-            chunks
-        }
-    }
-
-    #[gpui::test(iterations = 100)]
-    fn test_random_tabs(cx: &mut gpui::AppContext, mut rng: StdRng) {
-        let tab_size = NonZeroU32::new(rng.gen_range(1..=4)).unwrap();
-        let len = rng.gen_range(0..30);
-        let buffer = if rng.gen() {
-            let text = util::RandomCharIter::new(&mut rng)
-                .take(len)
-                .collect::<String>();
-            MultiBuffer::build_simple(&text, cx)
-        } else {
-            MultiBuffer::build_random(&mut rng, cx)
-        };
-        let buffer_snapshot = buffer.read(cx).snapshot(cx);
-        log::info!("Buffer text: {:?}", buffer_snapshot.text());
-
-        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
-        log::info!("InlayMap text: {:?}", inlay_snapshot.text());
-        let (mut fold_map, _) = FoldMap::new(inlay_snapshot.clone());
-        fold_map.randomly_mutate(&mut rng);
-        let (fold_snapshot, _) = fold_map.read(inlay_snapshot, vec![]);
-        log::info!("FoldMap text: {:?}", fold_snapshot.text());
-        let (inlay_snapshot, _) = inlay_map.randomly_mutate(&mut 0, &mut rng);
-        log::info!("InlayMap text: {:?}", inlay_snapshot.text());
-
-        let (mut tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size);
-        let tabs_snapshot = tab_map.set_max_expansion_column(32);
-
-        let text = text::Rope::from(tabs_snapshot.text().as_str());
-        log::info!(
-            "TabMap text (tab size: {}): {:?}",
-            tab_size,
-            tabs_snapshot.text(),
-        );
-
-        for _ in 0..5 {
-            let end_row = rng.gen_range(0..=text.max_point().row);
-            let end_column = rng.gen_range(0..=text.line_len(end_row));
-            let mut end = TabPoint(text.clip_point(Point::new(end_row, end_column), Bias::Right));
-            let start_row = rng.gen_range(0..=text.max_point().row);
-            let start_column = rng.gen_range(0..=text.line_len(start_row));
-            let mut start =
-                TabPoint(text.clip_point(Point::new(start_row, start_column), Bias::Left));
-            if start > end {
-                mem::swap(&mut start, &mut end);
-            }
-
-            let expected_text = text
-                .chunks_in_range(text.point_to_offset(start.0)..text.point_to_offset(end.0))
-                .collect::<String>();
-            let expected_summary = TextSummary::from(expected_text.as_str());
-            assert_eq!(
-                tabs_snapshot
-                    .chunks(start..end, false, Highlights::default())
-                    .map(|c| c.text)
-                    .collect::<String>(),
-                expected_text,
-                "chunks({:?}..{:?})",
-                start,
-                end
-            );
-
-            let mut actual_summary = tabs_snapshot.text_summary_for_range(start..end);
-            if tab_size.get() > 1 && inlay_snapshot.text().contains('\t') {
-                actual_summary.longest_row = expected_summary.longest_row;
-                actual_summary.longest_row_chars = expected_summary.longest_row_chars;
-            }
-            assert_eq!(actual_summary, expected_summary);
-        }
-
-        for row in 0..=text.max_point().row {
-            assert_eq!(
-                tabs_snapshot.line_len(row),
-                text.line_len(row),
-                "line_len({row})"
-            );
-        }
-    }
-}

crates/editor2/src/display_map/wrap_map.rs 🔗

@@ -1,1359 +0,0 @@
-use super::{
-    fold_map::FoldBufferRows,
-    tab_map::{self, TabEdit, TabPoint, TabSnapshot},
-    Highlights,
-};
-use crate::MultiBufferSnapshot;
-use gpui::{AppContext, Context, Font, LineWrapper, Model, ModelContext, Pixels, Task};
-use language::{Chunk, Point};
-use lazy_static::lazy_static;
-use smol::future::yield_now;
-use std::{cmp, collections::VecDeque, mem, ops::Range, time::Duration};
-use sum_tree::{Bias, Cursor, SumTree};
-use text::Patch;
-use util::ResultExt;
-
-pub use super::tab_map::TextSummary;
-pub type WrapEdit = text::Edit<u32>;
-
-pub struct WrapMap {
-    snapshot: WrapSnapshot,
-    pending_edits: VecDeque<(TabSnapshot, Vec<TabEdit>)>,
-    interpolated_edits: Patch<u32>,
-    edits_since_sync: Patch<u32>,
-    wrap_width: Option<Pixels>,
-    background_task: Option<Task<()>>,
-    font_with_size: (Font, Pixels),
-}
-
-#[derive(Clone)]
-pub struct WrapSnapshot {
-    tab_snapshot: TabSnapshot,
-    transforms: SumTree<Transform>,
-    interpolated: bool,
-}
-
-#[derive(Clone, Debug, Default, Eq, PartialEq)]
-struct Transform {
-    summary: TransformSummary,
-    display_text: Option<&'static str>,
-}
-
-#[derive(Clone, Debug, Default, Eq, PartialEq)]
-struct TransformSummary {
-    input: TextSummary,
-    output: TextSummary,
-}
-
-#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
-pub struct WrapPoint(pub Point);
-
-pub struct WrapChunks<'a> {
-    input_chunks: tab_map::TabChunks<'a>,
-    input_chunk: Chunk<'a>,
-    output_position: WrapPoint,
-    max_output_row: u32,
-    transforms: Cursor<'a, Transform, (WrapPoint, TabPoint)>,
-}
-
-#[derive(Clone)]
-pub struct WrapBufferRows<'a> {
-    input_buffer_rows: FoldBufferRows<'a>,
-    input_buffer_row: Option<u32>,
-    output_row: u32,
-    soft_wrapped: bool,
-    max_output_row: u32,
-    transforms: Cursor<'a, Transform, (WrapPoint, TabPoint)>,
-}
-
-impl WrapMap {
-    pub fn new(
-        tab_snapshot: TabSnapshot,
-        font: Font,
-        font_size: Pixels,
-        wrap_width: Option<Pixels>,
-        cx: &mut AppContext,
-    ) -> (Model<Self>, WrapSnapshot) {
-        let handle = cx.new_model(|cx| {
-            let mut this = Self {
-                font_with_size: (font, font_size),
-                wrap_width: None,
-                pending_edits: Default::default(),
-                interpolated_edits: Default::default(),
-                edits_since_sync: Default::default(),
-                snapshot: WrapSnapshot::new(tab_snapshot),
-                background_task: None,
-            };
-            this.set_wrap_width(wrap_width, cx);
-            mem::take(&mut this.edits_since_sync);
-            this
-        });
-        let snapshot = handle.read(cx).snapshot.clone();
-        (handle, snapshot)
-    }
-
-    #[cfg(test)]
-    pub fn is_rewrapping(&self) -> bool {
-        self.background_task.is_some()
-    }
-
-    pub fn sync(
-        &mut self,
-        tab_snapshot: TabSnapshot,
-        edits: Vec<TabEdit>,
-        cx: &mut ModelContext<Self>,
-    ) -> (WrapSnapshot, Patch<u32>) {
-        if self.wrap_width.is_some() {
-            self.pending_edits.push_back((tab_snapshot, edits));
-            self.flush_edits(cx);
-        } else {
-            self.edits_since_sync = self
-                .edits_since_sync
-                .compose(&self.snapshot.interpolate(tab_snapshot, &edits));
-            self.snapshot.interpolated = false;
-        }
-
-        (self.snapshot.clone(), mem::take(&mut self.edits_since_sync))
-    }
-
-    pub fn set_font_with_size(
-        &mut self,
-        font: Font,
-        font_size: Pixels,
-        cx: &mut ModelContext<Self>,
-    ) -> bool {
-        let font_with_size = (font, font_size);
-
-        if font_with_size != self.font_with_size {
-            self.font_with_size = font_with_size;
-            self.rewrap(cx);
-            true
-        } else {
-            false
-        }
-    }
-
-    pub fn set_wrap_width(
-        &mut self,
-        wrap_width: Option<Pixels>,
-        cx: &mut ModelContext<Self>,
-    ) -> bool {
-        if wrap_width == self.wrap_width {
-            return false;
-        }
-
-        self.wrap_width = wrap_width;
-        self.rewrap(cx);
-        true
-    }
-
-    fn rewrap(&mut self, cx: &mut ModelContext<Self>) {
-        self.background_task.take();
-        self.interpolated_edits.clear();
-        self.pending_edits.clear();
-
-        if let Some(wrap_width) = self.wrap_width {
-            let mut new_snapshot = self.snapshot.clone();
-            let mut edits = Patch::default();
-            let text_system = cx.text_system().clone();
-            let (font, font_size) = self.font_with_size.clone();
-            let task = cx.background_executor().spawn(async move {
-                if let Some(mut line_wrapper) = text_system.line_wrapper(font, font_size).log_err()
-                {
-                    let tab_snapshot = new_snapshot.tab_snapshot.clone();
-                    let range = TabPoint::zero()..tab_snapshot.max_point();
-                    edits = new_snapshot
-                        .update(
-                            tab_snapshot,
-                            &[TabEdit {
-                                old: range.clone(),
-                                new: range.clone(),
-                            }],
-                            wrap_width,
-                            &mut line_wrapper,
-                        )
-                        .await;
-                }
-                (new_snapshot, edits)
-            });
-
-            match cx
-                .background_executor()
-                .block_with_timeout(Duration::from_millis(5), task)
-            {
-                Ok((snapshot, edits)) => {
-                    self.snapshot = snapshot;
-                    self.edits_since_sync = self.edits_since_sync.compose(&edits);
-                }
-                Err(wrap_task) => {
-                    self.background_task = Some(cx.spawn(|this, mut cx| async move {
-                        let (snapshot, edits) = wrap_task.await;
-                        this.update(&mut cx, |this, cx| {
-                            this.snapshot = snapshot;
-                            this.edits_since_sync = this
-                                .edits_since_sync
-                                .compose(mem::take(&mut this.interpolated_edits).invert())
-                                .compose(&edits);
-                            this.background_task = None;
-                            this.flush_edits(cx);
-                            cx.notify();
-                        })
-                        .ok();
-                    }));
-                }
-            }
-        } else {
-            let old_rows = self.snapshot.transforms.summary().output.lines.row + 1;
-            self.snapshot.transforms = SumTree::new();
-            let summary = self.snapshot.tab_snapshot.text_summary();
-            if !summary.lines.is_zero() {
-                self.snapshot
-                    .transforms
-                    .push(Transform::isomorphic(summary), &());
-            }
-            let new_rows = self.snapshot.transforms.summary().output.lines.row + 1;
-            self.snapshot.interpolated = false;
-            self.edits_since_sync = self.edits_since_sync.compose(&Patch::new(vec![WrapEdit {
-                old: 0..old_rows,
-                new: 0..new_rows,
-            }]));
-        }
-    }
-
-    fn flush_edits(&mut self, cx: &mut ModelContext<Self>) {
-        if !self.snapshot.interpolated {
-            let mut to_remove_len = 0;
-            for (tab_snapshot, _) in &self.pending_edits {
-                if tab_snapshot.version <= self.snapshot.tab_snapshot.version {
-                    to_remove_len += 1;
-                } else {
-                    break;
-                }
-            }
-            self.pending_edits.drain(..to_remove_len);
-        }
-
-        if self.pending_edits.is_empty() {
-            return;
-        }
-
-        if let Some(wrap_width) = self.wrap_width {
-            if self.background_task.is_none() {
-                let pending_edits = self.pending_edits.clone();
-                let mut snapshot = self.snapshot.clone();
-                let text_system = cx.text_system().clone();
-                let (font, font_size) = self.font_with_size.clone();
-                let update_task = cx.background_executor().spawn(async move {
-                    let mut edits = Patch::default();
-                    if let Some(mut line_wrapper) =
-                        text_system.line_wrapper(font, font_size).log_err()
-                    {
-                        for (tab_snapshot, tab_edits) in pending_edits {
-                            let wrap_edits = snapshot
-                                .update(tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper)
-                                .await;
-                            edits = edits.compose(&wrap_edits);
-                        }
-                    }
-                    (snapshot, edits)
-                });
-
-                match cx
-                    .background_executor()
-                    .block_with_timeout(Duration::from_millis(1), update_task)
-                {
-                    Ok((snapshot, output_edits)) => {
-                        self.snapshot = snapshot;
-                        self.edits_since_sync = self.edits_since_sync.compose(&output_edits);
-                    }
-                    Err(update_task) => {
-                        self.background_task = Some(cx.spawn(|this, mut cx| async move {
-                            let (snapshot, edits) = update_task.await;
-                            this.update(&mut cx, |this, cx| {
-                                this.snapshot = snapshot;
-                                this.edits_since_sync = this
-                                    .edits_since_sync
-                                    .compose(mem::take(&mut this.interpolated_edits).invert())
-                                    .compose(&edits);
-                                this.background_task = None;
-                                this.flush_edits(cx);
-                                cx.notify();
-                            })
-                            .ok();
-                        }));
-                    }
-                }
-            }
-        }
-
-        let was_interpolated = self.snapshot.interpolated;
-        let mut to_remove_len = 0;
-        for (tab_snapshot, edits) in &self.pending_edits {
-            if tab_snapshot.version <= self.snapshot.tab_snapshot.version {
-                to_remove_len += 1;
-            } else {
-                let interpolated_edits = self.snapshot.interpolate(tab_snapshot.clone(), edits);
-                self.edits_since_sync = self.edits_since_sync.compose(&interpolated_edits);
-                self.interpolated_edits = self.interpolated_edits.compose(&interpolated_edits);
-            }
-        }
-
-        if !was_interpolated {
-            self.pending_edits.drain(..to_remove_len);
-        }
-    }
-}
-
-impl WrapSnapshot {
-    fn new(tab_snapshot: TabSnapshot) -> Self {
-        let mut transforms = SumTree::new();
-        let extent = tab_snapshot.text_summary();
-        if !extent.lines.is_zero() {
-            transforms.push(Transform::isomorphic(extent), &());
-        }
-        Self {
-            transforms,
-            tab_snapshot,
-            interpolated: true,
-        }
-    }
-
-    pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot {
-        self.tab_snapshot.buffer_snapshot()
-    }
-
-    fn interpolate(&mut self, new_tab_snapshot: TabSnapshot, tab_edits: &[TabEdit]) -> Patch<u32> {
-        let mut new_transforms;
-        if tab_edits.is_empty() {
-            new_transforms = self.transforms.clone();
-        } else {
-            let mut old_cursor = self.transforms.cursor::<TabPoint>();
-
-            let mut tab_edits_iter = tab_edits.iter().peekable();
-            new_transforms =
-                old_cursor.slice(&tab_edits_iter.peek().unwrap().old.start, Bias::Right, &());
-
-            while let Some(edit) = tab_edits_iter.next() {
-                if edit.new.start > TabPoint::from(new_transforms.summary().input.lines) {
-                    let summary = new_tab_snapshot.text_summary_for_range(
-                        TabPoint::from(new_transforms.summary().input.lines)..edit.new.start,
-                    );
-                    new_transforms.push_or_extend(Transform::isomorphic(summary));
-                }
-
-                if !edit.new.is_empty() {
-                    new_transforms.push_or_extend(Transform::isomorphic(
-                        new_tab_snapshot.text_summary_for_range(edit.new.clone()),
-                    ));
-                }
-
-                old_cursor.seek_forward(&edit.old.end, Bias::Right, &());
-                if let Some(next_edit) = tab_edits_iter.peek() {
-                    if next_edit.old.start > old_cursor.end(&()) {
-                        if old_cursor.end(&()) > edit.old.end {
-                            let summary = self
-                                .tab_snapshot
-                                .text_summary_for_range(edit.old.end..old_cursor.end(&()));
-                            new_transforms.push_or_extend(Transform::isomorphic(summary));
-                        }
-
-                        old_cursor.next(&());
-                        new_transforms.append(
-                            old_cursor.slice(&next_edit.old.start, Bias::Right, &()),
-                            &(),
-                        );
-                    }
-                } else {
-                    if old_cursor.end(&()) > edit.old.end {
-                        let summary = self
-                            .tab_snapshot
-                            .text_summary_for_range(edit.old.end..old_cursor.end(&()));
-                        new_transforms.push_or_extend(Transform::isomorphic(summary));
-                    }
-                    old_cursor.next(&());
-                    new_transforms.append(old_cursor.suffix(&()), &());
-                }
-            }
-        }
-
-        let old_snapshot = mem::replace(
-            self,
-            WrapSnapshot {
-                tab_snapshot: new_tab_snapshot,
-                transforms: new_transforms,
-                interpolated: true,
-            },
-        );
-        self.check_invariants();
-        old_snapshot.compute_edits(tab_edits, self)
-    }
-
-    async fn update(
-        &mut self,
-        new_tab_snapshot: TabSnapshot,
-        tab_edits: &[TabEdit],
-        wrap_width: Pixels,
-        line_wrapper: &mut LineWrapper,
-    ) -> Patch<u32> {
-        #[derive(Debug)]
-        struct RowEdit {
-            old_rows: Range<u32>,
-            new_rows: Range<u32>,
-        }
-
-        let mut tab_edits_iter = tab_edits.iter().peekable();
-        let mut row_edits = Vec::new();
-        while let Some(edit) = tab_edits_iter.next() {
-            let mut row_edit = RowEdit {
-                old_rows: edit.old.start.row()..edit.old.end.row() + 1,
-                new_rows: edit.new.start.row()..edit.new.end.row() + 1,
-            };
-
-            while let Some(next_edit) = tab_edits_iter.peek() {
-                if next_edit.old.start.row() <= row_edit.old_rows.end {
-                    row_edit.old_rows.end = next_edit.old.end.row() + 1;
-                    row_edit.new_rows.end = next_edit.new.end.row() + 1;
-                    tab_edits_iter.next();
-                } else {
-                    break;
-                }
-            }
-
-            row_edits.push(row_edit);
-        }
-
-        let mut new_transforms;
-        if row_edits.is_empty() {
-            new_transforms = self.transforms.clone();
-        } else {
-            let mut row_edits = row_edits.into_iter().peekable();
-            let mut old_cursor = self.transforms.cursor::<TabPoint>();
-
-            new_transforms = old_cursor.slice(
-                &TabPoint::new(row_edits.peek().unwrap().old_rows.start, 0),
-                Bias::Right,
-                &(),
-            );
-
-            while let Some(edit) = row_edits.next() {
-                if edit.new_rows.start > new_transforms.summary().input.lines.row {
-                    let summary = new_tab_snapshot.text_summary_for_range(
-                        TabPoint(new_transforms.summary().input.lines)
-                            ..TabPoint::new(edit.new_rows.start, 0),
-                    );
-                    new_transforms.push_or_extend(Transform::isomorphic(summary));
-                }
-
-                let mut line = String::new();
-                let mut remaining = None;
-                let mut chunks = new_tab_snapshot.chunks(
-                    TabPoint::new(edit.new_rows.start, 0)..new_tab_snapshot.max_point(),
-                    false,
-                    Highlights::default(),
-                );
-                let mut edit_transforms = Vec::<Transform>::new();
-                for _ in edit.new_rows.start..edit.new_rows.end {
-                    while let Some(chunk) =
-                        remaining.take().or_else(|| chunks.next().map(|c| c.text))
-                    {
-                        if let Some(ix) = chunk.find('\n') {
-                            line.push_str(&chunk[..ix + 1]);
-                            remaining = Some(&chunk[ix + 1..]);
-                            break;
-                        } else {
-                            line.push_str(chunk)
-                        }
-                    }
-
-                    if line.is_empty() {
-                        break;
-                    }
-
-                    let mut prev_boundary_ix = 0;
-                    for boundary in line_wrapper.wrap_line(&line, wrap_width) {
-                        let wrapped = &line[prev_boundary_ix..boundary.ix];
-                        push_isomorphic(&mut edit_transforms, TextSummary::from(wrapped));
-                        edit_transforms.push(Transform::wrap(boundary.next_indent));
-                        prev_boundary_ix = boundary.ix;
-                    }
-
-                    if prev_boundary_ix < line.len() {
-                        push_isomorphic(
-                            &mut edit_transforms,
-                            TextSummary::from(&line[prev_boundary_ix..]),
-                        );
-                    }
-
-                    line.clear();
-                    yield_now().await;
-                }
-
-                let mut edit_transforms = edit_transforms.into_iter();
-                if let Some(transform) = edit_transforms.next() {
-                    new_transforms.push_or_extend(transform);
-                }
-                new_transforms.extend(edit_transforms, &());
-
-                old_cursor.seek_forward(&TabPoint::new(edit.old_rows.end, 0), Bias::Right, &());
-                if let Some(next_edit) = row_edits.peek() {
-                    if next_edit.old_rows.start > old_cursor.end(&()).row() {
-                        if old_cursor.end(&()) > TabPoint::new(edit.old_rows.end, 0) {
-                            let summary = self.tab_snapshot.text_summary_for_range(
-                                TabPoint::new(edit.old_rows.end, 0)..old_cursor.end(&()),
-                            );
-                            new_transforms.push_or_extend(Transform::isomorphic(summary));
-                        }
-                        old_cursor.next(&());
-                        new_transforms.append(
-                            old_cursor.slice(
-                                &TabPoint::new(next_edit.old_rows.start, 0),
-                                Bias::Right,
-                                &(),
-                            ),
-                            &(),
-                        );
-                    }
-                } else {
-                    if old_cursor.end(&()) > TabPoint::new(edit.old_rows.end, 0) {
-                        let summary = self.tab_snapshot.text_summary_for_range(
-                            TabPoint::new(edit.old_rows.end, 0)..old_cursor.end(&()),
-                        );
-                        new_transforms.push_or_extend(Transform::isomorphic(summary));
-                    }
-                    old_cursor.next(&());
-                    new_transforms.append(old_cursor.suffix(&()), &());
-                }
-            }
-        }
-
-        let old_snapshot = mem::replace(
-            self,
-            WrapSnapshot {
-                tab_snapshot: new_tab_snapshot,
-                transforms: new_transforms,
-                interpolated: false,
-            },
-        );
-        self.check_invariants();
-        old_snapshot.compute_edits(tab_edits, self)
-    }
-
-    fn compute_edits(&self, tab_edits: &[TabEdit], new_snapshot: &WrapSnapshot) -> Patch<u32> {
-        let mut wrap_edits = Vec::new();
-        let mut old_cursor = self.transforms.cursor::<TransformSummary>();
-        let mut new_cursor = new_snapshot.transforms.cursor::<TransformSummary>();
-        for mut tab_edit in tab_edits.iter().cloned() {
-            tab_edit.old.start.0.column = 0;
-            tab_edit.old.end.0 += Point::new(1, 0);
-            tab_edit.new.start.0.column = 0;
-            tab_edit.new.end.0 += Point::new(1, 0);
-
-            old_cursor.seek(&tab_edit.old.start, Bias::Right, &());
-            let mut old_start = old_cursor.start().output.lines;
-            old_start += tab_edit.old.start.0 - old_cursor.start().input.lines;
-
-            old_cursor.seek(&tab_edit.old.end, Bias::Right, &());
-            let mut old_end = old_cursor.start().output.lines;
-            old_end += tab_edit.old.end.0 - old_cursor.start().input.lines;
-
-            new_cursor.seek(&tab_edit.new.start, Bias::Right, &());
-            let mut new_start = new_cursor.start().output.lines;
-            new_start += tab_edit.new.start.0 - new_cursor.start().input.lines;
-
-            new_cursor.seek(&tab_edit.new.end, Bias::Right, &());
-            let mut new_end = new_cursor.start().output.lines;
-            new_end += tab_edit.new.end.0 - new_cursor.start().input.lines;
-
-            wrap_edits.push(WrapEdit {
-                old: old_start.row..old_end.row,
-                new: new_start.row..new_end.row,
-            });
-        }
-
-        consolidate_wrap_edits(&mut wrap_edits);
-        Patch::new(wrap_edits)
-    }
-
-    pub fn chunks<'a>(
-        &'a self,
-        rows: Range<u32>,
-        language_aware: bool,
-        highlights: Highlights<'a>,
-    ) -> WrapChunks<'a> {
-        let output_start = WrapPoint::new(rows.start, 0);
-        let output_end = WrapPoint::new(rows.end, 0);
-        let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>();
-        transforms.seek(&output_start, Bias::Right, &());
-        let mut input_start = TabPoint(transforms.start().1 .0);
-        if transforms.item().map_or(false, |t| t.is_isomorphic()) {
-            input_start.0 += output_start.0 - transforms.start().0 .0;
-        }
-        let input_end = self
-            .to_tab_point(output_end)
-            .min(self.tab_snapshot.max_point());
-        WrapChunks {
-            input_chunks: self.tab_snapshot.chunks(
-                input_start..input_end,
-                language_aware,
-                highlights,
-            ),
-            input_chunk: Default::default(),
-            output_position: output_start,
-            max_output_row: rows.end,
-            transforms,
-        }
-    }
-
-    pub fn max_point(&self) -> WrapPoint {
-        WrapPoint(self.transforms.summary().output.lines)
-    }
-
-    pub fn line_len(&self, row: u32) -> u32 {
-        let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>();
-        cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Left, &());
-        if cursor
-            .item()
-            .map_or(false, |transform| transform.is_isomorphic())
-        {
-            let overshoot = row - cursor.start().0.row();
-            let tab_row = cursor.start().1.row() + overshoot;
-            let tab_line_len = self.tab_snapshot.line_len(tab_row);
-            if overshoot == 0 {
-                cursor.start().0.column() + (tab_line_len - cursor.start().1.column())
-            } else {
-                tab_line_len
-            }
-        } else {
-            cursor.start().0.column()
-        }
-    }
-
-    pub fn soft_wrap_indent(&self, row: u32) -> Option<u32> {
-        let mut cursor = self.transforms.cursor::<WrapPoint>();
-        cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Right, &());
-        cursor.item().and_then(|transform| {
-            if transform.is_isomorphic() {
-                None
-            } else {
-                Some(transform.summary.output.lines.column)
-            }
-        })
-    }
-
-    pub fn longest_row(&self) -> u32 {
-        self.transforms.summary().output.longest_row
-    }
-
-    pub fn buffer_rows(&self, start_row: u32) -> WrapBufferRows {
-        let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>();
-        transforms.seek(&WrapPoint::new(start_row, 0), Bias::Left, &());
-        let mut input_row = transforms.start().1.row();
-        if transforms.item().map_or(false, |t| t.is_isomorphic()) {
-            input_row += start_row - transforms.start().0.row();
-        }
-        let soft_wrapped = transforms.item().map_or(false, |t| !t.is_isomorphic());
-        let mut input_buffer_rows = self.tab_snapshot.buffer_rows(input_row);
-        let input_buffer_row = input_buffer_rows.next().unwrap();
-        WrapBufferRows {
-            transforms,
-            input_buffer_row,
-            input_buffer_rows,
-            output_row: start_row,
-            soft_wrapped,
-            max_output_row: self.max_point().row(),
-        }
-    }
-
-    pub fn to_tab_point(&self, point: WrapPoint) -> TabPoint {
-        let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>();
-        cursor.seek(&point, Bias::Right, &());
-        let mut tab_point = cursor.start().1 .0;
-        if cursor.item().map_or(false, |t| t.is_isomorphic()) {
-            tab_point += point.0 - cursor.start().0 .0;
-        }
-        TabPoint(tab_point)
-    }
-
-    pub fn to_point(&self, point: WrapPoint, bias: Bias) -> Point {
-        self.tab_snapshot.to_point(self.to_tab_point(point), bias)
-    }
-
-    pub fn make_wrap_point(&self, point: Point, bias: Bias) -> WrapPoint {
-        self.tab_point_to_wrap_point(self.tab_snapshot.make_tab_point(point, bias))
-    }
-
-    pub fn tab_point_to_wrap_point(&self, point: TabPoint) -> WrapPoint {
-        let mut cursor = self.transforms.cursor::<(TabPoint, WrapPoint)>();
-        cursor.seek(&point, Bias::Right, &());
-        WrapPoint(cursor.start().1 .0 + (point.0 - cursor.start().0 .0))
-    }
-
-    pub fn clip_point(&self, mut point: WrapPoint, bias: Bias) -> WrapPoint {
-        if bias == Bias::Left {
-            let mut cursor = self.transforms.cursor::<WrapPoint>();
-            cursor.seek(&point, Bias::Right, &());
-            if cursor.item().map_or(false, |t| !t.is_isomorphic()) {
-                point = *cursor.start();
-                *point.column_mut() -= 1;
-            }
-        }
-
-        self.tab_point_to_wrap_point(self.tab_snapshot.clip_point(self.to_tab_point(point), bias))
-    }
-
-    pub fn prev_row_boundary(&self, mut point: WrapPoint) -> u32 {
-        if self.transforms.is_empty() {
-            return 0;
-        }
-
-        *point.column_mut() = 0;
-
-        let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>();
-        cursor.seek(&point, Bias::Right, &());
-        if cursor.item().is_none() {
-            cursor.prev(&());
-        }
-
-        while let Some(transform) = cursor.item() {
-            if transform.is_isomorphic() && cursor.start().1.column() == 0 {
-                return cmp::min(cursor.end(&()).0.row(), point.row());
-            } else {
-                cursor.prev(&());
-            }
-        }
-
-        unreachable!()
-    }
-
-    pub fn next_row_boundary(&self, mut point: WrapPoint) -> Option<u32> {
-        point.0 += Point::new(1, 0);
-
-        let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>();
-        cursor.seek(&point, Bias::Right, &());
-        while let Some(transform) = cursor.item() {
-            if transform.is_isomorphic() && cursor.start().1.column() == 0 {
-                return Some(cmp::max(cursor.start().0.row(), point.row()));
-            } else {
-                cursor.next(&());
-            }
-        }
-
-        None
-    }
-
-    fn check_invariants(&self) {
-        #[cfg(test)]
-        {
-            assert_eq!(
-                TabPoint::from(self.transforms.summary().input.lines),
-                self.tab_snapshot.max_point()
-            );
-
-            {
-                let mut transforms = self.transforms.cursor::<()>().peekable();
-                while let Some(transform) = transforms.next() {
-                    if let Some(next_transform) = transforms.peek() {
-                        assert!(transform.is_isomorphic() != next_transform.is_isomorphic());
-                    }
-                }
-            }
-
-            let text = language::Rope::from(self.text().as_str());
-            let mut input_buffer_rows = self.tab_snapshot.buffer_rows(0);
-            let mut expected_buffer_rows = Vec::new();
-            let mut prev_tab_row = 0;
-            for display_row in 0..=self.max_point().row() {
-                let tab_point = self.to_tab_point(WrapPoint::new(display_row, 0));
-                if tab_point.row() == prev_tab_row && display_row != 0 {
-                    expected_buffer_rows.push(None);
-                } else {
-                    expected_buffer_rows.push(input_buffer_rows.next().unwrap());
-                }
-
-                prev_tab_row = tab_point.row();
-                assert_eq!(self.line_len(display_row), text.line_len(display_row));
-            }
-
-            for start_display_row in 0..expected_buffer_rows.len() {
-                assert_eq!(
-                    self.buffer_rows(start_display_row as u32)
-                        .collect::<Vec<_>>(),
-                    &expected_buffer_rows[start_display_row..],
-                    "invalid buffer_rows({}..)",
-                    start_display_row
-                );
-            }
-        }
-    }
-}
-
-impl<'a> Iterator for WrapChunks<'a> {
-    type Item = Chunk<'a>;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if self.output_position.row() >= self.max_output_row {
-            return None;
-        }
-
-        let transform = self.transforms.item()?;
-        if let Some(display_text) = transform.display_text {
-            let mut start_ix = 0;
-            let mut end_ix = display_text.len();
-            let mut summary = transform.summary.output.lines;
-
-            if self.output_position > self.transforms.start().0 {
-                // Exclude newline starting prior to the desired row.
-                start_ix = 1;
-                summary.row = 0;
-            } else if self.output_position.row() + 1 >= self.max_output_row {
-                // Exclude soft indentation ending after the desired row.
-                end_ix = 1;
-                summary.column = 0;
-            }
-
-            self.output_position.0 += summary;
-            self.transforms.next(&());
-            return Some(Chunk {
-                text: &display_text[start_ix..end_ix],
-                ..self.input_chunk
-            });
-        }
-
-        if self.input_chunk.text.is_empty() {
-            self.input_chunk = self.input_chunks.next().unwrap();
-        }
-
-        let mut input_len = 0;
-        let transform_end = self.transforms.end(&()).0;
-        for c in self.input_chunk.text.chars() {
-            let char_len = c.len_utf8();
-            input_len += char_len;
-            if c == '\n' {
-                *self.output_position.row_mut() += 1;
-                *self.output_position.column_mut() = 0;
-            } else {
-                *self.output_position.column_mut() += char_len as u32;
-            }
-
-            if self.output_position >= transform_end {
-                self.transforms.next(&());
-                break;
-            }
-        }
-
-        let (prefix, suffix) = self.input_chunk.text.split_at(input_len);
-        self.input_chunk.text = suffix;
-        Some(Chunk {
-            text: prefix,
-            ..self.input_chunk
-        })
-    }
-}
-
-impl<'a> Iterator for WrapBufferRows<'a> {
-    type Item = Option<u32>;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if self.output_row > self.max_output_row {
-            return None;
-        }
-
-        let buffer_row = self.input_buffer_row;
-        let soft_wrapped = self.soft_wrapped;
-
-        self.output_row += 1;
-        self.transforms
-            .seek_forward(&WrapPoint::new(self.output_row, 0), Bias::Left, &());
-        if self.transforms.item().map_or(false, |t| t.is_isomorphic()) {
-            self.input_buffer_row = self.input_buffer_rows.next().unwrap();
-            self.soft_wrapped = false;
-        } else {
-            self.soft_wrapped = true;
-        }
-
-        Some(if soft_wrapped { None } else { buffer_row })
-    }
-}
-
-impl Transform {
-    fn isomorphic(summary: TextSummary) -> Self {
-        #[cfg(test)]
-        assert!(!summary.lines.is_zero());
-
-        Self {
-            summary: TransformSummary {
-                input: summary.clone(),
-                output: summary,
-            },
-            display_text: None,
-        }
-    }
-
-    fn wrap(indent: u32) -> Self {
-        lazy_static! {
-            static ref WRAP_TEXT: String = {
-                let mut wrap_text = String::new();
-                wrap_text.push('\n');
-                wrap_text.extend((0..LineWrapper::MAX_INDENT as usize).map(|_| ' '));
-                wrap_text
-            };
-        }
-
-        Self {
-            summary: TransformSummary {
-                input: TextSummary::default(),
-                output: TextSummary {
-                    lines: Point::new(1, indent),
-                    first_line_chars: 0,
-                    last_line_chars: indent,
-                    longest_row: 1,
-                    longest_row_chars: indent,
-                },
-            },
-            display_text: Some(&WRAP_TEXT[..1 + indent as usize]),
-        }
-    }
-
-    fn is_isomorphic(&self) -> bool {
-        self.display_text.is_none()
-    }
-}
-
-impl sum_tree::Item for Transform {
-    type Summary = TransformSummary;
-
-    fn summary(&self) -> Self::Summary {
-        self.summary.clone()
-    }
-}
-
-fn push_isomorphic(transforms: &mut Vec<Transform>, summary: TextSummary) {
-    if let Some(last_transform) = transforms.last_mut() {
-        if last_transform.is_isomorphic() {
-            last_transform.summary.input += &summary;
-            last_transform.summary.output += &summary;
-            return;
-        }
-    }
-    transforms.push(Transform::isomorphic(summary));
-}
-
-trait SumTreeExt {
-    fn push_or_extend(&mut self, transform: Transform);
-}
-
-impl SumTreeExt for SumTree<Transform> {
-    fn push_or_extend(&mut self, transform: Transform) {
-        let mut transform = Some(transform);
-        self.update_last(
-            |last_transform| {
-                if last_transform.is_isomorphic() && transform.as_ref().unwrap().is_isomorphic() {
-                    let transform = transform.take().unwrap();
-                    last_transform.summary.input += &transform.summary.input;
-                    last_transform.summary.output += &transform.summary.output;
-                }
-            },
-            &(),
-        );
-
-        if let Some(transform) = transform {
-            self.push(transform, &());
-        }
-    }
-}
-
-impl WrapPoint {
-    pub fn new(row: u32, column: u32) -> Self {
-        Self(Point::new(row, column))
-    }
-
-    pub fn row(self) -> u32 {
-        self.0.row
-    }
-
-    pub fn row_mut(&mut self) -> &mut u32 {
-        &mut self.0.row
-    }
-
-    pub fn column(self) -> u32 {
-        self.0.column
-    }
-
-    pub fn column_mut(&mut self) -> &mut u32 {
-        &mut self.0.column
-    }
-}
-
-impl sum_tree::Summary for TransformSummary {
-    type Context = ();
-
-    fn add_summary(&mut self, other: &Self, _: &()) {
-        self.input += &other.input;
-        self.output += &other.output;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, TransformSummary> for TabPoint {
-    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
-        self.0 += summary.input.lines;
-    }
-}
-
-impl<'a> sum_tree::SeekTarget<'a, TransformSummary, TransformSummary> for TabPoint {
-    fn cmp(&self, cursor_location: &TransformSummary, _: &()) -> std::cmp::Ordering {
-        Ord::cmp(&self.0, &cursor_location.input.lines)
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapPoint {
-    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
-        self.0 += summary.output.lines;
-    }
-}
-
-fn consolidate_wrap_edits(edits: &mut Vec<WrapEdit>) {
-    let mut i = 1;
-    while i < edits.len() {
-        let edit = edits[i].clone();
-        let prev_edit = &mut edits[i - 1];
-        if prev_edit.old.end >= edit.old.start {
-            prev_edit.old.end = edit.old.end;
-            prev_edit.new.end = edit.new.end;
-            edits.remove(i);
-            continue;
-        }
-        i += 1;
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::{
-        display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap},
-        MultiBuffer,
-    };
-    use gpui::{font, px, test::observe};
-    use rand::prelude::*;
-    use settings::SettingsStore;
-    use smol::stream::StreamExt;
-    use std::{cmp, env, num::NonZeroU32};
-    use text::Rope;
-    use theme::LoadThemes;
-
-    #[gpui::test(iterations = 100)]
-    async fn test_random_wraps(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
-        // todo!() this test is flaky
-        init_test(cx);
-
-        cx.background_executor.set_block_on_ticks(0..=50);
-        let operations = env::var("OPERATIONS")
-            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
-            .unwrap_or(10);
-
-        let text_system = cx.read(|cx| cx.text_system().clone());
-        let mut wrap_width = if rng.gen_bool(0.1) {
-            None
-        } else {
-            Some(px(rng.gen_range(0.0..=1000.0)))
-        };
-        let tab_size = NonZeroU32::new(rng.gen_range(1..=4)).unwrap();
-        let font = font("Helvetica");
-        let _font_id = text_system.font_id(&font).unwrap();
-        let font_size = px(14.0);
-
-        log::info!("Tab size: {}", tab_size);
-        log::info!("Wrap width: {:?}", wrap_width);
-
-        let buffer = cx.update(|cx| {
-            if rng.gen() {
-                MultiBuffer::build_random(&mut rng, cx)
-            } else {
-                let len = rng.gen_range(0..10);
-                let text = util::RandomCharIter::new(&mut rng)
-                    .take(len)
-                    .collect::<String>();
-                MultiBuffer::build_simple(&text, cx)
-            }
-        });
-        let mut buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
-        log::info!("Buffer text: {:?}", buffer_snapshot.text());
-        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
-        log::info!("InlayMap text: {:?}", inlay_snapshot.text());
-        let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot.clone());
-        log::info!("FoldMap text: {:?}", fold_snapshot.text());
-        let (mut tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size);
-        let tabs_snapshot = tab_map.set_max_expansion_column(32);
-        log::info!("TabMap text: {:?}", tabs_snapshot.text());
-
-        let mut line_wrapper = text_system.line_wrapper(font.clone(), font_size).unwrap();
-        let unwrapped_text = tabs_snapshot.text();
-        let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper);
-
-        let (wrap_map, _) =
-            cx.update(|cx| WrapMap::new(tabs_snapshot.clone(), font, font_size, wrap_width, cx));
-        let mut notifications = observe(&wrap_map, cx);
-
-        if wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
-            notifications.next().await.unwrap();
-        }
-
-        let (initial_snapshot, _) = wrap_map.update(cx, |map, cx| {
-            assert!(!map.is_rewrapping());
-            map.sync(tabs_snapshot.clone(), Vec::new(), cx)
-        });
-
-        let actual_text = initial_snapshot.text();
-        assert_eq!(
-            actual_text, expected_text,
-            "unwrapped text is: {:?}",
-            unwrapped_text
-        );
-        log::info!("Wrapped text: {:?}", actual_text);
-
-        let mut next_inlay_id = 0;
-        let mut edits = Vec::new();
-        for _i in 0..operations {
-            log::info!("{} ==============================================", _i);
-
-            let mut buffer_edits = Vec::new();
-            match rng.gen_range(0..=100) {
-                0..=19 => {
-                    wrap_width = if rng.gen_bool(0.2) {
-                        None
-                    } else {
-                        Some(px(rng.gen_range(0.0..=1000.0)))
-                    };
-                    log::info!("Setting wrap width to {:?}", wrap_width);
-                    wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
-                }
-                20..=39 => {
-                    for (fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) {
-                        let (tabs_snapshot, tab_edits) =
-                            tab_map.sync(fold_snapshot, fold_edits, tab_size);
-                        let (mut snapshot, wrap_edits) =
-                            wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx));
-                        snapshot.check_invariants();
-                        snapshot.verify_chunks(&mut rng);
-                        edits.push((snapshot, wrap_edits));
-                    }
-                }
-                40..=59 => {
-                    let (inlay_snapshot, inlay_edits) =
-                        inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
-                    let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
-                    let (tabs_snapshot, tab_edits) =
-                        tab_map.sync(fold_snapshot, fold_edits, tab_size);
-                    let (mut snapshot, wrap_edits) =
-                        wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx));
-                    snapshot.check_invariants();
-                    snapshot.verify_chunks(&mut rng);
-                    edits.push((snapshot, wrap_edits));
-                }
-                _ => {
-                    buffer.update(cx, |buffer, cx| {
-                        let subscription = buffer.subscribe();
-                        let edit_count = rng.gen_range(1..=5);
-                        buffer.randomly_mutate(&mut rng, edit_count, cx);
-                        buffer_snapshot = buffer.snapshot(cx);
-                        buffer_edits.extend(subscription.consume());
-                    });
-                }
-            }
-
-            log::info!("Buffer text: {:?}", buffer_snapshot.text());
-            let (inlay_snapshot, inlay_edits) =
-                inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
-            log::info!("InlayMap text: {:?}", inlay_snapshot.text());
-            let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
-            log::info!("FoldMap text: {:?}", fold_snapshot.text());
-            let (tabs_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
-            log::info!("TabMap text: {:?}", tabs_snapshot.text());
-
-            let unwrapped_text = tabs_snapshot.text();
-            let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper);
-            let (mut snapshot, wrap_edits) =
-                wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot.clone(), tab_edits, cx));
-            snapshot.check_invariants();
-            snapshot.verify_chunks(&mut rng);
-            edits.push((snapshot, wrap_edits));
-
-            if wrap_map.read_with(cx, |map, _| map.is_rewrapping()) && rng.gen_bool(0.4) {
-                log::info!("Waiting for wrapping to finish");
-                while wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
-                    notifications.next().await.unwrap();
-                }
-                wrap_map.read_with(cx, |map, _| assert!(map.pending_edits.is_empty()));
-            }
-
-            if !wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
-                let (mut wrapped_snapshot, wrap_edits) =
-                    wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, Vec::new(), cx));
-                let actual_text = wrapped_snapshot.text();
-                let actual_longest_row = wrapped_snapshot.longest_row();
-                log::info!("Wrapping finished: {:?}", actual_text);
-                wrapped_snapshot.check_invariants();
-                wrapped_snapshot.verify_chunks(&mut rng);
-                edits.push((wrapped_snapshot.clone(), wrap_edits));
-                assert_eq!(
-                    actual_text, expected_text,
-                    "unwrapped text is: {:?}",
-                    unwrapped_text
-                );
-
-                let mut summary = TextSummary::default();
-                for (ix, item) in wrapped_snapshot
-                    .transforms
-                    .items(&())
-                    .into_iter()
-                    .enumerate()
-                {
-                    summary += &item.summary.output;
-                    log::info!("{} summary: {:?}", ix, item.summary.output,);
-                }
-
-                if tab_size.get() == 1
-                    || !wrapped_snapshot
-                        .tab_snapshot
-                        .fold_snapshot
-                        .text()
-                        .contains('\t')
-                {
-                    let mut expected_longest_rows = Vec::new();
-                    let mut longest_line_len = -1;
-                    for (row, line) in expected_text.split('\n').enumerate() {
-                        let line_char_count = line.chars().count() as isize;
-                        if line_char_count > longest_line_len {
-                            expected_longest_rows.clear();
-                            longest_line_len = line_char_count;
-                        }
-                        if line_char_count >= longest_line_len {
-                            expected_longest_rows.push(row as u32);
-                        }
-                    }
-
-                    assert!(
-                        expected_longest_rows.contains(&actual_longest_row),
-                        "incorrect longest row {}. expected {:?} with length {}",
-                        actual_longest_row,
-                        expected_longest_rows,
-                        longest_line_len,
-                    )
-                }
-            }
-        }
-
-        let mut initial_text = Rope::from(initial_snapshot.text().as_str());
-        for (snapshot, patch) in edits {
-            let snapshot_text = Rope::from(snapshot.text().as_str());
-            for edit in &patch {
-                let old_start = initial_text.point_to_offset(Point::new(edit.new.start, 0));
-                let old_end = initial_text.point_to_offset(cmp::min(
-                    Point::new(edit.new.start + edit.old.len() as u32, 0),
-                    initial_text.max_point(),
-                ));
-                let new_start = snapshot_text.point_to_offset(Point::new(edit.new.start, 0));
-                let new_end = snapshot_text.point_to_offset(cmp::min(
-                    Point::new(edit.new.end, 0),
-                    snapshot_text.max_point(),
-                ));
-                let new_text = snapshot_text
-                    .chunks_in_range(new_start..new_end)
-                    .collect::<String>();
-
-                initial_text.replace(old_start..old_end, &new_text);
-            }
-            assert_eq!(initial_text.to_string(), snapshot_text.to_string());
-        }
-
-        if wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
-            log::info!("Waiting for wrapping to finish");
-            while wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
-                notifications.next().await.unwrap();
-            }
-        }
-        wrap_map.read_with(cx, |map, _| assert!(map.pending_edits.is_empty()));
-    }
-
-    fn init_test(cx: &mut gpui::TestAppContext) {
-        cx.update(|cx| {
-            let settings = SettingsStore::test(cx);
-            cx.set_global(settings);
-            theme::init(LoadThemes::JustBase, cx);
-        });
-    }
-
-    fn wrap_text(
-        unwrapped_text: &str,
-        wrap_width: Option<Pixels>,
-        line_wrapper: &mut LineWrapper,
-    ) -> String {
-        if let Some(wrap_width) = wrap_width {
-            let mut wrapped_text = String::new();
-            for (row, line) in unwrapped_text.split('\n').enumerate() {
-                if row > 0 {
-                    wrapped_text.push('\n')
-                }
-
-                let mut prev_ix = 0;
-                for boundary in line_wrapper.wrap_line(line, wrap_width) {
-                    wrapped_text.push_str(&line[prev_ix..boundary.ix]);
-                    wrapped_text.push('\n');
-                    wrapped_text.push_str(&" ".repeat(boundary.next_indent as usize));
-                    prev_ix = boundary.ix;
-                }
-                wrapped_text.push_str(&line[prev_ix..]);
-            }
-            wrapped_text
-        } else {
-            unwrapped_text.to_string()
-        }
-    }
-
-    impl WrapSnapshot {
-        pub fn text(&self) -> String {
-            self.text_chunks(0).collect()
-        }
-
-        pub fn text_chunks(&self, wrap_row: u32) -> impl Iterator<Item = &str> {
-            self.chunks(
-                wrap_row..self.max_point().row() + 1,
-                false,
-                Highlights::default(),
-            )
-            .map(|h| h.text)
-        }
-
-        fn verify_chunks(&mut self, rng: &mut impl Rng) {
-            for _ in 0..5 {
-                let mut end_row = rng.gen_range(0..=self.max_point().row());
-                let start_row = rng.gen_range(0..=end_row);
-                end_row += 1;
-
-                let mut expected_text = self.text_chunks(start_row).collect::<String>();
-                if expected_text.ends_with('\n') {
-                    expected_text.push('\n');
-                }
-                let mut expected_text = expected_text
-                    .lines()
-                    .take((end_row - start_row) as usize)
-                    .collect::<Vec<_>>()
-                    .join("\n");
-                if end_row <= self.max_point().row() {
-                    expected_text.push('\n');
-                }
-
-                let actual_text = self
-                    .chunks(start_row..end_row, true, Highlights::default())
-                    .map(|c| c.text)
-                    .collect::<String>();
-                assert_eq!(
-                    expected_text,
-                    actual_text,
-                    "chunks != highlighted_chunks for rows {:?}",
-                    start_row..end_row
-                );
-            }
-        }
-    }
-}

crates/editor2/src/editor.rs 🔗

@@ -1,9884 +0,0 @@
-mod blink_manager;
-pub mod display_map;
-mod editor_settings;
-mod element;
-mod inlay_hint_cache;
-
-mod git;
-mod highlight_matching_bracket;
-mod hover_popover;
-pub mod items;
-mod link_go_to_definition;
-mod mouse_context_menu;
-pub mod movement;
-mod persistence;
-mod rust_analyzer_ext;
-pub mod scroll;
-pub mod selections_collection;
-
-#[cfg(test)]
-mod editor_tests;
-#[cfg(any(test, feature = "test-support"))]
-pub mod test;
-use ::git::diff::DiffHunk;
-use aho_corasick::AhoCorasick;
-use anyhow::{anyhow, Context as _, Result};
-use blink_manager::BlinkManager;
-use client::{Client, Collaborator, ParticipantIndex, TelemetrySettings};
-use clock::ReplicaId;
-use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
-use convert_case::{Case, Casing};
-use copilot::Copilot;
-pub use display_map::DisplayPoint;
-use display_map::*;
-pub use editor_settings::EditorSettings;
-pub use element::{
-    Cursor, EditorElement, HighlightedRange, HighlightedRangeLine, LineWithInvisibles,
-};
-use futures::FutureExt;
-use fuzzy::{StringMatch, StringMatchCandidate};
-use git::diff_hunk_to_display;
-use gpui::{
-    actions, div, impl_actions, point, prelude::*, px, relative, rems, size, uniform_list, Action,
-    AnyElement, AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Context,
-    DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight,
-    HighlightStyle, Hsla, InputHandler, InteractiveText, KeyContext, Model, MouseButton,
-    ParentElement, Pixels, Render, SharedString, Styled, StyledText, Subscription, Task, TextStyle,
-    UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WhiteSpace, WindowContext,
-};
-use highlight_matching_bracket::refresh_matching_bracket_highlights;
-use hover_popover::{hide_hover, HoverState};
-use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
-pub use items::MAX_TAB_TITLE_LEN;
-use itertools::Itertools;
-pub use language::{char_kind, CharKind};
-use language::{
-    language_settings::{self, all_language_settings, InlayHintSettings},
-    markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel,
-    Completion, CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language,
-    LanguageRegistry, LanguageServerName, OffsetRangeExt, Point, Selection, SelectionGoal,
-    TransactionId,
-};
-
-use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState};
-use lsp::{DiagnosticSeverity, LanguageServerId};
-use mouse_context_menu::MouseContextMenu;
-use movement::TextLayoutDetails;
-use multi_buffer::ToOffsetUtf16;
-pub use multi_buffer::{
-    Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset,
-    ToPoint,
-};
-use ordered_float::OrderedFloat;
-use parking_lot::RwLock;
-use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction};
-use rand::prelude::*;
-use rpc::proto::{self, *};
-use scroll::{
-    autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide,
-};
-use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
-use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsStore};
-use smallvec::SmallVec;
-use snippet::Snippet;
-use std::{
-    any::TypeId,
-    borrow::Cow,
-    cmp::{self, Ordering, Reverse},
-    mem,
-    num::NonZeroU32,
-    ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
-    path::Path,
-    sync::Arc,
-    sync::Weak,
-    time::{Duration, Instant},
-};
-pub use sum_tree::Bias;
-use sum_tree::TreeMap;
-use text::{OffsetUtf16, Rope};
-use theme::{ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings};
-use ui::{
-    h_stack, ButtonSize, ButtonStyle, Icon, IconButton, ListItem, ListItemSpacing, Popover, Tooltip,
-};
-use ui::{prelude::*, IconSize};
-use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
-use workspace::{searchable::SearchEvent, ItemNavHistory, Pane, SplitDirection, ViewId, Workspace};
-
-const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
-const MAX_LINE_LEN: usize = 1024;
-const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
-const MAX_SELECTION_HISTORY_LEN: usize = 1024;
-const COPILOT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
-pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
-pub const DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
-
-pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
-
-pub fn render_parsed_markdown(
-    element_id: impl Into<ElementId>,
-    parsed: &language::ParsedMarkdown,
-    editor_style: &EditorStyle,
-    workspace: Option<WeakView<Workspace>>,
-    cx: &mut ViewContext<Editor>,
-) -> InteractiveText {
-    let code_span_background_color = cx
-        .theme()
-        .colors()
-        .editor_document_highlight_read_background;
-
-    let highlights = gpui::combine_highlights(
-        parsed.highlights.iter().filter_map(|(range, highlight)| {
-            let highlight = highlight.to_highlight_style(&editor_style.syntax)?;
-            Some((range.clone(), highlight))
-        }),
-        parsed
-            .regions
-            .iter()
-            .zip(&parsed.region_ranges)
-            .filter_map(|(region, range)| {
-                if region.code {
-                    Some((
-                        range.clone(),
-                        HighlightStyle {
-                            background_color: Some(code_span_background_color),
-                            ..Default::default()
-                        },
-                    ))
-                } else {
-                    None
-                }
-            }),
-    );
-
-    let mut links = Vec::new();
-    let mut link_ranges = Vec::new();
-    for (range, region) in parsed.region_ranges.iter().zip(&parsed.regions) {
-        if let Some(link) = region.link.clone() {
-            links.push(link);
-            link_ranges.push(range.clone());
-        }
-    }
-
-    InteractiveText::new(
-        element_id,
-        StyledText::new(parsed.text.clone()).with_highlights(&editor_style.text, highlights),
-    )
-    .on_click(link_ranges, move |clicked_range_ix, cx| {
-        match &links[clicked_range_ix] {
-            markdown::Link::Web { url } => cx.open_url(url),
-            markdown::Link::Path { path } => {
-                if let Some(workspace) = &workspace {
-                    _ = workspace.update(cx, |workspace, cx| {
-                        workspace.open_abs_path(path.clone(), false, cx).detach();
-                    });
-                }
-            }
-        }
-    })
-}
-
-#[derive(PartialEq, Clone, Deserialize, Default)]
-pub struct SelectNext {
-    #[serde(default)]
-    pub replace_newest: bool,
-}
-
-#[derive(PartialEq, Clone, Deserialize, Default)]
-pub struct SelectPrevious {
-    #[serde(default)]
-    pub replace_newest: bool,
-}
-
-#[derive(PartialEq, Clone, Deserialize, Default)]
-pub struct SelectAllMatches {
-    #[serde(default)]
-    pub replace_newest: bool,
-}
-
-#[derive(PartialEq, Clone, Deserialize, Default)]
-pub struct SelectToBeginningOfLine {
-    #[serde(default)]
-    stop_at_soft_wraps: bool,
-}
-
-#[derive(PartialEq, Clone, Deserialize, Default)]
-pub struct MovePageUp {
-    #[serde(default)]
-    center_cursor: bool,
-}
-
-#[derive(PartialEq, Clone, Deserialize, Default)]
-pub struct MovePageDown {
-    #[serde(default)]
-    center_cursor: bool,
-}
-
-#[derive(PartialEq, Clone, Deserialize, Default)]
-pub struct SelectToEndOfLine {
-    #[serde(default)]
-    stop_at_soft_wraps: bool,
-}
-
-#[derive(PartialEq, Clone, Deserialize, Default)]
-pub struct ToggleCodeActions {
-    #[serde(default)]
-    pub deployed_from_indicator: bool,
-}
-
-#[derive(PartialEq, Clone, Deserialize, Default)]
-pub struct ConfirmCompletion {
-    #[serde(default)]
-    pub item_ix: Option<usize>,
-}
-
-#[derive(PartialEq, Clone, Deserialize, Default)]
-pub struct ConfirmCodeAction {
-    #[serde(default)]
-    pub item_ix: Option<usize>,
-}
-
-#[derive(PartialEq, Clone, Deserialize, Default)]
-pub struct ToggleComments {
-    #[serde(default)]
-    pub advance_downwards: bool,
-}
-
-#[derive(PartialEq, Clone, Deserialize, Default)]
-pub struct FoldAt {
-    pub buffer_row: u32,
-}
-
-#[derive(PartialEq, Clone, Deserialize, Default)]
-pub struct UnfoldAt {
-    pub buffer_row: u32,
-}
-
-impl_actions!(
-    editor,
-    [
-        SelectNext,
-        SelectPrevious,
-        SelectAllMatches,
-        SelectToBeginningOfLine,
-        MovePageUp,
-        MovePageDown,
-        SelectToEndOfLine,
-        ToggleCodeActions,
-        ConfirmCompletion,
-        ConfirmCodeAction,
-        ToggleComments,
-        FoldAt,
-        UnfoldAt
-    ]
-);
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub enum InlayId {
-    Suggestion(usize),
-    Hint(usize),
-}
-
-impl InlayId {
-    fn id(&self) -> usize {
-        match self {
-            Self::Suggestion(id) => *id,
-            Self::Hint(id) => *id,
-        }
-    }
-}
-
-actions!(
-    editor,
-    [
-        AddSelectionAbove,
-        AddSelectionBelow,
-        Backspace,
-        Cancel,
-        ConfirmRename,
-        ContextMenuFirst,
-        ContextMenuLast,
-        ContextMenuNext,
-        ContextMenuPrev,
-        ConvertToKebabCase,
-        ConvertToLowerCamelCase,
-        ConvertToLowerCase,
-        ConvertToSnakeCase,
-        ConvertToTitleCase,
-        ConvertToUpperCamelCase,
-        ConvertToUpperCase,
-        Copy,
-        CopyHighlightJson,
-        CopyPath,
-        CopyRelativePath,
-        Cut,
-        CutToEndOfLine,
-        Delete,
-        DeleteLine,
-        DeleteToBeginningOfLine,
-        DeleteToEndOfLine,
-        DeleteToNextSubwordEnd,
-        DeleteToNextWordEnd,
-        DeleteToPreviousSubwordStart,
-        DeleteToPreviousWordStart,
-        DuplicateLine,
-        ExpandMacroRecursively,
-        FindAllReferences,
-        Fold,
-        FoldSelectedRanges,
-        Format,
-        GoToDefinition,
-        GoToDefinitionSplit,
-        GoToDiagnostic,
-        GoToHunk,
-        GoToPrevDiagnostic,
-        GoToPrevHunk,
-        GoToTypeDefinition,
-        GoToTypeDefinitionSplit,
-        HalfPageDown,
-        HalfPageUp,
-        Hover,
-        Indent,
-        JoinLines,
-        LineDown,
-        LineUp,
-        MoveDown,
-        MoveLeft,
-        MoveLineDown,
-        MoveLineUp,
-        MoveRight,
-        MoveToBeginning,
-        MoveToBeginningOfLine,
-        MoveToEnclosingBracket,
-        MoveToEnd,
-        MoveToEndOfLine,
-        MoveToEndOfParagraph,
-        MoveToNextSubwordEnd,
-        MoveToNextWordEnd,
-        MoveToPreviousSubwordStart,
-        MoveToPreviousWordStart,
-        MoveToStartOfParagraph,
-        MoveUp,
-        Newline,
-        NewlineAbove,
-        NewlineBelow,
-        NextScreen,
-        OpenExcerpts,
-        Outdent,
-        PageDown,
-        PageUp,
-        Paste,
-        Redo,
-        RedoSelection,
-        Rename,
-        RestartLanguageServer,
-        RevealInFinder,
-        ReverseLines,
-        ScrollCursorBottom,
-        ScrollCursorCenter,
-        ScrollCursorTop,
-        SelectAll,
-        SelectDown,
-        SelectLargerSyntaxNode,
-        SelectLeft,
-        SelectLine,
-        SelectRight,
-        SelectSmallerSyntaxNode,
-        SelectToBeginning,
-        SelectToEnd,
-        SelectToEndOfParagraph,
-        SelectToNextSubwordEnd,
-        SelectToNextWordEnd,
-        SelectToPreviousSubwordStart,
-        SelectToPreviousWordStart,
-        SelectToStartOfParagraph,
-        SelectUp,
-        ShowCharacterPalette,
-        ShowCompletions,
-        ShuffleLines,
-        SortLinesCaseInsensitive,
-        SortLinesCaseSensitive,
-        SplitSelectionIntoLines,
-        Tab,
-        TabPrev,
-        ToggleInlayHints,
-        ToggleSoftWrap,
-        Transpose,
-        Undo,
-        UndoSelection,
-        UnfoldLines,
-    ]
-);
-
-enum DocumentHighlightRead {}
-enum DocumentHighlightWrite {}
-enum InputComposition {}
-
-#[derive(Copy, Clone, PartialEq, Eq)]
-pub enum Direction {
-    Prev,
-    Next,
-}
-
-pub fn init_settings(cx: &mut AppContext) {
-    EditorSettings::register(cx);
-}
-
-pub fn init(cx: &mut AppContext) {
-    init_settings(cx);
-
-    workspace::register_project_item::<Editor>(cx);
-    workspace::register_followable_item::<Editor>(cx);
-    workspace::register_deserializable_item::<Editor>(cx);
-    cx.observe_new_views(
-        |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
-            workspace.register_action(Editor::new_file);
-            workspace.register_action(Editor::new_file_in_direction);
-        },
-    )
-    .detach();
-
-    cx.on_action(move |_: &workspace::NewFile, cx| {
-        let app_state = cx.global::<Weak<workspace::AppState>>();
-        if let Some(app_state) = app_state.upgrade() {
-            workspace::open_new(&app_state, cx, |workspace, cx| {
-                Editor::new_file(workspace, &Default::default(), cx)
-            })
-            .detach();
-        }
-    });
-    cx.on_action(move |_: &workspace::NewWindow, cx| {
-        let app_state = cx.global::<Weak<workspace::AppState>>();
-        if let Some(app_state) = app_state.upgrade() {
-            workspace::open_new(&app_state, cx, |workspace, cx| {
-                Editor::new_file(workspace, &Default::default(), cx)
-            })
-            .detach();
-        }
-    });
-}
-
-trait InvalidationRegion {
-    fn ranges(&self) -> &[Range<Anchor>];
-}
-
-#[derive(Clone, Debug, PartialEq)]
-pub enum SelectPhase {
-    Begin {
-        position: DisplayPoint,
-        add: bool,
-        click_count: usize,
-    },
-    BeginColumnar {
-        position: DisplayPoint,
-        goal_column: u32,
-    },
-    Extend {
-        position: DisplayPoint,
-        click_count: usize,
-    },
-    Update {
-        position: DisplayPoint,
-        goal_column: u32,
-        scroll_position: gpui::Point<f32>,
-    },
-    End,
-}
-
-#[derive(Clone, Debug)]
-pub enum SelectMode {
-    Character,
-    Word(Range<Anchor>),
-    Line(Range<Anchor>),
-    All,
-}
-
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
-pub enum EditorMode {
-    SingleLine,
-    AutoHeight { max_lines: usize },
-    Full,
-}
-
-#[derive(Clone, Debug)]
-pub enum SoftWrap {
-    None,
-    EditorWidth,
-    Column(u32),
-}
-
-#[derive(Clone, Default)]
-pub struct EditorStyle {
-    pub background: Hsla,
-    pub local_player: PlayerColor,
-    pub text: TextStyle,
-    pub scrollbar_width: Pixels,
-    pub syntax: Arc<SyntaxTheme>,
-    pub status: StatusColors,
-    pub inlays_style: HighlightStyle,
-    pub suggestions_style: HighlightStyle,
-}
-
-type CompletionId = usize;
-
-// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
-// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
-
-type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Vec<Range<Anchor>>);
-type InlayBackgroundHighlight = (fn(&ThemeColors) -> Hsla, Vec<InlayHighlight>);
-
-pub struct Editor {
-    handle: WeakView<Self>,
-    focus_handle: FocusHandle,
-    buffer: Model<MultiBuffer>,
-    display_map: Model<DisplayMap>,
-    pub selections: SelectionsCollection,
-    pub scroll_manager: ScrollManager,
-    columnar_selection_tail: Option<Anchor>,
-    add_selections_state: Option<AddSelectionsState>,
-    select_next_state: Option<SelectNextState>,
-    select_prev_state: Option<SelectNextState>,
-    selection_history: SelectionHistory,
-    autoclose_regions: Vec<AutocloseRegion>,
-    snippet_stack: InvalidationStack<SnippetState>,
-    select_larger_syntax_node_stack: Vec<Box<[Selection<usize>]>>,
-    ime_transaction: Option<TransactionId>,
-    active_diagnostics: Option<ActiveDiagnosticGroup>,
-    soft_wrap_mode_override: Option<language_settings::SoftWrap>,
-    project: Option<Model<Project>>,
-    collaboration_hub: Option<Box<dyn CollaborationHub>>,
-    blink_manager: Model<BlinkManager>,
-    pub show_local_selections: bool,
-    mode: EditorMode,
-    show_gutter: bool,
-    show_wrap_guides: Option<bool>,
-    placeholder_text: Option<Arc<str>>,
-    highlighted_rows: Option<Range<u32>>,
-    background_highlights: BTreeMap<TypeId, BackgroundHighlight>,
-    inlay_background_highlights: TreeMap<Option<TypeId>, InlayBackgroundHighlight>,
-    nav_history: Option<ItemNavHistory>,
-    context_menu: RwLock<Option<ContextMenu>>,
-    mouse_context_menu: Option<MouseContextMenu>,
-    completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
-    next_completion_id: CompletionId,
-    available_code_actions: Option<(Model<Buffer>, Arc<[CodeAction]>)>,
-    code_actions_task: Option<Task<()>>,
-    document_highlights_task: Option<Task<()>>,
-    pending_rename: Option<RenameState>,
-    searchable: bool,
-    cursor_shape: CursorShape,
-    collapse_matches: bool,
-    autoindent_mode: Option<AutoindentMode>,
-    workspace: Option<(WeakView<Workspace>, i64)>,
-    keymap_context_layers: BTreeMap<TypeId, KeyContext>,
-    input_enabled: bool,
-    read_only: bool,
-    leader_peer_id: Option<PeerId>,
-    remote_id: Option<ViewId>,
-    hover_state: HoverState,
-    gutter_hovered: bool,
-    link_go_to_definition_state: LinkGoToDefinitionState,
-    copilot_state: CopilotState,
-    inlay_hint_cache: InlayHintCache,
-    next_inlay_id: usize,
-    _subscriptions: Vec<Subscription>,
-    pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
-    gutter_width: Pixels,
-    style: Option<EditorStyle>,
-    editor_actions: Vec<Box<dyn Fn(&mut ViewContext<Self>)>>,
-}
-
-pub struct EditorSnapshot {
-    pub mode: EditorMode,
-    pub show_gutter: bool,
-    pub display_snapshot: DisplaySnapshot,
-    pub placeholder_text: Option<Arc<str>>,
-    is_focused: bool,
-    scroll_anchor: ScrollAnchor,
-    ongoing_scroll: OngoingScroll,
-}
-
-pub struct RemoteSelection {
-    pub replica_id: ReplicaId,
-    pub selection: Selection<Anchor>,
-    pub cursor_shape: CursorShape,
-    pub peer_id: PeerId,
-    pub line_mode: bool,
-    pub participant_index: Option<ParticipantIndex>,
-}
-
-#[derive(Clone, Debug)]
-struct SelectionHistoryEntry {
-    selections: Arc<[Selection<Anchor>]>,
-    select_next_state: Option<SelectNextState>,
-    select_prev_state: Option<SelectNextState>,
-    add_selections_state: Option<AddSelectionsState>,
-}
-
-enum SelectionHistoryMode {
-    Normal,
-    Undoing,
-    Redoing,
-}
-
-impl Default for SelectionHistoryMode {
-    fn default() -> Self {
-        Self::Normal
-    }
-}
-
-#[derive(Default)]
-struct SelectionHistory {
-    #[allow(clippy::type_complexity)]
-    selections_by_transaction:
-        HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
-    mode: SelectionHistoryMode,
-    undo_stack: VecDeque<SelectionHistoryEntry>,
-    redo_stack: VecDeque<SelectionHistoryEntry>,
-}
-
-impl SelectionHistory {
-    fn insert_transaction(
-        &mut self,
-        transaction_id: TransactionId,
-        selections: Arc<[Selection<Anchor>]>,
-    ) {
-        self.selections_by_transaction
-            .insert(transaction_id, (selections, None));
-    }
-
-    #[allow(clippy::type_complexity)]
-    fn transaction(
-        &self,
-        transaction_id: TransactionId,
-    ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
-        self.selections_by_transaction.get(&transaction_id)
-    }
-
-    #[allow(clippy::type_complexity)]
-    fn transaction_mut(
-        &mut self,
-        transaction_id: TransactionId,
-    ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
-        self.selections_by_transaction.get_mut(&transaction_id)
-    }
-
-    fn push(&mut self, entry: SelectionHistoryEntry) {
-        if !entry.selections.is_empty() {
-            match self.mode {
-                SelectionHistoryMode::Normal => {
-                    self.push_undo(entry);
-                    self.redo_stack.clear();
-                }
-                SelectionHistoryMode::Undoing => self.push_redo(entry),
-                SelectionHistoryMode::Redoing => self.push_undo(entry),
-            }
-        }
-    }
-
-    fn push_undo(&mut self, entry: SelectionHistoryEntry) {
-        if self
-            .undo_stack
-            .back()
-            .map_or(true, |e| e.selections != entry.selections)
-        {
-            self.undo_stack.push_back(entry);
-            if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
-                self.undo_stack.pop_front();
-            }
-        }
-    }
-
-    fn push_redo(&mut self, entry: SelectionHistoryEntry) {
-        if self
-            .redo_stack
-            .back()
-            .map_or(true, |e| e.selections != entry.selections)
-        {
-            self.redo_stack.push_back(entry);
-            if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
-                self.redo_stack.pop_front();
-            }
-        }
-    }
-}
-
-#[derive(Clone, Debug)]
-struct AddSelectionsState {
-    above: bool,
-    stack: Vec<usize>,
-}
-
-#[derive(Clone)]
-struct SelectNextState {
-    query: AhoCorasick,
-    wordwise: bool,
-    done: bool,
-}
-
-impl std::fmt::Debug for SelectNextState {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct(std::any::type_name::<Self>())
-            .field("wordwise", &self.wordwise)
-            .field("done", &self.done)
-            .finish()
-    }
-}
-
-#[derive(Debug)]
-struct AutocloseRegion {
-    selection_id: usize,
-    range: Range<Anchor>,
-    pair: BracketPair,
-}
-
-#[derive(Debug)]
-struct SnippetState {
-    ranges: Vec<Vec<Range<Anchor>>>,
-    active_index: usize,
-}
-
-pub struct RenameState {
-    pub range: Range<Anchor>,
-    pub old_name: Arc<str>,
-    pub editor: View<Editor>,
-    block_id: BlockId,
-}
-
-struct InvalidationStack<T>(Vec<T>);
-
-enum ContextMenu {
-    Completions(CompletionsMenu),
-    CodeActions(CodeActionsMenu),
-}
-
-impl ContextMenu {
-    fn select_first(
-        &mut self,
-        project: Option<&Model<Project>>,
-        cx: &mut ViewContext<Editor>,
-    ) -> bool {
-        if self.visible() {
-            match self {
-                ContextMenu::Completions(menu) => menu.select_first(project, cx),
-                ContextMenu::CodeActions(menu) => menu.select_first(cx),
-            }
-            true
-        } else {
-            false
-        }
-    }
-
-    fn select_prev(
-        &mut self,
-        project: Option<&Model<Project>>,
-        cx: &mut ViewContext<Editor>,
-    ) -> bool {
-        if self.visible() {
-            match self {
-                ContextMenu::Completions(menu) => menu.select_prev(project, cx),
-                ContextMenu::CodeActions(menu) => menu.select_prev(cx),
-            }
-            true
-        } else {
-            false
-        }
-    }
-
-    fn select_next(
-        &mut self,
-        project: Option<&Model<Project>>,
-        cx: &mut ViewContext<Editor>,
-    ) -> bool {
-        if self.visible() {
-            match self {
-                ContextMenu::Completions(menu) => menu.select_next(project, cx),
-                ContextMenu::CodeActions(menu) => menu.select_next(cx),
-            }
-            true
-        } else {
-            false
-        }
-    }
-
-    fn select_last(
-        &mut self,
-        project: Option<&Model<Project>>,
-        cx: &mut ViewContext<Editor>,
-    ) -> bool {
-        if self.visible() {
-            match self {
-                ContextMenu::Completions(menu) => menu.select_last(project, cx),
-                ContextMenu::CodeActions(menu) => menu.select_last(cx),
-            }
-            true
-        } else {
-            false
-        }
-    }
-
-    fn visible(&self) -> bool {
-        match self {
-            ContextMenu::Completions(menu) => menu.visible(),
-            ContextMenu::CodeActions(menu) => menu.visible(),
-        }
-    }
-
-    fn render(
-        &self,
-        cursor_position: DisplayPoint,
-        style: &EditorStyle,
-        max_height: Pixels,
-        workspace: Option<WeakView<Workspace>>,
-        cx: &mut ViewContext<Editor>,
-    ) -> (DisplayPoint, AnyElement) {
-        match self {
-            ContextMenu::Completions(menu) => (
-                cursor_position,
-                menu.render(style, max_height, workspace, cx),
-            ),
-            ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, max_height, cx),
-        }
-    }
-}
-
-#[derive(Clone)]
-struct CompletionsMenu {
-    id: CompletionId,
-    initial_position: Anchor,
-    buffer: Model<Buffer>,
-    completions: Arc<RwLock<Box<[Completion]>>>,
-    match_candidates: Arc<[StringMatchCandidate]>,
-    matches: Arc<[StringMatch]>,
-    selected_item: usize,
-    scroll_handle: UniformListScrollHandle,
-}
-
-impl CompletionsMenu {
-    fn select_first(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
-        self.selected_item = 0;
-        self.scroll_handle.scroll_to_item(self.selected_item);
-        self.attempt_resolve_selected_completion_documentation(project, cx);
-        cx.notify();
-    }
-
-    fn select_prev(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
-        if self.selected_item > 0 {
-            self.selected_item -= 1;
-        } else {
-            self.selected_item = self.matches.len() - 1;
-        }
-        self.scroll_handle.scroll_to_item(self.selected_item);
-        self.attempt_resolve_selected_completion_documentation(project, cx);
-        cx.notify();
-    }
-
-    fn select_next(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
-        if self.selected_item + 1 < self.matches.len() {
-            self.selected_item += 1;
-        } else {
-            self.selected_item = 0;
-        }
-        self.scroll_handle.scroll_to_item(self.selected_item);
-        self.attempt_resolve_selected_completion_documentation(project, cx);
-        cx.notify();
-    }
-
-    fn select_last(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
-        self.selected_item = self.matches.len() - 1;
-        self.scroll_handle.scroll_to_item(self.selected_item);
-        self.attempt_resolve_selected_completion_documentation(project, cx);
-        cx.notify();
-    }
-
-    fn pre_resolve_completion_documentation(
-        &self,
-        editor: &Editor,
-        cx: &mut ViewContext<Editor>,
-    ) -> Option<Task<()>> {
-        let settings = EditorSettings::get_global(cx);
-        if !settings.show_completion_documentation {
-            return None;
-        }
-
-        let Some(project) = editor.project.clone() else {
-            return None;
-        };
-
-        let client = project.read(cx).client();
-        let language_registry = project.read(cx).languages().clone();
-
-        let is_remote = project.read(cx).is_remote();
-        let project_id = project.read(cx).remote_id();
-
-        let completions = self.completions.clone();
-        let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect();
-
-        Some(cx.spawn(move |this, mut cx| async move {
-            if is_remote {
-                let Some(project_id) = project_id else {
-                    log::error!("Remote project without remote_id");
-                    return;
-                };
-
-                for completion_index in completion_indices {
-                    let completions_guard = completions.read();
-                    let completion = &completions_guard[completion_index];
-                    if completion.documentation.is_some() {
-                        continue;
-                    }
-
-                    let server_id = completion.server_id;
-                    let completion = completion.lsp_completion.clone();
-                    drop(completions_guard);
-
-                    Self::resolve_completion_documentation_remote(
-                        project_id,
-                        server_id,
-                        completions.clone(),
-                        completion_index,
-                        completion,
-                        client.clone(),
-                        language_registry.clone(),
-                    )
-                    .await;
-
-                    _ = this.update(&mut cx, |_, cx| cx.notify());
-                }
-            } else {
-                for completion_index in completion_indices {
-                    let completions_guard = completions.read();
-                    let completion = &completions_guard[completion_index];
-                    if completion.documentation.is_some() {
-                        continue;
-                    }
-
-                    let server_id = completion.server_id;
-                    let completion = completion.lsp_completion.clone();
-                    drop(completions_guard);
-
-                    let server = project
-                        .read_with(&mut cx, |project, _| {
-                            project.language_server_for_id(server_id)
-                        })
-                        .ok()
-                        .flatten();
-                    let Some(server) = server else {
-                        return;
-                    };
-
-                    Self::resolve_completion_documentation_local(
-                        server,
-                        completions.clone(),
-                        completion_index,
-                        completion,
-                        language_registry.clone(),
-                    )
-                    .await;
-
-                    _ = this.update(&mut cx, |_, cx| cx.notify());
-                }
-            }
-        }))
-    }
-
-    fn attempt_resolve_selected_completion_documentation(
-        &mut self,
-        project: Option<&Model<Project>>,
-        cx: &mut ViewContext<Editor>,
-    ) {
-        let settings = EditorSettings::get_global(cx);
-        if !settings.show_completion_documentation {
-            return;
-        }
-
-        let completion_index = self.matches[self.selected_item].candidate_id;
-        let Some(project) = project else {
-            return;
-        };
-        let language_registry = project.read(cx).languages().clone();
-
-        let completions = self.completions.clone();
-        let completions_guard = completions.read();
-        let completion = &completions_guard[completion_index];
-        if completion.documentation.is_some() {
-            return;
-        }
-
-        let server_id = completion.server_id;
-        let completion = completion.lsp_completion.clone();
-        drop(completions_guard);
-
-        if project.read(cx).is_remote() {
-            let Some(project_id) = project.read(cx).remote_id() else {
-                log::error!("Remote project without remote_id");
-                return;
-            };
-
-            let client = project.read(cx).client();
-
-            cx.spawn(move |this, mut cx| async move {
-                Self::resolve_completion_documentation_remote(
-                    project_id,
-                    server_id,
-                    completions.clone(),
-                    completion_index,
-                    completion,
-                    client,
-                    language_registry.clone(),
-                )
-                .await;
-
-                _ = this.update(&mut cx, |_, cx| cx.notify());
-            })
-            .detach();
-        } else {
-            let Some(server) = project.read(cx).language_server_for_id(server_id) else {
-                return;
-            };
-
-            cx.spawn(move |this, mut cx| async move {
-                Self::resolve_completion_documentation_local(
-                    server,
-                    completions,
-                    completion_index,
-                    completion,
-                    language_registry,
-                )
-                .await;
-
-                _ = this.update(&mut cx, |_, cx| cx.notify());
-            })
-            .detach();
-        }
-    }
-
-    async fn resolve_completion_documentation_remote(
-        project_id: u64,
-        server_id: LanguageServerId,
-        completions: Arc<RwLock<Box<[Completion]>>>,
-        completion_index: usize,
-        completion: lsp::CompletionItem,
-        client: Arc<Client>,
-        language_registry: Arc<LanguageRegistry>,
-    ) {
-        let request = proto::ResolveCompletionDocumentation {
-            project_id,
-            language_server_id: server_id.0 as u64,
-            lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(),
-        };
-
-        let Some(response) = client
-            .request(request)
-            .await
-            .context("completion documentation resolve proto request")
-            .log_err()
-        else {
-            return;
-        };
-
-        if response.text.is_empty() {
-            let mut completions = completions.write();
-            let completion = &mut completions[completion_index];
-            completion.documentation = Some(Documentation::Undocumented);
-        }
-
-        let documentation = if response.is_markdown {
-            Documentation::MultiLineMarkdown(
-                markdown::parse_markdown(&response.text, &language_registry, None).await,
-            )
-        } else if response.text.lines().count() <= 1 {
-            Documentation::SingleLine(response.text)
-        } else {
-            Documentation::MultiLinePlainText(response.text)
-        };
-
-        let mut completions = completions.write();
-        let completion = &mut completions[completion_index];
-        completion.documentation = Some(documentation);
-    }
-
-    async fn resolve_completion_documentation_local(
-        server: Arc<lsp::LanguageServer>,
-        completions: Arc<RwLock<Box<[Completion]>>>,
-        completion_index: usize,
-        completion: lsp::CompletionItem,
-        language_registry: Arc<LanguageRegistry>,
-    ) {
-        let can_resolve = server
-            .capabilities()
-            .completion_provider
-            .as_ref()
-            .and_then(|options| options.resolve_provider)
-            .unwrap_or(false);
-        if !can_resolve {
-            return;
-        }
-
-        let request = server.request::<lsp::request::ResolveCompletionItem>(completion);
-        let Some(completion_item) = request.await.log_err() else {
-            return;
-        };
-
-        if let Some(lsp_documentation) = completion_item.documentation {
-            let documentation = language::prepare_completion_documentation(
-                &lsp_documentation,
-                &language_registry,
-                None, // TODO: Try to reasonably work out which language the completion is for
-            )
-            .await;
-
-            let mut completions = completions.write();
-            let completion = &mut completions[completion_index];
-            completion.documentation = Some(documentation);
-        } else {
-            let mut completions = completions.write();
-            let completion = &mut completions[completion_index];
-            completion.documentation = Some(Documentation::Undocumented);
-        }
-    }
-
-    fn visible(&self) -> bool {
-        !self.matches.is_empty()
-    }
-
-    fn render(
-        &self,
-        style: &EditorStyle,
-        max_height: Pixels,
-        workspace: Option<WeakView<Workspace>>,
-        cx: &mut ViewContext<Editor>,
-    ) -> AnyElement {
-        let settings = EditorSettings::get_global(cx);
-        let show_completion_documentation = settings.show_completion_documentation;
-
-        let widest_completion_ix = self
-            .matches
-            .iter()
-            .enumerate()
-            .max_by_key(|(_, mat)| {
-                let completions = self.completions.read();
-                let completion = &completions[mat.candidate_id];
-                let documentation = &completion.documentation;
-
-                let mut len = completion.label.text.chars().count();
-                if let Some(Documentation::SingleLine(text)) = documentation {
-                    if show_completion_documentation {
-                        len += text.chars().count();
-                    }
-                }
-
-                len
-            })
-            .map(|(ix, _)| ix);
-
-        let completions = self.completions.clone();
-        let matches = self.matches.clone();
-        let selected_item = self.selected_item;
-        let style = style.clone();
-
-        let multiline_docs = {
-            let mat = &self.matches[selected_item];
-            let multiline_docs = match &self.completions.read()[mat.candidate_id].documentation {
-                Some(Documentation::MultiLinePlainText(text)) => {
-                    Some(div().child(SharedString::from(text.clone())))
-                }
-                Some(Documentation::MultiLineMarkdown(parsed)) => Some(div().child(
-                    render_parsed_markdown("completions_markdown", parsed, &style, workspace, cx),
-                )),
-                _ => None,
-            };
-            multiline_docs.map(|div| {
-                div.id("multiline_docs")
-                    .max_h(max_height)
-                    .flex_1()
-                    .px_1p5()
-                    .py_1()
-                    .min_w(px(260.))
-                    .max_w(px(640.))
-                    .w(px(500.))
-                    .overflow_y_scroll()
-                    // Prevent a mouse down on documentation from being propagated to the editor,
-                    // because that would move the cursor.
-                    .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
-            })
-        };
-
-        let list = uniform_list(
-            cx.view().clone(),
-            "completions",
-            matches.len(),
-            move |_editor, range, cx| {
-                let start_ix = range.start;
-                let completions_guard = completions.read();
-
-                matches[range]
-                    .iter()
-                    .enumerate()
-                    .map(|(ix, mat)| {
-                        let item_ix = start_ix + ix;
-                        let candidate_id = mat.candidate_id;
-                        let completion = &completions_guard[candidate_id];
-
-                        let documentation = if show_completion_documentation {
-                            &completion.documentation
-                        } else {
-                            &None
-                        };
-
-                        let highlights = gpui::combine_highlights(
-                            mat.ranges().map(|range| (range, FontWeight::BOLD.into())),
-                            styled_runs_for_code_label(&completion.label, &style.syntax).map(
-                                |(range, mut highlight)| {
-                                    // Ignore font weight for syntax highlighting, as we'll use it
-                                    // for fuzzy matches.
-                                    highlight.font_weight = None;
-                                    (range, highlight)
-                                },
-                            ),
-                        );
-                        let completion_label = StyledText::new(completion.label.text.clone())
-                            .with_highlights(&style.text, highlights);
-                        let documentation_label =
-                            if let Some(Documentation::SingleLine(text)) = documentation {
-                                if text.trim().is_empty() {
-                                    None
-                                } else {
-                                    Some(
-                                        h_stack().ml_4().child(
-                                            Label::new(text.clone())
-                                                .size(LabelSize::Small)
-                                                .color(Color::Muted),
-                                        ),
-                                    )
-                                }
-                            } else {
-                                None
-                            };
-
-                        div().min_w(px(220.)).max_w(px(540.)).child(
-                            ListItem::new(mat.candidate_id)
-                                .inset(true)
-                                .spacing(ListItemSpacing::Sparse)
-                                .selected(item_ix == selected_item)
-                                .on_click(cx.listener(move |editor, _event, cx| {
-                                    cx.stop_propagation();
-                                    editor
-                                        .confirm_completion(
-                                            &ConfirmCompletion {
-                                                item_ix: Some(item_ix),
-                                            },
-                                            cx,
-                                        )
-                                        .map(|task| task.detach_and_log_err(cx));
-                                }))
-                                .child(h_stack().overflow_hidden().child(completion_label))
-                                .end_slot::<Div>(documentation_label),
-                        )
-                    })
-                    .collect()
-            },
-        )
-        .max_h(max_height)
-        .track_scroll(self.scroll_handle.clone())
-        .with_width_from_item(widest_completion_ix);
-
-        Popover::new()
-            .child(list)
-            .when_some(multiline_docs, |popover, multiline_docs| {
-                popover.aside(multiline_docs)
-            })
-            .into_any_element()
-    }
-
-    pub async fn filter(&mut self, query: Option<&str>, executor: BackgroundExecutor) {
-        let mut matches = if let Some(query) = query {
-            fuzzy::match_strings(
-                &self.match_candidates,
-                query,
-                query.chars().any(|c| c.is_uppercase()),
-                100,
-                &Default::default(),
-                executor,
-            )
-            .await
-        } else {
-            self.match_candidates
-                .iter()
-                .enumerate()
-                .map(|(candidate_id, candidate)| StringMatch {
-                    candidate_id,
-                    score: Default::default(),
-                    positions: Default::default(),
-                    string: candidate.string.clone(),
-                })
-                .collect()
-        };
-
-        // Remove all candidates where the query's start does not match the start of any word in the candidate
-        if let Some(query) = query {
-            if let Some(query_start) = query.chars().next() {
-                matches.retain(|string_match| {
-                    split_words(&string_match.string).any(|word| {
-                        // Check that the first codepoint of the word as lowercase matches the first
-                        // codepoint of the query as lowercase
-                        word.chars()
-                            .flat_map(|codepoint| codepoint.to_lowercase())
-                            .zip(query_start.to_lowercase())
-                            .all(|(word_cp, query_cp)| word_cp == query_cp)
-                    })
-                });
-            }
-        }
-
-        let completions = self.completions.read();
-        matches.sort_unstable_by_key(|mat| {
-            let completion = &completions[mat.candidate_id];
-            (
-                completion.lsp_completion.sort_text.as_ref(),
-                Reverse(OrderedFloat(mat.score)),
-                completion.sort_key(),
-            )
-        });
-
-        for mat in &mut matches {
-            let completion = &completions[mat.candidate_id];
-            mat.string = completion.label.text.clone();
-            for position in &mut mat.positions {
-                *position += completion.label.filter_range.start;
-            }
-        }
-        drop(completions);
-
-        self.matches = matches.into();
-        self.selected_item = 0;
-    }
-}
-
-#[derive(Clone)]
-struct CodeActionsMenu {
-    actions: Arc<[CodeAction]>,
-    buffer: Model<Buffer>,
-    selected_item: usize,
-    scroll_handle: UniformListScrollHandle,
-    deployed_from_indicator: bool,
-}
-
-impl CodeActionsMenu {
-    fn select_first(&mut self, cx: &mut ViewContext<Editor>) {
-        self.selected_item = 0;
-        self.scroll_handle.scroll_to_item(self.selected_item);
-        cx.notify()
-    }
-
-    fn select_prev(&mut self, cx: &mut ViewContext<Editor>) {
-        if self.selected_item > 0 {
-            self.selected_item -= 1;
-        } else {
-            self.selected_item = self.actions.len() - 1;
-        }
-        self.scroll_handle.scroll_to_item(self.selected_item);
-        cx.notify();
-    }
-
-    fn select_next(&mut self, cx: &mut ViewContext<Editor>) {
-        if self.selected_item + 1 < self.actions.len() {
-            self.selected_item += 1;
-        } else {
-            self.selected_item = 0;
-        }
-        self.scroll_handle.scroll_to_item(self.selected_item);
-        cx.notify();
-    }
-
-    fn select_last(&mut self, cx: &mut ViewContext<Editor>) {
-        self.selected_item = self.actions.len() - 1;
-        self.scroll_handle.scroll_to_item(self.selected_item);
-        cx.notify()
-    }
-
-    fn visible(&self) -> bool {
-        !self.actions.is_empty()
-    }
-
-    fn render(
-        &self,
-        mut cursor_position: DisplayPoint,
-        _style: &EditorStyle,
-        max_height: Pixels,
-        cx: &mut ViewContext<Editor>,
-    ) -> (DisplayPoint, AnyElement) {
-        let actions = self.actions.clone();
-        let selected_item = self.selected_item;
-
-        let element = uniform_list(
-            cx.view().clone(),
-            "code_actions_menu",
-            self.actions.len(),
-            move |_this, range, cx| {
-                actions[range.clone()]
-                    .iter()
-                    .enumerate()
-                    .map(|(ix, action)| {
-                        let item_ix = range.start + ix;
-                        let selected = selected_item == item_ix;
-                        let colors = cx.theme().colors();
-                        div()
-                            .px_2()
-                            .text_color(colors.text)
-                            .when(selected, |style| {
-                                style
-                                    .bg(colors.element_active)
-                                    .text_color(colors.text_accent)
-                            })
-                            .hover(|style| {
-                                style
-                                    .bg(colors.element_hover)
-                                    .text_color(colors.text_accent)
-                            })
-                            .on_mouse_down(
-                                MouseButton::Left,
-                                cx.listener(move |editor, _, cx| {
-                                    cx.stop_propagation();
-                                    editor
-                                        .confirm_code_action(
-                                            &ConfirmCodeAction {
-                                                item_ix: Some(item_ix),
-                                            },
-                                            cx,
-                                        )
-                                        .map(|task| task.detach_and_log_err(cx));
-                                }),
-                            )
-                            // TASK: It would be good to make lsp_action.title a SharedString to avoid allocating here.
-                            .child(SharedString::from(action.lsp_action.title.clone()))
-                    })
-                    .collect()
-            },
-        )
-        .elevation_1(cx)
-        .px_2()
-        .py_1()
-        .max_h(max_height)
-        .track_scroll(self.scroll_handle.clone())
-        .with_width_from_item(
-            self.actions
-                .iter()
-                .enumerate()
-                .max_by_key(|(_, action)| action.lsp_action.title.chars().count())
-                .map(|(ix, _)| ix),
-        )
-        .into_any_element();
-
-        if self.deployed_from_indicator {
-            *cursor_position.column_mut() = 0;
-        }
-
-        (cursor_position, element)
-    }
-}
-
-pub struct CopilotState {
-    excerpt_id: Option<ExcerptId>,
-    pending_refresh: Task<Option<()>>,
-    pending_cycling_refresh: Task<Option<()>>,
-    cycled: bool,
-    completions: Vec<copilot::Completion>,
-    active_completion_index: usize,
-    suggestion: Option<Inlay>,
-}
-
-impl Default for CopilotState {
-    fn default() -> Self {
-        Self {
-            excerpt_id: None,
-            pending_cycling_refresh: Task::ready(Some(())),
-            pending_refresh: Task::ready(Some(())),
-            completions: Default::default(),
-            active_completion_index: 0,
-            cycled: false,
-            suggestion: None,
-        }
-    }
-}
-
-impl CopilotState {
-    fn active_completion(&self) -> Option<&copilot::Completion> {
-        self.completions.get(self.active_completion_index)
-    }
-
-    fn text_for_active_completion(
-        &self,
-        cursor: Anchor,
-        buffer: &MultiBufferSnapshot,
-    ) -> Option<&str> {
-        use language::ToOffset as _;
-
-        let completion = self.active_completion()?;
-        let excerpt_id = self.excerpt_id?;
-        let completion_buffer = buffer.buffer_for_excerpt(excerpt_id)?;
-        if excerpt_id != cursor.excerpt_id
-            || !completion.range.start.is_valid(completion_buffer)
-            || !completion.range.end.is_valid(completion_buffer)
-        {
-            return None;
-        }
-
-        let mut completion_range = completion.range.to_offset(&completion_buffer);
-        let prefix_len = Self::common_prefix(
-            completion_buffer.chars_for_range(completion_range.clone()),
-            completion.text.chars(),
-        );
-        completion_range.start += prefix_len;
-        let suffix_len = Self::common_prefix(
-            completion_buffer.reversed_chars_for_range(completion_range.clone()),
-            completion.text[prefix_len..].chars().rev(),
-        );
-        completion_range.end = completion_range.end.saturating_sub(suffix_len);
-
-        if completion_range.is_empty()
-            && completion_range.start == cursor.text_anchor.to_offset(&completion_buffer)
-        {
-            Some(&completion.text[prefix_len..completion.text.len() - suffix_len])
-        } else {
-            None
-        }
-    }
-
-    fn cycle_completions(&mut self, direction: Direction) {
-        match direction {
-            Direction::Prev => {
-                self.active_completion_index = if self.active_completion_index == 0 {
-                    self.completions.len().saturating_sub(1)
-                } else {
-                    self.active_completion_index - 1
-                };
-            }
-            Direction::Next => {
-                if self.completions.len() == 0 {
-                    self.active_completion_index = 0
-                } else {
-                    self.active_completion_index =
-                        (self.active_completion_index + 1) % self.completions.len();
-                }
-            }
-        }
-    }
-
-    fn push_completion(&mut self, new_completion: copilot::Completion) {
-        for completion in &self.completions {
-            if completion.text == new_completion.text && completion.range == new_completion.range {
-                return;
-            }
-        }
-        self.completions.push(new_completion);
-    }
-
-    fn common_prefix<T1: Iterator<Item = char>, T2: Iterator<Item = char>>(a: T1, b: T2) -> usize {
-        a.zip(b)
-            .take_while(|(a, b)| a == b)
-            .map(|(a, _)| a.len_utf8())
-            .sum()
-    }
-}
-
-#[derive(Debug)]
-struct ActiveDiagnosticGroup {
-    primary_range: Range<Anchor>,
-    primary_message: String,
-    blocks: HashMap<BlockId, Diagnostic>,
-    is_valid: bool,
-}
-
-#[derive(Serialize, Deserialize)]
-pub struct ClipboardSelection {
-    pub len: usize,
-    pub is_entire_line: bool,
-    pub first_line_indent: u32,
-}
-
-#[derive(Debug)]
-pub struct NavigationData {
-    cursor_anchor: Anchor,
-    cursor_position: Point,
-    scroll_anchor: ScrollAnchor,
-    scroll_top_row: u32,
-}
-
-pub struct EditorCreated(pub View<Editor>);
-
-enum GotoDefinitionKind {
-    Symbol,
-    Type,
-}
-
-#[derive(Debug, Clone)]
-enum InlayHintRefreshReason {
-    Toggle(bool),
-    SettingsChange(InlayHintSettings),
-    NewLinesShown,
-    BufferEdited(HashSet<Arc<Language>>),
-    RefreshRequested,
-    ExcerptsRemoved(Vec<ExcerptId>),
-}
-impl InlayHintRefreshReason {
-    fn description(&self) -> &'static str {
-        match self {
-            Self::Toggle(_) => "toggle",
-            Self::SettingsChange(_) => "settings change",
-            Self::NewLinesShown => "new lines shown",
-            Self::BufferEdited(_) => "buffer edited",
-            Self::RefreshRequested => "refresh requested",
-            Self::ExcerptsRemoved(_) => "excerpts removed",
-        }
-    }
-}
-
-impl Editor {
-    pub fn single_line(cx: &mut ViewContext<Self>) -> Self {
-        let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), String::new()));
-        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
-        Self::new(EditorMode::SingleLine, buffer, None, cx)
-    }
-
-    pub fn multi_line(cx: &mut ViewContext<Self>) -> Self {
-        let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), String::new()));
-        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
-        Self::new(EditorMode::Full, buffer, None, cx)
-    }
-
-    pub fn auto_height(max_lines: usize, cx: &mut ViewContext<Self>) -> Self {
-        let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), String::new()));
-        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
-        Self::new(EditorMode::AutoHeight { max_lines }, buffer, None, cx)
-    }
-
-    pub fn for_buffer(
-        buffer: Model<Buffer>,
-        project: Option<Model<Project>>,
-        cx: &mut ViewContext<Self>,
-    ) -> Self {
-        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
-        Self::new(EditorMode::Full, buffer, project, cx)
-    }
-
-    pub fn for_multibuffer(
-        buffer: Model<MultiBuffer>,
-        project: Option<Model<Project>>,
-        cx: &mut ViewContext<Self>,
-    ) -> Self {
-        Self::new(EditorMode::Full, buffer, project, cx)
-    }
-
-    pub fn clone(&self, cx: &mut ViewContext<Self>) -> Self {
-        let mut clone = Self::new(self.mode, self.buffer.clone(), self.project.clone(), cx);
-        self.display_map.update(cx, |display_map, cx| {
-            let snapshot = display_map.snapshot(cx);
-            clone.display_map.update(cx, |display_map, cx| {
-                display_map.set_state(&snapshot, cx);
-            });
-        });
-        clone.selections.clone_state(&self.selections);
-        clone.scroll_manager.clone_state(&self.scroll_manager);
-        clone.searchable = self.searchable;
-        clone
-    }
-
-    fn new(
-        mode: EditorMode,
-        buffer: Model<MultiBuffer>,
-        project: Option<Model<Project>>,
-        cx: &mut ViewContext<Self>,
-    ) -> Self {
-        let style = cx.text_style();
-        let font_size = style.font_size.to_pixels(cx.rem_size());
-        let display_map = cx.new_model(|cx| {
-            DisplayMap::new(buffer.clone(), style.font(), font_size, None, 2, 1, cx)
-        });
-
-        let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
-
-        let blink_manager = cx.new_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
-
-        let soft_wrap_mode_override =
-            (mode == EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
-
-        let mut project_subscriptions = Vec::new();
-        if mode == EditorMode::Full {
-            if let Some(project) = project.as_ref() {
-                if buffer.read(cx).is_singleton() {
-                    project_subscriptions.push(cx.observe(project, |_, _, cx| {
-                        cx.emit(EditorEvent::TitleChanged);
-                    }));
-                }
-                project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| {
-                    if let project::Event::RefreshInlayHints = event {
-                        editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
-                    };
-                }));
-            }
-        }
-
-        let inlay_hint_settings = inlay_hint_settings(
-            selections.newest_anchor().head(),
-            &buffer.read(cx).snapshot(cx),
-            cx,
-        );
-
-        let focus_handle = cx.focus_handle();
-        cx.on_focus(&focus_handle, Self::handle_focus).detach();
-        cx.on_blur(&focus_handle, Self::handle_blur).detach();
-
-        let mut this = Self {
-            handle: cx.view().downgrade(),
-            focus_handle,
-            buffer: buffer.clone(),
-            display_map: display_map.clone(),
-            selections,
-            scroll_manager: ScrollManager::new(),
-            columnar_selection_tail: None,
-            add_selections_state: None,
-            select_next_state: None,
-            select_prev_state: None,
-            selection_history: Default::default(),
-            autoclose_regions: Default::default(),
-            snippet_stack: Default::default(),
-            select_larger_syntax_node_stack: Vec::new(),
-            ime_transaction: Default::default(),
-            active_diagnostics: None,
-            soft_wrap_mode_override,
-            collaboration_hub: project.clone().map(|project| Box::new(project) as _),
-            project,
-            blink_manager: blink_manager.clone(),
-            show_local_selections: true,
-            mode,
-            show_gutter: mode == EditorMode::Full,
-            show_wrap_guides: None,
-            placeholder_text: None,
-            highlighted_rows: None,
-            background_highlights: Default::default(),
-            inlay_background_highlights: Default::default(),
-            nav_history: None,
-            context_menu: RwLock::new(None),
-            mouse_context_menu: None,
-            completion_tasks: Default::default(),
-            next_completion_id: 0,
-            next_inlay_id: 0,
-            available_code_actions: Default::default(),
-            code_actions_task: Default::default(),
-            document_highlights_task: Default::default(),
-            pending_rename: Default::default(),
-            searchable: true,
-            cursor_shape: Default::default(),
-            autoindent_mode: Some(AutoindentMode::EachLine),
-            collapse_matches: false,
-            workspace: None,
-            keymap_context_layers: Default::default(),
-            input_enabled: true,
-            read_only: false,
-            leader_peer_id: None,
-            remote_id: None,
-            hover_state: Default::default(),
-            link_go_to_definition_state: Default::default(),
-            copilot_state: Default::default(),
-            inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
-            gutter_hovered: false,
-            pixel_position_of_newest_cursor: None,
-            gutter_width: Default::default(),
-            style: None,
-            editor_actions: Default::default(),
-            _subscriptions: vec![
-                cx.observe(&buffer, Self::on_buffer_changed),
-                cx.subscribe(&buffer, Self::on_buffer_event),
-                cx.observe(&display_map, Self::on_display_map_changed),
-                cx.observe(&blink_manager, |_, _, cx| cx.notify()),
-                cx.observe_global::<SettingsStore>(Self::settings_changed),
-                cx.observe_window_activation(|editor, cx| {
-                    let active = cx.is_window_active();
-                    editor.blink_manager.update(cx, |blink_manager, cx| {
-                        if active {
-                            blink_manager.enable(cx);
-                        } else {
-                            blink_manager.show_cursor(cx);
-                            blink_manager.disable(cx);
-                        }
-                    });
-                }),
-            ],
-        };
-
-        this._subscriptions.extend(project_subscriptions);
-
-        this.end_selection(cx);
-        this.scroll_manager.show_scrollbar(cx);
-
-        // todo!("use a different mechanism")
-        // let editor_created_event = EditorCreated(cx.handle());
-        // cx.emit_global(editor_created_event);
-
-        if mode == EditorMode::Full {
-            let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
-            cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
-        }
-
-        this.report_editor_event("open", None, cx);
-        this
-    }
-
-    fn key_context(&self, cx: &AppContext) -> KeyContext {
-        let mut key_context = KeyContext::default();
-        key_context.add("Editor");
-        let mode = match self.mode {
-            EditorMode::SingleLine => "single_line",
-            EditorMode::AutoHeight { .. } => "auto_height",
-            EditorMode::Full => "full",
-        };
-        key_context.set("mode", mode);
-        if self.pending_rename.is_some() {
-            key_context.add("renaming");
-        }
-        if self.context_menu_visible() {
-            match self.context_menu.read().as_ref() {
-                Some(ContextMenu::Completions(_)) => {
-                    key_context.add("menu");
-                    key_context.add("showing_completions")
-                }
-                Some(ContextMenu::CodeActions(_)) => {
-                    key_context.add("menu");
-                    key_context.add("showing_code_actions")
-                }
-                None => {}
-            }
-        }
-
-        for layer in self.keymap_context_layers.values() {
-            key_context.extend(layer);
-        }
-
-        if let Some(extension) = self
-            .buffer
-            .read(cx)
-            .as_singleton()
-            .and_then(|buffer| buffer.read(cx).file()?.path().extension()?.to_str())
-        {
-            key_context.set("extension", extension.to_string());
-        }
-
-        key_context
-    }
-
-    pub fn new_file(
-        workspace: &mut Workspace,
-        _: &workspace::NewFile,
-        cx: &mut ViewContext<Workspace>,
-    ) {
-        let project = workspace.project().clone();
-        if project.read(cx).is_remote() {
-            cx.propagate();
-        } else if let Some(buffer) = project
-            .update(cx, |project, cx| project.create_buffer("", None, cx))
-            .log_err()
-        {
-            workspace.add_item(
-                Box::new(cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx))),
-                cx,
-            );
-        }
-    }
-
-    pub fn new_file_in_direction(
-        workspace: &mut Workspace,
-        action: &workspace::NewFileInDirection,
-        cx: &mut ViewContext<Workspace>,
-    ) {
-        let project = workspace.project().clone();
-        if project.read(cx).is_remote() {
-            cx.propagate();
-        } else if let Some(buffer) = project
-            .update(cx, |project, cx| project.create_buffer("", None, cx))
-            .log_err()
-        {
-            workspace.split_item(
-                action.0,
-                Box::new(cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx))),
-                cx,
-            );
-        }
-    }
-
-    pub fn replica_id(&self, cx: &AppContext) -> ReplicaId {
-        self.buffer.read(cx).replica_id()
-    }
-
-    pub fn leader_peer_id(&self) -> Option<PeerId> {
-        self.leader_peer_id
-    }
-
-    pub fn buffer(&self) -> &Model<MultiBuffer> {
-        &self.buffer
-    }
-
-    pub fn workspace(&self) -> Option<View<Workspace>> {
-        self.workspace.as_ref()?.0.upgrade()
-    }
-
-    pub fn pane(&self, cx: &AppContext) -> Option<View<Pane>> {
-        self.workspace()?.read(cx).pane_for(&self.handle.upgrade()?)
-    }
-
-    pub fn title<'a>(&self, cx: &'a AppContext) -> Cow<'a, str> {
-        self.buffer().read(cx).title(cx)
-    }
-
-    pub fn snapshot(&mut self, cx: &mut WindowContext) -> EditorSnapshot {
-        EditorSnapshot {
-            mode: self.mode,
-            show_gutter: self.show_gutter,
-            display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
-            scroll_anchor: self.scroll_manager.anchor(),
-            ongoing_scroll: self.scroll_manager.ongoing_scroll(),
-            placeholder_text: self.placeholder_text.clone(),
-            is_focused: self.focus_handle.is_focused(cx),
-        }
-    }
-
-    //     pub fn language_at<'a, T: ToOffset>(
-    //         &self,
-    //         point: T,
-    //         cx: &'a AppContext,
-    //     ) -> Option<Arc<Language>> {
-    //         self.buffer.read(cx).language_at(point, cx)
-    //     }
-
-    //     pub fn file_at<'a, T: ToOffset>(&self, point: T, cx: &'a AppContext) -> Option<Arc<dyn File>> {
-    //         self.buffer.read(cx).read(cx).file_at(point).cloned()
-    //     }
-
-    pub fn active_excerpt(
-        &self,
-        cx: &AppContext,
-    ) -> Option<(ExcerptId, Model<Buffer>, Range<text::Anchor>)> {
-        self.buffer
-            .read(cx)
-            .excerpt_containing(self.selections.newest_anchor().head(), cx)
-    }
-
-    //     pub fn style(&self, cx: &AppContext) -> EditorStyle {
-    //         build_style(
-    //             settings::get::<ThemeSettings>(cx),
-    //             self.get_field_editor_theme.as_deref(),
-    //             self.override_text_style.as_deref(),
-    //             cx,
-    //         )
-    //     }
-
-    pub fn mode(&self) -> EditorMode {
-        self.mode
-    }
-
-    pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
-        self.collaboration_hub.as_deref()
-    }
-
-    pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
-        self.collaboration_hub = Some(hub);
-    }
-
-    pub fn placeholder_text(&self) -> Option<&str> {
-        self.placeholder_text.as_deref()
-    }
-
-    pub fn set_placeholder_text(
-        &mut self,
-        placeholder_text: impl Into<Arc<str>>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let placeholder_text = Some(placeholder_text.into());
-        if self.placeholder_text != placeholder_text {
-            self.placeholder_text = placeholder_text;
-            cx.notify();
-        }
-    }
-
-    pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut ViewContext<Self>) {
-        self.cursor_shape = cursor_shape;
-        cx.notify();
-    }
-
-    pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
-        self.collapse_matches = collapse_matches;
-    }
-
-    pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
-        if self.collapse_matches {
-            return range.start..range.start;
-        }
-        range.clone()
-    }
-
-    pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut ViewContext<Self>) {
-        if self.display_map.read(cx).clip_at_line_ends != clip {
-            self.display_map
-                .update(cx, |map, _| map.clip_at_line_ends = clip);
-        }
-    }
-
-    pub fn set_keymap_context_layer<Tag: 'static>(
-        &mut self,
-        context: KeyContext,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.keymap_context_layers
-            .insert(TypeId::of::<Tag>(), context);
-        cx.notify();
-    }
-
-    pub fn remove_keymap_context_layer<Tag: 'static>(&mut self, cx: &mut ViewContext<Self>) {
-        self.keymap_context_layers.remove(&TypeId::of::<Tag>());
-        cx.notify();
-    }
-
-    pub fn set_input_enabled(&mut self, input_enabled: bool) {
-        self.input_enabled = input_enabled;
-    }
-
-    pub fn set_autoindent(&mut self, autoindent: bool) {
-        if autoindent {
-            self.autoindent_mode = Some(AutoindentMode::EachLine);
-        } else {
-            self.autoindent_mode = None;
-        }
-    }
-
-    pub fn read_only(&self) -> bool {
-        self.read_only
-    }
-
-    pub fn set_read_only(&mut self, read_only: bool) {
-        self.read_only = read_only;
-    }
-
-    fn selections_did_change(
-        &mut self,
-        local: bool,
-        old_cursor_position: &Anchor,
-        cx: &mut ViewContext<Self>,
-    ) {
-        if self.focus_handle.is_focused(cx) && self.leader_peer_id.is_none() {
-            self.buffer.update(cx, |buffer, cx| {
-                buffer.set_active_selections(
-                    &self.selections.disjoint_anchors(),
-                    self.selections.line_mode,
-                    self.cursor_shape,
-                    cx,
-                )
-            });
-        }
-
-        let display_map = self
-            .display_map
-            .update(cx, |display_map, cx| display_map.snapshot(cx));
-        let buffer = &display_map.buffer_snapshot;
-        self.add_selections_state = None;
-        self.select_next_state = None;
-        self.select_prev_state = None;
-        self.select_larger_syntax_node_stack.clear();
-        self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
-        self.snippet_stack
-            .invalidate(&self.selections.disjoint_anchors(), buffer);
-        self.take_rename(false, cx);
-
-        let new_cursor_position = self.selections.newest_anchor().head();
-
-        self.push_to_nav_history(
-            old_cursor_position.clone(),
-            Some(new_cursor_position.to_point(buffer)),
-            cx,
-        );
-
-        if local {
-            let new_cursor_position = self.selections.newest_anchor().head();
-            let mut context_menu = self.context_menu.write();
-            let completion_menu = match context_menu.as_ref() {
-                Some(ContextMenu::Completions(menu)) => Some(menu),
-
-                _ => {
-                    *context_menu = None;
-                    None
-                }
-            };
-
-            if let Some(completion_menu) = completion_menu {
-                let cursor_position = new_cursor_position.to_offset(buffer);
-                let (word_range, kind) =
-                    buffer.surrounding_word(completion_menu.initial_position.clone());
-                if kind == Some(CharKind::Word)
-                    && word_range.to_inclusive().contains(&cursor_position)
-                {
-                    let mut completion_menu = completion_menu.clone();
-                    drop(context_menu);
-
-                    let query = Self::completion_query(buffer, cursor_position);
-                    cx.spawn(move |this, mut cx| async move {
-                        completion_menu
-                            .filter(query.as_deref(), cx.background_executor().clone())
-                            .await;
-
-                        this.update(&mut cx, |this, cx| {
-                            let mut context_menu = this.context_menu.write();
-                            let Some(ContextMenu::Completions(menu)) = context_menu.as_ref() else {
-                                return;
-                            };
-
-                            if menu.id > completion_menu.id {
-                                return;
-                            }
-
-                            *context_menu = Some(ContextMenu::Completions(completion_menu));
-                            drop(context_menu);
-                            cx.notify();
-                        })
-                    })
-                    .detach();
-
-                    self.show_completions(&ShowCompletions, cx);
-                } else {
-                    drop(context_menu);
-                    self.hide_context_menu(cx);
-                }
-            } else {
-                drop(context_menu);
-            }
-
-            hide_hover(self, cx);
-
-            if old_cursor_position.to_display_point(&display_map).row()
-                != new_cursor_position.to_display_point(&display_map).row()
-            {
-                self.available_code_actions.take();
-            }
-            self.refresh_code_actions(cx);
-            self.refresh_document_highlights(cx);
-            refresh_matching_bracket_highlights(self, cx);
-            self.discard_copilot_suggestion(cx);
-        }
-
-        self.blink_manager.update(cx, BlinkManager::pause_blinking);
-        cx.emit(EditorEvent::SelectionsChanged { local });
-
-        if self.selections.disjoint_anchors().len() == 1 {
-            cx.emit(SearchEvent::ActiveMatchChanged)
-        }
-
-        cx.notify();
-    }
-
-    pub fn change_selections<R>(
-        &mut self,
-        autoscroll: Option<Autoscroll>,
-        cx: &mut ViewContext<Self>,
-        change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
-    ) -> R {
-        let old_cursor_position = self.selections.newest_anchor().head();
-        self.push_to_selection_history();
-
-        let (changed, result) = self.selections.change_with(cx, change);
-
-        if changed {
-            if let Some(autoscroll) = autoscroll {
-                self.request_autoscroll(autoscroll, cx);
-            }
-            self.selections_did_change(true, &old_cursor_position, cx);
-        }
-
-        result
-    }
-
-    pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut ViewContext<Self>)
-    where
-        I: IntoIterator<Item = (Range<S>, T)>,
-        S: ToOffset,
-        T: Into<Arc<str>>,
-    {
-        if self.read_only {
-            return;
-        }
-
-        self.buffer
-            .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
-    }
-
-    pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut ViewContext<Self>)
-    where
-        I: IntoIterator<Item = (Range<S>, T)>,
-        S: ToOffset,
-        T: Into<Arc<str>>,
-    {
-        if self.read_only {
-            return;
-        }
-
-        self.buffer.update(cx, |buffer, cx| {
-            buffer.edit(edits, self.autoindent_mode.clone(), cx)
-        });
-    }
-
-    pub fn edit_with_block_indent<I, S, T>(
-        &mut self,
-        edits: I,
-        original_indent_columns: Vec<u32>,
-        cx: &mut ViewContext<Self>,
-    ) where
-        I: IntoIterator<Item = (Range<S>, T)>,
-        S: ToOffset,
-        T: Into<Arc<str>>,
-    {
-        if self.read_only {
-            return;
-        }
-
-        self.buffer.update(cx, |buffer, cx| {
-            buffer.edit(
-                edits,
-                Some(AutoindentMode::Block {
-                    original_indent_columns,
-                }),
-                cx,
-            )
-        });
-    }
-
-    fn select(&mut self, phase: SelectPhase, cx: &mut ViewContext<Self>) {
-        self.hide_context_menu(cx);
-
-        match phase {
-            SelectPhase::Begin {
-                position,
-                add,
-                click_count,
-            } => self.begin_selection(position, add, click_count, cx),
-            SelectPhase::BeginColumnar {
-                position,
-                goal_column,
-            } => self.begin_columnar_selection(position, goal_column, cx),
-            SelectPhase::Extend {
-                position,
-                click_count,
-            } => self.extend_selection(position, click_count, cx),
-            SelectPhase::Update {
-                position,
-                goal_column,
-                scroll_position,
-            } => self.update_selection(position, goal_column, scroll_position, cx),
-            SelectPhase::End => self.end_selection(cx),
-        }
-    }
-
-    fn extend_selection(
-        &mut self,
-        position: DisplayPoint,
-        click_count: usize,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-        let tail = self.selections.newest::<usize>(cx).tail();
-        self.begin_selection(position, false, click_count, cx);
-
-        let position = position.to_offset(&display_map, Bias::Left);
-        let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
-
-        let mut pending_selection = self
-            .selections
-            .pending_anchor()
-            .expect("extend_selection not called with pending selection");
-        if position >= tail {
-            pending_selection.start = tail_anchor;
-        } else {
-            pending_selection.end = tail_anchor;
-            pending_selection.reversed = true;
-        }
-
-        let mut pending_mode = self.selections.pending_mode().unwrap();
-        match &mut pending_mode {
-            SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
-            _ => {}
-        }
-
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.set_pending(pending_selection, pending_mode)
-        });
-    }
-
-    fn begin_selection(
-        &mut self,
-        position: DisplayPoint,
-        add: bool,
-        click_count: usize,
-        cx: &mut ViewContext<Self>,
-    ) {
-        if !self.focus_handle.is_focused(cx) {
-            cx.focus(&self.focus_handle);
-        }
-
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-        let buffer = &display_map.buffer_snapshot;
-        let newest_selection = self.selections.newest_anchor().clone();
-        let position = display_map.clip_point(position, Bias::Left);
-
-        let start;
-        let end;
-        let mode;
-        let auto_scroll;
-        match click_count {
-            1 => {
-                start = buffer.anchor_before(position.to_point(&display_map));
-                end = start.clone();
-                mode = SelectMode::Character;
-                auto_scroll = true;
-            }
-            2 => {
-                let range = movement::surrounding_word(&display_map, position);
-                start = buffer.anchor_before(range.start.to_point(&display_map));
-                end = buffer.anchor_before(range.end.to_point(&display_map));
-                mode = SelectMode::Word(start.clone()..end.clone());
-                auto_scroll = true;
-            }
-            3 => {
-                let position = display_map
-                    .clip_point(position, Bias::Left)
-                    .to_point(&display_map);
-                let line_start = display_map.prev_line_boundary(position).0;
-                let next_line_start = buffer.clip_point(
-                    display_map.next_line_boundary(position).0 + Point::new(1, 0),
-                    Bias::Left,
-                );
-                start = buffer.anchor_before(line_start);
-                end = buffer.anchor_before(next_line_start);
-                mode = SelectMode::Line(start.clone()..end.clone());
-                auto_scroll = true;
-            }
-            _ => {
-                start = buffer.anchor_before(0);
-                end = buffer.anchor_before(buffer.len());
-                mode = SelectMode::All;
-                auto_scroll = false;
-            }
-        }
-
-        self.change_selections(auto_scroll.then(|| Autoscroll::newest()), cx, |s| {
-            if !add {
-                s.clear_disjoint();
-            } else if click_count > 1 {
-                s.delete(newest_selection.id)
-            }
-
-            s.set_pending_anchor_range(start..end, mode);
-        });
-    }
-
-    fn begin_columnar_selection(
-        &mut self,
-        position: DisplayPoint,
-        goal_column: u32,
-        cx: &mut ViewContext<Self>,
-    ) {
-        if !self.focus_handle.is_focused(cx) {
-            cx.focus(&self.focus_handle);
-        }
-
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-        let tail = self.selections.newest::<Point>(cx).tail();
-        self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
-
-        self.select_columns(
-            tail.to_display_point(&display_map),
-            position,
-            goal_column,
-            &display_map,
-            cx,
-        );
-    }
-
-    fn update_selection(
-        &mut self,
-        position: DisplayPoint,
-        goal_column: u32,
-        scroll_position: gpui::Point<f32>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-
-        if let Some(tail) = self.columnar_selection_tail.as_ref() {
-            let tail = tail.to_display_point(&display_map);
-            self.select_columns(tail, position, goal_column, &display_map, cx);
-        } else if let Some(mut pending) = self.selections.pending_anchor() {
-            let buffer = self.buffer.read(cx).snapshot(cx);
-            let head;
-            let tail;
-            let mode = self.selections.pending_mode().unwrap();
-            match &mode {
-                SelectMode::Character => {
-                    head = position.to_point(&display_map);
-                    tail = pending.tail().to_point(&buffer);
-                }
-                SelectMode::Word(original_range) => {
-                    let original_display_range = original_range.start.to_display_point(&display_map)
-                        ..original_range.end.to_display_point(&display_map);
-                    let original_buffer_range = original_display_range.start.to_point(&display_map)
-                        ..original_display_range.end.to_point(&display_map);
-                    if movement::is_inside_word(&display_map, position)
-                        || original_display_range.contains(&position)
-                    {
-                        let word_range = movement::surrounding_word(&display_map, position);
-                        if word_range.start < original_display_range.start {
-                            head = word_range.start.to_point(&display_map);
-                        } else {
-                            head = word_range.end.to_point(&display_map);
-                        }
-                    } else {
-                        head = position.to_point(&display_map);
-                    }
-
-                    if head <= original_buffer_range.start {
-                        tail = original_buffer_range.end;
-                    } else {
-                        tail = original_buffer_range.start;
-                    }
-                }
-                SelectMode::Line(original_range) => {
-                    let original_range = original_range.to_point(&display_map.buffer_snapshot);
-
-                    let position = display_map
-                        .clip_point(position, Bias::Left)
-                        .to_point(&display_map);
-                    let line_start = display_map.prev_line_boundary(position).0;
-                    let next_line_start = buffer.clip_point(
-                        display_map.next_line_boundary(position).0 + Point::new(1, 0),
-                        Bias::Left,
-                    );
-
-                    if line_start < original_range.start {
-                        head = line_start
-                    } else {
-                        head = next_line_start
-                    }
-
-                    if head <= original_range.start {
-                        tail = original_range.end;
-                    } else {
-                        tail = original_range.start;
-                    }
-                }
-                SelectMode::All => {
-                    return;
-                }
-            };
-
-            if head < tail {
-                pending.start = buffer.anchor_before(head);
-                pending.end = buffer.anchor_before(tail);
-                pending.reversed = true;
-            } else {
-                pending.start = buffer.anchor_before(tail);
-                pending.end = buffer.anchor_before(head);
-                pending.reversed = false;
-            }
-
-            self.change_selections(None, cx, |s| {
-                s.set_pending(pending, mode);
-            });
-        } else {
-            log::error!("update_selection dispatched with no pending selection");
-            return;
-        }
-
-        self.set_scroll_position(scroll_position, cx);
-        cx.notify();
-    }
-
-    fn end_selection(&mut self, cx: &mut ViewContext<Self>) {
-        self.columnar_selection_tail.take();
-        if self.selections.pending_anchor().is_some() {
-            let selections = self.selections.all::<usize>(cx);
-            self.change_selections(None, cx, |s| {
-                s.select(selections);
-                s.clear_pending();
-            });
-        }
-    }
-
-    fn select_columns(
-        &mut self,
-        tail: DisplayPoint,
-        head: DisplayPoint,
-        goal_column: u32,
-        display_map: &DisplaySnapshot,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let start_row = cmp::min(tail.row(), head.row());
-        let end_row = cmp::max(tail.row(), head.row());
-        let start_column = cmp::min(tail.column(), goal_column);
-        let end_column = cmp::max(tail.column(), goal_column);
-        let reversed = start_column < tail.column();
-
-        let selection_ranges = (start_row..=end_row)
-            .filter_map(|row| {
-                if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
-                    let start = display_map
-                        .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
-                        .to_point(display_map);
-                    let end = display_map
-                        .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
-                        .to_point(display_map);
-                    if reversed {
-                        Some(end..start)
-                    } else {
-                        Some(start..end)
-                    }
-                } else {
-                    None
-                }
-            })
-            .collect::<Vec<_>>();
-
-        self.change_selections(None, cx, |s| {
-            s.select_ranges(selection_ranges);
-        });
-        cx.notify();
-    }
-
-    pub fn has_pending_nonempty_selection(&self) -> bool {
-        let pending_nonempty_selection = match self.selections.pending_anchor() {
-            Some(Selection { start, end, .. }) => start != end,
-            None => false,
-        };
-        pending_nonempty_selection || self.columnar_selection_tail.is_some()
-    }
-
-    pub fn has_pending_selection(&self) -> bool {
-        self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
-    }
-
-    pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
-        if self.take_rename(false, cx).is_some() {
-            return;
-        }
-
-        if hide_hover(self, cx) {
-            return;
-        }
-
-        if self.hide_context_menu(cx).is_some() {
-            return;
-        }
-
-        if self.discard_copilot_suggestion(cx) {
-            return;
-        }
-
-        if self.snippet_stack.pop().is_some() {
-            return;
-        }
-
-        if self.mode == EditorMode::Full {
-            if self.active_diagnostics.is_some() {
-                self.dismiss_diagnostics(cx);
-                return;
-            }
-
-            if self.change_selections(Some(Autoscroll::fit()), cx, |s| s.try_cancel()) {
-                return;
-            }
-        }
-
-        cx.propagate();
-    }
-
-    pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext<Self>) {
-        let text: Arc<str> = text.into();
-
-        if self.read_only {
-            return;
-        }
-
-        let selections = self.selections.all_adjusted(cx);
-        let mut brace_inserted = false;
-        let mut edits = Vec::new();
-        let mut new_selections = Vec::with_capacity(selections.len());
-        let mut new_autoclose_regions = Vec::new();
-        let snapshot = self.buffer.read(cx).read(cx);
-
-        for (selection, autoclose_region) in
-            self.selections_with_autoclose_regions(selections, &snapshot)
-        {
-            if let Some(scope) = snapshot.language_scope_at(selection.head()) {
-                // Determine if the inserted text matches the opening or closing
-                // bracket of any of this language's bracket pairs.
-                let mut bracket_pair = None;
-                let mut is_bracket_pair_start = false;
-                if !text.is_empty() {
-                    // `text` can be empty when an user is using IME (e.g. Chinese Wubi Simplified)
-                    //  and they are removing the character that triggered IME popup.
-                    for (pair, enabled) in scope.brackets() {
-                        if enabled && pair.close && pair.start.ends_with(text.as_ref()) {
-                            bracket_pair = Some(pair.clone());
-                            is_bracket_pair_start = true;
-                            break;
-                        } else if pair.end.as_str() == text.as_ref() {
-                            bracket_pair = Some(pair.clone());
-                            break;
-                        }
-                    }
-                }
-
-                if let Some(bracket_pair) = bracket_pair {
-                    if selection.is_empty() {
-                        if is_bracket_pair_start {
-                            let prefix_len = bracket_pair.start.len() - text.len();
-
-                            // If the inserted text is a suffix of an opening bracket and the
-                            // selection is preceded by the rest of the opening bracket, then
-                            // insert the closing bracket.
-                            let following_text_allows_autoclose = snapshot
-                                .chars_at(selection.start)
-                                .next()
-                                .map_or(true, |c| scope.should_autoclose_before(c));
-                            let preceding_text_matches_prefix = prefix_len == 0
-                                || (selection.start.column >= (prefix_len as u32)
-                                    && snapshot.contains_str_at(
-                                        Point::new(
-                                            selection.start.row,
-                                            selection.start.column - (prefix_len as u32),
-                                        ),
-                                        &bracket_pair.start[..prefix_len],
-                                    ));
-                            if following_text_allows_autoclose && preceding_text_matches_prefix {
-                                let anchor = snapshot.anchor_before(selection.end);
-                                new_selections.push((selection.map(|_| anchor), text.len()));
-                                new_autoclose_regions.push((
-                                    anchor,
-                                    text.len(),
-                                    selection.id,
-                                    bracket_pair.clone(),
-                                ));
-                                edits.push((
-                                    selection.range(),
-                                    format!("{}{}", text, bracket_pair.end).into(),
-                                ));
-                                brace_inserted = true;
-                                continue;
-                            }
-                        }
-
-                        if let Some(region) = autoclose_region {
-                            // If the selection is followed by an auto-inserted closing bracket,
-                            // then don't insert that closing bracket again; just move the selection
-                            // past the closing bracket.
-                            let should_skip = selection.end == region.range.end.to_point(&snapshot)
-                                && text.as_ref() == region.pair.end.as_str();
-                            if should_skip {
-                                let anchor = snapshot.anchor_after(selection.end);
-                                new_selections
-                                    .push((selection.map(|_| anchor), region.pair.end.len()));
-                                continue;
-                            }
-                        }
-                    }
-                    // If an opening bracket is 1 character long and is typed while
-                    // text is selected, then surround that text with the bracket pair.
-                    else if is_bracket_pair_start && bracket_pair.start.chars().count() == 1 {
-                        edits.push((selection.start..selection.start, text.clone()));
-                        edits.push((
-                            selection.end..selection.end,
-                            bracket_pair.end.as_str().into(),
-                        ));
-                        brace_inserted = true;
-                        new_selections.push((
-                            Selection {
-                                id: selection.id,
-                                start: snapshot.anchor_after(selection.start),
-                                end: snapshot.anchor_before(selection.end),
-                                reversed: selection.reversed,
-                                goal: selection.goal,
-                            },
-                            0,
-                        ));
-                        continue;
-                    }
-                }
-            }
-
-            // If not handling any auto-close operation, then just replace the selected
-            // text with the given input and move the selection to the end of the
-            // newly inserted text.
-            let anchor = snapshot.anchor_after(selection.end);
-            new_selections.push((selection.map(|_| anchor), 0));
-            edits.push((selection.start..selection.end, text.clone()));
-        }
-
-        drop(snapshot);
-        self.transact(cx, |this, cx| {
-            this.buffer.update(cx, |buffer, cx| {
-                buffer.edit(edits, this.autoindent_mode.clone(), cx);
-            });
-
-            let new_anchor_selections = new_selections.iter().map(|e| &e.0);
-            let new_selection_deltas = new_selections.iter().map(|e| e.1);
-            let snapshot = this.buffer.read(cx).read(cx);
-            let new_selections = resolve_multiple::<usize, _>(new_anchor_selections, &snapshot)
-                .zip(new_selection_deltas)
-                .map(|(selection, delta)| Selection {
-                    id: selection.id,
-                    start: selection.start + delta,
-                    end: selection.end + delta,
-                    reversed: selection.reversed,
-                    goal: SelectionGoal::None,
-                })
-                .collect::<Vec<_>>();
-
-            let mut i = 0;
-            for (position, delta, selection_id, pair) in new_autoclose_regions {
-                let position = position.to_offset(&snapshot) + delta;
-                let start = snapshot.anchor_before(position);
-                let end = snapshot.anchor_after(position);
-                while let Some(existing_state) = this.autoclose_regions.get(i) {
-                    match existing_state.range.start.cmp(&start, &snapshot) {
-                        Ordering::Less => i += 1,
-                        Ordering::Greater => break,
-                        Ordering::Equal => match end.cmp(&existing_state.range.end, &snapshot) {
-                            Ordering::Less => i += 1,
-                            Ordering::Equal => break,
-                            Ordering::Greater => break,
-                        },
-                    }
-                }
-                this.autoclose_regions.insert(
-                    i,
-                    AutocloseRegion {
-                        selection_id,
-                        range: start..end,
-                        pair,
-                    },
-                );
-            }
-
-            drop(snapshot);
-            let had_active_copilot_suggestion = this.has_active_copilot_suggestion(cx);
-            this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
-
-            if !brace_inserted && EditorSettings::get_global(cx).use_on_type_format {
-                if let Some(on_type_format_task) =
-                    this.trigger_on_type_formatting(text.to_string(), cx)
-                {
-                    on_type_format_task.detach_and_log_err(cx);
-                }
-            }
-
-            if had_active_copilot_suggestion {
-                this.refresh_copilot_suggestions(true, cx);
-                if !this.has_active_copilot_suggestion(cx) {
-                    this.trigger_completion_on_input(&text, cx);
-                }
-            } else {
-                this.trigger_completion_on_input(&text, cx);
-                this.refresh_copilot_suggestions(true, cx);
-            }
-        });
-    }
-
-    pub fn newline(&mut self, _: &Newline, cx: &mut ViewContext<Self>) {
-        self.transact(cx, |this, cx| {
-            let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
-                let selections = this.selections.all::<usize>(cx);
-                let multi_buffer = this.buffer.read(cx);
-                let buffer = multi_buffer.snapshot(cx);
-                selections
-                    .iter()
-                    .map(|selection| {
-                        let start_point = selection.start.to_point(&buffer);
-                        let mut indent = buffer.indent_size_for_line(start_point.row);
-                        indent.len = cmp::min(indent.len, start_point.column);
-                        let start = selection.start;
-                        let end = selection.end;
-                        let is_cursor = start == end;
-                        let language_scope = buffer.language_scope_at(start);
-                        let (comment_delimiter, insert_extra_newline) = if let Some(language) =
-                            &language_scope
-                        {
-                            let leading_whitespace_len = buffer
-                                .reversed_chars_at(start)
-                                .take_while(|c| c.is_whitespace() && *c != '\n')
-                                .map(|c| c.len_utf8())
-                                .sum::<usize>();
-
-                            let trailing_whitespace_len = buffer
-                                .chars_at(end)
-                                .take_while(|c| c.is_whitespace() && *c != '\n')
-                                .map(|c| c.len_utf8())
-                                .sum::<usize>();
-
-                            let insert_extra_newline =
-                                language.brackets().any(|(pair, enabled)| {
-                                    let pair_start = pair.start.trim_end();
-                                    let pair_end = pair.end.trim_start();
-
-                                    enabled
-                                        && pair.newline
-                                        && buffer.contains_str_at(
-                                            end + trailing_whitespace_len,
-                                            pair_end,
-                                        )
-                                        && buffer.contains_str_at(
-                                            (start - leading_whitespace_len)
-                                                .saturating_sub(pair_start.len()),
-                                            pair_start,
-                                        )
-                                });
-                            // Comment extension on newline is allowed only for cursor selections
-                            let comment_delimiter = language.line_comment_prefix().filter(|_| {
-                                let is_comment_extension_enabled =
-                                    multi_buffer.settings_at(0, cx).extend_comment_on_newline;
-                                is_cursor && is_comment_extension_enabled
-                            });
-                            let comment_delimiter = if let Some(delimiter) = comment_delimiter {
-                                buffer
-                                    .buffer_line_for_row(start_point.row)
-                                    .is_some_and(|(snapshot, range)| {
-                                        let mut index_of_first_non_whitespace = 0;
-                                        let line_starts_with_comment = snapshot
-                                            .chars_for_range(range)
-                                            .skip_while(|c| {
-                                                let should_skip = c.is_whitespace();
-                                                if should_skip {
-                                                    index_of_first_non_whitespace += 1;
-                                                }
-                                                should_skip
-                                            })
-                                            .take(delimiter.len())
-                                            .eq(delimiter.chars());
-                                        let cursor_is_placed_after_comment_marker =
-                                            index_of_first_non_whitespace + delimiter.len()
-                                                <= start_point.column as usize;
-                                        line_starts_with_comment
-                                            && cursor_is_placed_after_comment_marker
-                                    })
-                                    .then(|| delimiter.clone())
-                            } else {
-                                None
-                            };
-                            (comment_delimiter, insert_extra_newline)
-                        } else {
-                            (None, false)
-                        };
-
-                        let capacity_for_delimiter = comment_delimiter
-                            .as_deref()
-                            .map(str::len)
-                            .unwrap_or_default();
-                        let mut new_text =
-                            String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
-                        new_text.push_str("\n");
-                        new_text.extend(indent.chars());
-                        if let Some(delimiter) = &comment_delimiter {
-                            new_text.push_str(&delimiter);
-                        }
-                        if insert_extra_newline {
-                            new_text = new_text.repeat(2);
-                        }
-
-                        let anchor = buffer.anchor_after(end);
-                        let new_selection = selection.map(|_| anchor);
-                        (
-                            (start..end, new_text),
-                            (insert_extra_newline, new_selection),
-                        )
-                    })
-                    .unzip()
-            };
-
-            this.edit_with_autoindent(edits, cx);
-            let buffer = this.buffer.read(cx).snapshot(cx);
-            let new_selections = selection_fixup_info
-                .into_iter()
-                .map(|(extra_newline_inserted, new_selection)| {
-                    let mut cursor = new_selection.end.to_point(&buffer);
-                    if extra_newline_inserted {
-                        cursor.row -= 1;
-                        cursor.column = buffer.line_len(cursor.row);
-                    }
-                    new_selection.map(|_| cursor)
-                })
-                .collect();
-
-            this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
-            this.refresh_copilot_suggestions(true, cx);
-        });
-    }
-
-    pub fn newline_above(&mut self, _: &NewlineAbove, cx: &mut ViewContext<Self>) {
-        let buffer = self.buffer.read(cx);
-        let snapshot = buffer.snapshot(cx);
-
-        let mut edits = Vec::new();
-        let mut rows = Vec::new();
-        let mut rows_inserted = 0;
-
-        for selection in self.selections.all_adjusted(cx) {
-            let cursor = selection.head();
-            let row = cursor.row;
-
-            let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
-
-            let newline = "\n".to_string();
-            edits.push((start_of_line..start_of_line, newline));
-
-            rows.push(row + rows_inserted);
-            rows_inserted += 1;
-        }
-
-        self.transact(cx, |editor, cx| {
-            editor.edit(edits, cx);
-
-            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                let mut index = 0;
-                s.move_cursors_with(|map, _, _| {
-                    let row = rows[index];
-                    index += 1;
-
-                    let point = Point::new(row, 0);
-                    let boundary = map.next_line_boundary(point).1;
-                    let clipped = map.clip_point(boundary, Bias::Left);
-
-                    (clipped, SelectionGoal::None)
-                });
-            });
-
-            let mut indent_edits = Vec::new();
-            let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
-            for row in rows {
-                let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
-                for (row, indent) in indents {
-                    if indent.len == 0 {
-                        continue;
-                    }
-
-                    let text = match indent.kind {
-                        IndentKind::Space => " ".repeat(indent.len as usize),
-                        IndentKind::Tab => "\t".repeat(indent.len as usize),
-                    };
-                    let point = Point::new(row, 0);
-                    indent_edits.push((point..point, text));
-                }
-            }
-            editor.edit(indent_edits, cx);
-        });
-    }
-
-    pub fn newline_below(&mut self, _: &NewlineBelow, cx: &mut ViewContext<Self>) {
-        let buffer = self.buffer.read(cx);
-        let snapshot = buffer.snapshot(cx);
-
-        let mut edits = Vec::new();
-        let mut rows = Vec::new();
-        let mut rows_inserted = 0;
-
-        for selection in self.selections.all_adjusted(cx) {
-            let cursor = selection.head();
-            let row = cursor.row;
-
-            let point = Point::new(row + 1, 0);
-            let start_of_line = snapshot.clip_point(point, Bias::Left);
-
-            let newline = "\n".to_string();
-            edits.push((start_of_line..start_of_line, newline));
-
-            rows_inserted += 1;
-            rows.push(row + rows_inserted);
-        }
-
-        self.transact(cx, |editor, cx| {
-            editor.edit(edits, cx);
-
-            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                let mut index = 0;
-                s.move_cursors_with(|map, _, _| {
-                    let row = rows[index];
-                    index += 1;
-
-                    let point = Point::new(row, 0);
-                    let boundary = map.next_line_boundary(point).1;
-                    let clipped = map.clip_point(boundary, Bias::Left);
-
-                    (clipped, SelectionGoal::None)
-                });
-            });
-
-            let mut indent_edits = Vec::new();
-            let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
-            for row in rows {
-                let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
-                for (row, indent) in indents {
-                    if indent.len == 0 {
-                        continue;
-                    }
-
-                    let text = match indent.kind {
-                        IndentKind::Space => " ".repeat(indent.len as usize),
-                        IndentKind::Tab => "\t".repeat(indent.len as usize),
-                    };
-                    let point = Point::new(row, 0);
-                    indent_edits.push((point..point, text));
-                }
-            }
-            editor.edit(indent_edits, cx);
-        });
-    }
-
-    pub fn insert(&mut self, text: &str, cx: &mut ViewContext<Self>) {
-        self.insert_with_autoindent_mode(
-            text,
-            Some(AutoindentMode::Block {
-                original_indent_columns: Vec::new(),
-            }),
-            cx,
-        );
-    }
-
-    fn insert_with_autoindent_mode(
-        &mut self,
-        text: &str,
-        autoindent_mode: Option<AutoindentMode>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        if self.read_only {
-            return;
-        }
-
-        let text: Arc<str> = text.into();
-        self.transact(cx, |this, cx| {
-            let old_selections = this.selections.all_adjusted(cx);
-            let selection_anchors = this.buffer.update(cx, |buffer, cx| {
-                let anchors = {
-                    let snapshot = buffer.read(cx);
-                    old_selections
-                        .iter()
-                        .map(|s| {
-                            let anchor = snapshot.anchor_after(s.head());
-                            s.map(|_| anchor)
-                        })
-                        .collect::<Vec<_>>()
-                };
-                buffer.edit(
-                    old_selections
-                        .iter()
-                        .map(|s| (s.start..s.end, text.clone())),
-                    autoindent_mode,
-                    cx,
-                );
-                anchors
-            });
-
-            this.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                s.select_anchors(selection_anchors);
-            })
-        });
-    }
-
-    fn trigger_completion_on_input(&mut self, text: &str, cx: &mut ViewContext<Self>) {
-        if !EditorSettings::get_global(cx).show_completions_on_input {
-            return;
-        }
-
-        let selection = self.selections.newest_anchor();
-        if self
-            .buffer
-            .read(cx)
-            .is_completion_trigger(selection.head(), text, cx)
-        {
-            self.show_completions(&ShowCompletions, cx);
-        } else {
-            self.hide_context_menu(cx);
-        }
-    }
-
-    /// If any empty selections is touching the start of its innermost containing autoclose
-    /// region, expand it to select the brackets.
-    fn select_autoclose_pair(&mut self, cx: &mut ViewContext<Self>) {
-        let selections = self.selections.all::<usize>(cx);
-        let buffer = self.buffer.read(cx).read(cx);
-        let mut new_selections = Vec::new();
-        for (mut selection, region) in self.selections_with_autoclose_regions(selections, &buffer) {
-            if let (Some(region), true) = (region, selection.is_empty()) {
-                let mut range = region.range.to_offset(&buffer);
-                if selection.start == range.start {
-                    if range.start >= region.pair.start.len() {
-                        range.start -= region.pair.start.len();
-                        if buffer.contains_str_at(range.start, &region.pair.start) {
-                            if buffer.contains_str_at(range.end, &region.pair.end) {
-                                range.end += region.pair.end.len();
-                                selection.start = range.start;
-                                selection.end = range.end;
-                            }
-                        }
-                    }
-                }
-            }
-            new_selections.push(selection);
-        }
-
-        drop(buffer);
-        self.change_selections(None, cx, |selections| selections.select(new_selections));
-    }
-
-    /// Iterate the given selections, and for each one, find the smallest surrounding
-    /// autoclose region. This uses the ordering of the selections and the autoclose
-    /// regions to avoid repeated comparisons.
-    fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
-        &'a self,
-        selections: impl IntoIterator<Item = Selection<D>>,
-        buffer: &'a MultiBufferSnapshot,
-    ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
-        let mut i = 0;
-        let mut regions = self.autoclose_regions.as_slice();
-        selections.into_iter().map(move |selection| {
-            let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
-
-            let mut enclosing = None;
-            while let Some(pair_state) = regions.get(i) {
-                if pair_state.range.end.to_offset(buffer) < range.start {
-                    regions = &regions[i + 1..];
-                    i = 0;
-                } else if pair_state.range.start.to_offset(buffer) > range.end {
-                    break;
-                } else {
-                    if pair_state.selection_id == selection.id {
-                        enclosing = Some(pair_state);
-                    }
-                    i += 1;
-                }
-            }
-
-            (selection.clone(), enclosing)
-        })
-    }
-
-    /// Remove any autoclose regions that no longer contain their selection.
-    fn invalidate_autoclose_regions(
-        &mut self,
-        mut selections: &[Selection<Anchor>],
-        buffer: &MultiBufferSnapshot,
-    ) {
-        self.autoclose_regions.retain(|state| {
-            let mut i = 0;
-            while let Some(selection) = selections.get(i) {
-                if selection.end.cmp(&state.range.start, buffer).is_lt() {
-                    selections = &selections[1..];
-                    continue;
-                }
-                if selection.start.cmp(&state.range.end, buffer).is_gt() {
-                    break;
-                }
-                if selection.id == state.selection_id {
-                    return true;
-                } else {
-                    i += 1;
-                }
-            }
-            false
-        });
-    }
-
-    fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
-        let offset = position.to_offset(buffer);
-        let (word_range, kind) = buffer.surrounding_word(offset);
-        if offset > word_range.start && kind == Some(CharKind::Word) {
-            Some(
-                buffer
-                    .text_for_range(word_range.start..offset)
-                    .collect::<String>(),
-            )
-        } else {
-            None
-        }
-    }
-
-    pub fn toggle_inlay_hints(&mut self, _: &ToggleInlayHints, cx: &mut ViewContext<Self>) {
-        self.refresh_inlay_hints(
-            InlayHintRefreshReason::Toggle(!self.inlay_hint_cache.enabled),
-            cx,
-        );
-    }
-
-    pub fn inlay_hints_enabled(&self) -> bool {
-        self.inlay_hint_cache.enabled
-    }
-
-    fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut ViewContext<Self>) {
-        if self.project.is_none() || self.mode != EditorMode::Full {
-            return;
-        }
-
-        let reason_description = reason.description();
-        let (invalidate_cache, required_languages) = match reason {
-            InlayHintRefreshReason::Toggle(enabled) => {
-                self.inlay_hint_cache.enabled = enabled;
-                if enabled {
-                    (InvalidationStrategy::RefreshRequested, None)
-                } else {
-                    self.inlay_hint_cache.clear();
-                    self.splice_inlay_hints(
-                        self.visible_inlay_hints(cx)
-                            .iter()
-                            .map(|inlay| inlay.id)
-                            .collect(),
-                        Vec::new(),
-                        cx,
-                    );
-                    return;
-                }
-            }
-            InlayHintRefreshReason::SettingsChange(new_settings) => {
-                match self.inlay_hint_cache.update_settings(
-                    &self.buffer,
-                    new_settings,
-                    self.visible_inlay_hints(cx),
-                    cx,
-                ) {
-                    ControlFlow::Break(Some(InlaySplice {
-                        to_remove,
-                        to_insert,
-                    })) => {
-                        self.splice_inlay_hints(to_remove, to_insert, cx);
-                        return;
-                    }
-                    ControlFlow::Break(None) => return,
-                    ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
-                }
-            }
-            InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
-                if let Some(InlaySplice {
-                    to_remove,
-                    to_insert,
-                }) = self.inlay_hint_cache.remove_excerpts(excerpts_removed)
-                {
-                    self.splice_inlay_hints(to_remove, to_insert, cx);
-                }
-                return;
-            }
-            InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
-            InlayHintRefreshReason::BufferEdited(buffer_languages) => {
-                (InvalidationStrategy::BufferEdited, Some(buffer_languages))
-            }
-            InlayHintRefreshReason::RefreshRequested => {
-                (InvalidationStrategy::RefreshRequested, None)
-            }
-        };
-
-        if let Some(InlaySplice {
-            to_remove,
-            to_insert,
-        }) = self.inlay_hint_cache.spawn_hint_refresh(
-            reason_description,
-            self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
-            invalidate_cache,
-            cx,
-        ) {
-            self.splice_inlay_hints(to_remove, to_insert, cx);
-        }
-    }
-
-    fn visible_inlay_hints(&self, cx: &ViewContext<'_, Editor>) -> Vec<Inlay> {
-        self.display_map
-            .read(cx)
-            .current_inlays()
-            .filter(move |inlay| {
-                Some(inlay.id) != self.copilot_state.suggestion.as_ref().map(|h| h.id)
-            })
-            .cloned()
-            .collect()
-    }
-
-    pub fn excerpts_for_inlay_hints_query(
-        &self,
-        restrict_to_languages: Option<&HashSet<Arc<Language>>>,
-        cx: &mut ViewContext<Editor>,
-    ) -> HashMap<ExcerptId, (Model<Buffer>, clock::Global, Range<usize>)> {
-        let Some(project) = self.project.as_ref() else {
-            return HashMap::default();
-        };
-        let project = project.read(cx);
-        let multi_buffer = self.buffer().read(cx);
-        let multi_buffer_snapshot = multi_buffer.snapshot(cx);
-        let multi_buffer_visible_start = self
-            .scroll_manager
-            .anchor()
-            .anchor
-            .to_point(&multi_buffer_snapshot);
-        let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
-            multi_buffer_visible_start
-                + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
-            Bias::Left,
-        );
-        let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
-        multi_buffer
-            .range_to_buffer_ranges(multi_buffer_visible_range, cx)
-            .into_iter()
-            .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
-            .filter_map(|(buffer_handle, excerpt_visible_range, excerpt_id)| {
-                let buffer = buffer_handle.read(cx);
-                let buffer_file = project::worktree::File::from_dyn(buffer.file())?;
-                let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
-                let worktree_entry = buffer_worktree
-                    .read(cx)
-                    .entry_for_id(buffer_file.project_entry_id(cx)?)?;
-                if worktree_entry.is_ignored {
-                    return None;
-                }
-
-                let language = buffer.language()?;
-                if let Some(restrict_to_languages) = restrict_to_languages {
-                    if !restrict_to_languages.contains(language) {
-                        return None;
-                    }
-                }
-                Some((
-                    excerpt_id,
-                    (
-                        buffer_handle,
-                        buffer.version().clone(),
-                        excerpt_visible_range,
-                    ),
-                ))
-            })
-            .collect()
-    }
-
-    pub fn text_layout_details(&self, cx: &WindowContext) -> TextLayoutDetails {
-        TextLayoutDetails {
-            text_system: cx.text_system().clone(),
-            editor_style: self.style.clone().unwrap(),
-            rem_size: cx.rem_size(),
-        }
-    }
-
-    fn splice_inlay_hints(
-        &self,
-        to_remove: Vec<InlayId>,
-        to_insert: Vec<Inlay>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.display_map.update(cx, |display_map, cx| {
-            display_map.splice_inlays(to_remove, to_insert, cx);
-        });
-        cx.notify();
-    }
-
-    fn trigger_on_type_formatting(
-        &self,
-        input: String,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<Task<Result<()>>> {
-        if input.len() != 1 {
-            return None;
-        }
-
-        let project = self.project.as_ref()?;
-        let position = self.selections.newest_anchor().head();
-        let (buffer, buffer_position) = self
-            .buffer
-            .read(cx)
-            .text_anchor_for_position(position.clone(), cx)?;
-
-        // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
-        // hence we do LSP request & edit on host side only — add formats to host's history.
-        let push_to_lsp_host_history = true;
-        // If this is not the host, append its history with new edits.
-        let push_to_client_history = project.read(cx).is_remote();
-
-        let on_type_formatting = project.update(cx, |project, cx| {
-            project.on_type_format(
-                buffer.clone(),
-                buffer_position,
-                input,
-                push_to_lsp_host_history,
-                cx,
-            )
-        });
-        Some(cx.spawn(|editor, mut cx| async move {
-            if let Some(transaction) = on_type_formatting.await? {
-                if push_to_client_history {
-                    buffer
-                        .update(&mut cx, |buffer, _| {
-                            buffer.push_transaction(transaction, Instant::now());
-                        })
-                        .ok();
-                }
-                editor.update(&mut cx, |editor, cx| {
-                    editor.refresh_document_highlights(cx);
-                })?;
-            }
-            Ok(())
-        }))
-    }
-
-    fn show_completions(&mut self, _: &ShowCompletions, cx: &mut ViewContext<Self>) {
-        if self.pending_rename.is_some() {
-            return;
-        }
-
-        let project = if let Some(project) = self.project.clone() {
-            project
-        } else {
-            return;
-        };
-
-        let position = self.selections.newest_anchor().head();
-        let (buffer, buffer_position) = if let Some(output) = self
-            .buffer
-            .read(cx)
-            .text_anchor_for_position(position.clone(), cx)
-        {
-            output
-        } else {
-            return;
-        };
-
-        let query = Self::completion_query(&self.buffer.read(cx).read(cx), position.clone());
-        let completions = project.update(cx, |project, cx| {
-            project.completions(&buffer, buffer_position, cx)
-        });
-
-        let id = post_inc(&mut self.next_completion_id);
-        let task = cx.spawn(|this, mut cx| {
-            async move {
-                let completions = completions.await.log_err();
-                let (menu, pre_resolve_task) = if let Some(completions) = completions {
-                    let mut menu = CompletionsMenu {
-                        id,
-                        initial_position: position,
-                        match_candidates: completions
-                            .iter()
-                            .enumerate()
-                            .map(|(id, completion)| {
-                                StringMatchCandidate::new(
-                                    id,
-                                    completion.label.text[completion.label.filter_range.clone()]
-                                        .into(),
-                                )
-                            })
-                            .collect(),
-                        buffer,
-                        completions: Arc::new(RwLock::new(completions.into())),
-                        matches: Vec::new().into(),
-                        selected_item: 0,
-                        scroll_handle: UniformListScrollHandle::new(),
-                    };
-                    menu.filter(query.as_deref(), cx.background_executor().clone())
-                        .await;
-
-                    if menu.matches.is_empty() {
-                        (None, None)
-                    } else {
-                        let pre_resolve_task = this
-                            .update(&mut cx, |editor, cx| {
-                                menu.pre_resolve_completion_documentation(editor, cx)
-                            })
-                            .ok()
-                            .flatten();
-                        (Some(menu), pre_resolve_task)
-                    }
-                } else {
-                    (None, None)
-                };
-
-                this.update(&mut cx, |this, cx| {
-                    this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
-
-                    let mut context_menu = this.context_menu.write();
-                    match context_menu.as_ref() {
-                        None => {}
-
-                        Some(ContextMenu::Completions(prev_menu)) => {
-                            if prev_menu.id > id {
-                                return;
-                            }
-                        }
-
-                        _ => return,
-                    }
-
-                    if this.focus_handle.is_focused(cx) && menu.is_some() {
-                        let menu = menu.unwrap();
-                        *context_menu = Some(ContextMenu::Completions(menu));
-                        drop(context_menu);
-                        this.discard_copilot_suggestion(cx);
-                        cx.notify();
-                    } else if this.completion_tasks.len() <= 1 {
-                        // If there are no more completion tasks and the last menu was
-                        // empty, we should hide it. If it was already hidden, we should
-                        // also show the copilot suggestion when available.
-                        drop(context_menu);
-                        if this.hide_context_menu(cx).is_none() {
-                            this.update_visible_copilot_suggestion(cx);
-                        }
-                    }
-                })?;
-
-                if let Some(pre_resolve_task) = pre_resolve_task {
-                    pre_resolve_task.await;
-                }
-
-                Ok::<_, anyhow::Error>(())
-            }
-            .log_err()
-        });
-
-        self.completion_tasks.push((id, task));
-    }
-
-    pub fn confirm_completion(
-        &mut self,
-        action: &ConfirmCompletion,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<Task<Result<()>>> {
-        use language::ToOffset as _;
-
-        let completions_menu = if let ContextMenu::Completions(menu) = self.hide_context_menu(cx)? {
-            menu
-        } else {
-            return None;
-        };
-
-        let mat = completions_menu
-            .matches
-            .get(action.item_ix.unwrap_or(completions_menu.selected_item))?;
-        let buffer_handle = completions_menu.buffer;
-        let completions = completions_menu.completions.read();
-        let completion = completions.get(mat.candidate_id)?;
-
-        let snippet;
-        let text;
-        if completion.is_snippet() {
-            snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
-            text = snippet.as_ref().unwrap().text.clone();
-        } else {
-            snippet = None;
-            text = completion.new_text.clone();
-        };
-        let selections = self.selections.all::<usize>(cx);
-        let buffer = buffer_handle.read(cx);
-        let old_range = completion.old_range.to_offset(buffer);
-        let old_text = buffer.text_for_range(old_range.clone()).collect::<String>();
-
-        let newest_selection = self.selections.newest_anchor();
-        if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) {
-            return None;
-        }
-
-        let lookbehind = newest_selection
-            .start
-            .text_anchor
-            .to_offset(buffer)
-            .saturating_sub(old_range.start);
-        let lookahead = old_range
-            .end
-            .saturating_sub(newest_selection.end.text_anchor.to_offset(buffer));
-        let mut common_prefix_len = old_text
-            .bytes()
-            .zip(text.bytes())
-            .take_while(|(a, b)| a == b)
-            .count();
-
-        let snapshot = self.buffer.read(cx).snapshot(cx);
-        let mut range_to_replace: Option<Range<isize>> = None;
-        let mut ranges = Vec::new();
-        for selection in &selections {
-            if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) {
-                let start = selection.start.saturating_sub(lookbehind);
-                let end = selection.end + lookahead;
-                if selection.id == newest_selection.id {
-                    range_to_replace = Some(
-                        ((start + common_prefix_len) as isize - selection.start as isize)
-                            ..(end as isize - selection.start as isize),
-                    );
-                }
-                ranges.push(start + common_prefix_len..end);
-            } else {
-                common_prefix_len = 0;
-                ranges.clear();
-                ranges.extend(selections.iter().map(|s| {
-                    if s.id == newest_selection.id {
-                        range_to_replace = Some(
-                            old_range.start.to_offset_utf16(&snapshot).0 as isize
-                                - selection.start as isize
-                                ..old_range.end.to_offset_utf16(&snapshot).0 as isize
-                                    - selection.start as isize,
-                        );
-                        old_range.clone()
-                    } else {
-                        s.start..s.end
-                    }
-                }));
-                break;
-            }
-        }
-        let text = &text[common_prefix_len..];
-
-        cx.emit(EditorEvent::InputHandled {
-            utf16_range_to_replace: range_to_replace,
-            text: text.into(),
-        });
-
-        self.transact(cx, |this, cx| {
-            if let Some(mut snippet) = snippet {
-                snippet.text = text.to_string();
-                for tabstop in snippet.tabstops.iter_mut().flatten() {
-                    tabstop.start -= common_prefix_len as isize;
-                    tabstop.end -= common_prefix_len as isize;
-                }
-
-                this.insert_snippet(&ranges, snippet, cx).log_err();
-            } else {
-                this.buffer.update(cx, |buffer, cx| {
-                    buffer.edit(
-                        ranges.iter().map(|range| (range.clone(), text)),
-                        this.autoindent_mode.clone(),
-                        cx,
-                    );
-                });
-            }
-
-            this.refresh_copilot_suggestions(true, cx);
-        });
-
-        let project = self.project.clone()?;
-        let apply_edits = project.update(cx, |project, cx| {
-            project.apply_additional_edits_for_completion(
-                buffer_handle,
-                completion.clone(),
-                true,
-                cx,
-            )
-        });
-        Some(cx.foreground_executor().spawn(async move {
-            apply_edits.await?;
-            Ok(())
-        }))
-    }
-
-    pub fn toggle_code_actions(&mut self, action: &ToggleCodeActions, cx: &mut ViewContext<Self>) {
-        let mut context_menu = self.context_menu.write();
-        if matches!(context_menu.as_ref(), Some(ContextMenu::CodeActions(_))) {
-            *context_menu = None;
-            cx.notify();
-            return;
-        }
-        drop(context_menu);
-
-        let deployed_from_indicator = action.deployed_from_indicator;
-        let mut task = self.code_actions_task.take();
-        cx.spawn(|this, mut cx| async move {
-            while let Some(prev_task) = task {
-                prev_task.await;
-                task = this.update(&mut cx, |this, _| this.code_actions_task.take())?;
-            }
-
-            this.update(&mut cx, |this, cx| {
-                if this.focus_handle.is_focused(cx) {
-                    if let Some((buffer, actions)) = this.available_code_actions.clone() {
-                        this.completion_tasks.clear();
-                        this.discard_copilot_suggestion(cx);
-                        *this.context_menu.write() =
-                            Some(ContextMenu::CodeActions(CodeActionsMenu {
-                                buffer,
-                                actions,
-                                selected_item: Default::default(),
-                                scroll_handle: UniformListScrollHandle::default(),
-                                deployed_from_indicator,
-                            }));
-                        cx.notify();
-                    }
-                }
-            })?;
-
-            Ok::<_, anyhow::Error>(())
-        })
-        .detach_and_log_err(cx);
-    }
-
-    pub fn confirm_code_action(
-        &mut self,
-        action: &ConfirmCodeAction,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<Task<Result<()>>> {
-        let actions_menu = if let ContextMenu::CodeActions(menu) = self.hide_context_menu(cx)? {
-            menu
-        } else {
-            return None;
-        };
-        let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
-        let action = actions_menu.actions.get(action_ix)?.clone();
-        let title = action.lsp_action.title.clone();
-        let buffer = actions_menu.buffer;
-        let workspace = self.workspace()?;
-
-        let apply_code_actions = workspace
-            .read(cx)
-            .project()
-            .clone()
-            .update(cx, |project, cx| {
-                project.apply_code_action(buffer, action, true, cx)
-            });
-        let workspace = workspace.downgrade();
-        Some(cx.spawn(|editor, cx| async move {
-            let project_transaction = apply_code_actions.await?;
-            Self::open_project_transaction(&editor, workspace, project_transaction, title, cx).await
-        }))
-    }
-
-    async fn open_project_transaction(
-        this: &WeakView<Editor>,
-        workspace: WeakView<Workspace>,
-        transaction: ProjectTransaction,
-        title: String,
-        mut cx: AsyncWindowContext,
-    ) -> Result<()> {
-        let replica_id = this.update(&mut cx, |this, cx| this.replica_id(cx))?;
-
-        let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
-        cx.update(|_, cx| {
-            entries.sort_unstable_by_key(|(buffer, _)| {
-                buffer.read(cx).file().map(|f| f.path().clone())
-            });
-        })?;
-
-        // If the project transaction's edits are all contained within this editor, then
-        // avoid opening a new editor to display them.
-
-        if let Some((buffer, transaction)) = entries.first() {
-            if entries.len() == 1 {
-                let excerpt = this.update(&mut cx, |editor, cx| {
-                    editor
-                        .buffer()
-                        .read(cx)
-                        .excerpt_containing(editor.selections.newest_anchor().head(), cx)
-                })?;
-                if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
-                    if excerpted_buffer == *buffer {
-                        let all_edits_within_excerpt = buffer.read_with(&cx, |buffer, _| {
-                            let excerpt_range = excerpt_range.to_offset(buffer);
-                            buffer
-                                .edited_ranges_for_transaction::<usize>(transaction)
-                                .all(|range| {
-                                    excerpt_range.start <= range.start
-                                        && excerpt_range.end >= range.end
-                                })
-                        })?;
-
-                        if all_edits_within_excerpt {
-                            return Ok(());
-                        }
-                    }
-                }
-            }
-        } else {
-            return Ok(());
-        }
-
-        let mut ranges_to_highlight = Vec::new();
-        let excerpt_buffer = cx.new_model(|cx| {
-            let mut multibuffer = MultiBuffer::new(replica_id).with_title(title);
-            for (buffer_handle, transaction) in &entries {
-                let buffer = buffer_handle.read(cx);
-                ranges_to_highlight.extend(
-                    multibuffer.push_excerpts_with_context_lines(
-                        buffer_handle.clone(),
-                        buffer
-                            .edited_ranges_for_transaction::<usize>(transaction)
-                            .collect(),
-                        1,
-                        cx,
-                    ),
-                );
-            }
-            multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
-            multibuffer
-        })?;
-
-        workspace.update(&mut cx, |workspace, cx| {
-            let project = workspace.project().clone();
-            let editor =
-                cx.new_view(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), cx));
-            workspace.add_item(Box::new(editor.clone()), cx);
-            editor.update(cx, |editor, cx| {
-                editor.highlight_background::<Self>(
-                    ranges_to_highlight,
-                    |theme| theme.editor_highlighted_line_background,
-                    cx,
-                );
-            });
-        })?;
-
-        Ok(())
-    }
-
-    fn refresh_code_actions(&mut self, cx: &mut ViewContext<Self>) -> Option<()> {
-        let project = self.project.clone()?;
-        let buffer = self.buffer.read(cx);
-        let newest_selection = self.selections.newest_anchor().clone();
-        let (start_buffer, start) = buffer.text_anchor_for_position(newest_selection.start, cx)?;
-        let (end_buffer, end) = buffer.text_anchor_for_position(newest_selection.end, cx)?;
-        if start_buffer != end_buffer {
-            return None;
-        }
-
-        self.code_actions_task = Some(cx.spawn(|this, mut cx| async move {
-            cx.background_executor()
-                .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
-                .await;
-
-            let actions = if let Ok(code_actions) = project.update(&mut cx, |project, cx| {
-                project.code_actions(&start_buffer, start..end, cx)
-            }) {
-                code_actions.await.log_err()
-            } else {
-                None
-            };
-
-            this.update(&mut cx, |this, cx| {
-                this.available_code_actions = actions.and_then(|actions| {
-                    if actions.is_empty() {
-                        None
-                    } else {
-                        Some((start_buffer, actions.into()))
-                    }
-                });
-                cx.notify();
-            })
-            .log_err();
-        }));
-        None
-    }
-
-    fn refresh_document_highlights(&mut self, cx: &mut ViewContext<Self>) -> Option<()> {
-        if self.pending_rename.is_some() {
-            return None;
-        }
-
-        let project = self.project.clone()?;
-        let buffer = self.buffer.read(cx);
-        let newest_selection = self.selections.newest_anchor().clone();
-        let cursor_position = newest_selection.head();
-        let (cursor_buffer, cursor_buffer_position) =
-            buffer.text_anchor_for_position(cursor_position.clone(), cx)?;
-        let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
-        if cursor_buffer != tail_buffer {
-            return None;
-        }
-
-        self.document_highlights_task = Some(cx.spawn(|this, mut cx| async move {
-            cx.background_executor()
-                .timer(DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT)
-                .await;
-
-            let highlights = if let Some(highlights) = project
-                .update(&mut cx, |project, cx| {
-                    project.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
-                })
-                .log_err()
-            {
-                highlights.await.log_err()
-            } else {
-                None
-            };
-
-            if let Some(highlights) = highlights {
-                this.update(&mut cx, |this, cx| {
-                    if this.pending_rename.is_some() {
-                        return;
-                    }
-
-                    let buffer_id = cursor_position.buffer_id;
-                    let buffer = this.buffer.read(cx);
-                    if !buffer
-                        .text_anchor_for_position(cursor_position, cx)
-                        .map_or(false, |(buffer, _)| buffer == cursor_buffer)
-                    {
-                        return;
-                    }
-
-                    let cursor_buffer_snapshot = cursor_buffer.read(cx);
-                    let mut write_ranges = Vec::new();
-                    let mut read_ranges = Vec::new();
-                    for highlight in highlights {
-                        for (excerpt_id, excerpt_range) in
-                            buffer.excerpts_for_buffer(&cursor_buffer, cx)
-                        {
-                            let start = highlight
-                                .range
-                                .start
-                                .max(&excerpt_range.context.start, cursor_buffer_snapshot);
-                            let end = highlight
-                                .range
-                                .end
-                                .min(&excerpt_range.context.end, cursor_buffer_snapshot);
-                            if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
-                                continue;
-                            }
-
-                            let range = Anchor {
-                                buffer_id,
-                                excerpt_id: excerpt_id.clone(),
-                                text_anchor: start,
-                            }..Anchor {
-                                buffer_id,
-                                excerpt_id,
-                                text_anchor: end,
-                            };
-                            if highlight.kind == lsp::DocumentHighlightKind::WRITE {
-                                write_ranges.push(range);
-                            } else {
-                                read_ranges.push(range);
-                            }
-                        }
-                    }
-
-                    this.highlight_background::<DocumentHighlightRead>(
-                        read_ranges,
-                        |theme| theme.editor_document_highlight_read_background,
-                        cx,
-                    );
-                    this.highlight_background::<DocumentHighlightWrite>(
-                        write_ranges,
-                        |theme| theme.editor_document_highlight_write_background,
-                        cx,
-                    );
-                    cx.notify();
-                })
-                .log_err();
-            }
-        }));
-        None
-    }
-
-    fn refresh_copilot_suggestions(
-        &mut self,
-        debounce: bool,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<()> {
-        let copilot = Copilot::global(cx)?;
-        if self.mode != EditorMode::Full || !copilot.read(cx).status().is_authorized() {
-            self.clear_copilot_suggestions(cx);
-            return None;
-        }
-        self.update_visible_copilot_suggestion(cx);
-
-        let snapshot = self.buffer.read(cx).snapshot(cx);
-        let cursor = self.selections.newest_anchor().head();
-        if !self.is_copilot_enabled_at(cursor, &snapshot, cx) {
-            self.clear_copilot_suggestions(cx);
-            return None;
-        }
-
-        let (buffer, buffer_position) =
-            self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
-        self.copilot_state.pending_refresh = cx.spawn(|this, mut cx| async move {
-            if debounce {
-                cx.background_executor()
-                    .timer(COPILOT_DEBOUNCE_TIMEOUT)
-                    .await;
-            }
-
-            let completions = copilot
-                .update(&mut cx, |copilot, cx| {
-                    copilot.completions(&buffer, buffer_position, cx)
-                })
-                .log_err()
-                .unwrap_or(Task::ready(Ok(Vec::new())))
-                .await
-                .log_err()
-                .into_iter()
-                .flatten()
-                .collect_vec();
-
-            this.update(&mut cx, |this, cx| {
-                if !completions.is_empty() {
-                    this.copilot_state.cycled = false;
-                    this.copilot_state.pending_cycling_refresh = Task::ready(None);
-                    this.copilot_state.completions.clear();
-                    this.copilot_state.active_completion_index = 0;
-                    this.copilot_state.excerpt_id = Some(cursor.excerpt_id);
-                    for completion in completions {
-                        this.copilot_state.push_completion(completion);
-                    }
-                    this.update_visible_copilot_suggestion(cx);
-                }
-            })
-            .log_err()?;
-            Some(())
-        });
-
-        Some(())
-    }
-
-    fn cycle_copilot_suggestions(
-        &mut self,
-        direction: Direction,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<()> {
-        let copilot = Copilot::global(cx)?;
-        if self.mode != EditorMode::Full || !copilot.read(cx).status().is_authorized() {
-            return None;
-        }
-
-        if self.copilot_state.cycled {
-            self.copilot_state.cycle_completions(direction);
-            self.update_visible_copilot_suggestion(cx);
-        } else {
-            let cursor = self.selections.newest_anchor().head();
-            let (buffer, buffer_position) =
-                self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
-            self.copilot_state.pending_cycling_refresh = cx.spawn(|this, mut cx| async move {
-                let completions = copilot
-                    .update(&mut cx, |copilot, cx| {
-                        copilot.completions_cycling(&buffer, buffer_position, cx)
-                    })
-                    .log_err()?
-                    .await;
-
-                this.update(&mut cx, |this, cx| {
-                    this.copilot_state.cycled = true;
-                    for completion in completions.log_err().into_iter().flatten() {
-                        this.copilot_state.push_completion(completion);
-                    }
-                    this.copilot_state.cycle_completions(direction);
-                    this.update_visible_copilot_suggestion(cx);
-                })
-                .log_err()?;
-
-                Some(())
-            });
-        }
-
-        Some(())
-    }
-
-    fn copilot_suggest(&mut self, _: &copilot::Suggest, cx: &mut ViewContext<Self>) {
-        if !self.has_active_copilot_suggestion(cx) {
-            self.refresh_copilot_suggestions(false, cx);
-            return;
-        }
-
-        self.update_visible_copilot_suggestion(cx);
-    }
-
-    fn next_copilot_suggestion(&mut self, _: &copilot::NextSuggestion, cx: &mut ViewContext<Self>) {
-        if self.has_active_copilot_suggestion(cx) {
-            self.cycle_copilot_suggestions(Direction::Next, cx);
-        } else {
-            let is_copilot_disabled = self.refresh_copilot_suggestions(false, cx).is_none();
-            if is_copilot_disabled {
-                cx.propagate();
-            }
-        }
-    }
-
-    fn previous_copilot_suggestion(
-        &mut self,
-        _: &copilot::PreviousSuggestion,
-        cx: &mut ViewContext<Self>,
-    ) {
-        if self.has_active_copilot_suggestion(cx) {
-            self.cycle_copilot_suggestions(Direction::Prev, cx);
-        } else {
-            let is_copilot_disabled = self.refresh_copilot_suggestions(false, cx).is_none();
-            if is_copilot_disabled {
-                cx.propagate();
-            }
-        }
-    }
-
-    fn accept_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> bool {
-        if let Some(suggestion) = self.take_active_copilot_suggestion(cx) {
-            if let Some((copilot, completion)) =
-                Copilot::global(cx).zip(self.copilot_state.active_completion())
-            {
-                copilot
-                    .update(cx, |copilot, cx| copilot.accept_completion(completion, cx))
-                    .detach_and_log_err(cx);
-
-                self.report_copilot_event(Some(completion.uuid.clone()), true, cx)
-            }
-            cx.emit(EditorEvent::InputHandled {
-                utf16_range_to_replace: None,
-                text: suggestion.text.to_string().into(),
-            });
-            self.insert_with_autoindent_mode(&suggestion.text.to_string(), None, cx);
-            cx.notify();
-            true
-        } else {
-            false
-        }
-    }
-
-    fn discard_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> bool {
-        if let Some(suggestion) = self.take_active_copilot_suggestion(cx) {
-            if let Some(copilot) = Copilot::global(cx) {
-                copilot
-                    .update(cx, |copilot, cx| {
-                        copilot.discard_completions(&self.copilot_state.completions, cx)
-                    })
-                    .detach_and_log_err(cx);
-
-                self.report_copilot_event(None, false, cx)
-            }
-
-            self.display_map.update(cx, |map, cx| {
-                map.splice_inlays(vec![suggestion.id], Vec::new(), cx)
-            });
-            cx.notify();
-            true
-        } else {
-            false
-        }
-    }
-
-    fn is_copilot_enabled_at(
-        &self,
-        location: Anchor,
-        snapshot: &MultiBufferSnapshot,
-        cx: &mut ViewContext<Self>,
-    ) -> bool {
-        let file = snapshot.file_at(location);
-        let language = snapshot.language_at(location);
-        let settings = all_language_settings(file, cx);
-        settings.copilot_enabled(language, file.map(|f| f.path().as_ref()))
-    }
-
-    fn has_active_copilot_suggestion(&self, cx: &AppContext) -> bool {
-        if let Some(suggestion) = self.copilot_state.suggestion.as_ref() {
-            let buffer = self.buffer.read(cx).read(cx);
-            suggestion.position.is_valid(&buffer)
-        } else {
-            false
-        }
-    }
-
-    fn take_active_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> Option<Inlay> {
-        let suggestion = self.copilot_state.suggestion.take()?;
-        self.display_map.update(cx, |map, cx| {
-            map.splice_inlays(vec![suggestion.id], Default::default(), cx);
-        });
-        let buffer = self.buffer.read(cx).read(cx);
-
-        if suggestion.position.is_valid(&buffer) {
-            Some(suggestion)
-        } else {
-            None
-        }
-    }
-
-    fn update_visible_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) {
-        let snapshot = self.buffer.read(cx).snapshot(cx);
-        let selection = self.selections.newest_anchor();
-        let cursor = selection.head();
-
-        if self.context_menu.read().is_some()
-            || !self.completion_tasks.is_empty()
-            || selection.start != selection.end
-        {
-            self.discard_copilot_suggestion(cx);
-        } else if let Some(text) = self
-            .copilot_state
-            .text_for_active_completion(cursor, &snapshot)
-        {
-            let text = Rope::from(text);
-            let mut to_remove = Vec::new();
-            if let Some(suggestion) = self.copilot_state.suggestion.take() {
-                to_remove.push(suggestion.id);
-            }
-
-            let suggestion_inlay =
-                Inlay::suggestion(post_inc(&mut self.next_inlay_id), cursor, text);
-            self.copilot_state.suggestion = Some(suggestion_inlay.clone());
-            self.display_map.update(cx, move |map, cx| {
-                map.splice_inlays(to_remove, vec![suggestion_inlay], cx)
-            });
-            cx.notify();
-        } else {
-            self.discard_copilot_suggestion(cx);
-        }
-    }
-
-    fn clear_copilot_suggestions(&mut self, cx: &mut ViewContext<Self>) {
-        self.copilot_state = Default::default();
-        self.discard_copilot_suggestion(cx);
-    }
-
-    pub fn render_code_actions_indicator(
-        &self,
-        _style: &EditorStyle,
-        is_active: bool,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<IconButton> {
-        if self.available_code_actions.is_some() {
-            Some(
-                IconButton::new("code_actions_indicator", ui::Icon::Bolt)
-                    .icon_size(IconSize::Small)
-                    .icon_color(Color::Muted)
-                    .selected(is_active)
-                    .on_click(cx.listener(|editor, _e, cx| {
-                        editor.toggle_code_actions(
-                            &ToggleCodeActions {
-                                deployed_from_indicator: true,
-                            },
-                            cx,
-                        );
-                    })),
-            )
-        } else {
-            None
-        }
-    }
-
-    pub fn render_fold_indicators(
-        &self,
-        fold_data: Vec<Option<(FoldStatus, u32, bool)>>,
-        _style: &EditorStyle,
-        gutter_hovered: bool,
-        _line_height: Pixels,
-        _gutter_margin: Pixels,
-        cx: &mut ViewContext<Self>,
-    ) -> Vec<Option<IconButton>> {
-        fold_data
-            .iter()
-            .enumerate()
-            .map(|(ix, fold_data)| {
-                fold_data
-                    .map(|(fold_status, buffer_row, active)| {
-                        (active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| {
-                            IconButton::new(ix as usize, ui::Icon::ChevronDown)
-                                .on_click(cx.listener(move |editor, _e, cx| match fold_status {
-                                    FoldStatus::Folded => {
-                                        editor.unfold_at(&UnfoldAt { buffer_row }, cx);
-                                    }
-                                    FoldStatus::Foldable => {
-                                        editor.fold_at(&FoldAt { buffer_row }, cx);
-                                    }
-                                }))
-                                .icon_color(ui::Color::Muted)
-                                .icon_size(ui::IconSize::Small)
-                                .selected(fold_status == FoldStatus::Folded)
-                                .selected_icon(ui::Icon::ChevronRight)
-                                .size(ui::ButtonSize::None)
-                        })
-                    })
-                    .flatten()
-            })
-            .collect()
-    }
-
-    pub fn context_menu_visible(&self) -> bool {
-        self.context_menu
-            .read()
-            .as_ref()
-            .map_or(false, |menu| menu.visible())
-    }
-
-    pub fn render_context_menu(
-        &self,
-        cursor_position: DisplayPoint,
-        style: &EditorStyle,
-        max_height: Pixels,
-        cx: &mut ViewContext<Editor>,
-    ) -> Option<(DisplayPoint, AnyElement)> {
-        self.context_menu.read().as_ref().map(|menu| {
-            menu.render(
-                cursor_position,
-                style,
-                max_height,
-                self.workspace.as_ref().map(|(w, _)| w.clone()),
-                cx,
-            )
-        })
-    }
-
-    fn hide_context_menu(&mut self, cx: &mut ViewContext<Self>) -> Option<ContextMenu> {
-        cx.notify();
-        self.completion_tasks.clear();
-        let context_menu = self.context_menu.write().take();
-        if context_menu.is_some() {
-            self.update_visible_copilot_suggestion(cx);
-        }
-        context_menu
-    }
-
-    pub fn insert_snippet(
-        &mut self,
-        insertion_ranges: &[Range<usize>],
-        snippet: Snippet,
-        cx: &mut ViewContext<Self>,
-    ) -> Result<()> {
-        let tabstops = self.buffer.update(cx, |buffer, cx| {
-            let snippet_text: Arc<str> = snippet.text.clone().into();
-            buffer.edit(
-                insertion_ranges
-                    .iter()
-                    .cloned()
-                    .map(|range| (range, snippet_text.clone())),
-                Some(AutoindentMode::EachLine),
-                cx,
-            );
-
-            let snapshot = &*buffer.read(cx);
-            let snippet = &snippet;
-            snippet
-                .tabstops
-                .iter()
-                .map(|tabstop| {
-                    let mut tabstop_ranges = tabstop
-                        .iter()
-                        .flat_map(|tabstop_range| {
-                            let mut delta = 0_isize;
-                            insertion_ranges.iter().map(move |insertion_range| {
-                                let insertion_start = insertion_range.start as isize + delta;
-                                delta +=
-                                    snippet.text.len() as isize - insertion_range.len() as isize;
-
-                                let start = snapshot.anchor_before(
-                                    (insertion_start + tabstop_range.start) as usize,
-                                );
-                                let end = snapshot
-                                    .anchor_after((insertion_start + tabstop_range.end) as usize);
-                                start..end
-                            })
-                        })
-                        .collect::<Vec<_>>();
-                    tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
-                    tabstop_ranges
-                })
-                .collect::<Vec<_>>()
-        });
-
-        if let Some(tabstop) = tabstops.first() {
-            self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                s.select_ranges(tabstop.iter().cloned());
-            });
-            self.snippet_stack.push(SnippetState {
-                active_index: 0,
-                ranges: tabstops,
-            });
-        }
-
-        Ok(())
-    }
-
-    pub fn move_to_next_snippet_tabstop(&mut self, cx: &mut ViewContext<Self>) -> bool {
-        self.move_to_snippet_tabstop(Bias::Right, cx)
-    }
-
-    pub fn move_to_prev_snippet_tabstop(&mut self, cx: &mut ViewContext<Self>) -> bool {
-        self.move_to_snippet_tabstop(Bias::Left, cx)
-    }
-
-    pub fn move_to_snippet_tabstop(&mut self, bias: Bias, cx: &mut ViewContext<Self>) -> bool {
-        if let Some(mut snippet) = self.snippet_stack.pop() {
-            match bias {
-                Bias::Left => {
-                    if snippet.active_index > 0 {
-                        snippet.active_index -= 1;
-                    } else {
-                        self.snippet_stack.push(snippet);
-                        return false;
-                    }
-                }
-                Bias::Right => {
-                    if snippet.active_index + 1 < snippet.ranges.len() {
-                        snippet.active_index += 1;
-                    } else {
-                        self.snippet_stack.push(snippet);
-                        return false;
-                    }
-                }
-            }
-            if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
-                self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                    s.select_anchor_ranges(current_ranges.iter().cloned())
-                });
-                // If snippet state is not at the last tabstop, push it back on the stack
-                if snippet.active_index + 1 < snippet.ranges.len() {
-                    self.snippet_stack.push(snippet);
-                }
-                return true;
-            }
-        }
-
-        false
-    }
-
-    pub fn clear(&mut self, cx: &mut ViewContext<Self>) {
-        self.transact(cx, |this, cx| {
-            this.select_all(&SelectAll, cx);
-            this.insert("", cx);
-        });
-    }
-
-    pub fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
-        self.transact(cx, |this, cx| {
-            this.select_autoclose_pair(cx);
-            let mut selections = this.selections.all::<Point>(cx);
-            if !this.selections.line_mode {
-                let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
-                for selection in &mut selections {
-                    if selection.is_empty() {
-                        let old_head = selection.head();
-                        let mut new_head =
-                            movement::left(&display_map, old_head.to_display_point(&display_map))
-                                .to_point(&display_map);
-                        if let Some((buffer, line_buffer_range)) = display_map
-                            .buffer_snapshot
-                            .buffer_line_for_row(old_head.row)
-                        {
-                            let indent_size =
-                                buffer.indent_size_for_line(line_buffer_range.start.row);
-                            let indent_len = match indent_size.kind {
-                                IndentKind::Space => {
-                                    buffer.settings_at(line_buffer_range.start, cx).tab_size
-                                }
-                                IndentKind::Tab => NonZeroU32::new(1).unwrap(),
-                            };
-                            if old_head.column <= indent_size.len && old_head.column > 0 {
-                                let indent_len = indent_len.get();
-                                new_head = cmp::min(
-                                    new_head,
-                                    Point::new(
-                                        old_head.row,
-                                        ((old_head.column - 1) / indent_len) * indent_len,
-                                    ),
-                                );
-                            }
-                        }
-
-                        selection.set_head(new_head, SelectionGoal::None);
-                    }
-                }
-            }
-
-            this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
-            this.insert("", cx);
-            this.refresh_copilot_suggestions(true, cx);
-        });
-    }
-
-    pub fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) {
-        self.transact(cx, |this, cx| {
-            this.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                let line_mode = s.line_mode;
-                s.move_with(|map, selection| {
-                    if selection.is_empty() && !line_mode {
-                        let cursor = movement::right(map, selection.head());
-                        selection.end = cursor;
-                        selection.reversed = true;
-                        selection.goal = SelectionGoal::None;
-                    }
-                })
-            });
-            this.insert("", cx);
-            this.refresh_copilot_suggestions(true, cx);
-        });
-    }
-
-    pub fn tab_prev(&mut self, _: &TabPrev, cx: &mut ViewContext<Self>) {
-        if self.move_to_prev_snippet_tabstop(cx) {
-            return;
-        }
-
-        self.outdent(&Outdent, cx);
-    }
-
-    pub fn tab(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
-        if self.move_to_next_snippet_tabstop(cx) {
-            return;
-        }
-
-        let mut selections = self.selections.all_adjusted(cx);
-        let buffer = self.buffer.read(cx);
-        let snapshot = buffer.snapshot(cx);
-        let rows_iter = selections.iter().map(|s| s.head().row);
-        let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
-
-        let mut edits = Vec::new();
-        let mut prev_edited_row = 0;
-        let mut row_delta = 0;
-        for selection in &mut selections {
-            if selection.start.row != prev_edited_row {
-                row_delta = 0;
-            }
-            prev_edited_row = selection.end.row;
-
-            // If the selection is non-empty, then increase the indentation of the selected lines.
-            if !selection.is_empty() {
-                row_delta =
-                    Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
-                continue;
-            }
-
-            // If the selection is empty and the cursor is in the leading whitespace before the
-            // suggested indentation, then auto-indent the line.
-            let cursor = selection.head();
-            let current_indent = snapshot.indent_size_for_line(cursor.row);
-            if let Some(suggested_indent) = suggested_indents.get(&cursor.row).copied() {
-                if cursor.column < suggested_indent.len
-                    && cursor.column <= current_indent.len
-                    && current_indent.len <= suggested_indent.len
-                {
-                    selection.start = Point::new(cursor.row, suggested_indent.len);
-                    selection.end = selection.start;
-                    if row_delta == 0 {
-                        edits.extend(Buffer::edit_for_indent_size_adjustment(
-                            cursor.row,
-                            current_indent,
-                            suggested_indent,
-                        ));
-                        row_delta = suggested_indent.len - current_indent.len;
-                    }
-                    continue;
-                }
-            }
-
-            // Accept copilot suggestion if there is only one selection and the cursor is not
-            // in the leading whitespace.
-            if self.selections.count() == 1
-                && cursor.column >= current_indent.len
-                && self.has_active_copilot_suggestion(cx)
-            {
-                self.accept_copilot_suggestion(cx);
-                return;
-            }
-
-            // Otherwise, insert a hard or soft tab.
-            let settings = buffer.settings_at(cursor, cx);
-            let tab_size = if settings.hard_tabs {
-                IndentSize::tab()
-            } else {
-                let tab_size = settings.tab_size.get();
-                let char_column = snapshot
-                    .text_for_range(Point::new(cursor.row, 0)..cursor)
-                    .flat_map(str::chars)
-                    .count()
-                    + row_delta as usize;
-                let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size);
-                IndentSize::spaces(chars_to_next_tab_stop)
-            };
-            selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
-            selection.end = selection.start;
-            edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
-            row_delta += tab_size.len;
-        }
-
-        self.transact(cx, |this, cx| {
-            this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
-            this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
-            this.refresh_copilot_suggestions(true, cx);
-        });
-    }
-
-    pub fn indent(&mut self, _: &Indent, cx: &mut ViewContext<Self>) {
-        let mut selections = self.selections.all::<Point>(cx);
-        let mut prev_edited_row = 0;
-        let mut row_delta = 0;
-        let mut edits = Vec::new();
-        let buffer = self.buffer.read(cx);
-        let snapshot = buffer.snapshot(cx);
-        for selection in &mut selections {
-            if selection.start.row != prev_edited_row {
-                row_delta = 0;
-            }
-            prev_edited_row = selection.end.row;
-
-            row_delta =
-                Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
-        }
-
-        self.transact(cx, |this, cx| {
-            this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
-            this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
-        });
-    }
-
-    fn indent_selection(
-        buffer: &MultiBuffer,
-        snapshot: &MultiBufferSnapshot,
-        selection: &mut Selection<Point>,
-        edits: &mut Vec<(Range<Point>, String)>,
-        delta_for_start_row: u32,
-        cx: &AppContext,
-    ) -> u32 {
-        let settings = buffer.settings_at(selection.start, cx);
-        let tab_size = settings.tab_size.get();
-        let indent_kind = if settings.hard_tabs {
-            IndentKind::Tab
-        } else {
-            IndentKind::Space
-        };
-        let mut start_row = selection.start.row;
-        let mut end_row = selection.end.row + 1;
-
-        // If a selection ends at the beginning of a line, don't indent
-        // that last line.
-        if selection.end.column == 0 {
-            end_row -= 1;
-        }
-
-        // Avoid re-indenting a row that has already been indented by a
-        // previous selection, but still update this selection's column
-        // to reflect that indentation.
-        if delta_for_start_row > 0 {
-            start_row += 1;
-            selection.start.column += delta_for_start_row;
-            if selection.end.row == selection.start.row {
-                selection.end.column += delta_for_start_row;
-            }
-        }
-
-        let mut delta_for_end_row = 0;
-        for row in start_row..end_row {
-            let current_indent = snapshot.indent_size_for_line(row);
-            let indent_delta = match (current_indent.kind, indent_kind) {
-                (IndentKind::Space, IndentKind::Space) => {
-                    let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
-                    IndentSize::spaces(columns_to_next_tab_stop)
-                }
-                (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
-                (_, IndentKind::Tab) => IndentSize::tab(),
-            };
-
-            let row_start = Point::new(row, 0);
-            edits.push((
-                row_start..row_start,
-                indent_delta.chars().collect::<String>(),
-            ));
-
-            // Update this selection's endpoints to reflect the indentation.
-            if row == selection.start.row {
-                selection.start.column += indent_delta.len;
-            }
-            if row == selection.end.row {
-                selection.end.column += indent_delta.len;
-                delta_for_end_row = indent_delta.len;
-            }
-        }
-
-        if selection.start.row == selection.end.row {
-            delta_for_start_row + delta_for_end_row
-        } else {
-            delta_for_end_row
-        }
-    }
-
-    pub fn outdent(&mut self, _: &Outdent, cx: &mut ViewContext<Self>) {
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-        let selections = self.selections.all::<Point>(cx);
-        let mut deletion_ranges = Vec::new();
-        let mut last_outdent = None;
-        {
-            let buffer = self.buffer.read(cx);
-            let snapshot = buffer.snapshot(cx);
-            for selection in &selections {
-                let settings = buffer.settings_at(selection.start, cx);
-                let tab_size = settings.tab_size.get();
-                let mut rows = selection.spanned_rows(false, &display_map);
-
-                // Avoid re-outdenting a row that has already been outdented by a
-                // previous selection.
-                if let Some(last_row) = last_outdent {
-                    if last_row == rows.start {
-                        rows.start += 1;
-                    }
-                }
-
-                for row in rows {
-                    let indent_size = snapshot.indent_size_for_line(row);
-                    if indent_size.len > 0 {
-                        let deletion_len = match indent_size.kind {
-                            IndentKind::Space => {
-                                let columns_to_prev_tab_stop = indent_size.len % tab_size;
-                                if columns_to_prev_tab_stop == 0 {
-                                    tab_size
-                                } else {
-                                    columns_to_prev_tab_stop
-                                }
-                            }
-                            IndentKind::Tab => 1,
-                        };
-                        deletion_ranges.push(Point::new(row, 0)..Point::new(row, deletion_len));
-                        last_outdent = Some(row);
-                    }
-                }
-            }
-        }
-
-        self.transact(cx, |this, cx| {
-            this.buffer.update(cx, |buffer, cx| {
-                let empty_str: Arc<str> = "".into();
-                buffer.edit(
-                    deletion_ranges
-                        .into_iter()
-                        .map(|range| (range, empty_str.clone())),
-                    None,
-                    cx,
-                );
-            });
-            let selections = this.selections.all::<usize>(cx);
-            this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
-        });
-    }
-
-    pub fn delete_line(&mut self, _: &DeleteLine, cx: &mut ViewContext<Self>) {
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-        let selections = self.selections.all::<Point>(cx);
-
-        let mut new_cursors = Vec::new();
-        let mut edit_ranges = Vec::new();
-        let mut selections = selections.iter().peekable();
-        while let Some(selection) = selections.next() {
-            let mut rows = selection.spanned_rows(false, &display_map);
-            let goal_display_column = selection.head().to_display_point(&display_map).column();
-
-            // Accumulate contiguous regions of rows that we want to delete.
-            while let Some(next_selection) = selections.peek() {
-                let next_rows = next_selection.spanned_rows(false, &display_map);
-                if next_rows.start <= rows.end {
-                    rows.end = next_rows.end;
-                    selections.next().unwrap();
-                } else {
-                    break;
-                }
-            }
-
-            let buffer = &display_map.buffer_snapshot;
-            let mut edit_start = Point::new(rows.start, 0).to_offset(buffer);
-            let edit_end;
-            let cursor_buffer_row;
-            if buffer.max_point().row >= rows.end {
-                // If there's a line after the range, delete the \n from the end of the row range
-                // and position the cursor on the next line.
-                edit_end = Point::new(rows.end, 0).to_offset(buffer);
-                cursor_buffer_row = rows.end;
-            } else {
-                // If there isn't a line after the range, delete the \n from the line before the
-                // start of the row range and position the cursor there.
-                edit_start = edit_start.saturating_sub(1);
-                edit_end = buffer.len();
-                cursor_buffer_row = rows.start.saturating_sub(1);
-            }
-
-            let mut cursor = Point::new(cursor_buffer_row, 0).to_display_point(&display_map);
-            *cursor.column_mut() =
-                cmp::min(goal_display_column, display_map.line_len(cursor.row()));
-
-            new_cursors.push((
-                selection.id,
-                buffer.anchor_after(cursor.to_point(&display_map)),
-            ));
-            edit_ranges.push(edit_start..edit_end);
-        }
-
-        self.transact(cx, |this, cx| {
-            let buffer = this.buffer.update(cx, |buffer, cx| {
-                let empty_str: Arc<str> = "".into();
-                buffer.edit(
-                    edit_ranges
-                        .into_iter()
-                        .map(|range| (range, empty_str.clone())),
-                    None,
-                    cx,
-                );
-                buffer.snapshot(cx)
-            });
-            let new_selections = new_cursors
-                .into_iter()
-                .map(|(id, cursor)| {
-                    let cursor = cursor.to_point(&buffer);
-                    Selection {
-                        id,
-                        start: cursor,
-                        end: cursor,
-                        reversed: false,
-                        goal: SelectionGoal::None,
-                    }
-                })
-                .collect();
-
-            this.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                s.select(new_selections);
-            });
-        });
-    }
-
-    pub fn join_lines(&mut self, _: &JoinLines, cx: &mut ViewContext<Self>) {
-        let mut row_ranges = Vec::<Range<u32>>::new();
-        for selection in self.selections.all::<Point>(cx) {
-            let start = selection.start.row;
-            let end = if selection.start.row == selection.end.row {
-                selection.start.row + 1
-            } else {
-                selection.end.row
-            };
-
-            if let Some(last_row_range) = row_ranges.last_mut() {
-                if start <= last_row_range.end {
-                    last_row_range.end = end;
-                    continue;
-                }
-            }
-            row_ranges.push(start..end);
-        }
-
-        let snapshot = self.buffer.read(cx).snapshot(cx);
-        let mut cursor_positions = Vec::new();
-        for row_range in &row_ranges {
-            let anchor = snapshot.anchor_before(Point::new(
-                row_range.end - 1,
-                snapshot.line_len(row_range.end - 1),
-            ));
-            cursor_positions.push(anchor.clone()..anchor);
-        }
-
-        self.transact(cx, |this, cx| {
-            for row_range in row_ranges.into_iter().rev() {
-                for row in row_range.rev() {
-                    let end_of_line = Point::new(row, snapshot.line_len(row));
-                    let indent = snapshot.indent_size_for_line(row + 1);
-                    let start_of_next_line = Point::new(row + 1, indent.len);
-
-                    let replace = if snapshot.line_len(row + 1) > indent.len {
-                        " "
-                    } else {
-                        ""
-                    };
-
-                    this.buffer.update(cx, |buffer, cx| {
-                        buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
-                    });
-                }
-            }
-
-            this.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                s.select_anchor_ranges(cursor_positions)
-            });
-        });
-    }
-
-    pub fn sort_lines_case_sensitive(
-        &mut self,
-        _: &SortLinesCaseSensitive,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.manipulate_lines(cx, |lines| lines.sort())
-    }
-
-    pub fn sort_lines_case_insensitive(
-        &mut self,
-        _: &SortLinesCaseInsensitive,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.manipulate_lines(cx, |lines| lines.sort_by_key(|line| line.to_lowercase()))
-    }
-
-    pub fn reverse_lines(&mut self, _: &ReverseLines, cx: &mut ViewContext<Self>) {
-        self.manipulate_lines(cx, |lines| lines.reverse())
-    }
-
-    pub fn shuffle_lines(&mut self, _: &ShuffleLines, cx: &mut ViewContext<Self>) {
-        self.manipulate_lines(cx, |lines| lines.shuffle(&mut thread_rng()))
-    }
-
-    fn manipulate_lines<Fn>(&mut self, cx: &mut ViewContext<Self>, mut callback: Fn)
-    where
-        Fn: FnMut(&mut [&str]),
-    {
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-        let buffer = self.buffer.read(cx).snapshot(cx);
-
-        let mut edits = Vec::new();
-
-        let selections = self.selections.all::<Point>(cx);
-        let mut selections = selections.iter().peekable();
-        let mut contiguous_row_selections = Vec::new();
-        let mut new_selections = Vec::new();
-
-        while let Some(selection) = selections.next() {
-            let (start_row, end_row) = consume_contiguous_rows(
-                &mut contiguous_row_selections,
-                selection,
-                &display_map,
-                &mut selections,
-            );
-
-            let start_point = Point::new(start_row, 0);
-            let end_point = Point::new(end_row - 1, buffer.line_len(end_row - 1));
-            let text = buffer
-                .text_for_range(start_point..end_point)
-                .collect::<String>();
-            let mut lines = text.split("\n").collect_vec();
-
-            let lines_len = lines.len();
-            callback(&mut lines);
-
-            // This is a current limitation with selections.
-            // If we wanted to support removing or adding lines, we'd need to fix the logic associated with selections.
-            debug_assert!(
-                lines.len() == lines_len,
-                "callback should not change the number of lines"
-            );
-
-            edits.push((start_point..end_point, lines.join("\n")));
-            let start_anchor = buffer.anchor_after(start_point);
-            let end_anchor = buffer.anchor_before(end_point);
-
-            // Make selection and push
-            new_selections.push(Selection {
-                id: selection.id,
-                start: start_anchor.to_offset(&buffer),
-                end: end_anchor.to_offset(&buffer),
-                goal: SelectionGoal::None,
-                reversed: selection.reversed,
-            });
-        }
-
-        self.transact(cx, |this, cx| {
-            this.buffer.update(cx, |buffer, cx| {
-                buffer.edit(edits, None, cx);
-            });
-
-            this.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                s.select(new_selections);
-            });
-
-            this.request_autoscroll(Autoscroll::fit(), cx);
-        });
-    }
-
-    pub fn convert_to_upper_case(&mut self, _: &ConvertToUpperCase, cx: &mut ViewContext<Self>) {
-        self.manipulate_text(cx, |text| text.to_uppercase())
-    }
-
-    pub fn convert_to_lower_case(&mut self, _: &ConvertToLowerCase, cx: &mut ViewContext<Self>) {
-        self.manipulate_text(cx, |text| text.to_lowercase())
-    }
-
-    pub fn convert_to_title_case(&mut self, _: &ConvertToTitleCase, cx: &mut ViewContext<Self>) {
-        self.manipulate_text(cx, |text| {
-            // Hack to get around the fact that to_case crate doesn't support '\n' as a word boundary
-            // https://github.com/rutrum/convert-case/issues/16
-            text.split("\n")
-                .map(|line| line.to_case(Case::Title))
-                .join("\n")
-        })
-    }
-
-    pub fn convert_to_snake_case(&mut self, _: &ConvertToSnakeCase, cx: &mut ViewContext<Self>) {
-        self.manipulate_text(cx, |text| text.to_case(Case::Snake))
-    }
-
-    pub fn convert_to_kebab_case(&mut self, _: &ConvertToKebabCase, cx: &mut ViewContext<Self>) {
-        self.manipulate_text(cx, |text| text.to_case(Case::Kebab))
-    }
-
-    pub fn convert_to_upper_camel_case(
-        &mut self,
-        _: &ConvertToUpperCamelCase,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.manipulate_text(cx, |text| {
-            // Hack to get around the fact that to_case crate doesn't support '\n' as a word boundary
-            // https://github.com/rutrum/convert-case/issues/16
-            text.split("\n")
-                .map(|line| line.to_case(Case::UpperCamel))
-                .join("\n")
-        })
-    }
-
-    pub fn convert_to_lower_camel_case(
-        &mut self,
-        _: &ConvertToLowerCamelCase,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.manipulate_text(cx, |text| text.to_case(Case::Camel))
-    }
-
-    fn manipulate_text<Fn>(&mut self, cx: &mut ViewContext<Self>, mut callback: Fn)
-    where
-        Fn: FnMut(&str) -> String,
-    {
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-        let buffer = self.buffer.read(cx).snapshot(cx);
-
-        let mut new_selections = Vec::new();
-        let mut edits = Vec::new();
-        let mut selection_adjustment = 0i32;
-
-        for selection in self.selections.all::<usize>(cx) {
-            let selection_is_empty = selection.is_empty();
-
-            let (start, end) = if selection_is_empty {
-                let word_range = movement::surrounding_word(
-                    &display_map,
-                    selection.start.to_display_point(&display_map),
-                );
-                let start = word_range.start.to_offset(&display_map, Bias::Left);
-                let end = word_range.end.to_offset(&display_map, Bias::Left);
-                (start, end)
-            } else {
-                (selection.start, selection.end)
-            };
-
-            let text = buffer.text_for_range(start..end).collect::<String>();
-            let old_length = text.len() as i32;
-            let text = callback(&text);
-
-            new_selections.push(Selection {
-                start: (start as i32 - selection_adjustment) as usize,
-                end: ((start + text.len()) as i32 - selection_adjustment) as usize,
-                goal: SelectionGoal::None,
-                ..selection
-            });
-
-            selection_adjustment += old_length - text.len() as i32;
-
-            edits.push((start..end, text));
-        }
-
-        self.transact(cx, |this, cx| {
-            this.buffer.update(cx, |buffer, cx| {
-                buffer.edit(edits, None, cx);
-            });
-
-            this.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                s.select(new_selections);
-            });
-
-            this.request_autoscroll(Autoscroll::fit(), cx);
-        });
-    }
-
-    pub fn duplicate_line(&mut self, _: &DuplicateLine, cx: &mut ViewContext<Self>) {
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-        let buffer = &display_map.buffer_snapshot;
-        let selections = self.selections.all::<Point>(cx);
-
-        let mut edits = Vec::new();
-        let mut selections_iter = selections.iter().peekable();
-        while let Some(selection) = selections_iter.next() {
-            // Avoid duplicating the same lines twice.
-            let mut rows = selection.spanned_rows(false, &display_map);
-
-            while let Some(next_selection) = selections_iter.peek() {
-                let next_rows = next_selection.spanned_rows(false, &display_map);
-                if next_rows.start < rows.end {
-                    rows.end = next_rows.end;
-                    selections_iter.next().unwrap();
-                } else {
-                    break;
-                }
-            }
-
-            // Copy the text from the selected row region and splice it at the start of the region.
-            let start = Point::new(rows.start, 0);
-            let end = Point::new(rows.end - 1, buffer.line_len(rows.end - 1));
-            let text = buffer
-                .text_for_range(start..end)
-                .chain(Some("\n"))
-                .collect::<String>();
-            edits.push((start..start, text));
-        }
-
-        self.transact(cx, |this, cx| {
-            this.buffer.update(cx, |buffer, cx| {
-                buffer.edit(edits, None, cx);
-            });
-
-            this.request_autoscroll(Autoscroll::fit(), cx);
-        });
-    }
-
-    pub fn move_line_up(&mut self, _: &MoveLineUp, cx: &mut ViewContext<Self>) {
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-        let buffer = self.buffer.read(cx).snapshot(cx);
-
-        let mut edits = Vec::new();
-        let mut unfold_ranges = Vec::new();
-        let mut refold_ranges = Vec::new();
-
-        let selections = self.selections.all::<Point>(cx);
-        let mut selections = selections.iter().peekable();
-        let mut contiguous_row_selections = Vec::new();
-        let mut new_selections = Vec::new();
-
-        while let Some(selection) = selections.next() {
-            // Find all the selections that span a contiguous row range
-            let (start_row, end_row) = consume_contiguous_rows(
-                &mut contiguous_row_selections,
-                selection,
-                &display_map,
-                &mut selections,
-            );
-
-            // Move the text spanned by the row range to be before the line preceding the row range
-            if start_row > 0 {
-                let range_to_move = Point::new(start_row - 1, buffer.line_len(start_row - 1))
-                    ..Point::new(end_row - 1, buffer.line_len(end_row - 1));
-                let insertion_point = display_map
-                    .prev_line_boundary(Point::new(start_row - 1, 0))
-                    .0;
-
-                // Don't move lines across excerpts
-                if buffer
-                    .excerpt_boundaries_in_range((
-                        Bound::Excluded(insertion_point),
-                        Bound::Included(range_to_move.end),
-                    ))
-                    .next()
-                    .is_none()
-                {
-                    let text = buffer
-                        .text_for_range(range_to_move.clone())
-                        .flat_map(|s| s.chars())
-                        .skip(1)
-                        .chain(['\n'])
-                        .collect::<String>();
-
-                    edits.push((
-                        buffer.anchor_after(range_to_move.start)
-                            ..buffer.anchor_before(range_to_move.end),
-                        String::new(),
-                    ));
-                    let insertion_anchor = buffer.anchor_after(insertion_point);
-                    edits.push((insertion_anchor..insertion_anchor, text));
-
-                    let row_delta = range_to_move.start.row - insertion_point.row + 1;
-
-                    // Move selections up
-                    new_selections.extend(contiguous_row_selections.drain(..).map(
-                        |mut selection| {
-                            selection.start.row -= row_delta;
-                            selection.end.row -= row_delta;
-                            selection
-                        },
-                    ));
-
-                    // Move folds up
-                    unfold_ranges.push(range_to_move.clone());
-                    for fold in display_map.folds_in_range(
-                        buffer.anchor_before(range_to_move.start)
-                            ..buffer.anchor_after(range_to_move.end),
-                    ) {
-                        let mut start = fold.range.start.to_point(&buffer);
-                        let mut end = fold.range.end.to_point(&buffer);
-                        start.row -= row_delta;
-                        end.row -= row_delta;
-                        refold_ranges.push(start..end);
-                    }
-                }
-            }
-
-            // If we didn't move line(s), preserve the existing selections
-            new_selections.append(&mut contiguous_row_selections);
-        }
-
-        self.transact(cx, |this, cx| {
-            this.unfold_ranges(unfold_ranges, true, true, cx);
-            this.buffer.update(cx, |buffer, cx| {
-                for (range, text) in edits {
-                    buffer.edit([(range, text)], None, cx);
-                }
-            });
-            this.fold_ranges(refold_ranges, true, cx);
-            this.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                s.select(new_selections);
-            })
-        });
-    }
-
-    pub fn move_line_down(&mut self, _: &MoveLineDown, cx: &mut ViewContext<Self>) {
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-        let buffer = self.buffer.read(cx).snapshot(cx);
-
-        let mut edits = Vec::new();
-        let mut unfold_ranges = Vec::new();
-        let mut refold_ranges = Vec::new();
-
-        let selections = self.selections.all::<Point>(cx);
-        let mut selections = selections.iter().peekable();
-        let mut contiguous_row_selections = Vec::new();
-        let mut new_selections = Vec::new();
-
-        while let Some(selection) = selections.next() {
-            // Find all the selections that span a contiguous row range
-            let (start_row, end_row) = consume_contiguous_rows(
-                &mut contiguous_row_selections,
-                selection,
-                &display_map,
-                &mut selections,
-            );
-
-            // Move the text spanned by the row range to be after the last line of the row range
-            if end_row <= buffer.max_point().row {
-                let range_to_move = Point::new(start_row, 0)..Point::new(end_row, 0);
-                let insertion_point = display_map.next_line_boundary(Point::new(end_row, 0)).0;
-
-                // Don't move lines across excerpt boundaries
-                if buffer
-                    .excerpt_boundaries_in_range((
-                        Bound::Excluded(range_to_move.start),
-                        Bound::Included(insertion_point),
-                    ))
-                    .next()
-                    .is_none()
-                {
-                    let mut text = String::from("\n");
-                    text.extend(buffer.text_for_range(range_to_move.clone()));
-                    text.pop(); // Drop trailing newline
-                    edits.push((
-                        buffer.anchor_after(range_to_move.start)
-                            ..buffer.anchor_before(range_to_move.end),
-                        String::new(),
-                    ));
-                    let insertion_anchor = buffer.anchor_after(insertion_point);
-                    edits.push((insertion_anchor..insertion_anchor, text));
-
-                    let row_delta = insertion_point.row - range_to_move.end.row + 1;
-
-                    // Move selections down
-                    new_selections.extend(contiguous_row_selections.drain(..).map(
-                        |mut selection| {
-                            selection.start.row += row_delta;
-                            selection.end.row += row_delta;
-                            selection
-                        },
-                    ));
-
-                    // Move folds down
-                    unfold_ranges.push(range_to_move.clone());
-                    for fold in display_map.folds_in_range(
-                        buffer.anchor_before(range_to_move.start)
-                            ..buffer.anchor_after(range_to_move.end),
-                    ) {
-                        let mut start = fold.range.start.to_point(&buffer);
-                        let mut end = fold.range.end.to_point(&buffer);
-                        start.row += row_delta;
-                        end.row += row_delta;
-                        refold_ranges.push(start..end);
-                    }
-                }
-            }
-
-            // If we didn't move line(s), preserve the existing selections
-            new_selections.append(&mut contiguous_row_selections);
-        }
-
-        self.transact(cx, |this, cx| {
-            this.unfold_ranges(unfold_ranges, true, true, cx);
-            this.buffer.update(cx, |buffer, cx| {
-                for (range, text) in edits {
-                    buffer.edit([(range, text)], None, cx);
-                }
-            });
-            this.fold_ranges(refold_ranges, true, cx);
-            this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
-        });
-    }
-
-    pub fn transpose(&mut self, _: &Transpose, cx: &mut ViewContext<Self>) {
-        let text_layout_details = &self.text_layout_details(cx);
-        self.transact(cx, |this, cx| {
-            let edits = this.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                let mut edits: Vec<(Range<usize>, String)> = Default::default();
-                let line_mode = s.line_mode;
-                s.move_with(|display_map, selection| {
-                    if !selection.is_empty() || line_mode {
-                        return;
-                    }
-
-                    let mut head = selection.head();
-                    let mut transpose_offset = head.to_offset(display_map, Bias::Right);
-                    if head.column() == display_map.line_len(head.row()) {
-                        transpose_offset = display_map
-                            .buffer_snapshot
-                            .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
-                    }
-
-                    if transpose_offset == 0 {
-                        return;
-                    }
-
-                    *head.column_mut() += 1;
-                    head = display_map.clip_point(head, Bias::Right);
-                    let goal = SelectionGoal::HorizontalPosition(
-                        display_map
-                            .x_for_display_point(head, &text_layout_details)
-                            .into(),
-                    );
-                    selection.collapse_to(head, goal);
-
-                    let transpose_start = display_map
-                        .buffer_snapshot
-                        .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
-                    if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
-                        let transpose_end = display_map
-                            .buffer_snapshot
-                            .clip_offset(transpose_offset + 1, Bias::Right);
-                        if let Some(ch) =
-                            display_map.buffer_snapshot.chars_at(transpose_start).next()
-                        {
-                            edits.push((transpose_start..transpose_offset, String::new()));
-                            edits.push((transpose_end..transpose_end, ch.to_string()));
-                        }
-                    }
-                });
-                edits
-            });
-            this.buffer
-                .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
-            let selections = this.selections.all::<usize>(cx);
-            this.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                s.select(selections);
-            });
-        });
-    }
-
-    pub fn cut(&mut self, _: &Cut, cx: &mut ViewContext<Self>) {
-        let mut text = String::new();
-        let buffer = self.buffer.read(cx).snapshot(cx);
-        let mut selections = self.selections.all::<Point>(cx);
-        let mut clipboard_selections = Vec::with_capacity(selections.len());
-        {
-            let max_point = buffer.max_point();
-            let mut is_first = true;
-            for selection in &mut selections {
-                let is_entire_line = selection.is_empty() || self.selections.line_mode;
-                if is_entire_line {
-                    selection.start = Point::new(selection.start.row, 0);
-                    selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
-                    selection.goal = SelectionGoal::None;
-                }
-                if is_first {
-                    is_first = false;
-                } else {
-                    text += "\n";
-                }
-                let mut len = 0;
-                for chunk in buffer.text_for_range(selection.start..selection.end) {
-                    text.push_str(chunk);
-                    len += chunk.len();
-                }
-                clipboard_selections.push(ClipboardSelection {
-                    len,
-                    is_entire_line,
-                    first_line_indent: buffer.indent_size_for_line(selection.start.row).len,
-                });
-            }
-        }
-
-        self.transact(cx, |this, cx| {
-            this.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                s.select(selections);
-            });
-            this.insert("", cx);
-            cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
-        });
-    }
-
-    pub fn copy(&mut self, _: &Copy, cx: &mut ViewContext<Self>) {
-        let selections = self.selections.all::<Point>(cx);
-        let buffer = self.buffer.read(cx).read(cx);
-        let mut text = String::new();
-
-        let mut clipboard_selections = Vec::with_capacity(selections.len());
-        {
-            let max_point = buffer.max_point();
-            let mut is_first = true;
-            for selection in selections.iter() {
-                let mut start = selection.start;
-                let mut end = selection.end;
-                let is_entire_line = selection.is_empty() || self.selections.line_mode;
-                if is_entire_line {
-                    start = Point::new(start.row, 0);
-                    end = cmp::min(max_point, Point::new(end.row + 1, 0));
-                }
-                if is_first {
-                    is_first = false;
-                } else {
-                    text += "\n";
-                }
-                let mut len = 0;
-                for chunk in buffer.text_for_range(start..end) {
-                    text.push_str(chunk);
-                    len += chunk.len();
-                }
-                clipboard_selections.push(ClipboardSelection {
-                    len,
-                    is_entire_line,
-                    first_line_indent: buffer.indent_size_for_line(start.row).len,
-                });
-            }
-        }
-
-        cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
-    }
-
-    pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
-        self.transact(cx, |this, cx| {
-            if let Some(item) = cx.read_from_clipboard() {
-                let clipboard_text = Cow::Borrowed(item.text());
-                if let Some(mut clipboard_selections) = item.metadata::<Vec<ClipboardSelection>>() {
-                    let old_selections = this.selections.all::<usize>(cx);
-                    let all_selections_were_entire_line =
-                        clipboard_selections.iter().all(|s| s.is_entire_line);
-                    let first_selection_indent_column =
-                        clipboard_selections.first().map(|s| s.first_line_indent);
-                    if clipboard_selections.len() != old_selections.len() {
-                        clipboard_selections.drain(..);
-                    }
-
-                    this.buffer.update(cx, |buffer, cx| {
-                        let snapshot = buffer.read(cx);
-                        let mut start_offset = 0;
-                        let mut edits = Vec::new();
-                        let mut original_indent_columns = Vec::new();
-                        let line_mode = this.selections.line_mode;
-                        for (ix, selection) in old_selections.iter().enumerate() {
-                            let to_insert;
-                            let entire_line;
-                            let original_indent_column;
-                            if let Some(clipboard_selection) = clipboard_selections.get(ix) {
-                                let end_offset = start_offset + clipboard_selection.len;
-                                to_insert = &clipboard_text[start_offset..end_offset];
-                                entire_line = clipboard_selection.is_entire_line;
-                                start_offset = end_offset + 1;
-                                original_indent_column =
-                                    Some(clipboard_selection.first_line_indent);
-                            } else {
-                                to_insert = clipboard_text.as_str();
-                                entire_line = all_selections_were_entire_line;
-                                original_indent_column = first_selection_indent_column
-                            }
-
-                            // If the corresponding selection was empty when this slice of the
-                            // clipboard text was written, then the entire line containing the
-                            // selection was copied. If this selection is also currently empty,
-                            // then paste the line before the current line of the buffer.
-                            let range = if selection.is_empty() && !line_mode && entire_line {
-                                let column = selection.start.to_point(&snapshot).column as usize;
-                                let line_start = selection.start - column;
-                                line_start..line_start
-                            } else {
-                                selection.range()
-                            };
-
-                            edits.push((range, to_insert));
-                            original_indent_columns.extend(original_indent_column);
-                        }
-                        drop(snapshot);
-
-                        buffer.edit(
-                            edits,
-                            Some(AutoindentMode::Block {
-                                original_indent_columns,
-                            }),
-                            cx,
-                        );
-                    });
-
-                    let selections = this.selections.all::<usize>(cx);
-                    this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
-                } else {
-                    this.insert(&clipboard_text, cx);
-                }
-            }
-        });
-    }
-
-    pub fn undo(&mut self, _: &Undo, cx: &mut ViewContext<Self>) {
-        if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
-            if let Some((selections, _)) = self.selection_history.transaction(tx_id).cloned() {
-                self.change_selections(None, cx, |s| {
-                    s.select_anchors(selections.to_vec());
-                });
-            }
-            self.request_autoscroll(Autoscroll::fit(), cx);
-            self.unmark_text(cx);
-            self.refresh_copilot_suggestions(true, cx);
-            cx.emit(EditorEvent::Edited);
-        }
-    }
-
-    pub fn redo(&mut self, _: &Redo, cx: &mut ViewContext<Self>) {
-        if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
-            if let Some((_, Some(selections))) = self.selection_history.transaction(tx_id).cloned()
-            {
-                self.change_selections(None, cx, |s| {
-                    s.select_anchors(selections.to_vec());
-                });
-            }
-            self.request_autoscroll(Autoscroll::fit(), cx);
-            self.unmark_text(cx);
-            self.refresh_copilot_suggestions(true, cx);
-            cx.emit(EditorEvent::Edited);
-        }
-    }
-
-    pub fn finalize_last_transaction(&mut self, cx: &mut ViewContext<Self>) {
-        self.buffer
-            .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
-    }
-
-    pub fn move_left(&mut self, _: &MoveLeft, cx: &mut ViewContext<Self>) {
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            let line_mode = s.line_mode;
-            s.move_with(|map, selection| {
-                let cursor = if selection.is_empty() && !line_mode {
-                    movement::left(map, selection.start)
-                } else {
-                    selection.start
-                };
-                selection.collapse_to(cursor, SelectionGoal::None);
-            });
-        })
-    }
-
-    pub fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext<Self>) {
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
-        })
-    }
-
-    pub fn move_right(&mut self, _: &MoveRight, cx: &mut ViewContext<Self>) {
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            let line_mode = s.line_mode;
-            s.move_with(|map, selection| {
-                let cursor = if selection.is_empty() && !line_mode {
-                    movement::right(map, selection.end)
-                } else {
-                    selection.end
-                };
-                selection.collapse_to(cursor, SelectionGoal::None)
-            });
-        })
-    }
-
-    pub fn select_right(&mut self, _: &SelectRight, cx: &mut ViewContext<Self>) {
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
-        })
-    }
-
-    pub fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
-        if self.take_rename(true, cx).is_some() {
-            return;
-        }
-
-        if matches!(self.mode, EditorMode::SingleLine) {
-            cx.propagate();
-            return;
-        }
-
-        let text_layout_details = &self.text_layout_details(cx);
-
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            let line_mode = s.line_mode;
-            s.move_with(|map, selection| {
-                if !selection.is_empty() && !line_mode {
-                    selection.goal = SelectionGoal::None;
-                }
-                let (cursor, goal) = movement::up(
-                    map,
-                    selection.start,
-                    selection.goal,
-                    false,
-                    &text_layout_details,
-                );
-                selection.collapse_to(cursor, goal);
-            });
-        })
-    }
-
-    pub fn move_page_up(&mut self, action: &MovePageUp, cx: &mut ViewContext<Self>) {
-        if self.take_rename(true, cx).is_some() {
-            return;
-        }
-
-        if matches!(self.mode, EditorMode::SingleLine) {
-            cx.propagate();
-            return;
-        }
-
-        let row_count = if let Some(row_count) = self.visible_line_count() {
-            row_count as u32 - 1
-        } else {
-            return;
-        };
-
-        let autoscroll = if action.center_cursor {
-            Autoscroll::center()
-        } else {
-            Autoscroll::fit()
-        };
-
-        let text_layout_details = &self.text_layout_details(cx);
-
-        self.change_selections(Some(autoscroll), cx, |s| {
-            let line_mode = s.line_mode;
-            s.move_with(|map, selection| {
-                if !selection.is_empty() && !line_mode {
-                    selection.goal = SelectionGoal::None;
-                }
-                let (cursor, goal) = movement::up_by_rows(
-                    map,
-                    selection.end,
-                    row_count,
-                    selection.goal,
-                    false,
-                    &text_layout_details,
-                );
-                selection.collapse_to(cursor, goal);
-            });
-        });
-    }
-
-    pub fn select_up(&mut self, _: &SelectUp, cx: &mut ViewContext<Self>) {
-        let text_layout_details = &self.text_layout_details(cx);
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.move_heads_with(|map, head, goal| {
-                movement::up(map, head, goal, false, &text_layout_details)
-            })
-        })
-    }
-
-    pub fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
-        self.take_rename(true, cx);
-
-        if self.mode == EditorMode::SingleLine {
-            cx.propagate();
-            return;
-        }
-
-        let text_layout_details = &self.text_layout_details(cx);
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            let line_mode = s.line_mode;
-            s.move_with(|map, selection| {
-                if !selection.is_empty() && !line_mode {
-                    selection.goal = SelectionGoal::None;
-                }
-                let (cursor, goal) = movement::down(
-                    map,
-                    selection.end,
-                    selection.goal,
-                    false,
-                    &text_layout_details,
-                );
-                selection.collapse_to(cursor, goal);
-            });
-        });
-    }
-
-    pub fn move_page_down(&mut self, action: &MovePageDown, cx: &mut ViewContext<Self>) {
-        if self.take_rename(true, cx).is_some() {
-            return;
-        }
-
-        if self
-            .context_menu
-            .write()
-            .as_mut()
-            .map(|menu| menu.select_last(self.project.as_ref(), cx))
-            .unwrap_or(false)
-        {
-            return;
-        }
-
-        if matches!(self.mode, EditorMode::SingleLine) {
-            cx.propagate();
-            return;
-        }
-
-        let row_count = if let Some(row_count) = self.visible_line_count() {
-            row_count as u32 - 1
-        } else {
-            return;
-        };
-
-        let autoscroll = if action.center_cursor {
-            Autoscroll::center()
-        } else {
-            Autoscroll::fit()
-        };
-
-        let text_layout_details = &self.text_layout_details(cx);
-        self.change_selections(Some(autoscroll), cx, |s| {
-            let line_mode = s.line_mode;
-            s.move_with(|map, selection| {
-                if !selection.is_empty() && !line_mode {
-                    selection.goal = SelectionGoal::None;
-                }
-                let (cursor, goal) = movement::down_by_rows(
-                    map,
-                    selection.end,
-                    row_count,
-                    selection.goal,
-                    false,
-                    &text_layout_details,
-                );
-                selection.collapse_to(cursor, goal);
-            });
-        });
-    }
-
-    pub fn select_down(&mut self, _: &SelectDown, cx: &mut ViewContext<Self>) {
-        let text_layout_details = &self.text_layout_details(cx);
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.move_heads_with(|map, head, goal| {
-                movement::down(map, head, goal, false, &text_layout_details)
-            })
-        });
-    }
-
-    pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext<Self>) {
-        if let Some(context_menu) = self.context_menu.write().as_mut() {
-            context_menu.select_first(self.project.as_ref(), cx);
-        }
-    }
-
-    pub fn context_menu_prev(&mut self, _: &ContextMenuPrev, cx: &mut ViewContext<Self>) {
-        if let Some(context_menu) = self.context_menu.write().as_mut() {
-            context_menu.select_prev(self.project.as_ref(), cx);
-        }
-    }
-
-    pub fn context_menu_next(&mut self, _: &ContextMenuNext, cx: &mut ViewContext<Self>) {
-        if let Some(context_menu) = self.context_menu.write().as_mut() {
-            context_menu.select_next(self.project.as_ref(), cx);
-        }
-    }
-
-    pub fn context_menu_last(&mut self, _: &ContextMenuLast, cx: &mut ViewContext<Self>) {
-        if let Some(context_menu) = self.context_menu.write().as_mut() {
-            context_menu.select_last(self.project.as_ref(), cx);
-        }
-    }
-
-    pub fn move_to_previous_word_start(
-        &mut self,
-        _: &MoveToPreviousWordStart,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.move_cursors_with(|map, head, _| {
-                (
-                    movement::previous_word_start(map, head),
-                    SelectionGoal::None,
-                )
-            });
-        })
-    }
-
-    pub fn move_to_previous_subword_start(
-        &mut self,
-        _: &MoveToPreviousSubwordStart,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.move_cursors_with(|map, head, _| {
-                (
-                    movement::previous_subword_start(map, head),
-                    SelectionGoal::None,
-                )
-            });
-        })
-    }
-
-    pub fn select_to_previous_word_start(
-        &mut self,
-        _: &SelectToPreviousWordStart,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.move_heads_with(|map, head, _| {
-                (
-                    movement::previous_word_start(map, head),
-                    SelectionGoal::None,
-                )
-            });
-        })
-    }
-
-    pub fn select_to_previous_subword_start(
-        &mut self,
-        _: &SelectToPreviousSubwordStart,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.move_heads_with(|map, head, _| {
-                (
-                    movement::previous_subword_start(map, head),
-                    SelectionGoal::None,
-                )
-            });
-        })
-    }
-
-    pub fn delete_to_previous_word_start(
-        &mut self,
-        _: &DeleteToPreviousWordStart,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.transact(cx, |this, cx| {
-            this.select_autoclose_pair(cx);
-            this.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                let line_mode = s.line_mode;
-                s.move_with(|map, selection| {
-                    if selection.is_empty() && !line_mode {
-                        let cursor = movement::previous_word_start(map, selection.head());
-                        selection.set_head(cursor, SelectionGoal::None);
-                    }
-                });
-            });
-            this.insert("", cx);
-        });
-    }
-
-    pub fn delete_to_previous_subword_start(
-        &mut self,
-        _: &DeleteToPreviousSubwordStart,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.transact(cx, |this, cx| {
-            this.select_autoclose_pair(cx);
-            this.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                let line_mode = s.line_mode;
-                s.move_with(|map, selection| {
-                    if selection.is_empty() && !line_mode {
-                        let cursor = movement::previous_subword_start(map, selection.head());
-                        selection.set_head(cursor, SelectionGoal::None);
-                    }
-                });
-            });
-            this.insert("", cx);
-        });
-    }
-
-    pub fn move_to_next_word_end(&mut self, _: &MoveToNextWordEnd, cx: &mut ViewContext<Self>) {
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.move_cursors_with(|map, head, _| {
-                (movement::next_word_end(map, head), SelectionGoal::None)
-            });
-        })
-    }
-
-    pub fn move_to_next_subword_end(
-        &mut self,
-        _: &MoveToNextSubwordEnd,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.move_cursors_with(|map, head, _| {
-                (movement::next_subword_end(map, head), SelectionGoal::None)
-            });
-        })
-    }
-
-    pub fn select_to_next_word_end(&mut self, _: &SelectToNextWordEnd, cx: &mut ViewContext<Self>) {
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.move_heads_with(|map, head, _| {
-                (movement::next_word_end(map, head), SelectionGoal::None)
-            });
-        })
-    }
-
-    pub fn select_to_next_subword_end(
-        &mut self,
-        _: &SelectToNextSubwordEnd,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.move_heads_with(|map, head, _| {
-                (movement::next_subword_end(map, head), SelectionGoal::None)
-            });
-        })
-    }
-
-    pub fn delete_to_next_word_end(&mut self, _: &DeleteToNextWordEnd, cx: &mut ViewContext<Self>) {
-        self.transact(cx, |this, cx| {
-            this.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                let line_mode = s.line_mode;
-                s.move_with(|map, selection| {
-                    if selection.is_empty() && !line_mode {
-                        let cursor = movement::next_word_end(map, selection.head());
-                        selection.set_head(cursor, SelectionGoal::None);
-                    }
-                });
-            });
-            this.insert("", cx);
-        });
-    }
-
-    pub fn delete_to_next_subword_end(
-        &mut self,
-        _: &DeleteToNextSubwordEnd,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.transact(cx, |this, cx| {
-            this.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                s.move_with(|map, selection| {
-                    if selection.is_empty() {
-                        let cursor = movement::next_subword_end(map, selection.head());
-                        selection.set_head(cursor, SelectionGoal::None);
-                    }
-                });
-            });
-            this.insert("", cx);
-        });
-    }
-
-    pub fn move_to_beginning_of_line(
-        &mut self,
-        _: &MoveToBeginningOfLine,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.move_cursors_with(|map, head, _| {
-                (
-                    movement::indented_line_beginning(map, head, true),
-                    SelectionGoal::None,
-                )
-            });
-        })
-    }
-
-    pub fn select_to_beginning_of_line(
-        &mut self,
-        action: &SelectToBeginningOfLine,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.move_heads_with(|map, head, _| {
-                (
-                    movement::indented_line_beginning(map, head, action.stop_at_soft_wraps),
-                    SelectionGoal::None,
-                )
-            });
-        });
-    }
-
-    pub fn delete_to_beginning_of_line(
-        &mut self,
-        _: &DeleteToBeginningOfLine,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.transact(cx, |this, cx| {
-            this.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                s.move_with(|_, selection| {
-                    selection.reversed = true;
-                });
-            });
-
-            this.select_to_beginning_of_line(
-                &SelectToBeginningOfLine {
-                    stop_at_soft_wraps: false,
-                },
-                cx,
-            );
-            this.backspace(&Backspace, cx);
-        });
-    }
-
-    pub fn move_to_end_of_line(&mut self, _: &MoveToEndOfLine, cx: &mut ViewContext<Self>) {
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.move_cursors_with(|map, head, _| {
-                (movement::line_end(map, head, true), SelectionGoal::None)
-            });
-        })
-    }
-
-    pub fn select_to_end_of_line(
-        &mut self,
-        action: &SelectToEndOfLine,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.move_heads_with(|map, head, _| {
-                (
-                    movement::line_end(map, head, action.stop_at_soft_wraps),
-                    SelectionGoal::None,
-                )
-            });
-        })
-    }
-
-    pub fn delete_to_end_of_line(&mut self, _: &DeleteToEndOfLine, cx: &mut ViewContext<Self>) {
-        self.transact(cx, |this, cx| {
-            this.select_to_end_of_line(
-                &SelectToEndOfLine {
-                    stop_at_soft_wraps: false,
-                },
-                cx,
-            );
-            this.delete(&Delete, cx);
-        });
-    }
-
-    pub fn cut_to_end_of_line(&mut self, _: &CutToEndOfLine, cx: &mut ViewContext<Self>) {
-        self.transact(cx, |this, cx| {
-            this.select_to_end_of_line(
-                &SelectToEndOfLine {
-                    stop_at_soft_wraps: false,
-                },
-                cx,
-            );
-            this.cut(&Cut, cx);
-        });
-    }
-
-    pub fn move_to_start_of_paragraph(
-        &mut self,
-        _: &MoveToStartOfParagraph,
-        cx: &mut ViewContext<Self>,
-    ) {
-        if matches!(self.mode, EditorMode::SingleLine) {
-            cx.propagate();
-            return;
-        }
-
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.move_with(|map, selection| {
-                selection.collapse_to(
-                    movement::start_of_paragraph(map, selection.head(), 1),
-                    SelectionGoal::None,
-                )
-            });
-        })
-    }
-
-    pub fn move_to_end_of_paragraph(
-        &mut self,
-        _: &MoveToEndOfParagraph,
-        cx: &mut ViewContext<Self>,
-    ) {
-        if matches!(self.mode, EditorMode::SingleLine) {
-            cx.propagate();
-            return;
-        }
-
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.move_with(|map, selection| {
-                selection.collapse_to(
-                    movement::end_of_paragraph(map, selection.head(), 1),
-                    SelectionGoal::None,
-                )
-            });
-        })
-    }
-
-    pub fn select_to_start_of_paragraph(
-        &mut self,
-        _: &SelectToStartOfParagraph,
-        cx: &mut ViewContext<Self>,
-    ) {
-        if matches!(self.mode, EditorMode::SingleLine) {
-            cx.propagate();
-            return;
-        }
-
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.move_heads_with(|map, head, _| {
-                (
-                    movement::start_of_paragraph(map, head, 1),
-                    SelectionGoal::None,
-                )
-            });
-        })
-    }
-
-    pub fn select_to_end_of_paragraph(
-        &mut self,
-        _: &SelectToEndOfParagraph,
-        cx: &mut ViewContext<Self>,
-    ) {
-        if matches!(self.mode, EditorMode::SingleLine) {
-            cx.propagate();
-            return;
-        }
-
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.move_heads_with(|map, head, _| {
-                (
-                    movement::end_of_paragraph(map, head, 1),
-                    SelectionGoal::None,
-                )
-            });
-        })
-    }
-
-    pub fn move_to_beginning(&mut self, _: &MoveToBeginning, cx: &mut ViewContext<Self>) {
-        if matches!(self.mode, EditorMode::SingleLine) {
-            cx.propagate();
-            return;
-        }
-
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.select_ranges(vec![0..0]);
-        });
-    }
-
-    pub fn select_to_beginning(&mut self, _: &SelectToBeginning, cx: &mut ViewContext<Self>) {
-        let mut selection = self.selections.last::<Point>(cx);
-        selection.set_head(Point::zero(), SelectionGoal::None);
-
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.select(vec![selection]);
-        });
-    }
-
-    pub fn move_to_end(&mut self, _: &MoveToEnd, cx: &mut ViewContext<Self>) {
-        if matches!(self.mode, EditorMode::SingleLine) {
-            cx.propagate();
-            return;
-        }
-
-        let cursor = self.buffer.read(cx).read(cx).len();
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.select_ranges(vec![cursor..cursor])
-        });
-    }
-
-    pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
-        self.nav_history = nav_history;
-    }
-
-    pub fn nav_history(&self) -> Option<&ItemNavHistory> {
-        self.nav_history.as_ref()
-    }
-
-    fn push_to_nav_history(
-        &mut self,
-        cursor_anchor: Anchor,
-        new_position: Option<Point>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        if let Some(nav_history) = self.nav_history.as_mut() {
-            let buffer = self.buffer.read(cx).read(cx);
-            let cursor_position = cursor_anchor.to_point(&buffer);
-            let scroll_state = self.scroll_manager.anchor();
-            let scroll_top_row = scroll_state.top_row(&buffer);
-            drop(buffer);
-
-            if let Some(new_position) = new_position {
-                let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
-                if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
-                    return;
-                }
-            }
-
-            nav_history.push(
-                Some(NavigationData {
-                    cursor_anchor,
-                    cursor_position,
-                    scroll_anchor: scroll_state,
-                    scroll_top_row,
-                }),
-                cx,
-            );
-        }
-    }
-
-    pub fn select_to_end(&mut self, _: &SelectToEnd, cx: &mut ViewContext<Self>) {
-        let buffer = self.buffer.read(cx).snapshot(cx);
-        let mut selection = self.selections.first::<usize>(cx);
-        selection.set_head(buffer.len(), SelectionGoal::None);
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.select(vec![selection]);
-        });
-    }
-
-    pub fn select_all(&mut self, _: &SelectAll, cx: &mut ViewContext<Self>) {
-        let end = self.buffer.read(cx).read(cx).len();
-        self.change_selections(None, cx, |s| {
-            s.select_ranges(vec![0..end]);
-        });
-    }
-
-    pub fn select_line(&mut self, _: &SelectLine, cx: &mut ViewContext<Self>) {
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-        let mut selections = self.selections.all::<Point>(cx);
-        let max_point = display_map.buffer_snapshot.max_point();
-        for selection in &mut selections {
-            let rows = selection.spanned_rows(true, &display_map);
-            selection.start = Point::new(rows.start, 0);
-            selection.end = cmp::min(max_point, Point::new(rows.end, 0));
-            selection.reversed = false;
-        }
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.select(selections);
-        });
-    }
-
-    pub fn split_selection_into_lines(
-        &mut self,
-        _: &SplitSelectionIntoLines,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let mut to_unfold = Vec::new();
-        let mut new_selection_ranges = Vec::new();
-        {
-            let selections = self.selections.all::<Point>(cx);
-            let buffer = self.buffer.read(cx).read(cx);
-            for selection in selections {
-                for row in selection.start.row..selection.end.row {
-                    let cursor = Point::new(row, buffer.line_len(row));
-                    new_selection_ranges.push(cursor..cursor);
-                }
-                new_selection_ranges.push(selection.end..selection.end);
-                to_unfold.push(selection.start..selection.end);
-            }
-        }
-        self.unfold_ranges(to_unfold, true, true, cx);
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.select_ranges(new_selection_ranges);
-        });
-    }
-
-    pub fn add_selection_above(&mut self, _: &AddSelectionAbove, cx: &mut ViewContext<Self>) {
-        self.add_selection(true, cx);
-    }
-
-    pub fn add_selection_below(&mut self, _: &AddSelectionBelow, cx: &mut ViewContext<Self>) {
-        self.add_selection(false, cx);
-    }
-
-    fn add_selection(&mut self, above: bool, cx: &mut ViewContext<Self>) {
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-        let mut selections = self.selections.all::<Point>(cx);
-        let text_layout_details = self.text_layout_details(cx);
-        let mut state = self.add_selections_state.take().unwrap_or_else(|| {
-            let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
-            let range = oldest_selection.display_range(&display_map).sorted();
-
-            let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
-            let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
-            let positions = start_x.min(end_x)..start_x.max(end_x);
-
-            selections.clear();
-            let mut stack = Vec::new();
-            for row in range.start.row()..=range.end.row() {
-                if let Some(selection) = self.selections.build_columnar_selection(
-                    &display_map,
-                    row,
-                    &positions,
-                    oldest_selection.reversed,
-                    &text_layout_details,
-                ) {
-                    stack.push(selection.id);
-                    selections.push(selection);
-                }
-            }
-
-            if above {
-                stack.reverse();
-            }
-
-            AddSelectionsState { above, stack }
-        });
-
-        let last_added_selection = *state.stack.last().unwrap();
-        let mut new_selections = Vec::new();
-        if above == state.above {
-            let end_row = if above {
-                0
-            } else {
-                display_map.max_point().row()
-            };
-
-            'outer: for selection in selections {
-                if selection.id == last_added_selection {
-                    let range = selection.display_range(&display_map).sorted();
-                    debug_assert_eq!(range.start.row(), range.end.row());
-                    let mut row = range.start.row();
-                    let positions =
-                        if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
-                            px(start)..px(end)
-                        } else {
-                            let start_x =
-                                display_map.x_for_display_point(range.start, &text_layout_details);
-                            let end_x =
-                                display_map.x_for_display_point(range.end, &text_layout_details);
-                            start_x.min(end_x)..start_x.max(end_x)
-                        };
-
-                    while row != end_row {
-                        if above {
-                            row -= 1;
-                        } else {
-                            row += 1;
-                        }
-
-                        if let Some(new_selection) = self.selections.build_columnar_selection(
-                            &display_map,
-                            row,
-                            &positions,
-                            selection.reversed,
-                            &text_layout_details,
-                        ) {
-                            state.stack.push(new_selection.id);
-                            if above {
-                                new_selections.push(new_selection);
-                                new_selections.push(selection);
-                            } else {
-                                new_selections.push(selection);
-                                new_selections.push(new_selection);
-                            }
-
-                            continue 'outer;
-                        }
-                    }
-                }
-
-                new_selections.push(selection);
-            }
-        } else {
-            new_selections = selections;
-            new_selections.retain(|s| s.id != last_added_selection);
-            state.stack.pop();
-        }
-
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.select(new_selections);
-        });
-        if state.stack.len() > 1 {
-            self.add_selections_state = Some(state);
-        }
-    }
-
-    pub fn select_next_match_internal(
-        &mut self,
-        display_map: &DisplaySnapshot,
-        replace_newest: bool,
-        autoscroll: Option<Autoscroll>,
-        cx: &mut ViewContext<Self>,
-    ) -> Result<()> {
-        fn select_next_match_ranges(
-            this: &mut Editor,
-            range: Range<usize>,
-            replace_newest: bool,
-            auto_scroll: Option<Autoscroll>,
-            cx: &mut ViewContext<Editor>,
-        ) {
-            this.unfold_ranges([range.clone()], false, true, cx);
-            this.change_selections(auto_scroll, cx, |s| {
-                if replace_newest {
-                    s.delete(s.newest_anchor().id);
-                }
-                s.insert_range(range.clone());
-            });
-        }
-
-        let buffer = &display_map.buffer_snapshot;
-        let mut selections = self.selections.all::<usize>(cx);
-        if let Some(mut select_next_state) = self.select_next_state.take() {
-            let query = &select_next_state.query;
-            if !select_next_state.done {
-                let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
-                let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
-                let mut next_selected_range = None;
-
-                let bytes_after_last_selection =
-                    buffer.bytes_in_range(last_selection.end..buffer.len());
-                let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
-                let query_matches = query
-                    .stream_find_iter(bytes_after_last_selection)
-                    .map(|result| (last_selection.end, result))
-                    .chain(
-                        query
-                            .stream_find_iter(bytes_before_first_selection)
-                            .map(|result| (0, result)),
-                    );
-
-                for (start_offset, query_match) in query_matches {
-                    let query_match = query_match.unwrap(); // can only fail due to I/O
-                    let offset_range =
-                        start_offset + query_match.start()..start_offset + query_match.end();
-                    let display_range = offset_range.start.to_display_point(&display_map)
-                        ..offset_range.end.to_display_point(&display_map);
-
-                    if !select_next_state.wordwise
-                        || (!movement::is_inside_word(&display_map, display_range.start)
-                            && !movement::is_inside_word(&display_map, display_range.end))
-                    {
-                        if selections
-                            .iter()
-                            .find(|selection| selection.range().overlaps(&offset_range))
-                            .is_none()
-                        {
-                            next_selected_range = Some(offset_range);
-                            break;
-                        }
-                    }
-                }
-
-                if let Some(next_selected_range) = next_selected_range {
-                    select_next_match_ranges(
-                        self,
-                        next_selected_range,
-                        replace_newest,
-                        autoscroll,
-                        cx,
-                    );
-                } else {
-                    select_next_state.done = true;
-                }
-            }
-
-            self.select_next_state = Some(select_next_state);
-        } else if selections.len() == 1 {
-            let selection = selections.last_mut().unwrap();
-            if selection.start == selection.end {
-                let word_range = movement::surrounding_word(
-                    &display_map,
-                    selection.start.to_display_point(&display_map),
-                );
-                selection.start = word_range.start.to_offset(&display_map, Bias::Left);
-                selection.end = word_range.end.to_offset(&display_map, Bias::Left);
-                selection.goal = SelectionGoal::None;
-                selection.reversed = false;
-
-                let query = buffer
-                    .text_for_range(selection.start..selection.end)
-                    .collect::<String>();
-
-                let is_empty = query.is_empty();
-                let select_state = SelectNextState {
-                    query: AhoCorasick::new(&[query])?,
-                    wordwise: true,
-                    done: is_empty,
-                };
-                select_next_match_ranges(
-                    self,
-                    selection.start..selection.end,
-                    replace_newest,
-                    autoscroll,
-                    cx,
-                );
-                self.select_next_state = Some(select_state);
-            } else {
-                let query = buffer
-                    .text_for_range(selection.start..selection.end)
-                    .collect::<String>();
-                self.select_next_state = Some(SelectNextState {
-                    query: AhoCorasick::new(&[query])?,
-                    wordwise: false,
-                    done: false,
-                });
-                self.select_next_match_internal(display_map, replace_newest, autoscroll, cx)?;
-            }
-        }
-        Ok(())
-    }
-
-    pub fn select_all_matches(
-        &mut self,
-        action: &SelectAllMatches,
-        cx: &mut ViewContext<Self>,
-    ) -> Result<()> {
-        self.push_to_selection_history();
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-
-        loop {
-            self.select_next_match_internal(&display_map, action.replace_newest, None, cx)?;
-
-            if self
-                .select_next_state
-                .as_ref()
-                .map(|selection_state| selection_state.done)
-                .unwrap_or(true)
-            {
-                break;
-            }
-        }
-
-        Ok(())
-    }
-
-    pub fn select_next(&mut self, action: &SelectNext, cx: &mut ViewContext<Self>) -> Result<()> {
-        self.push_to_selection_history();
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-        self.select_next_match_internal(
-            &display_map,
-            action.replace_newest,
-            Some(Autoscroll::newest()),
-            cx,
-        )?;
-        Ok(())
-    }
-
-    pub fn select_previous(
-        &mut self,
-        action: &SelectPrevious,
-        cx: &mut ViewContext<Self>,
-    ) -> Result<()> {
-        self.push_to_selection_history();
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-        let buffer = &display_map.buffer_snapshot;
-        let mut selections = self.selections.all::<usize>(cx);
-        if let Some(mut select_prev_state) = self.select_prev_state.take() {
-            let query = &select_prev_state.query;
-            if !select_prev_state.done {
-                let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
-                let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
-                let mut next_selected_range = None;
-                // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
-                let bytes_before_last_selection =
-                    buffer.reversed_bytes_in_range(0..last_selection.start);
-                let bytes_after_first_selection =
-                    buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
-                let query_matches = query
-                    .stream_find_iter(bytes_before_last_selection)
-                    .map(|result| (last_selection.start, result))
-                    .chain(
-                        query
-                            .stream_find_iter(bytes_after_first_selection)
-                            .map(|result| (buffer.len(), result)),
-                    );
-                for (end_offset, query_match) in query_matches {
-                    let query_match = query_match.unwrap(); // can only fail due to I/O
-                    let offset_range =
-                        end_offset - query_match.end()..end_offset - query_match.start();
-                    let display_range = offset_range.start.to_display_point(&display_map)
-                        ..offset_range.end.to_display_point(&display_map);
-
-                    if !select_prev_state.wordwise
-                        || (!movement::is_inside_word(&display_map, display_range.start)
-                            && !movement::is_inside_word(&display_map, display_range.end))
-                    {
-                        next_selected_range = Some(offset_range);
-                        break;
-                    }
-                }
-
-                if let Some(next_selected_range) = next_selected_range {
-                    self.unfold_ranges([next_selected_range.clone()], false, true, cx);
-                    self.change_selections(Some(Autoscroll::newest()), cx, |s| {
-                        if action.replace_newest {
-                            s.delete(s.newest_anchor().id);
-                        }
-                        s.insert_range(next_selected_range);
-                    });
-                } else {
-                    select_prev_state.done = true;
-                }
-            }
-
-            self.select_prev_state = Some(select_prev_state);
-        } else if selections.len() == 1 {
-            let selection = selections.last_mut().unwrap();
-            if selection.start == selection.end {
-                let word_range = movement::surrounding_word(
-                    &display_map,
-                    selection.start.to_display_point(&display_map),
-                );
-                selection.start = word_range.start.to_offset(&display_map, Bias::Left);
-                selection.end = word_range.end.to_offset(&display_map, Bias::Left);
-                selection.goal = SelectionGoal::None;
-                selection.reversed = false;
-
-                let query = buffer
-                    .text_for_range(selection.start..selection.end)
-                    .collect::<String>();
-                let query = query.chars().rev().collect::<String>();
-                let select_state = SelectNextState {
-                    query: AhoCorasick::new(&[query])?,
-                    wordwise: true,
-                    done: false,
-                };
-                self.unfold_ranges([selection.start..selection.end], false, true, cx);
-                self.change_selections(Some(Autoscroll::newest()), cx, |s| {
-                    s.select(selections);
-                });
-                self.select_prev_state = Some(select_state);
-            } else {
-                let query = buffer
-                    .text_for_range(selection.start..selection.end)
-                    .collect::<String>();
-                let query = query.chars().rev().collect::<String>();
-                self.select_prev_state = Some(SelectNextState {
-                    query: AhoCorasick::new(&[query])?,
-                    wordwise: false,
-                    done: false,
-                });
-                self.select_previous(action, cx)?;
-            }
-        }
-        Ok(())
-    }
-
-    pub fn toggle_comments(&mut self, action: &ToggleComments, cx: &mut ViewContext<Self>) {
-        let text_layout_details = &self.text_layout_details(cx);
-        self.transact(cx, |this, cx| {
-            let mut selections = this.selections.all::<Point>(cx);
-            let mut edits = Vec::new();
-            let mut selection_edit_ranges = Vec::new();
-            let mut last_toggled_row = None;
-            let snapshot = this.buffer.read(cx).read(cx);
-            let empty_str: Arc<str> = "".into();
-            let mut suffixes_inserted = Vec::new();
-
-            fn comment_prefix_range(
-                snapshot: &MultiBufferSnapshot,
-                row: u32,
-                comment_prefix: &str,
-                comment_prefix_whitespace: &str,
-            ) -> Range<Point> {
-                let start = Point::new(row, snapshot.indent_size_for_line(row).len);
-
-                let mut line_bytes = snapshot
-                    .bytes_in_range(start..snapshot.max_point())
-                    .flatten()
-                    .copied();
-
-                // If this line currently begins with the line comment prefix, then record
-                // the range containing the prefix.
-                if line_bytes
-                    .by_ref()
-                    .take(comment_prefix.len())
-                    .eq(comment_prefix.bytes())
-                {
-                    // Include any whitespace that matches the comment prefix.
-                    let matching_whitespace_len = line_bytes
-                        .zip(comment_prefix_whitespace.bytes())
-                        .take_while(|(a, b)| a == b)
-                        .count() as u32;
-                    let end = Point::new(
-                        start.row,
-                        start.column + comment_prefix.len() as u32 + matching_whitespace_len,
-                    );
-                    start..end
-                } else {
-                    start..start
-                }
-            }
-
-            fn comment_suffix_range(
-                snapshot: &MultiBufferSnapshot,
-                row: u32,
-                comment_suffix: &str,
-                comment_suffix_has_leading_space: bool,
-            ) -> Range<Point> {
-                let end = Point::new(row, snapshot.line_len(row));
-                let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
-
-                let mut line_end_bytes = snapshot
-                    .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
-                    .flatten()
-                    .copied();
-
-                let leading_space_len = if suffix_start_column > 0
-                    && line_end_bytes.next() == Some(b' ')
-                    && comment_suffix_has_leading_space
-                {
-                    1
-                } else {
-                    0
-                };
-
-                // If this line currently begins with the line comment prefix, then record
-                // the range containing the prefix.
-                if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
-                    let start = Point::new(end.row, suffix_start_column - leading_space_len);
-                    start..end
-                } else {
-                    end..end
-                }
-            }
-
-            // TODO: Handle selections that cross excerpts
-            for selection in &mut selections {
-                let start_column = snapshot.indent_size_for_line(selection.start.row).len;
-                let language = if let Some(language) =
-                    snapshot.language_scope_at(Point::new(selection.start.row, start_column))
-                {
-                    language
-                } else {
-                    continue;
-                };
-
-                selection_edit_ranges.clear();
-
-                // If multiple selections contain a given row, avoid processing that
-                // row more than once.
-                let mut start_row = selection.start.row;
-                if last_toggled_row == Some(start_row) {
-                    start_row += 1;
-                }
-                let end_row =
-                    if selection.end.row > selection.start.row && selection.end.column == 0 {
-                        selection.end.row - 1
-                    } else {
-                        selection.end.row
-                    };
-                last_toggled_row = Some(end_row);
-
-                if start_row > end_row {
-                    continue;
-                }
-
-                // If the language has line comments, toggle those.
-                if let Some(full_comment_prefix) = language.line_comment_prefix() {
-                    // Split the comment prefix's trailing whitespace into a separate string,
-                    // as that portion won't be used for detecting if a line is a comment.
-                    let comment_prefix = full_comment_prefix.trim_end_matches(' ');
-                    let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
-                    let mut all_selection_lines_are_comments = true;
-
-                    for row in start_row..=end_row {
-                        if snapshot.is_line_blank(row) && start_row < end_row {
-                            continue;
-                        }
-
-                        let prefix_range = comment_prefix_range(
-                            snapshot.deref(),
-                            row,
-                            comment_prefix,
-                            comment_prefix_whitespace,
-                        );
-                        if prefix_range.is_empty() {
-                            all_selection_lines_are_comments = false;
-                        }
-                        selection_edit_ranges.push(prefix_range);
-                    }
-
-                    if all_selection_lines_are_comments {
-                        edits.extend(
-                            selection_edit_ranges
-                                .iter()
-                                .cloned()
-                                .map(|range| (range, empty_str.clone())),
-                        );
-                    } else {
-                        let min_column = selection_edit_ranges
-                            .iter()
-                            .map(|r| r.start.column)
-                            .min()
-                            .unwrap_or(0);
-                        edits.extend(selection_edit_ranges.iter().map(|range| {
-                            let position = Point::new(range.start.row, min_column);
-                            (position..position, full_comment_prefix.clone())
-                        }));
-                    }
-                } else if let Some((full_comment_prefix, comment_suffix)) =
-                    language.block_comment_delimiters()
-                {
-                    let comment_prefix = full_comment_prefix.trim_end_matches(' ');
-                    let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
-                    let prefix_range = comment_prefix_range(
-                        snapshot.deref(),
-                        start_row,
-                        comment_prefix,
-                        comment_prefix_whitespace,
-                    );
-                    let suffix_range = comment_suffix_range(
-                        snapshot.deref(),
-                        end_row,
-                        comment_suffix.trim_start_matches(' '),
-                        comment_suffix.starts_with(' '),
-                    );
-
-                    if prefix_range.is_empty() || suffix_range.is_empty() {
-                        edits.push((
-                            prefix_range.start..prefix_range.start,
-                            full_comment_prefix.clone(),
-                        ));
-                        edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
-                        suffixes_inserted.push((end_row, comment_suffix.len()));
-                    } else {
-                        edits.push((prefix_range, empty_str.clone()));
-                        edits.push((suffix_range, empty_str.clone()));
-                    }
-                } else {
-                    continue;
-                }
-            }
-
-            drop(snapshot);
-            this.buffer.update(cx, |buffer, cx| {
-                buffer.edit(edits, None, cx);
-            });
-
-            // Adjust selections so that they end before any comment suffixes that
-            // were inserted.
-            let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
-            let mut selections = this.selections.all::<Point>(cx);
-            let snapshot = this.buffer.read(cx).read(cx);
-            for selection in &mut selections {
-                while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
-                    match row.cmp(&selection.end.row) {
-                        Ordering::Less => {
-                            suffixes_inserted.next();
-                            continue;
-                        }
-                        Ordering::Greater => break,
-                        Ordering::Equal => {
-                            if selection.end.column == snapshot.line_len(row) {
-                                if selection.is_empty() {
-                                    selection.start.column -= suffix_len as u32;
-                                }
-                                selection.end.column -= suffix_len as u32;
-                            }
-                            break;
-                        }
-                    }
-                }
-            }
-
-            drop(snapshot);
-            this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
-
-            let selections = this.selections.all::<Point>(cx);
-            let selections_on_single_row = selections.windows(2).all(|selections| {
-                selections[0].start.row == selections[1].start.row
-                    && selections[0].end.row == selections[1].end.row
-                    && selections[0].start.row == selections[0].end.row
-            });
-            let selections_selecting = selections
-                .iter()
-                .any(|selection| selection.start != selection.end);
-            let advance_downwards = action.advance_downwards
-                && selections_on_single_row
-                && !selections_selecting
-                && this.mode != EditorMode::SingleLine;
-
-            if advance_downwards {
-                let snapshot = this.buffer.read(cx).snapshot(cx);
-
-                this.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                    s.move_cursors_with(|display_snapshot, display_point, _| {
-                        let mut point = display_point.to_point(display_snapshot);
-                        point.row += 1;
-                        point = snapshot.clip_point(point, Bias::Left);
-                        let display_point = point.to_display_point(display_snapshot);
-                        let goal = SelectionGoal::HorizontalPosition(
-                            display_snapshot
-                                .x_for_display_point(display_point, &text_layout_details)
-                                .into(),
-                        );
-                        (display_point, goal)
-                    })
-                });
-            }
-        });
-    }
-
-    pub fn select_larger_syntax_node(
-        &mut self,
-        _: &SelectLargerSyntaxNode,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-        let buffer = self.buffer.read(cx).snapshot(cx);
-        let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
-
-        let mut stack = mem::take(&mut self.select_larger_syntax_node_stack);
-        let mut selected_larger_node = false;
-        let new_selections = old_selections
-            .iter()
-            .map(|selection| {
-                let old_range = selection.start..selection.end;
-                let mut new_range = old_range.clone();
-                while let Some(containing_range) =
-                    buffer.range_for_syntax_ancestor(new_range.clone())
-                {
-                    new_range = containing_range;
-                    if !display_map.intersects_fold(new_range.start)
-                        && !display_map.intersects_fold(new_range.end)
-                    {
-                        break;
-                    }
-                }
-
-                selected_larger_node |= new_range != old_range;
-                Selection {
-                    id: selection.id,
-                    start: new_range.start,
-                    end: new_range.end,
-                    goal: SelectionGoal::None,
-                    reversed: selection.reversed,
-                }
-            })
-            .collect::<Vec<_>>();
-
-        if selected_larger_node {
-            stack.push(old_selections);
-            self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                s.select(new_selections);
-            });
-        }
-        self.select_larger_syntax_node_stack = stack;
-    }
-
-    pub fn select_smaller_syntax_node(
-        &mut self,
-        _: &SelectSmallerSyntaxNode,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let mut stack = mem::take(&mut self.select_larger_syntax_node_stack);
-        if let Some(selections) = stack.pop() {
-            self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                s.select(selections.to_vec());
-            });
-        }
-        self.select_larger_syntax_node_stack = stack;
-    }
-
-    pub fn move_to_enclosing_bracket(
-        &mut self,
-        _: &MoveToEnclosingBracket,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.move_offsets_with(|snapshot, selection| {
-                let Some(enclosing_bracket_ranges) =
-                    snapshot.enclosing_bracket_ranges(selection.start..selection.end)
-                else {
-                    return;
-                };
-
-                let mut best_length = usize::MAX;
-                let mut best_inside = false;
-                let mut best_in_bracket_range = false;
-                let mut best_destination = None;
-                for (open, close) in enclosing_bracket_ranges {
-                    let close = close.to_inclusive();
-                    let length = close.end() - open.start;
-                    let inside = selection.start >= open.end && selection.end <= *close.start();
-                    let in_bracket_range = open.to_inclusive().contains(&selection.head())
-                        || close.contains(&selection.head());
-
-                    // If best is next to a bracket and current isn't, skip
-                    if !in_bracket_range && best_in_bracket_range {
-                        continue;
-                    }
-
-                    // Prefer smaller lengths unless best is inside and current isn't
-                    if length > best_length && (best_inside || !inside) {
-                        continue;
-                    }
-
-                    best_length = length;
-                    best_inside = inside;
-                    best_in_bracket_range = in_bracket_range;
-                    best_destination = Some(
-                        if close.contains(&selection.start) && close.contains(&selection.end) {
-                            if inside {
-                                open.end
-                            } else {
-                                open.start
-                            }
-                        } else {
-                            if inside {
-                                *close.start()
-                            } else {
-                                *close.end()
-                            }
-                        },
-                    );
-                }
-
-                if let Some(destination) = best_destination {
-                    selection.collapse_to(destination, SelectionGoal::None);
-                }
-            })
-        });
-    }
-
-    pub fn undo_selection(&mut self, _: &UndoSelection, cx: &mut ViewContext<Self>) {
-        self.end_selection(cx);
-        self.selection_history.mode = SelectionHistoryMode::Undoing;
-        if let Some(entry) = self.selection_history.undo_stack.pop_back() {
-            self.change_selections(None, cx, |s| s.select_anchors(entry.selections.to_vec()));
-            self.select_next_state = entry.select_next_state;
-            self.select_prev_state = entry.select_prev_state;
-            self.add_selections_state = entry.add_selections_state;
-            self.request_autoscroll(Autoscroll::newest(), cx);
-        }
-        self.selection_history.mode = SelectionHistoryMode::Normal;
-    }
-
-    pub fn redo_selection(&mut self, _: &RedoSelection, cx: &mut ViewContext<Self>) {
-        self.end_selection(cx);
-        self.selection_history.mode = SelectionHistoryMode::Redoing;
-        if let Some(entry) = self.selection_history.redo_stack.pop_back() {
-            self.change_selections(None, cx, |s| s.select_anchors(entry.selections.to_vec()));
-            self.select_next_state = entry.select_next_state;
-            self.select_prev_state = entry.select_prev_state;
-            self.add_selections_state = entry.add_selections_state;
-            self.request_autoscroll(Autoscroll::newest(), cx);
-        }
-        self.selection_history.mode = SelectionHistoryMode::Normal;
-    }
-
-    fn go_to_diagnostic(&mut self, _: &GoToDiagnostic, cx: &mut ViewContext<Self>) {
-        self.go_to_diagnostic_impl(Direction::Next, cx)
-    }
-
-    fn go_to_prev_diagnostic(&mut self, _: &GoToPrevDiagnostic, cx: &mut ViewContext<Self>) {
-        self.go_to_diagnostic_impl(Direction::Prev, cx)
-    }
-
-    pub fn go_to_diagnostic_impl(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
-        let buffer = self.buffer.read(cx).snapshot(cx);
-        let selection = self.selections.newest::<usize>(cx);
-
-        // If there is an active Diagnostic Popover. Jump to it's diagnostic instead.
-        if direction == Direction::Next {
-            if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() {
-                let (group_id, jump_to) = popover.activation_info();
-                if self.activate_diagnostics(group_id, cx) {
-                    self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                        let mut new_selection = s.newest_anchor().clone();
-                        new_selection.collapse_to(jump_to, SelectionGoal::None);
-                        s.select_anchors(vec![new_selection.clone()]);
-                    });
-                }
-                return;
-            }
-        }
-
-        let mut active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
-            active_diagnostics
-                .primary_range
-                .to_offset(&buffer)
-                .to_inclusive()
-        });
-        let mut search_start = if let Some(active_primary_range) = active_primary_range.as_ref() {
-            if active_primary_range.contains(&selection.head()) {
-                *active_primary_range.end()
-            } else {
-                selection.head()
-            }
-        } else {
-            selection.head()
-        };
-
-        loop {
-            let mut diagnostics = if direction == Direction::Prev {
-                buffer.diagnostics_in_range::<_, usize>(0..search_start, true)
-            } else {
-                buffer.diagnostics_in_range::<_, usize>(search_start..buffer.len(), false)
-            };
-            let group = diagnostics.find_map(|entry| {
-                if entry.diagnostic.is_primary
-                    && entry.diagnostic.severity <= DiagnosticSeverity::WARNING
-                    && !entry.range.is_empty()
-                    && Some(entry.range.end) != active_primary_range.as_ref().map(|r| *r.end())
-                    && !entry.range.contains(&search_start)
-                {
-                    Some((entry.range, entry.diagnostic.group_id))
-                } else {
-                    None
-                }
-            });
-
-            if let Some((primary_range, group_id)) = group {
-                if self.activate_diagnostics(group_id, cx) {
-                    self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                        s.select(vec![Selection {
-                            id: selection.id,
-                            start: primary_range.start,
-                            end: primary_range.start,
-                            reversed: false,
-                            goal: SelectionGoal::None,
-                        }]);
-                    });
-                }
-                break;
-            } else {
-                // Cycle around to the start of the buffer, potentially moving back to the start of
-                // the currently active diagnostic.
-                active_primary_range.take();
-                if direction == Direction::Prev {
-                    if search_start == buffer.len() {
-                        break;
-                    } else {
-                        search_start = buffer.len();
-                    }
-                } else if search_start == 0 {
-                    break;
-                } else {
-                    search_start = 0;
-                }
-            }
-        }
-    }
-
-    fn go_to_hunk(&mut self, _: &GoToHunk, cx: &mut ViewContext<Self>) {
-        let snapshot = self
-            .display_map
-            .update(cx, |display_map, cx| display_map.snapshot(cx));
-        let selection = self.selections.newest::<Point>(cx);
-
-        if !self.seek_in_direction(
-            &snapshot,
-            selection.head(),
-            false,
-            snapshot
-                .buffer_snapshot
-                .git_diff_hunks_in_range((selection.head().row + 1)..u32::MAX),
-            cx,
-        ) {
-            let wrapped_point = Point::zero();
-            self.seek_in_direction(
-                &snapshot,
-                wrapped_point,
-                true,
-                snapshot
-                    .buffer_snapshot
-                    .git_diff_hunks_in_range((wrapped_point.row + 1)..u32::MAX),
-                cx,
-            );
-        }
-    }
-
-    fn go_to_prev_hunk(&mut self, _: &GoToPrevHunk, cx: &mut ViewContext<Self>) {
-        let snapshot = self
-            .display_map
-            .update(cx, |display_map, cx| display_map.snapshot(cx));
-        let selection = self.selections.newest::<Point>(cx);
-
-        if !self.seek_in_direction(
-            &snapshot,
-            selection.head(),
-            false,
-            snapshot
-                .buffer_snapshot
-                .git_diff_hunks_in_range_rev(0..selection.head().row),
-            cx,
-        ) {
-            let wrapped_point = snapshot.buffer_snapshot.max_point();
-            self.seek_in_direction(
-                &snapshot,
-                wrapped_point,
-                true,
-                snapshot
-                    .buffer_snapshot
-                    .git_diff_hunks_in_range_rev(0..wrapped_point.row),
-                cx,
-            );
-        }
-    }
-
-    fn seek_in_direction(
-        &mut self,
-        snapshot: &DisplaySnapshot,
-        initial_point: Point,
-        is_wrapped: bool,
-        hunks: impl Iterator<Item = DiffHunk<u32>>,
-        cx: &mut ViewContext<Editor>,
-    ) -> bool {
-        let display_point = initial_point.to_display_point(snapshot);
-        let mut hunks = hunks
-            .map(|hunk| diff_hunk_to_display(hunk, &snapshot))
-            .filter(|hunk| {
-                if is_wrapped {
-                    true
-                } else {
-                    !hunk.contains_display_row(display_point.row())
-                }
-            })
-            .dedup();
-
-        if let Some(hunk) = hunks.next() {
-            self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                let row = hunk.start_display_row();
-                let point = DisplayPoint::new(row, 0);
-                s.select_display_ranges([point..point]);
-            });
-
-            true
-        } else {
-            false
-        }
-    }
-
-    pub fn go_to_definition(&mut self, _: &GoToDefinition, cx: &mut ViewContext<Self>) {
-        self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, cx);
-    }
-
-    pub fn go_to_type_definition(&mut self, _: &GoToTypeDefinition, cx: &mut ViewContext<Self>) {
-        self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, cx);
-    }
-
-    pub fn go_to_definition_split(&mut self, _: &GoToDefinitionSplit, cx: &mut ViewContext<Self>) {
-        self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, cx);
-    }
-
-    pub fn go_to_type_definition_split(
-        &mut self,
-        _: &GoToTypeDefinitionSplit,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, cx);
-    }
-
-    fn go_to_definition_of_kind(
-        &mut self,
-        kind: GotoDefinitionKind,
-        split: bool,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let Some(workspace) = self.workspace() else {
-            return;
-        };
-        let buffer = self.buffer.read(cx);
-        let head = self.selections.newest::<usize>(cx).head();
-        let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
-            text_anchor
-        } else {
-            return;
-        };
-
-        let project = workspace.read(cx).project().clone();
-        let definitions = project.update(cx, |project, cx| match kind {
-            GotoDefinitionKind::Symbol => project.definition(&buffer, head, cx),
-            GotoDefinitionKind::Type => project.type_definition(&buffer, head, cx),
-        });
-
-        cx.spawn(|editor, mut cx| async move {
-            let definitions = definitions.await?;
-            editor.update(&mut cx, |editor, cx| {
-                editor.navigate_to_definitions(
-                    definitions
-                        .into_iter()
-                        .map(GoToDefinitionLink::Text)
-                        .collect(),
-                    split,
-                    cx,
-                );
-            })?;
-            Ok::<(), anyhow::Error>(())
-        })
-        .detach_and_log_err(cx);
-    }
-
-    pub fn navigate_to_definitions(
-        &mut self,
-        mut definitions: Vec<GoToDefinitionLink>,
-        split: bool,
-        cx: &mut ViewContext<Editor>,
-    ) {
-        let Some(workspace) = self.workspace() else {
-            return;
-        };
-        let pane = workspace.read(cx).active_pane().clone();
-        // If there is one definition, just open it directly
-        if definitions.len() == 1 {
-            let definition = definitions.pop().unwrap();
-            let target_task = match definition {
-                GoToDefinitionLink::Text(link) => Task::Ready(Some(Ok(Some(link.target)))),
-                GoToDefinitionLink::InlayHint(lsp_location, server_id) => {
-                    self.compute_target_location(lsp_location, server_id, cx)
-                }
-            };
-            cx.spawn(|editor, mut cx| async move {
-                let target = target_task.await.context("target resolution task")?;
-                if let Some(target) = target {
-                    editor.update(&mut cx, |editor, cx| {
-                        let range = target.range.to_offset(target.buffer.read(cx));
-                        let range = editor.range_for_match(&range);
-                        if Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
-                            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                                s.select_ranges([range]);
-                            });
-                        } else {
-                            cx.window_context().defer(move |cx| {
-                                let target_editor: View<Self> =
-                                    workspace.update(cx, |workspace, cx| {
-                                        if split {
-                                            workspace.split_project_item(target.buffer.clone(), cx)
-                                        } else {
-                                            workspace.open_project_item(target.buffer.clone(), cx)
-                                        }
-                                    });
-                                target_editor.update(cx, |target_editor, cx| {
-                                    // When selecting a definition in a different buffer, disable the nav history
-                                    // to avoid creating a history entry at the previous cursor location.
-                                    pane.update(cx, |pane, _| pane.disable_history());
-                                    target_editor.change_selections(
-                                        Some(Autoscroll::fit()),
-                                        cx,
-                                        |s| {
-                                            s.select_ranges([range]);
-                                        },
-                                    );
-                                    pane.update(cx, |pane, _| pane.enable_history());
-                                });
-                            });
-                        }
-                    })
-                } else {
-                    Ok(())
-                }
-            })
-            .detach_and_log_err(cx);
-        } else if !definitions.is_empty() {
-            let replica_id = self.replica_id(cx);
-            cx.spawn(|editor, mut cx| async move {
-                let (title, location_tasks) = editor
-                    .update(&mut cx, |editor, cx| {
-                        let title = definitions
-                            .iter()
-                            .find_map(|definition| match definition {
-                                GoToDefinitionLink::Text(link) => {
-                                    link.origin.as_ref().map(|origin| {
-                                        let buffer = origin.buffer.read(cx);
-                                        format!(
-                                            "Definitions for {}",
-                                            buffer
-                                                .text_for_range(origin.range.clone())
-                                                .collect::<String>()
-                                        )
-                                    })
-                                }
-                                GoToDefinitionLink::InlayHint(_, _) => None,
-                            })
-                            .unwrap_or("Definitions".to_string());
-                        let location_tasks = definitions
-                            .into_iter()
-                            .map(|definition| match definition {
-                                GoToDefinitionLink::Text(link) => {
-                                    Task::Ready(Some(Ok(Some(link.target))))
-                                }
-                                GoToDefinitionLink::InlayHint(lsp_location, server_id) => {
-                                    editor.compute_target_location(lsp_location, server_id, cx)
-                                }
-                            })
-                            .collect::<Vec<_>>();
-                        (title, location_tasks)
-                    })
-                    .context("location tasks preparation")?;
-
-                let locations = futures::future::join_all(location_tasks)
-                    .await
-                    .into_iter()
-                    .filter_map(|location| location.transpose())
-                    .collect::<Result<_>>()
-                    .context("location tasks")?;
-                workspace
-                    .update(&mut cx, |workspace, cx| {
-                        Self::open_locations_in_multibuffer(
-                            workspace, locations, replica_id, title, split, cx,
-                        )
-                    })
-                    .ok();
-
-                anyhow::Ok(())
-            })
-            .detach_and_log_err(cx);
-        }
-    }
-
-    fn compute_target_location(
-        &self,
-        lsp_location: lsp::Location,
-        server_id: LanguageServerId,
-        cx: &mut ViewContext<Editor>,
-    ) -> Task<anyhow::Result<Option<Location>>> {
-        let Some(project) = self.project.clone() else {
-            return Task::Ready(Some(Ok(None)));
-        };
-
-        cx.spawn(move |editor, mut cx| async move {
-            let location_task = editor.update(&mut cx, |editor, cx| {
-                project.update(cx, |project, cx| {
-                    let language_server_name =
-                        editor.buffer.read(cx).as_singleton().and_then(|buffer| {
-                            project
-                                .language_server_for_buffer(buffer.read(cx), server_id, cx)
-                                .map(|(_, lsp_adapter)| {
-                                    LanguageServerName(Arc::from(lsp_adapter.name()))
-                                })
-                        });
-                    language_server_name.map(|language_server_name| {
-                        project.open_local_buffer_via_lsp(
-                            lsp_location.uri.clone(),
-                            server_id,
-                            language_server_name,
-                            cx,
-                        )
-                    })
-                })
-            })?;
-            let location = match location_task {
-                Some(task) => Some({
-                    let target_buffer_handle = task.await.context("open local buffer")?;
-                    let range = target_buffer_handle.update(&mut cx, |target_buffer, _| {
-                        let target_start = target_buffer
-                            .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
-                        let target_end = target_buffer
-                            .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
-                        target_buffer.anchor_after(target_start)
-                            ..target_buffer.anchor_before(target_end)
-                    })?;
-                    Location {
-                        buffer: target_buffer_handle,
-                        range,
-                    }
-                }),
-                None => None,
-            };
-            Ok(location)
-        })
-    }
-
-    pub fn find_all_references(
-        &mut self,
-        _: &FindAllReferences,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<Task<Result<()>>> {
-        let buffer = self.buffer.read(cx);
-        let head = self.selections.newest::<usize>(cx).head();
-        let (buffer, head) = buffer.text_anchor_for_position(head, cx)?;
-        let replica_id = self.replica_id(cx);
-
-        let workspace = self.workspace()?;
-        let project = workspace.read(cx).project().clone();
-        let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
-        Some(cx.spawn(|_, mut cx| async move {
-            let locations = references.await?;
-            if locations.is_empty() {
-                return Ok(());
-            }
-
-            workspace.update(&mut cx, |workspace, cx| {
-                let title = locations
-                    .first()
-                    .as_ref()
-                    .map(|location| {
-                        let buffer = location.buffer.read(cx);
-                        format!(
-                            "References to `{}`",
-                            buffer
-                                .text_for_range(location.range.clone())
-                                .collect::<String>()
-                        )
-                    })
-                    .unwrap();
-                Self::open_locations_in_multibuffer(
-                    workspace, locations, replica_id, title, false, cx,
-                );
-            })?;
-
-            Ok(())
-        }))
-    }
-
-    /// Opens a multibuffer with the given project locations in it
-    pub fn open_locations_in_multibuffer(
-        workspace: &mut Workspace,
-        mut locations: Vec<Location>,
-        replica_id: ReplicaId,
-        title: String,
-        split: bool,
-        cx: &mut ViewContext<Workspace>,
-    ) {
-        // If there are multiple definitions, open them in a multibuffer
-        locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
-        let mut locations = locations.into_iter().peekable();
-        let mut ranges_to_highlight = Vec::new();
-
-        let excerpt_buffer = cx.new_model(|cx| {
-            let mut multibuffer = MultiBuffer::new(replica_id);
-            while let Some(location) = locations.next() {
-                let buffer = location.buffer.read(cx);
-                let mut ranges_for_buffer = Vec::new();
-                let range = location.range.to_offset(buffer);
-                ranges_for_buffer.push(range.clone());
-
-                while let Some(next_location) = locations.peek() {
-                    if next_location.buffer == location.buffer {
-                        ranges_for_buffer.push(next_location.range.to_offset(buffer));
-                        locations.next();
-                    } else {
-                        break;
-                    }
-                }
-
-                ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
-                ranges_to_highlight.extend(multibuffer.push_excerpts_with_context_lines(
-                    location.buffer.clone(),
-                    ranges_for_buffer,
-                    1,
-                    cx,
-                ))
-            }
-
-            multibuffer.with_title(title)
-        });
-
-        let editor = cx.new_view(|cx| {
-            Editor::for_multibuffer(excerpt_buffer, Some(workspace.project().clone()), cx)
-        });
-        editor.update(cx, |editor, cx| {
-            editor.highlight_background::<Self>(
-                ranges_to_highlight,
-                |theme| theme.editor_highlighted_line_background,
-                cx,
-            );
-        });
-        if split {
-            workspace.split_item(SplitDirection::Right, Box::new(editor), cx);
-        } else {
-            workspace.add_item(Box::new(editor), cx);
-        }
-    }
-
-    pub fn rename(&mut self, _: &Rename, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
-        use language::ToOffset as _;
-
-        let project = self.project.clone()?;
-        let selection = self.selections.newest_anchor().clone();
-        let (cursor_buffer, cursor_buffer_position) = self
-            .buffer
-            .read(cx)
-            .text_anchor_for_position(selection.head(), cx)?;
-        let (tail_buffer, _) = self
-            .buffer
-            .read(cx)
-            .text_anchor_for_position(selection.tail(), cx)?;
-        if tail_buffer != cursor_buffer {
-            return None;
-        }
-
-        let snapshot = cursor_buffer.read(cx).snapshot();
-        let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
-        let prepare_rename = project.update(cx, |project, cx| {
-            project.prepare_rename(cursor_buffer, cursor_buffer_offset, cx)
-        });
-
-        Some(cx.spawn(|this, mut cx| async move {
-            let rename_range = if let Some(range) = prepare_rename.await? {
-                Some(range)
-            } else {
-                this.update(&mut cx, |this, cx| {
-                    let buffer = this.buffer.read(cx).snapshot(cx);
-                    let mut buffer_highlights = this
-                        .document_highlights_for_position(selection.head(), &buffer)
-                        .filter(|highlight| {
-                            highlight.start.excerpt_id == selection.head().excerpt_id
-                                && highlight.end.excerpt_id == selection.head().excerpt_id
-                        });
-                    buffer_highlights
-                        .next()
-                        .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
-                })?
-            };
-            if let Some(rename_range) = rename_range {
-                let rename_buffer_range = rename_range.to_offset(&snapshot);
-                let cursor_offset_in_rename_range =
-                    cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
-
-                this.update(&mut cx, |this, cx| {
-                    this.take_rename(false, cx);
-                    let buffer = this.buffer.read(cx).read(cx);
-                    let cursor_offset = selection.head().to_offset(&buffer);
-                    let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
-                    let rename_end = rename_start + rename_buffer_range.len();
-                    let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
-                    let mut old_highlight_id = None;
-                    let old_name: Arc<str> = buffer
-                        .chunks(rename_start..rename_end, true)
-                        .map(|chunk| {
-                            if old_highlight_id.is_none() {
-                                old_highlight_id = chunk.syntax_highlight_id;
-                            }
-                            chunk.text
-                        })
-                        .collect::<String>()
-                        .into();
-
-                    drop(buffer);
-
-                    // Position the selection in the rename editor so that it matches the current selection.
-                    this.show_local_selections = false;
-                    let rename_editor = cx.new_view(|cx| {
-                        let mut editor = Editor::single_line(cx);
-                        editor.buffer.update(cx, |buffer, cx| {
-                            buffer.edit([(0..0, old_name.clone())], None, cx)
-                        });
-                        editor.select_all(&SelectAll, cx);
-                        editor
-                    });
-
-                    let ranges = this
-                        .clear_background_highlights::<DocumentHighlightWrite>(cx)
-                        .into_iter()
-                        .flat_map(|(_, ranges)| ranges.into_iter())
-                        .chain(
-                            this.clear_background_highlights::<DocumentHighlightRead>(cx)
-                                .into_iter()
-                                .flat_map(|(_, ranges)| ranges.into_iter()),
-                        )
-                        .collect();
-
-                    this.highlight_text::<Rename>(
-                        ranges,
-                        HighlightStyle {
-                            fade_out: Some(0.6),
-                            ..Default::default()
-                        },
-                        cx,
-                    );
-                    let rename_focus_handle = rename_editor.focus_handle(cx);
-                    cx.focus(&rename_focus_handle);
-                    let block_id = this.insert_blocks(
-                        [BlockProperties {
-                            style: BlockStyle::Flex,
-                            position: range.start.clone(),
-                            height: 1,
-                            render: Arc::new({
-                                let rename_editor = rename_editor.clone();
-                                move |cx: &mut BlockContext| {
-                                    let mut text_style = cx.editor_style.text.clone();
-                                    if let Some(highlight_style) = old_highlight_id
-                                        .and_then(|h| h.style(&cx.editor_style.syntax))
-                                    {
-                                        text_style = text_style.highlight(highlight_style);
-                                    }
-                                    div()
-                                        .pl(cx.anchor_x)
-                                        .child(EditorElement::new(
-                                            &rename_editor,
-                                            EditorStyle {
-                                                background: cx.theme().system().transparent,
-                                                local_player: cx.editor_style.local_player,
-                                                text: text_style,
-                                                scrollbar_width: cx.editor_style.scrollbar_width,
-                                                syntax: cx.editor_style.syntax.clone(),
-                                                status: cx.editor_style.status.clone(),
-                                                // todo!("what about the rest of the highlight style parts for inlays and suggestions?")
-                                                inlays_style: HighlightStyle {
-                                                    color: Some(cx.theme().status().hint),
-                                                    font_weight: Some(FontWeight::BOLD),
-                                                    ..HighlightStyle::default()
-                                                },
-                                                suggestions_style: HighlightStyle {
-                                                    color: Some(cx.theme().status().predictive),
-                                                    ..HighlightStyle::default()
-                                                },
-                                            },
-                                        ))
-                                        .into_any_element()
-                                }
-                            }),
-                            disposition: BlockDisposition::Below,
-                        }],
-                        Some(Autoscroll::fit()),
-                        cx,
-                    )[0];
-                    this.pending_rename = Some(RenameState {
-                        range,
-                        old_name,
-                        editor: rename_editor,
-                        block_id,
-                    });
-                })?;
-            }
-
-            Ok(())
-        }))
-    }
-
-    pub fn confirm_rename(
-        &mut self,
-        _: &ConfirmRename,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<Task<Result<()>>> {
-        let rename = self.take_rename(false, cx)?;
-        let workspace = self.workspace()?;
-        let (start_buffer, start) = self
-            .buffer
-            .read(cx)
-            .text_anchor_for_position(rename.range.start.clone(), cx)?;
-        let (end_buffer, end) = self
-            .buffer
-            .read(cx)
-            .text_anchor_for_position(rename.range.end.clone(), cx)?;
-        if start_buffer != end_buffer {
-            return None;
-        }
-
-        let buffer = start_buffer;
-        let range = start..end;
-        let old_name = rename.old_name;
-        let new_name = rename.editor.read(cx).text(cx);
-
-        let rename = workspace
-            .read(cx)
-            .project()
-            .clone()
-            .update(cx, |project, cx| {
-                project.perform_rename(buffer.clone(), range.start, new_name.clone(), true, cx)
-            });
-        let workspace = workspace.downgrade();
-
-        Some(cx.spawn(|editor, mut cx| async move {
-            let project_transaction = rename.await?;
-            Self::open_project_transaction(
-                &editor,
-                workspace,
-                project_transaction,
-                format!("Rename: {} → {}", old_name, new_name),
-                cx.clone(),
-            )
-            .await?;
-
-            editor.update(&mut cx, |editor, cx| {
-                editor.refresh_document_highlights(cx);
-            })?;
-            Ok(())
-        }))
-    }
-
-    fn take_rename(
-        &mut self,
-        moving_cursor: bool,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<RenameState> {
-        let rename = self.pending_rename.take()?;
-        if rename.editor.focus_handle(cx).is_focused(cx) {
-            cx.focus(&self.focus_handle);
-        }
-
-        self.remove_blocks(
-            [rename.block_id].into_iter().collect(),
-            Some(Autoscroll::fit()),
-            cx,
-        );
-        self.clear_highlights::<Rename>(cx);
-        self.show_local_selections = true;
-
-        if moving_cursor {
-            let rename_editor = rename.editor.read(cx);
-            let cursor_in_rename_editor = rename_editor.selections.newest::<usize>(cx).head();
-
-            // Update the selection to match the position of the selection inside
-            // the rename editor.
-            let snapshot = self.buffer.read(cx).read(cx);
-            let rename_range = rename.range.to_offset(&snapshot);
-            let cursor_in_editor = snapshot
-                .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
-                .min(rename_range.end);
-            drop(snapshot);
-
-            self.change_selections(None, cx, |s| {
-                s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
-            });
-        } else {
-            self.refresh_document_highlights(cx);
-        }
-
-        Some(rename)
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn pending_rename(&self) -> Option<&RenameState> {
-        self.pending_rename.as_ref()
-    }
-
-    fn format(&mut self, _: &Format, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
-        let project = match &self.project {
-            Some(project) => project.clone(),
-            None => return None,
-        };
-
-        Some(self.perform_format(project, FormatTrigger::Manual, cx))
-    }
-
-    fn perform_format(
-        &mut self,
-        project: Model<Project>,
-        trigger: FormatTrigger,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<Result<()>> {
-        let buffer = self.buffer().clone();
-        let buffers = buffer.read(cx).all_buffers();
-
-        let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
-        let format = project.update(cx, |project, cx| project.format(buffers, true, trigger, cx));
-
-        cx.spawn(|_, mut cx| async move {
-            let transaction = futures::select_biased! {
-                _ = timeout => {
-                    log::warn!("timed out waiting for formatting");
-                    None
-                }
-                transaction = format.log_err().fuse() => transaction,
-            };
-
-            buffer
-                .update(&mut cx, |buffer, cx| {
-                    if let Some(transaction) = transaction {
-                        if !buffer.is_singleton() {
-                            buffer.push_transaction(&transaction.0, cx);
-                        }
-                    }
-
-                    cx.notify();
-                })
-                .ok();
-
-            Ok(())
-        })
-    }
-
-    fn restart_language_server(&mut self, _: &RestartLanguageServer, cx: &mut ViewContext<Self>) {
-        if let Some(project) = self.project.clone() {
-            self.buffer.update(cx, |multi_buffer, cx| {
-                project.update(cx, |project, cx| {
-                    project.restart_language_servers_for_buffers(multi_buffer.all_buffers(), cx);
-                });
-            })
-        }
-    }
-
-    fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
-        cx.show_character_palette();
-    }
-
-    fn refresh_active_diagnostics(&mut self, cx: &mut ViewContext<Editor>) {
-        if let Some(active_diagnostics) = self.active_diagnostics.as_mut() {
-            let buffer = self.buffer.read(cx).snapshot(cx);
-            let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer);
-            let is_valid = buffer
-                .diagnostics_in_range::<_, usize>(active_diagnostics.primary_range.clone(), false)
-                .any(|entry| {
-                    entry.diagnostic.is_primary
-                        && !entry.range.is_empty()
-                        && entry.range.start == primary_range_start
-                        && entry.diagnostic.message == active_diagnostics.primary_message
-                });
-
-            if is_valid != active_diagnostics.is_valid {
-                active_diagnostics.is_valid = is_valid;
-                let mut new_styles = HashMap::default();
-                for (block_id, diagnostic) in &active_diagnostics.blocks {
-                    new_styles.insert(
-                        *block_id,
-                        diagnostic_block_renderer(diagnostic.clone(), is_valid),
-                    );
-                }
-                self.display_map
-                    .update(cx, |display_map, _| display_map.replace_blocks(new_styles));
-            }
-        }
-    }
-
-    fn activate_diagnostics(&mut self, group_id: usize, cx: &mut ViewContext<Self>) -> bool {
-        self.dismiss_diagnostics(cx);
-        self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
-            let buffer = self.buffer.read(cx).snapshot(cx);
-
-            let mut primary_range = None;
-            let mut primary_message = None;
-            let mut group_end = Point::zero();
-            let diagnostic_group = buffer
-                .diagnostic_group::<Point>(group_id)
-                .map(|entry| {
-                    if entry.range.end > group_end {
-                        group_end = entry.range.end;
-                    }
-                    if entry.diagnostic.is_primary {
-                        primary_range = Some(entry.range.clone());
-                        primary_message = Some(entry.diagnostic.message.clone());
-                    }
-                    entry
-                })
-                .collect::<Vec<_>>();
-            let primary_range = primary_range?;
-            let primary_message = primary_message?;
-            let primary_range =
-                buffer.anchor_after(primary_range.start)..buffer.anchor_before(primary_range.end);
-
-            let blocks = display_map
-                .insert_blocks(
-                    diagnostic_group.iter().map(|entry| {
-                        let diagnostic = entry.diagnostic.clone();
-                        let message_height = diagnostic.message.lines().count() as u8;
-                        BlockProperties {
-                            style: BlockStyle::Fixed,
-                            position: buffer.anchor_after(entry.range.start),
-                            height: message_height,
-                            render: diagnostic_block_renderer(diagnostic, true),
-                            disposition: BlockDisposition::Below,
-                        }
-                    }),
-                    cx,
-                )
-                .into_iter()
-                .zip(diagnostic_group.into_iter().map(|entry| entry.diagnostic))
-                .collect();
-
-            Some(ActiveDiagnosticGroup {
-                primary_range,
-                primary_message,
-                blocks,
-                is_valid: true,
-            })
-        });
-        self.active_diagnostics.is_some()
-    }
-
-    fn dismiss_diagnostics(&mut self, cx: &mut ViewContext<Self>) {
-        if let Some(active_diagnostic_group) = self.active_diagnostics.take() {
-            self.display_map.update(cx, |display_map, cx| {
-                display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx);
-            });
-            cx.notify();
-        }
-    }
-
-    pub fn set_selections_from_remote(
-        &mut self,
-        selections: Vec<Selection<Anchor>>,
-        pending_selection: Option<Selection<Anchor>>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let old_cursor_position = self.selections.newest_anchor().head();
-        self.selections.change_with(cx, |s| {
-            s.select_anchors(selections);
-            if let Some(pending_selection) = pending_selection {
-                s.set_pending(pending_selection, SelectMode::Character);
-            } else {
-                s.clear_pending();
-            }
-        });
-        self.selections_did_change(false, &old_cursor_position, cx);
-    }
-
-    fn push_to_selection_history(&mut self) {
-        self.selection_history.push(SelectionHistoryEntry {
-            selections: self.selections.disjoint_anchors(),
-            select_next_state: self.select_next_state.clone(),
-            select_prev_state: self.select_prev_state.clone(),
-            add_selections_state: self.add_selections_state.clone(),
-        });
-    }
-
-    pub fn transact(
-        &mut self,
-        cx: &mut ViewContext<Self>,
-        update: impl FnOnce(&mut Self, &mut ViewContext<Self>),
-    ) -> Option<TransactionId> {
-        self.start_transaction_at(Instant::now(), cx);
-        update(self, cx);
-        self.end_transaction_at(Instant::now(), cx)
-    }
-
-    fn start_transaction_at(&mut self, now: Instant, cx: &mut ViewContext<Self>) {
-        self.end_selection(cx);
-        if let Some(tx_id) = self
-            .buffer
-            .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
-        {
-            self.selection_history
-                .insert_transaction(tx_id, self.selections.disjoint_anchors());
-        }
-    }
-
-    fn end_transaction_at(
-        &mut self,
-        now: Instant,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<TransactionId> {
-        if let Some(tx_id) = self
-            .buffer
-            .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
-        {
-            if let Some((_, end_selections)) = self.selection_history.transaction_mut(tx_id) {
-                *end_selections = Some(self.selections.disjoint_anchors());
-            } else {
-                log::error!("unexpectedly ended a transaction that wasn't started by this editor");
-            }
-
-            cx.emit(EditorEvent::Edited);
-            Some(tx_id)
-        } else {
-            None
-        }
-    }
-
-    pub fn fold(&mut self, _: &Fold, cx: &mut ViewContext<Self>) {
-        let mut fold_ranges = Vec::new();
-
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-
-        let selections = self.selections.all_adjusted(cx);
-        for selection in selections {
-            let range = selection.range().sorted();
-            let buffer_start_row = range.start.row;
-
-            for row in (0..=range.end.row).rev() {
-                let fold_range = display_map.foldable_range(row);
-
-                if let Some(fold_range) = fold_range {
-                    if fold_range.end.row >= buffer_start_row {
-                        fold_ranges.push(fold_range);
-                        if row <= range.start.row {
-                            break;
-                        }
-                    }
-                }
-            }
-        }
-
-        self.fold_ranges(fold_ranges, true, cx);
-    }
-
-    pub fn fold_at(&mut self, fold_at: &FoldAt, cx: &mut ViewContext<Self>) {
-        let buffer_row = fold_at.buffer_row;
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-
-        if let Some(fold_range) = display_map.foldable_range(buffer_row) {
-            let autoscroll = self
-                .selections
-                .all::<Point>(cx)
-                .iter()
-                .any(|selection| fold_range.overlaps(&selection.range()));
-
-            self.fold_ranges(std::iter::once(fold_range), autoscroll, cx);
-        }
-    }
-
-    pub fn unfold_lines(&mut self, _: &UnfoldLines, cx: &mut ViewContext<Self>) {
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-        let buffer = &display_map.buffer_snapshot;
-        let selections = self.selections.all::<Point>(cx);
-        let ranges = selections
-            .iter()
-            .map(|s| {
-                let range = s.display_range(&display_map).sorted();
-                let mut start = range.start.to_point(&display_map);
-                let mut end = range.end.to_point(&display_map);
-                start.column = 0;
-                end.column = buffer.line_len(end.row);
-                start..end
-            })
-            .collect::<Vec<_>>();
-
-        self.unfold_ranges(ranges, true, true, cx);
-    }
-
-    pub fn unfold_at(&mut self, unfold_at: &UnfoldAt, cx: &mut ViewContext<Self>) {
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-
-        let intersection_range = Point::new(unfold_at.buffer_row, 0)
-            ..Point::new(
-                unfold_at.buffer_row,
-                display_map.buffer_snapshot.line_len(unfold_at.buffer_row),
-            );
-
-        let autoscroll = self
-            .selections
-            .all::<Point>(cx)
-            .iter()
-            .any(|selection| selection.range().overlaps(&intersection_range));
-
-        self.unfold_ranges(std::iter::once(intersection_range), true, autoscroll, cx)
-    }
-
-    pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext<Self>) {
-        let selections = self.selections.all::<Point>(cx);
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-        let line_mode = self.selections.line_mode;
-        let ranges = selections.into_iter().map(|s| {
-            if line_mode {
-                let start = Point::new(s.start.row, 0);
-                let end = Point::new(s.end.row, display_map.buffer_snapshot.line_len(s.end.row));
-                start..end
-            } else {
-                s.start..s.end
-            }
-        });
-        self.fold_ranges(ranges, true, cx);
-    }
-
-    pub fn fold_ranges<T: ToOffset + Clone>(
-        &mut self,
-        ranges: impl IntoIterator<Item = Range<T>>,
-        auto_scroll: bool,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let mut ranges = ranges.into_iter().peekable();
-        if ranges.peek().is_some() {
-            self.display_map.update(cx, |map, cx| map.fold(ranges, cx));
-
-            if auto_scroll {
-                self.request_autoscroll(Autoscroll::fit(), cx);
-            }
-
-            cx.notify();
-        }
-    }
-
-    pub fn unfold_ranges<T: ToOffset + Clone>(
-        &mut self,
-        ranges: impl IntoIterator<Item = Range<T>>,
-        inclusive: bool,
-        auto_scroll: bool,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let mut ranges = ranges.into_iter().peekable();
-        if ranges.peek().is_some() {
-            self.display_map
-                .update(cx, |map, cx| map.unfold(ranges, inclusive, cx));
-            if auto_scroll {
-                self.request_autoscroll(Autoscroll::fit(), cx);
-            }
-
-            cx.notify();
-        }
-    }
-
-    pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut ViewContext<Self>) {
-        if hovered != self.gutter_hovered {
-            self.gutter_hovered = hovered;
-            cx.notify();
-        }
-    }
-
-    pub fn insert_blocks(
-        &mut self,
-        blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
-        autoscroll: Option<Autoscroll>,
-        cx: &mut ViewContext<Self>,
-    ) -> Vec<BlockId> {
-        let blocks = self
-            .display_map
-            .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
-        if let Some(autoscroll) = autoscroll {
-            self.request_autoscroll(autoscroll, cx);
-        }
-        blocks
-    }
-
-    pub fn replace_blocks(
-        &mut self,
-        blocks: HashMap<BlockId, RenderBlock>,
-        autoscroll: Option<Autoscroll>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.display_map
-            .update(cx, |display_map, _| display_map.replace_blocks(blocks));
-        if let Some(autoscroll) = autoscroll {
-            self.request_autoscroll(autoscroll, cx);
-        }
-    }
-
-    pub fn remove_blocks(
-        &mut self,
-        block_ids: HashSet<BlockId>,
-        autoscroll: Option<Autoscroll>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.display_map.update(cx, |display_map, cx| {
-            display_map.remove_blocks(block_ids, cx)
-        });
-        if let Some(autoscroll) = autoscroll {
-            self.request_autoscroll(autoscroll, cx);
-        }
-    }
-
-    pub fn longest_row(&self, cx: &mut AppContext) -> u32 {
-        self.display_map
-            .update(cx, |map, cx| map.snapshot(cx))
-            .longest_row()
-    }
-
-    pub fn max_point(&self, cx: &mut AppContext) -> DisplayPoint {
-        self.display_map
-            .update(cx, |map, cx| map.snapshot(cx))
-            .max_point()
-    }
-
-    pub fn text(&self, cx: &AppContext) -> String {
-        self.buffer.read(cx).read(cx).text()
-    }
-
-    pub fn text_option(&self, cx: &AppContext) -> Option<String> {
-        let text = self.text(cx);
-        let text = text.trim();
-
-        if text.is_empty() {
-            return None;
-        }
-
-        Some(text.to_string())
-    }
-
-    pub fn set_text(&mut self, text: impl Into<Arc<str>>, cx: &mut ViewContext<Self>) {
-        self.transact(cx, |this, cx| {
-            this.buffer
-                .read(cx)
-                .as_singleton()
-                .expect("you can only call set_text on editors for singleton buffers")
-                .update(cx, |buffer, cx| buffer.set_text(text, cx));
-        });
-    }
-
-    pub fn display_text(&self, cx: &mut AppContext) -> String {
-        self.display_map
-            .update(cx, |map, cx| map.snapshot(cx))
-            .text()
-    }
-
-    pub fn wrap_guides(&self, cx: &AppContext) -> SmallVec<[(usize, bool); 2]> {
-        let mut wrap_guides = smallvec::smallvec![];
-
-        if self.show_wrap_guides == Some(false) {
-            return wrap_guides;
-        }
-
-        let settings = self.buffer.read(cx).settings_at(0, cx);
-        if settings.show_wrap_guides {
-            if let SoftWrap::Column(soft_wrap) = self.soft_wrap_mode(cx) {
-                wrap_guides.push((soft_wrap as usize, true));
-            }
-            wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
-        }
-
-        wrap_guides
-    }
-
-    pub fn soft_wrap_mode(&self, cx: &AppContext) -> SoftWrap {
-        let settings = self.buffer.read(cx).settings_at(0, cx);
-        let mode = self
-            .soft_wrap_mode_override
-            .unwrap_or_else(|| settings.soft_wrap);
-        match mode {
-            language_settings::SoftWrap::None => SoftWrap::None,
-            language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
-            language_settings::SoftWrap::PreferredLineLength => {
-                SoftWrap::Column(settings.preferred_line_length)
-            }
-        }
-    }
-
-    pub fn set_soft_wrap_mode(
-        &mut self,
-        mode: language_settings::SoftWrap,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.soft_wrap_mode_override = Some(mode);
-        cx.notify();
-    }
-
-    pub fn set_style(&mut self, style: EditorStyle, cx: &mut ViewContext<Self>) {
-        let rem_size = cx.rem_size();
-        self.display_map.update(cx, |map, cx| {
-            map.set_font(
-                style.text.font(),
-                style.text.font_size.to_pixels(rem_size),
-                cx,
-            )
-        });
-        self.style = Some(style);
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn style(&self) -> Option<&EditorStyle> {
-        self.style.as_ref()
-    }
-
-    // Called by the element. This method is not designed to be called outside of the editor
-    // element's layout code because it does not notify when rewrapping is computed synchronously.
-    pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut AppContext) -> bool {
-        self.display_map
-            .update(cx, |map, cx| map.set_wrap_width(width, cx))
-    }
-
-    pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, cx: &mut ViewContext<Self>) {
-        if self.soft_wrap_mode_override.is_some() {
-            self.soft_wrap_mode_override.take();
-        } else {
-            let soft_wrap = match self.soft_wrap_mode(cx) {
-                SoftWrap::None => language_settings::SoftWrap::EditorWidth,
-                SoftWrap::EditorWidth | SoftWrap::Column(_) => language_settings::SoftWrap::None,
-            };
-            self.soft_wrap_mode_override = Some(soft_wrap);
-        }
-        cx.notify();
-    }
-
-    pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut ViewContext<Self>) {
-        self.show_gutter = show_gutter;
-        cx.notify();
-    }
-
-    pub fn set_show_wrap_guides(&mut self, show_gutter: bool, cx: &mut ViewContext<Self>) {
-        self.show_wrap_guides = Some(show_gutter);
-        cx.notify();
-    }
-
-    pub fn reveal_in_finder(&mut self, _: &RevealInFinder, cx: &mut ViewContext<Self>) {
-        if let Some(buffer) = self.buffer().read(cx).as_singleton() {
-            if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
-                cx.reveal_path(&file.abs_path(cx));
-            }
-        }
-    }
-
-    pub fn copy_path(&mut self, _: &CopyPath, cx: &mut ViewContext<Self>) {
-        if let Some(buffer) = self.buffer().read(cx).as_singleton() {
-            if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
-                if let Some(path) = file.abs_path(cx).to_str() {
-                    cx.write_to_clipboard(ClipboardItem::new(path.to_string()));
-                }
-            }
-        }
-    }
-
-    pub fn copy_relative_path(&mut self, _: &CopyRelativePath, cx: &mut ViewContext<Self>) {
-        if let Some(buffer) = self.buffer().read(cx).as_singleton() {
-            if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
-                if let Some(path) = file.path().to_str() {
-                    cx.write_to_clipboard(ClipboardItem::new(path.to_string()));
-                }
-            }
-        }
-    }
-
-    pub fn highlight_rows(&mut self, rows: Option<Range<u32>>) {
-        self.highlighted_rows = rows;
-    }
-
-    pub fn highlighted_rows(&self) -> Option<Range<u32>> {
-        self.highlighted_rows.clone()
-    }
-
-    pub fn highlight_background<T: 'static>(
-        &mut self,
-        ranges: Vec<Range<Anchor>>,
-        color_fetcher: fn(&ThemeColors) -> Hsla,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.background_highlights
-            .insert(TypeId::of::<T>(), (color_fetcher, ranges));
-        cx.notify();
-    }
-
-    pub fn highlight_inlay_background<T: 'static>(
-        &mut self,
-        ranges: Vec<InlayHighlight>,
-        color_fetcher: fn(&ThemeColors) -> Hsla,
-        cx: &mut ViewContext<Self>,
-    ) {
-        // TODO: no actual highlights happen for inlays currently, find a way to do that
-        self.inlay_background_highlights
-            .insert(Some(TypeId::of::<T>()), (color_fetcher, ranges));
-        cx.notify();
-    }
-
-    pub fn clear_background_highlights<T: 'static>(
-        &mut self,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<BackgroundHighlight> {
-        let text_highlights = self.background_highlights.remove(&TypeId::of::<T>());
-        let inlay_highlights = self
-            .inlay_background_highlights
-            .remove(&Some(TypeId::of::<T>()));
-        if text_highlights.is_some() || inlay_highlights.is_some() {
-            cx.notify();
-        }
-        text_highlights
-    }
-
-    #[cfg(feature = "test-support")]
-    pub fn all_text_background_highlights(
-        &mut self,
-        cx: &mut ViewContext<Self>,
-    ) -> Vec<(Range<DisplayPoint>, Hsla)> {
-        let snapshot = self.snapshot(cx);
-        let buffer = &snapshot.buffer_snapshot;
-        let start = buffer.anchor_before(0);
-        let end = buffer.anchor_after(buffer.len());
-        let theme = cx.theme().colors();
-        self.background_highlights_in_range(start..end, &snapshot, theme)
-    }
-
-    fn document_highlights_for_position<'a>(
-        &'a self,
-        position: Anchor,
-        buffer: &'a MultiBufferSnapshot,
-    ) -> impl 'a + Iterator<Item = &Range<Anchor>> {
-        let read_highlights = self
-            .background_highlights
-            .get(&TypeId::of::<DocumentHighlightRead>())
-            .map(|h| &h.1);
-        let write_highlights = self
-            .background_highlights
-            .get(&TypeId::of::<DocumentHighlightWrite>())
-            .map(|h| &h.1);
-        let left_position = position.bias_left(buffer);
-        let right_position = position.bias_right(buffer);
-        read_highlights
-            .into_iter()
-            .chain(write_highlights)
-            .flat_map(move |ranges| {
-                let start_ix = match ranges.binary_search_by(|probe| {
-                    let cmp = probe.end.cmp(&left_position, buffer);
-                    if cmp.is_ge() {
-                        Ordering::Greater
-                    } else {
-                        Ordering::Less
-                    }
-                }) {
-                    Ok(i) | Err(i) => i,
-                };
-
-                let right_position = right_position.clone();
-                ranges[start_ix..]
-                    .iter()
-                    .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
-            })
-    }
-
-    pub fn background_highlights_in_range(
-        &self,
-        search_range: Range<Anchor>,
-        display_snapshot: &DisplaySnapshot,
-        theme: &ThemeColors,
-    ) -> Vec<(Range<DisplayPoint>, Hsla)> {
-        let mut results = Vec::new();
-        for (color_fetcher, ranges) in self.background_highlights.values() {
-            let color = color_fetcher(theme);
-            let start_ix = match ranges.binary_search_by(|probe| {
-                let cmp = probe
-                    .end
-                    .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
-                if cmp.is_gt() {
-                    Ordering::Greater
-                } else {
-                    Ordering::Less
-                }
-            }) {
-                Ok(i) | Err(i) => i,
-            };
-            for range in &ranges[start_ix..] {
-                if range
-                    .start
-                    .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
-                    .is_ge()
-                {
-                    break;
-                }
-
-                let start = range.start.to_display_point(&display_snapshot);
-                let end = range.end.to_display_point(&display_snapshot);
-                results.push((start..end, color))
-            }
-        }
-        results
-    }
-
-    pub fn background_highlight_row_ranges<T: 'static>(
-        &self,
-        search_range: Range<Anchor>,
-        display_snapshot: &DisplaySnapshot,
-        count: usize,
-    ) -> Vec<RangeInclusive<DisplayPoint>> {
-        let mut results = Vec::new();
-        let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
-            return vec![];
-        };
-
-        let start_ix = match ranges.binary_search_by(|probe| {
-            let cmp = probe
-                .end
-                .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
-            if cmp.is_gt() {
-                Ordering::Greater
-            } else {
-                Ordering::Less
-            }
-        }) {
-            Ok(i) | Err(i) => i,
-        };
-        let mut push_region = |start: Option<Point>, end: Option<Point>| {
-            if let (Some(start_display), Some(end_display)) = (start, end) {
-                results.push(
-                    start_display.to_display_point(display_snapshot)
-                        ..=end_display.to_display_point(display_snapshot),
-                );
-            }
-        };
-        let mut start_row: Option<Point> = None;
-        let mut end_row: Option<Point> = None;
-        if ranges.len() > count {
-            return Vec::new();
-        }
-        for range in &ranges[start_ix..] {
-            if range
-                .start
-                .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
-                .is_ge()
-            {
-                break;
-            }
-            let end = range.end.to_point(&display_snapshot.buffer_snapshot);
-            if let Some(current_row) = &end_row {
-                if end.row == current_row.row {
-                    continue;
-                }
-            }
-            let start = range.start.to_point(&display_snapshot.buffer_snapshot);
-            if start_row.is_none() {
-                assert_eq!(end_row, None);
-                start_row = Some(start);
-                end_row = Some(end);
-                continue;
-            }
-            if let Some(current_end) = end_row.as_mut() {
-                if start.row > current_end.row + 1 {
-                    push_region(start_row, end_row);
-                    start_row = Some(start);
-                    end_row = Some(end);
-                } else {
-                    // Merge two hunks.
-                    *current_end = end;
-                }
-            } else {
-                unreachable!();
-            }
-        }
-        // We might still have a hunk that was not rendered (if there was a search hit on the last line)
-        push_region(start_row, end_row);
-        results
-    }
-
-    pub fn highlight_text<T: 'static>(
-        &mut self,
-        ranges: Vec<Range<Anchor>>,
-        style: HighlightStyle,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.display_map.update(cx, |map, _| {
-            map.highlight_text(TypeId::of::<T>(), ranges, style)
-        });
-        cx.notify();
-    }
-
-    pub fn highlight_inlays<T: 'static>(
-        &mut self,
-        highlights: Vec<InlayHighlight>,
-        style: HighlightStyle,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.display_map.update(cx, |map, _| {
-            map.highlight_inlays(TypeId::of::<T>(), highlights, style)
-        });
-        cx.notify();
-    }
-
-    pub fn text_highlights<'a, T: 'static>(
-        &'a self,
-        cx: &'a AppContext,
-    ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
-        self.display_map.read(cx).text_highlights(TypeId::of::<T>())
-    }
-
-    pub fn clear_highlights<T: 'static>(&mut self, cx: &mut ViewContext<Self>) {
-        let cleared = self
-            .display_map
-            .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
-        if cleared {
-            cx.notify();
-        }
-    }
-
-    pub fn show_local_cursors(&self, cx: &WindowContext) -> bool {
-        self.blink_manager.read(cx).visible() && self.focus_handle.is_focused(cx)
-    }
-
-    fn on_buffer_changed(&mut self, _: Model<MultiBuffer>, cx: &mut ViewContext<Self>) {
-        cx.notify();
-    }
-
-    fn on_buffer_event(
-        &mut self,
-        multibuffer: Model<MultiBuffer>,
-        event: &multi_buffer::Event,
-        cx: &mut ViewContext<Self>,
-    ) {
-        match event {
-            multi_buffer::Event::Edited {
-                sigleton_buffer_edited,
-            } => {
-                self.refresh_active_diagnostics(cx);
-                self.refresh_code_actions(cx);
-                if self.has_active_copilot_suggestion(cx) {
-                    self.update_visible_copilot_suggestion(cx);
-                }
-                cx.emit(EditorEvent::BufferEdited);
-                cx.emit(SearchEvent::MatchesInvalidated);
-
-                if *sigleton_buffer_edited {
-                    if let Some(project) = &self.project {
-                        let project = project.read(cx);
-                        let languages_affected = multibuffer
-                            .read(cx)
-                            .all_buffers()
-                            .into_iter()
-                            .filter_map(|buffer| {
-                                let buffer = buffer.read(cx);
-                                let language = buffer.language()?;
-                                if project.is_local()
-                                    && project.language_servers_for_buffer(buffer, cx).count() == 0
-                                {
-                                    None
-                                } else {
-                                    Some(language)
-                                }
-                            })
-                            .cloned()
-                            .collect::<HashSet<_>>();
-                        if !languages_affected.is_empty() {
-                            self.refresh_inlay_hints(
-                                InlayHintRefreshReason::BufferEdited(languages_affected),
-                                cx,
-                            );
-                        }
-                    }
-                }
-            }
-            multi_buffer::Event::ExcerptsAdded {
-                buffer,
-                predecessor,
-                excerpts,
-            } => {
-                cx.emit(EditorEvent::ExcerptsAdded {
-                    buffer: buffer.clone(),
-                    predecessor: *predecessor,
-                    excerpts: excerpts.clone(),
-                });
-                self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
-            }
-            multi_buffer::Event::ExcerptsRemoved { ids } => {
-                self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
-                cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
-            }
-            multi_buffer::Event::Reparsed => cx.emit(EditorEvent::Reparsed),
-            multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
-            multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
-            multi_buffer::Event::FileHandleChanged | multi_buffer::Event::Reloaded => {
-                cx.emit(EditorEvent::TitleChanged)
-            }
-            multi_buffer::Event::DiffBaseChanged => cx.emit(EditorEvent::DiffBaseChanged),
-            multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
-            multi_buffer::Event::DiagnosticsUpdated => {
-                self.refresh_active_diagnostics(cx);
-            }
-            _ => {}
-        };
-    }
-
-    fn on_display_map_changed(&mut self, _: Model<DisplayMap>, cx: &mut ViewContext<Self>) {
-        cx.notify();
-    }
-
-    fn settings_changed(&mut self, cx: &mut ViewContext<Self>) {
-        self.refresh_copilot_suggestions(true, cx);
-        self.refresh_inlay_hints(
-            InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
-                self.selections.newest_anchor().head(),
-                &self.buffer.read(cx).snapshot(cx),
-                cx,
-            )),
-            cx,
-        );
-    }
-
-    pub fn set_searchable(&mut self, searchable: bool) {
-        self.searchable = searchable;
-    }
-
-    pub fn searchable(&self) -> bool {
-        self.searchable
-    }
-
-    fn open_excerpts(&mut self, _: &OpenExcerpts, cx: &mut ViewContext<Self>) {
-        let buffer = self.buffer.read(cx);
-        if buffer.is_singleton() {
-            cx.propagate();
-            return;
-        }
-
-        let Some(workspace) = self.workspace() else {
-            cx.propagate();
-            return;
-        };
-
-        let mut new_selections_by_buffer = HashMap::default();
-        for selection in self.selections.all::<usize>(cx) {
-            for (buffer, mut range, _) in
-                buffer.range_to_buffer_ranges(selection.start..selection.end, cx)
-            {
-                if selection.reversed {
-                    mem::swap(&mut range.start, &mut range.end);
-                }
-                new_selections_by_buffer
-                    .entry(buffer)
-                    .or_insert(Vec::new())
-                    .push(range)
-            }
-        }
-
-        self.push_to_nav_history(self.selections.newest_anchor().head(), None, cx);
-
-        // We defer the pane interaction because we ourselves are a workspace item
-        // and activating a new item causes the pane to call a method on us reentrantly,
-        // which panics if we're on the stack.
-        cx.window_context().defer(move |cx| {
-            workspace.update(cx, |workspace, cx| {
-                let pane = workspace.active_pane().clone();
-                pane.update(cx, |pane, _| pane.disable_history());
-
-                for (buffer, ranges) in new_selections_by_buffer.into_iter() {
-                    let editor = workspace.open_project_item::<Self>(buffer, cx);
-                    editor.update(cx, |editor, cx| {
-                        editor.change_selections(Some(Autoscroll::newest()), cx, |s| {
-                            s.select_ranges(ranges);
-                        });
-                    });
-                }
-
-                pane.update(cx, |pane, _| pane.enable_history());
-            })
-        });
-    }
-
-    fn jump(
-        &mut self,
-        path: ProjectPath,
-        position: Point,
-        anchor: language::Anchor,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let workspace = self.workspace();
-        cx.spawn(|_, mut cx| async move {
-            let workspace = workspace.ok_or_else(|| anyhow!("cannot jump without workspace"))?;
-            let editor = workspace.update(&mut cx, |workspace, cx| {
-                workspace.open_path(path, None, true, cx)
-            })?;
-            let editor = editor
-                .await?
-                .downcast::<Editor>()
-                .ok_or_else(|| anyhow!("opened item was not an editor"))?
-                .downgrade();
-            editor.update(&mut cx, |editor, cx| {
-                let buffer = editor
-                    .buffer()
-                    .read(cx)
-                    .as_singleton()
-                    .ok_or_else(|| anyhow!("cannot jump in a multi-buffer"))?;
-                let buffer = buffer.read(cx);
-                let cursor = if buffer.can_resolve(&anchor) {
-                    language::ToPoint::to_point(&anchor, buffer)
-                } else {
-                    buffer.clip_point(position, Bias::Left)
-                };
-
-                let nav_history = editor.nav_history.take();
-                editor.change_selections(Some(Autoscroll::newest()), cx, |s| {
-                    s.select_ranges([cursor..cursor]);
-                });
-                editor.nav_history = nav_history;
-
-                anyhow::Ok(())
-            })??;
-
-            anyhow::Ok(())
-        })
-        .detach_and_log_err(cx);
-    }
-
-    fn marked_text_ranges(&self, cx: &AppContext) -> Option<Vec<Range<OffsetUtf16>>> {
-        let snapshot = self.buffer.read(cx).read(cx);
-        let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
-        Some(
-            ranges
-                .iter()
-                .map(move |range| {
-                    range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
-                })
-                .collect(),
-        )
-    }
-
-    fn selection_replacement_ranges(
-        &self,
-        range: Range<OffsetUtf16>,
-        cx: &AppContext,
-    ) -> Vec<Range<OffsetUtf16>> {
-        let selections = self.selections.all::<OffsetUtf16>(cx);
-        let newest_selection = selections
-            .iter()
-            .max_by_key(|selection| selection.id)
-            .unwrap();
-        let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
-        let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
-        let snapshot = self.buffer.read(cx).read(cx);
-        selections
-            .into_iter()
-            .map(|mut selection| {
-                selection.start.0 =
-                    (selection.start.0 as isize).saturating_add(start_delta) as usize;
-                selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
-                snapshot.clip_offset_utf16(selection.start, Bias::Left)
-                    ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
-            })
-            .collect()
-    }
-
-    fn report_copilot_event(
-        &self,
-        suggestion_id: Option<String>,
-        suggestion_accepted: bool,
-        cx: &AppContext,
-    ) {
-        let Some(project) = &self.project else { return };
-
-        // If None, we are either getting suggestions in a new, unsaved file, or in a file without an extension
-        let file_extension = self
-            .buffer
-            .read(cx)
-            .as_singleton()
-            .and_then(|b| b.read(cx).file())
-            .and_then(|file| Path::new(file.file_name(cx)).extension())
-            .and_then(|e| e.to_str())
-            .map(|a| a.to_string());
-
-        let telemetry = project.read(cx).client().telemetry().clone();
-        let telemetry_settings = *TelemetrySettings::get_global(cx);
-
-        telemetry.report_copilot_event(
-            telemetry_settings,
-            suggestion_id,
-            suggestion_accepted,
-            file_extension,
-        )
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    fn report_editor_event(
-        &self,
-        _operation: &'static str,
-        _file_extension: Option<String>,
-        _cx: &AppContext,
-    ) {
-    }
-
-    #[cfg(not(any(test, feature = "test-support")))]
-    fn report_editor_event(
-        &self,
-        operation: &'static str,
-        file_extension: Option<String>,
-        cx: &AppContext,
-    ) {
-        let Some(project) = &self.project else { return };
-
-        // If None, we are in a file without an extension
-        let file = self
-            .buffer
-            .read(cx)
-            .as_singleton()
-            .and_then(|b| b.read(cx).file());
-        let file_extension = file_extension.or(file
-            .as_ref()
-            .and_then(|file| Path::new(file.file_name(cx)).extension())
-            .and_then(|e| e.to_str())
-            .map(|a| a.to_string()));
-
-        let vim_mode = cx
-            .global::<SettingsStore>()
-            .raw_user_settings()
-            .get("vim_mode")
-            == Some(&serde_json::Value::Bool(true));
-        let telemetry_settings = *TelemetrySettings::get_global(cx);
-        let copilot_enabled = all_language_settings(file, cx).copilot_enabled(None, None);
-        let copilot_enabled_for_language = self
-            .buffer
-            .read(cx)
-            .settings_at(0, cx)
-            .show_copilot_suggestions;
-
-        let telemetry = project.read(cx).client().telemetry().clone();
-        telemetry.report_editor_event(
-            telemetry_settings,
-            file_extension,
-            vim_mode,
-            operation,
-            copilot_enabled,
-            copilot_enabled_for_language,
-        )
-    }
-
-    /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
-    /// with each line being an array of {text, highlight} objects.
-    fn copy_highlight_json(&mut self, _: &CopyHighlightJson, cx: &mut ViewContext<Self>) {
-        let Some(buffer) = self.buffer.read(cx).as_singleton() else {
-            return;
-        };
-
-        #[derive(Serialize)]
-        struct Chunk<'a> {
-            text: String,
-            highlight: Option<&'a str>,
-        }
-
-        let snapshot = buffer.read(cx).snapshot();
-        let range = self
-            .selected_text_range(cx)
-            .and_then(|selected_range| {
-                if selected_range.is_empty() {
-                    None
-                } else {
-                    Some(selected_range)
-                }
-            })
-            .unwrap_or_else(|| 0..snapshot.len());
-
-        let chunks = snapshot.chunks(range, true);
-        let mut lines = Vec::new();
-        let mut line: VecDeque<Chunk> = VecDeque::new();
-
-        let Some(style) = self.style.as_ref() else {
-            return;
-        };
-
-        for chunk in chunks {
-            let highlight = chunk
-                .syntax_highlight_id
-                .and_then(|id| id.name(&style.syntax));
-            let mut chunk_lines = chunk.text.split("\n").peekable();
-            while let Some(text) = chunk_lines.next() {
-                let mut merged_with_last_token = false;
-                if let Some(last_token) = line.back_mut() {
-                    if last_token.highlight == highlight {
-                        last_token.text.push_str(text);
-                        merged_with_last_token = true;
-                    }
-                }
-
-                if !merged_with_last_token {
-                    line.push_back(Chunk {
-                        text: text.into(),
-                        highlight,
-                    });
-                }
-
-                if chunk_lines.peek().is_some() {
-                    if line.len() > 1 && line.front().unwrap().text.is_empty() {
-                        line.pop_front();
-                    }
-                    if line.len() > 1 && line.back().unwrap().text.is_empty() {
-                        line.pop_back();
-                    }
-
-                    lines.push(mem::take(&mut line));
-                }
-            }
-        }
-
-        let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
-            return;
-        };
-        cx.write_to_clipboard(ClipboardItem::new(lines));
-    }
-
-    pub fn inlay_hint_cache(&self) -> &InlayHintCache {
-        &self.inlay_hint_cache
-    }
-
-    pub fn replay_insert_event(
-        &mut self,
-        text: &str,
-        relative_utf16_range: Option<Range<isize>>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        if !self.input_enabled {
-            cx.emit(EditorEvent::InputIgnored { text: text.into() });
-            return;
-        }
-        if let Some(relative_utf16_range) = relative_utf16_range {
-            let selections = self.selections.all::<OffsetUtf16>(cx);
-            self.change_selections(None, cx, |s| {
-                let new_ranges = selections.into_iter().map(|range| {
-                    let start = OffsetUtf16(
-                        range
-                            .head()
-                            .0
-                            .saturating_add_signed(relative_utf16_range.start),
-                    );
-                    let end = OffsetUtf16(
-                        range
-                            .head()
-                            .0
-                            .saturating_add_signed(relative_utf16_range.end),
-                    );
-                    start..end
-                });
-                s.select_ranges(new_ranges);
-            });
-        }
-
-        self.handle_input(text, cx);
-    }
-
-    pub fn supports_inlay_hints(&self, cx: &AppContext) -> bool {
-        let Some(project) = self.project.as_ref() else {
-            return false;
-        };
-        let project = project.read(cx);
-
-        let mut supports = false;
-        self.buffer().read(cx).for_each_buffer(|buffer| {
-            if !supports {
-                supports = project
-                    .language_servers_for_buffer(buffer.read(cx), cx)
-                    .any(
-                        |(_, server)| match server.capabilities().inlay_hint_provider {
-                            Some(lsp::OneOf::Left(enabled)) => enabled,
-                            Some(lsp::OneOf::Right(_)) => true,
-                            None => false,
-                        },
-                    )
-            }
-        });
-        supports
-    }
-
-    pub fn focus(&self, cx: &mut WindowContext) {
-        cx.focus(&self.focus_handle)
-    }
-
-    pub fn is_focused(&self, cx: &WindowContext) -> bool {
-        self.focus_handle.is_focused(cx)
-    }
-
-    fn handle_focus(&mut self, cx: &mut ViewContext<Self>) {
-        cx.emit(EditorEvent::Focused);
-
-        if let Some(rename) = self.pending_rename.as_ref() {
-            let rename_editor_focus_handle = rename.editor.read(cx).focus_handle.clone();
-            cx.focus(&rename_editor_focus_handle);
-        } else {
-            self.blink_manager.update(cx, BlinkManager::enable);
-            self.buffer.update(cx, |buffer, cx| {
-                buffer.finalize_last_transaction(cx);
-                if self.leader_peer_id.is_none() {
-                    buffer.set_active_selections(
-                        &self.selections.disjoint_anchors(),
-                        self.selections.line_mode,
-                        self.cursor_shape,
-                        cx,
-                    );
-                }
-            });
-        }
-    }
-
-    pub fn handle_blur(&mut self, cx: &mut ViewContext<Self>) {
-        self.blink_manager.update(cx, BlinkManager::disable);
-        self.buffer
-            .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
-        self.hide_context_menu(cx);
-        hide_hover(self, cx);
-        cx.emit(EditorEvent::Blurred);
-        cx.notify();
-    }
-
-    pub fn register_action<A: Action>(
-        &mut self,
-        listener: impl Fn(&A, &mut WindowContext) + 'static,
-    ) -> &mut Self {
-        let listener = Arc::new(listener);
-
-        self.editor_actions.push(Box::new(move |cx| {
-            let _view = cx.view().clone();
-            let cx = cx.window_context();
-            let listener = listener.clone();
-            cx.on_action(TypeId::of::<A>(), move |action, phase, cx| {
-                let action = action.downcast_ref().unwrap();
-                if phase == DispatchPhase::Bubble {
-                    listener(action, cx)
-                }
-            })
-        }));
-        self
-    }
-}
-
-pub trait CollaborationHub {
-    fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator>;
-    fn user_participant_indices<'a>(
-        &self,
-        cx: &'a AppContext,
-    ) -> &'a HashMap<u64, ParticipantIndex>;
-}
-
-impl CollaborationHub for Model<Project> {
-    fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator> {
-        self.read(cx).collaborators()
-    }
-
-    fn user_participant_indices<'a>(
-        &self,
-        cx: &'a AppContext,
-    ) -> &'a HashMap<u64, ParticipantIndex> {
-        self.read(cx).user_store().read(cx).participant_indices()
-    }
-}
-
-fn inlay_hint_settings(
-    location: Anchor,
-    snapshot: &MultiBufferSnapshot,
-    cx: &mut ViewContext<'_, Editor>,
-) -> InlayHintSettings {
-    let file = snapshot.file_at(location);
-    let language = snapshot.language_at(location);
-    let settings = all_language_settings(file, cx);
-    settings
-        .language(language.map(|l| l.name()).as_deref())
-        .inlay_hints
-}
-
-fn consume_contiguous_rows(
-    contiguous_row_selections: &mut Vec<Selection<Point>>,
-    selection: &Selection<Point>,
-    display_map: &DisplaySnapshot,
-    selections: &mut std::iter::Peekable<std::slice::Iter<Selection<Point>>>,
-) -> (u32, u32) {
-    contiguous_row_selections.push(selection.clone());
-    let start_row = selection.start.row;
-    let mut end_row = ending_row(selection, display_map);
-
-    while let Some(next_selection) = selections.peek() {
-        if next_selection.start.row <= end_row {
-            end_row = ending_row(next_selection, display_map);
-            contiguous_row_selections.push(selections.next().unwrap().clone());
-        } else {
-            break;
-        }
-    }
-    (start_row, end_row)
-}
-
-fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> u32 {
-    if next_selection.end.column > 0 || next_selection.is_empty() {
-        display_map.next_line_boundary(next_selection.end).0.row + 1
-    } else {
-        next_selection.end.row
-    }
-}
-
-impl EditorSnapshot {
-    pub fn remote_selections_in_range<'a>(
-        &'a self,
-        range: &'a Range<Anchor>,
-        collaboration_hub: &dyn CollaborationHub,
-        cx: &'a AppContext,
-    ) -> impl 'a + Iterator<Item = RemoteSelection> {
-        let participant_indices = collaboration_hub.user_participant_indices(cx);
-        let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
-        let collaborators_by_replica_id = collaborators_by_peer_id
-            .iter()
-            .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
-            .collect::<HashMap<_, _>>();
-        self.buffer_snapshot
-            .remote_selections_in_range(range)
-            .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
-                let collaborator = collaborators_by_replica_id.get(&replica_id)?;
-                let participant_index = participant_indices.get(&collaborator.user_id).copied();
-                Some(RemoteSelection {
-                    replica_id,
-                    selection,
-                    cursor_shape,
-                    line_mode,
-                    participant_index,
-                    peer_id: collaborator.peer_id,
-                })
-            })
-    }
-
-    pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
-        self.display_snapshot.buffer_snapshot.language_at(position)
-    }
-
-    pub fn is_focused(&self) -> bool {
-        self.is_focused
-    }
-
-    pub fn placeholder_text(&self) -> Option<&Arc<str>> {
-        self.placeholder_text.as_ref()
-    }
-
-    pub fn scroll_position(&self) -> gpui::Point<f32> {
-        self.scroll_anchor.scroll_position(&self.display_snapshot)
-    }
-}
-
-impl Deref for EditorSnapshot {
-    type Target = DisplaySnapshot;
-
-    fn deref(&self) -> &Self::Target {
-        &self.display_snapshot
-    }
-}
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub enum EditorEvent {
-    InputIgnored {
-        text: Arc<str>,
-    },
-    InputHandled {
-        utf16_range_to_replace: Option<Range<isize>>,
-        text: Arc<str>,
-    },
-    ExcerptsAdded {
-        buffer: Model<Buffer>,
-        predecessor: ExcerptId,
-        excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
-    },
-    ExcerptsRemoved {
-        ids: Vec<ExcerptId>,
-    },
-    BufferEdited,
-    Edited,
-    Reparsed,
-    Focused,
-    Blurred,
-    DirtyChanged,
-    Saved,
-    TitleChanged,
-    DiffBaseChanged,
-    SelectionsChanged {
-        local: bool,
-    },
-    ScrollPositionChanged {
-        local: bool,
-        autoscroll: bool,
-    },
-    Closed,
-}
-
-impl EventEmitter<EditorEvent> for Editor {}
-
-impl FocusableView for Editor {
-    fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
-        self.focus_handle.clone()
-    }
-}
-
-impl Render for Editor {
-    fn render<'a>(&mut self, cx: &mut ViewContext<'a, Self>) -> impl IntoElement {
-        let settings = ThemeSettings::get_global(cx);
-        let text_style = match self.mode {
-            EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
-                color: cx.theme().colors().editor_foreground,
-                font_family: settings.ui_font.family.clone(),
-                font_features: settings.ui_font.features,
-                font_size: rems(0.875).into(),
-                font_weight: FontWeight::NORMAL,
-                font_style: FontStyle::Normal,
-                line_height: relative(settings.buffer_line_height.value()),
-                background_color: None,
-                underline: None,
-                white_space: WhiteSpace::Normal,
-            },
-
-            EditorMode::Full => TextStyle {
-                color: cx.theme().colors().editor_foreground,
-                font_family: settings.buffer_font.family.clone(),
-                font_features: settings.buffer_font.features,
-                font_size: settings.buffer_font_size(cx).into(),
-                font_weight: FontWeight::NORMAL,
-                font_style: FontStyle::Normal,
-                line_height: relative(settings.buffer_line_height.value()),
-                background_color: None,
-                underline: None,
-                white_space: WhiteSpace::Normal,
-            },
-        };
-
-        let background = match self.mode {
-            EditorMode::SingleLine => cx.theme().system().transparent,
-            EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
-            EditorMode::Full => cx.theme().colors().editor_background,
-        };
-
-        EditorElement::new(
-            cx.view(),
-            EditorStyle {
-                background,
-                local_player: cx.theme().players().local(),
-                text: text_style,
-                scrollbar_width: px(12.),
-                syntax: cx.theme().syntax().clone(),
-                status: cx.theme().status().clone(),
-                // todo!("what about the rest of the highlight style parts?")
-                inlays_style: HighlightStyle {
-                    color: Some(cx.theme().status().hint),
-                    font_weight: Some(FontWeight::BOLD),
-                    ..HighlightStyle::default()
-                },
-                suggestions_style: HighlightStyle {
-                    color: Some(cx.theme().status().predictive),
-                    ..HighlightStyle::default()
-                },
-            },
-        )
-    }
-}
-
-impl InputHandler for Editor {
-    fn text_for_range(
-        &mut self,
-        range_utf16: Range<usize>,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<String> {
-        Some(
-            self.buffer
-                .read(cx)
-                .read(cx)
-                .text_for_range(OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end))
-                .collect(),
-        )
-    }
-
-    fn selected_text_range(&mut self, cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
-        // Prevent the IME menu from appearing when holding down an alphabetic key
-        // while input is disabled.
-        if !self.input_enabled {
-            return None;
-        }
-
-        let range = self.selections.newest::<OffsetUtf16>(cx).range();
-        Some(range.start.0..range.end.0)
-    }
-
-    fn marked_text_range(&self, cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
-        let snapshot = self.buffer.read(cx).read(cx);
-        let range = self.text_highlights::<InputComposition>(cx)?.1.get(0)?;
-        Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
-    }
-
-    fn unmark_text(&mut self, cx: &mut ViewContext<Self>) {
-        self.clear_highlights::<InputComposition>(cx);
-        self.ime_transaction.take();
-    }
-
-    fn replace_text_in_range(
-        &mut self,
-        range_utf16: Option<Range<usize>>,
-        text: &str,
-        cx: &mut ViewContext<Self>,
-    ) {
-        if !self.input_enabled {
-            cx.emit(EditorEvent::InputIgnored { text: text.into() });
-            return;
-        }
-
-        self.transact(cx, |this, cx| {
-            let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
-                let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
-                Some(this.selection_replacement_ranges(range_utf16, cx))
-            } else {
-                this.marked_text_ranges(cx)
-            };
-
-            let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
-                let newest_selection_id = this.selections.newest_anchor().id;
-                this.selections
-                    .all::<OffsetUtf16>(cx)
-                    .iter()
-                    .zip(ranges_to_replace.iter())
-                    .find_map(|(selection, range)| {
-                        if selection.id == newest_selection_id {
-                            Some(
-                                (range.start.0 as isize - selection.head().0 as isize)
-                                    ..(range.end.0 as isize - selection.head().0 as isize),
-                            )
-                        } else {
-                            None
-                        }
-                    })
-            });
-
-            cx.emit(EditorEvent::InputHandled {
-                utf16_range_to_replace: range_to_replace,
-                text: text.into(),
-            });
-
-            if let Some(new_selected_ranges) = new_selected_ranges {
-                this.change_selections(None, cx, |selections| {
-                    selections.select_ranges(new_selected_ranges)
-                });
-            }
-
-            this.handle_input(text, cx);
-        });
-
-        if let Some(transaction) = self.ime_transaction {
-            self.buffer.update(cx, |buffer, cx| {
-                buffer.group_until_transaction(transaction, cx);
-            });
-        }
-
-        self.unmark_text(cx);
-    }
-
-    fn replace_and_mark_text_in_range(
-        &mut self,
-        range_utf16: Option<Range<usize>>,
-        text: &str,
-        new_selected_range_utf16: Option<Range<usize>>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        if !self.input_enabled {
-            cx.emit(EditorEvent::InputIgnored { text: text.into() });
-            return;
-        }
-
-        let transaction = self.transact(cx, |this, cx| {
-            let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
-                let snapshot = this.buffer.read(cx).read(cx);
-                if let Some(relative_range_utf16) = range_utf16.as_ref() {
-                    for marked_range in &mut marked_ranges {
-                        marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
-                        marked_range.start.0 += relative_range_utf16.start;
-                        marked_range.start =
-                            snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
-                        marked_range.end =
-                            snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
-                    }
-                }
-                Some(marked_ranges)
-            } else if let Some(range_utf16) = range_utf16 {
-                let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
-                Some(this.selection_replacement_ranges(range_utf16, cx))
-            } else {
-                None
-            };
-
-            let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
-                let newest_selection_id = this.selections.newest_anchor().id;
-                this.selections
-                    .all::<OffsetUtf16>(cx)
-                    .iter()
-                    .zip(ranges_to_replace.iter())
-                    .find_map(|(selection, range)| {
-                        if selection.id == newest_selection_id {
-                            Some(
-                                (range.start.0 as isize - selection.head().0 as isize)
-                                    ..(range.end.0 as isize - selection.head().0 as isize),
-                            )
-                        } else {
-                            None
-                        }
-                    })
-            });
-
-            cx.emit(EditorEvent::InputHandled {
-                utf16_range_to_replace: range_to_replace,
-                text: text.into(),
-            });
-
-            if let Some(ranges) = ranges_to_replace {
-                this.change_selections(None, cx, |s| s.select_ranges(ranges));
-            }
-
-            let marked_ranges = {
-                let snapshot = this.buffer.read(cx).read(cx);
-                this.selections
-                    .disjoint_anchors()
-                    .iter()
-                    .map(|selection| {
-                        selection.start.bias_left(&*snapshot)..selection.end.bias_right(&*snapshot)
-                    })
-                    .collect::<Vec<_>>()
-            };
-
-            if text.is_empty() {
-                this.unmark_text(cx);
-            } else {
-                this.highlight_text::<InputComposition>(
-                    marked_ranges.clone(),
-                    HighlightStyle::default(), // todo!() this.style(cx).composition_mark,
-                    cx,
-                );
-            }
-
-            this.handle_input(text, cx);
-
-            if let Some(new_selected_range) = new_selected_range_utf16 {
-                let snapshot = this.buffer.read(cx).read(cx);
-                let new_selected_ranges = marked_ranges
-                    .into_iter()
-                    .map(|marked_range| {
-                        let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
-                        let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
-                        let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
-                        snapshot.clip_offset_utf16(new_start, Bias::Left)
-                            ..snapshot.clip_offset_utf16(new_end, Bias::Right)
-                    })
-                    .collect::<Vec<_>>();
-
-                drop(snapshot);
-                this.change_selections(None, cx, |selections| {
-                    selections.select_ranges(new_selected_ranges)
-                });
-            }
-        });
-
-        self.ime_transaction = self.ime_transaction.or(transaction);
-        if let Some(transaction) = self.ime_transaction {
-            self.buffer.update(cx, |buffer, cx| {
-                buffer.group_until_transaction(transaction, cx);
-            });
-        }
-
-        if self.text_highlights::<InputComposition>(cx).is_none() {
-            self.ime_transaction.take();
-        }
-    }
-
-    fn bounds_for_range(
-        &mut self,
-        range_utf16: Range<usize>,
-        element_bounds: gpui::Bounds<Pixels>,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<gpui::Bounds<Pixels>> {
-        let text_layout_details = self.text_layout_details(cx);
-        let style = &text_layout_details.editor_style;
-        let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
-        let font_size = style.text.font_size.to_pixels(cx.rem_size());
-        let line_height = style.text.line_height_in_pixels(cx.rem_size());
-        let em_width = cx
-            .text_system()
-            .typographic_bounds(font_id, font_size, 'm')
-            .unwrap()
-            .size
-            .width;
-
-        let snapshot = self.snapshot(cx);
-        let scroll_position = snapshot.scroll_position();
-        let scroll_left = scroll_position.x * em_width;
-
-        let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
-        let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
-            + self.gutter_width;
-        let y = line_height * (start.row() as f32 - scroll_position.y);
-
-        Some(Bounds {
-            origin: element_bounds.origin + point(x, y),
-            size: size(em_width, line_height),
-        })
-    }
-}
-
-trait SelectionExt {
-    fn offset_range(&self, buffer: &MultiBufferSnapshot) -> Range<usize>;
-    fn point_range(&self, buffer: &MultiBufferSnapshot) -> Range<Point>;
-    fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
-    fn spanned_rows(&self, include_end_if_at_line_start: bool, map: &DisplaySnapshot)
-        -> Range<u32>;
-}
-
-impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
-    fn point_range(&self, buffer: &MultiBufferSnapshot) -> Range<Point> {
-        let start = self.start.to_point(buffer);
-        let end = self.end.to_point(buffer);
-        if self.reversed {
-            end..start
-        } else {
-            start..end
-        }
-    }
-
-    fn offset_range(&self, buffer: &MultiBufferSnapshot) -> Range<usize> {
-        let start = self.start.to_offset(buffer);
-        let end = self.end.to_offset(buffer);
-        if self.reversed {
-            end..start
-        } else {
-            start..end
-        }
-    }
-
-    fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
-        let start = self
-            .start
-            .to_point(&map.buffer_snapshot)
-            .to_display_point(map);
-        let end = self
-            .end
-            .to_point(&map.buffer_snapshot)
-            .to_display_point(map);
-        if self.reversed {
-            end..start
-        } else {
-            start..end
-        }
-    }
-
-    fn spanned_rows(
-        &self,
-        include_end_if_at_line_start: bool,
-        map: &DisplaySnapshot,
-    ) -> Range<u32> {
-        let start = self.start.to_point(&map.buffer_snapshot);
-        let mut end = self.end.to_point(&map.buffer_snapshot);
-        if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
-            end.row -= 1;
-        }
-
-        let buffer_start = map.prev_line_boundary(start).0;
-        let buffer_end = map.next_line_boundary(end).0;
-        buffer_start.row..buffer_end.row + 1
-    }
-}
-
-impl<T: InvalidationRegion> InvalidationStack<T> {
-    fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
-    where
-        S: Clone + ToOffset,
-    {
-        while let Some(region) = self.last() {
-            let all_selections_inside_invalidation_ranges =
-                if selections.len() == region.ranges().len() {
-                    selections
-                        .iter()
-                        .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
-                        .all(|(selection, invalidation_range)| {
-                            let head = selection.head().to_offset(buffer);
-                            invalidation_range.start <= head && invalidation_range.end >= head
-                        })
-                } else {
-                    false
-                };
-
-            if all_selections_inside_invalidation_ranges {
-                break;
-            } else {
-                self.pop();
-            }
-        }
-    }
-}
-
-impl<T> Default for InvalidationStack<T> {
-    fn default() -> Self {
-        Self(Default::default())
-    }
-}
-
-impl<T> Deref for InvalidationStack<T> {
-    type Target = Vec<T>;
-
-    fn deref(&self) -> &Self::Target {
-        &self.0
-    }
-}
-
-impl<T> DerefMut for InvalidationStack<T> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.0
-    }
-}
-
-impl InvalidationRegion for SnippetState {
-    fn ranges(&self) -> &[Range<Anchor>] {
-        &self.ranges[self.active_index]
-    }
-}
-
-pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> RenderBlock {
-    let (text_without_backticks, code_ranges) = highlight_diagnostic_message(&diagnostic);
-
-    Arc::new(move |cx: &mut BlockContext| {
-        let color = Some(cx.theme().colors().text_accent);
-        let group_id: SharedString = cx.block_id.to_string().into();
-        // TODO: Nate: We should tint the background of the block with the severity color
-        // We need to extend the theme before we can do this
-        h_stack()
-            .id(cx.block_id)
-            .group(group_id.clone())
-            .relative()
-            .pl(cx.anchor_x)
-            .size_full()
-            .gap_2()
-            .child(
-                StyledText::new(text_without_backticks.clone()).with_highlights(
-                    &cx.text_style(),
-                    code_ranges.iter().map(|range| {
-                        (
-                            range.clone(),
-                            HighlightStyle {
-                                color,
-                                ..Default::default()
-                            },
-                        )
-                    }),
-                ),
-            )
-            .child(
-                IconButton::new(("copy-block", cx.block_id), Icon::Copy)
-                    .icon_color(Color::Muted)
-                    .size(ButtonSize::Compact)
-                    .style(ButtonStyle::Transparent)
-                    .visible_on_hover(group_id)
-                    .on_click(cx.listener({
-                        let message = diagnostic.message.clone();
-                        move |_, _, cx| cx.write_to_clipboard(ClipboardItem::new(message.clone()))
-                    }))
-                    .tooltip(|cx| Tooltip::text("Copy diagnostic message", cx)),
-            )
-            .into_any_element()
-    })
-}
-
-pub fn highlight_diagnostic_message(diagnostic: &Diagnostic) -> (SharedString, Vec<Range<usize>>) {
-    let mut text_without_backticks = String::new();
-    let mut code_ranges = Vec::new();
-
-    if let Some(source) = &diagnostic.source {
-        text_without_backticks.push_str(&source);
-        code_ranges.push(0..source.len());
-        text_without_backticks.push_str(": ");
-    }
-
-    let mut prev_offset = 0;
-    let mut in_code_block = false;
-    for (ix, _) in diagnostic
-        .message
-        .match_indices('`')
-        .chain([(diagnostic.message.len(), "")])
-    {
-        let prev_len = text_without_backticks.len();
-        text_without_backticks.push_str(&diagnostic.message[prev_offset..ix]);
-        prev_offset = ix + 1;
-        if in_code_block {
-            code_ranges.push(prev_len..text_without_backticks.len());
-            in_code_block = false;
-        } else {
-            in_code_block = true;
-        }
-    }
-
-    (text_without_backticks.into(), code_ranges)
-}
-
-pub fn diagnostic_style(severity: DiagnosticSeverity, valid: bool, colors: &StatusColors) -> Hsla {
-    match (severity, valid) {
-        (DiagnosticSeverity::ERROR, true) => colors.error,
-        (DiagnosticSeverity::ERROR, false) => colors.error,
-        (DiagnosticSeverity::WARNING, true) => colors.warning,
-        (DiagnosticSeverity::WARNING, false) => colors.warning,
-        (DiagnosticSeverity::INFORMATION, true) => colors.info,
-        (DiagnosticSeverity::INFORMATION, false) => colors.info,
-        (DiagnosticSeverity::HINT, true) => colors.info,
-        (DiagnosticSeverity::HINT, false) => colors.info,
-        _ => colors.ignored,
-    }
-}
-
-pub fn styled_runs_for_code_label<'a>(
-    label: &'a CodeLabel,
-    syntax_theme: &'a theme::SyntaxTheme,
-) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
-    let fade_out = HighlightStyle {
-        fade_out: Some(0.35),
-        ..Default::default()
-    };
-
-    let mut prev_end = label.filter_range.end;
-    label
-        .runs
-        .iter()
-        .enumerate()
-        .flat_map(move |(ix, (range, highlight_id))| {
-            let style = if let Some(style) = highlight_id.style(syntax_theme) {
-                style
-            } else {
-                return Default::default();
-            };
-            let mut muted_style = style;
-            muted_style.highlight(fade_out);
-
-            let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
-            if range.start >= label.filter_range.end {
-                if range.start > prev_end {
-                    runs.push((prev_end..range.start, fade_out));
-                }
-                runs.push((range.clone(), muted_style));
-            } else if range.end <= label.filter_range.end {
-                runs.push((range.clone(), style));
-            } else {
-                runs.push((range.start..label.filter_range.end, style));
-                runs.push((label.filter_range.end..range.end, muted_style));
-            }
-            prev_end = cmp::max(prev_end, range.end);
-
-            if ix + 1 == label.runs.len() && label.text.len() > prev_end {
-                runs.push((prev_end..label.text.len(), fade_out));
-            }
-
-            runs
-        })
-}
-
-pub fn split_words<'a>(text: &'a str) -> impl std::iter::Iterator<Item = &'a str> + 'a {
-    let mut index = 0;
-    let mut codepoints = text.char_indices().peekable();
-
-    std::iter::from_fn(move || {
-        let start_index = index;
-        while let Some((new_index, codepoint)) = codepoints.next() {
-            index = new_index + codepoint.len_utf8();
-            let current_upper = codepoint.is_uppercase();
-            let next_upper = codepoints
-                .peek()
-                .map(|(_, c)| c.is_uppercase())
-                .unwrap_or(false);
-
-            if !current_upper && next_upper {
-                return Some(&text[start_index..index]);
-            }
-        }
-
-        index = text.len();
-        if start_index < text.len() {
-            return Some(&text[start_index..]);
-        }
-        None
-    })
-    .flat_map(|word| word.split_inclusive('_'))
-    .flat_map(|word| word.split_inclusive('-'))
-}
-
-trait RangeToAnchorExt {
-    fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
-}
-
-impl<T: ToOffset> RangeToAnchorExt for Range<T> {
-    fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
-        snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
-    }
-}

crates/editor2/src/editor_settings.rs 🔗

@@ -1,72 +0,0 @@
-use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
-use settings::Settings;
-
-#[derive(Deserialize)]
-pub struct EditorSettings {
-    pub cursor_blink: bool,
-    pub hover_popover_enabled: bool,
-    pub show_completions_on_input: bool,
-    pub show_completion_documentation: bool,
-    pub use_on_type_format: bool,
-    pub scrollbar: Scrollbar,
-    pub relative_line_numbers: bool,
-    pub seed_search_query_from_cursor: SeedQuerySetting,
-}
-
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum SeedQuerySetting {
-    Always,
-    Selection,
-    Never,
-}
-
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
-pub struct Scrollbar {
-    pub show: ShowScrollbar,
-    pub git_diff: bool,
-    pub selections: bool,
-}
-
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
-#[serde(rename_all = "snake_case")]
-pub enum ShowScrollbar {
-    Auto,
-    System,
-    Always,
-    Never,
-}
-
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
-pub struct EditorSettingsContent {
-    pub cursor_blink: Option<bool>,
-    pub hover_popover_enabled: Option<bool>,
-    pub show_completions_on_input: Option<bool>,
-    pub show_completion_documentation: Option<bool>,
-    pub use_on_type_format: Option<bool>,
-    pub scrollbar: Option<ScrollbarContent>,
-    pub relative_line_numbers: Option<bool>,
-    pub seed_search_query_from_cursor: Option<SeedQuerySetting>,
-}
-
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
-pub struct ScrollbarContent {
-    pub show: Option<ShowScrollbar>,
-    pub git_diff: Option<bool>,
-    pub selections: Option<bool>,
-}
-
-impl Settings for EditorSettings {
-    const KEY: Option<&'static str> = None;
-
-    type FileContent = EditorSettingsContent;
-
-    fn load(
-        default_value: &Self::FileContent,
-        user_values: &[&Self::FileContent],
-        _: &mut gpui::AppContext,
-    ) -> anyhow::Result<Self> {
-        Self::load_via_json_merge(default_value, user_values)
-    }
-}

crates/editor2/src/editor_tests.rs 🔗

@@ -1,8268 +0,0 @@
-use super::*;
-use crate::{
-    scroll::scroll_amount::ScrollAmount,
-    test::{
-        assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
-        editor_test_context::EditorTestContext, select_ranges,
-    },
-    JoinLines,
-};
-
-use futures::StreamExt;
-use gpui::{
-    div,
-    serde_json::{self, json},
-    TestAppContext, VisualTestContext, WindowBounds, WindowOptions,
-};
-use indoc::indoc;
-use language::{
-    language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent},
-    BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageRegistry,
-    Override, Point,
-};
-use parking_lot::Mutex;
-use project::project_settings::{LspSettings, ProjectSettings};
-use project::FakeFs;
-use std::sync::atomic;
-use std::sync::atomic::AtomicUsize;
-use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
-use unindent::Unindent;
-use util::{
-    assert_set_eq,
-    test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
-};
-use workspace::{
-    item::{FollowEvent, FollowableItem, Item, ItemHandle},
-    NavigationEntry, ViewId,
-};
-
-#[gpui::test]
-fn test_edit_events(cx: &mut TestAppContext) {
-    init_test(cx, |_| {});
-
-    let buffer = cx.new_model(|cx| {
-        let mut buffer = language::Buffer::new(0, cx.entity_id().as_u64(), "123456");
-        buffer.set_group_interval(Duration::from_secs(1));
-        buffer
-    });
-
-    let events = Rc::new(RefCell::new(Vec::new()));
-    let editor1 = cx.add_window({
-        let events = events.clone();
-        |cx| {
-            let view = cx.view().clone();
-            cx.subscribe(&view, move |_, _, event: &EditorEvent, _| {
-                if matches!(event, EditorEvent::Edited | EditorEvent::BufferEdited) {
-                    events.borrow_mut().push(("editor1", event.clone()));
-                }
-            })
-            .detach();
-            Editor::for_buffer(buffer.clone(), None, cx)
-        }
-    });
-
-    let editor2 = cx.add_window({
-        let events = events.clone();
-        |cx| {
-            cx.subscribe(&cx.view().clone(), move |_, _, event: &EditorEvent, _| {
-                if matches!(event, EditorEvent::Edited | EditorEvent::BufferEdited) {
-                    events.borrow_mut().push(("editor2", event.clone()));
-                }
-            })
-            .detach();
-            Editor::for_buffer(buffer.clone(), None, cx)
-        }
-    });
-
-    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
-
-    // Mutating editor 1 will emit an `Edited` event only for that editor.
-    _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
-    assert_eq!(
-        mem::take(&mut *events.borrow_mut()),
-        [
-            ("editor1", EditorEvent::Edited),
-            ("editor1", EditorEvent::BufferEdited),
-            ("editor2", EditorEvent::BufferEdited),
-        ]
-    );
-
-    // 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", EditorEvent::Edited),
-            ("editor1", EditorEvent::BufferEdited),
-            ("editor2", EditorEvent::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", EditorEvent::Edited),
-            ("editor1", EditorEvent::BufferEdited),
-            ("editor2", EditorEvent::BufferEdited),
-        ]
-    );
-
-    // 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", EditorEvent::Edited),
-            ("editor1", EditorEvent::BufferEdited),
-            ("editor2", EditorEvent::BufferEdited),
-        ]
-    );
-
-    // 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", EditorEvent::Edited),
-            ("editor1", EditorEvent::BufferEdited),
-            ("editor2", EditorEvent::BufferEdited),
-        ]
-    );
-
-    // 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", EditorEvent::Edited),
-            ("editor1", EditorEvent::BufferEdited),
-            ("editor2", EditorEvent::BufferEdited),
-        ]
-    );
-
-    // 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.new_model(|cx| language::Buffer::new(0, cx.entity_id().as_u64(), "123456"));
-    let group_interval = buffer.update(cx, |buffer, _| buffer.transaction_group_interval());
-    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
-    let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
-
-    _ = editor.update(cx, |editor, cx| {
-        editor.start_transaction_at(now, cx);
-        editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
-
-        editor.insert("cd", cx);
-        editor.end_transaction_at(now, cx);
-        assert_eq!(editor.text(cx), "12cd56");
-        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
-
-        editor.start_transaction_at(now, cx);
-        editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
-        editor.insert("e", cx);
-        editor.end_transaction_at(now, cx);
-        assert_eq!(editor.text(cx), "12cde6");
-        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
-
-        now += group_interval + Duration::from_millis(1);
-        editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
-
-        // Simulate an edit in another editor
-        _ = buffer.update(cx, |buffer, cx| {
-            buffer.start_transaction_at(now, cx);
-            buffer.edit([(0..1, "a")], None, cx);
-            buffer.edit([(1..1, "b")], None, cx);
-            buffer.end_transaction_at(now, cx);
-        });
-
-        assert_eq!(editor.text(cx), "ab2cde6");
-        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
-
-        // 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.new_model(|cx| {
-        let mut buffer = language::Buffer::new(0, cx.entity_id().as_u64(), "abcde");
-        // Ensure automatic grouping doesn't occur.
-        buffer.set_group_interval(Duration::ZERO);
-        buffer
-    });
-
-    let buffer = cx.new_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)
-    });
-
-    _ = editor.update(cx, |view, cx| {
-        view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
-    });
-    assert_eq!(
-        editor
-            .update(cx, |view, cx| view.selections.display_ranges(cx))
-            .unwrap(),
-        [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
-    );
-
-    _ = editor.update(cx, |view, cx| {
-        view.update_selection(
-            DisplayPoint::new(3, 3),
-            0,
-            gpui::Point::<f32>::default(),
-            cx,
-        );
-    });
-
-    assert_eq!(
-        editor
-            .update(cx, |view, cx| view.selections.display_ranges(cx))
-            .unwrap(),
-        [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
-    );
-
-    _ = editor.update(cx, |view, cx| {
-        view.update_selection(
-            DisplayPoint::new(1, 1),
-            0,
-            gpui::Point::<f32>::default(),
-            cx,
-        );
-    });
-
-    assert_eq!(
-        editor
-            .update(cx, |view, cx| view.selections.display_ranges(cx))
-            .unwrap(),
-        [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,
-            gpui::Point::<f32>::default(),
-            cx,
-        );
-    });
-
-    assert_eq!(
-        editor
-            .update(cx, |view, cx| view.selections.display_ranges(cx))
-            .unwrap(),
-        [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,
-            gpui::Point::<f32>::default(),
-            cx,
-        );
-    });
-
-    assert_eq!(
-        editor
-            .update(cx, |view, cx| view.selections.display_ranges(cx))
-            .unwrap(),
-        [
-            DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1),
-            DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)
-        ]
-    );
-
-    _ = editor.update(cx, |view, cx| {
-        view.end_selection(cx);
-    });
-
-    assert_eq!(
-        editor
-            .update(cx, |view, cx| view.selections.display_ranges(cx))
-            .unwrap(),
-        [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)
-    });
-
-    _ = 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,
-            gpui::Point::<f32>::default(),
-            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,
-            gpui::Point::<f32>::default(),
-            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)
-    });
-
-    _ = 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.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
-        })
-        .unwrap();
-
-    let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
-    let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
-
-    assert_eq!(
-        cloned_editor
-            .update(cx, |e, cx| e.display_text(cx))
-            .unwrap(),
-        editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
-    );
-    assert_eq!(
-        cloned_snapshot
-            .folds_in_range(0..text.len())
-            .collect::<Vec<_>>(),
-        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
-    );
-    assert_set_eq!(
-        cloned_editor
-            .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
-            .unwrap(),
-        editor
-            .update(cx, |editor, cx| editor.selections.ranges(cx))
-            .unwrap()
-    );
-    assert_set_eq!(
-        cloned_editor
-            .update(cx, |e, cx| e.selections.display_ranges(cx))
-            .unwrap(),
-        editor
-            .update(cx, |e, cx| e.selections.display_ranges(cx))
-            .unwrap()
-    );
-}
-
-//todo!(editor navigate)
-#[gpui::test]
-async fn test_navigation_history(cx: &mut TestAppContext) {
-    init_test(cx, |_| {});
-
-    use workspace::item::Item;
-
-    let fs = FakeFs::new(cx.executor());
-    let project = Project::test(fs, [], cx).await;
-    let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
-    let pane = workspace
-        .update(cx, |workspace, _| workspace.active_pane().clone())
-        .unwrap();
-
-    _ = workspace.update(cx, |_v, cx| {
-        cx.new_view(|cx| {
-            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
-            let mut editor = build_editor(buffer.clone(), cx);
-            let handle = cx.view();
-            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
-
-            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.entity_id());
-            assert_eq!(
-                editor.selections.display_ranges(cx),
-                &[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)]
-            );
-            assert!(pop_history(&mut editor, cx).is_none());
-
-            // 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.entity_id());
-            assert_eq!(
-                editor.selections.display_ranges(cx),
-                &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
-            );
-            assert!(pop_history(&mut editor, cx).is_none());
-
-            // Set scroll position to check later
-            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
-            let original_scroll_position = editor.scroll_manager.anchor();
-
-            // Jump to the end of the document and adjust scroll
-            editor.move_to_end(&MoveToEnd, cx);
-            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
-            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
-
-            let nav_entry = pop_history(&mut editor, cx).unwrap();
-            editor.navigate(nav_entry.data.unwrap(), cx);
-            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
-
-            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
-            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
-            invalid_anchor.text_anchor.buffer_id = Some(999);
-            let invalid_point = Point::new(9999, 0);
-            editor.navigate(
-                Box::new(NavigationData {
-                    cursor_anchor: invalid_anchor,
-                    cursor_position: invalid_point,
-                    scroll_anchor: ScrollAnchor {
-                        anchor: invalid_anchor,
-                        offset: Default::default(),
-                    },
-                    scroll_top_row: invalid_point.row,
-                }),
-                cx,
-            );
-            assert_eq!(
-                editor.selections.display_ranges(cx),
-                &[editor.max_point(cx)..editor.max_point(cx)]
-            );
-            assert_eq!(
-                editor.scroll_position(cx),
-                gpui::Point::new(0., editor.max_point(cx).row() as f32)
-            );
-
-            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)
-    });
-
-    _ = view.update(cx, |view, cx| {
-        view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx);
-        view.update_selection(
-            DisplayPoint::new(1, 1),
-            0,
-            gpui::Point::<f32>::default(),
-            cx,
-        );
-        view.end_selection(cx);
-
-        view.begin_selection(DisplayPoint::new(0, 1), true, 1, cx);
-        view.update_selection(
-            DisplayPoint::new(0, 3),
-            0,
-            gpui::Point::<f32>::default(),
-            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)
-    });
-
-    _ = 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));
-
-    _ = 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)
-    });
-
-    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())]
-        );
-    });
-}
-
-//todo!(finish editor tests)
-#[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)
-    });
-    _ = 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)
-    });
-    _ = 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)
-    });
-    _ = view.update(cx, |view, cx| {
-        view.change_selections(None, cx, |s| {
-            s.select_display_ranges([
-                DisplayPoint::new(0, 11)..DisplayPoint::new(0, 11),
-                DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4),
-            ])
-        });
-
-        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
-        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", view, cx);
-
-        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
-        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n  ˇ{baz.qux()}", view, cx);
-
-        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
-        assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ  {baz.qux()}", view, cx);
-
-        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
-        assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n  {baz.qux()}", view, cx);
-
-        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
-        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", view, cx);
-
-        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
-        assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n  {baz.qux()}", view, cx);
-
-        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
-        assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n  {baz.qux()}", view, cx);
-
-        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
-        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", view, cx);
-
-        view.move_right(&MoveRight, cx);
-        view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
-        assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}", view, cx);
-
-        view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
-        assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n  «ˇ{b»az.qux()}", view, cx);
-
-        view.select_to_next_word_end(&SelectToNextWordEnd, cx);
-        assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}", view, cx);
-    });
-}
-
-//todo!(finish editor tests)
-#[gpui::test]
-fn test_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)
-    });
-
-    _ = view.update(cx, |view, cx| {
-        view.set_wrap_width(Some(140.0.into()), 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)]
-        );
-    });
-}
-
-//todo!(simulate_resize)
-#[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()
-            .unwrap()
-            .text
-            .line_height_in_pixels(cx.rem_size())
-    });
-    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
-
-    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()
-            .unwrap()
-            .text
-            .line_height_in_pixels(cx.rem_size())
-    });
-    let window = cx.window;
-    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
-
-    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(),
-            gpui::Point::new(0., 0.)
-        );
-        editor.scroll_screen(&ScrollAmount::Page(1.), cx);
-        assert_eq!(
-            editor.snapshot(cx).scroll_position(),
-            gpui::Point::new(0., 3.)
-        );
-        editor.scroll_screen(&ScrollAmount::Page(1.), cx);
-        assert_eq!(
-            editor.snapshot(cx).scroll_position(),
-            gpui::Point::new(0., 6.)
-        );
-        editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
-        assert_eq!(
-            editor.snapshot(cx).scroll_position(),
-            gpui::Point::new(0., 3.)
-        );
-
-        editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
-        assert_eq!(
-            editor.snapshot(cx).scroll_position(),
-            gpui::Point::new(0., 1.)
-        );
-        editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
-        assert_eq!(
-            editor.snapshot(cx).scroll_position(),
-            gpui::Point::new(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()
-            .unwrap()
-            .text
-            .line_height_in_pixels(cx.rem_size())
-    });
-    let window = cx.window;
-    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
-
-    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(),
-            gpui::Point::new(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(),
-            gpui::Point::new(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(),
-            gpui::Point::new(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(),
-            gpui::Point::new(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()
-            .unwrap()
-            .text
-            .line_height_in_pixels(cx.rem_size())
-    });
-    let window = cx.window;
-    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
-    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)
-    });
-
-    _ = 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)
-    });
-
-    _ = 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
-    });
-
-    _ = 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
-    });
-
-    _ = 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.new_model(|cx| {
-        Buffer::new(0, cx.entity_id().as_u64(), "a = 1\nb = 2\n").with_language(toml_language, cx)
-    });
-    let rust_buffer = cx.new_model(|cx| {
-        Buffer::new(0, cx.entity_id().as_u64(), "const c: usize = 3;\n")
-            .with_language(rust_language, cx)
-    });
-    let multibuffer = cx.new_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)
-    });
-    _ = 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)
-    });
-    _ = view.update(cx, |view, cx| {
-        view.change_selections(None, cx, |s| {
-            s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)])
-        });
-        view.delete_line(&DeleteLine, cx);
-        assert_eq!(view.display_text(cx), "ghi\n");
-        assert_eq!(
-            view.selections.display_ranges(cx),
-            vec![DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)]
-        );
-    });
-}
-
-//todo!(select_anchor_ranges)
-#[gpui::test]
-fn test_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)
-    });
-    _ = view.update(cx, |view, cx| {
-        view.change_selections(None, cx, |s| {
-            s.select_display_ranges([
-                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
-                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
-                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
-                DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
-            ])
-        });
-        view.duplicate_line(&DuplicateLine, cx);
-        assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
-        assert_eq!(
-            view.selections.display_ranges(cx),
-            vec![
-                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
-                DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
-                DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
-                DisplayPoint::new(6, 0)..DisplayPoint::new(6, 0),
-            ]
-        );
-    });
-
-    let view = cx.add_window(|cx| {
-        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
-        build_editor(buffer, cx)
-    });
-    _ = view.update(cx, |view, cx| {
-        view.change_selections(None, cx, |s| {
-            s.select_display_ranges([
-                DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
-                DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
-            ])
-        });
-        view.duplicate_line(&DuplicateLine, cx);
-        assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
-        assert_eq!(
-            view.selections.display_ranges(cx),
-            vec![
-                DisplayPoint::new(3, 1)..DisplayPoint::new(4, 1),
-                DisplayPoint::new(4, 2)..DisplayPoint::new(5, 1),
-            ]
-        );
-    });
-}
-
-#[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)
-    });
-    _ = 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)
-    });
-    _ = editor.update(cx, |editor, cx| {
-        let snapshot = editor.buffer.read(cx).snapshot(cx);
-        editor.insert_blocks(
-            [BlockProperties {
-                style: BlockStyle::Fixed,
-                position: snapshot.anchor_after(Point::new(2, 0)),
-                disposition: BlockDisposition::Below,
-                height: 1,
-                render: Arc::new(|_| div().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);
-    });
-}
-
-//todo!(test_transpose)
-#[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.set_style(EditorStyle::default(), 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.set_style(EditorStyle::default(), 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.set_style(EditorStyle::default(), 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.set_style(EditorStyle::default(), 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));
-    assert_eq!(
-        cx.read_from_clipboard().map(|item| item.text().to_owned()),
-        Some("fox jumps over\n".to_owned())
-    );
-
-    // 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)
-    });
-    _ = 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)
-    });
-    _ = 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)
-    });
-    _ = view.update(cx, |view, cx| {
-        view.fold_ranges(
-            vec![
-                Point::new(0, 2)..Point::new(1, 2),
-                Point::new(2, 3)..Point::new(4, 1),
-                Point::new(7, 0)..Point::new(8, 4),
-            ],
-            true,
-            cx,
-        );
-        view.change_selections(None, cx, |s| {
-            s.select_display_ranges([
-                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
-                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
-                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
-                DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
-            ])
-        });
-        assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
-    });
-
-    _ = view.update(cx, |view, cx| {
-        view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
-        assert_eq!(
-            view.display_text(cx),
-            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
-        );
-        assert_eq!(
-            view.selections.display_ranges(cx),
-            [
-                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
-                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
-                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
-                DisplayPoint::new(5, 4)..DisplayPoint::new(5, 4)
-            ]
-        );
-    });
-
-    _ = view.update(cx, |view, cx| {
-        view.change_selections(None, cx, |s| {
-            s.select_display_ranges([DisplayPoint::new(5, 0)..DisplayPoint::new(0, 1)])
-        });
-        view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
-        assert_eq!(
-            view.display_text(cx),
-            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
-        );
-        assert_eq!(
-            view.selections.display_ranges(cx),
-            [
-                DisplayPoint::new(0, 5)..DisplayPoint::new(0, 5),
-                DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
-                DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
-                DisplayPoint::new(3, 5)..DisplayPoint::new(3, 5),
-                DisplayPoint::new(4, 5)..DisplayPoint::new(4, 5),
-                DisplayPoint::new(5, 5)..DisplayPoint::new(5, 5),
-                DisplayPoint::new(6, 5)..DisplayPoint::new(6, 5),
-                DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0)
-            ]
-        );
-    });
-}
-
-#[gpui::test]
-async fn test_add_selection_above_below(cx: &mut TestAppContext) {
-    init_test(cx, |_| {});
-
-    let mut cx = EditorTestContext::new(cx).await;
-
-    // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
-    cx.set_state(indoc!(
-        r#"abc
-           defˇghi
-
-           jk
-           nlmo
-           "#
-    ));
-
-    cx.update_editor(|editor, cx| {
-        editor.add_selection_above(&Default::default(), cx);
-    });
-
-    cx.assert_editor_state(indoc!(
-        r#"abcˇ
-           defˇghi
-
-           jk
-           nlmo
-           "#
-    ));
-
-    cx.update_editor(|editor, cx| {
-        editor.add_selection_above(&Default::default(), cx);
-    });
-
-    cx.assert_editor_state(indoc!(
-        r#"abcˇ
-            defˇghi
-
-            jk
-            nlmo
-            "#
-    ));
-
-    cx.update_editor(|view, cx| {
-        view.add_selection_below(&Default::default(), cx);
-    });
-
-    cx.assert_editor_state(indoc!(
-        r#"abc
-           defˇghi
-
-           jk
-           nlmo
-           "#
-    ));
-
-    cx.update_editor(|view, cx| {
-        view.undo_selection(&Default::default(), cx);
-    });
-
-    cx.assert_editor_state(indoc!(
-        r#"abcˇ
-           defˇghi
-
-           jk
-           nlmo
-           "#
-    ));
-
-    cx.update_editor(|view, cx| {
-        view.redo_selection(&Default::default(), cx);
-    });
-
-    cx.assert_editor_state(indoc!(
-        r#"abc
-           defˇghi
-
-           jk
-           nlmo
-           "#
-    ));
-
-    cx.update_editor(|view, cx| {
-        view.add_selection_below(&Default::default(), cx);
-    });
-
-    cx.assert_editor_state(indoc!(
-        r#"abc
-           defˇghi
-
-           jk
-           nlmˇo
-           "#
-    ));
-
-    cx.update_editor(|view, cx| {
-        view.add_selection_below(&Default::default(), cx);
-    });
-
-    cx.assert_editor_state(indoc!(
-        r#"abc
-           defˇghi
-
-           jk
-           nlmˇo
-           "#
-    ));
-
-    // change selections
-    cx.set_state(indoc!(
-        r#"abc
-           def«ˇg»hi
-
-           jk
-           nlmo
-           "#
-    ));
-
-    cx.update_editor(|view, cx| {
-        view.add_selection_below(&Default::default(), cx);
-    });
-
-    cx.assert_editor_state(indoc!(
-        r#"abc
-           def«ˇg»hi
-
-           jk
-           nlm«ˇo»
-           "#
-    ));
-
-    cx.update_editor(|view, cx| {
-        view.add_selection_below(&Default::default(), cx);
-    });
-
-    cx.assert_editor_state(indoc!(
-        r#"abc
-           def«ˇg»hi
-
-           jk
-           nlm«ˇo»
-           "#
-    ));
-
-    cx.update_editor(|view, cx| {
-        view.add_selection_above(&Default::default(), cx);
-    });
-
-    cx.assert_editor_state(indoc!(
-        r#"abc
-           def«ˇg»hi
-
-           jk
-           nlmo
-           "#
-    ));
-
-    cx.update_editor(|view, cx| {
-        view.add_selection_above(&Default::default(), cx);
-    });
-
-    cx.assert_editor_state(indoc!(
-        r#"abc
-           def«ˇg»hi
-
-           jk
-           nlmo
-           "#
-    ));
-
-    // Change selections again
-    cx.set_state(indoc!(
-        r#"a«bc
-           defgˇ»hi
-
-           jk
-           nlmo
-           "#
-    ));
-
-    cx.update_editor(|view, cx| {
-        view.add_selection_below(&Default::default(), cx);
-    });
-
-    cx.assert_editor_state(indoc!(
-        r#"a«bcˇ»
-           d«efgˇ»hi
-
-           j«kˇ»
-           nlmo
-           "#
-    ));
-
-    cx.update_editor(|view, cx| {
-        view.add_selection_below(&Default::default(), cx);
-    });
-    cx.assert_editor_state(indoc!(
-        r#"a«bcˇ»
-           d«efgˇ»hi
-
-           j«kˇ»
-           n«lmoˇ»
-           "#
-    ));
-    cx.update_editor(|view, cx| {
-        view.add_selection_above(&Default::default(), cx);
-    });
-
-    cx.assert_editor_state(indoc!(
-        r#"a«bcˇ»
-           d«efgˇ»hi
-
-           j«kˇ»
-           nlmo
-           "#
-    ));
-
-    // Change selections again
-    cx.set_state(indoc!(
-        r#"abc
-           d«ˇefghi
-
-           jk
-           nlm»o
-           "#
-    ));
-
-    cx.update_editor(|view, cx| {
-        view.add_selection_above(&Default::default(), cx);
-    });
-
-    cx.assert_editor_state(indoc!(
-        r#"a«ˇbc»
-           d«ˇef»ghi
-
-           j«ˇk»
-           n«ˇlm»o
-           "#
-    ));
-
-    cx.update_editor(|view, cx| {
-        view.add_selection_below(&Default::default(), cx);
-    });
-
-    cx.assert_editor_state(indoc!(
-        r#"abc
-           d«ˇef»ghi
-
-           j«ˇk»
-           n«ˇlm»o
-           "#
-    ));
-}
-
-#[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
-        .new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx));
-    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
-    let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
-
-    view.condition::<crate::EditorEvent>(&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
-        .new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx));
-    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
-    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
-    editor
-        .condition::<crate::EditorEvent>(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
-        .new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx));
-    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
-    let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
-    view.condition::<crate::EditorEvent>(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
-        .new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx));
-    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
-    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
-    editor
-        .condition::<crate::EditorEvent>(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)
-            ]
-        );
-    });
-}
-
-// todo!(select_anchor_ranges)
-#[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) = cx.add_window_view(|cx| build_editor(buffer, cx));
-
-    _ = editor.update(cx, |editor, cx| {
-        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
-
-        editor
-            .insert_snippet(&insertion_ranges, snippet, cx)
-            .unwrap();
-
-        fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
-            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
-            assert_eq!(editor.text(cx), expected_text);
-            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
-        }
-
-        assert(
-            editor,
-            cx,
-            indoc! {"
-                a.f(«one», two, «three») b
-                a.f(«one», two, «three») b
-                a.f(«one», two, «three») b
-            "},
-        );
-
-        // Can't move earlier than the first tab stop
-        assert!(!editor.move_to_prev_snippet_tabstop(cx));
-        assert(
-            editor,
-            cx,
-            indoc! {"
-                a.f(«one», two, «three») b
-                a.f(«one», two, «three») b
-                a.f(«one», two, «three») b
-            "},
-        );
-
-        assert!(editor.move_to_next_snippet_tabstop(cx));
-        assert(
-            editor,
-            cx,
-            indoc! {"
-                a.f(one, «two», three) b
-                a.f(one, «two», three) b
-                a.f(one, «two», three) b
-            "},
-        );
-
-        editor.move_to_prev_snippet_tabstop(cx);
-        assert(
-            editor,
-            cx,
-            indoc! {"
-                a.f(«one», two, «three») b
-                a.f(«one», two, «three») b
-                a.f(«one», two, «three») b
-            "},
-        );
-
-        assert!(editor.move_to_next_snippet_tabstop(cx));
-        assert(
-            editor,
-            cx,
-            indoc! {"
-                a.f(one, «two», three) b
-                a.f(one, «two», three) b
-                a.f(one, «two», three) b
-            "},
-        );
-        assert!(editor.move_to_next_snippet_tabstop(cx));
-        assert(
-            editor,
-            cx,
-            indoc! {"
-                a.f(one, two, three)ˇ b
-                a.f(one, two, three)ˇ b
-                a.f(one, two, three)ˇ b
-            "},
-        );
-
-        // As soon as the last tab stop is reached, snippet state is gone
-        editor.move_to_prev_snippet_tabstop(cx);
-        assert(
-            editor,
-            cx,
-            indoc! {"
-                a.f(one, two, three)ˇ b
-                a.f(one, two, three)ˇ b
-                a.f(one, two, three)ˇ b
-            "},
-        );
-    });
-}
-
-#[gpui::test]
-async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
-    init_test(cx, |_| {});
-
-    let mut language = Language::new(
-        LanguageConfig {
-            name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
-            ..Default::default()
-        },
-        Some(tree_sitter_rust::language()),
-    );
-    let mut fake_servers = language
-        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-            capabilities: lsp::ServerCapabilities {
-                document_formatting_provider: Some(lsp::OneOf::Left(true)),
-                ..Default::default()
-            },
-            ..Default::default()
-        }))
-        .await;
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_file("/file.rs", Default::default()).await;
-
-    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
-    _ = project.update(cx, |project, _| project.languages().add(Arc::new(language)));
-    let buffer = project
-        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
-        .await
-        .unwrap();
-
-    cx.executor().start_waiting();
-    let fake_server = fake_servers.next().await.unwrap();
-
-    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
-    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
-    _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
-    assert!(cx.read(|cx| editor.is_dirty(cx)));
-
-    let save = editor
-        .update(cx, |editor, cx| editor.save(project.clone(), cx))
-        .unwrap();
-    fake_server
-        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
-            assert_eq!(
-                params.text_document.uri,
-                lsp::Url::from_file_path("/file.rs").unwrap()
-            );
-            assert_eq!(params.options.tab_size, 4);
-            Ok(Some(vec![lsp::TextEdit::new(
-                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
-                ", ".to_string(),
-            )]))
-        })
-        .next()
-        .await;
-    cx.executor().start_waiting();
-    let _x = save.await;
-
-    assert_eq!(
-        editor.update(cx, |editor, cx| editor.text(cx)),
-        "one, two\nthree\n"
-    );
-    assert!(!cx.read(|cx| editor.is_dirty(cx)));
-
-    _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
-    assert!(cx.read(|cx| editor.is_dirty(cx)));
-
-    // Ensure we can still save even if formatting hangs.
-    fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
-        assert_eq!(
-            params.text_document.uri,
-            lsp::Url::from_file_path("/file.rs").unwrap()
-        );
-        futures::future::pending::<()>().await;
-        unreachable!()
-    });
-    let save = editor
-        .update(cx, |editor, cx| editor.save(project.clone(), cx))
-        .unwrap();
-    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
-    cx.executor().start_waiting();
-    save.await;
-    assert_eq!(
-        editor.update(cx, |editor, cx| editor.text(cx)),
-        "one\ntwo\nthree\n"
-    );
-    assert!(!cx.read(|cx| editor.is_dirty(cx)));
-
-    // Set rust language override and assert overridden tabsize is sent to language server
-    update_test_language_settings(cx, |settings| {
-        settings.languages.insert(
-            "Rust".into(),
-            LanguageSettingsContent {
-                tab_size: NonZeroU32::new(8),
-                ..Default::default()
-            },
-        );
-    });
-
-    let save = editor
-        .update(cx, |editor, cx| editor.save(project.clone(), cx))
-        .unwrap();
-    fake_server
-        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
-            assert_eq!(
-                params.text_document.uri,
-                lsp::Url::from_file_path("/file.rs").unwrap()
-            );
-            assert_eq!(params.options.tab_size, 8);
-            Ok(Some(vec![]))
-        })
-        .next()
-        .await;
-    cx.executor().start_waiting();
-    save.await;
-}
-
-#[gpui::test]
-async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
-    init_test(cx, |_| {});
-
-    let mut language = Language::new(
-        LanguageConfig {
-            name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
-            ..Default::default()
-        },
-        Some(tree_sitter_rust::language()),
-    );
-    let mut fake_servers = language
-        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-            capabilities: lsp::ServerCapabilities {
-                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
-                ..Default::default()
-            },
-            ..Default::default()
-        }))
-        .await;
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_file("/file.rs", Default::default()).await;
-
-    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
-    _ = project.update(cx, |project, _| project.languages().add(Arc::new(language)));
-    let buffer = project
-        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
-        .await
-        .unwrap();
-
-    cx.executor().start_waiting();
-    let fake_server = fake_servers.next().await.unwrap();
-
-    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
-    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
-    _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
-    assert!(cx.read(|cx| editor.is_dirty(cx)));
-
-    let save = editor
-        .update(cx, |editor, cx| editor.save(project.clone(), cx))
-        .unwrap();
-    fake_server
-        .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
-            assert_eq!(
-                params.text_document.uri,
-                lsp::Url::from_file_path("/file.rs").unwrap()
-            );
-            assert_eq!(params.options.tab_size, 4);
-            Ok(Some(vec![lsp::TextEdit::new(
-                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
-                ", ".to_string(),
-            )]))
-        })
-        .next()
-        .await;
-    cx.executor().start_waiting();
-    save.await;
-    assert_eq!(
-        editor.update(cx, |editor, cx| editor.text(cx)),
-        "one, two\nthree\n"
-    );
-    assert!(!cx.read(|cx| editor.is_dirty(cx)));
-
-    _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
-    assert!(cx.read(|cx| editor.is_dirty(cx)));
-
-    // Ensure we can still save even if formatting hangs.
-    fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
-        move |params, _| async move {
-            assert_eq!(
-                params.text_document.uri,
-                lsp::Url::from_file_path("/file.rs").unwrap()
-            );
-            futures::future::pending::<()>().await;
-            unreachable!()
-        },
-    );
-    let save = editor
-        .update(cx, |editor, cx| editor.save(project.clone(), cx))
-        .unwrap();
-    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
-    cx.executor().start_waiting();
-    save.await;
-    assert_eq!(
-        editor.update(cx, |editor, cx| editor.text(cx)),
-        "one\ntwo\nthree\n"
-    );
-    assert!(!cx.read(|cx| editor.is_dirty(cx)));
-
-    // Set rust language override and assert overridden tabsize is sent to language server
-    update_test_language_settings(cx, |settings| {
-        settings.languages.insert(
-            "Rust".into(),
-            LanguageSettingsContent {
-                tab_size: NonZeroU32::new(8),
-                ..Default::default()
-            },
-        );
-    });
-
-    let save = editor
-        .update(cx, |editor, cx| editor.save(project.clone(), cx))
-        .unwrap();
-    fake_server
-        .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
-            assert_eq!(
-                params.text_document.uri,
-                lsp::Url::from_file_path("/file.rs").unwrap()
-            );
-            assert_eq!(params.options.tab_size, 8);
-            Ok(Some(vec![]))
-        })
-        .next()
-        .await;
-    cx.executor().start_waiting();
-    save.await;
-}
-
-#[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.executor());
-    fs.insert_file("/file.rs", Default::default()).await;
-
-    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
-    _ = project.update(cx, |project, _| {
-        project.languages().add(Arc::new(language));
-    });
-    let buffer = project
-        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
-        .await
-        .unwrap();
-
-    cx.executor().start_waiting();
-    let fake_server = fake_servers.next().await.unwrap();
-
-    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
-    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
-    _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
-
-    let format = editor
-        .update(cx, |editor, cx| {
-            editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
-        })
-        .unwrap();
-    fake_server
-        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
-            assert_eq!(
-                params.text_document.uri,
-                lsp::Url::from_file_path("/file.rs").unwrap()
-            );
-            assert_eq!(params.options.tab_size, 4);
-            Ok(Some(vec![lsp::TextEdit::new(
-                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
-                ", ".to_string(),
-            )]))
-        })
-        .next()
-        .await;
-    cx.executor().start_waiting();
-    format.await;
-    assert_eq!(
-        editor.update(cx, |editor, cx| editor.text(cx)),
-        "one, two\nthree\n"
-    );
-
-    _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
-    // Ensure we don't lock if formatting hangs.
-    fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
-        assert_eq!(
-            params.text_document.uri,
-            lsp::Url::from_file_path("/file.rs").unwrap()
-        );
-        futures::future::pending::<()>().await;
-        unreachable!()
-    });
-    let format = editor
-        .update(cx, |editor, cx| {
-            editor.perform_format(project, FormatTrigger::Manual, cx)
-        })
-        .unwrap();
-    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
-    cx.executor().start_waiting();
-    format.await;
-    assert_eq!(
-        editor.update(cx, |editor, cx| editor.text(cx)),
-        "one\ntwo\nthree\n"
-    );
-}
-
-#[gpui::test]
-async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
-    init_test(cx, |_| {});
-
-    let mut cx = EditorLspTestContext::new_rust(
-        lsp::ServerCapabilities {
-            document_formatting_provider: Some(lsp::OneOf::Left(true)),
-            ..Default::default()
-        },
-        cx,
-    )
-    .await;
-
-    cx.set_state(indoc! {"
-        one.twoˇ
-    "});
-
-    // The format request takes a long time. When it completes, it inserts
-    // a newline and an indent before the `.`
-    cx.lsp
-        .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
-            let executor = cx.background_executor().clone();
-            async move {
-                executor.timer(Duration::from_millis(100)).await;
-                Ok(Some(vec![lsp::TextEdit {
-                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
-                    new_text: "\n    ".into(),
-                }]))
-            }
-        });
-
-    // Submit a format request.
-    let format_1 = cx
-        .update_editor(|editor, cx| editor.format(&Format, cx))
-        .unwrap();
-    cx.executor().run_until_parked();
-
-    // Submit a second format request.
-    let format_2 = cx
-        .update_editor(|editor, cx| editor.format(&Format, cx))
-        .unwrap();
-    cx.executor().run_until_parked();
-
-    // Wait for both format requests to complete
-    cx.executor().advance_clock(Duration::from_millis(200));
-    cx.executor().start_waiting();
-    format_1.await.unwrap();
-    cx.executor().start_waiting();
-    format_2.await.unwrap();
-
-    // The formatting edits only happens once.
-    cx.assert_editor_state(indoc! {"
-        one
-            .twoˇ
-    "});
-}
-
-#[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.executor().run_until_parked();
-    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
-    cx.assert_editor_state(
-        &r#"
-            <!-- ˇ<script> -->
-                // ˇvar x = new Y();
-            <!-- ˇ</script> -->
-        "#
-        .unindent(),
-    );
-}
-
-#[gpui::test]
-fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
-    init_test(cx, |_| {});
-
-    let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
-    let multibuffer = cx.new_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) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
-    _ = view.update(cx, |view, cx| {
-        assert_eq!(view.text(cx), "aaaa\nbbbb");
-        view.change_selections(None, cx, |s| {
-            s.select_ranges([
-                Point::new(0, 0)..Point::new(0, 0),
-                Point::new(1, 0)..Point::new(1, 0),
-            ])
-        });
-
-        view.handle_input("X", cx);
-        assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
-        assert_eq!(
-            view.selections.ranges(cx),
-            [
-                Point::new(0, 1)..Point::new(0, 1),
-                Point::new(1, 1)..Point::new(1, 1),
-            ]
-        );
-
-        // 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.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), initial_text));
-    let multibuffer = cx.new_model(|cx| {
-        let mut multibuffer = MultiBuffer::new(0);
-        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
-        multibuffer
-    });
-
-    let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
-    _ = view.update(cx, |view, cx| {
-        let (expected_text, selection_ranges) = marked_text_ranges(
-            indoc! {"
-                aaaa
-                bˇbbb
-                bˇbbˇb
-                cccc"
-            },
-            true,
-        );
-        assert_eq!(view.text(cx), expected_text);
-        view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
-
-        view.handle_input("X", cx);
-
-        let (expected_text, expected_selections) = marked_text_ranges(
-            indoc! {"
-                aaaa
-                bXˇbbXb
-                bXˇbbXˇb
-                cccc"
-            },
-            false,
-        );
-        assert_eq!(view.text(cx), expected_text);
-        assert_eq!(view.selections.ranges(cx), expected_selections);
-
-        view.newline(&Newline, cx);
-        let (expected_text, expected_selections) = marked_text_ranges(
-            indoc! {"
-                aaaa
-                bX
-                ˇbbX
-                b
-                bX
-                ˇbbX
-                ˇb
-                cccc"
-            },
-            false,
-        );
-        assert_eq!(view.text(cx), expected_text);
-        assert_eq!(view.selections.ranges(cx), expected_selections);
-    });
-}
-
-#[gpui::test]
-fn test_refresh_selections(cx: &mut TestAppContext) {
-    init_test(cx, |_| {});
-
-    let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
-    let mut excerpt1_id = None;
-    let multibuffer = cx.new_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
-    });
-
-    // 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.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
-    let mut excerpt1_id = None;
-    let multibuffer = cx.new_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
-    });
-
-    _ = 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
-        .new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx));
-    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
-    let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
-    view.condition::<crate::EditorEvent>(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)
-    });
-
-    _ = 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,
-            cx.theme().colors(),
-        );
-        // Enforce a consistent ordering based on color without relying on the ordering of the
-        // highlight's `TypeId` which is non-executor.
-        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
-        assert_eq!(
-            highlighted_ranges,
-            &[
-                (
-                    DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
-                    Hsla::red(),
-                ),
-                (
-                    DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
-                    Hsla::red(),
-                ),
-                (
-                    DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
-                    Hsla::green(),
-                ),
-                (
-                    DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
-                    Hsla::green(),
-                ),
-            ]
-        );
-        assert_eq!(
-            editor.background_highlights_in_range(
-                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
-                &snapshot,
-                cx.theme().colors(),
-            ),
-            &[(
-                DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
-                Hsla::red(),
-            )]
-        );
-    });
-}
-
-// todo!(following)
-#[gpui::test]
-async fn test_following(cx: &mut gpui::TestAppContext) {
-    init_test(cx, |_| {});
-
-    let fs = FakeFs::new(cx.executor());
-    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.new_model(|cx| MultiBuffer::singleton(buffer, cx))
-    });
-    let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
-    let follower = cx.update(|cx| {
-        cx.open_window(
-            WindowOptions {
-                bounds: WindowBounds::Fixed(Bounds::from_corners(
-                    gpui::Point::new((0. as f64).into(), (0. as f64).into()),
-                    gpui::Point::new((10. as f64).into(), (80. as f64).into()),
-                )),
-                ..Default::default()
-            },
-            |cx| cx.new_view(|cx| build_editor(buffer.clone(), 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.root_view(cx).unwrap(),
-                move |_, leader, event, cx| {
-                    leader
-                        .read(cx)
-                        .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
-                },
-            )
-            .detach();
-
-            cx.subscribe(
-                &follower.root_view(cx).unwrap(),
-                move |_, _, event: &EditorEvent, _cx| {
-                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
-                        *is_still_following.borrow_mut() = false;
-                    }
-
-                    if let EditorEvent::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)
-        })
-        .unwrap()
-        .await
-        .unwrap();
-    _ = follower.update(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(gpui::Point::new(1.5, 3.5), cx);
-    });
-    follower
-        .update(cx, |follower, cx| {
-            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
-        })
-        .unwrap()
-        .await
-        .unwrap();
-    assert_eq!(
-        follower
-            .update(cx, |follower, cx| follower.scroll_position(cx))
-            .unwrap(),
-        gpui::Point::new(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(gpui::Point::new(1.5, 3.5), cx);
-    });
-    follower
-        .update(cx, |follower, cx| {
-            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
-        })
-        .unwrap()
-        .await
-        .unwrap();
-    _ = follower.update(cx, |follower, cx| {
-        assert_eq!(follower.scroll_position(cx), gpui::Point::new(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)
-        })
-        .unwrap()
-        .await
-        .unwrap();
-    _ = follower.update(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)
-        })
-        .unwrap()
-        .await
-        .unwrap();
-    _ = follower.update(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: gpui::Point::new(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.executor());
-    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
-    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-    let pane = workspace
-        .update(cx, |workspace, _| workspace.active_pane().clone())
-        .unwrap();
-
-    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
-
-    let leader = pane.update(cx, |_, cx| {
-        let multibuffer = cx.new_model(|_| MultiBuffer::new(0));
-        cx.new_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_window(*workspace.deref(), |_, cx| {
-            Editor::from_state_proto(
-                pane.clone(),
-                workspace.root_view(cx).unwrap(),
-                ViewId {
-                    creator: Default::default(),
-                    id: 0,
-                },
-                &mut state_message,
-                cx,
-            )
-        })
-        .unwrap()
-        .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.update(cx, |editor, cx| editor.text(cx)),
-        leader.update(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_window(*workspace.deref(), |_, cx| {
-            Editor::from_state_proto(
-                pane.clone(),
-                workspace.root_view(cx).unwrap().clone(),
-                ViewId {
-                    creator: Default::default(),
-                    id: 0,
-                },
-                &mut state_message,
-                cx,
-            )
-        })
-        .unwrap()
-        .unwrap()
-        .await
-        .unwrap();
-    assert_eq!(
-        follower_2.update(cx, |editor, cx| editor.text(cx)),
-        leader.update(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.update(cx, |editor, cx| editor.text(cx)),
-        leader.update(cx, |editor, cx| editor.text(cx))
-    );
-}
-
-#[gpui::test]
-async fn go_to_prev_overlapping_diagnostic(
-    executor: BackgroundExecutor,
-    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()
-        });
-    });
-
-    executor.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(executor: BackgroundExecutor, 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));
-    executor.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')
-            }"},
-    );
-}
-
-// todo!(completions)
-#[gpui::test(iterations = 10)]
-async fn test_copilot(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
-    // flaky
-    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![],
-    );
-    executor.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![],
-    );
-    executor.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![],
-    );
-    executor.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");
-    executor.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![],
-    );
-    executor.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));
-    executor.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(
-    executor: BackgroundExecutor,
-    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));
-    executor.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(executor: BackgroundExecutor, 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.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "a = 1\nb = 2\n"));
-    let buffer_2 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "c = 3\nd = 4\n"));
-    let multibuffer = cx.new_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));
-
-    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);
-    });
-    executor.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.
-    executor.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(executor: BackgroundExecutor, 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.executor());
-    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.new_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));
-
-    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);
-    });
-
-    executor.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);
-    });
-
-    executor.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.executor());
-    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));
-
-    let cx = &mut VisualTestContext::from_window(*workspace, cx);
-
-    let worktree_id = workspace
-        .update(cx, |workspace, cx| {
-            workspace.project().update(cx, |project, cx| {
-                project.worktrees().next().unwrap().read(cx).id()
-            })
-        })
-        .unwrap();
-
-    let buffer = project
-        .update(cx, |project, cx| {
-            project.open_local_buffer("/a/main.rs", cx)
-        })
-        .await
-        .unwrap();
-    cx.executor().run_until_parked();
-    cx.executor().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)
-        })
-        .unwrap()
-        .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| {
-        editor.focus(cx);
-        editor.change_selections(None, cx, |s| {
-            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
-        });
-        editor.handle_input("{", cx);
-    });
-
-    cx.executor().run_until_parked();
-
-    _ = buffer.update(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.executor());
-    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.executor().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.executor().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.executor().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.executor().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.executor().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.executor().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.executor().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.executor().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.executor());
-    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.new_model(|cx| MultiBuffer::singleton(buffer, cx));
-    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
-    _ = editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
-
-    editor
-        .update(cx, |editor, cx| {
-            editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
-        })
-        .unwrap()
-        .await;
-    assert_eq!(
-        editor.update(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.update(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(|store: &mut SettingsStore, 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(|store: &mut SettingsStore, cx| {
-            store.update_user_settings::<ProjectSettings>(cx, f);
-        });
-    });
-}
-
-pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
-    _ = cx.update(|cx| {
-        let store = SettingsStore::test(cx);
-        cx.set_global(store);
-        theme::init(theme::LoadThemes::JustBase, 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/element.rs 🔗

@@ -1,3815 +0,0 @@
-use crate::{
-    display_map::{
-        BlockContext, BlockStyle, DisplaySnapshot, FoldStatus, HighlightedChunk, ToDisplayPoint,
-        TransformBlock,
-    },
-    editor_settings::ShowScrollbar,
-    git::{diff_hunk_to_display, DisplayDiffHunk},
-    hover_popover::{
-        self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
-    },
-    link_go_to_definition::{
-        go_to_fetched_definition, go_to_fetched_type_definition, show_link_definition,
-        update_go_to_definition_link, update_inlay_link_and_hover_points, GoToDefinitionTrigger,
-        LinkGoToDefinitionState,
-    },
-    mouse_context_menu,
-    scroll::scroll_amount::ScrollAmount,
-    CursorShape, DisplayPoint, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle,
-    HalfPageDown, HalfPageUp, LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point, SelectPhase,
-    Selection, SoftWrap, ToPoint, MAX_LINE_LEN,
-};
-use anyhow::Result;
-use collections::{BTreeMap, HashMap};
-use git::diff::DiffHunkStatus;
-use gpui::{
-    div, fill, outline, overlay, point, px, quad, relative, size, transparent_black, Action,
-    AnchorCorner, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners,
-    CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Hsla, InteractiveBounds,
-    InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, MouseDownEvent,
-    MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollWheelEvent, ShapedLine,
-    SharedString, Size, StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun,
-    TextStyle, View, ViewContext, WindowContext,
-};
-use itertools::Itertools;
-use language::language_settings::ShowWhitespaceSetting;
-use multi_buffer::Anchor;
-use project::{
-    project_settings::{GitGutterSetting, ProjectSettings},
-    ProjectPath,
-};
-use settings::Settings;
-use smallvec::SmallVec;
-use std::{
-    any::TypeId,
-    borrow::Cow,
-    cmp::{self, Ordering},
-    fmt::Write,
-    iter,
-    ops::Range,
-    sync::Arc,
-};
-use sum_tree::Bias;
-use theme::{ActiveTheme, PlayerColor};
-use ui::prelude::*;
-use ui::{h_stack, ButtonLike, ButtonStyle, IconButton, Tooltip};
-use util::ResultExt;
-use workspace::item::Item;
-
-struct SelectionLayout {
-    head: DisplayPoint,
-    cursor_shape: CursorShape,
-    is_newest: bool,
-    is_local: bool,
-    range: Range<DisplayPoint>,
-    active_rows: Range<u32>,
-}
-
-impl SelectionLayout {
-    fn new<T: ToPoint + ToDisplayPoint + Clone>(
-        selection: Selection<T>,
-        line_mode: bool,
-        cursor_shape: CursorShape,
-        map: &DisplaySnapshot,
-        is_newest: bool,
-        is_local: bool,
-    ) -> Self {
-        let point_selection = selection.map(|p| p.to_point(&map.buffer_snapshot));
-        let display_selection = point_selection.map(|p| p.to_display_point(map));
-        let mut range = display_selection.range();
-        let mut head = display_selection.head();
-        let mut active_rows = map.prev_line_boundary(point_selection.start).1.row()
-            ..map.next_line_boundary(point_selection.end).1.row();
-
-        // vim visual line mode
-        if line_mode {
-            let point_range = map.expand_to_line(point_selection.range());
-            range = point_range.start.to_display_point(map)..point_range.end.to_display_point(map);
-        }
-
-        // any vim visual mode (including line mode)
-        if cursor_shape == CursorShape::Block && !range.is_empty() && !selection.reversed {
-            if head.column() > 0 {
-                head = map.clip_point(DisplayPoint::new(head.row(), head.column() - 1), Bias::Left)
-            } else if head.row() > 0 && head != map.max_point() {
-                head = map.clip_point(
-                    DisplayPoint::new(head.row() - 1, map.line_len(head.row() - 1)),
-                    Bias::Left,
-                );
-                // updating range.end is a no-op unless you're cursor is
-                // on the newline containing a multi-buffer divider
-                // in which case the clip_point may have moved the head up
-                // an additional row.
-                range.end = DisplayPoint::new(head.row() + 1, 0);
-                active_rows.end = head.row();
-            }
-        }
-
-        Self {
-            head,
-            cursor_shape,
-            is_newest,
-            is_local,
-            range,
-            active_rows,
-        }
-    }
-}
-
-pub struct EditorElement {
-    editor: View<Editor>,
-    style: EditorStyle,
-}
-
-impl EditorElement {
-    pub fn new(editor: &View<Editor>, style: EditorStyle) -> Self {
-        Self {
-            editor: editor.clone(),
-            style,
-        }
-    }
-
-    fn register_actions(&self, cx: &mut WindowContext) {
-        let view = &self.editor;
-        view.update(cx, |editor, cx| {
-            for action in editor.editor_actions.iter() {
-                (action)(cx)
-            }
-        });
-
-        crate::rust_analyzer_ext::apply_related_actions(view, cx);
-        register_action(view, cx, Editor::move_left);
-        register_action(view, cx, Editor::move_right);
-        register_action(view, cx, Editor::move_down);
-        register_action(view, cx, Editor::move_up);
-        register_action(view, cx, Editor::cancel);
-        register_action(view, cx, Editor::newline);
-        register_action(view, cx, Editor::newline_above);
-        register_action(view, cx, Editor::newline_below);
-        register_action(view, cx, Editor::backspace);
-        register_action(view, cx, Editor::delete);
-        register_action(view, cx, Editor::tab);
-        register_action(view, cx, Editor::tab_prev);
-        register_action(view, cx, Editor::indent);
-        register_action(view, cx, Editor::outdent);
-        register_action(view, cx, Editor::delete_line);
-        register_action(view, cx, Editor::join_lines);
-        register_action(view, cx, Editor::sort_lines_case_sensitive);
-        register_action(view, cx, Editor::sort_lines_case_insensitive);
-        register_action(view, cx, Editor::reverse_lines);
-        register_action(view, cx, Editor::shuffle_lines);
-        register_action(view, cx, Editor::convert_to_upper_case);
-        register_action(view, cx, Editor::convert_to_lower_case);
-        register_action(view, cx, Editor::convert_to_title_case);
-        register_action(view, cx, Editor::convert_to_snake_case);
-        register_action(view, cx, Editor::convert_to_kebab_case);
-        register_action(view, cx, Editor::convert_to_upper_camel_case);
-        register_action(view, cx, Editor::convert_to_lower_camel_case);
-        register_action(view, cx, Editor::delete_to_previous_word_start);
-        register_action(view, cx, Editor::delete_to_previous_subword_start);
-        register_action(view, cx, Editor::delete_to_next_word_end);
-        register_action(view, cx, Editor::delete_to_next_subword_end);
-        register_action(view, cx, Editor::delete_to_beginning_of_line);
-        register_action(view, cx, Editor::delete_to_end_of_line);
-        register_action(view, cx, Editor::cut_to_end_of_line);
-        register_action(view, cx, Editor::duplicate_line);
-        register_action(view, cx, Editor::move_line_up);
-        register_action(view, cx, Editor::move_line_down);
-        register_action(view, cx, Editor::transpose);
-        register_action(view, cx, Editor::cut);
-        register_action(view, cx, Editor::copy);
-        register_action(view, cx, Editor::paste);
-        register_action(view, cx, Editor::undo);
-        register_action(view, cx, Editor::redo);
-        register_action(view, cx, Editor::move_page_up);
-        register_action(view, cx, Editor::move_page_down);
-        register_action(view, cx, Editor::next_screen);
-        register_action(view, cx, Editor::scroll_cursor_top);
-        register_action(view, cx, Editor::scroll_cursor_center);
-        register_action(view, cx, Editor::scroll_cursor_bottom);
-        register_action(view, cx, |editor, _: &LineDown, cx| {
-            editor.scroll_screen(&ScrollAmount::Line(1.), cx)
-        });
-        register_action(view, cx, |editor, _: &LineUp, cx| {
-            editor.scroll_screen(&ScrollAmount::Line(-1.), cx)
-        });
-        register_action(view, cx, |editor, _: &HalfPageDown, cx| {
-            editor.scroll_screen(&ScrollAmount::Page(0.5), cx)
-        });
-        register_action(view, cx, |editor, _: &HalfPageUp, cx| {
-            editor.scroll_screen(&ScrollAmount::Page(-0.5), cx)
-        });
-        register_action(view, cx, |editor, _: &PageDown, cx| {
-            editor.scroll_screen(&ScrollAmount::Page(1.), cx)
-        });
-        register_action(view, cx, |editor, _: &PageUp, cx| {
-            editor.scroll_screen(&ScrollAmount::Page(-1.), cx)
-        });
-        register_action(view, cx, Editor::move_to_previous_word_start);
-        register_action(view, cx, Editor::move_to_previous_subword_start);
-        register_action(view, cx, Editor::move_to_next_word_end);
-        register_action(view, cx, Editor::move_to_next_subword_end);
-        register_action(view, cx, Editor::move_to_beginning_of_line);
-        register_action(view, cx, Editor::move_to_end_of_line);
-        register_action(view, cx, Editor::move_to_start_of_paragraph);
-        register_action(view, cx, Editor::move_to_end_of_paragraph);
-        register_action(view, cx, Editor::move_to_beginning);
-        register_action(view, cx, Editor::move_to_end);
-        register_action(view, cx, Editor::select_up);
-        register_action(view, cx, Editor::select_down);
-        register_action(view, cx, Editor::select_left);
-        register_action(view, cx, Editor::select_right);
-        register_action(view, cx, Editor::select_to_previous_word_start);
-        register_action(view, cx, Editor::select_to_previous_subword_start);
-        register_action(view, cx, Editor::select_to_next_word_end);
-        register_action(view, cx, Editor::select_to_next_subword_end);
-        register_action(view, cx, Editor::select_to_beginning_of_line);
-        register_action(view, cx, Editor::select_to_end_of_line);
-        register_action(view, cx, Editor::select_to_start_of_paragraph);
-        register_action(view, cx, Editor::select_to_end_of_paragraph);
-        register_action(view, cx, Editor::select_to_beginning);
-        register_action(view, cx, Editor::select_to_end);
-        register_action(view, cx, Editor::select_all);
-        register_action(view, cx, |editor, action, cx| {
-            editor.select_all_matches(action, cx).log_err();
-        });
-        register_action(view, cx, Editor::select_line);
-        register_action(view, cx, Editor::split_selection_into_lines);
-        register_action(view, cx, Editor::add_selection_above);
-        register_action(view, cx, Editor::add_selection_below);
-        register_action(view, cx, |editor, action, cx| {
-            editor.select_next(action, cx).log_err();
-        });
-        register_action(view, cx, |editor, action, cx| {
-            editor.select_previous(action, cx).log_err();
-        });
-        register_action(view, cx, Editor::toggle_comments);
-        register_action(view, cx, Editor::select_larger_syntax_node);
-        register_action(view, cx, Editor::select_smaller_syntax_node);
-        register_action(view, cx, Editor::move_to_enclosing_bracket);
-        register_action(view, cx, Editor::undo_selection);
-        register_action(view, cx, Editor::redo_selection);
-        register_action(view, cx, Editor::go_to_diagnostic);
-        register_action(view, cx, Editor::go_to_prev_diagnostic);
-        register_action(view, cx, Editor::go_to_hunk);
-        register_action(view, cx, Editor::go_to_prev_hunk);
-        register_action(view, cx, Editor::go_to_definition);
-        register_action(view, cx, Editor::go_to_definition_split);
-        register_action(view, cx, Editor::go_to_type_definition);
-        register_action(view, cx, Editor::go_to_type_definition_split);
-        register_action(view, cx, Editor::fold);
-        register_action(view, cx, Editor::fold_at);
-        register_action(view, cx, Editor::unfold_lines);
-        register_action(view, cx, Editor::unfold_at);
-        register_action(view, cx, Editor::fold_selected_ranges);
-        register_action(view, cx, Editor::show_completions);
-        register_action(view, cx, Editor::toggle_code_actions);
-        register_action(view, cx, Editor::open_excerpts);
-        register_action(view, cx, Editor::toggle_soft_wrap);
-        register_action(view, cx, Editor::toggle_inlay_hints);
-        register_action(view, cx, hover_popover::hover);
-        register_action(view, cx, Editor::reveal_in_finder);
-        register_action(view, cx, Editor::copy_path);
-        register_action(view, cx, Editor::copy_relative_path);
-        register_action(view, cx, Editor::copy_highlight_json);
-        register_action(view, cx, |editor, action, cx| {
-            if let Some(task) = editor.format(action, cx) {
-                task.detach_and_log_err(cx);
-            } else {
-                cx.propagate();
-            }
-        });
-        register_action(view, cx, Editor::restart_language_server);
-        register_action(view, cx, Editor::show_character_palette);
-        register_action(view, cx, |editor, action, cx| {
-            if let Some(task) = editor.confirm_completion(action, cx) {
-                task.detach_and_log_err(cx);
-            } else {
-                cx.propagate();
-            }
-        });
-        register_action(view, cx, |editor, action, cx| {
-            if let Some(task) = editor.confirm_code_action(action, cx) {
-                task.detach_and_log_err(cx);
-            } else {
-                cx.propagate();
-            }
-        });
-        register_action(view, cx, |editor, action, cx| {
-            if let Some(task) = editor.rename(action, cx) {
-                task.detach_and_log_err(cx);
-            } else {
-                cx.propagate();
-            }
-        });
-        register_action(view, cx, |editor, action, cx| {
-            if let Some(task) = editor.confirm_rename(action, cx) {
-                task.detach_and_log_err(cx);
-            } else {
-                cx.propagate();
-            }
-        });
-        register_action(view, cx, |editor, action, cx| {
-            if let Some(task) = editor.find_all_references(action, cx) {
-                task.detach_and_log_err(cx);
-            } else {
-                cx.propagate();
-            }
-        });
-        register_action(view, cx, Editor::next_copilot_suggestion);
-        register_action(view, cx, Editor::previous_copilot_suggestion);
-        register_action(view, cx, Editor::copilot_suggest);
-        register_action(view, cx, Editor::context_menu_first);
-        register_action(view, cx, Editor::context_menu_prev);
-        register_action(view, cx, Editor::context_menu_next);
-        register_action(view, cx, Editor::context_menu_last);
-    }
-
-    fn register_key_listeners(&self, cx: &mut WindowContext) {
-        cx.on_key_event({
-            let editor = self.editor.clone();
-            move |event: &ModifiersChangedEvent, phase, cx| {
-                if phase != DispatchPhase::Bubble {
-                    return;
-                }
-
-                if editor.update(cx, |editor, cx| Self::modifiers_changed(editor, event, cx)) {
-                    cx.stop_propagation();
-                }
-            }
-        });
-    }
-
-    pub(crate) fn modifiers_changed(
-        editor: &mut Editor,
-        event: &ModifiersChangedEvent,
-        cx: &mut ViewContext<Editor>,
-    ) -> bool {
-        let pending_selection = editor.has_pending_selection();
-
-        if let Some(point) = &editor.link_go_to_definition_state.last_trigger_point {
-            if event.command && !pending_selection {
-                let point = point.clone();
-                let snapshot = editor.snapshot(cx);
-                let kind = point.definition_kind(event.shift);
-
-                show_link_definition(kind, editor, point, snapshot, cx);
-                return false;
-            }
-        }
-
-        {
-            if editor.link_go_to_definition_state.symbol_range.is_some()
-                || !editor.link_go_to_definition_state.definitions.is_empty()
-            {
-                editor.link_go_to_definition_state.symbol_range.take();
-                editor.link_go_to_definition_state.definitions.clear();
-                cx.notify();
-            }
-
-            editor.link_go_to_definition_state.task = None;
-
-            editor.clear_highlights::<LinkGoToDefinitionState>(cx);
-        }
-
-        false
-    }
-
-    fn mouse_left_down(
-        editor: &mut Editor,
-        event: &MouseDownEvent,
-        position_map: &PositionMap,
-        text_bounds: Bounds<Pixels>,
-        gutter_bounds: Bounds<Pixels>,
-        stacking_order: &StackingOrder,
-        cx: &mut ViewContext<Editor>,
-    ) {
-        let mut click_count = event.click_count;
-        let modifiers = event.modifiers;
-
-        if gutter_bounds.contains(&event.position) {
-            click_count = 3; // Simulate triple-click when clicking the gutter to select lines
-        } else if !text_bounds.contains(&event.position) {
-            return;
-        }
-        if !cx.was_top_layer(&event.position, stacking_order) {
-            return;
-        }
-
-        let point_for_position = position_map.point_for_position(text_bounds, event.position);
-        let position = point_for_position.previous_valid;
-        if modifiers.shift && modifiers.alt {
-            editor.select(
-                SelectPhase::BeginColumnar {
-                    position,
-                    goal_column: point_for_position.exact_unclipped.column(),
-                },
-                cx,
-            );
-        } else if modifiers.shift && !modifiers.control && !modifiers.alt && !modifiers.command {
-            editor.select(
-                SelectPhase::Extend {
-                    position,
-                    click_count,
-                },
-                cx,
-            );
-        } else {
-            editor.select(
-                SelectPhase::Begin {
-                    position,
-                    add: modifiers.alt,
-                    click_count,
-                },
-                cx,
-            );
-        }
-
-        cx.stop_propagation();
-    }
-
-    fn mouse_right_down(
-        editor: &mut Editor,
-        event: &MouseDownEvent,
-        position_map: &PositionMap,
-        text_bounds: Bounds<Pixels>,
-        cx: &mut ViewContext<Editor>,
-    ) {
-        if !text_bounds.contains(&event.position) {
-            return;
-        }
-        let point_for_position = position_map.point_for_position(text_bounds, event.position);
-        mouse_context_menu::deploy_context_menu(
-            editor,
-            event.position,
-            point_for_position.previous_valid,
-            cx,
-        );
-        cx.stop_propagation();
-    }
-
-    fn mouse_up(
-        editor: &mut Editor,
-        event: &MouseUpEvent,
-        position_map: &PositionMap,
-        text_bounds: Bounds<Pixels>,
-        stacking_order: &StackingOrder,
-        cx: &mut ViewContext<Editor>,
-    ) {
-        let end_selection = editor.has_pending_selection();
-        let pending_nonempty_selections = editor.has_pending_nonempty_selection();
-
-        if end_selection {
-            editor.select(SelectPhase::End, cx);
-        }
-
-        if !pending_nonempty_selections
-            && event.modifiers.command
-            && text_bounds.contains(&event.position)
-            && cx.was_top_layer(&event.position, stacking_order)
-        {
-            let point = position_map.point_for_position(text_bounds, event.position);
-            let could_be_inlay = point.as_valid().is_none();
-            let split = event.modifiers.alt;
-            if event.modifiers.shift || could_be_inlay {
-                go_to_fetched_type_definition(editor, point, split, cx);
-            } else {
-                go_to_fetched_definition(editor, point, split, cx);
-            }
-
-            cx.stop_propagation();
-        } else if end_selection {
-            cx.stop_propagation();
-        }
-    }
-
-    fn mouse_dragged(
-        editor: &mut Editor,
-        event: &MouseMoveEvent,
-        position_map: &PositionMap,
-        text_bounds: Bounds<Pixels>,
-        _gutter_bounds: Bounds<Pixels>,
-        _stacking_order: &StackingOrder,
-        cx: &mut ViewContext<Editor>,
-    ) {
-        if !editor.has_pending_selection() {
-            return;
-        }
-
-        let point_for_position = position_map.point_for_position(text_bounds, event.position);
-        let mut scroll_delta = gpui::Point::<f32>::default();
-        let vertical_margin = position_map.line_height.min(text_bounds.size.height / 3.0);
-        let top = text_bounds.origin.y + vertical_margin;
-        let bottom = text_bounds.lower_left().y - vertical_margin;
-        if event.position.y < top {
-            scroll_delta.y = -scale_vertical_mouse_autoscroll_delta(top - event.position.y);
-        }
-        if event.position.y > bottom {
-            scroll_delta.y = scale_vertical_mouse_autoscroll_delta(event.position.y - bottom);
-        }
-
-        let horizontal_margin = position_map.line_height.min(text_bounds.size.width / 3.0);
-        let left = text_bounds.origin.x + horizontal_margin;
-        let right = text_bounds.upper_right().x - horizontal_margin;
-        if event.position.x < left {
-            scroll_delta.x = -scale_horizontal_mouse_autoscroll_delta(left - event.position.x);
-        }
-        if event.position.x > right {
-            scroll_delta.x = scale_horizontal_mouse_autoscroll_delta(event.position.x - right);
-        }
-
-        editor.select(
-            SelectPhase::Update {
-                position: point_for_position.previous_valid,
-                goal_column: point_for_position.exact_unclipped.column(),
-                scroll_position: (position_map.snapshot.scroll_position() + scroll_delta)
-                    .clamp(&gpui::Point::default(), &position_map.scroll_max),
-            },
-            cx,
-        );
-    }
-
-    fn mouse_moved(
-        editor: &mut Editor,
-        event: &MouseMoveEvent,
-        position_map: &PositionMap,
-        text_bounds: Bounds<Pixels>,
-        gutter_bounds: Bounds<Pixels>,
-        stacking_order: &StackingOrder,
-        cx: &mut ViewContext<Editor>,
-    ) {
-        let modifiers = event.modifiers;
-        let text_hovered = text_bounds.contains(&event.position);
-        let gutter_hovered = gutter_bounds.contains(&event.position);
-        let was_top = cx.was_top_layer(&event.position, stacking_order);
-
-        editor.set_gutter_hovered(gutter_hovered, cx);
-
-        // Don't trigger hover popover if mouse is hovering over context menu
-        if text_hovered && was_top {
-            let point_for_position = position_map.point_for_position(text_bounds, event.position);
-
-            match point_for_position.as_valid() {
-                Some(point) => {
-                    update_go_to_definition_link(
-                        editor,
-                        Some(GoToDefinitionTrigger::Text(point)),
-                        modifiers.command,
-                        modifiers.shift,
-                        cx,
-                    );
-                    hover_at(editor, Some(point), cx);
-                }
-                None => {
-                    update_inlay_link_and_hover_points(
-                        &position_map.snapshot,
-                        point_for_position,
-                        editor,
-                        modifiers.command,
-                        modifiers.shift,
-                        cx,
-                    );
-                }
-            }
-        } else {
-            update_go_to_definition_link(editor, None, modifiers.command, modifiers.shift, cx);
-            hover_at(editor, None, cx);
-            if gutter_hovered && was_top {
-                cx.stop_propagation();
-            }
-        }
-    }
-
-    fn scroll(
-        editor: &mut Editor,
-        event: &ScrollWheelEvent,
-        position_map: &PositionMap,
-        bounds: &InteractiveBounds,
-        cx: &mut ViewContext<Editor>,
-    ) {
-        if !bounds.visibly_contains(&event.position, cx) {
-            return;
-        }
-
-        let line_height = position_map.line_height;
-        let max_glyph_width = position_map.em_width;
-        let (delta, axis) = match event.delta {
-            gpui::ScrollDelta::Pixels(mut pixels) => {
-                //Trackpad
-                let axis = position_map.snapshot.ongoing_scroll.filter(&mut pixels);
-                (pixels, axis)
-            }
-
-            gpui::ScrollDelta::Lines(lines) => {
-                //Not trackpad
-                let pixels = point(lines.x * max_glyph_width, lines.y * line_height);
-                (pixels, None)
-            }
-        };
-
-        let scroll_position = position_map.snapshot.scroll_position();
-        let x = f32::from((scroll_position.x * max_glyph_width - delta.x) / max_glyph_width);
-        let y = f32::from((scroll_position.y * line_height - delta.y) / line_height);
-        let scroll_position = point(x, y).clamp(&point(0., 0.), &position_map.scroll_max);
-        editor.scroll(scroll_position, axis, cx);
-        cx.stop_propagation();
-    }
-
-    fn paint_background(
-        &self,
-        gutter_bounds: Bounds<Pixels>,
-        text_bounds: Bounds<Pixels>,
-        layout: &LayoutState,
-        cx: &mut WindowContext,
-    ) {
-        let bounds = gutter_bounds.union(&text_bounds);
-        let scroll_top =
-            layout.position_map.snapshot.scroll_position().y * layout.position_map.line_height;
-        let gutter_bg = cx.theme().colors().editor_gutter_background;
-        cx.paint_quad(fill(gutter_bounds, gutter_bg));
-        cx.paint_quad(fill(text_bounds, self.style.background));
-
-        if let EditorMode::Full = layout.mode {
-            let mut active_rows = layout.active_rows.iter().peekable();
-            while let Some((start_row, contains_non_empty_selection)) = active_rows.next() {
-                let mut end_row = *start_row;
-                while active_rows.peek().map_or(false, |r| {
-                    *r.0 == end_row + 1 && r.1 == contains_non_empty_selection
-                }) {
-                    active_rows.next().unwrap();
-                    end_row += 1;
-                }
-
-                if !contains_non_empty_selection {
-                    let origin = point(
-                        bounds.origin.x,
-                        bounds.origin.y + (layout.position_map.line_height * *start_row as f32)
-                            - scroll_top,
-                    );
-                    let size = size(
-                        bounds.size.width,
-                        layout.position_map.line_height * (end_row - start_row + 1) as f32,
-                    );
-                    let active_line_bg = cx.theme().colors().editor_active_line_background;
-                    cx.paint_quad(fill(Bounds { origin, size }, active_line_bg));
-                }
-            }
-
-            if let Some(highlighted_rows) = &layout.highlighted_rows {
-                let origin = point(
-                    bounds.origin.x,
-                    bounds.origin.y
-                        + (layout.position_map.line_height * highlighted_rows.start as f32)
-                        - scroll_top,
-                );
-                let size = size(
-                    bounds.size.width,
-                    layout.position_map.line_height * highlighted_rows.len() as f32,
-                );
-                let highlighted_line_bg = cx.theme().colors().editor_highlighted_line_background;
-                cx.paint_quad(fill(Bounds { origin, size }, highlighted_line_bg));
-            }
-
-            let scroll_left =
-                layout.position_map.snapshot.scroll_position().x * layout.position_map.em_width;
-
-            for (wrap_position, active) in layout.wrap_guides.iter() {
-                let x = (text_bounds.origin.x + *wrap_position + layout.position_map.em_width / 2.)
-                    - scroll_left;
-
-                if x < text_bounds.origin.x
-                    || (layout.show_scrollbars && x > self.scrollbar_left(&bounds))
-                {
-                    continue;
-                }
-
-                let color = if *active {
-                    cx.theme().colors().editor_active_wrap_guide
-                } else {
-                    cx.theme().colors().editor_wrap_guide
-                };
-                cx.paint_quad(fill(
-                    Bounds {
-                        origin: point(x, text_bounds.origin.y),
-                        size: size(px(1.), text_bounds.size.height),
-                    },
-                    color,
-                ));
-            }
-        }
-    }
-
-    fn paint_gutter(
-        &mut self,
-        bounds: Bounds<Pixels>,
-        layout: &mut LayoutState,
-        cx: &mut WindowContext,
-    ) {
-        let line_height = layout.position_map.line_height;
-
-        let scroll_position = layout.position_map.snapshot.scroll_position();
-        let scroll_top = scroll_position.y * line_height;
-
-        let show_gutter = matches!(
-            ProjectSettings::get_global(cx).git.git_gutter,
-            Some(GitGutterSetting::TrackedFiles)
-        );
-
-        if show_gutter {
-            Self::paint_diff_hunks(bounds, layout, cx);
-        }
-
-        for (ix, line) in layout.line_numbers.iter().enumerate() {
-            if let Some(line) = line {
-                let line_origin = bounds.origin
-                    + point(
-                        bounds.size.width - line.width - layout.gutter_padding,
-                        ix as f32 * line_height - (scroll_top % line_height),
-                    );
-
-                line.paint(line_origin, line_height, cx).log_err();
-            }
-        }
-
-        cx.with_z_index(1, |cx| {
-            for (ix, fold_indicator) in layout.fold_indicators.drain(..).enumerate() {
-                if let Some(fold_indicator) = fold_indicator {
-                    let mut fold_indicator = fold_indicator.into_any_element();
-                    let available_space = size(
-                        AvailableSpace::MinContent,
-                        AvailableSpace::Definite(line_height * 0.55),
-                    );
-                    let fold_indicator_size = fold_indicator.measure(available_space, cx);
-
-                    let position = point(
-                        bounds.size.width - layout.gutter_padding,
-                        ix as f32 * line_height - (scroll_top % line_height),
-                    );
-                    let centering_offset = point(
-                        (layout.gutter_padding + layout.gutter_margin - fold_indicator_size.width)
-                            / 2.,
-                        (line_height - fold_indicator_size.height) / 2.,
-                    );
-                    let origin = bounds.origin + position + centering_offset;
-                    fold_indicator.draw(origin, available_space, cx);
-                }
-            }
-
-            if let Some(indicator) = layout.code_actions_indicator.take() {
-                let mut button = indicator.button.into_any_element();
-                let available_space = size(
-                    AvailableSpace::MinContent,
-                    AvailableSpace::Definite(line_height),
-                );
-                let indicator_size = button.measure(available_space, cx);
-
-                let mut x = Pixels::ZERO;
-                let mut y = indicator.row as f32 * line_height - scroll_top;
-                // Center indicator.
-                x += ((layout.gutter_padding + layout.gutter_margin) - indicator_size.width) / 2.;
-                y += (line_height - indicator_size.height) / 2.;
-
-                button.draw(bounds.origin + point(x, y), available_space, cx);
-            }
-        });
-    }
-
-    fn paint_diff_hunks(bounds: Bounds<Pixels>, layout: &LayoutState, cx: &mut WindowContext) {
-        let line_height = layout.position_map.line_height;
-
-        let scroll_position = layout.position_map.snapshot.scroll_position();
-        let scroll_top = scroll_position.y * line_height;
-
-        for hunk in &layout.display_hunks {
-            let (display_row_range, status) = match hunk {
-                //TODO: This rendering is entirely a horrible hack
-                &DisplayDiffHunk::Folded { display_row: row } => {
-                    let start_y = row as f32 * line_height - scroll_top;
-                    let end_y = start_y + line_height;
-
-                    let width = 0.275 * line_height;
-                    let highlight_origin = bounds.origin + point(-width, start_y);
-                    let highlight_size = size(width * 2., end_y - start_y);
-                    let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
-                    cx.paint_quad(quad(
-                        highlight_bounds,
-                        Corners::all(1. * line_height),
-                        gpui::yellow(), // todo!("use the right color")
-                        Edges::default(),
-                        transparent_black(),
-                    ));
-
-                    continue;
-                }
-
-                DisplayDiffHunk::Unfolded {
-                    display_row_range,
-                    status,
-                } => (display_row_range, status),
-            };
-
-            let color = match status {
-                DiffHunkStatus::Added => cx.theme().status().created,
-                DiffHunkStatus::Modified => cx.theme().status().modified,
-
-                //TODO: This rendering is entirely a horrible hack
-                DiffHunkStatus::Removed => {
-                    let row = display_row_range.start;
-
-                    let offset = line_height / 2.;
-                    let start_y = row as f32 * line_height - offset - scroll_top;
-                    let end_y = start_y + line_height;
-
-                    let width = 0.275 * line_height;
-                    let highlight_origin = bounds.origin + point(-width, start_y);
-                    let highlight_size = size(width * 2., end_y - start_y);
-                    let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
-                    cx.paint_quad(quad(
-                        highlight_bounds,
-                        Corners::all(1. * line_height),
-                        cx.theme().status().deleted,
-                        Edges::default(),
-                        transparent_black(),
-                    ));
-
-                    continue;
-                }
-            };
-
-            let start_row = display_row_range.start;
-            let end_row = display_row_range.end;
-
-            let start_y = start_row as f32 * line_height - scroll_top;
-            let end_y = end_row as f32 * line_height - scroll_top;
-
-            let width = 0.275 * line_height;
-            let highlight_origin = bounds.origin + point(-width, start_y);
-            let highlight_size = size(width * 2., end_y - start_y);
-            let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
-            cx.paint_quad(quad(
-                highlight_bounds,
-                Corners::all(0.05 * line_height),
-                color, // todo!("use the right color")
-                Edges::default(),
-                transparent_black(),
-            ));
-        }
-    }
-
-    fn paint_text(
-        &mut self,
-        text_bounds: Bounds<Pixels>,
-        layout: &mut LayoutState,
-        cx: &mut WindowContext,
-    ) {
-        let start_row = layout.visible_display_row_range.start;
-        let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
-        let line_end_overshoot = 0.15 * layout.position_map.line_height;
-        let whitespace_setting = self
-            .editor
-            .read(cx)
-            .buffer
-            .read(cx)
-            .settings_at(0, cx)
-            .show_whitespaces;
-
-        cx.with_content_mask(
-            Some(ContentMask {
-                bounds: text_bounds,
-            }),
-            |cx| {
-                let interactive_text_bounds = InteractiveBounds {
-                    bounds: text_bounds,
-                    stacking_order: cx.stacking_order().clone(),
-                };
-                if interactive_text_bounds.visibly_contains(&cx.mouse_position(), cx) {
-                    if self
-                        .editor
-                        .read(cx)
-                        .link_go_to_definition_state
-                        .definitions
-                        .is_empty()
-                    {
-                        cx.set_cursor_style(CursorStyle::IBeam);
-                    } else {
-                        cx.set_cursor_style(CursorStyle::PointingHand);
-                    }
-                }
-
-                let fold_corner_radius = 0.15 * layout.position_map.line_height;
-                cx.with_element_id(Some("folds"), |cx| {
-                    let snapshot = &layout.position_map.snapshot;
-                    for fold in snapshot.folds_in_range(layout.visible_anchor_range.clone()) {
-                        let fold_range = fold.range.clone();
-                        let display_range = fold.range.start.to_display_point(&snapshot)
-                            ..fold.range.end.to_display_point(&snapshot);
-                        debug_assert_eq!(display_range.start.row(), display_range.end.row());
-                        let row = display_range.start.row();
-
-                        let line_layout = &layout.position_map.line_layouts
-                            [(row - layout.visible_display_row_range.start) as usize]
-                            .line;
-                        let start_x = content_origin.x
-                            + line_layout.x_for_index(display_range.start.column() as usize)
-                            - layout.position_map.scroll_position.x;
-                        let start_y = content_origin.y
-                            + row as f32 * layout.position_map.line_height
-                            - layout.position_map.scroll_position.y;
-                        let end_x = content_origin.x
-                            + line_layout.x_for_index(display_range.end.column() as usize)
-                            - layout.position_map.scroll_position.x;
-
-                        let fold_bounds = Bounds {
-                            origin: point(start_x, start_y),
-                            size: size(end_x - start_x, layout.position_map.line_height),
-                        };
-
-                        let fold_background = cx.with_z_index(1, |cx| {
-                            div()
-                                .id(fold.id)
-                                .size_full()
-                                .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
-                                .on_click(cx.listener_for(
-                                    &self.editor,
-                                    move |editor: &mut Editor, _, cx| {
-                                        editor.unfold_ranges(
-                                            [fold_range.start..fold_range.end],
-                                            true,
-                                            false,
-                                            cx,
-                                        );
-                                        cx.stop_propagation();
-                                    },
-                                ))
-                                .draw_and_update_state(
-                                    fold_bounds.origin,
-                                    fold_bounds.size,
-                                    cx,
-                                    |fold_element_state, cx| {
-                                        if fold_element_state.is_active() {
-                                            cx.theme().colors().ghost_element_active
-                                        } else if fold_bounds.contains(&cx.mouse_position()) {
-                                            cx.theme().colors().ghost_element_hover
-                                        } else {
-                                            cx.theme().colors().ghost_element_background
-                                        }
-                                    },
-                                )
-                        });
-
-                        self.paint_highlighted_range(
-                            display_range.clone(),
-                            fold_background,
-                            fold_corner_radius,
-                            fold_corner_radius * 2.,
-                            layout,
-                            content_origin,
-                            text_bounds,
-                            cx,
-                        );
-                    }
-                });
-
-                for (range, color) in &layout.highlighted_ranges {
-                    self.paint_highlighted_range(
-                        range.clone(),
-                        *color,
-                        Pixels::ZERO,
-                        line_end_overshoot,
-                        layout,
-                        content_origin,
-                        text_bounds,
-                        cx,
-                    );
-                }
-
-                let mut cursors = SmallVec::<[Cursor; 32]>::new();
-                let corner_radius = 0.15 * layout.position_map.line_height;
-                let mut invisible_display_ranges = SmallVec::<[Range<DisplayPoint>; 32]>::new();
-
-                for (selection_style, selections) in &layout.selections {
-                    for selection in selections {
-                        self.paint_highlighted_range(
-                            selection.range.clone(),
-                            selection_style.selection,
-                            corner_radius,
-                            corner_radius * 2.,
-                            layout,
-                            content_origin,
-                            text_bounds,
-                            cx,
-                        );
-
-                        if selection.is_local && !selection.range.is_empty() {
-                            invisible_display_ranges.push(selection.range.clone());
-                        }
-
-                        if !selection.is_local || self.editor.read(cx).show_local_cursors(cx) {
-                            let cursor_position = selection.head;
-                            if layout
-                                .visible_display_row_range
-                                .contains(&cursor_position.row())
-                            {
-                                let cursor_row_layout = &layout.position_map.line_layouts
-                                    [(cursor_position.row() - start_row) as usize]
-                                    .line;
-                                let cursor_column = cursor_position.column() as usize;
-
-                                let cursor_character_x =
-                                    cursor_row_layout.x_for_index(cursor_column);
-                                let mut block_width = cursor_row_layout
-                                    .x_for_index(cursor_column + 1)
-                                    - cursor_character_x;
-                                if block_width == Pixels::ZERO {
-                                    block_width = layout.position_map.em_width;
-                                }
-                                let block_text = if let CursorShape::Block = selection.cursor_shape
-                                {
-                                    layout
-                                        .position_map
-                                        .snapshot
-                                        .chars_at(cursor_position)
-                                        .next()
-                                        .and_then(|(character, _)| {
-                                            // todo!() currently shape_line panics if text conatins newlines
-                                            let text = if character == '\n' {
-                                                SharedString::from(" ")
-                                            } else {
-                                                SharedString::from(character.to_string())
-                                            };
-                                            let len = text.len();
-                                            cx.text_system()
-                                                .shape_line(
-                                                    text,
-                                                    cursor_row_layout.font_size,
-                                                    &[TextRun {
-                                                        len,
-                                                        font: self.style.text.font(),
-                                                        color: self.style.background,
-                                                        background_color: None,
-                                                        underline: None,
-                                                    }],
-                                                )
-                                                .log_err()
-                                        })
-                                } else {
-                                    None
-                                };
-
-                                let x = cursor_character_x - layout.position_map.scroll_position.x;
-                                let y = cursor_position.row() as f32
-                                    * layout.position_map.line_height
-                                    - layout.position_map.scroll_position.y;
-                                if selection.is_newest {
-                                    self.editor.update(cx, |editor, _| {
-                                        editor.pixel_position_of_newest_cursor = Some(point(
-                                            text_bounds.origin.x + x + block_width / 2.,
-                                            text_bounds.origin.y
-                                                + y
-                                                + layout.position_map.line_height / 2.,
-                                        ))
-                                    });
-                                }
-                                cursors.push(Cursor {
-                                    color: selection_style.cursor,
-                                    block_width,
-                                    origin: point(x, y),
-                                    line_height: layout.position_map.line_height,
-                                    shape: selection.cursor_shape,
-                                    block_text,
-                                });
-                            }
-                        }
-                    }
-                }
-
-                for (ix, line_with_invisibles) in
-                    layout.position_map.line_layouts.iter().enumerate()
-                {
-                    let row = start_row + ix as u32;
-                    line_with_invisibles.draw(
-                        layout,
-                        row,
-                        content_origin,
-                        whitespace_setting,
-                        &invisible_display_ranges,
-                        cx,
-                    )
-                }
-
-                cx.with_z_index(0, |cx| {
-                    for cursor in cursors {
-                        cursor.paint(content_origin, cx);
-                    }
-                });
-            },
-        )
-    }
-
-    fn paint_overlays(
-        &mut self,
-        text_bounds: Bounds<Pixels>,
-        layout: &mut LayoutState,
-        cx: &mut WindowContext,
-    ) {
-        let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
-        let start_row = layout.visible_display_row_range.start;
-        if let Some((position, mut context_menu)) = layout.context_menu.take() {
-            let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
-            let context_menu_size = context_menu.measure(available_space, cx);
-
-            let cursor_row_layout =
-                &layout.position_map.line_layouts[(position.row() - start_row) as usize].line;
-            let x = cursor_row_layout.x_for_index(position.column() as usize)
-                - layout.position_map.scroll_position.x;
-            let y = (position.row() + 1) as f32 * layout.position_map.line_height
-                - layout.position_map.scroll_position.y;
-            let mut list_origin = content_origin + point(x, y);
-            let list_width = context_menu_size.width;
-            let list_height = context_menu_size.height;
-
-            // Snap the right edge of the list to the right edge of the window if
-            // its horizontal bounds overflow.
-            if list_origin.x + list_width > cx.viewport_size().width {
-                list_origin.x = (cx.viewport_size().width - list_width).max(Pixels::ZERO);
-            }
-
-            if list_origin.y + list_height > text_bounds.lower_right().y {
-                list_origin.y -= layout.position_map.line_height + list_height;
-            }
-
-            cx.break_content_mask(|cx| context_menu.draw(list_origin, available_space, cx));
-        }
-
-        if let Some((position, mut hover_popovers)) = layout.hover_popovers.take() {
-            let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
-
-            // This is safe because we check on layout whether the required row is available
-            let hovered_row_layout =
-                &layout.position_map.line_layouts[(position.row() - start_row) as usize].line;
-
-            // Minimum required size: Take the first popover, and add 1.5 times the minimum popover
-            // height. This is the size we will use to decide whether to render popovers above or below
-            // the hovered line.
-            let first_size = hover_popovers[0].measure(available_space, cx);
-            let height_to_reserve =
-                first_size.height + 1.5 * MIN_POPOVER_LINE_HEIGHT * layout.position_map.line_height;
-
-            // Compute Hovered Point
-            let x = hovered_row_layout.x_for_index(position.column() as usize)
-                - layout.position_map.scroll_position.x;
-            let y = position.row() as f32 * layout.position_map.line_height
-                - layout.position_map.scroll_position.y;
-            let hovered_point = content_origin + point(x, y);
-
-            if hovered_point.y - height_to_reserve > Pixels::ZERO {
-                // There is enough space above. Render popovers above the hovered point
-                let mut current_y = hovered_point.y;
-                for mut hover_popover in hover_popovers {
-                    let size = hover_popover.measure(available_space, cx);
-                    let mut popover_origin = point(hovered_point.x, current_y - size.height);
-
-                    let x_out_of_bounds =
-                        text_bounds.upper_right().x - (popover_origin.x + size.width);
-                    if x_out_of_bounds < Pixels::ZERO {
-                        popover_origin.x = popover_origin.x + x_out_of_bounds;
-                    }
-
-                    cx.break_content_mask(|cx| {
-                        hover_popover.draw(popover_origin, available_space, cx)
-                    });
-
-                    current_y = popover_origin.y - HOVER_POPOVER_GAP;
-                }
-            } else {
-                // There is not enough space above. Render popovers below the hovered point
-                let mut current_y = hovered_point.y + layout.position_map.line_height;
-                for mut hover_popover in hover_popovers {
-                    let size = hover_popover.measure(available_space, cx);
-                    let mut popover_origin = point(hovered_point.x, current_y);
-
-                    let x_out_of_bounds =
-                        text_bounds.upper_right().x - (popover_origin.x + size.width);
-                    if x_out_of_bounds < Pixels::ZERO {
-                        popover_origin.x = popover_origin.x + x_out_of_bounds;
-                    }
-
-                    hover_popover.draw(popover_origin, available_space, cx);
-
-                    current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP;
-                }
-            }
-        }
-
-        if let Some(mouse_context_menu) = self.editor.read(cx).mouse_context_menu.as_ref() {
-            let element = overlay()
-                .position(mouse_context_menu.position)
-                .child(mouse_context_menu.context_menu.clone())
-                .anchor(AnchorCorner::TopLeft)
-                .snap_to_window();
-            element.into_any().draw(
-                gpui::Point::default(),
-                size(AvailableSpace::MinContent, AvailableSpace::MinContent),
-                cx,
-            );
-        }
-    }
-
-    fn scrollbar_left(&self, bounds: &Bounds<Pixels>) -> Pixels {
-        bounds.upper_right().x - self.style.scrollbar_width
-    }
-
-    fn paint_scrollbar(
-        &mut self,
-        bounds: Bounds<Pixels>,
-        layout: &mut LayoutState,
-        cx: &mut WindowContext,
-    ) {
-        if layout.mode != EditorMode::Full {
-            return;
-        }
-
-        let top = bounds.origin.y;
-        let bottom = bounds.lower_left().y;
-        let right = bounds.lower_right().x;
-        let left = self.scrollbar_left(&bounds);
-        let row_range = layout.scrollbar_row_range.clone();
-        let max_row = layout.max_row as f32 + (row_range.end - row_range.start);
-
-        let mut height = bounds.size.height;
-        let mut first_row_y_offset = px(0.0);
-
-        // Impose a minimum height on the scrollbar thumb
-        let row_height = height / max_row;
-        let min_thumb_height = layout.position_map.line_height;
-        let thumb_height = (row_range.end - row_range.start) * row_height;
-        if thumb_height < min_thumb_height {
-            first_row_y_offset = (min_thumb_height - thumb_height) / 2.0;
-            height -= min_thumb_height - thumb_height;
-        }
-
-        let y_for_row = |row: f32| -> Pixels { top + first_row_y_offset + row * row_height };
-
-        let thumb_top = y_for_row(row_range.start) - first_row_y_offset;
-        let thumb_bottom = y_for_row(row_range.end) + first_row_y_offset;
-        let track_bounds = Bounds::from_corners(point(left, top), point(right, bottom));
-        let thumb_bounds = Bounds::from_corners(point(left, thumb_top), point(right, thumb_bottom));
-
-        if layout.show_scrollbars {
-            cx.paint_quad(quad(
-                track_bounds,
-                Corners::default(),
-                cx.theme().colors().scrollbar_track_background,
-                Edges {
-                    top: Pixels::ZERO,
-                    right: Pixels::ZERO,
-                    bottom: Pixels::ZERO,
-                    left: px(1.),
-                },
-                cx.theme().colors().scrollbar_track_border,
-            ));
-            let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
-            if layout.is_singleton && scrollbar_settings.selections {
-                let start_anchor = Anchor::min();
-                let end_anchor = Anchor::max();
-                let background_ranges = self
-                    .editor
-                    .read(cx)
-                    .background_highlight_row_ranges::<crate::items::BufferSearchHighlights>(
-                        start_anchor..end_anchor,
-                        &layout.position_map.snapshot,
-                        50000,
-                    );
-                for range in background_ranges {
-                    let start_y = y_for_row(range.start().row() as f32);
-                    let mut end_y = y_for_row(range.end().row() as f32);
-                    if end_y - start_y < px(1.) {
-                        end_y = start_y + px(1.);
-                    }
-                    let bounds = Bounds::from_corners(point(left, start_y), point(right, end_y));
-                    cx.paint_quad(quad(
-                        bounds,
-                        Corners::default(),
-                        cx.theme().status().info,
-                        Edges {
-                            top: Pixels::ZERO,
-                            right: px(1.),
-                            bottom: Pixels::ZERO,
-                            left: px(1.),
-                        },
-                        cx.theme().colors().scrollbar_thumb_border,
-                    ));
-                }
-            }
-
-            if layout.is_singleton && scrollbar_settings.git_diff {
-                for hunk in layout
-                    .position_map
-                    .snapshot
-                    .buffer_snapshot
-                    .git_diff_hunks_in_range(0..(max_row.floor() as u32))
-                {
-                    let start_display = Point::new(hunk.buffer_range.start, 0)
-                        .to_display_point(&layout.position_map.snapshot.display_snapshot);
-                    let end_display = Point::new(hunk.buffer_range.end, 0)
-                        .to_display_point(&layout.position_map.snapshot.display_snapshot);
-                    let start_y = y_for_row(start_display.row() as f32);
-                    let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end {
-                        y_for_row((end_display.row() + 1) as f32)
-                    } else {
-                        y_for_row((end_display.row()) as f32)
-                    };
-
-                    if end_y - start_y < px(1.) {
-                        end_y = start_y + px(1.);
-                    }
-                    let bounds = Bounds::from_corners(point(left, start_y), point(right, end_y));
-
-                    let color = match hunk.status() {
-                        DiffHunkStatus::Added => cx.theme().status().created,
-                        DiffHunkStatus::Modified => cx.theme().status().modified,
-                        DiffHunkStatus::Removed => cx.theme().status().deleted,
-                    };
-                    cx.paint_quad(quad(
-                        bounds,
-                        Corners::default(),
-                        color,
-                        Edges {
-                            top: Pixels::ZERO,
-                            right: px(1.),
-                            bottom: Pixels::ZERO,
-                            left: px(1.),
-                        },
-                        cx.theme().colors().scrollbar_thumb_border,
-                    ));
-                }
-            }
-
-            cx.paint_quad(quad(
-                thumb_bounds,
-                Corners::default(),
-                cx.theme().colors().scrollbar_thumb_background,
-                Edges {
-                    top: Pixels::ZERO,
-                    right: px(1.),
-                    bottom: Pixels::ZERO,
-                    left: px(1.),
-                },
-                cx.theme().colors().scrollbar_thumb_border,
-            ));
-        }
-
-        let interactive_track_bounds = InteractiveBounds {
-            bounds: track_bounds,
-            stacking_order: cx.stacking_order().clone(),
-        };
-        let mut mouse_position = cx.mouse_position();
-        if interactive_track_bounds.visibly_contains(&mouse_position, cx) {
-            cx.set_cursor_style(CursorStyle::Arrow);
-        }
-
-        cx.on_mouse_event({
-            let editor = self.editor.clone();
-            move |event: &MouseMoveEvent, phase, cx| {
-                if phase == DispatchPhase::Capture {
-                    return;
-                }
-
-                editor.update(cx, |editor, cx| {
-                    if event.pressed_button == Some(MouseButton::Left)
-                        && editor.scroll_manager.is_dragging_scrollbar()
-                    {
-                        let y = mouse_position.y;
-                        let new_y = event.position.y;
-                        if (track_bounds.top()..track_bounds.bottom()).contains(&y) {
-                            let mut position = editor.scroll_position(cx);
-                            position.y += (new_y - y) * (max_row as f32) / height;
-                            if position.y < 0.0 {
-                                position.y = 0.0;
-                            }
-                            editor.set_scroll_position(position, cx);
-                        }
-
-                        mouse_position = event.position;
-                        cx.stop_propagation();
-                    } else {
-                        editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
-                        if interactive_track_bounds.visibly_contains(&event.position, cx) {
-                            editor.scroll_manager.show_scrollbar(cx);
-                        }
-                    }
-                })
-            }
-        });
-
-        if self.editor.read(cx).scroll_manager.is_dragging_scrollbar() {
-            cx.on_mouse_event({
-                let editor = self.editor.clone();
-                move |_: &MouseUpEvent, phase, cx| {
-                    if phase == DispatchPhase::Capture {
-                        return;
-                    }
-
-                    editor.update(cx, |editor, cx| {
-                        editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
-                        cx.stop_propagation();
-                    });
-                }
-            });
-        } else {
-            cx.on_mouse_event({
-                let editor = self.editor.clone();
-                move |event: &MouseDownEvent, phase, cx| {
-                    if phase == DispatchPhase::Capture {
-                        return;
-                    }
-
-                    editor.update(cx, |editor, cx| {
-                        if track_bounds.contains(&event.position) {
-                            editor.scroll_manager.set_is_dragging_scrollbar(true, cx);
-
-                            let y = event.position.y;
-                            if y < thumb_top || thumb_bottom < y {
-                                let center_row =
-                                    ((y - top) * max_row as f32 / height).round() as u32;
-                                let top_row = center_row
-                                    .saturating_sub((row_range.end - row_range.start) as u32 / 2);
-                                let mut position = editor.scroll_position(cx);
-                                position.y = top_row as f32;
-                                editor.set_scroll_position(position, cx);
-                            } else {
-                                editor.scroll_manager.show_scrollbar(cx);
-                            }
-
-                            cx.stop_propagation();
-                        }
-                    });
-                }
-            });
-        }
-    }
-
-    #[allow(clippy::too_many_arguments)]
-    fn paint_highlighted_range(
-        &self,
-        range: Range<DisplayPoint>,
-        color: Hsla,
-        corner_radius: Pixels,
-        line_end_overshoot: Pixels,
-        layout: &LayoutState,
-        content_origin: gpui::Point<Pixels>,
-        bounds: Bounds<Pixels>,
-        cx: &mut WindowContext,
-    ) {
-        let start_row = layout.visible_display_row_range.start;
-        let end_row = layout.visible_display_row_range.end;
-        if range.start != range.end {
-            let row_range = if range.end.column() == 0 {
-                cmp::max(range.start.row(), start_row)..cmp::min(range.end.row(), end_row)
-            } else {
-                cmp::max(range.start.row(), start_row)..cmp::min(range.end.row() + 1, end_row)
-            };
-
-            let highlighted_range = HighlightedRange {
-                color,
-                line_height: layout.position_map.line_height,
-                corner_radius,
-                start_y: content_origin.y
-                    + row_range.start as f32 * layout.position_map.line_height
-                    - layout.position_map.scroll_position.y,
-                lines: row_range
-                    .into_iter()
-                    .map(|row| {
-                        let line_layout =
-                            &layout.position_map.line_layouts[(row - start_row) as usize].line;
-                        HighlightedRangeLine {
-                            start_x: if row == range.start.row() {
-                                content_origin.x
-                                    + line_layout.x_for_index(range.start.column() as usize)
-                                    - layout.position_map.scroll_position.x
-                            } else {
-                                content_origin.x - layout.position_map.scroll_position.x
-                            },
-                            end_x: if row == range.end.row() {
-                                content_origin.x
-                                    + line_layout.x_for_index(range.end.column() as usize)
-                                    - layout.position_map.scroll_position.x
-                            } else {
-                                content_origin.x + line_layout.width + line_end_overshoot
-                                    - layout.position_map.scroll_position.x
-                            },
-                        }
-                    })
-                    .collect(),
-            };
-
-            highlighted_range.paint(bounds, cx);
-        }
-    }
-
-    fn paint_blocks(
-        &mut self,
-        bounds: Bounds<Pixels>,
-        layout: &mut LayoutState,
-        cx: &mut WindowContext,
-    ) {
-        let scroll_position = layout.position_map.snapshot.scroll_position();
-        let scroll_left = scroll_position.x * layout.position_map.em_width;
-        let scroll_top = scroll_position.y * layout.position_map.line_height;
-
-        for mut block in layout.blocks.drain(..) {
-            let mut origin = bounds.origin
-                + point(
-                    Pixels::ZERO,
-                    block.row as f32 * layout.position_map.line_height - scroll_top,
-                );
-            if !matches!(block.style, BlockStyle::Sticky) {
-                origin += point(-scroll_left, Pixels::ZERO);
-            }
-            block.element.draw(origin, block.available_space, cx);
-        }
-    }
-
-    fn column_pixels(&self, column: usize, cx: &WindowContext) -> Pixels {
-        let style = &self.style;
-        let font_size = style.text.font_size.to_pixels(cx.rem_size());
-        let layout = cx
-            .text_system()
-            .shape_line(
-                SharedString::from(" ".repeat(column)),
-                font_size,
-                &[TextRun {
-                    len: column,
-                    font: style.text.font(),
-                    color: Hsla::default(),
-                    background_color: None,
-                    underline: None,
-                }],
-            )
-            .unwrap();
-
-        layout.width
-    }
-
-    fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &WindowContext) -> Pixels {
-        let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1;
-        self.column_pixels(digit_count, cx)
-    }
-
-    //Folds contained in a hunk are ignored apart from shrinking visual size
-    //If a fold contains any hunks then that fold line is marked as modified
-    fn layout_git_gutters(
-        &self,
-        display_rows: Range<u32>,
-        snapshot: &EditorSnapshot,
-    ) -> Vec<DisplayDiffHunk> {
-        let buffer_snapshot = &snapshot.buffer_snapshot;
-
-        let buffer_start_row = DisplayPoint::new(display_rows.start, 0)
-            .to_point(snapshot)
-            .row;
-        let buffer_end_row = DisplayPoint::new(display_rows.end, 0)
-            .to_point(snapshot)
-            .row;
-
-        buffer_snapshot
-            .git_diff_hunks_in_range(buffer_start_row..buffer_end_row)
-            .map(|hunk| diff_hunk_to_display(hunk, snapshot))
-            .dedup()
-            .collect()
-    }
-
-    fn calculate_relative_line_numbers(
-        &self,
-        snapshot: &EditorSnapshot,
-        rows: &Range<u32>,
-        relative_to: Option<u32>,
-    ) -> HashMap<u32, u32> {
-        let mut relative_rows: HashMap<u32, u32> = Default::default();
-        let Some(relative_to) = relative_to else {
-            return relative_rows;
-        };
-
-        let start = rows.start.min(relative_to);
-        let end = rows.end.max(relative_to);
-
-        let buffer_rows = snapshot
-            .buffer_rows(start)
-            .take(1 + (end - start) as usize)
-            .collect::<Vec<_>>();
-
-        let head_idx = relative_to - start;
-        let mut delta = 1;
-        let mut i = head_idx + 1;
-        while i < buffer_rows.len() as u32 {
-            if buffer_rows[i as usize].is_some() {
-                if rows.contains(&(i + start)) {
-                    relative_rows.insert(i + start, delta);
-                }
-                delta += 1;
-            }
-            i += 1;
-        }
-        delta = 1;
-        i = head_idx.min(buffer_rows.len() as u32 - 1);
-        while i > 0 && buffer_rows[i as usize].is_none() {
-            i -= 1;
-        }
-
-        while i > 0 {
-            i -= 1;
-            if buffer_rows[i as usize].is_some() {
-                if rows.contains(&(i + start)) {
-                    relative_rows.insert(i + start, delta);
-                }
-                delta += 1;
-            }
-        }
-
-        relative_rows
-    }
-
-    fn shape_line_numbers(
-        &self,
-        rows: Range<u32>,
-        active_rows: &BTreeMap<u32, bool>,
-        newest_selection_head: DisplayPoint,
-        is_singleton: bool,
-        snapshot: &EditorSnapshot,
-        cx: &ViewContext<Editor>,
-    ) -> (
-        Vec<Option<ShapedLine>>,
-        Vec<Option<(FoldStatus, BufferRow, bool)>>,
-    ) {
-        let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
-        let include_line_numbers = snapshot.mode == EditorMode::Full;
-        let mut shaped_line_numbers = Vec::with_capacity(rows.len());
-        let mut fold_statuses = Vec::with_capacity(rows.len());
-        let mut line_number = String::new();
-        let is_relative = EditorSettings::get_global(cx).relative_line_numbers;
-        let relative_to = if is_relative {
-            Some(newest_selection_head.row())
-        } else {
-            None
-        };
-
-        let relative_rows = self.calculate_relative_line_numbers(&snapshot, &rows, relative_to);
-
-        for (ix, row) in snapshot
-            .buffer_rows(rows.start)
-            .take((rows.end - rows.start) as usize)
-            .enumerate()
-        {
-            let display_row = rows.start + ix as u32;
-            let (active, color) = if active_rows.contains_key(&display_row) {
-                (true, cx.theme().colors().editor_active_line_number)
-            } else {
-                (false, cx.theme().colors().editor_line_number)
-            };
-            if let Some(buffer_row) = row {
-                if include_line_numbers {
-                    line_number.clear();
-                    let default_number = buffer_row + 1;
-                    let number = relative_rows
-                        .get(&(ix as u32 + rows.start))
-                        .unwrap_or(&default_number);
-                    write!(&mut line_number, "{}", number).unwrap();
-                    let run = TextRun {
-                        len: line_number.len(),
-                        font: self.style.text.font(),
-                        color,
-                        background_color: None,
-                        underline: None,
-                    };
-                    let shaped_line = cx
-                        .text_system()
-                        .shape_line(line_number.clone().into(), font_size, &[run])
-                        .unwrap();
-                    shaped_line_numbers.push(Some(shaped_line));
-                    fold_statuses.push(
-                        is_singleton
-                            .then(|| {
-                                snapshot
-                                    .fold_for_line(buffer_row)
-                                    .map(|fold_status| (fold_status, buffer_row, active))
-                            })
-                            .flatten(),
-                    )
-                }
-            } else {
-                fold_statuses.push(None);
-                shaped_line_numbers.push(None);
-            }
-        }
-
-        (shaped_line_numbers, fold_statuses)
-    }
-
-    fn layout_lines(
-        &self,
-        rows: Range<u32>,
-        line_number_layouts: &[Option<ShapedLine>],
-        snapshot: &EditorSnapshot,
-        cx: &ViewContext<Editor>,
-    ) -> Vec<LineWithInvisibles> {
-        if rows.start >= rows.end {
-            return Vec::new();
-        }
-
-        // Show the placeholder when the editor is empty
-        if snapshot.is_empty() {
-            let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
-            let placeholder_color = cx.theme().colors().text_placeholder;
-            let placeholder_text = snapshot.placeholder_text();
-
-            let placeholder_lines = placeholder_text
-                .as_ref()
-                .map_or("", AsRef::as_ref)
-                .split('\n')
-                .skip(rows.start as usize)
-                .chain(iter::repeat(""))
-                .take(rows.len());
-            placeholder_lines
-                .filter_map(move |line| {
-                    let run = TextRun {
-                        len: line.len(),
-                        font: self.style.text.font(),
-                        color: placeholder_color,
-                        background_color: None,
-                        underline: Default::default(),
-                    };
-                    cx.text_system()
-                        .shape_line(line.to_string().into(), font_size, &[run])
-                        .log_err()
-                })
-                .map(|line| LineWithInvisibles {
-                    line,
-                    invisibles: Vec::new(),
-                })
-                .collect()
-        } else {
-            let chunks = snapshot.highlighted_chunks(rows.clone(), true, &self.style);
-            LineWithInvisibles::from_chunks(
-                chunks,
-                &self.style.text,
-                MAX_LINE_LEN,
-                rows.len() as usize,
-                line_number_layouts,
-                snapshot.mode,
-                cx,
-            )
-        }
-    }
-
-    fn compute_layout(&mut self, bounds: Bounds<Pixels>, cx: &mut WindowContext) -> LayoutState {
-        self.editor.update(cx, |editor, cx| {
-            let snapshot = editor.snapshot(cx);
-            let style = self.style.clone();
-
-            let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
-            let font_size = style.text.font_size.to_pixels(cx.rem_size());
-            let line_height = style.text.line_height_in_pixels(cx.rem_size());
-            let em_width = cx
-                .text_system()
-                .typographic_bounds(font_id, font_size, 'm')
-                .unwrap()
-                .size
-                .width;
-            let em_advance = cx
-                .text_system()
-                .advance(font_id, font_size, 'm')
-                .unwrap()
-                .width;
-
-            let gutter_padding;
-            let gutter_width;
-            let gutter_margin;
-            if snapshot.show_gutter {
-                let descent = cx.text_system().descent(font_id, font_size);
-
-                let gutter_padding_factor = 3.5;
-                gutter_padding = (em_width * gutter_padding_factor).round();
-                gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0;
-                gutter_margin = -descent;
-            } else {
-                gutter_padding = Pixels::ZERO;
-                gutter_width = Pixels::ZERO;
-                gutter_margin = Pixels::ZERO;
-            };
-
-            editor.gutter_width = gutter_width;
-
-            let text_width = bounds.size.width - gutter_width;
-            let overscroll = size(em_width, px(0.));
-            let _snapshot = {
-                editor.set_visible_line_count((bounds.size.height / line_height).into(), cx);
-
-                let editor_width = text_width - gutter_margin - overscroll.width - em_width;
-                let wrap_width = match editor.soft_wrap_mode(cx) {
-                    SoftWrap::None => (MAX_LINE_LEN / 2) as f32 * em_advance,
-                    SoftWrap::EditorWidth => editor_width,
-                    SoftWrap::Column(column) => editor_width.min(column as f32 * em_advance),
-                };
-
-                if editor.set_wrap_width(Some(wrap_width), cx) {
-                    editor.snapshot(cx)
-                } else {
-                    snapshot
-                }
-            };
-
-            let wrap_guides = editor
-                .wrap_guides(cx)
-                .iter()
-                .map(|(guide, active)| (self.column_pixels(*guide, cx), *active))
-                .collect::<SmallVec<[_; 2]>>();
-
-            let gutter_size = size(gutter_width, bounds.size.height);
-            let text_size = size(text_width, bounds.size.height);
-
-            let autoscroll_horizontally =
-                editor.autoscroll_vertically(bounds.size.height, line_height, cx);
-            let mut snapshot = editor.snapshot(cx);
-
-            let scroll_position = snapshot.scroll_position();
-            // The scroll position is a fractional point, the whole number of which represents
-            // the top of the window in terms of display rows.
-            let start_row = scroll_position.y as u32;
-            let height_in_lines = f32::from(bounds.size.height / line_height);
-            let max_row = snapshot.max_point().row();
-
-            // Add 1 to ensure selections bleed off screen
-            let end_row = 1 + cmp::min((scroll_position.y + height_in_lines).ceil() as u32, max_row);
-
-            let start_anchor = if start_row == 0 {
-                Anchor::min()
-            } else {
-                snapshot
-                    .buffer_snapshot
-                    .anchor_before(DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left))
-            };
-            let end_anchor = if end_row > max_row {
-                Anchor::max()
-            } else {
-                snapshot
-                    .buffer_snapshot
-                    .anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right))
-            };
-
-            let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new();
-            let mut active_rows = BTreeMap::new();
-            let is_singleton = editor.is_singleton(cx);
-
-            let highlighted_rows = editor.highlighted_rows();
-            let highlighted_ranges = editor.background_highlights_in_range(
-                start_anchor..end_anchor,
-                &snapshot.display_snapshot,
-                cx.theme().colors(),
-            );
-
-            let mut newest_selection_head = None;
-
-            if editor.show_local_selections {
-                let mut local_selections: Vec<Selection<Point>> = editor
-                    .selections
-                    .disjoint_in_range(start_anchor..end_anchor, cx);
-                local_selections.extend(editor.selections.pending(cx));
-                let mut layouts = Vec::new();
-                let newest = editor.selections.newest(cx);
-                for selection in local_selections.drain(..) {
-                    let is_empty = selection.start == selection.end;
-                    let is_newest = selection == newest;
-
-                    let layout = SelectionLayout::new(
-                        selection,
-                        editor.selections.line_mode,
-                        editor.cursor_shape,
-                        &snapshot.display_snapshot,
-                        is_newest,
-                        true,
-                    );
-                    if is_newest {
-                        newest_selection_head = Some(layout.head);
-                    }
-
-                    for row in cmp::max(layout.active_rows.start, start_row)
-                        ..=cmp::min(layout.active_rows.end, end_row)
-                    {
-                        let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty);
-                        *contains_non_empty_selection |= !is_empty;
-                    }
-                    layouts.push(layout);
-                }
-
-                selections.push((style.local_player, layouts));
-            }
-
-            if let Some(collaboration_hub) = &editor.collaboration_hub {
-                // When following someone, render the local selections in their color.
-                if let Some(leader_id) = editor.leader_peer_id {
-                    if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) {
-                        if let Some(participant_index) = collaboration_hub
-                            .user_participant_indices(cx)
-                            .get(&collaborator.user_id)
-                        {
-                            if let Some((local_selection_style, _)) = selections.first_mut() {
-                                *local_selection_style = cx
-                                    .theme()
-                                    .players()
-                                    .color_for_participant(participant_index.0);
-                            }
-                        }
-                    }
-                }
-
-                let mut remote_selections = HashMap::default();
-                for selection in snapshot.remote_selections_in_range(
-                    &(start_anchor..end_anchor),
-                    collaboration_hub.as_ref(),
-                    cx,
-                ) {
-                    let selection_style = if let Some(participant_index) = selection.participant_index {
-                        cx.theme()
-                            .players()
-                            .color_for_participant(participant_index.0)
-                    } else {
-                        cx.theme().players().absent()
-                    };
-
-                    // Don't re-render the leader's selections, since the local selections
-                    // match theirs.
-                    if Some(selection.peer_id) == editor.leader_peer_id {
-                        continue;
-                    }
-
-                    remote_selections
-                        .entry(selection.replica_id)
-                        .or_insert((selection_style, Vec::new()))
-                        .1
-                        .push(SelectionLayout::new(
-                            selection.selection,
-                            selection.line_mode,
-                            selection.cursor_shape,
-                            &snapshot.display_snapshot,
-                            false,
-                            false,
-                        ));
-                }
-
-                selections.extend(remote_selections.into_values());
-            }
-
-            let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
-            let show_scrollbars = match scrollbar_settings.show {
-                ShowScrollbar::Auto => {
-                    // Git
-                    (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs())
-                    ||
-                    // Selections
-                    (is_singleton && scrollbar_settings.selections && !highlighted_ranges.is_empty())
-                    // Scrollmanager
-                    || editor.scroll_manager.scrollbars_visible()
-                }
-                ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(),
-                ShowScrollbar::Always => true,
-                ShowScrollbar::Never => false,
-            };
-
-            let head_for_relative = newest_selection_head.unwrap_or_else(|| {
-                let newest = editor.selections.newest::<Point>(cx);
-                SelectionLayout::new(
-                    newest,
-                    editor.selections.line_mode,
-                    editor.cursor_shape,
-                    &snapshot.display_snapshot,
-                    true,
-                    true,
-                )
-                .head
-            });
-
-            let (line_numbers, fold_statuses) = self.shape_line_numbers(
-                start_row..end_row,
-                &active_rows,
-                head_for_relative,
-                is_singleton,
-                &snapshot,
-                cx,
-            );
-
-            let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot);
-
-            let scrollbar_row_range = scroll_position.y..(scroll_position.y + height_in_lines);
-
-            let mut max_visible_line_width = Pixels::ZERO;
-            let line_layouts = self.layout_lines(start_row..end_row, &line_numbers, &snapshot, cx);
-            for line_with_invisibles in &line_layouts {
-                if line_with_invisibles.line.width > max_visible_line_width {
-                    max_visible_line_width = line_with_invisibles.line.width;
-                }
-            }
-
-            let longest_line_width = layout_line(snapshot.longest_row(), &snapshot, &style, cx)
-                .unwrap()
-                .width;
-            let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.width;
-
-            let (scroll_width, blocks) = cx.with_element_id(Some("editor_blocks"), |cx| {
-                self.layout_blocks(
-                    start_row..end_row,
-                    &snapshot,
-                    bounds.size.width,
-                    scroll_width,
-                    gutter_padding,
-                    gutter_width,
-                    em_width,
-                    gutter_width + gutter_margin,
-                    line_height,
-                    &style,
-                    &line_layouts,
-                    editor,
-                    cx,
-                )
-            });
-
-            let scroll_max = point(
-                f32::from((scroll_width - text_size.width) / em_width).max(0.0),
-                max_row as f32,
-            );
-
-            let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
-
-            let autoscrolled = if autoscroll_horizontally {
-                editor.autoscroll_horizontally(
-                    start_row,
-                    text_size.width,
-                    scroll_width,
-                    em_width,
-                    &line_layouts,
-                    cx,
-                )
-            } else {
-                false
-            };
-
-            if clamped || autoscrolled {
-                snapshot = editor.snapshot(cx);
-            }
-
-            let mut context_menu = None;
-            let mut code_actions_indicator = None;
-            if let Some(newest_selection_head) = newest_selection_head {
-                if (start_row..end_row).contains(&newest_selection_head.row()) {
-                    if editor.context_menu_visible() {
-                        let max_height = (12. * line_height).min((bounds.size.height - line_height) / 2.);
-                        context_menu =
-                            editor.render_context_menu(newest_selection_head, &self.style, max_height, cx);
-                    }
-
-                    let active = matches!(
-                        editor.context_menu.read().as_ref(),
-                        Some(crate::ContextMenu::CodeActions(_))
-                    );
-
-                    code_actions_indicator = editor
-                        .render_code_actions_indicator(&style, active, cx)
-                        .map(|element| CodeActionsIndicator {
-                            row: newest_selection_head.row(),
-                            button: element,
-                        });
-                }
-            }
-
-            let visible_rows = start_row..start_row + line_layouts.len() as u32;
-            let max_size = size(
-                (120. * em_width) // Default size
-                    .min(bounds.size.width / 2.) // Shrink to half of the editor width
-                    .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
-                (16. * line_height) // Default size
-                    .min(bounds.size.height / 2.) // Shrink to half of the editor height
-                    .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
-            );
-
-            let hover = editor.hover_state.render(
-                &snapshot,
-                &style,
-                visible_rows,
-                max_size,
-                editor.workspace.as_ref().map(|(w, _)| w.clone()),
-                cx,
-            );
-
-            let fold_indicators = cx.with_element_id(Some("gutter_fold_indicators"), |cx| {
-                editor.render_fold_indicators(
-                    fold_statuses,
-                    &style,
-                    editor.gutter_hovered,
-                    line_height,
-                    gutter_margin,
-                    cx,
-                )
-            });
-
-            let invisible_symbol_font_size = font_size / 2.;
-            let tab_invisible = cx
-                .text_system()
-                .shape_line(
-                    "→".into(),
-                    invisible_symbol_font_size,
-                    &[TextRun {
-                        len: "→".len(),
-                        font: self.style.text.font(),
-                        color: cx.theme().colors().editor_invisible,
-                        background_color: None,
-                        underline: None,
-                    }],
-                )
-                .unwrap();
-            let space_invisible = cx
-                .text_system()
-                .shape_line(
-                    "•".into(),
-                    invisible_symbol_font_size,
-                    &[TextRun {
-                        len: "•".len(),
-                        font: self.style.text.font(),
-                        color: cx.theme().colors().editor_invisible,
-                        background_color: None,
-                        underline: None,
-                    }],
-                )
-                .unwrap();
-
-            LayoutState {
-                mode: snapshot.mode,
-                position_map: Arc::new(PositionMap {
-                    size: bounds.size,
-                    scroll_position: point(
-                        scroll_position.x * em_width,
-                        scroll_position.y * line_height,
-                    ),
-                    scroll_max,
-                    line_layouts,
-                    line_height,
-                    em_width,
-                    em_advance,
-                    snapshot,
-                }),
-                visible_anchor_range: start_anchor..end_anchor,
-                visible_display_row_range: start_row..end_row,
-                wrap_guides,
-                gutter_size,
-                gutter_padding,
-                text_size,
-                scrollbar_row_range,
-                show_scrollbars,
-                is_singleton,
-                max_row,
-                gutter_margin,
-                active_rows,
-                highlighted_rows,
-                highlighted_ranges,
-                line_numbers,
-                display_hunks,
-                blocks,
-                selections,
-                context_menu,
-                code_actions_indicator,
-                fold_indicators,
-                tab_invisible,
-                space_invisible,
-                hover_popovers: hover,
-            }
-        })
-    }
-
-    #[allow(clippy::too_many_arguments)]
-    fn layout_blocks(
-        &self,
-        rows: Range<u32>,
-        snapshot: &EditorSnapshot,
-        editor_width: Pixels,
-        scroll_width: Pixels,
-        gutter_padding: Pixels,
-        gutter_width: Pixels,
-        em_width: Pixels,
-        text_x: Pixels,
-        line_height: Pixels,
-        style: &EditorStyle,
-        line_layouts: &[LineWithInvisibles],
-        editor: &mut Editor,
-        cx: &mut ViewContext<Editor>,
-    ) -> (Pixels, Vec<BlockLayout>) {
-        let mut block_id = 0;
-        let (fixed_blocks, non_fixed_blocks) = snapshot
-            .blocks_in_range(rows.clone())
-            .partition::<Vec<_>, _>(|(_, block)| match block {
-                TransformBlock::ExcerptHeader { .. } => false,
-                TransformBlock::Custom(block) => block.style() == BlockStyle::Fixed,
-            });
-
-        let render_block = |block: &TransformBlock,
-                            available_space: Size<AvailableSpace>,
-                            block_id: usize,
-                            editor: &mut Editor,
-                            cx: &mut ViewContext<Editor>| {
-            let mut element = match block {
-                TransformBlock::Custom(block) => {
-                    let align_to = block
-                        .position()
-                        .to_point(&snapshot.buffer_snapshot)
-                        .to_display_point(snapshot);
-                    let anchor_x = text_x
-                        + if rows.contains(&align_to.row()) {
-                            line_layouts[(align_to.row() - rows.start) as usize]
-                                .line
-                                .x_for_index(align_to.column() as usize)
-                        } else {
-                            layout_line(align_to.row(), snapshot, style, cx)
-                                .unwrap()
-                                .x_for_index(align_to.column() as usize)
-                        };
-
-                    block.render(&mut BlockContext {
-                        view_context: cx,
-                        anchor_x,
-                        gutter_padding,
-                        line_height,
-                        gutter_width,
-                        em_width,
-                        block_id,
-                        editor_style: &self.style,
-                    })
-                }
-
-                TransformBlock::ExcerptHeader {
-                    buffer,
-                    range,
-                    starts_new_buffer,
-                    ..
-                } => {
-                    let include_root = editor
-                        .project
-                        .as_ref()
-                        .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
-                        .unwrap_or_default();
-
-                    let jump_handler = project::File::from_dyn(buffer.file()).map(|file| {
-                        let jump_path = ProjectPath {
-                            worktree_id: file.worktree_id(cx),
-                            path: file.path.clone(),
-                        };
-                        let jump_anchor = range
-                            .primary
-                            .as_ref()
-                            .map_or(range.context.start, |primary| primary.start);
-                        let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
-
-                        let jump_handler = cx.listener_for(&self.editor, move |editor, _, cx| {
-                            editor.jump(jump_path.clone(), jump_position, jump_anchor, cx);
-                        });
-
-                        jump_handler
-                    });
-
-                    let element = if *starts_new_buffer {
-                        let path = buffer.resolve_file_path(cx, include_root);
-                        let mut filename = None;
-                        let mut parent_path = None;
-                        // Can't use .and_then() because `.file_name()` and `.parent()` return references :(
-                        if let Some(path) = path {
-                            filename = path.file_name().map(|f| f.to_string_lossy().to_string());
-                            parent_path = path
-                                .parent()
-                                .map(|p| SharedString::from(p.to_string_lossy().to_string() + "/"));
-                        }
-
-                        div()
-                            .id(("path header container", block_id))
-                            .size_full()
-                            .p_1p5()
-                            .child(
-                                h_stack()
-                                    .id("path header block")
-                                    .py_1p5()
-                                    .pl_3()
-                                    .pr_2()
-                                    .rounded_lg()
-                                    .shadow_md()
-                                    .border()
-                                    .border_color(cx.theme().colors().border)
-                                    .bg(cx.theme().colors().editor_subheader_background)
-                                    .justify_between()
-                                    .hover(|style| style.bg(cx.theme().colors().element_hover))
-                                    .child(
-                                        h_stack().gap_3().child(
-                                            h_stack()
-                                                .gap_2()
-                                                .child(
-                                                    filename
-                                                        .map(SharedString::from)
-                                                        .unwrap_or_else(|| "untitled".into()),
-                                                )
-                                                .when_some(parent_path, |then, path| {
-                                                    then.child(
-                                                        div().child(path).text_color(
-                                                            cx.theme().colors().text_muted,
-                                                        ),
-                                                    )
-                                                }),
-                                        ),
-                                    )
-                                    .when_some(jump_handler, |this, jump_handler| {
-                                        this.cursor_pointer()
-                                            .tooltip(|cx| {
-                                                Tooltip::for_action(
-                                                    "Jump to Buffer",
-                                                    &OpenExcerpts,
-                                                    cx,
-                                                )
-                                            })
-                                            .on_mouse_down(MouseButton::Left, |_, cx| {
-                                                cx.stop_propagation()
-                                            })
-                                            .on_click(jump_handler)
-                                    }),
-                            )
-                    } else {
-                        h_stack()
-                            .id(("collapsed context", block_id))
-                            .size_full()
-                            .gap(gutter_padding)
-                            .child(
-                                h_stack()
-                                    .justify_end()
-                                    .flex_none()
-                                    .w(gutter_width - gutter_padding)
-                                    .h_full()
-                                    .text_buffer(cx)
-                                    .text_color(cx.theme().colors().editor_line_number)
-                                    .child("..."),
-                            )
-                            .map(|this| {
-                                if let Some(jump_handler) = jump_handler {
-                                    this.child(
-                                        ButtonLike::new("jump to collapsed context")
-                                            .style(ButtonStyle::Transparent)
-                                            .full_width()
-                                            .on_click(jump_handler)
-                                            .tooltip(|cx| {
-                                                Tooltip::for_action(
-                                                    "Jump to Buffer",
-                                                    &OpenExcerpts,
-                                                    cx,
-                                                )
-                                            })
-                                            .child(
-                                                div()
-                                                    .h_px()
-                                                    .w_full()
-                                                    .bg(cx.theme().colors().border_variant)
-                                                    .group_hover("", |style| {
-                                                        style.bg(cx.theme().colors().border)
-                                                    }),
-                                            ),
-                                    )
-                                } else {
-                                    this.child(div().size_full().bg(gpui::green()))
-                                }
-                            })
-                    };
-                    element.into_any()
-                }
-            };
-
-            let size = element.measure(available_space, cx);
-            (element, size)
-        };
-
-        let mut fixed_block_max_width = Pixels::ZERO;
-        let mut blocks = Vec::new();
-        for (row, block) in fixed_blocks {
-            let available_space = size(
-                AvailableSpace::MinContent,
-                AvailableSpace::Definite(block.height() as f32 * line_height),
-            );
-            let (element, element_size) =
-                render_block(block, available_space, block_id, editor, cx);
-            block_id += 1;
-            fixed_block_max_width = fixed_block_max_width.max(element_size.width + em_width);
-            blocks.push(BlockLayout {
-                row,
-                element,
-                available_space,
-                style: BlockStyle::Fixed,
-            });
-        }
-        for (row, block) in non_fixed_blocks {
-            let style = match block {
-                TransformBlock::Custom(block) => block.style(),
-                TransformBlock::ExcerptHeader { .. } => BlockStyle::Sticky,
-            };
-            let width = match style {
-                BlockStyle::Sticky => editor_width,
-                BlockStyle::Flex => editor_width
-                    .max(fixed_block_max_width)
-                    .max(gutter_width + scroll_width),
-                BlockStyle::Fixed => unreachable!(),
-            };
-            let available_space = size(
-                AvailableSpace::Definite(width),
-                AvailableSpace::Definite(block.height() as f32 * line_height),
-            );
-            let (element, _) = render_block(block, available_space, block_id, editor, cx);
-            block_id += 1;
-            blocks.push(BlockLayout {
-                row,
-                element,
-                available_space,
-                style,
-            });
-        }
-        (
-            scroll_width.max(fixed_block_max_width - gutter_width),
-            blocks,
-        )
-    }
-
-    fn paint_mouse_listeners(
-        &mut self,
-        bounds: Bounds<Pixels>,
-        gutter_bounds: Bounds<Pixels>,
-        text_bounds: Bounds<Pixels>,
-        layout: &LayoutState,
-        cx: &mut WindowContext,
-    ) {
-        let interactive_bounds = InteractiveBounds {
-            bounds: bounds.intersect(&cx.content_mask().bounds),
-            stacking_order: cx.stacking_order().clone(),
-        };
-
-        cx.on_mouse_event({
-            let position_map = layout.position_map.clone();
-            let editor = self.editor.clone();
-            let interactive_bounds = interactive_bounds.clone();
-
-            move |event: &ScrollWheelEvent, phase, cx| {
-                if phase == DispatchPhase::Bubble
-                    && interactive_bounds.visibly_contains(&event.position, cx)
-                {
-                    editor.update(cx, |editor, cx| {
-                        Self::scroll(editor, event, &position_map, &interactive_bounds, cx)
-                    });
-                }
-            }
-        });
-
-        cx.on_mouse_event({
-            let position_map = layout.position_map.clone();
-            let editor = self.editor.clone();
-            let stacking_order = cx.stacking_order().clone();
-            let interactive_bounds = interactive_bounds.clone();
-
-            move |event: &MouseDownEvent, phase, cx| {
-                if phase == DispatchPhase::Bubble
-                    && interactive_bounds.visibly_contains(&event.position, cx)
-                {
-                    match event.button {
-                        MouseButton::Left => editor.update(cx, |editor, cx| {
-                            Self::mouse_left_down(
-                                editor,
-                                event,
-                                &position_map,
-                                text_bounds,
-                                gutter_bounds,
-                                &stacking_order,
-                                cx,
-                            );
-                        }),
-                        MouseButton::Right => editor.update(cx, |editor, cx| {
-                            Self::mouse_right_down(editor, event, &position_map, text_bounds, cx);
-                        }),
-                        _ => {}
-                    };
-                }
-            }
-        });
-
-        cx.on_mouse_event({
-            let position_map = layout.position_map.clone();
-            let editor = self.editor.clone();
-            let stacking_order = cx.stacking_order().clone();
-            let interactive_bounds = interactive_bounds.clone();
-
-            move |event: &MouseUpEvent, phase, cx| {
-                if phase == DispatchPhase::Bubble
-                    && interactive_bounds.visibly_contains(&event.position, cx)
-                {
-                    editor.update(cx, |editor, cx| {
-                        Self::mouse_up(
-                            editor,
-                            event,
-                            &position_map,
-                            text_bounds,
-                            &stacking_order,
-                            cx,
-                        )
-                    });
-                }
-            }
-        });
-        cx.on_mouse_event({
-            let position_map = layout.position_map.clone();
-            let editor = self.editor.clone();
-            let stacking_order = cx.stacking_order().clone();
-
-            move |event: &MouseMoveEvent, phase, cx| {
-                // if editor.has_pending_selection() && event.pressed_button == Some(MouseButton::Left) {
-
-                if phase == DispatchPhase::Bubble {
-                    editor.update(cx, |editor, cx| {
-                        if event.pressed_button == Some(MouseButton::Left) {
-                            Self::mouse_dragged(
-                                editor,
-                                event,
-                                &position_map,
-                                text_bounds,
-                                gutter_bounds,
-                                &stacking_order,
-                                cx,
-                            )
-                        }
-
-                        if interactive_bounds.visibly_contains(&event.position, cx) {
-                            Self::mouse_moved(
-                                editor,
-                                event,
-                                &position_map,
-                                text_bounds,
-                                gutter_bounds,
-                                &stacking_order,
-                                cx,
-                            )
-                        }
-                    });
-                }
-            }
-        });
-    }
-}
-
-#[derive(Debug)]
-pub struct LineWithInvisibles {
-    pub line: ShapedLine,
-    invisibles: Vec<Invisible>,
-}
-
-impl LineWithInvisibles {
-    fn from_chunks<'a>(
-        chunks: impl Iterator<Item = HighlightedChunk<'a>>,
-        text_style: &TextStyle,
-        max_line_len: usize,
-        max_line_count: usize,
-        line_number_layouts: &[Option<ShapedLine>],
-        editor_mode: EditorMode,
-        cx: &WindowContext,
-    ) -> Vec<Self> {
-        let mut layouts = Vec::with_capacity(max_line_count);
-        let mut line = String::new();
-        let mut invisibles = Vec::new();
-        let mut styles = Vec::new();
-        let mut non_whitespace_added = false;
-        let mut row = 0;
-        let mut line_exceeded_max_len = false;
-        let font_size = text_style.font_size.to_pixels(cx.rem_size());
-
-        for highlighted_chunk in chunks.chain([HighlightedChunk {
-            chunk: "\n",
-            style: None,
-            is_tab: false,
-        }]) {
-            for (ix, mut line_chunk) in highlighted_chunk.chunk.split('\n').enumerate() {
-                if ix > 0 {
-                    let shaped_line = cx
-                        .text_system()
-                        .shape_line(line.clone().into(), font_size, &styles)
-                        .unwrap();
-                    layouts.push(Self {
-                        line: shaped_line,
-                        invisibles: invisibles.drain(..).collect(),
-                    });
-
-                    line.clear();
-                    styles.clear();
-                    row += 1;
-                    line_exceeded_max_len = false;
-                    non_whitespace_added = false;
-                    if row == max_line_count {
-                        return layouts;
-                    }
-                }
-
-                if !line_chunk.is_empty() && !line_exceeded_max_len {
-                    let text_style = if let Some(style) = highlighted_chunk.style {
-                        Cow::Owned(text_style.clone().highlight(style))
-                    } else {
-                        Cow::Borrowed(text_style)
-                    };
-
-                    if line.len() + line_chunk.len() > max_line_len {
-                        let mut chunk_len = max_line_len - line.len();
-                        while !line_chunk.is_char_boundary(chunk_len) {
-                            chunk_len -= 1;
-                        }
-                        line_chunk = &line_chunk[..chunk_len];
-                        line_exceeded_max_len = true;
-                    }
-
-                    styles.push(TextRun {
-                        len: line_chunk.len(),
-                        font: text_style.font(),
-                        color: text_style.color,
-                        background_color: text_style.background_color,
-                        underline: text_style.underline,
-                    });
-
-                    if editor_mode == EditorMode::Full {
-                        // Line wrap pads its contents with fake whitespaces,
-                        // avoid printing them
-                        let inside_wrapped_string = line_number_layouts
-                            .get(row)
-                            .and_then(|layout| layout.as_ref())
-                            .is_none();
-                        if highlighted_chunk.is_tab {
-                            if non_whitespace_added || !inside_wrapped_string {
-                                invisibles.push(Invisible::Tab {
-                                    line_start_offset: line.len(),
-                                });
-                            }
-                        } else {
-                            invisibles.extend(
-                                line_chunk
-                                    .chars()
-                                    .enumerate()
-                                    .filter(|(_, line_char)| {
-                                        let is_whitespace = line_char.is_whitespace();
-                                        non_whitespace_added |= !is_whitespace;
-                                        is_whitespace
-                                            && (non_whitespace_added || !inside_wrapped_string)
-                                    })
-                                    .map(|(whitespace_index, _)| Invisible::Whitespace {
-                                        line_offset: line.len() + whitespace_index,
-                                    }),
-                            )
-                        }
-                    }
-
-                    line.push_str(line_chunk);
-                }
-            }
-        }
-
-        layouts
-    }
-
-    fn draw(
-        &self,
-        layout: &LayoutState,
-        row: u32,
-        content_origin: gpui::Point<Pixels>,
-        whitespace_setting: ShowWhitespaceSetting,
-        selection_ranges: &[Range<DisplayPoint>],
-        cx: &mut WindowContext,
-    ) {
-        let line_height = layout.position_map.line_height;
-        let line_y = line_height * row as f32 - layout.position_map.scroll_position.y;
-
-        self.line
-            .paint(
-                content_origin + gpui::point(-layout.position_map.scroll_position.x, line_y),
-                line_height,
-                cx,
-            )
-            .log_err();
-
-        self.draw_invisibles(
-            &selection_ranges,
-            layout,
-            content_origin,
-            line_y,
-            row,
-            line_height,
-            whitespace_setting,
-            cx,
-        );
-    }
-
-    fn draw_invisibles(
-        &self,
-        selection_ranges: &[Range<DisplayPoint>],
-        layout: &LayoutState,
-        content_origin: gpui::Point<Pixels>,
-        line_y: Pixels,
-        row: u32,
-        line_height: Pixels,
-        whitespace_setting: ShowWhitespaceSetting,
-        cx: &mut WindowContext,
-    ) {
-        let allowed_invisibles_regions = match whitespace_setting {
-            ShowWhitespaceSetting::None => return,
-            ShowWhitespaceSetting::Selection => Some(selection_ranges),
-            ShowWhitespaceSetting::All => None,
-        };
-
-        for invisible in &self.invisibles {
-            let (&token_offset, invisible_symbol) = match invisible {
-                Invisible::Tab { line_start_offset } => (line_start_offset, &layout.tab_invisible),
-                Invisible::Whitespace { line_offset } => (line_offset, &layout.space_invisible),
-            };
-
-            let x_offset = self.line.x_for_index(token_offset);
-            let invisible_offset =
-                (layout.position_map.em_width - invisible_symbol.width).max(Pixels::ZERO) / 2.0;
-            let origin = content_origin
-                + gpui::point(
-                    x_offset + invisible_offset - layout.position_map.scroll_position.x,
-                    line_y,
-                );
-
-            if let Some(allowed_regions) = allowed_invisibles_regions {
-                let invisible_point = DisplayPoint::new(row, token_offset as u32);
-                if !allowed_regions
-                    .iter()
-                    .any(|region| region.start <= invisible_point && invisible_point < region.end)
-                {
-                    continue;
-                }
-            }
-            invisible_symbol.paint(origin, line_height, cx).log_err();
-        }
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-enum Invisible {
-    Tab { line_start_offset: usize },
-    Whitespace { line_offset: usize },
-}
-
-impl Element for EditorElement {
-    type State = ();
-
-    fn request_layout(
-        &mut self,
-        _element_state: Option<Self::State>,
-        cx: &mut gpui::WindowContext,
-    ) -> (gpui::LayoutId, Self::State) {
-        self.editor.update(cx, |editor, cx| {
-            editor.set_style(self.style.clone(), cx);
-
-            let layout_id = match editor.mode {
-                EditorMode::SingleLine => {
-                    let rem_size = cx.rem_size();
-                    let mut style = Style::default();
-                    style.size.width = relative(1.).into();
-                    style.size.height = self.style.text.line_height_in_pixels(rem_size).into();
-                    cx.request_layout(&style, None)
-                }
-                EditorMode::AutoHeight { max_lines } => {
-                    let editor_handle = cx.view().clone();
-                    let max_line_number_width =
-                        self.max_line_number_width(&editor.snapshot(cx), cx);
-                    cx.request_measured_layout(Style::default(), move |known_dimensions, _, cx| {
-                        editor_handle
-                            .update(cx, |editor, cx| {
-                                compute_auto_height_layout(
-                                    editor,
-                                    max_lines,
-                                    max_line_number_width,
-                                    known_dimensions,
-                                    cx,
-                                )
-                            })
-                            .unwrap_or_default()
-                    })
-                }
-                EditorMode::Full => {
-                    let mut style = Style::default();
-                    style.size.width = relative(1.).into();
-                    style.size.height = relative(1.).into();
-                    cx.request_layout(&style, None)
-                }
-            };
-
-            (layout_id, ())
-        })
-    }
-
-    fn paint(
-        &mut self,
-        bounds: Bounds<gpui::Pixels>,
-        _element_state: &mut Self::State,
-        cx: &mut gpui::WindowContext,
-    ) {
-        let editor = self.editor.clone();
-
-        cx.with_text_style(
-            Some(gpui::TextStyleRefinement {
-                font_size: Some(self.style.text.font_size),
-                ..Default::default()
-            }),
-            |cx| {
-                let mut layout = self.compute_layout(bounds, cx);
-                let gutter_bounds = Bounds {
-                    origin: bounds.origin,
-                    size: layout.gutter_size,
-                };
-                let text_bounds = Bounds {
-                    origin: gutter_bounds.upper_right(),
-                    size: layout.text_size,
-                };
-
-                let focus_handle = editor.focus_handle(cx);
-                let key_context = self.editor.read(cx).key_context(cx);
-                cx.with_key_dispatch(Some(key_context), Some(focus_handle.clone()), |_, cx| {
-                    self.register_actions(cx);
-                    self.register_key_listeners(cx);
-
-                    cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
-                        let input_handler =
-                            ElementInputHandler::new(bounds, self.editor.clone(), cx);
-                        cx.handle_input(&focus_handle, input_handler);
-
-                        self.paint_background(gutter_bounds, text_bounds, &layout, cx);
-                        if layout.gutter_size.width > Pixels::ZERO {
-                            self.paint_gutter(gutter_bounds, &mut layout, cx);
-                        }
-                        self.paint_text(text_bounds, &mut layout, cx);
-
-                        cx.with_z_index(0, |cx| {
-                            self.paint_mouse_listeners(
-                                bounds,
-                                gutter_bounds,
-                                text_bounds,
-                                &layout,
-                                cx,
-                            );
-                        });
-                        if !layout.blocks.is_empty() {
-                            cx.with_z_index(0, |cx| {
-                                cx.with_element_id(Some("editor_blocks"), |cx| {
-                                    self.paint_blocks(bounds, &mut layout, cx);
-                                });
-                            })
-                        }
-
-                        cx.with_z_index(1, |cx| {
-                            self.paint_overlays(text_bounds, &mut layout, cx);
-                        });
-
-                        cx.with_z_index(2, |cx| self.paint_scrollbar(bounds, &mut layout, cx));
-                    });
-                })
-            },
-        );
-    }
-}
-
-impl IntoElement for EditorElement {
-    type Element = Self;
-
-    fn element_id(&self) -> Option<gpui::ElementId> {
-        self.editor.element_id()
-    }
-
-    fn into_element(self) -> Self::Element {
-        self
-    }
-}
-
-type BufferRow = u32;
-
-pub struct LayoutState {
-    position_map: Arc<PositionMap>,
-    gutter_size: Size<Pixels>,
-    gutter_padding: Pixels,
-    gutter_margin: Pixels,
-    text_size: gpui::Size<Pixels>,
-    mode: EditorMode,
-    wrap_guides: SmallVec<[(Pixels, bool); 2]>,
-    visible_anchor_range: Range<Anchor>,
-    visible_display_row_range: Range<u32>,
-    active_rows: BTreeMap<u32, bool>,
-    highlighted_rows: Option<Range<u32>>,
-    line_numbers: Vec<Option<ShapedLine>>,
-    display_hunks: Vec<DisplayDiffHunk>,
-    blocks: Vec<BlockLayout>,
-    highlighted_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
-    selections: Vec<(PlayerColor, Vec<SelectionLayout>)>,
-    scrollbar_row_range: Range<f32>,
-    show_scrollbars: bool,
-    is_singleton: bool,
-    max_row: u32,
-    context_menu: Option<(DisplayPoint, AnyElement)>,
-    code_actions_indicator: Option<CodeActionsIndicator>,
-    hover_popovers: Option<(DisplayPoint, Vec<AnyElement>)>,
-    fold_indicators: Vec<Option<IconButton>>,
-    tab_invisible: ShapedLine,
-    space_invisible: ShapedLine,
-}
-
-struct CodeActionsIndicator {
-    row: u32,
-    button: IconButton,
-}
-
-struct PositionMap {
-    size: Size<Pixels>,
-    line_height: Pixels,
-    scroll_position: gpui::Point<Pixels>,
-    scroll_max: gpui::Point<f32>,
-    em_width: Pixels,
-    em_advance: Pixels,
-    line_layouts: Vec<LineWithInvisibles>,
-    snapshot: EditorSnapshot,
-}
-
-#[derive(Debug, Copy, Clone)]
-pub struct PointForPosition {
-    pub previous_valid: DisplayPoint,
-    pub next_valid: DisplayPoint,
-    pub exact_unclipped: DisplayPoint,
-    pub column_overshoot_after_line_end: u32,
-}
-
-impl PointForPosition {
-    #[cfg(test)]
-    pub fn valid(valid: DisplayPoint) -> Self {
-        Self {
-            previous_valid: valid,
-            next_valid: valid,
-            exact_unclipped: valid,
-            column_overshoot_after_line_end: 0,
-        }
-    }
-
-    pub fn as_valid(&self) -> Option<DisplayPoint> {
-        if self.previous_valid == self.exact_unclipped && self.next_valid == self.exact_unclipped {
-            Some(self.previous_valid)
-        } else {
-            None
-        }
-    }
-}
-
-impl PositionMap {
-    fn point_for_position(
-        &self,
-        text_bounds: Bounds<Pixels>,
-        position: gpui::Point<Pixels>,
-    ) -> PointForPosition {
-        let scroll_position = self.snapshot.scroll_position();
-        let position = position - text_bounds.origin;
-        let y = position.y.max(px(0.)).min(self.size.height);
-        let x = position.x + (scroll_position.x * self.em_width);
-        let row = (f32::from(y / self.line_height) + scroll_position.y) as u32;
-
-        let (column, x_overshoot_after_line_end) = if let Some(line) = self
-            .line_layouts
-            .get(row as usize - scroll_position.y as usize)
-            .map(|&LineWithInvisibles { ref line, .. }| line)
-        {
-            if let Some(ix) = line.index_for_x(x) {
-                (ix as u32, px(0.))
-            } else {
-                (line.len as u32, px(0.).max(x - line.width))
-            }
-        } else {
-            (0, x)
-        };
-
-        let mut exact_unclipped = DisplayPoint::new(row, column);
-        let previous_valid = self.snapshot.clip_point(exact_unclipped, Bias::Left);
-        let next_valid = self.snapshot.clip_point(exact_unclipped, Bias::Right);
-
-        let column_overshoot_after_line_end = (x_overshoot_after_line_end / self.em_advance) as u32;
-        *exact_unclipped.column_mut() += column_overshoot_after_line_end;
-        PointForPosition {
-            previous_valid,
-            next_valid,
-            exact_unclipped,
-            column_overshoot_after_line_end,
-        }
-    }
-}
-
-struct BlockLayout {
-    row: u32,
-    element: AnyElement,
-    available_space: Size<AvailableSpace>,
-    style: BlockStyle,
-}
-
-fn layout_line(
-    row: u32,
-    snapshot: &EditorSnapshot,
-    style: &EditorStyle,
-    cx: &WindowContext,
-) -> Result<ShapedLine> {
-    let mut line = snapshot.line(row);
-
-    if line.len() > MAX_LINE_LEN {
-        let mut len = MAX_LINE_LEN;
-        while !line.is_char_boundary(len) {
-            len -= 1;
-        }
-
-        line.truncate(len);
-    }
-
-    cx.text_system().shape_line(
-        line.into(),
-        style.text.font_size.to_pixels(cx.rem_size()),
-        &[TextRun {
-            len: snapshot.line_len(row) as usize,
-            font: style.text.font(),
-            color: Hsla::default(),
-            background_color: None,
-            underline: None,
-        }],
-    )
-}
-
-#[derive(Debug)]
-pub struct Cursor {
-    origin: gpui::Point<Pixels>,
-    block_width: Pixels,
-    line_height: Pixels,
-    color: Hsla,
-    shape: CursorShape,
-    block_text: Option<ShapedLine>,
-}
-
-impl Cursor {
-    pub fn new(
-        origin: gpui::Point<Pixels>,
-        block_width: Pixels,
-        line_height: Pixels,
-        color: Hsla,
-        shape: CursorShape,
-        block_text: Option<ShapedLine>,
-    ) -> Cursor {
-        Cursor {
-            origin,
-            block_width,
-            line_height,
-            color,
-            shape,
-            block_text,
-        }
-    }
-
-    pub fn bounding_rect(&self, origin: gpui::Point<Pixels>) -> Bounds<Pixels> {
-        Bounds {
-            origin: self.origin + origin,
-            size: size(self.block_width, self.line_height),
-        }
-    }
-
-    pub fn paint(&self, origin: gpui::Point<Pixels>, cx: &mut WindowContext) {
-        let bounds = match self.shape {
-            CursorShape::Bar => Bounds {
-                origin: self.origin + origin,
-                size: size(px(2.0), self.line_height),
-            },
-            CursorShape::Block | CursorShape::Hollow => Bounds {
-                origin: self.origin + origin,
-                size: size(self.block_width, self.line_height),
-            },
-            CursorShape::Underscore => Bounds {
-                origin: self.origin
-                    + origin
-                    + gpui::Point::new(Pixels::ZERO, self.line_height - px(2.0)),
-                size: size(self.block_width, px(2.0)),
-            },
-        };
-
-        //Draw background or border quad
-        let cursor = if matches!(self.shape, CursorShape::Hollow) {
-            outline(bounds, self.color)
-        } else {
-            fill(bounds, self.color)
-        };
-
-        cx.paint_quad(cursor);
-
-        if let Some(block_text) = &self.block_text {
-            block_text
-                .paint(self.origin + origin, self.line_height, cx)
-                .log_err();
-        }
-    }
-
-    pub fn shape(&self) -> CursorShape {
-        self.shape
-    }
-}
-
-#[derive(Debug)]
-pub struct HighlightedRange {
-    pub start_y: Pixels,
-    pub line_height: Pixels,
-    pub lines: Vec<HighlightedRangeLine>,
-    pub color: Hsla,
-    pub corner_radius: Pixels,
-}
-
-#[derive(Debug)]
-pub struct HighlightedRangeLine {
-    pub start_x: Pixels,
-    pub end_x: Pixels,
-}
-
-impl HighlightedRange {
-    pub fn paint(&self, bounds: Bounds<Pixels>, cx: &mut WindowContext) {
-        if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
-            self.paint_lines(self.start_y, &self.lines[0..1], bounds, cx);
-            self.paint_lines(
-                self.start_y + self.line_height,
-                &self.lines[1..],
-                bounds,
-                cx,
-            );
-        } else {
-            self.paint_lines(self.start_y, &self.lines, bounds, cx);
-        }
-    }
-
-    fn paint_lines(
-        &self,
-        start_y: Pixels,
-        lines: &[HighlightedRangeLine],
-        _bounds: Bounds<Pixels>,
-        cx: &mut WindowContext,
-    ) {
-        if lines.is_empty() {
-            return;
-        }
-
-        let first_line = lines.first().unwrap();
-        let last_line = lines.last().unwrap();
-
-        let first_top_left = point(first_line.start_x, start_y);
-        let first_top_right = point(first_line.end_x, start_y);
-
-        let curve_height = point(Pixels::ZERO, self.corner_radius);
-        let curve_width = |start_x: Pixels, end_x: Pixels| {
-            let max = (end_x - start_x) / 2.;
-            let width = if max < self.corner_radius {
-                max
-            } else {
-                self.corner_radius
-            };
-
-            point(width, Pixels::ZERO)
-        };
-
-        let top_curve_width = curve_width(first_line.start_x, first_line.end_x);
-        let mut path = gpui::Path::new(first_top_right - top_curve_width);
-        path.curve_to(first_top_right + curve_height, first_top_right);
-
-        let mut iter = lines.iter().enumerate().peekable();
-        while let Some((ix, line)) = iter.next() {
-            let bottom_right = point(line.end_x, start_y + (ix + 1) as f32 * self.line_height);
-
-            if let Some((_, next_line)) = iter.peek() {
-                let next_top_right = point(next_line.end_x, bottom_right.y);
-
-                match next_top_right.x.partial_cmp(&bottom_right.x).unwrap() {
-                    Ordering::Equal => {
-                        path.line_to(bottom_right);
-                    }
-                    Ordering::Less => {
-                        let curve_width = curve_width(next_top_right.x, bottom_right.x);
-                        path.line_to(bottom_right - curve_height);
-                        if self.corner_radius > Pixels::ZERO {
-                            path.curve_to(bottom_right - curve_width, bottom_right);
-                        }
-                        path.line_to(next_top_right + curve_width);
-                        if self.corner_radius > Pixels::ZERO {
-                            path.curve_to(next_top_right + curve_height, next_top_right);
-                        }
-                    }
-                    Ordering::Greater => {
-                        let curve_width = curve_width(bottom_right.x, next_top_right.x);
-                        path.line_to(bottom_right - curve_height);
-                        if self.corner_radius > Pixels::ZERO {
-                            path.curve_to(bottom_right + curve_width, bottom_right);
-                        }
-                        path.line_to(next_top_right - curve_width);
-                        if self.corner_radius > Pixels::ZERO {
-                            path.curve_to(next_top_right + curve_height, next_top_right);
-                        }
-                    }
-                }
-            } else {
-                let curve_width = curve_width(line.start_x, line.end_x);
-                path.line_to(bottom_right - curve_height);
-                if self.corner_radius > Pixels::ZERO {
-                    path.curve_to(bottom_right - curve_width, bottom_right);
-                }
-
-                let bottom_left = point(line.start_x, bottom_right.y);
-                path.line_to(bottom_left + curve_width);
-                if self.corner_radius > Pixels::ZERO {
-                    path.curve_to(bottom_left - curve_height, bottom_left);
-                }
-            }
-        }
-
-        if first_line.start_x > last_line.start_x {
-            let curve_width = curve_width(last_line.start_x, first_line.start_x);
-            let second_top_left = point(last_line.start_x, start_y + self.line_height);
-            path.line_to(second_top_left + curve_height);
-            if self.corner_radius > Pixels::ZERO {
-                path.curve_to(second_top_left + curve_width, second_top_left);
-            }
-            let first_bottom_left = point(first_line.start_x, second_top_left.y);
-            path.line_to(first_bottom_left - curve_width);
-            if self.corner_radius > Pixels::ZERO {
-                path.curve_to(first_bottom_left - curve_height, first_bottom_left);
-            }
-        }
-
-        path.line_to(first_top_left + curve_height);
-        if self.corner_radius > Pixels::ZERO {
-            path.curve_to(first_top_left + top_curve_width, first_top_left);
-        }
-        path.line_to(first_top_right - top_curve_width);
-
-        cx.paint_path(path, self.color);
-    }
-}
-
-pub fn scale_vertical_mouse_autoscroll_delta(delta: Pixels) -> f32 {
-    (delta.pow(1.5) / 100.0).into()
-}
-
-fn scale_horizontal_mouse_autoscroll_delta(delta: Pixels) -> f32 {
-    (delta.pow(1.2) / 300.0).into()
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::{
-        display_map::{BlockDisposition, BlockProperties},
-        editor_tests::{init_test, update_test_language_settings},
-        Editor, MultiBuffer,
-    };
-    use gpui::TestAppContext;
-    use language::language_settings;
-    use log::info;
-    use std::{num::NonZeroU32, sync::Arc};
-    use util::test::sample_text;
-
-    #[gpui::test]
-    fn test_shape_line_numbers(cx: &mut TestAppContext) {
-        init_test(cx, |_| {});
-        let window = cx.add_window(|cx| {
-            let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
-            Editor::new(EditorMode::Full, buffer, None, cx)
-        });
-
-        let editor = window.root(cx).unwrap();
-        let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
-        let element = EditorElement::new(&editor, style);
-
-        let layouts = window
-            .update(cx, |editor, cx| {
-                let snapshot = editor.snapshot(cx);
-                element
-                    .shape_line_numbers(
-                        0..6,
-                        &Default::default(),
-                        DisplayPoint::new(0, 0),
-                        false,
-                        &snapshot,
-                        cx,
-                    )
-                    .0
-            })
-            .unwrap();
-        assert_eq!(layouts.len(), 6);
-
-        let relative_rows = window
-            .update(cx, |editor, cx| {
-                let snapshot = editor.snapshot(cx);
-                element.calculate_relative_line_numbers(&snapshot, &(0..6), Some(3))
-            })
-            .unwrap();
-        assert_eq!(relative_rows[&0], 3);
-        assert_eq!(relative_rows[&1], 2);
-        assert_eq!(relative_rows[&2], 1);
-        // current line has no relative number
-        assert_eq!(relative_rows[&4], 1);
-        assert_eq!(relative_rows[&5], 2);
-
-        // works if cursor is before screen
-        let relative_rows = window
-            .update(cx, |editor, cx| {
-                let snapshot = editor.snapshot(cx);
-
-                element.calculate_relative_line_numbers(&snapshot, &(3..6), Some(1))
-            })
-            .unwrap();
-        assert_eq!(relative_rows.len(), 3);
-        assert_eq!(relative_rows[&3], 2);
-        assert_eq!(relative_rows[&4], 3);
-        assert_eq!(relative_rows[&5], 4);
-
-        // works if cursor is after screen
-        let relative_rows = window
-            .update(cx, |editor, cx| {
-                let snapshot = editor.snapshot(cx);
-
-                element.calculate_relative_line_numbers(&snapshot, &(0..3), Some(6))
-            })
-            .unwrap();
-        assert_eq!(relative_rows.len(), 3);
-        assert_eq!(relative_rows[&0], 5);
-        assert_eq!(relative_rows[&1], 4);
-        assert_eq!(relative_rows[&2], 3);
-    }
-
-    #[gpui::test]
-    async fn test_vim_visual_selections(cx: &mut TestAppContext) {
-        init_test(cx, |_| {});
-
-        let window = cx.add_window(|cx| {
-            let buffer = MultiBuffer::build_simple(&(sample_text(6, 6, 'a') + "\n"), cx);
-            Editor::new(EditorMode::Full, buffer, None, cx)
-        });
-        let editor = window.root(cx).unwrap();
-        let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
-        let mut element = EditorElement::new(&editor, style);
-
-        window
-            .update(cx, |editor, cx| {
-                editor.cursor_shape = CursorShape::Block;
-                editor.change_selections(None, cx, |s| {
-                    s.select_ranges([
-                        Point::new(0, 0)..Point::new(1, 0),
-                        Point::new(3, 2)..Point::new(3, 3),
-                        Point::new(5, 6)..Point::new(6, 0),
-                    ]);
-                });
-            })
-            .unwrap();
-        let state = cx
-            .update_window(window.into(), |_, cx| {
-                element.compute_layout(
-                    Bounds {
-                        origin: point(px(500.), px(500.)),
-                        size: size(px(500.), px(500.)),
-                    },
-                    cx,
-                )
-            })
-            .unwrap();
-
-        assert_eq!(state.selections.len(), 1);
-        let local_selections = &state.selections[0].1;
-        assert_eq!(local_selections.len(), 3);
-        // moves cursor back one line
-        assert_eq!(local_selections[0].head, DisplayPoint::new(0, 6));
-        assert_eq!(
-            local_selections[0].range,
-            DisplayPoint::new(0, 0)..DisplayPoint::new(1, 0)
-        );
-
-        // moves cursor back one column
-        assert_eq!(
-            local_selections[1].range,
-            DisplayPoint::new(3, 2)..DisplayPoint::new(3, 3)
-        );
-        assert_eq!(local_selections[1].head, DisplayPoint::new(3, 2));
-
-        // leaves cursor on the max point
-        assert_eq!(
-            local_selections[2].range,
-            DisplayPoint::new(5, 6)..DisplayPoint::new(6, 0)
-        );
-        assert_eq!(local_selections[2].head, DisplayPoint::new(6, 0));
-
-        // active lines does not include 1 (even though the range of the selection does)
-        assert_eq!(
-            state.active_rows.keys().cloned().collect::<Vec<u32>>(),
-            vec![0, 3, 5, 6]
-        );
-
-        // multi-buffer support
-        // in DisplayPoint co-ordinates, this is what we're dealing with:
-        //  0: [[file
-        //  1:   header]]
-        //  2: aaaaaa
-        //  3: bbbbbb
-        //  4: cccccc
-        //  5:
-        //  6: ...
-        //  7: ffffff
-        //  8: gggggg
-        //  9: hhhhhh
-        // 10:
-        // 11: [[file
-        // 12:   header]]
-        // 13: bbbbbb
-        // 14: cccccc
-        // 15: dddddd
-        let window = cx.add_window(|cx| {
-            let buffer = MultiBuffer::build_multi(
-                [
-                    (
-                        &(sample_text(8, 6, 'a') + "\n"),
-                        vec![
-                            Point::new(0, 0)..Point::new(3, 0),
-                            Point::new(4, 0)..Point::new(7, 0),
-                        ],
-                    ),
-                    (
-                        &(sample_text(8, 6, 'a') + "\n"),
-                        vec![Point::new(1, 0)..Point::new(3, 0)],
-                    ),
-                ],
-                cx,
-            );
-            Editor::new(EditorMode::Full, buffer, None, cx)
-        });
-        let editor = window.root(cx).unwrap();
-        let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
-        let mut element = EditorElement::new(&editor, style);
-        let _state = window.update(cx, |editor, cx| {
-            editor.cursor_shape = CursorShape::Block;
-            editor.change_selections(None, cx, |s| {
-                s.select_display_ranges([
-                    DisplayPoint::new(4, 0)..DisplayPoint::new(7, 0),
-                    DisplayPoint::new(10, 0)..DisplayPoint::new(13, 0),
-                ]);
-            });
-        });
-
-        let state = cx
-            .update_window(window.into(), |_, cx| {
-                element.compute_layout(
-                    Bounds {
-                        origin: point(px(500.), px(500.)),
-                        size: size(px(500.), px(500.)),
-                    },
-                    cx,
-                )
-            })
-            .unwrap();
-        assert_eq!(state.selections.len(), 1);
-        let local_selections = &state.selections[0].1;
-        assert_eq!(local_selections.len(), 2);
-
-        // moves cursor on excerpt boundary back a line
-        // and doesn't allow selection to bleed through
-        assert_eq!(
-            local_selections[0].range,
-            DisplayPoint::new(4, 0)..DisplayPoint::new(6, 0)
-        );
-        assert_eq!(local_selections[0].head, DisplayPoint::new(5, 0));
-        // moves cursor on buffer boundary back two lines
-        // and doesn't allow selection to bleed through
-        assert_eq!(
-            local_selections[1].range,
-            DisplayPoint::new(10, 0)..DisplayPoint::new(11, 0)
-        );
-        assert_eq!(local_selections[1].head, DisplayPoint::new(10, 0));
-    }
-
-    #[gpui::test]
-    fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
-        init_test(cx, |_| {});
-
-        let window = cx.add_window(|cx| {
-            let buffer = MultiBuffer::build_simple("", cx);
-            Editor::new(EditorMode::Full, buffer, None, cx)
-        });
-        let editor = window.root(cx).unwrap();
-        let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
-        window
-            .update(cx, |editor, cx| {
-                editor.set_placeholder_text("hello", cx);
-                editor.insert_blocks(
-                    [BlockProperties {
-                        style: BlockStyle::Fixed,
-                        disposition: BlockDisposition::Above,
-                        height: 3,
-                        position: Anchor::min(),
-                        render: Arc::new(|_| div().into_any()),
-                    }],
-                    None,
-                    cx,
-                );
-
-                // Blur the editor so that it displays placeholder text.
-                cx.blur();
-            })
-            .unwrap();
-
-        let mut element = EditorElement::new(&editor, style);
-        let state = cx
-            .update_window(window.into(), |_, cx| {
-                element.compute_layout(
-                    Bounds {
-                        origin: point(px(500.), px(500.)),
-                        size: size(px(500.), px(500.)),
-                    },
-                    cx,
-                )
-            })
-            .unwrap();
-        let size = state.position_map.size;
-
-        assert_eq!(state.position_map.line_layouts.len(), 4);
-        assert_eq!(
-            state
-                .line_numbers
-                .iter()
-                .map(Option::is_some)
-                .collect::<Vec<_>>(),
-            &[false, false, false, true]
-        );
-
-        // Don't panic.
-        let bounds = Bounds::<Pixels>::new(Default::default(), size);
-        cx.update_window(window.into(), |_, cx| {
-            element.paint(bounds, &mut (), cx);
-        })
-        .unwrap()
-    }
-
-    #[gpui::test]
-    fn test_all_invisibles_drawing(cx: &mut TestAppContext) {
-        const TAB_SIZE: u32 = 4;
-
-        let input_text = "\t \t|\t| a b";
-        let expected_invisibles = vec![
-            Invisible::Tab {
-                line_start_offset: 0,
-            },
-            Invisible::Whitespace {
-                line_offset: TAB_SIZE as usize,
-            },
-            Invisible::Tab {
-                line_start_offset: TAB_SIZE as usize + 1,
-            },
-            Invisible::Tab {
-                line_start_offset: TAB_SIZE as usize * 2 + 1,
-            },
-            Invisible::Whitespace {
-                line_offset: TAB_SIZE as usize * 3 + 1,
-            },
-            Invisible::Whitespace {
-                line_offset: TAB_SIZE as usize * 3 + 3,
-            },
-        ];
-        assert_eq!(
-            expected_invisibles.len(),
-            input_text
-                .chars()
-                .filter(|initial_char| initial_char.is_whitespace())
-                .count(),
-            "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
-        );
-
-        init_test(cx, |s| {
-            s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
-            s.defaults.tab_size = NonZeroU32::new(TAB_SIZE);
-        });
-
-        let actual_invisibles =
-            collect_invisibles_from_new_editor(cx, EditorMode::Full, &input_text, px(500.0));
-
-        assert_eq!(expected_invisibles, actual_invisibles);
-    }
-
-    #[gpui::test]
-    fn test_invisibles_dont_appear_in_certain_editors(cx: &mut TestAppContext) {
-        init_test(cx, |s| {
-            s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
-            s.defaults.tab_size = NonZeroU32::new(4);
-        });
-
-        for editor_mode_without_invisibles in [
-            EditorMode::SingleLine,
-            EditorMode::AutoHeight { max_lines: 100 },
-        ] {
-            let invisibles = collect_invisibles_from_new_editor(
-                cx,
-                editor_mode_without_invisibles,
-                "\t\t\t| | a b",
-                px(500.0),
-            );
-            assert!(invisibles.is_empty(),
-                    "For editor mode {editor_mode_without_invisibles:?} no invisibles was expected but got {invisibles:?}");
-        }
-    }
-
-    #[gpui::test]
-    fn test_wrapped_invisibles_drawing(cx: &mut TestAppContext) {
-        let tab_size = 4;
-        let input_text = "a\tbcd   ".repeat(9);
-        let repeated_invisibles = [
-            Invisible::Tab {
-                line_start_offset: 1,
-            },
-            Invisible::Whitespace {
-                line_offset: tab_size as usize + 3,
-            },
-            Invisible::Whitespace {
-                line_offset: tab_size as usize + 4,
-            },
-            Invisible::Whitespace {
-                line_offset: tab_size as usize + 5,
-            },
-        ];
-        let expected_invisibles = std::iter::once(repeated_invisibles)
-            .cycle()
-            .take(9)
-            .flatten()
-            .collect::<Vec<_>>();
-        assert_eq!(
-            expected_invisibles.len(),
-            input_text
-                .chars()
-                .filter(|initial_char| initial_char.is_whitespace())
-                .count(),
-            "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
-        );
-        info!("Expected invisibles: {expected_invisibles:?}");
-
-        init_test(cx, |_| {});
-
-        // Put the same string with repeating whitespace pattern into editors of various size,
-        // take deliberately small steps during resizing, to put all whitespace kinds near the wrap point.
-        let resize_step = 10.0;
-        let mut editor_width = 200.0;
-        while editor_width <= 1000.0 {
-            update_test_language_settings(cx, |s| {
-                s.defaults.tab_size = NonZeroU32::new(tab_size);
-                s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
-                s.defaults.preferred_line_length = Some(editor_width as u32);
-                s.defaults.soft_wrap = Some(language_settings::SoftWrap::PreferredLineLength);
-            });
-
-            let actual_invisibles = collect_invisibles_from_new_editor(
-                cx,
-                EditorMode::Full,
-                &input_text,
-                px(editor_width),
-            );
-
-            // Whatever the editor size is, ensure it has the same invisible kinds in the same order
-            // (no good guarantees about the offsets: wrapping could trigger padding and its tests should check the offsets).
-            let mut i = 0;
-            for (actual_index, actual_invisible) in actual_invisibles.iter().enumerate() {
-                i = actual_index;
-                match expected_invisibles.get(i) {
-                    Some(expected_invisible) => match (expected_invisible, actual_invisible) {
-                        (Invisible::Whitespace { .. }, Invisible::Whitespace { .. })
-                        | (Invisible::Tab { .. }, Invisible::Tab { .. }) => {}
-                        _ => {
-                            panic!("At index {i}, expected invisible {expected_invisible:?} does not match actual {actual_invisible:?} by kind. Actual invisibles: {actual_invisibles:?}")
-                        }
-                    },
-                    None => panic!("Unexpected extra invisible {actual_invisible:?} at index {i}"),
-                }
-            }
-            let missing_expected_invisibles = &expected_invisibles[i + 1..];
-            assert!(
-                missing_expected_invisibles.is_empty(),
-                "Missing expected invisibles after index {i}: {missing_expected_invisibles:?}"
-            );
-
-            editor_width += resize_step;
-        }
-    }
-
-    fn collect_invisibles_from_new_editor(
-        cx: &mut TestAppContext,
-        editor_mode: EditorMode,
-        input_text: &str,
-        editor_width: Pixels,
-    ) -> Vec<Invisible> {
-        info!(
-            "Creating editor with mode {editor_mode:?}, width {}px and text '{input_text}'",
-            editor_width.0
-        );
-        let window = cx.add_window(|cx| {
-            let buffer = MultiBuffer::build_simple(&input_text, cx);
-            Editor::new(editor_mode, buffer, None, cx)
-        });
-        let editor = window.root(cx).unwrap();
-        let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
-        let mut element = EditorElement::new(&editor, style);
-        window
-            .update(cx, |editor, cx| {
-                editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
-                editor.set_wrap_width(Some(editor_width), cx);
-            })
-            .unwrap();
-        let layout_state = cx
-            .update_window(window.into(), |_, cx| {
-                element.compute_layout(
-                    Bounds {
-                        origin: point(px(500.), px(500.)),
-                        size: size(px(500.), px(500.)),
-                    },
-                    cx,
-                )
-            })
-            .unwrap();
-
-        layout_state
-            .position_map
-            .line_layouts
-            .iter()
-            .map(|line_with_invisibles| &line_with_invisibles.invisibles)
-            .flatten()
-            .cloned()
-            .collect()
-    }
-}
-
-pub fn register_action<T: Action>(
-    view: &View<Editor>,
-    cx: &mut WindowContext,
-    listener: impl Fn(&mut Editor, &T, &mut ViewContext<Editor>) + 'static,
-) {
-    let view = view.clone();
-    cx.on_action(TypeId::of::<T>(), move |action, phase, cx| {
-        let action = action.downcast_ref().unwrap();
-        if phase == DispatchPhase::Bubble {
-            view.update(cx, |editor, cx| {
-                listener(editor, action, cx);
-            })
-        }
-    })
-}
-
-fn compute_auto_height_layout(
-    editor: &mut Editor,
-    max_lines: usize,
-    max_line_number_width: Pixels,
-    known_dimensions: Size<Option<Pixels>>,
-    cx: &mut ViewContext<Editor>,
-) -> Option<Size<Pixels>> {
-    let width = known_dimensions.width?;
-    if let Some(height) = known_dimensions.height {
-        return Some(size(width, height));
-    }
-
-    let style = editor.style.as_ref().unwrap();
-    let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
-    let font_size = style.text.font_size.to_pixels(cx.rem_size());
-    let line_height = style.text.line_height_in_pixels(cx.rem_size());
-    let em_width = cx
-        .text_system()
-        .typographic_bounds(font_id, font_size, 'm')
-        .unwrap()
-        .size
-        .width;
-
-    let mut snapshot = editor.snapshot(cx);
-    let gutter_width;
-    let gutter_margin;
-    if snapshot.show_gutter {
-        let descent = cx.text_system().descent(font_id, font_size);
-        let gutter_padding_factor = 3.5;
-        let gutter_padding = (em_width * gutter_padding_factor).round();
-        gutter_width = max_line_number_width + gutter_padding * 2.0;
-        gutter_margin = -descent;
-    } else {
-        gutter_width = Pixels::ZERO;
-        gutter_margin = Pixels::ZERO;
-    };
-
-    editor.gutter_width = gutter_width;
-    let text_width = width - gutter_width;
-    let overscroll = size(em_width, px(0.));
-
-    let editor_width = text_width - gutter_margin - overscroll.width - em_width;
-    if editor.set_wrap_width(Some(editor_width), cx) {
-        snapshot = editor.snapshot(cx);
-    }
-
-    let scroll_height = Pixels::from(snapshot.max_point().row() + 1) * line_height;
-    let height = scroll_height
-        .max(line_height)
-        .min(line_height * max_lines as f32);
-
-    Some(size(width, height))
-}

crates/editor2/src/git.rs 🔗

@@ -1,282 +0,0 @@
-use std::ops::Range;
-
-use git::diff::{DiffHunk, DiffHunkStatus};
-use language::Point;
-
-use crate::{
-    display_map::{DisplaySnapshot, ToDisplayPoint},
-    AnchorRangeExt,
-};
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum DisplayDiffHunk {
-    Folded {
-        display_row: u32,
-    },
-
-    Unfolded {
-        display_row_range: Range<u32>,
-        status: DiffHunkStatus,
-    },
-}
-
-impl DisplayDiffHunk {
-    pub fn start_display_row(&self) -> u32 {
-        match self {
-            &DisplayDiffHunk::Folded { display_row } => display_row,
-            DisplayDiffHunk::Unfolded {
-                display_row_range, ..
-            } => display_row_range.start,
-        }
-    }
-
-    pub fn contains_display_row(&self, display_row: u32) -> bool {
-        let range = match self {
-            &DisplayDiffHunk::Folded { display_row } => display_row..=display_row,
-
-            DisplayDiffHunk::Unfolded {
-                display_row_range, ..
-            } => display_row_range.start..=display_row_range.end,
-        };
-
-        range.contains(&display_row)
-    }
-}
-
-pub fn diff_hunk_to_display(hunk: DiffHunk<u32>, snapshot: &DisplaySnapshot) -> DisplayDiffHunk {
-    let hunk_start_point = Point::new(hunk.buffer_range.start, 0);
-    let hunk_start_point_sub = Point::new(hunk.buffer_range.start.saturating_sub(1), 0);
-    let hunk_end_point_sub = Point::new(
-        hunk.buffer_range
-            .end
-            .saturating_sub(1)
-            .max(hunk.buffer_range.start),
-        0,
-    );
-
-    let is_removal = hunk.status() == DiffHunkStatus::Removed;
-
-    let folds_start = Point::new(hunk.buffer_range.start.saturating_sub(2), 0);
-    let folds_end = Point::new(hunk.buffer_range.end + 2, 0);
-    let folds_range = folds_start..folds_end;
-
-    let containing_fold = snapshot.folds_in_range(folds_range).find(|fold| {
-        let fold_point_range = fold.range.to_point(&snapshot.buffer_snapshot);
-        let fold_point_range = fold_point_range.start..=fold_point_range.end;
-
-        let folded_start = fold_point_range.contains(&hunk_start_point);
-        let folded_end = fold_point_range.contains(&hunk_end_point_sub);
-        let folded_start_sub = fold_point_range.contains(&hunk_start_point_sub);
-
-        (folded_start && folded_end) || (is_removal && folded_start_sub)
-    });
-
-    if let Some(fold) = containing_fold {
-        let row = fold.range.start.to_display_point(snapshot).row();
-        DisplayDiffHunk::Folded { display_row: row }
-    } else {
-        let start = hunk_start_point.to_display_point(snapshot).row();
-
-        let hunk_end_row = hunk.buffer_range.end.max(hunk.buffer_range.start);
-        let hunk_end_point = Point::new(hunk_end_row, 0);
-        let end = hunk_end_point.to_display_point(snapshot).row();
-
-        DisplayDiffHunk::Unfolded {
-            display_row_range: start..end,
-            status: hunk.status(),
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::editor_tests::init_test;
-    use crate::Point;
-    use gpui::{Context, TestAppContext};
-    use multi_buffer::{ExcerptRange, MultiBuffer};
-    use project::{FakeFs, Project};
-    use unindent::Unindent;
-    #[gpui::test]
-    async fn test_diff_hunks_in_range(cx: &mut TestAppContext) {
-        use git::diff::DiffHunkStatus;
-        init_test(cx, |_| {});
-
-        let fs = FakeFs::new(cx.background_executor.clone());
-        let project = Project::test(fs, [], cx).await;
-
-        // buffer has two modified hunks with two rows each
-        let buffer_1 = project
-            .update(cx, |project, cx| {
-                project.create_buffer(
-                    "
-                        1.zero
-                        1.ONE
-                        1.TWO
-                        1.three
-                        1.FOUR
-                        1.FIVE
-                        1.six
-                    "
-                    .unindent()
-                    .as_str(),
-                    None,
-                    cx,
-                )
-            })
-            .unwrap();
-        buffer_1.update(cx, |buffer, cx| {
-            buffer.set_diff_base(
-                Some(
-                    "
-                        1.zero
-                        1.one
-                        1.two
-                        1.three
-                        1.four
-                        1.five
-                        1.six
-                    "
-                    .unindent(),
-                ),
-                cx,
-            );
-        });
-
-        // buffer has a deletion hunk and an insertion hunk
-        let buffer_2 = project
-            .update(cx, |project, cx| {
-                project.create_buffer(
-                    "
-                        2.zero
-                        2.one
-                        2.two
-                        2.three
-                        2.four
-                        2.five
-                        2.six
-                    "
-                    .unindent()
-                    .as_str(),
-                    None,
-                    cx,
-                )
-            })
-            .unwrap();
-        buffer_2.update(cx, |buffer, cx| {
-            buffer.set_diff_base(
-                Some(
-                    "
-                        2.zero
-                        2.one
-                        2.one-and-a-half
-                        2.two
-                        2.three
-                        2.four
-                        2.six
-                    "
-                    .unindent(),
-                ),
-                cx,
-            );
-        });
-
-        cx.background_executor.run_until_parked();
-
-        let multibuffer = cx.new_model(|cx| {
-            let mut multibuffer = MultiBuffer::new(0);
-            multibuffer.push_excerpts(
-                buffer_1.clone(),
-                [
-                    // excerpt ends in the middle of a modified hunk
-                    ExcerptRange {
-                        context: Point::new(0, 0)..Point::new(1, 5),
-                        primary: Default::default(),
-                    },
-                    // excerpt begins in the middle of a modified hunk
-                    ExcerptRange {
-                        context: Point::new(5, 0)..Point::new(6, 5),
-                        primary: Default::default(),
-                    },
-                ],
-                cx,
-            );
-            multibuffer.push_excerpts(
-                buffer_2.clone(),
-                [
-                    // excerpt ends at a deletion
-                    ExcerptRange {
-                        context: Point::new(0, 0)..Point::new(1, 5),
-                        primary: Default::default(),
-                    },
-                    // excerpt starts at a deletion
-                    ExcerptRange {
-                        context: Point::new(2, 0)..Point::new(2, 5),
-                        primary: Default::default(),
-                    },
-                    // excerpt fully contains a deletion hunk
-                    ExcerptRange {
-                        context: Point::new(1, 0)..Point::new(2, 5),
-                        primary: Default::default(),
-                    },
-                    // excerpt fully contains an insertion hunk
-                    ExcerptRange {
-                        context: Point::new(4, 0)..Point::new(6, 5),
-                        primary: Default::default(),
-                    },
-                ],
-                cx,
-            );
-            multibuffer
-        });
-
-        let snapshot = multibuffer.read_with(cx, |b, cx| b.snapshot(cx));
-
-        assert_eq!(
-            snapshot.text(),
-            "
-                1.zero
-                1.ONE
-                1.FIVE
-                1.six
-                2.zero
-                2.one
-                2.two
-                2.one
-                2.two
-                2.four
-                2.five
-                2.six"
-                .unindent()
-        );
-
-        let expected = [
-            (DiffHunkStatus::Modified, 1..2),
-            (DiffHunkStatus::Modified, 2..3),
-            //TODO: Define better when and where removed hunks show up at range extremities
-            (DiffHunkStatus::Removed, 6..6),
-            (DiffHunkStatus::Removed, 8..8),
-            (DiffHunkStatus::Added, 10..11),
-        ];
-
-        assert_eq!(
-            snapshot
-                .git_diff_hunks_in_range(0..12)
-                .map(|hunk| (hunk.status(), hunk.buffer_range))
-                .collect::<Vec<_>>(),
-            &expected,
-        );
-
-        assert_eq!(
-            snapshot
-                .git_diff_hunks_in_range_rev(0..12)
-                .map(|hunk| (hunk.status(), hunk.buffer_range))
-                .collect::<Vec<_>>(),
-            expected
-                .iter()
-                .rev()
-                .cloned()
-                .collect::<Vec<_>>()
-                .as_slice(),
-        );
-    }
-}

crates/editor2/src/highlight_matching_bracket.rs 🔗

@@ -1,138 +0,0 @@
-use gpui::ViewContext;
-
-use crate::{Editor, RangeToAnchorExt};
-
-enum MatchingBracketHighlight {}
-
-pub fn refresh_matching_bracket_highlights(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
-    editor.clear_background_highlights::<MatchingBracketHighlight>(cx);
-
-    let newest_selection = editor.selections.newest::<usize>(cx);
-    // Don't highlight brackets if the selection isn't empty
-    if !newest_selection.is_empty() {
-        return;
-    }
-
-    let head = newest_selection.head();
-    let snapshot = editor.snapshot(cx);
-    if let Some((opening_range, closing_range)) = snapshot
-        .buffer_snapshot
-        .innermost_enclosing_bracket_ranges(head..head)
-    {
-        editor.highlight_background::<MatchingBracketHighlight>(
-            vec![
-                opening_range.to_anchors(&snapshot.buffer_snapshot),
-                closing_range.to_anchors(&snapshot.buffer_snapshot),
-            ],
-            |theme| theme.editor_document_highlight_read_background,
-            cx,
-        )
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
-    use indoc::indoc;
-    use language::{BracketPair, BracketPairConfig, Language, LanguageConfig};
-
-    #[gpui::test]
-    async fn test_matching_bracket_highlights(cx: &mut gpui::TestAppContext) {
-        init_test(cx, |_| {});
-
-        let mut cx = EditorLspTestContext::new(
-            Language::new(
-                LanguageConfig {
-                    name: "Rust".into(),
-                    path_suffixes: vec!["rs".to_string()],
-                    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_brackets_query(indoc! {r#"
-                ("{" @open "}" @close)
-                ("(" @open ")" @close)
-                "#})
-            .unwrap(),
-            Default::default(),
-            cx,
-        )
-        .await;
-
-        // positioning cursor inside bracket highlights both
-        cx.set_state(indoc! {r#"
-            pub fn test("Test ˇargument") {
-                another_test(1, 2, 3);
-            }
-        "#});
-        cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
-            pub fn test«(»"Test argument"«)» {
-                another_test(1, 2, 3);
-            }
-        "#});
-
-        cx.set_state(indoc! {r#"
-            pub fn test("Test argument") {
-                another_test(1, ˇ2, 3);
-            }
-        "#});
-        cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
-            pub fn test("Test argument") {
-                another_test«(»1, 2, 3«)»;
-            }
-        "#});
-
-        cx.set_state(indoc! {r#"
-            pub fn test("Test argument") {
-                anotherˇ_test(1, 2, 3);
-            }
-        "#});
-        cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
-            pub fn test("Test argument") «{»
-                another_test(1, 2, 3);
-            «}»
-        "#});
-
-        // positioning outside of brackets removes highlight
-        cx.set_state(indoc! {r#"
-            pub fˇn test("Test argument") {
-                another_test(1, 2, 3);
-            }
-        "#});
-        cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
-            pub fn test("Test argument") {
-                another_test(1, 2, 3);
-            }
-        "#});
-
-        // non empty selection dismisses highlight
-        cx.set_state(indoc! {r#"
-            pub fn test("Te«st argˇ»ument") {
-                another_test(1, 2, 3);
-            }
-        "#});
-        cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
-            pub fn test("Test argument") {
-                another_test(1, 2, 3);
-            }
-        "#});
-    }
-}

crates/editor2/src/hover_popover.rs 🔗

@@ -1,1345 +0,0 @@
-use crate::{
-    display_map::{InlayOffset, ToDisplayPoint},
-    link_go_to_definition::{InlayHighlight, RangeInEditor},
-    Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle,
-    ExcerptId, RangeToAnchorExt,
-};
-use futures::FutureExt;
-use gpui::{
-    actions, div, px, AnyElement, CursorStyle, Hsla, InteractiveElement, IntoElement, Model,
-    MouseButton, ParentElement, Pixels, SharedString, Size, StatefulInteractiveElement, Styled,
-    Task, ViewContext, WeakView,
-};
-use language::{markdown, Bias, DiagnosticEntry, Language, LanguageRegistry, ParsedMarkdown};
-
-use lsp::DiagnosticSeverity;
-use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project};
-use settings::Settings;
-use std::{ops::Range, sync::Arc, time::Duration};
-use ui::{StyledExt, Tooltip};
-use util::TryFutureExt;
-use workspace::Workspace;
-
-pub const HOVER_DELAY_MILLIS: u64 = 350;
-pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200;
-
-pub const MIN_POPOVER_CHARACTER_WIDTH: f32 = 20.;
-pub const MIN_POPOVER_LINE_HEIGHT: Pixels = px(4.);
-pub const HOVER_POPOVER_GAP: Pixels = px(10.);
-
-actions!(editor, [Hover]);
-
-/// Bindable action which uses the most recent selection head to trigger a hover
-pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext<Editor>) {
-    let head = editor.selections.newest_display(cx).head();
-    show_hover(editor, head, true, cx);
-}
-
-/// The internal hover action dispatches between `show_hover` or `hide_hover`
-/// depending on whether a point to hover over is provided.
-pub fn hover_at(editor: &mut Editor, point: Option<DisplayPoint>, cx: &mut ViewContext<Editor>) {
-    if EditorSettings::get_global(cx).hover_popover_enabled {
-        if let Some(point) = point {
-            show_hover(editor, point, false, cx);
-        } else {
-            hide_hover(editor, cx);
-        }
-    }
-}
-
-pub struct InlayHover {
-    pub excerpt: ExcerptId,
-    pub range: InlayHighlight,
-    pub tooltip: HoverBlock,
-}
-
-pub fn find_hovered_hint_part(
-    label_parts: Vec<InlayHintLabelPart>,
-    hint_start: InlayOffset,
-    hovered_offset: InlayOffset,
-) -> Option<(InlayHintLabelPart, Range<InlayOffset>)> {
-    if hovered_offset >= hint_start {
-        let mut hovered_character = (hovered_offset - hint_start).0;
-        let mut part_start = hint_start;
-        for part in label_parts {
-            let part_len = part.value.chars().count();
-            if hovered_character > part_len {
-                hovered_character -= part_len;
-                part_start.0 += part_len;
-            } else {
-                let part_end = InlayOffset(part_start.0 + part_len);
-                return Some((part, part_start..part_end));
-            }
-        }
-    }
-    None
-}
-
-pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut ViewContext<Editor>) {
-    if EditorSettings::get_global(cx).hover_popover_enabled {
-        if editor.pending_rename.is_some() {
-            return;
-        }
-
-        let Some(project) = editor.project.clone() else {
-            return;
-        };
-
-        if let Some(InfoPopover { symbol_range, .. }) = &editor.hover_state.info_popover {
-            if let RangeInEditor::Inlay(range) = symbol_range {
-                if range == &inlay_hover.range {
-                    // Hover triggered from same location as last time. Don't show again.
-                    return;
-                }
-            }
-            hide_hover(editor, cx);
-        }
-
-        let task = cx.spawn(|this, mut cx| {
-            async move {
-                cx.background_executor()
-                    .timer(Duration::from_millis(HOVER_DELAY_MILLIS))
-                    .await;
-                this.update(&mut cx, |this, _| {
-                    this.hover_state.diagnostic_popover = None;
-                })?;
-
-                let language_registry = project.update(&mut cx, |p, _| p.languages().clone())?;
-                let blocks = vec![inlay_hover.tooltip];
-                let parsed_content = parse_blocks(&blocks, &language_registry, None).await;
-
-                let hover_popover = InfoPopover {
-                    project: project.clone(),
-                    symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()),
-                    blocks,
-                    parsed_content,
-                };
-
-                this.update(&mut cx, |this, cx| {
-                    // Highlight the selected symbol using a background highlight
-                    this.highlight_inlay_background::<HoverState>(
-                        vec![inlay_hover.range],
-                        |theme| theme.element_hover, // todo!("use a proper background here")
-                        cx,
-                    );
-                    this.hover_state.info_popover = Some(hover_popover);
-                    cx.notify();
-                })?;
-
-                anyhow::Ok(())
-            }
-            .log_err()
-        });
-
-        editor.hover_state.info_task = Some(task);
-    }
-}
-
-/// Hides the type information popup.
-/// Triggered by the `Hover` action when the cursor is not over a symbol or when the
-/// selections changed.
-pub fn hide_hover(editor: &mut Editor, cx: &mut ViewContext<Editor>) -> bool {
-    let did_hide = editor.hover_state.info_popover.take().is_some()
-        | editor.hover_state.diagnostic_popover.take().is_some();
-
-    editor.hover_state.info_task = None;
-    editor.hover_state.triggered_from = None;
-
-    editor.clear_background_highlights::<HoverState>(cx);
-
-    if did_hide {
-        cx.notify();
-    }
-
-    did_hide
-}
-
-/// Queries the LSP and shows type info and documentation
-/// about the symbol the mouse is currently hovering over.
-/// Triggered by the `Hover` action when the cursor may be over a symbol.
-fn show_hover(
-    editor: &mut Editor,
-    point: DisplayPoint,
-    ignore_timeout: bool,
-    cx: &mut ViewContext<Editor>,
-) {
-    if editor.pending_rename.is_some() {
-        return;
-    }
-
-    let snapshot = editor.snapshot(cx);
-    let multibuffer_offset = point.to_offset(&snapshot.display_snapshot, Bias::Left);
-
-    let (buffer, buffer_position) = if let Some(output) = editor
-        .buffer
-        .read(cx)
-        .text_anchor_for_position(multibuffer_offset, cx)
-    {
-        output
-    } else {
-        return;
-    };
-
-    let excerpt_id = if let Some((excerpt_id, _, _)) = editor
-        .buffer()
-        .read(cx)
-        .excerpt_containing(multibuffer_offset, cx)
-    {
-        excerpt_id
-    } else {
-        return;
-    };
-
-    let project = if let Some(project) = editor.project.clone() {
-        project
-    } else {
-        return;
-    };
-
-    if !ignore_timeout {
-        if let Some(InfoPopover { symbol_range, .. }) = &editor.hover_state.info_popover {
-            if symbol_range
-                .as_text_range()
-                .map(|range| {
-                    range
-                        .to_offset(&snapshot.buffer_snapshot)
-                        .contains(&multibuffer_offset)
-                })
-                .unwrap_or(false)
-            {
-                // Hover triggered from same location as last time. Don't show again.
-                return;
-            } else {
-                hide_hover(editor, cx);
-            }
-        }
-    }
-
-    // Get input anchor
-    let anchor = snapshot
-        .buffer_snapshot
-        .anchor_at(multibuffer_offset, Bias::Left);
-
-    // Don't request again if the location is the same as the previous request
-    if let Some(triggered_from) = &editor.hover_state.triggered_from {
-        if triggered_from
-            .cmp(&anchor, &snapshot.buffer_snapshot)
-            .is_eq()
-        {
-            return;
-        }
-    }
-
-    let task = cx.spawn(|this, mut cx| {
-        async move {
-            // If we need to delay, delay a set amount initially before making the lsp request
-            let delay = if !ignore_timeout {
-                // Construct delay task to wait for later
-                let total_delay = Some(
-                    cx.background_executor()
-                        .timer(Duration::from_millis(HOVER_DELAY_MILLIS)),
-                );
-
-                cx.background_executor()
-                    .timer(Duration::from_millis(HOVER_REQUEST_DELAY_MILLIS))
-                    .await;
-                total_delay
-            } else {
-                None
-            };
-
-            // query the LSP for hover info
-            let hover_request = cx.update(|_, cx| {
-                project.update(cx, |project, cx| {
-                    project.hover(&buffer, buffer_position, cx)
-                })
-            })?;
-
-            if let Some(delay) = delay {
-                delay.await;
-            }
-
-            // If there's a diagnostic, assign it on the hover state and notify
-            let local_diagnostic = snapshot
-                .buffer_snapshot
-                .diagnostics_in_range::<_, usize>(multibuffer_offset..multibuffer_offset, false)
-                // Find the entry with the most specific range
-                .min_by_key(|entry| entry.range.end - entry.range.start)
-                .map(|entry| DiagnosticEntry {
-                    diagnostic: entry.diagnostic,
-                    range: entry.range.to_anchors(&snapshot.buffer_snapshot),
-                });
-
-            // Pull the primary diagnostic out so we can jump to it if the popover is clicked
-            let primary_diagnostic = local_diagnostic.as_ref().and_then(|local_diagnostic| {
-                snapshot
-                    .buffer_snapshot
-                    .diagnostic_group::<usize>(local_diagnostic.diagnostic.group_id)
-                    .find(|diagnostic| diagnostic.diagnostic.is_primary)
-                    .map(|entry| DiagnosticEntry {
-                        diagnostic: entry.diagnostic,
-                        range: entry.range.to_anchors(&snapshot.buffer_snapshot),
-                    })
-            });
-
-            this.update(&mut cx, |this, _| {
-                this.hover_state.diagnostic_popover =
-                    local_diagnostic.map(|local_diagnostic| DiagnosticPopover {
-                        local_diagnostic,
-                        primary_diagnostic,
-                    });
-            })?;
-
-            let hover_result = hover_request.await.ok().flatten();
-            let hover_popover = match hover_result {
-                Some(hover_result) if !hover_result.is_empty() => {
-                    // Create symbol range of anchors for highlighting and filtering of future requests.
-                    let range = if let Some(range) = hover_result.range {
-                        let start = snapshot
-                            .buffer_snapshot
-                            .anchor_in_excerpt(excerpt_id.clone(), range.start);
-                        let end = snapshot
-                            .buffer_snapshot
-                            .anchor_in_excerpt(excerpt_id.clone(), range.end);
-
-                        start..end
-                    } else {
-                        anchor..anchor
-                    };
-
-                    let language_registry =
-                        project.update(&mut cx, |p, _| p.languages().clone())?;
-                    let blocks = hover_result.contents;
-                    let language = hover_result.language;
-                    let parsed_content = parse_blocks(&blocks, &language_registry, language).await;
-
-                    Some(InfoPopover {
-                        project: project.clone(),
-                        symbol_range: RangeInEditor::Text(range),
-                        blocks,
-                        parsed_content,
-                    })
-                }
-
-                _ => None,
-            };
-
-            this.update(&mut cx, |this, cx| {
-                if let Some(symbol_range) = hover_popover
-                    .as_ref()
-                    .and_then(|hover_popover| hover_popover.symbol_range.as_text_range())
-                {
-                    // Highlight the selected symbol using a background highlight
-                    this.highlight_background::<HoverState>(
-                        vec![symbol_range],
-                        |theme| theme.element_hover, // todo! update theme
-                        cx,
-                    );
-                } else {
-                    this.clear_background_highlights::<HoverState>(cx);
-                }
-
-                this.hover_state.info_popover = hover_popover;
-                cx.notify();
-            })?;
-
-            Ok::<_, anyhow::Error>(())
-        }
-        .log_err()
-    });
-
-    editor.hover_state.info_task = Some(task);
-}
-
-async fn parse_blocks(
-    blocks: &[HoverBlock],
-    language_registry: &Arc<LanguageRegistry>,
-    language: Option<Arc<Language>>,
-) -> markdown::ParsedMarkdown {
-    let mut text = String::new();
-    let mut highlights = Vec::new();
-    let mut region_ranges = Vec::new();
-    let mut regions = Vec::new();
-
-    for block in blocks {
-        match &block.kind {
-            HoverBlockKind::PlainText => {
-                markdown::new_paragraph(&mut text, &mut Vec::new());
-                text.push_str(&block.text);
-            }
-
-            HoverBlockKind::Markdown => {
-                markdown::parse_markdown_block(
-                    &block.text,
-                    language_registry,
-                    language.clone(),
-                    &mut text,
-                    &mut highlights,
-                    &mut region_ranges,
-                    &mut regions,
-                )
-                .await
-            }
-
-            HoverBlockKind::Code { language } => {
-                if let Some(language) = language_registry
-                    .language_for_name(language)
-                    .now_or_never()
-                    .and_then(Result::ok)
-                {
-                    markdown::highlight_code(&mut text, &mut highlights, &block.text, &language);
-                } else {
-                    text.push_str(&block.text);
-                }
-            }
-        }
-    }
-
-    ParsedMarkdown {
-        text: text.trim().to_string(),
-        highlights,
-        region_ranges,
-        regions,
-    }
-}
-
-#[derive(Default)]
-pub struct HoverState {
-    pub info_popover: Option<InfoPopover>,
-    pub diagnostic_popover: Option<DiagnosticPopover>,
-    pub triggered_from: Option<Anchor>,
-    pub info_task: Option<Task<Option<()>>>,
-}
-
-impl HoverState {
-    pub fn visible(&self) -> bool {
-        self.info_popover.is_some() || self.diagnostic_popover.is_some()
-    }
-
-    pub fn render(
-        &mut self,
-        snapshot: &EditorSnapshot,
-        style: &EditorStyle,
-        visible_rows: Range<u32>,
-        max_size: Size<Pixels>,
-        workspace: Option<WeakView<Workspace>>,
-        cx: &mut ViewContext<Editor>,
-    ) -> Option<(DisplayPoint, Vec<AnyElement>)> {
-        // If there is a diagnostic, position the popovers based on that.
-        // Otherwise use the start of the hover range
-        let anchor = self
-            .diagnostic_popover
-            .as_ref()
-            .map(|diagnostic_popover| &diagnostic_popover.local_diagnostic.range.start)
-            .or_else(|| {
-                self.info_popover
-                    .as_ref()
-                    .map(|info_popover| match &info_popover.symbol_range {
-                        RangeInEditor::Text(range) => &range.start,
-                        RangeInEditor::Inlay(range) => &range.inlay_position,
-                    })
-            })?;
-        let point = anchor.to_display_point(&snapshot.display_snapshot);
-
-        // Don't render if the relevant point isn't on screen
-        if !self.visible() || !visible_rows.contains(&point.row()) {
-            return None;
-        }
-
-        let mut elements = Vec::new();
-
-        if let Some(diagnostic_popover) = self.diagnostic_popover.as_ref() {
-            elements.push(diagnostic_popover.render(style, max_size, cx));
-        }
-        if let Some(info_popover) = self.info_popover.as_mut() {
-            elements.push(info_popover.render(style, max_size, workspace, cx));
-        }
-
-        Some((point, elements))
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct InfoPopover {
-    pub project: Model<Project>,
-    symbol_range: RangeInEditor,
-    pub blocks: Vec<HoverBlock>,
-    parsed_content: ParsedMarkdown,
-}
-
-impl InfoPopover {
-    pub fn render(
-        &mut self,
-        style: &EditorStyle,
-        max_size: Size<Pixels>,
-        workspace: Option<WeakView<Workspace>>,
-        cx: &mut ViewContext<Editor>,
-    ) -> AnyElement {
-        div()
-            .id("info_popover")
-            .elevation_2(cx)
-            .p_2()
-            .overflow_y_scroll()
-            .max_w(max_size.width)
-            .max_h(max_size.height)
-            // Prevent a mouse move on the popover from being propagated to the editor,
-            // because that would dismiss the popover.
-            .on_mouse_move(|_, cx| cx.stop_propagation())
-            .child(crate::render_parsed_markdown(
-                "content",
-                &self.parsed_content,
-                style,
-                workspace,
-                cx,
-            ))
-            .into_any_element()
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct DiagnosticPopover {
-    local_diagnostic: DiagnosticEntry<Anchor>,
-    primary_diagnostic: Option<DiagnosticEntry<Anchor>>,
-}
-
-impl DiagnosticPopover {
-    pub fn render(
-        &self,
-        style: &EditorStyle,
-        max_size: Size<Pixels>,
-        cx: &mut ViewContext<Editor>,
-    ) -> AnyElement {
-        let text = match &self.local_diagnostic.diagnostic.source {
-            Some(source) => format!("{source}: {}", self.local_diagnostic.diagnostic.message),
-            None => self.local_diagnostic.diagnostic.message.clone(),
-        };
-
-        struct DiagnosticColors {
-            pub text: Hsla,
-            pub background: Hsla,
-            pub border: Hsla,
-        }
-
-        let diagnostic_colors = match self.local_diagnostic.diagnostic.severity {
-            DiagnosticSeverity::ERROR => DiagnosticColors {
-                text: style.status.error,
-                background: style.status.error_background,
-                border: style.status.error_border,
-            },
-            DiagnosticSeverity::WARNING => DiagnosticColors {
-                text: style.status.warning,
-                background: style.status.warning_background,
-                border: style.status.warning_border,
-            },
-            DiagnosticSeverity::INFORMATION => DiagnosticColors {
-                text: style.status.info,
-                background: style.status.info_background,
-                border: style.status.info_border,
-            },
-            DiagnosticSeverity::HINT => DiagnosticColors {
-                text: style.status.hint,
-                background: style.status.hint_background,
-                border: style.status.hint_border,
-            },
-            _ => DiagnosticColors {
-                text: style.status.ignored,
-                background: style.status.ignored_background,
-                border: style.status.ignored_border,
-            },
-        };
-
-        div()
-            .id("diagnostic")
-            .overflow_y_scroll()
-            .px_2()
-            .py_1()
-            .bg(diagnostic_colors.background)
-            .text_color(diagnostic_colors.text)
-            .border_1()
-            .border_color(diagnostic_colors.border)
-            .rounded_md()
-            .max_w(max_size.width)
-            .max_h(max_size.height)
-            .cursor(CursorStyle::PointingHand)
-            .tooltip(move |cx| Tooltip::for_action("Go To Diagnostic", &crate::GoToDiagnostic, cx))
-            // Prevent a mouse move on the popover from being propagated to the editor,
-            // because that would dismiss the popover.
-            .on_mouse_move(|_, cx| cx.stop_propagation())
-            // Prevent a mouse down on the popover from being propagated to the editor,
-            // because that would move the cursor.
-            .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
-            .on_click(cx.listener(|editor, _, cx| editor.go_to_diagnostic(&Default::default(), cx)))
-            .child(SharedString::from(text))
-            .into_any_element()
-    }
-
-    pub fn activation_info(&self) -> (usize, Anchor) {
-        let entry = self
-            .primary_diagnostic
-            .as_ref()
-            .unwrap_or(&self.local_diagnostic);
-
-        (entry.diagnostic.group_id, entry.range.start.clone())
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::{
-        editor_tests::init_test,
-        element::PointForPosition,
-        inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels},
-        link_go_to_definition::update_inlay_link_and_hover_points,
-        test::editor_lsp_test_context::EditorLspTestContext,
-        InlayId,
-    };
-    use collections::BTreeSet;
-    use gpui::{FontWeight, HighlightStyle, UnderlineStyle};
-    use indoc::indoc;
-    use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet};
-    use lsp::LanguageServerId;
-    use project::{HoverBlock, HoverBlockKind};
-    use smol::stream::StreamExt;
-    use unindent::Unindent;
-    use util::test::marked_text_ranges;
-
-    #[gpui::test]
-    async fn test_mouse_hover_info_popover(cx: &mut gpui::TestAppContext) {
-        init_test(cx, |_| {});
-
-        let mut cx = EditorLspTestContext::new_rust(
-            lsp::ServerCapabilities {
-                hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
-                ..Default::default()
-            },
-            cx,
-        )
-        .await;
-
-        // Basic hover delays and then pops without moving the mouse
-        cx.set_state(indoc! {"
-            fn ˇtest() { println!(); }
-        "});
-        let hover_point = cx.display_point(indoc! {"
-            fn test() { printˇln!(); }
-        "});
-
-        cx.update_editor(|editor, cx| hover_at(editor, Some(hover_point), cx));
-        assert!(!cx.editor(|editor, _| editor.hover_state.visible()));
-
-        // After delay, hover should be visible.
-        let symbol_range = cx.lsp_range(indoc! {"
-            fn test() { «println!»(); }
-        "});
-        let mut requests =
-            cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
-                Ok(Some(lsp::Hover {
-                    contents: lsp::HoverContents::Markup(lsp::MarkupContent {
-                        kind: lsp::MarkupKind::Markdown,
-                        value: "some basic docs".to_string(),
-                    }),
-                    range: Some(symbol_range),
-                }))
-            });
-        cx.background_executor
-            .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
-        requests.next().await;
-
-        cx.editor(|editor, _| {
-            assert!(editor.hover_state.visible());
-            assert_eq!(
-                editor.hover_state.info_popover.clone().unwrap().blocks,
-                vec![HoverBlock {
-                    text: "some basic docs".to_string(),
-                    kind: HoverBlockKind::Markdown,
-                },]
-            )
-        });
-
-        // Mouse moved with no hover response dismisses
-        let hover_point = cx.display_point(indoc! {"
-            fn teˇst() { println!(); }
-        "});
-        let mut request = cx
-            .lsp
-            .handle_request::<lsp::request::HoverRequest, _, _>(|_, _| async move { Ok(None) });
-        cx.update_editor(|editor, cx| hover_at(editor, Some(hover_point), cx));
-        cx.background_executor
-            .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
-        request.next().await;
-        cx.editor(|editor, _| {
-            assert!(!editor.hover_state.visible());
-        });
-    }
-
-    #[gpui::test]
-    async fn test_keyboard_hover_info_popover(cx: &mut gpui::TestAppContext) {
-        init_test(cx, |_| {});
-
-        let mut cx = EditorLspTestContext::new_rust(
-            lsp::ServerCapabilities {
-                hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
-                ..Default::default()
-            },
-            cx,
-        )
-        .await;
-
-        // Hover with keyboard has no delay
-        cx.set_state(indoc! {"
-            fˇn test() { println!(); }
-        "});
-        cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
-        let symbol_range = cx.lsp_range(indoc! {"
-            «fn» test() { println!(); }
-        "});
-        cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
-            Ok(Some(lsp::Hover {
-                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
-                    kind: lsp::MarkupKind::Markdown,
-                    value: "some other basic docs".to_string(),
-                }),
-                range: Some(symbol_range),
-            }))
-        })
-        .next()
-        .await;
-
-        cx.condition(|editor, _| editor.hover_state.visible()).await;
-        cx.editor(|editor, _| {
-            assert_eq!(
-                editor.hover_state.info_popover.clone().unwrap().blocks,
-                vec![HoverBlock {
-                    text: "some other basic docs".to_string(),
-                    kind: HoverBlockKind::Markdown,
-                }]
-            )
-        });
-    }
-
-    #[gpui::test]
-    async fn test_empty_hovers_filtered(cx: &mut gpui::TestAppContext) {
-        init_test(cx, |_| {});
-
-        let mut cx = EditorLspTestContext::new_rust(
-            lsp::ServerCapabilities {
-                hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
-                ..Default::default()
-            },
-            cx,
-        )
-        .await;
-
-        // Hover with keyboard has no delay
-        cx.set_state(indoc! {"
-            fˇn test() { println!(); }
-        "});
-        cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
-        let symbol_range = cx.lsp_range(indoc! {"
-            «fn» test() { println!(); }
-        "});
-        cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
-            Ok(Some(lsp::Hover {
-                contents: lsp::HoverContents::Array(vec![
-                    lsp::MarkedString::String("regular text for hover to show".to_string()),
-                    lsp::MarkedString::String("".to_string()),
-                    lsp::MarkedString::LanguageString(lsp::LanguageString {
-                        language: "Rust".to_string(),
-                        value: "".to_string(),
-                    }),
-                ]),
-                range: Some(symbol_range),
-            }))
-        })
-        .next()
-        .await;
-
-        cx.condition(|editor, _| editor.hover_state.visible()).await;
-        cx.editor(|editor, _| {
-            assert_eq!(
-                editor.hover_state.info_popover.clone().unwrap().blocks,
-                vec![HoverBlock {
-                    text: "regular text for hover to show".to_string(),
-                    kind: HoverBlockKind::Markdown,
-                }],
-                "No empty string hovers should be shown"
-            );
-        });
-    }
-
-    #[gpui::test]
-    async fn test_line_ends_trimmed(cx: &mut gpui::TestAppContext) {
-        init_test(cx, |_| {});
-
-        let mut cx = EditorLspTestContext::new_rust(
-            lsp::ServerCapabilities {
-                hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
-                ..Default::default()
-            },
-            cx,
-        )
-        .await;
-
-        // Hover with keyboard has no delay
-        cx.set_state(indoc! {"
-            fˇn test() { println!(); }
-        "});
-        cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
-        let symbol_range = cx.lsp_range(indoc! {"
-            «fn» test() { println!(); }
-        "});
-
-        let code_str = "\nlet hovered_point: Vector2F // size = 8, align = 0x4\n";
-        let markdown_string = format!("\n```rust\n{code_str}```");
-
-        let closure_markdown_string = markdown_string.clone();
-        cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| {
-            let future_markdown_string = closure_markdown_string.clone();
-            async move {
-                Ok(Some(lsp::Hover {
-                    contents: lsp::HoverContents::Markup(lsp::MarkupContent {
-                        kind: lsp::MarkupKind::Markdown,
-                        value: future_markdown_string,
-                    }),
-                    range: Some(symbol_range),
-                }))
-            }
-        })
-        .next()
-        .await;
-
-        cx.condition(|editor, _| editor.hover_state.visible()).await;
-        cx.editor(|editor, _| {
-            let blocks = editor.hover_state.info_popover.clone().unwrap().blocks;
-            assert_eq!(
-                blocks,
-                vec![HoverBlock {
-                    text: markdown_string,
-                    kind: HoverBlockKind::Markdown,
-                }],
-            );
-
-            let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None));
-            assert_eq!(
-                rendered.text,
-                code_str.trim(),
-                "Should not have extra line breaks at end of rendered hover"
-            );
-        });
-    }
-
-    #[gpui::test]
-    async fn test_hover_diagnostic_and_info_popovers(cx: &mut gpui::TestAppContext) {
-        init_test(cx, |_| {});
-
-        let mut cx = EditorLspTestContext::new_rust(
-            lsp::ServerCapabilities {
-                hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
-                ..Default::default()
-            },
-            cx,
-        )
-        .await;
-
-        // Hover with just diagnostic, pops DiagnosticPopover immediately and then
-        // info popover once request completes
-        cx.set_state(indoc! {"
-            fn teˇst() { println!(); }
-        "});
-
-        // Send diagnostic to client
-        let range = cx.text_anchor_range(indoc! {"
-            fn «test»() { println!(); }
-        "});
-        cx.update_buffer(|buffer, cx| {
-            let snapshot = buffer.text_snapshot();
-            let set = DiagnosticSet::from_sorted_entries(
-                vec![DiagnosticEntry {
-                    range,
-                    diagnostic: Diagnostic {
-                        message: "A test diagnostic message.".to_string(),
-                        ..Default::default()
-                    },
-                }],
-                &snapshot,
-            );
-            buffer.update_diagnostics(LanguageServerId(0), set, cx);
-        });
-
-        // Hover pops diagnostic immediately
-        cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
-        cx.background_executor.run_until_parked();
-
-        cx.editor(|Editor { hover_state, .. }, _| {
-            assert!(hover_state.diagnostic_popover.is_some() && hover_state.info_popover.is_none())
-        });
-
-        // Info Popover shows after request responded to
-        let range = cx.lsp_range(indoc! {"
-            fn «test»() { println!(); }
-        "});
-        cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
-            Ok(Some(lsp::Hover {
-                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
-                    kind: lsp::MarkupKind::Markdown,
-                    value: "some new docs".to_string(),
-                }),
-                range: Some(range),
-            }))
-        });
-        cx.background_executor
-            .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
-
-        cx.background_executor.run_until_parked();
-        cx.editor(|Editor { hover_state, .. }, _| {
-            hover_state.diagnostic_popover.is_some() && hover_state.info_task.is_some()
-        });
-    }
-
-    #[gpui::test]
-    fn test_render_blocks(cx: &mut gpui::TestAppContext) {
-        init_test(cx, |_| {});
-
-        let editor = cx.add_window(|cx| Editor::single_line(cx));
-        editor
-            .update(cx, |editor, _cx| {
-                let style = editor.style.clone().unwrap();
-
-                struct Row {
-                    blocks: Vec<HoverBlock>,
-                    expected_marked_text: String,
-                    expected_styles: Vec<HighlightStyle>,
-                }
-
-                let rows = &[
-                    // Strong emphasis
-                    Row {
-                        blocks: vec![HoverBlock {
-                            text: "one **two** three".to_string(),
-                            kind: HoverBlockKind::Markdown,
-                        }],
-                        expected_marked_text: "one «two» three".to_string(),
-                        expected_styles: vec![HighlightStyle {
-                            font_weight: Some(FontWeight::BOLD),
-                            ..Default::default()
-                        }],
-                    },
-                    // Links
-                    Row {
-                        blocks: vec![HoverBlock {
-                            text: "one [two](https://the-url) three".to_string(),
-                            kind: HoverBlockKind::Markdown,
-                        }],
-                        expected_marked_text: "one «two» three".to_string(),
-                        expected_styles: vec![HighlightStyle {
-                            underline: Some(UnderlineStyle {
-                                thickness: 1.0.into(),
-                                ..Default::default()
-                            }),
-                            ..Default::default()
-                        }],
-                    },
-                    // Lists
-                    Row {
-                        blocks: vec![HoverBlock {
-                            text: "
-                            lists:
-                            * one
-                                - a
-                                - b
-                            * two
-                                - [c](https://the-url)
-                                - d"
-                            .unindent(),
-                            kind: HoverBlockKind::Markdown,
-                        }],
-                        expected_marked_text: "
-                        lists:
-                        - one
-                          - a
-                          - b
-                        - two
-                          - «c»
-                          - d"
-                        .unindent(),
-                        expected_styles: vec![HighlightStyle {
-                            underline: Some(UnderlineStyle {
-                                thickness: 1.0.into(),
-                                ..Default::default()
-                            }),
-                            ..Default::default()
-                        }],
-                    },
-                    // Multi-paragraph list items
-                    Row {
-                        blocks: vec![HoverBlock {
-                            text: "
-                            * one two
-                              three
-
-                            * four five
-                                * six seven
-                                  eight
-
-                                  nine
-                                * ten
-                            * six"
-                                .unindent(),
-                            kind: HoverBlockKind::Markdown,
-                        }],
-                        expected_marked_text: "
-                        - one two three
-                        - four five
-                          - six seven eight
-
-                            nine
-                          - ten
-                        - six"
-                            .unindent(),
-                        expected_styles: vec![HighlightStyle {
-                            underline: Some(UnderlineStyle {
-                                thickness: 1.0.into(),
-                                ..Default::default()
-                            }),
-                            ..Default::default()
-                        }],
-                    },
-                ];
-
-                for Row {
-                    blocks,
-                    expected_marked_text,
-                    expected_styles,
-                } in &rows[0..]
-                {
-                    let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None));
-
-                    let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false);
-                    let expected_highlights = ranges
-                        .into_iter()
-                        .zip(expected_styles.iter().cloned())
-                        .collect::<Vec<_>>();
-                    assert_eq!(
-                        rendered.text, expected_text,
-                        "wrong text for input {blocks:?}"
-                    );
-
-                    let rendered_highlights: Vec<_> = rendered
-                        .highlights
-                        .iter()
-                        .filter_map(|(range, highlight)| {
-                            let highlight = highlight.to_highlight_style(&style.syntax)?;
-                            Some((range.clone(), highlight))
-                        })
-                        .collect();
-
-                    assert_eq!(
-                        rendered_highlights, expected_highlights,
-                        "wrong highlights for input {blocks:?}"
-                    );
-                }
-            })
-            .unwrap();
-    }
-
-    #[gpui::test]
-    async fn test_hover_inlay_label_parts(cx: &mut gpui::TestAppContext) {
-        init_test(cx, |settings| {
-            settings.defaults.inlay_hints = Some(InlayHintSettings {
-                enabled: true,
-                show_type_hints: true,
-                show_parameter_hints: true,
-                show_other_hints: true,
-            })
-        });
-
-        let mut cx = EditorLspTestContext::new_rust(
-            lsp::ServerCapabilities {
-                inlay_hint_provider: Some(lsp::OneOf::Right(
-                    lsp::InlayHintServerCapabilities::Options(lsp::InlayHintOptions {
-                        resolve_provider: Some(true),
-                        ..Default::default()
-                    }),
-                )),
-                ..Default::default()
-            },
-            cx,
-        )
-        .await;
-
-        cx.set_state(indoc! {"
-            struct TestStruct;
-
-            // ==================
-
-            struct TestNewType<T>(T);
-
-            fn main() {
-                let variableˇ = TestNewType(TestStruct);
-            }
-        "});
-
-        let hint_start_offset = cx.ranges(indoc! {"
-            struct TestStruct;
-
-            // ==================
-
-            struct TestNewType<T>(T);
-
-            fn main() {
-                let variableˇ = TestNewType(TestStruct);
-            }
-        "})[0]
-            .start;
-        let hint_position = cx.to_lsp(hint_start_offset);
-        let new_type_target_range = cx.lsp_range(indoc! {"
-            struct TestStruct;
-
-            // ==================
-
-            struct «TestNewType»<T>(T);
-
-            fn main() {
-                let variable = TestNewType(TestStruct);
-            }
-        "});
-        let struct_target_range = cx.lsp_range(indoc! {"
-            struct «TestStruct»;
-
-            // ==================
-
-            struct TestNewType<T>(T);
-
-            fn main() {
-                let variable = TestNewType(TestStruct);
-            }
-        "});
-
-        let uri = cx.buffer_lsp_url.clone();
-        let new_type_label = "TestNewType";
-        let struct_label = "TestStruct";
-        let entire_hint_label = ": TestNewType<TestStruct>";
-        let closure_uri = uri.clone();
-        cx.lsp
-            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
-                let task_uri = closure_uri.clone();
-                async move {
-                    assert_eq!(params.text_document.uri, task_uri);
-                    Ok(Some(vec![lsp::InlayHint {
-                        position: hint_position,
-                        label: lsp::InlayHintLabel::LabelParts(vec![lsp::InlayHintLabelPart {
-                            value: entire_hint_label.to_string(),
-                            ..Default::default()
-                        }]),
-                        kind: Some(lsp::InlayHintKind::TYPE),
-                        text_edits: None,
-                        tooltip: None,
-                        padding_left: Some(false),
-                        padding_right: Some(false),
-                        data: None,
-                    }]))
-                }
-            })
-            .next()
-            .await;
-        cx.background_executor.run_until_parked();
-        cx.update_editor(|editor, cx| {
-            let expected_layers = vec![entire_hint_label.to_string()];
-            assert_eq!(expected_layers, cached_hint_labels(editor));
-            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
-        });
-
-        let inlay_range = cx
-            .ranges(indoc! {"
-                struct TestStruct;
-
-                // ==================
-
-                struct TestNewType<T>(T);
-
-                fn main() {
-                    let variable« »= TestNewType(TestStruct);
-                }
-        "})
-            .get(0)
-            .cloned()
-            .unwrap();
-        let new_type_hint_part_hover_position = cx.update_editor(|editor, cx| {
-            let snapshot = editor.snapshot(cx);
-            let previous_valid = inlay_range.start.to_display_point(&snapshot);
-            let next_valid = inlay_range.end.to_display_point(&snapshot);
-            assert_eq!(previous_valid.row(), next_valid.row());
-            assert!(previous_valid.column() < next_valid.column());
-            let exact_unclipped = DisplayPoint::new(
-                previous_valid.row(),
-                previous_valid.column()
-                    + (entire_hint_label.find(new_type_label).unwrap() + new_type_label.len() / 2)
-                        as u32,
-            );
-            PointForPosition {
-                previous_valid,
-                next_valid,
-                exact_unclipped,
-                column_overshoot_after_line_end: 0,
-            }
-        });
-        cx.update_editor(|editor, cx| {
-            update_inlay_link_and_hover_points(
-                &editor.snapshot(cx),
-                new_type_hint_part_hover_position,
-                editor,
-                true,
-                false,
-                cx,
-            );
-        });
-
-        let resolve_closure_uri = uri.clone();
-        cx.lsp
-            .handle_request::<lsp::request::InlayHintResolveRequest, _, _>(
-                move |mut hint_to_resolve, _| {
-                    let mut resolved_hint_positions = BTreeSet::new();
-                    let task_uri = resolve_closure_uri.clone();
-                    async move {
-                        let inserted = resolved_hint_positions.insert(hint_to_resolve.position);
-                        assert!(inserted, "Hint {hint_to_resolve:?} was resolved twice");
-
-                        // `: TestNewType<TestStruct>`
-                        hint_to_resolve.label = lsp::InlayHintLabel::LabelParts(vec![
-                            lsp::InlayHintLabelPart {
-                                value: ": ".to_string(),
-                                ..Default::default()
-                            },
-                            lsp::InlayHintLabelPart {
-                                value: new_type_label.to_string(),
-                                location: Some(lsp::Location {
-                                    uri: task_uri.clone(),
-                                    range: new_type_target_range,
-                                }),
-                                tooltip: Some(lsp::InlayHintLabelPartTooltip::String(format!(
-                                    "A tooltip for `{new_type_label}`"
-                                ))),
-                                ..Default::default()
-                            },
-                            lsp::InlayHintLabelPart {
-                                value: "<".to_string(),
-                                ..Default::default()
-                            },
-                            lsp::InlayHintLabelPart {
-                                value: struct_label.to_string(),
-                                location: Some(lsp::Location {
-                                    uri: task_uri,
-                                    range: struct_target_range,
-                                }),
-                                tooltip: Some(lsp::InlayHintLabelPartTooltip::MarkupContent(
-                                    lsp::MarkupContent {
-                                        kind: lsp::MarkupKind::Markdown,
-                                        value: format!("A tooltip for `{struct_label}`"),
-                                    },
-                                )),
-                                ..Default::default()
-                            },
-                            lsp::InlayHintLabelPart {
-                                value: ">".to_string(),
-                                ..Default::default()
-                            },
-                        ]);
-
-                        Ok(hint_to_resolve)
-                    }
-                },
-            )
-            .next()
-            .await;
-        cx.background_executor.run_until_parked();
-
-        cx.update_editor(|editor, cx| {
-            update_inlay_link_and_hover_points(
-                &editor.snapshot(cx),
-                new_type_hint_part_hover_position,
-                editor,
-                true,
-                false,
-                cx,
-            );
-        });
-        cx.background_executor
-            .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
-        cx.background_executor.run_until_parked();
-        cx.update_editor(|editor, cx| {
-            let hover_state = &editor.hover_state;
-            assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some());
-            let popover = hover_state.info_popover.as_ref().unwrap();
-            let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx));
-            assert_eq!(
-                popover.symbol_range,
-                RangeInEditor::Inlay(InlayHighlight {
-                    inlay: InlayId::Hint(0),
-                    inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right),
-                    range: ": ".len()..": ".len() + new_type_label.len(),
-                }),
-                "Popover range should match the new type label part"
-            );
-            assert_eq!(
-                popover.parsed_content.text,
-                format!("A tooltip for `{new_type_label}`"),
-                "Rendered text should not anyhow alter backticks"
-            );
-        });
-
-        let struct_hint_part_hover_position = cx.update_editor(|editor, cx| {
-            let snapshot = editor.snapshot(cx);
-            let previous_valid = inlay_range.start.to_display_point(&snapshot);
-            let next_valid = inlay_range.end.to_display_point(&snapshot);
-            assert_eq!(previous_valid.row(), next_valid.row());
-            assert!(previous_valid.column() < next_valid.column());
-            let exact_unclipped = DisplayPoint::new(
-                previous_valid.row(),
-                previous_valid.column()
-                    + (entire_hint_label.find(struct_label).unwrap() + struct_label.len() / 2)
-                        as u32,
-            );
-            PointForPosition {
-                previous_valid,
-                next_valid,
-                exact_unclipped,
-                column_overshoot_after_line_end: 0,
-            }
-        });
-        cx.update_editor(|editor, cx| {
-            update_inlay_link_and_hover_points(
-                &editor.snapshot(cx),
-                struct_hint_part_hover_position,
-                editor,
-                true,
-                false,
-                cx,
-            );
-        });
-        cx.background_executor
-            .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
-        cx.background_executor.run_until_parked();
-        cx.update_editor(|editor, cx| {
-            let hover_state = &editor.hover_state;
-            assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some());
-            let popover = hover_state.info_popover.as_ref().unwrap();
-            let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx));
-            assert_eq!(
-                popover.symbol_range,
-                RangeInEditor::Inlay(InlayHighlight {
-                    inlay: InlayId::Hint(0),
-                    inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right),
-                    range: ": ".len() + new_type_label.len() + "<".len()
-                        ..": ".len() + new_type_label.len() + "<".len() + struct_label.len(),
-                }),
-                "Popover range should match the struct label part"
-            );
-            assert_eq!(
-                popover.parsed_content.text,
-                format!("A tooltip for {struct_label}"),
-                "Rendered markdown element should remove backticks from text"
-            );
-        });
-    }
-}

crates/editor2/src/inlay_hint_cache.rs 🔗

@@ -1,3268 +0,0 @@
-use std::{
-    cmp,
-    ops::{ControlFlow, Range},
-    sync::Arc,
-    time::Duration,
-};
-
-use crate::{
-    display_map::Inlay, Anchor, Editor, ExcerptId, InlayId, MultiBuffer, MultiBufferSnapshot,
-};
-use anyhow::Context;
-use clock::Global;
-use futures::future;
-use gpui::{Model, ModelContext, Task, ViewContext};
-use language::{language_settings::InlayHintKind, Buffer, BufferSnapshot};
-use parking_lot::RwLock;
-use project::{InlayHint, ResolveState};
-
-use collections::{hash_map, HashMap, HashSet};
-use language::language_settings::InlayHintSettings;
-use smol::lock::Semaphore;
-use sum_tree::Bias;
-use text::{ToOffset, ToPoint};
-use util::post_inc;
-
-pub struct InlayHintCache {
-    hints: HashMap<ExcerptId, Arc<RwLock<CachedExcerptHints>>>,
-    allowed_hint_kinds: HashSet<Option<InlayHintKind>>,
-    version: usize,
-    pub(super) enabled: bool,
-    update_tasks: HashMap<ExcerptId, TasksForRanges>,
-    lsp_request_limiter: Arc<Semaphore>,
-}
-
-#[derive(Debug)]
-struct TasksForRanges {
-    tasks: Vec<Task<()>>,
-    sorted_ranges: Vec<Range<language::Anchor>>,
-}
-
-#[derive(Debug)]
-pub struct CachedExcerptHints {
-    version: usize,
-    buffer_version: Global,
-    buffer_id: u64,
-    ordered_hints: Vec<InlayId>,
-    hints_by_id: HashMap<InlayId, InlayHint>,
-}
-
-#[derive(Debug, Clone, Copy)]
-pub enum InvalidationStrategy {
-    RefreshRequested,
-    BufferEdited,
-    None,
-}
-
-#[derive(Debug, Default)]
-pub struct InlaySplice {
-    pub to_remove: Vec<InlayId>,
-    pub to_insert: Vec<Inlay>,
-}
-
-#[derive(Debug)]
-struct ExcerptHintsUpdate {
-    excerpt_id: ExcerptId,
-    remove_from_visible: Vec<InlayId>,
-    remove_from_cache: HashSet<InlayId>,
-    add_to_cache: Vec<InlayHint>,
-}
-
-#[derive(Debug, Clone, Copy)]
-struct ExcerptQuery {
-    buffer_id: u64,
-    excerpt_id: ExcerptId,
-    cache_version: usize,
-    invalidate: InvalidationStrategy,
-    reason: &'static str,
-}
-
-impl InvalidationStrategy {
-    fn should_invalidate(&self) -> bool {
-        matches!(
-            self,
-            InvalidationStrategy::RefreshRequested | InvalidationStrategy::BufferEdited
-        )
-    }
-}
-
-impl TasksForRanges {
-    fn new(query_ranges: QueryRanges, task: Task<()>) -> Self {
-        let mut sorted_ranges = Vec::new();
-        sorted_ranges.extend(query_ranges.before_visible);
-        sorted_ranges.extend(query_ranges.visible);
-        sorted_ranges.extend(query_ranges.after_visible);
-        Self {
-            tasks: vec![task],
-            sorted_ranges,
-        }
-    }
-
-    fn update_cached_tasks(
-        &mut self,
-        buffer_snapshot: &BufferSnapshot,
-        query_ranges: QueryRanges,
-        invalidate: InvalidationStrategy,
-        spawn_task: impl FnOnce(QueryRanges) -> Task<()>,
-    ) {
-        let query_ranges = if invalidate.should_invalidate() {
-            self.tasks.clear();
-            self.sorted_ranges.clear();
-            query_ranges
-        } else {
-            let mut non_cached_query_ranges = query_ranges;
-            non_cached_query_ranges.before_visible = non_cached_query_ranges
-                .before_visible
-                .into_iter()
-                .flat_map(|query_range| {
-                    self.remove_cached_ranges_from_query(buffer_snapshot, query_range)
-                })
-                .collect();
-            non_cached_query_ranges.visible = non_cached_query_ranges
-                .visible
-                .into_iter()
-                .flat_map(|query_range| {
-                    self.remove_cached_ranges_from_query(buffer_snapshot, query_range)
-                })
-                .collect();
-            non_cached_query_ranges.after_visible = non_cached_query_ranges
-                .after_visible
-                .into_iter()
-                .flat_map(|query_range| {
-                    self.remove_cached_ranges_from_query(buffer_snapshot, query_range)
-                })
-                .collect();
-            non_cached_query_ranges
-        };
-
-        if !query_ranges.is_empty() {
-            self.tasks.push(spawn_task(query_ranges));
-        }
-    }
-
-    fn remove_cached_ranges_from_query(
-        &mut self,
-        buffer_snapshot: &BufferSnapshot,
-        query_range: Range<language::Anchor>,
-    ) -> Vec<Range<language::Anchor>> {
-        let mut ranges_to_query = Vec::new();
-        let mut latest_cached_range = None::<&mut Range<language::Anchor>>;
-        for cached_range in self
-            .sorted_ranges
-            .iter_mut()
-            .skip_while(|cached_range| {
-                cached_range
-                    .end
-                    .cmp(&query_range.start, buffer_snapshot)
-                    .is_lt()
-            })
-            .take_while(|cached_range| {
-                cached_range
-                    .start
-                    .cmp(&query_range.end, buffer_snapshot)
-                    .is_le()
-            })
-        {
-            match latest_cached_range {
-                Some(latest_cached_range) => {
-                    if latest_cached_range.end.offset.saturating_add(1) < cached_range.start.offset
-                    {
-                        ranges_to_query.push(latest_cached_range.end..cached_range.start);
-                        cached_range.start = latest_cached_range.end;
-                    }
-                }
-                None => {
-                    if query_range
-                        .start
-                        .cmp(&cached_range.start, buffer_snapshot)
-                        .is_lt()
-                    {
-                        ranges_to_query.push(query_range.start..cached_range.start);
-                        cached_range.start = query_range.start;
-                    }
-                }
-            }
-            latest_cached_range = Some(cached_range);
-        }
-
-        match latest_cached_range {
-            Some(latest_cached_range) => {
-                if latest_cached_range.end.offset.saturating_add(1) < query_range.end.offset {
-                    ranges_to_query.push(latest_cached_range.end..query_range.end);
-                    latest_cached_range.end = query_range.end;
-                }
-            }
-            None => {
-                ranges_to_query.push(query_range.clone());
-                self.sorted_ranges.push(query_range);
-                self.sorted_ranges
-                    .sort_by(|range_a, range_b| range_a.start.cmp(&range_b.start, buffer_snapshot));
-            }
-        }
-
-        ranges_to_query
-    }
-
-    fn invalidate_range(&mut self, buffer: &BufferSnapshot, range: &Range<language::Anchor>) {
-        self.sorted_ranges = self
-            .sorted_ranges
-            .drain(..)
-            .filter_map(|mut cached_range| {
-                if cached_range.start.cmp(&range.end, buffer).is_gt()
-                    || cached_range.end.cmp(&range.start, buffer).is_lt()
-                {
-                    Some(vec![cached_range])
-                } else if cached_range.start.cmp(&range.start, buffer).is_ge()
-                    && cached_range.end.cmp(&range.end, buffer).is_le()
-                {
-                    None
-                } else if range.start.cmp(&cached_range.start, buffer).is_ge()
-                    && range.end.cmp(&cached_range.end, buffer).is_le()
-                {
-                    Some(vec![
-                        cached_range.start..range.start,
-                        range.end..cached_range.end,
-                    ])
-                } else if cached_range.start.cmp(&range.start, buffer).is_ge() {
-                    cached_range.start = range.end;
-                    Some(vec![cached_range])
-                } else {
-                    cached_range.end = range.start;
-                    Some(vec![cached_range])
-                }
-            })
-            .flatten()
-            .collect();
-    }
-}
-
-impl InlayHintCache {
-    pub fn new(inlay_hint_settings: InlayHintSettings) -> Self {
-        Self {
-            allowed_hint_kinds: inlay_hint_settings.enabled_inlay_hint_kinds(),
-            enabled: inlay_hint_settings.enabled,
-            hints: HashMap::default(),
-            update_tasks: HashMap::default(),
-            version: 0,
-            lsp_request_limiter: Arc::new(Semaphore::new(MAX_CONCURRENT_LSP_REQUESTS)),
-        }
-    }
-
-    pub fn update_settings(
-        &mut self,
-        multi_buffer: &Model<MultiBuffer>,
-        new_hint_settings: InlayHintSettings,
-        visible_hints: Vec<Inlay>,
-        cx: &mut ViewContext<Editor>,
-    ) -> ControlFlow<Option<InlaySplice>> {
-        let new_allowed_hint_kinds = new_hint_settings.enabled_inlay_hint_kinds();
-        match (self.enabled, new_hint_settings.enabled) {
-            (false, false) => {
-                self.allowed_hint_kinds = new_allowed_hint_kinds;
-                ControlFlow::Break(None)
-            }
-            (true, true) => {
-                if new_allowed_hint_kinds == self.allowed_hint_kinds {
-                    ControlFlow::Break(None)
-                } else {
-                    let new_splice = self.new_allowed_hint_kinds_splice(
-                        multi_buffer,
-                        &visible_hints,
-                        &new_allowed_hint_kinds,
-                        cx,
-                    );
-                    if new_splice.is_some() {
-                        self.version += 1;
-                        self.allowed_hint_kinds = new_allowed_hint_kinds;
-                    }
-                    ControlFlow::Break(new_splice)
-                }
-            }
-            (true, false) => {
-                self.enabled = new_hint_settings.enabled;
-                self.allowed_hint_kinds = new_allowed_hint_kinds;
-                if self.hints.is_empty() {
-                    ControlFlow::Break(None)
-                } else {
-                    self.clear();
-                    ControlFlow::Break(Some(InlaySplice {
-                        to_remove: visible_hints.iter().map(|inlay| inlay.id).collect(),
-                        to_insert: Vec::new(),
-                    }))
-                }
-            }
-            (false, true) => {
-                self.enabled = new_hint_settings.enabled;
-                self.allowed_hint_kinds = new_allowed_hint_kinds;
-                ControlFlow::Continue(())
-            }
-        }
-    }
-
-    pub fn spawn_hint_refresh(
-        &mut self,
-        reason: &'static str,
-        excerpts_to_query: HashMap<ExcerptId, (Model<Buffer>, Global, Range<usize>)>,
-        invalidate: InvalidationStrategy,
-        cx: &mut ViewContext<Editor>,
-    ) -> Option<InlaySplice> {
-        if !self.enabled {
-            return None;
-        }
-
-        let mut invalidated_hints = Vec::new();
-        if invalidate.should_invalidate() {
-            self.update_tasks
-                .retain(|task_excerpt_id, _| excerpts_to_query.contains_key(task_excerpt_id));
-            self.hints.retain(|cached_excerpt, cached_hints| {
-                let retain = excerpts_to_query.contains_key(cached_excerpt);
-                if !retain {
-                    invalidated_hints.extend(cached_hints.read().ordered_hints.iter().copied());
-                }
-                retain
-            });
-        }
-        if excerpts_to_query.is_empty() && invalidated_hints.is_empty() {
-            return None;
-        }
-
-        let cache_version = self.version + 1;
-        cx.spawn(|editor, mut cx| async move {
-            editor
-                .update(&mut cx, |editor, cx| {
-                    spawn_new_update_tasks(
-                        editor,
-                        reason,
-                        excerpts_to_query,
-                        invalidate,
-                        cache_version,
-                        cx,
-                    )
-                })
-                .ok();
-        })
-        .detach();
-
-        if invalidated_hints.is_empty() {
-            None
-        } else {
-            Some(InlaySplice {
-                to_remove: invalidated_hints,
-                to_insert: Vec::new(),
-            })
-        }
-    }
-
-    fn new_allowed_hint_kinds_splice(
-        &self,
-        multi_buffer: &Model<MultiBuffer>,
-        visible_hints: &[Inlay],
-        new_kinds: &HashSet<Option<InlayHintKind>>,
-        cx: &mut ViewContext<Editor>,
-    ) -> Option<InlaySplice> {
-        let old_kinds = &self.allowed_hint_kinds;
-        if new_kinds == old_kinds {
-            return None;
-        }
-
-        let mut to_remove = Vec::new();
-        let mut to_insert = Vec::new();
-        let mut shown_hints_to_remove = visible_hints.iter().fold(
-            HashMap::<ExcerptId, Vec<(Anchor, InlayId)>>::default(),
-            |mut current_hints, inlay| {
-                current_hints
-                    .entry(inlay.position.excerpt_id)
-                    .or_default()
-                    .push((inlay.position, inlay.id));
-                current_hints
-            },
-        );
-
-        let multi_buffer = multi_buffer.read(cx);
-        let multi_buffer_snapshot = multi_buffer.snapshot(cx);
-
-        for (excerpt_id, excerpt_cached_hints) in &self.hints {
-            let shown_excerpt_hints_to_remove =
-                shown_hints_to_remove.entry(*excerpt_id).or_default();
-            let excerpt_cached_hints = excerpt_cached_hints.read();
-            let mut excerpt_cache = excerpt_cached_hints.ordered_hints.iter().fuse().peekable();
-            shown_excerpt_hints_to_remove.retain(|(shown_anchor, shown_hint_id)| {
-                let Some(buffer) = shown_anchor
-                    .buffer_id
-                    .and_then(|buffer_id| multi_buffer.buffer(buffer_id))
-                else {
-                    return false;
-                };
-                let buffer_snapshot = buffer.read(cx).snapshot();
-                loop {
-                    match excerpt_cache.peek() {
-                        Some(&cached_hint_id) => {
-                            let cached_hint = &excerpt_cached_hints.hints_by_id[cached_hint_id];
-                            if cached_hint_id == shown_hint_id {
-                                excerpt_cache.next();
-                                return !new_kinds.contains(&cached_hint.kind);
-                            }
-
-                            match cached_hint
-                                .position
-                                .cmp(&shown_anchor.text_anchor, &buffer_snapshot)
-                            {
-                                cmp::Ordering::Less | cmp::Ordering::Equal => {
-                                    if !old_kinds.contains(&cached_hint.kind)
-                                        && new_kinds.contains(&cached_hint.kind)
-                                    {
-                                        to_insert.push(Inlay::hint(
-                                            cached_hint_id.id(),
-                                            multi_buffer_snapshot.anchor_in_excerpt(
-                                                *excerpt_id,
-                                                cached_hint.position,
-                                            ),
-                                            &cached_hint,
-                                        ));
-                                    }
-                                    excerpt_cache.next();
-                                }
-                                cmp::Ordering::Greater => return true,
-                            }
-                        }
-                        None => return true,
-                    }
-                }
-            });
-
-            for cached_hint_id in excerpt_cache {
-                let maybe_missed_cached_hint = &excerpt_cached_hints.hints_by_id[cached_hint_id];
-                let cached_hint_kind = maybe_missed_cached_hint.kind;
-                if !old_kinds.contains(&cached_hint_kind) && new_kinds.contains(&cached_hint_kind) {
-                    to_insert.push(Inlay::hint(
-                        cached_hint_id.id(),
-                        multi_buffer_snapshot
-                            .anchor_in_excerpt(*excerpt_id, maybe_missed_cached_hint.position),
-                        &maybe_missed_cached_hint,
-                    ));
-                }
-            }
-        }
-
-        to_remove.extend(
-            shown_hints_to_remove
-                .into_values()
-                .flatten()
-                .map(|(_, hint_id)| hint_id),
-        );
-        if to_remove.is_empty() && to_insert.is_empty() {
-            None
-        } else {
-            Some(InlaySplice {
-                to_remove,
-                to_insert,
-            })
-        }
-    }
-
-    pub fn remove_excerpts(&mut self, excerpts_removed: Vec<ExcerptId>) -> Option<InlaySplice> {
-        let mut to_remove = Vec::new();
-        for excerpt_to_remove in excerpts_removed {
-            self.update_tasks.remove(&excerpt_to_remove);
-            if let Some(cached_hints) = self.hints.remove(&excerpt_to_remove) {
-                let cached_hints = cached_hints.read();
-                to_remove.extend(cached_hints.ordered_hints.iter().copied());
-            }
-        }
-        if to_remove.is_empty() {
-            None
-        } else {
-            self.version += 1;
-            Some(InlaySplice {
-                to_remove,
-                to_insert: Vec::new(),
-            })
-        }
-    }
-
-    pub fn clear(&mut self) {
-        if !self.update_tasks.is_empty() || !self.hints.is_empty() {
-            self.version += 1;
-        }
-        self.update_tasks.clear();
-        self.hints.clear();
-    }
-
-    pub fn hint_by_id(&self, excerpt_id: ExcerptId, hint_id: InlayId) -> Option<InlayHint> {
-        self.hints
-            .get(&excerpt_id)?
-            .read()
-            .hints_by_id
-            .get(&hint_id)
-            .cloned()
-    }
-
-    pub fn hints(&self) -> Vec<InlayHint> {
-        let mut hints = Vec::new();
-        for excerpt_hints in self.hints.values() {
-            let excerpt_hints = excerpt_hints.read();
-            hints.extend(
-                excerpt_hints
-                    .ordered_hints
-                    .iter()
-                    .map(|id| &excerpt_hints.hints_by_id[id])
-                    .cloned(),
-            );
-        }
-        hints
-    }
-
-    pub fn version(&self) -> usize {
-        self.version
-    }
-
-    pub fn spawn_hint_resolve(
-        &self,
-        buffer_id: u64,
-        excerpt_id: ExcerptId,
-        id: InlayId,
-        cx: &mut ViewContext<'_, Editor>,
-    ) {
-        if let Some(excerpt_hints) = self.hints.get(&excerpt_id) {
-            let mut guard = excerpt_hints.write();
-            if let Some(cached_hint) = guard.hints_by_id.get_mut(&id) {
-                if let ResolveState::CanResolve(server_id, _) = &cached_hint.resolve_state {
-                    let hint_to_resolve = cached_hint.clone();
-                    let server_id = *server_id;
-                    cached_hint.resolve_state = ResolveState::Resolving;
-                    drop(guard);
-                    cx.spawn(|editor, mut cx| async move {
-                        let resolved_hint_task = editor.update(&mut cx, |editor, cx| {
-                            editor
-                                .buffer()
-                                .read(cx)
-                                .buffer(buffer_id)
-                                .and_then(|buffer| {
-                                    let project = editor.project.as_ref()?;
-                                    Some(project.update(cx, |project, cx| {
-                                        project.resolve_inlay_hint(
-                                            hint_to_resolve,
-                                            buffer,
-                                            server_id,
-                                            cx,
-                                        )
-                                    }))
-                                })
-                        })?;
-                        if let Some(resolved_hint_task) = resolved_hint_task {
-                            let mut resolved_hint =
-                                resolved_hint_task.await.context("hint resolve task")?;
-                            editor.update(&mut cx, |editor, _| {
-                                if let Some(excerpt_hints) =
-                                    editor.inlay_hint_cache.hints.get(&excerpt_id)
-                                {
-                                    let mut guard = excerpt_hints.write();
-                                    if let Some(cached_hint) = guard.hints_by_id.get_mut(&id) {
-                                        if cached_hint.resolve_state == ResolveState::Resolving {
-                                            resolved_hint.resolve_state = ResolveState::Resolved;
-                                            *cached_hint = resolved_hint;
-                                        }
-                                    }
-                                }
-                            })?;
-                        }
-
-                        anyhow::Ok(())
-                    })
-                    .detach_and_log_err(cx);
-                }
-            }
-        }
-    }
-}
-
-fn spawn_new_update_tasks(
-    editor: &mut Editor,
-    reason: &'static str,
-    excerpts_to_query: HashMap<ExcerptId, (Model<Buffer>, Global, Range<usize>)>,
-    invalidate: InvalidationStrategy,
-    update_cache_version: usize,
-    cx: &mut ViewContext<'_, Editor>,
-) {
-    let visible_hints = Arc::new(editor.visible_inlay_hints(cx));
-    for (excerpt_id, (excerpt_buffer, new_task_buffer_version, excerpt_visible_range)) in
-        excerpts_to_query
-    {
-        if excerpt_visible_range.is_empty() {
-            continue;
-        }
-        let buffer = excerpt_buffer.read(cx);
-        let buffer_id = buffer.remote_id();
-        let buffer_snapshot = buffer.snapshot();
-        if buffer_snapshot
-            .version()
-            .changed_since(&new_task_buffer_version)
-        {
-            continue;
-        }
-
-        let cached_excerpt_hints = editor.inlay_hint_cache.hints.get(&excerpt_id).cloned();
-        if let Some(cached_excerpt_hints) = &cached_excerpt_hints {
-            let cached_excerpt_hints = cached_excerpt_hints.read();
-            let cached_buffer_version = &cached_excerpt_hints.buffer_version;
-            if cached_excerpt_hints.version > update_cache_version
-                || cached_buffer_version.changed_since(&new_task_buffer_version)
-            {
-                continue;
-            }
-        };
-
-        let (multi_buffer_snapshot, Some(query_ranges)) =
-            editor.buffer.update(cx, |multi_buffer, cx| {
-                (
-                    multi_buffer.snapshot(cx),
-                    determine_query_ranges(
-                        multi_buffer,
-                        excerpt_id,
-                        &excerpt_buffer,
-                        excerpt_visible_range,
-                        cx,
-                    ),
-                )
-            })
-        else {
-            return;
-        };
-        let query = ExcerptQuery {
-            buffer_id,
-            excerpt_id,
-            cache_version: update_cache_version,
-            invalidate,
-            reason,
-        };
-
-        let new_update_task = |query_ranges| {
-            new_update_task(
-                query,
-                query_ranges,
-                multi_buffer_snapshot,
-                buffer_snapshot.clone(),
-                Arc::clone(&visible_hints),
-                cached_excerpt_hints,
-                Arc::clone(&editor.inlay_hint_cache.lsp_request_limiter),
-                cx,
-            )
-        };
-
-        match editor.inlay_hint_cache.update_tasks.entry(excerpt_id) {
-            hash_map::Entry::Occupied(mut o) => {
-                o.get_mut().update_cached_tasks(
-                    &buffer_snapshot,
-                    query_ranges,
-                    invalidate,
-                    new_update_task,
-                );
-            }
-            hash_map::Entry::Vacant(v) => {
-                v.insert(TasksForRanges::new(
-                    query_ranges.clone(),
-                    new_update_task(query_ranges),
-                ));
-            }
-        }
-    }
-}
-
-#[derive(Debug, Clone)]
-struct QueryRanges {
-    before_visible: Vec<Range<language::Anchor>>,
-    visible: Vec<Range<language::Anchor>>,
-    after_visible: Vec<Range<language::Anchor>>,
-}
-
-impl QueryRanges {
-    fn is_empty(&self) -> bool {
-        self.before_visible.is_empty() && self.visible.is_empty() && self.after_visible.is_empty()
-    }
-}
-
-fn determine_query_ranges(
-    multi_buffer: &mut MultiBuffer,
-    excerpt_id: ExcerptId,
-    excerpt_buffer: &Model<Buffer>,
-    excerpt_visible_range: Range<usize>,
-    cx: &mut ModelContext<'_, MultiBuffer>,
-) -> Option<QueryRanges> {
-    let full_excerpt_range = multi_buffer
-        .excerpts_for_buffer(excerpt_buffer, cx)
-        .into_iter()
-        .find(|(id, _)| id == &excerpt_id)
-        .map(|(_, range)| range.context)?;
-    let buffer = excerpt_buffer.read(cx);
-    let snapshot = buffer.snapshot();
-    let excerpt_visible_len = excerpt_visible_range.end - excerpt_visible_range.start;
-
-    let visible_range = if excerpt_visible_range.start == excerpt_visible_range.end {
-        return None;
-    } else {
-        vec![
-            buffer.anchor_before(snapshot.clip_offset(excerpt_visible_range.start, Bias::Left))
-                ..buffer.anchor_after(snapshot.clip_offset(excerpt_visible_range.end, Bias::Right)),
-        ]
-    };
-
-    let full_excerpt_range_end_offset = full_excerpt_range.end.to_offset(&snapshot);
-    let after_visible_range_start = excerpt_visible_range
-        .end
-        .saturating_add(1)
-        .min(full_excerpt_range_end_offset)
-        .min(buffer.len());
-    let after_visible_range = if after_visible_range_start == full_excerpt_range_end_offset {
-        Vec::new()
-    } else {
-        let after_range_end_offset = after_visible_range_start
-            .saturating_add(excerpt_visible_len)
-            .min(full_excerpt_range_end_offset)
-            .min(buffer.len());
-        vec![
-            buffer.anchor_before(snapshot.clip_offset(after_visible_range_start, Bias::Left))
-                ..buffer.anchor_after(snapshot.clip_offset(after_range_end_offset, Bias::Right)),
-        ]
-    };
-
-    let full_excerpt_range_start_offset = full_excerpt_range.start.to_offset(&snapshot);
-    let before_visible_range_end = excerpt_visible_range
-        .start
-        .saturating_sub(1)
-        .max(full_excerpt_range_start_offset);
-    let before_visible_range = if before_visible_range_end == full_excerpt_range_start_offset {
-        Vec::new()
-    } else {
-        let before_range_start_offset = before_visible_range_end
-            .saturating_sub(excerpt_visible_len)
-            .max(full_excerpt_range_start_offset);
-        vec![
-            buffer.anchor_before(snapshot.clip_offset(before_range_start_offset, Bias::Left))
-                ..buffer.anchor_after(snapshot.clip_offset(before_visible_range_end, Bias::Right)),
-        ]
-    };
-
-    Some(QueryRanges {
-        before_visible: before_visible_range,
-        visible: visible_range,
-        after_visible: after_visible_range,
-    })
-}
-
-const MAX_CONCURRENT_LSP_REQUESTS: usize = 5;
-const INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS: u64 = 400;
-
-fn new_update_task(
-    query: ExcerptQuery,
-    query_ranges: QueryRanges,
-    multi_buffer_snapshot: MultiBufferSnapshot,
-    buffer_snapshot: BufferSnapshot,
-    visible_hints: Arc<Vec<Inlay>>,
-    cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
-    lsp_request_limiter: Arc<Semaphore>,
-    cx: &mut ViewContext<'_, Editor>,
-) -> Task<()> {
-    cx.spawn(|editor, mut cx| async move {
-        let closure_cx = cx.clone();
-        let fetch_and_update_hints = |invalidate, range| {
-            fetch_and_update_hints(
-                editor.clone(),
-                multi_buffer_snapshot.clone(),
-                buffer_snapshot.clone(),
-                Arc::clone(&visible_hints),
-                cached_excerpt_hints.as_ref().map(Arc::clone),
-                query,
-                invalidate,
-                range,
-                Arc::clone(&lsp_request_limiter),
-                closure_cx.clone(),
-            )
-        };
-        let visible_range_update_results = future::join_all(query_ranges.visible.into_iter().map(
-            |visible_range| async move {
-                (
-                    visible_range.clone(),
-                    fetch_and_update_hints(query.invalidate.should_invalidate(), visible_range)
-                        .await,
-                )
-            },
-        ))
-        .await;
-
-        let hint_delay = cx.background_executor().timer(Duration::from_millis(
-            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS,
-        ));
-
-        let mut query_range_failed = |range: &Range<language::Anchor>, e: anyhow::Error| {
-            log::error!("inlay hint update task for range {range:?} failed: {e:#}");
-            editor
-                .update(&mut cx, |editor, _| {
-                    if let Some(task_ranges) = editor
-                        .inlay_hint_cache
-                        .update_tasks
-                        .get_mut(&query.excerpt_id)
-                    {
-                        task_ranges.invalidate_range(&buffer_snapshot, &range);
-                    }
-                })
-                .ok()
-        };
-
-        for (range, result) in visible_range_update_results {
-            if let Err(e) = result {
-                query_range_failed(&range, e);
-            }
-        }
-
-        hint_delay.await;
-        let invisible_range_update_results = future::join_all(
-            query_ranges
-                .before_visible
-                .into_iter()
-                .chain(query_ranges.after_visible.into_iter())
-                .map(|invisible_range| async move {
-                    (
-                        invisible_range.clone(),
-                        fetch_and_update_hints(false, invisible_range).await,
-                    )
-                }),
-        )
-        .await;
-        for (range, result) in invisible_range_update_results {
-            if let Err(e) = result {
-                query_range_failed(&range, e);
-            }
-        }
-    })
-}
-
-async fn fetch_and_update_hints(
-    editor: gpui::WeakView<Editor>,
-    multi_buffer_snapshot: MultiBufferSnapshot,
-    buffer_snapshot: BufferSnapshot,
-    visible_hints: Arc<Vec<Inlay>>,
-    cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
-    query: ExcerptQuery,
-    invalidate: bool,
-    fetch_range: Range<language::Anchor>,
-    lsp_request_limiter: Arc<Semaphore>,
-    mut cx: gpui::AsyncWindowContext,
-) -> anyhow::Result<()> {
-    let (lsp_request_guard, got_throttled) = if query.invalidate.should_invalidate() {
-        (None, false)
-    } else {
-        match lsp_request_limiter.try_acquire() {
-            Some(guard) => (Some(guard), false),
-            None => (Some(lsp_request_limiter.acquire().await), true),
-        }
-    };
-    let fetch_range_to_log =
-        fetch_range.start.to_point(&buffer_snapshot)..fetch_range.end.to_point(&buffer_snapshot);
-    let inlay_hints_fetch_task = editor
-        .update(&mut cx, |editor, cx| {
-            if got_throttled {
-                let query_not_around_visible_range = match editor.excerpts_for_inlay_hints_query(None, cx).remove(&query.excerpt_id) {
-                    Some((_, _, current_visible_range)) => {
-                        let visible_offset_length = current_visible_range.len();
-                        let double_visible_range = current_visible_range
-                            .start
-                            .saturating_sub(visible_offset_length)
-                            ..current_visible_range
-                                .end
-                                .saturating_add(visible_offset_length)
-                                .min(buffer_snapshot.len());
-                        !double_visible_range
-                            .contains(&fetch_range.start.to_offset(&buffer_snapshot))
-                            && !double_visible_range
-                                .contains(&fetch_range.end.to_offset(&buffer_snapshot))
-                    },
-                    None => true,
-                };
-                if query_not_around_visible_range {
-                    log::trace!("Fetching inlay hints for range {fetch_range_to_log:?} got throttled and fell off the current visible range, skipping.");
-                    if let Some(task_ranges) = editor
-                        .inlay_hint_cache
-                        .update_tasks
-                        .get_mut(&query.excerpt_id)
-                    {
-                        task_ranges.invalidate_range(&buffer_snapshot, &fetch_range);
-                    }
-                    return None;
-                }
-            }
-            editor
-                .buffer()
-                .read(cx)
-                .buffer(query.buffer_id)
-                .and_then(|buffer| {
-                    let project = editor.project.as_ref()?;
-                    Some(project.update(cx, |project, cx| {
-                        project.inlay_hints(buffer, fetch_range.clone(), cx)
-                    }))
-                })
-        })
-        .ok()
-        .flatten();
-    let new_hints = match inlay_hints_fetch_task {
-        Some(fetch_task) => {
-            log::debug!(
-                "Fetching inlay hints for range {fetch_range_to_log:?}, reason: {query_reason}, invalidate: {invalidate}",
-                query_reason = query.reason,
-            );
-            log::trace!(
-                "Currently visible hints: {visible_hints:?}, cached hints present: {}",
-                cached_excerpt_hints.is_some(),
-            );
-            fetch_task.await.context("inlay hint fetch task")?
-        }
-        None => return Ok(()),
-    };
-    drop(lsp_request_guard);
-    log::debug!(
-        "Fetched {} hints for range {fetch_range_to_log:?}",
-        new_hints.len()
-    );
-    log::trace!("Fetched hints: {new_hints:?}");
-
-    let background_task_buffer_snapshot = buffer_snapshot.clone();
-    let backround_fetch_range = fetch_range.clone();
-    let new_update = cx
-        .background_executor()
-        .spawn(async move {
-            calculate_hint_updates(
-                query.excerpt_id,
-                invalidate,
-                backround_fetch_range,
-                new_hints,
-                &background_task_buffer_snapshot,
-                cached_excerpt_hints,
-                &visible_hints,
-            )
-        })
-        .await;
-    if let Some(new_update) = new_update {
-        log::debug!(
-            "Applying update for range {fetch_range_to_log:?}: remove from editor: {}, remove from cache: {}, add to cache: {}",
-            new_update.remove_from_visible.len(),
-            new_update.remove_from_cache.len(),
-            new_update.add_to_cache.len()
-        );
-        log::trace!("New update: {new_update:?}");
-        editor
-            .update(&mut cx, |editor, cx| {
-                apply_hint_update(
-                    editor,
-                    new_update,
-                    query,
-                    invalidate,
-                    buffer_snapshot,
-                    multi_buffer_snapshot,
-                    cx,
-                );
-            })
-            .ok();
-    }
-    Ok(())
-}
-
-fn calculate_hint_updates(
-    excerpt_id: ExcerptId,
-    invalidate: bool,
-    fetch_range: Range<language::Anchor>,
-    new_excerpt_hints: Vec<InlayHint>,
-    buffer_snapshot: &BufferSnapshot,
-    cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
-    visible_hints: &[Inlay],
-) -> Option<ExcerptHintsUpdate> {
-    let mut add_to_cache = Vec::<InlayHint>::new();
-    let mut excerpt_hints_to_persist = HashMap::default();
-    for new_hint in new_excerpt_hints {
-        if !contains_position(&fetch_range, new_hint.position, buffer_snapshot) {
-            continue;
-        }
-        let missing_from_cache = match &cached_excerpt_hints {
-            Some(cached_excerpt_hints) => {
-                let cached_excerpt_hints = cached_excerpt_hints.read();
-                match cached_excerpt_hints
-                    .ordered_hints
-                    .binary_search_by(|probe| {
-                        cached_excerpt_hints.hints_by_id[probe]
-                            .position
-                            .cmp(&new_hint.position, buffer_snapshot)
-                    }) {
-                    Ok(ix) => {
-                        let mut missing_from_cache = true;
-                        for id in &cached_excerpt_hints.ordered_hints[ix..] {
-                            let cached_hint = &cached_excerpt_hints.hints_by_id[id];
-                            if new_hint
-                                .position
-                                .cmp(&cached_hint.position, buffer_snapshot)
-                                .is_gt()
-                            {
-                                break;
-                            }
-                            if cached_hint == &new_hint {
-                                excerpt_hints_to_persist.insert(*id, cached_hint.kind);
-                                missing_from_cache = false;
-                            }
-                        }
-                        missing_from_cache
-                    }
-                    Err(_) => true,
-                }
-            }
-            None => true,
-        };
-        if missing_from_cache {
-            add_to_cache.push(new_hint);
-        }
-    }
-
-    let mut remove_from_visible = Vec::new();
-    let mut remove_from_cache = HashSet::default();
-    if invalidate {
-        remove_from_visible.extend(
-            visible_hints
-                .iter()
-                .filter(|hint| hint.position.excerpt_id == excerpt_id)
-                .map(|inlay_hint| inlay_hint.id)
-                .filter(|hint_id| !excerpt_hints_to_persist.contains_key(hint_id)),
-        );
-
-        if let Some(cached_excerpt_hints) = &cached_excerpt_hints {
-            let cached_excerpt_hints = cached_excerpt_hints.read();
-            remove_from_cache.extend(
-                cached_excerpt_hints
-                    .ordered_hints
-                    .iter()
-                    .filter(|cached_inlay_id| {
-                        !excerpt_hints_to_persist.contains_key(cached_inlay_id)
-                    })
-                    .copied(),
-            );
-        }
-    }
-
-    if remove_from_visible.is_empty() && remove_from_cache.is_empty() && add_to_cache.is_empty() {
-        None
-    } else {
-        Some(ExcerptHintsUpdate {
-            excerpt_id,
-            remove_from_visible,
-            remove_from_cache,
-            add_to_cache,
-        })
-    }
-}
-
-fn contains_position(
-    range: &Range<language::Anchor>,
-    position: language::Anchor,
-    buffer_snapshot: &BufferSnapshot,
-) -> bool {
-    range.start.cmp(&position, buffer_snapshot).is_le()
-        && range.end.cmp(&position, buffer_snapshot).is_ge()
-}
-
-fn apply_hint_update(
-    editor: &mut Editor,
-    new_update: ExcerptHintsUpdate,
-    query: ExcerptQuery,
-    invalidate: bool,
-    buffer_snapshot: BufferSnapshot,
-    multi_buffer_snapshot: MultiBufferSnapshot,
-    cx: &mut ViewContext<'_, Editor>,
-) {
-    let cached_excerpt_hints = editor
-        .inlay_hint_cache
-        .hints
-        .entry(new_update.excerpt_id)
-        .or_insert_with(|| {
-            Arc::new(RwLock::new(CachedExcerptHints {
-                version: query.cache_version,
-                buffer_version: buffer_snapshot.version().clone(),
-                buffer_id: query.buffer_id,
-                ordered_hints: Vec::new(),
-                hints_by_id: HashMap::default(),
-            }))
-        });
-    let mut cached_excerpt_hints = cached_excerpt_hints.write();
-    match query.cache_version.cmp(&cached_excerpt_hints.version) {
-        cmp::Ordering::Less => return,
-        cmp::Ordering::Greater | cmp::Ordering::Equal => {
-            cached_excerpt_hints.version = query.cache_version;
-        }
-    }
-
-    let mut cached_inlays_changed = !new_update.remove_from_cache.is_empty();
-    cached_excerpt_hints
-        .ordered_hints
-        .retain(|hint_id| !new_update.remove_from_cache.contains(hint_id));
-    cached_excerpt_hints
-        .hints_by_id
-        .retain(|hint_id, _| !new_update.remove_from_cache.contains(hint_id));
-    let mut splice = InlaySplice {
-        to_remove: new_update.remove_from_visible,
-        to_insert: Vec::new(),
-    };
-    for new_hint in new_update.add_to_cache {
-        let insert_position = match cached_excerpt_hints
-            .ordered_hints
-            .binary_search_by(|probe| {
-                cached_excerpt_hints.hints_by_id[probe]
-                    .position
-                    .cmp(&new_hint.position, &buffer_snapshot)
-            }) {
-            Ok(i) => {
-                let mut insert_position = Some(i);
-                for id in &cached_excerpt_hints.ordered_hints[i..] {
-                    let cached_hint = &cached_excerpt_hints.hints_by_id[id];
-                    if new_hint
-                        .position
-                        .cmp(&cached_hint.position, &buffer_snapshot)
-                        .is_gt()
-                    {
-                        break;
-                    }
-                    if cached_hint.text() == new_hint.text() {
-                        insert_position = None;
-                        break;
-                    }
-                }
-                insert_position
-            }
-            Err(i) => Some(i),
-        };
-
-        if let Some(insert_position) = insert_position {
-            let new_inlay_id = post_inc(&mut editor.next_inlay_id);
-            if editor
-                .inlay_hint_cache
-                .allowed_hint_kinds
-                .contains(&new_hint.kind)
-            {
-                let new_hint_position =
-                    multi_buffer_snapshot.anchor_in_excerpt(query.excerpt_id, new_hint.position);
-                splice
-                    .to_insert
-                    .push(Inlay::hint(new_inlay_id, new_hint_position, &new_hint));
-            }
-            let new_id = InlayId::Hint(new_inlay_id);
-            cached_excerpt_hints.hints_by_id.insert(new_id, new_hint);
-            cached_excerpt_hints
-                .ordered_hints
-                .insert(insert_position, new_id);
-            cached_inlays_changed = true;
-        }
-    }
-    cached_excerpt_hints.buffer_version = buffer_snapshot.version().clone();
-    drop(cached_excerpt_hints);
-
-    if invalidate {
-        let mut outdated_excerpt_caches = HashSet::default();
-        for (excerpt_id, excerpt_hints) in &editor.inlay_hint_cache().hints {
-            let excerpt_hints = excerpt_hints.read();
-            if excerpt_hints.buffer_id == query.buffer_id
-                && excerpt_id != &query.excerpt_id
-                && buffer_snapshot
-                    .version()
-                    .changed_since(&excerpt_hints.buffer_version)
-            {
-                outdated_excerpt_caches.insert(*excerpt_id);
-                splice
-                    .to_remove
-                    .extend(excerpt_hints.ordered_hints.iter().copied());
-            }
-        }
-        cached_inlays_changed |= !outdated_excerpt_caches.is_empty();
-        editor
-            .inlay_hint_cache
-            .hints
-            .retain(|excerpt_id, _| !outdated_excerpt_caches.contains(excerpt_id));
-    }
-
-    let InlaySplice {
-        to_remove,
-        to_insert,
-    } = splice;
-    let displayed_inlays_changed = !to_remove.is_empty() || !to_insert.is_empty();
-    if cached_inlays_changed || displayed_inlays_changed {
-        editor.inlay_hint_cache.version += 1;
-    }
-    if displayed_inlays_changed {
-        editor.splice_inlay_hints(to_remove, to_insert, cx)
-    }
-}
-
-#[cfg(test)]
-pub mod tests {
-    use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
-
-    use crate::{
-        scroll::{autoscroll::Autoscroll, scroll_amount::ScrollAmount},
-        ExcerptRange,
-    };
-    use futures::StreamExt;
-    use gpui::{Context, TestAppContext, WindowHandle};
-    use itertools::Itertools;
-    use language::{
-        language_settings::AllLanguageSettingsContent, FakeLspAdapter, Language, LanguageConfig,
-    };
-    use lsp::FakeLanguageServer;
-    use parking_lot::Mutex;
-    use project::{FakeFs, Project};
-    use serde_json::json;
-    use settings::SettingsStore;
-    use text::{Point, ToPoint};
-
-    use crate::editor_tests::update_test_language_settings;
-
-    use super::*;
-
-    #[gpui::test]
-    async fn test_basic_cache_update_with_duplicate_hints(cx: &mut gpui::TestAppContext) {
-        let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
-        init_test(cx, |settings| {
-            settings.defaults.inlay_hints = Some(InlayHintSettings {
-                enabled: true,
-                show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
-                show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
-                show_other_hints: allowed_hint_kinds.contains(&None),
-            })
-        });
-
-        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
-        let lsp_request_count = Arc::new(AtomicU32::new(0));
-        fake_server
-            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
-                let task_lsp_request_count = Arc::clone(&lsp_request_count);
-                async move {
-                    assert_eq!(
-                        params.text_document.uri,
-                        lsp::Url::from_file_path(file_with_hints).unwrap(),
-                    );
-                    let current_call_id =
-                        Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
-                    let mut new_hints = Vec::with_capacity(2 * current_call_id as usize);
-                    for _ in 0..2 {
-                        let mut i = current_call_id;
-                        loop {
-                            new_hints.push(lsp::InlayHint {
-                                position: lsp::Position::new(0, i),
-                                label: lsp::InlayHintLabel::String(i.to_string()),
-                                kind: None,
-                                text_edits: None,
-                                tooltip: None,
-                                padding_left: None,
-                                padding_right: None,
-                                data: None,
-                            });
-                            if i == 0 {
-                                break;
-                            }
-                            i -= 1;
-                        }
-                    }
-
-                    Ok(Some(new_hints))
-                }
-            })
-            .next()
-            .await;
-        cx.executor().run_until_parked();
-
-        let mut edits_made = 1;
-        _ = editor.update(cx, |editor, cx| {
-            let expected_hints = vec!["0".to_string()];
-            assert_eq!(
-                expected_hints,
-                cached_hint_labels(editor),
-                "Should get its first hints when opening the editor"
-            );
-            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-            let inlay_cache = editor.inlay_hint_cache();
-            assert_eq!(
-                inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
-                "Cache should use editor settings to get the allowed hint kinds"
-            );
-            assert_eq!(
-                inlay_cache.version, edits_made,
-                "The editor update the cache version after every cache/view change"
-            );
-        });
-
-        _ = editor.update(cx, |editor, cx| {
-            editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
-            editor.handle_input("some change", cx);
-            edits_made += 1;
-        });
-        cx.executor().run_until_parked();
-        _ = editor.update(cx, |editor, cx| {
-            let expected_hints = vec!["0".to_string(), "1".to_string()];
-            assert_eq!(
-                expected_hints,
-                cached_hint_labels(editor),
-                "Should get new hints after an edit"
-            );
-            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-            let inlay_cache = editor.inlay_hint_cache();
-            assert_eq!(
-                inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
-                "Cache should use editor settings to get the allowed hint kinds"
-            );
-            assert_eq!(
-                inlay_cache.version, edits_made,
-                "The editor update the cache version after every cache/view change"
-            );
-        });
-
-        fake_server
-            .request::<lsp::request::InlayHintRefreshRequest>(())
-            .await
-            .expect("inlay refresh request failed");
-        edits_made += 1;
-        cx.executor().run_until_parked();
-        _ = editor.update(cx, |editor, cx| {
-            let expected_hints = vec!["0".to_string(), "1".to_string(), "2".to_string()];
-            assert_eq!(
-                expected_hints,
-                cached_hint_labels(editor),
-                "Should get new hints after hint refresh/ request"
-            );
-            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-            let inlay_cache = editor.inlay_hint_cache();
-            assert_eq!(
-                inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
-                "Cache should use editor settings to get the allowed hint kinds"
-            );
-            assert_eq!(
-                inlay_cache.version, edits_made,
-                "The editor update the cache version after every cache/view change"
-            );
-        });
-    }
-
-    #[gpui::test]
-    async fn test_cache_update_on_lsp_completion_tasks(cx: &mut gpui::TestAppContext) {
-        init_test(cx, |settings| {
-            settings.defaults.inlay_hints = Some(InlayHintSettings {
-                enabled: true,
-                show_type_hints: true,
-                show_parameter_hints: true,
-                show_other_hints: true,
-            })
-        });
-
-        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
-        let lsp_request_count = Arc::new(AtomicU32::new(0));
-        fake_server
-            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
-                let task_lsp_request_count = Arc::clone(&lsp_request_count);
-                async move {
-                    assert_eq!(
-                        params.text_document.uri,
-                        lsp::Url::from_file_path(file_with_hints).unwrap(),
-                    );
-                    let current_call_id =
-                        Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
-                    Ok(Some(vec![lsp::InlayHint {
-                        position: lsp::Position::new(0, current_call_id),
-                        label: lsp::InlayHintLabel::String(current_call_id.to_string()),
-                        kind: None,
-                        text_edits: None,
-                        tooltip: None,
-                        padding_left: None,
-                        padding_right: None,
-                        data: None,
-                    }]))
-                }
-            })
-            .next()
-            .await;
-        cx.executor().run_until_parked();
-
-        let mut edits_made = 1;
-        _ = editor.update(cx, |editor, cx| {
-            let expected_hints = vec!["0".to_string()];
-            assert_eq!(
-                expected_hints,
-                cached_hint_labels(editor),
-                "Should get its first hints when opening the editor"
-            );
-            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-            assert_eq!(
-                editor.inlay_hint_cache().version,
-                edits_made,
-                "The editor update the cache version after every cache/view change"
-            );
-        });
-
-        let progress_token = "test_progress_token";
-        fake_server
-            .request::<lsp::request::WorkDoneProgressCreate>(lsp::WorkDoneProgressCreateParams {
-                token: lsp::ProgressToken::String(progress_token.to_string()),
-            })
-            .await
-            .expect("work done progress create request failed");
-        cx.executor().run_until_parked();
-        fake_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
-            token: lsp::ProgressToken::String(progress_token.to_string()),
-            value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Begin(
-                lsp::WorkDoneProgressBegin::default(),
-            )),
-        });
-        cx.executor().run_until_parked();
-
-        _ = editor.update(cx, |editor, cx| {
-            let expected_hints = vec!["0".to_string()];
-            assert_eq!(
-                expected_hints,
-                cached_hint_labels(editor),
-                "Should not update hints while the work task is running"
-            );
-            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-            assert_eq!(
-                editor.inlay_hint_cache().version,
-                edits_made,
-                "Should not update the cache while the work task is running"
-            );
-        });
-
-        fake_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
-            token: lsp::ProgressToken::String(progress_token.to_string()),
-            value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::End(
-                lsp::WorkDoneProgressEnd::default(),
-            )),
-        });
-        cx.executor().run_until_parked();
-
-        edits_made += 1;
-        _ = editor.update(cx, |editor, cx| {
-            let expected_hints = vec!["1".to_string()];
-            assert_eq!(
-                expected_hints,
-                cached_hint_labels(editor),
-                "New hints should be queried after the work task is done"
-            );
-            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-            assert_eq!(
-                editor.inlay_hint_cache().version,
-                edits_made,
-                "Cache version should udpate once after the work task is done"
-            );
-        });
-    }
-
-    #[gpui::test]
-    async fn test_no_hint_updates_for_unrelated_language_files(cx: &mut gpui::TestAppContext) {
-        init_test(cx, |settings| {
-            settings.defaults.inlay_hints = Some(InlayHintSettings {
-                enabled: true,
-                show_type_hints: true,
-                show_parameter_hints: true,
-                show_other_hints: true,
-            })
-        });
-
-        let fs = FakeFs::new(cx.background_executor.clone());
-        fs.insert_tree(
-                    "/a",
-                    json!({
-                        "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
-                        "other.md": "Test md file with some text",
-                    }),
-                )
-                .await;
-        let project = Project::test(fs, ["/a".as_ref()], cx).await;
-
-        let mut rs_fake_servers = None;
-        let mut md_fake_servers = None;
-        for (name, path_suffix) in [("Rust", "rs"), ("Markdown", "md")] {
-            let mut language = Language::new(
-                LanguageConfig {
-                    name: name.into(),
-                    path_suffixes: vec![path_suffix.to_string()],
-                    ..Default::default()
-                },
-                Some(tree_sitter_rust::language()),
-            );
-            let fake_servers = language
-                .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-                    name,
-                    capabilities: lsp::ServerCapabilities {
-                        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
-                        ..Default::default()
-                    },
-                    ..Default::default()
-                }))
-                .await;
-            match name {
-                "Rust" => rs_fake_servers = Some(fake_servers),
-                "Markdown" => md_fake_servers = Some(fake_servers),
-                _ => unreachable!(),
-            }
-            project.update(cx, |project, _| {
-                project.languages().add(Arc::new(language));
-            });
-        }
-
-        let rs_buffer = project
-            .update(cx, |project, cx| {
-                project.open_local_buffer("/a/main.rs", cx)
-            })
-            .await
-            .unwrap();
-        cx.executor().run_until_parked();
-        cx.executor().start_waiting();
-        let rs_fake_server = rs_fake_servers.unwrap().next().await.unwrap();
-        let rs_editor =
-            cx.add_window(|cx| Editor::for_buffer(rs_buffer, Some(project.clone()), cx));
-        let rs_lsp_request_count = Arc::new(AtomicU32::new(0));
-        rs_fake_server
-            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
-                let task_lsp_request_count = Arc::clone(&rs_lsp_request_count);
-                async move {
-                    assert_eq!(
-                        params.text_document.uri,
-                        lsp::Url::from_file_path("/a/main.rs").unwrap(),
-                    );
-                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
-                    Ok(Some(vec![lsp::InlayHint {
-                        position: lsp::Position::new(0, i),
-                        label: lsp::InlayHintLabel::String(i.to_string()),
-                        kind: None,
-                        text_edits: None,
-                        tooltip: None,
-                        padding_left: None,
-                        padding_right: None,
-                        data: None,
-                    }]))
-                }
-            })
-            .next()
-            .await;
-        cx.executor().run_until_parked();
-        _ = rs_editor.update(cx, |editor, cx| {
-            let expected_hints = vec!["0".to_string()];
-            assert_eq!(
-                expected_hints,
-                cached_hint_labels(editor),
-                "Should get its first hints when opening the editor"
-            );
-            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-            assert_eq!(
-                editor.inlay_hint_cache().version,
-                1,
-                "Rust editor update the cache version after every cache/view change"
-            );
-        });
-
-        cx.executor().run_until_parked();
-        let md_buffer = project
-            .update(cx, |project, cx| {
-                project.open_local_buffer("/a/other.md", cx)
-            })
-            .await
-            .unwrap();
-        cx.executor().run_until_parked();
-        cx.executor().start_waiting();
-        let md_fake_server = md_fake_servers.unwrap().next().await.unwrap();
-        let md_editor = cx.add_window(|cx| Editor::for_buffer(md_buffer, Some(project), cx));
-        let md_lsp_request_count = Arc::new(AtomicU32::new(0));
-        md_fake_server
-            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
-                let task_lsp_request_count = Arc::clone(&md_lsp_request_count);
-                async move {
-                    assert_eq!(
-                        params.text_document.uri,
-                        lsp::Url::from_file_path("/a/other.md").unwrap(),
-                    );
-                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
-                    Ok(Some(vec![lsp::InlayHint {
-                        position: lsp::Position::new(0, i),
-                        label: lsp::InlayHintLabel::String(i.to_string()),
-                        kind: None,
-                        text_edits: None,
-                        tooltip: None,
-                        padding_left: None,
-                        padding_right: None,
-                        data: None,
-                    }]))
-                }
-            })
-            .next()
-            .await;
-        cx.executor().run_until_parked();
-        _ = md_editor.update(cx, |editor, cx| {
-            let expected_hints = vec!["0".to_string()];
-            assert_eq!(
-                expected_hints,
-                cached_hint_labels(editor),
-                "Markdown editor should have a separate verison, repeating Rust editor rules"
-            );
-            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-            assert_eq!(editor.inlay_hint_cache().version, 1);
-        });
-
-        _ = rs_editor.update(cx, |editor, cx| {
-            editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
-            editor.handle_input("some rs change", cx);
-        });
-        cx.executor().run_until_parked();
-        _ = rs_editor.update(cx, |editor, cx| {
-            let expected_hints = vec!["1".to_string()];
-            assert_eq!(
-                expected_hints,
-                cached_hint_labels(editor),
-                "Rust inlay cache should change after the edit"
-            );
-            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-            assert_eq!(
-                editor.inlay_hint_cache().version,
-                2,
-                "Every time hint cache changes, cache version should be incremented"
-            );
-        });
-        _ = md_editor.update(cx, |editor, cx| {
-            let expected_hints = vec!["0".to_string()];
-            assert_eq!(
-                expected_hints,
-                cached_hint_labels(editor),
-                "Markdown editor should not be affected by Rust editor changes"
-            );
-            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-            assert_eq!(editor.inlay_hint_cache().version, 1);
-        });
-
-        _ = md_editor.update(cx, |editor, cx| {
-            editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
-            editor.handle_input("some md change", cx);
-        });
-        cx.executor().run_until_parked();
-        _ = md_editor.update(cx, |editor, cx| {
-            let expected_hints = vec!["1".to_string()];
-            assert_eq!(
-                expected_hints,
-                cached_hint_labels(editor),
-                "Rust editor should not be affected by Markdown editor changes"
-            );
-            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-            assert_eq!(editor.inlay_hint_cache().version, 2);
-        });
-        _ = rs_editor.update(cx, |editor, cx| {
-            let expected_hints = vec!["1".to_string()];
-            assert_eq!(
-                expected_hints,
-                cached_hint_labels(editor),
-                "Markdown editor should also change independently"
-            );
-            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-            assert_eq!(editor.inlay_hint_cache().version, 2);
-        });
-    }
-
-    #[gpui::test]
-    async fn test_hint_setting_changes(cx: &mut gpui::TestAppContext) {
-        let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
-        init_test(cx, |settings| {
-            settings.defaults.inlay_hints = Some(InlayHintSettings {
-                enabled: true,
-                show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
-                show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
-                show_other_hints: allowed_hint_kinds.contains(&None),
-            })
-        });
-
-        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
-        let lsp_request_count = Arc::new(AtomicU32::new(0));
-        let another_lsp_request_count = Arc::clone(&lsp_request_count);
-        fake_server
-            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
-                let task_lsp_request_count = Arc::clone(&another_lsp_request_count);
-                async move {
-                    Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
-                    assert_eq!(
-                        params.text_document.uri,
-                        lsp::Url::from_file_path(file_with_hints).unwrap(),
-                    );
-                    Ok(Some(vec![
-                        lsp::InlayHint {
-                            position: lsp::Position::new(0, 1),
-                            label: lsp::InlayHintLabel::String("type hint".to_string()),
-                            kind: Some(lsp::InlayHintKind::TYPE),
-                            text_edits: None,
-                            tooltip: None,
-                            padding_left: None,
-                            padding_right: None,
-                            data: None,
-                        },
-                        lsp::InlayHint {
-                            position: lsp::Position::new(0, 2),
-                            label: lsp::InlayHintLabel::String("parameter hint".to_string()),
-                            kind: Some(lsp::InlayHintKind::PARAMETER),
-                            text_edits: None,
-                            tooltip: None,
-                            padding_left: None,
-                            padding_right: None,
-                            data: None,
-                        },
-                        lsp::InlayHint {
-                            position: lsp::Position::new(0, 3),
-                            label: lsp::InlayHintLabel::String("other hint".to_string()),
-                            kind: None,
-                            text_edits: None,
-                            tooltip: None,
-                            padding_left: None,
-                            padding_right: None,
-                            data: None,
-                        },
-                    ]))
-                }
-            })
-            .next()
-            .await;
-        cx.executor().run_until_parked();
-
-        let mut edits_made = 1;
-        _ = editor.update(cx, |editor, cx| {
-            assert_eq!(
-                lsp_request_count.load(Ordering::Relaxed),
-                1,
-                "Should query new hints once"
-            );
-            assert_eq!(
-                vec![
-                    "other hint".to_string(),
-                    "parameter hint".to_string(),
-                    "type hint".to_string(),
-                ],
-                cached_hint_labels(editor),
-                "Should get its first hints when opening the editor"
-            );
-            assert_eq!(
-                vec!["other hint".to_string(), "type hint".to_string()],
-                visible_hint_labels(editor, cx)
-            );
-            let inlay_cache = editor.inlay_hint_cache();
-            assert_eq!(
-                inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
-                "Cache should use editor settings to get the allowed hint kinds"
-            );
-            assert_eq!(
-                inlay_cache.version, edits_made,
-                "The editor update the cache version after every cache/view change"
-            );
-        });
-
-        fake_server
-            .request::<lsp::request::InlayHintRefreshRequest>(())
-            .await
-            .expect("inlay refresh request failed");
-        cx.executor().run_until_parked();
-        _ = editor.update(cx, |editor, cx| {
-            assert_eq!(
-                lsp_request_count.load(Ordering::Relaxed),
-                2,
-                "Should load new hints twice"
-            );
-            assert_eq!(
-                vec![
-                    "other hint".to_string(),
-                    "parameter hint".to_string(),
-                    "type hint".to_string(),
-                ],
-                cached_hint_labels(editor),
-                "Cached hints should not change due to allowed hint kinds settings update"
-            );
-            assert_eq!(
-                vec!["other hint".to_string(), "type hint".to_string()],
-                visible_hint_labels(editor, cx)
-            );
-            assert_eq!(
-                editor.inlay_hint_cache().version,
-                edits_made,
-                "Should not update cache version due to new loaded hints being the same"
-            );
-        });
-
-        for (new_allowed_hint_kinds, expected_visible_hints) in [
-            (HashSet::from_iter([None]), vec!["other hint".to_string()]),
-            (
-                HashSet::from_iter([Some(InlayHintKind::Type)]),
-                vec!["type hint".to_string()],
-            ),
-            (
-                HashSet::from_iter([Some(InlayHintKind::Parameter)]),
-                vec!["parameter hint".to_string()],
-            ),
-            (
-                HashSet::from_iter([None, Some(InlayHintKind::Type)]),
-                vec!["other hint".to_string(), "type hint".to_string()],
-            ),
-            (
-                HashSet::from_iter([None, Some(InlayHintKind::Parameter)]),
-                vec!["other hint".to_string(), "parameter hint".to_string()],
-            ),
-            (
-                HashSet::from_iter([Some(InlayHintKind::Type), Some(InlayHintKind::Parameter)]),
-                vec!["parameter hint".to_string(), "type hint".to_string()],
-            ),
-            (
-                HashSet::from_iter([
-                    None,
-                    Some(InlayHintKind::Type),
-                    Some(InlayHintKind::Parameter),
-                ]),
-                vec![
-                    "other hint".to_string(),
-                    "parameter hint".to_string(),
-                    "type hint".to_string(),
-                ],
-            ),
-        ] {
-            edits_made += 1;
-            update_test_language_settings(cx, |settings| {
-                settings.defaults.inlay_hints = Some(InlayHintSettings {
-                    enabled: true,
-                    show_type_hints: new_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
-                    show_parameter_hints: new_allowed_hint_kinds
-                        .contains(&Some(InlayHintKind::Parameter)),
-                    show_other_hints: new_allowed_hint_kinds.contains(&None),
-                })
-            });
-            cx.executor().run_until_parked();
-            _ = editor.update(cx, |editor, cx| {
-                assert_eq!(
-                    lsp_request_count.load(Ordering::Relaxed),
-                    2,
-                    "Should not load new hints on allowed hint kinds change for hint kinds {new_allowed_hint_kinds:?}"
-                );
-                assert_eq!(
-                    vec![
-                        "other hint".to_string(),
-                        "parameter hint".to_string(),
-                        "type hint".to_string(),
-                    ],
-                    cached_hint_labels(editor),
-                    "Should get its cached hints unchanged after the settings change for hint kinds {new_allowed_hint_kinds:?}"
-                );
-                assert_eq!(
-                    expected_visible_hints,
-                    visible_hint_labels(editor, cx),
-                    "Should get its visible hints filtered after the settings change for hint kinds {new_allowed_hint_kinds:?}"
-                );
-                let inlay_cache = editor.inlay_hint_cache();
-                assert_eq!(
-                    inlay_cache.allowed_hint_kinds, new_allowed_hint_kinds,
-                    "Cache should use editor settings to get the allowed hint kinds for hint kinds {new_allowed_hint_kinds:?}"
-                );
-                assert_eq!(
-                    inlay_cache.version, edits_made,
-                    "The editor should update the cache version after every cache/view change for hint kinds {new_allowed_hint_kinds:?} due to visible hints change"
-                );
-            });
-        }
-
-        edits_made += 1;
-        let another_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Type)]);
-        update_test_language_settings(cx, |settings| {
-            settings.defaults.inlay_hints = Some(InlayHintSettings {
-                enabled: false,
-                show_type_hints: another_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
-                show_parameter_hints: another_allowed_hint_kinds
-                    .contains(&Some(InlayHintKind::Parameter)),
-                show_other_hints: another_allowed_hint_kinds.contains(&None),
-            })
-        });
-        cx.executor().run_until_parked();
-        _ = editor.update(cx, |editor, cx| {
-            assert_eq!(
-                lsp_request_count.load(Ordering::Relaxed),
-                2,
-                "Should not load new hints when hints got disabled"
-            );
-            assert!(
-                cached_hint_labels(editor).is_empty(),
-                "Should clear the cache when hints got disabled"
-            );
-            assert!(
-                visible_hint_labels(editor, cx).is_empty(),
-                "Should clear visible hints when hints got disabled"
-            );
-            let inlay_cache = editor.inlay_hint_cache();
-            assert_eq!(
-                inlay_cache.allowed_hint_kinds, another_allowed_hint_kinds,
-                "Should update its allowed hint kinds even when hints got disabled"
-            );
-            assert_eq!(
-                inlay_cache.version, edits_made,
-                "The editor should update the cache version after hints got disabled"
-            );
-        });
-
-        fake_server
-            .request::<lsp::request::InlayHintRefreshRequest>(())
-            .await
-            .expect("inlay refresh request failed");
-        cx.executor().run_until_parked();
-        _ = editor.update(cx, |editor, cx| {
-            assert_eq!(
-                lsp_request_count.load(Ordering::Relaxed),
-                2,
-                "Should not load new hints when they got disabled"
-            );
-            assert!(cached_hint_labels(editor).is_empty());
-            assert!(visible_hint_labels(editor, cx).is_empty());
-            assert_eq!(
-                editor.inlay_hint_cache().version, edits_made,
-                "The editor should not update the cache version after /refresh query without updates"
-            );
-        });
-
-        let final_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Parameter)]);
-        edits_made += 1;
-        update_test_language_settings(cx, |settings| {
-            settings.defaults.inlay_hints = Some(InlayHintSettings {
-                enabled: true,
-                show_type_hints: final_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
-                show_parameter_hints: final_allowed_hint_kinds
-                    .contains(&Some(InlayHintKind::Parameter)),
-                show_other_hints: final_allowed_hint_kinds.contains(&None),
-            })
-        });
-        cx.executor().run_until_parked();
-        _ = editor.update(cx, |editor, cx| {
-            assert_eq!(
-                lsp_request_count.load(Ordering::Relaxed),
-                3,
-                "Should query for new hints when they got reenabled"
-            );
-            assert_eq!(
-                vec![
-                    "other hint".to_string(),
-                    "parameter hint".to_string(),
-                    "type hint".to_string(),
-                ],
-                cached_hint_labels(editor),
-                "Should get its cached hints fully repopulated after the hints got reenabled"
-            );
-            assert_eq!(
-                vec!["parameter hint".to_string()],
-                visible_hint_labels(editor, cx),
-                "Should get its visible hints repopulated and filtered after the h"
-            );
-            let inlay_cache = editor.inlay_hint_cache();
-            assert_eq!(
-                inlay_cache.allowed_hint_kinds, final_allowed_hint_kinds,
-                "Cache should update editor settings when hints got reenabled"
-            );
-            assert_eq!(
-                inlay_cache.version, edits_made,
-                "Cache should update its version after hints got reenabled"
-            );
-        });
-
-        fake_server
-            .request::<lsp::request::InlayHintRefreshRequest>(())
-            .await
-            .expect("inlay refresh request failed");
-        cx.executor().run_until_parked();
-        _ = editor.update(cx, |editor, cx| {
-            assert_eq!(
-                lsp_request_count.load(Ordering::Relaxed),
-                4,
-                "Should query for new hints again"
-            );
-            assert_eq!(
-                vec![
-                    "other hint".to_string(),
-                    "parameter hint".to_string(),
-                    "type hint".to_string(),
-                ],
-                cached_hint_labels(editor),
-            );
-            assert_eq!(
-                vec!["parameter hint".to_string()],
-                visible_hint_labels(editor, cx),
-            );
-            assert_eq!(editor.inlay_hint_cache().version, edits_made);
-        });
-    }
-
-    #[gpui::test]
-    async fn test_hint_request_cancellation(cx: &mut gpui::TestAppContext) {
-        init_test(cx, |settings| {
-            settings.defaults.inlay_hints = Some(InlayHintSettings {
-                enabled: true,
-                show_type_hints: true,
-                show_parameter_hints: true,
-                show_other_hints: true,
-            })
-        });
-
-        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
-        let fake_server = Arc::new(fake_server);
-        let lsp_request_count = Arc::new(AtomicU32::new(0));
-        let another_lsp_request_count = Arc::clone(&lsp_request_count);
-        fake_server
-            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
-                let task_lsp_request_count = Arc::clone(&another_lsp_request_count);
-                async move {
-                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1;
-                    assert_eq!(
-                        params.text_document.uri,
-                        lsp::Url::from_file_path(file_with_hints).unwrap(),
-                    );
-                    Ok(Some(vec![lsp::InlayHint {
-                        position: lsp::Position::new(0, i),
-                        label: lsp::InlayHintLabel::String(i.to_string()),
-                        kind: None,
-                        text_edits: None,
-                        tooltip: None,
-                        padding_left: None,
-                        padding_right: None,
-                        data: None,
-                    }]))
-                }
-            })
-            .next()
-            .await;
-
-        let mut expected_changes = Vec::new();
-        for change_after_opening in [
-            "initial change #1",
-            "initial change #2",
-            "initial change #3",
-        ] {
-            _ = editor.update(cx, |editor, cx| {
-                editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
-                editor.handle_input(change_after_opening, cx);
-            });
-            expected_changes.push(change_after_opening);
-        }
-
-        cx.executor().run_until_parked();
-
-        _ = editor.update(cx, |editor, cx| {
-            let current_text = editor.text(cx);
-            for change in &expected_changes {
-                assert!(
-                    current_text.contains(change),
-                    "Should apply all changes made"
-                );
-            }
-            assert_eq!(
-                lsp_request_count.load(Ordering::Relaxed),
-                2,
-                "Should query new hints twice: for editor init and for the last edit that interrupted all others"
-            );
-            let expected_hints = vec!["2".to_string()];
-            assert_eq!(
-                expected_hints,
-                cached_hint_labels(editor),
-                "Should get hints from the last edit landed only"
-            );
-            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-            assert_eq!(
-                editor.inlay_hint_cache().version, 1,
-                "Only one update should be registered in the cache after all cancellations"
-            );
-        });
-
-        let mut edits = Vec::new();
-        for async_later_change in [
-            "another change #1",
-            "another change #2",
-            "another change #3",
-        ] {
-            expected_changes.push(async_later_change);
-            let task_editor = editor.clone();
-            edits.push(cx.spawn(|mut cx| async move {
-                _ = task_editor.update(&mut cx, |editor, cx| {
-                    editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
-                    editor.handle_input(async_later_change, cx);
-                });
-            }));
-        }
-        let _ = future::join_all(edits).await;
-        cx.executor().run_until_parked();
-
-        _ = editor.update(cx, |editor, cx| {
-            let current_text = editor.text(cx);
-            for change in &expected_changes {
-                assert!(
-                    current_text.contains(change),
-                    "Should apply all changes made"
-                );
-            }
-            assert_eq!(
-                lsp_request_count.load(Ordering::SeqCst),
-                3,
-                "Should query new hints one more time, for the last edit only"
-            );
-            let expected_hints = vec!["3".to_string()];
-            assert_eq!(
-                expected_hints,
-                cached_hint_labels(editor),
-                "Should get hints from the last edit landed only"
-            );
-            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-            assert_eq!(
-                editor.inlay_hint_cache().version,
-                2,
-                "Should update the cache version once more, for the new change"
-            );
-        });
-    }
-
-    #[gpui::test(iterations = 10)]
-    async fn test_large_buffer_inlay_requests_split(cx: &mut gpui::TestAppContext) {
-        init_test(cx, |settings| {
-            settings.defaults.inlay_hints = Some(InlayHintSettings {
-                enabled: true,
-                show_type_hints: true,
-                show_parameter_hints: true,
-                show_other_hints: true,
-            })
-        });
-
-        let mut language = Language::new(
-            LanguageConfig {
-                name: "Rust".into(),
-                path_suffixes: vec!["rs".to_string()],
-                ..Default::default()
-            },
-            Some(tree_sitter_rust::language()),
-        );
-        let mut fake_servers = language
-            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-                capabilities: lsp::ServerCapabilities {
-                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
-                    ..Default::default()
-                },
-                ..Default::default()
-            }))
-            .await;
-        let fs = FakeFs::new(cx.background_executor.clone());
-        fs.insert_tree(
-            "/a",
-            json!({
-                "main.rs": format!("fn main() {{\n{}\n}}", "let i = 5;\n".repeat(500)),
-                "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 buffer = project
-            .update(cx, |project, cx| {
-                project.open_local_buffer("/a/main.rs", cx)
-            })
-            .await
-            .unwrap();
-        cx.executor().run_until_parked();
-        cx.executor().start_waiting();
-        let fake_server = fake_servers.next().await.unwrap();
-        let editor = cx.add_window(|cx| Editor::for_buffer(buffer, Some(project), cx));
-        let lsp_request_ranges = Arc::new(Mutex::new(Vec::new()));
-        let lsp_request_count = Arc::new(AtomicUsize::new(0));
-        let closure_lsp_request_ranges = Arc::clone(&lsp_request_ranges);
-        let closure_lsp_request_count = Arc::clone(&lsp_request_count);
-        fake_server
-            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
-                let task_lsp_request_ranges = Arc::clone(&closure_lsp_request_ranges);
-                let task_lsp_request_count = Arc::clone(&closure_lsp_request_count);
-                async move {
-                    assert_eq!(
-                        params.text_document.uri,
-                        lsp::Url::from_file_path("/a/main.rs").unwrap(),
-                    );
-
-                    task_lsp_request_ranges.lock().push(params.range);
-                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::Release) + 1;
-                    Ok(Some(vec![lsp::InlayHint {
-                        position: params.range.end,
-                        label: lsp::InlayHintLabel::String(i.to_string()),
-                        kind: None,
-                        text_edits: None,
-                        tooltip: None,
-                        padding_left: None,
-                        padding_right: None,
-                        data: None,
-                    }]))
-                }
-            })
-            .next()
-            .await;
-
-        fn editor_visible_range(
-            editor: &WindowHandle<Editor>,
-            cx: &mut gpui::TestAppContext,
-        ) -> Range<Point> {
-            let ranges = editor
-                .update(cx, |editor, cx| {
-                    editor.excerpts_for_inlay_hints_query(None, cx)
-                })
-                .unwrap();
-            assert_eq!(
-                ranges.len(),
-                1,
-                "Single buffer should produce a single excerpt with visible range"
-            );
-            let (_, (excerpt_buffer, _, excerpt_visible_range)) =
-                ranges.into_iter().next().unwrap();
-            excerpt_buffer.update(cx, |buffer, _| {
-                let snapshot = buffer.snapshot();
-                let start = buffer
-                    .anchor_before(excerpt_visible_range.start)
-                    .to_point(&snapshot);
-                let end = buffer
-                    .anchor_after(excerpt_visible_range.end)
-                    .to_point(&snapshot);
-                start..end
-            })
-        }
-
-        // in large buffers, requests are made for more than visible range of a buffer.
-        // invisible parts are queried later, to avoid excessive requests on quick typing.
-        // wait the timeout needed to get all requests.
-        cx.executor().advance_clock(Duration::from_millis(
-            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
-        ));
-        cx.executor().run_until_parked();
-        let initial_visible_range = editor_visible_range(&editor, cx);
-        let lsp_initial_visible_range = lsp::Range::new(
-            lsp::Position::new(
-                initial_visible_range.start.row,
-                initial_visible_range.start.column,
-            ),
-            lsp::Position::new(
-                initial_visible_range.end.row,
-                initial_visible_range.end.column,
-            ),
-        );
-        let expected_initial_query_range_end =
-            lsp::Position::new(initial_visible_range.end.row * 2, 2);
-        let mut expected_invisible_query_start = lsp_initial_visible_range.end;
-        expected_invisible_query_start.character += 1;
-        _ = editor.update(cx, |editor, cx| {
-            let ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
-            assert_eq!(ranges.len(), 2,
-                "When scroll is at the edge of a big document, its visible part and the same range further should be queried in order, but got: {ranges:?}");
-            let visible_query_range = &ranges[0];
-            assert_eq!(visible_query_range.start, lsp_initial_visible_range.start);
-            assert_eq!(visible_query_range.end, lsp_initial_visible_range.end);
-            let invisible_query_range = &ranges[1];
-
-            assert_eq!(invisible_query_range.start, expected_invisible_query_start, "Should initially query visible edge of the document");
-            assert_eq!(invisible_query_range.end, expected_initial_query_range_end, "Should initially query visible edge of the document");
-
-            let requests_count = lsp_request_count.load(Ordering::Acquire);
-            assert_eq!(requests_count, 2, "Visible + invisible request");
-            let expected_hints = vec!["1".to_string(), "2".to_string()];
-            assert_eq!(
-                expected_hints,
-                cached_hint_labels(editor),
-                "Should have hints from both LSP requests made for a big file"
-            );
-            assert_eq!(expected_hints, visible_hint_labels(editor, cx), "Should display only hints from the visible range");
-            assert_eq!(
-                editor.inlay_hint_cache().version, requests_count,
-                "LSP queries should've bumped the cache version"
-            );
-        });
-
-        _ = editor.update(cx, |editor, cx| {
-            editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
-            editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
-        });
-        cx.executor().advance_clock(Duration::from_millis(
-            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
-        ));
-        cx.executor().run_until_parked();
-        let visible_range_after_scrolls = editor_visible_range(&editor, cx);
-        let visible_line_count = editor
-            .update(cx, |editor, _| editor.visible_line_count().unwrap())
-            .unwrap();
-        let selection_in_cached_range = editor
-            .update(cx, |editor, cx| {
-                let ranges = lsp_request_ranges
-                    .lock()
-                    .drain(..)
-                    .sorted_by_key(|r| r.start)
-                    .collect::<Vec<_>>();
-                assert_eq!(
-                    ranges.len(),
-                    2,
-                    "Should query 2 ranges after both scrolls, but got: {ranges:?}"
-                );
-                let first_scroll = &ranges[0];
-                let second_scroll = &ranges[1];
-                assert_eq!(
-                    first_scroll.end, second_scroll.start,
-                    "Should query 2 adjacent ranges after the scrolls, but got: {ranges:?}"
-                );
-                assert_eq!(
-                first_scroll.start, expected_initial_query_range_end,
-                "First scroll should start the query right after the end of the original scroll",
-            );
-                assert_eq!(
-                second_scroll.end,
-                lsp::Position::new(
-                    visible_range_after_scrolls.end.row
-                        + visible_line_count.ceil() as u32,
-                    1,
-                ),
-                "Second scroll should query one more screen down after the end of the visible range"
-            );
-
-                let lsp_requests = lsp_request_count.load(Ordering::Acquire);
-                assert_eq!(lsp_requests, 4, "Should query for hints after every scroll");
-                let expected_hints = vec![
-                    "1".to_string(),
-                    "2".to_string(),
-                    "3".to_string(),
-                    "4".to_string(),
-                ];
-                assert_eq!(
-                    expected_hints,
-                    cached_hint_labels(editor),
-                    "Should have hints from the new LSP response after the edit"
-                );
-                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-                assert_eq!(
-                    editor.inlay_hint_cache().version,
-                    lsp_requests,
-                    "Should update the cache for every LSP response with hints added"
-                );
-
-                let mut selection_in_cached_range = visible_range_after_scrolls.end;
-                selection_in_cached_range.row -= visible_line_count.ceil() as u32;
-                selection_in_cached_range
-            })
-            .unwrap();
-
-        _ = editor.update(cx, |editor, cx| {
-            editor.change_selections(Some(Autoscroll::center()), cx, |s| {
-                s.select_ranges([selection_in_cached_range..selection_in_cached_range])
-            });
-        });
-        cx.executor().advance_clock(Duration::from_millis(
-            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
-        ));
-        cx.executor().run_until_parked();
-        _ = editor.update(cx, |_, _| {
-            let ranges = lsp_request_ranges
-                .lock()
-                .drain(..)
-                .sorted_by_key(|r| r.start)
-                .collect::<Vec<_>>();
-            assert!(ranges.is_empty(), "No new ranges or LSP queries should be made after returning to the selection with cached hints");
-            assert_eq!(lsp_request_count.load(Ordering::Acquire), 4);
-        });
-
-        _ = editor.update(cx, |editor, cx| {
-            editor.handle_input("++++more text++++", cx);
-        });
-        cx.executor().advance_clock(Duration::from_millis(
-            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
-        ));
-        cx.executor().run_until_parked();
-        _ = editor.update(cx, |editor, cx| {
-            let mut ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
-            ranges.sort_by_key(|r| r.start);
-
-            assert_eq!(ranges.len(), 3,
-                "On edit, should scroll to selection and query a range around it: visible + same range above and below. Instead, got query ranges {ranges:?}");
-            let above_query_range = &ranges[0];
-            let visible_query_range = &ranges[1];
-            let below_query_range = &ranges[2];
-            assert!(above_query_range.end.character < visible_query_range.start.character || above_query_range.end.line + 1 == visible_query_range.start.line,
-                "Above range {above_query_range:?} should be before visible range {visible_query_range:?}");
-            assert!(visible_query_range.end.character < below_query_range.start.character || visible_query_range.end.line  + 1 == below_query_range.start.line,
-                "Visible range {visible_query_range:?} should be before below range {below_query_range:?}");
-            assert!(above_query_range.start.line < selection_in_cached_range.row,
-                "Hints should be queried with the selected range after the query range start");
-            assert!(below_query_range.end.line > selection_in_cached_range.row,
-                "Hints should be queried with the selected range before the query range end");
-            assert!(above_query_range.start.line <= selection_in_cached_range.row - (visible_line_count * 3.0 / 2.0) as u32,
-                "Hints query range should contain one more screen before");
-            assert!(below_query_range.end.line >= selection_in_cached_range.row + (visible_line_count * 3.0 / 2.0) as u32,
-                "Hints query range should contain one more screen after");
-
-            let lsp_requests = lsp_request_count.load(Ordering::Acquire);
-            assert_eq!(lsp_requests, 7, "There should be a visible range and two ranges above and below it queried");
-            let expected_hints = vec!["5".to_string(), "6".to_string(), "7".to_string()];
-            assert_eq!(expected_hints, cached_hint_labels(editor),
-                "Should have hints from the new LSP response after the edit");
-            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-            assert_eq!(editor.inlay_hint_cache().version, lsp_requests, "Should update the cache for every LSP response with hints added");
-        });
-    }
-
-    #[gpui::test(iterations = 10)]
-    async fn test_multiple_excerpts_large_multibuffer(cx: &mut gpui::TestAppContext) {
-        init_test(cx, |settings| {
-            settings.defaults.inlay_hints = Some(InlayHintSettings {
-                enabled: true,
-                show_type_hints: true,
-                show_parameter_hints: true,
-                show_other_hints: true,
-            })
-        });
-
-        let mut language = Language::new(
-            LanguageConfig {
-                name: "Rust".into(),
-                path_suffixes: vec!["rs".to_string()],
-                ..Default::default()
-            },
-            Some(tree_sitter_rust::language()),
-        );
-        let mut fake_servers = language
-            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-                capabilities: lsp::ServerCapabilities {
-                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
-                    ..Default::default()
-                },
-                ..Default::default()
-            }))
-            .await;
-        let language = Arc::new(language);
-        let fs = FakeFs::new(cx.background_executor.clone());
-        fs.insert_tree(
-                "/a",
-                json!({
-                    "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
-                    "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
-                }),
-            )
-            .await;
-        let project = Project::test(fs, ["/a".as_ref()], cx).await;
-        project.update(cx, |project, _| {
-            project.languages().add(Arc::clone(&language))
-        });
-        let worktree_id = project.update(cx, |project, cx| {
-            project.worktrees().next().unwrap().read(cx).id()
-        });
-
-        let buffer_1 = project
-            .update(cx, |project, cx| {
-                project.open_buffer((worktree_id, "main.rs"), cx)
-            })
-            .await
-            .unwrap();
-        let buffer_2 = project
-            .update(cx, |project, cx| {
-                project.open_buffer((worktree_id, "other.rs"), cx)
-            })
-            .await
-            .unwrap();
-        let multibuffer = cx.new_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,
-                    },
-                    ExcerptRange {
-                        context: Point::new(4, 0)..Point::new(11, 0),
-                        primary: None,
-                    },
-                    ExcerptRange {
-                        context: Point::new(22, 0)..Point::new(33, 0),
-                        primary: None,
-                    },
-                    ExcerptRange {
-                        context: Point::new(44, 0)..Point::new(55, 0),
-                        primary: None,
-                    },
-                    ExcerptRange {
-                        context: Point::new(56, 0)..Point::new(66, 0),
-                        primary: None,
-                    },
-                    ExcerptRange {
-                        context: Point::new(67, 0)..Point::new(77, 0),
-                        primary: None,
-                    },
-                ],
-                cx,
-            );
-            multibuffer.push_excerpts(
-                buffer_2.clone(),
-                [
-                    ExcerptRange {
-                        context: Point::new(0, 1)..Point::new(2, 1),
-                        primary: None,
-                    },
-                    ExcerptRange {
-                        context: Point::new(4, 1)..Point::new(11, 1),
-                        primary: None,
-                    },
-                    ExcerptRange {
-                        context: Point::new(22, 1)..Point::new(33, 1),
-                        primary: None,
-                    },
-                    ExcerptRange {
-                        context: Point::new(44, 1)..Point::new(55, 1),
-                        primary: None,
-                    },
-                    ExcerptRange {
-                        context: Point::new(56, 1)..Point::new(66, 1),
-                        primary: None,
-                    },
-                    ExcerptRange {
-                        context: Point::new(67, 1)..Point::new(77, 1),
-                        primary: None,
-                    },
-                ],
-                cx,
-            );
-            multibuffer
-        });
-
-        cx.executor().run_until_parked();
-        let editor =
-            cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
-        let editor_edited = Arc::new(AtomicBool::new(false));
-        let fake_server = fake_servers.next().await.unwrap();
-        let closure_editor_edited = Arc::clone(&editor_edited);
-        fake_server
-            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
-                let task_editor_edited = Arc::clone(&closure_editor_edited);
-                async move {
-                    let hint_text = if params.text_document.uri
-                        == lsp::Url::from_file_path("/a/main.rs").unwrap()
-                    {
-                        "main hint"
-                    } else if params.text_document.uri
-                        == lsp::Url::from_file_path("/a/other.rs").unwrap()
-                    {
-                        "other hint"
-                    } else {
-                        panic!("unexpected uri: {:?}", params.text_document.uri);
-                    };
-
-                    // one hint per excerpt
-                    let positions = [
-                        lsp::Position::new(0, 2),
-                        lsp::Position::new(4, 2),
-                        lsp::Position::new(22, 2),
-                        lsp::Position::new(44, 2),
-                        lsp::Position::new(56, 2),
-                        lsp::Position::new(67, 2),
-                    ];
-                    let out_of_range_hint = lsp::InlayHint {
-                        position: lsp::Position::new(
-                            params.range.start.line + 99,
-                            params.range.start.character + 99,
-                        ),
-                        label: lsp::InlayHintLabel::String(
-                            "out of excerpt range, should be ignored".to_string(),
-                        ),
-                        kind: None,
-                        text_edits: None,
-                        tooltip: None,
-                        padding_left: None,
-                        padding_right: None,
-                        data: None,
-                    };
-
-                    let edited = task_editor_edited.load(Ordering::Acquire);
-                    Ok(Some(
-                        std::iter::once(out_of_range_hint)
-                            .chain(positions.into_iter().enumerate().map(|(i, position)| {
-                                lsp::InlayHint {
-                                    position,
-                                    label: lsp::InlayHintLabel::String(format!(
-                                        "{hint_text}{} #{i}",
-                                        if edited { "(edited)" } else { "" },
-                                    )),
-                                    kind: None,
-                                    text_edits: None,
-                                    tooltip: None,
-                                    padding_left: None,
-                                    padding_right: None,
-                                    data: None,
-                                }
-                            }))
-                            .collect(),
-                    ))
-                }
-            })
-            .next()
-            .await;
-        cx.executor().run_until_parked();
-
-        _ = editor.update(cx, |editor, cx| {
-                let expected_hints = vec![
-                    "main hint #0".to_string(),
-                    "main hint #1".to_string(),
-                    "main hint #2".to_string(),
-                    "main hint #3".to_string(),
-                    "main hint #4".to_string(),
-                    "main hint #5".to_string(),
-                ];
-                assert_eq!(
-                    expected_hints,
-                    cached_hint_labels(editor),
-                    "When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
-                );
-                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-                assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(), "Every visible excerpt hints should bump the verison");
-            });
-
-        _ = editor.update(cx, |editor, cx| {
-            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
-                s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
-            });
-            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
-                s.select_ranges([Point::new(22, 0)..Point::new(22, 0)])
-            });
-            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
-                s.select_ranges([Point::new(50, 0)..Point::new(50, 0)])
-            });
-        });
-        cx.executor().run_until_parked();
-        _ = editor.update(cx, |editor, cx| {
-                let expected_hints = vec![
-                    "main hint #0".to_string(),
-                    "main hint #1".to_string(),
-                    "main hint #2".to_string(),
-                    "main hint #3".to_string(),
-                    "main hint #4".to_string(),
-                    "main hint #5".to_string(),
-                    "other hint #0".to_string(),
-                    "other hint #1".to_string(),
-                    "other hint #2".to_string(),
-                ];
-                assert_eq!(expected_hints, cached_hint_labels(editor),
-                    "With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits");
-                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-                assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(),
-                    "Due to every excerpt having one hint, we update cache per new excerpt scrolled");
-            });
-
-        _ = editor.update(cx, |editor, cx| {
-            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
-                s.select_ranges([Point::new(100, 0)..Point::new(100, 0)])
-            });
-        });
-        cx.executor().advance_clock(Duration::from_millis(
-            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
-        ));
-        cx.executor().run_until_parked();
-        let last_scroll_update_version = editor.update(cx, |editor, cx| {
-                let expected_hints = vec![
-                    "main hint #0".to_string(),
-                    "main hint #1".to_string(),
-                    "main hint #2".to_string(),
-                    "main hint #3".to_string(),
-                    "main hint #4".to_string(),
-                    "main hint #5".to_string(),
-                    "other hint #0".to_string(),
-                    "other hint #1".to_string(),
-                    "other hint #2".to_string(),
-                    "other hint #3".to_string(),
-                    "other hint #4".to_string(),
-                    "other hint #5".to_string(),
-                ];
-                assert_eq!(expected_hints, cached_hint_labels(editor),
-                    "After multibuffer was scrolled to the end, all hints for all excerpts should be fetched");
-                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-                assert_eq!(editor.inlay_hint_cache().version, expected_hints.len());
-                expected_hints.len()
-            }).unwrap();
-
-        _ = editor.update(cx, |editor, cx| {
-            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
-                s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
-            });
-        });
-        cx.executor().run_until_parked();
-        _ = editor.update(cx, |editor, cx| {
-                let expected_hints = vec![
-                    "main hint #0".to_string(),
-                    "main hint #1".to_string(),
-                    "main hint #2".to_string(),
-                    "main hint #3".to_string(),
-                    "main hint #4".to_string(),
-                    "main hint #5".to_string(),
-                    "other hint #0".to_string(),
-                    "other hint #1".to_string(),
-                    "other hint #2".to_string(),
-                    "other hint #3".to_string(),
-                    "other hint #4".to_string(),
-                    "other hint #5".to_string(),
-                ];
-                assert_eq!(expected_hints, cached_hint_labels(editor),
-                    "After multibuffer was scrolled to the end, further scrolls up should not bring more hints");
-                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-                assert_eq!(editor.inlay_hint_cache().version, last_scroll_update_version, "No updates should happen during scrolling already scolled buffer");
-            });
-
-        editor_edited.store(true, Ordering::Release);
-        _ = editor.update(cx, |editor, cx| {
-            editor.change_selections(None, cx, |s| {
-                // TODO if this gets set to hint boundary (e.g. 56) we sometimes get an extra cache version bump, why?
-                s.select_ranges([Point::new(57, 0)..Point::new(57, 0)])
-            });
-            editor.handle_input("++++more text++++", cx);
-        });
-        cx.executor().run_until_parked();
-        _ = editor.update(cx, |editor, cx| {
-            let expected_hints = vec![
-                "main hint(edited) #0".to_string(),
-                "main hint(edited) #1".to_string(),
-                "main hint(edited) #2".to_string(),
-                "main hint(edited) #3".to_string(),
-                "main hint(edited) #4".to_string(),
-                "main hint(edited) #5".to_string(),
-                "other hint(edited) #0".to_string(),
-                "other hint(edited) #1".to_string(),
-            ];
-            assert_eq!(
-                expected_hints,
-                cached_hint_labels(editor),
-                "After multibuffer edit, editor gets scolled back to the last selection; \
-    all hints should be invalidated and requeried for all of its visible excerpts"
-            );
-            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-
-            let current_cache_version = editor.inlay_hint_cache().version;
-            assert_eq!(
-                current_cache_version,
-                last_scroll_update_version + expected_hints.len(),
-                "We should have updated cache N times == N of new hints arrived (separately from each excerpt)"
-            );
-        });
-    }
-
-    #[gpui::test]
-    async fn test_excerpts_removed(cx: &mut gpui::TestAppContext) {
-        init_test(cx, |settings| {
-            settings.defaults.inlay_hints = Some(InlayHintSettings {
-                enabled: true,
-                show_type_hints: false,
-                show_parameter_hints: false,
-                show_other_hints: false,
-            })
-        });
-
-        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 {
-                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
-                    ..Default::default()
-                },
-                ..Default::default()
-            }))
-            .await;
-        let language = Arc::new(language);
-        let fs = FakeFs::new(cx.background_executor.clone());
-        fs.insert_tree(
-            "/a",
-            json!({
-                "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
-                "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
-            }),
-        )
-        .await;
-        let project = Project::test(fs, ["/a".as_ref()], cx).await;
-        project.update(cx, |project, _| {
-            project.languages().add(Arc::clone(&language))
-        });
-        let worktree_id = project.update(cx, |project, cx| {
-            project.worktrees().next().unwrap().read(cx).id()
-        });
-
-        let buffer_1 = project
-            .update(cx, |project, cx| {
-                project.open_buffer((worktree_id, "main.rs"), cx)
-            })
-            .await
-            .unwrap();
-        let buffer_2 = project
-            .update(cx, |project, cx| {
-                project.open_buffer((worktree_id, "other.rs"), cx)
-            })
-            .await
-            .unwrap();
-        let multibuffer = cx.new_model(|_| MultiBuffer::new(0));
-        let (buffer_1_excerpts, buffer_2_excerpts) = multibuffer.update(cx, |multibuffer, cx| {
-            let buffer_1_excerpts = multibuffer.push_excerpts(
-                buffer_1.clone(),
-                [ExcerptRange {
-                    context: Point::new(0, 0)..Point::new(2, 0),
-                    primary: None,
-                }],
-                cx,
-            );
-            let buffer_2_excerpts = multibuffer.push_excerpts(
-                buffer_2.clone(),
-                [ExcerptRange {
-                    context: Point::new(0, 1)..Point::new(2, 1),
-                    primary: None,
-                }],
-                cx,
-            );
-            (buffer_1_excerpts, buffer_2_excerpts)
-        });
-
-        assert!(!buffer_1_excerpts.is_empty());
-        assert!(!buffer_2_excerpts.is_empty());
-
-        cx.executor().run_until_parked();
-        let editor =
-            cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
-        let editor_edited = Arc::new(AtomicBool::new(false));
-        let fake_server = fake_servers.next().await.unwrap();
-        let closure_editor_edited = Arc::clone(&editor_edited);
-        fake_server
-            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
-                let task_editor_edited = Arc::clone(&closure_editor_edited);
-                async move {
-                    let hint_text = if params.text_document.uri
-                        == lsp::Url::from_file_path("/a/main.rs").unwrap()
-                    {
-                        "main hint"
-                    } else if params.text_document.uri
-                        == lsp::Url::from_file_path("/a/other.rs").unwrap()
-                    {
-                        "other hint"
-                    } else {
-                        panic!("unexpected uri: {:?}", params.text_document.uri);
-                    };
-
-                    let positions = [
-                        lsp::Position::new(0, 2),
-                        lsp::Position::new(4, 2),
-                        lsp::Position::new(22, 2),
-                        lsp::Position::new(44, 2),
-                        lsp::Position::new(56, 2),
-                        lsp::Position::new(67, 2),
-                    ];
-                    let out_of_range_hint = lsp::InlayHint {
-                        position: lsp::Position::new(
-                            params.range.start.line + 99,
-                            params.range.start.character + 99,
-                        ),
-                        label: lsp::InlayHintLabel::String(
-                            "out of excerpt range, should be ignored".to_string(),
-                        ),
-                        kind: None,
-                        text_edits: None,
-                        tooltip: None,
-                        padding_left: None,
-                        padding_right: None,
-                        data: None,
-                    };
-
-                    let edited = task_editor_edited.load(Ordering::Acquire);
-                    Ok(Some(
-                        std::iter::once(out_of_range_hint)
-                            .chain(positions.into_iter().enumerate().map(|(i, position)| {
-                                lsp::InlayHint {
-                                    position,
-                                    label: lsp::InlayHintLabel::String(format!(
-                                        "{hint_text}{} #{i}",
-                                        if edited { "(edited)" } else { "" },
-                                    )),
-                                    kind: None,
-                                    text_edits: None,
-                                    tooltip: None,
-                                    padding_left: None,
-                                    padding_right: None,
-                                    data: None,
-                                }
-                            }))
-                            .collect(),
-                    ))
-                }
-            })
-            .next()
-            .await;
-        cx.executor().run_until_parked();
-
-        _ = editor.update(cx, |editor, cx| {
-            assert_eq!(
-                vec!["main hint #0".to_string(), "other hint #0".to_string()],
-                cached_hint_labels(editor),
-                "Cache should update for both excerpts despite hints display was disabled"
-            );
-            assert!(
-                visible_hint_labels(editor, cx).is_empty(),
-                "All hints are disabled and should not be shown despite being present in the cache"
-            );
-            assert_eq!(
-                editor.inlay_hint_cache().version,
-                2,
-                "Cache should update once per excerpt query"
-            );
-        });
-
-        _ = editor.update(cx, |editor, cx| {
-            editor.buffer().update(cx, |multibuffer, cx| {
-                multibuffer.remove_excerpts(buffer_2_excerpts, cx)
-            })
-        });
-        cx.executor().run_until_parked();
-        _ = editor.update(cx, |editor, cx| {
-            assert_eq!(
-                vec!["main hint #0".to_string()],
-                cached_hint_labels(editor),
-                "For the removed excerpt, should clean corresponding cached hints"
-            );
-            assert!(
-                visible_hint_labels(editor, cx).is_empty(),
-                "All hints are disabled and should not be shown despite being present in the cache"
-            );
-            assert_eq!(
-                editor.inlay_hint_cache().version,
-                3,
-                "Excerpt removal should trigger a cache update"
-            );
-        });
-
-        update_test_language_settings(cx, |settings| {
-            settings.defaults.inlay_hints = Some(InlayHintSettings {
-                enabled: true,
-                show_type_hints: true,
-                show_parameter_hints: true,
-                show_other_hints: true,
-            })
-        });
-        cx.executor().run_until_parked();
-        _ = editor.update(cx, |editor, cx| {
-            let expected_hints = vec!["main hint #0".to_string()];
-            assert_eq!(
-                expected_hints,
-                cached_hint_labels(editor),
-                "Hint display settings change should not change the cache"
-            );
-            assert_eq!(
-                expected_hints,
-                visible_hint_labels(editor, cx),
-                "Settings change should make cached hints visible"
-            );
-            assert_eq!(
-                editor.inlay_hint_cache().version,
-                4,
-                "Settings change should trigger a cache update"
-            );
-        });
-    }
-
-    #[gpui::test]
-    async fn test_inside_char_boundary_range_hints(cx: &mut gpui::TestAppContext) {
-        init_test(cx, |settings| {
-            settings.defaults.inlay_hints = Some(InlayHintSettings {
-                enabled: true,
-                show_type_hints: true,
-                show_parameter_hints: true,
-                show_other_hints: true,
-            })
-        });
-
-        let mut language = Language::new(
-            LanguageConfig {
-                name: "Rust".into(),
-                path_suffixes: vec!["rs".to_string()],
-                ..Default::default()
-            },
-            Some(tree_sitter_rust::language()),
-        );
-        let mut fake_servers = language
-            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-                capabilities: lsp::ServerCapabilities {
-                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
-                    ..Default::default()
-                },
-                ..Default::default()
-            }))
-            .await;
-        let fs = FakeFs::new(cx.background_executor.clone());
-        fs.insert_tree(
-            "/a",
-            json!({
-                "main.rs": format!(r#"fn main() {{\n{}\n}}"#, format!("let i = {};\n", "√".repeat(10)).repeat(500)),
-                "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 buffer = project
-            .update(cx, |project, cx| {
-                project.open_local_buffer("/a/main.rs", cx)
-            })
-            .await
-            .unwrap();
-        cx.executor().run_until_parked();
-        cx.executor().start_waiting();
-        let fake_server = fake_servers.next().await.unwrap();
-        let editor = cx.add_window(|cx| Editor::for_buffer(buffer, Some(project), cx));
-        let lsp_request_count = Arc::new(AtomicU32::new(0));
-        let closure_lsp_request_count = Arc::clone(&lsp_request_count);
-        fake_server
-            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
-                let task_lsp_request_count = Arc::clone(&closure_lsp_request_count);
-                async move {
-                    assert_eq!(
-                        params.text_document.uri,
-                        lsp::Url::from_file_path("/a/main.rs").unwrap(),
-                    );
-                    let query_start = params.range.start;
-                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::Release) + 1;
-                    Ok(Some(vec![lsp::InlayHint {
-                        position: query_start,
-                        label: lsp::InlayHintLabel::String(i.to_string()),
-                        kind: None,
-                        text_edits: None,
-                        tooltip: None,
-                        padding_left: None,
-                        padding_right: None,
-                        data: None,
-                    }]))
-                }
-            })
-            .next()
-            .await;
-
-        cx.executor().run_until_parked();
-        _ = editor.update(cx, |editor, cx| {
-            editor.change_selections(None, cx, |s| {
-                s.select_ranges([Point::new(10, 0)..Point::new(10, 0)])
-            })
-        });
-        cx.executor().run_until_parked();
-        _ = editor.update(cx, |editor, cx| {
-            let expected_hints = vec!["1".to_string()];
-            assert_eq!(expected_hints, cached_hint_labels(editor));
-            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-            assert_eq!(editor.inlay_hint_cache().version, 1);
-        });
-    }
-
-    #[gpui::test]
-    async fn test_toggle_inlay_hints(cx: &mut gpui::TestAppContext) {
-        init_test(cx, |settings| {
-            settings.defaults.inlay_hints = Some(InlayHintSettings {
-                enabled: false,
-                show_type_hints: true,
-                show_parameter_hints: true,
-                show_other_hints: true,
-            })
-        });
-
-        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
-
-        _ = editor.update(cx, |editor, cx| {
-            editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx)
-        });
-        cx.executor().start_waiting();
-        let lsp_request_count = Arc::new(AtomicU32::new(0));
-        let closure_lsp_request_count = Arc::clone(&lsp_request_count);
-        fake_server
-            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
-                let task_lsp_request_count = Arc::clone(&closure_lsp_request_count);
-                async move {
-                    assert_eq!(
-                        params.text_document.uri,
-                        lsp::Url::from_file_path(file_with_hints).unwrap(),
-                    );
-
-                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1;
-                    Ok(Some(vec![lsp::InlayHint {
-                        position: lsp::Position::new(0, i),
-                        label: lsp::InlayHintLabel::String(i.to_string()),
-                        kind: None,
-                        text_edits: None,
-                        tooltip: None,
-                        padding_left: None,
-                        padding_right: None,
-                        data: None,
-                    }]))
-                }
-            })
-            .next()
-            .await;
-        cx.executor().run_until_parked();
-        _ = editor.update(cx, |editor, cx| {
-            let expected_hints = vec!["1".to_string()];
-            assert_eq!(
-                expected_hints,
-                cached_hint_labels(editor),
-                "Should display inlays after toggle despite them disabled in settings"
-            );
-            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-            assert_eq!(
-                editor.inlay_hint_cache().version,
-                1,
-                "First toggle should be cache's first update"
-            );
-        });
-
-        _ = editor.update(cx, |editor, cx| {
-            editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx)
-        });
-        cx.executor().run_until_parked();
-        _ = editor.update(cx, |editor, cx| {
-            assert!(
-                cached_hint_labels(editor).is_empty(),
-                "Should clear hints after 2nd toggle"
-            );
-            assert!(visible_hint_labels(editor, cx).is_empty());
-            assert_eq!(editor.inlay_hint_cache().version, 2);
-        });
-
-        update_test_language_settings(cx, |settings| {
-            settings.defaults.inlay_hints = Some(InlayHintSettings {
-                enabled: true,
-                show_type_hints: true,
-                show_parameter_hints: true,
-                show_other_hints: true,
-            })
-        });
-        cx.executor().run_until_parked();
-        _ = editor.update(cx, |editor, cx| {
-            let expected_hints = vec!["2".to_string()];
-            assert_eq!(
-                expected_hints,
-                cached_hint_labels(editor),
-                "Should query LSP hints for the 2nd time after enabling hints in settings"
-            );
-            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-            assert_eq!(editor.inlay_hint_cache().version, 3);
-        });
-
-        _ = editor.update(cx, |editor, cx| {
-            editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx)
-        });
-        cx.executor().run_until_parked();
-        _ = editor.update(cx, |editor, cx| {
-            assert!(
-                cached_hint_labels(editor).is_empty(),
-                "Should clear hints after enabling in settings and a 3rd toggle"
-            );
-            assert!(visible_hint_labels(editor, cx).is_empty());
-            assert_eq!(editor.inlay_hint_cache().version, 4);
-        });
-
-        _ = editor.update(cx, |editor, cx| {
-            editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx)
-        });
-        cx.executor().run_until_parked();
-        _ = editor.update(cx, |editor, cx| {
-            let expected_hints = vec!["3".to_string()];
-            assert_eq!(
-                expected_hints,
-                cached_hint_labels(editor),
-                "Should query LSP hints for the 3rd time after enabling hints in settings and toggling them back on"
-            );
-            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-            assert_eq!(editor.inlay_hint_cache().version, 5);
-        });
-    }
-
-    pub(crate) fn init_test(cx: &mut TestAppContext, f: impl Fn(&mut AllLanguageSettingsContent)) {
-        cx.update(|cx| {
-            let settings_store = SettingsStore::test(cx);
-            cx.set_global(settings_store);
-            theme::init(theme::LoadThemes::JustBase, 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);
-    }
-
-    async fn prepare_test_objects(
-        cx: &mut TestAppContext,
-    ) -> (&'static str, WindowHandle<Editor>, FakeLanguageServer) {
-        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 {
-                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
-                    ..Default::default()
-                },
-                ..Default::default()
-            }))
-            .await;
-
-        let fs = FakeFs::new(cx.background_executor.clone());
-        fs.insert_tree(
-            "/a",
-            json!({
-                "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
-                "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 buffer = project
-            .update(cx, |project, cx| {
-                project.open_local_buffer("/a/main.rs", cx)
-            })
-            .await
-            .unwrap();
-        cx.executor().run_until_parked();
-        cx.executor().start_waiting();
-        let fake_server = fake_servers.next().await.unwrap();
-        let editor = cx.add_window(|cx| Editor::for_buffer(buffer, Some(project), cx));
-
-        _ = editor.update(cx, |editor, cx| {
-            assert!(cached_hint_labels(editor).is_empty());
-            assert!(visible_hint_labels(editor, cx).is_empty());
-            assert_eq!(editor.inlay_hint_cache().version, 0);
-        });
-
-        ("/a/main.rs", editor, fake_server)
-    }
-
-    pub fn cached_hint_labels(editor: &Editor) -> Vec<String> {
-        let mut labels = Vec::new();
-        for (_, excerpt_hints) in &editor.inlay_hint_cache().hints {
-            let excerpt_hints = excerpt_hints.read();
-            for id in &excerpt_hints.ordered_hints {
-                labels.push(excerpt_hints.hints_by_id[id].text());
-            }
-        }
-
-        labels.sort();
-        labels
-    }
-
-    pub fn visible_hint_labels(editor: &Editor, cx: &ViewContext<'_, Editor>) -> Vec<String> {
-        let mut hints = editor
-            .visible_inlay_hints(cx)
-            .into_iter()
-            .map(|hint| hint.text.to_string())
-            .collect::<Vec<_>>();
-        hints.sort();
-        hints
-    }
-}

crates/editor2/src/items.rs 🔗

@@ -1,1339 +0,0 @@
-use crate::{
-    editor_settings::SeedQuerySetting, link_go_to_definition::hide_link_definition,
-    persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor, EditorEvent, EditorSettings,
-    ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _,
-};
-use anyhow::{anyhow, Context as _, Result};
-use collections::HashSet;
-use futures::future::try_join_all;
-use gpui::{
-    div, point, AnyElement, AppContext, AsyncWindowContext, Context, Entity, EntityId,
-    EventEmitter, IntoElement, Model, ParentElement, Pixels, Render, SharedString, Styled,
-    Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
-};
-use language::{
-    proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, OffsetRangeExt,
-    Point, SelectionGoal,
-};
-use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPath};
-use rpc::proto::{self, update_view, PeerId};
-use settings::Settings;
-
-use std::fmt::Write;
-use std::{
-    borrow::Cow,
-    cmp::{self, Ordering},
-    iter,
-    ops::Range,
-    path::{Path, PathBuf},
-    sync::Arc,
-};
-use text::Selection;
-use theme::{ActiveTheme, Theme};
-use ui::{h_stack, prelude::*, Label};
-use util::{paths::PathExt, paths::FILE_ROW_COLUMN_DELIMITER, ResultExt, TryFutureExt};
-use workspace::{
-    item::{BreadcrumbText, FollowEvent, FollowableItemHandle},
-    StatusItemView,
-};
-use workspace::{
-    item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem},
-    searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
-    ItemId, ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
-};
-
-pub const MAX_TAB_TITLE_LEN: usize = 24;
-
-impl FollowableItem for Editor {
-    fn remote_id(&self) -> Option<ViewId> {
-        self.remote_id
-    }
-
-    fn from_state_proto(
-        pane: View<workspace::Pane>,
-        workspace: View<Workspace>,
-        remote_id: ViewId,
-        state: &mut Option<proto::view::Variant>,
-        cx: &mut WindowContext,
-    ) -> Option<Task<Result<View<Self>>>> {
-        let project = workspace.read(cx).project().to_owned();
-        let Some(proto::view::Variant::Editor(_)) = state else {
-            return None;
-        };
-        let Some(proto::view::Variant::Editor(state)) = state.take() else {
-            unreachable!()
-        };
-
-        let client = project.read(cx).client();
-        let replica_id = project.read(cx).replica_id();
-        let buffer_ids = state
-            .excerpts
-            .iter()
-            .map(|excerpt| excerpt.buffer_id)
-            .collect::<HashSet<_>>();
-        let buffers = project.update(cx, |project, cx| {
-            buffer_ids
-                .iter()
-                .map(|id| project.open_buffer_by_id(*id, cx))
-                .collect::<Vec<_>>()
-        });
-
-        let pane = pane.downgrade();
-        Some(cx.spawn(|mut cx| async move {
-            let mut buffers = futures::future::try_join_all(buffers).await?;
-            let editor = pane.update(&mut cx, |pane, cx| {
-                let mut editors = pane.items_of_type::<Self>();
-                editors.find(|editor| {
-                    let ids_match = editor.remote_id(&client, cx) == Some(remote_id);
-                    let singleton_buffer_matches = state.singleton
-                        && buffers.first()
-                            == editor.read(cx).buffer.read(cx).as_singleton().as_ref();
-                    ids_match || singleton_buffer_matches
-                })
-            })?;
-
-            let editor = if let Some(editor) = editor {
-                editor
-            } else {
-                pane.update(&mut cx, |_, cx| {
-                    let multibuffer = cx.new_model(|cx| {
-                        let mut multibuffer;
-                        if state.singleton && buffers.len() == 1 {
-                            multibuffer = MultiBuffer::singleton(buffers.pop().unwrap(), cx)
-                        } else {
-                            multibuffer = MultiBuffer::new(replica_id);
-                            let mut excerpts = state.excerpts.into_iter().peekable();
-                            while let Some(excerpt) = excerpts.peek() {
-                                let buffer_id = excerpt.buffer_id;
-                                let buffer_excerpts = iter::from_fn(|| {
-                                    let excerpt = excerpts.peek()?;
-                                    (excerpt.buffer_id == buffer_id)
-                                        .then(|| excerpts.next().unwrap())
-                                });
-                                let buffer =
-                                    buffers.iter().find(|b| b.read(cx).remote_id() == buffer_id);
-                                if let Some(buffer) = buffer {
-                                    multibuffer.push_excerpts(
-                                        buffer.clone(),
-                                        buffer_excerpts.filter_map(deserialize_excerpt_range),
-                                        cx,
-                                    );
-                                }
-                            }
-                        };
-
-                        if let Some(title) = &state.title {
-                            multibuffer = multibuffer.with_title(title.clone())
-                        }
-
-                        multibuffer
-                    });
-
-                    cx.new_view(|cx| {
-                        let mut editor =
-                            Editor::for_multibuffer(multibuffer, Some(project.clone()), cx);
-                        editor.remote_id = Some(remote_id);
-                        editor
-                    })
-                })?
-            };
-
-            update_editor_from_message(
-                editor.downgrade(),
-                project,
-                proto::update_view::Editor {
-                    selections: state.selections,
-                    pending_selection: state.pending_selection,
-                    scroll_top_anchor: state.scroll_top_anchor,
-                    scroll_x: state.scroll_x,
-                    scroll_y: state.scroll_y,
-                    ..Default::default()
-                },
-                &mut cx,
-            )
-            .await?;
-
-            Ok(editor)
-        }))
-    }
-
-    fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>) {
-        self.leader_peer_id = leader_peer_id;
-        if self.leader_peer_id.is_some() {
-            self.buffer.update(cx, |buffer, cx| {
-                buffer.remove_active_selections(cx);
-            });
-        } else if self.focus_handle.is_focused(cx) {
-            self.buffer.update(cx, |buffer, cx| {
-                buffer.set_active_selections(
-                    &self.selections.disjoint_anchors(),
-                    self.selections.line_mode,
-                    self.cursor_shape,
-                    cx,
-                );
-            });
-        }
-        cx.notify();
-    }
-
-    fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
-        let buffer = self.buffer.read(cx);
-        let scroll_anchor = self.scroll_manager.anchor();
-        let excerpts = buffer
-            .read(cx)
-            .excerpts()
-            .map(|(id, buffer, range)| proto::Excerpt {
-                id: id.to_proto(),
-                buffer_id: buffer.remote_id(),
-                context_start: Some(serialize_text_anchor(&range.context.start)),
-                context_end: Some(serialize_text_anchor(&range.context.end)),
-                primary_start: range
-                    .primary
-                    .as_ref()
-                    .map(|range| serialize_text_anchor(&range.start)),
-                primary_end: range
-                    .primary
-                    .as_ref()
-                    .map(|range| serialize_text_anchor(&range.end)),
-            })
-            .collect();
-
-        Some(proto::view::Variant::Editor(proto::view::Editor {
-            singleton: buffer.is_singleton(),
-            title: (!buffer.is_singleton()).then(|| buffer.title(cx).into()),
-            excerpts,
-            scroll_top_anchor: Some(serialize_anchor(&scroll_anchor.anchor)),
-            scroll_x: scroll_anchor.offset.x,
-            scroll_y: scroll_anchor.offset.y,
-            selections: self
-                .selections
-                .disjoint_anchors()
-                .iter()
-                .map(serialize_selection)
-                .collect(),
-            pending_selection: self
-                .selections
-                .pending_anchor()
-                .as_ref()
-                .map(serialize_selection),
-        }))
-    }
-
-    fn to_follow_event(event: &EditorEvent) -> Option<workspace::item::FollowEvent> {
-        match event {
-            EditorEvent::Edited => Some(FollowEvent::Unfollow),
-            EditorEvent::SelectionsChanged { local }
-            | EditorEvent::ScrollPositionChanged { local, .. } => {
-                if *local {
-                    Some(FollowEvent::Unfollow)
-                } else {
-                    None
-                }
-            }
-            _ => None,
-        }
-    }
-
-    fn add_event_to_update_proto(
-        &self,
-        event: &EditorEvent,
-        update: &mut Option<proto::update_view::Variant>,
-        cx: &WindowContext,
-    ) -> bool {
-        let update =
-            update.get_or_insert_with(|| proto::update_view::Variant::Editor(Default::default()));
-
-        match update {
-            proto::update_view::Variant::Editor(update) => match event {
-                EditorEvent::ExcerptsAdded {
-                    buffer,
-                    predecessor,
-                    excerpts,
-                } => {
-                    let buffer_id = buffer.read(cx).remote_id();
-                    let mut excerpts = excerpts.iter();
-                    if let Some((id, range)) = excerpts.next() {
-                        update.inserted_excerpts.push(proto::ExcerptInsertion {
-                            previous_excerpt_id: Some(predecessor.to_proto()),
-                            excerpt: serialize_excerpt(buffer_id, id, range),
-                        });
-                        update.inserted_excerpts.extend(excerpts.map(|(id, range)| {
-                            proto::ExcerptInsertion {
-                                previous_excerpt_id: None,
-                                excerpt: serialize_excerpt(buffer_id, id, range),
-                            }
-                        }))
-                    }
-                    true
-                }
-                EditorEvent::ExcerptsRemoved { ids } => {
-                    update
-                        .deleted_excerpts
-                        .extend(ids.iter().map(ExcerptId::to_proto));
-                    true
-                }
-                EditorEvent::ScrollPositionChanged { .. } => {
-                    let scroll_anchor = self.scroll_manager.anchor();
-                    update.scroll_top_anchor = Some(serialize_anchor(&scroll_anchor.anchor));
-                    update.scroll_x = scroll_anchor.offset.x;
-                    update.scroll_y = scroll_anchor.offset.y;
-                    true
-                }
-                EditorEvent::SelectionsChanged { .. } => {
-                    update.selections = self
-                        .selections
-                        .disjoint_anchors()
-                        .iter()
-                        .map(serialize_selection)
-                        .collect();
-                    update.pending_selection = self
-                        .selections
-                        .pending_anchor()
-                        .as_ref()
-                        .map(serialize_selection);
-                    true
-                }
-                _ => false,
-            },
-        }
-    }
-
-    fn apply_update_proto(
-        &mut self,
-        project: &Model<Project>,
-        message: update_view::Variant,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<Result<()>> {
-        let update_view::Variant::Editor(message) = message;
-        let project = project.clone();
-        cx.spawn(|this, mut cx| async move {
-            update_editor_from_message(this, project, message, &mut cx).await
-        })
-    }
-
-    fn is_project_item(&self, _cx: &WindowContext) -> bool {
-        true
-    }
-}
-
-async fn update_editor_from_message(
-    this: WeakView<Editor>,
-    project: Model<Project>,
-    message: proto::update_view::Editor,
-    cx: &mut AsyncWindowContext,
-) -> Result<()> {
-    // Open all of the buffers of which excerpts were added to the editor.
-    let inserted_excerpt_buffer_ids = message
-        .inserted_excerpts
-        .iter()
-        .filter_map(|insertion| Some(insertion.excerpt.as_ref()?.buffer_id))
-        .collect::<HashSet<_>>();
-    let inserted_excerpt_buffers = project.update(cx, |project, cx| {
-        inserted_excerpt_buffer_ids
-            .into_iter()
-            .map(|id| project.open_buffer_by_id(id, cx))
-            .collect::<Vec<_>>()
-    })?;
-    let _inserted_excerpt_buffers = try_join_all(inserted_excerpt_buffers).await?;
-
-    // Update the editor's excerpts.
-    this.update(cx, |editor, cx| {
-        editor.buffer.update(cx, |multibuffer, cx| {
-            let mut removed_excerpt_ids = message
-                .deleted_excerpts
-                .into_iter()
-                .map(ExcerptId::from_proto)
-                .collect::<Vec<_>>();
-            removed_excerpt_ids.sort_by({
-                let multibuffer = multibuffer.read(cx);
-                move |a, b| a.cmp(&b, &multibuffer)
-            });
-
-            let mut insertions = message.inserted_excerpts.into_iter().peekable();
-            while let Some(insertion) = insertions.next() {
-                let Some(excerpt) = insertion.excerpt else {
-                    continue;
-                };
-                let Some(previous_excerpt_id) = insertion.previous_excerpt_id else {
-                    continue;
-                };
-                let buffer_id = excerpt.buffer_id;
-                let Some(buffer) = project.read(cx).buffer_for_id(buffer_id) else {
-                    continue;
-                };
-
-                let adjacent_excerpts = iter::from_fn(|| {
-                    let insertion = insertions.peek()?;
-                    if insertion.previous_excerpt_id.is_none()
-                        && insertion.excerpt.as_ref()?.buffer_id == buffer_id
-                    {
-                        insertions.next()?.excerpt
-                    } else {
-                        None
-                    }
-                });
-
-                multibuffer.insert_excerpts_with_ids_after(
-                    ExcerptId::from_proto(previous_excerpt_id),
-                    buffer,
-                    [excerpt]
-                        .into_iter()
-                        .chain(adjacent_excerpts)
-                        .filter_map(|excerpt| {
-                            Some((
-                                ExcerptId::from_proto(excerpt.id),
-                                deserialize_excerpt_range(excerpt)?,
-                            ))
-                        }),
-                    cx,
-                );
-            }
-
-            multibuffer.remove_excerpts(removed_excerpt_ids, cx);
-        });
-    })?;
-
-    // Deserialize the editor state.
-    let (selections, pending_selection, scroll_top_anchor) = this.update(cx, |editor, cx| {
-        let buffer = editor.buffer.read(cx).read(cx);
-        let selections = message
-            .selections
-            .into_iter()
-            .filter_map(|selection| deserialize_selection(&buffer, selection))
-            .collect::<Vec<_>>();
-        let pending_selection = message
-            .pending_selection
-            .and_then(|selection| deserialize_selection(&buffer, selection));
-        let scroll_top_anchor = message
-            .scroll_top_anchor
-            .and_then(|anchor| deserialize_anchor(&buffer, anchor));
-        anyhow::Ok((selections, pending_selection, scroll_top_anchor))
-    })??;
-
-    // Wait until the buffer has received all of the operations referenced by
-    // the editor's new state.
-    this.update(cx, |editor, cx| {
-        editor.buffer.update(cx, |buffer, cx| {
-            buffer.wait_for_anchors(
-                selections
-                    .iter()
-                    .chain(pending_selection.as_ref())
-                    .flat_map(|selection| [selection.start, selection.end])
-                    .chain(scroll_top_anchor),
-                cx,
-            )
-        })
-    })?
-    .await?;
-
-    // Update the editor's state.
-    this.update(cx, |editor, cx| {
-        if !selections.is_empty() || pending_selection.is_some() {
-            editor.set_selections_from_remote(selections, pending_selection, cx);
-            editor.request_autoscroll_remotely(Autoscroll::newest(), cx);
-        } else if let Some(scroll_top_anchor) = scroll_top_anchor {
-            editor.set_scroll_anchor_remote(
-                ScrollAnchor {
-                    anchor: scroll_top_anchor,
-                    offset: point(message.scroll_x, message.scroll_y),
-                },
-                cx,
-            );
-        }
-    })?;
-    Ok(())
-}
-
-fn serialize_excerpt(
-    buffer_id: u64,
-    id: &ExcerptId,
-    range: &ExcerptRange<language::Anchor>,
-) -> Option<proto::Excerpt> {
-    Some(proto::Excerpt {
-        id: id.to_proto(),
-        buffer_id,
-        context_start: Some(serialize_text_anchor(&range.context.start)),
-        context_end: Some(serialize_text_anchor(&range.context.end)),
-        primary_start: range
-            .primary
-            .as_ref()
-            .map(|r| serialize_text_anchor(&r.start)),
-        primary_end: range
-            .primary
-            .as_ref()
-            .map(|r| serialize_text_anchor(&r.end)),
-    })
-}
-
-fn serialize_selection(selection: &Selection<Anchor>) -> proto::Selection {
-    proto::Selection {
-        id: selection.id as u64,
-        start: Some(serialize_anchor(&selection.start)),
-        end: Some(serialize_anchor(&selection.end)),
-        reversed: selection.reversed,
-    }
-}
-
-fn serialize_anchor(anchor: &Anchor) -> proto::EditorAnchor {
-    proto::EditorAnchor {
-        excerpt_id: anchor.excerpt_id.to_proto(),
-        anchor: Some(serialize_text_anchor(&anchor.text_anchor)),
-    }
-}
-
-fn deserialize_excerpt_range(excerpt: proto::Excerpt) -> Option<ExcerptRange<language::Anchor>> {
-    let context = {
-        let start = language::proto::deserialize_anchor(excerpt.context_start?)?;
-        let end = language::proto::deserialize_anchor(excerpt.context_end?)?;
-        start..end
-    };
-    let primary = excerpt
-        .primary_start
-        .zip(excerpt.primary_end)
-        .and_then(|(start, end)| {
-            let start = language::proto::deserialize_anchor(start)?;
-            let end = language::proto::deserialize_anchor(end)?;
-            Some(start..end)
-        });
-    Some(ExcerptRange { context, primary })
-}
-
-fn deserialize_selection(
-    buffer: &MultiBufferSnapshot,
-    selection: proto::Selection,
-) -> Option<Selection<Anchor>> {
-    Some(Selection {
-        id: selection.id as usize,
-        start: deserialize_anchor(buffer, selection.start?)?,
-        end: deserialize_anchor(buffer, selection.end?)?,
-        reversed: selection.reversed,
-        goal: SelectionGoal::None,
-    })
-}
-
-fn deserialize_anchor(buffer: &MultiBufferSnapshot, anchor: proto::EditorAnchor) -> Option<Anchor> {
-    let excerpt_id = ExcerptId::from_proto(anchor.excerpt_id);
-    Some(Anchor {
-        excerpt_id,
-        text_anchor: language::proto::deserialize_anchor(anchor.anchor?)?,
-        buffer_id: buffer.buffer_id_for_excerpt(excerpt_id),
-    })
-}
-
-impl Item for Editor {
-    type Event = EditorEvent;
-
-    fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) -> bool {
-        if let Ok(data) = data.downcast::<NavigationData>() {
-            let newest_selection = self.selections.newest::<Point>(cx);
-            let buffer = self.buffer.read(cx).read(cx);
-            let offset = if buffer.can_resolve(&data.cursor_anchor) {
-                data.cursor_anchor.to_point(&buffer)
-            } else {
-                buffer.clip_point(data.cursor_position, Bias::Left)
-            };
-
-            let mut scroll_anchor = data.scroll_anchor;
-            if !buffer.can_resolve(&scroll_anchor.anchor) {
-                scroll_anchor.anchor = buffer.anchor_before(
-                    buffer.clip_point(Point::new(data.scroll_top_row, 0), Bias::Left),
-                );
-            }
-
-            drop(buffer);
-
-            if newest_selection.head() == offset {
-                false
-            } else {
-                let nav_history = self.nav_history.take();
-                self.set_scroll_anchor(scroll_anchor, cx);
-                self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                    s.select_ranges([offset..offset])
-                });
-                self.nav_history = nav_history;
-                true
-            }
-        } else {
-            false
-        }
-    }
-
-    fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
-        let file_path = self
-            .buffer()
-            .read(cx)
-            .as_singleton()?
-            .read(cx)
-            .file()
-            .and_then(|f| f.as_local())?
-            .abs_path(cx);
-
-        let file_path = file_path.compact().to_string_lossy().to_string();
-
-        Some(file_path.into())
-    }
-
-    fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option<SharedString> {
-        let path = path_for_buffer(&self.buffer, detail, true, cx)?;
-        Some(path.to_string_lossy().to_string().into())
-    }
-
-    fn tab_content(&self, detail: Option<usize>, selected: bool, cx: &WindowContext) -> AnyElement {
-        let _theme = cx.theme();
-
-        let description = detail.and_then(|detail| {
-            let path = path_for_buffer(&self.buffer, detail, false, cx)?;
-            let description = path.to_string_lossy();
-            let description = description.trim();
-
-            if description.is_empty() {
-                return None;
-            }
-
-            Some(util::truncate_and_trailoff(&description, MAX_TAB_TITLE_LEN))
-        });
-
-        h_stack()
-            .gap_2()
-            .child(Label::new(self.title(cx).to_string()).color(if selected {
-                Color::Default
-            } else {
-                Color::Muted
-            }))
-            .when_some(description, |this, description| {
-                this.child(
-                    Label::new(description)
-                        .size(LabelSize::XSmall)
-                        .color(Color::Muted),
-                )
-            })
-            .into_any_element()
-    }
-
-    fn for_each_project_item(
-        &self,
-        cx: &AppContext,
-        f: &mut dyn FnMut(EntityId, &dyn project::Item),
-    ) {
-        self.buffer
-            .read(cx)
-            .for_each_buffer(|buffer| f(buffer.entity_id(), buffer.read(cx)));
-    }
-
-    fn is_singleton(&self, cx: &AppContext) -> bool {
-        self.buffer.read(cx).is_singleton()
-    }
-
-    fn clone_on_split(
-        &self,
-        _workspace_id: WorkspaceId,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<View<Editor>>
-    where
-        Self: Sized,
-    {
-        Some(cx.new_view(|cx| self.clone(cx)))
-    }
-
-    fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
-        self.nav_history = Some(history);
-    }
-
-    fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
-        let selection = self.selections.newest_anchor();
-        self.push_to_nav_history(selection.head(), None, cx);
-    }
-
-    fn workspace_deactivated(&mut self, cx: &mut ViewContext<Self>) {
-        hide_link_definition(self, cx);
-        self.link_go_to_definition_state.last_trigger_point = None;
-    }
-
-    fn is_dirty(&self, cx: &AppContext) -> bool {
-        self.buffer().read(cx).read(cx).is_dirty()
-    }
-
-    fn has_conflict(&self, cx: &AppContext) -> bool {
-        self.buffer().read(cx).read(cx).has_conflict()
-    }
-
-    fn can_save(&self, cx: &AppContext) -> bool {
-        let buffer = &self.buffer().read(cx);
-        if let Some(buffer) = buffer.as_singleton() {
-            buffer.read(cx).project_path(cx).is_some()
-        } else {
-            true
-        }
-    }
-
-    fn save(&mut self, project: Model<Project>, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
-        self.report_editor_event("save", None, cx);
-        let format = self.perform_format(project.clone(), FormatTrigger::Save, cx);
-        let buffers = self.buffer().clone().read(cx).all_buffers();
-        cx.spawn(|_, mut cx| async move {
-            format.await?;
-
-            if buffers.len() == 1 {
-                project
-                    .update(&mut cx, |project, cx| project.save_buffers(buffers, cx))?
-                    .await?;
-            } else {
-                // For multi-buffers, only save those ones that contain changes. For clean buffers
-                // we simulate saving by calling `Buffer::did_save`, so that language servers or
-                // other downstream listeners of save events get notified.
-                let (dirty_buffers, clean_buffers) = buffers.into_iter().partition(|buffer| {
-                    buffer
-                        .update(&mut cx, |buffer, _| {
-                            buffer.is_dirty() || buffer.has_conflict()
-                        })
-                        .unwrap_or(false)
-                });
-
-                project
-                    .update(&mut cx, |project, cx| {
-                        project.save_buffers(dirty_buffers, cx)
-                    })?
-                    .await?;
-                for buffer in clean_buffers {
-                    buffer
-                        .update(&mut cx, |buffer, cx| {
-                            let version = buffer.saved_version().clone();
-                            let fingerprint = buffer.saved_version_fingerprint();
-                            let mtime = buffer.saved_mtime();
-                            buffer.did_save(version, fingerprint, mtime, cx);
-                        })
-                        .ok();
-                }
-            }
-
-            Ok(())
-        })
-    }
-
-    fn save_as(
-        &mut self,
-        project: Model<Project>,
-        abs_path: PathBuf,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<Result<()>> {
-        let buffer = self
-            .buffer()
-            .read(cx)
-            .as_singleton()
-            .expect("cannot call save_as on an excerpt list");
-
-        let file_extension = abs_path
-            .extension()
-            .map(|a| a.to_string_lossy().to_string());
-        self.report_editor_event("save", file_extension, cx);
-
-        project.update(cx, |project, cx| {
-            project.save_buffer_as(buffer, abs_path, cx)
-        })
-    }
-
-    fn reload(&mut self, project: Model<Project>, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
-        let buffer = self.buffer().clone();
-        let buffers = self.buffer.read(cx).all_buffers();
-        let reload_buffers =
-            project.update(cx, |project, cx| project.reload_buffers(buffers, true, cx));
-        cx.spawn(|this, mut cx| async move {
-            let transaction = reload_buffers.log_err().await;
-            this.update(&mut cx, |editor, cx| {
-                editor.request_autoscroll(Autoscroll::fit(), cx)
-            })?;
-            buffer
-                .update(&mut cx, |buffer, cx| {
-                    if let Some(transaction) = transaction {
-                        if !buffer.is_singleton() {
-                            buffer.push_transaction(&transaction.0, cx);
-                        }
-                    }
-                })
-                .ok();
-            Ok(())
-        })
-    }
-
-    fn as_searchable(&self, handle: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
-        Some(Box::new(handle.clone()))
-    }
-
-    fn pixel_position_of_cursor(&self, _: &AppContext) -> Option<gpui::Point<Pixels>> {
-        self.pixel_position_of_newest_cursor
-    }
-
-    fn breadcrumb_location(&self) -> ToolbarItemLocation {
-        ToolbarItemLocation::PrimaryLeft
-    }
-
-    fn breadcrumbs(&self, variant: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
-        let cursor = self.selections.newest_anchor().head();
-        let multibuffer = &self.buffer().read(cx);
-        let (buffer_id, symbols) =
-            multibuffer.symbols_containing(cursor, Some(&variant.syntax()), cx)?;
-        let buffer = multibuffer.buffer(buffer_id)?;
-
-        let buffer = buffer.read(cx);
-        let filename = buffer
-            .snapshot()
-            .resolve_file_path(
-                cx,
-                self.project
-                    .as_ref()
-                    .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
-                    .unwrap_or_default(),
-            )
-            .map(|path| path.to_string_lossy().to_string())
-            .unwrap_or_else(|| "untitled".to_string());
-
-        let mut breadcrumbs = vec![BreadcrumbText {
-            text: filename,
-            highlights: None,
-        }];
-        breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
-            text: symbol.text,
-            highlights: Some(symbol.highlight_ranges),
-        }));
-        Some(breadcrumbs)
-    }
-
-    fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
-        let workspace_id = workspace.database_id();
-        let item_id = cx.view().item_id().as_u64() as ItemId;
-        self.workspace = Some((workspace.weak_handle(), workspace.database_id()));
-
-        fn serialize(
-            buffer: Model<Buffer>,
-            workspace_id: WorkspaceId,
-            item_id: ItemId,
-            cx: &mut AppContext,
-        ) {
-            if let Some(file) = buffer.read(cx).file().and_then(|file| file.as_local()) {
-                let path = file.abs_path(cx);
-
-                cx.background_executor()
-                    .spawn(async move {
-                        DB.save_path(item_id, workspace_id, path.clone())
-                            .await
-                            .log_err()
-                    })
-                    .detach();
-            }
-        }
-
-        if let Some(buffer) = self.buffer().read(cx).as_singleton() {
-            serialize(buffer.clone(), workspace_id, item_id, cx);
-
-            cx.subscribe(&buffer, |this, buffer, event, cx| {
-                if let Some((_, workspace_id)) = this.workspace.as_ref() {
-                    if let language::Event::FileHandleChanged = event {
-                        serialize(
-                            buffer,
-                            *workspace_id,
-                            cx.view().item_id().as_u64() as ItemId,
-                            cx,
-                        );
-                    }
-                }
-            })
-            .detach();
-        }
-    }
-
-    fn serialized_item_kind() -> Option<&'static str> {
-        Some("Editor")
-    }
-
-    fn to_item_events(event: &EditorEvent, mut f: impl FnMut(ItemEvent)) {
-        match event {
-            EditorEvent::Closed => f(ItemEvent::CloseItem),
-
-            EditorEvent::Saved | EditorEvent::TitleChanged => {
-                f(ItemEvent::UpdateTab);
-                f(ItemEvent::UpdateBreadcrumbs);
-            }
-
-            EditorEvent::Reparsed => {
-                f(ItemEvent::UpdateBreadcrumbs);
-            }
-
-            EditorEvent::SelectionsChanged { local } if *local => {
-                f(ItemEvent::UpdateBreadcrumbs);
-            }
-
-            EditorEvent::DirtyChanged => {
-                f(ItemEvent::UpdateTab);
-            }
-
-            EditorEvent::BufferEdited => {
-                f(ItemEvent::Edit);
-                f(ItemEvent::UpdateBreadcrumbs);
-            }
-
-            EditorEvent::ExcerptsAdded { .. } | EditorEvent::ExcerptsRemoved { .. } => {
-                f(ItemEvent::Edit);
-            }
-
-            _ => {}
-        }
-    }
-
-    fn deserialize(
-        project: Model<Project>,
-        _workspace: WeakView<Workspace>,
-        workspace_id: workspace::WorkspaceId,
-        item_id: ItemId,
-        cx: &mut ViewContext<Pane>,
-    ) -> Task<Result<View<Self>>> {
-        let project_item: Result<_> = project.update(cx, |project, cx| {
-            // Look up the path with this key associated, create a self with that path
-            let path = DB
-                .get_path(item_id, workspace_id)?
-                .context("No path stored for this editor")?;
-
-            let (worktree, path) = project
-                .find_local_worktree(&path, cx)
-                .with_context(|| format!("No worktree for path: {path:?}"))?;
-            let project_path = ProjectPath {
-                worktree_id: worktree.read(cx).id(),
-                path: path.into(),
-            };
-
-            Ok(project.open_path(project_path, cx))
-        });
-
-        project_item
-            .map(|project_item| {
-                cx.spawn(|pane, mut cx| async move {
-                    let (_, project_item) = project_item.await?;
-                    let buffer = project_item
-                        .downcast::<Buffer>()
-                        .map_err(|_| anyhow!("Project item at stored path was not a buffer"))?;
-                    Ok(pane.update(&mut cx, |_, cx| {
-                        cx.new_view(|cx| {
-                            let mut editor = Editor::for_buffer(buffer, Some(project), cx);
-
-                            editor.read_scroll_position_from_db(item_id, workspace_id, cx);
-                            editor
-                        })
-                    })?)
-                })
-            })
-            .unwrap_or_else(|error| Task::ready(Err(error)))
-    }
-}
-
-impl ProjectItem for Editor {
-    type Item = Buffer;
-
-    fn for_project_item(
-        project: Model<Project>,
-        buffer: Model<Buffer>,
-        cx: &mut ViewContext<Self>,
-    ) -> Self {
-        Self::for_buffer(buffer, Some(project), cx)
-    }
-}
-
-impl EventEmitter<SearchEvent> for Editor {}
-
-pub(crate) enum BufferSearchHighlights {}
-impl SearchableItem for Editor {
-    type Match = Range<Anchor>;
-
-    fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
-        self.clear_background_highlights::<BufferSearchHighlights>(cx);
-    }
-
-    fn update_matches(&mut self, matches: Vec<Range<Anchor>>, cx: &mut ViewContext<Self>) {
-        self.highlight_background::<BufferSearchHighlights>(
-            matches,
-            |theme| theme.search_match_background,
-            cx,
-        );
-    }
-
-    fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
-        let setting = EditorSettings::get_global(cx).seed_search_query_from_cursor;
-        let snapshot = &self.snapshot(cx).buffer_snapshot;
-        let selection = self.selections.newest::<usize>(cx);
-
-        match setting {
-            SeedQuerySetting::Never => String::new(),
-            SeedQuerySetting::Selection | SeedQuerySetting::Always if !selection.is_empty() => {
-                snapshot
-                    .text_for_range(selection.start..selection.end)
-                    .collect()
-            }
-            SeedQuerySetting::Selection => String::new(),
-            SeedQuerySetting::Always => {
-                let (range, kind) = snapshot.surrounding_word(selection.start);
-                if kind == Some(CharKind::Word) {
-                    let text: String = snapshot.text_for_range(range).collect();
-                    if !text.trim().is_empty() {
-                        return text;
-                    }
-                }
-                String::new()
-            }
-        }
-    }
-
-    fn activate_match(
-        &mut self,
-        index: usize,
-        matches: Vec<Range<Anchor>>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.unfold_ranges([matches[index].clone()], false, true, cx);
-        let range = self.range_for_match(&matches[index]);
-        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.select_ranges([range]);
-        })
-    }
-
-    fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) {
-        self.unfold_ranges(matches.clone(), false, false, cx);
-        let mut ranges = Vec::new();
-        for m in &matches {
-            ranges.push(self.range_for_match(&m))
-        }
-        self.change_selections(None, cx, |s| s.select_ranges(ranges));
-    }
-    fn replace(
-        &mut self,
-        identifier: &Self::Match,
-        query: &SearchQuery,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let text = self.buffer.read(cx);
-        let text = text.snapshot(cx);
-        let text = text.text_for_range(identifier.clone()).collect::<Vec<_>>();
-        let text: Cow<_> = if text.len() == 1 {
-            text.first().cloned().unwrap().into()
-        } else {
-            let joined_chunks = text.join("");
-            joined_chunks.into()
-        };
-
-        if let Some(replacement) = query.replacement_for(&text) {
-            self.transact(cx, |this, cx| {
-                this.edit([(identifier.clone(), Arc::from(&*replacement))], cx);
-            });
-        }
-    }
-    fn match_index_for_direction(
-        &mut self,
-        matches: &Vec<Range<Anchor>>,
-        current_index: usize,
-        direction: Direction,
-        count: usize,
-        cx: &mut ViewContext<Self>,
-    ) -> usize {
-        let buffer = self.buffer().read(cx).snapshot(cx);
-        let current_index_position = if self.selections.disjoint_anchors().len() == 1 {
-            self.selections.newest_anchor().head()
-        } else {
-            matches[current_index].start
-        };
-
-        let mut count = count % matches.len();
-        if count == 0 {
-            return current_index;
-        }
-        match direction {
-            Direction::Next => {
-                if matches[current_index]
-                    .start
-                    .cmp(&current_index_position, &buffer)
-                    .is_gt()
-                {
-                    count = count - 1
-                }
-
-                (current_index + count) % matches.len()
-            }
-            Direction::Prev => {
-                if matches[current_index]
-                    .end
-                    .cmp(&current_index_position, &buffer)
-                    .is_lt()
-                {
-                    count = count - 1;
-                }
-
-                if current_index >= count {
-                    current_index - count
-                } else {
-                    matches.len() - (count - current_index)
-                }
-            }
-        }
-    }
-
-    fn find_matches(
-        &mut self,
-        query: Arc<project::search::SearchQuery>,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<Vec<Range<Anchor>>> {
-        let buffer = self.buffer().read(cx).snapshot(cx);
-        cx.background_executor().spawn(async move {
-            let mut ranges = Vec::new();
-            if let Some((_, _, excerpt_buffer)) = buffer.as_singleton() {
-                ranges.extend(
-                    query
-                        .search(excerpt_buffer, None)
-                        .await
-                        .into_iter()
-                        .map(|range| {
-                            buffer.anchor_after(range.start)..buffer.anchor_before(range.end)
-                        }),
-                );
-            } else {
-                for excerpt in buffer.excerpt_boundaries_in_range(0..buffer.len()) {
-                    let excerpt_range = excerpt.range.context.to_offset(&excerpt.buffer);
-                    ranges.extend(
-                        query
-                            .search(&excerpt.buffer, Some(excerpt_range.clone()))
-                            .await
-                            .into_iter()
-                            .map(|range| {
-                                let start = excerpt
-                                    .buffer
-                                    .anchor_after(excerpt_range.start + range.start);
-                                let end = excerpt
-                                    .buffer
-                                    .anchor_before(excerpt_range.start + range.end);
-                                buffer.anchor_in_excerpt(excerpt.id.clone(), start)
-                                    ..buffer.anchor_in_excerpt(excerpt.id.clone(), end)
-                            }),
-                    );
-                }
-            }
-            ranges
-        })
-    }
-
-    fn active_match_index(
-        &mut self,
-        matches: Vec<Range<Anchor>>,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<usize> {
-        active_match_index(
-            &matches,
-            &self.selections.newest_anchor().head(),
-            &self.buffer().read(cx).snapshot(cx),
-        )
-    }
-}
-
-pub fn active_match_index(
-    ranges: &[Range<Anchor>],
-    cursor: &Anchor,
-    buffer: &MultiBufferSnapshot,
-) -> Option<usize> {
-    if ranges.is_empty() {
-        None
-    } else {
-        match ranges.binary_search_by(|probe| {
-            if probe.end.cmp(cursor, &*buffer).is_lt() {
-                Ordering::Less
-            } else if probe.start.cmp(cursor, &*buffer).is_gt() {
-                Ordering::Greater
-            } else {
-                Ordering::Equal
-            }
-        }) {
-            Ok(i) | Err(i) => Some(cmp::min(i, ranges.len() - 1)),
-        }
-    }
-}
-
-pub struct CursorPosition {
-    position: Option<Point>,
-    selected_count: usize,
-    _observe_active_editor: Option<Subscription>,
-}
-
-impl Default for CursorPosition {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
-impl CursorPosition {
-    pub fn new() -> Self {
-        Self {
-            position: None,
-            selected_count: 0,
-            _observe_active_editor: None,
-        }
-    }
-
-    fn update_position(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
-        let editor = editor.read(cx);
-        let buffer = editor.buffer().read(cx).snapshot(cx);
-
-        self.selected_count = 0;
-        let mut last_selection: Option<Selection<usize>> = None;
-        for selection in editor.selections.all::<usize>(cx) {
-            self.selected_count += selection.end - selection.start;
-            if last_selection
-                .as_ref()
-                .map_or(true, |last_selection| selection.id > last_selection.id)
-            {
-                last_selection = Some(selection);
-            }
-        }
-        self.position = last_selection.map(|s| s.head().to_point(&buffer));
-
-        cx.notify();
-    }
-}
-
-impl Render for CursorPosition {
-    fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
-        div().when_some(self.position, |el, position| {
-            let mut text = format!(
-                "{}{FILE_ROW_COLUMN_DELIMITER}{}",
-                position.row + 1,
-                position.column + 1
-            );
-            if self.selected_count > 0 {
-                write!(text, " ({} selected)", self.selected_count).unwrap();
-            }
-
-            el.child(Label::new(text).size(LabelSize::Small))
-        })
-    }
-}
-
-impl StatusItemView for CursorPosition {
-    fn set_active_pane_item(
-        &mut self,
-        active_pane_item: Option<&dyn ItemHandle>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        if let Some(editor) = active_pane_item.and_then(|item| item.act_as::<Editor>(cx)) {
-            self._observe_active_editor = Some(cx.observe(&editor, Self::update_position));
-            self.update_position(editor, cx);
-        } else {
-            self.position = None;
-            self._observe_active_editor = None;
-        }
-
-        cx.notify();
-    }
-}
-
-fn path_for_buffer<'a>(
-    buffer: &Model<MultiBuffer>,
-    height: usize,
-    include_filename: bool,
-    cx: &'a AppContext,
-) -> Option<Cow<'a, Path>> {
-    let file = buffer.read(cx).as_singleton()?.read(cx).file()?;
-    path_for_file(file.as_ref(), height, include_filename, cx)
-}
-
-fn path_for_file<'a>(
-    file: &'a dyn language::File,
-    mut height: usize,
-    include_filename: bool,
-    cx: &'a AppContext,
-) -> Option<Cow<'a, Path>> {
-    // Ensure we always render at least the filename.
-    height += 1;
-
-    let mut prefix = file.path().as_ref();
-    while height > 0 {
-        if let Some(parent) = prefix.parent() {
-            prefix = parent;
-            height -= 1;
-        } else {
-            break;
-        }
-    }
-
-    // Here we could have just always used `full_path`, but that is very
-    // allocation-heavy and so we try to use a `Cow<Path>` if we haven't
-    // traversed all the way up to the worktree's root.
-    if height > 0 {
-        let full_path = file.full_path(cx);
-        if include_filename {
-            Some(full_path.into())
-        } else {
-            Some(full_path.parent()?.to_path_buf().into())
-        }
-    } else {
-        let mut path = file.path().strip_prefix(prefix).ok()?;
-        if !include_filename {
-            path = path.parent()?;
-        }
-        Some(path.into())
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use gpui::AppContext;
-    use std::{
-        path::{Path, PathBuf},
-        sync::Arc,
-        time::SystemTime,
-    };
-
-    #[gpui::test]
-    fn test_path_for_file(cx: &mut AppContext) {
-        let file = TestFile {
-            path: Path::new("").into(),
-            full_path: PathBuf::from(""),
-        };
-        assert_eq!(path_for_file(&file, 0, false, cx), None);
-    }
-
-    struct TestFile {
-        path: Arc<Path>,
-        full_path: PathBuf,
-    }
-
-    impl language::File for TestFile {
-        fn path(&self) -> &Arc<Path> {
-            &self.path
-        }
-
-        fn full_path(&self, _: &gpui::AppContext) -> PathBuf {
-            self.full_path.clone()
-        }
-
-        fn as_local(&self) -> Option<&dyn language::LocalFile> {
-            unimplemented!()
-        }
-
-        fn mtime(&self) -> SystemTime {
-            unimplemented!()
-        }
-
-        fn file_name<'a>(&'a self, _: &'a gpui::AppContext) -> &'a std::ffi::OsStr {
-            unimplemented!()
-        }
-
-        fn worktree_id(&self) -> usize {
-            0
-        }
-
-        fn is_deleted(&self) -> bool {
-            unimplemented!()
-        }
-
-        fn as_any(&self) -> &dyn std::any::Any {
-            unimplemented!()
-        }
-
-        fn to_proto(&self) -> rpc::proto::File {
-            unimplemented!()
-        }
-    }
-}
@@ -1,1279 +0,0 @@
-use crate::{
-    display_map::DisplaySnapshot,
-    element::PointForPosition,
-    hover_popover::{self, InlayHover},
-    Anchor, DisplayPoint, Editor, EditorSnapshot, GoToDefinition, GoToTypeDefinition, InlayId,
-    SelectPhase,
-};
-use gpui::{px, Task, ViewContext};
-use language::{Bias, ToOffset};
-use lsp::LanguageServerId;
-use project::{
-    HoverBlock, HoverBlockKind, InlayHintLabelPartTooltip, InlayHintTooltip, LocationLink,
-    ResolveState,
-};
-use std::ops::Range;
-use theme::ActiveTheme as _;
-use util::TryFutureExt;
-
-#[derive(Debug, Default)]
-pub struct LinkGoToDefinitionState {
-    pub last_trigger_point: Option<TriggerPoint>,
-    pub symbol_range: Option<RangeInEditor>,
-    pub kind: Option<LinkDefinitionKind>,
-    pub definitions: Vec<GoToDefinitionLink>,
-    pub task: Option<Task<Option<()>>>,
-}
-
-#[derive(Debug, Eq, PartialEq, Clone)]
-pub enum RangeInEditor {
-    Text(Range<Anchor>),
-    Inlay(InlayHighlight),
-}
-
-impl RangeInEditor {
-    pub fn as_text_range(&self) -> Option<Range<Anchor>> {
-        match self {
-            Self::Text(range) => Some(range.clone()),
-            Self::Inlay(_) => None,
-        }
-    }
-
-    fn point_within_range(&self, trigger_point: &TriggerPoint, snapshot: &EditorSnapshot) -> bool {
-        match (self, trigger_point) {
-            (Self::Text(range), TriggerPoint::Text(point)) => {
-                let point_after_start = range.start.cmp(point, &snapshot.buffer_snapshot).is_le();
-                point_after_start && range.end.cmp(point, &snapshot.buffer_snapshot).is_ge()
-            }
-            (Self::Inlay(highlight), TriggerPoint::InlayHint(point, _, _)) => {
-                highlight.inlay == point.inlay
-                    && highlight.range.contains(&point.range.start)
-                    && highlight.range.contains(&point.range.end)
-            }
-            (Self::Inlay(_), TriggerPoint::Text(_))
-            | (Self::Text(_), TriggerPoint::InlayHint(_, _, _)) => false,
-        }
-    }
-}
-
-#[derive(Debug)]
-pub enum GoToDefinitionTrigger {
-    Text(DisplayPoint),
-    InlayHint(InlayHighlight, lsp::Location, LanguageServerId),
-}
-
-#[derive(Debug, Clone)]
-pub enum GoToDefinitionLink {
-    Text(LocationLink),
-    InlayHint(lsp::Location, LanguageServerId),
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct InlayHighlight {
-    pub inlay: InlayId,
-    pub inlay_position: Anchor,
-    pub range: Range<usize>,
-}
-
-#[derive(Debug, Clone)]
-pub enum TriggerPoint {
-    Text(Anchor),
-    InlayHint(InlayHighlight, lsp::Location, LanguageServerId),
-}
-
-impl TriggerPoint {
-    pub fn definition_kind(&self, shift: bool) -> LinkDefinitionKind {
-        match self {
-            TriggerPoint::Text(_) => {
-                if shift {
-                    LinkDefinitionKind::Type
-                } else {
-                    LinkDefinitionKind::Symbol
-                }
-            }
-            TriggerPoint::InlayHint(_, _, _) => LinkDefinitionKind::Type,
-        }
-    }
-
-    fn anchor(&self) -> &Anchor {
-        match self {
-            TriggerPoint::Text(anchor) => anchor,
-            TriggerPoint::InlayHint(inlay_range, _, _) => &inlay_range.inlay_position,
-        }
-    }
-}
-
-pub fn update_go_to_definition_link(
-    editor: &mut Editor,
-    origin: Option<GoToDefinitionTrigger>,
-    cmd_held: bool,
-    shift_held: bool,
-    cx: &mut ViewContext<Editor>,
-) {
-    let pending_nonempty_selection = editor.has_pending_nonempty_selection();
-
-    // Store new mouse point as an anchor
-    let snapshot = editor.snapshot(cx);
-    let trigger_point = match origin {
-        Some(GoToDefinitionTrigger::Text(p)) => {
-            Some(TriggerPoint::Text(snapshot.buffer_snapshot.anchor_before(
-                p.to_offset(&snapshot.display_snapshot, Bias::Left),
-            )))
-        }
-        Some(GoToDefinitionTrigger::InlayHint(p, lsp_location, language_server_id)) => {
-            Some(TriggerPoint::InlayHint(p, lsp_location, language_server_id))
-        }
-        None => None,
-    };
-
-    // If the new point is the same as the previously stored one, return early
-    if let (Some(a), Some(b)) = (
-        &trigger_point,
-        &editor.link_go_to_definition_state.last_trigger_point,
-    ) {
-        match (a, b) {
-            (TriggerPoint::Text(anchor_a), TriggerPoint::Text(anchor_b)) => {
-                if anchor_a.cmp(anchor_b, &snapshot.buffer_snapshot).is_eq() {
-                    return;
-                }
-            }
-            (TriggerPoint::InlayHint(range_a, _, _), TriggerPoint::InlayHint(range_b, _, _)) => {
-                if range_a == range_b {
-                    return;
-                }
-            }
-            _ => {}
-        }
-    }
-
-    editor.link_go_to_definition_state.last_trigger_point = trigger_point.clone();
-
-    if pending_nonempty_selection {
-        hide_link_definition(editor, cx);
-        return;
-    }
-
-    if cmd_held {
-        if let Some(trigger_point) = trigger_point {
-            let kind = trigger_point.definition_kind(shift_held);
-            show_link_definition(kind, editor, trigger_point, snapshot, cx);
-            return;
-        }
-    }
-
-    hide_link_definition(editor, cx);
-}
-
-pub fn update_inlay_link_and_hover_points(
-    snapshot: &DisplaySnapshot,
-    point_for_position: PointForPosition,
-    editor: &mut Editor,
-    cmd_held: bool,
-    shift_held: bool,
-    cx: &mut ViewContext<'_, Editor>,
-) {
-    let hovered_offset = if point_for_position.column_overshoot_after_line_end == 0 {
-        Some(snapshot.display_point_to_inlay_offset(point_for_position.exact_unclipped, Bias::Left))
-    } else {
-        None
-    };
-    let mut go_to_definition_updated = false;
-    let mut hover_updated = false;
-    if let Some(hovered_offset) = hovered_offset {
-        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
-        let previous_valid_anchor = buffer_snapshot.anchor_at(
-            point_for_position.previous_valid.to_point(snapshot),
-            Bias::Left,
-        );
-        let next_valid_anchor = buffer_snapshot.anchor_at(
-            point_for_position.next_valid.to_point(snapshot),
-            Bias::Right,
-        );
-        if let Some(hovered_hint) = editor
-            .visible_inlay_hints(cx)
-            .into_iter()
-            .skip_while(|hint| {
-                hint.position
-                    .cmp(&previous_valid_anchor, &buffer_snapshot)
-                    .is_lt()
-            })
-            .take_while(|hint| {
-                hint.position
-                    .cmp(&next_valid_anchor, &buffer_snapshot)
-                    .is_le()
-            })
-            .max_by_key(|hint| hint.id)
-        {
-            let inlay_hint_cache = editor.inlay_hint_cache();
-            let excerpt_id = previous_valid_anchor.excerpt_id;
-            if let Some(cached_hint) = inlay_hint_cache.hint_by_id(excerpt_id, hovered_hint.id) {
-                match cached_hint.resolve_state {
-                    ResolveState::CanResolve(_, _) => {
-                        if let Some(buffer_id) = previous_valid_anchor.buffer_id {
-                            inlay_hint_cache.spawn_hint_resolve(
-                                buffer_id,
-                                excerpt_id,
-                                hovered_hint.id,
-                                cx,
-                            );
-                        }
-                    }
-                    ResolveState::Resolved => {
-                        let mut extra_shift_left = 0;
-                        let mut extra_shift_right = 0;
-                        if cached_hint.padding_left {
-                            extra_shift_left += 1;
-                            extra_shift_right += 1;
-                        }
-                        if cached_hint.padding_right {
-                            extra_shift_right += 1;
-                        }
-                        match cached_hint.label {
-                            project::InlayHintLabel::String(_) => {
-                                if let Some(tooltip) = cached_hint.tooltip {
-                                    hover_popover::hover_at_inlay(
-                                        editor,
-                                        InlayHover {
-                                            excerpt: excerpt_id,
-                                            tooltip: match tooltip {
-                                                InlayHintTooltip::String(text) => HoverBlock {
-                                                    text,
-                                                    kind: HoverBlockKind::PlainText,
-                                                },
-                                                InlayHintTooltip::MarkupContent(content) => {
-                                                    HoverBlock {
-                                                        text: content.value,
-                                                        kind: content.kind,
-                                                    }
-                                                }
-                                            },
-                                            range: InlayHighlight {
-                                                inlay: hovered_hint.id,
-                                                inlay_position: hovered_hint.position,
-                                                range: extra_shift_left
-                                                    ..hovered_hint.text.len() + extra_shift_right,
-                                            },
-                                        },
-                                        cx,
-                                    );
-                                    hover_updated = true;
-                                }
-                            }
-                            project::InlayHintLabel::LabelParts(label_parts) => {
-                                let hint_start =
-                                    snapshot.anchor_to_inlay_offset(hovered_hint.position);
-                                if let Some((hovered_hint_part, part_range)) =
-                                    hover_popover::find_hovered_hint_part(
-                                        label_parts,
-                                        hint_start,
-                                        hovered_offset,
-                                    )
-                                {
-                                    let highlight_start =
-                                        (part_range.start - hint_start).0 + extra_shift_left;
-                                    let highlight_end =
-                                        (part_range.end - hint_start).0 + extra_shift_right;
-                                    let highlight = InlayHighlight {
-                                        inlay: hovered_hint.id,
-                                        inlay_position: hovered_hint.position,
-                                        range: highlight_start..highlight_end,
-                                    };
-                                    if let Some(tooltip) = hovered_hint_part.tooltip {
-                                        hover_popover::hover_at_inlay(
-                                            editor,
-                                            InlayHover {
-                                                excerpt: excerpt_id,
-                                                tooltip: match tooltip {
-                                                    InlayHintLabelPartTooltip::String(text) => {
-                                                        HoverBlock {
-                                                            text,
-                                                            kind: HoverBlockKind::PlainText,
-                                                        }
-                                                    }
-                                                    InlayHintLabelPartTooltip::MarkupContent(
-                                                        content,
-                                                    ) => HoverBlock {
-                                                        text: content.value,
-                                                        kind: content.kind,
-                                                    },
-                                                },
-                                                range: highlight.clone(),
-                                            },
-                                            cx,
-                                        );
-                                        hover_updated = true;
-                                    }
-                                    if let Some((language_server_id, location)) =
-                                        hovered_hint_part.location
-                                    {
-                                        go_to_definition_updated = true;
-                                        update_go_to_definition_link(
-                                            editor,
-                                            Some(GoToDefinitionTrigger::InlayHint(
-                                                highlight,
-                                                location,
-                                                language_server_id,
-                                            )),
-                                            cmd_held,
-                                            shift_held,
-                                            cx,
-                                        );
-                                    }
-                                }
-                            }
-                        };
-                    }
-                    ResolveState::Resolving => {}
-                }
-            }
-        }
-    }
-
-    if !go_to_definition_updated {
-        update_go_to_definition_link(editor, None, cmd_held, shift_held, cx);
-    }
-    if !hover_updated {
-        hover_popover::hover_at(editor, None, cx);
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum LinkDefinitionKind {
-    Symbol,
-    Type,
-}
-
-pub fn show_link_definition(
-    definition_kind: LinkDefinitionKind,
-    editor: &mut Editor,
-    trigger_point: TriggerPoint,
-    snapshot: EditorSnapshot,
-    cx: &mut ViewContext<Editor>,
-) {
-    let same_kind = editor.link_go_to_definition_state.kind == Some(definition_kind);
-    if !same_kind {
-        hide_link_definition(editor, cx);
-    }
-
-    if editor.pending_rename.is_some() {
-        return;
-    }
-
-    let trigger_anchor = trigger_point.anchor();
-    let (buffer, buffer_position) = if let Some(output) = editor
-        .buffer
-        .read(cx)
-        .text_anchor_for_position(trigger_anchor.clone(), cx)
-    {
-        output
-    } else {
-        return;
-    };
-
-    let excerpt_id = if let Some((excerpt_id, _, _)) = editor
-        .buffer()
-        .read(cx)
-        .excerpt_containing(trigger_anchor.clone(), cx)
-    {
-        excerpt_id
-    } else {
-        return;
-    };
-
-    let project = if let Some(project) = editor.project.clone() {
-        project
-    } else {
-        return;
-    };
-
-    // Don't request again if the location is within the symbol region of a previous request with the same kind
-    if let Some(symbol_range) = &editor.link_go_to_definition_state.symbol_range {
-        if same_kind && symbol_range.point_within_range(&trigger_point, &snapshot) {
-            return;
-        }
-    }
-
-    let task = cx.spawn(|this, mut cx| {
-        async move {
-            let result = match &trigger_point {
-                TriggerPoint::Text(_) => {
-                    // query the LSP for definition info
-                    project
-                        .update(&mut cx, |project, cx| match definition_kind {
-                            LinkDefinitionKind::Symbol => {
-                                project.definition(&buffer, buffer_position, cx)
-                            }
-
-                            LinkDefinitionKind::Type => {
-                                project.type_definition(&buffer, buffer_position, cx)
-                            }
-                        })?
-                        .await
-                        .ok()
-                        .map(|definition_result| {
-                            (
-                                definition_result.iter().find_map(|link| {
-                                    link.origin.as_ref().map(|origin| {
-                                        let start = snapshot.buffer_snapshot.anchor_in_excerpt(
-                                            excerpt_id.clone(),
-                                            origin.range.start,
-                                        );
-                                        let end = snapshot.buffer_snapshot.anchor_in_excerpt(
-                                            excerpt_id.clone(),
-                                            origin.range.end,
-                                        );
-                                        RangeInEditor::Text(start..end)
-                                    })
-                                }),
-                                definition_result
-                                    .into_iter()
-                                    .map(GoToDefinitionLink::Text)
-                                    .collect(),
-                            )
-                        })
-                }
-                TriggerPoint::InlayHint(highlight, lsp_location, server_id) => Some((
-                    Some(RangeInEditor::Inlay(highlight.clone())),
-                    vec![GoToDefinitionLink::InlayHint(
-                        lsp_location.clone(),
-                        *server_id,
-                    )],
-                )),
-            };
-
-            this.update(&mut cx, |this, cx| {
-                // Clear any existing highlights
-                this.clear_highlights::<LinkGoToDefinitionState>(cx);
-                this.link_go_to_definition_state.kind = Some(definition_kind);
-                this.link_go_to_definition_state.symbol_range = result
-                    .as_ref()
-                    .and_then(|(symbol_range, _)| symbol_range.clone());
-
-                if let Some((symbol_range, definitions)) = result {
-                    this.link_go_to_definition_state.definitions = definitions.clone();
-
-                    let buffer_snapshot = buffer.read(cx).snapshot();
-
-                    // Only show highlight if there exists a definition to jump to that doesn't contain
-                    // the current location.
-                    let any_definition_does_not_contain_current_location =
-                        definitions.iter().any(|definition| {
-                            match &definition {
-                                GoToDefinitionLink::Text(link) => {
-                                    if link.target.buffer == buffer {
-                                        let range = &link.target.range;
-                                        // Expand range by one character as lsp definition ranges include positions adjacent
-                                        // but not contained by the symbol range
-                                        let start = buffer_snapshot.clip_offset(
-                                            range
-                                                .start
-                                                .to_offset(&buffer_snapshot)
-                                                .saturating_sub(1),
-                                            Bias::Left,
-                                        );
-                                        let end = buffer_snapshot.clip_offset(
-                                            range.end.to_offset(&buffer_snapshot) + 1,
-                                            Bias::Right,
-                                        );
-                                        let offset = buffer_position.to_offset(&buffer_snapshot);
-                                        !(start <= offset && end >= offset)
-                                    } else {
-                                        true
-                                    }
-                                }
-                                GoToDefinitionLink::InlayHint(_, _) => true,
-                            }
-                        });
-
-                    if any_definition_does_not_contain_current_location {
-                        let style = gpui::HighlightStyle {
-                            underline: Some(gpui::UnderlineStyle {
-                                thickness: px(1.),
-                                ..Default::default()
-                            }),
-                            color: Some(cx.theme().colors().link_text_hover),
-                            ..Default::default()
-                        };
-                        let highlight_range =
-                            symbol_range.unwrap_or_else(|| match &trigger_point {
-                                TriggerPoint::Text(trigger_anchor) => {
-                                    let snapshot = &snapshot.buffer_snapshot;
-                                    // If no symbol range returned from language server, use the surrounding word.
-                                    let (offset_range, _) =
-                                        snapshot.surrounding_word(*trigger_anchor);
-                                    RangeInEditor::Text(
-                                        snapshot.anchor_before(offset_range.start)
-                                            ..snapshot.anchor_after(offset_range.end),
-                                    )
-                                }
-                                TriggerPoint::InlayHint(highlight, _, _) => {
-                                    RangeInEditor::Inlay(highlight.clone())
-                                }
-                            });
-
-                        match highlight_range {
-                            RangeInEditor::Text(text_range) => this
-                                .highlight_text::<LinkGoToDefinitionState>(
-                                    vec![text_range],
-                                    style,
-                                    cx,
-                                ),
-                            RangeInEditor::Inlay(highlight) => this
-                                .highlight_inlays::<LinkGoToDefinitionState>(
-                                    vec![highlight],
-                                    style,
-                                    cx,
-                                ),
-                        }
-                    } else {
-                        hide_link_definition(this, cx);
-                    }
-                }
-            })?;
-
-            Ok::<_, anyhow::Error>(())
-        }
-        .log_err()
-    });
-
-    editor.link_go_to_definition_state.task = Some(task);
-}
-
-pub fn hide_link_definition(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
-    if editor.link_go_to_definition_state.symbol_range.is_some()
-        || !editor.link_go_to_definition_state.definitions.is_empty()
-    {
-        editor.link_go_to_definition_state.symbol_range.take();
-        editor.link_go_to_definition_state.definitions.clear();
-        cx.notify();
-    }
-
-    editor.link_go_to_definition_state.task = None;
-
-    editor.clear_highlights::<LinkGoToDefinitionState>(cx);
-}
-
-pub fn go_to_fetched_definition(
-    editor: &mut Editor,
-    point: PointForPosition,
-    split: bool,
-    cx: &mut ViewContext<Editor>,
-) {
-    go_to_fetched_definition_of_kind(LinkDefinitionKind::Symbol, editor, point, split, cx);
-}
-
-pub fn go_to_fetched_type_definition(
-    editor: &mut Editor,
-    point: PointForPosition,
-    split: bool,
-    cx: &mut ViewContext<Editor>,
-) {
-    go_to_fetched_definition_of_kind(LinkDefinitionKind::Type, editor, point, split, cx);
-}
-
-fn go_to_fetched_definition_of_kind(
-    kind: LinkDefinitionKind,
-    editor: &mut Editor,
-    point: PointForPosition,
-    split: bool,
-    cx: &mut ViewContext<Editor>,
-) {
-    let cached_definitions = editor.link_go_to_definition_state.definitions.clone();
-    hide_link_definition(editor, cx);
-    let cached_definitions_kind = editor.link_go_to_definition_state.kind;
-
-    let is_correct_kind = cached_definitions_kind == Some(kind);
-    if !cached_definitions.is_empty() && is_correct_kind {
-        if !editor.focus_handle.is_focused(cx) {
-            cx.focus(&editor.focus_handle);
-        }
-
-        editor.navigate_to_definitions(cached_definitions, split, cx);
-    } else {
-        editor.select(
-            SelectPhase::Begin {
-                position: point.next_valid,
-                add: false,
-                click_count: 1,
-            },
-            cx,
-        );
-
-        if point.as_valid().is_some() {
-            match kind {
-                LinkDefinitionKind::Symbol => editor.go_to_definition(&GoToDefinition, cx),
-                LinkDefinitionKind::Type => editor.go_to_type_definition(&GoToTypeDefinition, cx),
-            }
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::{
-        display_map::ToDisplayPoint,
-        editor_tests::init_test,
-        inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels},
-        test::editor_lsp_test_context::EditorLspTestContext,
-    };
-    use futures::StreamExt;
-    use gpui::{Modifiers, ModifiersChangedEvent};
-    use indoc::indoc;
-    use language::language_settings::InlayHintSettings;
-    use lsp::request::{GotoDefinition, GotoTypeDefinition};
-    use util::assert_set_eq;
-
-    #[gpui::test]
-    async fn test_link_go_to_type_definition(cx: &mut gpui::TestAppContext) {
-        init_test(cx, |_| {});
-
-        let mut cx = EditorLspTestContext::new_rust(
-            lsp::ServerCapabilities {
-                hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
-                type_definition_provider: Some(lsp::TypeDefinitionProviderCapability::Simple(true)),
-                ..Default::default()
-            },
-            cx,
-        )
-        .await;
-
-        cx.set_state(indoc! {"
-            struct A;
-            let vˇariable = A;
-        "});
-
-        // Basic hold cmd+shift, expect highlight in region if response contains type definition
-        let hover_point = cx.display_point(indoc! {"
-            struct A;
-            let vˇariable = A;
-        "});
-        let symbol_range = cx.lsp_range(indoc! {"
-            struct A;
-            let «variable» = A;
-        "});
-        let target_range = cx.lsp_range(indoc! {"
-            struct «A»;
-            let variable = A;
-        "});
-
-        let mut requests =
-            cx.handle_request::<GotoTypeDefinition, _, _>(move |url, _, _| async move {
-                Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![
-                    lsp::LocationLink {
-                        origin_selection_range: Some(symbol_range),
-                        target_uri: url.clone(),
-                        target_range,
-                        target_selection_range: target_range,
-                    },
-                ])))
-            });
-
-        // Press cmd+shift to trigger highlight
-        cx.update_editor(|editor, cx| {
-            update_go_to_definition_link(
-                editor,
-                Some(GoToDefinitionTrigger::Text(hover_point)),
-                true,
-                true,
-                cx,
-            );
-        });
-        requests.next().await;
-        cx.background_executor.run_until_parked();
-        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
-            struct A;
-            let «variable» = A;
-        "});
-
-        // Unpress shift causes highlight to go away (normal goto-definition is not valid here)
-        cx.update_editor(|editor, cx| {
-            crate::element::EditorElement::modifiers_changed(
-                editor,
-                &ModifiersChangedEvent {
-                    modifiers: Modifiers {
-                        command: true,
-                        ..Default::default()
-                    },
-                    ..Default::default()
-                },
-                cx,
-            );
-        });
-        // Assert no link highlights
-        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
-            struct A;
-            let variable = A;
-        "});
-
-        // Cmd+shift click without existing definition requests and jumps
-        let hover_point = cx.display_point(indoc! {"
-            struct A;
-            let vˇariable = A;
-        "});
-        let target_range = cx.lsp_range(indoc! {"
-            struct «A»;
-            let variable = A;
-        "});
-
-        let mut requests =
-            cx.handle_request::<GotoTypeDefinition, _, _>(move |url, _, _| async move {
-                Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![
-                    lsp::LocationLink {
-                        origin_selection_range: None,
-                        target_uri: url,
-                        target_range,
-                        target_selection_range: target_range,
-                    },
-                ])))
-            });
-
-        cx.update_editor(|editor, cx| {
-            go_to_fetched_type_definition(editor, PointForPosition::valid(hover_point), false, cx);
-        });
-        requests.next().await;
-        cx.background_executor.run_until_parked();
-
-        cx.assert_editor_state(indoc! {"
-            struct «Aˇ»;
-            let variable = A;
-        "});
-    }
-
-    #[gpui::test]
-    async fn test_link_go_to_definition(cx: &mut gpui::TestAppContext) {
-        init_test(cx, |_| {});
-
-        let mut cx = EditorLspTestContext::new_rust(
-            lsp::ServerCapabilities {
-                hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
-                ..Default::default()
-            },
-            cx,
-        )
-        .await;
-
-        cx.set_state(indoc! {"
-                fn ˇtest() { do_work(); }
-                fn do_work() { test(); }
-            "});
-
-        // Basic hold cmd, expect highlight in region if response contains definition
-        let hover_point = cx.display_point(indoc! {"
-                fn test() { do_wˇork(); }
-                fn do_work() { test(); }
-            "});
-        let symbol_range = cx.lsp_range(indoc! {"
-                fn test() { «do_work»(); }
-                fn do_work() { test(); }
-            "});
-        let target_range = cx.lsp_range(indoc! {"
-                fn test() { do_work(); }
-                fn «do_work»() { test(); }
-            "});
-
-        let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
-            Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
-                lsp::LocationLink {
-                    origin_selection_range: Some(symbol_range),
-                    target_uri: url.clone(),
-                    target_range,
-                    target_selection_range: target_range,
-                },
-            ])))
-        });
-
-        cx.update_editor(|editor, cx| {
-            update_go_to_definition_link(
-                editor,
-                Some(GoToDefinitionTrigger::Text(hover_point)),
-                true,
-                false,
-                cx,
-            );
-        });
-        requests.next().await;
-        cx.background_executor.run_until_parked();
-        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
-                fn test() { «do_work»(); }
-                fn do_work() { test(); }
-            "});
-
-        // Unpress cmd causes highlight to go away
-        cx.update_editor(|editor, cx| {
-            crate::element::EditorElement::modifiers_changed(editor, &Default::default(), cx);
-        });
-
-        // Assert no link highlights
-        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
-                fn test() { do_work(); }
-                fn do_work() { test(); }
-            "});
-
-        // Response without source range still highlights word
-        cx.update_editor(|editor, _| editor.link_go_to_definition_state.last_trigger_point = None);
-        let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
-            Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
-                lsp::LocationLink {
-                    // No origin range
-                    origin_selection_range: None,
-                    target_uri: url.clone(),
-                    target_range,
-                    target_selection_range: target_range,
-                },
-            ])))
-        });
-        cx.update_editor(|editor, cx| {
-            update_go_to_definition_link(
-                editor,
-                Some(GoToDefinitionTrigger::Text(hover_point)),
-                true,
-                false,
-                cx,
-            );
-        });
-        requests.next().await;
-        cx.background_executor.run_until_parked();
-
-        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
-                fn test() { «do_work»(); }
-                fn do_work() { test(); }
-            "});
-
-        // Moving mouse to location with no response dismisses highlight
-        let hover_point = cx.display_point(indoc! {"
-                fˇn test() { do_work(); }
-                fn do_work() { test(); }
-            "});
-        let mut requests = cx
-            .lsp
-            .handle_request::<GotoDefinition, _, _>(move |_, _| async move {
-                // No definitions returned
-                Ok(Some(lsp::GotoDefinitionResponse::Link(vec![])))
-            });
-        cx.update_editor(|editor, cx| {
-            update_go_to_definition_link(
-                editor,
-                Some(GoToDefinitionTrigger::Text(hover_point)),
-                true,
-                false,
-                cx,
-            );
-        });
-        requests.next().await;
-        cx.background_executor.run_until_parked();
-
-        // Assert no link highlights
-        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
-                fn test() { do_work(); }
-                fn do_work() { test(); }
-            "});
-
-        // Move mouse without cmd and then pressing cmd triggers highlight
-        let hover_point = cx.display_point(indoc! {"
-                fn test() { do_work(); }
-                fn do_work() { teˇst(); }
-            "});
-        cx.update_editor(|editor, cx| {
-            update_go_to_definition_link(
-                editor,
-                Some(GoToDefinitionTrigger::Text(hover_point)),
-                false,
-                false,
-                cx,
-            );
-        });
-        cx.background_executor.run_until_parked();
-
-        // Assert no link highlights
-        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
-                fn test() { do_work(); }
-                fn do_work() { test(); }
-            "});
-
-        let symbol_range = cx.lsp_range(indoc! {"
-                fn test() { do_work(); }
-                fn do_work() { «test»(); }
-            "});
-        let target_range = cx.lsp_range(indoc! {"
-                fn «test»() { do_work(); }
-                fn do_work() { test(); }
-            "});
-
-        let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
-            Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
-                lsp::LocationLink {
-                    origin_selection_range: Some(symbol_range),
-                    target_uri: url,
-                    target_range,
-                    target_selection_range: target_range,
-                },
-            ])))
-        });
-        cx.update_editor(|editor, cx| {
-            crate::element::EditorElement::modifiers_changed(
-                editor,
-                &ModifiersChangedEvent {
-                    modifiers: Modifiers {
-                        command: true,
-                        ..Default::default()
-                    },
-                },
-                cx,
-            );
-        });
-        requests.next().await;
-        cx.background_executor.run_until_parked();
-
-        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
-                fn test() { do_work(); }
-                fn do_work() { «test»(); }
-            "});
-
-        // Deactivating the window dismisses the highlight
-        cx.update_workspace(|workspace, cx| {
-            workspace.on_window_activation_changed(cx);
-        });
-        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
-                fn test() { do_work(); }
-                fn do_work() { test(); }
-            "});
-
-        // Moving the mouse restores the highlights.
-        cx.update_editor(|editor, cx| {
-            update_go_to_definition_link(
-                editor,
-                Some(GoToDefinitionTrigger::Text(hover_point)),
-                true,
-                false,
-                cx,
-            );
-        });
-        cx.background_executor.run_until_parked();
-        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
-                fn test() { do_work(); }
-                fn do_work() { «test»(); }
-            "});
-
-        // Moving again within the same symbol range doesn't re-request
-        let hover_point = cx.display_point(indoc! {"
-                fn test() { do_work(); }
-                fn do_work() { tesˇt(); }
-            "});
-        cx.update_editor(|editor, cx| {
-            update_go_to_definition_link(
-                editor,
-                Some(GoToDefinitionTrigger::Text(hover_point)),
-                true,
-                false,
-                cx,
-            );
-        });
-        cx.background_executor.run_until_parked();
-        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
-                fn test() { do_work(); }
-                fn do_work() { «test»(); }
-            "});
-
-        // Cmd click with existing definition doesn't re-request and dismisses highlight
-        cx.update_editor(|editor, cx| {
-            go_to_fetched_definition(editor, PointForPosition::valid(hover_point), false, cx);
-        });
-        // Assert selection moved to to definition
-        cx.lsp
-            .handle_request::<GotoDefinition, _, _>(move |_, _| async move {
-                // Empty definition response to make sure we aren't hitting the lsp and using
-                // the cached location instead
-                Ok(Some(lsp::GotoDefinitionResponse::Link(vec![])))
-            });
-        cx.background_executor.run_until_parked();
-        cx.assert_editor_state(indoc! {"
-                fn «testˇ»() { do_work(); }
-                fn do_work() { test(); }
-            "});
-
-        // Assert no link highlights after jump
-        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
-                fn test() { do_work(); }
-                fn do_work() { test(); }
-            "});
-
-        // Cmd click without existing definition requests and jumps
-        let hover_point = cx.display_point(indoc! {"
-                fn test() { do_wˇork(); }
-                fn do_work() { test(); }
-            "});
-        let target_range = cx.lsp_range(indoc! {"
-                fn test() { do_work(); }
-                fn «do_work»() { test(); }
-            "});
-
-        let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
-            Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
-                lsp::LocationLink {
-                    origin_selection_range: None,
-                    target_uri: url,
-                    target_range,
-                    target_selection_range: target_range,
-                },
-            ])))
-        });
-        cx.update_editor(|editor, cx| {
-            go_to_fetched_definition(editor, PointForPosition::valid(hover_point), false, cx);
-        });
-        requests.next().await;
-        cx.background_executor.run_until_parked();
-        cx.assert_editor_state(indoc! {"
-                fn test() { do_work(); }
-                fn «do_workˇ»() { test(); }
-            "});
-
-        // 1. We have a pending selection, mouse point is over a symbol that we have a response for, hitting cmd and nothing happens
-        // 2. Selection is completed, hovering
-        let hover_point = cx.display_point(indoc! {"
-                fn test() { do_wˇork(); }
-                fn do_work() { test(); }
-            "});
-        let target_range = cx.lsp_range(indoc! {"
-                fn test() { do_work(); }
-                fn «do_work»() { test(); }
-            "});
-        let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
-            Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
-                lsp::LocationLink {
-                    origin_selection_range: None,
-                    target_uri: url,
-                    target_range,
-                    target_selection_range: target_range,
-                },
-            ])))
-        });
-
-        // create a pending selection
-        let selection_range = cx.ranges(indoc! {"
-                fn «test() { do_w»ork(); }
-                fn do_work() { test(); }
-            "})[0]
-            .clone();
-        cx.update_editor(|editor, cx| {
-            let snapshot = editor.buffer().read(cx).snapshot(cx);
-            let anchor_range = snapshot.anchor_before(selection_range.start)
-                ..snapshot.anchor_after(selection_range.end);
-            editor.change_selections(Some(crate::Autoscroll::fit()), cx, |s| {
-                s.set_pending_anchor_range(anchor_range, crate::SelectMode::Character)
-            });
-        });
-        cx.update_editor(|editor, cx| {
-            update_go_to_definition_link(
-                editor,
-                Some(GoToDefinitionTrigger::Text(hover_point)),
-                true,
-                false,
-                cx,
-            );
-        });
-        cx.background_executor.run_until_parked();
-        assert!(requests.try_next().is_err());
-        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
-                fn test() { do_work(); }
-                fn do_work() { test(); }
-            "});
-        cx.background_executor.run_until_parked();
-    }
-
-    #[gpui::test]
-    async fn test_link_go_to_inlay(cx: &mut gpui::TestAppContext) {
-        init_test(cx, |settings| {
-            settings.defaults.inlay_hints = Some(InlayHintSettings {
-                enabled: true,
-                show_type_hints: true,
-                show_parameter_hints: true,
-                show_other_hints: true,
-            })
-        });
-
-        let mut cx = EditorLspTestContext::new_rust(
-            lsp::ServerCapabilities {
-                inlay_hint_provider: Some(lsp::OneOf::Left(true)),
-                ..Default::default()
-            },
-            cx,
-        )
-        .await;
-        cx.set_state(indoc! {"
-                struct TestStruct;
-
-                fn main() {
-                    let variableˇ = TestStruct;
-                }
-            "});
-        let hint_start_offset = cx.ranges(indoc! {"
-                struct TestStruct;
-
-                fn main() {
-                    let variableˇ = TestStruct;
-                }
-            "})[0]
-            .start;
-        let hint_position = cx.to_lsp(hint_start_offset);
-        let target_range = cx.lsp_range(indoc! {"
-                struct «TestStruct»;
-
-                fn main() {
-                    let variable = TestStruct;
-                }
-            "});
-
-        let expected_uri = cx.buffer_lsp_url.clone();
-        let hint_label = ": TestStruct";
-        cx.lsp
-            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
-                let expected_uri = expected_uri.clone();
-                async move {
-                    assert_eq!(params.text_document.uri, expected_uri);
-                    Ok(Some(vec![lsp::InlayHint {
-                        position: hint_position,
-                        label: lsp::InlayHintLabel::LabelParts(vec![lsp::InlayHintLabelPart {
-                            value: hint_label.to_string(),
-                            location: Some(lsp::Location {
-                                uri: params.text_document.uri,
-                                range: target_range,
-                            }),
-                            ..Default::default()
-                        }]),
-                        kind: Some(lsp::InlayHintKind::TYPE),
-                        text_edits: None,
-                        tooltip: None,
-                        padding_left: Some(false),
-                        padding_right: Some(false),
-                        data: None,
-                    }]))
-                }
-            })
-            .next()
-            .await;
-        cx.background_executor.run_until_parked();
-        cx.update_editor(|editor, cx| {
-            let expected_layers = vec![hint_label.to_string()];
-            assert_eq!(expected_layers, cached_hint_labels(editor));
-            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
-        });
-
-        let inlay_range = cx
-            .ranges(indoc! {"
-                struct TestStruct;
-
-                fn main() {
-                    let variable« »= TestStruct;
-                }
-            "})
-            .get(0)
-            .cloned()
-            .unwrap();
-        let hint_hover_position = cx.update_editor(|editor, cx| {
-            let snapshot = editor.snapshot(cx);
-            let previous_valid = inlay_range.start.to_display_point(&snapshot);
-            let next_valid = inlay_range.end.to_display_point(&snapshot);
-            assert_eq!(previous_valid.row(), next_valid.row());
-            assert!(previous_valid.column() < next_valid.column());
-            let exact_unclipped = DisplayPoint::new(
-                previous_valid.row(),
-                previous_valid.column() + (hint_label.len() / 2) as u32,
-            );
-            PointForPosition {
-                previous_valid,
-                next_valid,
-                exact_unclipped,
-                column_overshoot_after_line_end: 0,
-            }
-        });
-        // Press cmd to trigger highlight
-        cx.update_editor(|editor, cx| {
-            update_inlay_link_and_hover_points(
-                &editor.snapshot(cx),
-                hint_hover_position,
-                editor,
-                true,
-                false,
-                cx,
-            );
-        });
-        cx.background_executor.run_until_parked();
-        cx.update_editor(|editor, cx| {
-            let snapshot = editor.snapshot(cx);
-            let actual_highlights = snapshot
-                .inlay_highlights::<LinkGoToDefinitionState>()
-                .into_iter()
-                .flat_map(|highlights| highlights.values().map(|(_, highlight)| highlight))
-                .collect::<Vec<_>>();
-
-            let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx));
-            let expected_highlight = InlayHighlight {
-                inlay: InlayId::Hint(0),
-                inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right),
-                range: 0..hint_label.len(),
-            };
-            assert_set_eq!(actual_highlights, vec![&expected_highlight]);
-        });
-
-        // Unpress cmd causes highlight to go away
-        cx.update_editor(|editor, cx| {
-            crate::element::EditorElement::modifiers_changed(
-                editor,
-                &ModifiersChangedEvent {
-                    modifiers: Modifiers {
-                        command: false,
-                        ..Default::default()
-                    },
-                    ..Default::default()
-                },
-                cx,
-            );
-        });
-        // Assert no link highlights
-        cx.update_editor(|editor, cx| {
-                let snapshot = editor.snapshot(cx);
-                let actual_ranges = snapshot
-                    .text_highlight_ranges::<LinkGoToDefinitionState>()
-                    .map(|ranges| ranges.as_ref().clone().1)
-                    .unwrap_or_default();
-
-                assert!(actual_ranges.is_empty(), "When no cmd is pressed, should have no hint label selected, but got: {actual_ranges:?}");
-            });
-
-        // Cmd+click without existing definition requests and jumps
-        cx.update_editor(|editor, cx| {
-            crate::element::EditorElement::modifiers_changed(
-                editor,
-                &ModifiersChangedEvent {
-                    modifiers: Modifiers {
-                        command: true,
-                        ..Default::default()
-                    },
-                    ..Default::default()
-                },
-                cx,
-            );
-            update_inlay_link_and_hover_points(
-                &editor.snapshot(cx),
-                hint_hover_position,
-                editor,
-                true,
-                false,
-                cx,
-            );
-        });
-        cx.background_executor.run_until_parked();
-        cx.update_editor(|editor, cx| {
-            go_to_fetched_type_definition(editor, hint_hover_position, false, cx);
-        });
-        cx.background_executor.run_until_parked();
-        cx.assert_editor_state(indoc! {"
-                struct «TestStructˇ»;
-
-                fn main() {
-                    let variable = TestStruct;
-                }
-            "});
-    }
-}

crates/editor2/src/mouse_context_menu.rs 🔗

@@ -1,110 +0,0 @@
-use crate::{
-    DisplayPoint, Editor, EditorMode, FindAllReferences, GoToDefinition, GoToTypeDefinition,
-    Rename, RevealInFinder, SelectMode, ToggleCodeActions,
-};
-use gpui::{DismissEvent, Pixels, Point, Subscription, View, ViewContext};
-
-pub struct MouseContextMenu {
-    pub(crate) position: Point<Pixels>,
-    pub(crate) context_menu: View<ui::ContextMenu>,
-    _subscription: Subscription,
-}
-
-pub fn deploy_context_menu(
-    editor: &mut Editor,
-    position: Point<Pixels>,
-    point: DisplayPoint,
-    cx: &mut ViewContext<Editor>,
-) {
-    if !editor.is_focused(cx) {
-        editor.focus(cx);
-    }
-
-    // Don't show context menu for inline editors
-    if editor.mode() != EditorMode::Full {
-        return;
-    }
-
-    // Don't show the context menu if there isn't a project associated with this editor
-    if editor.project.is_none() {
-        return;
-    }
-
-    // Move the cursor to the clicked location so that dispatched actions make sense
-    editor.change_selections(None, cx, |s| {
-        s.clear_disjoint();
-        s.set_pending_display_range(point..point, SelectMode::Character);
-    });
-
-    let context_menu = ui::ContextMenu::build(cx, |menu, _cx| {
-        menu.action("Rename Symbol", Box::new(Rename))
-            .action("Go to Definition", Box::new(GoToDefinition))
-            .action("Go to Type Definition", Box::new(GoToTypeDefinition))
-            .action("Find All References", Box::new(FindAllReferences))
-            .action(
-                "Code Actions",
-                Box::new(ToggleCodeActions {
-                    deployed_from_indicator: false,
-                }),
-            )
-            .separator()
-            .action("Reveal in Finder", Box::new(RevealInFinder))
-    });
-    let context_menu_focus = context_menu.focus_handle(cx);
-    cx.focus(&context_menu_focus);
-
-    let _subscription = cx.subscribe(&context_menu, move |this, _, _event: &DismissEvent, cx| {
-        this.mouse_context_menu.take();
-        if context_menu_focus.contains_focused(cx) {
-            this.focus(cx);
-        }
-    });
-
-    editor.mouse_context_menu = Some(MouseContextMenu {
-        position,
-        context_menu,
-        _subscription,
-    });
-    cx.notify();
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
-    use indoc::indoc;
-
-    #[gpui::test]
-    async fn test_mouse_context_menu(cx: &mut gpui::TestAppContext) {
-        init_test(cx, |_| {});
-
-        let mut cx = EditorLspTestContext::new_rust(
-            lsp::ServerCapabilities {
-                hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
-                ..Default::default()
-            },
-            cx,
-        )
-        .await;
-
-        cx.set_state(indoc! {"
-            fn teˇst() {
-                do_work();
-            }
-        "});
-        let point = cx.display_point(indoc! {"
-            fn test() {
-                do_wˇork();
-            }
-        "});
-        cx.editor(|editor, _app| assert!(editor.mouse_context_menu.is_none()));
-        cx.update_editor(|editor, cx| deploy_context_menu(editor, Default::default(), point, cx));
-
-        cx.assert_editor_state(indoc! {"
-            fn test() {
-                do_wˇork();
-            }
-        "});
-        cx.editor(|editor, _app| assert!(editor.mouse_context_menu.is_some()));
-    }
-}

crates/editor2/src/movement.rs 🔗

@@ -1,926 +0,0 @@
-use super::{Bias, DisplayPoint, DisplaySnapshot, SelectionGoal, ToDisplayPoint};
-use crate::{char_kind, CharKind, EditorStyle, ToOffset, ToPoint};
-use gpui::{px, Pixels, TextSystem};
-use language::Point;
-
-use std::{ops::Range, sync::Arc};
-
-#[derive(Debug, PartialEq)]
-pub enum FindRange {
-    SingleLine,
-    MultiLine,
-}
-
-/// TextLayoutDetails encompasses everything we need to move vertically
-/// taking into account variable width characters.
-pub struct TextLayoutDetails {
-    pub text_system: Arc<TextSystem>,
-    pub editor_style: EditorStyle,
-    pub rem_size: Pixels,
-}
-
-pub fn left(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
-    if point.column() > 0 {
-        *point.column_mut() -= 1;
-    } else if point.row() > 0 {
-        *point.row_mut() -= 1;
-        *point.column_mut() = map.line_len(point.row());
-    }
-    map.clip_point(point, Bias::Left)
-}
-
-pub fn saturating_left(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
-    if point.column() > 0 {
-        *point.column_mut() -= 1;
-    }
-    map.clip_point(point, Bias::Left)
-}
-
-pub fn right(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
-    let max_column = map.line_len(point.row());
-    if point.column() < max_column {
-        *point.column_mut() += 1;
-    } else if point.row() < map.max_point().row() {
-        *point.row_mut() += 1;
-        *point.column_mut() = 0;
-    }
-    map.clip_point(point, Bias::Right)
-}
-
-pub fn saturating_right(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
-    *point.column_mut() += 1;
-    map.clip_point(point, Bias::Right)
-}
-
-pub fn up(
-    map: &DisplaySnapshot,
-    start: DisplayPoint,
-    goal: SelectionGoal,
-    preserve_column_at_start: bool,
-    text_layout_details: &TextLayoutDetails,
-) -> (DisplayPoint, SelectionGoal) {
-    up_by_rows(
-        map,
-        start,
-        1,
-        goal,
-        preserve_column_at_start,
-        text_layout_details,
-    )
-}
-
-pub fn down(
-    map: &DisplaySnapshot,
-    start: DisplayPoint,
-    goal: SelectionGoal,
-    preserve_column_at_end: bool,
-    text_layout_details: &TextLayoutDetails,
-) -> (DisplayPoint, SelectionGoal) {
-    down_by_rows(
-        map,
-        start,
-        1,
-        goal,
-        preserve_column_at_end,
-        text_layout_details,
-    )
-}
-
-pub fn up_by_rows(
-    map: &DisplaySnapshot,
-    start: DisplayPoint,
-    row_count: u32,
-    goal: SelectionGoal,
-    preserve_column_at_start: bool,
-    text_layout_details: &TextLayoutDetails,
-) -> (DisplayPoint, SelectionGoal) {
-    let mut goal_x = match goal {
-        SelectionGoal::HorizontalPosition(x) => x.into(), // todo!("Can the fields in SelectionGoal by Pixels? We should extract a geometry crate and depend on that.")
-        SelectionGoal::WrappedHorizontalPosition((_, x)) => x.into(),
-        SelectionGoal::HorizontalRange { end, .. } => end.into(),
-        _ => map.x_for_display_point(start, text_layout_details),
-    };
-
-    let prev_row = start.row().saturating_sub(row_count);
-    let mut point = map.clip_point(
-        DisplayPoint::new(prev_row, map.line_len(prev_row)),
-        Bias::Left,
-    );
-    if point.row() < start.row() {
-        *point.column_mut() = map.display_column_for_x(point.row(), goal_x, text_layout_details)
-    } else if preserve_column_at_start {
-        return (start, goal);
-    } else {
-        point = DisplayPoint::new(0, 0);
-        goal_x = px(0.);
-    }
-
-    let mut clipped_point = map.clip_point(point, Bias::Left);
-    if clipped_point.row() < point.row() {
-        clipped_point = map.clip_point(point, Bias::Right);
-    }
-    (
-        clipped_point,
-        SelectionGoal::HorizontalPosition(goal_x.into()),
-    )
-}
-
-pub fn down_by_rows(
-    map: &DisplaySnapshot,
-    start: DisplayPoint,
-    row_count: u32,
-    goal: SelectionGoal,
-    preserve_column_at_end: bool,
-    text_layout_details: &TextLayoutDetails,
-) -> (DisplayPoint, SelectionGoal) {
-    let mut goal_x = match goal {
-        SelectionGoal::HorizontalPosition(x) => x.into(),
-        SelectionGoal::WrappedHorizontalPosition((_, x)) => x.into(),
-        SelectionGoal::HorizontalRange { end, .. } => end.into(),
-        _ => map.x_for_display_point(start, text_layout_details),
-    };
-
-    let new_row = start.row() + row_count;
-    let mut point = map.clip_point(DisplayPoint::new(new_row, 0), Bias::Right);
-    if point.row() > start.row() {
-        *point.column_mut() = map.display_column_for_x(point.row(), goal_x, text_layout_details)
-    } else if preserve_column_at_end {
-        return (start, goal);
-    } else {
-        point = map.max_point();
-        goal_x = map.x_for_display_point(point, text_layout_details)
-    }
-
-    let mut clipped_point = map.clip_point(point, Bias::Right);
-    if clipped_point.row() > point.row() {
-        clipped_point = map.clip_point(point, Bias::Left);
-    }
-    (
-        clipped_point,
-        SelectionGoal::HorizontalPosition(goal_x.into()),
-    )
-}
-
-pub fn line_beginning(
-    map: &DisplaySnapshot,
-    display_point: DisplayPoint,
-    stop_at_soft_boundaries: bool,
-) -> DisplayPoint {
-    let point = display_point.to_point(map);
-    let soft_line_start = map.clip_point(DisplayPoint::new(display_point.row(), 0), Bias::Right);
-    let line_start = map.prev_line_boundary(point).1;
-
-    if stop_at_soft_boundaries && display_point != soft_line_start {
-        soft_line_start
-    } else {
-        line_start
-    }
-}
-
-pub fn indented_line_beginning(
-    map: &DisplaySnapshot,
-    display_point: DisplayPoint,
-    stop_at_soft_boundaries: bool,
-) -> DisplayPoint {
-    let point = display_point.to_point(map);
-    let soft_line_start = map.clip_point(DisplayPoint::new(display_point.row(), 0), Bias::Right);
-    let indent_start = Point::new(
-        point.row,
-        map.buffer_snapshot.indent_size_for_line(point.row).len,
-    )
-    .to_display_point(map);
-    let line_start = map.prev_line_boundary(point).1;
-
-    if stop_at_soft_boundaries && soft_line_start > indent_start && display_point != soft_line_start
-    {
-        soft_line_start
-    } else if stop_at_soft_boundaries && display_point != indent_start {
-        indent_start
-    } else {
-        line_start
-    }
-}
-
-pub fn line_end(
-    map: &DisplaySnapshot,
-    display_point: DisplayPoint,
-    stop_at_soft_boundaries: bool,
-) -> DisplayPoint {
-    let soft_line_end = map.clip_point(
-        DisplayPoint::new(display_point.row(), map.line_len(display_point.row())),
-        Bias::Left,
-    );
-    if stop_at_soft_boundaries && display_point != soft_line_end {
-        soft_line_end
-    } else {
-        map.next_line_boundary(display_point.to_point(map)).1
-    }
-}
-
-pub fn previous_word_start(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
-    let raw_point = point.to_point(map);
-    let scope = map.buffer_snapshot.language_scope_at(raw_point);
-
-    find_preceding_boundary(map, point, FindRange::MultiLine, |left, right| {
-        (char_kind(&scope, left) != char_kind(&scope, right) && !right.is_whitespace())
-            || left == '\n'
-    })
-}
-
-pub fn previous_subword_start(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
-    let raw_point = point.to_point(map);
-    let scope = map.buffer_snapshot.language_scope_at(raw_point);
-
-    find_preceding_boundary(map, point, FindRange::MultiLine, |left, right| {
-        let is_word_start =
-            char_kind(&scope, left) != char_kind(&scope, right) && !right.is_whitespace();
-        let is_subword_start =
-            left == '_' && right != '_' || left.is_lowercase() && right.is_uppercase();
-        is_word_start || is_subword_start || left == '\n'
-    })
-}
-
-pub fn next_word_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
-    let raw_point = point.to_point(map);
-    let scope = map.buffer_snapshot.language_scope_at(raw_point);
-
-    find_boundary(map, point, FindRange::MultiLine, |left, right| {
-        (char_kind(&scope, left) != char_kind(&scope, right) && !left.is_whitespace())
-            || right == '\n'
-    })
-}
-
-pub fn next_subword_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
-    let raw_point = point.to_point(map);
-    let scope = map.buffer_snapshot.language_scope_at(raw_point);
-
-    find_boundary(map, point, FindRange::MultiLine, |left, right| {
-        let is_word_end =
-            (char_kind(&scope, left) != char_kind(&scope, right)) && !left.is_whitespace();
-        let is_subword_end =
-            left != '_' && right == '_' || left.is_lowercase() && right.is_uppercase();
-        is_word_end || is_subword_end || right == '\n'
-    })
-}
-
-pub fn start_of_paragraph(
-    map: &DisplaySnapshot,
-    display_point: DisplayPoint,
-    mut count: usize,
-) -> DisplayPoint {
-    let point = display_point.to_point(map);
-    if point.row == 0 {
-        return DisplayPoint::zero();
-    }
-
-    let mut found_non_blank_line = false;
-    for row in (0..point.row + 1).rev() {
-        let blank = map.buffer_snapshot.is_line_blank(row);
-        if found_non_blank_line && blank {
-            if count <= 1 {
-                return Point::new(row, 0).to_display_point(map);
-            }
-            count -= 1;
-            found_non_blank_line = false;
-        }
-
-        found_non_blank_line |= !blank;
-    }
-
-    DisplayPoint::zero()
-}
-
-pub fn end_of_paragraph(
-    map: &DisplaySnapshot,
-    display_point: DisplayPoint,
-    mut count: usize,
-) -> DisplayPoint {
-    let point = display_point.to_point(map);
-    if point.row == map.max_buffer_row() {
-        return map.max_point();
-    }
-
-    let mut found_non_blank_line = false;
-    for row in point.row..map.max_buffer_row() + 1 {
-        let blank = map.buffer_snapshot.is_line_blank(row);
-        if found_non_blank_line && blank {
-            if count <= 1 {
-                return Point::new(row, 0).to_display_point(map);
-            }
-            count -= 1;
-            found_non_blank_line = false;
-        }
-
-        found_non_blank_line |= !blank;
-    }
-
-    map.max_point()
-}
-
-/// Scans for a boundary preceding the given start point `from` until a boundary is found,
-/// indicated by the given predicate returning true.
-/// The predicate is called with the character to the left and right of the candidate boundary location.
-/// If FindRange::SingleLine is specified and no boundary is found before the start of the current line, the start of the current line will be returned.
-pub fn find_preceding_boundary(
-    map: &DisplaySnapshot,
-    from: DisplayPoint,
-    find_range: FindRange,
-    mut is_boundary: impl FnMut(char, char) -> bool,
-) -> DisplayPoint {
-    let mut prev_ch = None;
-    let mut offset = from.to_point(map).to_offset(&map.buffer_snapshot);
-
-    for ch in map.buffer_snapshot.reversed_chars_at(offset) {
-        if find_range == FindRange::SingleLine && ch == '\n' {
-            break;
-        }
-        if let Some(prev_ch) = prev_ch {
-            if is_boundary(ch, prev_ch) {
-                break;
-            }
-        }
-
-        offset -= ch.len_utf8();
-        prev_ch = Some(ch);
-    }
-
-    map.clip_point(offset.to_display_point(map), Bias::Left)
-}
-
-/// Scans for a boundary following the given start point until a boundary is found, indicated by the
-/// given predicate returning true. The predicate is called with the character to the left and right
-/// of the candidate boundary location, and will be called with `\n` characters indicating the start
-/// or end of a line.
-pub fn find_boundary(
-    map: &DisplaySnapshot,
-    from: DisplayPoint,
-    find_range: FindRange,
-    mut is_boundary: impl FnMut(char, char) -> bool,
-) -> DisplayPoint {
-    let mut offset = from.to_offset(&map, Bias::Right);
-    let mut prev_ch = None;
-
-    for ch in map.buffer_snapshot.chars_at(offset) {
-        if find_range == FindRange::SingleLine && ch == '\n' {
-            break;
-        }
-        if let Some(prev_ch) = prev_ch {
-            if is_boundary(prev_ch, ch) {
-                break;
-            }
-        }
-
-        offset += ch.len_utf8();
-        prev_ch = Some(ch);
-    }
-    map.clip_point(offset.to_display_point(map), Bias::Right)
-}
-
-pub fn chars_after(
-    map: &DisplaySnapshot,
-    mut offset: usize,
-) -> impl Iterator<Item = (char, Range<usize>)> + '_ {
-    map.buffer_snapshot.chars_at(offset).map(move |ch| {
-        let before = offset;
-        offset = offset + ch.len_utf8();
-        (ch, before..offset)
-    })
-}
-
-pub fn chars_before(
-    map: &DisplaySnapshot,
-    mut offset: usize,
-) -> impl Iterator<Item = (char, Range<usize>)> + '_ {
-    map.buffer_snapshot
-        .reversed_chars_at(offset)
-        .map(move |ch| {
-            let after = offset;
-            offset = offset - ch.len_utf8();
-            (ch, offset..after)
-        })
-}
-
-pub fn is_inside_word(map: &DisplaySnapshot, point: DisplayPoint) -> bool {
-    let raw_point = point.to_point(map);
-    let scope = map.buffer_snapshot.language_scope_at(raw_point);
-    let ix = map.clip_point(point, Bias::Left).to_offset(map, Bias::Left);
-    let text = &map.buffer_snapshot;
-    let next_char_kind = text.chars_at(ix).next().map(|c| char_kind(&scope, c));
-    let prev_char_kind = text
-        .reversed_chars_at(ix)
-        .next()
-        .map(|c| char_kind(&scope, c));
-    prev_char_kind.zip(next_char_kind) == Some((CharKind::Word, CharKind::Word))
-}
-
-pub fn surrounding_word(map: &DisplaySnapshot, position: DisplayPoint) -> Range<DisplayPoint> {
-    let position = map
-        .clip_point(position, Bias::Left)
-        .to_offset(map, Bias::Left);
-    let (range, _) = map.buffer_snapshot.surrounding_word(position);
-    let start = range
-        .start
-        .to_point(&map.buffer_snapshot)
-        .to_display_point(map);
-    let end = range
-        .end
-        .to_point(&map.buffer_snapshot)
-        .to_display_point(map);
-    start..end
-}
-
-pub fn split_display_range_by_lines(
-    map: &DisplaySnapshot,
-    range: Range<DisplayPoint>,
-) -> Vec<Range<DisplayPoint>> {
-    let mut result = Vec::new();
-
-    let mut start = range.start;
-    // Loop over all the covered rows until the one containing the range end
-    for row in range.start.row()..range.end.row() {
-        let row_end_column = map.line_len(row);
-        let end = map.clip_point(DisplayPoint::new(row, row_end_column), Bias::Left);
-        if start != end {
-            result.push(start..end);
-        }
-        start = map.clip_point(DisplayPoint::new(row + 1, 0), Bias::Left);
-    }
-
-    // Add the final range from the start of the last end to the original range end.
-    result.push(start..range.end);
-
-    result
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::{
-        display_map::Inlay,
-        test::{editor_test_context::EditorTestContext, marked_display_snapshot},
-        Buffer, DisplayMap, ExcerptRange, InlayId, MultiBuffer,
-    };
-    use gpui::{font, Context as _};
-    use project::Project;
-    use settings::SettingsStore;
-    use util::post_inc;
-
-    #[gpui::test]
-    fn test_previous_word_start(cx: &mut gpui::AppContext) {
-        init_test(cx);
-
-        fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
-            let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
-            assert_eq!(
-                previous_word_start(&snapshot, display_points[1]),
-                display_points[0]
-            );
-        }
-
-        assert("\nˇ   ˇlorem", cx);
-        assert("ˇ\nˇ   lorem", cx);
-        assert("    ˇloremˇ", cx);
-        assert("ˇ    ˇlorem", cx);
-        assert("    ˇlorˇem", cx);
-        assert("\nlorem\nˇ   ˇipsum", cx);
-        assert("\n\nˇ\nˇ", cx);
-        assert("    ˇlorem  ˇipsum", cx);
-        assert("loremˇ-ˇipsum", cx);
-        assert("loremˇ-#$@ˇipsum", cx);
-        assert("ˇlorem_ˇipsum", cx);
-        assert(" ˇdefγˇ", cx);
-        assert(" ˇbcΔˇ", cx);
-        assert(" abˇ——ˇcd", cx);
-    }
-
-    #[gpui::test]
-    fn test_previous_subword_start(cx: &mut gpui::AppContext) {
-        init_test(cx);
-
-        fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
-            let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
-            assert_eq!(
-                previous_subword_start(&snapshot, display_points[1]),
-                display_points[0]
-            );
-        }
-
-        // Subword boundaries are respected
-        assert("lorem_ˇipˇsum", cx);
-        assert("lorem_ˇipsumˇ", cx);
-        assert("ˇlorem_ˇipsum", cx);
-        assert("lorem_ˇipsum_ˇdolor", cx);
-        assert("loremˇIpˇsum", cx);
-        assert("loremˇIpsumˇ", cx);
-
-        // Word boundaries are still respected
-        assert("\nˇ   ˇlorem", cx);
-        assert("    ˇloremˇ", cx);
-        assert("    ˇlorˇem", cx);
-        assert("\nlorem\nˇ   ˇipsum", cx);
-        assert("\n\nˇ\nˇ", cx);
-        assert("    ˇlorem  ˇipsum", cx);
-        assert("loremˇ-ˇipsum", cx);
-        assert("loremˇ-#$@ˇipsum", cx);
-        assert(" ˇdefγˇ", cx);
-        assert(" bcˇΔˇ", cx);
-        assert(" ˇbcδˇ", cx);
-        assert(" abˇ——ˇcd", cx);
-    }
-
-    #[gpui::test]
-    fn test_find_preceding_boundary(cx: &mut gpui::AppContext) {
-        init_test(cx);
-
-        fn assert(
-            marked_text: &str,
-            cx: &mut gpui::AppContext,
-            is_boundary: impl FnMut(char, char) -> bool,
-        ) {
-            let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
-            assert_eq!(
-                find_preceding_boundary(
-                    &snapshot,
-                    display_points[1],
-                    FindRange::MultiLine,
-                    is_boundary
-                ),
-                display_points[0]
-            );
-        }
-
-        assert("abcˇdef\ngh\nijˇk", cx, |left, right| {
-            left == 'c' && right == 'd'
-        });
-        assert("abcdef\nˇgh\nijˇk", cx, |left, right| {
-            left == '\n' && right == 'g'
-        });
-        let mut line_count = 0;
-        assert("abcdef\nˇgh\nijˇk", cx, |left, _| {
-            if left == '\n' {
-                line_count += 1;
-                line_count == 2
-            } else {
-                false
-            }
-        });
-    }
-
-    #[gpui::test]
-    fn test_find_preceding_boundary_with_inlays(cx: &mut gpui::AppContext) {
-        init_test(cx);
-
-        let input_text = "abcdefghijklmnopqrstuvwxys";
-        let font = font("Helvetica");
-        let font_size = px(14.0);
-        let buffer = MultiBuffer::build_simple(input_text, cx);
-        let buffer_snapshot = buffer.read(cx).snapshot(cx);
-        let display_map =
-            cx.new_model(|cx| DisplayMap::new(buffer, font, font_size, None, 1, 1, cx));
-
-        // add all kinds of inlays between two word boundaries: we should be able to cross them all, when looking for another boundary
-        let mut id = 0;
-        let inlays = (0..buffer_snapshot.len())
-            .map(|offset| {
-                [
-                    Inlay {
-                        id: InlayId::Suggestion(post_inc(&mut id)),
-                        position: buffer_snapshot.anchor_at(offset, Bias::Left),
-                        text: format!("test").into(),
-                    },
-                    Inlay {
-                        id: InlayId::Suggestion(post_inc(&mut id)),
-                        position: buffer_snapshot.anchor_at(offset, Bias::Right),
-                        text: format!("test").into(),
-                    },
-                    Inlay {
-                        id: InlayId::Hint(post_inc(&mut id)),
-                        position: buffer_snapshot.anchor_at(offset, Bias::Left),
-                        text: format!("test").into(),
-                    },
-                    Inlay {
-                        id: InlayId::Hint(post_inc(&mut id)),
-                        position: buffer_snapshot.anchor_at(offset, Bias::Right),
-                        text: format!("test").into(),
-                    },
-                ]
-            })
-            .flatten()
-            .collect();
-        let snapshot = display_map.update(cx, |map, cx| {
-            map.splice_inlays(Vec::new(), inlays, cx);
-            map.snapshot(cx)
-        });
-
-        assert_eq!(
-            find_preceding_boundary(
-                &snapshot,
-                buffer_snapshot.len().to_display_point(&snapshot),
-                FindRange::MultiLine,
-                |left, _| left == 'e',
-            ),
-            snapshot
-                .buffer_snapshot
-                .offset_to_point(5)
-                .to_display_point(&snapshot),
-            "Should not stop at inlays when looking for boundaries"
-        );
-    }
-
-    #[gpui::test]
-    fn test_next_word_end(cx: &mut gpui::AppContext) {
-        init_test(cx);
-
-        fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
-            let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
-            assert_eq!(
-                next_word_end(&snapshot, display_points[0]),
-                display_points[1]
-            );
-        }
-
-        assert("\nˇ   loremˇ", cx);
-        assert("    ˇloremˇ", cx);
-        assert("    lorˇemˇ", cx);
-        assert("    loremˇ    ˇ\nipsum\n", cx);
-        assert("\nˇ\nˇ\n\n", cx);
-        assert("loremˇ    ipsumˇ   ", cx);
-        assert("loremˇ-ˇipsum", cx);
-        assert("loremˇ#$@-ˇipsum", cx);
-        assert("loremˇ_ipsumˇ", cx);
-        assert(" ˇbcΔˇ", cx);
-        assert(" abˇ——ˇcd", cx);
-    }
-
-    #[gpui::test]
-    fn test_next_subword_end(cx: &mut gpui::AppContext) {
-        init_test(cx);
-
-        fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
-            let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
-            assert_eq!(
-                next_subword_end(&snapshot, display_points[0]),
-                display_points[1]
-            );
-        }
-
-        // Subword boundaries are respected
-        assert("loˇremˇ_ipsum", cx);
-        assert("ˇloremˇ_ipsum", cx);
-        assert("loremˇ_ipsumˇ", cx);
-        assert("loremˇ_ipsumˇ_dolor", cx);
-        assert("loˇremˇIpsum", cx);
-        assert("loremˇIpsumˇDolor", cx);
-
-        // Word boundaries are still respected
-        assert("\nˇ   loremˇ", cx);
-        assert("    ˇloremˇ", cx);
-        assert("    lorˇemˇ", cx);
-        assert("    loremˇ    ˇ\nipsum\n", cx);
-        assert("\nˇ\nˇ\n\n", cx);
-        assert("loremˇ    ipsumˇ   ", cx);
-        assert("loremˇ-ˇipsum", cx);
-        assert("loremˇ#$@-ˇipsum", cx);
-        assert("loremˇ_ipsumˇ", cx);
-        assert(" ˇbcˇΔ", cx);
-        assert(" abˇ——ˇcd", cx);
-    }
-
-    #[gpui::test]
-    fn test_find_boundary(cx: &mut gpui::AppContext) {
-        init_test(cx);
-
-        fn assert(
-            marked_text: &str,
-            cx: &mut gpui::AppContext,
-            is_boundary: impl FnMut(char, char) -> bool,
-        ) {
-            let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
-            assert_eq!(
-                find_boundary(
-                    &snapshot,
-                    display_points[0],
-                    FindRange::MultiLine,
-                    is_boundary
-                ),
-                display_points[1]
-            );
-        }
-
-        assert("abcˇdef\ngh\nijˇk", cx, |left, right| {
-            left == 'j' && right == 'k'
-        });
-        assert("abˇcdef\ngh\nˇijk", cx, |left, right| {
-            left == '\n' && right == 'i'
-        });
-        let mut line_count = 0;
-        assert("abcˇdef\ngh\nˇijk", cx, |left, _| {
-            if left == '\n' {
-                line_count += 1;
-                line_count == 2
-            } else {
-                false
-            }
-        });
-    }
-
-    #[gpui::test]
-    fn test_surrounding_word(cx: &mut gpui::AppContext) {
-        init_test(cx);
-
-        fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
-            let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
-            assert_eq!(
-                surrounding_word(&snapshot, display_points[1]),
-                display_points[0]..display_points[2],
-                "{}",
-                marked_text.to_string()
-            );
-        }
-
-        assert("ˇˇloremˇ  ipsum", cx);
-        assert("ˇloˇremˇ  ipsum", cx);
-        assert("ˇloremˇˇ  ipsum", cx);
-        assert("loremˇ ˇ  ˇipsum", cx);
-        assert("lorem\nˇˇˇ\nipsum", cx);
-        assert("lorem\nˇˇipsumˇ", cx);
-        assert("loremˇ,ˇˇ ipsum", cx);
-        assert("ˇloremˇˇ, ipsum", cx);
-    }
-
-    #[gpui::test]
-    async fn test_move_up_and_down_with_excerpts(cx: &mut gpui::TestAppContext) {
-        cx.update(|cx| {
-            init_test(cx);
-        });
-
-        let mut cx = EditorTestContext::new(cx).await;
-        let editor = cx.editor.clone();
-        let window = cx.window.clone();
-        _ = cx.update_window(window, |_, cx| {
-            let text_layout_details =
-                editor.update(cx, |editor, cx| editor.text_layout_details(cx));
-
-            let font = font("Helvetica");
-
-            let buffer =
-                cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abc\ndefg\nhijkl\nmn"));
-            let multibuffer = cx.new_model(|cx| {
-                let mut multibuffer = MultiBuffer::new(0);
-                multibuffer.push_excerpts(
-                    buffer.clone(),
-                    [
-                        ExcerptRange {
-                            context: Point::new(0, 0)..Point::new(1, 4),
-                            primary: None,
-                        },
-                        ExcerptRange {
-                            context: Point::new(2, 0)..Point::new(3, 2),
-                            primary: None,
-                        },
-                    ],
-                    cx,
-                );
-                multibuffer
-            });
-            let display_map =
-                cx.new_model(|cx| DisplayMap::new(multibuffer, font, px(14.0), None, 2, 2, cx));
-            let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
-
-            assert_eq!(snapshot.text(), "\n\nabc\ndefg\n\n\nhijkl\nmn");
-
-            let col_2_x =
-                snapshot.x_for_display_point(DisplayPoint::new(2, 2), &text_layout_details);
-
-            // Can't move up into the first excerpt's header
-            assert_eq!(
-                up(
-                    &snapshot,
-                    DisplayPoint::new(2, 2),
-                    SelectionGoal::HorizontalPosition(col_2_x.0),
-                    false,
-                    &text_layout_details
-                ),
-                (
-                    DisplayPoint::new(2, 0),
-                    SelectionGoal::HorizontalPosition(0.0)
-                ),
-            );
-            assert_eq!(
-                up(
-                    &snapshot,
-                    DisplayPoint::new(2, 0),
-                    SelectionGoal::None,
-                    false,
-                    &text_layout_details
-                ),
-                (
-                    DisplayPoint::new(2, 0),
-                    SelectionGoal::HorizontalPosition(0.0)
-                ),
-            );
-
-            let col_4_x =
-                snapshot.x_for_display_point(DisplayPoint::new(3, 4), &text_layout_details);
-
-            // Move up and down within first excerpt
-            assert_eq!(
-                up(
-                    &snapshot,
-                    DisplayPoint::new(3, 4),
-                    SelectionGoal::HorizontalPosition(col_4_x.0),
-                    false,
-                    &text_layout_details
-                ),
-                (
-                    DisplayPoint::new(2, 3),
-                    SelectionGoal::HorizontalPosition(col_4_x.0)
-                ),
-            );
-            assert_eq!(
-                down(
-                    &snapshot,
-                    DisplayPoint::new(2, 3),
-                    SelectionGoal::HorizontalPosition(col_4_x.0),
-                    false,
-                    &text_layout_details
-                ),
-                (
-                    DisplayPoint::new(3, 4),
-                    SelectionGoal::HorizontalPosition(col_4_x.0)
-                ),
-            );
-
-            let col_5_x =
-                snapshot.x_for_display_point(DisplayPoint::new(6, 5), &text_layout_details);
-
-            // Move up and down across second excerpt's header
-            assert_eq!(
-                up(
-                    &snapshot,
-                    DisplayPoint::new(6, 5),
-                    SelectionGoal::HorizontalPosition(col_5_x.0),
-                    false,
-                    &text_layout_details
-                ),
-                (
-                    DisplayPoint::new(3, 4),
-                    SelectionGoal::HorizontalPosition(col_5_x.0)
-                ),
-            );
-            assert_eq!(
-                down(
-                    &snapshot,
-                    DisplayPoint::new(3, 4),
-                    SelectionGoal::HorizontalPosition(col_5_x.0),
-                    false,
-                    &text_layout_details
-                ),
-                (
-                    DisplayPoint::new(6, 5),
-                    SelectionGoal::HorizontalPosition(col_5_x.0)
-                ),
-            );
-
-            let max_point_x =
-                snapshot.x_for_display_point(DisplayPoint::new(7, 2), &text_layout_details);
-
-            // Can't move down off the end
-            assert_eq!(
-                down(
-                    &snapshot,
-                    DisplayPoint::new(7, 0),
-                    SelectionGoal::HorizontalPosition(0.0),
-                    false,
-                    &text_layout_details
-                ),
-                (
-                    DisplayPoint::new(7, 2),
-                    SelectionGoal::HorizontalPosition(max_point_x.0)
-                ),
-            );
-            assert_eq!(
-                down(
-                    &snapshot,
-                    DisplayPoint::new(7, 2),
-                    SelectionGoal::HorizontalPosition(max_point_x.0),
-                    false,
-                    &text_layout_details
-                ),
-                (
-                    DisplayPoint::new(7, 2),
-                    SelectionGoal::HorizontalPosition(max_point_x.0)
-                ),
-            );
-        });
-    }
-
-    fn init_test(cx: &mut gpui::AppContext) {
-        let settings_store = SettingsStore::test(cx);
-        cx.set_global(settings_store);
-        theme::init(theme::LoadThemes::JustBase, cx);
-        language::init(cx);
-        crate::init(cx);
-        Project::init_settings(cx);
-    }
-}

crates/editor2/src/persistence.rs 🔗

@@ -1,83 +0,0 @@
-use std::path::PathBuf;
-
-use db::sqlez_macros::sql;
-use db::{define_connection, query};
-
-use workspace::{ItemId, WorkspaceDb, WorkspaceId};
-
-define_connection!(
-    // Current schema shape using pseudo-rust syntax:
-    // editors(
-    //   item_id: usize,
-    //   workspace_id: usize,
-    //   path: PathBuf,
-    //   scroll_top_row: usize,
-    //   scroll_vertical_offset: f32,
-    //   scroll_horizontal_offset: f32,
-    // )
-    pub static ref DB: EditorDb<WorkspaceDb> =
-        &[sql! (
-            CREATE TABLE editors(
-                item_id INTEGER NOT NULL,
-                workspace_id INTEGER NOT NULL,
-                path BLOB NOT NULL,
-                PRIMARY KEY(item_id, workspace_id),
-                FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
-                ON DELETE CASCADE
-                ON UPDATE CASCADE
-            ) STRICT;
-        ),
-        sql! (
-            ALTER TABLE editors ADD COLUMN scroll_top_row INTEGER NOT NULL DEFAULT 0;
-            ALTER TABLE editors ADD COLUMN scroll_horizontal_offset REAL NOT NULL DEFAULT 0;
-            ALTER TABLE editors ADD COLUMN scroll_vertical_offset REAL NOT NULL DEFAULT 0;
-        )];
-);
-
-impl EditorDb {
-    query! {
-        pub fn get_path(item_id: ItemId, workspace_id: WorkspaceId) -> Result<Option<PathBuf>> {
-            SELECT path FROM editors
-            WHERE item_id = ? AND workspace_id = ?
-        }
-    }
-
-    query! {
-        pub async fn save_path(item_id: ItemId, workspace_id: WorkspaceId, path: PathBuf) -> Result<()> {
-            INSERT INTO editors
-                (item_id, workspace_id, path)
-            VALUES
-                (?1, ?2, ?3)
-            ON CONFLICT DO UPDATE SET
-                item_id = ?1,
-                workspace_id = ?2,
-                path = ?3
-        }
-    }
-
-    // Returns the scroll top row, and offset
-    query! {
-        pub fn get_scroll_position(item_id: ItemId, workspace_id: WorkspaceId) -> Result<Option<(u32, f32, f32)>> {
-            SELECT scroll_top_row, scroll_horizontal_offset, scroll_vertical_offset
-            FROM editors
-            WHERE item_id = ? AND workspace_id = ?
-        }
-    }
-
-    query! {
-        pub async fn save_scroll_position(
-            item_id: ItemId,
-            workspace_id: WorkspaceId,
-            top_row: u32,
-            vertical_offset: f32,
-            horizontal_offset: f32
-        ) -> Result<()> {
-            UPDATE OR IGNORE editors
-            SET
-                scroll_top_row = ?3,
-                scroll_horizontal_offset = ?4,
-                scroll_vertical_offset = ?5
-            WHERE item_id = ?1 AND workspace_id = ?2
-        }
-    }
-}

crates/editor2/src/rust_analyzer_ext.rs 🔗

@@ -1,119 +0,0 @@
-use std::sync::Arc;
-
-use anyhow::Context as _;
-use gpui::{Context, View, ViewContext, VisualContext, WindowContext};
-use language::Language;
-use multi_buffer::MultiBuffer;
-use project::lsp_ext_command::ExpandMacro;
-use text::ToPointUtf16;
-
-use crate::{element::register_action, Editor, ExpandMacroRecursively};
-
-pub fn apply_related_actions(editor: &View<Editor>, cx: &mut WindowContext) {
-    let is_rust_related = editor.update(cx, |editor, cx| {
-        editor
-            .buffer()
-            .read(cx)
-            .all_buffers()
-            .iter()
-            .any(|b| match b.read(cx).language() {
-                Some(l) => is_rust_language(l),
-                None => false,
-            })
-    });
-
-    if is_rust_related {
-        register_action(editor, cx, expand_macro_recursively);
-    }
-}
-
-pub fn expand_macro_recursively(
-    editor: &mut Editor,
-    _: &ExpandMacroRecursively,
-    cx: &mut ViewContext<'_, Editor>,
-) {
-    if editor.selections.count() == 0 {
-        return;
-    }
-    let Some(project) = &editor.project else {
-        return;
-    };
-    let Some(workspace) = editor.workspace() else {
-        return;
-    };
-
-    let multibuffer = editor.buffer().read(cx);
-
-    let Some((trigger_anchor, rust_language, server_to_query, buffer)) = editor
-        .selections
-        .disjoint_anchors()
-        .into_iter()
-        .filter(|selection| selection.start == selection.end)
-        .filter_map(|selection| Some((selection.start.buffer_id?, selection.start)))
-        .filter_map(|(buffer_id, trigger_anchor)| {
-            let buffer = multibuffer.buffer(buffer_id)?;
-            let rust_language = buffer.read(cx).language_at(trigger_anchor.text_anchor)?;
-            if !is_rust_language(&rust_language) {
-                return None;
-            }
-            Some((trigger_anchor, rust_language, buffer))
-        })
-        .find_map(|(trigger_anchor, rust_language, buffer)| {
-            project
-                .read(cx)
-                .language_servers_for_buffer(buffer.read(cx), cx)
-                .into_iter()
-                .find_map(|(adapter, server)| {
-                    if adapter.name.0.as_ref() == "rust-analyzer" {
-                        Some((
-                            trigger_anchor,
-                            Arc::clone(&rust_language),
-                            server.server_id(),
-                            buffer.clone(),
-                        ))
-                    } else {
-                        None
-                    }
-                })
-        })
-    else {
-        return;
-    };
-
-    let project = project.clone();
-    let buffer_snapshot = buffer.read(cx).snapshot();
-    let position = trigger_anchor.text_anchor.to_point_utf16(&buffer_snapshot);
-    let expand_macro_task = project.update(cx, |project, cx| {
-        project.request_lsp(
-            buffer,
-            project::LanguageServerToQuery::Other(server_to_query),
-            ExpandMacro { position },
-            cx,
-        )
-    });
-    cx.spawn(|_editor, mut cx| async move {
-        let macro_expansion = expand_macro_task.await.context("expand macro")?;
-        if macro_expansion.is_empty() {
-            log::info!("Empty macro expansion for position {position:?}");
-            return Ok(());
-        }
-
-        let buffer = project.update(&mut cx, |project, cx| {
-            project.create_buffer(&macro_expansion.expansion, Some(rust_language), cx)
-        })??;
-        workspace.update(&mut cx, |workspace, cx| {
-            let buffer = cx.new_model(|cx| {
-                MultiBuffer::singleton(buffer, cx).with_title(macro_expansion.name)
-            });
-            workspace.add_item(
-                Box::new(cx.new_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx))),
-                cx,
-            );
-        })
-    })
-    .detach_and_log_err(cx);
-}
-
-fn is_rust_language(language: &Language) -> bool {
-    language.name().as_ref() == "Rust"
-}

crates/editor2/src/scroll.rs 🔗

@@ -1,460 +0,0 @@
-pub mod actions;
-pub mod autoscroll;
-pub mod scroll_amount;
-
-use crate::{
-    display_map::{DisplaySnapshot, ToDisplayPoint},
-    hover_popover::hide_hover,
-    persistence::DB,
-    Anchor, DisplayPoint, Editor, EditorEvent, EditorMode, InlayHintRefreshReason,
-    MultiBufferSnapshot, ToPoint,
-};
-use gpui::{point, px, AppContext, Entity, Pixels, Task, ViewContext};
-use language::{Bias, Point};
-use std::{
-    cmp::Ordering,
-    time::{Duration, Instant},
-};
-use util::ResultExt;
-use workspace::{ItemId, WorkspaceId};
-
-use self::{
-    autoscroll::{Autoscroll, AutoscrollStrategy},
-    scroll_amount::ScrollAmount,
-};
-
-pub const SCROLL_EVENT_SEPARATION: Duration = Duration::from_millis(28);
-pub const VERTICAL_SCROLL_MARGIN: f32 = 3.;
-const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
-
-#[derive(Default)]
-pub struct ScrollbarAutoHide(pub bool);
-
-#[derive(Clone, Copy, Debug, PartialEq)]
-pub struct ScrollAnchor {
-    pub offset: gpui::Point<f32>,
-    pub anchor: Anchor,
-}
-
-impl ScrollAnchor {
-    fn new() -> Self {
-        Self {
-            offset: gpui::Point::default(),
-            anchor: Anchor::min(),
-        }
-    }
-
-    pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<f32> {
-        let mut scroll_position = self.offset;
-        if self.anchor != Anchor::min() {
-            let scroll_top = self.anchor.to_display_point(snapshot).row() as f32;
-            scroll_position.y = scroll_top + scroll_position.y;
-        } else {
-            scroll_position.y = 0.;
-        }
-        scroll_position
-    }
-
-    pub fn top_row(&self, buffer: &MultiBufferSnapshot) -> u32 {
-        self.anchor.to_point(buffer).row
-    }
-}
-
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
-pub enum Axis {
-    Vertical,
-    Horizontal,
-}
-
-#[derive(Clone, Copy, Debug)]
-pub struct OngoingScroll {
-    last_event: Instant,
-    axis: Option<Axis>,
-}
-
-impl OngoingScroll {
-    fn new() -> Self {
-        Self {
-            last_event: Instant::now() - SCROLL_EVENT_SEPARATION,
-            axis: None,
-        }
-    }
-
-    pub fn filter(&self, delta: &mut gpui::Point<Pixels>) -> Option<Axis> {
-        const UNLOCK_PERCENT: f32 = 1.9;
-        const UNLOCK_LOWER_BOUND: Pixels = px(6.);
-        let mut axis = self.axis;
-
-        let x = delta.x.abs();
-        let y = delta.y.abs();
-        let duration = Instant::now().duration_since(self.last_event);
-        if duration > SCROLL_EVENT_SEPARATION {
-            //New ongoing scroll will start, determine axis
-            axis = if x <= y {
-                Some(Axis::Vertical)
-            } else {
-                Some(Axis::Horizontal)
-            };
-        } else if x.max(y) >= UNLOCK_LOWER_BOUND {
-            //Check if the current ongoing will need to unlock
-            match axis {
-                Some(Axis::Vertical) => {
-                    if x > y && x >= y * UNLOCK_PERCENT {
-                        axis = None;
-                    }
-                }
-
-                Some(Axis::Horizontal) => {
-                    if y > x && y >= x * UNLOCK_PERCENT {
-                        axis = None;
-                    }
-                }
-
-                None => {}
-            }
-        }
-
-        match axis {
-            Some(Axis::Vertical) => {
-                *delta = point(px(0.), delta.y);
-            }
-            Some(Axis::Horizontal) => {
-                *delta = point(delta.x, px(0.));
-            }
-            None => {}
-        }
-
-        axis
-    }
-}
-
-pub struct ScrollManager {
-    vertical_scroll_margin: f32,
-    anchor: ScrollAnchor,
-    ongoing: OngoingScroll,
-    autoscroll_request: Option<(Autoscroll, bool)>,
-    last_autoscroll: Option<(gpui::Point<f32>, f32, f32, AutoscrollStrategy)>,
-    show_scrollbars: bool,
-    hide_scrollbar_task: Option<Task<()>>,
-    dragging_scrollbar: bool,
-    visible_line_count: Option<f32>,
-}
-
-impl ScrollManager {
-    pub fn new() -> Self {
-        ScrollManager {
-            vertical_scroll_margin: VERTICAL_SCROLL_MARGIN,
-            anchor: ScrollAnchor::new(),
-            ongoing: OngoingScroll::new(),
-            autoscroll_request: None,
-            show_scrollbars: true,
-            hide_scrollbar_task: None,
-            dragging_scrollbar: false,
-            last_autoscroll: None,
-            visible_line_count: None,
-        }
-    }
-
-    pub fn clone_state(&mut self, other: &Self) {
-        self.anchor = other.anchor;
-        self.ongoing = other.ongoing;
-    }
-
-    pub fn anchor(&self) -> ScrollAnchor {
-        self.anchor
-    }
-
-    pub fn ongoing_scroll(&self) -> OngoingScroll {
-        self.ongoing
-    }
-
-    pub fn update_ongoing_scroll(&mut self, axis: Option<Axis>) {
-        self.ongoing.last_event = Instant::now();
-        self.ongoing.axis = axis;
-    }
-
-    pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<f32> {
-        self.anchor.scroll_position(snapshot)
-    }
-
-    fn set_scroll_position(
-        &mut self,
-        scroll_position: gpui::Point<f32>,
-        map: &DisplaySnapshot,
-        local: bool,
-        autoscroll: bool,
-        workspace_id: Option<i64>,
-        cx: &mut ViewContext<Editor>,
-    ) {
-        let (new_anchor, top_row) = if scroll_position.y <= 0. {
-            (
-                ScrollAnchor {
-                    anchor: Anchor::min(),
-                    offset: scroll_position.max(&gpui::Point::default()),
-                },
-                0,
-            )
-        } else {
-            let scroll_top_buffer_point =
-                DisplayPoint::new(scroll_position.y as u32, 0).to_point(&map);
-            let top_anchor = map
-                .buffer_snapshot
-                .anchor_at(scroll_top_buffer_point, Bias::Right);
-
-            (
-                ScrollAnchor {
-                    anchor: top_anchor,
-                    offset: point(
-                        scroll_position.x,
-                        scroll_position.y - top_anchor.to_display_point(&map).row() as f32,
-                    ),
-                },
-                scroll_top_buffer_point.row,
-            )
-        };
-
-        self.set_anchor(new_anchor, top_row, local, autoscroll, workspace_id, cx);
-    }
-
-    fn set_anchor(
-        &mut self,
-        anchor: ScrollAnchor,
-        top_row: u32,
-        local: bool,
-        autoscroll: bool,
-        workspace_id: Option<i64>,
-        cx: &mut ViewContext<Editor>,
-    ) {
-        self.anchor = anchor;
-        cx.emit(EditorEvent::ScrollPositionChanged { local, autoscroll });
-        self.show_scrollbar(cx);
-        self.autoscroll_request.take();
-        if let Some(workspace_id) = workspace_id {
-            let item_id = cx.view().entity_id().as_u64() as ItemId;
-
-            cx.foreground_executor()
-                .spawn(async move {
-                    DB.save_scroll_position(
-                        item_id,
-                        workspace_id,
-                        top_row,
-                        anchor.offset.x,
-                        anchor.offset.y,
-                    )
-                    .await
-                    .log_err()
-                })
-                .detach()
-        }
-        cx.notify();
-    }
-
-    pub fn show_scrollbar(&mut self, cx: &mut ViewContext<Editor>) {
-        if !self.show_scrollbars {
-            self.show_scrollbars = true;
-            cx.notify();
-        }
-
-        if cx.default_global::<ScrollbarAutoHide>().0 {
-            self.hide_scrollbar_task = Some(cx.spawn(|editor, mut cx| async move {
-                cx.background_executor()
-                    .timer(SCROLLBAR_SHOW_INTERVAL)
-                    .await;
-                editor
-                    .update(&mut cx, |editor, cx| {
-                        editor.scroll_manager.show_scrollbars = false;
-                        cx.notify();
-                    })
-                    .log_err();
-            }));
-        } else {
-            self.hide_scrollbar_task = None;
-        }
-    }
-
-    pub fn scrollbars_visible(&self) -> bool {
-        self.show_scrollbars
-    }
-
-    pub fn has_autoscroll_request(&self) -> bool {
-        self.autoscroll_request.is_some()
-    }
-
-    pub fn is_dragging_scrollbar(&self) -> bool {
-        self.dragging_scrollbar
-    }
-
-    pub fn set_is_dragging_scrollbar(&mut self, dragging: bool, cx: &mut ViewContext<Editor>) {
-        if dragging != self.dragging_scrollbar {
-            self.dragging_scrollbar = dragging;
-            cx.notify();
-        }
-    }
-
-    pub fn clamp_scroll_left(&mut self, max: f32) -> bool {
-        if max < self.anchor.offset.x {
-            self.anchor.offset.x = max;
-            true
-        } else {
-            false
-        }
-    }
-}
-
-impl Editor {
-    pub fn vertical_scroll_margin(&mut self) -> usize {
-        self.scroll_manager.vertical_scroll_margin as usize
-    }
-
-    pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut ViewContext<Self>) {
-        self.scroll_manager.vertical_scroll_margin = margin_rows as f32;
-        cx.notify();
-    }
-
-    pub fn visible_line_count(&self) -> Option<f32> {
-        self.scroll_manager.visible_line_count
-    }
-
-    pub(crate) fn set_visible_line_count(&mut self, lines: f32, cx: &mut ViewContext<Self>) {
-        let opened_first_time = self.scroll_manager.visible_line_count.is_none();
-        self.scroll_manager.visible_line_count = Some(lines);
-        if opened_first_time {
-            cx.spawn(|editor, mut cx| async move {
-                editor
-                    .update(&mut cx, |editor, cx| {
-                        editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx)
-                    })
-                    .ok()
-            })
-            .detach()
-        }
-    }
-
-    pub fn set_scroll_position(
-        &mut self,
-        scroll_position: gpui::Point<f32>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.set_scroll_position_internal(scroll_position, true, false, cx);
-    }
-
-    pub(crate) fn set_scroll_position_internal(
-        &mut self,
-        scroll_position: gpui::Point<f32>,
-        local: bool,
-        autoscroll: bool,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-
-        hide_hover(self, cx);
-        let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1);
-        self.scroll_manager.set_scroll_position(
-            scroll_position,
-            &map,
-            local,
-            autoscroll,
-            workspace_id,
-            cx,
-        );
-
-        self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
-    }
-
-    pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> gpui::Point<f32> {
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-        self.scroll_manager.anchor.scroll_position(&display_map)
-    }
-
-    pub fn set_scroll_anchor(&mut self, scroll_anchor: ScrollAnchor, cx: &mut ViewContext<Self>) {
-        hide_hover(self, cx);
-        let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1);
-        let top_row = scroll_anchor
-            .anchor
-            .to_point(&self.buffer().read(cx).snapshot(cx))
-            .row;
-        self.scroll_manager
-            .set_anchor(scroll_anchor, top_row, true, false, workspace_id, cx);
-    }
-
-    pub(crate) fn set_scroll_anchor_remote(
-        &mut self,
-        scroll_anchor: ScrollAnchor,
-        cx: &mut ViewContext<Self>,
-    ) {
-        hide_hover(self, cx);
-        let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1);
-        let top_row = scroll_anchor
-            .anchor
-            .to_point(&self.buffer().read(cx).snapshot(cx))
-            .row;
-        self.scroll_manager
-            .set_anchor(scroll_anchor, top_row, false, false, workspace_id, cx);
-    }
-
-    pub fn scroll_screen(&mut self, amount: &ScrollAmount, cx: &mut ViewContext<Self>) {
-        if matches!(self.mode, EditorMode::SingleLine) {
-            cx.propagate();
-            return;
-        }
-
-        if self.take_rename(true, cx).is_some() {
-            return;
-        }
-
-        let cur_position = self.scroll_position(cx);
-        let new_pos = cur_position + point(0., amount.lines(self));
-        self.set_scroll_position(new_pos, cx);
-    }
-
-    /// Returns an ordering. The newest selection is:
-    ///     Ordering::Equal => on screen
-    ///     Ordering::Less => above the screen
-    ///     Ordering::Greater => below the screen
-    pub fn newest_selection_on_screen(&self, cx: &mut AppContext) -> Ordering {
-        let snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-        let newest_head = self
-            .selections
-            .newest_anchor()
-            .head()
-            .to_display_point(&snapshot);
-        let screen_top = self
-            .scroll_manager
-            .anchor
-            .anchor
-            .to_display_point(&snapshot);
-
-        if screen_top > newest_head {
-            return Ordering::Less;
-        }
-
-        if let Some(visible_lines) = self.visible_line_count() {
-            if newest_head.row() < screen_top.row() + visible_lines as u32 {
-                return Ordering::Equal;
-            }
-        }
-
-        Ordering::Greater
-    }
-
-    pub fn read_scroll_position_from_db(
-        &mut self,
-        item_id: u64,
-        workspace_id: WorkspaceId,
-        cx: &mut ViewContext<Editor>,
-    ) {
-        let scroll_position = DB.get_scroll_position(item_id, workspace_id);
-        if let Ok(Some((top_row, x, y))) = scroll_position {
-            let top_anchor = self
-                .buffer()
-                .read(cx)
-                .snapshot(cx)
-                .anchor_at(Point::new(top_row as u32, 0), Bias::Left);
-            let scroll_anchor = ScrollAnchor {
-                offset: gpui::Point::new(x, y),
-                anchor: top_anchor,
-            };
-            self.set_scroll_anchor(scroll_anchor, cx);
-        }
-    }
-}

crates/editor2/src/scroll/actions.rs 🔗

@@ -1,103 +0,0 @@
-use super::Axis;
-use crate::{
-    Autoscroll, Bias, Editor, EditorMode, NextScreen, ScrollAnchor, ScrollCursorBottom,
-    ScrollCursorCenter, ScrollCursorTop,
-};
-use gpui::{Point, ViewContext};
-
-impl Editor {
-    pub fn next_screen(&mut self, _: &NextScreen, cx: &mut ViewContext<Editor>) {
-        if self.take_rename(true, cx).is_some() {
-            return;
-        }
-
-        // todo!()
-        // if self.mouse_context_menu.read(cx).visible() {
-        //     return None;
-        // }
-
-        if matches!(self.mode, EditorMode::SingleLine) {
-            cx.propagate();
-            return;
-        }
-        self.request_autoscroll(Autoscroll::Next, cx);
-    }
-
-    pub fn scroll(
-        &mut self,
-        scroll_position: Point<f32>,
-        axis: Option<Axis>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.scroll_manager.update_ongoing_scroll(axis);
-        self.set_scroll_position(scroll_position, cx);
-    }
-
-    pub fn scroll_cursor_top(&mut self, _: &ScrollCursorTop, cx: &mut ViewContext<Editor>) {
-        let snapshot = self.snapshot(cx).display_snapshot;
-        let scroll_margin_rows = self.vertical_scroll_margin() as u32;
-
-        let mut new_screen_top = self.selections.newest_display(cx).head();
-        *new_screen_top.row_mut() = new_screen_top.row().saturating_sub(scroll_margin_rows);
-        *new_screen_top.column_mut() = 0;
-        let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
-        let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top);
-
-        self.set_scroll_anchor(
-            ScrollAnchor {
-                anchor: new_anchor,
-                offset: Default::default(),
-            },
-            cx,
-        )
-    }
-
-    pub fn scroll_cursor_center(&mut self, _: &ScrollCursorCenter, cx: &mut ViewContext<Editor>) {
-        let snapshot = self.snapshot(cx).display_snapshot;
-        let visible_rows = if let Some(visible_rows) = self.visible_line_count() {
-            visible_rows as u32
-        } else {
-            return;
-        };
-
-        let mut new_screen_top = self.selections.newest_display(cx).head();
-        *new_screen_top.row_mut() = new_screen_top.row().saturating_sub(visible_rows / 2);
-        *new_screen_top.column_mut() = 0;
-        let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
-        let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top);
-
-        self.set_scroll_anchor(
-            ScrollAnchor {
-                anchor: new_anchor,
-                offset: Default::default(),
-            },
-            cx,
-        )
-    }
-
-    pub fn scroll_cursor_bottom(&mut self, _: &ScrollCursorBottom, cx: &mut ViewContext<Editor>) {
-        let snapshot = self.snapshot(cx).display_snapshot;
-        let scroll_margin_rows = self.vertical_scroll_margin() as u32;
-        let visible_rows = if let Some(visible_rows) = self.visible_line_count() {
-            visible_rows as u32
-        } else {
-            return;
-        };
-
-        let mut new_screen_top = self.selections.newest_display(cx).head();
-        *new_screen_top.row_mut() = new_screen_top
-            .row()
-            .saturating_sub(visible_rows.saturating_sub(scroll_margin_rows));
-        *new_screen_top.column_mut() = 0;
-        let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
-        let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top);
-
-        self.set_scroll_anchor(
-            ScrollAnchor {
-                anchor: new_anchor,
-                offset: Default::default(),
-            },
-            cx,
-        )
-    }
-}

crates/editor2/src/scroll/autoscroll.rs 🔗

@@ -1,253 +0,0 @@
-use std::{cmp, f32};
-
-use gpui::{px, Pixels, ViewContext};
-use language::Point;
-
-use crate::{display_map::ToDisplayPoint, Editor, EditorMode, LineWithInvisibles};
-
-#[derive(PartialEq, Eq)]
-pub enum Autoscroll {
-    Next,
-    Strategy(AutoscrollStrategy),
-}
-
-impl Autoscroll {
-    pub fn fit() -> Self {
-        Self::Strategy(AutoscrollStrategy::Fit)
-    }
-
-    pub fn newest() -> Self {
-        Self::Strategy(AutoscrollStrategy::Newest)
-    }
-
-    pub fn center() -> Self {
-        Self::Strategy(AutoscrollStrategy::Center)
-    }
-}
-
-#[derive(PartialEq, Eq, Default)]
-pub enum AutoscrollStrategy {
-    Fit,
-    Newest,
-    #[default]
-    Center,
-    Top,
-    Bottom,
-}
-
-impl AutoscrollStrategy {
-    fn next(&self) -> Self {
-        match self {
-            AutoscrollStrategy::Center => AutoscrollStrategy::Top,
-            AutoscrollStrategy::Top => AutoscrollStrategy::Bottom,
-            _ => AutoscrollStrategy::Center,
-        }
-    }
-}
-
-impl Editor {
-    pub fn autoscroll_vertically(
-        &mut self,
-        viewport_height: Pixels,
-        line_height: Pixels,
-        cx: &mut ViewContext<Editor>,
-    ) -> bool {
-        let visible_lines = f32::from(viewport_height / line_height);
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-        let mut scroll_position = self.scroll_manager.scroll_position(&display_map);
-        let max_scroll_top = if matches!(self.mode, EditorMode::AutoHeight { .. }) {
-            (display_map.max_point().row() as f32 - visible_lines + 1.).max(0.)
-        } else {
-            display_map.max_point().row() as f32
-        };
-        if scroll_position.y > max_scroll_top {
-            scroll_position.y = max_scroll_top;
-            self.set_scroll_position(scroll_position, cx);
-        }
-
-        let Some((autoscroll, local)) = self.scroll_manager.autoscroll_request.take() else {
-            return false;
-        };
-
-        let mut target_top;
-        let mut target_bottom;
-        if let Some(highlighted_rows) = &self.highlighted_rows {
-            target_top = highlighted_rows.start as f32;
-            target_bottom = target_top + 1.;
-        } else {
-            let selections = self.selections.all::<Point>(cx);
-            target_top = selections
-                .first()
-                .unwrap()
-                .head()
-                .to_display_point(&display_map)
-                .row() as f32;
-            target_bottom = selections
-                .last()
-                .unwrap()
-                .head()
-                .to_display_point(&display_map)
-                .row() as f32
-                + 1.0;
-
-            // If the selections can't all fit on screen, scroll to the newest.
-            if autoscroll == Autoscroll::newest()
-                || autoscroll == Autoscroll::fit() && target_bottom - target_top > visible_lines
-            {
-                let newest_selection_top = selections
-                    .iter()
-                    .max_by_key(|s| s.id)
-                    .unwrap()
-                    .head()
-                    .to_display_point(&display_map)
-                    .row() as f32;
-                target_top = newest_selection_top;
-                target_bottom = newest_selection_top + 1.;
-            }
-        }
-
-        let margin = if matches!(self.mode, EditorMode::AutoHeight { .. }) {
-            0.
-        } else {
-            ((visible_lines - (target_bottom - target_top)) / 2.0).floor()
-        };
-
-        let strategy = match autoscroll {
-            Autoscroll::Strategy(strategy) => strategy,
-            Autoscroll::Next => {
-                let last_autoscroll = &self.scroll_manager.last_autoscroll;
-                if let Some(last_autoscroll) = last_autoscroll {
-                    if self.scroll_manager.anchor.offset == last_autoscroll.0
-                        && target_top == last_autoscroll.1
-                        && target_bottom == last_autoscroll.2
-                    {
-                        last_autoscroll.3.next()
-                    } else {
-                        AutoscrollStrategy::default()
-                    }
-                } else {
-                    AutoscrollStrategy::default()
-                }
-            }
-        };
-
-        match strategy {
-            AutoscrollStrategy::Fit | AutoscrollStrategy::Newest => {
-                let margin = margin.min(self.scroll_manager.vertical_scroll_margin);
-                let target_top = (target_top - margin).max(0.0);
-                let target_bottom = target_bottom + margin;
-                let start_row = scroll_position.y;
-                let end_row = start_row + visible_lines;
-
-                let needs_scroll_up = target_top < start_row;
-                let needs_scroll_down = target_bottom >= end_row;
-
-                if needs_scroll_up && !needs_scroll_down {
-                    scroll_position.y = target_top;
-                    self.set_scroll_position_internal(scroll_position, local, true, cx);
-                }
-                if !needs_scroll_up && needs_scroll_down {
-                    scroll_position.y = target_bottom - visible_lines;
-                    self.set_scroll_position_internal(scroll_position, local, true, cx);
-                }
-            }
-            AutoscrollStrategy::Center => {
-                scroll_position.y = (target_top - margin).max(0.0);
-                self.set_scroll_position_internal(scroll_position, local, true, cx);
-            }
-            AutoscrollStrategy::Top => {
-                scroll_position.y = (target_top).max(0.0);
-                self.set_scroll_position_internal(scroll_position, local, true, cx);
-            }
-            AutoscrollStrategy::Bottom => {
-                scroll_position.y = (target_bottom - visible_lines).max(0.0);
-                self.set_scroll_position_internal(scroll_position, local, true, cx);
-            }
-        }
-
-        self.scroll_manager.last_autoscroll = Some((
-            self.scroll_manager.anchor.offset,
-            target_top,
-            target_bottom,
-            strategy,
-        ));
-
-        true
-    }
-
-    pub fn autoscroll_horizontally(
-        &mut self,
-        start_row: u32,
-        viewport_width: Pixels,
-        scroll_width: Pixels,
-        max_glyph_width: Pixels,
-        layouts: &[LineWithInvisibles],
-        cx: &mut ViewContext<Self>,
-    ) -> bool {
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-        let selections = self.selections.all::<Point>(cx);
-
-        let mut target_left;
-        let mut target_right;
-
-        if self.highlighted_rows.is_some() {
-            target_left = px(0.);
-            target_right = px(0.);
-        } else {
-            target_left = px(f32::INFINITY);
-            target_right = px(0.);
-            for selection in selections {
-                let head = selection.head().to_display_point(&display_map);
-                if head.row() >= start_row && head.row() < start_row + layouts.len() as u32 {
-                    let start_column = head.column().saturating_sub(3);
-                    let end_column = cmp::min(display_map.line_len(head.row()), head.column() + 3);
-                    target_left = target_left.min(
-                        layouts[(head.row() - start_row) as usize]
-                            .line
-                            .x_for_index(start_column as usize),
-                    );
-                    target_right = target_right.max(
-                        layouts[(head.row() - start_row) as usize]
-                            .line
-                            .x_for_index(end_column as usize)
-                            + max_glyph_width,
-                    );
-                }
-            }
-        }
-
-        target_right = target_right.min(scroll_width);
-
-        if target_right - target_left > viewport_width {
-            return false;
-        }
-
-        let scroll_left = self.scroll_manager.anchor.offset.x * max_glyph_width;
-        let scroll_right = scroll_left + viewport_width;
-
-        if target_left < scroll_left {
-            self.scroll_manager.anchor.offset.x = (target_left / max_glyph_width).into();
-            true
-        } else if target_right > scroll_right {
-            self.scroll_manager.anchor.offset.x =
-                ((target_right - viewport_width) / max_glyph_width).into();
-            true
-        } else {
-            false
-        }
-    }
-
-    pub fn request_autoscroll(&mut self, autoscroll: Autoscroll, cx: &mut ViewContext<Self>) {
-        self.scroll_manager.autoscroll_request = Some((autoscroll, true));
-        cx.notify();
-    }
-
-    pub(crate) fn request_autoscroll_remotely(
-        &mut self,
-        autoscroll: Autoscroll,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.scroll_manager.autoscroll_request = Some((autoscroll, false));
-        cx.notify();
-    }
-}

crates/editor2/src/scroll/scroll_amount.rs 🔗

@@ -1,28 +0,0 @@
-use crate::Editor;
-use serde::Deserialize;
-
-#[derive(Clone, PartialEq, Deserialize)]
-pub enum ScrollAmount {
-    // Scroll N lines (positive is towards the end of the document)
-    Line(f32),
-    // Scroll N pages (positive is towards the end of the document)
-    Page(f32),
-}
-
-impl ScrollAmount {
-    pub fn lines(&self, editor: &mut Editor) -> f32 {
-        match self {
-            Self::Line(count) => *count,
-            Self::Page(count) => editor
-                .visible_line_count()
-                .map(|mut l| {
-                    // for full pages subtract one to leave an anchor line
-                    if count.abs() == 1.0 {
-                        l -= 1.0
-                    }
-                    (l * count).trunc()
-                })
-                .unwrap_or(0.),
-        }
-    }
-}

crates/editor2/src/selections_collection.rs 🔗

@@ -1,888 +0,0 @@
-use std::{
-    cell::Ref,
-    iter, mem,
-    ops::{Deref, DerefMut, Range, Sub},
-    sync::Arc,
-};
-
-use collections::HashMap;
-use gpui::{AppContext, Model, Pixels};
-use itertools::Itertools;
-use language::{Bias, Point, Selection, SelectionGoal, TextDimension, ToPoint};
-use util::post_inc;
-
-use crate::{
-    display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint},
-    movement::TextLayoutDetails,
-    Anchor, DisplayPoint, ExcerptId, MultiBuffer, MultiBufferSnapshot, SelectMode, ToOffset,
-};
-
-#[derive(Debug, Clone)]
-pub struct PendingSelection {
-    pub selection: Selection<Anchor>,
-    pub mode: SelectMode,
-}
-
-#[derive(Debug, Clone)]
-pub struct SelectionsCollection {
-    display_map: Model<DisplayMap>,
-    buffer: Model<MultiBuffer>,
-    pub next_selection_id: usize,
-    pub line_mode: bool,
-    disjoint: Arc<[Selection<Anchor>]>,
-    pending: Option<PendingSelection>,
-}
-
-impl SelectionsCollection {
-    pub fn new(display_map: Model<DisplayMap>, buffer: Model<MultiBuffer>) -> Self {
-        Self {
-            display_map,
-            buffer,
-            next_selection_id: 1,
-            line_mode: false,
-            disjoint: Arc::from([]),
-            pending: Some(PendingSelection {
-                selection: Selection {
-                    id: 0,
-                    start: Anchor::min(),
-                    end: Anchor::min(),
-                    reversed: false,
-                    goal: SelectionGoal::None,
-                },
-                mode: SelectMode::Character,
-            }),
-        }
-    }
-
-    pub fn display_map(&self, cx: &mut AppContext) -> DisplaySnapshot {
-        self.display_map.update(cx, |map, cx| map.snapshot(cx))
-    }
-
-    fn buffer<'a>(&self, cx: &'a AppContext) -> Ref<'a, MultiBufferSnapshot> {
-        self.buffer.read(cx).read(cx)
-    }
-
-    pub fn clone_state(&mut self, other: &SelectionsCollection) {
-        self.next_selection_id = other.next_selection_id;
-        self.line_mode = other.line_mode;
-        self.disjoint = other.disjoint.clone();
-        self.pending = other.pending.clone();
-    }
-
-    pub fn count(&self) -> usize {
-        let mut count = self.disjoint.len();
-        if self.pending.is_some() {
-            count += 1;
-        }
-        count
-    }
-
-    /// The non-pending, non-overlapping selections. There could still be a pending
-    /// selection that overlaps these if the mouse is being dragged, etc. Returned as
-    /// selections over Anchors.
-    pub fn disjoint_anchors(&self) -> Arc<[Selection<Anchor>]> {
-        self.disjoint.clone()
-    }
-
-    pub fn pending_anchor(&self) -> Option<Selection<Anchor>> {
-        self.pending
-            .as_ref()
-            .map(|pending| pending.selection.clone())
-    }
-
-    pub fn pending<D: TextDimension + Ord + Sub<D, Output = D>>(
-        &self,
-        cx: &AppContext,
-    ) -> Option<Selection<D>> {
-        self.pending_anchor()
-            .as_ref()
-            .map(|pending| pending.map(|p| p.summary::<D>(&self.buffer(cx))))
-    }
-
-    pub fn pending_mode(&self) -> Option<SelectMode> {
-        self.pending.as_ref().map(|pending| pending.mode.clone())
-    }
-
-    pub fn all<'a, D>(&self, cx: &AppContext) -> Vec<Selection<D>>
-    where
-        D: 'a + TextDimension + Ord + Sub<D, Output = D> + std::fmt::Debug,
-    {
-        let disjoint_anchors = &self.disjoint;
-        let mut disjoint =
-            resolve_multiple::<D, _>(disjoint_anchors.iter(), &self.buffer(cx)).peekable();
-
-        let mut pending_opt = self.pending::<D>(cx);
-
-        iter::from_fn(move || {
-            if let Some(pending) = pending_opt.as_mut() {
-                while let Some(next_selection) = disjoint.peek() {
-                    if pending.start <= next_selection.end && pending.end >= next_selection.start {
-                        let next_selection = disjoint.next().unwrap();
-                        if next_selection.start < pending.start {
-                            pending.start = next_selection.start;
-                        }
-                        if next_selection.end > pending.end {
-                            pending.end = next_selection.end;
-                        }
-                    } else if next_selection.end < pending.start {
-                        return disjoint.next();
-                    } else {
-                        break;
-                    }
-                }
-
-                pending_opt.take()
-            } else {
-                disjoint.next()
-            }
-        })
-        .collect()
-    }
-
-    /// Returns all of the selections, adjusted to take into account the selection line_mode
-    pub fn all_adjusted(&self, cx: &mut AppContext) -> Vec<Selection<Point>> {
-        let mut selections = self.all::<Point>(cx);
-        if self.line_mode {
-            let map = self.display_map(cx);
-            for selection in &mut selections {
-                let new_range = map.expand_to_line(selection.range());
-                selection.start = new_range.start;
-                selection.end = new_range.end;
-            }
-        }
-        selections
-    }
-
-    pub fn all_adjusted_display(
-        &self,
-        cx: &mut AppContext,
-    ) -> (DisplaySnapshot, Vec<Selection<DisplayPoint>>) {
-        if self.line_mode {
-            let selections = self.all::<Point>(cx);
-            let map = self.display_map(cx);
-            let result = selections
-                .into_iter()
-                .map(|mut selection| {
-                    let new_range = map.expand_to_line(selection.range());
-                    selection.start = new_range.start;
-                    selection.end = new_range.end;
-                    selection.map(|point| point.to_display_point(&map))
-                })
-                .collect();
-            (map, result)
-        } else {
-            self.all_display(cx)
-        }
-    }
-
-    pub fn disjoint_in_range<'a, D>(
-        &self,
-        range: Range<Anchor>,
-        cx: &AppContext,
-    ) -> Vec<Selection<D>>
-    where
-        D: 'a + TextDimension + Ord + Sub<D, Output = D> + std::fmt::Debug,
-    {
-        let buffer = self.buffer(cx);
-        let start_ix = match self
-            .disjoint
-            .binary_search_by(|probe| probe.end.cmp(&range.start, &buffer))
-        {
-            Ok(ix) | Err(ix) => ix,
-        };
-        let end_ix = match self
-            .disjoint
-            .binary_search_by(|probe| probe.start.cmp(&range.end, &buffer))
-        {
-            Ok(ix) => ix + 1,
-            Err(ix) => ix,
-        };
-        resolve_multiple(&self.disjoint[start_ix..end_ix], &buffer).collect()
-    }
-
-    pub fn all_display(
-        &self,
-        cx: &mut AppContext,
-    ) -> (DisplaySnapshot, Vec<Selection<DisplayPoint>>) {
-        let display_map = self.display_map(cx);
-        let selections = self
-            .all::<Point>(cx)
-            .into_iter()
-            .map(|selection| selection.map(|point| point.to_display_point(&display_map)))
-            .collect();
-        (display_map, selections)
-    }
-
-    pub fn newest_anchor(&self) -> &Selection<Anchor> {
-        self.pending
-            .as_ref()
-            .map(|s| &s.selection)
-            .or_else(|| self.disjoint.iter().max_by_key(|s| s.id))
-            .unwrap()
-    }
-
-    pub fn newest<D: TextDimension + Ord + Sub<D, Output = D>>(
-        &self,
-        cx: &AppContext,
-    ) -> Selection<D> {
-        resolve(self.newest_anchor(), &self.buffer(cx))
-    }
-
-    pub fn newest_display(&self, cx: &mut AppContext) -> Selection<DisplayPoint> {
-        let display_map = self.display_map(cx);
-        let selection = self
-            .newest_anchor()
-            .map(|point| point.to_display_point(&display_map));
-        selection
-    }
-
-    pub fn oldest_anchor(&self) -> &Selection<Anchor> {
-        self.disjoint
-            .iter()
-            .min_by_key(|s| s.id)
-            .or_else(|| self.pending.as_ref().map(|p| &p.selection))
-            .unwrap()
-    }
-
-    pub fn oldest<D: TextDimension + Ord + Sub<D, Output = D>>(
-        &self,
-        cx: &AppContext,
-    ) -> Selection<D> {
-        resolve(self.oldest_anchor(), &self.buffer(cx))
-    }
-
-    pub fn first_anchor(&self) -> Selection<Anchor> {
-        self.disjoint[0].clone()
-    }
-
-    pub fn first<D: TextDimension + Ord + Sub<D, Output = D>>(
-        &self,
-        cx: &AppContext,
-    ) -> Selection<D> {
-        self.all(cx).first().unwrap().clone()
-    }
-
-    pub fn last<D: TextDimension + Ord + Sub<D, Output = D>>(
-        &self,
-        cx: &AppContext,
-    ) -> Selection<D> {
-        self.all(cx).last().unwrap().clone()
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn ranges<D: TextDimension + Ord + Sub<D, Output = D> + std::fmt::Debug>(
-        &self,
-        cx: &AppContext,
-    ) -> Vec<Range<D>> {
-        self.all::<D>(cx)
-            .iter()
-            .map(|s| {
-                if s.reversed {
-                    s.end.clone()..s.start.clone()
-                } else {
-                    s.start.clone()..s.end.clone()
-                }
-            })
-            .collect()
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn display_ranges(&self, cx: &mut AppContext) -> Vec<Range<DisplayPoint>> {
-        let display_map = self.display_map(cx);
-        self.disjoint_anchors()
-            .iter()
-            .chain(self.pending_anchor().as_ref())
-            .map(|s| {
-                if s.reversed {
-                    s.end.to_display_point(&display_map)..s.start.to_display_point(&display_map)
-                } else {
-                    s.start.to_display_point(&display_map)..s.end.to_display_point(&display_map)
-                }
-            })
-            .collect()
-    }
-
-    pub fn build_columnar_selection(
-        &mut self,
-        display_map: &DisplaySnapshot,
-        row: u32,
-        positions: &Range<Pixels>,
-        reversed: bool,
-        text_layout_details: &TextLayoutDetails,
-    ) -> Option<Selection<Point>> {
-        let is_empty = positions.start == positions.end;
-        let line_len = display_map.line_len(row);
-
-        let line = display_map.layout_row(row, &text_layout_details);
-
-        let start_col = line.closest_index_for_x(positions.start) as u32;
-        if start_col < line_len || (is_empty && positions.start == line.width) {
-            let start = DisplayPoint::new(row, start_col);
-            let end_col = line.closest_index_for_x(positions.end) as u32;
-            let end = DisplayPoint::new(row, end_col);
-
-            Some(Selection {
-                id: post_inc(&mut self.next_selection_id),
-                start: start.to_point(display_map),
-                end: end.to_point(display_map),
-                reversed,
-                goal: SelectionGoal::HorizontalRange {
-                    start: positions.start.into(),
-                    end: positions.end.into(),
-                },
-            })
-        } else {
-            None
-        }
-    }
-
-    pub(crate) fn change_with<R>(
-        &mut self,
-        cx: &mut AppContext,
-        change: impl FnOnce(&mut MutableSelectionsCollection) -> R,
-    ) -> (bool, R) {
-        let mut mutable_collection = MutableSelectionsCollection {
-            collection: self,
-            selections_changed: false,
-            cx,
-        };
-
-        let result = change(&mut mutable_collection);
-        assert!(
-            !mutable_collection.disjoint.is_empty() || mutable_collection.pending.is_some(),
-            "There must be at least one selection"
-        );
-        (mutable_collection.selections_changed, result)
-    }
-}
-
-pub struct MutableSelectionsCollection<'a> {
-    collection: &'a mut SelectionsCollection,
-    selections_changed: bool,
-    cx: &'a mut AppContext,
-}
-
-impl<'a> MutableSelectionsCollection<'a> {
-    pub fn display_map(&mut self) -> DisplaySnapshot {
-        self.collection.display_map(self.cx)
-    }
-
-    fn buffer(&self) -> Ref<MultiBufferSnapshot> {
-        self.collection.buffer(self.cx)
-    }
-
-    pub fn clear_disjoint(&mut self) {
-        self.collection.disjoint = Arc::from([]);
-    }
-
-    pub fn delete(&mut self, selection_id: usize) {
-        let mut changed = false;
-        self.collection.disjoint = self
-            .disjoint
-            .iter()
-            .filter(|selection| {
-                let found = selection.id == selection_id;
-                changed |= found;
-                !found
-            })
-            .cloned()
-            .collect();
-
-        self.selections_changed |= changed;
-    }
-
-    pub fn clear_pending(&mut self) {
-        if self.collection.pending.is_some() {
-            self.collection.pending = None;
-            self.selections_changed = true;
-        }
-    }
-
-    pub fn set_pending_anchor_range(&mut self, range: Range<Anchor>, mode: SelectMode) {
-        self.collection.pending = Some(PendingSelection {
-            selection: Selection {
-                id: post_inc(&mut self.collection.next_selection_id),
-                start: range.start,
-                end: range.end,
-                reversed: false,
-                goal: SelectionGoal::None,
-            },
-            mode,
-        });
-        self.selections_changed = true;
-    }
-
-    pub fn set_pending_display_range(&mut self, range: Range<DisplayPoint>, mode: SelectMode) {
-        let (start, end, reversed) = {
-            let display_map = self.display_map();
-            let buffer = self.buffer();
-            let mut start = range.start;
-            let mut end = range.end;
-            let reversed = if start > end {
-                mem::swap(&mut start, &mut end);
-                true
-            } else {
-                false
-            };
-
-            let end_bias = if end > start { Bias::Left } else { Bias::Right };
-            (
-                buffer.anchor_before(start.to_point(&display_map)),
-                buffer.anchor_at(end.to_point(&display_map), end_bias),
-                reversed,
-            )
-        };
-
-        let new_pending = PendingSelection {
-            selection: Selection {
-                id: post_inc(&mut self.collection.next_selection_id),
-                start,
-                end,
-                reversed,
-                goal: SelectionGoal::None,
-            },
-            mode,
-        };
-
-        self.collection.pending = Some(new_pending);
-        self.selections_changed = true;
-    }
-
-    pub fn set_pending(&mut self, selection: Selection<Anchor>, mode: SelectMode) {
-        self.collection.pending = Some(PendingSelection { selection, mode });
-        self.selections_changed = true;
-    }
-
-    pub fn try_cancel(&mut self) -> bool {
-        if let Some(pending) = self.collection.pending.take() {
-            if self.disjoint.is_empty() {
-                self.collection.disjoint = Arc::from([pending.selection]);
-            }
-            self.selections_changed = true;
-            return true;
-        }
-
-        let mut oldest = self.oldest_anchor().clone();
-        if self.count() > 1 {
-            self.collection.disjoint = Arc::from([oldest]);
-            self.selections_changed = true;
-            return true;
-        }
-
-        if !oldest.start.cmp(&oldest.end, &self.buffer()).is_eq() {
-            let head = oldest.head();
-            oldest.start = head.clone();
-            oldest.end = head;
-            self.collection.disjoint = Arc::from([oldest]);
-            self.selections_changed = true;
-            return true;
-        }
-
-        false
-    }
-
-    pub fn insert_range<T>(&mut self, range: Range<T>)
-    where
-        T: 'a + ToOffset + ToPoint + TextDimension + Ord + Sub<T, Output = T> + std::marker::Copy,
-    {
-        let mut selections = self.all(self.cx);
-        let mut start = range.start.to_offset(&self.buffer());
-        let mut end = range.end.to_offset(&self.buffer());
-        let reversed = if start > end {
-            mem::swap(&mut start, &mut end);
-            true
-        } else {
-            false
-        };
-        selections.push(Selection {
-            id: post_inc(&mut self.collection.next_selection_id),
-            start,
-            end,
-            reversed,
-            goal: SelectionGoal::None,
-        });
-        self.select(selections);
-    }
-
-    pub fn select<T>(&mut self, mut selections: Vec<Selection<T>>)
-    where
-        T: ToOffset + ToPoint + Ord + std::marker::Copy + std::fmt::Debug,
-    {
-        let buffer = self.buffer.read(self.cx).snapshot(self.cx);
-        selections.sort_unstable_by_key(|s| s.start);
-        // Merge overlapping selections.
-        let mut i = 1;
-        while i < selections.len() {
-            if selections[i - 1].end >= selections[i].start {
-                let removed = selections.remove(i);
-                if removed.start < selections[i - 1].start {
-                    selections[i - 1].start = removed.start;
-                }
-                if removed.end > selections[i - 1].end {
-                    selections[i - 1].end = removed.end;
-                }
-            } else {
-                i += 1;
-            }
-        }
-
-        self.collection.disjoint = Arc::from_iter(selections.into_iter().map(|selection| {
-            let end_bias = if selection.end > selection.start {
-                Bias::Left
-            } else {
-                Bias::Right
-            };
-            Selection {
-                id: selection.id,
-                start: buffer.anchor_after(selection.start),
-                end: buffer.anchor_at(selection.end, end_bias),
-                reversed: selection.reversed,
-                goal: selection.goal,
-            }
-        }));
-
-        self.collection.pending = None;
-        self.selections_changed = true;
-    }
-
-    pub fn select_anchors(&mut self, selections: Vec<Selection<Anchor>>) {
-        let buffer = self.buffer.read(self.cx).snapshot(self.cx);
-        let resolved_selections =
-            resolve_multiple::<usize, _>(&selections, &buffer).collect::<Vec<_>>();
-        self.select(resolved_selections);
-    }
-
-    pub fn select_ranges<I, T>(&mut self, ranges: I)
-    where
-        I: IntoIterator<Item = Range<T>>,
-        T: ToOffset,
-    {
-        let buffer = self.buffer.read(self.cx).snapshot(self.cx);
-        let ranges = ranges
-            .into_iter()
-            .map(|range| range.start.to_offset(&buffer)..range.end.to_offset(&buffer));
-        self.select_offset_ranges(ranges);
-    }
-
-    fn select_offset_ranges<I>(&mut self, ranges: I)
-    where
-        I: IntoIterator<Item = Range<usize>>,
-    {
-        let selections = ranges
-            .into_iter()
-            .map(|range| {
-                let mut start = range.start;
-                let mut end = range.end;
-                let reversed = if start > end {
-                    mem::swap(&mut start, &mut end);
-                    true
-                } else {
-                    false
-                };
-                Selection {
-                    id: post_inc(&mut self.collection.next_selection_id),
-                    start,
-                    end,
-                    reversed,
-                    goal: SelectionGoal::None,
-                }
-            })
-            .collect::<Vec<_>>();
-
-        self.select(selections)
-    }
-
-    pub fn select_anchor_ranges<I>(&mut self, ranges: I)
-    where
-        I: IntoIterator<Item = Range<Anchor>>,
-    {
-        let buffer = self.buffer.read(self.cx).snapshot(self.cx);
-        let selections = ranges
-            .into_iter()
-            .map(|range| {
-                let mut start = range.start;
-                let mut end = range.end;
-                let reversed = if start.cmp(&end, &buffer).is_gt() {
-                    mem::swap(&mut start, &mut end);
-                    true
-                } else {
-                    false
-                };
-                Selection {
-                    id: post_inc(&mut self.collection.next_selection_id),
-                    start,
-                    end,
-                    reversed,
-                    goal: SelectionGoal::None,
-                }
-            })
-            .collect::<Vec<_>>();
-        self.select_anchors(selections)
-    }
-
-    pub fn new_selection_id(&mut self) -> usize {
-        post_inc(&mut self.next_selection_id)
-    }
-
-    pub fn select_display_ranges<T>(&mut self, ranges: T)
-    where
-        T: IntoIterator<Item = Range<DisplayPoint>>,
-    {
-        let display_map = self.display_map();
-        let selections = ranges
-            .into_iter()
-            .map(|range| {
-                let mut start = range.start;
-                let mut end = range.end;
-                let reversed = if start > end {
-                    mem::swap(&mut start, &mut end);
-                    true
-                } else {
-                    false
-                };
-                Selection {
-                    id: post_inc(&mut self.collection.next_selection_id),
-                    start: start.to_point(&display_map),
-                    end: end.to_point(&display_map),
-                    reversed,
-                    goal: SelectionGoal::None,
-                }
-            })
-            .collect();
-        self.select(selections);
-    }
-
-    pub fn move_with(
-        &mut self,
-        mut move_selection: impl FnMut(&DisplaySnapshot, &mut Selection<DisplayPoint>),
-    ) {
-        let mut changed = false;
-        let display_map = self.display_map();
-        let selections = self
-            .all::<Point>(self.cx)
-            .into_iter()
-            .map(|selection| {
-                let mut moved_selection =
-                    selection.map(|point| point.to_display_point(&display_map));
-                move_selection(&display_map, &mut moved_selection);
-                let moved_selection =
-                    moved_selection.map(|display_point| display_point.to_point(&display_map));
-                if selection != moved_selection {
-                    changed = true;
-                }
-                moved_selection
-            })
-            .collect();
-
-        if changed {
-            self.select(selections)
-        }
-    }
-
-    pub fn move_offsets_with(
-        &mut self,
-        mut move_selection: impl FnMut(&MultiBufferSnapshot, &mut Selection<usize>),
-    ) {
-        let mut changed = false;
-        let snapshot = self.buffer().clone();
-        let selections = self
-            .all::<usize>(self.cx)
-            .into_iter()
-            .map(|selection| {
-                let mut moved_selection = selection.clone();
-                move_selection(&snapshot, &mut moved_selection);
-                if selection != moved_selection {
-                    changed = true;
-                }
-                moved_selection
-            })
-            .collect();
-        drop(snapshot);
-
-        if changed {
-            self.select(selections)
-        }
-    }
-
-    pub fn move_heads_with(
-        &mut self,
-        mut update_head: impl FnMut(
-            &DisplaySnapshot,
-            DisplayPoint,
-            SelectionGoal,
-        ) -> (DisplayPoint, SelectionGoal),
-    ) {
-        self.move_with(|map, selection| {
-            let (new_head, new_goal) = update_head(map, selection.head(), selection.goal);
-            selection.set_head(new_head, new_goal);
-        });
-    }
-
-    pub fn move_cursors_with(
-        &mut self,
-        mut update_cursor_position: impl FnMut(
-            &DisplaySnapshot,
-            DisplayPoint,
-            SelectionGoal,
-        ) -> (DisplayPoint, SelectionGoal),
-    ) {
-        self.move_with(|map, selection| {
-            let (cursor, new_goal) = update_cursor_position(map, selection.head(), selection.goal);
-            selection.collapse_to(cursor, new_goal)
-        });
-    }
-
-    pub fn maybe_move_cursors_with(
-        &mut self,
-        mut update_cursor_position: impl FnMut(
-            &DisplaySnapshot,
-            DisplayPoint,
-            SelectionGoal,
-        ) -> Option<(DisplayPoint, SelectionGoal)>,
-    ) {
-        self.move_cursors_with(|map, point, goal| {
-            update_cursor_position(map, point, goal).unwrap_or((point, goal))
-        })
-    }
-
-    pub fn replace_cursors_with(
-        &mut self,
-        mut find_replacement_cursors: impl FnMut(&DisplaySnapshot) -> Vec<DisplayPoint>,
-    ) {
-        let display_map = self.display_map();
-        let new_selections = find_replacement_cursors(&display_map)
-            .into_iter()
-            .map(|cursor| {
-                let cursor_point = cursor.to_point(&display_map);
-                Selection {
-                    id: post_inc(&mut self.collection.next_selection_id),
-                    start: cursor_point,
-                    end: cursor_point,
-                    reversed: false,
-                    goal: SelectionGoal::None,
-                }
-            })
-            .collect();
-        self.select(new_selections);
-    }
-
-    /// Compute new ranges for any selections that were located in excerpts that have
-    /// since been removed.
-    ///
-    /// Returns a `HashMap` indicating which selections whose former head position
-    /// was no longer present. The keys of the map are selection ids. The values are
-    /// the id of the new excerpt where the head of the selection has been moved.
-    pub fn refresh(&mut self) -> HashMap<usize, ExcerptId> {
-        let mut pending = self.collection.pending.take();
-        let mut selections_with_lost_position = HashMap::default();
-
-        let anchors_with_status = {
-            let buffer = self.buffer();
-            let disjoint_anchors = self
-                .disjoint
-                .iter()
-                .flat_map(|selection| [&selection.start, &selection.end]);
-            buffer.refresh_anchors(disjoint_anchors)
-        };
-        let adjusted_disjoint: Vec<_> = anchors_with_status
-            .chunks(2)
-            .map(|selection_anchors| {
-                let (anchor_ix, start, kept_start) = selection_anchors[0].clone();
-                let (_, end, kept_end) = selection_anchors[1].clone();
-                let selection = &self.disjoint[anchor_ix / 2];
-                let kept_head = if selection.reversed {
-                    kept_start
-                } else {
-                    kept_end
-                };
-                if !kept_head {
-                    selections_with_lost_position.insert(selection.id, selection.head().excerpt_id);
-                }
-
-                Selection {
-                    id: selection.id,
-                    start,
-                    end,
-                    reversed: selection.reversed,
-                    goal: selection.goal,
-                }
-            })
-            .collect();
-
-        if !adjusted_disjoint.is_empty() {
-            let resolved_selections =
-                resolve_multiple(adjusted_disjoint.iter(), &self.buffer()).collect();
-            self.select::<usize>(resolved_selections);
-        }
-
-        if let Some(pending) = pending.as_mut() {
-            let buffer = self.buffer();
-            let anchors =
-                buffer.refresh_anchors([&pending.selection.start, &pending.selection.end]);
-            let (_, start, kept_start) = anchors[0].clone();
-            let (_, end, kept_end) = anchors[1].clone();
-            let kept_head = if pending.selection.reversed {
-                kept_start
-            } else {
-                kept_end
-            };
-            if !kept_head {
-                selections_with_lost_position
-                    .insert(pending.selection.id, pending.selection.head().excerpt_id);
-            }
-
-            pending.selection.start = start;
-            pending.selection.end = end;
-        }
-        self.collection.pending = pending;
-        self.selections_changed = true;
-
-        selections_with_lost_position
-    }
-}
-
-impl<'a> Deref for MutableSelectionsCollection<'a> {
-    type Target = SelectionsCollection;
-    fn deref(&self) -> &Self::Target {
-        self.collection
-    }
-}
-
-impl<'a> DerefMut for MutableSelectionsCollection<'a> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        self.collection
-    }
-}
-
-// Panics if passed selections are not in order
-pub fn resolve_multiple<'a, D, I>(
-    selections: I,
-    snapshot: &MultiBufferSnapshot,
-) -> impl 'a + Iterator<Item = Selection<D>>
-where
-    D: TextDimension + Ord + Sub<D, Output = D> + std::fmt::Debug,
-    I: 'a + IntoIterator<Item = &'a Selection<Anchor>>,
-{
-    let (to_summarize, selections) = selections.into_iter().tee();
-    let mut summaries = snapshot
-        .summaries_for_anchors::<D, _>(
-            to_summarize
-                .flat_map(|s| [&s.start, &s.end])
-                .collect::<Vec<_>>(),
-        )
-        .into_iter();
-    selections.map(move |s| Selection {
-        id: s.id,
-        start: summaries.next().unwrap(),
-        end: summaries.next().unwrap(),
-        reversed: s.reversed,
-        goal: s.goal,
-    })
-}
-
-fn resolve<D: TextDimension + Ord + Sub<D, Output = D>>(
-    selection: &Selection<Anchor>,
-    buffer: &MultiBufferSnapshot,
-) -> Selection<D> {
-    selection.map(|p| p.summary::<D>(buffer))
-}

crates/editor2/src/test.rs 🔗

@@ -1,74 +0,0 @@
-pub mod editor_lsp_test_context;
-pub mod editor_test_context;
-
-use crate::{
-    display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint},
-    DisplayPoint, Editor, EditorMode, MultiBuffer,
-};
-
-use gpui::{Context, Model, Pixels, ViewContext};
-
-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();
-    }
-}
-
-// 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 font = cx.text_style().font();
-    let font_size: Pixels = 14usize.into();
-
-    let buffer = MultiBuffer::build_simple(&unmarked_text, cx);
-    let display_map = cx.new_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)
-}
-
-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);
-}
-
-// RA thinks this is dead code even though it is used in a whole lot of tests
-#[allow(dead_code)]
-#[cfg(any(test, feature = "test-support"))]
-pub(crate) fn build_editor(buffer: Model<MultiBuffer>, cx: &mut ViewContext<Editor>) -> Editor {
-    // todo!()
-    Editor::new(EditorMode::Full, buffer, None, /*None,*/ cx)
-}
-
-pub(crate) fn build_editor_with_project(
-    project: Model<Project>,
-    buffer: Model<MultiBuffer>,
-    cx: &mut ViewContext<Editor>,
-) -> Editor {
-    // todo!()
-    Editor::new(EditorMode::Full, buffer, Some(project), /*None,*/ cx)
-}

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

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

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

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

crates/feature_flags/src/feature_flags.rs 🔗

@@ -25,15 +25,18 @@ impl FeatureFlag for ChannelsAlpha {
 pub trait FeatureFlagViewExt<V: 'static> {
     fn observe_flag<T: FeatureFlag, F>(&mut self, callback: F) -> Subscription
     where
-        F: Fn(bool, &mut V, &mut ViewContext<V>) + 'static;
+        F: Fn(bool, &mut V, &mut ViewContext<V>) + Send + Sync + 'static;
 }
 
-impl<V: 'static> FeatureFlagViewExt<V> for ViewContext<'_, '_, V> {
+impl<V> FeatureFlagViewExt<V> for ViewContext<'_, V>
+where
+    V: 'static,
+{
     fn observe_flag<T: FeatureFlag, F>(&mut self, callback: F) -> Subscription
     where
         F: Fn(bool, &mut V, &mut ViewContext<V>) + 'static,
     {
-        self.observe_global::<FeatureFlags, _>(move |v, cx| {
+        self.observe_global::<FeatureFlags>(move |v, cx| {
             let feature_flags = cx.global::<FeatureFlags>();
             callback(feature_flags.has_flag(<T as FeatureFlag>::NAME), v, cx);
         })
@@ -49,16 +52,14 @@ pub trait FeatureFlagAppExt {
 
 impl FeatureFlagAppExt for AppContext {
     fn update_flags(&mut self, staff: bool, flags: Vec<String>) {
-        self.update_default_global::<FeatureFlags, _, _>(|feature_flags, _| {
-            feature_flags.staff = staff;
-            feature_flags.flags = flags;
-        })
+        let feature_flags = self.default_global::<FeatureFlags>();
+        feature_flags.staff = staff;
+        feature_flags.flags = flags;
     }
 
     fn set_staff(&mut self, staff: bool) {
-        self.update_default_global::<FeatureFlags, _, _>(|feature_flags, _| {
-            feature_flags.staff = staff;
-        })
+        let feature_flags = self.default_global::<FeatureFlags>();
+        feature_flags.staff = staff;
     }
 
     fn has_flag<T: FeatureFlag>(&self) -> bool {

crates/feature_flags2/Cargo.toml 🔗

@@ -1,12 +0,0 @@
-[package]
-name = "feature_flags2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/feature_flags2.rs"
-
-[dependencies]
-gpui = { package = "gpui2", path = "../gpui2" }
-anyhow.workspace = true

crates/feature_flags2/src/feature_flags2.rs 🔗

@@ -1,80 +0,0 @@
-use gpui::{AppContext, Subscription, ViewContext};
-
-#[derive(Default)]
-struct FeatureFlags {
-    flags: Vec<String>,
-    staff: bool,
-}
-
-impl FeatureFlags {
-    fn has_flag(&self, flag: &str) -> bool {
-        self.staff || self.flags.iter().find(|f| f.as_str() == flag).is_some()
-    }
-}
-
-pub trait FeatureFlag {
-    const NAME: &'static str;
-}
-
-pub enum ChannelsAlpha {}
-
-impl FeatureFlag for ChannelsAlpha {
-    const NAME: &'static str = "channels_alpha";
-}
-
-pub trait FeatureFlagViewExt<V: 'static> {
-    fn observe_flag<T: FeatureFlag, F>(&mut self, callback: F) -> Subscription
-    where
-        F: Fn(bool, &mut V, &mut ViewContext<V>) + Send + Sync + 'static;
-}
-
-impl<V> FeatureFlagViewExt<V> for ViewContext<'_, V>
-where
-    V: 'static,
-{
-    fn observe_flag<T: FeatureFlag, F>(&mut self, callback: F) -> Subscription
-    where
-        F: Fn(bool, &mut V, &mut ViewContext<V>) + 'static,
-    {
-        self.observe_global::<FeatureFlags>(move |v, cx| {
-            let feature_flags = cx.global::<FeatureFlags>();
-            callback(feature_flags.has_flag(<T as FeatureFlag>::NAME), v, cx);
-        })
-    }
-}
-
-pub trait FeatureFlagAppExt {
-    fn update_flags(&mut self, staff: bool, flags: Vec<String>);
-    fn set_staff(&mut self, staff: bool);
-    fn has_flag<T: FeatureFlag>(&self) -> bool;
-    fn is_staff(&self) -> bool;
-}
-
-impl FeatureFlagAppExt for AppContext {
-    fn update_flags(&mut self, staff: bool, flags: Vec<String>) {
-        let feature_flags = self.default_global::<FeatureFlags>();
-        feature_flags.staff = staff;
-        feature_flags.flags = flags;
-    }
-
-    fn set_staff(&mut self, staff: bool) {
-        let feature_flags = self.default_global::<FeatureFlags>();
-        feature_flags.staff = staff;
-    }
-
-    fn has_flag<T: FeatureFlag>(&self) -> bool {
-        if self.has_global::<FeatureFlags>() {
-            self.global::<FeatureFlags>().has_flag(T::NAME)
-        } else {
-            false
-        }
-    }
-
-    fn is_staff(&self) -> bool {
-        if self.has_global::<FeatureFlags>() {
-            return self.global::<FeatureFlags>().staff;
-        } else {
-            false
-        }
-    }
-}

crates/feedback/Cargo.toml 🔗

@@ -12,27 +12,33 @@ test-support = []
 
 [dependencies]
 client = { path = "../client" }
+db = { path = "../db" }
 editor = { path = "../editor" }
-language = { path = "../language" }
 gpui = { path = "../gpui" }
+language = { path = "../language" }
+menu = { path = "../menu" }
 project = { path = "../project" }
-regex.workspace = true
 search = { path = "../search" }
 settings = { path = "../settings" }
 theme = { path = "../theme" }
+ui = { path = "../ui" }
 util = { path = "../util" }
-workspace = { path = "../workspace" }
+workspace = { path = "../workspace"}
 
-log.workspace = true
-futures.workspace = true
-anyhow.workspace = true
-smallvec.workspace = true
+bitflags = "2.4.1"
 human_bytes = "0.4.1"
+
+anyhow.workspace = true
+futures.workspace = true
 isahc.workspace = true
 lazy_static.workspace = true
+log.workspace = true
 postage.workspace = true
+regex.workspace = true
 serde.workspace = true
 serde_derive.workspace = true
+smallvec.workspace = true
+smol.workspace = true
 sysinfo.workspace = true
 tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
 urlencoding = "2.1.2"

crates/feedback/src/deploy_feedback_button.rs 🔗

@@ -1,91 +1,49 @@
-use gpui::{
-    elements::*,
-    platform::{CursorStyle, MouseButton},
-    Entity, View, ViewContext, WeakViewHandle,
-};
+use gpui::{Render, ViewContext, WeakView};
+use ui::{prelude::*, ButtonCommon, Icon, IconButton, Tooltip};
 use workspace::{item::ItemHandle, StatusItemView, Workspace};
 
-use crate::feedback_editor::{FeedbackEditor, GiveFeedback};
+use crate::{feedback_modal::FeedbackModal, GiveFeedback};
 
 pub struct DeployFeedbackButton {
-    active: bool,
-    workspace: WeakViewHandle<Workspace>,
-}
-
-impl Entity for DeployFeedbackButton {
-    type Event = ();
+    workspace: WeakView<Workspace>,
 }
 
 impl DeployFeedbackButton {
     pub fn new(workspace: &Workspace) -> Self {
         DeployFeedbackButton {
-            active: false,
             workspace: workspace.weak_handle(),
         }
     }
 }
 
-impl View for DeployFeedbackButton {
-    fn ui_name() -> &'static str {
-        "DeployFeedbackButton"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let active = self.active;
-        let theme = theme::current(cx).clone();
-        Stack::new()
-            .with_child(
-                MouseEventHandler::new::<Self, _>(0, cx, |state, _| {
-                    let style = &theme
-                        .workspace
-                        .status_bar
-                        .panel_buttons
-                        .button
-                        .in_state(active)
-                        .style_for(state);
-
-                    Svg::new("icons/feedback.svg")
-                        .with_color(style.icon_color)
-                        .constrained()
-                        .with_width(style.icon_size)
-                        .aligned()
-                        .constrained()
-                        .with_width(style.icon_size)
-                        .with_height(style.icon_size)
-                        .contained()
-                        .with_style(style.container)
-                })
-                .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, this, cx| {
-                    if !active {
-                        if let Some(workspace) = this.workspace.upgrade(cx) {
-                            workspace
-                                .update(cx, |workspace, cx| FeedbackEditor::deploy(workspace, cx))
-                        }
-                    }
+impl Render for DeployFeedbackButton {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        let is_open = self
+            .workspace
+            .upgrade()
+            .and_then(|workspace| {
+                workspace.update(cx, |workspace, cx| {
+                    workspace.active_modal::<FeedbackModal>(cx)
                 })
-                .with_tooltip::<Self>(
-                    0,
-                    "Send Feedback",
-                    Some(Box::new(GiveFeedback)),
-                    theme.tooltip.clone(),
-                    cx,
-                ),
-            )
-            .into_any()
+            })
+            .is_some();
+        IconButton::new("give-feedback", Icon::Envelope)
+            .style(ui::ButtonStyle::Subtle)
+            .icon_size(IconSize::Small)
+            .selected(is_open)
+            .tooltip(|cx| Tooltip::text("Share Feedback", cx))
+            .on_click(|_, cx| {
+                cx.dispatch_action(Box::new(GiveFeedback));
+            })
+            .into_any_element()
     }
 }
 
 impl StatusItemView for DeployFeedbackButton {
-    fn set_active_pane_item(&mut self, item: Option<&dyn ItemHandle>, cx: &mut ViewContext<Self>) {
-        if let Some(item) = item {
-            if let Some(_) = item.downcast::<FeedbackEditor>() {
-                self.active = true;
-                cx.notify();
-                return;
-            }
-        }
-        self.active = false;
-        cx.notify();
+    fn set_active_pane_item(
+        &mut self,
+        _item: Option<&dyn ItemHandle>,
+        _cx: &mut ViewContext<Self>,
+    ) {
     }
 }

crates/feedback/src/feedback.rs 🔗

@@ -1,12 +1,13 @@
+use gpui::{actions, AppContext, ClipboardItem, PromptLevel};
+use system_specs::SystemSpecs;
+use workspace::Workspace;
+
 pub mod deploy_feedback_button;
-pub mod feedback_editor;
-pub mod feedback_info_text;
-pub mod submit_feedback_button;
+pub mod feedback_modal;
+
+actions!(feedback, [GiveFeedback, SubmitFeedback]);
 
 mod system_specs;
-use gpui::{actions, platform::PromptLevel, AppContext, ClipboardItem, ViewContext};
-use system_specs::SystemSpecs;
-use workspace::Workspace;
 
 actions!(
     zed,
@@ -19,44 +20,42 @@ actions!(
 );
 
 pub fn init(cx: &mut AppContext) {
-    feedback_editor::init(cx);
-
-    cx.add_action(
-        move |_: &mut Workspace,
-              _: &CopySystemSpecsIntoClipboard,
-              cx: &mut ViewContext<Workspace>| {
-            let specs = SystemSpecs::new(&cx).to_string();
-            cx.prompt(
-                PromptLevel::Info,
-                &format!("Copied into clipboard:\n\n{specs}"),
-                &["OK"],
-            );
-            let item = ClipboardItem::new(specs.clone());
-            cx.write_to_clipboard(item);
-        },
-    );
-
-    cx.add_action(
-        |_: &mut Workspace, _: &RequestFeature, cx: &mut ViewContext<Workspace>| {
-            let url = "https://github.com/zed-industries/community/issues/new?assignees=&labels=enhancement%2Ctriage&template=0_feature_request.yml";
-            cx.platform().open_url(url);
-        },
-    );
-
-    cx.add_action(
-        move |_: &mut Workspace, _: &FileBugReport, cx: &mut ViewContext<Workspace>| {
-            let url = format!(
-                "https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml&environment={}",
-                urlencoding::encode(&SystemSpecs::new(&cx).to_string())
-            );
-            cx.platform().open_url(&url);
-        },
-    );
-
-    cx.add_global_action(open_zed_community_repo);
-}
-
-pub fn open_zed_community_repo(_: &OpenZedCommunityRepo, cx: &mut AppContext) {
-    let url = "https://github.com/zed-industries/community";
-    cx.platform().open_url(&url);
+    // TODO: a way to combine these two into one?
+    cx.observe_new_views(feedback_modal::FeedbackModal::register)
+        .detach();
+
+    cx.observe_new_views(|workspace: &mut Workspace, _| {
+        workspace
+            .register_action(|_, _: &CopySystemSpecsIntoClipboard, cx| {
+                    let specs = SystemSpecs::new(&cx).to_string();
+
+                    let prompt = cx.prompt(
+                        PromptLevel::Info,
+                        &format!("Copied into clipboard:\n\n{specs}"),
+                        &["OK"],
+                    );
+                    cx.spawn(|_, _cx| async move {
+                        prompt.await.ok();
+                    })
+                    .detach();
+                    let item = ClipboardItem::new(specs.clone());
+                    cx.write_to_clipboard(item);
+                })
+            .register_action(|_, _: &RequestFeature, cx| {
+                let url = "https://github.com/zed-industries/community/issues/new?assignees=&labels=enhancement%2Ctriage&template=0_feature_request.yml";
+                cx.open_url(url);
+            })
+            .register_action(move |_, _: &FileBugReport, cx| {
+                let url = format!(
+                    "https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml&environment={}",
+                    urlencoding::encode(&SystemSpecs::new(&cx).to_string())
+                );
+                cx.open_url(&url);
+            })
+            .register_action(move |_, _: &OpenZedCommunityRepo, cx| {
+                let url = "https://github.com/zed-industries/community";
+                cx.open_url(&url);
+        });
+    })
+    .detach();
 }

crates/feedback/src/feedback_editor.rs 🔗

@@ -1,442 +0,0 @@
-use crate::system_specs::SystemSpecs;
-use anyhow::bail;
-use client::{Client, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};
-use editor::{Anchor, Editor};
-use futures::AsyncReadExt;
-use gpui::{
-    actions,
-    elements::{ChildView, Flex, Label, ParentElement, Svg},
-    platform::PromptLevel,
-    serde_json, AnyElement, AnyViewHandle, AppContext, Element, Entity, ModelHandle, Task, View,
-    ViewContext, ViewHandle,
-};
-use isahc::Request;
-use language::Buffer;
-use postage::prelude::Stream;
-use project::{search::SearchQuery, Project};
-use regex::Regex;
-use serde::Serialize;
-use smallvec::SmallVec;
-use std::{
-    any::TypeId,
-    borrow::Cow,
-    ops::{Range, RangeInclusive},
-    sync::Arc,
-};
-use util::ResultExt;
-use workspace::{
-    item::{Item, ItemEvent, ItemHandle},
-    searchable::{SearchableItem, SearchableItemHandle},
-    Workspace,
-};
-
-const FEEDBACK_CHAR_LIMIT: RangeInclusive<usize> = 10..=5000;
-const FEEDBACK_SUBMISSION_ERROR_TEXT: &str =
-    "Feedback failed to submit, see error log for details.";
-
-actions!(feedback, [GiveFeedback, SubmitFeedback]);
-
-pub fn init(cx: &mut AppContext) {
-    cx.add_action({
-        move |workspace: &mut Workspace, _: &GiveFeedback, cx: &mut ViewContext<Workspace>| {
-            FeedbackEditor::deploy(workspace, cx);
-        }
-    });
-}
-
-#[derive(Serialize)]
-struct FeedbackRequestBody<'a> {
-    feedback_text: &'a str,
-    email: Option<String>,
-    metrics_id: Option<Arc<str>>,
-    installation_id: Option<Arc<str>>,
-    system_specs: SystemSpecs,
-    is_staff: bool,
-    token: &'a str,
-}
-
-#[derive(Clone)]
-pub(crate) struct FeedbackEditor {
-    system_specs: SystemSpecs,
-    editor: ViewHandle<Editor>,
-    project: ModelHandle<Project>,
-    pub allow_submission: bool,
-}
-
-impl FeedbackEditor {
-    fn new(
-        system_specs: SystemSpecs,
-        project: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
-        cx: &mut ViewContext<Self>,
-    ) -> Self {
-        let editor = cx.add_view(|cx| {
-            let mut editor = Editor::for_buffer(buffer, Some(project.clone()), cx);
-            editor.set_vertical_scroll_margin(5, cx);
-            editor
-        });
-
-        cx.subscribe(&editor, |_, _, e, cx| cx.emit(e.clone()))
-            .detach();
-
-        Self {
-            system_specs: system_specs.clone(),
-            editor,
-            project,
-            allow_submission: true,
-        }
-    }
-
-    pub fn submit(&mut self, cx: &mut ViewContext<Self>) -> Task<anyhow::Result<()>> {
-        if !self.allow_submission {
-            return Task::ready(Ok(()));
-        }
-
-        let feedback_text = self.editor.read(cx).text(cx);
-        let feedback_char_count = feedback_text.chars().count();
-        let feedback_text = feedback_text.trim().to_string();
-
-        let error = if feedback_char_count < *FEEDBACK_CHAR_LIMIT.start() {
-            Some(format!(
-                "Feedback can't be shorter than {} characters.",
-                FEEDBACK_CHAR_LIMIT.start()
-            ))
-        } else if feedback_char_count > *FEEDBACK_CHAR_LIMIT.end() {
-            Some(format!(
-                "Feedback can't be longer than {} characters.",
-                FEEDBACK_CHAR_LIMIT.end()
-            ))
-        } else {
-            None
-        };
-
-        if let Some(error) = error {
-            cx.prompt(PromptLevel::Critical, &error, &["OK"]);
-            return Task::ready(Ok(()));
-        }
-
-        let mut answer = cx.prompt(
-            PromptLevel::Info,
-            "Ready to submit your feedback?",
-            &["Yes, Submit!", "No"],
-        );
-
-        let client = cx.global::<Arc<Client>>().clone();
-        let specs = self.system_specs.clone();
-
-        cx.spawn(|this, mut cx| async move {
-            let answer = answer.recv().await;
-
-            if answer == Some(0) {
-                this.update(&mut cx, |feedback_editor, cx| {
-                    feedback_editor.set_allow_submission(false, cx);
-                })
-                .log_err();
-
-                match FeedbackEditor::submit_feedback(&feedback_text, client, specs).await {
-                    Ok(_) => {
-                        this.update(&mut cx, |_, cx| cx.emit(editor::Event::Closed))
-                            .log_err();
-                    }
-
-                    Err(error) => {
-                        log::error!("{}", error);
-                        this.update(&mut cx, |feedback_editor, cx| {
-                            cx.prompt(
-                                PromptLevel::Critical,
-                                FEEDBACK_SUBMISSION_ERROR_TEXT,
-                                &["OK"],
-                            );
-                            feedback_editor.set_allow_submission(true, cx);
-                        })
-                        .log_err();
-                    }
-                }
-            }
-        })
-        .detach();
-
-        Task::ready(Ok(()))
-    }
-
-    fn set_allow_submission(&mut self, allow_submission: bool, cx: &mut ViewContext<Self>) {
-        self.allow_submission = allow_submission;
-        cx.notify();
-    }
-
-    async fn submit_feedback(
-        feedback_text: &str,
-        zed_client: Arc<Client>,
-        system_specs: SystemSpecs,
-    ) -> anyhow::Result<()> {
-        let feedback_endpoint = format!("{}/api/feedback", *ZED_SERVER_URL);
-
-        let telemetry = zed_client.telemetry();
-        let metrics_id = telemetry.metrics_id();
-        let installation_id = telemetry.installation_id();
-        let is_staff = telemetry.is_staff();
-        let http_client = zed_client.http_client();
-
-        let re = Regex::new(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b").unwrap();
-
-        let emails: Vec<&str> = re
-            .captures_iter(feedback_text)
-            .map(|capture| capture.get(0).unwrap().as_str())
-            .collect();
-
-        let email = emails.first().map(|e| e.to_string());
-
-        let request = FeedbackRequestBody {
-            feedback_text: &feedback_text,
-            email,
-            metrics_id,
-            installation_id,
-            system_specs,
-            is_staff: is_staff.unwrap_or(false),
-            token: ZED_SECRET_CLIENT_TOKEN,
-        };
-
-        let json_bytes = serde_json::to_vec(&request)?;
-
-        let request = Request::post(feedback_endpoint)
-            .header("content-type", "application/json")
-            .body(json_bytes.into())?;
-
-        let mut response = http_client.send(request).await?;
-        let mut body = String::new();
-        response.body_mut().read_to_string(&mut body).await?;
-
-        let response_status = response.status();
-
-        if !response_status.is_success() {
-            bail!("Feedback API failed with error: {}", response_status)
-        }
-
-        Ok(())
-    }
-}
-
-impl FeedbackEditor {
-    pub fn deploy(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
-        let markdown = workspace
-            .app_state()
-            .languages
-            .language_for_name("Markdown");
-        cx.spawn(|workspace, mut cx| async move {
-            let markdown = markdown.await.log_err();
-            workspace
-                .update(&mut cx, |workspace, cx| {
-                    workspace.with_local_workspace(cx, |workspace, cx| {
-                        let project = workspace.project().clone();
-                        let buffer = project
-                            .update(cx, |project, cx| project.create_buffer("", markdown, cx))
-                            .expect("creating buffers on a local workspace always succeeds");
-                        let system_specs = SystemSpecs::new(cx);
-                        let feedback_editor = cx
-                            .add_view(|cx| FeedbackEditor::new(system_specs, project, buffer, cx));
-                        workspace.add_item(Box::new(feedback_editor), cx);
-                    })
-                })?
-                .await
-        })
-        .detach_and_log_err(cx);
-    }
-}
-
-impl View for FeedbackEditor {
-    fn ui_name() -> &'static str {
-        "FeedbackEditor"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        ChildView::new(&self.editor, cx).into_any()
-    }
-
-    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
-        if cx.is_self_focused() {
-            cx.focus(&self.editor);
-        }
-    }
-}
-
-impl Entity for FeedbackEditor {
-    type Event = editor::Event;
-}
-
-impl Item for FeedbackEditor {
-    fn tab_tooltip_text(&self, _: &AppContext) -> Option<Cow<str>> {
-        Some("Send Feedback".into())
-    }
-
-    fn tab_content<T: 'static>(
-        &self,
-        _: Option<usize>,
-        style: &theme::Tab,
-        _: &AppContext,
-    ) -> AnyElement<T> {
-        Flex::row()
-            .with_child(
-                Svg::new("icons/feedback.svg")
-                    .with_color(style.label.text.color)
-                    .constrained()
-                    .with_width(style.type_icon_width)
-                    .aligned()
-                    .contained()
-                    .with_margin_right(style.spacing),
-            )
-            .with_child(
-                Label::new("Send Feedback", style.label.clone())
-                    .aligned()
-                    .contained(),
-            )
-            .into_any()
-    }
-
-    fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) {
-        self.editor.for_each_project_item(cx, f)
-    }
-
-    fn is_singleton(&self, _: &AppContext) -> bool {
-        true
-    }
-
-    fn can_save(&self, _: &AppContext) -> bool {
-        true
-    }
-
-    fn save(
-        &mut self,
-        _: ModelHandle<Project>,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<anyhow::Result<()>> {
-        self.submit(cx)
-    }
-
-    fn save_as(
-        &mut self,
-        _: ModelHandle<Project>,
-        _: std::path::PathBuf,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<anyhow::Result<()>> {
-        self.submit(cx)
-    }
-
-    fn reload(
-        &mut self,
-        _: ModelHandle<Project>,
-        _: &mut ViewContext<Self>,
-    ) -> Task<anyhow::Result<()>> {
-        Task::Ready(Some(Ok(())))
-    }
-
-    fn clone_on_split(
-        &self,
-        _workspace_id: workspace::WorkspaceId,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<Self>
-    where
-        Self: Sized,
-    {
-        let buffer = self
-            .editor
-            .read(cx)
-            .buffer()
-            .read(cx)
-            .as_singleton()
-            .expect("Feedback buffer is only ever singleton");
-
-        Some(Self::new(
-            self.system_specs.clone(),
-            self.project.clone(),
-            buffer.clone(),
-            cx,
-        ))
-    }
-
-    fn as_searchable(&self, handle: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
-        Some(Box::new(handle.clone()))
-    }
-
-    fn act_as_type<'a>(
-        &'a self,
-        type_id: TypeId,
-        self_handle: &'a ViewHandle<Self>,
-        _: &'a AppContext,
-    ) -> Option<&'a AnyViewHandle> {
-        if type_id == TypeId::of::<Self>() {
-            Some(self_handle)
-        } else if type_id == TypeId::of::<Editor>() {
-            Some(&self.editor)
-        } else {
-            None
-        }
-    }
-
-    fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
-        Editor::to_item_events(event)
-    }
-}
-
-impl SearchableItem for FeedbackEditor {
-    type Match = Range<Anchor>;
-
-    fn to_search_event(
-        &mut self,
-        event: &Self::Event,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<workspace::searchable::SearchEvent> {
-        self.editor
-            .update(cx, |editor, cx| editor.to_search_event(event, cx))
-    }
-
-    fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
-        self.editor
-            .update(cx, |editor, cx| editor.clear_matches(cx))
-    }
-
-    fn update_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) {
-        self.editor
-            .update(cx, |editor, cx| editor.update_matches(matches, cx))
-    }
-
-    fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
-        self.editor
-            .update(cx, |editor, cx| editor.query_suggestion(cx))
-    }
-
-    fn activate_match(
-        &mut self,
-        index: usize,
-        matches: Vec<Self::Match>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.editor
-            .update(cx, |editor, cx| editor.activate_match(index, matches, cx))
-    }
-
-    fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) {
-        self.editor
-            .update(cx, |e, cx| e.select_matches(matches, cx))
-    }
-    fn replace(&mut self, matches: &Self::Match, query: &SearchQuery, cx: &mut ViewContext<Self>) {
-        self.editor
-            .update(cx, |e, cx| e.replace(matches, query, cx));
-    }
-    fn find_matches(
-        &mut self,
-        query: Arc<project::search::SearchQuery>,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<Vec<Self::Match>> {
-        self.editor
-            .update(cx, |editor, cx| editor.find_matches(query, cx))
-    }
-
-    fn active_match_index(
-        &mut self,
-        matches: Vec<Self::Match>,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<usize> {
-        self.editor
-            .update(cx, |editor, cx| editor.active_match_index(matches, cx))
-    }
-}

crates/feedback/src/feedback_info_text.rs 🔗

@@ -1,94 +0,0 @@
-use gpui::{
-    elements::{Flex, Label, MouseEventHandler, ParentElement, Text},
-    platform::{CursorStyle, MouseButton},
-    AnyElement, Element, Entity, View, ViewContext, ViewHandle,
-};
-use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView};
-
-use crate::{feedback_editor::FeedbackEditor, open_zed_community_repo, OpenZedCommunityRepo};
-
-pub struct FeedbackInfoText {
-    active_item: Option<ViewHandle<FeedbackEditor>>,
-}
-
-impl FeedbackInfoText {
-    pub fn new() -> Self {
-        Self {
-            active_item: Default::default(),
-        }
-    }
-}
-
-impl Entity for FeedbackInfoText {
-    type Event = ();
-}
-
-impl View for FeedbackInfoText {
-    fn ui_name() -> &'static str {
-        "FeedbackInfoText"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = theme::current(cx).clone();
-
-        Flex::row()
-            .with_child(
-                Text::new(
-                    "Share your feedback. Include your email for replies. For issues and discussions, visit the ",
-                    theme.feedback.info_text_default.text.clone(),
-                )
-                .with_soft_wrap(false)
-                .aligned(),
-            )
-            .with_child(
-                MouseEventHandler::new::<OpenZedCommunityRepo, _>(0, cx, |state, _| {
-                    let style = if state.hovered() {
-                        &theme.feedback.link_text_hover
-                    } else {
-                        &theme.feedback.link_text_default
-                    };
-                    Label::new("community repo", style.text.clone())
-                        .contained()
-                        .with_style(style.container)
-                        .aligned()
-                        .left()
-                        .clipped()
-                })
-                .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, |_, _, cx| {
-                    open_zed_community_repo(&Default::default(), cx)
-                }),
-            )
-            .with_child(
-                Text::new(".", theme.feedback.info_text_default.text.clone())
-                    .with_soft_wrap(false)
-                    .aligned(),
-            )
-            .contained()
-            .with_style(theme.feedback.info_text_default.container)
-            .aligned()
-            .left()
-            .clipped()
-            .into_any()
-    }
-}
-
-impl ToolbarItemView for FeedbackInfoText {
-    fn set_active_pane_item(
-        &mut self,
-        active_pane_item: Option<&dyn ItemHandle>,
-        cx: &mut ViewContext<Self>,
-    ) -> workspace::ToolbarItemLocation {
-        cx.notify();
-        if let Some(feedback_editor) = active_pane_item.and_then(|i| i.downcast::<FeedbackEditor>())
-        {
-            self.active_item = Some(feedback_editor);
-            ToolbarItemLocation::PrimaryLeft {
-                flex: Some((1., false)),
-            }
-        } else {
-            self.active_item = None;
-            ToolbarItemLocation::Hidden
-        }
-    }
-}

crates/feedback/src/submit_feedback_button.rs 🔗

@@ -1,108 +0,0 @@
-use crate::feedback_editor::{FeedbackEditor, SubmitFeedback};
-use anyhow::Result;
-use gpui::{
-    elements::{Label, MouseEventHandler},
-    platform::{CursorStyle, MouseButton},
-    AnyElement, AppContext, Element, Entity, Task, View, ViewContext, ViewHandle,
-};
-use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView};
-
-pub fn init(cx: &mut AppContext) {
-    cx.add_async_action(SubmitFeedbackButton::submit);
-}
-
-pub struct SubmitFeedbackButton {
-    pub(crate) active_item: Option<ViewHandle<FeedbackEditor>>,
-}
-
-impl SubmitFeedbackButton {
-    pub fn new() -> Self {
-        Self {
-            active_item: Default::default(),
-        }
-    }
-
-    pub fn submit(
-        &mut self,
-        _: &SubmitFeedback,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<Task<Result<()>>> {
-        if let Some(active_item) = self.active_item.as_ref() {
-            Some(active_item.update(cx, |feedback_editor, cx| feedback_editor.submit(cx)))
-        } else {
-            None
-        }
-    }
-}
-
-impl Entity for SubmitFeedbackButton {
-    type Event = ();
-}
-
-impl View for SubmitFeedbackButton {
-    fn ui_name() -> &'static str {
-        "SubmitFeedbackButton"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = theme::current(cx).clone();
-        let allow_submission = self
-            .active_item
-            .as_ref()
-            .map_or(true, |i| i.read(cx).allow_submission);
-
-        enum SubmitFeedbackButton {}
-        MouseEventHandler::new::<SubmitFeedbackButton, _>(0, cx, |state, _| {
-            let text;
-            let style = if allow_submission {
-                text = "Submit as Markdown";
-                theme.feedback.submit_button.style_for(state)
-            } else {
-                text = "Submitting...";
-                theme
-                    .feedback
-                    .submit_button
-                    .disabled
-                    .as_ref()
-                    .unwrap_or(&theme.feedback.submit_button.default)
-            };
-
-            Label::new(text, style.text.clone())
-                .contained()
-                .with_style(style.container)
-        })
-        .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, |_, this, cx| {
-            this.submit(&Default::default(), cx);
-        })
-        .aligned()
-        .contained()
-        .with_margin_left(theme.feedback.button_margin)
-        .with_tooltip::<Self>(
-            0,
-            "cmd-s",
-            Some(Box::new(SubmitFeedback)),
-            theme.tooltip.clone(),
-            cx,
-        )
-        .into_any()
-    }
-}
-
-impl ToolbarItemView for SubmitFeedbackButton {
-    fn set_active_pane_item(
-        &mut self,
-        active_pane_item: Option<&dyn ItemHandle>,
-        cx: &mut ViewContext<Self>,
-    ) -> workspace::ToolbarItemLocation {
-        cx.notify();
-        if let Some(feedback_editor) = active_pane_item.and_then(|i| i.downcast::<FeedbackEditor>())
-        {
-            self.active_item = Some(feedback_editor);
-            ToolbarItemLocation::PrimaryRight { flex: None }
-        } else {
-            self.active_item = None;
-            ToolbarItemLocation::Hidden
-        }
-    }
-}

crates/feedback/src/system_specs.rs 🔗

@@ -1,17 +1,14 @@
 use client::ZED_APP_VERSION;
-use gpui::{platform::AppVersion, AppContext};
+use gpui::AppContext;
 use human_bytes::human_bytes;
 use serde::Serialize;
 use std::{env, fmt::Display};
 use sysinfo::{System, SystemExt};
 use util::channel::ReleaseChannel;
 
-// TODO: Move this file out of feedback and into a more general place
-
 #[derive(Clone, Debug, Serialize)]
 pub struct SystemSpecs {
-    #[serde(serialize_with = "serialize_app_version")]
-    app_version: Option<AppVersion>,
+    app_version: Option<String>,
     release_channel: &'static str,
     os_name: &'static str,
     os_version: Option<String>,
@@ -21,16 +18,17 @@ pub struct SystemSpecs {
 
 impl SystemSpecs {
     pub fn new(cx: &AppContext) -> Self {
-        let platform = cx.platform();
-        let app_version = ZED_APP_VERSION.or_else(|| platform.app_version().ok());
+        let app_version = ZED_APP_VERSION
+            .or_else(|| cx.app_metadata().app_version)
+            .map(|v| v.to_string());
         let release_channel = cx.global::<ReleaseChannel>().display_name();
-        let os_name = platform.os_name();
+        let os_name = cx.app_metadata().os_name;
         let system = System::new_all();
         let memory = system.total_memory();
         let architecture = env::consts::ARCH;
-        let os_version = platform
-            .os_version()
-            .ok()
+        let os_version = cx
+            .app_metadata()
+            .os_version
             .map(|os_version| os_version.to_string());
 
         SystemSpecs {
@@ -68,10 +66,3 @@ impl Display for SystemSpecs {
         write!(f, "{system_specs}")
     }
 }
-
-fn serialize_app_version<S>(version: &Option<AppVersion>, serializer: S) -> Result<S::Ok, S::Error>
-where
-    S: serde::Serializer,
-{
-    version.map(|v| v.to_string()).serialize(serializer)
-}

crates/feedback2/Cargo.toml 🔗

@@ -1,47 +0,0 @@
-[package]
-name = "feedback2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/feedback2.rs"
-
-[features]
-test-support = []
-
-[dependencies]
-client = { package = "client2", path = "../client2" }
-db = { package = "db2", path = "../db2" }
-editor = { package = "editor2", path = "../editor2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-language = { package = "language2", path = "../language2" }
-menu = { package = "menu2", path = "../menu2" }
-project = { package = "project2", path = "../project2" }
-search = { package = "search2", path = "../search2" }
-settings = { package = "settings2", path = "../settings2" }
-theme = { package = "theme2", path = "../theme2" }
-ui = { package = "ui2", path = "../ui2" }
-util = { path = "../util" }
-workspace = { package = "workspace2", path = "../workspace2"}
-
-bitflags = "2.4.1"
-human_bytes = "0.4.1"
-
-anyhow.workspace = true
-futures.workspace = true
-isahc.workspace = true
-lazy_static.workspace = true
-log.workspace = true
-postage.workspace = true
-regex.workspace = true
-serde.workspace = true
-serde_derive.workspace = true
-smallvec.workspace = true
-smol.workspace = true
-sysinfo.workspace = true
-tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
-urlencoding = "2.1.2"
-
-[dev-dependencies]
-editor = { package = "editor2", path = "../editor2", features = ["test-support"] }

crates/feedback2/src/deploy_feedback_button.rs 🔗

@@ -1,49 +0,0 @@
-use gpui::{Render, ViewContext, WeakView};
-use ui::{prelude::*, ButtonCommon, Icon, IconButton, Tooltip};
-use workspace::{item::ItemHandle, StatusItemView, Workspace};
-
-use crate::{feedback_modal::FeedbackModal, GiveFeedback};
-
-pub struct DeployFeedbackButton {
-    workspace: WeakView<Workspace>,
-}
-
-impl DeployFeedbackButton {
-    pub fn new(workspace: &Workspace) -> Self {
-        DeployFeedbackButton {
-            workspace: workspace.weak_handle(),
-        }
-    }
-}
-
-impl Render for DeployFeedbackButton {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        let is_open = self
-            .workspace
-            .upgrade()
-            .and_then(|workspace| {
-                workspace.update(cx, |workspace, cx| {
-                    workspace.active_modal::<FeedbackModal>(cx)
-                })
-            })
-            .is_some();
-        IconButton::new("give-feedback", Icon::Envelope)
-            .style(ui::ButtonStyle::Subtle)
-            .icon_size(IconSize::Small)
-            .selected(is_open)
-            .tooltip(|cx| Tooltip::text("Share Feedback", cx))
-            .on_click(|_, cx| {
-                cx.dispatch_action(Box::new(GiveFeedback));
-            })
-            .into_any_element()
-    }
-}
-
-impl StatusItemView for DeployFeedbackButton {
-    fn set_active_pane_item(
-        &mut self,
-        _item: Option<&dyn ItemHandle>,
-        _cx: &mut ViewContext<Self>,
-    ) {
-    }
-}

crates/feedback2/src/feedback2.rs 🔗

@@ -1,61 +0,0 @@
-use gpui::{actions, AppContext, ClipboardItem, PromptLevel};
-use system_specs::SystemSpecs;
-use workspace::Workspace;
-
-pub mod deploy_feedback_button;
-pub mod feedback_modal;
-
-actions!(feedback, [GiveFeedback, SubmitFeedback]);
-
-mod system_specs;
-
-actions!(
-    zed,
-    [
-        CopySystemSpecsIntoClipboard,
-        FileBugReport,
-        RequestFeature,
-        OpenZedCommunityRepo
-    ]
-);
-
-pub fn init(cx: &mut AppContext) {
-    // TODO: a way to combine these two into one?
-    cx.observe_new_views(feedback_modal::FeedbackModal::register)
-        .detach();
-
-    cx.observe_new_views(|workspace: &mut Workspace, _| {
-        workspace
-            .register_action(|_, _: &CopySystemSpecsIntoClipboard, cx| {
-                    let specs = SystemSpecs::new(&cx).to_string();
-
-                    let prompt = cx.prompt(
-                        PromptLevel::Info,
-                        &format!("Copied into clipboard:\n\n{specs}"),
-                        &["OK"],
-                    );
-                    cx.spawn(|_, _cx| async move {
-                        prompt.await.ok();
-                    })
-                    .detach();
-                    let item = ClipboardItem::new(specs.clone());
-                    cx.write_to_clipboard(item);
-                })
-            .register_action(|_, _: &RequestFeature, cx| {
-                let url = "https://github.com/zed-industries/community/issues/new?assignees=&labels=enhancement%2Ctriage&template=0_feature_request.yml";
-                cx.open_url(url);
-            })
-            .register_action(move |_, _: &FileBugReport, cx| {
-                let url = format!(
-                    "https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml&environment={}",
-                    urlencoding::encode(&SystemSpecs::new(&cx).to_string())
-                );
-                cx.open_url(&url);
-            })
-            .register_action(move |_, _: &OpenZedCommunityRepo, cx| {
-                let url = "https://github.com/zed-industries/community";
-                cx.open_url(&url);
-        });
-    })
-    .detach();
-}

crates/feedback2/src/system_specs.rs 🔗

@@ -1,68 +0,0 @@
-use client::ZED_APP_VERSION;
-use gpui::AppContext;
-use human_bytes::human_bytes;
-use serde::Serialize;
-use std::{env, fmt::Display};
-use sysinfo::{System, SystemExt};
-use util::channel::ReleaseChannel;
-
-#[derive(Clone, Debug, Serialize)]
-pub struct SystemSpecs {
-    app_version: Option<String>,
-    release_channel: &'static str,
-    os_name: &'static str,
-    os_version: Option<String>,
-    memory: u64,
-    architecture: &'static str,
-}
-
-impl SystemSpecs {
-    pub fn new(cx: &AppContext) -> Self {
-        let app_version = ZED_APP_VERSION
-            .or_else(|| cx.app_metadata().app_version)
-            .map(|v| v.to_string());
-        let release_channel = cx.global::<ReleaseChannel>().display_name();
-        let os_name = cx.app_metadata().os_name;
-        let system = System::new_all();
-        let memory = system.total_memory();
-        let architecture = env::consts::ARCH;
-        let os_version = cx
-            .app_metadata()
-            .os_version
-            .map(|os_version| os_version.to_string());
-
-        SystemSpecs {
-            app_version,
-            release_channel,
-            os_name,
-            os_version,
-            memory,
-            architecture,
-        }
-    }
-}
-
-impl Display for SystemSpecs {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        let os_information = match &self.os_version {
-            Some(os_version) => format!("OS: {} {}", self.os_name, os_version),
-            None => format!("OS: {}", self.os_name),
-        };
-        let app_version_information = self
-            .app_version
-            .as_ref()
-            .map(|app_version| format!("Zed: v{} ({})", app_version, self.release_channel));
-        let system_specs = [
-            app_version_information,
-            Some(os_information),
-            Some(format!("Memory: {}", human_bytes(self.memory as f64))),
-            Some(format!("Architecture: {}", self.architecture)),
-        ]
-        .into_iter()
-        .flatten()
-        .collect::<Vec<String>>()
-        .join("\n");
-
-        write!(f, "{system_specs}")
-    }
-}

crates/file_finder/Cargo.toml 🔗

@@ -11,7 +11,7 @@ doctest = false
 [dependencies]
 editor = { path = "../editor" }
 collections = { path = "../collections" }
-fuzzy = { path = "../fuzzy" }
+fuzzy = {  path = "../fuzzy" }
 gpui = { path = "../gpui" }
 menu = { path = "../menu" }
 picker = { path = "../picker" }
@@ -20,8 +20,10 @@ settings = { path = "../settings" }
 text = { path = "../text" }
 util = { path = "../util" }
 theme = { path = "../theme" }
+ui = { path = "../ui" }
 workspace = { path = "../workspace" }
 postage.workspace = true
+serde.workspace = true
 
 [dev-dependencies]
 editor = { path = "../editor", features = ["test-support"] }

crates/file_finder/src/file_finder.rs 🔗

@@ -2,7 +2,8 @@ use collections::HashMap;
 use editor::{scroll::autoscroll::Autoscroll, Bias, Editor};
 use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
 use gpui::{
-    actions, elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext, WeakViewHandle,
+    actions, rems, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model,
+    ParentElement, Render, Styled, Task, View, ViewContext, VisualContext, WeakView,
 };
 use picker::{Picker, PickerDelegate};
 use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
@@ -14,14 +15,118 @@ use std::{
     },
 };
 use text::Point;
+use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing};
 use util::{paths::PathLikeWithPosition, post_inc, ResultExt};
-use workspace::Workspace;
+use workspace::{ModalView, Workspace};
 
-pub type FileFinder = Picker<FileFinderDelegate>;
+actions!(file_finder, [Toggle]);
+
+impl ModalView for FileFinder {}
+
+pub struct FileFinder {
+    picker: View<Picker<FileFinderDelegate>>,
+}
+
+pub fn init(cx: &mut AppContext) {
+    cx.observe_new_views(FileFinder::register).detach();
+}
+
+impl FileFinder {
+    fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+        workspace.register_action(|workspace, _: &Toggle, cx| {
+            let Some(file_finder) = workspace.active_modal::<Self>(cx) else {
+                Self::open(workspace, cx);
+                return;
+            };
+
+            file_finder.update(cx, |file_finder, cx| {
+                file_finder
+                    .picker
+                    .update(cx, |picker, cx| picker.cycle_selection(cx))
+            });
+        });
+    }
+
+    fn open(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
+        let project = workspace.project().read(cx);
+
+        let currently_opened_path = workspace
+            .active_item(cx)
+            .and_then(|item| item.project_path(cx))
+            .map(|project_path| {
+                let abs_path = project
+                    .worktree_for_id(project_path.worktree_id, cx)
+                    .map(|worktree| worktree.read(cx).abs_path().join(&project_path.path));
+                FoundPath::new(project_path, abs_path)
+            });
+
+        // if exists, bubble the currently opened path to the top
+        let history_items = currently_opened_path
+            .clone()
+            .into_iter()
+            .chain(
+                workspace
+                    .recent_navigation_history(Some(MAX_RECENT_SELECTIONS), cx)
+                    .into_iter()
+                    .filter(|(history_path, _)| {
+                        Some(history_path)
+                            != currently_opened_path
+                                .as_ref()
+                                .map(|found_path| &found_path.project)
+                    })
+                    .filter(|(_, history_abs_path)| {
+                        history_abs_path.as_ref()
+                            != currently_opened_path
+                                .as_ref()
+                                .and_then(|found_path| found_path.absolute.as_ref())
+                    })
+                    .filter(|(_, history_abs_path)| match history_abs_path {
+                        Some(abs_path) => history_file_exists(abs_path),
+                        None => true,
+                    })
+                    .map(|(history_path, abs_path)| FoundPath::new(history_path, abs_path)),
+            )
+            .collect();
+
+        let project = workspace.project().clone();
+        let weak_workspace = cx.view().downgrade();
+        workspace.toggle_modal(cx, |cx| {
+            let delegate = FileFinderDelegate::new(
+                cx.view().downgrade(),
+                weak_workspace,
+                project,
+                currently_opened_path,
+                history_items,
+                cx,
+            );
+
+            FileFinder::new(delegate, cx)
+        });
+    }
+
+    fn new(delegate: FileFinderDelegate, cx: &mut ViewContext<Self>) -> Self {
+        Self {
+            picker: cx.new_view(|cx| Picker::new(delegate, cx)),
+        }
+    }
+}
+
+impl EventEmitter<DismissEvent> for FileFinder {}
+impl FocusableView for FileFinder {
+    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
+        self.picker.focus_handle(cx)
+    }
+}
+impl Render for FileFinder {
+    fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
+        v_stack().w(rems(34.)).child(self.picker.clone())
+    }
+}
 
 pub struct FileFinderDelegate {
-    workspace: WeakViewHandle<Workspace>,
-    project: ModelHandle<Project>,
+    file_finder: WeakView<FileFinder>,
+    workspace: WeakView<Workspace>,
+    project: Model<Project>,
     search_count: usize,
     latest_search_id: usize,
     latest_search_did_cancel: bool,
@@ -165,91 +270,8 @@ impl FoundPath {
     }
 }
 
-actions!(file_finder, [Toggle]);
-
-pub fn init(cx: &mut AppContext) {
-    cx.add_action(toggle_or_cycle_file_finder);
-    FileFinder::init(cx);
-}
-
 const MAX_RECENT_SELECTIONS: usize = 20;
 
-fn toggle_or_cycle_file_finder(
-    workspace: &mut Workspace,
-    _: &Toggle,
-    cx: &mut ViewContext<Workspace>,
-) {
-    match workspace.modal::<FileFinder>() {
-        Some(file_finder) => file_finder.update(cx, |file_finder, cx| {
-            let current_index = file_finder.delegate().selected_index();
-            file_finder.select_next(&menu::SelectNext, cx);
-            let new_index = file_finder.delegate().selected_index();
-            if current_index == new_index {
-                file_finder.select_first(&menu::SelectFirst, cx);
-            }
-        }),
-        None => {
-            workspace.toggle_modal(cx, |workspace, cx| {
-                let project = workspace.project().read(cx);
-
-                let currently_opened_path = workspace
-                    .active_item(cx)
-                    .and_then(|item| item.project_path(cx))
-                    .map(|project_path| {
-                        let abs_path = project
-                            .worktree_for_id(project_path.worktree_id, cx)
-                            .map(|worktree| worktree.read(cx).abs_path().join(&project_path.path));
-                        FoundPath::new(project_path, abs_path)
-                    });
-
-                // if exists, bubble the currently opened path to the top
-                let history_items = currently_opened_path
-                    .clone()
-                    .into_iter()
-                    .chain(
-                        workspace
-                            .recent_navigation_history(Some(MAX_RECENT_SELECTIONS), cx)
-                            .into_iter()
-                            .filter(|(history_path, _)| {
-                                Some(history_path)
-                                    != currently_opened_path
-                                        .as_ref()
-                                        .map(|found_path| &found_path.project)
-                            })
-                            .filter(|(_, history_abs_path)| {
-                                history_abs_path.as_ref()
-                                    != currently_opened_path
-                                        .as_ref()
-                                        .and_then(|found_path| found_path.absolute.as_ref())
-                            })
-                            .filter(|(_, history_abs_path)| match history_abs_path {
-                                Some(abs_path) => history_file_exists(abs_path),
-                                None => true,
-                            })
-                            .map(|(history_path, abs_path)| FoundPath::new(history_path, abs_path)),
-                    )
-                    .collect();
-
-                let project = workspace.project().clone();
-                let workspace = cx.handle().downgrade();
-                let finder = cx.add_view(|cx| {
-                    Picker::new(
-                        FileFinderDelegate::new(
-                            workspace,
-                            project,
-                            currently_opened_path,
-                            history_items,
-                            cx,
-                        ),
-                        cx,
-                    )
-                });
-                finder
-            });
-        }
-    }
-}
-
 #[cfg(not(test))]
 fn history_file_exists(abs_path: &PathBuf) -> bool {
     abs_path.exists()
@@ -282,17 +304,23 @@ impl FileSearchQuery {
 
 impl FileFinderDelegate {
     fn new(
-        workspace: WeakViewHandle<Workspace>,
-        project: ModelHandle<Project>,
+        file_finder: WeakView<FileFinder>,
+        workspace: WeakView<Workspace>,
+        project: Model<Project>,
         currently_opened_path: Option<FoundPath>,
         history_items: Vec<FoundPath>,
         cx: &mut ViewContext<FileFinder>,
     ) -> Self {
-        cx.observe(&project, |picker, _, cx| {
-            picker.update_matches(picker.query(cx), cx);
+        cx.observe(&project, |file_finder, _, cx| {
+            //todo!() We should probably not re-render on every project anything
+            file_finder
+                .picker
+                .update(cx, |picker, cx| picker.refresh(cx))
         })
         .detach();
+
         Self {
+            file_finder,
             workspace,
             project,
             search_count: 0,
@@ -310,7 +338,7 @@ impl FileFinderDelegate {
     fn spawn_search(
         &mut self,
         query: PathLikeWithPosition<FileSearchQuery>,
-        cx: &mut ViewContext<FileFinder>,
+        cx: &mut ViewContext<Picker<Self>>,
     ) -> Task<()> {
         let relative_to = self
             .currently_opened_path
@@ -343,14 +371,14 @@ impl FileFinderDelegate {
                 false,
                 100,
                 &cancel_flag,
-                cx.background(),
+                cx.background_executor().clone(),
             )
             .await;
             let did_cancel = cancel_flag.load(atomic::Ordering::Relaxed);
             picker
                 .update(&mut cx, |picker, cx| {
                     picker
-                        .delegate_mut()
+                        .delegate
                         .set_search_matches(search_id, did_cancel, query, matches, cx)
                 })
                 .log_err();
@@ -363,7 +391,7 @@ impl FileFinderDelegate {
         did_cancel: bool,
         query: PathLikeWithPosition<FileSearchQuery>,
         matches: Vec<PathMatch>,
-        cx: &mut ViewContext<FileFinder>,
+        cx: &mut ViewContext<Picker<Self>>,
     ) {
         if search_id >= self.latest_search_id {
             self.latest_search_id = search_id;
@@ -495,6 +523,8 @@ impl FileFinderDelegate {
 }
 
 impl PickerDelegate for FileFinderDelegate {
+    type ListItem = ListItem;
+
     fn placeholder_text(&self) -> Arc<str> {
         "Search project files...".into()
     }
@@ -507,12 +537,25 @@ impl PickerDelegate for FileFinderDelegate {
         self.selected_index.unwrap_or(0)
     }
 
-    fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<FileFinder>) {
+    fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
         self.selected_index = Some(ix);
         cx.notify();
     }
 
-    fn update_matches(&mut self, raw_query: String, cx: &mut ViewContext<FileFinder>) -> Task<()> {
+    fn separators_after_indices(&self) -> Vec<usize> {
+        let history_items = self.matches.history.len();
+        if history_items == 0 || self.matches.search.is_empty() {
+            Vec::new()
+        } else {
+            vec![history_items - 1]
+        }
+    }
+
+    fn update_matches(
+        &mut self,
+        raw_query: String,
+        cx: &mut ViewContext<Picker<Self>>,
+    ) -> Task<()> {
         let raw_query = raw_query.trim();
         if raw_query.is_empty() {
             let project = self.project.read(cx);
@@ -550,9 +593,9 @@ impl PickerDelegate for FileFinderDelegate {
         }
     }
 
-    fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<FileFinder>) {
+    fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<Picker<FileFinderDelegate>>) {
         if let Some(m) = self.matches.get(self.selected_index()) {
-            if let Some(workspace) = self.workspace.upgrade(cx) {
+            if let Some(workspace) = self.workspace.upgrade() {
                 let open_task = workspace.update(cx, move |workspace, cx| {
                     let split_or_open = |workspace: &mut Workspace, project_path, cx| {
                         if secondary {
@@ -628,6 +671,8 @@ impl PickerDelegate for FileFinderDelegate {
                     .and_then(|query| query.column)
                     .unwrap_or(0)
                     .saturating_sub(1);
+                let finder = self.file_finder.clone();
+
                 cx.spawn(|_, mut cx| async move {
                     let item = open_task.await.log_err()?;
                     if let Some(row) = row {
@@ -646,10 +691,7 @@ impl PickerDelegate for FileFinderDelegate {
                                 .log_err();
                         }
                     }
-                    workspace
-                        .downgrade()
-                        .update(&mut cx, |workspace, cx| workspace.dismiss_modal(cx))
-                        .log_err();
+                    finder.update(&mut cx, |_, cx| cx.emit(DismissEvent)).ok()?;
 
                     Some(())
                 })
@@ -658,44 +700,47 @@ impl PickerDelegate for FileFinderDelegate {
         }
     }
 
-    fn dismissed(&mut self, _: &mut ViewContext<FileFinder>) {}
+    fn dismissed(&mut self, cx: &mut ViewContext<Picker<FileFinderDelegate>>) {
+        self.file_finder
+            .update(cx, |_, cx| cx.emit(DismissEvent))
+            .log_err();
+    }
 
     fn render_match(
         &self,
         ix: usize,
-        mouse_state: &mut MouseState,
         selected: bool,
-        cx: &AppContext,
-    ) -> AnyElement<Picker<Self>> {
+        cx: &mut ViewContext<Picker<Self>>,
+    ) -> Option<Self::ListItem> {
         let path_match = self
             .matches
             .get(ix)
             .expect("Invalid matches state: no element for index {ix}");
-        let theme = theme::current(cx);
-        let style = theme.picker.item.in_state(selected).style_for(mouse_state);
+
         let (file_name, file_name_positions, full_path, full_path_positions) =
             self.labels_for_match(path_match, cx, ix);
-        Flex::column()
-            .with_child(
-                Label::new(file_name, style.label.clone()).with_highlights(file_name_positions),
-            )
-            .with_child(
-                Label::new(full_path, style.label.clone()).with_highlights(full_path_positions),
-            )
-            .flex(1., false)
-            .contained()
-            .with_style(style.container)
-            .into_any_named("match")
+
+        Some(
+            ListItem::new(ix)
+                .spacing(ListItemSpacing::Sparse)
+                .inset(true)
+                .selected(selected)
+                .child(
+                    v_stack()
+                        .child(HighlightedLabel::new(file_name, file_name_positions))
+                        .child(HighlightedLabel::new(full_path, full_path_positions)),
+                ),
+        )
     }
 }
 
 #[cfg(test)]
 mod tests {
-    use std::{assert_eq, collections::HashMap, path::Path, time::Duration};
+    use std::{assert_eq, path::Path, time::Duration};
 
     use super::*;
     use editor::Editor;
-    use gpui::{TestAppContext, ViewHandle};
+    use gpui::{Entity, TestAppContext, VisualTestContext};
     use menu::{Confirm, SelectNext};
     use serde_json::json;
     use workspace::{AppState, Workspace};
@@ -725,37 +770,18 @@ mod tests {
             .await;
 
         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
-        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-        let workspace = window.root(cx);
-        cx.dispatch_action(window.into(), Toggle);
 
-        let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
+        let (picker, workspace, cx) = build_find_picker(project, cx);
 
-        finder
-            .update(cx, |finder, cx| {
-                finder.delegate_mut().update_matches("bna".to_string(), cx)
-            })
-            .await;
-        finder.read_with(cx, |finder, _| {
-            assert_eq!(finder.delegate().matches.len(), 2);
+        cx.simulate_input("bna");
+        picker.update(cx, |picker, _| {
+            assert_eq!(picker.delegate.matches.len(), 2);
         });
-        let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
-        cx.dispatch_action(window.into(), SelectNext);
-        cx.dispatch_action(window.into(), Confirm);
-        active_pane
-            .condition(cx, |pane, _| pane.active_item().is_some())
-            .await;
+        cx.dispatch_action(SelectNext);
+        cx.dispatch_action(Confirm);
         cx.read(|cx| {
-            let active_item = active_pane.read(cx).active_item().unwrap();
-            assert_eq!(
-                active_item
-                    .as_any()
-                    .downcast_ref::<Editor>()
-                    .unwrap()
-                    .read(cx)
-                    .title(cx),
-                "bandana"
-            );
+            let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();
+            assert_eq!(active_editor.read(cx).title(cx), "bandana");
         });
 
         for bandana_query in [
@@ -766,35 +792,26 @@ mod tests {
             " ndan ",
             " band ",
         ] {
-            finder
-                .update(cx, |finder, cx| {
-                    finder
-                        .delegate_mut()
+            picker
+                .update(cx, |picker, cx| {
+                    picker
+                        .delegate
                         .update_matches(bandana_query.to_string(), cx)
                 })
                 .await;
-            finder.read_with(cx, |finder, _| {
+            picker.update(cx, |picker, _| {
                 assert_eq!(
-                    finder.delegate().matches.len(),
+                    picker.delegate.matches.len(),
                     1,
                     "Wrong number of matches for bandana query '{bandana_query}'"
                 );
             });
-            let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
-            cx.dispatch_action(window.into(), SelectNext);
-            cx.dispatch_action(window.into(), Confirm);
-            active_pane
-                .condition(cx, |pane, _| pane.active_item().is_some())
-                .await;
+            cx.dispatch_action(SelectNext);
+            cx.dispatch_action(Confirm);
             cx.read(|cx| {
-                let active_item = active_pane.read(cx).active_item().unwrap();
+                let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();
                 assert_eq!(
-                    active_item
-                        .as_any()
-                        .downcast_ref::<Editor>()
-                        .unwrap()
-                        .read(cx)
-                        .title(cx),
+                    active_editor.read(cx).title(cx),
                     "bandana",
                     "Wrong match for bandana query '{bandana_query}'"
                 );
@@ -823,25 +840,23 @@ mod tests {
             .await;
 
         let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
-        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-        let workspace = window.root(cx);
-        cx.dispatch_action(window.into(), Toggle);
-        let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
+
+        let (picker, workspace, cx) = build_find_picker(project, cx);
 
         let file_query = &first_file_name[..3];
         let file_row = 1;
         let file_column = 3;
         assert!(file_column <= first_file_contents.len());
         let query_inside_file = format!("{file_query}:{file_row}:{file_column}");
-        finder
+        picker
             .update(cx, |finder, cx| {
                 finder
-                    .delegate_mut()
+                    .delegate
                     .update_matches(query_inside_file.to_string(), cx)
             })
             .await;
-        finder.read_with(cx, |finder, _| {
-            let finder = finder.delegate();
+        picker.update(cx, |finder, _| {
+            let finder = &finder.delegate;
             assert_eq!(finder.matches.len(), 1);
             let latest_search_query = finder
                 .latest_search_query
@@ -856,34 +871,27 @@ mod tests {
             assert_eq!(latest_search_query.column, Some(file_column as u32));
         });
 
-        let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
-        cx.dispatch_action(window.into(), SelectNext);
-        cx.dispatch_action(window.into(), Confirm);
-        active_pane
-            .condition(cx, |pane, _| pane.active_item().is_some())
-            .await;
-        let editor = cx.update(|cx| {
-            let active_item = active_pane.read(cx).active_item().unwrap();
-            active_item.downcast::<Editor>().unwrap()
-        });
-        cx.foreground().advance_clock(Duration::from_secs(2));
-        cx.foreground().start_waiting();
-        cx.foreground().finish_waiting();
+        cx.dispatch_action(SelectNext);
+        cx.dispatch_action(Confirm);
+
+        let editor = cx.update(|cx| workspace.read(cx).active_item_as::<Editor>(cx).unwrap());
+        cx.executor().advance_clock(Duration::from_secs(2));
+
         editor.update(cx, |editor, cx| {
-            let all_selections = editor.selections.all_adjusted(cx);
-            assert_eq!(
-                all_selections.len(),
-                1,
-                "Expected to have 1 selection (caret) after file finder confirm, but got: {all_selections:?}"
-            );
-            let caret_selection = all_selections.into_iter().next().unwrap();
-            assert_eq!(caret_selection.start, caret_selection.end,
-                "Caret selection should have its start and end at the same position");
-            assert_eq!(file_row, caret_selection.start.row + 1,
-                "Query inside file should get caret with the same focus row");
-            assert_eq!(file_column, caret_selection.start.column as usize + 1,
-                "Query inside file should get caret with the same focus column");
-        });
+                let all_selections = editor.selections.all_adjusted(cx);
+                assert_eq!(
+                    all_selections.len(),
+                    1,
+                    "Expected to have 1 selection (caret) after file finder confirm, but got: {all_selections:?}"
+                );
+                let caret_selection = all_selections.into_iter().next().unwrap();
+                assert_eq!(caret_selection.start, caret_selection.end,
+                    "Caret selection should have its start and end at the same position");
+                assert_eq!(file_row, caret_selection.start.row + 1,
+                    "Query inside file should get caret with the same focus row");
+                assert_eq!(file_column, caret_selection.start.column as usize + 1,
+                    "Query inside file should get caret with the same focus column");
+            });
     }
 
     #[gpui::test]
@@ -907,27 +915,25 @@ mod tests {
             .await;
 
         let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
-        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-        let workspace = window.root(cx);
-        cx.dispatch_action(window.into(), Toggle);
-        let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
+
+        let (picker, workspace, cx) = build_find_picker(project, cx);
 
         let file_query = &first_file_name[..3];
         let file_row = 200;
         let file_column = 300;
         assert!(file_column > first_file_contents.len());
         let query_outside_file = format!("{file_query}:{file_row}:{file_column}");
-        finder
-            .update(cx, |finder, cx| {
-                finder
-                    .delegate_mut()
+        picker
+            .update(cx, |picker, cx| {
+                picker
+                    .delegate
                     .update_matches(query_outside_file.to_string(), cx)
             })
             .await;
-        finder.read_with(cx, |finder, _| {
-            let finder = finder.delegate();
-            assert_eq!(finder.matches.len(), 1);
-            let latest_search_query = finder
+        picker.update(cx, |finder, _| {
+            let delegate = &finder.delegate;
+            assert_eq!(delegate.matches.len(), 1);
+            let latest_search_query = delegate
                 .latest_search_query
                 .as_ref()
                 .expect("Finder should have a query after the update_matches call");
@@ -940,34 +946,27 @@ mod tests {
             assert_eq!(latest_search_query.column, Some(file_column as u32));
         });
 
-        let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
-        cx.dispatch_action(window.into(), SelectNext);
-        cx.dispatch_action(window.into(), Confirm);
-        active_pane
-            .condition(cx, |pane, _| pane.active_item().is_some())
-            .await;
-        let editor = cx.update(|cx| {
-            let active_item = active_pane.read(cx).active_item().unwrap();
-            active_item.downcast::<Editor>().unwrap()
-        });
-        cx.foreground().advance_clock(Duration::from_secs(2));
-        cx.foreground().start_waiting();
-        cx.foreground().finish_waiting();
+        cx.dispatch_action(SelectNext);
+        cx.dispatch_action(Confirm);
+
+        let editor = cx.update(|cx| workspace.read(cx).active_item_as::<Editor>(cx).unwrap());
+        cx.executor().advance_clock(Duration::from_secs(2));
+
         editor.update(cx, |editor, cx| {
-            let all_selections = editor.selections.all_adjusted(cx);
-            assert_eq!(
-                all_selections.len(),
-                1,
-                "Expected to have 1 selection (caret) after file finder confirm, but got: {all_selections:?}"
-            );
-            let caret_selection = all_selections.into_iter().next().unwrap();
-            assert_eq!(caret_selection.start, caret_selection.end,
-                "Caret selection should have its start and end at the same position");
-            assert_eq!(0, caret_selection.start.row,
-                "Excessive rows (as in query outside file borders) should get trimmed to last file row");
-            assert_eq!(first_file_contents.len(), caret_selection.start.column as usize,
-                "Excessive columns (as in query outside file borders) should get trimmed to selected row's last column");
-        });
+                let all_selections = editor.selections.all_adjusted(cx);
+                assert_eq!(
+                    all_selections.len(),
+                    1,
+                    "Expected to have 1 selection (caret) after file finder confirm, but got: {all_selections:?}"
+                );
+                let caret_selection = all_selections.into_iter().next().unwrap();
+                assert_eq!(caret_selection.start, caret_selection.end,
+                    "Caret selection should have its start and end at the same position");
+                assert_eq!(0, caret_selection.start.row,
+                    "Excessive rows (as in query outside file borders) should get trimmed to last file row");
+                assert_eq!(first_file_contents.len(), caret_selection.start.column as usize,
+                    "Excessive columns (as in query outside file borders) should get trimmed to selected row's last column");
+            });
     }
 
     #[gpui::test]
@@ -991,32 +990,22 @@ mod tests {
             .await;
 
         let project = Project::test(app_state.fs.clone(), ["/dir".as_ref()], cx).await;
-        let workspace = cx
-            .add_window(|cx| Workspace::test_new(project, cx))
-            .root(cx);
-        let finder = cx
-            .add_window(|cx| {
-                Picker::new(
-                    FileFinderDelegate::new(
-                        workspace.downgrade(),
-                        workspace.read(cx).project().clone(),
-                        None,
-                        Vec::new(),
-                        cx,
-                    ),
-                    cx,
-                )
-            })
-            .root(cx);
+
+        let (picker, _, cx) = build_find_picker(project, cx);
 
         let query = test_path_like("hi");
-        finder
-            .update(cx, |f, cx| f.delegate_mut().spawn_search(query.clone(), cx))
+        picker
+            .update(cx, |picker, cx| {
+                picker.delegate.spawn_search(query.clone(), cx)
+            })
             .await;
-        finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 5));
 
-        finder.update(cx, |finder, cx| {
-            let delegate = finder.delegate_mut();
+        picker.update(cx, |picker, _cx| {
+            assert_eq!(picker.delegate.matches.len(), 5)
+        });
+
+        picker.update(cx, |picker, cx| {
+            let delegate = &mut picker.delegate;
             assert!(
                 delegate.matches.history.is_empty(),
                 "Search matches expected"
@@ -1088,31 +1077,17 @@ mod tests {
             cx,
         )
         .await;
-        let workspace = cx
-            .add_window(|cx| Workspace::test_new(project, cx))
-            .root(cx);
-        let finder = cx
-            .add_window(|cx| {
-                Picker::new(
-                    FileFinderDelegate::new(
-                        workspace.downgrade(),
-                        workspace.read(cx).project().clone(),
-                        None,
-                        Vec::new(),
-                        cx,
-                    ),
-                    cx,
-                )
-            })
-            .root(cx);
-        finder
-            .update(cx, |f, cx| {
-                f.delegate_mut().spawn_search(test_path_like("hi"), cx)
+
+        let (picker, _, cx) = build_find_picker(project, cx);
+
+        picker
+            .update(cx, |picker, cx| {
+                picker.delegate.spawn_search(test_path_like("hi"), cx)
             })
             .await;
-        finder.update(cx, |f, _| {
+        picker.update(cx, |picker, _| {
             assert_eq!(
-                collect_search_results(f),
+                collect_search_results(picker),
                 vec![
                     PathBuf::from("ignored-root/happiness"),
                     PathBuf::from("ignored-root/height"),
@@ -1157,20 +1132,13 @@ mod tests {
             .await;
 
         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
-        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-        let workspace = window.root(cx);
-        cx.dispatch_action(window.into(), Toggle);
 
-        let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
+        let (picker, workspace, cx) = build_find_picker(project, cx);
 
-        finder
-            .update(cx, |finder, cx| {
-                finder.delegate_mut().update_matches("env".to_string(), cx)
-            })
-            .await;
-        finder.update(cx, |f, _| {
+        cx.simulate_input("env");
+        picker.update(cx, |picker, _| {
             assert_eq!(
-                collect_search_results(f),
+                collect_search_results(picker),
                 vec![
                     PathBuf::from(".env"),
                     PathBuf::from("a/banana_env"),
@@ -1190,15 +1158,11 @@ mod tests {
             })
             .await
             .unwrap();
-        cx.foreground().run_until_parked();
-        finder
-            .update(cx, |finder, cx| {
-                finder.delegate_mut().update_matches("env".to_string(), cx)
-            })
-            .await;
-        finder.update(cx, |f, _| {
+        cx.run_until_parked();
+        cx.simulate_input("env");
+        picker.update(cx, |picker, _| {
             assert_eq!(
-                collect_search_results(f),
+                collect_search_results(picker),
                 vec![
                     PathBuf::from(".env"),
                     PathBuf::from("a/banana_env"),
@@ -1226,34 +1190,19 @@ mod tests {
             cx,
         )
         .await;
-        let workspace = cx
-            .add_window(|cx| Workspace::test_new(project, cx))
-            .root(cx);
-        let finder = cx
-            .add_window(|cx| {
-                Picker::new(
-                    FileFinderDelegate::new(
-                        workspace.downgrade(),
-                        workspace.read(cx).project().clone(),
-                        None,
-                        Vec::new(),
-                        cx,
-                    ),
-                    cx,
-                )
-            })
-            .root(cx);
+
+        let (picker, _, cx) = build_find_picker(project, cx);
 
         // Even though there is only one worktree, that worktree's filename
         // is included in the matching, because the worktree is a single file.
-        finder
-            .update(cx, |f, cx| {
-                f.delegate_mut().spawn_search(test_path_like("thf"), cx)
+        picker
+            .update(cx, |picker, cx| {
+                picker.delegate.spawn_search(test_path_like("thf"), cx)
             })
             .await;
         cx.read(|cx| {
-            let finder = finder.read(cx);
-            let delegate = finder.delegate();
+            let picker = picker.read(cx);
+            let delegate = &picker.delegate;
             assert!(
                 delegate.matches.history.is_empty(),
                 "Search matches expected"
@@ -1271,12 +1220,12 @@ mod tests {
 
         // Since the worktree root is a file, searching for its name followed by a slash does
         // not match anything.
-        finder
+        picker
             .update(cx, |f, cx| {
-                f.delegate_mut().spawn_search(test_path_like("thf/"), cx)
+                f.delegate.spawn_search(test_path_like("thf/"), cx)
             })
             .await;
-        finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 0));
+        picker.update(cx, |f, _| assert_eq!(f.delegate.matches.len(), 0));
     }
 
     #[gpui::test]
@@ -1298,45 +1247,36 @@ mod tests {
             .await;
 
         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
-        let workspace = cx
-            .add_window(|cx| Workspace::test_new(project, cx))
-            .root(cx);
+        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
+
         let worktree_id = cx.read(|cx| {
             let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
             assert_eq!(worktrees.len(), 1);
-            WorktreeId::from_usize(worktrees[0].id())
+            WorktreeId::from_usize(worktrees[0].entity_id().as_u64() as usize)
         });
 
         // When workspace has an active item, sort items which are closer to that item
         // first when they have the same name. In this case, b.txt is closer to dir2's a.txt
         // so that one should be sorted earlier
-        let b_path = Some(dummy_found_path(ProjectPath {
+        let b_path = ProjectPath {
             worktree_id,
             path: Arc::from(Path::new("/root/dir2/b.txt")),
-        }));
-        let finder = cx
-            .add_window(|cx| {
-                Picker::new(
-                    FileFinderDelegate::new(
-                        workspace.downgrade(),
-                        workspace.read(cx).project().clone(),
-                        b_path,
-                        Vec::new(),
-                        cx,
-                    ),
-                    cx,
-                )
+        };
+        workspace
+            .update(cx, |workspace, cx| {
+                workspace.open_path(b_path, None, true, cx)
             })
-            .root(cx);
-
+            .await
+            .unwrap();
+        let finder = open_file_picker(&workspace, cx);
         finder
             .update(cx, |f, cx| {
-                f.delegate_mut().spawn_search(test_path_like("a.txt"), cx)
+                f.delegate.spawn_search(test_path_like("a.txt"), cx)
             })
             .await;
 
-        finder.read_with(cx, |f, _| {
-            let delegate = f.delegate();
+        finder.update(cx, |f, _| {
+            let delegate = &f.delegate;
             assert!(
                 delegate.matches.history.is_empty(),
                 "Search matches expected"
@@ -1365,39 +1305,21 @@ mod tests {
             .await;
 
         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
-        let workspace = cx
-            .add_window(|cx| Workspace::test_new(project, cx))
-            .root(cx);
-        let finder = cx
-            .add_window(|cx| {
-                Picker::new(
-                    FileFinderDelegate::new(
-                        workspace.downgrade(),
-                        workspace.read(cx).project().clone(),
-                        None,
-                        Vec::new(),
-                        cx,
-                    ),
-                    cx,
-                )
-            })
-            .root(cx);
-        finder
+        let (picker, _workspace, cx) = build_find_picker(project, cx);
+
+        picker
             .update(cx, |f, cx| {
-                f.delegate_mut().spawn_search(test_path_like("dir"), cx)
+                f.delegate.spawn_search(test_path_like("dir"), cx)
             })
             .await;
         cx.read(|cx| {
-            let finder = finder.read(cx);
-            assert_eq!(finder.delegate().matches.len(), 0);
+            let finder = picker.read(cx);
+            assert_eq!(finder.delegate.matches.len(), 0);
         });
     }
 
     #[gpui::test]
-    async fn test_query_history(
-        deterministic: Arc<gpui::executor::Deterministic>,
-        cx: &mut gpui::TestAppContext,
-    ) {
+    async fn test_query_history(cx: &mut gpui::TestAppContext) {
         let app_state = init_test(cx);
 
         app_state
@@ -1416,12 +1338,11 @@ mod tests {
             .await;
 
         let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
-        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-        let workspace = window.root(cx);
+        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
         let worktree_id = cx.read(|cx| {
             let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
             assert_eq!(worktrees.len(), 1);
-            WorktreeId::from_usize(worktrees[0].id())
+            WorktreeId::from_usize(worktrees[0].entity_id().as_u64() as usize)
         });
 
         // Open and close panels, getting their history items afterwards.

crates/file_finder2/Cargo.toml 🔗

@@ -1,37 +0,0 @@
-[package]
-name = "file_finder2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/file_finder.rs"
-doctest = false
-
-[dependencies]
-editor = { package = "editor2", path = "../editor2" }
-collections = { path = "../collections" }
-fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-menu = { package = "menu2", path = "../menu2" }
-picker = { package = "picker2", path = "../picker2" }
-project = { package = "project2", path = "../project2" }
-settings = { package = "settings2", path = "../settings2" }
-text = { package = "text2", path = "../text2" }
-util = { path = "../util" }
-theme = { package = "theme2", path = "../theme2" }
-ui = { package = "ui2", path = "../ui2" }
-workspace = { package = "workspace2", path = "../workspace2" }
-postage.workspace = true
-serde.workspace = true
-
-[dev-dependencies]
-editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-language = { package = "language2", path = "../language2", features = ["test-support"] }
-workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
-theme = { package = "theme2", path = "../theme2", features = ["test-support"] }
-
-serde_json.workspace = true
-ctor.workspace = true
-env_logger.workspace = true

crates/file_finder2/src/file_finder.rs 🔗

@@ -1,1956 +0,0 @@
-use collections::HashMap;
-use editor::{scroll::autoscroll::Autoscroll, Bias, Editor};
-use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
-use gpui::{
-    actions, rems, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model,
-    ParentElement, Render, Styled, Task, View, ViewContext, VisualContext, WeakView,
-};
-use picker::{Picker, PickerDelegate};
-use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
-use std::{
-    path::{Path, PathBuf},
-    sync::{
-        atomic::{self, AtomicBool},
-        Arc,
-    },
-};
-use text::Point;
-use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing};
-use util::{paths::PathLikeWithPosition, post_inc, ResultExt};
-use workspace::{ModalView, Workspace};
-
-actions!(file_finder, [Toggle]);
-
-impl ModalView for FileFinder {}
-
-pub struct FileFinder {
-    picker: View<Picker<FileFinderDelegate>>,
-}
-
-pub fn init(cx: &mut AppContext) {
-    cx.observe_new_views(FileFinder::register).detach();
-}
-
-impl FileFinder {
-    fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
-        workspace.register_action(|workspace, _: &Toggle, cx| {
-            let Some(file_finder) = workspace.active_modal::<Self>(cx) else {
-                Self::open(workspace, cx);
-                return;
-            };
-
-            file_finder.update(cx, |file_finder, cx| {
-                file_finder
-                    .picker
-                    .update(cx, |picker, cx| picker.cycle_selection(cx))
-            });
-        });
-    }
-
-    fn open(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
-        let project = workspace.project().read(cx);
-
-        let currently_opened_path = workspace
-            .active_item(cx)
-            .and_then(|item| item.project_path(cx))
-            .map(|project_path| {
-                let abs_path = project
-                    .worktree_for_id(project_path.worktree_id, cx)
-                    .map(|worktree| worktree.read(cx).abs_path().join(&project_path.path));
-                FoundPath::new(project_path, abs_path)
-            });
-
-        // if exists, bubble the currently opened path to the top
-        let history_items = currently_opened_path
-            .clone()
-            .into_iter()
-            .chain(
-                workspace
-                    .recent_navigation_history(Some(MAX_RECENT_SELECTIONS), cx)
-                    .into_iter()
-                    .filter(|(history_path, _)| {
-                        Some(history_path)
-                            != currently_opened_path
-                                .as_ref()
-                                .map(|found_path| &found_path.project)
-                    })
-                    .filter(|(_, history_abs_path)| {
-                        history_abs_path.as_ref()
-                            != currently_opened_path
-                                .as_ref()
-                                .and_then(|found_path| found_path.absolute.as_ref())
-                    })
-                    .filter(|(_, history_abs_path)| match history_abs_path {
-                        Some(abs_path) => history_file_exists(abs_path),
-                        None => true,
-                    })
-                    .map(|(history_path, abs_path)| FoundPath::new(history_path, abs_path)),
-            )
-            .collect();
-
-        let project = workspace.project().clone();
-        let weak_workspace = cx.view().downgrade();
-        workspace.toggle_modal(cx, |cx| {
-            let delegate = FileFinderDelegate::new(
-                cx.view().downgrade(),
-                weak_workspace,
-                project,
-                currently_opened_path,
-                history_items,
-                cx,
-            );
-
-            FileFinder::new(delegate, cx)
-        });
-    }
-
-    fn new(delegate: FileFinderDelegate, cx: &mut ViewContext<Self>) -> Self {
-        Self {
-            picker: cx.new_view(|cx| Picker::new(delegate, cx)),
-        }
-    }
-}
-
-impl EventEmitter<DismissEvent> for FileFinder {}
-impl FocusableView for FileFinder {
-    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
-        self.picker.focus_handle(cx)
-    }
-}
-impl Render for FileFinder {
-    fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
-        v_stack().w(rems(34.)).child(self.picker.clone())
-    }
-}
-
-pub struct FileFinderDelegate {
-    file_finder: WeakView<FileFinder>,
-    workspace: WeakView<Workspace>,
-    project: Model<Project>,
-    search_count: usize,
-    latest_search_id: usize,
-    latest_search_did_cancel: bool,
-    latest_search_query: Option<PathLikeWithPosition<FileSearchQuery>>,
-    currently_opened_path: Option<FoundPath>,
-    matches: Matches,
-    selected_index: Option<usize>,
-    cancel_flag: Arc<AtomicBool>,
-    history_items: Vec<FoundPath>,
-}
-
-#[derive(Debug, Default)]
-struct Matches {
-    history: Vec<(FoundPath, Option<PathMatch>)>,
-    search: Vec<PathMatch>,
-}
-
-#[derive(Debug)]
-enum Match<'a> {
-    History(&'a FoundPath, Option<&'a PathMatch>),
-    Search(&'a PathMatch),
-}
-
-impl Matches {
-    fn len(&self) -> usize {
-        self.history.len() + self.search.len()
-    }
-
-    fn get(&self, index: usize) -> Option<Match<'_>> {
-        if index < self.history.len() {
-            self.history
-                .get(index)
-                .map(|(path, path_match)| Match::History(path, path_match.as_ref()))
-        } else {
-            self.search
-                .get(index - self.history.len())
-                .map(Match::Search)
-        }
-    }
-
-    fn push_new_matches(
-        &mut self,
-        history_items: &Vec<FoundPath>,
-        query: &PathLikeWithPosition<FileSearchQuery>,
-        mut new_search_matches: Vec<PathMatch>,
-        extend_old_matches: bool,
-    ) {
-        let matching_history_paths = matching_history_item_paths(history_items, query);
-        new_search_matches
-            .retain(|path_match| !matching_history_paths.contains_key(&path_match.path));
-        let history_items_to_show = history_items
-            .iter()
-            .filter_map(|history_item| {
-                Some((
-                    history_item.clone(),
-                    Some(
-                        matching_history_paths
-                            .get(&history_item.project.path)?
-                            .clone(),
-                    ),
-                ))
-            })
-            .collect::<Vec<_>>();
-        self.history = history_items_to_show;
-        if extend_old_matches {
-            self.search
-                .retain(|path_match| !matching_history_paths.contains_key(&path_match.path));
-            util::extend_sorted(
-                &mut self.search,
-                new_search_matches.into_iter(),
-                100,
-                |a, b| b.cmp(a),
-            )
-        } else {
-            self.search = new_search_matches;
-        }
-    }
-}
-
-fn matching_history_item_paths(
-    history_items: &Vec<FoundPath>,
-    query: &PathLikeWithPosition<FileSearchQuery>,
-) -> HashMap<Arc<Path>, PathMatch> {
-    let history_items_by_worktrees = history_items
-        .iter()
-        .filter_map(|found_path| {
-            let candidate = PathMatchCandidate {
-                path: &found_path.project.path,
-                // Only match history items names, otherwise their paths may match too many queries, producing false positives.
-                // E.g. `foo` would match both `something/foo/bar.rs` and `something/foo/foo.rs` and if the former is a history item,
-                // it would be shown first always, despite the latter being a better match.
-                char_bag: CharBag::from_iter(
-                    found_path
-                        .project
-                        .path
-                        .file_name()?
-                        .to_string_lossy()
-                        .to_lowercase()
-                        .chars(),
-                ),
-            };
-            Some((found_path.project.worktree_id, candidate))
-        })
-        .fold(
-            HashMap::default(),
-            |mut candidates, (worktree_id, new_candidate)| {
-                candidates
-                    .entry(worktree_id)
-                    .or_insert_with(Vec::new)
-                    .push(new_candidate);
-                candidates
-            },
-        );
-    let mut matching_history_paths = HashMap::default();
-    for (worktree, candidates) in history_items_by_worktrees {
-        let max_results = candidates.len() + 1;
-        matching_history_paths.extend(
-            fuzzy::match_fixed_path_set(
-                candidates,
-                worktree.to_usize(),
-                query.path_like.path_query(),
-                false,
-                max_results,
-            )
-            .into_iter()
-            .map(|path_match| (Arc::clone(&path_match.path), path_match)),
-        );
-    }
-    matching_history_paths
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-struct FoundPath {
-    project: ProjectPath,
-    absolute: Option<PathBuf>,
-}
-
-impl FoundPath {
-    fn new(project: ProjectPath, absolute: Option<PathBuf>) -> Self {
-        Self { project, absolute }
-    }
-}
-
-const MAX_RECENT_SELECTIONS: usize = 20;
-
-#[cfg(not(test))]
-fn history_file_exists(abs_path: &PathBuf) -> bool {
-    abs_path.exists()
-}
-
-#[cfg(test)]
-fn history_file_exists(abs_path: &PathBuf) -> bool {
-    !abs_path.ends_with("nonexistent.rs")
-}
-
-pub enum Event {
-    Selected(ProjectPath),
-    Dismissed,
-}
-
-#[derive(Debug, Clone)]
-struct FileSearchQuery {
-    raw_query: String,
-    file_query_end: Option<usize>,
-}
-
-impl FileSearchQuery {
-    fn path_query(&self) -> &str {
-        match self.file_query_end {
-            Some(file_path_end) => &self.raw_query[..file_path_end],
-            None => &self.raw_query,
-        }
-    }
-}
-
-impl FileFinderDelegate {
-    fn new(
-        file_finder: WeakView<FileFinder>,
-        workspace: WeakView<Workspace>,
-        project: Model<Project>,
-        currently_opened_path: Option<FoundPath>,
-        history_items: Vec<FoundPath>,
-        cx: &mut ViewContext<FileFinder>,
-    ) -> Self {
-        cx.observe(&project, |file_finder, _, cx| {
-            //todo!() We should probably not re-render on every project anything
-            file_finder
-                .picker
-                .update(cx, |picker, cx| picker.refresh(cx))
-        })
-        .detach();
-
-        Self {
-            file_finder,
-            workspace,
-            project,
-            search_count: 0,
-            latest_search_id: 0,
-            latest_search_did_cancel: false,
-            latest_search_query: None,
-            currently_opened_path,
-            matches: Matches::default(),
-            selected_index: None,
-            cancel_flag: Arc::new(AtomicBool::new(false)),
-            history_items,
-        }
-    }
-
-    fn spawn_search(
-        &mut self,
-        query: PathLikeWithPosition<FileSearchQuery>,
-        cx: &mut ViewContext<Picker<Self>>,
-    ) -> Task<()> {
-        let relative_to = self
-            .currently_opened_path
-            .as_ref()
-            .map(|found_path| Arc::clone(&found_path.project.path));
-        let worktrees = self
-            .project
-            .read(cx)
-            .visible_worktrees(cx)
-            .collect::<Vec<_>>();
-        let include_root_name = worktrees.len() > 1;
-        let candidate_sets = worktrees
-            .into_iter()
-            .map(|worktree| PathMatchCandidateSet {
-                snapshot: worktree.read(cx).snapshot(),
-                include_ignored: true,
-                include_root_name,
-            })
-            .collect::<Vec<_>>();
-
-        let search_id = util::post_inc(&mut self.search_count);
-        self.cancel_flag.store(true, atomic::Ordering::Relaxed);
-        self.cancel_flag = Arc::new(AtomicBool::new(false));
-        let cancel_flag = self.cancel_flag.clone();
-        cx.spawn(|picker, mut cx| async move {
-            let matches = fuzzy::match_path_sets(
-                candidate_sets.as_slice(),
-                query.path_like.path_query(),
-                relative_to,
-                false,
-                100,
-                &cancel_flag,
-                cx.background_executor().clone(),
-            )
-            .await;
-            let did_cancel = cancel_flag.load(atomic::Ordering::Relaxed);
-            picker
-                .update(&mut cx, |picker, cx| {
-                    picker
-                        .delegate
-                        .set_search_matches(search_id, did_cancel, query, matches, cx)
-                })
-                .log_err();
-        })
-    }
-
-    fn set_search_matches(
-        &mut self,
-        search_id: usize,
-        did_cancel: bool,
-        query: PathLikeWithPosition<FileSearchQuery>,
-        matches: Vec<PathMatch>,
-        cx: &mut ViewContext<Picker<Self>>,
-    ) {
-        if search_id >= self.latest_search_id {
-            self.latest_search_id = search_id;
-            let extend_old_matches = self.latest_search_did_cancel
-                && Some(query.path_like.path_query())
-                    == self
-                        .latest_search_query
-                        .as_ref()
-                        .map(|query| query.path_like.path_query());
-            self.matches
-                .push_new_matches(&self.history_items, &query, matches, extend_old_matches);
-            self.latest_search_query = Some(query);
-            self.latest_search_did_cancel = did_cancel;
-            cx.notify();
-        }
-    }
-
-    fn labels_for_match(
-        &self,
-        path_match: Match,
-        cx: &AppContext,
-        ix: usize,
-    ) -> (String, Vec<usize>, String, Vec<usize>) {
-        let (file_name, file_name_positions, full_path, full_path_positions) = match path_match {
-            Match::History(found_path, found_path_match) => {
-                let worktree_id = found_path.project.worktree_id;
-                let project_relative_path = &found_path.project.path;
-                let has_worktree = self
-                    .project
-                    .read(cx)
-                    .worktree_for_id(worktree_id, cx)
-                    .is_some();
-
-                if !has_worktree {
-                    if let Some(absolute_path) = &found_path.absolute {
-                        return (
-                            absolute_path
-                                .file_name()
-                                .map_or_else(
-                                    || project_relative_path.to_string_lossy(),
-                                    |file_name| file_name.to_string_lossy(),
-                                )
-                                .to_string(),
-                            Vec::new(),
-                            absolute_path.to_string_lossy().to_string(),
-                            Vec::new(),
-                        );
-                    }
-                }
-
-                let mut path = Arc::clone(project_relative_path);
-                if project_relative_path.as_ref() == Path::new("") {
-                    if let Some(absolute_path) = &found_path.absolute {
-                        path = Arc::from(absolute_path.as_path());
-                    }
-                }
-
-                let mut path_match = PathMatch {
-                    score: ix as f64,
-                    positions: Vec::new(),
-                    worktree_id: worktree_id.to_usize(),
-                    path,
-                    path_prefix: "".into(),
-                    distance_to_relative_ancestor: usize::MAX,
-                };
-                if let Some(found_path_match) = found_path_match {
-                    path_match
-                        .positions
-                        .extend(found_path_match.positions.iter())
-                }
-
-                self.labels_for_path_match(&path_match)
-            }
-            Match::Search(path_match) => self.labels_for_path_match(path_match),
-        };
-
-        if file_name_positions.is_empty() {
-            if let Some(user_home_path) = std::env::var("HOME").ok() {
-                let user_home_path = user_home_path.trim();
-                if !user_home_path.is_empty() {
-                    if (&full_path).starts_with(user_home_path) {
-                        return (
-                            file_name,
-                            file_name_positions,
-                            full_path.replace(user_home_path, "~"),
-                            full_path_positions,
-                        );
-                    }
-                }
-            }
-        }
-
-        (
-            file_name,
-            file_name_positions,
-            full_path,
-            full_path_positions,
-        )
-    }
-
-    fn labels_for_path_match(
-        &self,
-        path_match: &PathMatch,
-    ) -> (String, Vec<usize>, String, Vec<usize>) {
-        let path = &path_match.path;
-        let path_string = path.to_string_lossy();
-        let full_path = [path_match.path_prefix.as_ref(), path_string.as_ref()].join("");
-        let path_positions = path_match.positions.clone();
-
-        let file_name = path.file_name().map_or_else(
-            || path_match.path_prefix.to_string(),
-            |file_name| file_name.to_string_lossy().to_string(),
-        );
-        let file_name_start = path_match.path_prefix.chars().count() + path_string.chars().count()
-            - file_name.chars().count();
-        let file_name_positions = path_positions
-            .iter()
-            .filter_map(|pos| {
-                if pos >= &file_name_start {
-                    Some(pos - file_name_start)
-                } else {
-                    None
-                }
-            })
-            .collect();
-
-        (file_name, file_name_positions, full_path, path_positions)
-    }
-}
-
-impl PickerDelegate for FileFinderDelegate {
-    type ListItem = ListItem;
-
-    fn placeholder_text(&self) -> Arc<str> {
-        "Search project files...".into()
-    }
-
-    fn match_count(&self) -> usize {
-        self.matches.len()
-    }
-
-    fn selected_index(&self) -> usize {
-        self.selected_index.unwrap_or(0)
-    }
-
-    fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
-        self.selected_index = Some(ix);
-        cx.notify();
-    }
-
-    fn separators_after_indices(&self) -> Vec<usize> {
-        let history_items = self.matches.history.len();
-        if history_items == 0 || self.matches.search.is_empty() {
-            Vec::new()
-        } else {
-            vec![history_items - 1]
-        }
-    }
-
-    fn update_matches(
-        &mut self,
-        raw_query: String,
-        cx: &mut ViewContext<Picker<Self>>,
-    ) -> Task<()> {
-        let raw_query = raw_query.trim();
-        if raw_query.is_empty() {
-            let project = self.project.read(cx);
-            self.latest_search_id = post_inc(&mut self.search_count);
-            self.matches = Matches {
-                history: self
-                    .history_items
-                    .iter()
-                    .filter(|history_item| {
-                        project
-                            .worktree_for_id(history_item.project.worktree_id, cx)
-                            .is_some()
-                            || (project.is_local() && history_item.absolute.is_some())
-                    })
-                    .cloned()
-                    .map(|p| (p, None))
-                    .collect(),
-                search: Vec::new(),
-            };
-            cx.notify();
-            Task::ready(())
-        } else {
-            let query = PathLikeWithPosition::parse_str(raw_query, |path_like_str| {
-                Ok::<_, std::convert::Infallible>(FileSearchQuery {
-                    raw_query: raw_query.to_owned(),
-                    file_query_end: if path_like_str == raw_query {
-                        None
-                    } else {
-                        Some(path_like_str.len())
-                    },
-                })
-            })
-            .expect("infallible");
-            self.spawn_search(query, cx)
-        }
-    }
-
-    fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<Picker<FileFinderDelegate>>) {
-        if let Some(m) = self.matches.get(self.selected_index()) {
-            if let Some(workspace) = self.workspace.upgrade() {
-                let open_task = workspace.update(cx, move |workspace, cx| {
-                    let split_or_open = |workspace: &mut Workspace, project_path, cx| {
-                        if secondary {
-                            workspace.split_path(project_path, cx)
-                        } else {
-                            workspace.open_path(project_path, None, true, cx)
-                        }
-                    };
-                    match m {
-                        Match::History(history_match, _) => {
-                            let worktree_id = history_match.project.worktree_id;
-                            if workspace
-                                .project()
-                                .read(cx)
-                                .worktree_for_id(worktree_id, cx)
-                                .is_some()
-                            {
-                                split_or_open(
-                                    workspace,
-                                    ProjectPath {
-                                        worktree_id,
-                                        path: Arc::clone(&history_match.project.path),
-                                    },
-                                    cx,
-                                )
-                            } else {
-                                match history_match.absolute.as_ref() {
-                                    Some(abs_path) => {
-                                        if secondary {
-                                            workspace.split_abs_path(
-                                                abs_path.to_path_buf(),
-                                                false,
-                                                cx,
-                                            )
-                                        } else {
-                                            workspace.open_abs_path(
-                                                abs_path.to_path_buf(),
-                                                false,
-                                                cx,
-                                            )
-                                        }
-                                    }
-                                    None => split_or_open(
-                                        workspace,
-                                        ProjectPath {
-                                            worktree_id,
-                                            path: Arc::clone(&history_match.project.path),
-                                        },
-                                        cx,
-                                    ),
-                                }
-                            }
-                        }
-                        Match::Search(m) => split_or_open(
-                            workspace,
-                            ProjectPath {
-                                worktree_id: WorktreeId::from_usize(m.worktree_id),
-                                path: m.path.clone(),
-                            },
-                            cx,
-                        ),
-                    }
-                });
-
-                let row = self
-                    .latest_search_query
-                    .as_ref()
-                    .and_then(|query| query.row)
-                    .map(|row| row.saturating_sub(1));
-                let col = self
-                    .latest_search_query
-                    .as_ref()
-                    .and_then(|query| query.column)
-                    .unwrap_or(0)
-                    .saturating_sub(1);
-                let finder = self.file_finder.clone();
-
-                cx.spawn(|_, mut cx| async move {
-                    let item = open_task.await.log_err()?;
-                    if let Some(row) = row {
-                        if let Some(active_editor) = item.downcast::<Editor>() {
-                            active_editor
-                                .downgrade()
-                                .update(&mut cx, |editor, cx| {
-                                    let snapshot = editor.snapshot(cx).display_snapshot;
-                                    let point = snapshot
-                                        .buffer_snapshot
-                                        .clip_point(Point::new(row, col), Bias::Left);
-                                    editor.change_selections(Some(Autoscroll::center()), cx, |s| {
-                                        s.select_ranges([point..point])
-                                    });
-                                })
-                                .log_err();
-                        }
-                    }
-                    finder.update(&mut cx, |_, cx| cx.emit(DismissEvent)).ok()?;
-
-                    Some(())
-                })
-                .detach();
-            }
-        }
-    }
-
-    fn dismissed(&mut self, cx: &mut ViewContext<Picker<FileFinderDelegate>>) {
-        self.file_finder
-            .update(cx, |_, cx| cx.emit(DismissEvent))
-            .log_err();
-    }
-
-    fn render_match(
-        &self,
-        ix: usize,
-        selected: bool,
-        cx: &mut ViewContext<Picker<Self>>,
-    ) -> Option<Self::ListItem> {
-        let path_match = self
-            .matches
-            .get(ix)
-            .expect("Invalid matches state: no element for index {ix}");
-
-        let (file_name, file_name_positions, full_path, full_path_positions) =
-            self.labels_for_match(path_match, cx, ix);
-
-        Some(
-            ListItem::new(ix)
-                .spacing(ListItemSpacing::Sparse)
-                .inset(true)
-                .selected(selected)
-                .child(
-                    v_stack()
-                        .child(HighlightedLabel::new(file_name, file_name_positions))
-                        .child(HighlightedLabel::new(full_path, full_path_positions)),
-                ),
-        )
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use std::{assert_eq, path::Path, time::Duration};
-
-    use super::*;
-    use editor::Editor;
-    use gpui::{Entity, TestAppContext, VisualTestContext};
-    use menu::{Confirm, SelectNext};
-    use serde_json::json;
-    use workspace::{AppState, Workspace};
-
-    #[ctor::ctor]
-    fn init_logger() {
-        if std::env::var("RUST_LOG").is_ok() {
-            env_logger::init();
-        }
-    }
-
-    #[gpui::test]
-    async fn test_matching_paths(cx: &mut TestAppContext) {
-        let app_state = init_test(cx);
-        app_state
-            .fs
-            .as_fake()
-            .insert_tree(
-                "/root",
-                json!({
-                    "a": {
-                        "banana": "",
-                        "bandana": "",
-                    }
-                }),
-            )
-            .await;
-
-        let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
-
-        let (picker, workspace, cx) = build_find_picker(project, cx);
-
-        cx.simulate_input("bna");
-        picker.update(cx, |picker, _| {
-            assert_eq!(picker.delegate.matches.len(), 2);
-        });
-        cx.dispatch_action(SelectNext);
-        cx.dispatch_action(Confirm);
-        cx.read(|cx| {
-            let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();
-            assert_eq!(active_editor.read(cx).title(cx), "bandana");
-        });
-
-        for bandana_query in [
-            "bandana",
-            " bandana",
-            "bandana ",
-            " bandana ",
-            " ndan ",
-            " band ",
-        ] {
-            picker
-                .update(cx, |picker, cx| {
-                    picker
-                        .delegate
-                        .update_matches(bandana_query.to_string(), cx)
-                })
-                .await;
-            picker.update(cx, |picker, _| {
-                assert_eq!(
-                    picker.delegate.matches.len(),
-                    1,
-                    "Wrong number of matches for bandana query '{bandana_query}'"
-                );
-            });
-            cx.dispatch_action(SelectNext);
-            cx.dispatch_action(Confirm);
-            cx.read(|cx| {
-                let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();
-                assert_eq!(
-                    active_editor.read(cx).title(cx),
-                    "bandana",
-                    "Wrong match for bandana query '{bandana_query}'"
-                );
-            });
-        }
-    }
-
-    #[gpui::test]
-    async fn test_row_column_numbers_query_inside_file(cx: &mut TestAppContext) {
-        let app_state = init_test(cx);
-
-        let first_file_name = "first.rs";
-        let first_file_contents = "// First Rust file";
-        app_state
-            .fs
-            .as_fake()
-            .insert_tree(
-                "/src",
-                json!({
-                    "test": {
-                        first_file_name: first_file_contents,
-                        "second.rs": "// Second Rust file",
-                    }
-                }),
-            )
-            .await;
-
-        let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
-
-        let (picker, workspace, cx) = build_find_picker(project, cx);
-
-        let file_query = &first_file_name[..3];
-        let file_row = 1;
-        let file_column = 3;
-        assert!(file_column <= first_file_contents.len());
-        let query_inside_file = format!("{file_query}:{file_row}:{file_column}");
-        picker
-            .update(cx, |finder, cx| {
-                finder
-                    .delegate
-                    .update_matches(query_inside_file.to_string(), cx)
-            })
-            .await;
-        picker.update(cx, |finder, _| {
-            let finder = &finder.delegate;
-            assert_eq!(finder.matches.len(), 1);
-            let latest_search_query = finder
-                .latest_search_query
-                .as_ref()
-                .expect("Finder should have a query after the update_matches call");
-            assert_eq!(latest_search_query.path_like.raw_query, query_inside_file);
-            assert_eq!(
-                latest_search_query.path_like.file_query_end,
-                Some(file_query.len())
-            );
-            assert_eq!(latest_search_query.row, Some(file_row));
-            assert_eq!(latest_search_query.column, Some(file_column as u32));
-        });
-
-        cx.dispatch_action(SelectNext);
-        cx.dispatch_action(Confirm);
-
-        let editor = cx.update(|cx| workspace.read(cx).active_item_as::<Editor>(cx).unwrap());
-        cx.executor().advance_clock(Duration::from_secs(2));
-
-        editor.update(cx, |editor, cx| {
-                let all_selections = editor.selections.all_adjusted(cx);
-                assert_eq!(
-                    all_selections.len(),
-                    1,
-                    "Expected to have 1 selection (caret) after file finder confirm, but got: {all_selections:?}"
-                );
-                let caret_selection = all_selections.into_iter().next().unwrap();
-                assert_eq!(caret_selection.start, caret_selection.end,
-                    "Caret selection should have its start and end at the same position");
-                assert_eq!(file_row, caret_selection.start.row + 1,
-                    "Query inside file should get caret with the same focus row");
-                assert_eq!(file_column, caret_selection.start.column as usize + 1,
-                    "Query inside file should get caret with the same focus column");
-            });
-    }
-
-    #[gpui::test]
-    async fn test_row_column_numbers_query_outside_file(cx: &mut TestAppContext) {
-        let app_state = init_test(cx);
-
-        let first_file_name = "first.rs";
-        let first_file_contents = "// First Rust file";
-        app_state
-            .fs
-            .as_fake()
-            .insert_tree(
-                "/src",
-                json!({
-                    "test": {
-                        first_file_name: first_file_contents,
-                        "second.rs": "// Second Rust file",
-                    }
-                }),
-            )
-            .await;
-
-        let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
-
-        let (picker, workspace, cx) = build_find_picker(project, cx);
-
-        let file_query = &first_file_name[..3];
-        let file_row = 200;
-        let file_column = 300;
-        assert!(file_column > first_file_contents.len());
-        let query_outside_file = format!("{file_query}:{file_row}:{file_column}");
-        picker
-            .update(cx, |picker, cx| {
-                picker
-                    .delegate
-                    .update_matches(query_outside_file.to_string(), cx)
-            })
-            .await;
-        picker.update(cx, |finder, _| {
-            let delegate = &finder.delegate;
-            assert_eq!(delegate.matches.len(), 1);
-            let latest_search_query = delegate
-                .latest_search_query
-                .as_ref()
-                .expect("Finder should have a query after the update_matches call");
-            assert_eq!(latest_search_query.path_like.raw_query, query_outside_file);
-            assert_eq!(
-                latest_search_query.path_like.file_query_end,
-                Some(file_query.len())
-            );
-            assert_eq!(latest_search_query.row, Some(file_row));
-            assert_eq!(latest_search_query.column, Some(file_column as u32));
-        });
-
-        cx.dispatch_action(SelectNext);
-        cx.dispatch_action(Confirm);
-
-        let editor = cx.update(|cx| workspace.read(cx).active_item_as::<Editor>(cx).unwrap());
-        cx.executor().advance_clock(Duration::from_secs(2));
-
-        editor.update(cx, |editor, cx| {
-                let all_selections = editor.selections.all_adjusted(cx);
-                assert_eq!(
-                    all_selections.len(),
-                    1,
-                    "Expected to have 1 selection (caret) after file finder confirm, but got: {all_selections:?}"
-                );
-                let caret_selection = all_selections.into_iter().next().unwrap();
-                assert_eq!(caret_selection.start, caret_selection.end,
-                    "Caret selection should have its start and end at the same position");
-                assert_eq!(0, caret_selection.start.row,
-                    "Excessive rows (as in query outside file borders) should get trimmed to last file row");
-                assert_eq!(first_file_contents.len(), caret_selection.start.column as usize,
-                    "Excessive columns (as in query outside file borders) should get trimmed to selected row's last column");
-            });
-    }
-
-    #[gpui::test]
-    async fn test_matching_cancellation(cx: &mut TestAppContext) {
-        let app_state = init_test(cx);
-        app_state
-            .fs
-            .as_fake()
-            .insert_tree(
-                "/dir",
-                json!({
-                    "hello": "",
-                    "goodbye": "",
-                    "halogen-light": "",
-                    "happiness": "",
-                    "height": "",
-                    "hi": "",
-                    "hiccup": "",
-                }),
-            )
-            .await;
-
-        let project = Project::test(app_state.fs.clone(), ["/dir".as_ref()], cx).await;
-
-        let (picker, _, cx) = build_find_picker(project, cx);
-
-        let query = test_path_like("hi");
-        picker
-            .update(cx, |picker, cx| {
-                picker.delegate.spawn_search(query.clone(), cx)
-            })
-            .await;
-
-        picker.update(cx, |picker, _cx| {
-            assert_eq!(picker.delegate.matches.len(), 5)
-        });
-
-        picker.update(cx, |picker, cx| {
-            let delegate = &mut picker.delegate;
-            assert!(
-                delegate.matches.history.is_empty(),
-                "Search matches expected"
-            );
-            let matches = delegate.matches.search.clone();
-
-            // Simulate a search being cancelled after the time limit,
-            // returning only a subset of the matches that would have been found.
-            drop(delegate.spawn_search(query.clone(), cx));
-            delegate.set_search_matches(
-                delegate.latest_search_id,
-                true, // did-cancel
-                query.clone(),
-                vec![matches[1].clone(), matches[3].clone()],
-                cx,
-            );
-
-            // Simulate another cancellation.
-            drop(delegate.spawn_search(query.clone(), cx));
-            delegate.set_search_matches(
-                delegate.latest_search_id,
-                true, // did-cancel
-                query.clone(),
-                vec![matches[0].clone(), matches[2].clone(), matches[3].clone()],
-                cx,
-            );
-
-            assert!(
-                delegate.matches.history.is_empty(),
-                "Search matches expected"
-            );
-            assert_eq!(delegate.matches.search.as_slice(), &matches[0..4]);
-        });
-    }
-
-    #[gpui::test]
-    async fn test_ignored_root(cx: &mut TestAppContext) {
-        let app_state = init_test(cx);
-        app_state
-            .fs
-            .as_fake()
-            .insert_tree(
-                "/ancestor",
-                json!({
-                    ".gitignore": "ignored-root",
-                    "ignored-root": {
-                        "happiness": "",
-                        "height": "",
-                        "hi": "",
-                        "hiccup": "",
-                    },
-                    "tracked-root": {
-                        ".gitignore": "height",
-                        "happiness": "",
-                        "height": "",
-                        "hi": "",
-                        "hiccup": "",
-                    },
-                }),
-            )
-            .await;
-
-        let project = Project::test(
-            app_state.fs.clone(),
-            [
-                "/ancestor/tracked-root".as_ref(),
-                "/ancestor/ignored-root".as_ref(),
-            ],
-            cx,
-        )
-        .await;
-
-        let (picker, _, cx) = build_find_picker(project, cx);
-
-        picker
-            .update(cx, |picker, cx| {
-                picker.delegate.spawn_search(test_path_like("hi"), cx)
-            })
-            .await;
-        picker.update(cx, |picker, _| {
-            assert_eq!(
-                collect_search_results(picker),
-                vec![
-                    PathBuf::from("ignored-root/happiness"),
-                    PathBuf::from("ignored-root/height"),
-                    PathBuf::from("ignored-root/hi"),
-                    PathBuf::from("ignored-root/hiccup"),
-                    PathBuf::from("tracked-root/happiness"),
-                    PathBuf::from("tracked-root/height"),
-                    PathBuf::from("tracked-root/hi"),
-                    PathBuf::from("tracked-root/hiccup"),
-                ],
-                "All files in all roots (including gitignored) should be searched"
-            )
-        });
-    }
-
-    #[gpui::test]
-    async fn test_ignored_files(cx: &mut TestAppContext) {
-        let app_state = init_test(cx);
-        app_state
-            .fs
-            .as_fake()
-            .insert_tree(
-                "/root",
-                json!({
-                    ".git": {},
-                    ".gitignore": "ignored_a\n.env\n",
-                    "a": {
-                        "banana_env": "11",
-                        "bandana_env": "12",
-                    },
-                    "ignored_a": {
-                        "ignored_banana_env": "21",
-                        "ignored_bandana_env": "22",
-                        "ignored_nested": {
-                            "ignored_nested_banana_env": "31",
-                            "ignored_nested_bandana_env": "32",
-                        },
-                    },
-                    ".env": "something",
-                }),
-            )
-            .await;
-
-        let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
-
-        let (picker, workspace, cx) = build_find_picker(project, cx);
-
-        cx.simulate_input("env");
-        picker.update(cx, |picker, _| {
-            assert_eq!(
-                collect_search_results(picker),
-                vec![
-                    PathBuf::from(".env"),
-                    PathBuf::from("a/banana_env"),
-                    PathBuf::from("a/bandana_env"),
-                ],
-                "Root gitignored files and all non-gitignored files should be searched"
-            )
-        });
-
-        let _ = workspace
-            .update(cx, |workspace, cx| {
-                workspace.open_abs_path(
-                    PathBuf::from("/root/ignored_a/ignored_banana_env"),
-                    true,
-                    cx,
-                )
-            })
-            .await
-            .unwrap();
-        cx.run_until_parked();
-        cx.simulate_input("env");
-        picker.update(cx, |picker, _| {
-            assert_eq!(
-                collect_search_results(picker),
-                vec![
-                    PathBuf::from(".env"),
-                    PathBuf::from("a/banana_env"),
-                    PathBuf::from("a/bandana_env"),
-                    PathBuf::from("ignored_a/ignored_banana_env"),
-                    PathBuf::from("ignored_a/ignored_bandana_env"),
-                ],
-                "Root gitignored dir got listed and its entries got into worktree, but all gitignored dirs below it were not listed. Old entries + new listed gitignored entries should be searched"
-            )
-        });
-    }
-
-    #[gpui::test]
-    async fn test_single_file_worktrees(cx: &mut TestAppContext) {
-        let app_state = init_test(cx);
-        app_state
-            .fs
-            .as_fake()
-            .insert_tree("/root", json!({ "the-parent-dir": { "the-file": "" } }))
-            .await;
-
-        let project = Project::test(
-            app_state.fs.clone(),
-            ["/root/the-parent-dir/the-file".as_ref()],
-            cx,
-        )
-        .await;
-
-        let (picker, _, cx) = build_find_picker(project, cx);
-
-        // Even though there is only one worktree, that worktree's filename
-        // is included in the matching, because the worktree is a single file.
-        picker
-            .update(cx, |picker, cx| {
-                picker.delegate.spawn_search(test_path_like("thf"), cx)
-            })
-            .await;
-        cx.read(|cx| {
-            let picker = picker.read(cx);
-            let delegate = &picker.delegate;
-            assert!(
-                delegate.matches.history.is_empty(),
-                "Search matches expected"
-            );
-            let matches = delegate.matches.search.clone();
-            assert_eq!(matches.len(), 1);
-
-            let (file_name, file_name_positions, full_path, full_path_positions) =
-                delegate.labels_for_path_match(&matches[0]);
-            assert_eq!(file_name, "the-file");
-            assert_eq!(file_name_positions, &[0, 1, 4]);
-            assert_eq!(full_path, "the-file");
-            assert_eq!(full_path_positions, &[0, 1, 4]);
-        });
-
-        // Since the worktree root is a file, searching for its name followed by a slash does
-        // not match anything.
-        picker
-            .update(cx, |f, cx| {
-                f.delegate.spawn_search(test_path_like("thf/"), cx)
-            })
-            .await;
-        picker.update(cx, |f, _| assert_eq!(f.delegate.matches.len(), 0));
-    }
-
-    #[gpui::test]
-    async fn test_path_distance_ordering(cx: &mut TestAppContext) {
-        let app_state = init_test(cx);
-        app_state
-            .fs
-            .as_fake()
-            .insert_tree(
-                "/root",
-                json!({
-                    "dir1": { "a.txt": "" },
-                    "dir2": {
-                        "a.txt": "",
-                        "b.txt": ""
-                    }
-                }),
-            )
-            .await;
-
-        let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
-        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
-
-        let worktree_id = cx.read(|cx| {
-            let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
-            assert_eq!(worktrees.len(), 1);
-            WorktreeId::from_usize(worktrees[0].entity_id().as_u64() as usize)
-        });
-
-        // When workspace has an active item, sort items which are closer to that item
-        // first when they have the same name. In this case, b.txt is closer to dir2's a.txt
-        // so that one should be sorted earlier
-        let b_path = ProjectPath {
-            worktree_id,
-            path: Arc::from(Path::new("/root/dir2/b.txt")),
-        };
-        workspace
-            .update(cx, |workspace, cx| {
-                workspace.open_path(b_path, None, true, cx)
-            })
-            .await
-            .unwrap();
-        let finder = open_file_picker(&workspace, cx);
-        finder
-            .update(cx, |f, cx| {
-                f.delegate.spawn_search(test_path_like("a.txt"), cx)
-            })
-            .await;
-
-        finder.update(cx, |f, _| {
-            let delegate = &f.delegate;
-            assert!(
-                delegate.matches.history.is_empty(),
-                "Search matches expected"
-            );
-            let matches = delegate.matches.search.clone();
-            assert_eq!(matches[0].path.as_ref(), Path::new("dir2/a.txt"));
-            assert_eq!(matches[1].path.as_ref(), Path::new("dir1/a.txt"));
-        });
-    }
-
-    #[gpui::test]
-    async fn test_search_worktree_without_files(cx: &mut TestAppContext) {
-        let app_state = init_test(cx);
-        app_state
-            .fs
-            .as_fake()
-            .insert_tree(
-                "/root",
-                json!({
-                    "dir1": {},
-                    "dir2": {
-                        "dir3": {}
-                    }
-                }),
-            )
-            .await;
-
-        let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
-        let (picker, _workspace, cx) = build_find_picker(project, cx);
-
-        picker
-            .update(cx, |f, cx| {
-                f.delegate.spawn_search(test_path_like("dir"), cx)
-            })
-            .await;
-        cx.read(|cx| {
-            let finder = picker.read(cx);
-            assert_eq!(finder.delegate.matches.len(), 0);
-        });
-    }
-
-    #[gpui::test]
-    async fn test_query_history(cx: &mut gpui::TestAppContext) {
-        let app_state = init_test(cx);
-
-        app_state
-            .fs
-            .as_fake()
-            .insert_tree(
-                "/src",
-                json!({
-                    "test": {
-                        "first.rs": "// First Rust file",
-                        "second.rs": "// Second Rust file",
-                        "third.rs": "// Third Rust file",
-                    }
-                }),
-            )
-            .await;
-
-        let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
-        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
-        let worktree_id = cx.read(|cx| {
-            let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
-            assert_eq!(worktrees.len(), 1);
-            WorktreeId::from_usize(worktrees[0].entity_id().as_u64() as usize)
-        });
-
-        // Open and close panels, getting their history items afterwards.
-        // Ensure history items get populated with opened items, and items are kept in a certain order.
-        // The history lags one opened buffer behind, since it's updated in the search panel only on its reopen.
-        //
-        // TODO: without closing, the opened items do not propagate their history changes for some reason
-        // it does work in real app though, only tests do not propagate.
-        workspace.update(cx, |_, cx| cx.focused());
-
-        let initial_history = open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
-        assert!(
-            initial_history.is_empty(),
-            "Should have no history before opening any files"
-        );
-
-        let history_after_first =
-            open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
-        assert_eq!(
-            history_after_first,
-            vec![FoundPath::new(
-                ProjectPath {
-                    worktree_id,
-                    path: Arc::from(Path::new("test/first.rs")),
-                },
-                Some(PathBuf::from("/src/test/first.rs"))
-            )],
-            "Should show 1st opened item in the history when opening the 2nd item"
-        );
-
-        let history_after_second =
-            open_close_queried_buffer("thi", 1, "third.rs", &workspace, cx).await;
-        assert_eq!(
-            history_after_second,
-            vec![
-                FoundPath::new(
-                    ProjectPath {
-                        worktree_id,
-                        path: Arc::from(Path::new("test/second.rs")),
-                    },
-                    Some(PathBuf::from("/src/test/second.rs"))
-                ),
-                FoundPath::new(
-                    ProjectPath {
-                        worktree_id,
-                        path: Arc::from(Path::new("test/first.rs")),
-                    },
-                    Some(PathBuf::from("/src/test/first.rs"))
-                ),
-            ],
-            "Should show 1st and 2nd opened items in the history when opening the 3rd item. \
-    2nd item should be the first in the history, as the last opened."
-        );
-
-        let history_after_third =
-            open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
-        assert_eq!(
-                history_after_third,
-                vec![
-                    FoundPath::new(
-                        ProjectPath {
-                            worktree_id,
-                            path: Arc::from(Path::new("test/third.rs")),
-                        },
-                        Some(PathBuf::from("/src/test/third.rs"))
-                    ),
-                    FoundPath::new(
-                        ProjectPath {
-                            worktree_id,
-                            path: Arc::from(Path::new("test/second.rs")),
-                        },
-                        Some(PathBuf::from("/src/test/second.rs"))
-                    ),
-                    FoundPath::new(
-                        ProjectPath {
-                            worktree_id,
-                            path: Arc::from(Path::new("test/first.rs")),
-                        },
-                        Some(PathBuf::from("/src/test/first.rs"))
-                    ),
-                ],
-                "Should show 1st, 2nd and 3rd opened items in the history when opening the 2nd item again. \
-    3rd item should be the first in the history, as the last opened."
-            );
-
-        let history_after_second_again =
-            open_close_queried_buffer("thi", 1, "third.rs", &workspace, cx).await;
-        assert_eq!(
-                history_after_second_again,
-                vec![
-                    FoundPath::new(
-                        ProjectPath {
-                            worktree_id,
-                            path: Arc::from(Path::new("test/second.rs")),
-                        },
-                        Some(PathBuf::from("/src/test/second.rs"))
-                    ),
-                    FoundPath::new(
-                        ProjectPath {
-                            worktree_id,
-                            path: Arc::from(Path::new("test/third.rs")),
-                        },
-                        Some(PathBuf::from("/src/test/third.rs"))
-                    ),
-                    FoundPath::new(
-                        ProjectPath {
-                            worktree_id,
-                            path: Arc::from(Path::new("test/first.rs")),
-                        },
-                        Some(PathBuf::from("/src/test/first.rs"))
-                    ),
-                ],
-                "Should show 1st, 2nd and 3rd opened items in the history when opening the 3rd item again. \
-    2nd item, as the last opened, 3rd item should go next as it was opened right before."
-            );
-    }
-
-    #[gpui::test]
-    async fn test_external_files_history(cx: &mut gpui::TestAppContext) {
-        let app_state = init_test(cx);
-
-        app_state
-            .fs
-            .as_fake()
-            .insert_tree(
-                "/src",
-                json!({
-                    "test": {
-                        "first.rs": "// First Rust file",
-                        "second.rs": "// Second Rust file",
-                    }
-                }),
-            )
-            .await;
-
-        app_state
-            .fs
-            .as_fake()
-            .insert_tree(
-                "/external-src",
-                json!({
-                    "test": {
-                        "third.rs": "// Third Rust file",
-                        "fourth.rs": "// Fourth Rust file",
-                    }
-                }),
-            )
-            .await;
-
-        let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
-        cx.update(|cx| {
-            project.update(cx, |project, cx| {
-                project.find_or_create_local_worktree("/external-src", false, cx)
-            })
-        })
-        .detach();
-        cx.background_executor.run_until_parked();
-
-        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
-        let worktree_id = cx.read(|cx| {
-            let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
-            assert_eq!(worktrees.len(), 1,);
-
-            WorktreeId::from_usize(worktrees[0].entity_id().as_u64() as usize)
-        });
-        workspace
-            .update(cx, |workspace, cx| {
-                workspace.open_abs_path(PathBuf::from("/external-src/test/third.rs"), false, cx)
-            })
-            .detach();
-        cx.background_executor.run_until_parked();
-        let external_worktree_id = cx.read(|cx| {
-            let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
-            assert_eq!(
-                worktrees.len(),
-                2,
-                "External file should get opened in a new worktree"
-            );
-
-            WorktreeId::from_usize(
-                worktrees
-                    .into_iter()
-                    .find(|worktree| {
-                        worktree.entity_id().as_u64() as usize != worktree_id.to_usize()
-                    })
-                    .expect("New worktree should have a different id")
-                    .entity_id()
-                    .as_u64() as usize,
-            )
-        });
-        cx.dispatch_action(workspace::CloseActiveItem { save_intent: None });
-
-        let initial_history_items =
-            open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
-        assert_eq!(
-            initial_history_items,
-            vec![FoundPath::new(
-                ProjectPath {
-                    worktree_id: external_worktree_id,
-                    path: Arc::from(Path::new("")),
-                },
-                Some(PathBuf::from("/external-src/test/third.rs"))
-            )],
-            "Should show external file with its full path in the history after it was open"
-        );
-
-        let updated_history_items =
-            open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
-        assert_eq!(
-            updated_history_items,
-            vec![
-                FoundPath::new(
-                    ProjectPath {
-                        worktree_id,
-                        path: Arc::from(Path::new("test/second.rs")),
-                    },
-                    Some(PathBuf::from("/src/test/second.rs"))
-                ),
-                FoundPath::new(
-                    ProjectPath {
-                        worktree_id: external_worktree_id,
-                        path: Arc::from(Path::new("")),
-                    },
-                    Some(PathBuf::from("/external-src/test/third.rs"))
-                ),
-            ],
-            "Should keep external file with history updates",
-        );
-    }
-
-    #[gpui::test]
-    async fn test_toggle_panel_new_selections(cx: &mut gpui::TestAppContext) {
-        let app_state = init_test(cx);
-
-        app_state
-            .fs
-            .as_fake()
-            .insert_tree(
-                "/src",
-                json!({
-                    "test": {
-                        "first.rs": "// First Rust file",
-                        "second.rs": "// Second Rust file",
-                        "third.rs": "// Third Rust file",
-                    }
-                }),
-            )
-            .await;
-
-        let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
-        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
-
-        // generate some history to select from
-        open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
-        cx.executor().run_until_parked();
-        open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
-        open_close_queried_buffer("thi", 1, "third.rs", &workspace, cx).await;
-        let current_history =
-            open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
-
-        for expected_selected_index in 0..current_history.len() {
-            cx.dispatch_action(Toggle);
-            let picker = active_file_picker(&workspace, cx);
-            let selected_index = picker.update(cx, |picker, _| picker.delegate.selected_index());
-            assert_eq!(
-                selected_index, expected_selected_index,
-                "Should select the next item in the history"
-            );
-        }
-
-        cx.dispatch_action(Toggle);
-        let selected_index = workspace.update(cx, |workspace, cx| {
-            workspace
-                .active_modal::<FileFinder>(cx)
-                .unwrap()
-                .read(cx)
-                .picker
-                .read(cx)
-                .delegate
-                .selected_index()
-        });
-        assert_eq!(
-            selected_index, 0,
-            "Should wrap around the history and start all over"
-        );
-    }
-
-    #[gpui::test]
-    async fn test_search_preserves_history_items(cx: &mut gpui::TestAppContext) {
-        let app_state = init_test(cx);
-
-        app_state
-            .fs
-            .as_fake()
-            .insert_tree(
-                "/src",
-                json!({
-                    "test": {
-                        "first.rs": "// First Rust file",
-                        "second.rs": "// Second Rust file",
-                        "third.rs": "// Third Rust file",
-                        "fourth.rs": "// Fourth Rust file",
-                    }
-                }),
-            )
-            .await;
-
-        let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
-        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
-        let worktree_id = cx.read(|cx| {
-            let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
-            assert_eq!(worktrees.len(), 1,);
-
-            WorktreeId::from_usize(worktrees[0].entity_id().as_u64() as usize)
-        });
-
-        // generate some history to select from
-        open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
-        open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
-        open_close_queried_buffer("thi", 1, "third.rs", &workspace, cx).await;
-        open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
-
-        let finder = open_file_picker(&workspace, cx);
-        let first_query = "f";
-        finder
-            .update(cx, |finder, cx| {
-                finder.delegate.update_matches(first_query.to_string(), cx)
-            })
-            .await;
-        finder.update(cx, |finder, _| {
-            let delegate = &finder.delegate;
-            assert_eq!(delegate.matches.history.len(), 1, "Only one history item contains {first_query}, it should be present and others should be filtered out");
-            let history_match = delegate.matches.history.first().unwrap();
-            assert!(history_match.1.is_some(), "Should have path matches for history items after querying");
-            assert_eq!(history_match.0, FoundPath::new(
-                ProjectPath {
-                    worktree_id,
-                    path: Arc::from(Path::new("test/first.rs")),
-                },
-                Some(PathBuf::from("/src/test/first.rs"))
-            ));
-            assert_eq!(delegate.matches.search.len(), 1, "Only one non-history item contains {first_query}, it should be present");
-            assert_eq!(delegate.matches.search.first().unwrap().path.as_ref(), Path::new("test/fourth.rs"));
-        });
-
-        let second_query = "fsdasdsa";
-        let finder = active_file_picker(&workspace, cx);
-        finder
-            .update(cx, |finder, cx| {
-                finder.delegate.update_matches(second_query.to_string(), cx)
-            })
-            .await;
-        finder.update(cx, |finder, _| {
-            let delegate = &finder.delegate;
-            assert!(
-                delegate.matches.history.is_empty(),
-                "No history entries should match {second_query}"
-            );
-            assert!(
-                delegate.matches.search.is_empty(),
-                "No search entries should match {second_query}"
-            );
-        });
-
-        let first_query_again = first_query;
-
-        let finder = active_file_picker(&workspace, cx);
-        finder
-            .update(cx, |finder, cx| {
-                finder
-                    .delegate
-                    .update_matches(first_query_again.to_string(), cx)
-            })
-            .await;
-        finder.update(cx, |finder, _| {
-            let delegate = &finder.delegate;
-            assert_eq!(delegate.matches.history.len(), 1, "Only one history item contains {first_query_again}, it should be present and others should be filtered out, even after non-matching query");
-            let history_match = delegate.matches.history.first().unwrap();
-            assert!(history_match.1.is_some(), "Should have path matches for history items after querying");
-            assert_eq!(history_match.0, FoundPath::new(
-                ProjectPath {
-                    worktree_id,
-                    path: Arc::from(Path::new("test/first.rs")),
-                },
-                Some(PathBuf::from("/src/test/first.rs"))
-            ));
-            assert_eq!(delegate.matches.search.len(), 1, "Only one non-history item contains {first_query_again}, it should be present, even after non-matching query");
-            assert_eq!(delegate.matches.search.first().unwrap().path.as_ref(), Path::new("test/fourth.rs"));
-        });
-    }
-
-    #[gpui::test]
-    async fn test_history_items_vs_very_good_external_match(cx: &mut gpui::TestAppContext) {
-        let app_state = init_test(cx);
-
-        app_state
-            .fs
-            .as_fake()
-            .insert_tree(
-                "/src",
-                json!({
-                    "collab_ui": {
-                        "first.rs": "// First Rust file",
-                        "second.rs": "// Second Rust file",
-                        "third.rs": "// Third Rust file",
-                        "collab_ui.rs": "// Fourth Rust file",
-                    }
-                }),
-            )
-            .await;
-
-        let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
-        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
-        // generate some history to select from
-        open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
-        open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
-        open_close_queried_buffer("thi", 1, "third.rs", &workspace, cx).await;
-        open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
-
-        let finder = open_file_picker(&workspace, cx);
-        let query = "collab_ui";
-        cx.simulate_input(query);
-        finder.update(cx, |finder, _| {
-            let delegate = &finder.delegate;
-            assert!(
-                delegate.matches.history.is_empty(),
-                "History items should not math query {query}, they should be matched by name only"
-            );
-
-            let search_entries = delegate
-                .matches
-                .search
-                .iter()
-                .map(|path_match| path_match.path.to_path_buf())
-                .collect::<Vec<_>>();
-            assert_eq!(
-                search_entries,
-                vec![
-                    PathBuf::from("collab_ui/collab_ui.rs"),
-                    PathBuf::from("collab_ui/third.rs"),
-                    PathBuf::from("collab_ui/first.rs"),
-                    PathBuf::from("collab_ui/second.rs"),
-                ],
-                "Despite all search results having the same directory name, the most matching one should be on top"
-            );
-        });
-    }
-
-    #[gpui::test]
-    async fn test_nonexistent_history_items_not_shown(cx: &mut gpui::TestAppContext) {
-        let app_state = init_test(cx);
-
-        app_state
-            .fs
-            .as_fake()
-            .insert_tree(
-                "/src",
-                json!({
-                    "test": {
-                        "first.rs": "// First Rust file",
-                        "nonexistent.rs": "// Second Rust file",
-                        "third.rs": "// Third Rust file",
-                    }
-                }),
-            )
-            .await;
-
-        let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
-        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx)); // generate some history to select from
-        open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
-        open_close_queried_buffer("non", 1, "nonexistent.rs", &workspace, cx).await;
-        open_close_queried_buffer("thi", 1, "third.rs", &workspace, cx).await;
-        open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
-
-        let picker = open_file_picker(&workspace, cx);
-        cx.simulate_input("rs");
-
-        picker.update(cx, |finder, _| {
-            let history_entries = finder.delegate
-                .matches
-                .history
-                .iter()
-                .map(|(_, path_match)| path_match.as_ref().expect("should have a path match").path.to_path_buf())
-                .collect::<Vec<_>>();
-            assert_eq!(
-                history_entries,
-                vec![
-                    PathBuf::from("test/first.rs"),
-                    PathBuf::from("test/third.rs"),
-                ],
-                "Should have all opened files in the history, except the ones that do not exist on disk"
-            );
-        });
-    }
-
-    async fn open_close_queried_buffer(
-        input: &str,
-        expected_matches: usize,
-        expected_editor_title: &str,
-        workspace: &View<Workspace>,
-        cx: &mut gpui::VisualTestContext<'_>,
-    ) -> Vec<FoundPath> {
-        let picker = open_file_picker(&workspace, cx);
-        cx.simulate_input(input);
-
-        let history_items = picker.update(cx, |finder, _| {
-            assert_eq!(
-                finder.delegate.matches.len(),
-                expected_matches,
-                "Unexpected number of matches found for query {input}"
-            );
-            finder.delegate.history_items.clone()
-        });
-
-        cx.dispatch_action(SelectNext);
-        cx.dispatch_action(Confirm);
-
-        cx.read(|cx| {
-            let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();
-            let active_editor_title = active_editor.read(cx).title(cx);
-            assert_eq!(
-                expected_editor_title, active_editor_title,
-                "Unexpected editor title for query {input}"
-            );
-        });
-
-        cx.dispatch_action(workspace::CloseActiveItem { save_intent: None });
-
-        history_items
-    }
-
-    fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
-        cx.update(|cx| {
-            let state = AppState::test(cx);
-            theme::init(theme::LoadThemes::JustBase, cx);
-            language::init(cx);
-            super::init(cx);
-            editor::init(cx);
-            workspace::init_settings(cx);
-            Project::init_settings(cx);
-            state
-        })
-    }
-
-    fn test_path_like(test_str: &str) -> PathLikeWithPosition<FileSearchQuery> {
-        PathLikeWithPosition::parse_str(test_str, |path_like_str| {
-            Ok::<_, std::convert::Infallible>(FileSearchQuery {
-                raw_query: test_str.to_owned(),
-                file_query_end: if path_like_str == test_str {
-                    None
-                } else {
-                    Some(path_like_str.len())
-                },
-            })
-        })
-        .unwrap()
-    }
-
-    fn build_find_picker(
-        project: Model<Project>,
-        cx: &mut TestAppContext,
-    ) -> (
-        View<Picker<FileFinderDelegate>>,
-        View<Workspace>,
-        &mut VisualTestContext,
-    ) {
-        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
-        let picker = open_file_picker(&workspace, cx);
-        (picker, workspace, cx)
-    }
-
-    #[track_caller]
-    fn open_file_picker(
-        workspace: &View<Workspace>,
-        cx: &mut VisualTestContext,
-    ) -> View<Picker<FileFinderDelegate>> {
-        cx.dispatch_action(Toggle);
-        active_file_picker(workspace, cx)
-    }
-
-    #[track_caller]
-    fn active_file_picker(
-        workspace: &View<Workspace>,
-        cx: &mut VisualTestContext,
-    ) -> View<Picker<FileFinderDelegate>> {
-        workspace.update(cx, |workspace, cx| {
-            workspace
-                .active_modal::<FileFinder>(cx)
-                .unwrap()
-                .read(cx)
-                .picker
-                .clone()
-        })
-    }
-
-    fn collect_search_results(picker: &Picker<FileFinderDelegate>) -> Vec<PathBuf> {
-        let matches = &picker.delegate.matches;
-        assert!(
-            matches.history.is_empty(),
-            "Should have no history matches, but got: {:?}",
-            matches.history
-        );
-        let mut results = matches
-            .search
-            .iter()
-            .map(|path_match| Path::new(path_match.path_prefix.as_ref()).join(&path_match.path))
-            .collect::<Vec<_>>();
-        results.sort();
-        results
-    }
-}

crates/fs/src/fs.rs 🔗

@@ -27,8 +27,6 @@ use collections::{btree_map, BTreeMap};
 use repository::{FakeGitRepositoryState, GitFileStatus};
 #[cfg(any(test, feature = "test-support"))]
 use std::ffi::OsStr;
-#[cfg(any(test, feature = "test-support"))]
-use std::sync::Weak;
 
 #[async_trait::async_trait]
 pub trait Fs: Send + Sync {
@@ -290,7 +288,7 @@ impl Fs for RealFs {
 pub struct FakeFs {
     // Use an unfair lock to ensure tests are deterministic.
     state: Mutex<FakeFsState>,
-    executor: Weak<gpui::executor::Background>,
+    executor: gpui::BackgroundExecutor,
 }
 
 #[cfg(any(test, feature = "test-support"))]
@@ -436,9 +434,9 @@ lazy_static::lazy_static! {
 
 #[cfg(any(test, feature = "test-support"))]
 impl FakeFs {
-    pub fn new(executor: Arc<gpui::executor::Background>) -> Arc<Self> {
+    pub fn new(executor: gpui::BackgroundExecutor) -> Arc<Self> {
         Arc::new(Self {
-            executor: Arc::downgrade(&executor),
+            executor,
             state: Mutex::new(FakeFsState {
                 root: Arc::new(Mutex::new(FakeFsEntry::Dir {
                     inode: 0,
@@ -699,12 +697,8 @@ impl FakeFs {
         self.state.lock().metadata_call_count
     }
 
-    async fn simulate_random_delay(&self) {
-        self.executor
-            .upgrade()
-            .expect("executor has been dropped")
-            .simulate_random_delay()
-            .await;
+    fn simulate_random_delay(&self) -> impl futures::Future<Output = ()> {
+        self.executor.simulate_random_delay()
     }
 }
 
@@ -1103,9 +1097,7 @@ impl Fs for FakeFs {
             let result = events.iter().any(|event| event.path.starts_with(&path));
             let executor = executor.clone();
             async move {
-                if let Some(executor) = executor.clone().upgrade() {
-                    executor.simulate_random_delay().await;
-                }
+                executor.simulate_random_delay().await;
                 result
             }
         }))
@@ -1230,13 +1222,12 @@ pub fn copy_recursive<'a>(
 #[cfg(test)]
 mod tests {
     use super::*;
-    use gpui::TestAppContext;
+    use gpui::BackgroundExecutor;
     use serde_json::json;
 
     #[gpui::test]
-    async fn test_fake_fs(cx: &mut TestAppContext) {
-        let fs = FakeFs::new(cx.background());
-
+    async fn test_fake_fs(executor: BackgroundExecutor) {
+        let fs = FakeFs::new(executor.clone());
         fs.insert_tree(
             "/root",
             json!({

crates/fs2/Cargo.toml 🔗

@@ -1,40 +0,0 @@
-[package]
-name = "fs2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/fs2.rs"
-
-[dependencies]
-collections = { path = "../collections" }
-rope = { package = "rope2", path = "../rope2" }
-text = { package = "text2", path = "../text2" }
-util = { path = "../util" }
-sum_tree = { path = "../sum_tree" }
-
-anyhow.workspace = true
-async-trait.workspace = true
-futures.workspace = true
-tempfile = "3"
-fsevent = { path = "../fsevent" }
-lazy_static.workspace = true
-parking_lot.workspace = true
-smol.workspace = true
-regex.workspace = true
-git2.workspace = true
-serde.workspace = true
-serde_derive.workspace = true
-serde_json.workspace = true
-log.workspace = true
-libc = "0.2"
-time.workspace = true
-
-gpui = { package = "gpui2", path = "../gpui2", optional = true}
-
-[dev-dependencies]
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-
-[features]
-test-support = ["gpui/test-support"]

crates/fs2/src/fs2.rs 🔗

@@ -1,1278 +0,0 @@
-pub mod repository;
-
-use anyhow::{anyhow, Result};
-use fsevent::EventStream;
-use futures::{future::BoxFuture, Stream, StreamExt};
-use git2::Repository as LibGitRepository;
-use parking_lot::Mutex;
-use repository::GitRepository;
-use rope::Rope;
-use smol::io::{AsyncReadExt, AsyncWriteExt};
-use std::io::Write;
-use std::sync::Arc;
-use std::{
-    io,
-    os::unix::fs::MetadataExt,
-    path::{Component, Path, PathBuf},
-    pin::Pin,
-    time::{Duration, SystemTime},
-};
-use tempfile::NamedTempFile;
-use text::LineEnding;
-use util::ResultExt;
-
-#[cfg(any(test, feature = "test-support"))]
-use collections::{btree_map, BTreeMap};
-#[cfg(any(test, feature = "test-support"))]
-use repository::{FakeGitRepositoryState, GitFileStatus};
-#[cfg(any(test, feature = "test-support"))]
-use std::ffi::OsStr;
-
-#[async_trait::async_trait]
-pub trait Fs: Send + Sync {
-    async fn create_dir(&self, path: &Path) -> Result<()>;
-    async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()>;
-    async fn copy_file(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()>;
-    async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()>;
-    async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()>;
-    async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()>;
-    async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>>;
-    async fn load(&self, path: &Path) -> Result<String>;
-    async fn atomic_write(&self, path: PathBuf, text: String) -> Result<()>;
-    async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()>;
-    async fn canonicalize(&self, path: &Path) -> Result<PathBuf>;
-    async fn is_file(&self, path: &Path) -> bool;
-    async fn metadata(&self, path: &Path) -> Result<Option<Metadata>>;
-    async fn read_link(&self, path: &Path) -> Result<PathBuf>;
-    async fn read_dir(
-        &self,
-        path: &Path,
-    ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>>;
-    async fn watch(
-        &self,
-        path: &Path,
-        latency: Duration,
-    ) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>>;
-    fn open_repo(&self, abs_dot_git: &Path) -> Option<Arc<Mutex<dyn GitRepository>>>;
-    fn is_fake(&self) -> bool;
-    #[cfg(any(test, feature = "test-support"))]
-    fn as_fake(&self) -> &FakeFs;
-}
-
-#[derive(Copy, Clone, Default)]
-pub struct CreateOptions {
-    pub overwrite: bool,
-    pub ignore_if_exists: bool,
-}
-
-#[derive(Copy, Clone, Default)]
-pub struct CopyOptions {
-    pub overwrite: bool,
-    pub ignore_if_exists: bool,
-}
-
-#[derive(Copy, Clone, Default)]
-pub struct RenameOptions {
-    pub overwrite: bool,
-    pub ignore_if_exists: bool,
-}
-
-#[derive(Copy, Clone, Default)]
-pub struct RemoveOptions {
-    pub recursive: bool,
-    pub ignore_if_not_exists: bool,
-}
-
-#[derive(Copy, Clone, Debug)]
-pub struct Metadata {
-    pub inode: u64,
-    pub mtime: SystemTime,
-    pub is_symlink: bool,
-    pub is_dir: bool,
-}
-
-pub struct RealFs;
-
-#[async_trait::async_trait]
-impl Fs for RealFs {
-    async fn create_dir(&self, path: &Path) -> Result<()> {
-        Ok(smol::fs::create_dir_all(path).await?)
-    }
-
-    async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()> {
-        let mut open_options = smol::fs::OpenOptions::new();
-        open_options.write(true).create(true);
-        if options.overwrite {
-            open_options.truncate(true);
-        } else if !options.ignore_if_exists {
-            open_options.create_new(true);
-        }
-        open_options.open(path).await?;
-        Ok(())
-    }
-
-    async fn copy_file(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()> {
-        if !options.overwrite && smol::fs::metadata(target).await.is_ok() {
-            if options.ignore_if_exists {
-                return Ok(());
-            } else {
-                return Err(anyhow!("{target:?} already exists"));
-            }
-        }
-
-        smol::fs::copy(source, target).await?;
-        Ok(())
-    }
-
-    async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()> {
-        if !options.overwrite && smol::fs::metadata(target).await.is_ok() {
-            if options.ignore_if_exists {
-                return Ok(());
-            } else {
-                return Err(anyhow!("{target:?} already exists"));
-            }
-        }
-
-        smol::fs::rename(source, target).await?;
-        Ok(())
-    }
-
-    async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {
-        let result = if options.recursive {
-            smol::fs::remove_dir_all(path).await
-        } else {
-            smol::fs::remove_dir(path).await
-        };
-        match result {
-            Ok(()) => Ok(()),
-            Err(err) if err.kind() == io::ErrorKind::NotFound && options.ignore_if_not_exists => {
-                Ok(())
-            }
-            Err(err) => Err(err)?,
-        }
-    }
-
-    async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()> {
-        match smol::fs::remove_file(path).await {
-            Ok(()) => Ok(()),
-            Err(err) if err.kind() == io::ErrorKind::NotFound && options.ignore_if_not_exists => {
-                Ok(())
-            }
-            Err(err) => Err(err)?,
-        }
-    }
-
-    async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>> {
-        Ok(Box::new(std::fs::File::open(path)?))
-    }
-
-    async fn load(&self, path: &Path) -> Result<String> {
-        let mut file = smol::fs::File::open(path).await?;
-        let mut text = String::new();
-        file.read_to_string(&mut text).await?;
-        Ok(text)
-    }
-
-    async fn atomic_write(&self, path: PathBuf, data: String) -> Result<()> {
-        smol::unblock(move || {
-            let mut tmp_file = NamedTempFile::new()?;
-            tmp_file.write_all(data.as_bytes())?;
-            tmp_file.persist(path)?;
-            Ok::<(), anyhow::Error>(())
-        })
-        .await?;
-
-        Ok(())
-    }
-
-    async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> {
-        let buffer_size = text.summary().len.min(10 * 1024);
-        if let Some(path) = path.parent() {
-            self.create_dir(path).await?;
-        }
-        let file = smol::fs::File::create(path).await?;
-        let mut writer = smol::io::BufWriter::with_capacity(buffer_size, file);
-        for chunk in chunks(text, line_ending) {
-            writer.write_all(chunk.as_bytes()).await?;
-        }
-        writer.flush().await?;
-        Ok(())
-    }
-
-    async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
-        Ok(smol::fs::canonicalize(path).await?)
-    }
-
-    async fn is_file(&self, path: &Path) -> bool {
-        smol::fs::metadata(path)
-            .await
-            .map_or(false, |metadata| metadata.is_file())
-    }
-
-    async fn metadata(&self, path: &Path) -> Result<Option<Metadata>> {
-        let symlink_metadata = match smol::fs::symlink_metadata(path).await {
-            Ok(metadata) => metadata,
-            Err(err) => {
-                return match (err.kind(), err.raw_os_error()) {
-                    (io::ErrorKind::NotFound, _) => Ok(None),
-                    (io::ErrorKind::Other, Some(libc::ENOTDIR)) => Ok(None),
-                    _ => Err(anyhow::Error::new(err)),
-                }
-            }
-        };
-
-        let is_symlink = symlink_metadata.file_type().is_symlink();
-        let metadata = if is_symlink {
-            smol::fs::metadata(path).await?
-        } else {
-            symlink_metadata
-        };
-        Ok(Some(Metadata {
-            inode: metadata.ino(),
-            mtime: metadata.modified().unwrap(),
-            is_symlink,
-            is_dir: metadata.file_type().is_dir(),
-        }))
-    }
-
-    async fn read_link(&self, path: &Path) -> Result<PathBuf> {
-        let path = smol::fs::read_link(path).await?;
-        Ok(path)
-    }
-
-    async fn read_dir(
-        &self,
-        path: &Path,
-    ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>> {
-        let result = smol::fs::read_dir(path).await?.map(|entry| match entry {
-            Ok(entry) => Ok(entry.path()),
-            Err(error) => Err(anyhow!("failed to read dir entry {:?}", error)),
-        });
-        Ok(Box::pin(result))
-    }
-
-    async fn watch(
-        &self,
-        path: &Path,
-        latency: Duration,
-    ) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>> {
-        let (tx, rx) = smol::channel::unbounded();
-        let (stream, handle) = EventStream::new(&[path], latency);
-        std::thread::spawn(move || {
-            stream.run(move |events| smol::block_on(tx.send(events)).is_ok());
-        });
-        Box::pin(rx.chain(futures::stream::once(async move {
-            drop(handle);
-            vec![]
-        })))
-    }
-
-    fn open_repo(&self, dotgit_path: &Path) -> Option<Arc<Mutex<dyn GitRepository>>> {
-        LibGitRepository::open(&dotgit_path)
-            .log_err()
-            .and_then::<Arc<Mutex<dyn GitRepository>>, _>(|libgit_repository| {
-                Some(Arc::new(Mutex::new(libgit_repository)))
-            })
-    }
-
-    fn is_fake(&self) -> bool {
-        false
-    }
-    #[cfg(any(test, feature = "test-support"))]
-    fn as_fake(&self) -> &FakeFs {
-        panic!("called `RealFs::as_fake`")
-    }
-}
-
-#[cfg(any(test, feature = "test-support"))]
-pub struct FakeFs {
-    // Use an unfair lock to ensure tests are deterministic.
-    state: Mutex<FakeFsState>,
-    executor: gpui::BackgroundExecutor,
-}
-
-#[cfg(any(test, feature = "test-support"))]
-struct FakeFsState {
-    root: Arc<Mutex<FakeFsEntry>>,
-    next_inode: u64,
-    next_mtime: SystemTime,
-    event_txs: Vec<smol::channel::Sender<Vec<fsevent::Event>>>,
-    events_paused: bool,
-    buffered_events: Vec<fsevent::Event>,
-    metadata_call_count: usize,
-    read_dir_call_count: usize,
-}
-
-#[cfg(any(test, feature = "test-support"))]
-#[derive(Debug)]
-enum FakeFsEntry {
-    File {
-        inode: u64,
-        mtime: SystemTime,
-        content: String,
-    },
-    Dir {
-        inode: u64,
-        mtime: SystemTime,
-        entries: BTreeMap<String, Arc<Mutex<FakeFsEntry>>>,
-        git_repo_state: Option<Arc<Mutex<repository::FakeGitRepositoryState>>>,
-    },
-    Symlink {
-        target: PathBuf,
-    },
-}
-
-#[cfg(any(test, feature = "test-support"))]
-impl FakeFsState {
-    fn read_path<'a>(&'a self, target: &Path) -> Result<Arc<Mutex<FakeFsEntry>>> {
-        Ok(self
-            .try_read_path(target, true)
-            .ok_or_else(|| anyhow!("path does not exist: {}", target.display()))?
-            .0)
-    }
-
-    fn try_read_path<'a>(
-        &'a self,
-        target: &Path,
-        follow_symlink: bool,
-    ) -> Option<(Arc<Mutex<FakeFsEntry>>, PathBuf)> {
-        let mut path = target.to_path_buf();
-        let mut canonical_path = PathBuf::new();
-        let mut entry_stack = Vec::new();
-        'outer: loop {
-            let mut path_components = path.components().peekable();
-            while let Some(component) = path_components.next() {
-                match component {
-                    Component::Prefix(_) => panic!("prefix paths aren't supported"),
-                    Component::RootDir => {
-                        entry_stack.clear();
-                        entry_stack.push(self.root.clone());
-                        canonical_path.clear();
-                        canonical_path.push("/");
-                    }
-                    Component::CurDir => {}
-                    Component::ParentDir => {
-                        entry_stack.pop()?;
-                        canonical_path.pop();
-                    }
-                    Component::Normal(name) => {
-                        let current_entry = entry_stack.last().cloned()?;
-                        let current_entry = current_entry.lock();
-                        if let FakeFsEntry::Dir { entries, .. } = &*current_entry {
-                            let entry = entries.get(name.to_str().unwrap()).cloned()?;
-                            if path_components.peek().is_some() || follow_symlink {
-                                let entry = entry.lock();
-                                if let FakeFsEntry::Symlink { target, .. } = &*entry {
-                                    let mut target = target.clone();
-                                    target.extend(path_components);
-                                    path = target;
-                                    continue 'outer;
-                                }
-                            }
-                            entry_stack.push(entry.clone());
-                            canonical_path.push(name);
-                        } else {
-                            return None;
-                        }
-                    }
-                }
-            }
-            break;
-        }
-        Some((entry_stack.pop()?, canonical_path))
-    }
-
-    fn write_path<Fn, T>(&self, path: &Path, callback: Fn) -> Result<T>
-    where
-        Fn: FnOnce(btree_map::Entry<String, Arc<Mutex<FakeFsEntry>>>) -> Result<T>,
-    {
-        let path = normalize_path(path);
-        let filename = path
-            .file_name()
-            .ok_or_else(|| anyhow!("cannot overwrite the root"))?;
-        let parent_path = path.parent().unwrap();
-
-        let parent = self.read_path(parent_path)?;
-        let mut parent = parent.lock();
-        let new_entry = parent
-            .dir_entries(parent_path)?
-            .entry(filename.to_str().unwrap().into());
-        callback(new_entry)
-    }
-
-    fn emit_event<I, T>(&mut self, paths: I)
-    where
-        I: IntoIterator<Item = T>,
-        T: Into<PathBuf>,
-    {
-        self.buffered_events
-            .extend(paths.into_iter().map(|path| fsevent::Event {
-                event_id: 0,
-                flags: fsevent::StreamFlags::empty(),
-                path: path.into(),
-            }));
-
-        if !self.events_paused {
-            self.flush_events(self.buffered_events.len());
-        }
-    }
-
-    fn flush_events(&mut self, mut count: usize) {
-        count = count.min(self.buffered_events.len());
-        let events = self.buffered_events.drain(0..count).collect::<Vec<_>>();
-        self.event_txs.retain(|tx| {
-            let _ = tx.try_send(events.clone());
-            !tx.is_closed()
-        });
-    }
-}
-
-#[cfg(any(test, feature = "test-support"))]
-lazy_static::lazy_static! {
-    pub static ref FS_DOT_GIT: &'static OsStr = OsStr::new(".git");
-}
-
-#[cfg(any(test, feature = "test-support"))]
-impl FakeFs {
-    pub fn new(executor: gpui::BackgroundExecutor) -> Arc<Self> {
-        Arc::new(Self {
-            executor,
-            state: Mutex::new(FakeFsState {
-                root: Arc::new(Mutex::new(FakeFsEntry::Dir {
-                    inode: 0,
-                    mtime: SystemTime::UNIX_EPOCH,
-                    entries: Default::default(),
-                    git_repo_state: None,
-                })),
-                next_mtime: SystemTime::UNIX_EPOCH,
-                next_inode: 1,
-                event_txs: Default::default(),
-                buffered_events: Vec::new(),
-                events_paused: false,
-                read_dir_call_count: 0,
-                metadata_call_count: 0,
-            }),
-        })
-    }
-
-    pub async fn insert_file(&self, path: impl AsRef<Path>, content: String) {
-        self.write_file_internal(path, content).unwrap()
-    }
-
-    pub async fn insert_symlink(&self, path: impl AsRef<Path>, target: PathBuf) {
-        let mut state = self.state.lock();
-        let path = path.as_ref();
-        let file = Arc::new(Mutex::new(FakeFsEntry::Symlink { target }));
-        state
-            .write_path(path.as_ref(), move |e| match e {
-                btree_map::Entry::Vacant(e) => {
-                    e.insert(file);
-                    Ok(())
-                }
-                btree_map::Entry::Occupied(mut e) => {
-                    *e.get_mut() = file;
-                    Ok(())
-                }
-            })
-            .unwrap();
-        state.emit_event(&[path]);
-    }
-
-    pub fn write_file_internal(&self, path: impl AsRef<Path>, content: String) -> Result<()> {
-        let mut state = self.state.lock();
-        let path = path.as_ref();
-        let inode = state.next_inode;
-        let mtime = state.next_mtime;
-        state.next_inode += 1;
-        state.next_mtime += Duration::from_nanos(1);
-        let file = Arc::new(Mutex::new(FakeFsEntry::File {
-            inode,
-            mtime,
-            content,
-        }));
-        state.write_path(path, move |entry| {
-            match entry {
-                btree_map::Entry::Vacant(e) => {
-                    e.insert(file);
-                }
-                btree_map::Entry::Occupied(mut e) => {
-                    *e.get_mut() = file;
-                }
-            }
-            Ok(())
-        })?;
-        state.emit_event(&[path]);
-        Ok(())
-    }
-
-    pub fn pause_events(&self) {
-        self.state.lock().events_paused = true;
-    }
-
-    pub fn buffered_event_count(&self) -> usize {
-        self.state.lock().buffered_events.len()
-    }
-
-    pub fn flush_events(&self, count: usize) {
-        self.state.lock().flush_events(count);
-    }
-
-    #[must_use]
-    pub fn insert_tree<'a>(
-        &'a self,
-        path: impl 'a + AsRef<Path> + Send,
-        tree: serde_json::Value,
-    ) -> futures::future::BoxFuture<'a, ()> {
-        use futures::FutureExt as _;
-        use serde_json::Value::*;
-
-        async move {
-            let path = path.as_ref();
-
-            match tree {
-                Object(map) => {
-                    self.create_dir(path).await.unwrap();
-                    for (name, contents) in map {
-                        let mut path = PathBuf::from(path);
-                        path.push(name);
-                        self.insert_tree(&path, contents).await;
-                    }
-                }
-                Null => {
-                    self.create_dir(path).await.unwrap();
-                }
-                String(contents) => {
-                    self.insert_file(&path, contents).await;
-                }
-                _ => {
-                    panic!("JSON object must contain only objects, strings, or null");
-                }
-            }
-        }
-        .boxed()
-    }
-
-    pub fn with_git_state<F>(&self, dot_git: &Path, emit_git_event: bool, f: F)
-    where
-        F: FnOnce(&mut FakeGitRepositoryState),
-    {
-        let mut state = self.state.lock();
-        let entry = state.read_path(dot_git).unwrap();
-        let mut entry = entry.lock();
-
-        if let FakeFsEntry::Dir { git_repo_state, .. } = &mut *entry {
-            let repo_state = git_repo_state.get_or_insert_with(Default::default);
-            let mut repo_state = repo_state.lock();
-
-            f(&mut repo_state);
-
-            if emit_git_event {
-                state.emit_event([dot_git]);
-            }
-        } else {
-            panic!("not a directory");
-        }
-    }
-
-    pub fn set_branch_name(&self, dot_git: &Path, branch: Option<impl Into<String>>) {
-        self.with_git_state(dot_git, true, |state| {
-            state.branch_name = branch.map(Into::into)
-        })
-    }
-
-    pub fn set_index_for_repo(&self, dot_git: &Path, head_state: &[(&Path, String)]) {
-        self.with_git_state(dot_git, true, |state| {
-            state.index_contents.clear();
-            state.index_contents.extend(
-                head_state
-                    .iter()
-                    .map(|(path, content)| (path.to_path_buf(), content.clone())),
-            );
-        });
-    }
-
-    pub fn set_status_for_repo_via_working_copy_change(
-        &self,
-        dot_git: &Path,
-        statuses: &[(&Path, GitFileStatus)],
-    ) {
-        self.with_git_state(dot_git, false, |state| {
-            state.worktree_statuses.clear();
-            state.worktree_statuses.extend(
-                statuses
-                    .iter()
-                    .map(|(path, content)| ((**path).into(), content.clone())),
-            );
-        });
-        self.state.lock().emit_event(
-            statuses
-                .iter()
-                .map(|(path, _)| dot_git.parent().unwrap().join(path)),
-        );
-    }
-
-    pub fn set_status_for_repo_via_git_operation(
-        &self,
-        dot_git: &Path,
-        statuses: &[(&Path, GitFileStatus)],
-    ) {
-        self.with_git_state(dot_git, true, |state| {
-            state.worktree_statuses.clear();
-            state.worktree_statuses.extend(
-                statuses
-                    .iter()
-                    .map(|(path, content)| ((**path).into(), content.clone())),
-            );
-        });
-    }
-
-    pub fn paths(&self, include_dot_git: bool) -> Vec<PathBuf> {
-        let mut result = Vec::new();
-        let mut queue = collections::VecDeque::new();
-        queue.push_back((PathBuf::from("/"), self.state.lock().root.clone()));
-        while let Some((path, entry)) = queue.pop_front() {
-            if let FakeFsEntry::Dir { entries, .. } = &*entry.lock() {
-                for (name, entry) in entries {
-                    queue.push_back((path.join(name), entry.clone()));
-                }
-            }
-            if include_dot_git
-                || !path
-                    .components()
-                    .any(|component| component.as_os_str() == *FS_DOT_GIT)
-            {
-                result.push(path);
-            }
-        }
-        result
-    }
-
-    pub fn directories(&self, include_dot_git: bool) -> Vec<PathBuf> {
-        let mut result = Vec::new();
-        let mut queue = collections::VecDeque::new();
-        queue.push_back((PathBuf::from("/"), self.state.lock().root.clone()));
-        while let Some((path, entry)) = queue.pop_front() {
-            if let FakeFsEntry::Dir { entries, .. } = &*entry.lock() {
-                for (name, entry) in entries {
-                    queue.push_back((path.join(name), entry.clone()));
-                }
-                if include_dot_git
-                    || !path
-                        .components()
-                        .any(|component| component.as_os_str() == *FS_DOT_GIT)
-                {
-                    result.push(path);
-                }
-            }
-        }
-        result
-    }
-
-    pub fn files(&self) -> Vec<PathBuf> {
-        let mut result = Vec::new();
-        let mut queue = collections::VecDeque::new();
-        queue.push_back((PathBuf::from("/"), self.state.lock().root.clone()));
-        while let Some((path, entry)) = queue.pop_front() {
-            let e = entry.lock();
-            match &*e {
-                FakeFsEntry::File { .. } => result.push(path),
-                FakeFsEntry::Dir { entries, .. } => {
-                    for (name, entry) in entries {
-                        queue.push_back((path.join(name), entry.clone()));
-                    }
-                }
-                FakeFsEntry::Symlink { .. } => {}
-            }
-        }
-        result
-    }
-
-    /// How many `read_dir` calls have been issued.
-    pub fn read_dir_call_count(&self) -> usize {
-        self.state.lock().read_dir_call_count
-    }
-
-    /// How many `metadata` calls have been issued.
-    pub fn metadata_call_count(&self) -> usize {
-        self.state.lock().metadata_call_count
-    }
-
-    fn simulate_random_delay(&self) -> impl futures::Future<Output = ()> {
-        self.executor.simulate_random_delay()
-    }
-}
-
-#[cfg(any(test, feature = "test-support"))]
-impl FakeFsEntry {
-    fn is_file(&self) -> bool {
-        matches!(self, Self::File { .. })
-    }
-
-    fn is_symlink(&self) -> bool {
-        matches!(self, Self::Symlink { .. })
-    }
-
-    fn file_content(&self, path: &Path) -> Result<&String> {
-        if let Self::File { content, .. } = self {
-            Ok(content)
-        } else {
-            Err(anyhow!("not a file: {}", path.display()))
-        }
-    }
-
-    fn set_file_content(&mut self, path: &Path, new_content: String) -> Result<()> {
-        if let Self::File { content, mtime, .. } = self {
-            *mtime = SystemTime::now();
-            *content = new_content;
-            Ok(())
-        } else {
-            Err(anyhow!("not a file: {}", path.display()))
-        }
-    }
-
-    fn dir_entries(
-        &mut self,
-        path: &Path,
-    ) -> Result<&mut BTreeMap<String, Arc<Mutex<FakeFsEntry>>>> {
-        if let Self::Dir { entries, .. } = self {
-            Ok(entries)
-        } else {
-            Err(anyhow!("not a directory: {}", path.display()))
-        }
-    }
-}
-
-#[cfg(any(test, feature = "test-support"))]
-#[async_trait::async_trait]
-impl Fs for FakeFs {
-    async fn create_dir(&self, path: &Path) -> Result<()> {
-        self.simulate_random_delay().await;
-
-        let mut created_dirs = Vec::new();
-        let mut cur_path = PathBuf::new();
-        for component in path.components() {
-            let mut state = self.state.lock();
-            cur_path.push(component);
-            if cur_path == Path::new("/") {
-                continue;
-            }
-
-            let inode = state.next_inode;
-            let mtime = state.next_mtime;
-            state.next_mtime += Duration::from_nanos(1);
-            state.next_inode += 1;
-            state.write_path(&cur_path, |entry| {
-                entry.or_insert_with(|| {
-                    created_dirs.push(cur_path.clone());
-                    Arc::new(Mutex::new(FakeFsEntry::Dir {
-                        inode,
-                        mtime,
-                        entries: Default::default(),
-                        git_repo_state: None,
-                    }))
-                });
-                Ok(())
-            })?
-        }
-
-        self.state.lock().emit_event(&created_dirs);
-        Ok(())
-    }
-
-    async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()> {
-        self.simulate_random_delay().await;
-        let mut state = self.state.lock();
-        let inode = state.next_inode;
-        let mtime = state.next_mtime;
-        state.next_mtime += Duration::from_nanos(1);
-        state.next_inode += 1;
-        let file = Arc::new(Mutex::new(FakeFsEntry::File {
-            inode,
-            mtime,
-            content: String::new(),
-        }));
-        state.write_path(path, |entry| {
-            match entry {
-                btree_map::Entry::Occupied(mut e) => {
-                    if options.overwrite {
-                        *e.get_mut() = file;
-                    } else if !options.ignore_if_exists {
-                        return Err(anyhow!("path already exists: {}", path.display()));
-                    }
-                }
-                btree_map::Entry::Vacant(e) => {
-                    e.insert(file);
-                }
-            }
-            Ok(())
-        })?;
-        state.emit_event(&[path]);
-        Ok(())
-    }
-
-    async fn rename(&self, old_path: &Path, new_path: &Path, options: RenameOptions) -> Result<()> {
-        self.simulate_random_delay().await;
-
-        let old_path = normalize_path(old_path);
-        let new_path = normalize_path(new_path);
-
-        let mut state = self.state.lock();
-        let moved_entry = state.write_path(&old_path, |e| {
-            if let btree_map::Entry::Occupied(e) = e {
-                Ok(e.get().clone())
-            } else {
-                Err(anyhow!("path does not exist: {}", &old_path.display()))
-            }
-        })?;
-
-        state.write_path(&new_path, |e| {
-            match e {
-                btree_map::Entry::Occupied(mut e) => {
-                    if options.overwrite {
-                        *e.get_mut() = moved_entry;
-                    } else if !options.ignore_if_exists {
-                        return Err(anyhow!("path already exists: {}", new_path.display()));
-                    }
-                }
-                btree_map::Entry::Vacant(e) => {
-                    e.insert(moved_entry);
-                }
-            }
-            Ok(())
-        })?;
-
-        state
-            .write_path(&old_path, |e| {
-                if let btree_map::Entry::Occupied(e) = e {
-                    Ok(e.remove())
-                } else {
-                    unreachable!()
-                }
-            })
-            .unwrap();
-
-        state.emit_event(&[old_path, new_path]);
-        Ok(())
-    }
-
-    async fn copy_file(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()> {
-        self.simulate_random_delay().await;
-
-        let source = normalize_path(source);
-        let target = normalize_path(target);
-        let mut state = self.state.lock();
-        let mtime = state.next_mtime;
-        let inode = util::post_inc(&mut state.next_inode);
-        state.next_mtime += Duration::from_nanos(1);
-        let source_entry = state.read_path(&source)?;
-        let content = source_entry.lock().file_content(&source)?.clone();
-        let entry = state.write_path(&target, |e| match e {
-            btree_map::Entry::Occupied(e) => {
-                if options.overwrite {
-                    Ok(Some(e.get().clone()))
-                } else if !options.ignore_if_exists {
-                    return Err(anyhow!("{target:?} already exists"));
-                } else {
-                    Ok(None)
-                }
-            }
-            btree_map::Entry::Vacant(e) => Ok(Some(
-                e.insert(Arc::new(Mutex::new(FakeFsEntry::File {
-                    inode,
-                    mtime,
-                    content: String::new(),
-                })))
-                .clone(),
-            )),
-        })?;
-        if let Some(entry) = entry {
-            entry.lock().set_file_content(&target, content)?;
-        }
-        state.emit_event(&[target]);
-        Ok(())
-    }
-
-    async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {
-        self.simulate_random_delay().await;
-
-        let path = normalize_path(path);
-        let parent_path = path
-            .parent()
-            .ok_or_else(|| anyhow!("cannot remove the root"))?;
-        let base_name = path.file_name().unwrap();
-
-        let mut state = self.state.lock();
-        let parent_entry = state.read_path(parent_path)?;
-        let mut parent_entry = parent_entry.lock();
-        let entry = parent_entry
-            .dir_entries(parent_path)?
-            .entry(base_name.to_str().unwrap().into());
-
-        match entry {
-            btree_map::Entry::Vacant(_) => {
-                if !options.ignore_if_not_exists {
-                    return Err(anyhow!("{path:?} does not exist"));
-                }
-            }
-            btree_map::Entry::Occupied(e) => {
-                {
-                    let mut entry = e.get().lock();
-                    let children = entry.dir_entries(&path)?;
-                    if !options.recursive && !children.is_empty() {
-                        return Err(anyhow!("{path:?} is not empty"));
-                    }
-                }
-                e.remove();
-            }
-        }
-        state.emit_event(&[path]);
-        Ok(())
-    }
-
-    async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()> {
-        self.simulate_random_delay().await;
-
-        let path = normalize_path(path);
-        let parent_path = path
-            .parent()
-            .ok_or_else(|| anyhow!("cannot remove the root"))?;
-        let base_name = path.file_name().unwrap();
-        let mut state = self.state.lock();
-        let parent_entry = state.read_path(parent_path)?;
-        let mut parent_entry = parent_entry.lock();
-        let entry = parent_entry
-            .dir_entries(parent_path)?
-            .entry(base_name.to_str().unwrap().into());
-        match entry {
-            btree_map::Entry::Vacant(_) => {
-                if !options.ignore_if_not_exists {
-                    return Err(anyhow!("{path:?} does not exist"));
-                }
-            }
-            btree_map::Entry::Occupied(e) => {
-                e.get().lock().file_content(&path)?;
-                e.remove();
-            }
-        }
-        state.emit_event(&[path]);
-        Ok(())
-    }
-
-    async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>> {
-        let text = self.load(path).await?;
-        Ok(Box::new(io::Cursor::new(text)))
-    }
-
-    async fn load(&self, path: &Path) -> Result<String> {
-        let path = normalize_path(path);
-        self.simulate_random_delay().await;
-        let state = self.state.lock();
-        let entry = state.read_path(&path)?;
-        let entry = entry.lock();
-        entry.file_content(&path).cloned()
-    }
-
-    async fn atomic_write(&self, path: PathBuf, data: String) -> Result<()> {
-        self.simulate_random_delay().await;
-        let path = normalize_path(path.as_path());
-        self.write_file_internal(path, data.to_string())?;
-
-        Ok(())
-    }
-
-    async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> {
-        self.simulate_random_delay().await;
-        let path = normalize_path(path);
-        let content = chunks(text, line_ending).collect();
-        if let Some(path) = path.parent() {
-            self.create_dir(path).await?;
-        }
-        self.write_file_internal(path, content)?;
-        Ok(())
-    }
-
-    async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
-        let path = normalize_path(path);
-        self.simulate_random_delay().await;
-        let state = self.state.lock();
-        if let Some((_, canonical_path)) = state.try_read_path(&path, true) {
-            Ok(canonical_path)
-        } else {
-            Err(anyhow!("path does not exist: {}", path.display()))
-        }
-    }
-
-    async fn is_file(&self, path: &Path) -> bool {
-        let path = normalize_path(path);
-        self.simulate_random_delay().await;
-        let state = self.state.lock();
-        if let Some((entry, _)) = state.try_read_path(&path, true) {
-            entry.lock().is_file()
-        } else {
-            false
-        }
-    }
-
-    async fn metadata(&self, path: &Path) -> Result<Option<Metadata>> {
-        self.simulate_random_delay().await;
-        let path = normalize_path(path);
-        let mut state = self.state.lock();
-        state.metadata_call_count += 1;
-        if let Some((mut entry, _)) = state.try_read_path(&path, false) {
-            let is_symlink = entry.lock().is_symlink();
-            if is_symlink {
-                if let Some(e) = state.try_read_path(&path, true).map(|e| e.0) {
-                    entry = e;
-                } else {
-                    return Ok(None);
-                }
-            }
-
-            let entry = entry.lock();
-            Ok(Some(match &*entry {
-                FakeFsEntry::File { inode, mtime, .. } => Metadata {
-                    inode: *inode,
-                    mtime: *mtime,
-                    is_dir: false,
-                    is_symlink,
-                },
-                FakeFsEntry::Dir { inode, mtime, .. } => Metadata {
-                    inode: *inode,
-                    mtime: *mtime,
-                    is_dir: true,
-                    is_symlink,
-                },
-                FakeFsEntry::Symlink { .. } => unreachable!(),
-            }))
-        } else {
-            Ok(None)
-        }
-    }
-
-    async fn read_link(&self, path: &Path) -> Result<PathBuf> {
-        self.simulate_random_delay().await;
-        let path = normalize_path(path);
-        let state = self.state.lock();
-        if let Some((entry, _)) = state.try_read_path(&path, false) {
-            let entry = entry.lock();
-            if let FakeFsEntry::Symlink { target } = &*entry {
-                Ok(target.clone())
-            } else {
-                Err(anyhow!("not a symlink: {}", path.display()))
-            }
-        } else {
-            Err(anyhow!("path does not exist: {}", path.display()))
-        }
-    }
-
-    async fn read_dir(
-        &self,
-        path: &Path,
-    ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>> {
-        self.simulate_random_delay().await;
-        let path = normalize_path(path);
-        let mut state = self.state.lock();
-        state.read_dir_call_count += 1;
-        let entry = state.read_path(&path)?;
-        let mut entry = entry.lock();
-        let children = entry.dir_entries(&path)?;
-        let paths = children
-            .keys()
-            .map(|file_name| Ok(path.join(file_name)))
-            .collect::<Vec<_>>();
-        Ok(Box::pin(futures::stream::iter(paths)))
-    }
-
-    async fn watch(
-        &self,
-        path: &Path,
-        _: Duration,
-    ) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>> {
-        self.simulate_random_delay().await;
-        let (tx, rx) = smol::channel::unbounded();
-        self.state.lock().event_txs.push(tx);
-        let path = path.to_path_buf();
-        let executor = self.executor.clone();
-        Box::pin(futures::StreamExt::filter(rx, move |events| {
-            let result = events.iter().any(|event| event.path.starts_with(&path));
-            let executor = executor.clone();
-            async move {
-                executor.simulate_random_delay().await;
-                result
-            }
-        }))
-    }
-
-    fn open_repo(&self, abs_dot_git: &Path) -> Option<Arc<Mutex<dyn GitRepository>>> {
-        let state = self.state.lock();
-        let entry = state.read_path(abs_dot_git).unwrap();
-        let mut entry = entry.lock();
-        if let FakeFsEntry::Dir { git_repo_state, .. } = &mut *entry {
-            let state = git_repo_state
-                .get_or_insert_with(|| Arc::new(Mutex::new(FakeGitRepositoryState::default())))
-                .clone();
-            Some(repository::FakeGitRepository::open(state))
-        } else {
-            None
-        }
-    }
-
-    fn is_fake(&self) -> bool {
-        true
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    fn as_fake(&self) -> &FakeFs {
-        self
-    }
-}
-
-fn chunks(rope: &Rope, line_ending: LineEnding) -> impl Iterator<Item = &str> {
-    rope.chunks().flat_map(move |chunk| {
-        let mut newline = false;
-        chunk.split('\n').flat_map(move |line| {
-            let ending = if newline {
-                Some(line_ending.as_str())
-            } else {
-                None
-            };
-            newline = true;
-            ending.into_iter().chain([line])
-        })
-    })
-}
-
-pub fn normalize_path(path: &Path) -> PathBuf {
-    let mut components = path.components().peekable();
-    let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
-        components.next();
-        PathBuf::from(c.as_os_str())
-    } else {
-        PathBuf::new()
-    };
-
-    for component in components {
-        match component {
-            Component::Prefix(..) => unreachable!(),
-            Component::RootDir => {
-                ret.push(component.as_os_str());
-            }
-            Component::CurDir => {}
-            Component::ParentDir => {
-                ret.pop();
-            }
-            Component::Normal(c) => {
-                ret.push(c);
-            }
-        }
-    }
-    ret
-}
-
-pub fn copy_recursive<'a>(
-    fs: &'a dyn Fs,
-    source: &'a Path,
-    target: &'a Path,
-    options: CopyOptions,
-) -> BoxFuture<'a, Result<()>> {
-    use futures::future::FutureExt;
-
-    async move {
-        let metadata = fs
-            .metadata(source)
-            .await?
-            .ok_or_else(|| anyhow!("path does not exist: {}", source.display()))?;
-        if metadata.is_dir {
-            if !options.overwrite && fs.metadata(target).await.is_ok() {
-                if options.ignore_if_exists {
-                    return Ok(());
-                } else {
-                    return Err(anyhow!("{target:?} already exists"));
-                }
-            }
-
-            let _ = fs
-                .remove_dir(
-                    target,
-                    RemoveOptions {
-                        recursive: true,
-                        ignore_if_not_exists: true,
-                    },
-                )
-                .await;
-            fs.create_dir(target).await?;
-            let mut children = fs.read_dir(source).await?;
-            while let Some(child_path) = children.next().await {
-                if let Ok(child_path) = child_path {
-                    if let Some(file_name) = child_path.file_name() {
-                        let child_target_path = target.join(file_name);
-                        copy_recursive(fs, &child_path, &child_target_path, options).await?;
-                    }
-                }
-            }
-
-            Ok(())
-        } else {
-            fs.copy_file(source, target, options).await
-        }
-    }
-    .boxed()
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use gpui::BackgroundExecutor;
-    use serde_json::json;
-
-    #[gpui::test]
-    async fn test_fake_fs(executor: BackgroundExecutor) {
-        let fs = FakeFs::new(executor.clone());
-        fs.insert_tree(
-            "/root",
-            json!({
-                "dir1": {
-                    "a": "A",
-                    "b": "B"
-                },
-                "dir2": {
-                    "c": "C",
-                    "dir3": {
-                        "d": "D"
-                    }
-                }
-            }),
-        )
-        .await;
-
-        assert_eq!(
-            fs.files(),
-            vec![
-                PathBuf::from("/root/dir1/a"),
-                PathBuf::from("/root/dir1/b"),
-                PathBuf::from("/root/dir2/c"),
-                PathBuf::from("/root/dir2/dir3/d"),
-            ]
-        );
-
-        fs.insert_symlink("/root/dir2/link-to-dir3", "./dir3".into())
-            .await;
-
-        assert_eq!(
-            fs.canonicalize("/root/dir2/link-to-dir3".as_ref())
-                .await
-                .unwrap(),
-            PathBuf::from("/root/dir2/dir3"),
-        );
-        assert_eq!(
-            fs.canonicalize("/root/dir2/link-to-dir3/d".as_ref())
-                .await
-                .unwrap(),
-            PathBuf::from("/root/dir2/dir3/d"),
-        );
-        assert_eq!(
-            fs.load("/root/dir2/link-to-dir3/d".as_ref()).await.unwrap(),
-            "D",
-        );
-    }
-}

crates/fs2/src/repository.rs 🔗

@@ -1,415 +0,0 @@
-use anyhow::Result;
-use collections::HashMap;
-use git2::{BranchType, StatusShow};
-use parking_lot::Mutex;
-use serde_derive::{Deserialize, Serialize};
-use std::{
-    cmp::Ordering,
-    ffi::OsStr,
-    os::unix::prelude::OsStrExt,
-    path::{Component, Path, PathBuf},
-    sync::Arc,
-    time::SystemTime,
-};
-use sum_tree::{MapSeekTarget, TreeMap};
-use util::ResultExt;
-
-pub use git2::Repository as LibGitRepository;
-
-#[derive(Clone, Debug, Hash, PartialEq)]
-pub struct Branch {
-    pub name: Box<str>,
-    /// Timestamp of most recent commit, normalized to Unix Epoch format.
-    pub unix_timestamp: Option<i64>,
-}
-
-pub trait GitRepository: Send {
-    fn reload_index(&self);
-    fn load_index_text(&self, relative_file_path: &Path) -> Option<String>;
-    fn branch_name(&self) -> Option<String>;
-
-    /// Get the statuses of all of the files in the index that start with the given
-    /// path and have changes with resepect to the HEAD commit. This is fast because
-    /// the index stores hashes of trees, so that unchanged directories can be skipped.
-    fn staged_statuses(&self, path_prefix: &Path) -> TreeMap<RepoPath, GitFileStatus>;
-
-    /// Get the status of a given file in the working directory with respect to
-    /// the index. In the common case, when there are no changes, this only requires
-    /// an index lookup. The index stores the mtime of each file when it was added,
-    /// so there's no work to do if the mtime matches.
-    fn unstaged_status(&self, path: &RepoPath, mtime: SystemTime) -> Option<GitFileStatus>;
-
-    /// Get the status of a given file in the working directory with respect to
-    /// the HEAD commit. In the common case, when there are no changes, this only
-    /// requires an index lookup and blob comparison between the index and the HEAD
-    /// commit. The index stores the mtime of each file when it was added, so there's
-    /// no need to consider the working directory file if the mtime matches.
-    fn status(&self, path: &RepoPath, mtime: SystemTime) -> Option<GitFileStatus>;
-
-    fn branches(&self) -> Result<Vec<Branch>>;
-    fn change_branch(&self, _: &str) -> Result<()>;
-    fn create_branch(&self, _: &str) -> Result<()>;
-}
-
-impl std::fmt::Debug for dyn GitRepository {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("dyn GitRepository<...>").finish()
-    }
-}
-
-impl GitRepository for LibGitRepository {
-    fn reload_index(&self) {
-        if let Ok(mut index) = self.index() {
-            _ = index.read(false);
-        }
-    }
-
-    fn load_index_text(&self, relative_file_path: &Path) -> Option<String> {
-        fn logic(repo: &LibGitRepository, relative_file_path: &Path) -> Result<Option<String>> {
-            const STAGE_NORMAL: i32 = 0;
-            let index = repo.index()?;
-
-            // This check is required because index.get_path() unwraps internally :(
-            check_path_to_repo_path_errors(relative_file_path)?;
-
-            let oid = match index.get_path(&relative_file_path, STAGE_NORMAL) {
-                Some(entry) => entry.id,
-                None => return Ok(None),
-            };
-
-            let content = repo.find_blob(oid)?.content().to_owned();
-            Ok(Some(String::from_utf8(content)?))
-        }
-
-        match logic(&self, relative_file_path) {
-            Ok(value) => return value,
-            Err(err) => log::error!("Error loading head text: {:?}", err),
-        }
-        None
-    }
-
-    fn branch_name(&self) -> Option<String> {
-        let head = self.head().log_err()?;
-        let branch = String::from_utf8_lossy(head.shorthand_bytes());
-        Some(branch.to_string())
-    }
-
-    fn staged_statuses(&self, path_prefix: &Path) -> TreeMap<RepoPath, GitFileStatus> {
-        let mut map = TreeMap::default();
-
-        let mut options = git2::StatusOptions::new();
-        options.pathspec(path_prefix);
-        options.show(StatusShow::Index);
-
-        if let Some(statuses) = self.statuses(Some(&mut options)).log_err() {
-            for status in statuses.iter() {
-                let path = RepoPath(PathBuf::from(OsStr::from_bytes(status.path_bytes())));
-                let status = status.status();
-                if !status.contains(git2::Status::IGNORED) {
-                    if let Some(status) = read_status(status) {
-                        map.insert(path, status)
-                    }
-                }
-            }
-        }
-        map
-    }
-
-    fn unstaged_status(&self, path: &RepoPath, mtime: SystemTime) -> Option<GitFileStatus> {
-        // If the file has not changed since it was added to the index, then
-        // there can't be any changes.
-        if matches_index(self, path, mtime) {
-            return None;
-        }
-
-        let mut options = git2::StatusOptions::new();
-        options.pathspec(&path.0);
-        options.disable_pathspec_match(true);
-        options.include_untracked(true);
-        options.recurse_untracked_dirs(true);
-        options.include_unmodified(true);
-        options.show(StatusShow::Workdir);
-
-        let statuses = self.statuses(Some(&mut options)).log_err()?;
-        let status = statuses.get(0).and_then(|s| read_status(s.status()));
-        status
-    }
-
-    fn status(&self, path: &RepoPath, mtime: SystemTime) -> Option<GitFileStatus> {
-        let mut options = git2::StatusOptions::new();
-        options.pathspec(&path.0);
-        options.disable_pathspec_match(true);
-        options.include_untracked(true);
-        options.recurse_untracked_dirs(true);
-        options.include_unmodified(true);
-
-        // If the file has not changed since it was added to the index, then
-        // there's no need to examine the working directory file: just compare
-        // the blob in the index to the one in the HEAD commit.
-        if matches_index(self, path, mtime) {
-            options.show(StatusShow::Index);
-        }
-
-        let statuses = self.statuses(Some(&mut options)).log_err()?;
-        let status = statuses.get(0).and_then(|s| read_status(s.status()));
-        status
-    }
-
-    fn branches(&self) -> Result<Vec<Branch>> {
-        let local_branches = self.branches(Some(BranchType::Local))?;
-        let valid_branches = local_branches
-            .filter_map(|branch| {
-                branch.ok().and_then(|(branch, _)| {
-                    let name = branch.name().ok().flatten().map(Box::from)?;
-                    let timestamp = branch.get().peel_to_commit().ok()?.time();
-                    let unix_timestamp = timestamp.seconds();
-                    let timezone_offset = timestamp.offset_minutes();
-                    let utc_offset =
-                        time::UtcOffset::from_whole_seconds(timezone_offset * 60).ok()?;
-                    let unix_timestamp =
-                        time::OffsetDateTime::from_unix_timestamp(unix_timestamp).ok()?;
-                    Some(Branch {
-                        name,
-                        unix_timestamp: Some(unix_timestamp.to_offset(utc_offset).unix_timestamp()),
-                    })
-                })
-            })
-            .collect();
-        Ok(valid_branches)
-    }
-    fn change_branch(&self, name: &str) -> Result<()> {
-        let revision = self.find_branch(name, BranchType::Local)?;
-        let revision = revision.get();
-        let as_tree = revision.peel_to_tree()?;
-        self.checkout_tree(as_tree.as_object(), None)?;
-        self.set_head(
-            revision
-                .name()
-                .ok_or_else(|| anyhow::anyhow!("Branch name could not be retrieved"))?,
-        )?;
-        Ok(())
-    }
-    fn create_branch(&self, name: &str) -> Result<()> {
-        let current_commit = self.head()?.peel_to_commit()?;
-        self.branch(name, &current_commit, false)?;
-
-        Ok(())
-    }
-}
-
-fn matches_index(repo: &LibGitRepository, path: &RepoPath, mtime: SystemTime) -> bool {
-    if let Some(index) = repo.index().log_err() {
-        if let Some(entry) = index.get_path(&path, 0) {
-            if let Some(mtime) = mtime.duration_since(SystemTime::UNIX_EPOCH).log_err() {
-                if entry.mtime.seconds() == mtime.as_secs() as i32
-                    && entry.mtime.nanoseconds() == mtime.subsec_nanos()
-                {
-                    return true;
-                }
-            }
-        }
-    }
-    false
-}
-
-fn read_status(status: git2::Status) -> Option<GitFileStatus> {
-    if status.contains(git2::Status::CONFLICTED) {
-        Some(GitFileStatus::Conflict)
-    } else if status.intersects(
-        git2::Status::WT_MODIFIED
-            | git2::Status::WT_RENAMED
-            | git2::Status::INDEX_MODIFIED
-            | git2::Status::INDEX_RENAMED,
-    ) {
-        Some(GitFileStatus::Modified)
-    } else if status.intersects(git2::Status::WT_NEW | git2::Status::INDEX_NEW) {
-        Some(GitFileStatus::Added)
-    } else {
-        None
-    }
-}
-
-#[derive(Debug, Clone, Default)]
-pub struct FakeGitRepository {
-    state: Arc<Mutex<FakeGitRepositoryState>>,
-}
-
-#[derive(Debug, Clone, Default)]
-pub struct FakeGitRepositoryState {
-    pub index_contents: HashMap<PathBuf, String>,
-    pub worktree_statuses: HashMap<RepoPath, GitFileStatus>,
-    pub branch_name: Option<String>,
-}
-
-impl FakeGitRepository {
-    pub fn open(state: Arc<Mutex<FakeGitRepositoryState>>) -> Arc<Mutex<dyn GitRepository>> {
-        Arc::new(Mutex::new(FakeGitRepository { state }))
-    }
-}
-
-impl GitRepository for FakeGitRepository {
-    fn reload_index(&self) {}
-
-    fn load_index_text(&self, path: &Path) -> Option<String> {
-        let state = self.state.lock();
-        state.index_contents.get(path).cloned()
-    }
-
-    fn branch_name(&self) -> Option<String> {
-        let state = self.state.lock();
-        state.branch_name.clone()
-    }
-
-    fn staged_statuses(&self, path_prefix: &Path) -> TreeMap<RepoPath, GitFileStatus> {
-        let mut map = TreeMap::default();
-        let state = self.state.lock();
-        for (repo_path, status) in state.worktree_statuses.iter() {
-            if repo_path.0.starts_with(path_prefix) {
-                map.insert(repo_path.to_owned(), status.to_owned());
-            }
-        }
-        map
-    }
-
-    fn unstaged_status(&self, _path: &RepoPath, _mtime: SystemTime) -> Option<GitFileStatus> {
-        None
-    }
-
-    fn status(&self, path: &RepoPath, _mtime: SystemTime) -> Option<GitFileStatus> {
-        let state = self.state.lock();
-        state.worktree_statuses.get(path).cloned()
-    }
-
-    fn branches(&self) -> Result<Vec<Branch>> {
-        Ok(vec![])
-    }
-
-    fn change_branch(&self, name: &str) -> Result<()> {
-        let mut state = self.state.lock();
-        state.branch_name = Some(name.to_owned());
-        Ok(())
-    }
-
-    fn create_branch(&self, name: &str) -> Result<()> {
-        let mut state = self.state.lock();
-        state.branch_name = Some(name.to_owned());
-        Ok(())
-    }
-}
-
-fn check_path_to_repo_path_errors(relative_file_path: &Path) -> Result<()> {
-    match relative_file_path.components().next() {
-        None => anyhow::bail!("repo path should not be empty"),
-        Some(Component::Prefix(_)) => anyhow::bail!(
-            "repo path `{}` should be relative, not a windows prefix",
-            relative_file_path.to_string_lossy()
-        ),
-        Some(Component::RootDir) => {
-            anyhow::bail!(
-                "repo path `{}` should be relative",
-                relative_file_path.to_string_lossy()
-            )
-        }
-        Some(Component::CurDir) => {
-            anyhow::bail!(
-                "repo path `{}` should not start with `.`",
-                relative_file_path.to_string_lossy()
-            )
-        }
-        Some(Component::ParentDir) => {
-            anyhow::bail!(
-                "repo path `{}` should not start with `..`",
-                relative_file_path.to_string_lossy()
-            )
-        }
-        _ => Ok(()),
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
-pub enum GitFileStatus {
-    Added,
-    Modified,
-    Conflict,
-}
-
-impl GitFileStatus {
-    pub fn merge(
-        this: Option<GitFileStatus>,
-        other: Option<GitFileStatus>,
-        prefer_other: bool,
-    ) -> Option<GitFileStatus> {
-        if prefer_other {
-            return other;
-        } else {
-            match (this, other) {
-                (Some(GitFileStatus::Conflict), _) | (_, Some(GitFileStatus::Conflict)) => {
-                    Some(GitFileStatus::Conflict)
-                }
-                (Some(GitFileStatus::Modified), _) | (_, Some(GitFileStatus::Modified)) => {
-                    Some(GitFileStatus::Modified)
-                }
-                (Some(GitFileStatus::Added), _) | (_, Some(GitFileStatus::Added)) => {
-                    Some(GitFileStatus::Added)
-                }
-                _ => None,
-            }
-        }
-    }
-}
-
-#[derive(Clone, Debug, Ord, Hash, PartialOrd, Eq, PartialEq)]
-pub struct RepoPath(pub PathBuf);
-
-impl RepoPath {
-    pub fn new(path: PathBuf) -> Self {
-        debug_assert!(path.is_relative(), "Repo paths must be relative");
-
-        RepoPath(path)
-    }
-}
-
-impl From<&Path> for RepoPath {
-    fn from(value: &Path) -> Self {
-        RepoPath::new(value.to_path_buf())
-    }
-}
-
-impl From<PathBuf> for RepoPath {
-    fn from(value: PathBuf) -> Self {
-        RepoPath::new(value)
-    }
-}
-
-impl Default for RepoPath {
-    fn default() -> Self {
-        RepoPath(PathBuf::new())
-    }
-}
-
-impl AsRef<Path> for RepoPath {
-    fn as_ref(&self) -> &Path {
-        self.0.as_ref()
-    }
-}
-
-impl std::ops::Deref for RepoPath {
-    type Target = PathBuf;
-
-    fn deref(&self) -> &Self::Target {
-        &self.0
-    }
-}
-
-#[derive(Debug)]
-pub struct RepoPathDescendants<'a>(pub &'a Path);
-
-impl<'a> MapSeekTarget<RepoPath> for RepoPathDescendants<'a> {
-    fn cmp_cursor(&self, key: &RepoPath) -> Ordering {
-        if key.starts_with(&self.0) {
-            Ordering::Greater
-        } else {
-            self.0.cmp(key)
-        }
-    }
-}

crates/fuzzy/src/paths.rs 🔗

@@ -1,3 +1,4 @@
+use gpui::BackgroundExecutor;
 use std::{
     borrow::Cow,
     cmp::{self, Ordering},
@@ -5,8 +6,6 @@ use std::{
     sync::{atomic::AtomicBool, Arc},
 };
 
-use gpui::executor;
-
 use crate::{
     matcher::{Match, MatchCandidate, Matcher},
     CharBag,
@@ -135,7 +134,7 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
     smart_case: bool,
     max_results: usize,
     cancel_flag: &AtomicBool,
-    background: Arc<executor::Background>,
+    executor: BackgroundExecutor,
 ) -> Vec<PathMatch> {
     let path_count: usize = candidate_sets.iter().map(|s| s.len()).sum();
     if path_count == 0 {
@@ -149,13 +148,13 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
     let query = &query;
     let query_char_bag = CharBag::from(&lowercase_query[..]);
 
-    let num_cpus = background.num_cpus().min(path_count);
+    let num_cpus = executor.num_cpus().min(path_count);
     let segment_size = (path_count + num_cpus - 1) / num_cpus;
     let mut segment_results = (0..num_cpus)
         .map(|_| Vec::with_capacity(max_results))
         .collect::<Vec<_>>();
 
-    background
+    executor
         .scoped(|scope| {
             for (segment_idx, results) in segment_results.iter_mut().enumerate() {
                 let relative_to = relative_to.clone();

crates/fuzzy/src/strings.rs 🔗

@@ -1,15 +1,15 @@
-use std::{
-    borrow::Cow,
-    cmp::{self, Ordering},
-    sync::{atomic::AtomicBool, Arc},
-};
-
-use gpui::executor;
-
 use crate::{
     matcher::{Match, MatchCandidate, Matcher},
     CharBag,
 };
+use gpui::BackgroundExecutor;
+use std::{
+    borrow::Cow,
+    cmp::{self, Ordering},
+    iter,
+    ops::Range,
+    sync::atomic::AtomicBool,
+};
 
 #[derive(Clone, Debug)]
 pub struct StringMatchCandidate {
@@ -56,6 +56,32 @@ pub struct StringMatch {
     pub string: String,
 }
 
+impl StringMatch {
+    pub fn ranges<'a>(&'a self) -> impl 'a + Iterator<Item = Range<usize>> {
+        let mut positions = self.positions.iter().peekable();
+        iter::from_fn(move || {
+            while let Some(start) = positions.next().copied() {
+                let mut end = start + self.char_len_at_index(start);
+                while let Some(next_start) = positions.peek() {
+                    if end == **next_start {
+                        end += self.char_len_at_index(end);
+                        positions.next();
+                    } else {
+                        break;
+                    }
+                }
+
+                return Some(start..end);
+            }
+            None
+        })
+    }
+
+    fn char_len_at_index(&self, ix: usize) -> usize {
+        self.string[ix..].chars().next().unwrap().len_utf8()
+    }
+}
+
 impl PartialEq for StringMatch {
     fn eq(&self, other: &Self) -> bool {
         self.cmp(other).is_eq()
@@ -85,7 +111,7 @@ pub async fn match_strings(
     smart_case: bool,
     max_results: usize,
     cancel_flag: &AtomicBool,
-    background: Arc<executor::Background>,
+    executor: BackgroundExecutor,
 ) -> Vec<StringMatch> {
     if candidates.is_empty() || max_results == 0 {
         return Default::default();
@@ -110,13 +136,13 @@ pub async fn match_strings(
     let query = &query;
     let query_char_bag = CharBag::from(&lowercase_query[..]);
 
-    let num_cpus = background.num_cpus().min(candidates.len());
+    let num_cpus = executor.num_cpus().min(candidates.len());
     let segment_size = (candidates.len() + num_cpus - 1) / num_cpus;
     let mut segment_results = (0..num_cpus)
         .map(|_| Vec::with_capacity(max_results.min(candidates.len())))
         .collect::<Vec<_>>();
 
-    background
+    executor
         .scoped(|scope| {
             for (segment_idx, results) in segment_results.iter_mut().enumerate() {
                 let cancel_flag = &cancel_flag;

crates/fuzzy2/Cargo.toml 🔗

@@ -1,13 +0,0 @@
-[package]
-name = "fuzzy2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/fuzzy2.rs"
-doctest = false
-
-[dependencies]
-gpui = { package = "gpui2", path = "../gpui2" }
-util = { path = "../util" }

crates/fuzzy2/src/char_bag.rs 🔗

@@ -1,63 +0,0 @@
-use std::iter::FromIterator;
-
-#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
-pub struct CharBag(u64);
-
-impl CharBag {
-    pub fn is_superset(self, other: CharBag) -> bool {
-        self.0 & other.0 == other.0
-    }
-
-    fn insert(&mut self, c: char) {
-        let c = c.to_ascii_lowercase();
-        if ('a'..='z').contains(&c) {
-            let mut count = self.0;
-            let idx = c as u8 - b'a';
-            count >>= idx * 2;
-            count = ((count << 1) | 1) & 3;
-            count <<= idx * 2;
-            self.0 |= count;
-        } else if ('0'..='9').contains(&c) {
-            let idx = c as u8 - b'0';
-            self.0 |= 1 << (idx + 52);
-        } else if c == '-' {
-            self.0 |= 1 << 62;
-        }
-    }
-}
-
-impl Extend<char> for CharBag {
-    fn extend<T: IntoIterator<Item = char>>(&mut self, iter: T) {
-        for c in iter {
-            self.insert(c);
-        }
-    }
-}
-
-impl FromIterator<char> for CharBag {
-    fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
-        let mut result = Self::default();
-        result.extend(iter);
-        result
-    }
-}
-
-impl From<&str> for CharBag {
-    fn from(s: &str) -> Self {
-        let mut bag = Self(0);
-        for c in s.chars() {
-            bag.insert(c);
-        }
-        bag
-    }
-}
-
-impl From<&[char]> for CharBag {
-    fn from(chars: &[char]) -> Self {
-        let mut bag = Self(0);
-        for c in chars {
-            bag.insert(*c);
-        }
-        bag
-    }
-}

crates/fuzzy2/src/fuzzy2.rs 🔗

@@ -1,10 +0,0 @@
-mod char_bag;
-mod matcher;
-mod paths;
-mod strings;
-
-pub use char_bag::CharBag;
-pub use paths::{
-    match_fixed_path_set, match_path_sets, PathMatch, PathMatchCandidate, PathMatchCandidateSet,
-};
-pub use strings::{match_strings, StringMatch, StringMatchCandidate};

crates/fuzzy2/src/matcher.rs 🔗

@@ -1,464 +0,0 @@
-use std::{
-    borrow::Cow,
-    sync::atomic::{self, AtomicBool},
-};
-
-use crate::CharBag;
-
-const BASE_DISTANCE_PENALTY: f64 = 0.6;
-const ADDITIONAL_DISTANCE_PENALTY: f64 = 0.05;
-const MIN_DISTANCE_PENALTY: f64 = 0.2;
-
-pub struct Matcher<'a> {
-    query: &'a [char],
-    lowercase_query: &'a [char],
-    query_char_bag: CharBag,
-    smart_case: bool,
-    max_results: usize,
-    min_score: f64,
-    match_positions: Vec<usize>,
-    last_positions: Vec<usize>,
-    score_matrix: Vec<Option<f64>>,
-    best_position_matrix: Vec<usize>,
-}
-
-pub trait Match: Ord {
-    fn score(&self) -> f64;
-    fn set_positions(&mut self, positions: Vec<usize>);
-}
-
-pub trait MatchCandidate {
-    fn has_chars(&self, bag: CharBag) -> bool;
-    fn to_string(&self) -> Cow<'_, str>;
-}
-
-impl<'a> Matcher<'a> {
-    pub fn new(
-        query: &'a [char],
-        lowercase_query: &'a [char],
-        query_char_bag: CharBag,
-        smart_case: bool,
-        max_results: usize,
-    ) -> Self {
-        Self {
-            query,
-            lowercase_query,
-            query_char_bag,
-            min_score: 0.0,
-            last_positions: vec![0; query.len()],
-            match_positions: vec![0; query.len()],
-            score_matrix: Vec::new(),
-            best_position_matrix: Vec::new(),
-            smart_case,
-            max_results,
-        }
-    }
-
-    pub fn match_candidates<C: MatchCandidate, R, F>(
-        &mut self,
-        prefix: &[char],
-        lowercase_prefix: &[char],
-        candidates: impl Iterator<Item = C>,
-        results: &mut Vec<R>,
-        cancel_flag: &AtomicBool,
-        build_match: F,
-    ) where
-        R: Match,
-        F: Fn(&C, f64) -> R,
-    {
-        let mut candidate_chars = Vec::new();
-        let mut lowercase_candidate_chars = Vec::new();
-
-        for candidate in candidates {
-            if !candidate.has_chars(self.query_char_bag) {
-                continue;
-            }
-
-            if cancel_flag.load(atomic::Ordering::Relaxed) {
-                break;
-            }
-
-            candidate_chars.clear();
-            lowercase_candidate_chars.clear();
-            for c in candidate.to_string().chars() {
-                candidate_chars.push(c);
-                lowercase_candidate_chars.push(c.to_ascii_lowercase());
-            }
-
-            if !self.find_last_positions(lowercase_prefix, &lowercase_candidate_chars) {
-                continue;
-            }
-
-            let matrix_len = self.query.len() * (prefix.len() + candidate_chars.len());
-            self.score_matrix.clear();
-            self.score_matrix.resize(matrix_len, None);
-            self.best_position_matrix.clear();
-            self.best_position_matrix.resize(matrix_len, 0);
-
-            let score = self.score_match(
-                &candidate_chars,
-                &lowercase_candidate_chars,
-                prefix,
-                lowercase_prefix,
-            );
-
-            if score > 0.0 {
-                let mut mat = build_match(&candidate, score);
-                if let Err(i) = results.binary_search_by(|m| mat.cmp(m)) {
-                    if results.len() < self.max_results {
-                        mat.set_positions(self.match_positions.clone());
-                        results.insert(i, mat);
-                    } else if i < results.len() {
-                        results.pop();
-                        mat.set_positions(self.match_positions.clone());
-                        results.insert(i, mat);
-                    }
-                    if results.len() == self.max_results {
-                        self.min_score = results.last().unwrap().score();
-                    }
-                }
-            }
-        }
-    }
-
-    fn find_last_positions(
-        &mut self,
-        lowercase_prefix: &[char],
-        lowercase_candidate: &[char],
-    ) -> bool {
-        let mut lowercase_prefix = lowercase_prefix.iter();
-        let mut lowercase_candidate = lowercase_candidate.iter();
-        for (i, char) in self.lowercase_query.iter().enumerate().rev() {
-            if let Some(j) = lowercase_candidate.rposition(|c| c == char) {
-                self.last_positions[i] = j + lowercase_prefix.len();
-            } else if let Some(j) = lowercase_prefix.rposition(|c| c == char) {
-                self.last_positions[i] = j;
-            } else {
-                return false;
-            }
-        }
-        true
-    }
-
-    fn score_match(
-        &mut self,
-        path: &[char],
-        path_cased: &[char],
-        prefix: &[char],
-        lowercase_prefix: &[char],
-    ) -> f64 {
-        let score = self.recursive_score_match(
-            path,
-            path_cased,
-            prefix,
-            lowercase_prefix,
-            0,
-            0,
-            self.query.len() as f64,
-        ) * self.query.len() as f64;
-
-        if score <= 0.0 {
-            return 0.0;
-        }
-
-        let path_len = prefix.len() + path.len();
-        let mut cur_start = 0;
-        let mut byte_ix = 0;
-        let mut char_ix = 0;
-        for i in 0..self.query.len() {
-            let match_char_ix = self.best_position_matrix[i * path_len + cur_start];
-            while char_ix < match_char_ix {
-                let ch = prefix
-                    .get(char_ix)
-                    .or_else(|| path.get(char_ix - prefix.len()))
-                    .unwrap();
-                byte_ix += ch.len_utf8();
-                char_ix += 1;
-            }
-            cur_start = match_char_ix + 1;
-            self.match_positions[i] = byte_ix;
-        }
-
-        score
-    }
-
-    #[allow(clippy::too_many_arguments)]
-    fn recursive_score_match(
-        &mut self,
-        path: &[char],
-        path_cased: &[char],
-        prefix: &[char],
-        lowercase_prefix: &[char],
-        query_idx: usize,
-        path_idx: usize,
-        cur_score: f64,
-    ) -> f64 {
-        if query_idx == self.query.len() {
-            return 1.0;
-        }
-
-        let path_len = prefix.len() + path.len();
-
-        if let Some(memoized) = self.score_matrix[query_idx * path_len + path_idx] {
-            return memoized;
-        }
-
-        let mut score = 0.0;
-        let mut best_position = 0;
-
-        let query_char = self.lowercase_query[query_idx];
-        let limit = self.last_positions[query_idx];
-
-        let mut last_slash = 0;
-        for j in path_idx..=limit {
-            let path_char = if j < prefix.len() {
-                lowercase_prefix[j]
-            } else {
-                path_cased[j - prefix.len()]
-            };
-            let is_path_sep = path_char == '/' || path_char == '\\';
-
-            if query_idx == 0 && is_path_sep {
-                last_slash = j;
-            }
-
-            if query_char == path_char || (is_path_sep && query_char == '_' || query_char == '\\') {
-                let curr = if j < prefix.len() {
-                    prefix[j]
-                } else {
-                    path[j - prefix.len()]
-                };
-
-                let mut char_score = 1.0;
-                if j > path_idx {
-                    let last = if j - 1 < prefix.len() {
-                        prefix[j - 1]
-                    } else {
-                        path[j - 1 - prefix.len()]
-                    };
-
-                    if last == '/' {
-                        char_score = 0.9;
-                    } else if (last == '-' || last == '_' || last == ' ' || last.is_numeric())
-                        || (last.is_lowercase() && curr.is_uppercase())
-                    {
-                        char_score = 0.8;
-                    } else if last == '.' {
-                        char_score = 0.7;
-                    } else if query_idx == 0 {
-                        char_score = BASE_DISTANCE_PENALTY;
-                    } else {
-                        char_score = MIN_DISTANCE_PENALTY.max(
-                            BASE_DISTANCE_PENALTY
-                                - (j - path_idx - 1) as f64 * ADDITIONAL_DISTANCE_PENALTY,
-                        );
-                    }
-                }
-
-                // Apply a severe penalty if the case doesn't match.
-                // This will make the exact matches have higher score than the case-insensitive and the
-                // path insensitive matches.
-                if (self.smart_case || curr == '/') && self.query[query_idx] != curr {
-                    char_score *= 0.001;
-                }
-
-                let mut multiplier = char_score;
-
-                // Scale the score based on how deep within the path we found the match.
-                if query_idx == 0 {
-                    multiplier /= ((prefix.len() + path.len()) - last_slash) as f64;
-                }
-
-                let mut next_score = 1.0;
-                if self.min_score > 0.0 {
-                    next_score = cur_score * multiplier;
-                    // Scores only decrease. If we can't pass the previous best, bail
-                    if next_score < self.min_score {
-                        // Ensure that score is non-zero so we use it in the memo table.
-                        if score == 0.0 {
-                            score = 1e-18;
-                        }
-                        continue;
-                    }
-                }
-
-                let new_score = self.recursive_score_match(
-                    path,
-                    path_cased,
-                    prefix,
-                    lowercase_prefix,
-                    query_idx + 1,
-                    j + 1,
-                    next_score,
-                ) * multiplier;
-
-                if new_score > score {
-                    score = new_score;
-                    best_position = j;
-                    // Optimization: can't score better than 1.
-                    if new_score == 1.0 {
-                        break;
-                    }
-                }
-            }
-        }
-
-        if best_position != 0 {
-            self.best_position_matrix[query_idx * path_len + path_idx] = best_position;
-        }
-
-        self.score_matrix[query_idx * path_len + path_idx] = Some(score);
-        score
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::{PathMatch, PathMatchCandidate};
-
-    use super::*;
-    use std::{
-        path::{Path, PathBuf},
-        sync::Arc,
-    };
-
-    #[test]
-    fn test_get_last_positions() {
-        let mut query: &[char] = &['d', 'c'];
-        let mut matcher = Matcher::new(query, query, query.into(), false, 10);
-        let result = matcher.find_last_positions(&['a', 'b', 'c'], &['b', 'd', 'e', 'f']);
-        assert!(!result);
-
-        query = &['c', 'd'];
-        let mut matcher = Matcher::new(query, query, query.into(), false, 10);
-        let result = matcher.find_last_positions(&['a', 'b', 'c'], &['b', 'd', 'e', 'f']);
-        assert!(result);
-        assert_eq!(matcher.last_positions, vec![2, 4]);
-
-        query = &['z', '/', 'z', 'f'];
-        let mut matcher = Matcher::new(query, query, query.into(), false, 10);
-        let result = matcher.find_last_positions(&['z', 'e', 'd', '/'], &['z', 'e', 'd', '/', 'f']);
-        assert!(result);
-        assert_eq!(matcher.last_positions, vec![0, 3, 4, 8]);
-    }
-
-    #[test]
-    fn test_match_path_entries() {
-        let paths = vec![
-            "",
-            "a",
-            "ab",
-            "abC",
-            "abcd",
-            "alphabravocharlie",
-            "AlphaBravoCharlie",
-            "thisisatestdir",
-            "/////ThisIsATestDir",
-            "/this/is/a/test/dir",
-            "/test/tiatd",
-        ];
-
-        assert_eq!(
-            match_single_path_query("abc", false, &paths),
-            vec![
-                ("abC", vec![0, 1, 2]),
-                ("abcd", vec![0, 1, 2]),
-                ("AlphaBravoCharlie", vec![0, 5, 10]),
-                ("alphabravocharlie", vec![4, 5, 10]),
-            ]
-        );
-        assert_eq!(
-            match_single_path_query("t/i/a/t/d", false, &paths),
-            vec![("/this/is/a/test/dir", vec![1, 5, 6, 8, 9, 10, 11, 15, 16]),]
-        );
-
-        assert_eq!(
-            match_single_path_query("tiatd", false, &paths),
-            vec![
-                ("/test/tiatd", vec![6, 7, 8, 9, 10]),
-                ("/this/is/a/test/dir", vec![1, 6, 9, 11, 16]),
-                ("/////ThisIsATestDir", vec![5, 9, 11, 12, 16]),
-                ("thisisatestdir", vec![0, 2, 6, 7, 11]),
-            ]
-        );
-    }
-
-    #[test]
-    fn test_match_multibyte_path_entries() {
-        let paths = vec!["aαbβ/cγdδ", "αβγδ/bcde", "c1️⃣2️⃣3️⃣/d4️⃣5️⃣6️⃣/e7️⃣8️⃣9️⃣/f", "/d/🆒/h"];
-        assert_eq!("1️⃣".len(), 7);
-        assert_eq!(
-            match_single_path_query("bcd", false, &paths),
-            vec![
-                ("αβγδ/bcde", vec![9, 10, 11]),
-                ("aαbβ/cγdδ", vec![3, 7, 10]),
-            ]
-        );
-        assert_eq!(
-            match_single_path_query("cde", false, &paths),
-            vec![
-                ("αβγδ/bcde", vec![10, 11, 12]),
-                ("c1️⃣2️⃣3️⃣/d4️⃣5️⃣6️⃣/e7️⃣8️⃣9️⃣/f", vec![0, 23, 46]),
-            ]
-        );
-    }
-
-    fn match_single_path_query<'a>(
-        query: &str,
-        smart_case: bool,
-        paths: &[&'a str],
-    ) -> Vec<(&'a str, Vec<usize>)> {
-        let lowercase_query = query.to_lowercase().chars().collect::<Vec<_>>();
-        let query = query.chars().collect::<Vec<_>>();
-        let query_chars = CharBag::from(&lowercase_query[..]);
-
-        let path_arcs: Vec<Arc<Path>> = paths
-            .iter()
-            .map(|path| Arc::from(PathBuf::from(path)))
-            .collect::<Vec<_>>();
-        let mut path_entries = Vec::new();
-        for (i, path) in paths.iter().enumerate() {
-            let lowercase_path = path.to_lowercase().chars().collect::<Vec<_>>();
-            let char_bag = CharBag::from(lowercase_path.as_slice());
-            path_entries.push(PathMatchCandidate {
-                char_bag,
-                path: &path_arcs[i],
-            });
-        }
-
-        let mut matcher = Matcher::new(&query, &lowercase_query, query_chars, smart_case, 100);
-
-        let cancel_flag = AtomicBool::new(false);
-        let mut results = Vec::new();
-
-        matcher.match_candidates(
-            &[],
-            &[],
-            path_entries.into_iter(),
-            &mut results,
-            &cancel_flag,
-            |candidate, score| PathMatch {
-                score,
-                worktree_id: 0,
-                positions: Vec::new(),
-                path: Arc::from(candidate.path),
-                path_prefix: "".into(),
-                distance_to_relative_ancestor: usize::MAX,
-            },
-        );
-
-        results
-            .into_iter()
-            .map(|result| {
-                (
-                    paths
-                        .iter()
-                        .copied()
-                        .find(|p| result.path.as_ref() == Path::new(p))
-                        .unwrap(),
-                    result.positions,
-                )
-            })
-            .collect()
-    }
-}

crates/fuzzy2/src/paths.rs 🔗

@@ -1,257 +0,0 @@
-use gpui::BackgroundExecutor;
-use std::{
-    borrow::Cow,
-    cmp::{self, Ordering},
-    path::Path,
-    sync::{atomic::AtomicBool, Arc},
-};
-
-use crate::{
-    matcher::{Match, MatchCandidate, Matcher},
-    CharBag,
-};
-
-#[derive(Clone, Debug)]
-pub struct PathMatchCandidate<'a> {
-    pub path: &'a Path,
-    pub char_bag: CharBag,
-}
-
-#[derive(Clone, Debug)]
-pub struct PathMatch {
-    pub score: f64,
-    pub positions: Vec<usize>,
-    pub worktree_id: usize,
-    pub path: Arc<Path>,
-    pub path_prefix: Arc<str>,
-    /// Number of steps removed from a shared parent with the relative path
-    /// Used to order closer paths first in the search list
-    pub distance_to_relative_ancestor: usize,
-}
-
-pub trait PathMatchCandidateSet<'a>: Send + Sync {
-    type Candidates: Iterator<Item = PathMatchCandidate<'a>>;
-    fn id(&self) -> usize;
-    fn len(&self) -> usize;
-    fn is_empty(&self) -> bool {
-        self.len() == 0
-    }
-    fn prefix(&self) -> Arc<str>;
-    fn candidates(&'a self, start: usize) -> Self::Candidates;
-}
-
-impl Match for PathMatch {
-    fn score(&self) -> f64 {
-        self.score
-    }
-
-    fn set_positions(&mut self, positions: Vec<usize>) {
-        self.positions = positions;
-    }
-}
-
-impl<'a> MatchCandidate for PathMatchCandidate<'a> {
-    fn has_chars(&self, bag: CharBag) -> bool {
-        self.char_bag.is_superset(bag)
-    }
-
-    fn to_string(&self) -> Cow<'a, str> {
-        self.path.to_string_lossy()
-    }
-}
-
-impl PartialEq for PathMatch {
-    fn eq(&self, other: &Self) -> bool {
-        self.cmp(other).is_eq()
-    }
-}
-
-impl Eq for PathMatch {}
-
-impl PartialOrd for PathMatch {
-    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
-        Some(self.cmp(other))
-    }
-}
-
-impl Ord for PathMatch {
-    fn cmp(&self, other: &Self) -> Ordering {
-        self.score
-            .partial_cmp(&other.score)
-            .unwrap_or(Ordering::Equal)
-            .then_with(|| self.worktree_id.cmp(&other.worktree_id))
-            .then_with(|| {
-                other
-                    .distance_to_relative_ancestor
-                    .cmp(&self.distance_to_relative_ancestor)
-            })
-            .then_with(|| self.path.cmp(&other.path))
-    }
-}
-
-pub fn match_fixed_path_set(
-    candidates: Vec<PathMatchCandidate>,
-    worktree_id: usize,
-    query: &str,
-    smart_case: bool,
-    max_results: usize,
-) -> Vec<PathMatch> {
-    let lowercase_query = query.to_lowercase().chars().collect::<Vec<_>>();
-    let query = query.chars().collect::<Vec<_>>();
-    let query_char_bag = CharBag::from(&lowercase_query[..]);
-
-    let mut matcher = Matcher::new(
-        &query,
-        &lowercase_query,
-        query_char_bag,
-        smart_case,
-        max_results,
-    );
-
-    let mut results = Vec::new();
-    matcher.match_candidates(
-        &[],
-        &[],
-        candidates.into_iter(),
-        &mut results,
-        &AtomicBool::new(false),
-        |candidate, score| PathMatch {
-            score,
-            worktree_id,
-            positions: Vec::new(),
-            path: Arc::from(candidate.path),
-            path_prefix: Arc::from(""),
-            distance_to_relative_ancestor: usize::MAX,
-        },
-    );
-    results
-}
-
-pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
-    candidate_sets: &'a [Set],
-    query: &str,
-    relative_to: Option<Arc<Path>>,
-    smart_case: bool,
-    max_results: usize,
-    cancel_flag: &AtomicBool,
-    executor: BackgroundExecutor,
-) -> Vec<PathMatch> {
-    let path_count: usize = candidate_sets.iter().map(|s| s.len()).sum();
-    if path_count == 0 {
-        return Vec::new();
-    }
-
-    let lowercase_query = query.to_lowercase().chars().collect::<Vec<_>>();
-    let query = query.chars().collect::<Vec<_>>();
-
-    let lowercase_query = &lowercase_query;
-    let query = &query;
-    let query_char_bag = CharBag::from(&lowercase_query[..]);
-
-    let num_cpus = executor.num_cpus().min(path_count);
-    let segment_size = (path_count + num_cpus - 1) / num_cpus;
-    let mut segment_results = (0..num_cpus)
-        .map(|_| Vec::with_capacity(max_results))
-        .collect::<Vec<_>>();
-
-    executor
-        .scoped(|scope| {
-            for (segment_idx, results) in segment_results.iter_mut().enumerate() {
-                let relative_to = relative_to.clone();
-                scope.spawn(async move {
-                    let segment_start = segment_idx * segment_size;
-                    let segment_end = segment_start + segment_size;
-                    let mut matcher = Matcher::new(
-                        query,
-                        lowercase_query,
-                        query_char_bag,
-                        smart_case,
-                        max_results,
-                    );
-
-                    let mut tree_start = 0;
-                    for candidate_set in candidate_sets {
-                        let tree_end = tree_start + candidate_set.len();
-
-                        if tree_start < segment_end && segment_start < tree_end {
-                            let start = cmp::max(tree_start, segment_start) - tree_start;
-                            let end = cmp::min(tree_end, segment_end) - tree_start;
-                            let candidates = candidate_set.candidates(start).take(end - start);
-
-                            let worktree_id = candidate_set.id();
-                            let prefix = candidate_set.prefix().chars().collect::<Vec<_>>();
-                            let lowercase_prefix = prefix
-                                .iter()
-                                .map(|c| c.to_ascii_lowercase())
-                                .collect::<Vec<_>>();
-                            matcher.match_candidates(
-                                &prefix,
-                                &lowercase_prefix,
-                                candidates,
-                                results,
-                                cancel_flag,
-                                |candidate, score| PathMatch {
-                                    score,
-                                    worktree_id,
-                                    positions: Vec::new(),
-                                    path: Arc::from(candidate.path),
-                                    path_prefix: candidate_set.prefix(),
-                                    distance_to_relative_ancestor: relative_to.as_ref().map_or(
-                                        usize::MAX,
-                                        |relative_to| {
-                                            distance_between_paths(
-                                                candidate.path.as_ref(),
-                                                relative_to.as_ref(),
-                                            )
-                                        },
-                                    ),
-                                },
-                            );
-                        }
-                        if tree_end >= segment_end {
-                            break;
-                        }
-                        tree_start = tree_end;
-                    }
-                })
-            }
-        })
-        .await;
-
-    let mut results = Vec::new();
-    for segment_result in segment_results {
-        if results.is_empty() {
-            results = segment_result;
-        } else {
-            util::extend_sorted(&mut results, segment_result, max_results, |a, b| b.cmp(a));
-        }
-    }
-    results
-}
-
-/// Compute the distance from a given path to some other path
-/// If there is no shared path, returns usize::MAX
-fn distance_between_paths(path: &Path, relative_to: &Path) -> usize {
-    let mut path_components = path.components();
-    let mut relative_components = relative_to.components();
-
-    while path_components
-        .next()
-        .zip(relative_components.next())
-        .map(|(path_component, relative_component)| path_component == relative_component)
-        .unwrap_or_default()
-    {}
-    path_components.count() + relative_components.count() + 1
-}
-
-#[cfg(test)]
-mod tests {
-    use std::path::Path;
-
-    use super::distance_between_paths;
-
-    #[test]
-    fn test_distance_between_paths_empty() {
-        distance_between_paths(Path::new(""), Path::new(""));
-    }
-}

crates/fuzzy2/src/strings.rs 🔗

@@ -1,187 +0,0 @@
-use crate::{
-    matcher::{Match, MatchCandidate, Matcher},
-    CharBag,
-};
-use gpui::BackgroundExecutor;
-use std::{
-    borrow::Cow,
-    cmp::{self, Ordering},
-    iter,
-    ops::Range,
-    sync::atomic::AtomicBool,
-};
-
-#[derive(Clone, Debug)]
-pub struct StringMatchCandidate {
-    pub id: usize,
-    pub string: String,
-    pub char_bag: CharBag,
-}
-
-impl Match for StringMatch {
-    fn score(&self) -> f64 {
-        self.score
-    }
-
-    fn set_positions(&mut self, positions: Vec<usize>) {
-        self.positions = positions;
-    }
-}
-
-impl StringMatchCandidate {
-    pub fn new(id: usize, string: String) -> Self {
-        Self {
-            id,
-            char_bag: CharBag::from(string.as_str()),
-            string,
-        }
-    }
-}
-
-impl<'a> MatchCandidate for &'a StringMatchCandidate {
-    fn has_chars(&self, bag: CharBag) -> bool {
-        self.char_bag.is_superset(bag)
-    }
-
-    fn to_string(&self) -> Cow<'a, str> {
-        self.string.as_str().into()
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct StringMatch {
-    pub candidate_id: usize,
-    pub score: f64,
-    pub positions: Vec<usize>,
-    pub string: String,
-}
-
-impl StringMatch {
-    pub fn ranges<'a>(&'a self) -> impl 'a + Iterator<Item = Range<usize>> {
-        let mut positions = self.positions.iter().peekable();
-        iter::from_fn(move || {
-            while let Some(start) = positions.next().copied() {
-                let mut end = start + self.char_len_at_index(start);
-                while let Some(next_start) = positions.peek() {
-                    if end == **next_start {
-                        end += self.char_len_at_index(end);
-                        positions.next();
-                    } else {
-                        break;
-                    }
-                }
-
-                return Some(start..end);
-            }
-            None
-        })
-    }
-
-    fn char_len_at_index(&self, ix: usize) -> usize {
-        self.string[ix..].chars().next().unwrap().len_utf8()
-    }
-}
-
-impl PartialEq for StringMatch {
-    fn eq(&self, other: &Self) -> bool {
-        self.cmp(other).is_eq()
-    }
-}
-
-impl Eq for StringMatch {}
-
-impl PartialOrd for StringMatch {
-    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
-        Some(self.cmp(other))
-    }
-}
-
-impl Ord for StringMatch {
-    fn cmp(&self, other: &Self) -> Ordering {
-        self.score
-            .partial_cmp(&other.score)
-            .unwrap_or(Ordering::Equal)
-            .then_with(|| self.candidate_id.cmp(&other.candidate_id))
-    }
-}
-
-pub async fn match_strings(
-    candidates: &[StringMatchCandidate],
-    query: &str,
-    smart_case: bool,
-    max_results: usize,
-    cancel_flag: &AtomicBool,
-    executor: BackgroundExecutor,
-) -> Vec<StringMatch> {
-    if candidates.is_empty() || max_results == 0 {
-        return Default::default();
-    }
-
-    if query.is_empty() {
-        return candidates
-            .iter()
-            .map(|candidate| StringMatch {
-                candidate_id: candidate.id,
-                score: 0.,
-                positions: Default::default(),
-                string: candidate.string.clone(),
-            })
-            .collect();
-    }
-
-    let lowercase_query = query.to_lowercase().chars().collect::<Vec<_>>();
-    let query = query.chars().collect::<Vec<_>>();
-
-    let lowercase_query = &lowercase_query;
-    let query = &query;
-    let query_char_bag = CharBag::from(&lowercase_query[..]);
-
-    let num_cpus = executor.num_cpus().min(candidates.len());
-    let segment_size = (candidates.len() + num_cpus - 1) / num_cpus;
-    let mut segment_results = (0..num_cpus)
-        .map(|_| Vec::with_capacity(max_results.min(candidates.len())))
-        .collect::<Vec<_>>();
-
-    executor
-        .scoped(|scope| {
-            for (segment_idx, results) in segment_results.iter_mut().enumerate() {
-                let cancel_flag = &cancel_flag;
-                scope.spawn(async move {
-                    let segment_start = cmp::min(segment_idx * segment_size, candidates.len());
-                    let segment_end = cmp::min(segment_start + segment_size, candidates.len());
-                    let mut matcher = Matcher::new(
-                        query,
-                        lowercase_query,
-                        query_char_bag,
-                        smart_case,
-                        max_results,
-                    );
-
-                    matcher.match_candidates(
-                        &[],
-                        &[],
-                        candidates[segment_start..segment_end].iter(),
-                        results,
-                        cancel_flag,
-                        |candidate, score| StringMatch {
-                            candidate_id: candidate.id,
-                            score,
-                            positions: Vec::new(),
-                            string: candidate.string.to_string(),
-                        },
-                    );
-                });
-            }
-        })
-        .await;
-
-    let mut results = Vec::new();
-    for segment_result in segment_results {
-        if results.is_empty() {
-            results = segment_result;
-        } else {
-            util::extend_sorted(&mut results, segment_result, max_results, |a, b| b.cmp(a));
-        }
-    }
-    results
-}

crates/git3/Cargo.toml 🔗

@@ -1,30 +0,0 @@
-[package]
-# git2 was already taken.
-name = "git3"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/git.rs"
-
-[dependencies]
-anyhow.workspace = true
-clock = { path = "../clock" }
-lazy_static.workspace = true
-sum_tree = { path = "../sum_tree" }
-text = { package = "text2", path = "../text2" }
-collections = { path = "../collections" }
-util = { path = "../util" }
-log.workspace = true
-smol.workspace = true
-parking_lot.workspace = true
-async-trait.workspace = true
-futures.workspace = true
-git2.workspace = true
-
-[dev-dependencies]
-unindent.workspace = true
-
-[features]
-test-support = []

crates/git3/src/diff.rs 🔗

@@ -1,412 +0,0 @@
-use std::{iter, ops::Range};
-use sum_tree::SumTree;
-use text::{Anchor, BufferSnapshot, OffsetRangeExt, Point};
-
-pub use git2 as libgit;
-use libgit::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch};
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum DiffHunkStatus {
-    Added,
-    Modified,
-    Removed,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct DiffHunk<T> {
-    pub buffer_range: Range<T>,
-    pub diff_base_byte_range: Range<usize>,
-}
-
-impl DiffHunk<u32> {
-    pub fn status(&self) -> DiffHunkStatus {
-        if self.diff_base_byte_range.is_empty() {
-            DiffHunkStatus::Added
-        } else if self.buffer_range.is_empty() {
-            DiffHunkStatus::Removed
-        } else {
-            DiffHunkStatus::Modified
-        }
-    }
-}
-
-impl sum_tree::Item for DiffHunk<Anchor> {
-    type Summary = DiffHunkSummary;
-
-    fn summary(&self) -> Self::Summary {
-        DiffHunkSummary {
-            buffer_range: self.buffer_range.clone(),
-        }
-    }
-}
-
-#[derive(Debug, Default, Clone)]
-pub struct DiffHunkSummary {
-    buffer_range: Range<Anchor>,
-}
-
-impl sum_tree::Summary for DiffHunkSummary {
-    type Context = text::BufferSnapshot;
-
-    fn add_summary(&mut self, other: &Self, buffer: &Self::Context) {
-        self.buffer_range.start = self
-            .buffer_range
-            .start
-            .min(&other.buffer_range.start, buffer);
-        self.buffer_range.end = self.buffer_range.end.max(&other.buffer_range.end, buffer);
-    }
-}
-
-#[derive(Clone)]
-pub struct BufferDiff {
-    last_buffer_version: Option<clock::Global>,
-    tree: SumTree<DiffHunk<Anchor>>,
-}
-
-impl BufferDiff {
-    pub fn new() -> BufferDiff {
-        BufferDiff {
-            last_buffer_version: None,
-            tree: SumTree::new(),
-        }
-    }
-
-    pub fn is_empty(&self) -> bool {
-        self.tree.is_empty()
-    }
-
-    pub fn hunks_in_row_range<'a>(
-        &'a self,
-        range: Range<u32>,
-        buffer: &'a BufferSnapshot,
-    ) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
-        let start = buffer.anchor_before(Point::new(range.start, 0));
-        let end = buffer.anchor_after(Point::new(range.end, 0));
-
-        self.hunks_intersecting_range(start..end, buffer)
-    }
-
-    pub fn hunks_intersecting_range<'a>(
-        &'a self,
-        range: Range<Anchor>,
-        buffer: &'a BufferSnapshot,
-    ) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
-        let mut cursor = self.tree.filter::<_, DiffHunkSummary>(move |summary| {
-            let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt();
-            let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt();
-            !before_start && !after_end
-        });
-
-        let anchor_iter = std::iter::from_fn(move || {
-            cursor.next(buffer);
-            cursor.item()
-        })
-        .flat_map(move |hunk| {
-            [
-                (&hunk.buffer_range.start, hunk.diff_base_byte_range.start),
-                (&hunk.buffer_range.end, hunk.diff_base_byte_range.end),
-            ]
-            .into_iter()
-        });
-
-        let mut summaries = buffer.summaries_for_anchors_with_payload::<Point, _, _>(anchor_iter);
-        iter::from_fn(move || {
-            let (start_point, start_base) = summaries.next()?;
-            let (end_point, end_base) = summaries.next()?;
-
-            let end_row = if end_point.column > 0 {
-                end_point.row + 1
-            } else {
-                end_point.row
-            };
-
-            Some(DiffHunk {
-                buffer_range: start_point.row..end_row,
-                diff_base_byte_range: start_base..end_base,
-            })
-        })
-    }
-
-    pub fn hunks_intersecting_range_rev<'a>(
-        &'a self,
-        range: Range<Anchor>,
-        buffer: &'a BufferSnapshot,
-    ) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
-        let mut cursor = self.tree.filter::<_, DiffHunkSummary>(move |summary| {
-            let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt();
-            let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt();
-            !before_start && !after_end
-        });
-
-        std::iter::from_fn(move || {
-            cursor.prev(buffer);
-
-            let hunk = cursor.item()?;
-            let range = hunk.buffer_range.to_point(buffer);
-            let end_row = if range.end.column > 0 {
-                range.end.row + 1
-            } else {
-                range.end.row
-            };
-
-            Some(DiffHunk {
-                buffer_range: range.start.row..end_row,
-                diff_base_byte_range: hunk.diff_base_byte_range.clone(),
-            })
-        })
-    }
-
-    pub fn clear(&mut self, buffer: &text::BufferSnapshot) {
-        self.last_buffer_version = Some(buffer.version().clone());
-        self.tree = SumTree::new();
-    }
-
-    pub async fn update(&mut self, diff_base: &str, buffer: &text::BufferSnapshot) {
-        let mut tree = SumTree::new();
-
-        let buffer_text = buffer.as_rope().to_string();
-        let patch = Self::diff(&diff_base, &buffer_text);
-
-        if let Some(patch) = patch {
-            let mut divergence = 0;
-            for hunk_index in 0..patch.num_hunks() {
-                let hunk = Self::process_patch_hunk(&patch, hunk_index, buffer, &mut divergence);
-                tree.push(hunk, buffer);
-            }
-        }
-
-        self.tree = tree;
-        self.last_buffer_version = Some(buffer.version().clone());
-    }
-
-    #[cfg(test)]
-    fn hunks<'a>(&'a self, text: &'a BufferSnapshot) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
-        let start = text.anchor_before(Point::new(0, 0));
-        let end = text.anchor_after(Point::new(u32::MAX, u32::MAX));
-        self.hunks_intersecting_range(start..end, text)
-    }
-
-    fn diff<'a>(head: &'a str, current: &'a str) -> Option<GitPatch<'a>> {
-        let mut options = GitOptions::default();
-        options.context_lines(0);
-
-        let patch = GitPatch::from_buffers(
-            head.as_bytes(),
-            None,
-            current.as_bytes(),
-            None,
-            Some(&mut options),
-        );
-
-        match patch {
-            Ok(patch) => Some(patch),
-
-            Err(err) => {
-                log::error!("`GitPatch::from_buffers` failed: {}", err);
-                None
-            }
-        }
-    }
-
-    fn process_patch_hunk<'a>(
-        patch: &GitPatch<'a>,
-        hunk_index: usize,
-        buffer: &text::BufferSnapshot,
-        buffer_row_divergence: &mut i64,
-    ) -> DiffHunk<Anchor> {
-        let line_item_count = patch.num_lines_in_hunk(hunk_index).unwrap();
-        assert!(line_item_count > 0);
-
-        let mut first_deletion_buffer_row: Option<u32> = None;
-        let mut buffer_row_range: Option<Range<u32>> = None;
-        let mut diff_base_byte_range: Option<Range<usize>> = None;
-
-        for line_index in 0..line_item_count {
-            let line = patch.line_in_hunk(hunk_index, line_index).unwrap();
-            let kind = line.origin_value();
-            let content_offset = line.content_offset() as isize;
-            let content_len = line.content().len() as isize;
-
-            if kind == GitDiffLineType::Addition {
-                *buffer_row_divergence += 1;
-                let row = line.new_lineno().unwrap().saturating_sub(1);
-
-                match &mut buffer_row_range {
-                    Some(buffer_row_range) => buffer_row_range.end = row + 1,
-                    None => buffer_row_range = Some(row..row + 1),
-                }
-            }
-
-            if kind == GitDiffLineType::Deletion {
-                let end = content_offset + content_len;
-
-                match &mut diff_base_byte_range {
-                    Some(head_byte_range) => head_byte_range.end = end as usize,
-                    None => diff_base_byte_range = Some(content_offset as usize..end as usize),
-                }
-
-                if first_deletion_buffer_row.is_none() {
-                    let old_row = line.old_lineno().unwrap().saturating_sub(1);
-                    let row = old_row as i64 + *buffer_row_divergence;
-                    first_deletion_buffer_row = Some(row as u32);
-                }
-
-                *buffer_row_divergence -= 1;
-            }
-        }
-
-        //unwrap_or deletion without addition
-        let buffer_row_range = buffer_row_range.unwrap_or_else(|| {
-            //we cannot have an addition-less hunk without deletion(s) or else there would be no hunk
-            let row = first_deletion_buffer_row.unwrap();
-            row..row
-        });
-
-        //unwrap_or addition without deletion
-        let diff_base_byte_range = diff_base_byte_range.unwrap_or(0..0);
-
-        let start = Point::new(buffer_row_range.start, 0);
-        let end = Point::new(buffer_row_range.end, 0);
-        let buffer_range = buffer.anchor_before(start)..buffer.anchor_before(end);
-        DiffHunk {
-            buffer_range,
-            diff_base_byte_range,
-        }
-    }
-}
-
-/// Range (crossing new lines), old, new
-#[cfg(any(test, feature = "test-support"))]
-#[track_caller]
-pub fn assert_hunks<Iter>(
-    diff_hunks: Iter,
-    buffer: &BufferSnapshot,
-    diff_base: &str,
-    expected_hunks: &[(Range<u32>, &str, &str)],
-) where
-    Iter: Iterator<Item = DiffHunk<u32>>,
-{
-    let actual_hunks = diff_hunks
-        .map(|hunk| {
-            (
-                hunk.buffer_range.clone(),
-                &diff_base[hunk.diff_base_byte_range],
-                buffer
-                    .text_for_range(
-                        Point::new(hunk.buffer_range.start, 0)
-                            ..Point::new(hunk.buffer_range.end, 0),
-                    )
-                    .collect::<String>(),
-            )
-        })
-        .collect::<Vec<_>>();
-
-    let expected_hunks: Vec<_> = expected_hunks
-        .iter()
-        .map(|(r, s, h)| (r.clone(), *s, h.to_string()))
-        .collect();
-
-    assert_eq!(actual_hunks, expected_hunks);
-}
-
-#[cfg(test)]
-mod tests {
-    use std::assert_eq;
-
-    use super::*;
-    use text::Buffer;
-    use unindent::Unindent as _;
-
-    #[test]
-    fn test_buffer_diff_simple() {
-        let diff_base = "
-            one
-            two
-            three
-        "
-        .unindent();
-
-        let buffer_text = "
-            one
-            HELLO
-            three
-        "
-        .unindent();
-
-        let mut buffer = Buffer::new(0, 0, buffer_text);
-        let mut diff = BufferDiff::new();
-        smol::block_on(diff.update(&diff_base, &buffer));
-        assert_hunks(
-            diff.hunks(&buffer),
-            &buffer,
-            &diff_base,
-            &[(1..2, "two\n", "HELLO\n")],
-        );
-
-        buffer.edit([(0..0, "point five\n")]);
-        smol::block_on(diff.update(&diff_base, &buffer));
-        assert_hunks(
-            diff.hunks(&buffer),
-            &buffer,
-            &diff_base,
-            &[(0..1, "", "point five\n"), (2..3, "two\n", "HELLO\n")],
-        );
-
-        diff.clear(&buffer);
-        assert_hunks(diff.hunks(&buffer), &buffer, &diff_base, &[]);
-    }
-
-    #[test]
-    fn test_buffer_diff_range() {
-        let diff_base = "
-            one
-            two
-            three
-            four
-            five
-            six
-            seven
-            eight
-            nine
-            ten
-        "
-        .unindent();
-
-        let buffer_text = "
-            A
-            one
-            B
-            two
-            C
-            three
-            HELLO
-            four
-            five
-            SIXTEEN
-            seven
-            eight
-            WORLD
-            nine
-
-            ten
-
-        "
-        .unindent();
-
-        let buffer = Buffer::new(0, 0, buffer_text);
-        let mut diff = BufferDiff::new();
-        smol::block_on(diff.update(&diff_base, &buffer));
-        assert_eq!(diff.hunks(&buffer).count(), 8);
-
-        assert_hunks(
-            diff.hunks_in_row_range(7..12, &buffer),
-            &buffer,
-            &diff_base,
-            &[
-                (6..7, "", "HELLO\n"),
-                (9..10, "six\n", "SIXTEEN\n"),
-                (12..13, "", "WORLD\n"),
-            ],
-        );
-    }
-}

crates/git3/src/git.rs 🔗

@@ -1,11 +0,0 @@
-use std::ffi::OsStr;
-
-pub use git2 as libgit;
-pub use lazy_static::lazy_static;
-
-pub mod diff;
-
-lazy_static! {
-    pub static ref DOT_GIT: &'static OsStr = OsStr::new(".git");
-    pub static ref GITIGNORE: &'static OsStr = OsStr::new(".gitignore");
-}

crates/go_to_line/Cargo.toml 🔗

@@ -12,11 +12,13 @@ doctest = false
 editor = { path = "../editor" }
 gpui = { path = "../gpui" }
 menu = { path = "../menu" }
+serde.workspace = true
 settings = { path = "../settings" }
 text = { path = "../text" }
 workspace = { path = "../workspace" }
 postage.workspace = true
 theme = { path = "../theme" }
+ui = { path = "../ui" }
 util = { path = "../util" }
 
 [dev-dependencies]

crates/go_to_line/src/go_to_line.rs 🔗

@@ -1,117 +1,117 @@
-use std::sync::Arc;
-
 use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor};
 use gpui::{
-    actions, elements::*, geometry::vector::Vector2F, AnyViewHandle, AppContext, Axis, Entity,
-    View, ViewContext, ViewHandle,
+    actions, div, prelude::*, AnyWindowHandle, AppContext, DismissEvent, EventEmitter, FocusHandle,
+    FocusableView, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext,
 };
-use menu::{Cancel, Confirm};
 use text::{Bias, Point};
+use theme::ActiveTheme;
+use ui::{h_stack, prelude::*, v_stack, Label};
 use util::paths::FILE_ROW_COLUMN_DELIMITER;
-use workspace::{Modal, Workspace};
+use workspace::ModalView;
 
 actions!(go_to_line, [Toggle]);
 
 pub fn init(cx: &mut AppContext) {
-    cx.add_action(GoToLine::toggle);
-    cx.add_action(GoToLine::confirm);
-    cx.add_action(GoToLine::cancel);
+    cx.observe_new_views(GoToLine::register).detach();
 }
 
 pub struct GoToLine {
-    line_editor: ViewHandle<Editor>,
-    active_editor: ViewHandle<Editor>,
-    prev_scroll_position: Option<Vector2F>,
-    cursor_point: Point,
-    max_point: Point,
-    has_focus: bool,
+    line_editor: View<Editor>,
+    active_editor: View<Editor>,
+    current_text: SharedString,
+    prev_scroll_position: Option<gpui::Point<f32>>,
+    _subscriptions: Vec<Subscription>,
 }
 
-pub enum Event {
-    Dismissed,
+impl ModalView for GoToLine {}
+
+impl FocusableView for GoToLine {
+    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
+        self.line_editor.focus_handle(cx)
+    }
 }
+impl EventEmitter<DismissEvent> for GoToLine {}
 
 impl GoToLine {
-    pub fn new(active_editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) -> Self {
-        let line_editor = cx.add_view(|cx| {
-            Editor::single_line(
-                Some(Arc::new(|theme| theme.picker.input_editor.clone())),
-                cx,
-            )
-        });
-        cx.subscribe(&line_editor, Self::on_line_editor_event)
-            .detach();
-
-        let (scroll_position, cursor_point, max_point) = active_editor.update(cx, |editor, cx| {
-            let scroll_position = editor.scroll_position(cx);
-            let buffer = editor.buffer().read(cx).snapshot(cx);
-            (
-                Some(scroll_position),
-                editor.selections.newest(cx).head(),
-                buffer.max_point(),
-            )
+    fn register(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
+        let handle = cx.view().downgrade();
+        editor.register_action(move |_: &Toggle, cx| {
+            let Some(editor) = handle.upgrade() else {
+                return;
+            };
+            let Some(workspace) = editor.read(cx).workspace() else {
+                return;
+            };
+            workspace.update(cx, |workspace, cx| {
+                workspace.toggle_modal(cx, move |cx| GoToLine::new(editor, cx));
+            })
         });
+    }
+
+    pub fn new(active_editor: View<Editor>, cx: &mut ViewContext<Self>) -> Self {
+        let line_editor = cx.new_view(|cx| Editor::single_line(cx));
+        let line_editor_change = cx.subscribe(&line_editor, Self::on_line_editor_event);
+
+        let editor = active_editor.read(cx);
+        let cursor = editor.selections.last::<Point>(cx).head();
+        let last_line = editor.buffer().read(cx).snapshot(cx).max_point().row;
+        let scroll_position = active_editor.update(cx, |editor, cx| editor.scroll_position(cx));
+
+        let current_text = format!(
+            "line {} of {} (column {})",
+            cursor.row + 1,
+            last_line + 1,
+            cursor.column + 1,
+        );
 
         Self {
             line_editor,
             active_editor,
-            prev_scroll_position: scroll_position,
-            cursor_point,
-            max_point,
-            has_focus: false,
+            current_text: current_text.into(),
+            prev_scroll_position: Some(scroll_position),
+            _subscriptions: vec![line_editor_change, cx.on_release(Self::release)],
         }
     }
 
-    fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
-        if let Some(editor) = workspace
-            .active_item(cx)
-            .and_then(|active_item| active_item.downcast::<Editor>())
-        {
-            workspace.toggle_modal(cx, |_, cx| cx.add_view(|cx| GoToLine::new(editor, cx)));
-        }
+    fn release(&mut self, window: AnyWindowHandle, cx: &mut AppContext) {
+        window
+            .update(cx, |_, cx| {
+                let scroll_position = self.prev_scroll_position.take();
+                self.active_editor.update(cx, |editor, cx| {
+                    editor.highlight_rows(None);
+                    if let Some(scroll_position) = scroll_position {
+                        editor.set_scroll_position(scroll_position, cx);
+                    }
+                    cx.notify();
+                })
+            })
+            .ok();
     }
 
-    fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
-        cx.emit(Event::Dismissed);
+    fn on_line_editor_event(
+        &mut self,
+        _: View<Editor>,
+        event: &editor::EditorEvent,
+        cx: &mut ViewContext<Self>,
+    ) {
+        match event {
+            editor::EditorEvent::Blurred => cx.emit(DismissEvent),
+            editor::EditorEvent::BufferEdited { .. } => self.highlight_current_line(cx),
+            _ => {}
+        }
     }
 
-    fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
-        self.prev_scroll_position.take();
+    fn highlight_current_line(&mut self, cx: &mut ViewContext<Self>) {
         if let Some(point) = self.point_from_query(cx) {
             self.active_editor.update(cx, |active_editor, cx| {
                 let snapshot = active_editor.snapshot(cx).display_snapshot;
                 let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
-                active_editor.change_selections(Some(Autoscroll::center()), cx, |s| {
-                    s.select_ranges([point..point])
-                });
+                let display_point = point.to_display_point(&snapshot);
+                let row = display_point.row();
+                active_editor.highlight_rows(Some(row..row + 1));
+                active_editor.request_autoscroll(Autoscroll::center(), cx);
             });
-        }
-
-        cx.emit(Event::Dismissed);
-    }
-
-    fn on_line_editor_event(
-        &mut self,
-        _: ViewHandle<Editor>,
-        event: &editor::Event,
-        cx: &mut ViewContext<Self>,
-    ) {
-        match event {
-            editor::Event::Blurred => cx.emit(Event::Dismissed),
-            editor::Event::BufferEdited { .. } => {
-                if let Some(point) = self.point_from_query(cx) {
-                    self.active_editor.update(cx, |active_editor, cx| {
-                        let snapshot = active_editor.snapshot(cx).display_snapshot;
-                        let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
-                        let display_point = point.to_display_point(&snapshot);
-                        let row = display_point.row();
-                        active_editor.highlight_rows(Some(row..row + 1));
-                        active_editor.request_autoscroll(Autoscroll::center(), cx);
-                    });
-                    cx.notify();
-                }
-            }
-            _ => {}
+            cx.notify();
         }
     }
 
@@ -128,73 +128,61 @@ impl GoToLine {
             column.unwrap_or(0).saturating_sub(1),
         ))
     }
-}
-
-impl Entity for GoToLine {
-    type Event = Event;
-
-    fn release(&mut self, cx: &mut AppContext) {
-        let scroll_position = self.prev_scroll_position.take();
-        self.active_editor.window().update(cx, |cx| {
-            self.active_editor.update(cx, |editor, cx| {
-                editor.highlight_rows(None);
-                if let Some(scroll_position) = scroll_position {
-                    editor.set_scroll_position(scroll_position, cx);
-                }
-            })
-        });
-    }
-}
-
-impl View for GoToLine {
-    fn ui_name() -> &'static str {
-        "GoToLine"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = &theme::current(cx).picker;
 
-        let label = format!(
-            "{}{FILE_ROW_COLUMN_DELIMITER}{} of {} lines",
-            self.cursor_point.row + 1,
-            self.cursor_point.column + 1,
-            self.max_point.row + 1
-        );
-
-        Flex::new(Axis::Vertical)
-            .with_child(
-                ChildView::new(&self.line_editor, cx)
-                    .contained()
-                    .with_style(theme.input_editor.container),
-            )
-            .with_child(
-                Label::new(label, theme.no_matches.label.clone())
-                    .contained()
-                    .with_style(theme.no_matches.container),
-            )
-            .contained()
-            .with_style(theme.container)
-            .constrained()
-            .with_max_width(500.0)
-            .into_any_named("go to line")
+    fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
+        cx.emit(DismissEvent);
     }
 
-    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
-        self.has_focus = true;
-        cx.focus(&self.line_editor);
-    }
+    fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
+        if let Some(point) = self.point_from_query(cx) {
+            self.active_editor.update(cx, |editor, cx| {
+                let snapshot = editor.snapshot(cx).display_snapshot;
+                let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
+                editor.change_selections(Some(Autoscroll::center()), cx, |s| {
+                    s.select_ranges([point..point])
+                });
+                editor.focus(cx);
+                cx.notify();
+            });
+            self.prev_scroll_position.take();
+        }
 
-    fn focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext<Self>) {
-        self.has_focus = false;
+        cx.emit(DismissEvent);
     }
 }
 
-impl Modal for GoToLine {
-    fn has_focus(&self) -> bool {
-        self.has_focus
-    }
-
-    fn dismiss_on_event(event: &Self::Event) -> bool {
-        matches!(event, Event::Dismissed)
+impl Render for GoToLine {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        div()
+            .elevation_2(cx)
+            .key_context("GoToLine")
+            .on_action(cx.listener(Self::cancel))
+            .on_action(cx.listener(Self::confirm))
+            .w_96()
+            .child(
+                v_stack()
+                    .px_1()
+                    .pt_0p5()
+                    .gap_px()
+                    .child(
+                        v_stack()
+                            .py_0p5()
+                            .px_1()
+                            .child(div().px_1().py_0p5().child(self.line_editor.clone())),
+                    )
+                    .child(
+                        div()
+                            .h_px()
+                            .w_full()
+                            .bg(cx.theme().colors().element_background),
+                    )
+                    .child(
+                        h_stack()
+                            .justify_between()
+                            .px_2()
+                            .py_1()
+                            .child(Label::new(self.current_text.clone()).color(Color::Muted)),
+                    ),
+            )
     }
 }

crates/go_to_line2/Cargo.toml 🔗

@@ -1,25 +0,0 @@
-[package]
-name = "go_to_line2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/go_to_line.rs"
-doctest = false
-
-[dependencies]
-editor = { package = "editor2", path = "../editor2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-menu = { package = "menu2", path = "../menu2" }
-serde.workspace = true
-settings = { package = "settings2", path = "../settings2" }
-text = { package = "text2", path = "../text2" }
-workspace = { package = "workspace2", path = "../workspace2" }
-postage.workspace = true
-theme = { package = "theme2", path = "../theme2" }
-ui = { package = "ui2", path = "../ui2" }
-util = { path = "../util" }
-
-[dev-dependencies]
-editor = { package = "editor2", path = "../editor2", features = ["test-support"] }

crates/go_to_line2/src/go_to_line.rs 🔗

@@ -1,188 +0,0 @@
-use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor};
-use gpui::{
-    actions, div, prelude::*, AnyWindowHandle, AppContext, DismissEvent, EventEmitter, FocusHandle,
-    FocusableView, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext,
-};
-use text::{Bias, Point};
-use theme::ActiveTheme;
-use ui::{h_stack, prelude::*, v_stack, Label};
-use util::paths::FILE_ROW_COLUMN_DELIMITER;
-use workspace::ModalView;
-
-actions!(go_to_line, [Toggle]);
-
-pub fn init(cx: &mut AppContext) {
-    cx.observe_new_views(GoToLine::register).detach();
-}
-
-pub struct GoToLine {
-    line_editor: View<Editor>,
-    active_editor: View<Editor>,
-    current_text: SharedString,
-    prev_scroll_position: Option<gpui::Point<f32>>,
-    _subscriptions: Vec<Subscription>,
-}
-
-impl ModalView for GoToLine {}
-
-impl FocusableView for GoToLine {
-    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
-        self.line_editor.focus_handle(cx)
-    }
-}
-impl EventEmitter<DismissEvent> for GoToLine {}
-
-impl GoToLine {
-    fn register(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
-        let handle = cx.view().downgrade();
-        editor.register_action(move |_: &Toggle, cx| {
-            let Some(editor) = handle.upgrade() else {
-                return;
-            };
-            let Some(workspace) = editor.read(cx).workspace() else {
-                return;
-            };
-            workspace.update(cx, |workspace, cx| {
-                workspace.toggle_modal(cx, move |cx| GoToLine::new(editor, cx));
-            })
-        });
-    }
-
-    pub fn new(active_editor: View<Editor>, cx: &mut ViewContext<Self>) -> Self {
-        let line_editor = cx.new_view(|cx| Editor::single_line(cx));
-        let line_editor_change = cx.subscribe(&line_editor, Self::on_line_editor_event);
-
-        let editor = active_editor.read(cx);
-        let cursor = editor.selections.last::<Point>(cx).head();
-        let last_line = editor.buffer().read(cx).snapshot(cx).max_point().row;
-        let scroll_position = active_editor.update(cx, |editor, cx| editor.scroll_position(cx));
-
-        let current_text = format!(
-            "line {} of {} (column {})",
-            cursor.row + 1,
-            last_line + 1,
-            cursor.column + 1,
-        );
-
-        Self {
-            line_editor,
-            active_editor,
-            current_text: current_text.into(),
-            prev_scroll_position: Some(scroll_position),
-            _subscriptions: vec![line_editor_change, cx.on_release(Self::release)],
-        }
-    }
-
-    fn release(&mut self, window: AnyWindowHandle, cx: &mut AppContext) {
-        window
-            .update(cx, |_, cx| {
-                let scroll_position = self.prev_scroll_position.take();
-                self.active_editor.update(cx, |editor, cx| {
-                    editor.highlight_rows(None);
-                    if let Some(scroll_position) = scroll_position {
-                        editor.set_scroll_position(scroll_position, cx);
-                    }
-                    cx.notify();
-                })
-            })
-            .ok();
-    }
-
-    fn on_line_editor_event(
-        &mut self,
-        _: View<Editor>,
-        event: &editor::EditorEvent,
-        cx: &mut ViewContext<Self>,
-    ) {
-        match event {
-            editor::EditorEvent::Blurred => cx.emit(DismissEvent),
-            editor::EditorEvent::BufferEdited { .. } => self.highlight_current_line(cx),
-            _ => {}
-        }
-    }
-
-    fn highlight_current_line(&mut self, cx: &mut ViewContext<Self>) {
-        if let Some(point) = self.point_from_query(cx) {
-            self.active_editor.update(cx, |active_editor, cx| {
-                let snapshot = active_editor.snapshot(cx).display_snapshot;
-                let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
-                let display_point = point.to_display_point(&snapshot);
-                let row = display_point.row();
-                active_editor.highlight_rows(Some(row..row + 1));
-                active_editor.request_autoscroll(Autoscroll::center(), cx);
-            });
-            cx.notify();
-        }
-    }
-
-    fn point_from_query(&self, cx: &ViewContext<Self>) -> Option<Point> {
-        let line_editor = self.line_editor.read(cx).text(cx);
-        let mut components = line_editor
-            .splitn(2, FILE_ROW_COLUMN_DELIMITER)
-            .map(str::trim)
-            .fuse();
-        let row = components.next().and_then(|row| row.parse::<u32>().ok())?;
-        let column = components.next().and_then(|col| col.parse::<u32>().ok());
-        Some(Point::new(
-            row.saturating_sub(1),
-            column.unwrap_or(0).saturating_sub(1),
-        ))
-    }
-
-    fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
-        cx.emit(DismissEvent);
-    }
-
-    fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
-        if let Some(point) = self.point_from_query(cx) {
-            self.active_editor.update(cx, |editor, cx| {
-                let snapshot = editor.snapshot(cx).display_snapshot;
-                let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
-                editor.change_selections(Some(Autoscroll::center()), cx, |s| {
-                    s.select_ranges([point..point])
-                });
-                editor.focus(cx);
-                cx.notify();
-            });
-            self.prev_scroll_position.take();
-        }
-
-        cx.emit(DismissEvent);
-    }
-}
-
-impl Render for GoToLine {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        div()
-            .elevation_2(cx)
-            .key_context("GoToLine")
-            .on_action(cx.listener(Self::cancel))
-            .on_action(cx.listener(Self::confirm))
-            .w_96()
-            .child(
-                v_stack()
-                    .px_1()
-                    .pt_0p5()
-                    .gap_px()
-                    .child(
-                        v_stack()
-                            .py_0p5()
-                            .px_1()
-                            .child(div().px_1().py_0p5().child(self.line_editor.clone())),
-                    )
-                    .child(
-                        div()
-                            .h_px()
-                            .w_full()
-                            .bg(cx.theme().colors().element_background),
-                    )
-                    .child(
-                        h_stack()
-                            .justify_between()
-                            .px_2()
-                            .py_1()
-                            .child(Label::new(self.current_text.clone()).color(Color::Muted)),
-                    ),
-            )
-    }
-}

crates/gpui/Cargo.toml 🔗

@@ -1,18 +1,18 @@
 [package]
-authors = ["Nathan Sobo <nathansobo@gmail.com>"]
-edition = "2021"
 name = "gpui"
 version = "0.1.0"
-description = "A GPU-accelerated UI framework"
+edition = "2021"
+authors = ["Nathan Sobo <nathan@zed.dev>"]
+description = "The next version of Zed's GPU-accelerated UI framework"
 publish = false
 
+[features]
+test-support = ["backtrace", "dhat", "env_logger", "collections/test-support", "util/test-support"]
+
 [lib]
 path = "src/gpui.rs"
 doctest = false
 
-[features]
-test-support = ["backtrace", "dhat", "env_logger", "collections/test-support", "util/test-support"]
-
 [dependencies]
 collections = { path = "../collections" }
 gpui_macros = { path = "../gpui_macros" }
@@ -22,6 +22,7 @@ sqlez = { path = "../sqlez" }
 async-task = "4.0.3"
 backtrace = { version = "0.3", optional = true }
 ctor.workspace = true
+linkme = "0.3"
 derive_more.workspace = true
 dhat = { version = "0.3", optional = true }
 env_logger = { version = "0.9", optional = true }
@@ -35,30 +36,27 @@ num_cpus = "1.13"
 ordered-float.workspace = true
 parking = "2.0.0"
 parking_lot.workspace = true
-pathfinder_color = "0.5"
 pathfinder_geometry = "0.5"
 postage.workspace = true
 rand.workspace = true
 refineable.workspace = true
 resvg = "0.14"
-schemars = "0.8"
 seahash = "4.1"
 serde.workspace = true
 serde_derive.workspace = true
 serde_json.workspace = true
 smallvec.workspace = true
 smol.workspace = true
-taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "4fb530bdd71609bb1d3f76c6a8bde1ba82805d5e" }
+taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "1876f72bee5e376023eaa518aa7b8a34c769bd1b" }
 thiserror.workspace = true
 time.workspace = true
 tiny-skia = "0.5"
 usvg = { version = "0.14", features = [] }
-uuid.workspace = true
+uuid = { version = "1.1.2", features = ["v4"] }
 waker-fn = "1.1.0"
-
-[build-dependencies]
-bindgen = "0.65.1"
-cc = "1.0.67"
+slotmap = "1.0.6"
+schemars.workspace = true
+bitflags = "2.4.0"
 
 [dev-dependencies]
 backtrace = "0.3"
@@ -69,6 +67,10 @@ png = "0.16"
 simplelog = "0.9"
 util = { path = "../util", features = ["test-support"] }
 
+[build-dependencies]
+bindgen = "0.65.1"
+cbindgen = "0.26.0"
+
 [target.'cfg(target_os = "macos")'.dependencies]
 media = { path = "../media" }
 anyhow.workspace = true

crates/gpui/build.rs 🔗

@@ -1,13 +1,15 @@
 use std::{
     env,
-    path::PathBuf,
+    path::{Path, PathBuf},
     process::{self, Command},
 };
 
+use cbindgen::Config;
+
 fn main() {
     generate_dispatch_bindings();
-    compile_metal_shaders();
-    generate_shader_bindings();
+    let header_path = generate_shader_bindings();
+    compile_metal_shaders(&header_path);
 }
 
 fn generate_dispatch_bindings() {
@@ -17,7 +19,12 @@ fn generate_dispatch_bindings() {
     let bindings = bindgen::Builder::default()
         .header("src/platform/mac/dispatch.h")
         .allowlist_var("_dispatch_main_q")
+        .allowlist_var("DISPATCH_QUEUE_PRIORITY_DEFAULT")
+        .allowlist_var("DISPATCH_TIME_NOW")
+        .allowlist_function("dispatch_get_global_queue")
         .allowlist_function("dispatch_async_f")
+        .allowlist_function("dispatch_after_f")
+        .allowlist_function("dispatch_time")
         .parse_callbacks(Box::new(bindgen::CargoCallbacks))
         .layout_tests(false)
         .generate()
@@ -29,14 +36,61 @@ fn generate_dispatch_bindings() {
         .expect("couldn't write dispatch bindings");
 }
 
-const SHADER_HEADER_PATH: &str = "./src/platform/mac/shaders/shaders.h";
+fn generate_shader_bindings() -> PathBuf {
+    let output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("scene.h");
+    let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
+    let mut config = Config::default();
+    config.include_guard = Some("SCENE_H".into());
+    config.language = cbindgen::Language::C;
+    config.export.include.extend([
+        "Bounds".into(),
+        "Corners".into(),
+        "Edges".into(),
+        "Size".into(),
+        "Pixels".into(),
+        "PointF".into(),
+        "Hsla".into(),
+        "ContentMask".into(),
+        "Uniforms".into(),
+        "AtlasTile".into(),
+        "PathRasterizationInputIndex".into(),
+        "PathVertex_ScaledPixels".into(),
+        "ShadowInputIndex".into(),
+        "Shadow".into(),
+        "QuadInputIndex".into(),
+        "Underline".into(),
+        "UnderlineInputIndex".into(),
+        "Quad".into(),
+        "SpriteInputIndex".into(),
+        "MonochromeSprite".into(),
+        "PolychromeSprite".into(),
+        "PathSprite".into(),
+        "SurfaceInputIndex".into(),
+        "SurfaceBounds".into(),
+    ]);
+    config.no_includes = true;
+    config.enumeration.prefix_with_name = true;
+    cbindgen::Builder::new()
+        .with_src(crate_dir.join("src/scene.rs"))
+        .with_src(crate_dir.join("src/geometry.rs"))
+        .with_src(crate_dir.join("src/color.rs"))
+        .with_src(crate_dir.join("src/window.rs"))
+        .with_src(crate_dir.join("src/platform.rs"))
+        .with_src(crate_dir.join("src/platform/mac/metal_renderer.rs"))
+        .with_config(config)
+        .generate()
+        .expect("Unable to generate bindings")
+        .write_to_file(&output_path);
+
+    output_path
+}
 
-fn compile_metal_shaders() {
-    let shader_path = "./src/platform/mac/shaders/shaders.metal";
+fn compile_metal_shaders(header_path: &Path) {
+    let shader_path = "./src/platform/mac/shaders.metal";
     let air_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.air");
     let metallib_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.metallib");
 
-    println!("cargo:rerun-if-changed={}", SHADER_HEADER_PATH);
+    println!("cargo:rerun-if-changed={}", header_path.display());
     println!("cargo:rerun-if-changed={}", shader_path);
 
     let output = Command::new("xcrun")
@@ -49,6 +103,8 @@ fn compile_metal_shaders() {
             "-MO",
             "-c",
             shader_path,
+            "-include",
+            &header_path.to_str().unwrap(),
             "-o",
         ])
         .arg(&air_output_path)
@@ -79,18 +135,3 @@ fn compile_metal_shaders() {
         process::exit(1);
     }
 }
-
-fn generate_shader_bindings() {
-    let bindings = bindgen::Builder::default()
-        .header(SHADER_HEADER_PATH)
-        .allowlist_type("GPUI.*")
-        .parse_callbacks(Box::new(bindgen::CargoCallbacks))
-        .layout_tests(false)
-        .generate()
-        .expect("unable to generate bindings");
-
-    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
-    bindings
-        .write_to_file(out_path.join("shaders.rs"))
-        .expect("couldn't write shader bindings");
-}

crates/gpui/examples/components.rs 🔗

@@ -1,237 +0,0 @@
-use button_component::Button;
-
-use gpui::{
-    color::Color,
-    elements::{ContainerStyle, Flex, Label, ParentElement, StatefulComponent},
-    fonts::{self, TextStyle},
-    platform::WindowOptions,
-    AnyElement, App, Element, Entity, View, ViewContext,
-};
-use log::LevelFilter;
-use pathfinder_geometry::vector::vec2f;
-use simplelog::SimpleLogger;
-use theme::Toggleable;
-use toggleable_button::ToggleableButton;
-
-// cargo run -p gpui --example components
-
-fn main() {
-    SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
-
-    App::new(()).unwrap().run(|cx| {
-        cx.platform().activate(true);
-        cx.add_window(WindowOptions::with_bounds(vec2f(300., 200.)), |_| {
-            TestView {
-                count: 0,
-                is_doubling: false,
-            }
-        });
-    });
-}
-
-pub struct TestView {
-    count: usize,
-    is_doubling: bool,
-}
-
-impl TestView {
-    fn increase_count(&mut self) {
-        if self.is_doubling {
-            self.count *= 2;
-        } else {
-            self.count += 1;
-        }
-    }
-}
-
-impl Entity for TestView {
-    type Event = ();
-}
-
-type ButtonStyle = ContainerStyle;
-
-impl View for TestView {
-    fn ui_name() -> &'static str {
-        "TestView"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<'_, '_, Self>) -> AnyElement<Self> {
-        fonts::with_font_cache(cx.font_cache.to_owned(), || {
-            Flex::column()
-                .with_child(Label::new(
-                    format!("Count: {}", self.count),
-                    TextStyle::for_color(Color::red()),
-                ))
-                .with_child(
-                    Button::new(move |_, v: &mut Self, cx| {
-                        v.increase_count();
-                        cx.notify();
-                    })
-                    .with_text(
-                        "Hello from a counting BUTTON",
-                        TextStyle::for_color(Color::blue()),
-                    )
-                    .with_style(ButtonStyle::fill(Color::yellow()))
-                    .element(),
-                )
-                .with_child(
-                    ToggleableButton::new(self.is_doubling, move |_, v: &mut Self, cx| {
-                        v.is_doubling = !v.is_doubling;
-                        cx.notify();
-                    })
-                    .with_text("Double the count?", TextStyle::for_color(Color::black()))
-                    .with_style(Toggleable {
-                        inactive: ButtonStyle::fill(Color::red()),
-                        active: ButtonStyle::fill(Color::green()),
-                    })
-                    .element(),
-                )
-                .expanded()
-                .contained()
-                .with_background_color(Color::white())
-                .into_any()
-        })
-    }
-}
-
-mod theme {
-    pub struct Toggleable<T> {
-        pub inactive: T,
-        pub active: T,
-    }
-
-    impl<T> Toggleable<T> {
-        pub fn style_for(&self, active: bool) -> &T {
-            if active {
-                &self.active
-            } else {
-                &self.inactive
-            }
-        }
-    }
-}
-
-// Component creation:
-mod toggleable_button {
-    use gpui::{
-        elements::{ContainerStyle, LabelStyle, StatefulComponent},
-        scene::MouseClick,
-        EventContext, View,
-    };
-
-    use crate::{button_component::Button, theme::Toggleable};
-
-    pub struct ToggleableButton<V: View> {
-        active: bool,
-        style: Option<Toggleable<ContainerStyle>>,
-        button: Button<V>,
-    }
-
-    impl<V: View> ToggleableButton<V> {
-        pub fn new<F>(active: bool, on_click: F) -> Self
-        where
-            F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
-        {
-            Self {
-                active,
-                button: Button::new(on_click),
-                style: None,
-            }
-        }
-
-        pub fn with_text(self, text: &str, style: impl Into<LabelStyle>) -> ToggleableButton<V> {
-            ToggleableButton {
-                active: self.active,
-                style: self.style,
-                button: self.button.with_text(text, style),
-            }
-        }
-
-        pub fn with_style(self, style: Toggleable<ContainerStyle>) -> ToggleableButton<V> {
-            ToggleableButton {
-                active: self.active,
-                style: Some(style),
-                button: self.button,
-            }
-        }
-    }
-
-    impl<V: View> StatefulComponent<V> for ToggleableButton<V> {
-        fn render(self, v: &mut V, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
-            let button = if let Some(style) = self.style {
-                self.button.with_style(*style.style_for(self.active))
-            } else {
-                self.button
-            };
-            button.render(v, cx)
-        }
-    }
-}
-
-mod button_component {
-
-    use gpui::{
-        elements::{ContainerStyle, Label, LabelStyle, MouseEventHandler, StatefulComponent},
-        platform::MouseButton,
-        scene::MouseClick,
-        AnyElement, Element, EventContext, TypeTag, View, ViewContext,
-    };
-
-    type ClickHandler<V> = Box<dyn Fn(MouseClick, &mut V, &mut EventContext<V>)>;
-
-    pub struct Button<V: View> {
-        click_handler: ClickHandler<V>,
-        tag: TypeTag,
-        contents: Option<AnyElement<V>>,
-        style: Option<ContainerStyle>,
-    }
-
-    impl<V: View> Button<V> {
-        pub fn new<F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static>(handler: F) -> Self {
-            Self {
-                click_handler: Box::new(handler),
-                tag: TypeTag::new::<F>(),
-                style: None,
-                contents: None,
-            }
-        }
-
-        pub fn with_text(mut self, text: &str, style: impl Into<LabelStyle>) -> Self {
-            self.contents = Some(Label::new(text.to_string(), style).into_any());
-            self
-        }
-
-        pub fn _with_contents<E: Element<V>>(mut self, contents: E) -> Self {
-            self.contents = Some(contents.into_any());
-            self
-        }
-
-        pub fn with_style(mut self, style: ContainerStyle) -> Self {
-            self.style = Some(style);
-            self
-        }
-    }
-
-    impl<V: View> StatefulComponent<V> for Button<V> {
-        fn render(self, _: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V> {
-            let click_handler = self.click_handler;
-
-            let result = MouseEventHandler::new_dynamic(self.tag, 0, cx, |_, _| {
-                self.contents
-                    .unwrap_or_else(|| gpui::elements::Empty::new().into_any())
-            })
-            .on_click(MouseButton::Left, move |click, v, cx| {
-                click_handler(click, v, cx);
-            })
-            .contained();
-
-            let result = if let Some(style) = self.style {
-                result.with_style(style)
-            } else {
-                result
-            };
-
-            result.into_any()
-        }
-    }
-}

crates/gpui/examples/corner_radii.rs 🔗

@@ -1,154 +0,0 @@
-use gpui::{
-    color::Color, geometry::rect::RectF, scene::Shadow, AnyElement, App, Element, Entity, Quad,
-    View,
-};
-use log::LevelFilter;
-use pathfinder_geometry::vector::vec2f;
-use simplelog::SimpleLogger;
-
-fn main() {
-    SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
-
-    App::new(()).unwrap().run(|cx| {
-        cx.platform().activate(true);
-        cx.add_window(Default::default(), |_| CornersView);
-    });
-}
-
-struct CornersView;
-
-impl Entity for CornersView {
-    type Event = ();
-}
-
-impl View for CornersView {
-    fn ui_name() -> &'static str {
-        "CornersView"
-    }
-
-    fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> AnyElement<CornersView> {
-        CornersElement.into_any()
-    }
-}
-
-struct CornersElement;
-
-impl<V: View> gpui::Element<V> for CornersElement {
-    type LayoutState = ();
-
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        constraint: gpui::SizeConstraint,
-        _: &mut V,
-        _: &mut gpui::ViewContext<V>,
-    ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
-        (constraint.max, ())
-    }
-
-    fn paint(
-        &mut self,
-        bounds: pathfinder_geometry::rect::RectF,
-        _: pathfinder_geometry::rect::RectF,
-        _: &mut Self::LayoutState,
-        _: &mut V,
-        cx: &mut gpui::ViewContext<V>,
-    ) -> Self::PaintState {
-        cx.scene().push_quad(Quad {
-            bounds,
-            background: Some(Color::white()),
-            ..Default::default()
-        });
-
-        cx.scene().push_layer(None);
-
-        cx.scene().push_quad(Quad {
-            bounds: RectF::new(vec2f(100., 100.), vec2f(100., 100.)),
-            background: Some(Color::red()),
-            border: Default::default(),
-            corner_radii: gpui::scene::CornerRadii {
-                top_left: 20.,
-                ..Default::default()
-            },
-        });
-
-        cx.scene().push_quad(Quad {
-            bounds: RectF::new(vec2f(200., 100.), vec2f(100., 100.)),
-            background: Some(Color::green()),
-            border: Default::default(),
-            corner_radii: gpui::scene::CornerRadii {
-                top_right: 20.,
-                ..Default::default()
-            },
-        });
-
-        cx.scene().push_quad(Quad {
-            bounds: RectF::new(vec2f(100., 200.), vec2f(100., 100.)),
-            background: Some(Color::blue()),
-            border: Default::default(),
-            corner_radii: gpui::scene::CornerRadii {
-                bottom_left: 20.,
-                ..Default::default()
-            },
-        });
-
-        cx.scene().push_quad(Quad {
-            bounds: RectF::new(vec2f(200., 200.), vec2f(100., 100.)),
-            background: Some(Color::yellow()),
-            border: Default::default(),
-            corner_radii: gpui::scene::CornerRadii {
-                bottom_right: 20.,
-                ..Default::default()
-            },
-        });
-
-        cx.scene().push_shadow(Shadow {
-            bounds: RectF::new(vec2f(400., 100.), vec2f(100., 100.)),
-            corner_radii: gpui::scene::CornerRadii {
-                bottom_right: 20.,
-                ..Default::default()
-            },
-            sigma: 20.0,
-            color: Color::black(),
-        });
-
-        cx.scene().push_layer(None);
-        cx.scene().push_quad(Quad {
-            bounds: RectF::new(vec2f(400., 100.), vec2f(100., 100.)),
-            background: Some(Color::red()),
-            border: Default::default(),
-            corner_radii: gpui::scene::CornerRadii {
-                bottom_right: 20.,
-                ..Default::default()
-            },
-        });
-
-        cx.scene().pop_layer();
-        cx.scene().pop_layer();
-    }
-
-    fn rect_for_text_range(
-        &self,
-        _: std::ops::Range<usize>,
-        _: pathfinder_geometry::rect::RectF,
-        _: pathfinder_geometry::rect::RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        _: &V,
-        _: &gpui::ViewContext<V>,
-    ) -> Option<pathfinder_geometry::rect::RectF> {
-        unimplemented!()
-    }
-
-    fn debug(
-        &self,
-        _: pathfinder_geometry::rect::RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        _: &V,
-        _: &gpui::ViewContext<V>,
-    ) -> serde_json::Value {
-        unimplemented!()
-    }
-}

crates/gpui/examples/text.rs 🔗

@@ -1,81 +0,0 @@
-use gpui::{
-    color::Color,
-    elements::Text,
-    fonts::{HighlightStyle, TextStyle},
-    platform::{CursorStyle, MouseButton},
-    AnyElement, CursorRegion, Element, MouseRegion,
-};
-use log::LevelFilter;
-use simplelog::SimpleLogger;
-
-fn main() {
-    SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
-
-    gpui::App::new(()).unwrap().run(|cx| {
-        cx.platform().activate(true);
-        cx.add_window(Default::default(), |_| TextView);
-    });
-}
-
-struct TextView;
-
-impl gpui::Entity for TextView {
-    type Event = ();
-}
-
-impl gpui::View for TextView {
-    fn ui_name() -> &'static str {
-        "View"
-    }
-
-    fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> AnyElement<TextView> {
-        let font_size = 12.;
-        let family = cx
-            .font_cache
-            .load_family(&["Monaco"], &Default::default())
-            .unwrap();
-        let font_id = cx
-            .font_cache
-            .select_font(family, &Default::default())
-            .unwrap();
-        let view_id = cx.view_id();
-
-        let underline = HighlightStyle {
-            underline: Some(gpui::fonts::Underline {
-                thickness: 1.0.into(),
-                ..Default::default()
-            }),
-            ..Default::default()
-        };
-
-        Text::new(
-            "The text:\nHello, beautiful world, hello!",
-            TextStyle {
-                font_id,
-                font_size,
-                color: Color::red(),
-                font_family_name: "".into(),
-                font_family_id: family,
-                underline: Default::default(),
-                font_properties: Default::default(),
-                soft_wrap: false,
-            },
-        )
-        .with_highlights(vec![(17..26, underline), (34..40, underline)])
-        .with_custom_runs(vec![(17..26), (34..40)], move |ix, bounds, cx| {
-            cx.scene().push_cursor_region(CursorRegion {
-                bounds,
-                style: CursorStyle::PointingHand,
-            });
-            cx.scene().push_mouse_region(
-                MouseRegion::new::<Self>(view_id, ix, bounds).on_click::<Self, _>(
-                    MouseButton::Left,
-                    move |_, _, _| {
-                        eprintln!("clicked link {ix}");
-                    },
-                ),
-            );
-        })
-        .into_any()
-    }
-}

crates/gpui/src/app.rs 🔗

@@ -1,624 +1,312 @@
-pub mod action;
-mod callback_collection;
-mod menu;
-pub(crate) mod ref_counts;
+mod async_context;
+mod entity_map;
+mod model_context;
 #[cfg(any(test, feature = "test-support"))]
-pub mod test_app_context;
-pub(crate) mod window;
-mod window_input_handler;
+mod test_context;
+
+pub use async_context::*;
+use derive_more::{Deref, DerefMut};
+pub use entity_map::*;
+pub use model_context::*;
+use refineable::Refineable;
+use smol::future::FutureExt;
+#[cfg(any(test, feature = "test-support"))]
+pub use test_context::*;
+use time::UtcOffset;
 
 use crate::{
-    elements::{AnyElement, AnyRootElement, RootElement},
-    executor::{self, Task},
-    image_cache::ImageCache,
-    json,
-    keymap_matcher::{self, Binding, KeymapContext, KeymapMatcher, Keystroke, MatchResult},
-    platform::{
-        self, FontSystem, KeyDownEvent, KeyUpEvent, ModifiersChangedEvent, MouseButton,
-        PathPromptOptions, Platform, PromptLevel, WindowBounds, WindowOptions,
-    },
-    util::post_inc,
-    window::{Window, WindowContext},
-    AssetCache, AssetSource, ClipboardItem, FontCache, MouseRegionId,
+    current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any,
+    AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context,
+    DispatchPhase, DisplayId, Entity, EventEmitter, ForegroundExecutor, KeyBinding, Keymap,
+    Keystroke, LayoutId, Menu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render,
+    SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement,
+    TextSystem, View, ViewContext, Window, WindowContext, WindowHandle, WindowId,
 };
-pub use action::*;
-use anyhow::{anyhow, Context, Result};
-use callback_collection::CallbackCollection;
-use collections::{hash_map::Entry, BTreeMap, HashMap, HashSet, VecDeque};
-use derive_more::Deref;
-pub use menu::*;
+use anyhow::{anyhow, Result};
+use collections::{FxHashMap, FxHashSet, VecDeque};
+use futures::{channel::oneshot, future::LocalBoxFuture, Future};
 use parking_lot::Mutex;
-use pathfinder_geometry::rect::RectF;
-use platform::Event;
-use postage::oneshot;
-#[cfg(any(test, feature = "test-support"))]
-use ref_counts::LeakDetector;
-use ref_counts::RefCounts;
-use smallvec::SmallVec;
-use smol::prelude::*;
+use slotmap::SlotMap;
 use std::{
-    any::{type_name, Any, TypeId},
-    cell::RefCell,
-    fmt::{self, Debug},
-    hash::{Hash, Hasher},
+    any::{type_name, TypeId},
+    cell::{Ref, RefCell, RefMut},
     marker::PhantomData,
     mem,
-    ops::{Deref, DerefMut, Range},
+    ops::{Deref, DerefMut},
     path::{Path, PathBuf},
-    pin::Pin,
-    rc::{self, Rc},
-    sync::{Arc, Weak},
+    rc::{Rc, Weak},
+    sync::{atomic::Ordering::SeqCst, Arc},
     time::Duration,
 };
-#[cfg(any(test, feature = "test-support"))]
-pub use test_app_context::{ContextHandle, TestAppContext};
 use util::{
     http::{self, HttpClient},
     ResultExt,
 };
-use uuid::Uuid;
-pub use window::MeasureParams;
-use window_input_handler::WindowInputHandler;
 
-pub trait Entity: 'static {
-    type Event;
-
-    fn release(&mut self, _: &mut AppContext) {}
-    fn app_will_quit(
-        &mut self,
-        _: &mut AppContext,
-    ) -> Option<Pin<Box<dyn 'static + Future<Output = ()>>>> {
-        None
-    }
+/// Temporary(?) wrapper around RefCell<AppContext> to help us debug any double borrows.
+/// Strongly consider removing after stabilization.
+pub struct AppCell {
+    app: RefCell<AppContext>,
 }
 
-pub trait View: Entity + Sized {
-    fn render(&mut self, cx: &mut ViewContext<'_, '_, Self>) -> AnyElement<Self>;
-    fn focus_in(&mut self, _: AnyViewHandle, _: &mut ViewContext<Self>) {}
-    fn focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext<Self>) {}
-    fn ui_name() -> &'static str {
-        type_name::<Self>()
-    }
-    fn key_down(&mut self, _: &KeyDownEvent, _: &mut ViewContext<Self>) -> bool {
-        false
-    }
-    fn key_up(&mut self, _: &KeyUpEvent, _: &mut ViewContext<Self>) -> bool {
-        false
-    }
-    fn modifiers_changed(&mut self, _: &ModifiersChangedEvent, _: &mut ViewContext<Self>) -> bool {
-        false
-    }
-
-    fn update_keymap_context(&self, keymap: &mut keymap_matcher::KeymapContext, _: &AppContext) {
-        Self::reset_to_default_keymap_context(keymap);
+impl AppCell {
+    #[track_caller]
+    pub fn borrow(&self) -> AppRef {
+        if option_env!("TRACK_THREAD_BORROWS").is_some() {
+            let thread_id = std::thread::current().id();
+            eprintln!("borrowed {thread_id:?}");
+        }
+        AppRef(self.app.borrow())
     }
 
-    fn reset_to_default_keymap_context(keymap: &mut keymap_matcher::KeymapContext) {
-        keymap.clear();
-        keymap.add_identifier(Self::ui_name());
+    #[track_caller]
+    pub fn borrow_mut(&self) -> AppRefMut {
+        if option_env!("TRACK_THREAD_BORROWS").is_some() {
+            let thread_id = std::thread::current().id();
+            eprintln!("borrowed {thread_id:?}");
+        }
+        AppRefMut(self.app.borrow_mut())
     }
+}
 
-    fn debug_json(&self, _: &AppContext) -> serde_json::Value {
-        serde_json::Value::Null
-    }
+#[derive(Deref, DerefMut)]
+pub struct AppRef<'a>(Ref<'a, AppContext>);
 
-    fn text_for_range(&self, _: Range<usize>, _: &AppContext) -> Option<String> {
-        None
-    }
-    fn selected_text_range(&self, _: &AppContext) -> Option<Range<usize>> {
-        None
-    }
-    fn marked_text_range(&self, _: &AppContext) -> Option<Range<usize>> {
-        None
-    }
-    fn unmark_text(&mut self, _: &mut ViewContext<Self>) {}
-    fn replace_text_in_range(
-        &mut self,
-        _: Option<Range<usize>>,
-        _: &str,
-        _: &mut ViewContext<Self>,
-    ) {
-    }
-    fn replace_and_mark_text_in_range(
-        &mut self,
-        _: Option<Range<usize>>,
-        _: &str,
-        _: Option<Range<usize>>,
-        _: &mut ViewContext<Self>,
-    ) {
+impl<'a> Drop for AppRef<'a> {
+    fn drop(&mut self) {
+        if option_env!("TRACK_THREAD_BORROWS").is_some() {
+            let thread_id = std::thread::current().id();
+            eprintln!("dropped borrow from {thread_id:?}");
+        }
     }
 }
 
-pub trait BorrowAppContext {
-    fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T;
-    fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T;
-}
-
-pub trait BorrowWindowContext {
-    type Result<T>;
+#[derive(Deref, DerefMut)]
+pub struct AppRefMut<'a>(RefMut<'a, AppContext>);
 
-    fn read_window<T, F>(&self, window: AnyWindowHandle, f: F) -> Self::Result<T>
-    where
-        F: FnOnce(&WindowContext) -> T;
-    fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
-    where
-        F: FnOnce(&WindowContext) -> Option<T>;
-    fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Self::Result<T>
-    where
-        F: FnOnce(&mut WindowContext) -> T;
-    fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
-    where
-        F: FnOnce(&mut WindowContext) -> Option<T>;
+impl<'a> Drop for AppRefMut<'a> {
+    fn drop(&mut self) {
+        if option_env!("TRACK_THREAD_BORROWS").is_some() {
+            let thread_id = std::thread::current().id();
+            eprintln!("dropped {thread_id:?}");
+        }
+    }
 }
 
-#[derive(Clone)]
-pub struct App(Rc<RefCell<AppContext>>);
+pub struct App(Rc<AppCell>);
 
+/// Represents an application before it is fully launched. Once your app is
+/// configured, you'll start the app with `App::run`.
 impl App {
-    pub fn new(asset_source: impl AssetSource) -> Result<Self> {
-        let platform = platform::current::platform();
-        let foreground = Rc::new(executor::Foreground::platform(platform.dispatcher())?);
-        let foreground_platform = platform::current::foreground_platform(foreground.clone());
-        let http_client = http::client();
-        let app = Self(Rc::new(RefCell::new(AppContext::new(
-            foreground,
-            Arc::new(executor::Background::new()),
-            platform.clone(),
-            foreground_platform.clone(),
-            Arc::new(FontCache::new(platform.fonts())),
-            http_client,
-            Default::default(),
+    /// Builds an app with the given asset source.
+    pub fn production(asset_source: Arc<dyn AssetSource>) -> Self {
+        Self(AppContext::new(
+            current_platform(),
             asset_source,
-        ))));
-
-        foreground_platform.on_event(Box::new({
-            let cx = app.0.clone();
-            move |event| {
-                if let Event::KeyDown(KeyDownEvent { keystroke, .. }) = &event {
-                    // Allow system menu "cmd-?" shortcut to be overridden
-                    if keystroke.cmd
-                        && !keystroke.shift
-                        && !keystroke.alt
-                        && !keystroke.function
-                        && keystroke.key == "?"
-                    {
-                        if cx
-                            .borrow_mut()
-                            .update_active_window(|cx| cx.dispatch_keystroke(keystroke))
-                            .unwrap_or(false)
-                        {
-                            return true;
-                        }
-                    }
-                }
-                false
-            }
-        }));
-        foreground_platform.on_quit(Box::new({
-            let cx = app.0.clone();
-            move || {
-                cx.borrow_mut().quit();
-            }
-        }));
-        setup_menu_handlers(foreground_platform.as_ref(), &app);
-
-        app.0.borrow_mut().weak_self = Some(Rc::downgrade(&app.0));
-        Ok(app)
-    }
-
-    pub fn background(&self) -> Arc<executor::Background> {
-        self.0.borrow().background().clone()
-    }
-
-    pub fn on_become_active<F>(self, mut callback: F) -> Self
-    where
-        F: 'static + FnMut(&mut AppContext),
-    {
-        let cx = self.0.clone();
-        self.0
-            .borrow_mut()
-            .foreground_platform
-            .on_become_active(Box::new(move || callback(&mut *cx.borrow_mut())));
-        self
-    }
-
-    pub fn on_resign_active<F>(self, mut callback: F) -> Self
-    where
-        F: 'static + FnMut(&mut AppContext),
-    {
-        let cx = self.0.clone();
-        self.0
-            .borrow_mut()
-            .foreground_platform
-            .on_resign_active(Box::new(move || callback(&mut *cx.borrow_mut())));
-        self
-    }
-
-    pub fn on_quit<F>(&mut self, mut callback: F) -> &mut Self
-    where
-        F: 'static + FnMut(&mut AppContext),
-    {
-        let cx = self.0.clone();
-        self.0
-            .borrow_mut()
-            .foreground_platform
-            .on_quit(Box::new(move || callback(&mut *cx.borrow_mut())));
-        self
-    }
-
-    /// Handle the application being re-activated when no windows are open.
-    pub fn on_reopen<F>(&mut self, mut callback: F) -> &mut Self
-    where
-        F: 'static + FnMut(&mut AppContext),
-    {
-        let cx = self.0.clone();
-        self.0
-            .borrow_mut()
-            .foreground_platform
-            .on_reopen(Box::new(move || callback(&mut *cx.borrow_mut())));
-        self
-    }
-
-    pub fn on_event<F>(&mut self, mut callback: F) -> &mut Self
-    where
-        F: 'static + FnMut(Event, &mut AppContext) -> bool,
-    {
-        let cx = self.0.clone();
-        self.0
-            .borrow_mut()
-            .foreground_platform
-            .on_event(Box::new(move |event| {
-                callback(event, &mut *cx.borrow_mut())
-            }));
-        self
-    }
-
-    pub fn on_open_urls<F>(&mut self, mut callback: F) -> &mut Self
-    where
-        F: 'static + FnMut(Vec<String>, &mut AppContext),
-    {
-        let cx = self.0.clone();
-        self.0
-            .borrow_mut()
-            .foreground_platform
-            .on_open_urls(Box::new(move |urls| callback(urls, &mut *cx.borrow_mut())));
-        self
+            http::client(),
+        ))
     }
 
+    /// Start the application. The provided callback will be called once the
+    /// app is fully launched.
     pub fn run<F>(self, on_finish_launching: F)
     where
         F: 'static + FnOnce(&mut AppContext),
     {
-        let platform = self.0.borrow().foreground_platform.clone();
+        let this = self.0.clone();
+        let platform = self.0.borrow().platform.clone();
         platform.run(Box::new(move || {
-            let mut cx = self.0.borrow_mut();
-            let cx = &mut *cx;
-            crate::views::init(cx);
+            let cx = &mut *this.borrow_mut();
             on_finish_launching(cx);
-        }))
-    }
-
-    pub fn platform(&self) -> Arc<dyn Platform> {
-        self.0.borrow().platform.clone()
-    }
-
-    pub fn font_cache(&self) -> Arc<FontCache> {
-        self.0.borrow().font_cache.clone()
-    }
-
-    fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, callback: F) -> T {
-        let mut state = self.0.borrow_mut();
-        let result = state.update(callback);
-        state.pending_notifications.clear();
-        result
-    }
-
-    fn update_window<T, F>(&mut self, window: AnyWindowHandle, callback: F) -> Option<T>
-    where
-        F: FnOnce(&mut WindowContext) -> T,
-    {
-        let mut state = self.0.borrow_mut();
-        let result = state.update_window(window, callback);
-        state.pending_notifications.clear();
-        result
-    }
-}
-
-#[derive(Clone)]
-pub struct AsyncAppContext(Rc<RefCell<AppContext>>);
-
-impl AsyncAppContext {
-    pub fn spawn<F, Fut, T>(&self, f: F) -> Task<T>
-    where
-        F: FnOnce(AsyncAppContext) -> Fut,
-        Fut: 'static + Future<Output = T>,
-        T: 'static,
-    {
-        self.0.borrow().foreground.spawn(f(self.clone()))
-    }
-
-    pub fn read<T, F: FnOnce(&AppContext) -> T>(&self, callback: F) -> T {
-        callback(&*self.0.borrow())
-    }
-
-    pub fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, callback: F) -> T {
-        self.0.borrow_mut().update(callback)
-    }
-
-    pub fn windows(&self) -> Vec<AnyWindowHandle> {
-        self.0.borrow().windows().collect()
+        }));
     }
 
-    pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
+    /// Register a handler to be invoked when the platform instructs the application
+    /// to open one or more URLs.
+    pub fn on_open_urls<F>(&self, mut callback: F) -> &Self
     where
-        T: Entity,
-        F: FnOnce(&mut ModelContext<T>) -> T,
+        F: 'static + FnMut(Vec<String>, &mut AppContext),
     {
-        self.update(|cx| cx.add_model(build_model))
+        let this = Rc::downgrade(&self.0);
+        self.0.borrow().platform.on_open_urls(Box::new(move |urls| {
+            if let Some(app) = this.upgrade() {
+                callback(urls, &mut app.borrow_mut());
+            }
+        }));
+        self
     }
 
-    pub fn add_window<T, F>(
-        &mut self,
-        window_options: WindowOptions,
-        build_root_view: F,
-    ) -> WindowHandle<T>
+    pub fn on_reopen<F>(&self, mut callback: F) -> &Self
     where
-        T: View,
-        F: FnOnce(&mut ViewContext<T>) -> T,
+        F: 'static + FnMut(&mut AppContext),
     {
-        self.update(|cx| cx.add_window(window_options, build_root_view))
-    }
-
-    pub fn platform(&self) -> Arc<dyn Platform> {
-        self.0.borrow().platform().clone()
+        let this = Rc::downgrade(&self.0);
+        self.0.borrow_mut().platform.on_reopen(Box::new(move || {
+            if let Some(app) = this.upgrade() {
+                callback(&mut app.borrow_mut());
+            }
+        }));
+        self
     }
 
-    pub fn foreground(&self) -> Rc<executor::Foreground> {
-        self.0.borrow().foreground.clone()
+    pub fn metadata(&self) -> AppMetadata {
+        self.0.borrow().app_metadata.clone()
     }
 
-    pub fn background(&self) -> Arc<executor::Background> {
-        self.0.borrow().background.clone()
+    pub fn background_executor(&self) -> BackgroundExecutor {
+        self.0.borrow().background_executor.clone()
     }
-}
 
-impl BorrowAppContext for AsyncAppContext {
-    fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
-        self.0.borrow().read_with(f)
+    pub fn foreground_executor(&self) -> ForegroundExecutor {
+        self.0.borrow().foreground_executor.clone()
     }
 
-    fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
-        self.0.borrow_mut().update(f)
+    pub fn text_system(&self) -> Arc<TextSystem> {
+        self.0.borrow().text_system.clone()
     }
 }
 
-impl BorrowWindowContext for AsyncAppContext {
-    type Result<T> = Option<T>;
-
-    fn read_window<T, F>(&self, window: AnyWindowHandle, f: F) -> Self::Result<T>
-    where
-        F: FnOnce(&WindowContext) -> T,
-    {
-        self.0.borrow().read_with(|cx| cx.read_window(window, f))
-    }
-
-    fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
-    where
-        F: FnOnce(&WindowContext) -> Option<T>,
-    {
-        self.0
-            .borrow_mut()
-            .update(|cx| cx.read_window_optional(window, f))
-    }
-
-    fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Self::Result<T>
-    where
-        F: FnOnce(&mut WindowContext) -> T,
-    {
-        self.0.borrow_mut().update(|cx| cx.update_window(window, f))
-    }
-
-    fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
-    where
-        F: FnOnce(&mut WindowContext) -> Option<T>,
-    {
-        self.0
-            .borrow_mut()
-            .update(|cx| cx.update_window_optional(window, f))
-    }
-}
+pub(crate) type FrameCallback = Box<dyn FnOnce(&mut AppContext)>;
+type Handler = Box<dyn FnMut(&mut AppContext) -> bool + 'static>;
+type Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool + 'static>;
+type KeystrokeObserver = Box<dyn FnMut(&KeystrokeEvent, &mut WindowContext) + 'static>;
+type QuitHandler = Box<dyn FnOnce(&mut AppContext) -> LocalBoxFuture<'static, ()> + 'static>;
+type ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut AppContext) + 'static>;
+type NewViewListener = Box<dyn FnMut(AnyView, &mut WindowContext) + 'static>;
 
-type ActionCallback = dyn FnMut(&mut dyn AnyView, &dyn Action, &mut WindowContext, usize);
-type GlobalActionCallback = dyn FnMut(&dyn Action, &mut AppContext);
-
-type SubscriptionCallback = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool>;
-type GlobalSubscriptionCallback = Box<dyn FnMut(&dyn Any, &mut AppContext)>;
-type ObservationCallback = Box<dyn FnMut(&mut AppContext) -> bool>;
-type GlobalObservationCallback = Box<dyn FnMut(&mut AppContext)>;
-type FocusObservationCallback = Box<dyn FnMut(bool, &mut WindowContext) -> bool>;
-type ReleaseObservationCallback = Box<dyn FnMut(&dyn Any, &mut AppContext)>;
-type ActionObservationCallback = Box<dyn FnMut(TypeId, &mut AppContext)>;
-type WindowActivationCallback = Box<dyn FnMut(bool, &mut WindowContext) -> bool>;
-type WindowFullscreenCallback = Box<dyn FnMut(bool, &mut WindowContext) -> bool>;
-type WindowBoundsCallback = Box<dyn FnMut(WindowBounds, Uuid, &mut WindowContext) -> bool>;
-type KeystrokeCallback =
-    Box<dyn FnMut(&Keystroke, &MatchResult, Option<&Box<dyn Action>>, &mut WindowContext) -> bool>;
-type ActiveLabeledTasksCallback = Box<dyn FnMut(&mut AppContext) -> bool>;
-type DeserializeActionCallback = fn(json: serde_json::Value) -> anyhow::Result<Box<dyn Action>>;
-type WindowShouldCloseSubscriptionCallback = Box<dyn FnMut(&mut AppContext) -> bool>;
+// struct FrameConsumer {
+//     next_frame_callbacks: Vec<FrameCallback>,
+//     task: Task<()>,
+//     display_linker
+// }
 
 pub struct AppContext {
-    models: HashMap<usize, Box<dyn AnyModel>>,
-    views: HashMap<(AnyWindowHandle, usize), Box<dyn AnyView>>,
-    views_metadata: HashMap<(AnyWindowHandle, usize), ViewMetadata>,
-    windows: HashMap<AnyWindowHandle, Window>,
-    globals: HashMap<TypeId, Box<dyn Any>>,
-    element_states: HashMap<ElementStateId, Box<dyn Any>>,
-    background: Arc<executor::Background>,
-    ref_counts: Arc<Mutex<RefCounts>>,
-
-    weak_self: Option<rc::Weak<RefCell<Self>>>,
-    platform: Arc<dyn Platform>,
-    foreground_platform: Rc<dyn platform::ForegroundPlatform>,
-    pub asset_cache: Arc<AssetCache>,
-    font_system: Arc<dyn FontSystem>,
-    pub font_cache: Arc<FontCache>,
-    pub image_cache: Arc<ImageCache>,
-    action_deserializers: HashMap<&'static str, (TypeId, DeserializeActionCallback)>,
-    capture_actions: HashMap<TypeId, HashMap<TypeId, Vec<Box<ActionCallback>>>>,
-    // Entity Types -> { Action Types -> Action Handlers }
-    actions: HashMap<TypeId, HashMap<TypeId, Vec<Box<ActionCallback>>>>,
-    // Action Types -> Action Handlers
-    global_actions: HashMap<TypeId, Box<GlobalActionCallback>>,
-    keystroke_matcher: KeymapMatcher,
-    next_id: usize,
-    // next_window: AnyWindowHandle,
-    next_subscription_id: usize,
-    frame_count: usize,
-
-    subscriptions: CallbackCollection<usize, SubscriptionCallback>,
-    global_subscriptions: CallbackCollection<TypeId, GlobalSubscriptionCallback>,
-    observations: CallbackCollection<usize, ObservationCallback>,
-    global_observations: CallbackCollection<TypeId, GlobalObservationCallback>,
-    focus_observations: CallbackCollection<usize, FocusObservationCallback>,
-    release_observations: CallbackCollection<usize, ReleaseObservationCallback>,
-    action_dispatch_observations: CallbackCollection<(), ActionObservationCallback>,
-    window_activation_observations: CallbackCollection<AnyWindowHandle, WindowActivationCallback>,
-    window_fullscreen_observations: CallbackCollection<AnyWindowHandle, WindowFullscreenCallback>,
-    window_bounds_observations: CallbackCollection<AnyWindowHandle, WindowBoundsCallback>,
-    keystroke_observations: CallbackCollection<AnyWindowHandle, KeystrokeCallback>,
-    active_labeled_task_observations: CallbackCollection<(), ActiveLabeledTasksCallback>,
-
-    foreground: Rc<executor::Foreground>,
-    pending_effects: VecDeque<Effect>,
-    pending_notifications: HashSet<usize>,
-    pending_global_notifications: HashSet<TypeId>,
-    pending_flushes: usize,
+    pub(crate) this: Weak<AppCell>,
+    pub(crate) platform: Rc<dyn Platform>,
+    app_metadata: AppMetadata,
+    text_system: Arc<TextSystem>,
     flushing_effects: bool,
-    halt_action_dispatch: bool,
-    next_labeled_task_id: usize,
-    active_labeled_tasks: BTreeMap<usize, &'static str>,
+    pending_updates: usize,
+    pub(crate) actions: Rc<ActionRegistry>,
+    pub(crate) active_drag: Option<AnyDrag>,
+    pub(crate) active_tooltip: Option<AnyTooltip>,
+    pub(crate) next_frame_callbacks: FxHashMap<DisplayId, Vec<FrameCallback>>,
+    pub(crate) frame_consumers: FxHashMap<DisplayId, Task<()>>,
+    pub(crate) background_executor: BackgroundExecutor,
+    pub(crate) foreground_executor: ForegroundExecutor,
+    pub(crate) svg_renderer: SvgRenderer,
+    asset_source: Arc<dyn AssetSource>,
+    pub(crate) image_cache: ImageCache,
+    pub(crate) text_style_stack: Vec<TextStyleRefinement>,
+    pub(crate) globals_by_type: FxHashMap<TypeId, Box<dyn Any>>,
+    pub(crate) entities: EntityMap,
+    pub(crate) new_view_observers: SubscriberSet<TypeId, NewViewListener>,
+    pub(crate) windows: SlotMap<WindowId, Option<Window>>,
+    pub(crate) keymap: Arc<Mutex<Keymap>>,
+    pub(crate) global_action_listeners:
+        FxHashMap<TypeId, Vec<Rc<dyn Fn(&dyn Any, DispatchPhase, &mut Self)>>>,
+    pending_effects: VecDeque<Effect>,
+    pub(crate) pending_notifications: FxHashSet<EntityId>,
+    pub(crate) pending_global_notifications: FxHashSet<TypeId>,
+    pub(crate) observers: SubscriberSet<EntityId, Handler>,
+    // TypeId is the type of the event that the listener callback expects
+    pub(crate) event_listeners: SubscriberSet<EntityId, (TypeId, Listener)>,
+    pub(crate) keystroke_observers: SubscriberSet<(), KeystrokeObserver>,
+    pub(crate) release_listeners: SubscriberSet<EntityId, ReleaseListener>,
+    pub(crate) global_observers: SubscriberSet<TypeId, Handler>,
+    pub(crate) quit_observers: SubscriberSet<(), QuitHandler>,
+    pub(crate) layout_id_buffer: Vec<LayoutId>, // We recycle this memory across layout requests.
+    pub(crate) propagate_event: bool,
 }
 
 impl AppContext {
-    fn new(
-        foreground: Rc<executor::Foreground>,
-        background: Arc<executor::Background>,
-        platform: Arc<dyn platform::Platform>,
-        foreground_platform: Rc<dyn platform::ForegroundPlatform>,
-        font_cache: Arc<FontCache>,
+    pub(crate) fn new(
+        platform: Rc<dyn Platform>,
+        asset_source: Arc<dyn AssetSource>,
         http_client: Arc<dyn HttpClient>,
-        ref_counts: RefCounts,
-        asset_source: impl AssetSource,
-    ) -> Self {
-        Self {
-            models: Default::default(),
-            views: Default::default(),
-            views_metadata: Default::default(),
-            windows: Default::default(),
-            globals: Default::default(),
-            element_states: Default::default(),
-            ref_counts: Arc::new(Mutex::new(ref_counts)),
-            background,
-
-            weak_self: None,
-            font_system: platform.fonts(),
-            platform,
-            foreground_platform,
-            font_cache,
-            image_cache: Arc::new(ImageCache::new(http_client)),
-            asset_cache: Arc::new(AssetCache::new(asset_source)),
-            action_deserializers: Default::default(),
-            capture_actions: Default::default(),
-            actions: Default::default(),
-            global_actions: Default::default(),
-            keystroke_matcher: KeymapMatcher::default(),
-            next_id: 0,
-            next_subscription_id: 0,
-            frame_count: 0,
-            subscriptions: Default::default(),
-            global_subscriptions: Default::default(),
-            observations: Default::default(),
-            focus_observations: Default::default(),
-            release_observations: Default::default(),
-            global_observations: Default::default(),
-            window_activation_observations: Default::default(),
-            window_fullscreen_observations: Default::default(),
-            window_bounds_observations: Default::default(),
-            keystroke_observations: Default::default(),
-            action_dispatch_observations: Default::default(),
-            active_labeled_task_observations: Default::default(),
-            foreground,
-            pending_effects: VecDeque::new(),
-            pending_notifications: Default::default(),
-            pending_global_notifications: Default::default(),
-            pending_flushes: 0,
-            flushing_effects: false,
-            halt_action_dispatch: false,
-            next_labeled_task_id: 0,
-            active_labeled_tasks: Default::default(),
-        }
-    }
-
-    pub fn background(&self) -> &Arc<executor::Background> {
-        &self.background
-    }
+    ) -> Rc<AppCell> {
+        let executor = platform.background_executor();
+        let foreground_executor = platform.foreground_executor();
+        assert!(
+            executor.is_main_thread(),
+            "must construct App on main thread"
+        );
 
-    pub fn font_cache(&self) -> &Arc<FontCache> {
-        &self.font_cache
-    }
+        let text_system = Arc::new(TextSystem::new(platform.text_system()));
+        let entities = EntityMap::new();
 
-    pub fn platform(&self) -> &Arc<dyn Platform> {
-        &self.platform
-    }
+        let app_metadata = AppMetadata {
+            os_name: platform.os_name(),
+            os_version: platform.os_version().ok(),
+            app_version: platform.app_version().ok(),
+        };
 
-    pub fn has_global<T: 'static>(&self) -> bool {
-        self.globals.contains_key(&TypeId::of::<T>())
-    }
+        let app = Rc::new_cyclic(|this| AppCell {
+            app: RefCell::new(AppContext {
+                this: this.clone(),
+                platform: platform.clone(),
+                app_metadata,
+                text_system,
+                actions: Rc::new(ActionRegistry::default()),
+                flushing_effects: false,
+                pending_updates: 0,
+                active_drag: None,
+                active_tooltip: None,
+                next_frame_callbacks: FxHashMap::default(),
+                frame_consumers: FxHashMap::default(),
+                background_executor: executor,
+                foreground_executor,
+                svg_renderer: SvgRenderer::new(asset_source.clone()),
+                asset_source,
+                image_cache: ImageCache::new(http_client),
+                text_style_stack: Vec::new(),
+                globals_by_type: FxHashMap::default(),
+                entities,
+                new_view_observers: SubscriberSet::new(),
+                windows: SlotMap::with_key(),
+                keymap: Arc::new(Mutex::new(Keymap::default())),
+                global_action_listeners: FxHashMap::default(),
+                pending_effects: VecDeque::new(),
+                pending_notifications: FxHashSet::default(),
+                pending_global_notifications: FxHashSet::default(),
+                observers: SubscriberSet::new(),
+                event_listeners: SubscriberSet::new(),
+                release_listeners: SubscriberSet::new(),
+                keystroke_observers: SubscriberSet::new(),
+                global_observers: SubscriberSet::new(),
+                quit_observers: SubscriberSet::new(),
+                layout_id_buffer: Default::default(),
+                propagate_event: true,
+            }),
+        });
 
-    pub fn global<T: 'static>(&self) -> &T {
-        if let Some(global) = self.globals.get(&TypeId::of::<T>()) {
-            global.downcast_ref().unwrap()
-        } else {
-            panic!("no global has been added for {}", type_name::<T>());
-        }
-    }
+        init_app_menus(platform.as_ref(), &mut app.borrow_mut());
 
-    pub fn optional_global<T: 'static>(&self) -> Option<&T> {
-        if let Some(global) = self.globals.get(&TypeId::of::<T>()) {
-            Some(global.downcast_ref().unwrap())
-        } else {
-            None
-        }
-    }
+        platform.on_quit(Box::new({
+            let cx = app.clone();
+            move || {
+                cx.borrow_mut().shutdown();
+            }
+        }));
 
-    pub fn upgrade(&self) -> App {
-        App(self.weak_self.as_ref().unwrap().upgrade().unwrap())
+        app
     }
 
-    fn quit(&mut self) {
+    /// Quit the application gracefully. Handlers registered with `ModelContext::on_app_quit`
+    /// will be given 100ms to complete before exiting.
+    pub fn shutdown(&mut self) {
         let mut futures = Vec::new();
 
-        self.update(|cx| {
-            for model_id in cx.models.keys().copied().collect::<Vec<_>>() {
-                let mut model = cx.models.remove(&model_id).unwrap();
-                futures.extend(model.app_will_quit(cx));
-                cx.models.insert(model_id, model);
-            }
-
-            for view_id in cx.views.keys().copied().collect::<Vec<_>>() {
-                let mut view = cx.views.remove(&view_id).unwrap();
-                futures.extend(view.app_will_quit(cx));
-                cx.views.insert(view_id, view);
-            }
-        });
+        for observer in self.quit_observers.remove(&()) {
+            futures.push(observer(self));
+        }
 
         self.windows.clear();
         self.flush_effects();
 
         let futures = futures::future::join_all(futures);
         if self
-            .background
+            .background_executor
             .block_with_timeout(Duration::from_millis(100), futures)
             .is_err()
         {
@@ -626,6263 +314,951 @@ impl AppContext {
         }
     }
 
-    pub fn foreground(&self) -> &Rc<executor::Foreground> {
-        &self.foreground
+    pub fn quit(&mut self) {
+        self.platform.quit();
     }
 
-    pub fn deserialize_action(
-        &self,
-        name: &str,
-        argument: Option<serde_json::Value>,
-    ) -> Result<Box<dyn Action>> {
-        let callback = self
-            .action_deserializers
-            .get(name)
-            .ok_or_else(|| anyhow!("unknown action {}", name))?
-            .1;
-        callback(argument.unwrap_or_else(|| serde_json::Value::Object(Default::default())))
-            .with_context(|| format!("invalid data for action {}", name))
+    pub fn app_metadata(&self) -> AppMetadata {
+        self.app_metadata.clone()
     }
 
-    pub fn add_action<A, V, F, R>(&mut self, handler: F)
-    where
-        A: Action,
-        V: 'static,
-        F: 'static + FnMut(&mut V, &A, &mut ViewContext<V>) -> R,
-    {
-        self.add_action_internal(handler, false)
+    /// Schedules all windows in the application to be redrawn. This can be called
+    /// multiple times in an update cycle and still result in a single redraw.
+    pub fn refresh(&mut self) {
+        self.pending_effects.push_back(Effect::Refresh);
+    }
+    pub(crate) fn update<R>(&mut self, update: impl FnOnce(&mut Self) -> R) -> R {
+        self.pending_updates += 1;
+        let result = update(self);
+        if !self.flushing_effects && self.pending_updates == 1 {
+            self.flushing_effects = true;
+            self.flush_effects();
+            self.flushing_effects = false;
+        }
+        self.pending_updates -= 1;
+        result
     }
 
-    pub fn capture_action<A, V, F>(&mut self, handler: F)
+    pub fn observe<W, E>(
+        &mut self,
+        entity: &E,
+        mut on_notify: impl FnMut(E, &mut AppContext) + 'static,
+    ) -> Subscription
     where
-        A: Action,
-        V: 'static,
-        F: 'static + FnMut(&mut V, &A, &mut ViewContext<V>),
+        W: 'static,
+        E: Entity<W>,
     {
-        self.add_action_internal(handler, true)
+        self.observe_internal(entity, move |e, cx| {
+            on_notify(e, cx);
+            true
+        })
     }
 
-    fn add_action_internal<A, V, F, R>(&mut self, mut handler: F, capture: bool)
+    pub fn observe_internal<W, E>(
+        &mut self,
+        entity: &E,
+        mut on_notify: impl FnMut(E, &mut AppContext) -> bool + 'static,
+    ) -> Subscription
     where
-        A: Action,
-        V: 'static,
-        F: 'static + FnMut(&mut V, &A, &mut ViewContext<V>) -> R,
+        W: 'static,
+        E: Entity<W>,
     {
-        let handler = Box::new(
-            move |view: &mut dyn AnyView,
-                  action: &dyn Action,
-                  cx: &mut WindowContext,
-                  view_id: usize| {
-                let action = action.as_any().downcast_ref().unwrap();
-                let mut cx = ViewContext::mutable(cx, view_id);
-                handler(
-                    view.as_any_mut()
-                        .downcast_mut()
-                        .expect("downcast is type safe"),
-                    action,
-                    &mut cx,
-                );
-            },
-        );
-        fn inner(
-            this: &mut AppContext,
-            name: &'static str,
-            deserializer: fn(serde_json::Value) -> anyhow::Result<Box<dyn Action>>,
-            action_id: TypeId,
-            view_id: TypeId,
-            handler: Box<ActionCallback>,
-            capture: bool,
-        ) {
-            this.action_deserializers
-                .entry(name)
-                .or_insert((action_id.clone(), deserializer));
-
-            let actions = if capture {
-                &mut this.capture_actions
-            } else {
-                &mut this.actions
-            };
-
-            actions
-                .entry(view_id)
-                .or_default()
-                .entry(action_id)
-                .or_default()
-                .push(handler);
-        }
-        inner(
-            self,
-            A::qualified_name(),
-            A::from_json_str,
-            TypeId::of::<A>(),
-            TypeId::of::<V>(),
-            handler,
-            capture,
+        let entity_id = entity.entity_id();
+        let handle = entity.downgrade();
+        let (subscription, activate) = self.observers.insert(
+            entity_id,
+            Box::new(move |cx| {
+                if let Some(handle) = E::upgrade_from(&handle) {
+                    on_notify(handle, cx)
+                } else {
+                    false
+                }
+            }),
         );
+        self.defer(move |_| activate());
+        subscription
     }
 
-    pub fn add_async_action<A, V, F>(&mut self, mut handler: F)
+    pub fn subscribe<T, E, Evt>(
+        &mut self,
+        entity: &E,
+        mut on_event: impl FnMut(E, &Evt, &mut AppContext) + 'static,
+    ) -> Subscription
     where
-        A: Action,
-        V: 'static,
-        F: 'static + FnMut(&mut V, &A, &mut ViewContext<V>) -> Option<Task<Result<()>>>,
+        T: 'static + EventEmitter<Evt>,
+        E: Entity<T>,
+        Evt: 'static,
     {
-        self.add_action(move |view, action, cx| {
-            if let Some(task) = handler(view, action, cx) {
-                task.detach_and_log_err(cx);
-            }
+        self.subscribe_internal(entity, move |entity, event, cx| {
+            on_event(entity, event, cx);
+            true
         })
     }
 
-    pub fn add_global_action<A, F>(&mut self, mut handler: F)
+    pub(crate) fn subscribe_internal<T, E, Evt>(
+        &mut self,
+        entity: &E,
+        mut on_event: impl FnMut(E, &Evt, &mut AppContext) -> bool + 'static,
+    ) -> Subscription
     where
-        A: Action,
-        F: 'static + FnMut(&A, &mut AppContext),
+        T: 'static + EventEmitter<Evt>,
+        E: Entity<T>,
+        Evt: 'static,
     {
-        let handler = Box::new(move |action: &dyn Action, cx: &mut AppContext| {
-            let action = action.as_any().downcast_ref().unwrap();
-            handler(action, cx);
-        });
+        let entity_id = entity.entity_id();
+        let entity = entity.downgrade();
+        let (subscription, activate) = self.event_listeners.insert(
+            entity_id,
+            (
+                TypeId::of::<Evt>(),
+                Box::new(move |event, cx| {
+                    let event: &Evt = event.downcast_ref().expect("invalid event type");
+                    if let Some(handle) = E::upgrade_from(&entity) {
+                        on_event(handle, event, cx)
+                    } else {
+                        false
+                    }
+                }),
+            ),
+        );
+        self.defer(move |_| activate());
+        subscription
+    }
 
-        self.action_deserializers
-            .entry(A::qualified_name())
-            .or_insert((TypeId::of::<A>(), A::from_json_str));
+    pub fn windows(&self) -> Vec<AnyWindowHandle> {
+        self.windows
+            .values()
+            .filter_map(|window| Some(window.as_ref()?.handle))
+            .collect()
+    }
 
-        if self
-            .global_actions
-            .insert(TypeId::of::<A>(), handler)
-            .is_some()
-        {
-            panic!(
-                "registered multiple global handlers for {}",
-                type_name::<A>()
-            );
-        }
+    pub fn active_window(&self) -> Option<AnyWindowHandle> {
+        self.platform.active_window()
     }
 
-    pub fn view_ui_name(&self, window: AnyWindowHandle, view_id: usize) -> Option<&'static str> {
-        Some(self.views.get(&(window, view_id))?.ui_name())
+    /// Opens a new window with the given option and the root view returned by the given function.
+    /// The function is invoked with a `WindowContext`, which can be used to interact with window-specific
+    /// functionality.
+    pub fn open_window<V: 'static + Render>(
+        &mut self,
+        options: crate::WindowOptions,
+        build_root_view: impl FnOnce(&mut WindowContext) -> View<V>,
+    ) -> WindowHandle<V> {
+        self.update(|cx| {
+            let id = cx.windows.insert(None);
+            let handle = WindowHandle::new(id);
+            let mut window = Window::new(handle.into(), options, cx);
+            let root_view = build_root_view(&mut WindowContext::new(cx, &mut window));
+            window.root_view.replace(root_view.into());
+            cx.windows.get_mut(id).unwrap().replace(window);
+            handle
+        })
     }
 
-    pub fn view_type_id(&self, window: AnyWindowHandle, view_id: usize) -> Option<TypeId> {
-        self.views_metadata
-            .get(&(window, view_id))
-            .map(|metadata| metadata.type_id)
+    /// Instructs the platform to activate the application by bringing it to the foreground.
+    pub fn activate(&self, ignoring_other_apps: bool) {
+        self.platform.activate(ignoring_other_apps);
     }
 
-    pub fn active_labeled_tasks<'a>(
-        &'a self,
-    ) -> impl DoubleEndedIterator<Item = &'static str> + 'a {
-        self.active_labeled_tasks.values().cloned()
+    pub fn hide(&self) {
+        self.platform.hide();
     }
 
-    pub(crate) fn start_frame(&mut self) {
-        self.frame_count += 1;
+    pub fn hide_other_apps(&self) {
+        self.platform.hide_other_apps();
     }
 
-    pub fn update<T, F: FnOnce(&mut Self) -> T>(&mut self, callback: F) -> T {
-        self.pending_flushes += 1;
-        let result = callback(self);
-        self.flush_effects();
-        result
+    pub fn unhide_other_apps(&self) {
+        self.platform.unhide_other_apps();
     }
 
-    fn read_window<T, F: FnOnce(&WindowContext) -> T>(
-        &self,
-        handle: AnyWindowHandle,
-        callback: F,
-    ) -> Option<T> {
-        let window = self.windows.get(&handle)?;
-        let window_context = WindowContext::immutable(self, &window, handle);
-        Some(callback(&window_context))
+    /// Returns the list of currently active displays.
+    pub fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
+        self.platform.displays()
     }
 
-    pub fn update_active_window<T, F: FnOnce(&mut WindowContext) -> T>(
-        &mut self,
-        callback: F,
-    ) -> Option<T> {
-        self.active_window()
-            .and_then(|window| window.update(self, callback))
+    /// Writes data to the platform clipboard.
+    pub fn write_to_clipboard(&self, item: ClipboardItem) {
+        self.platform.write_to_clipboard(item)
+    }
+
+    /// Reads data from the platform clipboard.
+    pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
+        self.platform.read_from_clipboard()
+    }
+
+    /// Writes credentials to the platform keychain.
+    pub fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
+        self.platform.write_credentials(url, username, password)
+    }
+
+    /// Reads credentials from the platform keychain.
+    pub fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>> {
+        self.platform.read_credentials(url)
+    }
+
+    /// Deletes credentials from the platform keychain.
+    pub fn delete_credentials(&self, url: &str) -> Result<()> {
+        self.platform.delete_credentials(url)
+    }
+
+    /// Directs the platform's default browser to open the given URL.
+    pub fn open_url(&self, url: &str) {
+        self.platform.open_url(url);
+    }
+
+    pub fn app_path(&self) -> Result<PathBuf> {
+        self.platform.app_path()
+    }
+
+    pub fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
+        self.platform.path_for_auxiliary_executable(name)
+    }
+
+    pub fn double_click_interval(&self) -> Duration {
+        self.platform.double_click_interval()
     }
 
     pub fn prompt_for_paths(
         &self,
         options: PathPromptOptions,
     ) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
-        self.foreground_platform.prompt_for_paths(options)
+        self.platform.prompt_for_paths(options)
     }
 
     pub fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> {
-        self.foreground_platform.prompt_for_new_path(directory)
+        self.platform.prompt_for_new_path(directory)
     }
 
     pub fn reveal_path(&self, path: &Path) {
-        self.foreground_platform.reveal_path(path)
-    }
-
-    pub fn emit_global<E: Any>(&mut self, payload: E) {
-        self.pending_effects.push_back(Effect::GlobalEvent {
-            payload: Box::new(payload),
-        });
+        self.platform.reveal_path(path)
     }
 
-    pub fn subscribe<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
-    where
-        E: Entity,
-        E::Event: 'static,
-        H: Handle<E>,
-        F: 'static + FnMut(H, &E::Event, &mut Self),
-    {
-        self.subscribe_internal(handle, move |handle, event, cx| {
-            callback(handle, event, cx);
-            true
-        })
+    pub fn should_auto_hide_scrollbars(&self) -> bool {
+        self.platform.should_auto_hide_scrollbars()
     }
 
-    pub fn subscribe_global<E, F>(&mut self, mut callback: F) -> Subscription
-    where
-        E: Any,
-        F: 'static + FnMut(&E, &mut Self),
-    {
-        let subscription_id = post_inc(&mut self.next_subscription_id);
-        let type_id = TypeId::of::<E>();
-        self.pending_effects.push_back(Effect::GlobalSubscription {
-            type_id,
-            subscription_id,
-            callback: Box::new(move |payload, cx| {
-                let payload = payload.downcast_ref().expect("downcast is type safe");
-                callback(payload, cx)
-            }),
-        });
-        Subscription::GlobalSubscription(
-            self.global_subscriptions
-                .subscribe(type_id, subscription_id),
-        )
+    pub fn restart(&self) {
+        self.platform.restart()
     }
 
-    pub fn observe<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
-    where
-        E: Entity,
-        E::Event: 'static,
-        H: Handle<E>,
-        F: 'static + FnMut(H, &mut Self),
-    {
-        self.observe_internal(handle, move |handle, cx| {
-            callback(handle, cx);
-            true
-        })
+    pub fn local_timezone(&self) -> UtcOffset {
+        self.platform.local_timezone()
     }
 
-    fn subscribe_internal<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
-    where
-        E: Entity,
-        E::Event: 'static,
-        H: Handle<E>,
-        F: 'static + FnMut(H, &E::Event, &mut Self) -> bool,
-    {
-        let subscription_id = post_inc(&mut self.next_subscription_id);
-        let emitter = handle.downgrade();
-        self.pending_effects.push_back(Effect::Subscription {
-            entity_id: handle.id(),
-            subscription_id,
-            callback: Box::new(move |payload, cx| {
-                if let Some(emitter) = H::upgrade_from(&emitter, cx) {
-                    let payload = payload.downcast_ref().expect("downcast is type safe");
-                    callback(emitter, payload, cx)
-                } else {
-                    false
+    pub(crate) fn push_effect(&mut self, effect: Effect) {
+        match &effect {
+            Effect::Notify { emitter } => {
+                if !self.pending_notifications.insert(*emitter) {
+                    return;
                 }
-            }),
-        });
-        Subscription::Subscription(self.subscriptions.subscribe(handle.id(), subscription_id))
-    }
-
-    fn observe_internal<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
-    where
-        E: Entity,
-        E::Event: 'static,
-        H: Handle<E>,
-        F: 'static + FnMut(H, &mut Self) -> bool,
-    {
-        let subscription_id = post_inc(&mut self.next_subscription_id);
-        let observed = handle.downgrade();
-        let entity_id = handle.id();
-        self.pending_effects.push_back(Effect::Observation {
-            entity_id,
-            subscription_id,
-            callback: Box::new(move |cx| {
-                if let Some(observed) = H::upgrade_from(&observed, cx) {
-                    callback(observed, cx)
-                } else {
-                    false
+            }
+            Effect::NotifyGlobalObservers { global_type } => {
+                if !self.pending_global_notifications.insert(*global_type) {
+                    return;
                 }
-            }),
-        });
-        Subscription::Observation(self.observations.subscribe(entity_id, subscription_id))
-    }
+            }
+            _ => {}
+        };
 
-    fn observe_focus<F, V>(&mut self, handle: &ViewHandle<V>, mut callback: F) -> Subscription
-    where
-        V: 'static,
-        F: 'static + FnMut(ViewHandle<V>, bool, &mut WindowContext) -> bool,
-    {
-        let subscription_id = post_inc(&mut self.next_subscription_id);
-        let observed = handle.downgrade();
-        let view_id = handle.id();
-
-        self.pending_effects.push_back(Effect::FocusObservation {
-            view_id,
-            subscription_id,
-            callback: Box::new(move |focused, cx| {
-                if let Some(observed) = observed.upgrade(cx) {
-                    callback(observed, focused, cx)
-                } else {
-                    false
-                }
-            }),
-        });
-        Subscription::FocusObservation(self.focus_observations.subscribe(view_id, subscription_id))
+        self.pending_effects.push_back(effect);
     }
 
-    pub fn observe_global<G, F>(&mut self, mut observe: F) -> Subscription
-    where
-        G: Any,
-        F: 'static + FnMut(&mut AppContext),
-    {
-        let type_id = TypeId::of::<G>();
-        let id = post_inc(&mut self.next_subscription_id);
+    /// Called at the end of AppContext::update to complete any side effects
+    /// such as notifying observers, emitting events, etc. Effects can themselves
+    /// cause effects, so we continue looping until all effects are processed.
+    fn flush_effects(&mut self) {
+        loop {
+            self.release_dropped_entities();
+            self.release_dropped_focus_handles();
 
-        self.global_observations.add_callback(
-            type_id,
-            id,
-            Box::new(move |cx: &mut AppContext| observe(cx)),
-        );
-        Subscription::GlobalObservation(self.global_observations.subscribe(type_id, id))
-    }
+            if let Some(effect) = self.pending_effects.pop_front() {
+                match effect {
+                    Effect::Notify { emitter } => {
+                        self.apply_notify_effect(emitter);
+                    }
 
-    pub fn observe_default_global<G, F>(&mut self, observe: F) -> Subscription
-    where
-        G: Any + Default,
-        F: 'static + FnMut(&mut AppContext),
-    {
-        if !self.has_global::<G>() {
-            self.set_global(G::default());
+                    Effect::Emit {
+                        emitter,
+                        event_type,
+                        event,
+                    } => self.apply_emit_effect(emitter, event_type, event),
+
+                    Effect::Refresh => {
+                        self.apply_refresh_effect();
+                    }
+
+                    Effect::NotifyGlobalObservers { global_type } => {
+                        self.apply_notify_global_observers_effect(global_type);
+                    }
+
+                    Effect::Defer { callback } => {
+                        self.apply_defer_effect(callback);
+                    }
+                }
+            } else {
+                for window in self.windows.values() {
+                    if let Some(window) = window.as_ref() {
+                        if window.dirty {
+                            window.platform_window.invalidate();
+                        }
+                    }
+                }
+
+                #[cfg(any(test, feature = "test-support"))]
+                for window in self
+                    .windows
+                    .values()
+                    .filter_map(|window| {
+                        let window = window.as_ref()?;
+                        (window.dirty || window.focus_invalidated).then_some(window.handle)
+                    })
+                    .collect::<Vec<_>>()
+                {
+                    self.update_window(window, |_, cx| cx.draw()).unwrap();
+                }
+
+                if self.pending_effects.is_empty() {
+                    break;
+                }
+            }
         }
-        self.observe_global::<G, F>(observe)
     }
 
-    pub fn observe_release<E, H, F>(&mut self, handle: &H, callback: F) -> Subscription
-    where
-        E: Entity,
-        E::Event: 'static,
-        H: Handle<E>,
-        F: 'static + FnOnce(&E, &mut Self),
-    {
-        let id = post_inc(&mut self.next_subscription_id);
-        let mut callback = Some(callback);
-        self.release_observations.add_callback(
-            handle.id(),
-            id,
-            Box::new(move |released, cx| {
-                let released = released.downcast_ref().unwrap();
-                if let Some(callback) = callback.take() {
-                    callback(released, cx)
+    /// Repeatedly called during `flush_effects` to release any entities whose
+    /// reference count has become zero. We invoke any release observers before dropping
+    /// each entity.
+    fn release_dropped_entities(&mut self) {
+        loop {
+            let dropped = self.entities.take_dropped();
+            if dropped.is_empty() {
+                break;
+            }
+
+            for (entity_id, mut entity) in dropped {
+                self.observers.remove(&entity_id);
+                self.event_listeners.remove(&entity_id);
+                for release_callback in self.release_listeners.remove(&entity_id) {
+                    release_callback(entity.as_mut(), self);
                 }
-            }),
-        );
-        Subscription::ReleaseObservation(self.release_observations.subscribe(handle.id(), id))
+            }
+        }
     }
 
-    pub fn observe_actions<F>(&mut self, callback: F) -> Subscription
-    where
-        F: 'static + FnMut(TypeId, &mut AppContext),
-    {
-        let subscription_id = post_inc(&mut self.next_subscription_id);
-        self.action_dispatch_observations
-            .add_callback((), subscription_id, Box::new(callback));
-        Subscription::ActionObservation(
-            self.action_dispatch_observations
-                .subscribe((), subscription_id),
-        )
-    }
+    /// Repeatedly called during `flush_effects` to handle a focused handle being dropped.
+    fn release_dropped_focus_handles(&mut self) {
+        for window_handle in self.windows() {
+            window_handle
+                .update(self, |_, cx| {
+                    let mut blur_window = false;
+                    let focus = cx.window.focus;
+                    cx.window.focus_handles.write().retain(|handle_id, count| {
+                        if count.load(SeqCst) == 0 {
+                            if focus == Some(handle_id) {
+                                blur_window = true;
+                            }
+                            false
+                        } else {
+                            true
+                        }
+                    });
 
-    fn observe_active_labeled_tasks<F>(&mut self, callback: F) -> Subscription
-    where
-        F: 'static + FnMut(&mut AppContext) -> bool,
-    {
-        let subscription_id = post_inc(&mut self.next_subscription_id);
-        self.active_labeled_task_observations
-            .add_callback((), subscription_id, Box::new(callback));
-        Subscription::ActiveLabeledTasksObservation(
-            self.active_labeled_task_observations
-                .subscribe((), subscription_id),
-        )
+                    if blur_window {
+                        cx.blur();
+                    }
+                })
+                .unwrap();
+        }
     }
 
-    pub fn defer(&mut self, callback: impl 'static + FnOnce(&mut AppContext)) {
-        self.pending_effects.push_back(Effect::Deferred {
-            callback: Box::new(callback),
-            after_window_update: false,
-        })
+    fn apply_notify_effect(&mut self, emitter: EntityId) {
+        self.pending_notifications.remove(&emitter);
+
+        self.observers
+            .clone()
+            .retain(&emitter, |handler| handler(self));
     }
 
-    pub fn after_window_update(&mut self, callback: impl 'static + FnOnce(&mut AppContext)) {
-        self.pending_effects.push_back(Effect::Deferred {
-            callback: Box::new(callback),
-            after_window_update: true,
-        })
+    fn apply_emit_effect(&mut self, emitter: EntityId, event_type: TypeId, event: Box<dyn Any>) {
+        self.event_listeners
+            .clone()
+            .retain(&emitter, |(stored_type, handler)| {
+                if *stored_type == event_type {
+                    handler(event.as_ref(), self)
+                } else {
+                    true
+                }
+            });
     }
 
-    fn notify_model(&mut self, model_id: usize) {
-        if self.pending_notifications.insert(model_id) {
-            self.pending_effects
-                .push_back(Effect::ModelNotification { model_id });
+    fn apply_refresh_effect(&mut self) {
+        for window in self.windows.values_mut() {
+            if let Some(window) = window.as_mut() {
+                window.dirty = true;
+            }
         }
     }
 
-    fn notify_view(&mut self, window: AnyWindowHandle, view_id: usize) {
-        if self.pending_notifications.insert(view_id) {
-            self.pending_effects
-                .push_back(Effect::ViewNotification { window, view_id });
-        }
+    fn apply_notify_global_observers_effect(&mut self, type_id: TypeId) {
+        self.pending_global_notifications.remove(&type_id);
+        self.global_observers
+            .clone()
+            .retain(&type_id, |observer| observer(self));
+    }
+
+    fn apply_defer_effect(&mut self, callback: Box<dyn FnOnce(&mut Self) + 'static>) {
+        callback(self);
     }
 
-    fn notify_global(&mut self, type_id: TypeId) {
-        if self.pending_global_notifications.insert(type_id) {
-            self.pending_effects
-                .push_back(Effect::GlobalNotification { type_id });
+    /// Creates an `AsyncAppContext`, which can be cloned and has a static lifetime
+    /// so it can be held across `await` points.
+    pub fn to_async(&self) -> AsyncAppContext {
+        AsyncAppContext {
+            app: unsafe { mem::transmute(self.this.clone()) },
+            background_executor: self.background_executor.clone(),
+            foreground_executor: self.foreground_executor.clone(),
         }
     }
 
-    pub fn all_action_names<'a>(&'a self) -> impl Iterator<Item = &'static str> + 'a {
-        self.action_deserializers.keys().copied()
+    /// Obtains a reference to the executor, which can be used to spawn futures.
+    pub fn background_executor(&self) -> &BackgroundExecutor {
+        &self.background_executor
     }
 
-    pub fn is_action_available(&self, action: &dyn Action) -> bool {
-        let mut available_in_window = false;
-        let action_id = action.id();
-        if let Some(window) = self.active_window() {
-            available_in_window = self
-                .read_window(window, |cx| {
-                    if let Some(focused_view_id) = cx.focused_view_id() {
-                        for view_id in cx.ancestors(focused_view_id) {
-                            if let Some(view_metadata) =
-                                cx.views_metadata.get(&(cx.window_handle, view_id))
-                            {
-                                if let Some(actions) = cx.actions.get(&view_metadata.type_id) {
-                                    if actions.contains_key(&action_id) {
-                                        return true;
-                                    }
-                                }
-                            }
-                        }
-                    }
-                    false
-                })
-                .unwrap_or(false);
-        }
-        available_in_window || self.global_actions.contains_key(&action_id)
+    /// Obtains a reference to the executor, which can be used to spawn futures.
+    pub fn foreground_executor(&self) -> &ForegroundExecutor {
+        &self.foreground_executor
     }
 
-    fn actions_mut(
-        &mut self,
-        capture_phase: bool,
-    ) -> &mut HashMap<TypeId, HashMap<TypeId, Vec<Box<ActionCallback>>>> {
-        if capture_phase {
-            &mut self.capture_actions
-        } else {
-            &mut self.actions
-        }
+    /// Spawns the future returned by the given function on the thread pool. The closure will be invoked
+    /// with AsyncAppContext, which allows the application state to be accessed across await points.
+    pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
+    where
+        Fut: Future<Output = R> + 'static,
+        R: 'static,
+    {
+        self.foreground_executor.spawn(f(self.to_async()))
     }
 
-    fn dispatch_global_action_any(&mut self, action: &dyn Action) -> bool {
-        self.update(|this| {
-            if let Some((name, mut handler)) = this.global_actions.remove_entry(&action.id()) {
-                handler(action, this);
-                this.global_actions.insert(name, handler);
-                true
-            } else {
-                false
-            }
-        })
+    /// Schedules the given function to be run at the end of the current effect cycle, allowing entities
+    /// that are currently on the stack to be returned to the app.
+    pub fn defer(&mut self, f: impl FnOnce(&mut AppContext) + 'static) {
+        self.push_effect(Effect::Defer {
+            callback: Box::new(f),
+        });
     }
 
-    pub fn add_bindings<T: IntoIterator<Item = Binding>>(&mut self, bindings: T) {
-        self.keystroke_matcher.add_bindings(bindings);
+    /// Accessor for the application's asset source, which is provided when constructing the `App`.
+    pub fn asset_source(&self) -> &Arc<dyn AssetSource> {
+        &self.asset_source
     }
 
-    pub fn clear_bindings(&mut self) {
-        self.keystroke_matcher.clear_bindings();
+    /// Accessor for the text system.
+    pub fn text_system(&self) -> &Arc<TextSystem> {
+        &self.text_system
     }
 
-    pub fn binding_for_action(&self, action: &dyn Action) -> Option<&Binding> {
-        self.keystroke_matcher
-            .bindings_for_action(action.id())
-            .find(|binding| binding.action().eq(action))
+    /// The current text style. Which is composed of all the style refinements provided to `with_text_style`.
+    pub fn text_style(&self) -> TextStyle {
+        let mut style = TextStyle::default();
+        for refinement in &self.text_style_stack {
+            style.refine(refinement);
+        }
+        style
     }
 
-    pub fn default_global<T: 'static + Default>(&mut self) -> &T {
-        let type_id = TypeId::of::<T>();
-        self.update(|this| {
-            if let Entry::Vacant(entry) = this.globals.entry(type_id) {
-                entry.insert(Box::new(T::default()));
-                this.notify_global(type_id);
-            }
-        });
-        self.globals.get(&type_id).unwrap().downcast_ref().unwrap()
+    /// Check whether a global of the given type has been assigned.
+    pub fn has_global<G: 'static>(&self) -> bool {
+        self.globals_by_type.contains_key(&TypeId::of::<G>())
     }
 
-    pub fn set_global<T: 'static>(&mut self, state: T) {
-        self.update(|this| {
-            let type_id = TypeId::of::<T>();
-            this.globals.insert(type_id, Box::new(state));
-            this.notify_global(type_id);
-        });
+    /// Access the global of the given type. Panics if a global for that type has not been assigned.
+    #[track_caller]
+    pub fn global<G: 'static>(&self) -> &G {
+        self.globals_by_type
+            .get(&TypeId::of::<G>())
+            .map(|any_state| any_state.downcast_ref::<G>().unwrap())
+            .ok_or_else(|| anyhow!("no state of type {} exists", type_name::<G>()))
+            .unwrap()
     }
 
-    pub fn update_default_global<T, F, U>(&mut self, update: F) -> U
-    where
-        T: 'static + Default,
-        F: FnOnce(&mut T, &mut AppContext) -> U,
-    {
-        self.update(|mut this| {
-            Self::update_default_global_internal(&mut this, |global, cx| update(global, cx))
-        })
+    /// Access the global of the given type if a value has been assigned.
+    pub fn try_global<G: 'static>(&self) -> Option<&G> {
+        self.globals_by_type
+            .get(&TypeId::of::<G>())
+            .map(|any_state| any_state.downcast_ref::<G>().unwrap())
     }
 
-    fn update_default_global_internal<C, T, F, U>(this: &mut C, update: F) -> U
-    where
-        C: DerefMut<Target = AppContext>,
-        T: 'static + Default,
-        F: FnOnce(&mut T, &mut C) -> U,
-    {
-        let type_id = TypeId::of::<T>();
-        let mut state = this
-            .globals
-            .remove(&type_id)
-            .unwrap_or_else(|| Box::new(T::default()));
-        let result = update(state.downcast_mut().unwrap(), this);
-        this.globals.insert(type_id, state);
-        this.notify_global(type_id);
-        result
+    /// Access the global of the given type mutably. Panics if a global for that type has not been assigned.
+    #[track_caller]
+    pub fn global_mut<G: 'static>(&mut self) -> &mut G {
+        let global_type = TypeId::of::<G>();
+        self.push_effect(Effect::NotifyGlobalObservers { global_type });
+        self.globals_by_type
+            .get_mut(&global_type)
+            .and_then(|any_state| any_state.downcast_mut::<G>())
+            .ok_or_else(|| anyhow!("no state of type {} exists", type_name::<G>()))
+            .unwrap()
     }
 
-    pub fn update_global<T, F, U>(&mut self, update: F) -> U
-    where
-        T: 'static,
-        F: FnOnce(&mut T, &mut AppContext) -> U,
-    {
-        self.update(|mut this| {
-            Self::update_global_internal(&mut this, |global, cx| update(global, cx))
-        })
+    /// Access the global of the given type mutably. A default value is assigned if a global of this type has not
+    /// yet been assigned.
+    pub fn default_global<G: 'static + Default>(&mut self) -> &mut G {
+        let global_type = TypeId::of::<G>();
+        self.push_effect(Effect::NotifyGlobalObservers { global_type });
+        self.globals_by_type
+            .entry(global_type)
+            .or_insert_with(|| Box::<G>::default())
+            .downcast_mut::<G>()
+            .unwrap()
     }
 
-    fn update_global_internal<C, T, F, U>(this: &mut C, update: F) -> U
-    where
-        C: DerefMut<Target = AppContext>,
-        T: 'static,
-        F: FnOnce(&mut T, &mut C) -> U,
-    {
-        let type_id = TypeId::of::<T>();
-        if let Some(mut state) = this.globals.remove(&type_id) {
-            let result = update(state.downcast_mut().unwrap(), this);
-            this.globals.insert(type_id, state);
-            this.notify_global(type_id);
-            result
-        } else {
-            panic!("no global added for {}", std::any::type_name::<T>());
-        }
+    /// Set the value of the global of the given type.
+    pub fn set_global<G: Any>(&mut self, global: G) {
+        let global_type = TypeId::of::<G>();
+        self.push_effect(Effect::NotifyGlobalObservers { global_type });
+        self.globals_by_type.insert(global_type, Box::new(global));
     }
 
+    /// Clear all stored globals. Does not notify global observers.
+    #[cfg(any(test, feature = "test-support"))]
     pub fn clear_globals(&mut self) {
-        self.globals.clear();
+        self.globals_by_type.drain();
     }
 
-    pub fn remove_global<T: 'static>(&mut self) -> T {
+    /// Remove the global of the given type from the app context. Does not notify global observers.
+    pub fn remove_global<G: Any>(&mut self) -> G {
+        let global_type = TypeId::of::<G>();
         *self
-            .globals
-            .remove(&TypeId::of::<T>())
-            .unwrap_or_else(|| panic!("no global added for {}", std::any::type_name::<T>()))
+            .globals_by_type
+            .remove(&global_type)
+            .unwrap_or_else(|| panic!("no global added for {}", std::any::type_name::<G>()))
             .downcast()
             .unwrap()
     }
 
-    pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
-    where
-        T: Entity,
-        F: FnOnce(&mut ModelContext<T>) -> T,
-    {
-        self.update(|this| {
-            let model_id = post_inc(&mut this.next_id);
-            let handle = ModelHandle::new(model_id, &this.ref_counts);
-            let mut cx = ModelContext::new(this, model_id);
-            let model = build_model(&mut cx);
-            this.models.insert(model_id, Box::new(model));
-            handle
-        })
-    }
-
-    pub fn read_model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
-        if let Some(model) = self.models.get(&handle.model_id) {
-            model
-                .as_any()
-                .downcast_ref()
-                .expect("downcast is type safe")
-        } else {
-            panic!("circular model reference");
-        }
+    /// Update the global of the given type with a closure. Unlike `global_mut`, this method provides
+    /// your closure with mutable access to the `AppContext` and the global simultaneously.
+    pub fn update_global<G: 'static, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R {
+        let mut global = self.lease_global::<G>();
+        let result = f(&mut global, self);
+        self.end_global_lease(global);
+        result
     }
 
-    fn update_model<T: Entity, V>(
+    /// Register a callback to be invoked when a global of the given type is updated.
+    pub fn observe_global<G: 'static>(
         &mut self,
-        handle: &ModelHandle<T>,
-        update: &mut dyn FnMut(&mut T, &mut ModelContext<T>) -> V,
-    ) -> V {
-        if let Some(mut model) = self.models.remove(&handle.model_id) {
-            self.update(|this| {
-                let mut cx = ModelContext::new(this, handle.model_id);
-                let result = update(
-                    model
-                        .as_any_mut()
-                        .downcast_mut()
-                        .expect("downcast is type safe"),
-                    &mut cx,
-                );
-                this.models.insert(handle.model_id, model);
-                result
-            })
-        } else {
-            panic!("circular model update for {}", std::any::type_name::<T>());
-        }
-    }
-
-    fn upgrade_model_handle<T: Entity>(
-        &self,
-        handle: &WeakModelHandle<T>,
-    ) -> Option<ModelHandle<T>> {
-        if self.ref_counts.lock().is_entity_alive(handle.model_id) {
-            Some(ModelHandle::new(handle.model_id, &self.ref_counts))
-        } else {
-            None
-        }
+        mut f: impl FnMut(&mut Self) + 'static,
+    ) -> Subscription {
+        let (subscription, activate) = self.global_observers.insert(
+            TypeId::of::<G>(),
+            Box::new(move |cx| {
+                f(cx);
+                true
+            }),
+        );
+        self.defer(move |_| activate());
+        subscription
     }
 
-    fn model_handle_is_upgradable<T: Entity>(&self, handle: &WeakModelHandle<T>) -> bool {
-        self.ref_counts.lock().is_entity_alive(handle.model_id)
+    /// Move the global of the given type to the stack.
+    pub(crate) fn lease_global<G: 'static>(&mut self) -> GlobalLease<G> {
+        GlobalLease::new(
+            self.globals_by_type
+                .remove(&TypeId::of::<G>())
+                .ok_or_else(|| anyhow!("no global registered of type {}", type_name::<G>()))
+                .unwrap(),
+        )
     }
 
-    fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option<AnyModelHandle> {
-        if self.ref_counts.lock().is_entity_alive(handle.model_id) {
-            Some(AnyModelHandle::new(
-                handle.model_id,
-                handle.model_type,
-                self.ref_counts.clone(),
-            ))
-        } else {
-            None
-        }
+    /// Restore the global of the given type after it is moved to the stack.
+    pub(crate) fn end_global_lease<G: 'static>(&mut self, lease: GlobalLease<G>) {
+        let global_type = TypeId::of::<G>();
+        self.push_effect(Effect::NotifyGlobalObservers { global_type });
+        self.globals_by_type.insert(global_type, lease.global);
     }
 
-    pub fn add_window<V, F>(
+    pub fn observe_new_views<V: 'static>(
         &mut self,
-        window_options: WindowOptions,
-        build_root_view: F,
-    ) -> WindowHandle<V>
-    where
-        V: View,
-        F: FnOnce(&mut ViewContext<V>) -> V,
-    {
-        self.update(|this| {
-            let handle = WindowHandle::<V>::new(post_inc(&mut this.next_id));
-            let platform_window =
-                this.platform
-                    .open_window(handle.into(), window_options, this.foreground.clone());
-            let window = this.build_window(handle.into(), platform_window, build_root_view);
-            this.windows.insert(handle.into(), window);
-            handle
-        })
+        on_new: impl 'static + Fn(&mut V, &mut ViewContext<V>),
+    ) -> Subscription {
+        let (subscription, activate) = self.new_view_observers.insert(
+            TypeId::of::<V>(),
+            Box::new(move |any_view: AnyView, cx: &mut WindowContext| {
+                any_view
+                    .downcast::<V>()
+                    .unwrap()
+                    .update(cx, |view_state, cx| {
+                        on_new(view_state, cx);
+                    })
+            }),
+        );
+        activate();
+        subscription
     }
 
-    pub fn add_status_bar_item<V, F>(&mut self, build_root_view: F) -> WindowHandle<V>
+    pub fn observe_release<E, T>(
+        &mut self,
+        handle: &E,
+        on_release: impl FnOnce(&mut T, &mut AppContext) + 'static,
+    ) -> Subscription
     where
-        V: View,
-        F: FnOnce(&mut ViewContext<V>) -> V,
+        E: Entity<T>,
+        T: 'static,
     {
-        self.update(|this| {
-            let handle = WindowHandle::<V>::new(post_inc(&mut this.next_id));
-            let platform_window = this.platform.add_status_item(handle.into());
-            let window = this.build_window(handle.into(), platform_window, build_root_view);
-            this.windows.insert(handle.into(), window);
-            handle.update_root(this, |view, cx| view.focus_in(cx.handle().into_any(), cx));
-            handle
-        })
+        let (subscription, activate) = self.release_listeners.insert(
+            handle.entity_id(),
+            Box::new(move |entity, cx| {
+                let entity = entity.downcast_mut().expect("invalid entity type");
+                on_release(entity, cx)
+            }),
+        );
+        activate();
+        subscription
     }
 
-    pub fn build_window<V, F>(
+    pub fn observe_keystrokes(
         &mut self,
-        handle: AnyWindowHandle,
-        mut platform_window: Box<dyn platform::Window>,
-        build_root_view: F,
-    ) -> Window
-    where
-        V: View,
-        F: FnOnce(&mut ViewContext<V>) -> V,
-    {
-        {
-            let mut app = self.upgrade();
-
-            platform_window.on_event(Box::new(move |event| {
-                app.update_window(handle, |cx| {
-                    if let Event::KeyDown(KeyDownEvent { keystroke, .. }) = &event {
-                        if cx.dispatch_keystroke(keystroke) {
-                            return true;
-                        }
-                    }
-
-                    cx.dispatch_event(event, false)
-                })
-                .unwrap_or(false)
-            }));
-        }
-
-        {
-            let mut app = self.upgrade();
-            platform_window.on_active_status_change(Box::new(move |is_active| {
-                app.update(|cx| cx.window_changed_active_status(handle, is_active))
-            }));
-        }
+        f: impl FnMut(&KeystrokeEvent, &mut WindowContext) + 'static,
+    ) -> Subscription {
+        let (subscription, activate) = self.keystroke_observers.insert((), Box::new(f));
+        activate();
+        subscription
+    }
 
-        {
-            let mut app = self.upgrade();
-            platform_window.on_resize(Box::new(move || {
-                app.update(|cx| cx.window_was_resized(handle))
-            }));
-        }
+    pub(crate) fn push_text_style(&mut self, text_style: TextStyleRefinement) {
+        self.text_style_stack.push(text_style);
+    }
 
-        {
-            let mut app = self.upgrade();
-            platform_window.on_moved(Box::new(move || {
-                app.update(|cx| cx.window_was_moved(handle))
-            }));
-        }
+    pub(crate) fn pop_text_style(&mut self) {
+        self.text_style_stack.pop();
+    }
 
-        {
-            let mut app = self.upgrade();
-            platform_window.on_fullscreen(Box::new(move |is_fullscreen| {
-                app.update(|cx| cx.window_was_fullscreen_changed(handle, is_fullscreen))
-            }));
-        }
+    /// Register key bindings.
+    pub fn bind_keys(&mut self, bindings: impl IntoIterator<Item = KeyBinding>) {
+        self.keymap.lock().add_bindings(bindings);
+        self.pending_effects.push_back(Effect::Refresh);
+    }
 
-        {
-            let mut app = self.upgrade();
-            platform_window.on_close(Box::new(move || {
-                app.update(|cx| cx.update_window(handle, |cx| cx.remove_window()));
+    /// Register a global listener for actions invoked via the keyboard.
+    pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut Self) + 'static) {
+        self.global_action_listeners
+            .entry(TypeId::of::<A>())
+            .or_default()
+            .push(Rc::new(move |action, phase, cx| {
+                if phase == DispatchPhase::Bubble {
+                    let action = action.downcast_ref().unwrap();
+                    listener(action, cx)
+                }
             }));
-        }
+    }
 
-        {
-            let mut app = self.upgrade();
-            platform_window
-                .on_appearance_changed(Box::new(move || app.update(|cx| cx.refresh_windows())));
-        }
+    /// Event handlers propagate events by default. Call this method to stop dispatching to
+    /// event handlers with a lower z-index (mouse) or higher in the tree (keyboard). This is
+    /// the opposite of [propagate]. It's also possible to cancel a call to [propagate] by
+    /// calling this method before effects are flushed.
+    pub fn stop_propagation(&mut self) {
+        self.propagate_event = false;
+    }
 
-        platform_window.set_input_handler(Box::new(WindowInputHandler {
-            app: self.upgrade().0,
-            window: handle,
-        }));
+    /// Action handlers stop propagation by default during the bubble phase of action dispatch
+    /// dispatching to action handlers higher in the element tree. This is the opposite of
+    /// [stop_propagation]. It's also possible to cancel a call to [stop_propagate] by calling
+    /// this method before effects are flushed.
+    pub fn propagate(&mut self) {
+        self.propagate_event = true;
+    }
 
-        let mut window = Window::new(handle, platform_window, self, build_root_view);
-        let mut cx = WindowContext::mutable(self, &mut window, handle);
-        cx.layout(false).expect("initial layout should not error");
-        let scene = cx.paint().expect("initial paint should not error");
-        window.platform_window.present_scene(scene);
-        window
+    pub fn build_action(
+        &self,
+        name: &str,
+        data: Option<serde_json::Value>,
+    ) -> Result<Box<dyn Action>> {
+        self.actions.build_action(name, data)
     }
 
-    pub fn active_window(&self) -> Option<AnyWindowHandle> {
-        self.platform.main_window()
+    pub fn all_action_names(&self) -> &[SharedString] {
+        self.actions.all_action_names()
     }
 
-    pub fn windows(&self) -> impl '_ + Iterator<Item = AnyWindowHandle> {
-        self.windows.keys().copied()
+    pub fn on_app_quit<Fut>(
+        &mut self,
+        mut on_quit: impl FnMut(&mut AppContext) -> Fut + 'static,
+    ) -> Subscription
+    where
+        Fut: 'static + Future<Output = ()>,
+    {
+        let (subscription, activate) = self.quit_observers.insert(
+            (),
+            Box::new(move |cx| {
+                let future = on_quit(cx);
+                future.boxed_local()
+            }),
+        );
+        activate();
+        subscription
     }
 
-    pub fn read_view<V: 'static>(&self, handle: &ViewHandle<V>) -> &V {
-        if let Some(view) = self.views.get(&(handle.window, handle.view_id)) {
-            view.as_any().downcast_ref().expect("downcast is type safe")
-        } else {
-            panic!("circular view reference for type {}", type_name::<V>());
+    pub(crate) fn clear_pending_keystrokes(&mut self) {
+        for window in self.windows() {
+            window
+                .update(self, |_, cx| {
+                    cx.window
+                        .rendered_frame
+                        .dispatch_tree
+                        .clear_pending_keystrokes();
+                    cx.window
+                        .next_frame
+                        .dispatch_tree
+                        .clear_pending_keystrokes();
+                })
+                .ok();
         }
     }
 
-    fn upgrade_view_handle<V: 'static>(&self, handle: &WeakViewHandle<V>) -> Option<ViewHandle<V>> {
-        if self.ref_counts.lock().is_entity_alive(handle.view_id) {
-            Some(ViewHandle::new(
-                handle.window,
-                handle.view_id,
-                &self.ref_counts,
-            ))
-        } else {
-            None
+    pub fn is_action_available(&mut self, action: &dyn Action) -> bool {
+        if let Some(window) = self.active_window() {
+            if let Ok(window_action_available) =
+                window.update(self, |_, cx| cx.is_action_available(action))
+            {
+                return window_action_available;
+            }
         }
+
+        self.global_action_listeners
+            .contains_key(&action.as_any().type_id())
     }
 
-    fn upgrade_any_view_handle(&self, handle: &AnyWeakViewHandle) -> Option<AnyViewHandle> {
-        if self.ref_counts.lock().is_entity_alive(handle.view_id) {
-            Some(AnyViewHandle::new(
-                handle.window,
-                handle.view_id,
-                handle.view_type,
-                self.ref_counts.clone(),
-            ))
-        } else {
-            None
-        }
+    pub fn set_menus(&mut self, menus: Vec<Menu>) {
+        self.platform.set_menus(menus, &self.keymap.lock());
     }
 
-    fn remove_dropped_entities(&mut self) {
-        loop {
-            let (dropped_models, dropped_views, dropped_element_states) =
-                self.ref_counts.lock().take_dropped();
-            if dropped_models.is_empty()
-                && dropped_views.is_empty()
-                && dropped_element_states.is_empty()
+    pub fn dispatch_action(&mut self, action: &dyn Action) {
+        if let Some(active_window) = self.active_window() {
+            active_window
+                .update(self, |_, cx| cx.dispatch_action(action.boxed_clone()))
+                .log_err();
+        } else {
+            self.propagate_event = true;
+
+            if let Some(mut global_listeners) = self
+                .global_action_listeners
+                .remove(&action.as_any().type_id())
             {
-                break;
-            }
+                for listener in &global_listeners {
+                    listener(action.as_any(), DispatchPhase::Capture, self);
+                    if !self.propagate_event {
+                        break;
+                    }
+                }
+
+                global_listeners.extend(
+                    self.global_action_listeners
+                        .remove(&action.as_any().type_id())
+                        .unwrap_or_default(),
+                );
 
-            for model_id in dropped_models {
-                self.subscriptions.remove(model_id);
-                self.observations.remove(model_id);
-                let mut model = self.models.remove(&model_id).unwrap();
-                model.release(self);
-                self.pending_effects
-                    .push_back(Effect::ModelRelease { model_id, model });
+                self.global_action_listeners
+                    .insert(action.as_any().type_id(), global_listeners);
             }
 
-            for (window, view_id) in dropped_views {
-                self.subscriptions.remove(view_id);
-                self.observations.remove(view_id);
-                self.views_metadata.remove(&(window, view_id));
-                let mut view = self.views.remove(&(window, view_id)).unwrap();
-                view.release(self);
-                if let Some(window) = self.windows.get_mut(&window) {
-                    window.parents.remove(&view_id);
-                    window
-                        .invalidation
-                        .get_or_insert_with(Default::default)
-                        .removed
-                        .push(view_id);
-                }
+            if self.propagate_event {
+                if let Some(mut global_listeners) = self
+                    .global_action_listeners
+                    .remove(&action.as_any().type_id())
+                {
+                    for listener in global_listeners.iter().rev() {
+                        listener(action.as_any(), DispatchPhase::Bubble, self);
+                        if !self.propagate_event {
+                            break;
+                        }
+                    }
 
-                self.pending_effects
-                    .push_back(Effect::ViewRelease { view_id, view });
-            }
+                    global_listeners.extend(
+                        self.global_action_listeners
+                            .remove(&action.as_any().type_id())
+                            .unwrap_or_default(),
+                    );
 
-            for key in dropped_element_states {
-                self.element_states.remove(&key);
+                    self.global_action_listeners
+                        .insert(action.as_any().type_id(), global_listeners);
+                }
             }
         }
     }
 
-    fn flush_effects(&mut self) {
-        self.pending_flushes = self.pending_flushes.saturating_sub(1);
-        let mut after_window_update_callbacks = Vec::new();
-
-        if !self.flushing_effects && self.pending_flushes == 0 {
-            self.flushing_effects = true;
+    pub fn has_active_drag(&self) -> bool {
+        self.active_drag.is_some()
+    }
 
-            let mut refreshing = false;
-            let mut updated_windows = HashSet::default();
-            let mut focus_effects = HashMap::<AnyWindowHandle, FocusEffect>::default();
-            loop {
-                self.remove_dropped_entities();
-                if let Some(effect) = self.pending_effects.pop_front() {
-                    match effect {
-                        Effect::Subscription {
-                            entity_id,
-                            subscription_id,
-                            callback,
-                        } => self
-                            .subscriptions
-                            .add_callback(entity_id, subscription_id, callback),
-
-                        Effect::Event { entity_id, payload } => {
-                            let mut subscriptions = self.subscriptions.clone();
-                            subscriptions
-                                .emit(entity_id, |callback| callback(payload.as_ref(), self))
-                        }
+    pub fn active_drag<T: 'static>(&self) -> Option<&T> {
+        self.active_drag
+            .as_ref()
+            .and_then(|drag| drag.value.downcast_ref())
+    }
+}
 
-                        Effect::GlobalSubscription {
-                            type_id,
-                            subscription_id,
-                            callback,
-                        } => self.global_subscriptions.add_callback(
-                            type_id,
-                            subscription_id,
-                            callback,
-                        ),
-
-                        Effect::GlobalEvent { payload } => self.emit_global_event(payload),
-
-                        Effect::Observation {
-                            entity_id,
-                            subscription_id,
-                            callback,
-                        } => self
-                            .observations
-                            .add_callback(entity_id, subscription_id, callback),
-
-                        Effect::ModelNotification { model_id } => {
-                            let mut observations = self.observations.clone();
-                            observations.emit(model_id, |callback| callback(self));
-                        }
+impl Context for AppContext {
+    type Result<T> = T;
 
-                        Effect::ViewNotification {
-                            window: window_id,
-                            view_id,
-                        } => self.handle_view_notification_effect(window_id, view_id),
-
-                        Effect::GlobalNotification { type_id } => {
-                            let mut subscriptions = self.global_observations.clone();
-                            subscriptions.emit(type_id, |callback| {
-                                callback(self);
-                                true
-                            });
-                        }
-
-                        Effect::Deferred {
-                            callback,
-                            after_window_update,
-                        } => {
-                            if after_window_update {
-                                after_window_update_callbacks.push(callback);
-                            } else {
-                                callback(self)
-                            }
-                        }
-
-                        Effect::ModelRelease { model_id, model } => {
-                            self.handle_entity_release_effect(model_id, model.as_any())
-                        }
-
-                        Effect::ViewRelease { view_id, view } => {
-                            self.handle_entity_release_effect(view_id, view.as_any())
-                        }
-
-                        Effect::Focus(mut effect) => {
-                            if focus_effects
-                                .get(&effect.window())
-                                .map_or(false, |prev_effect| prev_effect.is_forced())
-                            {
-                                effect.force();
-                            }
-
-                            focus_effects.insert(effect.window(), effect);
-                        }
-
-                        Effect::FocusObservation {
-                            view_id,
-                            subscription_id,
-                            callback,
-                        } => {
-                            self.focus_observations.add_callback(
-                                view_id,
-                                subscription_id,
-                                callback,
-                            );
-                        }
-
-                        Effect::ResizeWindow { window } => {
-                            if let Some(window) = self.windows.get_mut(&window) {
-                                window
-                                    .invalidation
-                                    .get_or_insert(WindowInvalidation::default());
-                            }
-                            self.handle_window_moved(window);
-                        }
-
-                        Effect::MoveWindow { window } => {
-                            self.handle_window_moved(window);
-                        }
-
-                        Effect::WindowActivationObservation {
-                            window,
-                            subscription_id,
-                            callback,
-                        } => self.window_activation_observations.add_callback(
-                            window,
-                            subscription_id,
-                            callback,
-                        ),
-
-                        Effect::ActivateWindow { window, is_active } => {
-                            if self.handle_window_activation_effect(window, is_active) && is_active
-                            {
-                                focus_effects
-                                    .entry(window)
-                                    .or_insert_with(|| FocusEffect::View {
-                                        window,
-                                        view_id: self
-                                            .read_window(window, |cx| cx.focused_view_id())
-                                            .flatten(),
-                                        is_forced: true,
-                                    })
-                                    .force();
-                            }
-                        }
-
-                        Effect::WindowFullscreenObservation {
-                            window,
-                            subscription_id,
-                            callback,
-                        } => self.window_fullscreen_observations.add_callback(
-                            window,
-                            subscription_id,
-                            callback,
-                        ),
-
-                        Effect::FullscreenWindow {
-                            window,
-                            is_fullscreen,
-                        } => self.handle_fullscreen_effect(window, is_fullscreen),
-
-                        Effect::WindowBoundsObservation {
-                            window,
-                            subscription_id,
-                            callback,
-                        } => self.window_bounds_observations.add_callback(
-                            window,
-                            subscription_id,
-                            callback,
-                        ),
-
-                        Effect::RefreshWindows => {
-                            refreshing = true;
-                        }
-
-                        Effect::ActionDispatchNotification { action_id } => {
-                            self.handle_action_dispatch_notification_effect(action_id)
-                        }
-                        Effect::WindowShouldCloseSubscription { window, callback } => {
-                            self.handle_window_should_close_subscription_effect(window, callback)
-                        }
-                        Effect::Keystroke {
-                            window,
-                            keystroke,
-                            handled_by,
-                            result,
-                        } => self.handle_keystroke_effect(window, keystroke, handled_by, result),
-                        Effect::ActiveLabeledTasksChanged => {
-                            self.handle_active_labeled_tasks_changed_effect()
-                        }
-                        Effect::ActiveLabeledTasksObservation {
-                            subscription_id,
-                            callback,
-                        } => self.active_labeled_task_observations.add_callback(
-                            (),
-                            subscription_id,
-                            callback,
-                        ),
-                        Effect::RepaintWindow { window } => {
-                            self.handle_repaint_window_effect(window)
-                        }
-                    }
-                    self.pending_notifications.clear();
-                } else {
-                    for window in self.windows().collect::<Vec<_>>() {
-                        self.update_window(window, |cx| {
-                            let invalidation = if refreshing {
-                                let mut invalidation =
-                                    cx.window.invalidation.take().unwrap_or_default();
-                                invalidation
-                                    .updated
-                                    .extend(cx.window.rendered_views.keys().copied());
-                                Some(invalidation)
-                            } else {
-                                cx.window.invalidation.take()
-                            };
-
-                            if let Some(invalidation) = invalidation {
-                                let appearance = cx.window.platform_window.appearance();
-                                cx.invalidate(invalidation, appearance);
-                                if let Some(old_parents) = cx.layout(refreshing).log_err() {
-                                    updated_windows.insert(window);
-
-                                    if let Some(focused_view_id) = cx.focused_view_id() {
-                                        let old_ancestors = std::iter::successors(
-                                            Some(focused_view_id),
-                                            |&view_id| old_parents.get(&view_id).copied(),
-                                        )
-                                        .collect::<HashSet<_>>();
-                                        let new_ancestors =
-                                            cx.ancestors(focused_view_id).collect::<HashSet<_>>();
-
-                                        // Notify the old ancestors of the focused view when they don't contain it anymore.
-                                        for old_ancestor in old_ancestors.iter().copied() {
-                                            if !new_ancestors.contains(&old_ancestor) {
-                                                if let Some(mut view) =
-                                                    cx.views.remove(&(window, old_ancestor))
-                                                {
-                                                    view.focus_out(
-                                                        focused_view_id,
-                                                        cx,
-                                                        old_ancestor,
-                                                    );
-                                                    cx.views.insert((window, old_ancestor), view);
-                                                }
-                                            }
-                                        }
-
-                                        // Notify the new ancestors of the focused view if they contain it now.
-                                        for new_ancestor in new_ancestors.iter().copied() {
-                                            if !old_ancestors.contains(&new_ancestor) {
-                                                if let Some(mut view) =
-                                                    cx.views.remove(&(window, new_ancestor))
-                                                {
-                                                    view.focus_in(
-                                                        focused_view_id,
-                                                        cx,
-                                                        new_ancestor,
-                                                    );
-                                                    cx.views.insert((window, new_ancestor), view);
-                                                }
-                                            }
-                                        }
-
-                                        // When the previously-focused view has been dropped and
-                                        // there isn't any pending focus, focus the root view.
-                                        let root_view_id = cx.window.root_view().id();
-                                        if focused_view_id != root_view_id
-                                            && !cx.views.contains_key(&(window, focused_view_id))
-                                            && !focus_effects.contains_key(&window)
-                                        {
-                                            focus_effects.insert(
-                                                window,
-                                                FocusEffect::View {
-                                                    window,
-                                                    view_id: Some(root_view_id),
-                                                    is_forced: false,
-                                                },
-                                            );
-                                        }
-                                    }
-                                }
-                            }
-                        });
-                    }
-
-                    for (_, effect) in focus_effects.drain() {
-                        self.handle_focus_effect(effect);
-                    }
-
-                    if self.pending_effects.is_empty() {
-                        for callback in after_window_update_callbacks.drain(..) {
-                            callback(self);
-                        }
-
-                        for window in updated_windows.drain() {
-                            self.update_window(window, |cx| {
-                                if let Some(scene) = cx.paint().log_err() {
-                                    cx.window.platform_window.present_scene(scene);
-                                }
-                            });
-                        }
-
-                        if self.pending_effects.is_empty() {
-                            self.flushing_effects = false;
-                            self.pending_notifications.clear();
-                            self.pending_global_notifications.clear();
-                            break;
-                        }
-                    }
-
-                    refreshing = false;
-                }
-            }
-        }
-    }
-
-    fn window_was_resized(&mut self, window: AnyWindowHandle) {
-        self.pending_effects
-            .push_back(Effect::ResizeWindow { window });
-    }
-
-    fn window_was_moved(&mut self, window: AnyWindowHandle) {
-        self.pending_effects
-            .push_back(Effect::MoveWindow { window });
-    }
-
-    fn window_was_fullscreen_changed(&mut self, window: AnyWindowHandle, is_fullscreen: bool) {
-        self.pending_effects.push_back(Effect::FullscreenWindow {
-            window,
-            is_fullscreen,
-        });
-    }
-
-    fn window_changed_active_status(&mut self, window: AnyWindowHandle, is_active: bool) {
-        self.pending_effects
-            .push_back(Effect::ActivateWindow { window, is_active });
-    }
-
-    fn keystroke(
-        &mut self,
-        window: AnyWindowHandle,
-        keystroke: Keystroke,
-        handled_by: Option<Box<dyn Action>>,
-        result: MatchResult,
-    ) {
-        self.pending_effects.push_back(Effect::Keystroke {
-            window,
-            keystroke,
-            handled_by,
-            result,
-        });
-    }
-
-    pub fn refresh_windows(&mut self) {
-        self.pending_effects.push_back(Effect::RefreshWindows);
-    }
-
-    fn emit_global_event(&mut self, payload: Box<dyn Any>) {
-        let type_id = (&*payload).type_id();
-
-        let mut subscriptions = self.global_subscriptions.clone();
-        subscriptions.emit(type_id, |callback| {
-            callback(payload.as_ref(), self);
-            true //Always alive
-        });
-    }
-
-    fn handle_view_notification_effect(
-        &mut self,
-        observed_window: AnyWindowHandle,
-        observed_view_id: usize,
-    ) {
-        let view_key = (observed_window, observed_view_id);
-        if let Some((view, mut view_metadata)) = self
-            .views
-            .remove(&view_key)
-            .zip(self.views_metadata.remove(&view_key))
-        {
-            if let Some(window) = self.windows.get_mut(&observed_window) {
-                window
-                    .invalidation
-                    .get_or_insert_with(Default::default)
-                    .updated
-                    .insert(observed_view_id);
-            }
-
-            view.update_keymap_context(&mut view_metadata.keymap_context, self);
-            self.views.insert(view_key, view);
-            self.views_metadata.insert(view_key, view_metadata);
-
-            let mut observations = self.observations.clone();
-            observations.emit(observed_view_id, |callback| callback(self));
-        }
-    }
-
-    fn handle_entity_release_effect(&mut self, entity_id: usize, entity: &dyn Any) {
-        self.release_observations
-            .clone()
-            .emit(entity_id, |callback| {
-                callback(entity, self);
-                // Release observations happen one time. So clear the callback by returning false
-                false
-            })
-    }
-
-    fn handle_fullscreen_effect(&mut self, window: AnyWindowHandle, is_fullscreen: bool) {
-        self.update_window(window, |cx| {
-            cx.window.is_fullscreen = is_fullscreen;
-
-            let mut fullscreen_observations = cx.window_fullscreen_observations.clone();
-            fullscreen_observations.emit(window, |callback| callback(is_fullscreen, cx));
-
-            if let Some(uuid) = cx.window_display_uuid() {
-                let bounds = cx.window_bounds();
-                let mut bounds_observations = cx.window_bounds_observations.clone();
-                bounds_observations.emit(window, |callback| callback(bounds, uuid, cx));
-            }
-
-            Some(())
-        });
-    }
-
-    fn handle_keystroke_effect(
+    /// Build an entity that is owned by the application. The given function will be invoked with
+    /// a `ModelContext` and must return an object representing the entity. A `Model` will be returned
+    /// which can be used to access the entity in a context.
+    fn new_model<T: 'static>(
         &mut self,
-        window: AnyWindowHandle,
-        keystroke: Keystroke,
-        handled_by: Option<Box<dyn Action>>,
-        result: MatchResult,
-    ) {
-        self.update_window(window, |cx| {
-            let mut observations = cx.keystroke_observations.clone();
-            observations.emit(window, move |callback| {
-                callback(&keystroke, &result, handled_by.as_ref(), cx)
-            });
-        });
-    }
-
-    fn handle_repaint_window_effect(&mut self, window: AnyWindowHandle) {
-        self.update_window(window, |cx| {
-            if let Some(scene) = cx.paint().log_err() {
-                cx.window.platform_window.present_scene(scene);
-            }
-        });
-    }
-
-    fn handle_window_activation_effect(&mut self, window: AnyWindowHandle, active: bool) -> bool {
-        self.update_window(window, |cx| {
-            if cx.window.is_active == active {
-                return false;
-            }
-            cx.window.is_active = active;
-
-            let mut observations = cx.window_activation_observations.clone();
-            observations.emit(window, |callback| callback(active, cx));
-            true
+        build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
+    ) -> Model<T> {
+        self.update(|cx| {
+            let slot = cx.entities.reserve();
+            let entity = build_model(&mut ModelContext::new(cx, slot.downgrade()));
+            cx.entities.insert(slot, entity)
         })
-        .unwrap_or(false)
-    }
-
-    fn handle_focus_effect(&mut self, effect: FocusEffect) {
-        let window = effect.window();
-        self.update_window(window, |cx| {
-            // Ensure the newly-focused view still exists, otherwise focus
-            // the root view instead.
-            let focused_id = match effect {
-                FocusEffect::View { view_id, .. } => {
-                    if let Some(view_id) = view_id {
-                        if cx.views.contains_key(&(window, view_id)) {
-                            Some(view_id)
-                        } else {
-                            Some(cx.root_view().id())
-                        }
-                    } else {
-                        None
-                    }
-                }
-                FocusEffect::ViewParent { view_id, .. } => Some(
-                    cx.window
-                        .parents
-                        .get(&view_id)
-                        .copied()
-                        .unwrap_or(cx.root_view().id()),
-                ),
-            };
-
-            let focus_changed = cx.window.focused_view_id != focused_id;
-            let blurred_id = cx.window.focused_view_id;
-            cx.window.focused_view_id = focused_id;
-
-            if focus_changed {
-                if let Some(blurred_id) = blurred_id {
-                    for view_id in cx.ancestors(blurred_id).collect::<Vec<_>>() {
-                        if let Some(mut view) = cx.views.remove(&(window, view_id)) {
-                            view.focus_out(blurred_id, cx, view_id);
-                            cx.views.insert((window, view_id), view);
-                        }
-                    }
-
-                    let mut subscriptions = cx.focus_observations.clone();
-                    subscriptions.emit(blurred_id, |callback| callback(false, cx));
-                }
-            }
-
-            if focus_changed || effect.is_forced() {
-                if let Some(focused_id) = focused_id {
-                    for view_id in cx.ancestors(focused_id).collect::<Vec<_>>() {
-                        if let Some(mut view) = cx.views.remove(&(window, view_id)) {
-                            view.focus_in(focused_id, cx, view_id);
-                            cx.views.insert((window, view_id), view);
-                        }
-                    }
-
-                    let mut subscriptions = cx.focus_observations.clone();
-                    subscriptions.emit(focused_id, |callback| callback(true, cx));
-                }
-            }
-        });
-    }
-
-    fn handle_action_dispatch_notification_effect(&mut self, action_id: TypeId) {
-        self.action_dispatch_observations
-            .clone()
-            .emit((), |callback| {
-                callback(action_id, self);
-                true
-            });
     }
 
-    fn handle_window_should_close_subscription_effect(
+    /// Update the entity referenced by the given model. The function is passed a mutable reference to the
+    /// entity along with a `ModelContext` for the entity.
+    fn update_model<T: 'static, R>(
         &mut self,
-        window: AnyWindowHandle,
-        mut callback: WindowShouldCloseSubscriptionCallback,
-    ) {
-        let mut app = self.upgrade();
-        if let Some(window) = self.windows.get_mut(&window) {
-            window
-                .platform_window
-                .on_should_close(Box::new(move || app.update(|cx| callback(cx))))
-        }
-    }
-
-    fn handle_window_moved(&mut self, window: AnyWindowHandle) {
-        self.update_window(window, |cx| {
-            if let Some(display) = cx.window_display_uuid() {
-                let bounds = cx.window_bounds();
-                cx.window_bounds_observations
-                    .clone()
-                    .emit(window, move |callback| {
-                        callback(bounds, display, cx);
-                        true
-                    });
-            }
-        });
-    }
-
-    fn handle_active_labeled_tasks_changed_effect(&mut self) {
-        self.active_labeled_task_observations
-            .clone()
-            .emit((), move |callback| {
-                callback(self);
-                true
-            });
-    }
-
-    pub fn focus(&mut self, window: AnyWindowHandle, view_id: Option<usize>) {
-        self.pending_effects
-            .push_back(Effect::Focus(FocusEffect::View {
-                window,
-                view_id,
-                is_forced: false,
-            }));
+        model: &Model<T>,
+        update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
+    ) -> R {
+        self.update(|cx| {
+            let mut entity = cx.entities.lease(model);
+            let result = update(&mut entity, &mut ModelContext::new(cx, model.downgrade()));
+            cx.entities.end_lease(entity);
+            result
+        })
     }
 
-    fn spawn_internal<F, Fut, T>(&mut self, task_name: Option<&'static str>, f: F) -> Task<T>
+    fn update_window<T, F>(&mut self, handle: AnyWindowHandle, update: F) -> Result<T>
     where
-        F: FnOnce(AsyncAppContext) -> Fut,
-        Fut: 'static + Future<Output = T>,
-        T: 'static,
+        F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
     {
-        let label_id = task_name.map(|task_name| {
-            let id = post_inc(&mut self.next_labeled_task_id);
-            self.active_labeled_tasks.insert(id, task_name);
-            self.pending_effects
-                .push_back(Effect::ActiveLabeledTasksChanged);
-            id
-        });
-
-        let future = f(self.to_async());
-        let cx = self.to_async();
-        self.foreground.spawn(async move {
-            let result = future.await;
-            let mut cx = cx.0.borrow_mut();
-
-            if let Some(completed_label_id) = label_id {
-                cx.active_labeled_tasks.remove(&completed_label_id);
-                cx.pending_effects
-                    .push_back(Effect::ActiveLabeledTasksChanged);
+        self.update(|cx| {
+            let mut window = cx
+                .windows
+                .get_mut(handle.id)
+                .ok_or_else(|| anyhow!("window not found"))?
+                .take()
+                .unwrap();
+
+            let root_view = window.root_view.clone().unwrap();
+            let result = update(root_view, &mut WindowContext::new(cx, &mut window));
+
+            if window.removed {
+                cx.windows.remove(handle.id);
+            } else {
+                cx.windows
+                    .get_mut(handle.id)
+                    .ok_or_else(|| anyhow!("window not found"))?
+                    .replace(window);
             }
-            cx.flush_effects();
-            result
+
+            Ok(result)
         })
     }
 
-    pub fn spawn_labeled<F, Fut, T>(&mut self, task_name: &'static str, f: F) -> Task<T>
+    fn read_model<T, R>(
+        &self,
+        handle: &Model<T>,
+        read: impl FnOnce(&T, &AppContext) -> R,
+    ) -> Self::Result<R>
     where
-        F: FnOnce(AsyncAppContext) -> Fut,
-        Fut: 'static + Future<Output = T>,
         T: 'static,
     {
-        self.spawn_internal(Some(task_name), f)
+        let entity = self.entities.read(handle);
+        read(entity, self)
     }
 
-    pub fn spawn<F, Fut, T>(&mut self, f: F) -> Task<T>
+    fn read_window<T, R>(
+        &self,
+        window: &WindowHandle<T>,
+        read: impl FnOnce(View<T>, &AppContext) -> R,
+    ) -> Result<R>
     where
-        F: FnOnce(AsyncAppContext) -> Fut,
-        Fut: 'static + Future<Output = T>,
         T: 'static,
     {
-        self.spawn_internal(None, f)
-    }
-
-    pub fn to_async(&self) -> AsyncAppContext {
-        AsyncAppContext(self.weak_self.as_ref().unwrap().upgrade().unwrap())
-    }
-
-    pub fn open_url(&self, url: &str) {
-        self.platform.open_url(url)
-    }
-
-    pub fn write_to_clipboard(&self, item: ClipboardItem) {
-        self.platform.write_to_clipboard(item);
-    }
-
-    pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
-        self.platform.read_from_clipboard()
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn leak_detector(&self) -> Arc<Mutex<LeakDetector>> {
-        self.ref_counts.lock().leak_detector.clone()
-    }
-}
-
-impl BorrowAppContext for AppContext {
-    fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
-        f(self)
-    }
-
-    fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
-        f(self)
-    }
-}
-
-impl BorrowWindowContext for AppContext {
-    type Result<T> = Option<T>;
-
-    fn read_window<T, F>(&self, window: AnyWindowHandle, f: F) -> Self::Result<T>
-    where
-        F: FnOnce(&WindowContext) -> T,
-    {
-        AppContext::read_window(self, window, f)
-    }
-
-    fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
-    where
-        F: FnOnce(&WindowContext) -> Option<T>,
-    {
-        AppContext::read_window(self, window, f).flatten()
-    }
-
-    fn update_window<T, F>(&mut self, handle: AnyWindowHandle, f: F) -> Self::Result<T>
-    where
-        F: FnOnce(&mut WindowContext) -> T,
-    {
-        self.update(|cx| {
-            let mut window = cx.windows.remove(&handle)?;
-            let mut window_context = WindowContext::mutable(cx, &mut window, handle);
-            let result = f(&mut window_context);
-            if !window_context.removed {
-                cx.windows.insert(handle, window);
-            }
-            Some(result)
-        })
-    }
-
-    fn update_window_optional<T, F>(&mut self, handle: AnyWindowHandle, f: F) -> Option<T>
-    where
-        F: FnOnce(&mut WindowContext) -> Option<T>,
-    {
-        AppContext::update_window(self, handle, f).flatten()
-    }
-}
-
-#[derive(Debug)]
-pub enum ParentId {
-    View(usize),
-    Root,
-}
-
-struct ViewMetadata {
-    type_id: TypeId,
-    keymap_context: KeymapContext,
-}
-
-#[derive(Default, Clone, Debug)]
-pub struct WindowInvalidation {
-    pub updated: HashSet<usize>,
-    pub removed: Vec<usize>,
-}
-
-#[derive(Debug)]
-pub enum FocusEffect {
-    View {
-        window: AnyWindowHandle,
-        view_id: Option<usize>,
-        is_forced: bool,
-    },
-    ViewParent {
-        window: AnyWindowHandle,
-        view_id: usize,
-        is_forced: bool,
-    },
-}
-
-impl FocusEffect {
-    fn window(&self) -> AnyWindowHandle {
-        match self {
-            FocusEffect::View { window, .. } => *window,
-            FocusEffect::ViewParent { window, .. } => *window,
-        }
-    }
+        let window = self
+            .windows
+            .get(window.id)
+            .ok_or_else(|| anyhow!("window not found"))?
+            .as_ref()
+            .unwrap();
 
-    fn is_forced(&self) -> bool {
-        match self {
-            FocusEffect::View { is_forced, .. } => *is_forced,
-            FocusEffect::ViewParent { is_forced, .. } => *is_forced,
-        }
-    }
+        let root_view = window.root_view.clone().unwrap();
+        let view = root_view
+            .downcast::<T>()
+            .map_err(|_| anyhow!("root view's type has changed"))?;
 
-    fn force(&mut self) {
-        match self {
-            FocusEffect::View { is_forced, .. } => *is_forced = true,
-            FocusEffect::ViewParent { is_forced, .. } => *is_forced = true,
-        }
+        Ok(read(view, self))
     }
 }
 
-pub enum Effect {
-    Subscription {
-        entity_id: usize,
-        subscription_id: usize,
-        callback: SubscriptionCallback,
-    },
-    Event {
-        entity_id: usize,
-        payload: Box<dyn Any>,
-    },
-    GlobalSubscription {
-        type_id: TypeId,
-        subscription_id: usize,
-        callback: GlobalSubscriptionCallback,
-    },
-    GlobalEvent {
-        payload: Box<dyn Any>,
-    },
-    Observation {
-        entity_id: usize,
-        subscription_id: usize,
-        callback: ObservationCallback,
-    },
-    ModelNotification {
-        model_id: usize,
-    },
-    ViewNotification {
-        window: AnyWindowHandle,
-        view_id: usize,
-    },
-    Deferred {
-        callback: Box<dyn FnOnce(&mut AppContext)>,
-        after_window_update: bool,
-    },
-    GlobalNotification {
-        type_id: TypeId,
-    },
-    ModelRelease {
-        model_id: usize,
-        model: Box<dyn AnyModel>,
-    },
-    ViewRelease {
-        view_id: usize,
-        view: Box<dyn AnyView>,
-    },
-    Focus(FocusEffect),
-    FocusObservation {
-        view_id: usize,
-        subscription_id: usize,
-        callback: FocusObservationCallback,
+/// These effects are processed at the end of each application update cycle.
+pub(crate) enum Effect {
+    Notify {
+        emitter: EntityId,
     },
-    ResizeWindow {
-        window: AnyWindowHandle,
+    Emit {
+        emitter: EntityId,
+        event_type: TypeId,
+        event: Box<dyn Any>,
     },
-    MoveWindow {
-        window: AnyWindowHandle,
+    Refresh,
+    NotifyGlobalObservers {
+        global_type: TypeId,
     },
-    ActivateWindow {
-        window: AnyWindowHandle,
-        is_active: bool,
-    },
-    RepaintWindow {
-        window: AnyWindowHandle,
-    },
-    WindowActivationObservation {
-        window: AnyWindowHandle,
-        subscription_id: usize,
-        callback: WindowActivationCallback,
-    },
-    FullscreenWindow {
-        window: AnyWindowHandle,
-        is_fullscreen: bool,
-    },
-    WindowFullscreenObservation {
-        window: AnyWindowHandle,
-        subscription_id: usize,
-        callback: WindowFullscreenCallback,
-    },
-    WindowBoundsObservation {
-        window: AnyWindowHandle,
-        subscription_id: usize,
-        callback: WindowBoundsCallback,
-    },
-    Keystroke {
-        window: AnyWindowHandle,
-        keystroke: Keystroke,
-        handled_by: Option<Box<dyn Action>>,
-        result: MatchResult,
-    },
-    RefreshWindows,
-    ActionDispatchNotification {
-        action_id: TypeId,
-    },
-    WindowShouldCloseSubscription {
-        window: AnyWindowHandle,
-        callback: WindowShouldCloseSubscriptionCallback,
-    },
-    ActiveLabeledTasksChanged,
-    ActiveLabeledTasksObservation {
-        subscription_id: usize,
-        callback: ActiveLabeledTasksCallback,
+    Defer {
+        callback: Box<dyn FnOnce(&mut AppContext) + 'static>,
     },
 }
 
-impl Debug for Effect {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            Effect::Subscription {
-                entity_id,
-                subscription_id,
-                ..
-            } => f
-                .debug_struct("Effect::Subscribe")
-                .field("entity_id", entity_id)
-                .field("subscription_id", subscription_id)
-                .finish(),
-            Effect::Event { entity_id, .. } => f
-                .debug_struct("Effect::Event")
-                .field("entity_id", entity_id)
-                .finish(),
-            Effect::GlobalSubscription {
-                type_id,
-                subscription_id,
-                ..
-            } => f
-                .debug_struct("Effect::Subscribe")
-                .field("type_id", type_id)
-                .field("subscription_id", subscription_id)
-                .finish(),
-            Effect::GlobalEvent { payload, .. } => f
-                .debug_struct("Effect::GlobalEvent")
-                .field("type_id", &(&*payload).type_id())
-                .finish(),
-            Effect::Observation {
-                entity_id,
-                subscription_id,
-                ..
-            } => f
-                .debug_struct("Effect::Observation")
-                .field("entity_id", entity_id)
-                .field("subscription_id", subscription_id)
-                .finish(),
-            Effect::ModelNotification { model_id } => f
-                .debug_struct("Effect::ModelNotification")
-                .field("model_id", model_id)
-                .finish(),
-            Effect::ViewNotification { window, view_id } => f
-                .debug_struct("Effect::ViewNotification")
-                .field("window_id", &window.id())
-                .field("view_id", view_id)
-                .finish(),
-            Effect::GlobalNotification { type_id } => f
-                .debug_struct("Effect::GlobalNotification")
-                .field("type_id", type_id)
-                .finish(),
-            Effect::Deferred { .. } => f.debug_struct("Effect::Deferred").finish(),
-            Effect::ModelRelease { model_id, .. } => f
-                .debug_struct("Effect::ModelRelease")
-                .field("model_id", model_id)
-                .finish(),
-            Effect::ViewRelease { view_id, .. } => f
-                .debug_struct("Effect::ViewRelease")
-                .field("view_id", view_id)
-                .finish(),
-            Effect::Focus(focus) => f.debug_tuple("Effect::Focus").field(focus).finish(),
-            Effect::FocusObservation {
-                view_id,
-                subscription_id,
-                ..
-            } => f
-                .debug_struct("Effect::FocusObservation")
-                .field("view_id", view_id)
-                .field("subscription_id", subscription_id)
-                .finish(),
-            Effect::ActionDispatchNotification { action_id, .. } => f
-                .debug_struct("Effect::ActionDispatchNotification")
-                .field("action_id", action_id)
-                .finish(),
-            Effect::ResizeWindow { window } => f
-                .debug_struct("Effect::RefreshWindow")
-                .field("window_id", &window.id())
-                .finish(),
-            Effect::MoveWindow { window } => f
-                .debug_struct("Effect::MoveWindow")
-                .field("window_id", &window.id())
-                .finish(),
-            Effect::WindowActivationObservation {
-                window,
-                subscription_id,
-                ..
-            } => f
-                .debug_struct("Effect::WindowActivationObservation")
-                .field("window_id", &window.id())
-                .field("subscription_id", subscription_id)
-                .finish(),
-            Effect::ActivateWindow { window, is_active } => f
-                .debug_struct("Effect::ActivateWindow")
-                .field("window_id", &window.id())
-                .field("is_active", is_active)
-                .finish(),
-            Effect::FullscreenWindow {
-                window,
-                is_fullscreen,
-            } => f
-                .debug_struct("Effect::FullscreenWindow")
-                .field("window_id", &window.id())
-                .field("is_fullscreen", is_fullscreen)
-                .finish(),
-            Effect::WindowFullscreenObservation {
-                window,
-                subscription_id,
-                callback: _,
-            } => f
-                .debug_struct("Effect::WindowFullscreenObservation")
-                .field("window_id", &window.id())
-                .field("subscription_id", subscription_id)
-                .finish(),
-
-            Effect::WindowBoundsObservation {
-                window,
-                subscription_id,
-                callback: _,
-            } => f
-                .debug_struct("Effect::WindowBoundsObservation")
-                .field("window_id", &window.id())
-                .field("subscription_id", subscription_id)
-                .finish(),
-            Effect::RefreshWindows => f.debug_struct("Effect::FullViewRefresh").finish(),
-            Effect::WindowShouldCloseSubscription { window, .. } => f
-                .debug_struct("Effect::WindowShouldCloseSubscription")
-                .field("window_id", &window.id())
-                .finish(),
-            Effect::Keystroke {
-                window,
-                keystroke,
-                handled_by,
-                result,
-            } => f
-                .debug_struct("Effect::Keystroke")
-                .field("window_id", &window.id())
-                .field("keystroke", keystroke)
-                .field(
-                    "keystroke",
-                    &handled_by.as_ref().map(|handled_by| handled_by.name()),
-                )
-                .field("result", result)
-                .finish(),
-            Effect::ActiveLabeledTasksChanged => {
-                f.debug_struct("Effect::ActiveLabeledTasksChanged").finish()
-            }
-            Effect::ActiveLabeledTasksObservation {
-                subscription_id,
-                callback: _,
-            } => f
-                .debug_struct("Effect::ActiveLabeledTasksObservation")
-                .field("subscription_id", subscription_id)
-                .finish(),
-            Effect::RepaintWindow { window } => f
-                .debug_struct("Effect::RepaintWindow")
-                .field("window_id", &window.id())
-                .finish(),
-        }
-    }
+/// Wraps a global variable value during `update_global` while the value has been moved to the stack.
+pub(crate) struct GlobalLease<G: 'static> {
+    global: Box<dyn Any>,
+    global_type: PhantomData<G>,
 }
 
-pub trait AnyModel {
-    fn as_any(&self) -> &dyn Any;
-    fn as_any_mut(&mut self) -> &mut dyn Any;
-    fn release(&mut self, cx: &mut AppContext);
-    fn app_will_quit(
-        &mut self,
-        cx: &mut AppContext,
-    ) -> Option<Pin<Box<dyn 'static + Future<Output = ()>>>>;
+impl<G: 'static> GlobalLease<G> {
+    fn new(global: Box<dyn Any>) -> Self {
+        GlobalLease {
+            global,
+            global_type: PhantomData,
+        }
+    }
 }
 
-impl<T> AnyModel for T
-where
-    T: Entity,
-{
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
+impl<G: 'static> Deref for GlobalLease<G> {
+    type Target = G;
 
-    fn as_any_mut(&mut self) -> &mut dyn Any {
-        self
+    fn deref(&self) -> &Self::Target {
+        self.global.downcast_ref().unwrap()
     }
+}
 
-    fn release(&mut self, cx: &mut AppContext) {
-        self.release(cx);
+impl<G: 'static> DerefMut for GlobalLease<G> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        self.global.downcast_mut().unwrap()
     }
+}
 
-    fn app_will_quit(
-        &mut self,
-        cx: &mut AppContext,
-    ) -> Option<Pin<Box<dyn 'static + Future<Output = ()>>>> {
-        self.app_will_quit(cx)
-    }
+/// Contains state associated with an active drag operation, started by dragging an element
+/// within the window or by dragging into the app from the underlying platform.
+pub struct AnyDrag {
+    pub view: AnyView,
+    pub value: Box<dyn Any>,
+    pub cursor_offset: Point<Pixels>,
 }
 
-pub trait AnyView {
-    fn as_any(&self) -> &dyn Any;
-    fn as_any_mut(&mut self) -> &mut dyn Any;
-    fn release(&mut self, cx: &mut AppContext);
-    fn app_will_quit(
-        &mut self,
-        cx: &mut AppContext,
-    ) -> Option<Pin<Box<dyn 'static + Future<Output = ()>>>>;
-    fn ui_name(&self) -> &'static str;
-    fn render(&mut self, cx: &mut WindowContext, view_id: usize) -> Box<dyn AnyRootElement>;
-    fn focus_in<'a, 'b>(&mut self, focused_id: usize, cx: &mut WindowContext<'a>, view_id: usize);
-    fn focus_out(&mut self, focused_id: usize, cx: &mut WindowContext, view_id: usize);
-    fn key_down(&mut self, event: &KeyDownEvent, cx: &mut WindowContext, view_id: usize) -> bool;
-    fn key_up(&mut self, event: &KeyUpEvent, cx: &mut WindowContext, view_id: usize) -> bool;
-    fn modifiers_changed(
-        &mut self,
-        event: &ModifiersChangedEvent,
-        cx: &mut WindowContext,
-        view_id: usize,
-    ) -> bool;
-    fn update_keymap_context(&self, keymap: &mut KeymapContext, cx: &AppContext);
-    fn debug_json(&self, cx: &WindowContext) -> serde_json::Value;
-
-    fn text_for_range(&self, range: Range<usize>, cx: &WindowContext) -> Option<String>;
-    fn selected_text_range(&self, cx: &WindowContext) -> Option<Range<usize>>;
-    fn marked_text_range(&self, cx: &WindowContext) -> Option<Range<usize>>;
-    fn unmark_text(&mut self, cx: &mut WindowContext, view_id: usize);
-    fn replace_text_in_range(
-        &mut self,
-        range: Option<Range<usize>>,
-        text: &str,
-        cx: &mut WindowContext,
-        view_id: usize,
-    );
-    fn replace_and_mark_text_in_range(
-        &mut self,
-        range: Option<Range<usize>>,
-        new_text: &str,
-        new_selected_range: Option<Range<usize>>,
-        cx: &mut WindowContext,
-        view_id: usize,
-    );
-    fn any_handle(
-        &self,
-        window: AnyWindowHandle,
-        view_id: usize,
-        cx: &AppContext,
-    ) -> AnyViewHandle {
-        AnyViewHandle::new(
-            window,
-            view_id,
-            self.as_any().type_id(),
-            cx.ref_counts.clone(),
-        )
-    }
+#[derive(Clone)]
+pub(crate) struct AnyTooltip {
+    pub view: AnyView,
+    pub cursor_offset: Point<Pixels>,
 }
 
-impl<V: View> AnyView for V {
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
-    fn as_any_mut(&mut self) -> &mut dyn Any {
-        self
-    }
-
-    fn release(&mut self, cx: &mut AppContext) {
-        self.release(cx);
-    }
-
-    fn app_will_quit(
-        &mut self,
-        cx: &mut AppContext,
-    ) -> Option<Pin<Box<dyn 'static + Future<Output = ()>>>> {
-        self.app_will_quit(cx)
-    }
-
-    fn ui_name(&self) -> &'static str {
-        V::ui_name()
-    }
-
-    fn render(&mut self, cx: &mut WindowContext, view_id: usize) -> Box<dyn AnyRootElement> {
-        let mut view_context = ViewContext::mutable(cx, view_id);
-        let element = V::render(self, &mut view_context);
-        let view = WeakViewHandle::new(cx.window_handle, view_id);
-        Box::new(RootElement::new(element, view))
-    }
-
-    fn focus_in(&mut self, focused_id: usize, cx: &mut WindowContext, view_id: usize) {
-        let mut cx = ViewContext::mutable(cx, view_id);
-        let focused_view_handle: AnyViewHandle = if view_id == focused_id {
-            cx.handle().into_any()
-        } else {
-            let focused_type = cx
-                .views_metadata
-                .get(&(cx.window_handle, focused_id))
-                .unwrap()
-                .type_id;
-            AnyViewHandle::new(
-                cx.window_handle,
-                focused_id,
-                focused_type,
-                cx.ref_counts.clone(),
-            )
-        };
-        View::focus_in(self, focused_view_handle, &mut cx);
-    }
-
-    fn focus_out(&mut self, blurred_id: usize, cx: &mut WindowContext, view_id: usize) {
-        let mut cx = ViewContext::mutable(cx, view_id);
-        let blurred_view_handle: AnyViewHandle = if view_id == blurred_id {
-            cx.handle().into_any()
-        } else {
-            let blurred_type = cx
-                .views_metadata
-                .get(&(cx.window_handle, blurred_id))
-                .unwrap()
-                .type_id;
-            AnyViewHandle::new(
-                cx.window_handle,
-                blurred_id,
-                blurred_type,
-                cx.ref_counts.clone(),
-            )
-        };
-        View::focus_out(self, blurred_view_handle, &mut cx);
-    }
-
-    fn key_down(&mut self, event: &KeyDownEvent, cx: &mut WindowContext, view_id: usize) -> bool {
-        let mut cx = ViewContext::mutable(cx, view_id);
-        View::key_down(self, event, &mut cx)
-    }
-
-    fn key_up(&mut self, event: &KeyUpEvent, cx: &mut WindowContext, view_id: usize) -> bool {
-        let mut cx = ViewContext::mutable(cx, view_id);
-        View::key_up(self, event, &mut cx)
-    }
-
-    fn modifiers_changed(
-        &mut self,
-        event: &ModifiersChangedEvent,
-        cx: &mut WindowContext,
-        view_id: usize,
-    ) -> bool {
-        let mut cx = ViewContext::mutable(cx, view_id);
-        View::modifiers_changed(self, event, &mut cx)
-    }
-
-    fn update_keymap_context(&self, keymap: &mut KeymapContext, cx: &AppContext) {
-        View::update_keymap_context(self, keymap, cx)
-    }
-
-    fn debug_json(&self, cx: &WindowContext) -> serde_json::Value {
-        View::debug_json(self, cx)
-    }
-
-    fn text_for_range(&self, range: Range<usize>, cx: &WindowContext) -> Option<String> {
-        View::text_for_range(self, range, cx)
-    }
-
-    fn selected_text_range(&self, cx: &WindowContext) -> Option<Range<usize>> {
-        View::selected_text_range(self, cx)
-    }
-
-    fn marked_text_range(&self, cx: &WindowContext) -> Option<Range<usize>> {
-        View::marked_text_range(self, cx)
-    }
-
-    fn unmark_text(&mut self, cx: &mut WindowContext, view_id: usize) {
-        let mut cx = ViewContext::mutable(cx, view_id);
-        View::unmark_text(self, &mut cx)
-    }
-
-    fn replace_text_in_range(
-        &mut self,
-        range: Option<Range<usize>>,
-        text: &str,
-        cx: &mut WindowContext,
-        view_id: usize,
-    ) {
-        let mut cx = ViewContext::mutable(cx, view_id);
-        View::replace_text_in_range(self, range, text, &mut cx)
-    }
-
-    fn replace_and_mark_text_in_range(
-        &mut self,
-        range: Option<Range<usize>>,
-        new_text: &str,
-        new_selected_range: Option<Range<usize>>,
-        cx: &mut WindowContext,
-        view_id: usize,
-    ) {
-        let mut cx = ViewContext::mutable(cx, view_id);
-        View::replace_and_mark_text_in_range(self, range, new_text, new_selected_range, &mut cx)
-    }
-}
-
-pub struct ModelContext<'a, T: ?Sized> {
-    app: &'a mut AppContext,
-    model_id: usize,
-    model_type: PhantomData<T>,
-    halt_stream: bool,
-}
-
-impl<'a, T: Entity> ModelContext<'a, T> {
-    fn new(app: &'a mut AppContext, model_id: usize) -> Self {
-        Self {
-            app,
-            model_id,
-            model_type: PhantomData,
-            halt_stream: false,
-        }
-    }
-
-    pub fn background(&self) -> &Arc<executor::Background> {
-        &self.app.background
-    }
-
-    pub fn halt_stream(&mut self) {
-        self.halt_stream = true;
-    }
-
-    pub fn model_id(&self) -> usize {
-        self.model_id
-    }
-
-    pub fn add_model<S, F>(&mut self, build_model: F) -> ModelHandle<S>
-    where
-        S: Entity,
-        F: FnOnce(&mut ModelContext<S>) -> S,
-    {
-        self.app.add_model(build_model)
-    }
-
-    pub fn emit(&mut self, payload: T::Event) {
-        self.app.pending_effects.push_back(Effect::Event {
-            entity_id: self.model_id,
-            payload: Box::new(payload),
-        });
-    }
-
-    pub fn notify(&mut self) {
-        self.app.notify_model(self.model_id);
-    }
-
-    pub fn subscribe<S: Entity, F>(
-        &mut self,
-        handle: &ModelHandle<S>,
-        mut callback: F,
-    ) -> Subscription
-    where
-        S::Event: 'static,
-        F: 'static + FnMut(&mut T, ModelHandle<S>, &S::Event, &mut ModelContext<T>),
-    {
-        let subscriber = self.weak_handle();
-        self.app
-            .subscribe_internal(handle, move |emitter, event, cx| {
-                if let Some(subscriber) = subscriber.upgrade(cx) {
-                    subscriber.update(cx, |subscriber, cx| {
-                        callback(subscriber, emitter, event, cx);
-                    });
-                    true
-                } else {
-                    false
-                }
-            })
-    }
-
-    pub fn observe<S, F>(&mut self, handle: &ModelHandle<S>, mut callback: F) -> Subscription
-    where
-        S: Entity,
-        F: 'static + FnMut(&mut T, ModelHandle<S>, &mut ModelContext<T>),
-    {
-        let observer = self.weak_handle();
-        self.app.observe_internal(handle, move |observed, cx| {
-            if let Some(observer) = observer.upgrade(cx) {
-                observer.update(cx, |observer, cx| {
-                    callback(observer, observed, cx);
-                });
-                true
-            } else {
-                false
-            }
-        })
-    }
-
-    pub fn observe_global<G, F>(&mut self, mut callback: F) -> Subscription
-    where
-        G: Any,
-        F: 'static + FnMut(&mut T, &mut ModelContext<T>),
-    {
-        let observer = self.weak_handle();
-        self.app.observe_global::<G, _>(move |cx| {
-            if let Some(observer) = observer.upgrade(cx) {
-                observer.update(cx, |observer, cx| callback(observer, cx));
-            }
-        })
-    }
-
-    pub fn observe_release<S, F>(
-        &mut self,
-        handle: &ModelHandle<S>,
-        mut callback: F,
-    ) -> Subscription
-    where
-        S: Entity,
-        F: 'static + FnMut(&mut T, &S, &mut ModelContext<T>),
-    {
-        let observer = self.weak_handle();
-        self.app.observe_release(handle, move |released, cx| {
-            if let Some(observer) = observer.upgrade(cx) {
-                observer.update(cx, |observer, cx| {
-                    callback(observer, released, cx);
-                });
-            }
-        })
-    }
-
-    pub fn handle(&self) -> ModelHandle<T> {
-        ModelHandle::new(self.model_id, &self.app.ref_counts)
-    }
-
-    pub fn weak_handle(&self) -> WeakModelHandle<T> {
-        WeakModelHandle::new(self.model_id)
-    }
-
-    pub fn spawn<F, Fut, S>(&mut self, f: F) -> Task<S>
-    where
-        F: FnOnce(ModelHandle<T>, AsyncAppContext) -> Fut,
-        Fut: 'static + Future<Output = S>,
-        S: 'static,
-    {
-        let handle = self.handle();
-        self.app.spawn(|cx| f(handle, cx))
-    }
-
-    pub fn spawn_weak<F, Fut, S>(&mut self, f: F) -> Task<S>
-    where
-        F: FnOnce(WeakModelHandle<T>, AsyncAppContext) -> Fut,
-        Fut: 'static + Future<Output = S>,
-        S: 'static,
-    {
-        let handle = self.weak_handle();
-        self.app.spawn(|cx| f(handle, cx))
-    }
-}
-
-impl<M> AsRef<AppContext> for ModelContext<'_, M> {
-    fn as_ref(&self) -> &AppContext {
-        &self.app
-    }
-}
-
-impl<M> AsMut<AppContext> for ModelContext<'_, M> {
-    fn as_mut(&mut self) -> &mut AppContext {
-        self.app
-    }
-}
-
-impl<M> BorrowAppContext for ModelContext<'_, M> {
-    fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
-        self.app.read_with(f)
-    }
-
-    fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
-        self.app.update(f)
-    }
-}
-
-impl<M> Deref for ModelContext<'_, M> {
-    type Target = AppContext;
-
-    fn deref(&self) -> &Self::Target {
-        self.app
-    }
-}
-
-impl<M> DerefMut for ModelContext<'_, M> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.app
-    }
-}
-
-pub struct ViewContext<'a, 'b, T: ?Sized> {
-    window_context: Reference<'b, WindowContext<'a>>,
-    view_id: usize,
-    view_type: PhantomData<T>,
-}
-
-impl<'a, 'b, V> Deref for ViewContext<'a, 'b, V> {
-    type Target = WindowContext<'a>;
-
-    fn deref(&self) -> &Self::Target {
-        &self.window_context
-    }
-}
-
-impl<'a, 'b, V> DerefMut for ViewContext<'a, 'b, V> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.window_context
-    }
-}
-
-impl<'a, 'b, V: 'static> ViewContext<'a, 'b, V> {
-    pub fn mutable(window_context: &'b mut WindowContext<'a>, view_id: usize) -> Self {
-        Self {
-            window_context: Reference::Mutable(window_context),
-            view_id,
-            view_type: PhantomData,
-        }
-    }
-
-    pub fn immutable(window_context: &'b WindowContext<'a>, view_id: usize) -> Self {
-        Self {
-            window_context: Reference::Immutable(window_context),
-            view_id,
-            view_type: PhantomData,
-        }
-    }
-
-    pub fn window_context(&mut self) -> &mut WindowContext<'a> {
-        &mut self.window_context
-    }
-
-    pub fn notify(&mut self) {
-        let window = self.window_handle;
-        let view_id = self.view_id;
-        self.window_context.notify_view(window, view_id);
-    }
-
-    pub fn handle(&self) -> ViewHandle<V> {
-        ViewHandle::new(
-            self.window_handle,
-            self.view_id,
-            &self.window_context.ref_counts,
-        )
-    }
-
-    pub fn weak_handle(&self) -> WeakViewHandle<V> {
-        WeakViewHandle::new(self.window_handle, self.view_id)
-    }
-
-    pub fn window(&self) -> AnyWindowHandle {
-        self.window_handle
-    }
-
-    pub fn view_id(&self) -> usize {
-        self.view_id
-    }
-
-    pub fn foreground(&self) -> &Rc<executor::Foreground> {
-        self.window_context.foreground()
-    }
-
-    pub fn background_executor(&self) -> &Arc<executor::Background> {
-        &self.window_context.background
-    }
-
-    pub fn platform(&self) -> &Arc<dyn Platform> {
-        self.window_context.platform()
-    }
-
-    pub fn prompt_for_paths(
-        &self,
-        options: PathPromptOptions,
-    ) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
-        self.window_context.prompt_for_paths(options)
-    }
-
-    pub fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> {
-        self.window_context.prompt_for_new_path(directory)
-    }
-
-    pub fn reveal_path(&self, path: &Path) {
-        self.window_context.reveal_path(path)
-    }
-
-    pub fn focus(&mut self, handle: &AnyViewHandle) {
-        self.window_context.focus(Some(handle.view_id));
-    }
-
-    pub fn focus_self(&mut self) {
-        let view_id = self.view_id;
-        self.window_context.focus(Some(view_id));
-    }
-
-    pub fn is_self_focused(&self) -> bool {
-        self.window.focused_view_id == Some(self.view_id)
-    }
-
-    pub fn focus_parent(&mut self) {
-        let window = self.window_handle;
-        let view_id = self.view_id;
-        self.pending_effects
-            .push_back(Effect::Focus(FocusEffect::ViewParent {
-                window,
-                view_id,
-                is_forced: false,
-            }));
-    }
-
-    pub fn blur(&mut self) {
-        self.window_context.focus(None);
-    }
-
-    pub fn on_window_should_close<F>(&mut self, mut callback: F)
-    where
-        F: 'static + FnMut(&mut V, &mut ViewContext<V>) -> bool,
-    {
-        let window = self.window_handle;
-        let view = self.weak_handle();
-        self.pending_effects
-            .push_back(Effect::WindowShouldCloseSubscription {
-                window,
-                callback: Box::new(move |cx| {
-                    cx.update_window(window, |cx| {
-                        if let Some(view) = view.upgrade(cx) {
-                            view.update(cx, |view, cx| callback(view, cx))
-                        } else {
-                            true
-                        }
-                    })
-                    .unwrap_or(true)
-                }),
-            });
-    }
-
-    pub fn subscribe<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
-    where
-        E: Entity,
-        E::Event: 'static,
-        H: Handle<E>,
-        F: 'static + FnMut(&mut V, H, &E::Event, &mut ViewContext<V>),
-    {
-        let subscriber = self.weak_handle();
-        self.window_context
-            .subscribe_internal(handle, move |emitter, event, cx| {
-                if let Some(subscriber) = subscriber.upgrade(cx) {
-                    subscriber.update(cx, |subscriber, cx| {
-                        callback(subscriber, emitter, event, cx);
-                    });
-                    true
-                } else {
-                    false
-                }
-            })
-    }
-
-    pub fn observe<E, F, H>(&mut self, handle: &H, mut callback: F) -> Subscription
-    where
-        E: Entity,
-        H: Handle<E>,
-        F: 'static + FnMut(&mut V, H, &mut ViewContext<V>),
-    {
-        let window = self.window_handle;
-        let observer = self.weak_handle();
-        self.window_context
-            .observe_internal(handle, move |observed, cx| {
-                cx.update_window(window, |cx| {
-                    if let Some(observer) = observer.upgrade(cx) {
-                        observer.update(cx, |observer, cx| {
-                            callback(observer, observed, cx);
-                        });
-                        true
-                    } else {
-                        false
-                    }
-                })
-                .unwrap_or(false)
-            })
-    }
-
-    pub fn observe_global<G, F>(&mut self, mut callback: F) -> Subscription
-    where
-        G: Any,
-        F: 'static + FnMut(&mut V, &mut ViewContext<V>),
-    {
-        let window = self.window_handle;
-        let observer = self.weak_handle();
-        self.window_context.observe_global::<G, _>(move |cx| {
-            cx.update_window(window, |cx| {
-                if let Some(observer) = observer.upgrade(cx) {
-                    observer.update(cx, |observer, cx| callback(observer, cx));
-                }
-            });
-        })
-    }
-
-    pub fn observe_focus<F, W>(&mut self, handle: &ViewHandle<W>, mut callback: F) -> Subscription
-    where
-        F: 'static + FnMut(&mut V, ViewHandle<W>, bool, &mut ViewContext<V>),
-        W: View,
-    {
-        let observer = self.weak_handle();
-        self.window_context
-            .observe_focus(handle, move |observed, focused, cx| {
-                if let Some(observer) = observer.upgrade(cx) {
-                    observer.update(cx, |observer, cx| {
-                        callback(observer, observed, focused, cx);
-                    });
-                    true
-                } else {
-                    false
-                }
-            })
-    }
-
-    pub fn observe_release<E, F, H>(&mut self, handle: &H, mut callback: F) -> Subscription
-    where
-        E: Entity,
-        H: Handle<E>,
-        F: 'static + FnMut(&mut V, &E, &mut ViewContext<V>),
-    {
-        let window = self.window_handle;
-        let observer = self.weak_handle();
-        self.window_context
-            .observe_release(handle, move |released, cx| {
-                cx.update_window(window, |cx| {
-                    if let Some(observer) = observer.upgrade(cx) {
-                        observer.update(cx, |observer, cx| {
-                            callback(observer, released, cx);
-                        });
-                    }
-                });
-            })
-    }
-
-    pub fn observe_actions<F>(&mut self, mut callback: F) -> Subscription
-    where
-        F: 'static + FnMut(&mut V, TypeId, &mut ViewContext<V>),
-    {
-        let window = self.window_handle;
-        let observer = self.weak_handle();
-        self.window_context.observe_actions(move |action_id, cx| {
-            cx.update_window(window, |cx| {
-                if let Some(observer) = observer.upgrade(cx) {
-                    observer.update(cx, |observer, cx| {
-                        callback(observer, action_id, cx);
-                    });
-                }
-            });
-        })
-    }
-
-    pub fn observe_window_activation<F>(&mut self, mut callback: F) -> Subscription
-    where
-        F: 'static + FnMut(&mut V, bool, &mut ViewContext<V>),
-    {
-        let observer = self.weak_handle();
-        self.window_context
-            .observe_window_activation(move |active, cx| {
-                if let Some(observer) = observer.upgrade(cx) {
-                    observer.update(cx, |observer, cx| {
-                        callback(observer, active, cx);
-                    });
-                    true
-                } else {
-                    false
-                }
-            })
-    }
-
-    pub fn observe_fullscreen<F>(&mut self, mut callback: F) -> Subscription
-    where
-        F: 'static + FnMut(&mut V, bool, &mut ViewContext<V>),
-    {
-        let observer = self.weak_handle();
-        self.window_context.observe_fullscreen(move |active, cx| {
-            if let Some(observer) = observer.upgrade(cx) {
-                observer.update(cx, |observer, cx| {
-                    callback(observer, active, cx);
-                });
-                true
-            } else {
-                false
-            }
-        })
-    }
-
-    pub fn observe_keystrokes<F>(&mut self, mut callback: F) -> Subscription
-    where
-        F: 'static
-            + FnMut(
-                &mut V,
-                &Keystroke,
-                Option<&Box<dyn Action>>,
-                &MatchResult,
-                &mut ViewContext<V>,
-            ) -> bool,
-    {
-        let observer = self.weak_handle();
-        self.window_context
-            .observe_keystrokes(move |keystroke, result, handled_by, cx| {
-                if let Some(observer) = observer.upgrade(cx) {
-                    observer.update(cx, |observer, cx| {
-                        callback(observer, keystroke, handled_by, result, cx);
-                    });
-                    true
-                } else {
-                    false
-                }
-            })
-    }
-
-    pub fn observe_window_bounds<F>(&mut self, mut callback: F) -> Subscription
-    where
-        F: 'static + FnMut(&mut V, WindowBounds, Uuid, &mut ViewContext<V>),
-    {
-        let observer = self.weak_handle();
-        self.window_context
-            .observe_window_bounds(move |bounds, display, cx| {
-                if let Some(observer) = observer.upgrade(cx) {
-                    observer.update(cx, |observer, cx| {
-                        callback(observer, bounds, display, cx);
-                    });
-                    true
-                } else {
-                    false
-                }
-            })
-    }
-
-    pub fn observe_active_labeled_tasks<F>(&mut self, mut callback: F) -> Subscription
-    where
-        F: 'static + FnMut(&mut V, &mut ViewContext<V>),
-    {
-        let window = self.window_handle;
-        let observer = self.weak_handle();
-        self.window_context.observe_active_labeled_tasks(move |cx| {
-            cx.update_window(window, |cx| {
-                if let Some(observer) = observer.upgrade(cx) {
-                    observer.update(cx, |observer, cx| {
-                        callback(observer, cx);
-                    });
-                    true
-                } else {
-                    false
-                }
-            })
-            .unwrap_or(false)
-        })
-    }
-
-    pub fn defer(&mut self, callback: impl 'static + FnOnce(&mut V, &mut ViewContext<V>)) {
-        let handle = self.handle();
-        self.window_context
-            .defer(move |cx| handle.update(cx, |view, cx| callback(view, cx)))
-    }
-
-    pub fn after_window_update(
-        &mut self,
-        callback: impl 'static + FnOnce(&mut V, &mut ViewContext<V>),
-    ) {
-        let window = self.window_handle;
-        let handle = self.handle();
-        self.window_context.after_window_update(move |cx| {
-            cx.update_window(window, |cx| {
-                handle.update(cx, |view, cx| {
-                    callback(view, cx);
-                })
-            });
-        })
-    }
-
-    pub fn propagate_action(&mut self) {
-        self.window_context.halt_action_dispatch = false;
-    }
-
-    pub fn spawn_labeled<F, Fut, S>(&mut self, task_label: &'static str, f: F) -> Task<S>
-    where
-        F: FnOnce(WeakViewHandle<V>, AsyncAppContext) -> Fut,
-        Fut: 'static + Future<Output = S>,
-        S: 'static,
-    {
-        let handle = self.weak_handle();
-        self.window_context
-            .spawn_labeled(task_label, |cx| f(handle, cx))
-    }
-
-    pub fn spawn<F, Fut, S>(&mut self, f: F) -> Task<S>
-    where
-        F: FnOnce(WeakViewHandle<V>, AsyncAppContext) -> Fut,
-        Fut: 'static + Future<Output = S>,
-        S: 'static,
-    {
-        let handle = self.weak_handle();
-        self.window_context.spawn(|cx| f(handle, cx))
-    }
-
-    pub fn mouse_state<Tag: 'static>(&self, region_id: usize) -> MouseState {
-        self.mouse_state_dynamic(TypeTag::new::<Tag>(), region_id)
-    }
-
-    pub fn mouse_state_dynamic(&self, tag: TypeTag, region_id: usize) -> MouseState {
-        let region_id = MouseRegionId::new(tag, self.view_id, region_id);
-        MouseState {
-            hovered: self.window.hovered_region_ids.contains(&region_id),
-            mouse_down: !self.window.clicked_region_ids.is_empty(),
-            clicked: self
-                .window
-                .clicked_region_ids
-                .iter()
-                .find(|click_region_id| **click_region_id == region_id)
-                // If we've gotten here, there should always be a clicked region.
-                // But let's be defensive and return None if there isn't.
-                .and_then(|_| self.window.clicked_region.map(|(_, button)| button)),
-            accessed_hovered: false,
-            accessed_clicked: false,
-        }
-    }
-
-    pub fn element_state<Tag: 'static, T: 'static>(
-        &mut self,
-        element_id: usize,
-        initial: T,
-    ) -> ElementStateHandle<T> {
-        self.element_state_dynamic(TypeTag::new::<Tag>(), element_id, initial)
-    }
-
-    pub fn element_state_dynamic<T: 'static>(
-        &mut self,
-        tag: TypeTag,
-        element_id: usize,
-        initial: T,
-    ) -> ElementStateHandle<T> {
-        let id = ElementStateId {
-            view_id: self.view_id(),
-            element_id,
-            tag,
-        };
-        self.element_states
-            .entry(id)
-            .or_insert_with(|| Box::new(initial));
-        ElementStateHandle::new(id, self.frame_count, &self.ref_counts)
-    }
-
-    pub fn default_element_state<Tag: 'static, T: 'static + Default>(
-        &mut self,
-        element_id: usize,
-    ) -> ElementStateHandle<T> {
-        self.element_state::<Tag, T>(element_id, T::default())
-    }
-
-    pub fn default_element_state_dynamic<T: 'static + Default>(
-        &mut self,
-        tag: TypeTag,
-        element_id: usize,
-    ) -> ElementStateHandle<T> {
-        self.element_state_dynamic::<T>(tag, element_id, T::default())
-    }
-
-    /// Return keystrokes that would dispatch the given action on the given view.
-    pub(crate) fn keystrokes_for_action(
-        &mut self,
-        view_id: usize,
-        action: &dyn Action,
-    ) -> Option<SmallVec<[Keystroke; 2]>> {
-        self.notify_if_view_ancestors_change(view_id);
-
-        let window = self.window_handle;
-        let mut contexts = Vec::new();
-        let mut handler_depth = None;
-        for (i, view_id) in self.ancestors(view_id).enumerate() {
-            if let Some(view_metadata) = self.views_metadata.get(&(window, view_id)) {
-                if let Some(actions) = self.actions.get(&view_metadata.type_id) {
-                    if actions.contains_key(&action.id()) {
-                        handler_depth = Some(i);
-                    }
-                }
-                contexts.push(view_metadata.keymap_context.clone());
-            }
-        }
-
-        if self.global_actions.contains_key(&action.id()) {
-            handler_depth = Some(contexts.len())
-        }
-
-        let handler_depth = handler_depth.unwrap_or(0);
-        (0..=handler_depth).find_map(|depth| {
-            let contexts = &contexts[depth..];
-            self.keystroke_matcher
-                .keystrokes_for_action(action, contexts)
-        })
-    }
-
-    fn notify_if_view_ancestors_change(&mut self, view_id: usize) {
-        let self_view_id = self.view_id;
-        self.window
-            .views_to_notify_if_ancestors_change
-            .entry(view_id)
-            .or_default()
-            .push(self_view_id);
-    }
-
-    pub fn paint_layer<F, R>(&mut self, clip_bounds: Option<RectF>, f: F) -> R
-    where
-        F: FnOnce(&mut Self) -> R,
-    {
-        self.scene().push_layer(clip_bounds);
-        let result = f(self);
-        self.scene().pop_layer();
-        result
-    }
-}
-
-impl<V: View> ViewContext<'_, '_, V> {
-    pub fn emit(&mut self, event: V::Event) {
-        self.window_context
-            .pending_effects
-            .push_back(Effect::Event {
-                entity_id: self.view_id,
-                payload: Box::new(event),
-            });
-    }
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
-pub struct TypeTag {
-    tag: TypeId,
-    composed: Option<TypeId>,
-    #[cfg(debug_assertions)]
-    tag_type_name: &'static str,
-}
-
-impl TypeTag {
-    pub fn new<Tag: 'static>() -> Self {
-        Self {
-            tag: TypeId::of::<Tag>(),
-            composed: None,
-            #[cfg(debug_assertions)]
-            tag_type_name: std::any::type_name::<Tag>(),
-        }
-    }
-
-    pub fn dynamic(tag: TypeId, #[cfg(debug_assertions)] type_name: &'static str) -> Self {
-        Self {
-            tag,
-            composed: None,
-            #[cfg(debug_assertions)]
-            tag_type_name: type_name,
-        }
-    }
-
-    pub fn compose(mut self, other: TypeTag) -> Self {
-        self.composed = Some(other.tag);
-        self
-    }
-
-    #[cfg(debug_assertions)]
-    pub(crate) fn type_name(&self) -> &'static str {
-        self.tag_type_name
-    }
-}
-
-impl<V> BorrowAppContext for ViewContext<'_, '_, V> {
-    fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
-        BorrowAppContext::read_with(&*self.window_context, f)
-    }
-
-    fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
-        BorrowAppContext::update(&mut *self.window_context, f)
-    }
-}
-
-impl<V> BorrowWindowContext for ViewContext<'_, '_, V> {
-    type Result<T> = T;
-
-    fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, window: AnyWindowHandle, f: F) -> T {
-        BorrowWindowContext::read_window(&*self.window_context, window, f)
-    }
-
-    fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
-    where
-        F: FnOnce(&WindowContext) -> Option<T>,
-    {
-        BorrowWindowContext::read_window_optional(&*self.window_context, window, f)
-    }
-
-    fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
-        &mut self,
-        window: AnyWindowHandle,
-        f: F,
-    ) -> T {
-        BorrowWindowContext::update_window(&mut *self.window_context, window, f)
-    }
-
-    fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
-    where
-        F: FnOnce(&mut WindowContext) -> Option<T>,
-    {
-        BorrowWindowContext::update_window_optional(&mut *self.window_context, window, f)
-    }
-}
-
-pub struct EventContext<'a, 'b, 'c, V> {
-    view_context: &'c mut ViewContext<'a, 'b, V>,
-    pub(crate) handled: bool,
-    // I would like to replace handled with this.
-    // Being additive for now.
-    pub bubble: bool,
-}
-
-impl<'a, 'b, 'c, V: 'static> EventContext<'a, 'b, 'c, V> {
-    pub fn new(view_context: &'c mut ViewContext<'a, 'b, V>) -> Self {
-        EventContext {
-            view_context,
-            handled: true,
-            bubble: false,
-        }
-    }
-
-    pub fn propagate_event(&mut self) {
-        self.handled = false;
-    }
-
-    pub fn bubble_event(&mut self) {
-        self.bubble = true;
-    }
-
-    pub fn event_bubbled(&self) -> bool {
-        self.bubble
-    }
-}
-
-impl<'a, 'b, 'c, V> Deref for EventContext<'a, 'b, 'c, V> {
-    type Target = ViewContext<'a, 'b, V>;
-
-    fn deref(&self) -> &Self::Target {
-        &self.view_context
-    }
-}
-
-impl<V> DerefMut for EventContext<'_, '_, '_, V> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.view_context
-    }
-}
-
-impl<V> BorrowAppContext for EventContext<'_, '_, '_, V> {
-    fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
-        BorrowAppContext::read_with(&*self.view_context, f)
-    }
-
-    fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
-        BorrowAppContext::update(&mut *self.view_context, f)
-    }
-}
-
-impl<V> BorrowWindowContext for EventContext<'_, '_, '_, V> {
-    type Result<T> = T;
-
-    fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, window: AnyWindowHandle, f: F) -> T {
-        BorrowWindowContext::read_window(&*self.view_context, window, f)
-    }
-
-    fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
-    where
-        F: FnOnce(&WindowContext) -> Option<T>,
-    {
-        BorrowWindowContext::read_window_optional(&*self.view_context, window, f)
-    }
-
-    fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
-        &mut self,
-        window: AnyWindowHandle,
-        f: F,
-    ) -> T {
-        BorrowWindowContext::update_window(&mut *self.view_context, window, f)
-    }
-
-    fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
-    where
-        F: FnOnce(&mut WindowContext) -> Option<T>,
-    {
-        BorrowWindowContext::update_window_optional(&mut *self.view_context, window, f)
-    }
-}
-
-pub enum Reference<'a, T> {
-    Immutable(&'a T),
-    Mutable(&'a mut T),
-}
-
-impl<'a, T> Deref for Reference<'a, T> {
-    type Target = T;
-
-    fn deref(&self) -> &Self::Target {
-        match self {
-            Reference::Immutable(target) => target,
-            Reference::Mutable(target) => target,
-        }
-    }
-}
-
-impl<'a, T> DerefMut for Reference<'a, T> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        match self {
-            Reference::Immutable(_) => {
-                panic!("cannot mutably deref an immutable reference. this is a bug in GPUI.");
-            }
-            Reference::Mutable(target) => target,
-        }
-    }
-}
-
-#[derive(Debug, Clone, Default)]
-pub struct MouseState {
-    pub(crate) hovered: bool,
-    pub(crate) clicked: Option<MouseButton>,
-    pub(crate) mouse_down: bool,
-    pub(crate) accessed_hovered: bool,
-    pub(crate) accessed_clicked: bool,
-}
-
-impl MouseState {
-    pub fn dragging(&mut self) -> bool {
-        self.accessed_hovered = true;
-        self.hovered && self.mouse_down
-    }
-
-    pub fn hovered(&mut self) -> bool {
-        self.accessed_hovered = true;
-        self.hovered && (!self.mouse_down || self.clicked.is_some())
-    }
-
-    pub fn clicked(&mut self) -> Option<MouseButton> {
-        self.accessed_clicked = true;
-        self.clicked
-    }
-
-    pub fn accessed_hovered(&self) -> bool {
-        self.accessed_hovered
-    }
-
-    pub fn accessed_clicked(&self) -> bool {
-        self.accessed_clicked
-    }
-}
-
-pub trait Handle<T> {
-    type Weak: 'static;
-    fn id(&self) -> usize;
-    fn location(&self) -> EntityLocation;
-    fn downgrade(&self) -> Self::Weak;
-    fn upgrade_from(weak: &Self::Weak, cx: &AppContext) -> Option<Self>
-    where
-        Self: Sized;
-}
-
-pub trait WeakHandle {
-    fn id(&self) -> usize;
-}
-
-#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
-pub enum EntityLocation {
-    Model(usize),
-    View(usize, usize),
-}
-
-pub struct ModelHandle<T: Entity> {
-    any_handle: AnyModelHandle,
-    model_type: PhantomData<T>,
-}
-
-impl<T: Entity> Deref for ModelHandle<T> {
-    type Target = AnyModelHandle;
-
-    fn deref(&self) -> &Self::Target {
-        &self.any_handle
-    }
-}
-
-impl<T: Entity> ModelHandle<T> {
-    fn new(model_id: usize, ref_counts: &Arc<Mutex<RefCounts>>) -> Self {
-        Self {
-            any_handle: AnyModelHandle::new(model_id, TypeId::of::<T>(), ref_counts.clone()),
-            model_type: PhantomData,
-        }
-    }
-
-    pub fn downgrade(&self) -> WeakModelHandle<T> {
-        WeakModelHandle::new(self.model_id)
-    }
-
-    pub fn id(&self) -> usize {
-        self.model_id
-    }
-
-    pub fn read<'a>(&self, cx: &'a AppContext) -> &'a T {
-        cx.read_model(self)
-    }
-
-    pub fn read_with<C, F, S>(&self, cx: &C, read: F) -> S
-    where
-        C: BorrowAppContext,
-        F: FnOnce(&T, &AppContext) -> S,
-    {
-        cx.read_with(|cx| read(self.read(cx), cx))
-    }
-
-    pub fn update<C, F, S>(&self, cx: &mut C, update: F) -> S
-    where
-        C: BorrowAppContext,
-        F: FnOnce(&mut T, &mut ModelContext<T>) -> S,
-    {
-        let mut update = Some(update);
-        cx.update(|cx| {
-            cx.update_model(self, &mut |model, cx| {
-                let update = update.take().unwrap();
-                update(model, cx)
-            })
-        })
-    }
-}
-
-impl<T: Entity> Clone for ModelHandle<T> {
-    fn clone(&self) -> Self {
-        Self::new(self.model_id, &self.ref_counts)
-    }
-}
-
-impl<T: Entity> PartialEq for ModelHandle<T> {
-    fn eq(&self, other: &Self) -> bool {
-        self.model_id == other.model_id
-    }
-}
-
-impl<T: Entity> Eq for ModelHandle<T> {}
-
-impl<T: Entity> PartialEq<WeakModelHandle<T>> for ModelHandle<T> {
-    fn eq(&self, other: &WeakModelHandle<T>) -> bool {
-        self.model_id == other.model_id
-    }
-}
-
-impl<T: Entity> Hash for ModelHandle<T> {
-    fn hash<H: Hasher>(&self, state: &mut H) {
-        self.model_id.hash(state);
-    }
-}
-
-impl<T: Entity> std::borrow::Borrow<usize> for ModelHandle<T> {
-    fn borrow(&self) -> &usize {
-        &self.model_id
-    }
-}
-
-impl<T: Entity> Debug for ModelHandle<T> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_tuple(&format!("ModelHandle<{}>", type_name::<T>()))
-            .field(&self.model_id)
-            .finish()
-    }
-}
-
-unsafe impl<T: Entity> Send for ModelHandle<T> {}
-unsafe impl<T: Entity> Sync for ModelHandle<T> {}
-
-impl<T: Entity> Handle<T> for ModelHandle<T> {
-    type Weak = WeakModelHandle<T>;
-
-    fn id(&self) -> usize {
-        self.model_id
-    }
-
-    fn location(&self) -> EntityLocation {
-        EntityLocation::Model(self.model_id)
-    }
-
-    fn downgrade(&self) -> Self::Weak {
-        self.downgrade()
-    }
-
-    fn upgrade_from(weak: &Self::Weak, cx: &AppContext) -> Option<Self>
-    where
-        Self: Sized,
-    {
-        weak.upgrade(cx)
-    }
-}
-
-pub struct WeakModelHandle<T> {
-    any_handle: AnyWeakModelHandle,
-    model_type: PhantomData<T>,
-}
-
-impl<T> WeakModelHandle<T> {
-    pub fn into_any(self) -> AnyWeakModelHandle {
-        self.any_handle
-    }
-}
-
-impl<T> Deref for WeakModelHandle<T> {
-    type Target = AnyWeakModelHandle;
-
-    fn deref(&self) -> &Self::Target {
-        &self.any_handle
-    }
-}
-
-impl<T> WeakHandle for WeakModelHandle<T> {
-    fn id(&self) -> usize {
-        self.model_id
-    }
-}
-
-unsafe impl<T> Send for WeakModelHandle<T> {}
-unsafe impl<T> Sync for WeakModelHandle<T> {}
-
-impl<T: Entity> WeakModelHandle<T> {
-    fn new(model_id: usize) -> Self {
-        Self {
-            any_handle: AnyWeakModelHandle {
-                model_id,
-                model_type: TypeId::of::<T>(),
-            },
-            model_type: PhantomData,
-        }
-    }
-
-    pub fn id(&self) -> usize {
-        self.model_id
-    }
-
-    pub fn is_upgradable(&self, cx: &impl BorrowAppContext) -> bool {
-        cx.read_with(|cx| cx.model_handle_is_upgradable(self))
-    }
-
-    pub fn upgrade(&self, cx: &impl BorrowAppContext) -> Option<ModelHandle<T>> {
-        cx.read_with(|cx| cx.upgrade_model_handle(self))
-    }
-}
-
-impl<T> Hash for WeakModelHandle<T> {
-    fn hash<H: Hasher>(&self, state: &mut H) {
-        self.model_id.hash(state)
-    }
-}
-
-impl<T> PartialEq for WeakModelHandle<T> {
-    fn eq(&self, other: &Self) -> bool {
-        self.model_id == other.model_id
-    }
-}
-
-impl<T> Eq for WeakModelHandle<T> {}
-
-impl<T: Entity> PartialEq<ModelHandle<T>> for WeakModelHandle<T> {
-    fn eq(&self, other: &ModelHandle<T>) -> bool {
-        self.model_id == other.model_id
-    }
-}
-
-impl<T> Clone for WeakModelHandle<T> {
-    fn clone(&self) -> Self {
-        Self {
-            any_handle: self.any_handle.clone(),
-            model_type: PhantomData,
-        }
-    }
-}
-
-impl<T> Copy for WeakModelHandle<T> {}
-
-#[derive(Deref)]
-pub struct WindowHandle<V> {
-    #[deref]
-    any_handle: AnyWindowHandle,
-    root_view_type: PhantomData<V>,
-}
-
-impl<V> Clone for WindowHandle<V> {
-    fn clone(&self) -> Self {
-        Self {
-            any_handle: self.any_handle.clone(),
-            root_view_type: PhantomData,
-        }
-    }
-}
-
-impl<V> Copy for WindowHandle<V> {}
-
-impl<V: 'static> WindowHandle<V> {
-    fn new(window_id: usize) -> Self {
-        WindowHandle {
-            any_handle: AnyWindowHandle::new(window_id, TypeId::of::<V>()),
-            root_view_type: PhantomData,
-        }
-    }
-
-    pub fn root<C: BorrowWindowContext>(&self, cx: &C) -> C::Result<ViewHandle<V>> {
-        self.read_with(cx, |cx| cx.root_view().clone().downcast().unwrap())
-    }
-
-    pub fn read_root_with<C, F, R>(&self, cx: &C, read: F) -> C::Result<R>
-    where
-        C: BorrowWindowContext,
-        F: FnOnce(&V, &ViewContext<V>) -> R,
-    {
-        self.read_with(cx, |cx| {
-            cx.root_view()
-                .downcast_ref::<V>()
-                .unwrap()
-                .read_with(cx, read)
-        })
-    }
-
-    pub fn update_root<C, F, R>(&self, cx: &mut C, update: F) -> C::Result<R>
-    where
-        C: BorrowWindowContext,
-        F: FnOnce(&mut V, &mut ViewContext<V>) -> R,
-    {
-        cx.update_window(self.any_handle, |cx| {
-            cx.root_view()
-                .clone()
-                .downcast::<V>()
-                .unwrap()
-                .update(cx, update)
-        })
-    }
-}
-
-impl<V: View> WindowHandle<V> {
-    pub fn replace_root<C, F>(&self, cx: &mut C, build_root: F) -> C::Result<ViewHandle<V>>
-    where
-        C: BorrowWindowContext,
-        F: FnOnce(&mut ViewContext<V>) -> V,
-    {
-        cx.update_window(self.any_handle, |cx| {
-            let root_view = self.add_view(cx, |cx| build_root(cx));
-            cx.window.root_view = Some(root_view.clone().into_any());
-            cx.window.focused_view_id = Some(root_view.id());
-            root_view
-        })
-    }
-}
-
-impl<V> Into<AnyWindowHandle> for WindowHandle<V> {
-    fn into(self) -> AnyWindowHandle {
-        self.any_handle
-    }
-}
-
-#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
-pub struct AnyWindowHandle {
-    window_id: usize,
-    root_view_type: TypeId,
-}
-
-impl AnyWindowHandle {
-    fn new(window_id: usize, root_view_type: TypeId) -> Self {
-        Self {
-            window_id,
-            root_view_type,
-        }
-    }
-
-    pub fn id(&self) -> usize {
-        self.window_id
-    }
-
-    pub fn read_with<C, F, R>(&self, cx: &C, read: F) -> C::Result<R>
-    where
-        C: BorrowWindowContext,
-        F: FnOnce(&WindowContext) -> R,
-    {
-        cx.read_window(*self, |cx| read(cx))
-    }
-
-    pub fn read_optional_with<C, F, R>(&self, cx: &C, read: F) -> Option<R>
-    where
-        C: BorrowWindowContext,
-        F: FnOnce(&WindowContext) -> Option<R>,
-    {
-        cx.read_window_optional(*self, |cx| read(cx))
-    }
-
-    pub fn update<C, F, R>(&self, cx: &mut C, update: F) -> C::Result<R>
-    where
-        C: BorrowWindowContext,
-        F: FnOnce(&mut WindowContext) -> R,
-    {
-        cx.update_window(*self, update)
-    }
-
-    pub fn update_optional<C, F, R>(&self, cx: &mut C, update: F) -> Option<R>
-    where
-        C: BorrowWindowContext,
-        F: FnOnce(&mut WindowContext) -> Option<R>,
-    {
-        cx.update_window_optional(*self, update)
-    }
-
-    pub fn add_view<C, U, F>(&self, cx: &mut C, build_view: F) -> C::Result<ViewHandle<U>>
-    where
-        C: BorrowWindowContext,
-        U: View,
-        F: FnOnce(&mut ViewContext<U>) -> U,
-    {
-        self.update(cx, |cx| cx.add_view(build_view))
-    }
-
-    pub fn downcast<V: 'static>(self) -> Option<WindowHandle<V>> {
-        if self.root_view_type == TypeId::of::<V>() {
-            Some(WindowHandle {
-                any_handle: self,
-                root_view_type: PhantomData,
-            })
-        } else {
-            None
-        }
-    }
-
-    pub fn root_is<V: 'static>(&self) -> bool {
-        self.root_view_type == TypeId::of::<V>()
-    }
-
-    pub fn is_active<C: BorrowWindowContext>(&self, cx: &C) -> C::Result<bool> {
-        self.read_with(cx, |cx| cx.window.is_active)
-    }
-
-    pub fn remove<C: BorrowWindowContext>(&self, cx: &mut C) -> C::Result<()> {
-        self.update(cx, |cx| cx.remove_window())
-    }
-
-    pub fn debug_elements<C: BorrowWindowContext>(&self, cx: &C) -> Option<json::Value> {
-        self.read_optional_with(cx, |cx| {
-            let root_view = cx.window.root_view();
-            let root_element = cx.window.rendered_views.get(&root_view.id())?;
-            root_element.debug(cx).log_err()
-        })
-    }
-
-    pub fn activate<C: BorrowWindowContext>(&mut self, cx: &mut C) -> C::Result<()> {
-        self.update(cx, |cx| cx.activate_window())
-    }
-
-    pub fn prompt<C: BorrowWindowContext>(
-        &self,
-        level: PromptLevel,
-        msg: &str,
-        answers: &[&str],
-        cx: &mut C,
-    ) -> C::Result<oneshot::Receiver<usize>> {
-        self.update(cx, |cx| cx.prompt(level, msg, answers))
-    }
-
-    pub fn dispatch_action<C: BorrowWindowContext>(
-        &self,
-        view_id: usize,
-        action: &dyn Action,
-        cx: &mut C,
-    ) -> C::Result<()> {
-        self.update(cx, |cx| {
-            cx.dispatch_action(Some(view_id), action);
-        })
-    }
-
-    pub fn available_actions<C: BorrowWindowContext>(
-        &self,
-        view_id: usize,
-        cx: &C,
-    ) -> C::Result<Vec<(&'static str, Box<dyn Action>, SmallVec<[Binding; 1]>)>> {
-        self.read_with(cx, |cx| cx.available_actions(view_id))
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn simulate_activation(&self, cx: &mut TestAppContext) {
-        self.update(cx, |cx| {
-            let other_windows = cx
-                .windows()
-                .filter(|window| *window != *self)
-                .collect::<Vec<_>>();
-
-            for window in other_windows {
-                cx.window_changed_active_status(window, false)
-            }
-
-            cx.window_changed_active_status(*self, true)
-        });
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn simulate_deactivation(&self, cx: &mut TestAppContext) {
-        self.update(cx, |cx| {
-            cx.window_changed_active_status(*self, false);
-        })
-    }
-}
-
-#[repr(transparent)]
-pub struct ViewHandle<V> {
-    any_handle: AnyViewHandle,
-    view_type: PhantomData<V>,
-}
-
-impl<T> Deref for ViewHandle<T> {
-    type Target = AnyViewHandle;
-
-    fn deref(&self) -> &Self::Target {
-        &self.any_handle
-    }
-}
-
-impl<V: 'static> ViewHandle<V> {
-    fn new(window: AnyWindowHandle, view_id: usize, ref_counts: &Arc<Mutex<RefCounts>>) -> Self {
-        Self {
-            any_handle: AnyViewHandle::new(window, view_id, TypeId::of::<V>(), ref_counts.clone()),
-            view_type: PhantomData,
-        }
-    }
-
-    pub fn downgrade(&self) -> WeakViewHandle<V> {
-        WeakViewHandle::new(self.window, self.view_id)
-    }
-
-    pub fn into_any(self) -> AnyViewHandle {
-        self.any_handle
-    }
-
-    pub fn window(&self) -> AnyWindowHandle {
-        self.window
-    }
-
-    pub fn id(&self) -> usize {
-        self.view_id
-    }
-
-    pub fn read<'a>(&self, cx: &'a AppContext) -> &'a V {
-        cx.read_view(self)
-    }
-
-    pub fn read_with<C, F, S>(&self, cx: &C, read: F) -> C::Result<S>
-    where
-        C: BorrowWindowContext,
-        F: FnOnce(&V, &ViewContext<V>) -> S,
-    {
-        cx.read_window(self.window, |cx| {
-            let cx = ViewContext::immutable(cx, self.view_id);
-            read(cx.read_view(self), &cx)
-        })
-    }
-
-    pub fn update<C, F, S>(&self, cx: &mut C, update: F) -> C::Result<S>
-    where
-        C: BorrowWindowContext,
-        F: FnOnce(&mut V, &mut ViewContext<V>) -> S,
-    {
-        let mut update = Some(update);
-
-        cx.update_window(self.window, |cx| {
-            cx.update_view(self, &mut |view, cx| {
-                let update = update.take().unwrap();
-                update(view, cx)
-            })
-        })
-    }
-
-    pub fn is_focused(&self, cx: &WindowContext) -> bool {
-        cx.focused_view_id() == Some(self.view_id)
-    }
-}
-
-impl<T: View> Clone for ViewHandle<T> {
-    fn clone(&self) -> Self {
-        ViewHandle::new(self.window, self.view_id, &self.ref_counts)
-    }
-}
-
-impl<T> PartialEq for ViewHandle<T> {
-    fn eq(&self, other: &Self) -> bool {
-        self.window == other.window && self.view_id == other.view_id
-    }
-}
-
-impl<T> PartialEq<AnyViewHandle> for ViewHandle<T> {
-    fn eq(&self, other: &AnyViewHandle) -> bool {
-        self.window == other.window && self.view_id == other.view_id
-    }
-}
-
-impl<T> PartialEq<WeakViewHandle<T>> for ViewHandle<T> {
-    fn eq(&self, other: &WeakViewHandle<T>) -> bool {
-        self.window == other.window && self.view_id == other.view_id
-    }
-}
-
-impl<T> PartialEq<ViewHandle<T>> for WeakViewHandle<T> {
-    fn eq(&self, other: &ViewHandle<T>) -> bool {
-        self.window == other.window && self.view_id == other.view_id
-    }
-}
-
-impl<T> Eq for ViewHandle<T> {}
-
-impl<T> Hash for ViewHandle<T> {
-    fn hash<H: Hasher>(&self, state: &mut H) {
-        self.window.hash(state);
-        self.view_id.hash(state);
-    }
-}
-
-impl<T> Debug for ViewHandle<T> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_struct(&format!("ViewHandle<{}>", type_name::<T>()))
-            .field("window_id", &self.window)
-            .field("view_id", &self.view_id)
-            .finish()
-    }
-}
-
-impl<T: View> Handle<T> for ViewHandle<T> {
-    type Weak = WeakViewHandle<T>;
-
-    fn id(&self) -> usize {
-        self.view_id
-    }
-
-    fn location(&self) -> EntityLocation {
-        EntityLocation::View(self.window.id(), self.view_id)
-    }
-
-    fn downgrade(&self) -> Self::Weak {
-        self.downgrade()
-    }
-
-    fn upgrade_from(weak: &Self::Weak, cx: &AppContext) -> Option<Self>
-    where
-        Self: Sized,
-    {
-        weak.upgrade(cx)
-    }
-}
-
-pub struct AnyViewHandle {
-    window: AnyWindowHandle,
-    view_id: usize,
-    view_type: TypeId,
-    ref_counts: Arc<Mutex<RefCounts>>,
-
-    #[cfg(any(test, feature = "test-support"))]
-    handle_id: usize,
-}
-
-impl AnyViewHandle {
-    fn new(
-        window: AnyWindowHandle,
-        view_id: usize,
-        view_type: TypeId,
-        ref_counts: Arc<Mutex<RefCounts>>,
-    ) -> Self {
-        ref_counts.lock().inc_view(window, view_id);
-
-        #[cfg(any(test, feature = "test-support"))]
-        let handle_id = ref_counts
-            .lock()
-            .leak_detector
-            .lock()
-            .handle_created(None, view_id);
-
-        Self {
-            window,
-            view_id,
-            view_type,
-            ref_counts,
-            #[cfg(any(test, feature = "test-support"))]
-            handle_id,
-        }
-    }
-
-    pub fn window(&self) -> AnyWindowHandle {
-        self.window
-    }
-
-    pub fn id(&self) -> usize {
-        self.view_id
-    }
-
-    pub fn is<T: 'static>(&self) -> bool {
-        TypeId::of::<T>() == self.view_type
-    }
-
-    pub fn downcast<V: 'static>(self) -> Option<ViewHandle<V>> {
-        if self.is::<V>() {
-            Some(ViewHandle {
-                any_handle: self,
-                view_type: PhantomData,
-            })
-        } else {
-            None
-        }
-    }
-
-    pub fn downcast_ref<V: 'static>(&self) -> Option<&ViewHandle<V>> {
-        if self.is::<V>() {
-            Some(unsafe { mem::transmute(self) })
-        } else {
-            None
-        }
-    }
-
-    pub fn downgrade(&self) -> AnyWeakViewHandle {
-        AnyWeakViewHandle {
-            window: self.window,
-            view_id: self.view_id,
-            view_type: self.view_type,
-        }
-    }
-
-    pub fn view_type(&self) -> TypeId {
-        self.view_type
-    }
-
-    pub fn debug_json<'a, 'b>(&self, cx: &'b WindowContext<'a>) -> serde_json::Value {
-        cx.views
-            .get(&(self.window, self.view_id))
-            .map_or_else(|| serde_json::Value::Null, |view| view.debug_json(cx))
-    }
-}
-
-impl Clone for AnyViewHandle {
-    fn clone(&self) -> Self {
-        Self::new(
-            self.window,
-            self.view_id,
-            self.view_type,
-            self.ref_counts.clone(),
-        )
-    }
-}
-
-impl PartialEq for AnyViewHandle {
-    fn eq(&self, other: &Self) -> bool {
-        self.window == other.window && self.view_id == other.view_id
-    }
-}
-
-impl<T> PartialEq<ViewHandle<T>> for AnyViewHandle {
-    fn eq(&self, other: &ViewHandle<T>) -> bool {
-        self.window == other.window && self.view_id == other.view_id
-    }
-}
-
-impl Drop for AnyViewHandle {
-    fn drop(&mut self) {
-        self.ref_counts.lock().dec_view(self.window, self.view_id);
-        #[cfg(any(test, feature = "test-support"))]
-        self.ref_counts
-            .lock()
-            .leak_detector
-            .lock()
-            .handle_dropped(self.view_id, self.handle_id);
-    }
-}
-
-impl Debug for AnyViewHandle {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_struct("AnyViewHandle")
-            .field("window_id", &self.window.id())
-            .field("view_id", &self.view_id)
-            .finish()
-    }
-}
-
-pub struct AnyModelHandle {
-    model_id: usize,
-    model_type: TypeId,
-    ref_counts: Arc<Mutex<RefCounts>>,
-
-    #[cfg(any(test, feature = "test-support"))]
-    handle_id: usize,
-}
-
-impl AnyModelHandle {
-    fn new(model_id: usize, model_type: TypeId, ref_counts: Arc<Mutex<RefCounts>>) -> Self {
-        ref_counts.lock().inc_model(model_id);
-
-        #[cfg(any(test, feature = "test-support"))]
-        let handle_id = ref_counts
-            .lock()
-            .leak_detector
-            .lock()
-            .handle_created(None, model_id);
-
-        Self {
-            model_id,
-            model_type,
-            ref_counts,
-
-            #[cfg(any(test, feature = "test-support"))]
-            handle_id,
-        }
-    }
-
-    pub fn downcast<T: Entity>(self) -> Option<ModelHandle<T>> {
-        if self.is::<T>() {
-            Some(ModelHandle {
-                any_handle: self,
-                model_type: PhantomData,
-            })
-        } else {
-            None
-        }
-    }
-
-    pub fn downgrade(&self) -> AnyWeakModelHandle {
-        AnyWeakModelHandle {
-            model_id: self.model_id,
-            model_type: self.model_type,
-        }
-    }
-
-    pub fn is<T: Entity>(&self) -> bool {
-        self.model_type == TypeId::of::<T>()
-    }
-
-    pub fn model_type(&self) -> TypeId {
-        self.model_type
-    }
-}
-
-impl Clone for AnyModelHandle {
-    fn clone(&self) -> Self {
-        Self::new(self.model_id, self.model_type, self.ref_counts.clone())
-    }
-}
-
-impl Drop for AnyModelHandle {
-    fn drop(&mut self) {
-        let mut ref_counts = self.ref_counts.lock();
-        ref_counts.dec_model(self.model_id);
-
-        #[cfg(any(test, feature = "test-support"))]
-        ref_counts
-            .leak_detector
-            .lock()
-            .handle_dropped(self.model_id, self.handle_id);
-    }
-}
-
-#[derive(Hash, PartialEq, Eq, Debug, Clone, Copy)]
-pub struct AnyWeakModelHandle {
-    model_id: usize,
-    model_type: TypeId,
-}
-
-impl AnyWeakModelHandle {
-    pub fn upgrade(&self, cx: &impl BorrowAppContext) -> Option<AnyModelHandle> {
-        cx.read_with(|cx| cx.upgrade_any_model_handle(self))
-    }
-
-    pub fn model_type(&self) -> TypeId {
-        self.model_type
-    }
-
-    fn is<T: 'static>(&self) -> bool {
-        TypeId::of::<T>() == self.model_type
-    }
-
-    pub fn downcast<T: Entity>(self) -> Option<WeakModelHandle<T>> {
-        if self.is::<T>() {
-            let result = Some(WeakModelHandle {
-                any_handle: self,
-                model_type: PhantomData,
-            });
-
-            result
-        } else {
-            None
-        }
-    }
-}
-
-pub struct WeakViewHandle<T> {
-    any_handle: AnyWeakViewHandle,
-    view_type: PhantomData<T>,
-}
-
-impl<T> Copy for WeakViewHandle<T> {}
-
-impl<T> Debug for WeakViewHandle<T> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_struct(&format!("WeakViewHandle<{}>", type_name::<T>()))
-            .field("any_handle", &self.any_handle)
-            .finish()
-    }
-}
-
-impl<T> WeakHandle for WeakViewHandle<T> {
-    fn id(&self) -> usize {
-        self.view_id
-    }
-}
-
-impl<V: 'static> WeakViewHandle<V> {
-    fn new(window: AnyWindowHandle, view_id: usize) -> Self {
-        Self {
-            any_handle: AnyWeakViewHandle {
-                window,
-                view_id,
-                view_type: TypeId::of::<V>(),
-            },
-            view_type: PhantomData,
-        }
-    }
-
-    pub fn id(&self) -> usize {
-        self.view_id
-    }
-
-    pub fn window(&self) -> AnyWindowHandle {
-        self.window
-    }
-
-    pub fn window_id(&self) -> usize {
-        self.window.id()
-    }
-
-    pub fn into_any(self) -> AnyWeakViewHandle {
-        self.any_handle
-    }
-
-    pub fn upgrade(&self, cx: &impl BorrowAppContext) -> Option<ViewHandle<V>> {
-        cx.read_with(|cx| cx.upgrade_view_handle(self))
-    }
-
-    pub fn read_with<T>(
-        &self,
-        cx: &AsyncAppContext,
-        read: impl FnOnce(&V, &ViewContext<V>) -> T,
-    ) -> Result<T> {
-        cx.read(|cx| {
-            let handle = cx
-                .upgrade_view_handle(self)
-                .ok_or_else(|| anyhow!("view was dropped"))?;
-            cx.read_window(self.window, |cx| handle.read_with(cx, read))
-                .ok_or_else(|| anyhow!("window was removed"))
-        })
-    }
-
-    pub fn update<T, B>(
-        &self,
-        cx: &mut B,
-        update: impl FnOnce(&mut V, &mut ViewContext<V>) -> T,
-    ) -> Result<T>
-    where
-        B: BorrowWindowContext,
-        B::Result<Option<T>>: Flatten<T>,
-    {
-        cx.update_window(self.window(), |cx| {
-            cx.upgrade_view_handle(self)
-                .map(|handle| handle.update(cx, update))
-        })
-        .flatten()
-        .ok_or_else(|| anyhow!("window was removed"))
-    }
-}
-
-pub trait Flatten<T> {
-    fn flatten(self) -> Option<T>;
-}
-
-impl<T> Flatten<T> for Option<Option<T>> {
-    fn flatten(self) -> Option<T> {
-        self.flatten()
-    }
-}
-
-impl<T> Flatten<T> for Option<T> {
-    fn flatten(self) -> Option<T> {
-        self
-    }
-}
-
-impl<V> Deref for WeakViewHandle<V> {
-    type Target = AnyWeakViewHandle;
-
-    fn deref(&self) -> &Self::Target {
-        &self.any_handle
-    }
-}
-
-impl<V> Clone for WeakViewHandle<V> {
-    fn clone(&self) -> Self {
-        Self {
-            any_handle: self.any_handle.clone(),
-            view_type: PhantomData,
-        }
-    }
-}
-
-impl<T> PartialEq for WeakViewHandle<T> {
-    fn eq(&self, other: &Self) -> bool {
-        self.window == other.window && self.view_id == other.view_id
-    }
-}
-
-impl<T> Eq for WeakViewHandle<T> {}
-
-impl<T> Hash for WeakViewHandle<T> {
-    fn hash<H: Hasher>(&self, state: &mut H) {
-        self.any_handle.hash(state);
-    }
-}
-
-#[derive(Debug, Clone, Copy, Eq, PartialEq)]
-pub struct AnyWeakViewHandle {
-    window: AnyWindowHandle,
-    view_id: usize,
-    view_type: TypeId,
-}
-
-impl AnyWeakViewHandle {
-    pub fn id(&self) -> usize {
-        self.view_id
-    }
-
-    fn is<T: 'static>(&self) -> bool {
-        TypeId::of::<T>() == self.view_type
-    }
-
-    pub fn upgrade(&self, cx: &impl BorrowAppContext) -> Option<AnyViewHandle> {
-        cx.read_with(|cx| cx.upgrade_any_view_handle(self))
-    }
-
-    pub fn downcast<T: View>(self) -> Option<WeakViewHandle<T>> {
-        if self.is::<T>() {
-            Some(WeakViewHandle {
-                any_handle: self,
-                view_type: PhantomData,
-            })
-        } else {
-            None
-        }
-    }
-}
-
-impl Hash for AnyWeakViewHandle {
-    fn hash<H: Hasher>(&self, state: &mut H) {
-        self.window.hash(state);
-        self.view_id.hash(state);
-        self.view_type.hash(state);
-    }
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
-pub struct ElementStateId {
-    view_id: usize,
-    element_id: usize,
-    tag: TypeTag,
-}
-
-pub struct ElementStateHandle<T> {
-    value_type: PhantomData<T>,
-    id: ElementStateId,
-    ref_counts: Weak<Mutex<RefCounts>>,
-}
-
-impl<T: 'static> ElementStateHandle<T> {
-    fn new(id: ElementStateId, frame_id: usize, ref_counts: &Arc<Mutex<RefCounts>>) -> Self {
-        ref_counts.lock().inc_element_state(id, frame_id);
-        Self {
-            value_type: PhantomData,
-            id,
-            ref_counts: Arc::downgrade(ref_counts),
-        }
-    }
-
-    pub fn id(&self) -> ElementStateId {
-        self.id
-    }
-
-    pub fn read<'a>(&self, cx: &'a AppContext) -> &'a T {
-        cx.element_states
-            .get(&self.id)
-            .unwrap()
-            .downcast_ref()
-            .unwrap()
-    }
-
-    pub fn update<C, D, R>(&self, cx: &mut C, f: impl FnOnce(&mut T, &mut C) -> R) -> R
-    where
-        C: DerefMut<Target = D>,
-        D: DerefMut<Target = AppContext>,
-    {
-        let mut element_state = cx.deref_mut().element_states.remove(&self.id).unwrap();
-        let result = f(element_state.downcast_mut().unwrap(), cx);
-        cx.deref_mut().element_states.insert(self.id, element_state);
-        result
-    }
-}
-
-impl<T> Drop for ElementStateHandle<T> {
-    fn drop(&mut self) {
-        if let Some(ref_counts) = self.ref_counts.upgrade() {
-            ref_counts.lock().dec_element_state(self.id);
-        }
-    }
-}
-
-#[must_use]
-pub enum Subscription {
-    Subscription(callback_collection::Subscription<usize, SubscriptionCallback>),
-    Observation(callback_collection::Subscription<usize, ObservationCallback>),
-    GlobalSubscription(callback_collection::Subscription<TypeId, GlobalSubscriptionCallback>),
-    GlobalObservation(callback_collection::Subscription<TypeId, GlobalObservationCallback>),
-    FocusObservation(callback_collection::Subscription<usize, FocusObservationCallback>),
-    WindowActivationObservation(
-        callback_collection::Subscription<AnyWindowHandle, WindowActivationCallback>,
-    ),
-    WindowFullscreenObservation(
-        callback_collection::Subscription<AnyWindowHandle, WindowFullscreenCallback>,
-    ),
-    WindowBoundsObservation(
-        callback_collection::Subscription<AnyWindowHandle, WindowBoundsCallback>,
-    ),
-    KeystrokeObservation(callback_collection::Subscription<AnyWindowHandle, KeystrokeCallback>),
-    ReleaseObservation(callback_collection::Subscription<usize, ReleaseObservationCallback>),
-    ActionObservation(callback_collection::Subscription<(), ActionObservationCallback>),
-    ActiveLabeledTasksObservation(
-        callback_collection::Subscription<(), ActiveLabeledTasksCallback>,
-    ),
-}
-
-impl Subscription {
-    pub fn id(&self) -> usize {
-        match self {
-            Subscription::Subscription(subscription) => subscription.id(),
-            Subscription::Observation(subscription) => subscription.id(),
-            Subscription::GlobalSubscription(subscription) => subscription.id(),
-            Subscription::GlobalObservation(subscription) => subscription.id(),
-            Subscription::FocusObservation(subscription) => subscription.id(),
-            Subscription::WindowActivationObservation(subscription) => subscription.id(),
-            Subscription::WindowFullscreenObservation(subscription) => subscription.id(),
-            Subscription::WindowBoundsObservation(subscription) => subscription.id(),
-            Subscription::KeystrokeObservation(subscription) => subscription.id(),
-            Subscription::ReleaseObservation(subscription) => subscription.id(),
-            Subscription::ActionObservation(subscription) => subscription.id(),
-            Subscription::ActiveLabeledTasksObservation(subscription) => subscription.id(),
-        }
-    }
-
-    pub fn detach(&mut self) {
-        match self {
-            Subscription::Subscription(subscription) => subscription.detach(),
-            Subscription::GlobalSubscription(subscription) => subscription.detach(),
-            Subscription::Observation(subscription) => subscription.detach(),
-            Subscription::GlobalObservation(subscription) => subscription.detach(),
-            Subscription::FocusObservation(subscription) => subscription.detach(),
-            Subscription::KeystrokeObservation(subscription) => subscription.detach(),
-            Subscription::WindowActivationObservation(subscription) => subscription.detach(),
-            Subscription::WindowFullscreenObservation(subscription) => subscription.detach(),
-            Subscription::WindowBoundsObservation(subscription) => subscription.detach(),
-            Subscription::ReleaseObservation(subscription) => subscription.detach(),
-            Subscription::ActionObservation(subscription) => subscription.detach(),
-            Subscription::ActiveLabeledTasksObservation(subscription) => subscription.detach(),
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::{
-        actions,
-        elements::*,
-        impl_actions,
-        platform::{MouseButton, MouseButtonEvent},
-        window::ChildView,
-    };
-    use itertools::Itertools;
-    use postage::{sink::Sink, stream::Stream};
-    use serde::Deserialize;
-    use smol::future::poll_once;
-    use std::{
-        cell::Cell,
-        sync::atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst},
-    };
-
-    #[crate::test(self)]
-    fn test_model_handles(cx: &mut AppContext) {
-        struct Model {
-            other: Option<ModelHandle<Model>>,
-            events: Vec<String>,
-        }
-
-        impl Entity for Model {
-            type Event = usize;
-        }
-
-        impl Model {
-            fn new(other: Option<ModelHandle<Self>>, cx: &mut ModelContext<Self>) -> Self {
-                if let Some(other) = other.as_ref() {
-                    cx.observe(other, |me, _, _| {
-                        me.events.push("notified".into());
-                    })
-                    .detach();
-                    cx.subscribe(other, |me, _, event, _| {
-                        me.events.push(format!("observed event {}", event));
-                    })
-                    .detach();
-                }
-
-                Self {
-                    other,
-                    events: Vec::new(),
-                }
-            }
-        }
-
-        let handle_1 = cx.add_model(|cx| Model::new(None, cx));
-        let handle_2 = cx.add_model(|cx| Model::new(Some(handle_1.clone()), cx));
-        assert_eq!(cx.models.len(), 2);
-
-        handle_1.update(cx, |model, cx| {
-            model.events.push("updated".into());
-            cx.emit(1);
-            cx.notify();
-            cx.emit(2);
-        });
-        assert_eq!(handle_1.read(cx).events, vec!["updated".to_string()]);
-        assert_eq!(
-            handle_2.read(cx).events,
-            vec![
-                "observed event 1".to_string(),
-                "notified".to_string(),
-                "observed event 2".to_string(),
-            ]
-        );
-
-        handle_2.update(cx, |model, _| {
-            drop(handle_1);
-            model.other.take();
-        });
-
-        assert_eq!(cx.models.len(), 1);
-        assert!(cx.subscriptions.is_empty());
-        assert!(cx.observations.is_empty());
-    }
-
-    #[crate::test(self)]
-    fn test_model_events(cx: &mut AppContext) {
-        #[derive(Default)]
-        struct Model {
-            events: Vec<usize>,
-        }
-
-        impl Entity for Model {
-            type Event = usize;
-        }
-
-        let handle_1 = cx.add_model(|_| Model::default());
-        let handle_2 = cx.add_model(|_| Model::default());
-
-        handle_1.update(cx, |_, cx| {
-            cx.subscribe(&handle_2, move |model: &mut Model, emitter, event, cx| {
-                model.events.push(*event);
-
-                cx.subscribe(&emitter, |model, _, event, _| {
-                    model.events.push(*event * 2);
-                })
-                .detach();
-            })
-            .detach();
-        });
-
-        handle_2.update(cx, |_, c| c.emit(7));
-        assert_eq!(handle_1.read(cx).events, vec![7]);
-
-        handle_2.update(cx, |_, c| c.emit(5));
-        assert_eq!(handle_1.read(cx).events, vec![7, 5, 10]);
-    }
-
-    #[crate::test(self)]
-    fn test_model_emit_before_subscribe_in_same_update_cycle(cx: &mut AppContext) {
-        #[derive(Default)]
-        struct Model;
-
-        impl Entity for Model {
-            type Event = ();
-        }
-
-        let events = Rc::new(RefCell::new(Vec::new()));
-        cx.add_model(|cx| {
-            drop(cx.subscribe(&cx.handle(), {
-                let events = events.clone();
-                move |_, _, _, _| events.borrow_mut().push("dropped before flush")
-            }));
-            cx.subscribe(&cx.handle(), {
-                let events = events.clone();
-                move |_, _, _, _| events.borrow_mut().push("before emit")
-            })
-            .detach();
-            cx.emit(());
-            cx.subscribe(&cx.handle(), {
-                let events = events.clone();
-                move |_, _, _, _| events.borrow_mut().push("after emit")
-            })
-            .detach();
-            Model
-        });
-        assert_eq!(*events.borrow(), ["before emit"]);
-    }
-
-    #[crate::test(self)]
-    fn test_observe_and_notify_from_model(cx: &mut AppContext) {
-        #[derive(Default)]
-        struct Model {
-            count: usize,
-            events: Vec<usize>,
-        }
-
-        impl Entity for Model {
-            type Event = ();
-        }
-
-        let handle_1 = cx.add_model(|_| Model::default());
-        let handle_2 = cx.add_model(|_| Model::default());
-
-        handle_1.update(cx, |_, c| {
-            c.observe(&handle_2, move |model, observed, c| {
-                model.events.push(observed.read(c).count);
-                c.observe(&observed, |model, observed, c| {
-                    model.events.push(observed.read(c).count * 2);
-                })
-                .detach();
-            })
-            .detach();
-        });
-
-        handle_2.update(cx, |model, c| {
-            model.count = 7;
-            c.notify()
-        });
-        assert_eq!(handle_1.read(cx).events, vec![7]);
-
-        handle_2.update(cx, |model, c| {
-            model.count = 5;
-            c.notify()
-        });
-        assert_eq!(handle_1.read(cx).events, vec![7, 5, 10])
-    }
-
-    #[crate::test(self)]
-    fn test_model_notify_before_observe_in_same_update_cycle(cx: &mut AppContext) {
-        #[derive(Default)]
-        struct Model;
-
-        impl Entity for Model {
-            type Event = ();
-        }
-
-        let events = Rc::new(RefCell::new(Vec::new()));
-        cx.add_model(|cx| {
-            drop(cx.observe(&cx.handle(), {
-                let events = events.clone();
-                move |_, _, _| events.borrow_mut().push("dropped before flush")
-            }));
-            cx.observe(&cx.handle(), {
-                let events = events.clone();
-                move |_, _, _| events.borrow_mut().push("before notify")
-            })
-            .detach();
-            cx.notify();
-            cx.observe(&cx.handle(), {
-                let events = events.clone();
-                move |_, _, _| events.borrow_mut().push("after notify")
-            })
-            .detach();
-            Model
-        });
-        assert_eq!(*events.borrow(), ["before notify"]);
-    }
-
-    #[crate::test(self)]
-    fn test_defer_and_after_window_update(cx: &mut TestAppContext) {
-        struct View {
-            render_count: usize,
-        }
-
-        impl Entity for View {
-            type Event = usize;
-        }
-
-        impl super::View for View {
-            fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
-                post_inc(&mut self.render_count);
-                Empty::new().into_any()
-            }
-
-            fn ui_name() -> &'static str {
-                "View"
-            }
-        }
-
-        let window = cx.add_window(|_| View { render_count: 0 });
-        let called_defer = Rc::new(AtomicBool::new(false));
-        let called_after_window_update = Rc::new(AtomicBool::new(false));
-
-        window.root(cx).update(cx, |this, cx| {
-            assert_eq!(this.render_count, 1);
-            cx.defer({
-                let called_defer = called_defer.clone();
-                move |this, _| {
-                    assert_eq!(this.render_count, 1);
-                    called_defer.store(true, SeqCst);
-                }
-            });
-            cx.after_window_update({
-                let called_after_window_update = called_after_window_update.clone();
-                move |this, cx| {
-                    assert_eq!(this.render_count, 2);
-                    called_after_window_update.store(true, SeqCst);
-                    cx.notify();
-                }
-            });
-            assert!(!called_defer.load(SeqCst));
-            assert!(!called_after_window_update.load(SeqCst));
-            cx.notify();
-        });
-
-        assert!(called_defer.load(SeqCst));
-        assert!(called_after_window_update.load(SeqCst));
-        assert_eq!(window.read_root_with(cx, |view, _| view.render_count), 3);
-    }
-
-    #[crate::test(self)]
-    fn test_view_handles(cx: &mut TestAppContext) {
-        struct View {
-            other: Option<ViewHandle<View>>,
-            events: Vec<String>,
-        }
-
-        impl Entity for View {
-            type Event = usize;
-        }
-
-        impl super::View for View {
-            fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
-                Empty::new().into_any()
-            }
-
-            fn ui_name() -> &'static str {
-                "View"
-            }
-        }
-
-        impl View {
-            fn new(other: Option<ViewHandle<View>>, cx: &mut ViewContext<Self>) -> Self {
-                if let Some(other) = other.as_ref() {
-                    cx.subscribe(other, |me, _, event, _| {
-                        me.events.push(format!("observed event {}", event));
-                    })
-                    .detach();
-                }
-                Self {
-                    other,
-                    events: Vec::new(),
-                }
-            }
-        }
-
-        let window = cx.add_window(|cx| View::new(None, cx));
-        let handle_1 = window.add_view(cx, |cx| View::new(None, cx));
-        let handle_2 = window.add_view(cx, |cx| View::new(Some(handle_1.clone()), cx));
-        assert_eq!(cx.read(|cx| cx.views.len()), 3);
-
-        handle_1.update(cx, |view, cx| {
-            view.events.push("updated".into());
-            cx.emit(1);
-            cx.emit(2);
-        });
-        handle_1.read_with(cx, |view, _| {
-            assert_eq!(view.events, vec!["updated".to_string()]);
-        });
-        handle_2.read_with(cx, |view, _| {
-            assert_eq!(
-                view.events,
-                vec![
-                    "observed event 1".to_string(),
-                    "observed event 2".to_string(),
-                ]
-            );
-        });
-
-        handle_2.update(cx, |view, _| {
-            drop(handle_1);
-            view.other.take();
-        });
-
-        cx.read(|cx| {
-            assert_eq!(cx.views.len(), 2);
-            assert!(cx.subscriptions.is_empty());
-            assert!(cx.observations.is_empty());
-        });
-    }
-
-    #[crate::test(self)]
-    fn test_add_window(cx: &mut AppContext) {
-        struct View {
-            mouse_down_count: Arc<AtomicUsize>,
-        }
-
-        impl Entity for View {
-            type Event = ();
-        }
-
-        impl super::View for View {
-            fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-                enum Handler {}
-                let mouse_down_count = self.mouse_down_count.clone();
-                MouseEventHandler::new::<Handler, _>(0, cx, |_, _| Empty::new())
-                    .on_down(MouseButton::Left, move |_, _, _| {
-                        mouse_down_count.fetch_add(1, SeqCst);
-                    })
-                    .into_any()
-            }
-
-            fn ui_name() -> &'static str {
-                "View"
-            }
-        }
-
-        let mouse_down_count = Arc::new(AtomicUsize::new(0));
-        let window = cx.add_window(Default::default(), |_| View {
-            mouse_down_count: mouse_down_count.clone(),
-        });
-
-        window.update(cx, |cx| {
-            // Ensure window's root element is in a valid lifecycle state.
-            cx.dispatch_event(
-                Event::MouseDown(MouseButtonEvent {
-                    position: Default::default(),
-                    button: MouseButton::Left,
-                    modifiers: Default::default(),
-                    click_count: 1,
-                    is_down: true,
-                }),
-                false,
-            );
-            assert_eq!(mouse_down_count.load(SeqCst), 1);
-        });
-    }
-
-    #[crate::test(self)]
-    fn test_entity_release_hooks(cx: &mut TestAppContext) {
-        struct Model {
-            released: Rc<Cell<bool>>,
-        }
-
-        struct View {
-            released: Rc<Cell<bool>>,
-        }
-
-        impl Entity for Model {
-            type Event = ();
-
-            fn release(&mut self, _: &mut AppContext) {
-                self.released.set(true);
-            }
-        }
-
-        impl Entity for View {
-            type Event = ();
-
-            fn release(&mut self, _: &mut AppContext) {
-                self.released.set(true);
-            }
-        }
-
-        impl super::View for View {
-            fn ui_name() -> &'static str {
-                "View"
-            }
-
-            fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
-                Empty::new().into_any()
-            }
-        }
-
-        let model_released = Rc::new(Cell::new(false));
-        let model_release_observed = Rc::new(Cell::new(false));
-        let view_released = Rc::new(Cell::new(false));
-        let view_release_observed = Rc::new(Cell::new(false));
-
-        let model = cx.add_model(|_| Model {
-            released: model_released.clone(),
-        });
-        let window = cx.add_window(|_| View {
-            released: view_released.clone(),
-        });
-        let view = window.root(cx);
-
-        assert!(!model_released.get());
-        assert!(!view_released.get());
-
-        cx.update(|cx| {
-            cx.observe_release(&model, {
-                let model_release_observed = model_release_observed.clone();
-                move |_, _| model_release_observed.set(true)
-            })
-            .detach();
-            cx.observe_release(&view, {
-                let view_release_observed = view_release_observed.clone();
-                move |_, _| view_release_observed.set(true)
-            })
-            .detach();
-        });
-
-        cx.update(move |_| {
-            drop(model);
-        });
-        assert!(model_released.get());
-        assert!(model_release_observed.get());
-
-        drop(view);
-        window.update(cx, |cx| cx.remove_window());
-        assert!(view_released.get());
-        assert!(view_release_observed.get());
-    }
-
-    #[crate::test(self)]
-    fn test_view_events(cx: &mut TestAppContext) {
-        struct Model;
-
-        impl Entity for Model {
-            type Event = String;
-        }
-
-        let window = cx.add_window(|_| TestView::default());
-        let handle_1 = window.root(cx);
-        let handle_2 = window.add_view(cx, |_| TestView::default());
-        let handle_3 = cx.add_model(|_| Model);
-
-        handle_1.update(cx, |_, cx| {
-            cx.subscribe(&handle_2, move |me, emitter, event, cx| {
-                me.events.push(event.clone());
-
-                cx.subscribe(&emitter, |me, _, event, _| {
-                    me.events.push(format!("{event} from inner"));
-                })
-                .detach();
-            })
-            .detach();
-
-            cx.subscribe(&handle_3, |me, _, event, _| {
-                me.events.push(event.clone());
-            })
-            .detach();
-        });
-
-        handle_2.update(cx, |_, c| c.emit("7".into()));
-        handle_1.read_with(cx, |view, _| assert_eq!(view.events, ["7"]));
-
-        handle_2.update(cx, |_, c| c.emit("5".into()));
-        handle_1.read_with(cx, |view, _| {
-            assert_eq!(view.events, ["7", "5", "5 from inner"])
-        });
-
-        handle_3.update(cx, |_, c| c.emit("9".into()));
-        handle_1.read_with(cx, |view, _| {
-            assert_eq!(view.events, ["7", "5", "5 from inner", "9"])
-        });
-    }
-
-    #[crate::test(self)]
-    fn test_global_events(cx: &mut AppContext) {
-        #[derive(Clone, Debug, Eq, PartialEq)]
-        struct GlobalEvent(u64);
-
-        let events = Rc::new(RefCell::new(Vec::new()));
-        let first_subscription;
-        let second_subscription;
-
-        {
-            let events = events.clone();
-            first_subscription = cx.subscribe_global(move |e: &GlobalEvent, _| {
-                events.borrow_mut().push(("First", e.clone()));
-            });
-        }
-
-        {
-            let events = events.clone();
-            second_subscription = cx.subscribe_global(move |e: &GlobalEvent, _| {
-                events.borrow_mut().push(("Second", e.clone()));
-            });
-        }
-
-        cx.update(|cx| {
-            cx.emit_global(GlobalEvent(1));
-            cx.emit_global(GlobalEvent(2));
-        });
-
-        drop(first_subscription);
-
-        cx.update(|cx| {
-            cx.emit_global(GlobalEvent(3));
-        });
-
-        drop(second_subscription);
-
-        cx.update(|cx| {
-            cx.emit_global(GlobalEvent(4));
-        });
-
-        assert_eq!(
-            &*events.borrow(),
-            &[
-                ("First", GlobalEvent(1)),
-                ("Second", GlobalEvent(1)),
-                ("First", GlobalEvent(2)),
-                ("Second", GlobalEvent(2)),
-                ("Second", GlobalEvent(3)),
-            ]
-        );
-    }
-
-    #[crate::test(self)]
-    fn test_global_events_emitted_before_subscription_in_same_update_cycle(cx: &mut AppContext) {
-        let events = Rc::new(RefCell::new(Vec::new()));
-        cx.update(|cx| {
-            {
-                let events = events.clone();
-                drop(cx.subscribe_global(move |_: &(), _| {
-                    events.borrow_mut().push("dropped before emit");
-                }));
-            }
-
-            {
-                let events = events.clone();
-                cx.subscribe_global(move |_: &(), _| {
-                    events.borrow_mut().push("before emit");
-                })
-                .detach();
-            }
-
-            cx.emit_global(());
-
-            {
-                let events = events.clone();
-                cx.subscribe_global(move |_: &(), _| {
-                    events.borrow_mut().push("after emit");
-                })
-                .detach();
-            }
-        });
-
-        assert_eq!(*events.borrow(), ["before emit"]);
-    }
-
-    #[crate::test(self)]
-    fn test_global_nested_events(cx: &mut AppContext) {
-        #[derive(Clone, Debug, Eq, PartialEq)]
-        struct GlobalEvent(u64);
-
-        let events = Rc::new(RefCell::new(Vec::new()));
-
-        {
-            let events = events.clone();
-            cx.subscribe_global(move |e: &GlobalEvent, cx| {
-                events.borrow_mut().push(("Outer", e.clone()));
-
-                if e.0 == 1 {
-                    let events = events.clone();
-                    cx.subscribe_global(move |e: &GlobalEvent, _| {
-                        events.borrow_mut().push(("Inner", e.clone()));
-                    })
-                    .detach();
-                }
-            })
-            .detach();
-        }
-
-        cx.update(|cx| {
-            cx.emit_global(GlobalEvent(1));
-            cx.emit_global(GlobalEvent(2));
-            cx.emit_global(GlobalEvent(3));
-        });
-        cx.update(|cx| {
-            cx.emit_global(GlobalEvent(4));
-        });
-
-        assert_eq!(
-            &*events.borrow(),
-            &[
-                ("Outer", GlobalEvent(1)),
-                ("Outer", GlobalEvent(2)),
-                ("Outer", GlobalEvent(3)),
-                ("Outer", GlobalEvent(4)),
-                ("Inner", GlobalEvent(4)),
-            ]
-        );
-    }
-
-    #[crate::test(self)]
-    fn test_global(cx: &mut AppContext) {
-        type Global = usize;
-
-        let observation_count = Rc::new(RefCell::new(0));
-        let subscription = cx.observe_global::<Global, _>({
-            let observation_count = observation_count.clone();
-            move |_| {
-                *observation_count.borrow_mut() += 1;
-            }
-        });
-
-        assert!(!cx.has_global::<Global>());
-        assert_eq!(cx.default_global::<Global>(), &0);
-        assert_eq!(*observation_count.borrow(), 1);
-        assert!(cx.has_global::<Global>());
-        assert_eq!(
-            cx.update_global::<Global, _, _>(|global, _| {
-                *global = 1;
-                "Update Result"
-            }),
-            "Update Result"
-        );
-        assert_eq!(*observation_count.borrow(), 2);
-        assert_eq!(cx.global::<Global>(), &1);
-
-        drop(subscription);
-        cx.update_global::<Global, _, _>(|global, _| {
-            *global = 2;
-        });
-        assert_eq!(*observation_count.borrow(), 2);
-
-        type OtherGlobal = f32;
-
-        let observation_count = Rc::new(RefCell::new(0));
-        cx.observe_global::<OtherGlobal, _>({
-            let observation_count = observation_count.clone();
-            move |_| {
-                *observation_count.borrow_mut() += 1;
-            }
-        })
-        .detach();
-
-        assert_eq!(
-            cx.update_default_global::<OtherGlobal, _, _>(|global, _| {
-                assert_eq!(global, &0.0);
-                *global = 2.0;
-                "Default update result"
-            }),
-            "Default update result"
-        );
-        assert_eq!(cx.global::<OtherGlobal>(), &2.0);
-        assert_eq!(*observation_count.borrow(), 1);
-    }
-
-    #[crate::test(self)]
-    fn test_dropping_subscribers(cx: &mut TestAppContext) {
-        struct Model;
-
-        impl Entity for Model {
-            type Event = ();
-        }
-
-        let window = cx.add_window(|_| TestView::default());
-        let observing_view = window.add_view(cx, |_| TestView::default());
-        let emitting_view = window.add_view(cx, |_| TestView::default());
-        let observing_model = cx.add_model(|_| Model);
-        let observed_model = cx.add_model(|_| Model);
-
-        observing_view.update(cx, |_, cx| {
-            cx.subscribe(&emitting_view, |_, _, _, _| {}).detach();
-            cx.subscribe(&observed_model, |_, _, _, _| {}).detach();
-        });
-        observing_model.update(cx, |_, cx| {
-            cx.subscribe(&observed_model, |_, _, _, _| {}).detach();
-        });
-
-        cx.update(|_| {
-            drop(observing_view);
-            drop(observing_model);
-        });
-
-        emitting_view.update(cx, |_, cx| cx.emit(Default::default()));
-        observed_model.update(cx, |_, cx| cx.emit(()));
-    }
-
-    #[crate::test(self)]
-    fn test_view_emit_before_subscribe_in_same_update_cycle(cx: &mut AppContext) {
-        let window = cx.add_window::<TestView, _>(Default::default(), |cx| {
-            drop(cx.subscribe(&cx.handle(), {
-                move |this, _, _, _| this.events.push("dropped before flush".into())
-            }));
-            cx.subscribe(&cx.handle(), {
-                move |this, _, _, _| this.events.push("before emit".into())
-            })
-            .detach();
-            cx.emit("the event".into());
-            cx.subscribe(&cx.handle(), {
-                move |this, _, _, _| this.events.push("after emit".into())
-            })
-            .detach();
-            TestView { events: Vec::new() }
-        });
-
-        window.read_root_with(cx, |view, _| assert_eq!(view.events, ["before emit"]));
-    }
-
-    #[crate::test(self)]
-    fn test_observe_and_notify_from_view(cx: &mut TestAppContext) {
-        #[derive(Default)]
-        struct Model {
-            state: String,
-        }
-
-        impl Entity for Model {
-            type Event = ();
-        }
-
-        let window = cx.add_window(|_| TestView::default());
-        let view = window.root(cx);
-        let model = cx.add_model(|_| Model {
-            state: "old-state".into(),
-        });
-
-        view.update(cx, |_, c| {
-            c.observe(&model, |me, observed, cx| {
-                me.events.push(observed.read(cx).state.clone())
-            })
-            .detach();
-        });
-
-        model.update(cx, |model, cx| {
-            model.state = "new-state".into();
-            cx.notify();
-        });
-        view.read_with(cx, |view, _| assert_eq!(view.events, ["new-state"]));
-    }
-
-    #[crate::test(self)]
-    fn test_view_notify_before_observe_in_same_update_cycle(cx: &mut AppContext) {
-        let window = cx.add_window::<TestView, _>(Default::default(), |cx| {
-            drop(cx.observe(&cx.handle(), {
-                move |this, _, _| this.events.push("dropped before flush".into())
-            }));
-            cx.observe(&cx.handle(), {
-                move |this, _, _| this.events.push("before notify".into())
-            })
-            .detach();
-            cx.notify();
-            cx.observe(&cx.handle(), {
-                move |this, _, _| this.events.push("after notify".into())
-            })
-            .detach();
-            TestView { events: Vec::new() }
-        });
-
-        window.read_root_with(cx, |view, _| assert_eq!(view.events, ["before notify"]));
-    }
-
-    #[crate::test(self)]
-    fn test_notify_and_drop_observe_subscription_in_same_update_cycle(cx: &mut TestAppContext) {
-        struct Model;
-        impl Entity for Model {
-            type Event = ();
-        }
-
-        let model = cx.add_model(|_| Model);
-        let window = cx.add_window(|_| TestView::default());
-        let view = window.root(cx);
-
-        view.update(cx, |_, cx| {
-            model.update(cx, |_, cx| cx.notify());
-            drop(cx.observe(&model, move |this, _, _| {
-                this.events.push("model notified".into());
-            }));
-            model.update(cx, |_, cx| cx.notify());
-        });
-
-        for _ in 0..3 {
-            model.update(cx, |_, cx| cx.notify());
-        }
-        view.read_with(cx, |view, _| assert_eq!(view.events, Vec::<&str>::new()));
-    }
-
-    #[crate::test(self)]
-    fn test_dropping_observers(cx: &mut TestAppContext) {
-        struct Model;
-
-        impl Entity for Model {
-            type Event = ();
-        }
-
-        let window = cx.add_window(|_| TestView::default());
-        let observing_view = window.add_view(cx, |_| TestView::default());
-        let observing_model = cx.add_model(|_| Model);
-        let observed_model = cx.add_model(|_| Model);
-
-        observing_view.update(cx, |_, cx| {
-            cx.observe(&observed_model, |_, _, _| {}).detach();
-        });
-        observing_model.update(cx, |_, cx| {
-            cx.observe(&observed_model, |_, _, _| {}).detach();
-        });
-
-        cx.update(|_| {
-            drop(observing_view);
-            drop(observing_model);
-        });
-
-        observed_model.update(cx, |_, cx| cx.notify());
-    }
-
-    #[crate::test(self)]
-    fn test_dropping_subscriptions_during_callback(cx: &mut TestAppContext) {
-        struct Model;
-
-        impl Entity for Model {
-            type Event = u64;
-        }
-
-        // Events
-        let observing_model = cx.add_model(|_| Model);
-        let observed_model = cx.add_model(|_| Model);
-
-        let events = Rc::new(RefCell::new(Vec::new()));
-
-        observing_model.update(cx, |_, cx| {
-            let events = events.clone();
-            let subscription = Rc::new(RefCell::new(None));
-            *subscription.borrow_mut() = Some(cx.subscribe(&observed_model, {
-                let subscription = subscription.clone();
-                move |_, _, e, _| {
-                    subscription.borrow_mut().take();
-                    events.borrow_mut().push(*e);
-                }
-            }));
-        });
-
-        observed_model.update(cx, |_, cx| {
-            cx.emit(1);
-            cx.emit(2);
-        });
-
-        assert_eq!(*events.borrow(), [1]);
-
-        // Global Events
-        #[derive(Clone, Debug, Eq, PartialEq)]
-        struct GlobalEvent(u64);
-
-        let events = Rc::new(RefCell::new(Vec::new()));
-
-        {
-            let events = events.clone();
-            let subscription = Rc::new(RefCell::new(None));
-            *subscription.borrow_mut() = Some(cx.subscribe_global({
-                let subscription = subscription.clone();
-                move |e: &GlobalEvent, _| {
-                    subscription.borrow_mut().take();
-                    events.borrow_mut().push(e.clone());
-                }
-            }));
-        }
-
-        cx.update(|cx| {
-            cx.emit_global(GlobalEvent(1));
-            cx.emit_global(GlobalEvent(2));
-        });
-
-        assert_eq!(*events.borrow(), [GlobalEvent(1)]);
-
-        // Model Observation
-        let observing_model = cx.add_model(|_| Model);
-        let observed_model = cx.add_model(|_| Model);
-
-        let observation_count = Rc::new(RefCell::new(0));
-
-        observing_model.update(cx, |_, cx| {
-            let observation_count = observation_count.clone();
-            let subscription = Rc::new(RefCell::new(None));
-            *subscription.borrow_mut() = Some(cx.observe(&observed_model, {
-                let subscription = subscription.clone();
-                move |_, _, _| {
-                    subscription.borrow_mut().take();
-                    *observation_count.borrow_mut() += 1;
-                }
-            }));
-        });
-
-        observed_model.update(cx, |_, cx| {
-            cx.notify();
-        });
-
-        observed_model.update(cx, |_, cx| {
-            cx.notify();
-        });
-
-        assert_eq!(*observation_count.borrow(), 1);
-
-        // View Observation
-        struct View;
-
-        impl Entity for View {
-            type Event = ();
-        }
-
-        impl super::View for View {
-            fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
-                Empty::new().into_any()
-            }
-
-            fn ui_name() -> &'static str {
-                "View"
-            }
-        }
-
-        let window = cx.add_window(|_| View);
-        let observing_view = window.add_view(cx, |_| View);
-        let observed_view = window.add_view(cx, |_| View);
-
-        let observation_count = Rc::new(RefCell::new(0));
-        observing_view.update(cx, |_, cx| {
-            let observation_count = observation_count.clone();
-            let subscription = Rc::new(RefCell::new(None));
-            *subscription.borrow_mut() = Some(cx.observe(&observed_view, {
-                let subscription = subscription.clone();
-                move |_, _, _| {
-                    subscription.borrow_mut().take();
-                    *observation_count.borrow_mut() += 1;
-                }
-            }));
-        });
-
-        observed_view.update(cx, |_, cx| {
-            cx.notify();
-        });
-
-        observed_view.update(cx, |_, cx| {
-            cx.notify();
-        });
-
-        assert_eq!(*observation_count.borrow(), 1);
-
-        // Global Observation
-        let observation_count = Rc::new(RefCell::new(0));
-        let subscription = Rc::new(RefCell::new(None));
-        *subscription.borrow_mut() = Some(cx.observe_global::<(), _>({
-            let observation_count = observation_count.clone();
-            let subscription = subscription.clone();
-            move |_| {
-                subscription.borrow_mut().take();
-                *observation_count.borrow_mut() += 1;
-            }
-        }));
-
-        cx.update(|cx| {
-            cx.default_global::<()>();
-            cx.set_global(());
-        });
-        assert_eq!(*observation_count.borrow(), 1);
-    }
-
-    #[crate::test(self)]
-    fn test_focus(cx: &mut TestAppContext) {
-        struct View {
-            name: String,
-            events: Arc<Mutex<Vec<String>>>,
-            child: Option<AnyViewHandle>,
-        }
-
-        impl Entity for View {
-            type Event = ();
-        }
-
-        impl super::View for View {
-            fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-                self.child
-                    .as_ref()
-                    .map(|child| ChildView::new(child, cx).into_any())
-                    .unwrap_or(Empty::new().into_any())
-            }
-
-            fn ui_name() -> &'static str {
-                "View"
-            }
-
-            fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext<Self>) {
-                if cx.handle().id() == focused.id() {
-                    self.events.lock().push(format!("{} focused", &self.name));
-                }
-            }
-
-            fn focus_out(&mut self, blurred: AnyViewHandle, cx: &mut ViewContext<Self>) {
-                if cx.handle().id() == blurred.id() {
-                    self.events.lock().push(format!("{} blurred", &self.name));
-                }
-            }
-        }
-
-        let view_events: Arc<Mutex<Vec<String>>> = Default::default();
-        let window = cx.add_window(|_| View {
-            events: view_events.clone(),
-            name: "view 1".to_string(),
-            child: None,
-        });
-        let view_1 = window.root(cx);
-        let view_2 = window.update(cx, |cx| {
-            let view_2 = cx.add_view(|_| View {
-                events: view_events.clone(),
-                name: "view 2".to_string(),
-                child: None,
-            });
-            view_1.update(cx, |view_1, cx| {
-                view_1.child = Some(view_2.clone().into_any());
-                cx.notify();
-            });
-            view_2
-        });
-
-        let observed_events: Arc<Mutex<Vec<String>>> = Default::default();
-        view_1.update(cx, |_, cx| {
-            cx.observe_focus(&view_2, {
-                let observed_events = observed_events.clone();
-                move |this, view, focused, cx| {
-                    let label = if focused { "focus" } else { "blur" };
-                    observed_events.lock().push(format!(
-                        "{} observed {}'s {}",
-                        this.name,
-                        view.read(cx).name,
-                        label
-                    ))
-                }
-            })
-            .detach();
-        });
-        view_2.update(cx, |_, cx| {
-            cx.observe_focus(&view_1, {
-                let observed_events = observed_events.clone();
-                move |this, view, focused, cx| {
-                    let label = if focused { "focus" } else { "blur" };
-                    observed_events.lock().push(format!(
-                        "{} observed {}'s {}",
-                        this.name,
-                        view.read(cx).name,
-                        label
-                    ))
-                }
-            })
-            .detach();
-        });
-        assert_eq!(mem::take(&mut *view_events.lock()), ["view 1 focused"]);
-        assert_eq!(mem::take(&mut *observed_events.lock()), Vec::<&str>::new());
-
-        view_1.update(cx, |_, cx| {
-            // Ensure only the last focus event is honored.
-            cx.focus(&view_2);
-            cx.focus(&view_1);
-            cx.focus(&view_2);
-        });
-
-        assert_eq!(
-            mem::take(&mut *view_events.lock()),
-            ["view 1 blurred", "view 2 focused"],
-        );
-        assert_eq!(
-            mem::take(&mut *observed_events.lock()),
-            [
-                "view 2 observed view 1's blur",
-                "view 1 observed view 2's focus"
-            ]
-        );
-
-        view_1.update(cx, |_, cx| cx.focus(&view_1));
-        assert_eq!(
-            mem::take(&mut *view_events.lock()),
-            ["view 2 blurred", "view 1 focused"],
-        );
-        assert_eq!(
-            mem::take(&mut *observed_events.lock()),
-            [
-                "view 1 observed view 2's blur",
-                "view 2 observed view 1's focus"
-            ]
-        );
-
-        view_1.update(cx, |_, cx| cx.focus(&view_2));
-        assert_eq!(
-            mem::take(&mut *view_events.lock()),
-            ["view 1 blurred", "view 2 focused"],
-        );
-        assert_eq!(
-            mem::take(&mut *observed_events.lock()),
-            [
-                "view 2 observed view 1's blur",
-                "view 1 observed view 2's focus"
-            ]
-        );
-
-        println!("=====================");
-        view_1.update(cx, |view, _| {
-            drop(view_2);
-            view.child = None;
-        });
-        assert_eq!(mem::take(&mut *view_events.lock()), ["view 1 focused"]);
-        assert_eq!(mem::take(&mut *observed_events.lock()), Vec::<&str>::new());
-    }
-
-    #[crate::test(self)]
-    fn test_deserialize_actions(cx: &mut AppContext) {
-        #[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
-        pub struct ComplexAction {
-            arg: String,
-            count: usize,
-        }
-
-        actions!(test::something, [SimpleAction]);
-        impl_actions!(test::something, [ComplexAction]);
-
-        cx.add_global_action(move |_: &SimpleAction, _: &mut AppContext| {});
-        cx.add_global_action(move |_: &ComplexAction, _: &mut AppContext| {});
-
-        let action1 = cx
-            .deserialize_action(
-                "test::something::ComplexAction",
-                Some(serde_json::from_str(r#"{"arg": "a", "count": 5}"#).unwrap()),
-            )
-            .unwrap();
-        let action2 = cx
-            .deserialize_action("test::something::SimpleAction", None)
-            .unwrap();
-        assert_eq!(
-            action1.as_any().downcast_ref::<ComplexAction>().unwrap(),
-            &ComplexAction {
-                arg: "a".to_string(),
-                count: 5,
-            }
-        );
-        assert_eq!(
-            action2.as_any().downcast_ref::<SimpleAction>().unwrap(),
-            &SimpleAction
-        );
-    }
-
-    #[crate::test(self)]
-    fn test_dispatch_action(cx: &mut TestAppContext) {
-        struct ViewA {
-            id: usize,
-            child: Option<AnyViewHandle>,
-        }
-
-        impl Entity for ViewA {
-            type Event = ();
-        }
-
-        impl View for ViewA {
-            fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-                self.child
-                    .as_ref()
-                    .map(|child| ChildView::new(child, cx).into_any())
-                    .unwrap_or(Empty::new().into_any())
-            }
-
-            fn ui_name() -> &'static str {
-                "View"
-            }
-        }
-
-        struct ViewB {
-            id: usize,
-            child: Option<AnyViewHandle>,
-        }
-
-        impl Entity for ViewB {
-            type Event = ();
-        }
-
-        impl View for ViewB {
-            fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-                self.child
-                    .as_ref()
-                    .map(|child| ChildView::new(child, cx).into_any())
-                    .unwrap_or(Empty::new().into_any())
-            }
-
-            fn ui_name() -> &'static str {
-                "View"
-            }
-        }
-
-        #[derive(Clone, Default, Deserialize, PartialEq)]
-        pub struct Action(pub String);
-
-        impl_actions!(test, [Action]);
-
-        let actions = Rc::new(RefCell::new(Vec::new()));
-        let observed_actions = Rc::new(RefCell::new(Vec::new()));
-
-        cx.update(|cx| {
-            cx.add_global_action({
-                let actions = actions.clone();
-                move |_: &Action, _: &mut AppContext| {
-                    actions.borrow_mut().push("global".to_string());
-                }
-            });
-
-            cx.add_action({
-                let actions = actions.clone();
-                move |view: &mut ViewA, action: &Action, cx| {
-                    assert_eq!(action.0, "bar");
-                    cx.propagate_action();
-                    actions.borrow_mut().push(format!("{} a", view.id));
-                }
-            });
-
-            cx.add_action({
-                let actions = actions.clone();
-                move |view: &mut ViewA, _: &Action, cx| {
-                    if view.id != 1 {
-                        cx.add_view(|cx| {
-                            cx.propagate_action(); // Still works on a nested ViewContext
-                            ViewB { id: 5, child: None }
-                        });
-                    }
-                    actions.borrow_mut().push(format!("{} b", view.id));
-                }
-            });
-
-            cx.add_action({
-                let actions = actions.clone();
-                move |view: &mut ViewB, _: &Action, cx| {
-                    cx.propagate_action();
-                    actions.borrow_mut().push(format!("{} c", view.id));
-                }
-            });
-
-            cx.add_action({
-                let actions = actions.clone();
-                move |view: &mut ViewB, _: &Action, cx| {
-                    cx.propagate_action();
-                    actions.borrow_mut().push(format!("{} d", view.id));
-                }
-            });
-
-            cx.capture_action({
-                let actions = actions.clone();
-                move |view: &mut ViewA, _: &Action, cx| {
-                    cx.propagate_action();
-                    actions.borrow_mut().push(format!("{} capture", view.id));
-                }
-            });
-
-            cx.observe_actions({
-                let observed_actions = observed_actions.clone();
-                move |action_id, _| observed_actions.borrow_mut().push(action_id)
-            })
-            .detach();
-        });
-
-        let window = cx.add_window(|_| ViewA { id: 1, child: None });
-        let view_1 = window.root(cx);
-        let view_2 = window.update(cx, |cx| {
-            let child = cx.add_view(|_| ViewB { id: 2, child: None });
-            view_1.update(cx, |view, cx| {
-                view.child = Some(child.clone().into_any());
-                cx.notify();
-            });
-            child
-        });
-        let view_3 = window.update(cx, |cx| {
-            let child = cx.add_view(|_| ViewA { id: 3, child: None });
-            view_2.update(cx, |view, cx| {
-                view.child = Some(child.clone().into_any());
-                cx.notify();
-            });
-            child
-        });
-        let view_4 = window.update(cx, |cx| {
-            let child = cx.add_view(|_| ViewB { id: 4, child: None });
-            view_3.update(cx, |view, cx| {
-                view.child = Some(child.clone().into_any());
-                cx.notify();
-            });
-            child
-        });
-
-        window.update(cx, |cx| {
-            cx.dispatch_action(Some(view_4.id()), &Action("bar".to_string()))
-        });
-
-        assert_eq!(
-            *actions.borrow(),
-            vec![
-                "1 capture",
-                "3 capture",
-                "4 d",
-                "4 c",
-                "3 b",
-                "3 a",
-                "2 d",
-                "2 c",
-                "1 b"
-            ]
-        );
-        assert_eq!(*observed_actions.borrow(), [Action::default().id()]);
-
-        // Remove view_1, which doesn't propagate the action
-
-        let window = cx.add_window(|_| ViewB { id: 2, child: None });
-        let view_2 = window.root(cx);
-        let view_3 = window.update(cx, |cx| {
-            let child = cx.add_view(|_| ViewA { id: 3, child: None });
-            view_2.update(cx, |view, cx| {
-                view.child = Some(child.clone().into_any());
-                cx.notify();
-            });
-            child
-        });
-        let view_4 = window.update(cx, |cx| {
-            let child = cx.add_view(|_| ViewB { id: 4, child: None });
-            view_3.update(cx, |view, cx| {
-                view.child = Some(child.clone().into_any());
-                cx.notify();
-            });
-            child
-        });
-
-        actions.borrow_mut().clear();
-        window.update(cx, |cx| {
-            cx.dispatch_action(Some(view_4.id()), &Action("bar".to_string()))
-        });
-
-        assert_eq!(
-            *actions.borrow(),
-            vec![
-                "3 capture",
-                "4 d",
-                "4 c",
-                "3 b",
-                "3 a",
-                "2 d",
-                "2 c",
-                "global"
-            ]
-        );
-        assert_eq!(
-            *observed_actions.borrow(),
-            [Action::default().id(), Action::default().id()]
-        );
-    }
-
-    #[crate::test(self)]
-    fn test_dispatch_keystroke(cx: &mut AppContext) {
-        #[derive(Clone, Deserialize, PartialEq)]
-        pub struct Action(String);
-
-        impl_actions!(test, [Action]);
-
-        struct View {
-            id: usize,
-            keymap_context: KeymapContext,
-            child: Option<AnyViewHandle>,
-        }
-
-        impl Entity for View {
-            type Event = ();
-        }
-
-        impl super::View for View {
-            fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-                self.child
-                    .as_ref()
-                    .map(|child| ChildView::new(child, cx).into_any())
-                    .unwrap_or(Empty::new().into_any())
-            }
-
-            fn ui_name() -> &'static str {
-                "View"
-            }
-
-            fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
-                *keymap = self.keymap_context.clone();
-            }
-        }
-
-        impl View {
-            fn new(id: usize) -> Self {
-                View {
-                    id,
-                    keymap_context: KeymapContext::default(),
-                    child: None,
-                }
-            }
-        }
-
-        let mut view_1 = View::new(1);
-        let mut view_2 = View::new(2);
-        let mut view_3 = View::new(3);
-        view_1.keymap_context.add_identifier("a");
-        view_2.keymap_context.add_identifier("a");
-        view_2.keymap_context.add_identifier("b");
-        view_3.keymap_context.add_identifier("a");
-        view_3.keymap_context.add_identifier("b");
-        view_3.keymap_context.add_identifier("c");
-
-        let window = cx.add_window(Default::default(), |cx| {
-            let view_2 = cx.add_view(|cx| {
-                let view_3 = cx.add_view(|cx| {
-                    cx.focus_self();
-                    view_3
-                });
-                view_2.child = Some(view_3.into_any());
-                view_2
-            });
-            view_1.child = Some(view_2.into_any());
-            view_1
-        });
-
-        // This binding only dispatches an action on view 2 because that view will have
-        // "a" and "b" in its context, but not "c".
-        cx.add_bindings(vec![Binding::new(
-            "a",
-            Action("a".to_string()),
-            Some("a && b && !c"),
-        )]);
-
-        cx.add_bindings(vec![Binding::new("b", Action("b".to_string()), None)]);
-
-        // This binding only dispatches an action on views 2 and 3, because they have
-        // a parent view with a in its context
-        cx.add_bindings(vec![Binding::new(
-            "c",
-            Action("c".to_string()),
-            Some("b > c"),
-        )]);
-
-        // This binding only dispatches an action on view 2, because they have
-        // a parent view with a in its context
-        cx.add_bindings(vec![Binding::new(
-            "d",
-            Action("d".to_string()),
-            Some("a && !b > b"),
-        )]);
-
-        let actions = Rc::new(RefCell::new(Vec::new()));
-        cx.add_action({
-            let actions = actions.clone();
-            move |view: &mut View, action: &Action, cx| {
-                actions
-                    .borrow_mut()
-                    .push(format!("{} {}", view.id, action.0));
-
-                if action.0 == "b" {
-                    cx.propagate_action();
-                }
-            }
-        });
-
-        cx.add_global_action({
-            let actions = actions.clone();
-            move |action: &Action, _| {
-                actions.borrow_mut().push(format!("global {}", action.0));
-            }
-        });
-
-        window.update(cx, |cx| {
-            cx.dispatch_keystroke(&Keystroke::parse("a").unwrap())
-        });
-        assert_eq!(&*actions.borrow(), &["2 a"]);
-        actions.borrow_mut().clear();
-
-        window.update(cx, |cx| {
-            cx.dispatch_keystroke(&Keystroke::parse("b").unwrap());
-        });
-
-        assert_eq!(&*actions.borrow(), &["3 b", "2 b", "1 b", "global b"]);
-        actions.borrow_mut().clear();
-
-        window.update(cx, |cx| {
-            cx.dispatch_keystroke(&Keystroke::parse("c").unwrap());
-        });
-        assert_eq!(&*actions.borrow(), &["3 c"]);
-        actions.borrow_mut().clear();
-
-        window.update(cx, |cx| {
-            cx.dispatch_keystroke(&Keystroke::parse("d").unwrap());
-        });
-        assert_eq!(&*actions.borrow(), &["2 d"]);
-        actions.borrow_mut().clear();
-    }
-
-    #[crate::test(self)]
-    fn test_keystrokes_for_action(cx: &mut TestAppContext) {
-        actions!(test, [Action1, Action2, Action3, GlobalAction]);
-
-        struct View1 {
-            child: ViewHandle<View2>,
-        }
-        struct View2 {}
-
-        impl Entity for View1 {
-            type Event = ();
-        }
-        impl Entity for View2 {
-            type Event = ();
-        }
-
-        impl super::View for View1 {
-            fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-                ChildView::new(&self.child, cx).into_any()
-            }
-            fn ui_name() -> &'static str {
-                "View1"
-            }
-        }
-        impl super::View for View2 {
-            fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
-                Empty::new().into_any()
-            }
-            fn ui_name() -> &'static str {
-                "View2"
-            }
-        }
-
-        let window = cx.add_window(|cx| {
-            let view_2 = cx.add_view(|cx| {
-                cx.focus_self();
-                View2 {}
-            });
-            View1 { child: view_2 }
-        });
-        let view_1 = window.root(cx);
-        let view_2 = view_1.read_with(cx, |view, _| view.child.clone());
-
-        cx.update(|cx| {
-            cx.add_action(|_: &mut View1, _: &Action1, _cx| {});
-            cx.add_action(|_: &mut View1, _: &Action3, _cx| {});
-            cx.add_action(|_: &mut View2, _: &Action2, _cx| {});
-            cx.add_global_action(|_: &GlobalAction, _| {});
-            cx.add_bindings(vec![
-                Binding::new("a", Action1, Some("View1")),
-                Binding::new("b", Action2, Some("View1 > View2")),
-                Binding::new("c", Action3, Some("View2")),
-                Binding::new("d", GlobalAction, Some("View3")), // View 3 does not exist
-            ]);
-        });
-
-        let view_1_id = view_1.id();
-        view_1.update(cx, |_, cx| {
-            view_2.update(cx, |_, cx| {
-                // Sanity check
-                assert_eq!(
-                    cx.keystrokes_for_action(view_1_id, &Action1)
-                        .unwrap()
-                        .as_slice(),
-                    &[Keystroke::parse("a").unwrap()]
-                );
-                assert_eq!(
-                    cx.keystrokes_for_action(view_2.id(), &Action2)
-                        .unwrap()
-                        .as_slice(),
-                    &[Keystroke::parse("b").unwrap()]
-                );
-                assert_eq!(cx.keystrokes_for_action(view_1.id(), &Action3), None);
-                assert_eq!(
-                    cx.keystrokes_for_action(view_2.id(), &Action3)
-                        .unwrap()
-                        .as_slice(),
-                    &[Keystroke::parse("c").unwrap()]
-                );
-
-                // The 'a' keystroke propagates up the view tree from view_2
-                // to view_1. The action, Action1, is handled by view_1.
-                assert_eq!(
-                    cx.keystrokes_for_action(view_2.id(), &Action1)
-                        .unwrap()
-                        .as_slice(),
-                    &[Keystroke::parse("a").unwrap()]
-                );
-
-                // Actions that are handled below the current view don't have bindings
-                assert_eq!(cx.keystrokes_for_action(view_1_id, &Action2), None);
-
-                // Actions that are handled in other branches of the tree should not have a binding
-                assert_eq!(cx.keystrokes_for_action(view_2.id(), &GlobalAction), None);
-            });
-        });
-
-        // Check that global actions do not have a binding, even if a binding does exist in another view
-        assert_eq!(
-            &available_actions(window.into(), view_1.id(), cx),
-            &[
-                ("test::Action1", vec![Keystroke::parse("a").unwrap()]),
-                ("test::Action3", vec![]),
-                ("test::GlobalAction", vec![]),
-            ],
-        );
-
-        // Check that view 1 actions and bindings are available even when called from view 2
-        assert_eq!(
-            &available_actions(window.into(), view_2.id(), cx),
-            &[
-                ("test::Action1", vec![Keystroke::parse("a").unwrap()]),
-                ("test::Action2", vec![Keystroke::parse("b").unwrap()]),
-                ("test::Action3", vec![Keystroke::parse("c").unwrap()]),
-                ("test::GlobalAction", vec![]),
-            ],
-        );
-
-        // Produces a list of actions and key bindings
-        fn available_actions(
-            window: AnyWindowHandle,
-            view_id: usize,
-            cx: &TestAppContext,
-        ) -> Vec<(&'static str, Vec<Keystroke>)> {
-            cx.available_actions(window.into(), view_id)
-                .into_iter()
-                .map(|(action_name, _, bindings)| {
-                    (
-                        action_name,
-                        bindings
-                            .iter()
-                            .map(|binding| binding.keystrokes()[0].clone())
-                            .collect::<Vec<_>>(),
-                    )
-                })
-                .sorted_by(|(name1, _), (name2, _)| name1.cmp(name2))
-                .collect()
-        }
-    }
-
-    #[crate::test(self)]
-    fn test_keystrokes_for_action_with_data(cx: &mut TestAppContext) {
-        #[derive(Clone, Debug, Deserialize, PartialEq)]
-        struct ActionWithArg {
-            #[serde(default)]
-            arg: bool,
-        }
-
-        struct View;
-        impl super::Entity for View {
-            type Event = ();
-        }
-        impl super::View for View {
-            fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
-                Empty::new().into_any()
-            }
-            fn ui_name() -> &'static str {
-                "View"
-            }
-        }
-
-        impl_actions!(test, [ActionWithArg]);
-
-        let window = cx.add_window(|_| View);
-        let view = window.root(cx);
-        cx.update(|cx| {
-            cx.add_global_action(|_: &ActionWithArg, _| {});
-            cx.add_bindings(vec![
-                Binding::new("a", ActionWithArg { arg: false }, None),
-                Binding::new("shift-a", ActionWithArg { arg: true }, None),
-            ]);
-        });
-
-        let actions = cx.available_actions(window.into(), view.id());
-        assert_eq!(
-            actions[0].1.as_any().downcast_ref::<ActionWithArg>(),
-            Some(&ActionWithArg { arg: false })
-        );
-        assert_eq!(
-            actions[0]
-                .2
-                .iter()
-                .map(|b| b.keystrokes()[0].clone())
-                .collect::<Vec<_>>(),
-            vec![Keystroke::parse("a").unwrap()],
-        );
-    }
-
-    #[crate::test(self)]
-    async fn test_model_condition(cx: &mut TestAppContext) {
-        struct Counter(usize);
-
-        impl super::Entity for Counter {
-            type Event = ();
-        }
-
-        impl Counter {
-            fn inc(&mut self, cx: &mut ModelContext<Self>) {
-                self.0 += 1;
-                cx.notify();
-            }
-        }
-
-        let model = cx.add_model(|_| Counter(0));
-
-        let condition1 = model.condition(cx, |model, _| model.0 == 2);
-        let condition2 = model.condition(cx, |model, _| model.0 == 3);
-        smol::pin!(condition1, condition2);
-
-        model.update(cx, |model, cx| model.inc(cx));
-        assert_eq!(poll_once(&mut condition1).await, None);
-        assert_eq!(poll_once(&mut condition2).await, None);
-
-        model.update(cx, |model, cx| model.inc(cx));
-        assert_eq!(poll_once(&mut condition1).await, Some(()));
-        assert_eq!(poll_once(&mut condition2).await, None);
-
-        model.update(cx, |model, cx| model.inc(cx));
-        assert_eq!(poll_once(&mut condition2).await, Some(()));
-
-        model.update(cx, |_, cx| cx.notify());
-    }
-
-    #[crate::test(self)]
-    #[should_panic]
-    async fn test_model_condition_timeout(cx: &mut TestAppContext) {
-        struct Model;
-
-        impl super::Entity for Model {
-            type Event = ();
-        }
-
-        let model = cx.add_model(|_| Model);
-        model.condition(cx, |_, _| false).await;
-    }
-
-    #[crate::test(self)]
-    #[should_panic(expected = "model dropped with pending condition")]
-    async fn test_model_condition_panic_on_drop(cx: &mut TestAppContext) {
-        struct Model;
-
-        impl super::Entity for Model {
-            type Event = ();
-        }
-
-        let model = cx.add_model(|_| Model);
-        let condition = model.condition(cx, |_, _| false);
-        cx.update(|_| drop(model));
-        condition.await;
-    }
-
-    #[crate::test(self)]
-    async fn test_view_condition(cx: &mut TestAppContext) {
-        struct Counter(usize);
-
-        impl super::Entity for Counter {
-            type Event = ();
-        }
-
-        impl super::View for Counter {
-            fn ui_name() -> &'static str {
-                "test view"
-            }
-
-            fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
-                Empty::new().into_any()
-            }
-        }
-
-        impl Counter {
-            fn inc(&mut self, cx: &mut ViewContext<Self>) {
-                self.0 += 1;
-                cx.notify();
-            }
-        }
-
-        let window = cx.add_window(|_| Counter(0));
-        let view = window.root(cx);
-
-        let condition1 = view.condition(cx, |view, _| view.0 == 2);
-        let condition2 = view.condition(cx, |view, _| view.0 == 3);
-        smol::pin!(condition1, condition2);
-
-        view.update(cx, |view, cx| view.inc(cx));
-        assert_eq!(poll_once(&mut condition1).await, None);
-        assert_eq!(poll_once(&mut condition2).await, None);
-
-        view.update(cx, |view, cx| view.inc(cx));
-        assert_eq!(poll_once(&mut condition1).await, Some(()));
-        assert_eq!(poll_once(&mut condition2).await, None);
-
-        view.update(cx, |view, cx| view.inc(cx));
-        assert_eq!(poll_once(&mut condition2).await, Some(()));
-        view.update(cx, |_, cx| cx.notify());
-    }
-
-    #[crate::test(self)]
-    #[should_panic]
-    async fn test_view_condition_timeout(cx: &mut TestAppContext) {
-        let window = cx.add_window(|_| TestView::default());
-        window.root(cx).condition(cx, |_, _| false).await;
-    }
-
-    #[crate::test(self)]
-    #[should_panic(expected = "view dropped with pending condition")]
-    async fn test_view_condition_panic_on_drop(cx: &mut TestAppContext) {
-        let window = cx.add_window(|_| TestView::default());
-        let view = window.add_view(cx, |_| TestView::default());
-
-        let condition = view.condition(cx, |_, _| false);
-        cx.update(|_| drop(view));
-        condition.await;
-    }
-
-    #[crate::test(self)]
-    fn test_refresh_windows(cx: &mut TestAppContext) {
-        struct View(usize);
-
-        impl super::Entity for View {
-            type Event = ();
-        }
-
-        impl super::View for View {
-            fn ui_name() -> &'static str {
-                "test view"
-            }
-
-            fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
-                Empty::new().into_any_named(format!("render count: {}", post_inc(&mut self.0)))
-            }
-        }
-
-        let window = cx.add_window(|_| View(0));
-        let root_view = window.root(cx);
-        window.update(cx, |cx| {
-            assert_eq!(
-                cx.window.rendered_views[&root_view.id()].name(),
-                Some("render count: 0")
-            );
-        });
-
-        let view = window.update(cx, |cx| {
-            cx.refresh_windows();
-            cx.add_view(|_| View(0))
-        });
-
-        window.update(cx, |cx| {
-            assert_eq!(
-                cx.window.rendered_views[&root_view.id()].name(),
-                Some("render count: 1")
-            );
-            assert_eq!(
-                cx.window.rendered_views[&view.id()].name(),
-                Some("render count: 0")
-            );
-        });
-
-        cx.update(|cx| cx.refresh_windows());
-
-        window.update(cx, |cx| {
-            assert_eq!(
-                cx.window.rendered_views[&root_view.id()].name(),
-                Some("render count: 2")
-            );
-            assert_eq!(
-                cx.window.rendered_views[&view.id()].name(),
-                Some("render count: 1")
-            );
-        });
-
-        cx.update(|cx| {
-            cx.refresh_windows();
-            drop(view);
-        });
-
-        window.update(cx, |cx| {
-            assert_eq!(
-                cx.window.rendered_views[&root_view.id()].name(),
-                Some("render count: 3")
-            );
-            assert_eq!(cx.window.rendered_views.len(), 1);
-        });
-    }
-
-    #[crate::test(self)]
-    async fn test_labeled_tasks(cx: &mut TestAppContext) {
-        assert_eq!(None, cx.update(|cx| cx.active_labeled_tasks().next()));
-        let (mut sender, mut receiver) = postage::oneshot::channel::<()>();
-        let task = cx
-            .update(|cx| cx.spawn_labeled("Test Label", |_| async move { receiver.recv().await }));
-
-        assert_eq!(
-            Some("Test Label"),
-            cx.update(|cx| cx.active_labeled_tasks().next())
-        );
-        sender
-            .send(())
-            .await
-            .expect("Could not send message to complete task");
-        task.await;
-
-        assert_eq!(None, cx.update(|cx| cx.active_labeled_tasks().next()));
-    }
-
-    #[crate::test(self)]
-    async fn test_window_activation(cx: &mut TestAppContext) {
-        struct View(&'static str);
-
-        impl super::Entity for View {
-            type Event = ();
-        }
-
-        impl super::View for View {
-            fn ui_name() -> &'static str {
-                "test view"
-            }
-
-            fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
-                Empty::new().into_any()
-            }
-        }
-
-        let events = Rc::new(RefCell::new(Vec::new()));
-        let window_1 = cx.add_window(|cx: &mut ViewContext<View>| {
-            cx.observe_window_activation({
-                let events = events.clone();
-                move |this, active, _| events.borrow_mut().push((this.0, active))
-            })
-            .detach();
-            View("window 1")
-        });
-        assert_eq!(mem::take(&mut *events.borrow_mut()), [("window 1", true)]);
-
-        let window_2 = cx.add_window(|cx: &mut ViewContext<View>| {
-            cx.observe_window_activation({
-                let events = events.clone();
-                move |this, active, _| events.borrow_mut().push((this.0, active))
-            })
-            .detach();
-            View("window 2")
-        });
-        assert_eq!(
-            mem::take(&mut *events.borrow_mut()),
-            [("window 1", false), ("window 2", true)]
-        );
-
-        let window_3 = cx.add_window(|cx: &mut ViewContext<View>| {
-            cx.observe_window_activation({
-                let events = events.clone();
-                move |this, active, _| events.borrow_mut().push((this.0, active))
-            })
-            .detach();
-            View("window 3")
-        });
-        assert_eq!(
-            mem::take(&mut *events.borrow_mut()),
-            [("window 2", false), ("window 3", true)]
-        );
-
-        window_2.simulate_activation(cx);
-        assert_eq!(
-            mem::take(&mut *events.borrow_mut()),
-            [("window 3", false), ("window 2", true)]
-        );
-
-        window_1.simulate_activation(cx);
-        assert_eq!(
-            mem::take(&mut *events.borrow_mut()),
-            [("window 2", false), ("window 1", true)]
-        );
-
-        window_3.simulate_activation(cx);
-        assert_eq!(
-            mem::take(&mut *events.borrow_mut()),
-            [("window 1", false), ("window 3", true)]
-        );
-
-        window_3.simulate_activation(cx);
-        assert_eq!(mem::take(&mut *events.borrow_mut()), []);
-    }
-
-    #[crate::test(self)]
-    fn test_child_view(cx: &mut TestAppContext) {
-        struct Child {
-            rendered: Rc<Cell<bool>>,
-            dropped: Rc<Cell<bool>>,
-        }
-
-        impl super::Entity for Child {
-            type Event = ();
-        }
-
-        impl super::View for Child {
-            fn ui_name() -> &'static str {
-                "child view"
-            }
-
-            fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
-                self.rendered.set(true);
-                Empty::new().into_any()
-            }
-        }
-
-        impl Drop for Child {
-            fn drop(&mut self) {
-                self.dropped.set(true);
-            }
-        }
-
-        struct Parent {
-            child: Option<ViewHandle<Child>>,
-        }
-
-        impl super::Entity for Parent {
-            type Event = ();
-        }
-
-        impl super::View for Parent {
-            fn ui_name() -> &'static str {
-                "parent view"
-            }
-
-            fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-                if let Some(child) = self.child.as_ref() {
-                    ChildView::new(child, cx).into_any()
-                } else {
-                    Empty::new().into_any()
-                }
-            }
-        }
-
-        let child_rendered = Rc::new(Cell::new(false));
-        let child_dropped = Rc::new(Cell::new(false));
-        let window = cx.add_window(|cx| Parent {
-            child: Some(cx.add_view(|_| Child {
-                rendered: child_rendered.clone(),
-                dropped: child_dropped.clone(),
-            })),
-        });
-        let root_view = window.root(cx);
-        assert!(child_rendered.take());
-        assert!(!child_dropped.take());
-
-        root_view.update(cx, |view, cx| {
-            view.child.take();
-            cx.notify();
-        });
-        assert!(!child_rendered.take());
-        assert!(child_dropped.take());
-    }
-
-    #[derive(Default)]
-    struct TestView {
-        events: Vec<String>,
-    }
-
-    impl Entity for TestView {
-        type Event = String;
-    }
-
-    impl View for TestView {
-        fn ui_name() -> &'static str {
-            "TestView"
-        }
-
-        fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
-            Empty::new().into_any()
-        }
-    }
+#[derive(Debug)]
+pub struct KeystrokeEvent {
+    pub keystroke: Keystroke,
+    pub action: Option<Box<dyn Action>>,
 }

crates/gpui/src/app/action.rs 🔗

@@ -1,120 +0,0 @@
-use std::any::{Any, TypeId};
-
-use crate::TypeTag;
-
-pub trait Action: 'static {
-    fn id(&self) -> TypeId;
-    fn namespace(&self) -> &'static str;
-    fn name(&self) -> &'static str;
-    fn as_any(&self) -> &dyn Any;
-    fn type_tag(&self) -> TypeTag;
-    fn boxed_clone(&self) -> Box<dyn Action>;
-    fn eq(&self, other: &dyn Action) -> bool;
-
-    fn qualified_name() -> &'static str
-    where
-        Self: Sized;
-    fn from_json_str(json: serde_json::Value) -> anyhow::Result<Box<dyn Action>>
-    where
-        Self: Sized;
-}
-
-impl std::fmt::Debug for dyn Action {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("dyn Action")
-            .field("namespace", &self.namespace())
-            .field("name", &self.name())
-            .finish()
-    }
-}
-/// Define a set of unit struct types that all implement the `Action` trait.
-///
-/// The first argument is a namespace that will be associated with each of
-/// the given action types, to ensure that they have globally unique
-/// qualified names for use in keymap files.
-#[macro_export]
-macro_rules! actions {
-    ($namespace:path, [ $($name:ident),* $(,)? ]) => {
-        $(
-            #[derive(Clone, Debug, Default, PartialEq, Eq)]
-            pub struct $name;
-            $crate::__impl_action! {
-                $namespace,
-                $name,
-                fn from_json_str(_: $crate::serde_json::Value) -> $crate::anyhow::Result<Box<dyn $crate::Action>> {
-                    Ok(Box::new(Self))
-                }
-            }
-        )*
-    };
-}
-
-/// Implement the `Action` trait for a set of existing types.
-///
-/// The first argument is a namespace that will be associated with each of
-/// the given action types, to ensure that they have globally unique
-/// qualified names for use in keymap files.
-#[macro_export]
-macro_rules! impl_actions {
-    ($namespace:path, [ $($name:ident),* $(,)? ]) => {
-        $(
-            $crate::__impl_action! {
-                $namespace,
-                $name,
-                fn from_json_str(json: $crate::serde_json::Value) -> $crate::anyhow::Result<Box<dyn $crate::Action>> {
-                    Ok(Box::new($crate::serde_json::from_value::<Self>(json)?))
-                }
-            }
-        )*
-    };
-}
-
-#[doc(hidden)]
-#[macro_export]
-macro_rules! __impl_action {
-    ($namespace:path, $name:ident, $from_json_fn:item) => {
-        impl $crate::action::Action for $name {
-            fn namespace(&self) -> &'static str {
-                stringify!($namespace)
-            }
-
-            fn name(&self) -> &'static str {
-                stringify!($name)
-            }
-
-            fn qualified_name() -> &'static str {
-                concat!(
-                    stringify!($namespace),
-                    "::",
-                    stringify!($name),
-                )
-            }
-
-            fn id(&self) -> std::any::TypeId {
-                std::any::TypeId::of::<$name>()
-            }
-
-            fn as_any(&self) -> &dyn std::any::Any {
-                self
-            }
-
-            fn boxed_clone(&self) -> Box<dyn $crate::Action> {
-                Box::new(self.clone())
-            }
-
-            fn eq(&self, other: &dyn $crate::Action) -> bool {
-                if let Some(other) = other.as_any().downcast_ref::<Self>() {
-                    self == other
-                } else {
-                    false
-                }
-            }
-
-            fn type_tag(&self) -> $crate::TypeTag {
-                $crate::TypeTag::new::<Self>()
-            }
-
-            $from_json_fn
-        }
-    };
-}

crates/gpui/src/app/callback_collection.rs 🔗

@@ -1,164 +0,0 @@
-use collections::{BTreeMap, HashMap, HashSet};
-use parking_lot::Mutex;
-use std::sync::Arc;
-use std::{hash::Hash, sync::Weak};
-
-pub struct CallbackCollection<K: Clone + Hash + Eq, F> {
-    internal: Arc<Mutex<Mapping<K, F>>>,
-}
-
-pub struct Subscription<K: Clone + Hash + Eq, F> {
-    key: K,
-    id: usize,
-    mapping: Option<Weak<Mutex<Mapping<K, F>>>>,
-}
-
-struct Mapping<K, F> {
-    callbacks: HashMap<K, BTreeMap<usize, F>>,
-    dropped_subscriptions: HashMap<K, HashSet<usize>>,
-}
-
-impl<K: Hash + Eq, F> Mapping<K, F> {
-    fn clear_dropped_state(&mut self, key: &K, subscription_id: usize) -> bool {
-        if let Some(subscriptions) = self.dropped_subscriptions.get_mut(&key) {
-            subscriptions.remove(&subscription_id)
-        } else {
-            false
-        }
-    }
-}
-
-impl<K, F> Default for Mapping<K, F> {
-    fn default() -> Self {
-        Self {
-            callbacks: Default::default(),
-            dropped_subscriptions: Default::default(),
-        }
-    }
-}
-
-impl<K: Clone + Hash + Eq, F> Clone for CallbackCollection<K, F> {
-    fn clone(&self) -> Self {
-        Self {
-            internal: self.internal.clone(),
-        }
-    }
-}
-
-impl<K: Clone + Hash + Eq + Copy, F> Default for CallbackCollection<K, F> {
-    fn default() -> Self {
-        CallbackCollection {
-            internal: Arc::new(Mutex::new(Default::default())),
-        }
-    }
-}
-
-impl<K: Clone + Hash + Eq + Copy, F> CallbackCollection<K, F> {
-    #[cfg(test)]
-    pub fn is_empty(&self) -> bool {
-        self.internal.lock().callbacks.is_empty()
-    }
-
-    pub fn subscribe(&mut self, key: K, subscription_id: usize) -> Subscription<K, F> {
-        Subscription {
-            key,
-            id: subscription_id,
-            mapping: Some(Arc::downgrade(&self.internal)),
-        }
-    }
-
-    pub fn add_callback(&mut self, key: K, subscription_id: usize, callback: F) {
-        let mut this = self.internal.lock();
-
-        // If this callback's subscription was dropped before the callback was
-        // added, then just drop the callback.
-        if this.clear_dropped_state(&key, subscription_id) {
-            return;
-        }
-
-        this.callbacks
-            .entry(key)
-            .or_default()
-            .insert(subscription_id, callback);
-    }
-
-    pub fn remove(&mut self, key: K) {
-        // Drop these callbacks after releasing the lock, in case one of them
-        // owns a subscription to this callback collection.
-        let mut this = self.internal.lock();
-        let callbacks = this.callbacks.remove(&key);
-        this.dropped_subscriptions.remove(&key);
-        drop(this);
-        drop(callbacks);
-    }
-
-    pub fn emit<C>(&mut self, key: K, mut call_callback: C)
-    where
-        C: FnMut(&mut F) -> bool,
-    {
-        let callbacks = self.internal.lock().callbacks.remove(&key);
-        if let Some(callbacks) = callbacks {
-            for (subscription_id, mut callback) in callbacks {
-                // If this callback's subscription was dropped while invoking an
-                // earlier callback, then just drop the callback.
-                let mut this = self.internal.lock();
-                if this.clear_dropped_state(&key, subscription_id) {
-                    continue;
-                }
-
-                drop(this);
-                let alive = call_callback(&mut callback);
-
-                // If this callback's subscription was dropped while invoking the callback
-                // itself, or if the callback returns false, then just drop the callback.
-                let mut this = self.internal.lock();
-                if this.clear_dropped_state(&key, subscription_id) || !alive {
-                    continue;
-                }
-
-                this.callbacks
-                    .entry(key)
-                    .or_default()
-                    .insert(subscription_id, callback);
-            }
-        }
-    }
-}
-
-impl<K: Clone + Hash + Eq, F> Subscription<K, F> {
-    pub fn id(&self) -> usize {
-        self.id
-    }
-
-    pub fn detach(&mut self) {
-        self.mapping.take();
-    }
-}
-
-impl<K: Clone + Hash + Eq, F> Drop for Subscription<K, F> {
-    fn drop(&mut self) {
-        if let Some(mapping) = self.mapping.as_ref().and_then(|mapping| mapping.upgrade()) {
-            let mut mapping = mapping.lock();
-
-            // If the callback is present in the mapping, then just remove it.
-            if let Some(callbacks) = mapping.callbacks.get_mut(&self.key) {
-                let callback = callbacks.remove(&self.id);
-                if callback.is_some() {
-                    drop(mapping);
-                    drop(callback);
-                    return;
-                }
-            }
-
-            // If this subscription's callback is not present, then either it has been
-            // temporarily removed during emit, or it has not yet been added. Record
-            // that this subscription has been dropped so that the callback can be
-            // removed later.
-            mapping
-                .dropped_subscriptions
-                .entry(self.key.clone())
-                .or_default()
-                .insert(self.id);
-        }
-    }
-}

crates/gpui/src/app/menu.rs 🔗

@@ -1,99 +0,0 @@
-use crate::{platform::ForegroundPlatform, Action, App, AppContext};
-
-pub struct Menu<'a> {
-    pub name: &'a str,
-    pub items: Vec<MenuItem<'a>>,
-}
-
-pub enum MenuItem<'a> {
-    Separator,
-    Submenu(Menu<'a>),
-    Action {
-        name: &'a str,
-        action: Box<dyn Action>,
-        os_action: Option<OsAction>,
-    },
-}
-
-impl<'a> MenuItem<'a> {
-    pub fn separator() -> Self {
-        Self::Separator
-    }
-
-    pub fn submenu(menu: Menu<'a>) -> Self {
-        Self::Submenu(menu)
-    }
-
-    pub fn action(name: &'a str, action: impl Action) -> Self {
-        Self::Action {
-            name,
-            action: Box::new(action),
-            os_action: None,
-        }
-    }
-
-    pub fn os_action(name: &'a str, action: impl Action, os_action: OsAction) -> Self {
-        Self::Action {
-            name,
-            action: Box::new(action),
-            os_action: Some(os_action),
-        }
-    }
-}
-
-#[derive(Copy, Clone, Eq, PartialEq)]
-pub enum OsAction {
-    Cut,
-    Copy,
-    Paste,
-    SelectAll,
-    Undo,
-    Redo,
-}
-
-impl AppContext {
-    pub fn set_menus(&mut self, menus: Vec<Menu>) {
-        self.foreground_platform
-            .set_menus(menus, &self.keystroke_matcher);
-    }
-}
-
-pub(crate) fn setup_menu_handlers(foreground_platform: &dyn ForegroundPlatform, app: &App) {
-    foreground_platform.on_will_open_menu(Box::new({
-        let cx = app.0.clone();
-        move || {
-            let mut cx = cx.borrow_mut();
-            cx.keystroke_matcher.clear_pending();
-        }
-    }));
-    foreground_platform.on_validate_menu_command(Box::new({
-        let cx = app.0.clone();
-        move |action| {
-            let cx = cx.borrow_mut();
-            !cx.keystroke_matcher.has_pending_keystrokes() && cx.is_action_available(action)
-        }
-    }));
-    foreground_platform.on_menu_command(Box::new({
-        let cx = app.0.clone();
-        move |action| {
-            let mut cx = cx.borrow_mut();
-            if let Some(main_window) = cx.active_window() {
-                let dispatched = main_window
-                    .update(&mut *cx, |cx| {
-                        if let Some(view_id) = cx.focused_view_id() {
-                            cx.dispatch_action(Some(view_id), action);
-                            true
-                        } else {
-                            false
-                        }
-                    })
-                    .unwrap_or(false);
-
-                if dispatched {
-                    return;
-                }
-            }
-            cx.dispatch_global_action_any(action);
-        }
-    }));
-}

crates/gpui/src/app/ref_counts.rs 🔗

@@ -1,220 +0,0 @@
-#[cfg(any(test, feature = "test-support"))]
-use std::sync::Arc;
-
-use lazy_static::lazy_static;
-#[cfg(any(test, feature = "test-support"))]
-use parking_lot::Mutex;
-
-use collections::{hash_map::Entry, HashMap, HashSet};
-
-#[cfg(any(test, feature = "test-support"))]
-use crate::util::post_inc;
-use crate::{AnyWindowHandle, ElementStateId};
-
-lazy_static! {
-    static ref LEAK_BACKTRACE: bool =
-        std::env::var("LEAK_BACKTRACE").map_or(false, |b| !b.is_empty());
-}
-
-struct ElementStateRefCount {
-    ref_count: usize,
-    frame_id: usize,
-}
-
-#[derive(Default)]
-pub struct RefCounts {
-    entity_counts: HashMap<usize, usize>,
-    element_state_counts: HashMap<ElementStateId, ElementStateRefCount>,
-    dropped_models: HashSet<usize>,
-    dropped_views: HashSet<(AnyWindowHandle, usize)>,
-    dropped_element_states: HashSet<ElementStateId>,
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub leak_detector: Arc<Mutex<LeakDetector>>,
-}
-
-impl RefCounts {
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn new(leak_detector: Arc<Mutex<LeakDetector>>) -> Self {
-        Self {
-            #[cfg(any(test, feature = "test-support"))]
-            leak_detector,
-            ..Default::default()
-        }
-    }
-
-    pub fn inc_model(&mut self, model_id: usize) {
-        match self.entity_counts.entry(model_id) {
-            Entry::Occupied(mut entry) => {
-                *entry.get_mut() += 1;
-            }
-            Entry::Vacant(entry) => {
-                entry.insert(1);
-                self.dropped_models.remove(&model_id);
-            }
-        }
-    }
-
-    pub fn inc_view(&mut self, window: AnyWindowHandle, view_id: usize) {
-        match self.entity_counts.entry(view_id) {
-            Entry::Occupied(mut entry) => *entry.get_mut() += 1,
-            Entry::Vacant(entry) => {
-                entry.insert(1);
-                self.dropped_views.remove(&(window, view_id));
-            }
-        }
-    }
-
-    pub fn inc_element_state(&mut self, id: ElementStateId, frame_id: usize) {
-        match self.element_state_counts.entry(id) {
-            Entry::Occupied(mut entry) => {
-                let entry = entry.get_mut();
-                if entry.frame_id == frame_id || entry.ref_count >= 2 {
-                    panic!("used the same element state more than once in the same frame");
-                }
-                entry.ref_count += 1;
-                entry.frame_id = frame_id;
-            }
-            Entry::Vacant(entry) => {
-                entry.insert(ElementStateRefCount {
-                    ref_count: 1,
-                    frame_id,
-                });
-                self.dropped_element_states.remove(&id);
-            }
-        }
-    }
-
-    pub fn dec_model(&mut self, model_id: usize) {
-        let count = self.entity_counts.get_mut(&model_id).unwrap();
-        *count -= 1;
-        if *count == 0 {
-            self.entity_counts.remove(&model_id);
-            self.dropped_models.insert(model_id);
-        }
-    }
-
-    pub fn dec_view(&mut self, window: AnyWindowHandle, view_id: usize) {
-        let count = self.entity_counts.get_mut(&view_id).unwrap();
-        *count -= 1;
-        if *count == 0 {
-            self.entity_counts.remove(&view_id);
-            self.dropped_views.insert((window, view_id));
-        }
-    }
-
-    pub fn dec_element_state(&mut self, id: ElementStateId) {
-        let entry = self.element_state_counts.get_mut(&id).unwrap();
-        entry.ref_count -= 1;
-        if entry.ref_count == 0 {
-            self.element_state_counts.remove(&id);
-            self.dropped_element_states.insert(id);
-        }
-    }
-
-    pub fn is_entity_alive(&self, entity_id: usize) -> bool {
-        self.entity_counts.contains_key(&entity_id)
-    }
-
-    pub fn take_dropped(
-        &mut self,
-    ) -> (
-        HashSet<usize>,
-        HashSet<(AnyWindowHandle, usize)>,
-        HashSet<ElementStateId>,
-    ) {
-        (
-            std::mem::take(&mut self.dropped_models),
-            std::mem::take(&mut self.dropped_views),
-            std::mem::take(&mut self.dropped_element_states),
-        )
-    }
-}
-
-#[cfg(any(test, feature = "test-support"))]
-#[derive(Default)]
-pub struct LeakDetector {
-    next_handle_id: usize,
-    #[allow(clippy::type_complexity)]
-    handle_backtraces: HashMap<
-        usize,
-        (
-            Option<&'static str>,
-            HashMap<usize, Option<backtrace::Backtrace>>,
-        ),
-    >,
-}
-
-#[cfg(any(test, feature = "test-support"))]
-impl LeakDetector {
-    pub fn handle_created(&mut self, type_name: Option<&'static str>, entity_id: usize) -> usize {
-        let handle_id = post_inc(&mut self.next_handle_id);
-        let entry = self.handle_backtraces.entry(entity_id).or_default();
-        let backtrace = if *LEAK_BACKTRACE {
-            Some(backtrace::Backtrace::new_unresolved())
-        } else {
-            None
-        };
-        if let Some(type_name) = type_name {
-            entry.0.get_or_insert(type_name);
-        }
-        entry.1.insert(handle_id, backtrace);
-        handle_id
-    }
-
-    pub fn handle_dropped(&mut self, entity_id: usize, handle_id: usize) {
-        if let Some((_, backtraces)) = self.handle_backtraces.get_mut(&entity_id) {
-            assert!(backtraces.remove(&handle_id).is_some());
-            if backtraces.is_empty() {
-                self.handle_backtraces.remove(&entity_id);
-            }
-        }
-    }
-
-    pub fn assert_dropped(&mut self, entity_id: usize) {
-        if let Some((type_name, backtraces)) = self.handle_backtraces.get_mut(&entity_id) {
-            for trace in backtraces.values_mut().flatten() {
-                trace.resolve();
-                eprintln!("{:?}", crate::util::CwdBacktrace(trace));
-            }
-
-            let hint = if *LEAK_BACKTRACE {
-                ""
-            } else {
-                " – set LEAK_BACKTRACE=1 for more information"
-            };
-
-            panic!(
-                "{} handles to {} {} still exist{}",
-                backtraces.len(),
-                type_name.unwrap_or("entity"),
-                entity_id,
-                hint
-            );
-        }
-    }
-
-    pub fn detect(&mut self) {
-        let mut found_leaks = false;
-        for (id, (type_name, backtraces)) in self.handle_backtraces.iter_mut() {
-            eprintln!(
-                "leaked {} handles to {} {}",
-                backtraces.len(),
-                type_name.unwrap_or("entity"),
-                id
-            );
-            for trace in backtraces.values_mut().flatten() {
-                trace.resolve();
-                eprintln!("{:?}", crate::util::CwdBacktrace(trace));
-            }
-            found_leaks = true;
-        }
-
-        let hint = if *LEAK_BACKTRACE {
-            ""
-        } else {
-            " – set LEAK_BACKTRACE=1 for more information"
-        };
-        assert!(!found_leaks, "detected leaked handles{}", hint);
-    }
-}

crates/gpui/src/app/test_app_context.rs 🔗

@@ -1,661 +0,0 @@
-use crate::{
-    executor,
-    geometry::vector::Vector2F,
-    keymap_matcher::{Binding, Keystroke},
-    platform,
-    platform::{Event, InputHandler, KeyDownEvent, Platform},
-    Action, AnyWindowHandle, AppContext, BorrowAppContext, BorrowWindowContext, Entity, FontCache,
-    Handle, ModelContext, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle,
-    WeakHandle, WindowContext, WindowHandle,
-};
-use collections::BTreeMap;
-use futures::Future;
-use itertools::Itertools;
-use parking_lot::{Mutex, RwLock};
-use smallvec::SmallVec;
-use smol::stream::StreamExt;
-use std::{
-    any::Any,
-    cell::RefCell,
-    mem,
-    path::PathBuf,
-    rc::Rc,
-    sync::{
-        atomic::{AtomicUsize, Ordering},
-        Arc,
-    },
-    time::Duration,
-};
-
-use super::{
-    ref_counts::LeakDetector, window_input_handler::WindowInputHandler, AsyncAppContext, RefCounts,
-};
-
-#[derive(Clone)]
-pub struct TestAppContext {
-    cx: Rc<RefCell<AppContext>>,
-    foreground_platform: Rc<platform::test::ForegroundPlatform>,
-    condition_duration: Option<Duration>,
-    pub function_name: String,
-    assertion_context: AssertionContextManager,
-}
-
-impl TestAppContext {
-    pub fn new(
-        foreground_platform: Rc<platform::test::ForegroundPlatform>,
-        platform: Arc<dyn Platform>,
-        foreground: Rc<executor::Foreground>,
-        background: Arc<executor::Background>,
-        font_cache: Arc<FontCache>,
-        leak_detector: Arc<Mutex<LeakDetector>>,
-        first_entity_id: usize,
-        function_name: String,
-    ) -> Self {
-        let mut cx = AppContext::new(
-            foreground,
-            background,
-            platform,
-            foreground_platform.clone(),
-            font_cache,
-            util::http::FakeHttpClient::with_404_response(),
-            RefCounts::new(leak_detector),
-            (),
-        );
-        cx.next_id = first_entity_id;
-        let cx = TestAppContext {
-            cx: Rc::new(RefCell::new(cx)),
-            foreground_platform,
-            condition_duration: None,
-            function_name,
-            assertion_context: AssertionContextManager::new(),
-        };
-        cx.cx.borrow_mut().weak_self = Some(Rc::downgrade(&cx.cx));
-        cx
-    }
-
-    pub fn dispatch_action<A: Action>(&mut self, window: AnyWindowHandle, action: A) {
-        self.update_window(window, |window| {
-            window.dispatch_action(window.focused_view_id(), &action);
-        })
-        .expect("window not found");
-    }
-
-    pub fn available_actions(
-        &self,
-        window: AnyWindowHandle,
-        view_id: usize,
-    ) -> Vec<(&'static str, Box<dyn Action>, SmallVec<[Binding; 1]>)> {
-        self.read_window(window, |cx| cx.available_actions(view_id))
-            .unwrap_or_default()
-    }
-
-    pub fn dispatch_global_action<A: Action>(&mut self, action: A) {
-        self.update(|cx| cx.dispatch_global_action_any(&action));
-    }
-
-    pub fn dispatch_keystroke(
-        &mut self,
-        window: AnyWindowHandle,
-        keystroke: Keystroke,
-        is_held: bool,
-    ) {
-        let handled = window.update(self, |cx| {
-            if cx.dispatch_keystroke(&keystroke) {
-                return true;
-            }
-
-            if cx.dispatch_event(
-                Event::KeyDown(KeyDownEvent {
-                    keystroke: keystroke.clone(),
-                    is_held,
-                }),
-                false,
-            ) {
-                return true;
-            }
-
-            false
-        });
-
-        if !handled && !keystroke.cmd && !keystroke.ctrl {
-            WindowInputHandler {
-                app: self.cx.clone(),
-                window,
-            }
-            .replace_text_in_range(None, &keystroke.key)
-        }
-    }
-
-    pub fn read_window<T, F: FnOnce(&WindowContext) -> T>(
-        &self,
-        window: AnyWindowHandle,
-        callback: F,
-    ) -> Option<T> {
-        self.cx.borrow().read_window(window, callback)
-    }
-
-    pub fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
-        &mut self,
-        window: AnyWindowHandle,
-        callback: F,
-    ) -> Option<T> {
-        self.cx.borrow_mut().update_window(window, callback)
-    }
-
-    pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
-    where
-        T: Entity,
-        F: FnOnce(&mut ModelContext<T>) -> T,
-    {
-        self.cx.borrow_mut().add_model(build_model)
-    }
-
-    pub fn add_window<V, F>(&mut self, build_root_view: F) -> WindowHandle<V>
-    where
-        V: View,
-        F: FnOnce(&mut ViewContext<V>) -> V,
-    {
-        let window = self
-            .cx
-            .borrow_mut()
-            .add_window(Default::default(), build_root_view);
-        window.simulate_activation(self);
-        window
-    }
-
-    pub fn observe_global<E, F>(&mut self, callback: F) -> Subscription
-    where
-        E: Any,
-        F: 'static + FnMut(&mut AppContext),
-    {
-        self.cx.borrow_mut().observe_global::<E, F>(callback)
-    }
-
-    pub fn set_global<T: 'static>(&mut self, state: T) {
-        self.cx.borrow_mut().set_global(state);
-    }
-
-    pub fn subscribe_global<E, F>(&mut self, callback: F) -> Subscription
-    where
-        E: Any,
-        F: 'static + FnMut(&E, &mut AppContext),
-    {
-        self.cx.borrow_mut().subscribe_global(callback)
-    }
-
-    pub fn windows(&self) -> Vec<AnyWindowHandle> {
-        self.cx.borrow().windows().collect()
-    }
-
-    pub fn remove_all_windows(&mut self) {
-        self.update(|cx| cx.windows.clear());
-    }
-
-    pub fn read<T, F: FnOnce(&AppContext) -> T>(&self, callback: F) -> T {
-        callback(&*self.cx.borrow())
-    }
-
-    pub fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, callback: F) -> T {
-        let mut state = self.cx.borrow_mut();
-        // Don't increment pending flushes in order for effects to be flushed before the callback
-        // completes, which is helpful in tests.
-        let result = callback(&mut *state);
-        // Flush effects after the callback just in case there are any. This can happen in edge
-        // cases such as the closure dropping handles.
-        state.flush_effects();
-        result
-    }
-
-    pub fn to_async(&self) -> AsyncAppContext {
-        AsyncAppContext(self.cx.clone())
-    }
-
-    pub fn font_cache(&self) -> Arc<FontCache> {
-        self.cx.borrow().font_cache.clone()
-    }
-
-    pub fn foreground_platform(&self) -> Rc<platform::test::ForegroundPlatform> {
-        self.foreground_platform.clone()
-    }
-
-    pub fn platform(&self) -> Arc<dyn platform::Platform> {
-        self.cx.borrow().platform.clone()
-    }
-
-    pub fn foreground(&self) -> Rc<executor::Foreground> {
-        self.cx.borrow().foreground().clone()
-    }
-
-    pub fn background(&self) -> Arc<executor::Background> {
-        self.cx.borrow().background().clone()
-    }
-
-    pub fn spawn<F, Fut, T>(&self, f: F) -> Task<T>
-    where
-        F: FnOnce(AsyncAppContext) -> Fut,
-        Fut: 'static + Future<Output = T>,
-        T: 'static,
-    {
-        let foreground = self.foreground();
-        let future = f(self.to_async());
-        let cx = self.to_async();
-        foreground.spawn(async move {
-            let result = future.await;
-            cx.0.borrow_mut().flush_effects();
-            result
-        })
-    }
-
-    pub fn simulate_new_path_selection(&self, result: impl FnOnce(PathBuf) -> Option<PathBuf>) {
-        self.foreground_platform.simulate_new_path_selection(result);
-    }
-
-    pub fn did_prompt_for_new_path(&self) -> bool {
-        self.foreground_platform.as_ref().did_prompt_for_new_path()
-    }
-
-    pub fn leak_detector(&self) -> Arc<Mutex<LeakDetector>> {
-        self.cx.borrow().leak_detector()
-    }
-
-    pub fn assert_dropped(&self, handle: impl WeakHandle) {
-        self.cx
-            .borrow()
-            .leak_detector()
-            .lock()
-            .assert_dropped(handle.id())
-    }
-
-    /// Drop a handle, assuming it is the last. If it is not the last, panic with debug information about
-    /// where the stray handles were created.
-    pub fn drop_last<T, W: WeakHandle, H: Handle<T, Weak = W>>(&mut self, handle: H) {
-        let weak = handle.downgrade();
-        self.update(|_| drop(handle));
-        self.assert_dropped(weak);
-    }
-
-    pub fn set_condition_duration(&mut self, duration: Option<Duration>) {
-        self.condition_duration = duration;
-    }
-
-    pub fn condition_duration(&self) -> Duration {
-        self.condition_duration.unwrap_or_else(|| {
-            if std::env::var("CI").is_ok() {
-                Duration::from_secs(2)
-            } else {
-                Duration::from_millis(500)
-            }
-        })
-    }
-
-    pub fn assert_clipboard_content(&mut self, expected_content: Option<&str>) {
-        self.update(|cx| {
-            let actual_content = cx.read_from_clipboard().map(|item| item.text().to_owned());
-            let expected_content = expected_content.map(|content| content.to_owned());
-            assert_eq!(actual_content, expected_content);
-        })
-    }
-
-    pub fn add_assertion_context(&self, context: String) -> ContextHandle {
-        self.assertion_context.add_context(context)
-    }
-
-    pub fn assertion_context(&self) -> String {
-        self.assertion_context.context()
-    }
-}
-
-impl BorrowAppContext for TestAppContext {
-    fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
-        self.cx.borrow().read_with(f)
-    }
-
-    fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
-        self.cx.borrow_mut().update(f)
-    }
-}
-
-impl BorrowWindowContext for TestAppContext {
-    type Result<T> = T;
-
-    fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, window: AnyWindowHandle, f: F) -> T {
-        self.cx
-            .borrow()
-            .read_window(window, f)
-            .expect("window was closed")
-    }
-
-    fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
-    where
-        F: FnOnce(&WindowContext) -> Option<T>,
-    {
-        BorrowWindowContext::read_window(self, window, f)
-    }
-
-    fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
-        &mut self,
-        window: AnyWindowHandle,
-        f: F,
-    ) -> T {
-        self.cx
-            .borrow_mut()
-            .update_window(window, f)
-            .expect("window was closed")
-    }
-
-    fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
-    where
-        F: FnOnce(&mut WindowContext) -> Option<T>,
-    {
-        BorrowWindowContext::update_window(self, window, f)
-    }
-}
-
-impl<T: Entity> ModelHandle<T> {
-    pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
-        let (tx, mut rx) = futures::channel::mpsc::unbounded();
-        let mut cx = cx.cx.borrow_mut();
-        let subscription = cx.observe(self, move |_, _| {
-            tx.unbounded_send(()).ok();
-        });
-
-        let duration = if std::env::var("CI").is_ok() {
-            Duration::from_secs(5)
-        } else {
-            Duration::from_secs(1)
-        };
-
-        let executor = cx.background().clone();
-        async move {
-            executor.start_waiting();
-            let notification = crate::util::timeout(duration, rx.next())
-                .await
-                .expect("next notification timed out");
-            drop(subscription);
-            notification.expect("model dropped while test was waiting for its next notification")
-        }
-    }
-
-    pub fn next_event(&self, cx: &TestAppContext) -> impl Future<Output = T::Event>
-    where
-        T::Event: Clone,
-    {
-        let (tx, mut rx) = futures::channel::mpsc::unbounded();
-        let mut cx = cx.cx.borrow_mut();
-        let subscription = cx.subscribe(self, move |_, event, _| {
-            tx.unbounded_send(event.clone()).ok();
-        });
-
-        let duration = if std::env::var("CI").is_ok() {
-            Duration::from_secs(5)
-        } else {
-            Duration::from_secs(1)
-        };
-
-        cx.foreground.start_waiting();
-        async move {
-            let event = crate::util::timeout(duration, rx.next())
-                .await
-                .expect("next event timed out");
-            drop(subscription);
-            event.expect("model dropped while test was waiting for its next event")
-        }
-    }
-
-    pub fn condition(
-        &self,
-        cx: &TestAppContext,
-        mut predicate: impl FnMut(&T, &AppContext) -> bool,
-    ) -> impl Future<Output = ()> {
-        let (tx, mut rx) = futures::channel::mpsc::unbounded();
-
-        let mut cx = cx.cx.borrow_mut();
-        let subscriptions = (
-            cx.observe(self, {
-                let tx = tx.clone();
-                move |_, _| {
-                    tx.unbounded_send(()).ok();
-                }
-            }),
-            cx.subscribe(self, {
-                move |_, _, _| {
-                    tx.unbounded_send(()).ok();
-                }
-            }),
-        );
-
-        let cx = cx.weak_self.as_ref().unwrap().upgrade().unwrap();
-        let handle = self.downgrade();
-        let duration = if std::env::var("CI").is_ok() {
-            Duration::from_secs(5)
-        } else {
-            Duration::from_secs(1)
-        };
-
-        async move {
-            crate::util::timeout(duration, async move {
-                loop {
-                    {
-                        let cx = cx.borrow();
-                        let cx = &*cx;
-                        if predicate(
-                            handle
-                                .upgrade(cx)
-                                .expect("model dropped with pending condition")
-                                .read(cx),
-                            cx,
-                        ) {
-                            break;
-                        }
-                    }
-
-                    cx.borrow().foreground().start_waiting();
-                    rx.next()
-                        .await
-                        .expect("model dropped with pending condition");
-                    cx.borrow().foreground().finish_waiting();
-                }
-            })
-            .await
-            .expect("condition timed out");
-            drop(subscriptions);
-        }
-    }
-}
-
-impl AnyWindowHandle {
-    pub fn has_pending_prompt(&self, cx: &mut TestAppContext) -> bool {
-        let window = self.platform_window_mut(cx);
-        let prompts = window.pending_prompts.borrow_mut();
-        !prompts.is_empty()
-    }
-
-    pub fn current_title(&self, cx: &mut TestAppContext) -> Option<String> {
-        self.platform_window_mut(cx).title.clone()
-    }
-
-    pub fn simulate_close(&self, cx: &mut TestAppContext) -> bool {
-        let handler = self.platform_window_mut(cx).should_close_handler.take();
-        if let Some(mut handler) = handler {
-            let should_close = handler();
-            self.platform_window_mut(cx).should_close_handler = Some(handler);
-            should_close
-        } else {
-            false
-        }
-    }
-
-    pub fn simulate_resize(&self, size: Vector2F, cx: &mut TestAppContext) {
-        let mut window = self.platform_window_mut(cx);
-        window.size = size;
-        let mut handlers = mem::take(&mut window.resize_handlers);
-        drop(window);
-        for handler in &mut handlers {
-            handler();
-        }
-        self.platform_window_mut(cx).resize_handlers = handlers;
-    }
-
-    pub fn is_edited(&self, cx: &mut TestAppContext) -> bool {
-        self.platform_window_mut(cx).edited
-    }
-
-    pub fn simulate_prompt_answer(&self, answer: usize, cx: &mut TestAppContext) {
-        use postage::prelude::Sink as _;
-
-        let mut done_tx = self
-            .platform_window_mut(cx)
-            .pending_prompts
-            .borrow_mut()
-            .pop_front()
-            .expect("prompt was not called");
-        done_tx.try_send(answer).ok();
-    }
-
-    fn platform_window_mut<'a>(
-        &self,
-        cx: &'a mut TestAppContext,
-    ) -> std::cell::RefMut<'a, platform::test::Window> {
-        std::cell::RefMut::map(cx.cx.borrow_mut(), |state| {
-            let window = state.windows.get_mut(&self).unwrap();
-            let test_window = window
-                .platform_window
-                .as_any_mut()
-                .downcast_mut::<platform::test::Window>()
-                .unwrap();
-            test_window
-        })
-    }
-}
-
-impl<T: View> ViewHandle<T> {
-    pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
-        use postage::prelude::{Sink as _, Stream as _};
-
-        let (mut tx, mut rx) = postage::mpsc::channel(1);
-        let mut cx = cx.cx.borrow_mut();
-        let subscription = cx.observe(self, move |_, _| {
-            tx.try_send(()).ok();
-        });
-
-        let duration = if std::env::var("CI").is_ok() {
-            Duration::from_secs(5)
-        } else {
-            Duration::from_secs(1)
-        };
-
-        async move {
-            let notification = crate::util::timeout(duration, rx.recv())
-                .await
-                .expect("next notification timed out");
-            drop(subscription);
-            notification.expect("model dropped while test was waiting for its next notification")
-        }
-    }
-
-    pub fn condition(
-        &self,
-        cx: &TestAppContext,
-        mut predicate: impl FnMut(&T, &AppContext) -> bool,
-    ) -> impl Future<Output = ()> {
-        use postage::prelude::{Sink as _, Stream as _};
-
-        let (tx, mut rx) = postage::mpsc::channel(1024);
-        let timeout_duration = cx.condition_duration();
-
-        let mut cx = cx.cx.borrow_mut();
-        let subscriptions = (
-            cx.observe(self, {
-                let mut tx = tx.clone();
-                move |_, _| {
-                    tx.blocking_send(()).ok();
-                }
-            }),
-            cx.subscribe(self, {
-                let mut tx = tx.clone();
-                move |_, _, _| {
-                    tx.blocking_send(()).ok();
-                }
-            }),
-        );
-
-        let cx = cx.weak_self.as_ref().unwrap().upgrade().unwrap();
-        let handle = self.downgrade();
-
-        async move {
-            crate::util::timeout(timeout_duration, async move {
-                loop {
-                    {
-                        let cx = cx.borrow();
-                        let cx = &*cx;
-                        if predicate(
-                            handle
-                                .upgrade(cx)
-                                .expect("view dropped with pending condition")
-                                .read(cx),
-                            cx,
-                        ) {
-                            break;
-                        }
-                    }
-
-                    cx.borrow().foreground().start_waiting();
-                    rx.recv()
-                        .await
-                        .expect("view dropped with pending condition");
-                    cx.borrow().foreground().finish_waiting();
-                }
-            })
-            .await
-            .expect("condition timed out");
-            drop(subscriptions);
-        }
-    }
-}
-
-/// Tracks string context to be printed when assertions fail.
-/// Often this is done by storing a context string in the manager and returning the handle.
-#[derive(Clone)]
-pub struct AssertionContextManager {
-    id: Arc<AtomicUsize>,
-    contexts: Arc<RwLock<BTreeMap<usize, String>>>,
-}
-
-impl AssertionContextManager {
-    pub fn new() -> Self {
-        Self {
-            id: Arc::new(AtomicUsize::new(0)),
-            contexts: Arc::new(RwLock::new(BTreeMap::new())),
-        }
-    }
-
-    pub fn add_context(&self, context: String) -> ContextHandle {
-        let id = self.id.fetch_add(1, Ordering::Relaxed);
-        let mut contexts = self.contexts.write();
-        contexts.insert(id, context);
-        ContextHandle {
-            id,
-            manager: self.clone(),
-        }
-    }
-
-    pub fn context(&self) -> String {
-        let contexts = self.contexts.read();
-        format!("\n{}\n", contexts.values().join("\n"))
-    }
-}
-
-/// Used to track the lifetime of a piece of context so that it can be provided when an assertion fails.
-/// For example, in the EditorTestContext, `set_state` returns a context handle so that if an assertion fails,
-/// the state that was set initially for the failure can be printed in the error message
-pub struct ContextHandle {
-    id: usize,
-    manager: AssertionContextManager,
-}
-
-impl Drop for ContextHandle {
-    fn drop(&mut self) {
-        let mut contexts = self.manager.contexts.write();
-        contexts.remove(&self.id);
-    }
-}

crates/gpui/src/app/window.rs 🔗

@@ -1,1767 +0,0 @@
-use crate::{
-    elements::AnyRootElement,
-    fonts::{TextStyle, TextStyleRefinement},
-    geometry::{rect::RectF, Size},
-    json::ToJson,
-    keymap_matcher::{Binding, KeymapContext, Keystroke, MatchResult},
-    platform::{
-        self, Appearance, CursorStyle, Event, KeyDownEvent, KeyUpEvent, ModifiersChangedEvent,
-        MouseButton, MouseMovedEvent, PromptLevel, WindowBounds,
-    },
-    scene::{
-        CursorRegion, EventHandler, MouseClick, MouseClickOut, MouseDown, MouseDownOut, MouseDrag,
-        MouseEvent, MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
-        Scene,
-    },
-    text_layout::TextLayoutCache,
-    util::post_inc,
-    Action, AnyView, AnyViewHandle, AnyWindowHandle, AppContext, BorrowAppContext,
-    BorrowWindowContext, Effect, Element, Entity, Handle, MouseRegion, MouseRegionId, SceneBuilder,
-    Subscription, View, ViewContext, ViewHandle, WindowInvalidation,
-};
-use anyhow::{anyhow, bail, Result};
-use collections::{HashMap, HashSet};
-use pathfinder_geometry::vector::{vec2f, Vector2F};
-use postage::oneshot;
-use serde_json::json;
-use smallvec::SmallVec;
-use sqlez::{
-    bindable::{Bind, Column, StaticColumnCount},
-    statement::Statement,
-};
-use std::{
-    any::{type_name, Any, TypeId},
-    mem,
-    ops::{Deref, DerefMut, Range, Sub},
-    sync::Arc,
-};
-use taffy::{
-    tree::{Measurable, MeasureFunc},
-    Taffy,
-};
-use util::ResultExt;
-use uuid::Uuid;
-
-use super::{Reference, ViewMetadata};
-
-pub struct Window {
-    layout_engines: Vec<LayoutEngine>,
-    pub(crate) root_view: Option<AnyViewHandle>,
-    pub(crate) focused_view_id: Option<usize>,
-    pub(crate) parents: HashMap<usize, usize>,
-    pub(crate) is_active: bool,
-    pub(crate) is_fullscreen: bool,
-    inspector_enabled: bool,
-    pub(crate) invalidation: Option<WindowInvalidation>,
-    pub(crate) platform_window: Box<dyn platform::Window>,
-    pub(crate) rendered_views: HashMap<usize, Box<dyn AnyRootElement>>,
-    scene: SceneBuilder,
-    pub(crate) text_style_stack: Vec<TextStyle>,
-    pub(crate) theme_stack: Vec<Arc<dyn Any + Send + Sync>>,
-    pub(crate) new_parents: HashMap<usize, usize>,
-    pub(crate) views_to_notify_if_ancestors_change: HashMap<usize, SmallVec<[usize; 2]>>,
-    titlebar_height: f32,
-    appearance: Appearance,
-    cursor_regions: Vec<CursorRegion>,
-    mouse_regions: Vec<(MouseRegion, usize)>,
-    event_handlers: Vec<EventHandler>,
-    last_mouse_moved_event: Option<Event>,
-    last_mouse_position: Vector2F,
-    pressed_buttons: HashSet<MouseButton>,
-    pub(crate) hovered_region_ids: Vec<MouseRegionId>,
-    pub(crate) clicked_region_ids: Vec<MouseRegionId>,
-    pub(crate) clicked_region: Option<(MouseRegionId, MouseButton)>,
-    text_layout_cache: Arc<TextLayoutCache>,
-    refreshing: bool,
-}
-
-impl Window {
-    pub fn new<V, F>(
-        handle: AnyWindowHandle,
-        platform_window: Box<dyn platform::Window>,
-        cx: &mut AppContext,
-        build_view: F,
-    ) -> Self
-    where
-        V: View,
-        F: FnOnce(&mut ViewContext<V>) -> V,
-    {
-        let titlebar_height = platform_window.titlebar_height();
-        let appearance = platform_window.appearance();
-        let mut window = Self {
-            layout_engines: Vec::new(),
-            root_view: None,
-            focused_view_id: None,
-            parents: Default::default(),
-            is_active: false,
-            invalidation: None,
-            is_fullscreen: false,
-            inspector_enabled: false,
-            platform_window,
-            rendered_views: Default::default(),
-            scene: SceneBuilder::new(),
-            text_style_stack: Vec::new(),
-            theme_stack: Vec::new(),
-            new_parents: HashMap::default(),
-            views_to_notify_if_ancestors_change: HashMap::default(),
-            cursor_regions: Default::default(),
-            mouse_regions: Default::default(),
-            event_handlers: Default::default(),
-            text_layout_cache: Arc::new(TextLayoutCache::new(cx.font_system.clone())),
-            last_mouse_moved_event: None,
-            last_mouse_position: Vector2F::zero(),
-            pressed_buttons: Default::default(),
-            hovered_region_ids: Default::default(),
-            clicked_region_ids: Default::default(),
-            clicked_region: None,
-            titlebar_height,
-            appearance,
-            refreshing: false,
-        };
-
-        let mut window_context = WindowContext::mutable(cx, &mut window, handle);
-        let root_view = window_context.add_view(|cx| build_view(cx));
-        if let Some(invalidation) = window_context.window.invalidation.take() {
-            window_context.invalidate(invalidation, appearance);
-        }
-        window.focused_view_id = Some(root_view.id());
-        window.root_view = Some(root_view.into_any());
-        window
-    }
-
-    pub fn root_view(&self) -> &AnyViewHandle {
-        &self
-            .root_view
-            .as_ref()
-            .expect("root_view called during window construction")
-    }
-
-    pub fn take_event_handlers(&mut self) -> Vec<EventHandler> {
-        mem::take(&mut self.event_handlers)
-    }
-}
-
-pub struct WindowContext<'a> {
-    pub(crate) app_context: Reference<'a, AppContext>,
-    pub(crate) window: Reference<'a, Window>,
-    pub(crate) window_handle: AnyWindowHandle,
-    pub(crate) removed: bool,
-}
-
-impl Deref for WindowContext<'_> {
-    type Target = AppContext;
-
-    fn deref(&self) -> &Self::Target {
-        &self.app_context
-    }
-}
-
-impl DerefMut for WindowContext<'_> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.app_context
-    }
-}
-
-impl BorrowAppContext for WindowContext<'_> {
-    fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
-        self.app_context.read_with(f)
-    }
-
-    fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
-        self.app_context.update(f)
-    }
-}
-
-impl BorrowWindowContext for WindowContext<'_> {
-    type Result<T> = T;
-
-    fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, handle: AnyWindowHandle, f: F) -> T {
-        if self.window_handle == handle {
-            f(self)
-        } else {
-            panic!("read_with called with id of window that does not belong to this context")
-        }
-    }
-
-    fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
-    where
-        F: FnOnce(&WindowContext) -> Option<T>,
-    {
-        BorrowWindowContext::read_window(self, window, f)
-    }
-
-    fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
-        &mut self,
-        handle: AnyWindowHandle,
-        f: F,
-    ) -> T {
-        if self.window_handle == handle {
-            f(self)
-        } else {
-            panic!("update called with id of window that does not belong to this context")
-        }
-    }
-
-    fn update_window_optional<T, F>(&mut self, handle: AnyWindowHandle, f: F) -> Option<T>
-    where
-        F: FnOnce(&mut WindowContext) -> Option<T>,
-    {
-        BorrowWindowContext::update_window(self, handle, f)
-    }
-}
-
-impl<'a> WindowContext<'a> {
-    pub fn mutable(
-        app_context: &'a mut AppContext,
-        window: &'a mut Window,
-        handle: AnyWindowHandle,
-    ) -> Self {
-        Self {
-            app_context: Reference::Mutable(app_context),
-            window: Reference::Mutable(window),
-            window_handle: handle,
-            removed: false,
-        }
-    }
-
-    pub fn immutable(
-        app_context: &'a AppContext,
-        window: &'a Window,
-        handle: AnyWindowHandle,
-    ) -> Self {
-        Self {
-            app_context: Reference::Immutable(app_context),
-            window: Reference::Immutable(window),
-            window_handle: handle,
-            removed: false,
-        }
-    }
-
-    pub fn repaint(&mut self) {
-        let window = self.window();
-        self.pending_effects
-            .push_back(Effect::RepaintWindow { window });
-    }
-
-    pub fn scene(&mut self) -> &mut SceneBuilder {
-        &mut self.window.scene
-    }
-
-    pub fn enable_inspector(&mut self) {
-        self.window.inspector_enabled = true;
-    }
-
-    pub fn is_inspector_enabled(&self) -> bool {
-        self.window.inspector_enabled
-    }
-
-    pub fn is_mouse_down(&self, button: MouseButton) -> bool {
-        self.window.pressed_buttons.contains(&button)
-    }
-
-    pub fn rem_size(&self) -> f32 {
-        16.
-    }
-
-    pub fn layout_engine(&mut self) -> Option<&mut LayoutEngine> {
-        self.window.layout_engines.last_mut()
-    }
-
-    pub fn push_layout_engine(&mut self, engine: LayoutEngine) {
-        self.window.layout_engines.push(engine);
-    }
-
-    pub fn pop_layout_engine(&mut self) -> Option<LayoutEngine> {
-        self.window.layout_engines.pop()
-    }
-
-    pub fn remove_window(&mut self) {
-        self.removed = true;
-    }
-
-    pub fn window(&self) -> AnyWindowHandle {
-        self.window_handle
-    }
-
-    pub fn app_context(&mut self) -> &mut AppContext {
-        &mut self.app_context
-    }
-
-    pub fn root_view(&self) -> &AnyViewHandle {
-        self.window.root_view()
-    }
-
-    pub fn window_size(&self) -> Vector2F {
-        self.window.platform_window.content_size()
-    }
-
-    pub fn mouse_position(&self) -> Vector2F {
-        self.window.platform_window.mouse_position()
-    }
-
-    pub fn refreshing(&self) -> bool {
-        self.window.refreshing
-    }
-
-    pub fn text_layout_cache(&self) -> &Arc<TextLayoutCache> {
-        &self.window.text_layout_cache
-    }
-
-    pub(crate) fn update_any_view<F, T>(&mut self, view_id: usize, f: F) -> Option<T>
-    where
-        F: FnOnce(&mut dyn AnyView, &mut Self) -> T,
-    {
-        let handle = self.window_handle;
-        let mut view = self.views.remove(&(handle, view_id))?;
-        let result = f(view.as_mut(), self);
-        self.views.insert((handle, view_id), view);
-        Some(result)
-    }
-
-    pub(crate) fn update_view<V: 'static, S>(
-        &mut self,
-        handle: &ViewHandle<V>,
-        update: &mut dyn FnMut(&mut V, &mut ViewContext<V>) -> S,
-    ) -> S {
-        self.update_any_view(handle.view_id, |view, cx| {
-            let mut cx = ViewContext::mutable(cx, handle.view_id);
-            update(
-                view.as_any_mut()
-                    .downcast_mut()
-                    .expect("downcast is type safe"),
-                &mut cx,
-            )
-        })
-        .expect("view is already on the stack")
-    }
-
-    pub fn defer(&mut self, callback: impl 'static + FnOnce(&mut WindowContext)) {
-        let handle = self.window_handle;
-        self.app_context.defer(move |cx| {
-            cx.update_window(handle, |cx| callback(cx));
-        })
-    }
-
-    pub fn update_global<T, F, U>(&mut self, update: F) -> U
-    where
-        T: 'static,
-        F: FnOnce(&mut T, &mut Self) -> U,
-    {
-        AppContext::update_global_internal(self, |global, cx| update(global, cx))
-    }
-
-    pub fn update_default_global<T, F, U>(&mut self, update: F) -> U
-    where
-        T: 'static + Default,
-        F: FnOnce(&mut T, &mut Self) -> U,
-    {
-        AppContext::update_default_global_internal(self, |global, cx| update(global, cx))
-    }
-
-    pub fn subscribe<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
-    where
-        E: Entity,
-        E::Event: 'static,
-        H: Handle<E>,
-        F: 'static + FnMut(H, &E::Event, &mut WindowContext),
-    {
-        self.subscribe_internal(handle, move |emitter, event, cx| {
-            callback(emitter, event, cx);
-            true
-        })
-    }
-
-    pub fn subscribe_internal<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
-    where
-        E: Entity,
-        E::Event: 'static,
-        H: Handle<E>,
-        F: 'static + FnMut(H, &E::Event, &mut WindowContext) -> bool,
-    {
-        let window_handle = self.window_handle;
-        self.app_context
-            .subscribe_internal(handle, move |emitter, event, cx| {
-                cx.update_window(window_handle, |cx| callback(emitter, event, cx))
-                    .unwrap_or(false)
-            })
-    }
-
-    pub(crate) fn observe_window_activation<F>(&mut self, callback: F) -> Subscription
-    where
-        F: 'static + FnMut(bool, &mut WindowContext) -> bool,
-    {
-        let handle = self.window_handle;
-        let subscription_id = post_inc(&mut self.next_subscription_id);
-        self.pending_effects
-            .push_back(Effect::WindowActivationObservation {
-                window: handle,
-                subscription_id,
-                callback: Box::new(callback),
-            });
-        Subscription::WindowActivationObservation(
-            self.window_activation_observations
-                .subscribe(handle, subscription_id),
-        )
-    }
-
-    pub(crate) fn observe_fullscreen<F>(&mut self, callback: F) -> Subscription
-    where
-        F: 'static + FnMut(bool, &mut WindowContext) -> bool,
-    {
-        let window = self.window_handle;
-        let subscription_id = post_inc(&mut self.next_subscription_id);
-        self.pending_effects
-            .push_back(Effect::WindowFullscreenObservation {
-                window,
-                subscription_id,
-                callback: Box::new(callback),
-            });
-        Subscription::WindowActivationObservation(
-            self.window_activation_observations
-                .subscribe(window, subscription_id),
-        )
-    }
-
-    pub(crate) fn observe_window_bounds<F>(&mut self, callback: F) -> Subscription
-    where
-        F: 'static + FnMut(WindowBounds, Uuid, &mut WindowContext) -> bool,
-    {
-        let window = self.window_handle;
-        let subscription_id = post_inc(&mut self.next_subscription_id);
-        self.pending_effects
-            .push_back(Effect::WindowBoundsObservation {
-                window,
-                subscription_id,
-                callback: Box::new(callback),
-            });
-        Subscription::WindowBoundsObservation(
-            self.window_bounds_observations
-                .subscribe(window, subscription_id),
-        )
-    }
-
-    pub fn observe_keystrokes<F>(&mut self, callback: F) -> Subscription
-    where
-        F: 'static
-            + FnMut(&Keystroke, &MatchResult, Option<&Box<dyn Action>>, &mut WindowContext) -> bool,
-    {
-        let window = self.window_handle;
-        let subscription_id = post_inc(&mut self.next_subscription_id);
-        self.keystroke_observations
-            .add_callback(window, subscription_id, Box::new(callback));
-        Subscription::KeystrokeObservation(
-            self.keystroke_observations
-                .subscribe(window, subscription_id),
-        )
-    }
-
-    pub(crate) fn available_actions(
-        &self,
-        view_id: usize,
-    ) -> Vec<(&'static str, Box<dyn Action>, SmallVec<[Binding; 1]>)> {
-        let handle = self.window_handle;
-        let mut contexts = Vec::new();
-        let mut handler_depths_by_action_id = HashMap::<TypeId, usize>::default();
-        for (depth, view_id) in self.ancestors(view_id).enumerate() {
-            if let Some(view_metadata) = self.views_metadata.get(&(handle, view_id)) {
-                contexts.push(view_metadata.keymap_context.clone());
-                if let Some(actions) = self.actions.get(&view_metadata.type_id) {
-                    handler_depths_by_action_id
-                        .extend(actions.keys().copied().map(|action_id| (action_id, depth)));
-                }
-            } else {
-                log::error!(
-                    "view {} not found when computing available actions",
-                    view_id
-                );
-            }
-        }
-
-        handler_depths_by_action_id.extend(
-            self.global_actions
-                .keys()
-                .copied()
-                .map(|action_id| (action_id, contexts.len())),
-        );
-
-        self.action_deserializers
-            .iter()
-            .filter_map(move |(name, (action_id, deserialize))| {
-                if let Some(action_depth) = handler_depths_by_action_id.get(action_id).copied() {
-                    let action = deserialize(serde_json::Value::Object(Default::default())).ok()?;
-                    let bindings = self
-                        .keystroke_matcher
-                        .bindings_for_action(*action_id)
-                        .filter(|b| {
-                            action.eq(b.action())
-                                && (0..=action_depth)
-                                    .any(|depth| b.match_context(&contexts[depth..]))
-                        })
-                        .cloned()
-                        .collect();
-                    Some((*name, action, bindings))
-                } else {
-                    None
-                }
-            })
-            .collect()
-    }
-
-    pub(crate) fn dispatch_keystroke(&mut self, keystroke: &Keystroke) -> bool {
-        let handle = self.window_handle;
-        if let Some(focused_view_id) = self.focused_view_id() {
-            let dispatch_path = self
-                .ancestors(focused_view_id)
-                .filter_map(|view_id| {
-                    self.views_metadata
-                        .get(&(handle, view_id))
-                        .map(|view| (view_id, view.keymap_context.clone()))
-                })
-                .collect();
-
-            let match_result = self
-                .keystroke_matcher
-                .push_keystroke(keystroke.clone(), dispatch_path);
-            let mut handled_by = None;
-
-            let keystroke_handled = match &match_result {
-                MatchResult::None => false,
-                MatchResult::Pending => true,
-                MatchResult::Matches(matches) => {
-                    for (view_id, action) in matches {
-                        if self.dispatch_action(Some(*view_id), action.as_ref()) {
-                            self.keystroke_matcher.clear_pending();
-                            handled_by = Some(action.boxed_clone());
-                            break;
-                        }
-                    }
-                    handled_by.is_some()
-                }
-            };
-
-            self.keystroke(handle, keystroke.clone(), handled_by, match_result.clone());
-            keystroke_handled
-        } else {
-            self.keystroke(handle, keystroke.clone(), None, MatchResult::None);
-            false
-        }
-    }
-
-    pub(crate) fn dispatch_event(&mut self, event: Event, event_reused: bool) -> bool {
-        if !event_reused {
-            self.dispatch_event_2(&event);
-        }
-
-        let mut mouse_events = SmallVec::<[_; 2]>::new();
-        let mut notified_views: HashSet<usize> = Default::default();
-        let handle = self.window_handle;
-
-        // 1. Handle platform event. Keyboard events get dispatched immediately, while mouse events
-        //    get mapped into the mouse-specific MouseEvent type.
-        //  -> These are usually small: [Mouse Down] or [Mouse up, Click] or [Mouse Moved, Mouse Dragged?]
-        //  -> Also updates mouse-related state
-        match &event {
-            Event::KeyDown(e) => return self.dispatch_key_down(e),
-
-            Event::KeyUp(e) => return self.dispatch_key_up(e),
-
-            Event::ModifiersChanged(e) => return self.dispatch_modifiers_changed(e),
-
-            Event::MouseDown(e) => {
-                // Click events are weird because they can be fired after a drag event.
-                // MDN says that browsers handle this by starting from 'the most
-                // specific ancestor element that contained both [positions]'
-                // So we need to store the overlapping regions on mouse down.
-
-                // If there is already region being clicked, don't replace it.
-                if self.window.clicked_region.is_none() {
-                    self.window.clicked_region_ids = self
-                        .window
-                        .mouse_regions
-                        .iter()
-                        .filter_map(|(region, _)| {
-                            if region.bounds.contains_point(e.position) {
-                                Some(region.id())
-                            } else {
-                                None
-                            }
-                        })
-                        .collect();
-
-                    let mut highest_z_index = 0;
-                    let mut clicked_region_id = None;
-                    for (region, z_index) in self.window.mouse_regions.iter() {
-                        if region.bounds.contains_point(e.position) && *z_index >= highest_z_index {
-                            highest_z_index = *z_index;
-                            clicked_region_id = Some(region.id());
-                        }
-                    }
-
-                    self.window.clicked_region =
-                        clicked_region_id.map(|region_id| (region_id, e.button));
-                }
-
-                mouse_events.push(MouseEvent::Down(MouseDown {
-                    region: Default::default(),
-                    platform_event: e.clone(),
-                }));
-                mouse_events.push(MouseEvent::DownOut(MouseDownOut {
-                    region: Default::default(),
-                    platform_event: e.clone(),
-                }));
-            }
-
-            Event::MouseUp(e) => {
-                mouse_events.push(MouseEvent::Up(MouseUp {
-                    region: Default::default(),
-                    platform_event: e.clone(),
-                }));
-
-                // Synthesize one last drag event to end the drag
-                mouse_events.push(MouseEvent::Drag(MouseDrag {
-                    region: Default::default(),
-                    prev_mouse_position: self.window.last_mouse_position,
-                    platform_event: MouseMovedEvent {
-                        position: e.position,
-                        pressed_button: Some(e.button),
-                        modifiers: e.modifiers,
-                    },
-                    end: true,
-                }));
-
-                mouse_events.push(MouseEvent::UpOut(MouseUpOut {
-                    region: Default::default(),
-                    platform_event: e.clone(),
-                }));
-                mouse_events.push(MouseEvent::Click(MouseClick {
-                    region: Default::default(),
-                    platform_event: e.clone(),
-                }));
-                mouse_events.push(MouseEvent::ClickOut(MouseClickOut {
-                    region: Default::default(),
-                    platform_event: e.clone(),
-                }));
-            }
-
-            Event::MouseMoved(
-                e @ MouseMovedEvent {
-                    position,
-                    pressed_button,
-                    ..
-                },
-            ) => {
-                let mut style_to_assign = CursorStyle::Arrow;
-                for region in self.window.cursor_regions.iter().rev() {
-                    if region.bounds.contains_point(*position) {
-                        style_to_assign = region.style;
-                        break;
-                    }
-                }
-
-                if pressed_button.is_none()
-                    && self
-                        .window
-                        .platform_window
-                        .is_topmost_for_position(*position)
-                {
-                    self.platform().set_cursor_style(style_to_assign);
-                }
-
-                if !event_reused {
-                    if pressed_button.is_some() {
-                        mouse_events.push(MouseEvent::Drag(MouseDrag {
-                            region: Default::default(),
-                            prev_mouse_position: self.window.last_mouse_position,
-                            platform_event: e.clone(),
-                            end: false,
-                        }));
-                    } else if let Some((_, clicked_button)) = self.window.clicked_region {
-                        mouse_events.push(MouseEvent::Drag(MouseDrag {
-                            region: Default::default(),
-                            prev_mouse_position: self.window.last_mouse_position,
-                            platform_event: e.clone(),
-                            end: true,
-                        }));
-
-                        // Mouse up event happened outside the current window. Simulate mouse up button event
-                        let button_event = e.to_button_event(clicked_button);
-                        mouse_events.push(MouseEvent::Up(MouseUp {
-                            region: Default::default(),
-                            platform_event: button_event.clone(),
-                        }));
-                        mouse_events.push(MouseEvent::UpOut(MouseUpOut {
-                            region: Default::default(),
-                            platform_event: button_event.clone(),
-                        }));
-                        mouse_events.push(MouseEvent::Click(MouseClick {
-                            region: Default::default(),
-                            platform_event: button_event.clone(),
-                        }));
-                    }
-
-                    mouse_events.push(MouseEvent::Move(MouseMove {
-                        region: Default::default(),
-                        platform_event: e.clone(),
-                    }));
-                }
-
-                mouse_events.push(MouseEvent::Hover(MouseHover {
-                    region: Default::default(),
-                    platform_event: e.clone(),
-                    started: false,
-                }));
-                mouse_events.push(MouseEvent::MoveOut(MouseMoveOut {
-                    region: Default::default(),
-                }));
-
-                self.window.last_mouse_moved_event = Some(event.clone());
-            }
-
-            Event::MouseExited(event) => {
-                // When the platform sends a MouseExited event, synthesize
-                // a MouseMoved event whose position is outside the window's
-                // bounds so that hover and cursor state can be updated.
-                return self.dispatch_event(
-                    Event::MouseMoved(MouseMovedEvent {
-                        position: event.position,
-                        pressed_button: event.pressed_button,
-                        modifiers: event.modifiers,
-                    }),
-                    event_reused,
-                );
-            }
-
-            Event::ScrollWheel(e) => mouse_events.push(MouseEvent::ScrollWheel(MouseScrollWheel {
-                region: Default::default(),
-                platform_event: e.clone(),
-            })),
-        }
-
-        if let Some(position) = event.position() {
-            self.window.last_mouse_position = position;
-        }
-
-        // 2. Dispatch mouse events on regions
-        let mut any_event_handled = false;
-        for mut mouse_event in mouse_events {
-            let mut valid_regions = Vec::new();
-
-            // GPUI elements are arranged by z_index but sibling elements can register overlapping
-            // mouse regions. As such, hover events are only fired on overlapping elements which
-            // are at the same z-index as the topmost element which overlaps with the mouse.
-            match &mouse_event {
-                MouseEvent::Hover(_) => {
-                    let mut highest_z_index = None;
-                    let mouse_position = self.mouse_position();
-                    let window = &mut *self.window;
-                    let prev_hovered_regions = mem::take(&mut window.hovered_region_ids);
-                    for (region, z_index) in window.mouse_regions.iter().rev() {
-                        // Allow mouse regions to appear transparent to hovers
-                        if !region.hoverable {
-                            continue;
-                        }
-
-                        let contains_mouse = region.bounds.contains_point(mouse_position);
-
-                        if contains_mouse && highest_z_index.is_none() {
-                            highest_z_index = Some(z_index);
-                        }
-
-                        // This unwrap relies on short circuiting boolean expressions
-                        // The right side of the && is only executed when contains_mouse
-                        // is true, and we know above that when contains_mouse is true
-                        // highest_z_index is set.
-                        if contains_mouse && z_index == highest_z_index.unwrap() {
-                            //Ensure that hover entrance events aren't sent twice
-                            if let Err(ix) = window.hovered_region_ids.binary_search(&region.id()) {
-                                window.hovered_region_ids.insert(ix, region.id());
-                            }
-                            // window.hovered_region_ids.insert(region.id());
-                            if !prev_hovered_regions.contains(&region.id()) {
-                                valid_regions.push(region.clone());
-                                if region.notify_on_hover {
-                                    notified_views.insert(region.id().view_id());
-                                }
-                            }
-                        } else {
-                            // Ensure that hover exit events aren't sent twice
-                            if prev_hovered_regions.contains(&region.id()) {
-                                valid_regions.push(region.clone());
-                                if region.notify_on_hover {
-                                    notified_views.insert(region.id().view_id());
-                                }
-                            }
-                        }
-                    }
-                }
-
-                MouseEvent::Down(_) | MouseEvent::Up(_) => {
-                    for (region, _) in self.window.mouse_regions.iter().rev() {
-                        if region.bounds.contains_point(self.mouse_position()) {
-                            valid_regions.push(region.clone());
-                            if region.notify_on_click {
-                                notified_views.insert(region.id().view_id());
-                            }
-                        }
-                    }
-                }
-
-                MouseEvent::Click(e) => {
-                    // Only raise click events if the released button is the same as the one stored
-                    if self
-                        .window
-                        .clicked_region
-                        .map(|(_, clicked_button)| clicked_button == e.button)
-                        .unwrap_or(false)
-                    {
-                        // Clear clicked regions and clicked button
-                        let clicked_region_ids = std::mem::replace(
-                            &mut self.window.clicked_region_ids,
-                            Default::default(),
-                        );
-                        self.window.clicked_region = None;
-
-                        // Find regions which still overlap with the mouse since the last MouseDown happened
-                        for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
-                            if clicked_region_ids.contains(&mouse_region.id()) {
-                                if mouse_region.bounds.contains_point(self.mouse_position()) {
-                                    valid_regions.push(mouse_region.clone());
-                                } else {
-                                    // Let the view know that it hasn't been clicked anymore
-                                    if mouse_region.notify_on_click {
-                                        notified_views.insert(mouse_region.id().view_id());
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-
-                MouseEvent::Drag(_) => {
-                    for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
-                        if self.window.clicked_region_ids.contains(&mouse_region.id()) {
-                            valid_regions.push(mouse_region.clone());
-                        }
-                    }
-                }
-
-                MouseEvent::MoveOut(_)
-                | MouseEvent::UpOut(_)
-                | MouseEvent::DownOut(_)
-                | MouseEvent::ClickOut(_) => {
-                    for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
-                        // NOT contains
-                        if !mouse_region.bounds.contains_point(self.mouse_position()) {
-                            valid_regions.push(mouse_region.clone());
-                        }
-                    }
-                }
-
-                _ => {
-                    for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
-                        // Contains
-                        if mouse_region.bounds.contains_point(self.mouse_position()) {
-                            valid_regions.push(mouse_region.clone());
-                        }
-                    }
-                }
-            }
-
-            //3. Fire region events
-            let hovered_region_ids = self.window.hovered_region_ids.clone();
-            for valid_region in valid_regions.into_iter() {
-                let mut handled = false;
-                mouse_event.set_region(valid_region.bounds);
-                if let MouseEvent::Hover(e) = &mut mouse_event {
-                    e.started = hovered_region_ids.contains(&valid_region.id())
-                }
-                // Handle Down events if the MouseRegion has a Click or Drag handler. This makes the api more intuitive as you would
-                // not expect a MouseRegion to be transparent to Down events if it also has a Click handler.
-                // This behavior can be overridden by adding a Down handler
-                if let MouseEvent::Down(e) = &mouse_event {
-                    let has_click = valid_region
-                        .handlers
-                        .contains(MouseEvent::click_disc(), Some(e.button));
-                    let has_drag = valid_region
-                        .handlers
-                        .contains(MouseEvent::drag_disc(), Some(e.button));
-                    let has_down = valid_region
-                        .handlers
-                        .contains(MouseEvent::down_disc(), Some(e.button));
-                    if !has_down && (has_click || has_drag) {
-                        handled = true;
-                    }
-                }
-
-                // `event_consumed` should only be true if there are any handlers for this event.
-                let mut event_consumed = handled;
-                if let Some(callbacks) = valid_region.handlers.get(&mouse_event.handler_key()) {
-                    for callback in callbacks {
-                        handled = true;
-                        let view_id = valid_region.id().view_id();
-                        self.update_any_view(view_id, |view, cx| {
-                            handled = callback(mouse_event.clone(), view.as_any_mut(), cx, view_id);
-                        });
-                        event_consumed |= handled;
-                        any_event_handled |= handled;
-                    }
-                }
-
-                any_event_handled |= handled;
-
-                // For bubbling events, if the event was handled, don't continue dispatching.
-                // This only makes sense for local events which return false from is_capturable.
-                if event_consumed && mouse_event.is_capturable() {
-                    break;
-                }
-            }
-        }
-
-        for view_id in notified_views {
-            self.notify_view(handle, view_id);
-        }
-
-        any_event_handled
-    }
-
-    fn dispatch_event_2(&mut self, event: &Event) {
-        match event {
-            Event::MouseDown(event) => {
-                self.window.pressed_buttons.insert(event.button);
-            }
-            Event::MouseUp(event) => {
-                self.window.pressed_buttons.remove(&event.button);
-            }
-            _ => {}
-        }
-
-        if let Some(mouse_event) = event.mouse_event() {
-            let event_handlers = self.window.take_event_handlers();
-            for event_handler in event_handlers.iter().rev() {
-                if event_handler.event_type == mouse_event.type_id() {
-                    if !(event_handler.handler)(mouse_event, self) {
-                        break;
-                    }
-                }
-            }
-            self.window.event_handlers = event_handlers;
-        }
-    }
-
-    pub(crate) fn dispatch_key_down(&mut self, event: &KeyDownEvent) -> bool {
-        let handle = self.window_handle;
-        if let Some(focused_view_id) = self.window.focused_view_id {
-            for view_id in self.ancestors(focused_view_id).collect::<Vec<_>>() {
-                if let Some(mut view) = self.views.remove(&(handle, view_id)) {
-                    let handled = view.key_down(event, self, view_id);
-                    self.views.insert((handle, view_id), view);
-                    if handled {
-                        return true;
-                    }
-                } else {
-                    log::error!("view {} does not exist", view_id)
-                }
-            }
-        }
-
-        false
-    }
-
-    pub(crate) fn dispatch_key_up(&mut self, event: &KeyUpEvent) -> bool {
-        let handle = self.window_handle;
-        if let Some(focused_view_id) = self.window.focused_view_id {
-            for view_id in self.ancestors(focused_view_id).collect::<Vec<_>>() {
-                if let Some(mut view) = self.views.remove(&(handle, view_id)) {
-                    let handled = view.key_up(event, self, view_id);
-                    self.views.insert((handle, view_id), view);
-                    if handled {
-                        return true;
-                    }
-                } else {
-                    log::error!("view {} does not exist", view_id)
-                }
-            }
-        }
-
-        false
-    }
-
-    pub(crate) fn dispatch_modifiers_changed(&mut self, event: &ModifiersChangedEvent) -> bool {
-        let handle = self.window_handle;
-        if let Some(focused_view_id) = self.window.focused_view_id {
-            for view_id in self.ancestors(focused_view_id).collect::<Vec<_>>() {
-                if let Some(mut view) = self.views.remove(&(handle, view_id)) {
-                    let handled = view.modifiers_changed(event, self, view_id);
-                    self.views.insert((handle, view_id), view);
-                    if handled {
-                        return true;
-                    }
-                } else {
-                    log::error!("view {} does not exist", view_id)
-                }
-            }
-        }
-
-        false
-    }
-
-    pub fn invalidate(&mut self, mut invalidation: WindowInvalidation, appearance: Appearance) {
-        self.start_frame();
-        self.window.appearance = appearance;
-        for view_id in &invalidation.removed {
-            invalidation.updated.remove(view_id);
-            self.window.rendered_views.remove(view_id);
-        }
-        for view_id in &invalidation.updated {
-            let titlebar_height = self.window.titlebar_height;
-            let element = self
-                .render_view(RenderParams {
-                    view_id: *view_id,
-                    titlebar_height,
-                    refreshing: false,
-                    appearance,
-                })
-                .unwrap();
-            self.window.rendered_views.insert(*view_id, element);
-        }
-    }
-
-    pub fn render_view(&mut self, params: RenderParams) -> Result<Box<dyn AnyRootElement>> {
-        let handle = self.window_handle;
-        let view_id = params.view_id;
-        let mut view = self
-            .views
-            .remove(&(handle, view_id))
-            .ok_or_else(|| anyhow!("view not found"))?;
-        let element = view.render(self, view_id);
-        self.views.insert((handle, view_id), view);
-        Ok(element)
-    }
-
-    pub fn layout(&mut self, refreshing: bool) -> Result<HashMap<usize, usize>> {
-        let window_size = self.window.platform_window.content_size();
-        let root_view_id = self.window.root_view().id();
-
-        let mut rendered_root = self.window.rendered_views.remove(&root_view_id).unwrap();
-
-        self.window.refreshing = refreshing;
-        rendered_root.layout(SizeConstraint::strict(window_size), self)?;
-        self.window.refreshing = false;
-
-        let views_to_notify_if_ancestors_change =
-            mem::take(&mut self.window.views_to_notify_if_ancestors_change);
-        for (view_id, view_ids_to_notify) in views_to_notify_if_ancestors_change {
-            let mut current_view_id = view_id;
-            loop {
-                let old_parent_id = self.window.parents.get(&current_view_id);
-                let new_parent_id = self.window.new_parents.get(&current_view_id);
-                if old_parent_id.is_none() && new_parent_id.is_none() {
-                    break;
-                } else if old_parent_id == new_parent_id {
-                    current_view_id = *old_parent_id.unwrap();
-                } else {
-                    let handle = self.window_handle;
-                    for view_id_to_notify in view_ids_to_notify {
-                        self.notify_view(handle, view_id_to_notify);
-                    }
-                    break;
-                }
-            }
-        }
-
-        let new_parents = mem::take(&mut self.window.new_parents);
-        let old_parents = mem::replace(&mut self.window.parents, new_parents);
-        self.window
-            .rendered_views
-            .insert(root_view_id, rendered_root);
-        Ok(old_parents)
-    }
-
-    pub fn paint(&mut self) -> Result<Scene> {
-        let window_size = self.window.platform_window.content_size();
-        let scale_factor = self.window.platform_window.scale_factor();
-
-        let root_view_id = self.window.root_view().id();
-        let mut rendered_root = self.window.rendered_views.remove(&root_view_id).unwrap();
-
-        rendered_root.paint(
-            Vector2F::zero(),
-            RectF::from_points(Vector2F::zero(), window_size),
-            self,
-        )?;
-        self.window
-            .rendered_views
-            .insert(root_view_id, rendered_root);
-
-        self.window.text_layout_cache.finish_frame();
-        let mut scene = self.window.scene.build(scale_factor);
-        self.window.cursor_regions = scene.cursor_regions();
-        self.window.mouse_regions = scene.mouse_regions();
-        self.window.event_handlers = scene.take_event_handlers();
-
-        if self.window_is_active() {
-            if let Some(event) = self.window.last_mouse_moved_event.clone() {
-                self.dispatch_event(event, true);
-            }
-        }
-
-        Ok(scene)
-    }
-
-    pub fn root_element(&self) -> &Box<dyn AnyRootElement> {
-        let view_id = self.window.root_view().id();
-        self.window.rendered_views.get(&view_id).unwrap()
-    }
-
-    pub fn rect_for_text_range(&self, range_utf16: Range<usize>) -> Option<RectF> {
-        let focused_view_id = self.window.focused_view_id?;
-        self.window
-            .rendered_views
-            .get(&focused_view_id)?
-            .rect_for_text_range(range_utf16, self)
-            .log_err()
-            .flatten()
-    }
-
-    pub fn set_window_title(&mut self, title: &str) {
-        self.window.platform_window.set_title(title);
-    }
-
-    pub fn set_window_edited(&mut self, edited: bool) {
-        self.window.platform_window.set_edited(edited);
-    }
-
-    pub fn is_topmost_window_for_position(&self, position: Vector2F) -> bool {
-        self.window
-            .platform_window
-            .is_topmost_for_position(position)
-    }
-
-    pub fn activate_window(&self) {
-        self.window.platform_window.activate();
-    }
-
-    pub fn window_is_active(&self) -> bool {
-        self.window.is_active
-    }
-
-    pub fn window_is_fullscreen(&self) -> bool {
-        self.window.is_fullscreen
-    }
-
-    pub fn dispatch_action(&mut self, view_id: Option<usize>, action: &dyn Action) -> bool {
-        if let Some(view_id) = view_id {
-            self.halt_action_dispatch = false;
-            self.visit_dispatch_path(view_id, |view_id, capture_phase, cx| {
-                cx.update_any_view(view_id, |view, cx| {
-                    let type_id = view.as_any().type_id();
-                    if let Some((name, mut handlers)) = cx
-                        .actions_mut(capture_phase)
-                        .get_mut(&type_id)
-                        .and_then(|h| h.remove_entry(&action.id()))
-                    {
-                        for handler in handlers.iter_mut().rev() {
-                            cx.halt_action_dispatch = true;
-                            handler(view, action, cx, view_id);
-                            if cx.halt_action_dispatch {
-                                break;
-                            }
-                        }
-                        cx.actions_mut(capture_phase)
-                            .get_mut(&type_id)
-                            .unwrap()
-                            .insert(name, handlers);
-                    }
-                });
-
-                !cx.halt_action_dispatch
-            });
-        }
-
-        if !self.halt_action_dispatch {
-            self.halt_action_dispatch = self.dispatch_global_action_any(action);
-        }
-
-        self.pending_effects
-            .push_back(Effect::ActionDispatchNotification {
-                action_id: action.id(),
-            });
-        self.halt_action_dispatch
-    }
-
-    /// Returns an iterator over all of the view ids from the passed view up to the root of the window
-    /// Includes the passed view itself
-    pub(crate) fn ancestors(&self, mut view_id: usize) -> impl Iterator<Item = usize> + '_ {
-        std::iter::once(view_id)
-            .into_iter()
-            .chain(std::iter::from_fn(move || {
-                if let Some(parent_id) = self.window.parents.get(&view_id) {
-                    view_id = *parent_id;
-                    Some(view_id)
-                } else {
-                    None
-                }
-            }))
-    }
-
-    // Traverses the parent tree. Walks down the tree toward the passed
-    // view calling visit with true. Then walks back up the tree calling visit with false.
-    // If `visit` returns false this function will immediately return.
-    fn visit_dispatch_path(
-        &mut self,
-        view_id: usize,
-        mut visit: impl FnMut(usize, bool, &mut WindowContext) -> bool,
-    ) {
-        // List of view ids from the leaf to the root of the window
-        let path = self.ancestors(view_id).collect::<Vec<_>>();
-
-        // Walk down from the root to the leaf calling visit with capture_phase = true
-        for view_id in path.iter().rev() {
-            if !visit(*view_id, true, self) {
-                return;
-            }
-        }
-
-        // Walk up from the leaf to the root calling visit with capture_phase = false
-        for view_id in path.iter() {
-            if !visit(*view_id, false, self) {
-                return;
-            }
-        }
-    }
-
-    pub fn focused_view_id(&self) -> Option<usize> {
-        self.window.focused_view_id
-    }
-
-    pub fn focus(&mut self, view_id: Option<usize>) {
-        self.app_context.focus(self.window_handle, view_id);
-    }
-
-    pub fn window_bounds(&self) -> WindowBounds {
-        self.window.platform_window.bounds()
-    }
-
-    pub fn titlebar_height(&self) -> f32 {
-        self.window.titlebar_height
-    }
-
-    pub fn window_appearance(&self) -> Appearance {
-        self.window.appearance
-    }
-
-    pub fn window_display_uuid(&self) -> Option<Uuid> {
-        self.window.platform_window.screen().display_uuid()
-    }
-
-    pub fn show_character_palette(&self) {
-        self.window.platform_window.show_character_palette();
-    }
-
-    pub fn minimize_window(&self) {
-        self.window.platform_window.minimize();
-    }
-
-    pub fn zoom_window(&self) {
-        self.window.platform_window.zoom();
-    }
-
-    pub fn toggle_full_screen(&self) {
-        self.window.platform_window.toggle_full_screen();
-    }
-
-    pub fn prompt(
-        &self,
-        level: PromptLevel,
-        msg: &str,
-        answers: &[&str],
-    ) -> oneshot::Receiver<usize> {
-        self.window.platform_window.prompt(level, msg, answers)
-    }
-
-    pub fn add_view<T, F>(&mut self, build_view: F) -> ViewHandle<T>
-    where
-        T: View,
-        F: FnOnce(&mut ViewContext<T>) -> T,
-    {
-        self.add_option_view(|cx| Some(build_view(cx))).unwrap()
-    }
-
-    pub fn add_option_view<T, F>(&mut self, build_view: F) -> Option<ViewHandle<T>>
-    where
-        T: View,
-        F: FnOnce(&mut ViewContext<T>) -> Option<T>,
-    {
-        let handle = self.window_handle;
-        let view_id = post_inc(&mut self.next_id);
-        let mut cx = ViewContext::mutable(self, view_id);
-        let handle = if let Some(view) = build_view(&mut cx) {
-            let mut keymap_context = KeymapContext::default();
-            view.update_keymap_context(&mut keymap_context, cx.app_context());
-            self.views_metadata.insert(
-                (handle, view_id),
-                ViewMetadata {
-                    type_id: TypeId::of::<T>(),
-                    keymap_context,
-                },
-            );
-            self.views.insert((handle, view_id), Box::new(view));
-            self.window
-                .invalidation
-                .get_or_insert_with(Default::default)
-                .updated
-                .insert(view_id);
-            Some(ViewHandle::new(handle, view_id, &self.ref_counts))
-        } else {
-            None
-        };
-        handle
-    }
-
-    pub fn text_style(&self) -> TextStyle {
-        self.window
-            .text_style_stack
-            .last()
-            .cloned()
-            .unwrap_or(TextStyle::default(&self.font_cache))
-    }
-
-    pub fn push_text_style(&mut self, refinement: &TextStyleRefinement) -> Result<()> {
-        let mut style = self.text_style();
-        style.refine(refinement, self.font_cache())?;
-        self.window.text_style_stack.push(style);
-        Ok(())
-    }
-
-    pub fn pop_text_style(&mut self) {
-        self.window.text_style_stack.pop();
-    }
-
-    pub fn theme<T: 'static + Send + Sync>(&self) -> Arc<T> {
-        self.window
-            .theme_stack
-            .iter()
-            .rev()
-            .find_map(|theme| {
-                let entry = Arc::clone(theme);
-                entry.downcast::<T>().ok()
-            })
-            .ok_or_else(|| anyhow!("no theme provided of type {}", type_name::<T>()))
-            .unwrap()
-    }
-
-    pub fn push_theme<T: 'static + Send + Sync>(&mut self, theme: T) {
-        self.window.theme_stack.push(Arc::new(theme));
-    }
-
-    pub fn pop_theme(&mut self) {
-        self.window.theme_stack.pop();
-    }
-}
-
-#[derive(Default)]
-pub struct LayoutEngine(Taffy);
-pub use taffy::style::Style as LayoutStyle;
-
-impl LayoutEngine {
-    pub fn new() -> Self {
-        Default::default()
-    }
-
-    pub fn add_node<C>(&mut self, style: LayoutStyle, children: C) -> Result<LayoutId>
-    where
-        C: IntoIterator<Item = LayoutId>,
-    {
-        let children = children.into_iter().collect::<Vec<_>>();
-        if children.is_empty() {
-            Ok(self.0.new_leaf(style)?)
-        } else {
-            Ok(self.0.new_with_children(style, &children)?)
-        }
-    }
-
-    pub fn add_measured_node<F>(&mut self, style: LayoutStyle, measure: F) -> Result<LayoutId>
-    where
-        F: Fn(MeasureParams) -> Size<f32> + Sync + Send + 'static,
-    {
-        Ok(self
-            .0
-            .new_leaf_with_measure(style, MeasureFunc::Boxed(Box::new(MeasureFn(measure))))?)
-    }
-
-    pub fn compute_layout(&mut self, root: LayoutId, available_space: Vector2F) -> Result<()> {
-        self.0.compute_layout(
-            root,
-            taffy::geometry::Size {
-                width: available_space.x().into(),
-                height: available_space.y().into(),
-            },
-        )?;
-        Ok(())
-    }
-
-    pub fn computed_layout(&mut self, node: LayoutId) -> Result<Layout> {
-        Ok(Layout::from(self.0.layout(node)?))
-    }
-}
-
-pub struct MeasureFn<F>(F);
-
-impl<F: Send + Sync> Measurable for MeasureFn<F>
-where
-    F: Fn(MeasureParams) -> Size<f32>,
-{
-    fn measure(
-        &self,
-        known_dimensions: taffy::prelude::Size<Option<f32>>,
-        available_space: taffy::prelude::Size<taffy::style::AvailableSpace>,
-    ) -> taffy::prelude::Size<f32> {
-        (self.0)(MeasureParams {
-            known_dimensions: known_dimensions.into(),
-            available_space: available_space.into(),
-        })
-        .into()
-    }
-}
-
-#[derive(Debug, Clone, Default)]
-pub struct Layout {
-    pub bounds: RectF,
-    pub order: u32,
-}
-
-pub struct MeasureParams {
-    pub known_dimensions: Size<Option<f32>>,
-    pub available_space: Size<AvailableSpace>,
-}
-
-#[derive(Clone, Debug)]
-pub enum AvailableSpace {
-    /// The amount of space available is the specified number of pixels
-    Pixels(f32),
-    /// The amount of space available is indefinite and the node should be laid out under a min-content constraint
-    MinContent,
-    /// The amount of space available is indefinite and the node should be laid out under a max-content constraint
-    MaxContent,
-}
-
-impl Default for AvailableSpace {
-    fn default() -> Self {
-        Self::Pixels(0.)
-    }
-}
-
-impl From<taffy::prelude::AvailableSpace> for AvailableSpace {
-    fn from(value: taffy::prelude::AvailableSpace) -> Self {
-        match value {
-            taffy::prelude::AvailableSpace::Definite(pixels) => Self::Pixels(pixels),
-            taffy::prelude::AvailableSpace::MinContent => Self::MinContent,
-            taffy::prelude::AvailableSpace::MaxContent => Self::MaxContent,
-        }
-    }
-}
-
-impl From<&taffy::tree::Layout> for Layout {
-    fn from(value: &taffy::tree::Layout) -> Self {
-        Self {
-            bounds: RectF::new(
-                vec2f(value.location.x, value.location.y),
-                vec2f(value.size.width, value.size.height),
-            ),
-            order: value.order,
-        }
-    }
-}
-
-pub type LayoutId = taffy::prelude::NodeId;
-
-pub struct RenderParams {
-    pub view_id: usize,
-    pub titlebar_height: f32,
-    pub refreshing: bool,
-    pub appearance: Appearance,
-}
-
-#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
-pub enum Axis {
-    #[default]
-    Horizontal,
-    Vertical,
-}
-
-impl Axis {
-    pub fn invert(self) -> Self {
-        match self {
-            Self::Horizontal => Self::Vertical,
-            Self::Vertical => Self::Horizontal,
-        }
-    }
-
-    pub fn component(&self, point: Vector2F) -> f32 {
-        match self {
-            Self::Horizontal => point.x(),
-            Self::Vertical => point.y(),
-        }
-    }
-}
-
-impl ToJson for Axis {
-    fn to_json(&self) -> serde_json::Value {
-        match self {
-            Axis::Horizontal => json!("horizontal"),
-            Axis::Vertical => json!("vertical"),
-        }
-    }
-}
-
-impl StaticColumnCount for Axis {}
-impl Bind for Axis {
-    fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result<i32> {
-        match self {
-            Axis::Horizontal => "Horizontal",
-            Axis::Vertical => "Vertical",
-        }
-        .bind(statement, start_index)
-    }
-}
-
-impl Column for Axis {
-    fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> {
-        String::column(statement, start_index).and_then(|(axis_text, next_index)| {
-            Ok((
-                match axis_text.as_str() {
-                    "Horizontal" => Axis::Horizontal,
-                    "Vertical" => Axis::Vertical,
-                    _ => bail!("Stored serialized item kind is incorrect"),
-                },
-                next_index,
-            ))
-        })
-    }
-}
-
-pub trait Vector2FExt {
-    fn along(self, axis: Axis) -> f32;
-}
-
-impl Vector2FExt for Vector2F {
-    fn along(self, axis: Axis) -> f32 {
-        match axis {
-            Axis::Horizontal => self.x(),
-            Axis::Vertical => self.y(),
-        }
-    }
-}
-
-pub trait RectFExt {
-    fn length_along(self, axis: Axis) -> f32;
-}
-
-impl RectFExt for RectF {
-    fn length_along(self, axis: Axis) -> f32 {
-        match axis {
-            Axis::Horizontal => self.width(),
-            Axis::Vertical => self.height(),
-        }
-    }
-}
-
-#[derive(Copy, Clone, Debug)]
-pub struct SizeConstraint {
-    pub min: Vector2F,
-    pub max: Vector2F,
-}
-
-impl SizeConstraint {
-    pub fn new(min: Vector2F, max: Vector2F) -> Self {
-        Self { min, max }
-    }
-
-    pub fn strict(size: Vector2F) -> Self {
-        Self {
-            min: size,
-            max: size,
-        }
-    }
-    pub fn loose(max: Vector2F) -> Self {
-        Self {
-            min: Vector2F::zero(),
-            max,
-        }
-    }
-
-    pub fn strict_along(axis: Axis, max: f32) -> Self {
-        match axis {
-            Axis::Horizontal => Self {
-                min: vec2f(max, 0.0),
-                max: vec2f(max, f32::INFINITY),
-            },
-            Axis::Vertical => Self {
-                min: vec2f(0.0, max),
-                max: vec2f(f32::INFINITY, max),
-            },
-        }
-    }
-
-    pub fn max_along(&self, axis: Axis) -> f32 {
-        match axis {
-            Axis::Horizontal => self.max.x(),
-            Axis::Vertical => self.max.y(),
-        }
-    }
-
-    pub fn min_along(&self, axis: Axis) -> f32 {
-        match axis {
-            Axis::Horizontal => self.min.x(),
-            Axis::Vertical => self.min.y(),
-        }
-    }
-
-    pub fn constrain(&self, size: Vector2F) -> Vector2F {
-        vec2f(
-            size.x().min(self.max.x()).max(self.min.x()),
-            size.y().min(self.max.y()).max(self.min.y()),
-        )
-    }
-}
-
-impl Sub<Vector2F> for SizeConstraint {
-    type Output = SizeConstraint;
-
-    fn sub(self, rhs: Vector2F) -> SizeConstraint {
-        SizeConstraint {
-            min: self.min - rhs,
-            max: self.max - rhs,
-        }
-    }
-}
-
-impl Default for SizeConstraint {
-    fn default() -> Self {
-        SizeConstraint {
-            min: Vector2F::zero(),
-            max: Vector2F::splat(f32::INFINITY),
-        }
-    }
-}
-
-impl ToJson for SizeConstraint {
-    fn to_json(&self) -> serde_json::Value {
-        json!({
-            "min": self.min.to_json(),
-            "max": self.max.to_json(),
-        })
-    }
-}
-
-#[derive(Clone)]
-pub struct ChildView {
-    view_id: usize,
-    view_name: &'static str,
-}
-
-impl ChildView {
-    pub fn new(view: &AnyViewHandle, cx: &AppContext) -> Self {
-        let view_name = cx.view_ui_name(view.window, view.id()).unwrap();
-        Self {
-            view_id: view.id(),
-            view_name,
-        }
-    }
-}
-
-impl<V: 'static> Element<V> for ChildView {
-    type LayoutState = ();
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        constraint: SizeConstraint,
-        _: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> (Vector2F, Self::LayoutState) {
-        if let Some(mut rendered_view) = cx.window.rendered_views.remove(&self.view_id) {
-            let parent_id = cx.view_id();
-            cx.window.new_parents.insert(self.view_id, parent_id);
-            let size = rendered_view
-                .layout(constraint, cx)
-                .log_err()
-                .unwrap_or(Vector2F::zero());
-            cx.window.rendered_views.insert(self.view_id, rendered_view);
-            (size, ())
-        } else {
-            log::error!(
-                "layout called on a ChildView element whose underlying view was dropped (view_id: {}, name: {:?})",
-                self.view_id,
-                self.view_name
-            );
-            (Vector2F::zero(), ())
-        }
-    }
-
-    fn paint(
-        &mut self,
-        bounds: RectF,
-        visible_bounds: RectF,
-        _: &mut Self::LayoutState,
-        _: &mut V,
-        cx: &mut ViewContext<V>,
-    ) {
-        if let Some(mut rendered_view) = cx.window.rendered_views.remove(&self.view_id) {
-            rendered_view
-                .paint(bounds.origin(), visible_bounds, cx)
-                .log_err();
-            cx.window.rendered_views.insert(self.view_id, rendered_view);
-        } else {
-            log::error!(
-                "paint called on a ChildView element whose underlying view was dropped (view_id: {}, name: {:?})",
-                self.view_id,
-                self.view_name
-            );
-        }
-    }
-
-    fn rect_for_text_range(
-        &self,
-        range_utf16: Range<usize>,
-        _: RectF,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        _: &V,
-        cx: &ViewContext<V>,
-    ) -> Option<RectF> {
-        if let Some(rendered_view) = cx.window.rendered_views.get(&self.view_id) {
-            rendered_view
-                .rect_for_text_range(range_utf16, &cx.window_context)
-                .log_err()
-                .flatten()
-        } else {
-            log::error!(
-                "rect_for_text_range called on a ChildView element whose underlying view was dropped (view_id: {}, name: {:?})",
-                self.view_id,
-                self.view_name
-            );
-            None
-        }
-    }
-
-    fn debug(
-        &self,
-        bounds: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        _: &V,
-        cx: &ViewContext<V>,
-    ) -> serde_json::Value {
-        json!({
-            "type": "ChildView",
-            "bounds": bounds.to_json(),
-            "child": if let Some(element) = cx.window.rendered_views.get(&self.view_id) {
-                element.debug(&cx.window_context).log_err().unwrap_or_else(|| json!(null))
-            } else {
-                json!(null)
-            }
-        })
-    }
-}

crates/gpui/src/app/window_input_handler.rs 🔗

@@ -1,90 +0,0 @@
-use std::{cell::RefCell, ops::Range, rc::Rc};
-
-use pathfinder_geometry::rect::RectF;
-
-use crate::{platform::InputHandler, window::WindowContext, AnyView, AnyWindowHandle, AppContext};
-
-pub struct WindowInputHandler {
-    pub app: Rc<RefCell<AppContext>>,
-    pub window: AnyWindowHandle,
-}
-
-impl WindowInputHandler {
-    fn read_focused_view<T, F>(&self, f: F) -> Option<T>
-    where
-        F: FnOnce(&dyn AnyView, &WindowContext) -> T,
-    {
-        // Input-related application hooks are sometimes called by the OS during
-        // a call to a window-manipulation API, like prompting the user for file
-        // paths. In that case, the AppContext will already be borrowed, so any
-        // InputHandler methods need to fail gracefully.
-        //
-        // See https://github.com/zed-industries/community/issues/444
-        let mut app = self.app.try_borrow_mut().ok()?;
-        self.window.update_optional(&mut *app, |cx| {
-            let view_id = cx.window.focused_view_id?;
-            let view = cx.views.get(&(self.window, view_id))?;
-            let result = f(view.as_ref(), &cx);
-            Some(result)
-        })
-    }
-
-    fn update_focused_view<T, F>(&mut self, f: F) -> Option<T>
-    where
-        F: FnOnce(&mut dyn AnyView, &mut WindowContext, usize) -> T,
-    {
-        let mut app = self.app.try_borrow_mut().ok()?;
-        self.window
-            .update(&mut *app, |cx| {
-                let view_id = cx.window.focused_view_id?;
-                cx.update_any_view(view_id, |view, cx| f(view, cx, view_id))
-            })
-            .flatten()
-    }
-}
-
-impl InputHandler for WindowInputHandler {
-    fn text_for_range(&self, range: Range<usize>) -> Option<String> {
-        self.read_focused_view(|view, cx| view.text_for_range(range.clone(), cx))
-            .flatten()
-    }
-
-    fn selected_text_range(&self) -> Option<Range<usize>> {
-        self.read_focused_view(|view, cx| view.selected_text_range(cx))
-            .flatten()
-    }
-
-    fn replace_text_in_range(&mut self, range: Option<Range<usize>>, text: &str) {
-        self.update_focused_view(|view, cx, view_id| {
-            view.replace_text_in_range(range, text, cx, view_id);
-        });
-    }
-
-    fn marked_text_range(&self) -> Option<Range<usize>> {
-        self.read_focused_view(|view, cx| view.marked_text_range(cx))
-            .flatten()
-    }
-
-    fn unmark_text(&mut self) {
-        self.update_focused_view(|view, cx, view_id| {
-            view.unmark_text(cx, view_id);
-        });
-    }
-
-    fn replace_and_mark_text_in_range(
-        &mut self,
-        range: Option<Range<usize>>,
-        new_text: &str,
-        new_selected_range: Option<Range<usize>>,
-    ) {
-        self.update_focused_view(|view, cx, view_id| {
-            view.replace_and_mark_text_in_range(range, new_text, new_selected_range, cx, view_id);
-        });
-    }
-
-    fn rect_for_range(&self, range_utf16: Range<usize>) -> Option<RectF> {
-        self.window.read_optional_with(&*self.app.borrow(), |cx| {
-            cx.rect_for_text_range(range_utf16)
-        })
-    }
-}

crates/gpui/src/assets.rs 🔗

@@ -1,12 +1,16 @@
-use anyhow::{anyhow, Result};
-use image::ImageFormat;
-use std::{borrow::Cow, cell::RefCell, collections::HashMap, sync::Arc};
-
-use crate::ImageData;
+use crate::{size, DevicePixels, Result, SharedString, Size};
+use anyhow::anyhow;
+use image::{Bgra, ImageBuffer};
+use std::{
+    borrow::Cow,
+    fmt,
+    hash::Hash,
+    sync::atomic::{AtomicUsize, Ordering::SeqCst},
+};
 
 pub trait AssetSource: 'static + Send + Sync {
     fn load(&self, path: &str) -> Result<Cow<[u8]>>;
-    fn list(&self, path: &str) -> Vec<Cow<'static, str>>;
+    fn list(&self, path: &str) -> Result<Vec<SharedString>>;
 }
 
 impl AssetSource for () {
@@ -17,49 +21,44 @@ impl AssetSource for () {
         ))
     }
 
-    fn list(&self, _: &str) -> Vec<Cow<'static, str>> {
-        vec![]
+    fn list(&self, _path: &str) -> Result<Vec<SharedString>> {
+        Ok(vec![])
     }
 }
 
-pub struct AssetCache {
-    source: Box<dyn AssetSource>,
-    svgs: RefCell<HashMap<String, usvg::Tree>>,
-    pngs: RefCell<HashMap<String, Arc<ImageData>>>,
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub struct ImageId(usize);
+
+pub struct ImageData {
+    pub id: ImageId,
+    data: ImageBuffer<Bgra<u8>, Vec<u8>>,
 }
 
-impl AssetCache {
-    pub fn new(source: impl AssetSource) -> Self {
+impl ImageData {
+    pub fn new(data: ImageBuffer<Bgra<u8>, Vec<u8>>) -> Self {
+        static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
+
         Self {
-            source: Box::new(source),
-            svgs: RefCell::new(HashMap::new()),
-            pngs: RefCell::new(HashMap::new()),
+            id: ImageId(NEXT_ID.fetch_add(1, SeqCst)),
+            data,
         }
     }
 
-    pub fn svg(&self, path: &str) -> Result<usvg::Tree> {
-        let mut svgs = self.svgs.borrow_mut();
-        if let Some(svg) = svgs.get(path) {
-            Ok(svg.clone())
-        } else {
-            let bytes = self.source.load(path)?;
-            let svg = usvg::Tree::from_data(&bytes, &usvg::Options::default())?;
-            svgs.insert(path.to_string(), svg.clone());
-            Ok(svg)
-        }
+    pub fn as_bytes(&self) -> &[u8] {
+        &self.data
     }
 
-    pub fn png(&self, path: &str) -> Result<Arc<ImageData>> {
-        let mut pngs = self.pngs.borrow_mut();
-        if let Some(png) = pngs.get(path) {
-            Ok(png.clone())
-        } else {
-            let bytes = self.source.load(path)?;
-            let image = ImageData::new(
-                image::load_from_memory_with_format(&bytes, ImageFormat::Png)?.into_bgra8(),
-            );
-            pngs.insert(path.to_string(), image.clone());
-            Ok(image)
-        }
+    pub fn size(&self) -> Size<DevicePixels> {
+        let (width, height) = self.data.dimensions();
+        size(width.into(), height.into())
+    }
+}
+
+impl fmt::Debug for ImageData {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("ImageData")
+            .field("id", &self.id)
+            .field("size", &self.data.dimensions())
+            .finish()
     }
 }

crates/gpui/src/clipboard.rs 🔗

@@ -1,42 +0,0 @@
-use seahash::SeaHasher;
-use serde::{Deserialize, Serialize};
-use std::hash::{Hash, Hasher};
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct ClipboardItem {
-    pub(crate) text: String,
-    pub(crate) metadata: Option<String>,
-}
-
-impl ClipboardItem {
-    pub fn new(text: String) -> Self {
-        Self {
-            text,
-            metadata: None,
-        }
-    }
-
-    pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
-        self.metadata = Some(serde_json::to_string(&metadata).unwrap());
-        self
-    }
-
-    pub fn text(&self) -> &String {
-        &self.text
-    }
-
-    pub fn metadata<T>(&self) -> Option<T>
-    where
-        T: for<'a> Deserialize<'a>,
-    {
-        self.metadata
-            .as_ref()
-            .and_then(|m| serde_json::from_str(m).ok())
-    }
-
-    pub(crate) fn text_hash(text: &str) -> u64 {
-        let mut hasher = SeaHasher::new();
-        text.hash(&mut hasher);
-        hasher.finish()
-    }
-}

crates/gpui/src/color.rs 🔗

@@ -1,65 +1,223 @@
-use std::{
-    borrow::Cow,
-    fmt,
-    ops::{Deref, DerefMut},
-};
+use anyhow::bail;
+use serde::de::{self, Deserialize, Deserializer, Visitor};
+use std::fmt;
+
+pub fn rgb<C: From<Rgba>>(hex: u32) -> C {
+    let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
+    let g = ((hex >> 8) & 0xFF) as f32 / 255.0;
+    let b = (hex & 0xFF) as f32 / 255.0;
+    Rgba { r, g, b, a: 1.0 }.into()
+}
 
-use crate::json::ToJson;
-use pathfinder_color::{ColorF, ColorU};
-use schemars::JsonSchema;
-use serde::{
-    de::{self, Unexpected},
-    Deserialize, Deserializer,
-};
-use serde_json::json;
+pub fn rgba(hex: u32) -> Rgba {
+    let r = ((hex >> 24) & 0xFF) as f32 / 255.0;
+    let g = ((hex >> 16) & 0xFF) as f32 / 255.0;
+    let b = ((hex >> 8) & 0xFF) as f32 / 255.0;
+    let a = (hex & 0xFF) as f32 / 255.0;
+    Rgba { r, g, b, a }
+}
 
-#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord, JsonSchema)]
-#[repr(transparent)]
-pub struct Color(#[schemars(with = "String")] pub ColorU);
+#[derive(PartialEq, Clone, Copy, Default)]
+pub struct Rgba {
+    pub r: f32,
+    pub g: f32,
+    pub b: f32,
+    pub a: f32,
+}
+
+impl fmt::Debug for Rgba {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "rgba({:#010x})", u32::from(*self))
+    }
+}
 
-pub fn color(rgba: u32) -> Color {
-    Color::from_u32(rgba)
+impl Rgba {
+    pub fn blend(&self, other: Rgba) -> Self {
+        if other.a >= 1.0 {
+            other
+        } else if other.a <= 0.0 {
+            return *self;
+        } else {
+            return Rgba {
+                r: (self.r * (1.0 - other.a)) + (other.r * other.a),
+                g: (self.g * (1.0 - other.a)) + (other.g * other.a),
+                b: (self.b * (1.0 - other.a)) + (other.b * other.a),
+                a: self.a,
+            };
+        }
+    }
 }
 
-pub fn rgb(r: f32, g: f32, b: f32) -> Color {
-    Color(ColorF::new(r, g, b, 1.).to_u8())
+impl From<Rgba> for u32 {
+    fn from(rgba: Rgba) -> Self {
+        let r = (rgba.r * 255.0) as u32;
+        let g = (rgba.g * 255.0) as u32;
+        let b = (rgba.b * 255.0) as u32;
+        let a = (rgba.a * 255.0) as u32;
+        (r << 24) | (g << 16) | (b << 8) | a
+    }
 }
 
-pub fn rgba(r: f32, g: f32, b: f32, a: f32) -> Color {
-    Color(ColorF::new(r, g, b, a).to_u8())
+struct RgbaVisitor;
+
+impl<'de> Visitor<'de> for RgbaVisitor {
+    type Value = Rgba;
+
+    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        formatter.write_str("a string in the format #rrggbb or #rrggbbaa")
+    }
+
+    fn visit_str<E: de::Error>(self, value: &str) -> Result<Rgba, E> {
+        Rgba::try_from(value).map_err(E::custom)
+    }
 }
 
-pub fn transparent_black() -> Color {
-    Color(ColorU::transparent_black())
+impl<'de> Deserialize<'de> for Rgba {
+    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+        deserializer.deserialize_str(RgbaVisitor)
+    }
 }
 
-pub fn black() -> Color {
-    Color(ColorU::black())
+impl From<Hsla> for Rgba {
+    fn from(color: Hsla) -> Self {
+        let h = color.h;
+        let s = color.s;
+        let l = color.l;
+
+        let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
+        let x = c * (1.0 - ((h * 6.0) % 2.0 - 1.0).abs());
+        let m = l - c / 2.0;
+        let cm = c + m;
+        let xm = x + m;
+
+        let (r, g, b) = match (h * 6.0).floor() as i32 {
+            0 | 6 => (cm, xm, m),
+            1 => (xm, cm, m),
+            2 => (m, cm, xm),
+            3 => (m, xm, cm),
+            4 => (xm, m, cm),
+            _ => (cm, m, xm),
+        };
+
+        Rgba {
+            r,
+            g,
+            b,
+            a: color.a,
+        }
+    }
 }
 
-pub fn white() -> Color {
-    Color(ColorU::white())
+impl TryFrom<&'_ str> for Rgba {
+    type Error = anyhow::Error;
+
+    fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
+        const RGB: usize = "rgb".len();
+        const RGBA: usize = "rgba".len();
+        const RRGGBB: usize = "rrggbb".len();
+        const RRGGBBAA: usize = "rrggbbaa".len();
+
+        const EXPECTED_FORMATS: &str = "Expected #rgb, #rgba, #rrggbb, or #rrggbbaa";
+
+        let Some(("", hex)) = value.trim().split_once('#') else {
+            bail!("invalid RGBA hex color: '{value}'. {EXPECTED_FORMATS}");
+        };
+
+        let (r, g, b, a) = match hex.len() {
+            RGB | RGBA => {
+                let r = u8::from_str_radix(&hex[0..1], 16)?;
+                let g = u8::from_str_radix(&hex[1..2], 16)?;
+                let b = u8::from_str_radix(&hex[2..3], 16)?;
+                let a = if hex.len() == RGBA {
+                    u8::from_str_radix(&hex[3..4], 16)?
+                } else {
+                    0xf
+                };
+
+                /// Duplicates a given hex digit.
+                /// E.g., `0xf` -> `0xff`.
+                const fn duplicate(value: u8) -> u8 {
+                    value << 4 | value
+                }
+
+                (duplicate(r), duplicate(g), duplicate(b), duplicate(a))
+            }
+            RRGGBB | RRGGBBAA => {
+                let r = u8::from_str_radix(&hex[0..2], 16)?;
+                let g = u8::from_str_radix(&hex[2..4], 16)?;
+                let b = u8::from_str_radix(&hex[4..6], 16)?;
+                let a = if hex.len() == RRGGBBAA {
+                    u8::from_str_radix(&hex[6..8], 16)?
+                } else {
+                    0xff
+                };
+                (r, g, b, a)
+            }
+            _ => bail!("invalid RGBA hex color: '{value}'. {EXPECTED_FORMATS}"),
+        };
+
+        Ok(Rgba {
+            r: r as f32 / 255.,
+            g: g as f32 / 255.,
+            b: b as f32 / 255.,
+            a: a as f32 / 255.,
+        })
+    }
 }
 
-pub fn red() -> Color {
-    color(0xff0000ff)
+#[derive(Default, Copy, Clone, Debug)]
+#[repr(C)]
+pub struct Hsla {
+    pub h: f32,
+    pub s: f32,
+    pub l: f32,
+    pub a: f32,
 }
 
-pub fn green() -> Color {
-    color(0x00ff00ff)
+impl PartialEq for Hsla {
+    fn eq(&self, other: &Self) -> bool {
+        self.h
+            .total_cmp(&other.h)
+            .then(self.s.total_cmp(&other.s))
+            .then(self.l.total_cmp(&other.l).then(self.a.total_cmp(&other.a)))
+            .is_eq()
+    }
 }
 
-pub fn blue() -> Color {
-    color(0x0000ffff)
+impl PartialOrd for Hsla {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        // SAFETY: The total ordering relies on this always being Some()
+        Some(
+            self.h
+                .total_cmp(&other.h)
+                .then(self.s.total_cmp(&other.s))
+                .then(self.l.total_cmp(&other.l).then(self.a.total_cmp(&other.a))),
+        )
+    }
 }
 
-pub fn yellow() -> Color {
-    color(0xffff00ff)
+impl Ord for Hsla {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        // SAFETY: The partial comparison is a total comparison
+        unsafe { self.partial_cmp(other).unwrap_unchecked() }
+    }
 }
 
-impl Color {
-    pub fn transparent_black() -> Self {
-        transparent_black()
+impl Hsla {
+    pub fn to_rgb(self) -> Rgba {
+        self.into()
+    }
+
+    pub fn red() -> Self {
+        red()
+    }
+
+    pub fn green() -> Self {
+        green()
+    }
+
+    pub fn blue() -> Self {
+        blue()
     }
 
     pub fn black() -> Self {
@@ -70,107 +228,230 @@ impl Color {
         white()
     }
 
-    pub fn red() -> Self {
-        Color::from_u32(0xff0000ff)
+    pub fn transparent_black() -> Self {
+        transparent_black()
+    }
+}
+
+impl Eq for Hsla {}
+
+pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
+    Hsla {
+        h: h.clamp(0., 1.),
+        s: s.clamp(0., 1.),
+        l: l.clamp(0., 1.),
+        a: a.clamp(0., 1.),
     }
+}
 
-    pub fn green() -> Self {
-        Color::from_u32(0x00ff00ff)
+pub fn black() -> Hsla {
+    Hsla {
+        h: 0.,
+        s: 0.,
+        l: 0.,
+        a: 1.,
     }
+}
 
-    pub fn blue() -> Self {
-        Color::from_u32(0x0000ffff)
+pub fn transparent_black() -> Hsla {
+    Hsla {
+        h: 0.,
+        s: 0.,
+        l: 0.,
+        a: 0.,
+    }
+}
+
+pub fn white() -> Hsla {
+    Hsla {
+        h: 0.,
+        s: 0.,
+        l: 1.,
+        a: 1.,
     }
+}
 
-    pub fn yellow() -> Self {
-        Color::from_u32(0xffff00ff)
+pub fn red() -> Hsla {
+    Hsla {
+        h: 0.,
+        s: 1.,
+        l: 0.5,
+        a: 1.,
     }
+}
 
-    pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
-        Self(ColorU::new(r, g, b, a))
+pub fn blue() -> Hsla {
+    Hsla {
+        h: 0.6,
+        s: 1.,
+        l: 0.5,
+        a: 1.,
     }
+}
 
-    pub fn from_u32(rgba: u32) -> Self {
-        Self(ColorU::from_u32(rgba))
+pub fn green() -> Hsla {
+    Hsla {
+        h: 0.33,
+        s: 1.,
+        l: 0.5,
+        a: 1.,
     }
+}
 
-    pub fn blend(source: Color, dest: Color) -> Color {
-        // Skip blending if we don't need it.
-        if source.a == 255 {
-            return source;
-        } else if source.a == 0 {
-            return dest;
-        }
+pub fn yellow() -> Hsla {
+    Hsla {
+        h: 0.16,
+        s: 1.,
+        l: 0.5,
+        a: 1.,
+    }
+}
 
-        let source = source.0.to_f32();
-        let dest = dest.0.to_f32();
+impl Hsla {
+    /// Returns true if the HSLA color is fully transparent, false otherwise.
+    pub fn is_transparent(&self) -> bool {
+        self.a == 0.0
+    }
 
-        let a = source.a() + (dest.a() * (1. - source.a()));
-        let r = ((source.r() * source.a()) + (dest.r() * dest.a() * (1. - source.a()))) / a;
-        let g = ((source.g() * source.a()) + (dest.g() * dest.a() * (1. - source.a()))) / a;
-        let b = ((source.b() * source.a()) + (dest.b() * dest.a() * (1. - source.a()))) / a;
+    /// Blends `other` on top of `self` based on `other`'s alpha value. The resulting color is a combination of `self`'s and `other`'s colors.
+    ///
+    /// If `other`'s alpha value is 1.0 or greater, `other` color is fully opaque, thus `other` is returned as the output color.
+    /// If `other`'s alpha value is 0.0 or less, `other` color is fully transparent, thus `self` is returned as the output color.
+    /// Else, the output color is calculated as a blend of `self` and `other` based on their weighted alpha values.
+    ///
+    /// Assumptions:
+    /// - Alpha values are contained in the range [0, 1], with 1 as fully opaque and 0 as fully transparent.
+    /// - The relative contributions of `self` and `other` is based on `self`'s alpha value (`self.a`) and `other`'s  alpha value (`other.a`), `self` contributing `self.a * (1.0 - other.a)` and `other` contributing it's own alpha value.
+    /// - RGB color components are contained in the range [0, 1].
+    /// - If `self` and `other` colors are out of the valid range, the blend operation's output and behavior is undefined.
+    pub fn blend(self, other: Hsla) -> Hsla {
+        let alpha = other.a;
+
+        if alpha >= 1.0 {
+            other
+        } else if alpha <= 0.0 {
+            return self;
+        } else {
+            let converted_self = Rgba::from(self);
+            let converted_other = Rgba::from(other);
+            let blended_rgb = converted_self.blend(converted_other);
+            return Hsla::from(blended_rgb);
+        }
+    }
 
-        Self(ColorF::new(r, g, b, a).to_u8())
+    /// Fade out the color by a given factor. This factor should be between 0.0 and 1.0.
+    /// Where 0.0 will leave the color unchanged, and 1.0 will completely fade out the color.
+    pub fn fade_out(&mut self, factor: f32) {
+        self.a *= 1.0 - factor.clamp(0., 1.);
     }
+}
 
-    pub fn fade_out(&mut self, fade: f32) {
-        let fade = fade.clamp(0., 1.);
-        self.0.a = (self.0.a as f32 * (1. - fade)) as u8;
+// impl From<Hsla> for Rgba {
+//     fn from(value: Hsla) -> Self {
+//         let h = value.h;
+//         let s = value.s;
+//         let l = value.l;
+
+//         let c = (1 - |2L - 1|) X s
+//     }
+// }
+
+impl From<Rgba> for Hsla {
+    fn from(color: Rgba) -> Self {
+        let r = color.r;
+        let g = color.g;
+        let b = color.b;
+
+        let max = r.max(g.max(b));
+        let min = r.min(g.min(b));
+        let delta = max - min;
+
+        let l = (max + min) / 2.0;
+        let s = if l == 0.0 || l == 1.0 {
+            0.0
+        } else if l < 0.5 {
+            delta / (2.0 * l)
+        } else {
+            delta / (2.0 - 2.0 * l)
+        };
+
+        let h = if delta == 0.0 {
+            0.0
+        } else if max == r {
+            ((g - b) / delta).rem_euclid(6.0) / 6.0
+        } else if max == g {
+            ((b - r) / delta + 2.0) / 6.0
+        } else {
+            ((r - g) / delta + 4.0) / 6.0
+        };
+
+        Hsla {
+            h,
+            s,
+            l,
+            a: color.a,
+        }
     }
 }
 
-impl<'de> Deserialize<'de> for Color {
+impl<'de> Deserialize<'de> for Hsla {
     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
     where
         D: Deserializer<'de>,
     {
-        let literal: Cow<str> = Deserialize::deserialize(deserializer)?;
-        if let Some(digits) = literal.strip_prefix('#') {
-            if let Ok(value) = u32::from_str_radix(digits, 16) {
-                if digits.len() == 6 {
-                    return Ok(Color::from_u32((value << 8) | 0xFF));
-                } else if digits.len() == 8 {
-                    return Ok(Color::from_u32(value));
-                }
-            }
-        }
-        Err(de::Error::invalid_value(
-            Unexpected::Str(literal.as_ref()),
-            &"#RRGGBB[AA]",
-        ))
+        // First, deserialize it into Rgba
+        let rgba = Rgba::deserialize(deserializer)?;
+
+        // Then, use the From<Rgba> for Hsla implementation to convert it
+        Ok(Hsla::from(rgba))
     }
 }
 
-impl From<u32> for Color {
-    fn from(value: u32) -> Self {
-        Self(ColorU::from_u32(value))
+#[cfg(test)]
+mod tests {
+    use serde_json::json;
+
+    use super::*;
+
+    #[test]
+    fn test_deserialize_three_value_hex_to_rgba() {
+        let actual: Rgba = serde_json::from_value(json!("#f09")).unwrap();
+
+        assert_eq!(actual, rgba(0xff0099ff))
     }
-}
 
-impl ToJson for Color {
-    fn to_json(&self) -> serde_json::Value {
-        json!(format!(
-            "0x{:x}{:x}{:x}{:x}",
-            self.0.r, self.0.g, self.0.b, self.0.a
-        ))
+    #[test]
+    fn test_deserialize_four_value_hex_to_rgba() {
+        let actual: Rgba = serde_json::from_value(json!("#f09f")).unwrap();
+
+        assert_eq!(actual, rgba(0xff0099ff))
     }
-}
 
-impl Deref for Color {
-    type Target = ColorU;
-    fn deref(&self) -> &Self::Target {
-        &self.0
+    #[test]
+    fn test_deserialize_six_value_hex_to_rgba() {
+        let actual: Rgba = serde_json::from_value(json!("#ff0099")).unwrap();
+
+        assert_eq!(actual, rgba(0xff0099ff))
     }
-}
 
-impl DerefMut for Color {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.0
+    #[test]
+    fn test_deserialize_eight_value_hex_to_rgba() {
+        let actual: Rgba = serde_json::from_value(json!("#ff0099ff")).unwrap();
+
+        assert_eq!(actual, rgba(0xff0099ff))
     }
-}
 
-impl fmt::Debug for Color {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        self.0.fmt(f)
+    #[test]
+    fn test_deserialize_eight_value_hex_with_padding_to_rgba() {
+        let actual: Rgba = serde_json::from_value(json!(" #f5f5f5ff   ")).unwrap();
+
+        assert_eq!(actual, rgba(0xf5f5f5ff))
+    }
+
+    #[test]
+    fn test_deserialize_eight_value_hex_with_mixed_case_to_rgba() {
+        let actual: Rgba = serde_json::from_value(json!("#DeAdbEeF")).unwrap();
+
+        assert_eq!(actual, rgba(0xdeadbeef))
     }
 }

crates/gpui/src/elements.rs 🔗

@@ -1,740 +0,0 @@
-mod align;
-mod canvas;
-mod clipped;
-mod component;
-mod constrained_box;
-mod container;
-mod empty;
-mod expanded;
-mod flex;
-mod hook;
-mod image;
-mod keystroke_label;
-mod label;
-mod list;
-mod mouse_event_handler;
-mod overlay;
-mod resizable;
-mod stack;
-mod svg;
-mod text;
-mod tooltip;
-mod uniform_list;
-
-pub use self::{
-    align::*, canvas::*, component::*, constrained_box::*, container::*, empty::*, flex::*,
-    hook::*, image::*, keystroke_label::*, label::*, list::*, mouse_event_handler::*, overlay::*,
-    resizable::*, stack::*, svg::*, text::*, tooltip::*, uniform_list::*,
-};
-pub use crate::window::ChildView;
-
-use self::{clipped::Clipped, expanded::Expanded};
-use crate::{
-    geometry::{
-        rect::RectF,
-        vector::{vec2f, Vector2F},
-    },
-    json, Action, Entity, SizeConstraint, TypeTag, View, ViewContext, WeakViewHandle,
-    WindowContext,
-};
-use anyhow::{anyhow, Result};
-use core::panic;
-use json::ToJson;
-use std::{
-    any::{type_name, Any},
-    borrow::Cow,
-    mem,
-    ops::Range,
-};
-
-pub trait Element<V: 'static>: 'static {
-    type LayoutState;
-    type PaintState;
-
-    fn view_name(&self) -> &'static str {
-        type_name::<V>()
-    }
-
-    fn layout(
-        &mut self,
-        constraint: SizeConstraint,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> (Vector2F, Self::LayoutState);
-
-    fn paint(
-        &mut self,
-        bounds: RectF,
-        visible_bounds: RectF,
-        layout: &mut Self::LayoutState,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> Self::PaintState;
-
-    fn rect_for_text_range(
-        &self,
-        range_utf16: Range<usize>,
-        bounds: RectF,
-        visible_bounds: RectF,
-        layout: &Self::LayoutState,
-        paint: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> Option<RectF>;
-
-    fn metadata(&self) -> Option<&dyn Any> {
-        None
-    }
-
-    fn debug(
-        &self,
-        bounds: RectF,
-        layout: &Self::LayoutState,
-        paint: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> serde_json::Value;
-
-    fn into_any(self) -> AnyElement<V>
-    where
-        Self: 'static + Sized,
-    {
-        AnyElement {
-            state: Box::new(ElementState::Init { element: self }),
-            name: None,
-        }
-    }
-
-    fn into_any_named(self, name: impl Into<Cow<'static, str>>) -> AnyElement<V>
-    where
-        Self: 'static + Sized,
-    {
-        AnyElement {
-            state: Box::new(ElementState::Init { element: self }),
-            name: Some(name.into()),
-        }
-    }
-
-    fn into_root_element(self, cx: &ViewContext<V>) -> RootElement<V>
-    where
-        Self: 'static + Sized,
-    {
-        RootElement {
-            element: self.into_any(),
-            view: cx.handle().downgrade(),
-        }
-    }
-
-    fn constrained(self) -> ConstrainedBox<V>
-    where
-        Self: 'static + Sized,
-    {
-        ConstrainedBox::new(self.into_any())
-    }
-
-    fn aligned(self) -> Align<V>
-    where
-        Self: 'static + Sized,
-    {
-        Align::new(self.into_any())
-    }
-
-    fn clipped(self) -> Clipped<V>
-    where
-        Self: 'static + Sized,
-    {
-        Clipped::new(self.into_any())
-    }
-
-    fn contained(self) -> Container<V>
-    where
-        Self: 'static + Sized,
-    {
-        Container::new(self.into_any())
-    }
-
-    fn expanded(self) -> Expanded<V>
-    where
-        Self: 'static + Sized,
-    {
-        Expanded::new(self.into_any())
-    }
-
-    fn flex(self, flex: f32, expanded: bool) -> FlexItem<V>
-    where
-        Self: 'static + Sized,
-    {
-        FlexItem::new(self.into_any()).flex(flex, expanded)
-    }
-
-    fn flex_float(self) -> FlexItem<V>
-    where
-        Self: 'static + Sized,
-    {
-        FlexItem::new(self.into_any()).float()
-    }
-
-    fn with_dynamic_tooltip(
-        self,
-        tag: TypeTag,
-        id: usize,
-        text: impl Into<Cow<'static, str>>,
-        action: Option<Box<dyn Action>>,
-        style: TooltipStyle,
-        cx: &mut ViewContext<V>,
-    ) -> Tooltip<V>
-    where
-        Self: 'static + Sized,
-    {
-        Tooltip::new_dynamic(tag, id, text, action, style, self.into_any(), cx)
-    }
-    fn with_tooltip<Tag: 'static>(
-        self,
-        id: usize,
-        text: impl Into<Cow<'static, str>>,
-        action: Option<Box<dyn Action>>,
-        style: TooltipStyle,
-        cx: &mut ViewContext<V>,
-    ) -> Tooltip<V>
-    where
-        Self: 'static + Sized,
-    {
-        Tooltip::new::<Tag>(id, text, action, style, self.into_any(), cx)
-    }
-
-    /// Uses the the given element to calculate resizes for the given tag
-    fn provide_resize_bounds<Tag: 'static>(self) -> BoundsProvider<V, Tag>
-    where
-        Self: 'static + Sized,
-    {
-        BoundsProvider::<_, Tag>::new(self.into_any())
-    }
-
-    /// Calls the given closure with the new size of the element whenever the
-    /// handle is dragged. This will be calculated in relation to the bounds
-    /// provided by the given tag
-    fn resizable<Tag: 'static>(
-        self,
-        side: HandleSide,
-        size: f32,
-        on_resize: impl 'static + FnMut(&mut V, Option<f32>, &mut ViewContext<V>),
-    ) -> Resizable<V>
-    where
-        Self: 'static + Sized,
-    {
-        Resizable::new::<Tag>(self.into_any(), side, size, on_resize)
-    }
-
-    fn mouse<Tag: 'static>(self, region_id: usize) -> MouseEventHandler<V>
-    where
-        Self: Sized,
-    {
-        MouseEventHandler::for_child::<Tag>(self.into_any(), region_id)
-    }
-
-    fn component(self) -> StatelessElementAdapter
-    where
-        Self: Sized,
-    {
-        StatelessElementAdapter::new(self.into_any())
-    }
-
-    fn stateful_component(self) -> StatefulElementAdapter<V>
-    where
-        Self: Sized,
-    {
-        StatefulElementAdapter::new(self.into_any())
-    }
-
-    fn styleable_component(self) -> StylableAdapter<StatelessElementAdapter>
-    where
-        Self: Sized,
-    {
-        StatelessElementAdapter::new(self.into_any()).stylable()
-    }
-}
-
-trait AnyElementState<V> {
-    fn layout(
-        &mut self,
-        constraint: SizeConstraint,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> Vector2F;
-
-    fn paint(
-        &mut self,
-        origin: Vector2F,
-        visible_bounds: RectF,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    );
-
-    fn rect_for_text_range(
-        &self,
-        range_utf16: Range<usize>,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> Option<RectF>;
-
-    fn debug(&self, view: &V, cx: &ViewContext<V>) -> serde_json::Value;
-
-    fn size(&self) -> Vector2F;
-
-    fn metadata(&self) -> Option<&dyn Any>;
-}
-
-enum ElementState<V: 'static, E: Element<V>> {
-    Empty,
-    Init {
-        element: E,
-    },
-    PostLayout {
-        element: E,
-        constraint: SizeConstraint,
-        size: Vector2F,
-        layout: E::LayoutState,
-    },
-    PostPaint {
-        element: E,
-        constraint: SizeConstraint,
-        bounds: RectF,
-        visible_bounds: RectF,
-        layout: E::LayoutState,
-        paint: E::PaintState,
-    },
-}
-
-impl<V, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
-    fn layout(
-        &mut self,
-        constraint: SizeConstraint,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> Vector2F {
-        let result;
-        *self = match mem::take(self) {
-            ElementState::Empty => unreachable!(),
-            ElementState::Init { mut element }
-            | ElementState::PostLayout { mut element, .. }
-            | ElementState::PostPaint { mut element, .. } => {
-                let (size, layout) = element.layout(constraint, view, cx);
-                debug_assert!(
-                    size.x().is_finite(),
-                    "Element for {:?} had infinite x size after layout",
-                    element.view_name()
-                );
-                debug_assert!(
-                    size.y().is_finite(),
-                    "Element for {:?} had infinite y size after layout",
-                    element.view_name()
-                );
-
-                result = size;
-                ElementState::PostLayout {
-                    element,
-                    constraint,
-                    size,
-                    layout,
-                }
-            }
-        };
-        result
-    }
-
-    fn paint(
-        &mut self,
-        origin: Vector2F,
-        visible_bounds: RectF,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) {
-        *self = match mem::take(self) {
-            ElementState::PostLayout {
-                mut element,
-                constraint,
-                size,
-                mut layout,
-            } => {
-                let bounds = RectF::new(origin, size);
-                let paint = element.paint(bounds, visible_bounds, &mut layout, view, cx);
-                ElementState::PostPaint {
-                    element,
-                    constraint,
-                    bounds,
-                    visible_bounds,
-                    layout,
-                    paint,
-                }
-            }
-            ElementState::PostPaint {
-                mut element,
-                constraint,
-                bounds,
-                mut layout,
-                ..
-            } => {
-                let bounds = RectF::new(origin, bounds.size());
-                let paint = element.paint(bounds, visible_bounds, &mut layout, view, cx);
-                ElementState::PostPaint {
-                    element,
-                    constraint,
-                    bounds,
-                    visible_bounds,
-                    layout,
-                    paint,
-                }
-            }
-            ElementState::Empty => panic!("invalid element lifecycle state"),
-            ElementState::Init { .. } => {
-                panic!("invalid element lifecycle state, paint called before layout")
-            }
-        }
-    }
-
-    fn rect_for_text_range(
-        &self,
-        range_utf16: Range<usize>,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> Option<RectF> {
-        if let ElementState::PostPaint {
-            element,
-            bounds,
-            visible_bounds,
-            layout,
-            paint,
-            ..
-        } = self
-        {
-            element.rect_for_text_range(
-                range_utf16,
-                *bounds,
-                *visible_bounds,
-                layout,
-                paint,
-                view,
-                cx,
-            )
-        } else {
-            None
-        }
-    }
-
-    fn size(&self) -> Vector2F {
-        match self {
-            ElementState::Empty | ElementState::Init { .. } => {
-                panic!("invalid element lifecycle state")
-            }
-            ElementState::PostLayout { size, .. } => *size,
-            ElementState::PostPaint { bounds, .. } => bounds.size(),
-        }
-    }
-
-    fn metadata(&self) -> Option<&dyn Any> {
-        match self {
-            ElementState::Empty => unreachable!(),
-            ElementState::Init { element }
-            | ElementState::PostLayout { element, .. }
-            | ElementState::PostPaint { element, .. } => element.metadata(),
-        }
-    }
-
-    fn debug(&self, view: &V, cx: &ViewContext<V>) -> serde_json::Value {
-        match self {
-            ElementState::PostPaint {
-                element,
-                constraint,
-                bounds,
-                visible_bounds,
-                layout,
-                paint,
-            } => {
-                let mut value = element.debug(*bounds, layout, paint, view, cx);
-                if let json::Value::Object(map) = &mut value {
-                    let mut new_map: crate::json::Map<String, serde_json::Value> =
-                        Default::default();
-                    if let Some(typ) = map.remove("type") {
-                        new_map.insert("type".into(), typ);
-                    }
-                    new_map.insert("constraint".into(), constraint.to_json());
-                    new_map.insert("bounds".into(), bounds.to_json());
-                    new_map.insert("visible_bounds".into(), visible_bounds.to_json());
-                    new_map.append(map);
-                    json::Value::Object(new_map)
-                } else {
-                    value
-                }
-            }
-
-            _ => panic!("invalid element lifecycle state"),
-        }
-    }
-}
-
-impl<V, E: Element<V>> Default for ElementState<V, E> {
-    fn default() -> Self {
-        Self::Empty
-    }
-}
-
-pub struct AnyElement<V> {
-    state: Box<dyn AnyElementState<V>>,
-    name: Option<Cow<'static, str>>,
-}
-
-impl<V> AnyElement<V> {
-    pub fn name(&self) -> Option<&str> {
-        self.name.as_deref()
-    }
-
-    pub fn metadata<T: 'static>(&self) -> Option<&T> {
-        self.state
-            .metadata()
-            .and_then(|data| data.downcast_ref::<T>())
-    }
-
-    pub fn layout(
-        &mut self,
-        constraint: SizeConstraint,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> Vector2F {
-        self.state.layout(constraint, view, cx)
-    }
-
-    pub fn paint(
-        &mut self,
-        origin: Vector2F,
-        visible_bounds: RectF,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) {
-        self.state.paint(origin, visible_bounds, view, cx);
-    }
-
-    pub fn rect_for_text_range(
-        &self,
-        range_utf16: Range<usize>,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> Option<RectF> {
-        self.state.rect_for_text_range(range_utf16, view, cx)
-    }
-
-    pub fn size(&self) -> Vector2F {
-        self.state.size()
-    }
-
-    pub fn debug(&self, view: &V, cx: &ViewContext<V>) -> json::Value {
-        let mut value = self.state.debug(view, cx);
-
-        if let Some(name) = &self.name {
-            if let json::Value::Object(map) = &mut value {
-                let mut new_map: crate::json::Map<String, serde_json::Value> = Default::default();
-                new_map.insert("name".into(), json::Value::String(name.to_string()));
-                new_map.append(map);
-                return json::Value::Object(new_map);
-            }
-        }
-
-        value
-    }
-
-    pub fn with_metadata<T, F, R>(&self, f: F) -> R
-    where
-        T: 'static,
-        F: FnOnce(Option<&T>) -> R,
-    {
-        f(self.state.metadata().and_then(|m| m.downcast_ref()))
-    }
-}
-
-impl<V: 'static> Element<V> for AnyElement<V> {
-    type LayoutState = ();
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        constraint: SizeConstraint,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> (Vector2F, Self::LayoutState) {
-        let size = self.layout(constraint, view, cx);
-        (size, ())
-    }
-
-    fn paint(
-        &mut self,
-        bounds: RectF,
-        visible_bounds: RectF,
-        _: &mut Self::LayoutState,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> Self::PaintState {
-        self.paint(bounds.origin(), visible_bounds, view, cx);
-    }
-
-    fn rect_for_text_range(
-        &self,
-        range_utf16: Range<usize>,
-        _: RectF,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> Option<RectF> {
-        self.rect_for_text_range(range_utf16, view, cx)
-    }
-
-    fn debug(
-        &self,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> serde_json::Value {
-        self.debug(view, cx)
-    }
-
-    fn into_any(self) -> AnyElement<V>
-    where
-        Self: Sized,
-    {
-        self
-    }
-}
-
-impl Entity for AnyElement<()> {
-    type Event = ();
-}
-
-// impl View for AnyElement<()> {}
-
-pub struct RootElement<V> {
-    element: AnyElement<V>,
-    view: WeakViewHandle<V>,
-}
-
-impl<V> RootElement<V> {
-    pub fn new(element: AnyElement<V>, view: WeakViewHandle<V>) -> Self {
-        Self { element, view }
-    }
-}
-
-pub trait AnyRootElement {
-    fn layout(&mut self, constraint: SizeConstraint, cx: &mut WindowContext) -> Result<Vector2F>;
-    fn paint(
-        &mut self,
-        origin: Vector2F,
-        visible_bounds: RectF,
-        cx: &mut WindowContext,
-    ) -> Result<()>;
-    fn rect_for_text_range(
-        &self,
-        range_utf16: Range<usize>,
-        cx: &WindowContext,
-    ) -> Result<Option<RectF>>;
-    fn debug(&self, cx: &WindowContext) -> Result<serde_json::Value>;
-    fn name(&self) -> Option<&str>;
-}
-
-impl<V: View> AnyRootElement for RootElement<V> {
-    fn layout(&mut self, constraint: SizeConstraint, cx: &mut WindowContext) -> Result<Vector2F> {
-        let view = self
-            .view
-            .upgrade(cx)
-            .ok_or_else(|| anyhow!("layout called on a root element for a dropped view"))?;
-        view.update(cx, |view, cx| Ok(self.element.layout(constraint, view, cx)))
-    }
-
-    fn paint(
-        &mut self,
-        origin: Vector2F,
-        visible_bounds: RectF,
-        cx: &mut WindowContext,
-    ) -> Result<()> {
-        let view = self
-            .view
-            .upgrade(cx)
-            .ok_or_else(|| anyhow!("paint called on a root element for a dropped view"))?;
-
-        view.update(cx, |view, cx| {
-            self.element.paint(origin, visible_bounds, view, cx);
-            Ok(())
-        })
-    }
-
-    fn rect_for_text_range(
-        &self,
-        range_utf16: Range<usize>,
-        cx: &WindowContext,
-    ) -> Result<Option<RectF>> {
-        let view = self.view.upgrade(cx).ok_or_else(|| {
-            anyhow!("rect_for_text_range called on a root element for a dropped view")
-        })?;
-        let view = view.read(cx);
-        let view_context = ViewContext::immutable(cx, self.view.id());
-        Ok(self
-            .element
-            .rect_for_text_range(range_utf16, view, &view_context))
-    }
-
-    fn debug(&self, cx: &WindowContext) -> Result<serde_json::Value> {
-        let view = self
-            .view
-            .upgrade(cx)
-            .ok_or_else(|| anyhow!("debug called on a root element for a dropped view"))?;
-        let view = view.read(cx);
-        let view_context = ViewContext::immutable(cx, self.view.id());
-        Ok(serde_json::json!({
-            "view_id": self.view.id(),
-            "view_name": V::ui_name(),
-            "view": view.debug_json(cx),
-            "element": self.element.debug(view, &view_context)
-        }))
-    }
-
-    fn name(&self) -> Option<&str> {
-        self.element.name()
-    }
-}
-
-pub trait ParentElement<'a, V: 'static>: Extend<AnyElement<V>> + Sized {
-    fn add_children<E: Element<V>>(&mut self, children: impl IntoIterator<Item = E>) {
-        self.extend(children.into_iter().map(|child| child.into_any()));
-    }
-
-    fn add_child<D: Element<V>>(&mut self, child: D) {
-        self.extend(Some(child.into_any()));
-    }
-
-    fn with_children<D: Element<V>>(mut self, children: impl IntoIterator<Item = D>) -> Self {
-        self.extend(children.into_iter().map(|child| child.into_any()));
-        self
-    }
-
-    fn with_child<D: Element<V>>(mut self, child: D) -> Self {
-        self.extend(Some(child.into_any()));
-        self
-    }
-}
-
-impl<'a, V, T> ParentElement<'a, V> for T
-where
-    V: 'static,
-    T: Extend<AnyElement<V>>,
-{
-}
-
-pub fn constrain_size_preserving_aspect_ratio(max_size: Vector2F, size: Vector2F) -> Vector2F {
-    if max_size.x().is_infinite() && max_size.y().is_infinite() {
-        size
-    } else if max_size.x().is_infinite() || max_size.x() / max_size.y() > size.x() / size.y() {
-        vec2f(size.x() * max_size.y() / size.y(), max_size.y())
-    } else {
-        vec2f(max_size.x(), size.y() * max_size.x() / size.x())
-    }
-}

crates/gpui/src/elements/align.rs 🔗

@@ -1,115 +0,0 @@
-use crate::{
-    geometry::{rect::RectF, vector::Vector2F},
-    json, AnyElement, Element, SizeConstraint, ViewContext,
-};
-use json::ToJson;
-
-use serde_json::json;
-
-pub struct Align<V> {
-    child: AnyElement<V>,
-    alignment: Vector2F,
-}
-
-impl<V> Align<V> {
-    pub fn new(child: AnyElement<V>) -> Self {
-        Self {
-            child,
-            alignment: Vector2F::zero(),
-        }
-    }
-
-    pub fn top(mut self) -> Self {
-        self.alignment.set_y(-1.0);
-        self
-    }
-
-    pub fn bottom(mut self) -> Self {
-        self.alignment.set_y(1.0);
-        self
-    }
-
-    pub fn left(mut self) -> Self {
-        self.alignment.set_x(-1.0);
-        self
-    }
-
-    pub fn right(mut self) -> Self {
-        self.alignment.set_x(1.0);
-        self
-    }
-}
-
-impl<V: 'static> Element<V> for Align<V> {
-    type LayoutState = ();
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        mut constraint: SizeConstraint,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> (Vector2F, Self::LayoutState) {
-        let mut size = constraint.max;
-        constraint.min = Vector2F::zero();
-        let child_size = self.child.layout(constraint, view, cx);
-        if size.x().is_infinite() {
-            size.set_x(child_size.x());
-        }
-        if size.y().is_infinite() {
-            size.set_y(child_size.y());
-        }
-        (size, ())
-    }
-
-    fn paint(
-        &mut self,
-        bounds: RectF,
-        visible_bounds: RectF,
-        _: &mut Self::LayoutState,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> Self::PaintState {
-        let my_center = bounds.size() / 2.;
-        let my_target = my_center + my_center * self.alignment;
-
-        let child_center = self.child.size() / 2.;
-        let child_target = child_center + child_center * self.alignment;
-
-        self.child.paint(
-            bounds.origin() - (child_target - my_target),
-            visible_bounds,
-            view,
-            cx,
-        );
-    }
-
-    fn rect_for_text_range(
-        &self,
-        range_utf16: std::ops::Range<usize>,
-        _: RectF,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> Option<RectF> {
-        self.child.rect_for_text_range(range_utf16, view, cx)
-    }
-
-    fn debug(
-        &self,
-        bounds: pathfinder_geometry::rect::RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> json::Value {
-        json!({
-            "type": "Align",
-            "bounds": bounds.to_json(),
-            "alignment": self.alignment.to_json(),
-            "child": self.child.debug(view, cx),
-        })
-    }
-}

crates/gpui/src/elements/canvas.rs 🔗

@@ -1,85 +1,54 @@
-use std::marker::PhantomData;
+use refineable::Refineable as _;
 
-use super::Element;
-use crate::{
-    json::{self, json},
-    ViewContext,
-};
-use json::ToJson;
-use pathfinder_geometry::{
-    rect::RectF,
-    vector::{vec2f, Vector2F},
-};
+use crate::{Bounds, Element, IntoElement, Pixels, Style, StyleRefinement, Styled, WindowContext};
 
-pub struct Canvas<V, F>(F, PhantomData<V>);
-
-impl<V, F> Canvas<V, F>
-where
-    F: FnMut(RectF, RectF, &mut V, &mut ViewContext<V>),
-{
-    pub fn new(f: F) -> Self {
-        Self(f, PhantomData)
+pub fn canvas(callback: impl 'static + FnOnce(&Bounds<Pixels>, &mut WindowContext)) -> Canvas {
+    Canvas {
+        paint_callback: Some(Box::new(callback)),
+        style: StyleRefinement::default(),
     }
 }
 
-impl<V: 'static, F> Element<V> for Canvas<V, F>
-where
-    F: 'static + FnMut(RectF, RectF, &mut V, &mut ViewContext<V>),
-{
-    type LayoutState = ();
-    type PaintState = ();
+pub struct Canvas {
+    paint_callback: Option<Box<dyn FnOnce(&Bounds<Pixels>, &mut WindowContext)>>,
+    style: StyleRefinement,
+}
 
-    fn layout(
-        &mut self,
-        constraint: crate::SizeConstraint,
-        _: &mut V,
-        _: &mut crate::ViewContext<V>,
-    ) -> (Vector2F, Self::LayoutState) {
-        let x = if constraint.max.x().is_finite() {
-            constraint.max.x()
-        } else {
-            constraint.min.x()
-        };
-        let y = if constraint.max.y().is_finite() {
-            constraint.max.y()
-        } else {
-            constraint.min.y()
-        };
-        (vec2f(x, y), ())
+impl IntoElement for Canvas {
+    type Element = Self;
+
+    fn element_id(&self) -> Option<crate::ElementId> {
+        None
     }
 
-    fn paint(
+    fn into_element(self) -> Self::Element {
+        self
+    }
+}
+
+impl Element for Canvas {
+    type State = Style;
+
+    fn request_layout(
         &mut self,
-        bounds: RectF,
-        visible_bounds: RectF,
-        _: &mut Self::LayoutState,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> Self::PaintState {
-        self.0(bounds, visible_bounds, view, cx)
+        _: Option<Self::State>,
+        cx: &mut WindowContext,
+    ) -> (crate::LayoutId, Self::State) {
+        let mut style = Style::default();
+        style.refine(&self.style);
+        let layout_id = cx.request_layout(&style, []);
+        (layout_id, style)
     }
 
-    fn rect_for_text_range(
-        &self,
-        _: std::ops::Range<usize>,
-        _: RectF,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        _: &V,
-        _: &ViewContext<V>,
-    ) -> Option<RectF> {
-        None
+    fn paint(&mut self, bounds: Bounds<Pixels>, style: &mut Style, cx: &mut WindowContext) {
+        style.paint(bounds, cx, |cx| {
+            (self.paint_callback.take().unwrap())(&bounds, cx)
+        });
     }
+}
 
-    fn debug(
-        &self,
-        bounds: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        _: &V,
-        _: &ViewContext<V>,
-    ) -> json::Value {
-        json!({"type": "Canvas", "bounds": bounds.to_json()})
+impl Styled for Canvas {
+    fn style(&mut self) -> &mut crate::StyleRefinement {
+        &mut self.style
     }
 }

crates/gpui/src/elements/clipped.rs 🔗

@@ -1,71 +0,0 @@
-use std::ops::Range;
-
-use pathfinder_geometry::{rect::RectF, vector::Vector2F};
-use serde_json::json;
-
-use crate::{json, AnyElement, Element, SizeConstraint, ViewContext};
-
-pub struct Clipped<V> {
-    child: AnyElement<V>,
-}
-
-impl<V> Clipped<V> {
-    pub fn new(child: AnyElement<V>) -> Self {
-        Self { child }
-    }
-}
-
-impl<V: 'static> Element<V> for Clipped<V> {
-    type LayoutState = ();
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        constraint: SizeConstraint,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> (Vector2F, Self::LayoutState) {
-        (self.child.layout(constraint, view, cx), ())
-    }
-
-    fn paint(
-        &mut self,
-        bounds: RectF,
-        visible_bounds: RectF,
-        _: &mut Self::LayoutState,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> Self::PaintState {
-        cx.scene().push_layer(Some(bounds));
-        let state = self.child.paint(bounds.origin(), visible_bounds, view, cx);
-        cx.scene().pop_layer();
-        state
-    }
-
-    fn rect_for_text_range(
-        &self,
-        range_utf16: Range<usize>,
-        _: RectF,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> Option<RectF> {
-        self.child.rect_for_text_range(range_utf16, view, cx)
-    }
-
-    fn debug(
-        &self,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> json::Value {
-        json!({
-            "type": "Clipped",
-            "child": self.child.debug(view, cx)
-        })
-    }
-}

crates/gpui/src/elements/component.rs 🔗

@@ -1,342 +0,0 @@
-use std::{any::Any, marker::PhantomData};
-
-use pathfinder_geometry::{rect::RectF, vector::Vector2F};
-
-use crate::{AnyElement, Element, SizeConstraint, ViewContext};
-
-use super::Empty;
-
-/// The core stateless component trait, simply rendering an element tree
-pub trait Component {
-    fn render<V: 'static>(self, cx: &mut ViewContext<V>) -> AnyElement<V>;
-
-    fn element<V: 'static>(self) -> ComponentAdapter<V, Self>
-    where
-        Self: Sized,
-    {
-        ComponentAdapter::new(self)
-    }
-
-    fn stylable(self) -> StylableAdapter<Self>
-    where
-        Self: Sized,
-    {
-        StylableAdapter::new(self)
-    }
-
-    fn stateful<V: 'static>(self) -> StatefulAdapter<Self, V>
-    where
-        Self: Sized,
-    {
-        StatefulAdapter::new(self)
-    }
-}
-
-/// Allows a a component's styles to be rebound in a simple way.
-pub trait Stylable: Component {
-    type Style: Clone;
-
-    fn with_style(self, style: Self::Style) -> Self;
-}
-
-/// This trait models the typestate pattern for a component's style,
-/// enforcing at compile time that a component is only usable after
-/// it has been styled while still allowing for late binding of the
-/// styling information
-pub trait SafeStylable {
-    type Style: Clone;
-    type Output: Component;
-
-    fn with_style(self, style: Self::Style) -> Self::Output;
-}
-
-/// All stylable components can trivially implement SafeStylable
-impl<C: Stylable> SafeStylable for C {
-    type Style = C::Style;
-
-    type Output = C;
-
-    fn with_style(self, style: Self::Style) -> Self::Output {
-        self.with_style(style)
-    }
-}
-
-/// Allows converting an unstylable component into a stylable one
-/// by using `()` as the style type
-pub struct StylableAdapter<C: Component> {
-    component: C,
-}
-
-impl<C: Component> StylableAdapter<C> {
-    pub fn new(component: C) -> Self {
-        Self { component }
-    }
-}
-
-impl<C: Component> SafeStylable for StylableAdapter<C> {
-    type Style = ();
-
-    type Output = C;
-
-    fn with_style(self, _: Self::Style) -> Self::Output {
-        self.component
-    }
-}
-
-/// This is a secondary trait for components that can be styled
-/// which rely on their view's state. This is useful for components that, for example,
-/// want to take click handler callbacks Unfortunately, the generic bound on the
-/// Component trait makes it incompatible with the stateless components above.
-// So let's just replicate them for now
-pub trait StatefulComponent<V: 'static> {
-    fn render(self, v: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V>;
-
-    fn element(self) -> ComponentAdapter<V, Self>
-    where
-        Self: Sized,
-    {
-        ComponentAdapter::new(self)
-    }
-
-    fn styleable(self) -> StatefulStylableAdapter<Self, V>
-    where
-        Self: Sized,
-    {
-        StatefulStylableAdapter::new(self)
-    }
-
-    fn stateless(self) -> StatelessElementAdapter
-    where
-        Self: Sized + 'static,
-    {
-        StatelessElementAdapter::new(self.element().into_any())
-    }
-}
-
-/// It is trivial to convert stateless components to stateful components, so lets
-/// do so en masse. Note that the reverse is impossible without a helper.
-impl<V: 'static, C: Component> StatefulComponent<V> for C {
-    fn render(self, _: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V> {
-        self.render(cx)
-    }
-}
-
-/// Same as stylable, but generic over a view type
-pub trait StatefulStylable<V: 'static>: StatefulComponent<V> {
-    type Style: Clone;
-
-    fn with_style(self, style: Self::Style) -> Self;
-}
-
-/// Same as SafeStylable, but generic over a view type
-pub trait StatefulSafeStylable<V: 'static> {
-    type Style: Clone;
-    type Output: StatefulComponent<V>;
-
-    fn with_style(self, style: Self::Style) -> Self::Output;
-}
-
-/// Converting from stateless to stateful
-impl<V: 'static, C: SafeStylable> StatefulSafeStylable<V> for C {
-    type Style = C::Style;
-
-    type Output = C::Output;
-
-    fn with_style(self, style: Self::Style) -> Self::Output {
-        self.with_style(style)
-    }
-}
-
-// A helper for converting stateless components into stateful ones
-pub struct StatefulAdapter<C, V> {
-    component: C,
-    phantom: std::marker::PhantomData<V>,
-}
-
-impl<C: Component, V: 'static> StatefulAdapter<C, V> {
-    pub fn new(component: C) -> Self {
-        Self {
-            component,
-            phantom: std::marker::PhantomData,
-        }
-    }
-}
-
-impl<C: Component, V: 'static> StatefulComponent<V> for StatefulAdapter<C, V> {
-    fn render(self, _: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V> {
-        self.component.render(cx)
-    }
-}
-
-// A helper for converting stateful but style-less components into stylable ones
-// by using `()` as the style type
-pub struct StatefulStylableAdapter<C: StatefulComponent<V>, V: 'static> {
-    component: C,
-    phantom: std::marker::PhantomData<V>,
-}
-
-impl<C: StatefulComponent<V>, V: 'static> StatefulStylableAdapter<C, V> {
-    pub fn new(component: C) -> Self {
-        Self {
-            component,
-            phantom: std::marker::PhantomData,
-        }
-    }
-}
-
-impl<C: StatefulComponent<V>, V: 'static> StatefulSafeStylable<V>
-    for StatefulStylableAdapter<C, V>
-{
-    type Style = ();
-
-    type Output = C;
-
-    fn with_style(self, _: Self::Style) -> Self::Output {
-        self.component
-    }
-}
-
-/// A way of erasing the view generic from an element, useful
-/// for wrapping up an explicit element tree into stateless
-/// components
-pub struct StatelessElementAdapter {
-    element: Box<dyn Any>,
-}
-
-impl StatelessElementAdapter {
-    pub fn new<V: 'static>(element: AnyElement<V>) -> Self {
-        StatelessElementAdapter {
-            element: Box::new(element) as Box<dyn Any>,
-        }
-    }
-}
-
-impl Component for StatelessElementAdapter {
-    fn render<V: 'static>(self, _: &mut ViewContext<V>) -> AnyElement<V> {
-        *self
-            .element
-            .downcast::<AnyElement<V>>()
-            .expect("Don't move elements out of their view :(")
-    }
-}
-
-// For converting elements into stateful components
-pub struct StatefulElementAdapter<V: 'static> {
-    element: AnyElement<V>,
-    _phantom: std::marker::PhantomData<V>,
-}
-
-impl<V: 'static> StatefulElementAdapter<V> {
-    pub fn new(element: AnyElement<V>) -> Self {
-        Self {
-            element,
-            _phantom: std::marker::PhantomData,
-        }
-    }
-}
-
-impl<V: 'static> StatefulComponent<V> for StatefulElementAdapter<V> {
-    fn render(self, _: &mut V, _: &mut ViewContext<V>) -> AnyElement<V> {
-        self.element
-    }
-}
-
-/// A convenient shorthand for creating an empty component.
-impl Component for () {
-    fn render<V: 'static>(self, _: &mut ViewContext<V>) -> AnyElement<V> {
-        Empty::new().into_any()
-    }
-}
-
-impl Stylable for () {
-    type Style = ();
-
-    fn with_style(self, _: Self::Style) -> Self {
-        ()
-    }
-}
-
-// For converting components back into Elements
-pub struct ComponentAdapter<V: 'static, E> {
-    component: Option<E>,
-    element: Option<AnyElement<V>>,
-    phantom: PhantomData<V>,
-}
-
-impl<E, V: 'static> ComponentAdapter<V, E> {
-    pub fn new(e: E) -> Self {
-        Self {
-            component: Some(e),
-            element: None,
-            phantom: PhantomData,
-        }
-    }
-}
-
-impl<V: 'static, C: StatefulComponent<V> + 'static> Element<V> for ComponentAdapter<V, C> {
-    type LayoutState = ();
-
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        constraint: SizeConstraint,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> (Vector2F, Self::LayoutState) {
-        if self.element.is_none() {
-            let element = self
-                .component
-                .take()
-                .expect("Component can only be rendered once")
-                .render(view, cx);
-            self.element = Some(element);
-        }
-        let constraint = self.element.as_mut().unwrap().layout(constraint, view, cx);
-        (constraint, ())
-    }
-
-    fn paint(
-        &mut self,
-        bounds: RectF,
-        visible_bounds: RectF,
-        _: &mut Self::LayoutState,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> Self::PaintState {
-        self.element
-            .as_mut()
-            .expect("Layout should always be called before paint")
-            .paint(bounds.origin(), visible_bounds, view, cx)
-    }
-
-    fn rect_for_text_range(
-        &self,
-        range_utf16: std::ops::Range<usize>,
-        _: RectF,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> Option<RectF> {
-        self.element
-            .as_ref()
-            .and_then(|el| el.rect_for_text_range(range_utf16, view, cx))
-    }
-
-    fn debug(
-        &self,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> serde_json::Value {
-        serde_json::json!({
-            "type": "ComponentAdapter",
-            "component": std::any::type_name::<C>(),
-            "child": self.element.as_ref().map(|el| el.debug(view, cx)),
-        })
-    }
-}

crates/gpui/src/elements/constrained_box.rs 🔗

@@ -1,187 +0,0 @@
-use std::ops::Range;
-
-use json::ToJson;
-use serde_json::json;
-
-use crate::{
-    geometry::{rect::RectF, vector::Vector2F},
-    json, AnyElement, Element, SizeConstraint, ViewContext,
-};
-
-pub struct ConstrainedBox<V> {
-    child: AnyElement<V>,
-    constraint: Constraint<V>,
-}
-
-pub enum Constraint<V> {
-    Static(SizeConstraint),
-    Dynamic(Box<dyn FnMut(SizeConstraint, &mut V, &mut ViewContext<V>) -> SizeConstraint>),
-}
-
-impl<V> ToJson for Constraint<V> {
-    fn to_json(&self) -> serde_json::Value {
-        match self {
-            Constraint::Static(constraint) => constraint.to_json(),
-            Constraint::Dynamic(_) => "dynamic".into(),
-        }
-    }
-}
-
-impl<V: 'static> ConstrainedBox<V> {
-    pub fn new(child: impl Element<V>) -> Self {
-        Self {
-            child: child.into_any(),
-            constraint: Constraint::Static(Default::default()),
-        }
-    }
-
-    pub fn dynamically(
-        mut self,
-        constraint: impl 'static + FnMut(SizeConstraint, &mut V, &mut ViewContext<V>) -> SizeConstraint,
-    ) -> Self {
-        self.constraint = Constraint::Dynamic(Box::new(constraint));
-        self
-    }
-
-    pub fn with_min_width(mut self, min_width: f32) -> Self {
-        if let Constraint::Dynamic(_) = self.constraint {
-            self.constraint = Constraint::Static(Default::default());
-        }
-
-        if let Constraint::Static(constraint) = &mut self.constraint {
-            constraint.min.set_x(min_width);
-        } else {
-            unreachable!()
-        }
-
-        self
-    }
-
-    pub fn with_max_width(mut self, max_width: f32) -> Self {
-        if let Constraint::Dynamic(_) = self.constraint {
-            self.constraint = Constraint::Static(Default::default());
-        }
-
-        if let Constraint::Static(constraint) = &mut self.constraint {
-            constraint.max.set_x(max_width);
-        } else {
-            unreachable!()
-        }
-
-        self
-    }
-
-    pub fn with_max_height(mut self, max_height: f32) -> Self {
-        if let Constraint::Dynamic(_) = self.constraint {
-            self.constraint = Constraint::Static(Default::default());
-        }
-
-        if let Constraint::Static(constraint) = &mut self.constraint {
-            constraint.max.set_y(max_height);
-        } else {
-            unreachable!()
-        }
-
-        self
-    }
-
-    pub fn with_width(mut self, width: f32) -> Self {
-        if let Constraint::Dynamic(_) = self.constraint {
-            self.constraint = Constraint::Static(Default::default());
-        }
-
-        if let Constraint::Static(constraint) = &mut self.constraint {
-            constraint.min.set_x(width);
-            constraint.max.set_x(width);
-        } else {
-            unreachable!()
-        }
-
-        self
-    }
-
-    pub fn with_height(mut self, height: f32) -> Self {
-        if let Constraint::Dynamic(_) = self.constraint {
-            self.constraint = Constraint::Static(Default::default());
-        }
-
-        if let Constraint::Static(constraint) = &mut self.constraint {
-            constraint.min.set_y(height);
-            constraint.max.set_y(height);
-        } else {
-            unreachable!()
-        }
-
-        self
-    }
-
-    fn constraint(
-        &mut self,
-        input_constraint: SizeConstraint,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> SizeConstraint {
-        match &mut self.constraint {
-            Constraint::Static(constraint) => *constraint,
-            Constraint::Dynamic(compute_constraint) => {
-                compute_constraint(input_constraint, view, cx)
-            }
-        }
-    }
-}
-
-impl<V: 'static> Element<V> for ConstrainedBox<V> {
-    type LayoutState = ();
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        mut parent_constraint: SizeConstraint,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> (Vector2F, Self::LayoutState) {
-        let constraint = self.constraint(parent_constraint, view, cx);
-        parent_constraint.min = parent_constraint.min.max(constraint.min);
-        parent_constraint.max = parent_constraint.max.min(constraint.max);
-        parent_constraint.max = parent_constraint.max.max(parent_constraint.min);
-        let size = self.child.layout(parent_constraint, view, cx);
-        (size, ())
-    }
-
-    fn paint(
-        &mut self,
-        bounds: RectF,
-        visible_bounds: RectF,
-        _: &mut Self::LayoutState,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> Self::PaintState {
-        cx.scene().push_layer(Some(visible_bounds));
-        self.child.paint(bounds.origin(), visible_bounds, view, cx);
-        cx.scene().pop_layer();
-    }
-
-    fn rect_for_text_range(
-        &self,
-        range_utf16: Range<usize>,
-        _: RectF,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> Option<RectF> {
-        self.child.rect_for_text_range(range_utf16, view, cx)
-    }
-
-    fn debug(
-        &self,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> json::Value {
-        json!({"type": "ConstrainedBox", "assigned_constraint": self.constraint.to_json(), "child": self.child.debug(view, cx)})
-    }
-}

crates/gpui/src/elements/container.rs 🔗

@@ -1,684 +0,0 @@
-use std::ops::Range;
-
-use crate::{
-    color::Color,
-    geometry::{
-        deserialize_vec2f,
-        rect::RectF,
-        vector::{vec2f, Vector2F},
-    },
-    json::ToJson,
-    platform::CursorStyle,
-    scene::{self, CornerRadii, CursorRegion, Quad},
-    AnyElement, Element, SizeConstraint, ViewContext,
-};
-use schemars::JsonSchema;
-use serde::Deserialize;
-use serde_json::json;
-
-#[derive(Clone, Copy, Debug, Default, Deserialize, JsonSchema)]
-pub struct ContainerStyle {
-    #[serde(default)]
-    pub margin: Margin,
-    #[serde(default)]
-    pub padding: Padding,
-    #[serde(rename = "background")]
-    pub background_color: Option<Color>,
-    #[serde(rename = "overlay")]
-    pub overlay_color: Option<Color>,
-    #[serde(default)]
-    pub border: Border,
-    #[serde(default)]
-    #[serde(alias = "corner_radius")]
-    pub corner_radii: CornerRadii,
-    #[serde(default)]
-    pub shadow: Option<Shadow>,
-    #[serde(default)]
-    pub cursor: Option<CursorStyle>,
-}
-
-impl ContainerStyle {
-    pub fn fill(color: Color) -> Self {
-        Self {
-            background_color: Some(color),
-            ..Default::default()
-        }
-    }
-
-    pub fn additional_length(&self) -> f32 {
-        self.padding.left
-            + self.padding.right
-            + self.border.width * 2.
-            + self.margin.left
-            + self.margin.right
-    }
-}
-
-pub struct Container<V> {
-    child: AnyElement<V>,
-    style: ContainerStyle,
-}
-
-impl<V> Container<V> {
-    pub fn new(child: AnyElement<V>) -> Self {
-        Self {
-            child,
-            style: Default::default(),
-        }
-    }
-
-    pub fn with_style(mut self, style: ContainerStyle) -> Self {
-        self.style = style;
-        self
-    }
-
-    pub fn with_margin_top(mut self, margin: f32) -> Self {
-        self.style.margin.top = margin;
-        self
-    }
-
-    pub fn with_margin_bottom(mut self, margin: f32) -> Self {
-        self.style.margin.bottom = margin;
-        self
-    }
-
-    pub fn with_margin_left(mut self, margin: f32) -> Self {
-        self.style.margin.left = margin;
-        self
-    }
-
-    pub fn with_margin_right(mut self, margin: f32) -> Self {
-        self.style.margin.right = margin;
-        self
-    }
-
-    pub fn with_horizontal_padding(mut self, padding: f32) -> Self {
-        self.style.padding.left = padding;
-        self.style.padding.right = padding;
-        self
-    }
-
-    pub fn with_vertical_padding(mut self, padding: f32) -> Self {
-        self.style.padding.top = padding;
-        self.style.padding.bottom = padding;
-        self
-    }
-
-    pub fn with_uniform_padding(mut self, padding: f32) -> Self {
-        self.style.padding = Padding {
-            top: padding,
-            left: padding,
-            bottom: padding,
-            right: padding,
-        };
-        self
-    }
-
-    pub fn with_padding_left(mut self, padding: f32) -> Self {
-        self.style.padding.left = padding;
-        self
-    }
-
-    pub fn with_padding_right(mut self, padding: f32) -> Self {
-        self.style.padding.right = padding;
-        self
-    }
-
-    pub fn with_padding_top(mut self, padding: f32) -> Self {
-        self.style.padding.top = padding;
-        self
-    }
-
-    pub fn with_padding_bottom(mut self, padding: f32) -> Self {
-        self.style.padding.bottom = padding;
-        self
-    }
-
-    pub fn with_background_color(mut self, color: Color) -> Self {
-        self.style.background_color = Some(color);
-        self
-    }
-
-    pub fn with_overlay_color(mut self, color: Color) -> Self {
-        self.style.overlay_color = Some(color);
-        self
-    }
-
-    pub fn with_border(mut self, border: Border) -> Self {
-        self.style.border = border;
-        self
-    }
-
-    pub fn with_corner_radius(mut self, radius: f32) -> Self {
-        self.style.corner_radii.top_left = radius;
-        self.style.corner_radii.top_right = radius;
-        self.style.corner_radii.bottom_right = radius;
-        self.style.corner_radii.bottom_left = radius;
-        self
-    }
-
-    pub fn with_shadow(mut self, offset: Vector2F, blur: f32, color: Color) -> Self {
-        self.style.shadow = Some(Shadow {
-            offset,
-            blur,
-            color,
-        });
-        self
-    }
-
-    pub fn with_cursor(mut self, style: CursorStyle) -> Self {
-        self.style.cursor = Some(style);
-        self
-    }
-
-    fn margin_size(&self) -> Vector2F {
-        vec2f(
-            self.style.margin.left + self.style.margin.right,
-            self.style.margin.top + self.style.margin.bottom,
-        )
-    }
-
-    fn padding_size(&self) -> Vector2F {
-        vec2f(
-            self.style.padding.left + self.style.padding.right,
-            self.style.padding.top + self.style.padding.bottom,
-        )
-    }
-
-    fn border_size(&self) -> Vector2F {
-        let mut x = 0.0;
-        if self.style.border.left {
-            x += self.style.border.width;
-        }
-        if self.style.border.right {
-            x += self.style.border.width;
-        }
-
-        let mut y = 0.0;
-        if self.style.border.top {
-            y += self.style.border.width;
-        }
-        if self.style.border.bottom {
-            y += self.style.border.width;
-        }
-
-        vec2f(x, y)
-    }
-}
-
-#[derive(Copy, Clone, Debug, Default, JsonSchema)]
-pub struct Border {
-    pub color: Color,
-    pub width: f32,
-    pub overlay: bool,
-    pub top: bool,
-    pub bottom: bool,
-    pub left: bool,
-    pub right: bool,
-}
-
-impl Into<scene::Border> for Border {
-    fn into(self) -> scene::Border {
-        scene::Border {
-            color: self.color,
-            left: if self.left { self.width } else { 0.0 },
-            right: if self.right { self.width } else { 0.0 },
-            top: if self.top { self.width } else { 0.0 },
-            bottom: if self.bottom { self.width } else { 0.0 },
-        }
-    }
-}
-
-impl Border {
-    pub fn new(width: f32, color: Color) -> Self {
-        Self {
-            width,
-            color,
-            overlay: false,
-            top: false,
-            left: false,
-            bottom: false,
-            right: false,
-        }
-    }
-
-    pub fn all(width: f32, color: Color) -> Self {
-        Self {
-            width,
-            color,
-            overlay: false,
-            top: true,
-            left: true,
-            bottom: true,
-            right: true,
-        }
-    }
-
-    pub fn top(width: f32, color: Color) -> Self {
-        let mut border = Self::new(width, color);
-        border.top = true;
-        border
-    }
-
-    pub fn left(width: f32, color: Color) -> Self {
-        let mut border = Self::new(width, color);
-        border.left = true;
-        border
-    }
-
-    pub fn bottom(width: f32, color: Color) -> Self {
-        let mut border = Self::new(width, color);
-        border.bottom = true;
-        border
-    }
-
-    pub fn right(width: f32, color: Color) -> Self {
-        let mut border = Self::new(width, color);
-        border.right = true;
-        border
-    }
-
-    pub fn with_sides(mut self, top: bool, left: bool, bottom: bool, right: bool) -> Self {
-        self.top = top;
-        self.left = left;
-        self.bottom = bottom;
-        self.right = right;
-        self
-    }
-
-    pub fn top_width(&self) -> f32 {
-        if self.top {
-            self.width
-        } else {
-            0.0
-        }
-    }
-
-    pub fn left_width(&self) -> f32 {
-        if self.left {
-            self.width
-        } else {
-            0.0
-        }
-    }
-}
-
-impl<'de> Deserialize<'de> for Border {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        #[derive(Deserialize)]
-        struct BorderData {
-            pub width: f32,
-            pub color: Color,
-            #[serde(default)]
-            pub overlay: bool,
-            #[serde(default)]
-            pub top: bool,
-            #[serde(default)]
-            pub right: bool,
-            #[serde(default)]
-            pub bottom: bool,
-            #[serde(default)]
-            pub left: bool,
-        }
-
-        let data = BorderData::deserialize(deserializer)?;
-        let mut border = Border {
-            width: data.width,
-            color: data.color,
-            overlay: data.overlay,
-            top: data.top,
-            bottom: data.bottom,
-            left: data.left,
-            right: data.right,
-        };
-        if !border.top && !border.bottom && !border.left && !border.right {
-            border.top = true;
-            border.bottom = true;
-            border.left = true;
-            border.right = true;
-        }
-        Ok(border)
-    }
-}
-
-impl ToJson for Border {
-    fn to_json(&self) -> serde_json::Value {
-        let mut value = json!({});
-        if self.top {
-            value["top"] = json!(self.width);
-        }
-        if self.right {
-            value["right"] = json!(self.width);
-        }
-        if self.bottom {
-            value["bottom"] = json!(self.width);
-        }
-        if self.left {
-            value["left"] = json!(self.width);
-        }
-        value
-    }
-}
-
-impl<V: 'static> Element<V> for Container<V> {
-    type LayoutState = ();
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        constraint: SizeConstraint,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> (Vector2F, Self::LayoutState) {
-        let mut size_buffer = self.margin_size() + self.padding_size();
-        if !self.style.border.overlay {
-            size_buffer += self.border_size();
-        }
-        let child_constraint = SizeConstraint {
-            min: (constraint.min - size_buffer).max(Vector2F::zero()),
-            max: (constraint.max - size_buffer).max(Vector2F::zero()),
-        };
-        let child_size = self.child.layout(child_constraint, view, cx);
-        (child_size + size_buffer, ())
-    }
-
-    fn paint(
-        &mut self,
-        bounds: RectF,
-        visible_bounds: RectF,
-        _: &mut Self::LayoutState,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> Self::PaintState {
-        let quad_bounds = RectF::from_points(
-            bounds.origin() + vec2f(self.style.margin.left, self.style.margin.top),
-            bounds.lower_right() - vec2f(self.style.margin.right, self.style.margin.bottom),
-        );
-
-        if let Some(shadow) = self.style.shadow.as_ref() {
-            cx.scene().push_shadow(scene::Shadow {
-                bounds: quad_bounds + shadow.offset,
-                corner_radii: self.style.corner_radii,
-                sigma: shadow.blur,
-                color: shadow.color,
-            });
-        }
-
-        if let Some(hit_bounds) = quad_bounds.intersection(visible_bounds) {
-            if let Some(style) = self.style.cursor {
-                cx.scene().push_cursor_region(CursorRegion {
-                    bounds: hit_bounds,
-                    style,
-                });
-            }
-        }
-
-        let child_origin =
-            quad_bounds.origin() + vec2f(self.style.padding.left, self.style.padding.top);
-
-        if self.style.border.overlay {
-            cx.scene().push_quad(Quad {
-                bounds: quad_bounds,
-                background: self.style.background_color,
-                border: Default::default(),
-                corner_radii: self.style.corner_radii.into(),
-            });
-
-            self.child.paint(child_origin, visible_bounds, view, cx);
-
-            cx.scene().push_layer(None);
-            cx.scene().push_quad(Quad {
-                bounds: quad_bounds,
-                background: self.style.overlay_color,
-                border: self.style.border.into(),
-                corner_radii: self.style.corner_radii.into(),
-            });
-            cx.scene().pop_layer();
-        } else {
-            cx.scene().push_quad(Quad {
-                bounds: quad_bounds,
-                background: self.style.background_color,
-                border: self.style.border.into(),
-                corner_radii: self.style.corner_radii.into(),
-            });
-
-            let child_origin = child_origin
-                + vec2f(
-                    self.style.border.left_width(),
-                    self.style.border.top_width(),
-                );
-            self.child.paint(child_origin, visible_bounds, view, cx);
-
-            if self.style.overlay_color.is_some() {
-                cx.scene().push_layer(None);
-                cx.scene().push_quad(Quad {
-                    bounds: quad_bounds,
-                    background: self.style.overlay_color,
-                    border: Default::default(),
-                    corner_radii: self.style.corner_radii.into(),
-                });
-                cx.scene().pop_layer();
-            }
-        }
-    }
-
-    fn rect_for_text_range(
-        &self,
-        range_utf16: Range<usize>,
-        _: RectF,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> Option<RectF> {
-        self.child.rect_for_text_range(range_utf16, view, cx)
-    }
-
-    fn debug(
-        &self,
-        bounds: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> serde_json::Value {
-        json!({
-            "type": "Container",
-            "bounds": bounds.to_json(),
-            "details": self.style.to_json(),
-            "child": self.child.debug(view, cx),
-        })
-    }
-}
-
-impl ToJson for ContainerStyle {
-    fn to_json(&self) -> serde_json::Value {
-        json!({
-            "margin": self.margin.to_json(),
-            "padding": self.padding.to_json(),
-            "background_color": self.background_color.to_json(),
-            "border": self.border.to_json(),
-            "corner_radius": self.corner_radii,
-            "shadow": self.shadow.to_json(),
-        })
-    }
-}
-
-#[derive(Clone, Copy, Debug, Default, JsonSchema)]
-pub struct Margin {
-    pub top: f32,
-    pub bottom: f32,
-    pub left: f32,
-    pub right: f32,
-}
-
-impl ToJson for Margin {
-    fn to_json(&self) -> serde_json::Value {
-        let mut value = json!({});
-        if self.top > 0. {
-            value["top"] = json!(self.top);
-        }
-        if self.right > 0. {
-            value["right"] = json!(self.right);
-        }
-        if self.bottom > 0. {
-            value["bottom"] = json!(self.bottom);
-        }
-        if self.left > 0. {
-            value["left"] = json!(self.left);
-        }
-        value
-    }
-}
-
-#[derive(Clone, Copy, Debug, Default, JsonSchema)]
-pub struct Padding {
-    pub top: f32,
-    pub left: f32,
-    pub bottom: f32,
-    pub right: f32,
-}
-
-impl Padding {
-    pub fn horizontal(padding: f32) -> Self {
-        Self {
-            left: padding,
-            right: padding,
-            ..Default::default()
-        }
-    }
-
-    pub fn vertical(padding: f32) -> Self {
-        Self {
-            top: padding,
-            bottom: padding,
-            ..Default::default()
-        }
-    }
-}
-
-impl<'de> Deserialize<'de> for Padding {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        let spacing = Spacing::deserialize(deserializer)?;
-        Ok(match spacing {
-            Spacing::Uniform(size) => Padding {
-                top: size,
-                left: size,
-                bottom: size,
-                right: size,
-            },
-            Spacing::Specific {
-                top,
-                left,
-                bottom,
-                right,
-            } => Padding {
-                top,
-                left,
-                bottom,
-                right,
-            },
-        })
-    }
-}
-
-impl<'de> Deserialize<'de> for Margin {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        let spacing = Spacing::deserialize(deserializer)?;
-        Ok(match spacing {
-            Spacing::Uniform(size) => Margin {
-                top: size,
-                left: size,
-                bottom: size,
-                right: size,
-            },
-            Spacing::Specific {
-                top,
-                left,
-                bottom,
-                right,
-            } => Margin {
-                top,
-                left,
-                bottom,
-                right,
-            },
-        })
-    }
-}
-#[derive(Deserialize)]
-#[serde(untagged)]
-enum Spacing {
-    Uniform(f32),
-    Specific {
-        #[serde(default)]
-        top: f32,
-        #[serde(default)]
-        left: f32,
-        #[serde(default)]
-        bottom: f32,
-        #[serde(default)]
-        right: f32,
-    },
-}
-
-impl Padding {
-    pub fn uniform(padding: f32) -> Self {
-        Self {
-            top: padding,
-            left: padding,
-            bottom: padding,
-            right: padding,
-        }
-    }
-}
-
-impl ToJson for Padding {
-    fn to_json(&self) -> serde_json::Value {
-        let mut value = json!({});
-        if self.top > 0. {
-            value["top"] = json!(self.top);
-        }
-        if self.right > 0. {
-            value["right"] = json!(self.right);
-        }
-        if self.bottom > 0. {
-            value["bottom"] = json!(self.bottom);
-        }
-        if self.left > 0. {
-            value["left"] = json!(self.left);
-        }
-        value
-    }
-}
-
-#[derive(Clone, Copy, Debug, Default, Deserialize, JsonSchema)]
-pub struct Shadow {
-    #[serde(default, deserialize_with = "deserialize_vec2f")]
-    #[schemars(with = "Vec::<f32>")]
-    offset: Vector2F,
-    #[serde(default)]
-    blur: f32,
-    #[serde(default)]
-    color: Color,
-}
-
-impl ToJson for Shadow {
-    fn to_json(&self) -> serde_json::Value {
-        json!({
-            "offset": self.offset.to_json(),
-            "blur": self.blur,
-            "color": self.color.to_json()
-        })
-    }
-}

crates/gpui/src/elements/empty.rs 🔗

@@ -1,89 +0,0 @@
-use std::ops::Range;
-
-use crate::{
-    geometry::{
-        rect::RectF,
-        vector::{vec2f, Vector2F},
-    },
-    json::{json, ToJson},
-    ViewContext,
-};
-use crate::{Element, SizeConstraint};
-
-#[derive(Default)]
-pub struct Empty {
-    collapsed: bool,
-}
-
-impl Empty {
-    pub fn new() -> Self {
-        Self::default()
-    }
-
-    pub fn collapsed(mut self) -> Self {
-        self.collapsed = true;
-        self
-    }
-}
-
-impl<V: 'static> Element<V> for Empty {
-    type LayoutState = ();
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        constraint: SizeConstraint,
-        _: &mut V,
-        _: &mut ViewContext<V>,
-    ) -> (Vector2F, Self::LayoutState) {
-        let x = if constraint.max.x().is_finite() && !self.collapsed {
-            constraint.max.x()
-        } else {
-            constraint.min.x()
-        };
-        let y = if constraint.max.y().is_finite() && !self.collapsed {
-            constraint.max.y()
-        } else {
-            constraint.min.y()
-        };
-
-        (vec2f(x, y), ())
-    }
-
-    fn paint(
-        &mut self,
-        _: RectF,
-        _: RectF,
-        _: &mut Self::LayoutState,
-        _: &mut V,
-        _: &mut ViewContext<V>,
-    ) -> Self::PaintState {
-    }
-
-    fn rect_for_text_range(
-        &self,
-        _: Range<usize>,
-        _: RectF,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        _: &V,
-        _: &ViewContext<V>,
-    ) -> Option<RectF> {
-        None
-    }
-
-    fn debug(
-        &self,
-        bounds: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        _: &V,
-        _: &ViewContext<V>,
-    ) -> serde_json::Value {
-        json!({
-            "type": "Empty",
-            "bounds": bounds.to_json(),
-        })
-    }
-}

crates/gpui/src/elements/expanded.rs 🔗

@@ -1,96 +0,0 @@
-use std::ops::Range;
-
-use crate::{
-    geometry::{rect::RectF, vector::Vector2F},
-    json, AnyElement, Element, SizeConstraint, ViewContext,
-};
-use serde_json::json;
-
-pub struct Expanded<V> {
-    child: AnyElement<V>,
-    full_width: bool,
-    full_height: bool,
-}
-
-impl<V: 'static> Expanded<V> {
-    pub fn new(child: impl Element<V>) -> Self {
-        Self {
-            child: child.into_any(),
-            full_width: true,
-            full_height: true,
-        }
-    }
-
-    pub fn full_width(mut self) -> Self {
-        self.full_width = true;
-        self.full_height = false;
-        self
-    }
-
-    pub fn full_height(mut self) -> Self {
-        self.full_width = false;
-        self.full_height = true;
-        self
-    }
-}
-
-impl<V: 'static> Element<V> for Expanded<V> {
-    type LayoutState = ();
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        mut constraint: SizeConstraint,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> (Vector2F, Self::LayoutState) {
-        if self.full_width {
-            constraint.min.set_x(constraint.max.x());
-        }
-        if self.full_height {
-            constraint.min.set_y(constraint.max.y());
-        }
-        let size = self.child.layout(constraint, view, cx);
-        (size, ())
-    }
-
-    fn paint(
-        &mut self,
-        bounds: RectF,
-        visible_bounds: RectF,
-        _: &mut Self::LayoutState,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> Self::PaintState {
-        self.child.paint(bounds.origin(), visible_bounds, view, cx);
-    }
-
-    fn rect_for_text_range(
-        &self,
-        range_utf16: Range<usize>,
-        _: RectF,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> Option<RectF> {
-        self.child.rect_for_text_range(range_utf16, view, cx)
-    }
-
-    fn debug(
-        &self,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> json::Value {
-        json!({
-            "type": "Expanded",
-            "full_width": self.full_width,
-            "full_height": self.full_height,
-            "child": self.child.debug(view, cx)
-        })
-    }
-}

crates/gpui/src/elements/flex.rs 🔗

@@ -1,512 +0,0 @@
-use std::{any::Any, cell::Cell, f32::INFINITY, ops::Range, rc::Rc};
-
-use crate::{
-    json::{self, ToJson, Value},
-    AnyElement, Axis, Element, ElementStateHandle, SizeConstraint, TypeTag, Vector2FExt,
-    ViewContext,
-};
-use pathfinder_geometry::{
-    rect::RectF,
-    vector::{vec2f, Vector2F},
-};
-use serde_json::json;
-
-struct ScrollState {
-    scroll_to: Cell<Option<usize>>,
-    scroll_position: Cell<f32>,
-    type_tag: TypeTag,
-}
-
-pub struct Flex<V> {
-    axis: Axis,
-    children: Vec<AnyElement<V>>,
-    scroll_state: Option<(ElementStateHandle<Rc<ScrollState>>, usize)>,
-    child_alignment: f32,
-    spacing: f32,
-}
-
-impl<V: 'static> Flex<V> {
-    pub fn new(axis: Axis) -> Self {
-        Self {
-            axis,
-            children: Default::default(),
-            scroll_state: None,
-            child_alignment: -1.,
-            spacing: 0.,
-        }
-    }
-
-    pub fn row() -> Self {
-        Self::new(Axis::Horizontal)
-    }
-
-    pub fn column() -> Self {
-        Self::new(Axis::Vertical)
-    }
-
-    /// Render children centered relative to the cross-axis of the parent flex.
-    ///
-    /// If this is a flex row, children will be centered vertically. If this is a
-    /// flex column, children will be centered horizontally.
-    pub fn align_children_center(mut self) -> Self {
-        self.child_alignment = 0.;
-        self
-    }
-
-    pub fn with_spacing(mut self, spacing: f32) -> Self {
-        self.spacing = spacing;
-        self
-    }
-
-    pub fn scrollable<Tag>(
-        mut self,
-        element_id: usize,
-        scroll_to: Option<usize>,
-        cx: &mut ViewContext<V>,
-    ) -> Self
-    where
-        Tag: 'static,
-    {
-        // Don't assume that this initialization is what scroll_state really is in other panes:
-        // `element_state` is shared and there could be init races.
-        let scroll_state = cx.element_state::<Tag, Rc<ScrollState>>(
-            element_id,
-            Rc::new(ScrollState {
-                type_tag: TypeTag::new::<Tag>(),
-                scroll_to: Default::default(),
-                scroll_position: Default::default(),
-            }),
-        );
-        // Set scroll_to separately, because the default state is already picked as `None` by other panes
-        // by the time we start setting it here, hence update all others' state too.
-        scroll_state.update(cx, |this, _| {
-            this.scroll_to.set(scroll_to);
-        });
-        self.scroll_state = Some((scroll_state, cx.handle().id()));
-        self
-    }
-
-    pub fn is_empty(&self) -> bool {
-        self.children.is_empty()
-    }
-
-    fn layout_flex_children(
-        &mut self,
-        layout_expanded: bool,
-        constraint: SizeConstraint,
-        remaining_space: &mut f32,
-        remaining_flex: &mut f32,
-        cross_axis_max: &mut f32,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) {
-        let cross_axis = self.axis.invert();
-        for child in self.children.iter_mut() {
-            if let Some(metadata) = child.metadata::<FlexParentData>() {
-                if let Some((flex, expanded)) = metadata.flex {
-                    if expanded != layout_expanded {
-                        continue;
-                    }
-
-                    let child_max = if *remaining_flex == 0.0 {
-                        *remaining_space
-                    } else {
-                        let space_per_flex = *remaining_space / *remaining_flex;
-                        space_per_flex * flex
-                    };
-                    let child_min = if expanded { child_max } else { 0. };
-                    let child_constraint = match self.axis {
-                        Axis::Horizontal => SizeConstraint::new(
-                            vec2f(child_min, constraint.min.y()),
-                            vec2f(child_max, constraint.max.y()),
-                        ),
-                        Axis::Vertical => SizeConstraint::new(
-                            vec2f(constraint.min.x(), child_min),
-                            vec2f(constraint.max.x(), child_max),
-                        ),
-                    };
-                    let child_size = child.layout(child_constraint, view, cx);
-                    *remaining_space -= child_size.along(self.axis);
-                    *remaining_flex -= flex;
-                    *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
-                }
-            }
-        }
-    }
-}
-
-impl<V> Extend<AnyElement<V>> for Flex<V> {
-    fn extend<T: IntoIterator<Item = AnyElement<V>>>(&mut self, children: T) {
-        self.children.extend(children);
-    }
-}
-
-impl<V: 'static> Element<V> for Flex<V> {
-    type LayoutState = f32;
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        constraint: SizeConstraint,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> (Vector2F, Self::LayoutState) {
-        let mut total_flex = None;
-        let mut fixed_space = self.children.len().saturating_sub(1) as f32 * self.spacing;
-        let mut contains_float = false;
-
-        let cross_axis = self.axis.invert();
-        let mut cross_axis_max: f32 = 0.0;
-        for child in self.children.iter_mut() {
-            let metadata = child.metadata::<FlexParentData>();
-            contains_float |= metadata.map_or(false, |metadata| metadata.float);
-
-            if let Some(flex) = metadata.and_then(|metadata| metadata.flex.map(|(flex, _)| flex)) {
-                *total_flex.get_or_insert(0.) += flex;
-            } else {
-                let child_constraint = match self.axis {
-                    Axis::Horizontal => SizeConstraint::new(
-                        vec2f(0.0, constraint.min.y()),
-                        vec2f(INFINITY, constraint.max.y()),
-                    ),
-                    Axis::Vertical => SizeConstraint::new(
-                        vec2f(constraint.min.x(), 0.0),
-                        vec2f(constraint.max.x(), INFINITY),
-                    ),
-                };
-                let size = child.layout(child_constraint, view, cx);
-                fixed_space += size.along(self.axis);
-                cross_axis_max = cross_axis_max.max(size.along(cross_axis));
-            }
-        }
-
-        let mut remaining_space = constraint.max_along(self.axis) - fixed_space;
-        let mut size = if let Some(mut remaining_flex) = total_flex {
-            if remaining_space.is_infinite() {
-                panic!("flex contains flexible children but has an infinite constraint along the flex axis");
-            }
-
-            self.layout_flex_children(
-                false,
-                constraint,
-                &mut remaining_space,
-                &mut remaining_flex,
-                &mut cross_axis_max,
-                view,
-                cx,
-            );
-            self.layout_flex_children(
-                true,
-                constraint,
-                &mut remaining_space,
-                &mut remaining_flex,
-                &mut cross_axis_max,
-                view,
-                cx,
-            );
-
-            match self.axis {
-                Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max),
-                Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space),
-            }
-        } else {
-            match self.axis {
-                Axis::Horizontal => vec2f(fixed_space, cross_axis_max),
-                Axis::Vertical => vec2f(cross_axis_max, fixed_space),
-            }
-        };
-
-        if contains_float {
-            match self.axis {
-                Axis::Horizontal => size.set_x(size.x().max(constraint.max.x())),
-                Axis::Vertical => size.set_y(size.y().max(constraint.max.y())),
-            }
-        }
-
-        if constraint.min.x().is_finite() {
-            size.set_x(size.x().max(constraint.min.x()));
-        }
-        if constraint.min.y().is_finite() {
-            size.set_y(size.y().max(constraint.min.y()));
-        }
-
-        if size.x() > constraint.max.x() {
-            size.set_x(constraint.max.x());
-        }
-        if size.y() > constraint.max.y() {
-            size.set_y(constraint.max.y());
-        }
-
-        if let Some(scroll_state) = self.scroll_state.as_ref() {
-            scroll_state.0.update(cx, |scroll_state, _| {
-                if let Some(scroll_to) = scroll_state.scroll_to.take() {
-                    let visible_start = scroll_state.scroll_position.get();
-                    let visible_end = visible_start + size.along(self.axis);
-                    if let Some(child) = self.children.get(scroll_to) {
-                        let child_start: f32 = self.children[..scroll_to]
-                            .iter()
-                            .map(|c| c.size().along(self.axis))
-                            .sum();
-                        let child_end = child_start + child.size().along(self.axis);
-                        if child_start < visible_start {
-                            scroll_state.scroll_position.set(child_start);
-                        } else if child_end > visible_end {
-                            scroll_state
-                                .scroll_position
-                                .set(child_end - size.along(self.axis));
-                        }
-                    }
-                }
-
-                scroll_state.scroll_position.set(
-                    scroll_state
-                        .scroll_position
-                        .get()
-                        .min(-remaining_space)
-                        .max(0.),
-                );
-            });
-        }
-
-        (size, remaining_space)
-    }
-
-    fn paint(
-        &mut self,
-        bounds: RectF,
-        visible_bounds: RectF,
-        remaining_space: &mut Self::LayoutState,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> Self::PaintState {
-        let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
-
-        let mut remaining_space = *remaining_space;
-        let overflowing = remaining_space < 0.;
-        if overflowing {
-            cx.scene().push_layer(Some(visible_bounds));
-        }
-
-        if let Some((scroll_state, id)) = &self.scroll_state {
-            let scroll_state = scroll_state.read(cx).clone();
-            cx.scene().push_mouse_region(
-                crate::MouseRegion::from_handlers(
-                    scroll_state.type_tag,
-                    *id,
-                    0,
-                    bounds,
-                    Default::default(),
-                )
-                .on_scroll({
-                    let axis = self.axis;
-                    move |e, _: &mut V, cx| {
-                        if remaining_space < 0. {
-                            let scroll_delta = e.delta.raw();
-
-                            let mut delta = match axis {
-                                Axis::Horizontal => {
-                                    if scroll_delta.x().abs() >= scroll_delta.y().abs() {
-                                        scroll_delta.x()
-                                    } else {
-                                        scroll_delta.y()
-                                    }
-                                }
-                                Axis::Vertical => scroll_delta.y(),
-                            };
-                            if !e.delta.precise() {
-                                delta *= 20.;
-                            }
-
-                            scroll_state
-                                .scroll_position
-                                .set(scroll_state.scroll_position.get() - delta);
-
-                            cx.notify();
-                        } else {
-                            cx.propagate_event();
-                        }
-                    }
-                })
-                .on_move(|_, _: &mut V, _| { /* Capture move events */ }),
-            )
-        }
-
-        let mut child_origin = bounds.origin();
-        if let Some(scroll_state) = self.scroll_state.as_ref() {
-            let scroll_position = scroll_state.0.read(cx).scroll_position.get();
-            match self.axis {
-                Axis::Horizontal => child_origin.set_x(child_origin.x() - scroll_position),
-                Axis::Vertical => child_origin.set_y(child_origin.y() - scroll_position),
-            }
-        }
-
-        for child in self.children.iter_mut() {
-            if remaining_space > 0. {
-                if let Some(metadata) = child.metadata::<FlexParentData>() {
-                    if metadata.float {
-                        match self.axis {
-                            Axis::Horizontal => child_origin += vec2f(remaining_space, 0.0),
-                            Axis::Vertical => child_origin += vec2f(0.0, remaining_space),
-                        }
-                        remaining_space = 0.;
-                    }
-                }
-            }
-
-            // We use the child_alignment f32 to determine a point along the cross axis of the
-            // overall flex element and each child. We then align these points. So 0 would center
-            // each child relative to the overall height/width of the flex. -1 puts children at
-            // the start. 1 puts children at the end.
-            let aligned_child_origin = {
-                let cross_axis = self.axis.invert();
-                let my_center = bounds.size().along(cross_axis) / 2.;
-                let my_target = my_center + my_center * self.child_alignment;
-
-                let child_center = child.size().along(cross_axis) / 2.;
-                let child_target = child_center + child_center * self.child_alignment;
-
-                let mut aligned_child_origin = child_origin;
-                match self.axis {
-                    Axis::Horizontal => aligned_child_origin
-                        .set_y(aligned_child_origin.y() - (child_target - my_target)),
-                    Axis::Vertical => aligned_child_origin
-                        .set_x(aligned_child_origin.x() - (child_target - my_target)),
-                }
-
-                aligned_child_origin
-            };
-
-            child.paint(aligned_child_origin, visible_bounds, view, cx);
-
-            match self.axis {
-                Axis::Horizontal => child_origin += vec2f(child.size().x() + self.spacing, 0.0),
-                Axis::Vertical => child_origin += vec2f(0.0, child.size().y() + self.spacing),
-            }
-        }
-
-        if overflowing {
-            cx.scene().pop_layer();
-        }
-    }
-
-    fn rect_for_text_range(
-        &self,
-        range_utf16: Range<usize>,
-        _: RectF,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> Option<RectF> {
-        self.children
-            .iter()
-            .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
-    }
-
-    fn debug(
-        &self,
-        bounds: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> json::Value {
-        json!({
-            "type": "Flex",
-            "bounds": bounds.to_json(),
-            "axis": self.axis.to_json(),
-            "children": self.children.iter().map(|child| child.debug(view, cx)).collect::<Vec<json::Value>>()
-        })
-    }
-}
-
-struct FlexParentData {
-    flex: Option<(f32, bool)>,
-    float: bool,
-}
-
-pub struct FlexItem<V> {
-    metadata: FlexParentData,
-    child: AnyElement<V>,
-}
-
-impl<V: 'static> FlexItem<V> {
-    pub fn new(child: impl Element<V>) -> Self {
-        FlexItem {
-            metadata: FlexParentData {
-                flex: None,
-                float: false,
-            },
-            child: child.into_any(),
-        }
-    }
-
-    pub fn flex(mut self, flex: f32, expanded: bool) -> Self {
-        self.metadata.flex = Some((flex, expanded));
-        self
-    }
-
-    pub fn float(mut self) -> Self {
-        self.metadata.float = true;
-        self
-    }
-}
-
-impl<V: 'static> Element<V> for FlexItem<V> {
-    type LayoutState = ();
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        constraint: SizeConstraint,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> (Vector2F, Self::LayoutState) {
-        let size = self.child.layout(constraint, view, cx);
-        (size, ())
-    }
-
-    fn paint(
-        &mut self,
-        bounds: RectF,
-        visible_bounds: RectF,
-        _: &mut Self::LayoutState,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> Self::PaintState {
-        self.child.paint(bounds.origin(), visible_bounds, view, cx)
-    }
-
-    fn rect_for_text_range(
-        &self,
-        range_utf16: Range<usize>,
-        _: RectF,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> Option<RectF> {
-        self.child.rect_for_text_range(range_utf16, view, cx)
-    }
-
-    fn metadata(&self) -> Option<&dyn Any> {
-        Some(&self.metadata)
-    }
-
-    fn debug(
-        &self,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> Value {
-        json!({
-            "type": "Flexible",
-            "flex": self.metadata.flex,
-            "child": self.child.debug(view, cx)
-        })
-    }
-}

crates/gpui/src/elements/hook.rs 🔗

@@ -1,85 +0,0 @@
-use std::ops::Range;
-
-use crate::{
-    geometry::{rect::RectF, vector::Vector2F},
-    json::json,
-    AnyElement, Element, SizeConstraint, ViewContext,
-};
-
-pub struct Hook<V> {
-    child: AnyElement<V>,
-    after_layout: Option<Box<dyn FnMut(Vector2F, &mut ViewContext<V>)>>,
-}
-
-impl<V: 'static> Hook<V> {
-    pub fn new(child: impl Element<V>) -> Self {
-        Self {
-            child: child.into_any(),
-            after_layout: None,
-        }
-    }
-
-    pub fn on_after_layout(
-        mut self,
-        f: impl 'static + FnMut(Vector2F, &mut ViewContext<V>),
-    ) -> Self {
-        self.after_layout = Some(Box::new(f));
-        self
-    }
-}
-
-impl<V: 'static> Element<V> for Hook<V> {
-    type LayoutState = ();
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        constraint: SizeConstraint,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> (Vector2F, Self::LayoutState) {
-        let size = self.child.layout(constraint, view, cx);
-        if let Some(handler) = self.after_layout.as_mut() {
-            handler(size, cx);
-        }
-        (size, ())
-    }
-
-    fn paint(
-        &mut self,
-        bounds: RectF,
-        visible_bounds: RectF,
-        _: &mut Self::LayoutState,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) {
-        self.child.paint(bounds.origin(), visible_bounds, view, cx);
-    }
-
-    fn rect_for_text_range(
-        &self,
-        range_utf16: Range<usize>,
-        _: RectF,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> Option<RectF> {
-        self.child.rect_for_text_range(range_utf16, view, cx)
-    }
-
-    fn debug(
-        &self,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> serde_json::Value {
-        json!({
-            "type": "Hooks",
-            "child": self.child.debug(view, cx),
-        })
-    }
-}

crates/gpui/src/elements/image.rs 🔗

@@ -1,137 +0,0 @@
-use super::{constrain_size_preserving_aspect_ratio, Border};
-use crate::{
-    geometry::{
-        rect::RectF,
-        vector::{vec2f, Vector2F},
-    },
-    json::{json, ToJson},
-    scene, Element, ImageData, SizeConstraint, ViewContext,
-};
-use schemars::JsonSchema;
-use serde::Deserialize;
-use std::{ops::Range, sync::Arc};
-
-enum ImageSource {
-    Path(&'static str),
-    Data(Arc<ImageData>),
-}
-
-pub struct Image {
-    source: ImageSource,
-    style: ImageStyle,
-}
-
-#[derive(Copy, Clone, Default, Deserialize, JsonSchema)]
-pub struct ImageStyle {
-    #[serde(default)]
-    pub border: Border,
-    #[serde(default)]
-    pub corner_radius: f32,
-    #[serde(default)]
-    pub height: Option<f32>,
-    #[serde(default)]
-    pub width: Option<f32>,
-    #[serde(default)]
-    pub grayscale: bool,
-}
-
-impl Image {
-    pub fn new(asset_path: &'static str) -> Self {
-        Self {
-            source: ImageSource::Path(asset_path),
-            style: Default::default(),
-        }
-    }
-
-    pub fn from_data(data: Arc<ImageData>) -> Self {
-        Self {
-            source: ImageSource::Data(data),
-            style: Default::default(),
-        }
-    }
-
-    pub fn with_style(mut self, style: ImageStyle) -> Self {
-        self.style = style;
-        self
-    }
-}
-
-impl<V: 'static> Element<V> for Image {
-    type LayoutState = Option<Arc<ImageData>>;
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        constraint: SizeConstraint,
-        _: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> (Vector2F, Self::LayoutState) {
-        let data = match &self.source {
-            ImageSource::Path(path) => match cx.asset_cache.png(path) {
-                Ok(data) => data,
-                Err(error) => {
-                    log::error!("could not load image: {}", error);
-                    return (Vector2F::zero(), None);
-                }
-            },
-            ImageSource::Data(data) => data.clone(),
-        };
-
-        let desired_size = vec2f(
-            self.style.width.unwrap_or_else(|| constraint.max.x()),
-            self.style.height.unwrap_or_else(|| constraint.max.y()),
-        );
-        let size = constrain_size_preserving_aspect_ratio(
-            constraint.constrain(desired_size),
-            data.size().to_f32(),
-        );
-
-        (size, Some(data))
-    }
-
-    fn paint(
-        &mut self,
-        bounds: RectF,
-        _: RectF,
-        layout: &mut Self::LayoutState,
-        _: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> Self::PaintState {
-        if let Some(data) = layout {
-            cx.scene().push_image(scene::Image {
-                bounds,
-                border: self.style.border.into(),
-                corner_radii: self.style.corner_radius.into(),
-                grayscale: self.style.grayscale,
-                data: data.clone(),
-            });
-        }
-    }
-
-    fn rect_for_text_range(
-        &self,
-        _: Range<usize>,
-        _: RectF,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        _: &V,
-        _: &ViewContext<V>,
-    ) -> Option<RectF> {
-        None
-    }
-
-    fn debug(
-        &self,
-        bounds: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        _: &V,
-        _: &ViewContext<V>,
-    ) -> serde_json::Value {
-        json!({
-            "type": "Image",
-            "bounds": bounds.to_json(),
-        })
-    }
-}

crates/gpui/src/elements/keystroke_label.rs 🔗

@@ -1,100 +0,0 @@
-use crate::{
-    elements::*,
-    fonts::TextStyle,
-    geometry::{rect::RectF, vector::Vector2F},
-    Action, AnyElement, SizeConstraint,
-};
-use serde_json::json;
-
-use super::ContainerStyle;
-
-pub struct KeystrokeLabel {
-    action: Box<dyn Action>,
-    container_style: ContainerStyle,
-    text_style: TextStyle,
-    view_id: usize,
-}
-
-impl KeystrokeLabel {
-    pub fn new(
-        view_id: usize,
-        action: Box<dyn Action>,
-        container_style: ContainerStyle,
-        text_style: TextStyle,
-    ) -> Self {
-        Self {
-            view_id,
-            action,
-            container_style,
-            text_style,
-        }
-    }
-}
-
-impl<V: 'static> Element<V> for KeystrokeLabel {
-    type LayoutState = AnyElement<V>;
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        constraint: SizeConstraint,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> (Vector2F, AnyElement<V>) {
-        let mut element = if let Some(keystrokes) =
-            cx.keystrokes_for_action(self.view_id, self.action.as_ref())
-        {
-            Flex::row()
-                .with_children(keystrokes.iter().map(|keystroke| {
-                    Label::new(keystroke.to_string(), self.text_style.clone())
-                        .contained()
-                        .with_style(self.container_style)
-                }))
-                .into_any()
-        } else {
-            Empty::new().collapsed().into_any()
-        };
-
-        let size = element.layout(constraint, view, cx);
-        (size, element)
-    }
-
-    fn paint(
-        &mut self,
-        bounds: RectF,
-        visible_bounds: RectF,
-        element: &mut AnyElement<V>,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) {
-        element.paint(bounds.origin(), visible_bounds, view, cx);
-    }
-
-    fn rect_for_text_range(
-        &self,
-        _: Range<usize>,
-        _: RectF,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        _: &V,
-        _: &ViewContext<V>,
-    ) -> Option<RectF> {
-        None
-    }
-
-    fn debug(
-        &self,
-        _: RectF,
-        element: &AnyElement<V>,
-        _: &(),
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> serde_json::Value {
-        json!({
-            "type": "KeystrokeLabel",
-            "action": self.action.name(),
-            "child": element.debug(view, cx)
-        })
-    }
-}

crates/gpui/src/elements/label.rs 🔗

@@ -1,280 +0,0 @@
-use std::{borrow::Cow, ops::Range};
-
-use crate::{
-    fonts::TextStyle,
-    geometry::{
-        rect::RectF,
-        vector::{vec2f, Vector2F},
-    },
-    json::{ToJson, Value},
-    text_layout::{Line, RunStyle},
-    Element, SizeConstraint, ViewContext,
-};
-use schemars::JsonSchema;
-use serde::Deserialize;
-use serde_json::json;
-use smallvec::{smallvec, SmallVec};
-
-pub struct Label {
-    text: Cow<'static, str>,
-    style: LabelStyle,
-    highlight_indices: Vec<usize>,
-}
-
-#[derive(Clone, Debug, Deserialize, Default, JsonSchema)]
-pub struct LabelStyle {
-    pub text: TextStyle,
-    pub highlight_text: Option<TextStyle>,
-}
-
-impl From<TextStyle> for LabelStyle {
-    fn from(text: TextStyle) -> Self {
-        LabelStyle {
-            text,
-            highlight_text: None,
-        }
-    }
-}
-
-impl LabelStyle {
-    pub fn with_font_size(mut self, font_size: f32) -> Self {
-        self.text.font_size = font_size;
-        self
-    }
-}
-
-impl Label {
-    pub fn new<I: Into<Cow<'static, str>>>(text: I, style: impl Into<LabelStyle>) -> Self {
-        Self {
-            text: text.into(),
-            highlight_indices: Default::default(),
-            style: style.into(),
-        }
-    }
-
-    pub fn with_highlights(mut self, indices: Vec<usize>) -> Self {
-        self.highlight_indices = indices;
-        self
-    }
-
-    fn compute_runs(&self) -> SmallVec<[(usize, RunStyle); 8]> {
-        let font_id = self.style.text.font_id;
-        if self.highlight_indices.is_empty() {
-            return smallvec![(
-                self.text.len(),
-                RunStyle {
-                    font_id,
-                    color: self.style.text.color,
-                    underline: self.style.text.underline,
-                }
-            )];
-        }
-
-        let highlight_font_id = self
-            .style
-            .highlight_text
-            .as_ref()
-            .map_or(font_id, |style| style.font_id);
-
-        let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
-        let mut runs = SmallVec::new();
-        let highlight_style = self
-            .style
-            .highlight_text
-            .as_ref()
-            .unwrap_or(&self.style.text);
-
-        for (char_ix, c) in self.text.char_indices() {
-            let mut font_id = font_id;
-            let mut color = self.style.text.color;
-            let mut underline = self.style.text.underline;
-            if let Some(highlight_ix) = highlight_indices.peek() {
-                if char_ix == *highlight_ix {
-                    font_id = highlight_font_id;
-                    color = highlight_style.color;
-                    underline = highlight_style.underline;
-                    highlight_indices.next();
-                }
-            }
-
-            let last_run: Option<&mut (usize, RunStyle)> = runs.last_mut();
-            let push_new_run = if let Some((last_len, last_style)) = last_run {
-                if font_id == last_style.font_id
-                    && color == last_style.color
-                    && underline == last_style.underline
-                {
-                    *last_len += c.len_utf8();
-                    false
-                } else {
-                    true
-                }
-            } else {
-                true
-            };
-
-            if push_new_run {
-                runs.push((
-                    c.len_utf8(),
-                    RunStyle {
-                        font_id,
-                        color,
-                        underline,
-                    },
-                ));
-            }
-        }
-
-        runs
-    }
-}
-
-impl<V: 'static> Element<V> for Label {
-    type LayoutState = Line;
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        constraint: SizeConstraint,
-        _: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> (Vector2F, Self::LayoutState) {
-        let runs = self.compute_runs();
-        let line = cx.text_layout_cache().layout_str(
-            &self.text,
-            self.style.text.font_size,
-            runs.as_slice(),
-        );
-
-        let size = vec2f(
-            line.width()
-                .ceil()
-                .max(constraint.min.x())
-                .min(constraint.max.x()),
-            cx.font_cache.line_height(self.style.text.font_size),
-        );
-
-        (size, line)
-    }
-
-    fn paint(
-        &mut self,
-        bounds: RectF,
-        visible_bounds: RectF,
-        line: &mut Self::LayoutState,
-        _: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> Self::PaintState {
-        let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
-        line.paint(bounds.origin(), visible_bounds, bounds.size().y(), cx)
-    }
-
-    fn rect_for_text_range(
-        &self,
-        _: Range<usize>,
-        _: RectF,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        _: &V,
-        _: &ViewContext<V>,
-    ) -> Option<RectF> {
-        None
-    }
-
-    fn debug(
-        &self,
-        bounds: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        _: &V,
-        _: &ViewContext<V>,
-    ) -> Value {
-        json!({
-            "type": "Label",
-            "bounds": bounds.to_json(),
-            "text": &self.text,
-            "highlight_indices": self.highlight_indices,
-            "style": self.style.to_json(),
-        })
-    }
-}
-
-impl ToJson for LabelStyle {
-    fn to_json(&self) -> Value {
-        json!({
-            "text": self.text.to_json(),
-            "highlight_text": self.highlight_text
-                .as_ref()
-                .map_or(serde_json::Value::Null, |style| style.to_json())
-        })
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::color::Color;
-    use crate::fonts::{Properties as FontProperties, Weight};
-
-    #[crate::test(self)]
-    fn test_layout_label_with_highlights(cx: &mut crate::AppContext) {
-        let default_style = TextStyle::new(
-            "Menlo",
-            12.,
-            Default::default(),
-            Default::default(),
-            Default::default(),
-            Color::black(),
-            cx.font_cache(),
-        )
-        .unwrap();
-        let highlight_style = TextStyle::new(
-            "Menlo",
-            12.,
-            *FontProperties::new().weight(Weight::BOLD),
-            Default::default(),
-            Default::default(),
-            Color::new(255, 0, 0, 255),
-            cx.font_cache(),
-        )
-        .unwrap();
-        let label = Label::new(
-            ".αβγδε.ⓐⓑⓒⓓⓔ.abcde.".to_string(),
-            LabelStyle {
-                text: default_style.clone(),
-                highlight_text: Some(highlight_style.clone()),
-            },
-        )
-        .with_highlights(vec![
-            ".α".len(),
-            ".αβ".len(),
-            ".αβγδ".len(),
-            ".αβγδε.ⓐ".len(),
-            ".αβγδε.ⓐⓑ".len(),
-        ]);
-
-        let default_run_style = RunStyle {
-            font_id: default_style.font_id,
-            color: default_style.color,
-            underline: default_style.underline,
-        };
-        let highlight_run_style = RunStyle {
-            font_id: highlight_style.font_id,
-            color: highlight_style.color,
-            underline: highlight_style.underline,
-        };
-        let runs = label.compute_runs();
-        assert_eq!(
-            runs.as_slice(),
-            &[
-                (".α".len(), default_run_style),
-                ("βγ".len(), highlight_run_style),
-                ("δ".len(), default_run_style),
-                ("ε".len(), highlight_run_style),
-                (".ⓐ".len(), default_run_style),
-                ("ⓑⓒ".len(), highlight_run_style),
-                ("ⓓⓔ.abcde.".len(), default_run_style),
-            ]
-        );
-    }
-}

crates/gpui/src/elements/list.rs 🔗

@@ -1,68 +1,54 @@
 use crate::{
-    geometry::{
-        rect::RectF,
-        vector::{vec2f, Vector2F},
-    },
-    json::json,
-    AnyElement, Element, MouseRegion, SizeConstraint, ViewContext,
+    point, px, AnyElement, AvailableSpace, BorrowAppContext, Bounds, DispatchPhase, Element,
+    IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style, StyleRefinement, Styled,
+    WindowContext,
 };
-use std::{cell::RefCell, collections::VecDeque, fmt::Debug, ops::Range, rc::Rc};
+use collections::VecDeque;
+use refineable::Refineable as _;
+use std::{cell::RefCell, ops::Range, rc::Rc};
 use sum_tree::{Bias, SumTree};
 
-pub struct List<V> {
-    state: ListState<V>,
+pub fn list(state: ListState) -> List {
+    List {
+        state,
+        style: StyleRefinement::default(),
+    }
 }
 
-pub struct ListState<V>(Rc<RefCell<StateInner<V>>>);
-
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub enum Orientation {
-    Top,
-    Bottom,
+pub struct List {
+    state: ListState,
+    style: StyleRefinement,
 }
 
-struct StateInner<V> {
-    last_layout_width: Option<f32>,
-    render_item: Box<dyn FnMut(&mut V, usize, &mut ViewContext<V>) -> AnyElement<V>>,
-    rendered_range: Range<usize>,
-    items: SumTree<ListItem<V>>,
+#[derive(Clone)]
+pub struct ListState(Rc<RefCell<StateInner>>);
+
+struct StateInner {
+    last_layout_bounds: Option<Bounds<Pixels>>,
+    render_item: Box<dyn FnMut(usize, &mut WindowContext) -> AnyElement>,
+    items: SumTree<ListItem>,
     logical_scroll_top: Option<ListOffset>,
-    orientation: Orientation,
-    overdraw: f32,
+    alignment: ListAlignment,
+    overdraw: Pixels,
     #[allow(clippy::type_complexity)]
-    scroll_handler: Option<Box<dyn FnMut(Range<usize>, usize, &mut V, &mut ViewContext<V>)>>,
+    scroll_handler: Option<Box<dyn FnMut(&ListScrollEvent, &mut WindowContext)>>,
 }
 
-#[derive(Clone, Copy, Debug, Default, PartialEq)]
-pub struct ListOffset {
-    pub item_ix: usize,
-    pub offset_in_item: f32,
-}
-
-enum ListItem<V> {
-    Unrendered,
-    Rendered(Rc<RefCell<AnyElement<V>>>),
-    Removed(f32),
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum ListAlignment {
+    Top,
+    Bottom,
 }
 
-impl<V> Clone for ListItem<V> {
-    fn clone(&self) -> Self {
-        match self {
-            Self::Unrendered => Self::Unrendered,
-            Self::Rendered(element) => Self::Rendered(element.clone()),
-            Self::Removed(height) => Self::Removed(*height),
-        }
-    }
+pub struct ListScrollEvent {
+    pub visible_range: Range<usize>,
+    pub count: usize,
 }
 
-impl<V> Debug for ListItem<V> {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            Self::Unrendered => write!(f, "Unrendered"),
-            Self::Rendered(_) => f.debug_tuple("Rendered").finish(),
-            Self::Removed(height) => f.debug_tuple("Removed").field(height).finish(),
-        }
-    }
+#[derive(Clone)]
+enum ListItem {
+    Unrendered,
+    Rendered { height: Pixels },
 }
 
 #[derive(Clone, Debug, Default, PartialEq)]
@@ -70,7 +56,7 @@ struct ListItemSummary {
     count: usize,
     rendered_count: usize,
     unrendered_count: usize,
-    height: f32,
+    height: Pixels,
 }
 
 #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
@@ -83,286 +69,26 @@ struct RenderedCount(usize);
 struct UnrenderedCount(usize);
 
 #[derive(Clone, Debug, Default)]
-struct Height(f32);
-
-impl<V> List<V> {
-    pub fn new(state: ListState<V>) -> Self {
-        Self { state }
-    }
-}
-
-impl<V: 'static> Element<V> for List<V> {
-    type LayoutState = ListOffset;
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        constraint: SizeConstraint,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> (Vector2F, Self::LayoutState) {
-        let state = &mut *self.state.0.borrow_mut();
-        let size = constraint.max;
-        let mut item_constraint = constraint;
-        item_constraint.min.set_y(0.);
-        item_constraint.max.set_y(f32::INFINITY);
-
-        if cx.refreshing() || state.last_layout_width != Some(size.x()) {
-            state.rendered_range = 0..0;
-            state.items = SumTree::from_iter(
-                (0..state.items.summary().count).map(|_| ListItem::Unrendered),
-                &(),
-            )
-        }
-
-        let old_items = state.items.clone();
-        let mut new_items = SumTree::new();
-        let mut rendered_items = VecDeque::new();
-        let mut rendered_height = 0.;
-        let mut scroll_top = state.logical_scroll_top();
+struct Height(Pixels);
 
-        // Render items after the scroll top, including those in the trailing overdraw.
-        let mut cursor = old_items.cursor::<Count>();
-        cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
-        for (ix, item) in cursor.by_ref().enumerate() {
-            let visible_height = rendered_height - scroll_top.offset_in_item;
-            if visible_height >= size.y() + state.overdraw {
-                break;
-            }
-
-            // Force re-render if the item is visible, but attempt to re-use an existing one
-            // if we are inside the overdraw.
-            let existing_element = if visible_height >= size.y() {
-                Some(item)
-            } else {
-                None
-            };
-            if let Some(element) = state.render_item(
-                scroll_top.item_ix + ix,
-                existing_element,
-                item_constraint,
-                view,
-                cx,
-            ) {
-                rendered_height += element.borrow().size().y();
-                rendered_items.push_back(ListItem::Rendered(element));
-            }
-        }
-
-        // Prepare to start walking upward from the item at the scroll top.
-        cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
-
-        // If the rendered items do not fill the visible region, then adjust
-        // the scroll top upward.
-        if rendered_height - scroll_top.offset_in_item < size.y() {
-            while rendered_height < size.y() {
-                cursor.prev(&());
-                if cursor.item().is_some() {
-                    if let Some(element) =
-                        state.render_item(cursor.start().0, None, item_constraint, view, cx)
-                    {
-                        rendered_height += element.borrow().size().y();
-                        rendered_items.push_front(ListItem::Rendered(element));
-                    }
-                } else {
-                    break;
-                }
-            }
-
-            scroll_top = ListOffset {
-                item_ix: cursor.start().0,
-                offset_in_item: rendered_height - size.y(),
-            };
-
-            match state.orientation {
-                Orientation::Top => {
-                    scroll_top.offset_in_item = scroll_top.offset_in_item.max(0.);
-                    state.logical_scroll_top = Some(scroll_top);
-                }
-                Orientation::Bottom => {
-                    scroll_top = ListOffset {
-                        item_ix: cursor.start().0,
-                        offset_in_item: rendered_height - size.y(),
-                    };
-                    state.logical_scroll_top = None;
-                }
-            };
-        }
-
-        // Render items in the leading overdraw.
-        let mut leading_overdraw = scroll_top.offset_in_item;
-        while leading_overdraw < state.overdraw {
-            cursor.prev(&());
-            if let Some(item) = cursor.item() {
-                if let Some(element) =
-                    state.render_item(cursor.start().0, Some(item), item_constraint, view, cx)
-                {
-                    leading_overdraw += element.borrow().size().y();
-                    rendered_items.push_front(ListItem::Rendered(element));
-                }
-            } else {
-                break;
-            }
-        }
-
-        let new_rendered_range = cursor.start().0..(cursor.start().0 + rendered_items.len());
-
-        let mut cursor = old_items.cursor::<Count>();
-
-        if state.rendered_range.start < new_rendered_range.start {
-            new_items.append(
-                cursor.slice(&Count(state.rendered_range.start), Bias::Right, &()),
-                &(),
-            );
-            let remove_to = state.rendered_range.end.min(new_rendered_range.start);
-            while cursor.start().0 < remove_to {
-                new_items.push(cursor.item().unwrap().remove(), &());
-                cursor.next(&());
-            }
-        }
-        new_items.append(
-            cursor.slice(&Count(new_rendered_range.start), Bias::Right, &()),
-            &(),
-        );
-
-        new_items.extend(rendered_items, &());
-        cursor.seek(&Count(new_rendered_range.end), Bias::Right, &());
-
-        if new_rendered_range.end < state.rendered_range.start {
-            new_items.append(
-                cursor.slice(&Count(state.rendered_range.start), Bias::Right, &()),
-                &(),
-            );
-        }
-        while cursor.start().0 < state.rendered_range.end {
-            new_items.push(cursor.item().unwrap().remove(), &());
-            cursor.next(&());
-        }
-
-        new_items.append(cursor.suffix(&()), &());
-
-        state.items = new_items;
-        state.rendered_range = new_rendered_range;
-        state.last_layout_width = Some(size.x());
-        (size, scroll_top)
-    }
-
-    fn paint(
-        &mut self,
-        bounds: RectF,
-        visible_bounds: RectF,
-        scroll_top: &mut ListOffset,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) {
-        let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
-        cx.scene().push_layer(Some(visible_bounds));
-        let view_id = cx.view_id();
-        cx.scene()
-            .push_mouse_region(MouseRegion::new::<Self>(view_id, 0, bounds).on_scroll({
-                let state = self.state.clone();
-                let height = bounds.height();
-                let scroll_top = scroll_top.clone();
-                move |e, view, cx| {
-                    state.0.borrow_mut().scroll(
-                        &scroll_top,
-                        height,
-                        *e.platform_event.delta.raw(),
-                        e.platform_event.delta.precise(),
-                        view,
-                        cx,
-                    )
-                }
-            }));
-
-        let state = &mut *self.state.0.borrow_mut();
-        for (element, origin) in state.visible_elements(bounds, scroll_top) {
-            element.borrow_mut().paint(origin, visible_bounds, view, cx);
-        }
-
-        cx.scene().pop_layer();
-    }
-
-    fn rect_for_text_range(
-        &self,
-        range_utf16: Range<usize>,
-        bounds: RectF,
-        _: RectF,
-        scroll_top: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> Option<RectF> {
-        let state = self.state.0.borrow();
-        let mut item_origin = bounds.origin() - vec2f(0., scroll_top.offset_in_item);
-        let mut cursor = state.items.cursor::<Count>();
-        cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
-        while let Some(item) = cursor.item() {
-            if item_origin.y() > bounds.max_y() {
-                break;
-            }
-
-            if let ListItem::Rendered(element) = item {
-                if let Some(rect) =
-                    element
-                        .borrow()
-                        .rect_for_text_range(range_utf16.clone(), view, cx)
-                {
-                    return Some(rect);
-                }
-
-                item_origin.set_y(item_origin.y() + element.borrow().size().y());
-                cursor.next(&());
-            } else {
-                unreachable!();
-            }
-        }
-
-        None
-    }
-
-    fn debug(
-        &self,
-        bounds: RectF,
-        scroll_top: &Self::LayoutState,
-        _: &(),
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> serde_json::Value {
-        let state = self.state.0.borrow_mut();
-        let visible_elements = state
-            .visible_elements(bounds, scroll_top)
-            .map(|e| e.0.borrow().debug(view, cx))
-            .collect::<Vec<_>>();
-        let visible_range = scroll_top.item_ix..(scroll_top.item_ix + visible_elements.len());
-        json!({
-            "visible_range": visible_range,
-            "visible_elements": visible_elements,
-            "scroll_top": state.logical_scroll_top.map(|top| (top.item_ix, top.offset_in_item)),
-        })
-    }
-}
-
-impl<V: 'static> ListState<V> {
-    pub fn new<D, F>(
+impl ListState {
+    pub fn new<F>(
         element_count: usize,
-        orientation: Orientation,
-        overdraw: f32,
-        mut render_item: F,
+        orientation: ListAlignment,
+        overdraw: Pixels,
+        render_item: F,
     ) -> Self
     where
-        D: Element<V>,
-        F: 'static + FnMut(&mut V, usize, &mut ViewContext<V>) -> D,
+        F: 'static + FnMut(usize, &mut WindowContext) -> AnyElement,
     {
         let mut items = SumTree::new();
         items.extend((0..element_count).map(|_| ListItem::Unrendered), &());
         Self(Rc::new(RefCell::new(StateInner {
-            last_layout_width: None,
-            render_item: Box::new(move |view, ix, cx| render_item(view, ix, cx).into_any()),
-            rendered_range: 0..0,
+            last_layout_bounds: None,
+            render_item: Box::new(render_item),
             items,
             logical_scroll_top: None,
-            orientation,
+            alignment: orientation,
             overdraw,
             scroll_handler: None,
         })))
@@ -370,7 +96,6 @@ impl<V: 'static> ListState<V> {
 
     pub fn reset(&self, element_count: usize) {
         let state = &mut *self.0.borrow_mut();
-        state.rendered_range = 0..0;
         state.logical_scroll_top = None;
         state.items = SumTree::new();
         state
@@ -392,22 +117,12 @@ impl<V: 'static> ListState<V> {
         {
             if old_range.contains(item_ix) {
                 *item_ix = old_range.start;
-                *offset_in_item = 0.;
+                *offset_in_item = px(0.);
             } else if old_range.end <= *item_ix {
                 *item_ix = *item_ix - (old_range.end - old_range.start) + count;
             }
         }
 
-        let new_end = old_range.start + count;
-        if old_range.start < state.rendered_range.start {
-            state.rendered_range.start =
-                new_end + state.rendered_range.start.saturating_sub(old_range.end);
-        }
-        if old_range.start < state.rendered_range.end {
-            state.rendered_range.end =
-                new_end + state.rendered_range.end.saturating_sub(old_range.end);
-        }
-
         let mut old_heights = state.items.cursor::<Count>();
         let mut new_heights = old_heights.slice(&Count(old_range.start), Bias::Right, &());
         old_heights.seek_forward(&Count(old_range.end), Bias::Right, &());
@@ -419,8 +134,8 @@ impl<V: 'static> ListState<V> {
     }
 
     pub fn set_scroll_handler(
-        &mut self,
-        handler: impl FnMut(Range<usize>, usize, &mut V, &mut ViewContext<V>) + 'static,
+        &self,
+        handler: impl FnMut(&ListScrollEvent, &mut WindowContext) + 'static,
     ) {
         self.0.borrow_mut().scroll_handler = Some(Box::new(handler))
     }
@@ -434,91 +149,92 @@ impl<V: 'static> ListState<V> {
         let item_count = state.items.summary().count;
         if scroll_top.item_ix >= item_count {
             scroll_top.item_ix = item_count;
-            scroll_top.offset_in_item = 0.;
+            scroll_top.offset_in_item = px(0.);
         }
         state.logical_scroll_top = Some(scroll_top);
     }
-}
 
-impl<V> Clone for ListState<V> {
-    fn clone(&self) -> Self {
-        Self(self.0.clone())
-    }
-}
+    pub fn scroll_to_reveal_item(&self, ix: usize) {
+        let state = &mut *self.0.borrow_mut();
+        let mut scroll_top = state.logical_scroll_top();
+        let height = state
+            .last_layout_bounds
+            .map_or(px(0.), |bounds| bounds.size.height);
 
-impl<V: 'static> StateInner<V> {
-    fn render_item(
-        &mut self,
-        ix: usize,
-        existing_element: Option<&ListItem<V>>,
-        constraint: SizeConstraint,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> Option<Rc<RefCell<AnyElement<V>>>> {
-        if let Some(ListItem::Rendered(element)) = existing_element {
-            Some(element.clone())
+        if ix <= scroll_top.item_ix {
+            scroll_top.item_ix = ix;
+            scroll_top.offset_in_item = px(0.);
         } else {
-            let mut element = (self.render_item)(view, ix, cx);
-            element.layout(constraint, view, cx);
-            Some(Rc::new(RefCell::new(element)))
+            let mut cursor = state.items.cursor::<ListItemSummary>();
+            cursor.seek(&Count(ix + 1), Bias::Right, &());
+            let bottom = cursor.start().height;
+            let goal_top = px(0.).max(bottom - height);
+
+            cursor.seek(&Height(goal_top), Bias::Left, &());
+            let start_ix = cursor.start().count;
+            let start_item_top = cursor.start().height;
+
+            if start_ix >= scroll_top.item_ix {
+                scroll_top.item_ix = start_ix;
+                scroll_top.offset_in_item = goal_top - start_item_top;
+            }
         }
-    }
 
-    fn visible_range(&self, height: f32, scroll_top: &ListOffset) -> Range<usize> {
-        let mut cursor = self.items.cursor::<ListItemSummary>();
-        cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
-        let start_y = cursor.start().height + scroll_top.offset_in_item;
-        cursor.seek_forward(&Height(start_y + height), Bias::Left, &());
-        scroll_top.item_ix..cursor.start().count + 1
+        state.logical_scroll_top = Some(scroll_top);
     }
 
-    fn visible_elements<'a>(
-        &'a self,
-        bounds: RectF,
-        scroll_top: &ListOffset,
-    ) -> impl Iterator<Item = (Rc<RefCell<AnyElement<V>>>, Vector2F)> + 'a {
-        let mut item_origin = bounds.origin() - vec2f(0., scroll_top.offset_in_item);
-        let mut cursor = self.items.cursor::<Count>();
-        cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
-        std::iter::from_fn(move || {
-            while let Some(item) = cursor.item() {
-                if item_origin.y() > bounds.max_y() {
-                    break;
-                }
+    /// Get the bounds for the given item in window coordinates.
+    pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
+        let state = &*self.0.borrow();
+        let bounds = state.last_layout_bounds.unwrap_or_default();
+        let scroll_top = state.logical_scroll_top();
 
-                if let ListItem::Rendered(element) = item {
-                    let result = (element.clone(), item_origin);
-                    item_origin.set_y(item_origin.y() + element.borrow().size().y());
-                    cursor.next(&());
-                    return Some(result);
-                }
+        if ix < scroll_top.item_ix {
+            return None;
+        }
+
+        let mut cursor = state.items.cursor::<(Count, Height)>();
+        cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
 
-                cursor.next(&());
+        let scroll_top = cursor.start().1 .0 + scroll_top.offset_in_item;
+
+        cursor.seek_forward(&Count(ix), Bias::Right, &());
+        if let Some(&ListItem::Rendered { height }) = cursor.item() {
+            let &(Count(count), Height(top)) = cursor.start();
+            if count == ix {
+                let top = bounds.top() + top - scroll_top;
+                return Some(Bounds::from_corners(
+                    point(bounds.left(), top),
+                    point(bounds.right(), top + height),
+                ));
             }
+        }
+        None
+    }
+}
 
-            None
-        })
+impl StateInner {
+    fn visible_range(&self, height: Pixels, scroll_top: &ListOffset) -> Range<usize> {
+        let mut cursor = self.items.cursor::<ListItemSummary>();
+        cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
+        let start_y = cursor.start().height + scroll_top.offset_in_item;
+        cursor.seek_forward(&Height(start_y + height), Bias::Left, &());
+        scroll_top.item_ix..cursor.start().count + 1
     }
 
     fn scroll(
         &mut self,
         scroll_top: &ListOffset,
-        height: f32,
-        mut delta: Vector2F,
-        precise: bool,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
+        height: Pixels,
+        delta: Point<Pixels>,
+        cx: &mut WindowContext,
     ) {
-        if !precise {
-            delta *= 20.;
-        }
-
-        let scroll_max = (self.items.summary().height - height).max(0.);
-        let new_scroll_top = (self.scroll_top(scroll_top) - delta.y())
-            .max(0.)
+        let scroll_max = (self.items.summary().height - height).max(px(0.));
+        let new_scroll_top = (self.scroll_top(scroll_top) - delta.y)
+            .max(px(0.))
             .min(scroll_max);
 
-        if self.orientation == Orientation::Bottom && new_scroll_top == scroll_max {
+        if self.alignment == ListAlignment::Bottom && new_scroll_top == scroll_max {
             self.logical_scroll_top = None;
         } else {
             let mut cursor = self.items.cursor::<ListItemSummary>();
@@ -534,9 +250,10 @@ impl<V: 'static> StateInner<V> {
         if self.scroll_handler.is_some() {
             let visible_range = self.visible_range(height, scroll_top);
             self.scroll_handler.as_mut().unwrap()(
-                visible_range,
-                self.items.summary().count,
-                view,
+                &ListScrollEvent {
+                    visible_range,
+                    count: self.items.summary().count,
+                },
                 cx,
             );
         }
@@ -546,36 +263,235 @@ impl<V: 'static> StateInner<V> {
 
     fn logical_scroll_top(&self) -> ListOffset {
         self.logical_scroll_top
-            .unwrap_or_else(|| match self.orientation {
-                Orientation::Top => ListOffset {
+            .unwrap_or_else(|| match self.alignment {
+                ListAlignment::Top => ListOffset {
                     item_ix: 0,
-                    offset_in_item: 0.,
+                    offset_in_item: px(0.),
                 },
-                Orientation::Bottom => ListOffset {
+                ListAlignment::Bottom => ListOffset {
                     item_ix: self.items.summary().count,
-                    offset_in_item: 0.,
+                    offset_in_item: px(0.),
                 },
             })
     }
 
-    fn scroll_top(&self, logical_scroll_top: &ListOffset) -> f32 {
+    fn scroll_top(&self, logical_scroll_top: &ListOffset) -> Pixels {
         let mut cursor = self.items.cursor::<ListItemSummary>();
         cursor.seek(&Count(logical_scroll_top.item_ix), Bias::Right, &());
         cursor.start().height + logical_scroll_top.offset_in_item
     }
 }
 
-impl<V> ListItem<V> {
-    fn remove(&self) -> Self {
+impl std::fmt::Debug for ListItem {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         match self {
-            ListItem::Unrendered => ListItem::Unrendered,
-            ListItem::Rendered(element) => ListItem::Removed(element.borrow().size().y()),
-            ListItem::Removed(height) => ListItem::Removed(*height),
+            Self::Unrendered => write!(f, "Unrendered"),
+            Self::Rendered { height, .. } => {
+                f.debug_struct("Rendered").field("height", height).finish()
+            }
+        }
+    }
+}
+
+#[derive(Debug, Clone, Copy, Default)]
+pub struct ListOffset {
+    pub item_ix: usize,
+    pub offset_in_item: Pixels,
+}
+
+impl Element for List {
+    type State = ();
+
+    fn request_layout(
+        &mut self,
+        _state: Option<Self::State>,
+        cx: &mut crate::WindowContext,
+    ) -> (crate::LayoutId, Self::State) {
+        let mut style = Style::default();
+        style.refine(&self.style);
+        let layout_id = cx.with_text_style(style.text_style().cloned(), |cx| {
+            cx.request_layout(&style, None)
+        });
+        (layout_id, ())
+    }
+
+    fn paint(
+        &mut self,
+        bounds: crate::Bounds<crate::Pixels>,
+        _state: &mut Self::State,
+        cx: &mut crate::WindowContext,
+    ) {
+        let state = &mut *self.state.0.borrow_mut();
+
+        // If the width of the list has changed, invalidate all cached item heights
+        if state.last_layout_bounds.map_or(true, |last_bounds| {
+            last_bounds.size.width != bounds.size.width
+        }) {
+            state.items = SumTree::from_iter(
+                (0..state.items.summary().count).map(|_| ListItem::Unrendered),
+                &(),
+            )
+        }
+
+        let old_items = state.items.clone();
+        let mut measured_items = VecDeque::new();
+        let mut item_elements = VecDeque::new();
+        let mut rendered_height = px(0.);
+        let mut scroll_top = state.logical_scroll_top();
+
+        let available_item_space = Size {
+            width: AvailableSpace::Definite(bounds.size.width),
+            height: AvailableSpace::MinContent,
+        };
+
+        // Render items after the scroll top, including those in the trailing overdraw
+        let mut cursor = old_items.cursor::<Count>();
+        cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
+        for (ix, item) in cursor.by_ref().enumerate() {
+            let visible_height = rendered_height - scroll_top.offset_in_item;
+            if visible_height >= bounds.size.height + state.overdraw {
+                break;
+            }
+
+            // Use the previously cached height if available
+            let mut height = if let ListItem::Rendered { height } = item {
+                Some(*height)
+            } else {
+                None
+            };
+
+            // If we're within the visible area or the height wasn't cached, render and measure the item's element
+            if visible_height < bounds.size.height || height.is_none() {
+                let mut element = (state.render_item)(scroll_top.item_ix + ix, cx);
+                let element_size = element.measure(available_item_space, cx);
+                height = Some(element_size.height);
+                if visible_height < bounds.size.height {
+                    item_elements.push_back(element);
+                }
+            }
+
+            let height = height.unwrap();
+            rendered_height += height;
+            measured_items.push_back(ListItem::Rendered { height });
+        }
+
+        // Prepare to start walking upward from the item at the scroll top.
+        cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
+
+        // If the rendered items do not fill the visible region, then adjust
+        // the scroll top upward.
+        if rendered_height - scroll_top.offset_in_item < bounds.size.height {
+            while rendered_height < bounds.size.height {
+                cursor.prev(&());
+                if cursor.item().is_some() {
+                    let mut element = (state.render_item)(cursor.start().0, cx);
+                    let element_size = element.measure(available_item_space, cx);
+
+                    rendered_height += element_size.height;
+                    measured_items.push_front(ListItem::Rendered {
+                        height: element_size.height,
+                    });
+                    item_elements.push_front(element)
+                } else {
+                    break;
+                }
+            }
+
+            scroll_top = ListOffset {
+                item_ix: cursor.start().0,
+                offset_in_item: rendered_height - bounds.size.height,
+            };
+
+            match state.alignment {
+                ListAlignment::Top => {
+                    scroll_top.offset_in_item = scroll_top.offset_in_item.max(px(0.));
+                    state.logical_scroll_top = Some(scroll_top);
+                }
+                ListAlignment::Bottom => {
+                    scroll_top = ListOffset {
+                        item_ix: cursor.start().0,
+                        offset_in_item: rendered_height - bounds.size.height,
+                    };
+                    state.logical_scroll_top = None;
+                }
+            };
+        }
+
+        // Measure items in the leading overdraw
+        let mut leading_overdraw = scroll_top.offset_in_item;
+        while leading_overdraw < state.overdraw {
+            cursor.prev(&());
+            if let Some(item) = cursor.item() {
+                let height = if let ListItem::Rendered { height } = item {
+                    *height
+                } else {
+                    let mut element = (state.render_item)(cursor.start().0, cx);
+                    element.measure(available_item_space, cx).height
+                };
+
+                leading_overdraw += height;
+                measured_items.push_front(ListItem::Rendered { height });
+            } else {
+                break;
+            }
+        }
+
+        let measured_range = cursor.start().0..(cursor.start().0 + measured_items.len());
+        let mut cursor = old_items.cursor::<Count>();
+        let mut new_items = cursor.slice(&Count(measured_range.start), Bias::Right, &());
+        new_items.extend(measured_items, &());
+        cursor.seek(&Count(measured_range.end), Bias::Right, &());
+        new_items.append(cursor.suffix(&()), &());
+
+        // Paint the visible items
+        let mut item_origin = bounds.origin;
+        item_origin.y -= scroll_top.offset_in_item;
+        for item_element in &mut item_elements {
+            let item_height = item_element.measure(available_item_space, cx).height;
+            item_element.draw(item_origin, available_item_space, cx);
+            item_origin.y += item_height;
         }
+
+        state.items = new_items;
+        state.last_layout_bounds = Some(bounds);
+
+        let list_state = self.state.clone();
+        let height = bounds.size.height;
+        cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
+            if phase == DispatchPhase::Bubble
+                && bounds.contains(&event.position)
+                && cx.was_top_layer(&event.position, cx.stacking_order())
+            {
+                list_state.0.borrow_mut().scroll(
+                    &scroll_top,
+                    height,
+                    event.delta.pixel_delta(px(20.)),
+                    cx,
+                )
+            }
+        });
+    }
+}
+
+impl IntoElement for List {
+    type Element = Self;
+
+    fn element_id(&self) -> Option<crate::ElementId> {
+        None
+    }
+
+    fn into_element(self) -> Self::Element {
+        self
     }
 }
 
-impl<V> sum_tree::Item for ListItem<V> {
+impl Styled for List {
+    fn style(&mut self) -> &mut StyleRefinement {
+        &mut self.style
+    }
+}
+
+impl sum_tree::Item for ListItem {
     type Summary = ListItemSummary;
 
     fn summary(&self) -> Self::Summary {
@@ -584,18 +500,12 @@ impl<V> sum_tree::Item for ListItem<V> {
                 count: 1,
                 rendered_count: 0,
                 unrendered_count: 1,
-                height: 0.,
+                height: px(0.),
             },
-            ListItem::Rendered(element) => ListItemSummary {
+            ListItem::Rendered { height } => ListItemSummary {
                 count: 1,
                 rendered_count: 1,
                 unrendered_count: 0,
-                height: element.borrow().size().y(),
-            },
-            ListItem::Removed(height) => ListItemSummary {
-                count: 1,
-                rendered_count: 0,
-                unrendered_count: 1,
                 height: *height,
             },
         }
@@ -648,339 +558,3 @@ impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Height {
         self.0.partial_cmp(&other.height).unwrap()
     }
 }
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::{elements::Empty, geometry::vector::vec2f, Entity};
-    use rand::prelude::*;
-    use std::env;
-
-    #[crate::test(self)]
-    fn test_layout(cx: &mut crate::AppContext) {
-        cx.add_window(Default::default(), |cx| {
-            let mut view = TestView;
-            let constraint = SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.));
-            let elements = Rc::new(RefCell::new(vec![(0, 20.), (1, 30.), (2, 100.)]));
-            let state = ListState::new(elements.borrow().len(), Orientation::Top, 1000.0, {
-                let elements = elements.clone();
-                move |_, ix, _| {
-                    let (id, height) = elements.borrow()[ix];
-                    TestElement::new(id, height).into_any()
-                }
-            });
-
-            let mut list = List::new(state.clone());
-            let (size, _) = list.layout(constraint, &mut view, cx);
-            assert_eq!(size, vec2f(100., 40.));
-            assert_eq!(
-                state.0.borrow().items.summary().clone(),
-                ListItemSummary {
-                    count: 3,
-                    rendered_count: 3,
-                    unrendered_count: 0,
-                    height: 150.
-                }
-            );
-
-            state.0.borrow_mut().scroll(
-                &ListOffset {
-                    item_ix: 0,
-                    offset_in_item: 0.,
-                },
-                40.,
-                vec2f(0., -54.),
-                true,
-                &mut view,
-                cx,
-            );
-
-            let (_, logical_scroll_top) = list.layout(constraint, &mut view, cx);
-            assert_eq!(
-                logical_scroll_top,
-                ListOffset {
-                    item_ix: 2,
-                    offset_in_item: 4.
-                }
-            );
-            assert_eq!(state.0.borrow().scroll_top(&logical_scroll_top), 54.);
-
-            elements.borrow_mut().splice(1..2, vec![(3, 40.), (4, 50.)]);
-            elements.borrow_mut().push((5, 60.));
-            state.splice(1..2, 2);
-            state.splice(4..4, 1);
-            assert_eq!(
-                state.0.borrow().items.summary().clone(),
-                ListItemSummary {
-                    count: 5,
-                    rendered_count: 2,
-                    unrendered_count: 3,
-                    height: 120.
-                }
-            );
-
-            let (size, logical_scroll_top) = list.layout(constraint, &mut view, cx);
-            assert_eq!(size, vec2f(100., 40.));
-            assert_eq!(
-                state.0.borrow().items.summary().clone(),
-                ListItemSummary {
-                    count: 5,
-                    rendered_count: 5,
-                    unrendered_count: 0,
-                    height: 270.
-                }
-            );
-            assert_eq!(
-                logical_scroll_top,
-                ListOffset {
-                    item_ix: 3,
-                    offset_in_item: 4.
-                }
-            );
-            assert_eq!(state.0.borrow().scroll_top(&logical_scroll_top), 114.);
-
-            view
-        });
-    }
-
-    #[crate::test(self, iterations = 10)]
-    fn test_random(cx: &mut crate::AppContext, mut rng: StdRng) {
-        let operations = env::var("OPERATIONS")
-            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
-            .unwrap_or(10);
-
-        cx.add_window(Default::default(), |cx| {
-            let mut view = TestView;
-
-            let mut next_id = 0;
-            let elements = Rc::new(RefCell::new(
-                (0..rng.gen_range(0..=20))
-                    .map(|_| {
-                        let id = next_id;
-                        next_id += 1;
-                        (id, rng.gen_range(0..=200) as f32 / 2.0)
-                    })
-                    .collect::<Vec<_>>(),
-            ));
-            let orientation = *[Orientation::Top, Orientation::Bottom]
-                .choose(&mut rng)
-                .unwrap();
-            let overdraw = rng.gen_range(1..=100) as f32;
-
-            let state = ListState::new(elements.borrow().len(), orientation, overdraw, {
-                let elements = elements.clone();
-                move |_, ix, _| {
-                    let (id, height) = elements.borrow()[ix];
-                    TestElement::new(id, height).into_any()
-                }
-            });
-
-            let mut width = rng.gen_range(0..=2000) as f32 / 2.;
-            let mut height = rng.gen_range(0..=2000) as f32 / 2.;
-            log::info!("orientation: {:?}", orientation);
-            log::info!("overdraw: {}", overdraw);
-            log::info!("elements: {:?}", elements.borrow());
-            log::info!("size: ({:?}, {:?})", width, height);
-            log::info!("==================");
-
-            let mut last_logical_scroll_top = None;
-            for _ in 0..operations {
-                match rng.gen_range(0..=100) {
-                    0..=29 if last_logical_scroll_top.is_some() => {
-                        let delta = vec2f(0., rng.gen_range(-overdraw..=overdraw));
-                        log::info!(
-                            "Scrolling by {:?}, previous scroll top: {:?}",
-                            delta,
-                            last_logical_scroll_top.unwrap()
-                        );
-                        state.0.borrow_mut().scroll(
-                            last_logical_scroll_top.as_ref().unwrap(),
-                            height,
-                            delta,
-                            true,
-                            &mut view,
-                            cx,
-                        );
-                    }
-                    30..=34 => {
-                        width = rng.gen_range(0..=2000) as f32 / 2.;
-                        log::info!("changing width: {:?}", width);
-                    }
-                    35..=54 => {
-                        height = rng.gen_range(0..=1000) as f32 / 2.;
-                        log::info!("changing height: {:?}", height);
-                    }
-                    _ => {
-                        let mut elements = elements.borrow_mut();
-                        let end_ix = rng.gen_range(0..=elements.len());
-                        let start_ix = rng.gen_range(0..=end_ix);
-                        let new_elements = (0..rng.gen_range(0..10))
-                            .map(|_| {
-                                let id = next_id;
-                                next_id += 1;
-                                (id, rng.gen_range(0..=200) as f32 / 2.)
-                            })
-                            .collect::<Vec<_>>();
-                        log::info!("splice({:?}, {:?})", start_ix..end_ix, new_elements);
-                        state.splice(start_ix..end_ix, new_elements.len());
-                        elements.splice(start_ix..end_ix, new_elements);
-                        for (ix, item) in state.0.borrow().items.cursor::<()>().enumerate() {
-                            if let ListItem::Rendered(element) = item {
-                                let (expected_id, _) = elements[ix];
-                                element.borrow().with_metadata(|metadata: Option<&usize>| {
-                                    assert_eq!(*metadata.unwrap(), expected_id);
-                                });
-                            }
-                        }
-                    }
-                }
-
-                let mut list = List::new(state.clone());
-                let window_size = vec2f(width, height);
-                let (size, logical_scroll_top) = list.layout(
-                    SizeConstraint::new(vec2f(0., 0.), window_size),
-                    &mut view,
-                    cx,
-                );
-                assert_eq!(size, window_size);
-                last_logical_scroll_top = Some(logical_scroll_top);
-
-                let state = state.0.borrow();
-                log::info!("items {:?}", state.items.items(&()));
-
-                let scroll_top = state.scroll_top(&logical_scroll_top);
-                let rendered_top = (scroll_top - overdraw).max(0.);
-                let rendered_bottom = scroll_top + height + overdraw;
-                let mut item_top = 0.;
-
-                log::info!(
-                    "rendered top {:?}, rendered bottom {:?}, scroll top {:?}",
-                    rendered_top,
-                    rendered_bottom,
-                    scroll_top,
-                );
-
-                let mut first_rendered_element_top = None;
-                let mut last_rendered_element_bottom = None;
-                assert_eq!(state.items.summary().count, elements.borrow().len());
-                for (ix, item) in state.items.cursor::<()>().enumerate() {
-                    match item {
-                        ListItem::Unrendered => {
-                            let item_bottom = item_top;
-                            assert!(item_bottom <= rendered_top || item_top >= rendered_bottom);
-                            item_top = item_bottom;
-                        }
-                        ListItem::Removed(height) => {
-                            let (id, expected_height) = elements.borrow()[ix];
-                            assert_eq!(
-                                *height, expected_height,
-                                "element {} height didn't match",
-                                id
-                            );
-                            let item_bottom = item_top + height;
-                            assert!(item_bottom <= rendered_top || item_top >= rendered_bottom);
-                            item_top = item_bottom;
-                        }
-                        ListItem::Rendered(element) => {
-                            let (expected_id, expected_height) = elements.borrow()[ix];
-                            let element = element.borrow();
-                            element.with_metadata(|metadata: Option<&usize>| {
-                                assert_eq!(*metadata.unwrap(), expected_id);
-                            });
-                            assert_eq!(element.size().y(), expected_height);
-                            let item_bottom = item_top + element.size().y();
-                            first_rendered_element_top.get_or_insert(item_top);
-                            last_rendered_element_bottom = Some(item_bottom);
-                            assert!(item_bottom > rendered_top || item_top < rendered_bottom);
-                            item_top = item_bottom;
-                        }
-                    }
-                }
-
-                match orientation {
-                    Orientation::Top => {
-                        if let Some(first_rendered_element_top) = first_rendered_element_top {
-                            assert!(first_rendered_element_top <= scroll_top);
-                        }
-                    }
-                    Orientation::Bottom => {
-                        if let Some(last_rendered_element_bottom) = last_rendered_element_bottom {
-                            assert!(last_rendered_element_bottom >= scroll_top + height);
-                        }
-                    }
-                }
-            }
-
-            view
-        });
-    }
-
-    struct TestView;
-
-    impl Entity for TestView {
-        type Event = ();
-    }
-
-    impl crate::View for TestView {
-        fn ui_name() -> &'static str {
-            "TestView"
-        }
-
-        fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
-            Empty::new().into_any()
-        }
-    }
-
-    struct TestElement {
-        id: usize,
-        size: Vector2F,
-    }
-
-    impl TestElement {
-        fn new(id: usize, height: f32) -> Self {
-            Self {
-                id,
-                size: vec2f(100., height),
-            }
-        }
-    }
-
-    impl<V: 'static> Element<V> for TestElement {
-        type LayoutState = ();
-        type PaintState = ();
-
-        fn layout(
-            &mut self,
-            _: SizeConstraint,
-            _: &mut V,
-            _: &mut ViewContext<V>,
-        ) -> (Vector2F, ()) {
-            (self.size, ())
-        }
-
-        fn paint(&mut self, _: RectF, _: RectF, _: &mut (), _: &mut V, _: &mut ViewContext<V>) {
-            unimplemented!()
-        }
-
-        fn rect_for_text_range(
-            &self,
-            _: Range<usize>,
-            _: RectF,
-            _: RectF,
-            _: &Self::LayoutState,
-            _: &Self::PaintState,
-            _: &V,
-            _: &ViewContext<V>,
-        ) -> Option<RectF> {
-            unimplemented!()
-        }
-
-        fn debug(&self, _: RectF, _: &(), _: &(), _: &V, _: &ViewContext<V>) -> serde_json::Value {
-            self.id.into()
-        }
-
-        fn metadata(&self) -> Option<&dyn std::any::Any> {
-            Some(&self.id)
-        }
-    }
-}

crates/gpui/src/elements/mouse_event_handler.rs 🔗

@@ -1,323 +0,0 @@
-use super::Padding;
-use crate::{
-    geometry::{
-        rect::RectF,
-        vector::{vec2f, Vector2F},
-    },
-    platform::CursorStyle,
-    platform::MouseButton,
-    scene::{
-        CursorRegion, HandlerSet, MouseClick, MouseClickOut, MouseDown, MouseDownOut, MouseDrag,
-        MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
-    },
-    AnyElement, Element, EventContext, MouseRegion, MouseState, SizeConstraint, TypeTag,
-    ViewContext,
-};
-use serde_json::json;
-use std::ops::Range;
-
-pub struct MouseEventHandler<V: 'static> {
-    child: AnyElement<V>,
-    region_id: usize,
-    cursor_style: Option<CursorStyle>,
-    handlers: HandlerSet,
-    hoverable: bool,
-    notify_on_hover: bool,
-    notify_on_click: bool,
-    above: bool,
-    padding: Padding,
-    tag: TypeTag,
-}
-
-/// Element which provides a render_child callback with a MouseState and paints a mouse
-/// region under (or above) it for easy mouse event handling.
-impl<V: 'static> MouseEventHandler<V> {
-    pub fn for_child<Tag: 'static>(child: impl Element<V>, region_id: usize) -> Self {
-        Self {
-            child: child.into_any(),
-            region_id,
-            cursor_style: None,
-            handlers: Default::default(),
-            notify_on_hover: false,
-            notify_on_click: false,
-            hoverable: false,
-            above: false,
-            padding: Default::default(),
-            tag: TypeTag::new::<Tag>(),
-        }
-    }
-
-    pub fn new<Tag: 'static, E>(
-        region_id: usize,
-        cx: &mut ViewContext<V>,
-        render_child: impl FnOnce(&mut MouseState, &mut ViewContext<V>) -> E,
-    ) -> Self
-    where
-        E: Element<V>,
-    {
-        let mut mouse_state = cx.mouse_state_dynamic(TypeTag::new::<Tag>(), region_id);
-        let child = render_child(&mut mouse_state, cx).into_any();
-        let notify_on_hover = mouse_state.accessed_hovered();
-        let notify_on_click = mouse_state.accessed_clicked();
-        Self {
-            child,
-            region_id,
-            cursor_style: None,
-            handlers: Default::default(),
-            notify_on_hover,
-            notify_on_click,
-            hoverable: true,
-            above: false,
-            padding: Default::default(),
-            tag: TypeTag::new::<Tag>(),
-        }
-    }
-
-    pub fn new_dynamic(
-        tag: TypeTag,
-        region_id: usize,
-        cx: &mut ViewContext<V>,
-        render_child: impl FnOnce(&mut MouseState, &mut ViewContext<V>) -> AnyElement<V>,
-    ) -> Self {
-        let mut mouse_state = cx.mouse_state_dynamic(tag, region_id);
-        let child = render_child(&mut mouse_state, cx);
-        let notify_on_hover = mouse_state.accessed_hovered();
-        let notify_on_click = mouse_state.accessed_clicked();
-        Self {
-            child,
-            region_id,
-            cursor_style: None,
-            handlers: Default::default(),
-            notify_on_hover,
-            notify_on_click,
-            hoverable: true,
-            above: false,
-            padding: Default::default(),
-            tag,
-        }
-    }
-
-    /// Modifies the MouseEventHandler to render the MouseRegion above the child element. Useful
-    /// for drag and drop handling and similar events which should be captured before the child
-    /// gets the opportunity
-    pub fn above<Tag: 'static, D>(
-        region_id: usize,
-        cx: &mut ViewContext<V>,
-        render_child: impl FnOnce(&mut MouseState, &mut ViewContext<V>) -> D,
-    ) -> Self
-    where
-        D: Element<V>,
-    {
-        let mut handler = Self::new::<Tag, _>(region_id, cx, render_child);
-        handler.above = true;
-        handler
-    }
-
-    pub fn with_cursor_style(mut self, cursor: CursorStyle) -> Self {
-        self.cursor_style = Some(cursor);
-        self
-    }
-
-    pub fn capture_all(mut self) -> Self {
-        self.handlers = HandlerSet::capture_all();
-        self
-    }
-
-    pub fn on_move(
-        mut self,
-        handler: impl Fn(MouseMove, &mut V, &mut EventContext<V>) + 'static,
-    ) -> Self {
-        self.handlers = self.handlers.on_move(handler);
-        self
-    }
-
-    pub fn on_move_out(
-        mut self,
-        handler: impl Fn(MouseMoveOut, &mut V, &mut EventContext<V>) + 'static,
-    ) -> Self {
-        self.handlers = self.handlers.on_move_out(handler);
-        self
-    }
-
-    pub fn on_down(
-        mut self,
-        button: MouseButton,
-        handler: impl Fn(MouseDown, &mut V, &mut EventContext<V>) + 'static,
-    ) -> Self {
-        self.handlers = self.handlers.on_down(button, handler);
-        self
-    }
-
-    pub fn on_up(
-        mut self,
-        button: MouseButton,
-        handler: impl Fn(MouseUp, &mut V, &mut EventContext<V>) + 'static,
-    ) -> Self {
-        self.handlers = self.handlers.on_up(button, handler);
-        self
-    }
-
-    pub fn on_click(
-        mut self,
-        button: MouseButton,
-        handler: impl Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
-    ) -> Self {
-        self.handlers = self.handlers.on_click(button, handler);
-        self
-    }
-
-    pub fn on_click_out(
-        mut self,
-        button: MouseButton,
-        handler: impl Fn(MouseClickOut, &mut V, &mut EventContext<V>) + 'static,
-    ) -> Self {
-        self.handlers = self.handlers.on_click_out(button, handler);
-        self
-    }
-
-    pub fn on_down_out(
-        mut self,
-        button: MouseButton,
-        handler: impl Fn(MouseDownOut, &mut V, &mut EventContext<V>) + 'static,
-    ) -> Self {
-        self.handlers = self.handlers.on_down_out(button, handler);
-        self
-    }
-
-    pub fn on_up_out(
-        mut self,
-        button: MouseButton,
-        handler: impl Fn(MouseUpOut, &mut V, &mut EventContext<V>) + 'static,
-    ) -> Self {
-        self.handlers = self.handlers.on_up_out(button, handler);
-        self
-    }
-
-    pub fn on_drag(
-        mut self,
-        button: MouseButton,
-        handler: impl Fn(MouseDrag, &mut V, &mut EventContext<V>) + 'static,
-    ) -> Self {
-        self.handlers = self.handlers.on_drag(button, handler);
-        self
-    }
-
-    pub fn on_hover(
-        mut self,
-        handler: impl Fn(MouseHover, &mut V, &mut EventContext<V>) + 'static,
-    ) -> Self {
-        self.handlers = self.handlers.on_hover(handler);
-        self
-    }
-
-    pub fn on_scroll(
-        mut self,
-        handler: impl Fn(MouseScrollWheel, &mut V, &mut EventContext<V>) + 'static,
-    ) -> Self {
-        self.handlers = self.handlers.on_scroll(handler);
-        self
-    }
-
-    pub fn with_hoverable(mut self, is_hoverable: bool) -> Self {
-        self.hoverable = is_hoverable;
-        self
-    }
-
-    pub fn with_padding(mut self, padding: Padding) -> Self {
-        self.padding = padding;
-        self
-    }
-
-    fn hit_bounds(&self, bounds: RectF) -> RectF {
-        RectF::from_points(
-            bounds.origin() - vec2f(self.padding.left, self.padding.top),
-            bounds.lower_right() + vec2f(self.padding.right, self.padding.bottom),
-        )
-        .round_out()
-    }
-
-    fn paint_regions(&self, bounds: RectF, visible_bounds: RectF, cx: &mut ViewContext<V>) {
-        let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
-        let hit_bounds = self.hit_bounds(visible_bounds);
-
-        if let Some(style) = self.cursor_style {
-            cx.scene().push_cursor_region(CursorRegion {
-                bounds: hit_bounds,
-                style,
-            });
-        }
-        let view_id = cx.view_id();
-        cx.scene().push_mouse_region(
-            MouseRegion::from_handlers(
-                self.tag,
-                view_id,
-                self.region_id,
-                hit_bounds,
-                self.handlers.clone(),
-            )
-            .with_hoverable(self.hoverable)
-            .with_notify_on_hover(self.notify_on_hover)
-            .with_notify_on_click(self.notify_on_click),
-        );
-    }
-}
-
-impl<V: 'static> Element<V> for MouseEventHandler<V> {
-    type LayoutState = ();
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        constraint: SizeConstraint,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> (Vector2F, Self::LayoutState) {
-        (self.child.layout(constraint, view, cx), ())
-    }
-
-    fn paint(
-        &mut self,
-        bounds: RectF,
-        visible_bounds: RectF,
-        _: &mut Self::LayoutState,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> Self::PaintState {
-        if self.above {
-            self.child.paint(bounds.origin(), visible_bounds, view, cx);
-            cx.paint_layer(None, |cx| {
-                self.paint_regions(bounds, visible_bounds, cx);
-            });
-        } else {
-            self.paint_regions(bounds, visible_bounds, cx);
-            self.child.paint(bounds.origin(), visible_bounds, view, cx);
-        }
-    }
-
-    fn rect_for_text_range(
-        &self,
-        range_utf16: Range<usize>,
-        _: RectF,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> Option<RectF> {
-        self.child.rect_for_text_range(range_utf16, view, cx)
-    }
-
-    fn debug(
-        &self,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> serde_json::Value {
-        json!({
-            "type": "MouseEventHandler",
-            "child": self.child.debug(view, cx),
-        })
-    }
-}

crates/gpui/src/elements/overlay.rs 🔗

@@ -1,260 +1,241 @@
-use std::ops::Range;
+use smallvec::SmallVec;
+use taffy::style::{Display, Position};
 
 use crate::{
-    geometry::{rect::RectF, vector::Vector2F},
-    json::ToJson,
-    AnyElement, Axis, Element, MouseRegion, SizeConstraint, ViewContext,
+    point, AnyElement, BorrowWindow, Bounds, Element, IntoElement, LayoutId, ParentElement, Pixels,
+    Point, Size, Style, WindowContext,
 };
-use serde_json::json;
 
-pub struct Overlay<V> {
-    child: AnyElement<V>,
-    anchor_position: Option<Vector2F>,
-    anchor_corner: AnchorCorner,
-    fit_mode: OverlayFitMode,
-    position_mode: OverlayPositionMode,
-    hoverable: bool,
-    z_index: Option<usize>,
-}
-
-#[derive(Copy, Clone)]
-pub enum OverlayFitMode {
-    SnapToWindow,
-    SwitchAnchor,
-    None,
+pub struct OverlayState {
+    child_layout_ids: SmallVec<[LayoutId; 4]>,
 }
 
-#[derive(Copy, Clone, PartialEq, Eq)]
-pub enum OverlayPositionMode {
-    Window,
-    Local,
-}
-
-#[derive(Clone, Copy, PartialEq, Eq)]
-pub enum AnchorCorner {
-    TopLeft,
-    TopRight,
-    BottomLeft,
-    BottomRight,
+pub struct Overlay {
+    children: SmallVec<[AnyElement; 2]>,
+    anchor_corner: AnchorCorner,
+    fit_mode: OverlayFitMode,
+    // todo!();
+    anchor_position: Option<Point<Pixels>>,
+    // position_mode: OverlayPositionMode,
 }
 
-impl AnchorCorner {
-    fn get_bounds(&self, anchor_position: Vector2F, size: Vector2F) -> RectF {
-        match self {
-            Self::TopLeft => RectF::from_points(anchor_position, anchor_position + size),
-            Self::TopRight => RectF::from_points(
-                anchor_position - Vector2F::new(size.x(), 0.),
-                anchor_position + Vector2F::new(0., size.y()),
-            ),
-            Self::BottomLeft => RectF::from_points(
-                anchor_position - Vector2F::new(0., size.y()),
-                anchor_position + Vector2F::new(size.x(), 0.),
-            ),
-            Self::BottomRight => RectF::from_points(anchor_position - size, anchor_position),
-        }
-    }
-
-    fn switch_axis(self, axis: Axis) -> Self {
-        match axis {
-            Axis::Vertical => match self {
-                AnchorCorner::TopLeft => AnchorCorner::BottomLeft,
-                AnchorCorner::TopRight => AnchorCorner::BottomRight,
-                AnchorCorner::BottomLeft => AnchorCorner::TopLeft,
-                AnchorCorner::BottomRight => AnchorCorner::TopRight,
-            },
-            Axis::Horizontal => match self {
-                AnchorCorner::TopLeft => AnchorCorner::TopRight,
-                AnchorCorner::TopRight => AnchorCorner::TopLeft,
-                AnchorCorner::BottomLeft => AnchorCorner::BottomRight,
-                AnchorCorner::BottomRight => AnchorCorner::BottomLeft,
-            },
-        }
+/// overlay gives you a floating element that will avoid overflowing the window bounds.
+/// Its children should have no margin to avoid measurement issues.
+pub fn overlay() -> Overlay {
+    Overlay {
+        children: SmallVec::new(),
+        anchor_corner: AnchorCorner::TopLeft,
+        fit_mode: OverlayFitMode::SwitchAnchor,
+        anchor_position: None,
     }
 }
 
-impl<V: 'static> Overlay<V> {
-    pub fn new(child: impl Element<V>) -> Self {
-        Self {
-            child: child.into_any(),
-            anchor_position: None,
-            anchor_corner: AnchorCorner::TopLeft,
-            fit_mode: OverlayFitMode::None,
-            position_mode: OverlayPositionMode::Window,
-            hoverable: false,
-            z_index: None,
-        }
-    }
-
-    pub fn with_anchor_position(mut self, position: Vector2F) -> Self {
-        self.anchor_position = Some(position);
-        self
-    }
-
-    pub fn with_anchor_corner(mut self, anchor_corner: AnchorCorner) -> Self {
-        self.anchor_corner = anchor_corner;
-        self
-    }
-
-    pub fn with_fit_mode(mut self, fit_mode: OverlayFitMode) -> Self {
-        self.fit_mode = fit_mode;
+impl Overlay {
+    /// Sets which corner of the overlay should be anchored to the current position.
+    pub fn anchor(mut self, anchor: AnchorCorner) -> Self {
+        self.anchor_corner = anchor;
         self
     }
 
-    pub fn with_position_mode(mut self, position_mode: OverlayPositionMode) -> Self {
-        self.position_mode = position_mode;
+    /// Sets the position in window co-ordinates
+    /// (otherwise the location the overlay is rendered is used)
+    pub fn position(mut self, anchor: Point<Pixels>) -> Self {
+        self.anchor_position = Some(anchor);
         self
     }
 
-    pub fn with_hoverable(mut self, hoverable: bool) -> Self {
-        self.hoverable = hoverable;
+    /// Snap to window edge instead of switching anchor corner when an overflow would occur.
+    pub fn snap_to_window(mut self) -> Self {
+        self.fit_mode = OverlayFitMode::SnapToWindow;
         self
     }
+}
 
-    pub fn with_z_index(mut self, z_index: usize) -> Self {
-        self.z_index = Some(z_index);
-        self
+impl ParentElement for Overlay {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
+        &mut self.children
     }
 }
 
-impl<V: 'static> Element<V> for Overlay<V> {
-    type LayoutState = Vector2F;
-    type PaintState = ();
+impl Element for Overlay {
+    type State = OverlayState;
 
-    fn layout(
+    fn request_layout(
         &mut self,
-        constraint: SizeConstraint,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> (Vector2F, Self::LayoutState) {
-        let constraint = if self.anchor_position.is_some() {
-            SizeConstraint::new(Vector2F::zero(), cx.window_size())
-        } else {
-            constraint
+        _: Option<Self::State>,
+        cx: &mut WindowContext,
+    ) -> (crate::LayoutId, Self::State) {
+        let child_layout_ids = self
+            .children
+            .iter_mut()
+            .map(|child| child.request_layout(cx))
+            .collect::<SmallVec<_>>();
+
+        let overlay_style = Style {
+            position: Position::Absolute,
+            display: Display::Flex,
+            ..Style::default()
         };
-        let size = self.child.layout(constraint, view, cx);
-        (Vector2F::zero(), size)
+
+        let layout_id = cx.request_layout(&overlay_style, child_layout_ids.iter().copied());
+
+        (layout_id, OverlayState { child_layout_ids })
     }
 
     fn paint(
         &mut self,
-        bounds: RectF,
-        _: RectF,
-        size: &mut Self::LayoutState,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
+        bounds: crate::Bounds<crate::Pixels>,
+        element_state: &mut Self::State,
+        cx: &mut WindowContext,
     ) {
-        let (anchor_position, mut bounds) = match self.position_mode {
-            OverlayPositionMode::Window => {
-                let anchor_position = self.anchor_position.unwrap_or_else(|| bounds.origin());
-                let bounds = self.anchor_corner.get_bounds(anchor_position, *size);
-                (anchor_position, bounds)
-            }
-            OverlayPositionMode::Local => {
-                let anchor_position = self.anchor_position.unwrap_or_default();
-                let bounds = self
-                    .anchor_corner
-                    .get_bounds(bounds.origin() + anchor_position, *size);
-                (anchor_position, bounds)
-            }
+        if element_state.child_layout_ids.is_empty() {
+            return;
+        }
+
+        let mut child_min = point(Pixels::MAX, Pixels::MAX);
+        let mut child_max = Point::default();
+        for child_layout_id in &element_state.child_layout_ids {
+            let child_bounds = cx.layout_bounds(*child_layout_id);
+            child_min = child_min.min(&child_bounds.origin);
+            child_max = child_max.max(&child_bounds.lower_right());
+        }
+        let size: Size<Pixels> = (child_max - child_min).into();
+        let origin = self.anchor_position.unwrap_or(bounds.origin);
+
+        let mut desired = self.anchor_corner.get_bounds(origin, size);
+        let limits = Bounds {
+            origin: Point::default(),
+            size: cx.viewport_size(),
         };
 
-        match self.fit_mode {
-            OverlayFitMode::SnapToWindow => {
-                // Snap the horizontal edges of the overlay to the horizontal edges of the window if
-                // its horizontal bounds overflow
-                if bounds.max_x() > cx.window_size().x() {
-                    let mut lower_right = bounds.lower_right();
-                    lower_right.set_x(cx.window_size().x());
-                    bounds = RectF::from_points(lower_right - *size, lower_right);
-                } else if bounds.min_x() < 0. {
-                    let mut upper_left = bounds.origin();
-                    upper_left.set_x(0.);
-                    bounds = RectF::from_points(upper_left, upper_left + *size);
-                }
+        if self.fit_mode == OverlayFitMode::SwitchAnchor {
+            let mut anchor_corner = self.anchor_corner;
 
-                // Snap the vertical edges of the overlay to the vertical edges of the window if
-                // its vertical bounds overflow.
-                if bounds.max_y() > cx.window_size().y() {
-                    let mut lower_right = bounds.lower_right();
-                    lower_right.set_y(cx.window_size().y());
-                    bounds = RectF::from_points(lower_right - *size, lower_right);
-                } else if bounds.min_y() < 0. {
-                    let mut upper_left = bounds.origin();
-                    upper_left.set_y(0.);
-                    bounds = RectF::from_points(upper_left, upper_left + *size);
+            if desired.left() < limits.left() || desired.right() > limits.right() {
+                let switched = anchor_corner
+                    .switch_axis(Axis::Horizontal)
+                    .get_bounds(origin, size);
+                if !(switched.left() < limits.left() || switched.right() > limits.right()) {
+                    anchor_corner = anchor_corner.switch_axis(Axis::Horizontal);
+                    desired = switched
                 }
             }
-            OverlayFitMode::SwitchAnchor => {
-                let mut anchor_corner = self.anchor_corner;
 
-                if bounds.max_x() > cx.window_size().x() {
-                    anchor_corner = anchor_corner.switch_axis(Axis::Horizontal);
+            if desired.top() < limits.top() || desired.bottom() > limits.bottom() {
+                let switched = anchor_corner
+                    .switch_axis(Axis::Vertical)
+                    .get_bounds(origin, size);
+                if !(switched.top() < limits.top() || switched.bottom() > limits.bottom()) {
+                    desired = switched;
                 }
+            }
+        }
 
-                if bounds.max_y() > cx.window_size().y() {
-                    anchor_corner = anchor_corner.switch_axis(Axis::Vertical);
-                }
+        // Snap the horizontal edges of the overlay to the horizontal edges of the window if
+        // its horizontal bounds overflow, aligning to the left if it is wider than the limits.
+        if desired.right() > limits.right() {
+            desired.origin.x -= desired.right() - limits.right();
+        }
+        if desired.left() < limits.left() {
+            desired.origin.x = limits.origin.x;
+        }
 
-                if bounds.min_x() < 0. {
-                    anchor_corner = anchor_corner.switch_axis(Axis::Horizontal)
-                }
+        // Snap the vertical edges of the overlay to the vertical edges of the window if
+        // its vertical bounds overflow, aligning to the top if it is taller than the limits.
+        if desired.bottom() > limits.bottom() {
+            desired.origin.y -= desired.bottom() - limits.bottom();
+        }
+        if desired.top() < limits.top() {
+            desired.origin.y = limits.origin.y;
+        }
 
-                if bounds.min_y() < 0. {
-                    anchor_corner = anchor_corner.switch_axis(Axis::Vertical)
+        let mut offset = cx.element_offset() + desired.origin - bounds.origin;
+        offset = point(offset.x.round(), offset.y.round());
+        cx.with_absolute_element_offset(offset, |cx| {
+            cx.break_content_mask(|cx| {
+                for child in &mut self.children {
+                    child.paint(cx);
                 }
+            })
+        })
+    }
+}
 
-                // Update bounds if needed
-                if anchor_corner != self.anchor_corner {
-                    bounds = anchor_corner.get_bounds(anchor_position, *size)
-                }
-            }
-            OverlayFitMode::None => {}
-        }
+impl IntoElement for Overlay {
+    type Element = Self;
 
-        cx.scene().push_stacking_context(None, self.z_index);
-        if self.hoverable {
-            enum OverlayHoverCapture {}
-            // Block hovers in lower stacking contexts
-            let view_id = cx.view_id();
-            cx.scene()
-                .push_mouse_region(MouseRegion::new::<OverlayHoverCapture>(
-                    view_id, view_id, bounds,
-                ));
-        }
-        self.child.paint(
-            bounds.origin(),
-            RectF::new(Vector2F::zero(), cx.window_size()),
-            view,
-            cx,
-        );
-        cx.scene().pop_stacking_context();
+    fn element_id(&self) -> Option<crate::ElementId> {
+        None
     }
 
-    fn rect_for_text_range(
-        &self,
-        range_utf16: Range<usize>,
-        _: RectF,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> Option<RectF> {
-        self.child.rect_for_text_range(range_utf16, view, cx)
+    fn into_element(self) -> Self::Element {
+        self
     }
+}
 
-    fn debug(
-        &self,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> serde_json::Value {
-        json!({
-            "type": "Overlay",
-            "abs_position": self.anchor_position.to_json(),
-            "child": self.child.debug(view, cx),
-        })
+enum Axis {
+    Horizontal,
+    Vertical,
+}
+
+#[derive(Copy, Clone, PartialEq)]
+pub enum OverlayFitMode {
+    SnapToWindow,
+    SwitchAnchor,
+}
+
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub enum AnchorCorner {
+    TopLeft,
+    TopRight,
+    BottomLeft,
+    BottomRight,
+}
+
+impl AnchorCorner {
+    fn get_bounds(&self, origin: Point<Pixels>, size: Size<Pixels>) -> Bounds<Pixels> {
+        let origin = match self {
+            Self::TopLeft => origin,
+            Self::TopRight => Point {
+                x: origin.x - size.width,
+                y: origin.y,
+            },
+            Self::BottomLeft => Point {
+                x: origin.x,
+                y: origin.y - size.height,
+            },
+            Self::BottomRight => Point {
+                x: origin.x - size.width,
+                y: origin.y - size.height,
+            },
+        };
+
+        Bounds { origin, size }
+    }
+
+    pub fn corner(&self, bounds: Bounds<Pixels>) -> Point<Pixels> {
+        match self {
+            Self::TopLeft => bounds.origin,
+            Self::TopRight => bounds.upper_right(),
+            Self::BottomLeft => bounds.lower_left(),
+            Self::BottomRight => bounds.lower_right(),
+        }
+    }
+
+    fn switch_axis(self, axis: Axis) -> Self {
+        match axis {
+            Axis::Vertical => match self {
+                AnchorCorner::TopLeft => AnchorCorner::BottomLeft,
+                AnchorCorner::TopRight => AnchorCorner::BottomRight,
+                AnchorCorner::BottomLeft => AnchorCorner::TopLeft,
+                AnchorCorner::BottomRight => AnchorCorner::TopRight,
+            },
+            Axis::Horizontal => match self {
+                AnchorCorner::TopLeft => AnchorCorner::TopRight,
+                AnchorCorner::TopRight => AnchorCorner::TopLeft,
+                AnchorCorner::BottomLeft => AnchorCorner::BottomRight,
+                AnchorCorner::BottomRight => AnchorCorner::BottomLeft,
+            },
+        }
     }
 }

crates/gpui/src/elements/resizable.rs 🔗

@@ -1,290 +0,0 @@
-use std::{cell::RefCell, rc::Rc};
-
-use collections::HashMap;
-use pathfinder_geometry::vector::{vec2f, Vector2F};
-use serde_json::json;
-
-use crate::{
-    geometry::rect::RectF,
-    platform::{CursorStyle, MouseButton},
-    AnyElement, AppContext, Axis, Element, MouseRegion, SizeConstraint, TypeTag, View, ViewContext,
-};
-
-#[derive(Copy, Clone, Debug)]
-pub enum HandleSide {
-    Top,
-    Bottom,
-    Left,
-    Right,
-}
-
-impl HandleSide {
-    fn axis(&self) -> Axis {
-        match self {
-            HandleSide::Left | HandleSide::Right => Axis::Horizontal,
-            HandleSide::Top | HandleSide::Bottom => Axis::Vertical,
-        }
-    }
-
-    fn relevant_component(&self, vector: Vector2F) -> f32 {
-        match self.axis() {
-            Axis::Horizontal => vector.x(),
-            Axis::Vertical => vector.y(),
-        }
-    }
-
-    fn of_rect(&self, bounds: RectF, handle_size: f32) -> RectF {
-        match self {
-            HandleSide::Top => RectF::new(bounds.origin(), vec2f(bounds.width(), handle_size)),
-            HandleSide::Left => RectF::new(bounds.origin(), vec2f(handle_size, bounds.height())),
-            HandleSide::Bottom => {
-                let mut origin = bounds.lower_left();
-                origin.set_y(origin.y() - handle_size);
-                RectF::new(origin, vec2f(bounds.width(), handle_size))
-            }
-            HandleSide::Right => {
-                let mut origin = bounds.upper_right();
-                origin.set_x(origin.x() - handle_size);
-                RectF::new(origin, vec2f(handle_size, bounds.height()))
-            }
-        }
-    }
-}
-
-fn get_bounds(tag: TypeTag, cx: &AppContext) -> Option<&(RectF, RectF)>
-where
-{
-    cx.optional_global::<ProviderMap>()
-        .and_then(|map| map.0.get(&tag))
-}
-
-pub struct Resizable<V: 'static> {
-    child: AnyElement<V>,
-    tag: TypeTag,
-    handle_side: HandleSide,
-    handle_size: f32,
-    on_resize: Rc<RefCell<dyn FnMut(&mut V, Option<f32>, &mut ViewContext<V>)>>,
-}
-
-const DEFAULT_HANDLE_SIZE: f32 = 4.0;
-
-impl<V: 'static> Resizable<V> {
-    pub fn new<Tag: 'static>(
-        child: AnyElement<V>,
-        handle_side: HandleSide,
-        size: f32,
-        on_resize: impl 'static + FnMut(&mut V, Option<f32>, &mut ViewContext<V>),
-    ) -> Self {
-        let child = match handle_side.axis() {
-            Axis::Horizontal => child.constrained().with_max_width(size),
-            Axis::Vertical => child.constrained().with_max_height(size),
-        }
-        .into_any();
-
-        Self {
-            child,
-            handle_side,
-            tag: TypeTag::new::<Tag>(),
-            handle_size: DEFAULT_HANDLE_SIZE,
-            on_resize: Rc::new(RefCell::new(on_resize)),
-        }
-    }
-
-    pub fn with_handle_size(mut self, handle_size: f32) -> Self {
-        self.handle_size = handle_size;
-        self
-    }
-}
-
-impl<V: 'static> Element<V> for Resizable<V> {
-    type LayoutState = SizeConstraint;
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        constraint: crate::SizeConstraint,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> (Vector2F, Self::LayoutState) {
-        (self.child.layout(constraint, view, cx), constraint)
-    }
-
-    fn paint(
-        &mut self,
-        bounds: pathfinder_geometry::rect::RectF,
-        visible_bounds: pathfinder_geometry::rect::RectF,
-        constraint: &mut SizeConstraint,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> Self::PaintState {
-        cx.scene().push_stacking_context(None, None);
-
-        let handle_region = self.handle_side.of_rect(bounds, self.handle_size);
-
-        enum ResizeHandle {}
-        let view_id = cx.view_id();
-        cx.scene().push_mouse_region(
-            MouseRegion::new::<ResizeHandle>(view_id, self.handle_side as usize, handle_region)
-                .on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere
-                .on_click(MouseButton::Left, {
-                    let on_resize = self.on_resize.clone();
-                    move |click, v, cx| {
-                        if click.click_count == 2 {
-                            on_resize.borrow_mut()(v, None, cx);
-                        }
-                    }
-                })
-                .on_drag(MouseButton::Left, {
-                    let bounds = bounds.clone();
-                    let side = self.handle_side;
-                    let prev_size = side.relevant_component(bounds.size());
-                    let min_size = side.relevant_component(constraint.min);
-                    let max_size = side.relevant_component(constraint.max);
-                    let on_resize = self.on_resize.clone();
-                    let tag = self.tag;
-                    move |event, view: &mut V, cx| {
-                        if event.end {
-                            return;
-                        }
-
-                        let Some((bounds, _)) = get_bounds(tag, cx) else {
-                            return;
-                        };
-
-                        let new_size_raw = match side {
-                            // Handle on top side of element => Element is on bottom
-                            HandleSide::Top => {
-                                bounds.height() + bounds.origin_y() - event.position.y()
-                            }
-                            // Handle on right side of element => Element is on left
-                            HandleSide::Right => event.position.x() - bounds.lower_left().x(),
-                            // Handle on left side of element => Element is on the right
-                            HandleSide::Left => {
-                                bounds.width() + bounds.origin_x() - event.position.x()
-                            }
-                            // Handle on bottom side of element => Element is on the top
-                            HandleSide::Bottom => event.position.y() - bounds.lower_left().y(),
-                        };
-
-                        let new_size = min_size.max(new_size_raw).min(max_size).round();
-                        if new_size != prev_size {
-                            on_resize.borrow_mut()(view, Some(new_size), cx);
-                        }
-                    }
-                }),
-        );
-
-        cx.scene().push_cursor_region(crate::CursorRegion {
-            bounds: handle_region,
-            style: match self.handle_side.axis() {
-                Axis::Horizontal => CursorStyle::ResizeLeftRight,
-                Axis::Vertical => CursorStyle::ResizeUpDown,
-            },
-        });
-
-        cx.scene().pop_stacking_context();
-
-        self.child.paint(bounds.origin(), visible_bounds, view, cx);
-    }
-
-    fn rect_for_text_range(
-        &self,
-        range_utf16: std::ops::Range<usize>,
-        _bounds: pathfinder_geometry::rect::RectF,
-        _visible_bounds: pathfinder_geometry::rect::RectF,
-        _layout: &Self::LayoutState,
-        _paint: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> Option<pathfinder_geometry::rect::RectF> {
-        self.child.rect_for_text_range(range_utf16, view, cx)
-    }
-
-    fn debug(
-        &self,
-        _bounds: pathfinder_geometry::rect::RectF,
-        _layout: &Self::LayoutState,
-        _paint: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> serde_json::Value {
-        json!({
-            "child": self.child.debug(view, cx),
-        })
-    }
-}
-
-#[derive(Debug, Default)]
-struct ProviderMap(HashMap<TypeTag, (RectF, RectF)>);
-
-pub struct BoundsProvider<V: 'static, P> {
-    child: AnyElement<V>,
-    phantom: std::marker::PhantomData<P>,
-}
-
-impl<V: 'static, P: 'static> BoundsProvider<V, P> {
-    pub fn new(child: AnyElement<V>) -> Self {
-        Self {
-            child,
-            phantom: std::marker::PhantomData,
-        }
-    }
-}
-
-impl<V: View, P: 'static> Element<V> for BoundsProvider<V, P> {
-    type LayoutState = ();
-
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        constraint: crate::SizeConstraint,
-        view: &mut V,
-        cx: &mut crate::ViewContext<V>,
-    ) -> (pathfinder_geometry::vector::Vector2F, Self::LayoutState) {
-        (self.child.layout(constraint, view, cx), ())
-    }
-
-    fn paint(
-        &mut self,
-        bounds: pathfinder_geometry::rect::RectF,
-        visible_bounds: pathfinder_geometry::rect::RectF,
-        _: &mut Self::LayoutState,
-        view: &mut V,
-        cx: &mut crate::ViewContext<V>,
-    ) -> Self::PaintState {
-        cx.update_default_global::<ProviderMap, _, _>(|map, _| {
-            map.0.insert(TypeTag::new::<P>(), (bounds, visible_bounds));
-        });
-
-        self.child.paint(bounds.origin(), visible_bounds, view, cx)
-    }
-
-    fn rect_for_text_range(
-        &self,
-        range_utf16: std::ops::Range<usize>,
-        _: pathfinder_geometry::rect::RectF,
-        _: pathfinder_geometry::rect::RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &crate::ViewContext<V>,
-    ) -> Option<pathfinder_geometry::rect::RectF> {
-        self.child.rect_for_text_range(range_utf16, view, cx)
-    }
-
-    fn debug(
-        &self,
-        _: pathfinder_geometry::rect::RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &crate::ViewContext<V>,
-    ) -> serde_json::Value {
-        serde_json::json!({
-            "type": "Provider",
-            "providing": format!("{:?}", TypeTag::new::<P>()),
-            "child": self.child.debug(view, cx),
-        })
-    }
-}

crates/gpui/src/elements/stack.rs 🔗

@@ -1,104 +0,0 @@
-use std::ops::Range;
-
-use crate::{
-    geometry::{rect::RectF, vector::Vector2F},
-    json::{self, json, ToJson},
-    AnyElement, Element, SizeConstraint, ViewContext,
-};
-
-/// Element which renders it's children in a stack on top of each other.
-/// The first child determines the size of the others.
-pub struct Stack<V> {
-    children: Vec<AnyElement<V>>,
-}
-
-impl<V> Default for Stack<V> {
-    fn default() -> Self {
-        Self {
-            children: Vec::new(),
-        }
-    }
-}
-
-impl<V> Stack<V> {
-    pub fn new() -> Self {
-        Self::default()
-    }
-}
-
-impl<V: 'static> Element<V> for Stack<V> {
-    type LayoutState = ();
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        mut constraint: SizeConstraint,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> (Vector2F, Self::LayoutState) {
-        let mut size = constraint.min;
-        let mut children = self.children.iter_mut();
-        if let Some(bottom_child) = children.next() {
-            size = bottom_child.layout(constraint, view, cx);
-            constraint = SizeConstraint::strict(size);
-        }
-
-        for child in children {
-            child.layout(constraint, view, cx);
-        }
-
-        (size, ())
-    }
-
-    fn paint(
-        &mut self,
-        bounds: RectF,
-        visible_bounds: RectF,
-        _: &mut Self::LayoutState,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> Self::PaintState {
-        for child in &mut self.children {
-            cx.scene().push_layer(None);
-            child.paint(bounds.origin(), visible_bounds, view, cx);
-            cx.scene().pop_layer();
-        }
-    }
-
-    fn rect_for_text_range(
-        &self,
-        range_utf16: Range<usize>,
-        _: RectF,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> Option<RectF> {
-        self.children
-            .iter()
-            .rev()
-            .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
-    }
-
-    fn debug(
-        &self,
-        bounds: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> json::Value {
-        json!({
-            "type": "Stack",
-            "bounds": bounds.to_json(),
-            "children": self.children.iter().map(|child| child.debug(view, cx)).collect::<Vec<json::Value>>()
-        })
-    }
-}
-
-impl<V> Extend<AnyElement<V>> for Stack<V> {
-    fn extend<T: IntoIterator<Item = AnyElement<V>>>(&mut self, children: T) {
-        self.children.extend(children)
-    }
-}

crates/gpui/src/elements/svg.rs 🔗

@@ -1,141 +1,78 @@
-use super::constrain_size_preserving_aspect_ratio;
-use crate::json::ToJson;
 use crate::{
-    color::Color,
-    geometry::{
-        rect::RectF,
-        vector::{vec2f, Vector2F},
-    },
-    scene, Element, SizeConstraint, ViewContext,
+    Bounds, Element, ElementId, InteractiveElement, InteractiveElementState, Interactivity,
+    IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled, WindowContext,
 };
-use schemars::JsonSchema;
-use serde_derive::Deserialize;
-use serde_json::json;
-use std::{borrow::Cow, ops::Range};
+use util::ResultExt;
 
 pub struct Svg {
-    path: Cow<'static, str>,
-    color: Color,
+    interactivity: Interactivity,
+    path: Option<SharedString>,
 }
 
-impl Svg {
-    pub fn new(path: impl Into<Cow<'static, str>>) -> Self {
-        Self {
-            path: path.into(),
-            color: Color::black(),
-        }
-    }
-
-    pub fn for_style<V: 'static>(style: SvgStyle) -> impl Element<V> {
-        Self::new(style.asset)
-            .with_color(style.color)
-            .constrained()
-            .with_width(style.dimensions.width)
-            .with_height(style.dimensions.height)
+pub fn svg() -> Svg {
+    Svg {
+        interactivity: Interactivity::default(),
+        path: None,
     }
+}
 
-    pub fn with_color(mut self, color: Color) -> Self {
-        self.color = color;
+impl Svg {
+    pub fn path(mut self, path: impl Into<SharedString>) -> Self {
+        self.path = Some(path.into());
         self
     }
 }
 
-impl<V: 'static> Element<V> for Svg {
-    type LayoutState = Option<usvg::Tree>;
-    type PaintState = ();
+impl Element for Svg {
+    type State = InteractiveElementState;
 
-    fn layout(
+    fn request_layout(
         &mut self,
-        constraint: SizeConstraint,
-        _: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> (Vector2F, Self::LayoutState) {
-        match cx.asset_cache.svg(&self.path) {
-            Ok(tree) => {
-                let size = constrain_size_preserving_aspect_ratio(
-                    constraint.max,
-                    from_usvg_rect(tree.svg_node().view_box.rect).size(),
-                );
-                (size, Some(tree))
-            }
-            Err(_error) => {
-                #[cfg(not(any(test, feature = "test-support")))]
-                log::error!("{}", _error);
-                (constraint.min, None)
-            }
-        }
+        element_state: Option<Self::State>,
+        cx: &mut WindowContext,
+    ) -> (LayoutId, Self::State) {
+        self.interactivity.layout(element_state, cx, |style, cx| {
+            cx.request_layout(&style, None)
+        })
     }
 
     fn paint(
         &mut self,
-        bounds: RectF,
-        _visible_bounds: RectF,
-        svg: &mut Self::LayoutState,
-        _: &mut V,
-        cx: &mut ViewContext<V>,
-    ) {
-        if let Some(svg) = svg.clone() {
-            cx.scene().push_icon(scene::Icon {
-                bounds,
-                svg,
-                path: self.path.clone(),
-                color: self.color,
-            });
-        }
+        bounds: Bounds<Pixels>,
+        element_state: &mut Self::State,
+        cx: &mut WindowContext,
+    ) where
+        Self: Sized,
+    {
+        self.interactivity
+            .paint(bounds, bounds.size, element_state, cx, |style, _, cx| {
+                if let Some((path, color)) = self.path.as_ref().zip(style.text.color) {
+                    cx.paint_svg(bounds, path.clone(), color).log_err();
+                }
+            })
     }
+}
 
-    fn rect_for_text_range(
-        &self,
-        _: Range<usize>,
-        _: RectF,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        _: &V,
-        _: &ViewContext<V>,
-    ) -> Option<RectF> {
-        None
-    }
+impl IntoElement for Svg {
+    type Element = Self;
 
-    fn debug(
-        &self,
-        bounds: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        _: &V,
-        _: &ViewContext<V>,
-    ) -> serde_json::Value {
-        json!({
-            "type": "Svg",
-            "bounds": bounds.to_json(),
-            "path": self.path,
-            "color": self.color.to_json(),
-        })
+    fn element_id(&self) -> Option<ElementId> {
+        self.interactivity.element_id.clone()
     }
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct SvgStyle {
-    pub color: Color,
-    pub asset: String,
-    pub dimensions: Dimensions,
-}
 
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct Dimensions {
-    pub width: f32,
-    pub height: f32,
+    fn into_element(self) -> Self::Element {
+        self
+    }
 }
 
-impl Dimensions {
-    pub fn to_vec(&self) -> Vector2F {
-        vec2f(self.width, self.height)
+impl Styled for Svg {
+    fn style(&mut self) -> &mut StyleRefinement {
+        &mut self.interactivity.base_style
     }
 }
 
-fn from_usvg_rect(rect: usvg::Rect) -> RectF {
-    RectF::new(
-        vec2f(rect.x() as f32, rect.y() as f32),
-        vec2f(rect.width() as f32, rect.height() as f32),
-    )
+impl InteractiveElement for Svg {
+    fn interactivity(&mut self) -> &mut Interactivity {
+        &mut self.interactivity
+    }
 }

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

@@ -1,438 +1,423 @@
 use crate::{
-    color::Color,
-    fonts::{HighlightStyle, TextStyle},
-    geometry::{
-        rect::RectF,
-        vector::{vec2f, Vector2F},
-    },
-    json::{ToJson, Value},
-    text_layout::{Line, RunStyle, ShapedBoundary},
-    Element, FontCache, SizeConstraint, TextLayoutCache, ViewContext, WindowContext,
+    Bounds, DispatchPhase, Element, ElementId, HighlightStyle, IntoElement, LayoutId,
+    MouseDownEvent, MouseUpEvent, Pixels, Point, SharedString, Size, TextRun, TextStyle,
+    WhiteSpace, WindowContext, WrappedLine,
 };
-use log::warn;
-use serde_json::json;
-use std::{borrow::Cow, ops::Range, sync::Arc};
-
-pub struct Text {
-    text: Cow<'static, str>,
-    style: TextStyle,
-    soft_wrap: bool,
-    highlights: Option<Box<[(Range<usize>, HighlightStyle)]>>,
-    custom_runs: Option<(
-        Box<[Range<usize>]>,
-        Box<dyn FnMut(usize, RectF, &mut WindowContext)>,
-    )>,
+use anyhow::anyhow;
+use parking_lot::{Mutex, MutexGuard};
+use smallvec::SmallVec;
+use std::{cell::Cell, mem, ops::Range, rc::Rc, sync::Arc};
+use util::ResultExt;
+
+impl Element for &'static str {
+    type State = TextState;
+
+    fn request_layout(
+        &mut self,
+        _: Option<Self::State>,
+        cx: &mut WindowContext,
+    ) -> (LayoutId, Self::State) {
+        let mut state = TextState::default();
+        let layout_id = state.layout(SharedString::from(*self), None, cx);
+        (layout_id, state)
+    }
+
+    fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut WindowContext) {
+        state.paint(bounds, self, cx)
+    }
 }
 
-pub struct LayoutState {
-    shaped_lines: Vec<Line>,
-    wrap_boundaries: Vec<Vec<ShapedBoundary>>,
-    line_height: f32,
+impl IntoElement for &'static str {
+    type Element = Self;
+
+    fn element_id(&self) -> Option<ElementId> {
+        None
+    }
+
+    fn into_element(self) -> Self::Element {
+        self
+    }
 }
 
-impl Text {
-    pub fn new<I: Into<Cow<'static, str>>>(text: I, style: TextStyle) -> Self {
-        Self {
-            text: text.into(),
-            style,
-            soft_wrap: true,
-            highlights: None,
-            custom_runs: None,
-        }
+impl Element for SharedString {
+    type State = TextState;
+
+    fn request_layout(
+        &mut self,
+        _: Option<Self::State>,
+        cx: &mut WindowContext,
+    ) -> (LayoutId, Self::State) {
+        let mut state = TextState::default();
+        let layout_id = state.layout(self.clone(), None, cx);
+        (layout_id, state)
     }
 
-    pub fn with_default_color(mut self, color: Color) -> Self {
-        self.style.color = color;
+    fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut WindowContext) {
+        let text_str: &str = self.as_ref();
+        state.paint(bounds, text_str, cx)
+    }
+}
+
+impl IntoElement for SharedString {
+    type Element = Self;
+
+    fn element_id(&self) -> Option<ElementId> {
+        None
+    }
+
+    fn into_element(self) -> Self::Element {
         self
     }
+}
+
+/// Renders text with runs of different styles.
+///
+/// Callers are responsible for setting the correct style for each run.
+/// For text with a uniform style, you can usually avoid calling this constructor
+/// and just pass text directly.
+pub struct StyledText {
+    text: SharedString,
+    runs: Option<Vec<TextRun>>,
+}
+
+impl StyledText {
+    pub fn new(text: impl Into<SharedString>) -> Self {
+        StyledText {
+            text: text.into(),
+            runs: None,
+        }
+    }
 
     pub fn with_highlights(
         mut self,
-        runs: impl Into<Box<[(Range<usize>, HighlightStyle)]>>,
+        default_style: &TextStyle,
+        highlights: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
     ) -> Self {
-        self.highlights = Some(runs.into());
+        let mut runs = Vec::new();
+        let mut ix = 0;
+        for (range, highlight) in highlights {
+            if ix < range.start {
+                runs.push(default_style.clone().to_run(range.start - ix));
+            }
+            runs.push(
+                default_style
+                    .clone()
+                    .highlight(highlight)
+                    .to_run(range.len()),
+            );
+            ix = range.end;
+        }
+        if ix < self.text.len() {
+            runs.push(default_style.to_run(self.text.len() - ix));
+        }
+        self.runs = Some(runs);
         self
     }
+}
 
-    pub fn with_custom_runs(
-        mut self,
-        runs: impl Into<Box<[Range<usize>]>>,
-        callback: impl 'static + FnMut(usize, RectF, &mut WindowContext),
-    ) -> Self {
-        self.custom_runs = Some((runs.into(), Box::new(callback)));
-        self
+impl Element for StyledText {
+    type State = TextState;
+
+    fn request_layout(
+        &mut self,
+        _: Option<Self::State>,
+        cx: &mut WindowContext,
+    ) -> (LayoutId, Self::State) {
+        let mut state = TextState::default();
+        let layout_id = state.layout(self.text.clone(), self.runs.take(), cx);
+        (layout_id, state)
     }
 
-    pub fn with_soft_wrap(mut self, soft_wrap: bool) -> Self {
-        self.soft_wrap = soft_wrap;
+    fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
+        state.paint(bounds, &self.text, cx)
+    }
+}
+
+impl IntoElement for StyledText {
+    type Element = Self;
+
+    fn element_id(&self) -> Option<crate::ElementId> {
+        None
+    }
+
+    fn into_element(self) -> Self::Element {
         self
     }
 }
 
-impl<V: 'static> Element<V> for Text {
-    type LayoutState = LayoutState;
-    type PaintState = ();
+#[derive(Default, Clone)]
+pub struct TextState(Arc<Mutex<Option<TextStateInner>>>);
+
+struct TextStateInner {
+    lines: SmallVec<[WrappedLine; 1]>,
+    line_height: Pixels,
+    wrap_width: Option<Pixels>,
+    size: Option<Size<Pixels>>,
+}
+
+impl TextState {
+    fn lock(&self) -> MutexGuard<Option<TextStateInner>> {
+        self.0.lock()
+    }
 
     fn layout(
         &mut self,
-        constraint: SizeConstraint,
-        _: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> (Vector2F, Self::LayoutState) {
-        // Convert the string and highlight ranges into an iterator of highlighted chunks.
-
-        let mut offset = 0;
-        let mut highlight_ranges = self
-            .highlights
-            .as_ref()
-            .map_or(Default::default(), AsRef::as_ref)
-            .iter()
-            .peekable();
-        let chunks = std::iter::from_fn(|| {
-            let result;
-            if let Some((range, highlight_style)) = highlight_ranges.peek() {
-                if offset < range.start {
-                    result = Some((&self.text[offset..range.start], None));
-                    offset = range.start;
-                } else if range.end <= self.text.len() {
-                    result = Some((&self.text[range.clone()], Some(*highlight_style)));
-                    highlight_ranges.next();
-                    offset = range.end;
+        text: SharedString,
+        runs: Option<Vec<TextRun>>,
+        cx: &mut WindowContext,
+    ) -> LayoutId {
+        let text_style = cx.text_style();
+        let font_size = text_style.font_size.to_pixels(cx.rem_size());
+        let line_height = text_style
+            .line_height
+            .to_pixels(font_size.into(), cx.rem_size());
+
+        let runs = if let Some(runs) = runs {
+            runs
+        } else {
+            vec![text_style.to_run(text.len())]
+        };
+
+        let layout_id = cx.request_measured_layout(Default::default(), {
+            let element_state = self.clone();
+
+            move |known_dimensions, available_space, cx| {
+                let wrap_width = if text_style.white_space == WhiteSpace::Normal {
+                    known_dimensions.width.or(match available_space.width {
+                        crate::AvailableSpace::Definite(x) => Some(x),
+                        _ => None,
+                    })
                 } else {
-                    warn!(
-                        "Highlight out of text range. Text len: {}, Highlight range: {}..{}",
-                        self.text.len(),
-                        range.start,
-                        range.end
-                    );
-                    result = None;
+                    None
+                };
+
+                if let Some(text_state) = element_state.0.lock().as_ref() {
+                    if text_state.size.is_some()
+                        && (wrap_width.is_none() || wrap_width == text_state.wrap_width)
+                    {
+                        return text_state.size.unwrap();
+                    }
                 }
-            } else if offset < self.text.len() {
-                result = Some((&self.text[offset..], None));
-                offset = self.text.len();
-            } else {
-                result = None;
+
+                let Some(lines) = cx
+                    .text_system()
+                    .shape_text(
+                        &text, font_size, &runs, wrap_width, // Wrap if we know the width.
+                    )
+                    .log_err()
+                else {
+                    element_state.lock().replace(TextStateInner {
+                        lines: Default::default(),
+                        line_height,
+                        wrap_width,
+                        size: Some(Size::default()),
+                    });
+                    return Size::default();
+                };
+
+                let mut size: Size<Pixels> = Size::default();
+                for line in &lines {
+                    let line_size = line.size(line_height);
+                    size.height += line_size.height;
+                    size.width = size.width.max(line_size.width).ceil();
+                }
+
+                element_state.lock().replace(TextStateInner {
+                    lines,
+                    line_height,
+                    wrap_width,
+                    size: Some(size),
+                });
+
+                size
             }
-            result
         });
 
-        // Perform shaping on these highlighted chunks
-        let shaped_lines = layout_highlighted_chunks(
-            chunks,
-            &self.style,
-            cx.text_layout_cache(),
-            &cx.font_cache,
-            usize::MAX,
-            self.text.matches('\n').count() + 1,
-        );
-
-        // If line wrapping is enabled, wrap each of the shaped lines.
-        let font_id = self.style.font_id;
-        let mut line_count = 0;
-        let mut max_line_width = 0_f32;
-        let mut wrap_boundaries = Vec::new();
-        let mut wrapper = cx.font_cache.line_wrapper(font_id, self.style.font_size);
-        for (line, shaped_line) in self.text.split('\n').zip(&shaped_lines) {
-            if self.soft_wrap {
-                let boundaries = wrapper
-                    .wrap_shaped_line(line, shaped_line, constraint.max.x())
-                    .collect::<Vec<_>>();
-                line_count += boundaries.len() + 1;
-                wrap_boundaries.push(boundaries);
-            } else {
-                line_count += 1;
-            }
-            max_line_width = max_line_width.max(shaped_line.width());
-        }
+        layout_id
+    }
 
-        let line_height = cx.font_cache.line_height(self.style.font_size);
-        let size = vec2f(
-            max_line_width
-                .ceil()
-                .max(constraint.min.x())
-                .min(constraint.max.x()),
-            (line_height * line_count as f32).ceil(),
-        );
-        (
-            size,
-            LayoutState {
-                shaped_lines,
-                wrap_boundaries,
-                line_height,
-            },
-        )
+    fn paint(&mut self, bounds: Bounds<Pixels>, text: &str, cx: &mut WindowContext) {
+        let element_state = self.lock();
+        let element_state = element_state
+            .as_ref()
+            .ok_or_else(|| anyhow!("measurement has not been performed on {}", text))
+            .unwrap();
+
+        let line_height = element_state.line_height;
+        let mut line_origin = bounds.origin;
+        for line in &element_state.lines {
+            line.paint(line_origin, line_height, cx).log_err();
+            line_origin.y += line.size(line_height).height;
+        }
     }
 
-    fn paint(
-        &mut self,
-        bounds: RectF,
-        visible_bounds: RectF,
-        layout: &mut Self::LayoutState,
-        _: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> Self::PaintState {
-        let mut origin = bounds.origin();
-        let empty = Vec::new();
-        let mut callback = |_, _, _: &mut WindowContext| {};
-
-        let mouse_runs;
-        let custom_run_callback;
-        if let Some((runs, build_region)) = &mut self.custom_runs {
-            mouse_runs = runs.iter();
-            custom_run_callback = build_region.as_mut();
-        } else {
-            mouse_runs = [].iter();
-            custom_run_callback = &mut callback;
+    fn index_for_position(&self, bounds: Bounds<Pixels>, position: Point<Pixels>) -> Option<usize> {
+        if !bounds.contains(&position) {
+            return None;
         }
-        let mut custom_runs = mouse_runs.enumerate().peekable();
-
-        let mut offset = 0;
-        for (ix, line) in layout.shaped_lines.iter().enumerate() {
-            let wrap_boundaries = layout.wrap_boundaries.get(ix).unwrap_or(&empty);
-            let boundaries = RectF::new(
-                origin,
-                vec2f(
-                    bounds.width(),
-                    (wrap_boundaries.len() + 1) as f32 * layout.line_height,
-                ),
-            );
 
-            if boundaries.intersects(visible_bounds) {
-                if self.soft_wrap {
-                    line.paint_wrapped(
-                        origin,
-                        visible_bounds,
-                        layout.line_height,
-                        wrap_boundaries,
-                        cx,
-                    );
-                } else {
-                    line.paint(origin, visible_bounds, layout.line_height, cx);
-                }
+        let element_state = self.lock();
+        let element_state = element_state
+            .as_ref()
+            .expect("measurement has not been performed");
+
+        let line_height = element_state.line_height;
+        let mut line_origin = bounds.origin;
+        let mut line_start_ix = 0;
+        for line in &element_state.lines {
+            let line_bottom = line_origin.y + line.size(line_height).height;
+            if position.y > line_bottom {
+                line_origin.y = line_bottom;
+                line_start_ix += line.len() + 1;
+            } else {
+                let position_within_line = position - line_origin;
+                let index_within_line =
+                    line.index_for_position(position_within_line, line_height)?;
+                return Some(line_start_ix + index_within_line);
             }
+        }
 
-            // Paint any custom runs that intersect this line.
-            let end_offset = offset + line.len();
-            if let Some((custom_run_ix, custom_run_range)) = custom_runs.peek().cloned() {
-                if custom_run_range.start < end_offset {
-                    let mut current_custom_run = None;
-                    if custom_run_range.start <= offset {
-                        current_custom_run = Some((custom_run_ix, custom_run_range.end, origin));
-                    }
-
-                    let mut glyph_origin = origin;
-                    let mut prev_position = 0.;
-                    let mut wrap_boundaries = wrap_boundaries.iter().copied().peekable();
-                    for (run_ix, glyph_ix, glyph) in
-                        line.runs().iter().enumerate().flat_map(|(run_ix, run)| {
-                            run.glyphs()
-                                .iter()
-                                .enumerate()
-                                .map(move |(ix, glyph)| (run_ix, ix, glyph))
-                        })
-                    {
-                        glyph_origin.set_x(glyph_origin.x() + glyph.position.x() - prev_position);
-                        prev_position = glyph.position.x();
-
-                        // If we've reached a soft wrap position, move down one line. If there
-                        // is a custom run in-progress, paint it.
-                        if wrap_boundaries
-                            .peek()
-                            .map_or(false, |b| b.run_ix == run_ix && b.glyph_ix == glyph_ix)
-                        {
-                            if let Some((run_ix, _, run_origin)) = &mut current_custom_run {
-                                let bounds = RectF::from_points(
-                                    *run_origin,
-                                    glyph_origin + vec2f(0., layout.line_height),
-                                );
-                                custom_run_callback(*run_ix, bounds, cx);
-                                *run_origin =
-                                    vec2f(origin.x(), glyph_origin.y() + layout.line_height);
-                            }
-                            wrap_boundaries.next();
-                            glyph_origin = vec2f(origin.x(), glyph_origin.y() + layout.line_height);
-                        }
+        None
+    }
+}
 
-                        // If we've reached the end of the current custom run, paint it.
-                        if let Some((run_ix, run_end_offset, run_origin)) = current_custom_run {
-                            if offset + glyph.index == run_end_offset {
-                                current_custom_run.take();
-                                let bounds = RectF::from_points(
-                                    run_origin,
-                                    glyph_origin + vec2f(0., layout.line_height),
-                                );
-                                custom_run_callback(run_ix, bounds, cx);
-                                custom_runs.next();
-                            }
-
-                            if let Some((_, run_range)) = custom_runs.peek() {
-                                if run_range.start >= end_offset {
-                                    break;
-                                }
-                                if run_range.start == offset + glyph.index {
-                                    current_custom_run =
-                                        Some((run_ix, run_range.end, glyph_origin));
-                                }
-                            }
-                        }
+pub struct InteractiveText {
+    element_id: ElementId,
+    text: StyledText,
+    click_listener:
+        Option<Box<dyn Fn(&[Range<usize>], InteractiveTextClickEvent, &mut WindowContext<'_>)>>,
+    clickable_ranges: Vec<Range<usize>>,
+}
 
-                        // If we've reached the start of a new custom run, start tracking it.
-                        if let Some((run_ix, run_range)) = custom_runs.peek() {
-                            if offset + glyph.index == run_range.start {
-                                current_custom_run = Some((*run_ix, run_range.end, glyph_origin));
-                            }
-                        }
-                    }
+struct InteractiveTextClickEvent {
+    mouse_down_index: usize,
+    mouse_up_index: usize,
+}
 
-                    // If a custom run extends beyond the end of the line, paint it.
-                    if let Some((run_ix, run_end_offset, run_origin)) = current_custom_run {
-                        let line_end = glyph_origin + vec2f(line.width() - prev_position, 0.);
-                        let bounds = RectF::from_points(
-                            run_origin,
-                            line_end + vec2f(0., layout.line_height),
-                        );
-                        custom_run_callback(run_ix, bounds, cx);
-                        if end_offset == run_end_offset {
-                            custom_runs.next();
-                        }
-                    }
-                }
-            }
+pub struct InteractiveTextState {
+    text_state: TextState,
+    mouse_down_index: Rc<Cell<Option<usize>>>,
+}
 
-            offset = end_offset + 1;
-            origin.set_y(boundaries.max_y());
+impl InteractiveText {
+    pub fn new(id: impl Into<ElementId>, text: StyledText) -> Self {
+        Self {
+            element_id: id.into(),
+            text,
+            click_listener: None,
+            clickable_ranges: Vec::new(),
         }
     }
 
-    fn rect_for_text_range(
-        &self,
-        _: Range<usize>,
-        _: RectF,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        _: &V,
-        _: &ViewContext<V>,
-    ) -> Option<RectF> {
-        None
+    pub fn on_click(
+        mut self,
+        ranges: Vec<Range<usize>>,
+        listener: impl Fn(usize, &mut WindowContext<'_>) + 'static,
+    ) -> Self {
+        self.click_listener = Some(Box::new(move |ranges, event, cx| {
+            for (range_ix, range) in ranges.iter().enumerate() {
+                if range.contains(&event.mouse_down_index) && range.contains(&event.mouse_up_index)
+                {
+                    listener(range_ix, cx);
+                }
+            }
+        }));
+        self.clickable_ranges = ranges;
+        self
     }
+}
 
-    fn debug(
-        &self,
-        bounds: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        _: &V,
-        _: &ViewContext<V>,
-    ) -> Value {
-        json!({
-            "type": "Text",
-            "bounds": bounds.to_json(),
-            "text": &self.text,
-            "style": self.style.to_json(),
-        })
+impl Element for InteractiveText {
+    type State = InteractiveTextState;
+
+    fn request_layout(
+        &mut self,
+        state: Option<Self::State>,
+        cx: &mut WindowContext,
+    ) -> (LayoutId, Self::State) {
+        if let Some(InteractiveTextState {
+            mouse_down_index, ..
+        }) = state
+        {
+            let (layout_id, text_state) = self.text.request_layout(None, cx);
+            let element_state = InteractiveTextState {
+                text_state,
+                mouse_down_index,
+            };
+            (layout_id, element_state)
+        } else {
+            let (layout_id, text_state) = self.text.request_layout(None, cx);
+            let element_state = InteractiveTextState {
+                text_state,
+                mouse_down_index: Rc::default(),
+            };
+            (layout_id, element_state)
+        }
     }
-}
 
-/// Perform text layout on a series of highlighted chunks of text.
-pub fn layout_highlighted_chunks<'a>(
-    chunks: impl Iterator<Item = (&'a str, Option<HighlightStyle>)>,
-    text_style: &TextStyle,
-    text_layout_cache: &TextLayoutCache,
-    font_cache: &Arc<FontCache>,
-    max_line_len: usize,
-    max_line_count: usize,
-) -> Vec<Line> {
-    let mut layouts = Vec::with_capacity(max_line_count);
-    let mut line = String::new();
-    let mut styles = Vec::new();
-    let mut row = 0;
-    let mut line_exceeded_max_len = false;
-    for (chunk, highlight_style) in chunks.chain([("\n", Default::default())]) {
-        for (ix, mut line_chunk) in chunk.split('\n').enumerate() {
-            if ix > 0 {
-                layouts.push(text_layout_cache.layout_str(&line, text_style.font_size, &styles));
-                line.clear();
-                styles.clear();
-                row += 1;
-                line_exceeded_max_len = false;
-                if row == max_line_count {
-                    return layouts;
+    fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
+        if let Some(click_listener) = self.click_listener.take() {
+            let mouse_position = cx.mouse_position();
+            if let Some(ix) = state.text_state.index_for_position(bounds, mouse_position) {
+                if self
+                    .clickable_ranges
+                    .iter()
+                    .any(|range| range.contains(&ix))
+                    && cx.was_top_layer(&mouse_position, cx.stacking_order())
+                {
+                    cx.set_cursor_style(crate::CursorStyle::PointingHand)
                 }
             }
 
-            if !line_chunk.is_empty() && !line_exceeded_max_len {
-                let text_style = if let Some(style) = highlight_style {
-                    text_style
-                        .clone()
-                        .highlight(style, font_cache)
-                        .map(Cow::Owned)
-                        .unwrap_or_else(|_| Cow::Borrowed(text_style))
-                } else {
-                    Cow::Borrowed(text_style)
-                };
+            let text_state = state.text_state.clone();
+            let mouse_down = state.mouse_down_index.clone();
+            if let Some(mouse_down_index) = mouse_down.get() {
+                let clickable_ranges = mem::take(&mut self.clickable_ranges);
+                cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
+                    if phase == DispatchPhase::Bubble {
+                        if let Some(mouse_up_index) =
+                            text_state.index_for_position(bounds, event.position)
+                        {
+                            click_listener(
+                                &clickable_ranges,
+                                InteractiveTextClickEvent {
+                                    mouse_down_index,
+                                    mouse_up_index,
+                                },
+                                cx,
+                            )
+                        }
 
-                if line.len() + line_chunk.len() > max_line_len {
-                    let mut chunk_len = max_line_len - line.len();
-                    while !line_chunk.is_char_boundary(chunk_len) {
-                        chunk_len -= 1;
+                        mouse_down.take();
+                        cx.notify();
                     }
-                    line_chunk = &line_chunk[..chunk_len];
-                    line_exceeded_max_len = true;
-                }
-
-                line.push_str(line_chunk);
-                styles.push((
-                    line_chunk.len(),
-                    RunStyle {
-                        font_id: text_style.font_id,
-                        color: text_style.color,
-                        underline: text_style.underline,
-                    },
-                ));
+                });
+            } else {
+                cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
+                    if phase == DispatchPhase::Bubble {
+                        if let Some(mouse_down_index) =
+                            text_state.index_for_position(bounds, event.position)
+                        {
+                            mouse_down.set(Some(mouse_down_index));
+                            cx.notify();
+                        }
+                    }
+                });
             }
         }
-    }
 
-    layouts
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::{elements::Empty, fonts, AnyElement, AppContext, Entity, View, ViewContext};
-
-    #[crate::test(self)]
-    fn test_soft_wrapping_with_carriage_returns(cx: &mut AppContext) {
-        cx.add_window(Default::default(), |cx| {
-            let mut view = TestView;
-            fonts::with_font_cache(cx.font_cache().clone(), || {
-                let mut text = Text::new("Hello\r\n", Default::default()).with_soft_wrap(true);
-                let (_, state) = text.layout(
-                    SizeConstraint::new(Default::default(), vec2f(f32::INFINITY, f32::INFINITY)),
-                    &mut view,
-                    cx,
-                );
-                assert_eq!(state.shaped_lines.len(), 2);
-                assert_eq!(state.wrap_boundaries.len(), 2);
-            });
-            view
-        });
+        self.text.paint(bounds, &mut state.text_state, cx)
     }
+}
 
-    struct TestView;
+impl IntoElement for InteractiveText {
+    type Element = Self;
 
-    impl Entity for TestView {
-        type Event = ();
+    fn element_id(&self) -> Option<ElementId> {
+        Some(self.element_id.clone())
     }
 
-    impl View for TestView {
-        fn ui_name() -> &'static str {
-            "TestView"
-        }
-
-        fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
-            Empty::new().into_any()
-        }
+    fn into_element(self) -> Self::Element {
+        self
     }
 }

crates/gpui/src/elements/tooltip.rs 🔗

@@ -1,244 +0,0 @@
-use super::{
-    AnyElement, ContainerStyle, Element, Flex, KeystrokeLabel, MouseEventHandler, Overlay,
-    OverlayFitMode, ParentElement, Text,
-};
-use crate::{
-    fonts::TextStyle,
-    geometry::{rect::RectF, vector::Vector2F},
-    json::json,
-    Action, Axis, ElementStateHandle, SizeConstraint, Task, TypeTag, ViewContext,
-};
-use schemars::JsonSchema;
-use serde::Deserialize;
-use std::{
-    borrow::Cow,
-    cell::{Cell, RefCell},
-    ops::Range,
-    rc::Rc,
-    time::Duration,
-};
-use util::ResultExt;
-
-const DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(500);
-
-pub struct Tooltip<V> {
-    child: AnyElement<V>,
-    tooltip: Option<AnyElement<V>>,
-    _state: ElementStateHandle<Rc<TooltipState>>,
-}
-
-#[derive(Default)]
-struct TooltipState {
-    visible: Cell<bool>,
-    position: Cell<Vector2F>,
-    debounce: RefCell<Option<Task<()>>>,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct TooltipStyle {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub text: TextStyle,
-    keystroke: KeystrokeStyle,
-    pub max_text_width: Option<f32>,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct KeystrokeStyle {
-    #[serde(flatten)]
-    container: ContainerStyle,
-    #[serde(flatten)]
-    text: TextStyle,
-}
-
-impl<V: 'static> Tooltip<V> {
-    pub fn new<Tag: 'static>(
-        id: usize,
-        text: impl Into<Cow<'static, str>>,
-        action: Option<Box<dyn Action>>,
-        style: TooltipStyle,
-        child: AnyElement<V>,
-        cx: &mut ViewContext<V>,
-    ) -> Self {
-        Self::new_dynamic(TypeTag::new::<Tag>(), id, text, action, style, child, cx)
-    }
-
-    pub fn new_dynamic(
-        mut tag: TypeTag,
-        id: usize,
-        text: impl Into<Cow<'static, str>>,
-        action: Option<Box<dyn Action>>,
-        style: TooltipStyle,
-        child: AnyElement<V>,
-        cx: &mut ViewContext<V>,
-    ) -> Self {
-        tag = tag.compose(TypeTag::new::<Self>());
-
-        let focused_view_id = cx.focused_view_id();
-
-        let state_handle = cx.default_element_state_dynamic::<Rc<TooltipState>>(tag, id);
-        let state = state_handle.read(cx).clone();
-        let text = text.into();
-
-        let tooltip = if state.visible.get() {
-            let mut collapsed_tooltip = Self::render_tooltip(
-                focused_view_id,
-                text.clone(),
-                style.clone(),
-                action.as_ref().map(|a| a.boxed_clone()),
-                true,
-            );
-            Some(
-                Overlay::new(
-                    Self::render_tooltip(focused_view_id, text, style, action, false)
-                        .constrained()
-                        .dynamically(move |constraint, view, cx| {
-                            SizeConstraint::strict_along(
-                                Axis::Vertical,
-                                collapsed_tooltip.layout(constraint, view, cx).0.y(),
-                            )
-                        }),
-                )
-                .with_fit_mode(OverlayFitMode::SwitchAnchor)
-                .with_anchor_position(state.position.get())
-                .into_any(),
-            )
-        } else {
-            None
-        };
-        let child = MouseEventHandler::new_dynamic(tag, id, cx, |_, _| child)
-            .on_hover(move |e, _, cx| {
-                let position = e.position;
-                if e.started {
-                    if !state.visible.get() {
-                        state.position.set(position);
-
-                        let mut debounce = state.debounce.borrow_mut();
-                        if debounce.is_none() {
-                            *debounce = Some(cx.spawn({
-                                let state = state.clone();
-                                |view, mut cx| async move {
-                                    cx.background().timer(DEBOUNCE_TIMEOUT).await;
-                                    state.visible.set(true);
-                                    view.update(&mut cx, |_, cx| cx.notify()).log_err();
-                                }
-                            }));
-                        }
-                    }
-                } else {
-                    state.visible.set(false);
-                    state.debounce.take();
-                    cx.notify();
-                }
-            })
-            .into_any();
-        Self {
-            child,
-            tooltip,
-            _state: state_handle,
-        }
-    }
-
-    pub fn render_tooltip(
-        focused_view_id: Option<usize>,
-        text: impl Into<Cow<'static, str>>,
-        style: TooltipStyle,
-        action: Option<Box<dyn Action>>,
-        measure: bool,
-    ) -> impl Element<V> {
-        Flex::row()
-            .with_child({
-                let text = if let Some(max_text_width) = style.max_text_width {
-                    Text::new(text, style.text)
-                        .constrained()
-                        .with_max_width(max_text_width)
-                } else {
-                    Text::new(text, style.text).constrained()
-                };
-
-                if measure {
-                    text.flex(1., false).into_any()
-                } else {
-                    text.flex(1., false).aligned().into_any()
-                }
-            })
-            .with_children(action.and_then(|action| {
-                let keystroke_label = KeystrokeLabel::new(
-                    focused_view_id?,
-                    action,
-                    style.keystroke.container,
-                    style.keystroke.text,
-                );
-                if measure {
-                    Some(keystroke_label.into_any())
-                } else {
-                    Some(keystroke_label.aligned().into_any())
-                }
-            }))
-            .contained()
-            .with_style(style.container)
-    }
-}
-
-impl<V: 'static> Element<V> for Tooltip<V> {
-    type LayoutState = ();
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        constraint: SizeConstraint,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> (Vector2F, Self::LayoutState) {
-        let size = self.child.layout(constraint, view, cx);
-        if let Some(tooltip) = self.tooltip.as_mut() {
-            tooltip.layout(
-                SizeConstraint::new(Vector2F::zero(), cx.window_size()),
-                view,
-                cx,
-            );
-        }
-        (size, ())
-    }
-
-    fn paint(
-        &mut self,
-        bounds: RectF,
-        visible_bounds: RectF,
-        _: &mut Self::LayoutState,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) {
-        self.child.paint(bounds.origin(), visible_bounds, view, cx);
-        if let Some(tooltip) = self.tooltip.as_mut() {
-            tooltip.paint(bounds.origin(), visible_bounds, view, cx);
-        }
-    }
-
-    fn rect_for_text_range(
-        &self,
-        range: Range<usize>,
-        _: RectF,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> Option<RectF> {
-        self.child.rect_for_text_range(range, view, cx)
-    }
-
-    fn debug(
-        &self,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> serde_json::Value {
-        json!({
-            "child": self.child.debug(view, cx),
-            "tooltip": self.tooltip.as_ref().map(|t| t.debug(view, cx)),
-        })
-    }
-}

crates/gpui/src/elements/uniform_list.rs 🔗

@@ -1,354 +1,316 @@
-use super::{Element, SizeConstraint};
 use crate::{
-    geometry::{
-        rect::RectF,
-        vector::{vec2f, Vector2F},
-    },
-    json::{self, json},
-    platform::ScrollWheelEvent,
-    AnyElement, MouseRegion, ViewContext,
+    point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Element,
+    ElementId, InteractiveElement, InteractiveElementState, Interactivity, IntoElement, LayoutId,
+    Pixels, Point, Render, Size, StyleRefinement, Styled, View, ViewContext, WindowContext,
 };
-use json::ToJson;
+use smallvec::SmallVec;
 use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
-
-#[derive(Clone, Default)]
-pub struct UniformListState(Rc<RefCell<StateInner>>);
-
-#[derive(Debug)]
-pub enum ScrollTarget {
-    Show(usize),
-    Center(usize),
-}
-
-impl UniformListState {
-    pub fn scroll_to(&self, scroll_to: ScrollTarget) {
-        self.0.borrow_mut().scroll_to = Some(scroll_to);
-    }
-
-    pub fn scroll_top(&self) -> f32 {
-        self.0.borrow().scroll_top
+use taffy::style::Overflow;
+
+/// uniform_list provides lazy rendering for a set of items that are of uniform height.
+/// When rendered into a container with overflow-y: hidden and a fixed (or max) height,
+/// uniform_list will only render the visible subset of items.
+#[track_caller]
+pub fn uniform_list<I, R, V>(
+    view: View<V>,
+    id: I,
+    item_count: usize,
+    f: impl 'static + Fn(&mut V, Range<usize>, &mut ViewContext<V>) -> Vec<R>,
+) -> UniformList
+where
+    I: Into<ElementId>,
+    R: IntoElement,
+    V: Render,
+{
+    let id = id.into();
+    let mut base_style = StyleRefinement::default();
+    base_style.overflow.y = Some(Overflow::Scroll);
+
+    let render_range = move |range, cx: &mut WindowContext| {
+        view.update(cx, |this, cx| {
+            f(this, range, cx)
+                .into_iter()
+                .map(|component| component.into_any_element())
+                .collect()
+        })
+    };
+
+    UniformList {
+        id: id.clone(),
+        item_count,
+        item_to_measure_index: 0,
+        render_items: Box::new(render_range),
+        interactivity: Interactivity {
+            element_id: Some(id),
+            base_style: Box::new(base_style),
+
+            #[cfg(debug_assertions)]
+            location: Some(*core::panic::Location::caller()),
+
+            ..Default::default()
+        },
+        scroll_handle: None,
     }
 }
 
-#[derive(Default)]
-struct StateInner {
-    scroll_top: f32,
-    scroll_to: Option<ScrollTarget>,
-}
-
-pub struct UniformListLayoutState<V> {
-    scroll_max: f32,
-    item_height: f32,
-    items: Vec<AnyElement<V>>,
-}
-
-pub struct UniformList<V> {
-    state: UniformListState,
+pub struct UniformList {
+    id: ElementId,
     item_count: usize,
-    #[allow(clippy::type_complexity)]
-    append_items: Box<dyn Fn(&mut V, Range<usize>, &mut Vec<AnyElement<V>>, &mut ViewContext<V>)>,
-    padding_top: f32,
-    padding_bottom: f32,
-    get_width_from_item: Option<usize>,
-    view_id: usize,
+    item_to_measure_index: usize,
+    render_items:
+        Box<dyn for<'a> Fn(Range<usize>, &'a mut WindowContext) -> SmallVec<[AnyElement; 64]>>,
+    interactivity: Interactivity,
+    scroll_handle: Option<UniformListScrollHandle>,
 }
 
-impl<V: 'static> UniformList<V> {
-    pub fn new<F>(
-        state: UniformListState,
-        item_count: usize,
-        cx: &mut ViewContext<V>,
-        append_items: F,
-    ) -> Self
-    where
-        F: 'static + Fn(&mut V, Range<usize>, &mut Vec<AnyElement<V>>, &mut ViewContext<V>),
-    {
-        Self {
-            state,
-            item_count,
-            append_items: Box::new(append_items),
-            padding_top: 0.,
-            padding_bottom: 0.,
-            get_width_from_item: None,
-            view_id: cx.handle().id(),
-        }
-    }
-
-    pub fn with_width_from_item(mut self, item_ix: Option<usize>) -> Self {
-        self.get_width_from_item = item_ix;
-        self
-    }
-
-    pub fn with_padding_top(mut self, padding: f32) -> Self {
-        self.padding_top = padding;
-        self
-    }
-
-    pub fn with_padding_bottom(mut self, padding: f32) -> Self {
-        self.padding_bottom = padding;
-        self
-    }
-
-    fn scroll(
-        state: UniformListState,
-        _: Vector2F,
-        mut delta: Vector2F,
-        precise: bool,
-        scroll_max: f32,
-        cx: &mut ViewContext<V>,
-    ) -> bool {
-        if !precise {
-            delta *= 20.;
-        }
+#[derive(Clone, Default)]
+pub struct UniformListScrollHandle(Rc<RefCell<Option<ScrollHandleState>>>);
 
-        let mut state = state.0.borrow_mut();
-        state.scroll_top = (state.scroll_top - delta.y()).max(0.0).min(scroll_max);
-        cx.notify();
+#[derive(Clone, Debug)]
+struct ScrollHandleState {
+    item_height: Pixels,
+    list_height: Pixels,
+    scroll_offset: Rc<RefCell<Point<Pixels>>>,
+}
 
-        true
+impl UniformListScrollHandle {
+    pub fn new() -> Self {
+        Self(Rc::new(RefCell::new(None)))
     }
 
-    fn autoscroll(&mut self, scroll_max: f32, list_height: f32, item_height: f32) {
-        let mut state = self.state.0.borrow_mut();
-
-        if let Some(scroll_to) = state.scroll_to.take() {
-            let item_ix;
-            let center;
-            match scroll_to {
-                ScrollTarget::Show(ix) => {
-                    item_ix = ix;
-                    center = false;
-                }
-                ScrollTarget::Center(ix) => {
-                    item_ix = ix;
-                    center = true;
-                }
-            }
-
-            let item_top = self.padding_top + item_ix as f32 * item_height;
-            let item_bottom = item_top + item_height;
-            if center {
-                let item_center = item_top + item_height / 2.;
-                state.scroll_top = (item_center - list_height / 2.).max(0.);
-            } else {
-                let scroll_bottom = state.scroll_top + list_height;
-                if item_top < state.scroll_top {
-                    state.scroll_top = item_top;
-                } else if item_bottom > scroll_bottom {
-                    state.scroll_top = item_bottom - list_height;
-                }
+    pub fn scroll_to_item(&self, ix: usize) {
+        if let Some(state) = &*self.0.borrow() {
+            let mut scroll_offset = state.scroll_offset.borrow_mut();
+            let item_top = state.item_height * ix;
+            let item_bottom = item_top + state.item_height;
+            let scroll_top = -scroll_offset.y;
+            if item_top < scroll_top {
+                scroll_offset.y = -item_top;
+            } else if item_bottom > scroll_top + state.list_height {
+                scroll_offset.y = -(item_bottom - state.list_height);
             }
         }
+    }
 
-        if state.scroll_top > scroll_max {
-            state.scroll_top = scroll_max;
+    pub fn scroll_top(&self) -> Pixels {
+        if let Some(state) = &*self.0.borrow() {
+            -state.scroll_offset.borrow().y
+        } else {
+            Pixels::ZERO
         }
     }
+}
 
-    fn scroll_top(&self) -> f32 {
-        self.state.0.borrow().scroll_top
+impl Styled for UniformList {
+    fn style(&mut self) -> &mut StyleRefinement {
+        &mut self.interactivity.base_style
     }
 }
 
-impl<V: 'static> Element<V> for UniformList<V> {
-    type LayoutState = UniformListLayoutState<V>;
-    type PaintState = ();
+#[derive(Default)]
+pub struct UniformListState {
+    interactive: InteractiveElementState,
+    item_size: Size<Pixels>,
+}
 
-    fn layout(
-        &mut self,
-        constraint: SizeConstraint,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> (Vector2F, Self::LayoutState) {
-        if constraint.max.y().is_infinite() {
-            unimplemented!(
-                "UniformList does not support being rendered with an unconstrained height"
-            );
-        }
+impl Element for UniformList {
+    type State = UniformListState;
 
-        let no_items = (
-            constraint.min,
-            UniformListLayoutState {
-                item_height: 0.,
-                scroll_max: 0.,
-                items: Default::default(),
-            },
-        );
+    fn request_layout(
+        &mut self,
+        state: Option<Self::State>,
+        cx: &mut WindowContext,
+    ) -> (LayoutId, Self::State) {
+        let max_items = self.item_count;
+        let item_size = state
+            .as_ref()
+            .map(|s| s.item_size)
+            .unwrap_or_else(|| self.measure_item(None, cx));
+
+        let (layout_id, interactive) =
+            self.interactivity
+                .layout(state.map(|s| s.interactive), cx, |style, cx| {
+                    cx.request_measured_layout(
+                        style,
+                        move |known_dimensions, available_space, _cx| {
+                            let desired_height = item_size.height * max_items;
+                            let width =
+                                known_dimensions
+                                    .width
+                                    .unwrap_or(match available_space.width {
+                                        AvailableSpace::Definite(x) => x,
+                                        AvailableSpace::MinContent | AvailableSpace::MaxContent => {
+                                            item_size.width
+                                        }
+                                    });
+
+                            let height = match available_space.height {
+                                AvailableSpace::Definite(height) => desired_height.min(height),
+                                AvailableSpace::MinContent | AvailableSpace::MaxContent => {
+                                    desired_height
+                                }
+                            };
+                            size(width, height)
+                        },
+                    )
+                });
+
+        let element_state = UniformListState {
+            interactive,
+            item_size,
+        };
 
-        if self.item_count == 0 {
-            return no_items;
-        }
+        (layout_id, element_state)
+    }
 
-        let mut items = Vec::new();
-        let mut size = constraint.max;
-        let mut item_size;
-        let sample_item_ix;
-        let sample_item;
-        if let Some(sample_ix) = self.get_width_from_item {
-            (self.append_items)(view, sample_ix..sample_ix + 1, &mut items, cx);
-            sample_item_ix = sample_ix;
-
-            if let Some(mut item) = items.pop() {
-                item_size = item.layout(constraint, view, cx);
-                size.set_x(item_size.x());
-                sample_item = item;
-            } else {
-                return no_items;
-            }
-        } else {
-            (self.append_items)(view, 0..1, &mut items, cx);
-            sample_item_ix = 0;
-            if let Some(mut item) = items.pop() {
-                item_size = item.layout(
-                    SizeConstraint::new(
-                        vec2f(constraint.max.x(), 0.0),
-                        vec2f(constraint.max.x(), f32::INFINITY),
-                    ),
-                    view,
-                    cx,
-                );
-                item_size.set_x(size.x());
-                sample_item = item
-            } else {
-                return no_items;
-            }
-        }
+    fn paint(
+        &mut self,
+        bounds: Bounds<crate::Pixels>,
+        element_state: &mut Self::State,
+        cx: &mut WindowContext,
+    ) {
+        let style =
+            self.interactivity
+                .compute_style(Some(bounds), &mut element_state.interactive, cx);
+        let border = style.border_widths.to_pixels(cx.rem_size());
+        let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
+
+        let padded_bounds = Bounds::from_corners(
+            bounds.origin + point(border.left + padding.left, border.top + padding.top),
+            bounds.lower_right()
+                - point(border.right + padding.right, border.bottom + padding.bottom),
+        );
 
-        let item_constraint = SizeConstraint {
-            min: item_size,
-            max: vec2f(constraint.max.x(), item_size.y()),
+        let item_size = element_state.item_size;
+        let content_size = Size {
+            width: padded_bounds.size.width,
+            height: item_size.height * self.item_count + padding.top + padding.bottom,
         };
-        let item_height = item_size.y();
 
-        let scroll_height = self.item_count as f32 * item_height;
-        if scroll_height < size.y() {
-            size.set_y(size.y().min(scroll_height).max(constraint.min.y()));
-        }
+        let shared_scroll_offset = element_state
+            .interactive
+            .scroll_offset
+            .get_or_insert_with(|| {
+                if let Some(scroll_handle) = self.scroll_handle.as_ref() {
+                    if let Some(scroll_handle) = scroll_handle.0.borrow().as_ref() {
+                        return scroll_handle.scroll_offset.clone();
+                    }
+                }
 
-        let scroll_height =
-            item_height * self.item_count as f32 + self.padding_top + self.padding_bottom;
-        let scroll_max = (scroll_height - size.y()).max(0.);
-        self.autoscroll(scroll_max, size.y(), item_height);
+                Rc::default()
+            })
+            .clone();
 
-        let start = cmp::min(
-            ((self.scroll_top() - self.padding_top) / item_height.max(1.)) as usize,
-            self.item_count,
-        );
-        let end = cmp::min(
-            self.item_count,
-            start + (size.y() / item_height.max(1.)).ceil() as usize + 1,
-        );
+        let item_height = self.measure_item(Some(padded_bounds.size.width), cx).height;
 
-        if (start..end).contains(&sample_item_ix) {
-            if sample_item_ix > start {
-                (self.append_items)(view, start..sample_item_ix, &mut items, cx);
-            }
+        self.interactivity.paint(
+            bounds,
+            content_size,
+            &mut element_state.interactive,
+            cx,
+            |style, mut scroll_offset, cx| {
+                let border = style.border_widths.to_pixels(cx.rem_size());
+                let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
 
-            items.push(sample_item);
+                let padded_bounds = Bounds::from_corners(
+                    bounds.origin + point(border.left + padding.left, border.top),
+                    bounds.lower_right() - point(border.right + padding.right, border.bottom),
+                );
 
-            if sample_item_ix < end {
-                (self.append_items)(view, sample_item_ix + 1..end, &mut items, cx);
-            }
-        } else {
-            (self.append_items)(view, start..end, &mut items, cx);
-        }
+                if self.item_count > 0 {
+                    let content_height =
+                        item_height * self.item_count + padding.top + padding.bottom;
+                    let min_scroll_offset = padded_bounds.size.height - content_height;
+                    let is_scrolled = scroll_offset.y != px(0.);
 
-        for item in &mut items {
-            let item_size = item.layout(item_constraint, view, cx);
-            if item_size.x() > size.x() {
-                size.set_x(item_size.x());
-            }
-        }
+                    if is_scrolled && scroll_offset.y < min_scroll_offset {
+                        shared_scroll_offset.borrow_mut().y = min_scroll_offset;
+                        scroll_offset.y = min_scroll_offset;
+                    }
+
+                    if let Some(scroll_handle) = self.scroll_handle.clone() {
+                        scroll_handle.0.borrow_mut().replace(ScrollHandleState {
+                            item_height,
+                            list_height: padded_bounds.size.height,
+                            scroll_offset: shared_scroll_offset,
+                        });
+                    }
 
-        (
-            size,
-            UniformListLayoutState {
-                item_height,
-                scroll_max,
-                items,
+                    let first_visible_element_ix =
+                        (-(scroll_offset.y + padding.top) / item_height).floor() as usize;
+                    let last_visible_element_ix = ((-scroll_offset.y + padded_bounds.size.height)
+                        / item_height)
+                        .ceil() as usize;
+                    let visible_range = first_visible_element_ix
+                        ..cmp::min(last_visible_element_ix, self.item_count);
+
+                    let mut items = (self.render_items)(visible_range.clone(), cx);
+                    cx.with_z_index(1, |cx| {
+                        let content_mask = ContentMask { bounds };
+                        cx.with_content_mask(Some(content_mask), |cx| {
+                            for (item, ix) in items.iter_mut().zip(visible_range) {
+                                let item_origin = padded_bounds.origin
+                                    + point(
+                                        px(0.),
+                                        item_height * ix + scroll_offset.y + padding.top,
+                                    );
+                                let available_space = size(
+                                    AvailableSpace::Definite(padded_bounds.size.width),
+                                    AvailableSpace::Definite(item_height),
+                                );
+                                item.draw(item_origin, available_space, cx);
+                            }
+                        });
+                    });
+                }
             },
         )
     }
+}
 
-    fn paint(
-        &mut self,
-        bounds: RectF,
-        visible_bounds: RectF,
-        layout: &mut Self::LayoutState,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> Self::PaintState {
-        let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
-
-        cx.scene().push_layer(Some(visible_bounds));
-
-        cx.scene().push_mouse_region(
-            MouseRegion::new::<Self>(self.view_id, 0, visible_bounds).on_scroll({
-                let scroll_max = layout.scroll_max;
-                let state = self.state.clone();
-                move |event, _, cx| {
-                    let ScrollWheelEvent {
-                        position, delta, ..
-                    } = event.platform_event;
-                    if !Self::scroll(
-                        state.clone(),
-                        position,
-                        *delta.raw(),
-                        delta.precise(),
-                        scroll_max,
-                        cx,
-                    ) {
-                        cx.propagate_event();
-                    }
-                }
-            }),
-        );
+impl IntoElement for UniformList {
+    type Element = Self;
 
-        let mut item_origin = bounds.origin()
-            - vec2f(
-                0.,
-                (self.state.scroll_top() - self.padding_top) % layout.item_height,
-            );
+    fn element_id(&self) -> Option<crate::ElementId> {
+        Some(self.id.clone())
+    }
 
-        for item in &mut layout.items {
-            item.paint(item_origin, visible_bounds, view, cx);
-            item_origin += vec2f(0.0, layout.item_height);
-        }
+    fn into_element(self) -> Self::Element {
+        self
+    }
+}
 
-        cx.scene().pop_layer();
+impl UniformList {
+    pub fn with_width_from_item(mut self, item_index: Option<usize>) -> Self {
+        self.item_to_measure_index = item_index.unwrap_or(0);
+        self
     }
 
-    fn rect_for_text_range(
-        &self,
-        range: Range<usize>,
-        _: RectF,
-        _: RectF,
-        layout: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> Option<RectF> {
-        layout
-            .items
-            .iter()
-            .find_map(|child| child.rect_for_text_range(range.clone(), view, cx))
+    fn measure_item(&self, list_width: Option<Pixels>, cx: &mut WindowContext) -> Size<Pixels> {
+        if self.item_count == 0 {
+            return Size::default();
+        }
+
+        let item_ix = cmp::min(self.item_to_measure_index, self.item_count - 1);
+        let mut items = (self.render_items)(item_ix..item_ix + 1, cx);
+        let mut item_to_measure = items.pop().unwrap();
+        let available_space = size(
+            list_width.map_or(AvailableSpace::MinContent, |width| {
+                AvailableSpace::Definite(width)
+            }),
+            AvailableSpace::MinContent,
+        );
+        item_to_measure.measure(available_space, cx)
     }
 
-    fn debug(
-        &self,
-        bounds: RectF,
-        layout: &Self::LayoutState,
-        _: &Self::PaintState,
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> json::Value {
-        json!({
-            "type": "UniformList",
-            "bounds": bounds.to_json(),
-            "scroll_max": layout.scroll_max,
-            "item_height": layout.item_height,
-            "items": layout.items.iter().map(|item| item.debug(view, cx)).collect::<Vec<json::Value>>()
+    pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self {
+        self.scroll_handle = Some(handle);
+        self
+    }
+}
 
-        })
+impl InteractiveElement for UniformList {
+    fn interactivity(&mut self) -> &mut crate::Interactivity {
+        &mut self.interactivity
     }
 }

crates/gpui/src/executor.rs 🔗

@@ -1,916 +1,372 @@
-use anyhow::{anyhow, Result};
-use async_task::Runnable;
-use futures::channel::mpsc;
-use smol::{channel, prelude::*, Executor};
+use crate::{AppContext, PlatformDispatcher};
+use futures::{channel::mpsc, pin_mut, FutureExt};
+use smol::prelude::*;
 use std::{
-    any::Any,
-    fmt::{self, Display},
+    fmt::Debug,
     marker::PhantomData,
     mem,
-    panic::Location,
+    num::NonZeroUsize,
     pin::Pin,
     rc::Rc,
-    sync::Arc,
+    sync::{
+        atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst},
+        Arc,
+    },
     task::{Context, Poll},
-    thread,
     time::Duration,
 };
+use util::TryFutureExt;
+use waker_fn::waker_fn;
 
-use crate::{
-    platform::{self, Dispatcher},
-    util, AppContext,
-};
+#[cfg(any(test, feature = "test-support"))]
+use rand::rngs::StdRng;
 
-pub enum Foreground {
-    Platform {
-        dispatcher: Arc<dyn platform::Dispatcher>,
-        _not_send_or_sync: PhantomData<Rc<()>>,
-    },
-    #[cfg(any(test, feature = "test-support"))]
-    Deterministic {
-        cx_id: usize,
-        executor: Arc<Deterministic>,
-    },
+#[derive(Clone)]
+pub struct BackgroundExecutor {
+    dispatcher: Arc<dyn PlatformDispatcher>,
 }
 
-pub enum Background {
-    #[cfg(any(test, feature = "test-support"))]
-    Deterministic { executor: Arc<Deterministic> },
-    Production {
-        executor: Arc<smol::Executor<'static>>,
-        _stop: channel::Sender<()>,
-    },
+#[derive(Clone)]
+pub struct ForegroundExecutor {
+    dispatcher: Arc<dyn PlatformDispatcher>,
+    not_send: PhantomData<Rc<()>>,
 }
 
-type AnyLocalFuture = Pin<Box<dyn 'static + Future<Output = Box<dyn Any + 'static>>>>;
-type AnyFuture = Pin<Box<dyn 'static + Send + Future<Output = Box<dyn Any + Send + 'static>>>>;
-type AnyTask = async_task::Task<Box<dyn Any + Send + 'static>>;
-type AnyLocalTask = async_task::Task<Box<dyn Any + 'static>>;
-
 #[must_use]
+#[derive(Debug)]
 pub enum Task<T> {
     Ready(Option<T>),
-    Local {
-        any_task: AnyLocalTask,
-        result_type: PhantomData<T>,
-    },
-    Send {
-        any_task: AnyTask,
-        result_type: PhantomData<T>,
-    },
+    Spawned(async_task::Task<T>),
 }
 
-unsafe impl<T: Send> Send for Task<T> {}
-
-#[cfg(any(test, feature = "test-support"))]
-struct DeterministicState {
-    rng: rand::prelude::StdRng,
-    seed: u64,
-    scheduled_from_foreground: collections::HashMap<usize, Vec<ForegroundRunnable>>,
-    scheduled_from_background: Vec<BackgroundRunnable>,
-    forbid_parking: bool,
-    block_on_ticks: std::ops::RangeInclusive<usize>,
-    now: std::time::Instant,
-    next_timer_id: usize,
-    pending_timers: Vec<(usize, std::time::Instant, postage::barrier::Sender)>,
-    waiting_backtrace: Option<backtrace::Backtrace>,
-    next_runnable_id: usize,
-    poll_history: Vec<ExecutorEvent>,
-    previous_poll_history: Option<Vec<ExecutorEvent>>,
-    enable_runnable_backtraces: bool,
-    runnable_backtraces: collections::HashMap<usize, backtrace::Backtrace>,
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub enum ExecutorEvent {
-    PollRunnable { id: usize },
-    EnqueueRunnable { id: usize },
-}
-
-#[cfg(any(test, feature = "test-support"))]
-struct ForegroundRunnable {
-    id: usize,
-    runnable: Runnable,
-    main: bool,
-}
+impl<T> Task<T> {
+    pub fn ready(val: T) -> Self {
+        Task::Ready(Some(val))
+    }
 
-#[cfg(any(test, feature = "test-support"))]
-struct BackgroundRunnable {
-    id: usize,
-    runnable: Runnable,
+    pub fn detach(self) {
+        match self {
+            Task::Ready(_) => {}
+            Task::Spawned(task) => task.detach(),
+        }
+    }
 }
 
-#[cfg(any(test, feature = "test-support"))]
-pub struct Deterministic {
-    state: Arc<parking_lot::Mutex<DeterministicState>>,
-    parker: parking_lot::Mutex<parking::Parker>,
+impl<E, T> Task<Result<T, E>>
+where
+    T: 'static,
+    E: 'static + Debug,
+{
+    #[track_caller]
+    pub fn detach_and_log_err(self, cx: &mut AppContext) {
+        let location = core::panic::Location::caller();
+        cx.foreground_executor()
+            .spawn(self.log_tracked_err(*location))
+            .detach();
+    }
 }
 
-#[must_use]
-pub enum Timer {
-    Production(smol::Timer),
-    #[cfg(any(test, feature = "test-support"))]
-    Deterministic(DeterministicTimer),
-}
+impl<T> Future for Task<T> {
+    type Output = T;
 
-#[cfg(any(test, feature = "test-support"))]
-pub struct DeterministicTimer {
-    rx: postage::barrier::Receiver,
-    id: usize,
-    state: Arc<parking_lot::Mutex<DeterministicState>>,
+    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
+        match unsafe { self.get_unchecked_mut() } {
+            Task::Ready(val) => Poll::Ready(val.take().unwrap()),
+            Task::Spawned(task) => task.poll(cx),
+        }
+    }
 }
 
-#[cfg(any(test, feature = "test-support"))]
-impl Deterministic {
-    pub fn new(seed: u64) -> Arc<Self> {
-        use rand::prelude::*;
+#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
+pub struct TaskLabel(NonZeroUsize);
 
-        Arc::new(Self {
-            state: Arc::new(parking_lot::Mutex::new(DeterministicState {
-                rng: StdRng::seed_from_u64(seed),
-                seed,
-                scheduled_from_foreground: Default::default(),
-                scheduled_from_background: Default::default(),
-                forbid_parking: false,
-                block_on_ticks: 0..=1000,
-                now: std::time::Instant::now(),
-                next_timer_id: Default::default(),
-                pending_timers: Default::default(),
-                waiting_backtrace: None,
-                next_runnable_id: 0,
-                poll_history: Default::default(),
-                previous_poll_history: Default::default(),
-                enable_runnable_backtraces: false,
-                runnable_backtraces: Default::default(),
-            })),
-            parker: Default::default(),
-        })
+impl Default for TaskLabel {
+    fn default() -> Self {
+        Self::new()
     }
+}
 
-    pub fn execution_history(&self) -> Vec<ExecutorEvent> {
-        self.state.lock().poll_history.clone()
+impl TaskLabel {
+    pub fn new() -> Self {
+        static NEXT_TASK_LABEL: AtomicUsize = AtomicUsize::new(1);
+        Self(NEXT_TASK_LABEL.fetch_add(1, SeqCst).try_into().unwrap())
     }
+}
 
-    pub fn set_previous_execution_history(&self, history: Option<Vec<ExecutorEvent>>) {
-        self.state.lock().previous_poll_history = history;
-    }
+type AnyLocalFuture<R> = Pin<Box<dyn 'static + Future<Output = R>>>;
 
-    pub fn enable_runnable_backtrace(&self) {
-        self.state.lock().enable_runnable_backtraces = true;
-    }
+type AnyFuture<R> = Pin<Box<dyn 'static + Send + Future<Output = R>>>;
 
-    pub fn runnable_backtrace(&self, runnable_id: usize) -> backtrace::Backtrace {
-        let mut backtrace = self.state.lock().runnable_backtraces[&runnable_id].clone();
-        backtrace.resolve();
-        backtrace
+impl BackgroundExecutor {
+    pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
+        Self { dispatcher }
     }
 
-    pub fn build_background(self: &Arc<Self>) -> Arc<Background> {
-        Arc::new(Background::Deterministic {
-            executor: self.clone(),
-        })
+    /// Enqueues the given future to be run to completion on a background thread.
+    pub fn spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
+    where
+        R: Send + 'static,
+    {
+        self.spawn_internal::<R>(Box::pin(future), None)
     }
 
-    pub fn build_foreground(self: &Arc<Self>, id: usize) -> Rc<Foreground> {
-        Rc::new(Foreground::Deterministic {
-            cx_id: id,
-            executor: self.clone(),
-        })
+    /// Enqueues the given future to be run to completion on a background thread.
+    /// The given label can be used to control the priority of the task in tests.
+    pub fn spawn_labeled<R>(
+        &self,
+        label: TaskLabel,
+        future: impl Future<Output = R> + Send + 'static,
+    ) -> Task<R>
+    where
+        R: Send + 'static,
+    {
+        self.spawn_internal::<R>(Box::pin(future), Some(label))
     }
 
-    fn spawn_from_foreground(
+    fn spawn_internal<R: Send + 'static>(
         &self,
-        cx_id: usize,
-        future: AnyLocalFuture,
-        main: bool,
-    ) -> AnyLocalTask {
-        let state = self.state.clone();
-        let id;
-        {
-            let mut state = state.lock();
-            id = util::post_inc(&mut state.next_runnable_id);
-            if state.enable_runnable_backtraces {
-                state
-                    .runnable_backtraces
-                    .insert(id, backtrace::Backtrace::new_unresolved());
-            }
-        }
-
-        let unparker = self.parker.lock().unparker();
-        let (runnable, task) = async_task::spawn_local(future, move |runnable| {
-            let mut state = state.lock();
-            state.push_to_history(ExecutorEvent::EnqueueRunnable { id });
-            state
-                .scheduled_from_foreground
-                .entry(cx_id)
-                .or_default()
-                .push(ForegroundRunnable { id, runnable, main });
-            unparker.unpark();
-        });
+        future: AnyFuture<R>,
+        label: Option<TaskLabel>,
+    ) -> Task<R> {
+        let dispatcher = self.dispatcher.clone();
+        let (runnable, task) =
+            async_task::spawn(future, move |runnable| dispatcher.dispatch(runnable, label));
         runnable.schedule();
-        task
+        Task::Spawned(task)
     }
 
-    fn spawn(&self, future: AnyFuture) -> AnyTask {
-        let state = self.state.clone();
-        let id;
-        {
-            let mut state = state.lock();
-            id = util::post_inc(&mut state.next_runnable_id);
-            if state.enable_runnable_backtraces {
-                state
-                    .runnable_backtraces
-                    .insert(id, backtrace::Backtrace::new_unresolved());
-            }
+    #[cfg(any(test, feature = "test-support"))]
+    #[track_caller]
+    pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R {
+        if let Ok(value) = self.block_internal(false, future, usize::MAX) {
+            value
+        } else {
+            unreachable!()
         }
-
-        let unparker = self.parker.lock().unparker();
-        let (runnable, task) = async_task::spawn(future, move |runnable| {
-            let mut state = state.lock();
-            state
-                .poll_history
-                .push(ExecutorEvent::EnqueueRunnable { id });
-            state
-                .scheduled_from_background
-                .push(BackgroundRunnable { id, runnable });
-            unparker.unpark();
-        });
-        runnable.schedule();
-        task
     }
 
-    fn run<'a>(
-        &self,
-        cx_id: usize,
-        main_future: Pin<Box<dyn 'a + Future<Output = Box<dyn Any>>>>,
-    ) -> Box<dyn Any> {
-        use std::sync::atomic::{AtomicBool, Ordering::SeqCst};
-
-        let woken = Arc::new(AtomicBool::new(false));
-
-        let state = self.state.clone();
-        let id;
-        {
-            let mut state = state.lock();
-            id = util::post_inc(&mut state.next_runnable_id);
-            if state.enable_runnable_backtraces {
-                state
-                    .runnable_backtraces
-                    .insert(id, backtrace::Backtrace::new_unresolved());
-            }
-        }
-
-        let unparker = self.parker.lock().unparker();
-        let (runnable, mut main_task) = unsafe {
-            async_task::spawn_unchecked(main_future, move |runnable| {
-                let state = &mut *state.lock();
-                state
-                    .scheduled_from_foreground
-                    .entry(cx_id)
-                    .or_default()
-                    .push(ForegroundRunnable {
-                        id: util::post_inc(&mut state.next_runnable_id),
-                        runnable,
-                        main: true,
-                    });
-                unparker.unpark();
-            })
-        };
-        runnable.schedule();
-
-        loop {
-            if let Some(result) = self.run_internal(woken.clone(), Some(&mut main_task)) {
-                return result;
-            }
-
-            if !woken.load(SeqCst) {
-                self.state.lock().will_park();
-            }
-
-            woken.store(false, SeqCst);
-            self.parker.lock().park();
+    pub fn block<R>(&self, future: impl Future<Output = R>) -> R {
+        if let Ok(value) = self.block_internal(true, future, usize::MAX) {
+            value
+        } else {
+            unreachable!()
         }
     }
 
-    pub fn run_until_parked(&self) {
-        use std::sync::atomic::AtomicBool;
-        let woken = Arc::new(AtomicBool::new(false));
-        self.run_internal(woken, None);
-    }
-
-    fn run_internal(
+    #[track_caller]
+    pub(crate) fn block_internal<R>(
         &self,
-        woken: Arc<std::sync::atomic::AtomicBool>,
-        mut main_task: Option<&mut AnyLocalTask>,
-    ) -> Option<Box<dyn Any>> {
-        use rand::prelude::*;
-        use std::sync::atomic::Ordering::SeqCst;
-
-        let unparker = self.parker.lock().unparker();
-        let waker = waker_fn::waker_fn(move || {
-            woken.store(true, SeqCst);
-            unparker.unpark();
+        background_only: bool,
+        future: impl Future<Output = R>,
+        mut max_ticks: usize,
+    ) -> Result<R, ()> {
+        pin_mut!(future);
+        let unparker = self.dispatcher.unparker();
+        let awoken = Arc::new(AtomicBool::new(false));
+
+        let waker = waker_fn({
+            let awoken = awoken.clone();
+            move || {
+                awoken.store(true, SeqCst);
+                unparker.unpark();
+            }
         });
+        let mut cx = std::task::Context::from_waker(&waker);
 
-        let mut cx = Context::from_waker(&waker);
         loop {
-            let mut state = self.state.lock();
-
-            if state.scheduled_from_foreground.is_empty()
-                && state.scheduled_from_background.is_empty()
-            {
-                if let Some(main_task) = main_task {
-                    if let Poll::Ready(result) = main_task.poll(&mut cx) {
-                        return Some(result);
+            match future.as_mut().poll(&mut cx) {
+                Poll::Ready(result) => return Ok(result),
+                Poll::Pending => {
+                    if max_ticks == 0 {
+                        return Err(());
                     }
-                }
+                    max_ticks -= 1;
 
-                return None;
-            }
-
-            if !state.scheduled_from_background.is_empty() && state.rng.gen() {
-                let background_len = state.scheduled_from_background.len();
-                let ix = state.rng.gen_range(0..background_len);
-                let background_runnable = state.scheduled_from_background.remove(ix);
-                state.push_to_history(ExecutorEvent::PollRunnable {
-                    id: background_runnable.id,
-                });
-                drop(state);
-                background_runnable.runnable.run();
-            } else if !state.scheduled_from_foreground.is_empty() {
-                let available_cx_ids = state
-                    .scheduled_from_foreground
-                    .keys()
-                    .copied()
-                    .collect::<Vec<_>>();
-                let cx_id_to_run = *available_cx_ids.iter().choose(&mut state.rng).unwrap();
-                let scheduled_from_cx = state
-                    .scheduled_from_foreground
-                    .get_mut(&cx_id_to_run)
-                    .unwrap();
-                let foreground_runnable = scheduled_from_cx.remove(0);
-                if scheduled_from_cx.is_empty() {
-                    state.scheduled_from_foreground.remove(&cx_id_to_run);
-                }
-                state.push_to_history(ExecutorEvent::PollRunnable {
-                    id: foreground_runnable.id,
-                });
-
-                drop(state);
-
-                foreground_runnable.runnable.run();
-                if let Some(main_task) = main_task.as_mut() {
-                    if foreground_runnable.main {
-                        if let Poll::Ready(result) = main_task.poll(&mut cx) {
-                            return Some(result);
+                    if !self.dispatcher.tick(background_only) {
+                        if awoken.swap(false, SeqCst) {
+                            continue;
                         }
-                    }
-                }
-            }
-        }
-    }
 
-    fn block<F, T>(&self, future: &mut F, max_ticks: usize) -> Option<T>
-    where
-        F: Unpin + Future<Output = T>,
-    {
-        use rand::prelude::*;
-
-        let unparker = self.parker.lock().unparker();
-        let waker = waker_fn::waker_fn(move || {
-            unparker.unpark();
-        });
-
-        let mut cx = Context::from_waker(&waker);
-        for _ in 0..max_ticks {
-            let mut state = self.state.lock();
-            let runnable_count = state.scheduled_from_background.len();
-            let ix = state.rng.gen_range(0..=runnable_count);
-            if ix < state.scheduled_from_background.len() {
-                let background_runnable = state.scheduled_from_background.remove(ix);
-                state.push_to_history(ExecutorEvent::PollRunnable {
-                    id: background_runnable.id,
-                });
-                drop(state);
-                background_runnable.runnable.run();
-            } else {
-                drop(state);
-                if let Poll::Ready(result) = future.poll(&mut cx) {
-                    return Some(result);
-                }
-                let mut state = self.state.lock();
-                if state.scheduled_from_background.is_empty() {
-                    state.will_park();
-                    drop(state);
-                    self.parker.lock().park();
-                }
-
-                continue;
-            }
-        }
-
-        None
-    }
-
-    pub fn timer(&self, duration: Duration) -> Timer {
-        let (tx, rx) = postage::barrier::channel();
-        let mut state = self.state.lock();
-        let wakeup_at = state.now + duration;
-        let id = util::post_inc(&mut state.next_timer_id);
-        match state
-            .pending_timers
-            .binary_search_by_key(&wakeup_at, |e| e.1)
-        {
-            Ok(ix) | Err(ix) => state.pending_timers.insert(ix, (id, wakeup_at, tx)),
-        }
-        let state = self.state.clone();
-        Timer::Deterministic(DeterministicTimer { rx, id, state })
-    }
-
-    pub fn now(&self) -> std::time::Instant {
-        let state = self.state.lock();
-        state.now
-    }
-
-    pub fn advance_clock(&self, duration: Duration) {
-        let new_now = self.state.lock().now + duration;
-        loop {
-            self.run_until_parked();
-            let mut state = self.state.lock();
+                        #[cfg(any(test, feature = "test-support"))]
+                        if let Some(test) = self.dispatcher.as_test() {
+                            if !test.parking_allowed() {
+                                let mut backtrace_message = String::new();
+                                if let Some(backtrace) = test.waiting_backtrace() {
+                                    backtrace_message =
+                                        format!("\nbacktrace of waiting future:\n{:?}", backtrace);
+                                }
+                                panic!("parked with nothing left to run\n{:?}", backtrace_message)
+                            }
+                        }
 
-            if let Some((_, wakeup_time, _)) = state.pending_timers.first() {
-                let wakeup_time = *wakeup_time;
-                if wakeup_time <= new_now {
-                    let timer_count = state
-                        .pending_timers
-                        .iter()
-                        .take_while(|(_, t, _)| *t == wakeup_time)
-                        .count();
-                    state.now = wakeup_time;
-                    let timers_to_wake = state
-                        .pending_timers
-                        .drain(0..timer_count)
-                        .collect::<Vec<_>>();
-                    drop(state);
-                    drop(timers_to_wake);
-                    continue;
+                        self.dispatcher.park();
+                    }
                 }
             }
-
-            break;
         }
-
-        self.state.lock().now = new_now;
     }
 
-    pub fn start_waiting(&self) {
-        self.state.lock().waiting_backtrace = Some(backtrace::Backtrace::new_unresolved());
-    }
-
-    pub fn finish_waiting(&self) {
-        self.state.lock().waiting_backtrace.take();
-    }
-
-    pub fn forbid_parking(&self) {
-        use rand::prelude::*;
-
-        let mut state = self.state.lock();
-        state.forbid_parking = true;
-        state.rng = StdRng::seed_from_u64(state.seed);
-    }
-
-    pub fn allow_parking(&self) {
-        use rand::prelude::*;
-
-        let mut state = self.state.lock();
-        state.forbid_parking = false;
-        state.rng = StdRng::seed_from_u64(state.seed);
-    }
-
-    pub async fn simulate_random_delay(&self) {
-        use rand::prelude::*;
-        use smol::future::yield_now;
-        if self.state.lock().rng.gen_bool(0.2) {
-            let yields = self.state.lock().rng.gen_range(1..=10);
-            for _ in 0..yields {
-                yield_now().await;
-            }
-        }
-    }
-
-    pub fn record_backtrace(&self) {
-        let mut state = self.state.lock();
-        if state.enable_runnable_backtraces {
-            let current_id = state
-                .poll_history
-                .iter()
-                .rev()
-                .find_map(|event| match event {
-                    ExecutorEvent::PollRunnable { id } => Some(*id),
-                    _ => None,
-                });
-            if let Some(id) = current_id {
-                state
-                    .runnable_backtraces
-                    .insert(id, backtrace::Backtrace::new_unresolved());
-            }
+    pub fn block_with_timeout<R>(
+        &self,
+        duration: Duration,
+        future: impl Future<Output = R>,
+    ) -> Result<R, impl Future<Output = R>> {
+        let mut future = Box::pin(future.fuse());
+        if duration.is_zero() {
+            return Err(future);
         }
-    }
-}
 
-impl Drop for Timer {
-    fn drop(&mut self) {
         #[cfg(any(test, feature = "test-support"))]
-        if let Timer::Deterministic(DeterministicTimer { state, id, .. }) = self {
-            state
-                .lock()
-                .pending_timers
-                .retain(|(timer_id, _, _)| timer_id != id)
-        }
-    }
-}
-
-impl Future for Timer {
-    type Output = ();
-
-    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
-        match &mut *self {
-            #[cfg(any(test, feature = "test-support"))]
-            Self::Deterministic(DeterministicTimer { rx, .. }) => {
-                use postage::stream::{PollRecv, Stream as _};
-                smol::pin!(rx);
-                match rx.poll_recv(&mut postage::Context::from_waker(cx.waker())) {
-                    PollRecv::Ready(()) | PollRecv::Closed => Poll::Ready(()),
-                    PollRecv::Pending => Poll::Pending,
-                }
+        let max_ticks = self
+            .dispatcher
+            .as_test()
+            .map_or(usize::MAX, |dispatcher| dispatcher.gen_block_on_ticks());
+        #[cfg(not(any(test, feature = "test-support")))]
+        let max_ticks = usize::MAX;
+
+        let mut timer = self.timer(duration).fuse();
+
+        let timeout = async {
+            futures::select_biased! {
+                value = future => Ok(value),
+                _ = timer => Err(()),
             }
-            Self::Production(timer) => {
-                smol::pin!(timer);
-                match timer.poll(cx) {
-                    Poll::Ready(_) => Poll::Ready(()),
-                    Poll::Pending => Poll::Pending,
-                }
-            }
-        }
-    }
-}
-
-#[cfg(any(test, feature = "test-support"))]
-impl DeterministicState {
-    fn push_to_history(&mut self, event: ExecutorEvent) {
-        use std::fmt::Write as _;
-
-        self.poll_history.push(event);
-        if let Some(prev_history) = &self.previous_poll_history {
-            let ix = self.poll_history.len() - 1;
-            let prev_event = prev_history[ix];
-            if event != prev_event {
-                let mut message = String::new();
-                writeln!(
-                    &mut message,
-                    "current runnable backtrace:\n{:?}",
-                    self.runnable_backtraces.get_mut(&event.id()).map(|trace| {
-                        trace.resolve();
-                        util::CwdBacktrace(trace)
-                    })
-                )
-                .unwrap();
-                writeln!(
-                    &mut message,
-                    "previous runnable backtrace:\n{:?}",
-                    self.runnable_backtraces
-                        .get_mut(&prev_event.id())
-                        .map(|trace| {
-                            trace.resolve();
-                            util::CwdBacktrace(trace)
-                        })
-                )
-                .unwrap();
-                panic!("detected non-determinism after {ix}. {message}");
-            }
-        }
-    }
-
-    fn will_park(&mut self) {
-        if self.forbid_parking {
-            let mut backtrace_message = String::new();
-            #[cfg(any(test, feature = "test-support"))]
-            if let Some(backtrace) = self.waiting_backtrace.as_mut() {
-                backtrace.resolve();
-                backtrace_message = format!(
-                    "\nbacktrace of waiting future:\n{:?}",
-                    util::CwdBacktrace(backtrace)
-                );
-            }
-
-            panic!(
-                "deterministic executor parked after a call to forbid_parking{}",
-                backtrace_message
-            );
-        }
-    }
-}
-
-#[cfg(any(test, feature = "test-support"))]
-impl ExecutorEvent {
-    pub fn id(&self) -> usize {
-        match self {
-            ExecutorEvent::PollRunnable { id } => *id,
-            ExecutorEvent::EnqueueRunnable { id } => *id,
+        };
+        match self.block_internal(true, timeout, max_ticks) {
+            Ok(Ok(value)) => Ok(value),
+            _ => Err(future),
         }
     }
-}
 
-impl Foreground {
-    pub fn platform(dispatcher: Arc<dyn platform::Dispatcher>) -> Result<Self> {
-        if dispatcher.is_main_thread() {
-            Ok(Self::Platform {
-                dispatcher,
-                _not_send_or_sync: PhantomData,
-            })
-        } else {
-            Err(anyhow!("must be constructed on main thread"))
+    pub async fn scoped<'scope, F>(&self, scheduler: F)
+    where
+        F: FnOnce(&mut Scope<'scope>),
+    {
+        let mut scope = Scope::new(self.clone());
+        (scheduler)(&mut scope);
+        let spawned = mem::take(&mut scope.futures)
+            .into_iter()
+            .map(|f| self.spawn(f))
+            .collect::<Vec<_>>();
+        for task in spawned {
+            task.await;
         }
     }
 
-    pub fn spawn<T: 'static>(&self, future: impl Future<Output = T> + 'static) -> Task<T> {
-        let future = any_local_future(future);
-        let any_task = match self {
-            #[cfg(any(test, feature = "test-support"))]
-            Self::Deterministic { cx_id, executor } => {
-                executor.spawn_from_foreground(*cx_id, future, false)
-            }
-            Self::Platform { dispatcher, .. } => {
-                fn spawn_inner(
-                    future: AnyLocalFuture,
-                    dispatcher: &Arc<dyn Dispatcher>,
-                ) -> AnyLocalTask {
-                    let dispatcher = dispatcher.clone();
-                    let schedule =
-                        move |runnable: Runnable| dispatcher.run_on_main_thread(runnable);
-                    let (runnable, task) = async_task::spawn_local(future, schedule);
-                    runnable.schedule();
-                    task
-                }
-                spawn_inner(future, dispatcher)
-            }
-        };
-        Task::local(any_task)
+    pub fn timer(&self, duration: Duration) -> Task<()> {
+        let (runnable, task) = async_task::spawn(async move {}, {
+            let dispatcher = self.dispatcher.clone();
+            move |runnable| dispatcher.dispatch_after(duration, runnable)
+        });
+        runnable.schedule();
+        Task::Spawned(task)
     }
 
     #[cfg(any(test, feature = "test-support"))]
-    pub fn run<T: 'static>(&self, future: impl Future<Output = T>) -> T {
-        let future = async move { Box::new(future.await) as Box<dyn Any> }.boxed_local();
-        let result = match self {
-            Self::Deterministic { cx_id, executor } => executor.run(*cx_id, future),
-            Self::Platform { .. } => panic!("you can't call run on a platform foreground executor"),
-        };
-        *result.downcast().unwrap()
+    pub fn start_waiting(&self) {
+        self.dispatcher.as_test().unwrap().start_waiting();
     }
 
     #[cfg(any(test, feature = "test-support"))]
-    pub fn run_until_parked(&self) {
-        match self {
-            Self::Deterministic { executor, .. } => executor.run_until_parked(),
-            _ => panic!("this method can only be called on a deterministic executor"),
-        }
+    pub fn finish_waiting(&self) {
+        self.dispatcher.as_test().unwrap().finish_waiting();
     }
 
     #[cfg(any(test, feature = "test-support"))]
-    pub fn parking_forbidden(&self) -> bool {
-        match self {
-            Self::Deterministic { executor, .. } => executor.state.lock().forbid_parking,
-            _ => panic!("this method can only be called on a deterministic executor"),
-        }
+    pub fn simulate_random_delay(&self) -> impl Future<Output = ()> {
+        self.dispatcher.as_test().unwrap().simulate_random_delay()
     }
 
     #[cfg(any(test, feature = "test-support"))]
-    pub fn start_waiting(&self) {
-        match self {
-            Self::Deterministic { executor, .. } => executor.start_waiting(),
-            _ => panic!("this method can only be called on a deterministic executor"),
-        }
+    pub fn deprioritize(&self, task_label: TaskLabel) {
+        self.dispatcher.as_test().unwrap().deprioritize(task_label)
     }
 
     #[cfg(any(test, feature = "test-support"))]
-    pub fn finish_waiting(&self) {
-        match self {
-            Self::Deterministic { executor, .. } => executor.finish_waiting(),
-            _ => panic!("this method can only be called on a deterministic executor"),
-        }
+    pub fn advance_clock(&self, duration: Duration) {
+        self.dispatcher.as_test().unwrap().advance_clock(duration)
     }
 
     #[cfg(any(test, feature = "test-support"))]
-    pub fn forbid_parking(&self) {
-        match self {
-            Self::Deterministic { executor, .. } => executor.forbid_parking(),
-            _ => panic!("this method can only be called on a deterministic executor"),
-        }
+    pub fn tick(&self) -> bool {
+        self.dispatcher.as_test().unwrap().tick(false)
     }
 
     #[cfg(any(test, feature = "test-support"))]
-    pub fn allow_parking(&self) {
-        match self {
-            Self::Deterministic { executor, .. } => executor.allow_parking(),
-            _ => panic!("this method can only be called on a deterministic executor"),
-        }
+    pub fn run_until_parked(&self) {
+        self.dispatcher.as_test().unwrap().run_until_parked()
     }
 
     #[cfg(any(test, feature = "test-support"))]
-    pub fn advance_clock(&self, duration: Duration) {
-        match self {
-            Self::Deterministic { executor, .. } => executor.advance_clock(duration),
-            _ => panic!("this method can only be called on a deterministic executor"),
-        }
+    pub fn allow_parking(&self) {
+        self.dispatcher.as_test().unwrap().allow_parking();
     }
 
     #[cfg(any(test, feature = "test-support"))]
-    pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {
-        match self {
-            Self::Deterministic { executor, .. } => executor.state.lock().block_on_ticks = range,
-            _ => panic!("this method can only be called on a deterministic executor"),
-        }
-    }
-}
-
-impl Background {
-    pub fn new() -> Self {
-        let executor = Arc::new(Executor::new());
-        let stop = channel::unbounded::<()>();
-
-        for i in 0..2 * num_cpus::get() {
-            let executor = executor.clone();
-            let stop = stop.1.clone();
-            thread::Builder::new()
-                .name(format!("background-executor-{}", i))
-                .spawn(move || smol::block_on(executor.run(stop.recv())))
-                .unwrap();
-        }
-
-        Self::Production {
-            executor,
-            _stop: stop.0,
-        }
+    pub fn rng(&self) -> StdRng {
+        self.dispatcher.as_test().unwrap().rng()
     }
 
     pub fn num_cpus(&self) -> usize {
         num_cpus::get()
     }
 
-    pub fn spawn<T, F>(&self, future: F) -> Task<T>
-    where
-        T: 'static + Send,
-        F: Send + Future<Output = T> + 'static,
-    {
-        let future = any_future(future);
-        let any_task = match self {
-            Self::Production { executor, .. } => executor.spawn(future),
-            #[cfg(any(test, feature = "test-support"))]
-            Self::Deterministic { executor } => executor.spawn(future),
-        };
-        Task::send(any_task)
+    pub fn is_main_thread(&self) -> bool {
+        self.dispatcher.is_main_thread()
     }
 
-    pub fn block<F, T>(&self, future: F) -> T
-    where
-        F: Future<Output = T>,
-    {
-        smol::pin!(future);
-        match self {
-            Self::Production { .. } => smol::block_on(&mut future),
-            #[cfg(any(test, feature = "test-support"))]
-            Self::Deterministic { executor, .. } => {
-                executor.block(&mut future, usize::MAX).unwrap()
-            }
-        }
+    #[cfg(any(test, feature = "test-support"))]
+    pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {
+        self.dispatcher.as_test().unwrap().set_block_on_ticks(range);
     }
+}
 
-    pub fn block_with_timeout<F, T>(
-        &self,
-        timeout: Duration,
-        future: F,
-    ) -> Result<T, impl Future<Output = T>>
-    where
-        T: 'static,
-        F: 'static + Unpin + Future<Output = T>,
-    {
-        let mut future = any_local_future(future);
-        if !timeout.is_zero() {
-            let output = match self {
-                Self::Production { .. } => smol::block_on(util::timeout(timeout, &mut future)).ok(),
-                #[cfg(any(test, feature = "test-support"))]
-                Self::Deterministic { executor, .. } => {
-                    use rand::prelude::*;
-                    let max_ticks = {
-                        let mut state = executor.state.lock();
-                        let range = state.block_on_ticks.clone();
-                        state.rng.gen_range(range)
-                    };
-                    executor.block(&mut future, max_ticks)
-                }
-            };
-            if let Some(output) = output {
-                return Ok(*output.downcast().unwrap());
-            }
+impl ForegroundExecutor {
+    pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
+        Self {
+            dispatcher,
+            not_send: PhantomData,
         }
-        Err(async { *future.await.downcast().unwrap() })
     }
 
-    pub async fn scoped<'scope, F>(self: &Arc<Self>, scheduler: F)
+    /// Enqueues the given closure to be run on any thread. The closure returns
+    /// a future which will be run to completion on any available thread.
+    pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
     where
-        F: FnOnce(&mut Scope<'scope>),
+        R: 'static,
     {
-        let mut scope = Scope::new(self.clone());
-        (scheduler)(&mut scope);
-        let spawned = mem::take(&mut scope.futures)
-            .into_iter()
-            .map(|f| self.spawn(f))
-            .collect::<Vec<_>>();
-        for task in spawned {
-            task.await;
-        }
-    }
-
-    pub fn timer(&self, duration: Duration) -> Timer {
-        match self {
-            Background::Production { .. } => Timer::Production(smol::Timer::after(duration)),
-            #[cfg(any(test, feature = "test-support"))]
-            Background::Deterministic { executor } => executor.timer(duration),
-        }
-    }
-
-    pub fn now(&self) -> std::time::Instant {
-        match self {
-            Background::Production { .. } => std::time::Instant::now(),
-            #[cfg(any(test, feature = "test-support"))]
-            Background::Deterministic { executor } => executor.now(),
-        }
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn rng<'a>(&'a self) -> impl 'a + std::ops::DerefMut<Target = rand::prelude::StdRng> {
-        match self {
-            Self::Deterministic { executor, .. } => {
-                parking_lot::lock_api::MutexGuard::map(executor.state.lock(), |s| &mut s.rng)
-            }
-            _ => panic!("this method can only be called on a deterministic executor"),
-        }
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub async fn simulate_random_delay(&self) {
-        match self {
-            Self::Deterministic { executor, .. } => {
-                executor.simulate_random_delay().await;
-            }
-            _ => {
-                panic!("this method can only be called on a deterministic executor")
-            }
-        }
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn record_backtrace(&self) {
-        match self {
-            Self::Deterministic { executor, .. } => executor.record_backtrace(),
-            _ => {
-                panic!("this method can only be called on a deterministic executor")
-            }
-        }
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn start_waiting(&self) {
-        match self {
-            Self::Deterministic { executor, .. } => executor.start_waiting(),
-            _ => panic!("this method can only be called on a deterministic executor"),
-        }
-    }
-}
-
-impl Default for Background {
-    fn default() -> Self {
-        Self::new()
+        let dispatcher = self.dispatcher.clone();
+        fn inner<R: 'static>(
+            dispatcher: Arc<dyn PlatformDispatcher>,
+            future: AnyLocalFuture<R>,
+        ) -> Task<R> {
+            let (runnable, task) = async_task::spawn_local(future, move |runnable| {
+                dispatcher.dispatch_on_main_thread(runnable)
+            });
+            runnable.schedule();
+            Task::Spawned(task)
+        }
+        inner::<R>(dispatcher, Box::pin(future))
     }
 }
 
 pub struct Scope<'a> {
-    executor: Arc<Background>,
+    executor: BackgroundExecutor,
     futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
     tx: Option<mpsc::Sender<()>>,
     rx: mpsc::Receiver<()>,
-    _phantom: PhantomData<&'a ()>,
+    lifetime: PhantomData<&'a ()>,
 }
 
 impl<'a> Scope<'a> {
-    fn new(executor: Arc<Background>) -> Self {
+    fn new(executor: BackgroundExecutor) -> Self {
         let (tx, rx) = mpsc::channel(1);
         Self {
             executor,
             tx: Some(tx),
             rx,
             futures: Default::default(),
-            _phantom: PhantomData,
+            lifetime: PhantomData,
         }
     }
 

crates/gpui/src/font_cache.rs 🔗

@@ -1,330 +0,0 @@
-use crate::{
-    fonts::{Features, FontId, Metrics, Properties},
-    geometry::vector::{vec2f, Vector2F},
-    platform,
-    text_layout::LineWrapper,
-};
-use anyhow::{anyhow, Result};
-use ordered_float::OrderedFloat;
-use parking_lot::{RwLock, RwLockUpgradableReadGuard};
-use schemars::JsonSchema;
-use std::{
-    collections::HashMap,
-    ops::{Deref, DerefMut},
-    sync::Arc,
-};
-
-#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, JsonSchema)]
-pub struct FamilyId(usize);
-
-struct Family {
-    name: Arc<str>,
-    font_features: Features,
-    font_ids: Vec<FontId>,
-}
-
-pub struct FontCache(RwLock<FontCacheState>);
-
-pub struct FontCacheState {
-    font_system: Arc<dyn platform::FontSystem>,
-    families: Vec<Family>,
-    default_family: Option<FamilyId>,
-    font_selections: HashMap<FamilyId, HashMap<Properties, FontId>>,
-    metrics: HashMap<FontId, Metrics>,
-    wrapper_pool: HashMap<(FontId, OrderedFloat<f32>), Vec<LineWrapper>>,
-}
-
-pub struct LineWrapperHandle {
-    wrapper: Option<LineWrapper>,
-    font_cache: Arc<FontCache>,
-}
-
-unsafe impl Send for FontCache {}
-
-impl FontCache {
-    pub fn new(fonts: Arc<dyn platform::FontSystem>) -> Self {
-        Self(RwLock::new(FontCacheState {
-            font_system: fonts,
-            families: Default::default(),
-            default_family: None,
-            font_selections: Default::default(),
-            metrics: Default::default(),
-            wrapper_pool: Default::default(),
-        }))
-    }
-
-    pub fn family_name(&self, family_id: FamilyId) -> Result<Arc<str>> {
-        self.0
-            .read()
-            .families
-            .get(family_id.0)
-            .ok_or_else(|| anyhow!("invalid family id"))
-            .map(|family| family.name.clone())
-    }
-
-    pub fn load_family(&self, names: &[&str], features: &Features) -> Result<FamilyId> {
-        for name in names {
-            let state = self.0.upgradable_read();
-
-            if let Some(ix) = state
-                .families
-                .iter()
-                .position(|f| f.name.as_ref() == *name && f.font_features == *features)
-            {
-                return Ok(FamilyId(ix));
-            }
-
-            let mut state = RwLockUpgradableReadGuard::upgrade(state);
-
-            if let Ok(font_ids) = state.font_system.load_family(name, features) {
-                if font_ids.is_empty() {
-                    continue;
-                }
-
-                let family_id = FamilyId(state.families.len());
-                for font_id in &font_ids {
-                    if state.font_system.glyph_for_char(*font_id, 'm').is_none() {
-                        return Err(anyhow!("font must contain a glyph for the 'm' character"));
-                    }
-                }
-
-                state.families.push(Family {
-                    name: Arc::from(*name),
-                    font_features: features.clone(),
-                    font_ids,
-                });
-                return Ok(family_id);
-            }
-        }
-
-        Err(anyhow!(
-            "could not find a non-empty font family matching one of the given names: {}",
-            names
-                .iter()
-                .map(|name| format!("`{name}`"))
-                .collect::<Vec<_>>()
-                .join(", ")
-        ))
-    }
-
-    /// Returns an arbitrary font family that is available on the system.
-    pub fn known_existing_family(&self) -> FamilyId {
-        if let Some(family_id) = self.0.read().default_family {
-            return family_id;
-        }
-
-        let default_family = self
-            .load_family(
-                &["Courier", "Helvetica", "Arial", "Verdana"],
-                &Default::default(),
-            )
-            .unwrap_or_else(|_| {
-                let all_family_names = self.0.read().font_system.all_families();
-                let all_family_names: Vec<_> = all_family_names
-                    .iter()
-                    .map(|string| string.as_str())
-                    .collect();
-                self.load_family(&all_family_names, &Default::default())
-                    .expect("could not load any default font family")
-            });
-
-        self.0.write().default_family = Some(default_family);
-        default_family
-    }
-
-    pub fn default_font(&self, family_id: FamilyId) -> FontId {
-        self.select_font(family_id, &Properties::default()).unwrap()
-    }
-
-    pub fn select_font(&self, family_id: FamilyId, properties: &Properties) -> Result<FontId> {
-        let inner = self.0.upgradable_read();
-        if let Some(font_id) = inner
-            .font_selections
-            .get(&family_id)
-            .and_then(|f| f.get(properties))
-        {
-            Ok(*font_id)
-        } else {
-            let mut inner = RwLockUpgradableReadGuard::upgrade(inner);
-            let family = &inner.families[family_id.0];
-            let font_id = inner
-                .font_system
-                .select_font(&family.font_ids, properties)
-                .unwrap_or(family.font_ids[0]);
-
-            inner
-                .font_selections
-                .entry(family_id)
-                .or_default()
-                .insert(*properties, font_id);
-            Ok(font_id)
-        }
-    }
-
-    pub fn metric<F, T>(&self, font_id: FontId, f: F) -> T
-    where
-        F: FnOnce(&Metrics) -> T,
-        T: 'static,
-    {
-        let state = self.0.upgradable_read();
-        if let Some(metrics) = state.metrics.get(&font_id) {
-            f(metrics)
-        } else {
-            let metrics = state.font_system.font_metrics(font_id);
-            let metric = f(&metrics);
-            let mut state = RwLockUpgradableReadGuard::upgrade(state);
-            state.metrics.insert(font_id, metrics);
-            metric
-        }
-    }
-
-    pub fn bounding_box(&self, font_id: FontId, font_size: f32) -> Vector2F {
-        let bounding_box = self.metric(font_id, |m| m.bounding_box);
-        let width = bounding_box.width() * self.em_scale(font_id, font_size);
-        let height = bounding_box.height() * self.em_scale(font_id, font_size);
-        vec2f(width, height)
-    }
-
-    pub fn em_width(&self, font_id: FontId, font_size: f32) -> f32 {
-        let glyph_id;
-        let bounds;
-        {
-            let state = self.0.read();
-            glyph_id = state.font_system.glyph_for_char(font_id, 'm').unwrap();
-            bounds = state
-                .font_system
-                .typographic_bounds(font_id, glyph_id)
-                .unwrap();
-        }
-        bounds.width() * self.em_scale(font_id, font_size)
-    }
-
-    pub fn em_advance(&self, font_id: FontId, font_size: f32) -> f32 {
-        let glyph_id;
-        let advance;
-        {
-            let state = self.0.read();
-            glyph_id = state.font_system.glyph_for_char(font_id, 'm').unwrap();
-            advance = state.font_system.advance(font_id, glyph_id).unwrap();
-        }
-        advance.x() * self.em_scale(font_id, font_size)
-    }
-
-    pub fn line_height(&self, font_size: f32) -> f32 {
-        (font_size * 1.618).round()
-    }
-
-    pub fn cap_height(&self, font_id: FontId, font_size: f32) -> f32 {
-        self.metric(font_id, |m| m.cap_height) * self.em_scale(font_id, font_size)
-    }
-
-    pub fn x_height(&self, font_id: FontId, font_size: f32) -> f32 {
-        self.metric(font_id, |m| m.x_height) * self.em_scale(font_id, font_size)
-    }
-
-    pub fn ascent(&self, font_id: FontId, font_size: f32) -> f32 {
-        self.metric(font_id, |m| m.ascent) * self.em_scale(font_id, font_size)
-    }
-
-    pub fn descent(&self, font_id: FontId, font_size: f32) -> f32 {
-        self.metric(font_id, |m| -m.descent) * self.em_scale(font_id, font_size)
-    }
-
-    pub fn em_scale(&self, font_id: FontId, font_size: f32) -> f32 {
-        font_size / self.metric(font_id, |m| m.units_per_em as f32)
-    }
-
-    pub fn baseline_offset(&self, font_id: FontId, font_size: f32) -> f32 {
-        let line_height = self.line_height(font_size);
-        let ascent = self.ascent(font_id, font_size);
-        let descent = self.descent(font_id, font_size);
-        let padding_top = (line_height - ascent - descent) / 2.;
-        padding_top + ascent
-    }
-
-    pub fn line_wrapper(self: &Arc<Self>, font_id: FontId, font_size: f32) -> LineWrapperHandle {
-        let mut state = self.0.write();
-        let wrappers = state
-            .wrapper_pool
-            .entry((font_id, OrderedFloat(font_size)))
-            .or_default();
-        let wrapper = wrappers
-            .pop()
-            .unwrap_or_else(|| LineWrapper::new(font_id, font_size, state.font_system.clone()));
-        LineWrapperHandle {
-            wrapper: Some(wrapper),
-            font_cache: self.clone(),
-        }
-    }
-}
-
-impl Drop for LineWrapperHandle {
-    fn drop(&mut self) {
-        let mut state = self.font_cache.0.write();
-        let wrapper = self.wrapper.take().unwrap();
-        state
-            .wrapper_pool
-            .get_mut(&(wrapper.font_id, OrderedFloat(wrapper.font_size)))
-            .unwrap()
-            .push(wrapper);
-    }
-}
-
-impl Deref for LineWrapperHandle {
-    type Target = LineWrapper;
-
-    fn deref(&self) -> &Self::Target {
-        self.wrapper.as_ref().unwrap()
-    }
-}
-
-impl DerefMut for LineWrapperHandle {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        self.wrapper.as_mut().unwrap()
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::{
-        fonts::{Style, Weight},
-        platform::{test, Platform as _},
-    };
-
-    #[test]
-    fn test_select_font() {
-        let platform = test::platform();
-        let fonts = FontCache::new(platform.fonts());
-        let arial = fonts
-            .load_family(
-                &["Arial"],
-                &Features {
-                    calt: Some(false),
-                    ..Default::default()
-                },
-            )
-            .unwrap();
-        let arial_regular = fonts.select_font(arial, &Properties::new()).unwrap();
-        let arial_italic = fonts
-            .select_font(arial, Properties::new().style(Style::Italic))
-            .unwrap();
-        let arial_bold = fonts
-            .select_font(arial, Properties::new().weight(Weight::BOLD))
-            .unwrap();
-        assert_ne!(arial_regular, arial_italic);
-        assert_ne!(arial_regular, arial_bold);
-        assert_ne!(arial_italic, arial_bold);
-
-        let arial_with_calt = fonts
-            .load_family(
-                &["Arial"],
-                &Features {
-                    calt: Some(true),
-                    ..Default::default()
-                },
-            )
-            .unwrap();
-        assert_ne!(arial_with_calt, arial);
-    }
-}

crates/gpui/src/fonts.rs 🔗

@@ -1,636 +0,0 @@
-use crate::{
-    color::Color,
-    font_cache::FamilyId,
-    json::{json, ToJson},
-    text_layout::RunStyle,
-    FontCache,
-};
-use anyhow::{anyhow, Result};
-pub use font_kit::{
-    metrics::Metrics,
-    properties::{Properties, Stretch, Style, Weight},
-};
-use ordered_float::OrderedFloat;
-use refineable::Refineable;
-use schemars::JsonSchema;
-use serde::{de, Deserialize, Serialize};
-use serde_json::Value;
-use std::{cell::RefCell, sync::Arc};
-
-#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, JsonSchema)]
-pub struct FontId(pub usize);
-
-pub type GlyphId = u32;
-
-#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
-pub struct Features {
-    pub calt: Option<bool>,
-    pub case: Option<bool>,
-    pub cpsp: Option<bool>,
-    pub frac: Option<bool>,
-    pub liga: Option<bool>,
-    pub onum: Option<bool>,
-    pub ordn: Option<bool>,
-    pub pnum: Option<bool>,
-    pub ss01: Option<bool>,
-    pub ss02: Option<bool>,
-    pub ss03: Option<bool>,
-    pub ss04: Option<bool>,
-    pub ss05: Option<bool>,
-    pub ss06: Option<bool>,
-    pub ss07: Option<bool>,
-    pub ss08: Option<bool>,
-    pub ss09: Option<bool>,
-    pub ss10: Option<bool>,
-    pub ss11: Option<bool>,
-    pub ss12: Option<bool>,
-    pub ss13: Option<bool>,
-    pub ss14: Option<bool>,
-    pub ss15: Option<bool>,
-    pub ss16: Option<bool>,
-    pub ss17: Option<bool>,
-    pub ss18: Option<bool>,
-    pub ss19: Option<bool>,
-    pub ss20: Option<bool>,
-    pub subs: Option<bool>,
-    pub sups: Option<bool>,
-    pub swsh: Option<bool>,
-    pub titl: Option<bool>,
-    pub tnum: Option<bool>,
-    pub zero: Option<bool>,
-}
-
-#[derive(Clone, Debug, JsonSchema)]
-pub struct TextStyle {
-    pub color: Color,
-    pub font_family_name: Arc<str>,
-    pub font_family_id: FamilyId,
-    pub font_id: FontId,
-    pub font_size: f32,
-    #[schemars(with = "PropertiesDef")]
-    pub font_properties: Properties,
-    pub underline: Underline,
-    pub soft_wrap: bool,
-}
-
-impl TextStyle {
-    pub fn for_color(color: Color) -> Self {
-        Self {
-            color,
-            ..Default::default()
-        }
-    }
-}
-
-impl TextStyle {
-    pub fn refine(
-        &mut self,
-        refinement: &TextStyleRefinement,
-        font_cache: &FontCache,
-    ) -> Result<()> {
-        if let Some(font_size) = refinement.font_size {
-            self.font_size = font_size;
-        }
-        if let Some(color) = refinement.color {
-            self.color = color;
-        }
-        if let Some(underline) = refinement.underline {
-            self.underline = underline;
-        }
-
-        let mut update_font_id = false;
-        if let Some(font_family) = refinement.font_family.clone() {
-            self.font_family_id = font_cache.load_family(&[&font_family], &Default::default())?;
-            self.font_family_name = font_family;
-            update_font_id = true;
-        }
-        if let Some(font_weight) = refinement.font_weight {
-            self.font_properties.weight = font_weight;
-            update_font_id = true;
-        }
-        if let Some(font_style) = refinement.font_style {
-            self.font_properties.style = font_style;
-            update_font_id = true;
-        }
-
-        if update_font_id {
-            self.font_id = font_cache.select_font(self.font_family_id, &self.font_properties)?;
-        }
-
-        Ok(())
-    }
-}
-
-#[derive(Clone, Debug, Default)]
-pub struct TextStyleRefinement {
-    pub color: Option<Color>,
-    pub font_family: Option<Arc<str>>,
-    pub font_size: Option<f32>,
-    pub font_weight: Option<Weight>,
-    pub font_style: Option<Style>,
-    pub underline: Option<Underline>,
-}
-
-impl Refineable for TextStyleRefinement {
-    type Refinement = Self;
-
-    fn refine(&mut self, refinement: &Self::Refinement) {
-        if refinement.color.is_some() {
-            self.color = refinement.color;
-        }
-        if refinement.font_family.is_some() {
-            self.font_family = refinement.font_family.clone();
-        }
-        if refinement.font_size.is_some() {
-            self.font_size = refinement.font_size;
-        }
-        if refinement.font_weight.is_some() {
-            self.font_weight = refinement.font_weight;
-        }
-        if refinement.font_style.is_some() {
-            self.font_style = refinement.font_style;
-        }
-        if refinement.underline.is_some() {
-            self.underline = refinement.underline;
-        }
-    }
-
-    fn refined(mut self, refinement: Self::Refinement) -> Self {
-        self.refine(&refinement);
-        self
-    }
-}
-
-#[derive(JsonSchema)]
-#[serde(remote = "Properties")]
-pub struct PropertiesDef {
-    /// The font style, as defined in CSS.
-    pub style: StyleDef,
-    /// The font weight, as defined in CSS.
-    pub weight: f32,
-    /// The font stretchiness, as defined in CSS.
-    pub stretch: f32,
-}
-
-#[derive(JsonSchema)]
-#[schemars(remote = "Style")]
-pub enum StyleDef {
-    /// A face that is neither italic not obliqued.
-    Normal,
-    /// A form that is generally cursive in nature.
-    Italic,
-    /// A typically-sloped version of the regular face.
-    Oblique,
-}
-
-#[derive(Copy, Clone, Debug, Default, PartialEq, JsonSchema)]
-pub struct HighlightStyle {
-    pub color: Option<Color>,
-    #[schemars(with = "Option::<f32>")]
-    pub weight: Option<Weight>,
-    pub italic: Option<bool>,
-    pub underline: Option<Underline>,
-    pub fade_out: Option<f32>,
-}
-
-impl Eq for HighlightStyle {}
-
-#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, JsonSchema)]
-pub struct Underline {
-    pub color: Option<Color>,
-    #[schemars(with = "f32")]
-    pub thickness: OrderedFloat<f32>,
-    pub squiggly: bool,
-}
-
-#[allow(non_camel_case_types)]
-#[derive(Deserialize)]
-enum WeightJson {
-    thin,
-    extra_light,
-    light,
-    normal,
-    medium,
-    semibold,
-    bold,
-    extra_bold,
-    black,
-}
-
-thread_local! {
-    static FONT_CACHE: RefCell<Option<Arc<FontCache>>> = Default::default();
-}
-
-#[derive(Deserialize)]
-struct TextStyleJson {
-    color: Color,
-    family: String,
-    #[serde(default)]
-    features: Features,
-    weight: Option<WeightJson>,
-    size: f32,
-    #[serde(default)]
-    italic: bool,
-    #[serde(default)]
-    underline: UnderlineStyleJson,
-}
-
-#[derive(Deserialize)]
-struct HighlightStyleJson {
-    color: Option<Color>,
-    weight: Option<WeightJson>,
-    italic: Option<bool>,
-    underline: Option<UnderlineStyleJson>,
-    fade_out: Option<f32>,
-}
-
-#[derive(Deserialize)]
-#[serde(untagged)]
-enum UnderlineStyleJson {
-    Underlined(bool),
-    UnderlinedWithProperties {
-        #[serde(default)]
-        color: Option<Color>,
-        #[serde(default)]
-        thickness: Option<f32>,
-        #[serde(default)]
-        squiggly: bool,
-    },
-}
-
-impl TextStyle {
-    pub fn new(
-        font_family_name: impl Into<Arc<str>>,
-        font_size: f32,
-        font_properties: Properties,
-        font_features: Features,
-        underline: Underline,
-        color: Color,
-        font_cache: &FontCache,
-    ) -> Result<Self> {
-        let font_family_name = font_family_name.into();
-        let font_family_id = font_cache.load_family(&[&font_family_name], &font_features)?;
-        let font_id = font_cache.select_font(font_family_id, &font_properties)?;
-        Ok(Self {
-            color,
-            font_family_name,
-            font_family_id,
-            font_id,
-            font_size,
-            font_properties,
-            underline,
-            soft_wrap: false,
-        })
-    }
-
-    pub fn default(font_cache: &FontCache) -> Self {
-        let font_family_id = font_cache.known_existing_family();
-        let font_id = font_cache
-            .select_font(font_family_id, &Default::default())
-            .expect("did not have any font in system-provided family");
-        let font_family_name = font_cache
-            .family_name(font_family_id)
-            .expect("we loaded this family from the font cache, so this should work");
-
-        Self {
-            color: Color::default(),
-            font_family_name,
-            font_family_id,
-            font_id,
-            font_size: 14.,
-            font_properties: Default::default(),
-            underline: Default::default(),
-            soft_wrap: true,
-        }
-    }
-
-    pub fn with_font_size(mut self, font_size: f32) -> Self {
-        self.font_size = font_size;
-        self
-    }
-
-    pub fn highlight(mut self, style: HighlightStyle, font_cache: &FontCache) -> Result<Self> {
-        let mut font_properties = self.font_properties;
-        if let Some(weight) = style.weight {
-            font_properties.weight(weight);
-        }
-        if let Some(italic) = style.italic {
-            if italic {
-                font_properties.style(Style::Italic);
-            } else {
-                font_properties.style(Style::Normal);
-            }
-        }
-
-        if self.font_properties != font_properties {
-            self.font_id = font_cache.select_font(self.font_family_id, &font_properties)?;
-        }
-        if let Some(color) = style.color {
-            self.color = Color::blend(color, self.color);
-        }
-        if let Some(factor) = style.fade_out {
-            self.color.fade_out(factor);
-        }
-        if let Some(underline) = style.underline {
-            self.underline = underline;
-        }
-
-        Ok(self)
-    }
-
-    pub fn to_run(&self) -> RunStyle {
-        RunStyle {
-            font_id: self.font_id,
-            color: self.color,
-            underline: self.underline,
-        }
-    }
-
-    fn from_json(json: TextStyleJson) -> Result<Self> {
-        FONT_CACHE.with(|font_cache| {
-            if let Some(font_cache) = font_cache.borrow().as_ref() {
-                let font_properties = properties_from_json(json.weight, json.italic);
-                Self::new(
-                    json.family,
-                    json.size,
-                    font_properties,
-                    json.features,
-                    underline_from_json(json.underline),
-                    json.color,
-                    font_cache,
-                )
-            } else {
-                Err(anyhow!(
-                    "TextStyle can only be deserialized within a call to with_font_cache"
-                ))
-            }
-        })
-    }
-
-    pub fn line_height(&self, font_cache: &FontCache) -> f32 {
-        font_cache.line_height(self.font_size)
-    }
-
-    pub fn cap_height(&self, font_cache: &FontCache) -> f32 {
-        font_cache.cap_height(self.font_id, self.font_size)
-    }
-
-    pub fn x_height(&self, font_cache: &FontCache) -> f32 {
-        font_cache.x_height(self.font_id, self.font_size)
-    }
-
-    pub fn em_width(&self, font_cache: &FontCache) -> f32 {
-        font_cache.em_width(self.font_id, self.font_size)
-    }
-
-    pub fn em_advance(&self, font_cache: &FontCache) -> f32 {
-        font_cache.em_advance(self.font_id, self.font_size)
-    }
-
-    pub fn descent(&self, font_cache: &FontCache) -> f32 {
-        font_cache.metric(self.font_id, |m| m.descent) * self.em_scale(font_cache)
-    }
-
-    pub fn baseline_offset(&self, font_cache: &FontCache) -> f32 {
-        font_cache.baseline_offset(self.font_id, self.font_size)
-    }
-
-    fn em_scale(&self, font_cache: &FontCache) -> f32 {
-        font_cache.em_scale(self.font_id, self.font_size)
-    }
-}
-
-impl From<TextStyle> for HighlightStyle {
-    fn from(other: TextStyle) -> Self {
-        Self::from(&other)
-    }
-}
-
-impl From<&TextStyle> for HighlightStyle {
-    fn from(other: &TextStyle) -> Self {
-        Self {
-            color: Some(other.color),
-            weight: Some(other.font_properties.weight),
-            italic: Some(other.font_properties.style == Style::Italic),
-            underline: Some(other.underline),
-            fade_out: None,
-        }
-    }
-}
-
-impl Default for UnderlineStyleJson {
-    fn default() -> Self {
-        Self::Underlined(false)
-    }
-}
-
-impl Default for TextStyle {
-    fn default() -> Self {
-        FONT_CACHE.with(|font_cache| {
-            let font_cache = font_cache.borrow();
-            let font_cache = font_cache
-                .as_ref()
-                .expect("TextStyle::default can only be called within a call to with_font_cache");
-            Self::default(font_cache)
-        })
-    }
-}
-
-impl HighlightStyle {
-    fn from_json(json: HighlightStyleJson) -> Self {
-        Self {
-            color: json.color,
-            weight: json.weight.map(weight_from_json),
-            italic: json.italic,
-            underline: json.underline.map(underline_from_json),
-            fade_out: json.fade_out,
-        }
-    }
-
-    pub fn highlight(&mut self, other: HighlightStyle) {
-        match (self.color, other.color) {
-            (Some(self_color), Some(other_color)) => {
-                self.color = Some(Color::blend(other_color, self_color));
-            }
-            (None, Some(other_color)) => {
-                self.color = Some(other_color);
-            }
-            _ => {}
-        }
-
-        if other.weight.is_some() {
-            self.weight = other.weight;
-        }
-
-        if other.italic.is_some() {
-            self.italic = other.italic;
-        }
-
-        if other.underline.is_some() {
-            self.underline = other.underline;
-        }
-
-        match (other.fade_out, self.fade_out) {
-            (Some(source_fade), None) => self.fade_out = Some(source_fade),
-            (Some(source_fade), Some(dest_fade)) => {
-                let source_alpha = 1. - source_fade;
-                let dest_alpha = 1. - dest_fade;
-                let blended_alpha = source_alpha + (dest_alpha * source_fade);
-                let blended_fade = 1. - blended_alpha;
-                self.fade_out = Some(blended_fade);
-            }
-            _ => {}
-        }
-    }
-}
-
-impl From<Color> for HighlightStyle {
-    fn from(color: Color) -> Self {
-        Self {
-            color: Some(color),
-            ..Default::default()
-        }
-    }
-}
-
-impl<'de> Deserialize<'de> for TextStyle {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        Self::from_json(TextStyleJson::deserialize(deserializer)?).map_err(de::Error::custom)
-    }
-}
-
-impl ToJson for TextStyle {
-    fn to_json(&self) -> Value {
-        json!({
-            "color": self.color.to_json(),
-            "font_family": self.font_family_name.as_ref(),
-            "font_properties": self.font_properties.to_json(),
-        })
-    }
-}
-
-impl<'de> Deserialize<'de> for HighlightStyle {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        let json = serde_json::Value::deserialize(deserializer)?;
-        if json.is_object() {
-            Ok(Self::from_json(
-                serde_json::from_value(json).map_err(de::Error::custom)?,
-            ))
-        } else {
-            Ok(Self {
-                color: serde_json::from_value(json).map_err(de::Error::custom)?,
-                ..Default::default()
-            })
-        }
-    }
-}
-
-fn underline_from_json(json: UnderlineStyleJson) -> Underline {
-    match json {
-        UnderlineStyleJson::Underlined(false) => Underline::default(),
-        UnderlineStyleJson::Underlined(true) => Underline {
-            color: None,
-            thickness: 1.0.into(),
-            squiggly: false,
-        },
-        UnderlineStyleJson::UnderlinedWithProperties {
-            color,
-            thickness,
-            squiggly,
-        } => Underline {
-            color,
-            thickness: thickness.unwrap_or(1.).into(),
-            squiggly,
-        },
-    }
-}
-
-fn properties_from_json(weight: Option<WeightJson>, italic: bool) -> Properties {
-    let weight = weight.map(weight_from_json).unwrap_or_default();
-    let style = if italic { Style::Italic } else { Style::Normal };
-    *Properties::new().weight(weight).style(style)
-}
-
-fn weight_from_json(weight: WeightJson) -> Weight {
-    match weight {
-        WeightJson::thin => Weight::THIN,
-        WeightJson::extra_light => Weight::EXTRA_LIGHT,
-        WeightJson::light => Weight::LIGHT,
-        WeightJson::normal => Weight::NORMAL,
-        WeightJson::medium => Weight::MEDIUM,
-        WeightJson::semibold => Weight::SEMIBOLD,
-        WeightJson::bold => Weight::BOLD,
-        WeightJson::extra_bold => Weight::EXTRA_BOLD,
-        WeightJson::black => Weight::BLACK,
-    }
-}
-
-impl ToJson for Properties {
-    fn to_json(&self) -> crate::json::Value {
-        json!({
-            "style": self.style.to_json(),
-            "weight": self.weight.to_json(),
-            "stretch": self.stretch.to_json(),
-        })
-    }
-}
-
-impl ToJson for Style {
-    fn to_json(&self) -> crate::json::Value {
-        match self {
-            Style::Normal => json!("normal"),
-            Style::Italic => json!("italic"),
-            Style::Oblique => json!("oblique"),
-        }
-    }
-}
-
-impl ToJson for Weight {
-    fn to_json(&self) -> crate::json::Value {
-        if self.0 == Weight::THIN.0 {
-            json!("thin")
-        } else if self.0 == Weight::EXTRA_LIGHT.0 {
-            json!("extra light")
-        } else if self.0 == Weight::LIGHT.0 {
-            json!("light")
-        } else if self.0 == Weight::NORMAL.0 {
-            json!("normal")
-        } else if self.0 == Weight::MEDIUM.0 {
-            json!("medium")
-        } else if self.0 == Weight::SEMIBOLD.0 {
-            json!("semibold")
-        } else if self.0 == Weight::BOLD.0 {
-            json!("bold")
-        } else if self.0 == Weight::EXTRA_BOLD.0 {
-            json!("extra bold")
-        } else if self.0 == Weight::BLACK.0 {
-            json!("black")
-        } else {
-            json!(self.0)
-        }
-    }
-}
-
-impl ToJson for Stretch {
-    fn to_json(&self) -> serde_json::Value {
-        json!(self.0)
-    }
-}
-
-pub fn with_font_cache<F, T>(font_cache: Arc<FontCache>, callback: F) -> T
-where
-    F: FnOnce() -> T,
-{
-    FONT_CACHE.with(|cache| {
-        *cache.borrow_mut() = Some(font_cache);
-        let result = callback();
-        cache.borrow_mut().take();
-        result
-    })
-}

crates/gpui/src/geometry.rs 🔗

@@ -1,407 +1,2484 @@
-use super::scene::{Path, PathVertex};
-use crate::{color::Color, json::ToJson};
-use derive_more::Neg;
-pub use pathfinder_geometry::*;
-use rect::RectF;
+use core::fmt::Debug;
+use derive_more::{Add, AddAssign, Div, DivAssign, Mul, Neg, Sub, SubAssign};
 use refineable::Refineable;
-use serde::{Deserialize, Deserializer};
-use serde_json::json;
-use std::fmt::Debug;
-use vector::{vec2f, Vector2F};
+use serde_derive::{Deserialize, Serialize};
+use std::{
+    cmp::{self, PartialOrd},
+    fmt,
+    ops::{Add, Div, Mul, MulAssign, Sub},
+};
 
-pub struct PathBuilder {
-    vertices: Vec<PathVertex>,
-    start: Vector2F,
-    current: Vector2F,
-    contour_count: usize,
-    bounds: RectF,
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub enum Axis {
+    Vertical,
+    Horizontal,
 }
 
-enum PathVertexKind {
-    Solid,
-    Quadratic,
+impl Axis {
+    pub fn invert(&self) -> Self {
+        match self {
+            Axis::Vertical => Axis::Horizontal,
+            Axis::Horizontal => Axis::Vertical,
+        }
+    }
+}
+
+pub trait Along {
+    type Unit;
+
+    fn along(&self, axis: Axis) -> Self::Unit;
+
+    fn apply_along(&self, axis: Axis, f: impl FnOnce(Self::Unit) -> Self::Unit) -> Self;
+}
+
+impl sqlez::bindable::StaticColumnCount for Axis {}
+impl sqlez::bindable::Bind for Axis {
+    fn bind(
+        &self,
+        statement: &sqlez::statement::Statement,
+        start_index: i32,
+    ) -> anyhow::Result<i32> {
+        match self {
+            Axis::Horizontal => "Horizontal",
+            Axis::Vertical => "Vertical",
+        }
+        .bind(statement, start_index)
+    }
+}
+
+impl sqlez::bindable::Column for Axis {
+    fn column(
+        statement: &mut sqlez::statement::Statement,
+        start_index: i32,
+    ) -> anyhow::Result<(Self, i32)> {
+        String::column(statement, start_index).and_then(|(axis_text, next_index)| {
+            Ok((
+                match axis_text.as_str() {
+                    "Horizontal" => Axis::Horizontal,
+                    "Vertical" => Axis::Vertical,
+                    _ => anyhow::bail!("Stored serialized item kind is incorrect"),
+                },
+                next_index,
+            ))
+        })
+    }
+}
+
+/// Describes a location in a 2D cartesian coordinate space.
+///
+/// It holds two public fields, `x` and `y`, which represent the coordinates in the space.
+/// The type `T` for the coordinates can be any type that implements `Default`, `Clone`, and `Debug`.
+///
+/// # Examples
+///
+/// ```
+/// # use zed::Point;
+/// let point = Point { x: 10, y: 20 };
+/// println!("{:?}", point); // Outputs: Point { x: 10, y: 20 }
+/// ```
+#[derive(Refineable, Default, Add, AddAssign, Sub, SubAssign, Copy, Debug, PartialEq, Eq, Hash)]
+#[refineable(Debug)]
+#[repr(C)]
+pub struct Point<T: Default + Clone + Debug> {
+    pub x: T,
+    pub y: T,
+}
+
+/// Constructs a new `Point<T>` with the given x and y coordinates.
+///
+/// # Arguments
+///
+/// * `x` - The x coordinate of the point.
+/// * `y` - The y coordinate of the point.
+///
+/// # Returns
+///
+/// Returns a `Point<T>` with the specified coordinates.
+///
+/// # Examples
+///
+/// ```
+/// # use zed::Point;
+/// let p = point(10, 20);
+/// assert_eq!(p.x, 10);
+/// assert_eq!(p.y, 20);
+/// ```
+pub fn point<T: Clone + Debug + Default>(x: T, y: T) -> Point<T> {
+    Point { x, y }
+}
+
+impl<T: Clone + Debug + Default> Point<T> {
+    /// Creates a new `Point` with the specified `x` and `y` coordinates.
+    ///
+    /// # Arguments
+    ///
+    /// * `x` - The horizontal coordinate of the point.
+    /// * `y` - The vertical coordinate of the point.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// let p = Point::new(10, 20);
+    /// assert_eq!(p.x, 10);
+    /// assert_eq!(p.y, 20);
+    /// ```
+    pub const fn new(x: T, y: T) -> Self {
+        Self { x, y }
+    }
+
+    /// Transforms the point to a `Point<U>` by applying the given function to both coordinates.
+    ///
+    /// This method allows for converting a `Point<T>` to a `Point<U>` by specifying a closure
+    /// that defines how to convert between the two types. The closure is applied to both the `x`
+    /// and `y` coordinates, resulting in a new point of the desired type.
+    ///
+    /// # Arguments
+    ///
+    /// * `f` - A closure that takes a value of type `T` and returns a value of type `U`.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::Point;
+    /// let p = Point { x: 3, y: 4 };
+    /// let p_float = p.map(|coord| coord as f32);
+    /// assert_eq!(p_float, Point { x: 3.0, y: 4.0 });
+    /// ```
+    pub fn map<U: Clone + Default + Debug>(&self, f: impl Fn(T) -> U) -> Point<U> {
+        Point {
+            x: f(self.x.clone()),
+            y: f(self.y.clone()),
+        }
+    }
+}
+
+impl<T: Clone + Debug + Default> Along for Point<T> {
+    type Unit = T;
+
+    fn along(&self, axis: Axis) -> T {
+        match axis {
+            Axis::Horizontal => self.x.clone(),
+            Axis::Vertical => self.y.clone(),
+        }
+    }
+
+    fn apply_along(&self, axis: Axis, f: impl FnOnce(T) -> T) -> Point<T> {
+        match axis {
+            Axis::Horizontal => Point {
+                x: f(self.x.clone()),
+                y: self.y.clone(),
+            },
+            Axis::Vertical => Point {
+                x: self.x.clone(),
+                y: f(self.y.clone()),
+            },
+        }
+    }
+}
+
+impl Point<Pixels> {
+    /// Scales the point by a given factor, which is typically derived from the resolution
+    /// of a target display to ensure proper sizing of UI elements.
+    ///
+    /// # Arguments
+    ///
+    /// * `factor` - The scaling factor to apply to both the x and y coordinates.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::{Point, Pixels, ScaledPixels};
+    /// let p = Point { x: Pixels(10.0), y: Pixels(20.0) };
+    /// let scaled_p = p.scale(1.5);
+    /// assert_eq!(scaled_p, Point { x: ScaledPixels(15.0), y: ScaledPixels(30.0) });
+    /// ```
+    pub fn scale(&self, factor: f32) -> Point<ScaledPixels> {
+        Point {
+            x: self.x.scale(factor),
+            y: self.y.scale(factor),
+        }
+    }
+
+    /// Calculates the Euclidean distance from the origin (0, 0) to this point.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::Point;
+    /// # use zed::Pixels;
+    /// let p = Point { x: Pixels(3.0), y: Pixels(4.0) };
+    /// assert_eq!(p.magnitude(), 5.0);
+    /// ```
+    pub fn magnitude(&self) -> f64 {
+        ((self.x.0.powi(2) + self.y.0.powi(2)) as f64).sqrt()
+    }
+}
+
+impl<T, Rhs> Mul<Rhs> for Point<T>
+where
+    T: Mul<Rhs, Output = T> + Clone + Default + Debug,
+    Rhs: Clone + Debug,
+{
+    type Output = Point<T>;
+
+    fn mul(self, rhs: Rhs) -> Self::Output {
+        Point {
+            x: self.x * rhs.clone(),
+            y: self.y * rhs,
+        }
+    }
+}
+
+impl<T, S> MulAssign<S> for Point<T>
+where
+    T: Clone + Mul<S, Output = T> + Default + Debug,
+    S: Clone,
+{
+    fn mul_assign(&mut self, rhs: S) {
+        self.x = self.x.clone() * rhs.clone();
+        self.y = self.y.clone() * rhs;
+    }
+}
+
+impl<T, S> Div<S> for Point<T>
+where
+    T: Div<S, Output = T> + Clone + Default + Debug,
+    S: Clone,
+{
+    type Output = Self;
+
+    fn div(self, rhs: S) -> Self::Output {
+        Self {
+            x: self.x / rhs.clone(),
+            y: self.y / rhs,
+        }
+    }
+}
+
+impl<T> Point<T>
+where
+    T: PartialOrd + Clone + Default + Debug,
+{
+    /// Returns a new point with the maximum values of each dimension from `self` and `other`.
+    ///
+    /// # Arguments
+    ///
+    /// * `other` - A reference to another `Point` to compare with `self`.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::Point;
+    /// let p1 = Point { x: 3, y: 7 };
+    /// let p2 = Point { x: 5, y: 2 };
+    /// let max_point = p1.max(&p2);
+    /// assert_eq!(max_point, Point { x: 5, y: 7 });
+    /// ```
+    pub fn max(&self, other: &Self) -> Self {
+        Point {
+            x: if self.x > other.x {
+                self.x.clone()
+            } else {
+                other.x.clone()
+            },
+            y: if self.y > other.y {
+                self.y.clone()
+            } else {
+                other.y.clone()
+            },
+        }
+    }
+
+    /// Returns a new point with the minimum values of each dimension from `self` and `other`.
+    ///
+    /// # Arguments
+    ///
+    /// * `other` - A reference to another `Point` to compare with `self`.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::Point;
+    /// let p1 = Point { x: 3, y: 7 };
+    /// let p2 = Point { x: 5, y: 2 };
+    /// let min_point = p1.min(&p2);
+    /// assert_eq!(min_point, Point { x: 3, y: 2 });
+    /// ```
+    pub fn min(&self, other: &Self) -> Self {
+        Point {
+            x: if self.x <= other.x {
+                self.x.clone()
+            } else {
+                other.x.clone()
+            },
+            y: if self.y <= other.y {
+                self.y.clone()
+            } else {
+                other.y.clone()
+            },
+        }
+    }
+
+    /// Clamps the point to a specified range.
+    ///
+    /// Given a minimum point and a maximum point, this method constrains the current point
+    /// such that its coordinates do not exceed the range defined by the minimum and maximum points.
+    /// If the current point's coordinates are less than the minimum, they are set to the minimum.
+    /// If they are greater than the maximum, they are set to the maximum.
+    ///
+    /// # Arguments
+    ///
+    /// * `min` - A reference to a `Point` representing the minimum allowable coordinates.
+    /// * `max` - A reference to a `Point` representing the maximum allowable coordinates.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::Point;
+    /// let p = Point { x: 10, y: 20 };
+    /// let min = Point { x: 0, y: 5 };
+    /// let max = Point { x: 15, y: 25 };
+    /// let clamped_p = p.clamp(&min, &max);
+    /// assert_eq!(clamped_p, Point { x: 10, y: 20 });
+    ///
+    /// let p_out_of_bounds = Point { x: -5, y: 30 };
+    /// let clamped_p_out_of_bounds = p_out_of_bounds.clamp(&min, &max);
+    /// assert_eq!(clamped_p_out_of_bounds, Point { x: 0, y: 25 });
+    /// ```
+    pub fn clamp(&self, min: &Self, max: &Self) -> Self {
+        self.max(min).min(max)
+    }
+}
+
+impl<T: Clone + Default + Debug> Clone for Point<T> {
+    fn clone(&self) -> Self {
+        Self {
+            x: self.x.clone(),
+            y: self.y.clone(),
+        }
+    }
+}
+
+/// A structure representing a two-dimensional size with width and height in a given unit.
+///
+/// This struct is generic over the type `T`, which can be any type that implements `Clone`, `Default`, and `Debug`.
+/// It is commonly used to specify dimensions for elements in a UI, such as a window or element.
+#[derive(Refineable, Default, Clone, Copy, PartialEq, Div, Hash, Serialize, Deserialize)]
+#[refineable(Debug)]
+#[repr(C)]
+pub struct Size<T: Clone + Default + Debug> {
+    pub width: T,
+    pub height: T,
+}
+
+/// Constructs a new `Size<T>` with the provided width and height.
+///
+/// # Arguments
+///
+/// * `width` - The width component of the `Size`.
+/// * `height` - The height component of the `Size`.
+///
+/// # Examples
+///
+/// ```
+/// # use zed::Size;
+/// let my_size = size(10, 20);
+/// assert_eq!(my_size.width, 10);
+/// assert_eq!(my_size.height, 20);
+/// ```
+pub fn size<T>(width: T, height: T) -> Size<T>
+where
+    T: Clone + Default + Debug,
+{
+    Size { width, height }
+}
+
+impl<T> Size<T>
+where
+    T: Clone + Default + Debug,
+{
+    /// Applies a function to the width and height of the size, producing a new `Size<U>`.
+    ///
+    /// This method allows for converting a `Size<T>` to a `Size<U>` by specifying a closure
+    /// that defines how to convert between the two types. The closure is applied to both the `width`
+    /// and `height`, resulting in a new size of the desired type.
+    ///
+    /// # Arguments
+    ///
+    /// * `f` - A closure that takes a value of type `T` and returns a value of type `U`.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::Size;
+    /// let my_size = Size { width: 10, height: 20 };
+    /// let my_new_size = my_size.map(|dimension| dimension as f32 * 1.5);
+    /// assert_eq!(my_new_size, Size { width: 15.0, height: 30.0 });
+    /// ```
+    pub fn map<U>(&self, f: impl Fn(T) -> U) -> Size<U>
+    where
+        U: Clone + Default + Debug,
+    {
+        Size {
+            width: f(self.width.clone()),
+            height: f(self.height.clone()),
+        }
+    }
+}
+
+impl Size<Pixels> {
+    /// Scales the size by a given factor.
+    ///
+    /// This method multiplies both the width and height by the provided scaling factor,
+    /// resulting in a new `Size<ScaledPixels>` that is proportionally larger or smaller
+    /// depending on the factor.
+    ///
+    /// # Arguments
+    ///
+    /// * `factor` - The scaling factor to apply to the width and height.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::{Size, Pixels, ScaledPixels};
+    /// let size = Size { width: Pixels(100.0), height: Pixels(50.0) };
+    /// let scaled_size = size.scale(2.0);
+    /// assert_eq!(scaled_size, Size { width: ScaledPixels(200.0), height: ScaledPixels(100.0) });
+    /// ```
+    pub fn scale(&self, factor: f32) -> Size<ScaledPixels> {
+        Size {
+            width: self.width.scale(factor),
+            height: self.height.scale(factor),
+        }
+    }
+}
+
+impl<T> Along for Size<T>
+where
+    T: Clone + Default + Debug,
+{
+    type Unit = T;
+
+    fn along(&self, axis: Axis) -> T {
+        match axis {
+            Axis::Horizontal => self.width.clone(),
+            Axis::Vertical => self.height.clone(),
+        }
+    }
+
+    /// Returns the value of this size along the given axis.
+    fn apply_along(&self, axis: Axis, f: impl FnOnce(T) -> T) -> Self {
+        match axis {
+            Axis::Horizontal => Size {
+                width: f(self.width.clone()),
+                height: self.height.clone(),
+            },
+            Axis::Vertical => Size {
+                width: self.width.clone(),
+                height: f(self.height.clone()),
+            },
+        }
+    }
+}
+
+impl<T> Size<T>
+where
+    T: PartialOrd + Clone + Default + Debug,
+{
+    /// Returns a new `Size` with the maximum width and height from `self` and `other`.
+    ///
+    /// # Arguments
+    ///
+    /// * `other` - A reference to another `Size` to compare with `self`.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::Size;
+    /// let size1 = Size { width: 30, height: 40 };
+    /// let size2 = Size { width: 50, height: 20 };
+    /// let max_size = size1.max(&size2);
+    /// assert_eq!(max_size, Size { width: 50, height: 40 });
+    /// ```
+    pub fn max(&self, other: &Self) -> Self {
+        Size {
+            width: if self.width >= other.width {
+                self.width.clone()
+            } else {
+                other.width.clone()
+            },
+            height: if self.height >= other.height {
+                self.height.clone()
+            } else {
+                other.height.clone()
+            },
+        }
+    }
+}
+
+impl<T> Sub for Size<T>
+where
+    T: Sub<Output = T> + Clone + Default + Debug,
+{
+    type Output = Size<T>;
+
+    fn sub(self, rhs: Self) -> Self::Output {
+        Size {
+            width: self.width - rhs.width,
+            height: self.height - rhs.height,
+        }
+    }
+}
+
+impl<T, Rhs> Mul<Rhs> for Size<T>
+where
+    T: Mul<Rhs, Output = Rhs> + Clone + Default + Debug,
+    Rhs: Clone + Default + Debug,
+{
+    type Output = Size<Rhs>;
+
+    fn mul(self, rhs: Rhs) -> Self::Output {
+        Size {
+            width: self.width * rhs.clone(),
+            height: self.height * rhs,
+        }
+    }
+}
+
+impl<T, S> MulAssign<S> for Size<T>
+where
+    T: Mul<S, Output = T> + Clone + Default + Debug,
+    S: Clone,
+{
+    fn mul_assign(&mut self, rhs: S) {
+        self.width = self.width.clone() * rhs.clone();
+        self.height = self.height.clone() * rhs;
+    }
+}
+
+impl<T> Eq for Size<T> where T: Eq + Default + Debug + Clone {}
+
+impl<T> Debug for Size<T>
+where
+    T: Clone + Default + Debug,
+{
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "Size {{ {:?} × {:?} }}", self.width, self.height)
+    }
+}
+
+impl<T: Clone + Default + Debug> From<Point<T>> for Size<T> {
+    fn from(point: Point<T>) -> Self {
+        Self {
+            width: point.x,
+            height: point.y,
+        }
+    }
+}
+
+impl From<Size<Pixels>> for Size<GlobalPixels> {
+    fn from(size: Size<Pixels>) -> Self {
+        Size {
+            width: GlobalPixels(size.width.0),
+            height: GlobalPixels(size.height.0),
+        }
+    }
+}
+
+impl From<Size<Pixels>> for Size<DefiniteLength> {
+    fn from(size: Size<Pixels>) -> Self {
+        Size {
+            width: size.width.into(),
+            height: size.height.into(),
+        }
+    }
+}
+
+impl From<Size<Pixels>> for Size<AbsoluteLength> {
+    fn from(size: Size<Pixels>) -> Self {
+        Size {
+            width: size.width.into(),
+            height: size.height.into(),
+        }
+    }
+}
+
+impl Size<Length> {
+    /// Returns a `Size` with both width and height set to fill the available space.
+    ///
+    /// This function creates a `Size` instance where both the width and height are set to `Length::Definite(DefiniteLength::Fraction(1.0))`,
+    /// which represents 100% of the available space in both dimensions.
+    ///
+    /// # Returns
+    ///
+    /// A `Size<Length>` that will fill the available space when used in a layout.
+    pub fn full() -> Self {
+        Self {
+            width: relative(1.).into(),
+            height: relative(1.).into(),
+        }
+    }
+}
+
+impl Size<Length> {
+    /// Returns a `Size` with both width and height set to `auto`, which allows the layout engine to determine the size.
+    ///
+    /// This function creates a `Size` instance where both the width and height are set to `Length::Auto`,
+    /// indicating that their size should be computed based on the layout context, such as the content size or
+    /// available space.
+    ///
+    /// # Returns
+    ///
+    /// A `Size<Length>` with width and height set to `Length::Auto`.
+    pub fn auto() -> Self {
+        Self {
+            width: Length::Auto,
+            height: Length::Auto,
+        }
+    }
+}
+
+/// Represents a rectangular area in a 2D space with an origin point and a size.
+///
+/// The `Bounds` struct is generic over a type `T` which represents the type of the coordinate system.
+/// The origin is represented as a `Point<T>` which defines the upper-left corner of the rectangle,
+/// and the size is represented as a `Size<T>` which defines the width and height of the rectangle.
+///
+/// # Examples
+///
+/// ```
+/// # use zed::{Bounds, Point, Size};
+/// let origin = Point { x: 0, y: 0 };
+/// let size = Size { width: 10, height: 20 };
+/// let bounds = Bounds::new(origin, size);
+///
+/// assert_eq!(bounds.origin, origin);
+/// assert_eq!(bounds.size, size);
+/// ```
+#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
+#[refineable(Debug)]
+#[repr(C)]
+pub struct Bounds<T: Clone + Default + Debug> {
+    pub origin: Point<T>,
+    pub size: Size<T>,
+}
+
+impl<T> Bounds<T>
+where
+    T: Clone + Debug + Sub<Output = T> + Default,
+{
+    /// Constructs a `Bounds` from two corner points: the upper-left and lower-right corners.
+    ///
+    /// This function calculates the origin and size of the `Bounds` based on the provided corner points.
+    /// The origin is set to the upper-left corner, and the size is determined by the difference between
+    /// the x and y coordinates of the lower-right and upper-left points.
+    ///
+    /// # Arguments
+    ///
+    /// * `upper_left` - A `Point<T>` representing the upper-left corner of the rectangle.
+    /// * `lower_right` - A `Point<T>` representing the lower-right corner of the rectangle.
+    ///
+    /// # Returns
+    ///
+    /// Returns a `Bounds<T>` that encompasses the area defined by the two corner points.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::{Bounds, Point};
+    /// let upper_left = Point { x: 0, y: 0 };
+    /// let lower_right = Point { x: 10, y: 10 };
+    /// let bounds = Bounds::from_corners(upper_left, lower_right);
+    ///
+    /// assert_eq!(bounds.origin, upper_left);
+    /// assert_eq!(bounds.size.width, 10);
+    /// assert_eq!(bounds.size.height, 10);
+    /// ```
+    pub fn from_corners(upper_left: Point<T>, lower_right: Point<T>) -> Self {
+        let origin = Point {
+            x: upper_left.x.clone(),
+            y: upper_left.y.clone(),
+        };
+        let size = Size {
+            width: lower_right.x - upper_left.x,
+            height: lower_right.y - upper_left.y,
+        };
+        Bounds { origin, size }
+    }
+
+    /// Creates a new `Bounds` with the specified origin and size.
+    ///
+    /// # Arguments
+    ///
+    /// * `origin` - A `Point<T>` representing the origin of the bounds.
+    /// * `size` - A `Size<T>` representing the size of the bounds.
+    ///
+    /// # Returns
+    ///
+    /// Returns a `Bounds<T>` that has the given origin and size.
+    pub fn new(origin: Point<T>, size: Size<T>) -> Self {
+        Bounds { origin, size }
+    }
+}
+
+impl<T> Bounds<T>
+where
+    T: Clone + Debug + PartialOrd + Add<T, Output = T> + Sub<Output = T> + Default + Half,
+{
+    /// Checks if this `Bounds` intersects with another `Bounds`.
+    ///
+    /// Two `Bounds` instances intersect if they overlap in the 2D space they occupy.
+    /// This method checks if there is any overlapping area between the two bounds.
+    ///
+    /// # Arguments
+    ///
+    /// * `other` - A reference to another `Bounds` to check for intersection with.
+    ///
+    /// # Returns
+    ///
+    /// Returns `true` if there is any intersection between the two bounds, `false` otherwise.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::{Bounds, Point, Size};
+    /// let bounds1 = Bounds {
+    ///     origin: Point { x: 0, y: 0 },
+    ///     size: Size { width: 10, height: 10 },
+    /// };
+    /// let bounds2 = Bounds {
+    ///     origin: Point { x: 5, y: 5 },
+    ///     size: Size { width: 10, height: 10 },
+    /// };
+    /// let bounds3 = Bounds {
+    ///     origin: Point { x: 20, y: 20 },
+    ///     size: Size { width: 10, height: 10 },
+    /// };
+    ///
+    /// assert_eq!(bounds1.intersects(&bounds2), true); // Overlapping bounds
+    /// assert_eq!(bounds1.intersects(&bounds3), false); // Non-overlapping bounds
+    /// ```
+    pub fn intersects(&self, other: &Bounds<T>) -> bool {
+        let my_lower_right = self.lower_right();
+        let their_lower_right = other.lower_right();
+
+        self.origin.x < their_lower_right.x
+            && my_lower_right.x > other.origin.x
+            && self.origin.y < their_lower_right.y
+            && my_lower_right.y > other.origin.y
+    }
+
+    /// Dilates the bounds by a specified amount in all directions.
+    ///
+    /// This method expands the bounds by the given `amount`, increasing the size
+    /// and adjusting the origin so that the bounds grow outwards equally in all directions.
+    /// The resulting bounds will have its width and height increased by twice the `amount`
+    /// (since it grows in both directions), and the origin will be moved by `-amount`
+    /// in both the x and y directions.
+    ///
+    /// # Arguments
+    ///
+    /// * `amount` - The amount by which to dilate the bounds.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::{Bounds, Point, Size};
+    /// let mut bounds = Bounds {
+    ///     origin: Point { x: 10, y: 10 },
+    ///     size: Size { width: 10, height: 10 },
+    /// };
+    /// bounds.dilate(5);
+    /// assert_eq!(bounds, Bounds {
+    ///     origin: Point { x: 5, y: 5 },
+    ///     size: Size { width: 20, height: 20 },
+    /// });
+    /// ```
+    pub fn dilate(&mut self, amount: T) {
+        self.origin.x = self.origin.x.clone() - amount.clone();
+        self.origin.y = self.origin.y.clone() - amount.clone();
+        let double_amount = amount.clone() + amount;
+        self.size.width = self.size.width.clone() + double_amount.clone();
+        self.size.height = self.size.height.clone() + double_amount;
+    }
+
+    /// Returns the center point of the bounds.
+    ///
+    /// Calculates the center by taking the origin's x and y coordinates and adding half the width and height
+    /// of the bounds, respectively. The center is represented as a `Point<T>` where `T` is the type of the
+    /// coordinate system.
+    ///
+    /// # Returns
+    ///
+    /// A `Point<T>` representing the center of the bounds.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::{Bounds, Point, Size};
+    /// let bounds = Bounds {
+    ///     origin: Point { x: 0, y: 0 },
+    ///     size: Size { width: 10, height: 20 },
+    /// };
+    /// let center = bounds.center();
+    /// assert_eq!(center, Point { x: 5, y: 10 });
+    /// ```
+    pub fn center(&self) -> Point<T> {
+        Point {
+            x: self.origin.x.clone() + self.size.width.clone().half(),
+            y: self.origin.y.clone() + self.size.height.clone().half(),
+        }
+    }
+}
+
+impl<T: Clone + Default + Debug + PartialOrd + Add<T, Output = T> + Sub<Output = T>> Bounds<T> {
+    /// Calculates the intersection of two `Bounds` objects.
+    ///
+    /// This method computes the overlapping region of two `Bounds`. If the bounds do not intersect,
+    /// the resulting `Bounds` will have a size with width and height of zero.
+    ///
+    /// # Arguments
+    ///
+    /// * `other` - A reference to another `Bounds` to intersect with.
+    ///
+    /// # Returns
+    ///
+    /// Returns a `Bounds` representing the intersection area. If there is no intersection,
+    /// the returned `Bounds` will have a size with width and height of zero.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::{Bounds, Point, Size};
+    /// let bounds1 = Bounds {
+    ///     origin: Point { x: 0, y: 0 },
+    ///     size: Size { width: 10, height: 10 },
+    /// };
+    /// let bounds2 = Bounds {
+    ///     origin: Point { x: 5, y: 5 },
+    ///     size: Size { width: 10, height: 10 },
+    /// };
+    /// let intersection = bounds1.intersect(&bounds2);
+    ///
+    /// assert_eq!(intersection, Bounds {
+    ///     origin: Point { x: 5, y: 5 },
+    ///     size: Size { width: 5, height: 5 },
+    /// });
+    /// ```
+    pub fn intersect(&self, other: &Self) -> Self {
+        let upper_left = self.origin.max(&other.origin);
+        let lower_right = self.lower_right().min(&other.lower_right());
+        Self::from_corners(upper_left, lower_right)
+    }
+
+    /// Computes the union of two `Bounds`.
+    ///
+    /// This method calculates the smallest `Bounds` that contains both the current `Bounds` and the `other` `Bounds`.
+    /// The resulting `Bounds` will have an origin that is the minimum of the origins of the two `Bounds`,
+    /// and a size that encompasses the furthest extents of both `Bounds`.
+    ///
+    /// # Arguments
+    ///
+    /// * `other` - A reference to another `Bounds` to create a union with.
+    ///
+    /// # Returns
+    ///
+    /// Returns a `Bounds` representing the union of the two `Bounds`.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::{Bounds, Point, Size};
+    /// let bounds1 = Bounds {
+    ///     origin: Point { x: 0, y: 0 },
+    ///     size: Size { width: 10, height: 10 },
+    /// };
+    /// let bounds2 = Bounds {
+    ///     origin: Point { x: 5, y: 5 },
+    ///     size: Size { width: 15, height: 15 },
+    /// };
+    /// let union_bounds = bounds1.union(&bounds2);
+    ///
+    /// assert_eq!(union_bounds, Bounds {
+    ///     origin: Point { x: 0, y: 0 },
+    ///     size: Size { width: 20, height: 20 },
+    /// });
+    /// ```
+    pub fn union(&self, other: &Self) -> Self {
+        let top_left = self.origin.min(&other.origin);
+        let bottom_right = self.lower_right().max(&other.lower_right());
+        Bounds::from_corners(top_left, bottom_right)
+    }
+}
+
+impl<T, Rhs> Mul<Rhs> for Bounds<T>
+where
+    T: Mul<Rhs, Output = Rhs> + Clone + Default + Debug,
+    Point<T>: Mul<Rhs, Output = Point<Rhs>>,
+    Rhs: Clone + Default + Debug,
+{
+    type Output = Bounds<Rhs>;
+
+    fn mul(self, rhs: Rhs) -> Self::Output {
+        Bounds {
+            origin: self.origin * rhs.clone(),
+            size: self.size * rhs,
+        }
+    }
+}
+
+impl<T, S> MulAssign<S> for Bounds<T>
+where
+    T: Mul<S, Output = T> + Clone + Default + Debug,
+    S: Clone,
+{
+    fn mul_assign(&mut self, rhs: S) {
+        self.origin *= rhs.clone();
+        self.size *= rhs;
+    }
+}
+
+impl<T, S> Div<S> for Bounds<T>
+where
+    Size<T>: Div<S, Output = Size<T>>,
+    T: Div<S, Output = T> + Default + Clone + Debug,
+    S: Clone,
+{
+    type Output = Self;
+
+    fn div(self, rhs: S) -> Self {
+        Self {
+            origin: self.origin / rhs.clone(),
+            size: self.size / rhs,
+        }
+    }
+}
+
+impl<T> Bounds<T>
+where
+    T: Add<T, Output = T> + Clone + Default + Debug,
+{
+    /// Returns the top edge of the bounds.
+    ///
+    /// # Returns
+    ///
+    /// A value of type `T` representing the y-coordinate of the top edge of the bounds.
+    pub fn top(&self) -> T {
+        self.origin.y.clone()
+    }
+
+    /// Returns the bottom edge of the bounds.
+    ///
+    /// # Returns
+    ///
+    /// A value of type `T` representing the y-coordinate of the bottom edge of the bounds.
+    pub fn bottom(&self) -> T {
+        self.origin.y.clone() + self.size.height.clone()
+    }
+
+    /// Returns the left edge of the bounds.
+    ///
+    /// # Returns
+    ///
+    /// A value of type `T` representing the x-coordinate of the left edge of the bounds.
+    pub fn left(&self) -> T {
+        self.origin.x.clone()
+    }
+
+    /// Returns the right edge of the bounds.
+    ///
+    /// # Returns
+    ///
+    /// A value of type `T` representing the x-coordinate of the right edge of the bounds.
+    pub fn right(&self) -> T {
+        self.origin.x.clone() + self.size.width.clone()
+    }
+
+    /// Returns the upper-right corner point of the bounds.
+    ///
+    /// # Returns
+    ///
+    /// A `Point<T>` representing the upper-right corner of the bounds.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::{Bounds, Point, Size};
+    /// let bounds = Bounds {
+    ///     origin: Point { x: 0, y: 0 },
+    ///     size: Size { width: 10, height: 20 },
+    /// };
+    /// let upper_right = bounds.upper_right();
+    /// assert_eq!(upper_right, Point { x: 10, y: 0 });
+    /// ```
+    pub fn upper_right(&self) -> Point<T> {
+        Point {
+            x: self.origin.x.clone() + self.size.width.clone(),
+            y: self.origin.y.clone(),
+        }
+    }
+
+    /// Returns the lower-right corner point of the bounds.
+    ///
+    /// # Returns
+    ///
+    /// A `Point<T>` representing the lower-right corner of the bounds.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::{Bounds, Point, Size};
+    /// let bounds = Bounds {
+    ///     origin: Point { x: 0, y: 0 },
+    ///     size: Size { width: 10, height: 20 },
+    /// };
+    /// let lower_right = bounds.lower_right();
+    /// assert_eq!(lower_right, Point { x: 10, y: 20 });
+    /// ```
+    pub fn lower_right(&self) -> Point<T> {
+        Point {
+            x: self.origin.x.clone() + self.size.width.clone(),
+            y: self.origin.y.clone() + self.size.height.clone(),
+        }
+    }
+
+    /// Returns the lower-left corner point of the bounds.
+    ///
+    /// # Returns
+    ///
+    /// A `Point<T>` representing the lower-left corner of the bounds.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::{Bounds, Point, Size};
+    /// let bounds = Bounds {
+    ///     origin: Point { x: 0, y: 0 },
+    ///     size: Size { width: 10, height: 20 },
+    /// };
+    /// let lower_left = bounds.lower_left();
+    /// assert_eq!(lower_left, Point { x: 0, y: 20 });
+    /// ```
+    pub fn lower_left(&self) -> Point<T> {
+        Point {
+            x: self.origin.x.clone(),
+            y: self.origin.y.clone() + self.size.height.clone(),
+        }
+    }
+}
+
+impl<T> Bounds<T>
+where
+    T: Add<T, Output = T> + PartialOrd + Clone + Default + Debug,
+{
+    /// Checks if the given point is within the bounds.
+    ///
+    /// This method determines whether a point lies inside the rectangle defined by the bounds,
+    /// including the edges. The point is considered inside if its x-coordinate is greater than
+    /// or equal to the left edge and less than or equal to the right edge, and its y-coordinate
+    /// is greater than or equal to the top edge and less than or equal to the bottom edge of the bounds.
+    ///
+    /// # Arguments
+    ///
+    /// * `point` - A reference to a `Point<T>` that represents the point to check.
+    ///
+    /// # Returns
+    ///
+    /// Returns `true` if the point is within the bounds, `false` otherwise.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::{Point, Bounds};
+    /// let bounds = Bounds {
+    ///     origin: Point { x: 0, y: 0 },
+    ///     size: Size { width: 10, height: 10 },
+    /// };
+    /// let inside_point = Point { x: 5, y: 5 };
+    /// let outside_point = Point { x: 15, y: 15 };
+    ///
+    /// assert!(bounds.contains_point(&inside_point));
+    /// assert!(!bounds.contains_point(&outside_point));
+    /// ```
+    pub fn contains(&self, point: &Point<T>) -> bool {
+        point.x >= self.origin.x
+            && point.x <= self.origin.x.clone() + self.size.width.clone()
+            && point.y >= self.origin.y
+            && point.y <= self.origin.y.clone() + self.size.height.clone()
+    }
+
+    /// Applies a function to the origin and size of the bounds, producing a new `Bounds<U>`.
+    ///
+    /// This method allows for converting a `Bounds<T>` to a `Bounds<U>` by specifying a closure
+    /// that defines how to convert between the two types. The closure is applied to the `origin` and
+    /// `size` fields, resulting in new bounds of the desired type.
+    ///
+    /// # Arguments
+    ///
+    /// * `f` - A closure that takes a value of type `T` and returns a value of type `U`.
+    ///
+    /// # Returns
+    ///
+    /// Returns a new `Bounds<U>` with the origin and size mapped by the provided function.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::{Bounds, Point, Size};
+    /// let bounds = Bounds {
+    ///     origin: Point { x: 10.0, y: 10.0 },
+    ///     size: Size { width: 10.0, height: 20.0 },
+    /// };
+    /// let new_bounds = bounds.map(|value| value as f64 * 1.5);
+    ///
+    /// assert_eq!(new_bounds, Bounds {
+    ///     origin: Point { x: 15.0, y: 15.0 },
+    ///     size: Size { width: 15.0, height: 30.0 },
+    /// });
+    pub fn map<U>(&self, f: impl Fn(T) -> U) -> Bounds<U>
+    where
+        U: Clone + Default + Debug,
+    {
+        Bounds {
+            origin: self.origin.map(&f),
+            size: self.size.map(f),
+        }
+    }
+}
+
+impl Bounds<Pixels> {
+    /// Scales the bounds by a given factor, typically used to adjust for display scaling.
+    ///
+    /// This method multiplies the origin and size of the bounds by the provided scaling factor,
+    /// resulting in a new `Bounds<ScaledPixels>` that is proportionally larger or smaller
+    /// depending on the scaling factor. This can be used to ensure that the bounds are properly
+    /// scaled for different display densities.
+    ///
+    /// # Arguments
+    ///
+    /// * `factor` - The scaling factor to apply to the origin and size, typically the display's scaling factor.
+    ///
+    /// # Returns
+    ///
+    /// Returns a new `Bounds<ScaledPixels>` that represents the scaled bounds.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::{Bounds, Point, Size, Pixels};
+    /// let bounds = Bounds {
+    ///     origin: Point { x: Pixels(10.0), y: Pixels(20.0) },
+    ///     size: Size { width: Pixels(30.0), height: Pixels(40.0) },
+    /// };
+    /// let display_scale_factor = 2.0;
+    /// let scaled_bounds = bounds.scale(display_scale_factor);
+    /// assert_eq!(scaled_bounds, Bounds {
+    ///     origin: Point { x: ScaledPixels(20.0), y: ScaledPixels(40.0) },
+    ///     size: Size { width: ScaledPixels(60.0), height: ScaledPixels(80.0) },
+    /// });
+    /// ```
+    pub fn scale(&self, factor: f32) -> Bounds<ScaledPixels> {
+        Bounds {
+            origin: self.origin.scale(factor),
+            size: self.size.scale(factor),
+        }
+    }
+}
+
+impl<T: Clone + Debug + Copy + Default> Copy for Bounds<T> {}
+
+/// Represents the edges of a box in a 2D space, such as padding or margin.
+///
+/// Each field represents the size of the edge on one side of the box: `top`, `right`, `bottom`, and `left`.
+///
+/// # Examples
+///
+/// ```
+/// # use zed::Edges;
+/// let edges = Edges {
+///     top: 10.0,
+///     right: 20.0,
+///     bottom: 30.0,
+///     left: 40.0,
+/// };
+///
+/// assert_eq!(edges.top, 10.0);
+/// assert_eq!(edges.right, 20.0);
+/// assert_eq!(edges.bottom, 30.0);
+/// assert_eq!(edges.left, 40.0);
+/// ```
+#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
+#[refineable(Debug)]
+#[repr(C)]
+pub struct Edges<T: Clone + Default + Debug> {
+    pub top: T,
+    pub right: T,
+    pub bottom: T,
+    pub left: T,
+}
+
+impl<T> Mul for Edges<T>
+where
+    T: Mul<Output = T> + Clone + Default + Debug,
+{
+    type Output = Self;
+
+    fn mul(self, rhs: Self) -> Self::Output {
+        Self {
+            top: self.top.clone() * rhs.top,
+            right: self.right.clone() * rhs.right,
+            bottom: self.bottom.clone() * rhs.bottom,
+            left: self.left.clone() * rhs.left,
+        }
+    }
+}
+
+impl<T, S> MulAssign<S> for Edges<T>
+where
+    T: Mul<S, Output = T> + Clone + Default + Debug,
+    S: Clone,
+{
+    fn mul_assign(&mut self, rhs: S) {
+        self.top = self.top.clone() * rhs.clone();
+        self.right = self.right.clone() * rhs.clone();
+        self.bottom = self.bottom.clone() * rhs.clone();
+        self.left = self.left.clone() * rhs;
+    }
+}
+
+impl<T: Clone + Default + Debug + Copy> Copy for Edges<T> {}
+
+impl<T: Clone + Default + Debug> Edges<T> {
+    /// Constructs `Edges` where all sides are set to the same specified value.
+    ///
+    /// This function creates an `Edges` instance with the `top`, `right`, `bottom`, and `left` fields all initialized
+    /// to the same value provided as an argument. This is useful when you want to have uniform edges around a box,
+    /// such as padding or margin with the same size on all sides.
+    ///
+    /// # Arguments
+    ///
+    /// * `value` - The value to set for all four sides of the edges.
+    ///
+    /// # Returns
+    ///
+    /// An `Edges` instance with all sides set to the given value.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::Edges;
+    /// let uniform_edges = Edges::all(10.0);
+    /// assert_eq!(uniform_edges.top, 10.0);
+    /// assert_eq!(uniform_edges.right, 10.0);
+    /// assert_eq!(uniform_edges.bottom, 10.0);
+    /// assert_eq!(uniform_edges.left, 10.0);
+    /// ```
+    pub fn all(value: T) -> Self {
+        Self {
+            top: value.clone(),
+            right: value.clone(),
+            bottom: value.clone(),
+            left: value,
+        }
+    }
+
+    /// Applies a function to each field of the `Edges`, producing a new `Edges<U>`.
+    ///
+    /// This method allows for converting an `Edges<T>` to an `Edges<U>` by specifying a closure
+    /// that defines how to convert between the two types. The closure is applied to each field
+    /// (`top`, `right`, `bottom`, `left`), resulting in new edges of the desired type.
+    ///
+    /// # Arguments
+    ///
+    /// * `f` - A closure that takes a reference to a value of type `T` and returns a value of type `U`.
+    ///
+    /// # Returns
+    ///
+    /// Returns a new `Edges<U>` with each field mapped by the provided function.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::Edges;
+    /// let edges = Edges { top: 10, right: 20, bottom: 30, left: 40 };
+    /// let edges_float = edges.map(|&value| value as f32 * 1.1);
+    /// assert_eq!(edges_float, Edges { top: 11.0, right: 22.0, bottom: 33.0, left: 44.0 });
+    /// ```
+    pub fn map<U>(&self, f: impl Fn(&T) -> U) -> Edges<U>
+    where
+        U: Clone + Default + Debug,
+    {
+        Edges {
+            top: f(&self.top),
+            right: f(&self.right),
+            bottom: f(&self.bottom),
+            left: f(&self.left),
+        }
+    }
+
+    /// Checks if any of the edges satisfy a given predicate.
+    ///
+    /// This method applies a predicate function to each field of the `Edges` and returns `true` if any field satisfies the predicate.
+    ///
+    /// # Arguments
+    ///
+    /// * `predicate` - A closure that takes a reference to a value of type `T` and returns a `bool`.
+    ///
+    /// # Returns
+    ///
+    /// Returns `true` if the predicate returns `true` for any of the edge values, `false` otherwise.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::Edges;
+    /// let edges = Edges {
+    ///     top: 10,
+    ///     right: 0,
+    ///     bottom: 5,
+    ///     left: 0,
+    /// };
+    ///
+    /// assert!(edges.any(|value| *value == 0));
+    /// assert!(edges.any(|value| *value > 0));
+    /// assert!(!edges.any(|value| *value > 10));
+    /// ```
+    pub fn any<F: Fn(&T) -> bool>(&self, predicate: F) -> bool {
+        predicate(&self.top)
+            || predicate(&self.right)
+            || predicate(&self.bottom)
+            || predicate(&self.left)
+    }
+}
+
+impl Edges<Length> {
+    /// Sets the edges of the `Edges` struct to `auto`, which is a special value that allows the layout engine to automatically determine the size of the edges.
+    ///
+    /// This is typically used in layout contexts where the exact size of the edges is not important, or when the size should be calculated based on the content or container.
+    ///
+    /// # Returns
+    ///
+    /// Returns an `Edges<Length>` with all edges set to `Length::Auto`.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::Edges;
+    /// let auto_edges = Edges::auto();
+    /// assert_eq!(auto_edges.top, Length::Auto);
+    /// assert_eq!(auto_edges.right, Length::Auto);
+    /// assert_eq!(auto_edges.bottom, Length::Auto);
+    /// assert_eq!(auto_edges.left, Length::Auto);
+    /// ```
+    pub fn auto() -> Self {
+        Self {
+            top: Length::Auto,
+            right: Length::Auto,
+            bottom: Length::Auto,
+            left: Length::Auto,
+        }
+    }
+
+    /// Sets the edges of the `Edges` struct to zero, which means no size or thickness.
+    ///
+    /// This is typically used when you want to specify that a box (like a padding or margin area)
+    /// should have no edges, effectively making it non-existent or invisible in layout calculations.
+    ///
+    /// # Returns
+    ///
+    /// Returns an `Edges<Length>` with all edges set to zero length.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::Edges;
+    /// let no_edges = Edges::zero();
+    /// assert_eq!(no_edges.top, Length::Definite(DefiniteLength::from(Pixels(0.))));
+    /// assert_eq!(no_edges.right, Length::Definite(DefiniteLength::from(Pixels(0.))));
+    /// assert_eq!(no_edges.bottom, Length::Definite(DefiniteLength::from(Pixels(0.))));
+    /// assert_eq!(no_edges.left, Length::Definite(DefiniteLength::from(Pixels(0.))));
+    /// ```
+    pub fn zero() -> Self {
+        Self {
+            top: px(0.).into(),
+            right: px(0.).into(),
+            bottom: px(0.).into(),
+            left: px(0.).into(),
+        }
+    }
+}
+
+impl Edges<DefiniteLength> {
+    /// Sets the edges of the `Edges` struct to zero, which means no size or thickness.
+    ///
+    /// This is typically used when you want to specify that a box (like a padding or margin area)
+    /// should have no edges, effectively making it non-existent or invisible in layout calculations.
+    ///
+    /// # Returns
+    ///
+    /// Returns an `Edges<DefiniteLength>` with all edges set to zero length.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::Edges;
+    /// let no_edges = Edges::zero();
+    /// assert_eq!(no_edges.top, DefiniteLength::from(zed::px(0.)));
+    /// assert_eq!(no_edges.right, DefiniteLength::from(zed::px(0.)));
+    /// assert_eq!(no_edges.bottom, DefiniteLength::from(zed::px(0.)));
+    /// assert_eq!(no_edges.left, DefiniteLength::from(zed::px(0.)));
+    /// ```
+    pub fn zero() -> Self {
+        Self {
+            top: px(0.).into(),
+            right: px(0.).into(),
+            bottom: px(0.).into(),
+            left: px(0.).into(),
+        }
+    }
+
+    /// Converts the `DefiniteLength` to `Pixels` based on the parent size and the REM size.
+    ///
+    /// This method allows for a `DefiniteLength` value to be converted into pixels, taking into account
+    /// the size of the parent element (for percentage-based lengths) and the size of a rem unit (for rem-based lengths).
+    ///
+    /// # Arguments
+    ///
+    /// * `parent_size` - `Size<AbsoluteLength>` representing the size of the parent element.
+    /// * `rem_size` - `Pixels` representing the size of one REM unit.
+    ///
+    /// # Returns
+    ///
+    /// Returns an `Edges<Pixels>` representing the edges with lengths converted to pixels.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::{Edges, DefiniteLength, px, AbsoluteLength, Size};
+    /// let edges = Edges {
+    ///     top: DefiniteLength::Absolute(AbsoluteLength::Pixels(px(10.0))),
+    ///     right: DefiniteLength::Fraction(0.5),
+    ///     bottom: DefiniteLength::Absolute(AbsoluteLength::Rems(rems(2.0))),
+    ///     left: DefiniteLength::Fraction(0.25),
+    /// };
+    /// let parent_size = Size {
+    ///     width: AbsoluteLength::Pixels(px(200.0)),
+    ///     height: AbsoluteLength::Pixels(px(100.0)),
+    /// };
+    /// let rem_size = px(16.0);
+    /// let edges_in_pixels = edges.to_pixels(parent_size, rem_size);
+    ///
+    /// assert_eq!(edges_in_pixels.top, px(10.0)); // Absolute length in pixels
+    /// assert_eq!(edges_in_pixels.right, px(100.0)); // 50% of parent width
+    /// assert_eq!(edges_in_pixels.bottom, px(32.0)); // 2 rems
+    /// assert_eq!(edges_in_pixels.left, px(50.0)); // 25% of parent width
+    /// ```
+    pub fn to_pixels(&self, parent_size: Size<AbsoluteLength>, rem_size: Pixels) -> Edges<Pixels> {
+        Edges {
+            top: self.top.to_pixels(parent_size.height, rem_size),
+            right: self.right.to_pixels(parent_size.width, rem_size),
+            bottom: self.bottom.to_pixels(parent_size.height, rem_size),
+            left: self.left.to_pixels(parent_size.width, rem_size),
+        }
+    }
+}
+
+impl Edges<AbsoluteLength> {
+    /// Sets the edges of the `Edges` struct to zero, which means no size or thickness.
+    ///
+    /// This is typically used when you want to specify that a box (like a padding or margin area)
+    /// should have no edges, effectively making it non-existent or invisible in layout calculations.
+    ///
+    /// # Returns
+    ///
+    /// Returns an `Edges<AbsoluteLength>` with all edges set to zero length.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::Edges;
+    /// let no_edges = Edges::zero();
+    /// assert_eq!(no_edges.top, AbsoluteLength::Pixels(Pixels(0.0)));
+    /// assert_eq!(no_edges.right, AbsoluteLength::Pixels(Pixels(0.0)));
+    /// assert_eq!(no_edges.bottom, AbsoluteLength::Pixels(Pixels(0.0)));
+    /// assert_eq!(no_edges.left, AbsoluteLength::Pixels(Pixels(0.0)));
+    /// ```
+    pub fn zero() -> Self {
+        Self {
+            top: px(0.).into(),
+            right: px(0.).into(),
+            bottom: px(0.).into(),
+            left: px(0.).into(),
+        }
+    }
+
+    /// Converts the `AbsoluteLength` to `Pixels` based on the `rem_size`.
+    ///
+    /// If the `AbsoluteLength` is already in pixels, it simply returns the corresponding `Pixels` value.
+    /// If the `AbsoluteLength` is in rems, it multiplies the number of rems by the `rem_size` to convert it to pixels.
+    ///
+    /// # Arguments
+    ///
+    /// * `rem_size` - The size of one rem unit in pixels.
+    ///
+    /// # Returns
+    ///
+    /// Returns an `Edges<Pixels>` representing the edges with lengths converted to pixels.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::{Edges, AbsoluteLength, Pixels, px};
+    /// let edges = Edges {
+    ///     top: AbsoluteLength::Pixels(px(10.0)),
+    ///     right: AbsoluteLength::Rems(rems(1.0)),
+    ///     bottom: AbsoluteLength::Pixels(px(20.0)),
+    ///     left: AbsoluteLength::Rems(rems(2.0)),
+    /// };
+    /// let rem_size = px(16.0);
+    /// let edges_in_pixels = edges.to_pixels(rem_size);
+    ///
+    /// assert_eq!(edges_in_pixels.top, px(10.0)); // Already in pixels
+    /// assert_eq!(edges_in_pixels.right, px(16.0)); // 1 rem converted to pixels
+    /// assert_eq!(edges_in_pixels.bottom, px(20.0)); // Already in pixels
+    /// assert_eq!(edges_in_pixels.left, px(32.0)); // 2 rems converted to pixels
+    /// ```
+    pub fn to_pixels(&self, rem_size: Pixels) -> Edges<Pixels> {
+        Edges {
+            top: self.top.to_pixels(rem_size),
+            right: self.right.to_pixels(rem_size),
+            bottom: self.bottom.to_pixels(rem_size),
+            left: self.left.to_pixels(rem_size),
+        }
+    }
+}
+
+impl Edges<Pixels> {
+    /// Scales the `Edges<Pixels>` by a given factor, returning `Edges<ScaledPixels>`.
+    ///
+    /// This method is typically used for adjusting the edge sizes for different display densities or scaling factors.
+    ///
+    /// # Arguments
+    ///
+    /// * `factor` - The scaling factor to apply to each edge.
+    ///
+    /// # Returns
+    ///
+    /// Returns a new `Edges<ScaledPixels>` where each edge is the result of scaling the original edge by the given factor.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::{Edges, Pixels};
+    /// let edges = Edges {
+    ///     top: Pixels(10.0),
+    ///     right: Pixels(20.0),
+    ///     bottom: Pixels(30.0),
+    ///     left: Pixels(40.0),
+    /// };
+    /// let scaled_edges = edges.scale(2.0);
+    /// assert_eq!(scaled_edges.top, ScaledPixels(20.0));
+    /// assert_eq!(scaled_edges.right, ScaledPixels(40.0));
+    /// assert_eq!(scaled_edges.bottom, ScaledPixels(60.0));
+    /// assert_eq!(scaled_edges.left, ScaledPixels(80.0));
+    /// ```
+    pub fn scale(&self, factor: f32) -> Edges<ScaledPixels> {
+        Edges {
+            top: self.top.scale(factor),
+            right: self.right.scale(factor),
+            bottom: self.bottom.scale(factor),
+            left: self.left.scale(factor),
+        }
+    }
+
+    /// Returns the maximum value of any edge.
+    ///
+    /// # Returns
+    ///
+    /// The maximum `Pixels` value among all four edges.
+    pub fn max(&self) -> Pixels {
+        self.top.max(self.right).max(self.bottom).max(self.left)
+    }
+}
+
+impl From<f32> for Edges<Pixels> {
+    fn from(val: f32) -> Self {
+        Edges {
+            top: val.into(),
+            right: val.into(),
+            bottom: val.into(),
+            left: val.into(),
+        }
+    }
+}
+
+/// Represents the corners of a box in a 2D space, such as border radius.
+///
+/// Each field represents the size of the corner on one side of the box: `top_left`, `top_right`, `bottom_right`, and `bottom_left`.
+/// ```
+#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
+#[refineable(Debug)]
+#[repr(C)]
+pub struct Corners<T: Clone + Default + Debug> {
+    pub top_left: T,
+    pub top_right: T,
+    pub bottom_right: T,
+    pub bottom_left: T,
+}
+
+impl<T> Corners<T>
+where
+    T: Clone + Default + Debug,
+{
+    /// Constructs `Corners` where all sides are set to the same specified value.
+    ///
+    /// This function creates a `Corners` instance with the `top_left`, `top_right`, `bottom_right`, and `bottom_left` fields all initialized
+    /// to the same value provided as an argument. This is useful when you want to have uniform corners around a box,
+    /// such as a uniform border radius on a rectangle.
+    ///
+    /// # Arguments
+    ///
+    /// * `value` - The value to set for all four corners.
+    ///
+    /// # Returns
+    ///
+    /// An `Corners` instance with all corners set to the given value.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::Corners;
+    /// let uniform_corners = Corners::all(5.0);
+    /// assert_eq!(uniform_corners.top_left, 5.0);
+    /// assert_eq!(uniform_corners.top_right, 5.0);
+    /// assert_eq!(uniform_corners.bottom_right, 5.0);
+    /// assert_eq!(uniform_corners.bottom_left, 5.0);
+    /// ```
+    pub fn all(value: T) -> Self {
+        Self {
+            top_left: value.clone(),
+            top_right: value.clone(),
+            bottom_right: value.clone(),
+            bottom_left: value,
+        }
+    }
+}
+
+impl Corners<AbsoluteLength> {
+    /// Converts the `AbsoluteLength` to `Pixels` based on the provided size and rem size, ensuring the resulting
+    /// `Pixels` do not exceed half of the maximum of the provided size's width and height.
+    ///
+    /// This method is particularly useful when dealing with corner radii, where the radius in pixels should not
+    /// exceed half the size of the box it applies to, to avoid the corners overlapping.
+    ///
+    /// # Arguments
+    ///
+    /// * `size` - The `Size<Pixels>` against which the maximum allowable radius is determined.
+    /// * `rem_size` - The size of one REM unit in pixels, used for conversion if the `AbsoluteLength` is in REMs.
+    ///
+    /// # Returns
+    ///
+    /// Returns a `Corners<Pixels>` instance with each corner's length converted to pixels and clamped to the
+    /// maximum allowable radius based on the provided size.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::{Corners, AbsoluteLength, Pixels, Size};
+    /// let corners = Corners {
+    ///     top_left: AbsoluteLength::Pixels(Pixels(15.0)),
+    ///     top_right: AbsoluteLength::Rems(Rems(1.0)),
+    ///     bottom_right: AbsoluteLength::Pixels(Pixels(20.0)),
+    ///     bottom_left: AbsoluteLength::Rems(Rems(2.0)),
+    /// };
+    /// let size = Size { width: Pixels(100.0), height: Pixels(50.0) };
+    /// let rem_size = Pixels(16.0);
+    /// let corners_in_pixels = corners.to_pixels(size, rem_size);
+    ///
+    /// // The resulting corners should not exceed half the size of the smallest dimension (50.0 / 2.0 = 25.0).
+    /// assert_eq!(corners_in_pixels.top_left, Pixels(15.0));
+    /// assert_eq!(corners_in_pixels.top_right, Pixels(16.0)); // 1 rem converted to pixels
+    /// assert_eq!(corners_in_pixels.bottom_right, Pixels(20.0).min(Pixels(25.0))); // Clamped to 25.0
+    /// assert_eq!(corners_in_pixels.bottom_left, Pixels(32.0).min(Pixels(25.0))); // 2 rems converted to pixels and clamped
+    /// ```
+    pub fn to_pixels(&self, size: Size<Pixels>, rem_size: Pixels) -> Corners<Pixels> {
+        let max = size.width.max(size.height) / 2.;
+        Corners {
+            top_left: self.top_left.to_pixels(rem_size).min(max),
+            top_right: self.top_right.to_pixels(rem_size).min(max),
+            bottom_right: self.bottom_right.to_pixels(rem_size).min(max),
+            bottom_left: self.bottom_left.to_pixels(rem_size).min(max),
+        }
+    }
+}
+
+impl Corners<Pixels> {
+    /// Scales the `Corners<Pixels>` by a given factor, returning `Corners<ScaledPixels>`.
+    ///
+    /// This method is typically used for adjusting the corner sizes for different display densities or scaling factors.
+    ///
+    /// # Arguments
+    ///
+    /// * `factor` - The scaling factor to apply to each corner.
+    ///
+    /// # Returns
+    ///
+    /// Returns a new `Corners<ScaledPixels>` where each corner is the result of scaling the original corner by the given factor.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::{Corners, Pixels};
+    /// let corners = Corners {
+    ///     top_left: Pixels(10.0),
+    ///     top_right: Pixels(20.0),
+    ///     bottom_right: Pixels(30.0),
+    ///     bottom_left: Pixels(40.0),
+    /// };
+    /// let scaled_corners = corners.scale(2.0);
+    /// assert_eq!(scaled_corners.top_left, ScaledPixels(20.0));
+    /// assert_eq!(scaled_corners.top_right, ScaledPixels(40.0));
+    /// assert_eq!(scaled_corners.bottom_right, ScaledPixels(60.0));
+    /// assert_eq!(scaled_corners.bottom_left, ScaledPixels(80.0));
+    /// ```
+    pub fn scale(&self, factor: f32) -> Corners<ScaledPixels> {
+        Corners {
+            top_left: self.top_left.scale(factor),
+            top_right: self.top_right.scale(factor),
+            bottom_right: self.bottom_right.scale(factor),
+            bottom_left: self.bottom_left.scale(factor),
+        }
+    }
+
+    /// Returns the maximum value of any corner.
+    ///
+    /// # Returns
+    ///
+    /// The maximum `Pixels` value among all four corners.
+    pub fn max(&self) -> Pixels {
+        self.top_left
+            .max(self.top_right)
+            .max(self.bottom_right)
+            .max(self.bottom_left)
+    }
+}
+
+impl<T: Clone + Default + Debug> Corners<T> {
+    /// Applies a function to each field of the `Corners`, producing a new `Corners<U>`.
+    ///
+    /// This method allows for converting a `Corners<T>` to a `Corners<U>` by specifying a closure
+    /// that defines how to convert between the two types. The closure is applied to each field
+    /// (`top_left`, `top_right`, `bottom_right`, `bottom_left`), resulting in new corners of the desired type.
+    ///
+    /// # Arguments
+    ///
+    /// * `f` - A closure that takes a reference to a value of type `T` and returns a value of type `U`.
+    ///
+    /// # Returns
+    ///
+    /// Returns a new `Corners<U>` with each field mapped by the provided function.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::{Corners, Pixels};
+    /// let corners = Corners {
+    ///     top_left: Pixels(10.0),
+    ///     top_right: Pixels(20.0),
+    ///     bottom_right: Pixels(30.0),
+    ///     bottom_left: Pixels(40.0),
+    /// };
+    /// let corners_in_rems = corners.map(|&px| Rems(px.0 / 16.0));
+    /// assert_eq!(corners_in_rems, Corners {
+    ///     top_left: Rems(0.625),
+    ///     top_right: Rems(1.25),
+    ///     bottom_right: Rems(1.875),
+    ///     bottom_left: Rems(2.5),
+    /// });
+    /// ```
+    pub fn map<U>(&self, f: impl Fn(&T) -> U) -> Corners<U>
+    where
+        U: Clone + Default + Debug,
+    {
+        Corners {
+            top_left: f(&self.top_left),
+            top_right: f(&self.top_right),
+            bottom_right: f(&self.bottom_right),
+            bottom_left: f(&self.bottom_left),
+        }
+    }
+}
+
+impl<T> Mul for Corners<T>
+where
+    T: Mul<Output = T> + Clone + Default + Debug,
+{
+    type Output = Self;
+
+    fn mul(self, rhs: Self) -> Self::Output {
+        Self {
+            top_left: self.top_left.clone() * rhs.top_left,
+            top_right: self.top_right.clone() * rhs.top_right,
+            bottom_right: self.bottom_right.clone() * rhs.bottom_right,
+            bottom_left: self.bottom_left.clone() * rhs.bottom_left,
+        }
+    }
+}
+
+impl<T, S> MulAssign<S> for Corners<T>
+where
+    T: Mul<S, Output = T> + Clone + Default + Debug,
+    S: Clone,
+{
+    fn mul_assign(&mut self, rhs: S) {
+        self.top_left = self.top_left.clone() * rhs.clone();
+        self.top_right = self.top_right.clone() * rhs.clone();
+        self.bottom_right = self.bottom_right.clone() * rhs.clone();
+        self.bottom_left = self.bottom_left.clone() * rhs;
+    }
+}
+
+impl<T> Copy for Corners<T> where T: Copy + Clone + Default + Debug {}
+
+impl From<f32> for Corners<Pixels> {
+    fn from(val: f32) -> Self {
+        Corners {
+            top_left: val.into(),
+            top_right: val.into(),
+            bottom_right: val.into(),
+            bottom_left: val.into(),
+        }
+    }
+}
+
+impl From<Pixels> for Corners<Pixels> {
+    fn from(val: Pixels) -> Self {
+        Corners {
+            top_left: val,
+            top_right: val,
+            bottom_right: val,
+            bottom_left: val,
+        }
+    }
+}
+
+/// Represents a length in pixels, the base unit of measurement in the UI framework.
+///
+/// `Pixels` is a value type that represents an absolute length in pixels, which is used
+/// for specifying sizes, positions, and distances in the UI. It is the fundamental unit
+/// of measurement for all visual elements and layout calculations.
+///
+/// The inner value is an `f32`, allowing for sub-pixel precision which can be useful for
+/// anti-aliasing and animations. However, when applied to actual pixel grids, the value
+/// is typically rounded to the nearest integer.
+///
+/// # Examples
+///
+/// ```
+/// use zed::Pixels;
+///
+/// // Define a length of 10 pixels
+/// let length = Pixels(10.0);
+///
+/// // Define a length and scale it by a factor of 2
+/// let scaled_length = length.scale(2.0);
+/// assert_eq!(scaled_length, Pixels(20.0));
+/// ```
+#[derive(
+    Clone,
+    Copy,
+    Default,
+    Add,
+    AddAssign,
+    Sub,
+    SubAssign,
+    Neg,
+    Div,
+    DivAssign,
+    PartialEq,
+    Serialize,
+    Deserialize,
+)]
+#[repr(transparent)]
+pub struct Pixels(pub f32);
+
+impl std::ops::Div for Pixels {
+    type Output = f32;
+
+    fn div(self, rhs: Self) -> Self::Output {
+        self.0 / rhs.0
+    }
+}
+
+impl std::ops::DivAssign for Pixels {
+    fn div_assign(&mut self, rhs: Self) {
+        *self = Self(self.0 / rhs.0);
+    }
+}
+
+impl std::ops::RemAssign for Pixels {
+    fn rem_assign(&mut self, rhs: Self) {
+        self.0 %= rhs.0;
+    }
 }
 
-impl Default for PathBuilder {
-    fn default() -> Self {
-        PathBuilder::new()
+impl std::ops::Rem for Pixels {
+    type Output = Self;
+
+    fn rem(self, rhs: Self) -> Self {
+        Self(self.0 % rhs.0)
     }
 }
 
-impl PathBuilder {
-    pub fn new() -> Self {
-        Self {
-            vertices: Vec::new(),
-            start: vec2f(0., 0.),
-            current: vec2f(0., 0.),
-            contour_count: 0,
-            bounds: RectF::default(),
-        }
+impl Mul<f32> for Pixels {
+    type Output = Pixels;
+
+    fn mul(self, other: f32) -> Pixels {
+        Pixels(self.0 * other)
     }
+}
 
-    pub fn reset(&mut self, point: Vector2F) {
-        self.vertices.clear();
-        self.start = point;
-        self.current = point;
-        self.contour_count = 0;
+impl Mul<usize> for Pixels {
+    type Output = Pixels;
+
+    fn mul(self, other: usize) -> Pixels {
+        Pixels(self.0 * other as f32)
     }
+}
 
-    pub fn line_to(&mut self, point: Vector2F) {
-        self.contour_count += 1;
-        if self.contour_count > 1 {
-            self.push_triangle(self.start, self.current, point, PathVertexKind::Solid);
-        }
+impl Mul<Pixels> for f32 {
+    type Output = Pixels;
 
-        self.current = point;
+    fn mul(self, rhs: Pixels) -> Self::Output {
+        Pixels(self * rhs.0)
     }
+}
 
-    pub fn curve_to(&mut self, point: Vector2F, ctrl: Vector2F) {
-        self.contour_count += 1;
-        if self.contour_count > 1 {
-            self.push_triangle(self.start, self.current, point, PathVertexKind::Solid);
-        }
+impl MulAssign<f32> for Pixels {
+    fn mul_assign(&mut self, other: f32) {
+        self.0 *= other;
+    }
+}
+
+impl Pixels {
+    /// Represents zero pixels.
+    pub const ZERO: Pixels = Pixels(0.0);
+    /// The maximum value that can be represented by `Pixels`.
+    pub const MAX: Pixels = Pixels(f32::MAX);
 
-        self.push_triangle(self.current, ctrl, point, PathVertexKind::Quadratic);
-        self.current = point;
+    /// Floors the `Pixels` value to the nearest whole number.
+    ///
+    /// # Returns
+    ///
+    /// Returns a new `Pixels` instance with the floored value.
+    pub fn floor(&self) -> Self {
+        Self(self.0.floor())
     }
 
-    pub fn build(mut self, color: Color, clip_bounds: Option<RectF>) -> Path {
-        if let Some(clip_bounds) = clip_bounds {
-            self.bounds = self.bounds.intersection(clip_bounds).unwrap_or_default();
-        }
-        Path {
-            bounds: self.bounds,
-            color,
-            vertices: self.vertices,
-        }
+    /// Rounds the `Pixels` value to the nearest whole number.
+    ///
+    /// # Returns
+    ///
+    /// Returns a new `Pixels` instance with the rounded value.
+    pub fn round(&self) -> Self {
+        Self(self.0.round())
     }
 
-    fn push_triangle(&mut self, a: Vector2F, b: Vector2F, c: Vector2F, kind: PathVertexKind) {
-        if self.vertices.is_empty() {
-            self.bounds = RectF::new(a, Vector2F::zero());
-        }
-        self.bounds = self.bounds.union_point(a).union_point(b).union_point(c);
+    /// Returns the ceiling of the `Pixels` value to the nearest whole number.
+    ///
+    /// # Returns
+    ///
+    /// Returns a new `Pixels` instance with the ceiling value.
+    pub fn ceil(&self) -> Self {
+        Self(self.0.ceil())
+    }
 
-        match kind {
-            PathVertexKind::Solid => {
-                self.vertices.push(PathVertex {
-                    xy_position: a,
-                    st_position: vec2f(0., 1.),
-                });
-                self.vertices.push(PathVertex {
-                    xy_position: b,
-                    st_position: vec2f(0., 1.),
-                });
-                self.vertices.push(PathVertex {
-                    xy_position: c,
-                    st_position: vec2f(0., 1.),
-                });
-            }
-            PathVertexKind::Quadratic => {
-                self.vertices.push(PathVertex {
-                    xy_position: a,
-                    st_position: vec2f(0., 0.),
-                });
-                self.vertices.push(PathVertex {
-                    xy_position: b,
-                    st_position: vec2f(0.5, 0.),
-                });
-                self.vertices.push(PathVertex {
-                    xy_position: c,
-                    st_position: vec2f(1., 1.),
-                });
-            }
-        }
+    /// Scales the `Pixels` value by a given factor, producing `ScaledPixels`.
+    ///
+    /// This method is used when adjusting pixel values for display scaling factors,
+    /// such as high DPI (dots per inch) or Retina displays, where the pixel density is higher and
+    /// thus requires scaling to maintain visual consistency and readability.
+    ///
+    /// The resulting `ScaledPixels` represent the scaled value which can be used for rendering
+    /// calculations where display scaling is considered.
+    pub fn scale(&self, factor: f32) -> ScaledPixels {
+        ScaledPixels(self.0 * factor)
+    }
+
+    /// Raises the `Pixels` value to a given power.
+    ///
+    /// # Arguments
+    ///
+    /// * `exponent` - The exponent to raise the `Pixels` value by.
+    ///
+    /// # Returns
+    ///
+    /// Returns a new `Pixels` instance with the value raised to the given exponent.
+    pub fn pow(&self, exponent: f32) -> Self {
+        Self(self.0.powf(exponent))
+    }
+
+    /// Returns the absolute value of the `Pixels`.
+    ///
+    /// # Returns
+    ///
+    /// A new `Pixels` instance with the absolute value of the original `Pixels`.
+    pub fn abs(&self) -> Self {
+        Self(self.0.abs())
     }
 }
 
-pub fn deserialize_vec2f<'de, D>(deserializer: D) -> Result<Vector2F, D::Error>
-where
-    D: Deserializer<'de>,
-{
-    let [x, y]: [f32; 2] = Deserialize::deserialize(deserializer)?;
-    Ok(vec2f(x, y))
+impl Mul<Pixels> for Pixels {
+    type Output = Pixels;
+
+    fn mul(self, rhs: Pixels) -> Self::Output {
+        Pixels(self.0 * rhs.0)
+    }
 }
 
-impl ToJson for Vector2F {
-    fn to_json(&self) -> serde_json::Value {
-        json!([self.x(), self.y()])
+impl Eq for Pixels {}
+
+impl PartialOrd for Pixels {
+    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
+        self.0.partial_cmp(&other.0)
     }
 }
 
-impl ToJson for RectF {
-    fn to_json(&self) -> serde_json::Value {
-        json!({"origin": self.origin().to_json(), "size": self.size().to_json()})
+impl Ord for Pixels {
+    fn cmp(&self, other: &Self) -> cmp::Ordering {
+        self.partial_cmp(other).unwrap()
     }
 }
 
-#[derive(Refineable, Debug)]
-#[refineable(Debug)]
-pub struct Point<T: Clone + Default + Debug> {
-    pub x: T,
-    pub y: T,
+impl std::hash::Hash for Pixels {
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        self.0.to_bits().hash(state);
+    }
 }
 
-impl<T: Clone + Default + Debug> Clone for Point<T> {
-    fn clone(&self) -> Self {
-        Self {
-            x: self.x.clone(),
-            y: self.y.clone(),
-        }
+impl From<f64> for Pixels {
+    fn from(pixels: f64) -> Self {
+        Pixels(pixels as f32)
     }
 }
 
-impl<T: Clone + Default + Debug> Into<taffy::geometry::Point<T>> for Point<T> {
-    fn into(self) -> taffy::geometry::Point<T> {
-        taffy::geometry::Point {
-            x: self.x,
-            y: self.y,
-        }
+impl From<f32> for Pixels {
+    fn from(pixels: f32) -> Self {
+        Pixels(pixels)
     }
 }
 
-#[derive(Refineable, Clone, Debug)]
-#[refineable(Debug)]
-pub struct Size<T: Clone + Default + Debug> {
-    pub width: T,
-    pub height: T,
+impl Debug for Pixels {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{} px", self.0)
+    }
 }
 
-impl<S, T: Clone + Default + Debug> From<taffy::geometry::Size<S>> for Size<T>
-where
-    S: Into<T>,
-{
-    fn from(value: taffy::geometry::Size<S>) -> Self {
-        Self {
-            width: value.width.into(),
-            height: value.height.into(),
-        }
+impl From<Pixels> for f32 {
+    fn from(pixels: Pixels) -> Self {
+        pixels.0
     }
 }
 
-impl<S, T: Clone + Default + Debug> Into<taffy::geometry::Size<S>> for Size<T>
-where
-    T: Into<S>,
-{
-    fn into(self) -> taffy::geometry::Size<S> {
-        taffy::geometry::Size {
-            width: self.width.into(),
-            height: self.height.into(),
-        }
+impl From<&Pixels> for f32 {
+    fn from(pixels: &Pixels) -> Self {
+        pixels.0
     }
 }
 
-impl Size<DefiniteLength> {
-    pub fn zero() -> Self {
-        Self {
-            width: pixels(0.).into(),
-            height: pixels(0.).into(),
-        }
+impl From<Pixels> for f64 {
+    fn from(pixels: Pixels) -> Self {
+        pixels.0 as f64
     }
+}
 
-    pub fn to_taffy(&self, rem_size: f32) -> taffy::geometry::Size<taffy::style::LengthPercentage> {
-        taffy::geometry::Size {
-            width: self.width.to_taffy(rem_size),
-            height: self.height.to_taffy(rem_size),
-        }
+impl From<Pixels> for u32 {
+    fn from(pixels: Pixels) -> Self {
+        pixels.0 as u32
     }
 }
 
-impl Size<Length> {
-    pub fn auto() -> Self {
-        Self {
-            width: Length::Auto,
-            height: Length::Auto,
-        }
+impl From<u32> for Pixels {
+    fn from(pixels: u32) -> Self {
+        Pixels(pixels as f32)
     }
+}
 
-    pub fn to_taffy<T: From<taffy::prelude::LengthPercentageAuto>>(
-        &self,
-        rem_size: f32,
-    ) -> taffy::geometry::Size<T> {
-        taffy::geometry::Size {
-            width: self.width.to_taffy(rem_size).into(),
-            height: self.height.to_taffy(rem_size).into(),
-        }
+impl From<Pixels> for usize {
+    fn from(pixels: Pixels) -> Self {
+        pixels.0 as usize
     }
 }
 
-#[derive(Clone, Default, Refineable, Debug)]
-#[refineable(Debug)]
-pub struct Edges<T: Clone + Default + Debug> {
-    pub top: T,
-    pub right: T,
-    pub bottom: T,
-    pub left: T,
+impl From<usize> for Pixels {
+    fn from(pixels: usize) -> Self {
+        Pixels(pixels as f32)
+    }
 }
 
-impl<T: Clone + Default + Debug> Edges<T> {
-    pub fn uniform(value: T) -> Self {
-        Self {
-            top: value.clone(),
-            right: value.clone(),
-            bottom: value.clone(),
-            left: value.clone(),
-        }
+/// Represents physical pixels on the display.
+///
+/// `DevicePixels` is a unit of measurement that refers to the actual pixels on a device's screen.
+/// This type is used when precise pixel manipulation is required, such as rendering graphics or
+/// interfacing with hardware that operates on the pixel level. Unlike logical pixels that may be
+/// affected by the device's scale factor, `DevicePixels` always correspond to real pixels on the
+/// display.
+#[derive(
+    Add, AddAssign, Clone, Copy, Default, Div, Eq, Hash, Ord, PartialEq, PartialOrd, Sub, SubAssign,
+)]
+#[repr(transparent)]
+pub struct DevicePixels(pub(crate) i32);
+
+impl DevicePixels {
+    /// Converts the `DevicePixels` value to the number of bytes needed to represent it in memory.
+    ///
+    /// This function is useful when working with graphical data that needs to be stored in a buffer,
+    /// such as images or framebuffers, where each pixel may be represented by a specific number of bytes.
+    ///
+    /// # Arguments
+    ///
+    /// * `bytes_per_pixel` - The number of bytes used to represent a single pixel.
+    ///
+    /// # Returns
+    ///
+    /// The number of bytes required to represent the `DevicePixels` value in memory.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::DevicePixels;
+    /// let pixels = DevicePixels(10); // 10 device pixels
+    /// let bytes_per_pixel = 4; // Assume each pixel is represented by 4 bytes (e.g., RGBA)
+    /// let total_bytes = pixels.to_bytes(bytes_per_pixel);
+    /// assert_eq!(total_bytes, 40); // 10 pixels * 4 bytes/pixel = 40 bytes
+    /// ```
+    pub fn to_bytes(&self, bytes_per_pixel: u8) -> u32 {
+        self.0 as u32 * bytes_per_pixel as u32
     }
 }
 
-impl Edges<Length> {
-    pub fn auto() -> Self {
-        Self {
-            top: Length::Auto,
-            right: Length::Auto,
-            bottom: Length::Auto,
-            left: Length::Auto,
-        }
+impl fmt::Debug for DevicePixels {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{} px (device)", self.0)
     }
+}
 
-    pub fn zero() -> Self {
-        Self {
-            top: pixels(0.).into(),
-            right: pixels(0.).into(),
-            bottom: pixels(0.).into(),
-            left: pixels(0.).into(),
-        }
+impl From<DevicePixels> for i32 {
+    fn from(device_pixels: DevicePixels) -> Self {
+        device_pixels.0
     }
+}
 
-    pub fn to_taffy(
-        &self,
-        rem_size: f32,
-    ) -> taffy::geometry::Rect<taffy::style::LengthPercentageAuto> {
-        taffy::geometry::Rect {
-            top: self.top.to_taffy(rem_size),
-            right: self.right.to_taffy(rem_size),
-            bottom: self.bottom.to_taffy(rem_size),
-            left: self.left.to_taffy(rem_size),
-        }
+impl From<i32> for DevicePixels {
+    fn from(device_pixels: i32) -> Self {
+        DevicePixels(device_pixels)
     }
 }
 
-impl Edges<DefiniteLength> {
-    pub fn zero() -> Self {
-        Self {
-            top: pixels(0.).into(),
-            right: pixels(0.).into(),
-            bottom: pixels(0.).into(),
-            left: pixels(0.).into(),
-        }
+impl From<u32> for DevicePixels {
+    fn from(device_pixels: u32) -> Self {
+        DevicePixels(device_pixels as i32)
     }
+}
 
-    pub fn to_taffy(&self, rem_size: f32) -> taffy::geometry::Rect<taffy::style::LengthPercentage> {
-        taffy::geometry::Rect {
-            top: self.top.to_taffy(rem_size),
-            right: self.right.to_taffy(rem_size),
-            bottom: self.bottom.to_taffy(rem_size),
-            left: self.left.to_taffy(rem_size),
-        }
+impl From<DevicePixels> for u32 {
+    fn from(device_pixels: DevicePixels) -> Self {
+        device_pixels.0 as u32
     }
 }
 
-impl Edges<AbsoluteLength> {
-    pub fn zero() -> Self {
-        Self {
-            top: pixels(0.),
-            right: pixels(0.),
-            bottom: pixels(0.),
-            left: pixels(0.),
-        }
+impl From<DevicePixels> for u64 {
+    fn from(device_pixels: DevicePixels) -> Self {
+        device_pixels.0 as u64
     }
+}
 
-    pub fn to_taffy(&self, rem_size: f32) -> taffy::geometry::Rect<taffy::style::LengthPercentage> {
-        taffy::geometry::Rect {
-            top: self.top.to_taffy(rem_size),
-            right: self.right.to_taffy(rem_size),
-            bottom: self.bottom.to_taffy(rem_size),
-            left: self.left.to_taffy(rem_size),
-        }
+impl From<u64> for DevicePixels {
+    fn from(device_pixels: u64) -> Self {
+        DevicePixels(device_pixels as i32)
     }
+}
 
-    pub fn to_pixels(&self, rem_size: f32) -> Edges<f32> {
-        Edges {
-            top: self.top.to_pixels(rem_size),
-            right: self.right.to_pixels(rem_size),
-            bottom: self.bottom.to_pixels(rem_size),
-            left: self.left.to_pixels(rem_size),
-        }
+impl From<DevicePixels> for usize {
+    fn from(device_pixels: DevicePixels) -> Self {
+        device_pixels.0 as usize
     }
 }
 
-impl Edges<f32> {
-    pub fn is_empty(&self) -> bool {
-        self.top == 0.0 && self.right == 0.0 && self.bottom == 0.0 && self.left == 0.0
+impl From<usize> for DevicePixels {
+    fn from(device_pixels: usize) -> Self {
+        DevicePixels(device_pixels as i32)
     }
 }
 
-#[derive(Clone, Copy, Neg)]
-pub enum AbsoluteLength {
-    Pixels(f32),
-    Rems(f32),
+/// Represents scaled pixels that take into account the device's scale factor.
+///
+/// `ScaledPixels` are used to ensure that UI elements appear at the correct size on devices
+/// with different pixel densities. When a device has a higher scale factor (such as Retina displays),
+/// a single logical pixel may correspond to multiple physical pixels. By using `ScaledPixels`,
+/// dimensions and positions can be specified in a way that scales appropriately across different
+/// display resolutions.
+#[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd)]
+#[repr(transparent)]
+pub struct ScaledPixels(pub(crate) f32);
+
+impl ScaledPixels {
+    /// Floors the `ScaledPixels` value to the nearest whole number.
+    ///
+    /// # Returns
+    ///
+    /// Returns a new `ScaledPixels` instance with the floored value.
+    pub fn floor(&self) -> Self {
+        Self(self.0.floor())
+    }
+
+    /// Rounds the `ScaledPixels` value to the nearest whole number.
+    ///
+    /// # Returns
+    ///
+    /// Returns a new `ScaledPixels` instance with the rounded value.
+    pub fn ceil(&self) -> Self {
+        Self(self.0.ceil())
+    }
 }
 
-impl std::fmt::Debug for AbsoluteLength {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            AbsoluteLength::Pixels(pixels) => write!(f, "{}px", pixels),
-            AbsoluteLength::Rems(rems) => write!(f, "{}rems", rems),
-        }
+impl Eq for ScaledPixels {}
+
+impl Debug for ScaledPixels {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{} px (scaled)", self.0)
+    }
+}
+
+impl From<ScaledPixels> for DevicePixels {
+    fn from(scaled: ScaledPixels) -> Self {
+        DevicePixels(scaled.0.ceil() as i32)
+    }
+}
+
+impl From<DevicePixels> for ScaledPixels {
+    fn from(device: DevicePixels) -> Self {
+        ScaledPixels(device.0 as f32)
     }
 }
 
+impl From<ScaledPixels> for f64 {
+    fn from(scaled_pixels: ScaledPixels) -> Self {
+        scaled_pixels.0 as f64
+    }
+}
+
+/// Represents pixels in a global coordinate space, which can span across multiple displays.
+///
+/// `GlobalPixels` is used when dealing with a coordinate system that is not limited to a single
+/// display's boundaries. This type is particularly useful in multi-monitor setups where
+/// positioning and measurements need to be consistent and relative to a "global" origin point
+/// rather than being relative to any individual display.
+#[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd)]
+#[repr(transparent)]
+pub struct GlobalPixels(pub(crate) f32);
+
+impl Debug for GlobalPixels {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{} px (global coordinate space)", self.0)
+    }
+}
+
+impl From<GlobalPixels> for f64 {
+    fn from(global_pixels: GlobalPixels) -> Self {
+        global_pixels.0 as f64
+    }
+}
+
+impl From<f64> for GlobalPixels {
+    fn from(global_pixels: f64) -> Self {
+        GlobalPixels(global_pixels as f32)
+    }
+}
+
+impl sqlez::bindable::StaticColumnCount for GlobalPixels {}
+
+impl sqlez::bindable::Bind for GlobalPixels {
+    fn bind(
+        &self,
+        statement: &sqlez::statement::Statement,
+        start_index: i32,
+    ) -> anyhow::Result<i32> {
+        self.0.bind(statement, start_index)
+    }
+}
+
+/// Represents a length in rems, a unit based on the font-size of the window, which can be assigned with [WindowContext::set_rem_size].
+///
+/// Rems are used for defining lengths that are scalable and consistent across different UI elements.
+/// The value of `1rem` is typically equal to the font-size of the root element (often the `<html>` element in browsers),
+/// making it a flexible unit that adapts to the user's text size preferences. In this framework, `rems` serve a similar
+/// purpose, allowing for scalable and accessible design that can adjust to different display settings or user preferences.
+///
+/// For example, if the root element's font-size is `16px`, then `1rem` equals `16px`. A length of `2rems` would then be `32px`.
+#[derive(Clone, Copy, Default, Add, Sub, Mul, Div, Neg)]
+pub struct Rems(pub f32);
+
+impl Mul<Pixels> for Rems {
+    type Output = Pixels;
+
+    fn mul(self, other: Pixels) -> Pixels {
+        Pixels(self.0 * other.0)
+    }
+}
+
+impl Debug for Rems {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{} rem", self.0)
+    }
+}
+
+/// Represents an absolute length in pixels or rems.
+///
+/// `AbsoluteLength` can be either a fixed number of pixels, which is an absolute measurement not
+/// affected by the current font size, or a number of rems, which is relative to the font size of
+/// the root element. It is used for specifying dimensions that are either independent of or
+/// related to the typographic scale.
+#[derive(Clone, Copy, Debug, Neg)]
+pub enum AbsoluteLength {
+    /// A length in pixels.
+    Pixels(Pixels),
+    /// A length in rems.
+    Rems(Rems),
+}
+
 impl AbsoluteLength {
-    pub fn to_pixels(&self, rem_size: f32) -> f32 {
+    /// Checks if the absolute length is zero.
+    pub fn is_zero(&self) -> bool {
         match self {
-            AbsoluteLength::Pixels(pixels) => *pixels,
-            AbsoluteLength::Rems(rems) => rems * rem_size,
+            AbsoluteLength::Pixels(px) => px.0 == 0.0,
+            AbsoluteLength::Rems(rems) => rems.0 == 0.0,
         }
     }
+}
+
+impl From<Pixels> for AbsoluteLength {
+    fn from(pixels: Pixels) -> Self {
+        AbsoluteLength::Pixels(pixels)
+    }
+}
+
+impl From<Rems> for AbsoluteLength {
+    fn from(rems: Rems) -> Self {
+        AbsoluteLength::Rems(rems)
+    }
+}
 
-    pub fn to_taffy(&self, rem_size: f32) -> taffy::style::LengthPercentage {
+impl AbsoluteLength {
+    /// Converts an `AbsoluteLength` to `Pixels` based on a given `rem_size`.
+    ///
+    /// # Arguments
+    ///
+    /// * `rem_size` - The size of one rem in pixels.
+    ///
+    /// # Returns
+    ///
+    /// Returns the `AbsoluteLength` as `Pixels`.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::{AbsoluteLength, Pixels};
+    /// let length_in_pixels = AbsoluteLength::Pixels(Pixels(42.0));
+    /// let length_in_rems = AbsoluteLength::Rems(Rems(2.0));
+    /// let rem_size = Pixels(16.0);
+    ///
+    /// assert_eq!(length_in_pixels.to_pixels(rem_size), Pixels(42.0));
+    /// assert_eq!(length_in_rems.to_pixels(rem_size), Pixels(32.0));
+    /// ```
+    pub fn to_pixels(&self, rem_size: Pixels) -> Pixels {
         match self {
-            AbsoluteLength::Pixels(pixels) => taffy::style::LengthPercentage::Length(*pixels),
-            AbsoluteLength::Rems(rems) => taffy::style::LengthPercentage::Length(rems * rem_size),
+            AbsoluteLength::Pixels(pixels) => *pixels,
+            AbsoluteLength::Rems(rems) => *rems * rem_size,
         }
     }
 }
 
 impl Default for AbsoluteLength {
     fn default() -> Self {
-        Self::Pixels(0.0)
+        px(0.).into()
     }
 }
 
 /// A non-auto length that can be defined in pixels, rems, or percent of parent.
+///
+/// This enum represents lengths that have a specific value, as opposed to lengths that are automatically
+/// determined by the context. It includes absolute lengths in pixels or rems, and relative lengths as a
+/// fraction of the parent's size.
 #[derive(Clone, Copy, Neg)]
 pub enum DefiniteLength {
+    /// An absolute length specified in pixels or rems.
     Absolute(AbsoluteLength),
-    Relative(f32), // 0. to 1.
+    /// A relative length specified as a fraction of the parent's size, between 0 and 1.
+    Fraction(f32),
 }
 
 impl DefiniteLength {
-    fn to_taffy(&self, rem_size: f32) -> taffy::style::LengthPercentage {
+    /// Converts the `DefiniteLength` to `Pixels` based on a given `base_size` and `rem_size`.
+    ///
+    /// If the `DefiniteLength` is an absolute length, it will be directly converted to `Pixels`.
+    /// If it is a fraction, the fraction will be multiplied by the `base_size` to get the length in pixels.
+    ///
+    /// # Arguments
+    ///
+    /// * `base_size` - The base size in `AbsoluteLength` to which the fraction will be applied.
+    /// * `rem_size` - The size of one rem in pixels, used to convert rems to pixels.
+    ///
+    /// # Returns
+    ///
+    /// Returns the `DefiniteLength` as `Pixels`.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::{DefiniteLength, AbsoluteLength, Pixels, px, rems};
+    /// let length_in_pixels = DefiniteLength::Absolute(AbsoluteLength::Pixels(px(42.0)));
+    /// let length_in_rems = DefiniteLength::Absolute(AbsoluteLength::Rems(rems(2.0)));
+    /// let length_as_fraction = DefiniteLength::Fraction(0.5);
+    /// let base_size = AbsoluteLength::Pixels(px(100.0));
+    /// let rem_size = px(16.0);
+    ///
+    /// assert_eq!(length_in_pixels.to_pixels(base_size, rem_size), Pixels(42.0));
+    /// assert_eq!(length_in_rems.to_pixels(base_size, rem_size), Pixels(32.0));
+    /// assert_eq!(length_as_fraction.to_pixels(base_size, rem_size), Pixels(50.0));
+    /// ```
+    pub fn to_pixels(&self, base_size: AbsoluteLength, rem_size: Pixels) -> Pixels {
         match self {
-            DefiniteLength::Absolute(length) => match length {
-                AbsoluteLength::Pixels(pixels) => taffy::style::LengthPercentage::Length(*pixels),
-                AbsoluteLength::Rems(rems) => {
-                    taffy::style::LengthPercentage::Length(rems * rem_size)
-                }
+            DefiniteLength::Absolute(size) => size.to_pixels(rem_size),
+            DefiniteLength::Fraction(fraction) => match base_size {
+                AbsoluteLength::Pixels(px) => px * *fraction,
+                AbsoluteLength::Rems(rems) => rems * rem_size * *fraction,
             },
-            DefiniteLength::Relative(fraction) => {
-                taffy::style::LengthPercentage::Percent(*fraction)
-            }
         }
     }
 }
 
-impl std::fmt::Debug for DefiniteLength {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+impl Debug for DefiniteLength {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
-            DefiniteLength::Absolute(length) => std::fmt::Debug::fmt(length, f),
-            DefiniteLength::Relative(fract) => write!(f, "{}%", (fract * 100.0) as i32),
+            DefiniteLength::Absolute(length) => Debug::fmt(length, f),
+            DefiniteLength::Fraction(fract) => write!(f, "{}%", (fract * 100.0) as i32),
         }
     }
 }
 
+impl From<Pixels> for DefiniteLength {
+    fn from(pixels: Pixels) -> Self {
+        Self::Absolute(pixels.into())
+    }
+}
+
+impl From<Rems> for DefiniteLength {
+    fn from(rems: Rems) -> Self {
+        Self::Absolute(rems.into())
+    }
+}
+
 impl From<AbsoluteLength> for DefiniteLength {
     fn from(length: AbsoluteLength) -> Self {
         Self::Absolute(length)

crates/gpui/src/gpui.rs 🔗

@@ -1,40 +1,215 @@
+#[macro_use]
+mod action;
 mod app;
-mod image_cache;
-pub use app::*;
+
+mod arena;
 mod assets;
+mod color;
+mod element;
+mod elements;
+mod executor;
+mod geometry;
+mod image_cache;
+mod input;
+mod interactive;
+mod key_dispatch;
+mod keymap;
+mod platform;
+pub mod prelude;
+mod scene;
+mod shared_string;
+mod style;
+mod styled;
+mod subscription;
+mod svg_renderer;
+mod taffy;
 #[cfg(any(test, feature = "test-support"))]
 pub mod test;
-pub use assets::*;
-pub mod elements;
-pub mod font_cache;
-mod image_data;
-pub use crate::image_data::ImageData;
-pub use taffy;
-pub mod views;
-pub use font_cache::FontCache;
-mod clipboard;
-pub use clipboard::ClipboardItem;
-pub mod fonts;
-pub mod geometry;
-pub mod scene;
-pub use scene::{Border, CursorRegion, MouseRegion, MouseRegionId, Quad, Scene, SceneBuilder};
-pub mod text_layout;
-pub use text_layout::TextLayoutCache;
+mod text_system;
 mod util;
-pub use elements::{AnyElement, Element};
-pub mod executor;
-pub use executor::Task;
-pub mod color;
-pub mod json;
-pub mod keymap_matcher;
-pub mod platform;
-pub use gpui_macros::{test, Element};
-pub use usvg;
-pub use window::{
-    Axis, Layout, LayoutEngine, LayoutId, RectFExt, SizeConstraint, Vector2FExt, WindowContext,
-};
+mod view;
+mod window;
+
+mod private {
+    /// A mechanism for restricting implementations of a trait to only those in GPUI.
+    /// See: https://predr.ag/blog/definitive-guide-to-sealed-traits-in-rust/
+    pub trait Sealed {}
+}
 
-pub use anyhow;
+pub use action::*;
+pub use anyhow::Result;
+pub use app::*;
+pub(crate) use arena::*;
+pub use assets::*;
+pub use color::*;
+pub use ctor::ctor;
+pub use element::*;
+pub use elements::*;
+pub use executor::*;
+pub use geometry::*;
+pub use gpui_macros::*;
+pub use image_cache::*;
+pub use input::*;
+pub use interactive::*;
+pub use key_dispatch::*;
+pub use keymap::*;
+pub use linkme;
+pub use platform::*;
+use private::Sealed;
+pub use refineable::*;
+pub use scene::*;
+pub use serde;
+pub use serde_derive;
 pub use serde_json;
+pub use shared_string::*;
+pub use smallvec;
+pub use smol::Timer;
+pub use style::*;
+pub use styled::*;
+pub use subscription::*;
+pub use svg_renderer::*;
+pub use taffy::{AvailableSpace, LayoutId};
+#[cfg(any(test, feature = "test-support"))]
+pub use test::*;
+pub use text_system::*;
+pub use util::arc_cow::ArcCow;
+pub use view::*;
+pub use window::*;
+
+use std::{
+    any::{Any, TypeId},
+    borrow::BorrowMut,
+};
+use taffy::TaffyLayoutEngine;
+
+pub trait Context {
+    type Result<T>;
+
+    fn new_model<T: 'static>(
+        &mut self,
+        build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
+    ) -> Self::Result<Model<T>>;
+
+    fn update_model<T, R>(
+        &mut self,
+        handle: &Model<T>,
+        update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
+    ) -> Self::Result<R>
+    where
+        T: 'static;
+
+    fn read_model<T, R>(
+        &self,
+        handle: &Model<T>,
+        read: impl FnOnce(&T, &AppContext) -> R,
+    ) -> Self::Result<R>
+    where
+        T: 'static;
+
+    fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
+    where
+        F: FnOnce(AnyView, &mut WindowContext<'_>) -> T;
+
+    fn read_window<T, R>(
+        &self,
+        window: &WindowHandle<T>,
+        read: impl FnOnce(View<T>, &AppContext) -> R,
+    ) -> Result<R>
+    where
+        T: 'static;
+}
+
+pub trait VisualContext: Context {
+    fn new_view<V>(
+        &mut self,
+        build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
+    ) -> Self::Result<View<V>>
+    where
+        V: 'static + Render;
+
+    fn update_view<V: 'static, R>(
+        &mut self,
+        view: &View<V>,
+        update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
+    ) -> Self::Result<R>;
+
+    fn replace_root_view<V>(
+        &mut self,
+        build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
+    ) -> Self::Result<View<V>>
+    where
+        V: 'static + Render;
+
+    fn focus_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
+    where
+        V: FocusableView;
+
+    fn dismiss_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
+    where
+        V: ManagedView;
+}
+
+pub trait Entity<T>: Sealed {
+    type Weak: 'static;
+
+    fn entity_id(&self) -> EntityId;
+    fn downgrade(&self) -> Self::Weak;
+    fn upgrade_from(weak: &Self::Weak) -> Option<Self>
+    where
+        Self: Sized;
+}
+
+pub trait EventEmitter<E: Any>: 'static {}
+
+pub enum GlobalKey {
+    Numeric(usize),
+    View(EntityId),
+    Type(TypeId),
+}
+
+pub trait BorrowAppContext {
+    fn with_text_style<F, R>(&mut self, style: Option<TextStyleRefinement>, f: F) -> R
+    where
+        F: FnOnce(&mut Self) -> R;
+
+    fn set_global<T: 'static>(&mut self, global: T);
+}
+
+impl<C> BorrowAppContext for C
+where
+    C: BorrowMut<AppContext>,
+{
+    fn with_text_style<F, R>(&mut self, style: Option<TextStyleRefinement>, f: F) -> R
+    where
+        F: FnOnce(&mut Self) -> R,
+    {
+        if let Some(style) = style {
+            self.borrow_mut().push_text_style(style);
+            let result = f(self);
+            self.borrow_mut().pop_text_style();
+            result
+        } else {
+            f(self)
+        }
+    }
+
+    fn set_global<G: 'static>(&mut self, global: G) {
+        self.borrow_mut().set_global(global)
+    }
+}
+
+pub trait Flatten<T> {
+    fn flatten(self) -> Result<T>;
+}
+
+impl<T> Flatten<T> for Result<Result<T>> {
+    fn flatten(self) -> Result<T> {
+        self?
+    }
+}
 
-actions!(zed, [NoAction]);
+impl<T> Flatten<T> for Result<T> {
+    fn flatten(self) -> Result<T> {
+        self
+    }
+}

crates/gpui/src/image_cache.rs 🔗

@@ -1,18 +1,19 @@
-use std::sync::Arc;
-
-use crate::ImageData;
+use crate::{ImageData, ImageId, SharedString};
 use collections::HashMap;
 use futures::{
     future::{BoxFuture, Shared},
-    AsyncReadExt, FutureExt,
+    AsyncReadExt, FutureExt, TryFutureExt,
 };
 use image::ImageError;
 use parking_lot::Mutex;
+use std::sync::Arc;
 use thiserror::Error;
-use util::{
-    arc_cow::ArcCow,
-    http::{self, HttpClient},
-};
+use util::http::{self, HttpClient};
+
+#[derive(PartialEq, Eq, Hash, Clone)]
+pub struct RenderImageParams {
+    pub(crate) image_id: ImageId,
+}
 
 #[derive(Debug, Error, Clone)]
 pub enum Error {
@@ -43,7 +44,7 @@ impl From<ImageError> for Error {
 
 pub struct ImageCache {
     client: Arc<dyn HttpClient>,
-    images: Arc<Mutex<HashMap<ArcCow<'static, str>, FetchImageFuture>>>,
+    images: Arc<Mutex<HashMap<SharedString, FetchImageFuture>>>,
 }
 
 type FetchImageFuture = Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>>;
@@ -58,12 +59,12 @@ impl ImageCache {
 
     pub fn get(
         &self,
-        uri: impl Into<ArcCow<'static, str>>,
+        uri: impl Into<SharedString>,
     ) -> Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>> {
         let uri = uri.into();
         let mut images = self.images.lock();
 
-        match images.get(uri.as_ref()) {
+        match images.get(&uri) {
             Some(future) => future.clone(),
             None => {
                 let client = self.client.clone();
@@ -84,9 +85,17 @@ impl ImageCache {
                         let format = image::guess_format(&body)?;
                         let image =
                             image::load_from_memory_with_format(&body, format)?.into_bgra8();
-                        Ok(ImageData::new(image))
+                        Ok(Arc::new(ImageData::new(image)))
                     }
                 }
+                .map_err({
+                    let uri = uri.clone();
+
+                    move |error| {
+                        log::log!(log::Level::Error, "{:?} {:?}", &uri, &error);
+                        error
+                    }
+                })
                 .boxed()
                 .shared();
 

crates/gpui/src/image_data.rs 🔗

@@ -1,43 +0,0 @@
-use crate::geometry::vector::{vec2i, Vector2I};
-use image::{Bgra, ImageBuffer};
-use std::{
-    fmt,
-    sync::{
-        atomic::{AtomicUsize, Ordering::SeqCst},
-        Arc,
-    },
-};
-
-pub struct ImageData {
-    pub id: usize,
-    data: ImageBuffer<Bgra<u8>, Vec<u8>>,
-}
-
-impl ImageData {
-    pub fn new(data: ImageBuffer<Bgra<u8>, Vec<u8>>) -> Arc<Self> {
-        static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
-
-        Arc::new(Self {
-            id: NEXT_ID.fetch_add(1, SeqCst),
-            data,
-        })
-    }
-
-    pub fn as_bytes(&self) -> &[u8] {
-        &self.data
-    }
-
-    pub fn size(&self) -> Vector2I {
-        let (width, height) = self.data.dimensions();
-        vec2i(width as i32, height as i32)
-    }
-}
-
-impl fmt::Debug for ImageData {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_struct("ImageData")
-            .field("id", &self.id)
-            .field("size", &self.data.dimensions())
-            .finish()
-    }
-}

crates/gpui/src/json.rs 🔗

@@ -1,15 +0,0 @@
-pub use serde_json::*;
-
-pub trait ToJson {
-    fn to_json(&self) -> Value;
-}
-
-impl<T: ToJson> ToJson for Option<T> {
-    fn to_json(&self) -> Value {
-        if let Some(value) = self.as_ref() {
-            value.to_json()
-        } else {
-            json!(null)
-        }
-    }
-}

crates/gpui/src/keymap_matcher.rs 🔗

@@ -1,587 +0,0 @@
-mod binding;
-mod keymap;
-mod keymap_context;
-mod keystroke;
-
-use std::{any::TypeId, fmt::Debug};
-
-use collections::HashMap;
-use smallvec::SmallVec;
-
-use crate::{Action, NoAction};
-
-pub use binding::{Binding, BindingMatchResult};
-pub use keymap::Keymap;
-pub use keymap_context::{KeymapContext, KeymapContextPredicate};
-pub use keystroke::Keystroke;
-
-pub struct KeymapMatcher {
-    pub contexts: Vec<KeymapContext>,
-    pending_views: HashMap<usize, KeymapContext>,
-    pending_keystrokes: Vec<Keystroke>,
-    keymap: Keymap,
-}
-
-impl KeymapMatcher {
-    pub fn new(keymap: Keymap) -> Self {
-        Self {
-            contexts: Vec::new(),
-            pending_views: Default::default(),
-            pending_keystrokes: Vec::new(),
-            keymap,
-        }
-    }
-
-    pub fn set_keymap(&mut self, keymap: Keymap) {
-        self.clear_pending();
-        self.keymap = keymap;
-    }
-
-    pub fn add_bindings<T: IntoIterator<Item = Binding>>(&mut self, bindings: T) {
-        self.clear_pending();
-        self.keymap.add_bindings(bindings);
-    }
-
-    pub fn clear_bindings(&mut self) {
-        self.clear_pending();
-        self.keymap.clear();
-    }
-
-    pub fn bindings_for_action(&self, action_id: TypeId) -> impl Iterator<Item = &Binding> {
-        self.keymap.bindings_for_action(action_id)
-    }
-
-    pub fn clear_pending(&mut self) {
-        self.pending_keystrokes.clear();
-        self.pending_views.clear();
-    }
-
-    pub fn has_pending_keystrokes(&self) -> bool {
-        !self.pending_keystrokes.is_empty()
-    }
-
-    /// Pushes a keystroke onto the matcher.
-    /// The result of the new keystroke is returned:
-    ///     MatchResult::None =>
-    ///         No match is valid for this key given any pending keystrokes.
-    ///     MatchResult::Pending =>
-    ///         There exist bindings which are still waiting for more keys.
-    ///     MatchResult::Complete(matches) =>
-    ///         1 or more bindings have received the necessary key presses.
-    ///         The order of the matched actions is by position of the matching first,
-    //          and order in the keymap second.
-    pub fn push_keystroke(
-        &mut self,
-        keystroke: Keystroke,
-        mut dispatch_path: Vec<(usize, KeymapContext)>,
-    ) -> MatchResult {
-        // Collect matched bindings into an ordered list using the position in the matching binding first,
-        // and then the order the binding matched in the view tree second.
-        // The key is the reverse position of the binding in the bindings list so that later bindings
-        // match before earlier ones in the user's config
-        let mut matched_bindings: Vec<(usize, Box<dyn Action>)> = Default::default();
-        let no_action_id = (NoAction {}).id();
-
-        let first_keystroke = self.pending_keystrokes.is_empty();
-        let mut pending_key = None;
-        let mut previous_keystrokes = self.pending_keystrokes.clone();
-
-        self.contexts.clear();
-        self.contexts
-            .extend(dispatch_path.iter_mut().map(|e| std::mem::take(&mut e.1)));
-
-        // Find the bindings which map the pending keystrokes and current context
-        for (i, (view_id, _)) in dispatch_path.iter().enumerate() {
-            // Don't require pending view entry if there are no pending keystrokes
-            if !first_keystroke && !self.pending_views.contains_key(view_id) {
-                continue;
-            }
-
-            // If there is a previous view context, invalidate that view if it
-            // has changed
-            if let Some(previous_view_context) = self.pending_views.remove(view_id) {
-                if previous_view_context != self.contexts[i] {
-                    continue;
-                }
-            }
-
-            for binding in self.keymap.bindings().iter().rev() {
-                for possibility in keystroke.match_possibilities() {
-                    previous_keystrokes.push(possibility.clone());
-                    match binding.match_keys_and_context(&previous_keystrokes, &self.contexts[i..])
-                    {
-                        BindingMatchResult::Complete(action) => {
-                            if action.id() != no_action_id {
-                                matched_bindings.push((*view_id, action));
-                            }
-                        }
-                        BindingMatchResult::Partial => {
-                            if pending_key == None || pending_key == Some(possibility.clone()) {
-                                self.pending_views
-                                    .insert(*view_id, self.contexts[i].clone());
-                                pending_key = Some(possibility)
-                            }
-                        }
-                        _ => {}
-                    }
-                    previous_keystrokes.pop();
-                }
-            }
-        }
-
-        if pending_key.is_some() {
-            self.pending_keystrokes.push(pending_key.unwrap());
-        } else {
-            self.clear_pending();
-        }
-
-        if !matched_bindings.is_empty() {
-            // Collect the sorted matched bindings into the final vec for ease of use
-            // Matched bindings are in order by precedence
-            MatchResult::Matches(matched_bindings)
-        } else if !self.pending_keystrokes.is_empty() {
-            MatchResult::Pending
-        } else {
-            MatchResult::None
-        }
-    }
-
-    pub fn keystrokes_for_action(
-        &self,
-        action: &dyn Action,
-        contexts: &[KeymapContext],
-    ) -> Option<SmallVec<[Keystroke; 2]>> {
-        self.keymap
-            .bindings()
-            .iter()
-            .rev()
-            .find_map(|binding| binding.keystrokes_for_action(action, contexts))
-    }
-}
-
-impl Default for KeymapMatcher {
-    fn default() -> Self {
-        Self::new(Keymap::default())
-    }
-}
-
-pub enum MatchResult {
-    None,
-    Pending,
-    Matches(Vec<(usize, Box<dyn Action>)>),
-}
-
-impl Debug for MatchResult {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            MatchResult::None => f.debug_struct("MatchResult::None").finish(),
-            MatchResult::Pending => f.debug_struct("MatchResult::Pending").finish(),
-            MatchResult::Matches(matches) => f
-                .debug_list()
-                .entries(
-                    matches
-                        .iter()
-                        .map(|(view_id, action)| format!("{view_id}, {}", action.name())),
-                )
-                .finish(),
-        }
-    }
-}
-
-impl PartialEq for MatchResult {
-    fn eq(&self, other: &Self) -> bool {
-        match (self, other) {
-            (MatchResult::None, MatchResult::None) => true,
-            (MatchResult::Pending, MatchResult::Pending) => true,
-            (MatchResult::Matches(matches), MatchResult::Matches(other_matches)) => {
-                matches.len() == other_matches.len()
-                    && matches.iter().zip(other_matches.iter()).all(
-                        |((view_id, action), (other_view_id, other_action))| {
-                            view_id == other_view_id && action.eq(other_action.as_ref())
-                        },
-                    )
-            }
-            _ => false,
-        }
-    }
-}
-
-impl Eq for MatchResult {}
-
-impl Clone for MatchResult {
-    fn clone(&self) -> Self {
-        match self {
-            MatchResult::None => MatchResult::None,
-            MatchResult::Pending => MatchResult::Pending,
-            MatchResult::Matches(matches) => MatchResult::Matches(
-                matches
-                    .iter()
-                    .map(|(view_id, action)| (*view_id, Action::boxed_clone(action.as_ref())))
-                    .collect(),
-            ),
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use anyhow::Result;
-    use serde::Deserialize;
-
-    use crate::{actions, impl_actions, keymap_matcher::KeymapContext};
-
-    use super::*;
-
-    #[test]
-    fn test_keymap_and_view_ordering() -> Result<()> {
-        actions!(test, [EditorAction, ProjectPanelAction]);
-
-        let mut editor = KeymapContext::default();
-        editor.add_identifier("Editor");
-
-        let mut project_panel = KeymapContext::default();
-        project_panel.add_identifier("ProjectPanel");
-
-        // Editor 'deeper' in than project panel
-        let dispatch_path = vec![(2, editor), (1, project_panel)];
-
-        // But editor actions 'higher' up in keymap
-        let keymap = Keymap::new(vec![
-            Binding::new("left", EditorAction, Some("Editor")),
-            Binding::new("left", ProjectPanelAction, Some("ProjectPanel")),
-        ]);
-
-        let mut matcher = KeymapMatcher::new(keymap);
-
-        assert_eq!(
-            matcher.push_keystroke(Keystroke::parse("left")?, dispatch_path.clone()),
-            MatchResult::Matches(vec![
-                (2, Box::new(EditorAction)),
-                (1, Box::new(ProjectPanelAction)),
-            ]),
-        );
-
-        Ok(())
-    }
-
-    #[test]
-    fn test_push_keystroke() -> Result<()> {
-        actions!(test, [B, AB, C, D, DA, E, EF]);
-
-        let mut context1 = KeymapContext::default();
-        context1.add_identifier("1");
-
-        let mut context2 = KeymapContext::default();
-        context2.add_identifier("2");
-
-        let dispatch_path = vec![(2, context2), (1, context1)];
-
-        let keymap = Keymap::new(vec![
-            Binding::new("a b", AB, Some("1")),
-            Binding::new("b", B, Some("2")),
-            Binding::new("c", C, Some("2")),
-            Binding::new("d", D, Some("1")),
-            Binding::new("d", D, Some("2")),
-            Binding::new("d a", DA, Some("2")),
-        ]);
-
-        let mut matcher = KeymapMatcher::new(keymap);
-
-        // Binding with pending prefix always takes precedence
-        assert_eq!(
-            matcher.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
-            MatchResult::Pending,
-        );
-        // B alone doesn't match because a was pending, so AB is returned instead
-        assert_eq!(
-            matcher.push_keystroke(Keystroke::parse("b")?, dispatch_path.clone()),
-            MatchResult::Matches(vec![(1, Box::new(AB))]),
-        );
-        assert!(!matcher.has_pending_keystrokes());
-
-        // Without an a prefix, B is dispatched like expected
-        assert_eq!(
-            matcher.push_keystroke(Keystroke::parse("b")?, dispatch_path.clone()),
-            MatchResult::Matches(vec![(2, Box::new(B))]),
-        );
-        assert!(!matcher.has_pending_keystrokes());
-
-        // If a is prefixed, C will not be dispatched because there
-        // was a pending binding for it
-        assert_eq!(
-            matcher.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
-            MatchResult::Pending,
-        );
-        assert_eq!(
-            matcher.push_keystroke(Keystroke::parse("c")?, dispatch_path.clone()),
-            MatchResult::None,
-        );
-        assert!(!matcher.has_pending_keystrokes());
-
-        // If a single keystroke matches multiple bindings in the tree
-        // all of them are returned so that we can fallback if the action
-        // handler decides to propagate the action
-        assert_eq!(
-            matcher.push_keystroke(Keystroke::parse("d")?, dispatch_path.clone()),
-            MatchResult::Matches(vec![(2, Box::new(D)), (1, Box::new(D))]),
-        );
-
-        // If none of the d action handlers consume the binding, a pending
-        // binding may then be used
-        assert_eq!(
-            matcher.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
-            MatchResult::Matches(vec![(2, Box::new(DA))]),
-        );
-        assert!(!matcher.has_pending_keystrokes());
-
-        Ok(())
-    }
-
-    #[test]
-    fn test_keystroke_parsing() -> Result<()> {
-        assert_eq!(
-            Keystroke::parse("ctrl-p")?,
-            Keystroke {
-                key: "p".into(),
-                ctrl: true,
-                alt: false,
-                shift: false,
-                cmd: false,
-                function: false,
-                ime_key: None,
-            }
-        );
-
-        assert_eq!(
-            Keystroke::parse("alt-shift-down")?,
-            Keystroke {
-                key: "down".into(),
-                ctrl: false,
-                alt: true,
-                shift: true,
-                cmd: false,
-                function: false,
-                ime_key: None,
-            }
-        );
-
-        assert_eq!(
-            Keystroke::parse("shift-cmd--")?,
-            Keystroke {
-                key: "-".into(),
-                ctrl: false,
-                alt: false,
-                shift: true,
-                cmd: true,
-                function: false,
-                ime_key: None,
-            }
-        );
-
-        Ok(())
-    }
-
-    #[test]
-    fn test_context_predicate_parsing() -> Result<()> {
-        use KeymapContextPredicate::*;
-
-        assert_eq!(
-            KeymapContextPredicate::parse("a && (b == c || d != e)")?,
-            And(
-                Box::new(Identifier("a".into())),
-                Box::new(Or(
-                    Box::new(Equal("b".into(), "c".into())),
-                    Box::new(NotEqual("d".into(), "e".into())),
-                ))
-            )
-        );
-
-        assert_eq!(
-            KeymapContextPredicate::parse("!a")?,
-            Not(Box::new(Identifier("a".into())),)
-        );
-
-        Ok(())
-    }
-
-    #[test]
-    fn test_context_predicate_eval() {
-        let predicate = KeymapContextPredicate::parse("a && b || c == d").unwrap();
-
-        let mut context = KeymapContext::default();
-        context.add_identifier("a");
-        assert!(!predicate.eval(&[context]));
-
-        let mut context = KeymapContext::default();
-        context.add_identifier("a");
-        context.add_identifier("b");
-        assert!(predicate.eval(&[context]));
-
-        let mut context = KeymapContext::default();
-        context.add_identifier("a");
-        context.add_key("c", "x");
-        assert!(!predicate.eval(&[context]));
-
-        let mut context = KeymapContext::default();
-        context.add_identifier("a");
-        context.add_key("c", "d");
-        assert!(predicate.eval(&[context]));
-
-        let predicate = KeymapContextPredicate::parse("!a").unwrap();
-        assert!(predicate.eval(&[KeymapContext::default()]));
-    }
-
-    #[test]
-    fn test_context_child_predicate_eval() {
-        let predicate = KeymapContextPredicate::parse("a && b > c").unwrap();
-        let contexts = [
-            context_set(&["e", "f"]),
-            context_set(&["c", "d"]), // match this context
-            context_set(&["a", "b"]),
-        ];
-
-        assert!(!predicate.eval(&contexts[0..]));
-        assert!(predicate.eval(&contexts[1..]));
-        assert!(!predicate.eval(&contexts[2..]));
-
-        let predicate = KeymapContextPredicate::parse("a && b > c && !d > e").unwrap();
-        let contexts = [
-            context_set(&["f"]),
-            context_set(&["e"]), // only match this context
-            context_set(&["c"]),
-            context_set(&["a", "b"]),
-            context_set(&["e"]),
-            context_set(&["c", "d"]),
-            context_set(&["a", "b"]),
-        ];
-
-        assert!(!predicate.eval(&contexts[0..]));
-        assert!(predicate.eval(&contexts[1..]));
-        assert!(!predicate.eval(&contexts[2..]));
-        assert!(!predicate.eval(&contexts[3..]));
-        assert!(!predicate.eval(&contexts[4..]));
-        assert!(!predicate.eval(&contexts[5..]));
-        assert!(!predicate.eval(&contexts[6..]));
-
-        fn context_set(names: &[&str]) -> KeymapContext {
-            let mut keymap = KeymapContext::new();
-            names
-                .iter()
-                .for_each(|name| keymap.add_identifier(name.to_string()));
-            keymap
-        }
-    }
-
-    #[test]
-    fn test_matcher() -> Result<()> {
-        #[derive(Clone, Deserialize, PartialEq, Eq, Debug)]
-        pub struct A(pub String);
-        impl_actions!(test, [A]);
-        actions!(test, [B, Ab, Dollar, Quote, Ess, Backtick]);
-
-        #[derive(Clone, Debug, Eq, PartialEq)]
-        struct ActionArg {
-            a: &'static str,
-        }
-
-        let keymap = Keymap::new(vec![
-            Binding::new("a", A("x".to_string()), Some("a")),
-            Binding::new("b", B, Some("a")),
-            Binding::new("a b", Ab, Some("a || b")),
-            Binding::new("$", Dollar, Some("a")),
-            Binding::new("\"", Quote, Some("a")),
-            Binding::new("alt-s", Ess, Some("a")),
-            Binding::new("ctrl-`", Backtick, Some("a")),
-        ]);
-
-        let mut context_a = KeymapContext::default();
-        context_a.add_identifier("a");
-
-        let mut context_b = KeymapContext::default();
-        context_b.add_identifier("b");
-
-        let mut matcher = KeymapMatcher::new(keymap);
-
-        // Basic match
-        assert_eq!(
-            matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]),
-            MatchResult::Matches(vec![(1, Box::new(A("x".to_string())))])
-        );
-        matcher.clear_pending();
-
-        // Multi-keystroke match
-        assert_eq!(
-            matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]),
-            MatchResult::Pending
-        );
-        assert_eq!(
-            matcher.push_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]),
-            MatchResult::Matches(vec![(1, Box::new(Ab))])
-        );
-        matcher.clear_pending();
-
-        // Failed matches don't interfere with matching subsequent keys
-        assert_eq!(
-            matcher.push_keystroke(Keystroke::parse("x")?, vec![(1, context_a.clone())]),
-            MatchResult::None
-        );
-        assert_eq!(
-            matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]),
-            MatchResult::Matches(vec![(1, Box::new(A("x".to_string())))])
-        );
-        matcher.clear_pending();
-
-        // Pending keystrokes are cleared when the context changes
-        assert_eq!(
-            matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]),
-            MatchResult::Pending
-        );
-        assert_eq!(
-            matcher.push_keystroke(Keystroke::parse("b")?, vec![(1, context_a.clone())]),
-            MatchResult::None
-        );
-        matcher.clear_pending();
-
-        let mut context_c = KeymapContext::default();
-        context_c.add_identifier("c");
-
-        // Pending keystrokes are maintained per-view
-        assert_eq!(
-            matcher.push_keystroke(
-                Keystroke::parse("a")?,
-                vec![(1, context_b.clone()), (2, context_c.clone())]
-            ),
-            MatchResult::Pending
-        );
-        assert_eq!(
-            matcher.push_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]),
-            MatchResult::Matches(vec![(1, Box::new(Ab))])
-        );
-
-        // handle Czech $ (option + 4 key)
-        assert_eq!(
-            matcher.push_keystroke(Keystroke::parse("alt-ç->$")?, vec![(1, context_a.clone())]),
-            MatchResult::Matches(vec![(1, Box::new(Dollar))])
-        );
-
-        // handle Brazillian quote (quote key then space key)
-        assert_eq!(
-            matcher.push_keystroke(Keystroke::parse("space->\"")?, vec![(1, context_a.clone())]),
-            MatchResult::Matches(vec![(1, Box::new(Quote))])
-        );
-
-        // handle ctrl+` on a brazillian keyboard
-        assert_eq!(
-            matcher.push_keystroke(Keystroke::parse("ctrl-->`")?, vec![(1, context_a.clone())]),
-            MatchResult::Matches(vec![(1, Box::new(Backtick))])
-        );
-
-        // handle alt-s on a US keyboard
-        assert_eq!(
-            matcher.push_keystroke(Keystroke::parse("alt-s->ß")?, vec![(1, context_a.clone())]),
-            MatchResult::Matches(vec![(1, Box::new(Ess))])
-        );
-
-        Ok(())
-    }
-}

crates/gpui/src/keymap_matcher/binding.rs 🔗

@@ -1,111 +0,0 @@
-use anyhow::Result;
-use smallvec::SmallVec;
-
-use crate::Action;
-
-use super::{KeymapContext, KeymapContextPredicate, Keystroke};
-
-pub struct Binding {
-    action: Box<dyn Action>,
-    pub(super) keystrokes: SmallVec<[Keystroke; 2]>,
-    pub(super) context_predicate: Option<KeymapContextPredicate>,
-}
-
-impl std::fmt::Debug for Binding {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(
-            f,
-            "Binding {{ keystrokes: {:?}, action: {}::{}, context_predicate: {:?} }}",
-            self.keystrokes,
-            self.action.namespace(),
-            self.action.name(),
-            self.context_predicate
-        )
-    }
-}
-
-impl Clone for Binding {
-    fn clone(&self) -> Self {
-        Self {
-            action: self.action.boxed_clone(),
-            keystrokes: self.keystrokes.clone(),
-            context_predicate: self.context_predicate.clone(),
-        }
-    }
-}
-
-impl Binding {
-    pub fn new<A: Action>(keystrokes: &str, action: A, context: Option<&str>) -> Self {
-        Self::load(keystrokes, Box::new(action), context).unwrap()
-    }
-
-    pub fn load(keystrokes: &str, action: Box<dyn Action>, context: Option<&str>) -> Result<Self> {
-        let context = if let Some(context) = context {
-            Some(KeymapContextPredicate::parse(context)?)
-        } else {
-            None
-        };
-
-        let keystrokes = keystrokes
-            .split_whitespace()
-            .map(Keystroke::parse)
-            .collect::<Result<_>>()?;
-
-        Ok(Self {
-            keystrokes,
-            action,
-            context_predicate: context,
-        })
-    }
-
-    pub fn match_context(&self, contexts: &[KeymapContext]) -> bool {
-        self.context_predicate
-            .as_ref()
-            .map(|predicate| predicate.eval(contexts))
-            .unwrap_or(true)
-    }
-
-    pub fn match_keys_and_context(
-        &self,
-        pending_keystrokes: &Vec<Keystroke>,
-        contexts: &[KeymapContext],
-    ) -> BindingMatchResult {
-        if self.keystrokes.as_ref().starts_with(&pending_keystrokes) && self.match_context(contexts)
-        {
-            // If the binding is completed, push it onto the matches list
-            if self.keystrokes.as_ref().len() == pending_keystrokes.len() {
-                BindingMatchResult::Complete(self.action.boxed_clone())
-            } else {
-                BindingMatchResult::Partial
-            }
-        } else {
-            BindingMatchResult::Fail
-        }
-    }
-
-    pub fn keystrokes_for_action(
-        &self,
-        action: &dyn Action,
-        contexts: &[KeymapContext],
-    ) -> Option<SmallVec<[Keystroke; 2]>> {
-        if self.action.eq(action) && self.match_context(contexts) {
-            Some(self.keystrokes.clone())
-        } else {
-            None
-        }
-    }
-
-    pub fn keystrokes(&self) -> &[Keystroke] {
-        self.keystrokes.as_slice()
-    }
-
-    pub fn action(&self) -> &dyn Action {
-        self.action.as_ref()
-    }
-}
-
-pub enum BindingMatchResult {
-    Complete(Box<dyn Action>),
-    Partial,
-    Fail,
-}

crates/gpui/src/keymap_matcher/keymap.rs 🔗

@@ -1,392 +0,0 @@
-use collections::HashSet;
-use smallvec::SmallVec;
-use std::{any::TypeId, collections::HashMap};
-
-use crate::{Action, NoAction};
-
-use super::{Binding, KeymapContextPredicate, Keystroke};
-
-#[derive(Default)]
-pub struct Keymap {
-    bindings: Vec<Binding>,
-    binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>,
-    disabled_keystrokes: HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<KeymapContextPredicate>>>,
-}
-
-impl Keymap {
-    #[cfg(test)]
-    pub(super) fn new(bindings: Vec<Binding>) -> Self {
-        let mut this = Self::default();
-        this.add_bindings(bindings);
-        this
-    }
-
-    pub(crate) fn bindings_for_action(
-        &self,
-        action_id: TypeId,
-    ) -> impl Iterator<Item = &'_ Binding> {
-        self.binding_indices_by_action_id
-            .get(&action_id)
-            .map(SmallVec::as_slice)
-            .unwrap_or(&[])
-            .iter()
-            .map(|ix| &self.bindings[*ix])
-            .filter(|binding| !self.binding_disabled(binding))
-    }
-
-    pub(crate) fn add_bindings<T: IntoIterator<Item = Binding>>(&mut self, bindings: T) {
-        let no_action_id = (NoAction {}).id();
-        let mut new_bindings = Vec::new();
-        let mut has_new_disabled_keystrokes = false;
-        for binding in bindings {
-            if binding.action().id() == no_action_id {
-                has_new_disabled_keystrokes |= self
-                    .disabled_keystrokes
-                    .entry(binding.keystrokes)
-                    .or_default()
-                    .insert(binding.context_predicate);
-            } else {
-                new_bindings.push(binding);
-            }
-        }
-
-        if has_new_disabled_keystrokes {
-            self.binding_indices_by_action_id.retain(|_, indices| {
-                indices.retain(|ix| {
-                    let binding = &self.bindings[*ix];
-                    match self.disabled_keystrokes.get(&binding.keystrokes) {
-                        Some(disabled_predicates) => {
-                            !disabled_predicates.contains(&binding.context_predicate)
-                        }
-                        None => true,
-                    }
-                });
-                !indices.is_empty()
-            });
-        }
-
-        for new_binding in new_bindings {
-            if !self.binding_disabled(&new_binding) {
-                self.binding_indices_by_action_id
-                    .entry(new_binding.action().id())
-                    .or_default()
-                    .push(self.bindings.len());
-                self.bindings.push(new_binding);
-            }
-        }
-    }
-
-    pub(crate) fn clear(&mut self) {
-        self.bindings.clear();
-        self.binding_indices_by_action_id.clear();
-        self.disabled_keystrokes.clear();
-    }
-
-    pub fn bindings(&self) -> Vec<&Binding> {
-        self.bindings
-            .iter()
-            .filter(|binding| !self.binding_disabled(binding))
-            .collect()
-    }
-
-    fn binding_disabled(&self, binding: &Binding) -> bool {
-        match self.disabled_keystrokes.get(&binding.keystrokes) {
-            Some(disabled_predicates) => disabled_predicates.contains(&binding.context_predicate),
-            None => false,
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::actions;
-
-    use super::*;
-
-    actions!(
-        keymap_test,
-        [Present1, Present2, Present3, Duplicate, Missing]
-    );
-
-    #[test]
-    fn regular_keymap() {
-        let present_1 = Binding::new("ctrl-q", Present1 {}, None);
-        let present_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
-        let present_3 = Binding::new("ctrl-e", Present3 {}, Some("editor"));
-        let keystroke_duplicate_to_1 = Binding::new("ctrl-q", Duplicate {}, None);
-        let full_duplicate_to_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
-        let missing = Binding::new("ctrl-r", Missing {}, None);
-        let all_bindings = [
-            &present_1,
-            &present_2,
-            &present_3,
-            &keystroke_duplicate_to_1,
-            &full_duplicate_to_2,
-            &missing,
-        ];
-
-        let mut keymap = Keymap::default();
-        assert_absent(&keymap, &all_bindings);
-        assert!(keymap.bindings().is_empty());
-
-        keymap.add_bindings([present_1.clone(), present_2.clone(), present_3.clone()]);
-        assert_absent(&keymap, &[&keystroke_duplicate_to_1, &missing]);
-        assert_present(
-            &keymap,
-            &[(&present_1, "q"), (&present_2, "w"), (&present_3, "e")],
-        );
-
-        keymap.add_bindings([
-            keystroke_duplicate_to_1.clone(),
-            full_duplicate_to_2.clone(),
-        ]);
-        assert_absent(&keymap, &[&missing]);
-        assert!(
-            !keymap.binding_disabled(&keystroke_duplicate_to_1),
-            "Duplicate binding 1 was added and should not be disabled"
-        );
-        assert!(
-            !keymap.binding_disabled(&full_duplicate_to_2),
-            "Duplicate binding 2 was added and should not be disabled"
-        );
-
-        assert_eq!(
-            keymap
-                .bindings_for_action(keystroke_duplicate_to_1.action().id())
-                .map(|binding| &binding.keystrokes)
-                .flatten()
-                .collect::<Vec<_>>(),
-            vec![&Keystroke {
-                ctrl: true,
-                alt: false,
-                shift: false,
-                cmd: false,
-                function: false,
-                key: "q".to_string(),
-                ime_key: None,
-            }],
-            "{keystroke_duplicate_to_1:?} should have the expected keystroke in the keymap"
-        );
-        assert_eq!(
-            keymap
-                .bindings_for_action(full_duplicate_to_2.action().id())
-                .map(|binding| &binding.keystrokes)
-                .flatten()
-                .collect::<Vec<_>>(),
-            vec![
-                &Keystroke {
-                    ctrl: true,
-                    alt: false,
-                    shift: false,
-                    cmd: false,
-                    function: false,
-                    key: "w".to_string(),
-                    ime_key: None,
-                },
-                &Keystroke {
-                    ctrl: true,
-                    alt: false,
-                    shift: false,
-                    cmd: false,
-                    function: false,
-                    key: "w".to_string(),
-                    ime_key: None,
-                }
-            ],
-            "{full_duplicate_to_2:?} should have a duplicated keystroke in the keymap"
-        );
-
-        let updated_bindings = keymap.bindings();
-        let expected_updated_bindings = vec![
-            &present_1,
-            &present_2,
-            &present_3,
-            &keystroke_duplicate_to_1,
-            &full_duplicate_to_2,
-        ];
-        assert_eq!(
-            updated_bindings.len(),
-            expected_updated_bindings.len(),
-            "Unexpected updated keymap bindings {updated_bindings:?}"
-        );
-        for (i, expected) in expected_updated_bindings.iter().enumerate() {
-            let keymap_binding = &updated_bindings[i];
-            assert_eq!(
-                keymap_binding.context_predicate, expected.context_predicate,
-                "Unexpected context predicate for keymap {i} element: {keymap_binding:?}"
-            );
-            assert_eq!(
-                keymap_binding.keystrokes, expected.keystrokes,
-                "Unexpected keystrokes for keymap {i} element: {keymap_binding:?}"
-            );
-        }
-
-        keymap.clear();
-        assert_absent(&keymap, &all_bindings);
-        assert!(keymap.bindings().is_empty());
-    }
-
-    #[test]
-    fn keymap_with_ignored() {
-        let present_1 = Binding::new("ctrl-q", Present1 {}, None);
-        let present_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
-        let present_3 = Binding::new("ctrl-e", Present3 {}, Some("editor"));
-        let keystroke_duplicate_to_1 = Binding::new("ctrl-q", Duplicate {}, None);
-        let full_duplicate_to_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
-        let ignored_1 = Binding::new("ctrl-q", NoAction {}, None);
-        let ignored_2 = Binding::new("ctrl-w", NoAction {}, Some("pane"));
-        let ignored_3_with_other_context =
-            Binding::new("ctrl-e", NoAction {}, Some("other_context"));
-
-        let mut keymap = Keymap::default();
-
-        keymap.add_bindings([
-            ignored_1.clone(),
-            ignored_2.clone(),
-            ignored_3_with_other_context.clone(),
-        ]);
-        assert_absent(&keymap, &[&present_3]);
-        assert_disabled(
-            &keymap,
-            &[
-                &present_1,
-                &present_2,
-                &ignored_1,
-                &ignored_2,
-                &ignored_3_with_other_context,
-            ],
-        );
-        assert!(keymap.bindings().is_empty());
-        keymap.clear();
-
-        keymap.add_bindings([
-            present_1.clone(),
-            present_2.clone(),
-            present_3.clone(),
-            ignored_1.clone(),
-            ignored_2.clone(),
-            ignored_3_with_other_context.clone(),
-        ]);
-        assert_present(&keymap, &[(&present_3, "e")]);
-        assert_disabled(
-            &keymap,
-            &[
-                &present_1,
-                &present_2,
-                &ignored_1,
-                &ignored_2,
-                &ignored_3_with_other_context,
-            ],
-        );
-        keymap.clear();
-
-        keymap.add_bindings([
-            present_1.clone(),
-            present_2.clone(),
-            present_3.clone(),
-            ignored_1.clone(),
-        ]);
-        assert_present(&keymap, &[(&present_2, "w"), (&present_3, "e")]);
-        assert_disabled(&keymap, &[&present_1, &ignored_1]);
-        assert_absent(&keymap, &[&ignored_2, &ignored_3_with_other_context]);
-        keymap.clear();
-
-        keymap.add_bindings([
-            present_1.clone(),
-            present_2.clone(),
-            present_3.clone(),
-            keystroke_duplicate_to_1.clone(),
-            full_duplicate_to_2.clone(),
-            ignored_1.clone(),
-            ignored_2.clone(),
-            ignored_3_with_other_context.clone(),
-        ]);
-        assert_present(&keymap, &[(&present_3, "e")]);
-        assert_disabled(
-            &keymap,
-            &[
-                &present_1,
-                &present_2,
-                &keystroke_duplicate_to_1,
-                &full_duplicate_to_2,
-                &ignored_1,
-                &ignored_2,
-                &ignored_3_with_other_context,
-            ],
-        );
-        keymap.clear();
-    }
-
-    #[track_caller]
-    fn assert_present(keymap: &Keymap, expected_bindings: &[(&Binding, &str)]) {
-        let keymap_bindings = keymap.bindings();
-        assert_eq!(
-            expected_bindings.len(),
-            keymap_bindings.len(),
-            "Unexpected keymap bindings {keymap_bindings:?}"
-        );
-        for (i, (expected, expected_key)) in expected_bindings.iter().enumerate() {
-            assert!(
-                !keymap.binding_disabled(expected),
-                "{expected:?} should not be disabled as it was added into keymap for element {i}"
-            );
-            assert_eq!(
-                keymap
-                    .bindings_for_action(expected.action().id())
-                    .map(|binding| &binding.keystrokes)
-                    .flatten()
-                    .collect::<Vec<_>>(),
-                vec![&Keystroke {
-                    ctrl: true,
-                    alt: false,
-                    shift: false,
-                    cmd: false,
-                    function: false,
-                    key: expected_key.to_string(),
-                    ime_key: None,
-                }],
-                "{expected:?} should have the expected keystroke with key '{expected_key}' in the keymap for element {i}"
-            );
-
-            let keymap_binding = &keymap_bindings[i];
-            assert_eq!(
-                keymap_binding.context_predicate, expected.context_predicate,
-                "Unexpected context predicate for keymap {i} element: {keymap_binding:?}"
-            );
-            assert_eq!(
-                keymap_binding.keystrokes, expected.keystrokes,
-                "Unexpected keystrokes for keymap {i} element: {keymap_binding:?}"
-            );
-        }
-    }
-
-    #[track_caller]
-    fn assert_absent(keymap: &Keymap, bindings: &[&Binding]) {
-        for binding in bindings.iter() {
-            assert!(
-                !keymap.binding_disabled(binding),
-                "{binding:?} should not be disabled in the keymap where was not added"
-            );
-            assert_eq!(
-                keymap.bindings_for_action(binding.action().id()).count(),
-                0,
-                "{binding:?} should have no actions in the keymap where was not added"
-            );
-        }
-    }
-
-    #[track_caller]
-    fn assert_disabled(keymap: &Keymap, bindings: &[&Binding]) {
-        for binding in bindings.iter() {
-            assert!(
-                keymap.binding_disabled(binding),
-                "{binding:?} should be disabled in the keymap"
-            );
-            assert_eq!(
-                keymap.bindings_for_action(binding.action().id()).count(),
-                0,
-                "{binding:?} should have no actions in the keymap where it was disabled"
-            );
-        }
-    }
-}

crates/gpui/src/keymap_matcher/keymap_context.rs 🔗

@@ -1,326 +0,0 @@
-use std::borrow::Cow;
-
-use anyhow::{anyhow, Result};
-use collections::{HashMap, HashSet};
-
-#[derive(Clone, Debug, Default, Eq, PartialEq)]
-pub struct KeymapContext {
-    set: HashSet<Cow<'static, str>>,
-    map: HashMap<Cow<'static, str>, Cow<'static, str>>,
-}
-
-impl KeymapContext {
-    pub fn new() -> Self {
-        KeymapContext {
-            set: HashSet::default(),
-            map: HashMap::default(),
-        }
-    }
-
-    pub fn clear(&mut self) {
-        self.set.clear();
-        self.map.clear();
-    }
-
-    pub fn extend(&mut self, other: &Self) {
-        for v in &other.set {
-            self.set.insert(v.clone());
-        }
-        for (k, v) in &other.map {
-            self.map.insert(k.clone(), v.clone());
-        }
-    }
-
-    pub fn add_identifier<I: Into<Cow<'static, str>>>(&mut self, identifier: I) {
-        self.set.insert(identifier.into());
-    }
-
-    pub fn add_key<S1: Into<Cow<'static, str>>, S2: Into<Cow<'static, str>>>(
-        &mut self,
-        key: S1,
-        value: S2,
-    ) {
-        self.map.insert(key.into(), value.into());
-    }
-}
-
-#[derive(Clone, Debug, Eq, PartialEq, Hash)]
-pub enum KeymapContextPredicate {
-    Identifier(String),
-    Equal(String, String),
-    NotEqual(String, String),
-    Child(Box<KeymapContextPredicate>, Box<KeymapContextPredicate>),
-    Not(Box<KeymapContextPredicate>),
-    And(Box<KeymapContextPredicate>, Box<KeymapContextPredicate>),
-    Or(Box<KeymapContextPredicate>, Box<KeymapContextPredicate>),
-}
-
-impl KeymapContextPredicate {
-    pub fn parse(source: &str) -> Result<Self> {
-        let source = Self::skip_whitespace(source);
-        let (predicate, rest) = Self::parse_expr(source, 0)?;
-        if let Some(next) = rest.chars().next() {
-            Err(anyhow!("unexpected character {next:?}"))
-        } else {
-            Ok(predicate)
-        }
-    }
-
-    pub fn eval(&self, contexts: &[KeymapContext]) -> bool {
-        let Some(context) = contexts.first() else {
-            return false;
-        };
-        match self {
-            Self::Identifier(name) => (&context.set).contains(name.as_str()),
-            Self::Equal(left, right) => context
-                .map
-                .get(left.as_str())
-                .map(|value| value == right)
-                .unwrap_or(false),
-            Self::NotEqual(left, right) => context
-                .map
-                .get(left.as_str())
-                .map(|value| value != right)
-                .unwrap_or(true),
-            Self::Not(pred) => !pred.eval(contexts),
-            Self::Child(parent, child) => parent.eval(&contexts[1..]) && child.eval(contexts),
-            Self::And(left, right) => left.eval(contexts) && right.eval(contexts),
-            Self::Or(left, right) => left.eval(contexts) || right.eval(contexts),
-        }
-    }
-
-    fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> {
-        type Op =
-            fn(KeymapContextPredicate, KeymapContextPredicate) -> Result<KeymapContextPredicate>;
-
-        let (mut predicate, rest) = Self::parse_primary(source)?;
-        source = rest;
-
-        'parse: loop {
-            for (operator, precedence, constructor) in [
-                (">", PRECEDENCE_CHILD, Self::new_child as Op),
-                ("&&", PRECEDENCE_AND, Self::new_and as Op),
-                ("||", PRECEDENCE_OR, Self::new_or as Op),
-                ("==", PRECEDENCE_EQ, Self::new_eq as Op),
-                ("!=", PRECEDENCE_EQ, Self::new_neq as Op),
-            ] {
-                if source.starts_with(operator) && precedence >= min_precedence {
-                    source = Self::skip_whitespace(&source[operator.len()..]);
-                    let (right, rest) = Self::parse_expr(source, precedence + 1)?;
-                    predicate = constructor(predicate, right)?;
-                    source = rest;
-                    continue 'parse;
-                }
-            }
-            break;
-        }
-
-        Ok((predicate, source))
-    }
-
-    fn parse_primary(mut source: &str) -> anyhow::Result<(Self, &str)> {
-        let next = source
-            .chars()
-            .next()
-            .ok_or_else(|| anyhow!("unexpected eof"))?;
-        match next {
-            '(' => {
-                source = Self::skip_whitespace(&source[1..]);
-                let (predicate, rest) = Self::parse_expr(source, 0)?;
-                if rest.starts_with(')') {
-                    source = Self::skip_whitespace(&rest[1..]);
-                    Ok((predicate, source))
-                } else {
-                    Err(anyhow!("expected a ')'"))
-                }
-            }
-            '!' => {
-                let source = Self::skip_whitespace(&source[1..]);
-                let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?;
-                Ok((KeymapContextPredicate::Not(Box::new(predicate)), source))
-            }
-            _ if next.is_alphanumeric() || next == '_' => {
-                let len = source
-                    .find(|c: char| !(c.is_alphanumeric() || c == '_'))
-                    .unwrap_or(source.len());
-                let (identifier, rest) = source.split_at(len);
-                source = Self::skip_whitespace(rest);
-                Ok((
-                    KeymapContextPredicate::Identifier(identifier.into()),
-                    source,
-                ))
-            }
-            _ => Err(anyhow!("unexpected character {next:?}")),
-        }
-    }
-
-    fn skip_whitespace(source: &str) -> &str {
-        let len = source
-            .find(|c: char| !c.is_whitespace())
-            .unwrap_or(source.len());
-        &source[len..]
-    }
-
-    fn new_or(self, other: Self) -> Result<Self> {
-        Ok(Self::Or(Box::new(self), Box::new(other)))
-    }
-
-    fn new_and(self, other: Self) -> Result<Self> {
-        Ok(Self::And(Box::new(self), Box::new(other)))
-    }
-
-    fn new_child(self, other: Self) -> Result<Self> {
-        Ok(Self::Child(Box::new(self), Box::new(other)))
-    }
-
-    fn new_eq(self, other: Self) -> Result<Self> {
-        if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
-            Ok(Self::Equal(left, right))
-        } else {
-            Err(anyhow!("operands must be identifiers"))
-        }
-    }
-
-    fn new_neq(self, other: Self) -> Result<Self> {
-        if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
-            Ok(Self::NotEqual(left, right))
-        } else {
-            Err(anyhow!("operands must be identifiers"))
-        }
-    }
-}
-
-const PRECEDENCE_CHILD: u32 = 1;
-const PRECEDENCE_OR: u32 = 2;
-const PRECEDENCE_AND: u32 = 3;
-const PRECEDENCE_EQ: u32 = 4;
-const PRECEDENCE_NOT: u32 = 5;
-
-#[cfg(test)]
-mod tests {
-    use super::KeymapContextPredicate::{self, *};
-
-    #[test]
-    fn test_parse_identifiers() {
-        // Identifiers
-        assert_eq!(
-            KeymapContextPredicate::parse("abc12").unwrap(),
-            Identifier("abc12".into())
-        );
-        assert_eq!(
-            KeymapContextPredicate::parse("_1a").unwrap(),
-            Identifier("_1a".into())
-        );
-    }
-
-    #[test]
-    fn test_parse_negations() {
-        assert_eq!(
-            KeymapContextPredicate::parse("!abc").unwrap(),
-            Not(Box::new(Identifier("abc".into())))
-        );
-        assert_eq!(
-            KeymapContextPredicate::parse(" ! ! abc").unwrap(),
-            Not(Box::new(Not(Box::new(Identifier("abc".into())))))
-        );
-    }
-
-    #[test]
-    fn test_parse_equality_operators() {
-        assert_eq!(
-            KeymapContextPredicate::parse("a == b").unwrap(),
-            Equal("a".into(), "b".into())
-        );
-        assert_eq!(
-            KeymapContextPredicate::parse("c!=d").unwrap(),
-            NotEqual("c".into(), "d".into())
-        );
-        assert_eq!(
-            KeymapContextPredicate::parse("c == !d")
-                .unwrap_err()
-                .to_string(),
-            "operands must be identifiers"
-        );
-    }
-
-    #[test]
-    fn test_parse_boolean_operators() {
-        assert_eq!(
-            KeymapContextPredicate::parse("a || b").unwrap(),
-            Or(
-                Box::new(Identifier("a".into())),
-                Box::new(Identifier("b".into()))
-            )
-        );
-        assert_eq!(
-            KeymapContextPredicate::parse("a || !b && c").unwrap(),
-            Or(
-                Box::new(Identifier("a".into())),
-                Box::new(And(
-                    Box::new(Not(Box::new(Identifier("b".into())))),
-                    Box::new(Identifier("c".into()))
-                ))
-            )
-        );
-        assert_eq!(
-            KeymapContextPredicate::parse("a && b || c&&d").unwrap(),
-            Or(
-                Box::new(And(
-                    Box::new(Identifier("a".into())),
-                    Box::new(Identifier("b".into()))
-                )),
-                Box::new(And(
-                    Box::new(Identifier("c".into())),
-                    Box::new(Identifier("d".into()))
-                ))
-            )
-        );
-        assert_eq!(
-            KeymapContextPredicate::parse("a == b && c || d == e && f").unwrap(),
-            Or(
-                Box::new(And(
-                    Box::new(Equal("a".into(), "b".into())),
-                    Box::new(Identifier("c".into()))
-                )),
-                Box::new(And(
-                    Box::new(Equal("d".into(), "e".into())),
-                    Box::new(Identifier("f".into()))
-                ))
-            )
-        );
-        assert_eq!(
-            KeymapContextPredicate::parse("a && b && c && d").unwrap(),
-            And(
-                Box::new(And(
-                    Box::new(And(
-                        Box::new(Identifier("a".into())),
-                        Box::new(Identifier("b".into()))
-                    )),
-                    Box::new(Identifier("c".into())),
-                )),
-                Box::new(Identifier("d".into()))
-            ),
-        );
-    }
-
-    #[test]
-    fn test_parse_parenthesized_expressions() {
-        assert_eq!(
-            KeymapContextPredicate::parse("a && (b == c || d != e)").unwrap(),
-            And(
-                Box::new(Identifier("a".into())),
-                Box::new(Or(
-                    Box::new(Equal("b".into(), "c".into())),
-                    Box::new(NotEqual("d".into(), "e".into())),
-                )),
-            ),
-        );
-        assert_eq!(
-            KeymapContextPredicate::parse(" ( a || b ) ").unwrap(),
-            Or(
-                Box::new(Identifier("a".into())),
-                Box::new(Identifier("b".into())),
-            )
-        );
-    }
-}

crates/gpui/src/keymap_matcher/keystroke.rs 🔗

@@ -1,141 +0,0 @@
-use std::fmt::Write;
-
-use anyhow::anyhow;
-use serde::Deserialize;
-use smallvec::SmallVec;
-
-#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
-pub struct Keystroke {
-    pub ctrl: bool,
-    pub alt: bool,
-    pub shift: bool,
-    pub cmd: bool,
-    pub function: bool,
-    /// key is the character printed on the key that was pressed
-    /// e.g. for option-s, key is "s"
-    pub key: String,
-    /// ime_key is the character inserted by the IME engine when that key was pressed.
-    /// e.g. for option-s, ime_key is "ß"
-    pub ime_key: Option<String>,
-}
-
-impl Keystroke {
-    // When matching a key we cannot know whether the user intended to type
-    // the ime_key or the key. On some non-US keyboards keys we use in our
-    // bindings are behind option (for example `$` is typed `alt-ç` on a Czech keyboard),
-    // and on some keyboards the IME handler converts a sequence of keys into a
-    // specific character (for example `"` is typed as `" space` on a brazillian keyboard).
-    pub fn match_possibilities(&self) -> SmallVec<[Keystroke; 2]> {
-        let mut possibilities = SmallVec::new();
-        match self.ime_key.as_ref() {
-            None => possibilities.push(self.clone()),
-            Some(ime_key) => {
-                possibilities.push(Keystroke {
-                    ctrl: self.ctrl,
-                    alt: false,
-                    shift: false,
-                    cmd: false,
-                    function: false,
-                    key: ime_key.to_string(),
-                    ime_key: None,
-                });
-                possibilities.push(Keystroke {
-                    ime_key: None,
-                    ..self.clone()
-                });
-            }
-        }
-        possibilities
-    }
-
-    /// key syntax is:
-    /// [ctrl-][alt-][shift-][cmd-][fn-]key[->ime_key]
-    /// ime_key is only used for generating test events,
-    /// when matching a key with an ime_key set will be matched without it.
-    pub fn parse(source: &str) -> anyhow::Result<Self> {
-        let mut ctrl = false;
-        let mut alt = false;
-        let mut shift = false;
-        let mut cmd = false;
-        let mut function = false;
-        let mut key = None;
-        let mut ime_key = None;
-
-        let mut components = source.split('-').peekable();
-        while let Some(component) = components.next() {
-            match component {
-                "ctrl" => ctrl = true,
-                "alt" => alt = true,
-                "shift" => shift = true,
-                "cmd" => cmd = true,
-                "fn" => function = true,
-                _ => {
-                    if let Some(next) = components.peek() {
-                        if next.is_empty() && source.ends_with('-') {
-                            key = Some(String::from("-"));
-                            break;
-                        } else if next.len() > 1 && next.starts_with('>') {
-                            key = Some(String::from(component));
-                            ime_key = Some(String::from(&next[1..]));
-                            components.next();
-                        } else {
-                            return Err(anyhow!("Invalid keystroke `{}`", source));
-                        }
-                    } else {
-                        key = Some(String::from(component));
-                    }
-                }
-            }
-        }
-
-        let key = key.ok_or_else(|| anyhow!("Invalid keystroke `{}`", source))?;
-
-        Ok(Keystroke {
-            ctrl,
-            alt,
-            shift,
-            cmd,
-            function,
-            key,
-            ime_key,
-        })
-    }
-
-    pub fn modified(&self) -> bool {
-        self.ctrl || self.alt || self.shift || self.cmd
-    }
-}
-
-impl std::fmt::Display for Keystroke {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        if self.ctrl {
-            f.write_char('^')?;
-        }
-        if self.alt {
-            f.write_char('⌥')?;
-        }
-        if self.cmd {
-            f.write_char('⌘')?;
-        }
-        if self.shift {
-            f.write_char('⇧')?;
-        }
-        let key = match self.key.as_str() {
-            "backspace" => '⌫',
-            "up" => '↑',
-            "down" => '↓',
-            "left" => '←',
-            "right" => '→',
-            "tab" => '⇥',
-            "escape" => '⎋',
-            key => {
-                if key.len() == 1 {
-                    key.chars().next().unwrap().to_ascii_uppercase()
-                } else {
-                    return f.write_str(key);
-                }
-            }
-        };
-        f.write_char(key)
-    }
-}

crates/gpui/src/platform.rs 🔗

@@ -1,37 +1,27 @@
-mod event;
+mod app_menu;
+mod keystroke;
 #[cfg(target_os = "macos")]
-pub mod mac;
-pub mod test;
-pub mod current {
-    #[cfg(target_os = "macos")]
-    pub use super::mac::*;
-}
+mod mac;
+#[cfg(any(test, feature = "test-support"))]
+mod test;
 
 use crate::{
-    executor,
-    fonts::{
-        Features as FontFeatures, FontId, GlyphId, Metrics as FontMetrics,
-        Properties as FontProperties,
-    },
-    geometry::{
-        rect::{RectF, RectI},
-        vector::Vector2F,
-    },
-    keymap_matcher::KeymapMatcher,
-    text_layout::{LineLayout, RunStyle},
-    Action, AnyWindowHandle, ClipboardItem, Menu, Scene,
+    point, size, Action, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId,
+    FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, Keymap,
+    LineLayout, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result,
+    Scene, SharedString, Size, TaskLabel,
 };
-use anyhow::{anyhow, bail, Result};
+use anyhow::{anyhow, bail};
 use async_task::Runnable;
-pub use event::*;
-use pathfinder_geometry::vector::vec2f;
-use postage::oneshot;
-use schemars::JsonSchema;
-use serde::Deserialize;
-use sqlez::{
-    bindable::{Bind, Column, StaticColumnCount},
-    statement::Statement,
-};
+use futures::channel::oneshot;
+use parking::Unparker;
+use seahash::SeaHasher;
+use serde::{Deserialize, Serialize};
+use sqlez::bindable::{Bind, Column, StaticColumnCount};
+use sqlez::statement::Statement;
+use std::borrow::Cow;
+use std::hash::{Hash, Hasher};
+use std::time::Duration;
 use std::{
     any::Any,
     fmt::{self, Debug, Display},
@@ -41,115 +31,126 @@ use std::{
     str::FromStr,
     sync::Arc,
 };
-use time::UtcOffset;
 use uuid::Uuid;
 
-pub trait Platform: Send + Sync {
-    fn dispatcher(&self) -> Arc<dyn Dispatcher>;
-    fn fonts(&self) -> Arc<dyn FontSystem>;
+pub use app_menu::*;
+pub use keystroke::*;
+#[cfg(target_os = "macos")]
+pub use mac::*;
+#[cfg(any(test, feature = "test-support"))]
+pub use test::*;
+pub use time::UtcOffset;
+
+#[cfg(target_os = "macos")]
+pub(crate) fn current_platform() -> Rc<dyn Platform> {
+    Rc::new(MacPlatform::new())
+}
+
+pub type DrawWindow = Box<dyn FnMut() -> Result<Scene>>;
+
+pub(crate) trait Platform: 'static {
+    fn background_executor(&self) -> BackgroundExecutor;
+    fn foreground_executor(&self) -> ForegroundExecutor;
+    fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
 
+    fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
+    fn quit(&self);
+    fn restart(&self);
     fn activate(&self, ignoring_other_apps: bool);
     fn hide(&self);
     fn hide_other_apps(&self);
     fn unhide_other_apps(&self);
-    fn quit(&self);
-
-    fn screen_by_id(&self, id: Uuid) -> Option<Rc<dyn Screen>>;
-    fn screens(&self) -> Vec<Rc<dyn Screen>>;
 
+    fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
+    fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
+    fn active_window(&self) -> Option<AnyWindowHandle>;
     fn open_window(
         &self,
         handle: AnyWindowHandle,
         options: WindowOptions,
-        executor: Rc<executor::Foreground>,
-    ) -> Box<dyn Window>;
-    fn main_window(&self) -> Option<AnyWindowHandle>;
+        draw: DrawWindow,
+    ) -> Box<dyn PlatformWindow>;
 
-    fn add_status_item(&self, handle: AnyWindowHandle) -> Box<dyn Window>;
+    fn set_display_link_output_callback(
+        &self,
+        display_id: DisplayId,
+        callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp) + Send>,
+    );
+    fn start_display_link(&self, display_id: DisplayId);
+    fn stop_display_link(&self, display_id: DisplayId);
+    // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn PlatformWindow>;
 
-    fn write_to_clipboard(&self, item: ClipboardItem);
-    fn read_from_clipboard(&self) -> Option<ClipboardItem>;
     fn open_url(&self, url: &str);
+    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
+    fn prompt_for_paths(
+        &self,
+        options: PathPromptOptions,
+    ) -> oneshot::Receiver<Option<Vec<PathBuf>>>;
+    fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>>;
+    fn reveal_path(&self, path: &Path);
 
-    fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()>;
-    fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>>;
-    fn delete_credentials(&self, url: &str) -> Result<()>;
+    fn on_become_active(&self, callback: Box<dyn FnMut()>);
+    fn on_resign_active(&self, callback: Box<dyn FnMut()>);
+    fn on_quit(&self, callback: Box<dyn FnMut()>);
+    fn on_reopen(&self, callback: Box<dyn FnMut()>);
+    fn on_event(&self, callback: Box<dyn FnMut(InputEvent) -> bool>);
 
-    fn set_cursor_style(&self, style: CursorStyle);
-    fn should_auto_hide_scrollbars(&self) -> bool;
+    fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
+    fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
+    fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
+    fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
 
+    fn os_name(&self) -> &'static str;
+    fn os_version(&self) -> Result<SemanticVersion>;
+    fn app_version(&self) -> Result<SemanticVersion>;
+    fn app_path(&self) -> Result<PathBuf>;
     fn local_timezone(&self) -> UtcOffset;
-
+    fn double_click_interval(&self) -> Duration;
     fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
-    fn app_path(&self) -> Result<PathBuf>;
-    fn app_version(&self) -> Result<AppVersion>;
-    fn os_name(&self) -> &'static str;
-    fn os_version(&self) -> Result<AppVersion>;
-    fn restart(&self);
-}
-
-pub(crate) trait ForegroundPlatform {
-    fn on_become_active(&self, callback: Box<dyn FnMut()>);
-    fn on_resign_active(&self, callback: Box<dyn FnMut()>);
-    fn on_quit(&self, callback: Box<dyn FnMut()>);
 
-    /// Handle the application being re-activated with no windows open.
-    fn on_reopen(&self, callback: Box<dyn FnMut()>);
+    fn set_cursor_style(&self, style: CursorStyle);
+    fn should_auto_hide_scrollbars(&self) -> bool;
 
-    fn on_event(&self, callback: Box<dyn FnMut(Event) -> bool>);
-    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
-    fn run(&self, on_finish_launching: Box<dyn FnOnce()>);
+    fn write_to_clipboard(&self, item: ClipboardItem);
+    fn read_from_clipboard(&self) -> Option<ClipboardItem>;
 
-    fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>);
-    fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
-    fn on_will_open_menu(&self, callback: Box<dyn FnMut()>);
-    fn set_menus(&self, menus: Vec<Menu>, matcher: &KeymapMatcher);
-    fn prompt_for_paths(
-        &self,
-        options: PathPromptOptions,
-    ) -> oneshot::Receiver<Option<Vec<PathBuf>>>;
-    fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>>;
-    fn reveal_path(&self, path: &Path);
+    fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()>;
+    fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>>;
+    fn delete_credentials(&self, url: &str) -> Result<()>;
 }
 
-pub trait Dispatcher: Send + Sync {
-    fn is_main_thread(&self) -> bool;
-    fn run_on_main_thread(&self, task: Runnable);
+pub trait PlatformDisplay: Send + Sync + Debug {
+    fn id(&self) -> DisplayId;
+    /// Returns a stable identifier for this display that can be persisted and used
+    /// across system restarts.
+    fn uuid(&self) -> Result<Uuid>;
+    fn as_any(&self) -> &dyn Any;
+    fn bounds(&self) -> Bounds<GlobalPixels>;
 }
 
-pub trait InputHandler {
-    fn selected_text_range(&self) -> Option<Range<usize>>;
-    fn marked_text_range(&self) -> Option<Range<usize>>;
-    fn text_for_range(&self, range_utf16: Range<usize>) -> Option<String>;
-    fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
-    fn replace_and_mark_text_in_range(
-        &mut self,
-        range_utf16: Option<Range<usize>>,
-        new_text: &str,
-        new_selected_range: Option<Range<usize>>,
-    );
-    fn unmark_text(&mut self);
-    fn rect_for_range(&self, range_utf16: Range<usize>) -> Option<RectF>;
-}
+#[derive(PartialEq, Eq, Hash, Copy, Clone)]
+pub struct DisplayId(pub(crate) u32);
 
-pub trait Screen: Debug {
-    fn as_any(&self) -> &dyn Any;
-    fn bounds(&self) -> RectF;
-    fn content_bounds(&self) -> RectF;
-    fn display_uuid(&self) -> Option<Uuid>;
+impl Debug for DisplayId {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "DisplayId({})", self.0)
+    }
 }
 
-pub trait Window {
+unsafe impl Send for DisplayId {}
+
+pub trait PlatformWindow {
     fn bounds(&self) -> WindowBounds;
-    fn content_size(&self) -> Vector2F;
+    fn content_size(&self) -> Size<Pixels>;
     fn scale_factor(&self) -> f32;
-    fn titlebar_height(&self) -> f32;
-    fn appearance(&self) -> Appearance;
-    fn screen(&self) -> Rc<dyn Screen>;
-    fn mouse_position(&self) -> Vector2F;
-
+    fn titlebar_height(&self) -> Pixels;
+    fn appearance(&self) -> WindowAppearance;
+    fn display(&self) -> Rc<dyn PlatformDisplay>;
+    fn mouse_position(&self) -> Point<Pixels>;
+    fn modifiers(&self) -> Modifiers;
     fn as_any_mut(&mut self) -> &mut dyn Any;
-    fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>);
+    fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>);
+    fn clear_input_handler(&mut self);
     fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize>;
     fn activate(&self);
     fn set_title(&mut self, title: &str);
@@ -157,47 +158,214 @@ pub trait Window {
     fn show_character_palette(&self);
     fn minimize(&self);
     fn zoom(&self);
-    fn present_scene(&mut self, scene: Scene);
     fn toggle_full_screen(&self);
+    fn on_input(&self, callback: Box<dyn FnMut(InputEvent) -> bool>);
+    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
+    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
+    fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>);
+    fn on_moved(&self, callback: Box<dyn FnMut()>);
+    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
+    fn on_close(&self, callback: Box<dyn FnOnce()>);
+    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
+    fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
+    fn invalidate(&self);
+
+    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
+
+    #[cfg(any(test, feature = "test-support"))]
+    fn as_test(&mut self) -> Option<&mut TestWindow> {
+        None
+    }
+}
+
+pub trait PlatformDispatcher: Send + Sync {
+    fn is_main_thread(&self) -> bool;
+    fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>);
+    fn dispatch_on_main_thread(&self, runnable: Runnable);
+    fn dispatch_after(&self, duration: Duration, runnable: Runnable);
+    fn tick(&self, background_only: bool) -> bool;
+    fn park(&self);
+    fn unparker(&self) -> Unparker;
+
+    #[cfg(any(test, feature = "test-support"))]
+    fn as_test(&self) -> Option<&TestDispatcher> {
+        None
+    }
+}
 
-    fn on_event(&mut self, callback: Box<dyn FnMut(Event) -> bool>);
-    fn on_active_status_change(&mut self, callback: Box<dyn FnMut(bool)>);
-    fn on_resize(&mut self, callback: Box<dyn FnMut()>);
-    fn on_fullscreen(&mut self, callback: Box<dyn FnMut(bool)>);
-    fn on_moved(&mut self, callback: Box<dyn FnMut()>);
-    fn on_should_close(&mut self, callback: Box<dyn FnMut() -> bool>);
-    fn on_close(&mut self, callback: Box<dyn FnOnce()>);
-    fn on_appearance_changed(&mut self, callback: Box<dyn FnMut()>);
-    fn is_topmost_for_position(&self, position: Vector2F) -> bool;
+pub trait PlatformTextSystem: Send + Sync {
+    fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()>;
+    fn all_font_families(&self) -> Vec<String>;
+    fn font_id(&self, descriptor: &Font) -> Result<FontId>;
+    fn font_metrics(&self, font_id: FontId) -> FontMetrics;
+    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
+    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
+    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
+    fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
+    fn rasterize_glyph(
+        &self,
+        params: &RenderGlyphParams,
+        raster_bounds: Bounds<DevicePixels>,
+    ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
+    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
+    fn wrap_line(
+        &self,
+        text: &str,
+        font_id: FontId,
+        font_size: Pixels,
+        width: Pixels,
+    ) -> Vec<usize>;
+}
+
+#[derive(Clone, Debug)]
+pub struct AppMetadata {
+    pub os_name: &'static str,
+    pub os_version: Option<SemanticVersion>,
+    pub app_version: Option<SemanticVersion>,
+}
+
+#[derive(PartialEq, Eq, Hash, Clone)]
+pub enum AtlasKey {
+    Glyph(RenderGlyphParams),
+    Svg(RenderSvgParams),
+    Image(RenderImageParams),
+}
+
+impl AtlasKey {
+    pub(crate) fn texture_kind(&self) -> AtlasTextureKind {
+        match self {
+            AtlasKey::Glyph(params) => {
+                if params.is_emoji {
+                    AtlasTextureKind::Polychrome
+                } else {
+                    AtlasTextureKind::Monochrome
+                }
+            }
+            AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
+            AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
+        }
+    }
+}
+
+impl From<RenderGlyphParams> for AtlasKey {
+    fn from(params: RenderGlyphParams) -> Self {
+        Self::Glyph(params)
+    }
+}
+
+impl From<RenderSvgParams> for AtlasKey {
+    fn from(params: RenderSvgParams) -> Self {
+        Self::Svg(params)
+    }
+}
+
+impl From<RenderImageParams> for AtlasKey {
+    fn from(params: RenderImageParams) -> Self {
+        Self::Image(params)
+    }
+}
+
+pub trait PlatformAtlas: Send + Sync {
+    fn get_or_insert_with<'a>(
+        &self,
+        key: &AtlasKey,
+        build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
+    ) -> Result<AtlasTile>;
+
+    fn clear(&self);
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+#[repr(C)]
+pub struct AtlasTile {
+    pub(crate) texture_id: AtlasTextureId,
+    pub(crate) tile_id: TileId,
+    pub(crate) bounds: Bounds<DevicePixels>,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+#[repr(C)]
+pub(crate) struct AtlasTextureId {
+    // We use u32 instead of usize for Metal Shader Language compatibility
+    pub(crate) index: u32,
+    pub(crate) kind: AtlasTextureKind,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+#[repr(C)]
+pub(crate) enum AtlasTextureKind {
+    Monochrome = 0,
+    Polychrome = 1,
+    Path = 2,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
+#[repr(C)]
+pub(crate) struct TileId(pub(crate) u32);
+
+impl From<etagere::AllocId> for TileId {
+    fn from(id: etagere::AllocId) -> Self {
+        Self(id.serialize())
+    }
+}
+
+impl From<TileId> for etagere::AllocId {
+    fn from(id: TileId) -> Self {
+        Self::deserialize(id.0)
+    }
+}
+
+pub trait PlatformInputHandler: 'static {
+    fn selected_text_range(&mut self) -> Option<Range<usize>>;
+    fn marked_text_range(&mut self) -> Option<Range<usize>>;
+    fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String>;
+    fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
+    fn replace_and_mark_text_in_range(
+        &mut self,
+        range_utf16: Option<Range<usize>>,
+        new_text: &str,
+        new_selected_range: Option<Range<usize>>,
+    );
+    fn unmark_text(&mut self);
+    fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>>;
 }
 
 #[derive(Debug)]
-pub struct WindowOptions<'a> {
+pub struct WindowOptions {
     pub bounds: WindowBounds,
-    pub titlebar: Option<TitlebarOptions<'a>>,
+    pub titlebar: Option<TitlebarOptions>,
     pub center: bool,
     pub focus: bool,
     pub show: bool,
     pub kind: WindowKind,
     pub is_movable: bool,
-    pub screen: Option<Rc<dyn Screen>>,
+    pub display_id: Option<DisplayId>,
 }
 
-impl<'a> WindowOptions<'a> {
-    pub fn with_bounds(bounds: Vector2F) -> Self {
+impl Default for WindowOptions {
+    fn default() -> Self {
         Self {
-            bounds: WindowBounds::Fixed(RectF::new(vec2f(0., 0.), bounds)),
-            center: true,
-            ..Default::default()
+            bounds: WindowBounds::default(),
+            titlebar: Some(TitlebarOptions {
+                title: Default::default(),
+                appears_transparent: Default::default(),
+                traffic_light_position: Default::default(),
+            }),
+            center: false,
+            focus: true,
+            show: true,
+            kind: WindowKind::Normal,
+            is_movable: true,
+            display_id: None,
         }
     }
 }
 
 #[derive(Debug, Default)]
-pub struct TitlebarOptions<'a> {
-    pub title: Option<&'a str>,
+pub struct TitlebarOptions {
+    pub title: Option<SharedString>,
     pub appears_transparent: bool,
-    pub traffic_light_position: Option<Vector2F>,
+    pub traffic_light_position: Option<Point<Pixels>>,
 }
 
 #[derive(Copy, Clone, Debug)]
@@ -220,11 +388,12 @@ pub enum WindowKind {
     PopUp,
 }
 
-#[derive(Copy, Clone, Debug, PartialEq)]
+#[derive(Copy, Clone, Debug, PartialEq, Default)]
 pub enum WindowBounds {
     Fullscreen,
+    #[default]
     Maximized,
-    Fixed(RectF),
+    Fixed(Bounds<GlobalPixels>),
 }
 
 impl StaticColumnCount for WindowBounds {
@@ -253,10 +422,10 @@ impl Bind for WindowBounds {
         statement.bind(
             &region.map(|region| {
                 (
-                    region.min_x(),
-                    region.min_y(),
-                    region.width(),
-                    region.height(),
+                    region.origin.x,
+                    region.origin.y,
+                    region.size.width,
+                    region.size.height,
                 )
             }),
             next_index,
@@ -272,10 +441,14 @@ impl Column for WindowBounds {
             "Maximized" => WindowBounds::Maximized,
             "Fixed" => {
                 let ((x, y, width, height), _) = Column::column(statement, next_index)?;
-                WindowBounds::Fixed(RectF::new(
-                    Vector2F::new(x, y),
-                    Vector2F::new(width, height),
-                ))
+                let x: f64 = x;
+                let y: f64 = y;
+                let width: f64 = width;
+                let height: f64 = height;
+                WindowBounds::Fixed(Bounds {
+                    origin: point(x.into(), y.into()),
+                    size: size(width.into(), height.into()),
+                })
             }
             _ => bail!("Window State did not have a valid string"),
         };
@@ -284,25 +457,55 @@ impl Column for WindowBounds {
     }
 }
 
+#[derive(Copy, Clone, Debug)]
+pub enum WindowAppearance {
+    Light,
+    VibrantLight,
+    Dark,
+    VibrantDark,
+}
+
+impl Default for WindowAppearance {
+    fn default() -> Self {
+        Self::Light
+    }
+}
+
+#[derive(Copy, Clone, Debug)]
 pub struct PathPromptOptions {
     pub files: bool,
     pub directories: bool,
     pub multiple: bool,
 }
 
+#[derive(Copy, Clone, Debug)]
 pub enum PromptLevel {
     Info,
     Warning,
     Critical,
 }
 
-#[derive(Copy, Clone, Debug, Deserialize, JsonSchema)]
+/// The style of the cursor (pointer)
+#[derive(Copy, Clone, Debug)]
 pub enum CursorStyle {
     Arrow,
+    IBeam,
+    Crosshair,
+    ClosedHand,
+    OpenHand,
+    PointingHand,
+    ResizeLeft,
+    ResizeRight,
     ResizeLeftRight,
+    ResizeUp,
+    ResizeDown,
     ResizeUpDown,
-    PointingHand,
-    IBeam,
+    DisappearingItem,
+    IBeamCursorForVerticalLayout,
+    OperationNotAllowed,
+    DragLink,
+    DragCopy,
+    ContextualMenu,
 }
 
 impl Default for CursorStyle {
@@ -311,14 +514,14 @@ impl Default for CursorStyle {
     }
 }
 
-#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
-pub struct AppVersion {
+#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize)]
+pub struct SemanticVersion {
     major: usize,
     minor: usize,
     patch: usize,
 }
 
-impl FromStr for AppVersion {
+impl FromStr for SemanticVersion {
     type Err = anyhow::Error;
 
     fn from_str(s: &str) -> Result<Self> {
@@ -343,59 +546,47 @@ impl FromStr for AppVersion {
     }
 }
 
-impl Display for AppVersion {
+impl Display for SemanticVersion {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
     }
 }
 
-#[derive(Copy, Clone, Debug)]
-pub enum RasterizationOptions {
-    Alpha,
-    Bgra,
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct ClipboardItem {
+    pub(crate) text: String,
+    pub(crate) metadata: Option<String>,
 }
 
-pub trait FontSystem: Send + Sync {
-    fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()>;
-    fn all_families(&self) -> Vec<String>;
-    fn load_family(&self, name: &str, features: &FontFeatures) -> anyhow::Result<Vec<FontId>>;
-    fn select_font(
-        &self,
-        font_ids: &[FontId],
-        properties: &FontProperties,
-    ) -> anyhow::Result<FontId>;
-    fn font_metrics(&self, font_id: FontId) -> FontMetrics;
-    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<RectF>;
-    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Vector2F>;
-    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
-    fn rasterize_glyph(
-        &self,
-        font_id: FontId,
-        font_size: f32,
-        glyph_id: GlyphId,
-        subpixel_shift: Vector2F,
-        scale_factor: f32,
-        options: RasterizationOptions,
-    ) -> Option<(RectI, Vec<u8>)>;
-    fn layout_line(&self, text: &str, font_size: f32, runs: &[(usize, RunStyle)]) -> LineLayout;
-    fn wrap_line(&self, text: &str, font_id: FontId, font_size: f32, width: f32) -> Vec<usize>;
-}
-
-impl<'a> Default for WindowOptions<'a> {
-    fn default() -> Self {
+impl ClipboardItem {
+    pub fn new(text: String) -> Self {
         Self {
-            bounds: WindowBounds::Maximized,
-            titlebar: Some(TitlebarOptions {
-                title: Default::default(),
-                appears_transparent: Default::default(),
-                traffic_light_position: Default::default(),
-            }),
-            center: false,
-            focus: true,
-            show: true,
-            kind: WindowKind::Normal,
-            is_movable: true,
-            screen: None,
+            text,
+            metadata: None,
         }
     }
+
+    pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
+        self.metadata = Some(serde_json::to_string(&metadata).unwrap());
+        self
+    }
+
+    pub fn text(&self) -> &String {
+        &self.text
+    }
+
+    pub fn metadata<T>(&self) -> Option<T>
+    where
+        T: for<'a> Deserialize<'a>,
+    {
+        self.metadata
+            .as_ref()
+            .and_then(|m| serde_json::from_str(m).ok())
+    }
+
+    pub(crate) fn text_hash(text: &str) -> u64 {
+        let mut hasher = SeaHasher::new();
+        text.hash(&mut hasher);
+        hasher.finish()
+    }
 }

crates/gpui/src/platform/event.rs 🔗

@@ -1,236 +0,0 @@
-use std::{any::Any, ops::Deref};
-
-use pathfinder_geometry::vector::vec2f;
-
-use crate::{geometry::vector::Vector2F, keymap_matcher::Keystroke};
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct KeyDownEvent {
-    pub keystroke: Keystroke,
-    pub is_held: bool,
-}
-
-#[derive(Clone, Debug)]
-pub struct KeyUpEvent {
-    pub keystroke: Keystroke,
-}
-
-#[derive(Clone, Copy, Debug, Default, PartialEq)]
-pub struct Modifiers {
-    pub ctrl: bool,
-    pub alt: bool,
-    pub shift: bool,
-    pub cmd: bool,
-    pub fun: bool,
-}
-
-#[derive(Clone, Copy, Debug, Default)]
-pub struct ModifiersChangedEvent {
-    pub modifiers: Modifiers,
-}
-
-impl Deref for ModifiersChangedEvent {
-    type Target = Modifiers;
-
-    fn deref(&self) -> &Self::Target {
-        &self.modifiers
-    }
-}
-
-/// The phase of a touch motion event.
-/// Based on the winit enum of the same name,
-#[derive(Clone, Copy, Debug)]
-pub enum TouchPhase {
-    Started,
-    Moved,
-    Ended,
-}
-
-#[derive(Clone, Copy, Debug)]
-pub enum ScrollDelta {
-    Pixels(Vector2F),
-    Lines(Vector2F),
-}
-
-impl Default for ScrollDelta {
-    fn default() -> Self {
-        Self::Lines(Default::default())
-    }
-}
-
-impl ScrollDelta {
-    pub fn raw(&self) -> &Vector2F {
-        match self {
-            ScrollDelta::Pixels(v) => v,
-            ScrollDelta::Lines(v) => v,
-        }
-    }
-
-    pub fn precise(&self) -> bool {
-        match self {
-            ScrollDelta::Pixels(_) => true,
-            ScrollDelta::Lines(_) => false,
-        }
-    }
-
-    pub fn pixel_delta(&self, line_height: f32) -> Vector2F {
-        match self {
-            ScrollDelta::Pixels(delta) => *delta,
-            ScrollDelta::Lines(delta) => vec2f(delta.x() * line_height, delta.y() * line_height),
-        }
-    }
-}
-
-#[derive(Clone, Copy, Debug, Default)]
-pub struct ScrollWheelEvent {
-    pub position: Vector2F,
-    pub delta: ScrollDelta,
-    pub modifiers: Modifiers,
-    /// If the platform supports returning the phase of a scroll wheel event, it will be stored here
-    pub phase: Option<TouchPhase>,
-}
-
-impl Deref for ScrollWheelEvent {
-    type Target = Modifiers;
-
-    fn deref(&self) -> &Self::Target {
-        &self.modifiers
-    }
-}
-
-#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
-pub enum NavigationDirection {
-    Back,
-    Forward,
-}
-
-impl Default for NavigationDirection {
-    fn default() -> Self {
-        Self::Back
-    }
-}
-
-#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
-pub enum MouseButton {
-    Left,
-    Right,
-    Middle,
-    Navigate(NavigationDirection),
-}
-
-impl MouseButton {
-    pub fn all() -> Vec<Self> {
-        vec![
-            MouseButton::Left,
-            MouseButton::Right,
-            MouseButton::Middle,
-            MouseButton::Navigate(NavigationDirection::Back),
-            MouseButton::Navigate(NavigationDirection::Forward),
-        ]
-    }
-}
-
-impl Default for MouseButton {
-    fn default() -> Self {
-        Self::Left
-    }
-}
-
-#[derive(Clone, Copy, Debug, Default)]
-pub struct MouseButtonEvent {
-    pub button: MouseButton,
-    pub position: Vector2F,
-    pub modifiers: Modifiers,
-    pub click_count: usize,
-    pub is_down: bool,
-}
-
-impl Deref for MouseButtonEvent {
-    type Target = Modifiers;
-
-    fn deref(&self) -> &Self::Target {
-        &self.modifiers
-    }
-}
-
-#[derive(Clone, Copy, Debug, Default)]
-pub struct MouseMovedEvent {
-    pub position: Vector2F,
-    pub pressed_button: Option<MouseButton>,
-    pub modifiers: Modifiers,
-}
-
-impl Deref for MouseMovedEvent {
-    type Target = Modifiers;
-
-    fn deref(&self) -> &Self::Target {
-        &self.modifiers
-    }
-}
-
-impl MouseMovedEvent {
-    pub fn to_button_event(&self, button: MouseButton) -> MouseButtonEvent {
-        MouseButtonEvent {
-            position: self.position,
-            button: self.pressed_button.unwrap_or(button),
-            modifiers: self.modifiers,
-            click_count: 0,
-            is_down: self.pressed_button.is_some(),
-        }
-    }
-}
-
-#[derive(Clone, Copy, Debug, Default)]
-pub struct MouseExitedEvent {
-    pub position: Vector2F,
-    pub pressed_button: Option<MouseButton>,
-    pub modifiers: Modifiers,
-}
-
-impl Deref for MouseExitedEvent {
-    type Target = Modifiers;
-
-    fn deref(&self) -> &Self::Target {
-        &self.modifiers
-    }
-}
-
-#[derive(Clone, Debug)]
-pub enum Event {
-    KeyDown(KeyDownEvent),
-    KeyUp(KeyUpEvent),
-    ModifiersChanged(ModifiersChangedEvent),
-    MouseDown(MouseButtonEvent),
-    MouseUp(MouseButtonEvent),
-    MouseMoved(MouseMovedEvent),
-    MouseExited(MouseExitedEvent),
-    ScrollWheel(ScrollWheelEvent),
-}
-
-impl Event {
-    pub fn position(&self) -> Option<Vector2F> {
-        match self {
-            Event::KeyDown { .. } => None,
-            Event::KeyUp { .. } => None,
-            Event::ModifiersChanged { .. } => None,
-            Event::MouseDown(event) => Some(event.position),
-            Event::MouseUp(event) => Some(event.position),
-            Event::MouseMoved(event) => Some(event.position),
-            Event::MouseExited(event) => Some(event.position),
-            Event::ScrollWheel(event) => Some(event.position),
-        }
-    }
-
-    pub fn mouse_event<'a>(&'a self) -> Option<&'a dyn Any> {
-        match self {
-            Event::KeyDown { .. } => None,
-            Event::KeyUp { .. } => None,
-            Event::ModifiersChanged { .. } => None,
-            Event::MouseDown(event) => Some(event),
-            Event::MouseUp(event) => Some(event),
-            Event::MouseMoved(event) => Some(event),
-            Event::MouseExited(event) => Some(event),
-            Event::ScrollWheel(event) => Some(event),
-        }
-    }
-}

crates/gpui/src/platform/mac.rs 🔗

@@ -1,39 +1,33 @@
-mod appearance;
-mod atlas;
+//! Macos screen have a y axis that goings up from the bottom of the screen and
+//! an origin at the bottom left of the main display.
 mod dispatcher;
-mod event;
-mod fonts;
-mod geometry;
-mod image_cache;
+mod display;
+mod display_linker;
+mod events;
+mod metal_atlas;
+mod metal_renderer;
+mod open_type;
 mod platform;
-mod renderer;
-mod screen;
-mod sprite_cache;
-mod status_item;
+mod text_system;
 mod window;
+mod window_appearence;
 
+use crate::{px, size, GlobalPixels, Pixels, Size};
 use cocoa::{
-    base::{id, nil, BOOL, NO, YES},
-    foundation::{NSAutoreleasePool, NSNotFound, NSString, NSUInteger},
+    base::{id, nil},
+    foundation::{NSAutoreleasePool, NSNotFound, NSRect, NSSize, NSString, NSUInteger},
 };
-pub use dispatcher::Dispatcher;
-pub use fonts::FontSystem;
-use platform::{MacForegroundPlatform, MacPlatform};
-pub use renderer::Surface;
-use std::{ops::Range, rc::Rc, sync::Arc};
-use window::MacWindow;
+use metal_renderer::*;
+use objc::runtime::{BOOL, NO, YES};
+use std::ops::Range;
 
-use crate::executor;
-
-pub fn platform() -> Arc<dyn super::Platform> {
-    Arc::new(MacPlatform::new())
-}
-
-pub(crate) fn foreground_platform(
-    foreground: Rc<executor::Foreground>,
-) -> Rc<dyn super::ForegroundPlatform> {
-    Rc::new(MacForegroundPlatform::new(foreground))
-}
+pub use dispatcher::*;
+pub use display::*;
+pub use display_linker::*;
+pub use metal_atlas::*;
+pub use platform::*;
+pub use text_system::*;
+pub use window::*;
 
 trait BoolExt {
     fn to_objc(self) -> BOOL;
@@ -102,3 +96,44 @@ unsafe impl objc::Encode for NSRange {
 unsafe fn ns_string(string: &str) -> id {
     NSString::alloc(nil).init_str(string).autorelease()
 }
+
+impl From<NSSize> for Size<Pixels> {
+    fn from(value: NSSize) -> Self {
+        Size {
+            width: px(value.width as f32),
+            height: px(value.height as f32),
+        }
+    }
+}
+
+pub trait NSRectExt {
+    fn size(&self) -> Size<Pixels>;
+    fn intersects(&self, other: Self) -> bool;
+}
+
+impl From<NSRect> for Size<Pixels> {
+    fn from(rect: NSRect) -> Self {
+        let NSSize { width, height } = rect.size;
+        size(width.into(), height.into())
+    }
+}
+
+impl From<NSRect> for Size<GlobalPixels> {
+    fn from(rect: NSRect) -> Self {
+        let NSSize { width, height } = rect.size;
+        size(width.into(), height.into())
+    }
+}
+
+// impl NSRectExt for NSRect {
+//     fn intersects(&self, other: Self) -> bool {
+//         self.size.width > 0.
+//             && self.size.height > 0.
+//             && other.size.width > 0.
+//             && other.size.height > 0.
+//             && self.origin.x <= other.origin.x + other.size.width
+//             && self.origin.x + self.size.width >= other.origin.x
+//             && self.origin.y <= other.origin.y + other.size.height
+//             && self.origin.y + self.size.height >= other.origin.y
+//     }
+// }

crates/gpui/src/platform/mac/appearance.rs 🔗

@@ -1,36 +0,0 @@
-use std::ffi::CStr;
-
-use crate::platform::Appearance;
-use cocoa::{
-    appkit::{NSAppearanceNameVibrantDark, NSAppearanceNameVibrantLight},
-    base::id,
-    foundation::NSString,
-};
-use objc::{msg_send, sel, sel_impl};
-
-impl Appearance {
-    pub unsafe fn from_native(appearance: id) -> Self {
-        let name: id = msg_send![appearance, name];
-        if name == NSAppearanceNameVibrantLight {
-            Self::VibrantLight
-        } else if name == NSAppearanceNameVibrantDark {
-            Self::VibrantDark
-        } else if name == NSAppearanceNameAqua {
-            Self::Light
-        } else if name == NSAppearanceNameDarkAqua {
-            Self::Dark
-        } else {
-            println!(
-                "unknown appearance: {:?}",
-                CStr::from_ptr(name.UTF8String())
-            );
-            Self::Light
-        }
-    }
-}
-
-#[link(name = "AppKit", kind = "framework")]
-extern "C" {
-    pub static NSAppearanceNameAqua: id;
-    pub static NSAppearanceNameDarkAqua: id;
-}

crates/gpui/src/platform/mac/atlas.rs 🔗

@@ -1,173 +0,0 @@
-use crate::geometry::{
-    rect::RectI,
-    vector::{vec2i, Vector2I},
-};
-use etagere::BucketedAtlasAllocator;
-use foreign_types::ForeignType;
-use log::warn;
-use metal::{Device, TextureDescriptor};
-use objc::{msg_send, sel, sel_impl};
-
-pub struct AtlasAllocator {
-    device: Device,
-    texture_descriptor: TextureDescriptor,
-    atlases: Vec<Atlas>,
-    last_used_atlas_id: usize,
-}
-
-#[derive(Copy, Clone, Debug)]
-pub struct AllocId {
-    pub atlas_id: usize,
-    alloc_id: etagere::AllocId,
-}
-
-impl AtlasAllocator {
-    pub fn new(device: Device, texture_descriptor: TextureDescriptor) -> Self {
-        let mut this = Self {
-            device,
-            texture_descriptor,
-            atlases: vec![],
-            last_used_atlas_id: 0,
-        };
-        let atlas = this.new_atlas(Vector2I::zero());
-        this.atlases.push(atlas);
-        this
-    }
-
-    pub fn default_atlas_size(&self) -> Vector2I {
-        vec2i(
-            self.texture_descriptor.width() as i32,
-            self.texture_descriptor.height() as i32,
-        )
-    }
-
-    pub fn allocate(&mut self, requested_size: Vector2I) -> Option<(AllocId, Vector2I)> {
-        let atlas_id = self.last_used_atlas_id;
-        if let Some((alloc_id, origin)) = self.atlases[atlas_id].allocate(requested_size) {
-            return Some((AllocId { atlas_id, alloc_id }, origin));
-        }
-
-        for (atlas_id, atlas) in self.atlases.iter_mut().enumerate() {
-            if atlas_id == self.last_used_atlas_id {
-                continue;
-            }
-            if let Some((alloc_id, origin)) = atlas.allocate(requested_size) {
-                self.last_used_atlas_id = atlas_id;
-                return Some((AllocId { atlas_id, alloc_id }, origin));
-            }
-        }
-
-        let atlas_id = self.atlases.len();
-        let mut atlas = self.new_atlas(requested_size);
-        let allocation = atlas
-            .allocate(requested_size)
-            .map(|(alloc_id, origin)| (AllocId { atlas_id, alloc_id }, origin));
-        self.atlases.push(atlas);
-
-        if allocation.is_none() {
-            warn!(
-                "allocation of size {:?} could not be created",
-                requested_size,
-            );
-        }
-
-        allocation
-    }
-
-    pub fn upload(&mut self, size: Vector2I, bytes: &[u8]) -> Option<(AllocId, RectI)> {
-        let (alloc_id, origin) = self.allocate(size)?;
-        let bounds = RectI::new(origin, size);
-        self.atlases[alloc_id.atlas_id].upload(bounds, bytes);
-        Some((alloc_id, bounds))
-    }
-
-    pub fn deallocate(&mut self, id: AllocId) {
-        if let Some(atlas) = self.atlases.get_mut(id.atlas_id) {
-            atlas.deallocate(id.alloc_id);
-        }
-    }
-
-    pub fn clear(&mut self) {
-        for atlas in &mut self.atlases {
-            atlas.clear();
-        }
-    }
-
-    pub fn texture(&self, atlas_id: usize) -> Option<&metal::TextureRef> {
-        self.atlases.get(atlas_id).map(|a| a.texture.as_ref())
-    }
-
-    fn new_atlas(&mut self, required_size: Vector2I) -> Atlas {
-        let size = self.default_atlas_size().max(required_size);
-        let texture = if size.x() as u64 > self.texture_descriptor.width()
-            || size.y() as u64 > self.texture_descriptor.height()
-        {
-            let descriptor = unsafe {
-                let descriptor_ptr: *mut metal::MTLTextureDescriptor =
-                    msg_send![self.texture_descriptor, copy];
-                metal::TextureDescriptor::from_ptr(descriptor_ptr)
-            };
-            descriptor.set_width(size.x() as u64);
-            descriptor.set_height(size.y() as u64);
-
-            self.device.new_texture(&descriptor)
-        } else {
-            self.device.new_texture(&self.texture_descriptor)
-        };
-        Atlas::new(size, texture)
-    }
-}
-
-struct Atlas {
-    allocator: BucketedAtlasAllocator,
-    texture: metal::Texture,
-}
-
-impl Atlas {
-    fn new(size: Vector2I, texture: metal::Texture) -> Self {
-        Self {
-            allocator: BucketedAtlasAllocator::new(etagere::Size::new(size.x(), size.y())),
-            texture,
-        }
-    }
-
-    fn allocate(&mut self, size: Vector2I) -> Option<(etagere::AllocId, Vector2I)> {
-        let alloc = self
-            .allocator
-            .allocate(etagere::Size::new(size.x(), size.y()))?;
-        let origin = alloc.rectangle.min;
-        Some((alloc.id, vec2i(origin.x, origin.y)))
-    }
-
-    fn upload(&mut self, bounds: RectI, bytes: &[u8]) {
-        let region = metal::MTLRegion::new_2d(
-            bounds.origin().x() as u64,
-            bounds.origin().y() as u64,
-            bounds.size().x() as u64,
-            bounds.size().y() as u64,
-        );
-        self.texture.replace_region(
-            region,
-            0,
-            bytes.as_ptr() as *const _,
-            (bounds.size().x() * self.bytes_per_pixel() as i32) as u64,
-        );
-    }
-
-    fn bytes_per_pixel(&self) -> u8 {
-        use metal::MTLPixelFormat::*;
-        match self.texture.pixel_format() {
-            A8Unorm | R8Unorm => 1,
-            RGBA8Unorm | BGRA8Unorm => 4,
-            _ => unimplemented!(),
-        }
-    }
-
-    fn deallocate(&mut self, id: etagere::AllocId) {
-        self.allocator.deallocate(id);
-    }
-
-    fn clear(&mut self) {
-        self.allocator.clear();
-    }
-}

crates/gpui/src/platform/mac/dispatcher.rs 🔗

@@ -2,15 +2,16 @@
 #![allow(non_camel_case_types)]
 #![allow(non_snake_case)]
 
+use crate::{PlatformDispatcher, TaskLabel};
 use async_task::Runnable;
 use objc::{
     class, msg_send,
     runtime::{BOOL, YES},
     sel, sel_impl,
 };
-use std::ffi::c_void;
-
-use crate::platform;
+use parking::{Parker, Unparker};
+use parking_lot::Mutex;
+use std::{ffi::c_void, sync::Arc, time::Duration};
 
 include!(concat!(env!("OUT_DIR"), "/dispatch_sys.rs"));
 
@@ -18,15 +19,41 @@ pub fn dispatch_get_main_queue() -> dispatch_queue_t {
     unsafe { &_dispatch_main_q as *const _ as dispatch_queue_t }
 }
 
-pub struct Dispatcher;
+pub struct MacDispatcher {
+    parker: Arc<Mutex<Parker>>,
+}
+
+impl Default for MacDispatcher {
+    fn default() -> Self {
+        Self::new()
+    }
+}
 
-impl platform::Dispatcher for Dispatcher {
+impl MacDispatcher {
+    pub fn new() -> Self {
+        MacDispatcher {
+            parker: Arc::new(Mutex::new(Parker::new())),
+        }
+    }
+}
+
+impl PlatformDispatcher for MacDispatcher {
     fn is_main_thread(&self) -> bool {
         let is_main_thread: BOOL = unsafe { msg_send![class!(NSThread), isMainThread] };
         is_main_thread == YES
     }
 
-    fn run_on_main_thread(&self, runnable: Runnable) {
+    fn dispatch(&self, runnable: Runnable, _: Option<TaskLabel>) {
+        unsafe {
+            dispatch_async_f(
+                dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0),
+                runnable.into_raw() as *mut c_void,
+                Some(trampoline),
+            );
+        }
+    }
+
+    fn dispatch_on_main_thread(&self, runnable: Runnable) {
         unsafe {
             dispatch_async_f(
                 dispatch_get_main_queue(),
@@ -34,10 +61,36 @@ impl platform::Dispatcher for Dispatcher {
                 Some(trampoline),
             );
         }
+    }
 
-        extern "C" fn trampoline(runnable: *mut c_void) {
-            let task = unsafe { Runnable::from_raw(runnable as *mut ()) };
-            task.run();
+    fn dispatch_after(&self, duration: Duration, runnable: Runnable) {
+        unsafe {
+            let queue =
+                dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0);
+            let when = dispatch_time(DISPATCH_TIME_NOW as u64, duration.as_nanos() as i64);
+            dispatch_after_f(
+                when,
+                queue,
+                runnable.into_raw() as *mut c_void,
+                Some(trampoline),
+            );
         }
     }
+
+    fn tick(&self, _background_only: bool) -> bool {
+        false
+    }
+
+    fn park(&self) {
+        self.parker.lock().park()
+    }
+
+    fn unparker(&self) -> Unparker {
+        self.parker.lock().unparker()
+    }
+}
+
+extern "C" fn trampoline(runnable: *mut c_void) {
+    let task = unsafe { Runnable::from_raw(runnable as *mut ()) };
+    task.run();
 }

crates/gpui/src/platform/mac/event.rs 🔗

@@ -1,359 +0,0 @@
-use crate::{
-    geometry::vector::vec2f,
-    keymap_matcher::Keystroke,
-    platform::{
-        Event, KeyDownEvent, KeyUpEvent, Modifiers, ModifiersChangedEvent, MouseButton,
-        MouseButtonEvent, MouseExitedEvent, MouseMovedEvent, NavigationDirection, ScrollDelta,
-        ScrollWheelEvent, TouchPhase,
-    },
-};
-use cocoa::{
-    appkit::{NSEvent, NSEventModifierFlags, NSEventPhase, NSEventType},
-    base::{id, YES},
-    foundation::NSString as _,
-};
-use core_graphics::{
-    event::{CGEvent, CGEventFlags, CGKeyCode},
-    event_source::{CGEventSource, CGEventSourceStateID},
-};
-use ctor::ctor;
-use foreign_types::ForeignType;
-use objc::{class, msg_send, sel, sel_impl};
-use std::{borrow::Cow, ffi::CStr, mem, os::raw::c_char, ptr};
-
-const BACKSPACE_KEY: u16 = 0x7f;
-const SPACE_KEY: u16 = b' ' as u16;
-const ENTER_KEY: u16 = 0x0d;
-const NUMPAD_ENTER_KEY: u16 = 0x03;
-const ESCAPE_KEY: u16 = 0x1b;
-const TAB_KEY: u16 = 0x09;
-const SHIFT_TAB_KEY: u16 = 0x19;
-
-static mut EVENT_SOURCE: core_graphics::sys::CGEventSourceRef = ptr::null_mut();
-
-#[ctor]
-unsafe fn build_event_source() {
-    let source = CGEventSource::new(CGEventSourceStateID::Private).unwrap();
-    EVENT_SOURCE = source.as_ptr();
-    mem::forget(source);
-}
-
-pub fn key_to_native(key: &str) -> Cow<str> {
-    use cocoa::appkit::*;
-    let code = match key {
-        "space" => SPACE_KEY,
-        "backspace" => BACKSPACE_KEY,
-        "up" => NSUpArrowFunctionKey,
-        "down" => NSDownArrowFunctionKey,
-        "left" => NSLeftArrowFunctionKey,
-        "right" => NSRightArrowFunctionKey,
-        "pageup" => NSPageUpFunctionKey,
-        "pagedown" => NSPageDownFunctionKey,
-        "home" => NSHomeFunctionKey,
-        "end" => NSEndFunctionKey,
-        "delete" => NSDeleteFunctionKey,
-        "f1" => NSF1FunctionKey,
-        "f2" => NSF2FunctionKey,
-        "f3" => NSF3FunctionKey,
-        "f4" => NSF4FunctionKey,
-        "f5" => NSF5FunctionKey,
-        "f6" => NSF6FunctionKey,
-        "f7" => NSF7FunctionKey,
-        "f8" => NSF8FunctionKey,
-        "f9" => NSF9FunctionKey,
-        "f10" => NSF10FunctionKey,
-        "f11" => NSF11FunctionKey,
-        "f12" => NSF12FunctionKey,
-        _ => return Cow::Borrowed(key),
-    };
-    Cow::Owned(String::from_utf16(&[code]).unwrap())
-}
-
-unsafe fn read_modifiers(native_event: id) -> Modifiers {
-    let modifiers = native_event.modifierFlags();
-    let ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
-    let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
-    let shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
-    let cmd = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
-    let fun = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask);
-
-    Modifiers {
-        ctrl,
-        alt,
-        shift,
-        cmd,
-        fun,
-    }
-}
-
-impl Event {
-    pub unsafe fn from_native(native_event: id, window_height: Option<f32>) -> Option<Self> {
-        let event_type = native_event.eventType();
-
-        // Filter out event types that aren't in the NSEventType enum.
-        // See https://github.com/servo/cocoa-rs/issues/155#issuecomment-323482792 for details.
-        match event_type as u64 {
-            0 | 21 | 32 | 33 | 35 | 36 | 37 => {
-                return None;
-            }
-            _ => {}
-        }
-
-        match event_type {
-            NSEventType::NSFlagsChanged => Some(Self::ModifiersChanged(ModifiersChangedEvent {
-                modifiers: read_modifiers(native_event),
-            })),
-            NSEventType::NSKeyDown => Some(Self::KeyDown(KeyDownEvent {
-                keystroke: parse_keystroke(native_event),
-                is_held: native_event.isARepeat() == YES,
-            })),
-            NSEventType::NSKeyUp => Some(Self::KeyUp(KeyUpEvent {
-                keystroke: parse_keystroke(native_event),
-            })),
-            NSEventType::NSLeftMouseDown
-            | NSEventType::NSRightMouseDown
-            | NSEventType::NSOtherMouseDown => {
-                let button = match native_event.buttonNumber() {
-                    0 => MouseButton::Left,
-                    1 => MouseButton::Right,
-                    2 => MouseButton::Middle,
-                    3 => MouseButton::Navigate(NavigationDirection::Back),
-                    4 => MouseButton::Navigate(NavigationDirection::Forward),
-                    // Other mouse buttons aren't tracked currently
-                    _ => return None,
-                };
-                window_height.map(|window_height| {
-                    Self::MouseDown(MouseButtonEvent {
-                        button,
-                        position: vec2f(
-                            native_event.locationInWindow().x as f32,
-                            // MacOS screen coordinates are relative to bottom left
-                            window_height - native_event.locationInWindow().y as f32,
-                        ),
-                        modifiers: read_modifiers(native_event),
-                        click_count: native_event.clickCount() as usize,
-                        is_down: true,
-                    })
-                })
-            }
-            NSEventType::NSLeftMouseUp
-            | NSEventType::NSRightMouseUp
-            | NSEventType::NSOtherMouseUp => {
-                let button = match native_event.buttonNumber() {
-                    0 => MouseButton::Left,
-                    1 => MouseButton::Right,
-                    2 => MouseButton::Middle,
-                    3 => MouseButton::Navigate(NavigationDirection::Back),
-                    4 => MouseButton::Navigate(NavigationDirection::Forward),
-                    // Other mouse buttons aren't tracked currently
-                    _ => return None,
-                };
-
-                window_height.map(|window_height| {
-                    Self::MouseUp(MouseButtonEvent {
-                        button,
-                        position: vec2f(
-                            native_event.locationInWindow().x as f32,
-                            // MacOS view coordinates are relative to bottom left
-                            window_height - native_event.locationInWindow().y as f32,
-                        ),
-                        modifiers: read_modifiers(native_event),
-                        click_count: native_event.clickCount() as usize,
-                        is_down: false,
-                    })
-                })
-            }
-            NSEventType::NSScrollWheel => window_height.map(|window_height| {
-                let phase = match native_event.phase() {
-                    NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => {
-                        Some(TouchPhase::Started)
-                    }
-                    NSEventPhase::NSEventPhaseEnded => Some(TouchPhase::Ended),
-                    _ => Some(TouchPhase::Moved),
-                };
-
-                let raw_data = vec2f(
-                    native_event.scrollingDeltaX() as f32,
-                    native_event.scrollingDeltaY() as f32,
-                );
-
-                let delta = if native_event.hasPreciseScrollingDeltas() == YES {
-                    ScrollDelta::Pixels(raw_data)
-                } else {
-                    ScrollDelta::Lines(raw_data)
-                };
-
-                Self::ScrollWheel(ScrollWheelEvent {
-                    position: vec2f(
-                        native_event.locationInWindow().x as f32,
-                        window_height - native_event.locationInWindow().y as f32,
-                    ),
-                    delta,
-                    phase,
-                    modifiers: read_modifiers(native_event),
-                })
-            }),
-            NSEventType::NSLeftMouseDragged
-            | NSEventType::NSRightMouseDragged
-            | NSEventType::NSOtherMouseDragged => {
-                let pressed_button = match native_event.buttonNumber() {
-                    0 => MouseButton::Left,
-                    1 => MouseButton::Right,
-                    2 => MouseButton::Middle,
-                    3 => MouseButton::Navigate(NavigationDirection::Back),
-                    4 => MouseButton::Navigate(NavigationDirection::Forward),
-                    // Other mouse buttons aren't tracked currently
-                    _ => return None,
-                };
-
-                window_height.map(|window_height| {
-                    Self::MouseMoved(MouseMovedEvent {
-                        pressed_button: Some(pressed_button),
-                        position: vec2f(
-                            native_event.locationInWindow().x as f32,
-                            window_height - native_event.locationInWindow().y as f32,
-                        ),
-                        modifiers: read_modifiers(native_event),
-                    })
-                })
-            }
-            NSEventType::NSMouseMoved => window_height.map(|window_height| {
-                Self::MouseMoved(MouseMovedEvent {
-                    position: vec2f(
-                        native_event.locationInWindow().x as f32,
-                        window_height - native_event.locationInWindow().y as f32,
-                    ),
-                    pressed_button: None,
-                    modifiers: read_modifiers(native_event),
-                })
-            }),
-            NSEventType::NSMouseExited => window_height.map(|window_height| {
-                Self::MouseExited(MouseExitedEvent {
-                    position: vec2f(
-                        native_event.locationInWindow().x as f32,
-                        window_height - native_event.locationInWindow().y as f32,
-                    ),
-                    pressed_button: None,
-                    modifiers: read_modifiers(native_event),
-                })
-            }),
-            _ => None,
-        }
-    }
-}
-
-unsafe fn parse_keystroke(native_event: id) -> Keystroke {
-    use cocoa::appkit::*;
-
-    let mut chars_ignoring_modifiers =
-        CStr::from_ptr(native_event.charactersIgnoringModifiers().UTF8String() as *mut c_char)
-            .to_str()
-            .unwrap()
-            .to_string();
-    let first_char = chars_ignoring_modifiers.chars().next().map(|ch| ch as u16);
-    let modifiers = native_event.modifierFlags();
-
-    let ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
-    let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
-    let mut shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
-    let cmd = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
-    let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask)
-        && first_char.map_or(true, |ch| {
-            !(NSUpArrowFunctionKey..=NSModeSwitchFunctionKey).contains(&ch)
-        });
-
-    #[allow(non_upper_case_globals)]
-    let key = match first_char {
-        Some(SPACE_KEY) => "space".to_string(),
-        Some(BACKSPACE_KEY) => "backspace".to_string(),
-        Some(ENTER_KEY) | Some(NUMPAD_ENTER_KEY) => "enter".to_string(),
-        Some(ESCAPE_KEY) => "escape".to_string(),
-        Some(TAB_KEY) => "tab".to_string(),
-        Some(SHIFT_TAB_KEY) => "tab".to_string(),
-        Some(NSUpArrowFunctionKey) => "up".to_string(),
-        Some(NSDownArrowFunctionKey) => "down".to_string(),
-        Some(NSLeftArrowFunctionKey) => "left".to_string(),
-        Some(NSRightArrowFunctionKey) => "right".to_string(),
-        Some(NSPageUpFunctionKey) => "pageup".to_string(),
-        Some(NSPageDownFunctionKey) => "pagedown".to_string(),
-        Some(NSHomeFunctionKey) => "home".to_string(),
-        Some(NSEndFunctionKey) => "end".to_string(),
-        Some(NSDeleteFunctionKey) => "delete".to_string(),
-        Some(NSF1FunctionKey) => "f1".to_string(),
-        Some(NSF2FunctionKey) => "f2".to_string(),
-        Some(NSF3FunctionKey) => "f3".to_string(),
-        Some(NSF4FunctionKey) => "f4".to_string(),
-        Some(NSF5FunctionKey) => "f5".to_string(),
-        Some(NSF6FunctionKey) => "f6".to_string(),
-        Some(NSF7FunctionKey) => "f7".to_string(),
-        Some(NSF8FunctionKey) => "f8".to_string(),
-        Some(NSF9FunctionKey) => "f9".to_string(),
-        Some(NSF10FunctionKey) => "f10".to_string(),
-        Some(NSF11FunctionKey) => "f11".to_string(),
-        Some(NSF12FunctionKey) => "f12".to_string(),
-        _ => {
-            let mut chars_ignoring_modifiers_and_shift =
-                chars_for_modified_key(native_event.keyCode(), false, false);
-
-            // Honor ⌘ when Dvorak-QWERTY is used.
-            let chars_with_cmd = chars_for_modified_key(native_event.keyCode(), true, false);
-            if cmd && chars_ignoring_modifiers_and_shift != chars_with_cmd {
-                chars_ignoring_modifiers =
-                    chars_for_modified_key(native_event.keyCode(), true, shift);
-                chars_ignoring_modifiers_and_shift = chars_with_cmd;
-            }
-
-            if shift {
-                if chars_ignoring_modifiers_and_shift
-                    == chars_ignoring_modifiers.to_ascii_lowercase()
-                {
-                    chars_ignoring_modifiers_and_shift
-                } else if chars_ignoring_modifiers_and_shift != chars_ignoring_modifiers {
-                    shift = false;
-                    chars_ignoring_modifiers
-                } else {
-                    chars_ignoring_modifiers
-                }
-            } else {
-                chars_ignoring_modifiers
-            }
-        }
-    };
-
-    Keystroke {
-        ctrl,
-        alt,
-        shift,
-        cmd,
-        function,
-        key,
-        ime_key: None,
-    }
-}
-
-fn chars_for_modified_key(code: CGKeyCode, cmd: bool, shift: bool) -> String {
-    // Ideally, we would use `[NSEvent charactersByApplyingModifiers]` but that
-    // always returns an empty string with certain keyboards, e.g. Japanese. Synthesizing
-    // an event with the given flags instead lets us access `characters`, which always
-    // returns a valid string.
-    let source = unsafe { core_graphics::event_source::CGEventSource::from_ptr(EVENT_SOURCE) };
-    let event = CGEvent::new_keyboard_event(source.clone(), code, true).unwrap();
-    mem::forget(source);
-
-    let mut flags = CGEventFlags::empty();
-    if cmd {
-        flags |= CGEventFlags::CGEventFlagCommand;
-    }
-    if shift {
-        flags |= CGEventFlags::CGEventFlagShift;
-    }
-    event.set_flags(flags);
-
-    unsafe {
-        let event: id = msg_send![class!(NSEvent), eventWithCGEvent: &*event];
-        CStr::from_ptr(event.characters().UTF8String())
-            .to_str()
-            .unwrap()
-            .to_string()
-    }
-}

crates/gpui/src/platform/mac/fonts.rs 🔗

@@ -1,671 +0,0 @@
-mod open_type;
-
-use crate::{
-    fonts::{Features, FontId, GlyphId, Metrics, Properties},
-    geometry::{
-        rect::{RectF, RectI},
-        transform2d::Transform2F,
-        vector::{vec2f, Vector2F},
-    },
-    platform::{self, RasterizationOptions},
-    text_layout::{Glyph, LineLayout, Run, RunStyle},
-};
-use cocoa::appkit::{CGFloat, CGPoint};
-use collections::HashMap;
-use core_foundation::{
-    array::CFIndex,
-    attributed_string::{CFAttributedStringRef, CFMutableAttributedString},
-    base::{CFRange, TCFType},
-    string::CFString,
-};
-use core_graphics::{
-    base::{kCGImageAlphaPremultipliedLast, CGGlyph},
-    color_space::CGColorSpace,
-    context::CGContext,
-};
-use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName};
-use font_kit::{
-    handle::Handle, hinting::HintingOptions, source::SystemSource, sources::mem::MemSource,
-};
-use parking_lot::RwLock;
-use std::{cell::RefCell, char, cmp, convert::TryFrom, ffi::c_void, sync::Arc};
-
-#[allow(non_upper_case_globals)]
-const kCGImageAlphaOnly: u32 = 7;
-
-pub struct FontSystem(RwLock<FontSystemState>);
-
-struct FontSystemState {
-    memory_source: MemSource,
-    system_source: SystemSource,
-    fonts: Vec<font_kit::font::Font>,
-    font_ids_by_postscript_name: HashMap<String, FontId>,
-    postscript_names_by_font_id: HashMap<FontId, String>,
-}
-
-impl FontSystem {
-    pub fn new() -> Self {
-        Self(RwLock::new(FontSystemState {
-            memory_source: MemSource::empty(),
-            system_source: SystemSource::new(),
-            fonts: Vec::new(),
-            font_ids_by_postscript_name: Default::default(),
-            postscript_names_by_font_id: Default::default(),
-        }))
-    }
-}
-
-impl Default for FontSystem {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
-impl platform::FontSystem for FontSystem {
-    fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()> {
-        self.0.write().add_fonts(fonts)
-    }
-
-    fn all_families(&self) -> Vec<String> {
-        self.0
-            .read()
-            .system_source
-            .all_families()
-            .expect("core text should never return an error")
-    }
-
-    fn load_family(&self, name: &str, features: &Features) -> anyhow::Result<Vec<FontId>> {
-        self.0.write().load_family(name, features)
-    }
-
-    fn select_font(&self, font_ids: &[FontId], properties: &Properties) -> anyhow::Result<FontId> {
-        self.0.read().select_font(font_ids, properties)
-    }
-
-    fn font_metrics(&self, font_id: FontId) -> Metrics {
-        self.0.read().font_metrics(font_id)
-    }
-
-    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<RectF> {
-        self.0.read().typographic_bounds(font_id, glyph_id)
-    }
-
-    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Vector2F> {
-        self.0.read().advance(font_id, glyph_id)
-    }
-
-    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
-        self.0.read().glyph_for_char(font_id, ch)
-    }
-
-    fn rasterize_glyph(
-        &self,
-        font_id: FontId,
-        font_size: f32,
-        glyph_id: GlyphId,
-        subpixel_shift: Vector2F,
-        scale_factor: f32,
-        options: RasterizationOptions,
-    ) -> Option<(RectI, Vec<u8>)> {
-        self.0.read().rasterize_glyph(
-            font_id,
-            font_size,
-            glyph_id,
-            subpixel_shift,
-            scale_factor,
-            options,
-        )
-    }
-
-    fn layout_line(&self, text: &str, font_size: f32, runs: &[(usize, RunStyle)]) -> LineLayout {
-        self.0.write().layout_line(text, font_size, runs)
-    }
-
-    fn wrap_line(&self, text: &str, font_id: FontId, font_size: f32, width: f32) -> Vec<usize> {
-        self.0.read().wrap_line(text, font_id, font_size, width)
-    }
-}
-
-impl FontSystemState {
-    fn add_fonts(&mut self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()> {
-        self.memory_source.add_fonts(
-            fonts
-                .iter()
-                .map(|bytes| Handle::from_memory(bytes.clone(), 0)),
-        )?;
-        Ok(())
-    }
-
-    fn load_family(&mut self, name: &str, features: &Features) -> anyhow::Result<Vec<FontId>> {
-        let mut font_ids = Vec::new();
-
-        let family = self
-            .memory_source
-            .select_family_by_name(name)
-            .or_else(|_| self.system_source.select_family_by_name(name))?;
-        for font in family.fonts() {
-            let mut font = font.load()?;
-            open_type::apply_features(&mut font, features);
-            let font_id = FontId(self.fonts.len());
-            font_ids.push(font_id);
-            let postscript_name = font.postscript_name().unwrap();
-            self.font_ids_by_postscript_name
-                .insert(postscript_name.clone(), font_id);
-            self.postscript_names_by_font_id
-                .insert(font_id, postscript_name);
-            self.fonts.push(font);
-        }
-        Ok(font_ids)
-    }
-
-    fn select_font(&self, font_ids: &[FontId], properties: &Properties) -> anyhow::Result<FontId> {
-        let candidates = font_ids
-            .iter()
-            .map(|font_id| self.fonts[font_id.0].properties())
-            .collect::<Vec<_>>();
-        let idx = font_kit::matching::find_best_match(&candidates, properties)?;
-        Ok(font_ids[idx])
-    }
-
-    fn font_metrics(&self, font_id: FontId) -> Metrics {
-        self.fonts[font_id.0].metrics()
-    }
-
-    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<RectF> {
-        Ok(self.fonts[font_id.0].typographic_bounds(glyph_id)?)
-    }
-
-    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Vector2F> {
-        Ok(self.fonts[font_id.0].advance(glyph_id)?)
-    }
-
-    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
-        self.fonts[font_id.0].glyph_for_char(ch)
-    }
-
-    fn id_for_native_font(&mut self, requested_font: CTFont) -> FontId {
-        let postscript_name = requested_font.postscript_name();
-        if let Some(font_id) = self.font_ids_by_postscript_name.get(&postscript_name) {
-            *font_id
-        } else {
-            let font_id = FontId(self.fonts.len());
-            self.font_ids_by_postscript_name
-                .insert(postscript_name.clone(), font_id);
-            self.postscript_names_by_font_id
-                .insert(font_id, postscript_name);
-            self.fonts
-                .push(font_kit::font::Font::from_core_graphics_font(
-                    requested_font.copy_to_CGFont(),
-                ));
-            font_id
-        }
-    }
-
-    fn is_emoji(&self, font_id: FontId) -> bool {
-        self.postscript_names_by_font_id
-            .get(&font_id)
-            .map_or(false, |postscript_name| {
-                postscript_name == "AppleColorEmoji"
-            })
-    }
-
-    fn rasterize_glyph(
-        &self,
-        font_id: FontId,
-        font_size: f32,
-        glyph_id: GlyphId,
-        subpixel_shift: Vector2F,
-        scale_factor: f32,
-        options: RasterizationOptions,
-    ) -> Option<(RectI, Vec<u8>)> {
-        let font = &self.fonts[font_id.0];
-        let scale = Transform2F::from_scale(scale_factor);
-        let glyph_bounds = font
-            .raster_bounds(
-                glyph_id,
-                font_size,
-                scale,
-                HintingOptions::None,
-                font_kit::canvas::RasterizationOptions::GrayscaleAa,
-            )
-            .ok()?;
-
-        if glyph_bounds.width() == 0 || glyph_bounds.height() == 0 {
-            None
-        } else {
-            // Make room for subpixel variants.
-            let subpixel_padding = subpixel_shift.ceil().to_i32();
-            let cx_bounds = RectI::new(
-                glyph_bounds.origin(),
-                glyph_bounds.size() + subpixel_padding,
-            );
-
-            let mut bytes;
-            let cx;
-            match options {
-                RasterizationOptions::Alpha => {
-                    bytes = vec![0; cx_bounds.width() as usize * cx_bounds.height() as usize];
-                    cx = CGContext::create_bitmap_context(
-                        Some(bytes.as_mut_ptr() as *mut _),
-                        cx_bounds.width() as usize,
-                        cx_bounds.height() as usize,
-                        8,
-                        cx_bounds.width() as usize,
-                        &CGColorSpace::create_device_gray(),
-                        kCGImageAlphaOnly,
-                    );
-                }
-                RasterizationOptions::Bgra => {
-                    bytes = vec![0; cx_bounds.width() as usize * 4 * cx_bounds.height() as usize];
-                    cx = CGContext::create_bitmap_context(
-                        Some(bytes.as_mut_ptr() as *mut _),
-                        cx_bounds.width() as usize,
-                        cx_bounds.height() as usize,
-                        8,
-                        cx_bounds.width() as usize * 4,
-                        &CGColorSpace::create_device_rgb(),
-                        kCGImageAlphaPremultipliedLast,
-                    );
-                }
-            }
-
-            // Move the origin to bottom left and account for scaling, this
-            // makes drawing text consistent with the font-kit's raster_bounds.
-            cx.translate(
-                -glyph_bounds.origin_x() as CGFloat,
-                (glyph_bounds.origin_y() + glyph_bounds.height()) as CGFloat,
-            );
-            cx.scale(scale_factor as CGFloat, scale_factor as CGFloat);
-
-            cx.set_allows_font_subpixel_positioning(true);
-            cx.set_should_subpixel_position_fonts(true);
-            cx.set_allows_font_subpixel_quantization(false);
-            cx.set_should_subpixel_quantize_fonts(false);
-            font.native_font()
-                .clone_with_font_size(font_size as CGFloat)
-                .draw_glyphs(
-                    &[glyph_id as CGGlyph],
-                    &[CGPoint::new(
-                        (subpixel_shift.x() / scale_factor) as CGFloat,
-                        (subpixel_shift.y() / scale_factor) as CGFloat,
-                    )],
-                    cx,
-                );
-
-            if let RasterizationOptions::Bgra = options {
-                // Convert from RGBA with premultiplied alpha to BGRA with straight alpha.
-                for pixel in bytes.chunks_exact_mut(4) {
-                    pixel.swap(0, 2);
-                    let a = pixel[3] as f32 / 255.;
-                    pixel[0] = (pixel[0] as f32 / a) as u8;
-                    pixel[1] = (pixel[1] as f32 / a) as u8;
-                    pixel[2] = (pixel[2] as f32 / a) as u8;
-                }
-            }
-
-            Some((cx_bounds, bytes))
-        }
-    }
-
-    fn layout_line(
-        &mut self,
-        text: &str,
-        font_size: f32,
-        runs: &[(usize, RunStyle)],
-    ) -> LineLayout {
-        // Construct the attributed string, converting UTF8 ranges to UTF16 ranges.
-        let mut string = CFMutableAttributedString::new();
-        {
-            string.replace_str(&CFString::new(text), CFRange::init(0, 0));
-            let utf16_line_len = string.char_len() as usize;
-
-            let last_run: RefCell<Option<(usize, FontId)>> = Default::default();
-            let font_runs = runs
-                .iter()
-                .filter_map(|(len, style)| {
-                    let mut last_run = last_run.borrow_mut();
-                    if let Some((last_len, last_font_id)) = last_run.as_mut() {
-                        if style.font_id == *last_font_id {
-                            *last_len += *len;
-                            None
-                        } else {
-                            let result = (*last_len, *last_font_id);
-                            *last_len = *len;
-                            *last_font_id = style.font_id;
-                            Some(result)
-                        }
-                    } else {
-                        *last_run = Some((*len, style.font_id));
-                        None
-                    }
-                })
-                .chain(std::iter::from_fn(|| last_run.borrow_mut().take()));
-
-            let mut ix_converter = StringIndexConverter::new(text);
-            for (run_len, font_id) in font_runs {
-                let utf8_end = ix_converter.utf8_ix + run_len;
-                let utf16_start = ix_converter.utf16_ix;
-
-                if utf16_start >= utf16_line_len {
-                    break;
-                }
-
-                ix_converter.advance_to_utf8_ix(utf8_end);
-                let utf16_end = cmp::min(ix_converter.utf16_ix, utf16_line_len);
-
-                let cf_range =
-                    CFRange::init(utf16_start as isize, (utf16_end - utf16_start) as isize);
-                let font = &self.fonts[font_id.0];
-                unsafe {
-                    string.set_attribute(
-                        cf_range,
-                        kCTFontAttributeName,
-                        &font.native_font().clone_with_font_size(font_size as f64),
-                    );
-                }
-
-                if utf16_end == utf16_line_len {
-                    break;
-                }
-            }
-        }
-
-        // Retrieve the glyphs from the shaped line, converting UTF16 offsets to UTF8 offsets.
-        let line = CTLine::new_with_attributed_string(string.as_concrete_TypeRef());
-
-        let mut runs = Vec::new();
-        for run in line.glyph_runs().into_iter() {
-            let attributes = run.attributes().unwrap();
-            let font = unsafe {
-                attributes
-                    .get(kCTFontAttributeName)
-                    .downcast::<CTFont>()
-                    .unwrap()
-            };
-            let font_id = self.id_for_native_font(font);
-
-            let mut ix_converter = StringIndexConverter::new(text);
-            let mut glyphs = Vec::new();
-            for ((glyph_id, position), glyph_utf16_ix) in run
-                .glyphs()
-                .iter()
-                .zip(run.positions().iter())
-                .zip(run.string_indices().iter())
-            {
-                let glyph_utf16_ix = usize::try_from(*glyph_utf16_ix).unwrap();
-                ix_converter.advance_to_utf16_ix(glyph_utf16_ix);
-                glyphs.push(Glyph {
-                    id: *glyph_id as GlyphId,
-                    position: vec2f(position.x as f32, position.y as f32),
-                    index: ix_converter.utf8_ix,
-                    is_emoji: self.is_emoji(font_id),
-                });
-            }
-
-            runs.push(Run { font_id, glyphs })
-        }
-
-        let typographic_bounds = line.get_typographic_bounds();
-        LineLayout {
-            width: typographic_bounds.width as f32,
-            ascent: typographic_bounds.ascent as f32,
-            descent: typographic_bounds.descent as f32,
-            runs,
-            font_size,
-            len: text.len(),
-        }
-    }
-
-    fn wrap_line(&self, text: &str, font_id: FontId, font_size: f32, width: f32) -> Vec<usize> {
-        let mut string = CFMutableAttributedString::new();
-        string.replace_str(&CFString::new(text), CFRange::init(0, 0));
-        let cf_range = CFRange::init(0, text.encode_utf16().count() as isize);
-        let font = &self.fonts[font_id.0];
-        unsafe {
-            string.set_attribute(
-                cf_range,
-                kCTFontAttributeName,
-                &font.native_font().clone_with_font_size(font_size as f64),
-            );
-
-            let typesetter = CTTypesetterCreateWithAttributedString(string.as_concrete_TypeRef());
-            let mut ix_converter = StringIndexConverter::new(text);
-            let mut break_indices = Vec::new();
-            while ix_converter.utf8_ix < text.len() {
-                let utf16_len = CTTypesetterSuggestLineBreak(
-                    typesetter,
-                    ix_converter.utf16_ix as isize,
-                    width as f64,
-                ) as usize;
-                ix_converter.advance_to_utf16_ix(ix_converter.utf16_ix + utf16_len);
-                if ix_converter.utf8_ix >= text.len() {
-                    break;
-                }
-                break_indices.push(ix_converter.utf8_ix as usize);
-            }
-            break_indices
-        }
-    }
-}
-
-#[derive(Clone)]
-struct StringIndexConverter<'a> {
-    text: &'a str,
-    utf8_ix: usize,
-    utf16_ix: usize,
-}
-
-impl<'a> StringIndexConverter<'a> {
-    fn new(text: &'a str) -> Self {
-        Self {
-            text,
-            utf8_ix: 0,
-            utf16_ix: 0,
-        }
-    }
-
-    fn advance_to_utf8_ix(&mut self, utf8_target: usize) {
-        for (ix, c) in self.text[self.utf8_ix..].char_indices() {
-            if self.utf8_ix + ix >= utf8_target {
-                self.utf8_ix += ix;
-                return;
-            }
-            self.utf16_ix += c.len_utf16();
-        }
-        self.utf8_ix = self.text.len();
-    }
-
-    fn advance_to_utf16_ix(&mut self, utf16_target: usize) {
-        for (ix, c) in self.text[self.utf8_ix..].char_indices() {
-            if self.utf16_ix >= utf16_target {
-                self.utf8_ix += ix;
-                return;
-            }
-            self.utf16_ix += c.len_utf16();
-        }
-        self.utf8_ix = self.text.len();
-    }
-}
-
-#[repr(C)]
-pub struct __CFTypesetter(c_void);
-
-pub type CTTypesetterRef = *const __CFTypesetter;
-
-#[link(name = "CoreText", kind = "framework")]
-extern "C" {
-    fn CTTypesetterCreateWithAttributedString(string: CFAttributedStringRef) -> CTTypesetterRef;
-
-    fn CTTypesetterSuggestLineBreak(
-        typesetter: CTTypesetterRef,
-        start_index: CFIndex,
-        width: f64,
-    ) -> CFIndex;
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::AppContext;
-    use font_kit::properties::{Style, Weight};
-    use platform::FontSystem as _;
-
-    #[crate::test(self, retries = 5)]
-    fn test_layout_str(_: &mut AppContext) {
-        // This is failing intermittently on CI and we don't have time to figure it out
-        let fonts = FontSystem::new();
-        let menlo = fonts.load_family("Menlo", &Default::default()).unwrap();
-        let menlo_regular = RunStyle {
-            font_id: fonts.select_font(&menlo, &Properties::new()).unwrap(),
-            color: Default::default(),
-            underline: Default::default(),
-        };
-        let menlo_italic = RunStyle {
-            font_id: fonts
-                .select_font(&menlo, Properties::new().style(Style::Italic))
-                .unwrap(),
-            color: Default::default(),
-            underline: Default::default(),
-        };
-        let menlo_bold = RunStyle {
-            font_id: fonts
-                .select_font(&menlo, Properties::new().weight(Weight::BOLD))
-                .unwrap(),
-            color: Default::default(),
-            underline: Default::default(),
-        };
-        assert_ne!(menlo_regular, menlo_italic);
-        assert_ne!(menlo_regular, menlo_bold);
-        assert_ne!(menlo_italic, menlo_bold);
-
-        let line = fonts.layout_line(
-            "hello world",
-            16.0,
-            &[(2, menlo_bold), (4, menlo_italic), (5, menlo_regular)],
-        );
-        assert_eq!(line.runs.len(), 3);
-        assert_eq!(line.runs[0].font_id, menlo_bold.font_id);
-        assert_eq!(line.runs[0].glyphs.len(), 2);
-        assert_eq!(line.runs[1].font_id, menlo_italic.font_id);
-        assert_eq!(line.runs[1].glyphs.len(), 4);
-        assert_eq!(line.runs[2].font_id, menlo_regular.font_id);
-        assert_eq!(line.runs[2].glyphs.len(), 5);
-    }
-
-    #[test]
-    fn test_glyph_offsets() -> anyhow::Result<()> {
-        let fonts = FontSystem::new();
-        let zapfino = fonts.load_family("Zapfino", &Default::default())?;
-        let zapfino_regular = RunStyle {
-            font_id: fonts.select_font(&zapfino, &Properties::new())?,
-            color: Default::default(),
-            underline: Default::default(),
-        };
-        let menlo = fonts.load_family("Menlo", &Default::default())?;
-        let menlo_regular = RunStyle {
-            font_id: fonts.select_font(&menlo, &Properties::new())?,
-            color: Default::default(),
-            underline: Default::default(),
-        };
-
-        let text = "This is, m𐍈re 𐍈r less, Zapfino!𐍈";
-        let line = fonts.layout_line(
-            text,
-            16.0,
-            &[
-                (9, zapfino_regular),
-                (13, menlo_regular),
-                (text.len() - 22, zapfino_regular),
-            ],
-        );
-        assert_eq!(
-            line.runs
-                .iter()
-                .flat_map(|r| r.glyphs.iter())
-                .map(|g| g.index)
-                .collect::<Vec<_>>(),
-            vec![0, 2, 4, 5, 7, 8, 9, 10, 14, 15, 16, 17, 21, 22, 23, 24, 26, 27, 28, 29, 36, 37],
-        );
-        Ok(())
-    }
-
-    #[test]
-    #[ignore]
-    fn test_rasterize_glyph() {
-        use std::{fs::File, io::BufWriter, path::Path};
-
-        let fonts = FontSystem::new();
-        let font_ids = fonts.load_family("Fira Code", &Default::default()).unwrap();
-        let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
-        let glyph_id = fonts.glyph_for_char(font_id, 'G').unwrap();
-
-        const VARIANTS: usize = 1;
-        for i in 0..VARIANTS {
-            let variant = i as f32 / VARIANTS as f32;
-            let (bounds, bytes) = fonts
-                .rasterize_glyph(
-                    font_id,
-                    16.0,
-                    glyph_id,
-                    vec2f(variant, variant),
-                    2.,
-                    RasterizationOptions::Alpha,
-                )
-                .unwrap();
-
-            let name = format!("/Users/as-cii/Desktop/twog-{}.png", i);
-            let path = Path::new(&name);
-            let file = File::create(path).unwrap();
-            let w = &mut BufWriter::new(file);
-
-            let mut encoder = png::Encoder::new(w, bounds.width() as u32, bounds.height() as u32);
-            encoder.set_color(png::ColorType::Grayscale);
-            encoder.set_depth(png::BitDepth::Eight);
-            let mut writer = encoder.write_header().unwrap();
-            writer.write_image_data(&bytes).unwrap();
-        }
-    }
-
-    #[test]
-    fn test_wrap_line() {
-        let fonts = FontSystem::new();
-        let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap();
-        let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
-
-        let line = "one two three four five\n";
-        let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0);
-        assert_eq!(wrap_boundaries, &["one two ".len(), "one two three ".len()]);
-
-        let line = "aaa ααα ✋✋✋ 🎉🎉🎉\n";
-        let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0);
-        assert_eq!(
-            wrap_boundaries,
-            &["aaa ααα ".len(), "aaa ααα ✋✋✋ ".len(),]
-        );
-    }
-
-    #[test]
-    fn test_layout_line_bom_char() {
-        let fonts = FontSystem::new();
-        let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap();
-        let style = RunStyle {
-            font_id: fonts.select_font(&font_ids, &Default::default()).unwrap(),
-            color: Default::default(),
-            underline: Default::default(),
-        };
-
-        let line = "\u{feff}";
-        let layout = fonts.layout_line(line, 16., &[(line.len(), style)]);
-        assert_eq!(layout.len, line.len());
-        assert!(layout.runs.is_empty());
-
-        let line = "a\u{feff}b";
-        let layout = fonts.layout_line(line, 16., &[(line.len(), style)]);
-        assert_eq!(layout.len, line.len());
-        assert_eq!(layout.runs.len(), 1);
-        assert_eq!(layout.runs[0].glyphs.len(), 2);
-        assert_eq!(layout.runs[0].glyphs[0].id, 68); // a
-                                                     // There's no glyph for \u{feff}
-        assert_eq!(layout.runs[0].glyphs[1].id, 69); // b
-    }
-}

crates/gpui/src/platform/mac/fonts/open_type.rs 🔗

@@ -1,395 +0,0 @@
-#![allow(unused, non_upper_case_globals)]
-
-use std::ptr;
-
-use crate::fonts::Features;
-use cocoa::appkit::CGFloat;
-use core_foundation::{base::TCFType, number::CFNumber};
-use core_graphics::geometry::CGAffineTransform;
-use core_text::{
-    font::{CTFont, CTFontRef},
-    font_descriptor::{
-        CTFontDescriptor, CTFontDescriptorCreateCopyWithFeature, CTFontDescriptorRef,
-    },
-};
-use font_kit::font::Font;
-
-const kCaseSensitiveLayoutOffSelector: i32 = 1;
-const kCaseSensitiveLayoutOnSelector: i32 = 0;
-const kCaseSensitiveLayoutType: i32 = 33;
-const kCaseSensitiveSpacingOffSelector: i32 = 3;
-const kCaseSensitiveSpacingOnSelector: i32 = 2;
-const kCharacterAlternativesType: i32 = 17;
-const kCommonLigaturesOffSelector: i32 = 3;
-const kCommonLigaturesOnSelector: i32 = 2;
-const kContextualAlternatesOffSelector: i32 = 1;
-const kContextualAlternatesOnSelector: i32 = 0;
-const kContextualAlternatesType: i32 = 36;
-const kContextualLigaturesOffSelector: i32 = 19;
-const kContextualLigaturesOnSelector: i32 = 18;
-const kContextualSwashAlternatesOffSelector: i32 = 5;
-const kContextualSwashAlternatesOnSelector: i32 = 4;
-const kDefaultLowerCaseSelector: i32 = 0;
-const kDefaultUpperCaseSelector: i32 = 0;
-const kDiagonalFractionsSelector: i32 = 2;
-const kFractionsType: i32 = 11;
-const kHistoricalLigaturesOffSelector: i32 = 21;
-const kHistoricalLigaturesOnSelector: i32 = 20;
-const kHojoCharactersSelector: i32 = 12;
-const kInferiorsSelector: i32 = 2;
-const kJIS2004CharactersSelector: i32 = 11;
-const kLigaturesType: i32 = 1;
-const kLowerCasePetiteCapsSelector: i32 = 2;
-const kLowerCaseSmallCapsSelector: i32 = 1;
-const kLowerCaseType: i32 = 37;
-const kLowerCaseNumbersSelector: i32 = 0;
-const kMathematicalGreekOffSelector: i32 = 11;
-const kMathematicalGreekOnSelector: i32 = 10;
-const kMonospacedNumbersSelector: i32 = 0;
-const kNLCCharactersSelector: i32 = 13;
-const kNoFractionsSelector: i32 = 0;
-const kNormalPositionSelector: i32 = 0;
-const kNoStyleOptionsSelector: i32 = 0;
-const kNumberCaseType: i32 = 21;
-const kNumberSpacingType: i32 = 6;
-const kOrdinalsSelector: i32 = 3;
-const kProportionalNumbersSelector: i32 = 1;
-const kQuarterWidthTextSelector: i32 = 4;
-const kScientificInferiorsSelector: i32 = 4;
-const kSlashedZeroOffSelector: i32 = 5;
-const kSlashedZeroOnSelector: i32 = 4;
-const kStyleOptionsType: i32 = 19;
-const kStylisticAltEighteenOffSelector: i32 = 37;
-const kStylisticAltEighteenOnSelector: i32 = 36;
-const kStylisticAltEightOffSelector: i32 = 17;
-const kStylisticAltEightOnSelector: i32 = 16;
-const kStylisticAltElevenOffSelector: i32 = 23;
-const kStylisticAltElevenOnSelector: i32 = 22;
-const kStylisticAlternativesType: i32 = 35;
-const kStylisticAltFifteenOffSelector: i32 = 31;
-const kStylisticAltFifteenOnSelector: i32 = 30;
-const kStylisticAltFiveOffSelector: i32 = 11;
-const kStylisticAltFiveOnSelector: i32 = 10;
-const kStylisticAltFourOffSelector: i32 = 9;
-const kStylisticAltFourOnSelector: i32 = 8;
-const kStylisticAltFourteenOffSelector: i32 = 29;
-const kStylisticAltFourteenOnSelector: i32 = 28;
-const kStylisticAltNineOffSelector: i32 = 19;
-const kStylisticAltNineOnSelector: i32 = 18;
-const kStylisticAltNineteenOffSelector: i32 = 39;
-const kStylisticAltNineteenOnSelector: i32 = 38;
-const kStylisticAltOneOffSelector: i32 = 3;
-const kStylisticAltOneOnSelector: i32 = 2;
-const kStylisticAltSevenOffSelector: i32 = 15;
-const kStylisticAltSevenOnSelector: i32 = 14;
-const kStylisticAltSeventeenOffSelector: i32 = 35;
-const kStylisticAltSeventeenOnSelector: i32 = 34;
-const kStylisticAltSixOffSelector: i32 = 13;
-const kStylisticAltSixOnSelector: i32 = 12;
-const kStylisticAltSixteenOffSelector: i32 = 33;
-const kStylisticAltSixteenOnSelector: i32 = 32;
-const kStylisticAltTenOffSelector: i32 = 21;
-const kStylisticAltTenOnSelector: i32 = 20;
-const kStylisticAltThirteenOffSelector: i32 = 27;
-const kStylisticAltThirteenOnSelector: i32 = 26;
-const kStylisticAltThreeOffSelector: i32 = 7;
-const kStylisticAltThreeOnSelector: i32 = 6;
-const kStylisticAltTwelveOffSelector: i32 = 25;
-const kStylisticAltTwelveOnSelector: i32 = 24;
-const kStylisticAltTwentyOffSelector: i32 = 41;
-const kStylisticAltTwentyOnSelector: i32 = 40;
-const kStylisticAltTwoOffSelector: i32 = 5;
-const kStylisticAltTwoOnSelector: i32 = 4;
-const kSuperiorsSelector: i32 = 1;
-const kSwashAlternatesOffSelector: i32 = 3;
-const kSwashAlternatesOnSelector: i32 = 2;
-const kTitlingCapsSelector: i32 = 4;
-const kTypographicExtrasType: i32 = 14;
-const kVerticalFractionsSelector: i32 = 1;
-const kVerticalPositionType: i32 = 10;
-
-pub fn apply_features(font: &mut Font, features: &Features) {
-    // See https://chromium.googlesource.com/chromium/src/+/66.0.3359.158/third_party/harfbuzz-ng/src/hb-coretext.cc
-    // for a reference implementation.
-    toggle_open_type_feature(
-        font,
-        features.calt,
-        kContextualAlternatesType,
-        kContextualAlternatesOnSelector,
-        kContextualAlternatesOffSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.case,
-        kCaseSensitiveLayoutType,
-        kCaseSensitiveLayoutOnSelector,
-        kCaseSensitiveLayoutOffSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.cpsp,
-        kCaseSensitiveLayoutType,
-        kCaseSensitiveSpacingOnSelector,
-        kCaseSensitiveSpacingOffSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.frac,
-        kFractionsType,
-        kDiagonalFractionsSelector,
-        kNoFractionsSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.liga,
-        kLigaturesType,
-        kCommonLigaturesOnSelector,
-        kCommonLigaturesOffSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.onum,
-        kNumberCaseType,
-        kLowerCaseNumbersSelector,
-        2,
-    );
-    toggle_open_type_feature(
-        font,
-        features.ordn,
-        kVerticalPositionType,
-        kOrdinalsSelector,
-        kNormalPositionSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.pnum,
-        kNumberSpacingType,
-        kProportionalNumbersSelector,
-        4,
-    );
-    toggle_open_type_feature(
-        font,
-        features.ss01,
-        kStylisticAlternativesType,
-        kStylisticAltOneOnSelector,
-        kStylisticAltOneOffSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.ss02,
-        kStylisticAlternativesType,
-        kStylisticAltTwoOnSelector,
-        kStylisticAltTwoOffSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.ss03,
-        kStylisticAlternativesType,
-        kStylisticAltThreeOnSelector,
-        kStylisticAltThreeOffSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.ss04,
-        kStylisticAlternativesType,
-        kStylisticAltFourOnSelector,
-        kStylisticAltFourOffSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.ss05,
-        kStylisticAlternativesType,
-        kStylisticAltFiveOnSelector,
-        kStylisticAltFiveOffSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.ss06,
-        kStylisticAlternativesType,
-        kStylisticAltSixOnSelector,
-        kStylisticAltSixOffSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.ss07,
-        kStylisticAlternativesType,
-        kStylisticAltSevenOnSelector,
-        kStylisticAltSevenOffSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.ss08,
-        kStylisticAlternativesType,
-        kStylisticAltEightOnSelector,
-        kStylisticAltEightOffSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.ss09,
-        kStylisticAlternativesType,
-        kStylisticAltNineOnSelector,
-        kStylisticAltNineOffSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.ss10,
-        kStylisticAlternativesType,
-        kStylisticAltTenOnSelector,
-        kStylisticAltTenOffSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.ss11,
-        kStylisticAlternativesType,
-        kStylisticAltElevenOnSelector,
-        kStylisticAltElevenOffSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.ss12,
-        kStylisticAlternativesType,
-        kStylisticAltTwelveOnSelector,
-        kStylisticAltTwelveOffSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.ss13,
-        kStylisticAlternativesType,
-        kStylisticAltThirteenOnSelector,
-        kStylisticAltThirteenOffSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.ss14,
-        kStylisticAlternativesType,
-        kStylisticAltFourteenOnSelector,
-        kStylisticAltFourteenOffSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.ss15,
-        kStylisticAlternativesType,
-        kStylisticAltFifteenOnSelector,
-        kStylisticAltFifteenOffSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.ss16,
-        kStylisticAlternativesType,
-        kStylisticAltSixteenOnSelector,
-        kStylisticAltSixteenOffSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.ss17,
-        kStylisticAlternativesType,
-        kStylisticAltSeventeenOnSelector,
-        kStylisticAltSeventeenOffSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.ss18,
-        kStylisticAlternativesType,
-        kStylisticAltEighteenOnSelector,
-        kStylisticAltEighteenOffSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.ss19,
-        kStylisticAlternativesType,
-        kStylisticAltNineteenOnSelector,
-        kStylisticAltNineteenOffSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.ss20,
-        kStylisticAlternativesType,
-        kStylisticAltTwentyOnSelector,
-        kStylisticAltTwentyOffSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.subs,
-        kVerticalPositionType,
-        kInferiorsSelector,
-        kNormalPositionSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.sups,
-        kVerticalPositionType,
-        kSuperiorsSelector,
-        kNormalPositionSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.swsh,
-        kContextualAlternatesType,
-        kSwashAlternatesOnSelector,
-        kSwashAlternatesOffSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.titl,
-        kStyleOptionsType,
-        kTitlingCapsSelector,
-        kNoStyleOptionsSelector,
-    );
-    toggle_open_type_feature(
-        font,
-        features.tnum,
-        kNumberSpacingType,
-        kMonospacedNumbersSelector,
-        4,
-    );
-    toggle_open_type_feature(
-        font,
-        features.zero,
-        kTypographicExtrasType,
-        kSlashedZeroOnSelector,
-        kSlashedZeroOffSelector,
-    );
-}
-
-fn toggle_open_type_feature(
-    font: &mut Font,
-    enabled: Option<bool>,
-    type_identifier: i32,
-    on_selector_identifier: i32,
-    off_selector_identifier: i32,
-) {
-    if let Some(enabled) = enabled {
-        let native_font = font.native_font();
-        unsafe {
-            let selector_identifier = if enabled {
-                on_selector_identifier
-            } else {
-                off_selector_identifier
-            };
-            let new_descriptor = CTFontDescriptorCreateCopyWithFeature(
-                native_font.copy_descriptor().as_concrete_TypeRef(),
-                CFNumber::from(type_identifier).as_concrete_TypeRef(),
-                CFNumber::from(selector_identifier).as_concrete_TypeRef(),
-            );
-            let new_descriptor = CTFontDescriptor::wrap_under_create_rule(new_descriptor);
-            let new_font = CTFontCreateCopyWithAttributes(
-                font.native_font().as_concrete_TypeRef(),
-                0.0,
-                ptr::null(),
-                new_descriptor.as_concrete_TypeRef(),
-            );
-            let new_font = CTFont::wrap_under_create_rule(new_font);
-            *font = Font::from_native_font(new_font);
-        }
-    }
-}
-
-#[link(name = "CoreText", kind = "framework")]
-extern "C" {
-    fn CTFontCreateCopyWithAttributes(
-        font: CTFontRef,
-        size: CGFloat,
-        matrix: *const CGAffineTransform,
-        attributes: CTFontDescriptorRef,
-    ) -> CTFontRef;
-}

crates/gpui/src/platform/mac/geometry.rs 🔗

@@ -1,45 +0,0 @@
-use cocoa::{
-    base::id,
-    foundation::{NSPoint, NSRect},
-};
-use objc::{msg_send, sel, sel_impl};
-use pathfinder_geometry::vector::{vec2f, Vector2F};
-
-///! Macos screen have a y axis that goings up from the bottom of the screen and
-///! an origin at the bottom left of the main display.
-
-pub trait Vector2FExt {
-    /// Converts self to an NSPoint with y axis pointing up.
-    fn to_screen_ns_point(&self, native_window: id, window_height: f64) -> NSPoint;
-}
-
-impl Vector2FExt for Vector2F {
-    fn to_screen_ns_point(&self, native_window: id, window_height: f64) -> NSPoint {
-        unsafe {
-            let point = NSPoint::new(self.x() as f64, window_height - self.y() as f64);
-            msg_send![native_window, convertPointToScreen: point]
-        }
-    }
-}
-
-pub trait NSRectExt {
-    fn size_vec(&self) -> Vector2F;
-    fn intersects(&self, other: Self) -> bool;
-}
-
-impl NSRectExt for NSRect {
-    fn size_vec(&self) -> Vector2F {
-        vec2f(self.size.width as f32, self.size.height as f32)
-    }
-
-    fn intersects(&self, other: Self) -> bool {
-        self.size.width > 0.
-            && self.size.height > 0.
-            && other.size.width > 0.
-            && other.size.height > 0.
-            && self.origin.x <= other.origin.x + other.size.width
-            && self.origin.x + self.size.width >= other.origin.x
-            && self.origin.y <= other.origin.y + other.size.height
-            && self.origin.y + self.size.height >= other.origin.y
-    }
-}

crates/gpui/src/platform/mac/image_cache.rs 🔗

@@ -1,115 +0,0 @@
-use super::atlas::{AllocId, AtlasAllocator};
-use crate::{
-    fonts::{FontId, GlyphId},
-    geometry::{rect::RectI, vector::Vector2I},
-    platform::{FontSystem, RasterizationOptions},
-    scene::ImageGlyph,
-    ImageData,
-};
-use anyhow::anyhow;
-use metal::{MTLPixelFormat, TextureDescriptor, TextureRef};
-use ordered_float::OrderedFloat;
-use std::{collections::HashMap, mem, sync::Arc};
-
-#[derive(Hash, Eq, PartialEq)]
-struct GlyphDescriptor {
-    font_id: FontId,
-    font_size: OrderedFloat<f32>,
-    glyph_id: GlyphId,
-}
-
-pub struct ImageCache {
-    prev_frame: HashMap<usize, (AllocId, RectI)>,
-    curr_frame: HashMap<usize, (AllocId, RectI)>,
-    image_glyphs: HashMap<GlyphDescriptor, Option<(AllocId, RectI, Vector2I)>>,
-    atlases: AtlasAllocator,
-    scale_factor: f32,
-    fonts: Arc<dyn FontSystem>,
-}
-
-impl ImageCache {
-    pub fn new(
-        device: metal::Device,
-        size: Vector2I,
-        scale_factor: f32,
-        fonts: Arc<dyn FontSystem>,
-    ) -> Self {
-        let descriptor = TextureDescriptor::new();
-        descriptor.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
-        descriptor.set_width(size.x() as u64);
-        descriptor.set_height(size.y() as u64);
-        Self {
-            prev_frame: Default::default(),
-            curr_frame: Default::default(),
-            image_glyphs: Default::default(),
-            atlases: AtlasAllocator::new(device, descriptor),
-            scale_factor,
-            fonts,
-        }
-    }
-
-    pub fn set_scale_factor(&mut self, scale_factor: f32) {
-        if scale_factor != self.scale_factor {
-            self.scale_factor = scale_factor;
-            for (_, glyph) in self.image_glyphs.drain() {
-                if let Some((alloc_id, _, _)) = glyph {
-                    self.atlases.deallocate(alloc_id);
-                }
-            }
-        }
-    }
-
-    pub fn render(&mut self, image: &ImageData) -> (AllocId, RectI) {
-        let (alloc_id, atlas_bounds) = self
-            .prev_frame
-            .remove(&image.id)
-            .or_else(|| self.curr_frame.get(&image.id).copied())
-            .or_else(|| self.atlases.upload(image.size(), image.as_bytes()))
-            .ok_or_else(|| anyhow!("could not upload image of size {:?}", image.size()))
-            .unwrap();
-        self.curr_frame.insert(image.id, (alloc_id, atlas_bounds));
-        (alloc_id, atlas_bounds)
-    }
-
-    pub fn render_glyph(&mut self, image_glyph: &ImageGlyph) -> Option<(AllocId, RectI, Vector2I)> {
-        *self
-            .image_glyphs
-            .entry(GlyphDescriptor {
-                font_id: image_glyph.font_id,
-                font_size: OrderedFloat(image_glyph.font_size),
-                glyph_id: image_glyph.id,
-            })
-            .or_insert_with(|| {
-                let (glyph_bounds, bytes) = self.fonts.rasterize_glyph(
-                    image_glyph.font_id,
-                    image_glyph.font_size,
-                    image_glyph.id,
-                    Default::default(),
-                    self.scale_factor,
-                    RasterizationOptions::Bgra,
-                )?;
-                let (alloc_id, atlas_bounds) = self
-                    .atlases
-                    .upload(glyph_bounds.size(), &bytes)
-                    .ok_or_else(|| {
-                        anyhow!(
-                            "could not upload image glyph of size {:?}",
-                            glyph_bounds.size()
-                        )
-                    })
-                    .unwrap();
-                Some((alloc_id, atlas_bounds, glyph_bounds.origin()))
-            })
-    }
-
-    pub fn finish_frame(&mut self) {
-        mem::swap(&mut self.prev_frame, &mut self.curr_frame);
-        for (_, (id, _)) in self.curr_frame.drain() {
-            self.atlases.deallocate(id);
-        }
-    }
-
-    pub fn atlas_texture(&self, atlas_id: usize) -> Option<&TextureRef> {
-        self.atlases.texture(atlas_id)
-    }
-}

crates/gpui/src/platform/mac/platform.rs 🔗

@@ -1,14 +1,12 @@
-use super::{
-    event::key_to_native, screen::Screen, status_item::StatusItem, BoolExt as _, Dispatcher,
-    FontSystem, MacWindow,
-};
+use super::{events::key_to_native, BoolExt};
 use crate::{
-    executor,
-    keymap_matcher::KeymapMatcher,
-    platform::{self, AppVersion, CursorStyle, Event},
-    Action, AnyWindowHandle, ClipboardItem, Menu, MenuItem,
+    Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
+    ForegroundExecutor, InputEvent, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker,
+    MacTextSystem, MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay,
+    PlatformTextSystem, PlatformWindow, Result, Scene, SemanticVersion, VideoTimestamp,
+    WindowOptions,
 };
-use anyhow::{anyhow, Result};
+use anyhow::anyhow;
 use block::ConcreteBlock;
 use cocoa::{
     appkit::{
@@ -30,6 +28,7 @@ use core_foundation::{
     string::{CFString, CFStringRef},
 };
 use ctor::ctor;
+use futures::channel::oneshot;
 use objc::{
     class,
     declare::ClassDecl,
@@ -37,11 +36,10 @@ use objc::{
     runtime::{Class, Object, Sel},
     sel, sel_impl,
 };
-
-use postage::oneshot;
+use parking_lot::Mutex;
 use ptr::null_mut;
 use std::{
-    cell::{Cell, RefCell},
+    cell::Cell,
     convert::TryInto,
     ffi::{c_void, CStr, OsStr},
     os::{raw::c_char, unix::ffi::OsStrExt},
@@ -51,6 +49,7 @@ use std::{
     rc::Rc,
     slice, str,
     sync::Arc,
+    time::Duration,
 };
 use time::UtcOffset;
 
@@ -140,34 +139,50 @@ unsafe fn build_classes() {
             sel!(application:openURLs:),
             open_urls as extern "C" fn(&mut Object, Sel, id, id),
         );
-        decl.add_method(
-            sel!(application:continueUserActivity:restorationHandler:),
-            continue_user_activity as extern "C" fn(&mut Object, Sel, id, id, id),
-        );
         decl.register()
     }
 }
 
-pub struct MacForegroundPlatform(RefCell<MacForegroundPlatformState>);
+pub struct MacPlatform(Mutex<MacPlatformState>);
 
-pub struct MacForegroundPlatformState {
+pub struct MacPlatformState {
+    background_executor: BackgroundExecutor,
+    foreground_executor: ForegroundExecutor,
+    text_system: Arc<MacTextSystem>,
+    display_linker: MacDisplayLinker,
+    pasteboard: id,
+    text_hash_pasteboard_type: id,
+    metadata_pasteboard_type: id,
     become_active: Option<Box<dyn FnMut()>>,
     resign_active: Option<Box<dyn FnMut()>>,
     reopen: Option<Box<dyn FnMut()>>,
     quit: Option<Box<dyn FnMut()>>,
-    event: Option<Box<dyn FnMut(platform::Event) -> bool>>,
+    event: Option<Box<dyn FnMut(InputEvent) -> bool>>,
     menu_command: Option<Box<dyn FnMut(&dyn Action)>>,
     validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
     will_open_menu: Option<Box<dyn FnMut()>>,
+    menu_actions: Vec<Box<dyn Action>>,
     open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
     finish_launching: Option<Box<dyn FnOnce()>>,
-    menu_actions: Vec<Box<dyn Action>>,
-    foreground: Rc<executor::Foreground>,
 }
 
-impl MacForegroundPlatform {
-    pub fn new(foreground: Rc<executor::Foreground>) -> Self {
-        Self(RefCell::new(MacForegroundPlatformState {
+impl Default for MacPlatform {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl MacPlatform {
+    pub fn new() -> Self {
+        let dispatcher = Arc::new(MacDispatcher::new());
+        Self(Mutex::new(MacPlatformState {
+            background_executor: BackgroundExecutor::new(dispatcher.clone()),
+            foreground_executor: ForegroundExecutor::new(dispatcher),
+            text_system: Arc::new(MacTextSystem::new()),
+            display_linker: MacDisplayLinker::new(),
+            pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },
+            text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") },
+            metadata_pasteboard_type: unsafe { ns_string("zed-metadata") },
             become_active: None,
             resign_active: None,
             reopen: None,
@@ -176,19 +191,30 @@ impl MacForegroundPlatform {
             menu_command: None,
             validate_menu_command: None,
             will_open_menu: None,
+            menu_actions: Default::default(),
             open_urls: None,
             finish_launching: None,
-            menu_actions: Default::default(),
-            foreground,
         }))
     }
 
+    unsafe fn read_from_pasteboard(&self, pasteboard: *mut Object, kind: id) -> Option<&[u8]> {
+        let data = pasteboard.dataForType(kind);
+        if data == nil {
+            None
+        } else {
+            Some(slice::from_raw_parts(
+                data.bytes() as *mut u8,
+                data.length() as usize,
+            ))
+        }
+    }
+
     unsafe fn create_menu_bar(
         &self,
         menus: Vec<Menu>,
         delegate: id,
         actions: &mut Vec<Box<dyn Action>>,
-        keystroke_matcher: &KeymapMatcher,
+        keymap: &Keymap,
     ) -> id {
         let application_menu = NSMenu::new(nil).autorelease();
         application_menu.setDelegate_(delegate);
@@ -199,11 +225,11 @@ impl MacForegroundPlatform {
             menu.setDelegate_(delegate);
 
             for item_config in menu_config.items {
-                menu.addItem_(self.create_menu_item(
+                menu.addItem_(Self::create_menu_item(
                     item_config,
                     delegate,
                     actions,
-                    keystroke_matcher,
+                    keymap,
                 ));
             }
 
@@ -221,11 +247,10 @@ impl MacForegroundPlatform {
     }
 
     unsafe fn create_menu_item(
-        &self,
         item: MenuItem,
         delegate: id,
         actions: &mut Vec<Box<dyn Action>>,
-        keystroke_matcher: &KeymapMatcher,
+        keymap: &Keymap,
     ) -> id {
         match item {
             MenuItem::Separator => NSMenuItem::separatorItem(nil),
@@ -234,11 +259,11 @@ impl MacForegroundPlatform {
                 action,
                 os_action,
             } => {
-                // TODO
-                let keystrokes = keystroke_matcher
-                    .bindings_for_action(action.id())
-                    .find(|binding| binding.action().eq(action.as_ref()))
+                let keystrokes = keymap
+                    .bindings_for_action(action.type_id())
+                    .find(|binding| binding.action().partial_eq(action.as_ref()))
                     .map(|binding| binding.keystrokes());
+
                 let selector = match os_action {
                     Some(crate::OsAction::Cut) => selector("cut:"),
                     Some(crate::OsAction::Copy) => selector("copy:"),
@@ -255,10 +280,22 @@ impl MacForegroundPlatform {
                         let keystroke = &keystrokes[0];
                         let mut mask = NSEventModifierFlags::empty();
                         for (modifier, flag) in &[
-                            (keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask),
-                            (keystroke.ctrl, NSEventModifierFlags::NSControlKeyMask),
-                            (keystroke.alt, NSEventModifierFlags::NSAlternateKeyMask),
-                            (keystroke.shift, NSEventModifierFlags::NSShiftKeyMask),
+                            (
+                                keystroke.modifiers.command,
+                                NSEventModifierFlags::NSCommandKeyMask,
+                            ),
+                            (
+                                keystroke.modifiers.control,
+                                NSEventModifierFlags::NSControlKeyMask,
+                            ),
+                            (
+                                keystroke.modifiers.alt,
+                                NSEventModifierFlags::NSAlternateKeyMask,
+                            ),
+                            (
+                                keystroke.modifiers.shift,
+                                NSEventModifierFlags::NSShiftKeyMask,
+                            ),
                         ] {
                             if *modifier {
                                 mask |= *flag;
@@ -315,12 +352,7 @@ impl MacForegroundPlatform {
                 let submenu = NSMenu::new(nil).autorelease();
                 submenu.setDelegate_(delegate);
                 for item in items {
-                    submenu.addItem_(self.create_menu_item(
-                        item,
-                        delegate,
-                        actions,
-                        keystroke_matcher,
-                    ));
+                    submenu.addItem_(Self::create_menu_item(item, delegate, actions, keymap));
                 }
                 item.setSubmenu_(submenu);
                 item.setTitle_(ns_string(name));
@@ -330,33 +362,21 @@ impl MacForegroundPlatform {
     }
 }
 
-impl platform::ForegroundPlatform for MacForegroundPlatform {
-    fn on_become_active(&self, callback: Box<dyn FnMut()>) {
-        self.0.borrow_mut().become_active = Some(callback);
-    }
-
-    fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
-        self.0.borrow_mut().resign_active = Some(callback);
+impl Platform for MacPlatform {
+    fn background_executor(&self) -> BackgroundExecutor {
+        self.0.lock().background_executor.clone()
     }
 
-    fn on_quit(&self, callback: Box<dyn FnMut()>) {
-        self.0.borrow_mut().quit = Some(callback);
+    fn foreground_executor(&self) -> crate::ForegroundExecutor {
+        self.0.lock().foreground_executor.clone()
     }
 
-    fn on_reopen(&self, callback: Box<dyn FnMut()>) {
-        self.0.borrow_mut().reopen = Some(callback);
-    }
-
-    fn on_event(&self, callback: Box<dyn FnMut(platform::Event) -> bool>) {
-        self.0.borrow_mut().event = Some(callback);
-    }
-
-    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
-        self.0.borrow_mut().open_urls = Some(callback);
+    fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
+        self.0.lock().text_system.clone()
     }
 
     fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
-        self.0.borrow_mut().finish_launching = Some(on_finish_launching);
+        self.0.lock().finish_launching = Some(on_finish_launching);
 
         unsafe {
             let app: id = msg_send![APP_CLASS, sharedApplication];
@@ -376,35 +396,157 @@ impl platform::ForegroundPlatform for MacForegroundPlatform {
         }
     }
 
-    fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>) {
-        self.0.borrow_mut().menu_command = Some(callback);
+    fn quit(&self) {
+        // Quitting the app causes us to close windows, which invokes `Window::on_close` callbacks
+        // synchronously before this method terminates. If we call `Platform::quit` while holding a
+        // borrow of the app state (which most of the time we will do), we will end up
+        // double-borrowing the app state in the `on_close` callbacks for our open windows. To solve
+        // this, we make quitting the application asynchronous so that we aren't holding borrows to
+        // the app state on the stack when we actually terminate the app.
+
+        use super::dispatcher::{dispatch_async_f, dispatch_get_main_queue};
+
+        unsafe {
+            dispatch_async_f(dispatch_get_main_queue(), ptr::null_mut(), Some(quit));
+        }
+
+        unsafe extern "C" fn quit(_: *mut c_void) {
+            let app = NSApplication::sharedApplication(nil);
+            let _: () = msg_send![app, terminate: nil];
+        }
+    }
+
+    fn restart(&self) {
+        use std::os::unix::process::CommandExt as _;
+
+        let app_pid = std::process::id().to_string();
+        let app_path = self
+            .app_path()
+            .ok()
+            // When the app is not bundled, `app_path` returns the
+            // directory containing the executable. Disregard this
+            // and get the path to the executable itself.
+            .and_then(|path| (path.extension()?.to_str()? == "app").then_some(path))
+            .unwrap_or_else(|| std::env::current_exe().unwrap());
+
+        // Wait until this process has exited and then re-open this path.
+        let script = r#"
+            while kill -0 $0 2> /dev/null; do
+                sleep 0.1
+            done
+            open "$1"
+        "#;
+
+        let restart_process = Command::new("/bin/bash")
+            .arg("-c")
+            .arg(script)
+            .arg(app_pid)
+            .arg(app_path)
+            .process_group(0)
+            .spawn();
+
+        match restart_process {
+            Ok(_) => self.quit(),
+            Err(e) => log::error!("failed to spawn restart script: {:?}", e),
+        }
     }
 
-    fn on_will_open_menu(&self, callback: Box<dyn FnMut()>) {
-        self.0.borrow_mut().will_open_menu = Some(callback);
+    fn activate(&self, ignoring_other_apps: bool) {
+        unsafe {
+            let app = NSApplication::sharedApplication(nil);
+            app.activateIgnoringOtherApps_(ignoring_other_apps.to_objc());
+        }
     }
 
-    fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
-        self.0.borrow_mut().validate_menu_command = Some(callback);
+    fn hide(&self) {
+        unsafe {
+            let app = NSApplication::sharedApplication(nil);
+            let _: () = msg_send![app, hide: nil];
+        }
     }
 
-    fn set_menus(&self, menus: Vec<Menu>, keystroke_matcher: &KeymapMatcher) {
+    fn hide_other_apps(&self) {
         unsafe {
-            let app: id = msg_send![APP_CLASS, sharedApplication];
-            let mut state = self.0.borrow_mut();
-            let actions = &mut state.menu_actions;
-            app.setMainMenu_(self.create_menu_bar(
-                menus,
-                app.delegate(),
-                actions,
-                keystroke_matcher,
-            ));
+            let app = NSApplication::sharedApplication(nil);
+            let _: () = msg_send![app, hideOtherApplications: nil];
+        }
+    }
+
+    fn unhide_other_apps(&self) {
+        unsafe {
+            let app = NSApplication::sharedApplication(nil);
+            let _: () = msg_send![app, unhideAllApplications: nil];
+        }
+    }
+
+    // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> {
+    //     Box::new(StatusItem::add(self.fonts()))
+    // }
+
+    fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
+        MacDisplay::all()
+            .map(|screen| Rc::new(screen) as Rc<_>)
+            .collect()
+    }
+
+    fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
+        MacDisplay::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
+    }
+
+    fn active_window(&self) -> Option<AnyWindowHandle> {
+        MacWindow::active_window()
+    }
+
+    fn open_window(
+        &self,
+        handle: AnyWindowHandle,
+        options: WindowOptions,
+        draw: Box<dyn FnMut() -> Result<Scene>>,
+    ) -> Box<dyn PlatformWindow> {
+        Box::new(MacWindow::open(
+            handle,
+            options,
+            draw,
+            self.foreground_executor(),
+        ))
+    }
+
+    fn set_display_link_output_callback(
+        &self,
+        display_id: DisplayId,
+        callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp) + Send>,
+    ) {
+        self.0
+            .lock()
+            .display_linker
+            .set_output_callback(display_id, callback);
+    }
+
+    fn start_display_link(&self, display_id: DisplayId) {
+        self.0.lock().display_linker.start(display_id);
+    }
+
+    fn stop_display_link(&self, display_id: DisplayId) {
+        self.0.lock().display_linker.stop(display_id);
+    }
+
+    fn open_url(&self, url: &str) {
+        unsafe {
+            let url = NSURL::alloc(nil)
+                .initWithString_(ns_string(url))
+                .autorelease();
+            let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
+            msg_send![workspace, openURL: url]
         }
     }
 
+    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
+        self.0.lock().open_urls = Some(callback);
+    }
+
     fn prompt_for_paths(
         &self,
-        options: platform::PathPromptOptions,
+        options: PathPromptOptions,
     ) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
         unsafe {
             let panel = NSOpenPanel::openPanel(nil);
@@ -431,8 +573,8 @@ impl platform::ForegroundPlatform for MacForegroundPlatform {
                     None
                 };
 
-                if let Some(mut done_tx) = done_tx.take() {
-                    let _ = postage::sink::Sink::try_send(&mut done_tx, result);
+                if let Some(done_tx) = done_tx.take() {
+                    let _ = done_tx.send(result);
                 }
             });
             let block = block.copy();
@@ -459,8 +601,8 @@ impl platform::ForegroundPlatform for MacForegroundPlatform {
                     }
                 }
 
-                if let Some(mut done_tx) = done_tx.take() {
-                    let _ = postage::sink::Sink::try_send(&mut done_tx, result);
+                if let Some(done_tx) = done_tx.take() {
+                    let _ = done_tx.send(result);
                 }
             });
             let block = block.copy();
@@ -473,8 +615,8 @@ impl platform::ForegroundPlatform for MacForegroundPlatform {
         unsafe {
             let path = path.to_path_buf();
             self.0
-                .borrow()
-                .foreground
+                .lock()
+                .background_executor
                 .spawn(async move {
                     let full_path = ns_string(path.to_str().unwrap_or(""));
                     let root_full_path = ns_string("");
@@ -488,138 +630,182 @@ impl platform::ForegroundPlatform for MacForegroundPlatform {
                 .detach();
         }
     }
-}
 
-pub struct MacPlatform {
-    dispatcher: Arc<Dispatcher>,
-    fonts: Arc<FontSystem>,
-    pasteboard: id,
-    text_hash_pasteboard_type: id,
-    metadata_pasteboard_type: id,
-}
+    fn on_become_active(&self, callback: Box<dyn FnMut()>) {
+        self.0.lock().become_active = Some(callback);
+    }
 
-impl MacPlatform {
-    pub fn new() -> Self {
-        Self {
-            dispatcher: Arc::new(Dispatcher),
-            fonts: Arc::new(FontSystem::new()),
-            pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },
-            text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") },
-            metadata_pasteboard_type: unsafe { ns_string("zed-metadata") },
-        }
+    fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
+        self.0.lock().resign_active = Some(callback);
     }
 
-    unsafe fn read_from_pasteboard(&self, kind: id) -> Option<&[u8]> {
-        let data = self.pasteboard.dataForType(kind);
-        if data == nil {
-            None
-        } else {
-            Some(slice::from_raw_parts(
-                data.bytes() as *mut u8,
-                data.length() as usize,
-            ))
-        }
+    fn on_quit(&self, callback: Box<dyn FnMut()>) {
+        self.0.lock().quit = Some(callback);
     }
-}
 
-unsafe impl Send for MacPlatform {}
-unsafe impl Sync for MacPlatform {}
+    fn on_reopen(&self, callback: Box<dyn FnMut()>) {
+        self.0.lock().reopen = Some(callback);
+    }
 
-impl platform::Platform for MacPlatform {
-    fn dispatcher(&self) -> Arc<dyn platform::Dispatcher> {
-        self.dispatcher.clone()
+    fn on_event(&self, callback: Box<dyn FnMut(InputEvent) -> bool>) {
+        self.0.lock().event = Some(callback);
     }
 
-    fn fonts(&self) -> Arc<dyn platform::FontSystem> {
-        self.fonts.clone()
+    fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
+        self.0.lock().menu_command = Some(callback);
     }
 
-    fn activate(&self, ignoring_other_apps: bool) {
+    fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
+        self.0.lock().will_open_menu = Some(callback);
+    }
+
+    fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
+        self.0.lock().validate_menu_command = Some(callback);
+    }
+
+    fn os_name(&self) -> &'static str {
+        "macOS"
+    }
+
+    fn double_click_interval(&self) -> Duration {
         unsafe {
-            let app = NSApplication::sharedApplication(nil);
-            app.activateIgnoringOtherApps_(ignoring_other_apps.to_objc());
+            let double_click_interval: f64 = msg_send![class!(NSEvent), doubleClickInterval];
+            Duration::from_secs_f64(double_click_interval)
         }
     }
 
-    fn hide(&self) {
+    fn os_version(&self) -> Result<SemanticVersion> {
         unsafe {
-            let app = NSApplication::sharedApplication(nil);
-            let _: () = msg_send![app, hide: nil];
+            let process_info = NSProcessInfo::processInfo(nil);
+            let version = process_info.operatingSystemVersion();
+            Ok(SemanticVersion {
+                major: version.majorVersion as usize,
+                minor: version.minorVersion as usize,
+                patch: version.patchVersion as usize,
+            })
         }
     }
 
-    fn hide_other_apps(&self) {
+    fn app_version(&self) -> Result<SemanticVersion> {
         unsafe {
-            let app = NSApplication::sharedApplication(nil);
-            let _: () = msg_send![app, hideOtherApplications: nil];
+            let bundle: id = NSBundle::mainBundle();
+            if bundle.is_null() {
+                Err(anyhow!("app is not running inside a bundle"))
+            } else {
+                let version: id = msg_send![bundle, objectForInfoDictionaryKey: ns_string("CFBundleShortVersionString")];
+                let len = msg_send![version, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
+                let bytes = version.UTF8String() as *const u8;
+                let version = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap();
+                version.parse()
+            }
         }
     }
 
-    fn unhide_other_apps(&self) {
+    fn app_path(&self) -> Result<PathBuf> {
         unsafe {
-            let app = NSApplication::sharedApplication(nil);
-            let _: () = msg_send![app, unhideAllApplications: nil];
+            let bundle: id = NSBundle::mainBundle();
+            if bundle.is_null() {
+                Err(anyhow!("app is not running inside a bundle"))
+            } else {
+                Ok(path_from_objc(msg_send![bundle, bundlePath]))
+            }
         }
     }
 
-    fn quit(&self) {
-        // Quitting the app causes us to close windows, which invokes `Window::on_close` callbacks
-        // synchronously before this method terminates. If we call `Platform::quit` while holding a
-        // borrow of the app state (which most of the time we will do), we will end up
-        // double-borrowing the app state in the `on_close` callbacks for our open windows. To solve
-        // this, we make quitting the application asynchronous so that we aren't holding borrows to
-        // the app state on the stack when we actually terminate the app.
-
-        use super::dispatcher::{dispatch_async_f, dispatch_get_main_queue};
-
+    fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap) {
         unsafe {
-            dispatch_async_f(dispatch_get_main_queue(), ptr::null_mut(), Some(quit));
+            let app: id = msg_send![APP_CLASS, sharedApplication];
+            let mut state = self.0.lock();
+            let actions = &mut state.menu_actions;
+            app.setMainMenu_(self.create_menu_bar(menus, app.delegate(), actions, keymap));
         }
+    }
 
-        unsafe extern "C" fn quit(_: *mut c_void) {
-            let app = NSApplication::sharedApplication(nil);
-            let _: () = msg_send![app, terminate: nil];
+    fn local_timezone(&self) -> UtcOffset {
+        unsafe {
+            let local_timezone: id = msg_send![class!(NSTimeZone), localTimeZone];
+            let seconds_from_gmt: NSInteger = msg_send![local_timezone, secondsFromGMT];
+            UtcOffset::from_whole_seconds(seconds_from_gmt.try_into().unwrap()).unwrap()
         }
     }
 
-    fn screen_by_id(&self, id: uuid::Uuid) -> Option<Rc<dyn platform::Screen>> {
-        Screen::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
+    fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
+        unsafe {
+            let bundle: id = NSBundle::mainBundle();
+            if bundle.is_null() {
+                Err(anyhow!("app is not running inside a bundle"))
+            } else {
+                let name = ns_string(name);
+                let url: id = msg_send![bundle, URLForAuxiliaryExecutable: name];
+                if url.is_null() {
+                    Err(anyhow!("resource not found"))
+                } else {
+                    ns_url_to_path(url)
+                }
+            }
+        }
     }
 
-    fn screens(&self) -> Vec<Rc<dyn platform::Screen>> {
-        Screen::all()
-            .into_iter()
-            .map(|screen| Rc::new(screen) as Rc<_>)
-            .collect()
-    }
+    /// Match cursor style to one of the styles available
+    /// in macOS's [NSCursor](https://developer.apple.com/documentation/appkit/nscursor).
+    fn set_cursor_style(&self, style: CursorStyle) {
+        unsafe {
+            let new_cursor: id = match style {
+                CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor],
+                CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor],
+                CursorStyle::Crosshair => msg_send![class!(NSCursor), crosshairCursor],
+                CursorStyle::ClosedHand => msg_send![class!(NSCursor), closedHandCursor],
+                CursorStyle::OpenHand => msg_send![class!(NSCursor), openHandCursor],
+                CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor],
+                CursorStyle::ResizeLeft => msg_send![class!(NSCursor), resizeLeftCursor],
+                CursorStyle::ResizeRight => msg_send![class!(NSCursor), resizeRightCursor],
+                CursorStyle::ResizeLeftRight => msg_send![class!(NSCursor), resizeLeftRightCursor],
+                CursorStyle::ResizeUp => msg_send![class!(NSCursor), resizeUpCursor],
+                CursorStyle::ResizeDown => msg_send![class!(NSCursor), resizeDownCursor],
+                CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor],
+                CursorStyle::DisappearingItem => {
+                    msg_send![class!(NSCursor), disappearingItemCursor]
+                }
+                CursorStyle::IBeamCursorForVerticalLayout => {
+                    msg_send![class!(NSCursor), IBeamCursorForVerticalLayout]
+                }
+                CursorStyle::OperationNotAllowed => {
+                    msg_send![class!(NSCursor), operationNotAllowedCursor]
+                }
+                CursorStyle::DragLink => msg_send![class!(NSCursor), dragLinkCursor],
+                CursorStyle::DragCopy => msg_send![class!(NSCursor), dragCopyCursor],
+                CursorStyle::ContextualMenu => msg_send![class!(NSCursor), contextualMenuCursor],
+            };
 
-    fn open_window(
-        &self,
-        handle: AnyWindowHandle,
-        options: platform::WindowOptions,
-        executor: Rc<executor::Foreground>,
-    ) -> Box<dyn platform::Window> {
-        Box::new(MacWindow::open(handle, options, executor, self.fonts()))
+            let old_cursor: id = msg_send![class!(NSCursor), currentCursor];
+            if new_cursor != old_cursor {
+                let _: () = msg_send![new_cursor, set];
+            }
+        }
     }
 
-    fn main_window(&self) -> Option<AnyWindowHandle> {
-        MacWindow::main_window()
-    }
+    fn should_auto_hide_scrollbars(&self) -> bool {
+        #[allow(non_upper_case_globals)]
+        const NSScrollerStyleOverlay: NSInteger = 1;
 
-    fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> {
-        Box::new(StatusItem::add(self.fonts()))
+        unsafe {
+            let style: NSInteger = msg_send![class!(NSScroller), preferredScrollerStyle];
+            style == NSScrollerStyleOverlay
+        }
     }
 
     fn write_to_clipboard(&self, item: ClipboardItem) {
+        let state = self.0.lock();
         unsafe {
-            self.pasteboard.clearContents();
+            state.pasteboard.clearContents();
 
             let text_bytes = NSData::dataWithBytes_length_(
                 nil,
                 item.text.as_ptr() as *const c_void,
                 item.text.len() as u64,
             );
-            self.pasteboard
+            state
+                .pasteboard
                 .setData_forType(text_bytes, NSPasteboardTypeString);
 
             if let Some(metadata) = item.metadata.as_ref() {
@@ -629,30 +815,35 @@ impl platform::Platform for MacPlatform {
                     hash_bytes.as_ptr() as *const c_void,
                     hash_bytes.len() as u64,
                 );
-                self.pasteboard
-                    .setData_forType(hash_bytes, self.text_hash_pasteboard_type);
+                state
+                    .pasteboard
+                    .setData_forType(hash_bytes, state.text_hash_pasteboard_type);
 
                 let metadata_bytes = NSData::dataWithBytes_length_(
                     nil,
                     metadata.as_ptr() as *const c_void,
                     metadata.len() as u64,
                 );
-                self.pasteboard
-                    .setData_forType(metadata_bytes, self.metadata_pasteboard_type);
+                state
+                    .pasteboard
+                    .setData_forType(metadata_bytes, state.metadata_pasteboard_type);
             }
         }
     }
 
     fn read_from_clipboard(&self) -> Option<ClipboardItem> {
+        let state = self.0.lock();
         unsafe {
-            if let Some(text_bytes) = self.read_from_pasteboard(NSPasteboardTypeString) {
+            if let Some(text_bytes) =
+                self.read_from_pasteboard(state.pasteboard, NSPasteboardTypeString)
+            {
                 let text = String::from_utf8_lossy(text_bytes).to_string();
                 let hash_bytes = self
-                    .read_from_pasteboard(self.text_hash_pasteboard_type)
+                    .read_from_pasteboard(state.pasteboard, state.text_hash_pasteboard_type)
                     .and_then(|bytes| bytes.try_into().ok())
                     .map(u64::from_be_bytes);
                 let metadata_bytes = self
-                    .read_from_pasteboard(self.metadata_pasteboard_type)
+                    .read_from_pasteboard(state.pasteboard, state.metadata_pasteboard_type)
                     .and_then(|bytes| String::from_utf8(bytes.to_vec()).ok());
 
                 if let Some((hash, metadata)) = hash_bytes.zip(metadata_bytes) {
@@ -679,16 +870,6 @@ impl platform::Platform for MacPlatform {
         }
     }
 
-    fn open_url(&self, url: &str) {
-        unsafe {
-            let url = NSURL::alloc(nil)
-                .initWithString_(ns_string(url))
-                .autorelease();
-            let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
-            msg_send![workspace, openURL: url]
-        }
-    }
-
     fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
         let url = CFString::from(url);
         let username = CFString::from(username);
@@ -788,137 +969,6 @@ impl platform::Platform for MacPlatform {
         }
         Ok(())
     }
-
-    fn set_cursor_style(&self, style: CursorStyle) {
-        unsafe {
-            let new_cursor: id = match style {
-                CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor],
-                CursorStyle::ResizeLeftRight => {
-                    msg_send![class!(NSCursor), resizeLeftRightCursor]
-                }
-                CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor],
-                CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor],
-                CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor],
-            };
-
-            let old_cursor: id = msg_send![class!(NSCursor), currentCursor];
-            if new_cursor != old_cursor {
-                let _: () = msg_send![new_cursor, set];
-            }
-        }
-    }
-
-    fn should_auto_hide_scrollbars(&self) -> bool {
-        #[allow(non_upper_case_globals)]
-        const NSScrollerStyleOverlay: NSInteger = 1;
-
-        unsafe {
-            let style: NSInteger = msg_send![class!(NSScroller), preferredScrollerStyle];
-            style == NSScrollerStyleOverlay
-        }
-    }
-
-    fn local_timezone(&self) -> UtcOffset {
-        unsafe {
-            let local_timezone: id = msg_send![class!(NSTimeZone), localTimeZone];
-            let seconds_from_gmt: NSInteger = msg_send![local_timezone, secondsFromGMT];
-            UtcOffset::from_whole_seconds(seconds_from_gmt.try_into().unwrap()).unwrap()
-        }
-    }
-
-    fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
-        unsafe {
-            let bundle: id = NSBundle::mainBundle();
-            if bundle.is_null() {
-                Err(anyhow!("app is not running inside a bundle"))
-            } else {
-                let name = ns_string(name);
-                let url: id = msg_send![bundle, URLForAuxiliaryExecutable: name];
-                if url.is_null() {
-                    Err(anyhow!("resource not found"))
-                } else {
-                    ns_url_to_path(url)
-                }
-            }
-        }
-    }
-
-    fn app_path(&self) -> Result<PathBuf> {
-        unsafe {
-            let bundle: id = NSBundle::mainBundle();
-            if bundle.is_null() {
-                Err(anyhow!("app is not running inside a bundle"))
-            } else {
-                Ok(path_from_objc(msg_send![bundle, bundlePath]))
-            }
-        }
-    }
-
-    fn app_version(&self) -> Result<platform::AppVersion> {
-        unsafe {
-            let bundle: id = NSBundle::mainBundle();
-            if bundle.is_null() {
-                Err(anyhow!("app is not running inside a bundle"))
-            } else {
-                let version: id = msg_send![bundle, objectForInfoDictionaryKey: ns_string("CFBundleShortVersionString")];
-                let len = msg_send![version, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
-                let bytes = version.UTF8String() as *const u8;
-                let version = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap();
-                version.parse()
-            }
-        }
-    }
-
-    fn os_name(&self) -> &'static str {
-        "macOS"
-    }
-
-    fn os_version(&self) -> Result<crate::platform::AppVersion> {
-        unsafe {
-            let process_info = NSProcessInfo::processInfo(nil);
-            let version = process_info.operatingSystemVersion();
-            Ok(AppVersion {
-                major: version.majorVersion as usize,
-                minor: version.minorVersion as usize,
-                patch: version.patchVersion as usize,
-            })
-        }
-    }
-
-    fn restart(&self) {
-        use std::os::unix::process::CommandExt as _;
-
-        let app_pid = std::process::id().to_string();
-        let app_path = self
-            .app_path()
-            .ok()
-            // When the app is not bundled, `app_path` returns the
-            // directory containing the executable. Disregard this
-            // and get the path to the executable itself.
-            .and_then(|path| (path.extension()?.to_str()? == "app").then_some(path))
-            .unwrap_or_else(|| std::env::current_exe().unwrap());
-
-        // Wait until this process has exited and then re-open this path.
-        let script = r#"
-            while kill -0 $0 2> /dev/null; do
-                sleep 0.1
-            done
-            open "$1"
-        "#;
-
-        let restart_process = Command::new("/bin/bash")
-            .arg("-c")
-            .arg(script)
-            .arg(app_pid)
-            .arg(app_path)
-            .process_group(0)
-            .spawn();
-
-        match restart_process {
-            Ok(_) => self.quit(),
-            Err(e) => log::error!("failed to spawn restart script: {:?}", e),
-        }
-    }
 }
 
 unsafe fn path_from_objc(path: id) -> PathBuf {
@@ -928,18 +978,18 @@ unsafe fn path_from_objc(path: id) -> PathBuf {
     PathBuf::from(path)
 }
 
-unsafe fn get_foreground_platform(object: &mut Object) -> &MacForegroundPlatform {
+unsafe fn get_mac_platform(object: &mut Object) -> &MacPlatform {
     let platform_ptr: *mut c_void = *object.get_ivar(MAC_PLATFORM_IVAR);
     assert!(!platform_ptr.is_null());
-    &*(platform_ptr as *const MacForegroundPlatform)
+    &*(platform_ptr as *const MacPlatform)
 }
 
 extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {
     unsafe {
-        if let Some(event) = Event::from_native(native_event, None) {
-            let platform = get_foreground_platform(this);
-            if let Some(callback) = platform.0.borrow_mut().event.as_mut() {
-                if callback(event) {
+        if let Some(event) = InputEvent::from_native(native_event, None) {
+            let platform = get_mac_platform(this);
+            if let Some(callback) = platform.0.lock().event.as_mut() {
+                if !callback(event) {
                     return;
                 }
             }
@@ -953,8 +1003,8 @@ extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
         let app: id = msg_send![APP_CLASS, sharedApplication];
         app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
 
-        let platform = get_foreground_platform(this);
-        let callback = platform.0.borrow_mut().finish_launching.take();
+        let platform = get_mac_platform(this);
+        let callback = platform.0.lock().finish_launching.take();
         if let Some(callback) = callback {
             callback();
         }
@@ -963,30 +1013,30 @@ extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
 
 extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_windows: bool) {
     if !has_open_windows {
-        let platform = unsafe { get_foreground_platform(this) };
-        if let Some(callback) = platform.0.borrow_mut().reopen.as_mut() {
+        let platform = unsafe { get_mac_platform(this) };
+        if let Some(callback) = platform.0.lock().reopen.as_mut() {
             callback();
         }
     }
 }
 
 extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) {
-    let platform = unsafe { get_foreground_platform(this) };
-    if let Some(callback) = platform.0.borrow_mut().become_active.as_mut() {
+    let platform = unsafe { get_mac_platform(this) };
+    if let Some(callback) = platform.0.lock().become_active.as_mut() {
         callback();
     }
 }
 
 extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) {
-    let platform = unsafe { get_foreground_platform(this) };
-    if let Some(callback) = platform.0.borrow_mut().resign_active.as_mut() {
+    let platform = unsafe { get_mac_platform(this) };
+    if let Some(callback) = platform.0.lock().resign_active.as_mut() {
         callback();
     }
 }
 
 extern "C" fn will_terminate(this: &mut Object, _: Sel, _: id) {
-    let platform = unsafe { get_foreground_platform(this) };
-    if let Some(callback) = platform.0.borrow_mut().quit.as_mut() {
+    let platform = unsafe { get_mac_platform(this) };
+    if let Some(callback) = platform.0.lock().quit.as_mut() {
         callback();
     }
 }

crates/gpui/src/platform/mac/renderer.rs 🔗

@@ -1,1277 +0,0 @@
-use super::{atlas::AtlasAllocator, image_cache::ImageCache, sprite_cache::SpriteCache};
-use crate::{
-    color::Color,
-    geometry::{
-        rect::RectF,
-        vector::{vec2f, vec2i, Vector2F},
-    },
-    platform,
-    scene::{Glyph, Icon, Image, ImageGlyph, Layer, Quad, Scene, Shadow, Underline},
-};
-use cocoa::{
-    base::{NO, YES},
-    foundation::NSUInteger,
-    quartzcore::AutoresizingMask,
-};
-use core_foundation::base::TCFType;
-use foreign_types::ForeignTypeRef;
-use log::warn;
-use media::core_video::{self, CVMetalTextureCache};
-use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
-use objc::{self, msg_send, sel, sel_impl};
-use shaders::ToFloat2 as _;
-use std::{collections::HashMap, ffi::c_void, iter::Peekable, mem, ptr, sync::Arc, vec};
-
-const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
-const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value.
-
-pub struct Renderer {
-    layer: metal::MetalLayer,
-    command_queue: CommandQueue,
-    sprite_cache: SpriteCache,
-    image_cache: ImageCache,
-    path_atlases: AtlasAllocator,
-    quad_pipeline_state: metal::RenderPipelineState,
-    shadow_pipeline_state: metal::RenderPipelineState,
-    sprite_pipeline_state: metal::RenderPipelineState,
-    image_pipeline_state: metal::RenderPipelineState,
-    surface_pipeline_state: metal::RenderPipelineState,
-    path_atlas_pipeline_state: metal::RenderPipelineState,
-    underline_pipeline_state: metal::RenderPipelineState,
-    unit_vertices: metal::Buffer,
-    instances: metal::Buffer,
-    cv_texture_cache: core_video::CVMetalTextureCache,
-}
-
-struct PathSprite {
-    layer_id: usize,
-    atlas_id: usize,
-    shader_data: shaders::GPUISprite,
-}
-
-pub struct Surface {
-    pub bounds: RectF,
-    pub image_buffer: core_video::CVImageBuffer,
-}
-
-impl Renderer {
-    pub fn new(is_opaque: bool, fonts: Arc<dyn platform::FontSystem>) -> Self {
-        const PIXEL_FORMAT: MTLPixelFormat = MTLPixelFormat::BGRA8Unorm;
-
-        let device: metal::Device = if let Some(device) = metal::Device::system_default() {
-            device
-        } else {
-            log::error!("unable to access a compatible graphics device");
-            std::process::exit(1);
-        };
-
-        let layer = metal::MetalLayer::new();
-        layer.set_device(&device);
-        layer.set_pixel_format(PIXEL_FORMAT);
-        layer.set_presents_with_transaction(true);
-        layer.set_opaque(is_opaque);
-        unsafe {
-            let _: () = msg_send![&*layer, setAllowsNextDrawableTimeout: NO];
-            let _: () = msg_send![&*layer, setNeedsDisplayOnBoundsChange: YES];
-            let _: () = msg_send![
-                &*layer,
-                setAutoresizingMask: AutoresizingMask::WIDTH_SIZABLE
-                    | AutoresizingMask::HEIGHT_SIZABLE
-            ];
-        }
-
-        let library = device
-            .new_library_with_data(SHADERS_METALLIB)
-            .expect("error building metal library");
-
-        let unit_vertices = [
-            (0., 0.).to_float2(),
-            (1., 0.).to_float2(),
-            (0., 1.).to_float2(),
-            (0., 1.).to_float2(),
-            (1., 0.).to_float2(),
-            (1., 1.).to_float2(),
-        ];
-        let unit_vertices = device.new_buffer_with_data(
-            unit_vertices.as_ptr() as *const c_void,
-            (unit_vertices.len() * mem::size_of::<shaders::vector_float2>()) as u64,
-            MTLResourceOptions::StorageModeManaged,
-        );
-        let instances = device.new_buffer(
-            INSTANCE_BUFFER_SIZE as u64,
-            MTLResourceOptions::StorageModeManaged,
-        );
-
-        let sprite_cache = SpriteCache::new(device.clone(), vec2i(1024, 768), 1., fonts.clone());
-        let image_cache = ImageCache::new(device.clone(), vec2i(1024, 768), 1., fonts);
-        let path_atlases =
-            AtlasAllocator::new(device.clone(), build_path_atlas_texture_descriptor());
-        let quad_pipeline_state = build_pipeline_state(
-            &device,
-            &library,
-            "quad",
-            "quad_vertex",
-            "quad_fragment",
-            PIXEL_FORMAT,
-        );
-        let shadow_pipeline_state = build_pipeline_state(
-            &device,
-            &library,
-            "shadow",
-            "shadow_vertex",
-            "shadow_fragment",
-            PIXEL_FORMAT,
-        );
-        let sprite_pipeline_state = build_pipeline_state(
-            &device,
-            &library,
-            "sprite",
-            "sprite_vertex",
-            "sprite_fragment",
-            PIXEL_FORMAT,
-        );
-        let image_pipeline_state = build_pipeline_state(
-            &device,
-            &library,
-            "image",
-            "image_vertex",
-            "image_fragment",
-            PIXEL_FORMAT,
-        );
-        let surface_pipeline_state = build_pipeline_state(
-            &device,
-            &library,
-            "surface",
-            "surface_vertex",
-            "surface_fragment",
-            PIXEL_FORMAT,
-        );
-        let path_atlas_pipeline_state = build_path_atlas_pipeline_state(
-            &device,
-            &library,
-            "path_atlas",
-            "path_atlas_vertex",
-            "path_atlas_fragment",
-            MTLPixelFormat::R16Float,
-        );
-        let underline_pipeline_state = build_pipeline_state(
-            &device,
-            &library,
-            "underline",
-            "underline_vertex",
-            "underline_fragment",
-            PIXEL_FORMAT,
-        );
-        let cv_texture_cache = unsafe { CVMetalTextureCache::new(device.as_ptr()).unwrap() };
-        Self {
-            layer,
-            command_queue: device.new_command_queue(),
-            sprite_cache,
-            image_cache,
-            path_atlases,
-            quad_pipeline_state,
-            shadow_pipeline_state,
-            sprite_pipeline_state,
-            image_pipeline_state,
-            surface_pipeline_state,
-            path_atlas_pipeline_state,
-            underline_pipeline_state,
-            unit_vertices,
-            instances,
-            cv_texture_cache,
-        }
-    }
-
-    pub fn layer(&self) -> &metal::MetalLayerRef {
-        &*self.layer
-    }
-
-    pub fn render(&mut self, scene: &Scene) {
-        let layer = self.layer.clone();
-        let drawable_size = layer.drawable_size();
-        let drawable = if let Some(drawable) = layer.next_drawable() {
-            drawable
-        } else {
-            log::error!(
-                "failed to retrieve next drawable, drawable size: {:?}",
-                drawable_size
-            );
-            return;
-        };
-        let command_queue = self.command_queue.clone();
-        let command_buffer = command_queue.new_command_buffer();
-
-        self.sprite_cache.set_scale_factor(scene.scale_factor());
-        self.image_cache.set_scale_factor(scene.scale_factor());
-
-        let mut offset = 0;
-
-        let path_sprites = self.render_path_atlases(scene, &mut offset, command_buffer);
-        self.render_layers(
-            scene,
-            path_sprites,
-            &mut offset,
-            vec2f(drawable_size.width as f32, drawable_size.height as f32),
-            command_buffer,
-            drawable.texture(),
-        );
-        self.instances.did_modify_range(NSRange {
-            location: 0,
-            length: offset as NSUInteger,
-        });
-        self.image_cache.finish_frame();
-
-        command_buffer.commit();
-        command_buffer.wait_until_completed();
-        drawable.present();
-    }
-
-    fn render_path_atlases(
-        &mut self,
-        scene: &Scene,
-        offset: &mut usize,
-        command_buffer: &metal::CommandBufferRef,
-    ) -> Vec<PathSprite> {
-        self.path_atlases.clear();
-        let mut sprites = Vec::new();
-        let mut vertices = Vec::<shaders::GPUIPathVertex>::new();
-        let mut current_atlas_id = None;
-        for (layer_id, layer) in scene.layers().enumerate() {
-            for path in layer.paths() {
-                let origin = path.bounds.origin() * scene.scale_factor();
-                let size = (path.bounds.size() * scene.scale_factor()).ceil();
-
-                let path_allocation = self.path_atlases.allocate(size.to_i32());
-                if path_allocation.is_none() {
-                    // Path size was likely zero.
-                    warn!("could not allocate path texture of size {:?}", size);
-                    continue;
-                }
-                let (alloc_id, atlas_origin) = path_allocation.unwrap();
-                let atlas_origin = atlas_origin.to_f32();
-                sprites.push(PathSprite {
-                    layer_id,
-                    atlas_id: alloc_id.atlas_id,
-                    shader_data: shaders::GPUISprite {
-                        origin: origin.floor().to_float2(),
-                        target_size: size.to_float2(),
-                        source_size: size.to_float2(),
-                        atlas_origin: atlas_origin.to_float2(),
-                        color: path.color.to_uchar4(),
-                        compute_winding: 1,
-                    },
-                });
-
-                if let Some(current_atlas_id) = current_atlas_id {
-                    if alloc_id.atlas_id != current_atlas_id {
-                        self.render_paths_to_atlas(
-                            offset,
-                            &vertices,
-                            current_atlas_id,
-                            command_buffer,
-                        );
-                        vertices.clear();
-                    }
-                }
-
-                current_atlas_id = Some(alloc_id.atlas_id);
-
-                for vertex in &path.vertices {
-                    let xy_position =
-                        (vertex.xy_position - path.bounds.origin()) * scene.scale_factor();
-                    vertices.push(shaders::GPUIPathVertex {
-                        xy_position: (atlas_origin + xy_position).to_float2(),
-                        st_position: vertex.st_position.to_float2(),
-                        clip_rect_origin: atlas_origin.to_float2(),
-                        clip_rect_size: size.to_float2(),
-                    });
-                }
-            }
-        }
-
-        if let Some(atlas_id) = current_atlas_id {
-            self.render_paths_to_atlas(offset, &vertices, atlas_id, command_buffer);
-        }
-
-        sprites
-    }
-
-    fn render_paths_to_atlas(
-        &mut self,
-        offset: &mut usize,
-        vertices: &[shaders::GPUIPathVertex],
-        atlas_id: usize,
-        command_buffer: &metal::CommandBufferRef,
-    ) {
-        align_offset(offset);
-        let next_offset = *offset + vertices.len() * mem::size_of::<shaders::GPUIPathVertex>();
-        assert!(
-            next_offset <= INSTANCE_BUFFER_SIZE,
-            "instance buffer exhausted"
-        );
-
-        let render_pass_descriptor = metal::RenderPassDescriptor::new();
-        let color_attachment = render_pass_descriptor
-            .color_attachments()
-            .object_at(0)
-            .unwrap();
-        let texture = self.path_atlases.texture(atlas_id).unwrap();
-        color_attachment.set_texture(Some(texture));
-        color_attachment.set_load_action(metal::MTLLoadAction::Clear);
-        color_attachment.set_store_action(metal::MTLStoreAction::Store);
-        color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., 1.));
-
-        let path_atlas_command_encoder =
-            command_buffer.new_render_command_encoder(render_pass_descriptor);
-        path_atlas_command_encoder.set_render_pipeline_state(&self.path_atlas_pipeline_state);
-        path_atlas_command_encoder.set_vertex_buffer(
-            shaders::GPUIPathAtlasVertexInputIndex_GPUIPathAtlasVertexInputIndexVertices as u64,
-            Some(&self.instances),
-            *offset as u64,
-        );
-        path_atlas_command_encoder.set_vertex_bytes(
-            shaders::GPUIPathAtlasVertexInputIndex_GPUIPathAtlasVertexInputIndexAtlasSize as u64,
-            mem::size_of::<shaders::vector_float2>() as u64,
-            [vec2i(texture.width() as i32, texture.height() as i32).to_float2()].as_ptr()
-                as *const c_void,
-        );
-
-        let buffer_contents = unsafe {
-            (self.instances.contents() as *mut u8).add(*offset) as *mut shaders::GPUIPathVertex
-        };
-
-        for (ix, vertex) in vertices.iter().enumerate() {
-            unsafe {
-                *buffer_contents.add(ix) = *vertex;
-            }
-        }
-
-        path_atlas_command_encoder.draw_primitives(
-            metal::MTLPrimitiveType::Triangle,
-            0,
-            vertices.len() as u64,
-        );
-        path_atlas_command_encoder.end_encoding();
-        *offset = next_offset;
-    }
-
-    fn render_layers(
-        &mut self,
-        scene: &Scene,
-        path_sprites: Vec<PathSprite>,
-        offset: &mut usize,
-        drawable_size: Vector2F,
-        command_buffer: &metal::CommandBufferRef,
-        output: &metal::TextureRef,
-    ) {
-        let render_pass_descriptor = metal::RenderPassDescriptor::new();
-        let color_attachment = render_pass_descriptor
-            .color_attachments()
-            .object_at(0)
-            .unwrap();
-        color_attachment.set_texture(Some(output));
-        color_attachment.set_load_action(metal::MTLLoadAction::Clear);
-        color_attachment.set_store_action(metal::MTLStoreAction::Store);
-        let alpha = if self.layer.is_opaque() { 1. } else { 0. };
-        color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., alpha));
-        let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
-
-        command_encoder.set_viewport(metal::MTLViewport {
-            originX: 0.0,
-            originY: 0.0,
-            width: drawable_size.x() as f64,
-            height: drawable_size.y() as f64,
-            znear: 0.0,
-            zfar: 1.0,
-        });
-
-        let scale_factor = scene.scale_factor();
-        let mut path_sprites = path_sprites.into_iter().peekable();
-        for (layer_id, layer) in scene.layers().enumerate() {
-            self.clip(scene, layer, drawable_size, command_encoder);
-            self.render_shadows(
-                layer.shadows(),
-                scale_factor,
-                offset,
-                drawable_size,
-                command_encoder,
-            );
-            self.render_quads(
-                layer.quads(),
-                scale_factor,
-                offset,
-                drawable_size,
-                command_encoder,
-            );
-            self.render_path_sprites(
-                layer_id,
-                &mut path_sprites,
-                offset,
-                drawable_size,
-                command_encoder,
-            );
-            self.render_underlines(
-                layer.underlines(),
-                scale_factor,
-                offset,
-                drawable_size,
-                command_encoder,
-            );
-            self.render_sprites(
-                layer.glyphs(),
-                layer.icons(),
-                scale_factor,
-                offset,
-                drawable_size,
-                command_encoder,
-            );
-            self.render_images(
-                layer.images(),
-                layer.image_glyphs(),
-                scale_factor,
-                offset,
-                drawable_size,
-                command_encoder,
-            );
-            self.render_surfaces(
-                layer.surfaces(),
-                scale_factor,
-                offset,
-                drawable_size,
-                command_encoder,
-            );
-        }
-
-        command_encoder.end_encoding();
-    }
-
-    fn clip(
-        &mut self,
-        scene: &Scene,
-        layer: &Layer,
-        drawable_size: Vector2F,
-        command_encoder: &metal::RenderCommandEncoderRef,
-    ) {
-        let clip_bounds = (layer
-            .clip_bounds()
-            .unwrap_or_else(|| RectF::new(vec2f(0., 0.), drawable_size / scene.scale_factor()))
-            * scene.scale_factor())
-        .round();
-        command_encoder.set_scissor_rect(metal::MTLScissorRect {
-            x: clip_bounds.origin_x() as NSUInteger,
-            y: clip_bounds.origin_y() as NSUInteger,
-            width: clip_bounds.width() as NSUInteger,
-            height: clip_bounds.height() as NSUInteger,
-        });
-    }
-
-    fn render_shadows(
-        &mut self,
-        shadows: &[Shadow],
-        scale_factor: f32,
-        offset: &mut usize,
-        drawable_size: Vector2F,
-        command_encoder: &metal::RenderCommandEncoderRef,
-    ) {
-        if shadows.is_empty() {
-            return;
-        }
-
-        align_offset(offset);
-        let next_offset = *offset + shadows.len() * mem::size_of::<shaders::GPUIShadow>();
-        assert!(
-            next_offset <= INSTANCE_BUFFER_SIZE,
-            "instance buffer exhausted"
-        );
-
-        command_encoder.set_render_pipeline_state(&self.shadow_pipeline_state);
-        command_encoder.set_vertex_buffer(
-            shaders::GPUIShadowInputIndex_GPUIShadowInputIndexVertices as u64,
-            Some(&self.unit_vertices),
-            0,
-        );
-        command_encoder.set_vertex_buffer(
-            shaders::GPUIShadowInputIndex_GPUIShadowInputIndexShadows as u64,
-            Some(&self.instances),
-            *offset as u64,
-        );
-        command_encoder.set_vertex_bytes(
-            shaders::GPUIShadowInputIndex_GPUIShadowInputIndexUniforms as u64,
-            mem::size_of::<shaders::GPUIUniforms>() as u64,
-            [shaders::GPUIUniforms {
-                viewport_size: drawable_size.to_float2(),
-            }]
-            .as_ptr() as *const c_void,
-        );
-
-        let buffer_contents = unsafe {
-            (self.instances.contents() as *mut u8).add(*offset) as *mut shaders::GPUIShadow
-        };
-        for (ix, shadow) in shadows.iter().enumerate() {
-            let shape_bounds = shadow.bounds * scale_factor;
-            let corner_radii = shadow.corner_radii * scale_factor;
-            let shader_shadow = shaders::GPUIShadow {
-                origin: shape_bounds.origin().to_float2(),
-                size: shape_bounds.size().to_float2(),
-                corner_radius_top_left: corner_radii.top_left,
-                corner_radius_top_right: corner_radii.top_right,
-                corner_radius_bottom_right: corner_radii.bottom_right,
-                corner_radius_bottom_left: corner_radii.bottom_left,
-                sigma: shadow.sigma,
-                color: shadow.color.to_uchar4(),
-            };
-            unsafe {
-                *(buffer_contents.add(ix)) = shader_shadow;
-            }
-        }
-
-        command_encoder.draw_primitives_instanced(
-            metal::MTLPrimitiveType::Triangle,
-            0,
-            6,
-            shadows.len() as u64,
-        );
-        *offset = next_offset;
-    }
-
-    fn render_quads(
-        &mut self,
-        quads: &[Quad],
-        scale_factor: f32,
-        offset: &mut usize,
-        drawable_size: Vector2F,
-        command_encoder: &metal::RenderCommandEncoderRef,
-    ) {
-        if quads.is_empty() {
-            return;
-        }
-        align_offset(offset);
-        let next_offset = *offset + quads.len() * mem::size_of::<shaders::GPUIQuad>();
-        assert!(
-            next_offset <= INSTANCE_BUFFER_SIZE,
-            "instance buffer exhausted"
-        );
-
-        command_encoder.set_render_pipeline_state(&self.quad_pipeline_state);
-        command_encoder.set_vertex_buffer(
-            shaders::GPUIQuadInputIndex_GPUIQuadInputIndexVertices as u64,
-            Some(&self.unit_vertices),
-            0,
-        );
-        command_encoder.set_vertex_buffer(
-            shaders::GPUIQuadInputIndex_GPUIQuadInputIndexQuads as u64,
-            Some(&self.instances),
-            *offset as u64,
-        );
-        command_encoder.set_vertex_bytes(
-            shaders::GPUIQuadInputIndex_GPUIQuadInputIndexUniforms as u64,
-            mem::size_of::<shaders::GPUIUniforms>() as u64,
-            [shaders::GPUIUniforms {
-                viewport_size: drawable_size.to_float2(),
-            }]
-            .as_ptr() as *const c_void,
-        );
-
-        let buffer_contents = unsafe {
-            (self.instances.contents() as *mut u8).add(*offset) as *mut shaders::GPUIQuad
-        };
-        for (ix, quad) in quads.iter().enumerate() {
-            let bounds = quad.bounds * scale_factor;
-            let shader_quad = shaders::GPUIQuad {
-                origin: bounds.origin().round().to_float2(),
-                size: bounds.size().round().to_float2(),
-                background_color: quad
-                    .background
-                    .unwrap_or_else(Color::transparent_black)
-                    .to_uchar4(),
-                border_top: quad.border.top * scale_factor,
-                border_right: quad.border.right * scale_factor,
-                border_bottom: quad.border.bottom * scale_factor,
-                border_left: quad.border.left * scale_factor,
-                border_color: quad.border.color.to_uchar4(),
-                corner_radius_top_left: quad.corner_radii.top_left * scale_factor,
-                corner_radius_top_right: quad.corner_radii.top_right * scale_factor,
-                corner_radius_bottom_right: quad.corner_radii.bottom_right * scale_factor,
-                corner_radius_bottom_left: quad.corner_radii.bottom_left * scale_factor,
-            };
-            unsafe {
-                *(buffer_contents.add(ix)) = shader_quad;
-            }
-        }
-
-        command_encoder.draw_primitives_instanced(
-            metal::MTLPrimitiveType::Triangle,
-            0,
-            6,
-            quads.len() as u64,
-        );
-        *offset = next_offset;
-    }
-
-    fn render_sprites(
-        &mut self,
-        glyphs: &[Glyph],
-        icons: &[Icon],
-        scale_factor: f32,
-        offset: &mut usize,
-        drawable_size: Vector2F,
-        command_encoder: &metal::RenderCommandEncoderRef,
-    ) {
-        if glyphs.is_empty() && icons.is_empty() {
-            return;
-        }
-
-        let mut sprites_by_atlas = HashMap::new();
-
-        for glyph in glyphs {
-            if let Some(sprite) = self.sprite_cache.render_glyph(
-                glyph.font_id,
-                glyph.font_size,
-                glyph.id,
-                glyph.origin,
-            ) {
-                // Snap sprite to pixel grid.
-                let origin = (glyph.origin * scale_factor).floor() + sprite.offset.to_f32();
-
-                sprites_by_atlas
-                    .entry(sprite.atlas_id)
-                    .or_insert_with(Vec::new)
-                    .push(shaders::GPUISprite {
-                        origin: origin.to_float2(),
-                        target_size: sprite.size.to_float2(),
-                        source_size: sprite.size.to_float2(),
-                        atlas_origin: sprite.atlas_origin.to_float2(),
-                        color: glyph.color.to_uchar4(),
-                        compute_winding: 0,
-                    });
-            }
-        }
-
-        for icon in icons {
-            // Snap sprite to pixel grid.
-            let origin = (icon.bounds.origin() * scale_factor).floor();
-            let target_size = (icon.bounds.size() * scale_factor).ceil();
-            let source_size = (target_size * 2.).to_i32();
-
-            let sprite =
-                self.sprite_cache
-                    .render_icon(source_size, icon.path.clone(), icon.svg.clone());
-            if sprite.is_none() {
-                continue;
-            }
-            let sprite = sprite.unwrap();
-
-            sprites_by_atlas
-                .entry(sprite.atlas_id)
-                .or_insert_with(Vec::new)
-                .push(shaders::GPUISprite {
-                    origin: origin.to_float2(),
-                    target_size: target_size.to_float2(),
-                    source_size: sprite.size.to_float2(),
-                    atlas_origin: sprite.atlas_origin.to_float2(),
-                    color: icon.color.to_uchar4(),
-                    compute_winding: 0,
-                });
-        }
-
-        command_encoder.set_render_pipeline_state(&self.sprite_pipeline_state);
-        command_encoder.set_vertex_buffer(
-            shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexVertices as u64,
-            Some(&self.unit_vertices),
-            0,
-        );
-        command_encoder.set_vertex_bytes(
-            shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexViewportSize as u64,
-            mem::size_of::<shaders::vector_float2>() as u64,
-            [drawable_size.to_float2()].as_ptr() as *const c_void,
-        );
-
-        for (atlas_id, sprites) in sprites_by_atlas {
-            align_offset(offset);
-            let next_offset = *offset + sprites.len() * mem::size_of::<shaders::GPUISprite>();
-            assert!(
-                next_offset <= INSTANCE_BUFFER_SIZE,
-                "instance buffer exhausted"
-            );
-
-            let texture = self.sprite_cache.atlas_texture(atlas_id).unwrap();
-            command_encoder.set_vertex_buffer(
-                shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexSprites as u64,
-                Some(&self.instances),
-                *offset as u64,
-            );
-            command_encoder.set_vertex_bytes(
-                shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexAtlasSize as u64,
-                mem::size_of::<shaders::vector_float2>() as u64,
-                [vec2i(texture.width() as i32, texture.height() as i32).to_float2()].as_ptr()
-                    as *const c_void,
-            );
-
-            command_encoder.set_fragment_texture(
-                shaders::GPUISpriteFragmentInputIndex_GPUISpriteFragmentInputIndexAtlas as u64,
-                Some(texture),
-            );
-
-            unsafe {
-                let buffer_contents =
-                    (self.instances.contents() as *mut u8).add(*offset) as *mut shaders::GPUISprite;
-                std::ptr::copy_nonoverlapping(sprites.as_ptr(), buffer_contents, sprites.len());
-            }
-
-            command_encoder.draw_primitives_instanced(
-                metal::MTLPrimitiveType::Triangle,
-                0,
-                6,
-                sprites.len() as u64,
-            );
-            *offset = next_offset;
-        }
-    }
-
-    fn render_images(
-        &mut self,
-        images: &[Image],
-        image_glyphs: &[ImageGlyph],
-        scale_factor: f32,
-        offset: &mut usize,
-        drawable_size: Vector2F,
-        command_encoder: &metal::RenderCommandEncoderRef,
-    ) {
-        if images.is_empty() && image_glyphs.is_empty() {
-            return;
-        }
-
-        let mut images_by_atlas = HashMap::new();
-        for image in images {
-            let origin = image.bounds.origin() * scale_factor;
-            let target_size = image.bounds.size() * scale_factor;
-            let corner_radii = image.corner_radii * scale_factor;
-            let (alloc_id, atlas_bounds) = self.image_cache.render(&image.data);
-            images_by_atlas
-                .entry(alloc_id.atlas_id)
-                .or_insert_with(Vec::new)
-                .push(shaders::GPUIImage {
-                    origin: origin.to_float2(),
-                    target_size: target_size.to_float2(),
-                    source_size: atlas_bounds.size().to_float2(),
-                    atlas_origin: atlas_bounds.origin().to_float2(),
-                    border_top: image.border.top * scale_factor,
-                    border_right: image.border.right * scale_factor,
-                    border_bottom: image.border.bottom * scale_factor,
-                    border_left: image.border.left * scale_factor,
-                    border_color: image.border.color.to_uchar4(),
-                    corner_radius_top_left: corner_radii.top_left,
-                    corner_radius_top_right: corner_radii.top_right,
-                    corner_radius_bottom_right: corner_radii.bottom_right,
-                    corner_radius_bottom_left: corner_radii.bottom_left,
-                    grayscale: image.grayscale as u8,
-                });
-        }
-
-        for image_glyph in image_glyphs {
-            let origin = (image_glyph.origin * scale_factor).floor();
-            if let Some((alloc_id, atlas_bounds, glyph_origin)) =
-                self.image_cache.render_glyph(image_glyph)
-            {
-                images_by_atlas
-                    .entry(alloc_id.atlas_id)
-                    .or_insert_with(Vec::new)
-                    .push(shaders::GPUIImage {
-                        origin: (origin + glyph_origin.to_f32()).to_float2(),
-                        target_size: atlas_bounds.size().to_float2(),
-                        source_size: atlas_bounds.size().to_float2(),
-                        atlas_origin: atlas_bounds.origin().to_float2(),
-                        border_top: 0.,
-                        border_right: 0.,
-                        border_bottom: 0.,
-                        border_left: 0.,
-                        border_color: Default::default(),
-                        corner_radius_top_left: 0.,
-                        corner_radius_top_right: 0.,
-                        corner_radius_bottom_right: 0.,
-                        corner_radius_bottom_left: 0.,
-                        grayscale: false as u8,
-                    });
-            } else {
-                log::warn!("could not render glyph with id {}", image_glyph.id);
-            }
-        }
-
-        command_encoder.set_render_pipeline_state(&self.image_pipeline_state);
-        command_encoder.set_vertex_buffer(
-            shaders::GPUIImageVertexInputIndex_GPUIImageVertexInputIndexVertices as u64,
-            Some(&self.unit_vertices),
-            0,
-        );
-        command_encoder.set_vertex_bytes(
-            shaders::GPUIImageVertexInputIndex_GPUIImageVertexInputIndexViewportSize as u64,
-            mem::size_of::<shaders::vector_float2>() as u64,
-            [drawable_size.to_float2()].as_ptr() as *const c_void,
-        );
-
-        for (atlas_id, images) in images_by_atlas {
-            align_offset(offset);
-            let next_offset = *offset + images.len() * mem::size_of::<shaders::GPUIImage>();
-            assert!(
-                next_offset <= INSTANCE_BUFFER_SIZE,
-                "instance buffer exhausted"
-            );
-
-            let texture = self.image_cache.atlas_texture(atlas_id).unwrap();
-            command_encoder.set_vertex_buffer(
-                shaders::GPUIImageVertexInputIndex_GPUIImageVertexInputIndexImages as u64,
-                Some(&self.instances),
-                *offset as u64,
-            );
-            command_encoder.set_vertex_bytes(
-                shaders::GPUIImageVertexInputIndex_GPUIImageVertexInputIndexAtlasSize as u64,
-                mem::size_of::<shaders::vector_float2>() as u64,
-                [vec2i(texture.width() as i32, texture.height() as i32).to_float2()].as_ptr()
-                    as *const c_void,
-            );
-            command_encoder.set_fragment_texture(
-                shaders::GPUIImageFragmentInputIndex_GPUIImageFragmentInputIndexAtlas as u64,
-                Some(texture),
-            );
-
-            unsafe {
-                let buffer_contents =
-                    (self.instances.contents() as *mut u8).add(*offset) as *mut shaders::GPUIImage;
-                std::ptr::copy_nonoverlapping(images.as_ptr(), buffer_contents, images.len());
-            }
-
-            command_encoder.draw_primitives_instanced(
-                metal::MTLPrimitiveType::Triangle,
-                0,
-                6,
-                images.len() as u64,
-            );
-            *offset = next_offset;
-        }
-    }
-
-    fn render_surfaces(
-        &mut self,
-        surfaces: &[Surface],
-        scale_factor: f32,
-        offset: &mut usize,
-        drawable_size: Vector2F,
-        command_encoder: &metal::RenderCommandEncoderRef,
-    ) {
-        if surfaces.is_empty() {
-            return;
-        }
-
-        command_encoder.set_render_pipeline_state(&self.surface_pipeline_state);
-        command_encoder.set_vertex_buffer(
-            shaders::GPUISurfaceVertexInputIndex_GPUISurfaceVertexInputIndexVertices as u64,
-            Some(&self.unit_vertices),
-            0,
-        );
-        command_encoder.set_vertex_bytes(
-            shaders::GPUISurfaceVertexInputIndex_GPUISurfaceVertexInputIndexViewportSize as u64,
-            mem::size_of::<shaders::vector_float2>() as u64,
-            [drawable_size.to_float2()].as_ptr() as *const c_void,
-        );
-
-        for surface in surfaces {
-            let origin = surface.bounds.origin() * scale_factor;
-            let source_size = vec2i(
-                surface.image_buffer.width() as i32,
-                surface.image_buffer.height() as i32,
-            );
-            let target_size = surface.bounds.size() * scale_factor;
-
-            assert_eq!(
-                surface.image_buffer.pixel_format_type(),
-                core_video::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
-            );
-
-            let y_texture = unsafe {
-                self.cv_texture_cache
-                    .create_texture_from_image(
-                        surface.image_buffer.as_concrete_TypeRef(),
-                        ptr::null(),
-                        MTLPixelFormat::R8Unorm,
-                        surface.image_buffer.plane_width(0),
-                        surface.image_buffer.plane_height(0),
-                        0,
-                    )
-                    .unwrap()
-            };
-            let cb_cr_texture = unsafe {
-                self.cv_texture_cache
-                    .create_texture_from_image(
-                        surface.image_buffer.as_concrete_TypeRef(),
-                        ptr::null(),
-                        MTLPixelFormat::RG8Unorm,
-                        surface.image_buffer.plane_width(1),
-                        surface.image_buffer.plane_height(1),
-                        1,
-                    )
-                    .unwrap()
-            };
-
-            align_offset(offset);
-            let next_offset = *offset + mem::size_of::<shaders::GPUISurface>();
-            assert!(
-                next_offset <= INSTANCE_BUFFER_SIZE,
-                "instance buffer exhausted"
-            );
-
-            command_encoder.set_vertex_buffer(
-                shaders::GPUISurfaceVertexInputIndex_GPUISurfaceVertexInputIndexSurfaces as u64,
-                Some(&self.instances),
-                *offset as u64,
-            );
-            command_encoder.set_vertex_bytes(
-                shaders::GPUISurfaceVertexInputIndex_GPUISurfaceVertexInputIndexAtlasSize as u64,
-                mem::size_of::<shaders::vector_float2>() as u64,
-                [source_size.to_float2()].as_ptr() as *const c_void,
-            );
-            command_encoder.set_fragment_texture(
-                shaders::GPUISurfaceFragmentInputIndex_GPUISurfaceFragmentInputIndexYAtlas as u64,
-                Some(y_texture.as_texture_ref()),
-            );
-            command_encoder.set_fragment_texture(
-                shaders::GPUISurfaceFragmentInputIndex_GPUISurfaceFragmentInputIndexCbCrAtlas
-                    as u64,
-                Some(cb_cr_texture.as_texture_ref()),
-            );
-
-            unsafe {
-                let buffer_contents = (self.instances.contents() as *mut u8).add(*offset)
-                    as *mut shaders::GPUISurface;
-                std::ptr::write(
-                    buffer_contents,
-                    shaders::GPUISurface {
-                        origin: origin.to_float2(),
-                        target_size: target_size.to_float2(),
-                        source_size: source_size.to_float2(),
-                    },
-                );
-            }
-
-            command_encoder.draw_primitives(metal::MTLPrimitiveType::Triangle, 0, 6);
-            *offset = next_offset;
-        }
-    }
-
-    fn render_path_sprites(
-        &mut self,
-        layer_id: usize,
-        sprites: &mut Peekable<vec::IntoIter<PathSprite>>,
-        offset: &mut usize,
-        drawable_size: Vector2F,
-        command_encoder: &metal::RenderCommandEncoderRef,
-    ) {
-        command_encoder.set_render_pipeline_state(&self.sprite_pipeline_state);
-        command_encoder.set_vertex_buffer(
-            shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexVertices as u64,
-            Some(&self.unit_vertices),
-            0,
-        );
-        command_encoder.set_vertex_bytes(
-            shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexViewportSize as u64,
-            mem::size_of::<shaders::vector_float2>() as u64,
-            [drawable_size.to_float2()].as_ptr() as *const c_void,
-        );
-
-        let mut atlas_id = None;
-        let mut atlas_sprite_count = 0;
-        align_offset(offset);
-
-        while let Some(sprite) = sprites.peek() {
-            if sprite.layer_id != layer_id {
-                break;
-            }
-
-            let sprite = sprites.next().unwrap();
-            if let Some(atlas_id) = atlas_id.as_mut() {
-                if sprite.atlas_id != *atlas_id {
-                    self.render_path_sprites_for_atlas(
-                        offset,
-                        *atlas_id,
-                        atlas_sprite_count,
-                        command_encoder,
-                    );
-
-                    *atlas_id = sprite.atlas_id;
-                    atlas_sprite_count = 0;
-                    align_offset(offset);
-                }
-            } else {
-                atlas_id = Some(sprite.atlas_id);
-            }
-
-            unsafe {
-                let buffer_contents =
-                    (self.instances.contents() as *mut u8).add(*offset) as *mut shaders::GPUISprite;
-                *buffer_contents.add(atlas_sprite_count) = sprite.shader_data;
-            }
-
-            atlas_sprite_count += 1;
-        }
-
-        if let Some(atlas_id) = atlas_id {
-            self.render_path_sprites_for_atlas(
-                offset,
-                atlas_id,
-                atlas_sprite_count,
-                command_encoder,
-            );
-        }
-    }
-
-    fn render_path_sprites_for_atlas(
-        &mut self,
-        offset: &mut usize,
-        atlas_id: usize,
-        sprite_count: usize,
-        command_encoder: &metal::RenderCommandEncoderRef,
-    ) {
-        let next_offset = *offset + sprite_count * mem::size_of::<shaders::GPUISprite>();
-        assert!(
-            next_offset <= INSTANCE_BUFFER_SIZE,
-            "instance buffer exhausted"
-        );
-        command_encoder.set_vertex_buffer(
-            shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexSprites as u64,
-            Some(&self.instances),
-            *offset as u64,
-        );
-        let texture = self.path_atlases.texture(atlas_id).unwrap();
-        command_encoder.set_fragment_texture(
-            shaders::GPUISpriteFragmentInputIndex_GPUISpriteFragmentInputIndexAtlas as u64,
-            Some(texture),
-        );
-        command_encoder.set_vertex_bytes(
-            shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexAtlasSize as u64,
-            mem::size_of::<shaders::vector_float2>() as u64,
-            [vec2i(texture.width() as i32, texture.height() as i32).to_float2()].as_ptr()
-                as *const c_void,
-        );
-
-        command_encoder.draw_primitives_instanced(
-            metal::MTLPrimitiveType::Triangle,
-            0,
-            6,
-            sprite_count as u64,
-        );
-        *offset = next_offset;
-    }
-
-    fn render_underlines(
-        &mut self,
-        underlines: &[Underline],
-        scale_factor: f32,
-        offset: &mut usize,
-        drawable_size: Vector2F,
-        command_encoder: &metal::RenderCommandEncoderRef,
-    ) {
-        if underlines.is_empty() {
-            return;
-        }
-        align_offset(offset);
-        let next_offset = *offset + underlines.len() * mem::size_of::<shaders::GPUIUnderline>();
-        assert!(
-            next_offset <= INSTANCE_BUFFER_SIZE,
-            "instance buffer exhausted"
-        );
-
-        command_encoder.set_render_pipeline_state(&self.underline_pipeline_state);
-        command_encoder.set_vertex_buffer(
-            shaders::GPUIUnderlineInputIndex_GPUIUnderlineInputIndexVertices as u64,
-            Some(&self.unit_vertices),
-            0,
-        );
-        command_encoder.set_vertex_buffer(
-            shaders::GPUIUnderlineInputIndex_GPUIUnderlineInputIndexUnderlines as u64,
-            Some(&self.instances),
-            *offset as u64,
-        );
-        command_encoder.set_vertex_bytes(
-            shaders::GPUIUnderlineInputIndex_GPUIUnderlineInputIndexUniforms as u64,
-            mem::size_of::<shaders::GPUIUniforms>() as u64,
-            [shaders::GPUIUniforms {
-                viewport_size: drawable_size.to_float2(),
-            }]
-            .as_ptr() as *const c_void,
-        );
-
-        let buffer_contents = unsafe {
-            (self.instances.contents() as *mut u8).add(*offset) as *mut shaders::GPUIUnderline
-        };
-        for (ix, underline) in underlines.iter().enumerate() {
-            let origin = underline.origin * scale_factor;
-            let mut height = underline.thickness;
-            if underline.squiggly {
-                height *= 3.;
-            }
-            let size = vec2f(underline.width, height) * scale_factor;
-            let shader_underline = shaders::GPUIUnderline {
-                origin: origin.round().to_float2(),
-                size: size.round().to_float2(),
-                thickness: underline.thickness * scale_factor,
-                color: underline.color.to_uchar4(),
-                squiggly: underline.squiggly as u8,
-            };
-            unsafe {
-                *(buffer_contents.add(ix)) = shader_underline;
-            }
-        }
-
-        command_encoder.draw_primitives_instanced(
-            metal::MTLPrimitiveType::Triangle,
-            0,
-            6,
-            underlines.len() as u64,
-        );
-        *offset = next_offset;
-    }
-}
-
-fn build_path_atlas_texture_descriptor() -> metal::TextureDescriptor {
-    let texture_descriptor = metal::TextureDescriptor::new();
-    texture_descriptor.set_width(2048);
-    texture_descriptor.set_height(2048);
-    texture_descriptor.set_pixel_format(MTLPixelFormat::R16Float);
-    texture_descriptor
-        .set_usage(metal::MTLTextureUsage::RenderTarget | metal::MTLTextureUsage::ShaderRead);
-    texture_descriptor.set_storage_mode(metal::MTLStorageMode::Private);
-    texture_descriptor
-}
-
-fn align_offset(offset: &mut usize) {
-    let r = *offset % 256;
-    if r > 0 {
-        *offset += 256 - r; // Align to a multiple of 256 to make Metal happy
-    }
-}
-
-fn build_pipeline_state(
-    device: &metal::DeviceRef,
-    library: &metal::LibraryRef,
-    label: &str,
-    vertex_fn_name: &str,
-    fragment_fn_name: &str,
-    pixel_format: metal::MTLPixelFormat,
-) -> metal::RenderPipelineState {
-    let vertex_fn = library
-        .get_function(vertex_fn_name, None)
-        .expect("error locating vertex function");
-    let fragment_fn = library
-        .get_function(fragment_fn_name, None)
-        .expect("error locating fragment function");
-
-    let descriptor = metal::RenderPipelineDescriptor::new();
-    descriptor.set_label(label);
-    descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
-    descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
-    let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
-    color_attachment.set_pixel_format(pixel_format);
-    color_attachment.set_blending_enabled(true);
-    color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
-    color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
-    color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::SourceAlpha);
-    color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
-    color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
-    color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
-
-    device
-        .new_render_pipeline_state(&descriptor)
-        .expect("could not create render pipeline state")
-}
-
-fn build_path_atlas_pipeline_state(
-    device: &metal::DeviceRef,
-    library: &metal::LibraryRef,
-    label: &str,
-    vertex_fn_name: &str,
-    fragment_fn_name: &str,
-    pixel_format: metal::MTLPixelFormat,
-) -> metal::RenderPipelineState {
-    let vertex_fn = library
-        .get_function(vertex_fn_name, None)
-        .expect("error locating vertex function");
-    let fragment_fn = library
-        .get_function(fragment_fn_name, None)
-        .expect("error locating fragment function");
-
-    let descriptor = metal::RenderPipelineDescriptor::new();
-    descriptor.set_label(label);
-    descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
-    descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
-    let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
-    color_attachment.set_pixel_format(pixel_format);
-    color_attachment.set_blending_enabled(true);
-    color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
-    color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
-    color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::One);
-    color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
-    color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::One);
-    color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
-
-    device
-        .new_render_pipeline_state(&descriptor)
-        .expect("could not create render pipeline state")
-}
-
-mod shaders {
-    #![allow(non_upper_case_globals)]
-    #![allow(non_camel_case_types)]
-    #![allow(non_snake_case)]
-
-    use crate::{
-        color::Color,
-        geometry::vector::{Vector2F, Vector2I},
-    };
-    use std::mem;
-
-    include!(concat!(env!("OUT_DIR"), "/shaders.rs"));
-
-    pub trait ToFloat2 {
-        fn to_float2(&self) -> vector_float2;
-    }
-
-    impl ToFloat2 for (f32, f32) {
-        fn to_float2(&self) -> vector_float2 {
-            unsafe {
-                let mut output = mem::transmute::<_, u32>(self.1.to_bits()) as vector_float2;
-                output <<= 32;
-                output |= mem::transmute::<_, u32>(self.0.to_bits()) as vector_float2;
-                output
-            }
-        }
-    }
-
-    impl ToFloat2 for Vector2F {
-        fn to_float2(&self) -> vector_float2 {
-            unsafe {
-                let mut output = mem::transmute::<_, u32>(self.y().to_bits()) as vector_float2;
-                output <<= 32;
-                output |= mem::transmute::<_, u32>(self.x().to_bits()) as vector_float2;
-                output
-            }
-        }
-    }
-
-    impl ToFloat2 for Vector2I {
-        fn to_float2(&self) -> vector_float2 {
-            self.to_f32().to_float2()
-        }
-    }
-
-    impl Color {
-        pub fn to_uchar4(&self) -> vector_uchar4 {
-            let mut vec = self.a as vector_uchar4;
-            vec <<= 8;
-            vec |= self.b as vector_uchar4;
-            vec <<= 8;
-            vec |= self.g as vector_uchar4;
-            vec <<= 8;
-            vec |= self.r as vector_uchar4;
-            vec
-        }
-    }
-}

crates/gpui/src/platform/mac/screen.rs 🔗

@@ -1,144 +0,0 @@
-use super::ns_string;
-use crate::platform;
-use cocoa::{
-    appkit::NSScreen,
-    base::{id, nil},
-    foundation::{NSArray, NSDictionary, NSPoint, NSRect, NSSize},
-};
-use core_foundation::{
-    number::{kCFNumberIntType, CFNumberGetValue, CFNumberRef},
-    uuid::{CFUUIDGetUUIDBytes, CFUUIDRef},
-};
-use core_graphics::display::CGDirectDisplayID;
-use pathfinder_geometry::{rect::RectF, vector::vec2f};
-use std::{any::Any, ffi::c_void};
-use uuid::Uuid;
-
-#[link(name = "ApplicationServices", kind = "framework")]
-extern "C" {
-    pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
-}
-
-#[derive(Debug)]
-pub struct Screen {
-    pub(crate) native_screen: id,
-}
-
-impl Screen {
-    /// Get the screen with the given UUID.
-    pub fn find_by_id(uuid: Uuid) -> Option<Self> {
-        Self::all().find(|screen| platform::Screen::display_uuid(screen) == Some(uuid))
-    }
-
-    /// Get the primary screen - the one with the menu bar, and whose bottom left
-    /// corner is at the origin of the AppKit coordinate system.
-    fn primary() -> Self {
-        Self::all().next().unwrap()
-    }
-
-    pub fn all() -> impl Iterator<Item = Self> {
-        unsafe {
-            let native_screens = NSScreen::screens(nil);
-            (0..NSArray::count(native_screens)).map(move |ix| Screen {
-                native_screen: native_screens.objectAtIndex(ix),
-            })
-        }
-    }
-
-    /// Convert the given rectangle in screen coordinates from GPUI's
-    /// coordinate system to the AppKit coordinate system.
-    ///
-    /// In GPUI's coordinates, the origin is at the top left of the primary screen, with
-    /// the Y axis pointing downward. In the AppKit coordindate system, the origin is at the
-    /// bottom left of the primary screen, with the Y axis pointing upward.
-    pub(crate) fn screen_rect_to_native(rect: RectF) -> NSRect {
-        let primary_screen_height = unsafe { Self::primary().native_screen.frame().size.height };
-        NSRect::new(
-            NSPoint::new(
-                rect.origin_x() as f64,
-                primary_screen_height - rect.origin_y() as f64 - rect.height() as f64,
-            ),
-            NSSize::new(rect.width() as f64, rect.height() as f64),
-        )
-    }
-
-    /// Convert the given rectangle in screen coordinates from the AppKit
-    /// coordinate system to GPUI's coordinate system.
-    ///
-    /// In GPUI's coordinates, the origin is at the top left of the primary screen, with
-    /// the Y axis pointing downward. In the AppKit coordindate system, the origin is at the
-    /// bottom left of the primary screen, with the Y axis pointing upward.
-    pub(crate) fn screen_rect_from_native(rect: NSRect) -> RectF {
-        let primary_screen_height = unsafe { Self::primary().native_screen.frame().size.height };
-        RectF::new(
-            vec2f(
-                rect.origin.x as f32,
-                (primary_screen_height - rect.origin.y - rect.size.height) as f32,
-            ),
-            vec2f(rect.size.width as f32, rect.size.height as f32),
-        )
-    }
-}
-
-impl platform::Screen for Screen {
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
-    fn display_uuid(&self) -> Option<uuid::Uuid> {
-        unsafe {
-            // Screen ids are not stable. Further, the default device id is also unstable across restarts.
-            // CGDisplayCreateUUIDFromDisplayID is stable but not exposed in the bindings we use.
-            // This approach is similar to that which winit takes
-            // https://github.com/rust-windowing/winit/blob/402cbd55f932e95dbfb4e8b5e8551c49e56ff9ac/src/platform_impl/macos/monitor.rs#L99
-            let device_description = self.native_screen.deviceDescription();
-
-            let key = ns_string("NSScreenNumber");
-            let device_id_obj = device_description.objectForKey_(key);
-            if device_id_obj.is_null() {
-                // Under some circumstances, especially display re-arrangements or display locking, we seem to get a null pointer
-                // to the device id. See: https://linear.app/zed-industries/issue/Z-257/lock-screen-crash-with-multiple-monitors
-                return None;
-            }
-
-            let mut device_id: u32 = 0;
-            CFNumberGetValue(
-                device_id_obj as CFNumberRef,
-                kCFNumberIntType,
-                (&mut device_id) as *mut _ as *mut c_void,
-            );
-            let cfuuid = CGDisplayCreateUUIDFromDisplayID(device_id as CGDirectDisplayID);
-            if cfuuid.is_null() {
-                return None;
-            }
-
-            let bytes = CFUUIDGetUUIDBytes(cfuuid);
-            Some(Uuid::from_bytes([
-                bytes.byte0,
-                bytes.byte1,
-                bytes.byte2,
-                bytes.byte3,
-                bytes.byte4,
-                bytes.byte5,
-                bytes.byte6,
-                bytes.byte7,
-                bytes.byte8,
-                bytes.byte9,
-                bytes.byte10,
-                bytes.byte11,
-                bytes.byte12,
-                bytes.byte13,
-                bytes.byte14,
-                bytes.byte15,
-            ]))
-        }
-    }
-
-    fn bounds(&self) -> RectF {
-        unsafe { Self::screen_rect_from_native(self.native_screen.frame()) }
-    }
-
-    fn content_bounds(&self) -> RectF {
-        unsafe { Self::screen_rect_from_native(self.native_screen.visibleFrame()) }
-    }
-}

crates/gpui/src/platform/mac/shaders/shaders.h 🔗

@@ -1,135 +0,0 @@
-#include <simd/simd.h>
-
-typedef struct {
-  vector_float2 viewport_size;
-} GPUIUniforms;
-
-typedef enum {
-  GPUIQuadInputIndexVertices = 0,
-  GPUIQuadInputIndexQuads = 1,
-  GPUIQuadInputIndexUniforms = 2,
-} GPUIQuadInputIndex;
-
-typedef struct {
-  vector_float2 origin;
-  vector_float2 size;
-  vector_uchar4 background_color;
-  float border_top;
-  float border_right;
-  float border_bottom;
-  float border_left;
-  vector_uchar4 border_color;
-  float corner_radius_top_left;
-  float corner_radius_top_right;
-  float corner_radius_bottom_right;
-  float corner_radius_bottom_left;
-} GPUIQuad;
-
-typedef enum {
-  GPUIShadowInputIndexVertices = 0,
-  GPUIShadowInputIndexShadows = 1,
-  GPUIShadowInputIndexUniforms = 2,
-} GPUIShadowInputIndex;
-
-typedef struct {
-  vector_float2 origin;
-  vector_float2 size;
-  float corner_radius_top_left;
-  float corner_radius_top_right;
-  float corner_radius_bottom_right;
-  float corner_radius_bottom_left;
-  float sigma;
-  vector_uchar4 color;
-} GPUIShadow;
-
-typedef enum {
-  GPUISpriteVertexInputIndexVertices = 0,
-  GPUISpriteVertexInputIndexSprites = 1,
-  GPUISpriteVertexInputIndexViewportSize = 2,
-  GPUISpriteVertexInputIndexAtlasSize = 3,
-} GPUISpriteVertexInputIndex;
-
-typedef enum {
-  GPUISpriteFragmentInputIndexAtlas = 0,
-} GPUISpriteFragmentInputIndex;
-
-typedef struct {
-  vector_float2 origin;
-  vector_float2 target_size;
-  vector_float2 source_size;
-  vector_float2 atlas_origin;
-  vector_uchar4 color;
-  uint8_t compute_winding;
-} GPUISprite;
-
-typedef enum {
-  GPUIPathAtlasVertexInputIndexVertices = 0,
-  GPUIPathAtlasVertexInputIndexAtlasSize = 1,
-} GPUIPathAtlasVertexInputIndex;
-
-typedef struct {
-  vector_float2 xy_position;
-  vector_float2 st_position;
-  vector_float2 clip_rect_origin;
-  vector_float2 clip_rect_size;
-} GPUIPathVertex;
-
-typedef enum {
-  GPUIImageVertexInputIndexVertices = 0,
-  GPUIImageVertexInputIndexImages = 1,
-  GPUIImageVertexInputIndexViewportSize = 2,
-  GPUIImageVertexInputIndexAtlasSize = 3,
-} GPUIImageVertexInputIndex;
-
-typedef enum {
-  GPUIImageFragmentInputIndexAtlas = 0,
-} GPUIImageFragmentInputIndex;
-
-typedef struct {
-  vector_float2 origin;
-  vector_float2 target_size;
-  vector_float2 source_size;
-  vector_float2 atlas_origin;
-  float border_top;
-  float border_right;
-  float border_bottom;
-  float border_left;
-  vector_uchar4 border_color;
-  float corner_radius_top_left;
-  float corner_radius_top_right;
-  float corner_radius_bottom_right;
-  float corner_radius_bottom_left;
-  uint8_t grayscale;
-} GPUIImage;
-
-typedef enum {
-  GPUISurfaceVertexInputIndexVertices = 0,
-  GPUISurfaceVertexInputIndexSurfaces = 1,
-  GPUISurfaceVertexInputIndexViewportSize = 2,
-  GPUISurfaceVertexInputIndexAtlasSize = 3,
-} GPUISurfaceVertexInputIndex;
-
-typedef enum {
-  GPUISurfaceFragmentInputIndexYAtlas = 0,
-  GPUISurfaceFragmentInputIndexCbCrAtlas = 1,
-} GPUISurfaceFragmentInputIndex;
-
-typedef struct {
-  vector_float2 origin;
-  vector_float2 target_size;
-  vector_float2 source_size;
-} GPUISurface;
-
-typedef enum {
-  GPUIUnderlineInputIndexVertices = 0,
-  GPUIUnderlineInputIndexUnderlines = 1,
-  GPUIUnderlineInputIndexUniforms = 2,
-} GPUIUnderlineInputIndex;
-
-typedef struct {
-  vector_float2 origin;
-  vector_float2 size;
-  float thickness;
-  vector_uchar4 color;
-  uint8_t squiggly;
-} GPUIUnderline;

crates/gpui/src/platform/mac/shaders/shaders.metal 🔗

@@ -1,464 +0,0 @@
-#include <metal_stdlib>
-#include "shaders.h"
-
-using namespace metal;
-
-float4 coloru_to_colorf(uchar4 coloru) {
-    return float4(coloru) / float4(0xff, 0xff, 0xff, 0xff);
-}
-
-float4 to_device_position(float2 pixel_position, float2 viewport_size) {
-    return float4(pixel_position / viewport_size * float2(2., -2.) + float2(-1., 1.), 0., 1.);
-}
-
-// A standard gaussian function, used for weighting samples
-float gaussian(float x, float sigma) {
-    return exp(-(x * x) / (2. * sigma * sigma)) / (sqrt(2. * M_PI_F) * sigma);
-}
-
-// This approximates the error function, needed for the gaussian integral
-float2 erf(float2 x) {
-    float2 s = sign(x);
-    float2 a = abs(x);
-    x = 1. + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a;
-    x *= x;
-    return s - s / (x * x);
-}
-
-float blur_along_x(float x, float y, float sigma, float corner, float2 halfSize) {
-    float delta = min(halfSize.y - corner - abs(y), 0.);
-    float curved = halfSize.x - corner + sqrt(max(0., corner * corner - delta * delta));
-    float2 integral = 0.5 + 0.5 * erf((x + float2(-curved, curved)) * (sqrt(0.5) / sigma));
-    return integral.y - integral.x;
-}
-
-struct QuadFragmentInput {
-    float4 position [[position]];
-    float2 atlas_position; // only used in the image shader
-    float2 origin;
-    float2 size;
-    float4 background_color;
-    float border_top;
-    float border_right;
-    float border_bottom;
-    float border_left;
-    float4 border_color;
-    float corner_radius_top_left;
-    float corner_radius_top_right;
-    float corner_radius_bottom_right;
-    float corner_radius_bottom_left;
-    uchar grayscale; // only used in image shader
-};
-
-float4 quad_sdf(QuadFragmentInput input) {
-    float2 half_size = input.size / 2.;
-    float2 center = input.origin + half_size;
-    float2 center_to_point = input.position.xy - center;
-    float corner_radius;
-    if (center_to_point.x < 0.) {
-        if (center_to_point.y < 0.) {
-            corner_radius = input.corner_radius_top_left;
-        } else {
-            corner_radius = input.corner_radius_bottom_left;
-        }
-    } else {
-        if (center_to_point.y < 0.) {
-            corner_radius = input.corner_radius_top_right;
-        } else {
-            corner_radius = input.corner_radius_bottom_right;
-        }
-    }
-
-    float2 rounded_edge_to_point = abs(center_to_point) - half_size + corner_radius;
-    float distance = length(max(0., rounded_edge_to_point)) + min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) - corner_radius;
-
-    float vertical_border = center_to_point.x <= 0. ? input.border_left : input.border_right;
-    float horizontal_border = center_to_point.y <= 0. ? input.border_top : input.border_bottom;
-    float2 inset_size = half_size - corner_radius - float2(vertical_border, horizontal_border);
-    float2 point_to_inset_corner = abs(center_to_point) - inset_size;
-    float border_width;
-    if (point_to_inset_corner.x < 0. && point_to_inset_corner.y < 0.) {
-        border_width = 0.;
-    } else if (point_to_inset_corner.y > point_to_inset_corner.x) {
-        border_width = horizontal_border;
-    } else {
-        border_width = vertical_border;
-    }
-
-    float4 color;
-    if (border_width == 0.) {
-        color = input.background_color;
-    } else {
-        float inset_distance = distance + border_width;
-
-        // Decrease border's opacity as we move inside the background.
-        input.border_color.a *= 1. - saturate(0.5 - inset_distance);
-
-        // Alpha-blend the border and the background.
-        float output_alpha = input.border_color.a + input.background_color.a * (1. - input.border_color.a);
-        float3 premultiplied_border_rgb = input.border_color.rgb * input.border_color.a;
-        float3 premultiplied_background_rgb = input.background_color.rgb * input.background_color.a;
-        float3 premultiplied_output_rgb = premultiplied_border_rgb + premultiplied_background_rgb * (1. - input.border_color.a);
-        color = float4(premultiplied_output_rgb / output_alpha, output_alpha);
-    }
-
-    return color * float4(1., 1., 1., saturate(0.5 - distance));
-}
-
-vertex QuadFragmentInput quad_vertex(
-    uint unit_vertex_id [[vertex_id]],
-    uint quad_id [[instance_id]],
-    constant float2 *unit_vertices [[buffer(GPUIQuadInputIndexVertices)]],
-    constant GPUIQuad *quads [[buffer(GPUIQuadInputIndexQuads)]],
-    constant GPUIUniforms *uniforms [[buffer(GPUIQuadInputIndexUniforms)]]
-) {
-    float2 unit_vertex = unit_vertices[unit_vertex_id];
-    GPUIQuad quad = quads[quad_id];
-    float2 position = unit_vertex * quad.size + quad.origin;
-    float4 device_position = to_device_position(position, uniforms->viewport_size);
-
-    return QuadFragmentInput {
-        device_position,
-        float2(0., 0.),
-        quad.origin,
-        quad.size,
-        coloru_to_colorf(quad.background_color),
-        quad.border_top,
-        quad.border_right,
-        quad.border_bottom,
-        quad.border_left,
-        coloru_to_colorf(quad.border_color),
-        quad.corner_radius_top_left,
-        quad.corner_radius_top_right,
-        quad.corner_radius_bottom_right,
-        quad.corner_radius_bottom_left,
-        0,
-    };
-}
-
-fragment float4 quad_fragment(
-    QuadFragmentInput input [[stage_in]]
-) {
-    return quad_sdf(input);
-}
-
-struct ShadowFragmentInput {
-    float4 position [[position]];
-    vector_float2 origin;
-    vector_float2 size;
-    float corner_radius_top_left;
-    float corner_radius_top_right;
-    float corner_radius_bottom_right;
-    float corner_radius_bottom_left;
-    float sigma;
-    vector_uchar4 color;
-};
-
-vertex ShadowFragmentInput shadow_vertex(
-    uint unit_vertex_id [[vertex_id]],
-    uint shadow_id [[instance_id]],
-    constant float2 *unit_vertices [[buffer(GPUIShadowInputIndexVertices)]],
-    constant GPUIShadow *shadows [[buffer(GPUIShadowInputIndexShadows)]],
-    constant GPUIUniforms *uniforms [[buffer(GPUIShadowInputIndexUniforms)]]
-) {
-    float2 unit_vertex = unit_vertices[unit_vertex_id];
-    GPUIShadow shadow = shadows[shadow_id];
-
-    float margin = 3. * shadow.sigma;
-    float2 position = unit_vertex * (shadow.size + 2. * margin) + shadow.origin - margin;
-    float4 device_position = to_device_position(position, uniforms->viewport_size);
-
-    return ShadowFragmentInput {
-        device_position,
-        shadow.origin,
-        shadow.size,
-        shadow.corner_radius_top_left,
-        shadow.corner_radius_top_right,
-        shadow.corner_radius_bottom_right,
-        shadow.corner_radius_bottom_left,
-        shadow.sigma,
-        shadow.color,
-    };
-}
-
-fragment float4 shadow_fragment(
-    ShadowFragmentInput input [[stage_in]]
-) {
-    float sigma = input.sigma;
-    float2 half_size = input.size / 2.;
-    float2 center = input.origin + half_size;
-    float2 point = input.position.xy - center;
-    float2 center_to_point = input.position.xy - center;
-    float corner_radius;
-    if (center_to_point.x < 0.) {
-        if (center_to_point.y < 0.) {
-            corner_radius = input.corner_radius_top_left;
-        } else {
-            corner_radius = input.corner_radius_bottom_left;
-        }
-    } else {
-        if (center_to_point.y < 0.) {
-            corner_radius = input.corner_radius_top_right;
-        } else {
-            corner_radius = input.corner_radius_bottom_right;
-        }
-    }
-
-    // The signal is only non-zero in a limited range, so don't waste samples
-    float low = point.y - half_size.y;
-    float high = point.y + half_size.y;
-    float start = clamp(-3. * sigma, low, high);
-    float end = clamp(3. * sigma, low, high);
-
-    // Accumulate samples (we can get away with surprisingly few samples)
-    float step = (end - start) / 4.;
-    float y = start + step * 0.5;
-    float alpha = 0.;
-    for (int i = 0; i < 4; i++) {
-        alpha += blur_along_x(point.x, point.y - y, sigma, corner_radius, half_size) * gaussian(y, sigma) * step;
-        y += step;
-    }
-
-    return float4(1., 1., 1., alpha) * coloru_to_colorf(input.color);
-}
-
-struct SpriteFragmentInput {
-    float4 position [[position]];
-    float2 atlas_position;
-    float4 color [[flat]];
-    uchar compute_winding [[flat]];
-};
-
-vertex SpriteFragmentInput sprite_vertex(
-    uint unit_vertex_id [[vertex_id]],
-    uint sprite_id [[instance_id]],
-    constant float2 *unit_vertices [[buffer(GPUISpriteVertexInputIndexVertices)]],
-    constant GPUISprite *sprites [[buffer(GPUISpriteVertexInputIndexSprites)]],
-    constant float2 *viewport_size [[buffer(GPUISpriteVertexInputIndexViewportSize)]],
-    constant float2 *atlas_size [[buffer(GPUISpriteVertexInputIndexAtlasSize)]]
-) {
-    float2 unit_vertex = unit_vertices[unit_vertex_id];
-    GPUISprite sprite = sprites[sprite_id];
-    float2 position = unit_vertex * sprite.target_size + sprite.origin;
-    float4 device_position = to_device_position(position, *viewport_size);
-    float2 atlas_position = (unit_vertex * sprite.source_size + sprite.atlas_origin) / *atlas_size;
-
-    return SpriteFragmentInput {
-        device_position,
-        atlas_position,
-        coloru_to_colorf(sprite.color),
-        sprite.compute_winding
-    };
-}
-
-fragment float4 sprite_fragment(
-    SpriteFragmentInput input [[stage_in]],
-    texture2d<float> atlas [[ texture(GPUISpriteFragmentInputIndexAtlas) ]]
-) {
-    constexpr sampler atlas_sampler(mag_filter::linear, min_filter::linear);
-    float4 color = input.color;
-    float4 sample = atlas.sample(atlas_sampler, input.atlas_position);
-    float mask;
-    if (input.compute_winding) {
-        mask = 1. - abs(1. - fmod(sample.r, 2.));
-    } else {
-        mask = sample.a;
-    }
-    color.a *= mask;
-    return color;
-}
-
-vertex QuadFragmentInput image_vertex(
-    uint unit_vertex_id [[vertex_id]],
-    uint image_id [[instance_id]],
-    constant float2 *unit_vertices [[buffer(GPUIImageVertexInputIndexVertices)]],
-    constant GPUIImage *images [[buffer(GPUIImageVertexInputIndexImages)]],
-    constant float2 *viewport_size [[buffer(GPUIImageVertexInputIndexViewportSize)]],
-    constant float2 *atlas_size [[buffer(GPUIImageVertexInputIndexAtlasSize)]]
-) {
-    float2 unit_vertex = unit_vertices[unit_vertex_id];
-    GPUIImage image = images[image_id];
-    float2 position = unit_vertex * image.target_size + image.origin;
-    float4 device_position = to_device_position(position, *viewport_size);
-    float2 atlas_position = (unit_vertex * image.source_size + image.atlas_origin) / *atlas_size;
-
-    return QuadFragmentInput {
-        device_position,
-        atlas_position,
-        image.origin,
-        image.target_size,
-        float4(0.),
-        image.border_top,
-        image.border_right,
-        image.border_bottom,
-        image.border_left,
-        coloru_to_colorf(image.border_color),
-        image.corner_radius_top_left,
-        image.corner_radius_top_right,
-        image.corner_radius_bottom_right,
-        image.corner_radius_bottom_left,
-        image.grayscale,
-    };
-}
-
-fragment float4 image_fragment(
-    QuadFragmentInput input [[stage_in]],
-    texture2d<float> atlas [[ texture(GPUIImageFragmentInputIndexAtlas) ]]
-) {
-    constexpr sampler atlas_sampler(mag_filter::linear, min_filter::linear);
-    input.background_color = atlas.sample(atlas_sampler, input.atlas_position);
-    if (input.grayscale) {
-        float grayscale =
-            0.2126 * input.background_color.r +
-            0.7152 * input.background_color.g +
-            0.0722 * input.background_color.b;
-        input.background_color = float4(grayscale, grayscale, grayscale, input.background_color.a);
-    }
-    return quad_sdf(input);
-}
-
-vertex QuadFragmentInput surface_vertex(
-    uint unit_vertex_id [[vertex_id]],
-    uint image_id [[instance_id]],
-    constant float2 *unit_vertices [[buffer(GPUISurfaceVertexInputIndexVertices)]],
-    constant GPUISurface *images [[buffer(GPUISurfaceVertexInputIndexSurfaces)]],
-    constant float2 *viewport_size [[buffer(GPUISurfaceVertexInputIndexViewportSize)]],
-    constant float2 *atlas_size [[buffer(GPUISurfaceVertexInputIndexAtlasSize)]]
-) {
-    float2 unit_vertex = unit_vertices[unit_vertex_id];
-    GPUISurface image = images[image_id];
-    float2 position = unit_vertex * image.target_size + image.origin;
-    float4 device_position = to_device_position(position, *viewport_size);
-    float2 atlas_position = (unit_vertex * image.source_size) / *atlas_size;
-
-    return QuadFragmentInput {
-        device_position,
-        atlas_position,
-        image.origin,
-        image.target_size,
-        float4(0.),
-        0.,
-        0.,
-        0.,
-        0.,
-        float4(0.),
-        0.,
-        0,
-    };
-}
-
-fragment float4 surface_fragment(
-    QuadFragmentInput input [[stage_in]],
-    texture2d<float> y_atlas [[ texture(GPUISurfaceFragmentInputIndexYAtlas) ]],
-    texture2d<float> cb_cr_atlas [[ texture(GPUISurfaceFragmentInputIndexCbCrAtlas) ]]
-) {
-    constexpr sampler atlas_sampler(mag_filter::linear, min_filter::linear);
-    const float4x4 ycbcrToRGBTransform = float4x4(
-        float4(+1.0000f, +1.0000f, +1.0000f, +0.0000f),
-        float4(+0.0000f, -0.3441f, +1.7720f, +0.0000f),
-        float4(+1.4020f, -0.7141f, +0.0000f, +0.0000f),
-        float4(-0.7010f, +0.5291f, -0.8860f, +1.0000f)
-    );
-    float4 ycbcr = float4(y_atlas.sample(atlas_sampler, input.atlas_position).r,
-                          cb_cr_atlas.sample(atlas_sampler, input.atlas_position).rg, 1.0);
-
-    input.background_color = ycbcrToRGBTransform * ycbcr;
-    return quad_sdf(input);
-}
-
-struct PathAtlasVertexOutput {
-    float4 position [[position]];
-    float2 st_position;
-    float clip_rect_distance [[clip_distance]] [4];
-};
-
-struct PathAtlasFragmentInput {
-    float4 position [[position]];
-    float2 st_position;
-};
-
-vertex PathAtlasVertexOutput path_atlas_vertex(
-    uint vertex_id [[vertex_id]],
-    constant GPUIPathVertex *vertices [[buffer(GPUIPathAtlasVertexInputIndexVertices)]],
-    constant float2 *atlas_size [[buffer(GPUIPathAtlasVertexInputIndexAtlasSize)]]
-) {
-    GPUIPathVertex v = vertices[vertex_id];
-    float4 device_position = to_device_position(v.xy_position, *atlas_size);
-    return PathAtlasVertexOutput {
-        device_position,
-        v.st_position,
-        {
-            v.xy_position.x - v.clip_rect_origin.x,
-            v.clip_rect_origin.x + v.clip_rect_size.x - v.xy_position.x,
-            v.xy_position.y - v.clip_rect_origin.y,
-            v.clip_rect_origin.y + v.clip_rect_size.y - v.xy_position.y
-        }
-    };
-}
-
-fragment float4 path_atlas_fragment(
-    PathAtlasFragmentInput input [[stage_in]]
-) {
-    float2 dx = dfdx(input.st_position);
-    float2 dy = dfdy(input.st_position);
-    float2 gradient = float2(
-        (2. * input.st_position.x) * dx.x - dx.y,
-        (2. * input.st_position.x) * dy.x - dy.y
-    );
-    float f = (input.st_position.x * input.st_position.x) - input.st_position.y;
-    float distance = f / length(gradient);
-    float alpha = saturate(0.5 - distance);
-    return float4(alpha, 0., 0., 1.);
-}
-
-struct UnderlineFragmentInput {
-    float4 position [[position]];
-    float2 origin;
-    float2 size;
-    float thickness;
-    float4 color;
-    bool squiggly;
-};
-
-vertex UnderlineFragmentInput underline_vertex(
-    uint unit_vertex_id [[vertex_id]],
-    uint underline_id [[instance_id]],
-    constant float2 *unit_vertices [[buffer(GPUIUnderlineInputIndexVertices)]],
-    constant GPUIUnderline *underlines [[buffer(GPUIUnderlineInputIndexUnderlines)]],
-    constant GPUIUniforms *uniforms [[buffer(GPUIUnderlineInputIndexUniforms)]]
-) {
-    float2 unit_vertex = unit_vertices[unit_vertex_id];
-    GPUIUnderline underline = underlines[underline_id];
-    float2 position = unit_vertex * underline.size + underline.origin;
-    float4 device_position = to_device_position(position, uniforms->viewport_size);
-
-    return UnderlineFragmentInput {
-        device_position,
-        underline.origin,
-        underline.size,
-        underline.thickness,
-        coloru_to_colorf(underline.color),
-        underline.squiggly != 0,
-    };
-}
-
-fragment float4 underline_fragment(
-    UnderlineFragmentInput input [[stage_in]]
-) {
-    if (input.squiggly) {
-        float half_thickness = input.thickness * 0.5;
-        float2 st = ((input.position.xy - input.origin) / input.size.y) - float2(0., 0.5);
-        float frequency = (M_PI_F * (3. * input.thickness)) / 8.;
-        float amplitude = 1. / (2. * input.thickness);
-        float sine = sin(st.x * frequency) * amplitude;
-        float dSine = cos(st.x * frequency) * amplitude * frequency;
-        float distance = (st.y - sine) / sqrt(1. + dSine * dSine);
-        float distance_in_pixels = distance * input.size.y;
-        float distance_from_top_border = distance_in_pixels - half_thickness;
-        float distance_from_bottom_border = distance_in_pixels + half_thickness;
-        float alpha = saturate(0.5 - max(-distance_from_bottom_border, distance_from_top_border));
-        return input.color * float4(1., 1., 1., alpha);
-    } else {
-        return input.color;
-    }
-}

crates/gpui/src/platform/mac/sprite_cache.rs 🔗

@@ -1,164 +0,0 @@
-use super::atlas::AtlasAllocator;
-use crate::{
-    fonts::{FontId, GlyphId},
-    geometry::vector::{vec2f, Vector2F, Vector2I},
-    platform::{self, RasterizationOptions},
-};
-use collections::hash_map::Entry;
-use metal::{MTLPixelFormat, TextureDescriptor};
-use ordered_float::OrderedFloat;
-use std::{borrow::Cow, collections::HashMap, sync::Arc};
-
-#[derive(Hash, Eq, PartialEq)]
-struct GlyphDescriptor {
-    font_id: FontId,
-    font_size: OrderedFloat<f32>,
-    glyph_id: GlyphId,
-    subpixel_variant: (u8, u8),
-}
-
-#[derive(Clone)]
-pub struct GlyphSprite {
-    pub atlas_id: usize,
-    pub atlas_origin: Vector2I,
-    pub offset: Vector2I,
-    pub size: Vector2I,
-}
-
-#[derive(Hash, Eq, PartialEq)]
-struct IconDescriptor {
-    path: Cow<'static, str>,
-    width: i32,
-    height: i32,
-}
-
-#[derive(Clone)]
-pub struct IconSprite {
-    pub atlas_id: usize,
-    pub atlas_origin: Vector2I,
-    pub size: Vector2I,
-}
-
-pub struct SpriteCache {
-    fonts: Arc<dyn platform::FontSystem>,
-    atlases: AtlasAllocator,
-    glyphs: HashMap<GlyphDescriptor, Option<GlyphSprite>>,
-    icons: HashMap<IconDescriptor, IconSprite>,
-    scale_factor: f32,
-}
-
-impl SpriteCache {
-    pub fn new(
-        device: metal::Device,
-        size: Vector2I,
-        scale_factor: f32,
-        fonts: Arc<dyn platform::FontSystem>,
-    ) -> Self {
-        let descriptor = TextureDescriptor::new();
-        descriptor.set_pixel_format(MTLPixelFormat::A8Unorm);
-        descriptor.set_width(size.x() as u64);
-        descriptor.set_height(size.y() as u64);
-        Self {
-            fonts,
-            atlases: AtlasAllocator::new(device, descriptor),
-            glyphs: Default::default(),
-            icons: Default::default(),
-            scale_factor,
-        }
-    }
-
-    pub fn set_scale_factor(&mut self, scale_factor: f32) {
-        if scale_factor != self.scale_factor {
-            self.icons.clear();
-            self.glyphs.clear();
-            self.atlases.clear();
-        }
-        self.scale_factor = scale_factor;
-    }
-
-    pub fn render_glyph(
-        &mut self,
-        font_id: FontId,
-        font_size: f32,
-        glyph_id: GlyphId,
-        target_position: Vector2F,
-    ) -> Option<GlyphSprite> {
-        const SUBPIXEL_VARIANTS: u8 = 4;
-
-        let target_position = target_position * self.scale_factor;
-        let subpixel_variant = (
-            (target_position.x().fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
-            (target_position.y().fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
-        );
-
-        self.glyphs
-            .entry(GlyphDescriptor {
-                font_id,
-                font_size: OrderedFloat(font_size),
-                glyph_id,
-                subpixel_variant,
-            })
-            .or_insert_with(|| {
-                let subpixel_shift = vec2f(
-                    subpixel_variant.0 as f32 / SUBPIXEL_VARIANTS as f32,
-                    subpixel_variant.1 as f32 / SUBPIXEL_VARIANTS as f32,
-                );
-                let (glyph_bounds, mask) = self.fonts.rasterize_glyph(
-                    font_id,
-                    font_size,
-                    glyph_id,
-                    subpixel_shift,
-                    self.scale_factor,
-                    RasterizationOptions::Alpha,
-                )?;
-
-                let (alloc_id, atlas_bounds) = self
-                    .atlases
-                    .upload(glyph_bounds.size(), &mask)
-                    .expect("could not upload glyph");
-                Some(GlyphSprite {
-                    atlas_id: alloc_id.atlas_id,
-                    atlas_origin: atlas_bounds.origin(),
-                    offset: glyph_bounds.origin(),
-                    size: glyph_bounds.size(),
-                })
-            })
-            .clone()
-    }
-
-    pub fn render_icon(
-        &mut self,
-        size: Vector2I,
-        path: Cow<'static, str>,
-        svg: usvg::Tree,
-    ) -> Option<IconSprite> {
-        let atlases = &mut self.atlases;
-        match self.icons.entry(IconDescriptor {
-            path,
-            width: size.x(),
-            height: size.y(),
-        }) {
-            Entry::Occupied(entry) => Some(entry.get().clone()),
-            Entry::Vacant(entry) => {
-                let mut pixmap = tiny_skia::Pixmap::new(size.x() as u32, size.y() as u32)?;
-                resvg::render(&svg, usvg::FitTo::Width(size.x() as u32), pixmap.as_mut());
-                let mask = pixmap
-                    .pixels()
-                    .iter()
-                    .map(|a| a.alpha())
-                    .collect::<Vec<_>>();
-                let (alloc_id, atlas_bounds) = atlases.upload(size, &mask)?;
-                let icon_sprite = IconSprite {
-                    atlas_id: alloc_id.atlas_id,
-                    atlas_origin: atlas_bounds.origin(),
-                    size,
-                };
-                Some(entry.insert(icon_sprite).clone())
-            }
-        }
-    }
-
-    pub fn atlas_texture(&self, atlas_id: usize) -> Option<&metal::TextureRef> {
-        self.atlases.texture(atlas_id)
-    }
-}

crates/gpui/src/platform/mac/window.rs 🔗

@@ -1,33 +1,29 @@
+use super::{display_bounds_from_native, ns_string, MacDisplay, MetalRenderer, NSRange};
 use crate::{
-    executor,
-    geometry::{
-        rect::RectF,
-        vector::{vec2f, Vector2F},
-    },
-    keymap_matcher::Keystroke,
-    platform::{
-        self,
-        mac::{
-            platform::NSViewLayerContentsRedrawDuringViewResize, renderer::Renderer, screen::Screen,
-        },
-        Event, InputHandler, KeyDownEvent, Modifiers, ModifiersChangedEvent, MouseButton,
-        MouseButtonEvent, MouseMovedEvent, Scene, WindowBounds, WindowKind,
-    },
-    AnyWindowHandle,
+    display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, DrawWindow, ExternalPaths,
+    FileDropEvent, ForegroundExecutor, GlobalPixels, InputEvent, KeyDownEvent, Keystroke,
+    Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
+    Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
+    PromptLevel, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions,
 };
 use block::ConcreteBlock;
 use cocoa::{
     appkit::{
-        CGPoint, NSApplication, NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable,
+        CGPoint, NSApplication, NSBackingStoreBuffered, NSEventModifierFlags,
+        NSFilenamesPboardType, NSPasteboard, NSScreen, NSView, NSViewHeightSizable,
         NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowCollectionBehavior,
         NSWindowStyleMask, NSWindowTitleVisibility,
     },
     base::{id, nil},
-    foundation::{NSAutoreleasePool, NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger},
+    foundation::{
+        NSArray, NSAutoreleasePool, NSDictionary, NSFastEnumeration, NSInteger, NSPoint, NSRect,
+        NSSize, NSString, NSUInteger,
+    },
 };
 use core_graphics::display::CGRect;
 use ctor::ctor;
 use foreign_types::ForeignTypeRef;
+use futures::channel::oneshot;
 use objc::{
     class,
     declare::ClassDecl,
@@ -35,26 +31,22 @@ use objc::{
     runtime::{Class, Object, Protocol, Sel, BOOL, NO, YES},
     sel, sel_impl,
 };
-use postage::oneshot;
-use smol::Timer;
+use parking_lot::Mutex;
+use smallvec::SmallVec;
 use std::{
     any::Any,
     cell::{Cell, RefCell},
-    convert::TryInto,
     ffi::{c_void, CStr},
     mem,
     ops::Range,
     os::raw::c_char,
+    path::PathBuf,
     ptr,
-    rc::{Rc, Weak},
-    sync::Arc,
+    rc::Rc,
+    sync::{Arc, Weak},
     time::Duration,
 };
-
-use super::{
-    geometry::{NSRectExt, Vector2FExt},
-    ns_string, NSRange,
-};
+use util::ResultExt;
 
 const WINDOW_STATE_IVAR: &str = "windowState";
 
@@ -79,10 +71,17 @@ const NSTrackingActiveAlways: NSUInteger = 0x80;
 const NSTrackingInVisibleRect: NSUInteger = 0x200;
 #[allow(non_upper_case_globals)]
 const NSWindowAnimationBehaviorUtilityWindow: NSInteger = 4;
+#[allow(non_upper_case_globals)]
+const NSViewLayerContentsRedrawDuringViewResize: NSInteger = 2;
+// https://developer.apple.com/documentation/appkit/nsdragoperation
+type NSDragOperation = NSUInteger;
+#[allow(non_upper_case_globals)]
+const NSDragOperationNone: NSDragOperation = 0;
+#[allow(non_upper_case_globals)]
+const NSDragOperationCopy: NSDragOperation = 1;
 
 #[ctor]
 unsafe fn build_classes() {
-    ::util::gpui1_loaded();
     WINDOW_CLASS = build_window_class("GPUIWindow", class!(NSWindow));
     PANEL_CLASS = build_window_class("GPUIPanel", class!(NSPanel));
     VIEW_CLASS = {
@@ -222,11 +221,11 @@ unsafe fn build_classes() {
     };
 }
 
-pub fn convert_mouse_position(position: NSPoint, window_height: f32) -> Vector2F {
-    vec2f(
-        position.x as f32,
+pub fn convert_mouse_position(position: NSPoint, window_height: Pixels) -> Point<Pixels> {
+    point(
+        px(position.x as f32),
         // MacOS screen coordinates are relative to bottom left
-        window_height - position.y as f32,
+        window_height - px(position.y as f32),
     )
 }
 
@@ -271,6 +270,28 @@ unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const C
         window_should_close as extern "C" fn(&Object, Sel, id) -> BOOL,
     );
     decl.add_method(sel!(close), close_window as extern "C" fn(&Object, Sel));
+
+    decl.add_method(
+        sel!(draggingEntered:),
+        dragging_entered as extern "C" fn(&Object, Sel, id) -> NSDragOperation,
+    );
+    decl.add_method(
+        sel!(draggingUpdated:),
+        dragging_updated as extern "C" fn(&Object, Sel, id) -> NSDragOperation,
+    );
+    decl.add_method(
+        sel!(draggingExited:),
+        dragging_exited as extern "C" fn(&Object, Sel, id),
+    );
+    decl.add_method(
+        sel!(performDragOperation:),
+        perform_drag_operation as extern "C" fn(&Object, Sel, id) -> BOOL,
+    );
+    decl.add_method(
+        sel!(concludeDragOperation:),
+        conclude_drag_operation as extern "C" fn(&Object, Sel, id),
+    );
+
     decl.register()
 }
 
@@ -286,41 +307,40 @@ enum ImeState {
     None,
 }
 
-#[derive(Debug)]
 struct InsertText {
     replacement_range: Option<Range<usize>>,
     text: String,
 }
 
-struct WindowState {
+struct MacWindowState {
     handle: AnyWindowHandle,
+    executor: ForegroundExecutor,
     native_window: id,
+    renderer: MetalRenderer,
+    draw: Option<DrawWindow>,
     kind: WindowKind,
-    event_callback: Option<Box<dyn FnMut(Event) -> bool>>,
+    event_callback: Option<Box<dyn FnMut(InputEvent) -> bool>>,
     activate_callback: Option<Box<dyn FnMut(bool)>>,
-    resize_callback: Option<Box<dyn FnMut()>>,
+    resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
     fullscreen_callback: Option<Box<dyn FnMut(bool)>>,
     moved_callback: Option<Box<dyn FnMut()>>,
     should_close_callback: Option<Box<dyn FnMut() -> bool>>,
     close_callback: Option<Box<dyn FnOnce()>>,
     appearance_changed_callback: Option<Box<dyn FnMut()>>,
-    input_handler: Option<Box<dyn InputHandler>>,
+    input_handler: Option<Box<dyn PlatformInputHandler>>,
     pending_key_down: Option<(KeyDownEvent, Option<InsertText>)>,
     last_key_equivalent: Option<KeyDownEvent>,
     synthetic_drag_counter: usize,
-    executor: Rc<executor::Foreground>,
-    scene_to_render: Option<Scene>,
-    renderer: Renderer,
     last_fresh_keydown: Option<Keystroke>,
-    traffic_light_position: Option<Vector2F>,
-    previous_modifiers_changed_event: Option<Event>,
-    //State tracking what the IME did after the last request
+    traffic_light_position: Option<Point<Pixels>>,
+    previous_modifiers_changed_event: Option<InputEvent>,
+    // State tracking what the IME did after the last request
     ime_state: ImeState,
-    //Retains the last IME Text
+    // Retains the last IME Text
     ime_text: Option<String>,
 }
 
-impl WindowState {
+impl MacWindowState {
     fn move_traffic_light(&self) {
         if let Some(traffic_light_position) = self.traffic_light_position {
             let titlebar_height = self.titlebar_height();
@@ -342,25 +362,26 @@ impl WindowState {
                 let mut close_button_frame: CGRect = msg_send![close_button, frame];
                 let mut min_button_frame: CGRect = msg_send![min_button, frame];
                 let mut zoom_button_frame: CGRect = msg_send![zoom_button, frame];
-                let mut origin = vec2f(
-                    traffic_light_position.x(),
+                let mut origin = point(
+                    traffic_light_position.x,
                     titlebar_height
-                        - traffic_light_position.y()
-                        - close_button_frame.size.height as f32,
+                        - traffic_light_position.y
+                        - px(close_button_frame.size.height as f32),
                 );
                 let button_spacing =
-                    (min_button_frame.origin.x - close_button_frame.origin.x) as f32;
+                    px((min_button_frame.origin.x - close_button_frame.origin.x) as f32);
 
-                close_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64);
+                close_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
                 let _: () = msg_send![close_button, setFrame: close_button_frame];
-                origin.set_x(origin.x() + button_spacing);
+                origin.x += button_spacing;
 
-                min_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64);
+                min_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
                 let _: () = msg_send![min_button, setFrame: min_button_frame];
-                origin.set_x(origin.x() + button_spacing);
+                origin.x += button_spacing;
 
-                zoom_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64);
+                zoom_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
                 let _: () = msg_send![zoom_button, setFrame: zoom_button_frame];
+                origin.x += button_spacing;
             }
         }
     }
@@ -379,8 +400,8 @@ impl WindowState {
             }
 
             let frame = self.frame();
-            let screen_size = self.native_window.screen().visibleFrame().size_vec();
-            if frame.size() == screen_size {
+            let screen_size = self.native_window.screen().visibleFrame().into();
+            if frame.size == screen_size {
                 WindowBounds::Maximized
             } else {
                 WindowBounds::Fixed(frame)
@@ -388,47 +409,52 @@ impl WindowState {
         }
     }
 
-    fn frame(&self) -> RectF {
+    fn frame(&self) -> Bounds<GlobalPixels> {
         unsafe {
             let frame = NSWindow::frame(self.native_window);
-            Screen::screen_rect_from_native(frame)
+            display_bounds_from_native(mem::transmute::<NSRect, CGRect>(frame))
         }
     }
 
-    fn content_size(&self) -> Vector2F {
+    fn content_size(&self) -> Size<Pixels> {
         let NSSize { width, height, .. } =
             unsafe { NSView::frame(self.native_window.contentView()) }.size;
-        vec2f(width as f32, height as f32)
+        size(px(width as f32), px(height as f32))
     }
 
     fn scale_factor(&self) -> f32 {
         get_scale_factor(self.native_window)
     }
 
-    fn titlebar_height(&self) -> f32 {
+    fn titlebar_height(&self) -> Pixels {
         unsafe {
             let frame = NSWindow::frame(self.native_window);
             let content_layout_rect: CGRect = msg_send![self.native_window, contentLayoutRect];
-            (frame.size.height - content_layout_rect.size.height) as f32
+            px((frame.size.height - content_layout_rect.size.height) as f32)
         }
     }
 
-    fn present_scene(&mut self, scene: Scene) {
-        self.scene_to_render = Some(scene);
+    fn to_screen_ns_point(&self, point: Point<Pixels>) -> NSPoint {
         unsafe {
-            let _: () = msg_send![self.native_window.contentView(), setNeedsDisplay: YES];
+            let point = NSPoint::new(
+                point.x.into(),
+                (self.content_size().height - point.y).into(),
+            );
+            msg_send![self.native_window, convertPointToScreen: point]
         }
     }
 }
 
-pub struct MacWindow(Rc<RefCell<WindowState>>);
+unsafe impl Send for MacWindowState {}
+
+pub struct MacWindow(Arc<Mutex<MacWindowState>>);
 
 impl MacWindow {
     pub fn open(
         handle: AnyWindowHandle,
-        options: platform::WindowOptions,
-        executor: Rc<executor::Foreground>,
-        fonts: Arc<dyn platform::FontSystem>,
+        options: WindowOptions,
+        draw: DrawWindow,
+        executor: ForegroundExecutor,
     ) -> Self {
         unsafe {
             let pool = NSAutoreleasePool::new(nil);
@@ -455,19 +481,40 @@ impl MacWindow {
                     msg_send![PANEL_CLASS, alloc]
                 }
             };
+
+            let display = options
+                .display_id
+                .and_then(|display_id| MacDisplay::all().find(|display| display.id() == display_id))
+                .unwrap_or_else(MacDisplay::primary);
+
+            let mut target_screen = nil;
+            let screens = NSScreen::screens(nil);
+            let count: u64 = cocoa::foundation::NSArray::count(screens);
+            for i in 0..count {
+                let screen = cocoa::foundation::NSArray::objectAtIndex(screens, i);
+                let device_description = NSScreen::deviceDescription(screen);
+                let screen_number_key: id = NSString::alloc(nil).init_str("NSScreenNumber");
+                let screen_number = device_description.objectForKey_(screen_number_key);
+                let screen_number: NSUInteger = msg_send![screen_number, unsignedIntegerValue];
+                if screen_number as u32 == display.id().0 {
+                    target_screen = screen;
+                    break;
+                }
+            }
+
             let native_window = native_window.initWithContentRect_styleMask_backing_defer_screen_(
                 NSRect::new(NSPoint::new(0., 0.), NSSize::new(1024., 768.)),
                 style_mask,
                 NSBackingStoreBuffered,
                 NO,
-                options
-                    .screen
-                    .and_then(|screen| {
-                        Some(screen.as_any().downcast_ref::<Screen>()?.native_screen)
-                    })
-                    .unwrap_or(nil),
+                target_screen,
             );
             assert!(!native_window.is_null());
+            let () = msg_send![
+                native_window,
+                registerForDraggedTypes:
+                    NSArray::arrayWithObject(nil, NSFilenamesPboardType)
+            ];
 
             let screen = native_window.screen();
             match options.bounds {
@@ -477,14 +524,14 @@ impl MacWindow {
                 WindowBounds::Maximized => {
                     native_window.setFrame_display_(screen.visibleFrame(), YES);
                 }
-                WindowBounds::Fixed(rect) => {
-                    let bounds = Screen::screen_rect_to_native(rect);
-                    let screen_bounds = screen.visibleFrame();
-                    if bounds.intersects(screen_bounds) {
-                        native_window.setFrame_display_(bounds, YES);
+                WindowBounds::Fixed(bounds) => {
+                    let display_bounds = display.bounds();
+                    let frame = if bounds.intersects(&display_bounds) {
+                        display_bounds_to_native(bounds)
                     } else {
-                        native_window.setFrame_display_(screen_bounds, YES);
-                    }
+                        display_bounds_to_native(display_bounds)
+                    };
+                    native_window.setFrame_display_(mem::transmute::<CGRect, NSRect>(frame), YES);
                 }
             }
 
@@ -493,25 +540,25 @@ impl MacWindow {
 
             assert!(!native_view.is_null());
 
-            let window = Self(Rc::new(RefCell::new(WindowState {
+            let window = Self(Arc::new(Mutex::new(MacWindowState {
                 handle,
+                executor,
                 native_window,
+                renderer: MetalRenderer::new(true),
+                draw: Some(draw),
                 kind: options.kind,
                 event_callback: None,
-                resize_callback: None,
-                should_close_callback: None,
-                close_callback: None,
                 activate_callback: None,
+                resize_callback: None,
                 fullscreen_callback: None,
                 moved_callback: None,
+                should_close_callback: None,
+                close_callback: None,
                 appearance_changed_callback: None,
                 input_handler: None,
                 pending_key_down: None,
                 last_key_equivalent: None,
                 synthetic_drag_counter: 0,
-                executor,
-                scene_to_render: Default::default(),
-                renderer: Renderer::new(true, fonts),
                 last_fresh_keydown: None,
                 traffic_light_position: options
                     .titlebar
@@ -524,15 +571,19 @@ impl MacWindow {
 
             (*native_window).set_ivar(
                 WINDOW_STATE_IVAR,
-                Rc::into_raw(window.0.clone()) as *const c_void,
+                Arc::into_raw(window.0.clone()) as *const c_void,
             );
             native_window.setDelegate_(native_window);
             (*native_view).set_ivar(
                 WINDOW_STATE_IVAR,
-                Rc::into_raw(window.0.clone()) as *const c_void,
+                Arc::into_raw(window.0.clone()) as *const c_void,
             );
 
-            if let Some(title) = options.titlebar.as_ref().and_then(|t| t.title) {
+            if let Some(title) = options
+                .titlebar
+                .as_ref()
+                .and_then(|t| t.title.as_ref().map(AsRef::as_ref))
+            {
                 native_window.setTitle_(NSString::alloc(nil).init_str(title));
             }
 
@@ -604,19 +655,19 @@ impl MacWindow {
                 native_window.orderFront_(nil);
             }
 
-            window.0.borrow().move_traffic_light();
+            window.0.lock().move_traffic_light();
             pool.drain();
 
             window
         }
     }
 
-    pub fn main_window() -> Option<AnyWindowHandle> {
+    pub fn active_window() -> Option<AnyWindowHandle> {
         unsafe {
             let app = NSApplication::sharedApplication(nil);
             let main_window: id = msg_send![app, mainWindow];
             if msg_send![main_window, isKindOfClass: WINDOW_CLASS] {
-                let handle = get_window_state(&*main_window).borrow().handle;
+                let handle = get_window_state(&*main_window).lock().handle;
                 Some(handle)
             } else {
                 None
@@ -627,11 +678,14 @@ impl MacWindow {
 
 impl Drop for MacWindow {
     fn drop(&mut self) {
-        let this = self.0.borrow();
+        let this = self.0.lock();
         let window = this.native_window;
         this.executor
             .spawn(async move {
                 unsafe {
+                    // todo!() this panic()s when you click the red close button
+                    // unless should_close returns false.
+                    // (luckliy in zed it always returns false)
                     window.close();
                 }
             })
@@ -639,62 +693,88 @@ impl Drop for MacWindow {
     }
 }
 
-impl platform::Window for MacWindow {
+impl PlatformWindow for MacWindow {
     fn bounds(&self) -> WindowBounds {
-        self.0.as_ref().borrow().bounds()
+        self.0.as_ref().lock().bounds()
     }
 
-    fn content_size(&self) -> Vector2F {
-        self.0.as_ref().borrow().content_size()
+    fn content_size(&self) -> Size<Pixels> {
+        self.0.as_ref().lock().content_size()
     }
 
     fn scale_factor(&self) -> f32 {
-        self.0.as_ref().borrow().scale_factor()
+        self.0.as_ref().lock().scale_factor()
     }
 
-    fn titlebar_height(&self) -> f32 {
-        self.0.as_ref().borrow().titlebar_height()
+    fn titlebar_height(&self) -> Pixels {
+        self.0.as_ref().lock().titlebar_height()
     }
 
-    fn appearance(&self) -> platform::Appearance {
+    fn appearance(&self) -> WindowAppearance {
         unsafe {
-            let appearance: id = msg_send![self.0.borrow().native_window, effectiveAppearance];
-            platform::Appearance::from_native(appearance)
+            let appearance: id = msg_send![self.0.lock().native_window, effectiveAppearance];
+            WindowAppearance::from_native(appearance)
         }
     }
 
-    fn screen(&self) -> Rc<dyn platform::Screen> {
+    fn display(&self) -> Rc<dyn PlatformDisplay> {
         unsafe {
-            Rc::new(Screen {
-                native_screen: self.0.as_ref().borrow().native_window.screen(),
-            })
+            let screen = self.0.lock().native_window.screen();
+            let device_description: id = msg_send![screen, deviceDescription];
+            let screen_number: id = NSDictionary::valueForKey_(
+                device_description,
+                NSString::alloc(nil).init_str("NSScreenNumber"),
+            );
+
+            let screen_number: u32 = msg_send![screen_number, unsignedIntValue];
+
+            Rc::new(MacDisplay(screen_number))
         }
     }
 
-    fn mouse_position(&self) -> Vector2F {
+    fn mouse_position(&self) -> Point<Pixels> {
         let position = unsafe {
             self.0
-                .borrow()
+                .lock()
                 .native_window
                 .mouseLocationOutsideOfEventStream()
         };
-        convert_mouse_position(position, self.content_size().y())
+        convert_mouse_position(position, self.content_size().height)
+    }
+
+    fn modifiers(&self) -> Modifiers {
+        unsafe {
+            let modifiers: NSEventModifierFlags = msg_send![class!(NSEvent), modifierFlags];
+
+            let control = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
+            let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
+            let shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
+            let command = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
+            let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask);
+
+            Modifiers {
+                control,
+                alt,
+                shift,
+                command,
+                function,
+            }
+        }
     }
 
     fn as_any_mut(&mut self) -> &mut dyn Any {
         self
     }
 
-    fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>) {
-        self.0.as_ref().borrow_mut().input_handler = Some(input_handler);
+    fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>) {
+        self.0.as_ref().lock().input_handler = Some(input_handler);
+    }
+
+    fn clear_input_handler(&mut self) {
+        self.0.as_ref().lock().input_handler = None;
     }
 
-    fn prompt(
-        &self,
-        level: platform::PromptLevel,
-        msg: &str,
-        answers: &[&str],
-    ) -> oneshot::Receiver<usize> {
+    fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize> {
         // macOs applies overrides to modal window buttons after they are added.
         // Two most important for this logic are:
         // * Buttons with "Cancel" title will be displayed as the last buttons in the modal
@@ -724,9 +804,9 @@ impl platform::Window for MacWindow {
             let alert: id = msg_send![class!(NSAlert), alloc];
             let alert: id = msg_send![alert, init];
             let alert_style = match level {
-                platform::PromptLevel::Info => 1,
-                platform::PromptLevel::Warning => 0,
-                platform::PromptLevel::Critical => 2,
+                PromptLevel::Info => 1,
+                PromptLevel::Warning => 0,
+                PromptLevel::Critical => 2,
             };
             let _: () = msg_send![alert, setAlertStyle: alert_style];
             let _: () = msg_send![alert, setMessageText: ns_string(msg)];
@@ -747,15 +827,14 @@ impl platform::Window for MacWindow {
             let (done_tx, done_rx) = oneshot::channel();
             let done_tx = Cell::new(Some(done_tx));
             let block = ConcreteBlock::new(move |answer: NSInteger| {
-                if let Some(mut done_tx) = done_tx.take() {
-                    let _ = postage::sink::Sink::try_send(&mut done_tx, answer.try_into().unwrap());
+                if let Some(done_tx) = done_tx.take() {
+                    let _ = done_tx.send(answer.try_into().unwrap());
                 }
             });
             let block = block.copy();
-            let native_window = self.0.borrow().native_window;
-            self.0
-                .borrow()
-                .executor
+            let native_window = self.0.lock().native_window;
+            let executor = self.0.lock().executor.clone();
+            executor
                 .spawn(async move {
                     let _: () = msg_send![
                         alert,
@@ -770,10 +849,9 @@ impl platform::Window for MacWindow {
     }
 
     fn activate(&self) {
-        let window = self.0.borrow().native_window;
-        self.0
-            .borrow()
-            .executor
+        let window = self.0.lock().native_window;
+        let executor = self.0.lock().executor.clone();
+        executor
             .spawn(async move {
                 unsafe {
                     let _: () = msg_send![window, makeKeyAndOrderFront: nil];
@@ -785,42 +863,47 @@ impl platform::Window for MacWindow {
     fn set_title(&mut self, title: &str) {
         unsafe {
             let app = NSApplication::sharedApplication(nil);
-            let window = self.0.borrow().native_window;
+            let window = self.0.lock().native_window;
             let title = ns_string(title);
             let _: () = msg_send![app, changeWindowsItem:window title:title filename:false];
             let _: () = msg_send![window, setTitle: title];
-            self.0.borrow().move_traffic_light();
+            self.0.lock().move_traffic_light();
         }
     }
 
     fn set_edited(&mut self, edited: bool) {
         unsafe {
-            let window = self.0.borrow().native_window;
+            let window = self.0.lock().native_window;
             msg_send![window, setDocumentEdited: edited as BOOL]
         }
 
         // Changing the document edited state resets the traffic light position,
         // so we have to move it again.
-        self.0.borrow().move_traffic_light();
+        self.0.lock().move_traffic_light();
     }
 
     fn show_character_palette(&self) {
-        unsafe {
-            let app = NSApplication::sharedApplication(nil);
-            let window = self.0.borrow().native_window;
-            let _: () = msg_send![app, orderFrontCharacterPalette: window];
-        }
+        let this = self.0.lock();
+        let window = this.native_window;
+        this.executor
+            .spawn(async move {
+                unsafe {
+                    let app = NSApplication::sharedApplication(nil);
+                    let _: () = msg_send![app, orderFrontCharacterPalette: window];
+                }
+            })
+            .detach();
     }
 
     fn minimize(&self) {
-        let window = self.0.borrow().native_window;
+        let window = self.0.lock().native_window;
         unsafe {
             window.miniaturize_(nil);
         }
     }
 
     fn zoom(&self) {
-        let this = self.0.borrow();
+        let this = self.0.lock();
         let window = this.native_window;
         this.executor
             .spawn(async move {
@@ -831,12 +914,8 @@ impl platform::Window for MacWindow {
             .detach();
     }
 
-    fn present_scene(&mut self, scene: Scene) {
-        self.0.as_ref().borrow_mut().present_scene(scene);
-    }
-
     fn toggle_full_screen(&self) {
-        let this = self.0.borrow();
+        let this = self.0.lock();
         let window = this.native_window;
         this.executor
             .spawn(async move {
@@ -847,50 +926,47 @@ impl platform::Window for MacWindow {
             .detach();
     }
 
-    fn on_event(&mut self, callback: Box<dyn FnMut(Event) -> bool>) {
-        self.0.as_ref().borrow_mut().event_callback = Some(callback);
+    fn on_input(&self, callback: Box<dyn FnMut(InputEvent) -> bool>) {
+        self.0.as_ref().lock().event_callback = Some(callback);
     }
 
-    fn on_active_status_change(&mut self, callback: Box<dyn FnMut(bool)>) {
-        self.0.as_ref().borrow_mut().activate_callback = Some(callback);
+    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
+        self.0.as_ref().lock().activate_callback = Some(callback);
     }
 
-    fn on_resize(&mut self, callback: Box<dyn FnMut()>) {
-        self.0.as_ref().borrow_mut().resize_callback = Some(callback);
+    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
+        self.0.as_ref().lock().resize_callback = Some(callback);
     }
 
-    fn on_fullscreen(&mut self, callback: Box<dyn FnMut(bool)>) {
-        self.0.as_ref().borrow_mut().fullscreen_callback = Some(callback);
+    fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>) {
+        self.0.as_ref().lock().fullscreen_callback = Some(callback);
     }
 
-    fn on_moved(&mut self, callback: Box<dyn FnMut()>) {
-        self.0.as_ref().borrow_mut().moved_callback = Some(callback);
+    fn on_moved(&self, callback: Box<dyn FnMut()>) {
+        self.0.as_ref().lock().moved_callback = Some(callback);
     }
 
-    fn on_should_close(&mut self, callback: Box<dyn FnMut() -> bool>) {
-        self.0.as_ref().borrow_mut().should_close_callback = Some(callback);
+    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
+        self.0.as_ref().lock().should_close_callback = Some(callback);
     }
 
-    fn on_close(&mut self, callback: Box<dyn FnOnce()>) {
-        self.0.as_ref().borrow_mut().close_callback = Some(callback);
+    fn on_close(&self, callback: Box<dyn FnOnce()>) {
+        self.0.as_ref().lock().close_callback = Some(callback);
     }
 
-    fn on_appearance_changed(&mut self, callback: Box<dyn FnMut()>) {
-        self.0.borrow_mut().appearance_changed_callback = Some(callback);
+    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
+        self.0.lock().appearance_changed_callback = Some(callback);
     }
 
-    fn is_topmost_for_position(&self, position: Vector2F) -> bool {
-        let self_borrow = self.0.borrow();
+    fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool {
+        let self_borrow = self.0.lock();
         let self_handle = self_borrow.handle;
 
         unsafe {
             let app = NSApplication::sharedApplication(nil);
 
             // Convert back to screen coordinates
-            let screen_point = position.to_screen_ns_point(
-                self_borrow.native_window,
-                self_borrow.content_size().y() as f64,
-            );
+            let screen_point = self_borrow.to_screen_ns_point(position);
 
             let window_number: NSInteger = msg_send![class!(NSWindow), windowNumberAtPoint:screen_point belowWindowWithWindowNumber:0];
             let top_most_window: id = msg_send![app, windowWithWindowNumber: window_number];
@@ -898,7 +974,7 @@ impl platform::Window for MacWindow {
             let is_panel: BOOL = msg_send![top_most_window, isKindOfClass: PANEL_CLASS];
             let is_window: BOOL = msg_send![top_most_window, isKindOfClass: WINDOW_CLASS];
             if is_panel == YES || is_window == YES {
-                let topmost_window = get_window_state(&*top_most_window).borrow().handle;
+                let topmost_window = get_window_state(&*top_most_window).lock().handle;
                 topmost_window == self_handle
             } else {
                 // Someone else's window is on top
@@ -906,6 +982,17 @@ impl platform::Window for MacWindow {
             }
         }
     }
+
+    fn invalidate(&self) {
+        let this = self.0.lock();
+        unsafe {
+            let _: () = msg_send![this.native_window.contentView(), setNeedsDisplay: YES];
+        }
+    }
+
+    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
+        self.0.lock().renderer.sprite_atlas().clone()
+    }
 }
 
 fn get_scale_factor(native_window: id) -> f32 {
@@ -915,9 +1002,9 @@ fn get_scale_factor(native_window: id) -> f32 {
     }
 }
 
-unsafe fn get_window_state(object: &Object) -> Rc<RefCell<WindowState>> {
+unsafe fn get_window_state(object: &Object) -> Arc<Mutex<MacWindowState>> {
     let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
-    let rc1 = Rc::from_raw(raw as *mut RefCell<WindowState>);
+    let rc1 = Arc::from_raw(raw as *mut Mutex<MacWindowState>);
     let rc2 = rc1.clone();
     mem::forget(rc1);
     rc2
@@ -925,7 +1012,7 @@ unsafe fn get_window_state(object: &Object) -> Rc<RefCell<WindowState>> {
 
 unsafe fn drop_window_state(object: &Object) {
     let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
-    Rc::from_raw(raw as *mut RefCell<WindowState>);
+    Rc::from_raw(raw as *mut RefCell<MacWindowState>);
 }
 
 extern "C" fn yes(_: &Object, _: Sel) -> BOOL {
@@ -956,35 +1043,35 @@ extern "C" fn handle_key_down(this: &Object, _: Sel, native_event: id) {
 
 extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: bool) -> BOOL {
     let window_state = unsafe { get_window_state(this) };
-    let mut window_state_borrow = window_state.as_ref().borrow_mut();
+    let mut lock = window_state.as_ref().lock();
 
-    let window_height = window_state_borrow.content_size().y();
-    let event = unsafe { Event::from_native(native_event, Some(window_height)) };
+    let window_height = lock.content_size().height;
+    let event = unsafe { InputEvent::from_native(native_event, Some(window_height)) };
 
-    if let Some(Event::KeyDown(event)) = event {
+    if let Some(InputEvent::KeyDown(event)) = event {
         // For certain keystrokes, macOS will first dispatch a "key equivalent" event.
         // If that event isn't handled, it will then dispatch a "key down" event. GPUI
         // makes no distinction between these two types of events, so we need to ignore
         // the "key down" event if we've already just processed its "key equivalent" version.
         if key_equivalent {
-            window_state_borrow.last_key_equivalent = Some(event.clone());
-        } else if window_state_borrow.last_key_equivalent.take().as_ref() == Some(&event) {
+            lock.last_key_equivalent = Some(event.clone());
+        } else if lock.last_key_equivalent.take().as_ref() == Some(&event) {
             return NO;
         }
 
         let keydown = event.keystroke.clone();
-        let fn_modifier = keydown.function;
+        let fn_modifier = keydown.modifiers.function;
         // Ignore events from held-down keys after some of the initially-pressed keys
         // were released.
         if event.is_held {
-            if window_state_borrow.last_fresh_keydown.as_ref() != Some(&keydown) {
+            if lock.last_fresh_keydown.as_ref() != Some(&keydown) {
                 return YES;
             }
         } else {
-            window_state_borrow.last_fresh_keydown = Some(keydown);
+            lock.last_fresh_keydown = Some(keydown);
         }
-        window_state_borrow.pending_key_down = Some((event, None));
-        drop(window_state_borrow);
+        lock.pending_key_down = Some((event, None));
+        drop(lock);
 
         // Send the event to the input context for IME handling, unless the `fn` modifier is
         // being pressed.
@@ -996,19 +1083,49 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent:
         }
 
         let mut handled = false;
-        let mut window_state_borrow = window_state.borrow_mut();
-        let ime_text = window_state_borrow.ime_text.clone();
-        if let Some((event, insert_text)) = window_state_borrow.pending_key_down.take() {
+        let mut lock = window_state.lock();
+        let ime_text = lock.ime_text.clone();
+        if let Some((event, insert_text)) = lock.pending_key_down.take() {
             let is_held = event.is_held;
-            if let Some(mut callback) = window_state_borrow.event_callback.take() {
-                drop(window_state_borrow);
+            if let Some(mut callback) = lock.event_callback.take() {
+                drop(lock);
 
                 let is_composing =
                     with_input_handler(this, |input_handler| input_handler.marked_text_range())
                         .flatten()
                         .is_some();
                 if !is_composing {
-                    handled = callback(Event::KeyDown(event));
+                    // if the IME has changed the key, we'll first emit an event with the character
+                    // generated by the IME system; then fallback to the keystroke if that is not
+                    // handled.
+                    // cases that we have working:
+                    // - " on a brazillian layout by typing <quote><space>
+                    // - ctrl-` on a brazillian layout by typing <ctrl-`>
+                    // - $ on a czech QWERTY layout by typing <alt-4>
+                    // - 4 on a czech QWERTY layout by typing <shift-4>
+                    // - ctrl-4 on a czech QWERTY layout by typing <ctrl-alt-4> (or <ctrl-shift-4>)
+                    if ime_text.is_some() && ime_text.as_ref() != Some(&event.keystroke.key) {
+                        let event_with_ime_text = KeyDownEvent {
+                            is_held: false,
+                            keystroke: Keystroke {
+                                // we match ctrl because some use-cases need it.
+                                // we don't match alt because it's often used to generate the optional character
+                                // we don't match shift because we're not here with letters (usually)
+                                // we don't match cmd/fn because they don't seem to use IME
+                                modifiers: Default::default(),
+                                key: ime_text.clone().unwrap(),
+                                ime_key: None, // todo!("handle IME key")
+                            },
+                        };
+                        handled = callback(InputEvent::KeyDown(event_with_ime_text));
+                    }
+                    if !handled {
+                        // empty key happens when you type a deadkey in input composition.
+                        // (e.g. on a brazillian keyboard typing quote is a deadkey)
+                        if !event.keystroke.key.is_empty() {
+                            handled = callback(InputEvent::KeyDown(event));
+                        }
+                    }
                 }
 
                 if !handled {
@@ -1033,7 +1150,7 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent:
                     }
                 }
 
-                window_state.borrow_mut().event_callback = Some(callback);
+                window_state.lock().event_callback = Some(callback);
             }
         } else {
             handled = true;
@@ -1047,108 +1164,103 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent:
 
 extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
     let window_state = unsafe { get_window_state(this) };
-    let weak_window_state = Rc::downgrade(&window_state);
-    let mut window_state_borrow = window_state.as_ref().borrow_mut();
-    let is_active = unsafe { window_state_borrow.native_window.isKeyWindow() == YES };
+    let weak_window_state = Arc::downgrade(&window_state);
+    let mut lock = window_state.as_ref().lock();
+    let is_active = unsafe { lock.native_window.isKeyWindow() == YES };
 
-    let window_height = window_state_borrow.content_size().y();
-    let event = unsafe { Event::from_native(native_event, Some(window_height)) };
+    let window_height = lock.content_size().height;
+    let event = unsafe { InputEvent::from_native(native_event, Some(window_height)) };
 
     if let Some(mut event) = event {
-        let synthesized_second_event = match &mut event {
-            Event::MouseDown(
-                event @ MouseButtonEvent {
+        match &mut event {
+            InputEvent::MouseDown(
+                event @ MouseDownEvent {
                     button: MouseButton::Left,
-                    modifiers: Modifiers { ctrl: true, .. },
+                    modifiers: Modifiers { control: true, .. },
                     ..
                 },
             ) => {
-                *event = MouseButtonEvent {
+                // On mac, a ctrl-left click should be handled as a right click.
+                *event = MouseDownEvent {
                     button: MouseButton::Right,
                     modifiers: Modifiers {
-                        ctrl: false,
+                        control: false,
                         ..event.modifiers
                     },
                     click_count: 1,
                     ..*event
                 };
-
-                Some(Event::MouseUp(MouseButtonEvent {
-                    button: MouseButton::Right,
-                    ..*event
-                }))
             }
 
             // Because we map a ctrl-left_down to a right_down -> right_up let's ignore
             // the ctrl-left_up to avoid having a mismatch in button down/up events if the
             // user is still holding ctrl when releasing the left mouse button
-            Event::MouseUp(MouseButtonEvent {
-                button: MouseButton::Left,
-                modifiers: Modifiers { ctrl: true, .. },
-                ..
-            }) => {
-                window_state_borrow.synthetic_drag_counter += 1;
-                return;
+            InputEvent::MouseUp(
+                event @ MouseUpEvent {
+                    button: MouseButton::Left,
+                    modifiers: Modifiers { control: true, .. },
+                    ..
+                },
+            ) => {
+                *event = MouseUpEvent {
+                    button: MouseButton::Right,
+                    modifiers: Modifiers {
+                        control: false,
+                        ..event.modifiers
+                    },
+                    click_count: 1,
+                    ..*event
+                };
             }
 
-            _ => None,
+            _ => {}
         };
 
         match &event {
-            Event::MouseMoved(
-                event @ MouseMovedEvent {
+            InputEvent::MouseMove(
+                event @ MouseMoveEvent {
                     pressed_button: Some(_),
                     ..
                 },
             ) => {
-                window_state_borrow.synthetic_drag_counter += 1;
-                window_state_borrow
-                    .executor
+                lock.synthetic_drag_counter += 1;
+                let executor = lock.executor.clone();
+                executor
                     .spawn(synthetic_drag(
                         weak_window_state,
-                        window_state_borrow.synthetic_drag_counter,
-                        *event,
+                        lock.synthetic_drag_counter,
+                        event.clone(),
                     ))
                     .detach();
             }
 
-            Event::MouseMoved(_)
-                if !(is_active || window_state_borrow.kind == WindowKind::PopUp) =>
-            {
-                return
-            }
+            InputEvent::MouseMove(_) if !(is_active || lock.kind == WindowKind::PopUp) => return,
 
-            Event::MouseUp(MouseButtonEvent {
-                button: MouseButton::Left,
-                ..
-            }) => {
-                window_state_borrow.synthetic_drag_counter += 1;
+            InputEvent::MouseUp(MouseUpEvent { .. }) => {
+                lock.synthetic_drag_counter += 1;
             }
 
-            Event::ModifiersChanged(ModifiersChangedEvent { modifiers }) => {
+            InputEvent::ModifiersChanged(ModifiersChangedEvent { modifiers }) => {
                 // Only raise modifiers changed event when they have actually changed
-                if let Some(Event::ModifiersChanged(ModifiersChangedEvent {
+                if let Some(InputEvent::ModifiersChanged(ModifiersChangedEvent {
                     modifiers: prev_modifiers,
-                })) = &window_state_borrow.previous_modifiers_changed_event
+                })) = &lock.previous_modifiers_changed_event
                 {
                     if prev_modifiers == modifiers {
                         return;
                     }
                 }
 
-                window_state_borrow.previous_modifiers_changed_event = Some(event.clone());
+                lock.previous_modifiers_changed_event = Some(event.clone());
             }
 
             _ => {}
         }
 
-        if let Some(mut callback) = window_state_borrow.event_callback.take() {
-            drop(window_state_borrow);
+        if let Some(mut callback) = lock.event_callback.take() {
+            drop(lock);
             callback(event);
-            if let Some(event) = synthesized_second_event {
-                callback(event);
-            }
-            window_state.borrow_mut().event_callback = Some(callback);
+            window_state.lock().event_callback = Some(callback);
         }
     }
 }

crates/gpui/src/platform/test.rs 🔗

@@ -1,434 +1,9 @@
-use super::{AppVersion, CursorStyle, WindowBounds};
-use crate::{
-    geometry::{
-        rect::RectF,
-        vector::{vec2f, Vector2F},
-    },
-    keymap_matcher::KeymapMatcher,
-    Action, AnyWindowHandle, ClipboardItem, Menu,
-};
-use anyhow::{anyhow, Result};
-use collections::VecDeque;
-use parking_lot::Mutex;
-use postage::oneshot;
-use std::{
-    any::Any,
-    cell::RefCell,
-    path::{Path, PathBuf},
-    rc::Rc,
-    sync::Arc,
-};
-use time::UtcOffset;
-
-struct Dispatcher;
-
-impl super::Dispatcher for Dispatcher {
-    fn is_main_thread(&self) -> bool {
-        true
-    }
-
-    fn run_on_main_thread(&self, task: async_task::Runnable) {
-        task.run();
-    }
-}
-
-pub fn foreground_platform() -> ForegroundPlatform {
-    ForegroundPlatform::default()
-}
-
-#[derive(Default)]
-pub struct ForegroundPlatform {
-    last_prompt_for_new_path_args: RefCell<Option<(PathBuf, oneshot::Sender<Option<PathBuf>>)>>,
-}
-
-#[cfg(any(test, feature = "test-support"))]
-impl ForegroundPlatform {
-    pub(crate) fn simulate_new_path_selection(
-        &self,
-        result: impl FnOnce(PathBuf) -> Option<PathBuf>,
-    ) {
-        let (dir_path, mut done_tx) = self
-            .last_prompt_for_new_path_args
-            .take()
-            .expect("prompt_for_new_path was not called");
-        let _ = postage::sink::Sink::try_send(&mut done_tx, result(dir_path));
-    }
-
-    pub(crate) fn did_prompt_for_new_path(&self) -> bool {
-        self.last_prompt_for_new_path_args.borrow().is_some()
-    }
-}
-
-impl super::ForegroundPlatform for ForegroundPlatform {
-    fn on_become_active(&self, _: Box<dyn FnMut()>) {}
-    fn on_resign_active(&self, _: Box<dyn FnMut()>) {}
-    fn on_quit(&self, _: Box<dyn FnMut()>) {}
-    fn on_reopen(&self, _: Box<dyn FnMut()>) {}
-    fn on_event(&self, _: Box<dyn FnMut(crate::platform::Event) -> bool>) {}
-    fn on_open_urls(&self, _: Box<dyn FnMut(Vec<String>)>) {}
-
-    fn run(&self, _on_finish_launching: Box<dyn FnOnce()>) {
-        unimplemented!()
-    }
-
-    fn on_menu_command(&self, _: Box<dyn FnMut(&dyn Action)>) {}
-    fn on_validate_menu_command(&self, _: Box<dyn FnMut(&dyn Action) -> bool>) {}
-    fn on_will_open_menu(&self, _: Box<dyn FnMut()>) {}
-    fn set_menus(&self, _: Vec<Menu>, _: &KeymapMatcher) {}
-
-    fn prompt_for_paths(
-        &self,
-        _: super::PathPromptOptions,
-    ) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
-        let (_done_tx, done_rx) = oneshot::channel();
-        done_rx
-    }
-
-    fn prompt_for_new_path(&self, path: &Path) -> oneshot::Receiver<Option<PathBuf>> {
-        let (done_tx, done_rx) = oneshot::channel();
-        *self.last_prompt_for_new_path_args.borrow_mut() = Some((path.to_path_buf(), done_tx));
-        done_rx
-    }
-
-    fn reveal_path(&self, _: &Path) {}
-}
-
-pub fn platform() -> Platform {
-    Platform::new()
-}
-
-pub struct Platform {
-    dispatcher: Arc<dyn super::Dispatcher>,
-    fonts: Arc<dyn super::FontSystem>,
-    current_clipboard_item: Mutex<Option<ClipboardItem>>,
-    cursor: Mutex<CursorStyle>,
-    active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
-    active_screen: Screen,
-}
-
-impl Platform {
-    fn new() -> Self {
-        Self {
-            dispatcher: Arc::new(Dispatcher),
-            fonts: Arc::new(super::current::FontSystem::new()),
-            current_clipboard_item: Default::default(),
-            cursor: Mutex::new(CursorStyle::Arrow),
-            active_window: Default::default(),
-            active_screen: Screen::new(),
-        }
-    }
-}
-
-impl super::Platform for Platform {
-    fn dispatcher(&self) -> Arc<dyn super::Dispatcher> {
-        self.dispatcher.clone()
-    }
-
-    fn fonts(&self) -> std::sync::Arc<dyn super::FontSystem> {
-        self.fonts.clone()
-    }
-
-    fn activate(&self, _ignoring_other_apps: bool) {}
-
-    fn hide(&self) {}
-
-    fn hide_other_apps(&self) {}
-
-    fn unhide_other_apps(&self) {}
-
-    fn quit(&self) {}
-
-    fn screen_by_id(&self, uuid: uuid::Uuid) -> Option<Rc<dyn crate::platform::Screen>> {
-        if self.active_screen.uuid == uuid {
-            Some(Rc::new(self.active_screen.clone()))
-        } else {
-            None
-        }
-    }
-
-    fn screens(&self) -> Vec<Rc<dyn crate::platform::Screen>> {
-        vec![Rc::new(self.active_screen.clone())]
-    }
-
-    fn open_window(
-        &self,
-        handle: AnyWindowHandle,
-        options: super::WindowOptions,
-        _executor: Rc<super::executor::Foreground>,
-    ) -> Box<dyn super::Window> {
-        *self.active_window.lock() = Some(handle);
-        Box::new(Window::new(
-            handle,
-            match options.bounds {
-                WindowBounds::Maximized | WindowBounds::Fullscreen => vec2f(1024., 768.),
-                WindowBounds::Fixed(rect) => rect.size(),
-            },
-            self.active_window.clone(),
-            Rc::new(self.active_screen.clone()),
-        ))
-    }
-
-    fn main_window(&self) -> Option<AnyWindowHandle> {
-        self.active_window.lock().clone()
-    }
-
-    fn add_status_item(&self, handle: AnyWindowHandle) -> Box<dyn crate::platform::Window> {
-        Box::new(Window::new(
-            handle,
-            vec2f(24., 24.),
-            self.active_window.clone(),
-            Rc::new(self.active_screen.clone()),
-        ))
-    }
-
-    fn write_to_clipboard(&self, item: ClipboardItem) {
-        *self.current_clipboard_item.lock() = Some(item);
-    }
-
-    fn read_from_clipboard(&self) -> Option<ClipboardItem> {
-        self.current_clipboard_item.lock().clone()
-    }
-
-    fn open_url(&self, _: &str) {}
-
-    fn write_credentials(&self, _: &str, _: &str, _: &[u8]) -> Result<()> {
-        Ok(())
-    }
-
-    fn read_credentials(&self, _: &str) -> Result<Option<(String, Vec<u8>)>> {
-        Ok(None)
-    }
-
-    fn delete_credentials(&self, _: &str) -> Result<()> {
-        Ok(())
-    }
-
-    fn set_cursor_style(&self, style: CursorStyle) {
-        *self.cursor.lock() = style;
-    }
-
-    fn should_auto_hide_scrollbars(&self) -> bool {
-        false
-    }
-
-    fn local_timezone(&self) -> UtcOffset {
-        UtcOffset::UTC
-    }
-
-    fn path_for_auxiliary_executable(&self, _name: &str) -> Result<PathBuf> {
-        Err(anyhow!("app not running inside a bundle"))
-    }
-
-    fn app_path(&self) -> Result<PathBuf> {
-        Err(anyhow!("app not running inside a bundle"))
-    }
-
-    fn app_version(&self) -> Result<AppVersion> {
-        Ok(AppVersion {
-            major: 1,
-            minor: 0,
-            patch: 0,
-        })
-    }
-
-    fn os_name(&self) -> &'static str {
-        "test"
-    }
-
-    fn os_version(&self) -> Result<AppVersion> {
-        Ok(AppVersion {
-            major: 1,
-            minor: 0,
-            patch: 0,
-        })
-    }
-
-    fn restart(&self) {}
-}
-
-#[derive(Debug, Clone)]
-pub struct Screen {
-    uuid: uuid::Uuid,
-}
-
-impl Screen {
-    fn new() -> Self {
-        Self {
-            uuid: uuid::Uuid::new_v4(),
-        }
-    }
-}
-
-impl super::Screen for Screen {
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
-    fn bounds(&self) -> RectF {
-        RectF::new(Vector2F::zero(), Vector2F::new(1920., 1080.))
-    }
-
-    fn content_bounds(&self) -> RectF {
-        RectF::new(Vector2F::zero(), Vector2F::new(1920., 1080.))
-    }
-
-    fn display_uuid(&self) -> Option<uuid::Uuid> {
-        Some(self.uuid)
-    }
-}
-
-pub struct Window {
-    handle: AnyWindowHandle,
-    pub(crate) size: Vector2F,
-    scale_factor: f32,
-    current_scene: Option<crate::Scene>,
-    event_handlers: Vec<Box<dyn FnMut(super::Event) -> bool>>,
-    pub(crate) resize_handlers: Vec<Box<dyn FnMut()>>,
-    pub(crate) moved_handlers: Vec<Box<dyn FnMut()>>,
-    close_handlers: Vec<Box<dyn FnOnce()>>,
-    fullscreen_handlers: Vec<Box<dyn FnMut(bool)>>,
-    pub(crate) active_status_change_handlers: Vec<Box<dyn FnMut(bool)>>,
-    pub(crate) should_close_handler: Option<Box<dyn FnMut() -> bool>>,
-    pub(crate) title: Option<String>,
-    pub(crate) edited: bool,
-    pub(crate) pending_prompts: RefCell<VecDeque<oneshot::Sender<usize>>>,
-    active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
-    screen: Rc<Screen>,
-}
-
-impl Window {
-    pub fn new(
-        handle: AnyWindowHandle,
-        size: Vector2F,
-        active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
-        screen: Rc<Screen>,
-    ) -> Self {
-        Self {
-            handle,
-            size,
-            event_handlers: Default::default(),
-            resize_handlers: Default::default(),
-            moved_handlers: Default::default(),
-            close_handlers: Default::default(),
-            should_close_handler: Default::default(),
-            active_status_change_handlers: Default::default(),
-            fullscreen_handlers: Default::default(),
-            scale_factor: 1.0,
-            current_scene: None,
-            title: None,
-            edited: false,
-            pending_prompts: Default::default(),
-            active_window,
-            screen,
-        }
-    }
-
-    pub fn title(&self) -> Option<String> {
-        self.title.clone()
-    }
-}
-
-impl super::Window for Window {
-    fn bounds(&self) -> WindowBounds {
-        WindowBounds::Fixed(RectF::new(Vector2F::zero(), self.size))
-    }
-
-    fn content_size(&self) -> Vector2F {
-        self.size
-    }
-
-    fn scale_factor(&self) -> f32 {
-        self.scale_factor
-    }
-
-    fn titlebar_height(&self) -> f32 {
-        24.
-    }
-
-    fn appearance(&self) -> crate::platform::Appearance {
-        crate::platform::Appearance::Light
-    }
-
-    fn screen(&self) -> Rc<dyn crate::platform::Screen> {
-        self.screen.clone()
-    }
-
-    fn mouse_position(&self) -> Vector2F {
-        Vector2F::zero()
-    }
-
-    fn as_any_mut(&mut self) -> &mut dyn Any {
-        self
-    }
-
-    fn set_input_handler(&mut self, _: Box<dyn crate::platform::InputHandler>) {}
-
-    fn prompt(
-        &self,
-        _: crate::platform::PromptLevel,
-        _: &str,
-        _: &[&str],
-    ) -> oneshot::Receiver<usize> {
-        let (done_tx, done_rx) = oneshot::channel();
-        self.pending_prompts.borrow_mut().push_back(done_tx);
-        done_rx
-    }
-
-    fn activate(&self) {
-        *self.active_window.lock() = Some(self.handle);
-    }
-
-    fn set_title(&mut self, title: &str) {
-        self.title = Some(title.to_string())
-    }
-
-    fn set_edited(&mut self, edited: bool) {
-        self.edited = edited;
-    }
-
-    fn show_character_palette(&self) {}
-
-    fn minimize(&self) {}
-
-    fn zoom(&self) {}
-
-    fn present_scene(&mut self, scene: crate::Scene) {
-        self.current_scene = Some(scene);
-    }
-
-    fn toggle_full_screen(&self) {}
-
-    fn on_event(&mut self, callback: Box<dyn FnMut(crate::platform::Event) -> bool>) {
-        self.event_handlers.push(callback);
-    }
-
-    fn on_active_status_change(&mut self, callback: Box<dyn FnMut(bool)>) {
-        self.active_status_change_handlers.push(callback);
-    }
-
-    fn on_resize(&mut self, callback: Box<dyn FnMut()>) {
-        self.resize_handlers.push(callback);
-    }
-
-    fn on_fullscreen(&mut self, callback: Box<dyn FnMut(bool)>) {
-        self.fullscreen_handlers.push(callback)
-    }
-
-    fn on_moved(&mut self, callback: Box<dyn FnMut()>) {
-        self.moved_handlers.push(callback);
-    }
-
-    fn on_should_close(&mut self, callback: Box<dyn FnMut() -> bool>) {
-        self.should_close_handler = Some(callback);
-    }
-
-    fn on_close(&mut self, callback: Box<dyn FnOnce()>) {
-        self.close_handlers.push(callback);
-    }
-
-    fn on_appearance_changed(&mut self, _: Box<dyn FnMut()>) {}
-
-    fn is_topmost_for_position(&self, _position: Vector2F) -> bool {
-        true
-    }
-}
+mod dispatcher;
+mod display;
+mod platform;
+mod window;
+
+pub use dispatcher::*;
+pub use display::*;
+pub use platform::*;
+pub use window::*;

crates/gpui/src/scene.rs 🔗

@@ -1,565 +1,778 @@
-mod mouse_event;
-mod mouse_region;
-mod region;
-
-#[cfg(debug_assertions)]
-use collections::HashSet;
-use derive_more::Mul;
-use schemars::JsonSchema;
-use serde::Deserialize;
-use serde_derive::Serialize;
-use std::{
-    any::{Any, TypeId},
-    borrow::Cow,
-    rc::Rc,
-    sync::Arc,
-};
-
 use crate::{
-    color::Color,
-    fonts::{FontId, GlyphId},
-    geometry::{rect::RectF, vector::Vector2F},
-    platform::{current::Surface, CursorStyle},
-    ImageData, WindowContext,
+    point, AtlasTextureId, AtlasTile, Bounds, ContentMask, Corners, Edges, Hsla, Pixels, Point,
+    ScaledPixels, StackingOrder,
 };
-pub use mouse_event::*;
-pub use mouse_region::*;
+use collections::BTreeMap;
+use std::{fmt::Debug, iter::Peekable, mem, slice};
 
-pub struct SceneBuilder {
-    stacking_contexts: Vec<StackingContext>,
-    active_stacking_context_stack: Vec<usize>,
-    /// Used by the gpui2 crate.
-    pub event_handlers: Vec<EventHandler>,
-    #[cfg(debug_assertions)]
-    mouse_region_ids: HashSet<MouseRegionId>,
-}
+// Exported to metal
+pub(crate) type PointF = Point<f32>;
+#[allow(non_camel_case_types, unused)]
+pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
 
-pub struct Scene {
-    scale_factor: f32,
-    stacking_contexts: Vec<StackingContext>,
-    event_handlers: Vec<EventHandler>,
-}
+pub type LayerId = u32;
 
-struct StackingContext {
-    layers: Vec<Layer>,
-    active_layer_stack: Vec<usize>,
-    z_index: usize,
-}
+pub type DrawOrder = u32;
 
 #[derive(Default)]
-pub struct Layer {
-    clip_bounds: Option<RectF>,
+pub(crate) struct SceneBuilder {
+    last_order: Option<(StackingOrder, LayerId)>,
+    layers_by_order: BTreeMap<StackingOrder, LayerId>,
+    shadows: Vec<Shadow>,
     quads: Vec<Quad>,
+    paths: Vec<Path<ScaledPixels>>,
     underlines: Vec<Underline>,
-    images: Vec<Image>,
+    monochrome_sprites: Vec<MonochromeSprite>,
+    polychrome_sprites: Vec<PolychromeSprite>,
     surfaces: Vec<Surface>,
-    shadows: Vec<Shadow>,
-    glyphs: Vec<Glyph>,
-    image_glyphs: Vec<ImageGlyph>,
-    icons: Vec<Icon>,
-    paths: Vec<Path>,
-    cursor_regions: Vec<CursorRegion>,
-    mouse_regions: Vec<MouseRegion>,
-}
-
-#[derive(Copy, Clone)]
-pub struct CursorRegion {
-    pub bounds: RectF,
-    pub style: CursorStyle,
 }
 
-#[derive(Default, Debug)]
-pub struct Quad {
-    pub bounds: RectF,
-    pub background: Option<Color>,
-    pub border: Border,
-    pub corner_radii: CornerRadii,
-}
-
-#[derive(Default, Debug, Mul, Clone, Copy, Serialize, JsonSchema)]
-pub struct CornerRadii {
-    pub top_left: f32,
-    pub top_right: f32,
-    pub bottom_right: f32,
-    pub bottom_left: f32,
-}
-
-impl<'de> Deserialize<'de> for CornerRadii {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        #[derive(Deserialize)]
-        pub struct CornerRadiiHelper {
-            pub top_left: Option<f32>,
-            pub top_right: Option<f32>,
-            pub bottom_right: Option<f32>,
-            pub bottom_left: Option<f32>,
+impl SceneBuilder {
+    pub fn build(&mut self) -> Scene {
+        let mut orders = vec![0; self.layers_by_order.len()];
+        for (ix, layer_id) in self.layers_by_order.values().enumerate() {
+            orders[*layer_id as usize] = ix as u32;
         }
+        self.layers_by_order.clear();
+        self.last_order = None;
 
-        #[derive(Deserialize)]
-        #[serde(untagged)]
-        enum RadiusOrRadii {
-            Radius(f32),
-            Radii(CornerRadiiHelper),
+        for shadow in &mut self.shadows {
+            shadow.order = orders[shadow.order as usize];
         }
+        self.shadows.sort_by_key(|shadow| shadow.order);
 
-        let json = RadiusOrRadii::deserialize(deserializer)?;
-
-        let result = match json {
-            RadiusOrRadii::Radius(radius) => CornerRadii::from(radius),
-            RadiusOrRadii::Radii(CornerRadiiHelper {
-                top_left,
-                top_right,
-                bottom_right,
-                bottom_left,
-            }) => CornerRadii {
-                top_left: top_left.unwrap_or(0.0),
-                top_right: top_right.unwrap_or(0.0),
-                bottom_right: bottom_right.unwrap_or(0.0),
-                bottom_left: bottom_left.unwrap_or(0.0),
-            },
-        };
+        for quad in &mut self.quads {
+            quad.order = orders[quad.order as usize];
+        }
+        self.quads.sort_by_key(|quad| quad.order);
 
-        Ok(result)
-    }
-}
+        for path in &mut self.paths {
+            path.order = orders[path.order as usize];
+        }
+        self.paths.sort_by_key(|path| path.order);
 
-impl From<f32> for CornerRadii {
-    fn from(radius: f32) -> Self {
-        Self {
-            top_left: radius,
-            top_right: radius,
-            bottom_right: radius,
-            bottom_left: radius,
+        for underline in &mut self.underlines {
+            underline.order = orders[underline.order as usize];
         }
-    }
-}
+        self.underlines.sort_by_key(|underline| underline.order);
 
-#[derive(Debug)]
-pub struct Shadow {
-    pub bounds: RectF,
-    pub corner_radii: CornerRadii,
-    pub sigma: f32,
-    pub color: Color,
-}
+        for monochrome_sprite in &mut self.monochrome_sprites {
+            monochrome_sprite.order = orders[monochrome_sprite.order as usize];
+        }
+        self.monochrome_sprites.sort_by_key(|sprite| sprite.order);
 
-#[derive(Debug, Clone, Copy)]
-pub struct Glyph {
-    pub font_id: FontId,
-    pub font_size: f32,
-    pub id: GlyphId,
-    pub origin: Vector2F,
-    pub color: Color,
-}
+        for polychrome_sprite in &mut self.polychrome_sprites {
+            polychrome_sprite.order = orders[polychrome_sprite.order as usize];
+        }
+        self.polychrome_sprites.sort_by_key(|sprite| sprite.order);
 
-#[derive(Debug)]
-pub struct ImageGlyph {
-    pub font_id: FontId,
-    pub font_size: f32,
-    pub id: GlyphId,
-    pub origin: Vector2F,
-}
+        for surface in &mut self.surfaces {
+            surface.order = orders[surface.order as usize];
+        }
+        self.surfaces.sort_by_key(|surface| surface.order);
 
-pub struct Icon {
-    pub bounds: RectF,
-    pub svg: usvg::Tree,
-    pub path: Cow<'static, str>,
-    pub color: Color,
-}
+        Scene {
+            shadows: mem::take(&mut self.shadows),
+            quads: mem::take(&mut self.quads),
+            paths: mem::take(&mut self.paths),
+            underlines: mem::take(&mut self.underlines),
+            monochrome_sprites: mem::take(&mut self.monochrome_sprites),
+            polychrome_sprites: mem::take(&mut self.polychrome_sprites),
+            surfaces: mem::take(&mut self.surfaces),
+        }
+    }
 
-#[derive(Clone, Copy, Default, Debug)]
-pub struct Border {
-    pub color: Color,
-    pub top: f32,
-    pub right: f32,
-    pub bottom: f32,
-    pub left: f32,
-}
+    pub fn insert(&mut self, order: &StackingOrder, primitive: impl Into<Primitive>) {
+        let primitive = primitive.into();
+        let clipped_bounds = primitive
+            .bounds()
+            .intersect(&primitive.content_mask().bounds);
+        if clipped_bounds.size.width <= ScaledPixels(0.)
+            || clipped_bounds.size.height <= ScaledPixels(0.)
+        {
+            return;
+        }
 
-#[derive(Clone, Copy, Default, Debug)]
-pub struct Underline {
-    pub origin: Vector2F,
-    pub width: f32,
-    pub thickness: f32,
-    pub color: Color,
-    pub squiggly: bool,
-}
+        let layer_id = self.layer_id_for_order(order);
+        match primitive {
+            Primitive::Shadow(mut shadow) => {
+                shadow.order = layer_id;
+                self.shadows.push(shadow);
+            }
+            Primitive::Quad(mut quad) => {
+                quad.order = layer_id;
+                self.quads.push(quad);
+            }
+            Primitive::Path(mut path) => {
+                path.order = layer_id;
+                path.id = PathId(self.paths.len());
+                self.paths.push(path);
+            }
+            Primitive::Underline(mut underline) => {
+                underline.order = layer_id;
+                self.underlines.push(underline);
+            }
+            Primitive::MonochromeSprite(mut sprite) => {
+                sprite.order = layer_id;
+                self.monochrome_sprites.push(sprite);
+            }
+            Primitive::PolychromeSprite(mut sprite) => {
+                sprite.order = layer_id;
+                self.polychrome_sprites.push(sprite);
+            }
+            Primitive::Surface(mut surface) => {
+                surface.order = layer_id;
+                self.surfaces.push(surface);
+            }
+        }
+    }
 
-#[derive(Debug)]
-pub struct Path {
-    pub bounds: RectF,
-    pub color: Color,
-    pub vertices: Vec<PathVertex>,
-}
+    fn layer_id_for_order(&mut self, order: &StackingOrder) -> u32 {
+        if let Some((last_order, last_layer_id)) = self.last_order.as_ref() {
+            if last_order == order {
+                return *last_layer_id;
+            }
+        };
 
-#[derive(Debug)]
-pub struct PathVertex {
-    pub xy_position: Vector2F,
-    pub st_position: Vector2F,
+        let layer_id = if let Some(layer_id) = self.layers_by_order.get(order) {
+            *layer_id
+        } else {
+            let next_id = self.layers_by_order.len() as LayerId;
+            self.layers_by_order.insert(order.clone(), next_id);
+            next_id
+        };
+        self.last_order = Some((order.clone(), layer_id));
+        layer_id
+    }
 }
 
-pub struct Image {
-    pub bounds: RectF,
-    pub border: Border,
-    pub corner_radii: CornerRadii,
-    pub grayscale: bool,
-    pub data: Arc<ImageData>,
+pub struct Scene {
+    pub shadows: Vec<Shadow>,
+    pub quads: Vec<Quad>,
+    pub paths: Vec<Path<ScaledPixels>>,
+    pub underlines: Vec<Underline>,
+    pub monochrome_sprites: Vec<MonochromeSprite>,
+    pub polychrome_sprites: Vec<PolychromeSprite>,
+    pub surfaces: Vec<Surface>,
 }
 
 impl Scene {
-    pub fn scale_factor(&self) -> f32 {
-        self.scale_factor
-    }
-
-    pub fn layers(&self) -> impl Iterator<Item = &Layer> {
-        self.stacking_contexts.iter().flat_map(|s| &s.layers)
-    }
-
-    pub fn cursor_regions(&self) -> Vec<CursorRegion> {
-        self.layers()
-            .flat_map(|layer| &layer.cursor_regions)
-            .copied()
-            .collect()
-    }
-
-    pub fn mouse_regions(&self) -> Vec<(MouseRegion, usize)> {
-        self.stacking_contexts
-            .iter()
-            .flat_map(|context| {
-                context
-                    .layers
-                    .iter()
-                    .flat_map(|layer| &layer.mouse_regions)
-                    .map(|region| (region.clone(), context.z_index))
-            })
-            .collect()
+    pub fn paths(&self) -> &[Path<ScaledPixels>] {
+        &self.paths
+    }
+
+    pub(crate) fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> {
+        BatchIterator {
+            shadows: &self.shadows,
+            shadows_start: 0,
+            shadows_iter: self.shadows.iter().peekable(),
+            quads: &self.quads,
+            quads_start: 0,
+            quads_iter: self.quads.iter().peekable(),
+            paths: &self.paths,
+            paths_start: 0,
+            paths_iter: self.paths.iter().peekable(),
+            underlines: &self.underlines,
+            underlines_start: 0,
+            underlines_iter: self.underlines.iter().peekable(),
+            monochrome_sprites: &self.monochrome_sprites,
+            monochrome_sprites_start: 0,
+            monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(),
+            polychrome_sprites: &self.polychrome_sprites,
+            polychrome_sprites_start: 0,
+            polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(),
+            surfaces: &self.surfaces,
+            surfaces_start: 0,
+            surfaces_iter: self.surfaces.iter().peekable(),
+        }
     }
+}
 
-    pub fn take_event_handlers(&mut self) -> Vec<EventHandler> {
-        self.event_handlers
-            .sort_by(|a, b| a.order.cmp(&b.order).reverse());
-        std::mem::take(&mut self.event_handlers)
-    }
+struct BatchIterator<'a> {
+    shadows: &'a [Shadow],
+    shadows_start: usize,
+    shadows_iter: Peekable<slice::Iter<'a, Shadow>>,
+    quads: &'a [Quad],
+    quads_start: usize,
+    quads_iter: Peekable<slice::Iter<'a, Quad>>,
+    paths: &'a [Path<ScaledPixels>],
+    paths_start: usize,
+    paths_iter: Peekable<slice::Iter<'a, Path<ScaledPixels>>>,
+    underlines: &'a [Underline],
+    underlines_start: usize,
+    underlines_iter: Peekable<slice::Iter<'a, Underline>>,
+    monochrome_sprites: &'a [MonochromeSprite],
+    monochrome_sprites_start: usize,
+    monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>,
+    polychrome_sprites: &'a [PolychromeSprite],
+    polychrome_sprites_start: usize,
+    polychrome_sprites_iter: Peekable<slice::Iter<'a, PolychromeSprite>>,
+    surfaces: &'a [Surface],
+    surfaces_start: usize,
+    surfaces_iter: Peekable<slice::Iter<'a, Surface>>,
 }
 
-impl SceneBuilder {
-    pub fn new() -> Self {
-        let mut this = SceneBuilder {
-            stacking_contexts: Vec::new(),
-            active_stacking_context_stack: Vec::new(),
-            #[cfg(debug_assertions)]
-            mouse_region_ids: HashSet::default(),
-            event_handlers: Vec::new(),
+impl<'a> Iterator for BatchIterator<'a> {
+    type Item = PrimitiveBatch<'a>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let mut orders_and_kinds = [
+            (
+                self.shadows_iter.peek().map(|s| s.order),
+                PrimitiveKind::Shadow,
+            ),
+            (self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad),
+            (self.paths_iter.peek().map(|q| q.order), PrimitiveKind::Path),
+            (
+                self.underlines_iter.peek().map(|u| u.order),
+                PrimitiveKind::Underline,
+            ),
+            (
+                self.monochrome_sprites_iter.peek().map(|s| s.order),
+                PrimitiveKind::MonochromeSprite,
+            ),
+            (
+                self.polychrome_sprites_iter.peek().map(|s| s.order),
+                PrimitiveKind::PolychromeSprite,
+            ),
+            (
+                self.surfaces_iter.peek().map(|s| s.order),
+                PrimitiveKind::Surface,
+            ),
+        ];
+        orders_and_kinds.sort_by_key(|(order, kind)| (order.unwrap_or(u32::MAX), *kind));
+
+        let first = orders_and_kinds[0];
+        let second = orders_and_kinds[1];
+        let (batch_kind, max_order) = if first.0.is_some() {
+            (first.1, second.0.unwrap_or(u32::MAX))
+        } else {
+            return None;
         };
-        this.clear();
-        this
-    }
-
-    pub fn clear(&mut self) {
-        self.stacking_contexts.clear();
-        self.stacking_contexts.push(StackingContext::new(None, 0));
-        self.active_stacking_context_stack.clear();
-        self.active_stacking_context_stack.push(0);
-        #[cfg(debug_assertions)]
-        self.mouse_region_ids.clear();
-    }
-
-    pub fn build(&mut self, scale_factor: f32) -> Scene {
-        let mut stacking_contexts = std::mem::take(&mut self.stacking_contexts);
-        stacking_contexts.sort_by_key(|context| context.z_index);
-        let event_handlers = std::mem::take(&mut self.event_handlers);
-        self.clear();
 
-        Scene {
-            scale_factor,
-            stacking_contexts,
-            event_handlers,
+        match batch_kind {
+            PrimitiveKind::Shadow => {
+                let shadows_start = self.shadows_start;
+                let mut shadows_end = shadows_start + 1;
+                self.shadows_iter.next();
+                while self
+                    .shadows_iter
+                    .next_if(|shadow| shadow.order < max_order)
+                    .is_some()
+                {
+                    shadows_end += 1;
+                }
+                self.shadows_start = shadows_end;
+                Some(PrimitiveBatch::Shadows(
+                    &self.shadows[shadows_start..shadows_end],
+                ))
+            }
+            PrimitiveKind::Quad => {
+                let quads_start = self.quads_start;
+                let mut quads_end = quads_start + 1;
+                self.quads_iter.next();
+                while self
+                    .quads_iter
+                    .next_if(|quad| quad.order < max_order)
+                    .is_some()
+                {
+                    quads_end += 1;
+                }
+                self.quads_start = quads_end;
+                Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end]))
+            }
+            PrimitiveKind::Path => {
+                let paths_start = self.paths_start;
+                let mut paths_end = paths_start + 1;
+                self.paths_iter.next();
+                while self
+                    .paths_iter
+                    .next_if(|path| path.order < max_order)
+                    .is_some()
+                {
+                    paths_end += 1;
+                }
+                self.paths_start = paths_end;
+                Some(PrimitiveBatch::Paths(&self.paths[paths_start..paths_end]))
+            }
+            PrimitiveKind::Underline => {
+                let underlines_start = self.underlines_start;
+                let mut underlines_end = underlines_start + 1;
+                self.underlines_iter.next();
+                while self
+                    .underlines_iter
+                    .next_if(|underline| underline.order < max_order)
+                    .is_some()
+                {
+                    underlines_end += 1;
+                }
+                self.underlines_start = underlines_end;
+                Some(PrimitiveBatch::Underlines(
+                    &self.underlines[underlines_start..underlines_end],
+                ))
+            }
+            PrimitiveKind::MonochromeSprite => {
+                let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id;
+                let sprites_start = self.monochrome_sprites_start;
+                let mut sprites_end = sprites_start + 1;
+                self.monochrome_sprites_iter.next();
+                while self
+                    .monochrome_sprites_iter
+                    .next_if(|sprite| {
+                        sprite.order < max_order && sprite.tile.texture_id == texture_id
+                    })
+                    .is_some()
+                {
+                    sprites_end += 1;
+                }
+                self.monochrome_sprites_start = sprites_end;
+                Some(PrimitiveBatch::MonochromeSprites {
+                    texture_id,
+                    sprites: &self.monochrome_sprites[sprites_start..sprites_end],
+                })
+            }
+            PrimitiveKind::PolychromeSprite => {
+                let texture_id = self.polychrome_sprites_iter.peek().unwrap().tile.texture_id;
+                let sprites_start = self.polychrome_sprites_start;
+                let mut sprites_end = self.polychrome_sprites_start + 1;
+                self.polychrome_sprites_iter.next();
+                while self
+                    .polychrome_sprites_iter
+                    .next_if(|sprite| {
+                        sprite.order < max_order && sprite.tile.texture_id == texture_id
+                    })
+                    .is_some()
+                {
+                    sprites_end += 1;
+                }
+                self.polychrome_sprites_start = sprites_end;
+                Some(PrimitiveBatch::PolychromeSprites {
+                    texture_id,
+                    sprites: &self.polychrome_sprites[sprites_start..sprites_end],
+                })
+            }
+            PrimitiveKind::Surface => {
+                let surfaces_start = self.surfaces_start;
+                let mut surfaces_end = surfaces_start + 1;
+                self.surfaces_iter.next();
+                while self
+                    .surfaces_iter
+                    .next_if(|surface| surface.order < max_order)
+                    .is_some()
+                {
+                    surfaces_end += 1;
+                }
+                self.surfaces_start = surfaces_end;
+                Some(PrimitiveBatch::Surfaces(
+                    &self.surfaces[surfaces_start..surfaces_end],
+                ))
+            }
         }
     }
+}
 
-    pub fn push_stacking_context(&mut self, clip_bounds: Option<RectF>, z_index: Option<usize>) {
-        let z_index = z_index.unwrap_or_else(|| self.active_stacking_context().z_index + 1);
-        self.active_stacking_context_stack
-            .push(self.stacking_contexts.len());
-        self.stacking_contexts
-            .push(StackingContext::new(clip_bounds, z_index))
-    }
-
-    pub fn pop_stacking_context(&mut self) {
-        self.active_stacking_context_stack.pop();
-        assert!(!self.active_stacking_context_stack.is_empty());
-    }
-
-    pub fn push_layer(&mut self, clip_bounds: Option<RectF>) {
-        self.active_stacking_context().push_layer(clip_bounds);
-    }
-
-    pub fn pop_layer(&mut self) {
-        self.active_stacking_context().pop_layer();
-    }
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
+pub enum PrimitiveKind {
+    Shadow,
+    #[default]
+    Quad,
+    Path,
+    Underline,
+    MonochromeSprite,
+    PolychromeSprite,
+    Surface,
+}
 
-    pub fn push_quad(&mut self, quad: Quad) {
-        self.active_layer().push_quad(quad)
-    }
+pub enum Primitive {
+    Shadow(Shadow),
+    Quad(Quad),
+    Path(Path<ScaledPixels>),
+    Underline(Underline),
+    MonochromeSprite(MonochromeSprite),
+    PolychromeSprite(PolychromeSprite),
+    Surface(Surface),
+}
 
-    pub fn push_cursor_region(&mut self, region: CursorRegion) {
-        if can_draw(region.bounds) {
-            self.active_layer().push_cursor_region(region);
+impl Primitive {
+    pub fn bounds(&self) -> &Bounds<ScaledPixels> {
+        match self {
+            Primitive::Shadow(shadow) => &shadow.bounds,
+            Primitive::Quad(quad) => &quad.bounds,
+            Primitive::Path(path) => &path.bounds,
+            Primitive::Underline(underline) => &underline.bounds,
+            Primitive::MonochromeSprite(sprite) => &sprite.bounds,
+            Primitive::PolychromeSprite(sprite) => &sprite.bounds,
+            Primitive::Surface(surface) => &surface.bounds,
         }
     }
 
-    pub fn push_mouse_region(&mut self, region: MouseRegion) {
-        if can_draw(region.bounds) {
-            // Ensure that Regions cannot be added to a scene with the same region id.
-            #[cfg(debug_assertions)]
-            let region_id;
-            #[cfg(debug_assertions)]
-            {
-                region_id = region.id();
-            }
-
-            if self.active_layer().push_mouse_region(region) {
-                #[cfg(debug_assertions)]
-                {
-                    if !self.mouse_region_ids.insert(region_id) {
-                        let tag_name = region_id.tag_type_name();
-                        panic!("Same MouseRegionId: {region_id:?} inserted multiple times to the same scene. \
-                            Will cause problems! Look for MouseRegion that uses Tag: {tag_name}");
-                    }
-                }
-            }
+    pub fn content_mask(&self) -> &ContentMask<ScaledPixels> {
+        match self {
+            Primitive::Shadow(shadow) => &shadow.content_mask,
+            Primitive::Quad(quad) => &quad.content_mask,
+            Primitive::Path(path) => &path.content_mask,
+            Primitive::Underline(underline) => &underline.content_mask,
+            Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
+            Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
+            Primitive::Surface(surface) => &surface.content_mask,
         }
     }
+}
 
-    pub fn push_image(&mut self, image: Image) {
-        self.active_layer().push_image(image)
-    }
+#[derive(Debug)]
+pub(crate) enum PrimitiveBatch<'a> {
+    Shadows(&'a [Shadow]),
+    Quads(&'a [Quad]),
+    Paths(&'a [Path<ScaledPixels>]),
+    Underlines(&'a [Underline]),
+    MonochromeSprites {
+        texture_id: AtlasTextureId,
+        sprites: &'a [MonochromeSprite],
+    },
+    PolychromeSprites {
+        texture_id: AtlasTextureId,
+        sprites: &'a [PolychromeSprite],
+    },
+    Surfaces(&'a [Surface]),
+}
 
-    pub fn push_surface(&mut self, surface: Surface) {
-        self.active_layer().push_surface(surface)
-    }
+#[derive(Default, Debug, Clone, Eq, PartialEq)]
+#[repr(C)]
+pub struct Quad {
+    pub order: u32, // Initially a LayerId, then a DrawOrder.
+    pub bounds: Bounds<ScaledPixels>,
+    pub content_mask: ContentMask<ScaledPixels>,
+    pub background: Hsla,
+    pub border_color: Hsla,
+    pub corner_radii: Corners<ScaledPixels>,
+    pub border_widths: Edges<ScaledPixels>,
+}
 
-    pub fn push_underline(&mut self, underline: Underline) {
-        self.active_layer().push_underline(underline)
+impl Ord for Quad {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.order.cmp(&other.order)
     }
+}
 
-    pub fn push_shadow(&mut self, shadow: Shadow) {
-        self.active_layer().push_shadow(shadow)
+impl PartialOrd for Quad {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
     }
+}
 
-    pub fn push_glyph(&mut self, glyph: Glyph) {
-        self.active_layer().push_glyph(glyph)
+impl From<Quad> for Primitive {
+    fn from(quad: Quad) -> Self {
+        Primitive::Quad(quad)
     }
+}
 
-    pub fn push_image_glyph(&mut self, image_glyph: ImageGlyph) {
-        self.active_layer().push_image_glyph(image_glyph)
-    }
+#[derive(Debug, Clone, Eq, PartialEq)]
+#[repr(C)]
+pub struct Underline {
+    pub order: u32,
+    pub bounds: Bounds<ScaledPixels>,
+    pub content_mask: ContentMask<ScaledPixels>,
+    pub thickness: ScaledPixels,
+    pub color: Hsla,
+    pub wavy: bool,
+}
 
-    pub fn push_icon(&mut self, icon: Icon) {
-        self.active_layer().push_icon(icon)
+impl Ord for Underline {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.order.cmp(&other.order)
     }
+}
 
-    pub fn push_path(&mut self, path: Path) {
-        self.active_layer().push_path(path);
+impl PartialOrd for Underline {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
     }
+}
 
-    fn active_stacking_context(&mut self) -> &mut StackingContext {
-        let ix = *self.active_stacking_context_stack.last().unwrap();
-        &mut self.stacking_contexts[ix]
+impl From<Underline> for Primitive {
+    fn from(underline: Underline) -> Self {
+        Primitive::Underline(underline)
     }
+}
 
-    fn active_layer(&mut self) -> &mut Layer {
-        self.active_stacking_context().active_layer()
-    }
+#[derive(Debug, Clone, Eq, PartialEq)]
+#[repr(C)]
+pub struct Shadow {
+    pub order: u32,
+    pub bounds: Bounds<ScaledPixels>,
+    pub corner_radii: Corners<ScaledPixels>,
+    pub content_mask: ContentMask<ScaledPixels>,
+    pub color: Hsla,
+    pub blur_radius: ScaledPixels,
 }
 
-impl StackingContext {
-    fn new(clip_bounds: Option<RectF>, z_index: usize) -> Self {
-        Self {
-            layers: vec![Layer::new(clip_bounds)],
-            active_layer_stack: vec![0],
-            z_index,
-        }
+impl Ord for Shadow {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.order.cmp(&other.order)
     }
+}
 
-    fn active_layer(&mut self) -> &mut Layer {
-        &mut self.layers[*self.active_layer_stack.last().unwrap()]
+impl PartialOrd for Shadow {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
     }
+}
 
-    fn push_layer(&mut self, clip_bounds: Option<RectF>) {
-        let parent_clip_bounds = self.active_layer().clip_bounds();
-        let clip_bounds = clip_bounds
-            .map(|clip_bounds| {
-                clip_bounds
-                    .intersection(parent_clip_bounds.unwrap_or(clip_bounds))
-                    .unwrap_or_else(|| {
-                        if !clip_bounds.is_empty() {
-                            log::warn!("specified clip bounds are disjoint from parent layer");
-                        }
-                        RectF::default()
-                    })
-            })
-            .or(parent_clip_bounds);
-
-        let ix = self.layers.len();
-        self.layers.push(Layer::new(clip_bounds));
-        self.active_layer_stack.push(ix);
+impl From<Shadow> for Primitive {
+    fn from(shadow: Shadow) -> Self {
+        Primitive::Shadow(shadow)
     }
+}
 
-    fn pop_layer(&mut self) {
-        self.active_layer_stack.pop().unwrap();
-        assert!(!self.active_layer_stack.is_empty());
-    }
+#[derive(Clone, Debug, Eq, PartialEq)]
+#[repr(C)]
+pub struct MonochromeSprite {
+    pub order: u32,
+    pub bounds: Bounds<ScaledPixels>,
+    pub content_mask: ContentMask<ScaledPixels>,
+    pub color: Hsla,
+    pub tile: AtlasTile,
 }
 
-impl Layer {
-    pub fn new(clip_bounds: Option<RectF>) -> Self {
-        Self {
-            clip_bounds,
-            quads: Default::default(),
-            underlines: Default::default(),
-            images: Default::default(),
-            surfaces: Default::default(),
-            shadows: Default::default(),
-            image_glyphs: Default::default(),
-            glyphs: Default::default(),
-            icons: Default::default(),
-            paths: Default::default(),
-            cursor_regions: Default::default(),
-            mouse_regions: Default::default(),
+impl Ord for MonochromeSprite {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        match self.order.cmp(&other.order) {
+            std::cmp::Ordering::Equal => self.tile.tile_id.cmp(&other.tile.tile_id),
+            order => order,
         }
     }
+}
 
-    pub fn clip_bounds(&self) -> Option<RectF> {
-        self.clip_bounds
+impl PartialOrd for MonochromeSprite {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
     }
+}
 
-    fn push_quad(&mut self, quad: Quad) {
-        if can_draw(quad.bounds) {
-            self.quads.push(quad);
-        }
+impl From<MonochromeSprite> for Primitive {
+    fn from(sprite: MonochromeSprite) -> Self {
+        Primitive::MonochromeSprite(sprite)
     }
+}
 
-    pub fn quads(&self) -> &[Quad] {
-        self.quads.as_slice()
-    }
+#[derive(Clone, Debug, Eq, PartialEq)]
+#[repr(C)]
+pub struct PolychromeSprite {
+    pub order: u32,
+    pub bounds: Bounds<ScaledPixels>,
+    pub content_mask: ContentMask<ScaledPixels>,
+    pub corner_radii: Corners<ScaledPixels>,
+    pub tile: AtlasTile,
+    pub grayscale: bool,
+}
 
-    fn push_cursor_region(&mut self, region: CursorRegion) {
-        if let Some(bounds) = region
-            .bounds
-            .intersection(self.clip_bounds.unwrap_or(region.bounds))
-        {
-            if can_draw(bounds) {
-                self.cursor_regions.push(region);
-            }
+impl Ord for PolychromeSprite {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        match self.order.cmp(&other.order) {
+            std::cmp::Ordering::Equal => self.tile.tile_id.cmp(&other.tile.tile_id),
+            order => order,
         }
     }
+}
 
-    fn push_mouse_region(&mut self, region: MouseRegion) -> bool {
-        if let Some(bounds) = region
-            .bounds
-            .intersection(self.clip_bounds.unwrap_or(region.bounds))
-        {
-            if can_draw(bounds) {
-                self.mouse_regions.push(region);
-                return true;
-            }
-        }
-        false
+impl PartialOrd for PolychromeSprite {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
     }
+}
 
-    fn push_underline(&mut self, underline: Underline) {
-        if underline.width > 0. {
-            self.underlines.push(underline);
-        }
+impl From<PolychromeSprite> for Primitive {
+    fn from(sprite: PolychromeSprite) -> Self {
+        Primitive::PolychromeSprite(sprite)
     }
+}
 
-    pub fn underlines(&self) -> &[Underline] {
-        self.underlines.as_slice()
-    }
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct Surface {
+    pub order: u32,
+    pub bounds: Bounds<ScaledPixels>,
+    pub content_mask: ContentMask<ScaledPixels>,
+    pub image_buffer: media::core_video::CVImageBuffer,
+}
 
-    fn push_image(&mut self, image: Image) {
-        if can_draw(image.bounds) {
-            self.images.push(image);
-        }
+impl Ord for Surface {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.order.cmp(&other.order)
     }
+}
 
-    pub fn images(&self) -> &[Image] {
-        self.images.as_slice()
+impl PartialOrd for Surface {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
     }
+}
 
-    fn push_surface(&mut self, surface: Surface) {
-        if can_draw(surface.bounds) {
-            self.surfaces.push(surface);
-        }
+impl From<Surface> for Primitive {
+    fn from(surface: Surface) -> Self {
+        Primitive::Surface(surface)
     }
+}
 
-    pub fn surfaces(&self) -> &[Surface] {
-        self.surfaces.as_slice()
-    }
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
+pub(crate) struct PathId(pub(crate) usize);
+
+#[derive(Debug)]
+pub struct Path<P: Clone + Default + Debug> {
+    pub(crate) id: PathId,
+    order: u32,
+    pub(crate) bounds: Bounds<P>,
+    pub(crate) content_mask: ContentMask<P>,
+    pub(crate) vertices: Vec<PathVertex<P>>,
+    pub(crate) color: Hsla,
+    start: Point<P>,
+    current: Point<P>,
+    contour_count: usize,
+}
 
-    fn push_shadow(&mut self, shadow: Shadow) {
-        if can_draw(shadow.bounds) {
-            self.shadows.push(shadow);
+impl Path<Pixels> {
+    pub fn new(start: Point<Pixels>) -> Self {
+        Self {
+            id: PathId(0),
+            order: 0,
+            vertices: Vec::new(),
+            start,
+            current: start,
+            bounds: Bounds {
+                origin: start,
+                size: Default::default(),
+            },
+            content_mask: Default::default(),
+            color: Default::default(),
+            contour_count: 0,
         }
     }
 
-    pub fn shadows(&self) -> &[Shadow] {
-        self.shadows.as_slice()
+    pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
+        Path {
+            id: self.id,
+            order: self.order,
+            bounds: self.bounds.scale(factor),
+            content_mask: self.content_mask.scale(factor),
+            vertices: self
+                .vertices
+                .iter()
+                .map(|vertex| vertex.scale(factor))
+                .collect(),
+            start: self.start.map(|start| start.scale(factor)),
+            current: self.current.scale(factor),
+            contour_count: self.contour_count,
+            color: self.color,
+        }
     }
 
-    fn push_image_glyph(&mut self, glyph: ImageGlyph) {
-        self.image_glyphs.push(glyph);
+    pub fn line_to(&mut self, to: Point<Pixels>) {
+        self.contour_count += 1;
+        if self.contour_count > 1 {
+            self.push_triangle(
+                (self.start, self.current, to),
+                (point(0., 1.), point(0., 1.), point(0., 1.)),
+            );
+        }
+        self.current = to;
     }
 
-    pub fn image_glyphs(&self) -> &[ImageGlyph] {
-        self.image_glyphs.as_slice()
-    }
+    pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
+        self.contour_count += 1;
+        if self.contour_count > 1 {
+            self.push_triangle(
+                (self.start, self.current, to),
+                (point(0., 1.), point(0., 1.), point(0., 1.)),
+            );
+        }
 
-    fn push_glyph(&mut self, glyph: Glyph) {
-        self.glyphs.push(glyph);
+        self.push_triangle(
+            (self.current, ctrl, to),
+            (point(0., 0.), point(0.5, 0.), point(1., 1.)),
+        );
+        self.current = to;
     }
 
-    pub fn glyphs(&self) -> &[Glyph] {
-        self.glyphs.as_slice()
+    fn push_triangle(
+        &mut self,
+        xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
+        st: (Point<f32>, Point<f32>, Point<f32>),
+    ) {
+        self.bounds = self
+            .bounds
+            .union(&Bounds {
+                origin: xy.0,
+                size: Default::default(),
+            })
+            .union(&Bounds {
+                origin: xy.1,
+                size: Default::default(),
+            })
+            .union(&Bounds {
+                origin: xy.2,
+                size: Default::default(),
+            });
+
+        self.vertices.push(PathVertex {
+            xy_position: xy.0,
+            st_position: st.0,
+            content_mask: Default::default(),
+        });
+        self.vertices.push(PathVertex {
+            xy_position: xy.1,
+            st_position: st.1,
+            content_mask: Default::default(),
+        });
+        self.vertices.push(PathVertex {
+            xy_position: xy.2,
+            st_position: st.2,
+            content_mask: Default::default(),
+        });
     }
+}
 
-    pub fn push_icon(&mut self, icon: Icon) {
-        if can_draw(icon.bounds) {
-            self.icons.push(icon);
-        }
-    }
+impl Eq for Path<ScaledPixels> {}
 
-    pub fn icons(&self) -> &[Icon] {
-        self.icons.as_slice()
+impl PartialEq for Path<ScaledPixels> {
+    fn eq(&self, other: &Self) -> bool {
+        self.order == other.order
     }
+}
 
-    fn push_path(&mut self, path: Path) {
-        if can_draw(path.bounds) {
-            self.paths.push(path);
-        }
+impl Ord for Path<ScaledPixels> {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.order.cmp(&other.order)
     }
+}
 
-    pub fn paths(&self) -> &[Path] {
-        self.paths.as_slice()
+impl PartialOrd for Path<ScaledPixels> {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
     }
 }
 
-impl MouseRegion {
-    pub fn id(&self) -> MouseRegionId {
-        self.id
+impl From<Path<ScaledPixels>> for Primitive {
+    fn from(path: Path<ScaledPixels>) -> Self {
+        Primitive::Path(path)
     }
 }
 
-pub struct EventHandler {
-    pub order: u32,
-    // The &dyn Any parameter below expects an event.
-    pub handler: Rc<dyn Fn(&dyn Any, &mut WindowContext) -> bool>,
-    pub event_type: TypeId,
+#[derive(Clone, Debug)]
+#[repr(C)]
+pub struct PathVertex<P: Clone + Default + Debug> {
+    pub(crate) xy_position: Point<P>,
+    pub(crate) st_position: Point<f32>,
+    pub(crate) content_mask: ContentMask<P>,
 }
 
-fn can_draw(bounds: RectF) -> bool {
-    let size = bounds.size();
-    size.x() > 0. && size.y() > 0.
+impl PathVertex<Pixels> {
+    pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> {
+        PathVertex {
+            xy_position: self.xy_position.scale(factor),
+            st_position: self.st_position,
+            content_mask: self.content_mask.scale(factor),
+        }
+    }
 }
+
+#[derive(Copy, Clone, Debug)]
+pub struct AtlasId(pub(crate) usize);

crates/gpui/src/scene/mouse_event.rs 🔗

@@ -1,270 +0,0 @@
-use crate::{
-    platform::{MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent},
-    scene::mouse_region::HandlerKey,
-};
-use pathfinder_geometry::{rect::RectF, vector::Vector2F};
-use std::{
-    mem::{discriminant, Discriminant},
-    ops::Deref,
-};
-
-#[derive(Debug, Default, Clone)]
-pub struct MouseMove {
-    pub region: RectF,
-    pub platform_event: MouseMovedEvent,
-}
-
-impl Deref for MouseMove {
-    type Target = MouseMovedEvent;
-
-    fn deref(&self) -> &Self::Target {
-        &self.platform_event
-    }
-}
-
-#[derive(Debug, Default, Clone)]
-pub struct MouseMoveOut {
-    pub region: RectF,
-}
-
-#[derive(Debug, Default, Clone)]
-pub struct MouseDrag {
-    pub region: RectF,
-    pub prev_mouse_position: Vector2F,
-    pub platform_event: MouseMovedEvent,
-    pub end: bool,
-}
-
-impl Deref for MouseDrag {
-    type Target = MouseMovedEvent;
-
-    fn deref(&self) -> &Self::Target {
-        &self.platform_event
-    }
-}
-
-#[derive(Debug, Default, Clone)]
-pub struct MouseHover {
-    pub region: RectF,
-    pub started: bool,
-    pub platform_event: MouseMovedEvent,
-}
-
-impl Deref for MouseHover {
-    type Target = MouseMovedEvent;
-
-    fn deref(&self) -> &Self::Target {
-        &self.platform_event
-    }
-}
-
-#[derive(Debug, Default, Clone)]
-pub struct MouseDown {
-    pub region: RectF,
-    pub platform_event: MouseButtonEvent,
-}
-
-impl Deref for MouseDown {
-    type Target = MouseButtonEvent;
-
-    fn deref(&self) -> &Self::Target {
-        &self.platform_event
-    }
-}
-
-#[derive(Debug, Default, Clone)]
-pub struct MouseUp {
-    pub region: RectF,
-    pub platform_event: MouseButtonEvent,
-}
-
-impl Deref for MouseUp {
-    type Target = MouseButtonEvent;
-
-    fn deref(&self) -> &Self::Target {
-        &self.platform_event
-    }
-}
-
-#[derive(Debug, Default, Clone)]
-pub struct MouseClick {
-    pub region: RectF,
-    pub platform_event: MouseButtonEvent,
-}
-
-impl Deref for MouseClick {
-    type Target = MouseButtonEvent;
-
-    fn deref(&self) -> &Self::Target {
-        &self.platform_event
-    }
-}
-
-#[derive(Debug, Default, Clone)]
-pub struct MouseClickOut {
-    pub region: RectF,
-    pub platform_event: MouseButtonEvent,
-}
-
-impl Deref for MouseClickOut {
-    type Target = MouseButtonEvent;
-
-    fn deref(&self) -> &Self::Target {
-        &self.platform_event
-    }
-}
-
-#[derive(Debug, Default, Clone)]
-pub struct MouseDownOut {
-    pub region: RectF,
-    pub platform_event: MouseButtonEvent,
-}
-
-impl Deref for MouseDownOut {
-    type Target = MouseButtonEvent;
-
-    fn deref(&self) -> &Self::Target {
-        &self.platform_event
-    }
-}
-
-#[derive(Debug, Default, Clone)]
-pub struct MouseUpOut {
-    pub region: RectF,
-    pub platform_event: MouseButtonEvent,
-}
-
-impl Deref for MouseUpOut {
-    type Target = MouseButtonEvent;
-
-    fn deref(&self) -> &Self::Target {
-        &self.platform_event
-    }
-}
-
-#[derive(Debug, Default, Clone)]
-pub struct MouseScrollWheel {
-    pub region: RectF,
-    pub platform_event: ScrollWheelEvent,
-}
-
-impl Deref for MouseScrollWheel {
-    type Target = ScrollWheelEvent;
-
-    fn deref(&self) -> &Self::Target {
-        &self.platform_event
-    }
-}
-
-#[derive(Debug, Clone)]
-pub enum MouseEvent {
-    Move(MouseMove),
-    MoveOut(MouseMoveOut),
-    Drag(MouseDrag),
-    Hover(MouseHover),
-    Down(MouseDown),
-    Up(MouseUp),
-    Click(MouseClick),
-    ClickOut(MouseClickOut),
-    DownOut(MouseDownOut),
-    UpOut(MouseUpOut),
-    ScrollWheel(MouseScrollWheel),
-}
-
-impl MouseEvent {
-    pub fn set_region(&mut self, region: RectF) {
-        match self {
-            MouseEvent::Move(r) => r.region = region,
-            MouseEvent::MoveOut(r) => r.region = region,
-            MouseEvent::Drag(r) => r.region = region,
-            MouseEvent::Hover(r) => r.region = region,
-            MouseEvent::Down(r) => r.region = region,
-            MouseEvent::Up(r) => r.region = region,
-            MouseEvent::Click(r) => r.region = region,
-            MouseEvent::ClickOut(r) => r.region = region,
-            MouseEvent::DownOut(r) => r.region = region,
-            MouseEvent::UpOut(r) => r.region = region,
-            MouseEvent::ScrollWheel(r) => r.region = region,
-        }
-    }
-
-    /// When true, mouse event handlers must call cx.propagate_event() to bubble
-    /// the event to handlers they are painted on top of.
-    pub fn is_capturable(&self) -> bool {
-        match self {
-            MouseEvent::Move(_) => true,
-            MouseEvent::MoveOut(_) => false,
-            MouseEvent::Drag(_) => true,
-            MouseEvent::Hover(_) => false,
-            MouseEvent::Down(_) => true,
-            MouseEvent::Up(_) => true,
-            MouseEvent::Click(_) => true,
-            MouseEvent::ClickOut(_) => true,
-            MouseEvent::DownOut(_) => false,
-            MouseEvent::UpOut(_) => false,
-            MouseEvent::ScrollWheel(_) => true,
-        }
-    }
-}
-
-impl MouseEvent {
-    pub fn move_disc() -> Discriminant<MouseEvent> {
-        discriminant(&MouseEvent::Move(Default::default()))
-    }
-
-    pub fn move_out_disc() -> Discriminant<MouseEvent> {
-        discriminant(&MouseEvent::MoveOut(Default::default()))
-    }
-
-    pub fn drag_disc() -> Discriminant<MouseEvent> {
-        discriminant(&MouseEvent::Drag(Default::default()))
-    }
-
-    pub fn hover_disc() -> Discriminant<MouseEvent> {
-        discriminant(&MouseEvent::Hover(Default::default()))
-    }
-
-    pub fn down_disc() -> Discriminant<MouseEvent> {
-        discriminant(&MouseEvent::Down(Default::default()))
-    }
-
-    pub fn up_disc() -> Discriminant<MouseEvent> {
-        discriminant(&MouseEvent::Up(Default::default()))
-    }
-
-    pub fn up_out_disc() -> Discriminant<MouseEvent> {
-        discriminant(&MouseEvent::UpOut(Default::default()))
-    }
-
-    pub fn click_disc() -> Discriminant<MouseEvent> {
-        discriminant(&MouseEvent::Click(Default::default()))
-    }
-
-    pub fn click_out_disc() -> Discriminant<MouseEvent> {
-        discriminant(&MouseEvent::ClickOut(Default::default()))
-    }
-
-    pub fn down_out_disc() -> Discriminant<MouseEvent> {
-        discriminant(&MouseEvent::DownOut(Default::default()))
-    }
-
-    pub fn scroll_wheel_disc() -> Discriminant<MouseEvent> {
-        discriminant(&MouseEvent::ScrollWheel(Default::default()))
-    }
-
-    pub fn handler_key(&self) -> HandlerKey {
-        match self {
-            MouseEvent::Move(_) => HandlerKey::new(Self::move_disc(), None),
-            MouseEvent::MoveOut(_) => HandlerKey::new(Self::move_out_disc(), None),
-            MouseEvent::Drag(e) => HandlerKey::new(Self::drag_disc(), e.pressed_button),
-            MouseEvent::Hover(_) => HandlerKey::new(Self::hover_disc(), None),
-            MouseEvent::Down(e) => HandlerKey::new(Self::down_disc(), Some(e.button)),
-            MouseEvent::Up(e) => HandlerKey::new(Self::up_disc(), Some(e.button)),
-            MouseEvent::Click(e) => HandlerKey::new(Self::click_disc(), Some(e.button)),
-            MouseEvent::ClickOut(e) => HandlerKey::new(Self::click_out_disc(), Some(e.button)),
-            MouseEvent::UpOut(e) => HandlerKey::new(Self::up_out_disc(), Some(e.button)),
-            MouseEvent::DownOut(e) => HandlerKey::new(Self::down_out_disc(), Some(e.button)),
-            MouseEvent::ScrollWheel(_) => HandlerKey::new(Self::scroll_wheel_disc(), None),
-        }
-    }
-}

crates/gpui/src/scene/mouse_region.rs 🔗

@@ -1,555 +0,0 @@
-use crate::{platform::MouseButton, window::WindowContext, EventContext, TypeTag, ViewContext};
-use collections::HashMap;
-use pathfinder_geometry::rect::RectF;
-use smallvec::SmallVec;
-use std::{any::Any, fmt::Debug, mem::Discriminant, rc::Rc};
-
-use super::{
-    mouse_event::{
-        MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseEvent, MouseHover, MouseMove, MouseUp,
-        MouseUpOut,
-    },
-    MouseClickOut, MouseMoveOut, MouseScrollWheel,
-};
-
-#[derive(Clone)]
-pub struct MouseRegion {
-    pub id: MouseRegionId,
-    pub bounds: RectF,
-    pub handlers: HandlerSet,
-    pub hoverable: bool,
-    pub notify_on_hover: bool,
-    pub notify_on_click: bool,
-}
-
-impl MouseRegion {
-    /// Region ID is used to track semantically equivalent mouse regions across render passes.
-    /// e.g. if you have mouse handlers attached to a list item type, then each item of the list
-    /// should pass a different (consistent) region_id. If you have one big region that covers your
-    /// whole component, just pass the view_id again.
-    pub fn new<Tag: 'static>(view_id: usize, region_id: usize, bounds: RectF) -> Self {
-        Self::from_handlers(
-            TypeTag::new::<Tag>(),
-            view_id,
-            region_id,
-            bounds,
-            Default::default(),
-        )
-    }
-
-    pub fn handle_all<Tag: 'static>(view_id: usize, region_id: usize, bounds: RectF) -> Self {
-        Self::from_handlers(
-            TypeTag::new::<Tag>(),
-            view_id,
-            region_id,
-            bounds,
-            HandlerSet::capture_all(),
-        )
-    }
-
-    pub fn from_handlers(
-        tag: TypeTag,
-        view_id: usize,
-        region_id: usize,
-        bounds: RectF,
-        handlers: HandlerSet,
-    ) -> Self {
-        Self {
-            id: MouseRegionId {
-                view_id,
-                tag,
-                region_id,
-            },
-            bounds,
-            handlers,
-            hoverable: true,
-            notify_on_hover: false,
-            notify_on_click: false,
-        }
-    }
-
-    pub fn on_down<V, F>(mut self, button: MouseButton, handler: F) -> Self
-    where
-        V: 'static,
-        F: Fn(MouseDown, &mut V, &mut EventContext<V>) + 'static,
-    {
-        self.handlers = self.handlers.on_down(button, handler);
-        self
-    }
-
-    pub fn on_up<V, F>(mut self, button: MouseButton, handler: F) -> Self
-    where
-        V: 'static,
-        F: Fn(MouseUp, &mut V, &mut EventContext<V>) + 'static,
-    {
-        self.handlers = self.handlers.on_up(button, handler);
-        self
-    }
-
-    pub fn on_click<V, F>(mut self, button: MouseButton, handler: F) -> Self
-    where
-        V: 'static,
-        F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
-    {
-        self.handlers = self.handlers.on_click(button, handler);
-        self
-    }
-
-    pub fn on_click_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
-    where
-        V: 'static,
-        F: Fn(MouseClickOut, &mut V, &mut EventContext<V>) + 'static,
-    {
-        self.handlers = self.handlers.on_click_out(button, handler);
-        self
-    }
-
-    pub fn on_down_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
-    where
-        V: 'static,
-        F: Fn(MouseDownOut, &mut V, &mut EventContext<V>) + 'static,
-    {
-        self.handlers = self.handlers.on_down_out(button, handler);
-        self
-    }
-
-    pub fn on_up_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
-    where
-        V: 'static,
-        F: Fn(MouseUpOut, &mut V, &mut EventContext<V>) + 'static,
-    {
-        self.handlers = self.handlers.on_up_out(button, handler);
-        self
-    }
-
-    pub fn on_drag<V, F>(mut self, button: MouseButton, handler: F) -> Self
-    where
-        V: 'static,
-        F: Fn(MouseDrag, &mut V, &mut EventContext<V>) + 'static,
-    {
-        self.handlers = self.handlers.on_drag(button, handler);
-        self
-    }
-
-    pub fn on_hover<V, F>(mut self, handler: F) -> Self
-    where
-        V: 'static,
-        F: Fn(MouseHover, &mut V, &mut EventContext<V>) + 'static,
-    {
-        self.handlers = self.handlers.on_hover(handler);
-        self
-    }
-
-    pub fn on_move<V, F>(mut self, handler: F) -> Self
-    where
-        V: 'static,
-        F: Fn(MouseMove, &mut V, &mut EventContext<V>) + 'static,
-    {
-        self.handlers = self.handlers.on_move(handler);
-        self
-    }
-
-    pub fn on_move_out<V, F>(mut self, handler: F) -> Self
-    where
-        V: 'static,
-        F: Fn(MouseMoveOut, &mut V, &mut EventContext<V>) + 'static,
-    {
-        self.handlers = self.handlers.on_move_out(handler);
-        self
-    }
-
-    pub fn on_scroll<V, F>(mut self, handler: F) -> Self
-    where
-        V: 'static,
-        F: Fn(MouseScrollWheel, &mut V, &mut EventContext<V>) + 'static,
-    {
-        self.handlers = self.handlers.on_scroll(handler);
-        self
-    }
-
-    pub fn with_hoverable(mut self, is_hoverable: bool) -> Self {
-        self.hoverable = is_hoverable;
-        self
-    }
-
-    pub fn with_notify_on_hover(mut self, notify: bool) -> Self {
-        self.notify_on_hover = notify;
-        self
-    }
-
-    pub fn with_notify_on_click(mut self, notify: bool) -> Self {
-        self.notify_on_click = notify;
-        self
-    }
-}
-
-#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)]
-pub struct MouseRegionId {
-    view_id: usize,
-    tag: TypeTag,
-    region_id: usize,
-}
-
-impl MouseRegionId {
-    pub(crate) fn new(tag: TypeTag, view_id: usize, region_id: usize) -> Self {
-        MouseRegionId {
-            view_id,
-            region_id,
-            tag,
-        }
-    }
-
-    pub fn view_id(&self) -> usize {
-        self.view_id
-    }
-
-    #[cfg(debug_assertions)]
-    pub fn tag_type_name(&self) -> &'static str {
-        self.tag.type_name()
-    }
-}
-
-pub type HandlerCallback = Rc<dyn Fn(MouseEvent, &mut dyn Any, &mut WindowContext, usize) -> bool>;
-
-#[derive(Clone, PartialEq, Eq, Hash)]
-pub struct HandlerKey {
-    event_kind: Discriminant<MouseEvent>,
-    button: Option<MouseButton>,
-}
-
-impl HandlerKey {
-    pub fn new(event_kind: Discriminant<MouseEvent>, button: Option<MouseButton>) -> HandlerKey {
-        HandlerKey { event_kind, button }
-    }
-}
-
-#[derive(Clone, Default)]
-pub struct HandlerSet {
-    set: HashMap<HandlerKey, SmallVec<[HandlerCallback; 1]>>,
-}
-
-impl HandlerSet {
-    pub fn capture_all() -> Self {
-        let mut set: HashMap<HandlerKey, SmallVec<[HandlerCallback; 1]>> = HashMap::default();
-
-        set.insert(
-            HandlerKey::new(MouseEvent::move_disc(), None),
-            SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
-        );
-        set.insert(
-            HandlerKey::new(MouseEvent::hover_disc(), None),
-            SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
-        );
-        for button in MouseButton::all() {
-            set.insert(
-                HandlerKey::new(MouseEvent::drag_disc(), Some(button)),
-                SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
-            );
-            set.insert(
-                HandlerKey::new(MouseEvent::down_disc(), Some(button)),
-                SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
-            );
-            set.insert(
-                HandlerKey::new(MouseEvent::up_disc(), Some(button)),
-                SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
-            );
-            set.insert(
-                HandlerKey::new(MouseEvent::click_disc(), Some(button)),
-                SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
-            );
-            set.insert(
-                HandlerKey::new(MouseEvent::click_out_disc(), Some(button)),
-                SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
-            );
-            set.insert(
-                HandlerKey::new(MouseEvent::down_out_disc(), Some(button)),
-                SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
-            );
-            set.insert(
-                HandlerKey::new(MouseEvent::up_out_disc(), Some(button)),
-                SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
-            );
-        }
-        set.insert(
-            HandlerKey::new(MouseEvent::scroll_wheel_disc(), None),
-            SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
-        );
-
-        HandlerSet { set }
-    }
-
-    pub fn get(&self, key: &HandlerKey) -> Option<&[HandlerCallback]> {
-        self.set.get(key).map(|vec| vec.as_slice())
-    }
-
-    pub fn contains(
-        &self,
-        discriminant: Discriminant<MouseEvent>,
-        button: Option<MouseButton>,
-    ) -> bool {
-        self.set
-            .contains_key(&HandlerKey::new(discriminant, button))
-    }
-
-    fn insert(
-        &mut self,
-        event_kind: Discriminant<MouseEvent>,
-        button: Option<MouseButton>,
-        callback: HandlerCallback,
-    ) {
-        use std::collections::hash_map::Entry;
-
-        match self.set.entry(HandlerKey::new(event_kind, button)) {
-            Entry::Occupied(mut vec) => {
-                vec.get_mut().push(callback);
-            }
-
-            Entry::Vacant(entry) => {
-                entry.insert(SmallVec::from_buf([callback]));
-            }
-        }
-    }
-
-    pub fn on_move<V, F>(mut self, handler: F) -> Self
-    where
-        V: 'static,
-        F: Fn(MouseMove, &mut V, &mut EventContext<V>) + 'static,
-    {
-        self.insert(MouseEvent::move_disc(), None,
-            Rc::new(move |region_event, view, cx, view_id| {
-                if let MouseEvent::Move(e) = region_event {
-                    let view = view.downcast_mut().unwrap();
-                    let mut cx = ViewContext::mutable(cx, view_id);
-                    let mut cx = EventContext::new(&mut cx);
-                    handler(e, view, &mut cx);
-                    cx.handled
-                } else {
-                    panic!(
-                        "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Move, found {:?}",
-                        region_event);
-                }
-            }));
-        self
-    }
-
-    pub fn on_move_out<V, F>(mut self, handler: F) -> Self
-    where
-        V: 'static,
-        F: Fn(MouseMoveOut, &mut V, &mut EventContext<V>) + 'static,
-    {
-        self.insert(MouseEvent::move_out_disc(), None,
-            Rc::new(move |region_event, view, cx, view_id| {
-                if let MouseEvent::MoveOut(e) = region_event {
-                    let view = view.downcast_mut().unwrap();
-                    let mut cx = ViewContext::<V>::mutable(cx, view_id);
-                    let mut cx = EventContext::new(&mut cx);
-                    handler(e, view, &mut cx);
-                    cx.handled
-                } else {
-                    panic!(
-                        "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::MoveOut, found {:?}",
-                        region_event);
-                }
-            }));
-        self
-    }
-
-    pub fn on_down<V, F>(mut self, button: MouseButton, handler: F) -> Self
-    where
-        V: 'static,
-        F: Fn(MouseDown, &mut V, &mut EventContext<V>) + 'static,
-    {
-        self.insert(MouseEvent::down_disc(), Some(button),
-            Rc::new(move |region_event, view, cx, view_id| {
-                if let MouseEvent::Down(e) = region_event {
-                    let view = view.downcast_mut().unwrap();
-                    let mut cx = ViewContext::mutable(cx, view_id);
-                    let mut cx = EventContext::new(&mut cx);
-                    handler(e, view, &mut cx);
-                    cx.handled
-                } else {
-                    panic!(
-                        "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Down, found {:?}",
-                        region_event);
-                }
-            }));
-        self
-    }
-
-    pub fn on_up<V, F>(mut self, button: MouseButton, handler: F) -> Self
-    where
-        V: 'static,
-        F: Fn(MouseUp, &mut V, &mut EventContext<V>) + 'static,
-    {
-        self.insert(MouseEvent::up_disc(), Some(button),
-            Rc::new(move |region_event, view, cx, view_id| {
-                if let MouseEvent::Up(e) = region_event {
-                    let view = view.downcast_mut().unwrap();
-                    let mut cx = ViewContext::mutable(cx, view_id);
-                    let mut cx = EventContext::new(&mut cx);
-                    handler(e, view, &mut cx);
-                    cx.handled
-                } else {
-                    panic!(
-                        "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Up, found {:?}",
-                        region_event);
-                }
-            }));
-        self
-    }
-
-    pub fn on_click<V, F>(mut self, button: MouseButton, handler: F) -> Self
-    where
-        V: 'static,
-        F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
-    {
-        self.insert(MouseEvent::click_disc(), Some(button),
-            Rc::new(move |region_event, view, cx, view_id| {
-                if let MouseEvent::Click(e) = region_event {
-                    let view = view.downcast_mut().unwrap();
-                    let mut cx = ViewContext::mutable(cx, view_id);
-                    let mut cx = EventContext::new(&mut cx);
-                    handler(e, view, &mut cx);
-                    cx.handled
-                } else {
-                    panic!(
-                        "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Click, found {:?}",
-                        region_event);
-                }
-            }));
-        self
-    }
-
-    pub fn on_click_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
-    where
-        V: 'static,
-        F: Fn(MouseClickOut, &mut V, &mut EventContext<V>) + 'static,
-    {
-        self.insert(MouseEvent::click_out_disc(), Some(button),
-            Rc::new(move |region_event, view, cx, view_id| {
-                if let MouseEvent::ClickOut(e) = region_event {
-                    let view = view.downcast_mut().unwrap();
-                    let mut cx = ViewContext::mutable(cx, view_id);
-                    let mut cx = EventContext::new(&mut cx);
-                    handler(e, view, &mut cx);
-                    cx.handled
-                } else {
-                    panic!(
-                        "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::ClickOut, found {:?}",
-                        region_event);
-                }
-            }));
-        self
-    }
-
-    pub fn on_down_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
-    where
-        V: 'static,
-        F: Fn(MouseDownOut, &mut V, &mut EventContext<V>) + 'static,
-    {
-        self.insert(MouseEvent::down_out_disc(), Some(button),
-            Rc::new(move |region_event, view, cx, view_id| {
-                if let MouseEvent::DownOut(e) = region_event {
-                    let view = view.downcast_mut().unwrap();
-                    let mut cx = ViewContext::mutable(cx, view_id);
-                    let mut cx = EventContext::new(&mut cx);
-                    handler(e, view, &mut cx);
-                    cx.handled
-                } else {
-                    panic!(
-                        "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::DownOut, found {:?}",
-                        region_event);
-                }
-            }));
-        self
-    }
-
-    pub fn on_up_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
-    where
-        V: 'static,
-        F: Fn(MouseUpOut, &mut V, &mut EventContext<V>) + 'static,
-    {
-        self.insert(MouseEvent::up_out_disc(), Some(button),
-            Rc::new(move |region_event, view, cx, view_id| {
-                if let MouseEvent::UpOut(e) = region_event {
-                    let view = view.downcast_mut().unwrap();
-                    let mut cx = ViewContext::mutable(cx, view_id);
-                    let mut cx = EventContext::new(&mut cx);
-                    handler(e, view, &mut cx);
-                    cx.handled
-                } else {
-                    panic!(
-                        "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::UpOut, found {:?}",
-                        region_event);
-                }
-            }));
-        self
-    }
-
-    pub fn on_drag<V, F>(mut self, button: MouseButton, handler: F) -> Self
-    where
-        V: 'static,
-        F: Fn(MouseDrag, &mut V, &mut EventContext<V>) + 'static,
-    {
-        self.insert(MouseEvent::drag_disc(), Some(button),
-            Rc::new(move |region_event, view, cx, view_id| {
-                if let MouseEvent::Drag(e) = region_event {
-                    let view = view.downcast_mut().unwrap();
-                    let mut cx = ViewContext::mutable(cx, view_id);
-                    let mut cx = EventContext::new(&mut cx);
-                    handler(e, view, &mut cx);
-                    cx.handled
-                } else {
-                    panic!(
-                        "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Drag, found {:?}",
-                        region_event);
-                }
-            }));
-        self
-    }
-
-    pub fn on_hover<V, F>(mut self, handler: F) -> Self
-    where
-        V: 'static,
-        F: Fn(MouseHover, &mut V, &mut EventContext<V>) + 'static,
-    {
-        self.insert(MouseEvent::hover_disc(), None,
-            Rc::new(move |region_event, view, cx, view_id| {
-                if let MouseEvent::Hover(e) = region_event {
-                    let view = view.downcast_mut().unwrap();
-                    let mut cx = ViewContext::mutable(cx, view_id);
-                    let mut cx = EventContext::new(&mut cx);
-                    handler(e, view, &mut cx);
-                    cx.handled
-                } else {
-                    panic!(
-                        "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Hover, found {:?}",
-                        region_event);
-                }
-            }));
-        self
-    }
-
-    pub fn on_scroll<V, F>(mut self, handler: F) -> Self
-    where
-        V: 'static,
-        F: Fn(MouseScrollWheel, &mut V, &mut EventContext<V>) + 'static,
-    {
-        self.insert(MouseEvent::scroll_wheel_disc(), None,
-            Rc::new(move |region_event, view, cx, view_id| {
-                if let MouseEvent::ScrollWheel(e) = region_event {
-                    let view = view.downcast_mut().unwrap();
-                    let mut cx = ViewContext::mutable(cx, view_id);
-                    let mut cx = EventContext::new(&mut cx);
-                    handler(e, view, &mut cx);
-                    cx.handled
-                } else {
-                    panic!(
-                        "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::ScrollWheel, found {:?}",
-                        region_event
-                    );
-                }
-            }));
-        self
-    }
-}

crates/gpui/src/scene/region.rs 🔗

@@ -1,7 +0,0 @@
-// use crate::geometry::rect::RectF;
-// use crate::WindowContext;
-
-// struct Region {
-//     pub bounds: RectF,
-//     pub click_handler: Option<Rc<dyn Fn(&dyn Any, MouseEvent, &mut WindowContext)>>,
-// }

crates/gpui2/src/styled.rs → crates/gpui/src/styled.rs 🔗

@@ -10,7 +10,7 @@ use taffy::style::Overflow;
 pub trait Styled: Sized {
     fn style(&mut self) -> &mut StyleRefinement;
 
-    gpui2_macros::style_helpers!();
+    gpui_macros::style_helpers!();
 
     fn z_index(mut self, z_index: u8) -> Self {
         self.style().z_index = Some(z_index);

crates/gpui/src/test.rs 🔗

@@ -1,186 +1,51 @@
+use crate::{Entity, Subscription, TestAppContext, TestDispatcher};
+use futures::StreamExt as _;
+use rand::prelude::*;
+use smol::channel;
 use std::{
-    fmt::Write,
+    env,
     panic::{self, RefUnwindSafe},
-    rc::Rc,
-    sync::{
-        atomic::{AtomicU64, Ordering::SeqCst},
-        Arc,
-    },
 };
 
-use futures::StreamExt;
-use parking_lot::Mutex;
-use smol::channel;
-
-use crate::{
-    app::ref_counts::LeakDetector,
-    elements::Empty,
-    executor::{self, ExecutorEvent},
-    platform,
-    platform::Platform,
-    util::CwdBacktrace,
-    AnyElement, AppContext, Element, Entity, FontCache, Handle, Subscription, TestAppContext, View,
-    ViewContext,
-};
-
-#[cfg(test)]
-#[ctor::ctor]
-fn init_logger() {
-    if std::env::var("RUST_LOG").is_ok() {
-        env_logger::init();
-    }
-}
-
-// #[global_allocator]
-// static ALLOC: dhat::Alloc = dhat::Alloc;
-
 pub fn run_test(
     mut num_iterations: u64,
-    mut starting_seed: u64,
     max_retries: usize,
-    detect_nondeterminism: bool,
-    test_fn: &mut (dyn RefUnwindSafe
-              + Fn(
-        &mut AppContext,
-        Rc<platform::test::ForegroundPlatform>,
-        Arc<executor::Deterministic>,
-        u64,
-    )),
+    test_fn: &mut (dyn RefUnwindSafe + Fn(TestDispatcher, u64)),
     on_fail_fn: Option<fn()>,
-    fn_name: String,
+    _fn_name: String, // todo!("re-enable fn_name")
 ) {
-    // let _profiler = dhat::Profiler::new_heap();
-
+    let starting_seed = env::var("SEED")
+        .map(|seed| seed.parse().expect("invalid SEED variable"))
+        .unwrap_or(0);
     let is_randomized = num_iterations > 1;
-    if is_randomized {
-        if let Ok(value) = std::env::var("SEED") {
-            starting_seed = value.parse().expect("invalid SEED variable");
-        }
-        if let Ok(value) = std::env::var("ITERATIONS") {
-            num_iterations = value.parse().expect("invalid ITERATIONS variable");
-        }
+    if let Ok(iterations) = env::var("ITERATIONS") {
+        num_iterations = iterations.parse().expect("invalid ITERATIONS variable");
     }
 
-    let atomic_seed = AtomicU64::new(starting_seed as u64);
-    let mut retries = 0;
-
-    loop {
-        let result = panic::catch_unwind(|| {
-            let foreground_platform = Rc::new(platform::test::foreground_platform());
-            let platform = Arc::new(platform::test::platform());
-            let font_system = platform.fonts();
-            let font_cache = Arc::new(FontCache::new(font_system));
-            let mut prev_runnable_history: Option<Vec<ExecutorEvent>> = None;
-
-            for _ in 0..num_iterations {
-                let seed = atomic_seed.load(SeqCst);
-
-                if is_randomized {
-                    eprintln!("seed = {seed}");
-                }
-
-                let deterministic = executor::Deterministic::new(seed);
-                if detect_nondeterminism {
-                    deterministic.set_previous_execution_history(prev_runnable_history.clone());
-                    deterministic.enable_runnable_backtrace();
-                }
-
-                let leak_detector = Arc::new(Mutex::new(LeakDetector::default()));
-                let mut cx = TestAppContext::new(
-                    foreground_platform.clone(),
-                    platform.clone(),
-                    deterministic.build_foreground(usize::MAX),
-                    deterministic.build_background(),
-                    font_cache.clone(),
-                    leak_detector.clone(),
-                    0,
-                    fn_name.clone(),
-                );
-                cx.update(|cx| {
-                    test_fn(cx, foreground_platform.clone(), deterministic.clone(), seed);
-                });
-
-                cx.remove_all_windows();
-                deterministic.run_until_parked();
-                cx.update(|cx| cx.clear_globals());
-
-                leak_detector.lock().detect();
-
-                if detect_nondeterminism {
-                    let curr_runnable_history = deterministic.execution_history();
-                    if let Some(prev_runnable_history) = prev_runnable_history {
-                        let mut prev_entries = prev_runnable_history.iter().fuse();
-                        let mut curr_entries = curr_runnable_history.iter().fuse();
-
-                        let mut nondeterministic = false;
-                        let mut common_history_prefix = Vec::new();
-                        let mut prev_history_suffix = Vec::new();
-                        let mut curr_history_suffix = Vec::new();
-                        loop {
-                            match (prev_entries.next(), curr_entries.next()) {
-                                (None, None) => break,
-                                (None, Some(curr_id)) => curr_history_suffix.push(*curr_id),
-                                (Some(prev_id), None) => prev_history_suffix.push(*prev_id),
-                                (Some(prev_id), Some(curr_id)) => {
-                                    if nondeterministic {
-                                        prev_history_suffix.push(*prev_id);
-                                        curr_history_suffix.push(*curr_id);
-                                    } else if prev_id == curr_id {
-                                        common_history_prefix.push(*curr_id);
-                                    } else {
-                                        nondeterministic = true;
-                                        prev_history_suffix.push(*prev_id);
-                                        curr_history_suffix.push(*curr_id);
-                                    }
-                                }
-                            }
-                        }
-
-                        if nondeterministic {
-                            let mut error = String::new();
-                            writeln!(&mut error, "Common prefix: {:?}", common_history_prefix)
-                                .unwrap();
-                            writeln!(&mut error, "Previous suffix: {:?}", prev_history_suffix)
-                                .unwrap();
-                            writeln!(&mut error, "Current suffix: {:?}", curr_history_suffix)
-                                .unwrap();
-
-                            let last_common_backtrace = common_history_prefix
-                                .last()
-                                .map(|event| deterministic.runnable_backtrace(event.id()));
-
-                            writeln!(
-                                &mut error,
-                                "Last future that ran on both executions: {:?}",
-                                last_common_backtrace.as_ref().map(CwdBacktrace)
-                            )
-                            .unwrap();
-                            panic!("Detected non-determinism.\n{}", error);
-                        }
-                    }
-                    prev_runnable_history = Some(curr_runnable_history);
-                }
-
-                if !detect_nondeterminism {
-                    atomic_seed.fetch_add(1, SeqCst);
-                }
+    for seed in starting_seed..starting_seed + num_iterations {
+        let mut retry = 0;
+        loop {
+            if is_randomized {
+                eprintln!("seed = {seed}");
             }
-        });
-
-        match result {
-            Ok(_) => {
-                break;
-            }
-            Err(error) => {
-                if retries < max_retries {
-                    retries += 1;
-                    println!("retrying: attempt {}", retries);
-                } else {
-                    if is_randomized {
-                        eprintln!("failing seed: {}", atomic_seed.load(SeqCst));
+            let result = panic::catch_unwind(|| {
+                let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(seed));
+                test_fn(dispatcher, seed);
+            });
+
+            match result {
+                Ok(_) => break,
+                Err(error) => {
+                    if retry < max_retries {
+                        println!("retrying: attempt {}", retry);
+                        retry += 1;
+                    } else {
+                        if is_randomized {
+                            eprintln!("failing seed: {}", seed);
+                        }
+                        on_fail_fn.map(|f| f());
+                        panic::resume_unwind(error);
                     }
-                    on_fail_fn.map(|f| f());
-                    panic::resume_unwind(error);
                 }
             }
         }
@@ -192,7 +57,7 @@ pub struct Observation<T> {
     _subscription: Subscription,
 }
 
-impl<T> futures::Stream for Observation<T> {
+impl<T: 'static> futures::Stream for Observation<T> {
     type Item = T;
 
     fn poll_next(
@@ -203,7 +68,7 @@ impl<T> futures::Stream for Observation<T> {
     }
 }
 
-pub fn observe<T: Entity>(entity: &impl Handle<T>, cx: &mut TestAppContext) -> Observation<()> {
+pub fn observe<T: 'static>(entity: &impl Entity<T>, cx: &mut TestAppContext) -> Observation<()> {
     let (tx, rx) = smol::channel::unbounded();
     let _subscription = cx.update(|cx| {
         cx.observe(entity, move |_, _| {
@@ -213,36 +78,3 @@ pub fn observe<T: Entity>(entity: &impl Handle<T>, cx: &mut TestAppContext) -> O
 
     Observation { rx, _subscription }
 }
-
-pub fn subscribe<T: Entity>(
-    entity: &impl Handle<T>,
-    cx: &mut TestAppContext,
-) -> Observation<T::Event>
-where
-    T::Event: Clone,
-{
-    let (tx, rx) = smol::channel::unbounded();
-    let _subscription = cx.update(|cx| {
-        cx.subscribe(entity, move |_, event, _| {
-            let _ = smol::block_on(tx.send(event.clone()));
-        })
-    });
-
-    Observation { rx, _subscription }
-}
-
-pub struct EmptyView;
-
-impl Entity for EmptyView {
-    type Event = ();
-}
-
-impl View for EmptyView {
-    fn ui_name() -> &'static str {
-        "empty view"
-    }
-
-    fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
-        Empty::new().into_any()
-    }
-}

crates/gpui/src/text_layout.rs 🔗

@@ -1,876 +0,0 @@
-use crate::{
-    color::Color,
-    fonts::{FontId, GlyphId, Underline},
-    geometry::{
-        rect::RectF,
-        vector::{vec2f, Vector2F},
-    },
-    platform,
-    platform::FontSystem,
-    scene,
-    window::WindowContext,
-};
-use ordered_float::OrderedFloat;
-use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
-use smallvec::SmallVec;
-use std::{
-    borrow::Borrow,
-    collections::HashMap,
-    hash::{Hash, Hasher},
-    iter,
-    sync::Arc,
-};
-
-pub struct TextLayoutCache {
-    prev_frame: Mutex<HashMap<OwnedCacheKey, Arc<LineLayout>>>,
-    curr_frame: RwLock<HashMap<OwnedCacheKey, Arc<LineLayout>>>,
-    fonts: Arc<dyn platform::FontSystem>,
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub struct RunStyle {
-    pub color: Color,
-    pub font_id: FontId,
-    pub underline: Underline,
-}
-
-impl TextLayoutCache {
-    pub fn new(fonts: Arc<dyn platform::FontSystem>) -> Self {
-        Self {
-            prev_frame: Mutex::new(HashMap::new()),
-            curr_frame: RwLock::new(HashMap::new()),
-            fonts,
-        }
-    }
-
-    pub fn finish_frame(&self) {
-        let mut prev_frame = self.prev_frame.lock();
-        let mut curr_frame = self.curr_frame.write();
-        std::mem::swap(&mut *prev_frame, &mut *curr_frame);
-        curr_frame.clear();
-    }
-
-    pub fn layout_str<'a>(
-        &'a self,
-        text: &'a str,
-        font_size: f32,
-        runs: &'a [(usize, RunStyle)],
-    ) -> Line {
-        let key = &BorrowedCacheKey {
-            text,
-            font_size: OrderedFloat(font_size),
-            runs,
-        } as &dyn CacheKey;
-        let curr_frame = self.curr_frame.upgradable_read();
-        if let Some(layout) = curr_frame.get(key) {
-            return Line::new(layout.clone(), runs);
-        }
-
-        let mut curr_frame = RwLockUpgradableReadGuard::upgrade(curr_frame);
-        if let Some((key, layout)) = self.prev_frame.lock().remove_entry(key) {
-            curr_frame.insert(key, layout.clone());
-            Line::new(layout, runs)
-        } else {
-            let layout = Arc::new(self.fonts.layout_line(text, font_size, runs));
-            let key = OwnedCacheKey {
-                text: text.into(),
-                font_size: OrderedFloat(font_size),
-                runs: SmallVec::from(runs),
-            };
-            curr_frame.insert(key, layout.clone());
-            Line::new(layout, runs)
-        }
-    }
-}
-
-trait CacheKey {
-    fn key(&self) -> BorrowedCacheKey;
-}
-
-impl<'a> PartialEq for (dyn CacheKey + 'a) {
-    fn eq(&self, other: &dyn CacheKey) -> bool {
-        self.key() == other.key()
-    }
-}
-
-impl<'a> Eq for (dyn CacheKey + 'a) {}
-
-impl<'a> Hash for (dyn CacheKey + 'a) {
-    fn hash<H: Hasher>(&self, state: &mut H) {
-        self.key().hash(state)
-    }
-}
-
-#[derive(Eq)]
-struct OwnedCacheKey {
-    text: String,
-    font_size: OrderedFloat<f32>,
-    runs: SmallVec<[(usize, RunStyle); 1]>,
-}
-
-impl CacheKey for OwnedCacheKey {
-    fn key(&self) -> BorrowedCacheKey {
-        BorrowedCacheKey {
-            text: self.text.as_str(),
-            font_size: self.font_size,
-            runs: self.runs.as_slice(),
-        }
-    }
-}
-
-impl PartialEq for OwnedCacheKey {
-    fn eq(&self, other: &Self) -> bool {
-        self.key().eq(&other.key())
-    }
-}
-
-impl Hash for OwnedCacheKey {
-    fn hash<H: Hasher>(&self, state: &mut H) {
-        self.key().hash(state);
-    }
-}
-
-impl<'a> Borrow<dyn CacheKey + 'a> for OwnedCacheKey {
-    fn borrow(&self) -> &(dyn CacheKey + 'a) {
-        self as &dyn CacheKey
-    }
-}
-
-#[derive(Copy, Clone)]
-struct BorrowedCacheKey<'a> {
-    text: &'a str,
-    font_size: OrderedFloat<f32>,
-    runs: &'a [(usize, RunStyle)],
-}
-
-impl<'a> CacheKey for BorrowedCacheKey<'a> {
-    fn key(&self) -> BorrowedCacheKey {
-        *self
-    }
-}
-
-impl<'a> PartialEq for BorrowedCacheKey<'a> {
-    fn eq(&self, other: &Self) -> bool {
-        self.text == other.text
-            && self.font_size == other.font_size
-            && self.runs.len() == other.runs.len()
-            && self.runs.iter().zip(other.runs.iter()).all(
-                |((len_a, style_a), (len_b, style_b))| {
-                    len_a == len_b && style_a.font_id == style_b.font_id
-                },
-            )
-    }
-}
-
-impl<'a> Hash for BorrowedCacheKey<'a> {
-    fn hash<H: Hasher>(&self, state: &mut H) {
-        self.text.hash(state);
-        self.font_size.hash(state);
-        for (len, style_id) in self.runs {
-            len.hash(state);
-            style_id.font_id.hash(state);
-        }
-    }
-}
-
-#[derive(Default, Debug, Clone)]
-pub struct Line {
-    layout: Arc<LineLayout>,
-    style_runs: SmallVec<[StyleRun; 32]>,
-}
-
-#[derive(Debug, Clone, Copy)]
-struct StyleRun {
-    len: u32,
-    color: Color,
-    underline: Underline,
-}
-
-#[derive(Default, Debug)]
-pub struct LineLayout {
-    pub width: f32,
-    pub ascent: f32,
-    pub descent: f32,
-    pub runs: Vec<Run>,
-    pub len: usize,
-    pub font_size: f32,
-}
-
-#[derive(Debug)]
-pub struct Run {
-    pub font_id: FontId,
-    pub glyphs: Vec<Glyph>,
-}
-
-#[derive(Clone, Debug)]
-pub struct Glyph {
-    pub id: GlyphId,
-    pub position: Vector2F,
-    pub index: usize,
-    pub is_emoji: bool,
-}
-
-impl Line {
-    pub fn new(layout: Arc<LineLayout>, runs: &[(usize, RunStyle)]) -> Self {
-        let mut style_runs = SmallVec::new();
-        for (len, style) in runs {
-            style_runs.push(StyleRun {
-                len: *len as u32,
-                color: style.color,
-                underline: style.underline,
-            });
-        }
-        Self { layout, style_runs }
-    }
-
-    pub fn runs(&self) -> &[Run] {
-        &self.layout.runs
-    }
-
-    pub fn width(&self) -> f32 {
-        self.layout.width
-    }
-
-    pub fn font_size(&self) -> f32 {
-        self.layout.font_size
-    }
-
-    pub fn x_for_index(&self, index: usize) -> f32 {
-        for run in &self.layout.runs {
-            for glyph in &run.glyphs {
-                if glyph.index >= index {
-                    return glyph.position.x();
-                }
-            }
-        }
-        self.layout.width
-    }
-
-    pub fn font_for_index(&self, index: usize) -> Option<FontId> {
-        for run in &self.layout.runs {
-            for glyph in &run.glyphs {
-                if glyph.index >= index {
-                    return Some(run.font_id);
-                }
-            }
-        }
-
-        None
-    }
-
-    pub fn len(&self) -> usize {
-        self.layout.len
-    }
-
-    pub fn is_empty(&self) -> bool {
-        self.layout.len == 0
-    }
-
-    /// index_for_x returns the character containing the given x coordinate.
-    /// (e.g. to handle a mouse-click)
-    pub fn index_for_x(&self, x: f32) -> Option<usize> {
-        if x >= self.layout.width {
-            None
-        } else {
-            for run in self.layout.runs.iter().rev() {
-                for glyph in run.glyphs.iter().rev() {
-                    if glyph.position.x() <= x {
-                        return Some(glyph.index);
-                    }
-                }
-            }
-            Some(0)
-        }
-    }
-
-    /// closest_index_for_x returns the character boundary closest to the given x coordinate
-    /// (e.g. to handle aligning up/down arrow keys)
-    pub fn closest_index_for_x(&self, x: f32) -> usize {
-        let mut prev_index = 0;
-        let mut prev_x = 0.0;
-
-        for run in self.layout.runs.iter() {
-            for glyph in run.glyphs.iter() {
-                if glyph.position.x() >= x {
-                    if glyph.position.x() - x < x - prev_x {
-                        return glyph.index;
-                    } else {
-                        return prev_index;
-                    }
-                }
-                prev_index = glyph.index;
-                prev_x = glyph.position.x();
-            }
-        }
-        prev_index
-    }
-
-    pub fn paint(
-        &self,
-        origin: Vector2F,
-        visible_bounds: RectF,
-        line_height: f32,
-        cx: &mut WindowContext,
-    ) {
-        let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
-        let baseline_offset = vec2f(0., padding_top + self.layout.ascent);
-
-        let mut style_runs = self.style_runs.iter();
-        let mut run_end = 0;
-        let mut color = Color::black();
-        let mut underline = None;
-
-        for run in &self.layout.runs {
-            let max_glyph_width = cx
-                .font_cache
-                .bounding_box(run.font_id, self.layout.font_size)
-                .x();
-
-            for glyph in &run.glyphs {
-                let glyph_origin = origin + baseline_offset + glyph.position;
-                if glyph_origin.x() > visible_bounds.upper_right().x() {
-                    break;
-                }
-
-                let mut finished_underline = None;
-                if glyph.index >= run_end {
-                    if let Some(style_run) = style_runs.next() {
-                        if let Some((_, underline_style)) = underline {
-                            if style_run.underline != underline_style {
-                                finished_underline = underline.take();
-                            }
-                        }
-                        if style_run.underline.thickness.into_inner() > 0. {
-                            underline.get_or_insert((
-                                vec2f(
-                                    glyph_origin.x(),
-                                    origin.y() + baseline_offset.y() + 0.618 * self.layout.descent,
-                                ),
-                                Underline {
-                                    color: Some(
-                                        style_run.underline.color.unwrap_or(style_run.color),
-                                    ),
-                                    thickness: style_run.underline.thickness,
-                                    squiggly: style_run.underline.squiggly,
-                                },
-                            ));
-                        }
-
-                        run_end += style_run.len as usize;
-                        color = style_run.color;
-                    } else {
-                        run_end = self.layout.len;
-                        finished_underline = underline.take();
-                    }
-                }
-
-                if glyph_origin.x() + max_glyph_width < visible_bounds.origin().x() {
-                    continue;
-                }
-
-                if let Some((underline_origin, underline_style)) = finished_underline {
-                    cx.scene().push_underline(scene::Underline {
-                        origin: underline_origin,
-                        width: glyph_origin.x() - underline_origin.x(),
-                        thickness: underline_style.thickness.into(),
-                        color: underline_style.color.unwrap(),
-                        squiggly: underline_style.squiggly,
-                    });
-                }
-
-                if glyph.is_emoji {
-                    cx.scene().push_image_glyph(scene::ImageGlyph {
-                        font_id: run.font_id,
-                        font_size: self.layout.font_size,
-                        id: glyph.id,
-                        origin: glyph_origin,
-                    });
-                } else {
-                    cx.scene().push_glyph(scene::Glyph {
-                        font_id: run.font_id,
-                        font_size: self.layout.font_size,
-                        id: glyph.id,
-                        origin: glyph_origin,
-                        color,
-                    });
-                }
-            }
-        }
-
-        if let Some((underline_start, underline_style)) = underline.take() {
-            let line_end_x = origin.x() + self.layout.width;
-            cx.scene().push_underline(scene::Underline {
-                origin: underline_start,
-                width: line_end_x - underline_start.x(),
-                color: underline_style.color.unwrap(),
-                thickness: underline_style.thickness.into(),
-                squiggly: underline_style.squiggly,
-            });
-        }
-    }
-
-    pub fn paint_wrapped(
-        &self,
-        origin: Vector2F,
-        visible_bounds: RectF,
-        line_height: f32,
-        boundaries: &[ShapedBoundary],
-        cx: &mut WindowContext,
-    ) {
-        let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
-        let baseline_offset = vec2f(0., padding_top + self.layout.ascent);
-
-        let mut boundaries = boundaries.into_iter().peekable();
-        let mut color_runs = self.style_runs.iter();
-        let mut style_run_end = 0;
-        let mut color = Color::black();
-        let mut underline: Option<(Vector2F, Underline)> = None;
-
-        let mut glyph_origin = origin;
-        let mut prev_position = 0.;
-        for (run_ix, run) in self.layout.runs.iter().enumerate() {
-            for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
-                glyph_origin.set_x(glyph_origin.x() + glyph.position.x() - prev_position);
-
-                if boundaries
-                    .peek()
-                    .map_or(false, |b| b.run_ix == run_ix && b.glyph_ix == glyph_ix)
-                {
-                    boundaries.next();
-                    if let Some((underline_origin, underline_style)) = underline {
-                        cx.scene().push_underline(scene::Underline {
-                            origin: underline_origin,
-                            width: glyph_origin.x() - underline_origin.x(),
-                            thickness: underline_style.thickness.into(),
-                            color: underline_style.color.unwrap(),
-                            squiggly: underline_style.squiggly,
-                        });
-                    }
-
-                    glyph_origin = vec2f(origin.x(), glyph_origin.y() + line_height);
-                }
-                prev_position = glyph.position.x();
-
-                let mut finished_underline = None;
-                if glyph.index >= style_run_end {
-                    if let Some(style_run) = color_runs.next() {
-                        style_run_end += style_run.len as usize;
-                        color = style_run.color;
-                        if let Some((_, underline_style)) = underline {
-                            if style_run.underline != underline_style {
-                                finished_underline = underline.take();
-                            }
-                        }
-                        if style_run.underline.thickness.into_inner() > 0. {
-                            underline.get_or_insert((
-                                glyph_origin
-                                    + vec2f(0., baseline_offset.y() + 0.618 * self.layout.descent),
-                                Underline {
-                                    color: Some(
-                                        style_run.underline.color.unwrap_or(style_run.color),
-                                    ),
-                                    thickness: style_run.underline.thickness,
-                                    squiggly: style_run.underline.squiggly,
-                                },
-                            ));
-                        }
-                    } else {
-                        style_run_end = self.layout.len;
-                        color = Color::black();
-                        finished_underline = underline.take();
-                    }
-                }
-
-                if let Some((underline_origin, underline_style)) = finished_underline {
-                    cx.scene().push_underline(scene::Underline {
-                        origin: underline_origin,
-                        width: glyph_origin.x() - underline_origin.x(),
-                        thickness: underline_style.thickness.into(),
-                        color: underline_style.color.unwrap(),
-                        squiggly: underline_style.squiggly,
-                    });
-                }
-
-                let glyph_bounds = RectF::new(
-                    glyph_origin,
-                    cx.font_cache
-                        .bounding_box(run.font_id, self.layout.font_size),
-                );
-                if glyph_bounds.intersects(visible_bounds) {
-                    if glyph.is_emoji {
-                        cx.scene().push_image_glyph(scene::ImageGlyph {
-                            font_id: run.font_id,
-                            font_size: self.layout.font_size,
-                            id: glyph.id,
-                            origin: glyph_bounds.origin() + baseline_offset,
-                        });
-                    } else {
-                        cx.scene().push_glyph(scene::Glyph {
-                            font_id: run.font_id,
-                            font_size: self.layout.font_size,
-                            id: glyph.id,
-                            origin: glyph_bounds.origin() + baseline_offset,
-                            color,
-                        });
-                    }
-                }
-            }
-        }
-
-        if let Some((underline_origin, underline_style)) = underline.take() {
-            let line_end_x = glyph_origin.x() + self.layout.width - prev_position;
-            cx.scene().push_underline(scene::Underline {
-                origin: underline_origin,
-                width: line_end_x - underline_origin.x(),
-                thickness: underline_style.thickness.into(),
-                color: underline_style.color.unwrap(),
-                squiggly: underline_style.squiggly,
-            });
-        }
-    }
-}
-
-impl Run {
-    pub fn glyphs(&self) -> &[Glyph] {
-        &self.glyphs
-    }
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub struct Boundary {
-    pub ix: usize,
-    pub next_indent: u32,
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
-pub struct ShapedBoundary {
-    pub run_ix: usize,
-    pub glyph_ix: usize,
-}
-
-impl Boundary {
-    fn new(ix: usize, next_indent: u32) -> Self {
-        Self { ix, next_indent }
-    }
-}
-
-pub struct LineWrapper {
-    font_system: Arc<dyn FontSystem>,
-    pub(crate) font_id: FontId,
-    pub(crate) font_size: f32,
-    cached_ascii_char_widths: [f32; 128],
-    cached_other_char_widths: HashMap<char, f32>,
-}
-
-impl LineWrapper {
-    pub const MAX_INDENT: u32 = 256;
-
-    pub fn new(font_id: FontId, font_size: f32, font_system: Arc<dyn FontSystem>) -> Self {
-        Self {
-            font_system,
-            font_id,
-            font_size,
-            cached_ascii_char_widths: [f32::NAN; 128],
-            cached_other_char_widths: HashMap::new(),
-        }
-    }
-
-    pub fn wrap_line<'a>(
-        &'a mut self,
-        line: &'a str,
-        wrap_width: f32,
-    ) -> impl Iterator<Item = Boundary> + 'a {
-        let mut width = 0.0;
-        let mut first_non_whitespace_ix = None;
-        let mut indent = None;
-        let mut last_candidate_ix = 0;
-        let mut last_candidate_width = 0.0;
-        let mut last_wrap_ix = 0;
-        let mut prev_c = '\0';
-        let mut char_indices = line.char_indices();
-        iter::from_fn(move || {
-            for (ix, c) in char_indices.by_ref() {
-                if c == '\n' {
-                    continue;
-                }
-
-                if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
-                    last_candidate_ix = ix;
-                    last_candidate_width = width;
-                }
-
-                if c != ' ' && first_non_whitespace_ix.is_none() {
-                    first_non_whitespace_ix = Some(ix);
-                }
-
-                let char_width = self.width_for_char(c);
-                width += char_width;
-                if width > wrap_width && ix > last_wrap_ix {
-                    if let (None, Some(first_non_whitespace_ix)) = (indent, first_non_whitespace_ix)
-                    {
-                        indent = Some(
-                            Self::MAX_INDENT.min((first_non_whitespace_ix - last_wrap_ix) as u32),
-                        );
-                    }
-
-                    if last_candidate_ix > 0 {
-                        last_wrap_ix = last_candidate_ix;
-                        width -= last_candidate_width;
-                        last_candidate_ix = 0;
-                    } else {
-                        last_wrap_ix = ix;
-                        width = char_width;
-                    }
-
-                    let indent_width =
-                        indent.map(|indent| indent as f32 * self.width_for_char(' '));
-                    width += indent_width.unwrap_or(0.);
-
-                    return Some(Boundary::new(last_wrap_ix, indent.unwrap_or(0)));
-                }
-                prev_c = c;
-            }
-
-            None
-        })
-    }
-
-    pub fn wrap_shaped_line<'a>(
-        &'a mut self,
-        str: &'a str,
-        line: &'a Line,
-        wrap_width: f32,
-    ) -> impl Iterator<Item = ShapedBoundary> + 'a {
-        let mut first_non_whitespace_ix = None;
-        let mut last_candidate_ix = None;
-        let mut last_candidate_x = 0.0;
-        let mut last_wrap_ix = ShapedBoundary {
-            run_ix: 0,
-            glyph_ix: 0,
-        };
-        let mut last_wrap_x = 0.;
-        let mut prev_c = '\0';
-        let mut glyphs = line
-            .runs()
-            .iter()
-            .enumerate()
-            .flat_map(move |(run_ix, run)| {
-                run.glyphs()
-                    .iter()
-                    .enumerate()
-                    .map(move |(glyph_ix, glyph)| {
-                        let character = str[glyph.index..].chars().next().unwrap();
-                        (
-                            ShapedBoundary { run_ix, glyph_ix },
-                            character,
-                            glyph.position.x(),
-                        )
-                    })
-            })
-            .peekable();
-
-        iter::from_fn(move || {
-            while let Some((ix, c, x)) = glyphs.next() {
-                if c == '\n' {
-                    continue;
-                }
-
-                if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
-                    last_candidate_ix = Some(ix);
-                    last_candidate_x = x;
-                }
-
-                if c != ' ' && first_non_whitespace_ix.is_none() {
-                    first_non_whitespace_ix = Some(ix);
-                }
-
-                let next_x = glyphs.peek().map_or(line.width(), |(_, _, x)| *x);
-                let width = next_x - last_wrap_x;
-                if width > wrap_width && ix > last_wrap_ix {
-                    if let Some(last_candidate_ix) = last_candidate_ix.take() {
-                        last_wrap_ix = last_candidate_ix;
-                        last_wrap_x = last_candidate_x;
-                    } else {
-                        last_wrap_ix = ix;
-                        last_wrap_x = x;
-                    }
-
-                    return Some(last_wrap_ix);
-                }
-                prev_c = c;
-            }
-
-            None
-        })
-    }
-
-    fn is_boundary(&self, prev: char, next: char) -> bool {
-        (prev == ' ') && (next != ' ')
-    }
-
-    #[inline(always)]
-    fn width_for_char(&mut self, c: char) -> f32 {
-        if (c as u32) < 128 {
-            let mut width = self.cached_ascii_char_widths[c as usize];
-            if width.is_nan() {
-                width = self.compute_width_for_char(c);
-                self.cached_ascii_char_widths[c as usize] = width;
-            }
-            width
-        } else {
-            let mut width = self
-                .cached_other_char_widths
-                .get(&c)
-                .copied()
-                .unwrap_or(f32::NAN);
-            if width.is_nan() {
-                width = self.compute_width_for_char(c);
-                self.cached_other_char_widths.insert(c, width);
-            }
-            width
-        }
-    }
-
-    fn compute_width_for_char(&self, c: char) -> f32 {
-        self.font_system
-            .layout_line(
-                &c.to_string(),
-                self.font_size,
-                &[(
-                    1,
-                    RunStyle {
-                        font_id: self.font_id,
-                        color: Default::default(),
-                        underline: Default::default(),
-                    },
-                )],
-            )
-            .width
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::fonts::{Properties, Weight};
-
-    #[crate::test(self)]
-    fn test_wrap_line(cx: &mut crate::AppContext) {
-        let font_cache = cx.font_cache().clone();
-        let font_system = cx.platform().fonts();
-        let family = font_cache
-            .load_family(&["Courier"], &Default::default())
-            .unwrap();
-        let font_id = font_cache.select_font(family, &Default::default()).unwrap();
-
-        let mut wrapper = LineWrapper::new(font_id, 16., font_system);
-        assert_eq!(
-            wrapper
-                .wrap_line("aa bbb cccc ddddd eeee", 72.0)
-                .collect::<Vec<_>>(),
-            &[
-                Boundary::new(7, 0),
-                Boundary::new(12, 0),
-                Boundary::new(18, 0)
-            ],
-        );
-        assert_eq!(
-            wrapper
-                .wrap_line("aaa aaaaaaaaaaaaaaaaaa", 72.0)
-                .collect::<Vec<_>>(),
-            &[
-                Boundary::new(4, 0),
-                Boundary::new(11, 0),
-                Boundary::new(18, 0)
-            ],
-        );
-        assert_eq!(
-            wrapper.wrap_line("     aaaaaaa", 72.).collect::<Vec<_>>(),
-            &[
-                Boundary::new(7, 5),
-                Boundary::new(9, 5),
-                Boundary::new(11, 5),
-            ]
-        );
-        assert_eq!(
-            wrapper
-                .wrap_line("                            ", 72.)
-                .collect::<Vec<_>>(),
-            &[
-                Boundary::new(7, 0),
-                Boundary::new(14, 0),
-                Boundary::new(21, 0)
-            ]
-        );
-        assert_eq!(
-            wrapper
-                .wrap_line("          aaaaaaaaaaaaaa", 72.)
-                .collect::<Vec<_>>(),
-            &[
-                Boundary::new(7, 0),
-                Boundary::new(14, 3),
-                Boundary::new(18, 3),
-                Boundary::new(22, 3),
-            ]
-        );
-    }
-
-    #[crate::test(self, retries = 5)]
-    fn test_wrap_shaped_line(cx: &mut crate::AppContext) {
-        // This is failing intermittently on CI and we don't have time to figure it out
-        let font_cache = cx.font_cache().clone();
-        let font_system = cx.platform().fonts();
-        let text_layout_cache = TextLayoutCache::new(font_system.clone());
-
-        let family = font_cache
-            .load_family(&["Helvetica"], &Default::default())
-            .unwrap();
-        let font_id = font_cache.select_font(family, &Default::default()).unwrap();
-        let normal = RunStyle {
-            font_id,
-            color: Default::default(),
-            underline: Default::default(),
-        };
-        let bold = RunStyle {
-            font_id: font_cache
-                .select_font(
-                    family,
-                    &Properties {
-                        weight: Weight::BOLD,
-                        ..Default::default()
-                    },
-                )
-                .unwrap(),
-            color: Default::default(),
-            underline: Default::default(),
-        };
-
-        let text = "aa bbb cccc ddddd eeee";
-        let line = text_layout_cache.layout_str(
-            text,
-            16.0,
-            &[(4, normal), (5, bold), (6, normal), (1, bold), (7, normal)],
-        );
-
-        let mut wrapper = LineWrapper::new(font_id, 16., font_system);
-        assert_eq!(
-            wrapper
-                .wrap_shaped_line(text, &line, 72.0)
-                .collect::<Vec<_>>(),
-            &[
-                ShapedBoundary {
-                    run_ix: 1,
-                    glyph_ix: 3
-                },
-                ShapedBoundary {
-                    run_ix: 2,
-                    glyph_ix: 3
-                },
-                ShapedBoundary {
-                    run_ix: 4,
-                    glyph_ix: 2
-                }
-            ],
-        );
-    }
-}

crates/gpui/src/util.rs 🔗

@@ -1,12 +1,15 @@
+#[cfg(any(test, feature = "test-support"))]
+use std::time::Duration;
+
+#[cfg(any(test, feature = "test-support"))]
+use futures::Future;
+
+#[cfg(any(test, feature = "test-support"))]
 use smol::future::FutureExt;
-use std::{future::Future, time::Duration};
 
-pub fn post_inc(value: &mut usize) -> usize {
-    let prev = *value;
-    *value += 1;
-    prev
-}
+pub use util::*;
 
+#[cfg(any(test, feature = "test-support"))]
 pub async fn timeout<F, T>(timeout: Duration, f: F) -> Result<T, ()>
 where
     F: Future<Output = T>,

crates/gpui/src/views.rs 🔗

@@ -1,5 +0,0 @@
-mod select;
-
-pub use select::{ItemType, Select, SelectStyle};
-
-pub fn init(_: &mut super::AppContext) {}

crates/gpui/src/views/select.rs 🔗

@@ -1,156 +0,0 @@
-use crate::{
-    elements::*,
-    platform::{CursorStyle, MouseButton},
-    AppContext, Entity, View, ViewContext, WeakViewHandle,
-};
-
-pub struct Select {
-    handle: WeakViewHandle<Self>,
-    render_item: Box<dyn Fn(usize, ItemType, bool, &mut ViewContext<Select>) -> AnyElement<Self>>,
-    selected_item_ix: usize,
-    item_count: usize,
-    is_open: bool,
-    list_state: UniformListState,
-    build_style: Option<Box<dyn FnMut(&mut AppContext) -> SelectStyle>>,
-}
-
-#[derive(Clone, Default)]
-pub struct SelectStyle {
-    pub header: ContainerStyle,
-    pub menu: ContainerStyle,
-}
-
-pub enum ItemType {
-    Header,
-    Selected,
-    Unselected,
-}
-
-pub enum Event {}
-
-impl Select {
-    pub fn new<
-        F: 'static + Fn(usize, ItemType, bool, &mut ViewContext<Self>) -> AnyElement<Self>,
-    >(
-        item_count: usize,
-        cx: &mut ViewContext<Self>,
-        render_item: F,
-    ) -> Self {
-        Self {
-            handle: cx.weak_handle(),
-            render_item: Box::new(render_item),
-            selected_item_ix: 0,
-            item_count,
-            is_open: false,
-            list_state: UniformListState::default(),
-            build_style: Default::default(),
-        }
-    }
-
-    pub fn with_style(mut self, f: impl 'static + FnMut(&mut AppContext) -> SelectStyle) -> Self {
-        self.build_style = Some(Box::new(f));
-        self
-    }
-
-    pub fn set_item_count(&mut self, count: usize, cx: &mut ViewContext<Self>) {
-        if count != self.item_count {
-            self.item_count = count;
-            cx.notify();
-        }
-    }
-
-    fn toggle(&mut self, cx: &mut ViewContext<Self>) {
-        self.is_open = !self.is_open;
-        cx.notify();
-    }
-
-    pub fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Self>) {
-        if ix != self.selected_item_ix || self.is_open {
-            self.selected_item_ix = ix;
-            self.is_open = false;
-            cx.notify();
-        }
-    }
-
-    pub fn selected_index(&self) -> usize {
-        self.selected_item_ix
-    }
-}
-
-impl Entity for Select {
-    type Event = Event;
-}
-
-impl View for Select {
-    fn ui_name() -> &'static str {
-        "Select"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        if self.item_count == 0 {
-            return Empty::new().into_any();
-        }
-
-        enum Header {}
-        enum Item {}
-
-        let style = if let Some(build_style) = self.build_style.as_mut() {
-            (build_style)(cx)
-        } else {
-            Default::default()
-        };
-        let mut result = Flex::column().with_child(
-            MouseEventHandler::new::<Header, _>(self.handle.id(), cx, |mouse_state, cx| {
-                (self.render_item)(
-                    self.selected_item_ix,
-                    ItemType::Header,
-                    mouse_state.hovered(),
-                    cx,
-                )
-                .contained()
-                .with_style(style.header)
-            })
-            .with_cursor_style(CursorStyle::PointingHand)
-            .on_click(MouseButton::Left, move |_, this, cx| {
-                this.toggle(cx);
-            }),
-        );
-        if self.is_open {
-            result.add_child(Overlay::new(
-                UniformList::new(
-                    self.list_state.clone(),
-                    self.item_count,
-                    cx,
-                    move |this, mut range, items, cx| {
-                        let selected_item_ix = this.selected_item_ix;
-                        range.end = range.end.min(this.item_count);
-                        items.extend(range.map(|ix| {
-                            MouseEventHandler::new::<Item, _>(ix, cx, |mouse_state, cx| {
-                                (this.render_item)(
-                                    ix,
-                                    if ix == selected_item_ix {
-                                        ItemType::Selected
-                                    } else {
-                                        ItemType::Unselected
-                                    },
-                                    mouse_state.hovered(),
-                                    cx,
-                                )
-                            })
-                            .with_cursor_style(CursorStyle::PointingHand)
-                            .on_click(MouseButton::Left, move |_, this, cx| {
-                                this.set_selected_index(ix, cx);
-                            })
-                            .into_any()
-                        }))
-                    },
-                )
-                .constrained()
-                .with_max_height(200.)
-                .contained()
-                .with_style(style.menu),
-            ));
-        }
-        result.into_any()
-    }
-}

crates/gpui2/tests/action_macros.rs → crates/gpui/tests/action_macros.rs 🔗

@@ -1,11 +1,9 @@
-use gpui2::{actions, impl_actions};
-use gpui2_macros::register_action;
+use gpui::{actions, impl_actions};
+use gpui_macros::register_action;
 use serde_derive::Deserialize;
 
 #[test]
 fn test_action_macros() {
-    use gpui2 as gpui;
-
     actions!(test, [TestAction]);
 
     #[derive(PartialEq, Clone, Deserialize)]

crates/gpui/tests/test.rs 🔗

@@ -1,14 +0,0 @@
-use gpui::{elements::Empty, Element, ViewContext};
-// use gpui_macros::Element;
-
-#[test]
-fn test_derive_render_element() {
-    #[derive(Element)]
-    struct TestElement {}
-
-    impl TestElement {
-        fn render<V: 'static>(&mut self, _: &mut V, _: &mut ViewContext<V>) -> impl Element<V> {
-            Empty::new()
-        }
-    }
-}

crates/gpui2/Cargo.toml 🔗

@@ -1,92 +0,0 @@
-[package]
-name = "gpui2"
-version = "0.1.0"
-edition = "2021"
-authors = ["Nathan Sobo <nathan@zed.dev>"]
-description = "The next version of Zed's GPU-accelerated UI framework"
-publish = false
-
-[features]
-test-support = ["backtrace", "dhat", "env_logger", "collections/test-support", "util/test-support"]
-
-# Suppress a panic when both GPUI1 and GPUI2 are loaded.
-#
-# This is used in the `theme_importer` where we need to depend on both
-# GPUI1 and GPUI2 in order to convert Zed1 themes to Zed2 themes.
-allow-multiple-gpui-versions = ["util/allow-multiple-gpui-versions"]
-
-[lib]
-path = "src/gpui2.rs"
-doctest = false
-
-[dependencies]
-collections = { path = "../collections" }
-gpui2_macros = { path = "../gpui2_macros" }
-util = { path = "../util" }
-sum_tree = { path = "../sum_tree" }
-sqlez = { path = "../sqlez" }
-async-task = "4.0.3"
-backtrace = { version = "0.3", optional = true }
-ctor.workspace = true
-linkme = "0.3"
-derive_more.workspace = true
-dhat = { version = "0.3", optional = true }
-env_logger = { version = "0.9", optional = true }
-etagere = "0.2"
-futures.workspace = true
-image = "0.23"
-itertools = "0.10"
-lazy_static.workspace = true
-log.workspace = true
-num_cpus = "1.13"
-ordered-float.workspace = true
-parking = "2.0.0"
-parking_lot.workspace = true
-pathfinder_geometry = "0.5"
-postage.workspace = true
-rand.workspace = true
-refineable.workspace = true
-resvg = "0.14"
-seahash = "4.1"
-serde.workspace = true
-serde_derive.workspace = true
-serde_json.workspace = true
-smallvec.workspace = true
-smol.workspace = true
-taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "1876f72bee5e376023eaa518aa7b8a34c769bd1b" }
-thiserror.workspace = true
-time.workspace = true
-tiny-skia = "0.5"
-usvg = { version = "0.14", features = [] }
-uuid = { version = "1.1.2", features = ["v4"] }
-waker-fn = "1.1.0"
-slotmap = "1.0.6"
-schemars.workspace = true
-bitflags = "2.4.0"
-
-[dev-dependencies]
-backtrace = "0.3"
-collections = { path = "../collections", features = ["test-support"] }
-dhat = "0.3"
-env_logger.workspace = true
-png = "0.16"
-simplelog = "0.9"
-util = { path = "../util", features = ["test-support"] }
-
-[build-dependencies]
-bindgen = "0.65.1"
-cbindgen = "0.26.0"
-
-[target.'cfg(target_os = "macos")'.dependencies]
-media = { path = "../media" }
-anyhow.workspace = true
-block = "0.1"
-cocoa = "0.24"
-core-foundation = { version = "0.9.3", features = ["with-uuid"] }
-core-graphics = "0.22.3"
-core-text = "19.2"
-font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18" }
-foreign-types = "0.3"
-log.workspace = true
-metal = "0.21.0"
-objc = "0.2"

crates/gpui2/build.rs 🔗

@@ -1,137 +0,0 @@
-use std::{
-    env,
-    path::{Path, PathBuf},
-    process::{self, Command},
-};
-
-use cbindgen::Config;
-
-fn main() {
-    generate_dispatch_bindings();
-    let header_path = generate_shader_bindings();
-    compile_metal_shaders(&header_path);
-}
-
-fn generate_dispatch_bindings() {
-    println!("cargo:rustc-link-lib=framework=System");
-    println!("cargo:rerun-if-changed=src/platform/mac/dispatch.h");
-
-    let bindings = bindgen::Builder::default()
-        .header("src/platform/mac/dispatch.h")
-        .allowlist_var("_dispatch_main_q")
-        .allowlist_var("DISPATCH_QUEUE_PRIORITY_DEFAULT")
-        .allowlist_var("DISPATCH_TIME_NOW")
-        .allowlist_function("dispatch_get_global_queue")
-        .allowlist_function("dispatch_async_f")
-        .allowlist_function("dispatch_after_f")
-        .allowlist_function("dispatch_time")
-        .parse_callbacks(Box::new(bindgen::CargoCallbacks))
-        .layout_tests(false)
-        .generate()
-        .expect("unable to generate bindings");
-
-    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
-    bindings
-        .write_to_file(out_path.join("dispatch_sys.rs"))
-        .expect("couldn't write dispatch bindings");
-}
-
-fn generate_shader_bindings() -> PathBuf {
-    let output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("scene.h");
-    let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
-    let mut config = Config::default();
-    config.include_guard = Some("SCENE_H".into());
-    config.language = cbindgen::Language::C;
-    config.export.include.extend([
-        "Bounds".into(),
-        "Corners".into(),
-        "Edges".into(),
-        "Size".into(),
-        "Pixels".into(),
-        "PointF".into(),
-        "Hsla".into(),
-        "ContentMask".into(),
-        "Uniforms".into(),
-        "AtlasTile".into(),
-        "PathRasterizationInputIndex".into(),
-        "PathVertex_ScaledPixels".into(),
-        "ShadowInputIndex".into(),
-        "Shadow".into(),
-        "QuadInputIndex".into(),
-        "Underline".into(),
-        "UnderlineInputIndex".into(),
-        "Quad".into(),
-        "SpriteInputIndex".into(),
-        "MonochromeSprite".into(),
-        "PolychromeSprite".into(),
-        "PathSprite".into(),
-        "SurfaceInputIndex".into(),
-        "SurfaceBounds".into(),
-    ]);
-    config.no_includes = true;
-    config.enumeration.prefix_with_name = true;
-    cbindgen::Builder::new()
-        .with_src(crate_dir.join("src/scene.rs"))
-        .with_src(crate_dir.join("src/geometry.rs"))
-        .with_src(crate_dir.join("src/color.rs"))
-        .with_src(crate_dir.join("src/window.rs"))
-        .with_src(crate_dir.join("src/platform.rs"))
-        .with_src(crate_dir.join("src/platform/mac/metal_renderer.rs"))
-        .with_config(config)
-        .generate()
-        .expect("Unable to generate bindings")
-        .write_to_file(&output_path);
-
-    output_path
-}
-
-fn compile_metal_shaders(header_path: &Path) {
-    let shader_path = "./src/platform/mac/shaders.metal";
-    let air_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.air");
-    let metallib_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.metallib");
-
-    println!("cargo:rerun-if-changed={}", header_path.display());
-    println!("cargo:rerun-if-changed={}", shader_path);
-
-    let output = Command::new("xcrun")
-        .args([
-            "-sdk",
-            "macosx",
-            "metal",
-            "-gline-tables-only",
-            "-mmacosx-version-min=10.15.7",
-            "-MO",
-            "-c",
-            shader_path,
-            "-include",
-            &header_path.to_str().unwrap(),
-            "-o",
-        ])
-        .arg(&air_output_path)
-        .output()
-        .unwrap();
-
-    if !output.status.success() {
-        eprintln!(
-            "metal shader compilation failed:\n{}",
-            String::from_utf8_lossy(&output.stderr)
-        );
-        process::exit(1);
-    }
-
-    let output = Command::new("xcrun")
-        .args(["-sdk", "macosx", "metallib"])
-        .arg(air_output_path)
-        .arg("-o")
-        .arg(metallib_output_path)
-        .output()
-        .unwrap();
-
-    if !output.status.success() {
-        eprintln!(
-            "metallib compilation failed:\n{}",
-            String::from_utf8_lossy(&output.stderr)
-        );
-        process::exit(1);
-    }
-}

crates/gpui2/src/app.rs 🔗

@@ -1,1264 +0,0 @@
-mod async_context;
-mod entity_map;
-mod model_context;
-#[cfg(any(test, feature = "test-support"))]
-mod test_context;
-
-pub use async_context::*;
-use derive_more::{Deref, DerefMut};
-pub use entity_map::*;
-pub use model_context::*;
-use refineable::Refineable;
-use smol::future::FutureExt;
-#[cfg(any(test, feature = "test-support"))]
-pub use test_context::*;
-use time::UtcOffset;
-
-use crate::{
-    current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any,
-    AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context,
-    DispatchPhase, DisplayId, Entity, EventEmitter, ForegroundExecutor, KeyBinding, Keymap,
-    Keystroke, LayoutId, Menu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render,
-    SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement,
-    TextSystem, View, ViewContext, Window, WindowContext, WindowHandle, WindowId,
-};
-use anyhow::{anyhow, Result};
-use collections::{FxHashMap, FxHashSet, VecDeque};
-use futures::{channel::oneshot, future::LocalBoxFuture, Future};
-use parking_lot::Mutex;
-use slotmap::SlotMap;
-use std::{
-    any::{type_name, TypeId},
-    cell::{Ref, RefCell, RefMut},
-    marker::PhantomData,
-    mem,
-    ops::{Deref, DerefMut},
-    path::{Path, PathBuf},
-    rc::{Rc, Weak},
-    sync::{atomic::Ordering::SeqCst, Arc},
-    time::Duration,
-};
-use util::{
-    http::{self, HttpClient},
-    ResultExt,
-};
-
-/// Temporary(?) wrapper around RefCell<AppContext> to help us debug any double borrows.
-/// Strongly consider removing after stabilization.
-pub struct AppCell {
-    app: RefCell<AppContext>,
-}
-
-impl AppCell {
-    #[track_caller]
-    pub fn borrow(&self) -> AppRef {
-        if option_env!("TRACK_THREAD_BORROWS").is_some() {
-            let thread_id = std::thread::current().id();
-            eprintln!("borrowed {thread_id:?}");
-        }
-        AppRef(self.app.borrow())
-    }
-
-    #[track_caller]
-    pub fn borrow_mut(&self) -> AppRefMut {
-        if option_env!("TRACK_THREAD_BORROWS").is_some() {
-            let thread_id = std::thread::current().id();
-            eprintln!("borrowed {thread_id:?}");
-        }
-        AppRefMut(self.app.borrow_mut())
-    }
-}
-
-#[derive(Deref, DerefMut)]
-pub struct AppRef<'a>(Ref<'a, AppContext>);
-
-impl<'a> Drop for AppRef<'a> {
-    fn drop(&mut self) {
-        if option_env!("TRACK_THREAD_BORROWS").is_some() {
-            let thread_id = std::thread::current().id();
-            eprintln!("dropped borrow from {thread_id:?}");
-        }
-    }
-}
-
-#[derive(Deref, DerefMut)]
-pub struct AppRefMut<'a>(RefMut<'a, AppContext>);
-
-impl<'a> Drop for AppRefMut<'a> {
-    fn drop(&mut self) {
-        if option_env!("TRACK_THREAD_BORROWS").is_some() {
-            let thread_id = std::thread::current().id();
-            eprintln!("dropped {thread_id:?}");
-        }
-    }
-}
-
-pub struct App(Rc<AppCell>);
-
-/// Represents an application before it is fully launched. Once your app is
-/// configured, you'll start the app with `App::run`.
-impl App {
-    /// Builds an app with the given asset source.
-    pub fn production(asset_source: Arc<dyn AssetSource>) -> Self {
-        Self(AppContext::new(
-            current_platform(),
-            asset_source,
-            http::client(),
-        ))
-    }
-
-    /// Start the application. The provided callback will be called once the
-    /// app is fully launched.
-    pub fn run<F>(self, on_finish_launching: F)
-    where
-        F: 'static + FnOnce(&mut AppContext),
-    {
-        let this = self.0.clone();
-        let platform = self.0.borrow().platform.clone();
-        platform.run(Box::new(move || {
-            let cx = &mut *this.borrow_mut();
-            on_finish_launching(cx);
-        }));
-    }
-
-    /// Register a handler to be invoked when the platform instructs the application
-    /// to open one or more URLs.
-    pub fn on_open_urls<F>(&self, mut callback: F) -> &Self
-    where
-        F: 'static + FnMut(Vec<String>, &mut AppContext),
-    {
-        let this = Rc::downgrade(&self.0);
-        self.0.borrow().platform.on_open_urls(Box::new(move |urls| {
-            if let Some(app) = this.upgrade() {
-                callback(urls, &mut app.borrow_mut());
-            }
-        }));
-        self
-    }
-
-    pub fn on_reopen<F>(&self, mut callback: F) -> &Self
-    where
-        F: 'static + FnMut(&mut AppContext),
-    {
-        let this = Rc::downgrade(&self.0);
-        self.0.borrow_mut().platform.on_reopen(Box::new(move || {
-            if let Some(app) = this.upgrade() {
-                callback(&mut app.borrow_mut());
-            }
-        }));
-        self
-    }
-
-    pub fn metadata(&self) -> AppMetadata {
-        self.0.borrow().app_metadata.clone()
-    }
-
-    pub fn background_executor(&self) -> BackgroundExecutor {
-        self.0.borrow().background_executor.clone()
-    }
-
-    pub fn foreground_executor(&self) -> ForegroundExecutor {
-        self.0.borrow().foreground_executor.clone()
-    }
-
-    pub fn text_system(&self) -> Arc<TextSystem> {
-        self.0.borrow().text_system.clone()
-    }
-}
-
-pub(crate) type FrameCallback = Box<dyn FnOnce(&mut AppContext)>;
-type Handler = Box<dyn FnMut(&mut AppContext) -> bool + 'static>;
-type Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool + 'static>;
-type KeystrokeObserver = Box<dyn FnMut(&KeystrokeEvent, &mut WindowContext) + 'static>;
-type QuitHandler = Box<dyn FnOnce(&mut AppContext) -> LocalBoxFuture<'static, ()> + 'static>;
-type ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut AppContext) + 'static>;
-type NewViewListener = Box<dyn FnMut(AnyView, &mut WindowContext) + 'static>;
-
-// struct FrameConsumer {
-//     next_frame_callbacks: Vec<FrameCallback>,
-//     task: Task<()>,
-//     display_linker
-// }
-
-pub struct AppContext {
-    pub(crate) this: Weak<AppCell>,
-    pub(crate) platform: Rc<dyn Platform>,
-    app_metadata: AppMetadata,
-    text_system: Arc<TextSystem>,
-    flushing_effects: bool,
-    pending_updates: usize,
-    pub(crate) actions: Rc<ActionRegistry>,
-    pub(crate) active_drag: Option<AnyDrag>,
-    pub(crate) active_tooltip: Option<AnyTooltip>,
-    pub(crate) next_frame_callbacks: FxHashMap<DisplayId, Vec<FrameCallback>>,
-    pub(crate) frame_consumers: FxHashMap<DisplayId, Task<()>>,
-    pub(crate) background_executor: BackgroundExecutor,
-    pub(crate) foreground_executor: ForegroundExecutor,
-    pub(crate) svg_renderer: SvgRenderer,
-    asset_source: Arc<dyn AssetSource>,
-    pub(crate) image_cache: ImageCache,
-    pub(crate) text_style_stack: Vec<TextStyleRefinement>,
-    pub(crate) globals_by_type: FxHashMap<TypeId, Box<dyn Any>>,
-    pub(crate) entities: EntityMap,
-    pub(crate) new_view_observers: SubscriberSet<TypeId, NewViewListener>,
-    pub(crate) windows: SlotMap<WindowId, Option<Window>>,
-    pub(crate) keymap: Arc<Mutex<Keymap>>,
-    pub(crate) global_action_listeners:
-        FxHashMap<TypeId, Vec<Rc<dyn Fn(&dyn Any, DispatchPhase, &mut Self)>>>,
-    pending_effects: VecDeque<Effect>,
-    pub(crate) pending_notifications: FxHashSet<EntityId>,
-    pub(crate) pending_global_notifications: FxHashSet<TypeId>,
-    pub(crate) observers: SubscriberSet<EntityId, Handler>,
-    // TypeId is the type of the event that the listener callback expects
-    pub(crate) event_listeners: SubscriberSet<EntityId, (TypeId, Listener)>,
-    pub(crate) keystroke_observers: SubscriberSet<(), KeystrokeObserver>,
-    pub(crate) release_listeners: SubscriberSet<EntityId, ReleaseListener>,
-    pub(crate) global_observers: SubscriberSet<TypeId, Handler>,
-    pub(crate) quit_observers: SubscriberSet<(), QuitHandler>,
-    pub(crate) layout_id_buffer: Vec<LayoutId>, // We recycle this memory across layout requests.
-    pub(crate) propagate_event: bool,
-}
-
-impl AppContext {
-    pub(crate) fn new(
-        platform: Rc<dyn Platform>,
-        asset_source: Arc<dyn AssetSource>,
-        http_client: Arc<dyn HttpClient>,
-    ) -> Rc<AppCell> {
-        let executor = platform.background_executor();
-        let foreground_executor = platform.foreground_executor();
-        assert!(
-            executor.is_main_thread(),
-            "must construct App on main thread"
-        );
-
-        let text_system = Arc::new(TextSystem::new(platform.text_system()));
-        let entities = EntityMap::new();
-
-        let app_metadata = AppMetadata {
-            os_name: platform.os_name(),
-            os_version: platform.os_version().ok(),
-            app_version: platform.app_version().ok(),
-        };
-
-        let app = Rc::new_cyclic(|this| AppCell {
-            app: RefCell::new(AppContext {
-                this: this.clone(),
-                platform: platform.clone(),
-                app_metadata,
-                text_system,
-                actions: Rc::new(ActionRegistry::default()),
-                flushing_effects: false,
-                pending_updates: 0,
-                active_drag: None,
-                active_tooltip: None,
-                next_frame_callbacks: FxHashMap::default(),
-                frame_consumers: FxHashMap::default(),
-                background_executor: executor,
-                foreground_executor,
-                svg_renderer: SvgRenderer::new(asset_source.clone()),
-                asset_source,
-                image_cache: ImageCache::new(http_client),
-                text_style_stack: Vec::new(),
-                globals_by_type: FxHashMap::default(),
-                entities,
-                new_view_observers: SubscriberSet::new(),
-                windows: SlotMap::with_key(),
-                keymap: Arc::new(Mutex::new(Keymap::default())),
-                global_action_listeners: FxHashMap::default(),
-                pending_effects: VecDeque::new(),
-                pending_notifications: FxHashSet::default(),
-                pending_global_notifications: FxHashSet::default(),
-                observers: SubscriberSet::new(),
-                event_listeners: SubscriberSet::new(),
-                release_listeners: SubscriberSet::new(),
-                keystroke_observers: SubscriberSet::new(),
-                global_observers: SubscriberSet::new(),
-                quit_observers: SubscriberSet::new(),
-                layout_id_buffer: Default::default(),
-                propagate_event: true,
-            }),
-        });
-
-        init_app_menus(platform.as_ref(), &mut app.borrow_mut());
-
-        platform.on_quit(Box::new({
-            let cx = app.clone();
-            move || {
-                cx.borrow_mut().shutdown();
-            }
-        }));
-
-        app
-    }
-
-    /// Quit the application gracefully. Handlers registered with `ModelContext::on_app_quit`
-    /// will be given 100ms to complete before exiting.
-    pub fn shutdown(&mut self) {
-        let mut futures = Vec::new();
-
-        for observer in self.quit_observers.remove(&()) {
-            futures.push(observer(self));
-        }
-
-        self.windows.clear();
-        self.flush_effects();
-
-        let futures = futures::future::join_all(futures);
-        if self
-            .background_executor
-            .block_with_timeout(Duration::from_millis(100), futures)
-            .is_err()
-        {
-            log::error!("timed out waiting on app_will_quit");
-        }
-    }
-
-    pub fn quit(&mut self) {
-        self.platform.quit();
-    }
-
-    pub fn app_metadata(&self) -> AppMetadata {
-        self.app_metadata.clone()
-    }
-
-    /// Schedules all windows in the application to be redrawn. This can be called
-    /// multiple times in an update cycle and still result in a single redraw.
-    pub fn refresh(&mut self) {
-        self.pending_effects.push_back(Effect::Refresh);
-    }
-    pub(crate) fn update<R>(&mut self, update: impl FnOnce(&mut Self) -> R) -> R {
-        self.pending_updates += 1;
-        let result = update(self);
-        if !self.flushing_effects && self.pending_updates == 1 {
-            self.flushing_effects = true;
-            self.flush_effects();
-            self.flushing_effects = false;
-        }
-        self.pending_updates -= 1;
-        result
-    }
-
-    pub fn observe<W, E>(
-        &mut self,
-        entity: &E,
-        mut on_notify: impl FnMut(E, &mut AppContext) + 'static,
-    ) -> Subscription
-    where
-        W: 'static,
-        E: Entity<W>,
-    {
-        self.observe_internal(entity, move |e, cx| {
-            on_notify(e, cx);
-            true
-        })
-    }
-
-    pub fn observe_internal<W, E>(
-        &mut self,
-        entity: &E,
-        mut on_notify: impl FnMut(E, &mut AppContext) -> bool + 'static,
-    ) -> Subscription
-    where
-        W: 'static,
-        E: Entity<W>,
-    {
-        let entity_id = entity.entity_id();
-        let handle = entity.downgrade();
-        let (subscription, activate) = self.observers.insert(
-            entity_id,
-            Box::new(move |cx| {
-                if let Some(handle) = E::upgrade_from(&handle) {
-                    on_notify(handle, cx)
-                } else {
-                    false
-                }
-            }),
-        );
-        self.defer(move |_| activate());
-        subscription
-    }
-
-    pub fn subscribe<T, E, Evt>(
-        &mut self,
-        entity: &E,
-        mut on_event: impl FnMut(E, &Evt, &mut AppContext) + 'static,
-    ) -> Subscription
-    where
-        T: 'static + EventEmitter<Evt>,
-        E: Entity<T>,
-        Evt: 'static,
-    {
-        self.subscribe_internal(entity, move |entity, event, cx| {
-            on_event(entity, event, cx);
-            true
-        })
-    }
-
-    pub(crate) fn subscribe_internal<T, E, Evt>(
-        &mut self,
-        entity: &E,
-        mut on_event: impl FnMut(E, &Evt, &mut AppContext) -> bool + 'static,
-    ) -> Subscription
-    where
-        T: 'static + EventEmitter<Evt>,
-        E: Entity<T>,
-        Evt: 'static,
-    {
-        let entity_id = entity.entity_id();
-        let entity = entity.downgrade();
-        let (subscription, activate) = self.event_listeners.insert(
-            entity_id,
-            (
-                TypeId::of::<Evt>(),
-                Box::new(move |event, cx| {
-                    let event: &Evt = event.downcast_ref().expect("invalid event type");
-                    if let Some(handle) = E::upgrade_from(&entity) {
-                        on_event(handle, event, cx)
-                    } else {
-                        false
-                    }
-                }),
-            ),
-        );
-        self.defer(move |_| activate());
-        subscription
-    }
-
-    pub fn windows(&self) -> Vec<AnyWindowHandle> {
-        self.windows
-            .values()
-            .filter_map(|window| Some(window.as_ref()?.handle))
-            .collect()
-    }
-
-    pub fn active_window(&self) -> Option<AnyWindowHandle> {
-        self.platform.active_window()
-    }
-
-    /// Opens a new window with the given option and the root view returned by the given function.
-    /// The function is invoked with a `WindowContext`, which can be used to interact with window-specific
-    /// functionality.
-    pub fn open_window<V: 'static + Render>(
-        &mut self,
-        options: crate::WindowOptions,
-        build_root_view: impl FnOnce(&mut WindowContext) -> View<V>,
-    ) -> WindowHandle<V> {
-        self.update(|cx| {
-            let id = cx.windows.insert(None);
-            let handle = WindowHandle::new(id);
-            let mut window = Window::new(handle.into(), options, cx);
-            let root_view = build_root_view(&mut WindowContext::new(cx, &mut window));
-            window.root_view.replace(root_view.into());
-            cx.windows.get_mut(id).unwrap().replace(window);
-            handle
-        })
-    }
-
-    /// Instructs the platform to activate the application by bringing it to the foreground.
-    pub fn activate(&self, ignoring_other_apps: bool) {
-        self.platform.activate(ignoring_other_apps);
-    }
-
-    pub fn hide(&self) {
-        self.platform.hide();
-    }
-
-    pub fn hide_other_apps(&self) {
-        self.platform.hide_other_apps();
-    }
-
-    pub fn unhide_other_apps(&self) {
-        self.platform.unhide_other_apps();
-    }
-
-    /// Returns the list of currently active displays.
-    pub fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
-        self.platform.displays()
-    }
-
-    /// Writes data to the platform clipboard.
-    pub fn write_to_clipboard(&self, item: ClipboardItem) {
-        self.platform.write_to_clipboard(item)
-    }
-
-    /// Reads data from the platform clipboard.
-    pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
-        self.platform.read_from_clipboard()
-    }
-
-    /// Writes credentials to the platform keychain.
-    pub fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
-        self.platform.write_credentials(url, username, password)
-    }
-
-    /// Reads credentials from the platform keychain.
-    pub fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>> {
-        self.platform.read_credentials(url)
-    }
-
-    /// Deletes credentials from the platform keychain.
-    pub fn delete_credentials(&self, url: &str) -> Result<()> {
-        self.platform.delete_credentials(url)
-    }
-
-    /// Directs the platform's default browser to open the given URL.
-    pub fn open_url(&self, url: &str) {
-        self.platform.open_url(url);
-    }
-
-    pub fn app_path(&self) -> Result<PathBuf> {
-        self.platform.app_path()
-    }
-
-    pub fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
-        self.platform.path_for_auxiliary_executable(name)
-    }
-
-    pub fn double_click_interval(&self) -> Duration {
-        self.platform.double_click_interval()
-    }
-
-    pub fn prompt_for_paths(
-        &self,
-        options: PathPromptOptions,
-    ) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
-        self.platform.prompt_for_paths(options)
-    }
-
-    pub fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> {
-        self.platform.prompt_for_new_path(directory)
-    }
-
-    pub fn reveal_path(&self, path: &Path) {
-        self.platform.reveal_path(path)
-    }
-
-    pub fn should_auto_hide_scrollbars(&self) -> bool {
-        self.platform.should_auto_hide_scrollbars()
-    }
-
-    pub fn restart(&self) {
-        self.platform.restart()
-    }
-
-    pub fn local_timezone(&self) -> UtcOffset {
-        self.platform.local_timezone()
-    }
-
-    pub(crate) fn push_effect(&mut self, effect: Effect) {
-        match &effect {
-            Effect::Notify { emitter } => {
-                if !self.pending_notifications.insert(*emitter) {
-                    return;
-                }
-            }
-            Effect::NotifyGlobalObservers { global_type } => {
-                if !self.pending_global_notifications.insert(*global_type) {
-                    return;
-                }
-            }
-            _ => {}
-        };
-
-        self.pending_effects.push_back(effect);
-    }
-
-    /// Called at the end of AppContext::update to complete any side effects
-    /// such as notifying observers, emitting events, etc. Effects can themselves
-    /// cause effects, so we continue looping until all effects are processed.
-    fn flush_effects(&mut self) {
-        loop {
-            self.release_dropped_entities();
-            self.release_dropped_focus_handles();
-
-            if let Some(effect) = self.pending_effects.pop_front() {
-                match effect {
-                    Effect::Notify { emitter } => {
-                        self.apply_notify_effect(emitter);
-                    }
-
-                    Effect::Emit {
-                        emitter,
-                        event_type,
-                        event,
-                    } => self.apply_emit_effect(emitter, event_type, event),
-
-                    Effect::Refresh => {
-                        self.apply_refresh_effect();
-                    }
-
-                    Effect::NotifyGlobalObservers { global_type } => {
-                        self.apply_notify_global_observers_effect(global_type);
-                    }
-
-                    Effect::Defer { callback } => {
-                        self.apply_defer_effect(callback);
-                    }
-                }
-            } else {
-                for window in self.windows.values() {
-                    if let Some(window) = window.as_ref() {
-                        if window.dirty {
-                            window.platform_window.invalidate();
-                        }
-                    }
-                }
-
-                #[cfg(any(test, feature = "test-support"))]
-                for window in self
-                    .windows
-                    .values()
-                    .filter_map(|window| {
-                        let window = window.as_ref()?;
-                        (window.dirty || window.focus_invalidated).then_some(window.handle)
-                    })
-                    .collect::<Vec<_>>()
-                {
-                    self.update_window(window, |_, cx| cx.draw()).unwrap();
-                }
-
-                if self.pending_effects.is_empty() {
-                    break;
-                }
-            }
-        }
-    }
-
-    /// Repeatedly called during `flush_effects` to release any entities whose
-    /// reference count has become zero. We invoke any release observers before dropping
-    /// each entity.
-    fn release_dropped_entities(&mut self) {
-        loop {
-            let dropped = self.entities.take_dropped();
-            if dropped.is_empty() {
-                break;
-            }
-
-            for (entity_id, mut entity) in dropped {
-                self.observers.remove(&entity_id);
-                self.event_listeners.remove(&entity_id);
-                for release_callback in self.release_listeners.remove(&entity_id) {
-                    release_callback(entity.as_mut(), self);
-                }
-            }
-        }
-    }
-
-    /// Repeatedly called during `flush_effects` to handle a focused handle being dropped.
-    fn release_dropped_focus_handles(&mut self) {
-        for window_handle in self.windows() {
-            window_handle
-                .update(self, |_, cx| {
-                    let mut blur_window = false;
-                    let focus = cx.window.focus;
-                    cx.window.focus_handles.write().retain(|handle_id, count| {
-                        if count.load(SeqCst) == 0 {
-                            if focus == Some(handle_id) {
-                                blur_window = true;
-                            }
-                            false
-                        } else {
-                            true
-                        }
-                    });
-
-                    if blur_window {
-                        cx.blur();
-                    }
-                })
-                .unwrap();
-        }
-    }
-
-    fn apply_notify_effect(&mut self, emitter: EntityId) {
-        self.pending_notifications.remove(&emitter);
-
-        self.observers
-            .clone()
-            .retain(&emitter, |handler| handler(self));
-    }
-
-    fn apply_emit_effect(&mut self, emitter: EntityId, event_type: TypeId, event: Box<dyn Any>) {
-        self.event_listeners
-            .clone()
-            .retain(&emitter, |(stored_type, handler)| {
-                if *stored_type == event_type {
-                    handler(event.as_ref(), self)
-                } else {
-                    true
-                }
-            });
-    }
-
-    fn apply_refresh_effect(&mut self) {
-        for window in self.windows.values_mut() {
-            if let Some(window) = window.as_mut() {
-                window.dirty = true;
-            }
-        }
-    }
-
-    fn apply_notify_global_observers_effect(&mut self, type_id: TypeId) {
-        self.pending_global_notifications.remove(&type_id);
-        self.global_observers
-            .clone()
-            .retain(&type_id, |observer| observer(self));
-    }
-
-    fn apply_defer_effect(&mut self, callback: Box<dyn FnOnce(&mut Self) + 'static>) {
-        callback(self);
-    }
-
-    /// Creates an `AsyncAppContext`, which can be cloned and has a static lifetime
-    /// so it can be held across `await` points.
-    pub fn to_async(&self) -> AsyncAppContext {
-        AsyncAppContext {
-            app: unsafe { mem::transmute(self.this.clone()) },
-            background_executor: self.background_executor.clone(),
-            foreground_executor: self.foreground_executor.clone(),
-        }
-    }
-
-    /// Obtains a reference to the executor, which can be used to spawn futures.
-    pub fn background_executor(&self) -> &BackgroundExecutor {
-        &self.background_executor
-    }
-
-    /// Obtains a reference to the executor, which can be used to spawn futures.
-    pub fn foreground_executor(&self) -> &ForegroundExecutor {
-        &self.foreground_executor
-    }
-
-    /// Spawns the future returned by the given function on the thread pool. The closure will be invoked
-    /// with AsyncAppContext, which allows the application state to be accessed across await points.
-    pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
-    where
-        Fut: Future<Output = R> + 'static,
-        R: 'static,
-    {
-        self.foreground_executor.spawn(f(self.to_async()))
-    }
-
-    /// Schedules the given function to be run at the end of the current effect cycle, allowing entities
-    /// that are currently on the stack to be returned to the app.
-    pub fn defer(&mut self, f: impl FnOnce(&mut AppContext) + 'static) {
-        self.push_effect(Effect::Defer {
-            callback: Box::new(f),
-        });
-    }
-
-    /// Accessor for the application's asset source, which is provided when constructing the `App`.
-    pub fn asset_source(&self) -> &Arc<dyn AssetSource> {
-        &self.asset_source
-    }
-
-    /// Accessor for the text system.
-    pub fn text_system(&self) -> &Arc<TextSystem> {
-        &self.text_system
-    }
-
-    /// The current text style. Which is composed of all the style refinements provided to `with_text_style`.
-    pub fn text_style(&self) -> TextStyle {
-        let mut style = TextStyle::default();
-        for refinement in &self.text_style_stack {
-            style.refine(refinement);
-        }
-        style
-    }
-
-    /// Check whether a global of the given type has been assigned.
-    pub fn has_global<G: 'static>(&self) -> bool {
-        self.globals_by_type.contains_key(&TypeId::of::<G>())
-    }
-
-    /// Access the global of the given type. Panics if a global for that type has not been assigned.
-    #[track_caller]
-    pub fn global<G: 'static>(&self) -> &G {
-        self.globals_by_type
-            .get(&TypeId::of::<G>())
-            .map(|any_state| any_state.downcast_ref::<G>().unwrap())
-            .ok_or_else(|| anyhow!("no state of type {} exists", type_name::<G>()))
-            .unwrap()
-    }
-
-    /// Access the global of the given type if a value has been assigned.
-    pub fn try_global<G: 'static>(&self) -> Option<&G> {
-        self.globals_by_type
-            .get(&TypeId::of::<G>())
-            .map(|any_state| any_state.downcast_ref::<G>().unwrap())
-    }
-
-    /// Access the global of the given type mutably. Panics if a global for that type has not been assigned.
-    #[track_caller]
-    pub fn global_mut<G: 'static>(&mut self) -> &mut G {
-        let global_type = TypeId::of::<G>();
-        self.push_effect(Effect::NotifyGlobalObservers { global_type });
-        self.globals_by_type
-            .get_mut(&global_type)
-            .and_then(|any_state| any_state.downcast_mut::<G>())
-            .ok_or_else(|| anyhow!("no state of type {} exists", type_name::<G>()))
-            .unwrap()
-    }
-
-    /// Access the global of the given type mutably. A default value is assigned if a global of this type has not
-    /// yet been assigned.
-    pub fn default_global<G: 'static + Default>(&mut self) -> &mut G {
-        let global_type = TypeId::of::<G>();
-        self.push_effect(Effect::NotifyGlobalObservers { global_type });
-        self.globals_by_type
-            .entry(global_type)
-            .or_insert_with(|| Box::<G>::default())
-            .downcast_mut::<G>()
-            .unwrap()
-    }
-
-    /// Set the value of the global of the given type.
-    pub fn set_global<G: Any>(&mut self, global: G) {
-        let global_type = TypeId::of::<G>();
-        self.push_effect(Effect::NotifyGlobalObservers { global_type });
-        self.globals_by_type.insert(global_type, Box::new(global));
-    }
-
-    /// Clear all stored globals. Does not notify global observers.
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn clear_globals(&mut self) {
-        self.globals_by_type.drain();
-    }
-
-    /// Remove the global of the given type from the app context. Does not notify global observers.
-    pub fn remove_global<G: Any>(&mut self) -> G {
-        let global_type = TypeId::of::<G>();
-        *self
-            .globals_by_type
-            .remove(&global_type)
-            .unwrap_or_else(|| panic!("no global added for {}", std::any::type_name::<G>()))
-            .downcast()
-            .unwrap()
-    }
-
-    /// Update the global of the given type with a closure. Unlike `global_mut`, this method provides
-    /// your closure with mutable access to the `AppContext` and the global simultaneously.
-    pub fn update_global<G: 'static, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R {
-        let mut global = self.lease_global::<G>();
-        let result = f(&mut global, self);
-        self.end_global_lease(global);
-        result
-    }
-
-    /// Register a callback to be invoked when a global of the given type is updated.
-    pub fn observe_global<G: 'static>(
-        &mut self,
-        mut f: impl FnMut(&mut Self) + 'static,
-    ) -> Subscription {
-        let (subscription, activate) = self.global_observers.insert(
-            TypeId::of::<G>(),
-            Box::new(move |cx| {
-                f(cx);
-                true
-            }),
-        );
-        self.defer(move |_| activate());
-        subscription
-    }
-
-    /// Move the global of the given type to the stack.
-    pub(crate) fn lease_global<G: 'static>(&mut self) -> GlobalLease<G> {
-        GlobalLease::new(
-            self.globals_by_type
-                .remove(&TypeId::of::<G>())
-                .ok_or_else(|| anyhow!("no global registered of type {}", type_name::<G>()))
-                .unwrap(),
-        )
-    }
-
-    /// Restore the global of the given type after it is moved to the stack.
-    pub(crate) fn end_global_lease<G: 'static>(&mut self, lease: GlobalLease<G>) {
-        let global_type = TypeId::of::<G>();
-        self.push_effect(Effect::NotifyGlobalObservers { global_type });
-        self.globals_by_type.insert(global_type, lease.global);
-    }
-
-    pub fn observe_new_views<V: 'static>(
-        &mut self,
-        on_new: impl 'static + Fn(&mut V, &mut ViewContext<V>),
-    ) -> Subscription {
-        let (subscription, activate) = self.new_view_observers.insert(
-            TypeId::of::<V>(),
-            Box::new(move |any_view: AnyView, cx: &mut WindowContext| {
-                any_view
-                    .downcast::<V>()
-                    .unwrap()
-                    .update(cx, |view_state, cx| {
-                        on_new(view_state, cx);
-                    })
-            }),
-        );
-        activate();
-        subscription
-    }
-
-    pub fn observe_release<E, T>(
-        &mut self,
-        handle: &E,
-        on_release: impl FnOnce(&mut T, &mut AppContext) + 'static,
-    ) -> Subscription
-    where
-        E: Entity<T>,
-        T: 'static,
-    {
-        let (subscription, activate) = self.release_listeners.insert(
-            handle.entity_id(),
-            Box::new(move |entity, cx| {
-                let entity = entity.downcast_mut().expect("invalid entity type");
-                on_release(entity, cx)
-            }),
-        );
-        activate();
-        subscription
-    }
-
-    pub fn observe_keystrokes(
-        &mut self,
-        f: impl FnMut(&KeystrokeEvent, &mut WindowContext) + 'static,
-    ) -> Subscription {
-        let (subscription, activate) = self.keystroke_observers.insert((), Box::new(f));
-        activate();
-        subscription
-    }
-
-    pub(crate) fn push_text_style(&mut self, text_style: TextStyleRefinement) {
-        self.text_style_stack.push(text_style);
-    }
-
-    pub(crate) fn pop_text_style(&mut self) {
-        self.text_style_stack.pop();
-    }
-
-    /// Register key bindings.
-    pub fn bind_keys(&mut self, bindings: impl IntoIterator<Item = KeyBinding>) {
-        self.keymap.lock().add_bindings(bindings);
-        self.pending_effects.push_back(Effect::Refresh);
-    }
-
-    /// Register a global listener for actions invoked via the keyboard.
-    pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut Self) + 'static) {
-        self.global_action_listeners
-            .entry(TypeId::of::<A>())
-            .or_default()
-            .push(Rc::new(move |action, phase, cx| {
-                if phase == DispatchPhase::Bubble {
-                    let action = action.downcast_ref().unwrap();
-                    listener(action, cx)
-                }
-            }));
-    }
-
-    /// Event handlers propagate events by default. Call this method to stop dispatching to
-    /// event handlers with a lower z-index (mouse) or higher in the tree (keyboard). This is
-    /// the opposite of [propagate]. It's also possible to cancel a call to [propagate] by
-    /// calling this method before effects are flushed.
-    pub fn stop_propagation(&mut self) {
-        self.propagate_event = false;
-    }
-
-    /// Action handlers stop propagation by default during the bubble phase of action dispatch
-    /// dispatching to action handlers higher in the element tree. This is the opposite of
-    /// [stop_propagation]. It's also possible to cancel a call to [stop_propagate] by calling
-    /// this method before effects are flushed.
-    pub fn propagate(&mut self) {
-        self.propagate_event = true;
-    }
-
-    pub fn build_action(
-        &self,
-        name: &str,
-        data: Option<serde_json::Value>,
-    ) -> Result<Box<dyn Action>> {
-        self.actions.build_action(name, data)
-    }
-
-    pub fn all_action_names(&self) -> &[SharedString] {
-        self.actions.all_action_names()
-    }
-
-    pub fn on_app_quit<Fut>(
-        &mut self,
-        mut on_quit: impl FnMut(&mut AppContext) -> Fut + 'static,
-    ) -> Subscription
-    where
-        Fut: 'static + Future<Output = ()>,
-    {
-        let (subscription, activate) = self.quit_observers.insert(
-            (),
-            Box::new(move |cx| {
-                let future = on_quit(cx);
-                future.boxed_local()
-            }),
-        );
-        activate();
-        subscription
-    }
-
-    pub(crate) fn clear_pending_keystrokes(&mut self) {
-        for window in self.windows() {
-            window
-                .update(self, |_, cx| {
-                    cx.window
-                        .rendered_frame
-                        .dispatch_tree
-                        .clear_pending_keystrokes();
-                    cx.window
-                        .next_frame
-                        .dispatch_tree
-                        .clear_pending_keystrokes();
-                })
-                .ok();
-        }
-    }
-
-    pub fn is_action_available(&mut self, action: &dyn Action) -> bool {
-        if let Some(window) = self.active_window() {
-            if let Ok(window_action_available) =
-                window.update(self, |_, cx| cx.is_action_available(action))
-            {
-                return window_action_available;
-            }
-        }
-
-        self.global_action_listeners
-            .contains_key(&action.as_any().type_id())
-    }
-
-    pub fn set_menus(&mut self, menus: Vec<Menu>) {
-        self.platform.set_menus(menus, &self.keymap.lock());
-    }
-
-    pub fn dispatch_action(&mut self, action: &dyn Action) {
-        if let Some(active_window) = self.active_window() {
-            active_window
-                .update(self, |_, cx| cx.dispatch_action(action.boxed_clone()))
-                .log_err();
-        } else {
-            self.propagate_event = true;
-
-            if let Some(mut global_listeners) = self
-                .global_action_listeners
-                .remove(&action.as_any().type_id())
-            {
-                for listener in &global_listeners {
-                    listener(action.as_any(), DispatchPhase::Capture, self);
-                    if !self.propagate_event {
-                        break;
-                    }
-                }
-
-                global_listeners.extend(
-                    self.global_action_listeners
-                        .remove(&action.as_any().type_id())
-                        .unwrap_or_default(),
-                );
-
-                self.global_action_listeners
-                    .insert(action.as_any().type_id(), global_listeners);
-            }
-
-            if self.propagate_event {
-                if let Some(mut global_listeners) = self
-                    .global_action_listeners
-                    .remove(&action.as_any().type_id())
-                {
-                    for listener in global_listeners.iter().rev() {
-                        listener(action.as_any(), DispatchPhase::Bubble, self);
-                        if !self.propagate_event {
-                            break;
-                        }
-                    }
-
-                    global_listeners.extend(
-                        self.global_action_listeners
-                            .remove(&action.as_any().type_id())
-                            .unwrap_or_default(),
-                    );
-
-                    self.global_action_listeners
-                        .insert(action.as_any().type_id(), global_listeners);
-                }
-            }
-        }
-    }
-
-    pub fn has_active_drag(&self) -> bool {
-        self.active_drag.is_some()
-    }
-
-    pub fn active_drag<T: 'static>(&self) -> Option<&T> {
-        self.active_drag
-            .as_ref()
-            .and_then(|drag| drag.value.downcast_ref())
-    }
-}
-
-impl Context for AppContext {
-    type Result<T> = T;
-
-    /// Build an entity that is owned by the application. The given function will be invoked with
-    /// a `ModelContext` and must return an object representing the entity. A `Model` will be returned
-    /// which can be used to access the entity in a context.
-    fn new_model<T: 'static>(
-        &mut self,
-        build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
-    ) -> Model<T> {
-        self.update(|cx| {
-            let slot = cx.entities.reserve();
-            let entity = build_model(&mut ModelContext::new(cx, slot.downgrade()));
-            cx.entities.insert(slot, entity)
-        })
-    }
-
-    /// Update the entity referenced by the given model. The function is passed a mutable reference to the
-    /// entity along with a `ModelContext` for the entity.
-    fn update_model<T: 'static, R>(
-        &mut self,
-        model: &Model<T>,
-        update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
-    ) -> R {
-        self.update(|cx| {
-            let mut entity = cx.entities.lease(model);
-            let result = update(&mut entity, &mut ModelContext::new(cx, model.downgrade()));
-            cx.entities.end_lease(entity);
-            result
-        })
-    }
-
-    fn update_window<T, F>(&mut self, handle: AnyWindowHandle, update: F) -> Result<T>
-    where
-        F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
-    {
-        self.update(|cx| {
-            let mut window = cx
-                .windows
-                .get_mut(handle.id)
-                .ok_or_else(|| anyhow!("window not found"))?
-                .take()
-                .unwrap();
-
-            let root_view = window.root_view.clone().unwrap();
-            let result = update(root_view, &mut WindowContext::new(cx, &mut window));
-
-            if window.removed {
-                cx.windows.remove(handle.id);
-            } else {
-                cx.windows
-                    .get_mut(handle.id)
-                    .ok_or_else(|| anyhow!("window not found"))?
-                    .replace(window);
-            }
-
-            Ok(result)
-        })
-    }
-
-    fn read_model<T, R>(
-        &self,
-        handle: &Model<T>,
-        read: impl FnOnce(&T, &AppContext) -> R,
-    ) -> Self::Result<R>
-    where
-        T: 'static,
-    {
-        let entity = self.entities.read(handle);
-        read(entity, self)
-    }
-
-    fn read_window<T, R>(
-        &self,
-        window: &WindowHandle<T>,
-        read: impl FnOnce(View<T>, &AppContext) -> R,
-    ) -> Result<R>
-    where
-        T: 'static,
-    {
-        let window = self
-            .windows
-            .get(window.id)
-            .ok_or_else(|| anyhow!("window not found"))?
-            .as_ref()
-            .unwrap();
-
-        let root_view = window.root_view.clone().unwrap();
-        let view = root_view
-            .downcast::<T>()
-            .map_err(|_| anyhow!("root view's type has changed"))?;
-
-        Ok(read(view, self))
-    }
-}
-
-/// These effects are processed at the end of each application update cycle.
-pub(crate) enum Effect {
-    Notify {
-        emitter: EntityId,
-    },
-    Emit {
-        emitter: EntityId,
-        event_type: TypeId,
-        event: Box<dyn Any>,
-    },
-    Refresh,
-    NotifyGlobalObservers {
-        global_type: TypeId,
-    },
-    Defer {
-        callback: Box<dyn FnOnce(&mut AppContext) + 'static>,
-    },
-}
-
-/// Wraps a global variable value during `update_global` while the value has been moved to the stack.
-pub(crate) struct GlobalLease<G: 'static> {
-    global: Box<dyn Any>,
-    global_type: PhantomData<G>,
-}
-
-impl<G: 'static> GlobalLease<G> {
-    fn new(global: Box<dyn Any>) -> Self {
-        GlobalLease {
-            global,
-            global_type: PhantomData,
-        }
-    }
-}
-
-impl<G: 'static> Deref for GlobalLease<G> {
-    type Target = G;
-
-    fn deref(&self) -> &Self::Target {
-        self.global.downcast_ref().unwrap()
-    }
-}
-
-impl<G: 'static> DerefMut for GlobalLease<G> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        self.global.downcast_mut().unwrap()
-    }
-}
-
-/// Contains state associated with an active drag operation, started by dragging an element
-/// within the window or by dragging into the app from the underlying platform.
-pub struct AnyDrag {
-    pub view: AnyView,
-    pub value: Box<dyn Any>,
-    pub cursor_offset: Point<Pixels>,
-}
-
-#[derive(Clone)]
-pub(crate) struct AnyTooltip {
-    pub view: AnyView,
-    pub cursor_offset: Point<Pixels>,
-}
-
-#[derive(Debug)]
-pub struct KeystrokeEvent {
-    pub keystroke: Keystroke,
-    pub action: Option<Box<dyn Action>>,
-}

crates/gpui2/src/assets.rs 🔗

@@ -1,64 +0,0 @@
-use crate::{size, DevicePixels, Result, SharedString, Size};
-use anyhow::anyhow;
-use image::{Bgra, ImageBuffer};
-use std::{
-    borrow::Cow,
-    fmt,
-    hash::Hash,
-    sync::atomic::{AtomicUsize, Ordering::SeqCst},
-};
-
-pub trait AssetSource: 'static + Send + Sync {
-    fn load(&self, path: &str) -> Result<Cow<[u8]>>;
-    fn list(&self, path: &str) -> Result<Vec<SharedString>>;
-}
-
-impl AssetSource for () {
-    fn load(&self, path: &str) -> Result<Cow<[u8]>> {
-        Err(anyhow!(
-            "get called on empty asset provider with \"{}\"",
-            path
-        ))
-    }
-
-    fn list(&self, _path: &str) -> Result<Vec<SharedString>> {
-        Ok(vec![])
-    }
-}
-
-#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
-pub struct ImageId(usize);
-
-pub struct ImageData {
-    pub id: ImageId,
-    data: ImageBuffer<Bgra<u8>, Vec<u8>>,
-}
-
-impl ImageData {
-    pub fn new(data: ImageBuffer<Bgra<u8>, Vec<u8>>) -> Self {
-        static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
-
-        Self {
-            id: ImageId(NEXT_ID.fetch_add(1, SeqCst)),
-            data,
-        }
-    }
-
-    pub fn as_bytes(&self) -> &[u8] {
-        &self.data
-    }
-
-    pub fn size(&self) -> Size<DevicePixels> {
-        let (width, height) = self.data.dimensions();
-        size(width.into(), height.into())
-    }
-}
-
-impl fmt::Debug for ImageData {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_struct("ImageData")
-            .field("id", &self.id)
-            .field("size", &self.data.dimensions())
-            .finish()
-    }
-}

crates/gpui2/src/color.rs 🔗

@@ -1,457 +0,0 @@
-use anyhow::bail;
-use serde::de::{self, Deserialize, Deserializer, Visitor};
-use std::fmt;
-
-pub fn rgb<C: From<Rgba>>(hex: u32) -> C {
-    let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
-    let g = ((hex >> 8) & 0xFF) as f32 / 255.0;
-    let b = (hex & 0xFF) as f32 / 255.0;
-    Rgba { r, g, b, a: 1.0 }.into()
-}
-
-pub fn rgba(hex: u32) -> Rgba {
-    let r = ((hex >> 24) & 0xFF) as f32 / 255.0;
-    let g = ((hex >> 16) & 0xFF) as f32 / 255.0;
-    let b = ((hex >> 8) & 0xFF) as f32 / 255.0;
-    let a = (hex & 0xFF) as f32 / 255.0;
-    Rgba { r, g, b, a }
-}
-
-#[derive(PartialEq, Clone, Copy, Default)]
-pub struct Rgba {
-    pub r: f32,
-    pub g: f32,
-    pub b: f32,
-    pub a: f32,
-}
-
-impl fmt::Debug for Rgba {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "rgba({:#010x})", u32::from(*self))
-    }
-}
-
-impl Rgba {
-    pub fn blend(&self, other: Rgba) -> Self {
-        if other.a >= 1.0 {
-            other
-        } else if other.a <= 0.0 {
-            return *self;
-        } else {
-            return Rgba {
-                r: (self.r * (1.0 - other.a)) + (other.r * other.a),
-                g: (self.g * (1.0 - other.a)) + (other.g * other.a),
-                b: (self.b * (1.0 - other.a)) + (other.b * other.a),
-                a: self.a,
-            };
-        }
-    }
-}
-
-impl From<Rgba> for u32 {
-    fn from(rgba: Rgba) -> Self {
-        let r = (rgba.r * 255.0) as u32;
-        let g = (rgba.g * 255.0) as u32;
-        let b = (rgba.b * 255.0) as u32;
-        let a = (rgba.a * 255.0) as u32;
-        (r << 24) | (g << 16) | (b << 8) | a
-    }
-}
-
-struct RgbaVisitor;
-
-impl<'de> Visitor<'de> for RgbaVisitor {
-    type Value = Rgba;
-
-    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
-        formatter.write_str("a string in the format #rrggbb or #rrggbbaa")
-    }
-
-    fn visit_str<E: de::Error>(self, value: &str) -> Result<Rgba, E> {
-        Rgba::try_from(value).map_err(E::custom)
-    }
-}
-
-impl<'de> Deserialize<'de> for Rgba {
-    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
-        deserializer.deserialize_str(RgbaVisitor)
-    }
-}
-
-impl From<Hsla> for Rgba {
-    fn from(color: Hsla) -> Self {
-        let h = color.h;
-        let s = color.s;
-        let l = color.l;
-
-        let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
-        let x = c * (1.0 - ((h * 6.0) % 2.0 - 1.0).abs());
-        let m = l - c / 2.0;
-        let cm = c + m;
-        let xm = x + m;
-
-        let (r, g, b) = match (h * 6.0).floor() as i32 {
-            0 | 6 => (cm, xm, m),
-            1 => (xm, cm, m),
-            2 => (m, cm, xm),
-            3 => (m, xm, cm),
-            4 => (xm, m, cm),
-            _ => (cm, m, xm),
-        };
-
-        Rgba {
-            r,
-            g,
-            b,
-            a: color.a,
-        }
-    }
-}
-
-impl TryFrom<&'_ str> for Rgba {
-    type Error = anyhow::Error;
-
-    fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
-        const RGB: usize = "rgb".len();
-        const RGBA: usize = "rgba".len();
-        const RRGGBB: usize = "rrggbb".len();
-        const RRGGBBAA: usize = "rrggbbaa".len();
-
-        const EXPECTED_FORMATS: &str = "Expected #rgb, #rgba, #rrggbb, or #rrggbbaa";
-
-        let Some(("", hex)) = value.trim().split_once('#') else {
-            bail!("invalid RGBA hex color: '{value}'. {EXPECTED_FORMATS}");
-        };
-
-        let (r, g, b, a) = match hex.len() {
-            RGB | RGBA => {
-                let r = u8::from_str_radix(&hex[0..1], 16)?;
-                let g = u8::from_str_radix(&hex[1..2], 16)?;
-                let b = u8::from_str_radix(&hex[2..3], 16)?;
-                let a = if hex.len() == RGBA {
-                    u8::from_str_radix(&hex[3..4], 16)?
-                } else {
-                    0xf
-                };
-
-                /// Duplicates a given hex digit.
-                /// E.g., `0xf` -> `0xff`.
-                const fn duplicate(value: u8) -> u8 {
-                    value << 4 | value
-                }
-
-                (duplicate(r), duplicate(g), duplicate(b), duplicate(a))
-            }
-            RRGGBB | RRGGBBAA => {
-                let r = u8::from_str_radix(&hex[0..2], 16)?;
-                let g = u8::from_str_radix(&hex[2..4], 16)?;
-                let b = u8::from_str_radix(&hex[4..6], 16)?;
-                let a = if hex.len() == RRGGBBAA {
-                    u8::from_str_radix(&hex[6..8], 16)?
-                } else {
-                    0xff
-                };
-                (r, g, b, a)
-            }
-            _ => bail!("invalid RGBA hex color: '{value}'. {EXPECTED_FORMATS}"),
-        };
-
-        Ok(Rgba {
-            r: r as f32 / 255.,
-            g: g as f32 / 255.,
-            b: b as f32 / 255.,
-            a: a as f32 / 255.,
-        })
-    }
-}
-
-#[derive(Default, Copy, Clone, Debug)]
-#[repr(C)]
-pub struct Hsla {
-    pub h: f32,
-    pub s: f32,
-    pub l: f32,
-    pub a: f32,
-}
-
-impl PartialEq for Hsla {
-    fn eq(&self, other: &Self) -> bool {
-        self.h
-            .total_cmp(&other.h)
-            .then(self.s.total_cmp(&other.s))
-            .then(self.l.total_cmp(&other.l).then(self.a.total_cmp(&other.a)))
-            .is_eq()
-    }
-}
-
-impl PartialOrd for Hsla {
-    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
-        // SAFETY: The total ordering relies on this always being Some()
-        Some(
-            self.h
-                .total_cmp(&other.h)
-                .then(self.s.total_cmp(&other.s))
-                .then(self.l.total_cmp(&other.l).then(self.a.total_cmp(&other.a))),
-        )
-    }
-}
-
-impl Ord for Hsla {
-    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
-        // SAFETY: The partial comparison is a total comparison
-        unsafe { self.partial_cmp(other).unwrap_unchecked() }
-    }
-}
-
-impl Hsla {
-    pub fn to_rgb(self) -> Rgba {
-        self.into()
-    }
-
-    pub fn red() -> Self {
-        red()
-    }
-
-    pub fn green() -> Self {
-        green()
-    }
-
-    pub fn blue() -> Self {
-        blue()
-    }
-
-    pub fn black() -> Self {
-        black()
-    }
-
-    pub fn white() -> Self {
-        white()
-    }
-
-    pub fn transparent_black() -> Self {
-        transparent_black()
-    }
-}
-
-impl Eq for Hsla {}
-
-pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
-    Hsla {
-        h: h.clamp(0., 1.),
-        s: s.clamp(0., 1.),
-        l: l.clamp(0., 1.),
-        a: a.clamp(0., 1.),
-    }
-}
-
-pub fn black() -> Hsla {
-    Hsla {
-        h: 0.,
-        s: 0.,
-        l: 0.,
-        a: 1.,
-    }
-}
-
-pub fn transparent_black() -> Hsla {
-    Hsla {
-        h: 0.,
-        s: 0.,
-        l: 0.,
-        a: 0.,
-    }
-}
-
-pub fn white() -> Hsla {
-    Hsla {
-        h: 0.,
-        s: 0.,
-        l: 1.,
-        a: 1.,
-    }
-}
-
-pub fn red() -> Hsla {
-    Hsla {
-        h: 0.,
-        s: 1.,
-        l: 0.5,
-        a: 1.,
-    }
-}
-
-pub fn blue() -> Hsla {
-    Hsla {
-        h: 0.6,
-        s: 1.,
-        l: 0.5,
-        a: 1.,
-    }
-}
-
-pub fn green() -> Hsla {
-    Hsla {
-        h: 0.33,
-        s: 1.,
-        l: 0.5,
-        a: 1.,
-    }
-}
-
-pub fn yellow() -> Hsla {
-    Hsla {
-        h: 0.16,
-        s: 1.,
-        l: 0.5,
-        a: 1.,
-    }
-}
-
-impl Hsla {
-    /// Returns true if the HSLA color is fully transparent, false otherwise.
-    pub fn is_transparent(&self) -> bool {
-        self.a == 0.0
-    }
-
-    /// Blends `other` on top of `self` based on `other`'s alpha value. The resulting color is a combination of `self`'s and `other`'s colors.
-    ///
-    /// If `other`'s alpha value is 1.0 or greater, `other` color is fully opaque, thus `other` is returned as the output color.
-    /// If `other`'s alpha value is 0.0 or less, `other` color is fully transparent, thus `self` is returned as the output color.
-    /// Else, the output color is calculated as a blend of `self` and `other` based on their weighted alpha values.
-    ///
-    /// Assumptions:
-    /// - Alpha values are contained in the range [0, 1], with 1 as fully opaque and 0 as fully transparent.
-    /// - The relative contributions of `self` and `other` is based on `self`'s alpha value (`self.a`) and `other`'s  alpha value (`other.a`), `self` contributing `self.a * (1.0 - other.a)` and `other` contributing it's own alpha value.
-    /// - RGB color components are contained in the range [0, 1].
-    /// - If `self` and `other` colors are out of the valid range, the blend operation's output and behavior is undefined.
-    pub fn blend(self, other: Hsla) -> Hsla {
-        let alpha = other.a;
-
-        if alpha >= 1.0 {
-            other
-        } else if alpha <= 0.0 {
-            return self;
-        } else {
-            let converted_self = Rgba::from(self);
-            let converted_other = Rgba::from(other);
-            let blended_rgb = converted_self.blend(converted_other);
-            return Hsla::from(blended_rgb);
-        }
-    }
-
-    /// Fade out the color by a given factor. This factor should be between 0.0 and 1.0.
-    /// Where 0.0 will leave the color unchanged, and 1.0 will completely fade out the color.
-    pub fn fade_out(&mut self, factor: f32) {
-        self.a *= 1.0 - factor.clamp(0., 1.);
-    }
-}
-
-// impl From<Hsla> for Rgba {
-//     fn from(value: Hsla) -> Self {
-//         let h = value.h;
-//         let s = value.s;
-//         let l = value.l;
-
-//         let c = (1 - |2L - 1|) X s
-//     }
-// }
-
-impl From<Rgba> for Hsla {
-    fn from(color: Rgba) -> Self {
-        let r = color.r;
-        let g = color.g;
-        let b = color.b;
-
-        let max = r.max(g.max(b));
-        let min = r.min(g.min(b));
-        let delta = max - min;
-
-        let l = (max + min) / 2.0;
-        let s = if l == 0.0 || l == 1.0 {
-            0.0
-        } else if l < 0.5 {
-            delta / (2.0 * l)
-        } else {
-            delta / (2.0 - 2.0 * l)
-        };
-
-        let h = if delta == 0.0 {
-            0.0
-        } else if max == r {
-            ((g - b) / delta).rem_euclid(6.0) / 6.0
-        } else if max == g {
-            ((b - r) / delta + 2.0) / 6.0
-        } else {
-            ((r - g) / delta + 4.0) / 6.0
-        };
-
-        Hsla {
-            h,
-            s,
-            l,
-            a: color.a,
-        }
-    }
-}
-
-impl<'de> Deserialize<'de> for Hsla {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-    where
-        D: Deserializer<'de>,
-    {
-        // First, deserialize it into Rgba
-        let rgba = Rgba::deserialize(deserializer)?;
-
-        // Then, use the From<Rgba> for Hsla implementation to convert it
-        Ok(Hsla::from(rgba))
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use serde_json::json;
-
-    use super::*;
-
-    #[test]
-    fn test_deserialize_three_value_hex_to_rgba() {
-        let actual: Rgba = serde_json::from_value(json!("#f09")).unwrap();
-
-        assert_eq!(actual, rgba(0xff0099ff))
-    }
-
-    #[test]
-    fn test_deserialize_four_value_hex_to_rgba() {
-        let actual: Rgba = serde_json::from_value(json!("#f09f")).unwrap();
-
-        assert_eq!(actual, rgba(0xff0099ff))
-    }
-
-    #[test]
-    fn test_deserialize_six_value_hex_to_rgba() {
-        let actual: Rgba = serde_json::from_value(json!("#ff0099")).unwrap();
-
-        assert_eq!(actual, rgba(0xff0099ff))
-    }
-
-    #[test]
-    fn test_deserialize_eight_value_hex_to_rgba() {
-        let actual: Rgba = serde_json::from_value(json!("#ff0099ff")).unwrap();
-
-        assert_eq!(actual, rgba(0xff0099ff))
-    }
-
-    #[test]
-    fn test_deserialize_eight_value_hex_with_padding_to_rgba() {
-        let actual: Rgba = serde_json::from_value(json!(" #f5f5f5ff   ")).unwrap();
-
-        assert_eq!(actual, rgba(0xf5f5f5ff))
-    }
-
-    #[test]
-    fn test_deserialize_eight_value_hex_with_mixed_case_to_rgba() {
-        let actual: Rgba = serde_json::from_value(json!("#DeAdbEeF")).unwrap();
-
-        assert_eq!(actual, rgba(0xdeadbeef))
-    }
-}

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

@@ -1,54 +0,0 @@
-use refineable::Refineable as _;
-
-use crate::{Bounds, Element, IntoElement, Pixels, Style, StyleRefinement, Styled, WindowContext};
-
-pub fn canvas(callback: impl 'static + FnOnce(&Bounds<Pixels>, &mut WindowContext)) -> Canvas {
-    Canvas {
-        paint_callback: Some(Box::new(callback)),
-        style: StyleRefinement::default(),
-    }
-}
-
-pub struct Canvas {
-    paint_callback: Option<Box<dyn FnOnce(&Bounds<Pixels>, &mut WindowContext)>>,
-    style: StyleRefinement,
-}
-
-impl IntoElement for Canvas {
-    type Element = Self;
-
-    fn element_id(&self) -> Option<crate::ElementId> {
-        None
-    }
-
-    fn into_element(self) -> Self::Element {
-        self
-    }
-}
-
-impl Element for Canvas {
-    type State = Style;
-
-    fn request_layout(
-        &mut self,
-        _: Option<Self::State>,
-        cx: &mut WindowContext,
-    ) -> (crate::LayoutId, Self::State) {
-        let mut style = Style::default();
-        style.refine(&self.style);
-        let layout_id = cx.request_layout(&style, []);
-        (layout_id, style)
-    }
-
-    fn paint(&mut self, bounds: Bounds<Pixels>, style: &mut Style, cx: &mut WindowContext) {
-        style.paint(bounds, cx, |cx| {
-            (self.paint_callback.take().unwrap())(&bounds, cx)
-        });
-    }
-}
-
-impl Styled for Canvas {
-    fn style(&mut self) -> &mut crate::StyleRefinement {
-        &mut self.style
-    }
-}

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

@@ -1,560 +0,0 @@
-use crate::{
-    point, px, AnyElement, AvailableSpace, BorrowAppContext, Bounds, DispatchPhase, Element,
-    IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style, StyleRefinement, Styled,
-    WindowContext,
-};
-use collections::VecDeque;
-use refineable::Refineable as _;
-use std::{cell::RefCell, ops::Range, rc::Rc};
-use sum_tree::{Bias, SumTree};
-
-pub fn list(state: ListState) -> List {
-    List {
-        state,
-        style: StyleRefinement::default(),
-    }
-}
-
-pub struct List {
-    state: ListState,
-    style: StyleRefinement,
-}
-
-#[derive(Clone)]
-pub struct ListState(Rc<RefCell<StateInner>>);
-
-struct StateInner {
-    last_layout_bounds: Option<Bounds<Pixels>>,
-    render_item: Box<dyn FnMut(usize, &mut WindowContext) -> AnyElement>,
-    items: SumTree<ListItem>,
-    logical_scroll_top: Option<ListOffset>,
-    alignment: ListAlignment,
-    overdraw: Pixels,
-    #[allow(clippy::type_complexity)]
-    scroll_handler: Option<Box<dyn FnMut(&ListScrollEvent, &mut WindowContext)>>,
-}
-
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub enum ListAlignment {
-    Top,
-    Bottom,
-}
-
-pub struct ListScrollEvent {
-    pub visible_range: Range<usize>,
-    pub count: usize,
-}
-
-#[derive(Clone)]
-enum ListItem {
-    Unrendered,
-    Rendered { height: Pixels },
-}
-
-#[derive(Clone, Debug, Default, PartialEq)]
-struct ListItemSummary {
-    count: usize,
-    rendered_count: usize,
-    unrendered_count: usize,
-    height: Pixels,
-}
-
-#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
-struct Count(usize);
-
-#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
-struct RenderedCount(usize);
-
-#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
-struct UnrenderedCount(usize);
-
-#[derive(Clone, Debug, Default)]
-struct Height(Pixels);
-
-impl ListState {
-    pub fn new<F>(
-        element_count: usize,
-        orientation: ListAlignment,
-        overdraw: Pixels,
-        render_item: F,
-    ) -> Self
-    where
-        F: 'static + FnMut(usize, &mut WindowContext) -> AnyElement,
-    {
-        let mut items = SumTree::new();
-        items.extend((0..element_count).map(|_| ListItem::Unrendered), &());
-        Self(Rc::new(RefCell::new(StateInner {
-            last_layout_bounds: None,
-            render_item: Box::new(render_item),
-            items,
-            logical_scroll_top: None,
-            alignment: orientation,
-            overdraw,
-            scroll_handler: None,
-        })))
-    }
-
-    pub fn reset(&self, element_count: usize) {
-        let state = &mut *self.0.borrow_mut();
-        state.logical_scroll_top = None;
-        state.items = SumTree::new();
-        state
-            .items
-            .extend((0..element_count).map(|_| ListItem::Unrendered), &());
-    }
-
-    pub fn item_count(&self) -> usize {
-        self.0.borrow().items.summary().count
-    }
-
-    pub fn splice(&self, old_range: Range<usize>, count: usize) {
-        let state = &mut *self.0.borrow_mut();
-
-        if let Some(ListOffset {
-            item_ix,
-            offset_in_item,
-        }) = state.logical_scroll_top.as_mut()
-        {
-            if old_range.contains(item_ix) {
-                *item_ix = old_range.start;
-                *offset_in_item = px(0.);
-            } else if old_range.end <= *item_ix {
-                *item_ix = *item_ix - (old_range.end - old_range.start) + count;
-            }
-        }
-
-        let mut old_heights = state.items.cursor::<Count>();
-        let mut new_heights = old_heights.slice(&Count(old_range.start), Bias::Right, &());
-        old_heights.seek_forward(&Count(old_range.end), Bias::Right, &());
-
-        new_heights.extend((0..count).map(|_| ListItem::Unrendered), &());
-        new_heights.append(old_heights.suffix(&()), &());
-        drop(old_heights);
-        state.items = new_heights;
-    }
-
-    pub fn set_scroll_handler(
-        &self,
-        handler: impl FnMut(&ListScrollEvent, &mut WindowContext) + 'static,
-    ) {
-        self.0.borrow_mut().scroll_handler = Some(Box::new(handler))
-    }
-
-    pub fn logical_scroll_top(&self) -> ListOffset {
-        self.0.borrow().logical_scroll_top()
-    }
-
-    pub fn scroll_to(&self, mut scroll_top: ListOffset) {
-        let state = &mut *self.0.borrow_mut();
-        let item_count = state.items.summary().count;
-        if scroll_top.item_ix >= item_count {
-            scroll_top.item_ix = item_count;
-            scroll_top.offset_in_item = px(0.);
-        }
-        state.logical_scroll_top = Some(scroll_top);
-    }
-
-    pub fn scroll_to_reveal_item(&self, ix: usize) {
-        let state = &mut *self.0.borrow_mut();
-        let mut scroll_top = state.logical_scroll_top();
-        let height = state
-            .last_layout_bounds
-            .map_or(px(0.), |bounds| bounds.size.height);
-
-        if ix <= scroll_top.item_ix {
-            scroll_top.item_ix = ix;
-            scroll_top.offset_in_item = px(0.);
-        } else {
-            let mut cursor = state.items.cursor::<ListItemSummary>();
-            cursor.seek(&Count(ix + 1), Bias::Right, &());
-            let bottom = cursor.start().height;
-            let goal_top = px(0.).max(bottom - height);
-
-            cursor.seek(&Height(goal_top), Bias::Left, &());
-            let start_ix = cursor.start().count;
-            let start_item_top = cursor.start().height;
-
-            if start_ix >= scroll_top.item_ix {
-                scroll_top.item_ix = start_ix;
-                scroll_top.offset_in_item = goal_top - start_item_top;
-            }
-        }
-
-        state.logical_scroll_top = Some(scroll_top);
-    }
-
-    /// Get the bounds for the given item in window coordinates.
-    pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
-        let state = &*self.0.borrow();
-        let bounds = state.last_layout_bounds.unwrap_or_default();
-        let scroll_top = state.logical_scroll_top();
-
-        if ix < scroll_top.item_ix {
-            return None;
-        }
-
-        let mut cursor = state.items.cursor::<(Count, Height)>();
-        cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
-
-        let scroll_top = cursor.start().1 .0 + scroll_top.offset_in_item;
-
-        cursor.seek_forward(&Count(ix), Bias::Right, &());
-        if let Some(&ListItem::Rendered { height }) = cursor.item() {
-            let &(Count(count), Height(top)) = cursor.start();
-            if count == ix {
-                let top = bounds.top() + top - scroll_top;
-                return Some(Bounds::from_corners(
-                    point(bounds.left(), top),
-                    point(bounds.right(), top + height),
-                ));
-            }
-        }
-        None
-    }
-}
-
-impl StateInner {
-    fn visible_range(&self, height: Pixels, scroll_top: &ListOffset) -> Range<usize> {
-        let mut cursor = self.items.cursor::<ListItemSummary>();
-        cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
-        let start_y = cursor.start().height + scroll_top.offset_in_item;
-        cursor.seek_forward(&Height(start_y + height), Bias::Left, &());
-        scroll_top.item_ix..cursor.start().count + 1
-    }
-
-    fn scroll(
-        &mut self,
-        scroll_top: &ListOffset,
-        height: Pixels,
-        delta: Point<Pixels>,
-        cx: &mut WindowContext,
-    ) {
-        let scroll_max = (self.items.summary().height - height).max(px(0.));
-        let new_scroll_top = (self.scroll_top(scroll_top) - delta.y)
-            .max(px(0.))
-            .min(scroll_max);
-
-        if self.alignment == ListAlignment::Bottom && new_scroll_top == scroll_max {
-            self.logical_scroll_top = None;
-        } else {
-            let mut cursor = self.items.cursor::<ListItemSummary>();
-            cursor.seek(&Height(new_scroll_top), Bias::Right, &());
-            let item_ix = cursor.start().count;
-            let offset_in_item = new_scroll_top - cursor.start().height;
-            self.logical_scroll_top = Some(ListOffset {
-                item_ix,
-                offset_in_item,
-            });
-        }
-
-        if self.scroll_handler.is_some() {
-            let visible_range = self.visible_range(height, scroll_top);
-            self.scroll_handler.as_mut().unwrap()(
-                &ListScrollEvent {
-                    visible_range,
-                    count: self.items.summary().count,
-                },
-                cx,
-            );
-        }
-
-        cx.notify();
-    }
-
-    fn logical_scroll_top(&self) -> ListOffset {
-        self.logical_scroll_top
-            .unwrap_or_else(|| match self.alignment {
-                ListAlignment::Top => ListOffset {
-                    item_ix: 0,
-                    offset_in_item: px(0.),
-                },
-                ListAlignment::Bottom => ListOffset {
-                    item_ix: self.items.summary().count,
-                    offset_in_item: px(0.),
-                },
-            })
-    }
-
-    fn scroll_top(&self, logical_scroll_top: &ListOffset) -> Pixels {
-        let mut cursor = self.items.cursor::<ListItemSummary>();
-        cursor.seek(&Count(logical_scroll_top.item_ix), Bias::Right, &());
-        cursor.start().height + logical_scroll_top.offset_in_item
-    }
-}
-
-impl std::fmt::Debug for ListItem {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            Self::Unrendered => write!(f, "Unrendered"),
-            Self::Rendered { height, .. } => {
-                f.debug_struct("Rendered").field("height", height).finish()
-            }
-        }
-    }
-}
-
-#[derive(Debug, Clone, Copy, Default)]
-pub struct ListOffset {
-    pub item_ix: usize,
-    pub offset_in_item: Pixels,
-}
-
-impl Element for List {
-    type State = ();
-
-    fn request_layout(
-        &mut self,
-        _state: Option<Self::State>,
-        cx: &mut crate::WindowContext,
-    ) -> (crate::LayoutId, Self::State) {
-        let mut style = Style::default();
-        style.refine(&self.style);
-        let layout_id = cx.with_text_style(style.text_style().cloned(), |cx| {
-            cx.request_layout(&style, None)
-        });
-        (layout_id, ())
-    }
-
-    fn paint(
-        &mut self,
-        bounds: crate::Bounds<crate::Pixels>,
-        _state: &mut Self::State,
-        cx: &mut crate::WindowContext,
-    ) {
-        let state = &mut *self.state.0.borrow_mut();
-
-        // If the width of the list has changed, invalidate all cached item heights
-        if state.last_layout_bounds.map_or(true, |last_bounds| {
-            last_bounds.size.width != bounds.size.width
-        }) {
-            state.items = SumTree::from_iter(
-                (0..state.items.summary().count).map(|_| ListItem::Unrendered),
-                &(),
-            )
-        }
-
-        let old_items = state.items.clone();
-        let mut measured_items = VecDeque::new();
-        let mut item_elements = VecDeque::new();
-        let mut rendered_height = px(0.);
-        let mut scroll_top = state.logical_scroll_top();
-
-        let available_item_space = Size {
-            width: AvailableSpace::Definite(bounds.size.width),
-            height: AvailableSpace::MinContent,
-        };
-
-        // Render items after the scroll top, including those in the trailing overdraw
-        let mut cursor = old_items.cursor::<Count>();
-        cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
-        for (ix, item) in cursor.by_ref().enumerate() {
-            let visible_height = rendered_height - scroll_top.offset_in_item;
-            if visible_height >= bounds.size.height + state.overdraw {
-                break;
-            }
-
-            // Use the previously cached height if available
-            let mut height = if let ListItem::Rendered { height } = item {
-                Some(*height)
-            } else {
-                None
-            };
-
-            // If we're within the visible area or the height wasn't cached, render and measure the item's element
-            if visible_height < bounds.size.height || height.is_none() {
-                let mut element = (state.render_item)(scroll_top.item_ix + ix, cx);
-                let element_size = element.measure(available_item_space, cx);
-                height = Some(element_size.height);
-                if visible_height < bounds.size.height {
-                    item_elements.push_back(element);
-                }
-            }
-
-            let height = height.unwrap();
-            rendered_height += height;
-            measured_items.push_back(ListItem::Rendered { height });
-        }
-
-        // Prepare to start walking upward from the item at the scroll top.
-        cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
-
-        // If the rendered items do not fill the visible region, then adjust
-        // the scroll top upward.
-        if rendered_height - scroll_top.offset_in_item < bounds.size.height {
-            while rendered_height < bounds.size.height {
-                cursor.prev(&());
-                if cursor.item().is_some() {
-                    let mut element = (state.render_item)(cursor.start().0, cx);
-                    let element_size = element.measure(available_item_space, cx);
-
-                    rendered_height += element_size.height;
-                    measured_items.push_front(ListItem::Rendered {
-                        height: element_size.height,
-                    });
-                    item_elements.push_front(element)
-                } else {
-                    break;
-                }
-            }
-
-            scroll_top = ListOffset {
-                item_ix: cursor.start().0,
-                offset_in_item: rendered_height - bounds.size.height,
-            };
-
-            match state.alignment {
-                ListAlignment::Top => {
-                    scroll_top.offset_in_item = scroll_top.offset_in_item.max(px(0.));
-                    state.logical_scroll_top = Some(scroll_top);
-                }
-                ListAlignment::Bottom => {
-                    scroll_top = ListOffset {
-                        item_ix: cursor.start().0,
-                        offset_in_item: rendered_height - bounds.size.height,
-                    };
-                    state.logical_scroll_top = None;
-                }
-            };
-        }
-
-        // Measure items in the leading overdraw
-        let mut leading_overdraw = scroll_top.offset_in_item;
-        while leading_overdraw < state.overdraw {
-            cursor.prev(&());
-            if let Some(item) = cursor.item() {
-                let height = if let ListItem::Rendered { height } = item {
-                    *height
-                } else {
-                    let mut element = (state.render_item)(cursor.start().0, cx);
-                    element.measure(available_item_space, cx).height
-                };
-
-                leading_overdraw += height;
-                measured_items.push_front(ListItem::Rendered { height });
-            } else {
-                break;
-            }
-        }
-
-        let measured_range = cursor.start().0..(cursor.start().0 + measured_items.len());
-        let mut cursor = old_items.cursor::<Count>();
-        let mut new_items = cursor.slice(&Count(measured_range.start), Bias::Right, &());
-        new_items.extend(measured_items, &());
-        cursor.seek(&Count(measured_range.end), Bias::Right, &());
-        new_items.append(cursor.suffix(&()), &());
-
-        // Paint the visible items
-        let mut item_origin = bounds.origin;
-        item_origin.y -= scroll_top.offset_in_item;
-        for item_element in &mut item_elements {
-            let item_height = item_element.measure(available_item_space, cx).height;
-            item_element.draw(item_origin, available_item_space, cx);
-            item_origin.y += item_height;
-        }
-
-        state.items = new_items;
-        state.last_layout_bounds = Some(bounds);
-
-        let list_state = self.state.clone();
-        let height = bounds.size.height;
-        cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
-            if phase == DispatchPhase::Bubble
-                && bounds.contains(&event.position)
-                && cx.was_top_layer(&event.position, cx.stacking_order())
-            {
-                list_state.0.borrow_mut().scroll(
-                    &scroll_top,
-                    height,
-                    event.delta.pixel_delta(px(20.)),
-                    cx,
-                )
-            }
-        });
-    }
-}
-
-impl IntoElement for List {
-    type Element = Self;
-
-    fn element_id(&self) -> Option<crate::ElementId> {
-        None
-    }
-
-    fn into_element(self) -> Self::Element {
-        self
-    }
-}
-
-impl Styled for List {
-    fn style(&mut self) -> &mut StyleRefinement {
-        &mut self.style
-    }
-}
-
-impl sum_tree::Item for ListItem {
-    type Summary = ListItemSummary;
-
-    fn summary(&self) -> Self::Summary {
-        match self {
-            ListItem::Unrendered => ListItemSummary {
-                count: 1,
-                rendered_count: 0,
-                unrendered_count: 1,
-                height: px(0.),
-            },
-            ListItem::Rendered { height } => ListItemSummary {
-                count: 1,
-                rendered_count: 1,
-                unrendered_count: 0,
-                height: *height,
-            },
-        }
-    }
-}
-
-impl sum_tree::Summary for ListItemSummary {
-    type Context = ();
-
-    fn add_summary(&mut self, summary: &Self, _: &()) {
-        self.count += summary.count;
-        self.rendered_count += summary.rendered_count;
-        self.unrendered_count += summary.unrendered_count;
-        self.height += summary.height;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, ListItemSummary> for Count {
-    fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
-        self.0 += summary.count;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, ListItemSummary> for RenderedCount {
-    fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
-        self.0 += summary.rendered_count;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, ListItemSummary> for UnrenderedCount {
-    fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
-        self.0 += summary.unrendered_count;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, ListItemSummary> for Height {
-    fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
-        self.0 += summary.height;
-    }
-}
-
-impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Count {
-    fn cmp(&self, other: &ListItemSummary, _: &()) -> std::cmp::Ordering {
-        self.0.partial_cmp(&other.count).unwrap()
-    }
-}
-
-impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Height {
-    fn cmp(&self, other: &ListItemSummary, _: &()) -> std::cmp::Ordering {
-        self.0.partial_cmp(&other.height).unwrap()
-    }
-}

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

@@ -1,241 +0,0 @@
-use smallvec::SmallVec;
-use taffy::style::{Display, Position};
-
-use crate::{
-    point, AnyElement, BorrowWindow, Bounds, Element, IntoElement, LayoutId, ParentElement, Pixels,
-    Point, Size, Style, WindowContext,
-};
-
-pub struct OverlayState {
-    child_layout_ids: SmallVec<[LayoutId; 4]>,
-}
-
-pub struct Overlay {
-    children: SmallVec<[AnyElement; 2]>,
-    anchor_corner: AnchorCorner,
-    fit_mode: OverlayFitMode,
-    // todo!();
-    anchor_position: Option<Point<Pixels>>,
-    // position_mode: OverlayPositionMode,
-}
-
-/// overlay gives you a floating element that will avoid overflowing the window bounds.
-/// Its children should have no margin to avoid measurement issues.
-pub fn overlay() -> Overlay {
-    Overlay {
-        children: SmallVec::new(),
-        anchor_corner: AnchorCorner::TopLeft,
-        fit_mode: OverlayFitMode::SwitchAnchor,
-        anchor_position: None,
-    }
-}
-
-impl Overlay {
-    /// Sets which corner of the overlay should be anchored to the current position.
-    pub fn anchor(mut self, anchor: AnchorCorner) -> Self {
-        self.anchor_corner = anchor;
-        self
-    }
-
-    /// Sets the position in window co-ordinates
-    /// (otherwise the location the overlay is rendered is used)
-    pub fn position(mut self, anchor: Point<Pixels>) -> Self {
-        self.anchor_position = Some(anchor);
-        self
-    }
-
-    /// Snap to window edge instead of switching anchor corner when an overflow would occur.
-    pub fn snap_to_window(mut self) -> Self {
-        self.fit_mode = OverlayFitMode::SnapToWindow;
-        self
-    }
-}
-
-impl ParentElement for Overlay {
-    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
-        &mut self.children
-    }
-}
-
-impl Element for Overlay {
-    type State = OverlayState;
-
-    fn request_layout(
-        &mut self,
-        _: Option<Self::State>,
-        cx: &mut WindowContext,
-    ) -> (crate::LayoutId, Self::State) {
-        let child_layout_ids = self
-            .children
-            .iter_mut()
-            .map(|child| child.request_layout(cx))
-            .collect::<SmallVec<_>>();
-
-        let overlay_style = Style {
-            position: Position::Absolute,
-            display: Display::Flex,
-            ..Style::default()
-        };
-
-        let layout_id = cx.request_layout(&overlay_style, child_layout_ids.iter().copied());
-
-        (layout_id, OverlayState { child_layout_ids })
-    }
-
-    fn paint(
-        &mut self,
-        bounds: crate::Bounds<crate::Pixels>,
-        element_state: &mut Self::State,
-        cx: &mut WindowContext,
-    ) {
-        if element_state.child_layout_ids.is_empty() {
-            return;
-        }
-
-        let mut child_min = point(Pixels::MAX, Pixels::MAX);
-        let mut child_max = Point::default();
-        for child_layout_id in &element_state.child_layout_ids {
-            let child_bounds = cx.layout_bounds(*child_layout_id);
-            child_min = child_min.min(&child_bounds.origin);
-            child_max = child_max.max(&child_bounds.lower_right());
-        }
-        let size: Size<Pixels> = (child_max - child_min).into();
-        let origin = self.anchor_position.unwrap_or(bounds.origin);
-
-        let mut desired = self.anchor_corner.get_bounds(origin, size);
-        let limits = Bounds {
-            origin: Point::default(),
-            size: cx.viewport_size(),
-        };
-
-        if self.fit_mode == OverlayFitMode::SwitchAnchor {
-            let mut anchor_corner = self.anchor_corner;
-
-            if desired.left() < limits.left() || desired.right() > limits.right() {
-                let switched = anchor_corner
-                    .switch_axis(Axis::Horizontal)
-                    .get_bounds(origin, size);
-                if !(switched.left() < limits.left() || switched.right() > limits.right()) {
-                    anchor_corner = anchor_corner.switch_axis(Axis::Horizontal);
-                    desired = switched
-                }
-            }
-
-            if desired.top() < limits.top() || desired.bottom() > limits.bottom() {
-                let switched = anchor_corner
-                    .switch_axis(Axis::Vertical)
-                    .get_bounds(origin, size);
-                if !(switched.top() < limits.top() || switched.bottom() > limits.bottom()) {
-                    desired = switched;
-                }
-            }
-        }
-
-        // Snap the horizontal edges of the overlay to the horizontal edges of the window if
-        // its horizontal bounds overflow, aligning to the left if it is wider than the limits.
-        if desired.right() > limits.right() {
-            desired.origin.x -= desired.right() - limits.right();
-        }
-        if desired.left() < limits.left() {
-            desired.origin.x = limits.origin.x;
-        }
-
-        // Snap the vertical edges of the overlay to the vertical edges of the window if
-        // its vertical bounds overflow, aligning to the top if it is taller than the limits.
-        if desired.bottom() > limits.bottom() {
-            desired.origin.y -= desired.bottom() - limits.bottom();
-        }
-        if desired.top() < limits.top() {
-            desired.origin.y = limits.origin.y;
-        }
-
-        let mut offset = cx.element_offset() + desired.origin - bounds.origin;
-        offset = point(offset.x.round(), offset.y.round());
-        cx.with_absolute_element_offset(offset, |cx| {
-            cx.break_content_mask(|cx| {
-                for child in &mut self.children {
-                    child.paint(cx);
-                }
-            })
-        })
-    }
-}
-
-impl IntoElement for Overlay {
-    type Element = Self;
-
-    fn element_id(&self) -> Option<crate::ElementId> {
-        None
-    }
-
-    fn into_element(self) -> Self::Element {
-        self
-    }
-}
-
-enum Axis {
-    Horizontal,
-    Vertical,
-}
-
-#[derive(Copy, Clone, PartialEq)]
-pub enum OverlayFitMode {
-    SnapToWindow,
-    SwitchAnchor,
-}
-
-#[derive(Clone, Copy, PartialEq, Eq)]
-pub enum AnchorCorner {
-    TopLeft,
-    TopRight,
-    BottomLeft,
-    BottomRight,
-}
-
-impl AnchorCorner {
-    fn get_bounds(&self, origin: Point<Pixels>, size: Size<Pixels>) -> Bounds<Pixels> {
-        let origin = match self {
-            Self::TopLeft => origin,
-            Self::TopRight => Point {
-                x: origin.x - size.width,
-                y: origin.y,
-            },
-            Self::BottomLeft => Point {
-                x: origin.x,
-                y: origin.y - size.height,
-            },
-            Self::BottomRight => Point {
-                x: origin.x - size.width,
-                y: origin.y - size.height,
-            },
-        };
-
-        Bounds { origin, size }
-    }
-
-    pub fn corner(&self, bounds: Bounds<Pixels>) -> Point<Pixels> {
-        match self {
-            Self::TopLeft => bounds.origin,
-            Self::TopRight => bounds.upper_right(),
-            Self::BottomLeft => bounds.lower_left(),
-            Self::BottomRight => bounds.lower_right(),
-        }
-    }
-
-    fn switch_axis(self, axis: Axis) -> Self {
-        match axis {
-            Axis::Vertical => match self {
-                AnchorCorner::TopLeft => AnchorCorner::BottomLeft,
-                AnchorCorner::TopRight => AnchorCorner::BottomRight,
-                AnchorCorner::BottomLeft => AnchorCorner::TopLeft,
-                AnchorCorner::BottomRight => AnchorCorner::TopRight,
-            },
-            Axis::Horizontal => match self {
-                AnchorCorner::TopLeft => AnchorCorner::TopRight,
-                AnchorCorner::TopRight => AnchorCorner::TopLeft,
-                AnchorCorner::BottomLeft => AnchorCorner::BottomRight,
-                AnchorCorner::BottomRight => AnchorCorner::BottomLeft,
-            },
-        }
-    }
-}

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

@@ -1,78 +0,0 @@
-use crate::{
-    Bounds, Element, ElementId, InteractiveElement, InteractiveElementState, Interactivity,
-    IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled, WindowContext,
-};
-use util::ResultExt;
-
-pub struct Svg {
-    interactivity: Interactivity,
-    path: Option<SharedString>,
-}
-
-pub fn svg() -> Svg {
-    Svg {
-        interactivity: Interactivity::default(),
-        path: None,
-    }
-}
-
-impl Svg {
-    pub fn path(mut self, path: impl Into<SharedString>) -> Self {
-        self.path = Some(path.into());
-        self
-    }
-}
-
-impl Element for Svg {
-    type State = InteractiveElementState;
-
-    fn request_layout(
-        &mut self,
-        element_state: Option<Self::State>,
-        cx: &mut WindowContext,
-    ) -> (LayoutId, Self::State) {
-        self.interactivity.layout(element_state, cx, |style, cx| {
-            cx.request_layout(&style, None)
-        })
-    }
-
-    fn paint(
-        &mut self,
-        bounds: Bounds<Pixels>,
-        element_state: &mut Self::State,
-        cx: &mut WindowContext,
-    ) where
-        Self: Sized,
-    {
-        self.interactivity
-            .paint(bounds, bounds.size, element_state, cx, |style, _, cx| {
-                if let Some((path, color)) = self.path.as_ref().zip(style.text.color) {
-                    cx.paint_svg(bounds, path.clone(), color).log_err();
-                }
-            })
-    }
-}
-
-impl IntoElement for Svg {
-    type Element = Self;
-
-    fn element_id(&self) -> Option<ElementId> {
-        self.interactivity.element_id.clone()
-    }
-
-    fn into_element(self) -> Self::Element {
-        self
-    }
-}
-
-impl Styled for Svg {
-    fn style(&mut self) -> &mut StyleRefinement {
-        &mut self.interactivity.base_style
-    }
-}
-
-impl InteractiveElement for Svg {
-    fn interactivity(&mut self) -> &mut Interactivity {
-        &mut self.interactivity
-    }
-}

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

@@ -1,423 +0,0 @@
-use crate::{
-    Bounds, DispatchPhase, Element, ElementId, HighlightStyle, IntoElement, LayoutId,
-    MouseDownEvent, MouseUpEvent, Pixels, Point, SharedString, Size, TextRun, TextStyle,
-    WhiteSpace, WindowContext, WrappedLine,
-};
-use anyhow::anyhow;
-use parking_lot::{Mutex, MutexGuard};
-use smallvec::SmallVec;
-use std::{cell::Cell, mem, ops::Range, rc::Rc, sync::Arc};
-use util::ResultExt;
-
-impl Element for &'static str {
-    type State = TextState;
-
-    fn request_layout(
-        &mut self,
-        _: Option<Self::State>,
-        cx: &mut WindowContext,
-    ) -> (LayoutId, Self::State) {
-        let mut state = TextState::default();
-        let layout_id = state.layout(SharedString::from(*self), None, cx);
-        (layout_id, state)
-    }
-
-    fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut WindowContext) {
-        state.paint(bounds, self, cx)
-    }
-}
-
-impl IntoElement for &'static str {
-    type Element = Self;
-
-    fn element_id(&self) -> Option<ElementId> {
-        None
-    }
-
-    fn into_element(self) -> Self::Element {
-        self
-    }
-}
-
-impl Element for SharedString {
-    type State = TextState;
-
-    fn request_layout(
-        &mut self,
-        _: Option<Self::State>,
-        cx: &mut WindowContext,
-    ) -> (LayoutId, Self::State) {
-        let mut state = TextState::default();
-        let layout_id = state.layout(self.clone(), None, cx);
-        (layout_id, state)
-    }
-
-    fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut WindowContext) {
-        let text_str: &str = self.as_ref();
-        state.paint(bounds, text_str, cx)
-    }
-}
-
-impl IntoElement for SharedString {
-    type Element = Self;
-
-    fn element_id(&self) -> Option<ElementId> {
-        None
-    }
-
-    fn into_element(self) -> Self::Element {
-        self
-    }
-}
-
-/// Renders text with runs of different styles.
-///
-/// Callers are responsible for setting the correct style for each run.
-/// For text with a uniform style, you can usually avoid calling this constructor
-/// and just pass text directly.
-pub struct StyledText {
-    text: SharedString,
-    runs: Option<Vec<TextRun>>,
-}
-
-impl StyledText {
-    pub fn new(text: impl Into<SharedString>) -> Self {
-        StyledText {
-            text: text.into(),
-            runs: None,
-        }
-    }
-
-    pub fn with_highlights(
-        mut self,
-        default_style: &TextStyle,
-        highlights: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
-    ) -> Self {
-        let mut runs = Vec::new();
-        let mut ix = 0;
-        for (range, highlight) in highlights {
-            if ix < range.start {
-                runs.push(default_style.clone().to_run(range.start - ix));
-            }
-            runs.push(
-                default_style
-                    .clone()
-                    .highlight(highlight)
-                    .to_run(range.len()),
-            );
-            ix = range.end;
-        }
-        if ix < self.text.len() {
-            runs.push(default_style.to_run(self.text.len() - ix));
-        }
-        self.runs = Some(runs);
-        self
-    }
-}
-
-impl Element for StyledText {
-    type State = TextState;
-
-    fn request_layout(
-        &mut self,
-        _: Option<Self::State>,
-        cx: &mut WindowContext,
-    ) -> (LayoutId, Self::State) {
-        let mut state = TextState::default();
-        let layout_id = state.layout(self.text.clone(), self.runs.take(), cx);
-        (layout_id, state)
-    }
-
-    fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
-        state.paint(bounds, &self.text, cx)
-    }
-}
-
-impl IntoElement for StyledText {
-    type Element = Self;
-
-    fn element_id(&self) -> Option<crate::ElementId> {
-        None
-    }
-
-    fn into_element(self) -> Self::Element {
-        self
-    }
-}
-
-#[derive(Default, Clone)]
-pub struct TextState(Arc<Mutex<Option<TextStateInner>>>);
-
-struct TextStateInner {
-    lines: SmallVec<[WrappedLine; 1]>,
-    line_height: Pixels,
-    wrap_width: Option<Pixels>,
-    size: Option<Size<Pixels>>,
-}
-
-impl TextState {
-    fn lock(&self) -> MutexGuard<Option<TextStateInner>> {
-        self.0.lock()
-    }
-
-    fn layout(
-        &mut self,
-        text: SharedString,
-        runs: Option<Vec<TextRun>>,
-        cx: &mut WindowContext,
-    ) -> LayoutId {
-        let text_style = cx.text_style();
-        let font_size = text_style.font_size.to_pixels(cx.rem_size());
-        let line_height = text_style
-            .line_height
-            .to_pixels(font_size.into(), cx.rem_size());
-
-        let runs = if let Some(runs) = runs {
-            runs
-        } else {
-            vec![text_style.to_run(text.len())]
-        };
-
-        let layout_id = cx.request_measured_layout(Default::default(), {
-            let element_state = self.clone();
-
-            move |known_dimensions, available_space, cx| {
-                let wrap_width = if text_style.white_space == WhiteSpace::Normal {
-                    known_dimensions.width.or(match available_space.width {
-                        crate::AvailableSpace::Definite(x) => Some(x),
-                        _ => None,
-                    })
-                } else {
-                    None
-                };
-
-                if let Some(text_state) = element_state.0.lock().as_ref() {
-                    if text_state.size.is_some()
-                        && (wrap_width.is_none() || wrap_width == text_state.wrap_width)
-                    {
-                        return text_state.size.unwrap();
-                    }
-                }
-
-                let Some(lines) = cx
-                    .text_system()
-                    .shape_text(
-                        &text, font_size, &runs, wrap_width, // Wrap if we know the width.
-                    )
-                    .log_err()
-                else {
-                    element_state.lock().replace(TextStateInner {
-                        lines: Default::default(),
-                        line_height,
-                        wrap_width,
-                        size: Some(Size::default()),
-                    });
-                    return Size::default();
-                };
-
-                let mut size: Size<Pixels> = Size::default();
-                for line in &lines {
-                    let line_size = line.size(line_height);
-                    size.height += line_size.height;
-                    size.width = size.width.max(line_size.width).ceil();
-                }
-
-                element_state.lock().replace(TextStateInner {
-                    lines,
-                    line_height,
-                    wrap_width,
-                    size: Some(size),
-                });
-
-                size
-            }
-        });
-
-        layout_id
-    }
-
-    fn paint(&mut self, bounds: Bounds<Pixels>, text: &str, cx: &mut WindowContext) {
-        let element_state = self.lock();
-        let element_state = element_state
-            .as_ref()
-            .ok_or_else(|| anyhow!("measurement has not been performed on {}", text))
-            .unwrap();
-
-        let line_height = element_state.line_height;
-        let mut line_origin = bounds.origin;
-        for line in &element_state.lines {
-            line.paint(line_origin, line_height, cx).log_err();
-            line_origin.y += line.size(line_height).height;
-        }
-    }
-
-    fn index_for_position(&self, bounds: Bounds<Pixels>, position: Point<Pixels>) -> Option<usize> {
-        if !bounds.contains(&position) {
-            return None;
-        }
-
-        let element_state = self.lock();
-        let element_state = element_state
-            .as_ref()
-            .expect("measurement has not been performed");
-
-        let line_height = element_state.line_height;
-        let mut line_origin = bounds.origin;
-        let mut line_start_ix = 0;
-        for line in &element_state.lines {
-            let line_bottom = line_origin.y + line.size(line_height).height;
-            if position.y > line_bottom {
-                line_origin.y = line_bottom;
-                line_start_ix += line.len() + 1;
-            } else {
-                let position_within_line = position - line_origin;
-                let index_within_line =
-                    line.index_for_position(position_within_line, line_height)?;
-                return Some(line_start_ix + index_within_line);
-            }
-        }
-
-        None
-    }
-}
-
-pub struct InteractiveText {
-    element_id: ElementId,
-    text: StyledText,
-    click_listener:
-        Option<Box<dyn Fn(&[Range<usize>], InteractiveTextClickEvent, &mut WindowContext<'_>)>>,
-    clickable_ranges: Vec<Range<usize>>,
-}
-
-struct InteractiveTextClickEvent {
-    mouse_down_index: usize,
-    mouse_up_index: usize,
-}
-
-pub struct InteractiveTextState {
-    text_state: TextState,
-    mouse_down_index: Rc<Cell<Option<usize>>>,
-}
-
-impl InteractiveText {
-    pub fn new(id: impl Into<ElementId>, text: StyledText) -> Self {
-        Self {
-            element_id: id.into(),
-            text,
-            click_listener: None,
-            clickable_ranges: Vec::new(),
-        }
-    }
-
-    pub fn on_click(
-        mut self,
-        ranges: Vec<Range<usize>>,
-        listener: impl Fn(usize, &mut WindowContext<'_>) + 'static,
-    ) -> Self {
-        self.click_listener = Some(Box::new(move |ranges, event, cx| {
-            for (range_ix, range) in ranges.iter().enumerate() {
-                if range.contains(&event.mouse_down_index) && range.contains(&event.mouse_up_index)
-                {
-                    listener(range_ix, cx);
-                }
-            }
-        }));
-        self.clickable_ranges = ranges;
-        self
-    }
-}
-
-impl Element for InteractiveText {
-    type State = InteractiveTextState;
-
-    fn request_layout(
-        &mut self,
-        state: Option<Self::State>,
-        cx: &mut WindowContext,
-    ) -> (LayoutId, Self::State) {
-        if let Some(InteractiveTextState {
-            mouse_down_index, ..
-        }) = state
-        {
-            let (layout_id, text_state) = self.text.request_layout(None, cx);
-            let element_state = InteractiveTextState {
-                text_state,
-                mouse_down_index,
-            };
-            (layout_id, element_state)
-        } else {
-            let (layout_id, text_state) = self.text.request_layout(None, cx);
-            let element_state = InteractiveTextState {
-                text_state,
-                mouse_down_index: Rc::default(),
-            };
-            (layout_id, element_state)
-        }
-    }
-
-    fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
-        if let Some(click_listener) = self.click_listener.take() {
-            let mouse_position = cx.mouse_position();
-            if let Some(ix) = state.text_state.index_for_position(bounds, mouse_position) {
-                if self
-                    .clickable_ranges
-                    .iter()
-                    .any(|range| range.contains(&ix))
-                    && cx.was_top_layer(&mouse_position, cx.stacking_order())
-                {
-                    cx.set_cursor_style(crate::CursorStyle::PointingHand)
-                }
-            }
-
-            let text_state = state.text_state.clone();
-            let mouse_down = state.mouse_down_index.clone();
-            if let Some(mouse_down_index) = mouse_down.get() {
-                let clickable_ranges = mem::take(&mut self.clickable_ranges);
-                cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
-                    if phase == DispatchPhase::Bubble {
-                        if let Some(mouse_up_index) =
-                            text_state.index_for_position(bounds, event.position)
-                        {
-                            click_listener(
-                                &clickable_ranges,
-                                InteractiveTextClickEvent {
-                                    mouse_down_index,
-                                    mouse_up_index,
-                                },
-                                cx,
-                            )
-                        }
-
-                        mouse_down.take();
-                        cx.notify();
-                    }
-                });
-            } else {
-                cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
-                    if phase == DispatchPhase::Bubble {
-                        if let Some(mouse_down_index) =
-                            text_state.index_for_position(bounds, event.position)
-                        {
-                            mouse_down.set(Some(mouse_down_index));
-                            cx.notify();
-                        }
-                    }
-                });
-            }
-        }
-
-        self.text.paint(bounds, &mut state.text_state, cx)
-    }
-}
-
-impl IntoElement for InteractiveText {
-    type Element = Self;
-
-    fn element_id(&self) -> Option<ElementId> {
-        Some(self.element_id.clone())
-    }
-
-    fn into_element(self) -> Self::Element {
-        self
-    }
-}

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

@@ -1,316 +0,0 @@
-use crate::{
-    point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Element,
-    ElementId, InteractiveElement, InteractiveElementState, Interactivity, IntoElement, LayoutId,
-    Pixels, Point, Render, Size, StyleRefinement, Styled, View, ViewContext, WindowContext,
-};
-use smallvec::SmallVec;
-use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
-use taffy::style::Overflow;
-
-/// uniform_list provides lazy rendering for a set of items that are of uniform height.
-/// When rendered into a container with overflow-y: hidden and a fixed (or max) height,
-/// uniform_list will only render the visible subset of items.
-#[track_caller]
-pub fn uniform_list<I, R, V>(
-    view: View<V>,
-    id: I,
-    item_count: usize,
-    f: impl 'static + Fn(&mut V, Range<usize>, &mut ViewContext<V>) -> Vec<R>,
-) -> UniformList
-where
-    I: Into<ElementId>,
-    R: IntoElement,
-    V: Render,
-{
-    let id = id.into();
-    let mut base_style = StyleRefinement::default();
-    base_style.overflow.y = Some(Overflow::Scroll);
-
-    let render_range = move |range, cx: &mut WindowContext| {
-        view.update(cx, |this, cx| {
-            f(this, range, cx)
-                .into_iter()
-                .map(|component| component.into_any_element())
-                .collect()
-        })
-    };
-
-    UniformList {
-        id: id.clone(),
-        item_count,
-        item_to_measure_index: 0,
-        render_items: Box::new(render_range),
-        interactivity: Interactivity {
-            element_id: Some(id),
-            base_style: Box::new(base_style),
-
-            #[cfg(debug_assertions)]
-            location: Some(*core::panic::Location::caller()),
-
-            ..Default::default()
-        },
-        scroll_handle: None,
-    }
-}
-
-pub struct UniformList {
-    id: ElementId,
-    item_count: usize,
-    item_to_measure_index: usize,
-    render_items:
-        Box<dyn for<'a> Fn(Range<usize>, &'a mut WindowContext) -> SmallVec<[AnyElement; 64]>>,
-    interactivity: Interactivity,
-    scroll_handle: Option<UniformListScrollHandle>,
-}
-
-#[derive(Clone, Default)]
-pub struct UniformListScrollHandle(Rc<RefCell<Option<ScrollHandleState>>>);
-
-#[derive(Clone, Debug)]
-struct ScrollHandleState {
-    item_height: Pixels,
-    list_height: Pixels,
-    scroll_offset: Rc<RefCell<Point<Pixels>>>,
-}
-
-impl UniformListScrollHandle {
-    pub fn new() -> Self {
-        Self(Rc::new(RefCell::new(None)))
-    }
-
-    pub fn scroll_to_item(&self, ix: usize) {
-        if let Some(state) = &*self.0.borrow() {
-            let mut scroll_offset = state.scroll_offset.borrow_mut();
-            let item_top = state.item_height * ix;
-            let item_bottom = item_top + state.item_height;
-            let scroll_top = -scroll_offset.y;
-            if item_top < scroll_top {
-                scroll_offset.y = -item_top;
-            } else if item_bottom > scroll_top + state.list_height {
-                scroll_offset.y = -(item_bottom - state.list_height);
-            }
-        }
-    }
-
-    pub fn scroll_top(&self) -> Pixels {
-        if let Some(state) = &*self.0.borrow() {
-            -state.scroll_offset.borrow().y
-        } else {
-            Pixels::ZERO
-        }
-    }
-}
-
-impl Styled for UniformList {
-    fn style(&mut self) -> &mut StyleRefinement {
-        &mut self.interactivity.base_style
-    }
-}
-
-#[derive(Default)]
-pub struct UniformListState {
-    interactive: InteractiveElementState,
-    item_size: Size<Pixels>,
-}
-
-impl Element for UniformList {
-    type State = UniformListState;
-
-    fn request_layout(
-        &mut self,
-        state: Option<Self::State>,
-        cx: &mut WindowContext,
-    ) -> (LayoutId, Self::State) {
-        let max_items = self.item_count;
-        let item_size = state
-            .as_ref()
-            .map(|s| s.item_size)
-            .unwrap_or_else(|| self.measure_item(None, cx));
-
-        let (layout_id, interactive) =
-            self.interactivity
-                .layout(state.map(|s| s.interactive), cx, |style, cx| {
-                    cx.request_measured_layout(
-                        style,
-                        move |known_dimensions, available_space, _cx| {
-                            let desired_height = item_size.height * max_items;
-                            let width =
-                                known_dimensions
-                                    .width
-                                    .unwrap_or(match available_space.width {
-                                        AvailableSpace::Definite(x) => x,
-                                        AvailableSpace::MinContent | AvailableSpace::MaxContent => {
-                                            item_size.width
-                                        }
-                                    });
-
-                            let height = match available_space.height {
-                                AvailableSpace::Definite(height) => desired_height.min(height),
-                                AvailableSpace::MinContent | AvailableSpace::MaxContent => {
-                                    desired_height
-                                }
-                            };
-                            size(width, height)
-                        },
-                    )
-                });
-
-        let element_state = UniformListState {
-            interactive,
-            item_size,
-        };
-
-        (layout_id, element_state)
-    }
-
-    fn paint(
-        &mut self,
-        bounds: Bounds<crate::Pixels>,
-        element_state: &mut Self::State,
-        cx: &mut WindowContext,
-    ) {
-        let style =
-            self.interactivity
-                .compute_style(Some(bounds), &mut element_state.interactive, cx);
-        let border = style.border_widths.to_pixels(cx.rem_size());
-        let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
-
-        let padded_bounds = Bounds::from_corners(
-            bounds.origin + point(border.left + padding.left, border.top + padding.top),
-            bounds.lower_right()
-                - point(border.right + padding.right, border.bottom + padding.bottom),
-        );
-
-        let item_size = element_state.item_size;
-        let content_size = Size {
-            width: padded_bounds.size.width,
-            height: item_size.height * self.item_count + padding.top + padding.bottom,
-        };
-
-        let shared_scroll_offset = element_state
-            .interactive
-            .scroll_offset
-            .get_or_insert_with(|| {
-                if let Some(scroll_handle) = self.scroll_handle.as_ref() {
-                    if let Some(scroll_handle) = scroll_handle.0.borrow().as_ref() {
-                        return scroll_handle.scroll_offset.clone();
-                    }
-                }
-
-                Rc::default()
-            })
-            .clone();
-
-        let item_height = self.measure_item(Some(padded_bounds.size.width), cx).height;
-
-        self.interactivity.paint(
-            bounds,
-            content_size,
-            &mut element_state.interactive,
-            cx,
-            |style, mut scroll_offset, cx| {
-                let border = style.border_widths.to_pixels(cx.rem_size());
-                let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
-
-                let padded_bounds = Bounds::from_corners(
-                    bounds.origin + point(border.left + padding.left, border.top),
-                    bounds.lower_right() - point(border.right + padding.right, border.bottom),
-                );
-
-                if self.item_count > 0 {
-                    let content_height =
-                        item_height * self.item_count + padding.top + padding.bottom;
-                    let min_scroll_offset = padded_bounds.size.height - content_height;
-                    let is_scrolled = scroll_offset.y != px(0.);
-
-                    if is_scrolled && scroll_offset.y < min_scroll_offset {
-                        shared_scroll_offset.borrow_mut().y = min_scroll_offset;
-                        scroll_offset.y = min_scroll_offset;
-                    }
-
-                    if let Some(scroll_handle) = self.scroll_handle.clone() {
-                        scroll_handle.0.borrow_mut().replace(ScrollHandleState {
-                            item_height,
-                            list_height: padded_bounds.size.height,
-                            scroll_offset: shared_scroll_offset,
-                        });
-                    }
-
-                    let first_visible_element_ix =
-                        (-(scroll_offset.y + padding.top) / item_height).floor() as usize;
-                    let last_visible_element_ix = ((-scroll_offset.y + padded_bounds.size.height)
-                        / item_height)
-                        .ceil() as usize;
-                    let visible_range = first_visible_element_ix
-                        ..cmp::min(last_visible_element_ix, self.item_count);
-
-                    let mut items = (self.render_items)(visible_range.clone(), cx);
-                    cx.with_z_index(1, |cx| {
-                        let content_mask = ContentMask { bounds };
-                        cx.with_content_mask(Some(content_mask), |cx| {
-                            for (item, ix) in items.iter_mut().zip(visible_range) {
-                                let item_origin = padded_bounds.origin
-                                    + point(
-                                        px(0.),
-                                        item_height * ix + scroll_offset.y + padding.top,
-                                    );
-                                let available_space = size(
-                                    AvailableSpace::Definite(padded_bounds.size.width),
-                                    AvailableSpace::Definite(item_height),
-                                );
-                                item.draw(item_origin, available_space, cx);
-                            }
-                        });
-                    });
-                }
-            },
-        )
-    }
-}
-
-impl IntoElement for UniformList {
-    type Element = Self;
-
-    fn element_id(&self) -> Option<crate::ElementId> {
-        Some(self.id.clone())
-    }
-
-    fn into_element(self) -> Self::Element {
-        self
-    }
-}
-
-impl UniformList {
-    pub fn with_width_from_item(mut self, item_index: Option<usize>) -> Self {
-        self.item_to_measure_index = item_index.unwrap_or(0);
-        self
-    }
-
-    fn measure_item(&self, list_width: Option<Pixels>, cx: &mut WindowContext) -> Size<Pixels> {
-        if self.item_count == 0 {
-            return Size::default();
-        }
-
-        let item_ix = cmp::min(self.item_to_measure_index, self.item_count - 1);
-        let mut items = (self.render_items)(item_ix..item_ix + 1, cx);
-        let mut item_to_measure = items.pop().unwrap();
-        let available_space = size(
-            list_width.map_or(AvailableSpace::MinContent, |width| {
-                AvailableSpace::Definite(width)
-            }),
-            AvailableSpace::MinContent,
-        );
-        item_to_measure.measure(available_space, cx)
-    }
-
-    pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self {
-        self.scroll_handle = Some(handle);
-        self
-    }
-}
-
-impl InteractiveElement for UniformList {
-    fn interactivity(&mut self) -> &mut crate::Interactivity {
-        &mut self.interactivity
-    }
-}

crates/gpui2/src/executor.rs 🔗

@@ -1,402 +0,0 @@
-use crate::{AppContext, PlatformDispatcher};
-use futures::{channel::mpsc, pin_mut, FutureExt};
-use smol::prelude::*;
-use std::{
-    fmt::Debug,
-    marker::PhantomData,
-    mem,
-    num::NonZeroUsize,
-    pin::Pin,
-    rc::Rc,
-    sync::{
-        atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst},
-        Arc,
-    },
-    task::{Context, Poll},
-    time::Duration,
-};
-use util::TryFutureExt;
-use waker_fn::waker_fn;
-
-#[cfg(any(test, feature = "test-support"))]
-use rand::rngs::StdRng;
-
-#[derive(Clone)]
-pub struct BackgroundExecutor {
-    dispatcher: Arc<dyn PlatformDispatcher>,
-}
-
-#[derive(Clone)]
-pub struct ForegroundExecutor {
-    dispatcher: Arc<dyn PlatformDispatcher>,
-    not_send: PhantomData<Rc<()>>,
-}
-
-#[must_use]
-#[derive(Debug)]
-pub enum Task<T> {
-    Ready(Option<T>),
-    Spawned(async_task::Task<T>),
-}
-
-impl<T> Task<T> {
-    pub fn ready(val: T) -> Self {
-        Task::Ready(Some(val))
-    }
-
-    pub fn detach(self) {
-        match self {
-            Task::Ready(_) => {}
-            Task::Spawned(task) => task.detach(),
-        }
-    }
-}
-
-impl<E, T> Task<Result<T, E>>
-where
-    T: 'static,
-    E: 'static + Debug,
-{
-    #[track_caller]
-    pub fn detach_and_log_err(self, cx: &mut AppContext) {
-        let location = core::panic::Location::caller();
-        cx.foreground_executor()
-            .spawn(self.log_tracked_err(*location))
-            .detach();
-    }
-}
-
-impl<T> Future for Task<T> {
-    type Output = T;
-
-    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
-        match unsafe { self.get_unchecked_mut() } {
-            Task::Ready(val) => Poll::Ready(val.take().unwrap()),
-            Task::Spawned(task) => task.poll(cx),
-        }
-    }
-}
-
-#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
-pub struct TaskLabel(NonZeroUsize);
-
-impl Default for TaskLabel {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
-impl TaskLabel {
-    pub fn new() -> Self {
-        static NEXT_TASK_LABEL: AtomicUsize = AtomicUsize::new(1);
-        Self(NEXT_TASK_LABEL.fetch_add(1, SeqCst).try_into().unwrap())
-    }
-}
-
-type AnyLocalFuture<R> = Pin<Box<dyn 'static + Future<Output = R>>>;
-
-type AnyFuture<R> = Pin<Box<dyn 'static + Send + Future<Output = R>>>;
-
-impl BackgroundExecutor {
-    pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
-        Self { dispatcher }
-    }
-
-    /// Enqueues the given future to be run to completion on a background thread.
-    pub fn spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
-    where
-        R: Send + 'static,
-    {
-        self.spawn_internal::<R>(Box::pin(future), None)
-    }
-
-    /// Enqueues the given future to be run to completion on a background thread.
-    /// The given label can be used to control the priority of the task in tests.
-    pub fn spawn_labeled<R>(
-        &self,
-        label: TaskLabel,
-        future: impl Future<Output = R> + Send + 'static,
-    ) -> Task<R>
-    where
-        R: Send + 'static,
-    {
-        self.spawn_internal::<R>(Box::pin(future), Some(label))
-    }
-
-    fn spawn_internal<R: Send + 'static>(
-        &self,
-        future: AnyFuture<R>,
-        label: Option<TaskLabel>,
-    ) -> Task<R> {
-        let dispatcher = self.dispatcher.clone();
-        let (runnable, task) =
-            async_task::spawn(future, move |runnable| dispatcher.dispatch(runnable, label));
-        runnable.schedule();
-        Task::Spawned(task)
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    #[track_caller]
-    pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R {
-        if let Ok(value) = self.block_internal(false, future, usize::MAX) {
-            value
-        } else {
-            unreachable!()
-        }
-    }
-
-    pub fn block<R>(&self, future: impl Future<Output = R>) -> R {
-        if let Ok(value) = self.block_internal(true, future, usize::MAX) {
-            value
-        } else {
-            unreachable!()
-        }
-    }
-
-    #[track_caller]
-    pub(crate) fn block_internal<R>(
-        &self,
-        background_only: bool,
-        future: impl Future<Output = R>,
-        mut max_ticks: usize,
-    ) -> Result<R, ()> {
-        pin_mut!(future);
-        let unparker = self.dispatcher.unparker();
-        let awoken = Arc::new(AtomicBool::new(false));
-
-        let waker = waker_fn({
-            let awoken = awoken.clone();
-            move || {
-                awoken.store(true, SeqCst);
-                unparker.unpark();
-            }
-        });
-        let mut cx = std::task::Context::from_waker(&waker);
-
-        loop {
-            match future.as_mut().poll(&mut cx) {
-                Poll::Ready(result) => return Ok(result),
-                Poll::Pending => {
-                    if max_ticks == 0 {
-                        return Err(());
-                    }
-                    max_ticks -= 1;
-
-                    if !self.dispatcher.tick(background_only) {
-                        if awoken.swap(false, SeqCst) {
-                            continue;
-                        }
-
-                        #[cfg(any(test, feature = "test-support"))]
-                        if let Some(test) = self.dispatcher.as_test() {
-                            if !test.parking_allowed() {
-                                let mut backtrace_message = String::new();
-                                if let Some(backtrace) = test.waiting_backtrace() {
-                                    backtrace_message =
-                                        format!("\nbacktrace of waiting future:\n{:?}", backtrace);
-                                }
-                                panic!("parked with nothing left to run\n{:?}", backtrace_message)
-                            }
-                        }
-
-                        self.dispatcher.park();
-                    }
-                }
-            }
-        }
-    }
-
-    pub fn block_with_timeout<R>(
-        &self,
-        duration: Duration,
-        future: impl Future<Output = R>,
-    ) -> Result<R, impl Future<Output = R>> {
-        let mut future = Box::pin(future.fuse());
-        if duration.is_zero() {
-            return Err(future);
-        }
-
-        #[cfg(any(test, feature = "test-support"))]
-        let max_ticks = self
-            .dispatcher
-            .as_test()
-            .map_or(usize::MAX, |dispatcher| dispatcher.gen_block_on_ticks());
-        #[cfg(not(any(test, feature = "test-support")))]
-        let max_ticks = usize::MAX;
-
-        let mut timer = self.timer(duration).fuse();
-
-        let timeout = async {
-            futures::select_biased! {
-                value = future => Ok(value),
-                _ = timer => Err(()),
-            }
-        };
-        match self.block_internal(true, timeout, max_ticks) {
-            Ok(Ok(value)) => Ok(value),
-            _ => Err(future),
-        }
-    }
-
-    pub async fn scoped<'scope, F>(&self, scheduler: F)
-    where
-        F: FnOnce(&mut Scope<'scope>),
-    {
-        let mut scope = Scope::new(self.clone());
-        (scheduler)(&mut scope);
-        let spawned = mem::take(&mut scope.futures)
-            .into_iter()
-            .map(|f| self.spawn(f))
-            .collect::<Vec<_>>();
-        for task in spawned {
-            task.await;
-        }
-    }
-
-    pub fn timer(&self, duration: Duration) -> Task<()> {
-        let (runnable, task) = async_task::spawn(async move {}, {
-            let dispatcher = self.dispatcher.clone();
-            move |runnable| dispatcher.dispatch_after(duration, runnable)
-        });
-        runnable.schedule();
-        Task::Spawned(task)
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn start_waiting(&self) {
-        self.dispatcher.as_test().unwrap().start_waiting();
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn finish_waiting(&self) {
-        self.dispatcher.as_test().unwrap().finish_waiting();
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn simulate_random_delay(&self) -> impl Future<Output = ()> {
-        self.dispatcher.as_test().unwrap().simulate_random_delay()
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn deprioritize(&self, task_label: TaskLabel) {
-        self.dispatcher.as_test().unwrap().deprioritize(task_label)
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn advance_clock(&self, duration: Duration) {
-        self.dispatcher.as_test().unwrap().advance_clock(duration)
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn tick(&self) -> bool {
-        self.dispatcher.as_test().unwrap().tick(false)
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn run_until_parked(&self) {
-        self.dispatcher.as_test().unwrap().run_until_parked()
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn allow_parking(&self) {
-        self.dispatcher.as_test().unwrap().allow_parking();
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn rng(&self) -> StdRng {
-        self.dispatcher.as_test().unwrap().rng()
-    }
-
-    pub fn num_cpus(&self) -> usize {
-        num_cpus::get()
-    }
-
-    pub fn is_main_thread(&self) -> bool {
-        self.dispatcher.is_main_thread()
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {
-        self.dispatcher.as_test().unwrap().set_block_on_ticks(range);
-    }
-}
-
-impl ForegroundExecutor {
-    pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
-        Self {
-            dispatcher,
-            not_send: PhantomData,
-        }
-    }
-
-    /// Enqueues the given closure to be run on any thread. The closure returns
-    /// a future which will be run to completion on any available thread.
-    pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
-    where
-        R: 'static,
-    {
-        let dispatcher = self.dispatcher.clone();
-        fn inner<R: 'static>(
-            dispatcher: Arc<dyn PlatformDispatcher>,
-            future: AnyLocalFuture<R>,
-        ) -> Task<R> {
-            let (runnable, task) = async_task::spawn_local(future, move |runnable| {
-                dispatcher.dispatch_on_main_thread(runnable)
-            });
-            runnable.schedule();
-            Task::Spawned(task)
-        }
-        inner::<R>(dispatcher, Box::pin(future))
-    }
-}
-
-pub struct Scope<'a> {
-    executor: BackgroundExecutor,
-    futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
-    tx: Option<mpsc::Sender<()>>,
-    rx: mpsc::Receiver<()>,
-    lifetime: PhantomData<&'a ()>,
-}
-
-impl<'a> Scope<'a> {
-    fn new(executor: BackgroundExecutor) -> Self {
-        let (tx, rx) = mpsc::channel(1);
-        Self {
-            executor,
-            tx: Some(tx),
-            rx,
-            futures: Default::default(),
-            lifetime: PhantomData,
-        }
-    }
-
-    pub fn spawn<F>(&mut self, f: F)
-    where
-        F: Future<Output = ()> + Send + 'a,
-    {
-        let tx = self.tx.clone().unwrap();
-
-        // Safety: The 'a lifetime is guaranteed to outlive any of these futures because
-        // dropping this `Scope` blocks until all of the futures have resolved.
-        let f = unsafe {
-            mem::transmute::<
-                Pin<Box<dyn Future<Output = ()> + Send + 'a>>,
-                Pin<Box<dyn Future<Output = ()> + Send + 'static>>,
-            >(Box::pin(async move {
-                f.await;
-                drop(tx);
-            }))
-        };
-        self.futures.push(f);
-    }
-}
-
-impl<'a> Drop for Scope<'a> {
-    fn drop(&mut self) {
-        self.tx.take().unwrap();
-
-        // Wait until the channel is closed, which means that all of the spawned
-        // futures have resolved.
-        self.executor.block(self.rx.next());
-    }
-}

crates/gpui2/src/geometry.rs 🔗

@@ -1,2796 +0,0 @@
-use core::fmt::Debug;
-use derive_more::{Add, AddAssign, Div, DivAssign, Mul, Neg, Sub, SubAssign};
-use refineable::Refineable;
-use serde_derive::{Deserialize, Serialize};
-use std::{
-    cmp::{self, PartialOrd},
-    fmt,
-    ops::{Add, Div, Mul, MulAssign, Sub},
-};
-
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
-pub enum Axis {
-    Vertical,
-    Horizontal,
-}
-
-impl Axis {
-    pub fn invert(&self) -> Self {
-        match self {
-            Axis::Vertical => Axis::Horizontal,
-            Axis::Horizontal => Axis::Vertical,
-        }
-    }
-}
-
-pub trait Along {
-    type Unit;
-
-    fn along(&self, axis: Axis) -> Self::Unit;
-
-    fn apply_along(&self, axis: Axis, f: impl FnOnce(Self::Unit) -> Self::Unit) -> Self;
-}
-
-impl sqlez::bindable::StaticColumnCount for Axis {}
-impl sqlez::bindable::Bind for Axis {
-    fn bind(
-        &self,
-        statement: &sqlez::statement::Statement,
-        start_index: i32,
-    ) -> anyhow::Result<i32> {
-        match self {
-            Axis::Horizontal => "Horizontal",
-            Axis::Vertical => "Vertical",
-        }
-        .bind(statement, start_index)
-    }
-}
-
-impl sqlez::bindable::Column for Axis {
-    fn column(
-        statement: &mut sqlez::statement::Statement,
-        start_index: i32,
-    ) -> anyhow::Result<(Self, i32)> {
-        String::column(statement, start_index).and_then(|(axis_text, next_index)| {
-            Ok((
-                match axis_text.as_str() {
-                    "Horizontal" => Axis::Horizontal,
-                    "Vertical" => Axis::Vertical,
-                    _ => anyhow::bail!("Stored serialized item kind is incorrect"),
-                },
-                next_index,
-            ))
-        })
-    }
-}
-
-/// Describes a location in a 2D cartesian coordinate space.
-///
-/// It holds two public fields, `x` and `y`, which represent the coordinates in the space.
-/// The type `T` for the coordinates can be any type that implements `Default`, `Clone`, and `Debug`.
-///
-/// # Examples
-///
-/// ```
-/// # use zed::Point;
-/// let point = Point { x: 10, y: 20 };
-/// println!("{:?}", point); // Outputs: Point { x: 10, y: 20 }
-/// ```
-#[derive(Refineable, Default, Add, AddAssign, Sub, SubAssign, Copy, Debug, PartialEq, Eq, Hash)]
-#[refineable(Debug)]
-#[repr(C)]
-pub struct Point<T: Default + Clone + Debug> {
-    pub x: T,
-    pub y: T,
-}
-
-/// Constructs a new `Point<T>` with the given x and y coordinates.
-///
-/// # Arguments
-///
-/// * `x` - The x coordinate of the point.
-/// * `y` - The y coordinate of the point.
-///
-/// # Returns
-///
-/// Returns a `Point<T>` with the specified coordinates.
-///
-/// # Examples
-///
-/// ```
-/// # use zed::Point;
-/// let p = point(10, 20);
-/// assert_eq!(p.x, 10);
-/// assert_eq!(p.y, 20);
-/// ```
-pub fn point<T: Clone + Debug + Default>(x: T, y: T) -> Point<T> {
-    Point { x, y }
-}
-
-impl<T: Clone + Debug + Default> Point<T> {
-    /// Creates a new `Point` with the specified `x` and `y` coordinates.
-    ///
-    /// # Arguments
-    ///
-    /// * `x` - The horizontal coordinate of the point.
-    /// * `y` - The vertical coordinate of the point.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// let p = Point::new(10, 20);
-    /// assert_eq!(p.x, 10);
-    /// assert_eq!(p.y, 20);
-    /// ```
-    pub const fn new(x: T, y: T) -> Self {
-        Self { x, y }
-    }
-
-    /// Transforms the point to a `Point<U>` by applying the given function to both coordinates.
-    ///
-    /// This method allows for converting a `Point<T>` to a `Point<U>` by specifying a closure
-    /// that defines how to convert between the two types. The closure is applied to both the `x`
-    /// and `y` coordinates, resulting in a new point of the desired type.
-    ///
-    /// # Arguments
-    ///
-    /// * `f` - A closure that takes a value of type `T` and returns a value of type `U`.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::Point;
-    /// let p = Point { x: 3, y: 4 };
-    /// let p_float = p.map(|coord| coord as f32);
-    /// assert_eq!(p_float, Point { x: 3.0, y: 4.0 });
-    /// ```
-    pub fn map<U: Clone + Default + Debug>(&self, f: impl Fn(T) -> U) -> Point<U> {
-        Point {
-            x: f(self.x.clone()),
-            y: f(self.y.clone()),
-        }
-    }
-}
-
-impl<T: Clone + Debug + Default> Along for Point<T> {
-    type Unit = T;
-
-    fn along(&self, axis: Axis) -> T {
-        match axis {
-            Axis::Horizontal => self.x.clone(),
-            Axis::Vertical => self.y.clone(),
-        }
-    }
-
-    fn apply_along(&self, axis: Axis, f: impl FnOnce(T) -> T) -> Point<T> {
-        match axis {
-            Axis::Horizontal => Point {
-                x: f(self.x.clone()),
-                y: self.y.clone(),
-            },
-            Axis::Vertical => Point {
-                x: self.x.clone(),
-                y: f(self.y.clone()),
-            },
-        }
-    }
-}
-
-impl Point<Pixels> {
-    /// Scales the point by a given factor, which is typically derived from the resolution
-    /// of a target display to ensure proper sizing of UI elements.
-    ///
-    /// # Arguments
-    ///
-    /// * `factor` - The scaling factor to apply to both the x and y coordinates.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::{Point, Pixels, ScaledPixels};
-    /// let p = Point { x: Pixels(10.0), y: Pixels(20.0) };
-    /// let scaled_p = p.scale(1.5);
-    /// assert_eq!(scaled_p, Point { x: ScaledPixels(15.0), y: ScaledPixels(30.0) });
-    /// ```
-    pub fn scale(&self, factor: f32) -> Point<ScaledPixels> {
-        Point {
-            x: self.x.scale(factor),
-            y: self.y.scale(factor),
-        }
-    }
-
-    /// Calculates the Euclidean distance from the origin (0, 0) to this point.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::Point;
-    /// # use zed::Pixels;
-    /// let p = Point { x: Pixels(3.0), y: Pixels(4.0) };
-    /// assert_eq!(p.magnitude(), 5.0);
-    /// ```
-    pub fn magnitude(&self) -> f64 {
-        ((self.x.0.powi(2) + self.y.0.powi(2)) as f64).sqrt()
-    }
-}
-
-impl<T, Rhs> Mul<Rhs> for Point<T>
-where
-    T: Mul<Rhs, Output = T> + Clone + Default + Debug,
-    Rhs: Clone + Debug,
-{
-    type Output = Point<T>;
-
-    fn mul(self, rhs: Rhs) -> Self::Output {
-        Point {
-            x: self.x * rhs.clone(),
-            y: self.y * rhs,
-        }
-    }
-}
-
-impl<T, S> MulAssign<S> for Point<T>
-where
-    T: Clone + Mul<S, Output = T> + Default + Debug,
-    S: Clone,
-{
-    fn mul_assign(&mut self, rhs: S) {
-        self.x = self.x.clone() * rhs.clone();
-        self.y = self.y.clone() * rhs;
-    }
-}
-
-impl<T, S> Div<S> for Point<T>
-where
-    T: Div<S, Output = T> + Clone + Default + Debug,
-    S: Clone,
-{
-    type Output = Self;
-
-    fn div(self, rhs: S) -> Self::Output {
-        Self {
-            x: self.x / rhs.clone(),
-            y: self.y / rhs,
-        }
-    }
-}
-
-impl<T> Point<T>
-where
-    T: PartialOrd + Clone + Default + Debug,
-{
-    /// Returns a new point with the maximum values of each dimension from `self` and `other`.
-    ///
-    /// # Arguments
-    ///
-    /// * `other` - A reference to another `Point` to compare with `self`.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::Point;
-    /// let p1 = Point { x: 3, y: 7 };
-    /// let p2 = Point { x: 5, y: 2 };
-    /// let max_point = p1.max(&p2);
-    /// assert_eq!(max_point, Point { x: 5, y: 7 });
-    /// ```
-    pub fn max(&self, other: &Self) -> Self {
-        Point {
-            x: if self.x > other.x {
-                self.x.clone()
-            } else {
-                other.x.clone()
-            },
-            y: if self.y > other.y {
-                self.y.clone()
-            } else {
-                other.y.clone()
-            },
-        }
-    }
-
-    /// Returns a new point with the minimum values of each dimension from `self` and `other`.
-    ///
-    /// # Arguments
-    ///
-    /// * `other` - A reference to another `Point` to compare with `self`.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::Point;
-    /// let p1 = Point { x: 3, y: 7 };
-    /// let p2 = Point { x: 5, y: 2 };
-    /// let min_point = p1.min(&p2);
-    /// assert_eq!(min_point, Point { x: 3, y: 2 });
-    /// ```
-    pub fn min(&self, other: &Self) -> Self {
-        Point {
-            x: if self.x <= other.x {
-                self.x.clone()
-            } else {
-                other.x.clone()
-            },
-            y: if self.y <= other.y {
-                self.y.clone()
-            } else {
-                other.y.clone()
-            },
-        }
-    }
-
-    /// Clamps the point to a specified range.
-    ///
-    /// Given a minimum point and a maximum point, this method constrains the current point
-    /// such that its coordinates do not exceed the range defined by the minimum and maximum points.
-    /// If the current point's coordinates are less than the minimum, they are set to the minimum.
-    /// If they are greater than the maximum, they are set to the maximum.
-    ///
-    /// # Arguments
-    ///
-    /// * `min` - A reference to a `Point` representing the minimum allowable coordinates.
-    /// * `max` - A reference to a `Point` representing the maximum allowable coordinates.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::Point;
-    /// let p = Point { x: 10, y: 20 };
-    /// let min = Point { x: 0, y: 5 };
-    /// let max = Point { x: 15, y: 25 };
-    /// let clamped_p = p.clamp(&min, &max);
-    /// assert_eq!(clamped_p, Point { x: 10, y: 20 });
-    ///
-    /// let p_out_of_bounds = Point { x: -5, y: 30 };
-    /// let clamped_p_out_of_bounds = p_out_of_bounds.clamp(&min, &max);
-    /// assert_eq!(clamped_p_out_of_bounds, Point { x: 0, y: 25 });
-    /// ```
-    pub fn clamp(&self, min: &Self, max: &Self) -> Self {
-        self.max(min).min(max)
-    }
-}
-
-impl<T: Clone + Default + Debug> Clone for Point<T> {
-    fn clone(&self) -> Self {
-        Self {
-            x: self.x.clone(),
-            y: self.y.clone(),
-        }
-    }
-}
-
-/// A structure representing a two-dimensional size with width and height in a given unit.
-///
-/// This struct is generic over the type `T`, which can be any type that implements `Clone`, `Default`, and `Debug`.
-/// It is commonly used to specify dimensions for elements in a UI, such as a window or element.
-#[derive(Refineable, Default, Clone, Copy, PartialEq, Div, Hash, Serialize, Deserialize)]
-#[refineable(Debug)]
-#[repr(C)]
-pub struct Size<T: Clone + Default + Debug> {
-    pub width: T,
-    pub height: T,
-}
-
-/// Constructs a new `Size<T>` with the provided width and height.
-///
-/// # Arguments
-///
-/// * `width` - The width component of the `Size`.
-/// * `height` - The height component of the `Size`.
-///
-/// # Examples
-///
-/// ```
-/// # use zed::Size;
-/// let my_size = size(10, 20);
-/// assert_eq!(my_size.width, 10);
-/// assert_eq!(my_size.height, 20);
-/// ```
-pub fn size<T>(width: T, height: T) -> Size<T>
-where
-    T: Clone + Default + Debug,
-{
-    Size { width, height }
-}
-
-impl<T> Size<T>
-where
-    T: Clone + Default + Debug,
-{
-    /// Applies a function to the width and height of the size, producing a new `Size<U>`.
-    ///
-    /// This method allows for converting a `Size<T>` to a `Size<U>` by specifying a closure
-    /// that defines how to convert between the two types. The closure is applied to both the `width`
-    /// and `height`, resulting in a new size of the desired type.
-    ///
-    /// # Arguments
-    ///
-    /// * `f` - A closure that takes a value of type `T` and returns a value of type `U`.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::Size;
-    /// let my_size = Size { width: 10, height: 20 };
-    /// let my_new_size = my_size.map(|dimension| dimension as f32 * 1.5);
-    /// assert_eq!(my_new_size, Size { width: 15.0, height: 30.0 });
-    /// ```
-    pub fn map<U>(&self, f: impl Fn(T) -> U) -> Size<U>
-    where
-        U: Clone + Default + Debug,
-    {
-        Size {
-            width: f(self.width.clone()),
-            height: f(self.height.clone()),
-        }
-    }
-}
-
-impl Size<Pixels> {
-    /// Scales the size by a given factor.
-    ///
-    /// This method multiplies both the width and height by the provided scaling factor,
-    /// resulting in a new `Size<ScaledPixels>` that is proportionally larger or smaller
-    /// depending on the factor.
-    ///
-    /// # Arguments
-    ///
-    /// * `factor` - The scaling factor to apply to the width and height.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::{Size, Pixels, ScaledPixels};
-    /// let size = Size { width: Pixels(100.0), height: Pixels(50.0) };
-    /// let scaled_size = size.scale(2.0);
-    /// assert_eq!(scaled_size, Size { width: ScaledPixels(200.0), height: ScaledPixels(100.0) });
-    /// ```
-    pub fn scale(&self, factor: f32) -> Size<ScaledPixels> {
-        Size {
-            width: self.width.scale(factor),
-            height: self.height.scale(factor),
-        }
-    }
-}
-
-impl<T> Along for Size<T>
-where
-    T: Clone + Default + Debug,
-{
-    type Unit = T;
-
-    fn along(&self, axis: Axis) -> T {
-        match axis {
-            Axis::Horizontal => self.width.clone(),
-            Axis::Vertical => self.height.clone(),
-        }
-    }
-
-    /// Returns the value of this size along the given axis.
-    fn apply_along(&self, axis: Axis, f: impl FnOnce(T) -> T) -> Self {
-        match axis {
-            Axis::Horizontal => Size {
-                width: f(self.width.clone()),
-                height: self.height.clone(),
-            },
-            Axis::Vertical => Size {
-                width: self.width.clone(),
-                height: f(self.height.clone()),
-            },
-        }
-    }
-}
-
-impl<T> Size<T>
-where
-    T: PartialOrd + Clone + Default + Debug,
-{
-    /// Returns a new `Size` with the maximum width and height from `self` and `other`.
-    ///
-    /// # Arguments
-    ///
-    /// * `other` - A reference to another `Size` to compare with `self`.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::Size;
-    /// let size1 = Size { width: 30, height: 40 };
-    /// let size2 = Size { width: 50, height: 20 };
-    /// let max_size = size1.max(&size2);
-    /// assert_eq!(max_size, Size { width: 50, height: 40 });
-    /// ```
-    pub fn max(&self, other: &Self) -> Self {
-        Size {
-            width: if self.width >= other.width {
-                self.width.clone()
-            } else {
-                other.width.clone()
-            },
-            height: if self.height >= other.height {
-                self.height.clone()
-            } else {
-                other.height.clone()
-            },
-        }
-    }
-}
-
-impl<T> Sub for Size<T>
-where
-    T: Sub<Output = T> + Clone + Default + Debug,
-{
-    type Output = Size<T>;
-
-    fn sub(self, rhs: Self) -> Self::Output {
-        Size {
-            width: self.width - rhs.width,
-            height: self.height - rhs.height,
-        }
-    }
-}
-
-impl<T, Rhs> Mul<Rhs> for Size<T>
-where
-    T: Mul<Rhs, Output = Rhs> + Clone + Default + Debug,
-    Rhs: Clone + Default + Debug,
-{
-    type Output = Size<Rhs>;
-
-    fn mul(self, rhs: Rhs) -> Self::Output {
-        Size {
-            width: self.width * rhs.clone(),
-            height: self.height * rhs,
-        }
-    }
-}
-
-impl<T, S> MulAssign<S> for Size<T>
-where
-    T: Mul<S, Output = T> + Clone + Default + Debug,
-    S: Clone,
-{
-    fn mul_assign(&mut self, rhs: S) {
-        self.width = self.width.clone() * rhs.clone();
-        self.height = self.height.clone() * rhs;
-    }
-}
-
-impl<T> Eq for Size<T> where T: Eq + Default + Debug + Clone {}
-
-impl<T> Debug for Size<T>
-where
-    T: Clone + Default + Debug,
-{
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "Size {{ {:?} × {:?} }}", self.width, self.height)
-    }
-}
-
-impl<T: Clone + Default + Debug> From<Point<T>> for Size<T> {
-    fn from(point: Point<T>) -> Self {
-        Self {
-            width: point.x,
-            height: point.y,
-        }
-    }
-}
-
-impl From<Size<Pixels>> for Size<GlobalPixels> {
-    fn from(size: Size<Pixels>) -> Self {
-        Size {
-            width: GlobalPixels(size.width.0),
-            height: GlobalPixels(size.height.0),
-        }
-    }
-}
-
-impl From<Size<Pixels>> for Size<DefiniteLength> {
-    fn from(size: Size<Pixels>) -> Self {
-        Size {
-            width: size.width.into(),
-            height: size.height.into(),
-        }
-    }
-}
-
-impl From<Size<Pixels>> for Size<AbsoluteLength> {
-    fn from(size: Size<Pixels>) -> Self {
-        Size {
-            width: size.width.into(),
-            height: size.height.into(),
-        }
-    }
-}
-
-impl Size<Length> {
-    /// Returns a `Size` with both width and height set to fill the available space.
-    ///
-    /// This function creates a `Size` instance where both the width and height are set to `Length::Definite(DefiniteLength::Fraction(1.0))`,
-    /// which represents 100% of the available space in both dimensions.
-    ///
-    /// # Returns
-    ///
-    /// A `Size<Length>` that will fill the available space when used in a layout.
-    pub fn full() -> Self {
-        Self {
-            width: relative(1.).into(),
-            height: relative(1.).into(),
-        }
-    }
-}
-
-impl Size<Length> {
-    /// Returns a `Size` with both width and height set to `auto`, which allows the layout engine to determine the size.
-    ///
-    /// This function creates a `Size` instance where both the width and height are set to `Length::Auto`,
-    /// indicating that their size should be computed based on the layout context, such as the content size or
-    /// available space.
-    ///
-    /// # Returns
-    ///
-    /// A `Size<Length>` with width and height set to `Length::Auto`.
-    pub fn auto() -> Self {
-        Self {
-            width: Length::Auto,
-            height: Length::Auto,
-        }
-    }
-}
-
-/// Represents a rectangular area in a 2D space with an origin point and a size.
-///
-/// The `Bounds` struct is generic over a type `T` which represents the type of the coordinate system.
-/// The origin is represented as a `Point<T>` which defines the upper-left corner of the rectangle,
-/// and the size is represented as a `Size<T>` which defines the width and height of the rectangle.
-///
-/// # Examples
-///
-/// ```
-/// # use zed::{Bounds, Point, Size};
-/// let origin = Point { x: 0, y: 0 };
-/// let size = Size { width: 10, height: 20 };
-/// let bounds = Bounds::new(origin, size);
-///
-/// assert_eq!(bounds.origin, origin);
-/// assert_eq!(bounds.size, size);
-/// ```
-#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
-#[refineable(Debug)]
-#[repr(C)]
-pub struct Bounds<T: Clone + Default + Debug> {
-    pub origin: Point<T>,
-    pub size: Size<T>,
-}
-
-impl<T> Bounds<T>
-where
-    T: Clone + Debug + Sub<Output = T> + Default,
-{
-    /// Constructs a `Bounds` from two corner points: the upper-left and lower-right corners.
-    ///
-    /// This function calculates the origin and size of the `Bounds` based on the provided corner points.
-    /// The origin is set to the upper-left corner, and the size is determined by the difference between
-    /// the x and y coordinates of the lower-right and upper-left points.
-    ///
-    /// # Arguments
-    ///
-    /// * `upper_left` - A `Point<T>` representing the upper-left corner of the rectangle.
-    /// * `lower_right` - A `Point<T>` representing the lower-right corner of the rectangle.
-    ///
-    /// # Returns
-    ///
-    /// Returns a `Bounds<T>` that encompasses the area defined by the two corner points.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::{Bounds, Point};
-    /// let upper_left = Point { x: 0, y: 0 };
-    /// let lower_right = Point { x: 10, y: 10 };
-    /// let bounds = Bounds::from_corners(upper_left, lower_right);
-    ///
-    /// assert_eq!(bounds.origin, upper_left);
-    /// assert_eq!(bounds.size.width, 10);
-    /// assert_eq!(bounds.size.height, 10);
-    /// ```
-    pub fn from_corners(upper_left: Point<T>, lower_right: Point<T>) -> Self {
-        let origin = Point {
-            x: upper_left.x.clone(),
-            y: upper_left.y.clone(),
-        };
-        let size = Size {
-            width: lower_right.x - upper_left.x,
-            height: lower_right.y - upper_left.y,
-        };
-        Bounds { origin, size }
-    }
-
-    /// Creates a new `Bounds` with the specified origin and size.
-    ///
-    /// # Arguments
-    ///
-    /// * `origin` - A `Point<T>` representing the origin of the bounds.
-    /// * `size` - A `Size<T>` representing the size of the bounds.
-    ///
-    /// # Returns
-    ///
-    /// Returns a `Bounds<T>` that has the given origin and size.
-    pub fn new(origin: Point<T>, size: Size<T>) -> Self {
-        Bounds { origin, size }
-    }
-}
-
-impl<T> Bounds<T>
-where
-    T: Clone + Debug + PartialOrd + Add<T, Output = T> + Sub<Output = T> + Default + Half,
-{
-    /// Checks if this `Bounds` intersects with another `Bounds`.
-    ///
-    /// Two `Bounds` instances intersect if they overlap in the 2D space they occupy.
-    /// This method checks if there is any overlapping area between the two bounds.
-    ///
-    /// # Arguments
-    ///
-    /// * `other` - A reference to another `Bounds` to check for intersection with.
-    ///
-    /// # Returns
-    ///
-    /// Returns `true` if there is any intersection between the two bounds, `false` otherwise.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::{Bounds, Point, Size};
-    /// let bounds1 = Bounds {
-    ///     origin: Point { x: 0, y: 0 },
-    ///     size: Size { width: 10, height: 10 },
-    /// };
-    /// let bounds2 = Bounds {
-    ///     origin: Point { x: 5, y: 5 },
-    ///     size: Size { width: 10, height: 10 },
-    /// };
-    /// let bounds3 = Bounds {
-    ///     origin: Point { x: 20, y: 20 },
-    ///     size: Size { width: 10, height: 10 },
-    /// };
-    ///
-    /// assert_eq!(bounds1.intersects(&bounds2), true); // Overlapping bounds
-    /// assert_eq!(bounds1.intersects(&bounds3), false); // Non-overlapping bounds
-    /// ```
-    pub fn intersects(&self, other: &Bounds<T>) -> bool {
-        let my_lower_right = self.lower_right();
-        let their_lower_right = other.lower_right();
-
-        self.origin.x < their_lower_right.x
-            && my_lower_right.x > other.origin.x
-            && self.origin.y < their_lower_right.y
-            && my_lower_right.y > other.origin.y
-    }
-
-    /// Dilates the bounds by a specified amount in all directions.
-    ///
-    /// This method expands the bounds by the given `amount`, increasing the size
-    /// and adjusting the origin so that the bounds grow outwards equally in all directions.
-    /// The resulting bounds will have its width and height increased by twice the `amount`
-    /// (since it grows in both directions), and the origin will be moved by `-amount`
-    /// in both the x and y directions.
-    ///
-    /// # Arguments
-    ///
-    /// * `amount` - The amount by which to dilate the bounds.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::{Bounds, Point, Size};
-    /// let mut bounds = Bounds {
-    ///     origin: Point { x: 10, y: 10 },
-    ///     size: Size { width: 10, height: 10 },
-    /// };
-    /// bounds.dilate(5);
-    /// assert_eq!(bounds, Bounds {
-    ///     origin: Point { x: 5, y: 5 },
-    ///     size: Size { width: 20, height: 20 },
-    /// });
-    /// ```
-    pub fn dilate(&mut self, amount: T) {
-        self.origin.x = self.origin.x.clone() - amount.clone();
-        self.origin.y = self.origin.y.clone() - amount.clone();
-        let double_amount = amount.clone() + amount;
-        self.size.width = self.size.width.clone() + double_amount.clone();
-        self.size.height = self.size.height.clone() + double_amount;
-    }
-
-    /// Returns the center point of the bounds.
-    ///
-    /// Calculates the center by taking the origin's x and y coordinates and adding half the width and height
-    /// of the bounds, respectively. The center is represented as a `Point<T>` where `T` is the type of the
-    /// coordinate system.
-    ///
-    /// # Returns
-    ///
-    /// A `Point<T>` representing the center of the bounds.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::{Bounds, Point, Size};
-    /// let bounds = Bounds {
-    ///     origin: Point { x: 0, y: 0 },
-    ///     size: Size { width: 10, height: 20 },
-    /// };
-    /// let center = bounds.center();
-    /// assert_eq!(center, Point { x: 5, y: 10 });
-    /// ```
-    pub fn center(&self) -> Point<T> {
-        Point {
-            x: self.origin.x.clone() + self.size.width.clone().half(),
-            y: self.origin.y.clone() + self.size.height.clone().half(),
-        }
-    }
-}
-
-impl<T: Clone + Default + Debug + PartialOrd + Add<T, Output = T> + Sub<Output = T>> Bounds<T> {
-    /// Calculates the intersection of two `Bounds` objects.
-    ///
-    /// This method computes the overlapping region of two `Bounds`. If the bounds do not intersect,
-    /// the resulting `Bounds` will have a size with width and height of zero.
-    ///
-    /// # Arguments
-    ///
-    /// * `other` - A reference to another `Bounds` to intersect with.
-    ///
-    /// # Returns
-    ///
-    /// Returns a `Bounds` representing the intersection area. If there is no intersection,
-    /// the returned `Bounds` will have a size with width and height of zero.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::{Bounds, Point, Size};
-    /// let bounds1 = Bounds {
-    ///     origin: Point { x: 0, y: 0 },
-    ///     size: Size { width: 10, height: 10 },
-    /// };
-    /// let bounds2 = Bounds {
-    ///     origin: Point { x: 5, y: 5 },
-    ///     size: Size { width: 10, height: 10 },
-    /// };
-    /// let intersection = bounds1.intersect(&bounds2);
-    ///
-    /// assert_eq!(intersection, Bounds {
-    ///     origin: Point { x: 5, y: 5 },
-    ///     size: Size { width: 5, height: 5 },
-    /// });
-    /// ```
-    pub fn intersect(&self, other: &Self) -> Self {
-        let upper_left = self.origin.max(&other.origin);
-        let lower_right = self.lower_right().min(&other.lower_right());
-        Self::from_corners(upper_left, lower_right)
-    }
-
-    /// Computes the union of two `Bounds`.
-    ///
-    /// This method calculates the smallest `Bounds` that contains both the current `Bounds` and the `other` `Bounds`.
-    /// The resulting `Bounds` will have an origin that is the minimum of the origins of the two `Bounds`,
-    /// and a size that encompasses the furthest extents of both `Bounds`.
-    ///
-    /// # Arguments
-    ///
-    /// * `other` - A reference to another `Bounds` to create a union with.
-    ///
-    /// # Returns
-    ///
-    /// Returns a `Bounds` representing the union of the two `Bounds`.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::{Bounds, Point, Size};
-    /// let bounds1 = Bounds {
-    ///     origin: Point { x: 0, y: 0 },
-    ///     size: Size { width: 10, height: 10 },
-    /// };
-    /// let bounds2 = Bounds {
-    ///     origin: Point { x: 5, y: 5 },
-    ///     size: Size { width: 15, height: 15 },
-    /// };
-    /// let union_bounds = bounds1.union(&bounds2);
-    ///
-    /// assert_eq!(union_bounds, Bounds {
-    ///     origin: Point { x: 0, y: 0 },
-    ///     size: Size { width: 20, height: 20 },
-    /// });
-    /// ```
-    pub fn union(&self, other: &Self) -> Self {
-        let top_left = self.origin.min(&other.origin);
-        let bottom_right = self.lower_right().max(&other.lower_right());
-        Bounds::from_corners(top_left, bottom_right)
-    }
-}
-
-impl<T, Rhs> Mul<Rhs> for Bounds<T>
-where
-    T: Mul<Rhs, Output = Rhs> + Clone + Default + Debug,
-    Point<T>: Mul<Rhs, Output = Point<Rhs>>,
-    Rhs: Clone + Default + Debug,
-{
-    type Output = Bounds<Rhs>;
-
-    fn mul(self, rhs: Rhs) -> Self::Output {
-        Bounds {
-            origin: self.origin * rhs.clone(),
-            size: self.size * rhs,
-        }
-    }
-}
-
-impl<T, S> MulAssign<S> for Bounds<T>
-where
-    T: Mul<S, Output = T> + Clone + Default + Debug,
-    S: Clone,
-{
-    fn mul_assign(&mut self, rhs: S) {
-        self.origin *= rhs.clone();
-        self.size *= rhs;
-    }
-}
-
-impl<T, S> Div<S> for Bounds<T>
-where
-    Size<T>: Div<S, Output = Size<T>>,
-    T: Div<S, Output = T> + Default + Clone + Debug,
-    S: Clone,
-{
-    type Output = Self;
-
-    fn div(self, rhs: S) -> Self {
-        Self {
-            origin: self.origin / rhs.clone(),
-            size: self.size / rhs,
-        }
-    }
-}
-
-impl<T> Bounds<T>
-where
-    T: Add<T, Output = T> + Clone + Default + Debug,
-{
-    /// Returns the top edge of the bounds.
-    ///
-    /// # Returns
-    ///
-    /// A value of type `T` representing the y-coordinate of the top edge of the bounds.
-    pub fn top(&self) -> T {
-        self.origin.y.clone()
-    }
-
-    /// Returns the bottom edge of the bounds.
-    ///
-    /// # Returns
-    ///
-    /// A value of type `T` representing the y-coordinate of the bottom edge of the bounds.
-    pub fn bottom(&self) -> T {
-        self.origin.y.clone() + self.size.height.clone()
-    }
-
-    /// Returns the left edge of the bounds.
-    ///
-    /// # Returns
-    ///
-    /// A value of type `T` representing the x-coordinate of the left edge of the bounds.
-    pub fn left(&self) -> T {
-        self.origin.x.clone()
-    }
-
-    /// Returns the right edge of the bounds.
-    ///
-    /// # Returns
-    ///
-    /// A value of type `T` representing the x-coordinate of the right edge of the bounds.
-    pub fn right(&self) -> T {
-        self.origin.x.clone() + self.size.width.clone()
-    }
-
-    /// Returns the upper-right corner point of the bounds.
-    ///
-    /// # Returns
-    ///
-    /// A `Point<T>` representing the upper-right corner of the bounds.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::{Bounds, Point, Size};
-    /// let bounds = Bounds {
-    ///     origin: Point { x: 0, y: 0 },
-    ///     size: Size { width: 10, height: 20 },
-    /// };
-    /// let upper_right = bounds.upper_right();
-    /// assert_eq!(upper_right, Point { x: 10, y: 0 });
-    /// ```
-    pub fn upper_right(&self) -> Point<T> {
-        Point {
-            x: self.origin.x.clone() + self.size.width.clone(),
-            y: self.origin.y.clone(),
-        }
-    }
-
-    /// Returns the lower-right corner point of the bounds.
-    ///
-    /// # Returns
-    ///
-    /// A `Point<T>` representing the lower-right corner of the bounds.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::{Bounds, Point, Size};
-    /// let bounds = Bounds {
-    ///     origin: Point { x: 0, y: 0 },
-    ///     size: Size { width: 10, height: 20 },
-    /// };
-    /// let lower_right = bounds.lower_right();
-    /// assert_eq!(lower_right, Point { x: 10, y: 20 });
-    /// ```
-    pub fn lower_right(&self) -> Point<T> {
-        Point {
-            x: self.origin.x.clone() + self.size.width.clone(),
-            y: self.origin.y.clone() + self.size.height.clone(),
-        }
-    }
-
-    /// Returns the lower-left corner point of the bounds.
-    ///
-    /// # Returns
-    ///
-    /// A `Point<T>` representing the lower-left corner of the bounds.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::{Bounds, Point, Size};
-    /// let bounds = Bounds {
-    ///     origin: Point { x: 0, y: 0 },
-    ///     size: Size { width: 10, height: 20 },
-    /// };
-    /// let lower_left = bounds.lower_left();
-    /// assert_eq!(lower_left, Point { x: 0, y: 20 });
-    /// ```
-    pub fn lower_left(&self) -> Point<T> {
-        Point {
-            x: self.origin.x.clone(),
-            y: self.origin.y.clone() + self.size.height.clone(),
-        }
-    }
-}
-
-impl<T> Bounds<T>
-where
-    T: Add<T, Output = T> + PartialOrd + Clone + Default + Debug,
-{
-    /// Checks if the given point is within the bounds.
-    ///
-    /// This method determines whether a point lies inside the rectangle defined by the bounds,
-    /// including the edges. The point is considered inside if its x-coordinate is greater than
-    /// or equal to the left edge and less than or equal to the right edge, and its y-coordinate
-    /// is greater than or equal to the top edge and less than or equal to the bottom edge of the bounds.
-    ///
-    /// # Arguments
-    ///
-    /// * `point` - A reference to a `Point<T>` that represents the point to check.
-    ///
-    /// # Returns
-    ///
-    /// Returns `true` if the point is within the bounds, `false` otherwise.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::{Point, Bounds};
-    /// let bounds = Bounds {
-    ///     origin: Point { x: 0, y: 0 },
-    ///     size: Size { width: 10, height: 10 },
-    /// };
-    /// let inside_point = Point { x: 5, y: 5 };
-    /// let outside_point = Point { x: 15, y: 15 };
-    ///
-    /// assert!(bounds.contains_point(&inside_point));
-    /// assert!(!bounds.contains_point(&outside_point));
-    /// ```
-    pub fn contains(&self, point: &Point<T>) -> bool {
-        point.x >= self.origin.x
-            && point.x <= self.origin.x.clone() + self.size.width.clone()
-            && point.y >= self.origin.y
-            && point.y <= self.origin.y.clone() + self.size.height.clone()
-    }
-
-    /// Applies a function to the origin and size of the bounds, producing a new `Bounds<U>`.
-    ///
-    /// This method allows for converting a `Bounds<T>` to a `Bounds<U>` by specifying a closure
-    /// that defines how to convert between the two types. The closure is applied to the `origin` and
-    /// `size` fields, resulting in new bounds of the desired type.
-    ///
-    /// # Arguments
-    ///
-    /// * `f` - A closure that takes a value of type `T` and returns a value of type `U`.
-    ///
-    /// # Returns
-    ///
-    /// Returns a new `Bounds<U>` with the origin and size mapped by the provided function.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::{Bounds, Point, Size};
-    /// let bounds = Bounds {
-    ///     origin: Point { x: 10.0, y: 10.0 },
-    ///     size: Size { width: 10.0, height: 20.0 },
-    /// };
-    /// let new_bounds = bounds.map(|value| value as f64 * 1.5);
-    ///
-    /// assert_eq!(new_bounds, Bounds {
-    ///     origin: Point { x: 15.0, y: 15.0 },
-    ///     size: Size { width: 15.0, height: 30.0 },
-    /// });
-    pub fn map<U>(&self, f: impl Fn(T) -> U) -> Bounds<U>
-    where
-        U: Clone + Default + Debug,
-    {
-        Bounds {
-            origin: self.origin.map(&f),
-            size: self.size.map(f),
-        }
-    }
-}
-
-impl Bounds<Pixels> {
-    /// Scales the bounds by a given factor, typically used to adjust for display scaling.
-    ///
-    /// This method multiplies the origin and size of the bounds by the provided scaling factor,
-    /// resulting in a new `Bounds<ScaledPixels>` that is proportionally larger or smaller
-    /// depending on the scaling factor. This can be used to ensure that the bounds are properly
-    /// scaled for different display densities.
-    ///
-    /// # Arguments
-    ///
-    /// * `factor` - The scaling factor to apply to the origin and size, typically the display's scaling factor.
-    ///
-    /// # Returns
-    ///
-    /// Returns a new `Bounds<ScaledPixels>` that represents the scaled bounds.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::{Bounds, Point, Size, Pixels};
-    /// let bounds = Bounds {
-    ///     origin: Point { x: Pixels(10.0), y: Pixels(20.0) },
-    ///     size: Size { width: Pixels(30.0), height: Pixels(40.0) },
-    /// };
-    /// let display_scale_factor = 2.0;
-    /// let scaled_bounds = bounds.scale(display_scale_factor);
-    /// assert_eq!(scaled_bounds, Bounds {
-    ///     origin: Point { x: ScaledPixels(20.0), y: ScaledPixels(40.0) },
-    ///     size: Size { width: ScaledPixels(60.0), height: ScaledPixels(80.0) },
-    /// });
-    /// ```
-    pub fn scale(&self, factor: f32) -> Bounds<ScaledPixels> {
-        Bounds {
-            origin: self.origin.scale(factor),
-            size: self.size.scale(factor),
-        }
-    }
-}
-
-impl<T: Clone + Debug + Copy + Default> Copy for Bounds<T> {}
-
-/// Represents the edges of a box in a 2D space, such as padding or margin.
-///
-/// Each field represents the size of the edge on one side of the box: `top`, `right`, `bottom`, and `left`.
-///
-/// # Examples
-///
-/// ```
-/// # use zed::Edges;
-/// let edges = Edges {
-///     top: 10.0,
-///     right: 20.0,
-///     bottom: 30.0,
-///     left: 40.0,
-/// };
-///
-/// assert_eq!(edges.top, 10.0);
-/// assert_eq!(edges.right, 20.0);
-/// assert_eq!(edges.bottom, 30.0);
-/// assert_eq!(edges.left, 40.0);
-/// ```
-#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
-#[refineable(Debug)]
-#[repr(C)]
-pub struct Edges<T: Clone + Default + Debug> {
-    pub top: T,
-    pub right: T,
-    pub bottom: T,
-    pub left: T,
-}
-
-impl<T> Mul for Edges<T>
-where
-    T: Mul<Output = T> + Clone + Default + Debug,
-{
-    type Output = Self;
-
-    fn mul(self, rhs: Self) -> Self::Output {
-        Self {
-            top: self.top.clone() * rhs.top,
-            right: self.right.clone() * rhs.right,
-            bottom: self.bottom.clone() * rhs.bottom,
-            left: self.left.clone() * rhs.left,
-        }
-    }
-}
-
-impl<T, S> MulAssign<S> for Edges<T>
-where
-    T: Mul<S, Output = T> + Clone + Default + Debug,
-    S: Clone,
-{
-    fn mul_assign(&mut self, rhs: S) {
-        self.top = self.top.clone() * rhs.clone();
-        self.right = self.right.clone() * rhs.clone();
-        self.bottom = self.bottom.clone() * rhs.clone();
-        self.left = self.left.clone() * rhs;
-    }
-}
-
-impl<T: Clone + Default + Debug + Copy> Copy for Edges<T> {}
-
-impl<T: Clone + Default + Debug> Edges<T> {
-    /// Constructs `Edges` where all sides are set to the same specified value.
-    ///
-    /// This function creates an `Edges` instance with the `top`, `right`, `bottom`, and `left` fields all initialized
-    /// to the same value provided as an argument. This is useful when you want to have uniform edges around a box,
-    /// such as padding or margin with the same size on all sides.
-    ///
-    /// # Arguments
-    ///
-    /// * `value` - The value to set for all four sides of the edges.
-    ///
-    /// # Returns
-    ///
-    /// An `Edges` instance with all sides set to the given value.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::Edges;
-    /// let uniform_edges = Edges::all(10.0);
-    /// assert_eq!(uniform_edges.top, 10.0);
-    /// assert_eq!(uniform_edges.right, 10.0);
-    /// assert_eq!(uniform_edges.bottom, 10.0);
-    /// assert_eq!(uniform_edges.left, 10.0);
-    /// ```
-    pub fn all(value: T) -> Self {
-        Self {
-            top: value.clone(),
-            right: value.clone(),
-            bottom: value.clone(),
-            left: value,
-        }
-    }
-
-    /// Applies a function to each field of the `Edges`, producing a new `Edges<U>`.
-    ///
-    /// This method allows for converting an `Edges<T>` to an `Edges<U>` by specifying a closure
-    /// that defines how to convert between the two types. The closure is applied to each field
-    /// (`top`, `right`, `bottom`, `left`), resulting in new edges of the desired type.
-    ///
-    /// # Arguments
-    ///
-    /// * `f` - A closure that takes a reference to a value of type `T` and returns a value of type `U`.
-    ///
-    /// # Returns
-    ///
-    /// Returns a new `Edges<U>` with each field mapped by the provided function.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::Edges;
-    /// let edges = Edges { top: 10, right: 20, bottom: 30, left: 40 };
-    /// let edges_float = edges.map(|&value| value as f32 * 1.1);
-    /// assert_eq!(edges_float, Edges { top: 11.0, right: 22.0, bottom: 33.0, left: 44.0 });
-    /// ```
-    pub fn map<U>(&self, f: impl Fn(&T) -> U) -> Edges<U>
-    where
-        U: Clone + Default + Debug,
-    {
-        Edges {
-            top: f(&self.top),
-            right: f(&self.right),
-            bottom: f(&self.bottom),
-            left: f(&self.left),
-        }
-    }
-
-    /// Checks if any of the edges satisfy a given predicate.
-    ///
-    /// This method applies a predicate function to each field of the `Edges` and returns `true` if any field satisfies the predicate.
-    ///
-    /// # Arguments
-    ///
-    /// * `predicate` - A closure that takes a reference to a value of type `T` and returns a `bool`.
-    ///
-    /// # Returns
-    ///
-    /// Returns `true` if the predicate returns `true` for any of the edge values, `false` otherwise.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::Edges;
-    /// let edges = Edges {
-    ///     top: 10,
-    ///     right: 0,
-    ///     bottom: 5,
-    ///     left: 0,
-    /// };
-    ///
-    /// assert!(edges.any(|value| *value == 0));
-    /// assert!(edges.any(|value| *value > 0));
-    /// assert!(!edges.any(|value| *value > 10));
-    /// ```
-    pub fn any<F: Fn(&T) -> bool>(&self, predicate: F) -> bool {
-        predicate(&self.top)
-            || predicate(&self.right)
-            || predicate(&self.bottom)
-            || predicate(&self.left)
-    }
-}
-
-impl Edges<Length> {
-    /// Sets the edges of the `Edges` struct to `auto`, which is a special value that allows the layout engine to automatically determine the size of the edges.
-    ///
-    /// This is typically used in layout contexts where the exact size of the edges is not important, or when the size should be calculated based on the content or container.
-    ///
-    /// # Returns
-    ///
-    /// Returns an `Edges<Length>` with all edges set to `Length::Auto`.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::Edges;
-    /// let auto_edges = Edges::auto();
-    /// assert_eq!(auto_edges.top, Length::Auto);
-    /// assert_eq!(auto_edges.right, Length::Auto);
-    /// assert_eq!(auto_edges.bottom, Length::Auto);
-    /// assert_eq!(auto_edges.left, Length::Auto);
-    /// ```
-    pub fn auto() -> Self {
-        Self {
-            top: Length::Auto,
-            right: Length::Auto,
-            bottom: Length::Auto,
-            left: Length::Auto,
-        }
-    }
-
-    /// Sets the edges of the `Edges` struct to zero, which means no size or thickness.
-    ///
-    /// This is typically used when you want to specify that a box (like a padding or margin area)
-    /// should have no edges, effectively making it non-existent or invisible in layout calculations.
-    ///
-    /// # Returns
-    ///
-    /// Returns an `Edges<Length>` with all edges set to zero length.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::Edges;
-    /// let no_edges = Edges::zero();
-    /// assert_eq!(no_edges.top, Length::Definite(DefiniteLength::from(Pixels(0.))));
-    /// assert_eq!(no_edges.right, Length::Definite(DefiniteLength::from(Pixels(0.))));
-    /// assert_eq!(no_edges.bottom, Length::Definite(DefiniteLength::from(Pixels(0.))));
-    /// assert_eq!(no_edges.left, Length::Definite(DefiniteLength::from(Pixels(0.))));
-    /// ```
-    pub fn zero() -> Self {
-        Self {
-            top: px(0.).into(),
-            right: px(0.).into(),
-            bottom: px(0.).into(),
-            left: px(0.).into(),
-        }
-    }
-}
-
-impl Edges<DefiniteLength> {
-    /// Sets the edges of the `Edges` struct to zero, which means no size or thickness.
-    ///
-    /// This is typically used when you want to specify that a box (like a padding or margin area)
-    /// should have no edges, effectively making it non-existent or invisible in layout calculations.
-    ///
-    /// # Returns
-    ///
-    /// Returns an `Edges<DefiniteLength>` with all edges set to zero length.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::Edges;
-    /// let no_edges = Edges::zero();
-    /// assert_eq!(no_edges.top, DefiniteLength::from(zed::px(0.)));
-    /// assert_eq!(no_edges.right, DefiniteLength::from(zed::px(0.)));
-    /// assert_eq!(no_edges.bottom, DefiniteLength::from(zed::px(0.)));
-    /// assert_eq!(no_edges.left, DefiniteLength::from(zed::px(0.)));
-    /// ```
-    pub fn zero() -> Self {
-        Self {
-            top: px(0.).into(),
-            right: px(0.).into(),
-            bottom: px(0.).into(),
-            left: px(0.).into(),
-        }
-    }
-
-    /// Converts the `DefiniteLength` to `Pixels` based on the parent size and the REM size.
-    ///
-    /// This method allows for a `DefiniteLength` value to be converted into pixels, taking into account
-    /// the size of the parent element (for percentage-based lengths) and the size of a rem unit (for rem-based lengths).
-    ///
-    /// # Arguments
-    ///
-    /// * `parent_size` - `Size<AbsoluteLength>` representing the size of the parent element.
-    /// * `rem_size` - `Pixels` representing the size of one REM unit.
-    ///
-    /// # Returns
-    ///
-    /// Returns an `Edges<Pixels>` representing the edges with lengths converted to pixels.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::{Edges, DefiniteLength, px, AbsoluteLength, Size};
-    /// let edges = Edges {
-    ///     top: DefiniteLength::Absolute(AbsoluteLength::Pixels(px(10.0))),
-    ///     right: DefiniteLength::Fraction(0.5),
-    ///     bottom: DefiniteLength::Absolute(AbsoluteLength::Rems(rems(2.0))),
-    ///     left: DefiniteLength::Fraction(0.25),
-    /// };
-    /// let parent_size = Size {
-    ///     width: AbsoluteLength::Pixels(px(200.0)),
-    ///     height: AbsoluteLength::Pixels(px(100.0)),
-    /// };
-    /// let rem_size = px(16.0);
-    /// let edges_in_pixels = edges.to_pixels(parent_size, rem_size);
-    ///
-    /// assert_eq!(edges_in_pixels.top, px(10.0)); // Absolute length in pixels
-    /// assert_eq!(edges_in_pixels.right, px(100.0)); // 50% of parent width
-    /// assert_eq!(edges_in_pixels.bottom, px(32.0)); // 2 rems
-    /// assert_eq!(edges_in_pixels.left, px(50.0)); // 25% of parent width
-    /// ```
-    pub fn to_pixels(&self, parent_size: Size<AbsoluteLength>, rem_size: Pixels) -> Edges<Pixels> {
-        Edges {
-            top: self.top.to_pixels(parent_size.height, rem_size),
-            right: self.right.to_pixels(parent_size.width, rem_size),
-            bottom: self.bottom.to_pixels(parent_size.height, rem_size),
-            left: self.left.to_pixels(parent_size.width, rem_size),
-        }
-    }
-}
-
-impl Edges<AbsoluteLength> {
-    /// Sets the edges of the `Edges` struct to zero, which means no size or thickness.
-    ///
-    /// This is typically used when you want to specify that a box (like a padding or margin area)
-    /// should have no edges, effectively making it non-existent or invisible in layout calculations.
-    ///
-    /// # Returns
-    ///
-    /// Returns an `Edges<AbsoluteLength>` with all edges set to zero length.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::Edges;
-    /// let no_edges = Edges::zero();
-    /// assert_eq!(no_edges.top, AbsoluteLength::Pixels(Pixels(0.0)));
-    /// assert_eq!(no_edges.right, AbsoluteLength::Pixels(Pixels(0.0)));
-    /// assert_eq!(no_edges.bottom, AbsoluteLength::Pixels(Pixels(0.0)));
-    /// assert_eq!(no_edges.left, AbsoluteLength::Pixels(Pixels(0.0)));
-    /// ```
-    pub fn zero() -> Self {
-        Self {
-            top: px(0.).into(),
-            right: px(0.).into(),
-            bottom: px(0.).into(),
-            left: px(0.).into(),
-        }
-    }
-
-    /// Converts the `AbsoluteLength` to `Pixels` based on the `rem_size`.
-    ///
-    /// If the `AbsoluteLength` is already in pixels, it simply returns the corresponding `Pixels` value.
-    /// If the `AbsoluteLength` is in rems, it multiplies the number of rems by the `rem_size` to convert it to pixels.
-    ///
-    /// # Arguments
-    ///
-    /// * `rem_size` - The size of one rem unit in pixels.
-    ///
-    /// # Returns
-    ///
-    /// Returns an `Edges<Pixels>` representing the edges with lengths converted to pixels.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::{Edges, AbsoluteLength, Pixels, px};
-    /// let edges = Edges {
-    ///     top: AbsoluteLength::Pixels(px(10.0)),
-    ///     right: AbsoluteLength::Rems(rems(1.0)),
-    ///     bottom: AbsoluteLength::Pixels(px(20.0)),
-    ///     left: AbsoluteLength::Rems(rems(2.0)),
-    /// };
-    /// let rem_size = px(16.0);
-    /// let edges_in_pixels = edges.to_pixels(rem_size);
-    ///
-    /// assert_eq!(edges_in_pixels.top, px(10.0)); // Already in pixels
-    /// assert_eq!(edges_in_pixels.right, px(16.0)); // 1 rem converted to pixels
-    /// assert_eq!(edges_in_pixels.bottom, px(20.0)); // Already in pixels
-    /// assert_eq!(edges_in_pixels.left, px(32.0)); // 2 rems converted to pixels
-    /// ```
-    pub fn to_pixels(&self, rem_size: Pixels) -> Edges<Pixels> {
-        Edges {
-            top: self.top.to_pixels(rem_size),
-            right: self.right.to_pixels(rem_size),
-            bottom: self.bottom.to_pixels(rem_size),
-            left: self.left.to_pixels(rem_size),
-        }
-    }
-}
-
-impl Edges<Pixels> {
-    /// Scales the `Edges<Pixels>` by a given factor, returning `Edges<ScaledPixels>`.
-    ///
-    /// This method is typically used for adjusting the edge sizes for different display densities or scaling factors.
-    ///
-    /// # Arguments
-    ///
-    /// * `factor` - The scaling factor to apply to each edge.
-    ///
-    /// # Returns
-    ///
-    /// Returns a new `Edges<ScaledPixels>` where each edge is the result of scaling the original edge by the given factor.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::{Edges, Pixels};
-    /// let edges = Edges {
-    ///     top: Pixels(10.0),
-    ///     right: Pixels(20.0),
-    ///     bottom: Pixels(30.0),
-    ///     left: Pixels(40.0),
-    /// };
-    /// let scaled_edges = edges.scale(2.0);
-    /// assert_eq!(scaled_edges.top, ScaledPixels(20.0));
-    /// assert_eq!(scaled_edges.right, ScaledPixels(40.0));
-    /// assert_eq!(scaled_edges.bottom, ScaledPixels(60.0));
-    /// assert_eq!(scaled_edges.left, ScaledPixels(80.0));
-    /// ```
-    pub fn scale(&self, factor: f32) -> Edges<ScaledPixels> {
-        Edges {
-            top: self.top.scale(factor),
-            right: self.right.scale(factor),
-            bottom: self.bottom.scale(factor),
-            left: self.left.scale(factor),
-        }
-    }
-
-    /// Returns the maximum value of any edge.
-    ///
-    /// # Returns
-    ///
-    /// The maximum `Pixels` value among all four edges.
-    pub fn max(&self) -> Pixels {
-        self.top.max(self.right).max(self.bottom).max(self.left)
-    }
-}
-
-impl From<f32> for Edges<Pixels> {
-    fn from(val: f32) -> Self {
-        Edges {
-            top: val.into(),
-            right: val.into(),
-            bottom: val.into(),
-            left: val.into(),
-        }
-    }
-}
-
-/// Represents the corners of a box in a 2D space, such as border radius.
-///
-/// Each field represents the size of the corner on one side of the box: `top_left`, `top_right`, `bottom_right`, and `bottom_left`.
-/// ```
-#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
-#[refineable(Debug)]
-#[repr(C)]
-pub struct Corners<T: Clone + Default + Debug> {
-    pub top_left: T,
-    pub top_right: T,
-    pub bottom_right: T,
-    pub bottom_left: T,
-}
-
-impl<T> Corners<T>
-where
-    T: Clone + Default + Debug,
-{
-    /// Constructs `Corners` where all sides are set to the same specified value.
-    ///
-    /// This function creates a `Corners` instance with the `top_left`, `top_right`, `bottom_right`, and `bottom_left` fields all initialized
-    /// to the same value provided as an argument. This is useful when you want to have uniform corners around a box,
-    /// such as a uniform border radius on a rectangle.
-    ///
-    /// # Arguments
-    ///
-    /// * `value` - The value to set for all four corners.
-    ///
-    /// # Returns
-    ///
-    /// An `Corners` instance with all corners set to the given value.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::Corners;
-    /// let uniform_corners = Corners::all(5.0);
-    /// assert_eq!(uniform_corners.top_left, 5.0);
-    /// assert_eq!(uniform_corners.top_right, 5.0);
-    /// assert_eq!(uniform_corners.bottom_right, 5.0);
-    /// assert_eq!(uniform_corners.bottom_left, 5.0);
-    /// ```
-    pub fn all(value: T) -> Self {
-        Self {
-            top_left: value.clone(),
-            top_right: value.clone(),
-            bottom_right: value.clone(),
-            bottom_left: value,
-        }
-    }
-}
-
-impl Corners<AbsoluteLength> {
-    /// Converts the `AbsoluteLength` to `Pixels` based on the provided size and rem size, ensuring the resulting
-    /// `Pixels` do not exceed half of the maximum of the provided size's width and height.
-    ///
-    /// This method is particularly useful when dealing with corner radii, where the radius in pixels should not
-    /// exceed half the size of the box it applies to, to avoid the corners overlapping.
-    ///
-    /// # Arguments
-    ///
-    /// * `size` - The `Size<Pixels>` against which the maximum allowable radius is determined.
-    /// * `rem_size` - The size of one REM unit in pixels, used for conversion if the `AbsoluteLength` is in REMs.
-    ///
-    /// # Returns
-    ///
-    /// Returns a `Corners<Pixels>` instance with each corner's length converted to pixels and clamped to the
-    /// maximum allowable radius based on the provided size.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::{Corners, AbsoluteLength, Pixels, Size};
-    /// let corners = Corners {
-    ///     top_left: AbsoluteLength::Pixels(Pixels(15.0)),
-    ///     top_right: AbsoluteLength::Rems(Rems(1.0)),
-    ///     bottom_right: AbsoluteLength::Pixels(Pixels(20.0)),
-    ///     bottom_left: AbsoluteLength::Rems(Rems(2.0)),
-    /// };
-    /// let size = Size { width: Pixels(100.0), height: Pixels(50.0) };
-    /// let rem_size = Pixels(16.0);
-    /// let corners_in_pixels = corners.to_pixels(size, rem_size);
-    ///
-    /// // The resulting corners should not exceed half the size of the smallest dimension (50.0 / 2.0 = 25.0).
-    /// assert_eq!(corners_in_pixels.top_left, Pixels(15.0));
-    /// assert_eq!(corners_in_pixels.top_right, Pixels(16.0)); // 1 rem converted to pixels
-    /// assert_eq!(corners_in_pixels.bottom_right, Pixels(20.0).min(Pixels(25.0))); // Clamped to 25.0
-    /// assert_eq!(corners_in_pixels.bottom_left, Pixels(32.0).min(Pixels(25.0))); // 2 rems converted to pixels and clamped
-    /// ```
-    pub fn to_pixels(&self, size: Size<Pixels>, rem_size: Pixels) -> Corners<Pixels> {
-        let max = size.width.max(size.height) / 2.;
-        Corners {
-            top_left: self.top_left.to_pixels(rem_size).min(max),
-            top_right: self.top_right.to_pixels(rem_size).min(max),
-            bottom_right: self.bottom_right.to_pixels(rem_size).min(max),
-            bottom_left: self.bottom_left.to_pixels(rem_size).min(max),
-        }
-    }
-}
-
-impl Corners<Pixels> {
-    /// Scales the `Corners<Pixels>` by a given factor, returning `Corners<ScaledPixels>`.
-    ///
-    /// This method is typically used for adjusting the corner sizes for different display densities or scaling factors.
-    ///
-    /// # Arguments
-    ///
-    /// * `factor` - The scaling factor to apply to each corner.
-    ///
-    /// # Returns
-    ///
-    /// Returns a new `Corners<ScaledPixels>` where each corner is the result of scaling the original corner by the given factor.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::{Corners, Pixels};
-    /// let corners = Corners {
-    ///     top_left: Pixels(10.0),
-    ///     top_right: Pixels(20.0),
-    ///     bottom_right: Pixels(30.0),
-    ///     bottom_left: Pixels(40.0),
-    /// };
-    /// let scaled_corners = corners.scale(2.0);
-    /// assert_eq!(scaled_corners.top_left, ScaledPixels(20.0));
-    /// assert_eq!(scaled_corners.top_right, ScaledPixels(40.0));
-    /// assert_eq!(scaled_corners.bottom_right, ScaledPixels(60.0));
-    /// assert_eq!(scaled_corners.bottom_left, ScaledPixels(80.0));
-    /// ```
-    pub fn scale(&self, factor: f32) -> Corners<ScaledPixels> {
-        Corners {
-            top_left: self.top_left.scale(factor),
-            top_right: self.top_right.scale(factor),
-            bottom_right: self.bottom_right.scale(factor),
-            bottom_left: self.bottom_left.scale(factor),
-        }
-    }
-
-    /// Returns the maximum value of any corner.
-    ///
-    /// # Returns
-    ///
-    /// The maximum `Pixels` value among all four corners.
-    pub fn max(&self) -> Pixels {
-        self.top_left
-            .max(self.top_right)
-            .max(self.bottom_right)
-            .max(self.bottom_left)
-    }
-}
-
-impl<T: Clone + Default + Debug> Corners<T> {
-    /// Applies a function to each field of the `Corners`, producing a new `Corners<U>`.
-    ///
-    /// This method allows for converting a `Corners<T>` to a `Corners<U>` by specifying a closure
-    /// that defines how to convert between the two types. The closure is applied to each field
-    /// (`top_left`, `top_right`, `bottom_right`, `bottom_left`), resulting in new corners of the desired type.
-    ///
-    /// # Arguments
-    ///
-    /// * `f` - A closure that takes a reference to a value of type `T` and returns a value of type `U`.
-    ///
-    /// # Returns
-    ///
-    /// Returns a new `Corners<U>` with each field mapped by the provided function.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::{Corners, Pixels};
-    /// let corners = Corners {
-    ///     top_left: Pixels(10.0),
-    ///     top_right: Pixels(20.0),
-    ///     bottom_right: Pixels(30.0),
-    ///     bottom_left: Pixels(40.0),
-    /// };
-    /// let corners_in_rems = corners.map(|&px| Rems(px.0 / 16.0));
-    /// assert_eq!(corners_in_rems, Corners {
-    ///     top_left: Rems(0.625),
-    ///     top_right: Rems(1.25),
-    ///     bottom_right: Rems(1.875),
-    ///     bottom_left: Rems(2.5),
-    /// });
-    /// ```
-    pub fn map<U>(&self, f: impl Fn(&T) -> U) -> Corners<U>
-    where
-        U: Clone + Default + Debug,
-    {
-        Corners {
-            top_left: f(&self.top_left),
-            top_right: f(&self.top_right),
-            bottom_right: f(&self.bottom_right),
-            bottom_left: f(&self.bottom_left),
-        }
-    }
-}
-
-impl<T> Mul for Corners<T>
-where
-    T: Mul<Output = T> + Clone + Default + Debug,
-{
-    type Output = Self;
-
-    fn mul(self, rhs: Self) -> Self::Output {
-        Self {
-            top_left: self.top_left.clone() * rhs.top_left,
-            top_right: self.top_right.clone() * rhs.top_right,
-            bottom_right: self.bottom_right.clone() * rhs.bottom_right,
-            bottom_left: self.bottom_left.clone() * rhs.bottom_left,
-        }
-    }
-}
-
-impl<T, S> MulAssign<S> for Corners<T>
-where
-    T: Mul<S, Output = T> + Clone + Default + Debug,
-    S: Clone,
-{
-    fn mul_assign(&mut self, rhs: S) {
-        self.top_left = self.top_left.clone() * rhs.clone();
-        self.top_right = self.top_right.clone() * rhs.clone();
-        self.bottom_right = self.bottom_right.clone() * rhs.clone();
-        self.bottom_left = self.bottom_left.clone() * rhs;
-    }
-}
-
-impl<T> Copy for Corners<T> where T: Copy + Clone + Default + Debug {}
-
-impl From<f32> for Corners<Pixels> {
-    fn from(val: f32) -> Self {
-        Corners {
-            top_left: val.into(),
-            top_right: val.into(),
-            bottom_right: val.into(),
-            bottom_left: val.into(),
-        }
-    }
-}
-
-impl From<Pixels> for Corners<Pixels> {
-    fn from(val: Pixels) -> Self {
-        Corners {
-            top_left: val,
-            top_right: val,
-            bottom_right: val,
-            bottom_left: val,
-        }
-    }
-}
-
-/// Represents a length in pixels, the base unit of measurement in the UI framework.
-///
-/// `Pixels` is a value type that represents an absolute length in pixels, which is used
-/// for specifying sizes, positions, and distances in the UI. It is the fundamental unit
-/// of measurement for all visual elements and layout calculations.
-///
-/// The inner value is an `f32`, allowing for sub-pixel precision which can be useful for
-/// anti-aliasing and animations. However, when applied to actual pixel grids, the value
-/// is typically rounded to the nearest integer.
-///
-/// # Examples
-///
-/// ```
-/// use zed::Pixels;
-///
-/// // Define a length of 10 pixels
-/// let length = Pixels(10.0);
-///
-/// // Define a length and scale it by a factor of 2
-/// let scaled_length = length.scale(2.0);
-/// assert_eq!(scaled_length, Pixels(20.0));
-/// ```
-#[derive(
-    Clone,
-    Copy,
-    Default,
-    Add,
-    AddAssign,
-    Sub,
-    SubAssign,
-    Neg,
-    Div,
-    DivAssign,
-    PartialEq,
-    Serialize,
-    Deserialize,
-)]
-#[repr(transparent)]
-pub struct Pixels(pub f32);
-
-impl std::ops::Div for Pixels {
-    type Output = f32;
-
-    fn div(self, rhs: Self) -> Self::Output {
-        self.0 / rhs.0
-    }
-}
-
-impl std::ops::DivAssign for Pixels {
-    fn div_assign(&mut self, rhs: Self) {
-        *self = Self(self.0 / rhs.0);
-    }
-}
-
-impl std::ops::RemAssign for Pixels {
-    fn rem_assign(&mut self, rhs: Self) {
-        self.0 %= rhs.0;
-    }
-}
-
-impl std::ops::Rem for Pixels {
-    type Output = Self;
-
-    fn rem(self, rhs: Self) -> Self {
-        Self(self.0 % rhs.0)
-    }
-}
-
-impl Mul<f32> for Pixels {
-    type Output = Pixels;
-
-    fn mul(self, other: f32) -> Pixels {
-        Pixels(self.0 * other)
-    }
-}
-
-impl Mul<usize> for Pixels {
-    type Output = Pixels;
-
-    fn mul(self, other: usize) -> Pixels {
-        Pixels(self.0 * other as f32)
-    }
-}
-
-impl Mul<Pixels> for f32 {
-    type Output = Pixels;
-
-    fn mul(self, rhs: Pixels) -> Self::Output {
-        Pixels(self * rhs.0)
-    }
-}
-
-impl MulAssign<f32> for Pixels {
-    fn mul_assign(&mut self, other: f32) {
-        self.0 *= other;
-    }
-}
-
-impl Pixels {
-    /// Represents zero pixels.
-    pub const ZERO: Pixels = Pixels(0.0);
-    /// The maximum value that can be represented by `Pixels`.
-    pub const MAX: Pixels = Pixels(f32::MAX);
-
-    /// Floors the `Pixels` value to the nearest whole number.
-    ///
-    /// # Returns
-    ///
-    /// Returns a new `Pixels` instance with the floored value.
-    pub fn floor(&self) -> Self {
-        Self(self.0.floor())
-    }
-
-    /// Rounds the `Pixels` value to the nearest whole number.
-    ///
-    /// # Returns
-    ///
-    /// Returns a new `Pixels` instance with the rounded value.
-    pub fn round(&self) -> Self {
-        Self(self.0.round())
-    }
-
-    /// Returns the ceiling of the `Pixels` value to the nearest whole number.
-    ///
-    /// # Returns
-    ///
-    /// Returns a new `Pixels` instance with the ceiling value.
-    pub fn ceil(&self) -> Self {
-        Self(self.0.ceil())
-    }
-
-    /// Scales the `Pixels` value by a given factor, producing `ScaledPixels`.
-    ///
-    /// This method is used when adjusting pixel values for display scaling factors,
-    /// such as high DPI (dots per inch) or Retina displays, where the pixel density is higher and
-    /// thus requires scaling to maintain visual consistency and readability.
-    ///
-    /// The resulting `ScaledPixels` represent the scaled value which can be used for rendering
-    /// calculations where display scaling is considered.
-    pub fn scale(&self, factor: f32) -> ScaledPixels {
-        ScaledPixels(self.0 * factor)
-    }
-
-    /// Raises the `Pixels` value to a given power.
-    ///
-    /// # Arguments
-    ///
-    /// * `exponent` - The exponent to raise the `Pixels` value by.
-    ///
-    /// # Returns
-    ///
-    /// Returns a new `Pixels` instance with the value raised to the given exponent.
-    pub fn pow(&self, exponent: f32) -> Self {
-        Self(self.0.powf(exponent))
-    }
-
-    /// Returns the absolute value of the `Pixels`.
-    ///
-    /// # Returns
-    ///
-    /// A new `Pixels` instance with the absolute value of the original `Pixels`.
-    pub fn abs(&self) -> Self {
-        Self(self.0.abs())
-    }
-}
-
-impl Mul<Pixels> for Pixels {
-    type Output = Pixels;
-
-    fn mul(self, rhs: Pixels) -> Self::Output {
-        Pixels(self.0 * rhs.0)
-    }
-}
-
-impl Eq for Pixels {}
-
-impl PartialOrd for Pixels {
-    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
-        self.0.partial_cmp(&other.0)
-    }
-}
-
-impl Ord for Pixels {
-    fn cmp(&self, other: &Self) -> cmp::Ordering {
-        self.partial_cmp(other).unwrap()
-    }
-}
-
-impl std::hash::Hash for Pixels {
-    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
-        self.0.to_bits().hash(state);
-    }
-}
-
-impl From<f64> for Pixels {
-    fn from(pixels: f64) -> Self {
-        Pixels(pixels as f32)
-    }
-}
-
-impl From<f32> for Pixels {
-    fn from(pixels: f32) -> Self {
-        Pixels(pixels)
-    }
-}
-
-impl Debug for Pixels {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "{} px", self.0)
-    }
-}
-
-impl From<Pixels> for f32 {
-    fn from(pixels: Pixels) -> Self {
-        pixels.0
-    }
-}
-
-impl From<&Pixels> for f32 {
-    fn from(pixels: &Pixels) -> Self {
-        pixels.0
-    }
-}
-
-impl From<Pixels> for f64 {
-    fn from(pixels: Pixels) -> Self {
-        pixels.0 as f64
-    }
-}
-
-impl From<Pixels> for u32 {
-    fn from(pixels: Pixels) -> Self {
-        pixels.0 as u32
-    }
-}
-
-impl From<u32> for Pixels {
-    fn from(pixels: u32) -> Self {
-        Pixels(pixels as f32)
-    }
-}
-
-impl From<Pixels> for usize {
-    fn from(pixels: Pixels) -> Self {
-        pixels.0 as usize
-    }
-}
-
-impl From<usize> for Pixels {
-    fn from(pixels: usize) -> Self {
-        Pixels(pixels as f32)
-    }
-}
-
-/// Represents physical pixels on the display.
-///
-/// `DevicePixels` is a unit of measurement that refers to the actual pixels on a device's screen.
-/// This type is used when precise pixel manipulation is required, such as rendering graphics or
-/// interfacing with hardware that operates on the pixel level. Unlike logical pixels that may be
-/// affected by the device's scale factor, `DevicePixels` always correspond to real pixels on the
-/// display.
-#[derive(
-    Add, AddAssign, Clone, Copy, Default, Div, Eq, Hash, Ord, PartialEq, PartialOrd, Sub, SubAssign,
-)]
-#[repr(transparent)]
-pub struct DevicePixels(pub(crate) i32);
-
-impl DevicePixels {
-    /// Converts the `DevicePixels` value to the number of bytes needed to represent it in memory.
-    ///
-    /// This function is useful when working with graphical data that needs to be stored in a buffer,
-    /// such as images or framebuffers, where each pixel may be represented by a specific number of bytes.
-    ///
-    /// # Arguments
-    ///
-    /// * `bytes_per_pixel` - The number of bytes used to represent a single pixel.
-    ///
-    /// # Returns
-    ///
-    /// The number of bytes required to represent the `DevicePixels` value in memory.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::DevicePixels;
-    /// let pixels = DevicePixels(10); // 10 device pixels
-    /// let bytes_per_pixel = 4; // Assume each pixel is represented by 4 bytes (e.g., RGBA)
-    /// let total_bytes = pixels.to_bytes(bytes_per_pixel);
-    /// assert_eq!(total_bytes, 40); // 10 pixels * 4 bytes/pixel = 40 bytes
-    /// ```
-    pub fn to_bytes(&self, bytes_per_pixel: u8) -> u32 {
-        self.0 as u32 * bytes_per_pixel as u32
-    }
-}
-
-impl fmt::Debug for DevicePixels {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "{} px (device)", self.0)
-    }
-}
-
-impl From<DevicePixels> for i32 {
-    fn from(device_pixels: DevicePixels) -> Self {
-        device_pixels.0
-    }
-}
-
-impl From<i32> for DevicePixels {
-    fn from(device_pixels: i32) -> Self {
-        DevicePixels(device_pixels)
-    }
-}
-
-impl From<u32> for DevicePixels {
-    fn from(device_pixels: u32) -> Self {
-        DevicePixels(device_pixels as i32)
-    }
-}
-
-impl From<DevicePixels> for u32 {
-    fn from(device_pixels: DevicePixels) -> Self {
-        device_pixels.0 as u32
-    }
-}
-
-impl From<DevicePixels> for u64 {
-    fn from(device_pixels: DevicePixels) -> Self {
-        device_pixels.0 as u64
-    }
-}
-
-impl From<u64> for DevicePixels {
-    fn from(device_pixels: u64) -> Self {
-        DevicePixels(device_pixels as i32)
-    }
-}
-
-impl From<DevicePixels> for usize {
-    fn from(device_pixels: DevicePixels) -> Self {
-        device_pixels.0 as usize
-    }
-}
-
-impl From<usize> for DevicePixels {
-    fn from(device_pixels: usize) -> Self {
-        DevicePixels(device_pixels as i32)
-    }
-}
-
-/// Represents scaled pixels that take into account the device's scale factor.
-///
-/// `ScaledPixels` are used to ensure that UI elements appear at the correct size on devices
-/// with different pixel densities. When a device has a higher scale factor (such as Retina displays),
-/// a single logical pixel may correspond to multiple physical pixels. By using `ScaledPixels`,
-/// dimensions and positions can be specified in a way that scales appropriately across different
-/// display resolutions.
-#[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd)]
-#[repr(transparent)]
-pub struct ScaledPixels(pub(crate) f32);
-
-impl ScaledPixels {
-    /// Floors the `ScaledPixels` value to the nearest whole number.
-    ///
-    /// # Returns
-    ///
-    /// Returns a new `ScaledPixels` instance with the floored value.
-    pub fn floor(&self) -> Self {
-        Self(self.0.floor())
-    }
-
-    /// Rounds the `ScaledPixels` value to the nearest whole number.
-    ///
-    /// # Returns
-    ///
-    /// Returns a new `ScaledPixels` instance with the rounded value.
-    pub fn ceil(&self) -> Self {
-        Self(self.0.ceil())
-    }
-}
-
-impl Eq for ScaledPixels {}
-
-impl Debug for ScaledPixels {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "{} px (scaled)", self.0)
-    }
-}
-
-impl From<ScaledPixels> for DevicePixels {
-    fn from(scaled: ScaledPixels) -> Self {
-        DevicePixels(scaled.0.ceil() as i32)
-    }
-}
-
-impl From<DevicePixels> for ScaledPixels {
-    fn from(device: DevicePixels) -> Self {
-        ScaledPixels(device.0 as f32)
-    }
-}
-
-impl From<ScaledPixels> for f64 {
-    fn from(scaled_pixels: ScaledPixels) -> Self {
-        scaled_pixels.0 as f64
-    }
-}
-
-/// Represents pixels in a global coordinate space, which can span across multiple displays.
-///
-/// `GlobalPixels` is used when dealing with a coordinate system that is not limited to a single
-/// display's boundaries. This type is particularly useful in multi-monitor setups where
-/// positioning and measurements need to be consistent and relative to a "global" origin point
-/// rather than being relative to any individual display.
-#[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd)]
-#[repr(transparent)]
-pub struct GlobalPixels(pub(crate) f32);
-
-impl Debug for GlobalPixels {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "{} px (global coordinate space)", self.0)
-    }
-}
-
-impl From<GlobalPixels> for f64 {
-    fn from(global_pixels: GlobalPixels) -> Self {
-        global_pixels.0 as f64
-    }
-}
-
-impl From<f64> for GlobalPixels {
-    fn from(global_pixels: f64) -> Self {
-        GlobalPixels(global_pixels as f32)
-    }
-}
-
-impl sqlez::bindable::StaticColumnCount for GlobalPixels {}
-
-impl sqlez::bindable::Bind for GlobalPixels {
-    fn bind(
-        &self,
-        statement: &sqlez::statement::Statement,
-        start_index: i32,
-    ) -> anyhow::Result<i32> {
-        self.0.bind(statement, start_index)
-    }
-}
-
-/// Represents a length in rems, a unit based on the font-size of the window, which can be assigned with [WindowContext::set_rem_size].
-///
-/// Rems are used for defining lengths that are scalable and consistent across different UI elements.
-/// The value of `1rem` is typically equal to the font-size of the root element (often the `<html>` element in browsers),
-/// making it a flexible unit that adapts to the user's text size preferences. In this framework, `rems` serve a similar
-/// purpose, allowing for scalable and accessible design that can adjust to different display settings or user preferences.
-///
-/// For example, if the root element's font-size is `16px`, then `1rem` equals `16px`. A length of `2rems` would then be `32px`.
-#[derive(Clone, Copy, Default, Add, Sub, Mul, Div, Neg)]
-pub struct Rems(pub f32);
-
-impl Mul<Pixels> for Rems {
-    type Output = Pixels;
-
-    fn mul(self, other: Pixels) -> Pixels {
-        Pixels(self.0 * other.0)
-    }
-}
-
-impl Debug for Rems {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "{} rem", self.0)
-    }
-}
-
-/// Represents an absolute length in pixels or rems.
-///
-/// `AbsoluteLength` can be either a fixed number of pixels, which is an absolute measurement not
-/// affected by the current font size, or a number of rems, which is relative to the font size of
-/// the root element. It is used for specifying dimensions that are either independent of or
-/// related to the typographic scale.
-#[derive(Clone, Copy, Debug, Neg)]
-pub enum AbsoluteLength {
-    /// A length in pixels.
-    Pixels(Pixels),
-    /// A length in rems.
-    Rems(Rems),
-}
-
-impl AbsoluteLength {
-    /// Checks if the absolute length is zero.
-    pub fn is_zero(&self) -> bool {
-        match self {
-            AbsoluteLength::Pixels(px) => px.0 == 0.0,
-            AbsoluteLength::Rems(rems) => rems.0 == 0.0,
-        }
-    }
-}
-
-impl From<Pixels> for AbsoluteLength {
-    fn from(pixels: Pixels) -> Self {
-        AbsoluteLength::Pixels(pixels)
-    }
-}
-
-impl From<Rems> for AbsoluteLength {
-    fn from(rems: Rems) -> Self {
-        AbsoluteLength::Rems(rems)
-    }
-}
-
-impl AbsoluteLength {
-    /// Converts an `AbsoluteLength` to `Pixels` based on a given `rem_size`.
-    ///
-    /// # Arguments
-    ///
-    /// * `rem_size` - The size of one rem in pixels.
-    ///
-    /// # Returns
-    ///
-    /// Returns the `AbsoluteLength` as `Pixels`.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::{AbsoluteLength, Pixels};
-    /// let length_in_pixels = AbsoluteLength::Pixels(Pixels(42.0));
-    /// let length_in_rems = AbsoluteLength::Rems(Rems(2.0));
-    /// let rem_size = Pixels(16.0);
-    ///
-    /// assert_eq!(length_in_pixels.to_pixels(rem_size), Pixels(42.0));
-    /// assert_eq!(length_in_rems.to_pixels(rem_size), Pixels(32.0));
-    /// ```
-    pub fn to_pixels(&self, rem_size: Pixels) -> Pixels {
-        match self {
-            AbsoluteLength::Pixels(pixels) => *pixels,
-            AbsoluteLength::Rems(rems) => *rems * rem_size,
-        }
-    }
-}
-
-impl Default for AbsoluteLength {
-    fn default() -> Self {
-        px(0.).into()
-    }
-}
-
-/// A non-auto length that can be defined in pixels, rems, or percent of parent.
-///
-/// This enum represents lengths that have a specific value, as opposed to lengths that are automatically
-/// determined by the context. It includes absolute lengths in pixels or rems, and relative lengths as a
-/// fraction of the parent's size.
-#[derive(Clone, Copy, Neg)]
-pub enum DefiniteLength {
-    /// An absolute length specified in pixels or rems.
-    Absolute(AbsoluteLength),
-    /// A relative length specified as a fraction of the parent's size, between 0 and 1.
-    Fraction(f32),
-}
-
-impl DefiniteLength {
-    /// Converts the `DefiniteLength` to `Pixels` based on a given `base_size` and `rem_size`.
-    ///
-    /// If the `DefiniteLength` is an absolute length, it will be directly converted to `Pixels`.
-    /// If it is a fraction, the fraction will be multiplied by the `base_size` to get the length in pixels.
-    ///
-    /// # Arguments
-    ///
-    /// * `base_size` - The base size in `AbsoluteLength` to which the fraction will be applied.
-    /// * `rem_size` - The size of one rem in pixels, used to convert rems to pixels.
-    ///
-    /// # Returns
-    ///
-    /// Returns the `DefiniteLength` as `Pixels`.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use zed::{DefiniteLength, AbsoluteLength, Pixels, px, rems};
-    /// let length_in_pixels = DefiniteLength::Absolute(AbsoluteLength::Pixels(px(42.0)));
-    /// let length_in_rems = DefiniteLength::Absolute(AbsoluteLength::Rems(rems(2.0)));
-    /// let length_as_fraction = DefiniteLength::Fraction(0.5);
-    /// let base_size = AbsoluteLength::Pixels(px(100.0));
-    /// let rem_size = px(16.0);
-    ///
-    /// assert_eq!(length_in_pixels.to_pixels(base_size, rem_size), Pixels(42.0));
-    /// assert_eq!(length_in_rems.to_pixels(base_size, rem_size), Pixels(32.0));
-    /// assert_eq!(length_as_fraction.to_pixels(base_size, rem_size), Pixels(50.0));
-    /// ```
-    pub fn to_pixels(&self, base_size: AbsoluteLength, rem_size: Pixels) -> Pixels {
-        match self {
-            DefiniteLength::Absolute(size) => size.to_pixels(rem_size),
-            DefiniteLength::Fraction(fraction) => match base_size {
-                AbsoluteLength::Pixels(px) => px * *fraction,
-                AbsoluteLength::Rems(rems) => rems * rem_size * *fraction,
-            },
-        }
-    }
-}
-
-impl Debug for DefiniteLength {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            DefiniteLength::Absolute(length) => Debug::fmt(length, f),
-            DefiniteLength::Fraction(fract) => write!(f, "{}%", (fract * 100.0) as i32),
-        }
-    }
-}
-
-impl From<Pixels> for DefiniteLength {
-    fn from(pixels: Pixels) -> Self {
-        Self::Absolute(pixels.into())
-    }
-}
-
-impl From<Rems> for DefiniteLength {
-    fn from(rems: Rems) -> Self {
-        Self::Absolute(rems.into())
-    }
-}
-
-impl From<AbsoluteLength> for DefiniteLength {
-    fn from(length: AbsoluteLength) -> Self {
-        Self::Absolute(length)
-    }
-}
-
-impl Default for DefiniteLength {
-    fn default() -> Self {
-        Self::Absolute(AbsoluteLength::default())
-    }
-}
-
-/// A length that can be defined in pixels, rems, percent of parent, or auto.
-#[derive(Clone, Copy)]
-pub enum Length {
-    /// A definite length specified either in pixels, rems, or as a fraction of the parent's size.
-    Definite(DefiniteLength),
-    /// An automatic length that is determined by the context in which it is used.
-    Auto,
-}
-
-impl Debug for Length {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            Length::Definite(definite_length) => write!(f, "{:?}", definite_length),
-            Length::Auto => write!(f, "auto"),
-        }
-    }
-}
-
-/// Constructs a `DefiniteLength` representing a relative fraction of a parent size.
-///
-/// This function creates a `DefiniteLength` that is a specified fraction of a parent's dimension.
-/// The fraction should be a floating-point number between 0.0 and 1.0, where 1.0 represents 100% of the parent's size.
-///
-/// # Arguments
-///
-/// * `fraction` - The fraction of the parent's size, between 0.0 and 1.0.
-///
-/// # Returns
-///
-/// A `DefiniteLength` representing the relative length as a fraction of the parent's size.
-pub fn relative(fraction: f32) -> DefiniteLength {
-    DefiniteLength::Fraction(fraction)
-}
-
-/// Returns the Golden Ratio, i.e. `~(1.0 + sqrt(5.0)) / 2.0`.
-pub fn phi() -> DefiniteLength {
-    relative(1.618_034)
-}
-
-/// Constructs a `Rems` value representing a length in rems.
-///
-/// # Arguments
-///
-/// * `rems` - The number of rems for the length.
-///
-/// # Returns
-///
-/// A `Rems` representing the specified number of rems.
-pub fn rems(rems: f32) -> Rems {
-    Rems(rems)
-}
-
-/// Constructs a `Pixels` value representing a length in pixels.
-///
-/// # Arguments
-///
-/// * `pixels` - The number of pixels for the length.
-///
-/// # Returns
-///
-/// A `Pixels` representing the specified number of pixels.
-pub const fn px(pixels: f32) -> Pixels {
-    Pixels(pixels)
-}
-
-/// Returns a `Length` representing an automatic length.
-///
-/// The `auto` length is often used in layout calculations where the length should be determined
-/// by the layout context itself rather than being explicitly set. This is commonly used in CSS
-/// for properties like `width`, `height`, `margin`, `padding`, etc., where `auto` can be used
-/// to instruct the layout engine to calculate the size based on other factors like the size of the
-/// container or the intrinsic size of the content.
-///
-/// # Returns
-///
-/// A `Length` variant set to `Auto`.
-pub fn auto() -> Length {
-    Length::Auto
-}
-
-impl From<Pixels> for Length {
-    fn from(pixels: Pixels) -> Self {
-        Self::Definite(pixels.into())
-    }
-}
-
-impl From<Rems> for Length {
-    fn from(rems: Rems) -> Self {
-        Self::Definite(rems.into())
-    }
-}
-
-impl From<DefiniteLength> for Length {
-    fn from(length: DefiniteLength) -> Self {
-        Self::Definite(length)
-    }
-}
-
-impl From<AbsoluteLength> for Length {
-    fn from(length: AbsoluteLength) -> Self {
-        Self::Definite(length.into())
-    }
-}
-
-impl Default for Length {
-    fn default() -> Self {
-        Self::Definite(DefiniteLength::default())
-    }
-}
-
-impl From<()> for Length {
-    fn from(_: ()) -> Self {
-        Self::Definite(DefiniteLength::default())
-    }
-}
-
-/// Provides a trait for types that can calculate half of their value.
-///
-/// The `Half` trait is used for types that can be evenly divided, returning a new instance of the same type
-/// representing half of the original value. This is commonly used for types that represent measurements or sizes,
-/// such as lengths or pixels, where halving is a frequent operation during layout calculations or animations.
-pub trait Half {
-    /// Returns half of the current value.
-    ///
-    /// # Returns
-    ///
-    /// A new instance of the implementing type, representing half of the original value.
-    fn half(&self) -> Self;
-}
-
-impl Half for f32 {
-    fn half(&self) -> Self {
-        self / 2.
-    }
-}
-
-impl Half for DevicePixels {
-    fn half(&self) -> Self {
-        Self(self.0 / 2)
-    }
-}
-
-impl Half for ScaledPixels {
-    fn half(&self) -> Self {
-        Self(self.0 / 2.)
-    }
-}
-
-impl Half for Pixels {
-    fn half(&self) -> Self {
-        Self(self.0 / 2.)
-    }
-}
-
-impl Half for Rems {
-    fn half(&self) -> Self {
-        Self(self.0 / 2.)
-    }
-}
-
-impl Half for GlobalPixels {
-    fn half(&self) -> Self {
-        Self(self.0 / 2.)
-    }
-}
-
-/// A trait for checking if a value is zero.
-///
-/// This trait provides a method to determine if a value is considered to be zero.
-/// It is implemented for various numeric and length-related types where the concept
-/// of zero is applicable. This can be useful for comparisons, optimizations, or
-/// determining if an operation has a neutral effect.
-pub trait IsZero {
-    /// Determines if the value is zero.
-    ///
-    /// # Returns
-    ///
-    /// Returns `true` if the value is zero, `false` otherwise.
-    fn is_zero(&self) -> bool;
-}
-
-impl IsZero for DevicePixels {
-    fn is_zero(&self) -> bool {
-        self.0 == 0
-    }
-}
-
-impl IsZero for ScaledPixels {
-    fn is_zero(&self) -> bool {
-        self.0 == 0.
-    }
-}
-
-impl IsZero for Pixels {
-    fn is_zero(&self) -> bool {
-        self.0 == 0.
-    }
-}
-
-impl IsZero for Rems {
-    fn is_zero(&self) -> bool {
-        self.0 == 0.
-    }
-}
-
-impl IsZero for AbsoluteLength {
-    fn is_zero(&self) -> bool {
-        match self {
-            AbsoluteLength::Pixels(pixels) => pixels.is_zero(),
-            AbsoluteLength::Rems(rems) => rems.is_zero(),
-        }
-    }
-}
-
-impl IsZero for DefiniteLength {
-    fn is_zero(&self) -> bool {
-        match self {
-            DefiniteLength::Absolute(length) => length.is_zero(),
-            DefiniteLength::Fraction(fraction) => *fraction == 0.,
-        }
-    }
-}
-
-impl IsZero for Length {
-    fn is_zero(&self) -> bool {
-        match self {
-            Length::Definite(length) => length.is_zero(),
-            Length::Auto => false,
-        }
-    }
-}
-
-impl<T: IsZero + Debug + Clone + Default> IsZero for Point<T> {
-    fn is_zero(&self) -> bool {
-        self.x.is_zero() && self.y.is_zero()
-    }
-}
-
-impl<T> IsZero for Size<T>
-where
-    T: IsZero + Default + Debug + Clone,
-{
-    fn is_zero(&self) -> bool {
-        self.width.is_zero() || self.height.is_zero()
-    }
-}
-
-impl<T: IsZero + Debug + Clone + Default> IsZero for Bounds<T> {
-    fn is_zero(&self) -> bool {
-        self.size.is_zero()
-    }
-}
-
-impl<T> IsZero for Corners<T>
-where
-    T: IsZero + Clone + Default + Debug,
-{
-    fn is_zero(&self) -> bool {
-        self.top_left.is_zero()
-            && self.top_right.is_zero()
-            && self.bottom_right.is_zero()
-            && self.bottom_left.is_zero()
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn test_bounds_intersects() {
-        let bounds1 = Bounds {
-            origin: Point { x: 0.0, y: 0.0 },
-            size: Size {
-                width: 5.0,
-                height: 5.0,
-            },
-        };
-        let bounds2 = Bounds {
-            origin: Point { x: 4.0, y: 4.0 },
-            size: Size {
-                width: 5.0,
-                height: 5.0,
-            },
-        };
-        let bounds3 = Bounds {
-            origin: Point { x: 10.0, y: 10.0 },
-            size: Size {
-                width: 5.0,
-                height: 5.0,
-            },
-        };
-
-        // Test Case 1: Intersecting bounds
-        assert_eq!(bounds1.intersects(&bounds2), true);
-
-        // Test Case 2: Non-Intersecting bounds
-        assert_eq!(bounds1.intersects(&bounds3), false);
-
-        // Test Case 3: Bounds intersecting with themselves
-        assert_eq!(bounds1.intersects(&bounds1), true);
-    }
-}

crates/gpui2/src/gpui2.rs 🔗

@@ -1,215 +0,0 @@
-#[macro_use]
-mod action;
-mod app;
-
-mod arena;
-mod assets;
-mod color;
-mod element;
-mod elements;
-mod executor;
-mod geometry;
-mod image_cache;
-mod input;
-mod interactive;
-mod key_dispatch;
-mod keymap;
-mod platform;
-pub mod prelude;
-mod scene;
-mod shared_string;
-mod style;
-mod styled;
-mod subscription;
-mod svg_renderer;
-mod taffy;
-#[cfg(any(test, feature = "test-support"))]
-pub mod test;
-mod text_system;
-mod util;
-mod view;
-mod window;
-
-mod private {
-    /// A mechanism for restricting implementations of a trait to only those in GPUI.
-    /// See: https://predr.ag/blog/definitive-guide-to-sealed-traits-in-rust/
-    pub trait Sealed {}
-}
-
-pub use action::*;
-pub use anyhow::Result;
-pub use app::*;
-pub(crate) use arena::*;
-pub use assets::*;
-pub use color::*;
-pub use ctor::ctor;
-pub use element::*;
-pub use elements::*;
-pub use executor::*;
-pub use geometry::*;
-pub use gpui2_macros::*;
-pub use image_cache::*;
-pub use input::*;
-pub use interactive::*;
-pub use key_dispatch::*;
-pub use keymap::*;
-pub use linkme;
-pub use platform::*;
-use private::Sealed;
-pub use refineable::*;
-pub use scene::*;
-pub use serde;
-pub use serde_derive;
-pub use serde_json;
-pub use shared_string::*;
-pub use smallvec;
-pub use smol::Timer;
-pub use style::*;
-pub use styled::*;
-pub use subscription::*;
-pub use svg_renderer::*;
-pub use taffy::{AvailableSpace, LayoutId};
-#[cfg(any(test, feature = "test-support"))]
-pub use test::*;
-pub use text_system::*;
-pub use util::arc_cow::ArcCow;
-pub use view::*;
-pub use window::*;
-
-use std::{
-    any::{Any, TypeId},
-    borrow::BorrowMut,
-};
-use taffy::TaffyLayoutEngine;
-
-pub trait Context {
-    type Result<T>;
-
-    fn new_model<T: 'static>(
-        &mut self,
-        build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
-    ) -> Self::Result<Model<T>>;
-
-    fn update_model<T, R>(
-        &mut self,
-        handle: &Model<T>,
-        update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
-    ) -> Self::Result<R>
-    where
-        T: 'static;
-
-    fn read_model<T, R>(
-        &self,
-        handle: &Model<T>,
-        read: impl FnOnce(&T, &AppContext) -> R,
-    ) -> Self::Result<R>
-    where
-        T: 'static;
-
-    fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
-    where
-        F: FnOnce(AnyView, &mut WindowContext<'_>) -> T;
-
-    fn read_window<T, R>(
-        &self,
-        window: &WindowHandle<T>,
-        read: impl FnOnce(View<T>, &AppContext) -> R,
-    ) -> Result<R>
-    where
-        T: 'static;
-}
-
-pub trait VisualContext: Context {
-    fn new_view<V>(
-        &mut self,
-        build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
-    ) -> Self::Result<View<V>>
-    where
-        V: 'static + Render;
-
-    fn update_view<V: 'static, R>(
-        &mut self,
-        view: &View<V>,
-        update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
-    ) -> Self::Result<R>;
-
-    fn replace_root_view<V>(
-        &mut self,
-        build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
-    ) -> Self::Result<View<V>>
-    where
-        V: 'static + Render;
-
-    fn focus_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
-    where
-        V: FocusableView;
-
-    fn dismiss_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
-    where
-        V: ManagedView;
-}
-
-pub trait Entity<T>: Sealed {
-    type Weak: 'static;
-
-    fn entity_id(&self) -> EntityId;
-    fn downgrade(&self) -> Self::Weak;
-    fn upgrade_from(weak: &Self::Weak) -> Option<Self>
-    where
-        Self: Sized;
-}
-
-pub trait EventEmitter<E: Any>: 'static {}
-
-pub enum GlobalKey {
-    Numeric(usize),
-    View(EntityId),
-    Type(TypeId),
-}
-
-pub trait BorrowAppContext {
-    fn with_text_style<F, R>(&mut self, style: Option<TextStyleRefinement>, f: F) -> R
-    where
-        F: FnOnce(&mut Self) -> R;
-
-    fn set_global<T: 'static>(&mut self, global: T);
-}
-
-impl<C> BorrowAppContext for C
-where
-    C: BorrowMut<AppContext>,
-{
-    fn with_text_style<F, R>(&mut self, style: Option<TextStyleRefinement>, f: F) -> R
-    where
-        F: FnOnce(&mut Self) -> R,
-    {
-        if let Some(style) = style {
-            self.borrow_mut().push_text_style(style);
-            let result = f(self);
-            self.borrow_mut().pop_text_style();
-            result
-        } else {
-            f(self)
-        }
-    }
-
-    fn set_global<G: 'static>(&mut self, global: G) {
-        self.borrow_mut().set_global(global)
-    }
-}
-
-pub trait Flatten<T> {
-    fn flatten(self) -> Result<T>;
-}
-
-impl<T> Flatten<T> for Result<Result<T>> {
-    fn flatten(self) -> Result<T> {
-        self?
-    }
-}
-
-impl<T> Flatten<T> for Result<T> {
-    fn flatten(self) -> Result<T> {
-        self
-    }
-}

crates/gpui2/src/image_cache.rs 🔗

@@ -1,107 +0,0 @@
-use crate::{ImageData, ImageId, SharedString};
-use collections::HashMap;
-use futures::{
-    future::{BoxFuture, Shared},
-    AsyncReadExt, FutureExt, TryFutureExt,
-};
-use image::ImageError;
-use parking_lot::Mutex;
-use std::sync::Arc;
-use thiserror::Error;
-use util::http::{self, HttpClient};
-
-#[derive(PartialEq, Eq, Hash, Clone)]
-pub struct RenderImageParams {
-    pub(crate) image_id: ImageId,
-}
-
-#[derive(Debug, Error, Clone)]
-pub enum Error {
-    #[error("http error: {0}")]
-    Client(#[from] http::Error),
-    #[error("IO error: {0}")]
-    Io(Arc<std::io::Error>),
-    #[error("unexpected http status: {status}, body: {body}")]
-    BadStatus {
-        status: http::StatusCode,
-        body: String,
-    },
-    #[error("image error: {0}")]
-    Image(Arc<ImageError>),
-}
-
-impl From<std::io::Error> for Error {
-    fn from(error: std::io::Error) -> Self {
-        Error::Io(Arc::new(error))
-    }
-}
-
-impl From<ImageError> for Error {
-    fn from(error: ImageError) -> Self {
-        Error::Image(Arc::new(error))
-    }
-}
-
-pub struct ImageCache {
-    client: Arc<dyn HttpClient>,
-    images: Arc<Mutex<HashMap<SharedString, FetchImageFuture>>>,
-}
-
-type FetchImageFuture = Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>>;
-
-impl ImageCache {
-    pub fn new(client: Arc<dyn HttpClient>) -> Self {
-        ImageCache {
-            client,
-            images: Default::default(),
-        }
-    }
-
-    pub fn get(
-        &self,
-        uri: impl Into<SharedString>,
-    ) -> Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>> {
-        let uri = uri.into();
-        let mut images = self.images.lock();
-
-        match images.get(&uri) {
-            Some(future) => future.clone(),
-            None => {
-                let client = self.client.clone();
-                let future = {
-                    let uri = uri.clone();
-                    async move {
-                        let mut response = client.get(uri.as_ref(), ().into(), true).await?;
-                        let mut body = Vec::new();
-                        response.body_mut().read_to_end(&mut body).await?;
-
-                        if !response.status().is_success() {
-                            return Err(Error::BadStatus {
-                                status: response.status(),
-                                body: String::from_utf8_lossy(&body).into_owned(),
-                            });
-                        }
-
-                        let format = image::guess_format(&body)?;
-                        let image =
-                            image::load_from_memory_with_format(&body, format)?.into_bgra8();
-                        Ok(Arc::new(ImageData::new(image)))
-                    }
-                }
-                .map_err({
-                    let uri = uri.clone();
-
-                    move |error| {
-                        log::log!(log::Level::Error, "{:?} {:?}", &uri, &error);
-                        error
-                    }
-                })
-                .boxed()
-                .shared();
-
-                images.insert(uri, future.clone());
-                future
-            }
-        }
-    }
-}

crates/gpui2/src/platform.rs 🔗

@@ -1,592 +0,0 @@
-mod app_menu;
-mod keystroke;
-#[cfg(target_os = "macos")]
-mod mac;
-#[cfg(any(test, feature = "test-support"))]
-mod test;
-
-use crate::{
-    point, size, Action, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId,
-    FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, Keymap,
-    LineLayout, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result,
-    Scene, SharedString, Size, TaskLabel,
-};
-use anyhow::{anyhow, bail};
-use async_task::Runnable;
-use futures::channel::oneshot;
-use parking::Unparker;
-use seahash::SeaHasher;
-use serde::{Deserialize, Serialize};
-use sqlez::bindable::{Bind, Column, StaticColumnCount};
-use sqlez::statement::Statement;
-use std::borrow::Cow;
-use std::hash::{Hash, Hasher};
-use std::time::Duration;
-use std::{
-    any::Any,
-    fmt::{self, Debug, Display},
-    ops::Range,
-    path::{Path, PathBuf},
-    rc::Rc,
-    str::FromStr,
-    sync::Arc,
-};
-use uuid::Uuid;
-
-pub use app_menu::*;
-pub use keystroke::*;
-#[cfg(target_os = "macos")]
-pub use mac::*;
-#[cfg(any(test, feature = "test-support"))]
-pub use test::*;
-pub use time::UtcOffset;
-
-#[cfg(target_os = "macos")]
-pub(crate) fn current_platform() -> Rc<dyn Platform> {
-    Rc::new(MacPlatform::new())
-}
-
-pub type DrawWindow = Box<dyn FnMut() -> Result<Scene>>;
-
-pub(crate) trait Platform: 'static {
-    fn background_executor(&self) -> BackgroundExecutor;
-    fn foreground_executor(&self) -> ForegroundExecutor;
-    fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
-
-    fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
-    fn quit(&self);
-    fn restart(&self);
-    fn activate(&self, ignoring_other_apps: bool);
-    fn hide(&self);
-    fn hide_other_apps(&self);
-    fn unhide_other_apps(&self);
-
-    fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
-    fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
-    fn active_window(&self) -> Option<AnyWindowHandle>;
-    fn open_window(
-        &self,
-        handle: AnyWindowHandle,
-        options: WindowOptions,
-        draw: DrawWindow,
-    ) -> Box<dyn PlatformWindow>;
-
-    fn set_display_link_output_callback(
-        &self,
-        display_id: DisplayId,
-        callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp) + Send>,
-    );
-    fn start_display_link(&self, display_id: DisplayId);
-    fn stop_display_link(&self, display_id: DisplayId);
-    // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn PlatformWindow>;
-
-    fn open_url(&self, url: &str);
-    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
-    fn prompt_for_paths(
-        &self,
-        options: PathPromptOptions,
-    ) -> oneshot::Receiver<Option<Vec<PathBuf>>>;
-    fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>>;
-    fn reveal_path(&self, path: &Path);
-
-    fn on_become_active(&self, callback: Box<dyn FnMut()>);
-    fn on_resign_active(&self, callback: Box<dyn FnMut()>);
-    fn on_quit(&self, callback: Box<dyn FnMut()>);
-    fn on_reopen(&self, callback: Box<dyn FnMut()>);
-    fn on_event(&self, callback: Box<dyn FnMut(InputEvent) -> bool>);
-
-    fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
-    fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
-    fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
-    fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
-
-    fn os_name(&self) -> &'static str;
-    fn os_version(&self) -> Result<SemanticVersion>;
-    fn app_version(&self) -> Result<SemanticVersion>;
-    fn app_path(&self) -> Result<PathBuf>;
-    fn local_timezone(&self) -> UtcOffset;
-    fn double_click_interval(&self) -> Duration;
-    fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
-
-    fn set_cursor_style(&self, style: CursorStyle);
-    fn should_auto_hide_scrollbars(&self) -> bool;
-
-    fn write_to_clipboard(&self, item: ClipboardItem);
-    fn read_from_clipboard(&self) -> Option<ClipboardItem>;
-
-    fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()>;
-    fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>>;
-    fn delete_credentials(&self, url: &str) -> Result<()>;
-}
-
-pub trait PlatformDisplay: Send + Sync + Debug {
-    fn id(&self) -> DisplayId;
-    /// Returns a stable identifier for this display that can be persisted and used
-    /// across system restarts.
-    fn uuid(&self) -> Result<Uuid>;
-    fn as_any(&self) -> &dyn Any;
-    fn bounds(&self) -> Bounds<GlobalPixels>;
-}
-
-#[derive(PartialEq, Eq, Hash, Copy, Clone)]
-pub struct DisplayId(pub(crate) u32);
-
-impl Debug for DisplayId {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "DisplayId({})", self.0)
-    }
-}
-
-unsafe impl Send for DisplayId {}
-
-pub trait PlatformWindow {
-    fn bounds(&self) -> WindowBounds;
-    fn content_size(&self) -> Size<Pixels>;
-    fn scale_factor(&self) -> f32;
-    fn titlebar_height(&self) -> Pixels;
-    fn appearance(&self) -> WindowAppearance;
-    fn display(&self) -> Rc<dyn PlatformDisplay>;
-    fn mouse_position(&self) -> Point<Pixels>;
-    fn modifiers(&self) -> Modifiers;
-    fn as_any_mut(&mut self) -> &mut dyn Any;
-    fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>);
-    fn clear_input_handler(&mut self);
-    fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize>;
-    fn activate(&self);
-    fn set_title(&mut self, title: &str);
-    fn set_edited(&mut self, edited: bool);
-    fn show_character_palette(&self);
-    fn minimize(&self);
-    fn zoom(&self);
-    fn toggle_full_screen(&self);
-    fn on_input(&self, callback: Box<dyn FnMut(InputEvent) -> bool>);
-    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
-    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
-    fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>);
-    fn on_moved(&self, callback: Box<dyn FnMut()>);
-    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
-    fn on_close(&self, callback: Box<dyn FnOnce()>);
-    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
-    fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
-    fn invalidate(&self);
-
-    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
-
-    #[cfg(any(test, feature = "test-support"))]
-    fn as_test(&mut self) -> Option<&mut TestWindow> {
-        None
-    }
-}
-
-pub trait PlatformDispatcher: Send + Sync {
-    fn is_main_thread(&self) -> bool;
-    fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>);
-    fn dispatch_on_main_thread(&self, runnable: Runnable);
-    fn dispatch_after(&self, duration: Duration, runnable: Runnable);
-    fn tick(&self, background_only: bool) -> bool;
-    fn park(&self);
-    fn unparker(&self) -> Unparker;
-
-    #[cfg(any(test, feature = "test-support"))]
-    fn as_test(&self) -> Option<&TestDispatcher> {
-        None
-    }
-}
-
-pub trait PlatformTextSystem: Send + Sync {
-    fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()>;
-    fn all_font_families(&self) -> Vec<String>;
-    fn font_id(&self, descriptor: &Font) -> Result<FontId>;
-    fn font_metrics(&self, font_id: FontId) -> FontMetrics;
-    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
-    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
-    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
-    fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
-    fn rasterize_glyph(
-        &self,
-        params: &RenderGlyphParams,
-        raster_bounds: Bounds<DevicePixels>,
-    ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
-    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
-    fn wrap_line(
-        &self,
-        text: &str,
-        font_id: FontId,
-        font_size: Pixels,
-        width: Pixels,
-    ) -> Vec<usize>;
-}
-
-#[derive(Clone, Debug)]
-pub struct AppMetadata {
-    pub os_name: &'static str,
-    pub os_version: Option<SemanticVersion>,
-    pub app_version: Option<SemanticVersion>,
-}
-
-#[derive(PartialEq, Eq, Hash, Clone)]
-pub enum AtlasKey {
-    Glyph(RenderGlyphParams),
-    Svg(RenderSvgParams),
-    Image(RenderImageParams),
-}
-
-impl AtlasKey {
-    pub(crate) fn texture_kind(&self) -> AtlasTextureKind {
-        match self {
-            AtlasKey::Glyph(params) => {
-                if params.is_emoji {
-                    AtlasTextureKind::Polychrome
-                } else {
-                    AtlasTextureKind::Monochrome
-                }
-            }
-            AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
-            AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
-        }
-    }
-}
-
-impl From<RenderGlyphParams> for AtlasKey {
-    fn from(params: RenderGlyphParams) -> Self {
-        Self::Glyph(params)
-    }
-}
-
-impl From<RenderSvgParams> for AtlasKey {
-    fn from(params: RenderSvgParams) -> Self {
-        Self::Svg(params)
-    }
-}
-
-impl From<RenderImageParams> for AtlasKey {
-    fn from(params: RenderImageParams) -> Self {
-        Self::Image(params)
-    }
-}
-
-pub trait PlatformAtlas: Send + Sync {
-    fn get_or_insert_with<'a>(
-        &self,
-        key: &AtlasKey,
-        build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
-    ) -> Result<AtlasTile>;
-
-    fn clear(&self);
-}
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-#[repr(C)]
-pub struct AtlasTile {
-    pub(crate) texture_id: AtlasTextureId,
-    pub(crate) tile_id: TileId,
-    pub(crate) bounds: Bounds<DevicePixels>,
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
-#[repr(C)]
-pub(crate) struct AtlasTextureId {
-    // We use u32 instead of usize for Metal Shader Language compatibility
-    pub(crate) index: u32,
-    pub(crate) kind: AtlasTextureKind,
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
-#[repr(C)]
-pub(crate) enum AtlasTextureKind {
-    Monochrome = 0,
-    Polychrome = 1,
-    Path = 2,
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
-#[repr(C)]
-pub(crate) struct TileId(pub(crate) u32);
-
-impl From<etagere::AllocId> for TileId {
-    fn from(id: etagere::AllocId) -> Self {
-        Self(id.serialize())
-    }
-}
-
-impl From<TileId> for etagere::AllocId {
-    fn from(id: TileId) -> Self {
-        Self::deserialize(id.0)
-    }
-}
-
-pub trait PlatformInputHandler: 'static {
-    fn selected_text_range(&mut self) -> Option<Range<usize>>;
-    fn marked_text_range(&mut self) -> Option<Range<usize>>;
-    fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String>;
-    fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
-    fn replace_and_mark_text_in_range(
-        &mut self,
-        range_utf16: Option<Range<usize>>,
-        new_text: &str,
-        new_selected_range: Option<Range<usize>>,
-    );
-    fn unmark_text(&mut self);
-    fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>>;
-}
-
-#[derive(Debug)]
-pub struct WindowOptions {
-    pub bounds: WindowBounds,
-    pub titlebar: Option<TitlebarOptions>,
-    pub center: bool,
-    pub focus: bool,
-    pub show: bool,
-    pub kind: WindowKind,
-    pub is_movable: bool,
-    pub display_id: Option<DisplayId>,
-}
-
-impl Default for WindowOptions {
-    fn default() -> Self {
-        Self {
-            bounds: WindowBounds::default(),
-            titlebar: Some(TitlebarOptions {
-                title: Default::default(),
-                appears_transparent: Default::default(),
-                traffic_light_position: Default::default(),
-            }),
-            center: false,
-            focus: true,
-            show: true,
-            kind: WindowKind::Normal,
-            is_movable: true,
-            display_id: None,
-        }
-    }
-}
-
-#[derive(Debug, Default)]
-pub struct TitlebarOptions {
-    pub title: Option<SharedString>,
-    pub appears_transparent: bool,
-    pub traffic_light_position: Option<Point<Pixels>>,
-}
-
-#[derive(Copy, Clone, Debug)]
-pub enum Appearance {
-    Light,
-    VibrantLight,
-    Dark,
-    VibrantDark,
-}
-
-impl Default for Appearance {
-    fn default() -> Self {
-        Self::Light
-    }
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub enum WindowKind {
-    Normal,
-    PopUp,
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, Default)]
-pub enum WindowBounds {
-    Fullscreen,
-    #[default]
-    Maximized,
-    Fixed(Bounds<GlobalPixels>),
-}
-
-impl StaticColumnCount for WindowBounds {
-    fn column_count() -> usize {
-        5
-    }
-}
-
-impl Bind for WindowBounds {
-    fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
-        let (region, next_index) = match self {
-            WindowBounds::Fullscreen => {
-                let next_index = statement.bind(&"Fullscreen", start_index)?;
-                (None, next_index)
-            }
-            WindowBounds::Maximized => {
-                let next_index = statement.bind(&"Maximized", start_index)?;
-                (None, next_index)
-            }
-            WindowBounds::Fixed(region) => {
-                let next_index = statement.bind(&"Fixed", start_index)?;
-                (Some(*region), next_index)
-            }
-        };
-
-        statement.bind(
-            &region.map(|region| {
-                (
-                    region.origin.x,
-                    region.origin.y,
-                    region.size.width,
-                    region.size.height,
-                )
-            }),
-            next_index,
-        )
-    }
-}
-
-impl Column for WindowBounds {
-    fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
-        let (window_state, next_index) = String::column(statement, start_index)?;
-        let bounds = match window_state.as_str() {
-            "Fullscreen" => WindowBounds::Fullscreen,
-            "Maximized" => WindowBounds::Maximized,
-            "Fixed" => {
-                let ((x, y, width, height), _) = Column::column(statement, next_index)?;
-                let x: f64 = x;
-                let y: f64 = y;
-                let width: f64 = width;
-                let height: f64 = height;
-                WindowBounds::Fixed(Bounds {
-                    origin: point(x.into(), y.into()),
-                    size: size(width.into(), height.into()),
-                })
-            }
-            _ => bail!("Window State did not have a valid string"),
-        };
-
-        Ok((bounds, next_index + 4))
-    }
-}
-
-#[derive(Copy, Clone, Debug)]
-pub enum WindowAppearance {
-    Light,
-    VibrantLight,
-    Dark,
-    VibrantDark,
-}
-
-impl Default for WindowAppearance {
-    fn default() -> Self {
-        Self::Light
-    }
-}
-
-#[derive(Copy, Clone, Debug)]
-pub struct PathPromptOptions {
-    pub files: bool,
-    pub directories: bool,
-    pub multiple: bool,
-}
-
-#[derive(Copy, Clone, Debug)]
-pub enum PromptLevel {
-    Info,
-    Warning,
-    Critical,
-}
-
-/// The style of the cursor (pointer)
-#[derive(Copy, Clone, Debug)]
-pub enum CursorStyle {
-    Arrow,
-    IBeam,
-    Crosshair,
-    ClosedHand,
-    OpenHand,
-    PointingHand,
-    ResizeLeft,
-    ResizeRight,
-    ResizeLeftRight,
-    ResizeUp,
-    ResizeDown,
-    ResizeUpDown,
-    DisappearingItem,
-    IBeamCursorForVerticalLayout,
-    OperationNotAllowed,
-    DragLink,
-    DragCopy,
-    ContextualMenu,
-}
-
-impl Default for CursorStyle {
-    fn default() -> Self {
-        Self::Arrow
-    }
-}
-
-#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize)]
-pub struct SemanticVersion {
-    major: usize,
-    minor: usize,
-    patch: usize,
-}
-
-impl FromStr for SemanticVersion {
-    type Err = anyhow::Error;
-
-    fn from_str(s: &str) -> Result<Self> {
-        let mut components = s.trim().split('.');
-        let major = components
-            .next()
-            .ok_or_else(|| anyhow!("missing major version number"))?
-            .parse()?;
-        let minor = components
-            .next()
-            .ok_or_else(|| anyhow!("missing minor version number"))?
-            .parse()?;
-        let patch = components
-            .next()
-            .ok_or_else(|| anyhow!("missing patch version number"))?
-            .parse()?;
-        Ok(Self {
-            major,
-            minor,
-            patch,
-        })
-    }
-}
-
-impl Display for SemanticVersion {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
-    }
-}
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct ClipboardItem {
-    pub(crate) text: String,
-    pub(crate) metadata: Option<String>,
-}
-
-impl ClipboardItem {
-    pub fn new(text: String) -> Self {
-        Self {
-            text,
-            metadata: None,
-        }
-    }
-
-    pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
-        self.metadata = Some(serde_json::to_string(&metadata).unwrap());
-        self
-    }
-
-    pub fn text(&self) -> &String {
-        &self.text
-    }
-
-    pub fn metadata<T>(&self) -> Option<T>
-    where
-        T: for<'a> Deserialize<'a>,
-    {
-        self.metadata
-            .as_ref()
-            .and_then(|m| serde_json::from_str(m).ok())
-    }
-
-    pub(crate) fn text_hash(text: &str) -> u64 {
-        let mut hasher = SeaHasher::new();
-        text.hash(&mut hasher);
-        hasher.finish()
-    }
-}

crates/gpui2/src/platform/mac.rs 🔗

@@ -1,139 +0,0 @@
-//! Macos screen have a y axis that goings up from the bottom of the screen and
-//! an origin at the bottom left of the main display.
-mod dispatcher;
-mod display;
-mod display_linker;
-mod events;
-mod metal_atlas;
-mod metal_renderer;
-mod open_type;
-mod platform;
-mod text_system;
-mod window;
-mod window_appearence;
-
-use crate::{px, size, GlobalPixels, Pixels, Size};
-use cocoa::{
-    base::{id, nil},
-    foundation::{NSAutoreleasePool, NSNotFound, NSRect, NSSize, NSString, NSUInteger},
-};
-use metal_renderer::*;
-use objc::runtime::{BOOL, NO, YES};
-use std::ops::Range;
-
-pub use dispatcher::*;
-pub use display::*;
-pub use display_linker::*;
-pub use metal_atlas::*;
-pub use platform::*;
-pub use text_system::*;
-pub use window::*;
-
-trait BoolExt {
-    fn to_objc(self) -> BOOL;
-}
-
-impl BoolExt for bool {
-    fn to_objc(self) -> BOOL {
-        if self {
-            YES
-        } else {
-            NO
-        }
-    }
-}
-
-#[repr(C)]
-#[derive(Copy, Clone, Debug)]
-struct NSRange {
-    pub location: NSUInteger,
-    pub length: NSUInteger,
-}
-
-impl NSRange {
-    fn invalid() -> Self {
-        Self {
-            location: NSNotFound as NSUInteger,
-            length: 0,
-        }
-    }
-
-    fn is_valid(&self) -> bool {
-        self.location != NSNotFound as NSUInteger
-    }
-
-    fn to_range(self) -> Option<Range<usize>> {
-        if self.is_valid() {
-            let start = self.location as usize;
-            let end = start + self.length as usize;
-            Some(start..end)
-        } else {
-            None
-        }
-    }
-}
-
-impl From<Range<usize>> for NSRange {
-    fn from(range: Range<usize>) -> Self {
-        NSRange {
-            location: range.start as NSUInteger,
-            length: range.len() as NSUInteger,
-        }
-    }
-}
-
-unsafe impl objc::Encode for NSRange {
-    fn encode() -> objc::Encoding {
-        let encoding = format!(
-            "{{NSRange={}{}}}",
-            NSUInteger::encode().as_str(),
-            NSUInteger::encode().as_str()
-        );
-        unsafe { objc::Encoding::from_str(&encoding) }
-    }
-}
-
-unsafe fn ns_string(string: &str) -> id {
-    NSString::alloc(nil).init_str(string).autorelease()
-}
-
-impl From<NSSize> for Size<Pixels> {
-    fn from(value: NSSize) -> Self {
-        Size {
-            width: px(value.width as f32),
-            height: px(value.height as f32),
-        }
-    }
-}
-
-pub trait NSRectExt {
-    fn size(&self) -> Size<Pixels>;
-    fn intersects(&self, other: Self) -> bool;
-}
-
-impl From<NSRect> for Size<Pixels> {
-    fn from(rect: NSRect) -> Self {
-        let NSSize { width, height } = rect.size;
-        size(width.into(), height.into())
-    }
-}
-
-impl From<NSRect> for Size<GlobalPixels> {
-    fn from(rect: NSRect) -> Self {
-        let NSSize { width, height } = rect.size;
-        size(width.into(), height.into())
-    }
-}
-
-// impl NSRectExt for NSRect {
-//     fn intersects(&self, other: Self) -> bool {
-//         self.size.width > 0.
-//             && self.size.height > 0.
-//             && other.size.width > 0.
-//             && other.size.height > 0.
-//             && self.origin.x <= other.origin.x + other.size.width
-//             && self.origin.x + self.size.width >= other.origin.x
-//             && self.origin.y <= other.origin.y + other.size.height
-//             && self.origin.y + self.size.height >= other.origin.y
-//     }
-// }

crates/gpui2/src/platform/mac/dispatcher.rs 🔗

@@ -1,96 +0,0 @@
-#![allow(non_upper_case_globals)]
-#![allow(non_camel_case_types)]
-#![allow(non_snake_case)]
-
-use crate::{PlatformDispatcher, TaskLabel};
-use async_task::Runnable;
-use objc::{
-    class, msg_send,
-    runtime::{BOOL, YES},
-    sel, sel_impl,
-};
-use parking::{Parker, Unparker};
-use parking_lot::Mutex;
-use std::{ffi::c_void, sync::Arc, time::Duration};
-
-include!(concat!(env!("OUT_DIR"), "/dispatch_sys.rs"));
-
-pub fn dispatch_get_main_queue() -> dispatch_queue_t {
-    unsafe { &_dispatch_main_q as *const _ as dispatch_queue_t }
-}
-
-pub struct MacDispatcher {
-    parker: Arc<Mutex<Parker>>,
-}
-
-impl Default for MacDispatcher {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
-impl MacDispatcher {
-    pub fn new() -> Self {
-        MacDispatcher {
-            parker: Arc::new(Mutex::new(Parker::new())),
-        }
-    }
-}
-
-impl PlatformDispatcher for MacDispatcher {
-    fn is_main_thread(&self) -> bool {
-        let is_main_thread: BOOL = unsafe { msg_send![class!(NSThread), isMainThread] };
-        is_main_thread == YES
-    }
-
-    fn dispatch(&self, runnable: Runnable, _: Option<TaskLabel>) {
-        unsafe {
-            dispatch_async_f(
-                dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0),
-                runnable.into_raw() as *mut c_void,
-                Some(trampoline),
-            );
-        }
-    }
-
-    fn dispatch_on_main_thread(&self, runnable: Runnable) {
-        unsafe {
-            dispatch_async_f(
-                dispatch_get_main_queue(),
-                runnable.into_raw() as *mut c_void,
-                Some(trampoline),
-            );
-        }
-    }
-
-    fn dispatch_after(&self, duration: Duration, runnable: Runnable) {
-        unsafe {
-            let queue =
-                dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0);
-            let when = dispatch_time(DISPATCH_TIME_NOW as u64, duration.as_nanos() as i64);
-            dispatch_after_f(
-                when,
-                queue,
-                runnable.into_raw() as *mut c_void,
-                Some(trampoline),
-            );
-        }
-    }
-
-    fn tick(&self, _background_only: bool) -> bool {
-        false
-    }
-
-    fn park(&self) {
-        self.parker.lock().park()
-    }
-
-    fn unparker(&self) -> Unparker {
-        self.parker.lock().unparker()
-    }
-}
-
-extern "C" fn trampoline(runnable: *mut c_void) {
-    let task = unsafe { Runnable::from_raw(runnable as *mut ()) };
-    task.run();
-}

crates/gpui2/src/platform/mac/platform.rs 🔗

@@ -1,1194 +0,0 @@
-use super::{events::key_to_native, BoolExt};
-use crate::{
-    Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
-    ForegroundExecutor, InputEvent, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker,
-    MacTextSystem, MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay,
-    PlatformTextSystem, PlatformWindow, Result, Scene, SemanticVersion, VideoTimestamp,
-    WindowOptions,
-};
-use anyhow::anyhow;
-use block::ConcreteBlock;
-use cocoa::{
-    appkit::{
-        NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
-        NSEventModifierFlags, NSMenu, NSMenuItem, NSModalResponse, NSOpenPanel, NSPasteboard,
-        NSPasteboardTypeString, NSSavePanel, NSWindow,
-    },
-    base::{id, nil, selector, BOOL, YES},
-    foundation::{
-        NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSProcessInfo, NSString,
-        NSUInteger, NSURL,
-    },
-};
-use core_foundation::{
-    base::{CFType, CFTypeRef, OSStatus, TCFType as _},
-    boolean::CFBoolean,
-    data::CFData,
-    dictionary::{CFDictionary, CFDictionaryRef, CFMutableDictionary},
-    string::{CFString, CFStringRef},
-};
-use ctor::ctor;
-use futures::channel::oneshot;
-use objc::{
-    class,
-    declare::ClassDecl,
-    msg_send,
-    runtime::{Class, Object, Sel},
-    sel, sel_impl,
-};
-use parking_lot::Mutex;
-use ptr::null_mut;
-use std::{
-    cell::Cell,
-    convert::TryInto,
-    ffi::{c_void, CStr, OsStr},
-    os::{raw::c_char, unix::ffi::OsStrExt},
-    path::{Path, PathBuf},
-    process::Command,
-    ptr,
-    rc::Rc,
-    slice, str,
-    sync::Arc,
-    time::Duration,
-};
-use time::UtcOffset;
-
-#[allow(non_upper_case_globals)]
-const NSUTF8StringEncoding: NSUInteger = 4;
-
-#[allow(non_upper_case_globals)]
-pub const NSViewLayerContentsRedrawDuringViewResize: NSInteger = 2;
-
-const MAC_PLATFORM_IVAR: &str = "platform";
-static mut APP_CLASS: *const Class = ptr::null();
-static mut APP_DELEGATE_CLASS: *const Class = ptr::null();
-
-#[ctor]
-unsafe fn build_classes() {
-    APP_CLASS = {
-        let mut decl = ClassDecl::new("GPUI2Application", class!(NSApplication)).unwrap();
-        decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);
-        decl.add_method(
-            sel!(sendEvent:),
-            send_event as extern "C" fn(&mut Object, Sel, id),
-        );
-        decl.register()
-    };
-
-    APP_DELEGATE_CLASS = {
-        let mut decl = ClassDecl::new("GPUI2ApplicationDelegate", class!(NSResponder)).unwrap();
-        decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);
-        decl.add_method(
-            sel!(applicationDidFinishLaunching:),
-            did_finish_launching as extern "C" fn(&mut Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(applicationShouldHandleReopen:hasVisibleWindows:),
-            should_handle_reopen as extern "C" fn(&mut Object, Sel, id, bool),
-        );
-        decl.add_method(
-            sel!(applicationDidBecomeActive:),
-            did_become_active as extern "C" fn(&mut Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(applicationDidResignActive:),
-            did_resign_active as extern "C" fn(&mut Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(applicationWillTerminate:),
-            will_terminate as extern "C" fn(&mut Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(handleGPUIMenuItem:),
-            handle_menu_item as extern "C" fn(&mut Object, Sel, id),
-        );
-        // Add menu item handlers so that OS save panels have the correct key commands
-        decl.add_method(
-            sel!(cut:),
-            handle_menu_item as extern "C" fn(&mut Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(copy:),
-            handle_menu_item as extern "C" fn(&mut Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(paste:),
-            handle_menu_item as extern "C" fn(&mut Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(selectAll:),
-            handle_menu_item as extern "C" fn(&mut Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(undo:),
-            handle_menu_item as extern "C" fn(&mut Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(redo:),
-            handle_menu_item as extern "C" fn(&mut Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(validateMenuItem:),
-            validate_menu_item as extern "C" fn(&mut Object, Sel, id) -> bool,
-        );
-        decl.add_method(
-            sel!(menuWillOpen:),
-            menu_will_open as extern "C" fn(&mut Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(application:openURLs:),
-            open_urls as extern "C" fn(&mut Object, Sel, id, id),
-        );
-        decl.register()
-    }
-}
-
-pub struct MacPlatform(Mutex<MacPlatformState>);
-
-pub struct MacPlatformState {
-    background_executor: BackgroundExecutor,
-    foreground_executor: ForegroundExecutor,
-    text_system: Arc<MacTextSystem>,
-    display_linker: MacDisplayLinker,
-    pasteboard: id,
-    text_hash_pasteboard_type: id,
-    metadata_pasteboard_type: id,
-    become_active: Option<Box<dyn FnMut()>>,
-    resign_active: Option<Box<dyn FnMut()>>,
-    reopen: Option<Box<dyn FnMut()>>,
-    quit: Option<Box<dyn FnMut()>>,
-    event: Option<Box<dyn FnMut(InputEvent) -> bool>>,
-    menu_command: Option<Box<dyn FnMut(&dyn Action)>>,
-    validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
-    will_open_menu: Option<Box<dyn FnMut()>>,
-    menu_actions: Vec<Box<dyn Action>>,
-    open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
-    finish_launching: Option<Box<dyn FnOnce()>>,
-}
-
-impl Default for MacPlatform {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
-impl MacPlatform {
-    pub fn new() -> Self {
-        let dispatcher = Arc::new(MacDispatcher::new());
-        Self(Mutex::new(MacPlatformState {
-            background_executor: BackgroundExecutor::new(dispatcher.clone()),
-            foreground_executor: ForegroundExecutor::new(dispatcher),
-            text_system: Arc::new(MacTextSystem::new()),
-            display_linker: MacDisplayLinker::new(),
-            pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },
-            text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") },
-            metadata_pasteboard_type: unsafe { ns_string("zed-metadata") },
-            become_active: None,
-            resign_active: None,
-            reopen: None,
-            quit: None,
-            event: None,
-            menu_command: None,
-            validate_menu_command: None,
-            will_open_menu: None,
-            menu_actions: Default::default(),
-            open_urls: None,
-            finish_launching: None,
-        }))
-    }
-
-    unsafe fn read_from_pasteboard(&self, pasteboard: *mut Object, kind: id) -> Option<&[u8]> {
-        let data = pasteboard.dataForType(kind);
-        if data == nil {
-            None
-        } else {
-            Some(slice::from_raw_parts(
-                data.bytes() as *mut u8,
-                data.length() as usize,
-            ))
-        }
-    }
-
-    unsafe fn create_menu_bar(
-        &self,
-        menus: Vec<Menu>,
-        delegate: id,
-        actions: &mut Vec<Box<dyn Action>>,
-        keymap: &Keymap,
-    ) -> id {
-        let application_menu = NSMenu::new(nil).autorelease();
-        application_menu.setDelegate_(delegate);
-
-        for menu_config in menus {
-            let menu = NSMenu::new(nil).autorelease();
-            menu.setTitle_(ns_string(menu_config.name));
-            menu.setDelegate_(delegate);
-
-            for item_config in menu_config.items {
-                menu.addItem_(Self::create_menu_item(
-                    item_config,
-                    delegate,
-                    actions,
-                    keymap,
-                ));
-            }
-
-            let menu_item = NSMenuItem::new(nil).autorelease();
-            menu_item.setSubmenu_(menu);
-            application_menu.addItem_(menu_item);
-
-            if menu_config.name == "Window" {
-                let app: id = msg_send![APP_CLASS, sharedApplication];
-                app.setWindowsMenu_(menu);
-            }
-        }
-
-        application_menu
-    }
-
-    unsafe fn create_menu_item(
-        item: MenuItem,
-        delegate: id,
-        actions: &mut Vec<Box<dyn Action>>,
-        keymap: &Keymap,
-    ) -> id {
-        match item {
-            MenuItem::Separator => NSMenuItem::separatorItem(nil),
-            MenuItem::Action {
-                name,
-                action,
-                os_action,
-            } => {
-                let keystrokes = keymap
-                    .bindings_for_action(action.type_id())
-                    .find(|binding| binding.action().partial_eq(action.as_ref()))
-                    .map(|binding| binding.keystrokes());
-
-                let selector = match os_action {
-                    Some(crate::OsAction::Cut) => selector("cut:"),
-                    Some(crate::OsAction::Copy) => selector("copy:"),
-                    Some(crate::OsAction::Paste) => selector("paste:"),
-                    Some(crate::OsAction::SelectAll) => selector("selectAll:"),
-                    Some(crate::OsAction::Undo) => selector("undo:"),
-                    Some(crate::OsAction::Redo) => selector("redo:"),
-                    None => selector("handleGPUIMenuItem:"),
-                };
-
-                let item;
-                if let Some(keystrokes) = keystrokes {
-                    if keystrokes.len() == 1 {
-                        let keystroke = &keystrokes[0];
-                        let mut mask = NSEventModifierFlags::empty();
-                        for (modifier, flag) in &[
-                            (
-                                keystroke.modifiers.command,
-                                NSEventModifierFlags::NSCommandKeyMask,
-                            ),
-                            (
-                                keystroke.modifiers.control,
-                                NSEventModifierFlags::NSControlKeyMask,
-                            ),
-                            (
-                                keystroke.modifiers.alt,
-                                NSEventModifierFlags::NSAlternateKeyMask,
-                            ),
-                            (
-                                keystroke.modifiers.shift,
-                                NSEventModifierFlags::NSShiftKeyMask,
-                            ),
-                        ] {
-                            if *modifier {
-                                mask |= *flag;
-                            }
-                        }
-
-                        item = NSMenuItem::alloc(nil)
-                            .initWithTitle_action_keyEquivalent_(
-                                ns_string(name),
-                                selector,
-                                ns_string(key_to_native(&keystroke.key).as_ref()),
-                            )
-                            .autorelease();
-                        item.setKeyEquivalentModifierMask_(mask);
-                    }
-                    // For multi-keystroke bindings, render the keystroke as part of the title.
-                    else {
-                        use std::fmt::Write;
-
-                        let mut name = format!("{name} [");
-                        for (i, keystroke) in keystrokes.iter().enumerate() {
-                            if i > 0 {
-                                name.push(' ');
-                            }
-                            write!(&mut name, "{}", keystroke).unwrap();
-                        }
-                        name.push(']');
-
-                        item = NSMenuItem::alloc(nil)
-                            .initWithTitle_action_keyEquivalent_(
-                                ns_string(&name),
-                                selector,
-                                ns_string(""),
-                            )
-                            .autorelease();
-                    }
-                } else {
-                    item = NSMenuItem::alloc(nil)
-                        .initWithTitle_action_keyEquivalent_(
-                            ns_string(name),
-                            selector,
-                            ns_string(""),
-                        )
-                        .autorelease();
-                }
-
-                let tag = actions.len() as NSInteger;
-                let _: () = msg_send![item, setTag: tag];
-                actions.push(action);
-                item
-            }
-            MenuItem::Submenu(Menu { name, items }) => {
-                let item = NSMenuItem::new(nil).autorelease();
-                let submenu = NSMenu::new(nil).autorelease();
-                submenu.setDelegate_(delegate);
-                for item in items {
-                    submenu.addItem_(Self::create_menu_item(item, delegate, actions, keymap));
-                }
-                item.setSubmenu_(submenu);
-                item.setTitle_(ns_string(name));
-                item
-            }
-        }
-    }
-}
-
-impl Platform for MacPlatform {
-    fn background_executor(&self) -> BackgroundExecutor {
-        self.0.lock().background_executor.clone()
-    }
-
-    fn foreground_executor(&self) -> crate::ForegroundExecutor {
-        self.0.lock().foreground_executor.clone()
-    }
-
-    fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
-        self.0.lock().text_system.clone()
-    }
-
-    fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
-        self.0.lock().finish_launching = Some(on_finish_launching);
-
-        unsafe {
-            let app: id = msg_send![APP_CLASS, sharedApplication];
-            let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new];
-            app.setDelegate_(app_delegate);
-
-            let self_ptr = self as *const Self as *const c_void;
-            (*app).set_ivar(MAC_PLATFORM_IVAR, self_ptr);
-            (*app_delegate).set_ivar(MAC_PLATFORM_IVAR, self_ptr);
-
-            let pool = NSAutoreleasePool::new(nil);
-            app.run();
-            pool.drain();
-
-            (*app).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
-            (*app.delegate()).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
-        }
-    }
-
-    fn quit(&self) {
-        // Quitting the app causes us to close windows, which invokes `Window::on_close` callbacks
-        // synchronously before this method terminates. If we call `Platform::quit` while holding a
-        // borrow of the app state (which most of the time we will do), we will end up
-        // double-borrowing the app state in the `on_close` callbacks for our open windows. To solve
-        // this, we make quitting the application asynchronous so that we aren't holding borrows to
-        // the app state on the stack when we actually terminate the app.
-
-        use super::dispatcher::{dispatch_async_f, dispatch_get_main_queue};
-
-        unsafe {
-            dispatch_async_f(dispatch_get_main_queue(), ptr::null_mut(), Some(quit));
-        }
-
-        unsafe extern "C" fn quit(_: *mut c_void) {
-            let app = NSApplication::sharedApplication(nil);
-            let _: () = msg_send![app, terminate: nil];
-        }
-    }
-
-    fn restart(&self) {
-        use std::os::unix::process::CommandExt as _;
-
-        let app_pid = std::process::id().to_string();
-        let app_path = self
-            .app_path()
-            .ok()
-            // When the app is not bundled, `app_path` returns the
-            // directory containing the executable. Disregard this
-            // and get the path to the executable itself.
-            .and_then(|path| (path.extension()?.to_str()? == "app").then_some(path))
-            .unwrap_or_else(|| std::env::current_exe().unwrap());
-
-        // Wait until this process has exited and then re-open this path.
-        let script = r#"
-            while kill -0 $0 2> /dev/null; do
-                sleep 0.1
-            done
-            open "$1"
-        "#;
-
-        let restart_process = Command::new("/bin/bash")
-            .arg("-c")
-            .arg(script)
-            .arg(app_pid)
-            .arg(app_path)
-            .process_group(0)
-            .spawn();
-
-        match restart_process {
-            Ok(_) => self.quit(),
-            Err(e) => log::error!("failed to spawn restart script: {:?}", e),
-        }
-    }
-
-    fn activate(&self, ignoring_other_apps: bool) {
-        unsafe {
-            let app = NSApplication::sharedApplication(nil);
-            app.activateIgnoringOtherApps_(ignoring_other_apps.to_objc());
-        }
-    }
-
-    fn hide(&self) {
-        unsafe {
-            let app = NSApplication::sharedApplication(nil);
-            let _: () = msg_send![app, hide: nil];
-        }
-    }
-
-    fn hide_other_apps(&self) {
-        unsafe {
-            let app = NSApplication::sharedApplication(nil);
-            let _: () = msg_send![app, hideOtherApplications: nil];
-        }
-    }
-
-    fn unhide_other_apps(&self) {
-        unsafe {
-            let app = NSApplication::sharedApplication(nil);
-            let _: () = msg_send![app, unhideAllApplications: nil];
-        }
-    }
-
-    // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> {
-    //     Box::new(StatusItem::add(self.fonts()))
-    // }
-
-    fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
-        MacDisplay::all()
-            .map(|screen| Rc::new(screen) as Rc<_>)
-            .collect()
-    }
-
-    fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
-        MacDisplay::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
-    }
-
-    fn active_window(&self) -> Option<AnyWindowHandle> {
-        MacWindow::active_window()
-    }
-
-    fn open_window(
-        &self,
-        handle: AnyWindowHandle,
-        options: WindowOptions,
-        draw: Box<dyn FnMut() -> Result<Scene>>,
-    ) -> Box<dyn PlatformWindow> {
-        Box::new(MacWindow::open(
-            handle,
-            options,
-            draw,
-            self.foreground_executor(),
-        ))
-    }
-
-    fn set_display_link_output_callback(
-        &self,
-        display_id: DisplayId,
-        callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp) + Send>,
-    ) {
-        self.0
-            .lock()
-            .display_linker
-            .set_output_callback(display_id, callback);
-    }
-
-    fn start_display_link(&self, display_id: DisplayId) {
-        self.0.lock().display_linker.start(display_id);
-    }
-
-    fn stop_display_link(&self, display_id: DisplayId) {
-        self.0.lock().display_linker.stop(display_id);
-    }
-
-    fn open_url(&self, url: &str) {
-        unsafe {
-            let url = NSURL::alloc(nil)
-                .initWithString_(ns_string(url))
-                .autorelease();
-            let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
-            msg_send![workspace, openURL: url]
-        }
-    }
-
-    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
-        self.0.lock().open_urls = Some(callback);
-    }
-
-    fn prompt_for_paths(
-        &self,
-        options: PathPromptOptions,
-    ) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
-        unsafe {
-            let panel = NSOpenPanel::openPanel(nil);
-            panel.setCanChooseDirectories_(options.directories.to_objc());
-            panel.setCanChooseFiles_(options.files.to_objc());
-            panel.setAllowsMultipleSelection_(options.multiple.to_objc());
-            panel.setResolvesAliases_(false.to_objc());
-            let (done_tx, done_rx) = oneshot::channel();
-            let done_tx = Cell::new(Some(done_tx));
-            let block = ConcreteBlock::new(move |response: NSModalResponse| {
-                let result = if response == NSModalResponse::NSModalResponseOk {
-                    let mut result = Vec::new();
-                    let urls = panel.URLs();
-                    for i in 0..urls.count() {
-                        let url = urls.objectAtIndex(i);
-                        if url.isFileURL() == YES {
-                            if let Ok(path) = ns_url_to_path(url) {
-                                result.push(path)
-                            }
-                        }
-                    }
-                    Some(result)
-                } else {
-                    None
-                };
-
-                if let Some(done_tx) = done_tx.take() {
-                    let _ = done_tx.send(result);
-                }
-            });
-            let block = block.copy();
-            let _: () = msg_send![panel, beginWithCompletionHandler: block];
-            done_rx
-        }
-    }
-
-    fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> {
-        unsafe {
-            let panel = NSSavePanel::savePanel(nil);
-            let path = ns_string(directory.to_string_lossy().as_ref());
-            let url = NSURL::fileURLWithPath_isDirectory_(nil, path, true.to_objc());
-            panel.setDirectoryURL(url);
-
-            let (done_tx, done_rx) = oneshot::channel();
-            let done_tx = Cell::new(Some(done_tx));
-            let block = ConcreteBlock::new(move |response: NSModalResponse| {
-                let mut result = None;
-                if response == NSModalResponse::NSModalResponseOk {
-                    let url = panel.URL();
-                    if url.isFileURL() == YES {
-                        result = ns_url_to_path(panel.URL()).ok()
-                    }
-                }
-
-                if let Some(done_tx) = done_tx.take() {
-                    let _ = done_tx.send(result);
-                }
-            });
-            let block = block.copy();
-            let _: () = msg_send![panel, beginWithCompletionHandler: block];
-            done_rx
-        }
-    }
-
-    fn reveal_path(&self, path: &Path) {
-        unsafe {
-            let path = path.to_path_buf();
-            self.0
-                .lock()
-                .background_executor
-                .spawn(async move {
-                    let full_path = ns_string(path.to_str().unwrap_or(""));
-                    let root_full_path = ns_string("");
-                    let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
-                    let _: BOOL = msg_send![
-                        workspace,
-                        selectFile: full_path
-                        inFileViewerRootedAtPath: root_full_path
-                    ];
-                })
-                .detach();
-        }
-    }
-
-    fn on_become_active(&self, callback: Box<dyn FnMut()>) {
-        self.0.lock().become_active = Some(callback);
-    }
-
-    fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
-        self.0.lock().resign_active = Some(callback);
-    }
-
-    fn on_quit(&self, callback: Box<dyn FnMut()>) {
-        self.0.lock().quit = Some(callback);
-    }
-
-    fn on_reopen(&self, callback: Box<dyn FnMut()>) {
-        self.0.lock().reopen = Some(callback);
-    }
-
-    fn on_event(&self, callback: Box<dyn FnMut(InputEvent) -> bool>) {
-        self.0.lock().event = Some(callback);
-    }
-
-    fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
-        self.0.lock().menu_command = Some(callback);
-    }
-
-    fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
-        self.0.lock().will_open_menu = Some(callback);
-    }
-
-    fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
-        self.0.lock().validate_menu_command = Some(callback);
-    }
-
-    fn os_name(&self) -> &'static str {
-        "macOS"
-    }
-
-    fn double_click_interval(&self) -> Duration {
-        unsafe {
-            let double_click_interval: f64 = msg_send![class!(NSEvent), doubleClickInterval];
-            Duration::from_secs_f64(double_click_interval)
-        }
-    }
-
-    fn os_version(&self) -> Result<SemanticVersion> {
-        unsafe {
-            let process_info = NSProcessInfo::processInfo(nil);
-            let version = process_info.operatingSystemVersion();
-            Ok(SemanticVersion {
-                major: version.majorVersion as usize,
-                minor: version.minorVersion as usize,
-                patch: version.patchVersion as usize,
-            })
-        }
-    }
-
-    fn app_version(&self) -> Result<SemanticVersion> {
-        unsafe {
-            let bundle: id = NSBundle::mainBundle();
-            if bundle.is_null() {
-                Err(anyhow!("app is not running inside a bundle"))
-            } else {
-                let version: id = msg_send![bundle, objectForInfoDictionaryKey: ns_string("CFBundleShortVersionString")];
-                let len = msg_send![version, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
-                let bytes = version.UTF8String() as *const u8;
-                let version = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap();
-                version.parse()
-            }
-        }
-    }
-
-    fn app_path(&self) -> Result<PathBuf> {
-        unsafe {
-            let bundle: id = NSBundle::mainBundle();
-            if bundle.is_null() {
-                Err(anyhow!("app is not running inside a bundle"))
-            } else {
-                Ok(path_from_objc(msg_send![bundle, bundlePath]))
-            }
-        }
-    }
-
-    fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap) {
-        unsafe {
-            let app: id = msg_send![APP_CLASS, sharedApplication];
-            let mut state = self.0.lock();
-            let actions = &mut state.menu_actions;
-            app.setMainMenu_(self.create_menu_bar(menus, app.delegate(), actions, keymap));
-        }
-    }
-
-    fn local_timezone(&self) -> UtcOffset {
-        unsafe {
-            let local_timezone: id = msg_send![class!(NSTimeZone), localTimeZone];
-            let seconds_from_gmt: NSInteger = msg_send![local_timezone, secondsFromGMT];
-            UtcOffset::from_whole_seconds(seconds_from_gmt.try_into().unwrap()).unwrap()
-        }
-    }
-
-    fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
-        unsafe {
-            let bundle: id = NSBundle::mainBundle();
-            if bundle.is_null() {
-                Err(anyhow!("app is not running inside a bundle"))
-            } else {
-                let name = ns_string(name);
-                let url: id = msg_send![bundle, URLForAuxiliaryExecutable: name];
-                if url.is_null() {
-                    Err(anyhow!("resource not found"))
-                } else {
-                    ns_url_to_path(url)
-                }
-            }
-        }
-    }
-
-    /// Match cursor style to one of the styles available
-    /// in macOS's [NSCursor](https://developer.apple.com/documentation/appkit/nscursor).
-    fn set_cursor_style(&self, style: CursorStyle) {
-        unsafe {
-            let new_cursor: id = match style {
-                CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor],
-                CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor],
-                CursorStyle::Crosshair => msg_send![class!(NSCursor), crosshairCursor],
-                CursorStyle::ClosedHand => msg_send![class!(NSCursor), closedHandCursor],
-                CursorStyle::OpenHand => msg_send![class!(NSCursor), openHandCursor],
-                CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor],
-                CursorStyle::ResizeLeft => msg_send![class!(NSCursor), resizeLeftCursor],
-                CursorStyle::ResizeRight => msg_send![class!(NSCursor), resizeRightCursor],
-                CursorStyle::ResizeLeftRight => msg_send![class!(NSCursor), resizeLeftRightCursor],
-                CursorStyle::ResizeUp => msg_send![class!(NSCursor), resizeUpCursor],
-                CursorStyle::ResizeDown => msg_send![class!(NSCursor), resizeDownCursor],
-                CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor],
-                CursorStyle::DisappearingItem => {
-                    msg_send![class!(NSCursor), disappearingItemCursor]
-                }
-                CursorStyle::IBeamCursorForVerticalLayout => {
-                    msg_send![class!(NSCursor), IBeamCursorForVerticalLayout]
-                }
-                CursorStyle::OperationNotAllowed => {
-                    msg_send![class!(NSCursor), operationNotAllowedCursor]
-                }
-                CursorStyle::DragLink => msg_send![class!(NSCursor), dragLinkCursor],
-                CursorStyle::DragCopy => msg_send![class!(NSCursor), dragCopyCursor],
-                CursorStyle::ContextualMenu => msg_send![class!(NSCursor), contextualMenuCursor],
-            };
-
-            let old_cursor: id = msg_send![class!(NSCursor), currentCursor];
-            if new_cursor != old_cursor {
-                let _: () = msg_send![new_cursor, set];
-            }
-        }
-    }
-
-    fn should_auto_hide_scrollbars(&self) -> bool {
-        #[allow(non_upper_case_globals)]
-        const NSScrollerStyleOverlay: NSInteger = 1;
-
-        unsafe {
-            let style: NSInteger = msg_send![class!(NSScroller), preferredScrollerStyle];
-            style == NSScrollerStyleOverlay
-        }
-    }
-
-    fn write_to_clipboard(&self, item: ClipboardItem) {
-        let state = self.0.lock();
-        unsafe {
-            state.pasteboard.clearContents();
-
-            let text_bytes = NSData::dataWithBytes_length_(
-                nil,
-                item.text.as_ptr() as *const c_void,
-                item.text.len() as u64,
-            );
-            state
-                .pasteboard
-                .setData_forType(text_bytes, NSPasteboardTypeString);
-
-            if let Some(metadata) = item.metadata.as_ref() {
-                let hash_bytes = ClipboardItem::text_hash(&item.text).to_be_bytes();
-                let hash_bytes = NSData::dataWithBytes_length_(
-                    nil,
-                    hash_bytes.as_ptr() as *const c_void,
-                    hash_bytes.len() as u64,
-                );
-                state
-                    .pasteboard
-                    .setData_forType(hash_bytes, state.text_hash_pasteboard_type);
-
-                let metadata_bytes = NSData::dataWithBytes_length_(
-                    nil,
-                    metadata.as_ptr() as *const c_void,
-                    metadata.len() as u64,
-                );
-                state
-                    .pasteboard
-                    .setData_forType(metadata_bytes, state.metadata_pasteboard_type);
-            }
-        }
-    }
-
-    fn read_from_clipboard(&self) -> Option<ClipboardItem> {
-        let state = self.0.lock();
-        unsafe {
-            if let Some(text_bytes) =
-                self.read_from_pasteboard(state.pasteboard, NSPasteboardTypeString)
-            {
-                let text = String::from_utf8_lossy(text_bytes).to_string();
-                let hash_bytes = self
-                    .read_from_pasteboard(state.pasteboard, state.text_hash_pasteboard_type)
-                    .and_then(|bytes| bytes.try_into().ok())
-                    .map(u64::from_be_bytes);
-                let metadata_bytes = self
-                    .read_from_pasteboard(state.pasteboard, state.metadata_pasteboard_type)
-                    .and_then(|bytes| String::from_utf8(bytes.to_vec()).ok());
-
-                if let Some((hash, metadata)) = hash_bytes.zip(metadata_bytes) {
-                    if hash == ClipboardItem::text_hash(&text) {
-                        Some(ClipboardItem {
-                            text,
-                            metadata: Some(metadata),
-                        })
-                    } else {
-                        Some(ClipboardItem {
-                            text,
-                            metadata: None,
-                        })
-                    }
-                } else {
-                    Some(ClipboardItem {
-                        text,
-                        metadata: None,
-                    })
-                }
-            } else {
-                None
-            }
-        }
-    }
-
-    fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
-        let url = CFString::from(url);
-        let username = CFString::from(username);
-        let password = CFData::from_buffer(password);
-
-        unsafe {
-            use security::*;
-
-            // First, check if there are already credentials for the given server. If so, then
-            // update the username and password.
-            let mut verb = "updating";
-            let mut query_attrs = CFMutableDictionary::with_capacity(2);
-            query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
-            query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
-
-            let mut attrs = CFMutableDictionary::with_capacity(4);
-            attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
-            attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
-            attrs.set(kSecAttrAccount as *const _, username.as_CFTypeRef());
-            attrs.set(kSecValueData as *const _, password.as_CFTypeRef());
-
-            let mut status = SecItemUpdate(
-                query_attrs.as_concrete_TypeRef(),
-                attrs.as_concrete_TypeRef(),
-            );
-
-            // If there were no existing credentials for the given server, then create them.
-            if status == errSecItemNotFound {
-                verb = "creating";
-                status = SecItemAdd(attrs.as_concrete_TypeRef(), ptr::null_mut());
-            }
-
-            if status != errSecSuccess {
-                return Err(anyhow!("{} password failed: {}", verb, status));
-            }
-        }
-        Ok(())
-    }
-
-    fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>> {
-        let url = CFString::from(url);
-        let cf_true = CFBoolean::true_value().as_CFTypeRef();
-
-        unsafe {
-            use security::*;
-
-            // Find any credentials for the given server URL.
-            let mut attrs = CFMutableDictionary::with_capacity(5);
-            attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
-            attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
-            attrs.set(kSecReturnAttributes as *const _, cf_true);
-            attrs.set(kSecReturnData as *const _, cf_true);
-
-            let mut result = CFTypeRef::from(ptr::null());
-            let status = SecItemCopyMatching(attrs.as_concrete_TypeRef(), &mut result);
-            match status {
-                security::errSecSuccess => {}
-                security::errSecItemNotFound | security::errSecUserCanceled => return Ok(None),
-                _ => return Err(anyhow!("reading password failed: {}", status)),
-            }
-
-            let result = CFType::wrap_under_create_rule(result)
-                .downcast::<CFDictionary>()
-                .ok_or_else(|| anyhow!("keychain item was not a dictionary"))?;
-            let username = result
-                .find(kSecAttrAccount as *const _)
-                .ok_or_else(|| anyhow!("account was missing from keychain item"))?;
-            let username = CFType::wrap_under_get_rule(*username)
-                .downcast::<CFString>()
-                .ok_or_else(|| anyhow!("account was not a string"))?;
-            let password = result
-                .find(kSecValueData as *const _)
-                .ok_or_else(|| anyhow!("password was missing from keychain item"))?;
-            let password = CFType::wrap_under_get_rule(*password)
-                .downcast::<CFData>()
-                .ok_or_else(|| anyhow!("password was not a string"))?;
-
-            Ok(Some((username.to_string(), password.bytes().to_vec())))
-        }
-    }
-
-    fn delete_credentials(&self, url: &str) -> Result<()> {
-        let url = CFString::from(url);
-
-        unsafe {
-            use security::*;
-
-            let mut query_attrs = CFMutableDictionary::with_capacity(2);
-            query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
-            query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
-
-            let status = SecItemDelete(query_attrs.as_concrete_TypeRef());
-
-            if status != errSecSuccess {
-                return Err(anyhow!("delete password failed: {}", status));
-            }
-        }
-        Ok(())
-    }
-}
-
-unsafe fn path_from_objc(path: id) -> PathBuf {
-    let len = msg_send![path, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
-    let bytes = path.UTF8String() as *const u8;
-    let path = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap();
-    PathBuf::from(path)
-}
-
-unsafe fn get_mac_platform(object: &mut Object) -> &MacPlatform {
-    let platform_ptr: *mut c_void = *object.get_ivar(MAC_PLATFORM_IVAR);
-    assert!(!platform_ptr.is_null());
-    &*(platform_ptr as *const MacPlatform)
-}
-
-extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {
-    unsafe {
-        if let Some(event) = InputEvent::from_native(native_event, None) {
-            let platform = get_mac_platform(this);
-            if let Some(callback) = platform.0.lock().event.as_mut() {
-                if !callback(event) {
-                    return;
-                }
-            }
-        }
-        msg_send![super(this, class!(NSApplication)), sendEvent: native_event]
-    }
-}
-
-extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
-    unsafe {
-        let app: id = msg_send![APP_CLASS, sharedApplication];
-        app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
-
-        let platform = get_mac_platform(this);
-        let callback = platform.0.lock().finish_launching.take();
-        if let Some(callback) = callback {
-            callback();
-        }
-    }
-}
-
-extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_windows: bool) {
-    if !has_open_windows {
-        let platform = unsafe { get_mac_platform(this) };
-        if let Some(callback) = platform.0.lock().reopen.as_mut() {
-            callback();
-        }
-    }
-}
-
-extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) {
-    let platform = unsafe { get_mac_platform(this) };
-    if let Some(callback) = platform.0.lock().become_active.as_mut() {
-        callback();
-    }
-}
-
-extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) {
-    let platform = unsafe { get_mac_platform(this) };
-    if let Some(callback) = platform.0.lock().resign_active.as_mut() {
-        callback();
-    }
-}
-
-extern "C" fn will_terminate(this: &mut Object, _: Sel, _: id) {
-    let platform = unsafe { get_mac_platform(this) };
-    if let Some(callback) = platform.0.lock().quit.as_mut() {
-        callback();
-    }
-}
-
-extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) {
-    let urls = unsafe {
-        (0..urls.count())
-            .filter_map(|i| {
-                let url = urls.objectAtIndex(i);
-                match CStr::from_ptr(url.absoluteString().UTF8String() as *mut c_char).to_str() {
-                    Ok(string) => Some(string.to_string()),
-                    Err(err) => {
-                        log::error!("error converting path to string: {}", err);
-                        None
-                    }
-                }
-            })
-            .collect::<Vec<_>>()
-    };
-    let platform = unsafe { get_mac_platform(this) };
-    if let Some(callback) = platform.0.lock().open_urls.as_mut() {
-        callback(urls);
-    }
-}
-
-extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) {
-    unsafe {
-        let platform = get_mac_platform(this);
-        let mut platform = platform.0.lock();
-        if let Some(mut callback) = platform.menu_command.take() {
-            let tag: NSInteger = msg_send![item, tag];
-            let index = tag as usize;
-            if let Some(action) = platform.menu_actions.get(index) {
-                callback(action.as_ref());
-            }
-            platform.menu_command = Some(callback);
-        }
-    }
-}
-
-extern "C" fn validate_menu_item(this: &mut Object, _: Sel, item: id) -> bool {
-    unsafe {
-        let mut result = false;
-        let platform = get_mac_platform(this);
-        let mut platform = platform.0.lock();
-        if let Some(mut callback) = platform.validate_menu_command.take() {
-            let tag: NSInteger = msg_send![item, tag];
-            let index = tag as usize;
-            if let Some(action) = platform.menu_actions.get(index) {
-                result = callback(action.as_ref());
-            }
-            platform.validate_menu_command = Some(callback);
-        }
-        result
-    }
-}
-
-extern "C" fn menu_will_open(this: &mut Object, _: Sel, _: id) {
-    unsafe {
-        let platform = get_mac_platform(this);
-        let mut platform = platform.0.lock();
-        if let Some(mut callback) = platform.will_open_menu.take() {
-            callback();
-            platform.will_open_menu = Some(callback);
-        }
-    }
-}
-
-unsafe fn ns_string(string: &str) -> id {
-    NSString::alloc(nil).init_str(string).autorelease()
-}
-
-unsafe fn ns_url_to_path(url: id) -> Result<PathBuf> {
-    let path: *mut c_char = msg_send![url, fileSystemRepresentation];
-    if path.is_null() {
-        Err(anyhow!(
-            "url is not a file path: {}",
-            CStr::from_ptr(url.absoluteString().UTF8String()).to_string_lossy()
-        ))
-    } else {
-        Ok(PathBuf::from(OsStr::from_bytes(
-            CStr::from_ptr(path).to_bytes(),
-        )))
-    }
-}
-
-mod security {
-    #![allow(non_upper_case_globals)]
-    use super::*;
-
-    #[link(name = "Security", kind = "framework")]
-    extern "C" {
-        pub static kSecClass: CFStringRef;
-        pub static kSecClassInternetPassword: CFStringRef;
-        pub static kSecAttrServer: CFStringRef;
-        pub static kSecAttrAccount: CFStringRef;
-        pub static kSecValueData: CFStringRef;
-        pub static kSecReturnAttributes: CFStringRef;
-        pub static kSecReturnData: CFStringRef;
-
-        pub fn SecItemAdd(attributes: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus;
-        pub fn SecItemUpdate(query: CFDictionaryRef, attributes: CFDictionaryRef) -> OSStatus;
-        pub fn SecItemDelete(query: CFDictionaryRef) -> OSStatus;
-        pub fn SecItemCopyMatching(query: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus;
-    }
-
-    pub const errSecSuccess: OSStatus = 0;
-    pub const errSecUserCanceled: OSStatus = -128;
-    pub const errSecItemNotFound: OSStatus = -25300;
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::ClipboardItem;
-
-    use super::*;
-
-    #[test]
-    fn test_clipboard() {
-        let platform = build_platform();
-        assert_eq!(platform.read_from_clipboard(), None);
-
-        let item = ClipboardItem::new("1".to_string());
-        platform.write_to_clipboard(item.clone());
-        assert_eq!(platform.read_from_clipboard(), Some(item));
-
-        let item = ClipboardItem::new("2".to_string()).with_metadata(vec![3, 4]);
-        platform.write_to_clipboard(item.clone());
-        assert_eq!(platform.read_from_clipboard(), Some(item));
-
-        let text_from_other_app = "text from other app";
-        unsafe {
-            let bytes = NSData::dataWithBytes_length_(
-                nil,
-                text_from_other_app.as_ptr() as *const c_void,
-                text_from_other_app.len() as u64,
-            );
-            platform
-                .0
-                .lock()
-                .pasteboard
-                .setData_forType(bytes, NSPasteboardTypeString);
-        }
-        assert_eq!(
-            platform.read_from_clipboard(),
-            Some(ClipboardItem::new(text_from_other_app.to_string()))
-        );
-    }
-
-    fn build_platform() -> MacPlatform {
-        let platform = MacPlatform::new();
-        platform.0.lock().pasteboard = unsafe { NSPasteboard::pasteboardWithUniqueName(nil) };
-        platform
-    }
-}

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

@@ -1,1791 +0,0 @@
-use super::{display_bounds_from_native, ns_string, MacDisplay, MetalRenderer, NSRange};
-use crate::{
-    display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, DrawWindow, ExternalPaths,
-    FileDropEvent, ForegroundExecutor, GlobalPixels, InputEvent, KeyDownEvent, Keystroke,
-    Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
-    Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
-    PromptLevel, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions,
-};
-use block::ConcreteBlock;
-use cocoa::{
-    appkit::{
-        CGPoint, NSApplication, NSBackingStoreBuffered, NSEventModifierFlags,
-        NSFilenamesPboardType, NSPasteboard, NSScreen, NSView, NSViewHeightSizable,
-        NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowCollectionBehavior,
-        NSWindowStyleMask, NSWindowTitleVisibility,
-    },
-    base::{id, nil},
-    foundation::{
-        NSArray, NSAutoreleasePool, NSDictionary, NSFastEnumeration, NSInteger, NSPoint, NSRect,
-        NSSize, NSString, NSUInteger,
-    },
-};
-use core_graphics::display::CGRect;
-use ctor::ctor;
-use foreign_types::ForeignTypeRef;
-use futures::channel::oneshot;
-use objc::{
-    class,
-    declare::ClassDecl,
-    msg_send,
-    runtime::{Class, Object, Protocol, Sel, BOOL, NO, YES},
-    sel, sel_impl,
-};
-use parking_lot::Mutex;
-use smallvec::SmallVec;
-use std::{
-    any::Any,
-    cell::{Cell, RefCell},
-    ffi::{c_void, CStr},
-    mem,
-    ops::Range,
-    os::raw::c_char,
-    path::PathBuf,
-    ptr,
-    rc::Rc,
-    sync::{Arc, Weak},
-    time::Duration,
-};
-use util::ResultExt;
-
-const WINDOW_STATE_IVAR: &str = "windowState";
-
-static mut WINDOW_CLASS: *const Class = ptr::null();
-static mut PANEL_CLASS: *const Class = ptr::null();
-static mut VIEW_CLASS: *const Class = ptr::null();
-
-#[allow(non_upper_case_globals)]
-const NSWindowStyleMaskNonactivatingPanel: NSWindowStyleMask =
-    unsafe { NSWindowStyleMask::from_bits_unchecked(1 << 7) };
-#[allow(non_upper_case_globals)]
-const NSNormalWindowLevel: NSInteger = 0;
-#[allow(non_upper_case_globals)]
-const NSPopUpWindowLevel: NSInteger = 101;
-#[allow(non_upper_case_globals)]
-const NSTrackingMouseEnteredAndExited: NSUInteger = 0x01;
-#[allow(non_upper_case_globals)]
-const NSTrackingMouseMoved: NSUInteger = 0x02;
-#[allow(non_upper_case_globals)]
-const NSTrackingActiveAlways: NSUInteger = 0x80;
-#[allow(non_upper_case_globals)]
-const NSTrackingInVisibleRect: NSUInteger = 0x200;
-#[allow(non_upper_case_globals)]
-const NSWindowAnimationBehaviorUtilityWindow: NSInteger = 4;
-#[allow(non_upper_case_globals)]
-const NSViewLayerContentsRedrawDuringViewResize: NSInteger = 2;
-// https://developer.apple.com/documentation/appkit/nsdragoperation
-type NSDragOperation = NSUInteger;
-#[allow(non_upper_case_globals)]
-const NSDragOperationNone: NSDragOperation = 0;
-#[allow(non_upper_case_globals)]
-const NSDragOperationCopy: NSDragOperation = 1;
-
-#[ctor]
-unsafe fn build_classes() {
-    ::util::gpui2_loaded();
-
-    WINDOW_CLASS = build_window_class("GPUI2Window", class!(NSWindow));
-    PANEL_CLASS = build_window_class("GPUI2Panel", class!(NSPanel));
-    VIEW_CLASS = {
-        let mut decl = ClassDecl::new("GPUI2View", class!(NSView)).unwrap();
-        decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
-
-        decl.add_method(sel!(dealloc), dealloc_view as extern "C" fn(&Object, Sel));
-
-        decl.add_method(
-            sel!(performKeyEquivalent:),
-            handle_key_equivalent as extern "C" fn(&Object, Sel, id) -> BOOL,
-        );
-        decl.add_method(
-            sel!(keyDown:),
-            handle_key_down as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(mouseDown:),
-            handle_view_event as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(mouseUp:),
-            handle_view_event as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(rightMouseDown:),
-            handle_view_event as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(rightMouseUp:),
-            handle_view_event as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(otherMouseDown:),
-            handle_view_event as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(otherMouseUp:),
-            handle_view_event as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(mouseMoved:),
-            handle_view_event as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(mouseExited:),
-            handle_view_event as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(mouseDragged:),
-            handle_view_event as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(scrollWheel:),
-            handle_view_event as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(flagsChanged:),
-            handle_view_event as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(cancelOperation:),
-            cancel_operation as extern "C" fn(&Object, Sel, id),
-        );
-
-        decl.add_method(
-            sel!(makeBackingLayer),
-            make_backing_layer as extern "C" fn(&Object, Sel) -> id,
-        );
-
-        decl.add_protocol(Protocol::get("CALayerDelegate").unwrap());
-        decl.add_method(
-            sel!(viewDidChangeBackingProperties),
-            view_did_change_backing_properties as extern "C" fn(&Object, Sel),
-        );
-        decl.add_method(
-            sel!(setFrameSize:),
-            set_frame_size as extern "C" fn(&Object, Sel, NSSize),
-        );
-        decl.add_method(
-            sel!(displayLayer:),
-            display_layer as extern "C" fn(&Object, Sel, id),
-        );
-
-        decl.add_protocol(Protocol::get("NSTextInputClient").unwrap());
-        decl.add_method(
-            sel!(validAttributesForMarkedText),
-            valid_attributes_for_marked_text as extern "C" fn(&Object, Sel) -> id,
-        );
-        decl.add_method(
-            sel!(hasMarkedText),
-            has_marked_text as extern "C" fn(&Object, Sel) -> BOOL,
-        );
-        decl.add_method(
-            sel!(markedRange),
-            marked_range as extern "C" fn(&Object, Sel) -> NSRange,
-        );
-        decl.add_method(
-            sel!(selectedRange),
-            selected_range as extern "C" fn(&Object, Sel) -> NSRange,
-        );
-        decl.add_method(
-            sel!(firstRectForCharacterRange:actualRange:),
-            first_rect_for_character_range as extern "C" fn(&Object, Sel, NSRange, id) -> NSRect,
-        );
-        decl.add_method(
-            sel!(insertText:replacementRange:),
-            insert_text as extern "C" fn(&Object, Sel, id, NSRange),
-        );
-        decl.add_method(
-            sel!(setMarkedText:selectedRange:replacementRange:),
-            set_marked_text as extern "C" fn(&Object, Sel, id, NSRange, NSRange),
-        );
-        decl.add_method(sel!(unmarkText), unmark_text as extern "C" fn(&Object, Sel));
-        decl.add_method(
-            sel!(attributedSubstringForProposedRange:actualRange:),
-            attributed_substring_for_proposed_range
-                as extern "C" fn(&Object, Sel, NSRange, *mut c_void) -> id,
-        );
-        decl.add_method(
-            sel!(viewDidChangeEffectiveAppearance),
-            view_did_change_effective_appearance as extern "C" fn(&Object, Sel),
-        );
-
-        // Suppress beep on keystrokes with modifier keys.
-        decl.add_method(
-            sel!(doCommandBySelector:),
-            do_command_by_selector as extern "C" fn(&Object, Sel, Sel),
-        );
-
-        decl.add_method(
-            sel!(acceptsFirstMouse:),
-            accepts_first_mouse as extern "C" fn(&Object, Sel, id) -> BOOL,
-        );
-
-        decl.register()
-    };
-}
-
-pub fn convert_mouse_position(position: NSPoint, window_height: Pixels) -> Point<Pixels> {
-    point(
-        px(position.x as f32),
-        // MacOS screen coordinates are relative to bottom left
-        window_height - px(position.y as f32),
-    )
-}
-
-unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const Class {
-    let mut decl = ClassDecl::new(name, superclass).unwrap();
-    decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
-    decl.add_method(sel!(dealloc), dealloc_window as extern "C" fn(&Object, Sel));
-    decl.add_method(
-        sel!(canBecomeMainWindow),
-        yes as extern "C" fn(&Object, Sel) -> BOOL,
-    );
-    decl.add_method(
-        sel!(canBecomeKeyWindow),
-        yes as extern "C" fn(&Object, Sel) -> BOOL,
-    );
-    decl.add_method(
-        sel!(windowDidResize:),
-        window_did_resize as extern "C" fn(&Object, Sel, id),
-    );
-    decl.add_method(
-        sel!(windowWillEnterFullScreen:),
-        window_will_enter_fullscreen as extern "C" fn(&Object, Sel, id),
-    );
-    decl.add_method(
-        sel!(windowWillExitFullScreen:),
-        window_will_exit_fullscreen as extern "C" fn(&Object, Sel, id),
-    );
-    decl.add_method(
-        sel!(windowDidMove:),
-        window_did_move as extern "C" fn(&Object, Sel, id),
-    );
-    decl.add_method(
-        sel!(windowDidBecomeKey:),
-        window_did_change_key_status as extern "C" fn(&Object, Sel, id),
-    );
-    decl.add_method(
-        sel!(windowDidResignKey:),
-        window_did_change_key_status as extern "C" fn(&Object, Sel, id),
-    );
-    decl.add_method(
-        sel!(windowShouldClose:),
-        window_should_close as extern "C" fn(&Object, Sel, id) -> BOOL,
-    );
-    decl.add_method(sel!(close), close_window as extern "C" fn(&Object, Sel));
-
-    decl.add_method(
-        sel!(draggingEntered:),
-        dragging_entered as extern "C" fn(&Object, Sel, id) -> NSDragOperation,
-    );
-    decl.add_method(
-        sel!(draggingUpdated:),
-        dragging_updated as extern "C" fn(&Object, Sel, id) -> NSDragOperation,
-    );
-    decl.add_method(
-        sel!(draggingExited:),
-        dragging_exited as extern "C" fn(&Object, Sel, id),
-    );
-    decl.add_method(
-        sel!(performDragOperation:),
-        perform_drag_operation as extern "C" fn(&Object, Sel, id) -> BOOL,
-    );
-    decl.add_method(
-        sel!(concludeDragOperation:),
-        conclude_drag_operation as extern "C" fn(&Object, Sel, id),
-    );
-
-    decl.register()
-}
-
-///Used to track what the IME does when we send it a keystroke.
-///This is only used to handle the case where the IME mysteriously
-///swallows certain keys.
-///
-///Basically a direct copy of the approach that WezTerm uses in:
-///github.com/wez/wezterm : d5755f3e : window/src/os/macos/window.rs
-enum ImeState {
-    Continue,
-    Acted,
-    None,
-}
-
-struct InsertText {
-    replacement_range: Option<Range<usize>>,
-    text: String,
-}
-
-struct MacWindowState {
-    handle: AnyWindowHandle,
-    executor: ForegroundExecutor,
-    native_window: id,
-    renderer: MetalRenderer,
-    draw: Option<DrawWindow>,
-    kind: WindowKind,
-    event_callback: Option<Box<dyn FnMut(InputEvent) -> bool>>,
-    activate_callback: Option<Box<dyn FnMut(bool)>>,
-    resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
-    fullscreen_callback: Option<Box<dyn FnMut(bool)>>,
-    moved_callback: Option<Box<dyn FnMut()>>,
-    should_close_callback: Option<Box<dyn FnMut() -> bool>>,
-    close_callback: Option<Box<dyn FnOnce()>>,
-    appearance_changed_callback: Option<Box<dyn FnMut()>>,
-    input_handler: Option<Box<dyn PlatformInputHandler>>,
-    pending_key_down: Option<(KeyDownEvent, Option<InsertText>)>,
-    last_key_equivalent: Option<KeyDownEvent>,
-    synthetic_drag_counter: usize,
-    last_fresh_keydown: Option<Keystroke>,
-    traffic_light_position: Option<Point<Pixels>>,
-    previous_modifiers_changed_event: Option<InputEvent>,
-    // State tracking what the IME did after the last request
-    ime_state: ImeState,
-    // Retains the last IME Text
-    ime_text: Option<String>,
-}
-
-impl MacWindowState {
-    fn move_traffic_light(&self) {
-        if let Some(traffic_light_position) = self.traffic_light_position {
-            let titlebar_height = self.titlebar_height();
-
-            unsafe {
-                let close_button: id = msg_send![
-                    self.native_window,
-                    standardWindowButton: NSWindowButton::NSWindowCloseButton
-                ];
-                let min_button: id = msg_send![
-                    self.native_window,
-                    standardWindowButton: NSWindowButton::NSWindowMiniaturizeButton
-                ];
-                let zoom_button: id = msg_send![
-                    self.native_window,
-                    standardWindowButton: NSWindowButton::NSWindowZoomButton
-                ];
-
-                let mut close_button_frame: CGRect = msg_send![close_button, frame];
-                let mut min_button_frame: CGRect = msg_send![min_button, frame];
-                let mut zoom_button_frame: CGRect = msg_send![zoom_button, frame];
-                let mut origin = point(
-                    traffic_light_position.x,
-                    titlebar_height
-                        - traffic_light_position.y
-                        - px(close_button_frame.size.height as f32),
-                );
-                let button_spacing =
-                    px((min_button_frame.origin.x - close_button_frame.origin.x) as f32);
-
-                close_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
-                let _: () = msg_send![close_button, setFrame: close_button_frame];
-                origin.x += button_spacing;
-
-                min_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
-                let _: () = msg_send![min_button, setFrame: min_button_frame];
-                origin.x += button_spacing;
-
-                zoom_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
-                let _: () = msg_send![zoom_button, setFrame: zoom_button_frame];
-                origin.x += button_spacing;
-            }
-        }
-    }
-
-    fn is_fullscreen(&self) -> bool {
-        unsafe {
-            let style_mask = self.native_window.styleMask();
-            style_mask.contains(NSWindowStyleMask::NSFullScreenWindowMask)
-        }
-    }
-
-    fn bounds(&self) -> WindowBounds {
-        unsafe {
-            if self.is_fullscreen() {
-                return WindowBounds::Fullscreen;
-            }
-
-            let frame = self.frame();
-            let screen_size = self.native_window.screen().visibleFrame().into();
-            if frame.size == screen_size {
-                WindowBounds::Maximized
-            } else {
-                WindowBounds::Fixed(frame)
-            }
-        }
-    }
-
-    fn frame(&self) -> Bounds<GlobalPixels> {
-        unsafe {
-            let frame = NSWindow::frame(self.native_window);
-            display_bounds_from_native(mem::transmute::<NSRect, CGRect>(frame))
-        }
-    }
-
-    fn content_size(&self) -> Size<Pixels> {
-        let NSSize { width, height, .. } =
-            unsafe { NSView::frame(self.native_window.contentView()) }.size;
-        size(px(width as f32), px(height as f32))
-    }
-
-    fn scale_factor(&self) -> f32 {
-        get_scale_factor(self.native_window)
-    }
-
-    fn titlebar_height(&self) -> Pixels {
-        unsafe {
-            let frame = NSWindow::frame(self.native_window);
-            let content_layout_rect: CGRect = msg_send![self.native_window, contentLayoutRect];
-            px((frame.size.height - content_layout_rect.size.height) as f32)
-        }
-    }
-
-    fn to_screen_ns_point(&self, point: Point<Pixels>) -> NSPoint {
-        unsafe {
-            let point = NSPoint::new(
-                point.x.into(),
-                (self.content_size().height - point.y).into(),
-            );
-            msg_send![self.native_window, convertPointToScreen: point]
-        }
-    }
-}
-
-unsafe impl Send for MacWindowState {}
-
-pub struct MacWindow(Arc<Mutex<MacWindowState>>);
-
-impl MacWindow {
-    pub fn open(
-        handle: AnyWindowHandle,
-        options: WindowOptions,
-        draw: DrawWindow,
-        executor: ForegroundExecutor,
-    ) -> Self {
-        unsafe {
-            let pool = NSAutoreleasePool::new(nil);
-
-            let mut style_mask;
-            if let Some(titlebar) = options.titlebar.as_ref() {
-                style_mask = NSWindowStyleMask::NSClosableWindowMask
-                    | NSWindowStyleMask::NSMiniaturizableWindowMask
-                    | NSWindowStyleMask::NSResizableWindowMask
-                    | NSWindowStyleMask::NSTitledWindowMask;
-
-                if titlebar.appears_transparent {
-                    style_mask |= NSWindowStyleMask::NSFullSizeContentViewWindowMask;
-                }
-            } else {
-                style_mask = NSWindowStyleMask::NSTitledWindowMask
-                    | NSWindowStyleMask::NSFullSizeContentViewWindowMask;
-            }
-
-            let native_window: id = match options.kind {
-                WindowKind::Normal => msg_send![WINDOW_CLASS, alloc],
-                WindowKind::PopUp => {
-                    style_mask |= NSWindowStyleMaskNonactivatingPanel;
-                    msg_send![PANEL_CLASS, alloc]
-                }
-            };
-
-            let display = options
-                .display_id
-                .and_then(|display_id| MacDisplay::all().find(|display| display.id() == display_id))
-                .unwrap_or_else(MacDisplay::primary);
-
-            let mut target_screen = nil;
-            let screens = NSScreen::screens(nil);
-            let count: u64 = cocoa::foundation::NSArray::count(screens);
-            for i in 0..count {
-                let screen = cocoa::foundation::NSArray::objectAtIndex(screens, i);
-                let device_description = NSScreen::deviceDescription(screen);
-                let screen_number_key: id = NSString::alloc(nil).init_str("NSScreenNumber");
-                let screen_number = device_description.objectForKey_(screen_number_key);
-                let screen_number: NSUInteger = msg_send![screen_number, unsignedIntegerValue];
-                if screen_number as u32 == display.id().0 {
-                    target_screen = screen;
-                    break;
-                }
-            }
-
-            let native_window = native_window.initWithContentRect_styleMask_backing_defer_screen_(
-                NSRect::new(NSPoint::new(0., 0.), NSSize::new(1024., 768.)),
-                style_mask,
-                NSBackingStoreBuffered,
-                NO,
-                target_screen,
-            );
-            assert!(!native_window.is_null());
-            let () = msg_send![
-                native_window,
-                registerForDraggedTypes:
-                    NSArray::arrayWithObject(nil, NSFilenamesPboardType)
-            ];
-
-            let screen = native_window.screen();
-            match options.bounds {
-                WindowBounds::Fullscreen => {
-                    native_window.toggleFullScreen_(nil);
-                }
-                WindowBounds::Maximized => {
-                    native_window.setFrame_display_(screen.visibleFrame(), YES);
-                }
-                WindowBounds::Fixed(bounds) => {
-                    let display_bounds = display.bounds();
-                    let frame = if bounds.intersects(&display_bounds) {
-                        display_bounds_to_native(bounds)
-                    } else {
-                        display_bounds_to_native(display_bounds)
-                    };
-                    native_window.setFrame_display_(mem::transmute::<CGRect, NSRect>(frame), YES);
-                }
-            }
-
-            let native_view: id = msg_send![VIEW_CLASS, alloc];
-            let native_view = NSView::init(native_view);
-
-            assert!(!native_view.is_null());
-
-            let window = Self(Arc::new(Mutex::new(MacWindowState {
-                handle,
-                executor,
-                native_window,
-                renderer: MetalRenderer::new(true),
-                draw: Some(draw),
-                kind: options.kind,
-                event_callback: None,
-                activate_callback: None,
-                resize_callback: None,
-                fullscreen_callback: None,
-                moved_callback: None,
-                should_close_callback: None,
-                close_callback: None,
-                appearance_changed_callback: None,
-                input_handler: None,
-                pending_key_down: None,
-                last_key_equivalent: None,
-                synthetic_drag_counter: 0,
-                last_fresh_keydown: None,
-                traffic_light_position: options
-                    .titlebar
-                    .as_ref()
-                    .and_then(|titlebar| titlebar.traffic_light_position),
-                previous_modifiers_changed_event: None,
-                ime_state: ImeState::None,
-                ime_text: None,
-            })));
-
-            (*native_window).set_ivar(
-                WINDOW_STATE_IVAR,
-                Arc::into_raw(window.0.clone()) as *const c_void,
-            );
-            native_window.setDelegate_(native_window);
-            (*native_view).set_ivar(
-                WINDOW_STATE_IVAR,
-                Arc::into_raw(window.0.clone()) as *const c_void,
-            );
-
-            if let Some(title) = options
-                .titlebar
-                .as_ref()
-                .and_then(|t| t.title.as_ref().map(AsRef::as_ref))
-            {
-                native_window.setTitle_(NSString::alloc(nil).init_str(title));
-            }
-
-            native_window.setMovable_(options.is_movable as BOOL);
-
-            if options
-                .titlebar
-                .map_or(true, |titlebar| titlebar.appears_transparent)
-            {
-                native_window.setTitlebarAppearsTransparent_(YES);
-                native_window.setTitleVisibility_(NSWindowTitleVisibility::NSWindowTitleHidden);
-            }
-
-            native_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable);
-            native_view.setWantsBestResolutionOpenGLSurface_(YES);
-
-            // From winit crate: On Mojave, views automatically become layer-backed shortly after
-            // being added to a native_window. Changing the layer-backedness of a view breaks the
-            // association between the view and its associated OpenGL context. To work around this,
-            // on we explicitly make the view layer-backed up front so that AppKit doesn't do it
-            // itself and break the association with its context.
-            native_view.setWantsLayer(YES);
-            let _: () = msg_send![
-                native_view,
-                setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize
-            ];
-
-            native_window.setContentView_(native_view.autorelease());
-            native_window.makeFirstResponder_(native_view);
-
-            if options.center {
-                native_window.center();
-            }
-
-            match options.kind {
-                WindowKind::Normal => {
-                    native_window.setLevel_(NSNormalWindowLevel);
-                    native_window.setAcceptsMouseMovedEvents_(YES);
-                }
-                WindowKind::PopUp => {
-                    // Use a tracking area to allow receiving MouseMoved events even when
-                    // the window or application aren't active, which is often the case
-                    // e.g. for notification windows.
-                    let tracking_area: id = msg_send![class!(NSTrackingArea), alloc];
-                    let _: () = msg_send![
-                        tracking_area,
-                        initWithRect: NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.))
-                        options: NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways | NSTrackingInVisibleRect
-                        owner: native_view
-                        userInfo: nil
-                    ];
-                    let _: () =
-                        msg_send![native_view, addTrackingArea: tracking_area.autorelease()];
-
-                    native_window.setLevel_(NSPopUpWindowLevel);
-                    let _: () = msg_send![
-                        native_window,
-                        setAnimationBehavior: NSWindowAnimationBehaviorUtilityWindow
-                    ];
-                    native_window.setCollectionBehavior_(
-                        NSWindowCollectionBehavior::NSWindowCollectionBehaviorCanJoinAllSpaces |
-                        NSWindowCollectionBehavior::NSWindowCollectionBehaviorFullScreenAuxiliary
-                    );
-                }
-            }
-            if options.focus {
-                native_window.makeKeyAndOrderFront_(nil);
-            } else if options.show {
-                native_window.orderFront_(nil);
-            }
-
-            window.0.lock().move_traffic_light();
-            pool.drain();
-
-            window
-        }
-    }
-
-    pub fn active_window() -> Option<AnyWindowHandle> {
-        unsafe {
-            let app = NSApplication::sharedApplication(nil);
-            let main_window: id = msg_send![app, mainWindow];
-            if msg_send![main_window, isKindOfClass: WINDOW_CLASS] {
-                let handle = get_window_state(&*main_window).lock().handle;
-                Some(handle)
-            } else {
-                None
-            }
-        }
-    }
-}
-
-impl Drop for MacWindow {
-    fn drop(&mut self) {
-        let this = self.0.lock();
-        let window = this.native_window;
-        this.executor
-            .spawn(async move {
-                unsafe {
-                    // todo!() this panic()s when you click the red close button
-                    // unless should_close returns false.
-                    // (luckliy in zed it always returns false)
-                    window.close();
-                }
-            })
-            .detach();
-    }
-}
-
-impl PlatformWindow for MacWindow {
-    fn bounds(&self) -> WindowBounds {
-        self.0.as_ref().lock().bounds()
-    }
-
-    fn content_size(&self) -> Size<Pixels> {
-        self.0.as_ref().lock().content_size()
-    }
-
-    fn scale_factor(&self) -> f32 {
-        self.0.as_ref().lock().scale_factor()
-    }
-
-    fn titlebar_height(&self) -> Pixels {
-        self.0.as_ref().lock().titlebar_height()
-    }
-
-    fn appearance(&self) -> WindowAppearance {
-        unsafe {
-            let appearance: id = msg_send![self.0.lock().native_window, effectiveAppearance];
-            WindowAppearance::from_native(appearance)
-        }
-    }
-
-    fn display(&self) -> Rc<dyn PlatformDisplay> {
-        unsafe {
-            let screen = self.0.lock().native_window.screen();
-            let device_description: id = msg_send![screen, deviceDescription];
-            let screen_number: id = NSDictionary::valueForKey_(
-                device_description,
-                NSString::alloc(nil).init_str("NSScreenNumber"),
-            );
-
-            let screen_number: u32 = msg_send![screen_number, unsignedIntValue];
-
-            Rc::new(MacDisplay(screen_number))
-        }
-    }
-
-    fn mouse_position(&self) -> Point<Pixels> {
-        let position = unsafe {
-            self.0
-                .lock()
-                .native_window
-                .mouseLocationOutsideOfEventStream()
-        };
-        convert_mouse_position(position, self.content_size().height)
-    }
-
-    fn modifiers(&self) -> Modifiers {
-        unsafe {
-            let modifiers: NSEventModifierFlags = msg_send![class!(NSEvent), modifierFlags];
-
-            let control = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
-            let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
-            let shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
-            let command = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
-            let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask);
-
-            Modifiers {
-                control,
-                alt,
-                shift,
-                command,
-                function,
-            }
-        }
-    }
-
-    fn as_any_mut(&mut self) -> &mut dyn Any {
-        self
-    }
-
-    fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>) {
-        self.0.as_ref().lock().input_handler = Some(input_handler);
-    }
-
-    fn clear_input_handler(&mut self) {
-        self.0.as_ref().lock().input_handler = None;
-    }
-
-    fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize> {
-        // macOs applies overrides to modal window buttons after they are added.
-        // Two most important for this logic are:
-        // * Buttons with "Cancel" title will be displayed as the last buttons in the modal
-        // * Last button added to the modal via `addButtonWithTitle` stays focused
-        // * Focused buttons react on "space"/" " keypresses
-        // * Usage of `keyEquivalent`, `makeFirstResponder` or `setInitialFirstResponder` does not change the focus
-        //
-        // See also https://developer.apple.com/documentation/appkit/nsalert/1524532-addbuttonwithtitle#discussion
-        // ```
-        // By default, the first button has a key equivalent of Return,
-        // any button with a title of “Cancel” has a key equivalent of Escape,
-        // and any button with the title “Don’t Save” has a key equivalent of Command-D (but only if it’s not the first button).
-        // ```
-        //
-        // To avoid situations when the last element added is "Cancel" and it gets the focus
-        // (hence stealing both ESC and Space shortcuts), we find and add one non-Cancel button
-        // last, so it gets focus and a Space shortcut.
-        // This way, "Save this file? Yes/No/Cancel"-ish modals will get all three buttons mapped with a key.
-        let latest_non_cancel_label = answers
-            .iter()
-            .enumerate()
-            .rev()
-            .find(|(_, &label)| label != "Cancel")
-            .filter(|&(label_index, _)| label_index > 0);
-
-        unsafe {
-            let alert: id = msg_send![class!(NSAlert), alloc];
-            let alert: id = msg_send![alert, init];
-            let alert_style = match level {
-                PromptLevel::Info => 1,
-                PromptLevel::Warning => 0,
-                PromptLevel::Critical => 2,
-            };
-            let _: () = msg_send![alert, setAlertStyle: alert_style];
-            let _: () = msg_send![alert, setMessageText: ns_string(msg)];
-
-            for (ix, answer) in answers
-                .iter()
-                .enumerate()
-                .filter(|&(ix, _)| Some(ix) != latest_non_cancel_label.map(|(ix, _)| ix))
-            {
-                let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)];
-                let _: () = msg_send![button, setTag: ix as NSInteger];
-            }
-            if let Some((ix, answer)) = latest_non_cancel_label {
-                let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)];
-                let _: () = msg_send![button, setTag: ix as NSInteger];
-            }
-
-            let (done_tx, done_rx) = oneshot::channel();
-            let done_tx = Cell::new(Some(done_tx));
-            let block = ConcreteBlock::new(move |answer: NSInteger| {
-                if let Some(done_tx) = done_tx.take() {
-                    let _ = done_tx.send(answer.try_into().unwrap());
-                }
-            });
-            let block = block.copy();
-            let native_window = self.0.lock().native_window;
-            let executor = self.0.lock().executor.clone();
-            executor
-                .spawn(async move {
-                    let _: () = msg_send![
-                        alert,
-                        beginSheetModalForWindow: native_window
-                        completionHandler: block
-                    ];
-                })
-                .detach();
-
-            done_rx
-        }
-    }
-
-    fn activate(&self) {
-        let window = self.0.lock().native_window;
-        let executor = self.0.lock().executor.clone();
-        executor
-            .spawn(async move {
-                unsafe {
-                    let _: () = msg_send![window, makeKeyAndOrderFront: nil];
-                }
-            })
-            .detach();
-    }
-
-    fn set_title(&mut self, title: &str) {
-        unsafe {
-            let app = NSApplication::sharedApplication(nil);
-            let window = self.0.lock().native_window;
-            let title = ns_string(title);
-            let _: () = msg_send![app, changeWindowsItem:window title:title filename:false];
-            let _: () = msg_send![window, setTitle: title];
-            self.0.lock().move_traffic_light();
-        }
-    }
-
-    fn set_edited(&mut self, edited: bool) {
-        unsafe {
-            let window = self.0.lock().native_window;
-            msg_send![window, setDocumentEdited: edited as BOOL]
-        }
-
-        // Changing the document edited state resets the traffic light position,
-        // so we have to move it again.
-        self.0.lock().move_traffic_light();
-    }
-
-    fn show_character_palette(&self) {
-        let this = self.0.lock();
-        let window = this.native_window;
-        this.executor
-            .spawn(async move {
-                unsafe {
-                    let app = NSApplication::sharedApplication(nil);
-                    let _: () = msg_send![app, orderFrontCharacterPalette: window];
-                }
-            })
-            .detach();
-    }
-
-    fn minimize(&self) {
-        let window = self.0.lock().native_window;
-        unsafe {
-            window.miniaturize_(nil);
-        }
-    }
-
-    fn zoom(&self) {
-        let this = self.0.lock();
-        let window = this.native_window;
-        this.executor
-            .spawn(async move {
-                unsafe {
-                    window.zoom_(nil);
-                }
-            })
-            .detach();
-    }
-
-    fn toggle_full_screen(&self) {
-        let this = self.0.lock();
-        let window = this.native_window;
-        this.executor
-            .spawn(async move {
-                unsafe {
-                    window.toggleFullScreen_(nil);
-                }
-            })
-            .detach();
-    }
-
-    fn on_input(&self, callback: Box<dyn FnMut(InputEvent) -> bool>) {
-        self.0.as_ref().lock().event_callback = Some(callback);
-    }
-
-    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
-        self.0.as_ref().lock().activate_callback = Some(callback);
-    }
-
-    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
-        self.0.as_ref().lock().resize_callback = Some(callback);
-    }
-
-    fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>) {
-        self.0.as_ref().lock().fullscreen_callback = Some(callback);
-    }
-
-    fn on_moved(&self, callback: Box<dyn FnMut()>) {
-        self.0.as_ref().lock().moved_callback = Some(callback);
-    }
-
-    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
-        self.0.as_ref().lock().should_close_callback = Some(callback);
-    }
-
-    fn on_close(&self, callback: Box<dyn FnOnce()>) {
-        self.0.as_ref().lock().close_callback = Some(callback);
-    }
-
-    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
-        self.0.lock().appearance_changed_callback = Some(callback);
-    }
-
-    fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool {
-        let self_borrow = self.0.lock();
-        let self_handle = self_borrow.handle;
-
-        unsafe {
-            let app = NSApplication::sharedApplication(nil);
-
-            // Convert back to screen coordinates
-            let screen_point = self_borrow.to_screen_ns_point(position);
-
-            let window_number: NSInteger = msg_send![class!(NSWindow), windowNumberAtPoint:screen_point belowWindowWithWindowNumber:0];
-            let top_most_window: id = msg_send![app, windowWithWindowNumber: window_number];
-
-            let is_panel: BOOL = msg_send![top_most_window, isKindOfClass: PANEL_CLASS];
-            let is_window: BOOL = msg_send![top_most_window, isKindOfClass: WINDOW_CLASS];
-            if is_panel == YES || is_window == YES {
-                let topmost_window = get_window_state(&*top_most_window).lock().handle;
-                topmost_window == self_handle
-            } else {
-                // Someone else's window is on top
-                false
-            }
-        }
-    }
-
-    fn invalidate(&self) {
-        let this = self.0.lock();
-        unsafe {
-            let _: () = msg_send![this.native_window.contentView(), setNeedsDisplay: YES];
-        }
-    }
-
-    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
-        self.0.lock().renderer.sprite_atlas().clone()
-    }
-}
-
-fn get_scale_factor(native_window: id) -> f32 {
-    unsafe {
-        let screen: id = msg_send![native_window, screen];
-        NSScreen::backingScaleFactor(screen) as f32
-    }
-}
-
-unsafe fn get_window_state(object: &Object) -> Arc<Mutex<MacWindowState>> {
-    let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
-    let rc1 = Arc::from_raw(raw as *mut Mutex<MacWindowState>);
-    let rc2 = rc1.clone();
-    mem::forget(rc1);
-    rc2
-}
-
-unsafe fn drop_window_state(object: &Object) {
-    let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
-    Rc::from_raw(raw as *mut RefCell<MacWindowState>);
-}
-
-extern "C" fn yes(_: &Object, _: Sel) -> BOOL {
-    YES
-}
-
-extern "C" fn dealloc_window(this: &Object, _: Sel) {
-    unsafe {
-        drop_window_state(this);
-        let _: () = msg_send![super(this, class!(NSWindow)), dealloc];
-    }
-}
-
-extern "C" fn dealloc_view(this: &Object, _: Sel) {
-    unsafe {
-        drop_window_state(this);
-        let _: () = msg_send![super(this, class!(NSView)), dealloc];
-    }
-}
-
-extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) -> BOOL {
-    handle_key_event(this, native_event, true)
-}
-
-extern "C" fn handle_key_down(this: &Object, _: Sel, native_event: id) {
-    handle_key_event(this, native_event, false);
-}
-
-extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: bool) -> BOOL {
-    let window_state = unsafe { get_window_state(this) };
-    let mut lock = window_state.as_ref().lock();
-
-    let window_height = lock.content_size().height;
-    let event = unsafe { InputEvent::from_native(native_event, Some(window_height)) };
-
-    if let Some(InputEvent::KeyDown(event)) = event {
-        // For certain keystrokes, macOS will first dispatch a "key equivalent" event.
-        // If that event isn't handled, it will then dispatch a "key down" event. GPUI
-        // makes no distinction between these two types of events, so we need to ignore
-        // the "key down" event if we've already just processed its "key equivalent" version.
-        if key_equivalent {
-            lock.last_key_equivalent = Some(event.clone());
-        } else if lock.last_key_equivalent.take().as_ref() == Some(&event) {
-            return NO;
-        }
-
-        let keydown = event.keystroke.clone();
-        let fn_modifier = keydown.modifiers.function;
-        // Ignore events from held-down keys after some of the initially-pressed keys
-        // were released.
-        if event.is_held {
-            if lock.last_fresh_keydown.as_ref() != Some(&keydown) {
-                return YES;
-            }
-        } else {
-            lock.last_fresh_keydown = Some(keydown);
-        }
-        lock.pending_key_down = Some((event, None));
-        drop(lock);
-
-        // Send the event to the input context for IME handling, unless the `fn` modifier is
-        // being pressed.
-        if !fn_modifier {
-            unsafe {
-                let input_context: id = msg_send![this, inputContext];
-                let _: BOOL = msg_send![input_context, handleEvent: native_event];
-            }
-        }
-
-        let mut handled = false;
-        let mut lock = window_state.lock();
-        let ime_text = lock.ime_text.clone();
-        if let Some((event, insert_text)) = lock.pending_key_down.take() {
-            let is_held = event.is_held;
-            if let Some(mut callback) = lock.event_callback.take() {
-                drop(lock);
-
-                let is_composing =
-                    with_input_handler(this, |input_handler| input_handler.marked_text_range())
-                        .flatten()
-                        .is_some();
-                if !is_composing {
-                    // if the IME has changed the key, we'll first emit an event with the character
-                    // generated by the IME system; then fallback to the keystroke if that is not
-                    // handled.
-                    // cases that we have working:
-                    // - " on a brazillian layout by typing <quote><space>
-                    // - ctrl-` on a brazillian layout by typing <ctrl-`>
-                    // - $ on a czech QWERTY layout by typing <alt-4>
-                    // - 4 on a czech QWERTY layout by typing <shift-4>
-                    // - ctrl-4 on a czech QWERTY layout by typing <ctrl-alt-4> (or <ctrl-shift-4>)
-                    if ime_text.is_some() && ime_text.as_ref() != Some(&event.keystroke.key) {
-                        let event_with_ime_text = KeyDownEvent {
-                            is_held: false,
-                            keystroke: Keystroke {
-                                // we match ctrl because some use-cases need it.
-                                // we don't match alt because it's often used to generate the optional character
-                                // we don't match shift because we're not here with letters (usually)
-                                // we don't match cmd/fn because they don't seem to use IME
-                                modifiers: Default::default(),
-                                key: ime_text.clone().unwrap(),
-                                ime_key: None, // todo!("handle IME key")
-                            },
-                        };
-                        handled = callback(InputEvent::KeyDown(event_with_ime_text));
-                    }
-                    if !handled {
-                        // empty key happens when you type a deadkey in input composition.
-                        // (e.g. on a brazillian keyboard typing quote is a deadkey)
-                        if !event.keystroke.key.is_empty() {
-                            handled = callback(InputEvent::KeyDown(event));
-                        }
-                    }
-                }
-
-                if !handled {
-                    if let Some(insert) = insert_text {
-                        handled = true;
-                        with_input_handler(this, |input_handler| {
-                            input_handler
-                                .replace_text_in_range(insert.replacement_range, &insert.text)
-                        });
-                    } else if !is_composing && is_held {
-                        if let Some(last_insert_text) = ime_text {
-                            //MacOS IME is a bit funky, and even when you've told it there's nothing to
-                            //inter it will still swallow certain keys (e.g. 'f', 'j') and not others
-                            //(e.g. 'n'). This is a problem for certain kinds of views, like the terminal
-                            with_input_handler(this, |input_handler| {
-                                if input_handler.selected_text_range().is_none() {
-                                    handled = true;
-                                    input_handler.replace_text_in_range(None, &last_insert_text)
-                                }
-                            });
-                        }
-                    }
-                }
-
-                window_state.lock().event_callback = Some(callback);
-            }
-        } else {
-            handled = true;
-        }
-
-        handled as BOOL
-    } else {
-        NO
-    }
-}
-
-extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
-    let window_state = unsafe { get_window_state(this) };
-    let weak_window_state = Arc::downgrade(&window_state);
-    let mut lock = window_state.as_ref().lock();
-    let is_active = unsafe { lock.native_window.isKeyWindow() == YES };
-
-    let window_height = lock.content_size().height;
-    let event = unsafe { InputEvent::from_native(native_event, Some(window_height)) };
-
-    if let Some(mut event) = event {
-        match &mut event {
-            InputEvent::MouseDown(
-                event @ MouseDownEvent {
-                    button: MouseButton::Left,
-                    modifiers: Modifiers { control: true, .. },
-                    ..
-                },
-            ) => {
-                // On mac, a ctrl-left click should be handled as a right click.
-                *event = MouseDownEvent {
-                    button: MouseButton::Right,
-                    modifiers: Modifiers {
-                        control: false,
-                        ..event.modifiers
-                    },
-                    click_count: 1,
-                    ..*event
-                };
-            }
-
-            // Because we map a ctrl-left_down to a right_down -> right_up let's ignore
-            // the ctrl-left_up to avoid having a mismatch in button down/up events if the
-            // user is still holding ctrl when releasing the left mouse button
-            InputEvent::MouseUp(
-                event @ MouseUpEvent {
-                    button: MouseButton::Left,
-                    modifiers: Modifiers { control: true, .. },
-                    ..
-                },
-            ) => {
-                *event = MouseUpEvent {
-                    button: MouseButton::Right,
-                    modifiers: Modifiers {
-                        control: false,
-                        ..event.modifiers
-                    },
-                    click_count: 1,
-                    ..*event
-                };
-            }
-
-            _ => {}
-        };
-
-        match &event {
-            InputEvent::MouseMove(
-                event @ MouseMoveEvent {
-                    pressed_button: Some(_),
-                    ..
-                },
-            ) => {
-                lock.synthetic_drag_counter += 1;
-                let executor = lock.executor.clone();
-                executor
-                    .spawn(synthetic_drag(
-                        weak_window_state,
-                        lock.synthetic_drag_counter,
-                        event.clone(),
-                    ))
-                    .detach();
-            }
-
-            InputEvent::MouseMove(_) if !(is_active || lock.kind == WindowKind::PopUp) => return,
-
-            InputEvent::MouseUp(MouseUpEvent { .. }) => {
-                lock.synthetic_drag_counter += 1;
-            }
-
-            InputEvent::ModifiersChanged(ModifiersChangedEvent { modifiers }) => {
-                // Only raise modifiers changed event when they have actually changed
-                if let Some(InputEvent::ModifiersChanged(ModifiersChangedEvent {
-                    modifiers: prev_modifiers,
-                })) = &lock.previous_modifiers_changed_event
-                {
-                    if prev_modifiers == modifiers {
-                        return;
-                    }
-                }
-
-                lock.previous_modifiers_changed_event = Some(event.clone());
-            }
-
-            _ => {}
-        }
-
-        if let Some(mut callback) = lock.event_callback.take() {
-            drop(lock);
-            callback(event);
-            window_state.lock().event_callback = Some(callback);
-        }
-    }
-}
-
-// Allows us to receive `cmd-.` (the shortcut for closing a dialog)
-// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6
-extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
-    let window_state = unsafe { get_window_state(this) };
-    let mut lock = window_state.as_ref().lock();
-
-    let keystroke = Keystroke {
-        modifiers: Default::default(),
-        key: ".".into(),
-        ime_key: None,
-    };
-    let event = InputEvent::KeyDown(KeyDownEvent {
-        keystroke: keystroke.clone(),
-        is_held: false,
-    });
-
-    lock.last_fresh_keydown = Some(keystroke);
-    if let Some(mut callback) = lock.event_callback.take() {
-        drop(lock);
-        callback(event);
-        window_state.lock().event_callback = Some(callback);
-    }
-}
-
-extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) {
-    let window_state = unsafe { get_window_state(this) };
-    window_state.as_ref().lock().move_traffic_light();
-}
-
-extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) {
-    window_fullscreen_changed(this, true);
-}
-
-extern "C" fn window_will_exit_fullscreen(this: &Object, _: Sel, _: id) {
-    window_fullscreen_changed(this, false);
-}
-
-fn window_fullscreen_changed(this: &Object, is_fullscreen: bool) {
-    let window_state = unsafe { get_window_state(this) };
-    let mut lock = window_state.as_ref().lock();
-    if let Some(mut callback) = lock.fullscreen_callback.take() {
-        drop(lock);
-        callback(is_fullscreen);
-        window_state.lock().fullscreen_callback = Some(callback);
-    }
-}
-
-extern "C" fn window_did_move(this: &Object, _: Sel, _: id) {
-    let window_state = unsafe { get_window_state(this) };
-    let mut lock = window_state.as_ref().lock();
-    if let Some(mut callback) = lock.moved_callback.take() {
-        drop(lock);
-        callback();
-        window_state.lock().moved_callback = Some(callback);
-    }
-}
-
-extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) {
-    let window_state = unsafe { get_window_state(this) };
-    let lock = window_state.lock();
-    let is_active = unsafe { lock.native_window.isKeyWindow() == YES };
-
-    // When opening a pop-up while the application isn't active, Cocoa sends a spurious
-    // `windowDidBecomeKey` message to the previous key window even though that window
-    // isn't actually key. This causes a bug if the application is later activated while
-    // the pop-up is still open, making it impossible to activate the previous key window
-    // even if the pop-up gets closed. The only way to activate it again is to de-activate
-    // the app and re-activate it, which is a pretty bad UX.
-    // The following code detects the spurious event and invokes `resignKeyWindow`:
-    // in theory, we're not supposed to invoke this method manually but it balances out
-    // the spurious `becomeKeyWindow` event and helps us work around that bug.
-    if selector == sel!(windowDidBecomeKey:) && !is_active {
-        unsafe {
-            let _: () = msg_send![lock.native_window, resignKeyWindow];
-            return;
-        }
-    }
-
-    let executor = lock.executor.clone();
-    drop(lock);
-    executor
-        .spawn(async move {
-            let mut lock = window_state.as_ref().lock();
-            if let Some(mut callback) = lock.activate_callback.take() {
-                drop(lock);
-                callback(is_active);
-                window_state.lock().activate_callback = Some(callback);
-            };
-        })
-        .detach();
-}
-
-extern "C" fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL {
-    let window_state = unsafe { get_window_state(this) };
-    let mut lock = window_state.as_ref().lock();
-    if let Some(mut callback) = lock.should_close_callback.take() {
-        drop(lock);
-        let should_close = callback();
-        window_state.lock().should_close_callback = Some(callback);
-        should_close as BOOL
-    } else {
-        YES
-    }
-}
-
-extern "C" fn close_window(this: &Object, _: Sel) {
-    unsafe {
-        let close_callback = {
-            let window_state = get_window_state(this);
-            window_state
-                .as_ref()
-                .try_lock()
-                .and_then(|mut window_state| window_state.close_callback.take())
-        };
-
-        if let Some(callback) = close_callback {
-            callback();
-        }
-
-        let _: () = msg_send![super(this, class!(NSWindow)), close];
-    }
-}
-
-extern "C" fn make_backing_layer(this: &Object, _: Sel) -> id {
-    let window_state = unsafe { get_window_state(this) };
-    let window_state = window_state.as_ref().lock();
-    window_state.renderer.layer().as_ptr() as id
-}
-
-extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel) {
-    let window_state = unsafe { get_window_state(this) };
-    let mut lock = window_state.as_ref().lock();
-
-    unsafe {
-        let scale_factor = lock.scale_factor() as f64;
-        let size = lock.content_size();
-        let drawable_size: NSSize = NSSize {
-            width: f64::from(size.width) * scale_factor,
-            height: f64::from(size.height) * scale_factor,
-        };
-
-        let _: () = msg_send![
-            lock.renderer.layer(),
-            setContentsScale: scale_factor
-        ];
-        let _: () = msg_send![
-            lock.renderer.layer(),
-            setDrawableSize: drawable_size
-        ];
-    }
-
-    if let Some(mut callback) = lock.resize_callback.take() {
-        let content_size = lock.content_size();
-        let scale_factor = lock.scale_factor();
-        drop(lock);
-        callback(content_size, scale_factor);
-        window_state.as_ref().lock().resize_callback = Some(callback);
-    };
-}
-
-extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) {
-    let window_state = unsafe { get_window_state(this) };
-    let lock = window_state.as_ref().lock();
-
-    if lock.content_size() == size.into() {
-        return;
-    }
-
-    unsafe {
-        let _: () = msg_send![super(this, class!(NSView)), setFrameSize: size];
-    }
-
-    let scale_factor = lock.scale_factor() as f64;
-    let drawable_size: NSSize = NSSize {
-        width: size.width * scale_factor,
-        height: size.height * scale_factor,
-    };
-
-    unsafe {
-        let _: () = msg_send![
-            lock.renderer.layer(),
-            setDrawableSize: drawable_size
-        ];
-    }
-
-    drop(lock);
-    let mut lock = window_state.lock();
-    if let Some(mut callback) = lock.resize_callback.take() {
-        let content_size = lock.content_size();
-        let scale_factor = lock.scale_factor();
-        drop(lock);
-        callback(content_size, scale_factor);
-        window_state.lock().resize_callback = Some(callback);
-    };
-}
-
-extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
-    unsafe {
-        let window_state = get_window_state(this);
-        let mut draw = window_state.lock().draw.take().unwrap();
-        let scene = draw().log_err();
-        let mut window_state = window_state.lock();
-        window_state.draw = Some(draw);
-        if let Some(scene) = scene {
-            window_state.renderer.draw(&scene);
-        }
-    }
-}
-
-extern "C" fn valid_attributes_for_marked_text(_: &Object, _: Sel) -> id {
-    unsafe { msg_send![class!(NSArray), array] }
-}
-
-extern "C" fn has_marked_text(this: &Object, _: Sel) -> BOOL {
-    with_input_handler(this, |input_handler| input_handler.marked_text_range())
-        .flatten()
-        .is_some() as BOOL
-}
-
-extern "C" fn marked_range(this: &Object, _: Sel) -> NSRange {
-    with_input_handler(this, |input_handler| input_handler.marked_text_range())
-        .flatten()
-        .map_or(NSRange::invalid(), |range| range.into())
-}
-
-extern "C" fn selected_range(this: &Object, _: Sel) -> NSRange {
-    with_input_handler(this, |input_handler| input_handler.selected_text_range())
-        .flatten()
-        .map_or(NSRange::invalid(), |range| range.into())
-}
-
-extern "C" fn first_rect_for_character_range(
-    this: &Object,
-    _: Sel,
-    range: NSRange,
-    _: id,
-) -> NSRect {
-    let frame = unsafe {
-        let window = get_window_state(this).lock().native_window;
-        NSView::frame(window)
-    };
-    with_input_handler(this, |input_handler| {
-        input_handler.bounds_for_range(range.to_range()?)
-    })
-    .flatten()
-    .map_or(
-        NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.)),
-        |bounds| {
-            NSRect::new(
-                NSPoint::new(
-                    frame.origin.x + bounds.origin.x.0 as f64,
-                    frame.origin.y + frame.size.height
-                        - bounds.origin.y.0 as f64
-                        - bounds.size.height.0 as f64,
-                ),
-                NSSize::new(bounds.size.width.0 as f64, bounds.size.height.0 as f64),
-            )
-        },
-    )
-}
-
-extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NSRange) {
-    unsafe {
-        let window_state = get_window_state(this);
-        let mut lock = window_state.lock();
-        let pending_key_down = lock.pending_key_down.take();
-        drop(lock);
-
-        let is_attributed_string: BOOL =
-            msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
-        let text: id = if is_attributed_string == YES {
-            msg_send![text, string]
-        } else {
-            text
-        };
-        let text = CStr::from_ptr(text.UTF8String() as *mut c_char)
-            .to_str()
-            .unwrap();
-        let replacement_range = replacement_range.to_range();
-
-        window_state.lock().ime_text = Some(text.to_string());
-        window_state.lock().ime_state = ImeState::Acted;
-
-        let is_composing =
-            with_input_handler(this, |input_handler| input_handler.marked_text_range())
-                .flatten()
-                .is_some();
-
-        if is_composing || text.chars().count() > 1 || pending_key_down.is_none() {
-            with_input_handler(this, |input_handler| {
-                input_handler.replace_text_in_range(replacement_range, text)
-            });
-        } else {
-            let mut pending_key_down = pending_key_down.unwrap();
-            pending_key_down.1 = Some(InsertText {
-                replacement_range,
-                text: text.to_string(),
-            });
-            window_state.lock().pending_key_down = Some(pending_key_down);
-        }
-    }
-}
-
-extern "C" fn set_marked_text(
-    this: &Object,
-    _: Sel,
-    text: id,
-    selected_range: NSRange,
-    replacement_range: NSRange,
-) {
-    unsafe {
-        let window_state = get_window_state(this);
-        window_state.lock().pending_key_down.take();
-
-        let is_attributed_string: BOOL =
-            msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
-        let text: id = if is_attributed_string == YES {
-            msg_send![text, string]
-        } else {
-            text
-        };
-        let selected_range = selected_range.to_range();
-        let replacement_range = replacement_range.to_range();
-        let text = CStr::from_ptr(text.UTF8String() as *mut c_char)
-            .to_str()
-            .unwrap();
-
-        window_state.lock().ime_state = ImeState::Acted;
-        window_state.lock().ime_text = Some(text.to_string());
-
-        with_input_handler(this, |input_handler| {
-            input_handler.replace_and_mark_text_in_range(replacement_range, text, selected_range);
-        });
-    }
-}
-
-extern "C" fn unmark_text(this: &Object, _: Sel) {
-    unsafe {
-        let state = get_window_state(this);
-        let mut borrow = state.lock();
-        borrow.ime_state = ImeState::Acted;
-        borrow.ime_text.take();
-    }
-
-    with_input_handler(this, |input_handler| input_handler.unmark_text());
-}
-
-extern "C" fn attributed_substring_for_proposed_range(
-    this: &Object,
-    _: Sel,
-    range: NSRange,
-    _actual_range: *mut c_void,
-) -> id {
-    with_input_handler(this, |input_handler| {
-        let range = range.to_range()?;
-        if range.is_empty() {
-            return None;
-        }
-
-        let selected_text = input_handler.text_for_range(range)?;
-        unsafe {
-            let string: id = msg_send![class!(NSAttributedString), alloc];
-            let string: id = msg_send![string, initWithString: ns_string(&selected_text)];
-            Some(string)
-        }
-    })
-    .flatten()
-    .unwrap_or(nil)
-}
-
-extern "C" fn do_command_by_selector(this: &Object, _: Sel, _: Sel) {
-    unsafe {
-        let state = get_window_state(this);
-        let mut borrow = state.lock();
-        borrow.ime_state = ImeState::Continue;
-        borrow.ime_text.take();
-    }
-}
-
-extern "C" fn view_did_change_effective_appearance(this: &Object, _: Sel) {
-    unsafe {
-        let state = get_window_state(this);
-        let mut lock = state.as_ref().lock();
-        if let Some(mut callback) = lock.appearance_changed_callback.take() {
-            drop(lock);
-            callback();
-            state.lock().appearance_changed_callback = Some(callback);
-        }
-    }
-}
-
-extern "C" fn accepts_first_mouse(this: &Object, _: Sel, _: id) -> BOOL {
-    unsafe {
-        let state = get_window_state(this);
-        let lock = state.as_ref().lock();
-        if lock.kind == WindowKind::PopUp {
-            YES
-        } else {
-            NO
-        }
-    }
-}
-
-extern "C" fn dragging_entered(this: &Object, _: Sel, dragging_info: id) -> NSDragOperation {
-    let window_state = unsafe { get_window_state(this) };
-    if send_new_event(&window_state, {
-        let position = drag_event_position(&window_state, dragging_info);
-        let paths = external_paths_from_event(dragging_info);
-        InputEvent::FileDrop(FileDropEvent::Entered {
-            position,
-            files: paths,
-        })
-    }) {
-        NSDragOperationCopy
-    } else {
-        NSDragOperationNone
-    }
-}
-
-extern "C" fn dragging_updated(this: &Object, _: Sel, dragging_info: id) -> NSDragOperation {
-    let window_state = unsafe { get_window_state(this) };
-    let position = drag_event_position(&window_state, dragging_info);
-    if send_new_event(
-        &window_state,
-        InputEvent::FileDrop(FileDropEvent::Pending { position }),
-    ) {
-        NSDragOperationCopy
-    } else {
-        NSDragOperationNone
-    }
-}
-
-extern "C" fn dragging_exited(this: &Object, _: Sel, _: id) {
-    let window_state = unsafe { get_window_state(this) };
-    send_new_event(&window_state, InputEvent::FileDrop(FileDropEvent::Exited));
-}
-
-extern "C" fn perform_drag_operation(this: &Object, _: Sel, dragging_info: id) -> BOOL {
-    let window_state = unsafe { get_window_state(this) };
-    let position = drag_event_position(&window_state, dragging_info);
-    if send_new_event(
-        &window_state,
-        InputEvent::FileDrop(FileDropEvent::Submit { position }),
-    ) {
-        YES
-    } else {
-        NO
-    }
-}
-
-fn external_paths_from_event(dragging_info: *mut Object) -> ExternalPaths {
-    let mut paths = SmallVec::new();
-    let pasteboard: id = unsafe { msg_send![dragging_info, draggingPasteboard] };
-    let filenames = unsafe { NSPasteboard::propertyListForType(pasteboard, NSFilenamesPboardType) };
-    for file in unsafe { filenames.iter() } {
-        let path = unsafe {
-            let f = NSString::UTF8String(file);
-            CStr::from_ptr(f).to_string_lossy().into_owned()
-        };
-        paths.push(PathBuf::from(path))
-    }
-    ExternalPaths(paths)
-}
-
-extern "C" fn conclude_drag_operation(this: &Object, _: Sel, _: id) {
-    let window_state = unsafe { get_window_state(this) };
-    send_new_event(&window_state, InputEvent::FileDrop(FileDropEvent::Exited));
-}
-
-async fn synthetic_drag(
-    window_state: Weak<Mutex<MacWindowState>>,
-    drag_id: usize,
-    event: MouseMoveEvent,
-) {
-    loop {
-        Timer::after(Duration::from_millis(16)).await;
-        if let Some(window_state) = window_state.upgrade() {
-            let mut lock = window_state.lock();
-            if lock.synthetic_drag_counter == drag_id {
-                if let Some(mut callback) = lock.event_callback.take() {
-                    drop(lock);
-                    callback(InputEvent::MouseMove(event.clone()));
-                    window_state.lock().event_callback = Some(callback);
-                }
-            } else {
-                break;
-            }
-        }
-    }
-}
-
-fn send_new_event(window_state_lock: &Mutex<MacWindowState>, e: InputEvent) -> bool {
-    let window_state = window_state_lock.lock().event_callback.take();
-    if let Some(mut callback) = window_state {
-        callback(e);
-        window_state_lock.lock().event_callback = Some(callback);
-        true
-    } else {
-        false
-    }
-}
-
-fn drag_event_position(window_state: &Mutex<MacWindowState>, dragging_info: id) -> Point<Pixels> {
-    let drag_location: NSPoint = unsafe { msg_send![dragging_info, draggingLocation] };
-    convert_mouse_position(drag_location, window_state.lock().content_size().height)
-}
-
-fn with_input_handler<F, R>(window: &Object, f: F) -> Option<R>
-where
-    F: FnOnce(&mut dyn PlatformInputHandler) -> R,
-{
-    let window_state = unsafe { get_window_state(window) };
-    let mut lock = window_state.as_ref().lock();
-    if let Some(mut input_handler) = lock.input_handler.take() {
-        drop(lock);
-        let result = f(input_handler.as_mut());
-        window_state.lock().input_handler = Some(input_handler);
-        Some(result)
-    } else {
-        None
-    }
-}

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

@@ -1,9 +0,0 @@
-mod dispatcher;
-mod display;
-mod platform;
-mod window;
-
-pub use dispatcher::*;
-pub use display::*;
-pub use platform::*;
-pub use window::*;

crates/gpui2/src/scene.rs 🔗

@@ -1,778 +0,0 @@
-use crate::{
-    point, AtlasTextureId, AtlasTile, Bounds, ContentMask, Corners, Edges, Hsla, Pixels, Point,
-    ScaledPixels, StackingOrder,
-};
-use collections::BTreeMap;
-use std::{fmt::Debug, iter::Peekable, mem, slice};
-
-// Exported to metal
-pub(crate) type PointF = Point<f32>;
-#[allow(non_camel_case_types, unused)]
-pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
-
-pub type LayerId = u32;
-
-pub type DrawOrder = u32;
-
-#[derive(Default)]
-pub(crate) struct SceneBuilder {
-    last_order: Option<(StackingOrder, LayerId)>,
-    layers_by_order: BTreeMap<StackingOrder, LayerId>,
-    shadows: Vec<Shadow>,
-    quads: Vec<Quad>,
-    paths: Vec<Path<ScaledPixels>>,
-    underlines: Vec<Underline>,
-    monochrome_sprites: Vec<MonochromeSprite>,
-    polychrome_sprites: Vec<PolychromeSprite>,
-    surfaces: Vec<Surface>,
-}
-
-impl SceneBuilder {
-    pub fn build(&mut self) -> Scene {
-        let mut orders = vec![0; self.layers_by_order.len()];
-        for (ix, layer_id) in self.layers_by_order.values().enumerate() {
-            orders[*layer_id as usize] = ix as u32;
-        }
-        self.layers_by_order.clear();
-        self.last_order = None;
-
-        for shadow in &mut self.shadows {
-            shadow.order = orders[shadow.order as usize];
-        }
-        self.shadows.sort_by_key(|shadow| shadow.order);
-
-        for quad in &mut self.quads {
-            quad.order = orders[quad.order as usize];
-        }
-        self.quads.sort_by_key(|quad| quad.order);
-
-        for path in &mut self.paths {
-            path.order = orders[path.order as usize];
-        }
-        self.paths.sort_by_key(|path| path.order);
-
-        for underline in &mut self.underlines {
-            underline.order = orders[underline.order as usize];
-        }
-        self.underlines.sort_by_key(|underline| underline.order);
-
-        for monochrome_sprite in &mut self.monochrome_sprites {
-            monochrome_sprite.order = orders[monochrome_sprite.order as usize];
-        }
-        self.monochrome_sprites.sort_by_key(|sprite| sprite.order);
-
-        for polychrome_sprite in &mut self.polychrome_sprites {
-            polychrome_sprite.order = orders[polychrome_sprite.order as usize];
-        }
-        self.polychrome_sprites.sort_by_key(|sprite| sprite.order);
-
-        for surface in &mut self.surfaces {
-            surface.order = orders[surface.order as usize];
-        }
-        self.surfaces.sort_by_key(|surface| surface.order);
-
-        Scene {
-            shadows: mem::take(&mut self.shadows),
-            quads: mem::take(&mut self.quads),
-            paths: mem::take(&mut self.paths),
-            underlines: mem::take(&mut self.underlines),
-            monochrome_sprites: mem::take(&mut self.monochrome_sprites),
-            polychrome_sprites: mem::take(&mut self.polychrome_sprites),
-            surfaces: mem::take(&mut self.surfaces),
-        }
-    }
-
-    pub fn insert(&mut self, order: &StackingOrder, primitive: impl Into<Primitive>) {
-        let primitive = primitive.into();
-        let clipped_bounds = primitive
-            .bounds()
-            .intersect(&primitive.content_mask().bounds);
-        if clipped_bounds.size.width <= ScaledPixels(0.)
-            || clipped_bounds.size.height <= ScaledPixels(0.)
-        {
-            return;
-        }
-
-        let layer_id = self.layer_id_for_order(order);
-        match primitive {
-            Primitive::Shadow(mut shadow) => {
-                shadow.order = layer_id;
-                self.shadows.push(shadow);
-            }
-            Primitive::Quad(mut quad) => {
-                quad.order = layer_id;
-                self.quads.push(quad);
-            }
-            Primitive::Path(mut path) => {
-                path.order = layer_id;
-                path.id = PathId(self.paths.len());
-                self.paths.push(path);
-            }
-            Primitive::Underline(mut underline) => {
-                underline.order = layer_id;
-                self.underlines.push(underline);
-            }
-            Primitive::MonochromeSprite(mut sprite) => {
-                sprite.order = layer_id;
-                self.monochrome_sprites.push(sprite);
-            }
-            Primitive::PolychromeSprite(mut sprite) => {
-                sprite.order = layer_id;
-                self.polychrome_sprites.push(sprite);
-            }
-            Primitive::Surface(mut surface) => {
-                surface.order = layer_id;
-                self.surfaces.push(surface);
-            }
-        }
-    }
-
-    fn layer_id_for_order(&mut self, order: &StackingOrder) -> u32 {
-        if let Some((last_order, last_layer_id)) = self.last_order.as_ref() {
-            if last_order == order {
-                return *last_layer_id;
-            }
-        };
-
-        let layer_id = if let Some(layer_id) = self.layers_by_order.get(order) {
-            *layer_id
-        } else {
-            let next_id = self.layers_by_order.len() as LayerId;
-            self.layers_by_order.insert(order.clone(), next_id);
-            next_id
-        };
-        self.last_order = Some((order.clone(), layer_id));
-        layer_id
-    }
-}
-
-pub struct Scene {
-    pub shadows: Vec<Shadow>,
-    pub quads: Vec<Quad>,
-    pub paths: Vec<Path<ScaledPixels>>,
-    pub underlines: Vec<Underline>,
-    pub monochrome_sprites: Vec<MonochromeSprite>,
-    pub polychrome_sprites: Vec<PolychromeSprite>,
-    pub surfaces: Vec<Surface>,
-}
-
-impl Scene {
-    pub fn paths(&self) -> &[Path<ScaledPixels>] {
-        &self.paths
-    }
-
-    pub(crate) fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> {
-        BatchIterator {
-            shadows: &self.shadows,
-            shadows_start: 0,
-            shadows_iter: self.shadows.iter().peekable(),
-            quads: &self.quads,
-            quads_start: 0,
-            quads_iter: self.quads.iter().peekable(),
-            paths: &self.paths,
-            paths_start: 0,
-            paths_iter: self.paths.iter().peekable(),
-            underlines: &self.underlines,
-            underlines_start: 0,
-            underlines_iter: self.underlines.iter().peekable(),
-            monochrome_sprites: &self.monochrome_sprites,
-            monochrome_sprites_start: 0,
-            monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(),
-            polychrome_sprites: &self.polychrome_sprites,
-            polychrome_sprites_start: 0,
-            polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(),
-            surfaces: &self.surfaces,
-            surfaces_start: 0,
-            surfaces_iter: self.surfaces.iter().peekable(),
-        }
-    }
-}
-
-struct BatchIterator<'a> {
-    shadows: &'a [Shadow],
-    shadows_start: usize,
-    shadows_iter: Peekable<slice::Iter<'a, Shadow>>,
-    quads: &'a [Quad],
-    quads_start: usize,
-    quads_iter: Peekable<slice::Iter<'a, Quad>>,
-    paths: &'a [Path<ScaledPixels>],
-    paths_start: usize,
-    paths_iter: Peekable<slice::Iter<'a, Path<ScaledPixels>>>,
-    underlines: &'a [Underline],
-    underlines_start: usize,
-    underlines_iter: Peekable<slice::Iter<'a, Underline>>,
-    monochrome_sprites: &'a [MonochromeSprite],
-    monochrome_sprites_start: usize,
-    monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>,
-    polychrome_sprites: &'a [PolychromeSprite],
-    polychrome_sprites_start: usize,
-    polychrome_sprites_iter: Peekable<slice::Iter<'a, PolychromeSprite>>,
-    surfaces: &'a [Surface],
-    surfaces_start: usize,
-    surfaces_iter: Peekable<slice::Iter<'a, Surface>>,
-}
-
-impl<'a> Iterator for BatchIterator<'a> {
-    type Item = PrimitiveBatch<'a>;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        let mut orders_and_kinds = [
-            (
-                self.shadows_iter.peek().map(|s| s.order),
-                PrimitiveKind::Shadow,
-            ),
-            (self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad),
-            (self.paths_iter.peek().map(|q| q.order), PrimitiveKind::Path),
-            (
-                self.underlines_iter.peek().map(|u| u.order),
-                PrimitiveKind::Underline,
-            ),
-            (
-                self.monochrome_sprites_iter.peek().map(|s| s.order),
-                PrimitiveKind::MonochromeSprite,
-            ),
-            (
-                self.polychrome_sprites_iter.peek().map(|s| s.order),
-                PrimitiveKind::PolychromeSprite,
-            ),
-            (
-                self.surfaces_iter.peek().map(|s| s.order),
-                PrimitiveKind::Surface,
-            ),
-        ];
-        orders_and_kinds.sort_by_key(|(order, kind)| (order.unwrap_or(u32::MAX), *kind));
-
-        let first = orders_and_kinds[0];
-        let second = orders_and_kinds[1];
-        let (batch_kind, max_order) = if first.0.is_some() {
-            (first.1, second.0.unwrap_or(u32::MAX))
-        } else {
-            return None;
-        };
-
-        match batch_kind {
-            PrimitiveKind::Shadow => {
-                let shadows_start = self.shadows_start;
-                let mut shadows_end = shadows_start + 1;
-                self.shadows_iter.next();
-                while self
-                    .shadows_iter
-                    .next_if(|shadow| shadow.order < max_order)
-                    .is_some()
-                {
-                    shadows_end += 1;
-                }
-                self.shadows_start = shadows_end;
-                Some(PrimitiveBatch::Shadows(
-                    &self.shadows[shadows_start..shadows_end],
-                ))
-            }
-            PrimitiveKind::Quad => {
-                let quads_start = self.quads_start;
-                let mut quads_end = quads_start + 1;
-                self.quads_iter.next();
-                while self
-                    .quads_iter
-                    .next_if(|quad| quad.order < max_order)
-                    .is_some()
-                {
-                    quads_end += 1;
-                }
-                self.quads_start = quads_end;
-                Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end]))
-            }
-            PrimitiveKind::Path => {
-                let paths_start = self.paths_start;
-                let mut paths_end = paths_start + 1;
-                self.paths_iter.next();
-                while self
-                    .paths_iter
-                    .next_if(|path| path.order < max_order)
-                    .is_some()
-                {
-                    paths_end += 1;
-                }
-                self.paths_start = paths_end;
-                Some(PrimitiveBatch::Paths(&self.paths[paths_start..paths_end]))
-            }
-            PrimitiveKind::Underline => {
-                let underlines_start = self.underlines_start;
-                let mut underlines_end = underlines_start + 1;
-                self.underlines_iter.next();
-                while self
-                    .underlines_iter
-                    .next_if(|underline| underline.order < max_order)
-                    .is_some()
-                {
-                    underlines_end += 1;
-                }
-                self.underlines_start = underlines_end;
-                Some(PrimitiveBatch::Underlines(
-                    &self.underlines[underlines_start..underlines_end],
-                ))
-            }
-            PrimitiveKind::MonochromeSprite => {
-                let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id;
-                let sprites_start = self.monochrome_sprites_start;
-                let mut sprites_end = sprites_start + 1;
-                self.monochrome_sprites_iter.next();
-                while self
-                    .monochrome_sprites_iter
-                    .next_if(|sprite| {
-                        sprite.order < max_order && sprite.tile.texture_id == texture_id
-                    })
-                    .is_some()
-                {
-                    sprites_end += 1;
-                }
-                self.monochrome_sprites_start = sprites_end;
-                Some(PrimitiveBatch::MonochromeSprites {
-                    texture_id,
-                    sprites: &self.monochrome_sprites[sprites_start..sprites_end],
-                })
-            }
-            PrimitiveKind::PolychromeSprite => {
-                let texture_id = self.polychrome_sprites_iter.peek().unwrap().tile.texture_id;
-                let sprites_start = self.polychrome_sprites_start;
-                let mut sprites_end = self.polychrome_sprites_start + 1;
-                self.polychrome_sprites_iter.next();
-                while self
-                    .polychrome_sprites_iter
-                    .next_if(|sprite| {
-                        sprite.order < max_order && sprite.tile.texture_id == texture_id
-                    })
-                    .is_some()
-                {
-                    sprites_end += 1;
-                }
-                self.polychrome_sprites_start = sprites_end;
-                Some(PrimitiveBatch::PolychromeSprites {
-                    texture_id,
-                    sprites: &self.polychrome_sprites[sprites_start..sprites_end],
-                })
-            }
-            PrimitiveKind::Surface => {
-                let surfaces_start = self.surfaces_start;
-                let mut surfaces_end = surfaces_start + 1;
-                self.surfaces_iter.next();
-                while self
-                    .surfaces_iter
-                    .next_if(|surface| surface.order < max_order)
-                    .is_some()
-                {
-                    surfaces_end += 1;
-                }
-                self.surfaces_start = surfaces_end;
-                Some(PrimitiveBatch::Surfaces(
-                    &self.surfaces[surfaces_start..surfaces_end],
-                ))
-            }
-        }
-    }
-}
-
-#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
-pub enum PrimitiveKind {
-    Shadow,
-    #[default]
-    Quad,
-    Path,
-    Underline,
-    MonochromeSprite,
-    PolychromeSprite,
-    Surface,
-}
-
-pub enum Primitive {
-    Shadow(Shadow),
-    Quad(Quad),
-    Path(Path<ScaledPixels>),
-    Underline(Underline),
-    MonochromeSprite(MonochromeSprite),
-    PolychromeSprite(PolychromeSprite),
-    Surface(Surface),
-}
-
-impl Primitive {
-    pub fn bounds(&self) -> &Bounds<ScaledPixels> {
-        match self {
-            Primitive::Shadow(shadow) => &shadow.bounds,
-            Primitive::Quad(quad) => &quad.bounds,
-            Primitive::Path(path) => &path.bounds,
-            Primitive::Underline(underline) => &underline.bounds,
-            Primitive::MonochromeSprite(sprite) => &sprite.bounds,
-            Primitive::PolychromeSprite(sprite) => &sprite.bounds,
-            Primitive::Surface(surface) => &surface.bounds,
-        }
-    }
-
-    pub fn content_mask(&self) -> &ContentMask<ScaledPixels> {
-        match self {
-            Primitive::Shadow(shadow) => &shadow.content_mask,
-            Primitive::Quad(quad) => &quad.content_mask,
-            Primitive::Path(path) => &path.content_mask,
-            Primitive::Underline(underline) => &underline.content_mask,
-            Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
-            Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
-            Primitive::Surface(surface) => &surface.content_mask,
-        }
-    }
-}
-
-#[derive(Debug)]
-pub(crate) enum PrimitiveBatch<'a> {
-    Shadows(&'a [Shadow]),
-    Quads(&'a [Quad]),
-    Paths(&'a [Path<ScaledPixels>]),
-    Underlines(&'a [Underline]),
-    MonochromeSprites {
-        texture_id: AtlasTextureId,
-        sprites: &'a [MonochromeSprite],
-    },
-    PolychromeSprites {
-        texture_id: AtlasTextureId,
-        sprites: &'a [PolychromeSprite],
-    },
-    Surfaces(&'a [Surface]),
-}
-
-#[derive(Default, Debug, Clone, Eq, PartialEq)]
-#[repr(C)]
-pub struct Quad {
-    pub order: u32, // Initially a LayerId, then a DrawOrder.
-    pub bounds: Bounds<ScaledPixels>,
-    pub content_mask: ContentMask<ScaledPixels>,
-    pub background: Hsla,
-    pub border_color: Hsla,
-    pub corner_radii: Corners<ScaledPixels>,
-    pub border_widths: Edges<ScaledPixels>,
-}
-
-impl Ord for Quad {
-    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
-        self.order.cmp(&other.order)
-    }
-}
-
-impl PartialOrd for Quad {
-    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
-        Some(self.cmp(other))
-    }
-}
-
-impl From<Quad> for Primitive {
-    fn from(quad: Quad) -> Self {
-        Primitive::Quad(quad)
-    }
-}
-
-#[derive(Debug, Clone, Eq, PartialEq)]
-#[repr(C)]
-pub struct Underline {
-    pub order: u32,
-    pub bounds: Bounds<ScaledPixels>,
-    pub content_mask: ContentMask<ScaledPixels>,
-    pub thickness: ScaledPixels,
-    pub color: Hsla,
-    pub wavy: bool,
-}
-
-impl Ord for Underline {
-    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
-        self.order.cmp(&other.order)
-    }
-}
-
-impl PartialOrd for Underline {
-    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
-        Some(self.cmp(other))
-    }
-}
-
-impl From<Underline> for Primitive {
-    fn from(underline: Underline) -> Self {
-        Primitive::Underline(underline)
-    }
-}
-
-#[derive(Debug, Clone, Eq, PartialEq)]
-#[repr(C)]
-pub struct Shadow {
-    pub order: u32,
-    pub bounds: Bounds<ScaledPixels>,
-    pub corner_radii: Corners<ScaledPixels>,
-    pub content_mask: ContentMask<ScaledPixels>,
-    pub color: Hsla,
-    pub blur_radius: ScaledPixels,
-}
-
-impl Ord for Shadow {
-    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
-        self.order.cmp(&other.order)
-    }
-}
-
-impl PartialOrd for Shadow {
-    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
-        Some(self.cmp(other))
-    }
-}
-
-impl From<Shadow> for Primitive {
-    fn from(shadow: Shadow) -> Self {
-        Primitive::Shadow(shadow)
-    }
-}
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-#[repr(C)]
-pub struct MonochromeSprite {
-    pub order: u32,
-    pub bounds: Bounds<ScaledPixels>,
-    pub content_mask: ContentMask<ScaledPixels>,
-    pub color: Hsla,
-    pub tile: AtlasTile,
-}
-
-impl Ord for MonochromeSprite {
-    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
-        match self.order.cmp(&other.order) {
-            std::cmp::Ordering::Equal => self.tile.tile_id.cmp(&other.tile.tile_id),
-            order => order,
-        }
-    }
-}
-
-impl PartialOrd for MonochromeSprite {
-    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
-        Some(self.cmp(other))
-    }
-}
-
-impl From<MonochromeSprite> for Primitive {
-    fn from(sprite: MonochromeSprite) -> Self {
-        Primitive::MonochromeSprite(sprite)
-    }
-}
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-#[repr(C)]
-pub struct PolychromeSprite {
-    pub order: u32,
-    pub bounds: Bounds<ScaledPixels>,
-    pub content_mask: ContentMask<ScaledPixels>,
-    pub corner_radii: Corners<ScaledPixels>,
-    pub tile: AtlasTile,
-    pub grayscale: bool,
-}
-
-impl Ord for PolychromeSprite {
-    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
-        match self.order.cmp(&other.order) {
-            std::cmp::Ordering::Equal => self.tile.tile_id.cmp(&other.tile.tile_id),
-            order => order,
-        }
-    }
-}
-
-impl PartialOrd for PolychromeSprite {
-    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
-        Some(self.cmp(other))
-    }
-}
-
-impl From<PolychromeSprite> for Primitive {
-    fn from(sprite: PolychromeSprite) -> Self {
-        Primitive::PolychromeSprite(sprite)
-    }
-}
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct Surface {
-    pub order: u32,
-    pub bounds: Bounds<ScaledPixels>,
-    pub content_mask: ContentMask<ScaledPixels>,
-    pub image_buffer: media::core_video::CVImageBuffer,
-}
-
-impl Ord for Surface {
-    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
-        self.order.cmp(&other.order)
-    }
-}
-
-impl PartialOrd for Surface {
-    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
-        Some(self.cmp(other))
-    }
-}
-
-impl From<Surface> for Primitive {
-    fn from(surface: Surface) -> Self {
-        Primitive::Surface(surface)
-    }
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
-pub(crate) struct PathId(pub(crate) usize);
-
-#[derive(Debug)]
-pub struct Path<P: Clone + Default + Debug> {
-    pub(crate) id: PathId,
-    order: u32,
-    pub(crate) bounds: Bounds<P>,
-    pub(crate) content_mask: ContentMask<P>,
-    pub(crate) vertices: Vec<PathVertex<P>>,
-    pub(crate) color: Hsla,
-    start: Point<P>,
-    current: Point<P>,
-    contour_count: usize,
-}
-
-impl Path<Pixels> {
-    pub fn new(start: Point<Pixels>) -> Self {
-        Self {
-            id: PathId(0),
-            order: 0,
-            vertices: Vec::new(),
-            start,
-            current: start,
-            bounds: Bounds {
-                origin: start,
-                size: Default::default(),
-            },
-            content_mask: Default::default(),
-            color: Default::default(),
-            contour_count: 0,
-        }
-    }
-
-    pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
-        Path {
-            id: self.id,
-            order: self.order,
-            bounds: self.bounds.scale(factor),
-            content_mask: self.content_mask.scale(factor),
-            vertices: self
-                .vertices
-                .iter()
-                .map(|vertex| vertex.scale(factor))
-                .collect(),
-            start: self.start.map(|start| start.scale(factor)),
-            current: self.current.scale(factor),
-            contour_count: self.contour_count,
-            color: self.color,
-        }
-    }
-
-    pub fn line_to(&mut self, to: Point<Pixels>) {
-        self.contour_count += 1;
-        if self.contour_count > 1 {
-            self.push_triangle(
-                (self.start, self.current, to),
-                (point(0., 1.), point(0., 1.), point(0., 1.)),
-            );
-        }
-        self.current = to;
-    }
-
-    pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
-        self.contour_count += 1;
-        if self.contour_count > 1 {
-            self.push_triangle(
-                (self.start, self.current, to),
-                (point(0., 1.), point(0., 1.), point(0., 1.)),
-            );
-        }
-
-        self.push_triangle(
-            (self.current, ctrl, to),
-            (point(0., 0.), point(0.5, 0.), point(1., 1.)),
-        );
-        self.current = to;
-    }
-
-    fn push_triangle(
-        &mut self,
-        xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
-        st: (Point<f32>, Point<f32>, Point<f32>),
-    ) {
-        self.bounds = self
-            .bounds
-            .union(&Bounds {
-                origin: xy.0,
-                size: Default::default(),
-            })
-            .union(&Bounds {
-                origin: xy.1,
-                size: Default::default(),
-            })
-            .union(&Bounds {
-                origin: xy.2,
-                size: Default::default(),
-            });
-
-        self.vertices.push(PathVertex {
-            xy_position: xy.0,
-            st_position: st.0,
-            content_mask: Default::default(),
-        });
-        self.vertices.push(PathVertex {
-            xy_position: xy.1,
-            st_position: st.1,
-            content_mask: Default::default(),
-        });
-        self.vertices.push(PathVertex {
-            xy_position: xy.2,
-            st_position: st.2,
-            content_mask: Default::default(),
-        });
-    }
-}
-
-impl Eq for Path<ScaledPixels> {}
-
-impl PartialEq for Path<ScaledPixels> {
-    fn eq(&self, other: &Self) -> bool {
-        self.order == other.order
-    }
-}
-
-impl Ord for Path<ScaledPixels> {
-    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
-        self.order.cmp(&other.order)
-    }
-}
-
-impl PartialOrd for Path<ScaledPixels> {
-    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
-        Some(self.cmp(other))
-    }
-}
-
-impl From<Path<ScaledPixels>> for Primitive {
-    fn from(path: Path<ScaledPixels>) -> Self {
-        Primitive::Path(path)
-    }
-}
-
-#[derive(Clone, Debug)]
-#[repr(C)]
-pub struct PathVertex<P: Clone + Default + Debug> {
-    pub(crate) xy_position: Point<P>,
-    pub(crate) st_position: Point<f32>,
-    pub(crate) content_mask: ContentMask<P>,
-}
-
-impl PathVertex<Pixels> {
-    pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> {
-        PathVertex {
-            xy_position: self.xy_position.scale(factor),
-            st_position: self.st_position,
-            content_mask: self.content_mask.scale(factor),
-        }
-    }
-}
-
-#[derive(Copy, Clone, Debug)]
-pub struct AtlasId(pub(crate) usize);

crates/gpui2/src/test.rs 🔗

@@ -1,80 +0,0 @@
-use crate::{Entity, Subscription, TestAppContext, TestDispatcher};
-use futures::StreamExt as _;
-use rand::prelude::*;
-use smol::channel;
-use std::{
-    env,
-    panic::{self, RefUnwindSafe},
-};
-
-pub fn run_test(
-    mut num_iterations: u64,
-    max_retries: usize,
-    test_fn: &mut (dyn RefUnwindSafe + Fn(TestDispatcher, u64)),
-    on_fail_fn: Option<fn()>,
-    _fn_name: String, // todo!("re-enable fn_name")
-) {
-    let starting_seed = env::var("SEED")
-        .map(|seed| seed.parse().expect("invalid SEED variable"))
-        .unwrap_or(0);
-    let is_randomized = num_iterations > 1;
-    if let Ok(iterations) = env::var("ITERATIONS") {
-        num_iterations = iterations.parse().expect("invalid ITERATIONS variable");
-    }
-
-    for seed in starting_seed..starting_seed + num_iterations {
-        let mut retry = 0;
-        loop {
-            if is_randomized {
-                eprintln!("seed = {seed}");
-            }
-            let result = panic::catch_unwind(|| {
-                let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(seed));
-                test_fn(dispatcher, seed);
-            });
-
-            match result {
-                Ok(_) => break,
-                Err(error) => {
-                    if retry < max_retries {
-                        println!("retrying: attempt {}", retry);
-                        retry += 1;
-                    } else {
-                        if is_randomized {
-                            eprintln!("failing seed: {}", seed);
-                        }
-                        on_fail_fn.map(|f| f());
-                        panic::resume_unwind(error);
-                    }
-                }
-            }
-        }
-    }
-}
-
-pub struct Observation<T> {
-    rx: channel::Receiver<T>,
-    _subscription: Subscription,
-}
-
-impl<T: 'static> futures::Stream for Observation<T> {
-    type Item = T;
-
-    fn poll_next(
-        mut self: std::pin::Pin<&mut Self>,
-        cx: &mut std::task::Context<'_>,
-    ) -> std::task::Poll<Option<Self::Item>> {
-        self.rx.poll_next_unpin(cx)
-    }
-}
-
-pub fn observe<T: 'static>(entity: &impl Entity<T>, cx: &mut TestAppContext) -> Observation<()> {
-    let (tx, rx) = smol::channel::unbounded();
-    let _subscription = cx.update(|cx| {
-        cx.observe(entity, move |_, _| {
-            let _ = smol::block_on(tx.send(()));
-        })
-    });
-
-    Observation { rx, _subscription }
-}

crates/gpui2/src/util.rs 🔗

@@ -1,51 +0,0 @@
-#[cfg(any(test, feature = "test-support"))]
-use std::time::Duration;
-
-#[cfg(any(test, feature = "test-support"))]
-use futures::Future;
-
-#[cfg(any(test, feature = "test-support"))]
-use smol::future::FutureExt;
-
-pub use util::*;
-
-#[cfg(any(test, feature = "test-support"))]
-pub async fn timeout<F, T>(timeout: Duration, f: F) -> Result<T, ()>
-where
-    F: Future<Output = T>,
-{
-    let timer = async {
-        smol::Timer::after(timeout).await;
-        Err(())
-    };
-    let future = async move { Ok(f.await) };
-    timer.race(future).await
-}
-
-#[cfg(any(test, feature = "test-support"))]
-pub struct CwdBacktrace<'a>(pub &'a backtrace::Backtrace);
-
-#[cfg(any(test, feature = "test-support"))]
-impl<'a> std::fmt::Debug for CwdBacktrace<'a> {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        use backtrace::{BacktraceFmt, BytesOrWideString};
-
-        let cwd = std::env::current_dir().unwrap();
-        let cwd = cwd.parent().unwrap();
-        let mut print_path = |fmt: &mut std::fmt::Formatter<'_>, path: BytesOrWideString<'_>| {
-            std::fmt::Display::fmt(&path, fmt)
-        };
-        let mut fmt = BacktraceFmt::new(f, backtrace::PrintFmt::Full, &mut print_path);
-        for frame in self.0.frames() {
-            let mut formatted_frame = fmt.frame();
-            if frame
-                .symbols()
-                .iter()
-                .any(|s| s.filename().map_or(false, |f| f.starts_with(&cwd)))
-            {
-                formatted_frame.backtrace_frame(frame)?;
-            }
-        }
-        fmt.finish()
-    }
-}

crates/gpui2_macros/Cargo.toml 🔗

@@ -1,14 +0,0 @@
-[package]
-name = "gpui2_macros"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/gpui2_macros.rs"
-proc-macro = true
-
-[dependencies]
-syn = { version = "1.0.72", features = ["full"] }
-quote = "1.0.9"
-proc-macro2 = "1.0.66"

crates/gpui2_macros/src/gpui2_macros.rs 🔗

@@ -1,32 +0,0 @@
-mod derive_into_element;
-mod derive_render;
-mod register_action;
-mod style_helpers;
-mod test;
-
-use proc_macro::TokenStream;
-
-#[proc_macro]
-pub fn register_action(ident: TokenStream) -> TokenStream {
-    register_action::register_action_macro(ident)
-}
-
-#[proc_macro_derive(IntoElement)]
-pub fn derive_into_element(input: TokenStream) -> TokenStream {
-    derive_into_element::derive_into_element(input)
-}
-
-#[proc_macro_derive(Render)]
-pub fn derive_render(input: TokenStream) -> TokenStream {
-    derive_render::derive_render(input)
-}
-
-#[proc_macro]
-pub fn style_helpers(input: TokenStream) -> TokenStream {
-    style_helpers::style_helpers(input)
-}
-
-#[proc_macro_attribute]
-pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
-    test::test(args, function)
-}

crates/gpui_macros/Cargo.toml 🔗

@@ -7,10 +7,8 @@ publish = false
 [lib]
 path = "src/gpui_macros.rs"
 proc-macro = true
-doctest = false
 
 [dependencies]
-lazy_static.workspace = true
-proc-macro2 = "1.0"
-syn = "1.0"
-quote = "1.0"
+syn = { version = "1.0.72", features = ["full"] }
+quote = "1.0.9"
+proc-macro2 = "1.0.66"

crates/gpui2_macros/src/derive_into_element.rs → crates/gpui_macros/src/derive_into_element.rs 🔗

@@ -13,7 +13,7 @@ pub fn derive_into_element(input: TokenStream) -> TokenStream {
         {
             type Element = gpui::Component<Self>;
 
-            fn element_id(&self) -> Option<ElementId> {
+            fn element_id(&self) -> Option<gpui::ElementId> {
                 None
             }
 

crates/gpui2_macros/src/derive_render.rs → crates/gpui_macros/src/derive_render.rs 🔗

@@ -11,7 +11,7 @@ pub fn derive_render(input: TokenStream) -> TokenStream {
         impl #impl_generics gpui::Render for #type_name #type_generics
         #where_clause
         {
-            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl Element {
+            fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> impl gpui::Element {
                 ()
             }
         }

crates/gpui_macros/src/gpui_macros.rs 🔗

@@ -1,381 +1,32 @@
-use proc_macro::TokenStream;
-use proc_macro2::Ident;
-use quote::{format_ident, quote};
-use std::mem;
-use syn::{
-    parse_macro_input, parse_quote, spanned::Spanned as _, AttributeArgs, DeriveInput, FnArg,
-    GenericParam, Generics, ItemFn, Lit, Meta, NestedMeta, Type, WhereClause,
-};
-
-#[proc_macro_attribute]
-pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
-    let mut namespace = format_ident!("gpui");
-
-    let args = syn::parse_macro_input!(args as AttributeArgs);
-    let mut max_retries = 0;
-    let mut num_iterations = 1;
-    let mut starting_seed = 0;
-    let mut detect_nondeterminism = false;
-    let mut on_failure_fn_name = quote!(None);
-
-    for arg in args {
-        match arg {
-            NestedMeta::Meta(Meta::Path(name))
-                if name.get_ident().map_or(false, |n| n == "self") =>
-            {
-                namespace = format_ident!("crate");
-            }
-            NestedMeta::Meta(Meta::NameValue(meta)) => {
-                let key_name = meta.path.get_ident().map(|i| i.to_string());
-                let result = (|| {
-                    match key_name.as_deref() {
-                        Some("detect_nondeterminism") => {
-                            detect_nondeterminism = parse_bool(&meta.lit)?
-                        }
-                        Some("retries") => max_retries = parse_int(&meta.lit)?,
-                        Some("iterations") => num_iterations = parse_int(&meta.lit)?,
-                        Some("seed") => starting_seed = parse_int(&meta.lit)?,
-                        Some("on_failure") => {
-                            if let Lit::Str(name) = meta.lit {
-                                let mut path = syn::Path {
-                                    leading_colon: None,
-                                    segments: Default::default(),
-                                };
-                                for part in name.value().split("::") {
-                                    path.segments.push(Ident::new(part, name.span()).into());
-                                }
-                                on_failure_fn_name = quote!(Some(#path));
-                            } else {
-                                return Err(TokenStream::from(
-                                    syn::Error::new(
-                                        meta.lit.span(),
-                                        "on_failure argument must be a string",
-                                    )
-                                    .into_compile_error(),
-                                ));
-                            }
-                        }
-                        _ => {
-                            return Err(TokenStream::from(
-                                syn::Error::new(meta.path.span(), "invalid argument")
-                                    .into_compile_error(),
-                            ))
-                        }
-                    }
-                    Ok(())
-                })();
-
-                if let Err(tokens) = result {
-                    return tokens;
-                }
-            }
-            other => {
-                return TokenStream::from(
-                    syn::Error::new_spanned(other, "invalid argument").into_compile_error(),
-                )
-            }
-        }
-    }
-
-    let mut inner_fn = parse_macro_input!(function as ItemFn);
-    if max_retries > 0 && num_iterations > 1 {
-        return TokenStream::from(
-            syn::Error::new_spanned(inner_fn, "retries and randomized iterations can't be mixed")
-                .into_compile_error(),
-        );
-    }
-    let inner_fn_attributes = mem::take(&mut inner_fn.attrs);
-    let inner_fn_name = format_ident!("_{}", inner_fn.sig.ident);
-    let outer_fn_name = mem::replace(&mut inner_fn.sig.ident, inner_fn_name.clone());
-
-    let mut outer_fn: ItemFn = if inner_fn.sig.asyncness.is_some() {
-        // Pass to the test function the number of app contexts that it needs,
-        // based on its parameter list.
-        let mut cx_vars = proc_macro2::TokenStream::new();
-        let mut cx_teardowns = proc_macro2::TokenStream::new();
-        let mut inner_fn_args = proc_macro2::TokenStream::new();
-        for (ix, arg) in inner_fn.sig.inputs.iter().enumerate() {
-            if let FnArg::Typed(arg) = arg {
-                if let Type::Path(ty) = &*arg.ty {
-                    let last_segment = ty.path.segments.last();
-                    match last_segment.map(|s| s.ident.to_string()).as_deref() {
-                        Some("StdRng") => {
-                            inner_fn_args.extend(quote!(rand::SeedableRng::seed_from_u64(seed),));
-                            continue;
-                        }
-                        Some("Arc") => {
-                            if let syn::PathArguments::AngleBracketed(args) =
-                                &last_segment.unwrap().arguments
-                            {
-                                if let Some(syn::GenericArgument::Type(syn::Type::Path(ty))) =
-                                    args.args.last()
-                                {
-                                    let last_segment = ty.path.segments.last();
-                                    if let Some("Deterministic") =
-                                        last_segment.map(|s| s.ident.to_string()).as_deref()
-                                    {
-                                        inner_fn_args.extend(quote!(deterministic.clone(),));
-                                        continue;
-                                    }
-                                }
-                            }
-                        }
-                        _ => {}
-                    }
-                } else if let Type::Reference(ty) = &*arg.ty {
-                    if let Type::Path(ty) = &*ty.elem {
-                        let last_segment = ty.path.segments.last();
-                        if let Some("TestAppContext") =
-                            last_segment.map(|s| s.ident.to_string()).as_deref()
-                        {
-                            let first_entity_id = ix * 100_000;
-                            let cx_varname = format_ident!("cx_{}", ix);
-                            cx_vars.extend(quote!(
-                                let mut #cx_varname = #namespace::TestAppContext::new(
-                                    foreground_platform.clone(),
-                                    cx.platform().clone(),
-                                    deterministic.build_foreground(#ix),
-                                    deterministic.build_background(),
-                                    cx.font_cache().clone(),
-                                    cx.leak_detector(),
-                                    #first_entity_id,
-                                    stringify!(#outer_fn_name).to_string(),
-                                );
-                            ));
-                            cx_teardowns.extend(quote!(
-                                #cx_varname.remove_all_windows();
-                                deterministic.run_until_parked();
-                                #cx_varname.update(|cx| cx.clear_globals());
-                            ));
-                            inner_fn_args.extend(quote!(&mut #cx_varname,));
-                            continue;
-                        }
-                    }
-                }
-            }
-
-            return TokenStream::from(
-                syn::Error::new_spanned(arg, "invalid argument").into_compile_error(),
-            );
-        }
-
-        parse_quote! {
-            #[test]
-            fn #outer_fn_name() {
-                #inner_fn
-
-                #namespace::test::run_test(
-                    #num_iterations as u64,
-                    #starting_seed as u64,
-                    #max_retries,
-                    #detect_nondeterminism,
-                    &mut |cx, foreground_platform, deterministic, seed| {
-                        // some of the macro contents do not use all variables, silence the warnings
-                        let _ = (&cx, &foreground_platform, &deterministic, &seed);
-                        #cx_vars
-                        cx.foreground().run(#inner_fn_name(#inner_fn_args));
-                        #cx_teardowns
-                    },
-                    #on_failure_fn_name,
-                    stringify!(#outer_fn_name).to_string(),
-                );
-            }
-        }
-    } else {
-        // Pass to the test function the number of app contexts that it needs,
-        // based on its parameter list.
-        let mut cx_vars = proc_macro2::TokenStream::new();
-        let mut cx_teardowns = proc_macro2::TokenStream::new();
-        let mut inner_fn_args = proc_macro2::TokenStream::new();
-        for (ix, arg) in inner_fn.sig.inputs.iter().enumerate() {
-            if let FnArg::Typed(arg) = arg {
-                if let Type::Path(ty) = &*arg.ty {
-                    let last_segment = ty.path.segments.last();
-
-                    if let Some("StdRng") = last_segment.map(|s| s.ident.to_string()).as_deref() {
-                        inner_fn_args.extend(quote!(rand::SeedableRng::seed_from_u64(seed),));
-                        continue;
-                    }
-                } else if let Type::Reference(ty) = &*arg.ty {
-                    if let Type::Path(ty) = &*ty.elem {
-                        let last_segment = ty.path.segments.last();
-                        match last_segment.map(|s| s.ident.to_string()).as_deref() {
-                            Some("AppContext") => {
-                                inner_fn_args.extend(quote!(cx,));
-                                continue;
-                            }
-                            Some("TestAppContext") => {
-                                let first_entity_id = ix * 100_000;
-                                let cx_varname = format_ident!("cx_{}", ix);
-                                cx_vars.extend(quote!(
-                                    let mut #cx_varname = #namespace::TestAppContext::new(
-                                        foreground_platform.clone(),
-                                        cx.platform().clone(),
-                                        deterministic.build_foreground(#ix),
-                                        deterministic.build_background(),
-                                        cx.font_cache().clone(),
-                                        cx.leak_detector(),
-                                        #first_entity_id,
-                                        stringify!(#outer_fn_name).to_string(),
-                                    );
-                                ));
-                                cx_teardowns.extend(quote!(
-                                    #cx_varname.remove_all_windows();
-                                    deterministic.run_until_parked();
-                                    #cx_varname.update(|cx| cx.clear_globals());
-                                ));
-                                inner_fn_args.extend(quote!(&mut #cx_varname,));
-                                continue;
-                            }
-                            _ => {}
-                        }
-                    }
-                }
-            }
-
-            return TokenStream::from(
-                syn::Error::new_spanned(arg, "invalid argument").into_compile_error(),
-            );
-        }
-
-        parse_quote! {
-            #[test]
-            fn #outer_fn_name() {
-                #inner_fn
+mod derive_into_element;
+mod derive_render;
+mod register_action;
+mod style_helpers;
+mod test;
 
-                #namespace::test::run_test(
-                    #num_iterations as u64,
-                    #starting_seed as u64,
-                    #max_retries,
-                    #detect_nondeterminism,
-                    &mut |cx, foreground_platform, deterministic, seed| {
-                        // some of the macro contents do not use all variables, silence the warnings
-                        let _ = (&cx, &foreground_platform, &deterministic, &seed);
-                        #cx_vars
-                        #inner_fn_name(#inner_fn_args);
-                        #cx_teardowns
-                    },
-                    #on_failure_fn_name,
-                    stringify!(#outer_fn_name).to_string(),
-                );
-            }
-        }
-    };
-    outer_fn.attrs.extend(inner_fn_attributes);
+use proc_macro::TokenStream;
 
-    TokenStream::from(quote!(#outer_fn))
+#[proc_macro]
+pub fn register_action(ident: TokenStream) -> TokenStream {
+    register_action::register_action_macro(ident)
 }
 
-fn parse_int(literal: &Lit) -> Result<usize, TokenStream> {
-    let result = if let Lit::Int(int) = &literal {
-        int.base10_parse()
-    } else {
-        Err(syn::Error::new(literal.span(), "must be an integer"))
-    };
-
-    result.map_err(|err| TokenStream::from(err.into_compile_error()))
+#[proc_macro_derive(IntoElement)]
+pub fn derive_into_element(input: TokenStream) -> TokenStream {
+    derive_into_element::derive_into_element(input)
 }
 
-fn parse_bool(literal: &Lit) -> Result<bool, TokenStream> {
-    let result = if let Lit::Bool(result) = &literal {
-        Ok(result.value)
-    } else {
-        Err(syn::Error::new(literal.span(), "must be a boolean"))
-    };
-
-    result.map_err(|err| TokenStream::from(err.into_compile_error()))
+#[proc_macro_derive(Render)]
+pub fn derive_render(input: TokenStream) -> TokenStream {
+    derive_render::derive_render(input)
 }
 
-#[proc_macro_derive(Element)]
-pub fn element_derive(input: TokenStream) -> TokenStream {
-    let ast = parse_macro_input!(input as DeriveInput);
-    let type_name = ast.ident;
-
-    let placeholder_view_generics: Generics = parse_quote! { <V: 'static> };
-    let placeholder_view_type_name: Ident = parse_quote! { V };
-    let view_type_name: Ident;
-    let impl_generics: syn::ImplGenerics<'_>;
-    let type_generics: Option<syn::TypeGenerics<'_>>;
-    let where_clause: Option<&'_ WhereClause>;
-
-    match ast.generics.params.iter().find_map(|param| {
-        if let GenericParam::Type(type_param) = param {
-            Some(type_param.ident.clone())
-        } else {
-            None
-        }
-    }) {
-        Some(type_name) => {
-            view_type_name = type_name;
-            let generics = ast.generics.split_for_impl();
-            impl_generics = generics.0;
-            type_generics = Some(generics.1);
-            where_clause = generics.2;
-        }
-        _ => {
-            view_type_name = placeholder_view_type_name;
-            let generics = placeholder_view_generics.split_for_impl();
-            impl_generics = generics.0;
-            type_generics = None;
-            where_clause = generics.2;
-        }
-    }
-
-    let gen = quote! {
-        impl #impl_generics Element<#view_type_name> for #type_name #type_generics
-        #where_clause
-        {
-
-            type LayoutState = gpui::elements::AnyElement<V>;
-            type PaintState = ();
-
-            fn layout(
-                &mut self,
-                constraint: gpui::SizeConstraint,
-                view: &mut V,
-                cx: &mut gpui::ViewContext<V>,
-            ) -> (gpui::geometry::vector::Vector2F, gpui::elements::AnyElement<V>) {
-                let mut element = self.render(view, cx).into_any();
-                let size = element.layout(constraint, view, cx);
-                (size, element)
-            }
-
-            fn paint(
-                &mut self,
-                bounds: gpui::geometry::rect::RectF,
-                visible_bounds: gpui::geometry::rect::RectF,
-                element: &mut gpui::elements::AnyElement<V>,
-                view: &mut V,
-                cx: &mut gpui::ViewContext<V>,
-            ) {
-                element.paint(bounds.origin(), visible_bounds, view, cx);
-            }
-
-            fn rect_for_text_range(
-                &self,
-                range_utf16: std::ops::Range<usize>,
-                _: gpui::geometry::rect::RectF,
-                _: gpui::geometry::rect::RectF,
-                element: &gpui::elements::AnyElement<V>,
-                _: &(),
-                view: &V,
-                cx: &gpui::ViewContext<V>,
-            ) -> Option<gpui::geometry::rect::RectF> {
-                element.rect_for_text_range(range_utf16, view, cx)
-            }
-
-            fn debug(
-                &self,
-                _: gpui::geometry::rect::RectF,
-                element: &gpui::elements::AnyElement<V>,
-                _: &(),
-                view: &V,
-                cx: &gpui::ViewContext<V>,
-            ) -> gpui::json::Value {
-                element.debug(view, cx)
-            }
-        }
-    };
+#[proc_macro]
+pub fn style_helpers(input: TokenStream) -> TokenStream {
+    style_helpers::style_helpers(input)
+}
 
-    gen.into()
+#[proc_macro_attribute]
+pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
+    test::test(args, function)
 }

crates/install_cli/src/install_cli.rs 🔗

@@ -1,13 +1,12 @@
-use std::path::Path;
-
 use anyhow::{anyhow, Result};
 use gpui::{actions, AsyncAppContext};
+use std::path::Path;
 use util::ResultExt;
 
 actions!(cli, [Install]);
 
 pub async fn install_cli(cx: &AsyncAppContext) -> Result<()> {
-    let cli_path = cx.platform().path_for_auxiliary_executable("cli")?;
+    let cli_path = cx.update(|cx| cx.path_for_auxiliary_executable("cli"))??;
     let link_path = Path::new("/usr/local/bin/zed");
     let bin_dir_path = link_path.parent().unwrap();
 

crates/install_cli2/Cargo.toml 🔗

@@ -1,19 +0,0 @@
-[package]
-name = "install_cli2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/install_cli2.rs"
-
-[features]
-test-support = []
-
-[dependencies]
-smol.workspace = true
-anyhow.workspace = true
-log.workspace = true
-serde.workspace = true
-gpui = { package = "gpui2", path = "../gpui2" }
-util = { path = "../util" }

crates/install_cli2/src/install_cli2.rs 🔗

@@ -1,54 +0,0 @@
-use anyhow::{anyhow, Result};
-use gpui::{actions, AsyncAppContext};
-use std::path::Path;
-use util::ResultExt;
-
-actions!(cli, [Install]);
-
-pub async fn install_cli(cx: &AsyncAppContext) -> Result<()> {
-    let cli_path = cx.update(|cx| cx.path_for_auxiliary_executable("cli"))??;
-    let link_path = Path::new("/usr/local/bin/zed");
-    let bin_dir_path = link_path.parent().unwrap();
-
-    // Don't re-create symlink if it points to the same CLI binary.
-    if smol::fs::read_link(link_path).await.ok().as_ref() == Some(&cli_path) {
-        return Ok(());
-    }
-
-    // If the symlink is not there or is outdated, first try replacing it
-    // without escalating.
-    smol::fs::remove_file(link_path).await.log_err();
-    if smol::fs::unix::symlink(&cli_path, link_path)
-        .await
-        .log_err()
-        .is_some()
-    {
-        return Ok(());
-    }
-
-    // The symlink could not be created, so use osascript with admin privileges
-    // to create it.
-    let status = smol::process::Command::new("/usr/bin/osascript")
-        .args([
-            "-e",
-            &format!(
-                "do shell script \" \
-                    mkdir -p \'{}\' && \
-                    ln -sf \'{}\' \'{}\' \
-                \" with administrator privileges",
-                bin_dir_path.to_string_lossy(),
-                cli_path.to_string_lossy(),
-                link_path.to_string_lossy(),
-            ),
-        ])
-        .stdout(smol::process::Stdio::inherit())
-        .stderr(smol::process::Stdio::inherit())
-        .output()
-        .await?
-        .status;
-    if status.success() {
-        Ok(())
-    } else {
-        Err(anyhow!("error running osascript"))
-    }
-}

crates/journal/Cargo.toml 🔗

@@ -14,7 +14,6 @@ gpui = { path = "../gpui" }
 util = { path = "../util" }
 workspace = { path = "../workspace" }
 settings = { path = "../settings" }
-
 anyhow.workspace = true
 chrono = "0.4"
 dirs = "4.0"

crates/journal/src/journal.rs 🔗

@@ -1,15 +1,15 @@
 use anyhow::Result;
 use chrono::{Datelike, Local, NaiveTime, Timelike};
-use editor::{scroll::autoscroll::Autoscroll, Editor};
-use gpui::{actions, AppContext};
+use gpui::{actions, AppContext, ViewContext};
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
+use settings::Settings;
 use std::{
     fs::OpenOptions,
     path::{Path, PathBuf},
     sync::Arc,
 };
-use workspace::AppState;
+use workspace::{AppState, Workspace};
 
 actions!(journal, [NewJournalEntry]);
 
@@ -36,24 +36,35 @@ pub enum HourFormat {
     Hour24,
 }
 
-impl settings::Setting for JournalSettings {
+impl settings::Settings for JournalSettings {
     const KEY: Option<&'static str> = Some("journal");
 
     type FileContent = Self;
 
-    fn load(default_value: &Self, user_values: &[&Self], _: &AppContext) -> Result<Self> {
-        Self::load_via_json_merge(default_value, user_values)
+    fn load(
+        defaults: &Self::FileContent,
+        user_values: &[&Self::FileContent],
+        _: &mut AppContext,
+    ) -> Result<Self> {
+        Self::load_via_json_merge(defaults, user_values)
     }
 }
 
-pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
-    settings::register::<JournalSettings>(cx);
-
-    cx.add_global_action(move |_: &NewJournalEntry, cx| new_journal_entry(app_state.clone(), cx));
+pub fn init(_: Arc<AppState>, cx: &mut AppContext) {
+    JournalSettings::register(cx);
+
+    cx.observe_new_views(
+        |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
+            workspace.register_action(|workspace, _: &NewJournalEntry, cx| {
+                new_journal_entry(workspace.app_state().clone(), cx);
+            });
+        },
+    )
+    .detach();
 }
 
 pub fn new_journal_entry(app_state: Arc<AppState>, cx: &mut AppContext) {
-    let settings = settings::get::<JournalSettings>(cx);
+    let settings = JournalSettings::get_global(cx);
     let journal_dir = match journal_dir(settings.path.as_ref().unwrap()) {
         Some(journal_dir) => journal_dir,
         None => {
@@ -68,9 +79,9 @@ pub fn new_journal_entry(app_state: Arc<AppState>, cx: &mut AppContext) {
         .join(format!("{:02}", now.month()));
     let entry_path = month_dir.join(format!("{:02}.md", now.day()));
     let now = now.time();
-    let entry_heading = heading_entry(now, &settings.hour_format);
+    let _entry_heading = heading_entry(now, &settings.hour_format);
 
-    let create_entry = cx.background().spawn(async move {
+    let create_entry = cx.background_executor().spawn(async move {
         std::fs::create_dir_all(month_dir)?;
         OpenOptions::new()
             .create(true)
@@ -82,30 +93,31 @@ pub fn new_journal_entry(app_state: Arc<AppState>, cx: &mut AppContext) {
     cx.spawn(|mut cx| async move {
         let (journal_dir, entry_path) = create_entry.await?;
         let (workspace, _) = cx
-            .update(|cx| workspace::open_paths(&[journal_dir], &app_state, None, cx))
+            .update(|cx| workspace::open_paths(&[journal_dir], &app_state, None, cx))?
             .await?;
 
-        let opened = workspace
+        let _opened = workspace
             .update(&mut cx, |workspace, cx| {
                 workspace.open_paths(vec![entry_path], true, cx)
             })?
             .await;
 
-        if let Some(Some(Ok(item))) = opened.first() {
-            if let Some(editor) = item.downcast::<Editor>().map(|editor| editor.downgrade()) {
-                editor.update(&mut cx, |editor, cx| {
-                    let len = editor.buffer().read(cx).len(cx);
-                    editor.change_selections(Some(Autoscroll::center()), cx, |s| {
-                        s.select_ranges([len..len])
-                    });
-                    if len > 0 {
-                        editor.insert("\n\n", cx);
-                    }
-                    editor.insert(&entry_heading, cx);
-                    editor.insert("\n\n", cx);
-                })?;
-            }
-        }
+        // todo!("editor")
+        // if let Some(Some(Ok(item))) = opened.first() {
+        //     if let Some(editor) = item.downcast::<Editor>().map(|editor| editor.downgrade()) {
+        //         editor.update(&mut cx, |editor, cx| {
+        //             let len = editor.buffer().read(cx).len(cx);
+        //             editor.change_selections(Some(Autoscroll::center()), cx, |s| {
+        //                 s.select_ranges([len..len])
+        //             });
+        //             if len > 0 {
+        //                 editor.insert("\n\n", cx);
+        //             }
+        //             editor.insert(&entry_heading, cx);
+        //             editor.insert("\n\n", cx);
+        //         })?;
+        //     }
+        // }
 
         anyhow::Ok(())
     })

crates/journal2/Cargo.toml 🔗

@@ -1,27 +0,0 @@
-[package]
-name = "journal2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/journal2.rs"
-doctest = false
-
-[dependencies]
-editor = { package = "editor2", path = "../editor2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-util = { path = "../util" }
-workspace2 = { path = "../workspace2" }
-settings2 = { path = "../settings2" }
-
-anyhow.workspace = true
-chrono = "0.4"
-dirs = "4.0"
-serde.workspace = true
-schemars.workspace = true
-log.workspace = true
-shellexpand = "2.1.0"
-
-[dev-dependencies]
-editor = { package="editor2", path = "../editor2", features = ["test-support"] }

crates/journal2/src/journal2.rs 🔗

@@ -1,181 +0,0 @@
-use anyhow::Result;
-use chrono::{Datelike, Local, NaiveTime, Timelike};
-use gpui::{actions, AppContext, ViewContext};
-use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
-use settings2::Settings;
-use std::{
-    fs::OpenOptions,
-    path::{Path, PathBuf},
-    sync::Arc,
-};
-use workspace2::{AppState, Workspace};
-
-actions!(journal, [NewJournalEntry]);
-
-#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
-pub struct JournalSettings {
-    pub path: Option<String>,
-    pub hour_format: Option<HourFormat>,
-}
-
-impl Default for JournalSettings {
-    fn default() -> Self {
-        Self {
-            path: Some("~".into()),
-            hour_format: Some(Default::default()),
-        }
-    }
-}
-
-#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum HourFormat {
-    #[default]
-    Hour12,
-    Hour24,
-}
-
-impl settings2::Settings for JournalSettings {
-    const KEY: Option<&'static str> = Some("journal");
-
-    type FileContent = Self;
-
-    fn load(
-        defaults: &Self::FileContent,
-        user_values: &[&Self::FileContent],
-        _: &mut AppContext,
-    ) -> Result<Self> {
-        Self::load_via_json_merge(defaults, user_values)
-    }
-}
-
-pub fn init(_: Arc<AppState>, cx: &mut AppContext) {
-    JournalSettings::register(cx);
-
-    cx.observe_new_views(
-        |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
-            workspace.register_action(|workspace, _: &NewJournalEntry, cx| {
-                new_journal_entry(workspace.app_state().clone(), cx);
-            });
-        },
-    )
-    .detach();
-}
-
-pub fn new_journal_entry(app_state: Arc<AppState>, cx: &mut AppContext) {
-    let settings = JournalSettings::get_global(cx);
-    let journal_dir = match journal_dir(settings.path.as_ref().unwrap()) {
-        Some(journal_dir) => journal_dir,
-        None => {
-            log::error!("Can't determine journal directory");
-            return;
-        }
-    };
-
-    let now = Local::now();
-    let month_dir = journal_dir
-        .join(format!("{:02}", now.year()))
-        .join(format!("{:02}", now.month()));
-    let entry_path = month_dir.join(format!("{:02}.md", now.day()));
-    let now = now.time();
-    let _entry_heading = heading_entry(now, &settings.hour_format);
-
-    let create_entry = cx.background_executor().spawn(async move {
-        std::fs::create_dir_all(month_dir)?;
-        OpenOptions::new()
-            .create(true)
-            .write(true)
-            .open(&entry_path)?;
-        Ok::<_, std::io::Error>((journal_dir, entry_path))
-    });
-
-    cx.spawn(|mut cx| async move {
-        let (journal_dir, entry_path) = create_entry.await?;
-        let (workspace, _) = cx
-            .update(|cx| workspace2::open_paths(&[journal_dir], &app_state, None, cx))?
-            .await?;
-
-        let _opened = workspace
-            .update(&mut cx, |workspace, cx| {
-                workspace.open_paths(vec![entry_path], true, cx)
-            })?
-            .await;
-
-        // todo!("editor")
-        // if let Some(Some(Ok(item))) = opened.first() {
-        //     if let Some(editor) = item.downcast::<Editor>().map(|editor| editor.downgrade()) {
-        //         editor.update(&mut cx, |editor, cx| {
-        //             let len = editor.buffer().read(cx).len(cx);
-        //             editor.change_selections(Some(Autoscroll::center()), cx, |s| {
-        //                 s.select_ranges([len..len])
-        //             });
-        //             if len > 0 {
-        //                 editor.insert("\n\n", cx);
-        //             }
-        //             editor.insert(&entry_heading, cx);
-        //             editor.insert("\n\n", cx);
-        //         })?;
-        //     }
-        // }
-
-        anyhow::Ok(())
-    })
-    .detach_and_log_err(cx);
-}
-
-fn journal_dir(path: &str) -> Option<PathBuf> {
-    let expanded_journal_dir = shellexpand::full(path) //TODO handle this better
-        .ok()
-        .map(|dir| Path::new(&dir.to_string()).to_path_buf().join("journal"));
-
-    return expanded_journal_dir;
-}
-
-fn heading_entry(now: NaiveTime, hour_format: &Option<HourFormat>) -> String {
-    match hour_format {
-        Some(HourFormat::Hour24) => {
-            let hour = now.hour();
-            format!("# {}:{:02}", hour, now.minute())
-        }
-        _ => {
-            let (pm, hour) = now.hour12();
-            let am_or_pm = if pm { "PM" } else { "AM" };
-            format!("# {}:{:02} {}", hour, now.minute(), am_or_pm)
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    mod heading_entry_tests {
-        use super::super::*;
-
-        #[test]
-        fn test_heading_entry_defaults_to_hour_12() {
-            let naive_time = NaiveTime::from_hms_milli_opt(15, 0, 0, 0).unwrap();
-            let actual_heading_entry = heading_entry(naive_time, &None);
-            let expected_heading_entry = "# 3:00 PM";
-
-            assert_eq!(actual_heading_entry, expected_heading_entry);
-        }
-
-        #[test]
-        fn test_heading_entry_is_hour_12() {
-            let naive_time = NaiveTime::from_hms_milli_opt(15, 0, 0, 0).unwrap();
-            let actual_heading_entry = heading_entry(naive_time, &Some(HourFormat::Hour12));
-            let expected_heading_entry = "# 3:00 PM";
-
-            assert_eq!(actual_heading_entry, expected_heading_entry);
-        }
-
-        #[test]
-        fn test_heading_entry_is_hour_24() {
-            let naive_time = NaiveTime::from_hms_milli_opt(15, 0, 0, 0).unwrap();
-            let actual_heading_entry = heading_entry(naive_time, &Some(HourFormat::Hour24));
-            let expected_heading_entry = "# 15:00";
-
-            assert_eq!(actual_heading_entry, expected_heading_entry);
-        }
-    }
-}

crates/language/Cargo.toml 🔗

@@ -24,8 +24,7 @@ test-support = [
 [dependencies]
 clock = { path = "../clock" }
 collections = { path = "../collections" }
-fuzzy = { path = "../fuzzy" }
-fs = { path = "../fs" }
+fuzzy = {  path = "../fuzzy" }
 git = { path = "../git" }
 gpui = { path = "../gpui" }
 lsp = { path = "../lsp" }
@@ -45,7 +44,6 @@ lazy_static.workspace = true
 log.workspace = true
 parking_lot.workspace = true
 postage.workspace = true
-pulldown-cmark = { version = "0.9.2", default-features = false }
 regex.workspace = true
 schemars.workspace = true
 serde.workspace = true
@@ -60,6 +58,7 @@ unicase = "2.6"
 rand = { workspace = true, optional = true }
 tree-sitter-rust = { workspace = true, optional = true }
 tree-sitter-typescript = { workspace = true, optional = true }
+pulldown-cmark = { version = "0.9.2", default-features = false }
 
 [dev-dependencies]
 client = { path = "../client", features = ["test-support"] }

crates/language/src/buffer.rs 🔗

@@ -18,7 +18,8 @@ use crate::{
 use anyhow::{anyhow, Result};
 pub use clock::ReplicaId;
 use futures::channel::oneshot;
-use gpui::{fonts::HighlightStyle, AppContext, Entity, ModelContext, Task};
+use gpui::{AppContext, EventEmitter, HighlightStyle, ModelContext, Task, TaskLabel};
+use lazy_static::lazy_static;
 use lsp::LanguageServerId;
 use parking_lot::Mutex;
 use similar::{ChangeTag, TextDiff};
@@ -52,14 +53,23 @@ pub use {tree_sitter_rust, tree_sitter_typescript};
 
 pub use lsp::DiagnosticSeverity;
 
+lazy_static! {
+    pub static ref BUFFER_DIFF_TASK: TaskLabel = TaskLabel::new();
+}
+
 pub struct Buffer {
     text: TextBuffer,
     diff_base: Option<String>,
     git_diff: git::diff::BufferDiff,
     file: Option<Arc<dyn File>>,
-    saved_version: clock::Global,
-    saved_version_fingerprint: RopeFingerprint,
+    /// The mtime of the file when this buffer was last loaded from
+    /// or saved to disk.
     saved_mtime: SystemTime,
+    /// The version vector when this buffer was last loaded from
+    /// or saved to disk.
+    saved_version: clock::Global,
+    /// A hash of the current contents of the buffer's file.
+    file_fingerprint: RopeFingerprint,
     transaction_depth: usize,
     was_dirty_before_starting_transaction: Option<bool>,
     reload_task: Option<Task<Result<()>>>,
@@ -190,8 +200,8 @@ pub struct Completion {
     pub old_range: Range<Anchor>,
     pub new_text: String,
     pub label: CodeLabel,
-    pub documentation: Option<Documentation>,
     pub server_id: LanguageServerId,
+    pub documentation: Option<Documentation>,
     pub lsp_completion: lsp::CompletionItem,
 }
 
@@ -422,8 +432,7 @@ impl Buffer {
                 .ok_or_else(|| anyhow!("missing line_ending"))?,
         ));
         this.saved_version = proto::deserialize_version(&message.saved_version);
-        this.saved_version_fingerprint =
-            proto::deserialize_fingerprint(&message.saved_version_fingerprint)?;
+        this.file_fingerprint = proto::deserialize_fingerprint(&message.saved_version_fingerprint)?;
         this.saved_mtime = message
             .saved_mtime
             .ok_or_else(|| anyhow!("invalid saved_mtime"))?
@@ -439,7 +448,7 @@ impl Buffer {
             diff_base: self.diff_base.as_ref().map(|h| h.to_string()),
             line_ending: proto::serialize_line_ending(self.line_ending()) as i32,
             saved_version: proto::serialize_version(&self.saved_version),
-            saved_version_fingerprint: proto::serialize_fingerprint(self.saved_version_fingerprint),
+            saved_version_fingerprint: proto::serialize_fingerprint(self.file_fingerprint),
             saved_mtime: Some(self.saved_mtime.into()),
         }
     }
@@ -477,7 +486,7 @@ impl Buffer {
         ));
 
         let text_operations = self.text.operations().clone();
-        cx.background().spawn(async move {
+        cx.background_executor().spawn(async move {
             let since = since.unwrap_or_default();
             operations.extend(
                 text_operations
@@ -509,7 +518,7 @@ impl Buffer {
         Self {
             saved_mtime,
             saved_version: buffer.version(),
-            saved_version_fingerprint: buffer.as_rope().fingerprint(),
+            file_fingerprint: buffer.as_rope().fingerprint(),
             reload_task: None,
             transaction_depth: 0,
             was_dirty_before_starting_transaction: None,
@@ -576,7 +585,7 @@ impl Buffer {
     }
 
     pub fn saved_version_fingerprint(&self) -> RopeFingerprint {
-        self.saved_version_fingerprint
+        self.file_fingerprint
     }
 
     pub fn saved_mtime(&self) -> SystemTime {
@@ -604,7 +613,7 @@ impl Buffer {
         cx: &mut ModelContext<Self>,
     ) {
         self.saved_version = version;
-        self.saved_version_fingerprint = fingerprint;
+        self.file_fingerprint = fingerprint;
         self.saved_mtime = mtime;
         cx.emit(Event::Saved);
         cx.notify();
@@ -620,13 +629,14 @@ impl Buffer {
             let Some((new_mtime, new_text)) = this.update(&mut cx, |this, cx| {
                 let file = this.file.as_ref()?.as_local()?;
                 Some((file.mtime(), file.load(cx)))
-            }) else {
+            })?
+            else {
                 return Ok(());
             };
 
             let new_text = new_text.await?;
             let diff = this
-                .update(&mut cx, |this, cx| this.diff(new_text.clone(), cx))
+                .update(&mut cx, |this, cx| this.diff(new_text.clone(), cx))?
                 .await;
             this.update(&mut cx, |this, cx| {
                 if this.version() == diff.base_version {
@@ -652,8 +662,7 @@ impl Buffer {
                 }
 
                 this.reload_task.take();
-            });
-            Ok(())
+            })
         }));
         rx
     }
@@ -667,14 +676,14 @@ impl Buffer {
         cx: &mut ModelContext<Self>,
     ) {
         self.saved_version = version;
-        self.saved_version_fingerprint = fingerprint;
+        self.file_fingerprint = fingerprint;
         self.text.set_line_ending(line_ending);
         self.saved_mtime = mtime;
         if let Some(file) = self.file.as_ref().and_then(|f| f.as_local()) {
             file.buffer_reloaded(
                 self.remote_id(),
                 &self.saved_version,
-                self.saved_version_fingerprint,
+                self.file_fingerprint,
                 self.line_ending(),
                 self.saved_mtime,
                 cx,
@@ -736,20 +745,18 @@ impl Buffer {
         let snapshot = self.snapshot();
 
         let mut diff = self.git_diff.clone();
-        let diff = cx.background().spawn(async move {
+        let diff = cx.background_executor().spawn(async move {
             diff.update(&diff_base, &snapshot).await;
             diff
         });
 
-        let handle = cx.weak_handle();
-        Some(cx.spawn_weak(|_, mut cx| async move {
+        Some(cx.spawn(|this, mut cx| async move {
             let buffer_diff = diff.await;
-            if let Some(this) = handle.upgrade(&mut cx) {
-                this.update(&mut cx, |this, _| {
-                    this.git_diff = buffer_diff;
-                    this.git_diff_update_count += 1;
-                })
-            }
+            this.update(&mut cx, |this, _| {
+                this.git_diff = buffer_diff;
+                this.git_diff_update_count += 1;
+            })
+            .ok();
         }))
     }
 
@@ -847,7 +854,7 @@ impl Buffer {
         let mut syntax_snapshot = syntax_map.snapshot();
         drop(syntax_map);
 
-        let parse_task = cx.background().spawn({
+        let parse_task = cx.background_executor().spawn({
             let language = language.clone();
             let language_registry = language_registry.clone();
             async move {
@@ -857,7 +864,7 @@ impl Buffer {
         });
 
         match cx
-            .background()
+            .background_executor()
             .block_with_timeout(self.sync_parse_timeout, parse_task)
         {
             Ok(new_syntax_snapshot) => {
@@ -886,7 +893,8 @@ impl Buffer {
                         if parse_again {
                             this.reparse(cx);
                         }
-                    });
+                    })
+                    .ok();
                 })
                 .detach();
             }
@@ -919,9 +927,9 @@ impl Buffer {
 
     fn request_autoindent(&mut self, cx: &mut ModelContext<Self>) {
         if let Some(indent_sizes) = self.compute_autoindents() {
-            let indent_sizes = cx.background().spawn(indent_sizes);
+            let indent_sizes = cx.background_executor().spawn(indent_sizes);
             match cx
-                .background()
+                .background_executor()
                 .block_with_timeout(Duration::from_micros(500), indent_sizes)
             {
                 Ok(indent_sizes) => self.apply_autoindents(indent_sizes, cx),
@@ -930,7 +938,8 @@ impl Buffer {
                         let indent_sizes = indent_sizes.await;
                         this.update(&mut cx, |this, cx| {
                             this.apply_autoindents(indent_sizes, cx);
-                        });
+                        })
+                        .ok();
                     }));
                 }
             }
@@ -1169,36 +1178,72 @@ impl Buffer {
     pub fn diff(&self, mut new_text: String, cx: &AppContext) -> Task<Diff> {
         let old_text = self.as_rope().clone();
         let base_version = self.version();
-        cx.background().spawn(async move {
-            let old_text = old_text.to_string();
-            let line_ending = LineEnding::detect(&new_text);
-            LineEnding::normalize(&mut new_text);
-            let diff = TextDiff::from_chars(old_text.as_str(), new_text.as_str());
-            let mut edits = Vec::new();
-            let mut offset = 0;
-            let empty: Arc<str> = "".into();
-            for change in diff.iter_all_changes() {
-                let value = change.value();
-                let end_offset = offset + value.len();
-                match change.tag() {
-                    ChangeTag::Equal => {
-                        offset = end_offset;
-                    }
-                    ChangeTag::Delete => {
-                        edits.push((offset..end_offset, empty.clone()));
-                        offset = end_offset;
+        cx.background_executor()
+            .spawn_labeled(*BUFFER_DIFF_TASK, async move {
+                let old_text = old_text.to_string();
+                let line_ending = LineEnding::detect(&new_text);
+                LineEnding::normalize(&mut new_text);
+
+                let diff = TextDiff::from_chars(old_text.as_str(), new_text.as_str());
+                let empty: Arc<str> = "".into();
+
+                let mut edits = Vec::new();
+                let mut old_offset = 0;
+                let mut new_offset = 0;
+                let mut last_edit: Option<(Range<usize>, Range<usize>)> = None;
+                for change in diff.iter_all_changes().map(Some).chain([None]) {
+                    if let Some(change) = &change {
+                        let len = change.value().len();
+                        match change.tag() {
+                            ChangeTag::Equal => {
+                                old_offset += len;
+                                new_offset += len;
+                            }
+                            ChangeTag::Delete => {
+                                let old_end_offset = old_offset + len;
+                                if let Some((last_old_range, _)) = &mut last_edit {
+                                    last_old_range.end = old_end_offset;
+                                } else {
+                                    last_edit =
+                                        Some((old_offset..old_end_offset, new_offset..new_offset));
+                                }
+                                old_offset = old_end_offset;
+                            }
+                            ChangeTag::Insert => {
+                                let new_end_offset = new_offset + len;
+                                if let Some((_, last_new_range)) = &mut last_edit {
+                                    last_new_range.end = new_end_offset;
+                                } else {
+                                    last_edit =
+                                        Some((old_offset..old_offset, new_offset..new_end_offset));
+                                }
+                                new_offset = new_end_offset;
+                            }
+                        }
                     }
-                    ChangeTag::Insert => {
-                        edits.push((offset..offset, value.into()));
+
+                    if let Some((old_range, new_range)) = &last_edit {
+                        if old_offset > old_range.end
+                            || new_offset > new_range.end
+                            || change.is_none()
+                        {
+                            let text = if new_range.is_empty() {
+                                empty.clone()
+                            } else {
+                                new_text[new_range.clone()].into()
+                            };
+                            edits.push((old_range.clone(), text));
+                            last_edit.take();
+                        }
                     }
                 }
-            }
-            Diff {
-                base_version,
-                line_ending,
-                edits,
-            }
-        })
+
+                Diff {
+                    base_version,
+                    line_ending,
+                    edits,
+                }
+            })
     }
 
     /// Spawn a background task that searches the buffer for any whitespace
@@ -1207,7 +1252,7 @@ impl Buffer {
         let old_text = self.as_rope().clone();
         let line_ending = self.line_ending();
         let base_version = self.version();
-        cx.background().spawn(async move {
+        cx.background_executor().spawn(async move {
             let ranges = trailing_whitespace_ranges(&old_text);
             let empty = Arc::<str>::from("");
             Diff {
@@ -1282,12 +1327,12 @@ impl Buffer {
     }
 
     pub fn is_dirty(&self) -> bool {
-        self.saved_version_fingerprint != self.as_rope().fingerprint()
+        self.file_fingerprint != self.as_rope().fingerprint()
             || self.file.as_ref().map_or(false, |file| file.is_deleted())
     }
 
     pub fn has_conflict(&self) -> bool {
-        self.saved_version_fingerprint != self.as_rope().fingerprint()
+        self.file_fingerprint != self.as_rope().fingerprint()
             && self
                 .file
                 .as_ref()
@@ -1458,95 +1503,82 @@ impl Buffer {
             return None;
         }
 
-        // Non-generic part hoisted out to reduce LLVM IR size.
-        fn tail(
-            this: &mut Buffer,
-            edits: Vec<(Range<usize>, Arc<str>)>,
-            autoindent_mode: Option<AutoindentMode>,
-            cx: &mut ModelContext<Buffer>,
-        ) -> Option<clock::Lamport> {
-            this.start_transaction();
-            this.pending_autoindent.take();
-            let autoindent_request = autoindent_mode
-                .and_then(|mode| this.language.as_ref().map(|_| (this.snapshot(), mode)));
-
-            let edit_operation = this.text.edit(edits.iter().cloned());
-            let edit_id = edit_operation.timestamp();
-
-            if let Some((before_edit, mode)) = autoindent_request {
-                let mut delta = 0isize;
-                let entries = edits
-                    .into_iter()
-                    .enumerate()
-                    .zip(&edit_operation.as_edit().unwrap().new_text)
-                    .map(|((ix, (range, _)), new_text)| {
-                        let new_text_length = new_text.len();
-                        let old_start = range.start.to_point(&before_edit);
-                        let new_start = (delta + range.start as isize) as usize;
-                        delta +=
-                            new_text_length as isize - (range.end as isize - range.start as isize);
-
-                        let mut range_of_insertion_to_indent = 0..new_text_length;
-                        let mut first_line_is_new = false;
-                        let mut original_indent_column = None;
-
-                        // When inserting an entire line at the beginning of an existing line,
-                        // treat the insertion as new.
-                        if new_text.contains('\n')
-                            && old_start.column
-                                <= before_edit.indent_size_for_line(old_start.row).len
-                        {
-                            first_line_is_new = true;
-                        }
+        self.start_transaction();
+        self.pending_autoindent.take();
+        let autoindent_request = autoindent_mode
+            .and_then(|mode| self.language.as_ref().map(|_| (self.snapshot(), mode)));
 
-                        // When inserting text starting with a newline, avoid auto-indenting the
-                        // previous line.
-                        if new_text.starts_with('\n') {
-                            range_of_insertion_to_indent.start += 1;
-                            first_line_is_new = true;
-                        }
+        let edit_operation = self.text.edit(edits.iter().cloned());
+        let edit_id = edit_operation.timestamp();
 
-                        // Avoid auto-indenting after the insertion.
-                        if let AutoindentMode::Block {
-                            original_indent_columns,
-                        } = &mode
-                        {
-                            original_indent_column = Some(
-                                original_indent_columns.get(ix).copied().unwrap_or_else(|| {
-                                    indent_size_for_text(
-                                        new_text[range_of_insertion_to_indent.clone()].chars(),
-                                    )
-                                    .len
-                                }),
-                            );
-                            if new_text[range_of_insertion_to_indent.clone()].ends_with('\n') {
-                                range_of_insertion_to_indent.end -= 1;
-                            }
-                        }
+        if let Some((before_edit, mode)) = autoindent_request {
+            let mut delta = 0isize;
+            let entries = edits
+                .into_iter()
+                .enumerate()
+                .zip(&edit_operation.as_edit().unwrap().new_text)
+                .map(|((ix, (range, _)), new_text)| {
+                    let new_text_length = new_text.len();
+                    let old_start = range.start.to_point(&before_edit);
+                    let new_start = (delta + range.start as isize) as usize;
+                    delta += new_text_length as isize - (range.end as isize - range.start as isize);
+
+                    let mut range_of_insertion_to_indent = 0..new_text_length;
+                    let mut first_line_is_new = false;
+                    let mut original_indent_column = None;
+
+                    // When inserting an entire line at the beginning of an existing line,
+                    // treat the insertion as new.
+                    if new_text.contains('\n')
+                        && old_start.column <= before_edit.indent_size_for_line(old_start.row).len
+                    {
+                        first_line_is_new = true;
+                    }
+
+                    // When inserting text starting with a newline, avoid auto-indenting the
+                    // previous line.
+                    if new_text.starts_with('\n') {
+                        range_of_insertion_to_indent.start += 1;
+                        first_line_is_new = true;
+                    }
 
-                        AutoindentRequestEntry {
-                            first_line_is_new,
-                            original_indent_column,
-                            indent_size: before_edit.language_indent_size_at(range.start, cx),
-                            range: this
-                                .anchor_before(new_start + range_of_insertion_to_indent.start)
-                                ..this.anchor_after(new_start + range_of_insertion_to_indent.end),
+                    // Avoid auto-indenting after the insertion.
+                    if let AutoindentMode::Block {
+                        original_indent_columns,
+                    } = &mode
+                    {
+                        original_indent_column =
+                            Some(original_indent_columns.get(ix).copied().unwrap_or_else(|| {
+                                indent_size_for_text(
+                                    new_text[range_of_insertion_to_indent.clone()].chars(),
+                                )
+                                .len
+                            }));
+                        if new_text[range_of_insertion_to_indent.clone()].ends_with('\n') {
+                            range_of_insertion_to_indent.end -= 1;
                         }
-                    })
-                    .collect();
+                    }
 
-                this.autoindent_requests.push(Arc::new(AutoindentRequest {
-                    before_edit,
-                    entries,
-                    is_block_mode: matches!(mode, AutoindentMode::Block { .. }),
-                }));
-            }
+                    AutoindentRequestEntry {
+                        first_line_is_new,
+                        original_indent_column,
+                        indent_size: before_edit.language_indent_size_at(range.start, cx),
+                        range: self.anchor_before(new_start + range_of_insertion_to_indent.start)
+                            ..self.anchor_after(new_start + range_of_insertion_to_indent.end),
+                    }
+                })
+                .collect();
 
-            this.end_transaction(cx);
-            this.send_operation(Operation::Buffer(edit_operation), cx);
-            Some(edit_id)
+            self.autoindent_requests.push(Arc::new(AutoindentRequest {
+                before_edit,
+                entries,
+                is_block_mode: matches!(mode, AutoindentMode::Block { .. }),
+            }));
         }
-        tail(self, edits, autoindent_mode, cx)
+
+        self.end_transaction(cx);
+        self.send_operation(Operation::Buffer(edit_operation), cx);
+        Some(edit_id)
     }
 
     fn did_edit(
@@ -1879,9 +1911,7 @@ impl Buffer {
     }
 }
 
-impl Entity for Buffer {
-    type Event = Event;
-}
+impl EventEmitter<Event> for Buffer {}
 
 impl Deref for Buffer {
     type Target = TextBuffer;

crates/language/src/buffer_tests.rs 🔗

@@ -1,25 +1,25 @@
+use super::*;
 use crate::language_settings::{
     AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent,
 };
-
-use super::*;
+use crate::Buffer;
 use clock::ReplicaId;
 use collections::BTreeMap;
-use gpui::{AppContext, ModelHandle};
+use gpui::{AppContext, Model};
+use gpui::{Context, TestAppContext};
 use indoc::indoc;
 use proto::deserialize_operation;
 use rand::prelude::*;
 use regex::RegexBuilder;
 use settings::SettingsStore;
 use std::{
-    cell::RefCell,
     env,
     ops::Range,
-    rc::Rc,
     time::{Duration, Instant},
 };
 use text::network::Network;
 use text::LineEnding;
+use text::{Point, ToPoint};
 use unindent::Unindent as _;
 use util::{assert_set_eq, post_inc, test::marked_text_ranges, RandomCharIter};
 
@@ -42,8 +42,8 @@ fn init_logger() {
 fn test_line_endings(cx: &mut gpui::AppContext) {
     init_settings(cx, |_| {});
 
-    cx.add_model(|cx| {
-        let mut buffer = Buffer::new(0, cx.model_id() as u64, "one\r\ntwo\rthree")
+    cx.new_model(|cx| {
+        let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), "one\r\ntwo\rthree")
             .with_language(Arc::new(rust_lang()), cx);
         assert_eq!(buffer.text(), "one\ntwo\nthree");
         assert_eq!(buffer.line_ending(), LineEnding::Windows);
@@ -135,24 +135,24 @@ fn test_select_language() {
 #[gpui::test]
 fn test_edit_events(cx: &mut gpui::AppContext) {
     let mut now = Instant::now();
-    let buffer_1_events = Rc::new(RefCell::new(Vec::new()));
-    let buffer_2_events = Rc::new(RefCell::new(Vec::new()));
+    let buffer_1_events = Arc::new(Mutex::new(Vec::new()));
+    let buffer_2_events = Arc::new(Mutex::new(Vec::new()));
 
-    let buffer1 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "abcdef"));
-    let buffer2 = cx.add_model(|cx| Buffer::new(1, cx.model_id() as u64, "abcdef"));
-    let buffer1_ops = Rc::new(RefCell::new(Vec::new()));
+    let buffer1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abcdef"));
+    let buffer2 = cx.new_model(|cx| Buffer::new(1, cx.entity_id().as_u64(), "abcdef"));
+    let buffer1_ops = Arc::new(Mutex::new(Vec::new()));
     buffer1.update(cx, {
         let buffer1_ops = buffer1_ops.clone();
         |buffer, cx| {
             let buffer_1_events = buffer_1_events.clone();
             cx.subscribe(&buffer1, move |_, _, event, _| match event.clone() {
-                Event::Operation(op) => buffer1_ops.borrow_mut().push(op),
-                event => buffer_1_events.borrow_mut().push(event),
+                Event::Operation(op) => buffer1_ops.lock().push(op),
+                event => buffer_1_events.lock().push(event),
             })
             .detach();
             let buffer_2_events = buffer_2_events.clone();
             cx.subscribe(&buffer2, move |_, _, event, _| {
-                buffer_2_events.borrow_mut().push(event.clone())
+                buffer_2_events.lock().push(event.clone())
             })
             .detach();
 
@@ -179,12 +179,10 @@ fn test_edit_events(cx: &mut gpui::AppContext) {
     // Incorporating a set of remote ops emits a single edited event,
     // followed by a dirty changed event.
     buffer2.update(cx, |buffer, cx| {
-        buffer
-            .apply_ops(buffer1_ops.borrow_mut().drain(..), cx)
-            .unwrap();
+        buffer.apply_ops(buffer1_ops.lock().drain(..), cx).unwrap();
     });
     assert_eq!(
-        mem::take(&mut *buffer_1_events.borrow_mut()),
+        mem::take(&mut *buffer_1_events.lock()),
         vec![
             Event::Edited,
             Event::DirtyChanged,
@@ -193,7 +191,7 @@ fn test_edit_events(cx: &mut gpui::AppContext) {
         ]
     );
     assert_eq!(
-        mem::take(&mut *buffer_2_events.borrow_mut()),
+        mem::take(&mut *buffer_2_events.lock()),
         vec![Event::Edited, Event::DirtyChanged]
     );
 
@@ -205,28 +203,26 @@ fn test_edit_events(cx: &mut gpui::AppContext) {
     // Incorporating the remote ops again emits a single edited event,
     // followed by a dirty changed event.
     buffer2.update(cx, |buffer, cx| {
-        buffer
-            .apply_ops(buffer1_ops.borrow_mut().drain(..), cx)
-            .unwrap();
+        buffer.apply_ops(buffer1_ops.lock().drain(..), cx).unwrap();
     });
     assert_eq!(
-        mem::take(&mut *buffer_1_events.borrow_mut()),
+        mem::take(&mut *buffer_1_events.lock()),
         vec![Event::Edited, Event::DirtyChanged,]
     );
     assert_eq!(
-        mem::take(&mut *buffer_2_events.borrow_mut()),
+        mem::take(&mut *buffer_2_events.lock()),
         vec![Event::Edited, Event::DirtyChanged]
     );
 }
 
 #[gpui::test]
-async fn test_apply_diff(cx: &mut gpui::TestAppContext) {
+async fn test_apply_diff(cx: &mut TestAppContext) {
     let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n";
-    let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, text));
-    let anchor = buffer.read_with(cx, |buffer, _| buffer.anchor_before(Point::new(3, 3)));
+    let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text));
+    let anchor = buffer.update(cx, |buffer, _| buffer.anchor_before(Point::new(3, 3)));
 
     let text = "a\nccc\ndddd\nffffff\n";
-    let diff = buffer.read_with(cx, |b, cx| b.diff(text.into(), cx)).await;
+    let diff = buffer.update(cx, |b, cx| b.diff(text.into(), cx)).await;
     buffer.update(cx, |buffer, cx| {
         buffer.apply_diff(diff, cx).unwrap();
         assert_eq!(buffer.text(), text);
@@ -234,7 +230,7 @@ async fn test_apply_diff(cx: &mut gpui::TestAppContext) {
     });
 
     let text = "a\n1\n\nccc\ndd2dd\nffffff\n";
-    let diff = buffer.read_with(cx, |b, cx| b.diff(text.into(), cx)).await;
+    let diff = buffer.update(cx, |b, cx| b.diff(text.into(), cx)).await;
     buffer.update(cx, |buffer, cx| {
         buffer.apply_diff(diff, cx).unwrap();
         assert_eq!(buffer.text(), text);
@@ -254,15 +250,15 @@ async fn test_normalize_whitespace(cx: &mut gpui::TestAppContext) {
     ]
     .join("\n");
 
-    let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, text));
+    let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text));
 
     // Spawn a task to format the buffer's whitespace.
     // Pause so that the foratting task starts running.
-    let format = buffer.read_with(cx, |buffer, cx| buffer.remove_trailing_whitespace(cx));
+    let format = buffer.update(cx, |buffer, cx| buffer.remove_trailing_whitespace(cx));
     smol::future::yield_now().await;
 
     // Edit the buffer while the normalization task is running.
-    let version_before_edit = buffer.read_with(cx, |buffer, _| buffer.version());
+    let version_before_edit = buffer.update(cx, |buffer, _| buffer.version());
     buffer.update(cx, |buffer, cx| {
         buffer.edit(
             [
@@ -318,12 +314,13 @@ async fn test_normalize_whitespace(cx: &mut gpui::TestAppContext) {
 #[gpui::test]
 async fn test_reparse(cx: &mut gpui::TestAppContext) {
     let text = "fn a() {}";
-    let buffer = cx.add_model(|cx| {
-        Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx)
+    let buffer = cx.new_model(|cx| {
+        Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx)
     });
 
     // Wait for the initial text to parse
-    buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await;
+    cx.executor().run_until_parked();
+    assert!(!buffer.update(cx, |buffer, _| buffer.is_parsing()));
     assert_eq!(
         get_tree_sexp(&buffer, cx),
         concat!(
@@ -354,7 +351,8 @@ async fn test_reparse(cx: &mut gpui::TestAppContext) {
         assert_eq!(buf.text(), "fn a(b: C) { d; }");
         assert!(buf.is_parsing());
     });
-    buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await;
+    cx.executor().run_until_parked();
+    assert!(!buffer.update(cx, |buffer, _| buffer.is_parsing()));
     assert_eq!(
         get_tree_sexp(&buffer, cx),
         concat!(
@@ -386,7 +384,7 @@ async fn test_reparse(cx: &mut gpui::TestAppContext) {
         assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
         assert!(buf.is_parsing());
     });
-    buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await;
+    cx.executor().run_until_parked();
     assert_eq!(
         get_tree_sexp(&buffer, cx),
         concat!(
@@ -408,7 +406,8 @@ async fn test_reparse(cx: &mut gpui::TestAppContext) {
         assert_eq!(buf.text(), "fn a() {}");
         assert!(buf.is_parsing());
     });
-    buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await;
+
+    cx.executor().run_until_parked();
     assert_eq!(
         get_tree_sexp(&buffer, cx),
         concat!(
@@ -426,7 +425,7 @@ async fn test_reparse(cx: &mut gpui::TestAppContext) {
         assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
         assert!(buf.is_parsing());
     });
-    buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await;
+    cx.executor().run_until_parked();
     assert_eq!(
         get_tree_sexp(&buffer, cx),
         concat!(
@@ -443,15 +442,15 @@ async fn test_reparse(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_resetting_language(cx: &mut gpui::TestAppContext) {
-    let buffer = cx.add_model(|cx| {
+    let buffer = cx.new_model(|cx| {
         let mut buffer =
-            Buffer::new(0, cx.model_id() as u64, "{}").with_language(Arc::new(rust_lang()), cx);
+            Buffer::new(0, cx.entity_id().as_u64(), "{}").with_language(Arc::new(rust_lang()), cx);
         buffer.set_sync_parse_timeout(Duration::ZERO);
         buffer
     });
 
     // Wait for the initial text to parse
-    buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await;
+    cx.executor().run_until_parked();
     assert_eq!(
         get_tree_sexp(&buffer, cx),
         "(source_file (expression_statement (block)))"
@@ -460,7 +459,7 @@ async fn test_resetting_language(cx: &mut gpui::TestAppContext) {
     buffer.update(cx, |buffer, cx| {
         buffer.set_language(Some(Arc::new(json_lang())), cx)
     });
-    buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await;
+    cx.executor().run_until_parked();
     assert_eq!(get_tree_sexp(&buffer, cx), "(document (object))");
 }
 
@@ -493,11 +492,11 @@ async fn test_outline(cx: &mut gpui::TestAppContext) {
     "#
     .unindent();
 
-    let buffer = cx.add_model(|cx| {
-        Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx)
+    let buffer = cx.new_model(|cx| {
+        Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx)
     });
     let outline = buffer
-        .read_with(cx, |buffer, _| buffer.snapshot().outline(None))
+        .update(cx, |buffer, _| buffer.snapshot().outline(None))
         .unwrap();
 
     assert_eq!(
@@ -560,7 +559,7 @@ async fn test_outline(cx: &mut gpui::TestAppContext) {
         cx: &'a gpui::TestAppContext,
     ) -> Vec<(&'a str, Vec<usize>)> {
         let matches = cx
-            .read(|cx| outline.search(query, cx.background().clone()))
+            .update(|cx| outline.search(query, cx.background_executor().clone()))
             .await;
         matches
             .into_iter()
@@ -579,11 +578,11 @@ async fn test_outline_nodes_with_newlines(cx: &mut gpui::TestAppContext) {
     "#
     .unindent();
 
-    let buffer = cx.add_model(|cx| {
-        Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx)
+    let buffer = cx.new_model(|cx| {
+        Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx)
     });
     let outline = buffer
-        .read_with(cx, |buffer, _| buffer.snapshot().outline(None))
+        .update(cx, |buffer, _| buffer.snapshot().outline(None))
         .unwrap();
 
     assert_eq!(
@@ -617,10 +616,10 @@ async fn test_outline_with_extra_context(cx: &mut gpui::TestAppContext) {
     "#
     .unindent();
 
-    let buffer = cx.add_model(|cx| {
-        Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(language), cx)
+    let buffer = cx.new_model(|cx| {
+        Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(language), cx)
     });
-    let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
+    let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
 
     // extra context nodes are included in the outline.
     let outline = snapshot.outline(None).unwrap();
@@ -661,10 +660,10 @@ async fn test_symbols_containing(cx: &mut gpui::TestAppContext) {
     "#
     .unindent();
 
-    let buffer = cx.add_model(|cx| {
-        Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx)
+    let buffer = cx.new_model(|cx| {
+        Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx)
     });
-    let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
+    let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
 
     // point is at the start of an item
     assert_eq!(
@@ -882,10 +881,10 @@ fn test_enclosing_bracket_ranges_where_brackets_are_not_outermost_children(cx: &
 
 #[gpui::test]
 fn test_range_for_syntax_ancestor(cx: &mut AppContext) {
-    cx.add_model(|cx| {
+    cx.new_model(|cx| {
         let text = "fn a() { b(|c| {}) }";
         let buffer =
-            Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx);
+            Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
         let snapshot = buffer.snapshot();
 
         assert_eq!(
@@ -923,10 +922,10 @@ fn test_range_for_syntax_ancestor(cx: &mut AppContext) {
 fn test_autoindent_with_soft_tabs(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.add_model(|cx| {
+    cx.new_model(|cx| {
         let text = "fn a() {}";
         let mut buffer =
-            Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx);
+            Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
 
         buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
         assert_eq!(buffer.text(), "fn a() {\n    \n}");
@@ -966,10 +965,10 @@ fn test_autoindent_with_hard_tabs(cx: &mut AppContext) {
         settings.defaults.hard_tabs = Some(true);
     });
 
-    cx.add_model(|cx| {
+    cx.new_model(|cx| {
         let text = "fn a() {}";
         let mut buffer =
-            Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx);
+            Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
 
         buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
         assert_eq!(buffer.text(), "fn a() {\n\t\n}");
@@ -1007,10 +1006,11 @@ fn test_autoindent_with_hard_tabs(cx: &mut AppContext) {
 fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.add_model(|cx| {
+    cx.new_model(|cx| {
+        let entity_id = cx.entity_id();
         let mut buffer = Buffer::new(
             0,
-            cx.model_id() as u64,
+            entity_id.as_u64(),
             "
             fn a() {
             c;
@@ -1080,10 +1080,12 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppC
         buffer
     });
 
-    cx.add_model(|cx| {
+    cx.new_model(|cx| {
+        eprintln!("second buffer: {:?}", cx.entity_id());
+
         let mut buffer = Buffer::new(
             0,
-            cx.model_id() as u64,
+            cx.entity_id().as_u64(),
             "
             fn a() {
                 b();
@@ -1137,16 +1139,18 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppC
         );
         buffer
     });
+
+    eprintln!("DONE");
 }
 
 #[gpui::test]
 fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.add_model(|cx| {
+    cx.new_model(|cx| {
         let mut buffer = Buffer::new(
             0,
-            cx.model_id() as u64,
+            cx.entity_id().as_u64(),
             "
             fn a() {
                 i
@@ -1205,10 +1209,10 @@ fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut Ap
 fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.add_model(|cx| {
+    cx.new_model(|cx| {
         let mut buffer = Buffer::new(
             0,
-            cx.model_id() as u64,
+            cx.entity_id().as_u64(),
             "
             fn a() {}
             "
@@ -1262,10 +1266,10 @@ fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut AppContext) {
 fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.add_model(|cx| {
+    cx.new_model(|cx| {
         let text = "a\nb";
         let mut buffer =
-            Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx);
+            Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
         buffer.edit(
             [(0..1, "\n"), (2..3, "\n")],
             Some(AutoindentMode::EachLine),
@@ -1280,7 +1284,7 @@ fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut AppContext) {
 fn test_autoindent_multi_line_insertion(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.add_model(|cx| {
+    cx.new_model(|cx| {
         let text = "
             const a: usize = 1;
             fn b() {
@@ -1292,7 +1296,7 @@ fn test_autoindent_multi_line_insertion(cx: &mut AppContext) {
         .unindent();
 
         let mut buffer =
-            Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx);
+            Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
         buffer.edit(
             [(Point::new(3, 0)..Point::new(3, 0), "e(\n    f()\n);\n")],
             Some(AutoindentMode::EachLine),
@@ -1322,7 +1326,7 @@ fn test_autoindent_multi_line_insertion(cx: &mut AppContext) {
 fn test_autoindent_block_mode(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.add_model(|cx| {
+    cx.new_model(|cx| {
         let text = r#"
             fn a() {
                 b();
@@ -1330,7 +1334,7 @@ fn test_autoindent_block_mode(cx: &mut AppContext) {
         "#
         .unindent();
         let mut buffer =
-            Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx);
+            Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
 
         // When this text was copied, both of the quotation marks were at the same
         // indent level, but the indentation of the first line was not included in
@@ -1406,7 +1410,7 @@ fn test_autoindent_block_mode(cx: &mut AppContext) {
 fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.add_model(|cx| {
+    cx.new_model(|cx| {
         let text = r#"
             fn a() {
                 if b() {
@@ -1416,7 +1420,7 @@ fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContex
         "#
         .unindent();
         let mut buffer =
-            Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx);
+            Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
 
         // The original indent columns are not known, so this text is
         // auto-indented in a block as if the first line was copied in
@@ -1486,7 +1490,7 @@ fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContex
 fn test_autoindent_language_without_indents_query(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.add_model(|cx| {
+    cx.new_model(|cx| {
         let text = "
             * one
                 - a
@@ -1495,7 +1499,7 @@ fn test_autoindent_language_without_indents_query(cx: &mut AppContext) {
         "
         .unindent();
 
-        let mut buffer = Buffer::new(0, cx.model_id() as u64, text).with_language(
+        let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), text).with_language(
             Arc::new(Language::new(
                 LanguageConfig {
                     name: "Markdown".into(),
@@ -1555,7 +1559,7 @@ fn test_autoindent_with_injected_languages(cx: &mut AppContext) {
     language_registry.add(html_language.clone());
     language_registry.add(javascript_language.clone());
 
-    cx.add_model(|cx| {
+    cx.new_model(|cx| {
         let (text, ranges) = marked_text_ranges(
             &"
                 <div>ˇ
@@ -1571,7 +1575,7 @@ fn test_autoindent_with_injected_languages(cx: &mut AppContext) {
             false,
         );
 
-        let mut buffer = Buffer::new(0, cx.model_id() as u64, text);
+        let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), text);
         buffer.set_language_registry(language_registry);
         buffer.set_language(Some(html_language), cx);
         buffer.edit(
@@ -1606,9 +1610,9 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) {
         settings.defaults.tab_size = Some(2.try_into().unwrap());
     });
 
-    cx.add_model(|cx| {
+    cx.new_model(|cx| {
         let mut buffer =
-            Buffer::new(0, cx.model_id() as u64, "").with_language(Arc::new(ruby_lang()), cx);
+            Buffer::new(0, cx.entity_id().as_u64(), "").with_language(Arc::new(ruby_lang()), cx);
 
         let text = r#"
             class C
@@ -1649,7 +1653,7 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) {
 fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.add_model(|cx| {
+    cx.new_model(|cx| {
         let language = Language::new(
             LanguageConfig {
                 name: "JavaScript".into(),
@@ -1710,7 +1714,7 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
         .unindent();
 
         let buffer =
-            Buffer::new(0, cx.model_id() as u64, &text).with_language(Arc::new(language), cx);
+            Buffer::new(0, cx.entity_id().as_u64(), &text).with_language(Arc::new(language), cx);
         let snapshot = buffer.snapshot();
 
         let config = snapshot.language_scope_at(0).unwrap();
@@ -1782,7 +1786,7 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
 fn test_language_scope_at_with_rust(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.add_model(|cx| {
+    cx.new_model(|cx| {
         let language = Language::new(
             LanguageConfig {
                 name: "Rust".into(),
@@ -1822,7 +1826,7 @@ fn test_language_scope_at_with_rust(cx: &mut AppContext) {
         "#
         .unindent();
 
-        let buffer = Buffer::new(0, cx.model_id() as u64, text.clone())
+        let buffer = Buffer::new(0, cx.entity_id().as_u64(), text.clone())
             .with_language(Arc::new(language), cx);
         let snapshot = buffer.snapshot();
 
@@ -1850,7 +1854,7 @@ fn test_language_scope_at_with_rust(cx: &mut AppContext) {
 fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.add_model(|cx| {
+    cx.new_model(|cx| {
         let text = r#"
             <ol>
             <% people.each do |person| %>
@@ -1867,7 +1871,7 @@ fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) {
         language_registry.add(Arc::new(html_lang()));
         language_registry.add(Arc::new(erb_lang()));
 
-        let mut buffer = Buffer::new(0, cx.model_id() as u64, text);
+        let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), text);
         buffer.set_language_registry(language_registry.clone());
         buffer.set_language(
             language_registry
@@ -1898,8 +1902,8 @@ fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) {
 fn test_serialization(cx: &mut gpui::AppContext) {
     let mut now = Instant::now();
 
-    let buffer1 = cx.add_model(|cx| {
-        let mut buffer = Buffer::new(0, cx.model_id() as u64, "abc");
+    let buffer1 = cx.new_model(|cx| {
+        let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), "abc");
         buffer.edit([(3..3, "D")], None, cx);
 
         now += Duration::from_secs(1);
@@ -1919,9 +1923,9 @@ fn test_serialization(cx: &mut gpui::AppContext) {
 
     let state = buffer1.read(cx).to_proto();
     let ops = cx
-        .background()
+        .background_executor()
         .block(buffer1.read(cx).serialize_ops(None, cx));
-    let buffer2 = cx.add_model(|cx| {
+    let buffer2 = cx.new_model(|cx| {
         let mut buffer = Buffer::from_proto(1, state, None).unwrap();
         buffer
             .apply_ops(
@@ -1953,14 +1957,15 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
         .collect::<String>();
     let mut replica_ids = Vec::new();
     let mut buffers = Vec::new();
-    let network = Rc::new(RefCell::new(Network::new(rng.clone())));
-    let base_buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, base_text.as_str()));
+    let network = Arc::new(Mutex::new(Network::new(rng.clone())));
+    let base_buffer =
+        cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), base_text.as_str()));
 
     for i in 0..rng.gen_range(min_peers..=max_peers) {
-        let buffer = cx.add_model(|cx| {
+        let buffer = cx.new_model(|cx| {
             let state = base_buffer.read(cx).to_proto();
             let ops = cx
-                .background()
+                .background_executor()
                 .block(base_buffer.read(cx).serialize_ops(None, cx));
             let mut buffer = Buffer::from_proto(i as ReplicaId, state, None).unwrap();
             buffer
@@ -1975,16 +1980,17 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
             cx.subscribe(&cx.handle(), move |buffer, _, event, _| {
                 if let Event::Operation(op) = event {
                     network
-                        .borrow_mut()
+                        .lock()
                         .broadcast(buffer.replica_id(), vec![proto::serialize_operation(op)]);
                 }
             })
             .detach();
             buffer
         });
+
         buffers.push(buffer);
         replica_ids.push(i as ReplicaId);
-        network.borrow_mut().add_peer(i as ReplicaId);
+        network.lock().add_peer(i as ReplicaId);
         log::info!("Adding initial peer with replica id {}", i);
     }
 
@@ -2065,7 +2071,7 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
             50..=59 if replica_ids.len() < max_peers => {
                 let old_buffer_state = buffer.read(cx).to_proto();
                 let old_buffer_ops = cx
-                    .background()
+                    .background_executor()
                     .block(buffer.read(cx).serialize_ops(None, cx));
                 let new_replica_id = (0..=replica_ids.len() as ReplicaId)
                     .filter(|replica_id| *replica_id != buffer.read(cx).replica_id())
@@ -2076,7 +2082,7 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
                     new_replica_id,
                     replica_id
                 );
-                new_buffer = Some(cx.add_model(|cx| {
+                new_buffer = Some(cx.new_model(|cx| {
                     let mut new_buffer =
                         Buffer::from_proto(new_replica_id, old_buffer_state, None).unwrap();
                     new_buffer
@@ -2096,7 +2102,7 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
                     let network = network.clone();
                     cx.subscribe(&cx.handle(), move |buffer, _, event, _| {
                         if let Event::Operation(op) = event {
-                            network.borrow_mut().broadcast(
+                            network.lock().broadcast(
                                 buffer.replica_id(),
                                 vec![proto::serialize_operation(op)],
                             );
@@ -2105,15 +2111,15 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
                     .detach();
                     new_buffer
                 }));
-                network.borrow_mut().replicate(replica_id, new_replica_id);
+                network.lock().replicate(replica_id, new_replica_id);
 
                 if new_replica_id as usize == replica_ids.len() {
                     replica_ids.push(new_replica_id);
                 } else {
                     let new_buffer = new_buffer.take().unwrap();
-                    while network.borrow().has_unreceived(new_replica_id) {
+                    while network.lock().has_unreceived(new_replica_id) {
                         let ops = network
-                            .borrow_mut()
+                            .lock()
                             .receive(new_replica_id)
                             .into_iter()
                             .map(|op| proto::deserialize_operation(op).unwrap());
@@ -2140,9 +2146,9 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
                 });
                 mutation_count -= 1;
             }
-            _ if network.borrow().has_unreceived(replica_id) => {
+            _ if network.lock().has_unreceived(replica_id) => {
                 let ops = network
-                    .borrow_mut()
+                    .lock()
                     .receive(replica_id)
                     .into_iter()
                     .map(|op| proto::deserialize_operation(op).unwrap());
@@ -2167,7 +2173,7 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
             buffer.read(cx).check_invariants();
         }
 
-        if mutation_count == 0 && network.borrow().is_idle() {
+        if mutation_count == 0 && network.lock().is_idle() {
             break;
         }
     }
@@ -2438,8 +2444,8 @@ fn javascript_lang() -> Language {
     .unwrap()
 }
 
-fn get_tree_sexp(buffer: &ModelHandle<Buffer>, cx: &gpui::TestAppContext) -> String {
-    buffer.read_with(cx, |buffer, _| {
+fn get_tree_sexp(buffer: &Model<Buffer>, cx: &mut gpui::TestAppContext) -> String {
+    buffer.update(cx, |buffer, _| {
         let snapshot = buffer.snapshot();
         let layers = snapshot.syntax.layers(buffer.as_text_snapshot());
         layers[0].node().to_sexp()
@@ -2454,8 +2460,8 @@ fn assert_bracket_pairs(
     cx: &mut AppContext,
 ) {
     let (expected_text, selection_ranges) = marked_text_ranges(selection_text, false);
-    let buffer = cx.add_model(|cx| {
-        Buffer::new(0, cx.model_id() as u64, expected_text.clone())
+    let buffer = cx.new_model(|cx| {
+        Buffer::new(0, cx.entity_id().as_u64(), expected_text.clone())
             .with_language(Arc::new(language), cx)
     });
     let buffer = buffer.update(cx, |buffer, _cx| buffer.snapshot());
@@ -2478,9 +2484,10 @@ fn assert_bracket_pairs(
 }
 
 fn init_settings(cx: &mut AppContext, f: fn(&mut AllLanguageSettingsContent)) {
-    cx.set_global(SettingsStore::test(cx));
+    let settings_store = SettingsStore::test(cx);
+    cx.set_global(settings_store);
     crate::init(cx);
-    cx.update_global::<SettingsStore, _, _>(|settings, cx| {
+    cx.update_global::<SettingsStore, _>(|settings, cx| {
         settings.update_user_settings::<AllLanguageSettings>(cx, f);
     });
 }

crates/language/src/highlight_map.rs 🔗

@@ -1,4 +1,4 @@
-use gpui::fonts::HighlightStyle;
+use gpui::HighlightStyle;
 use std::sync::Arc;
 use theme::SyntaxTheme;
 
@@ -79,23 +79,23 @@ impl Default for HighlightId {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use gpui::color::Color;
+    use gpui::rgba;
 
     #[test]
     fn test_highlight_map() {
-        let theme = SyntaxTheme::new(
-            [
-                ("function", Color::from_u32(0x100000ff)),
-                ("function.method", Color::from_u32(0x200000ff)),
-                ("function.async", Color::from_u32(0x300000ff)),
-                ("variable.builtin.self.rust", Color::from_u32(0x400000ff)),
-                ("variable.builtin", Color::from_u32(0x500000ff)),
-                ("variable", Color::from_u32(0x600000ff)),
+        let theme = SyntaxTheme {
+            highlights: [
+                ("function", rgba(0x100000ff)),
+                ("function.method", rgba(0x200000ff)),
+                ("function.async", rgba(0x300000ff)),
+                ("variable.builtin.self.rust", rgba(0x400000ff)),
+                ("variable.builtin", rgba(0x500000ff)),
+                ("variable", rgba(0x600000ff)),
             ]
             .iter()
             .map(|(name, color)| (name.to_string(), (*color).into()))
             .collect(),
-        );
+        };
 
         let capture_names = &[
             "function.special",

crates/language/src/language.rs 🔗

@@ -2,13 +2,13 @@ mod buffer;
 mod diagnostic_set;
 mod highlight_map;
 pub mod language_settings;
-pub mod markdown;
 mod outline;
 pub mod proto;
 mod syntax_map;
 
 #[cfg(test)]
 mod buffer_tests;
+pub mod markdown;
 
 use anyhow::{anyhow, Context, Result};
 use async_trait::async_trait;
@@ -18,7 +18,7 @@ use futures::{
     future::{BoxFuture, Shared},
     FutureExt, TryFutureExt as _,
 };
-use gpui::{executor::Background, AppContext, AsyncAppContext, Task};
+use gpui::{AppContext, AsyncAppContext, BackgroundExecutor, Task};
 pub use highlight_map::HighlightMap;
 use lazy_static::lazy_static;
 use lsp::{CodeActionKind, LanguageServerBinary};
@@ -44,7 +44,7 @@ use std::{
 };
 use syntax_map::SyntaxSnapshot;
 use theme::{SyntaxTheme, Theme};
-use tree_sitter::{self, Query};
+use tree_sitter::{self, wasmtime, Query, WasmStore};
 use unicase::UniCase;
 use util::{http::HttpClient, paths::PathExt};
 use util::{post_inc, ResultExt, TryFutureExt as _, UnwrapFuture};
@@ -84,10 +84,15 @@ impl LspBinaryStatusSender {
 }
 
 thread_local! {
-    static PARSER: RefCell<Parser> = RefCell::new(Parser::new());
+    static PARSER: RefCell<Parser> = {
+        let mut parser = Parser::new();
+        parser.set_wasm_store(WasmStore::new(WASM_ENGINE.clone()).unwrap()).unwrap();
+        RefCell::new(parser)
+    };
 }
 
 lazy_static! {
+    pub static ref WASM_ENGINE: wasmtime::Engine = wasmtime::Engine::default();
     pub static ref NEXT_GRAMMAR_ID: AtomicUsize = Default::default();
     pub static ref PLAIN_TEXT: Arc<Language> = Arc::new(Language::new(
         LanguageConfig {
@@ -111,6 +116,7 @@ pub struct LanguageServerName(pub Arc<str>);
 pub struct CachedLspAdapter {
     pub name: LanguageServerName,
     pub short_name: &'static str,
+    pub initialization_options: Option<Value>,
     pub disk_based_diagnostic_sources: Vec<String>,
     pub disk_based_diagnostics_progress_token: Option<String>,
     pub language_ids: HashMap<String, String>,
@@ -122,6 +128,7 @@ impl CachedLspAdapter {
     pub async fn new(adapter: Arc<dyn LspAdapter>) -> Arc<Self> {
         let name = adapter.name().await;
         let short_name = adapter.short_name();
+        let initialization_options = adapter.initialization_options().await;
         let disk_based_diagnostic_sources = adapter.disk_based_diagnostic_sources().await;
         let disk_based_diagnostics_progress_token =
             adapter.disk_based_diagnostics_progress_token().await;
@@ -130,6 +137,7 @@ impl CachedLspAdapter {
         Arc::new(CachedLspAdapter {
             name,
             short_name,
+            initialization_options,
             disk_based_diagnostic_sources,
             disk_based_diagnostics_progress_token,
             language_ids,
@@ -357,6 +365,7 @@ pub struct CodeLabel {
 #[derive(Clone, Deserialize)]
 pub struct LanguageConfig {
     pub name: Arc<str>,
+    pub grammar_name: Option<Arc<str>>,
     pub path_suffixes: Vec<String>,
     pub brackets: BracketPairConfig,
     #[serde(default, deserialize_with = "deserialize_regex")]
@@ -443,6 +452,7 @@ impl Default for LanguageConfig {
     fn default() -> Self {
         Self {
             name: "".into(),
+            grammar_name: None,
             path_suffixes: Default::default(),
             brackets: Default::default(),
             auto_indent_using_last_non_empty_line: auto_indent_using_last_non_empty_line_default(),
@@ -617,14 +627,25 @@ type AvailableLanguageId = usize;
 #[derive(Clone)]
 struct AvailableLanguage {
     id: AvailableLanguageId,
-    path: &'static str,
     config: LanguageConfig,
-    grammar: tree_sitter::Language,
+    grammar: AvailableGrammar,
     lsp_adapters: Vec<Arc<dyn LspAdapter>>,
-    get_queries: fn(&str) -> LanguageQueries,
     loaded: bool,
 }
 
+#[derive(Clone)]
+enum AvailableGrammar {
+    Native {
+        grammar: tree_sitter::Language,
+        asset_dir: &'static str,
+        get_queries: fn(&str) -> LanguageQueries,
+    },
+    Wasm {
+        grammar_name: Arc<str>,
+        path: Arc<Path>,
+    },
+}
+
 pub struct LanguageRegistry {
     state: RwLock<LanguageRegistryState>,
     language_server_download_dir: Option<Arc<Path>>,
@@ -633,7 +654,7 @@ pub struct LanguageRegistry {
     lsp_binary_paths: Mutex<
         HashMap<LanguageServerName, Shared<Task<Result<LanguageServerBinary, Arc<anyhow::Error>>>>>,
     >,
-    executor: Option<Arc<Background>>,
+    executor: Option<BackgroundExecutor>,
     lsp_binary_status_tx: LspBinaryStatusSender,
 }
 
@@ -682,7 +703,7 @@ impl LanguageRegistry {
         Self::new(Task::ready(()))
     }
 
-    pub fn set_executor(&mut self, executor: Arc<Background>) {
+    pub fn set_executor(&mut self, executor: BackgroundExecutor) {
         self.executor = Some(executor);
     }
 
@@ -696,7 +717,7 @@ impl LanguageRegistry {
 
     pub fn register(
         &self,
-        path: &'static str,
+        asset_dir: &'static str,
         config: LanguageConfig,
         grammar: tree_sitter::Language,
         lsp_adapters: Vec<Arc<dyn LspAdapter>>,
@@ -705,11 +726,24 @@ impl LanguageRegistry {
         let state = &mut *self.state.write();
         state.available_languages.push(AvailableLanguage {
             id: post_inc(&mut state.next_available_language_id),
-            path,
             config,
-            grammar,
+            grammar: AvailableGrammar::Native {
+                grammar,
+                get_queries,
+                asset_dir,
+            },
             lsp_adapters,
-            get_queries,
+            loaded: false,
+        });
+    }
+
+    pub fn register_wasm(&self, path: Arc<Path>, grammar_name: Arc<str>, config: LanguageConfig) {
+        let state = &mut *self.state.write();
+        state.available_languages.push(AvailableLanguage {
+            id: post_inc(&mut state.next_available_language_id),
+            config,
+            grammar: AvailableGrammar::Wasm { grammar_name, path },
+            lsp_adapters: Vec::new(),
             loaded: false,
         });
     }
@@ -749,7 +783,7 @@ impl LanguageRegistry {
         let mut state = self.state.write();
         state.theme = Some(theme.clone());
         for language in &state.languages {
-            language.set_theme(&theme.editor.syntax);
+            language.set_theme(&theme.syntax());
         }
     }
 
@@ -834,13 +868,43 @@ impl LanguageRegistry {
                         executor
                             .spawn(async move {
                                 let id = language.id;
-                                let queries = (language.get_queries)(&language.path);
-                                let language =
-                                    Language::new(language.config, Some(language.grammar))
+                                let name = language.config.name.clone();
+                                let language = async {
+                                    let (grammar, queries) = match language.grammar {
+                                        AvailableGrammar::Native {
+                                            grammar,
+                                            asset_dir,
+                                            get_queries,
+                                        } => (grammar, (get_queries)(asset_dir)),
+                                        AvailableGrammar::Wasm { grammar_name, path } => {
+                                            let mut wasm_path = path.join(grammar_name.as_ref());
+                                            wasm_path.set_extension("wasm");
+                                            let wasm_bytes = std::fs::read(&wasm_path)?;
+                                            let grammar = PARSER.with(|parser| {
+                                                let mut parser = parser.borrow_mut();
+                                                let mut store = parser.take_wasm_store().unwrap();
+                                                let grammar =
+                                                    store.load_language(&grammar_name, &wasm_bytes);
+                                                parser.set_wasm_store(store).unwrap();
+                                                grammar
+                                            })?;
+                                            let mut queries = LanguageQueries::default();
+                                            if let Ok(contents) = std::fs::read_to_string(
+                                                &path.join("highlights.scm"),
+                                            ) {
+                                                queries.highlights = Some(contents.into());
+                                            }
+                                            (grammar, queries)
+                                        }
+                                    };
+                                    Language::new(language.config, Some(grammar))
                                         .with_lsp_adapters(language.lsp_adapters)
-                                        .await;
-                                let name = language.name();
-                                match language.with_queries(queries) {
+                                        .await
+                                        .with_queries(queries)
+                                }
+                                .await;
+
+                                match language {
                                     Ok(language) => {
                                         let language = Arc::new(language);
                                         let mut state = this.state.write();
@@ -918,7 +982,7 @@ impl LanguageRegistry {
                 }
 
                 let servers_tx = servers_tx.clone();
-                cx.background()
+                cx.background_executor()
                     .spawn(async move {
                         if fake_server
                             .try_receive_notification::<lsp::notification::Initialized>()
@@ -955,18 +1019,22 @@ impl LanguageRegistry {
 
         let task = {
             let container_dir = container_dir.clone();
-            cx.spawn(|mut cx| async move {
+            cx.spawn(move |mut cx| async move {
                 login_shell_env_loaded.await;
 
-                let mut lock = this.lsp_binary_paths.lock();
-                let entry = lock
+                let entry = this
+                    .lsp_binary_paths
+                    .lock()
                     .entry(adapter.name.clone())
                     .or_insert_with(|| {
+                        let adapter = adapter.clone();
+                        let language = language.clone();
+                        let delegate = delegate.clone();
                         cx.spawn(|cx| {
                             get_binary(
-                                adapter.clone(),
-                                language.clone(),
-                                delegate.clone(),
+                                adapter,
+                                language,
+                                delegate,
                                 container_dir,
                                 lsp_binary_statuses,
                                 cx,
@@ -976,9 +1044,8 @@ impl LanguageRegistry {
                         .shared()
                     })
                     .clone();
-                drop(lock);
 
-                let binary = match entry.clone().await {
+                let binary = match entry.await {
                     Ok(binary) => binary,
                     Err(err) => anyhow::bail!("{err}"),
                 };
@@ -1047,7 +1114,7 @@ impl LanguageRegistryState {
 
     fn add(&mut self, language: Arc<Language>) {
         if let Some(theme) = self.theme.as_ref() {
-            language.set_theme(&theme.editor.syntax);
+            language.set_theme(&theme.syntax());
         }
         self.languages.push(language);
         self.version += 1;
@@ -1387,9 +1454,9 @@ impl Language {
         let query = Query::new(&self.grammar_mut().ts_language, source)?;
 
         let mut override_configs_by_id = HashMap::default();
-        for (ix, name) in query.capture_names().iter().copied().enumerate() {
+        for (ix, name) in query.capture_names().iter().enumerate() {
             if !name.starts_with('_') {
-                let value = self.config.overrides.remove(name).unwrap_or_default();
+                let value = self.config.overrides.remove(*name).unwrap_or_default();
                 for server_name in &value.opt_into_language_servers {
                     if !self
                         .config
@@ -1400,7 +1467,7 @@ impl Language {
                     }
                 }
 
-                override_configs_by_id.insert(ix as u32, (name.into(), value));
+                override_configs_by_id.insert(ix as u32, (name.to_string(), value));
             }
         }
 
@@ -1855,7 +1922,8 @@ mod tests {
     #[gpui::test(iterations = 10)]
     async fn test_first_line_pattern(cx: &mut TestAppContext) {
         let mut languages = LanguageRegistry::test();
-        languages.set_executor(cx.background());
+
+        languages.set_executor(cx.executor());
         let languages = Arc::new(languages);
         languages.register(
             "/javascript",
@@ -1892,7 +1960,7 @@ mod tests {
     #[gpui::test(iterations = 10)]
     async fn test_language_loading(cx: &mut TestAppContext) {
         let mut languages = LanguageRegistry::test();
-        languages.set_executor(cx.background());
+        languages.set_executor(cx.executor());
         let languages = Arc::new(languages);
         languages.register(
             "/JSON",

crates/language/src/language_settings.rs 🔗

@@ -8,10 +8,11 @@ use schemars::{
     JsonSchema,
 };
 use serde::{Deserialize, Serialize};
+use settings::Settings;
 use std::{num::NonZeroU32, path::Path, sync::Arc};
 
 pub fn init(cx: &mut AppContext) {
-    settings::register::<AllLanguageSettings>(cx);
+    AllLanguageSettings::register(cx);
 }
 
 pub fn language_settings<'a>(
@@ -28,7 +29,7 @@ pub fn all_language_settings<'a>(
     cx: &'a AppContext,
 ) -> &'a AllLanguageSettings {
     let location = file.map(|f| (f.worktree_id(), f.path().as_ref()));
-    settings::get_local(location, cx)
+    AllLanguageSettings::get(location, cx)
 }
 
 #[derive(Debug, Clone)]
@@ -254,7 +255,7 @@ impl InlayHintKind {
     }
 }
 
-impl settings::Setting for AllLanguageSettings {
+impl settings::Settings for AllLanguageSettings {
     const KEY: Option<&'static str> = None;
 
     type FileContent = AllLanguageSettingsContent;
@@ -262,7 +263,7 @@ impl settings::Setting for AllLanguageSettings {
     fn load(
         default_value: &Self::FileContent,
         user_settings: &[&Self::FileContent],
-        _: &AppContext,
+        _: &mut AppContext,
     ) -> Result<Self> {
         // A default is provided for all settings.
         let mut defaults: LanguageSettings =

crates/language/src/markdown.rs 🔗

@@ -2,7 +2,7 @@ use std::sync::Arc;
 use std::{ops::Range, path::PathBuf};
 
 use crate::{HighlightId, Language, LanguageRegistry};
-use gpui::fonts::{self, HighlightStyle, Weight};
+use gpui::{px, FontStyle, FontWeight, HighlightStyle, UnderlineStyle};
 use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};
 
 #[derive(Debug, Clone)]
@@ -26,18 +26,18 @@ impl MarkdownHighlight {
                 let mut highlight = HighlightStyle::default();
 
                 if style.italic {
-                    highlight.italic = Some(true);
+                    highlight.font_style = Some(FontStyle::Italic);
                 }
 
                 if style.underline {
-                    highlight.underline = Some(fonts::Underline {
-                        thickness: 1.0.into(),
+                    highlight.underline = Some(UnderlineStyle {
+                        thickness: px(1.),
                         ..Default::default()
                     });
                 }
 
-                if style.weight != fonts::Weight::default() {
-                    highlight.weight = Some(style.weight);
+                if style.weight != FontWeight::default() {
+                    highlight.font_weight = Some(style.weight);
                 }
 
                 Some(highlight)
@@ -52,7 +52,7 @@ impl MarkdownHighlight {
 pub struct MarkdownHighlightStyle {
     pub italic: bool,
     pub underline: bool,
-    pub weight: Weight,
+    pub weight: FontWeight,
 }
 
 #[derive(Debug, Clone)]
@@ -138,7 +138,7 @@ pub async fn parse_markdown_block(
                     let mut style = MarkdownHighlightStyle::default();
 
                     if bold_depth > 0 {
-                        style.weight = Weight::BOLD;
+                        style.weight = FontWeight::BOLD;
                     }
 
                     if italic_depth > 0 {

crates/language/src/outline.rs 🔗

@@ -1,6 +1,6 @@
 use fuzzy::{StringMatch, StringMatchCandidate};
-use gpui::{executor::Background, fonts::HighlightStyle};
-use std::{ops::Range, sync::Arc};
+use gpui::{BackgroundExecutor, HighlightStyle};
+use std::ops::Range;
 
 #[derive(Debug)]
 pub struct Outline<T> {
@@ -57,7 +57,7 @@ impl<T> Outline<T> {
         }
     }
 
-    pub async fn search(&self, query: &str, executor: Arc<Background>) -> Vec<StringMatch> {
+    pub async fn search(&self, query: &str, executor: BackgroundExecutor) -> Vec<StringMatch> {
         let query = query.trim_start();
         let is_path_query = query.contains(' ');
         let smart_case = query.chars().any(|c| c.is_uppercase());
@@ -81,6 +81,7 @@ impl<T> Outline<T> {
         let mut prev_item_ix = 0;
         for mut string_match in matches {
             let outline_match = &self.items[string_match.candidate_id];
+            string_match.string = outline_match.text.clone();
 
             if is_path_query {
                 let prefix_len = self.path_candidate_prefixes[string_match.candidate_id];

crates/language/src/syntax_map.rs 🔗

@@ -7,7 +7,6 @@ use futures::FutureExt;
 use parking_lot::Mutex;
 use std::{
     borrow::Cow,
-    cell::RefCell,
     cmp::{self, Ordering, Reverse},
     collections::BinaryHeap,
     fmt, iter,
@@ -16,13 +15,9 @@ use std::{
 };
 use sum_tree::{Bias, SeekTarget, SumTree};
 use text::{Anchor, BufferSnapshot, OffsetRangeExt, Point, Rope, ToOffset, ToPoint};
-use tree_sitter::{
-    Node, Parser, Query, QueryCapture, QueryCaptures, QueryCursor, QueryMatches, Tree,
-};
+use tree_sitter::{Node, Query, QueryCapture, QueryCaptures, QueryCursor, QueryMatches, Tree};
 
-thread_local! {
-    static PARSER: RefCell<Parser> = RefCell::new(Parser::new());
-}
+use super::PARSER;
 
 static QUERY_CURSORS: Mutex<Vec<QueryCursor>> = Mutex::new(vec![]);
 

crates/language2/Cargo.toml 🔗

@@ -1,86 +0,0 @@
-[package]
-name = "language2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/language2.rs"
-doctest = false
-
-[features]
-test-support = [
-    "rand",
-    "client/test-support",
-    "collections/test-support",
-    "lsp/test-support",
-    "text/test-support",
-    "tree-sitter-rust",
-    "tree-sitter-typescript",
-    "settings/test-support",
-    "util/test-support",
-]
-
-[dependencies]
-clock = { path = "../clock" }
-collections = { path = "../collections" }
-fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
-git = { package = "git3", path = "../git3" }
-gpui = { package = "gpui2", path = "../gpui2" }
-lsp = { package = "lsp2", path = "../lsp2" }
-rpc = { package = "rpc2", path = "../rpc2" }
-settings = { package = "settings2", path = "../settings2" }
-sum_tree = { path = "../sum_tree" }
-text = { package = "text2", path = "../text2" }
-theme = { package = "theme2", path = "../theme2" }
-util = { path = "../util" }
-
-anyhow.workspace = true
-async-broadcast = "0.4"
-async-trait.workspace = true
-futures.workspace = true
-globset.workspace = true
-lazy_static.workspace = true
-log.workspace = true
-parking_lot.workspace = true
-postage.workspace = true
-regex.workspace = true
-schemars.workspace = true
-serde.workspace = true
-serde_derive.workspace = true
-serde_json.workspace = true
-similar = "1.3"
-smallvec.workspace = true
-smol.workspace = true
-tree-sitter.workspace = true
-unicase = "2.6"
-
-rand = { workspace = true, optional = true }
-tree-sitter-rust = { workspace = true, optional = true }
-tree-sitter-typescript = { workspace = true, optional = true }
-pulldown-cmark = { version = "0.9.2", default-features = false }
-
-[dev-dependencies]
-client = { package = "client2", path = "../client2", features = ["test-support"] }
-collections = { path = "../collections", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
-text = { package = "text2", path = "../text2", features = ["test-support"] }
-settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
-util = { path = "../util", features = ["test-support"] }
-ctor.workspace = true
-env_logger.workspace = true
-indoc.workspace = true
-rand.workspace = true
-unindent.workspace = true
-
-tree-sitter-embedded-template.workspace = true
-tree-sitter-html.workspace = true
-tree-sitter-json.workspace = true
-tree-sitter-markdown.workspace = true
-tree-sitter-rust.workspace = true
-tree-sitter-python.workspace = true
-tree-sitter-typescript.workspace = true
-tree-sitter-ruby.workspace = true
-tree-sitter-elixir.workspace = true
-tree-sitter-heex.workspace = true

crates/language2/build.rs 🔗

@@ -1,5 +0,0 @@
-fn main() {
-    if let Ok(bundled) = std::env::var("ZED_BUNDLE") {
-        println!("cargo:rustc-env=ZED_BUNDLE={}", bundled);
-    }
-}

crates/language2/src/buffer.rs 🔗

@@ -1,3193 +0,0 @@
-pub use crate::{
-    diagnostic_set::DiagnosticSet,
-    highlight_map::{HighlightId, HighlightMap},
-    markdown::ParsedMarkdown,
-    proto, Grammar, Language, LanguageRegistry,
-};
-use crate::{
-    diagnostic_set::{DiagnosticEntry, DiagnosticGroup},
-    language_settings::{language_settings, LanguageSettings},
-    markdown::parse_markdown,
-    outline::OutlineItem,
-    syntax_map::{
-        SyntaxLayerInfo, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches,
-        SyntaxSnapshot, ToTreeSitterPoint,
-    },
-    CodeLabel, LanguageScope, Outline,
-};
-use anyhow::{anyhow, Result};
-pub use clock::ReplicaId;
-use futures::channel::oneshot;
-use gpui::{AppContext, EventEmitter, HighlightStyle, ModelContext, Task, TaskLabel};
-use lazy_static::lazy_static;
-use lsp::LanguageServerId;
-use parking_lot::Mutex;
-use similar::{ChangeTag, TextDiff};
-use smallvec::SmallVec;
-use smol::future::yield_now;
-use std::{
-    any::Any,
-    cmp::{self, Ordering},
-    collections::BTreeMap,
-    ffi::OsStr,
-    future::Future,
-    iter::{self, Iterator, Peekable},
-    mem,
-    ops::{Deref, Range},
-    path::{Path, PathBuf},
-    str,
-    sync::Arc,
-    time::{Duration, Instant, SystemTime, UNIX_EPOCH},
-    vec,
-};
-use sum_tree::TreeMap;
-use text::operation_queue::OperationQueue;
-pub use text::{Buffer as TextBuffer, BufferSnapshot as TextBufferSnapshot, *};
-use theme::SyntaxTheme;
-#[cfg(any(test, feature = "test-support"))]
-use util::RandomCharIter;
-use util::RangeExt;
-
-#[cfg(any(test, feature = "test-support"))]
-pub use {tree_sitter_rust, tree_sitter_typescript};
-
-pub use lsp::DiagnosticSeverity;
-
-lazy_static! {
-    pub static ref BUFFER_DIFF_TASK: TaskLabel = TaskLabel::new();
-}
-
-pub struct Buffer {
-    text: TextBuffer,
-    diff_base: Option<String>,
-    git_diff: git::diff::BufferDiff,
-    file: Option<Arc<dyn File>>,
-    /// The mtime of the file when this buffer was last loaded from
-    /// or saved to disk.
-    saved_mtime: SystemTime,
-    /// The version vector when this buffer was last loaded from
-    /// or saved to disk.
-    saved_version: clock::Global,
-    /// A hash of the current contents of the buffer's file.
-    file_fingerprint: RopeFingerprint,
-    transaction_depth: usize,
-    was_dirty_before_starting_transaction: Option<bool>,
-    reload_task: Option<Task<Result<()>>>,
-    language: Option<Arc<Language>>,
-    autoindent_requests: Vec<Arc<AutoindentRequest>>,
-    pending_autoindent: Option<Task<()>>,
-    sync_parse_timeout: Duration,
-    syntax_map: Mutex<SyntaxMap>,
-    parsing_in_background: bool,
-    parse_count: usize,
-    diagnostics: SmallVec<[(LanguageServerId, DiagnosticSet); 2]>,
-    remote_selections: TreeMap<ReplicaId, SelectionSet>,
-    selections_update_count: usize,
-    diagnostics_update_count: usize,
-    diagnostics_timestamp: clock::Lamport,
-    file_update_count: usize,
-    git_diff_update_count: usize,
-    completion_triggers: Vec<String>,
-    completion_triggers_timestamp: clock::Lamport,
-    deferred_ops: OperationQueue<Operation>,
-}
-
-pub struct BufferSnapshot {
-    text: text::BufferSnapshot,
-    pub git_diff: git::diff::BufferDiff,
-    pub(crate) syntax: SyntaxSnapshot,
-    file: Option<Arc<dyn File>>,
-    diagnostics: SmallVec<[(LanguageServerId, DiagnosticSet); 2]>,
-    diagnostics_update_count: usize,
-    file_update_count: usize,
-    git_diff_update_count: usize,
-    remote_selections: TreeMap<ReplicaId, SelectionSet>,
-    selections_update_count: usize,
-    language: Option<Arc<Language>>,
-    parse_count: usize,
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
-pub struct IndentSize {
-    pub len: u32,
-    pub kind: IndentKind,
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
-pub enum IndentKind {
-    #[default]
-    Space,
-    Tab,
-}
-
-#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
-pub enum CursorShape {
-    #[default]
-    Bar,
-    Block,
-    Underscore,
-    Hollow,
-}
-
-#[derive(Clone, Debug)]
-struct SelectionSet {
-    line_mode: bool,
-    cursor_shape: CursorShape,
-    selections: Arc<[Selection<Anchor>]>,
-    lamport_timestamp: clock::Lamport,
-}
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct GroupId {
-    source: Arc<str>,
-    id: usize,
-}
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct Diagnostic {
-    pub source: Option<String>,
-    pub code: Option<String>,
-    pub severity: DiagnosticSeverity,
-    pub message: String,
-    pub group_id: usize,
-    pub is_valid: bool,
-    pub is_primary: bool,
-    pub is_disk_based: bool,
-    pub is_unnecessary: bool,
-}
-
-pub async fn prepare_completion_documentation(
-    documentation: &lsp::Documentation,
-    language_registry: &Arc<LanguageRegistry>,
-    language: Option<Arc<Language>>,
-) -> Documentation {
-    match documentation {
-        lsp::Documentation::String(text) => {
-            if text.lines().count() <= 1 {
-                Documentation::SingleLine(text.clone())
-            } else {
-                Documentation::MultiLinePlainText(text.clone())
-            }
-        }
-
-        lsp::Documentation::MarkupContent(lsp::MarkupContent { kind, value }) => match kind {
-            lsp::MarkupKind::PlainText => {
-                if value.lines().count() <= 1 {
-                    Documentation::SingleLine(value.clone())
-                } else {
-                    Documentation::MultiLinePlainText(value.clone())
-                }
-            }
-
-            lsp::MarkupKind::Markdown => {
-                let parsed = parse_markdown(value, language_registry, language).await;
-                Documentation::MultiLineMarkdown(parsed)
-            }
-        },
-    }
-}
-
-#[derive(Clone, Debug)]
-pub enum Documentation {
-    Undocumented,
-    SingleLine(String),
-    MultiLinePlainText(String),
-    MultiLineMarkdown(ParsedMarkdown),
-}
-
-#[derive(Clone, Debug)]
-pub struct Completion {
-    pub old_range: Range<Anchor>,
-    pub new_text: String,
-    pub label: CodeLabel,
-    pub server_id: LanguageServerId,
-    pub documentation: Option<Documentation>,
-    pub lsp_completion: lsp::CompletionItem,
-}
-
-#[derive(Clone, Debug)]
-pub struct CodeAction {
-    pub server_id: LanguageServerId,
-    pub range: Range<Anchor>,
-    pub lsp_action: lsp::CodeAction,
-}
-
-#[derive(Clone, Debug, PartialEq)]
-pub enum Operation {
-    Buffer(text::Operation),
-
-    UpdateDiagnostics {
-        server_id: LanguageServerId,
-        diagnostics: Arc<[DiagnosticEntry<Anchor>]>,
-        lamport_timestamp: clock::Lamport,
-    },
-
-    UpdateSelections {
-        selections: Arc<[Selection<Anchor>]>,
-        lamport_timestamp: clock::Lamport,
-        line_mode: bool,
-        cursor_shape: CursorShape,
-    },
-
-    UpdateCompletionTriggers {
-        triggers: Vec<String>,
-        lamport_timestamp: clock::Lamport,
-    },
-}
-
-#[derive(Clone, Debug, PartialEq)]
-pub enum Event {
-    Operation(Operation),
-    Edited,
-    DirtyChanged,
-    Saved,
-    FileHandleChanged,
-    Reloaded,
-    DiffBaseChanged,
-    LanguageChanged,
-    Reparsed,
-    DiagnosticsUpdated,
-    Closed,
-}
-
-pub trait File: Send + Sync {
-    fn as_local(&self) -> Option<&dyn LocalFile>;
-
-    fn is_local(&self) -> bool {
-        self.as_local().is_some()
-    }
-
-    fn mtime(&self) -> SystemTime;
-
-    /// Returns the path of this file relative to the worktree's root directory.
-    fn path(&self) -> &Arc<Path>;
-
-    /// Returns the path of this file relative to the worktree's parent directory (this means it
-    /// includes the name of the worktree's root folder).
-    fn full_path(&self, cx: &AppContext) -> PathBuf;
-
-    /// Returns the last component of this handle's absolute path. If this handle refers to the root
-    /// of its worktree, then this method will return the name of the worktree itself.
-    fn file_name<'a>(&'a self, cx: &'a AppContext) -> &'a OsStr;
-
-    /// Returns the id of the worktree to which this file belongs.
-    ///
-    /// This is needed for looking up project-specific settings.
-    fn worktree_id(&self) -> usize;
-
-    fn is_deleted(&self) -> bool;
-
-    fn as_any(&self) -> &dyn Any;
-
-    fn to_proto(&self) -> rpc::proto::File;
-}
-
-pub trait LocalFile: File {
-    /// Returns the absolute path of this file.
-    fn abs_path(&self, cx: &AppContext) -> PathBuf;
-
-    fn load(&self, cx: &AppContext) -> Task<Result<String>>;
-
-    fn buffer_reloaded(
-        &self,
-        buffer_id: u64,
-        version: &clock::Global,
-        fingerprint: RopeFingerprint,
-        line_ending: LineEnding,
-        mtime: SystemTime,
-        cx: &mut AppContext,
-    );
-}
-
-#[derive(Clone, Debug)]
-pub enum AutoindentMode {
-    /// Indent each line of inserted text.
-    EachLine,
-    /// Apply the same indentation adjustment to all of the lines
-    /// in a given insertion.
-    Block {
-        /// The original indentation level of the first line of each
-        /// insertion, if it has been copied.
-        original_indent_columns: Vec<u32>,
-    },
-}
-
-#[derive(Clone)]
-struct AutoindentRequest {
-    before_edit: BufferSnapshot,
-    entries: Vec<AutoindentRequestEntry>,
-    is_block_mode: bool,
-}
-
-#[derive(Clone)]
-struct AutoindentRequestEntry {
-    /// A range of the buffer whose indentation should be adjusted.
-    range: Range<Anchor>,
-    /// Whether or not these lines should be considered brand new, for the
-    /// purpose of auto-indent. When text is not new, its indentation will
-    /// only be adjusted if the suggested indentation level has *changed*
-    /// since the edit was made.
-    first_line_is_new: bool,
-    indent_size: IndentSize,
-    original_indent_column: Option<u32>,
-}
-
-#[derive(Debug)]
-struct IndentSuggestion {
-    basis_row: u32,
-    delta: Ordering,
-    within_error: bool,
-}
-
-struct BufferChunkHighlights<'a> {
-    captures: SyntaxMapCaptures<'a>,
-    next_capture: Option<SyntaxMapCapture<'a>>,
-    stack: Vec<(usize, HighlightId)>,
-    highlight_maps: Vec<HighlightMap>,
-}
-
-pub struct BufferChunks<'a> {
-    range: Range<usize>,
-    chunks: text::Chunks<'a>,
-    diagnostic_endpoints: Peekable<vec::IntoIter<DiagnosticEndpoint>>,
-    error_depth: usize,
-    warning_depth: usize,
-    information_depth: usize,
-    hint_depth: usize,
-    unnecessary_depth: usize,
-    highlights: Option<BufferChunkHighlights<'a>>,
-}
-
-#[derive(Clone, Copy, Debug, Default)]
-pub struct Chunk<'a> {
-    pub text: &'a str,
-    pub syntax_highlight_id: Option<HighlightId>,
-    pub highlight_style: Option<HighlightStyle>,
-    pub diagnostic_severity: Option<DiagnosticSeverity>,
-    pub is_unnecessary: bool,
-    pub is_tab: bool,
-}
-
-pub struct Diff {
-    pub(crate) base_version: clock::Global,
-    line_ending: LineEnding,
-    edits: Vec<(Range<usize>, Arc<str>)>,
-}
-
-#[derive(Clone, Copy)]
-pub(crate) struct DiagnosticEndpoint {
-    offset: usize,
-    is_start: bool,
-    severity: DiagnosticSeverity,
-    is_unnecessary: bool,
-}
-
-#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug)]
-pub enum CharKind {
-    Whitespace,
-    Punctuation,
-    Word,
-}
-
-impl CharKind {
-    pub fn coerce_punctuation(self, treat_punctuation_as_word: bool) -> Self {
-        if treat_punctuation_as_word && self == CharKind::Punctuation {
-            CharKind::Word
-        } else {
-            self
-        }
-    }
-}
-
-impl Buffer {
-    pub fn new<T: Into<String>>(replica_id: ReplicaId, id: u64, base_text: T) -> Self {
-        Self::build(
-            TextBuffer::new(replica_id, id, base_text.into()),
-            None,
-            None,
-        )
-    }
-
-    pub fn remote(remote_id: u64, replica_id: ReplicaId, base_text: String) -> Self {
-        Self::build(
-            TextBuffer::new(replica_id, remote_id, base_text),
-            None,
-            None,
-        )
-    }
-
-    pub fn from_proto(
-        replica_id: ReplicaId,
-        message: proto::BufferState,
-        file: Option<Arc<dyn File>>,
-    ) -> Result<Self> {
-        let buffer = TextBuffer::new(replica_id, message.id, message.base_text);
-        let mut this = Self::build(
-            buffer,
-            message.diff_base.map(|text| text.into_boxed_str().into()),
-            file,
-        );
-        this.text.set_line_ending(proto::deserialize_line_ending(
-            rpc::proto::LineEnding::from_i32(message.line_ending)
-                .ok_or_else(|| anyhow!("missing line_ending"))?,
-        ));
-        this.saved_version = proto::deserialize_version(&message.saved_version);
-        this.file_fingerprint = proto::deserialize_fingerprint(&message.saved_version_fingerprint)?;
-        this.saved_mtime = message
-            .saved_mtime
-            .ok_or_else(|| anyhow!("invalid saved_mtime"))?
-            .into();
-        Ok(this)
-    }
-
-    pub fn to_proto(&self) -> proto::BufferState {
-        proto::BufferState {
-            id: self.remote_id(),
-            file: self.file.as_ref().map(|f| f.to_proto()),
-            base_text: self.base_text().to_string(),
-            diff_base: self.diff_base.as_ref().map(|h| h.to_string()),
-            line_ending: proto::serialize_line_ending(self.line_ending()) as i32,
-            saved_version: proto::serialize_version(&self.saved_version),
-            saved_version_fingerprint: proto::serialize_fingerprint(self.file_fingerprint),
-            saved_mtime: Some(self.saved_mtime.into()),
-        }
-    }
-
-    pub fn serialize_ops(
-        &self,
-        since: Option<clock::Global>,
-        cx: &AppContext,
-    ) -> Task<Vec<proto::Operation>> {
-        let mut operations = Vec::new();
-        operations.extend(self.deferred_ops.iter().map(proto::serialize_operation));
-
-        operations.extend(self.remote_selections.iter().map(|(_, set)| {
-            proto::serialize_operation(&Operation::UpdateSelections {
-                selections: set.selections.clone(),
-                lamport_timestamp: set.lamport_timestamp,
-                line_mode: set.line_mode,
-                cursor_shape: set.cursor_shape,
-            })
-        }));
-
-        for (server_id, diagnostics) in &self.diagnostics {
-            operations.push(proto::serialize_operation(&Operation::UpdateDiagnostics {
-                lamport_timestamp: self.diagnostics_timestamp,
-                server_id: *server_id,
-                diagnostics: diagnostics.iter().cloned().collect(),
-            }));
-        }
-
-        operations.push(proto::serialize_operation(
-            &Operation::UpdateCompletionTriggers {
-                triggers: self.completion_triggers.clone(),
-                lamport_timestamp: self.completion_triggers_timestamp,
-            },
-        ));
-
-        let text_operations = self.text.operations().clone();
-        cx.background_executor().spawn(async move {
-            let since = since.unwrap_or_default();
-            operations.extend(
-                text_operations
-                    .iter()
-                    .filter(|(_, op)| !since.observed(op.timestamp()))
-                    .map(|(_, op)| proto::serialize_operation(&Operation::Buffer(op.clone()))),
-            );
-            operations.sort_unstable_by_key(proto::lamport_timestamp_for_operation);
-            operations
-        })
-    }
-
-    pub fn with_language(mut self, language: Arc<Language>, cx: &mut ModelContext<Self>) -> Self {
-        self.set_language(Some(language), cx);
-        self
-    }
-
-    pub fn build(
-        buffer: TextBuffer,
-        diff_base: Option<String>,
-        file: Option<Arc<dyn File>>,
-    ) -> Self {
-        let saved_mtime = if let Some(file) = file.as_ref() {
-            file.mtime()
-        } else {
-            UNIX_EPOCH
-        };
-
-        Self {
-            saved_mtime,
-            saved_version: buffer.version(),
-            file_fingerprint: buffer.as_rope().fingerprint(),
-            reload_task: None,
-            transaction_depth: 0,
-            was_dirty_before_starting_transaction: None,
-            text: buffer,
-            diff_base,
-            git_diff: git::diff::BufferDiff::new(),
-            file,
-            syntax_map: Mutex::new(SyntaxMap::new()),
-            parsing_in_background: false,
-            parse_count: 0,
-            sync_parse_timeout: Duration::from_millis(1),
-            autoindent_requests: Default::default(),
-            pending_autoindent: Default::default(),
-            language: None,
-            remote_selections: Default::default(),
-            selections_update_count: 0,
-            diagnostics: Default::default(),
-            diagnostics_update_count: 0,
-            diagnostics_timestamp: Default::default(),
-            file_update_count: 0,
-            git_diff_update_count: 0,
-            completion_triggers: Default::default(),
-            completion_triggers_timestamp: Default::default(),
-            deferred_ops: OperationQueue::new(),
-        }
-    }
-
-    pub fn snapshot(&self) -> BufferSnapshot {
-        let text = self.text.snapshot();
-        let mut syntax_map = self.syntax_map.lock();
-        syntax_map.interpolate(&text);
-        let syntax = syntax_map.snapshot();
-
-        BufferSnapshot {
-            text,
-            syntax,
-            git_diff: self.git_diff.clone(),
-            file: self.file.clone(),
-            remote_selections: self.remote_selections.clone(),
-            diagnostics: self.diagnostics.clone(),
-            diagnostics_update_count: self.diagnostics_update_count,
-            file_update_count: self.file_update_count,
-            git_diff_update_count: self.git_diff_update_count,
-            language: self.language.clone(),
-            parse_count: self.parse_count,
-            selections_update_count: self.selections_update_count,
-        }
-    }
-
-    pub fn as_text_snapshot(&self) -> &text::BufferSnapshot {
-        &self.text
-    }
-
-    pub fn text_snapshot(&self) -> text::BufferSnapshot {
-        self.text.snapshot()
-    }
-
-    pub fn file(&self) -> Option<&Arc<dyn File>> {
-        self.file.as_ref()
-    }
-
-    pub fn saved_version(&self) -> &clock::Global {
-        &self.saved_version
-    }
-
-    pub fn saved_version_fingerprint(&self) -> RopeFingerprint {
-        self.file_fingerprint
-    }
-
-    pub fn saved_mtime(&self) -> SystemTime {
-        self.saved_mtime
-    }
-
-    pub fn set_language(&mut self, language: Option<Arc<Language>>, cx: &mut ModelContext<Self>) {
-        self.syntax_map.lock().clear();
-        self.language = language;
-        self.reparse(cx);
-        cx.emit(Event::LanguageChanged);
-    }
-
-    pub fn set_language_registry(&mut self, language_registry: Arc<LanguageRegistry>) {
-        self.syntax_map
-            .lock()
-            .set_language_registry(language_registry);
-    }
-
-    pub fn did_save(
-        &mut self,
-        version: clock::Global,
-        fingerprint: RopeFingerprint,
-        mtime: SystemTime,
-        cx: &mut ModelContext<Self>,
-    ) {
-        self.saved_version = version;
-        self.file_fingerprint = fingerprint;
-        self.saved_mtime = mtime;
-        cx.emit(Event::Saved);
-        cx.notify();
-    }
-
-    pub fn reload(
-        &mut self,
-        cx: &mut ModelContext<Self>,
-    ) -> oneshot::Receiver<Option<Transaction>> {
-        let (tx, rx) = futures::channel::oneshot::channel();
-        let prev_version = self.text.version();
-        self.reload_task = Some(cx.spawn(|this, mut cx| async move {
-            let Some((new_mtime, new_text)) = this.update(&mut cx, |this, cx| {
-                let file = this.file.as_ref()?.as_local()?;
-                Some((file.mtime(), file.load(cx)))
-            })?
-            else {
-                return Ok(());
-            };
-
-            let new_text = new_text.await?;
-            let diff = this
-                .update(&mut cx, |this, cx| this.diff(new_text.clone(), cx))?
-                .await;
-            this.update(&mut cx, |this, cx| {
-                if this.version() == diff.base_version {
-                    this.finalize_last_transaction();
-                    this.apply_diff(diff, cx);
-                    tx.send(this.finalize_last_transaction().cloned()).ok();
-
-                    this.did_reload(
-                        this.version(),
-                        this.as_rope().fingerprint(),
-                        this.line_ending(),
-                        new_mtime,
-                        cx,
-                    );
-                } else {
-                    this.did_reload(
-                        prev_version,
-                        Rope::text_fingerprint(&new_text),
-                        this.line_ending(),
-                        this.saved_mtime,
-                        cx,
-                    );
-                }
-
-                this.reload_task.take();
-            })
-        }));
-        rx
-    }
-
-    pub fn did_reload(
-        &mut self,
-        version: clock::Global,
-        fingerprint: RopeFingerprint,
-        line_ending: LineEnding,
-        mtime: SystemTime,
-        cx: &mut ModelContext<Self>,
-    ) {
-        self.saved_version = version;
-        self.file_fingerprint = fingerprint;
-        self.text.set_line_ending(line_ending);
-        self.saved_mtime = mtime;
-        if let Some(file) = self.file.as_ref().and_then(|f| f.as_local()) {
-            file.buffer_reloaded(
-                self.remote_id(),
-                &self.saved_version,
-                self.file_fingerprint,
-                self.line_ending(),
-                self.saved_mtime,
-                cx,
-            );
-        }
-        cx.emit(Event::Reloaded);
-        cx.notify();
-    }
-
-    pub fn file_updated(&mut self, new_file: Arc<dyn File>, cx: &mut ModelContext<Self>) {
-        let mut file_changed = false;
-
-        if let Some(old_file) = self.file.as_ref() {
-            if new_file.path() != old_file.path() {
-                file_changed = true;
-            }
-
-            if new_file.is_deleted() {
-                if !old_file.is_deleted() {
-                    file_changed = true;
-                    if !self.is_dirty() {
-                        cx.emit(Event::DirtyChanged);
-                    }
-                }
-            } else {
-                let new_mtime = new_file.mtime();
-                if new_mtime != old_file.mtime() {
-                    file_changed = true;
-
-                    if !self.is_dirty() {
-                        self.reload(cx).close();
-                    }
-                }
-            }
-        } else {
-            file_changed = true;
-        };
-
-        self.file = Some(new_file);
-        if file_changed {
-            self.file_update_count += 1;
-            cx.emit(Event::FileHandleChanged);
-            cx.notify();
-        }
-    }
-
-    pub fn diff_base(&self) -> Option<&str> {
-        self.diff_base.as_deref()
-    }
-
-    pub fn set_diff_base(&mut self, diff_base: Option<String>, cx: &mut ModelContext<Self>) {
-        self.diff_base = diff_base;
-        self.git_diff_recalc(cx);
-        cx.emit(Event::DiffBaseChanged);
-    }
-
-    pub fn git_diff_recalc(&mut self, cx: &mut ModelContext<Self>) -> Option<Task<()>> {
-        let diff_base = self.diff_base.clone()?; // TODO: Make this an Arc
-        let snapshot = self.snapshot();
-
-        let mut diff = self.git_diff.clone();
-        let diff = cx.background_executor().spawn(async move {
-            diff.update(&diff_base, &snapshot).await;
-            diff
-        });
-
-        Some(cx.spawn(|this, mut cx| async move {
-            let buffer_diff = diff.await;
-            this.update(&mut cx, |this, _| {
-                this.git_diff = buffer_diff;
-                this.git_diff_update_count += 1;
-            })
-            .ok();
-        }))
-    }
-
-    pub fn close(&mut self, cx: &mut ModelContext<Self>) {
-        cx.emit(Event::Closed);
-    }
-
-    pub fn language(&self) -> Option<&Arc<Language>> {
-        self.language.as_ref()
-    }
-
-    pub fn language_at<D: ToOffset>(&self, position: D) -> Option<Arc<Language>> {
-        let offset = position.to_offset(self);
-        self.syntax_map
-            .lock()
-            .layers_for_range(offset..offset, &self.text)
-            .last()
-            .map(|info| info.language.clone())
-            .or_else(|| self.language.clone())
-    }
-
-    pub fn parse_count(&self) -> usize {
-        self.parse_count
-    }
-
-    pub fn selections_update_count(&self) -> usize {
-        self.selections_update_count
-    }
-
-    pub fn diagnostics_update_count(&self) -> usize {
-        self.diagnostics_update_count
-    }
-
-    pub fn file_update_count(&self) -> usize {
-        self.file_update_count
-    }
-
-    pub fn git_diff_update_count(&self) -> usize {
-        self.git_diff_update_count
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn is_parsing(&self) -> bool {
-        self.parsing_in_background
-    }
-
-    pub fn contains_unknown_injections(&self) -> bool {
-        self.syntax_map.lock().contains_unknown_injections()
-    }
-
-    #[cfg(test)]
-    pub fn set_sync_parse_timeout(&mut self, timeout: Duration) {
-        self.sync_parse_timeout = timeout;
-    }
-
-    /// Called after an edit to synchronize the buffer's main parse tree with
-    /// the buffer's new underlying state.
-    ///
-    /// Locks the syntax map and interpolates the edits since the last reparse
-    /// into the foreground syntax tree.
-    ///
-    /// Then takes a stable snapshot of the syntax map before unlocking it.
-    /// The snapshot with the interpolated edits is sent to a background thread,
-    /// where we ask Tree-sitter to perform an incremental parse.
-    ///
-    /// Meanwhile, in the foreground, we block the main thread for up to 1ms
-    /// waiting on the parse to complete. As soon as it completes, we proceed
-    /// synchronously, unless a 1ms timeout elapses.
-    ///
-    /// If we time out waiting on the parse, we spawn a second task waiting
-    /// until the parse does complete and return with the interpolated tree still
-    /// in the foreground. When the background parse completes, call back into
-    /// the main thread and assign the foreground parse state.
-    ///
-    /// If the buffer or grammar changed since the start of the background parse,
-    /// initiate an additional reparse recursively. To avoid concurrent parses
-    /// for the same buffer, we only initiate a new parse if we are not already
-    /// parsing in the background.
-    pub fn reparse(&mut self, cx: &mut ModelContext<Self>) {
-        if self.parsing_in_background {
-            return;
-        }
-        let language = if let Some(language) = self.language.clone() {
-            language
-        } else {
-            return;
-        };
-
-        let text = self.text_snapshot();
-        let parsed_version = self.version();
-
-        let mut syntax_map = self.syntax_map.lock();
-        syntax_map.interpolate(&text);
-        let language_registry = syntax_map.language_registry();
-        let mut syntax_snapshot = syntax_map.snapshot();
-        drop(syntax_map);
-
-        let parse_task = cx.background_executor().spawn({
-            let language = language.clone();
-            let language_registry = language_registry.clone();
-            async move {
-                syntax_snapshot.reparse(&text, language_registry, language);
-                syntax_snapshot
-            }
-        });
-
-        match cx
-            .background_executor()
-            .block_with_timeout(self.sync_parse_timeout, parse_task)
-        {
-            Ok(new_syntax_snapshot) => {
-                self.did_finish_parsing(new_syntax_snapshot, cx);
-                return;
-            }
-            Err(parse_task) => {
-                self.parsing_in_background = true;
-                cx.spawn(move |this, mut cx| async move {
-                    let new_syntax_map = parse_task.await;
-                    this.update(&mut cx, move |this, cx| {
-                        let grammar_changed =
-                            this.language.as_ref().map_or(true, |current_language| {
-                                !Arc::ptr_eq(&language, current_language)
-                            });
-                        let language_registry_changed = new_syntax_map
-                            .contains_unknown_injections()
-                            && language_registry.map_or(false, |registry| {
-                                registry.version() != new_syntax_map.language_registry_version()
-                            });
-                        let parse_again = language_registry_changed
-                            || grammar_changed
-                            || this.version.changed_since(&parsed_version);
-                        this.did_finish_parsing(new_syntax_map, cx);
-                        this.parsing_in_background = false;
-                        if parse_again {
-                            this.reparse(cx);
-                        }
-                    })
-                    .ok();
-                })
-                .detach();
-            }
-        }
-    }
-
-    fn did_finish_parsing(&mut self, syntax_snapshot: SyntaxSnapshot, cx: &mut ModelContext<Self>) {
-        self.parse_count += 1;
-        self.syntax_map.lock().did_parse(syntax_snapshot);
-        self.request_autoindent(cx);
-        cx.emit(Event::Reparsed);
-        cx.notify();
-    }
-
-    pub fn update_diagnostics(
-        &mut self,
-        server_id: LanguageServerId,
-        diagnostics: DiagnosticSet,
-        cx: &mut ModelContext<Self>,
-    ) {
-        let lamport_timestamp = self.text.lamport_clock.tick();
-        let op = Operation::UpdateDiagnostics {
-            server_id,
-            diagnostics: diagnostics.iter().cloned().collect(),
-            lamport_timestamp,
-        };
-        self.apply_diagnostic_update(server_id, diagnostics, lamport_timestamp, cx);
-        self.send_operation(op, cx);
-    }
-
-    fn request_autoindent(&mut self, cx: &mut ModelContext<Self>) {
-        if let Some(indent_sizes) = self.compute_autoindents() {
-            let indent_sizes = cx.background_executor().spawn(indent_sizes);
-            match cx
-                .background_executor()
-                .block_with_timeout(Duration::from_micros(500), indent_sizes)
-            {
-                Ok(indent_sizes) => self.apply_autoindents(indent_sizes, cx),
-                Err(indent_sizes) => {
-                    self.pending_autoindent = Some(cx.spawn(|this, mut cx| async move {
-                        let indent_sizes = indent_sizes.await;
-                        this.update(&mut cx, |this, cx| {
-                            this.apply_autoindents(indent_sizes, cx);
-                        })
-                        .ok();
-                    }));
-                }
-            }
-        } else {
-            self.autoindent_requests.clear();
-        }
-    }
-
-    fn compute_autoindents(&self) -> Option<impl Future<Output = BTreeMap<u32, IndentSize>>> {
-        let max_rows_between_yields = 100;
-        let snapshot = self.snapshot();
-        if snapshot.syntax.is_empty() || self.autoindent_requests.is_empty() {
-            return None;
-        }
-
-        let autoindent_requests = self.autoindent_requests.clone();
-        Some(async move {
-            let mut indent_sizes = BTreeMap::new();
-            for request in autoindent_requests {
-                // Resolve each edited range to its row in the current buffer and in the
-                // buffer before this batch of edits.
-                let mut row_ranges = Vec::new();
-                let mut old_to_new_rows = BTreeMap::new();
-                let mut language_indent_sizes_by_new_row = Vec::new();
-                for entry in &request.entries {
-                    let position = entry.range.start;
-                    let new_row = position.to_point(&snapshot).row;
-                    let new_end_row = entry.range.end.to_point(&snapshot).row + 1;
-                    language_indent_sizes_by_new_row.push((new_row, entry.indent_size));
-
-                    if !entry.first_line_is_new {
-                        let old_row = position.to_point(&request.before_edit).row;
-                        old_to_new_rows.insert(old_row, new_row);
-                    }
-                    row_ranges.push((new_row..new_end_row, entry.original_indent_column));
-                }
-
-                // Build a map containing the suggested indentation for each of the edited lines
-                // with respect to the state of the buffer before these edits. This map is keyed
-                // by the rows for these lines in the current state of the buffer.
-                let mut old_suggestions = BTreeMap::<u32, (IndentSize, bool)>::default();
-                let old_edited_ranges =
-                    contiguous_ranges(old_to_new_rows.keys().copied(), max_rows_between_yields);
-                let mut language_indent_sizes = language_indent_sizes_by_new_row.iter().peekable();
-                let mut language_indent_size = IndentSize::default();
-                for old_edited_range in old_edited_ranges {
-                    let suggestions = request
-                        .before_edit
-                        .suggest_autoindents(old_edited_range.clone())
-                        .into_iter()
-                        .flatten();
-                    for (old_row, suggestion) in old_edited_range.zip(suggestions) {
-                        if let Some(suggestion) = suggestion {
-                            let new_row = *old_to_new_rows.get(&old_row).unwrap();
-
-                            // Find the indent size based on the language for this row.
-                            while let Some((row, size)) = language_indent_sizes.peek() {
-                                if *row > new_row {
-                                    break;
-                                }
-                                language_indent_size = *size;
-                                language_indent_sizes.next();
-                            }
-
-                            let suggested_indent = old_to_new_rows
-                                .get(&suggestion.basis_row)
-                                .and_then(|from_row| {
-                                    Some(old_suggestions.get(from_row).copied()?.0)
-                                })
-                                .unwrap_or_else(|| {
-                                    request
-                                        .before_edit
-                                        .indent_size_for_line(suggestion.basis_row)
-                                })
-                                .with_delta(suggestion.delta, language_indent_size);
-                            old_suggestions
-                                .insert(new_row, (suggested_indent, suggestion.within_error));
-                        }
-                    }
-                    yield_now().await;
-                }
-
-                // In block mode, only compute indentation suggestions for the first line
-                // of each insertion. Otherwise, compute suggestions for every inserted line.
-                let new_edited_row_ranges = contiguous_ranges(
-                    row_ranges.iter().flat_map(|(range, _)| {
-                        if request.is_block_mode {
-                            range.start..range.start + 1
-                        } else {
-                            range.clone()
-                        }
-                    }),
-                    max_rows_between_yields,
-                );
-
-                // Compute new suggestions for each line, but only include them in the result
-                // if they differ from the old suggestion for that line.
-                let mut language_indent_sizes = language_indent_sizes_by_new_row.iter().peekable();
-                let mut language_indent_size = IndentSize::default();
-                for new_edited_row_range in new_edited_row_ranges {
-                    let suggestions = snapshot
-                        .suggest_autoindents(new_edited_row_range.clone())
-                        .into_iter()
-                        .flatten();
-                    for (new_row, suggestion) in new_edited_row_range.zip(suggestions) {
-                        if let Some(suggestion) = suggestion {
-                            // Find the indent size based on the language for this row.
-                            while let Some((row, size)) = language_indent_sizes.peek() {
-                                if *row > new_row {
-                                    break;
-                                }
-                                language_indent_size = *size;
-                                language_indent_sizes.next();
-                            }
-
-                            let suggested_indent = indent_sizes
-                                .get(&suggestion.basis_row)
-                                .copied()
-                                .unwrap_or_else(|| {
-                                    snapshot.indent_size_for_line(suggestion.basis_row)
-                                })
-                                .with_delta(suggestion.delta, language_indent_size);
-                            if old_suggestions.get(&new_row).map_or(
-                                true,
-                                |(old_indentation, was_within_error)| {
-                                    suggested_indent != *old_indentation
-                                        && (!suggestion.within_error || *was_within_error)
-                                },
-                            ) {
-                                indent_sizes.insert(new_row, suggested_indent);
-                            }
-                        }
-                    }
-                    yield_now().await;
-                }
-
-                // For each block of inserted text, adjust the indentation of the remaining
-                // lines of the block by the same amount as the first line was adjusted.
-                if request.is_block_mode {
-                    for (row_range, original_indent_column) in
-                        row_ranges
-                            .into_iter()
-                            .filter_map(|(range, original_indent_column)| {
-                                if range.len() > 1 {
-                                    Some((range, original_indent_column?))
-                                } else {
-                                    None
-                                }
-                            })
-                    {
-                        let new_indent = indent_sizes
-                            .get(&row_range.start)
-                            .copied()
-                            .unwrap_or_else(|| snapshot.indent_size_for_line(row_range.start));
-                        let delta = new_indent.len as i64 - original_indent_column as i64;
-                        if delta != 0 {
-                            for row in row_range.skip(1) {
-                                indent_sizes.entry(row).or_insert_with(|| {
-                                    let mut size = snapshot.indent_size_for_line(row);
-                                    if size.kind == new_indent.kind {
-                                        match delta.cmp(&0) {
-                                            Ordering::Greater => size.len += delta as u32,
-                                            Ordering::Less => {
-                                                size.len = size.len.saturating_sub(-delta as u32)
-                                            }
-                                            Ordering::Equal => {}
-                                        }
-                                    }
-                                    size
-                                });
-                            }
-                        }
-                    }
-                }
-            }
-
-            indent_sizes
-        })
-    }
-
-    fn apply_autoindents(
-        &mut self,
-        indent_sizes: BTreeMap<u32, IndentSize>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        self.autoindent_requests.clear();
-
-        let edits: Vec<_> = indent_sizes
-            .into_iter()
-            .filter_map(|(row, indent_size)| {
-                let current_size = indent_size_for_line(self, row);
-                Self::edit_for_indent_size_adjustment(row, current_size, indent_size)
-            })
-            .collect();
-
-        self.edit(edits, None, cx);
-    }
-
-    // Create a minimal edit that will cause the the given row to be indented
-    // with the given size. After applying this edit, the length of the line
-    // will always be at least `new_size.len`.
-    pub fn edit_for_indent_size_adjustment(
-        row: u32,
-        current_size: IndentSize,
-        new_size: IndentSize,
-    ) -> Option<(Range<Point>, String)> {
-        if new_size.kind != current_size.kind {
-            Some((
-                Point::new(row, 0)..Point::new(row, current_size.len),
-                iter::repeat(new_size.char())
-                    .take(new_size.len as usize)
-                    .collect::<String>(),
-            ))
-        } else {
-            match new_size.len.cmp(&current_size.len) {
-                Ordering::Greater => {
-                    let point = Point::new(row, 0);
-                    Some((
-                        point..point,
-                        iter::repeat(new_size.char())
-                            .take((new_size.len - current_size.len) as usize)
-                            .collect::<String>(),
-                    ))
-                }
-
-                Ordering::Less => Some((
-                    Point::new(row, 0)..Point::new(row, current_size.len - new_size.len),
-                    String::new(),
-                )),
-
-                Ordering::Equal => None,
-            }
-        }
-    }
-
-    pub fn diff(&self, mut new_text: String, cx: &AppContext) -> Task<Diff> {
-        let old_text = self.as_rope().clone();
-        let base_version = self.version();
-        cx.background_executor()
-            .spawn_labeled(*BUFFER_DIFF_TASK, async move {
-                let old_text = old_text.to_string();
-                let line_ending = LineEnding::detect(&new_text);
-                LineEnding::normalize(&mut new_text);
-
-                let diff = TextDiff::from_chars(old_text.as_str(), new_text.as_str());
-                let empty: Arc<str> = "".into();
-
-                let mut edits = Vec::new();
-                let mut old_offset = 0;
-                let mut new_offset = 0;
-                let mut last_edit: Option<(Range<usize>, Range<usize>)> = None;
-                for change in diff.iter_all_changes().map(Some).chain([None]) {
-                    if let Some(change) = &change {
-                        let len = change.value().len();
-                        match change.tag() {
-                            ChangeTag::Equal => {
-                                old_offset += len;
-                                new_offset += len;
-                            }
-                            ChangeTag::Delete => {
-                                let old_end_offset = old_offset + len;
-                                if let Some((last_old_range, _)) = &mut last_edit {
-                                    last_old_range.end = old_end_offset;
-                                } else {
-                                    last_edit =
-                                        Some((old_offset..old_end_offset, new_offset..new_offset));
-                                }
-                                old_offset = old_end_offset;
-                            }
-                            ChangeTag::Insert => {
-                                let new_end_offset = new_offset + len;
-                                if let Some((_, last_new_range)) = &mut last_edit {
-                                    last_new_range.end = new_end_offset;
-                                } else {
-                                    last_edit =
-                                        Some((old_offset..old_offset, new_offset..new_end_offset));
-                                }
-                                new_offset = new_end_offset;
-                            }
-                        }
-                    }
-
-                    if let Some((old_range, new_range)) = &last_edit {
-                        if old_offset > old_range.end
-                            || new_offset > new_range.end
-                            || change.is_none()
-                        {
-                            let text = if new_range.is_empty() {
-                                empty.clone()
-                            } else {
-                                new_text[new_range.clone()].into()
-                            };
-                            edits.push((old_range.clone(), text));
-                            last_edit.take();
-                        }
-                    }
-                }
-
-                Diff {
-                    base_version,
-                    line_ending,
-                    edits,
-                }
-            })
-    }
-
-    /// Spawn a background task that searches the buffer for any whitespace
-    /// at the ends of a lines, and returns a `Diff` that removes that whitespace.
-    pub fn remove_trailing_whitespace(&self, cx: &AppContext) -> Task<Diff> {
-        let old_text = self.as_rope().clone();
-        let line_ending = self.line_ending();
-        let base_version = self.version();
-        cx.background_executor().spawn(async move {
-            let ranges = trailing_whitespace_ranges(&old_text);
-            let empty = Arc::<str>::from("");
-            Diff {
-                base_version,
-                line_ending,
-                edits: ranges
-                    .into_iter()
-                    .map(|range| (range, empty.clone()))
-                    .collect(),
-            }
-        })
-    }
-
-    /// Ensure that the buffer ends with a single newline character, and
-    /// no other whitespace.
-    pub fn ensure_final_newline(&mut self, cx: &mut ModelContext<Self>) {
-        let len = self.len();
-        let mut offset = len;
-        for chunk in self.as_rope().reversed_chunks_in_range(0..len) {
-            let non_whitespace_len = chunk
-                .trim_end_matches(|c: char| c.is_ascii_whitespace())
-                .len();
-            offset -= chunk.len();
-            offset += non_whitespace_len;
-            if non_whitespace_len != 0 {
-                if offset == len - 1 && chunk.get(non_whitespace_len..) == Some("\n") {
-                    return;
-                }
-                break;
-            }
-        }
-        self.edit([(offset..len, "\n")], None, cx);
-    }
-
-    /// Apply a diff to the buffer. If the buffer has changed since the given diff was
-    /// calculated, then adjust the diff to account for those changes, and discard any
-    /// parts of the diff that conflict with those changes.
-    pub fn apply_diff(&mut self, diff: Diff, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
-        // Check for any edits to the buffer that have occurred since this diff
-        // was computed.
-        let snapshot = self.snapshot();
-        let mut edits_since = snapshot.edits_since::<usize>(&diff.base_version).peekable();
-        let mut delta = 0;
-        let adjusted_edits = diff.edits.into_iter().filter_map(|(range, new_text)| {
-            while let Some(edit_since) = edits_since.peek() {
-                // If the edit occurs after a diff hunk, then it does not
-                // affect that hunk.
-                if edit_since.old.start > range.end {
-                    break;
-                }
-                // If the edit precedes the diff hunk, then adjust the hunk
-                // to reflect the edit.
-                else if edit_since.old.end < range.start {
-                    delta += edit_since.new_len() as i64 - edit_since.old_len() as i64;
-                    edits_since.next();
-                }
-                // If the edit intersects a diff hunk, then discard that hunk.
-                else {
-                    return None;
-                }
-            }
-
-            let start = (range.start as i64 + delta) as usize;
-            let end = (range.end as i64 + delta) as usize;
-            Some((start..end, new_text))
-        });
-
-        self.start_transaction();
-        self.text.set_line_ending(diff.line_ending);
-        self.edit(adjusted_edits, None, cx);
-        self.end_transaction(cx)
-    }
-
-    pub fn is_dirty(&self) -> bool {
-        self.file_fingerprint != self.as_rope().fingerprint()
-            || self.file.as_ref().map_or(false, |file| file.is_deleted())
-    }
-
-    pub fn has_conflict(&self) -> bool {
-        self.file_fingerprint != self.as_rope().fingerprint()
-            && self
-                .file
-                .as_ref()
-                .map_or(false, |file| file.mtime() > self.saved_mtime)
-    }
-
-    pub fn subscribe(&mut self) -> Subscription {
-        self.text.subscribe()
-    }
-
-    pub fn start_transaction(&mut self) -> Option<TransactionId> {
-        self.start_transaction_at(Instant::now())
-    }
-
-    pub fn start_transaction_at(&mut self, now: Instant) -> Option<TransactionId> {
-        self.transaction_depth += 1;
-        if self.was_dirty_before_starting_transaction.is_none() {
-            self.was_dirty_before_starting_transaction = Some(self.is_dirty());
-        }
-        self.text.start_transaction_at(now)
-    }
-
-    pub fn end_transaction(&mut self, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
-        self.end_transaction_at(Instant::now(), cx)
-    }
-
-    pub fn end_transaction_at(
-        &mut self,
-        now: Instant,
-        cx: &mut ModelContext<Self>,
-    ) -> Option<TransactionId> {
-        assert!(self.transaction_depth > 0);
-        self.transaction_depth -= 1;
-        let was_dirty = if self.transaction_depth == 0 {
-            self.was_dirty_before_starting_transaction.take().unwrap()
-        } else {
-            false
-        };
-        if let Some((transaction_id, start_version)) = self.text.end_transaction_at(now) {
-            self.did_edit(&start_version, was_dirty, cx);
-            Some(transaction_id)
-        } else {
-            None
-        }
-    }
-
-    pub fn push_transaction(&mut self, transaction: Transaction, now: Instant) {
-        self.text.push_transaction(transaction, now);
-    }
-
-    pub fn finalize_last_transaction(&mut self) -> Option<&Transaction> {
-        self.text.finalize_last_transaction()
-    }
-
-    pub fn group_until_transaction(&mut self, transaction_id: TransactionId) {
-        self.text.group_until_transaction(transaction_id);
-    }
-
-    pub fn forget_transaction(&mut self, transaction_id: TransactionId) {
-        self.text.forget_transaction(transaction_id);
-    }
-
-    pub fn merge_transactions(&mut self, transaction: TransactionId, destination: TransactionId) {
-        self.text.merge_transactions(transaction, destination);
-    }
-
-    pub fn wait_for_edits(
-        &mut self,
-        edit_ids: impl IntoIterator<Item = clock::Lamport>,
-    ) -> impl Future<Output = Result<()>> {
-        self.text.wait_for_edits(edit_ids)
-    }
-
-    pub fn wait_for_anchors(
-        &mut self,
-        anchors: impl IntoIterator<Item = Anchor>,
-    ) -> impl 'static + Future<Output = Result<()>> {
-        self.text.wait_for_anchors(anchors)
-    }
-
-    pub fn wait_for_version(&mut self, version: clock::Global) -> impl Future<Output = Result<()>> {
-        self.text.wait_for_version(version)
-    }
-
-    pub fn give_up_waiting(&mut self) {
-        self.text.give_up_waiting();
-    }
-
-    pub fn set_active_selections(
-        &mut self,
-        selections: Arc<[Selection<Anchor>]>,
-        line_mode: bool,
-        cursor_shape: CursorShape,
-        cx: &mut ModelContext<Self>,
-    ) {
-        let lamport_timestamp = self.text.lamport_clock.tick();
-        self.remote_selections.insert(
-            self.text.replica_id(),
-            SelectionSet {
-                selections: selections.clone(),
-                lamport_timestamp,
-                line_mode,
-                cursor_shape,
-            },
-        );
-        self.send_operation(
-            Operation::UpdateSelections {
-                selections,
-                line_mode,
-                lamport_timestamp,
-                cursor_shape,
-            },
-            cx,
-        );
-    }
-
-    pub fn remove_active_selections(&mut self, cx: &mut ModelContext<Self>) {
-        if self
-            .remote_selections
-            .get(&self.text.replica_id())
-            .map_or(true, |set| !set.selections.is_empty())
-        {
-            self.set_active_selections(Arc::from([]), false, Default::default(), cx);
-        }
-    }
-
-    pub fn set_text<T>(&mut self, text: T, cx: &mut ModelContext<Self>) -> Option<clock::Lamport>
-    where
-        T: Into<Arc<str>>,
-    {
-        self.autoindent_requests.clear();
-        self.edit([(0..self.len(), text)], None, cx)
-    }
-
-    pub fn edit<I, S, T>(
-        &mut self,
-        edits_iter: I,
-        autoindent_mode: Option<AutoindentMode>,
-        cx: &mut ModelContext<Self>,
-    ) -> Option<clock::Lamport>
-    where
-        I: IntoIterator<Item = (Range<S>, T)>,
-        S: ToOffset,
-        T: Into<Arc<str>>,
-    {
-        // Skip invalid edits and coalesce contiguous ones.
-        let mut edits: Vec<(Range<usize>, Arc<str>)> = Vec::new();
-        for (range, new_text) in edits_iter {
-            let mut range = range.start.to_offset(self)..range.end.to_offset(self);
-            if range.start > range.end {
-                mem::swap(&mut range.start, &mut range.end);
-            }
-            let new_text = new_text.into();
-            if !new_text.is_empty() || !range.is_empty() {
-                if let Some((prev_range, prev_text)) = edits.last_mut() {
-                    if prev_range.end >= range.start {
-                        prev_range.end = cmp::max(prev_range.end, range.end);
-                        *prev_text = format!("{prev_text}{new_text}").into();
-                    } else {
-                        edits.push((range, new_text));
-                    }
-                } else {
-                    edits.push((range, new_text));
-                }
-            }
-        }
-        if edits.is_empty() {
-            return None;
-        }
-
-        self.start_transaction();
-        self.pending_autoindent.take();
-        let autoindent_request = autoindent_mode
-            .and_then(|mode| self.language.as_ref().map(|_| (self.snapshot(), mode)));
-
-        let edit_operation = self.text.edit(edits.iter().cloned());
-        let edit_id = edit_operation.timestamp();
-
-        if let Some((before_edit, mode)) = autoindent_request {
-            let mut delta = 0isize;
-            let entries = edits
-                .into_iter()
-                .enumerate()
-                .zip(&edit_operation.as_edit().unwrap().new_text)
-                .map(|((ix, (range, _)), new_text)| {
-                    let new_text_length = new_text.len();
-                    let old_start = range.start.to_point(&before_edit);
-                    let new_start = (delta + range.start as isize) as usize;
-                    delta += new_text_length as isize - (range.end as isize - range.start as isize);
-
-                    let mut range_of_insertion_to_indent = 0..new_text_length;
-                    let mut first_line_is_new = false;
-                    let mut original_indent_column = None;
-
-                    // When inserting an entire line at the beginning of an existing line,
-                    // treat the insertion as new.
-                    if new_text.contains('\n')
-                        && old_start.column <= before_edit.indent_size_for_line(old_start.row).len
-                    {
-                        first_line_is_new = true;
-                    }
-
-                    // When inserting text starting with a newline, avoid auto-indenting the
-                    // previous line.
-                    if new_text.starts_with('\n') {
-                        range_of_insertion_to_indent.start += 1;
-                        first_line_is_new = true;
-                    }
-
-                    // Avoid auto-indenting after the insertion.
-                    if let AutoindentMode::Block {
-                        original_indent_columns,
-                    } = &mode
-                    {
-                        original_indent_column =
-                            Some(original_indent_columns.get(ix).copied().unwrap_or_else(|| {
-                                indent_size_for_text(
-                                    new_text[range_of_insertion_to_indent.clone()].chars(),
-                                )
-                                .len
-                            }));
-                        if new_text[range_of_insertion_to_indent.clone()].ends_with('\n') {
-                            range_of_insertion_to_indent.end -= 1;
-                        }
-                    }
-
-                    AutoindentRequestEntry {
-                        first_line_is_new,
-                        original_indent_column,
-                        indent_size: before_edit.language_indent_size_at(range.start, cx),
-                        range: self.anchor_before(new_start + range_of_insertion_to_indent.start)
-                            ..self.anchor_after(new_start + range_of_insertion_to_indent.end),
-                    }
-                })
-                .collect();
-
-            self.autoindent_requests.push(Arc::new(AutoindentRequest {
-                before_edit,
-                entries,
-                is_block_mode: matches!(mode, AutoindentMode::Block { .. }),
-            }));
-        }
-
-        self.end_transaction(cx);
-        self.send_operation(Operation::Buffer(edit_operation), cx);
-        Some(edit_id)
-    }
-
-    fn did_edit(
-        &mut self,
-        old_version: &clock::Global,
-        was_dirty: bool,
-        cx: &mut ModelContext<Self>,
-    ) {
-        if self.edits_since::<usize>(old_version).next().is_none() {
-            return;
-        }
-
-        self.reparse(cx);
-
-        cx.emit(Event::Edited);
-        if was_dirty != self.is_dirty() {
-            cx.emit(Event::DirtyChanged);
-        }
-        cx.notify();
-    }
-
-    pub fn apply_ops<I: IntoIterator<Item = Operation>>(
-        &mut self,
-        ops: I,
-        cx: &mut ModelContext<Self>,
-    ) -> Result<()> {
-        self.pending_autoindent.take();
-        let was_dirty = self.is_dirty();
-        let old_version = self.version.clone();
-        let mut deferred_ops = Vec::new();
-        let buffer_ops = ops
-            .into_iter()
-            .filter_map(|op| match op {
-                Operation::Buffer(op) => Some(op),
-                _ => {
-                    if self.can_apply_op(&op) {
-                        self.apply_op(op, cx);
-                    } else {
-                        deferred_ops.push(op);
-                    }
-                    None
-                }
-            })
-            .collect::<Vec<_>>();
-        self.text.apply_ops(buffer_ops)?;
-        self.deferred_ops.insert(deferred_ops);
-        self.flush_deferred_ops(cx);
-        self.did_edit(&old_version, was_dirty, cx);
-        // Notify independently of whether the buffer was edited as the operations could include a
-        // selection update.
-        cx.notify();
-        Ok(())
-    }
-
-    fn flush_deferred_ops(&mut self, cx: &mut ModelContext<Self>) {
-        let mut deferred_ops = Vec::new();
-        for op in self.deferred_ops.drain().iter().cloned() {
-            if self.can_apply_op(&op) {
-                self.apply_op(op, cx);
-            } else {
-                deferred_ops.push(op);
-            }
-        }
-        self.deferred_ops.insert(deferred_ops);
-    }
-
-    fn can_apply_op(&self, operation: &Operation) -> bool {
-        match operation {
-            Operation::Buffer(_) => {
-                unreachable!("buffer operations should never be applied at this layer")
-            }
-            Operation::UpdateDiagnostics {
-                diagnostics: diagnostic_set,
-                ..
-            } => diagnostic_set.iter().all(|diagnostic| {
-                self.text.can_resolve(&diagnostic.range.start)
-                    && self.text.can_resolve(&diagnostic.range.end)
-            }),
-            Operation::UpdateSelections { selections, .. } => selections
-                .iter()
-                .all(|s| self.can_resolve(&s.start) && self.can_resolve(&s.end)),
-            Operation::UpdateCompletionTriggers { .. } => true,
-        }
-    }
-
-    fn apply_op(&mut self, operation: Operation, cx: &mut ModelContext<Self>) {
-        match operation {
-            Operation::Buffer(_) => {
-                unreachable!("buffer operations should never be applied at this layer")
-            }
-            Operation::UpdateDiagnostics {
-                server_id,
-                diagnostics: diagnostic_set,
-                lamport_timestamp,
-            } => {
-                let snapshot = self.snapshot();
-                self.apply_diagnostic_update(
-                    server_id,
-                    DiagnosticSet::from_sorted_entries(diagnostic_set.iter().cloned(), &snapshot),
-                    lamport_timestamp,
-                    cx,
-                );
-            }
-            Operation::UpdateSelections {
-                selections,
-                lamport_timestamp,
-                line_mode,
-                cursor_shape,
-            } => {
-                if let Some(set) = self.remote_selections.get(&lamport_timestamp.replica_id) {
-                    if set.lamport_timestamp > lamport_timestamp {
-                        return;
-                    }
-                }
-
-                self.remote_selections.insert(
-                    lamport_timestamp.replica_id,
-                    SelectionSet {
-                        selections,
-                        lamport_timestamp,
-                        line_mode,
-                        cursor_shape,
-                    },
-                );
-                self.text.lamport_clock.observe(lamport_timestamp);
-                self.selections_update_count += 1;
-            }
-            Operation::UpdateCompletionTriggers {
-                triggers,
-                lamport_timestamp,
-            } => {
-                self.completion_triggers = triggers;
-                self.text.lamport_clock.observe(lamport_timestamp);
-            }
-        }
-    }
-
-    fn apply_diagnostic_update(
-        &mut self,
-        server_id: LanguageServerId,
-        diagnostics: DiagnosticSet,
-        lamport_timestamp: clock::Lamport,
-        cx: &mut ModelContext<Self>,
-    ) {
-        if lamport_timestamp > self.diagnostics_timestamp {
-            let ix = self.diagnostics.binary_search_by_key(&server_id, |e| e.0);
-            if diagnostics.len() == 0 {
-                if let Ok(ix) = ix {
-                    self.diagnostics.remove(ix);
-                }
-            } else {
-                match ix {
-                    Err(ix) => self.diagnostics.insert(ix, (server_id, diagnostics)),
-                    Ok(ix) => self.diagnostics[ix].1 = diagnostics,
-                };
-            }
-            self.diagnostics_timestamp = lamport_timestamp;
-            self.diagnostics_update_count += 1;
-            self.text.lamport_clock.observe(lamport_timestamp);
-            cx.notify();
-            cx.emit(Event::DiagnosticsUpdated);
-        }
-    }
-
-    fn send_operation(&mut self, operation: Operation, cx: &mut ModelContext<Self>) {
-        cx.emit(Event::Operation(operation));
-    }
-
-    pub fn remove_peer(&mut self, replica_id: ReplicaId, cx: &mut ModelContext<Self>) {
-        self.remote_selections.remove(&replica_id);
-        cx.notify();
-    }
-
-    pub fn undo(&mut self, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
-        let was_dirty = self.is_dirty();
-        let old_version = self.version.clone();
-
-        if let Some((transaction_id, operation)) = self.text.undo() {
-            self.send_operation(Operation::Buffer(operation), cx);
-            self.did_edit(&old_version, was_dirty, cx);
-            Some(transaction_id)
-        } else {
-            None
-        }
-    }
-
-    pub fn undo_transaction(
-        &mut self,
-        transaction_id: TransactionId,
-        cx: &mut ModelContext<Self>,
-    ) -> bool {
-        let was_dirty = self.is_dirty();
-        let old_version = self.version.clone();
-        if let Some(operation) = self.text.undo_transaction(transaction_id) {
-            self.send_operation(Operation::Buffer(operation), cx);
-            self.did_edit(&old_version, was_dirty, cx);
-            true
-        } else {
-            false
-        }
-    }
-
-    pub fn undo_to_transaction(
-        &mut self,
-        transaction_id: TransactionId,
-        cx: &mut ModelContext<Self>,
-    ) -> bool {
-        let was_dirty = self.is_dirty();
-        let old_version = self.version.clone();
-
-        let operations = self.text.undo_to_transaction(transaction_id);
-        let undone = !operations.is_empty();
-        for operation in operations {
-            self.send_operation(Operation::Buffer(operation), cx);
-        }
-        if undone {
-            self.did_edit(&old_version, was_dirty, cx)
-        }
-        undone
-    }
-
-    pub fn redo(&mut self, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
-        let was_dirty = self.is_dirty();
-        let old_version = self.version.clone();
-
-        if let Some((transaction_id, operation)) = self.text.redo() {
-            self.send_operation(Operation::Buffer(operation), cx);
-            self.did_edit(&old_version, was_dirty, cx);
-            Some(transaction_id)
-        } else {
-            None
-        }
-    }
-
-    pub fn redo_to_transaction(
-        &mut self,
-        transaction_id: TransactionId,
-        cx: &mut ModelContext<Self>,
-    ) -> bool {
-        let was_dirty = self.is_dirty();
-        let old_version = self.version.clone();
-
-        let operations = self.text.redo_to_transaction(transaction_id);
-        let redone = !operations.is_empty();
-        for operation in operations {
-            self.send_operation(Operation::Buffer(operation), cx);
-        }
-        if redone {
-            self.did_edit(&old_version, was_dirty, cx)
-        }
-        redone
-    }
-
-    pub fn set_completion_triggers(&mut self, triggers: Vec<String>, cx: &mut ModelContext<Self>) {
-        self.completion_triggers = triggers.clone();
-        self.completion_triggers_timestamp = self.text.lamport_clock.tick();
-        self.send_operation(
-            Operation::UpdateCompletionTriggers {
-                triggers,
-                lamport_timestamp: self.completion_triggers_timestamp,
-            },
-            cx,
-        );
-        cx.notify();
-    }
-
-    pub fn completion_triggers(&self) -> &[String] {
-        &self.completion_triggers
-    }
-}
-
-#[cfg(any(test, feature = "test-support"))]
-impl Buffer {
-    pub fn edit_via_marked_text(
-        &mut self,
-        marked_string: &str,
-        autoindent_mode: Option<AutoindentMode>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        let edits = self.edits_for_marked_text(marked_string);
-        self.edit(edits, autoindent_mode, cx);
-    }
-
-    pub fn set_group_interval(&mut self, group_interval: Duration) {
-        self.text.set_group_interval(group_interval);
-    }
-
-    pub fn randomly_edit<T>(
-        &mut self,
-        rng: &mut T,
-        old_range_count: usize,
-        cx: &mut ModelContext<Self>,
-    ) where
-        T: rand::Rng,
-    {
-        let mut edits: Vec<(Range<usize>, String)> = Vec::new();
-        let mut last_end = None;
-        for _ in 0..old_range_count {
-            if last_end.map_or(false, |last_end| last_end >= self.len()) {
-                break;
-            }
-
-            let new_start = last_end.map_or(0, |last_end| last_end + 1);
-            let mut range = self.random_byte_range(new_start, rng);
-            if rng.gen_bool(0.2) {
-                mem::swap(&mut range.start, &mut range.end);
-            }
-            last_end = Some(range.end);
-
-            let new_text_len = rng.gen_range(0..10);
-            let new_text: String = RandomCharIter::new(&mut *rng).take(new_text_len).collect();
-
-            edits.push((range, new_text));
-        }
-        log::info!("mutating buffer {} with {:?}", self.replica_id(), edits);
-        self.edit(edits, None, cx);
-    }
-
-    pub fn randomly_undo_redo(&mut self, rng: &mut impl rand::Rng, cx: &mut ModelContext<Self>) {
-        let was_dirty = self.is_dirty();
-        let old_version = self.version.clone();
-
-        let ops = self.text.randomly_undo_redo(rng);
-        if !ops.is_empty() {
-            for op in ops {
-                self.send_operation(Operation::Buffer(op), cx);
-                self.did_edit(&old_version, was_dirty, cx);
-            }
-        }
-    }
-}
-
-impl EventEmitter<Event> for Buffer {}
-
-impl Deref for Buffer {
-    type Target = TextBuffer;
-
-    fn deref(&self) -> &Self::Target {
-        &self.text
-    }
-}
-
-impl BufferSnapshot {
-    pub fn indent_size_for_line(&self, row: u32) -> IndentSize {
-        indent_size_for_line(self, row)
-    }
-
-    pub fn language_indent_size_at<T: ToOffset>(&self, position: T, cx: &AppContext) -> IndentSize {
-        let settings = language_settings(self.language_at(position), self.file(), cx);
-        if settings.hard_tabs {
-            IndentSize::tab()
-        } else {
-            IndentSize::spaces(settings.tab_size.get())
-        }
-    }
-
-    pub fn suggested_indents(
-        &self,
-        rows: impl Iterator<Item = u32>,
-        single_indent_size: IndentSize,
-    ) -> BTreeMap<u32, IndentSize> {
-        let mut result = BTreeMap::new();
-
-        for row_range in contiguous_ranges(rows, 10) {
-            let suggestions = match self.suggest_autoindents(row_range.clone()) {
-                Some(suggestions) => suggestions,
-                _ => break,
-            };
-
-            for (row, suggestion) in row_range.zip(suggestions) {
-                let indent_size = if let Some(suggestion) = suggestion {
-                    result
-                        .get(&suggestion.basis_row)
-                        .copied()
-                        .unwrap_or_else(|| self.indent_size_for_line(suggestion.basis_row))
-                        .with_delta(suggestion.delta, single_indent_size)
-                } else {
-                    self.indent_size_for_line(row)
-                };
-
-                result.insert(row, indent_size);
-            }
-        }
-
-        result
-    }
-
-    fn suggest_autoindents(
-        &self,
-        row_range: Range<u32>,
-    ) -> Option<impl Iterator<Item = Option<IndentSuggestion>> + '_> {
-        let config = &self.language.as_ref()?.config;
-        let prev_non_blank_row = self.prev_non_blank_row(row_range.start);
-
-        // Find the suggested indentation ranges based on the syntax tree.
-        let start = Point::new(prev_non_blank_row.unwrap_or(row_range.start), 0);
-        let end = Point::new(row_range.end, 0);
-        let range = (start..end).to_offset(&self.text);
-        let mut matches = self.syntax.matches(range.clone(), &self.text, |grammar| {
-            Some(&grammar.indents_config.as_ref()?.query)
-        });
-        let indent_configs = matches
-            .grammars()
-            .iter()
-            .map(|grammar| grammar.indents_config.as_ref().unwrap())
-            .collect::<Vec<_>>();
-
-        let mut indent_ranges = Vec::<Range<Point>>::new();
-        let mut outdent_positions = Vec::<Point>::new();
-        while let Some(mat) = matches.peek() {
-            let mut start: Option<Point> = None;
-            let mut end: Option<Point> = None;
-
-            let config = &indent_configs[mat.grammar_index];
-            for capture in mat.captures {
-                if capture.index == config.indent_capture_ix {
-                    start.get_or_insert(Point::from_ts_point(capture.node.start_position()));
-                    end.get_or_insert(Point::from_ts_point(capture.node.end_position()));
-                } else if Some(capture.index) == config.start_capture_ix {
-                    start = Some(Point::from_ts_point(capture.node.end_position()));
-                } else if Some(capture.index) == config.end_capture_ix {
-                    end = Some(Point::from_ts_point(capture.node.start_position()));
-                } else if Some(capture.index) == config.outdent_capture_ix {
-                    outdent_positions.push(Point::from_ts_point(capture.node.start_position()));
-                }
-            }
-
-            matches.advance();
-            if let Some((start, end)) = start.zip(end) {
-                if start.row == end.row {
-                    continue;
-                }
-
-                let range = start..end;
-                match indent_ranges.binary_search_by_key(&range.start, |r| r.start) {
-                    Err(ix) => indent_ranges.insert(ix, range),
-                    Ok(ix) => {
-                        let prev_range = &mut indent_ranges[ix];
-                        prev_range.end = prev_range.end.max(range.end);
-                    }
-                }
-            }
-        }
-
-        let mut error_ranges = Vec::<Range<Point>>::new();
-        let mut matches = self.syntax.matches(range.clone(), &self.text, |grammar| {
-            Some(&grammar.error_query)
-        });
-        while let Some(mat) = matches.peek() {
-            let node = mat.captures[0].node;
-            let start = Point::from_ts_point(node.start_position());
-            let end = Point::from_ts_point(node.end_position());
-            let range = start..end;
-            let ix = match error_ranges.binary_search_by_key(&range.start, |r| r.start) {
-                Ok(ix) | Err(ix) => ix,
-            };
-            let mut end_ix = ix;
-            while let Some(existing_range) = error_ranges.get(end_ix) {
-                if existing_range.end < end {
-                    end_ix += 1;
-                } else {
-                    break;
-                }
-            }
-            error_ranges.splice(ix..end_ix, [range]);
-            matches.advance();
-        }
-
-        outdent_positions.sort();
-        for outdent_position in outdent_positions {
-            // find the innermost indent range containing this outdent_position
-            // set its end to the outdent position
-            if let Some(range_to_truncate) = indent_ranges
-                .iter_mut()
-                .filter(|indent_range| indent_range.contains(&outdent_position))
-                .last()
-            {
-                range_to_truncate.end = outdent_position;
-            }
-        }
-
-        // Find the suggested indentation increases and decreased based on regexes.
-        let mut indent_change_rows = Vec::<(u32, Ordering)>::new();
-        self.for_each_line(
-            Point::new(prev_non_blank_row.unwrap_or(row_range.start), 0)
-                ..Point::new(row_range.end, 0),
-            |row, line| {
-                if config
-                    .decrease_indent_pattern
-                    .as_ref()
-                    .map_or(false, |regex| regex.is_match(line))
-                {
-                    indent_change_rows.push((row, Ordering::Less));
-                }
-                if config
-                    .increase_indent_pattern
-                    .as_ref()
-                    .map_or(false, |regex| regex.is_match(line))
-                {
-                    indent_change_rows.push((row + 1, Ordering::Greater));
-                }
-            },
-        );
-
-        let mut indent_changes = indent_change_rows.into_iter().peekable();
-        let mut prev_row = if config.auto_indent_using_last_non_empty_line {
-            prev_non_blank_row.unwrap_or(0)
-        } else {
-            row_range.start.saturating_sub(1)
-        };
-        let mut prev_row_start = Point::new(prev_row, self.indent_size_for_line(prev_row).len);
-        Some(row_range.map(move |row| {
-            let row_start = Point::new(row, self.indent_size_for_line(row).len);
-
-            let mut indent_from_prev_row = false;
-            let mut outdent_from_prev_row = false;
-            let mut outdent_to_row = u32::MAX;
-
-            while let Some((indent_row, delta)) = indent_changes.peek() {
-                match indent_row.cmp(&row) {
-                    Ordering::Equal => match delta {
-                        Ordering::Less => outdent_from_prev_row = true,
-                        Ordering::Greater => indent_from_prev_row = true,
-                        _ => {}
-                    },
-
-                    Ordering::Greater => break,
-                    Ordering::Less => {}
-                }
-
-                indent_changes.next();
-            }
-
-            for range in &indent_ranges {
-                if range.start.row >= row {
-                    break;
-                }
-                if range.start.row == prev_row && range.end > row_start {
-                    indent_from_prev_row = true;
-                }
-                if range.end > prev_row_start && range.end <= row_start {
-                    outdent_to_row = outdent_to_row.min(range.start.row);
-                }
-            }
-
-            let within_error = error_ranges
-                .iter()
-                .any(|e| e.start.row < row && e.end > row_start);
-
-            let suggestion = if outdent_to_row == prev_row
-                || (outdent_from_prev_row && indent_from_prev_row)
-            {
-                Some(IndentSuggestion {
-                    basis_row: prev_row,
-                    delta: Ordering::Equal,
-                    within_error,
-                })
-            } else if indent_from_prev_row {
-                Some(IndentSuggestion {
-                    basis_row: prev_row,
-                    delta: Ordering::Greater,
-                    within_error,
-                })
-            } else if outdent_to_row < prev_row {
-                Some(IndentSuggestion {
-                    basis_row: outdent_to_row,
-                    delta: Ordering::Equal,
-                    within_error,
-                })
-            } else if outdent_from_prev_row {
-                Some(IndentSuggestion {
-                    basis_row: prev_row,
-                    delta: Ordering::Less,
-                    within_error,
-                })
-            } else if config.auto_indent_using_last_non_empty_line || !self.is_line_blank(prev_row)
-            {
-                Some(IndentSuggestion {
-                    basis_row: prev_row,
-                    delta: Ordering::Equal,
-                    within_error,
-                })
-            } else {
-                None
-            };
-
-            prev_row = row;
-            prev_row_start = row_start;
-            suggestion
-        }))
-    }
-
-    fn prev_non_blank_row(&self, mut row: u32) -> Option<u32> {
-        while row > 0 {
-            row -= 1;
-            if !self.is_line_blank(row) {
-                return Some(row);
-            }
-        }
-        None
-    }
-
-    pub fn chunks<T: ToOffset>(&self, range: Range<T>, language_aware: bool) -> BufferChunks {
-        let range = range.start.to_offset(self)..range.end.to_offset(self);
-
-        let mut syntax = None;
-        let mut diagnostic_endpoints = Vec::new();
-        if language_aware {
-            let captures = self.syntax.captures(range.clone(), &self.text, |grammar| {
-                grammar.highlights_query.as_ref()
-            });
-            let highlight_maps = captures
-                .grammars()
-                .into_iter()
-                .map(|grammar| grammar.highlight_map())
-                .collect();
-            syntax = Some((captures, highlight_maps));
-            for entry in self.diagnostics_in_range::<_, usize>(range.clone(), false) {
-                diagnostic_endpoints.push(DiagnosticEndpoint {
-                    offset: entry.range.start,
-                    is_start: true,
-                    severity: entry.diagnostic.severity,
-                    is_unnecessary: entry.diagnostic.is_unnecessary,
-                });
-                diagnostic_endpoints.push(DiagnosticEndpoint {
-                    offset: entry.range.end,
-                    is_start: false,
-                    severity: entry.diagnostic.severity,
-                    is_unnecessary: entry.diagnostic.is_unnecessary,
-                });
-            }
-            diagnostic_endpoints
-                .sort_unstable_by_key(|endpoint| (endpoint.offset, !endpoint.is_start));
-        }
-
-        BufferChunks::new(self.text.as_rope(), range, syntax, diagnostic_endpoints)
-    }
-
-    pub fn for_each_line(&self, range: Range<Point>, mut callback: impl FnMut(u32, &str)) {
-        let mut line = String::new();
-        let mut row = range.start.row;
-        for chunk in self
-            .as_rope()
-            .chunks_in_range(range.to_offset(self))
-            .chain(["\n"])
-        {
-            for (newline_ix, text) in chunk.split('\n').enumerate() {
-                if newline_ix > 0 {
-                    callback(row, &line);
-                    row += 1;
-                    line.clear();
-                }
-                line.push_str(text);
-            }
-        }
-    }
-
-    pub fn syntax_layers(&self) -> impl Iterator<Item = SyntaxLayerInfo> + '_ {
-        self.syntax.layers_for_range(0..self.len(), &self.text)
-    }
-
-    pub fn syntax_layer_at<D: ToOffset>(&self, position: D) -> Option<SyntaxLayerInfo> {
-        let offset = position.to_offset(self);
-        self.syntax
-            .layers_for_range(offset..offset, &self.text)
-            .filter(|l| l.node().end_byte() > offset)
-            .last()
-    }
-
-    pub fn language_at<D: ToOffset>(&self, position: D) -> Option<&Arc<Language>> {
-        self.syntax_layer_at(position)
-            .map(|info| info.language)
-            .or(self.language.as_ref())
-    }
-
-    pub fn settings_at<'a, D: ToOffset>(
-        &self,
-        position: D,
-        cx: &'a AppContext,
-    ) -> &'a LanguageSettings {
-        language_settings(self.language_at(position), self.file.as_ref(), cx)
-    }
-
-    pub fn language_scope_at<D: ToOffset>(&self, position: D) -> Option<LanguageScope> {
-        let offset = position.to_offset(self);
-        let mut scope = None;
-        let mut smallest_range: Option<Range<usize>> = None;
-
-        // Use the layer that has the smallest node intersecting the given point.
-        for layer in self.syntax.layers_for_range(offset..offset, &self.text) {
-            let mut cursor = layer.node().walk();
-
-            let mut range = None;
-            loop {
-                let child_range = cursor.node().byte_range();
-                if !child_range.to_inclusive().contains(&offset) {
-                    break;
-                }
-
-                range = Some(child_range);
-                if cursor.goto_first_child_for_byte(offset).is_none() {
-                    break;
-                }
-            }
-
-            if let Some(range) = range {
-                if smallest_range
-                    .as_ref()
-                    .map_or(true, |smallest_range| range.len() < smallest_range.len())
-                {
-                    smallest_range = Some(range);
-                    scope = Some(LanguageScope {
-                        language: layer.language.clone(),
-                        override_id: layer.override_id(offset, &self.text),
-                    });
-                }
-            }
-        }
-
-        scope.or_else(|| {
-            self.language.clone().map(|language| LanguageScope {
-                language,
-                override_id: None,
-            })
-        })
-    }
-
-    pub fn surrounding_word<T: ToOffset>(&self, start: T) -> (Range<usize>, Option<CharKind>) {
-        let mut start = start.to_offset(self);
-        let mut end = start;
-        let mut next_chars = self.chars_at(start).peekable();
-        let mut prev_chars = self.reversed_chars_at(start).peekable();
-
-        let scope = self.language_scope_at(start);
-        let kind = |c| char_kind(&scope, c);
-        let word_kind = cmp::max(
-            prev_chars.peek().copied().map(kind),
-            next_chars.peek().copied().map(kind),
-        );
-
-        for ch in prev_chars {
-            if Some(kind(ch)) == word_kind && ch != '\n' {
-                start -= ch.len_utf8();
-            } else {
-                break;
-            }
-        }
-
-        for ch in next_chars {
-            if Some(kind(ch)) == word_kind && ch != '\n' {
-                end += ch.len_utf8();
-            } else {
-                break;
-            }
-        }
-
-        (start..end, word_kind)
-    }
-
-    pub fn range_for_syntax_ancestor<T: ToOffset>(&self, range: Range<T>) -> Option<Range<usize>> {
-        let range = range.start.to_offset(self)..range.end.to_offset(self);
-        let mut result: Option<Range<usize>> = None;
-        'outer: for layer in self.syntax.layers_for_range(range.clone(), &self.text) {
-            let mut cursor = layer.node().walk();
-
-            // Descend to the first leaf that touches the start of the range,
-            // and if the range is non-empty, extends beyond the start.
-            while cursor.goto_first_child_for_byte(range.start).is_some() {
-                if !range.is_empty() && cursor.node().end_byte() == range.start {
-                    cursor.goto_next_sibling();
-                }
-            }
-
-            // Ascend to the smallest ancestor that strictly contains the range.
-            loop {
-                let node_range = cursor.node().byte_range();
-                if node_range.start <= range.start
-                    && node_range.end >= range.end
-                    && node_range.len() > range.len()
-                {
-                    break;
-                }
-                if !cursor.goto_parent() {
-                    continue 'outer;
-                }
-            }
-
-            let left_node = cursor.node();
-            let mut layer_result = left_node.byte_range();
-
-            // For an empty range, try to find another node immediately to the right of the range.
-            if left_node.end_byte() == range.start {
-                let mut right_node = None;
-                while !cursor.goto_next_sibling() {
-                    if !cursor.goto_parent() {
-                        break;
-                    }
-                }
-
-                while cursor.node().start_byte() == range.start {
-                    right_node = Some(cursor.node());
-                    if !cursor.goto_first_child() {
-                        break;
-                    }
-                }
-
-                // If there is a candidate node on both sides of the (empty) range, then
-                // decide between the two by favoring a named node over an anonymous token.
-                // If both nodes are the same in that regard, favor the right one.
-                if let Some(right_node) = right_node {
-                    if right_node.is_named() || !left_node.is_named() {
-                        layer_result = right_node.byte_range();
-                    }
-                }
-            }
-
-            if let Some(previous_result) = &result {
-                if previous_result.len() < layer_result.len() {
-                    continue;
-                }
-            }
-            result = Some(layer_result);
-        }
-
-        result
-    }
-
-    pub fn outline(&self, theme: Option<&SyntaxTheme>) -> Option<Outline<Anchor>> {
-        self.outline_items_containing(0..self.len(), true, theme)
-            .map(Outline::new)
-    }
-
-    pub fn symbols_containing<T: ToOffset>(
-        &self,
-        position: T,
-        theme: Option<&SyntaxTheme>,
-    ) -> Option<Vec<OutlineItem<Anchor>>> {
-        let position = position.to_offset(self);
-        let mut items = self.outline_items_containing(
-            position.saturating_sub(1)..self.len().min(position + 1),
-            false,
-            theme,
-        )?;
-        let mut prev_depth = None;
-        items.retain(|item| {
-            let result = prev_depth.map_or(true, |prev_depth| item.depth > prev_depth);
-            prev_depth = Some(item.depth);
-            result
-        });
-        Some(items)
-    }
-
-    fn outline_items_containing(
-        &self,
-        range: Range<usize>,
-        include_extra_context: bool,
-        theme: Option<&SyntaxTheme>,
-    ) -> Option<Vec<OutlineItem<Anchor>>> {
-        let mut matches = self.syntax.matches(range.clone(), &self.text, |grammar| {
-            grammar.outline_config.as_ref().map(|c| &c.query)
-        });
-        let configs = matches
-            .grammars()
-            .iter()
-            .map(|g| g.outline_config.as_ref().unwrap())
-            .collect::<Vec<_>>();
-
-        let mut stack = Vec::<Range<usize>>::new();
-        let mut items = Vec::new();
-        while let Some(mat) = matches.peek() {
-            let config = &configs[mat.grammar_index];
-            let item_node = mat.captures.iter().find_map(|cap| {
-                if cap.index == config.item_capture_ix {
-                    Some(cap.node)
-                } else {
-                    None
-                }
-            })?;
-
-            let item_range = item_node.byte_range();
-            if item_range.end < range.start || item_range.start > range.end {
-                matches.advance();
-                continue;
-            }
-
-            let mut buffer_ranges = Vec::new();
-            for capture in mat.captures {
-                let node_is_name;
-                if capture.index == config.name_capture_ix {
-                    node_is_name = true;
-                } else if Some(capture.index) == config.context_capture_ix
-                    || (Some(capture.index) == config.extra_context_capture_ix
-                        && include_extra_context)
-                {
-                    node_is_name = false;
-                } else {
-                    continue;
-                }
-
-                let mut range = capture.node.start_byte()..capture.node.end_byte();
-                let start = capture.node.start_position();
-                if capture.node.end_position().row > start.row {
-                    range.end =
-                        range.start + self.line_len(start.row as u32) as usize - start.column;
-                }
-
-                buffer_ranges.push((range, node_is_name));
-            }
-
-            if buffer_ranges.is_empty() {
-                continue;
-            }
-
-            let mut text = String::new();
-            let mut highlight_ranges = Vec::new();
-            let mut name_ranges = Vec::new();
-            let mut chunks = self.chunks(
-                buffer_ranges.first().unwrap().0.start..buffer_ranges.last().unwrap().0.end,
-                true,
-            );
-            let mut last_buffer_range_end = 0;
-            for (buffer_range, is_name) in buffer_ranges {
-                if !text.is_empty() && buffer_range.start > last_buffer_range_end {
-                    text.push(' ');
-                }
-                last_buffer_range_end = buffer_range.end;
-                if is_name {
-                    let mut start = text.len();
-                    let end = start + buffer_range.len();
-
-                    // When multiple names are captured, then the matcheable text
-                    // includes the whitespace in between the names.
-                    if !name_ranges.is_empty() {
-                        start -= 1;
-                    }
-
-                    name_ranges.push(start..end);
-                }
-
-                let mut offset = buffer_range.start;
-                chunks.seek(offset);
-                for mut chunk in chunks.by_ref() {
-                    if chunk.text.len() > buffer_range.end - offset {
-                        chunk.text = &chunk.text[0..(buffer_range.end - offset)];
-                        offset = buffer_range.end;
-                    } else {
-                        offset += chunk.text.len();
-                    }
-                    let style = chunk
-                        .syntax_highlight_id
-                        .zip(theme)
-                        .and_then(|(highlight, theme)| highlight.style(theme));
-                    if let Some(style) = style {
-                        let start = text.len();
-                        let end = start + chunk.text.len();
-                        highlight_ranges.push((start..end, style));
-                    }
-                    text.push_str(chunk.text);
-                    if offset >= buffer_range.end {
-                        break;
-                    }
-                }
-            }
-
-            matches.advance();
-            while stack.last().map_or(false, |prev_range| {
-                prev_range.start > item_range.start || prev_range.end < item_range.end
-            }) {
-                stack.pop();
-            }
-            stack.push(item_range.clone());
-
-            items.push(OutlineItem {
-                depth: stack.len() - 1,
-                range: self.anchor_after(item_range.start)..self.anchor_before(item_range.end),
-                text,
-                highlight_ranges,
-                name_ranges,
-            })
-        }
-        Some(items)
-    }
-
-    pub fn matches(
-        &self,
-        range: Range<usize>,
-        query: fn(&Grammar) -> Option<&tree_sitter::Query>,
-    ) -> SyntaxMapMatches {
-        self.syntax.matches(range, self, query)
-    }
-
-    /// Returns bracket range pairs overlapping or adjacent to `range`
-    pub fn bracket_ranges<'a, T: ToOffset>(
-        &'a self,
-        range: Range<T>,
-    ) -> impl Iterator<Item = (Range<usize>, Range<usize>)> + 'a {
-        // Find bracket pairs that *inclusively* contain the given range.
-        let range = range.start.to_offset(self).saturating_sub(1)
-            ..self.len().min(range.end.to_offset(self) + 1);
-
-        let mut matches = self.syntax.matches(range.clone(), &self.text, |grammar| {
-            grammar.brackets_config.as_ref().map(|c| &c.query)
-        });
-        let configs = matches
-            .grammars()
-            .iter()
-            .map(|grammar| grammar.brackets_config.as_ref().unwrap())
-            .collect::<Vec<_>>();
-
-        iter::from_fn(move || {
-            while let Some(mat) = matches.peek() {
-                let mut open = None;
-                let mut close = None;
-                let config = &configs[mat.grammar_index];
-                for capture in mat.captures {
-                    if capture.index == config.open_capture_ix {
-                        open = Some(capture.node.byte_range());
-                    } else if capture.index == config.close_capture_ix {
-                        close = Some(capture.node.byte_range());
-                    }
-                }
-
-                matches.advance();
-
-                let Some((open, close)) = open.zip(close) else {
-                    continue;
-                };
-
-                let bracket_range = open.start..=close.end;
-                if !bracket_range.overlaps(&range) {
-                    continue;
-                }
-
-                return Some((open, close));
-            }
-            None
-        })
-    }
-
-    #[allow(clippy::type_complexity)]
-    pub fn remote_selections_in_range(
-        &self,
-        range: Range<Anchor>,
-    ) -> impl Iterator<
-        Item = (
-            ReplicaId,
-            bool,
-            CursorShape,
-            impl Iterator<Item = &Selection<Anchor>> + '_,
-        ),
-    > + '_ {
-        self.remote_selections
-            .iter()
-            .filter(|(replica_id, set)| {
-                **replica_id != self.text.replica_id() && !set.selections.is_empty()
-            })
-            .map(move |(replica_id, set)| {
-                let start_ix = match set.selections.binary_search_by(|probe| {
-                    probe.end.cmp(&range.start, self).then(Ordering::Greater)
-                }) {
-                    Ok(ix) | Err(ix) => ix,
-                };
-                let end_ix = match set.selections.binary_search_by(|probe| {
-                    probe.start.cmp(&range.end, self).then(Ordering::Less)
-                }) {
-                    Ok(ix) | Err(ix) => ix,
-                };
-
-                (
-                    *replica_id,
-                    set.line_mode,
-                    set.cursor_shape,
-                    set.selections[start_ix..end_ix].iter(),
-                )
-            })
-    }
-
-    pub fn git_diff_hunks_in_row_range<'a>(
-        &'a self,
-        range: Range<u32>,
-    ) -> impl 'a + Iterator<Item = git::diff::DiffHunk<u32>> {
-        self.git_diff.hunks_in_row_range(range, self)
-    }
-
-    pub fn git_diff_hunks_intersecting_range<'a>(
-        &'a self,
-        range: Range<Anchor>,
-    ) -> impl 'a + Iterator<Item = git::diff::DiffHunk<u32>> {
-        self.git_diff.hunks_intersecting_range(range, self)
-    }
-
-    pub fn git_diff_hunks_intersecting_range_rev<'a>(
-        &'a self,
-        range: Range<Anchor>,
-    ) -> impl 'a + Iterator<Item = git::diff::DiffHunk<u32>> {
-        self.git_diff.hunks_intersecting_range_rev(range, self)
-    }
-
-    pub fn diagnostics_in_range<'a, T, O>(
-        &'a self,
-        search_range: Range<T>,
-        reversed: bool,
-    ) -> impl 'a + Iterator<Item = DiagnosticEntry<O>>
-    where
-        T: 'a + Clone + ToOffset,
-        O: 'a + FromAnchor + Ord,
-    {
-        let mut iterators: Vec<_> = self
-            .diagnostics
-            .iter()
-            .map(|(_, collection)| {
-                collection
-                    .range::<T, O>(search_range.clone(), self, true, reversed)
-                    .peekable()
-            })
-            .collect();
-
-        std::iter::from_fn(move || {
-            let (next_ix, _) = iterators
-                .iter_mut()
-                .enumerate()
-                .flat_map(|(ix, iter)| Some((ix, iter.peek()?)))
-                .min_by(|(_, a), (_, b)| a.range.start.cmp(&b.range.start))?;
-            iterators[next_ix].next()
-        })
-    }
-
-    pub fn diagnostic_groups(
-        &self,
-        language_server_id: Option<LanguageServerId>,
-    ) -> Vec<(LanguageServerId, DiagnosticGroup<Anchor>)> {
-        let mut groups = Vec::new();
-
-        if let Some(language_server_id) = language_server_id {
-            if let Ok(ix) = self
-                .diagnostics
-                .binary_search_by_key(&language_server_id, |e| e.0)
-            {
-                self.diagnostics[ix]
-                    .1
-                    .groups(language_server_id, &mut groups, self);
-            }
-        } else {
-            for (language_server_id, diagnostics) in self.diagnostics.iter() {
-                diagnostics.groups(*language_server_id, &mut groups, self);
-            }
-        }
-
-        groups.sort_by(|(id_a, group_a), (id_b, group_b)| {
-            let a_start = &group_a.entries[group_a.primary_ix].range.start;
-            let b_start = &group_b.entries[group_b.primary_ix].range.start;
-            a_start.cmp(b_start, self).then_with(|| id_a.cmp(&id_b))
-        });
-
-        groups
-    }
-
-    pub fn diagnostic_group<'a, O>(
-        &'a self,
-        group_id: usize,
-    ) -> impl 'a + Iterator<Item = DiagnosticEntry<O>>
-    where
-        O: 'a + FromAnchor,
-    {
-        self.diagnostics
-            .iter()
-            .flat_map(move |(_, set)| set.group(group_id, self))
-    }
-
-    pub fn diagnostics_update_count(&self) -> usize {
-        self.diagnostics_update_count
-    }
-
-    pub fn parse_count(&self) -> usize {
-        self.parse_count
-    }
-
-    pub fn selections_update_count(&self) -> usize {
-        self.selections_update_count
-    }
-
-    pub fn file(&self) -> Option<&Arc<dyn File>> {
-        self.file.as_ref()
-    }
-
-    pub fn resolve_file_path(&self, cx: &AppContext, include_root: bool) -> Option<PathBuf> {
-        if let Some(file) = self.file() {
-            if file.path().file_name().is_none() || include_root {
-                Some(file.full_path(cx))
-            } else {
-                Some(file.path().to_path_buf())
-            }
-        } else {
-            None
-        }
-    }
-
-    pub fn file_update_count(&self) -> usize {
-        self.file_update_count
-    }
-
-    pub fn git_diff_update_count(&self) -> usize {
-        self.git_diff_update_count
-    }
-}
-
-fn indent_size_for_line(text: &text::BufferSnapshot, row: u32) -> IndentSize {
-    indent_size_for_text(text.chars_at(Point::new(row, 0)))
-}
-
-pub fn indent_size_for_text(text: impl Iterator<Item = char>) -> IndentSize {
-    let mut result = IndentSize::spaces(0);
-    for c in text {
-        let kind = match c {
-            ' ' => IndentKind::Space,
-            '\t' => IndentKind::Tab,
-            _ => break,
-        };
-        if result.len == 0 {
-            result.kind = kind;
-        }
-        result.len += 1;
-    }
-    result
-}
-
-impl Clone for BufferSnapshot {
-    fn clone(&self) -> Self {
-        Self {
-            text: self.text.clone(),
-            git_diff: self.git_diff.clone(),
-            syntax: self.syntax.clone(),
-            file: self.file.clone(),
-            remote_selections: self.remote_selections.clone(),
-            diagnostics: self.diagnostics.clone(),
-            selections_update_count: self.selections_update_count,
-            diagnostics_update_count: self.diagnostics_update_count,
-            file_update_count: self.file_update_count,
-            git_diff_update_count: self.git_diff_update_count,
-            language: self.language.clone(),
-            parse_count: self.parse_count,
-        }
-    }
-}
-
-impl Deref for BufferSnapshot {
-    type Target = text::BufferSnapshot;
-
-    fn deref(&self) -> &Self::Target {
-        &self.text
-    }
-}
-
-unsafe impl<'a> Send for BufferChunks<'a> {}
-
-impl<'a> BufferChunks<'a> {
-    pub(crate) fn new(
-        text: &'a Rope,
-        range: Range<usize>,
-        syntax: Option<(SyntaxMapCaptures<'a>, Vec<HighlightMap>)>,
-        diagnostic_endpoints: Vec<DiagnosticEndpoint>,
-    ) -> Self {
-        let mut highlights = None;
-        if let Some((captures, highlight_maps)) = syntax {
-            highlights = Some(BufferChunkHighlights {
-                captures,
-                next_capture: None,
-                stack: Default::default(),
-                highlight_maps,
-            })
-        }
-
-        let diagnostic_endpoints = diagnostic_endpoints.into_iter().peekable();
-        let chunks = text.chunks_in_range(range.clone());
-
-        BufferChunks {
-            range,
-            chunks,
-            diagnostic_endpoints,
-            error_depth: 0,
-            warning_depth: 0,
-            information_depth: 0,
-            hint_depth: 0,
-            unnecessary_depth: 0,
-            highlights,
-        }
-    }
-
-    pub fn seek(&mut self, offset: usize) {
-        self.range.start = offset;
-        self.chunks.seek(self.range.start);
-        if let Some(highlights) = self.highlights.as_mut() {
-            highlights
-                .stack
-                .retain(|(end_offset, _)| *end_offset > offset);
-            if let Some(capture) = &highlights.next_capture {
-                if offset >= capture.node.start_byte() {
-                    let next_capture_end = capture.node.end_byte();
-                    if offset < next_capture_end {
-                        highlights.stack.push((
-                            next_capture_end,
-                            highlights.highlight_maps[capture.grammar_index].get(capture.index),
-                        ));
-                    }
-                    highlights.next_capture.take();
-                }
-            }
-            highlights.captures.set_byte_range(self.range.clone());
-        }
-    }
-
-    pub fn offset(&self) -> usize {
-        self.range.start
-    }
-
-    fn update_diagnostic_depths(&mut self, endpoint: DiagnosticEndpoint) {
-        let depth = match endpoint.severity {
-            DiagnosticSeverity::ERROR => &mut self.error_depth,
-            DiagnosticSeverity::WARNING => &mut self.warning_depth,
-            DiagnosticSeverity::INFORMATION => &mut self.information_depth,
-            DiagnosticSeverity::HINT => &mut self.hint_depth,
-            _ => return,
-        };
-        if endpoint.is_start {
-            *depth += 1;
-        } else {
-            *depth -= 1;
-        }
-
-        if endpoint.is_unnecessary {
-            if endpoint.is_start {
-                self.unnecessary_depth += 1;
-            } else {
-                self.unnecessary_depth -= 1;
-            }
-        }
-    }
-
-    fn current_diagnostic_severity(&self) -> Option<DiagnosticSeverity> {
-        if self.error_depth > 0 {
-            Some(DiagnosticSeverity::ERROR)
-        } else if self.warning_depth > 0 {
-            Some(DiagnosticSeverity::WARNING)
-        } else if self.information_depth > 0 {
-            Some(DiagnosticSeverity::INFORMATION)
-        } else if self.hint_depth > 0 {
-            Some(DiagnosticSeverity::HINT)
-        } else {
-            None
-        }
-    }
-
-    fn current_code_is_unnecessary(&self) -> bool {
-        self.unnecessary_depth > 0
-    }
-}
-
-impl<'a> Iterator for BufferChunks<'a> {
-    type Item = Chunk<'a>;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        let mut next_capture_start = usize::MAX;
-        let mut next_diagnostic_endpoint = usize::MAX;
-
-        if let Some(highlights) = self.highlights.as_mut() {
-            while let Some((parent_capture_end, _)) = highlights.stack.last() {
-                if *parent_capture_end <= self.range.start {
-                    highlights.stack.pop();
-                } else {
-                    break;
-                }
-            }
-
-            if highlights.next_capture.is_none() {
-                highlights.next_capture = highlights.captures.next();
-            }
-
-            while let Some(capture) = highlights.next_capture.as_ref() {
-                if self.range.start < capture.node.start_byte() {
-                    next_capture_start = capture.node.start_byte();
-                    break;
-                } else {
-                    let highlight_id =
-                        highlights.highlight_maps[capture.grammar_index].get(capture.index);
-                    highlights
-                        .stack
-                        .push((capture.node.end_byte(), highlight_id));
-                    highlights.next_capture = highlights.captures.next();
-                }
-            }
-        }
-
-        while let Some(endpoint) = self.diagnostic_endpoints.peek().copied() {
-            if endpoint.offset <= self.range.start {
-                self.update_diagnostic_depths(endpoint);
-                self.diagnostic_endpoints.next();
-            } else {
-                next_diagnostic_endpoint = endpoint.offset;
-                break;
-            }
-        }
-
-        if let Some(chunk) = self.chunks.peek() {
-            let chunk_start = self.range.start;
-            let mut chunk_end = (self.chunks.offset() + chunk.len())
-                .min(next_capture_start)
-                .min(next_diagnostic_endpoint);
-            let mut highlight_id = None;
-            if let Some(highlights) = self.highlights.as_ref() {
-                if let Some((parent_capture_end, parent_highlight_id)) = highlights.stack.last() {
-                    chunk_end = chunk_end.min(*parent_capture_end);
-                    highlight_id = Some(*parent_highlight_id);
-                }
-            }
-
-            let slice =
-                &chunk[chunk_start - self.chunks.offset()..chunk_end - self.chunks.offset()];
-            self.range.start = chunk_end;
-            if self.range.start == self.chunks.offset() + chunk.len() {
-                self.chunks.next().unwrap();
-            }
-
-            Some(Chunk {
-                text: slice,
-                syntax_highlight_id: highlight_id,
-                diagnostic_severity: self.current_diagnostic_severity(),
-                is_unnecessary: self.current_code_is_unnecessary(),
-                ..Default::default()
-            })
-        } else {
-            None
-        }
-    }
-}
-
-impl operation_queue::Operation for Operation {
-    fn lamport_timestamp(&self) -> clock::Lamport {
-        match self {
-            Operation::Buffer(_) => {
-                unreachable!("buffer operations should never be deferred at this layer")
-            }
-            Operation::UpdateDiagnostics {
-                lamport_timestamp, ..
-            }
-            | Operation::UpdateSelections {
-                lamport_timestamp, ..
-            }
-            | Operation::UpdateCompletionTriggers {
-                lamport_timestamp, ..
-            } => *lamport_timestamp,
-        }
-    }
-}
-
-impl Default for Diagnostic {
-    fn default() -> Self {
-        Self {
-            source: Default::default(),
-            code: None,
-            severity: DiagnosticSeverity::ERROR,
-            message: Default::default(),
-            group_id: 0,
-            is_primary: false,
-            is_valid: true,
-            is_disk_based: false,
-            is_unnecessary: false,
-        }
-    }
-}
-
-impl IndentSize {
-    pub fn spaces(len: u32) -> Self {
-        Self {
-            len,
-            kind: IndentKind::Space,
-        }
-    }
-
-    pub fn tab() -> Self {
-        Self {
-            len: 1,
-            kind: IndentKind::Tab,
-        }
-    }
-
-    pub fn chars(&self) -> impl Iterator<Item = char> {
-        iter::repeat(self.char()).take(self.len as usize)
-    }
-
-    pub fn char(&self) -> char {
-        match self.kind {
-            IndentKind::Space => ' ',
-            IndentKind::Tab => '\t',
-        }
-    }
-
-    pub fn with_delta(mut self, direction: Ordering, size: IndentSize) -> Self {
-        match direction {
-            Ordering::Less => {
-                if self.kind == size.kind && self.len >= size.len {
-                    self.len -= size.len;
-                }
-            }
-            Ordering::Equal => {}
-            Ordering::Greater => {
-                if self.len == 0 {
-                    self = size;
-                } else if self.kind == size.kind {
-                    self.len += size.len;
-                }
-            }
-        }
-        self
-    }
-}
-
-impl Completion {
-    pub fn sort_key(&self) -> (usize, &str) {
-        let kind_key = match self.lsp_completion.kind {
-            Some(lsp::CompletionItemKind::VARIABLE) => 0,
-            _ => 1,
-        };
-        (kind_key, &self.label.text[self.label.filter_range.clone()])
-    }
-
-    pub fn is_snippet(&self) -> bool {
-        self.lsp_completion.insert_text_format == Some(lsp::InsertTextFormat::SNIPPET)
-    }
-}
-
-pub fn contiguous_ranges(
-    values: impl Iterator<Item = u32>,
-    max_len: usize,
-) -> impl Iterator<Item = Range<u32>> {
-    let mut values = values;
-    let mut current_range: Option<Range<u32>> = None;
-    std::iter::from_fn(move || loop {
-        if let Some(value) = values.next() {
-            if let Some(range) = &mut current_range {
-                if value == range.end && range.len() < max_len {
-                    range.end += 1;
-                    continue;
-                }
-            }
-
-            let prev_range = current_range.clone();
-            current_range = Some(value..(value + 1));
-            if prev_range.is_some() {
-                return prev_range;
-            }
-        } else {
-            return current_range.take();
-        }
-    })
-}
-
-pub fn char_kind(scope: &Option<LanguageScope>, c: char) -> CharKind {
-    if c.is_whitespace() {
-        return CharKind::Whitespace;
-    } else if c.is_alphanumeric() || c == '_' {
-        return CharKind::Word;
-    }
-
-    if let Some(scope) = scope {
-        if let Some(characters) = scope.word_characters() {
-            if characters.contains(&c) {
-                return CharKind::Word;
-            }
-        }
-    }
-
-    CharKind::Punctuation
-}
-
-/// Find all of the ranges of whitespace that occur at the ends of lines
-/// in the given rope.
-///
-/// This could also be done with a regex search, but this implementation
-/// avoids copying text.
-pub fn trailing_whitespace_ranges(rope: &Rope) -> Vec<Range<usize>> {
-    let mut ranges = Vec::new();
-
-    let mut offset = 0;
-    let mut prev_chunk_trailing_whitespace_range = 0..0;
-    for chunk in rope.chunks() {
-        let mut prev_line_trailing_whitespace_range = 0..0;
-        for (i, line) in chunk.split('\n').enumerate() {
-            let line_end_offset = offset + line.len();
-            let trimmed_line_len = line.trim_end_matches(|c| matches!(c, ' ' | '\t')).len();
-            let mut trailing_whitespace_range = (offset + trimmed_line_len)..line_end_offset;
-
-            if i == 0 && trimmed_line_len == 0 {
-                trailing_whitespace_range.start = prev_chunk_trailing_whitespace_range.start;
-            }
-            if !prev_line_trailing_whitespace_range.is_empty() {
-                ranges.push(prev_line_trailing_whitespace_range);
-            }
-
-            offset = line_end_offset + 1;
-            prev_line_trailing_whitespace_range = trailing_whitespace_range;
-        }
-
-        offset -= 1;
-        prev_chunk_trailing_whitespace_range = prev_line_trailing_whitespace_range;
-    }
-
-    if !prev_chunk_trailing_whitespace_range.is_empty() {
-        ranges.push(prev_chunk_trailing_whitespace_range);
-    }
-
-    ranges
-}

crates/language2/src/buffer_tests.rs 🔗

@@ -1,2493 +0,0 @@
-use super::*;
-use crate::language_settings::{
-    AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent,
-};
-use crate::Buffer;
-use clock::ReplicaId;
-use collections::BTreeMap;
-use gpui::{AppContext, Model};
-use gpui::{Context, TestAppContext};
-use indoc::indoc;
-use proto::deserialize_operation;
-use rand::prelude::*;
-use regex::RegexBuilder;
-use settings::SettingsStore;
-use std::{
-    env,
-    ops::Range,
-    time::{Duration, Instant},
-};
-use text::network::Network;
-use text::LineEnding;
-use text::{Point, ToPoint};
-use unindent::Unindent as _;
-use util::{assert_set_eq, post_inc, test::marked_text_ranges, RandomCharIter};
-
-lazy_static! {
-    static ref TRAILING_WHITESPACE_REGEX: Regex = RegexBuilder::new("[ \t]+$")
-        .multi_line(true)
-        .build()
-        .unwrap();
-}
-
-#[cfg(test)]
-#[ctor::ctor]
-fn init_logger() {
-    if std::env::var("RUST_LOG").is_ok() {
-        env_logger::init();
-    }
-}
-
-#[gpui::test]
-fn test_line_endings(cx: &mut gpui::AppContext) {
-    init_settings(cx, |_| {});
-
-    cx.new_model(|cx| {
-        let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), "one\r\ntwo\rthree")
-            .with_language(Arc::new(rust_lang()), cx);
-        assert_eq!(buffer.text(), "one\ntwo\nthree");
-        assert_eq!(buffer.line_ending(), LineEnding::Windows);
-
-        buffer.check_invariants();
-        buffer.edit(
-            [(buffer.len()..buffer.len(), "\r\nfour")],
-            Some(AutoindentMode::EachLine),
-            cx,
-        );
-        buffer.edit([(0..0, "zero\r\n")], None, cx);
-        assert_eq!(buffer.text(), "zero\none\ntwo\nthree\nfour");
-        assert_eq!(buffer.line_ending(), LineEnding::Windows);
-        buffer.check_invariants();
-
-        buffer
-    });
-}
-
-#[gpui::test]
-fn test_select_language() {
-    let registry = Arc::new(LanguageRegistry::test());
-    registry.add(Arc::new(Language::new(
-        LanguageConfig {
-            name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
-            ..Default::default()
-        },
-        Some(tree_sitter_rust::language()),
-    )));
-    registry.add(Arc::new(Language::new(
-        LanguageConfig {
-            name: "Make".into(),
-            path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
-            ..Default::default()
-        },
-        Some(tree_sitter_rust::language()),
-    )));
-
-    // matching file extension
-    assert_eq!(
-        registry
-            .language_for_file("zed/lib.rs", None)
-            .now_or_never()
-            .and_then(|l| Some(l.ok()?.name())),
-        Some("Rust".into())
-    );
-    assert_eq!(
-        registry
-            .language_for_file("zed/lib.mk", None)
-            .now_or_never()
-            .and_then(|l| Some(l.ok()?.name())),
-        Some("Make".into())
-    );
-
-    // matching filename
-    assert_eq!(
-        registry
-            .language_for_file("zed/Makefile", None)
-            .now_or_never()
-            .and_then(|l| Some(l.ok()?.name())),
-        Some("Make".into())
-    );
-
-    // matching suffix that is not the full file extension or filename
-    assert_eq!(
-        registry
-            .language_for_file("zed/cars", None)
-            .now_or_never()
-            .and_then(|l| Some(l.ok()?.name())),
-        None
-    );
-    assert_eq!(
-        registry
-            .language_for_file("zed/a.cars", None)
-            .now_or_never()
-            .and_then(|l| Some(l.ok()?.name())),
-        None
-    );
-    assert_eq!(
-        registry
-            .language_for_file("zed/sumk", None)
-            .now_or_never()
-            .and_then(|l| Some(l.ok()?.name())),
-        None
-    );
-}
-
-#[gpui::test]
-fn test_edit_events(cx: &mut gpui::AppContext) {
-    let mut now = Instant::now();
-    let buffer_1_events = Arc::new(Mutex::new(Vec::new()));
-    let buffer_2_events = Arc::new(Mutex::new(Vec::new()));
-
-    let buffer1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abcdef"));
-    let buffer2 = cx.new_model(|cx| Buffer::new(1, cx.entity_id().as_u64(), "abcdef"));
-    let buffer1_ops = Arc::new(Mutex::new(Vec::new()));
-    buffer1.update(cx, {
-        let buffer1_ops = buffer1_ops.clone();
-        |buffer, cx| {
-            let buffer_1_events = buffer_1_events.clone();
-            cx.subscribe(&buffer1, move |_, _, event, _| match event.clone() {
-                Event::Operation(op) => buffer1_ops.lock().push(op),
-                event => buffer_1_events.lock().push(event),
-            })
-            .detach();
-            let buffer_2_events = buffer_2_events.clone();
-            cx.subscribe(&buffer2, move |_, _, event, _| {
-                buffer_2_events.lock().push(event.clone())
-            })
-            .detach();
-
-            // An edit emits an edited event, followed by a dirty changed event,
-            // since the buffer was previously in a clean state.
-            buffer.edit([(2..4, "XYZ")], None, cx);
-
-            // An empty transaction does not emit any events.
-            buffer.start_transaction();
-            buffer.end_transaction(cx);
-
-            // A transaction containing two edits emits one edited event.
-            now += Duration::from_secs(1);
-            buffer.start_transaction_at(now);
-            buffer.edit([(5..5, "u")], None, cx);
-            buffer.edit([(6..6, "w")], None, cx);
-            buffer.end_transaction_at(now, cx);
-
-            // Undoing a transaction emits one edited event.
-            buffer.undo(cx);
-        }
-    });
-
-    // Incorporating a set of remote ops emits a single edited event,
-    // followed by a dirty changed event.
-    buffer2.update(cx, |buffer, cx| {
-        buffer.apply_ops(buffer1_ops.lock().drain(..), cx).unwrap();
-    });
-    assert_eq!(
-        mem::take(&mut *buffer_1_events.lock()),
-        vec![
-            Event::Edited,
-            Event::DirtyChanged,
-            Event::Edited,
-            Event::Edited,
-        ]
-    );
-    assert_eq!(
-        mem::take(&mut *buffer_2_events.lock()),
-        vec![Event::Edited, Event::DirtyChanged]
-    );
-
-    buffer1.update(cx, |buffer, cx| {
-        // Undoing the first transaction emits edited event, followed by a
-        // dirty changed event, since the buffer is again in a clean state.
-        buffer.undo(cx);
-    });
-    // Incorporating the remote ops again emits a single edited event,
-    // followed by a dirty changed event.
-    buffer2.update(cx, |buffer, cx| {
-        buffer.apply_ops(buffer1_ops.lock().drain(..), cx).unwrap();
-    });
-    assert_eq!(
-        mem::take(&mut *buffer_1_events.lock()),
-        vec![Event::Edited, Event::DirtyChanged,]
-    );
-    assert_eq!(
-        mem::take(&mut *buffer_2_events.lock()),
-        vec![Event::Edited, Event::DirtyChanged]
-    );
-}
-
-#[gpui::test]
-async fn test_apply_diff(cx: &mut TestAppContext) {
-    let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n";
-    let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text));
-    let anchor = buffer.update(cx, |buffer, _| buffer.anchor_before(Point::new(3, 3)));
-
-    let text = "a\nccc\ndddd\nffffff\n";
-    let diff = buffer.update(cx, |b, cx| b.diff(text.into(), cx)).await;
-    buffer.update(cx, |buffer, cx| {
-        buffer.apply_diff(diff, cx).unwrap();
-        assert_eq!(buffer.text(), text);
-        assert_eq!(anchor.to_point(buffer), Point::new(2, 3));
-    });
-
-    let text = "a\n1\n\nccc\ndd2dd\nffffff\n";
-    let diff = buffer.update(cx, |b, cx| b.diff(text.into(), cx)).await;
-    buffer.update(cx, |buffer, cx| {
-        buffer.apply_diff(diff, cx).unwrap();
-        assert_eq!(buffer.text(), text);
-        assert_eq!(anchor.to_point(buffer), Point::new(4, 4));
-    });
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_normalize_whitespace(cx: &mut gpui::TestAppContext) {
-    let text = [
-        "zero",     //
-        "one  ",    // 2 trailing spaces
-        "two",      //
-        "three   ", // 3 trailing spaces
-        "four",     //
-        "five    ", // 4 trailing spaces
-    ]
-    .join("\n");
-
-    let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text));
-
-    // Spawn a task to format the buffer's whitespace.
-    // Pause so that the foratting task starts running.
-    let format = buffer.update(cx, |buffer, cx| buffer.remove_trailing_whitespace(cx));
-    smol::future::yield_now().await;
-
-    // Edit the buffer while the normalization task is running.
-    let version_before_edit = buffer.update(cx, |buffer, _| buffer.version());
-    buffer.update(cx, |buffer, cx| {
-        buffer.edit(
-            [
-                (Point::new(0, 1)..Point::new(0, 1), "EE"),
-                (Point::new(3, 5)..Point::new(3, 5), "EEE"),
-            ],
-            None,
-            cx,
-        );
-    });
-
-    let format_diff = format.await;
-    buffer.update(cx, |buffer, cx| {
-        let version_before_format = format_diff.base_version.clone();
-        buffer.apply_diff(format_diff, cx);
-
-        // The outcome depends on the order of concurrent taks.
-        //
-        // If the edit occurred while searching for trailing whitespace ranges,
-        // then the trailing whitespace region touched by the edit is left intact.
-        if version_before_format == version_before_edit {
-            assert_eq!(
-                buffer.text(),
-                [
-                    "zEEero",      //
-                    "one",         //
-                    "two",         //
-                    "threeEEE   ", //
-                    "four",        //
-                    "five",        //
-                ]
-                .join("\n")
-            );
-        }
-        // Otherwise, all trailing whitespace is removed.
-        else {
-            assert_eq!(
-                buffer.text(),
-                [
-                    "zEEero",   //
-                    "one",      //
-                    "two",      //
-                    "threeEEE", //
-                    "four",     //
-                    "five",     //
-                ]
-                .join("\n")
-            );
-        }
-    });
-}
-
-#[gpui::test]
-async fn test_reparse(cx: &mut gpui::TestAppContext) {
-    let text = "fn a() {}";
-    let buffer = cx.new_model(|cx| {
-        Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx)
-    });
-
-    // Wait for the initial text to parse
-    cx.executor().run_until_parked();
-    assert!(!buffer.update(cx, |buffer, _| buffer.is_parsing()));
-    assert_eq!(
-        get_tree_sexp(&buffer, cx),
-        concat!(
-            "(source_file (function_item name: (identifier) ",
-            "parameters: (parameters) ",
-            "body: (block)))"
-        )
-    );
-
-    buffer.update(cx, |buffer, _| {
-        buffer.set_sync_parse_timeout(Duration::ZERO)
-    });
-
-    // Perform some edits (add parameter and variable reference)
-    // Parsing doesn't begin until the transaction is complete
-    buffer.update(cx, |buf, cx| {
-        buf.start_transaction();
-
-        let offset = buf.text().find(')').unwrap();
-        buf.edit([(offset..offset, "b: C")], None, cx);
-        assert!(!buf.is_parsing());
-
-        let offset = buf.text().find('}').unwrap();
-        buf.edit([(offset..offset, " d; ")], None, cx);
-        assert!(!buf.is_parsing());
-
-        buf.end_transaction(cx);
-        assert_eq!(buf.text(), "fn a(b: C) { d; }");
-        assert!(buf.is_parsing());
-    });
-    cx.executor().run_until_parked();
-    assert!(!buffer.update(cx, |buffer, _| buffer.is_parsing()));
-    assert_eq!(
-        get_tree_sexp(&buffer, cx),
-        concat!(
-            "(source_file (function_item name: (identifier) ",
-            "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
-            "body: (block (expression_statement (identifier)))))"
-        )
-    );
-
-    // Perform a series of edits without waiting for the current parse to complete:
-    // * turn identifier into a field expression
-    // * turn field expression into a method call
-    // * add a turbofish to the method call
-    buffer.update(cx, |buf, cx| {
-        let offset = buf.text().find(';').unwrap();
-        buf.edit([(offset..offset, ".e")], None, cx);
-        assert_eq!(buf.text(), "fn a(b: C) { d.e; }");
-        assert!(buf.is_parsing());
-    });
-    buffer.update(cx, |buf, cx| {
-        let offset = buf.text().find(';').unwrap();
-        buf.edit([(offset..offset, "(f)")], None, cx);
-        assert_eq!(buf.text(), "fn a(b: C) { d.e(f); }");
-        assert!(buf.is_parsing());
-    });
-    buffer.update(cx, |buf, cx| {
-        let offset = buf.text().find("(f)").unwrap();
-        buf.edit([(offset..offset, "::<G>")], None, cx);
-        assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
-        assert!(buf.is_parsing());
-    });
-    cx.executor().run_until_parked();
-    assert_eq!(
-        get_tree_sexp(&buffer, cx),
-        concat!(
-            "(source_file (function_item name: (identifier) ",
-            "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
-            "body: (block (expression_statement (call_expression ",
-            "function: (generic_function ",
-            "function: (field_expression value: (identifier) field: (field_identifier)) ",
-            "type_arguments: (type_arguments (type_identifier))) ",
-            "arguments: (arguments (identifier)))))))",
-        )
-    );
-
-    buffer.update(cx, |buf, cx| {
-        buf.undo(cx);
-        buf.undo(cx);
-        buf.undo(cx);
-        buf.undo(cx);
-        assert_eq!(buf.text(), "fn a() {}");
-        assert!(buf.is_parsing());
-    });
-
-    cx.executor().run_until_parked();
-    assert_eq!(
-        get_tree_sexp(&buffer, cx),
-        concat!(
-            "(source_file (function_item name: (identifier) ",
-            "parameters: (parameters) ",
-            "body: (block)))"
-        )
-    );
-
-    buffer.update(cx, |buf, cx| {
-        buf.redo(cx);
-        buf.redo(cx);
-        buf.redo(cx);
-        buf.redo(cx);
-        assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
-        assert!(buf.is_parsing());
-    });
-    cx.executor().run_until_parked();
-    assert_eq!(
-        get_tree_sexp(&buffer, cx),
-        concat!(
-            "(source_file (function_item name: (identifier) ",
-            "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
-            "body: (block (expression_statement (call_expression ",
-            "function: (generic_function ",
-            "function: (field_expression value: (identifier) field: (field_identifier)) ",
-            "type_arguments: (type_arguments (type_identifier))) ",
-            "arguments: (arguments (identifier)))))))",
-        )
-    );
-}
-
-#[gpui::test]
-async fn test_resetting_language(cx: &mut gpui::TestAppContext) {
-    let buffer = cx.new_model(|cx| {
-        let mut buffer =
-            Buffer::new(0, cx.entity_id().as_u64(), "{}").with_language(Arc::new(rust_lang()), cx);
-        buffer.set_sync_parse_timeout(Duration::ZERO);
-        buffer
-    });
-
-    // Wait for the initial text to parse
-    cx.executor().run_until_parked();
-    assert_eq!(
-        get_tree_sexp(&buffer, cx),
-        "(source_file (expression_statement (block)))"
-    );
-
-    buffer.update(cx, |buffer, cx| {
-        buffer.set_language(Some(Arc::new(json_lang())), cx)
-    });
-    cx.executor().run_until_parked();
-    assert_eq!(get_tree_sexp(&buffer, cx), "(document (object))");
-}
-
-#[gpui::test]
-async fn test_outline(cx: &mut gpui::TestAppContext) {
-    let text = r#"
-        struct Person {
-            name: String,
-            age: usize,
-        }
-
-        mod module {
-            enum LoginState {
-                LoggedOut,
-                LoggingOn,
-                LoggedIn {
-                    person: Person,
-                    time: Instant,
-                }
-            }
-        }
-
-        impl Eq for Person {}
-
-        impl Drop for Person {
-            fn drop(&mut self) {
-                println!("bye");
-            }
-        }
-    "#
-    .unindent();
-
-    let buffer = cx.new_model(|cx| {
-        Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx)
-    });
-    let outline = buffer
-        .update(cx, |buffer, _| buffer.snapshot().outline(None))
-        .unwrap();
-
-    assert_eq!(
-        outline
-            .items
-            .iter()
-            .map(|item| (item.text.as_str(), item.depth))
-            .collect::<Vec<_>>(),
-        &[
-            ("struct Person", 0),
-            ("name", 1),
-            ("age", 1),
-            ("mod module", 0),
-            ("enum LoginState", 1),
-            ("LoggedOut", 2),
-            ("LoggingOn", 2),
-            ("LoggedIn", 2),
-            ("person", 3),
-            ("time", 3),
-            ("impl Eq for Person", 0),
-            ("impl Drop for Person", 0),
-            ("fn drop", 1),
-        ]
-    );
-
-    // Without space, we only match on names
-    assert_eq!(
-        search(&outline, "oon", cx).await,
-        &[
-            ("mod module", vec![]),                    // included as the parent of a match
-            ("enum LoginState", vec![]),               // included as the parent of a match
-            ("LoggingOn", vec![1, 7, 8]),              // matches
-            ("impl Drop for Person", vec![7, 18, 19]), // matches in two disjoint names
-        ]
-    );
-
-    assert_eq!(
-        search(&outline, "dp p", cx).await,
-        &[
-            ("impl Drop for Person", vec![5, 8, 9, 14]),
-            ("fn drop", vec![]),
-        ]
-    );
-    assert_eq!(
-        search(&outline, "dpn", cx).await,
-        &[("impl Drop for Person", vec![5, 14, 19])]
-    );
-    assert_eq!(
-        search(&outline, "impl ", cx).await,
-        &[
-            ("impl Eq for Person", vec![0, 1, 2, 3, 4]),
-            ("impl Drop for Person", vec![0, 1, 2, 3, 4]),
-            ("fn drop", vec![]),
-        ]
-    );
-
-    async fn search<'a>(
-        outline: &'a Outline<Anchor>,
-        query: &'a str,
-        cx: &'a gpui::TestAppContext,
-    ) -> Vec<(&'a str, Vec<usize>)> {
-        let matches = cx
-            .update(|cx| outline.search(query, cx.background_executor().clone()))
-            .await;
-        matches
-            .into_iter()
-            .map(|mat| (outline.items[mat.candidate_id].text.as_str(), mat.positions))
-            .collect::<Vec<_>>()
-    }
-}
-
-#[gpui::test]
-async fn test_outline_nodes_with_newlines(cx: &mut gpui::TestAppContext) {
-    let text = r#"
-        impl A for B<
-            C
-        > {
-        };
-    "#
-    .unindent();
-
-    let buffer = cx.new_model(|cx| {
-        Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx)
-    });
-    let outline = buffer
-        .update(cx, |buffer, _| buffer.snapshot().outline(None))
-        .unwrap();
-
-    assert_eq!(
-        outline
-            .items
-            .iter()
-            .map(|item| (item.text.as_str(), item.depth))
-            .collect::<Vec<_>>(),
-        &[("impl A for B<", 0)]
-    );
-}
-
-#[gpui::test]
-async fn test_outline_with_extra_context(cx: &mut gpui::TestAppContext) {
-    let language = javascript_lang()
-        .with_outline_query(
-            r#"
-            (function_declaration
-                "function" @context
-                name: (_) @name
-                parameters: (formal_parameters
-                    "(" @context.extra
-                    ")" @context.extra)) @item
-            "#,
-        )
-        .unwrap();
-
-    let text = r#"
-        function a() {}
-        function b(c) {}
-    "#
-    .unindent();
-
-    let buffer = cx.new_model(|cx| {
-        Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(language), cx)
-    });
-    let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
-
-    // extra context nodes are included in the outline.
-    let outline = snapshot.outline(None).unwrap();
-    assert_eq!(
-        outline
-            .items
-            .iter()
-            .map(|item| (item.text.as_str(), item.depth))
-            .collect::<Vec<_>>(),
-        &[("function a()", 0), ("function b( )", 0),]
-    );
-
-    // extra context nodes do not appear in breadcrumbs.
-    let symbols = snapshot.symbols_containing(3, None).unwrap();
-    assert_eq!(
-        symbols
-            .iter()
-            .map(|item| (item.text.as_str(), item.depth))
-            .collect::<Vec<_>>(),
-        &[("function a", 0)]
-    );
-}
-
-#[gpui::test]
-async fn test_symbols_containing(cx: &mut gpui::TestAppContext) {
-    let text = r#"
-        impl Person {
-            fn one() {
-                1
-            }
-
-            fn two() {
-                2
-            }fn three() {
-                3
-            }
-        }
-    "#
-    .unindent();
-
-    let buffer = cx.new_model(|cx| {
-        Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx)
-    });
-    let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
-
-    // point is at the start of an item
-    assert_eq!(
-        symbols_containing(Point::new(1, 4), &snapshot),
-        vec![
-            (
-                "impl Person".to_string(),
-                Point::new(0, 0)..Point::new(10, 1)
-            ),
-            ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5))
-        ]
-    );
-
-    // point is in the middle of an item
-    assert_eq!(
-        symbols_containing(Point::new(2, 8), &snapshot),
-        vec![
-            (
-                "impl Person".to_string(),
-                Point::new(0, 0)..Point::new(10, 1)
-            ),
-            ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5))
-        ]
-    );
-
-    // point is at the end of an item
-    assert_eq!(
-        symbols_containing(Point::new(3, 5), &snapshot),
-        vec![
-            (
-                "impl Person".to_string(),
-                Point::new(0, 0)..Point::new(10, 1)
-            ),
-            ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5))
-        ]
-    );
-
-    // point is in between two adjacent items
-    assert_eq!(
-        symbols_containing(Point::new(7, 5), &snapshot),
-        vec![
-            (
-                "impl Person".to_string(),
-                Point::new(0, 0)..Point::new(10, 1)
-            ),
-            ("fn two".to_string(), Point::new(5, 4)..Point::new(7, 5))
-        ]
-    );
-
-    fn symbols_containing(
-        position: Point,
-        snapshot: &BufferSnapshot,
-    ) -> Vec<(String, Range<Point>)> {
-        snapshot
-            .symbols_containing(position, None)
-            .unwrap()
-            .into_iter()
-            .map(|item| {
-                (
-                    item.text,
-                    item.range.start.to_point(snapshot)..item.range.end.to_point(snapshot),
-                )
-            })
-            .collect()
-    }
-}
-
-#[gpui::test]
-fn test_enclosing_bracket_ranges(cx: &mut AppContext) {
-    let mut assert = |selection_text, range_markers| {
-        assert_bracket_pairs(selection_text, range_markers, rust_lang(), cx)
-    };
-
-    assert(
-        indoc! {"
-            mod x {
-                moˇd y {
-
-                }
-            }
-            let foo = 1;"},
-        vec![indoc! {"
-            mod x «{»
-                mod y {
-
-                }
-            «}»
-            let foo = 1;"}],
-    );
-
-    assert(
-        indoc! {"
-            mod x {
-                mod y ˇ{
-
-                }
-            }
-            let foo = 1;"},
-        vec![
-            indoc! {"
-                mod x «{»
-                    mod y {
-
-                    }
-                «}»
-                let foo = 1;"},
-            indoc! {"
-                mod x {
-                    mod y «{»
-
-                    «}»
-                }
-                let foo = 1;"},
-        ],
-    );
-
-    assert(
-        indoc! {"
-            mod x {
-                mod y {
-
-                }ˇ
-            }
-            let foo = 1;"},
-        vec![
-            indoc! {"
-                mod x «{»
-                    mod y {
-
-                    }
-                «}»
-                let foo = 1;"},
-            indoc! {"
-                mod x {
-                    mod y «{»
-
-                    «}»
-                }
-                let foo = 1;"},
-        ],
-    );
-
-    assert(
-        indoc! {"
-            mod x {
-                mod y {
-
-                }
-            ˇ}
-            let foo = 1;"},
-        vec![indoc! {"
-            mod x «{»
-                mod y {
-
-                }
-            «}»
-            let foo = 1;"}],
-    );
-
-    assert(
-        indoc! {"
-            mod x {
-                mod y {
-
-                }
-            }
-            let fˇoo = 1;"},
-        vec![],
-    );
-
-    // Regression test: avoid crash when querying at the end of the buffer.
-    assert(
-        indoc! {"
-            mod x {
-                mod y {
-
-                }
-            }
-            let foo = 1;ˇ"},
-        vec![],
-    );
-}
-
-#[gpui::test]
-fn test_enclosing_bracket_ranges_where_brackets_are_not_outermost_children(cx: &mut AppContext) {
-    let mut assert = |selection_text, bracket_pair_texts| {
-        assert_bracket_pairs(selection_text, bracket_pair_texts, javascript_lang(), cx)
-    };
-
-    assert(
-        indoc! {"
-        for (const a in b)ˇ {
-            // a comment that's longer than the for-loop header
-        }"},
-        vec![indoc! {"
-        for «(»const a in b«)» {
-            // a comment that's longer than the for-loop header
-        }"}],
-    );
-
-    // Regression test: even though the parent node of the parentheses (the for loop) does
-    // intersect the given range, the parentheses themselves do not contain the range, so
-    // they should not be returned. Only the curly braces contain the range.
-    assert(
-        indoc! {"
-        for (const a in b) {ˇ
-            // a comment that's longer than the for-loop header
-        }"},
-        vec![indoc! {"
-        for (const a in b) «{»
-            // a comment that's longer than the for-loop header
-        «}»"}],
-    );
-}
-
-#[gpui::test]
-fn test_range_for_syntax_ancestor(cx: &mut AppContext) {
-    cx.new_model(|cx| {
-        let text = "fn a() { b(|c| {}) }";
-        let buffer =
-            Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
-        let snapshot = buffer.snapshot();
-
-        assert_eq!(
-            snapshot.range_for_syntax_ancestor(empty_range_at(text, "|")),
-            Some(range_of(text, "|"))
-        );
-        assert_eq!(
-            snapshot.range_for_syntax_ancestor(range_of(text, "|")),
-            Some(range_of(text, "|c|"))
-        );
-        assert_eq!(
-            snapshot.range_for_syntax_ancestor(range_of(text, "|c|")),
-            Some(range_of(text, "|c| {}"))
-        );
-        assert_eq!(
-            snapshot.range_for_syntax_ancestor(range_of(text, "|c| {}")),
-            Some(range_of(text, "(|c| {})"))
-        );
-
-        buffer
-    });
-
-    fn empty_range_at(text: &str, part: &str) -> Range<usize> {
-        let start = text.find(part).unwrap();
-        start..start
-    }
-
-    fn range_of(text: &str, part: &str) -> Range<usize> {
-        let start = text.find(part).unwrap();
-        start..start + part.len()
-    }
-}
-
-#[gpui::test]
-fn test_autoindent_with_soft_tabs(cx: &mut AppContext) {
-    init_settings(cx, |_| {});
-
-    cx.new_model(|cx| {
-        let text = "fn a() {}";
-        let mut buffer =
-            Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
-
-        buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
-        assert_eq!(buffer.text(), "fn a() {\n    \n}");
-
-        buffer.edit(
-            [(Point::new(1, 4)..Point::new(1, 4), "b()\n")],
-            Some(AutoindentMode::EachLine),
-            cx,
-        );
-        assert_eq!(buffer.text(), "fn a() {\n    b()\n    \n}");
-
-        // Create a field expression on a new line, causing that line
-        // to be indented.
-        buffer.edit(
-            [(Point::new(2, 4)..Point::new(2, 4), ".c")],
-            Some(AutoindentMode::EachLine),
-            cx,
-        );
-        assert_eq!(buffer.text(), "fn a() {\n    b()\n        .c\n}");
-
-        // Remove the dot so that the line is no longer a field expression,
-        // causing the line to be outdented.
-        buffer.edit(
-            [(Point::new(2, 8)..Point::new(2, 9), "")],
-            Some(AutoindentMode::EachLine),
-            cx,
-        );
-        assert_eq!(buffer.text(), "fn a() {\n    b()\n    c\n}");
-
-        buffer
-    });
-}
-
-#[gpui::test]
-fn test_autoindent_with_hard_tabs(cx: &mut AppContext) {
-    init_settings(cx, |settings| {
-        settings.defaults.hard_tabs = Some(true);
-    });
-
-    cx.new_model(|cx| {
-        let text = "fn a() {}";
-        let mut buffer =
-            Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
-
-        buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
-        assert_eq!(buffer.text(), "fn a() {\n\t\n}");
-
-        buffer.edit(
-            [(Point::new(1, 1)..Point::new(1, 1), "b()\n")],
-            Some(AutoindentMode::EachLine),
-            cx,
-        );
-        assert_eq!(buffer.text(), "fn a() {\n\tb()\n\t\n}");
-
-        // Create a field expression on a new line, causing that line
-        // to be indented.
-        buffer.edit(
-            [(Point::new(2, 1)..Point::new(2, 1), ".c")],
-            Some(AutoindentMode::EachLine),
-            cx,
-        );
-        assert_eq!(buffer.text(), "fn a() {\n\tb()\n\t\t.c\n}");
-
-        // Remove the dot so that the line is no longer a field expression,
-        // causing the line to be outdented.
-        buffer.edit(
-            [(Point::new(2, 2)..Point::new(2, 3), "")],
-            Some(AutoindentMode::EachLine),
-            cx,
-        );
-        assert_eq!(buffer.text(), "fn a() {\n\tb()\n\tc\n}");
-
-        buffer
-    });
-}
-
-#[gpui::test]
-fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppContext) {
-    init_settings(cx, |_| {});
-
-    cx.new_model(|cx| {
-        let entity_id = cx.entity_id();
-        let mut buffer = Buffer::new(
-            0,
-            entity_id.as_u64(),
-            "
-            fn a() {
-            c;
-            d;
-            }
-            "
-            .unindent(),
-        )
-        .with_language(Arc::new(rust_lang()), cx);
-
-        // Lines 2 and 3 don't match the indentation suggestion. When editing these lines,
-        // their indentation is not adjusted.
-        buffer.edit_via_marked_text(
-            &"
-            fn a() {
-            c«()»;
-            d«()»;
-            }
-            "
-            .unindent(),
-            Some(AutoindentMode::EachLine),
-            cx,
-        );
-        assert_eq!(
-            buffer.text(),
-            "
-            fn a() {
-            c();
-            d();
-            }
-            "
-            .unindent()
-        );
-
-        // When appending new content after these lines, the indentation is based on the
-        // preceding lines' actual indentation.
-        buffer.edit_via_marked_text(
-            &"
-            fn a() {
-            c«
-            .f
-            .g()»;
-            d«
-            .f
-            .g()»;
-            }
-            "
-            .unindent(),
-            Some(AutoindentMode::EachLine),
-            cx,
-        );
-
-        assert_eq!(
-            buffer.text(),
-            "
-            fn a() {
-            c
-                .f
-                .g();
-            d
-                .f
-                .g();
-            }
-            "
-            .unindent()
-        );
-        buffer
-    });
-
-    cx.new_model(|cx| {
-        eprintln!("second buffer: {:?}", cx.entity_id());
-
-        let mut buffer = Buffer::new(
-            0,
-            cx.entity_id().as_u64(),
-            "
-            fn a() {
-                b();
-                |
-            "
-            .replace("|", "") // marker to preserve trailing whitespace
-            .unindent(),
-        )
-        .with_language(Arc::new(rust_lang()), cx);
-
-        // Insert a closing brace. It is outdented.
-        buffer.edit_via_marked_text(
-            &"
-            fn a() {
-                b();
-                «}»
-            "
-            .unindent(),
-            Some(AutoindentMode::EachLine),
-            cx,
-        );
-        assert_eq!(
-            buffer.text(),
-            "
-            fn a() {
-                b();
-            }
-            "
-            .unindent()
-        );
-
-        // Manually edit the leading whitespace. The edit is preserved.
-        buffer.edit_via_marked_text(
-            &"
-            fn a() {
-                b();
-            «    »}
-            "
-            .unindent(),
-            Some(AutoindentMode::EachLine),
-            cx,
-        );
-        assert_eq!(
-            buffer.text(),
-            "
-            fn a() {
-                b();
-                }
-            "
-            .unindent()
-        );
-        buffer
-    });
-
-    eprintln!("DONE");
-}
-
-#[gpui::test]
-fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut AppContext) {
-    init_settings(cx, |_| {});
-
-    cx.new_model(|cx| {
-        let mut buffer = Buffer::new(
-            0,
-            cx.entity_id().as_u64(),
-            "
-            fn a() {
-                i
-            }
-            "
-            .unindent(),
-        )
-        .with_language(Arc::new(rust_lang()), cx);
-
-        // Regression test: line does not get outdented due to syntax error
-        buffer.edit_via_marked_text(
-            &"
-            fn a() {
-                i«f let Some(x) = y»
-            }
-            "
-            .unindent(),
-            Some(AutoindentMode::EachLine),
-            cx,
-        );
-        assert_eq!(
-            buffer.text(),
-            "
-            fn a() {
-                if let Some(x) = y
-            }
-            "
-            .unindent()
-        );
-
-        buffer.edit_via_marked_text(
-            &"
-            fn a() {
-                if let Some(x) = y« {»
-            }
-            "
-            .unindent(),
-            Some(AutoindentMode::EachLine),
-            cx,
-        );
-        assert_eq!(
-            buffer.text(),
-            "
-            fn a() {
-                if let Some(x) = y {
-            }
-            "
-            .unindent()
-        );
-
-        buffer
-    });
-}
-
-#[gpui::test]
-fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut AppContext) {
-    init_settings(cx, |_| {});
-
-    cx.new_model(|cx| {
-        let mut buffer = Buffer::new(
-            0,
-            cx.entity_id().as_u64(),
-            "
-            fn a() {}
-            "
-            .unindent(),
-        )
-        .with_language(Arc::new(rust_lang()), cx);
-
-        buffer.edit_via_marked_text(
-            &"
-            fn a(«
-            b») {}
-            "
-            .unindent(),
-            Some(AutoindentMode::EachLine),
-            cx,
-        );
-        assert_eq!(
-            buffer.text(),
-            "
-            fn a(
-                b) {}
-            "
-            .unindent()
-        );
-
-        // The indentation suggestion changed because `@end` node (a close paren)
-        // is now at the beginning of the line.
-        buffer.edit_via_marked_text(
-            &"
-            fn a(
-                ˇ) {}
-            "
-            .unindent(),
-            Some(AutoindentMode::EachLine),
-            cx,
-        );
-        assert_eq!(
-            buffer.text(),
-            "
-                fn a(
-                ) {}
-            "
-            .unindent()
-        );
-
-        buffer
-    });
-}
-
-#[gpui::test]
-fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut AppContext) {
-    init_settings(cx, |_| {});
-
-    cx.new_model(|cx| {
-        let text = "a\nb";
-        let mut buffer =
-            Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
-        buffer.edit(
-            [(0..1, "\n"), (2..3, "\n")],
-            Some(AutoindentMode::EachLine),
-            cx,
-        );
-        assert_eq!(buffer.text(), "\n\n\n");
-        buffer
-    });
-}
-
-#[gpui::test]
-fn test_autoindent_multi_line_insertion(cx: &mut AppContext) {
-    init_settings(cx, |_| {});
-
-    cx.new_model(|cx| {
-        let text = "
-            const a: usize = 1;
-            fn b() {
-                if c {
-                    let d = 2;
-                }
-            }
-        "
-        .unindent();
-
-        let mut buffer =
-            Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
-        buffer.edit(
-            [(Point::new(3, 0)..Point::new(3, 0), "e(\n    f()\n);\n")],
-            Some(AutoindentMode::EachLine),
-            cx,
-        );
-        assert_eq!(
-            buffer.text(),
-            "
-                const a: usize = 1;
-                fn b() {
-                    if c {
-                        e(
-                            f()
-                        );
-                        let d = 2;
-                    }
-                }
-            "
-            .unindent()
-        );
-
-        buffer
-    });
-}
-
-#[gpui::test]
-fn test_autoindent_block_mode(cx: &mut AppContext) {
-    init_settings(cx, |_| {});
-
-    cx.new_model(|cx| {
-        let text = r#"
-            fn a() {
-                b();
-            }
-        "#
-        .unindent();
-        let mut buffer =
-            Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
-
-        // When this text was copied, both of the quotation marks were at the same
-        // indent level, but the indentation of the first line was not included in
-        // the copied text. This information is retained in the
-        // 'original_indent_columns' vector.
-        let original_indent_columns = vec![4];
-        let inserted_text = r#"
-            "
-                  c
-                    d
-                      e
-                "
-        "#
-        .unindent();
-
-        // Insert the block at column zero. The entire block is indented
-        // so that the first line matches the previous line's indentation.
-        buffer.edit(
-            [(Point::new(2, 0)..Point::new(2, 0), inserted_text.clone())],
-            Some(AutoindentMode::Block {
-                original_indent_columns: original_indent_columns.clone(),
-            }),
-            cx,
-        );
-        assert_eq!(
-            buffer.text(),
-            r#"
-            fn a() {
-                b();
-                "
-                  c
-                    d
-                      e
-                "
-            }
-            "#
-            .unindent()
-        );
-
-        // Grouping is disabled in tests, so we need 2 undos
-        buffer.undo(cx); // Undo the auto-indent
-        buffer.undo(cx); // Undo the original edit
-
-        // Insert the block at a deeper indent level. The entire block is outdented.
-        buffer.edit([(Point::new(2, 0)..Point::new(2, 0), "        ")], None, cx);
-        buffer.edit(
-            [(Point::new(2, 8)..Point::new(2, 8), inserted_text)],
-            Some(AutoindentMode::Block {
-                original_indent_columns: original_indent_columns.clone(),
-            }),
-            cx,
-        );
-        assert_eq!(
-            buffer.text(),
-            r#"
-            fn a() {
-                b();
-                "
-                  c
-                    d
-                      e
-                "
-            }
-            "#
-            .unindent()
-        );
-
-        buffer
-    });
-}
-
-#[gpui::test]
-fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContext) {
-    init_settings(cx, |_| {});
-
-    cx.new_model(|cx| {
-        let text = r#"
-            fn a() {
-                if b() {
-
-                }
-            }
-        "#
-        .unindent();
-        let mut buffer =
-            Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
-
-        // The original indent columns are not known, so this text is
-        // auto-indented in a block as if the first line was copied in
-        // its entirety.
-        let original_indent_columns = Vec::new();
-        let inserted_text = "    c\n        .d()\n        .e();";
-
-        // Insert the block at column zero. The entire block is indented
-        // so that the first line matches the previous line's indentation.
-        buffer.edit(
-            [(Point::new(2, 0)..Point::new(2, 0), inserted_text)],
-            Some(AutoindentMode::Block {
-                original_indent_columns: original_indent_columns.clone(),
-            }),
-            cx,
-        );
-        assert_eq!(
-            buffer.text(),
-            r#"
-            fn a() {
-                if b() {
-                    c
-                        .d()
-                        .e();
-                }
-            }
-            "#
-            .unindent()
-        );
-
-        // Grouping is disabled in tests, so we need 2 undos
-        buffer.undo(cx); // Undo the auto-indent
-        buffer.undo(cx); // Undo the original edit
-
-        // Insert the block at a deeper indent level. The entire block is outdented.
-        buffer.edit(
-            [(Point::new(2, 0)..Point::new(2, 0), " ".repeat(12))],
-            None,
-            cx,
-        );
-        buffer.edit(
-            [(Point::new(2, 12)..Point::new(2, 12), inserted_text)],
-            Some(AutoindentMode::Block {
-                original_indent_columns: Vec::new(),
-            }),
-            cx,
-        );
-        assert_eq!(
-            buffer.text(),
-            r#"
-            fn a() {
-                if b() {
-                    c
-                        .d()
-                        .e();
-                }
-            }
-            "#
-            .unindent()
-        );
-
-        buffer
-    });
-}
-
-#[gpui::test]
-fn test_autoindent_language_without_indents_query(cx: &mut AppContext) {
-    init_settings(cx, |_| {});
-
-    cx.new_model(|cx| {
-        let text = "
-            * one
-                - a
-                - b
-            * two
-        "
-        .unindent();
-
-        let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), text).with_language(
-            Arc::new(Language::new(
-                LanguageConfig {
-                    name: "Markdown".into(),
-                    auto_indent_using_last_non_empty_line: false,
-                    ..Default::default()
-                },
-                Some(tree_sitter_json::language()),
-            )),
-            cx,
-        );
-        buffer.edit(
-            [(Point::new(3, 0)..Point::new(3, 0), "\n")],
-            Some(AutoindentMode::EachLine),
-            cx,
-        );
-        assert_eq!(
-            buffer.text(),
-            "
-            * one
-                - a
-                - b
-
-            * two
-            "
-            .unindent()
-        );
-        buffer
-    });
-}
-
-#[gpui::test]
-fn test_autoindent_with_injected_languages(cx: &mut AppContext) {
-    init_settings(cx, |settings| {
-        settings.languages.extend([
-            (
-                "HTML".into(),
-                LanguageSettingsContent {
-                    tab_size: Some(2.try_into().unwrap()),
-                    ..Default::default()
-                },
-            ),
-            (
-                "JavaScript".into(),
-                LanguageSettingsContent {
-                    tab_size: Some(8.try_into().unwrap()),
-                    ..Default::default()
-                },
-            ),
-        ])
-    });
-
-    let html_language = Arc::new(html_lang());
-
-    let javascript_language = Arc::new(javascript_lang());
-
-    let language_registry = Arc::new(LanguageRegistry::test());
-    language_registry.add(html_language.clone());
-    language_registry.add(javascript_language.clone());
-
-    cx.new_model(|cx| {
-        let (text, ranges) = marked_text_ranges(
-            &"
-                <div>ˇ
-                </div>
-                <script>
-                    init({ˇ
-                    })
-                </script>
-                <span>ˇ
-                </span>
-            "
-            .unindent(),
-            false,
-        );
-
-        let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), text);
-        buffer.set_language_registry(language_registry);
-        buffer.set_language(Some(html_language), cx);
-        buffer.edit(
-            ranges.into_iter().map(|range| (range, "\na")),
-            Some(AutoindentMode::EachLine),
-            cx,
-        );
-        assert_eq!(
-            buffer.text(),
-            "
-                <div>
-                  a
-                </div>
-                <script>
-                    init({
-                            a
-                    })
-                </script>
-                <span>
-                  a
-                </span>
-            "
-            .unindent()
-        );
-        buffer
-    });
-}
-
-#[gpui::test]
-fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) {
-    init_settings(cx, |settings| {
-        settings.defaults.tab_size = Some(2.try_into().unwrap());
-    });
-
-    cx.new_model(|cx| {
-        let mut buffer =
-            Buffer::new(0, cx.entity_id().as_u64(), "").with_language(Arc::new(ruby_lang()), cx);
-
-        let text = r#"
-            class C
-            def a(b, c)
-            puts b
-            puts c
-            rescue
-            puts "errored"
-            exit 1
-            end
-            end
-        "#
-        .unindent();
-
-        buffer.edit([(0..0, text)], Some(AutoindentMode::EachLine), cx);
-
-        assert_eq!(
-            buffer.text(),
-            r#"
-                class C
-                  def a(b, c)
-                    puts b
-                    puts c
-                  rescue
-                    puts "errored"
-                    exit 1
-                  end
-                end
-            "#
-            .unindent()
-        );
-
-        buffer
-    });
-}
-
-#[gpui::test]
-fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
-    init_settings(cx, |_| {});
-
-    cx.new_model(|cx| {
-        let language = Language::new(
-            LanguageConfig {
-                name: "JavaScript".into(),
-                line_comment: Some("// ".into()),
-                brackets: BracketPairConfig {
-                    pairs: vec![
-                        BracketPair {
-                            start: "{".into(),
-                            end: "}".into(),
-                            close: true,
-                            newline: false,
-                        },
-                        BracketPair {
-                            start: "'".into(),
-                            end: "'".into(),
-                            close: true,
-                            newline: false,
-                        },
-                    ],
-                    disabled_scopes_by_bracket_ix: vec![
-                        Vec::new(), //
-                        vec!["string".into()],
-                    ],
-                },
-                overrides: [(
-                    "element".into(),
-                    LanguageConfigOverride {
-                        line_comment: Override::Remove { remove: true },
-                        block_comment: Override::Set(("{/*".into(), "*/}".into())),
-                        ..Default::default()
-                    },
-                )]
-                .into_iter()
-                .collect(),
-                ..Default::default()
-            },
-            Some(tree_sitter_typescript::language_tsx()),
-        )
-        .with_override_query(
-            r#"
-                (jsx_element) @element
-                (string) @string
-                [
-                    (jsx_opening_element)
-                    (jsx_closing_element)
-                    (jsx_expression)
-                ] @default
-            "#,
-        )
-        .unwrap();
-
-        let text = r#"
-            a["b"] = <C d="e">
-                <F></F>
-                { g() }
-            </C>;
-        "#
-        .unindent();
-
-        let buffer =
-            Buffer::new(0, cx.entity_id().as_u64(), &text).with_language(Arc::new(language), cx);
-        let snapshot = buffer.snapshot();
-
-        let config = snapshot.language_scope_at(0).unwrap();
-        assert_eq!(config.line_comment_prefix().unwrap().as_ref(), "// ");
-        // Both bracket pairs are enabled
-        assert_eq!(
-            config.brackets().map(|e| e.1).collect::<Vec<_>>(),
-            &[true, true]
-        );
-
-        let string_config = snapshot
-            .language_scope_at(text.find("b\"").unwrap())
-            .unwrap();
-        assert_eq!(string_config.line_comment_prefix().unwrap().as_ref(), "// ");
-        // Second bracket pair is disabled
-        assert_eq!(
-            string_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
-            &[true, false]
-        );
-
-        // In between JSX tags: use the `element` override.
-        let element_config = snapshot
-            .language_scope_at(text.find("<F>").unwrap())
-            .unwrap();
-        assert_eq!(element_config.line_comment_prefix(), None);
-        assert_eq!(
-            element_config.block_comment_delimiters(),
-            Some((&"{/*".into(), &"*/}".into()))
-        );
-        assert_eq!(
-            element_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
-            &[true, true]
-        );
-
-        // Within a JSX tag: use the default config.
-        let tag_config = snapshot
-            .language_scope_at(text.find(" d=").unwrap() + 1)
-            .unwrap();
-        assert_eq!(tag_config.line_comment_prefix().unwrap().as_ref(), "// ");
-        assert_eq!(
-            tag_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
-            &[true, true]
-        );
-
-        // In a JSX expression: use the default config.
-        let expression_in_element_config = snapshot
-            .language_scope_at(text.find("{").unwrap() + 1)
-            .unwrap();
-        assert_eq!(
-            expression_in_element_config
-                .line_comment_prefix()
-                .unwrap()
-                .as_ref(),
-            "// "
-        );
-        assert_eq!(
-            expression_in_element_config
-                .brackets()
-                .map(|e| e.1)
-                .collect::<Vec<_>>(),
-            &[true, true]
-        );
-
-        buffer
-    });
-}
-
-#[gpui::test]
-fn test_language_scope_at_with_rust(cx: &mut AppContext) {
-    init_settings(cx, |_| {});
-
-    cx.new_model(|cx| {
-        let language = Language::new(
-            LanguageConfig {
-                name: "Rust".into(),
-                brackets: BracketPairConfig {
-                    pairs: vec![
-                        BracketPair {
-                            start: "{".into(),
-                            end: "}".into(),
-                            close: true,
-                            newline: false,
-                        },
-                        BracketPair {
-                            start: "'".into(),
-                            end: "'".into(),
-                            close: true,
-                            newline: false,
-                        },
-                    ],
-                    disabled_scopes_by_bracket_ix: vec![
-                        Vec::new(), //
-                        vec!["string".into()],
-                    ],
-                },
-                ..Default::default()
-            },
-            Some(tree_sitter_rust::language()),
-        )
-        .with_override_query(
-            r#"
-                (string_literal) @string
-            "#,
-        )
-        .unwrap();
-
-        let text = r#"
-            const S: &'static str = "hello";
-        "#
-        .unindent();
-
-        let buffer = Buffer::new(0, cx.entity_id().as_u64(), text.clone())
-            .with_language(Arc::new(language), cx);
-        let snapshot = buffer.snapshot();
-
-        // By default, all brackets are enabled
-        let config = snapshot.language_scope_at(0).unwrap();
-        assert_eq!(
-            config.brackets().map(|e| e.1).collect::<Vec<_>>(),
-            &[true, true]
-        );
-
-        // Within a string, the quotation brackets are disabled.
-        let string_config = snapshot
-            .language_scope_at(text.find("ello").unwrap())
-            .unwrap();
-        assert_eq!(
-            string_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
-            &[true, false]
-        );
-
-        buffer
-    });
-}
-
-#[gpui::test]
-fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) {
-    init_settings(cx, |_| {});
-
-    cx.new_model(|cx| {
-        let text = r#"
-            <ol>
-            <% people.each do |person| %>
-                <li>
-                    <%= person.name %>
-                </li>
-            <% end %>
-            </ol>
-        "#
-        .unindent();
-
-        let language_registry = Arc::new(LanguageRegistry::test());
-        language_registry.add(Arc::new(ruby_lang()));
-        language_registry.add(Arc::new(html_lang()));
-        language_registry.add(Arc::new(erb_lang()));
-
-        let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), text);
-        buffer.set_language_registry(language_registry.clone());
-        buffer.set_language(
-            language_registry
-                .language_for_name("ERB")
-                .now_or_never()
-                .unwrap()
-                .ok(),
-            cx,
-        );
-
-        let snapshot = buffer.snapshot();
-        let html_config = snapshot.language_scope_at(Point::new(2, 4)).unwrap();
-        assert_eq!(html_config.line_comment_prefix(), None);
-        assert_eq!(
-            html_config.block_comment_delimiters(),
-            Some((&"<!--".into(), &"-->".into()))
-        );
-
-        let ruby_config = snapshot.language_scope_at(Point::new(3, 12)).unwrap();
-        assert_eq!(ruby_config.line_comment_prefix().unwrap().as_ref(), "# ");
-        assert_eq!(ruby_config.block_comment_delimiters(), None);
-
-        buffer
-    });
-}
-
-#[gpui::test]
-fn test_serialization(cx: &mut gpui::AppContext) {
-    let mut now = Instant::now();
-
-    let buffer1 = cx.new_model(|cx| {
-        let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), "abc");
-        buffer.edit([(3..3, "D")], None, cx);
-
-        now += Duration::from_secs(1);
-        buffer.start_transaction_at(now);
-        buffer.edit([(4..4, "E")], None, cx);
-        buffer.end_transaction_at(now, cx);
-        assert_eq!(buffer.text(), "abcDE");
-
-        buffer.undo(cx);
-        assert_eq!(buffer.text(), "abcD");
-
-        buffer.edit([(4..4, "F")], None, cx);
-        assert_eq!(buffer.text(), "abcDF");
-        buffer
-    });
-    assert_eq!(buffer1.read(cx).text(), "abcDF");
-
-    let state = buffer1.read(cx).to_proto();
-    let ops = cx
-        .background_executor()
-        .block(buffer1.read(cx).serialize_ops(None, cx));
-    let buffer2 = cx.new_model(|cx| {
-        let mut buffer = Buffer::from_proto(1, state, None).unwrap();
-        buffer
-            .apply_ops(
-                ops.into_iter()
-                    .map(|op| proto::deserialize_operation(op).unwrap()),
-                cx,
-            )
-            .unwrap();
-        buffer
-    });
-    assert_eq!(buffer2.read(cx).text(), "abcDF");
-}
-
-#[gpui::test(iterations = 100)]
-fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
-    let min_peers = env::var("MIN_PEERS")
-        .map(|i| i.parse().expect("invalid `MIN_PEERS` variable"))
-        .unwrap_or(1);
-    let max_peers = env::var("MAX_PEERS")
-        .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
-        .unwrap_or(5);
-    let operations = env::var("OPERATIONS")
-        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
-        .unwrap_or(10);
-
-    let base_text_len = rng.gen_range(0..10);
-    let base_text = RandomCharIter::new(&mut rng)
-        .take(base_text_len)
-        .collect::<String>();
-    let mut replica_ids = Vec::new();
-    let mut buffers = Vec::new();
-    let network = Arc::new(Mutex::new(Network::new(rng.clone())));
-    let base_buffer =
-        cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), base_text.as_str()));
-
-    for i in 0..rng.gen_range(min_peers..=max_peers) {
-        let buffer = cx.new_model(|cx| {
-            let state = base_buffer.read(cx).to_proto();
-            let ops = cx
-                .background_executor()
-                .block(base_buffer.read(cx).serialize_ops(None, cx));
-            let mut buffer = Buffer::from_proto(i as ReplicaId, state, None).unwrap();
-            buffer
-                .apply_ops(
-                    ops.into_iter()
-                        .map(|op| proto::deserialize_operation(op).unwrap()),
-                    cx,
-                )
-                .unwrap();
-            buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
-            let network = network.clone();
-            cx.subscribe(&cx.handle(), move |buffer, _, event, _| {
-                if let Event::Operation(op) = event {
-                    network
-                        .lock()
-                        .broadcast(buffer.replica_id(), vec![proto::serialize_operation(op)]);
-                }
-            })
-            .detach();
-            buffer
-        });
-
-        buffers.push(buffer);
-        replica_ids.push(i as ReplicaId);
-        network.lock().add_peer(i as ReplicaId);
-        log::info!("Adding initial peer with replica id {}", i);
-    }
-
-    log::info!("initial text: {:?}", base_text);
-
-    let mut now = Instant::now();
-    let mut mutation_count = operations;
-    let mut next_diagnostic_id = 0;
-    let mut active_selections = BTreeMap::default();
-    loop {
-        let replica_index = rng.gen_range(0..replica_ids.len());
-        let replica_id = replica_ids[replica_index];
-        let buffer = &mut buffers[replica_index];
-        let mut new_buffer = None;
-        match rng.gen_range(0..100) {
-            0..=29 if mutation_count != 0 => {
-                buffer.update(cx, |buffer, cx| {
-                    buffer.start_transaction_at(now);
-                    buffer.randomly_edit(&mut rng, 5, cx);
-                    buffer.end_transaction_at(now, cx);
-                    log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
-                });
-                mutation_count -= 1;
-            }
-            30..=39 if mutation_count != 0 => {
-                buffer.update(cx, |buffer, cx| {
-                    if rng.gen_bool(0.2) {
-                        log::info!("peer {} clearing active selections", replica_id);
-                        active_selections.remove(&replica_id);
-                        buffer.remove_active_selections(cx);
-                    } else {
-                        let mut selections = Vec::new();
-                        for id in 0..rng.gen_range(1..=5) {
-                            let range = buffer.random_byte_range(0, &mut rng);
-                            selections.push(Selection {
-                                id,
-                                start: buffer.anchor_before(range.start),
-                                end: buffer.anchor_before(range.end),
-                                reversed: false,
-                                goal: SelectionGoal::None,
-                            });
-                        }
-                        let selections: Arc<[Selection<Anchor>]> = selections.into();
-                        log::info!(
-                            "peer {} setting active selections: {:?}",
-                            replica_id,
-                            selections
-                        );
-                        active_selections.insert(replica_id, selections.clone());
-                        buffer.set_active_selections(selections, false, Default::default(), cx);
-                    }
-                });
-                mutation_count -= 1;
-            }
-            40..=49 if mutation_count != 0 && replica_id == 0 => {
-                let entry_count = rng.gen_range(1..=5);
-                buffer.update(cx, |buffer, cx| {
-                    let diagnostics = DiagnosticSet::new(
-                        (0..entry_count).map(|_| {
-                            let range = buffer.random_byte_range(0, &mut rng);
-                            let range = range.to_point_utf16(buffer);
-                            let range = range.start..range.end;
-                            DiagnosticEntry {
-                                range,
-                                diagnostic: Diagnostic {
-                                    message: post_inc(&mut next_diagnostic_id).to_string(),
-                                    ..Default::default()
-                                },
-                            }
-                        }),
-                        buffer,
-                    );
-                    log::info!("peer {} setting diagnostics: {:?}", replica_id, diagnostics);
-                    buffer.update_diagnostics(LanguageServerId(0), diagnostics, cx);
-                });
-                mutation_count -= 1;
-            }
-            50..=59 if replica_ids.len() < max_peers => {
-                let old_buffer_state = buffer.read(cx).to_proto();
-                let old_buffer_ops = cx
-                    .background_executor()
-                    .block(buffer.read(cx).serialize_ops(None, cx));
-                let new_replica_id = (0..=replica_ids.len() as ReplicaId)
-                    .filter(|replica_id| *replica_id != buffer.read(cx).replica_id())
-                    .choose(&mut rng)
-                    .unwrap();
-                log::info!(
-                    "Adding new replica {} (replicating from {})",
-                    new_replica_id,
-                    replica_id
-                );
-                new_buffer = Some(cx.new_model(|cx| {
-                    let mut new_buffer =
-                        Buffer::from_proto(new_replica_id, old_buffer_state, None).unwrap();
-                    new_buffer
-                        .apply_ops(
-                            old_buffer_ops
-                                .into_iter()
-                                .map(|op| deserialize_operation(op).unwrap()),
-                            cx,
-                        )
-                        .unwrap();
-                    log::info!(
-                        "New replica {} text: {:?}",
-                        new_buffer.replica_id(),
-                        new_buffer.text()
-                    );
-                    new_buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
-                    let network = network.clone();
-                    cx.subscribe(&cx.handle(), move |buffer, _, event, _| {
-                        if let Event::Operation(op) = event {
-                            network.lock().broadcast(
-                                buffer.replica_id(),
-                                vec![proto::serialize_operation(op)],
-                            );
-                        }
-                    })
-                    .detach();
-                    new_buffer
-                }));
-                network.lock().replicate(replica_id, new_replica_id);
-
-                if new_replica_id as usize == replica_ids.len() {
-                    replica_ids.push(new_replica_id);
-                } else {
-                    let new_buffer = new_buffer.take().unwrap();
-                    while network.lock().has_unreceived(new_replica_id) {
-                        let ops = network
-                            .lock()
-                            .receive(new_replica_id)
-                            .into_iter()
-                            .map(|op| proto::deserialize_operation(op).unwrap());
-                        if ops.len() > 0 {
-                            log::info!(
-                                "peer {} (version: {:?}) applying {} ops from the network. {:?}",
-                                new_replica_id,
-                                buffer.read(cx).version(),
-                                ops.len(),
-                                ops
-                            );
-                            new_buffer.update(cx, |new_buffer, cx| {
-                                new_buffer.apply_ops(ops, cx).unwrap();
-                            });
-                        }
-                    }
-                    buffers[new_replica_id as usize] = new_buffer;
-                }
-            }
-            60..=69 if mutation_count != 0 => {
-                buffer.update(cx, |buffer, cx| {
-                    buffer.randomly_undo_redo(&mut rng, cx);
-                    log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
-                });
-                mutation_count -= 1;
-            }
-            _ if network.lock().has_unreceived(replica_id) => {
-                let ops = network
-                    .lock()
-                    .receive(replica_id)
-                    .into_iter()
-                    .map(|op| proto::deserialize_operation(op).unwrap());
-                if ops.len() > 0 {
-                    log::info!(
-                        "peer {} (version: {:?}) applying {} ops from the network. {:?}",
-                        replica_id,
-                        buffer.read(cx).version(),
-                        ops.len(),
-                        ops
-                    );
-                    buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx).unwrap());
-                }
-            }
-            _ => {}
-        }
-
-        now += Duration::from_millis(rng.gen_range(0..=200));
-        buffers.extend(new_buffer);
-
-        for buffer in &buffers {
-            buffer.read(cx).check_invariants();
-        }
-
-        if mutation_count == 0 && network.lock().is_idle() {
-            break;
-        }
-    }
-
-    let first_buffer = buffers[0].read(cx).snapshot();
-    for buffer in &buffers[1..] {
-        let buffer = buffer.read(cx).snapshot();
-        assert_eq!(
-            buffer.version(),
-            first_buffer.version(),
-            "Replica {} version != Replica 0 version",
-            buffer.replica_id()
-        );
-        assert_eq!(
-            buffer.text(),
-            first_buffer.text(),
-            "Replica {} text != Replica 0 text",
-            buffer.replica_id()
-        );
-        assert_eq!(
-            buffer
-                .diagnostics_in_range::<_, usize>(0..buffer.len(), false)
-                .collect::<Vec<_>>(),
-            first_buffer
-                .diagnostics_in_range::<_, usize>(0..first_buffer.len(), false)
-                .collect::<Vec<_>>(),
-            "Replica {} diagnostics != Replica 0 diagnostics",
-            buffer.replica_id()
-        );
-    }
-
-    for buffer in &buffers {
-        let buffer = buffer.read(cx).snapshot();
-        let actual_remote_selections = buffer
-            .remote_selections_in_range(Anchor::MIN..Anchor::MAX)
-            .map(|(replica_id, _, _, selections)| (replica_id, selections.collect::<Vec<_>>()))
-            .collect::<Vec<_>>();
-        let expected_remote_selections = active_selections
-            .iter()
-            .filter(|(replica_id, _)| **replica_id != buffer.replica_id())
-            .map(|(replica_id, selections)| (*replica_id, selections.iter().collect::<Vec<_>>()))
-            .collect::<Vec<_>>();
-        assert_eq!(
-            actual_remote_selections,
-            expected_remote_selections,
-            "Replica {} remote selections != expected selections",
-            buffer.replica_id()
-        );
-    }
-}
-
-#[test]
-fn test_contiguous_ranges() {
-    assert_eq!(
-        contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12].into_iter(), 100).collect::<Vec<_>>(),
-        &[1..4, 5..7, 9..13]
-    );
-
-    // Respects the `max_len` parameter
-    assert_eq!(
-        contiguous_ranges(
-            [2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31].into_iter(),
-            3
-        )
-        .collect::<Vec<_>>(),
-        &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32],
-    );
-}
-
-#[gpui::test(iterations = 500)]
-fn test_trailing_whitespace_ranges(mut rng: StdRng) {
-    // Generate a random multi-line string containing
-    // some lines with trailing whitespace.
-    let mut text = String::new();
-    for _ in 0..rng.gen_range(0..16) {
-        for _ in 0..rng.gen_range(0..36) {
-            text.push(match rng.gen_range(0..10) {
-                0..=1 => ' ',
-                3 => '\t',
-                _ => rng.gen_range('a'..'z'),
-            });
-        }
-        text.push('\n');
-    }
-
-    match rng.gen_range(0..10) {
-        // sometimes remove the last newline
-        0..=1 => drop(text.pop()), //
-
-        // sometimes add extra newlines
-        2..=3 => text.push_str(&"\n".repeat(rng.gen_range(1..5))),
-        _ => {}
-    }
-
-    let rope = Rope::from(text.as_str());
-    let actual_ranges = trailing_whitespace_ranges(&rope);
-    let expected_ranges = TRAILING_WHITESPACE_REGEX
-        .find_iter(&text)
-        .map(|m| m.range())
-        .collect::<Vec<_>>();
-    assert_eq!(
-        actual_ranges,
-        expected_ranges,
-        "wrong ranges for text lines:\n{:?}",
-        text.split("\n").collect::<Vec<_>>()
-    );
-}
-
-fn ruby_lang() -> Language {
-    Language::new(
-        LanguageConfig {
-            name: "Ruby".into(),
-            path_suffixes: vec!["rb".to_string()],
-            line_comment: Some("# ".into()),
-            ..Default::default()
-        },
-        Some(tree_sitter_ruby::language()),
-    )
-    .with_indents_query(
-        r#"
-            (class "end" @end) @indent
-            (method "end" @end) @indent
-            (rescue) @outdent
-            (then) @indent
-        "#,
-    )
-    .unwrap()
-}
-
-fn html_lang() -> Language {
-    Language::new(
-        LanguageConfig {
-            name: "HTML".into(),
-            block_comment: Some(("<!--".into(), "-->".into())),
-            ..Default::default()
-        },
-        Some(tree_sitter_html::language()),
-    )
-    .with_indents_query(
-        "
-        (element
-          (start_tag) @start
-          (end_tag)? @end) @indent
-        ",
-    )
-    .unwrap()
-    .with_injection_query(
-        r#"
-        (script_element
-            (raw_text) @content
-            (#set! "language" "javascript"))
-        "#,
-    )
-    .unwrap()
-}
-
-fn erb_lang() -> Language {
-    Language::new(
-        LanguageConfig {
-            name: "ERB".into(),
-            path_suffixes: vec!["erb".to_string()],
-            block_comment: Some(("<%#".into(), "%>".into())),
-            ..Default::default()
-        },
-        Some(tree_sitter_embedded_template::language()),
-    )
-    .with_injection_query(
-        r#"
-            (
-                (code) @content
-                (#set! "language" "ruby")
-                (#set! "combined")
-            )
-
-            (
-                (content) @content
-                (#set! "language" "html")
-                (#set! "combined")
-            )
-        "#,
-    )
-    .unwrap()
-}
-
-fn rust_lang() -> Language {
-    Language::new(
-        LanguageConfig {
-            name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
-            ..Default::default()
-        },
-        Some(tree_sitter_rust::language()),
-    )
-    .with_indents_query(
-        r#"
-        (call_expression) @indent
-        (field_expression) @indent
-        (_ "(" ")" @end) @indent
-        (_ "{" "}" @end) @indent
-        "#,
-    )
-    .unwrap()
-    .with_brackets_query(
-        r#"
-        ("{" @open "}" @close)
-        "#,
-    )
-    .unwrap()
-    .with_outline_query(
-        r#"
-        (struct_item
-            "struct" @context
-            name: (_) @name) @item
-        (enum_item
-            "enum" @context
-            name: (_) @name) @item
-        (enum_variant
-            name: (_) @name) @item
-        (field_declaration
-            name: (_) @name) @item
-        (impl_item
-            "impl" @context
-            trait: (_)? @name
-            "for"? @context
-            type: (_) @name) @item
-        (function_item
-            "fn" @context
-            name: (_) @name) @item
-        (mod_item
-            "mod" @context
-            name: (_) @name) @item
-        "#,
-    )
-    .unwrap()
-}
-
-fn json_lang() -> Language {
-    Language::new(
-        LanguageConfig {
-            name: "Json".into(),
-            path_suffixes: vec!["js".to_string()],
-            ..Default::default()
-        },
-        Some(tree_sitter_json::language()),
-    )
-}
-
-fn javascript_lang() -> Language {
-    Language::new(
-        LanguageConfig {
-            name: "JavaScript".into(),
-            ..Default::default()
-        },
-        Some(tree_sitter_typescript::language_tsx()),
-    )
-    .with_brackets_query(
-        r#"
-        ("{" @open "}" @close)
-        ("(" @open ")" @close)
-        "#,
-    )
-    .unwrap()
-    .with_indents_query(
-        r#"
-        (object "}" @end) @indent
-        "#,
-    )
-    .unwrap()
-}
-
-fn get_tree_sexp(buffer: &Model<Buffer>, cx: &mut gpui::TestAppContext) -> String {
-    buffer.update(cx, |buffer, _| {
-        let snapshot = buffer.snapshot();
-        let layers = snapshot.syntax.layers(buffer.as_text_snapshot());
-        layers[0].node().to_sexp()
-    })
-}
-
-// Assert that the enclosing bracket ranges around the selection match the pairs indicated by the marked text in `range_markers`
-fn assert_bracket_pairs(
-    selection_text: &'static str,
-    bracket_pair_texts: Vec<&'static str>,
-    language: Language,
-    cx: &mut AppContext,
-) {
-    let (expected_text, selection_ranges) = marked_text_ranges(selection_text, false);
-    let buffer = cx.new_model(|cx| {
-        Buffer::new(0, cx.entity_id().as_u64(), expected_text.clone())
-            .with_language(Arc::new(language), cx)
-    });
-    let buffer = buffer.update(cx, |buffer, _cx| buffer.snapshot());
-
-    let selection_range = selection_ranges[0].clone();
-
-    let bracket_pairs = bracket_pair_texts
-        .into_iter()
-        .map(|pair_text| {
-            let (bracket_text, ranges) = marked_text_ranges(pair_text, false);
-            assert_eq!(bracket_text, expected_text);
-            (ranges[0].clone(), ranges[1].clone())
-        })
-        .collect::<Vec<_>>();
-
-    assert_set_eq!(
-        buffer.bracket_ranges(selection_range).collect::<Vec<_>>(),
-        bracket_pairs
-    );
-}
-
-fn init_settings(cx: &mut AppContext, f: fn(&mut AllLanguageSettingsContent)) {
-    let settings_store = SettingsStore::test(cx);
-    cx.set_global(settings_store);
-    crate::init(cx);
-    cx.update_global::<SettingsStore, _>(|settings, cx| {
-        settings.update_user_settings::<AllLanguageSettings>(cx, f);
-    });
-}

crates/language2/src/diagnostic_set.rs 🔗

@@ -1,236 +0,0 @@
-use crate::Diagnostic;
-use collections::HashMap;
-use lsp::LanguageServerId;
-use std::{
-    cmp::{Ordering, Reverse},
-    iter,
-    ops::Range,
-};
-use sum_tree::{self, Bias, SumTree};
-use text::{Anchor, FromAnchor, PointUtf16, ToOffset};
-
-#[derive(Clone, Debug, Default)]
-pub struct DiagnosticSet {
-    diagnostics: SumTree<DiagnosticEntry<Anchor>>,
-}
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct DiagnosticEntry<T> {
-    pub range: Range<T>,
-    pub diagnostic: Diagnostic,
-}
-
-#[derive(Debug)]
-pub struct DiagnosticGroup<T> {
-    pub entries: Vec<DiagnosticEntry<T>>,
-    pub primary_ix: usize,
-}
-
-#[derive(Clone, Debug)]
-pub struct Summary {
-    start: Anchor,
-    end: Anchor,
-    min_start: Anchor,
-    max_end: Anchor,
-    count: usize,
-}
-
-impl<T> DiagnosticEntry<T> {
-    // Used to provide diagnostic context to lsp codeAction request
-    pub fn to_lsp_diagnostic_stub(&self) -> lsp::Diagnostic {
-        let code = self
-            .diagnostic
-            .code
-            .clone()
-            .map(lsp::NumberOrString::String);
-
-        lsp::Diagnostic {
-            code,
-            severity: Some(self.diagnostic.severity),
-            ..Default::default()
-        }
-    }
-}
-
-impl DiagnosticSet {
-    pub fn from_sorted_entries<I>(iter: I, buffer: &text::BufferSnapshot) -> Self
-    where
-        I: IntoIterator<Item = DiagnosticEntry<Anchor>>,
-    {
-        Self {
-            diagnostics: SumTree::from_iter(iter, buffer),
-        }
-    }
-
-    pub fn new<I>(iter: I, buffer: &text::BufferSnapshot) -> Self
-    where
-        I: IntoIterator<Item = DiagnosticEntry<PointUtf16>>,
-    {
-        let mut entries = iter.into_iter().collect::<Vec<_>>();
-        entries.sort_unstable_by_key(|entry| (entry.range.start, Reverse(entry.range.end)));
-        Self {
-            diagnostics: SumTree::from_iter(
-                entries.into_iter().map(|entry| DiagnosticEntry {
-                    range: buffer.anchor_before(entry.range.start)
-                        ..buffer.anchor_before(entry.range.end),
-                    diagnostic: entry.diagnostic,
-                }),
-                buffer,
-            ),
-        }
-    }
-
-    pub fn len(&self) -> usize {
-        self.diagnostics.summary().count
-    }
-
-    pub fn iter(&self) -> impl Iterator<Item = &DiagnosticEntry<Anchor>> {
-        self.diagnostics.iter()
-    }
-
-    pub fn range<'a, T, O>(
-        &'a self,
-        range: Range<T>,
-        buffer: &'a text::BufferSnapshot,
-        inclusive: bool,
-        reversed: bool,
-    ) -> impl 'a + Iterator<Item = DiagnosticEntry<O>>
-    where
-        T: 'a + ToOffset,
-        O: FromAnchor,
-    {
-        let end_bias = if inclusive { Bias::Right } else { Bias::Left };
-        let range = buffer.anchor_before(range.start)..buffer.anchor_at(range.end, end_bias);
-        let mut cursor = self.diagnostics.filter::<_, ()>({
-            move |summary: &Summary| {
-                let start_cmp = range.start.cmp(&summary.max_end, buffer);
-                let end_cmp = range.end.cmp(&summary.min_start, buffer);
-                if inclusive {
-                    start_cmp <= Ordering::Equal && end_cmp >= Ordering::Equal
-                } else {
-                    start_cmp == Ordering::Less && end_cmp == Ordering::Greater
-                }
-            }
-        });
-
-        if reversed {
-            cursor.prev(buffer);
-        } else {
-            cursor.next(buffer);
-        }
-        iter::from_fn({
-            move || {
-                if let Some(diagnostic) = cursor.item() {
-                    if reversed {
-                        cursor.prev(buffer);
-                    } else {
-                        cursor.next(buffer);
-                    }
-                    Some(diagnostic.resolve(buffer))
-                } else {
-                    None
-                }
-            }
-        })
-    }
-
-    pub fn groups(
-        &self,
-        language_server_id: LanguageServerId,
-        output: &mut Vec<(LanguageServerId, DiagnosticGroup<Anchor>)>,
-        buffer: &text::BufferSnapshot,
-    ) {
-        let mut groups = HashMap::default();
-        for entry in self.diagnostics.iter() {
-            groups
-                .entry(entry.diagnostic.group_id)
-                .or_insert(Vec::new())
-                .push(entry.clone());
-        }
-
-        let start_ix = output.len();
-        output.extend(groups.into_values().filter_map(|mut entries| {
-            entries.sort_unstable_by(|a, b| a.range.start.cmp(&b.range.start, buffer));
-            entries
-                .iter()
-                .position(|entry| entry.diagnostic.is_primary)
-                .map(|primary_ix| {
-                    (
-                        language_server_id,
-                        DiagnosticGroup {
-                            entries,
-                            primary_ix,
-                        },
-                    )
-                })
-        }));
-        output[start_ix..].sort_unstable_by(|(id_a, group_a), (id_b, group_b)| {
-            group_a.entries[group_a.primary_ix]
-                .range
-                .start
-                .cmp(&group_b.entries[group_b.primary_ix].range.start, buffer)
-                .then_with(|| id_a.cmp(&id_b))
-        });
-    }
-
-    pub fn group<'a, O: FromAnchor>(
-        &'a self,
-        group_id: usize,
-        buffer: &'a text::BufferSnapshot,
-    ) -> impl 'a + Iterator<Item = DiagnosticEntry<O>> {
-        self.iter()
-            .filter(move |entry| entry.diagnostic.group_id == group_id)
-            .map(|entry| entry.resolve(buffer))
-    }
-}
-impl sum_tree::Item for DiagnosticEntry<Anchor> {
-    type Summary = Summary;
-
-    fn summary(&self) -> Self::Summary {
-        Summary {
-            start: self.range.start,
-            end: self.range.end,
-            min_start: self.range.start,
-            max_end: self.range.end,
-            count: 1,
-        }
-    }
-}
-
-impl DiagnosticEntry<Anchor> {
-    pub fn resolve<O: FromAnchor>(&self, buffer: &text::BufferSnapshot) -> DiagnosticEntry<O> {
-        DiagnosticEntry {
-            range: O::from_anchor(&self.range.start, buffer)
-                ..O::from_anchor(&self.range.end, buffer),
-            diagnostic: self.diagnostic.clone(),
-        }
-    }
-}
-
-impl Default for Summary {
-    fn default() -> Self {
-        Self {
-            start: Anchor::MIN,
-            end: Anchor::MAX,
-            min_start: Anchor::MAX,
-            max_end: Anchor::MIN,
-            count: 0,
-        }
-    }
-}
-
-impl sum_tree::Summary for Summary {
-    type Context = text::BufferSnapshot;
-
-    fn add_summary(&mut self, other: &Self, buffer: &Self::Context) {
-        if other.min_start.cmp(&self.min_start, buffer).is_lt() {
-            self.min_start = other.min_start;
-        }
-        if other.max_end.cmp(&self.max_end, buffer).is_gt() {
-            self.max_end = other.max_end;
-        }
-        self.start = other.start;
-        self.end = other.end;
-        self.count += other.count;
-    }
-}

crates/language2/src/highlight_map.rs 🔗

@@ -1,111 +0,0 @@
-use gpui::HighlightStyle;
-use std::sync::Arc;
-use theme::SyntaxTheme;
-
-#[derive(Clone, Debug)]
-pub struct HighlightMap(Arc<[HighlightId]>);
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub struct HighlightId(pub u32);
-
-const DEFAULT_SYNTAX_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX);
-
-impl HighlightMap {
-    pub fn new(capture_names: &[&str], theme: &SyntaxTheme) -> Self {
-        // For each capture name in the highlight query, find the longest
-        // key in the theme's syntax styles that matches all of the
-        // dot-separated components of the capture name.
-        HighlightMap(
-            capture_names
-                .iter()
-                .map(|capture_name| {
-                    theme
-                        .highlights
-                        .iter()
-                        .enumerate()
-                        .filter_map(|(i, (key, _))| {
-                            let mut len = 0;
-                            let capture_parts = capture_name.split('.');
-                            for key_part in key.split('.') {
-                                if capture_parts.clone().any(|part| part == key_part) {
-                                    len += 1;
-                                } else {
-                                    return None;
-                                }
-                            }
-                            Some((i, len))
-                        })
-                        .max_by_key(|(_, len)| *len)
-                        .map_or(DEFAULT_SYNTAX_HIGHLIGHT_ID, |(i, _)| HighlightId(i as u32))
-                })
-                .collect(),
-        )
-    }
-
-    pub fn get(&self, capture_id: u32) -> HighlightId {
-        self.0
-            .get(capture_id as usize)
-            .copied()
-            .unwrap_or(DEFAULT_SYNTAX_HIGHLIGHT_ID)
-    }
-}
-
-impl HighlightId {
-    pub fn is_default(&self) -> bool {
-        *self == DEFAULT_SYNTAX_HIGHLIGHT_ID
-    }
-
-    pub fn style(&self, theme: &SyntaxTheme) -> Option<HighlightStyle> {
-        theme.highlights.get(self.0 as usize).map(|entry| entry.1)
-    }
-
-    pub fn name<'a>(&self, theme: &'a SyntaxTheme) -> Option<&'a str> {
-        theme.highlights.get(self.0 as usize).map(|e| e.0.as_str())
-    }
-}
-
-impl Default for HighlightMap {
-    fn default() -> Self {
-        Self(Arc::new([]))
-    }
-}
-
-impl Default for HighlightId {
-    fn default() -> Self {
-        DEFAULT_SYNTAX_HIGHLIGHT_ID
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use gpui::rgba;
-
-    #[test]
-    fn test_highlight_map() {
-        let theme = SyntaxTheme {
-            highlights: [
-                ("function", rgba(0x100000ff)),
-                ("function.method", rgba(0x200000ff)),
-                ("function.async", rgba(0x300000ff)),
-                ("variable.builtin.self.rust", rgba(0x400000ff)),
-                ("variable.builtin", rgba(0x500000ff)),
-                ("variable", rgba(0x600000ff)),
-            ]
-            .iter()
-            .map(|(name, color)| (name.to_string(), (*color).into()))
-            .collect(),
-        };
-
-        let capture_names = &[
-            "function.special",
-            "function.async.rust",
-            "variable.builtin.self",
-        ];
-
-        let map = HighlightMap::new(capture_names, &theme);
-        assert_eq!(map.get(0).name(&theme), Some("function"));
-        assert_eq!(map.get(1).name(&theme), Some("function.async"));
-        assert_eq!(map.get(2).name(&theme), Some("variable.builtin"));
-    }
-}

crates/language2/src/language2.rs 🔗

@@ -1,2025 +0,0 @@
-mod buffer;
-mod diagnostic_set;
-mod highlight_map;
-pub mod language_settings;
-mod outline;
-pub mod proto;
-mod syntax_map;
-
-#[cfg(test)]
-mod buffer_tests;
-pub mod markdown;
-
-use anyhow::{anyhow, Context, Result};
-use async_trait::async_trait;
-use collections::{HashMap, HashSet};
-use futures::{
-    channel::{mpsc, oneshot},
-    future::{BoxFuture, Shared},
-    FutureExt, TryFutureExt as _,
-};
-use gpui::{AppContext, AsyncAppContext, BackgroundExecutor, Task};
-pub use highlight_map::HighlightMap;
-use lazy_static::lazy_static;
-use lsp::{CodeActionKind, LanguageServerBinary};
-use parking_lot::{Mutex, RwLock};
-use postage::watch;
-use regex::Regex;
-use serde::{de, Deserialize, Deserializer};
-use serde_json::Value;
-use std::{
-    any::Any,
-    borrow::Cow,
-    cell::RefCell,
-    fmt::Debug,
-    hash::Hash,
-    mem,
-    ops::{Not, Range},
-    path::{Path, PathBuf},
-    str,
-    sync::{
-        atomic::{AtomicU64, AtomicUsize, Ordering::SeqCst},
-        Arc,
-    },
-};
-use syntax_map::SyntaxSnapshot;
-use theme::{SyntaxTheme, Theme};
-use tree_sitter::{self, wasmtime, Query, WasmStore};
-use unicase::UniCase;
-use util::{http::HttpClient, paths::PathExt};
-use util::{post_inc, ResultExt, TryFutureExt as _, UnwrapFuture};
-
-pub use buffer::Operation;
-pub use buffer::*;
-pub use diagnostic_set::DiagnosticEntry;
-pub use lsp::LanguageServerId;
-pub use outline::{Outline, OutlineItem};
-pub use syntax_map::{OwnedSyntaxLayerInfo, SyntaxLayerInfo};
-pub use text::LineEnding;
-pub use tree_sitter::{Parser, Tree};
-
-pub fn init(cx: &mut AppContext) {
-    language_settings::init(cx);
-}
-
-#[derive(Clone, Default)]
-struct LspBinaryStatusSender {
-    txs: Arc<Mutex<Vec<mpsc::UnboundedSender<(Arc<Language>, LanguageServerBinaryStatus)>>>>,
-}
-
-impl LspBinaryStatusSender {
-    fn subscribe(&self) -> mpsc::UnboundedReceiver<(Arc<Language>, LanguageServerBinaryStatus)> {
-        let (tx, rx) = mpsc::unbounded();
-        self.txs.lock().push(tx);
-        rx
-    }
-
-    fn send(&self, language: Arc<Language>, status: LanguageServerBinaryStatus) {
-        let mut txs = self.txs.lock();
-        txs.retain(|tx| {
-            tx.unbounded_send((language.clone(), status.clone()))
-                .is_ok()
-        });
-    }
-}
-
-thread_local! {
-    static PARSER: RefCell<Parser> = {
-        let mut parser = Parser::new();
-        parser.set_wasm_store(WasmStore::new(WASM_ENGINE.clone()).unwrap()).unwrap();
-        RefCell::new(parser)
-    };
-}
-
-lazy_static! {
-    pub static ref WASM_ENGINE: wasmtime::Engine = wasmtime::Engine::default();
-    pub static ref NEXT_GRAMMAR_ID: AtomicUsize = Default::default();
-    pub static ref PLAIN_TEXT: Arc<Language> = Arc::new(Language::new(
-        LanguageConfig {
-            name: "Plain Text".into(),
-            ..Default::default()
-        },
-        None,
-    ));
-}
-
-pub trait ToLspPosition {
-    fn to_lsp_position(self) -> lsp::Position;
-}
-
-#[derive(Clone, Debug, PartialEq, Eq, Hash)]
-pub struct LanguageServerName(pub Arc<str>);
-
-/// Represents a Language Server, with certain cached sync properties.
-/// Uses [`LspAdapter`] under the hood, but calls all 'static' methods
-/// once at startup, and caches the results.
-pub struct CachedLspAdapter {
-    pub name: LanguageServerName,
-    pub short_name: &'static str,
-    pub initialization_options: Option<Value>,
-    pub disk_based_diagnostic_sources: Vec<String>,
-    pub disk_based_diagnostics_progress_token: Option<String>,
-    pub language_ids: HashMap<String, String>,
-    pub adapter: Arc<dyn LspAdapter>,
-    pub reinstall_attempt_count: AtomicU64,
-}
-
-impl CachedLspAdapter {
-    pub async fn new(adapter: Arc<dyn LspAdapter>) -> Arc<Self> {
-        let name = adapter.name().await;
-        let short_name = adapter.short_name();
-        let initialization_options = adapter.initialization_options().await;
-        let disk_based_diagnostic_sources = adapter.disk_based_diagnostic_sources().await;
-        let disk_based_diagnostics_progress_token =
-            adapter.disk_based_diagnostics_progress_token().await;
-        let language_ids = adapter.language_ids().await;
-
-        Arc::new(CachedLspAdapter {
-            name,
-            short_name,
-            initialization_options,
-            disk_based_diagnostic_sources,
-            disk_based_diagnostics_progress_token,
-            language_ids,
-            adapter,
-            reinstall_attempt_count: AtomicU64::new(0),
-        })
-    }
-
-    pub async fn fetch_latest_server_version(
-        &self,
-        delegate: &dyn LspAdapterDelegate,
-    ) -> Result<Box<dyn 'static + Send + Any>> {
-        self.adapter.fetch_latest_server_version(delegate).await
-    }
-
-    pub fn will_fetch_server(
-        &self,
-        delegate: &Arc<dyn LspAdapterDelegate>,
-        cx: &mut AsyncAppContext,
-    ) -> Option<Task<Result<()>>> {
-        self.adapter.will_fetch_server(delegate, cx)
-    }
-
-    pub fn will_start_server(
-        &self,
-        delegate: &Arc<dyn LspAdapterDelegate>,
-        cx: &mut AsyncAppContext,
-    ) -> Option<Task<Result<()>>> {
-        self.adapter.will_start_server(delegate, cx)
-    }
-
-    pub async fn fetch_server_binary(
-        &self,
-        version: Box<dyn 'static + Send + Any>,
-        container_dir: PathBuf,
-        delegate: &dyn LspAdapterDelegate,
-    ) -> Result<LanguageServerBinary> {
-        self.adapter
-            .fetch_server_binary(version, container_dir, delegate)
-            .await
-    }
-
-    pub async fn cached_server_binary(
-        &self,
-        container_dir: PathBuf,
-        delegate: &dyn LspAdapterDelegate,
-    ) -> Option<LanguageServerBinary> {
-        self.adapter
-            .cached_server_binary(container_dir, delegate)
-            .await
-    }
-
-    pub fn can_be_reinstalled(&self) -> bool {
-        self.adapter.can_be_reinstalled()
-    }
-
-    pub async fn installation_test_binary(
-        &self,
-        container_dir: PathBuf,
-    ) -> Option<LanguageServerBinary> {
-        self.adapter.installation_test_binary(container_dir).await
-    }
-
-    pub fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
-        self.adapter.code_action_kinds()
-    }
-
-    pub fn workspace_configuration(
-        &self,
-        workspace_root: &Path,
-        cx: &mut AppContext,
-    ) -> BoxFuture<'static, Value> {
-        self.adapter.workspace_configuration(workspace_root, cx)
-    }
-
-    pub fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) {
-        self.adapter.process_diagnostics(params)
-    }
-
-    pub async fn process_completion(&self, completion_item: &mut lsp::CompletionItem) {
-        self.adapter.process_completion(completion_item).await
-    }
-
-    pub async fn label_for_completion(
-        &self,
-        completion_item: &lsp::CompletionItem,
-        language: &Arc<Language>,
-    ) -> Option<CodeLabel> {
-        self.adapter
-            .label_for_completion(completion_item, language)
-            .await
-    }
-
-    pub async fn label_for_symbol(
-        &self,
-        name: &str,
-        kind: lsp::SymbolKind,
-        language: &Arc<Language>,
-    ) -> Option<CodeLabel> {
-        self.adapter.label_for_symbol(name, kind, language).await
-    }
-
-    pub fn prettier_plugins(&self) -> &[&'static str] {
-        self.adapter.prettier_plugins()
-    }
-}
-
-pub trait LspAdapterDelegate: Send + Sync {
-    fn show_notification(&self, message: &str, cx: &mut AppContext);
-    fn http_client(&self) -> Arc<dyn HttpClient>;
-}
-
-#[async_trait]
-pub trait LspAdapter: 'static + Send + Sync {
-    async fn name(&self) -> LanguageServerName;
-
-    fn short_name(&self) -> &'static str;
-
-    async fn fetch_latest_server_version(
-        &self,
-        delegate: &dyn LspAdapterDelegate,
-    ) -> Result<Box<dyn 'static + Send + Any>>;
-
-    fn will_fetch_server(
-        &self,
-        _: &Arc<dyn LspAdapterDelegate>,
-        _: &mut AsyncAppContext,
-    ) -> Option<Task<Result<()>>> {
-        None
-    }
-
-    fn will_start_server(
-        &self,
-        _: &Arc<dyn LspAdapterDelegate>,
-        _: &mut AsyncAppContext,
-    ) -> Option<Task<Result<()>>> {
-        None
-    }
-
-    async fn fetch_server_binary(
-        &self,
-        version: Box<dyn 'static + Send + Any>,
-        container_dir: PathBuf,
-        delegate: &dyn LspAdapterDelegate,
-    ) -> Result<LanguageServerBinary>;
-
-    async fn cached_server_binary(
-        &self,
-        container_dir: PathBuf,
-        delegate: &dyn LspAdapterDelegate,
-    ) -> Option<LanguageServerBinary>;
-
-    fn can_be_reinstalled(&self) -> bool {
-        true
-    }
-
-    async fn installation_test_binary(
-        &self,
-        container_dir: PathBuf,
-    ) -> Option<LanguageServerBinary>;
-
-    fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
-
-    async fn process_completion(&self, _: &mut lsp::CompletionItem) {}
-
-    async fn label_for_completion(
-        &self,
-        _: &lsp::CompletionItem,
-        _: &Arc<Language>,
-    ) -> Option<CodeLabel> {
-        None
-    }
-
-    async fn label_for_symbol(
-        &self,
-        _: &str,
-        _: lsp::SymbolKind,
-        _: &Arc<Language>,
-    ) -> Option<CodeLabel> {
-        None
-    }
-
-    async fn initialization_options(&self) -> Option<Value> {
-        None
-    }
-
-    fn workspace_configuration(&self, _: &Path, _: &mut AppContext) -> BoxFuture<'static, Value> {
-        futures::future::ready(serde_json::json!({})).boxed()
-    }
-
-    fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
-        Some(vec![
-            CodeActionKind::EMPTY,
-            CodeActionKind::QUICKFIX,
-            CodeActionKind::REFACTOR,
-            CodeActionKind::REFACTOR_EXTRACT,
-            CodeActionKind::SOURCE,
-        ])
-    }
-
-    async fn disk_based_diagnostic_sources(&self) -> Vec<String> {
-        Default::default()
-    }
-
-    async fn disk_based_diagnostics_progress_token(&self) -> Option<String> {
-        None
-    }
-
-    async fn language_ids(&self) -> HashMap<String, String> {
-        Default::default()
-    }
-
-    fn prettier_plugins(&self) -> &[&'static str] {
-        &[]
-    }
-}
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct CodeLabel {
-    pub text: String,
-    pub runs: Vec<(Range<usize>, HighlightId)>,
-    pub filter_range: Range<usize>,
-}
-
-#[derive(Clone, Deserialize)]
-pub struct LanguageConfig {
-    pub name: Arc<str>,
-    pub grammar_name: Option<Arc<str>>,
-    pub path_suffixes: Vec<String>,
-    pub brackets: BracketPairConfig,
-    #[serde(default, deserialize_with = "deserialize_regex")]
-    pub first_line_pattern: Option<Regex>,
-    #[serde(default = "auto_indent_using_last_non_empty_line_default")]
-    pub auto_indent_using_last_non_empty_line: bool,
-    #[serde(default, deserialize_with = "deserialize_regex")]
-    pub increase_indent_pattern: Option<Regex>,
-    #[serde(default, deserialize_with = "deserialize_regex")]
-    pub decrease_indent_pattern: Option<Regex>,
-    #[serde(default)]
-    pub autoclose_before: String,
-    #[serde(default)]
-    pub line_comment: Option<Arc<str>>,
-    #[serde(default)]
-    pub collapsed_placeholder: String,
-    #[serde(default)]
-    pub block_comment: Option<(Arc<str>, Arc<str>)>,
-    #[serde(default)]
-    pub scope_opt_in_language_servers: Vec<String>,
-    #[serde(default)]
-    pub overrides: HashMap<String, LanguageConfigOverride>,
-    #[serde(default)]
-    pub word_characters: HashSet<char>,
-    #[serde(default)]
-    pub prettier_parser_name: Option<String>,
-}
-
-#[derive(Debug, Default)]
-pub struct LanguageQueries {
-    pub highlights: Option<Cow<'static, str>>,
-    pub brackets: Option<Cow<'static, str>>,
-    pub indents: Option<Cow<'static, str>>,
-    pub outline: Option<Cow<'static, str>>,
-    pub embedding: Option<Cow<'static, str>>,
-    pub injections: Option<Cow<'static, str>>,
-    pub overrides: Option<Cow<'static, str>>,
-}
-
-#[derive(Clone, Debug)]
-pub struct LanguageScope {
-    language: Arc<Language>,
-    override_id: Option<u32>,
-}
-
-#[derive(Clone, Deserialize, Default, Debug)]
-pub struct LanguageConfigOverride {
-    #[serde(default)]
-    pub line_comment: Override<Arc<str>>,
-    #[serde(default)]
-    pub block_comment: Override<(Arc<str>, Arc<str>)>,
-    #[serde(skip_deserializing)]
-    pub disabled_bracket_ixs: Vec<u16>,
-    #[serde(default)]
-    pub word_characters: Override<HashSet<char>>,
-    #[serde(default)]
-    pub opt_into_language_servers: Vec<String>,
-}
-
-#[derive(Clone, Deserialize, Debug)]
-#[serde(untagged)]
-pub enum Override<T> {
-    Remove { remove: bool },
-    Set(T),
-}
-
-impl<T> Default for Override<T> {
-    fn default() -> Self {
-        Override::Remove { remove: false }
-    }
-}
-
-impl<T> Override<T> {
-    fn as_option<'a>(this: Option<&'a Self>, original: Option<&'a T>) -> Option<&'a T> {
-        match this {
-            Some(Self::Set(value)) => Some(value),
-            Some(Self::Remove { remove: true }) => None,
-            Some(Self::Remove { remove: false }) | None => original,
-        }
-    }
-}
-
-impl Default for LanguageConfig {
-    fn default() -> Self {
-        Self {
-            name: "".into(),
-            grammar_name: None,
-            path_suffixes: Default::default(),
-            brackets: Default::default(),
-            auto_indent_using_last_non_empty_line: auto_indent_using_last_non_empty_line_default(),
-            first_line_pattern: Default::default(),
-            increase_indent_pattern: Default::default(),
-            decrease_indent_pattern: Default::default(),
-            autoclose_before: Default::default(),
-            line_comment: Default::default(),
-            block_comment: Default::default(),
-            scope_opt_in_language_servers: Default::default(),
-            overrides: Default::default(),
-            collapsed_placeholder: Default::default(),
-            word_characters: Default::default(),
-            prettier_parser_name: None,
-        }
-    }
-}
-
-fn auto_indent_using_last_non_empty_line_default() -> bool {
-    true
-}
-
-fn deserialize_regex<'de, D: Deserializer<'de>>(d: D) -> Result<Option<Regex>, D::Error> {
-    let source = Option::<String>::deserialize(d)?;
-    if let Some(source) = source {
-        Ok(Some(regex::Regex::new(&source).map_err(de::Error::custom)?))
-    } else {
-        Ok(None)
-    }
-}
-
-#[cfg(any(test, feature = "test-support"))]
-pub struct FakeLspAdapter {
-    pub name: &'static str,
-    pub initialization_options: Option<Value>,
-    pub capabilities: lsp::ServerCapabilities,
-    pub initializer: Option<Box<dyn 'static + Send + Sync + Fn(&mut lsp::FakeLanguageServer)>>,
-    pub disk_based_diagnostics_progress_token: Option<String>,
-    pub disk_based_diagnostics_sources: Vec<String>,
-    pub prettier_plugins: Vec<&'static str>,
-}
-
-#[derive(Clone, Debug, Default)]
-pub struct BracketPairConfig {
-    pub pairs: Vec<BracketPair>,
-    pub disabled_scopes_by_bracket_ix: Vec<Vec<String>>,
-}
-
-impl<'de> Deserialize<'de> for BracketPairConfig {
-    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
-    where
-        D: Deserializer<'de>,
-    {
-        #[derive(Deserialize)]
-        pub struct Entry {
-            #[serde(flatten)]
-            pub bracket_pair: BracketPair,
-            #[serde(default)]
-            pub not_in: Vec<String>,
-        }
-
-        let result = Vec::<Entry>::deserialize(deserializer)?;
-        let mut brackets = Vec::with_capacity(result.len());
-        let mut disabled_scopes_by_bracket_ix = Vec::with_capacity(result.len());
-        for entry in result {
-            brackets.push(entry.bracket_pair);
-            disabled_scopes_by_bracket_ix.push(entry.not_in);
-        }
-
-        Ok(BracketPairConfig {
-            pairs: brackets,
-            disabled_scopes_by_bracket_ix,
-        })
-    }
-}
-
-#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
-pub struct BracketPair {
-    pub start: String,
-    pub end: String,
-    pub close: bool,
-    pub newline: bool,
-}
-
-pub struct Language {
-    pub(crate) config: LanguageConfig,
-    pub(crate) grammar: Option<Arc<Grammar>>,
-    pub(crate) adapters: Vec<Arc<CachedLspAdapter>>,
-
-    #[cfg(any(test, feature = "test-support"))]
-    fake_adapter: Option<(
-        mpsc::UnboundedSender<lsp::FakeLanguageServer>,
-        Arc<FakeLspAdapter>,
-    )>,
-}
-
-pub struct Grammar {
-    id: usize,
-    pub ts_language: tree_sitter::Language,
-    pub(crate) error_query: Query,
-    pub(crate) highlights_query: Option<Query>,
-    pub(crate) brackets_config: Option<BracketConfig>,
-    pub(crate) indents_config: Option<IndentConfig>,
-    pub outline_config: Option<OutlineConfig>,
-    pub embedding_config: Option<EmbeddingConfig>,
-    pub(crate) injection_config: Option<InjectionConfig>,
-    pub(crate) override_config: Option<OverrideConfig>,
-    pub(crate) highlight_map: Mutex<HighlightMap>,
-}
-
-struct IndentConfig {
-    query: Query,
-    indent_capture_ix: u32,
-    start_capture_ix: Option<u32>,
-    end_capture_ix: Option<u32>,
-    outdent_capture_ix: Option<u32>,
-}
-
-pub struct OutlineConfig {
-    pub query: Query,
-    pub item_capture_ix: u32,
-    pub name_capture_ix: u32,
-    pub context_capture_ix: Option<u32>,
-    pub extra_context_capture_ix: Option<u32>,
-}
-
-#[derive(Debug)]
-pub struct EmbeddingConfig {
-    pub query: Query,
-    pub item_capture_ix: u32,
-    pub name_capture_ix: Option<u32>,
-    pub context_capture_ix: Option<u32>,
-    pub collapse_capture_ix: Option<u32>,
-    pub keep_capture_ix: Option<u32>,
-}
-
-struct InjectionConfig {
-    query: Query,
-    content_capture_ix: u32,
-    language_capture_ix: Option<u32>,
-    patterns: Vec<InjectionPatternConfig>,
-}
-
-struct OverrideConfig {
-    query: Query,
-    values: HashMap<u32, (String, LanguageConfigOverride)>,
-}
-
-#[derive(Default, Clone)]
-struct InjectionPatternConfig {
-    language: Option<Box<str>>,
-    combined: bool,
-}
-
-struct BracketConfig {
-    query: Query,
-    open_capture_ix: u32,
-    close_capture_ix: u32,
-}
-
-#[derive(Clone)]
-pub enum LanguageServerBinaryStatus {
-    CheckingForUpdate,
-    Downloading,
-    Downloaded,
-    Cached,
-    Failed { error: String },
-}
-
-type AvailableLanguageId = usize;
-
-#[derive(Clone)]
-struct AvailableLanguage {
-    id: AvailableLanguageId,
-    config: LanguageConfig,
-    grammar: AvailableGrammar,
-    lsp_adapters: Vec<Arc<dyn LspAdapter>>,
-    loaded: bool,
-}
-
-#[derive(Clone)]
-enum AvailableGrammar {
-    Native {
-        grammar: tree_sitter::Language,
-        asset_dir: &'static str,
-        get_queries: fn(&str) -> LanguageQueries,
-    },
-    Wasm {
-        grammar_name: Arc<str>,
-        path: Arc<Path>,
-    },
-}
-
-pub struct LanguageRegistry {
-    state: RwLock<LanguageRegistryState>,
-    language_server_download_dir: Option<Arc<Path>>,
-    login_shell_env_loaded: Shared<Task<()>>,
-    #[allow(clippy::type_complexity)]
-    lsp_binary_paths: Mutex<
-        HashMap<LanguageServerName, Shared<Task<Result<LanguageServerBinary, Arc<anyhow::Error>>>>>,
-    >,
-    executor: Option<BackgroundExecutor>,
-    lsp_binary_status_tx: LspBinaryStatusSender,
-}
-
-struct LanguageRegistryState {
-    next_language_server_id: usize,
-    languages: Vec<Arc<Language>>,
-    available_languages: Vec<AvailableLanguage>,
-    next_available_language_id: AvailableLanguageId,
-    loading_languages: HashMap<AvailableLanguageId, Vec<oneshot::Sender<Result<Arc<Language>>>>>,
-    subscription: (watch::Sender<()>, watch::Receiver<()>),
-    theme: Option<Arc<Theme>>,
-    version: usize,
-    reload_count: usize,
-}
-
-pub struct PendingLanguageServer {
-    pub server_id: LanguageServerId,
-    pub task: Task<Result<lsp::LanguageServer>>,
-    pub container_dir: Option<Arc<Path>>,
-}
-
-impl LanguageRegistry {
-    pub fn new(login_shell_env_loaded: Task<()>) -> Self {
-        Self {
-            state: RwLock::new(LanguageRegistryState {
-                next_language_server_id: 0,
-                languages: vec![PLAIN_TEXT.clone()],
-                available_languages: Default::default(),
-                next_available_language_id: 0,
-                loading_languages: Default::default(),
-                subscription: watch::channel(),
-                theme: Default::default(),
-                version: 0,
-                reload_count: 0,
-            }),
-            language_server_download_dir: None,
-            login_shell_env_loaded: login_shell_env_loaded.shared(),
-            lsp_binary_paths: Default::default(),
-            executor: None,
-            lsp_binary_status_tx: Default::default(),
-        }
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn test() -> Self {
-        Self::new(Task::ready(()))
-    }
-
-    pub fn set_executor(&mut self, executor: BackgroundExecutor) {
-        self.executor = Some(executor);
-    }
-
-    /// Clear out all of the loaded languages and reload them from scratch.
-    ///
-    /// This is useful in development, when queries have changed.
-    #[cfg(debug_assertions)]
-    pub fn reload(&self) {
-        self.state.write().reload();
-    }
-
-    pub fn register(
-        &self,
-        asset_dir: &'static str,
-        config: LanguageConfig,
-        grammar: tree_sitter::Language,
-        lsp_adapters: Vec<Arc<dyn LspAdapter>>,
-        get_queries: fn(&str) -> LanguageQueries,
-    ) {
-        let state = &mut *self.state.write();
-        state.available_languages.push(AvailableLanguage {
-            id: post_inc(&mut state.next_available_language_id),
-            config,
-            grammar: AvailableGrammar::Native {
-                grammar,
-                get_queries,
-                asset_dir,
-            },
-            lsp_adapters,
-            loaded: false,
-        });
-    }
-
-    pub fn register_wasm(&self, path: Arc<Path>, grammar_name: Arc<str>, config: LanguageConfig) {
-        let state = &mut *self.state.write();
-        state.available_languages.push(AvailableLanguage {
-            id: post_inc(&mut state.next_available_language_id),
-            config,
-            grammar: AvailableGrammar::Wasm { grammar_name, path },
-            lsp_adapters: Vec::new(),
-            loaded: false,
-        });
-    }
-
-    pub fn language_names(&self) -> Vec<String> {
-        let state = self.state.read();
-        let mut result = state
-            .available_languages
-            .iter()
-            .filter_map(|l| l.loaded.not().then_some(l.config.name.to_string()))
-            .chain(state.languages.iter().map(|l| l.config.name.to_string()))
-            .collect::<Vec<_>>();
-        result.sort_unstable_by_key(|language_name| language_name.to_lowercase());
-        result
-    }
-
-    pub fn add(&self, language: Arc<Language>) {
-        self.state.write().add(language);
-    }
-
-    pub fn subscribe(&self) -> watch::Receiver<()> {
-        self.state.read().subscription.1.clone()
-    }
-
-    /// The number of times that the registry has been changed,
-    /// by adding languages or reloading.
-    pub fn version(&self) -> usize {
-        self.state.read().version
-    }
-
-    /// The number of times that the registry has been reloaded.
-    pub fn reload_count(&self) -> usize {
-        self.state.read().reload_count
-    }
-
-    pub fn set_theme(&self, theme: Arc<Theme>) {
-        let mut state = self.state.write();
-        state.theme = Some(theme.clone());
-        for language in &state.languages {
-            language.set_theme(&theme.syntax());
-        }
-    }
-
-    pub fn set_language_server_download_dir(&mut self, path: impl Into<Arc<Path>>) {
-        self.language_server_download_dir = Some(path.into());
-    }
-
-    pub fn language_for_name(
-        self: &Arc<Self>,
-        name: &str,
-    ) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
-        let name = UniCase::new(name);
-        self.get_or_load_language(|config| UniCase::new(config.name.as_ref()) == name)
-    }
-
-    pub fn language_for_name_or_extension(
-        self: &Arc<Self>,
-        string: &str,
-    ) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
-        let string = UniCase::new(string);
-        self.get_or_load_language(|config| {
-            UniCase::new(config.name.as_ref()) == string
-                || config
-                    .path_suffixes
-                    .iter()
-                    .any(|suffix| UniCase::new(suffix) == string)
-        })
-    }
-
-    pub fn language_for_file(
-        self: &Arc<Self>,
-        path: impl AsRef<Path>,
-        content: Option<&Rope>,
-    ) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
-        let path = path.as_ref();
-        let filename = path.file_name().and_then(|name| name.to_str());
-        let extension = path.extension_or_hidden_file_name();
-        let path_suffixes = [extension, filename];
-        self.get_or_load_language(|config| {
-            let path_matches = config
-                .path_suffixes
-                .iter()
-                .any(|suffix| path_suffixes.contains(&Some(suffix.as_str())));
-            let content_matches = content.zip(config.first_line_pattern.as_ref()).map_or(
-                false,
-                |(content, pattern)| {
-                    let end = content.clip_point(Point::new(0, 256), Bias::Left);
-                    let end = content.point_to_offset(end);
-                    let text = content.chunks_in_range(0..end).collect::<String>();
-                    pattern.is_match(&text)
-                },
-            );
-            path_matches || content_matches
-        })
-    }
-
-    fn get_or_load_language(
-        self: &Arc<Self>,
-        callback: impl Fn(&LanguageConfig) -> bool,
-    ) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
-        let (tx, rx) = oneshot::channel();
-
-        let mut state = self.state.write();
-        if let Some(language) = state
-            .languages
-            .iter()
-            .find(|language| callback(&language.config))
-        {
-            let _ = tx.send(Ok(language.clone()));
-        } else if let Some(executor) = self.executor.clone() {
-            if let Some(language) = state
-                .available_languages
-                .iter()
-                .find(|l| !l.loaded && callback(&l.config))
-                .cloned()
-            {
-                let txs = state
-                    .loading_languages
-                    .entry(language.id)
-                    .or_insert_with(|| {
-                        let this = self.clone();
-                        executor
-                            .spawn(async move {
-                                let id = language.id;
-                                let name = language.config.name.clone();
-                                let language = async {
-                                    let (grammar, queries) = match language.grammar {
-                                        AvailableGrammar::Native {
-                                            grammar,
-                                            asset_dir,
-                                            get_queries,
-                                        } => (grammar, (get_queries)(asset_dir)),
-                                        AvailableGrammar::Wasm { grammar_name, path } => {
-                                            let mut wasm_path = path.join(grammar_name.as_ref());
-                                            wasm_path.set_extension("wasm");
-                                            let wasm_bytes = std::fs::read(&wasm_path)?;
-                                            let grammar = PARSER.with(|parser| {
-                                                let mut parser = parser.borrow_mut();
-                                                let mut store = parser.take_wasm_store().unwrap();
-                                                let grammar =
-                                                    store.load_language(&grammar_name, &wasm_bytes);
-                                                parser.set_wasm_store(store).unwrap();
-                                                grammar
-                                            })?;
-                                            let mut queries = LanguageQueries::default();
-                                            if let Ok(contents) = std::fs::read_to_string(
-                                                &path.join("highlights.scm"),
-                                            ) {
-                                                queries.highlights = Some(contents.into());
-                                            }
-                                            (grammar, queries)
-                                        }
-                                    };
-                                    Language::new(language.config, Some(grammar))
-                                        .with_lsp_adapters(language.lsp_adapters)
-                                        .await
-                                        .with_queries(queries)
-                                }
-                                .await;
-
-                                match language {
-                                    Ok(language) => {
-                                        let language = Arc::new(language);
-                                        let mut state = this.state.write();
-
-                                        state.add(language.clone());
-                                        state.mark_language_loaded(id);
-                                        if let Some(mut txs) = state.loading_languages.remove(&id) {
-                                            for tx in txs.drain(..) {
-                                                let _ = tx.send(Ok(language.clone()));
-                                            }
-                                        }
-                                    }
-                                    Err(e) => {
-                                        log::error!("failed to load language {name}:\n{:?}", e);
-                                        let mut state = this.state.write();
-                                        state.mark_language_loaded(id);
-                                        if let Some(mut txs) = state.loading_languages.remove(&id) {
-                                            for tx in txs.drain(..) {
-                                                let _ = tx.send(Err(anyhow!(
-                                                    "failed to load language {}: {}",
-                                                    name,
-                                                    e
-                                                )));
-                                            }
-                                        }
-                                    }
-                                };
-                            })
-                            .detach();
-
-                        Vec::new()
-                    });
-                txs.push(tx);
-            } else {
-                let _ = tx.send(Err(anyhow!("language not found")));
-            }
-        } else {
-            let _ = tx.send(Err(anyhow!("executor does not exist")));
-        }
-
-        rx.unwrap()
-    }
-
-    pub fn to_vec(&self) -> Vec<Arc<Language>> {
-        self.state.read().languages.iter().cloned().collect()
-    }
-
-    pub fn create_pending_language_server(
-        self: &Arc<Self>,
-        stderr_capture: Arc<Mutex<Option<String>>>,
-        language: Arc<Language>,
-        adapter: Arc<CachedLspAdapter>,
-        root_path: Arc<Path>,
-        delegate: Arc<dyn LspAdapterDelegate>,
-        cx: &mut AppContext,
-    ) -> Option<PendingLanguageServer> {
-        let server_id = self.state.write().next_language_server_id();
-        log::info!(
-            "starting language server {:?}, path: {root_path:?}, id: {server_id}",
-            adapter.name.0
-        );
-
-        #[cfg(any(test, feature = "test-support"))]
-        if language.fake_adapter.is_some() {
-            let task = cx.spawn(|cx| async move {
-                let (servers_tx, fake_adapter) = language.fake_adapter.as_ref().unwrap();
-                let (server, mut fake_server) = lsp::LanguageServer::fake(
-                    fake_adapter.name.to_string(),
-                    fake_adapter.capabilities.clone(),
-                    cx.clone(),
-                );
-
-                if let Some(initializer) = &fake_adapter.initializer {
-                    initializer(&mut fake_server);
-                }
-
-                let servers_tx = servers_tx.clone();
-                cx.background_executor()
-                    .spawn(async move {
-                        if fake_server
-                            .try_receive_notification::<lsp::notification::Initialized>()
-                            .await
-                            .is_some()
-                        {
-                            servers_tx.unbounded_send(fake_server).ok();
-                        }
-                    })
-                    .detach();
-
-                Ok(server)
-            });
-
-            return Some(PendingLanguageServer {
-                server_id,
-                task,
-                container_dir: None,
-            });
-        }
-
-        let download_dir = self
-            .language_server_download_dir
-            .clone()
-            .ok_or_else(|| anyhow!("language server download directory has not been assigned before starting server"))
-            .log_err()?;
-        let this = self.clone();
-        let language = language.clone();
-        let container_dir: Arc<Path> = Arc::from(download_dir.join(adapter.name.0.as_ref()));
-        let root_path = root_path.clone();
-        let adapter = adapter.clone();
-        let login_shell_env_loaded = self.login_shell_env_loaded.clone();
-        let lsp_binary_statuses = self.lsp_binary_status_tx.clone();
-
-        let task = {
-            let container_dir = container_dir.clone();
-            cx.spawn(move |mut cx| async move {
-                login_shell_env_loaded.await;
-
-                let entry = this
-                    .lsp_binary_paths
-                    .lock()
-                    .entry(adapter.name.clone())
-                    .or_insert_with(|| {
-                        let adapter = adapter.clone();
-                        let language = language.clone();
-                        let delegate = delegate.clone();
-                        cx.spawn(|cx| {
-                            get_binary(
-                                adapter,
-                                language,
-                                delegate,
-                                container_dir,
-                                lsp_binary_statuses,
-                                cx,
-                            )
-                            .map_err(Arc::new)
-                        })
-                        .shared()
-                    })
-                    .clone();
-
-                let binary = match entry.await {
-                    Ok(binary) => binary,
-                    Err(err) => anyhow::bail!("{err}"),
-                };
-
-                if let Some(task) = adapter.will_start_server(&delegate, &mut cx) {
-                    task.await?;
-                }
-
-                lsp::LanguageServer::new(
-                    stderr_capture,
-                    server_id,
-                    binary,
-                    &root_path,
-                    adapter.code_action_kinds(),
-                    cx,
-                )
-            })
-        };
-
-        Some(PendingLanguageServer {
-            server_id,
-            task,
-            container_dir: Some(container_dir),
-        })
-    }
-
-    pub fn language_server_binary_statuses(
-        &self,
-    ) -> mpsc::UnboundedReceiver<(Arc<Language>, LanguageServerBinaryStatus)> {
-        self.lsp_binary_status_tx.subscribe()
-    }
-
-    pub fn delete_server_container(
-        &self,
-        adapter: Arc<CachedLspAdapter>,
-        cx: &mut AppContext,
-    ) -> Task<()> {
-        log::info!("deleting server container");
-
-        let mut lock = self.lsp_binary_paths.lock();
-        lock.remove(&adapter.name);
-
-        let download_dir = self
-            .language_server_download_dir
-            .clone()
-            .expect("language server download directory has not been assigned before deleting server container");
-
-        cx.spawn(|_| async move {
-            let container_dir = download_dir.join(adapter.name.0.as_ref());
-            smol::fs::remove_dir_all(container_dir)
-                .await
-                .context("server container removal")
-                .log_err();
-        })
-    }
-
-    pub fn next_language_server_id(&self) -> LanguageServerId {
-        self.state.write().next_language_server_id()
-    }
-}
-
-impl LanguageRegistryState {
-    fn next_language_server_id(&mut self) -> LanguageServerId {
-        LanguageServerId(post_inc(&mut self.next_language_server_id))
-    }
-
-    fn add(&mut self, language: Arc<Language>) {
-        if let Some(theme) = self.theme.as_ref() {
-            language.set_theme(&theme.syntax());
-        }
-        self.languages.push(language);
-        self.version += 1;
-        *self.subscription.0.borrow_mut() = ();
-    }
-
-    #[cfg(debug_assertions)]
-    fn reload(&mut self) {
-        self.languages.clear();
-        self.version += 1;
-        self.reload_count += 1;
-        for language in &mut self.available_languages {
-            language.loaded = false;
-        }
-        *self.subscription.0.borrow_mut() = ();
-    }
-
-    /// Mark the given language a having been loaded, so that the
-    /// language registry won't try to load it again.
-    fn mark_language_loaded(&mut self, id: AvailableLanguageId) {
-        for language in &mut self.available_languages {
-            if language.id == id {
-                language.loaded = true;
-                break;
-            }
-        }
-    }
-}
-
-#[cfg(any(test, feature = "test-support"))]
-impl Default for LanguageRegistry {
-    fn default() -> Self {
-        Self::test()
-    }
-}
-
-async fn get_binary(
-    adapter: Arc<CachedLspAdapter>,
-    language: Arc<Language>,
-    delegate: Arc<dyn LspAdapterDelegate>,
-    container_dir: Arc<Path>,
-    statuses: LspBinaryStatusSender,
-    mut cx: AsyncAppContext,
-) -> Result<LanguageServerBinary> {
-    if !container_dir.exists() {
-        smol::fs::create_dir_all(&container_dir)
-            .await
-            .context("failed to create container directory")?;
-    }
-
-    if let Some(task) = adapter.will_fetch_server(&delegate, &mut cx) {
-        task.await?;
-    }
-
-    let binary = fetch_latest_binary(
-        adapter.clone(),
-        language.clone(),
-        delegate.as_ref(),
-        &container_dir,
-        statuses.clone(),
-    )
-    .await;
-
-    if let Err(error) = binary.as_ref() {
-        if let Some(binary) = adapter
-            .cached_server_binary(container_dir.to_path_buf(), delegate.as_ref())
-            .await
-        {
-            statuses.send(language.clone(), LanguageServerBinaryStatus::Cached);
-            return Ok(binary);
-        } else {
-            statuses.send(
-                language.clone(),
-                LanguageServerBinaryStatus::Failed {
-                    error: format!("{:?}", error),
-                },
-            );
-        }
-    }
-
-    binary
-}
-
-async fn fetch_latest_binary(
-    adapter: Arc<CachedLspAdapter>,
-    language: Arc<Language>,
-    delegate: &dyn LspAdapterDelegate,
-    container_dir: &Path,
-    lsp_binary_statuses_tx: LspBinaryStatusSender,
-) -> Result<LanguageServerBinary> {
-    let container_dir: Arc<Path> = container_dir.into();
-    lsp_binary_statuses_tx.send(
-        language.clone(),
-        LanguageServerBinaryStatus::CheckingForUpdate,
-    );
-
-    let version_info = adapter.fetch_latest_server_version(delegate).await?;
-    lsp_binary_statuses_tx.send(language.clone(), LanguageServerBinaryStatus::Downloading);
-
-    let binary = adapter
-        .fetch_server_binary(version_info, container_dir.to_path_buf(), delegate)
-        .await?;
-    lsp_binary_statuses_tx.send(language.clone(), LanguageServerBinaryStatus::Downloaded);
-
-    Ok(binary)
-}
-
-impl Language {
-    pub fn new(config: LanguageConfig, ts_language: Option<tree_sitter::Language>) -> Self {
-        Self {
-            config,
-            grammar: ts_language.map(|ts_language| {
-                Arc::new(Grammar {
-                    id: NEXT_GRAMMAR_ID.fetch_add(1, SeqCst),
-                    highlights_query: None,
-                    brackets_config: None,
-                    outline_config: None,
-                    embedding_config: None,
-                    indents_config: None,
-                    injection_config: None,
-                    override_config: None,
-                    error_query: Query::new(&ts_language, "(ERROR) @error").unwrap(),
-                    ts_language,
-                    highlight_map: Default::default(),
-                })
-            }),
-            adapters: Vec::new(),
-
-            #[cfg(any(test, feature = "test-support"))]
-            fake_adapter: None,
-        }
-    }
-
-    pub fn lsp_adapters(&self) -> &[Arc<CachedLspAdapter>] {
-        &self.adapters
-    }
-
-    pub fn id(&self) -> Option<usize> {
-        self.grammar.as_ref().map(|g| g.id)
-    }
-
-    pub fn with_queries(mut self, queries: LanguageQueries) -> Result<Self> {
-        if let Some(query) = queries.highlights {
-            self = self
-                .with_highlights_query(query.as_ref())
-                .context("Error loading highlights query")?;
-        }
-        if let Some(query) = queries.brackets {
-            self = self
-                .with_brackets_query(query.as_ref())
-                .context("Error loading brackets query")?;
-        }
-        if let Some(query) = queries.indents {
-            self = self
-                .with_indents_query(query.as_ref())
-                .context("Error loading indents query")?;
-        }
-        if let Some(query) = queries.outline {
-            self = self
-                .with_outline_query(query.as_ref())
-                .context("Error loading outline query")?;
-        }
-        if let Some(query) = queries.embedding {
-            self = self
-                .with_embedding_query(query.as_ref())
-                .context("Error loading embedding query")?;
-        }
-        if let Some(query) = queries.injections {
-            self = self
-                .with_injection_query(query.as_ref())
-                .context("Error loading injection query")?;
-        }
-        if let Some(query) = queries.overrides {
-            self = self
-                .with_override_query(query.as_ref())
-                .context("Error loading override query")?;
-        }
-        Ok(self)
-    }
-
-    pub fn with_highlights_query(mut self, source: &str) -> Result<Self> {
-        let grammar = self.grammar_mut();
-        grammar.highlights_query = Some(Query::new(&grammar.ts_language, source)?);
-        Ok(self)
-    }
-
-    pub fn with_outline_query(mut self, source: &str) -> Result<Self> {
-        let grammar = self.grammar_mut();
-        let query = Query::new(&grammar.ts_language, source)?;
-        let mut item_capture_ix = None;
-        let mut name_capture_ix = None;
-        let mut context_capture_ix = None;
-        let mut extra_context_capture_ix = None;
-        get_capture_indices(
-            &query,
-            &mut [
-                ("item", &mut item_capture_ix),
-                ("name", &mut name_capture_ix),
-                ("context", &mut context_capture_ix),
-                ("context.extra", &mut extra_context_capture_ix),
-            ],
-        );
-        if let Some((item_capture_ix, name_capture_ix)) = item_capture_ix.zip(name_capture_ix) {
-            grammar.outline_config = Some(OutlineConfig {
-                query,
-                item_capture_ix,
-                name_capture_ix,
-                context_capture_ix,
-                extra_context_capture_ix,
-            });
-        }
-        Ok(self)
-    }
-
-    pub fn with_embedding_query(mut self, source: &str) -> Result<Self> {
-        let grammar = self.grammar_mut();
-        let query = Query::new(&grammar.ts_language, source)?;
-        let mut item_capture_ix = None;
-        let mut name_capture_ix = None;
-        let mut context_capture_ix = None;
-        let mut collapse_capture_ix = None;
-        let mut keep_capture_ix = None;
-        get_capture_indices(
-            &query,
-            &mut [
-                ("item", &mut item_capture_ix),
-                ("name", &mut name_capture_ix),
-                ("context", &mut context_capture_ix),
-                ("keep", &mut keep_capture_ix),
-                ("collapse", &mut collapse_capture_ix),
-            ],
-        );
-        if let Some(item_capture_ix) = item_capture_ix {
-            grammar.embedding_config = Some(EmbeddingConfig {
-                query,
-                item_capture_ix,
-                name_capture_ix,
-                context_capture_ix,
-                collapse_capture_ix,
-                keep_capture_ix,
-            });
-        }
-        Ok(self)
-    }
-
-    pub fn with_brackets_query(mut self, source: &str) -> Result<Self> {
-        let grammar = self.grammar_mut();
-        let query = Query::new(&grammar.ts_language, source)?;
-        let mut open_capture_ix = None;
-        let mut close_capture_ix = None;
-        get_capture_indices(
-            &query,
-            &mut [
-                ("open", &mut open_capture_ix),
-                ("close", &mut close_capture_ix),
-            ],
-        );
-        if let Some((open_capture_ix, close_capture_ix)) = open_capture_ix.zip(close_capture_ix) {
-            grammar.brackets_config = Some(BracketConfig {
-                query,
-                open_capture_ix,
-                close_capture_ix,
-            });
-        }
-        Ok(self)
-    }
-
-    pub fn with_indents_query(mut self, source: &str) -> Result<Self> {
-        let grammar = self.grammar_mut();
-        let query = Query::new(&grammar.ts_language, source)?;
-        let mut indent_capture_ix = None;
-        let mut start_capture_ix = None;
-        let mut end_capture_ix = None;
-        let mut outdent_capture_ix = None;
-        get_capture_indices(
-            &query,
-            &mut [
-                ("indent", &mut indent_capture_ix),
-                ("start", &mut start_capture_ix),
-                ("end", &mut end_capture_ix),
-                ("outdent", &mut outdent_capture_ix),
-            ],
-        );
-        if let Some(indent_capture_ix) = indent_capture_ix {
-            grammar.indents_config = Some(IndentConfig {
-                query,
-                indent_capture_ix,
-                start_capture_ix,
-                end_capture_ix,
-                outdent_capture_ix,
-            });
-        }
-        Ok(self)
-    }
-
-    pub fn with_injection_query(mut self, source: &str) -> Result<Self> {
-        let grammar = self.grammar_mut();
-        let query = Query::new(&grammar.ts_language, source)?;
-        let mut language_capture_ix = None;
-        let mut content_capture_ix = None;
-        get_capture_indices(
-            &query,
-            &mut [
-                ("language", &mut language_capture_ix),
-                ("content", &mut content_capture_ix),
-            ],
-        );
-        let patterns = (0..query.pattern_count())
-            .map(|ix| {
-                let mut config = InjectionPatternConfig::default();
-                for setting in query.property_settings(ix) {
-                    match setting.key.as_ref() {
-                        "language" => {
-                            config.language = setting.value.clone();
-                        }
-                        "combined" => {
-                            config.combined = true;
-                        }
-                        _ => {}
-                    }
-                }
-                config
-            })
-            .collect();
-        if let Some(content_capture_ix) = content_capture_ix {
-            grammar.injection_config = Some(InjectionConfig {
-                query,
-                language_capture_ix,
-                content_capture_ix,
-                patterns,
-            });
-        }
-        Ok(self)
-    }
-
-    pub fn with_override_query(mut self, source: &str) -> anyhow::Result<Self> {
-        let query = Query::new(&self.grammar_mut().ts_language, source)?;
-
-        let mut override_configs_by_id = HashMap::default();
-        for (ix, name) in query.capture_names().iter().enumerate() {
-            if !name.starts_with('_') {
-                let value = self.config.overrides.remove(*name).unwrap_or_default();
-                for server_name in &value.opt_into_language_servers {
-                    if !self
-                        .config
-                        .scope_opt_in_language_servers
-                        .contains(server_name)
-                    {
-                        util::debug_panic!("Server {server_name:?} has been opted-in by scope {name:?} but has not been marked as an opt-in server");
-                    }
-                }
-
-                override_configs_by_id.insert(ix as u32, (name.to_string(), value));
-            }
-        }
-
-        if !self.config.overrides.is_empty() {
-            let keys = self.config.overrides.keys().collect::<Vec<_>>();
-            Err(anyhow!(
-                "language {:?} has overrides in config not in query: {keys:?}",
-                self.config.name
-            ))?;
-        }
-
-        for disabled_scope_name in self
-            .config
-            .brackets
-            .disabled_scopes_by_bracket_ix
-            .iter()
-            .flatten()
-        {
-            if !override_configs_by_id
-                .values()
-                .any(|(scope_name, _)| scope_name == disabled_scope_name)
-            {
-                Err(anyhow!(
-                    "language {:?} has overrides in config not in query: {disabled_scope_name:?}",
-                    self.config.name
-                ))?;
-            }
-        }
-
-        for (name, override_config) in override_configs_by_id.values_mut() {
-            override_config.disabled_bracket_ixs = self
-                .config
-                .brackets
-                .disabled_scopes_by_bracket_ix
-                .iter()
-                .enumerate()
-                .filter_map(|(ix, disabled_scope_names)| {
-                    if disabled_scope_names.contains(name) {
-                        Some(ix as u16)
-                    } else {
-                        None
-                    }
-                })
-                .collect();
-        }
-
-        self.config.brackets.disabled_scopes_by_bracket_ix.clear();
-        self.grammar_mut().override_config = Some(OverrideConfig {
-            query,
-            values: override_configs_by_id,
-        });
-        Ok(self)
-    }
-
-    fn grammar_mut(&mut self) -> &mut Grammar {
-        Arc::get_mut(self.grammar.as_mut().unwrap()).unwrap()
-    }
-
-    pub async fn with_lsp_adapters(mut self, lsp_adapters: Vec<Arc<dyn LspAdapter>>) -> Self {
-        for adapter in lsp_adapters {
-            self.adapters.push(CachedLspAdapter::new(adapter).await);
-        }
-        self
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub async fn set_fake_lsp_adapter(
-        &mut self,
-        fake_lsp_adapter: Arc<FakeLspAdapter>,
-    ) -> mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
-        let (servers_tx, servers_rx) = mpsc::unbounded();
-        self.fake_adapter = Some((servers_tx, fake_lsp_adapter.clone()));
-        let adapter = CachedLspAdapter::new(Arc::new(fake_lsp_adapter)).await;
-        self.adapters = vec![adapter];
-        servers_rx
-    }
-
-    pub fn name(&self) -> Arc<str> {
-        self.config.name.clone()
-    }
-
-    pub async fn disk_based_diagnostic_sources(&self) -> &[String] {
-        match self.adapters.first().as_ref() {
-            Some(adapter) => &adapter.disk_based_diagnostic_sources,
-            None => &[],
-        }
-    }
-
-    pub async fn disk_based_diagnostics_progress_token(&self) -> Option<&str> {
-        for adapter in &self.adapters {
-            let token = adapter.disk_based_diagnostics_progress_token.as_deref();
-            if token.is_some() {
-                return token;
-            }
-        }
-
-        None
-    }
-
-    pub async fn process_completion(self: &Arc<Self>, completion: &mut lsp::CompletionItem) {
-        for adapter in &self.adapters {
-            adapter.process_completion(completion).await;
-        }
-    }
-
-    pub async fn label_for_completion(
-        self: &Arc<Self>,
-        completion: &lsp::CompletionItem,
-    ) -> Option<CodeLabel> {
-        self.adapters
-            .first()
-            .as_ref()?
-            .label_for_completion(completion, self)
-            .await
-    }
-
-    pub async fn label_for_symbol(
-        self: &Arc<Self>,
-        name: &str,
-        kind: lsp::SymbolKind,
-    ) -> Option<CodeLabel> {
-        self.adapters
-            .first()
-            .as_ref()?
-            .label_for_symbol(name, kind, self)
-            .await
-    }
-
-    pub fn highlight_text<'a>(
-        self: &'a Arc<Self>,
-        text: &'a Rope,
-        range: Range<usize>,
-    ) -> Vec<(Range<usize>, HighlightId)> {
-        let mut result = Vec::new();
-        if let Some(grammar) = &self.grammar {
-            let tree = grammar.parse_text(text, None);
-            let captures =
-                SyntaxSnapshot::single_tree_captures(range.clone(), text, &tree, self, |grammar| {
-                    grammar.highlights_query.as_ref()
-                });
-            let highlight_maps = vec![grammar.highlight_map()];
-            let mut offset = 0;
-            for chunk in BufferChunks::new(text, range, Some((captures, highlight_maps)), vec![]) {
-                let end_offset = offset + chunk.text.len();
-                if let Some(highlight_id) = chunk.syntax_highlight_id {
-                    if !highlight_id.is_default() {
-                        result.push((offset..end_offset, highlight_id));
-                    }
-                }
-                offset = end_offset;
-            }
-        }
-        result
-    }
-
-    pub fn path_suffixes(&self) -> &[String] {
-        &self.config.path_suffixes
-    }
-
-    pub fn should_autoclose_before(&self, c: char) -> bool {
-        c.is_whitespace() || self.config.autoclose_before.contains(c)
-    }
-
-    pub fn set_theme(&self, theme: &SyntaxTheme) {
-        if let Some(grammar) = self.grammar.as_ref() {
-            if let Some(highlights_query) = &grammar.highlights_query {
-                *grammar.highlight_map.lock() =
-                    HighlightMap::new(highlights_query.capture_names(), theme);
-            }
-        }
-    }
-
-    pub fn grammar(&self) -> Option<&Arc<Grammar>> {
-        self.grammar.as_ref()
-    }
-
-    pub fn default_scope(self: &Arc<Self>) -> LanguageScope {
-        LanguageScope {
-            language: self.clone(),
-            override_id: None,
-        }
-    }
-
-    pub fn prettier_parser_name(&self) -> Option<&str> {
-        self.config.prettier_parser_name.as_deref()
-    }
-}
-
-impl LanguageScope {
-    pub fn collapsed_placeholder(&self) -> &str {
-        self.language.config.collapsed_placeholder.as_ref()
-    }
-
-    pub fn line_comment_prefix(&self) -> Option<&Arc<str>> {
-        Override::as_option(
-            self.config_override().map(|o| &o.line_comment),
-            self.language.config.line_comment.as_ref(),
-        )
-    }
-
-    pub fn block_comment_delimiters(&self) -> Option<(&Arc<str>, &Arc<str>)> {
-        Override::as_option(
-            self.config_override().map(|o| &o.block_comment),
-            self.language.config.block_comment.as_ref(),
-        )
-        .map(|e| (&e.0, &e.1))
-    }
-
-    pub fn word_characters(&self) -> Option<&HashSet<char>> {
-        Override::as_option(
-            self.config_override().map(|o| &o.word_characters),
-            Some(&self.language.config.word_characters),
-        )
-    }
-
-    pub fn brackets(&self) -> impl Iterator<Item = (&BracketPair, bool)> {
-        let mut disabled_ids = self
-            .config_override()
-            .map_or(&[] as _, |o| o.disabled_bracket_ixs.as_slice());
-        self.language
-            .config
-            .brackets
-            .pairs
-            .iter()
-            .enumerate()
-            .map(move |(ix, bracket)| {
-                let mut is_enabled = true;
-                if let Some(next_disabled_ix) = disabled_ids.first() {
-                    if ix == *next_disabled_ix as usize {
-                        disabled_ids = &disabled_ids[1..];
-                        is_enabled = false;
-                    }
-                }
-                (bracket, is_enabled)
-            })
-    }
-
-    pub fn should_autoclose_before(&self, c: char) -> bool {
-        c.is_whitespace() || self.language.config.autoclose_before.contains(c)
-    }
-
-    pub fn language_allowed(&self, name: &LanguageServerName) -> bool {
-        let config = &self.language.config;
-        let opt_in_servers = &config.scope_opt_in_language_servers;
-        if opt_in_servers.iter().any(|o| *o == *name.0) {
-            if let Some(over) = self.config_override() {
-                over.opt_into_language_servers.iter().any(|o| *o == *name.0)
-            } else {
-                false
-            }
-        } else {
-            true
-        }
-    }
-
-    fn config_override(&self) -> Option<&LanguageConfigOverride> {
-        let id = self.override_id?;
-        let grammar = self.language.grammar.as_ref()?;
-        let override_config = grammar.override_config.as_ref()?;
-        override_config.values.get(&id).map(|e| &e.1)
-    }
-}
-
-impl Hash for Language {
-    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
-        self.id().hash(state)
-    }
-}
-
-impl PartialEq for Language {
-    fn eq(&self, other: &Self) -> bool {
-        self.id().eq(&other.id())
-    }
-}
-
-impl Eq for Language {}
-
-impl Debug for Language {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("Language")
-            .field("name", &self.config.name)
-            .finish()
-    }
-}
-
-impl Grammar {
-    pub fn id(&self) -> usize {
-        self.id
-    }
-
-    fn parse_text(&self, text: &Rope, old_tree: Option<Tree>) -> Tree {
-        PARSER.with(|parser| {
-            let mut parser = parser.borrow_mut();
-            parser
-                .set_language(&self.ts_language)
-                .expect("incompatible grammar");
-            let mut chunks = text.chunks_in_range(0..text.len());
-            parser
-                .parse_with(
-                    &mut move |offset, _| {
-                        chunks.seek(offset);
-                        chunks.next().unwrap_or("").as_bytes()
-                    },
-                    old_tree.as_ref(),
-                )
-                .unwrap()
-        })
-    }
-
-    pub fn highlight_map(&self) -> HighlightMap {
-        self.highlight_map.lock().clone()
-    }
-
-    pub fn highlight_id_for_name(&self, name: &str) -> Option<HighlightId> {
-        let capture_id = self
-            .highlights_query
-            .as_ref()?
-            .capture_index_for_name(name)?;
-        Some(self.highlight_map.lock().get(capture_id))
-    }
-}
-
-impl CodeLabel {
-    pub fn plain(text: String, filter_text: Option<&str>) -> Self {
-        let mut result = Self {
-            runs: Vec::new(),
-            filter_range: 0..text.len(),
-            text,
-        };
-        if let Some(filter_text) = filter_text {
-            if let Some(ix) = result.text.find(filter_text) {
-                result.filter_range = ix..ix + filter_text.len();
-            }
-        }
-        result
-    }
-}
-
-#[cfg(any(test, feature = "test-support"))]
-impl Default for FakeLspAdapter {
-    fn default() -> Self {
-        Self {
-            name: "the-fake-language-server",
-            capabilities: lsp::LanguageServer::full_capabilities(),
-            initializer: None,
-            disk_based_diagnostics_progress_token: None,
-            initialization_options: None,
-            disk_based_diagnostics_sources: Vec::new(),
-            prettier_plugins: Vec::new(),
-        }
-    }
-}
-
-#[cfg(any(test, feature = "test-support"))]
-#[async_trait]
-impl LspAdapter for Arc<FakeLspAdapter> {
-    async fn name(&self) -> LanguageServerName {
-        LanguageServerName(self.name.into())
-    }
-
-    fn short_name(&self) -> &'static str {
-        "FakeLspAdapter"
-    }
-
-    async fn fetch_latest_server_version(
-        &self,
-        _: &dyn LspAdapterDelegate,
-    ) -> Result<Box<dyn 'static + Send + Any>> {
-        unreachable!();
-    }
-
-    async fn fetch_server_binary(
-        &self,
-        _: Box<dyn 'static + Send + Any>,
-        _: PathBuf,
-        _: &dyn LspAdapterDelegate,
-    ) -> Result<LanguageServerBinary> {
-        unreachable!();
-    }
-
-    async fn cached_server_binary(
-        &self,
-        _: PathBuf,
-        _: &dyn LspAdapterDelegate,
-    ) -> Option<LanguageServerBinary> {
-        unreachable!();
-    }
-
-    async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
-        unreachable!();
-    }
-
-    fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
-
-    async fn disk_based_diagnostic_sources(&self) -> Vec<String> {
-        self.disk_based_diagnostics_sources.clone()
-    }
-
-    async fn disk_based_diagnostics_progress_token(&self) -> Option<String> {
-        self.disk_based_diagnostics_progress_token.clone()
-    }
-
-    async fn initialization_options(&self) -> Option<Value> {
-        self.initialization_options.clone()
-    }
-
-    fn prettier_plugins(&self) -> &[&'static str] {
-        &self.prettier_plugins
-    }
-}
-
-fn get_capture_indices(query: &Query, captures: &mut [(&str, &mut Option<u32>)]) {
-    for (ix, name) in query.capture_names().iter().enumerate() {
-        for (capture_name, index) in captures.iter_mut() {
-            if capture_name == name {
-                **index = Some(ix as u32);
-                break;
-            }
-        }
-    }
-}
-
-pub fn point_to_lsp(point: PointUtf16) -> lsp::Position {
-    lsp::Position::new(point.row, point.column)
-}
-
-pub fn point_from_lsp(point: lsp::Position) -> Unclipped<PointUtf16> {
-    Unclipped(PointUtf16::new(point.line, point.character))
-}
-
-pub fn range_to_lsp(range: Range<PointUtf16>) -> lsp::Range {
-    lsp::Range {
-        start: point_to_lsp(range.start),
-        end: point_to_lsp(range.end),
-    }
-}
-
-pub fn range_from_lsp(range: lsp::Range) -> Range<Unclipped<PointUtf16>> {
-    let mut start = point_from_lsp(range.start);
-    let mut end = point_from_lsp(range.end);
-    if start > end {
-        mem::swap(&mut start, &mut end);
-    }
-    start..end
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use gpui::TestAppContext;
-
-    #[gpui::test(iterations = 10)]
-    async fn test_first_line_pattern(cx: &mut TestAppContext) {
-        let mut languages = LanguageRegistry::test();
-
-        languages.set_executor(cx.executor());
-        let languages = Arc::new(languages);
-        languages.register(
-            "/javascript",
-            LanguageConfig {
-                name: "JavaScript".into(),
-                path_suffixes: vec!["js".into()],
-                first_line_pattern: Some(Regex::new(r"\bnode\b").unwrap()),
-                ..Default::default()
-            },
-            tree_sitter_typescript::language_tsx(),
-            vec![],
-            |_| Default::default(),
-        );
-
-        languages
-            .language_for_file("the/script", None)
-            .await
-            .unwrap_err();
-        languages
-            .language_for_file("the/script", Some(&"nothing".into()))
-            .await
-            .unwrap_err();
-        assert_eq!(
-            languages
-                .language_for_file("the/script", Some(&"#!/bin/env node".into()))
-                .await
-                .unwrap()
-                .name()
-                .as_ref(),
-            "JavaScript"
-        );
-    }
-
-    #[gpui::test(iterations = 10)]
-    async fn test_language_loading(cx: &mut TestAppContext) {
-        let mut languages = LanguageRegistry::test();
-        languages.set_executor(cx.executor());
-        let languages = Arc::new(languages);
-        languages.register(
-            "/JSON",
-            LanguageConfig {
-                name: "JSON".into(),
-                path_suffixes: vec!["json".into()],
-                ..Default::default()
-            },
-            tree_sitter_json::language(),
-            vec![],
-            |_| Default::default(),
-        );
-        languages.register(
-            "/rust",
-            LanguageConfig {
-                name: "Rust".into(),
-                path_suffixes: vec!["rs".into()],
-                ..Default::default()
-            },
-            tree_sitter_rust::language(),
-            vec![],
-            |_| Default::default(),
-        );
-        assert_eq!(
-            languages.language_names(),
-            &[
-                "JSON".to_string(),
-                "Plain Text".to_string(),
-                "Rust".to_string(),
-            ]
-        );
-
-        let rust1 = languages.language_for_name("Rust");
-        let rust2 = languages.language_for_name("Rust");
-
-        // Ensure language is still listed even if it's being loaded.
-        assert_eq!(
-            languages.language_names(),
-            &[
-                "JSON".to_string(),
-                "Plain Text".to_string(),
-                "Rust".to_string(),
-            ]
-        );
-
-        let (rust1, rust2) = futures::join!(rust1, rust2);
-        assert!(Arc::ptr_eq(&rust1.unwrap(), &rust2.unwrap()));
-
-        // Ensure language is still listed even after loading it.
-        assert_eq!(
-            languages.language_names(),
-            &[
-                "JSON".to_string(),
-                "Plain Text".to_string(),
-                "Rust".to_string(),
-            ]
-        );
-
-        // Loading an unknown language returns an error.
-        assert!(languages.language_for_name("Unknown").await.is_err());
-    }
-}

crates/language2/src/language_settings.rs 🔗

@@ -1,431 +0,0 @@
-use crate::{File, Language};
-use anyhow::Result;
-use collections::{HashMap, HashSet};
-use globset::GlobMatcher;
-use gpui::AppContext;
-use schemars::{
-    schema::{InstanceType, ObjectValidation, Schema, SchemaObject},
-    JsonSchema,
-};
-use serde::{Deserialize, Serialize};
-use settings::Settings;
-use std::{num::NonZeroU32, path::Path, sync::Arc};
-
-pub fn init(cx: &mut AppContext) {
-    AllLanguageSettings::register(cx);
-}
-
-pub fn language_settings<'a>(
-    language: Option<&Arc<Language>>,
-    file: Option<&Arc<dyn File>>,
-    cx: &'a AppContext,
-) -> &'a LanguageSettings {
-    let language_name = language.map(|l| l.name());
-    all_language_settings(file, cx).language(language_name.as_deref())
-}
-
-pub fn all_language_settings<'a>(
-    file: Option<&Arc<dyn File>>,
-    cx: &'a AppContext,
-) -> &'a AllLanguageSettings {
-    let location = file.map(|f| (f.worktree_id(), f.path().as_ref()));
-    AllLanguageSettings::get(location, cx)
-}
-
-#[derive(Debug, Clone)]
-pub struct AllLanguageSettings {
-    pub copilot: CopilotSettings,
-    defaults: LanguageSettings,
-    languages: HashMap<Arc<str>, LanguageSettings>,
-}
-
-#[derive(Debug, Clone, Deserialize)]
-pub struct LanguageSettings {
-    pub tab_size: NonZeroU32,
-    pub hard_tabs: bool,
-    pub soft_wrap: SoftWrap,
-    pub preferred_line_length: u32,
-    pub show_wrap_guides: bool,
-    pub wrap_guides: Vec<usize>,
-    pub format_on_save: FormatOnSave,
-    pub remove_trailing_whitespace_on_save: bool,
-    pub ensure_final_newline_on_save: bool,
-    pub formatter: Formatter,
-    pub prettier: HashMap<String, serde_json::Value>,
-    pub enable_language_server: bool,
-    pub show_copilot_suggestions: bool,
-    pub show_whitespaces: ShowWhitespaceSetting,
-    pub extend_comment_on_newline: bool,
-    pub inlay_hints: InlayHintSettings,
-}
-
-#[derive(Clone, Debug, Default)]
-pub struct CopilotSettings {
-    pub feature_enabled: bool,
-    pub disabled_globs: Vec<GlobMatcher>,
-}
-
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
-pub struct AllLanguageSettingsContent {
-    #[serde(default)]
-    pub features: Option<FeaturesContent>,
-    #[serde(default)]
-    pub copilot: Option<CopilotSettingsContent>,
-    #[serde(flatten)]
-    pub defaults: LanguageSettingsContent,
-    #[serde(default, alias = "language_overrides")]
-    pub languages: HashMap<Arc<str>, LanguageSettingsContent>,
-}
-
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
-pub struct LanguageSettingsContent {
-    #[serde(default)]
-    pub tab_size: Option<NonZeroU32>,
-    #[serde(default)]
-    pub hard_tabs: Option<bool>,
-    #[serde(default)]
-    pub soft_wrap: Option<SoftWrap>,
-    #[serde(default)]
-    pub preferred_line_length: Option<u32>,
-    #[serde(default)]
-    pub show_wrap_guides: Option<bool>,
-    #[serde(default)]
-    pub wrap_guides: Option<Vec<usize>>,
-    #[serde(default)]
-    pub format_on_save: Option<FormatOnSave>,
-    #[serde(default)]
-    pub remove_trailing_whitespace_on_save: Option<bool>,
-    #[serde(default)]
-    pub ensure_final_newline_on_save: Option<bool>,
-    #[serde(default)]
-    pub formatter: Option<Formatter>,
-    #[serde(default)]
-    pub prettier: Option<HashMap<String, serde_json::Value>>,
-    #[serde(default)]
-    pub enable_language_server: Option<bool>,
-    #[serde(default)]
-    pub show_copilot_suggestions: Option<bool>,
-    #[serde(default)]
-    pub show_whitespaces: Option<ShowWhitespaceSetting>,
-    #[serde(default)]
-    pub extend_comment_on_newline: Option<bool>,
-    #[serde(default)]
-    pub inlay_hints: Option<InlayHintSettings>,
-}
-
-#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
-pub struct CopilotSettingsContent {
-    #[serde(default)]
-    pub disabled_globs: Option<Vec<String>>,
-}
-
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub struct FeaturesContent {
-    pub copilot: Option<bool>,
-}
-
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum SoftWrap {
-    None,
-    EditorWidth,
-    PreferredLineLength,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum FormatOnSave {
-    On,
-    Off,
-    LanguageServer,
-    External {
-        command: Arc<str>,
-        arguments: Arc<[String]>,
-    },
-}
-
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum ShowWhitespaceSetting {
-    Selection,
-    None,
-    All,
-}
-
-#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum Formatter {
-    #[default]
-    Auto,
-    LanguageServer,
-    Prettier,
-    External {
-        command: Arc<str>,
-        arguments: Arc<[String]>,
-    },
-}
-
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
-pub struct InlayHintSettings {
-    #[serde(default)]
-    pub enabled: bool,
-    #[serde(default = "default_true")]
-    pub show_type_hints: bool,
-    #[serde(default = "default_true")]
-    pub show_parameter_hints: bool,
-    #[serde(default = "default_true")]
-    pub show_other_hints: bool,
-}
-
-fn default_true() -> bool {
-    true
-}
-
-impl InlayHintSettings {
-    pub fn enabled_inlay_hint_kinds(&self) -> HashSet<Option<InlayHintKind>> {
-        let mut kinds = HashSet::default();
-        if self.show_type_hints {
-            kinds.insert(Some(InlayHintKind::Type));
-        }
-        if self.show_parameter_hints {
-            kinds.insert(Some(InlayHintKind::Parameter));
-        }
-        if self.show_other_hints {
-            kinds.insert(None);
-        }
-        kinds
-    }
-}
-
-impl AllLanguageSettings {
-    pub fn language<'a>(&'a self, language_name: Option<&str>) -> &'a LanguageSettings {
-        if let Some(name) = language_name {
-            if let Some(overrides) = self.languages.get(name) {
-                return overrides;
-            }
-        }
-        &self.defaults
-    }
-
-    pub fn copilot_enabled_for_path(&self, path: &Path) -> bool {
-        !self
-            .copilot
-            .disabled_globs
-            .iter()
-            .any(|glob| glob.is_match(path))
-    }
-
-    pub fn copilot_enabled(&self, language: Option<&Arc<Language>>, path: Option<&Path>) -> bool {
-        if !self.copilot.feature_enabled {
-            return false;
-        }
-
-        if let Some(path) = path {
-            if !self.copilot_enabled_for_path(path) {
-                return false;
-            }
-        }
-
-        self.language(language.map(|l| l.name()).as_deref())
-            .show_copilot_suggestions
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub enum InlayHintKind {
-    Type,
-    Parameter,
-}
-
-impl InlayHintKind {
-    pub fn from_name(name: &str) -> Option<Self> {
-        match name {
-            "type" => Some(InlayHintKind::Type),
-            "parameter" => Some(InlayHintKind::Parameter),
-            _ => None,
-        }
-    }
-
-    pub fn name(&self) -> &'static str {
-        match self {
-            InlayHintKind::Type => "type",
-            InlayHintKind::Parameter => "parameter",
-        }
-    }
-}
-
-impl settings::Settings for AllLanguageSettings {
-    const KEY: Option<&'static str> = None;
-
-    type FileContent = AllLanguageSettingsContent;
-
-    fn load(
-        default_value: &Self::FileContent,
-        user_settings: &[&Self::FileContent],
-        _: &mut AppContext,
-    ) -> Result<Self> {
-        // A default is provided for all settings.
-        let mut defaults: LanguageSettings =
-            serde_json::from_value(serde_json::to_value(&default_value.defaults)?)?;
-
-        let mut languages = HashMap::default();
-        for (language_name, settings) in &default_value.languages {
-            let mut language_settings = defaults.clone();
-            merge_settings(&mut language_settings, &settings);
-            languages.insert(language_name.clone(), language_settings);
-        }
-
-        let mut copilot_enabled = default_value
-            .features
-            .as_ref()
-            .and_then(|f| f.copilot)
-            .ok_or_else(Self::missing_default)?;
-        let mut copilot_globs = default_value
-            .copilot
-            .as_ref()
-            .and_then(|c| c.disabled_globs.as_ref())
-            .ok_or_else(Self::missing_default)?;
-
-        for user_settings in user_settings {
-            if let Some(copilot) = user_settings.features.as_ref().and_then(|f| f.copilot) {
-                copilot_enabled = copilot;
-            }
-            if let Some(globs) = user_settings
-                .copilot
-                .as_ref()
-                .and_then(|f| f.disabled_globs.as_ref())
-            {
-                copilot_globs = globs;
-            }
-
-            // A user's global settings override the default global settings and
-            // all default language-specific settings.
-            merge_settings(&mut defaults, &user_settings.defaults);
-            for language_settings in languages.values_mut() {
-                merge_settings(language_settings, &user_settings.defaults);
-            }
-
-            // A user's language-specific settings override default language-specific settings.
-            for (language_name, user_language_settings) in &user_settings.languages {
-                merge_settings(
-                    languages
-                        .entry(language_name.clone())
-                        .or_insert_with(|| defaults.clone()),
-                    &user_language_settings,
-                );
-            }
-        }
-
-        Ok(Self {
-            copilot: CopilotSettings {
-                feature_enabled: copilot_enabled,
-                disabled_globs: copilot_globs
-                    .iter()
-                    .filter_map(|g| Some(globset::Glob::new(g).ok()?.compile_matcher()))
-                    .collect(),
-            },
-            defaults,
-            languages,
-        })
-    }
-
-    fn json_schema(
-        generator: &mut schemars::gen::SchemaGenerator,
-        params: &settings::SettingsJsonSchemaParams,
-        _: &AppContext,
-    ) -> schemars::schema::RootSchema {
-        let mut root_schema = generator.root_schema_for::<Self::FileContent>();
-
-        // Create a schema for a 'languages overrides' object, associating editor
-        // settings with specific languages.
-        assert!(root_schema
-            .definitions
-            .contains_key("LanguageSettingsContent"));
-
-        let languages_object_schema = SchemaObject {
-            instance_type: Some(InstanceType::Object.into()),
-            object: Some(Box::new(ObjectValidation {
-                properties: params
-                    .language_names
-                    .iter()
-                    .map(|name| {
-                        (
-                            name.clone(),
-                            Schema::new_ref("#/definitions/LanguageSettingsContent".into()),
-                        )
-                    })
-                    .collect(),
-                ..Default::default()
-            })),
-            ..Default::default()
-        };
-
-        root_schema
-            .definitions
-            .extend([("Languages".into(), languages_object_schema.into())]);
-
-        root_schema
-            .schema
-            .object
-            .as_mut()
-            .unwrap()
-            .properties
-            .extend([
-                (
-                    "languages".to_owned(),
-                    Schema::new_ref("#/definitions/Languages".into()),
-                ),
-                // For backward compatibility
-                (
-                    "language_overrides".to_owned(),
-                    Schema::new_ref("#/definitions/Languages".into()),
-                ),
-            ]);
-
-        root_schema
-    }
-}
-
-fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent) {
-    merge(&mut settings.tab_size, src.tab_size);
-    merge(&mut settings.hard_tabs, src.hard_tabs);
-    merge(&mut settings.soft_wrap, src.soft_wrap);
-    merge(&mut settings.show_wrap_guides, src.show_wrap_guides);
-    merge(&mut settings.wrap_guides, src.wrap_guides.clone());
-
-    merge(
-        &mut settings.preferred_line_length,
-        src.preferred_line_length,
-    );
-    merge(&mut settings.formatter, src.formatter.clone());
-    merge(&mut settings.prettier, src.prettier.clone());
-    merge(&mut settings.format_on_save, src.format_on_save.clone());
-    merge(
-        &mut settings.remove_trailing_whitespace_on_save,
-        src.remove_trailing_whitespace_on_save,
-    );
-    merge(
-        &mut settings.ensure_final_newline_on_save,
-        src.ensure_final_newline_on_save,
-    );
-    merge(
-        &mut settings.enable_language_server,
-        src.enable_language_server,
-    );
-    merge(
-        &mut settings.show_copilot_suggestions,
-        src.show_copilot_suggestions,
-    );
-    merge(&mut settings.show_whitespaces, src.show_whitespaces);
-    merge(
-        &mut settings.extend_comment_on_newline,
-        src.extend_comment_on_newline,
-    );
-    merge(&mut settings.inlay_hints, src.inlay_hints);
-    fn merge<T>(target: &mut T, value: Option<T>) {
-        if let Some(value) = value {
-            *target = value;
-        }
-    }
-}

crates/language2/src/markdown.rs 🔗

@@ -1,301 +0,0 @@
-use std::sync::Arc;
-use std::{ops::Range, path::PathBuf};
-
-use crate::{HighlightId, Language, LanguageRegistry};
-use gpui::{px, FontStyle, FontWeight, HighlightStyle, UnderlineStyle};
-use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};
-
-#[derive(Debug, Clone)]
-pub struct ParsedMarkdown {
-    pub text: String,
-    pub highlights: Vec<(Range<usize>, MarkdownHighlight)>,
-    pub region_ranges: Vec<Range<usize>>,
-    pub regions: Vec<ParsedRegion>,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum MarkdownHighlight {
-    Style(MarkdownHighlightStyle),
-    Code(HighlightId),
-}
-
-impl MarkdownHighlight {
-    pub fn to_highlight_style(&self, theme: &theme::SyntaxTheme) -> Option<HighlightStyle> {
-        match self {
-            MarkdownHighlight::Style(style) => {
-                let mut highlight = HighlightStyle::default();
-
-                if style.italic {
-                    highlight.font_style = Some(FontStyle::Italic);
-                }
-
-                if style.underline {
-                    highlight.underline = Some(UnderlineStyle {
-                        thickness: px(1.),
-                        ..Default::default()
-                    });
-                }
-
-                if style.weight != FontWeight::default() {
-                    highlight.font_weight = Some(style.weight);
-                }
-
-                Some(highlight)
-            }
-
-            MarkdownHighlight::Code(id) => id.style(theme),
-        }
-    }
-}
-
-#[derive(Debug, Clone, Default, PartialEq, Eq)]
-pub struct MarkdownHighlightStyle {
-    pub italic: bool,
-    pub underline: bool,
-    pub weight: FontWeight,
-}
-
-#[derive(Debug, Clone)]
-pub struct ParsedRegion {
-    pub code: bool,
-    pub link: Option<Link>,
-}
-
-#[derive(Debug, Clone)]
-pub enum Link {
-    Web { url: String },
-    Path { path: PathBuf },
-}
-
-impl Link {
-    fn identify(text: String) -> Option<Link> {
-        if text.starts_with("http") {
-            return Some(Link::Web { url: text });
-        }
-
-        let path = PathBuf::from(text);
-        if path.is_absolute() {
-            return Some(Link::Path { path });
-        }
-
-        None
-    }
-}
-
-pub async fn parse_markdown(
-    markdown: &str,
-    language_registry: &Arc<LanguageRegistry>,
-    language: Option<Arc<Language>>,
-) -> ParsedMarkdown {
-    let mut text = String::new();
-    let mut highlights = Vec::new();
-    let mut region_ranges = Vec::new();
-    let mut regions = Vec::new();
-
-    parse_markdown_block(
-        markdown,
-        language_registry,
-        language,
-        &mut text,
-        &mut highlights,
-        &mut region_ranges,
-        &mut regions,
-    )
-    .await;
-
-    ParsedMarkdown {
-        text,
-        highlights,
-        region_ranges,
-        regions,
-    }
-}
-
-pub async fn parse_markdown_block(
-    markdown: &str,
-    language_registry: &Arc<LanguageRegistry>,
-    language: Option<Arc<Language>>,
-    text: &mut String,
-    highlights: &mut Vec<(Range<usize>, MarkdownHighlight)>,
-    region_ranges: &mut Vec<Range<usize>>,
-    regions: &mut Vec<ParsedRegion>,
-) {
-    let mut bold_depth = 0;
-    let mut italic_depth = 0;
-    let mut link_url = None;
-    let mut current_language = None;
-    let mut list_stack = Vec::new();
-
-    for event in Parser::new_ext(&markdown, Options::all()) {
-        let prev_len = text.len();
-        match event {
-            Event::Text(t) => {
-                if let Some(language) = &current_language {
-                    highlight_code(text, highlights, t.as_ref(), language);
-                } else {
-                    text.push_str(t.as_ref());
-
-                    let mut style = MarkdownHighlightStyle::default();
-
-                    if bold_depth > 0 {
-                        style.weight = FontWeight::BOLD;
-                    }
-
-                    if italic_depth > 0 {
-                        style.italic = true;
-                    }
-
-                    if let Some(link) = link_url.clone().and_then(|u| Link::identify(u)) {
-                        region_ranges.push(prev_len..text.len());
-                        regions.push(ParsedRegion {
-                            code: false,
-                            link: Some(link),
-                        });
-                        style.underline = true;
-                    }
-
-                    if style != MarkdownHighlightStyle::default() {
-                        let mut new_highlight = true;
-                        if let Some((last_range, MarkdownHighlight::Style(last_style))) =
-                            highlights.last_mut()
-                        {
-                            if last_range.end == prev_len && last_style == &style {
-                                last_range.end = text.len();
-                                new_highlight = false;
-                            }
-                        }
-                        if new_highlight {
-                            let range = prev_len..text.len();
-                            highlights.push((range, MarkdownHighlight::Style(style)));
-                        }
-                    }
-                }
-            }
-
-            Event::Code(t) => {
-                text.push_str(t.as_ref());
-                region_ranges.push(prev_len..text.len());
-
-                let link = link_url.clone().and_then(|u| Link::identify(u));
-                if link.is_some() {
-                    highlights.push((
-                        prev_len..text.len(),
-                        MarkdownHighlight::Style(MarkdownHighlightStyle {
-                            underline: true,
-                            ..Default::default()
-                        }),
-                    ));
-                }
-                regions.push(ParsedRegion { code: true, link });
-            }
-
-            Event::Start(tag) => match tag {
-                Tag::Paragraph => new_paragraph(text, &mut list_stack),
-
-                Tag::Heading(_, _, _) => {
-                    new_paragraph(text, &mut list_stack);
-                    bold_depth += 1;
-                }
-
-                Tag::CodeBlock(kind) => {
-                    new_paragraph(text, &mut list_stack);
-                    current_language = if let CodeBlockKind::Fenced(language) = kind {
-                        language_registry
-                            .language_for_name(language.as_ref())
-                            .await
-                            .ok()
-                    } else {
-                        language.clone()
-                    }
-                }
-
-                Tag::Emphasis => italic_depth += 1,
-
-                Tag::Strong => bold_depth += 1,
-
-                Tag::Link(_, url, _) => link_url = Some(url.to_string()),
-
-                Tag::List(number) => {
-                    list_stack.push((number, false));
-                }
-
-                Tag::Item => {
-                    let len = list_stack.len();
-                    if let Some((list_number, has_content)) = list_stack.last_mut() {
-                        *has_content = false;
-                        if !text.is_empty() && !text.ends_with('\n') {
-                            text.push('\n');
-                        }
-                        for _ in 0..len - 1 {
-                            text.push_str("  ");
-                        }
-                        if let Some(number) = list_number {
-                            text.push_str(&format!("{}. ", number));
-                            *number += 1;
-                            *has_content = false;
-                        } else {
-                            text.push_str("- ");
-                        }
-                    }
-                }
-
-                _ => {}
-            },
-
-            Event::End(tag) => match tag {
-                Tag::Heading(_, _, _) => bold_depth -= 1,
-                Tag::CodeBlock(_) => current_language = None,
-                Tag::Emphasis => italic_depth -= 1,
-                Tag::Strong => bold_depth -= 1,
-                Tag::Link(_, _, _) => link_url = None,
-                Tag::List(_) => drop(list_stack.pop()),
-                _ => {}
-            },
-
-            Event::HardBreak => text.push('\n'),
-
-            Event::SoftBreak => text.push(' '),
-
-            _ => {}
-        }
-    }
-}
-
-pub fn highlight_code(
-    text: &mut String,
-    highlights: &mut Vec<(Range<usize>, MarkdownHighlight)>,
-    content: &str,
-    language: &Arc<Language>,
-) {
-    let prev_len = text.len();
-    text.push_str(content);
-    for (range, highlight_id) in language.highlight_text(&content.into(), 0..content.len()) {
-        let highlight = MarkdownHighlight::Code(highlight_id);
-        highlights.push((prev_len + range.start..prev_len + range.end, highlight));
-    }
-}
-
-pub fn new_paragraph(text: &mut String, list_stack: &mut Vec<(Option<u64>, bool)>) {
-    let mut is_subsequent_paragraph_of_list = false;
-    if let Some((_, has_content)) = list_stack.last_mut() {
-        if *has_content {
-            is_subsequent_paragraph_of_list = true;
-        } else {
-            *has_content = true;
-            return;
-        }
-    }
-
-    if !text.is_empty() {
-        if !text.ends_with('\n') {
-            text.push('\n');
-        }
-        text.push('\n');
-    }
-    for _ in 0..list_stack.len().saturating_sub(1) {
-        text.push_str("  ");
-    }
-    if is_subsequent_paragraph_of_list {
-        text.push_str("  ");
-    }
-}

crates/language2/src/outline.rs 🔗

@@ -1,139 +0,0 @@
-use fuzzy::{StringMatch, StringMatchCandidate};
-use gpui::{BackgroundExecutor, HighlightStyle};
-use std::ops::Range;
-
-#[derive(Debug)]
-pub struct Outline<T> {
-    pub items: Vec<OutlineItem<T>>,
-    candidates: Vec<StringMatchCandidate>,
-    path_candidates: Vec<StringMatchCandidate>,
-    path_candidate_prefixes: Vec<usize>,
-}
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct OutlineItem<T> {
-    pub depth: usize,
-    pub range: Range<T>,
-    pub text: String,
-    pub highlight_ranges: Vec<(Range<usize>, HighlightStyle)>,
-    pub name_ranges: Vec<Range<usize>>,
-}
-
-impl<T> Outline<T> {
-    pub fn new(items: Vec<OutlineItem<T>>) -> Self {
-        let mut candidates = Vec::new();
-        let mut path_candidates = Vec::new();
-        let mut path_candidate_prefixes = Vec::new();
-        let mut path_text = String::new();
-        let mut path_stack = Vec::new();
-
-        for (id, item) in items.iter().enumerate() {
-            if item.depth < path_stack.len() {
-                path_stack.truncate(item.depth);
-                path_text.truncate(path_stack.last().copied().unwrap_or(0));
-            }
-            if !path_text.is_empty() {
-                path_text.push(' ');
-            }
-            path_candidate_prefixes.push(path_text.len());
-            path_text.push_str(&item.text);
-            path_stack.push(path_text.len());
-
-            let candidate_text = item
-                .name_ranges
-                .iter()
-                .map(|range| &item.text[range.start as usize..range.end as usize])
-                .collect::<String>();
-
-            path_candidates.push(StringMatchCandidate::new(id, path_text.clone()));
-            candidates.push(StringMatchCandidate::new(id, candidate_text));
-        }
-
-        Self {
-            candidates,
-            path_candidates,
-            path_candidate_prefixes,
-            items,
-        }
-    }
-
-    pub async fn search(&self, query: &str, executor: BackgroundExecutor) -> Vec<StringMatch> {
-        let query = query.trim_start();
-        let is_path_query = query.contains(' ');
-        let smart_case = query.chars().any(|c| c.is_uppercase());
-        let mut matches = fuzzy::match_strings(
-            if is_path_query {
-                &self.path_candidates
-            } else {
-                &self.candidates
-            },
-            query,
-            smart_case,
-            100,
-            &Default::default(),
-            executor.clone(),
-        )
-        .await;
-        matches.sort_unstable_by_key(|m| m.candidate_id);
-
-        let mut tree_matches = Vec::new();
-
-        let mut prev_item_ix = 0;
-        for mut string_match in matches {
-            let outline_match = &self.items[string_match.candidate_id];
-            string_match.string = outline_match.text.clone();
-
-            if is_path_query {
-                let prefix_len = self.path_candidate_prefixes[string_match.candidate_id];
-                string_match
-                    .positions
-                    .retain(|position| *position >= prefix_len);
-                for position in &mut string_match.positions {
-                    *position -= prefix_len;
-                }
-            } else {
-                let mut name_ranges = outline_match.name_ranges.iter();
-                let mut name_range = name_ranges.next().unwrap();
-                let mut preceding_ranges_len = 0;
-                for position in &mut string_match.positions {
-                    while *position >= preceding_ranges_len + name_range.len() as usize {
-                        preceding_ranges_len += name_range.len();
-                        name_range = name_ranges.next().unwrap();
-                    }
-                    *position = name_range.start as usize + (*position - preceding_ranges_len);
-                }
-            }
-
-            let insertion_ix = tree_matches.len();
-            let mut cur_depth = outline_match.depth;
-            for (ix, item) in self.items[prev_item_ix..string_match.candidate_id]
-                .iter()
-                .enumerate()
-                .rev()
-            {
-                if cur_depth == 0 {
-                    break;
-                }
-
-                let candidate_index = ix + prev_item_ix;
-                if item.depth == cur_depth - 1 {
-                    tree_matches.insert(
-                        insertion_ix,
-                        StringMatch {
-                            candidate_id: candidate_index,
-                            score: Default::default(),
-                            positions: Default::default(),
-                            string: Default::default(),
-                        },
-                    );
-                    cur_depth -= 1;
-                }
-            }
-
-            prev_item_ix = string_match.candidate_id + 1;
-            tree_matches.push(string_match);
-        }
-
-        tree_matches
-    }
-}

crates/language2/src/proto.rs 🔗

@@ -1,590 +0,0 @@
-use crate::{
-    diagnostic_set::DiagnosticEntry, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic,
-    Language,
-};
-use anyhow::{anyhow, Result};
-use clock::ReplicaId;
-use lsp::{DiagnosticSeverity, LanguageServerId};
-use rpc::proto;
-use std::{ops::Range, sync::Arc};
-use text::*;
-
-pub use proto::{BufferState, Operation};
-
-pub fn serialize_fingerprint(fingerprint: RopeFingerprint) -> String {
-    fingerprint.to_hex()
-}
-
-pub fn deserialize_fingerprint(fingerprint: &str) -> Result<RopeFingerprint> {
-    RopeFingerprint::from_hex(fingerprint)
-        .map_err(|error| anyhow!("invalid fingerprint: {}", error))
-}
-
-pub fn deserialize_line_ending(message: proto::LineEnding) -> text::LineEnding {
-    match message {
-        proto::LineEnding::Unix => text::LineEnding::Unix,
-        proto::LineEnding::Windows => text::LineEnding::Windows,
-    }
-}
-
-pub fn serialize_line_ending(message: text::LineEnding) -> proto::LineEnding {
-    match message {
-        text::LineEnding::Unix => proto::LineEnding::Unix,
-        text::LineEnding::Windows => proto::LineEnding::Windows,
-    }
-}
-
-pub fn serialize_operation(operation: &crate::Operation) -> proto::Operation {
-    proto::Operation {
-        variant: Some(match operation {
-            crate::Operation::Buffer(text::Operation::Edit(edit)) => {
-                proto::operation::Variant::Edit(serialize_edit_operation(edit))
-            }
-
-            crate::Operation::Buffer(text::Operation::Undo(undo)) => {
-                proto::operation::Variant::Undo(proto::operation::Undo {
-                    replica_id: undo.timestamp.replica_id as u32,
-                    lamport_timestamp: undo.timestamp.value,
-                    version: serialize_version(&undo.version),
-                    counts: undo
-                        .counts
-                        .iter()
-                        .map(|(edit_id, count)| proto::UndoCount {
-                            replica_id: edit_id.replica_id as u32,
-                            lamport_timestamp: edit_id.value,
-                            count: *count,
-                        })
-                        .collect(),
-                })
-            }
-
-            crate::Operation::UpdateSelections {
-                selections,
-                line_mode,
-                lamport_timestamp,
-                cursor_shape,
-            } => proto::operation::Variant::UpdateSelections(proto::operation::UpdateSelections {
-                replica_id: lamport_timestamp.replica_id as u32,
-                lamport_timestamp: lamport_timestamp.value,
-                selections: serialize_selections(selections),
-                line_mode: *line_mode,
-                cursor_shape: serialize_cursor_shape(cursor_shape) as i32,
-            }),
-
-            crate::Operation::UpdateDiagnostics {
-                lamport_timestamp,
-                server_id,
-                diagnostics,
-            } => proto::operation::Variant::UpdateDiagnostics(proto::UpdateDiagnostics {
-                replica_id: lamport_timestamp.replica_id as u32,
-                lamport_timestamp: lamport_timestamp.value,
-                server_id: server_id.0 as u64,
-                diagnostics: serialize_diagnostics(diagnostics.iter()),
-            }),
-
-            crate::Operation::UpdateCompletionTriggers {
-                triggers,
-                lamport_timestamp,
-            } => proto::operation::Variant::UpdateCompletionTriggers(
-                proto::operation::UpdateCompletionTriggers {
-                    replica_id: lamport_timestamp.replica_id as u32,
-                    lamport_timestamp: lamport_timestamp.value,
-                    triggers: triggers.clone(),
-                },
-            ),
-        }),
-    }
-}
-
-pub fn serialize_edit_operation(operation: &EditOperation) -> proto::operation::Edit {
-    proto::operation::Edit {
-        replica_id: operation.timestamp.replica_id as u32,
-        lamport_timestamp: operation.timestamp.value,
-        version: serialize_version(&operation.version),
-        ranges: operation.ranges.iter().map(serialize_range).collect(),
-        new_text: operation
-            .new_text
-            .iter()
-            .map(|text| text.to_string())
-            .collect(),
-    }
-}
-
-pub fn serialize_undo_map_entry(
-    (edit_id, counts): (&clock::Lamport, &[(clock::Lamport, u32)]),
-) -> proto::UndoMapEntry {
-    proto::UndoMapEntry {
-        replica_id: edit_id.replica_id as u32,
-        local_timestamp: edit_id.value,
-        counts: counts
-            .iter()
-            .map(|(undo_id, count)| proto::UndoCount {
-                replica_id: undo_id.replica_id as u32,
-                lamport_timestamp: undo_id.value,
-                count: *count,
-            })
-            .collect(),
-    }
-}
-
-pub fn split_operations(
-    mut operations: Vec<proto::Operation>,
-) -> impl Iterator<Item = Vec<proto::Operation>> {
-    #[cfg(any(test, feature = "test-support"))]
-    const CHUNK_SIZE: usize = 5;
-
-    #[cfg(not(any(test, feature = "test-support")))]
-    const CHUNK_SIZE: usize = 100;
-
-    let mut done = false;
-    std::iter::from_fn(move || {
-        if done {
-            return None;
-        }
-
-        let operations = operations
-            .drain(..std::cmp::min(CHUNK_SIZE, operations.len()))
-            .collect::<Vec<_>>();
-        if operations.is_empty() {
-            done = true;
-        }
-        Some(operations)
-    })
-}
-
-pub fn serialize_selections(selections: &Arc<[Selection<Anchor>]>) -> Vec<proto::Selection> {
-    selections.iter().map(serialize_selection).collect()
-}
-
-pub fn serialize_selection(selection: &Selection<Anchor>) -> proto::Selection {
-    proto::Selection {
-        id: selection.id as u64,
-        start: Some(proto::EditorAnchor {
-            anchor: Some(serialize_anchor(&selection.start)),
-            excerpt_id: 0,
-        }),
-        end: Some(proto::EditorAnchor {
-            anchor: Some(serialize_anchor(&selection.end)),
-            excerpt_id: 0,
-        }),
-        reversed: selection.reversed,
-    }
-}
-
-pub fn serialize_cursor_shape(cursor_shape: &CursorShape) -> proto::CursorShape {
-    match cursor_shape {
-        CursorShape::Bar => proto::CursorShape::CursorBar,
-        CursorShape::Block => proto::CursorShape::CursorBlock,
-        CursorShape::Underscore => proto::CursorShape::CursorUnderscore,
-        CursorShape::Hollow => proto::CursorShape::CursorHollow,
-    }
-}
-
-pub fn deserialize_cursor_shape(cursor_shape: proto::CursorShape) -> CursorShape {
-    match cursor_shape {
-        proto::CursorShape::CursorBar => CursorShape::Bar,
-        proto::CursorShape::CursorBlock => CursorShape::Block,
-        proto::CursorShape::CursorUnderscore => CursorShape::Underscore,
-        proto::CursorShape::CursorHollow => CursorShape::Hollow,
-    }
-}
-
-pub fn serialize_diagnostics<'a>(
-    diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<Anchor>>,
-) -> Vec<proto::Diagnostic> {
-    diagnostics
-        .into_iter()
-        .map(|entry| proto::Diagnostic {
-            source: entry.diagnostic.source.clone(),
-            start: Some(serialize_anchor(&entry.range.start)),
-            end: Some(serialize_anchor(&entry.range.end)),
-            message: entry.diagnostic.message.clone(),
-            severity: match entry.diagnostic.severity {
-                DiagnosticSeverity::ERROR => proto::diagnostic::Severity::Error,
-                DiagnosticSeverity::WARNING => proto::diagnostic::Severity::Warning,
-                DiagnosticSeverity::INFORMATION => proto::diagnostic::Severity::Information,
-                DiagnosticSeverity::HINT => proto::diagnostic::Severity::Hint,
-                _ => proto::diagnostic::Severity::None,
-            } as i32,
-            group_id: entry.diagnostic.group_id as u64,
-            is_primary: entry.diagnostic.is_primary,
-            is_valid: entry.diagnostic.is_valid,
-            code: entry.diagnostic.code.clone(),
-            is_disk_based: entry.diagnostic.is_disk_based,
-            is_unnecessary: entry.diagnostic.is_unnecessary,
-        })
-        .collect()
-}
-
-pub fn serialize_anchor(anchor: &Anchor) -> proto::Anchor {
-    proto::Anchor {
-        replica_id: anchor.timestamp.replica_id as u32,
-        timestamp: anchor.timestamp.value,
-        offset: anchor.offset as u64,
-        bias: match anchor.bias {
-            Bias::Left => proto::Bias::Left as i32,
-            Bias::Right => proto::Bias::Right as i32,
-        },
-        buffer_id: anchor.buffer_id,
-    }
-}
-
-// This behavior is currently copied in the collab database, for snapshotting channel notes
-pub fn deserialize_operation(message: proto::Operation) -> Result<crate::Operation> {
-    Ok(
-        match message
-            .variant
-            .ok_or_else(|| anyhow!("missing operation variant"))?
-        {
-            proto::operation::Variant::Edit(edit) => {
-                crate::Operation::Buffer(text::Operation::Edit(deserialize_edit_operation(edit)))
-            }
-            proto::operation::Variant::Undo(undo) => {
-                crate::Operation::Buffer(text::Operation::Undo(UndoOperation {
-                    timestamp: clock::Lamport {
-                        replica_id: undo.replica_id as ReplicaId,
-                        value: undo.lamport_timestamp,
-                    },
-                    version: deserialize_version(&undo.version),
-                    counts: undo
-                        .counts
-                        .into_iter()
-                        .map(|c| {
-                            (
-                                clock::Lamport {
-                                    replica_id: c.replica_id as ReplicaId,
-                                    value: c.lamport_timestamp,
-                                },
-                                c.count,
-                            )
-                        })
-                        .collect(),
-                }))
-            }
-            proto::operation::Variant::UpdateSelections(message) => {
-                let selections = message
-                    .selections
-                    .into_iter()
-                    .filter_map(|selection| {
-                        Some(Selection {
-                            id: selection.id as usize,
-                            start: deserialize_anchor(selection.start?.anchor?)?,
-                            end: deserialize_anchor(selection.end?.anchor?)?,
-                            reversed: selection.reversed,
-                            goal: SelectionGoal::None,
-                        })
-                    })
-                    .collect::<Vec<_>>();
-
-                crate::Operation::UpdateSelections {
-                    lamport_timestamp: clock::Lamport {
-                        replica_id: message.replica_id as ReplicaId,
-                        value: message.lamport_timestamp,
-                    },
-                    selections: Arc::from(selections),
-                    line_mode: message.line_mode,
-                    cursor_shape: deserialize_cursor_shape(
-                        proto::CursorShape::from_i32(message.cursor_shape)
-                            .ok_or_else(|| anyhow!("Missing cursor shape"))?,
-                    ),
-                }
-            }
-            proto::operation::Variant::UpdateDiagnostics(message) => {
-                crate::Operation::UpdateDiagnostics {
-                    lamport_timestamp: clock::Lamport {
-                        replica_id: message.replica_id as ReplicaId,
-                        value: message.lamport_timestamp,
-                    },
-                    server_id: LanguageServerId(message.server_id as usize),
-                    diagnostics: deserialize_diagnostics(message.diagnostics),
-                }
-            }
-            proto::operation::Variant::UpdateCompletionTriggers(message) => {
-                crate::Operation::UpdateCompletionTriggers {
-                    triggers: message.triggers,
-                    lamport_timestamp: clock::Lamport {
-                        replica_id: message.replica_id as ReplicaId,
-                        value: message.lamport_timestamp,
-                    },
-                }
-            }
-        },
-    )
-}
-
-pub fn deserialize_edit_operation(edit: proto::operation::Edit) -> EditOperation {
-    EditOperation {
-        timestamp: clock::Lamport {
-            replica_id: edit.replica_id as ReplicaId,
-            value: edit.lamport_timestamp,
-        },
-        version: deserialize_version(&edit.version),
-        ranges: edit.ranges.into_iter().map(deserialize_range).collect(),
-        new_text: edit.new_text.into_iter().map(Arc::from).collect(),
-    }
-}
-
-pub fn deserialize_undo_map_entry(
-    entry: proto::UndoMapEntry,
-) -> (clock::Lamport, Vec<(clock::Lamport, u32)>) {
-    (
-        clock::Lamport {
-            replica_id: entry.replica_id as u16,
-            value: entry.local_timestamp,
-        },
-        entry
-            .counts
-            .into_iter()
-            .map(|undo_count| {
-                (
-                    clock::Lamport {
-                        replica_id: undo_count.replica_id as u16,
-                        value: undo_count.lamport_timestamp,
-                    },
-                    undo_count.count,
-                )
-            })
-            .collect(),
-    )
-}
-
-pub fn deserialize_selections(selections: Vec<proto::Selection>) -> Arc<[Selection<Anchor>]> {
-    Arc::from(
-        selections
-            .into_iter()
-            .filter_map(deserialize_selection)
-            .collect::<Vec<_>>(),
-    )
-}
-
-pub fn deserialize_selection(selection: proto::Selection) -> Option<Selection<Anchor>> {
-    Some(Selection {
-        id: selection.id as usize,
-        start: deserialize_anchor(selection.start?.anchor?)?,
-        end: deserialize_anchor(selection.end?.anchor?)?,
-        reversed: selection.reversed,
-        goal: SelectionGoal::None,
-    })
-}
-
-pub fn deserialize_diagnostics(
-    diagnostics: Vec<proto::Diagnostic>,
-) -> Arc<[DiagnosticEntry<Anchor>]> {
-    diagnostics
-        .into_iter()
-        .filter_map(|diagnostic| {
-            Some(DiagnosticEntry {
-                range: deserialize_anchor(diagnostic.start?)?..deserialize_anchor(diagnostic.end?)?,
-                diagnostic: Diagnostic {
-                    source: diagnostic.source,
-                    severity: match proto::diagnostic::Severity::from_i32(diagnostic.severity)? {
-                        proto::diagnostic::Severity::Error => DiagnosticSeverity::ERROR,
-                        proto::diagnostic::Severity::Warning => DiagnosticSeverity::WARNING,
-                        proto::diagnostic::Severity::Information => DiagnosticSeverity::INFORMATION,
-                        proto::diagnostic::Severity::Hint => DiagnosticSeverity::HINT,
-                        proto::diagnostic::Severity::None => return None,
-                    },
-                    message: diagnostic.message,
-                    group_id: diagnostic.group_id as usize,
-                    code: diagnostic.code,
-                    is_valid: diagnostic.is_valid,
-                    is_primary: diagnostic.is_primary,
-                    is_disk_based: diagnostic.is_disk_based,
-                    is_unnecessary: diagnostic.is_unnecessary,
-                },
-            })
-        })
-        .collect()
-}
-
-pub fn deserialize_anchor(anchor: proto::Anchor) -> Option<Anchor> {
-    Some(Anchor {
-        timestamp: clock::Lamport {
-            replica_id: anchor.replica_id as ReplicaId,
-            value: anchor.timestamp,
-        },
-        offset: anchor.offset as usize,
-        bias: match proto::Bias::from_i32(anchor.bias)? {
-            proto::Bias::Left => Bias::Left,
-            proto::Bias::Right => Bias::Right,
-        },
-        buffer_id: anchor.buffer_id,
-    })
-}
-
-pub fn lamport_timestamp_for_operation(operation: &proto::Operation) -> Option<clock::Lamport> {
-    let replica_id;
-    let value;
-    match operation.variant.as_ref()? {
-        proto::operation::Variant::Edit(op) => {
-            replica_id = op.replica_id;
-            value = op.lamport_timestamp;
-        }
-        proto::operation::Variant::Undo(op) => {
-            replica_id = op.replica_id;
-            value = op.lamport_timestamp;
-        }
-        proto::operation::Variant::UpdateDiagnostics(op) => {
-            replica_id = op.replica_id;
-            value = op.lamport_timestamp;
-        }
-        proto::operation::Variant::UpdateSelections(op) => {
-            replica_id = op.replica_id;
-            value = op.lamport_timestamp;
-        }
-        proto::operation::Variant::UpdateCompletionTriggers(op) => {
-            replica_id = op.replica_id;
-            value = op.lamport_timestamp;
-        }
-    }
-
-    Some(clock::Lamport {
-        replica_id: replica_id as ReplicaId,
-        value,
-    })
-}
-
-pub fn serialize_completion(completion: &Completion) -> proto::Completion {
-    proto::Completion {
-        old_start: Some(serialize_anchor(&completion.old_range.start)),
-        old_end: Some(serialize_anchor(&completion.old_range.end)),
-        new_text: completion.new_text.clone(),
-        server_id: completion.server_id.0 as u64,
-        lsp_completion: serde_json::to_vec(&completion.lsp_completion).unwrap(),
-    }
-}
-
-pub async fn deserialize_completion(
-    completion: proto::Completion,
-    language: Option<Arc<Language>>,
-) -> Result<Completion> {
-    let old_start = completion
-        .old_start
-        .and_then(deserialize_anchor)
-        .ok_or_else(|| anyhow!("invalid old start"))?;
-    let old_end = completion
-        .old_end
-        .and_then(deserialize_anchor)
-        .ok_or_else(|| anyhow!("invalid old end"))?;
-    let lsp_completion = serde_json::from_slice(&completion.lsp_completion)?;
-
-    let mut label = None;
-    if let Some(language) = language {
-        label = language.label_for_completion(&lsp_completion).await;
-    }
-
-    Ok(Completion {
-        old_range: old_start..old_end,
-        new_text: completion.new_text,
-        label: label.unwrap_or_else(|| {
-            CodeLabel::plain(
-                lsp_completion.label.clone(),
-                lsp_completion.filter_text.as_deref(),
-            )
-        }),
-        documentation: None,
-        server_id: LanguageServerId(completion.server_id as usize),
-        lsp_completion,
-    })
-}
-
-pub fn serialize_code_action(action: &CodeAction) -> proto::CodeAction {
-    proto::CodeAction {
-        server_id: action.server_id.0 as u64,
-        start: Some(serialize_anchor(&action.range.start)),
-        end: Some(serialize_anchor(&action.range.end)),
-        lsp_action: serde_json::to_vec(&action.lsp_action).unwrap(),
-    }
-}
-
-pub fn deserialize_code_action(action: proto::CodeAction) -> Result<CodeAction> {
-    let start = action
-        .start
-        .and_then(deserialize_anchor)
-        .ok_or_else(|| anyhow!("invalid start"))?;
-    let end = action
-        .end
-        .and_then(deserialize_anchor)
-        .ok_or_else(|| anyhow!("invalid end"))?;
-    let lsp_action = serde_json::from_slice(&action.lsp_action)?;
-    Ok(CodeAction {
-        server_id: LanguageServerId(action.server_id as usize),
-        range: start..end,
-        lsp_action,
-    })
-}
-
-pub fn serialize_transaction(transaction: &Transaction) -> proto::Transaction {
-    proto::Transaction {
-        id: Some(serialize_timestamp(transaction.id)),
-        edit_ids: transaction
-            .edit_ids
-            .iter()
-            .copied()
-            .map(serialize_timestamp)
-            .collect(),
-        start: serialize_version(&transaction.start),
-    }
-}
-
-pub fn deserialize_transaction(transaction: proto::Transaction) -> Result<Transaction> {
-    Ok(Transaction {
-        id: deserialize_timestamp(
-            transaction
-                .id
-                .ok_or_else(|| anyhow!("missing transaction id"))?,
-        ),
-        edit_ids: transaction
-            .edit_ids
-            .into_iter()
-            .map(deserialize_timestamp)
-            .collect(),
-        start: deserialize_version(&transaction.start),
-    })
-}
-
-pub fn serialize_timestamp(timestamp: clock::Lamport) -> proto::LamportTimestamp {
-    proto::LamportTimestamp {
-        replica_id: timestamp.replica_id as u32,
-        value: timestamp.value,
-    }
-}
-
-pub fn deserialize_timestamp(timestamp: proto::LamportTimestamp) -> clock::Lamport {
-    clock::Lamport {
-        replica_id: timestamp.replica_id as ReplicaId,
-        value: timestamp.value,
-    }
-}
-
-pub fn serialize_range(range: &Range<FullOffset>) -> proto::Range {
-    proto::Range {
-        start: range.start.0 as u64,
-        end: range.end.0 as u64,
-    }
-}
-
-pub fn deserialize_range(range: proto::Range) -> Range<FullOffset> {
-    FullOffset(range.start as usize)..FullOffset(range.end as usize)
-}
-
-pub fn deserialize_version(message: &[proto::VectorClockEntry]) -> clock::Global {
-    let mut version = clock::Global::new();
-    for entry in message {
-        version.observe(clock::Lamport {
-            replica_id: entry.replica_id as ReplicaId,
-            value: entry.timestamp,
-        });
-    }
-    version
-}
-
-pub fn serialize_version(version: &clock::Global) -> Vec<proto::VectorClockEntry> {
-    version
-        .iter()
-        .map(|entry| proto::VectorClockEntry {
-            replica_id: entry.replica_id as u32,
-            timestamp: entry.value,
-        })
-        .collect()
-}

crates/language2/src/syntax_map.rs 🔗

@@ -1,1806 +0,0 @@
-#[cfg(test)]
-mod syntax_map_tests;
-
-use crate::{Grammar, InjectionConfig, Language, LanguageRegistry};
-use collections::HashMap;
-use futures::FutureExt;
-use parking_lot::Mutex;
-use std::{
-    borrow::Cow,
-    cmp::{self, Ordering, Reverse},
-    collections::BinaryHeap,
-    fmt, iter,
-    ops::{Deref, DerefMut, Range},
-    sync::Arc,
-};
-use sum_tree::{Bias, SeekTarget, SumTree};
-use text::{Anchor, BufferSnapshot, OffsetRangeExt, Point, Rope, ToOffset, ToPoint};
-use tree_sitter::{Node, Query, QueryCapture, QueryCaptures, QueryCursor, QueryMatches, Tree};
-
-use super::PARSER;
-
-static QUERY_CURSORS: Mutex<Vec<QueryCursor>> = Mutex::new(vec![]);
-
-#[derive(Default)]
-pub struct SyntaxMap {
-    snapshot: SyntaxSnapshot,
-    language_registry: Option<Arc<LanguageRegistry>>,
-}
-
-#[derive(Clone, Default)]
-pub struct SyntaxSnapshot {
-    layers: SumTree<SyntaxLayer>,
-    parsed_version: clock::Global,
-    interpolated_version: clock::Global,
-    language_registry_version: usize,
-}
-
-#[derive(Default)]
-pub struct SyntaxMapCaptures<'a> {
-    layers: Vec<SyntaxMapCapturesLayer<'a>>,
-    active_layer_count: usize,
-    grammars: Vec<&'a Grammar>,
-}
-
-#[derive(Default)]
-pub struct SyntaxMapMatches<'a> {
-    layers: Vec<SyntaxMapMatchesLayer<'a>>,
-    active_layer_count: usize,
-    grammars: Vec<&'a Grammar>,
-}
-
-#[derive(Debug)]
-pub struct SyntaxMapCapture<'a> {
-    pub depth: usize,
-    pub node: Node<'a>,
-    pub index: u32,
-    pub grammar_index: usize,
-}
-
-#[derive(Debug)]
-pub struct SyntaxMapMatch<'a> {
-    pub depth: usize,
-    pub pattern_index: usize,
-    pub captures: &'a [QueryCapture<'a>],
-    pub grammar_index: usize,
-}
-
-struct SyntaxMapCapturesLayer<'a> {
-    depth: usize,
-    captures: QueryCaptures<'a, 'a, TextProvider<'a>, &'a [u8]>,
-    next_capture: Option<QueryCapture<'a>>,
-    grammar_index: usize,
-    _query_cursor: QueryCursorHandle,
-}
-
-struct SyntaxMapMatchesLayer<'a> {
-    depth: usize,
-    next_pattern_index: usize,
-    next_captures: Vec<QueryCapture<'a>>,
-    has_next: bool,
-    matches: QueryMatches<'a, 'a, TextProvider<'a>, &'a [u8]>,
-    grammar_index: usize,
-    _query_cursor: QueryCursorHandle,
-}
-
-#[derive(Clone)]
-struct SyntaxLayer {
-    depth: usize,
-    range: Range<Anchor>,
-    content: SyntaxLayerContent,
-}
-
-#[derive(Clone)]
-enum SyntaxLayerContent {
-    Parsed {
-        tree: tree_sitter::Tree,
-        language: Arc<Language>,
-    },
-    Pending {
-        language_name: Arc<str>,
-    },
-}
-
-impl SyntaxLayerContent {
-    fn language_id(&self) -> Option<usize> {
-        match self {
-            SyntaxLayerContent::Parsed { language, .. } => language.id(),
-            SyntaxLayerContent::Pending { .. } => None,
-        }
-    }
-
-    fn tree(&self) -> Option<&Tree> {
-        match self {
-            SyntaxLayerContent::Parsed { tree, .. } => Some(tree),
-            SyntaxLayerContent::Pending { .. } => None,
-        }
-    }
-}
-
-#[derive(Debug)]
-pub struct SyntaxLayerInfo<'a> {
-    pub depth: usize,
-    pub language: &'a Arc<Language>,
-    tree: &'a Tree,
-    offset: (usize, tree_sitter::Point),
-}
-
-#[derive(Clone)]
-pub struct OwnedSyntaxLayerInfo {
-    pub depth: usize,
-    pub language: Arc<Language>,
-    tree: tree_sitter::Tree,
-    offset: (usize, tree_sitter::Point),
-}
-
-#[derive(Debug, Clone)]
-struct SyntaxLayerSummary {
-    min_depth: usize,
-    max_depth: usize,
-    range: Range<Anchor>,
-    last_layer_range: Range<Anchor>,
-    last_layer_language: Option<usize>,
-    contains_unknown_injections: bool,
-}
-
-#[derive(Clone, Debug)]
-struct SyntaxLayerPosition {
-    depth: usize,
-    range: Range<Anchor>,
-    language: Option<usize>,
-}
-
-#[derive(Clone, Debug)]
-struct ChangeStartPosition {
-    depth: usize,
-    position: Anchor,
-}
-
-#[derive(Clone, Debug)]
-struct SyntaxLayerPositionBeforeChange {
-    position: SyntaxLayerPosition,
-    change: ChangeStartPosition,
-}
-
-struct ParseStep {
-    depth: usize,
-    language: ParseStepLanguage,
-    range: Range<Anchor>,
-    included_ranges: Vec<tree_sitter::Range>,
-    mode: ParseMode,
-}
-
-#[derive(Debug)]
-enum ParseStepLanguage {
-    Loaded { language: Arc<Language> },
-    Pending { name: Arc<str> },
-}
-
-impl ParseStepLanguage {
-    fn id(&self) -> Option<usize> {
-        match self {
-            ParseStepLanguage::Loaded { language } => language.id(),
-            ParseStepLanguage::Pending { .. } => None,
-        }
-    }
-}
-
-enum ParseMode {
-    Single,
-    Combined {
-        parent_layer_range: Range<usize>,
-        parent_layer_changed_ranges: Vec<Range<usize>>,
-    },
-}
-
-#[derive(Debug, PartialEq, Eq)]
-struct ChangedRegion {
-    depth: usize,
-    range: Range<Anchor>,
-}
-
-#[derive(Default)]
-struct ChangeRegionSet(Vec<ChangedRegion>);
-
-struct TextProvider<'a>(&'a Rope);
-
-struct ByteChunks<'a>(text::Chunks<'a>);
-
-struct QueryCursorHandle(Option<QueryCursor>);
-
-impl SyntaxMap {
-    pub fn new() -> Self {
-        Self::default()
-    }
-
-    pub fn set_language_registry(&mut self, registry: Arc<LanguageRegistry>) {
-        self.language_registry = Some(registry);
-    }
-
-    pub fn snapshot(&self) -> SyntaxSnapshot {
-        self.snapshot.clone()
-    }
-
-    pub fn language_registry(&self) -> Option<Arc<LanguageRegistry>> {
-        self.language_registry.clone()
-    }
-
-    pub fn interpolate(&mut self, text: &BufferSnapshot) {
-        self.snapshot.interpolate(text);
-    }
-
-    #[cfg(test)]
-    pub fn reparse(&mut self, language: Arc<Language>, text: &BufferSnapshot) {
-        self.snapshot
-            .reparse(text, self.language_registry.clone(), language);
-    }
-
-    pub fn did_parse(&mut self, snapshot: SyntaxSnapshot) {
-        self.snapshot = snapshot;
-    }
-
-    pub fn clear(&mut self) {
-        self.snapshot = SyntaxSnapshot::default();
-    }
-}
-
-impl SyntaxSnapshot {
-    pub fn is_empty(&self) -> bool {
-        self.layers.is_empty()
-    }
-
-    fn interpolate(&mut self, text: &BufferSnapshot) {
-        let edits = text
-            .anchored_edits_since::<(usize, Point)>(&self.interpolated_version)
-            .collect::<Vec<_>>();
-        self.interpolated_version = text.version().clone();
-
-        if edits.is_empty() {
-            return;
-        }
-
-        let mut layers = SumTree::new();
-        let mut first_edit_ix_for_depth = 0;
-        let mut prev_depth = 0;
-        let mut cursor = self.layers.cursor::<SyntaxLayerSummary>();
-        cursor.next(text);
-
-        'outer: loop {
-            let depth = cursor.end(text).max_depth;
-            if depth > prev_depth {
-                first_edit_ix_for_depth = 0;
-                prev_depth = depth;
-            }
-
-            // Preserve any layers at this depth that precede the first edit.
-            if let Some((_, edit_range)) = edits.get(first_edit_ix_for_depth) {
-                let target = ChangeStartPosition {
-                    depth,
-                    position: edit_range.start,
-                };
-                if target.cmp(&cursor.start(), text).is_gt() {
-                    let slice = cursor.slice(&target, Bias::Left, text);
-                    layers.append(slice, text);
-                }
-            }
-            // If this layer follows all of the edits, then preserve it and any
-            // subsequent layers at this same depth.
-            else if cursor.item().is_some() {
-                let slice = cursor.slice(
-                    &SyntaxLayerPosition {
-                        depth: depth + 1,
-                        range: Anchor::MIN..Anchor::MAX,
-                        language: None,
-                    },
-                    Bias::Left,
-                    text,
-                );
-                layers.append(slice, text);
-                continue;
-            };
-
-            let Some(layer) = cursor.item() else { break };
-            let (start_byte, start_point) = layer.range.start.summary::<(usize, Point)>(text);
-
-            // Ignore edits that end before the start of this layer, and don't consider them
-            // for any subsequent layers at this same depth.
-            loop {
-                let Some((_, edit_range)) = edits.get(first_edit_ix_for_depth) else {
-                    continue 'outer;
-                };
-                if edit_range.end.cmp(&layer.range.start, text).is_le() {
-                    first_edit_ix_for_depth += 1;
-                } else {
-                    break;
-                }
-            }
-
-            let mut layer = layer.clone();
-            if let SyntaxLayerContent::Parsed { tree, .. } = &mut layer.content {
-                for (edit, edit_range) in &edits[first_edit_ix_for_depth..] {
-                    // Ignore any edits that follow this layer.
-                    if edit_range.start.cmp(&layer.range.end, text).is_ge() {
-                        break;
-                    }
-
-                    // Apply any edits that intersect this layer to the layer's syntax tree.
-                    let tree_edit = if edit_range.start.cmp(&layer.range.start, text).is_ge() {
-                        tree_sitter::InputEdit {
-                            start_byte: edit.new.start.0 - start_byte,
-                            old_end_byte: edit.new.start.0 - start_byte
-                                + (edit.old.end.0 - edit.old.start.0),
-                            new_end_byte: edit.new.end.0 - start_byte,
-                            start_position: (edit.new.start.1 - start_point).to_ts_point(),
-                            old_end_position: (edit.new.start.1 - start_point
-                                + (edit.old.end.1 - edit.old.start.1))
-                                .to_ts_point(),
-                            new_end_position: (edit.new.end.1 - start_point).to_ts_point(),
-                        }
-                    } else {
-                        let node = tree.root_node();
-                        tree_sitter::InputEdit {
-                            start_byte: 0,
-                            old_end_byte: node.end_byte(),
-                            new_end_byte: 0,
-                            start_position: Default::default(),
-                            old_end_position: node.end_position(),
-                            new_end_position: Default::default(),
-                        }
-                    };
-
-                    tree.edit(&tree_edit);
-                }
-
-                debug_assert!(
-                    tree.root_node().end_byte() <= text.len(),
-                    "tree's size {}, is larger than text size {}",
-                    tree.root_node().end_byte(),
-                    text.len(),
-                );
-            }
-
-            layers.push(layer, text);
-            cursor.next(text);
-        }
-
-        layers.append(cursor.suffix(&text), &text);
-        drop(cursor);
-        self.layers = layers;
-    }
-
-    pub fn reparse(
-        &mut self,
-        text: &BufferSnapshot,
-        registry: Option<Arc<LanguageRegistry>>,
-        root_language: Arc<Language>,
-    ) {
-        let edit_ranges = text
-            .edits_since::<usize>(&self.parsed_version)
-            .map(|edit| edit.new)
-            .collect::<Vec<_>>();
-        self.reparse_with_ranges(text, root_language.clone(), edit_ranges, registry.as_ref());
-
-        if let Some(registry) = registry {
-            if registry.version() != self.language_registry_version {
-                let mut resolved_injection_ranges = Vec::new();
-                let mut cursor = self
-                    .layers
-                    .filter::<_, ()>(|summary| summary.contains_unknown_injections);
-                cursor.next(text);
-                while let Some(layer) = cursor.item() {
-                    let SyntaxLayerContent::Pending { language_name } = &layer.content else {
-                        unreachable!()
-                    };
-                    if registry
-                        .language_for_name_or_extension(language_name)
-                        .now_or_never()
-                        .and_then(|language| language.ok())
-                        .is_some()
-                    {
-                        resolved_injection_ranges.push(layer.range.to_offset(text));
-                    }
-
-                    cursor.next(text);
-                }
-                drop(cursor);
-
-                if !resolved_injection_ranges.is_empty() {
-                    self.reparse_with_ranges(
-                        text,
-                        root_language,
-                        resolved_injection_ranges,
-                        Some(&registry),
-                    );
-                }
-                self.language_registry_version = registry.version();
-            }
-        }
-    }
-
-    fn reparse_with_ranges(
-        &mut self,
-        text: &BufferSnapshot,
-        root_language: Arc<Language>,
-        invalidated_ranges: Vec<Range<usize>>,
-        registry: Option<&Arc<LanguageRegistry>>,
-    ) {
-        log::trace!("reparse. invalidated ranges:{:?}", invalidated_ranges);
-
-        let max_depth = self.layers.summary().max_depth;
-        let mut cursor = self.layers.cursor::<SyntaxLayerSummary>();
-        cursor.next(&text);
-        let mut layers = SumTree::new();
-
-        let mut changed_regions = ChangeRegionSet::default();
-        let mut queue = BinaryHeap::new();
-        let mut combined_injection_ranges = HashMap::default();
-        queue.push(ParseStep {
-            depth: 0,
-            language: ParseStepLanguage::Loaded {
-                language: root_language,
-            },
-            included_ranges: vec![tree_sitter::Range {
-                start_byte: 0,
-                end_byte: text.len(),
-                start_point: Point::zero().to_ts_point(),
-                end_point: text.max_point().to_ts_point(),
-            }],
-            range: Anchor::MIN..Anchor::MAX,
-            mode: ParseMode::Single,
-        });
-
-        loop {
-            let step = queue.pop();
-            let position = if let Some(step) = &step {
-                SyntaxLayerPosition {
-                    depth: step.depth,
-                    range: step.range.clone(),
-                    language: step.language.id(),
-                }
-            } else {
-                SyntaxLayerPosition {
-                    depth: max_depth + 1,
-                    range: Anchor::MAX..Anchor::MAX,
-                    language: None,
-                }
-            };
-
-            let mut done = cursor.item().is_none();
-            while !done && position.cmp(&cursor.end(text), &text).is_gt() {
-                done = true;
-
-                let bounded_position = SyntaxLayerPositionBeforeChange {
-                    position: position.clone(),
-                    change: changed_regions.start_position(),
-                };
-                if bounded_position.cmp(&cursor.start(), &text).is_gt() {
-                    let slice = cursor.slice(&bounded_position, Bias::Left, text);
-                    if !slice.is_empty() {
-                        layers.append(slice, &text);
-                        if changed_regions.prune(cursor.end(text), text) {
-                            done = false;
-                        }
-                    }
-                }
-
-                while position.cmp(&cursor.end(text), text).is_gt() {
-                    let Some(layer) = cursor.item() else { break };
-
-                    if changed_regions.intersects(&layer, text) {
-                        if let SyntaxLayerContent::Parsed { language, .. } = &layer.content {
-                            log::trace!(
-                                "discard layer. language:{}, range:{:?}. changed_regions:{:?}",
-                                language.name(),
-                                LogAnchorRange(&layer.range, text),
-                                LogChangedRegions(&changed_regions, text),
-                            );
-                        }
-
-                        changed_regions.insert(
-                            ChangedRegion {
-                                depth: layer.depth + 1,
-                                range: layer.range.clone(),
-                            },
-                            text,
-                        );
-                    } else {
-                        layers.push(layer.clone(), text);
-                    }
-
-                    cursor.next(text);
-                    if changed_regions.prune(cursor.end(text), text) {
-                        done = false;
-                    }
-                }
-            }
-
-            let Some(step) = step else { break };
-            let (step_start_byte, step_start_point) =
-                step.range.start.summary::<(usize, Point)>(text);
-            let step_end_byte = step.range.end.to_offset(text);
-
-            let mut old_layer = cursor.item();
-            if let Some(layer) = old_layer {
-                if layer.range.to_offset(text) == (step_start_byte..step_end_byte)
-                    && layer.content.language_id() == step.language.id()
-                {
-                    cursor.next(&text);
-                } else {
-                    old_layer = None;
-                }
-            }
-
-            let content = match step.language {
-                ParseStepLanguage::Loaded { language } => {
-                    let Some(grammar) = language.grammar() else {
-                        continue;
-                    };
-                    let tree;
-                    let changed_ranges;
-
-                    let mut included_ranges = step.included_ranges;
-                    for range in &mut included_ranges {
-                        range.start_byte -= step_start_byte;
-                        range.end_byte -= step_start_byte;
-                        range.start_point = (Point::from_ts_point(range.start_point)
-                            - step_start_point)
-                            .to_ts_point();
-                        range.end_point = (Point::from_ts_point(range.end_point)
-                            - step_start_point)
-                            .to_ts_point();
-                    }
-
-                    if let Some((SyntaxLayerContent::Parsed { tree: old_tree, .. }, layer_start)) =
-                        old_layer.map(|layer| (&layer.content, layer.range.start))
-                    {
-                        log::trace!(
-                            "existing layer. language:{}, start:{:?}, ranges:{:?}",
-                            language.name(),
-                            LogPoint(layer_start.to_point(&text)),
-                            LogIncludedRanges(&old_tree.included_ranges())
-                        );
-
-                        if let ParseMode::Combined {
-                            mut parent_layer_changed_ranges,
-                            ..
-                        } = step.mode
-                        {
-                            for range in &mut parent_layer_changed_ranges {
-                                range.start = range.start.saturating_sub(step_start_byte);
-                                range.end = range.end.saturating_sub(step_start_byte);
-                            }
-
-                            let changed_indices;
-                            (included_ranges, changed_indices) = splice_included_ranges(
-                                old_tree.included_ranges(),
-                                &parent_layer_changed_ranges,
-                                &included_ranges,
-                            );
-                            insert_newlines_between_ranges(
-                                changed_indices,
-                                &mut included_ranges,
-                                &text,
-                                step_start_byte,
-                                step_start_point,
-                            );
-                        }
-
-                        if included_ranges.is_empty() {
-                            included_ranges.push(tree_sitter::Range {
-                                start_byte: 0,
-                                end_byte: 0,
-                                start_point: Default::default(),
-                                end_point: Default::default(),
-                            });
-                        }
-
-                        log::trace!(
-                            "update layer. language:{}, start:{:?}, included_ranges:{:?}",
-                            language.name(),
-                            LogAnchorRange(&step.range, text),
-                            LogIncludedRanges(&included_ranges),
-                        );
-
-                        tree = parse_text(
-                            grammar,
-                            text.as_rope(),
-                            step_start_byte,
-                            included_ranges,
-                            Some(old_tree.clone()),
-                        );
-                        changed_ranges = join_ranges(
-                            invalidated_ranges.iter().cloned().filter(|range| {
-                                range.start <= step_end_byte && range.end >= step_start_byte
-                            }),
-                            old_tree.changed_ranges(&tree).map(|r| {
-                                step_start_byte + r.start_byte..step_start_byte + r.end_byte
-                            }),
-                        );
-                    } else {
-                        if matches!(step.mode, ParseMode::Combined { .. }) {
-                            insert_newlines_between_ranges(
-                                0..included_ranges.len(),
-                                &mut included_ranges,
-                                text,
-                                step_start_byte,
-                                step_start_point,
-                            );
-                        }
-
-                        if included_ranges.is_empty() {
-                            included_ranges.push(tree_sitter::Range {
-                                start_byte: 0,
-                                end_byte: 0,
-                                start_point: Default::default(),
-                                end_point: Default::default(),
-                            });
-                        }
-
-                        log::trace!(
-                            "create layer. language:{}, range:{:?}, included_ranges:{:?}",
-                            language.name(),
-                            LogAnchorRange(&step.range, text),
-                            LogIncludedRanges(&included_ranges),
-                        );
-
-                        tree = parse_text(
-                            grammar,
-                            text.as_rope(),
-                            step_start_byte,
-                            included_ranges,
-                            None,
-                        );
-                        changed_ranges = vec![step_start_byte..step_end_byte];
-                    }
-
-                    if let (Some((config, registry)), false) = (
-                        grammar.injection_config.as_ref().zip(registry.as_ref()),
-                        changed_ranges.is_empty(),
-                    ) {
-                        for range in &changed_ranges {
-                            changed_regions.insert(
-                                ChangedRegion {
-                                    depth: step.depth + 1,
-                                    range: text.anchor_before(range.start)
-                                        ..text.anchor_after(range.end),
-                                },
-                                text,
-                            );
-                        }
-                        get_injections(
-                            config,
-                            text,
-                            step.range.clone(),
-                            tree.root_node_with_offset(
-                                step_start_byte,
-                                step_start_point.to_ts_point(),
-                            ),
-                            registry,
-                            step.depth + 1,
-                            &changed_ranges,
-                            &mut combined_injection_ranges,
-                            &mut queue,
-                        );
-                    }
-
-                    SyntaxLayerContent::Parsed { tree, language }
-                }
-                ParseStepLanguage::Pending { name } => SyntaxLayerContent::Pending {
-                    language_name: name,
-                },
-            };
-
-            layers.push(
-                SyntaxLayer {
-                    depth: step.depth,
-                    range: step.range,
-                    content,
-                },
-                &text,
-            );
-        }
-
-        drop(cursor);
-        self.layers = layers;
-        self.interpolated_version = text.version.clone();
-        self.parsed_version = text.version.clone();
-        #[cfg(debug_assertions)]
-        self.check_invariants(text);
-    }
-
-    #[cfg(debug_assertions)]
-    fn check_invariants(&self, text: &BufferSnapshot) {
-        let mut max_depth = 0;
-        let mut prev_range: Option<Range<Anchor>> = None;
-        for layer in self.layers.iter() {
-            if layer.depth == max_depth {
-                if let Some(prev_range) = prev_range {
-                    match layer.range.start.cmp(&prev_range.start, text) {
-                        Ordering::Less => panic!("layers out of order"),
-                        Ordering::Equal => {
-                            assert!(layer.range.end.cmp(&prev_range.end, text).is_ge())
-                        }
-                        Ordering::Greater => {}
-                    }
-                }
-            } else if layer.depth < max_depth {
-                panic!("layers out of order")
-            }
-            max_depth = layer.depth;
-            prev_range = Some(layer.range.clone());
-        }
-    }
-
-    pub fn single_tree_captures<'a>(
-        range: Range<usize>,
-        text: &'a Rope,
-        tree: &'a Tree,
-        language: &'a Arc<Language>,
-        query: fn(&Grammar) -> Option<&Query>,
-    ) -> SyntaxMapCaptures<'a> {
-        SyntaxMapCaptures::new(
-            range.clone(),
-            text,
-            [SyntaxLayerInfo {
-                language,
-                tree,
-                depth: 0,
-                offset: (0, tree_sitter::Point::new(0, 0)),
-            }]
-            .into_iter(),
-            query,
-        )
-    }
-
-    pub fn captures<'a>(
-        &'a self,
-        range: Range<usize>,
-        buffer: &'a BufferSnapshot,
-        query: fn(&Grammar) -> Option<&Query>,
-    ) -> SyntaxMapCaptures {
-        SyntaxMapCaptures::new(
-            range.clone(),
-            buffer.as_rope(),
-            self.layers_for_range(range, buffer).into_iter(),
-            query,
-        )
-    }
-
-    pub fn matches<'a>(
-        &'a self,
-        range: Range<usize>,
-        buffer: &'a BufferSnapshot,
-        query: fn(&Grammar) -> Option<&Query>,
-    ) -> SyntaxMapMatches {
-        SyntaxMapMatches::new(
-            range.clone(),
-            buffer.as_rope(),
-            self.layers_for_range(range, buffer).into_iter(),
-            query,
-        )
-    }
-
-    #[cfg(test)]
-    pub fn layers<'a>(&'a self, buffer: &'a BufferSnapshot) -> Vec<SyntaxLayerInfo> {
-        self.layers_for_range(0..buffer.len(), buffer).collect()
-    }
-
-    pub fn layers_for_range<'a, T: ToOffset>(
-        &'a self,
-        range: Range<T>,
-        buffer: &'a BufferSnapshot,
-    ) -> impl 'a + Iterator<Item = SyntaxLayerInfo> {
-        let start_offset = range.start.to_offset(buffer);
-        let end_offset = range.end.to_offset(buffer);
-        let start = buffer.anchor_before(start_offset);
-        let end = buffer.anchor_after(end_offset);
-
-        let mut cursor = self.layers.filter::<_, ()>(move |summary| {
-            if summary.max_depth > summary.min_depth {
-                true
-            } else {
-                let is_before_start = summary.range.end.cmp(&start, buffer).is_lt();
-                let is_after_end = summary.range.start.cmp(&end, buffer).is_gt();
-                !is_before_start && !is_after_end
-            }
-        });
-
-        cursor.next(buffer);
-        iter::from_fn(move || {
-            while let Some(layer) = cursor.item() {
-                let mut info = None;
-                if let SyntaxLayerContent::Parsed { tree, language } = &layer.content {
-                    let layer_start_offset = layer.range.start.to_offset(buffer);
-                    let layer_start_point = layer.range.start.to_point(buffer).to_ts_point();
-
-                    info = Some(SyntaxLayerInfo {
-                        tree,
-                        language,
-                        depth: layer.depth,
-                        offset: (layer_start_offset, layer_start_point),
-                    });
-                }
-                cursor.next(buffer);
-                if info.is_some() {
-                    return info;
-                }
-            }
-            None
-        })
-    }
-
-    pub fn contains_unknown_injections(&self) -> bool {
-        self.layers.summary().contains_unknown_injections
-    }
-
-    pub fn language_registry_version(&self) -> usize {
-        self.language_registry_version
-    }
-}
-
-impl<'a> SyntaxMapCaptures<'a> {
-    fn new(
-        range: Range<usize>,
-        text: &'a Rope,
-        layers: impl Iterator<Item = SyntaxLayerInfo<'a>>,
-        query: fn(&Grammar) -> Option<&Query>,
-    ) -> Self {
-        let mut result = Self {
-            layers: Vec::new(),
-            grammars: Vec::new(),
-            active_layer_count: 0,
-        };
-        for layer in layers {
-            let grammar = match &layer.language.grammar {
-                Some(grammar) => grammar,
-                None => continue,
-            };
-            let query = match query(&grammar) {
-                Some(query) => query,
-                None => continue,
-            };
-
-            let mut query_cursor = QueryCursorHandle::new();
-
-            // TODO - add a Tree-sitter API to remove the need for this.
-            let cursor = unsafe {
-                std::mem::transmute::<_, &'static mut QueryCursor>(query_cursor.deref_mut())
-            };
-
-            cursor.set_byte_range(range.clone());
-            let captures = cursor.captures(query, layer.node(), TextProvider(text));
-            let grammar_index = result
-                .grammars
-                .iter()
-                .position(|g| g.id == grammar.id())
-                .unwrap_or_else(|| {
-                    result.grammars.push(grammar);
-                    result.grammars.len() - 1
-                });
-            let mut layer = SyntaxMapCapturesLayer {
-                depth: layer.depth,
-                grammar_index,
-                next_capture: None,
-                captures,
-                _query_cursor: query_cursor,
-            };
-
-            layer.advance();
-            if layer.next_capture.is_some() {
-                let key = layer.sort_key();
-                let ix = match result.layers[..result.active_layer_count]
-                    .binary_search_by_key(&key, |layer| layer.sort_key())
-                {
-                    Ok(ix) | Err(ix) => ix,
-                };
-                result.layers.insert(ix, layer);
-                result.active_layer_count += 1;
-            } else {
-                result.layers.push(layer);
-            }
-        }
-
-        result
-    }
-
-    pub fn grammars(&self) -> &[&'a Grammar] {
-        &self.grammars
-    }
-
-    pub fn peek(&self) -> Option<SyntaxMapCapture<'a>> {
-        let layer = self.layers[..self.active_layer_count].first()?;
-        let capture = layer.next_capture?;
-        Some(SyntaxMapCapture {
-            depth: layer.depth,
-            grammar_index: layer.grammar_index,
-            index: capture.index,
-            node: capture.node,
-        })
-    }
-
-    pub fn advance(&mut self) -> bool {
-        let layer = if let Some(layer) = self.layers[..self.active_layer_count].first_mut() {
-            layer
-        } else {
-            return false;
-        };
-
-        layer.advance();
-        if layer.next_capture.is_some() {
-            let key = layer.sort_key();
-            let i = 1 + self.layers[1..self.active_layer_count]
-                .iter()
-                .position(|later_layer| key < later_layer.sort_key())
-                .unwrap_or(self.active_layer_count - 1);
-            self.layers[0..i].rotate_left(1);
-        } else {
-            self.layers[0..self.active_layer_count].rotate_left(1);
-            self.active_layer_count -= 1;
-        }
-
-        true
-    }
-
-    pub fn set_byte_range(&mut self, range: Range<usize>) {
-        for layer in &mut self.layers {
-            layer.captures.set_byte_range(range.clone());
-            if let Some(capture) = &layer.next_capture {
-                if capture.node.end_byte() > range.start {
-                    continue;
-                }
-            }
-            layer.advance();
-        }
-        self.layers.sort_unstable_by_key(|layer| layer.sort_key());
-        self.active_layer_count = self
-            .layers
-            .iter()
-            .position(|layer| layer.next_capture.is_none())
-            .unwrap_or(self.layers.len());
-    }
-}
-
-impl<'a> SyntaxMapMatches<'a> {
-    fn new(
-        range: Range<usize>,
-        text: &'a Rope,
-        layers: impl Iterator<Item = SyntaxLayerInfo<'a>>,
-        query: fn(&Grammar) -> Option<&Query>,
-    ) -> Self {
-        let mut result = Self::default();
-        for layer in layers {
-            let grammar = match &layer.language.grammar {
-                Some(grammar) => grammar,
-                None => continue,
-            };
-            let query = match query(&grammar) {
-                Some(query) => query,
-                None => continue,
-            };
-
-            let mut query_cursor = QueryCursorHandle::new();
-
-            // TODO - add a Tree-sitter API to remove the need for this.
-            let cursor = unsafe {
-                std::mem::transmute::<_, &'static mut QueryCursor>(query_cursor.deref_mut())
-            };
-
-            cursor.set_byte_range(range.clone());
-            let matches = cursor.matches(query, layer.node(), TextProvider(text));
-            let grammar_index = result
-                .grammars
-                .iter()
-                .position(|g| g.id == grammar.id())
-                .unwrap_or_else(|| {
-                    result.grammars.push(grammar);
-                    result.grammars.len() - 1
-                });
-            let mut layer = SyntaxMapMatchesLayer {
-                depth: layer.depth,
-                grammar_index,
-                matches,
-                next_pattern_index: 0,
-                next_captures: Vec::new(),
-                has_next: false,
-                _query_cursor: query_cursor,
-            };
-
-            layer.advance();
-            if layer.has_next {
-                let key = layer.sort_key();
-                let ix = match result.layers[..result.active_layer_count]
-                    .binary_search_by_key(&key, |layer| layer.sort_key())
-                {
-                    Ok(ix) | Err(ix) => ix,
-                };
-                result.layers.insert(ix, layer);
-                result.active_layer_count += 1;
-            } else {
-                result.layers.push(layer);
-            }
-        }
-        result
-    }
-
-    pub fn grammars(&self) -> &[&'a Grammar] {
-        &self.grammars
-    }
-
-    pub fn peek(&self) -> Option<SyntaxMapMatch> {
-        let layer = self.layers.first()?;
-        if !layer.has_next {
-            return None;
-        }
-        Some(SyntaxMapMatch {
-            depth: layer.depth,
-            grammar_index: layer.grammar_index,
-            pattern_index: layer.next_pattern_index,
-            captures: &layer.next_captures,
-        })
-    }
-
-    pub fn advance(&mut self) -> bool {
-        let layer = if let Some(layer) = self.layers.first_mut() {
-            layer
-        } else {
-            return false;
-        };
-
-        layer.advance();
-        if layer.has_next {
-            let key = layer.sort_key();
-            let i = 1 + self.layers[1..self.active_layer_count]
-                .iter()
-                .position(|later_layer| key < later_layer.sort_key())
-                .unwrap_or(self.active_layer_count - 1);
-            self.layers[0..i].rotate_left(1);
-        } else {
-            self.layers[0..self.active_layer_count].rotate_left(1);
-            self.active_layer_count -= 1;
-        }
-
-        true
-    }
-}
-
-impl<'a> SyntaxMapCapturesLayer<'a> {
-    fn advance(&mut self) {
-        self.next_capture = self.captures.next().map(|(mat, ix)| mat.captures[ix]);
-    }
-
-    fn sort_key(&self) -> (usize, Reverse<usize>, usize) {
-        if let Some(capture) = &self.next_capture {
-            let range = capture.node.byte_range();
-            (range.start, Reverse(range.end), self.depth)
-        } else {
-            (usize::MAX, Reverse(0), usize::MAX)
-        }
-    }
-}
-
-impl<'a> SyntaxMapMatchesLayer<'a> {
-    fn advance(&mut self) {
-        if let Some(mat) = self.matches.next() {
-            self.next_captures.clear();
-            self.next_captures.extend_from_slice(&mat.captures);
-            self.next_pattern_index = mat.pattern_index;
-            self.has_next = true;
-        } else {
-            self.has_next = false;
-        }
-    }
-
-    fn sort_key(&self) -> (usize, Reverse<usize>, usize) {
-        if self.has_next {
-            let captures = &self.next_captures;
-            if let Some((first, last)) = captures.first().zip(captures.last()) {
-                return (
-                    first.node.start_byte(),
-                    Reverse(last.node.end_byte()),
-                    self.depth,
-                );
-            }
-        }
-        (usize::MAX, Reverse(0), usize::MAX)
-    }
-}
-
-impl<'a> Iterator for SyntaxMapCaptures<'a> {
-    type Item = SyntaxMapCapture<'a>;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        let result = self.peek();
-        self.advance();
-        result
-    }
-}
-
-fn join_ranges(
-    a: impl Iterator<Item = Range<usize>>,
-    b: impl Iterator<Item = Range<usize>>,
-) -> Vec<Range<usize>> {
-    let mut result = Vec::<Range<usize>>::new();
-    let mut a = a.peekable();
-    let mut b = b.peekable();
-    loop {
-        let range = match (a.peek(), b.peek()) {
-            (Some(range_a), Some(range_b)) => {
-                if range_a.start < range_b.start {
-                    a.next().unwrap()
-                } else {
-                    b.next().unwrap()
-                }
-            }
-            (None, Some(_)) => b.next().unwrap(),
-            (Some(_), None) => a.next().unwrap(),
-            (None, None) => break,
-        };
-
-        if let Some(last) = result.last_mut() {
-            if range.start <= last.end {
-                last.end = last.end.max(range.end);
-                continue;
-            }
-        }
-        result.push(range);
-    }
-    result
-}
-
-fn parse_text(
-    grammar: &Grammar,
-    text: &Rope,
-    start_byte: usize,
-    ranges: Vec<tree_sitter::Range>,
-    old_tree: Option<Tree>,
-) -> Tree {
-    PARSER.with(|parser| {
-        let mut parser = parser.borrow_mut();
-        let mut chunks = text.chunks_in_range(start_byte..text.len());
-        parser
-            .set_included_ranges(&ranges)
-            .expect("overlapping ranges");
-        parser
-            .set_language(&grammar.ts_language)
-            .expect("incompatible grammar");
-        parser
-            .parse_with(
-                &mut move |offset, _| {
-                    chunks.seek(start_byte + offset);
-                    chunks.next().unwrap_or("").as_bytes()
-                },
-                old_tree.as_ref(),
-            )
-            .expect("invalid language")
-    })
-}
-
-fn get_injections(
-    config: &InjectionConfig,
-    text: &BufferSnapshot,
-    outer_range: Range<Anchor>,
-    node: Node,
-    language_registry: &Arc<LanguageRegistry>,
-    depth: usize,
-    changed_ranges: &[Range<usize>],
-    combined_injection_ranges: &mut HashMap<Arc<Language>, Vec<tree_sitter::Range>>,
-    queue: &mut BinaryHeap<ParseStep>,
-) {
-    let mut query_cursor = QueryCursorHandle::new();
-    let mut prev_match = None;
-
-    // Ensure that a `ParseStep` is created for every combined injection language, even
-    // if there currently no matches for that injection.
-    combined_injection_ranges.clear();
-    for pattern in &config.patterns {
-        if let (Some(language_name), true) = (pattern.language.as_ref(), pattern.combined) {
-            if let Some(language) = language_registry
-                .language_for_name_or_extension(language_name)
-                .now_or_never()
-                .and_then(|language| language.ok())
-            {
-                combined_injection_ranges.insert(language, Vec::new());
-            }
-        }
-    }
-
-    for query_range in changed_ranges {
-        query_cursor.set_byte_range(query_range.start.saturating_sub(1)..query_range.end + 1);
-        for mat in query_cursor.matches(&config.query, node, TextProvider(text.as_rope())) {
-            let content_ranges = mat
-                .nodes_for_capture_index(config.content_capture_ix)
-                .map(|node| node.range())
-                .collect::<Vec<_>>();
-            if content_ranges.is_empty() {
-                continue;
-            }
-
-            let content_range =
-                content_ranges.first().unwrap().start_byte..content_ranges.last().unwrap().end_byte;
-
-            // Avoid duplicate matches if two changed ranges intersect the same injection.
-            if let Some((prev_pattern_ix, prev_range)) = &prev_match {
-                if mat.pattern_index == *prev_pattern_ix && content_range == *prev_range {
-                    continue;
-                }
-            }
-
-            prev_match = Some((mat.pattern_index, content_range.clone()));
-            let combined = config.patterns[mat.pattern_index].combined;
-
-            let mut language_name = None;
-            let mut step_range = content_range.clone();
-            if let Some(name) = config.patterns[mat.pattern_index].language.as_ref() {
-                language_name = Some(Cow::Borrowed(name.as_ref()))
-            } else if let Some(language_node) = config
-                .language_capture_ix
-                .and_then(|ix| mat.nodes_for_capture_index(ix).next())
-            {
-                step_range.start = cmp::min(content_range.start, language_node.start_byte());
-                step_range.end = cmp::max(content_range.end, language_node.end_byte());
-                language_name = Some(Cow::Owned(
-                    text.text_for_range(language_node.byte_range()).collect(),
-                ))
-            };
-
-            if let Some(language_name) = language_name {
-                let language = language_registry
-                    .language_for_name_or_extension(&language_name)
-                    .now_or_never()
-                    .and_then(|language| language.ok());
-                let range = text.anchor_before(step_range.start)..text.anchor_after(step_range.end);
-                if let Some(language) = language {
-                    if combined {
-                        combined_injection_ranges
-                            .entry(language.clone())
-                            .or_default()
-                            .extend(content_ranges);
-                    } else {
-                        queue.push(ParseStep {
-                            depth,
-                            language: ParseStepLanguage::Loaded { language },
-                            included_ranges: content_ranges,
-                            range,
-                            mode: ParseMode::Single,
-                        });
-                    }
-                } else {
-                    queue.push(ParseStep {
-                        depth,
-                        language: ParseStepLanguage::Pending {
-                            name: language_name.into(),
-                        },
-                        included_ranges: content_ranges,
-                        range,
-                        mode: ParseMode::Single,
-                    });
-                }
-            }
-        }
-    }
-
-    for (language, mut included_ranges) in combined_injection_ranges.drain() {
-        included_ranges.sort_unstable_by(|a, b| {
-            Ord::cmp(&a.start_byte, &b.start_byte).then_with(|| Ord::cmp(&a.end_byte, &b.end_byte))
-        });
-        queue.push(ParseStep {
-            depth,
-            language: ParseStepLanguage::Loaded { language },
-            range: outer_range.clone(),
-            included_ranges,
-            mode: ParseMode::Combined {
-                parent_layer_range: node.start_byte()..node.end_byte(),
-                parent_layer_changed_ranges: changed_ranges.to_vec(),
-            },
-        })
-    }
-}
-
-/// Update the given list of included `ranges`, removing any ranges that intersect
-/// `removed_ranges`, and inserting the given `new_ranges`.
-///
-/// Returns a new vector of ranges, and the range of the vector that was changed,
-/// from the previous `ranges` vector.
-pub(crate) fn splice_included_ranges(
-    mut ranges: Vec<tree_sitter::Range>,
-    removed_ranges: &[Range<usize>],
-    new_ranges: &[tree_sitter::Range],
-) -> (Vec<tree_sitter::Range>, Range<usize>) {
-    let mut removed_ranges = removed_ranges.iter().cloned().peekable();
-    let mut new_ranges = new_ranges.into_iter().cloned().peekable();
-    let mut ranges_ix = 0;
-    let mut changed_portion = usize::MAX..0;
-    loop {
-        let next_new_range = new_ranges.peek();
-        let next_removed_range = removed_ranges.peek();
-
-        let (remove, insert) = match (next_removed_range, next_new_range) {
-            (None, None) => break,
-            (Some(_), None) => (removed_ranges.next().unwrap(), None),
-            (Some(next_removed_range), Some(next_new_range)) => {
-                if next_removed_range.end < next_new_range.start_byte {
-                    (removed_ranges.next().unwrap(), None)
-                } else {
-                    let mut start = next_new_range.start_byte;
-                    let mut end = next_new_range.end_byte;
-
-                    while let Some(next_removed_range) = removed_ranges.peek() {
-                        if next_removed_range.start > next_new_range.end_byte {
-                            break;
-                        }
-                        let next_removed_range = removed_ranges.next().unwrap();
-                        start = cmp::min(start, next_removed_range.start);
-                        end = cmp::max(end, next_removed_range.end);
-                    }
-
-                    (start..end, Some(new_ranges.next().unwrap()))
-                }
-            }
-            (None, Some(next_new_range)) => (
-                next_new_range.start_byte..next_new_range.end_byte,
-                Some(new_ranges.next().unwrap()),
-            ),
-        };
-
-        let mut start_ix = ranges_ix
-            + match ranges[ranges_ix..].binary_search_by_key(&remove.start, |r| r.end_byte) {
-                Ok(ix) => ix,
-                Err(ix) => ix,
-            };
-        let mut end_ix = ranges_ix
-            + match ranges[ranges_ix..].binary_search_by_key(&remove.end, |r| r.start_byte) {
-                Ok(ix) => ix + 1,
-                Err(ix) => ix,
-            };
-
-        // If there are empty ranges, then there may be multiple ranges with the same
-        // start or end. Expand the splice to include any adjacent ranges that touch
-        // the changed range.
-        while start_ix > 0 {
-            if ranges[start_ix - 1].end_byte == remove.start {
-                start_ix -= 1;
-            } else {
-                break;
-            }
-        }
-        while let Some(range) = ranges.get(end_ix) {
-            if range.start_byte == remove.end {
-                end_ix += 1;
-            } else {
-                break;
-            }
-        }
-
-        changed_portion.start = changed_portion.start.min(start_ix);
-        changed_portion.end = changed_portion.end.max(if insert.is_some() {
-            start_ix + 1
-        } else {
-            start_ix
-        });
-
-        ranges.splice(start_ix..end_ix, insert);
-        ranges_ix = start_ix;
-    }
-
-    if changed_portion.end < changed_portion.start {
-        changed_portion = 0..0;
-    }
-
-    (ranges, changed_portion)
-}
-
-/// Ensure there are newline ranges in between content range that appear on
-/// different lines. For performance, only iterate through the given range of
-/// indices. All of the ranges in the array are relative to a given start byte
-/// and point.
-fn insert_newlines_between_ranges(
-    indices: Range<usize>,
-    ranges: &mut Vec<tree_sitter::Range>,
-    text: &text::BufferSnapshot,
-    start_byte: usize,
-    start_point: Point,
-) {
-    let mut ix = indices.end + 1;
-    while ix > indices.start {
-        ix -= 1;
-        if 0 == ix || ix == ranges.len() {
-            continue;
-        }
-
-        let range_b = ranges[ix].clone();
-        let range_a = &mut ranges[ix - 1];
-        if range_a.end_point.column == 0 {
-            continue;
-        }
-
-        if range_a.end_point.row < range_b.start_point.row {
-            let end_point = start_point + Point::from_ts_point(range_a.end_point);
-            let line_end = Point::new(end_point.row, text.line_len(end_point.row));
-            if end_point.column as u32 >= line_end.column {
-                range_a.end_byte += 1;
-                range_a.end_point.row += 1;
-                range_a.end_point.column = 0;
-            } else {
-                let newline_offset = text.point_to_offset(line_end);
-                ranges.insert(
-                    ix,
-                    tree_sitter::Range {
-                        start_byte: newline_offset - start_byte,
-                        end_byte: newline_offset - start_byte + 1,
-                        start_point: (line_end - start_point).to_ts_point(),
-                        end_point: ((line_end - start_point) + Point::new(1, 0)).to_ts_point(),
-                    },
-                )
-            }
-        }
-    }
-}
-
-impl OwnedSyntaxLayerInfo {
-    pub fn node(&self) -> Node {
-        self.tree
-            .root_node_with_offset(self.offset.0, self.offset.1)
-    }
-}
-
-impl<'a> SyntaxLayerInfo<'a> {
-    pub fn to_owned(&self) -> OwnedSyntaxLayerInfo {
-        OwnedSyntaxLayerInfo {
-            tree: self.tree.clone(),
-            offset: self.offset,
-            depth: self.depth,
-            language: self.language.clone(),
-        }
-    }
-
-    pub fn node(&self) -> Node<'a> {
-        self.tree
-            .root_node_with_offset(self.offset.0, self.offset.1)
-    }
-
-    pub(crate) fn override_id(&self, offset: usize, text: &text::BufferSnapshot) -> Option<u32> {
-        let text = TextProvider(text.as_rope());
-        let config = self.language.grammar.as_ref()?.override_config.as_ref()?;
-
-        let mut query_cursor = QueryCursorHandle::new();
-        query_cursor.set_byte_range(offset..offset);
-
-        let mut smallest_match: Option<(u32, Range<usize>)> = None;
-        for mat in query_cursor.matches(&config.query, self.node(), text) {
-            for capture in mat.captures {
-                if !config.values.contains_key(&capture.index) {
-                    continue;
-                }
-
-                let range = capture.node.byte_range();
-                if offset <= range.start || offset >= range.end {
-                    continue;
-                }
-
-                if let Some((_, smallest_range)) = &smallest_match {
-                    if range.len() < smallest_range.len() {
-                        smallest_match = Some((capture.index, range))
-                    }
-                    continue;
-                }
-
-                smallest_match = Some((capture.index, range));
-            }
-        }
-
-        smallest_match.map(|(index, _)| index)
-    }
-}
-
-impl std::ops::Deref for SyntaxMap {
-    type Target = SyntaxSnapshot;
-
-    fn deref(&self) -> &Self::Target {
-        &self.snapshot
-    }
-}
-
-impl PartialEq for ParseStep {
-    fn eq(&self, _: &Self) -> bool {
-        false
-    }
-}
-
-impl Eq for ParseStep {}
-
-impl PartialOrd for ParseStep {
-    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
-        Some(self.cmp(&other))
-    }
-}
-
-impl Ord for ParseStep {
-    fn cmp(&self, other: &Self) -> Ordering {
-        let range_a = self.range();
-        let range_b = other.range();
-        Ord::cmp(&other.depth, &self.depth)
-            .then_with(|| Ord::cmp(&range_b.start, &range_a.start))
-            .then_with(|| Ord::cmp(&range_a.end, &range_b.end))
-            .then_with(|| self.language.id().cmp(&other.language.id()))
-    }
-}
-
-impl ParseStep {
-    fn range(&self) -> Range<usize> {
-        if let ParseMode::Combined {
-            parent_layer_range, ..
-        } = &self.mode
-        {
-            parent_layer_range.clone()
-        } else {
-            let start = self.included_ranges.first().map_or(0, |r| r.start_byte);
-            let end = self.included_ranges.last().map_or(0, |r| r.end_byte);
-            start..end
-        }
-    }
-}
-
-impl ChangedRegion {
-    fn cmp(&self, other: &Self, buffer: &BufferSnapshot) -> Ordering {
-        let range_a = &self.range;
-        let range_b = &other.range;
-        Ord::cmp(&self.depth, &other.depth)
-            .then_with(|| range_a.start.cmp(&range_b.start, buffer))
-            .then_with(|| range_b.end.cmp(&range_a.end, buffer))
-    }
-}
-
-impl ChangeRegionSet {
-    fn start_position(&self) -> ChangeStartPosition {
-        self.0.first().map_or(
-            ChangeStartPosition {
-                depth: usize::MAX,
-                position: Anchor::MAX,
-            },
-            |region| ChangeStartPosition {
-                depth: region.depth,
-                position: region.range.start,
-            },
-        )
-    }
-
-    fn intersects(&self, layer: &SyntaxLayer, text: &BufferSnapshot) -> bool {
-        for region in &self.0 {
-            if region.depth < layer.depth {
-                continue;
-            }
-            if region.depth > layer.depth {
-                break;
-            }
-            if region.range.end.cmp(&layer.range.start, text).is_le() {
-                continue;
-            }
-            if region.range.start.cmp(&layer.range.end, text).is_ge() {
-                break;
-            }
-            return true;
-        }
-        false
-    }
-
-    fn insert(&mut self, region: ChangedRegion, text: &BufferSnapshot) {
-        if let Err(ix) = self.0.binary_search_by(|probe| probe.cmp(&region, text)) {
-            self.0.insert(ix, region);
-        }
-    }
-
-    fn prune(&mut self, summary: SyntaxLayerSummary, text: &BufferSnapshot) -> bool {
-        let prev_len = self.0.len();
-        self.0.retain(|region| {
-            region.depth > summary.max_depth
-                || (region.depth == summary.max_depth
-                    && region
-                        .range
-                        .end
-                        .cmp(&summary.last_layer_range.start, text)
-                        .is_gt())
-        });
-        self.0.len() < prev_len
-    }
-}
-
-impl Default for SyntaxLayerSummary {
-    fn default() -> Self {
-        Self {
-            max_depth: 0,
-            min_depth: 0,
-            range: Anchor::MAX..Anchor::MIN,
-            last_layer_range: Anchor::MIN..Anchor::MAX,
-            last_layer_language: None,
-            contains_unknown_injections: false,
-        }
-    }
-}
-
-impl sum_tree::Summary for SyntaxLayerSummary {
-    type Context = BufferSnapshot;
-
-    fn add_summary(&mut self, other: &Self, buffer: &Self::Context) {
-        if other.max_depth > self.max_depth {
-            self.max_depth = other.max_depth;
-            self.range = other.range.clone();
-        } else {
-            if self.range == (Anchor::MAX..Anchor::MAX) {
-                self.range.start = other.range.start;
-            }
-            if other.range.end.cmp(&self.range.end, buffer).is_gt() {
-                self.range.end = other.range.end;
-            }
-        }
-        self.last_layer_range = other.last_layer_range.clone();
-        self.last_layer_language = other.last_layer_language;
-        self.contains_unknown_injections |= other.contains_unknown_injections;
-    }
-}
-
-impl<'a> SeekTarget<'a, SyntaxLayerSummary, SyntaxLayerSummary> for SyntaxLayerPosition {
-    fn cmp(&self, cursor_location: &SyntaxLayerSummary, buffer: &BufferSnapshot) -> Ordering {
-        Ord::cmp(&self.depth, &cursor_location.max_depth)
-            .then_with(|| {
-                self.range
-                    .start
-                    .cmp(&cursor_location.last_layer_range.start, buffer)
-            })
-            .then_with(|| {
-                cursor_location
-                    .last_layer_range
-                    .end
-                    .cmp(&self.range.end, buffer)
-            })
-            .then_with(|| self.language.cmp(&cursor_location.last_layer_language))
-    }
-}
-
-impl<'a> SeekTarget<'a, SyntaxLayerSummary, SyntaxLayerSummary> for ChangeStartPosition {
-    fn cmp(&self, cursor_location: &SyntaxLayerSummary, text: &BufferSnapshot) -> Ordering {
-        Ord::cmp(&self.depth, &cursor_location.max_depth)
-            .then_with(|| self.position.cmp(&cursor_location.range.end, text))
-    }
-}
-
-impl<'a> SeekTarget<'a, SyntaxLayerSummary, SyntaxLayerSummary>
-    for SyntaxLayerPositionBeforeChange
-{
-    fn cmp(&self, cursor_location: &SyntaxLayerSummary, buffer: &BufferSnapshot) -> Ordering {
-        if self.change.cmp(cursor_location, buffer).is_le() {
-            return Ordering::Less;
-        } else {
-            self.position.cmp(cursor_location, buffer)
-        }
-    }
-}
-
-impl sum_tree::Item for SyntaxLayer {
-    type Summary = SyntaxLayerSummary;
-
-    fn summary(&self) -> Self::Summary {
-        SyntaxLayerSummary {
-            min_depth: self.depth,
-            max_depth: self.depth,
-            range: self.range.clone(),
-            last_layer_range: self.range.clone(),
-            last_layer_language: self.content.language_id(),
-            contains_unknown_injections: matches!(self.content, SyntaxLayerContent::Pending { .. }),
-        }
-    }
-}
-
-impl std::fmt::Debug for SyntaxLayer {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("SyntaxLayer")
-            .field("depth", &self.depth)
-            .field("range", &self.range)
-            .field("tree", &self.content.tree())
-            .finish()
-    }
-}
-
-impl<'a> tree_sitter::TextProvider<&'a [u8]> for TextProvider<'a> {
-    type I = ByteChunks<'a>;
-
-    fn text(&mut self, node: tree_sitter::Node) -> Self::I {
-        ByteChunks(self.0.chunks_in_range(node.byte_range()))
-    }
-}
-
-impl<'a> Iterator for ByteChunks<'a> {
-    type Item = &'a [u8];
-
-    fn next(&mut self) -> Option<Self::Item> {
-        self.0.next().map(str::as_bytes)
-    }
-}
-
-impl QueryCursorHandle {
-    pub(crate) fn new() -> Self {
-        let mut cursor = QUERY_CURSORS.lock().pop().unwrap_or_else(QueryCursor::new);
-        cursor.set_match_limit(64);
-        QueryCursorHandle(Some(cursor))
-    }
-}
-
-impl Deref for QueryCursorHandle {
-    type Target = QueryCursor;
-
-    fn deref(&self) -> &Self::Target {
-        self.0.as_ref().unwrap()
-    }
-}
-
-impl DerefMut for QueryCursorHandle {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        self.0.as_mut().unwrap()
-    }
-}
-
-impl Drop for QueryCursorHandle {
-    fn drop(&mut self) {
-        let mut cursor = self.0.take().unwrap();
-        cursor.set_byte_range(0..usize::MAX);
-        cursor.set_point_range(Point::zero().to_ts_point()..Point::MAX.to_ts_point());
-        QUERY_CURSORS.lock().push(cursor)
-    }
-}
-
-pub(crate) trait ToTreeSitterPoint {
-    fn to_ts_point(self) -> tree_sitter::Point;
-    fn from_ts_point(point: tree_sitter::Point) -> Self;
-}
-
-impl ToTreeSitterPoint for Point {
-    fn to_ts_point(self) -> tree_sitter::Point {
-        tree_sitter::Point::new(self.row as usize, self.column as usize)
-    }
-
-    fn from_ts_point(point: tree_sitter::Point) -> Self {
-        Point::new(point.row as u32, point.column as u32)
-    }
-}
-
-struct LogIncludedRanges<'a>(&'a [tree_sitter::Range]);
-struct LogPoint(Point);
-struct LogAnchorRange<'a>(&'a Range<Anchor>, &'a text::BufferSnapshot);
-struct LogChangedRegions<'a>(&'a ChangeRegionSet, &'a text::BufferSnapshot);
-
-impl<'a> fmt::Debug for LogIncludedRanges<'a> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_list()
-            .entries(self.0.iter().map(|range| {
-                let start = range.start_point;
-                let end = range.end_point;
-                (start.row, start.column)..(end.row, end.column)
-            }))
-            .finish()
-    }
-}
-
-impl<'a> fmt::Debug for LogAnchorRange<'a> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let range = self.0.to_point(self.1);
-        (LogPoint(range.start)..LogPoint(range.end)).fmt(f)
-    }
-}
-
-impl<'a> fmt::Debug for LogChangedRegions<'a> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_list()
-            .entries(
-                self.0
-                     .0
-                    .iter()
-                    .map(|region| LogAnchorRange(&region.range, self.1)),
-            )
-            .finish()
-    }
-}
-
-impl fmt::Debug for LogPoint {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        (self.0.row, self.0.column).fmt(f)
-    }
-}

crates/language2/src/syntax_map/syntax_map_tests.rs 🔗

@@ -1,1323 +0,0 @@
-use super::*;
-use crate::LanguageConfig;
-use rand::rngs::StdRng;
-use std::{env, ops::Range, sync::Arc};
-use text::Buffer;
-use tree_sitter::Node;
-use unindent::Unindent as _;
-use util::test::marked_text_ranges;
-
-#[test]
-fn test_splice_included_ranges() {
-    let ranges = vec![ts_range(20..30), ts_range(50..60), ts_range(80..90)];
-
-    let (new_ranges, change) = splice_included_ranges(
-        ranges.clone(),
-        &[54..56, 58..68],
-        &[ts_range(50..54), ts_range(59..67)],
-    );
-    assert_eq!(
-        new_ranges,
-        &[
-            ts_range(20..30),
-            ts_range(50..54),
-            ts_range(59..67),
-            ts_range(80..90),
-        ]
-    );
-    assert_eq!(change, 1..3);
-
-    let (new_ranges, change) = splice_included_ranges(ranges.clone(), &[70..71, 91..100], &[]);
-    assert_eq!(
-        new_ranges,
-        &[ts_range(20..30), ts_range(50..60), ts_range(80..90)]
-    );
-    assert_eq!(change, 2..3);
-
-    let (new_ranges, change) =
-        splice_included_ranges(ranges.clone(), &[], &[ts_range(0..2), ts_range(70..75)]);
-    assert_eq!(
-        new_ranges,
-        &[
-            ts_range(0..2),
-            ts_range(20..30),
-            ts_range(50..60),
-            ts_range(70..75),
-            ts_range(80..90)
-        ]
-    );
-    assert_eq!(change, 0..4);
-
-    let (new_ranges, change) =
-        splice_included_ranges(ranges.clone(), &[30..50], &[ts_range(25..55)]);
-    assert_eq!(new_ranges, &[ts_range(25..55), ts_range(80..90)]);
-    assert_eq!(change, 0..1);
-
-    // does not create overlapping ranges
-    let (new_ranges, change) =
-        splice_included_ranges(ranges.clone(), &[0..18], &[ts_range(20..32)]);
-    assert_eq!(
-        new_ranges,
-        &[ts_range(20..32), ts_range(50..60), ts_range(80..90)]
-    );
-    assert_eq!(change, 0..1);
-
-    fn ts_range(range: Range<usize>) -> tree_sitter::Range {
-        tree_sitter::Range {
-            start_byte: range.start,
-            start_point: tree_sitter::Point {
-                row: 0,
-                column: range.start,
-            },
-            end_byte: range.end,
-            end_point: tree_sitter::Point {
-                row: 0,
-                column: range.end,
-            },
-        }
-    }
-}
-
-#[gpui::test]
-fn test_syntax_map_layers_for_range() {
-    let registry = Arc::new(LanguageRegistry::test());
-    let language = Arc::new(rust_lang());
-    registry.add(language.clone());
-
-    let mut buffer = Buffer::new(
-        0,
-        0,
-        r#"
-            fn a() {
-                assert_eq!(
-                    b(vec![C {}]),
-                    vec![d.e],
-                );
-                println!("{}", f(|_| true));
-            }
-        "#
-        .unindent(),
-    );
-
-    let mut syntax_map = SyntaxMap::new();
-    syntax_map.set_language_registry(registry.clone());
-    syntax_map.reparse(language.clone(), &buffer);
-
-    assert_layers_for_range(
-        &syntax_map,
-        &buffer,
-        Point::new(2, 0)..Point::new(2, 0),
-        &[
-            "...(function_item ... (block (expression_statement (macro_invocation...",
-            "...(tuple_expression (call_expression ... arguments: (arguments (macro_invocation...",
-        ],
-    );
-    assert_layers_for_range(
-        &syntax_map,
-        &buffer,
-        Point::new(2, 14)..Point::new(2, 16),
-        &[
-            "...(function_item ...",
-            "...(tuple_expression (call_expression ... arguments: (arguments (macro_invocation...",
-            "...(array_expression (struct_expression ...",
-        ],
-    );
-    assert_layers_for_range(
-        &syntax_map,
-        &buffer,
-        Point::new(3, 14)..Point::new(3, 16),
-        &[
-            "...(function_item ...",
-            "...(tuple_expression (call_expression ... arguments: (arguments (macro_invocation...",
-            "...(array_expression (field_expression ...",
-        ],
-    );
-    assert_layers_for_range(
-        &syntax_map,
-        &buffer,
-        Point::new(5, 12)..Point::new(5, 16),
-        &[
-            "...(function_item ...",
-            "...(call_expression ... (arguments (closure_expression ...",
-        ],
-    );
-
-    // Replace a vec! macro invocation with a plain slice, removing a syntactic layer.
-    let macro_name_range = range_for_text(&buffer, "vec!");
-    buffer.edit([(macro_name_range, "&")]);
-    syntax_map.interpolate(&buffer);
-    syntax_map.reparse(language.clone(), &buffer);
-
-    assert_layers_for_range(
-            &syntax_map,
-            &buffer,
-            Point::new(2, 14)..Point::new(2, 16),
-            &[
-                "...(function_item ...",
-                "...(tuple_expression (call_expression ... arguments: (arguments (reference_expression value: (array_expression...",
-            ],
-        );
-
-    // Put the vec! macro back, adding back the syntactic layer.
-    buffer.undo();
-    syntax_map.interpolate(&buffer);
-    syntax_map.reparse(language.clone(), &buffer);
-
-    assert_layers_for_range(
-        &syntax_map,
-        &buffer,
-        Point::new(2, 14)..Point::new(2, 16),
-        &[
-            "...(function_item ...",
-            "...(tuple_expression (call_expression ... arguments: (arguments (macro_invocation...",
-            "...(array_expression (struct_expression ...",
-        ],
-    );
-}
-
-#[gpui::test]
-fn test_dynamic_language_injection() {
-    let registry = Arc::new(LanguageRegistry::test());
-    let markdown = Arc::new(markdown_lang());
-    registry.add(markdown.clone());
-    registry.add(Arc::new(rust_lang()));
-    registry.add(Arc::new(ruby_lang()));
-
-    let mut buffer = Buffer::new(
-        0,
-        0,
-        r#"
-            This is a code block:
-
-            ```rs
-            fn foo() {}
-            ```
-        "#
-        .unindent(),
-    );
-
-    let mut syntax_map = SyntaxMap::new();
-    syntax_map.set_language_registry(registry.clone());
-    syntax_map.reparse(markdown.clone(), &buffer);
-    assert_layers_for_range(
-            &syntax_map,
-            &buffer,
-            Point::new(3, 0)..Point::new(3, 0),
-            &[
-                "...(fenced_code_block (fenced_code_block_delimiter) (info_string (language)) (code_fence_content) (fenced_code_block_delimiter...",
-                "...(function_item name: (identifier) parameters: (parameters) body: (block)...",
-            ],
-        );
-
-    // Replace Rust with Ruby in code block.
-    let macro_name_range = range_for_text(&buffer, "rs");
-    buffer.edit([(macro_name_range, "ruby")]);
-    syntax_map.interpolate(&buffer);
-    syntax_map.reparse(markdown.clone(), &buffer);
-    assert_layers_for_range(
-            &syntax_map,
-            &buffer,
-            Point::new(3, 0)..Point::new(3, 0),
-            &[
-                "...(fenced_code_block (fenced_code_block_delimiter) (info_string (language)) (code_fence_content) (fenced_code_block_delimiter...",
-                "...(call method: (identifier) arguments: (argument_list (call method: (identifier) arguments: (argument_list) block: (block)...",
-            ],
-        );
-
-    // Replace Ruby with a language that hasn't been loaded yet.
-    let macro_name_range = range_for_text(&buffer, "ruby");
-    buffer.edit([(macro_name_range, "html")]);
-    syntax_map.interpolate(&buffer);
-    syntax_map.reparse(markdown.clone(), &buffer);
-    assert_layers_for_range(
-            &syntax_map,
-            &buffer,
-            Point::new(3, 0)..Point::new(3, 0),
-            &[
-                "...(fenced_code_block (fenced_code_block_delimiter) (info_string (language)) (code_fence_content) (fenced_code_block_delimiter..."
-            ],
-        );
-    assert!(syntax_map.contains_unknown_injections());
-
-    registry.add(Arc::new(html_lang()));
-    syntax_map.reparse(markdown.clone(), &buffer);
-    assert_layers_for_range(
-            &syntax_map,
-            &buffer,
-            Point::new(3, 0)..Point::new(3, 0),
-            &[
-                "...(fenced_code_block (fenced_code_block_delimiter) (info_string (language)) (code_fence_content) (fenced_code_block_delimiter...",
-                "(fragment (text))",
-            ],
-        );
-    assert!(!syntax_map.contains_unknown_injections());
-}
-
-#[gpui::test]
-fn test_typing_multiple_new_injections() {
-    let (buffer, syntax_map) = test_edit_sequence(
-        "Rust",
-        &[
-            "fn a() { dbg }",
-            "fn a() { dbg«!» }",
-            "fn a() { dbg!«()» }",
-            "fn a() { dbg!(«b») }",
-            "fn a() { dbg!(b«.») }",
-            "fn a() { dbg!(b.«c») }",
-            "fn a() { dbg!(b.c«()») }",
-            "fn a() { dbg!(b.c(«vec»)) }",
-            "fn a() { dbg!(b.c(vec«!»)) }",
-            "fn a() { dbg!(b.c(vec!«[]»)) }",
-            "fn a() { dbg!(b.c(vec![«d»])) }",
-            "fn a() { dbg!(b.c(vec![d«.»])) }",
-            "fn a() { dbg!(b.c(vec![d.«e»])) }",
-        ],
-    );
-
-    assert_capture_ranges(
-        &syntax_map,
-        &buffer,
-        &["field"],
-        "fn a() { dbg!(b.«c»(vec![d.«e»])) }",
-    );
-}
-
-#[gpui::test]
-fn test_pasting_new_injection_line_between_others() {
-    let (buffer, syntax_map) = test_edit_sequence(
-        "Rust",
-        &[
-            "
-                fn a() {
-                    b!(B {});
-                    c!(C {});
-                    d!(D {});
-                    e!(E {});
-                    f!(F {});
-                    g!(G {});
-                }
-            ",
-            "
-                fn a() {
-                    b!(B {});
-                    c!(C {});
-                    d!(D {});
-                «    h!(H {});
-                »    e!(E {});
-                    f!(F {});
-                    g!(G {});
-                }
-            ",
-        ],
-    );
-
-    assert_capture_ranges(
-        &syntax_map,
-        &buffer,
-        &["struct"],
-        "
-        fn a() {
-            b!(«B {}»);
-            c!(«C {}»);
-            d!(«D {}»);
-            h!(«H {}»);
-            e!(«E {}»);
-            f!(«F {}»);
-            g!(«G {}»);
-        }
-        ",
-    );
-}
-
-#[gpui::test]
-fn test_joining_injections_with_child_injections() {
-    let (buffer, syntax_map) = test_edit_sequence(
-        "Rust",
-        &[
-            "
-                fn a() {
-                    b!(
-                        c![one.two.three],
-                        d![four.five.six],
-                    );
-                    e!(
-                        f![seven.eight],
-                    );
-                }
-            ",
-            "
-                fn a() {
-                    b!(
-                        c![one.two.three],
-                        d![four.five.six],
-                    ˇ    f![seven.eight],
-                    );
-                }
-            ",
-        ],
-    );
-
-    assert_capture_ranges(
-        &syntax_map,
-        &buffer,
-        &["field"],
-        "
-        fn a() {
-            b!(
-                c![one.«two».«three»],
-                d![four.«five».«six»],
-                f![seven.«eight»],
-            );
-        }
-        ",
-    );
-}
-
-#[gpui::test]
-fn test_editing_edges_of_injection() {
-    test_edit_sequence(
-        "Rust",
-        &[
-            "
-                fn a() {
-                    b!(c!())
-                }
-            ",
-            "
-                fn a() {
-                    «d»!(c!())
-                }
-            ",
-            "
-                fn a() {
-                    «e»d!(c!())
-                }
-            ",
-            "
-                fn a() {
-                    ed!«[»c!()«]»
-                }
-            ",
-        ],
-    );
-}
-
-#[gpui::test]
-fn test_edits_preceding_and_intersecting_injection() {
-    test_edit_sequence(
-        "Rust",
-        &[
-            //
-            "const aaaaaaaaaaaa: B = c!(d(e.f));",
-            "const aˇa: B = c!(d(eˇ));",
-        ],
-    );
-}
-
-#[gpui::test]
-fn test_non_local_changes_create_injections() {
-    test_edit_sequence(
-        "Rust",
-        &[
-            "
-                // a! {
-                    static B: C = d;
-                // }
-            ",
-            "
-                ˇa! {
-                    static B: C = d;
-                ˇ}
-            ",
-        ],
-    );
-}
-
-#[gpui::test]
-fn test_creating_many_injections_in_one_edit() {
-    test_edit_sequence(
-        "Rust",
-        &[
-            "
-                fn a() {
-                    one(Two::three(3));
-                    four(Five::six(6));
-                    seven(Eight::nine(9));
-                }
-            ",
-            "
-                fn a() {
-                    one«!»(Two::three(3));
-                    four«!»(Five::six(6));
-                    seven«!»(Eight::nine(9));
-                }
-            ",
-            "
-                fn a() {
-                    one!(Two::three«!»(3));
-                    four!(Five::six«!»(6));
-                    seven!(Eight::nine«!»(9));
-                }
-            ",
-        ],
-    );
-}
-
-#[gpui::test]
-fn test_editing_across_injection_boundary() {
-    test_edit_sequence(
-        "Rust",
-        &[
-            "
-                fn one() {
-                    two();
-                    three!(
-                        three.four,
-                        five.six,
-                    );
-                }
-            ",
-            "
-                fn one() {
-                    two();
-                    th«irty_five![»
-                        three.four,
-                        five.six,
-                    «   seven.eight,
-                    ];»
-                }
-            ",
-        ],
-    );
-}
-
-#[gpui::test]
-fn test_removing_injection_by_replacing_across_boundary() {
-    test_edit_sequence(
-        "Rust",
-        &[
-            "
-                fn one() {
-                    two!(
-                        three.four,
-                    );
-                }
-            ",
-            "
-                fn one() {
-                    t«en
-                        .eleven(
-                        twelve,
-                    »
-                        three.four,
-                    );
-                }
-            ",
-        ],
-    );
-}
-
-#[gpui::test]
-fn test_combined_injections_simple() {
-    let (buffer, syntax_map) = test_edit_sequence(
-        "ERB",
-        &[
-            "
-                <body>
-                    <% if @one %>
-                        <div class=one>
-                    <% else %>
-                        <div class=two>
-                    <% end %>
-                    </div>
-                </body>
-            ",
-            "
-                <body>
-                    <% if @one %>
-                        <div class=one>
-                    ˇ else ˇ
-                        <div class=two>
-                    <% end %>
-                    </div>
-                </body>
-            ",
-            "
-                <body>
-                    <% if @one «;» end %>
-                    </div>
-                </body>
-            ",
-        ],
-    );
-
-    assert_capture_ranges(
-        &syntax_map,
-        &buffer,
-        &["tag", "ivar"],
-        "
-            <«body»>
-                <% if «@one» ; end %>
-                </«div»>
-            </«body»>
-        ",
-    );
-}
-
-#[gpui::test]
-fn test_combined_injections_empty_ranges() {
-    test_edit_sequence(
-        "ERB",
-        &[
-            "
-                <% if @one %>
-                <% else %>
-                <% end %>
-            ",
-            "
-                <% if @one %>
-                ˇ<% end %>
-            ",
-        ],
-    );
-}
-
-#[gpui::test]
-fn test_combined_injections_edit_edges_of_ranges() {
-    let (buffer, syntax_map) = test_edit_sequence(
-        "ERB",
-        &[
-            "
-                <%= one @two %>
-                <%= three @four %>
-            ",
-            "
-                <%= one @two %ˇ
-                <%= three @four %>
-            ",
-            "
-                <%= one @two %«>»
-                <%= three @four %>
-            ",
-        ],
-    );
-
-    assert_capture_ranges(
-        &syntax_map,
-        &buffer,
-        &["tag", "ivar"],
-        "
-            <%= one «@two» %>
-            <%= three «@four» %>
-        ",
-    );
-}
-
-#[gpui::test]
-fn test_combined_injections_splitting_some_injections() {
-    let (_buffer, _syntax_map) = test_edit_sequence(
-        "ERB",
-        &[
-            r#"
-                <%A if b(:c) %>
-                d
-                <% end %>
-                eee
-                <% f %>
-            "#,
-            r#"
-                <%« AAAAAAA %>
-                hhhhhhh
-                <%=» if b(:c) %>
-                d
-                <% end %>
-                eee
-                <% f %>
-            "#,
-        ],
-    );
-}
-
-#[gpui::test]
-fn test_combined_injections_editing_after_last_injection() {
-    test_edit_sequence(
-        "ERB",
-        &[
-            r#"
-                <% foo %>
-                <div></div>
-                <% bar %>
-            "#,
-            r#"
-                <% foo %>
-                <div></div>
-                <% bar %>«
-                more text»
-            "#,
-        ],
-    );
-}
-
-#[gpui::test]
-fn test_combined_injections_inside_injections() {
-    let (buffer, syntax_map) = test_edit_sequence(
-        "Markdown",
-        &[
-            r#"
-                here is
-                some
-                ERB code:
-
-                ```erb
-                <ul>
-                <% people.each do |person| %>
-                    <li><%= person.name %></li>
-                    <li><%= person.age %></li>
-                <% end %>
-                </ul>
-                ```
-            "#,
-            r#"
-                here is
-                some
-                ERB code:
-
-                ```erb
-                <ul>
-                <% people«2».each do |person| %>
-                    <li><%= person.name %></li>
-                    <li><%= person.age %></li>
-                <% end %>
-                </ul>
-                ```
-            "#,
-            // Inserting a comment character inside one code directive
-            // does not cause the other code directive to become a comment,
-            // because newlines are included in between each injection range.
-            r#"
-                here is
-                some
-                ERB code:
-
-                ```erb
-                <ul>
-                <% people2.each do |person| %>
-                    <li><%= «# »person.name %></li>
-                    <li><%= person.age %></li>
-                <% end %>
-                </ul>
-                ```
-            "#,
-        ],
-    );
-
-    // Check that the code directive below the ruby comment is
-    // not parsed as a comment.
-    assert_capture_ranges(
-        &syntax_map,
-        &buffer,
-        &["method"],
-        "
-            here is
-            some
-            ERB code:
-
-            ```erb
-            <ul>
-            <% people2.«each» do |person| %>
-                <li><%= # person.name %></li>
-                <li><%= person.«age» %></li>
-            <% end %>
-            </ul>
-            ```
-        ",
-    );
-}
-
-#[gpui::test]
-fn test_empty_combined_injections_inside_injections() {
-    let (buffer, syntax_map) = test_edit_sequence(
-        "Markdown",
-        &[r#"
-            ```erb
-            hello
-            ```
-
-            goodbye
-        "#],
-    );
-
-    assert_layers_for_range(
-        &syntax_map,
-        &buffer,
-        Point::new(0, 0)..Point::new(5, 0),
-        &[
-            "...(paragraph)...",
-            "(template...",
-            "(fragment...",
-            // The ruby syntax tree should be empty, since there are
-            // no interpolations in the ERB template.
-            "(program)",
-        ],
-    );
-}
-
-#[gpui::test(iterations = 50)]
-fn test_random_syntax_map_edits_rust_macros(rng: StdRng) {
-    let text = r#"
-        fn test_something() {
-            let vec = vec![5, 1, 3, 8];
-            assert_eq!(
-                vec
-                    .into_iter()
-                    .map(|i| i * 2)
-                    .collect::<Vec<usize>>(),
-                vec![
-                    5 * 2, 1 * 2, 3 * 2, 8 * 2
-                ],
-            );
-        }
-    "#
-    .unindent()
-    .repeat(2);
-
-    let registry = Arc::new(LanguageRegistry::test());
-    let language = Arc::new(rust_lang());
-    registry.add(language.clone());
-
-    test_random_edits(text, registry, language, rng);
-}
-
-#[gpui::test(iterations = 50)]
-fn test_random_syntax_map_edits_with_erb(rng: StdRng) {
-    let text = r#"
-        <div id="main">
-        <% if one?(:two) %>
-            <p class="three" four>
-            <%= yield :five %>
-            </p>
-        <% elsif Six.seven(8) %>
-            <p id="three" four>
-            <%= yield :five %>
-            </p>
-        <% else %>
-            <span>Ok</span>
-        <% end %>
-        </div>
-    "#
-    .unindent()
-    .repeat(5);
-
-    let registry = Arc::new(LanguageRegistry::test());
-    let language = Arc::new(erb_lang());
-    registry.add(language.clone());
-    registry.add(Arc::new(ruby_lang()));
-    registry.add(Arc::new(html_lang()));
-
-    test_random_edits(text, registry, language, rng);
-}
-
-#[gpui::test(iterations = 50)]
-fn test_random_syntax_map_edits_with_heex(rng: StdRng) {
-    let text = r#"
-        defmodule TheModule do
-            def the_method(assigns) do
-                ~H"""
-                <%= if @empty do %>
-                    <div class="h-4"></div>
-                <% else %>
-                    <div class="max-w-2xl w-full animate-pulse">
-                    <div class="flex-1 space-y-4">
-                        <div class={[@bg_class, "h-4 rounded-lg w-3/4"]}></div>
-                        <div class={[@bg_class, "h-4 rounded-lg"]}></div>
-                        <div class={[@bg_class, "h-4 rounded-lg w-5/6"]}></div>
-                    </div>
-                    </div>
-                <% end %>
-                """
-            end
-        end
-    "#
-    .unindent()
-    .repeat(3);
-
-    let registry = Arc::new(LanguageRegistry::test());
-    let language = Arc::new(elixir_lang());
-    registry.add(language.clone());
-    registry.add(Arc::new(heex_lang()));
-    registry.add(Arc::new(html_lang()));
-
-    test_random_edits(text, registry, language, rng);
-}
-
-fn test_random_edits(
-    text: String,
-    registry: Arc<LanguageRegistry>,
-    language: Arc<Language>,
-    mut rng: StdRng,
-) {
-    let operations = env::var("OPERATIONS")
-        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
-        .unwrap_or(10);
-
-    let mut buffer = Buffer::new(0, 0, text);
-
-    let mut syntax_map = SyntaxMap::new();
-    syntax_map.set_language_registry(registry.clone());
-    syntax_map.reparse(language.clone(), &buffer);
-
-    let mut reference_syntax_map = SyntaxMap::new();
-    reference_syntax_map.set_language_registry(registry.clone());
-
-    log::info!("initial text:\n{}", buffer.text());
-
-    for _ in 0..operations {
-        let prev_buffer = buffer.snapshot();
-        let prev_syntax_map = syntax_map.snapshot();
-
-        buffer.randomly_edit(&mut rng, 3);
-        log::info!("text:\n{}", buffer.text());
-
-        syntax_map.interpolate(&buffer);
-        check_interpolation(&prev_syntax_map, &syntax_map, &prev_buffer, &buffer);
-
-        syntax_map.reparse(language.clone(), &buffer);
-
-        reference_syntax_map.clear();
-        reference_syntax_map.reparse(language.clone(), &buffer);
-    }
-
-    for i in 0..operations {
-        let i = operations - i - 1;
-        buffer.undo();
-        log::info!("undoing operation {}", i);
-        log::info!("text:\n{}", buffer.text());
-
-        syntax_map.interpolate(&buffer);
-        syntax_map.reparse(language.clone(), &buffer);
-
-        reference_syntax_map.clear();
-        reference_syntax_map.reparse(language.clone(), &buffer);
-        assert_eq!(
-            syntax_map.layers(&buffer).len(),
-            reference_syntax_map.layers(&buffer).len(),
-            "wrong number of layers after undoing edit {i}"
-        );
-    }
-
-    let layers = syntax_map.layers(&buffer);
-    let reference_layers = reference_syntax_map.layers(&buffer);
-    for (edited_layer, reference_layer) in layers.into_iter().zip(reference_layers.into_iter()) {
-        assert_eq!(
-            edited_layer.node().to_sexp(),
-            reference_layer.node().to_sexp()
-        );
-        assert_eq!(edited_layer.node().range(), reference_layer.node().range());
-    }
-}
-
-fn check_interpolation(
-    old_syntax_map: &SyntaxSnapshot,
-    new_syntax_map: &SyntaxSnapshot,
-    old_buffer: &BufferSnapshot,
-    new_buffer: &BufferSnapshot,
-) {
-    let edits = new_buffer
-        .edits_since::<usize>(&old_buffer.version())
-        .collect::<Vec<_>>();
-
-    for (old_layer, new_layer) in old_syntax_map
-        .layers
-        .iter()
-        .zip(new_syntax_map.layers.iter())
-    {
-        assert_eq!(old_layer.range, new_layer.range);
-        let Some(old_tree) = old_layer.content.tree() else {
-            continue;
-        };
-        let Some(new_tree) = new_layer.content.tree() else {
-            continue;
-        };
-        let old_start_byte = old_layer.range.start.to_offset(old_buffer);
-        let new_start_byte = new_layer.range.start.to_offset(new_buffer);
-        let old_start_point = old_layer.range.start.to_point(old_buffer).to_ts_point();
-        let new_start_point = new_layer.range.start.to_point(new_buffer).to_ts_point();
-        let old_node = old_tree.root_node_with_offset(old_start_byte, old_start_point);
-        let new_node = new_tree.root_node_with_offset(new_start_byte, new_start_point);
-        check_node_edits(
-            old_layer.depth,
-            &old_layer.range,
-            old_node,
-            new_node,
-            old_buffer,
-            new_buffer,
-            &edits,
-        );
-    }
-
-    fn check_node_edits(
-        depth: usize,
-        range: &Range<Anchor>,
-        old_node: Node,
-        new_node: Node,
-        old_buffer: &BufferSnapshot,
-        new_buffer: &BufferSnapshot,
-        edits: &[text::Edit<usize>],
-    ) {
-        assert_eq!(old_node.kind(), new_node.kind());
-
-        let old_range = old_node.byte_range();
-        let new_range = new_node.byte_range();
-
-        let is_edited = edits
-            .iter()
-            .any(|edit| edit.new.start < new_range.end && edit.new.end > new_range.start);
-        if is_edited {
-            assert!(
-                new_node.has_changes(),
-                concat!(
-                    "failed to mark node as edited.\n",
-                    "layer depth: {}, old layer range: {:?}, new layer range: {:?},\n",
-                    "node kind: {}, old node range: {:?}, new node range: {:?}",
-                ),
-                depth,
-                range.to_offset(old_buffer),
-                range.to_offset(new_buffer),
-                new_node.kind(),
-                old_range,
-                new_range,
-            );
-        }
-
-        if !new_node.has_changes() {
-            assert_eq!(
-                old_buffer
-                    .text_for_range(old_range.clone())
-                    .collect::<String>(),
-                new_buffer
-                    .text_for_range(new_range.clone())
-                    .collect::<String>(),
-                concat!(
-                    "mismatched text for node\n",
-                    "layer depth: {}, old layer range: {:?}, new layer range: {:?},\n",
-                    "node kind: {}, old node range:{:?}, new node range:{:?}",
-                ),
-                depth,
-                range.to_offset(old_buffer),
-                range.to_offset(new_buffer),
-                new_node.kind(),
-                old_range,
-                new_range,
-            );
-        }
-
-        for i in 0..new_node.child_count() {
-            check_node_edits(
-                depth,
-                range,
-                old_node.child(i).unwrap(),
-                new_node.child(i).unwrap(),
-                old_buffer,
-                new_buffer,
-                edits,
-            )
-        }
-    }
-}
-
-fn test_edit_sequence(language_name: &str, steps: &[&str]) -> (Buffer, SyntaxMap) {
-    let registry = Arc::new(LanguageRegistry::test());
-    registry.add(Arc::new(elixir_lang()));
-    registry.add(Arc::new(heex_lang()));
-    registry.add(Arc::new(rust_lang()));
-    registry.add(Arc::new(ruby_lang()));
-    registry.add(Arc::new(html_lang()));
-    registry.add(Arc::new(erb_lang()));
-    registry.add(Arc::new(markdown_lang()));
-
-    let language = registry
-        .language_for_name(language_name)
-        .now_or_never()
-        .unwrap()
-        .unwrap();
-    let mut buffer = Buffer::new(0, 0, Default::default());
-
-    let mut mutated_syntax_map = SyntaxMap::new();
-    mutated_syntax_map.set_language_registry(registry.clone());
-    mutated_syntax_map.reparse(language.clone(), &buffer);
-
-    for (i, marked_string) in steps.into_iter().enumerate() {
-        let marked_string = marked_string.unindent();
-        log::info!("incremental parse {i}: {marked_string:?}");
-        buffer.edit_via_marked_text(&marked_string);
-
-        // Reparse the syntax map
-        mutated_syntax_map.interpolate(&buffer);
-        mutated_syntax_map.reparse(language.clone(), &buffer);
-
-        // Create a second syntax map from scratch
-        log::info!("fresh parse {i}: {marked_string:?}");
-        let mut reference_syntax_map = SyntaxMap::new();
-        reference_syntax_map.set_language_registry(registry.clone());
-        reference_syntax_map.reparse(language.clone(), &buffer);
-
-        // Compare the mutated syntax map to the new syntax map
-        let mutated_layers = mutated_syntax_map.layers(&buffer);
-        let reference_layers = reference_syntax_map.layers(&buffer);
-        assert_eq!(
-            mutated_layers.len(),
-            reference_layers.len(),
-            "wrong number of layers at step {i}"
-        );
-        for (edited_layer, reference_layer) in
-            mutated_layers.into_iter().zip(reference_layers.into_iter())
-        {
-            assert_eq!(
-                edited_layer.node().to_sexp(),
-                reference_layer.node().to_sexp(),
-                "different layer at step {i}"
-            );
-            assert_eq!(
-                edited_layer.node().range(),
-                reference_layer.node().range(),
-                "different layer at step {i}"
-            );
-        }
-    }
-
-    (buffer, mutated_syntax_map)
-}
-
-fn html_lang() -> Language {
-    Language::new(
-        LanguageConfig {
-            name: "HTML".into(),
-            path_suffixes: vec!["html".to_string()],
-            ..Default::default()
-        },
-        Some(tree_sitter_html::language()),
-    )
-    .with_highlights_query(
-        r#"
-            (tag_name) @tag
-            (erroneous_end_tag_name) @tag
-            (attribute_name) @property
-        "#,
-    )
-    .unwrap()
-}
-
-fn ruby_lang() -> Language {
-    Language::new(
-        LanguageConfig {
-            name: "Ruby".into(),
-            path_suffixes: vec!["rb".to_string()],
-            ..Default::default()
-        },
-        Some(tree_sitter_ruby::language()),
-    )
-    .with_highlights_query(
-        r#"
-            ["if" "do" "else" "end"] @keyword
-            (instance_variable) @ivar
-            (call method: (identifier) @method)
-        "#,
-    )
-    .unwrap()
-}
-
-fn erb_lang() -> Language {
-    Language::new(
-        LanguageConfig {
-            name: "ERB".into(),
-            path_suffixes: vec!["erb".to_string()],
-            ..Default::default()
-        },
-        Some(tree_sitter_embedded_template::language()),
-    )
-    .with_highlights_query(
-        r#"
-            ["<%" "%>"] @keyword
-        "#,
-    )
-    .unwrap()
-    .with_injection_query(
-        r#"
-            (
-                (code) @content
-                (#set! "language" "ruby")
-                (#set! "combined")
-            )
-
-            (
-                (content) @content
-                (#set! "language" "html")
-                (#set! "combined")
-            )
-        "#,
-    )
-    .unwrap()
-}
-
-fn rust_lang() -> Language {
-    Language::new(
-        LanguageConfig {
-            name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
-            ..Default::default()
-        },
-        Some(tree_sitter_rust::language()),
-    )
-    .with_highlights_query(
-        r#"
-            (field_identifier) @field
-            (struct_expression) @struct
-        "#,
-    )
-    .unwrap()
-    .with_injection_query(
-        r#"
-            (macro_invocation
-                (token_tree) @content
-                (#set! "language" "rust"))
-        "#,
-    )
-    .unwrap()
-}
-
-fn markdown_lang() -> Language {
-    Language::new(
-        LanguageConfig {
-            name: "Markdown".into(),
-            path_suffixes: vec!["md".into()],
-            ..Default::default()
-        },
-        Some(tree_sitter_markdown::language()),
-    )
-    .with_injection_query(
-        r#"
-            (fenced_code_block
-                (info_string
-                    (language) @language)
-                (code_fence_content) @content)
-        "#,
-    )
-    .unwrap()
-}
-
-fn elixir_lang() -> Language {
-    Language::new(
-        LanguageConfig {
-            name: "Elixir".into(),
-            path_suffixes: vec!["ex".into()],
-            ..Default::default()
-        },
-        Some(tree_sitter_elixir::language()),
-    )
-    .with_highlights_query(
-        r#"
-
-        "#,
-    )
-    .unwrap()
-}
-
-fn heex_lang() -> Language {
-    Language::new(
-        LanguageConfig {
-            name: "HEEx".into(),
-            path_suffixes: vec!["heex".into()],
-            ..Default::default()
-        },
-        Some(tree_sitter_heex::language()),
-    )
-    .with_injection_query(
-        r#"
-        (
-          (directive
-            [
-              (partial_expression_value)
-              (expression_value)
-              (ending_expression_value)
-            ] @content)
-          (#set! language "elixir")
-          (#set! combined)
-        )
-
-        ((expression (expression_value) @content)
-         (#set! language "elixir"))
-        "#,
-    )
-    .unwrap()
-}
-
-fn range_for_text(buffer: &Buffer, text: &str) -> Range<usize> {
-    let start = buffer.as_rope().to_string().find(text).unwrap();
-    start..start + text.len()
-}
-
-#[track_caller]
-fn assert_layers_for_range(
-    syntax_map: &SyntaxMap,
-    buffer: &BufferSnapshot,
-    range: Range<Point>,
-    expected_layers: &[&str],
-) {
-    let layers = syntax_map
-        .layers_for_range(range, &buffer)
-        .collect::<Vec<_>>();
-    assert_eq!(
-        layers.len(),
-        expected_layers.len(),
-        "wrong number of layers"
-    );
-    for (i, (layer, expected_s_exp)) in layers.iter().zip(expected_layers.iter()).enumerate() {
-        let actual_s_exp = layer.node().to_sexp();
-        assert!(
-            string_contains_sequence(
-                &actual_s_exp,
-                &expected_s_exp.split("...").collect::<Vec<_>>()
-            ),
-            "layer {i}:\n\nexpected: {expected_s_exp}\nactual:   {actual_s_exp}",
-        );
-    }
-}
-
-fn assert_capture_ranges(
-    syntax_map: &SyntaxMap,
-    buffer: &BufferSnapshot,
-    highlight_query_capture_names: &[&str],
-    marked_string: &str,
-) {
-    let mut actual_ranges = Vec::<Range<usize>>::new();
-    let captures = syntax_map.captures(0..buffer.len(), buffer, |grammar| {
-        grammar.highlights_query.as_ref()
-    });
-    let queries = captures
-        .grammars()
-        .iter()
-        .map(|grammar| grammar.highlights_query.as_ref().unwrap())
-        .collect::<Vec<_>>();
-    for capture in captures {
-        let name = &queries[capture.grammar_index].capture_names()[capture.index as usize];
-        if highlight_query_capture_names.contains(&name) {
-            actual_ranges.push(capture.node.byte_range());
-        }
-    }
-
-    let (text, expected_ranges) = marked_text_ranges(&marked_string.unindent(), false);
-    assert_eq!(text, buffer.text());
-    assert_eq!(actual_ranges, expected_ranges);
-}
-
-pub fn string_contains_sequence(text: &str, parts: &[&str]) -> bool {
-    let mut last_part_end = 0;
-    for part in parts {
-        if let Some(start_ix) = text[last_part_end..].find(part) {
-            last_part_end = start_ix + part.len();
-        } else {
-            return false;
-        }
-    }
-    true
-}

crates/language_selector/Cargo.toml 🔗

@@ -10,12 +10,13 @@ doctest = false
 
 [dependencies]
 editor = { path = "../editor" }
-fuzzy = { path = "../fuzzy" }
+fuzzy = {  path = "../fuzzy" }
 language = { path = "../language" }
 gpui = { path = "../gpui" }
 picker = { path = "../picker" }
 project = { path = "../project" }
 theme = { path = "../theme" }
+ui = { path = "../ui" }
 settings = { path = "../settings" }
 util = { path = "../util" }
 workspace = { path = "../workspace" }

crates/language_selector/src/active_buffer_language.rs 🔗

@@ -1,15 +1,14 @@
 use editor::Editor;
-use gpui::{
-    elements::*,
-    platform::{CursorStyle, MouseButton},
-    Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
-};
+use gpui::{div, IntoElement, ParentElement, Render, Subscription, View, ViewContext, WeakView};
 use std::sync::Arc;
+use ui::{Button, ButtonCommon, Clickable, LabelSize, Tooltip};
 use workspace::{item::ItemHandle, StatusItemView, Workspace};
 
+use crate::LanguageSelector;
+
 pub struct ActiveBufferLanguage {
     active_language: Option<Option<Arc<str>>>,
-    workspace: WeakViewHandle<Workspace>,
+    workspace: WeakView<Workspace>,
     _observe_active_editor: Option<Subscription>,
 }
 
@@ -22,7 +21,7 @@ impl ActiveBufferLanguage {
         }
     }
 
-    fn update_language(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
+    fn update_language(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
         self.active_language = Some(None);
 
         let editor = editor.read(cx);
@@ -36,44 +35,28 @@ impl ActiveBufferLanguage {
     }
 }
 
-impl Entity for ActiveBufferLanguage {
-    type Event = ();
-}
-
-impl View for ActiveBufferLanguage {
-    fn ui_name() -> &'static str {
-        "ActiveBufferLanguage"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        if let Some(active_language) = self.active_language.as_ref() {
+impl Render for ActiveBufferLanguage {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        div().when_some(self.active_language.as_ref(), |el, active_language| {
             let active_language_text = if let Some(active_language_text) = active_language {
                 active_language_text.to_string()
             } else {
                 "Unknown".to_string()
             };
-            let theme = theme::current(cx).clone();
 
-            MouseEventHandler::new::<Self, _>(0, cx, |state, cx| {
-                let theme = &theme::current(cx).workspace.status_bar;
-                let style = theme.active_language.style_for(state);
-                Label::new(active_language_text, style.text.clone())
-                    .contained()
-                    .with_style(style.container)
-            })
-            .with_cursor_style(CursorStyle::PointingHand)
-            .on_click(MouseButton::Left, |_, this, cx| {
-                if let Some(workspace) = this.workspace.upgrade(cx) {
-                    workspace.update(cx, |workspace, cx| {
-                        crate::toggle(workspace, &Default::default(), cx)
-                    });
-                }
-            })
-            .with_tooltip::<Self>(0, "Select Language", None, theme.tooltip.clone(), cx)
-            .into_any()
-        } else {
-            Empty::new().into_any()
-        }
+            el.child(
+                Button::new("change-language", active_language_text)
+                    .label_size(LabelSize::Small)
+                    .on_click(cx.listener(|this, _, cx| {
+                        if let Some(workspace) = this.workspace.upgrade() {
+                            workspace.update(cx, |workspace, cx| {
+                                LanguageSelector::toggle(workspace, cx)
+                            });
+                        }
+                    }))
+                    .tooltip(|cx| Tooltip::text("Select Language", cx)),
+            )
+        })
     }
 }
 

crates/language_selector/src/language_selector.rs 🔗

@@ -4,46 +4,87 @@ pub use active_buffer_language::ActiveBufferLanguage;
 use anyhow::anyhow;
 use editor::Editor;
 use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
-use gpui::{actions, elements::*, AppContext, ModelHandle, MouseState, ViewContext};
+use gpui::{
+    actions, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model,
+    ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
+};
 use language::{Buffer, LanguageRegistry};
-use picker::{Picker, PickerDelegate, PickerEvent};
+use picker::{Picker, PickerDelegate};
 use project::Project;
 use std::sync::Arc;
+use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing};
 use util::ResultExt;
-use workspace::Workspace;
+use workspace::{ModalView, Workspace};
 
 actions!(language_selector, [Toggle]);
 
 pub fn init(cx: &mut AppContext) {
-    Picker::<LanguageSelectorDelegate>::init(cx);
-    cx.add_action(toggle);
+    cx.observe_new_views(LanguageSelector::register).detach();
 }
 
-pub fn toggle(
-    workspace: &mut Workspace,
-    _: &Toggle,
-    cx: &mut ViewContext<Workspace>,
-) -> Option<()> {
-    let (_, buffer, _) = workspace
-        .active_item(cx)?
-        .act_as::<Editor>(cx)?
-        .read(cx)
-        .active_excerpt(cx)?;
-    workspace.toggle_modal(cx, |workspace, cx| {
+pub struct LanguageSelector {
+    picker: View<Picker<LanguageSelectorDelegate>>,
+}
+
+impl LanguageSelector {
+    fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+        workspace.register_action(move |workspace, _: &Toggle, cx| {
+            Self::toggle(workspace, cx);
+        });
+    }
+
+    fn toggle(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> Option<()> {
         let registry = workspace.app_state().languages.clone();
-        cx.add_view(|cx| {
-            Picker::new(
-                LanguageSelectorDelegate::new(buffer, workspace.project().clone(), registry),
-                cx,
-            )
-        })
-    });
-    Some(())
+        let (_, buffer, _) = workspace
+            .active_item(cx)?
+            .act_as::<Editor>(cx)?
+            .read(cx)
+            .active_excerpt(cx)?;
+        let project = workspace.project().clone();
+
+        workspace.toggle_modal(cx, move |cx| {
+            LanguageSelector::new(buffer, project, registry, cx)
+        });
+        Some(())
+    }
+
+    fn new(
+        buffer: Model<Buffer>,
+        project: Model<Project>,
+        language_registry: Arc<LanguageRegistry>,
+        cx: &mut ViewContext<Self>,
+    ) -> Self {
+        let delegate = LanguageSelectorDelegate::new(
+            cx.view().downgrade(),
+            buffer,
+            project,
+            language_registry,
+        );
+
+        let picker = cx.new_view(|cx| Picker::new(delegate, cx));
+        Self { picker }
+    }
 }
 
+impl Render for LanguageSelector {
+    fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
+        v_stack().w(rems(34.)).child(self.picker.clone())
+    }
+}
+
+impl FocusableView for LanguageSelector {
+    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
+        self.picker.focus_handle(cx)
+    }
+}
+
+impl EventEmitter<DismissEvent> for LanguageSelector {}
+impl ModalView for LanguageSelector {}
+
 pub struct LanguageSelectorDelegate {
-    buffer: ModelHandle<Buffer>,
-    project: ModelHandle<Project>,
+    language_selector: WeakView<LanguageSelector>,
+    buffer: Model<Buffer>,
+    project: Model<Project>,
     language_registry: Arc<LanguageRegistry>,
     candidates: Vec<StringMatchCandidate>,
     matches: Vec<StringMatch>,
@@ -52,8 +93,9 @@ pub struct LanguageSelectorDelegate {
 
 impl LanguageSelectorDelegate {
     fn new(
-        buffer: ModelHandle<Buffer>,
-        project: ModelHandle<Project>,
+        language_selector: WeakView<LanguageSelector>,
+        buffer: Model<Buffer>,
+        project: Model<Project>,
         language_registry: Arc<LanguageRegistry>,
     ) -> Self {
         let candidates = language_registry
@@ -62,29 +104,22 @@ impl LanguageSelectorDelegate {
             .enumerate()
             .map(|(candidate_id, name)| StringMatchCandidate::new(candidate_id, name))
             .collect::<Vec<_>>();
-        let mut matches = candidates
-            .iter()
-            .map(|candidate| StringMatch {
-                candidate_id: candidate.id,
-                score: 0.,
-                positions: Default::default(),
-                string: candidate.string.clone(),
-            })
-            .collect::<Vec<_>>();
-        matches.sort_unstable_by(|mat1, mat2| mat1.string.cmp(&mat2.string));
 
         Self {
+            language_selector,
             buffer,
             project,
             language_registry,
             candidates,
-            matches,
+            matches: vec![],
             selected_index: 0,
         }
     }
 }
 
 impl PickerDelegate for LanguageSelectorDelegate {
+    type ListItem = ListItem;
+
     fn placeholder_text(&self) -> Arc<str> {
         "Select a language...".into()
     }
@@ -102,23 +137,25 @@ impl PickerDelegate for LanguageSelectorDelegate {
             cx.spawn(|_, mut cx| async move {
                 let language = language.await?;
                 let project = project
-                    .upgrade(&cx)
+                    .upgrade()
                     .ok_or_else(|| anyhow!("project was dropped"))?;
                 let buffer = buffer
-                    .upgrade(&cx)
+                    .upgrade()
                     .ok_or_else(|| anyhow!("buffer was dropped"))?;
                 project.update(&mut cx, |project, cx| {
                     project.set_language_for_buffer(&buffer, language, cx);
-                });
-                anyhow::Ok(())
+                })
             })
             .detach_and_log_err(cx);
         }
-
-        cx.emit(PickerEvent::Dismiss);
+        self.dismissed(cx);
     }
 
-    fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
+    fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
+        self.language_selector
+            .update(cx, |_, cx| cx.emit(DismissEvent))
+            .log_err();
+    }
 
     fn selected_index(&self) -> usize {
         self.selected_index
@@ -133,7 +170,7 @@ impl PickerDelegate for LanguageSelectorDelegate {
         query: String,
         cx: &mut ViewContext<Picker<Self>>,
     ) -> gpui::Task<()> {
-        let background = cx.background().clone();
+        let background = cx.background_executor().clone();
         let candidates = self.candidates.clone();
         cx.spawn(|this, mut cx| async move {
             let matches = if query.is_empty() {
@@ -160,7 +197,7 @@ impl PickerDelegate for LanguageSelectorDelegate {
             };
 
             this.update(&mut cx, |this, cx| {
-                let delegate = this.delegate_mut();
+                let delegate = &mut this.delegate;
                 delegate.matches = matches;
                 delegate.selected_index = delegate
                     .selected_index
@@ -174,23 +211,22 @@ impl PickerDelegate for LanguageSelectorDelegate {
     fn render_match(
         &self,
         ix: usize,
-        mouse_state: &mut MouseState,
         selected: bool,
-        cx: &AppContext,
-    ) -> AnyElement<Picker<Self>> {
-        let theme = theme::current(cx);
+        cx: &mut ViewContext<Picker<Self>>,
+    ) -> Option<Self::ListItem> {
         let mat = &self.matches[ix];
-        let style = theme.picker.item.in_state(selected).style_for(mouse_state);
         let buffer_language_name = self.buffer.read(cx).language().map(|l| l.name());
         let mut label = mat.string.clone();
         if buffer_language_name.as_deref() == Some(mat.string.as_str()) {
             label.push_str(" (current)");
         }
 
-        Label::new(label, style.label.clone())
-            .with_highlights(mat.positions.clone())
-            .contained()
-            .with_style(style.container)
-            .into_any()
+        Some(
+            ListItem::new(ix)
+                .inset(true)
+                .spacing(ListItemSpacing::Sparse)
+                .selected(selected)
+                .child(HighlightedLabel::new(label, mat.positions.clone())),
+        )
     }
 }

crates/language_selector2/Cargo.toml 🔗

@@ -1,26 +0,0 @@
-[package]
-name = "language_selector2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/language_selector.rs"
-doctest = false
-
-[dependencies]
-editor = { package = "editor2", path = "../editor2" }
-fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
-language = { package = "language2", path = "../language2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-picker = { package = "picker2", path = "../picker2" }
-project = { package = "project2", path = "../project2" }
-theme = { package = "theme2", path = "../theme2" }
-ui = { package = "ui2", path = "../ui2" }
-settings = { package = "settings2", path = "../settings2" }
-util = { path = "../util" }
-workspace = { package = "workspace2", path = "../workspace2" }
-anyhow.workspace = true
-
-[dev-dependencies]
-editor = { package = "editor2", path = "../editor2", features = ["test-support"] }

crates/language_selector2/src/active_buffer_language.rs 🔗

@@ -1,79 +0,0 @@
-use editor::Editor;
-use gpui::{div, IntoElement, ParentElement, Render, Subscription, View, ViewContext, WeakView};
-use std::sync::Arc;
-use ui::{Button, ButtonCommon, Clickable, LabelSize, Tooltip};
-use workspace::{item::ItemHandle, StatusItemView, Workspace};
-
-use crate::LanguageSelector;
-
-pub struct ActiveBufferLanguage {
-    active_language: Option<Option<Arc<str>>>,
-    workspace: WeakView<Workspace>,
-    _observe_active_editor: Option<Subscription>,
-}
-
-impl ActiveBufferLanguage {
-    pub fn new(workspace: &Workspace) -> Self {
-        Self {
-            active_language: None,
-            workspace: workspace.weak_handle(),
-            _observe_active_editor: None,
-        }
-    }
-
-    fn update_language(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
-        self.active_language = Some(None);
-
-        let editor = editor.read(cx);
-        if let Some((_, buffer, _)) = editor.active_excerpt(cx) {
-            if let Some(language) = buffer.read(cx).language() {
-                self.active_language = Some(Some(language.name()));
-            }
-        }
-
-        cx.notify();
-    }
-}
-
-impl Render for ActiveBufferLanguage {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        div().when_some(self.active_language.as_ref(), |el, active_language| {
-            let active_language_text = if let Some(active_language_text) = active_language {
-                active_language_text.to_string()
-            } else {
-                "Unknown".to_string()
-            };
-
-            el.child(
-                Button::new("change-language", active_language_text)
-                    .label_size(LabelSize::Small)
-                    .on_click(cx.listener(|this, _, cx| {
-                        if let Some(workspace) = this.workspace.upgrade() {
-                            workspace.update(cx, |workspace, cx| {
-                                LanguageSelector::toggle(workspace, cx)
-                            });
-                        }
-                    }))
-                    .tooltip(|cx| Tooltip::text("Select Language", cx)),
-            )
-        })
-    }
-}
-
-impl StatusItemView for ActiveBufferLanguage {
-    fn set_active_pane_item(
-        &mut self,
-        active_pane_item: Option<&dyn ItemHandle>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        if let Some(editor) = active_pane_item.and_then(|item| item.act_as::<Editor>(cx)) {
-            self._observe_active_editor = Some(cx.observe(&editor, Self::update_language));
-            self.update_language(editor, cx);
-        } else {
-            self.active_language = None;
-            self._observe_active_editor = None;
-        }
-
-        cx.notify();
-    }
-}

crates/language_selector2/src/language_selector.rs 🔗

@@ -1,232 +0,0 @@
-mod active_buffer_language;
-
-pub use active_buffer_language::ActiveBufferLanguage;
-use anyhow::anyhow;
-use editor::Editor;
-use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
-use gpui::{
-    actions, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model,
-    ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
-};
-use language::{Buffer, LanguageRegistry};
-use picker::{Picker, PickerDelegate};
-use project::Project;
-use std::sync::Arc;
-use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing};
-use util::ResultExt;
-use workspace::{ModalView, Workspace};
-
-actions!(language_selector, [Toggle]);
-
-pub fn init(cx: &mut AppContext) {
-    cx.observe_new_views(LanguageSelector::register).detach();
-}
-
-pub struct LanguageSelector {
-    picker: View<Picker<LanguageSelectorDelegate>>,
-}
-
-impl LanguageSelector {
-    fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
-        workspace.register_action(move |workspace, _: &Toggle, cx| {
-            Self::toggle(workspace, cx);
-        });
-    }
-
-    fn toggle(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> Option<()> {
-        let registry = workspace.app_state().languages.clone();
-        let (_, buffer, _) = workspace
-            .active_item(cx)?
-            .act_as::<Editor>(cx)?
-            .read(cx)
-            .active_excerpt(cx)?;
-        let project = workspace.project().clone();
-
-        workspace.toggle_modal(cx, move |cx| {
-            LanguageSelector::new(buffer, project, registry, cx)
-        });
-        Some(())
-    }
-
-    fn new(
-        buffer: Model<Buffer>,
-        project: Model<Project>,
-        language_registry: Arc<LanguageRegistry>,
-        cx: &mut ViewContext<Self>,
-    ) -> Self {
-        let delegate = LanguageSelectorDelegate::new(
-            cx.view().downgrade(),
-            buffer,
-            project,
-            language_registry,
-        );
-
-        let picker = cx.new_view(|cx| Picker::new(delegate, cx));
-        Self { picker }
-    }
-}
-
-impl Render for LanguageSelector {
-    fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
-        v_stack().w(rems(34.)).child(self.picker.clone())
-    }
-}
-
-impl FocusableView for LanguageSelector {
-    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
-        self.picker.focus_handle(cx)
-    }
-}
-
-impl EventEmitter<DismissEvent> for LanguageSelector {}
-impl ModalView for LanguageSelector {}
-
-pub struct LanguageSelectorDelegate {
-    language_selector: WeakView<LanguageSelector>,
-    buffer: Model<Buffer>,
-    project: Model<Project>,
-    language_registry: Arc<LanguageRegistry>,
-    candidates: Vec<StringMatchCandidate>,
-    matches: Vec<StringMatch>,
-    selected_index: usize,
-}
-
-impl LanguageSelectorDelegate {
-    fn new(
-        language_selector: WeakView<LanguageSelector>,
-        buffer: Model<Buffer>,
-        project: Model<Project>,
-        language_registry: Arc<LanguageRegistry>,
-    ) -> Self {
-        let candidates = language_registry
-            .language_names()
-            .into_iter()
-            .enumerate()
-            .map(|(candidate_id, name)| StringMatchCandidate::new(candidate_id, name))
-            .collect::<Vec<_>>();
-
-        Self {
-            language_selector,
-            buffer,
-            project,
-            language_registry,
-            candidates,
-            matches: vec![],
-            selected_index: 0,
-        }
-    }
-}
-
-impl PickerDelegate for LanguageSelectorDelegate {
-    type ListItem = ListItem;
-
-    fn placeholder_text(&self) -> Arc<str> {
-        "Select a language...".into()
-    }
-
-    fn match_count(&self) -> usize {
-        self.matches.len()
-    }
-
-    fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
-        if let Some(mat) = self.matches.get(self.selected_index) {
-            let language_name = &self.candidates[mat.candidate_id].string;
-            let language = self.language_registry.language_for_name(language_name);
-            let project = self.project.downgrade();
-            let buffer = self.buffer.downgrade();
-            cx.spawn(|_, mut cx| async move {
-                let language = language.await?;
-                let project = project
-                    .upgrade()
-                    .ok_or_else(|| anyhow!("project was dropped"))?;
-                let buffer = buffer
-                    .upgrade()
-                    .ok_or_else(|| anyhow!("buffer was dropped"))?;
-                project.update(&mut cx, |project, cx| {
-                    project.set_language_for_buffer(&buffer, language, cx);
-                })
-            })
-            .detach_and_log_err(cx);
-        }
-        self.dismissed(cx);
-    }
-
-    fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
-        self.language_selector
-            .update(cx, |_, cx| cx.emit(DismissEvent))
-            .log_err();
-    }
-
-    fn selected_index(&self) -> usize {
-        self.selected_index
-    }
-
-    fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
-        self.selected_index = ix;
-    }
-
-    fn update_matches(
-        &mut self,
-        query: String,
-        cx: &mut ViewContext<Picker<Self>>,
-    ) -> gpui::Task<()> {
-        let background = cx.background_executor().clone();
-        let candidates = self.candidates.clone();
-        cx.spawn(|this, mut cx| async move {
-            let matches = if query.is_empty() {
-                candidates
-                    .into_iter()
-                    .enumerate()
-                    .map(|(index, candidate)| StringMatch {
-                        candidate_id: index,
-                        string: candidate.string,
-                        positions: Vec::new(),
-                        score: 0.0,
-                    })
-                    .collect()
-            } else {
-                match_strings(
-                    &candidates,
-                    &query,
-                    false,
-                    100,
-                    &Default::default(),
-                    background,
-                )
-                .await
-            };
-
-            this.update(&mut cx, |this, cx| {
-                let delegate = &mut this.delegate;
-                delegate.matches = matches;
-                delegate.selected_index = delegate
-                    .selected_index
-                    .min(delegate.matches.len().saturating_sub(1));
-                cx.notify();
-            })
-            .log_err();
-        })
-    }
-
-    fn render_match(
-        &self,
-        ix: usize,
-        selected: bool,
-        cx: &mut ViewContext<Picker<Self>>,
-    ) -> Option<Self::ListItem> {
-        let mat = &self.matches[ix];
-        let buffer_language_name = self.buffer.read(cx).language().map(|l| l.name());
-        let mut label = mat.string.clone();
-        if buffer_language_name.as_deref() == Some(mat.string.as_str()) {
-            label.push_str(" (current)");
-        }
-
-        Some(
-            ListItem::new(ix)
-                .inset(true)
-                .spacing(ListItemSpacing::Sparse)
-                .selected(selected)
-                .child(HighlightedLabel::new(label, mat.positions.clone())),
-        )
-    }
-}

crates/language_tools/Cargo.toml 🔗

@@ -17,6 +17,7 @@ language = { path = "../language" }
 project = { path = "../project" }
 workspace = { path = "../workspace" }
 gpui = { path = "../gpui" }
+ui = { path = "../ui" }
 util = { path = "../util" }
 lsp = { path = "../lsp" }
 futures.workspace = true

crates/language_tools/src/lsp_log.rs 🔗

@@ -1,25 +1,20 @@
 use collections::{HashMap, VecDeque};
-use editor::{Editor, MoveToEnd};
+use editor::{Editor, EditorEvent, MoveToEnd};
 use futures::{channel::mpsc, StreamExt};
 use gpui::{
-    actions,
-    elements::{
-        AnchorCorner, ChildView, Empty, Flex, Label, MouseEventHandler, Overlay, OverlayFitMode,
-        ParentElement, Stack,
-    },
-    platform::{CursorStyle, MouseButton},
-    AnyElement, AppContext, Element, Entity, ModelContext, ModelHandle, Subscription, View,
-    ViewContext, ViewHandle, WeakModelHandle,
+    actions, div, AnchorCorner, AnyElement, AppContext, Context, EventEmitter, FocusHandle,
+    FocusableView, IntoElement, Model, ModelContext, ParentElement, Render, Styled, Subscription,
+    View, ViewContext, VisualContext, WeakModel, WindowContext,
 };
 use language::{LanguageServerId, LanguageServerName};
 use lsp::IoKind;
 use project::{search::SearchQuery, Project};
 use std::{borrow::Cow, sync::Arc};
-use theme::{ui, Theme};
+use ui::{h_stack, popover_menu, Button, Checkbox, Clickable, ContextMenu, Label, Selection};
 use workspace::{
     item::{Item, ItemHandle},
-    searchable::{SearchableItem, SearchableItemHandle},
-    ToolbarItemLocation, ToolbarItemView, Workspace, WorkspaceCreated,
+    searchable::{SearchEvent, SearchableItem, SearchableItemHandle},
+    ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
 };
 
 const SEND_LINE: &str = "// Send:";
@@ -27,8 +22,8 @@ const RECEIVE_LINE: &str = "// Receive:";
 const MAX_STORED_LOG_ENTRIES: usize = 2000;
 
 pub struct LogStore {
-    projects: HashMap<WeakModelHandle<Project>, ProjectState>,
-    io_tx: mpsc::UnboundedSender<(WeakModelHandle<Project>, LanguageServerId, IoKind, String)>,
+    projects: HashMap<WeakModel<Project>, ProjectState>,
+    io_tx: mpsc::UnboundedSender<(WeakModel<Project>, LanguageServerId, IoKind, String)>,
 }
 
 struct ProjectState {
@@ -49,19 +44,19 @@ struct LanguageServerRpcState {
 }
 
 pub struct LspLogView {
-    pub(crate) editor: ViewHandle<Editor>,
+    pub(crate) editor: View<Editor>,
     editor_subscription: Subscription,
-    log_store: ModelHandle<LogStore>,
+    log_store: Model<LogStore>,
     current_server_id: Option<LanguageServerId>,
     is_showing_rpc_trace: bool,
-    project: ModelHandle<Project>,
+    project: Model<Project>,
+    focus_handle: FocusHandle,
     _log_store_subscriptions: Vec<Subscription>,
 }
 
 pub struct LspLogToolbarItemView {
-    log_view: Option<ViewHandle<LspLogView>>,
+    log_view: Option<View<LspLogView>>,
     _log_view_subscription: Option<Subscription>,
-    menu_open: bool,
 }
 
 #[derive(Copy, Clone, PartialEq, Eq)]
@@ -83,37 +78,30 @@ pub(crate) struct LogMenuItem {
 actions!(debug, [OpenLanguageServerLogs]);
 
 pub fn init(cx: &mut AppContext) {
-    let log_store = cx.add_model(|cx| LogStore::new(cx));
+    let log_store = cx.new_model(|cx| LogStore::new(cx));
 
-    cx.subscribe_global::<WorkspaceCreated, _>({
-        let log_store = log_store.clone();
-        move |event, cx| {
-            let workspace = &event.0;
-            if let Some(workspace) = workspace.upgrade(cx) {
-                let project = workspace.read(cx).project().clone();
-                if project.read(cx).is_local() {
-                    log_store.update(cx, |store, cx| {
-                        store.add_project(&project, cx);
-                    });
-                }
-            }
+    cx.observe_new_views(move |workspace: &mut Workspace, cx| {
+        let project = workspace.project();
+        if project.read(cx).is_local() {
+            log_store.update(cx, |store, cx| {
+                store.add_project(&project, cx);
+            });
         }
-    })
-    .detach();
 
-    cx.add_action(
-        move |workspace: &mut Workspace, _: &OpenLanguageServerLogs, cx: _| {
+        let log_store = log_store.clone();
+        workspace.register_action(move |workspace, _: &OpenLanguageServerLogs, cx| {
             let project = workspace.project().read(cx);
             if project.is_local() {
                 workspace.add_item(
-                    Box::new(cx.add_view(|cx| {
+                    Box::new(cx.new_view(|cx| {
                         LspLogView::new(workspace.project().clone(), log_store.clone(), cx)
                     })),
                     cx,
                 );
             }
-        },
-    );
+        });
+    })
+    .detach();
 }
 
 impl LogStore {
@@ -123,28 +111,28 @@ impl LogStore {
             projects: HashMap::default(),
             io_tx,
         };
-        cx.spawn_weak(|this, mut cx| async move {
+        cx.spawn(|this, mut cx| async move {
             while let Some((project, server_id, io_kind, message)) = io_rx.next().await {
-                if let Some(this) = this.upgrade(&cx) {
+                if let Some(this) = this.upgrade() {
                     this.update(&mut cx, |this, cx| {
                         this.on_io(project, server_id, io_kind, &message, cx);
-                    });
+                    })?;
                 }
             }
             anyhow::Ok(())
         })
-        .detach();
+        .detach_and_log_err(cx);
         this
     }
 
-    pub fn add_project(&mut self, project: &ModelHandle<Project>, cx: &mut ModelContext<Self>) {
+    pub fn add_project(&mut self, project: &Model<Project>, cx: &mut ModelContext<Self>) {
         let weak_project = project.downgrade();
         self.projects.insert(
-            weak_project,
+            project.downgrade(),
             ProjectState {
                 servers: HashMap::default(),
                 _subscriptions: [
-                    cx.observe_release(&project, move |this, _, _| {
+                    cx.observe_release(project, move |this, _, _| {
                         this.projects.remove(&weak_project);
                     }),
                     cx.subscribe(project, |this, project, event, cx| match event {
@@ -166,7 +154,7 @@ impl LogStore {
 
     fn add_language_server(
         &mut self,
-        project: &ModelHandle<Project>,
+        project: &Model<Project>,
         id: LanguageServerId,
         cx: &mut ModelContext<Self>,
     ) -> Option<&mut LanguageServerState> {
@@ -194,22 +182,21 @@ impl LogStore {
         server_state._io_logs_subscription = server.as_ref().map(|server| {
             server.on_io(move |io_kind, message| {
                 io_tx
-                    .unbounded_send((weak_project, id, io_kind, message.to_string()))
+                    .unbounded_send((weak_project.clone(), id, io_kind, message.to_string()))
                     .ok();
             })
         });
-        let this = cx.weak_handle();
+        let this = cx.handle().downgrade();
         let weak_project = project.downgrade();
         server_state._lsp_logs_subscription = server.map(|server| {
             let server_id = server.server_id();
             server.on_notification::<lsp::notification::LogMessage, _>({
                 move |params, mut cx| {
-                    if let Some((project, this)) =
-                        weak_project.upgrade(&mut cx).zip(this.upgrade(&mut cx))
-                    {
+                    if let Some((project, this)) = weak_project.upgrade().zip(this.upgrade()) {
                         this.update(&mut cx, |this, cx| {
                             this.add_language_server_log(&project, server_id, &params.message, cx);
-                        });
+                        })
+                        .ok();
                     }
                 }
             })
@@ -219,7 +206,7 @@ impl LogStore {
 
     fn add_language_server_log(
         &mut self,
-        project: &ModelHandle<Project>,
+        project: &Model<Project>,
         id: LanguageServerId,
         message: &str,
         cx: &mut ModelContext<Self>,
@@ -251,7 +238,7 @@ impl LogStore {
 
     fn remove_language_server(
         &mut self,
-        project: &ModelHandle<Project>,
+        project: &Model<Project>,
         id: LanguageServerId,
         cx: &mut ModelContext<Self>,
     ) -> Option<()> {
@@ -263,7 +250,7 @@ impl LogStore {
 
     fn server_logs(
         &self,
-        project: &ModelHandle<Project>,
+        project: &Model<Project>,
         server_id: LanguageServerId,
     ) -> Option<&VecDeque<String>> {
         let weak_project = project.downgrade();
@@ -274,7 +261,7 @@ impl LogStore {
 
     fn enable_rpc_trace_for_language_server(
         &mut self,
-        project: &ModelHandle<Project>,
+        project: &Model<Project>,
         server_id: LanguageServerId,
     ) -> Option<&mut LanguageServerRpcState> {
         let weak_project = project.downgrade();
@@ -291,7 +278,7 @@ impl LogStore {
 
     pub fn disable_rpc_trace_for_language_server(
         &mut self,
-        project: &ModelHandle<Project>,
+        project: &Model<Project>,
         server_id: LanguageServerId,
         _: &mut ModelContext<Self>,
     ) -> Option<()> {
@@ -304,7 +291,7 @@ impl LogStore {
 
     fn on_io(
         &mut self,
-        project: WeakModelHandle<Project>,
+        project: WeakModel<Project>,
         language_server_id: LanguageServerId,
         io_kind: IoKind,
         message: &str,
@@ -314,7 +301,7 @@ impl LogStore {
             IoKind::StdOut => true,
             IoKind::StdIn => false,
             IoKind::StdErr => {
-                let project = project.upgrade(cx)?;
+                let project = project.upgrade()?;
                 let message = format!("stderr: {}", message.trim());
                 self.add_language_server_log(&project, language_server_id, &message, cx);
                 return Some(());
@@ -365,8 +352,8 @@ impl LogStore {
 
 impl LspLogView {
     pub fn new(
-        project: ModelHandle<Project>,
-        log_store: ModelHandle<LogStore>,
+        project: Model<Project>,
+        log_store: Model<LogStore>,
         cx: &mut ViewContext<Self>,
     ) -> Self {
         let server_id = log_store
@@ -427,14 +414,25 @@ impl LspLogView {
             }
         });
         let (editor, editor_subscription) = Self::editor_for_logs(String::new(), cx);
+
+        let focus_handle = cx.focus_handle();
+        let focus_subscription = cx.on_focus(&focus_handle, |log_view, cx| {
+            cx.focus_view(&log_view.editor);
+        });
+
         let mut this = Self {
+            focus_handle,
             editor,
             editor_subscription,
             project,
             log_store,
             current_server_id: None,
             is_showing_rpc_trace: false,
-            _log_store_subscriptions: vec![model_changes_subscription, events_subscriptions],
+            _log_store_subscriptions: vec![
+                model_changes_subscription,
+                events_subscriptions,
+                focus_subscription,
+            ],
         };
         if let Some(server_id) = server_id {
             this.show_logs_for_server(server_id, cx);
@@ -445,15 +443,20 @@ impl LspLogView {
     fn editor_for_logs(
         log_contents: String,
         cx: &mut ViewContext<Self>,
-    ) -> (ViewHandle<Editor>, Subscription) {
-        let editor = cx.add_view(|cx| {
-            let mut editor = Editor::multi_line(None, cx);
+    ) -> (View<Editor>, Subscription) {
+        let editor = cx.new_view(|cx| {
+            let mut editor = Editor::multi_line(cx);
             editor.set_text(log_contents, cx);
             editor.move_to_end(&MoveToEnd, cx);
             editor.set_read_only(true);
             editor
         });
-        let editor_subscription = cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone()));
+        let editor_subscription = cx.subscribe(
+            &editor,
+            |_, _, event: &EditorEvent, cx: &mut ViewContext<'_, LspLogView>| {
+                cx.emit(event.clone())
+            },
+        );
         (editor, editor_subscription)
     }
 
@@ -516,6 +519,7 @@ impl LspLogView {
             self.editor_subscription = editor_subscription;
             cx.notify();
         }
+        cx.focus(&self.focus_handle);
     }
 
     fn show_rpc_trace_for_server(
@@ -540,22 +544,24 @@ impl LspLogView {
                 .as_singleton()
                 .expect("log buffer should be a singleton")
                 .update(cx, |_, cx| {
-                    cx.spawn_weak({
+                    cx.spawn({
                         let buffer = cx.handle();
                         |_, mut cx| async move {
                             let language = language.await.ok();
                             buffer.update(&mut cx, |buffer, cx| {
                                 buffer.set_language(language, cx);
-                            });
+                            })
                         }
                     })
-                    .detach();
+                    .detach_and_log_err(cx);
                 });
 
             self.editor = editor;
             self.editor_subscription = editor_subscription;
             cx.notify();
         }
+
+        cx.focus(&self.focus_handle);
     }
 
     fn toggle_rpc_trace_for_server(
@@ -588,33 +594,31 @@ fn log_contents(lines: &VecDeque<String>) -> String {
     }
 }
 
-impl View for LspLogView {
-    fn ui_name() -> &'static str {
-        "LspLogView"
+impl Render for LspLogView {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        self.editor
+            .update(cx, |editor, cx| editor.render(cx).into_any_element())
     }
+}
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        ChildView::new(&self.editor, cx).into_any()
-    }
-
-    fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
-        if cx.is_self_focused() {
-            cx.focus(&self.editor);
-        }
+impl FocusableView for LspLogView {
+    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
+        self.focus_handle.clone()
     }
 }
 
 impl Item for LspLogView {
-    fn tab_content<V: 'static>(
-        &self,
-        _: Option<usize>,
-        style: &theme::Tab,
-        _: &AppContext,
-    ) -> AnyElement<V> {
-        Label::new("LSP Logs", style.label.clone()).into_any()
+    type Event = EditorEvent;
+
+    fn to_item_events(event: &Self::Event, f: impl FnMut(workspace::item::ItemEvent)) {
+        Editor::to_item_events(event, f)
     }
 
-    fn as_searchable(&self, handle: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
+    fn tab_content(&self, _: Option<usize>, _: bool, _: &WindowContext<'_>) -> AnyElement {
+        Label::new("LSP Logs").into_any_element()
+    }
+
+    fn as_searchable(&self, handle: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
         Some(Box::new(handle.clone()))
     }
 }
@@ -622,15 +626,6 @@ impl Item for LspLogView {
 impl SearchableItem for LspLogView {
     type Match = <Editor as SearchableItem>::Match;
 
-    fn to_search_event(
-        &mut self,
-        event: &Self::Event,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<workspace::searchable::SearchEvent> {
-        self.editor
-            .update(cx, |editor, cx| editor.to_search_event(event, cx))
-    }
-
     fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
         self.editor.update(cx, |e, cx| e.clear_matches(cx))
     }
@@ -689,22 +684,21 @@ impl SearchableItem for LspLogView {
     }
 }
 
+impl EventEmitter<ToolbarItemEvent> for LspLogToolbarItemView {}
+
 impl ToolbarItemView for LspLogToolbarItemView {
     fn set_active_pane_item(
         &mut self,
         active_pane_item: Option<&dyn ItemHandle>,
         cx: &mut ViewContext<Self>,
     ) -> workspace::ToolbarItemLocation {
-        self.menu_open = false;
         if let Some(item) = active_pane_item {
             if let Some(log_view) = item.downcast::<LspLogView>() {
                 self.log_view = Some(log_view.clone());
                 self._log_view_subscription = Some(cx.observe(&log_view, |_, _, cx| {
                     cx.notify();
                 }));
-                return ToolbarItemLocation::PrimaryLeft {
-                    flex: Some((1., false)),
-                };
+                return ToolbarItemLocation::PrimaryLeft;
             }
         }
         self.log_view = None;
@@ -713,15 +707,10 @@ impl ToolbarItemView for LspLogToolbarItemView {
     }
 }
 
-impl View for LspLogToolbarItemView {
-    fn ui_name() -> &'static str {
-        "LspLogView"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = theme::current(cx).clone();
-        let Some(log_view) = self.log_view.as_ref() else {
-            return Empty::new().into_any();
+impl Render for LspLogToolbarItemView {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        let Some(log_view) = self.log_view.clone() else {
+            return div();
         };
         let (menu_rows, current_server_id) = log_view.update(cx, |log_view, cx| {
             let menu_rows = log_view.menu_items(cx).unwrap_or_default();
@@ -736,99 +725,128 @@ impl View for LspLogToolbarItemView {
                 None
             }
         });
-        let server_selected = current_server.is_some();
-
-        enum LspLogScroll {}
-        enum Menu {}
-        let lsp_menu = Stack::new()
-            .with_child(Self::render_language_server_menu_header(
-                current_server,
-                &theme,
-                cx,
+
+        let log_toolbar_view = cx.view().clone();
+        let lsp_menu = popover_menu("LspLogView")
+            .anchor(AnchorCorner::TopLeft)
+            .trigger(Button::new(
+                "language_server_menu_header",
+                current_server
+                    .and_then(|row| {
+                        Some(Cow::Owned(format!(
+                            "{} ({}) - {}",
+                            row.server_name.0,
+                            row.worktree_root_name,
+                            if row.rpc_trace_selected {
+                                RPC_MESSAGES
+                            } else {
+                                SERVER_LOGS
+                            },
+                        )))
+                    })
+                    .unwrap_or_else(|| "No server selected".into()),
             ))
-            .with_children(if self.menu_open {
-                Some(
-                    Overlay::new(
-                        MouseEventHandler::new::<Menu, _>(0, cx, move |_, cx| {
-                            Flex::column()
-                                .scrollable::<LspLogScroll>(0, None, cx)
-                                .with_children(menu_rows.into_iter().map(|row| {
-                                    Self::render_language_server_menu_item(
-                                        row.server_id,
-                                        row.server_name,
-                                        &row.worktree_root_name,
-                                        row.rpc_trace_enabled,
-                                        row.logs_selected,
-                                        row.rpc_trace_selected,
-                                        &theme,
-                                        cx,
-                                    )
-                                }))
-                                .contained()
-                                .with_style(theme.toolbar_dropdown_menu.container)
-                                .constrained()
-                                .with_width(400.)
-                                .with_height(400.)
-                        })
-                        .on_down_out(MouseButton::Left, |_, this, cx| {
-                            this.menu_open = false;
-                            cx.notify()
-                        }),
-                    )
-                    .with_hoverable(true)
-                    .with_fit_mode(OverlayFitMode::SwitchAnchor)
-                    .with_anchor_corner(AnchorCorner::TopLeft)
-                    .with_z_index(999)
-                    .aligned()
-                    .bottom()
-                    .left(),
+            .menu(move |cx| {
+                let menu_rows = menu_rows.clone();
+                let log_view = log_view.clone();
+                let log_toolbar_view = log_toolbar_view.clone();
+                ContextMenu::build(cx, move |mut menu, cx| {
+                    for (ix, row) in menu_rows.into_iter().enumerate() {
+                        let server_selected = Some(row.server_id) == current_server_id;
+                        menu = menu
+                            .header(format!(
+                                "{} ({})",
+                                row.server_name.0, row.worktree_root_name
+                            ))
+                            .entry(
+                                SERVER_LOGS,
+                                None,
+                                cx.handler_for(&log_view, move |view, cx| {
+                                    view.show_logs_for_server(row.server_id, cx);
+                                }),
+                            );
+                        if server_selected && row.logs_selected {
+                            debug_assert_eq!(
+                                Some(ix * 3 + 1),
+                                menu.select_last(),
+                                "Could not scroll to a just added LSP menu item"
+                            );
+                        }
+
+                        menu = menu.custom_entry(
+                            {
+                                let log_toolbar_view = log_toolbar_view.clone();
+                                move |cx| {
+                                    h_stack()
+                                        .w_full()
+                                        .justify_between()
+                                        .child(Label::new(RPC_MESSAGES))
+                                        .child(
+                                            div().z_index(120).child(
+                                                Checkbox::new(
+                                                    ix,
+                                                    if row.rpc_trace_enabled {
+                                                        Selection::Selected
+                                                    } else {
+                                                        Selection::Unselected
+                                                    },
+                                                )
+                                                .on_click(cx.listener_for(
+                                                    &log_toolbar_view,
+                                                    move |view, selection, cx| {
+                                                        let enabled = matches!(
+                                                            selection,
+                                                            Selection::Selected
+                                                        );
+                                                        view.toggle_logging_for_server(
+                                                            row.server_id,
+                                                            enabled,
+                                                            cx,
+                                                        );
+                                                        cx.stop_propagation();
+                                                    },
+                                                )),
+                                            ),
+                                        )
+                                        .into_any_element()
+                                }
+                            },
+                            cx.handler_for(&log_view, move |view, cx| {
+                                view.show_rpc_trace_for_server(row.server_id, cx);
+                            }),
+                        );
+                        if server_selected && row.rpc_trace_selected {
+                            debug_assert_eq!(
+                                Some(ix * 3 + 2),
+                                menu.select_last(),
+                                "Could not scroll to a just added LSP menu item"
+                            );
+                        }
+                    }
+                    menu
+                })
+                .into()
+            });
+
+        h_stack().size_full().child(lsp_menu).child(
+            div()
+                .child(
+                    Button::new("clear_log_button", "Clear").on_click(cx.listener(
+                        |this, _, cx| {
+                            if let Some(log_view) = this.log_view.as_ref() {
+                                log_view.update(cx, |log_view, cx| {
+                                    log_view.editor.update(cx, |editor, cx| {
+                                        editor.set_read_only(false);
+                                        editor.clear(cx);
+                                        editor.set_read_only(true);
+                                    });
+                                })
+                            }
+                        },
+                    )),
                 )
-            } else {
-                None
-            })
-            .aligned()
-            .left()
-            .clipped();
-
-        enum LspCleanupButton {}
-        let log_cleanup_button =
-            MouseEventHandler::new::<LspCleanupButton, _>(1, cx, |state, cx| {
-                let theme = theme::current(cx).clone();
-                let style = theme
-                    .workspace
-                    .toolbar
-                    .toggleable_text_tool
-                    .in_state(server_selected)
-                    .style_for(state);
-                Label::new("Clear", style.text.clone())
-                    .aligned()
-                    .contained()
-                    .with_style(style.container)
-                    .constrained()
-                    .with_height(theme.toolbar_dropdown_menu.row_height / 6.0 * 5.0)
-            })
-            .on_click(MouseButton::Left, move |_, this, cx| {
-                if let Some(log_view) = this.log_view.as_ref() {
-                    log_view.update(cx, |log_view, cx| {
-                        log_view.editor.update(cx, |editor, cx| {
-                            editor.set_read_only(false);
-                            editor.clear(cx);
-                            editor.set_read_only(true);
-                        });
-                    })
-                }
-            })
-            .with_cursor_style(CursorStyle::PointingHand)
-            .aligned()
-            .right();
-
-        Flex::row()
-            .with_child(lsp_menu)
-            .with_child(log_cleanup_button)
-            .contained()
-            .aligned()
-            .left()
-            .into_any_named("lsp log controls")
+                .ml_2(),
+        )
     }
 }
 
@@ -838,17 +856,11 @@ const SERVER_LOGS: &str = "Server Logs";
 impl LspLogToolbarItemView {
     pub fn new() -> Self {
         Self {
-            menu_open: false,
             log_view: None,
             _log_view_subscription: None,
         }
     }
 
-    fn toggle_menu(&mut self, cx: &mut ViewContext<Self>) {
-        self.menu_open = !self.menu_open;
-        cx.notify();
-    }
-
     fn toggle_logging_for_server(
         &mut self,
         id: LanguageServerId,
@@ -862,144 +874,11 @@ impl LspLogToolbarItemView {
                     log_view.show_logs_for_server(id, cx);
                     cx.notify();
                 }
+                cx.focus(&log_view.focus_handle);
             });
         }
         cx.notify();
     }
-
-    fn show_logs_for_server(&mut self, id: LanguageServerId, cx: &mut ViewContext<Self>) {
-        if let Some(log_view) = &self.log_view {
-            log_view.update(cx, |view, cx| view.show_logs_for_server(id, cx));
-            self.menu_open = false;
-            cx.notify();
-        }
-    }
-
-    fn show_rpc_trace_for_server(&mut self, id: LanguageServerId, cx: &mut ViewContext<Self>) {
-        if let Some(log_view) = &self.log_view {
-            log_view.update(cx, |view, cx| view.show_rpc_trace_for_server(id, cx));
-            self.menu_open = false;
-            cx.notify();
-        }
-    }
-
-    fn render_language_server_menu_header(
-        current_server: Option<LogMenuItem>,
-        theme: &Arc<Theme>,
-        cx: &mut ViewContext<Self>,
-    ) -> impl Element<Self> {
-        enum ToggleMenu {}
-        MouseEventHandler::new::<ToggleMenu, _>(0, cx, move |state, _| {
-            let label: Cow<str> = current_server
-                .and_then(|row| {
-                    Some(
-                        format!(
-                            "{} ({}) - {}",
-                            row.server_name.0,
-                            row.worktree_root_name,
-                            if row.rpc_trace_selected {
-                                RPC_MESSAGES
-                            } else {
-                                SERVER_LOGS
-                            },
-                        )
-                        .into(),
-                    )
-                })
-                .unwrap_or_else(|| "No server selected".into());
-            let style = theme.toolbar_dropdown_menu.header.style_for(state);
-            Label::new(label, style.text.clone())
-                .contained()
-                .with_style(style.container)
-        })
-        .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, move |_, view, cx| {
-            view.toggle_menu(cx);
-        })
-    }
-
-    fn render_language_server_menu_item(
-        id: LanguageServerId,
-        name: LanguageServerName,
-        worktree_root_name: &str,
-        rpc_trace_enabled: bool,
-        logs_selected: bool,
-        rpc_trace_selected: bool,
-        theme: &Arc<Theme>,
-        cx: &mut ViewContext<Self>,
-    ) -> impl Element<Self> {
-        enum ActivateLog {}
-        enum ActivateRpcTrace {}
-        enum LanguageServerCheckbox {}
-
-        Flex::column()
-            .with_child({
-                let style = &theme.toolbar_dropdown_menu.section_header;
-                Label::new(
-                    format!("{} ({})", name.0, worktree_root_name),
-                    style.text.clone(),
-                )
-                .contained()
-                .with_style(style.container)
-                .constrained()
-                .with_height(theme.toolbar_dropdown_menu.row_height)
-            })
-            .with_child(
-                MouseEventHandler::new::<ActivateLog, _>(id.0, cx, move |state, _| {
-                    let style = theme
-                        .toolbar_dropdown_menu
-                        .item
-                        .in_state(logs_selected)
-                        .style_for(state);
-                    Label::new(SERVER_LOGS, style.text.clone())
-                        .contained()
-                        .with_style(style.container)
-                        .constrained()
-                        .with_height(theme.toolbar_dropdown_menu.row_height)
-                })
-                .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, view, cx| {
-                    view.show_logs_for_server(id, cx);
-                }),
-            )
-            .with_child(
-                MouseEventHandler::new::<ActivateRpcTrace, _>(id.0, cx, move |state, cx| {
-                    let style = theme
-                        .toolbar_dropdown_menu
-                        .item
-                        .in_state(rpc_trace_selected)
-                        .style_for(state);
-                    Flex::row()
-                        .with_child(
-                            Label::new(RPC_MESSAGES, style.text.clone())
-                                .constrained()
-                                .with_height(theme.toolbar_dropdown_menu.row_height),
-                        )
-                        .with_child(
-                            ui::checkbox_with_label::<LanguageServerCheckbox, _, Self, _>(
-                                Empty::new(),
-                                &theme.welcome.checkbox,
-                                rpc_trace_enabled,
-                                id.0,
-                                cx,
-                                move |this, enabled, cx| {
-                                    this.toggle_logging_for_server(id, enabled, cx);
-                                },
-                            )
-                            .flex_float(),
-                        )
-                        .align_children_center()
-                        .contained()
-                        .with_style(style.container)
-                        .constrained()
-                        .with_height(theme.toolbar_dropdown_menu.row_height)
-                })
-                .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, view, cx| {
-                    view.show_rpc_trace_for_server(id, cx);
-                }),
-            )
-    }
 }
 
 pub enum Event {
@@ -1010,14 +889,7 @@ pub enum Event {
     },
 }
 
-impl Entity for LogStore {
-    type Event = Event;
-}
-
-impl Entity for LspLogView {
-    type Event = editor::Event;
-}
-
-impl Entity for LspLogToolbarItemView {
-    type Event = ();
-}
+impl EventEmitter<Event> for LogStore {}
+impl EventEmitter<Event> for LspLogView {}
+impl EventEmitter<EditorEvent> for LspLogView {}
+impl EventEmitter<SearchEvent> for LspLogView {}

crates/language_tools/src/lsp_log_tests.rs 🔗

@@ -4,7 +4,7 @@ use crate::lsp_log::LogMenuItem;
 
 use super::*;
 use futures::StreamExt;
-use gpui::{serde_json::json, TestAppContext};
+use gpui::{serde_json::json, Context, TestAppContext, VisualTestContext};
 use language::{tree_sitter_rust, FakeLspAdapter, Language, LanguageConfig, LanguageServerName};
 use project::{FakeFs, Project};
 use settings::SettingsStore;
@@ -32,7 +32,7 @@ async fn test_lsp_logs(cx: &mut TestAppContext) {
         }))
         .await;
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.background_executor.clone());
     fs.insert_tree(
         "/the-root",
         json!({
@@ -46,7 +46,7 @@ async fn test_lsp_logs(cx: &mut TestAppContext) {
         project.languages().add(Arc::new(rust_language));
     });
 
-    let log_store = cx.add_model(|cx| LogStore::new(cx));
+    let log_store = cx.new_model(|cx| LogStore::new(cx));
     log_store.update(cx, |store, cx| store.add_project(&project, cx));
 
     let _rust_buffer = project
@@ -61,17 +61,17 @@ async fn test_lsp_logs(cx: &mut TestAppContext) {
         .receive_notification::<lsp::notification::DidOpenTextDocument>()
         .await;
 
-    let log_view = cx
-        .add_window(|cx| LspLogView::new(project.clone(), log_store.clone(), cx))
-        .root(cx);
+    let window = cx.add_window(|cx| LspLogView::new(project.clone(), log_store.clone(), cx));
+    let log_view = window.root(cx).unwrap();
+    let mut cx = VisualTestContext::from_window(*window, cx);
 
     language_server.notify::<lsp::notification::LogMessage>(lsp::LogMessageParams {
         message: "hello from the server".into(),
         typ: lsp::MessageType::INFO,
     });
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
 
-    log_view.read_with(cx, |view, cx| {
+    log_view.update(&mut cx, |view, cx| {
         assert_eq!(
             view.menu_items(cx).unwrap(),
             &[LogMenuItem {
@@ -79,7 +79,7 @@ async fn test_lsp_logs(cx: &mut TestAppContext) {
                 server_name: LanguageServerName("the-rust-language-server".into()),
                 worktree_root_name: project
                     .read(cx)
-                    .worktrees(cx)
+                    .worktrees()
                     .next()
                     .unwrap()
                     .read(cx)
@@ -95,11 +95,10 @@ async fn test_lsp_logs(cx: &mut TestAppContext) {
 }
 
 fn init_test(cx: &mut gpui::TestAppContext) {
-    cx.foreground().forbid_parking();
-
     cx.update(|cx| {
-        cx.set_global(SettingsStore::test(cx));
-        theme::init((), cx);
+        let settings_store = SettingsStore::test(cx);
+        cx.set_global(settings_store);
+        theme::init(theme::LoadThemes::JustBase, cx);
         language::init(cx);
         client::init_settings(cx);
         Project::init_settings(cx);

crates/language_tools/src/syntax_tree_view.rs 🔗

@@ -1,85 +1,85 @@
 use editor::{scroll::autoscroll::Autoscroll, Anchor, Editor, ExcerptId};
 use gpui::{
-    actions,
-    elements::{
-        AnchorCorner, Empty, Flex, Label, MouseEventHandler, Overlay, OverlayFitMode,
-        ParentElement, ScrollTarget, Stack, UniformList, UniformListState,
-    },
-    fonts::TextStyle,
-    platform::{CursorStyle, MouseButton},
-    AppContext, Element, Entity, ModelHandle, View, ViewContext, ViewHandle, WeakViewHandle,
+    actions, canvas, div, rems, uniform_list, AnyElement, AppContext, AvailableSpace, Div,
+    EventEmitter, FocusHandle, FocusableView, Hsla, InteractiveElement, IntoElement, Model,
+    MouseButton, MouseDownEvent, MouseMoveEvent, ParentElement, Pixels, Render, Styled,
+    UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WindowContext,
 };
-use language::{Buffer, OwnedSyntaxLayerInfo, SyntaxLayerInfo};
-use std::{mem, ops::Range, sync::Arc};
-use theme::{Theme, ThemeSettings};
+use language::{Buffer, OwnedSyntaxLayerInfo};
+use settings::Settings;
+use std::{mem, ops::Range};
+use theme::{ActiveTheme, ThemeSettings};
 use tree_sitter::{Node, TreeCursor};
+use ui::{h_stack, popover_menu, ButtonLike, Color, ContextMenu, Label, LabelCommon, PopoverMenu};
 use workspace::{
     item::{Item, ItemHandle},
-    ToolbarItemLocation, ToolbarItemView, Workspace,
+    SplitDirection, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
 };
 
 actions!(debug, [OpenSyntaxTreeView]);
 
 pub fn init(cx: &mut AppContext) {
-    cx.add_action(
-        move |workspace: &mut Workspace, _: &OpenSyntaxTreeView, cx: _| {
+    cx.observe_new_views(|workspace: &mut Workspace, _| {
+        workspace.register_action(|workspace, _: &OpenSyntaxTreeView, cx| {
             let active_item = workspace.active_item(cx);
             let workspace_handle = workspace.weak_handle();
             let syntax_tree_view =
-                cx.add_view(|cx| SyntaxTreeView::new(workspace_handle, active_item, cx));
-            workspace.add_item(Box::new(syntax_tree_view), cx);
-        },
-    );
+                cx.new_view(|cx| SyntaxTreeView::new(workspace_handle, active_item, cx));
+            workspace.split_item(SplitDirection::Right, Box::new(syntax_tree_view), cx)
+        });
+    })
+    .detach();
 }
 
 pub struct SyntaxTreeView {
-    workspace_handle: WeakViewHandle<Workspace>,
+    workspace_handle: WeakView<Workspace>,
     editor: Option<EditorState>,
-    mouse_y: Option<f32>,
-    line_height: Option<f32>,
-    list_state: UniformListState,
+    mouse_y: Option<Pixels>,
+    line_height: Option<Pixels>,
+    list_scroll_handle: UniformListScrollHandle,
     selected_descendant_ix: Option<usize>,
     hovered_descendant_ix: Option<usize>,
+    focus_handle: FocusHandle,
 }
 
 pub struct SyntaxTreeToolbarItemView {
-    tree_view: Option<ViewHandle<SyntaxTreeView>>,
+    tree_view: Option<View<SyntaxTreeView>>,
     subscription: Option<gpui::Subscription>,
-    menu_open: bool,
 }
 
 struct EditorState {
-    editor: ViewHandle<Editor>,
+    editor: View<Editor>,
     active_buffer: Option<BufferState>,
     _subscription: gpui::Subscription,
 }
 
 #[derive(Clone)]
 struct BufferState {
-    buffer: ModelHandle<Buffer>,
+    buffer: Model<Buffer>,
     excerpt_id: ExcerptId,
     active_layer: Option<OwnedSyntaxLayerInfo>,
 }
 
 impl SyntaxTreeView {
     pub fn new(
-        workspace_handle: WeakViewHandle<Workspace>,
+        workspace_handle: WeakView<Workspace>,
         active_item: Option<Box<dyn ItemHandle>>,
         cx: &mut ViewContext<Self>,
     ) -> Self {
         let mut this = Self {
             workspace_handle: workspace_handle.clone(),
-            list_state: UniformListState::default(),
+            list_scroll_handle: UniformListScrollHandle::new(),
             editor: None,
             mouse_y: None,
             line_height: None,
             hovered_descendant_ix: None,
             selected_descendant_ix: None,
+            focus_handle: cx.focus_handle(),
         };
 
         this.workspace_updated(active_item, cx);
         cx.observe(
-            &workspace_handle.upgrade(cx).unwrap(),
+            &workspace_handle.upgrade().unwrap(),
             |this, workspace, cx| {
                 this.workspace_updated(workspace.read(cx).active_item(cx), cx);
             },
@@ -95,7 +95,7 @@ impl SyntaxTreeView {
         cx: &mut ViewContext<Self>,
     ) {
         if let Some(item) = active_item {
-            if item.id() != cx.view_id() {
+            if item.item_id() != cx.entity_id() {
                 if let Some(editor) = item.act_as::<Editor>(cx) {
                     self.set_editor(editor, cx);
                 }
@@ -103,7 +103,7 @@ impl SyntaxTreeView {
         }
     }
 
-    fn set_editor(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
+    fn set_editor(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
         if let Some(state) = &self.editor {
             if state.editor == editor {
                 return;
@@ -115,8 +115,8 @@ impl SyntaxTreeView {
 
         let subscription = cx.subscribe(&editor, |this, _, event, cx| {
             let did_reparse = match event {
-                editor::Event::Reparsed => true,
-                editor::Event::SelectionsChanged { .. } => false,
+                editor::EditorEvent::Reparsed => true,
+                editor::EditorEvent::SelectionsChanged { .. } => false,
                 _ => return,
             };
             this.editor_updated(did_reparse, cx);
@@ -202,15 +202,15 @@ impl SyntaxTreeView {
 
         let descendant_ix = cursor.descendant_index();
         self.selected_descendant_ix = Some(descendant_ix);
-        self.list_state.scroll_to(ScrollTarget::Show(descendant_ix));
+        self.list_scroll_handle.scroll_to_item(descendant_ix);
 
         cx.notify();
         Some(())
     }
 
-    fn handle_click(&mut self, y: f32, cx: &mut ViewContext<SyntaxTreeView>) -> Option<()> {
+    fn handle_click(&mut self, y: Pixels, cx: &mut ViewContext<SyntaxTreeView>) -> Option<()> {
         let line_height = self.line_height?;
-        let ix = ((self.list_state.scroll_top() + y) / line_height) as usize;
+        let ix = ((self.list_scroll_handle.scroll_top() + y) / line_height) as usize;
 
         self.update_editor_with_range_for_descendant_ix(ix, cx, |editor, mut range, cx| {
             // Put the cursor at the beginning of the node.
@@ -225,14 +225,14 @@ impl SyntaxTreeView {
 
     fn hover_state_changed(&mut self, cx: &mut ViewContext<SyntaxTreeView>) {
         if let Some((y, line_height)) = self.mouse_y.zip(self.line_height) {
-            let ix = ((self.list_state.scroll_top() + y) / line_height) as usize;
+            let ix = ((self.list_scroll_handle.scroll_top() + y) / line_height) as usize;
             if self.hovered_descendant_ix != Some(ix) {
                 self.hovered_descendant_ix = Some(ix);
                 self.update_editor_with_range_for_descendant_ix(ix, cx, |editor, range, cx| {
                     editor.clear_background_highlights::<Self>(cx);
                     editor.highlight_background::<Self>(
                         vec![range],
-                        |theme| theme.editor.document_highlight_write_background,
+                        |theme| theme.editor_document_highlight_write_background,
                         cx,
                     );
                 });
@@ -275,113 +275,48 @@ impl SyntaxTreeView {
         Some(())
     }
 
-    fn render_node(
-        cursor: &TreeCursor,
-        depth: u32,
-        selected: bool,
-        hovered: bool,
-        list_hovered: bool,
-        style: &TextStyle,
-        editor_theme: &theme::Editor,
-        cx: &AppContext,
-    ) -> gpui::AnyElement<SyntaxTreeView> {
-        let node = cursor.node();
-        let mut range_style = style.clone();
-        let em_width = style.em_width(cx.font_cache());
-        let gutter_padding = (em_width * editor_theme.gutter_padding_factor).round();
-
-        range_style.color = editor_theme.line_number;
-
-        let mut anonymous_node_style = style.clone();
-        let string_color = editor_theme
-            .syntax
-            .highlights
-            .iter()
-            .find_map(|(name, style)| (name == "string").then(|| style.color)?);
-        let property_color = editor_theme
-            .syntax
-            .highlights
-            .iter()
-            .find_map(|(name, style)| (name == "property").then(|| style.color)?);
-        if let Some(color) = string_color {
-            anonymous_node_style.color = color;
-        }
-
-        let mut row = Flex::row();
+    fn render_node(cursor: &TreeCursor, depth: u32, selected: bool, cx: &AppContext) -> Div {
+        let colors = cx.theme().colors();
+        let mut row = h_stack();
         if let Some(field_name) = cursor.field_name() {
-            let mut field_style = style.clone();
-            if let Some(color) = property_color {
-                field_style.color = color;
-            }
-
-            row.add_children([
-                Label::new(field_name, field_style),
-                Label::new(": ", style.clone()),
-            ]);
+            row = row.children([Label::new(field_name).color(Color::Info), Label::new(": ")]);
         }
 
+        let node = cursor.node();
         return row
-            .with_child(
-                if node.is_named() {
-                    Label::new(node.kind(), style.clone())
-                } else {
-                    Label::new(format!("\"{}\"", node.kind()), anonymous_node_style)
-                }
-                .contained()
-                .with_margin_right(em_width),
+            .child(if node.is_named() {
+                Label::new(node.kind()).color(Color::Default)
+            } else {
+                Label::new(format!("\"{}\"", node.kind())).color(Color::Created)
+            })
+            .child(
+                div()
+                    .child(Label::new(format_node_range(node)).color(Color::Muted))
+                    .pl_1(),
             )
-            .with_child(Label::new(format_node_range(node), range_style))
-            .contained()
-            .with_background_color(if selected {
-                editor_theme.selection.selection
-            } else if hovered && list_hovered {
-                editor_theme.active_line_background
+            .text_bg(if selected {
+                colors.element_selected
             } else {
-                Default::default()
+                Hsla::default()
             })
-            .with_padding_left(gutter_padding + depth as f32 * 18.0)
-            .into_any();
+            .pl(rems(depth as f32))
+            .hover(|style| style.bg(colors.element_hover));
     }
 }
 
-impl Entity for SyntaxTreeView {
-    type Event = ();
-}
-
-impl View for SyntaxTreeView {
-    fn ui_name() -> &'static str {
-        "SyntaxTreeView"
-    }
-
-    fn render(&mut self, cx: &mut gpui::ViewContext<'_, '_, Self>) -> gpui::AnyElement<Self> {
-        let settings = settings::get::<ThemeSettings>(cx);
-        let font_family_id = settings.buffer_font_family;
-        let font_family_name = cx.font_cache().family_name(font_family_id).unwrap();
-        let font_properties = Default::default();
-        let font_id = cx
-            .font_cache()
-            .select_font(font_family_id, &font_properties)
-            .unwrap();
-        let font_size = settings.buffer_font_size(cx);
-
-        let editor_theme = settings.theme.editor.clone();
-        let style = TextStyle {
-            color: editor_theme.text_color,
-            font_family_name,
-            font_family_id,
-            font_id,
-            font_size,
-            font_properties: Default::default(),
-            underline: Default::default(),
-            soft_wrap: false,
-        };
-
-        let line_height = cx.font_cache().line_height(font_size);
+impl Render for SyntaxTreeView {
+    fn render(&mut self, cx: &mut gpui::ViewContext<'_, Self>) -> impl IntoElement {
+        let settings = ThemeSettings::get_global(cx);
+        let line_height = cx
+            .text_style()
+            .line_height_in_pixels(settings.buffer_font_size(cx));
         if Some(line_height) != self.line_height {
             self.line_height = Some(line_height);
             self.hover_state_changed(cx);
         }
 
+        let mut rendered = div().flex_1();
+
         if let Some(layer) = self
             .editor
             .as_ref()
@@ -389,108 +324,118 @@ impl View for SyntaxTreeView {
             .and_then(|buffer| buffer.active_layer.as_ref())
         {
             let layer = layer.clone();
-            let theme = editor_theme.clone();
-            return MouseEventHandler::new::<Self, _>(0, cx, move |state, cx| {
-                let list_hovered = state.hovered();
-                UniformList::new(
-                    self.list_state.clone(),
-                    layer.node().descendant_count(),
-                    cx,
-                    move |this, range, items, cx| {
-                        let mut cursor = layer.node().walk();
-                        let mut descendant_ix = range.start as usize;
-                        cursor.goto_descendant(descendant_ix);
-                        let mut depth = cursor.depth();
-                        let mut visited_children = false;
-                        while descendant_ix < range.end {
-                            if visited_children {
-                                if cursor.goto_next_sibling() {
-                                    visited_children = false;
-                                } else if cursor.goto_parent() {
-                                    depth -= 1;
-                                } else {
-                                    break;
-                                }
+            let list = uniform_list(
+                cx.view().clone(),
+                "SyntaxTreeView",
+                layer.node().descendant_count(),
+                move |this, range, cx| {
+                    let mut items = Vec::new();
+                    let mut cursor = layer.node().walk();
+                    let mut descendant_ix = range.start as usize;
+                    cursor.goto_descendant(descendant_ix);
+                    let mut depth = cursor.depth();
+                    let mut visited_children = false;
+                    while descendant_ix < range.end {
+                        if visited_children {
+                            if cursor.goto_next_sibling() {
+                                visited_children = false;
+                            } else if cursor.goto_parent() {
+                                depth -= 1;
+                            } else {
+                                break;
+                            }
+                        } else {
+                            items.push(Self::render_node(
+                                &cursor,
+                                depth,
+                                Some(descendant_ix) == this.selected_descendant_ix,
+                                cx,
+                            ));
+                            descendant_ix += 1;
+                            if cursor.goto_first_child() {
+                                depth += 1;
                             } else {
-                                items.push(Self::render_node(
-                                    &cursor,
-                                    depth,
-                                    Some(descendant_ix) == this.selected_descendant_ix,
-                                    Some(descendant_ix) == this.hovered_descendant_ix,
-                                    list_hovered,
-                                    &style,
-                                    &theme,
-                                    cx,
-                                ));
-                                descendant_ix += 1;
-                                if cursor.goto_first_child() {
-                                    depth += 1;
-                                } else {
-                                    visited_children = true;
-                                }
+                                visited_children = true;
                             }
                         }
-                    },
-                )
-            })
-            .on_move(move |event, this, cx| {
-                let y = event.position.y() - event.region.origin_y();
-                this.mouse_y = Some(y);
-                this.hover_state_changed(cx);
-            })
-            .on_click(MouseButton::Left, move |event, this, cx| {
-                let y = event.position.y() - event.region.origin_y();
-                this.handle_click(y, cx);
-            })
-            .contained()
-            .with_background_color(editor_theme.background)
-            .into_any();
+                    }
+                    items
+                },
+            )
+            .size_full()
+            .track_scroll(self.list_scroll_handle.clone())
+            .on_mouse_move(cx.listener(move |tree_view, event: &MouseMoveEvent, cx| {
+                tree_view.mouse_y = Some(event.position.y);
+                tree_view.hover_state_changed(cx);
+            }))
+            .on_mouse_down(
+                MouseButton::Left,
+                cx.listener(move |tree_view, event: &MouseDownEvent, cx| {
+                    tree_view.handle_click(event.position.y, cx);
+                }),
+            )
+            .text_bg(cx.theme().colors().background);
+
+            rendered = rendered.child(
+                canvas(move |bounds, cx| {
+                    list.into_any_element().draw(
+                        bounds.origin,
+                        bounds.size.map(AvailableSpace::Definite),
+                        cx,
+                    )
+                })
+                .size_full(),
+            );
         }
 
-        Empty::new().into_any()
+        rendered
+    }
+}
+
+impl EventEmitter<()> for SyntaxTreeView {}
+
+impl FocusableView for SyntaxTreeView {
+    fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
+        self.focus_handle.clone()
     }
 }
 
 impl Item for SyntaxTreeView {
-    fn tab_content<V: 'static>(
-        &self,
-        _: Option<usize>,
-        style: &theme::Tab,
-        _: &AppContext,
-    ) -> gpui::AnyElement<V> {
-        Label::new("Syntax Tree", style.label.clone()).into_any()
+    type Event = ();
+
+    fn to_item_events(_: &Self::Event, _: impl FnMut(workspace::item::ItemEvent)) {}
+
+    fn tab_content(&self, _: Option<usize>, _: bool, _: &WindowContext<'_>) -> AnyElement {
+        Label::new("Syntax Tree").into_any_element()
     }
 
     fn clone_on_split(
         &self,
-        _workspace_id: workspace::WorkspaceId,
+        _: workspace::WorkspaceId,
         cx: &mut ViewContext<Self>,
-    ) -> Option<Self>
+    ) -> Option<View<Self>>
     where
         Self: Sized,
     {
-        let mut clone = Self::new(self.workspace_handle.clone(), None, cx);
-        if let Some(editor) = &self.editor {
-            clone.set_editor(editor.editor.clone(), cx)
-        }
-        Some(clone)
+        Some(cx.new_view(|cx| {
+            let mut clone = Self::new(self.workspace_handle.clone(), None, cx);
+            if let Some(editor) = &self.editor {
+                clone.set_editor(editor.editor.clone(), cx)
+            }
+            clone
+        }))
     }
 }
 
 impl SyntaxTreeToolbarItemView {
     pub fn new() -> Self {
         Self {
-            menu_open: false,
             tree_view: None,
             subscription: None,
         }
     }
 
-    fn render_menu(
-        &mut self,
-        cx: &mut ViewContext<'_, '_, Self>,
-    ) -> Option<gpui::AnyElement<Self>> {
-        let theme = theme::current(cx).clone();
+    fn render_menu(&mut self, cx: &mut ViewContext<'_, Self>) -> Option<PopoverMenu<ContextMenu>> {
         let tree_view = self.tree_view.as_ref()?;
         let tree_view = tree_view.read(cx);
 
@@ -499,51 +444,32 @@ impl SyntaxTreeToolbarItemView {
         let active_layer = buffer_state.active_layer.clone()?;
         let active_buffer = buffer_state.buffer.read(cx).snapshot();
 
-        enum Menu {}
-
+        let view = cx.view().clone();
         Some(
-            Stack::new()
-                .with_child(Self::render_header(&theme, &active_layer, cx))
-                .with_children(self.menu_open.then(|| {
-                    Overlay::new(
-                        MouseEventHandler::new::<Menu, _>(0, cx, move |_, cx| {
-                            Flex::column()
-                                .with_children(active_buffer.syntax_layers().enumerate().map(
-                                    |(ix, layer)| {
-                                        Self::render_menu_item(&theme, &active_layer, layer, ix, cx)
-                                    },
-                                ))
-                                .contained()
-                                .with_style(theme.toolbar_dropdown_menu.container)
-                                .constrained()
-                                .with_width(400.)
-                                .with_height(400.)
-                        })
-                        .on_down_out(MouseButton::Left, |_, this, cx| {
-                            this.menu_open = false;
-                            cx.notify()
-                        }),
-                    )
-                    .with_hoverable(true)
-                    .with_fit_mode(OverlayFitMode::SwitchAnchor)
-                    .with_anchor_corner(AnchorCorner::TopLeft)
-                    .with_z_index(999)
-                    .aligned()
-                    .bottom()
-                    .left()
-                }))
-                .aligned()
-                .left()
-                .clipped()
-                .into_any(),
+            popover_menu("Syntax Tree")
+                .trigger(Self::render_header(&active_layer))
+                .menu(move |cx| {
+                    ContextMenu::build(cx, |mut menu, cx| {
+                        for (layer_ix, layer) in active_buffer.syntax_layers().enumerate() {
+                            menu = menu.entry(
+                                format!(
+                                    "{} {}",
+                                    layer.language.name(),
+                                    format_node_range(layer.node())
+                                ),
+                                None,
+                                cx.handler_for(&view, move |view, cx| {
+                                    view.select_layer(layer_ix, cx);
+                                }),
+                            );
+                        }
+                        menu
+                    })
+                    .into()
+                }),
         )
     }
 
-    fn toggle_menu(&mut self, cx: &mut ViewContext<Self>) {
-        self.menu_open = !self.menu_open;
-        cx.notify();
-    }
-
     fn select_layer(&mut self, layer_ix: usize, cx: &mut ViewContext<Self>) -> Option<()> {
         let tree_view = self.tree_view.as_ref()?;
         tree_view.update(cx, |view, cx| {
@@ -553,77 +479,16 @@ impl SyntaxTreeToolbarItemView {
             let layer = snapshot.syntax_layers().nth(layer_ix)?;
             buffer_state.active_layer = Some(layer.to_owned());
             view.selected_descendant_ix = None;
-            self.menu_open = false;
             cx.notify();
+            view.focus_handle.focus(cx);
             Some(())
         })
     }
 
-    fn render_header(
-        theme: &Arc<Theme>,
-        active_layer: &OwnedSyntaxLayerInfo,
-        cx: &mut ViewContext<Self>,
-    ) -> impl Element<Self> {
-        enum ToggleMenu {}
-        MouseEventHandler::new::<ToggleMenu, _>(0, cx, move |state, _| {
-            let style = theme.toolbar_dropdown_menu.header.style_for(state);
-            Flex::row()
-                .with_child(
-                    Label::new(active_layer.language.name().to_string(), style.text.clone())
-                        .contained()
-                        .with_margin_right(style.secondary_text_spacing),
-                )
-                .with_child(Label::new(
-                    format_node_range(active_layer.node()),
-                    style
-                        .secondary_text
-                        .clone()
-                        .unwrap_or_else(|| style.text.clone()),
-                ))
-                .contained()
-                .with_style(style.container)
-        })
-        .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, move |_, view, cx| {
-            view.toggle_menu(cx);
-        })
-    }
-
-    fn render_menu_item(
-        theme: &Arc<Theme>,
-        active_layer: &OwnedSyntaxLayerInfo,
-        layer: SyntaxLayerInfo,
-        layer_ix: usize,
-        cx: &mut ViewContext<Self>,
-    ) -> impl Element<Self> {
-        enum ActivateLayer {}
-        MouseEventHandler::new::<ActivateLayer, _>(layer_ix, cx, move |state, _| {
-            let is_selected = layer.node() == active_layer.node();
-            let style = theme
-                .toolbar_dropdown_menu
-                .item
-                .in_state(is_selected)
-                .style_for(state);
-            Flex::row()
-                .with_child(
-                    Label::new(layer.language.name().to_string(), style.text.clone())
-                        .contained()
-                        .with_margin_right(style.secondary_text_spacing),
-                )
-                .with_child(Label::new(
-                    format_node_range(layer.node()),
-                    style
-                        .secondary_text
-                        .clone()
-                        .unwrap_or_else(|| style.text.clone()),
-                ))
-                .contained()
-                .with_style(style.container)
-        })
-        .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, move |_, view, cx| {
-            view.select_layer(layer_ix, cx);
-        })
+    fn render_header(active_layer: &OwnedSyntaxLayerInfo) -> ButtonLike {
+        ButtonLike::new("syntax tree header")
+            .child(Label::new(active_layer.language.name()))
+            .child(Label::new(format_node_range(active_layer.node())))
     }
 }
 
@@ -639,35 +504,26 @@ fn format_node_range(node: Node) -> String {
     )
 }
 
-impl Entity for SyntaxTreeToolbarItemView {
-    type Event = ();
-}
-
-impl View for SyntaxTreeToolbarItemView {
-    fn ui_name() -> &'static str {
-        "SyntaxTreeToolbarItemView"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<'_, '_, Self>) -> gpui::AnyElement<Self> {
+impl Render for SyntaxTreeToolbarItemView {
+    fn render(&mut self, cx: &mut ViewContext<'_, Self>) -> impl IntoElement {
         self.render_menu(cx)
-            .unwrap_or_else(|| Empty::new().into_any())
+            .unwrap_or_else(|| popover_menu("Empty Syntax Tree"))
     }
 }
 
+impl EventEmitter<ToolbarItemEvent> for SyntaxTreeToolbarItemView {}
+
 impl ToolbarItemView for SyntaxTreeToolbarItemView {
     fn set_active_pane_item(
         &mut self,
         active_pane_item: Option<&dyn ItemHandle>,
         cx: &mut ViewContext<Self>,
-    ) -> workspace::ToolbarItemLocation {
-        self.menu_open = false;
+    ) -> ToolbarItemLocation {
         if let Some(item) = active_pane_item {
             if let Some(view) = item.downcast::<SyntaxTreeView>() {
                 self.tree_view = Some(view.clone());
                 self.subscription = Some(cx.observe(&view, |_, _, cx| cx.notify()));
-                return ToolbarItemLocation::PrimaryLeft {
-                    flex: Some((1., false)),
-                };
+                return ToolbarItemLocation::PrimaryLeft;
             }
         }
         self.tree_view = None;

crates/language_tools2/Cargo.toml 🔗

@@ -1,34 +0,0 @@
-[package]
-name = "language_tools2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/language_tools.rs"
-doctest = false
-
-[dependencies]
-collections = { path = "../collections" }
-editor = { package = "editor2", path = "../editor2" }
-settings = { package = "settings2", path = "../settings2" }
-theme = { package = "theme2", path = "../theme2" }
-language = { package = "language2", path = "../language2" }
-project = { package = "project2", path = "../project2" }
-workspace = { package = "workspace2", path = "../workspace2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-ui = { package = "ui2", path = "../ui2" }
-util = { path = "../util" }
-lsp = { package = "lsp2", path = "../lsp2" }
-futures.workspace = true
-serde.workspace = true
-anyhow.workspace = true
-tree-sitter.workspace = true
-
-[dev-dependencies]
-client = { package = "client2", path = "../client2", features = ["test-support"] }
-editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-util = { path = "../util", features = ["test-support"] }
-env_logger.workspace = true
-unindent.workspace = true

crates/language_tools2/src/language_tools.rs 🔗

@@ -1,15 +0,0 @@
-mod lsp_log;
-mod syntax_tree_view;
-
-#[cfg(test)]
-mod lsp_log_tests;
-
-use gpui::AppContext;
-
-pub use lsp_log::{LogStore, LspLogToolbarItemView, LspLogView};
-pub use syntax_tree_view::{SyntaxTreeToolbarItemView, SyntaxTreeView};
-
-pub fn init(cx: &mut AppContext) {
-    lsp_log::init(cx);
-    syntax_tree_view::init(cx);
-}

crates/language_tools2/src/lsp_log.rs 🔗

@@ -1,895 +0,0 @@
-use collections::{HashMap, VecDeque};
-use editor::{Editor, EditorEvent, MoveToEnd};
-use futures::{channel::mpsc, StreamExt};
-use gpui::{
-    actions, div, AnchorCorner, AnyElement, AppContext, Context, EventEmitter, FocusHandle,
-    FocusableView, IntoElement, Model, ModelContext, ParentElement, Render, Styled, Subscription,
-    View, ViewContext, VisualContext, WeakModel, WindowContext,
-};
-use language::{LanguageServerId, LanguageServerName};
-use lsp::IoKind;
-use project::{search::SearchQuery, Project};
-use std::{borrow::Cow, sync::Arc};
-use ui::{h_stack, popover_menu, Button, Checkbox, Clickable, ContextMenu, Label, Selection};
-use workspace::{
-    item::{Item, ItemHandle},
-    searchable::{SearchEvent, SearchableItem, SearchableItemHandle},
-    ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
-};
-
-const SEND_LINE: &str = "// Send:";
-const RECEIVE_LINE: &str = "// Receive:";
-const MAX_STORED_LOG_ENTRIES: usize = 2000;
-
-pub struct LogStore {
-    projects: HashMap<WeakModel<Project>, ProjectState>,
-    io_tx: mpsc::UnboundedSender<(WeakModel<Project>, LanguageServerId, IoKind, String)>,
-}
-
-struct ProjectState {
-    servers: HashMap<LanguageServerId, LanguageServerState>,
-    _subscriptions: [gpui::Subscription; 2],
-}
-
-struct LanguageServerState {
-    log_messages: VecDeque<String>,
-    rpc_state: Option<LanguageServerRpcState>,
-    _io_logs_subscription: Option<lsp::Subscription>,
-    _lsp_logs_subscription: Option<lsp::Subscription>,
-}
-
-struct LanguageServerRpcState {
-    rpc_messages: VecDeque<String>,
-    last_message_kind: Option<MessageKind>,
-}
-
-pub struct LspLogView {
-    pub(crate) editor: View<Editor>,
-    editor_subscription: Subscription,
-    log_store: Model<LogStore>,
-    current_server_id: Option<LanguageServerId>,
-    is_showing_rpc_trace: bool,
-    project: Model<Project>,
-    focus_handle: FocusHandle,
-    _log_store_subscriptions: Vec<Subscription>,
-}
-
-pub struct LspLogToolbarItemView {
-    log_view: Option<View<LspLogView>>,
-    _log_view_subscription: Option<Subscription>,
-}
-
-#[derive(Copy, Clone, PartialEq, Eq)]
-enum MessageKind {
-    Send,
-    Receive,
-}
-
-#[derive(Clone, Debug, PartialEq)]
-pub(crate) struct LogMenuItem {
-    pub server_id: LanguageServerId,
-    pub server_name: LanguageServerName,
-    pub worktree_root_name: String,
-    pub rpc_trace_enabled: bool,
-    pub rpc_trace_selected: bool,
-    pub logs_selected: bool,
-}
-
-actions!(debug, [OpenLanguageServerLogs]);
-
-pub fn init(cx: &mut AppContext) {
-    let log_store = cx.new_model(|cx| LogStore::new(cx));
-
-    cx.observe_new_views(move |workspace: &mut Workspace, cx| {
-        let project = workspace.project();
-        if project.read(cx).is_local() {
-            log_store.update(cx, |store, cx| {
-                store.add_project(&project, cx);
-            });
-        }
-
-        let log_store = log_store.clone();
-        workspace.register_action(move |workspace, _: &OpenLanguageServerLogs, cx| {
-            let project = workspace.project().read(cx);
-            if project.is_local() {
-                workspace.add_item(
-                    Box::new(cx.new_view(|cx| {
-                        LspLogView::new(workspace.project().clone(), log_store.clone(), cx)
-                    })),
-                    cx,
-                );
-            }
-        });
-    })
-    .detach();
-}
-
-impl LogStore {
-    pub fn new(cx: &mut ModelContext<Self>) -> Self {
-        let (io_tx, mut io_rx) = mpsc::unbounded();
-        let this = Self {
-            projects: HashMap::default(),
-            io_tx,
-        };
-        cx.spawn(|this, mut cx| async move {
-            while let Some((project, server_id, io_kind, message)) = io_rx.next().await {
-                if let Some(this) = this.upgrade() {
-                    this.update(&mut cx, |this, cx| {
-                        this.on_io(project, server_id, io_kind, &message, cx);
-                    })?;
-                }
-            }
-            anyhow::Ok(())
-        })
-        .detach_and_log_err(cx);
-        this
-    }
-
-    pub fn add_project(&mut self, project: &Model<Project>, cx: &mut ModelContext<Self>) {
-        let weak_project = project.downgrade();
-        self.projects.insert(
-            project.downgrade(),
-            ProjectState {
-                servers: HashMap::default(),
-                _subscriptions: [
-                    cx.observe_release(project, move |this, _, _| {
-                        this.projects.remove(&weak_project);
-                    }),
-                    cx.subscribe(project, |this, project, event, cx| match event {
-                        project::Event::LanguageServerAdded(id) => {
-                            this.add_language_server(&project, *id, cx);
-                        }
-                        project::Event::LanguageServerRemoved(id) => {
-                            this.remove_language_server(&project, *id, cx);
-                        }
-                        project::Event::LanguageServerLog(id, message) => {
-                            this.add_language_server_log(&project, *id, message, cx);
-                        }
-                        _ => {}
-                    }),
-                ],
-            },
-        );
-    }
-
-    fn add_language_server(
-        &mut self,
-        project: &Model<Project>,
-        id: LanguageServerId,
-        cx: &mut ModelContext<Self>,
-    ) -> Option<&mut LanguageServerState> {
-        let project_state = self.projects.get_mut(&project.downgrade())?;
-        let server_state = project_state.servers.entry(id).or_insert_with(|| {
-            cx.notify();
-            LanguageServerState {
-                rpc_state: None,
-                log_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
-                _io_logs_subscription: None,
-                _lsp_logs_subscription: None,
-            }
-        });
-
-        let server = project.read(cx).language_server_for_id(id);
-        if let Some(server) = server.as_deref() {
-            if server.has_notification_handler::<lsp::notification::LogMessage>() {
-                // Another event wants to re-add the server that was already added and subscribed to, avoid doing it again.
-                return Some(server_state);
-            }
-        }
-
-        let weak_project = project.downgrade();
-        let io_tx = self.io_tx.clone();
-        server_state._io_logs_subscription = server.as_ref().map(|server| {
-            server.on_io(move |io_kind, message| {
-                io_tx
-                    .unbounded_send((weak_project.clone(), id, io_kind, message.to_string()))
-                    .ok();
-            })
-        });
-        let this = cx.handle().downgrade();
-        let weak_project = project.downgrade();
-        server_state._lsp_logs_subscription = server.map(|server| {
-            let server_id = server.server_id();
-            server.on_notification::<lsp::notification::LogMessage, _>({
-                move |params, mut cx| {
-                    if let Some((project, this)) = weak_project.upgrade().zip(this.upgrade()) {
-                        this.update(&mut cx, |this, cx| {
-                            this.add_language_server_log(&project, server_id, &params.message, cx);
-                        })
-                        .ok();
-                    }
-                }
-            })
-        });
-        Some(server_state)
-    }
-
-    fn add_language_server_log(
-        &mut self,
-        project: &Model<Project>,
-        id: LanguageServerId,
-        message: &str,
-        cx: &mut ModelContext<Self>,
-    ) -> Option<()> {
-        let language_server_state = match self
-            .projects
-            .get_mut(&project.downgrade())?
-            .servers
-            .get_mut(&id)
-        {
-            Some(existing_state) => existing_state,
-            None => self.add_language_server(&project, id, cx)?,
-        };
-
-        let log_lines = &mut language_server_state.log_messages;
-        while log_lines.len() >= MAX_STORED_LOG_ENTRIES {
-            log_lines.pop_front();
-        }
-        let message = message.trim();
-        log_lines.push_back(message.to_string());
-        cx.emit(Event::NewServerLogEntry {
-            id,
-            entry: message.to_string(),
-            is_rpc: false,
-        });
-        cx.notify();
-        Some(())
-    }
-
-    fn remove_language_server(
-        &mut self,
-        project: &Model<Project>,
-        id: LanguageServerId,
-        cx: &mut ModelContext<Self>,
-    ) -> Option<()> {
-        let project_state = self.projects.get_mut(&project.downgrade())?;
-        project_state.servers.remove(&id);
-        cx.notify();
-        Some(())
-    }
-
-    fn server_logs(
-        &self,
-        project: &Model<Project>,
-        server_id: LanguageServerId,
-    ) -> Option<&VecDeque<String>> {
-        let weak_project = project.downgrade();
-        let project_state = self.projects.get(&weak_project)?;
-        let server_state = project_state.servers.get(&server_id)?;
-        Some(&server_state.log_messages)
-    }
-
-    fn enable_rpc_trace_for_language_server(
-        &mut self,
-        project: &Model<Project>,
-        server_id: LanguageServerId,
-    ) -> Option<&mut LanguageServerRpcState> {
-        let weak_project = project.downgrade();
-        let project_state = self.projects.get_mut(&weak_project)?;
-        let server_state = project_state.servers.get_mut(&server_id)?;
-        let rpc_state = server_state
-            .rpc_state
-            .get_or_insert_with(|| LanguageServerRpcState {
-                rpc_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
-                last_message_kind: None,
-            });
-        Some(rpc_state)
-    }
-
-    pub fn disable_rpc_trace_for_language_server(
-        &mut self,
-        project: &Model<Project>,
-        server_id: LanguageServerId,
-        _: &mut ModelContext<Self>,
-    ) -> Option<()> {
-        let project = project.downgrade();
-        let project_state = self.projects.get_mut(&project)?;
-        let server_state = project_state.servers.get_mut(&server_id)?;
-        server_state.rpc_state.take();
-        Some(())
-    }
-
-    fn on_io(
-        &mut self,
-        project: WeakModel<Project>,
-        language_server_id: LanguageServerId,
-        io_kind: IoKind,
-        message: &str,
-        cx: &mut ModelContext<Self>,
-    ) -> Option<()> {
-        let is_received = match io_kind {
-            IoKind::StdOut => true,
-            IoKind::StdIn => false,
-            IoKind::StdErr => {
-                let project = project.upgrade()?;
-                let message = format!("stderr: {}", message.trim());
-                self.add_language_server_log(&project, language_server_id, &message, cx);
-                return Some(());
-            }
-        };
-
-        let state = self
-            .projects
-            .get_mut(&project)?
-            .servers
-            .get_mut(&language_server_id)?
-            .rpc_state
-            .as_mut()?;
-        let kind = if is_received {
-            MessageKind::Receive
-        } else {
-            MessageKind::Send
-        };
-
-        let rpc_log_lines = &mut state.rpc_messages;
-        if state.last_message_kind != Some(kind) {
-            let line_before_message = match kind {
-                MessageKind::Send => SEND_LINE,
-                MessageKind::Receive => RECEIVE_LINE,
-            };
-            rpc_log_lines.push_back(line_before_message.to_string());
-            cx.emit(Event::NewServerLogEntry {
-                id: language_server_id,
-                entry: line_before_message.to_string(),
-                is_rpc: true,
-            });
-        }
-
-        while rpc_log_lines.len() >= MAX_STORED_LOG_ENTRIES {
-            rpc_log_lines.pop_front();
-        }
-        let message = message.trim();
-        rpc_log_lines.push_back(message.to_string());
-        cx.emit(Event::NewServerLogEntry {
-            id: language_server_id,
-            entry: message.to_string(),
-            is_rpc: true,
-        });
-        cx.notify();
-        Some(())
-    }
-}
-
-impl LspLogView {
-    pub fn new(
-        project: Model<Project>,
-        log_store: Model<LogStore>,
-        cx: &mut ViewContext<Self>,
-    ) -> Self {
-        let server_id = log_store
-            .read(cx)
-            .projects
-            .get(&project.downgrade())
-            .and_then(|project| project.servers.keys().copied().next());
-        let model_changes_subscription = cx.observe(&log_store, |this, store, cx| {
-            (|| -> Option<()> {
-                let project_state = store.read(cx).projects.get(&this.project.downgrade())?;
-                if let Some(current_lsp) = this.current_server_id {
-                    if !project_state.servers.contains_key(&current_lsp) {
-                        if let Some(server) = project_state.servers.iter().next() {
-                            if this.is_showing_rpc_trace {
-                                this.show_rpc_trace_for_server(*server.0, cx)
-                            } else {
-                                this.show_logs_for_server(*server.0, cx)
-                            }
-                        } else {
-                            this.current_server_id = None;
-                            this.editor.update(cx, |editor, cx| {
-                                editor.set_read_only(false);
-                                editor.clear(cx);
-                                editor.set_read_only(true);
-                            });
-                            cx.notify();
-                        }
-                    }
-                } else {
-                    if let Some(server) = project_state.servers.iter().next() {
-                        if this.is_showing_rpc_trace {
-                            this.show_rpc_trace_for_server(*server.0, cx)
-                        } else {
-                            this.show_logs_for_server(*server.0, cx)
-                        }
-                    }
-                }
-
-                Some(())
-            })();
-
-            cx.notify();
-        });
-        let events_subscriptions = cx.subscribe(&log_store, |log_view, _, e, cx| match e {
-            Event::NewServerLogEntry { id, entry, is_rpc } => {
-                if log_view.current_server_id == Some(*id) {
-                    if (*is_rpc && log_view.is_showing_rpc_trace)
-                        || (!*is_rpc && !log_view.is_showing_rpc_trace)
-                    {
-                        log_view.editor.update(cx, |editor, cx| {
-                            editor.set_read_only(false);
-                            editor.handle_input(entry.trim(), cx);
-                            editor.handle_input("\n", cx);
-                            editor.set_read_only(true);
-                        });
-                    }
-                }
-            }
-        });
-        let (editor, editor_subscription) = Self::editor_for_logs(String::new(), cx);
-
-        let focus_handle = cx.focus_handle();
-        let focus_subscription = cx.on_focus(&focus_handle, |log_view, cx| {
-            cx.focus_view(&log_view.editor);
-        });
-
-        let mut this = Self {
-            focus_handle,
-            editor,
-            editor_subscription,
-            project,
-            log_store,
-            current_server_id: None,
-            is_showing_rpc_trace: false,
-            _log_store_subscriptions: vec![
-                model_changes_subscription,
-                events_subscriptions,
-                focus_subscription,
-            ],
-        };
-        if let Some(server_id) = server_id {
-            this.show_logs_for_server(server_id, cx);
-        }
-        this
-    }
-
-    fn editor_for_logs(
-        log_contents: String,
-        cx: &mut ViewContext<Self>,
-    ) -> (View<Editor>, Subscription) {
-        let editor = cx.new_view(|cx| {
-            let mut editor = Editor::multi_line(cx);
-            editor.set_text(log_contents, cx);
-            editor.move_to_end(&MoveToEnd, cx);
-            editor.set_read_only(true);
-            editor
-        });
-        let editor_subscription = cx.subscribe(
-            &editor,
-            |_, _, event: &EditorEvent, cx: &mut ViewContext<'_, LspLogView>| {
-                cx.emit(event.clone())
-            },
-        );
-        (editor, editor_subscription)
-    }
-
-    pub(crate) fn menu_items<'a>(&'a self, cx: &'a AppContext) -> Option<Vec<LogMenuItem>> {
-        let log_store = self.log_store.read(cx);
-        let state = log_store.projects.get(&self.project.downgrade())?;
-        let mut rows = self
-            .project
-            .read(cx)
-            .language_servers()
-            .filter_map(|(server_id, language_server_name, worktree_id)| {
-                let worktree = self.project.read(cx).worktree_for_id(worktree_id, cx)?;
-                let state = state.servers.get(&server_id)?;
-                Some(LogMenuItem {
-                    server_id,
-                    server_name: language_server_name,
-                    worktree_root_name: worktree.read(cx).root_name().to_string(),
-                    rpc_trace_enabled: state.rpc_state.is_some(),
-                    rpc_trace_selected: self.is_showing_rpc_trace
-                        && self.current_server_id == Some(server_id),
-                    logs_selected: !self.is_showing_rpc_trace
-                        && self.current_server_id == Some(server_id),
-                })
-            })
-            .chain(
-                self.project
-                    .read(cx)
-                    .supplementary_language_servers()
-                    .filter_map(|(&server_id, (name, _))| {
-                        let state = state.servers.get(&server_id)?;
-                        Some(LogMenuItem {
-                            server_id,
-                            server_name: name.clone(),
-                            worktree_root_name: "supplementary".to_string(),
-                            rpc_trace_enabled: state.rpc_state.is_some(),
-                            rpc_trace_selected: self.is_showing_rpc_trace
-                                && self.current_server_id == Some(server_id),
-                            logs_selected: !self.is_showing_rpc_trace
-                                && self.current_server_id == Some(server_id),
-                        })
-                    }),
-            )
-            .collect::<Vec<_>>();
-        rows.sort_by_key(|row| row.server_id);
-        rows.dedup_by_key(|row| row.server_id);
-        Some(rows)
-    }
-
-    fn show_logs_for_server(&mut self, server_id: LanguageServerId, cx: &mut ViewContext<Self>) {
-        let log_contents = self
-            .log_store
-            .read(cx)
-            .server_logs(&self.project, server_id)
-            .map(log_contents);
-        if let Some(log_contents) = log_contents {
-            self.current_server_id = Some(server_id);
-            self.is_showing_rpc_trace = false;
-            let (editor, editor_subscription) = Self::editor_for_logs(log_contents, cx);
-            self.editor = editor;
-            self.editor_subscription = editor_subscription;
-            cx.notify();
-        }
-        cx.focus(&self.focus_handle);
-    }
-
-    fn show_rpc_trace_for_server(
-        &mut self,
-        server_id: LanguageServerId,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let rpc_log = self.log_store.update(cx, |log_store, _| {
-            log_store
-                .enable_rpc_trace_for_language_server(&self.project, server_id)
-                .map(|state| log_contents(&state.rpc_messages))
-        });
-        if let Some(rpc_log) = rpc_log {
-            self.current_server_id = Some(server_id);
-            self.is_showing_rpc_trace = true;
-            let (editor, editor_subscription) = Self::editor_for_logs(rpc_log, cx);
-            let language = self.project.read(cx).languages().language_for_name("JSON");
-            editor
-                .read(cx)
-                .buffer()
-                .read(cx)
-                .as_singleton()
-                .expect("log buffer should be a singleton")
-                .update(cx, |_, cx| {
-                    cx.spawn({
-                        let buffer = cx.handle();
-                        |_, mut cx| async move {
-                            let language = language.await.ok();
-                            buffer.update(&mut cx, |buffer, cx| {
-                                buffer.set_language(language, cx);
-                            })
-                        }
-                    })
-                    .detach_and_log_err(cx);
-                });
-
-            self.editor = editor;
-            self.editor_subscription = editor_subscription;
-            cx.notify();
-        }
-
-        cx.focus(&self.focus_handle);
-    }
-
-    fn toggle_rpc_trace_for_server(
-        &mut self,
-        server_id: LanguageServerId,
-        enabled: bool,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.log_store.update(cx, |log_store, cx| {
-            if enabled {
-                log_store.enable_rpc_trace_for_language_server(&self.project, server_id);
-            } else {
-                log_store.disable_rpc_trace_for_language_server(&self.project, server_id, cx);
-            }
-        });
-        if !enabled && Some(server_id) == self.current_server_id {
-            self.show_logs_for_server(server_id, cx);
-            cx.notify();
-        }
-    }
-}
-
-fn log_contents(lines: &VecDeque<String>) -> String {
-    let (a, b) = lines.as_slices();
-    let log_contents = a.join("\n");
-    if b.is_empty() {
-        log_contents
-    } else {
-        log_contents + "\n" + &b.join("\n")
-    }
-}
-
-impl Render for LspLogView {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        self.editor
-            .update(cx, |editor, cx| editor.render(cx).into_any_element())
-    }
-}
-
-impl FocusableView for LspLogView {
-    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
-        self.focus_handle.clone()
-    }
-}
-
-impl Item for LspLogView {
-    type Event = EditorEvent;
-
-    fn to_item_events(event: &Self::Event, f: impl FnMut(workspace::item::ItemEvent)) {
-        Editor::to_item_events(event, f)
-    }
-
-    fn tab_content(&self, _: Option<usize>, _: bool, _: &WindowContext<'_>) -> AnyElement {
-        Label::new("LSP Logs").into_any_element()
-    }
-
-    fn as_searchable(&self, handle: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
-        Some(Box::new(handle.clone()))
-    }
-}
-
-impl SearchableItem for LspLogView {
-    type Match = <Editor as SearchableItem>::Match;
-
-    fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
-        self.editor.update(cx, |e, cx| e.clear_matches(cx))
-    }
-
-    fn update_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) {
-        self.editor
-            .update(cx, |e, cx| e.update_matches(matches, cx))
-    }
-
-    fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
-        self.editor.update(cx, |e, cx| e.query_suggestion(cx))
-    }
-
-    fn activate_match(
-        &mut self,
-        index: usize,
-        matches: Vec<Self::Match>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.editor
-            .update(cx, |e, cx| e.activate_match(index, matches, cx))
-    }
-
-    fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) {
-        self.editor
-            .update(cx, |e, cx| e.select_matches(matches, cx))
-    }
-
-    fn find_matches(
-        &mut self,
-        query: Arc<project::search::SearchQuery>,
-        cx: &mut ViewContext<Self>,
-    ) -> gpui::Task<Vec<Self::Match>> {
-        self.editor.update(cx, |e, cx| e.find_matches(query, cx))
-    }
-
-    fn replace(&mut self, _: &Self::Match, _: &SearchQuery, _: &mut ViewContext<Self>) {
-        // Since LSP Log is read-only, it doesn't make sense to support replace operation.
-    }
-    fn supported_options() -> workspace::searchable::SearchOptions {
-        workspace::searchable::SearchOptions {
-            case: true,
-            word: true,
-            regex: true,
-            // LSP log is read-only.
-            replacement: false,
-        }
-    }
-    fn active_match_index(
-        &mut self,
-        matches: Vec<Self::Match>,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<usize> {
-        self.editor
-            .update(cx, |e, cx| e.active_match_index(matches, cx))
-    }
-}
-
-impl EventEmitter<ToolbarItemEvent> for LspLogToolbarItemView {}
-
-impl ToolbarItemView for LspLogToolbarItemView {
-    fn set_active_pane_item(
-        &mut self,
-        active_pane_item: Option<&dyn ItemHandle>,
-        cx: &mut ViewContext<Self>,
-    ) -> workspace::ToolbarItemLocation {
-        if let Some(item) = active_pane_item {
-            if let Some(log_view) = item.downcast::<LspLogView>() {
-                self.log_view = Some(log_view.clone());
-                self._log_view_subscription = Some(cx.observe(&log_view, |_, _, cx| {
-                    cx.notify();
-                }));
-                return ToolbarItemLocation::PrimaryLeft;
-            }
-        }
-        self.log_view = None;
-        self._log_view_subscription = None;
-        ToolbarItemLocation::Hidden
-    }
-}
-
-impl Render for LspLogToolbarItemView {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        let Some(log_view) = self.log_view.clone() else {
-            return div();
-        };
-        let (menu_rows, current_server_id) = log_view.update(cx, |log_view, cx| {
-            let menu_rows = log_view.menu_items(cx).unwrap_or_default();
-            let current_server_id = log_view.current_server_id;
-            (menu_rows, current_server_id)
-        });
-
-        let current_server = current_server_id.and_then(|current_server_id| {
-            if let Ok(ix) = menu_rows.binary_search_by_key(&current_server_id, |e| e.server_id) {
-                Some(menu_rows[ix].clone())
-            } else {
-                None
-            }
-        });
-
-        let log_toolbar_view = cx.view().clone();
-        let lsp_menu = popover_menu("LspLogView")
-            .anchor(AnchorCorner::TopLeft)
-            .trigger(Button::new(
-                "language_server_menu_header",
-                current_server
-                    .and_then(|row| {
-                        Some(Cow::Owned(format!(
-                            "{} ({}) - {}",
-                            row.server_name.0,
-                            row.worktree_root_name,
-                            if row.rpc_trace_selected {
-                                RPC_MESSAGES
-                            } else {
-                                SERVER_LOGS
-                            },
-                        )))
-                    })
-                    .unwrap_or_else(|| "No server selected".into()),
-            ))
-            .menu(move |cx| {
-                let menu_rows = menu_rows.clone();
-                let log_view = log_view.clone();
-                let log_toolbar_view = log_toolbar_view.clone();
-                ContextMenu::build(cx, move |mut menu, cx| {
-                    for (ix, row) in menu_rows.into_iter().enumerate() {
-                        let server_selected = Some(row.server_id) == current_server_id;
-                        menu = menu
-                            .header(format!(
-                                "{} ({})",
-                                row.server_name.0, row.worktree_root_name
-                            ))
-                            .entry(
-                                SERVER_LOGS,
-                                None,
-                                cx.handler_for(&log_view, move |view, cx| {
-                                    view.show_logs_for_server(row.server_id, cx);
-                                }),
-                            );
-                        if server_selected && row.logs_selected {
-                            debug_assert_eq!(
-                                Some(ix * 3 + 1),
-                                menu.select_last(),
-                                "Could not scroll to a just added LSP menu item"
-                            );
-                        }
-
-                        menu = menu.custom_entry(
-                            {
-                                let log_toolbar_view = log_toolbar_view.clone();
-                                move |cx| {
-                                    h_stack()
-                                        .w_full()
-                                        .justify_between()
-                                        .child(Label::new(RPC_MESSAGES))
-                                        .child(
-                                            div().z_index(120).child(
-                                                Checkbox::new(
-                                                    ix,
-                                                    if row.rpc_trace_enabled {
-                                                        Selection::Selected
-                                                    } else {
-                                                        Selection::Unselected
-                                                    },
-                                                )
-                                                .on_click(cx.listener_for(
-                                                    &log_toolbar_view,
-                                                    move |view, selection, cx| {
-                                                        let enabled = matches!(
-                                                            selection,
-                                                            Selection::Selected
-                                                        );
-                                                        view.toggle_logging_for_server(
-                                                            row.server_id,
-                                                            enabled,
-                                                            cx,
-                                                        );
-                                                        cx.stop_propagation();
-                                                    },
-                                                )),
-                                            ),
-                                        )
-                                        .into_any_element()
-                                }
-                            },
-                            cx.handler_for(&log_view, move |view, cx| {
-                                view.show_rpc_trace_for_server(row.server_id, cx);
-                            }),
-                        );
-                        if server_selected && row.rpc_trace_selected {
-                            debug_assert_eq!(
-                                Some(ix * 3 + 2),
-                                menu.select_last(),
-                                "Could not scroll to a just added LSP menu item"
-                            );
-                        }
-                    }
-                    menu
-                })
-                .into()
-            });
-
-        h_stack().size_full().child(lsp_menu).child(
-            div()
-                .child(
-                    Button::new("clear_log_button", "Clear").on_click(cx.listener(
-                        |this, _, cx| {
-                            if let Some(log_view) = this.log_view.as_ref() {
-                                log_view.update(cx, |log_view, cx| {
-                                    log_view.editor.update(cx, |editor, cx| {
-                                        editor.set_read_only(false);
-                                        editor.clear(cx);
-                                        editor.set_read_only(true);
-                                    });
-                                })
-                            }
-                        },
-                    )),
-                )
-                .ml_2(),
-        )
-    }
-}
-
-const RPC_MESSAGES: &str = "RPC Messages";
-const SERVER_LOGS: &str = "Server Logs";
-
-impl LspLogToolbarItemView {
-    pub fn new() -> Self {
-        Self {
-            log_view: None,
-            _log_view_subscription: None,
-        }
-    }
-
-    fn toggle_logging_for_server(
-        &mut self,
-        id: LanguageServerId,
-        enabled: bool,
-        cx: &mut ViewContext<Self>,
-    ) {
-        if let Some(log_view) = &self.log_view {
-            log_view.update(cx, |log_view, cx| {
-                log_view.toggle_rpc_trace_for_server(id, enabled, cx);
-                if !enabled && Some(id) == log_view.current_server_id {
-                    log_view.show_logs_for_server(id, cx);
-                    cx.notify();
-                }
-                cx.focus(&log_view.focus_handle);
-            });
-        }
-        cx.notify();
-    }
-}
-
-pub enum Event {
-    NewServerLogEntry {
-        id: LanguageServerId,
-        entry: String,
-        is_rpc: bool,
-    },
-}
-
-impl EventEmitter<Event> for LogStore {}
-impl EventEmitter<Event> for LspLogView {}
-impl EventEmitter<EditorEvent> for LspLogView {}
-impl EventEmitter<SearchEvent> for LspLogView {}

crates/language_tools2/src/lsp_log_tests.rs 🔗

@@ -1,107 +0,0 @@
-use std::sync::Arc;
-
-use crate::lsp_log::LogMenuItem;
-
-use super::*;
-use futures::StreamExt;
-use gpui::{serde_json::json, Context, TestAppContext, VisualTestContext};
-use language::{tree_sitter_rust, FakeLspAdapter, Language, LanguageConfig, LanguageServerName};
-use project::{FakeFs, Project};
-use settings::SettingsStore;
-
-#[gpui::test]
-async fn test_lsp_logs(cx: &mut TestAppContext) {
-    if std::env::var("RUST_LOG").is_ok() {
-        env_logger::init();
-    }
-
-    init_test(cx);
-
-    let mut rust_language = Language::new(
-        LanguageConfig {
-            name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
-            ..Default::default()
-        },
-        Some(tree_sitter_rust::language()),
-    );
-    let mut fake_rust_servers = rust_language
-        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-            name: "the-rust-language-server",
-            ..Default::default()
-        }))
-        .await;
-
-    let fs = FakeFs::new(cx.background_executor.clone());
-    fs.insert_tree(
-        "/the-root",
-        json!({
-            "test.rs": "",
-            "package.json": "",
-        }),
-    )
-    .await;
-    let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;
-    project.update(cx, |project, _| {
-        project.languages().add(Arc::new(rust_language));
-    });
-
-    let log_store = cx.new_model(|cx| LogStore::new(cx));
-    log_store.update(cx, |store, cx| store.add_project(&project, cx));
-
-    let _rust_buffer = project
-        .update(cx, |project, cx| {
-            project.open_local_buffer("/the-root/test.rs", cx)
-        })
-        .await
-        .unwrap();
-
-    let mut language_server = fake_rust_servers.next().await.unwrap();
-    language_server
-        .receive_notification::<lsp::notification::DidOpenTextDocument>()
-        .await;
-
-    let window = cx.add_window(|cx| LspLogView::new(project.clone(), log_store.clone(), cx));
-    let log_view = window.root(cx).unwrap();
-    let mut cx = VisualTestContext::from_window(*window, cx);
-
-    language_server.notify::<lsp::notification::LogMessage>(lsp::LogMessageParams {
-        message: "hello from the server".into(),
-        typ: lsp::MessageType::INFO,
-    });
-    cx.executor().run_until_parked();
-
-    log_view.update(&mut cx, |view, cx| {
-        assert_eq!(
-            view.menu_items(cx).unwrap(),
-            &[LogMenuItem {
-                server_id: language_server.server.server_id(),
-                server_name: LanguageServerName("the-rust-language-server".into()),
-                worktree_root_name: project
-                    .read(cx)
-                    .worktrees()
-                    .next()
-                    .unwrap()
-                    .read(cx)
-                    .root_name()
-                    .to_string(),
-                rpc_trace_enabled: false,
-                rpc_trace_selected: false,
-                logs_selected: true,
-            }]
-        );
-        assert_eq!(view.editor.read(cx).text(cx), "hello from the server\n");
-    });
-}
-
-fn init_test(cx: &mut gpui::TestAppContext) {
-    cx.update(|cx| {
-        let settings_store = SettingsStore::test(cx);
-        cx.set_global(settings_store);
-        theme::init(theme::LoadThemes::JustBase, cx);
-        language::init(cx);
-        client::init_settings(cx);
-        Project::init_settings(cx);
-        editor::init_settings(cx);
-    });
-}

crates/language_tools2/src/syntax_tree_view.rs 🔗

@@ -1,533 +0,0 @@
-use editor::{scroll::autoscroll::Autoscroll, Anchor, Editor, ExcerptId};
-use gpui::{
-    actions, canvas, div, rems, uniform_list, AnyElement, AppContext, AvailableSpace, Div,
-    EventEmitter, FocusHandle, FocusableView, Hsla, InteractiveElement, IntoElement, Model,
-    MouseButton, MouseDownEvent, MouseMoveEvent, ParentElement, Pixels, Render, Styled,
-    UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WindowContext,
-};
-use language::{Buffer, OwnedSyntaxLayerInfo};
-use settings::Settings;
-use std::{mem, ops::Range};
-use theme::{ActiveTheme, ThemeSettings};
-use tree_sitter::{Node, TreeCursor};
-use ui::{h_stack, popover_menu, ButtonLike, Color, ContextMenu, Label, LabelCommon, PopoverMenu};
-use workspace::{
-    item::{Item, ItemHandle},
-    SplitDirection, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
-};
-
-actions!(debug, [OpenSyntaxTreeView]);
-
-pub fn init(cx: &mut AppContext) {
-    cx.observe_new_views(|workspace: &mut Workspace, _| {
-        workspace.register_action(|workspace, _: &OpenSyntaxTreeView, cx| {
-            let active_item = workspace.active_item(cx);
-            let workspace_handle = workspace.weak_handle();
-            let syntax_tree_view =
-                cx.new_view(|cx| SyntaxTreeView::new(workspace_handle, active_item, cx));
-            workspace.split_item(SplitDirection::Right, Box::new(syntax_tree_view), cx)
-        });
-    })
-    .detach();
-}
-
-pub struct SyntaxTreeView {
-    workspace_handle: WeakView<Workspace>,
-    editor: Option<EditorState>,
-    mouse_y: Option<Pixels>,
-    line_height: Option<Pixels>,
-    list_scroll_handle: UniformListScrollHandle,
-    selected_descendant_ix: Option<usize>,
-    hovered_descendant_ix: Option<usize>,
-    focus_handle: FocusHandle,
-}
-
-pub struct SyntaxTreeToolbarItemView {
-    tree_view: Option<View<SyntaxTreeView>>,
-    subscription: Option<gpui::Subscription>,
-}
-
-struct EditorState {
-    editor: View<Editor>,
-    active_buffer: Option<BufferState>,
-    _subscription: gpui::Subscription,
-}
-
-#[derive(Clone)]
-struct BufferState {
-    buffer: Model<Buffer>,
-    excerpt_id: ExcerptId,
-    active_layer: Option<OwnedSyntaxLayerInfo>,
-}
-
-impl SyntaxTreeView {
-    pub fn new(
-        workspace_handle: WeakView<Workspace>,
-        active_item: Option<Box<dyn ItemHandle>>,
-        cx: &mut ViewContext<Self>,
-    ) -> Self {
-        let mut this = Self {
-            workspace_handle: workspace_handle.clone(),
-            list_scroll_handle: UniformListScrollHandle::new(),
-            editor: None,
-            mouse_y: None,
-            line_height: None,
-            hovered_descendant_ix: None,
-            selected_descendant_ix: None,
-            focus_handle: cx.focus_handle(),
-        };
-
-        this.workspace_updated(active_item, cx);
-        cx.observe(
-            &workspace_handle.upgrade().unwrap(),
-            |this, workspace, cx| {
-                this.workspace_updated(workspace.read(cx).active_item(cx), cx);
-            },
-        )
-        .detach();
-
-        this
-    }
-
-    fn workspace_updated(
-        &mut self,
-        active_item: Option<Box<dyn ItemHandle>>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        if let Some(item) = active_item {
-            if item.item_id() != cx.entity_id() {
-                if let Some(editor) = item.act_as::<Editor>(cx) {
-                    self.set_editor(editor, cx);
-                }
-            }
-        }
-    }
-
-    fn set_editor(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
-        if let Some(state) = &self.editor {
-            if state.editor == editor {
-                return;
-            }
-            editor.update(cx, |editor, cx| {
-                editor.clear_background_highlights::<Self>(cx)
-            });
-        }
-
-        let subscription = cx.subscribe(&editor, |this, _, event, cx| {
-            let did_reparse = match event {
-                editor::EditorEvent::Reparsed => true,
-                editor::EditorEvent::SelectionsChanged { .. } => false,
-                _ => return,
-            };
-            this.editor_updated(did_reparse, cx);
-        });
-
-        self.editor = Some(EditorState {
-            editor,
-            _subscription: subscription,
-            active_buffer: None,
-        });
-        self.editor_updated(true, cx);
-    }
-
-    fn editor_updated(&mut self, did_reparse: bool, cx: &mut ViewContext<Self>) -> Option<()> {
-        // Find which excerpt the cursor is in, and the position within that excerpted buffer.
-        let editor_state = self.editor.as_mut()?;
-        let editor = &editor_state.editor.read(cx);
-        let selection_range = editor.selections.last::<usize>(cx).range();
-        let multibuffer = editor.buffer().read(cx);
-        let (buffer, range, excerpt_id) = multibuffer
-            .range_to_buffer_ranges(selection_range, cx)
-            .pop()?;
-
-        // If the cursor has moved into a different excerpt, retrieve a new syntax layer
-        // from that buffer.
-        let buffer_state = editor_state
-            .active_buffer
-            .get_or_insert_with(|| BufferState {
-                buffer: buffer.clone(),
-                excerpt_id,
-                active_layer: None,
-            });
-        let mut prev_layer = None;
-        if did_reparse {
-            prev_layer = buffer_state.active_layer.take();
-        }
-        if buffer_state.buffer != buffer || buffer_state.excerpt_id != buffer_state.excerpt_id {
-            buffer_state.buffer = buffer.clone();
-            buffer_state.excerpt_id = excerpt_id;
-            buffer_state.active_layer = None;
-        }
-
-        let layer = match &mut buffer_state.active_layer {
-            Some(layer) => layer,
-            None => {
-                let snapshot = buffer.read(cx).snapshot();
-                let layer = if let Some(prev_layer) = prev_layer {
-                    let prev_range = prev_layer.node().byte_range();
-                    snapshot
-                        .syntax_layers()
-                        .filter(|layer| layer.language == &prev_layer.language)
-                        .min_by_key(|layer| {
-                            let range = layer.node().byte_range();
-                            ((range.start as i64) - (prev_range.start as i64)).abs()
-                                + ((range.end as i64) - (prev_range.end as i64)).abs()
-                        })?
-                } else {
-                    snapshot.syntax_layers().next()?
-                };
-                buffer_state.active_layer.insert(layer.to_owned())
-            }
-        };
-
-        // Within the active layer, find the syntax node under the cursor,
-        // and scroll to it.
-        let mut cursor = layer.node().walk();
-        while cursor.goto_first_child_for_byte(range.start).is_some() {
-            if !range.is_empty() && cursor.node().end_byte() == range.start {
-                cursor.goto_next_sibling();
-            }
-        }
-
-        // Ascend to the smallest ancestor that contains the range.
-        loop {
-            let node_range = cursor.node().byte_range();
-            if node_range.start <= range.start && node_range.end >= range.end {
-                break;
-            }
-            if !cursor.goto_parent() {
-                break;
-            }
-        }
-
-        let descendant_ix = cursor.descendant_index();
-        self.selected_descendant_ix = Some(descendant_ix);
-        self.list_scroll_handle.scroll_to_item(descendant_ix);
-
-        cx.notify();
-        Some(())
-    }
-
-    fn handle_click(&mut self, y: Pixels, cx: &mut ViewContext<SyntaxTreeView>) -> Option<()> {
-        let line_height = self.line_height?;
-        let ix = ((self.list_scroll_handle.scroll_top() + y) / line_height) as usize;
-
-        self.update_editor_with_range_for_descendant_ix(ix, cx, |editor, mut range, cx| {
-            // Put the cursor at the beginning of the node.
-            mem::swap(&mut range.start, &mut range.end);
-
-            editor.change_selections(Some(Autoscroll::newest()), cx, |selections| {
-                selections.select_ranges(vec![range]);
-            });
-        });
-        Some(())
-    }
-
-    fn hover_state_changed(&mut self, cx: &mut ViewContext<SyntaxTreeView>) {
-        if let Some((y, line_height)) = self.mouse_y.zip(self.line_height) {
-            let ix = ((self.list_scroll_handle.scroll_top() + y) / line_height) as usize;
-            if self.hovered_descendant_ix != Some(ix) {
-                self.hovered_descendant_ix = Some(ix);
-                self.update_editor_with_range_for_descendant_ix(ix, cx, |editor, range, cx| {
-                    editor.clear_background_highlights::<Self>(cx);
-                    editor.highlight_background::<Self>(
-                        vec![range],
-                        |theme| theme.editor_document_highlight_write_background,
-                        cx,
-                    );
-                });
-                cx.notify();
-            }
-        }
-    }
-
-    fn update_editor_with_range_for_descendant_ix(
-        &self,
-        descendant_ix: usize,
-        cx: &mut ViewContext<Self>,
-        mut f: impl FnMut(&mut Editor, Range<Anchor>, &mut ViewContext<Editor>),
-    ) -> Option<()> {
-        let editor_state = self.editor.as_ref()?;
-        let buffer_state = editor_state.active_buffer.as_ref()?;
-        let layer = buffer_state.active_layer.as_ref()?;
-
-        // Find the node.
-        let mut cursor = layer.node().walk();
-        cursor.goto_descendant(descendant_ix);
-        let node = cursor.node();
-        let range = node.byte_range();
-
-        // Build a text anchor range.
-        let buffer = buffer_state.buffer.read(cx);
-        let range = buffer.anchor_before(range.start)..buffer.anchor_after(range.end);
-
-        // Build a multibuffer anchor range.
-        let multibuffer = editor_state.editor.read(cx).buffer();
-        let multibuffer = multibuffer.read(cx).snapshot(cx);
-        let excerpt_id = buffer_state.excerpt_id;
-        let range = multibuffer.anchor_in_excerpt(excerpt_id, range.start)
-            ..multibuffer.anchor_in_excerpt(excerpt_id, range.end);
-
-        // Update the editor with the anchor range.
-        editor_state.editor.update(cx, |editor, cx| {
-            f(editor, range, cx);
-        });
-        Some(())
-    }
-
-    fn render_node(cursor: &TreeCursor, depth: u32, selected: bool, cx: &AppContext) -> Div {
-        let colors = cx.theme().colors();
-        let mut row = h_stack();
-        if let Some(field_name) = cursor.field_name() {
-            row = row.children([Label::new(field_name).color(Color::Info), Label::new(": ")]);
-        }
-
-        let node = cursor.node();
-        return row
-            .child(if node.is_named() {
-                Label::new(node.kind()).color(Color::Default)
-            } else {
-                Label::new(format!("\"{}\"", node.kind())).color(Color::Created)
-            })
-            .child(
-                div()
-                    .child(Label::new(format_node_range(node)).color(Color::Muted))
-                    .pl_1(),
-            )
-            .text_bg(if selected {
-                colors.element_selected
-            } else {
-                Hsla::default()
-            })
-            .pl(rems(depth as f32))
-            .hover(|style| style.bg(colors.element_hover));
-    }
-}
-
-impl Render for SyntaxTreeView {
-    fn render(&mut self, cx: &mut gpui::ViewContext<'_, Self>) -> impl IntoElement {
-        let settings = ThemeSettings::get_global(cx);
-        let line_height = cx
-            .text_style()
-            .line_height_in_pixels(settings.buffer_font_size(cx));
-        if Some(line_height) != self.line_height {
-            self.line_height = Some(line_height);
-            self.hover_state_changed(cx);
-        }
-
-        let mut rendered = div().flex_1();
-
-        if let Some(layer) = self
-            .editor
-            .as_ref()
-            .and_then(|editor| editor.active_buffer.as_ref())
-            .and_then(|buffer| buffer.active_layer.as_ref())
-        {
-            let layer = layer.clone();
-            let list = uniform_list(
-                cx.view().clone(),
-                "SyntaxTreeView",
-                layer.node().descendant_count(),
-                move |this, range, cx| {
-                    let mut items = Vec::new();
-                    let mut cursor = layer.node().walk();
-                    let mut descendant_ix = range.start as usize;
-                    cursor.goto_descendant(descendant_ix);
-                    let mut depth = cursor.depth();
-                    let mut visited_children = false;
-                    while descendant_ix < range.end {
-                        if visited_children {
-                            if cursor.goto_next_sibling() {
-                                visited_children = false;
-                            } else if cursor.goto_parent() {
-                                depth -= 1;
-                            } else {
-                                break;
-                            }
-                        } else {
-                            items.push(Self::render_node(
-                                &cursor,
-                                depth,
-                                Some(descendant_ix) == this.selected_descendant_ix,
-                                cx,
-                            ));
-                            descendant_ix += 1;
-                            if cursor.goto_first_child() {
-                                depth += 1;
-                            } else {
-                                visited_children = true;
-                            }
-                        }
-                    }
-                    items
-                },
-            )
-            .size_full()
-            .track_scroll(self.list_scroll_handle.clone())
-            .on_mouse_move(cx.listener(move |tree_view, event: &MouseMoveEvent, cx| {
-                tree_view.mouse_y = Some(event.position.y);
-                tree_view.hover_state_changed(cx);
-            }))
-            .on_mouse_down(
-                MouseButton::Left,
-                cx.listener(move |tree_view, event: &MouseDownEvent, cx| {
-                    tree_view.handle_click(event.position.y, cx);
-                }),
-            )
-            .text_bg(cx.theme().colors().background);
-
-            rendered = rendered.child(
-                canvas(move |bounds, cx| {
-                    list.into_any_element().draw(
-                        bounds.origin,
-                        bounds.size.map(AvailableSpace::Definite),
-                        cx,
-                    )
-                })
-                .size_full(),
-            );
-        }
-
-        rendered
-    }
-}
-
-impl EventEmitter<()> for SyntaxTreeView {}
-
-impl FocusableView for SyntaxTreeView {
-    fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
-        self.focus_handle.clone()
-    }
-}
-
-impl Item for SyntaxTreeView {
-    type Event = ();
-
-    fn to_item_events(_: &Self::Event, _: impl FnMut(workspace::item::ItemEvent)) {}
-
-    fn tab_content(&self, _: Option<usize>, _: bool, _: &WindowContext<'_>) -> AnyElement {
-        Label::new("Syntax Tree").into_any_element()
-    }
-
-    fn clone_on_split(
-        &self,
-        _: workspace::WorkspaceId,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<View<Self>>
-    where
-        Self: Sized,
-    {
-        Some(cx.new_view(|cx| {
-            let mut clone = Self::new(self.workspace_handle.clone(), None, cx);
-            if let Some(editor) = &self.editor {
-                clone.set_editor(editor.editor.clone(), cx)
-            }
-            clone
-        }))
-    }
-}
-
-impl SyntaxTreeToolbarItemView {
-    pub fn new() -> Self {
-        Self {
-            tree_view: None,
-            subscription: None,
-        }
-    }
-
-    fn render_menu(&mut self, cx: &mut ViewContext<'_, Self>) -> Option<PopoverMenu<ContextMenu>> {
-        let tree_view = self.tree_view.as_ref()?;
-        let tree_view = tree_view.read(cx);
-
-        let editor_state = tree_view.editor.as_ref()?;
-        let buffer_state = editor_state.active_buffer.as_ref()?;
-        let active_layer = buffer_state.active_layer.clone()?;
-        let active_buffer = buffer_state.buffer.read(cx).snapshot();
-
-        let view = cx.view().clone();
-        Some(
-            popover_menu("Syntax Tree")
-                .trigger(Self::render_header(&active_layer))
-                .menu(move |cx| {
-                    ContextMenu::build(cx, |mut menu, cx| {
-                        for (layer_ix, layer) in active_buffer.syntax_layers().enumerate() {
-                            menu = menu.entry(
-                                format!(
-                                    "{} {}",
-                                    layer.language.name(),
-                                    format_node_range(layer.node())
-                                ),
-                                None,
-                                cx.handler_for(&view, move |view, cx| {
-                                    view.select_layer(layer_ix, cx);
-                                }),
-                            );
-                        }
-                        menu
-                    })
-                    .into()
-                }),
-        )
-    }
-
-    fn select_layer(&mut self, layer_ix: usize, cx: &mut ViewContext<Self>) -> Option<()> {
-        let tree_view = self.tree_view.as_ref()?;
-        tree_view.update(cx, |view, cx| {
-            let editor_state = view.editor.as_mut()?;
-            let buffer_state = editor_state.active_buffer.as_mut()?;
-            let snapshot = buffer_state.buffer.read(cx).snapshot();
-            let layer = snapshot.syntax_layers().nth(layer_ix)?;
-            buffer_state.active_layer = Some(layer.to_owned());
-            view.selected_descendant_ix = None;
-            cx.notify();
-            view.focus_handle.focus(cx);
-            Some(())
-        })
-    }
-
-    fn render_header(active_layer: &OwnedSyntaxLayerInfo) -> ButtonLike {
-        ButtonLike::new("syntax tree header")
-            .child(Label::new(active_layer.language.name()))
-            .child(Label::new(format_node_range(active_layer.node())))
-    }
-}
-
-fn format_node_range(node: Node) -> String {
-    let start = node.start_position();
-    let end = node.end_position();
-    format!(
-        "[{}:{} - {}:{}]",
-        start.row + 1,
-        start.column + 1,
-        end.row + 1,
-        end.column + 1,
-    )
-}
-
-impl Render for SyntaxTreeToolbarItemView {
-    fn render(&mut self, cx: &mut ViewContext<'_, Self>) -> impl IntoElement {
-        self.render_menu(cx)
-            .unwrap_or_else(|| popover_menu("Empty Syntax Tree"))
-    }
-}
-
-impl EventEmitter<ToolbarItemEvent> for SyntaxTreeToolbarItemView {}
-
-impl ToolbarItemView for SyntaxTreeToolbarItemView {
-    fn set_active_pane_item(
-        &mut self,
-        active_pane_item: Option<&dyn ItemHandle>,
-        cx: &mut ViewContext<Self>,
-    ) -> ToolbarItemLocation {
-        if let Some(item) = active_pane_item {
-            if let Some(view) = item.downcast::<SyntaxTreeView>() {
-                self.tree_view = Some(view.clone());
-                self.subscription = Some(cx.observe(&view, |_, _, cx| cx.notify()));
-                return ToolbarItemLocation::PrimaryLeft;
-            }
-        }
-        self.tree_view = None;
-        self.subscription = None;
-        ToolbarItemLocation::Hidden
-    }
-}

crates/live_kit_client/LiveKitBridge/Package.resolved 🔗

@@ -42,8 +42,8 @@
         "repositoryURL": "https://github.com/apple/swift-protobuf.git",
         "state": {
           "branch": null,
-          "revision": "0af9125c4eae12a4973fb66574c53a54962a9e1e",
-          "version": "1.21.0"
+          "revision": "ce20dc083ee485524b802669890291c0d8090170",
+          "version": "1.22.1"
         }
       }
     ]

crates/live_kit_client/build.rs 🔗

@@ -61,12 +61,14 @@ fn build_bridge(swift_target: &SwiftTarget) {
 
     let swift_package_root = swift_package_root();
     let swift_target_folder = swift_target_folder();
+    let swift_cache_folder = swift_cache_folder();
     if !Command::new("swift")
         .arg("build")
         .arg("--disable-automatic-resolution")
         .args(["--configuration", &env::var("PROFILE").unwrap()])
         .args(["--triple", &swift_target.target.triple])
         .args(["--build-path".into(), swift_target_folder])
+        .args(["--cache-path".into(), swift_cache_folder])
         .current_dir(&swift_package_root)
         .status()
         .unwrap()
@@ -133,9 +135,17 @@ fn swift_package_root() -> PathBuf {
 }
 
 fn swift_target_folder() -> PathBuf {
+    let target = env::var("TARGET").unwrap();
     env::current_dir()
         .unwrap()
-        .join(format!("../../target/{SWIFT_PACKAGE_NAME}"))
+        .join(format!("../../target/{target}/{SWIFT_PACKAGE_NAME}_target"))
+}
+
+fn swift_cache_folder() -> PathBuf {
+    let target = env::var("TARGET").unwrap();
+    env::current_dir()
+        .unwrap()
+        .join(format!("../../target/{target}/{SWIFT_PACKAGE_NAME}_cache"))
 }
 
 fn copy_dir(source: &Path, destination: &Path) {

crates/live_kit_client/examples/test_app.rs 🔗

@@ -1,7 +1,7 @@
-use std::time::Duration;
+use std::{sync::Arc, time::Duration};
 
 use futures::StreamExt;
-use gpui::{actions, keymap_matcher::Binding, Menu, MenuItem};
+use gpui::{actions, KeyBinding};
 use live_kit_client::{
     LocalAudioTrack, LocalVideoTrack, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate, Room,
 };
@@ -9,30 +9,32 @@ use live_kit_server::token::{self, VideoGrant};
 use log::LevelFilter;
 use simplelog::SimpleLogger;
 
-actions!(capture, [Quit]);
+actions!(live_kit_client, [Quit]);
 
 fn main() {
     SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
 
-    gpui::App::new(()).unwrap().run(|cx| {
+    gpui::App::production(Arc::new(())).run(|cx| {
         #[cfg(any(test, feature = "test-support"))]
         println!("USING TEST LIVEKIT");
 
         #[cfg(not(any(test, feature = "test-support")))]
         println!("USING REAL LIVEKIT");
 
-        cx.platform().activate(true);
-        cx.add_global_action(quit);
+        cx.activate(true);
 
-        cx.add_bindings([Binding::new("cmd-q", Quit, None)]);
-        cx.set_menus(vec![Menu {
-            name: "Zed",
-            items: vec![MenuItem::Action {
-                name: "Quit",
-                action: Box::new(Quit),
-                os_action: None,
-            }],
-        }]);
+        cx.on_action(quit);
+        cx.bind_keys([KeyBinding::new("cmd-q", Quit, None)]);
+
+        // todo!()
+        // cx.set_menus(vec![Menu {
+        //     name: "Zed",
+        //     items: vec![MenuItem::Action {
+        //         name: "Quit",
+        //         action: Box::new(Quit),
+        //         os_action: None,
+        //     }],
+        // }]);
 
         let live_kit_url = std::env::var("LIVE_KIT_URL").unwrap_or("http://localhost:7880".into());
         let live_kit_key = std::env::var("LIVE_KIT_KEY").unwrap_or("devkey".into());
@@ -100,7 +102,7 @@ fn main() {
             }
 
             println!("Pausing for 5 seconds to test audio, make some noise!");
-            let timer = cx.background().timer(Duration::from_secs(5));
+            let timer = cx.background_executor().timer(Duration::from_secs(5));
             timer.await;
             let remote_audio_track = room_b
                 .remote_audio_tracks("test-participant-1")
@@ -163,12 +165,12 @@ fn main() {
                 panic!("unexpected message");
             }
 
-            cx.platform().quit();
+            cx.update(|cx| cx.shutdown()).ok();
         })
         .detach();
     });
 }
 
 fn quit(_: &Quit, cx: &mut gpui::AppContext) {
-    cx.platform().quit();
+    cx.quit();
 }

crates/live_kit_client/src/prod.rs 🔗

@@ -17,6 +17,29 @@ use std::{
     sync::{Arc, Weak},
 };
 
+// SAFETY: Most live kit types are threadsafe:
+// https://github.com/livekit/client-sdk-swift#thread-safety
+macro_rules! pointer_type {
+    ($pointer_name:ident) => {
+        #[repr(transparent)]
+        #[derive(Copy, Clone, Debug)]
+        pub struct $pointer_name(pub *const std::ffi::c_void);
+        unsafe impl Send for $pointer_name {}
+    };
+}
+
+mod swift {
+    pointer_type!(Room);
+    pointer_type!(LocalAudioTrack);
+    pointer_type!(RemoteAudioTrack);
+    pointer_type!(LocalVideoTrack);
+    pointer_type!(RemoteVideoTrack);
+    pointer_type!(LocalTrackPublication);
+    pointer_type!(RemoteTrackPublication);
+    pointer_type!(MacOSDisplay);
+    pointer_type!(RoomDelegate);
+}
+
 extern "C" {
     fn LKRoomDelegateCreate(
         callback_data: *mut c_void,
@@ -25,8 +48,8 @@ extern "C" {
             callback_data: *mut c_void,
             publisher_id: CFStringRef,
             track_id: CFStringRef,
-            remote_track: *const c_void,
-            remote_publication: *const c_void,
+            remote_track: swift::RemoteAudioTrack,
+            remote_publication: swift::RemoteTrackPublication,
         ),
         on_did_unsubscribe_from_remote_audio_track: extern "C" fn(
             callback_data: *mut c_void,
@@ -46,49 +69,50 @@ extern "C" {
             callback_data: *mut c_void,
             publisher_id: CFStringRef,
             track_id: CFStringRef,
-            remote_track: *const c_void,
+            remote_track: swift::RemoteVideoTrack,
         ),
         on_did_unsubscribe_from_remote_video_track: extern "C" fn(
             callback_data: *mut c_void,
             publisher_id: CFStringRef,
             track_id: CFStringRef,
         ),
-    ) -> *const c_void;
+    ) -> swift::RoomDelegate;
 
-    fn LKRoomCreate(delegate: *const c_void) -> *const c_void;
+    fn LKRoomCreate(delegate: swift::RoomDelegate) -> swift::Room;
     fn LKRoomConnect(
-        room: *const c_void,
+        room: swift::Room,
         url: CFStringRef,
         token: CFStringRef,
         callback: extern "C" fn(*mut c_void, CFStringRef),
         callback_data: *mut c_void,
     );
-    fn LKRoomDisconnect(room: *const c_void);
+    fn LKRoomDisconnect(room: swift::Room);
     fn LKRoomPublishVideoTrack(
-        room: *const c_void,
-        track: *const c_void,
-        callback: extern "C" fn(*mut c_void, *mut c_void, CFStringRef),
+        room: swift::Room,
+        track: swift::LocalVideoTrack,
+        callback: extern "C" fn(*mut c_void, swift::LocalTrackPublication, CFStringRef),
         callback_data: *mut c_void,
     );
     fn LKRoomPublishAudioTrack(
-        room: *const c_void,
-        track: *const c_void,
-        callback: extern "C" fn(*mut c_void, *mut c_void, CFStringRef),
+        room: swift::Room,
+        track: swift::LocalAudioTrack,
+        callback: extern "C" fn(*mut c_void, swift::LocalTrackPublication, CFStringRef),
         callback_data: *mut c_void,
     );
-    fn LKRoomUnpublishTrack(room: *const c_void, publication: *const c_void);
+    fn LKRoomUnpublishTrack(room: swift::Room, publication: swift::LocalTrackPublication);
+
     fn LKRoomAudioTracksForRemoteParticipant(
-        room: *const c_void,
+        room: swift::Room,
         participant_id: CFStringRef,
     ) -> CFArrayRef;
 
     fn LKRoomAudioTrackPublicationsForRemoteParticipant(
-        room: *const c_void,
+        room: swift::Room,
         participant_id: CFStringRef,
     ) -> CFArrayRef;
 
     fn LKRoomVideoTracksForRemoteParticipant(
-        room: *const c_void,
+        room: swift::Room,
         participant_id: CFStringRef,
     ) -> CFArrayRef;
 
@@ -98,9 +122,9 @@ extern "C" {
         on_drop: extern "C" fn(callback_data: *mut c_void),
     ) -> *const c_void;
 
-    fn LKRemoteAudioTrackGetSid(track: *const c_void) -> CFStringRef;
-    fn LKVideoTrackAddRenderer(track: *const c_void, renderer: *const c_void);
-    fn LKRemoteVideoTrackGetSid(track: *const c_void) -> CFStringRef;
+    fn LKRemoteAudioTrackGetSid(track: swift::RemoteAudioTrack) -> CFStringRef;
+    fn LKVideoTrackAddRenderer(track: swift::RemoteVideoTrack, renderer: *const c_void);
+    fn LKRemoteVideoTrackGetSid(track: swift::RemoteVideoTrack) -> CFStringRef;
 
     fn LKDisplaySources(
         callback_data: *mut c_void,
@@ -110,25 +134,25 @@ extern "C" {
             error: CFStringRef,
         ),
     );
-    fn LKCreateScreenShareTrackForDisplay(display: *const c_void) -> *const c_void;
-    fn LKLocalAudioTrackCreateTrack() -> *const c_void;
+    fn LKCreateScreenShareTrackForDisplay(display: swift::MacOSDisplay) -> swift::LocalVideoTrack;
+    fn LKLocalAudioTrackCreateTrack() -> swift::LocalAudioTrack;
 
     fn LKLocalTrackPublicationSetMute(
-        publication: *const c_void,
+        publication: swift::LocalTrackPublication,
         muted: bool,
         on_complete: extern "C" fn(callback_data: *mut c_void, error: CFStringRef),
         callback_data: *mut c_void,
     );
 
     fn LKRemoteTrackPublicationSetEnabled(
-        publication: *const c_void,
+        publication: swift::RemoteTrackPublication,
         enabled: bool,
         on_complete: extern "C" fn(callback_data: *mut c_void, error: CFStringRef),
         callback_data: *mut c_void,
     );
 
-    fn LKRemoteTrackPublicationIsMuted(publication: *const c_void) -> bool;
-    fn LKRemoteTrackPublicationGetSid(publication: *const c_void) -> CFStringRef;
+    fn LKRemoteTrackPublicationIsMuted(publication: swift::RemoteTrackPublication) -> bool;
+    fn LKRemoteTrackPublicationGetSid(publication: swift::RemoteTrackPublication) -> CFStringRef;
 }
 
 pub type Sid = String;
@@ -140,30 +164,29 @@ pub enum ConnectionState {
 }
 
 pub struct Room {
-    native_room: *const c_void,
+    native_room: Mutex<swift::Room>,
     connection: Mutex<(
         watch::Sender<ConnectionState>,
         watch::Receiver<ConnectionState>,
     )>,
     remote_audio_track_subscribers: Mutex<Vec<mpsc::UnboundedSender<RemoteAudioTrackUpdate>>>,
     remote_video_track_subscribers: Mutex<Vec<mpsc::UnboundedSender<RemoteVideoTrackUpdate>>>,
-    _delegate: RoomDelegate,
+    _delegate: Mutex<RoomDelegate>,
 }
 
-// SAFETY: LiveKit objects are thread-safe: https://github.com/livekit/client-sdk-swift#thread-safety
-unsafe impl Send for Room {}
-unsafe impl Sync for Room {}
+trait AssertSendSync: Send {}
+impl AssertSendSync for Room {}
 
 impl Room {
     pub fn new() -> Arc<Self> {
         Arc::new_cyclic(|weak_room| {
             let delegate = RoomDelegate::new(weak_room.clone());
             Self {
-                native_room: unsafe { LKRoomCreate(delegate.native_delegate) },
+                native_room: Mutex::new(unsafe { LKRoomCreate(delegate.native_delegate) }),
                 connection: Mutex::new(watch::channel_with(ConnectionState::Disconnected)),
                 remote_audio_track_subscribers: Default::default(),
                 remote_video_track_subscribers: Default::default(),
-                _delegate: delegate,
+                _delegate: Mutex::new(delegate),
             }
         })
     }
@@ -178,7 +201,7 @@ impl Room {
         let (did_connect, tx, rx) = Self::build_done_callback();
         unsafe {
             LKRoomConnect(
-                self.native_room,
+                *self.native_room.lock(),
                 url.as_concrete_TypeRef(),
                 token.as_concrete_TypeRef(),
                 did_connect,
@@ -210,7 +233,7 @@ impl Room {
                 } else {
                     let sources = CFArray::wrap_under_get_rule(sources)
                         .into_iter()
-                        .map(|source| MacOSDisplay::new(*source))
+                        .map(|source| MacOSDisplay::new(swift::MacOSDisplay(*source)))
                         .collect();
 
                     let _ = tx.send(Ok(sources));
@@ -232,7 +255,11 @@ impl Room {
         track: LocalVideoTrack,
     ) -> impl Future<Output = Result<LocalTrackPublication>> {
         let (tx, rx) = oneshot::channel::<Result<LocalTrackPublication>>();
-        extern "C" fn callback(tx: *mut c_void, publication: *mut c_void, error: CFStringRef) {
+        extern "C" fn callback(
+            tx: *mut c_void,
+            publication: swift::LocalTrackPublication,
+            error: CFStringRef,
+        ) {
             let tx =
                 unsafe { Box::from_raw(tx as *mut oneshot::Sender<Result<LocalTrackPublication>>) };
             if error.is_null() {
@@ -244,7 +271,7 @@ impl Room {
         }
         unsafe {
             LKRoomPublishVideoTrack(
-                self.native_room,
+                *self.native_room.lock(),
                 track.0,
                 callback,
                 Box::into_raw(Box::new(tx)) as *mut c_void,
@@ -258,7 +285,11 @@ impl Room {
         track: LocalAudioTrack,
     ) -> impl Future<Output = Result<LocalTrackPublication>> {
         let (tx, rx) = oneshot::channel::<Result<LocalTrackPublication>>();
-        extern "C" fn callback(tx: *mut c_void, publication: *mut c_void, error: CFStringRef) {
+        extern "C" fn callback(
+            tx: *mut c_void,
+            publication: swift::LocalTrackPublication,
+            error: CFStringRef,
+        ) {
             let tx =
                 unsafe { Box::from_raw(tx as *mut oneshot::Sender<Result<LocalTrackPublication>>) };
             if error.is_null() {
@@ -270,7 +301,7 @@ impl Room {
         }
         unsafe {
             LKRoomPublishAudioTrack(
-                self.native_room,
+                *self.native_room.lock(),
                 track.0,
                 callback,
                 Box::into_raw(Box::new(tx)) as *mut c_void,
@@ -281,14 +312,14 @@ impl Room {
 
     pub fn unpublish_track(&self, publication: LocalTrackPublication) {
         unsafe {
-            LKRoomUnpublishTrack(self.native_room, publication.0);
+            LKRoomUnpublishTrack(*self.native_room.lock(), publication.0);
         }
     }
 
     pub fn remote_video_tracks(&self, participant_id: &str) -> Vec<Arc<RemoteVideoTrack>> {
         unsafe {
             let tracks = LKRoomVideoTracksForRemoteParticipant(
-                self.native_room,
+                *self.native_room.lock(),
                 CFString::new(participant_id).as_concrete_TypeRef(),
             );
 
@@ -299,7 +330,7 @@ impl Room {
                 tracks
                     .into_iter()
                     .map(|native_track| {
-                        let native_track = *native_track;
+                        let native_track = swift::RemoteVideoTrack(*native_track);
                         let id =
                             CFString::wrap_under_get_rule(LKRemoteVideoTrackGetSid(native_track))
                                 .to_string();
@@ -317,7 +348,7 @@ impl Room {
     pub fn remote_audio_tracks(&self, participant_id: &str) -> Vec<Arc<RemoteAudioTrack>> {
         unsafe {
             let tracks = LKRoomAudioTracksForRemoteParticipant(
-                self.native_room,
+                *self.native_room.lock(),
                 CFString::new(participant_id).as_concrete_TypeRef(),
             );
 
@@ -328,7 +359,7 @@ impl Room {
                 tracks
                     .into_iter()
                     .map(|native_track| {
-                        let native_track = *native_track;
+                        let native_track = swift::RemoteAudioTrack(*native_track);
                         let id =
                             CFString::wrap_under_get_rule(LKRemoteAudioTrackGetSid(native_track))
                                 .to_string();
@@ -349,7 +380,7 @@ impl Room {
     ) -> Vec<Arc<RemoteTrackPublication>> {
         unsafe {
             let tracks = LKRoomAudioTrackPublicationsForRemoteParticipant(
-                self.native_room,
+                *self.native_room.lock(),
                 CFString::new(participant_id).as_concrete_TypeRef(),
             );
 
@@ -360,7 +391,8 @@ impl Room {
                 tracks
                     .into_iter()
                     .map(|native_track_publication| {
-                        let native_track_publication = *native_track_publication;
+                        let native_track_publication =
+                            swift::RemoteTrackPublication(*native_track_publication);
                         Arc::new(RemoteTrackPublication::new(native_track_publication))
                     })
                     .collect()
@@ -467,28 +499,32 @@ impl Room {
             rx,
         )
     }
+
+    pub fn set_display_sources(&self, _: Vec<MacOSDisplay>) {
+        unreachable!("This is a test-only function")
+    }
 }
 
 impl Drop for Room {
     fn drop(&mut self) {
         unsafe {
-            LKRoomDisconnect(self.native_room);
-            CFRelease(self.native_room);
+            let native_room = &*self.native_room.lock();
+            LKRoomDisconnect(*native_room);
+            CFRelease(native_room.0);
         }
     }
 }
 
 struct RoomDelegate {
-    native_delegate: *const c_void,
-    weak_room: *const Room,
+    native_delegate: swift::RoomDelegate,
+    _weak_room: Weak<Room>,
 }
 
 impl RoomDelegate {
     fn new(weak_room: Weak<Room>) -> Self {
-        let weak_room = Weak::into_raw(weak_room);
         let native_delegate = unsafe {
             LKRoomDelegateCreate(
-                weak_room as *mut c_void,
+                weak_room.as_ptr() as *mut c_void,
                 Self::on_did_disconnect,
                 Self::on_did_subscribe_to_remote_audio_track,
                 Self::on_did_unsubscribe_from_remote_audio_track,
@@ -500,7 +536,7 @@ impl RoomDelegate {
         };
         Self {
             native_delegate,
-            weak_room,
+            _weak_room: weak_room,
         }
     }
 
@@ -516,8 +552,8 @@ impl RoomDelegate {
         room: *mut c_void,
         publisher_id: CFStringRef,
         track_id: CFStringRef,
-        track: *const c_void,
-        publication: *const c_void,
+        track: swift::RemoteAudioTrack,
+        publication: swift::RemoteTrackPublication,
     ) {
         let room = unsafe { Weak::from_raw(room as *mut Room) };
         let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() };
@@ -584,7 +620,7 @@ impl RoomDelegate {
         room: *mut c_void,
         publisher_id: CFStringRef,
         track_id: CFStringRef,
-        track: *const c_void,
+        track: swift::RemoteVideoTrack,
     ) {
         let room = unsafe { Weak::from_raw(room as *mut Room) };
         let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() };
@@ -614,14 +650,12 @@ impl RoomDelegate {
 impl Drop for RoomDelegate {
     fn drop(&mut self) {
         unsafe {
-            CFRelease(self.native_delegate);
-            let _ = Weak::from_raw(self.weak_room);
+            CFRelease(self.native_delegate.0);
         }
     }
 }
 
-pub struct LocalAudioTrack(*const c_void);
-unsafe impl Send for LocalAudioTrack {}
+pub struct LocalAudioTrack(swift::LocalAudioTrack);
 
 impl LocalAudioTrack {
     pub fn create() -> Self {
@@ -631,12 +665,11 @@ impl LocalAudioTrack {
 
 impl Drop for LocalAudioTrack {
     fn drop(&mut self) {
-        unsafe { CFRelease(self.0) }
+        unsafe { CFRelease(self.0 .0) }
     }
 }
 
-pub struct LocalVideoTrack(*const c_void);
-unsafe impl Send for LocalVideoTrack {}
+pub struct LocalVideoTrack(swift::LocalVideoTrack);
 
 impl LocalVideoTrack {
     pub fn screen_share_for_display(display: &MacOSDisplay) -> Self {
@@ -646,17 +679,16 @@ impl LocalVideoTrack {
 
 impl Drop for LocalVideoTrack {
     fn drop(&mut self) {
-        unsafe { CFRelease(self.0) }
+        unsafe { CFRelease(self.0 .0) }
     }
 }
 
-pub struct LocalTrackPublication(*const c_void);
-unsafe impl Send for LocalTrackPublication {}
+pub struct LocalTrackPublication(swift::LocalTrackPublication);
 
 impl LocalTrackPublication {
-    pub fn new(native_track_publication: *const c_void) -> Self {
+    pub fn new(native_track_publication: swift::LocalTrackPublication) -> Self {
         unsafe {
-            CFRetain(native_track_publication);
+            CFRetain(native_track_publication.0);
         }
         Self(native_track_publication)
     }
@@ -689,28 +721,35 @@ impl LocalTrackPublication {
 
 impl Drop for LocalTrackPublication {
     fn drop(&mut self) {
-        unsafe { CFRelease(self.0) }
+        unsafe { CFRelease(self.0 .0) }
     }
 }
 
-pub struct RemoteTrackPublication(*const c_void);
-
-unsafe impl Send for RemoteTrackPublication {}
+pub struct RemoteTrackPublication {
+    native_publication: Mutex<swift::RemoteTrackPublication>,
+}
 
 impl RemoteTrackPublication {
-    pub fn new(native_track_publication: *const c_void) -> Self {
+    pub fn new(native_track_publication: swift::RemoteTrackPublication) -> Self {
         unsafe {
-            CFRetain(native_track_publication);
+            CFRetain(native_track_publication.0);
+        }
+        Self {
+            native_publication: Mutex::new(native_track_publication),
         }
-        Self(native_track_publication)
     }
 
     pub fn sid(&self) -> String {
-        unsafe { CFString::wrap_under_get_rule(LKRemoteTrackPublicationGetSid(self.0)).to_string() }
+        unsafe {
+            CFString::wrap_under_get_rule(LKRemoteTrackPublicationGetSid(
+                *self.native_publication.lock(),
+            ))
+            .to_string()
+        }
     }
 
     pub fn is_muted(&self) -> bool {
-        unsafe { LKRemoteTrackPublicationIsMuted(self.0) }
+        unsafe { LKRemoteTrackPublicationIsMuted(*self.native_publication.lock()) }
     }
 
     pub fn set_enabled(&self, enabled: bool) -> impl Future<Output = Result<()>> {
@@ -728,7 +767,7 @@ impl RemoteTrackPublication {
 
         unsafe {
             LKRemoteTrackPublicationSetEnabled(
-                self.0,
+                *self.native_publication.lock(),
                 enabled,
                 complete_callback,
                 Box::into_raw(Box::new(tx)) as *mut c_void,
@@ -741,26 +780,24 @@ impl RemoteTrackPublication {
 
 impl Drop for RemoteTrackPublication {
     fn drop(&mut self) {
-        unsafe { CFRelease(self.0) }
+        unsafe { CFRelease((*self.native_publication.lock()).0) }
     }
 }
 
 #[derive(Debug)]
 pub struct RemoteAudioTrack {
-    _native_track: *const c_void,
+    native_track: Mutex<swift::RemoteAudioTrack>,
     sid: Sid,
     publisher_id: String,
 }
 
-unsafe impl Send for RemoteAudioTrack {}
-
 impl RemoteAudioTrack {
-    fn new(native_track: *const c_void, sid: Sid, publisher_id: String) -> Self {
+    fn new(native_track: swift::RemoteAudioTrack, sid: Sid, publisher_id: String) -> Self {
         unsafe {
-            CFRetain(native_track);
+            CFRetain(native_track.0);
         }
         Self {
-            _native_track: native_track,
+            native_track: Mutex::new(native_track),
             sid,
             publisher_id,
         }
@@ -783,22 +820,26 @@ impl RemoteAudioTrack {
     }
 }
 
+impl Drop for RemoteAudioTrack {
+    fn drop(&mut self) {
+        unsafe { CFRelease(self.native_track.lock().0) }
+    }
+}
+
 #[derive(Debug)]
 pub struct RemoteVideoTrack {
-    native_track: *const c_void,
+    native_track: Mutex<swift::RemoteVideoTrack>,
     sid: Sid,
     publisher_id: String,
 }
 
-unsafe impl Send for RemoteVideoTrack {}
-
 impl RemoteVideoTrack {
-    fn new(native_track: *const c_void, sid: Sid, publisher_id: String) -> Self {
+    fn new(native_track: swift::RemoteVideoTrack, sid: Sid, publisher_id: String) -> Self {
         unsafe {
-            CFRetain(native_track);
+            CFRetain(native_track.0);
         }
         Self {
-            native_track,
+            native_track: Mutex::new(native_track),
             sid,
             publisher_id,
         }
@@ -847,7 +888,7 @@ impl RemoteVideoTrack {
                 on_frame,
                 on_drop,
             );
-            LKVideoTrackAddRenderer(self.native_track, renderer);
+            LKVideoTrackAddRenderer(*self.native_track.lock(), renderer);
             rx
         }
     }
@@ -855,7 +896,7 @@ impl RemoteVideoTrack {
 
 impl Drop for RemoteVideoTrack {
     fn drop(&mut self) {
-        unsafe { CFRelease(self.native_track) }
+        unsafe { CFRelease(self.native_track.lock().0) }
     }
 }
 
@@ -871,14 +912,12 @@ pub enum RemoteAudioTrackUpdate {
     Unsubscribed { publisher_id: Sid, track_id: Sid },
 }
 
-pub struct MacOSDisplay(*const c_void);
-
-unsafe impl Send for MacOSDisplay {}
+pub struct MacOSDisplay(swift::MacOSDisplay);
 
 impl MacOSDisplay {
-    fn new(ptr: *const c_void) -> Self {
+    fn new(ptr: swift::MacOSDisplay) -> Self {
         unsafe {
-            CFRetain(ptr);
+            CFRetain(ptr.0);
         }
         Self(ptr)
     }
@@ -886,7 +925,7 @@ impl MacOSDisplay {
 
 impl Drop for MacOSDisplay {
     fn drop(&mut self) {
-        unsafe { CFRelease(self.0) }
+        unsafe { CFRelease(self.0 .0) }
     }
 }
 

crates/live_kit_client/src/test.rs 🔗

@@ -1,8 +1,8 @@
-use anyhow::{anyhow, Result};
+use anyhow::{anyhow, Context, Result};
 use async_trait::async_trait;
 use collections::{BTreeMap, HashMap};
 use futures::Stream;
-use gpui::executor::Background;
+use gpui::BackgroundExecutor;
 use live_kit_server::token;
 use media::core_video::CVImageBuffer;
 use parking_lot::Mutex;
@@ -16,7 +16,7 @@ pub struct TestServer {
     pub api_key: String,
     pub secret_key: String,
     rooms: Mutex<HashMap<String, TestServerRoom>>,
-    background: Arc<Background>,
+    executor: BackgroundExecutor,
 }
 
 impl TestServer {
@@ -24,7 +24,7 @@ impl TestServer {
         url: String,
         api_key: String,
         secret_key: String,
-        background: Arc<Background>,
+        executor: BackgroundExecutor,
     ) -> Result<Arc<TestServer>> {
         let mut servers = SERVERS.lock();
         if servers.contains_key(&url) {
@@ -35,7 +35,7 @@ impl TestServer {
                 api_key,
                 secret_key,
                 rooms: Default::default(),
-                background,
+                executor,
             });
             servers.insert(url, server.clone());
             Ok(server)
@@ -65,7 +65,7 @@ impl TestServer {
     }
 
     pub async fn create_room(&self, room: String) -> Result<()> {
-        self.background.simulate_random_delay().await;
+        self.executor.simulate_random_delay().await;
         let mut server_rooms = self.rooms.lock();
         if server_rooms.contains_key(&room) {
             Err(anyhow!("room {:?} already exists", room))
@@ -77,7 +77,7 @@ impl TestServer {
 
     async fn delete_room(&self, room: String) -> Result<()> {
         // TODO: clear state associated with all `Room`s.
-        self.background.simulate_random_delay().await;
+        self.executor.simulate_random_delay().await;
         let mut server_rooms = self.rooms.lock();
         server_rooms
             .remove(&room)
@@ -86,7 +86,7 @@ impl TestServer {
     }
 
     async fn join_room(&self, token: String, client_room: Arc<Room>) -> Result<()> {
-        self.background.simulate_random_delay().await;
+        self.executor.simulate_random_delay().await;
         let claims = live_kit_server::token::validate(&token, &self.secret_key)?;
         let identity = claims.sub.unwrap().to_string();
         let room_name = claims.video.room.unwrap();
@@ -115,7 +115,7 @@ impl TestServer {
     }
 
     async fn leave_room(&self, token: String) -> Result<()> {
-        self.background.simulate_random_delay().await;
+        self.executor.simulate_random_delay().await;
         let claims = live_kit_server::token::validate(&token, &self.secret_key)?;
         let identity = claims.sub.unwrap().to_string();
         let room_name = claims.video.room.unwrap();
@@ -136,7 +136,7 @@ impl TestServer {
     async fn remove_participant(&self, room_name: String, identity: String) -> Result<()> {
         // TODO: clear state associated with the `Room`.
 
-        self.background.simulate_random_delay().await;
+        self.executor.simulate_random_delay().await;
         let mut server_rooms = self.rooms.lock();
         let room = server_rooms
             .get_mut(&room_name)
@@ -152,7 +152,7 @@ impl TestServer {
     }
 
     pub async fn disconnect_client(&self, client_identity: String) {
-        self.background.simulate_random_delay().await;
+        self.executor.simulate_random_delay().await;
         let mut server_rooms = self.rooms.lock();
         for room in server_rooms.values_mut() {
             if let Some(room) = room.client_rooms.remove(&client_identity) {
@@ -162,7 +162,7 @@ impl TestServer {
     }
 
     async fn publish_video_track(&self, token: String, local_track: LocalVideoTrack) -> Result<()> {
-        self.background.simulate_random_delay().await;
+        self.executor.simulate_random_delay().await;
         let claims = live_kit_server::token::validate(&token, &self.secret_key)?;
         let identity = claims.sub.unwrap().to_string();
         let room_name = claims.video.room.unwrap();
@@ -200,7 +200,7 @@ impl TestServer {
         token: String,
         _local_track: &LocalAudioTrack,
     ) -> Result<()> {
-        self.background.simulate_random_delay().await;
+        self.executor.simulate_random_delay().await;
         let claims = live_kit_server::token::validate(&token, &self.secret_key)?;
         let identity = claims.sub.unwrap().to_string();
         let room_name = claims.video.room.unwrap();
@@ -364,7 +364,10 @@ impl Room {
         let token = token.to_string();
         async move {
             let server = TestServer::get(&url)?;
-            server.join_room(token.clone(), this.clone()).await?;
+            server
+                .join_room(token.clone(), this.clone())
+                .await
+                .context("room join")?;
             *this.0.lock().connection.0.borrow_mut() = ConnectionState::Connected { url, token };
             Ok(())
         }
@@ -374,7 +377,7 @@ impl Room {
         let this = self.clone();
         async move {
             let server = this.test_server();
-            server.background.simulate_random_delay().await;
+            server.executor.simulate_random_delay().await;
             Ok(this.0.lock().display_sources.clone())
         }
     }
@@ -492,8 +495,8 @@ impl Drop for Room {
             ConnectionState::Disconnected,
         ) {
             if let Ok(server) = TestServer::get(&token) {
-                let background = server.background.clone();
-                background
+                let executor = server.executor.clone();
+                executor
                     .spawn(async move { server.leave_room(token).await.unwrap() })
                     .detach();
             }
@@ -547,6 +550,7 @@ impl LocalAudioTrack {
     }
 }
 
+#[derive(Debug)]
 pub struct RemoteVideoTrack {
     sid: Sid,
     publisher_id: Sid,

crates/live_kit_client2/Cargo.toml 🔗

@@ -1,71 +0,0 @@
-[package]
-name = "live_kit_client2"
-version = "0.1.0"
-edition = "2021"
-description = "Bindings to LiveKit Swift client SDK"
-publish = false
-
-[lib]
-path = "src/live_kit_client2.rs"
-doctest = false
-
-[[example]]
-name = "test_app2"
-
-[features]
-test-support = [
-    "async-trait",
-    "collections/test-support",
-    "gpui/test-support",
-    "live_kit_server",
-    "nanoid",
-]
-
-[dependencies]
-collections = { path = "../collections", optional = true }
-gpui = { package = "gpui2", path = "../gpui2", optional = true }
-live_kit_server = { path = "../live_kit_server", optional = true }
-media = { path = "../media" }
-
-anyhow.workspace = true
-async-broadcast = "0.4"
-core-foundation = "0.9.3"
-core-graphics = "0.22.3"
-futures.workspace = true
-log.workspace = true
-parking_lot.workspace = true
-postage.workspace = true
-
-async-trait = { workspace = true, optional = true }
-nanoid = { version ="0.4", optional = true}
-
-[dev-dependencies]
-collections = { path = "../collections", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-live_kit_server = { path = "../live_kit_server" }
-media = { path = "../media" }
-nanoid = "0.4"
-
-anyhow.workspace = true
-async-trait.workspace = true
-block = "0.1"
-bytes = "1.2"
-byteorder = "1.4"
-cocoa = "0.24"
-core-foundation = "0.9.3"
-core-graphics = "0.22.3"
-foreign-types = "0.3"
-futures.workspace = true
-hmac = "0.12"
-jwt = "0.16"
-objc = "0.2"
-parking_lot.workspace = true
-serde.workspace = true
-serde_derive.workspace = true
-sha2 = "0.10"
-simplelog = "0.9"
-
-[build-dependencies]
-serde.workspace = true
-serde_derive.workspace = true
-serde_json.workspace = true

crates/live_kit_client2/LiveKitBridge2/Package.resolved 🔗

@@ -1,52 +0,0 @@
-{
-  "object": {
-    "pins": [
-      {
-        "package": "LiveKit",
-        "repositoryURL": "https://github.com/livekit/client-sdk-swift.git",
-        "state": {
-          "branch": null,
-          "revision": "8b9cefed8d1669ec8fce41376b56dce3036a5f50",
-          "version": "1.1.4"
-        }
-      },
-      {
-        "package": "Promises",
-        "repositoryURL": "https://github.com/google/promises.git",
-        "state": {
-          "branch": null,
-          "revision": "ec957ccddbcc710ccc64c9dcbd4c7006fcf8b73a",
-          "version": "2.2.0"
-        }
-      },
-      {
-        "package": "WebRTC",
-        "repositoryURL": "https://github.com/webrtc-sdk/Specs.git",
-        "state": {
-          "branch": null,
-          "revision": "4fa8d6d647fc759cdd0265fd413d2f28ea2e0e08",
-          "version": "114.5735.8"
-        }
-      },
-      {
-        "package": "swift-log",
-        "repositoryURL": "https://github.com/apple/swift-log.git",
-        "state": {
-          "branch": null,
-          "revision": "32e8d724467f8fe623624570367e3d50c5638e46",
-          "version": "1.5.2"
-        }
-      },
-      {
-        "package": "SwiftProtobuf",
-        "repositoryURL": "https://github.com/apple/swift-protobuf.git",
-        "state": {
-          "branch": null,
-          "revision": "ce20dc083ee485524b802669890291c0d8090170",
-          "version": "1.22.1"
-        }
-      }
-    ]
-  },
-  "version": 1
-}

crates/live_kit_client2/LiveKitBridge2/Package.swift 🔗

@@ -1,27 +0,0 @@
-// swift-tools-version: 5.5
-
-import PackageDescription
-
-let package = Package(
-    name: "LiveKitBridge2",
-    platforms: [
-        .macOS(.v10_15)
-    ],
-    products: [
-        // Products define the executables and libraries a package produces, and make them visible to other packages.
-        .library(
-            name: "LiveKitBridge2",
-            type: .static,
-            targets: ["LiveKitBridge2"]),
-    ],
-    dependencies: [
-        .package(url: "https://github.com/livekit/client-sdk-swift.git", .exact("1.1.4")),
-    ],
-    targets: [
-        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
-        // Targets can depend on other targets in this package, and on products in packages this package depends on.
-        .target(
-            name: "LiveKitBridge2",
-            dependencies: [.product(name: "LiveKit", package: "client-sdk-swift")]),
-    ]
-)

crates/live_kit_client2/LiveKitBridge2/Sources/LiveKitBridge2/LiveKitBridge2.swift 🔗

@@ -1,327 +0,0 @@
-import Foundation
-import LiveKit
-import WebRTC
-import ScreenCaptureKit
-
-class LKRoomDelegate: RoomDelegate {
-    var data: UnsafeRawPointer
-    var onDidDisconnect: @convention(c) (UnsafeRawPointer) -> Void
-    var onDidSubscribeToRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer, UnsafeRawPointer) -> Void
-    var onDidUnsubscribeFromRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void
-    var onMuteChangedFromRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void
-    var onActiveSpeakersChanged: @convention(c) (UnsafeRawPointer, CFArray) -> Void
-    var onDidSubscribeToRemoteVideoTrack: @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void
-    var onDidUnsubscribeFromRemoteVideoTrack: @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void
-
-    init(
-        data: UnsafeRawPointer,
-        onDidDisconnect: @escaping @convention(c) (UnsafeRawPointer) -> Void,
-        onDidSubscribeToRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer, UnsafeRawPointer) -> Void,
-        onDidUnsubscribeFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void,
-        onMuteChangedFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void,
-        onActiveSpeakersChanged: @convention(c) (UnsafeRawPointer, CFArray) -> Void,
-        onDidSubscribeToRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void,
-        onDidUnsubscribeFromRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void)
-    {
-        self.data = data
-        self.onDidDisconnect = onDidDisconnect
-        self.onDidSubscribeToRemoteAudioTrack = onDidSubscribeToRemoteAudioTrack
-        self.onDidUnsubscribeFromRemoteAudioTrack = onDidUnsubscribeFromRemoteAudioTrack
-        self.onDidSubscribeToRemoteVideoTrack = onDidSubscribeToRemoteVideoTrack
-        self.onDidUnsubscribeFromRemoteVideoTrack = onDidUnsubscribeFromRemoteVideoTrack
-        self.onMuteChangedFromRemoteAudioTrack = onMuteChangedFromRemoteAudioTrack
-        self.onActiveSpeakersChanged = onActiveSpeakersChanged
-    }
-
-    func room(_ room: Room, didUpdate connectionState: ConnectionState, oldValue: ConnectionState) {
-        if connectionState.isDisconnected {
-            self.onDidDisconnect(self.data)
-        }
-    }
-
-    func room(_ room: Room, participant: RemoteParticipant, didSubscribe publication: RemoteTrackPublication, track: Track) {
-        if track.kind == .video {
-            self.onDidSubscribeToRemoteVideoTrack(self.data, participant.identity as CFString, track.sid! as CFString, Unmanaged.passUnretained(track).toOpaque())
-        } else if track.kind == .audio {
-            self.onDidSubscribeToRemoteAudioTrack(self.data, participant.identity as CFString, track.sid! as CFString, Unmanaged.passUnretained(track).toOpaque(), Unmanaged.passUnretained(publication).toOpaque())
-        }
-    }
-
-    func room(_ room: Room, participant: Participant, didUpdate publication: TrackPublication, muted: Bool) {
-        if publication.kind == .audio {
-            self.onMuteChangedFromRemoteAudioTrack(self.data, publication.sid as CFString, muted)
-        }
-    }
-
-    func room(_ room: Room, didUpdate speakers: [Participant]) {
-        guard let speaker_ids = speakers.compactMap({ $0.identity as CFString }) as CFArray? else { return }
-        self.onActiveSpeakersChanged(self.data, speaker_ids)
-    }
-
-    func room(_ room: Room, participant: RemoteParticipant, didUnsubscribe publication: RemoteTrackPublication, track: Track) {
-        if track.kind == .video {
-            self.onDidUnsubscribeFromRemoteVideoTrack(self.data, participant.identity as CFString, track.sid! as CFString)
-        } else if track.kind == .audio {
-            self.onDidUnsubscribeFromRemoteAudioTrack(self.data, participant.identity as CFString, track.sid! as CFString)
-        }
-    }
-}
-
-class LKVideoRenderer: NSObject, VideoRenderer {
-    var data: UnsafeRawPointer
-    var onFrame: @convention(c) (UnsafeRawPointer, CVPixelBuffer) -> Bool
-    var onDrop: @convention(c) (UnsafeRawPointer) -> Void
-    var adaptiveStreamIsEnabled: Bool = false
-    var adaptiveStreamSize: CGSize = .zero
-    weak var track: VideoTrack?
-
-    init(data: UnsafeRawPointer, onFrame: @escaping @convention(c) (UnsafeRawPointer, CVPixelBuffer) -> Bool, onDrop: @escaping @convention(c) (UnsafeRawPointer) -> Void) {
-        self.data = data
-        self.onFrame = onFrame
-        self.onDrop = onDrop
-    }
-
-    deinit {
-        self.onDrop(self.data)
-    }
-
-    func setSize(_ size: CGSize) {
-    }
-
-    func renderFrame(_ frame: RTCVideoFrame?) {
-        let buffer = frame?.buffer as? RTCCVPixelBuffer
-        if let pixelBuffer = buffer?.pixelBuffer {
-            if !self.onFrame(self.data, pixelBuffer) {
-                DispatchQueue.main.async {
-                    self.track?.remove(videoRenderer: self)
-                }
-            }
-        }
-    }
-}
-
-@_cdecl("LKRoomDelegateCreate")
-public func LKRoomDelegateCreate(
-    data: UnsafeRawPointer,
-    onDidDisconnect: @escaping @convention(c) (UnsafeRawPointer) -> Void,
-    onDidSubscribeToRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer, UnsafeRawPointer) -> Void,
-    onDidUnsubscribeFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void,
-    onMuteChangedFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void,
-    onActiveSpeakerChanged: @escaping @convention(c) (UnsafeRawPointer, CFArray) -> Void,
-    onDidSubscribeToRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void,
-    onDidUnsubscribeFromRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void
-) -> UnsafeMutableRawPointer {
-    let delegate = LKRoomDelegate(
-        data: data,
-        onDidDisconnect: onDidDisconnect,
-        onDidSubscribeToRemoteAudioTrack: onDidSubscribeToRemoteAudioTrack,
-        onDidUnsubscribeFromRemoteAudioTrack: onDidUnsubscribeFromRemoteAudioTrack,
-        onMuteChangedFromRemoteAudioTrack: onMuteChangedFromRemoteAudioTrack,
-        onActiveSpeakersChanged: onActiveSpeakerChanged,
-        onDidSubscribeToRemoteVideoTrack: onDidSubscribeToRemoteVideoTrack,
-        onDidUnsubscribeFromRemoteVideoTrack: onDidUnsubscribeFromRemoteVideoTrack
-    )
-    return Unmanaged.passRetained(delegate).toOpaque()
-}
-
-@_cdecl("LKRoomCreate")
-public func LKRoomCreate(delegate: UnsafeRawPointer) -> UnsafeMutableRawPointer  {
-    let delegate = Unmanaged<LKRoomDelegate>.fromOpaque(delegate).takeUnretainedValue()
-    return Unmanaged.passRetained(Room(delegate: delegate)).toOpaque()
-}
-
-@_cdecl("LKRoomConnect")
-public func LKRoomConnect(room: UnsafeRawPointer, url: CFString, token: CFString, callback: @escaping @convention(c) (UnsafeRawPointer, CFString?) -> Void, callback_data: UnsafeRawPointer) {
-    let room = Unmanaged<Room>.fromOpaque(room).takeUnretainedValue()
-
-    room.connect(url as String, token as String).then { _ in
-        callback(callback_data, UnsafeRawPointer(nil) as! CFString?)
-    }.catch { error in
-        callback(callback_data, error.localizedDescription as CFString)
-    }
-}
-
-@_cdecl("LKRoomDisconnect")
-public func LKRoomDisconnect(room: UnsafeRawPointer) {
-    let room = Unmanaged<Room>.fromOpaque(room).takeUnretainedValue()
-    room.disconnect()
-}
-
-@_cdecl("LKRoomPublishVideoTrack")
-public func LKRoomPublishVideoTrack(room: UnsafeRawPointer, track: UnsafeRawPointer, callback: @escaping @convention(c) (UnsafeRawPointer, UnsafeMutableRawPointer?, CFString?) -> Void, callback_data: UnsafeRawPointer) {
-    let room = Unmanaged<Room>.fromOpaque(room).takeUnretainedValue()
-    let track = Unmanaged<LocalVideoTrack>.fromOpaque(track).takeUnretainedValue()
-    room.localParticipant?.publishVideoTrack(track: track).then { publication in
-        callback(callback_data, Unmanaged.passRetained(publication).toOpaque(), nil)
-    }.catch { error in
-        callback(callback_data, nil, error.localizedDescription as CFString)
-    }
-}
-
-@_cdecl("LKRoomPublishAudioTrack")
-public func LKRoomPublishAudioTrack(room: UnsafeRawPointer, track: UnsafeRawPointer, callback: @escaping @convention(c) (UnsafeRawPointer, UnsafeMutableRawPointer?, CFString?) -> Void, callback_data: UnsafeRawPointer) {
-    let room = Unmanaged<Room>.fromOpaque(room).takeUnretainedValue()
-    let track = Unmanaged<LocalAudioTrack>.fromOpaque(track).takeUnretainedValue()
-    room.localParticipant?.publishAudioTrack(track: track).then { publication in
-        callback(callback_data, Unmanaged.passRetained(publication).toOpaque(), nil)
-    }.catch { error in
-        callback(callback_data, nil, error.localizedDescription as CFString)
-    }
-}
-
-
-@_cdecl("LKRoomUnpublishTrack")
-public func LKRoomUnpublishTrack(room: UnsafeRawPointer, publication: UnsafeRawPointer) {
-    let room = Unmanaged<Room>.fromOpaque(room).takeUnretainedValue()
-    let publication = Unmanaged<LocalTrackPublication>.fromOpaque(publication).takeUnretainedValue()
-    let _ = room.localParticipant?.unpublish(publication: publication)
-}
-
-@_cdecl("LKRoomAudioTracksForRemoteParticipant")
-public func LKRoomAudioTracksForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? {
-    let room = Unmanaged<Room>.fromOpaque(room).takeUnretainedValue()
-
-    for (_, participant) in room.remoteParticipants {
-        if participant.identity == participantId as String {
-            return participant.audioTracks.compactMap { $0.track as? RemoteAudioTrack } as CFArray?
-        }
-    }
-
-    return nil;
-}
-
-@_cdecl("LKRoomAudioTrackPublicationsForRemoteParticipant")
-public func LKRoomAudioTrackPublicationsForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? {
-    let room = Unmanaged<Room>.fromOpaque(room).takeUnretainedValue()
-
-    for (_, participant) in room.remoteParticipants {
-        if participant.identity == participantId as String {
-            return participant.audioTracks.compactMap { $0 as? RemoteTrackPublication } as CFArray?
-        }
-    }
-
-    return nil;
-}
-
-@_cdecl("LKRoomVideoTracksForRemoteParticipant")
-public func LKRoomVideoTracksForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? {
-    let room = Unmanaged<Room>.fromOpaque(room).takeUnretainedValue()
-
-    for (_, participant) in room.remoteParticipants {
-        if participant.identity == participantId as String {
-            return participant.videoTracks.compactMap { $0.track as? RemoteVideoTrack } as CFArray?
-        }
-    }
-
-    return nil;
-}
-
-@_cdecl("LKLocalAudioTrackCreateTrack")
-public func LKLocalAudioTrackCreateTrack() -> UnsafeMutableRawPointer {
-    let track = LocalAudioTrack.createTrack(options: AudioCaptureOptions(
-      echoCancellation: true,
-      noiseSuppression: true
-    ))
-
-    return Unmanaged.passRetained(track).toOpaque()
-}
-
-
-@_cdecl("LKCreateScreenShareTrackForDisplay")
-public func LKCreateScreenShareTrackForDisplay(display: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer {
-    let display = Unmanaged<MacOSDisplay>.fromOpaque(display).takeUnretainedValue()
-    let track = LocalVideoTrack.createMacOSScreenShareTrack(source: display, preferredMethod: .legacy)
-    return Unmanaged.passRetained(track).toOpaque()
-}
-
-@_cdecl("LKVideoRendererCreate")
-public func LKVideoRendererCreate(data: UnsafeRawPointer, onFrame: @escaping @convention(c) (UnsafeRawPointer, CVPixelBuffer) -> Bool, onDrop: @escaping @convention(c) (UnsafeRawPointer) -> Void) -> UnsafeMutableRawPointer {
-    Unmanaged.passRetained(LKVideoRenderer(data: data, onFrame: onFrame, onDrop: onDrop)).toOpaque()
-}
-
-@_cdecl("LKVideoTrackAddRenderer")
-public func LKVideoTrackAddRenderer(track: UnsafeRawPointer, renderer: UnsafeRawPointer) {
-    let track = Unmanaged<Track>.fromOpaque(track).takeUnretainedValue() as! VideoTrack
-    let renderer = Unmanaged<LKVideoRenderer>.fromOpaque(renderer).takeRetainedValue()
-    renderer.track = track
-    track.add(videoRenderer: renderer)
-}
-
-@_cdecl("LKRemoteVideoTrackGetSid")
-public func LKRemoteVideoTrackGetSid(track: UnsafeRawPointer) -> CFString {
-    let track = Unmanaged<RemoteVideoTrack>.fromOpaque(track).takeUnretainedValue()
-    return track.sid! as CFString
-}
-
-@_cdecl("LKRemoteAudioTrackGetSid")
-public func LKRemoteAudioTrackGetSid(track: UnsafeRawPointer) -> CFString {
-    let track = Unmanaged<RemoteAudioTrack>.fromOpaque(track).takeUnretainedValue()
-    return track.sid! as CFString
-}
-
-@_cdecl("LKDisplaySources")
-public func LKDisplaySources(data: UnsafeRawPointer, callback: @escaping @convention(c) (UnsafeRawPointer, CFArray?, CFString?) -> Void) {
-    MacOSScreenCapturer.sources(for: .display, includeCurrentApplication: false, preferredMethod: .legacy).then { displaySources in
-        callback(data, displaySources as CFArray, nil)
-    }.catch { error in
-        callback(data, nil, error.localizedDescription as CFString)
-    }
-}
-
-@_cdecl("LKLocalTrackPublicationSetMute")
-public func LKLocalTrackPublicationSetMute(
-    publication: UnsafeRawPointer,
-    muted: Bool,
-    on_complete: @escaping @convention(c) (UnsafeRawPointer, CFString?) -> Void,
-    callback_data: UnsafeRawPointer
-) {
-    let publication = Unmanaged<LocalTrackPublication>.fromOpaque(publication).takeUnretainedValue()
-
-    if muted {
-        publication.mute().then {
-            on_complete(callback_data, nil)
-        }.catch { error in
-            on_complete(callback_data, error.localizedDescription as CFString)
-        }
-    } else {
-        publication.unmute().then {
-            on_complete(callback_data, nil)
-        }.catch { error in
-            on_complete(callback_data, error.localizedDescription as CFString)
-        }
-    }
-}
-
-@_cdecl("LKRemoteTrackPublicationSetEnabled")
-public func LKRemoteTrackPublicationSetEnabled(
-    publication: UnsafeRawPointer,
-    enabled: Bool,
-    on_complete: @escaping @convention(c) (UnsafeRawPointer, CFString?) -> Void,
-    callback_data: UnsafeRawPointer
-) {
-    let publication = Unmanaged<RemoteTrackPublication>.fromOpaque(publication).takeUnretainedValue()
-
-    publication.set(enabled: enabled).then {
-        on_complete(callback_data, nil)
-    }.catch { error in
-        on_complete(callback_data, error.localizedDescription as CFString)
-    }
-}
-
-@_cdecl("LKRemoteTrackPublicationIsMuted")
-public func LKRemoteTrackPublicationIsMuted(
-    publication: UnsafeRawPointer
-) -> Bool {
-    let publication = Unmanaged<RemoteTrackPublication>.fromOpaque(publication).takeUnretainedValue()
-
-    return publication.muted
-}
-
-@_cdecl("LKRemoteTrackPublicationGetSid")
-public func LKRemoteTrackPublicationGetSid(
-    publication: UnsafeRawPointer
-) -> CFString {
-    let publication = Unmanaged<RemoteTrackPublication>.fromOpaque(publication).takeUnretainedValue()
-
-    return publication.sid as CFString
-}

crates/live_kit_client2/build.rs 🔗

@@ -1,182 +0,0 @@
-use serde::Deserialize;
-use std::{
-    env,
-    path::{Path, PathBuf},
-    process::Command,
-};
-
-const SWIFT_PACKAGE_NAME: &str = "LiveKitBridge2";
-
-#[derive(Debug, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct SwiftTargetInfo {
-    pub triple: String,
-    pub unversioned_triple: String,
-    pub module_triple: String,
-    pub swift_runtime_compatibility_version: String,
-    #[serde(rename = "librariesRequireRPath")]
-    pub libraries_require_rpath: bool,
-}
-
-#[derive(Debug, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct SwiftPaths {
-    pub runtime_library_paths: Vec<String>,
-    pub runtime_library_import_paths: Vec<String>,
-    pub runtime_resource_path: String,
-}
-
-#[derive(Debug, Deserialize)]
-pub struct SwiftTarget {
-    pub target: SwiftTargetInfo,
-    pub paths: SwiftPaths,
-}
-
-const MACOS_TARGET_VERSION: &str = "10.15.7";
-
-fn main() {
-    if cfg!(not(any(test, feature = "test-support"))) {
-        let swift_target = get_swift_target();
-
-        build_bridge(&swift_target);
-        link_swift_stdlib(&swift_target);
-        link_webrtc_framework(&swift_target);
-
-        // Register exported Objective-C selectors, protocols, etc when building example binaries.
-        println!("cargo:rustc-link-arg=-Wl,-ObjC");
-    }
-}
-
-fn build_bridge(swift_target: &SwiftTarget) {
-    println!("cargo:rerun-if-env-changed=MACOSX_DEPLOYMENT_TARGET");
-    println!("cargo:rerun-if-changed={}/Sources", SWIFT_PACKAGE_NAME);
-    println!(
-        "cargo:rerun-if-changed={}/Package.swift",
-        SWIFT_PACKAGE_NAME
-    );
-    println!(
-        "cargo:rerun-if-changed={}/Package.resolved",
-        SWIFT_PACKAGE_NAME
-    );
-
-    let swift_package_root = swift_package_root();
-    let swift_target_folder = swift_target_folder();
-    let swift_cache_folder = swift_cache_folder();
-    if !Command::new("swift")
-        .arg("build")
-        .arg("--disable-automatic-resolution")
-        .args(["--configuration", &env::var("PROFILE").unwrap()])
-        .args(["--triple", &swift_target.target.triple])
-        .args(["--build-path".into(), swift_target_folder])
-        .args(["--cache-path".into(), swift_cache_folder])
-        .current_dir(&swift_package_root)
-        .status()
-        .unwrap()
-        .success()
-    {
-        panic!(
-            "Failed to compile swift package in {}",
-            swift_package_root.display()
-        );
-    }
-
-    println!(
-        "cargo:rustc-link-search=native={}",
-        swift_target.out_dir_path().display()
-    );
-    println!("cargo:rustc-link-lib=static={}", SWIFT_PACKAGE_NAME);
-}
-
-fn link_swift_stdlib(swift_target: &SwiftTarget) {
-    for path in &swift_target.paths.runtime_library_paths {
-        println!("cargo:rustc-link-search=native={}", path);
-    }
-}
-
-fn link_webrtc_framework(swift_target: &SwiftTarget) {
-    let swift_out_dir_path = swift_target.out_dir_path();
-    println!("cargo:rustc-link-lib=framework=WebRTC");
-    println!(
-        "cargo:rustc-link-search=framework={}",
-        swift_out_dir_path.display()
-    );
-    // Find WebRTC.framework as a sibling of the executable when running tests.
-    println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path");
-    // Find WebRTC.framework in parent directory of the executable when running examples.
-    println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path/..");
-
-    let source_path = swift_out_dir_path.join("WebRTC.framework");
-    let deps_dir_path =
-        PathBuf::from(env::var("OUT_DIR").unwrap()).join("../../../deps/WebRTC.framework");
-    let target_dir_path =
-        PathBuf::from(env::var("OUT_DIR").unwrap()).join("../../../WebRTC.framework");
-    copy_dir(&source_path, &deps_dir_path);
-    copy_dir(&source_path, &target_dir_path);
-}
-
-fn get_swift_target() -> SwiftTarget {
-    let mut arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
-    if arch == "aarch64" {
-        arch = "arm64".into();
-    }
-    let target = format!("{}-apple-macosx{}", arch, MACOS_TARGET_VERSION);
-
-    let swift_target_info_str = Command::new("swift")
-        .args(["-target", &target, "-print-target-info"])
-        .output()
-        .unwrap()
-        .stdout;
-
-    serde_json::from_slice(&swift_target_info_str).unwrap()
-}
-
-fn swift_package_root() -> PathBuf {
-    env::current_dir().unwrap().join(SWIFT_PACKAGE_NAME)
-}
-
-fn swift_target_folder() -> PathBuf {
-    let target = env::var("TARGET").unwrap();
-    env::current_dir()
-        .unwrap()
-        .join(format!("../../target/{target}/{SWIFT_PACKAGE_NAME}_target"))
-}
-
-fn swift_cache_folder() -> PathBuf {
-    let target = env::var("TARGET").unwrap();
-    env::current_dir()
-        .unwrap()
-        .join(format!("../../target/{target}/{SWIFT_PACKAGE_NAME}_cache"))
-}
-
-fn copy_dir(source: &Path, destination: &Path) {
-    assert!(
-        Command::new("rm")
-            .arg("-rf")
-            .arg(destination)
-            .status()
-            .unwrap()
-            .success(),
-        "could not remove {:?} before copying",
-        destination
-    );
-
-    assert!(
-        Command::new("cp")
-            .arg("-R")
-            .args([source, destination])
-            .status()
-            .unwrap()
-            .success(),
-        "could not copy {:?} to {:?}",
-        source,
-        destination
-    );
-}
-
-impl SwiftTarget {
-    fn out_dir_path(&self) -> PathBuf {
-        swift_target_folder()
-            .join(&self.target.unversioned_triple)
-            .join(env::var("PROFILE").unwrap())
-    }
-}

crates/live_kit_client2/examples/test_app2.rs 🔗

@@ -1,176 +0,0 @@
-use std::{sync::Arc, time::Duration};
-
-use futures::StreamExt;
-use gpui::{actions, KeyBinding};
-use live_kit_client2::{
-    LocalAudioTrack, LocalVideoTrack, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate, Room,
-};
-use live_kit_server::token::{self, VideoGrant};
-use log::LevelFilter;
-use simplelog::SimpleLogger;
-
-actions!(live_kit_client, [Quit]);
-
-fn main() {
-    SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
-
-    gpui::App::production(Arc::new(())).run(|cx| {
-        #[cfg(any(test, feature = "test-support"))]
-        println!("USING TEST LIVEKIT");
-
-        #[cfg(not(any(test, feature = "test-support")))]
-        println!("USING REAL LIVEKIT");
-
-        cx.activate(true);
-
-        cx.on_action(quit);
-        cx.bind_keys([KeyBinding::new("cmd-q", Quit, None)]);
-
-        // todo!()
-        // cx.set_menus(vec![Menu {
-        //     name: "Zed",
-        //     items: vec![MenuItem::Action {
-        //         name: "Quit",
-        //         action: Box::new(Quit),
-        //         os_action: None,
-        //     }],
-        // }]);
-
-        let live_kit_url = std::env::var("LIVE_KIT_URL").unwrap_or("http://localhost:7880".into());
-        let live_kit_key = std::env::var("LIVE_KIT_KEY").unwrap_or("devkey".into());
-        let live_kit_secret = std::env::var("LIVE_KIT_SECRET").unwrap_or("secret".into());
-
-        cx.spawn(|cx| async move {
-            let user_a_token = token::create(
-                &live_kit_key,
-                &live_kit_secret,
-                Some("test-participant-1"),
-                VideoGrant::to_join("test-room"),
-            )
-            .unwrap();
-            let room_a = Room::new();
-            room_a.connect(&live_kit_url, &user_a_token).await.unwrap();
-
-            let user2_token = token::create(
-                &live_kit_key,
-                &live_kit_secret,
-                Some("test-participant-2"),
-                VideoGrant::to_join("test-room"),
-            )
-            .unwrap();
-            let room_b = Room::new();
-            room_b.connect(&live_kit_url, &user2_token).await.unwrap();
-
-            let mut audio_track_updates = room_b.remote_audio_track_updates();
-            let audio_track = LocalAudioTrack::create();
-            let audio_track_publication = room_a.publish_audio_track(audio_track).await.unwrap();
-
-            if let RemoteAudioTrackUpdate::Subscribed(track, _) =
-                audio_track_updates.next().await.unwrap()
-            {
-                let remote_tracks = room_b.remote_audio_tracks("test-participant-1");
-                assert_eq!(remote_tracks.len(), 1);
-                assert_eq!(remote_tracks[0].publisher_id(), "test-participant-1");
-                assert_eq!(track.publisher_id(), "test-participant-1");
-            } else {
-                panic!("unexpected message");
-            }
-
-            audio_track_publication.set_mute(true).await.unwrap();
-
-            println!("waiting for mute changed!");
-            if let RemoteAudioTrackUpdate::MuteChanged { track_id, muted } =
-                audio_track_updates.next().await.unwrap()
-            {
-                let remote_tracks = room_b.remote_audio_tracks("test-participant-1");
-                assert_eq!(remote_tracks[0].sid(), track_id);
-                assert_eq!(muted, true);
-            } else {
-                panic!("unexpected message");
-            }
-
-            audio_track_publication.set_mute(false).await.unwrap();
-
-            if let RemoteAudioTrackUpdate::MuteChanged { track_id, muted } =
-                audio_track_updates.next().await.unwrap()
-            {
-                let remote_tracks = room_b.remote_audio_tracks("test-participant-1");
-                assert_eq!(remote_tracks[0].sid(), track_id);
-                assert_eq!(muted, false);
-            } else {
-                panic!("unexpected message");
-            }
-
-            println!("Pausing for 5 seconds to test audio, make some noise!");
-            let timer = cx.background_executor().timer(Duration::from_secs(5));
-            timer.await;
-            let remote_audio_track = room_b
-                .remote_audio_tracks("test-participant-1")
-                .pop()
-                .unwrap();
-            room_a.unpublish_track(audio_track_publication);
-
-            // Clear out any active speakers changed messages
-            let mut next = audio_track_updates.next().await.unwrap();
-            while let RemoteAudioTrackUpdate::ActiveSpeakersChanged { speakers } = next {
-                println!("Speakers changed: {:?}", speakers);
-                next = audio_track_updates.next().await.unwrap();
-            }
-
-            if let RemoteAudioTrackUpdate::Unsubscribed {
-                publisher_id,
-                track_id,
-            } = next
-            {
-                assert_eq!(publisher_id, "test-participant-1");
-                assert_eq!(remote_audio_track.sid(), track_id);
-                assert_eq!(room_b.remote_audio_tracks("test-participant-1").len(), 0);
-            } else {
-                panic!("unexpected message");
-            }
-
-            let mut video_track_updates = room_b.remote_video_track_updates();
-            let displays = room_a.display_sources().await.unwrap();
-            let display = displays.into_iter().next().unwrap();
-
-            let local_video_track = LocalVideoTrack::screen_share_for_display(&display);
-            let local_video_track_publication =
-                room_a.publish_video_track(local_video_track).await.unwrap();
-
-            if let RemoteVideoTrackUpdate::Subscribed(track) =
-                video_track_updates.next().await.unwrap()
-            {
-                let remote_video_tracks = room_b.remote_video_tracks("test-participant-1");
-                assert_eq!(remote_video_tracks.len(), 1);
-                assert_eq!(remote_video_tracks[0].publisher_id(), "test-participant-1");
-                assert_eq!(track.publisher_id(), "test-participant-1");
-            } else {
-                panic!("unexpected message");
-            }
-
-            let remote_video_track = room_b
-                .remote_video_tracks("test-participant-1")
-                .pop()
-                .unwrap();
-            room_a.unpublish_track(local_video_track_publication);
-            if let RemoteVideoTrackUpdate::Unsubscribed {
-                publisher_id,
-                track_id,
-            } = video_track_updates.next().await.unwrap()
-            {
-                assert_eq!(publisher_id, "test-participant-1");
-                assert_eq!(remote_video_track.sid(), track_id);
-                assert_eq!(room_b.remote_video_tracks("test-participant-1").len(), 0);
-            } else {
-                panic!("unexpected message");
-            }
-
-            cx.update(|cx| cx.shutdown()).ok();
-        })
-        .detach();
-    });
-}
-
-fn quit(_: &Quit, cx: &mut gpui::AppContext) {
-    cx.quit();
-}

crates/live_kit_client2/src/live_kit_client2.rs 🔗

@@ -1,11 +0,0 @@
-#[cfg(not(any(test, feature = "test-support")))]
-pub mod prod;
-
-#[cfg(not(any(test, feature = "test-support")))]
-pub use prod::*;
-
-#[cfg(any(test, feature = "test-support"))]
-pub mod test;
-
-#[cfg(any(test, feature = "test-support"))]
-pub use test::*;

crates/live_kit_client2/src/prod.rs 🔗

@@ -1,947 +0,0 @@
-use anyhow::{anyhow, Context, Result};
-use core_foundation::{
-    array::{CFArray, CFArrayRef},
-    base::{CFRelease, CFRetain, TCFType},
-    string::{CFString, CFStringRef},
-};
-use futures::{
-    channel::{mpsc, oneshot},
-    Future,
-};
-pub use media::core_video::CVImageBuffer;
-use media::core_video::CVImageBufferRef;
-use parking_lot::Mutex;
-use postage::watch;
-use std::{
-    ffi::c_void,
-    sync::{Arc, Weak},
-};
-
-// SAFETY: Most live kit types are threadsafe:
-// https://github.com/livekit/client-sdk-swift#thread-safety
-macro_rules! pointer_type {
-    ($pointer_name:ident) => {
-        #[repr(transparent)]
-        #[derive(Copy, Clone, Debug)]
-        pub struct $pointer_name(pub *const std::ffi::c_void);
-        unsafe impl Send for $pointer_name {}
-    };
-}
-
-mod swift {
-    pointer_type!(Room);
-    pointer_type!(LocalAudioTrack);
-    pointer_type!(RemoteAudioTrack);
-    pointer_type!(LocalVideoTrack);
-    pointer_type!(RemoteVideoTrack);
-    pointer_type!(LocalTrackPublication);
-    pointer_type!(RemoteTrackPublication);
-    pointer_type!(MacOSDisplay);
-    pointer_type!(RoomDelegate);
-}
-
-extern "C" {
-    fn LKRoomDelegateCreate(
-        callback_data: *mut c_void,
-        on_did_disconnect: extern "C" fn(callback_data: *mut c_void),
-        on_did_subscribe_to_remote_audio_track: extern "C" fn(
-            callback_data: *mut c_void,
-            publisher_id: CFStringRef,
-            track_id: CFStringRef,
-            remote_track: swift::RemoteAudioTrack,
-            remote_publication: swift::RemoteTrackPublication,
-        ),
-        on_did_unsubscribe_from_remote_audio_track: extern "C" fn(
-            callback_data: *mut c_void,
-            publisher_id: CFStringRef,
-            track_id: CFStringRef,
-        ),
-        on_mute_changed_from_remote_audio_track: extern "C" fn(
-            callback_data: *mut c_void,
-            track_id: CFStringRef,
-            muted: bool,
-        ),
-        on_active_speakers_changed: extern "C" fn(
-            callback_data: *mut c_void,
-            participants: CFArrayRef,
-        ),
-        on_did_subscribe_to_remote_video_track: extern "C" fn(
-            callback_data: *mut c_void,
-            publisher_id: CFStringRef,
-            track_id: CFStringRef,
-            remote_track: swift::RemoteVideoTrack,
-        ),
-        on_did_unsubscribe_from_remote_video_track: extern "C" fn(
-            callback_data: *mut c_void,
-            publisher_id: CFStringRef,
-            track_id: CFStringRef,
-        ),
-    ) -> swift::RoomDelegate;
-
-    fn LKRoomCreate(delegate: swift::RoomDelegate) -> swift::Room;
-    fn LKRoomConnect(
-        room: swift::Room,
-        url: CFStringRef,
-        token: CFStringRef,
-        callback: extern "C" fn(*mut c_void, CFStringRef),
-        callback_data: *mut c_void,
-    );
-    fn LKRoomDisconnect(room: swift::Room);
-    fn LKRoomPublishVideoTrack(
-        room: swift::Room,
-        track: swift::LocalVideoTrack,
-        callback: extern "C" fn(*mut c_void, swift::LocalTrackPublication, CFStringRef),
-        callback_data: *mut c_void,
-    );
-    fn LKRoomPublishAudioTrack(
-        room: swift::Room,
-        track: swift::LocalAudioTrack,
-        callback: extern "C" fn(*mut c_void, swift::LocalTrackPublication, CFStringRef),
-        callback_data: *mut c_void,
-    );
-    fn LKRoomUnpublishTrack(room: swift::Room, publication: swift::LocalTrackPublication);
-
-    fn LKRoomAudioTracksForRemoteParticipant(
-        room: swift::Room,
-        participant_id: CFStringRef,
-    ) -> CFArrayRef;
-
-    fn LKRoomAudioTrackPublicationsForRemoteParticipant(
-        room: swift::Room,
-        participant_id: CFStringRef,
-    ) -> CFArrayRef;
-
-    fn LKRoomVideoTracksForRemoteParticipant(
-        room: swift::Room,
-        participant_id: CFStringRef,
-    ) -> CFArrayRef;
-
-    fn LKVideoRendererCreate(
-        callback_data: *mut c_void,
-        on_frame: extern "C" fn(callback_data: *mut c_void, frame: CVImageBufferRef) -> bool,
-        on_drop: extern "C" fn(callback_data: *mut c_void),
-    ) -> *const c_void;
-
-    fn LKRemoteAudioTrackGetSid(track: swift::RemoteAudioTrack) -> CFStringRef;
-    fn LKVideoTrackAddRenderer(track: swift::RemoteVideoTrack, renderer: *const c_void);
-    fn LKRemoteVideoTrackGetSid(track: swift::RemoteVideoTrack) -> CFStringRef;
-
-    fn LKDisplaySources(
-        callback_data: *mut c_void,
-        callback: extern "C" fn(
-            callback_data: *mut c_void,
-            sources: CFArrayRef,
-            error: CFStringRef,
-        ),
-    );
-    fn LKCreateScreenShareTrackForDisplay(display: swift::MacOSDisplay) -> swift::LocalVideoTrack;
-    fn LKLocalAudioTrackCreateTrack() -> swift::LocalAudioTrack;
-
-    fn LKLocalTrackPublicationSetMute(
-        publication: swift::LocalTrackPublication,
-        muted: bool,
-        on_complete: extern "C" fn(callback_data: *mut c_void, error: CFStringRef),
-        callback_data: *mut c_void,
-    );
-
-    fn LKRemoteTrackPublicationSetEnabled(
-        publication: swift::RemoteTrackPublication,
-        enabled: bool,
-        on_complete: extern "C" fn(callback_data: *mut c_void, error: CFStringRef),
-        callback_data: *mut c_void,
-    );
-
-    fn LKRemoteTrackPublicationIsMuted(publication: swift::RemoteTrackPublication) -> bool;
-    fn LKRemoteTrackPublicationGetSid(publication: swift::RemoteTrackPublication) -> CFStringRef;
-}
-
-pub type Sid = String;
-
-#[derive(Clone, Eq, PartialEq)]
-pub enum ConnectionState {
-    Disconnected,
-    Connected { url: String, token: String },
-}
-
-pub struct Room {
-    native_room: Mutex<swift::Room>,
-    connection: Mutex<(
-        watch::Sender<ConnectionState>,
-        watch::Receiver<ConnectionState>,
-    )>,
-    remote_audio_track_subscribers: Mutex<Vec<mpsc::UnboundedSender<RemoteAudioTrackUpdate>>>,
-    remote_video_track_subscribers: Mutex<Vec<mpsc::UnboundedSender<RemoteVideoTrackUpdate>>>,
-    _delegate: Mutex<RoomDelegate>,
-}
-
-trait AssertSendSync: Send {}
-impl AssertSendSync for Room {}
-
-impl Room {
-    pub fn new() -> Arc<Self> {
-        Arc::new_cyclic(|weak_room| {
-            let delegate = RoomDelegate::new(weak_room.clone());
-            Self {
-                native_room: Mutex::new(unsafe { LKRoomCreate(delegate.native_delegate) }),
-                connection: Mutex::new(watch::channel_with(ConnectionState::Disconnected)),
-                remote_audio_track_subscribers: Default::default(),
-                remote_video_track_subscribers: Default::default(),
-                _delegate: Mutex::new(delegate),
-            }
-        })
-    }
-
-    pub fn status(&self) -> watch::Receiver<ConnectionState> {
-        self.connection.lock().1.clone()
-    }
-
-    pub fn connect(self: &Arc<Self>, url: &str, token: &str) -> impl Future<Output = Result<()>> {
-        let url = CFString::new(url);
-        let token = CFString::new(token);
-        let (did_connect, tx, rx) = Self::build_done_callback();
-        unsafe {
-            LKRoomConnect(
-                *self.native_room.lock(),
-                url.as_concrete_TypeRef(),
-                token.as_concrete_TypeRef(),
-                did_connect,
-                tx,
-            )
-        }
-
-        let this = self.clone();
-        let url = url.to_string();
-        let token = token.to_string();
-        async move {
-            rx.await.unwrap().context("error connecting to room")?;
-            *this.connection.lock().0.borrow_mut() = ConnectionState::Connected { url, token };
-            Ok(())
-        }
-    }
-
-    fn did_disconnect(&self) {
-        *self.connection.lock().0.borrow_mut() = ConnectionState::Disconnected;
-    }
-
-    pub fn display_sources(self: &Arc<Self>) -> impl Future<Output = Result<Vec<MacOSDisplay>>> {
-        extern "C" fn callback(tx: *mut c_void, sources: CFArrayRef, error: CFStringRef) {
-            unsafe {
-                let tx = Box::from_raw(tx as *mut oneshot::Sender<Result<Vec<MacOSDisplay>>>);
-
-                if sources.is_null() {
-                    let _ = tx.send(Err(anyhow!("{}", CFString::wrap_under_get_rule(error))));
-                } else {
-                    let sources = CFArray::wrap_under_get_rule(sources)
-                        .into_iter()
-                        .map(|source| MacOSDisplay::new(swift::MacOSDisplay(*source)))
-                        .collect();
-
-                    let _ = tx.send(Ok(sources));
-                }
-            }
-        }
-
-        let (tx, rx) = oneshot::channel();
-
-        unsafe {
-            LKDisplaySources(Box::into_raw(Box::new(tx)) as *mut _, callback);
-        }
-
-        async move { rx.await.unwrap() }
-    }
-
-    pub fn publish_video_track(
-        self: &Arc<Self>,
-        track: LocalVideoTrack,
-    ) -> impl Future<Output = Result<LocalTrackPublication>> {
-        let (tx, rx) = oneshot::channel::<Result<LocalTrackPublication>>();
-        extern "C" fn callback(
-            tx: *mut c_void,
-            publication: swift::LocalTrackPublication,
-            error: CFStringRef,
-        ) {
-            let tx =
-                unsafe { Box::from_raw(tx as *mut oneshot::Sender<Result<LocalTrackPublication>>) };
-            if error.is_null() {
-                let _ = tx.send(Ok(LocalTrackPublication::new(publication)));
-            } else {
-                let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
-                let _ = tx.send(Err(anyhow!(error)));
-            }
-        }
-        unsafe {
-            LKRoomPublishVideoTrack(
-                *self.native_room.lock(),
-                track.0,
-                callback,
-                Box::into_raw(Box::new(tx)) as *mut c_void,
-            );
-        }
-        async { rx.await.unwrap().context("error publishing video track") }
-    }
-
-    pub fn publish_audio_track(
-        self: &Arc<Self>,
-        track: LocalAudioTrack,
-    ) -> impl Future<Output = Result<LocalTrackPublication>> {
-        let (tx, rx) = oneshot::channel::<Result<LocalTrackPublication>>();
-        extern "C" fn callback(
-            tx: *mut c_void,
-            publication: swift::LocalTrackPublication,
-            error: CFStringRef,
-        ) {
-            let tx =
-                unsafe { Box::from_raw(tx as *mut oneshot::Sender<Result<LocalTrackPublication>>) };
-            if error.is_null() {
-                let _ = tx.send(Ok(LocalTrackPublication::new(publication)));
-            } else {
-                let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
-                let _ = tx.send(Err(anyhow!(error)));
-            }
-        }
-        unsafe {
-            LKRoomPublishAudioTrack(
-                *self.native_room.lock(),
-                track.0,
-                callback,
-                Box::into_raw(Box::new(tx)) as *mut c_void,
-            );
-        }
-        async { rx.await.unwrap().context("error publishing audio track") }
-    }
-
-    pub fn unpublish_track(&self, publication: LocalTrackPublication) {
-        unsafe {
-            LKRoomUnpublishTrack(*self.native_room.lock(), publication.0);
-        }
-    }
-
-    pub fn remote_video_tracks(&self, participant_id: &str) -> Vec<Arc<RemoteVideoTrack>> {
-        unsafe {
-            let tracks = LKRoomVideoTracksForRemoteParticipant(
-                *self.native_room.lock(),
-                CFString::new(participant_id).as_concrete_TypeRef(),
-            );
-
-            if tracks.is_null() {
-                Vec::new()
-            } else {
-                let tracks = CFArray::wrap_under_get_rule(tracks);
-                tracks
-                    .into_iter()
-                    .map(|native_track| {
-                        let native_track = swift::RemoteVideoTrack(*native_track);
-                        let id =
-                            CFString::wrap_under_get_rule(LKRemoteVideoTrackGetSid(native_track))
-                                .to_string();
-                        Arc::new(RemoteVideoTrack::new(
-                            native_track,
-                            id,
-                            participant_id.into(),
-                        ))
-                    })
-                    .collect()
-            }
-        }
-    }
-
-    pub fn remote_audio_tracks(&self, participant_id: &str) -> Vec<Arc<RemoteAudioTrack>> {
-        unsafe {
-            let tracks = LKRoomAudioTracksForRemoteParticipant(
-                *self.native_room.lock(),
-                CFString::new(participant_id).as_concrete_TypeRef(),
-            );
-
-            if tracks.is_null() {
-                Vec::new()
-            } else {
-                let tracks = CFArray::wrap_under_get_rule(tracks);
-                tracks
-                    .into_iter()
-                    .map(|native_track| {
-                        let native_track = swift::RemoteAudioTrack(*native_track);
-                        let id =
-                            CFString::wrap_under_get_rule(LKRemoteAudioTrackGetSid(native_track))
-                                .to_string();
-                        Arc::new(RemoteAudioTrack::new(
-                            native_track,
-                            id,
-                            participant_id.into(),
-                        ))
-                    })
-                    .collect()
-            }
-        }
-    }
-
-    pub fn remote_audio_track_publications(
-        &self,
-        participant_id: &str,
-    ) -> Vec<Arc<RemoteTrackPublication>> {
-        unsafe {
-            let tracks = LKRoomAudioTrackPublicationsForRemoteParticipant(
-                *self.native_room.lock(),
-                CFString::new(participant_id).as_concrete_TypeRef(),
-            );
-
-            if tracks.is_null() {
-                Vec::new()
-            } else {
-                let tracks = CFArray::wrap_under_get_rule(tracks);
-                tracks
-                    .into_iter()
-                    .map(|native_track_publication| {
-                        let native_track_publication =
-                            swift::RemoteTrackPublication(*native_track_publication);
-                        Arc::new(RemoteTrackPublication::new(native_track_publication))
-                    })
-                    .collect()
-            }
-        }
-    }
-
-    pub fn remote_audio_track_updates(&self) -> mpsc::UnboundedReceiver<RemoteAudioTrackUpdate> {
-        let (tx, rx) = mpsc::unbounded();
-        self.remote_audio_track_subscribers.lock().push(tx);
-        rx
-    }
-
-    pub fn remote_video_track_updates(&self) -> mpsc::UnboundedReceiver<RemoteVideoTrackUpdate> {
-        let (tx, rx) = mpsc::unbounded();
-        self.remote_video_track_subscribers.lock().push(tx);
-        rx
-    }
-
-    fn did_subscribe_to_remote_audio_track(
-        &self,
-        track: RemoteAudioTrack,
-        publication: RemoteTrackPublication,
-    ) {
-        let track = Arc::new(track);
-        let publication = Arc::new(publication);
-        self.remote_audio_track_subscribers.lock().retain(|tx| {
-            tx.unbounded_send(RemoteAudioTrackUpdate::Subscribed(
-                track.clone(),
-                publication.clone(),
-            ))
-            .is_ok()
-        });
-    }
-
-    fn did_unsubscribe_from_remote_audio_track(&self, publisher_id: String, track_id: String) {
-        self.remote_audio_track_subscribers.lock().retain(|tx| {
-            tx.unbounded_send(RemoteAudioTrackUpdate::Unsubscribed {
-                publisher_id: publisher_id.clone(),
-                track_id: track_id.clone(),
-            })
-            .is_ok()
-        });
-    }
-
-    fn mute_changed_from_remote_audio_track(&self, track_id: String, muted: bool) {
-        self.remote_audio_track_subscribers.lock().retain(|tx| {
-            tx.unbounded_send(RemoteAudioTrackUpdate::MuteChanged {
-                track_id: track_id.clone(),
-                muted,
-            })
-            .is_ok()
-        });
-    }
-
-    // A vec of publisher IDs
-    fn active_speakers_changed(&self, speakers: Vec<String>) {
-        self.remote_audio_track_subscribers
-            .lock()
-            .retain(move |tx| {
-                tx.unbounded_send(RemoteAudioTrackUpdate::ActiveSpeakersChanged {
-                    speakers: speakers.clone(),
-                })
-                .is_ok()
-            });
-    }
-
-    fn did_subscribe_to_remote_video_track(&self, track: RemoteVideoTrack) {
-        let track = Arc::new(track);
-        self.remote_video_track_subscribers.lock().retain(|tx| {
-            tx.unbounded_send(RemoteVideoTrackUpdate::Subscribed(track.clone()))
-                .is_ok()
-        });
-    }
-
-    fn did_unsubscribe_from_remote_video_track(&self, publisher_id: String, track_id: String) {
-        self.remote_video_track_subscribers.lock().retain(|tx| {
-            tx.unbounded_send(RemoteVideoTrackUpdate::Unsubscribed {
-                publisher_id: publisher_id.clone(),
-                track_id: track_id.clone(),
-            })
-            .is_ok()
-        });
-    }
-
-    fn build_done_callback() -> (
-        extern "C" fn(*mut c_void, CFStringRef),
-        *mut c_void,
-        oneshot::Receiver<Result<()>>,
-    ) {
-        let (tx, rx) = oneshot::channel();
-        extern "C" fn done_callback(tx: *mut c_void, error: CFStringRef) {
-            let tx = unsafe { Box::from_raw(tx as *mut oneshot::Sender<Result<()>>) };
-            if error.is_null() {
-                let _ = tx.send(Ok(()));
-            } else {
-                let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
-                let _ = tx.send(Err(anyhow!(error)));
-            }
-        }
-        (
-            done_callback,
-            Box::into_raw(Box::new(tx)) as *mut c_void,
-            rx,
-        )
-    }
-
-    pub fn set_display_sources(&self, _: Vec<MacOSDisplay>) {
-        unreachable!("This is a test-only function")
-    }
-}
-
-impl Drop for Room {
-    fn drop(&mut self) {
-        unsafe {
-            let native_room = &*self.native_room.lock();
-            LKRoomDisconnect(*native_room);
-            CFRelease(native_room.0);
-        }
-    }
-}
-
-struct RoomDelegate {
-    native_delegate: swift::RoomDelegate,
-    _weak_room: Weak<Room>,
-}
-
-impl RoomDelegate {
-    fn new(weak_room: Weak<Room>) -> Self {
-        let native_delegate = unsafe {
-            LKRoomDelegateCreate(
-                weak_room.as_ptr() as *mut c_void,
-                Self::on_did_disconnect,
-                Self::on_did_subscribe_to_remote_audio_track,
-                Self::on_did_unsubscribe_from_remote_audio_track,
-                Self::on_mute_change_from_remote_audio_track,
-                Self::on_active_speakers_changed,
-                Self::on_did_subscribe_to_remote_video_track,
-                Self::on_did_unsubscribe_from_remote_video_track,
-            )
-        };
-        Self {
-            native_delegate,
-            _weak_room: weak_room,
-        }
-    }
-
-    extern "C" fn on_did_disconnect(room: *mut c_void) {
-        let room = unsafe { Weak::from_raw(room as *mut Room) };
-        if let Some(room) = room.upgrade() {
-            room.did_disconnect();
-        }
-        let _ = Weak::into_raw(room);
-    }
-
-    extern "C" fn on_did_subscribe_to_remote_audio_track(
-        room: *mut c_void,
-        publisher_id: CFStringRef,
-        track_id: CFStringRef,
-        track: swift::RemoteAudioTrack,
-        publication: swift::RemoteTrackPublication,
-    ) {
-        let room = unsafe { Weak::from_raw(room as *mut Room) };
-        let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() };
-        let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
-        let track = RemoteAudioTrack::new(track, track_id, publisher_id);
-        let publication = RemoteTrackPublication::new(publication);
-        if let Some(room) = room.upgrade() {
-            room.did_subscribe_to_remote_audio_track(track, publication);
-        }
-        let _ = Weak::into_raw(room);
-    }
-
-    extern "C" fn on_did_unsubscribe_from_remote_audio_track(
-        room: *mut c_void,
-        publisher_id: CFStringRef,
-        track_id: CFStringRef,
-    ) {
-        let room = unsafe { Weak::from_raw(room as *mut Room) };
-        let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() };
-        let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
-        if let Some(room) = room.upgrade() {
-            room.did_unsubscribe_from_remote_audio_track(publisher_id, track_id);
-        }
-        let _ = Weak::into_raw(room);
-    }
-
-    extern "C" fn on_mute_change_from_remote_audio_track(
-        room: *mut c_void,
-        track_id: CFStringRef,
-        muted: bool,
-    ) {
-        let room = unsafe { Weak::from_raw(room as *mut Room) };
-        let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
-        if let Some(room) = room.upgrade() {
-            room.mute_changed_from_remote_audio_track(track_id, muted);
-        }
-        let _ = Weak::into_raw(room);
-    }
-
-    extern "C" fn on_active_speakers_changed(room: *mut c_void, participants: CFArrayRef) {
-        if participants.is_null() {
-            return;
-        }
-
-        let room = unsafe { Weak::from_raw(room as *mut Room) };
-        let speakers = unsafe {
-            CFArray::wrap_under_get_rule(participants)
-                .into_iter()
-                .map(
-                    |speaker: core_foundation::base::ItemRef<'_, *const c_void>| {
-                        CFString::wrap_under_get_rule(*speaker as CFStringRef).to_string()
-                    },
-                )
-                .collect()
-        };
-
-        if let Some(room) = room.upgrade() {
-            room.active_speakers_changed(speakers);
-        }
-        let _ = Weak::into_raw(room);
-    }
-
-    extern "C" fn on_did_subscribe_to_remote_video_track(
-        room: *mut c_void,
-        publisher_id: CFStringRef,
-        track_id: CFStringRef,
-        track: swift::RemoteVideoTrack,
-    ) {
-        let room = unsafe { Weak::from_raw(room as *mut Room) };
-        let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() };
-        let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
-        let track = RemoteVideoTrack::new(track, track_id, publisher_id);
-        if let Some(room) = room.upgrade() {
-            room.did_subscribe_to_remote_video_track(track);
-        }
-        let _ = Weak::into_raw(room);
-    }
-
-    extern "C" fn on_did_unsubscribe_from_remote_video_track(
-        room: *mut c_void,
-        publisher_id: CFStringRef,
-        track_id: CFStringRef,
-    ) {
-        let room = unsafe { Weak::from_raw(room as *mut Room) };
-        let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() };
-        let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
-        if let Some(room) = room.upgrade() {
-            room.did_unsubscribe_from_remote_video_track(publisher_id, track_id);
-        }
-        let _ = Weak::into_raw(room);
-    }
-}
-
-impl Drop for RoomDelegate {
-    fn drop(&mut self) {
-        unsafe {
-            CFRelease(self.native_delegate.0);
-        }
-    }
-}
-
-pub struct LocalAudioTrack(swift::LocalAudioTrack);
-
-impl LocalAudioTrack {
-    pub fn create() -> Self {
-        Self(unsafe { LKLocalAudioTrackCreateTrack() })
-    }
-}
-
-impl Drop for LocalAudioTrack {
-    fn drop(&mut self) {
-        unsafe { CFRelease(self.0 .0) }
-    }
-}
-
-pub struct LocalVideoTrack(swift::LocalVideoTrack);
-
-impl LocalVideoTrack {
-    pub fn screen_share_for_display(display: &MacOSDisplay) -> Self {
-        Self(unsafe { LKCreateScreenShareTrackForDisplay(display.0) })
-    }
-}
-
-impl Drop for LocalVideoTrack {
-    fn drop(&mut self) {
-        unsafe { CFRelease(self.0 .0) }
-    }
-}
-
-pub struct LocalTrackPublication(swift::LocalTrackPublication);
-
-impl LocalTrackPublication {
-    pub fn new(native_track_publication: swift::LocalTrackPublication) -> Self {
-        unsafe {
-            CFRetain(native_track_publication.0);
-        }
-        Self(native_track_publication)
-    }
-
-    pub fn set_mute(&self, muted: bool) -> impl Future<Output = Result<()>> {
-        let (tx, rx) = futures::channel::oneshot::channel();
-
-        extern "C" fn complete_callback(callback_data: *mut c_void, error: CFStringRef) {
-            let tx = unsafe { Box::from_raw(callback_data as *mut oneshot::Sender<Result<()>>) };
-            if error.is_null() {
-                tx.send(Ok(())).ok();
-            } else {
-                let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
-                tx.send(Err(anyhow!(error))).ok();
-            }
-        }
-
-        unsafe {
-            LKLocalTrackPublicationSetMute(
-                self.0,
-                muted,
-                complete_callback,
-                Box::into_raw(Box::new(tx)) as *mut c_void,
-            )
-        }
-
-        async move { rx.await.unwrap() }
-    }
-}
-
-impl Drop for LocalTrackPublication {
-    fn drop(&mut self) {
-        unsafe { CFRelease(self.0 .0) }
-    }
-}
-
-pub struct RemoteTrackPublication {
-    native_publication: Mutex<swift::RemoteTrackPublication>,
-}
-
-impl RemoteTrackPublication {
-    pub fn new(native_track_publication: swift::RemoteTrackPublication) -> Self {
-        unsafe {
-            CFRetain(native_track_publication.0);
-        }
-        Self {
-            native_publication: Mutex::new(native_track_publication),
-        }
-    }
-
-    pub fn sid(&self) -> String {
-        unsafe {
-            CFString::wrap_under_get_rule(LKRemoteTrackPublicationGetSid(
-                *self.native_publication.lock(),
-            ))
-            .to_string()
-        }
-    }
-
-    pub fn is_muted(&self) -> bool {
-        unsafe { LKRemoteTrackPublicationIsMuted(*self.native_publication.lock()) }
-    }
-
-    pub fn set_enabled(&self, enabled: bool) -> impl Future<Output = Result<()>> {
-        let (tx, rx) = futures::channel::oneshot::channel();
-
-        extern "C" fn complete_callback(callback_data: *mut c_void, error: CFStringRef) {
-            let tx = unsafe { Box::from_raw(callback_data as *mut oneshot::Sender<Result<()>>) };
-            if error.is_null() {
-                tx.send(Ok(())).ok();
-            } else {
-                let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
-                tx.send(Err(anyhow!(error))).ok();
-            }
-        }
-
-        unsafe {
-            LKRemoteTrackPublicationSetEnabled(
-                *self.native_publication.lock(),
-                enabled,
-                complete_callback,
-                Box::into_raw(Box::new(tx)) as *mut c_void,
-            )
-        }
-
-        async move { rx.await.unwrap() }
-    }
-}
-
-impl Drop for RemoteTrackPublication {
-    fn drop(&mut self) {
-        unsafe { CFRelease((*self.native_publication.lock()).0) }
-    }
-}
-
-#[derive(Debug)]
-pub struct RemoteAudioTrack {
-    native_track: Mutex<swift::RemoteAudioTrack>,
-    sid: Sid,
-    publisher_id: String,
-}
-
-impl RemoteAudioTrack {
-    fn new(native_track: swift::RemoteAudioTrack, sid: Sid, publisher_id: String) -> Self {
-        unsafe {
-            CFRetain(native_track.0);
-        }
-        Self {
-            native_track: Mutex::new(native_track),
-            sid,
-            publisher_id,
-        }
-    }
-
-    pub fn sid(&self) -> &str {
-        &self.sid
-    }
-
-    pub fn publisher_id(&self) -> &str {
-        &self.publisher_id
-    }
-
-    pub fn enable(&self) -> impl Future<Output = Result<()>> {
-        async { Ok(()) }
-    }
-
-    pub fn disable(&self) -> impl Future<Output = Result<()>> {
-        async { Ok(()) }
-    }
-}
-
-impl Drop for RemoteAudioTrack {
-    fn drop(&mut self) {
-        unsafe { CFRelease(self.native_track.lock().0) }
-    }
-}
-
-#[derive(Debug)]
-pub struct RemoteVideoTrack {
-    native_track: Mutex<swift::RemoteVideoTrack>,
-    sid: Sid,
-    publisher_id: String,
-}
-
-impl RemoteVideoTrack {
-    fn new(native_track: swift::RemoteVideoTrack, sid: Sid, publisher_id: String) -> Self {
-        unsafe {
-            CFRetain(native_track.0);
-        }
-        Self {
-            native_track: Mutex::new(native_track),
-            sid,
-            publisher_id,
-        }
-    }
-
-    pub fn sid(&self) -> &str {
-        &self.sid
-    }
-
-    pub fn publisher_id(&self) -> &str {
-        &self.publisher_id
-    }
-
-    pub fn frames(&self) -> async_broadcast::Receiver<Frame> {
-        extern "C" fn on_frame(callback_data: *mut c_void, frame: CVImageBufferRef) -> bool {
-            unsafe {
-                let tx = Box::from_raw(callback_data as *mut async_broadcast::Sender<Frame>);
-                let buffer = CVImageBuffer::wrap_under_get_rule(frame);
-                let result = tx.try_broadcast(Frame(buffer));
-                let _ = Box::into_raw(tx);
-                match result {
-                    Ok(_) => true,
-                    Err(async_broadcast::TrySendError::Closed(_))
-                    | Err(async_broadcast::TrySendError::Inactive(_)) => {
-                        log::warn!("no active receiver for frame");
-                        false
-                    }
-                    Err(async_broadcast::TrySendError::Full(_)) => {
-                        log::warn!("skipping frame as receiver is not keeping up");
-                        true
-                    }
-                }
-            }
-        }
-
-        extern "C" fn on_drop(callback_data: *mut c_void) {
-            unsafe {
-                let _ = Box::from_raw(callback_data as *mut async_broadcast::Sender<Frame>);
-            }
-        }
-
-        let (tx, rx) = async_broadcast::broadcast(64);
-        unsafe {
-            let renderer = LKVideoRendererCreate(
-                Box::into_raw(Box::new(tx)) as *mut c_void,
-                on_frame,
-                on_drop,
-            );
-            LKVideoTrackAddRenderer(*self.native_track.lock(), renderer);
-            rx
-        }
-    }
-}
-
-impl Drop for RemoteVideoTrack {
-    fn drop(&mut self) {
-        unsafe { CFRelease(self.native_track.lock().0) }
-    }
-}
-
-pub enum RemoteVideoTrackUpdate {
-    Subscribed(Arc<RemoteVideoTrack>),
-    Unsubscribed { publisher_id: Sid, track_id: Sid },
-}
-
-pub enum RemoteAudioTrackUpdate {
-    ActiveSpeakersChanged { speakers: Vec<Sid> },
-    MuteChanged { track_id: Sid, muted: bool },
-    Subscribed(Arc<RemoteAudioTrack>, Arc<RemoteTrackPublication>),
-    Unsubscribed { publisher_id: Sid, track_id: Sid },
-}
-
-pub struct MacOSDisplay(swift::MacOSDisplay);
-
-impl MacOSDisplay {
-    fn new(ptr: swift::MacOSDisplay) -> Self {
-        unsafe {
-            CFRetain(ptr.0);
-        }
-        Self(ptr)
-    }
-}
-
-impl Drop for MacOSDisplay {
-    fn drop(&mut self) {
-        unsafe { CFRelease(self.0 .0) }
-    }
-}
-
-#[derive(Clone)]
-pub struct Frame(CVImageBuffer);
-
-impl Frame {
-    pub fn width(&self) -> usize {
-        self.0.width()
-    }
-
-    pub fn height(&self) -> usize {
-        self.0.height()
-    }
-
-    pub fn image(&self) -> CVImageBuffer {
-        self.0.clone()
-    }
-}

crates/live_kit_client2/src/test.rs 🔗

@@ -1,651 +0,0 @@
-use anyhow::{anyhow, Context, Result};
-use async_trait::async_trait;
-use collections::{BTreeMap, HashMap};
-use futures::Stream;
-use gpui::BackgroundExecutor;
-use live_kit_server::token;
-use media::core_video::CVImageBuffer;
-use parking_lot::Mutex;
-use postage::watch;
-use std::{future::Future, mem, sync::Arc};
-
-static SERVERS: Mutex<BTreeMap<String, Arc<TestServer>>> = Mutex::new(BTreeMap::new());
-
-pub struct TestServer {
-    pub url: String,
-    pub api_key: String,
-    pub secret_key: String,
-    rooms: Mutex<HashMap<String, TestServerRoom>>,
-    executor: BackgroundExecutor,
-}
-
-impl TestServer {
-    pub fn create(
-        url: String,
-        api_key: String,
-        secret_key: String,
-        executor: BackgroundExecutor,
-    ) -> Result<Arc<TestServer>> {
-        let mut servers = SERVERS.lock();
-        if servers.contains_key(&url) {
-            Err(anyhow!("a server with url {:?} already exists", url))
-        } else {
-            let server = Arc::new(TestServer {
-                url: url.clone(),
-                api_key,
-                secret_key,
-                rooms: Default::default(),
-                executor,
-            });
-            servers.insert(url, server.clone());
-            Ok(server)
-        }
-    }
-
-    fn get(url: &str) -> Result<Arc<TestServer>> {
-        Ok(SERVERS
-            .lock()
-            .get(url)
-            .ok_or_else(|| anyhow!("no server found for url"))?
-            .clone())
-    }
-
-    pub fn teardown(&self) -> Result<()> {
-        SERVERS
-            .lock()
-            .remove(&self.url)
-            .ok_or_else(|| anyhow!("server with url {:?} does not exist", self.url))?;
-        Ok(())
-    }
-
-    pub fn create_api_client(&self) -> TestApiClient {
-        TestApiClient {
-            url: self.url.clone(),
-        }
-    }
-
-    pub async fn create_room(&self, room: String) -> Result<()> {
-        self.executor.simulate_random_delay().await;
-        let mut server_rooms = self.rooms.lock();
-        if server_rooms.contains_key(&room) {
-            Err(anyhow!("room {:?} already exists", room))
-        } else {
-            server_rooms.insert(room, Default::default());
-            Ok(())
-        }
-    }
-
-    async fn delete_room(&self, room: String) -> Result<()> {
-        // TODO: clear state associated with all `Room`s.
-        self.executor.simulate_random_delay().await;
-        let mut server_rooms = self.rooms.lock();
-        server_rooms
-            .remove(&room)
-            .ok_or_else(|| anyhow!("room {:?} does not exist", room))?;
-        Ok(())
-    }
-
-    async fn join_room(&self, token: String, client_room: Arc<Room>) -> Result<()> {
-        self.executor.simulate_random_delay().await;
-        let claims = live_kit_server::token::validate(&token, &self.secret_key)?;
-        let identity = claims.sub.unwrap().to_string();
-        let room_name = claims.video.room.unwrap();
-        let mut server_rooms = self.rooms.lock();
-        let room = (*server_rooms).entry(room_name.to_string()).or_default();
-
-        if room.client_rooms.contains_key(&identity) {
-            Err(anyhow!(
-                "{:?} attempted to join room {:?} twice",
-                identity,
-                room_name
-            ))
-        } else {
-            for track in &room.video_tracks {
-                client_room
-                    .0
-                    .lock()
-                    .video_track_updates
-                    .0
-                    .try_broadcast(RemoteVideoTrackUpdate::Subscribed(track.clone()))
-                    .unwrap();
-            }
-            room.client_rooms.insert(identity, client_room);
-            Ok(())
-        }
-    }
-
-    async fn leave_room(&self, token: String) -> Result<()> {
-        self.executor.simulate_random_delay().await;
-        let claims = live_kit_server::token::validate(&token, &self.secret_key)?;
-        let identity = claims.sub.unwrap().to_string();
-        let room_name = claims.video.room.unwrap();
-        let mut server_rooms = self.rooms.lock();
-        let room = server_rooms
-            .get_mut(&*room_name)
-            .ok_or_else(|| anyhow!("room {} does not exist", room_name))?;
-        room.client_rooms.remove(&identity).ok_or_else(|| {
-            anyhow!(
-                "{:?} attempted to leave room {:?} before joining it",
-                identity,
-                room_name
-            )
-        })?;
-        Ok(())
-    }
-
-    async fn remove_participant(&self, room_name: String, identity: String) -> Result<()> {
-        // TODO: clear state associated with the `Room`.
-
-        self.executor.simulate_random_delay().await;
-        let mut server_rooms = self.rooms.lock();
-        let room = server_rooms
-            .get_mut(&room_name)
-            .ok_or_else(|| anyhow!("room {} does not exist", room_name))?;
-        room.client_rooms.remove(&identity).ok_or_else(|| {
-            anyhow!(
-                "participant {:?} did not join room {:?}",
-                identity,
-                room_name
-            )
-        })?;
-        Ok(())
-    }
-
-    pub async fn disconnect_client(&self, client_identity: String) {
-        self.executor.simulate_random_delay().await;
-        let mut server_rooms = self.rooms.lock();
-        for room in server_rooms.values_mut() {
-            if let Some(room) = room.client_rooms.remove(&client_identity) {
-                *room.0.lock().connection.0.borrow_mut() = ConnectionState::Disconnected;
-            }
-        }
-    }
-
-    async fn publish_video_track(&self, token: String, local_track: LocalVideoTrack) -> Result<()> {
-        self.executor.simulate_random_delay().await;
-        let claims = live_kit_server::token::validate(&token, &self.secret_key)?;
-        let identity = claims.sub.unwrap().to_string();
-        let room_name = claims.video.room.unwrap();
-
-        let mut server_rooms = self.rooms.lock();
-        let room = server_rooms
-            .get_mut(&*room_name)
-            .ok_or_else(|| anyhow!("room {} does not exist", room_name))?;
-
-        let track = Arc::new(RemoteVideoTrack {
-            sid: nanoid::nanoid!(17),
-            publisher_id: identity.clone(),
-            frames_rx: local_track.frames_rx.clone(),
-        });
-
-        room.video_tracks.push(track.clone());
-
-        for (id, client_room) in &room.client_rooms {
-            if *id != identity {
-                let _ = client_room
-                    .0
-                    .lock()
-                    .video_track_updates
-                    .0
-                    .try_broadcast(RemoteVideoTrackUpdate::Subscribed(track.clone()))
-                    .unwrap();
-            }
-        }
-
-        Ok(())
-    }
-
-    async fn publish_audio_track(
-        &self,
-        token: String,
-        _local_track: &LocalAudioTrack,
-    ) -> Result<()> {
-        self.executor.simulate_random_delay().await;
-        let claims = live_kit_server::token::validate(&token, &self.secret_key)?;
-        let identity = claims.sub.unwrap().to_string();
-        let room_name = claims.video.room.unwrap();
-
-        let mut server_rooms = self.rooms.lock();
-        let room = server_rooms
-            .get_mut(&*room_name)
-            .ok_or_else(|| anyhow!("room {} does not exist", room_name))?;
-
-        let track = Arc::new(RemoteAudioTrack {
-            sid: nanoid::nanoid!(17),
-            publisher_id: identity.clone(),
-        });
-
-        let publication = Arc::new(RemoteTrackPublication);
-
-        room.audio_tracks.push(track.clone());
-
-        for (id, client_room) in &room.client_rooms {
-            if *id != identity {
-                let _ = client_room
-                    .0
-                    .lock()
-                    .audio_track_updates
-                    .0
-                    .try_broadcast(RemoteAudioTrackUpdate::Subscribed(
-                        track.clone(),
-                        publication.clone(),
-                    ))
-                    .unwrap();
-            }
-        }
-
-        Ok(())
-    }
-
-    fn video_tracks(&self, token: String) -> Result<Vec<Arc<RemoteVideoTrack>>> {
-        let claims = live_kit_server::token::validate(&token, &self.secret_key)?;
-        let room_name = claims.video.room.unwrap();
-
-        let mut server_rooms = self.rooms.lock();
-        let room = server_rooms
-            .get_mut(&*room_name)
-            .ok_or_else(|| anyhow!("room {} does not exist", room_name))?;
-        Ok(room.video_tracks.clone())
-    }
-
-    fn audio_tracks(&self, token: String) -> Result<Vec<Arc<RemoteAudioTrack>>> {
-        let claims = live_kit_server::token::validate(&token, &self.secret_key)?;
-        let room_name = claims.video.room.unwrap();
-
-        let mut server_rooms = self.rooms.lock();
-        let room = server_rooms
-            .get_mut(&*room_name)
-            .ok_or_else(|| anyhow!("room {} does not exist", room_name))?;
-        Ok(room.audio_tracks.clone())
-    }
-}
-
-#[derive(Default)]
-struct TestServerRoom {
-    client_rooms: HashMap<Sid, Arc<Room>>,
-    video_tracks: Vec<Arc<RemoteVideoTrack>>,
-    audio_tracks: Vec<Arc<RemoteAudioTrack>>,
-}
-
-impl TestServerRoom {}
-
-pub struct TestApiClient {
-    url: String,
-}
-
-#[async_trait]
-impl live_kit_server::api::Client for TestApiClient {
-    fn url(&self) -> &str {
-        &self.url
-    }
-
-    async fn create_room(&self, name: String) -> Result<()> {
-        let server = TestServer::get(&self.url)?;
-        server.create_room(name).await?;
-        Ok(())
-    }
-
-    async fn delete_room(&self, name: String) -> Result<()> {
-        let server = TestServer::get(&self.url)?;
-        server.delete_room(name).await?;
-        Ok(())
-    }
-
-    async fn remove_participant(&self, room: String, identity: String) -> Result<()> {
-        let server = TestServer::get(&self.url)?;
-        server.remove_participant(room, identity).await?;
-        Ok(())
-    }
-
-    fn room_token(&self, room: &str, identity: &str) -> Result<String> {
-        let server = TestServer::get(&self.url)?;
-        token::create(
-            &server.api_key,
-            &server.secret_key,
-            Some(identity),
-            token::VideoGrant::to_join(room),
-        )
-    }
-
-    fn guest_token(&self, room: &str, identity: &str) -> Result<String> {
-        let server = TestServer::get(&self.url)?;
-        token::create(
-            &server.api_key,
-            &server.secret_key,
-            Some(identity),
-            token::VideoGrant::for_guest(room),
-        )
-    }
-}
-
-pub type Sid = String;
-
-struct RoomState {
-    connection: (
-        watch::Sender<ConnectionState>,
-        watch::Receiver<ConnectionState>,
-    ),
-    display_sources: Vec<MacOSDisplay>,
-    audio_track_updates: (
-        async_broadcast::Sender<RemoteAudioTrackUpdate>,
-        async_broadcast::Receiver<RemoteAudioTrackUpdate>,
-    ),
-    video_track_updates: (
-        async_broadcast::Sender<RemoteVideoTrackUpdate>,
-        async_broadcast::Receiver<RemoteVideoTrackUpdate>,
-    ),
-}
-
-#[derive(Clone, Eq, PartialEq)]
-pub enum ConnectionState {
-    Disconnected,
-    Connected { url: String, token: String },
-}
-
-pub struct Room(Mutex<RoomState>);
-
-impl Room {
-    pub fn new() -> Arc<Self> {
-        Arc::new(Self(Mutex::new(RoomState {
-            connection: watch::channel_with(ConnectionState::Disconnected),
-            display_sources: Default::default(),
-            video_track_updates: async_broadcast::broadcast(128),
-            audio_track_updates: async_broadcast::broadcast(128),
-        })))
-    }
-
-    pub fn status(&self) -> watch::Receiver<ConnectionState> {
-        self.0.lock().connection.1.clone()
-    }
-
-    pub fn connect(self: &Arc<Self>, url: &str, token: &str) -> impl Future<Output = Result<()>> {
-        let this = self.clone();
-        let url = url.to_string();
-        let token = token.to_string();
-        async move {
-            let server = TestServer::get(&url)?;
-            server
-                .join_room(token.clone(), this.clone())
-                .await
-                .context("room join")?;
-            *this.0.lock().connection.0.borrow_mut() = ConnectionState::Connected { url, token };
-            Ok(())
-        }
-    }
-
-    pub fn display_sources(self: &Arc<Self>) -> impl Future<Output = Result<Vec<MacOSDisplay>>> {
-        let this = self.clone();
-        async move {
-            let server = this.test_server();
-            server.executor.simulate_random_delay().await;
-            Ok(this.0.lock().display_sources.clone())
-        }
-    }
-
-    pub fn publish_video_track(
-        self: &Arc<Self>,
-        track: LocalVideoTrack,
-    ) -> impl Future<Output = Result<LocalTrackPublication>> {
-        let this = self.clone();
-        let track = track.clone();
-        async move {
-            this.test_server()
-                .publish_video_track(this.token(), track)
-                .await?;
-            Ok(LocalTrackPublication)
-        }
-    }
-    pub fn publish_audio_track(
-        self: &Arc<Self>,
-        track: LocalAudioTrack,
-    ) -> impl Future<Output = Result<LocalTrackPublication>> {
-        let this = self.clone();
-        let track = track.clone();
-        async move {
-            this.test_server()
-                .publish_audio_track(this.token(), &track)
-                .await?;
-            Ok(LocalTrackPublication)
-        }
-    }
-
-    pub fn unpublish_track(&self, _publication: LocalTrackPublication) {}
-
-    pub fn remote_audio_tracks(&self, publisher_id: &str) -> Vec<Arc<RemoteAudioTrack>> {
-        if !self.is_connected() {
-            return Vec::new();
-        }
-
-        self.test_server()
-            .audio_tracks(self.token())
-            .unwrap()
-            .into_iter()
-            .filter(|track| track.publisher_id() == publisher_id)
-            .collect()
-    }
-
-    pub fn remote_audio_track_publications(
-        &self,
-        publisher_id: &str,
-    ) -> Vec<Arc<RemoteTrackPublication>> {
-        if !self.is_connected() {
-            return Vec::new();
-        }
-
-        self.test_server()
-            .audio_tracks(self.token())
-            .unwrap()
-            .into_iter()
-            .filter(|track| track.publisher_id() == publisher_id)
-            .map(|_track| Arc::new(RemoteTrackPublication {}))
-            .collect()
-    }
-
-    pub fn remote_video_tracks(&self, publisher_id: &str) -> Vec<Arc<RemoteVideoTrack>> {
-        if !self.is_connected() {
-            return Vec::new();
-        }
-
-        self.test_server()
-            .video_tracks(self.token())
-            .unwrap()
-            .into_iter()
-            .filter(|track| track.publisher_id() == publisher_id)
-            .collect()
-    }
-
-    pub fn remote_audio_track_updates(&self) -> impl Stream<Item = RemoteAudioTrackUpdate> {
-        self.0.lock().audio_track_updates.1.clone()
-    }
-
-    pub fn remote_video_track_updates(&self) -> impl Stream<Item = RemoteVideoTrackUpdate> {
-        self.0.lock().video_track_updates.1.clone()
-    }
-
-    pub fn set_display_sources(&self, sources: Vec<MacOSDisplay>) {
-        self.0.lock().display_sources = sources;
-    }
-
-    fn test_server(&self) -> Arc<TestServer> {
-        match self.0.lock().connection.1.borrow().clone() {
-            ConnectionState::Disconnected => panic!("must be connected to call this method"),
-            ConnectionState::Connected { url, .. } => TestServer::get(&url).unwrap(),
-        }
-    }
-
-    fn token(&self) -> String {
-        match self.0.lock().connection.1.borrow().clone() {
-            ConnectionState::Disconnected => panic!("must be connected to call this method"),
-            ConnectionState::Connected { token, .. } => token,
-        }
-    }
-
-    fn is_connected(&self) -> bool {
-        match *self.0.lock().connection.1.borrow() {
-            ConnectionState::Disconnected => false,
-            ConnectionState::Connected { .. } => true,
-        }
-    }
-}
-
-impl Drop for Room {
-    fn drop(&mut self) {
-        if let ConnectionState::Connected { token, .. } = mem::replace(
-            &mut *self.0.lock().connection.0.borrow_mut(),
-            ConnectionState::Disconnected,
-        ) {
-            if let Ok(server) = TestServer::get(&token) {
-                let executor = server.executor.clone();
-                executor
-                    .spawn(async move { server.leave_room(token).await.unwrap() })
-                    .detach();
-            }
-        }
-    }
-}
-
-pub struct LocalTrackPublication;
-
-impl LocalTrackPublication {
-    pub fn set_mute(&self, _mute: bool) -> impl Future<Output = Result<()>> {
-        async { Ok(()) }
-    }
-}
-
-pub struct RemoteTrackPublication;
-
-impl RemoteTrackPublication {
-    pub fn set_enabled(&self, _enabled: bool) -> impl Future<Output = Result<()>> {
-        async { Ok(()) }
-    }
-
-    pub fn is_muted(&self) -> bool {
-        false
-    }
-
-    pub fn sid(&self) -> String {
-        "".to_string()
-    }
-}
-
-#[derive(Clone)]
-pub struct LocalVideoTrack {
-    frames_rx: async_broadcast::Receiver<Frame>,
-}
-
-impl LocalVideoTrack {
-    pub fn screen_share_for_display(display: &MacOSDisplay) -> Self {
-        Self {
-            frames_rx: display.frames.1.clone(),
-        }
-    }
-}
-
-#[derive(Clone)]
-pub struct LocalAudioTrack;
-
-impl LocalAudioTrack {
-    pub fn create() -> Self {
-        Self
-    }
-}
-
-#[derive(Debug)]
-pub struct RemoteVideoTrack {
-    sid: Sid,
-    publisher_id: Sid,
-    frames_rx: async_broadcast::Receiver<Frame>,
-}
-
-impl RemoteVideoTrack {
-    pub fn sid(&self) -> &str {
-        &self.sid
-    }
-
-    pub fn publisher_id(&self) -> &str {
-        &self.publisher_id
-    }
-
-    pub fn frames(&self) -> async_broadcast::Receiver<Frame> {
-        self.frames_rx.clone()
-    }
-}
-
-#[derive(Debug)]
-pub struct RemoteAudioTrack {
-    sid: Sid,
-    publisher_id: Sid,
-}
-
-impl RemoteAudioTrack {
-    pub fn sid(&self) -> &str {
-        &self.sid
-    }
-
-    pub fn publisher_id(&self) -> &str {
-        &self.publisher_id
-    }
-
-    pub fn enable(&self) -> impl Future<Output = Result<()>> {
-        async { Ok(()) }
-    }
-
-    pub fn disable(&self) -> impl Future<Output = Result<()>> {
-        async { Ok(()) }
-    }
-}
-
-#[derive(Clone)]
-pub enum RemoteVideoTrackUpdate {
-    Subscribed(Arc<RemoteVideoTrack>),
-    Unsubscribed { publisher_id: Sid, track_id: Sid },
-}
-
-#[derive(Clone)]
-pub enum RemoteAudioTrackUpdate {
-    ActiveSpeakersChanged { speakers: Vec<Sid> },
-    MuteChanged { track_id: Sid, muted: bool },
-    Subscribed(Arc<RemoteAudioTrack>, Arc<RemoteTrackPublication>),
-    Unsubscribed { publisher_id: Sid, track_id: Sid },
-}
-
-#[derive(Clone)]
-pub struct MacOSDisplay {
-    frames: (
-        async_broadcast::Sender<Frame>,
-        async_broadcast::Receiver<Frame>,
-    ),
-}
-
-impl MacOSDisplay {
-    pub fn new() -> Self {
-        Self {
-            frames: async_broadcast::broadcast(128),
-        }
-    }
-
-    pub fn send_frame(&self, frame: Frame) {
-        self.frames.0.try_broadcast(frame).unwrap();
-    }
-}
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct Frame {
-    pub label: String,
-    pub width: usize,
-    pub height: usize,
-}
-
-impl Frame {
-    pub fn width(&self) -> usize {
-        self.width
-    }
-
-    pub fn height(&self) -> usize {
-        self.height
-    }
-
-    pub fn image(&self) -> CVImageBuffer {
-        unimplemented!("you can't call this in test mode")
-    }
-}

crates/lsp/src/lsp.rs 🔗

@@ -5,7 +5,7 @@ pub use lsp_types::*;
 use anyhow::{anyhow, Context, Result};
 use collections::HashMap;
 use futures::{channel::oneshot, io::BufWriter, AsyncRead, AsyncWrite, FutureExt};
-use gpui::{executor, AsyncAppContext, Task};
+use gpui::{AsyncAppContext, BackgroundExecutor, Task};
 use parking_lot::Mutex;
 use postage::{barrier, prelude::Stream};
 use serde::{de::DeserializeOwned, Deserialize, Serialize};
@@ -62,7 +62,7 @@ pub struct LanguageServer {
     notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
     response_handlers: Arc<Mutex<Option<HashMap<usize, ResponseHandler>>>>,
     io_handlers: Arc<Mutex<HashMap<usize, IoHandler>>>,
-    executor: Arc<executor::Background>,
+    executor: BackgroundExecutor,
     #[allow(clippy::type_complexity)]
     io_tasks: Mutex<Option<(Task<Option<()>>, Task<Option<()>>)>>,
     output_done_rx: Mutex<Option<barrier::Receiver>>,
@@ -210,7 +210,7 @@ impl LanguageServer {
         Stdin: AsyncWrite + Unpin + Send + 'static,
         Stdout: AsyncRead + Unpin + Send + 'static,
         Stderr: AsyncRead + Unpin + Send + 'static,
-        F: FnMut(AnyNotification) + 'static + Send + Clone,
+        F: FnMut(AnyNotification) + 'static + Send + Sync + Clone,
     {
         let (outbound_tx, outbound_rx) = channel::unbounded::<String>();
         let (output_done_tx, output_done_rx) = barrier::channel();
@@ -220,30 +220,35 @@ impl LanguageServer {
             Arc::new(Mutex::new(Some(HashMap::<_, ResponseHandler>::default())));
         let io_handlers = Arc::new(Mutex::new(HashMap::default()));
 
-        let stdout_input_task = cx.spawn(|cx| {
-            Self::handle_input(
-                stdout,
-                on_unhandled_notification.clone(),
-                notification_handlers.clone(),
-                response_handlers.clone(),
-                io_handlers.clone(),
-                cx,
-            )
-            .log_err()
+        let stdout_input_task = cx.spawn({
+            let on_unhandled_notification = on_unhandled_notification.clone();
+            let notification_handlers = notification_handlers.clone();
+            let response_handlers = response_handlers.clone();
+            let io_handlers = io_handlers.clone();
+            move |cx| {
+                Self::handle_input(
+                    stdout,
+                    on_unhandled_notification,
+                    notification_handlers,
+                    response_handlers,
+                    io_handlers,
+                    cx,
+                )
+                .log_err()
+            }
         });
         let stderr_input_task = stderr
             .map(|stderr| {
-                cx.spawn(|_| {
-                    Self::handle_stderr(stderr, io_handlers.clone(), stderr_capture.clone())
-                        .log_err()
-                })
+                let io_handlers = io_handlers.clone();
+                let stderr_captures = stderr_capture.clone();
+                cx.spawn(|_| Self::handle_stderr(stderr, io_handlers, stderr_captures).log_err())
             })
             .unwrap_or_else(|| Task::Ready(Some(None)));
         let input_task = cx.spawn(|_| async move {
             let (stdout, stderr) = futures::join!(stdout_input_task, stderr_input_task);
             stdout.or(stderr)
         });
-        let output_task = cx.background().spawn({
+        let output_task = cx.background_executor().spawn({
             Self::handle_output(
                 stdin,
                 outbound_rx,
@@ -264,7 +269,7 @@ impl LanguageServer {
             code_action_kinds,
             next_id: Default::default(),
             outbound_tx,
-            executor: cx.background(),
+            executor: cx.background_executor().clone(),
             io_tasks: Mutex::new(Some((input_task, output_task))),
             output_done_rx: Mutex::new(Some(output_done_rx)),
             root_path: root_path.to_path_buf(),
@@ -481,10 +486,7 @@ impl LanguageServer {
                         completion_item: Some(CompletionItemCapability {
                             snippet_support: Some(true),
                             resolve_support: Some(CompletionItemCapabilityResolveSupport {
-                                properties: vec![
-                                    "documentation".to_string(),
-                                    "additionalTextEdits".to_string(),
-                                ],
+                                properties: vec!["additionalTextEdits".to_string()],
                             }),
                             ..Default::default()
                         }),
@@ -610,7 +612,7 @@ impl LanguageServer {
     where
         T: request::Request,
         T::Params: 'static + Send,
-        F: 'static + Send + FnMut(T::Params, AsyncAppContext) -> Fut,
+        F: 'static + FnMut(T::Params, AsyncAppContext) -> Fut + Send,
         Fut: 'static + Future<Output = Result<T::Result>>,
     {
         self.on_custom_request(T::METHOD, f)
@@ -644,7 +646,7 @@ impl LanguageServer {
     #[must_use]
     pub fn on_custom_notification<Params, F>(&self, method: &'static str, mut f: F) -> Subscription
     where
-        F: 'static + Send + FnMut(Params, AsyncAppContext),
+        F: 'static + FnMut(Params, AsyncAppContext) + Send,
         Params: DeserializeOwned,
     {
         let prev_handler = self.notification_handlers.lock().insert(
@@ -672,7 +674,7 @@ impl LanguageServer {
         mut f: F,
     ) -> Subscription
     where
-        F: 'static + Send + FnMut(Params, AsyncAppContext) -> Fut,
+        F: 'static + FnMut(Params, AsyncAppContext) -> Fut + Send,
         Fut: 'static + Future<Output = Result<Res>>,
         Params: DeserializeOwned + Send + 'static,
         Res: Serialize,
@@ -685,7 +687,7 @@ impl LanguageServer {
                     match serde_json::from_str(params) {
                         Ok(params) => {
                             let response = f(params, cx.clone());
-                            cx.foreground()
+                            cx.foreground_executor()
                                 .spawn({
                                     let outbound_tx = outbound_tx.clone();
                                     async move {
@@ -780,20 +782,11 @@ impl LanguageServer {
         )
     }
 
-    // some child of string literal (be it "" or ``) which is the child of an attribute
-
-    // <Foo className="bar" />
-    // <Foo className={`bar`} />
-    // <Foo className={something + "bar"} />
-    // <Foo className={something + "bar"} />
-    // const classes = "awesome ";
-    // <Foo className={classes} />
-
     fn request_internal<T: request::Request>(
         next_id: &AtomicUsize,
         response_handlers: &Mutex<Option<HashMap<usize, ResponseHandler>>>,
         outbound_tx: &channel::Sender<String>,
-        executor: &Arc<executor::Background>,
+        executor: &BackgroundExecutor,
         params: T::Params,
     ) -> impl 'static + Future<Output = anyhow::Result<T::Result>>
     where
@@ -1071,8 +1064,9 @@ impl FakeLanguageServer {
             .on_request::<T, _, _>(move |params, cx| {
                 let result = handler(params, cx.clone());
                 let responded_tx = responded_tx.clone();
+                let executor = cx.background_executor().clone();
                 async move {
-                    cx.background().simulate_random_delay().await;
+                    executor.simulate_random_delay().await;
                     let result = result.await;
                     responded_tx.unbounded_send(()).ok();
                     result

crates/lsp2/Cargo.toml 🔗

@@ -1,38 +0,0 @@
-[package]
-name = "lsp2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/lsp2.rs"
-doctest = false
-
-[features]
-test-support = ["async-pipe"]
-
-[dependencies]
-collections = { path = "../collections" }
-gpui = { package = "gpui2", path = "../gpui2" }
-util = { path = "../util" }
-
-anyhow.workspace = true
-async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553", optional = true }
-futures.workspace = true
-log.workspace = true
-lsp-types = { git = "https://github.com/zed-industries/lsp-types", branch = "updated-completion-list-item-defaults" }
-parking_lot.workspace = true
-postage.workspace = true
-serde.workspace = true
-serde_derive.workspace = true
-serde_json.workspace = true
-smol.workspace = true
-
-[dev-dependencies]
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-util = { path = "../util", features = ["test-support"] }
-
-async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553" }
-ctor.workspace = true
-env_logger.workspace = true
-unindent.workspace = true

crates/lsp2/src/lsp2.rs 🔗

@@ -1,1197 +0,0 @@
-use log::warn;
-pub use lsp_types::request::*;
-pub use lsp_types::*;
-
-use anyhow::{anyhow, Context, Result};
-use collections::HashMap;
-use futures::{channel::oneshot, io::BufWriter, AsyncRead, AsyncWrite, FutureExt};
-use gpui::{AsyncAppContext, BackgroundExecutor, Task};
-use parking_lot::Mutex;
-use postage::{barrier, prelude::Stream};
-use serde::{de::DeserializeOwned, Deserialize, Serialize};
-use serde_json::{json, value::RawValue, Value};
-use smol::{
-    channel,
-    io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader},
-    process::{self, Child},
-};
-use std::{
-    ffi::OsString,
-    fmt,
-    future::Future,
-    io::Write,
-    path::PathBuf,
-    str::{self, FromStr as _},
-    sync::{
-        atomic::{AtomicUsize, Ordering::SeqCst},
-        Arc, Weak,
-    },
-    time::{Duration, Instant},
-};
-use std::{path::Path, process::Stdio};
-use util::{ResultExt, TryFutureExt};
-
-const JSON_RPC_VERSION: &str = "2.0";
-const CONTENT_LEN_HEADER: &str = "Content-Length: ";
-const LSP_REQUEST_TIMEOUT: Duration = Duration::from_secs(60 * 2);
-
-type NotificationHandler = Box<dyn Send + FnMut(Option<usize>, &str, AsyncAppContext)>;
-type ResponseHandler = Box<dyn Send + FnOnce(Result<String, Error>)>;
-type IoHandler = Box<dyn Send + FnMut(IoKind, &str)>;
-
-#[derive(Debug, Clone, Copy)]
-pub enum IoKind {
-    StdOut,
-    StdIn,
-    StdErr,
-}
-
-#[derive(Debug, Clone, Deserialize)]
-pub struct LanguageServerBinary {
-    pub path: PathBuf,
-    pub arguments: Vec<OsString>,
-}
-
-pub struct LanguageServer {
-    server_id: LanguageServerId,
-    next_id: AtomicUsize,
-    outbound_tx: channel::Sender<String>,
-    name: String,
-    capabilities: ServerCapabilities,
-    code_action_kinds: Option<Vec<CodeActionKind>>,
-    notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
-    response_handlers: Arc<Mutex<Option<HashMap<usize, ResponseHandler>>>>,
-    io_handlers: Arc<Mutex<HashMap<usize, IoHandler>>>,
-    executor: BackgroundExecutor,
-    #[allow(clippy::type_complexity)]
-    io_tasks: Mutex<Option<(Task<Option<()>>, Task<Option<()>>)>>,
-    output_done_rx: Mutex<Option<barrier::Receiver>>,
-    root_path: PathBuf,
-    _server: Option<Mutex<Child>>,
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
-#[repr(transparent)]
-pub struct LanguageServerId(pub usize);
-
-pub enum Subscription {
-    Notification {
-        method: &'static str,
-        notification_handlers: Option<Arc<Mutex<HashMap<&'static str, NotificationHandler>>>>,
-    },
-    Io {
-        id: usize,
-        io_handlers: Option<Weak<Mutex<HashMap<usize, IoHandler>>>>,
-    },
-}
-
-#[derive(Serialize, Deserialize)]
-pub struct Request<'a, T> {
-    jsonrpc: &'static str,
-    id: usize,
-    method: &'a str,
-    params: T,
-}
-
-#[derive(Serialize, Deserialize)]
-struct AnyResponse<'a> {
-    jsonrpc: &'a str,
-    id: usize,
-    #[serde(default)]
-    error: Option<Error>,
-    #[serde(borrow)]
-    result: Option<&'a RawValue>,
-}
-
-#[derive(Serialize)]
-struct Response<T> {
-    jsonrpc: &'static str,
-    id: usize,
-    result: Option<T>,
-    error: Option<Error>,
-}
-
-#[derive(Serialize, Deserialize)]
-struct Notification<'a, T> {
-    jsonrpc: &'static str,
-    #[serde(borrow)]
-    method: &'a str,
-    params: T,
-}
-
-#[derive(Debug, Clone, Deserialize)]
-struct AnyNotification<'a> {
-    #[serde(default)]
-    id: Option<usize>,
-    #[serde(borrow)]
-    method: &'a str,
-    #[serde(borrow, default)]
-    params: Option<&'a RawValue>,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-struct Error {
-    message: String,
-}
-
-impl LanguageServer {
-    pub fn new(
-        stderr_capture: Arc<Mutex<Option<String>>>,
-        server_id: LanguageServerId,
-        binary: LanguageServerBinary,
-        root_path: &Path,
-        code_action_kinds: Option<Vec<CodeActionKind>>,
-        cx: AsyncAppContext,
-    ) -> Result<Self> {
-        let working_dir = if root_path.is_dir() {
-            root_path
-        } else {
-            root_path.parent().unwrap_or_else(|| Path::new("/"))
-        };
-
-        let mut server = process::Command::new(&binary.path)
-            .current_dir(working_dir)
-            .args(binary.arguments)
-            .stdin(Stdio::piped())
-            .stdout(Stdio::piped())
-            .stderr(Stdio::piped())
-            .kill_on_drop(true)
-            .spawn()?;
-
-        let stdin = server.stdin.take().unwrap();
-        let stdout = server.stdout.take().unwrap();
-        let stderr = server.stderr.take().unwrap();
-        let mut server = Self::new_internal(
-            server_id.clone(),
-            stdin,
-            stdout,
-            Some(stderr),
-            stderr_capture,
-            Some(server),
-            root_path,
-            code_action_kinds,
-            cx,
-            move |notification| {
-                log::info!(
-                    "{} unhandled notification {}:\n{}",
-                    server_id,
-                    notification.method,
-                    serde_json::to_string_pretty(
-                        &notification
-                            .params
-                            .and_then(|params| Value::from_str(params.get()).ok())
-                            .unwrap_or(Value::Null)
-                    )
-                    .unwrap(),
-                );
-            },
-        );
-
-        if let Some(name) = binary.path.file_name() {
-            server.name = name.to_string_lossy().to_string();
-        }
-
-        Ok(server)
-    }
-
-    fn new_internal<Stdin, Stdout, Stderr, F>(
-        server_id: LanguageServerId,
-        stdin: Stdin,
-        stdout: Stdout,
-        stderr: Option<Stderr>,
-        stderr_capture: Arc<Mutex<Option<String>>>,
-        server: Option<Child>,
-        root_path: &Path,
-        code_action_kinds: Option<Vec<CodeActionKind>>,
-        cx: AsyncAppContext,
-        on_unhandled_notification: F,
-    ) -> Self
-    where
-        Stdin: AsyncWrite + Unpin + Send + 'static,
-        Stdout: AsyncRead + Unpin + Send + 'static,
-        Stderr: AsyncRead + Unpin + Send + 'static,
-        F: FnMut(AnyNotification) + 'static + Send + Sync + Clone,
-    {
-        let (outbound_tx, outbound_rx) = channel::unbounded::<String>();
-        let (output_done_tx, output_done_rx) = barrier::channel();
-        let notification_handlers =
-            Arc::new(Mutex::new(HashMap::<_, NotificationHandler>::default()));
-        let response_handlers =
-            Arc::new(Mutex::new(Some(HashMap::<_, ResponseHandler>::default())));
-        let io_handlers = Arc::new(Mutex::new(HashMap::default()));
-
-        let stdout_input_task = cx.spawn({
-            let on_unhandled_notification = on_unhandled_notification.clone();
-            let notification_handlers = notification_handlers.clone();
-            let response_handlers = response_handlers.clone();
-            let io_handlers = io_handlers.clone();
-            move |cx| {
-                Self::handle_input(
-                    stdout,
-                    on_unhandled_notification,
-                    notification_handlers,
-                    response_handlers,
-                    io_handlers,
-                    cx,
-                )
-                .log_err()
-            }
-        });
-        let stderr_input_task = stderr
-            .map(|stderr| {
-                let io_handlers = io_handlers.clone();
-                let stderr_captures = stderr_capture.clone();
-                cx.spawn(|_| Self::handle_stderr(stderr, io_handlers, stderr_captures).log_err())
-            })
-            .unwrap_or_else(|| Task::Ready(Some(None)));
-        let input_task = cx.spawn(|_| async move {
-            let (stdout, stderr) = futures::join!(stdout_input_task, stderr_input_task);
-            stdout.or(stderr)
-        });
-        let output_task = cx.background_executor().spawn({
-            Self::handle_output(
-                stdin,
-                outbound_rx,
-                output_done_tx,
-                response_handlers.clone(),
-                io_handlers.clone(),
-            )
-            .log_err()
-        });
-
-        Self {
-            server_id,
-            notification_handlers,
-            response_handlers,
-            io_handlers,
-            name: Default::default(),
-            capabilities: Default::default(),
-            code_action_kinds,
-            next_id: Default::default(),
-            outbound_tx,
-            executor: cx.background_executor().clone(),
-            io_tasks: Mutex::new(Some((input_task, output_task))),
-            output_done_rx: Mutex::new(Some(output_done_rx)),
-            root_path: root_path.to_path_buf(),
-            _server: server.map(|server| Mutex::new(server)),
-        }
-    }
-
-    pub fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
-        self.code_action_kinds.clone()
-    }
-
-    async fn handle_input<Stdout, F>(
-        stdout: Stdout,
-        mut on_unhandled_notification: F,
-        notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
-        response_handlers: Arc<Mutex<Option<HashMap<usize, ResponseHandler>>>>,
-        io_handlers: Arc<Mutex<HashMap<usize, IoHandler>>>,
-        cx: AsyncAppContext,
-    ) -> anyhow::Result<()>
-    where
-        Stdout: AsyncRead + Unpin + Send + 'static,
-        F: FnMut(AnyNotification) + 'static + Send,
-    {
-        let mut stdout = BufReader::new(stdout);
-        let _clear_response_handlers = util::defer({
-            let response_handlers = response_handlers.clone();
-            move || {
-                response_handlers.lock().take();
-            }
-        });
-        let mut buffer = Vec::new();
-        loop {
-            buffer.clear();
-            stdout.read_until(b'\n', &mut buffer).await?;
-            stdout.read_until(b'\n', &mut buffer).await?;
-            let header = std::str::from_utf8(&buffer)?;
-            let message_len: usize = header
-                .strip_prefix(CONTENT_LEN_HEADER)
-                .ok_or_else(|| anyhow!("invalid LSP message header {header:?}"))?
-                .trim_end()
-                .parse()?;
-
-            buffer.resize(message_len, 0);
-            stdout.read_exact(&mut buffer).await?;
-
-            if let Ok(message) = str::from_utf8(&buffer) {
-                log::trace!("incoming message: {}", message);
-                for handler in io_handlers.lock().values_mut() {
-                    handler(IoKind::StdOut, message);
-                }
-            }
-
-            if let Ok(msg) = serde_json::from_slice::<AnyNotification>(&buffer) {
-                if let Some(handler) = notification_handlers.lock().get_mut(msg.method) {
-                    handler(
-                        msg.id,
-                        &msg.params.map(|params| params.get()).unwrap_or("null"),
-                        cx.clone(),
-                    );
-                } else {
-                    on_unhandled_notification(msg);
-                }
-            } else if let Ok(AnyResponse {
-                id, error, result, ..
-            }) = serde_json::from_slice(&buffer)
-            {
-                if let Some(handler) = response_handlers
-                    .lock()
-                    .as_mut()
-                    .and_then(|handlers| handlers.remove(&id))
-                {
-                    if let Some(error) = error {
-                        handler(Err(error));
-                    } else if let Some(result) = result {
-                        handler(Ok(result.get().into()));
-                    } else {
-                        handler(Ok("null".into()));
-                    }
-                }
-            } else {
-                warn!(
-                    "failed to deserialize LSP message:\n{}",
-                    std::str::from_utf8(&buffer)?
-                );
-            }
-
-            // Don't starve the main thread when receiving lots of messages at once.
-            smol::future::yield_now().await;
-        }
-    }
-
-    async fn handle_stderr<Stderr>(
-        stderr: Stderr,
-        io_handlers: Arc<Mutex<HashMap<usize, IoHandler>>>,
-        stderr_capture: Arc<Mutex<Option<String>>>,
-    ) -> anyhow::Result<()>
-    where
-        Stderr: AsyncRead + Unpin + Send + 'static,
-    {
-        let mut stderr = BufReader::new(stderr);
-        let mut buffer = Vec::new();
-
-        loop {
-            buffer.clear();
-            stderr.read_until(b'\n', &mut buffer).await?;
-            if let Ok(message) = str::from_utf8(&buffer) {
-                log::trace!("incoming stderr message:{message}");
-                for handler in io_handlers.lock().values_mut() {
-                    handler(IoKind::StdErr, message);
-                }
-
-                if let Some(stderr) = stderr_capture.lock().as_mut() {
-                    stderr.push_str(message);
-                }
-            }
-
-            // Don't starve the main thread when receiving lots of messages at once.
-            smol::future::yield_now().await;
-        }
-    }
-
-    async fn handle_output<Stdin>(
-        stdin: Stdin,
-        outbound_rx: channel::Receiver<String>,
-        output_done_tx: barrier::Sender,
-        response_handlers: Arc<Mutex<Option<HashMap<usize, ResponseHandler>>>>,
-        io_handlers: Arc<Mutex<HashMap<usize, IoHandler>>>,
-    ) -> anyhow::Result<()>
-    where
-        Stdin: AsyncWrite + Unpin + Send + 'static,
-    {
-        let mut stdin = BufWriter::new(stdin);
-        let _clear_response_handlers = util::defer({
-            let response_handlers = response_handlers.clone();
-            move || {
-                response_handlers.lock().take();
-            }
-        });
-        let mut content_len_buffer = Vec::new();
-        while let Ok(message) = outbound_rx.recv().await {
-            log::trace!("outgoing message:{}", message);
-            for handler in io_handlers.lock().values_mut() {
-                handler(IoKind::StdIn, &message);
-            }
-
-            content_len_buffer.clear();
-            write!(content_len_buffer, "{}", message.len()).unwrap();
-            stdin.write_all(CONTENT_LEN_HEADER.as_bytes()).await?;
-            stdin.write_all(&content_len_buffer).await?;
-            stdin.write_all("\r\n\r\n".as_bytes()).await?;
-            stdin.write_all(message.as_bytes()).await?;
-            stdin.flush().await?;
-        }
-        drop(output_done_tx);
-        Ok(())
-    }
-
-    /// Initializes a language server.
-    /// Note that `options` is used directly to construct [`InitializeParams`],
-    /// which is why it is owned.
-    pub async fn initialize(mut self, options: Option<Value>) -> Result<Arc<Self>> {
-        let root_uri = Url::from_file_path(&self.root_path).unwrap();
-        #[allow(deprecated)]
-        let params = InitializeParams {
-            process_id: None,
-            root_path: None,
-            root_uri: Some(root_uri.clone()),
-            initialization_options: options,
-            capabilities: ClientCapabilities {
-                workspace: Some(WorkspaceClientCapabilities {
-                    configuration: Some(true),
-                    did_change_watched_files: Some(DidChangeWatchedFilesClientCapabilities {
-                        dynamic_registration: Some(true),
-                        relative_pattern_support: Some(true),
-                    }),
-                    did_change_configuration: Some(DynamicRegistrationClientCapabilities {
-                        dynamic_registration: Some(true),
-                    }),
-                    workspace_folders: Some(true),
-                    symbol: Some(WorkspaceSymbolClientCapabilities {
-                        resolve_support: None,
-                        ..WorkspaceSymbolClientCapabilities::default()
-                    }),
-                    inlay_hint: Some(InlayHintWorkspaceClientCapabilities {
-                        refresh_support: Some(true),
-                    }),
-                    diagnostic: Some(DiagnosticWorkspaceClientCapabilities {
-                        refresh_support: None,
-                    }),
-                    ..Default::default()
-                }),
-                text_document: Some(TextDocumentClientCapabilities {
-                    definition: Some(GotoCapability {
-                        link_support: Some(true),
-                        dynamic_registration: None,
-                    }),
-                    code_action: Some(CodeActionClientCapabilities {
-                        code_action_literal_support: Some(CodeActionLiteralSupport {
-                            code_action_kind: CodeActionKindLiteralSupport {
-                                value_set: vec![
-                                    CodeActionKind::REFACTOR.as_str().into(),
-                                    CodeActionKind::QUICKFIX.as_str().into(),
-                                    CodeActionKind::SOURCE.as_str().into(),
-                                ],
-                            },
-                        }),
-                        data_support: Some(true),
-                        resolve_support: Some(CodeActionCapabilityResolveSupport {
-                            properties: vec!["edit".to_string(), "command".to_string()],
-                        }),
-                        ..Default::default()
-                    }),
-                    completion: Some(CompletionClientCapabilities {
-                        completion_item: Some(CompletionItemCapability {
-                            snippet_support: Some(true),
-                            resolve_support: Some(CompletionItemCapabilityResolveSupport {
-                                properties: vec!["additionalTextEdits".to_string()],
-                            }),
-                            ..Default::default()
-                        }),
-                        completion_list: Some(CompletionListCapability {
-                            item_defaults: Some(vec![
-                                "commitCharacters".to_owned(),
-                                "editRange".to_owned(),
-                                "insertTextMode".to_owned(),
-                                "data".to_owned(),
-                            ]),
-                        }),
-                        ..Default::default()
-                    }),
-                    rename: Some(RenameClientCapabilities {
-                        prepare_support: Some(true),
-                        ..Default::default()
-                    }),
-                    hover: Some(HoverClientCapabilities {
-                        content_format: Some(vec![MarkupKind::Markdown]),
-                        dynamic_registration: None,
-                    }),
-                    inlay_hint: Some(InlayHintClientCapabilities {
-                        resolve_support: Some(InlayHintResolveClientCapabilities {
-                            properties: vec![
-                                "textEdits".to_string(),
-                                "tooltip".to_string(),
-                                "label.tooltip".to_string(),
-                                "label.location".to_string(),
-                                "label.command".to_string(),
-                            ],
-                        }),
-                        dynamic_registration: Some(false),
-                    }),
-                    publish_diagnostics: Some(PublishDiagnosticsClientCapabilities {
-                        related_information: Some(true),
-                        ..Default::default()
-                    }),
-                    formatting: Some(DynamicRegistrationClientCapabilities {
-                        dynamic_registration: None,
-                    }),
-                    on_type_formatting: Some(DynamicRegistrationClientCapabilities {
-                        dynamic_registration: None,
-                    }),
-                    diagnostic: Some(DiagnosticClientCapabilities {
-                        related_document_support: Some(true),
-                        dynamic_registration: None,
-                    }),
-                    ..Default::default()
-                }),
-                experimental: Some(json!({
-                    "serverStatusNotification": true,
-                })),
-                window: Some(WindowClientCapabilities {
-                    work_done_progress: Some(true),
-                    ..Default::default()
-                }),
-                general: None,
-            },
-            trace: None,
-            workspace_folders: Some(vec![WorkspaceFolder {
-                uri: root_uri,
-                name: Default::default(),
-            }]),
-            client_info: None,
-            locale: None,
-        };
-
-        let response = self.request::<request::Initialize>(params).await?;
-        if let Some(info) = response.server_info {
-            self.name = info.name;
-        }
-        self.capabilities = response.capabilities;
-
-        self.notify::<notification::Initialized>(InitializedParams {})?;
-        Ok(Arc::new(self))
-    }
-
-    pub fn shutdown(&self) -> Option<impl 'static + Send + Future<Output = Option<()>>> {
-        if let Some(tasks) = self.io_tasks.lock().take() {
-            let response_handlers = self.response_handlers.clone();
-            let next_id = AtomicUsize::new(self.next_id.load(SeqCst));
-            let outbound_tx = self.outbound_tx.clone();
-            let executor = self.executor.clone();
-            let mut output_done = self.output_done_rx.lock().take().unwrap();
-            let shutdown_request = Self::request_internal::<request::Shutdown>(
-                &next_id,
-                &response_handlers,
-                &outbound_tx,
-                &executor,
-                (),
-            );
-            let exit = Self::notify_internal::<notification::Exit>(&outbound_tx, ());
-            outbound_tx.close();
-            Some(
-                async move {
-                    log::debug!("language server shutdown started");
-                    shutdown_request.await?;
-                    response_handlers.lock().take();
-                    exit?;
-                    output_done.recv().await;
-                    log::debug!("language server shutdown finished");
-                    drop(tasks);
-                    anyhow::Ok(())
-                }
-                .log_err(),
-            )
-        } else {
-            None
-        }
-    }
-
-    #[must_use]
-    pub fn on_notification<T, F>(&self, f: F) -> Subscription
-    where
-        T: notification::Notification,
-        F: 'static + Send + FnMut(T::Params, AsyncAppContext),
-    {
-        self.on_custom_notification(T::METHOD, f)
-    }
-
-    #[must_use]
-    pub fn on_request<T, F, Fut>(&self, f: F) -> Subscription
-    where
-        T: request::Request,
-        T::Params: 'static + Send,
-        F: 'static + FnMut(T::Params, AsyncAppContext) -> Fut + Send,
-        Fut: 'static + Future<Output = Result<T::Result>>,
-    {
-        self.on_custom_request(T::METHOD, f)
-    }
-
-    #[must_use]
-    pub fn on_io<F>(&self, f: F) -> Subscription
-    where
-        F: 'static + Send + FnMut(IoKind, &str),
-    {
-        let id = self.next_id.fetch_add(1, SeqCst);
-        self.io_handlers.lock().insert(id, Box::new(f));
-        Subscription::Io {
-            id,
-            io_handlers: Some(Arc::downgrade(&self.io_handlers)),
-        }
-    }
-
-    pub fn remove_request_handler<T: request::Request>(&self) {
-        self.notification_handlers.lock().remove(T::METHOD);
-    }
-
-    pub fn remove_notification_handler<T: notification::Notification>(&self) {
-        self.notification_handlers.lock().remove(T::METHOD);
-    }
-
-    pub fn has_notification_handler<T: notification::Notification>(&self) -> bool {
-        self.notification_handlers.lock().contains_key(T::METHOD)
-    }
-
-    #[must_use]
-    pub fn on_custom_notification<Params, F>(&self, method: &'static str, mut f: F) -> Subscription
-    where
-        F: 'static + FnMut(Params, AsyncAppContext) + Send,
-        Params: DeserializeOwned,
-    {
-        let prev_handler = self.notification_handlers.lock().insert(
-            method,
-            Box::new(move |_, params, cx| {
-                if let Some(params) = serde_json::from_str(params).log_err() {
-                    f(params, cx);
-                }
-            }),
-        );
-        assert!(
-            prev_handler.is_none(),
-            "registered multiple handlers for the same LSP method"
-        );
-        Subscription::Notification {
-            method,
-            notification_handlers: Some(self.notification_handlers.clone()),
-        }
-    }
-
-    #[must_use]
-    pub fn on_custom_request<Params, Res, Fut, F>(
-        &self,
-        method: &'static str,
-        mut f: F,
-    ) -> Subscription
-    where
-        F: 'static + FnMut(Params, AsyncAppContext) -> Fut + Send,
-        Fut: 'static + Future<Output = Result<Res>>,
-        Params: DeserializeOwned + Send + 'static,
-        Res: Serialize,
-    {
-        let outbound_tx = self.outbound_tx.clone();
-        let prev_handler = self.notification_handlers.lock().insert(
-            method,
-            Box::new(move |id, params, cx| {
-                if let Some(id) = id {
-                    match serde_json::from_str(params) {
-                        Ok(params) => {
-                            let response = f(params, cx.clone());
-                            cx.foreground_executor()
-                                .spawn({
-                                    let outbound_tx = outbound_tx.clone();
-                                    async move {
-                                        let response = match response.await {
-                                            Ok(result) => Response {
-                                                jsonrpc: JSON_RPC_VERSION,
-                                                id,
-                                                result: Some(result),
-                                                error: None,
-                                            },
-                                            Err(error) => Response {
-                                                jsonrpc: JSON_RPC_VERSION,
-                                                id,
-                                                result: None,
-                                                error: Some(Error {
-                                                    message: error.to_string(),
-                                                }),
-                                            },
-                                        };
-                                        if let Some(response) =
-                                            serde_json::to_string(&response).log_err()
-                                        {
-                                            outbound_tx.try_send(response).ok();
-                                        }
-                                    }
-                                })
-                                .detach();
-                        }
-
-                        Err(error) => {
-                            log::error!(
-                                "error deserializing {} request: {:?}, message: {:?}",
-                                method,
-                                error,
-                                params
-                            );
-                            let response = AnyResponse {
-                                jsonrpc: JSON_RPC_VERSION,
-                                id,
-                                result: None,
-                                error: Some(Error {
-                                    message: error.to_string(),
-                                }),
-                            };
-                            if let Some(response) = serde_json::to_string(&response).log_err() {
-                                outbound_tx.try_send(response).ok();
-                            }
-                        }
-                    }
-                }
-            }),
-        );
-        assert!(
-            prev_handler.is_none(),
-            "registered multiple handlers for the same LSP method"
-        );
-        Subscription::Notification {
-            method,
-            notification_handlers: Some(self.notification_handlers.clone()),
-        }
-    }
-
-    pub fn name(&self) -> &str {
-        &self.name
-    }
-
-    pub fn capabilities(&self) -> &ServerCapabilities {
-        &self.capabilities
-    }
-
-    pub fn server_id(&self) -> LanguageServerId {
-        self.server_id
-    }
-
-    pub fn root_path(&self) -> &PathBuf {
-        &self.root_path
-    }
-
-    pub fn request<T: request::Request>(
-        &self,
-        params: T::Params,
-    ) -> impl Future<Output = Result<T::Result>>
-    where
-        T::Result: 'static + Send,
-    {
-        Self::request_internal::<T>(
-            &self.next_id,
-            &self.response_handlers,
-            &self.outbound_tx,
-            &self.executor,
-            params,
-        )
-    }
-
-    fn request_internal<T: request::Request>(
-        next_id: &AtomicUsize,
-        response_handlers: &Mutex<Option<HashMap<usize, ResponseHandler>>>,
-        outbound_tx: &channel::Sender<String>,
-        executor: &BackgroundExecutor,
-        params: T::Params,
-    ) -> impl 'static + Future<Output = anyhow::Result<T::Result>>
-    where
-        T::Result: 'static + Send,
-    {
-        let id = next_id.fetch_add(1, SeqCst);
-        let message = serde_json::to_string(&Request {
-            jsonrpc: JSON_RPC_VERSION,
-            id,
-            method: T::METHOD,
-            params,
-        })
-        .unwrap();
-
-        let (tx, rx) = oneshot::channel();
-        let handle_response = response_handlers
-            .lock()
-            .as_mut()
-            .ok_or_else(|| anyhow!("server shut down"))
-            .map(|handlers| {
-                let executor = executor.clone();
-                handlers.insert(
-                    id,
-                    Box::new(move |result| {
-                        executor
-                            .spawn(async move {
-                                let response = match result {
-                                    Ok(response) => serde_json::from_str(&response)
-                                        .context("failed to deserialize response"),
-                                    Err(error) => Err(anyhow!("{}", error.message)),
-                                };
-                                _ = tx.send(response);
-                            })
-                            .detach();
-                    }),
-                );
-            });
-
-        let send = outbound_tx
-            .try_send(message)
-            .context("failed to write to language server's stdin");
-
-        let mut timeout = executor.timer(LSP_REQUEST_TIMEOUT).fuse();
-        let started = Instant::now();
-        async move {
-            handle_response?;
-            send?;
-
-            let method = T::METHOD;
-            futures::select! {
-                response = rx.fuse() => {
-                    let elapsed = started.elapsed();
-                    log::trace!("Took {elapsed:?} to recieve response to {method:?} id {id}");
-                    response?
-                }
-
-                _ = timeout => {
-                    log::error!("Cancelled LSP request task for {method:?} id {id} which took over {LSP_REQUEST_TIMEOUT:?}");
-                    anyhow::bail!("LSP request timeout");
-                }
-            }
-        }
-    }
-
-    pub fn notify<T: notification::Notification>(&self, params: T::Params) -> Result<()> {
-        Self::notify_internal::<T>(&self.outbound_tx, params)
-    }
-
-    fn notify_internal<T: notification::Notification>(
-        outbound_tx: &channel::Sender<String>,
-        params: T::Params,
-    ) -> Result<()> {
-        let message = serde_json::to_string(&Notification {
-            jsonrpc: JSON_RPC_VERSION,
-            method: T::METHOD,
-            params,
-        })
-        .unwrap();
-        outbound_tx.try_send(message)?;
-        Ok(())
-    }
-}
-
-impl Drop for LanguageServer {
-    fn drop(&mut self) {
-        if let Some(shutdown) = self.shutdown() {
-            self.executor.spawn(shutdown).detach();
-        }
-    }
-}
-
-impl Subscription {
-    pub fn detach(&mut self) {
-        match self {
-            Subscription::Notification {
-                notification_handlers,
-                ..
-            } => *notification_handlers = None,
-            Subscription::Io { io_handlers, .. } => *io_handlers = None,
-        }
-    }
-}
-
-impl fmt::Display for LanguageServerId {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        self.0.fmt(f)
-    }
-}
-
-impl fmt::Debug for LanguageServer {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_struct("LanguageServer")
-            .field("id", &self.server_id.0)
-            .field("name", &self.name)
-            .finish_non_exhaustive()
-    }
-}
-
-impl Drop for Subscription {
-    fn drop(&mut self) {
-        match self {
-            Subscription::Notification {
-                method,
-                notification_handlers,
-            } => {
-                if let Some(handlers) = notification_handlers {
-                    handlers.lock().remove(method);
-                }
-            }
-            Subscription::Io { id, io_handlers } => {
-                if let Some(io_handlers) = io_handlers.as_ref().and_then(|h| h.upgrade()) {
-                    io_handlers.lock().remove(id);
-                }
-            }
-        }
-    }
-}
-
-#[cfg(any(test, feature = "test-support"))]
-#[derive(Clone)]
-pub struct FakeLanguageServer {
-    pub server: Arc<LanguageServer>,
-    notifications_rx: channel::Receiver<(String, String)>,
-}
-
-#[cfg(any(test, feature = "test-support"))]
-impl LanguageServer {
-    pub fn full_capabilities() -> ServerCapabilities {
-        ServerCapabilities {
-            document_highlight_provider: Some(OneOf::Left(true)),
-            code_action_provider: Some(CodeActionProviderCapability::Simple(true)),
-            document_formatting_provider: Some(OneOf::Left(true)),
-            document_range_formatting_provider: Some(OneOf::Left(true)),
-            definition_provider: Some(OneOf::Left(true)),
-            type_definition_provider: Some(TypeDefinitionProviderCapability::Simple(true)),
-            ..Default::default()
-        }
-    }
-
-    pub fn fake(
-        name: String,
-        capabilities: ServerCapabilities,
-        cx: AsyncAppContext,
-    ) -> (Self, FakeLanguageServer) {
-        let (stdin_writer, stdin_reader) = async_pipe::pipe();
-        let (stdout_writer, stdout_reader) = async_pipe::pipe();
-        let (notifications_tx, notifications_rx) = channel::unbounded();
-
-        let server = Self::new_internal(
-            LanguageServerId(0),
-            stdin_writer,
-            stdout_reader,
-            None::<async_pipe::PipeReader>,
-            Arc::new(Mutex::new(None)),
-            None,
-            Path::new("/"),
-            None,
-            cx.clone(),
-            |_| {},
-        );
-        let fake = FakeLanguageServer {
-            server: Arc::new(Self::new_internal(
-                LanguageServerId(0),
-                stdout_writer,
-                stdin_reader,
-                None::<async_pipe::PipeReader>,
-                Arc::new(Mutex::new(None)),
-                None,
-                Path::new("/"),
-                None,
-                cx,
-                move |msg| {
-                    notifications_tx
-                        .try_send((
-                            msg.method.to_string(),
-                            msg.params
-                                .map(|raw_value| raw_value.get())
-                                .unwrap_or("null")
-                                .to_string(),
-                        ))
-                        .ok();
-                },
-            )),
-            notifications_rx,
-        };
-        fake.handle_request::<request::Initialize, _, _>({
-            let capabilities = capabilities;
-            move |_, _| {
-                let capabilities = capabilities.clone();
-                let name = name.clone();
-                async move {
-                    Ok(InitializeResult {
-                        capabilities,
-                        server_info: Some(ServerInfo {
-                            name,
-                            ..Default::default()
-                        }),
-                    })
-                }
-            }
-        });
-
-        (server, fake)
-    }
-}
-
-#[cfg(any(test, feature = "test-support"))]
-impl FakeLanguageServer {
-    pub fn notify<T: notification::Notification>(&self, params: T::Params) {
-        self.server.notify::<T>(params).ok();
-    }
-
-    pub async fn request<T>(&self, params: T::Params) -> Result<T::Result>
-    where
-        T: request::Request,
-        T::Result: 'static + Send,
-    {
-        self.server.executor.start_waiting();
-        self.server.request::<T>(params).await
-    }
-
-    pub async fn receive_notification<T: notification::Notification>(&mut self) -> T::Params {
-        self.server.executor.start_waiting();
-        self.try_receive_notification::<T>().await.unwrap()
-    }
-
-    pub async fn try_receive_notification<T: notification::Notification>(
-        &mut self,
-    ) -> Option<T::Params> {
-        use futures::StreamExt as _;
-
-        loop {
-            let (method, params) = self.notifications_rx.next().await?;
-            if method == T::METHOD {
-                return Some(serde_json::from_str::<T::Params>(&params).unwrap());
-            } else {
-                log::info!("skipping message in fake language server {:?}", params);
-            }
-        }
-    }
-
-    pub fn handle_request<T, F, Fut>(
-        &self,
-        mut handler: F,
-    ) -> futures::channel::mpsc::UnboundedReceiver<()>
-    where
-        T: 'static + request::Request,
-        T::Params: 'static + Send,
-        F: 'static + Send + FnMut(T::Params, gpui::AsyncAppContext) -> Fut,
-        Fut: 'static + Send + Future<Output = Result<T::Result>>,
-    {
-        let (responded_tx, responded_rx) = futures::channel::mpsc::unbounded();
-        self.server.remove_request_handler::<T>();
-        self.server
-            .on_request::<T, _, _>(move |params, cx| {
-                let result = handler(params, cx.clone());
-                let responded_tx = responded_tx.clone();
-                let executor = cx.background_executor().clone();
-                async move {
-                    executor.simulate_random_delay().await;
-                    let result = result.await;
-                    responded_tx.unbounded_send(()).ok();
-                    result
-                }
-            })
-            .detach();
-        responded_rx
-    }
-
-    pub fn handle_notification<T, F>(
-        &self,
-        mut handler: F,
-    ) -> futures::channel::mpsc::UnboundedReceiver<()>
-    where
-        T: 'static + notification::Notification,
-        T::Params: 'static + Send,
-        F: 'static + Send + FnMut(T::Params, gpui::AsyncAppContext),
-    {
-        let (handled_tx, handled_rx) = futures::channel::mpsc::unbounded();
-        self.server.remove_notification_handler::<T>();
-        self.server
-            .on_notification::<T, _>(move |params, cx| {
-                handler(params, cx.clone());
-                handled_tx.unbounded_send(()).ok();
-            })
-            .detach();
-        handled_rx
-    }
-
-    pub fn remove_request_handler<T>(&mut self)
-    where
-        T: 'static + request::Request,
-    {
-        self.server.remove_request_handler::<T>();
-    }
-
-    pub async fn start_progress(&self, token: impl Into<String>) {
-        let token = token.into();
-        self.request::<request::WorkDoneProgressCreate>(WorkDoneProgressCreateParams {
-            token: NumberOrString::String(token.clone()),
-        })
-        .await
-        .unwrap();
-        self.notify::<notification::Progress>(ProgressParams {
-            token: NumberOrString::String(token),
-            value: ProgressParamsValue::WorkDone(WorkDoneProgress::Begin(Default::default())),
-        });
-    }
-
-    pub fn end_progress(&self, token: impl Into<String>) {
-        self.notify::<notification::Progress>(ProgressParams {
-            token: NumberOrString::String(token.into()),
-            value: ProgressParamsValue::WorkDone(WorkDoneProgress::End(Default::default())),
-        });
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use gpui::TestAppContext;
-
-    #[ctor::ctor]
-    fn init_logger() {
-        if std::env::var("RUST_LOG").is_ok() {
-            env_logger::init();
-        }
-    }
-
-    #[gpui::test]
-    async fn test_fake(cx: &mut TestAppContext) {
-        let (server, mut fake) =
-            LanguageServer::fake("the-lsp".to_string(), Default::default(), cx.to_async());
-
-        let (message_tx, message_rx) = channel::unbounded();
-        let (diagnostics_tx, diagnostics_rx) = channel::unbounded();
-        server
-            .on_notification::<notification::ShowMessage, _>(move |params, _| {
-                message_tx.try_send(params).unwrap()
-            })
-            .detach();
-        server
-            .on_notification::<notification::PublishDiagnostics, _>(move |params, _| {
-                diagnostics_tx.try_send(params).unwrap()
-            })
-            .detach();
-
-        let server = server.initialize(None).await.unwrap();
-        server
-            .notify::<notification::DidOpenTextDocument>(DidOpenTextDocumentParams {
-                text_document: TextDocumentItem::new(
-                    Url::from_str("file://a/b").unwrap(),
-                    "rust".to_string(),
-                    0,
-                    "".to_string(),
-                ),
-            })
-            .unwrap();
-        assert_eq!(
-            fake.receive_notification::<notification::DidOpenTextDocument>()
-                .await
-                .text_document
-                .uri
-                .as_str(),
-            "file://a/b"
-        );
-
-        fake.notify::<notification::ShowMessage>(ShowMessageParams {
-            typ: MessageType::ERROR,
-            message: "ok".to_string(),
-        });
-        fake.notify::<notification::PublishDiagnostics>(PublishDiagnosticsParams {
-            uri: Url::from_str("file://b/c").unwrap(),
-            version: Some(5),
-            diagnostics: vec![],
-        });
-        assert_eq!(message_rx.recv().await.unwrap().message, "ok");
-        assert_eq!(
-            diagnostics_rx.recv().await.unwrap().uri.as_str(),
-            "file://b/c"
-        );
-
-        fake.handle_request::<request::Shutdown, _, _>(|_, _| async move { Ok(()) });
-
-        drop(server);
-        fake.receive_notification::<notification::Exit>().await;
-    }
-}

crates/menu/Cargo.toml 🔗

@@ -10,3 +10,4 @@ doctest = false
 
 [dependencies]
 gpui = { path = "../gpui" }
+serde = { workspace = true }

crates/menu/src/menu.rs 🔗

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

crates/menu2/Cargo.toml 🔗

@@ -1,13 +0,0 @@
-[package]
-name = "menu2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/menu2.rs"
-doctest = false
-
-[dependencies]
-gpui = { package = "gpui2", path = "../gpui2" }
-serde = { workspace = true }

crates/menu2/src/menu2.rs 🔗

@@ -1,24 +0,0 @@
-use gpui::actions;
-
-// If the zed binary doesn't use anything in this crate, it will be optimized away
-// and the actions won't initialize. So we just provide an empty initialization function
-// to be called from main.
-//
-// These may provide relevant context:
-// https://github.com/rust-lang/rust/issues/47384
-// https://github.com/mmastrac/rust-ctor/issues/280
-pub fn init() {}
-
-actions!(
-    menu,
-    [
-        Cancel,
-        Confirm,
-        SecondaryConfirm,
-        SelectPrev,
-        SelectNext,
-        SelectFirst,
-        SelectLast,
-        ShowContextMenu
-    ]
-);

crates/multi_buffer/Cargo.toml 🔗

@@ -23,7 +23,6 @@ test-support = [
 client = { path = "../client" }
 clock = { path = "../clock" }
 collections = { path = "../collections" }
-context_menu = { path = "../context_menu" }
 git = { path = "../git" }
 gpui = { path = "../gpui" }
 language = { path = "../language" }
@@ -68,7 +67,6 @@ gpui = { path = "../gpui", features = ["test-support"] }
 util = { path = "../util", features = ["test-support"] }
 project = { path = "../project", features = ["test-support"] }
 settings = { path = "../settings", features = ["test-support"] }
-workspace = { path = "../workspace", features = ["test-support"] }
 
 ctor.workspace = true
 env_logger.workspace = true

crates/multi_buffer/src/multi_buffer.rs 🔗

@@ -6,7 +6,7 @@ use clock::ReplicaId;
 use collections::{BTreeMap, Bound, HashMap, HashSet};
 use futures::{channel::mpsc, SinkExt};
 use git::diff::DiffHunk;
-use gpui::{AppContext, Entity, ModelContext, ModelHandle};
+use gpui::{AppContext, EventEmitter, Model, ModelContext};
 pub use language::Completion;
 use language::{
     char_kind,
@@ -38,6 +38,9 @@ use text::{
 use theme::SyntaxTheme;
 use util::post_inc;
 
+#[cfg(any(test, feature = "test-support"))]
+use gpui::Context;
+
 const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize];
 
 #[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
@@ -57,7 +60,7 @@ pub struct MultiBuffer {
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub enum Event {
     ExcerptsAdded {
-        buffer: ModelHandle<Buffer>,
+        buffer: Model<Buffer>,
         predecessor: ExcerptId,
         excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
     },
@@ -119,7 +122,7 @@ pub trait ToPointUtf16: 'static + fmt::Debug {
 }
 
 struct BufferState {
-    buffer: ModelHandle<Buffer>,
+    buffer: Model<Buffer>,
     last_version: clock::Global,
     last_parse_count: usize,
     last_selections_update_count: usize,
@@ -279,7 +282,7 @@ impl MultiBuffer {
         self
     }
 
-    pub fn singleton(buffer: ModelHandle<Buffer>, cx: &mut ModelContext<Self>) -> Self {
+    pub fn singleton(buffer: Model<Buffer>, cx: &mut ModelContext<Self>) -> Self {
         let mut this = Self::new(buffer.read(cx).replica_id());
         this.singleton = true;
         this.push_excerpts(
@@ -308,7 +311,7 @@ impl MultiBuffer {
         self.snapshot.borrow()
     }
 
-    pub fn as_singleton(&self) -> Option<ModelHandle<Buffer>> {
+    pub fn as_singleton(&self) -> Option<Model<Buffer>> {
         if self.singleton {
             return Some(
                 self.buffers
@@ -681,7 +684,7 @@ impl MultiBuffer {
 
     pub fn push_transaction<'a, T>(&mut self, buffer_transactions: T, cx: &mut ModelContext<Self>)
     where
-        T: IntoIterator<Item = (&'a ModelHandle<Buffer>, &'a language::Transaction)>,
+        T: IntoIterator<Item = (&'a Model<Buffer>, &'a language::Transaction)>,
     {
         self.history
             .push_transaction(buffer_transactions, Instant::now(), cx);
@@ -863,19 +866,19 @@ impl MultiBuffer {
 
     pub fn stream_excerpts_with_context_lines(
         &mut self,
-        buffer: ModelHandle<Buffer>,
+        buffer: Model<Buffer>,
         ranges: Vec<Range<text::Anchor>>,
         context_line_count: u32,
         cx: &mut ModelContext<Self>,
     ) -> mpsc::Receiver<Range<Anchor>> {
-        let (mut tx, rx) = mpsc::channel(256);
-        cx.spawn(|this, mut cx| async move {
-            let (buffer_id, buffer_snapshot) =
-                buffer.read_with(&cx, |buffer, _| (buffer.remote_id(), buffer.snapshot()));
+        let (buffer_id, buffer_snapshot) =
+            buffer.update(cx, |buffer, _| (buffer.remote_id(), buffer.snapshot()));
 
+        let (mut tx, rx) = mpsc::channel(256);
+        cx.spawn(move |this, mut cx| async move {
             let mut excerpt_ranges = Vec::new();
             let mut range_counts = Vec::new();
-            cx.background()
+            cx.background_executor()
                 .scoped(|scope| {
                     scope.spawn(async {
                         let (ranges, counts) =
@@ -889,9 +892,12 @@ impl MultiBuffer {
             let mut ranges = ranges.into_iter();
             let mut range_counts = range_counts.into_iter();
             for excerpt_ranges in excerpt_ranges.chunks(100) {
-                let excerpt_ids = this.update(&mut cx, |this, cx| {
+                let excerpt_ids = match this.update(&mut cx, |this, cx| {
                     this.push_excerpts(buffer.clone(), excerpt_ranges.iter().cloned(), cx)
-                });
+                }) {
+                    Ok(excerpt_ids) => excerpt_ids,
+                    Err(_) => return,
+                };
 
                 for (excerpt_id, range_count) in excerpt_ids.into_iter().zip(range_counts.by_ref())
                 {
@@ -920,7 +926,7 @@ impl MultiBuffer {
 
     pub fn push_excerpts<O>(
         &mut self,
-        buffer: ModelHandle<Buffer>,
+        buffer: Model<Buffer>,
         ranges: impl IntoIterator<Item = ExcerptRange<O>>,
         cx: &mut ModelContext<Self>,
     ) -> Vec<ExcerptId>
@@ -932,7 +938,7 @@ impl MultiBuffer {
 
     pub fn push_excerpts_with_context_lines<O>(
         &mut self,
-        buffer: ModelHandle<Buffer>,
+        buffer: Model<Buffer>,
         ranges: Vec<Range<O>>,
         context_line_count: u32,
         cx: &mut ModelContext<Self>,
@@ -970,7 +976,7 @@ impl MultiBuffer {
     pub fn insert_excerpts_after<O>(
         &mut self,
         prev_excerpt_id: ExcerptId,
-        buffer: ModelHandle<Buffer>,
+        buffer: Model<Buffer>,
         ranges: impl IntoIterator<Item = ExcerptRange<O>>,
         cx: &mut ModelContext<Self>,
     ) -> Vec<ExcerptId>
@@ -995,7 +1001,7 @@ impl MultiBuffer {
     pub fn insert_excerpts_with_ids_after<O>(
         &mut self,
         prev_excerpt_id: ExcerptId,
-        buffer: ModelHandle<Buffer>,
+        buffer: Model<Buffer>,
         ranges: impl IntoIterator<Item = (ExcerptId, ExcerptRange<O>)>,
         cx: &mut ModelContext<Self>,
     ) where
@@ -1132,7 +1138,7 @@ impl MultiBuffer {
 
     pub fn excerpts_for_buffer(
         &self,
-        buffer: &ModelHandle<Buffer>,
+        buffer: &Model<Buffer>,
         cx: &AppContext,
     ) -> Vec<(ExcerptId, ExcerptRange<text::Anchor>)> {
         let mut excerpts = Vec::new();
@@ -1169,7 +1175,7 @@ impl MultiBuffer {
         &self,
         position: impl ToOffset,
         cx: &AppContext,
-    ) -> Option<(ExcerptId, ModelHandle<Buffer>, Range<text::Anchor>)> {
+    ) -> Option<(ExcerptId, Model<Buffer>, Range<text::Anchor>)> {
         let snapshot = self.read(cx);
         let position = position.to_offset(&snapshot);
 
@@ -1197,7 +1203,7 @@ impl MultiBuffer {
         &self,
         point: T,
         cx: &AppContext,
-    ) -> Option<(ModelHandle<Buffer>, usize, ExcerptId)> {
+    ) -> Option<(Model<Buffer>, usize, ExcerptId)> {
         let snapshot = self.read(cx);
         let offset = point.to_offset(&snapshot);
         let mut cursor = snapshot.excerpts.cursor::<usize>();
@@ -1219,7 +1225,7 @@ impl MultiBuffer {
         &self,
         range: Range<T>,
         cx: &AppContext,
-    ) -> Vec<(ModelHandle<Buffer>, Range<usize>, ExcerptId)> {
+    ) -> Vec<(Model<Buffer>, Range<usize>, ExcerptId)> {
         let snapshot = self.read(cx);
         let start = range.start.to_offset(&snapshot);
         let end = range.end.to_offset(&snapshot);
@@ -1377,7 +1383,7 @@ impl MultiBuffer {
         &self,
         position: T,
         cx: &AppContext,
-    ) -> Option<(ModelHandle<Buffer>, language::Anchor)> {
+    ) -> Option<(Model<Buffer>, language::Anchor)> {
         let snapshot = self.read(cx);
         let anchor = snapshot.anchor_before(position);
         let buffer = self
@@ -1391,7 +1397,7 @@ impl MultiBuffer {
 
     fn on_buffer_event(
         &mut self,
-        _: ModelHandle<Buffer>,
+        _: Model<Buffer>,
         event: &language::Event,
         cx: &mut ModelContext<Self>,
     ) {
@@ -1414,7 +1420,7 @@ impl MultiBuffer {
         });
     }
 
-    pub fn all_buffers(&self) -> HashSet<ModelHandle<Buffer>> {
+    pub fn all_buffers(&self) -> HashSet<Model<Buffer>> {
         self.buffers
             .borrow()
             .values()
@@ -1422,7 +1428,7 @@ impl MultiBuffer {
             .collect()
     }
 
-    pub fn buffer(&self, buffer_id: u64) -> Option<ModelHandle<Buffer>> {
+    pub fn buffer(&self, buffer_id: u64) -> Option<Model<Buffer>> {
         self.buffers
             .borrow()
             .get(&buffer_id)
@@ -1487,7 +1493,7 @@ impl MultiBuffer {
         language_settings(language.as_ref(), file, cx)
     }
 
-    pub fn for_each_buffer(&self, mut f: impl FnMut(&ModelHandle<Buffer>)) {
+    pub fn for_each_buffer(&self, mut f: impl FnMut(&Model<Buffer>)) {
         self.buffers
             .borrow()
             .values()
@@ -1642,18 +1648,18 @@ impl MultiBuffer {
 
 #[cfg(any(test, feature = "test-support"))]
 impl MultiBuffer {
-    pub fn build_simple(text: &str, cx: &mut gpui::AppContext) -> ModelHandle<Self> {
-        let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, text));
-        cx.add_model(|cx| Self::singleton(buffer, cx))
+    pub fn build_simple(text: &str, cx: &mut gpui::AppContext) -> Model<Self> {
+        let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text));
+        cx.new_model(|cx| Self::singleton(buffer, cx))
     }
 
     pub fn build_multi<const COUNT: usize>(
         excerpts: [(&str, Vec<Range<Point>>); COUNT],
         cx: &mut gpui::AppContext,
-    ) -> ModelHandle<Self> {
-        let multi = cx.add_model(|_| Self::new(0));
+    ) -> Model<Self> {
+        let multi = cx.new_model(|_| Self::new(0));
         for (text, ranges) in excerpts {
-            let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, text));
+            let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text));
             let excerpt_ranges = ranges.into_iter().map(|range| ExcerptRange {
                 context: range,
                 primary: None,
@@ -1666,15 +1672,12 @@ impl MultiBuffer {
         multi
     }
 
-    pub fn build_from_buffer(
-        buffer: ModelHandle<Buffer>,
-        cx: &mut gpui::AppContext,
-    ) -> ModelHandle<Self> {
-        cx.add_model(|cx| Self::singleton(buffer, cx))
+    pub fn build_from_buffer(buffer: Model<Buffer>, cx: &mut gpui::AppContext) -> Model<Self> {
+        cx.new_model(|cx| Self::singleton(buffer, cx))
     }
 
-    pub fn build_random(rng: &mut impl rand::Rng, cx: &mut gpui::AppContext) -> ModelHandle<Self> {
-        cx.add_model(|cx| {
+    pub fn build_random(rng: &mut impl rand::Rng, cx: &mut gpui::AppContext) -> Model<Self> {
+        cx.new_model(|cx| {
             let mut multibuffer = MultiBuffer::new(0);
             let mutation_count = rng.gen_range(1..=5);
             multibuffer.randomly_edit_excerpts(rng, mutation_count, cx);
@@ -1745,7 +1748,7 @@ impl MultiBuffer {
             if excerpt_ids.is_empty() || (rng.gen() && excerpt_ids.len() < max_excerpts) {
                 let buffer_handle = if rng.gen() || self.buffers.borrow().is_empty() {
                     let text = RandomCharIter::new(&mut *rng).take(10).collect::<String>();
-                    buffers.push(cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, text)));
+                    buffers.push(cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text)));
                     let buffer = buffers.last().unwrap().read(cx);
                     log::info!(
                         "Creating new buffer {} with text: {:?}",
@@ -1868,9 +1871,7 @@ impl MultiBuffer {
     }
 }
 
-impl Entity for MultiBuffer {
-    type Event = Event;
-}
+impl EventEmitter<Event> for MultiBuffer {}
 
 impl MultiBufferSnapshot {
     pub fn text(&self) -> String {
@@ -3405,7 +3406,7 @@ impl History {
         now: Instant,
         cx: &mut ModelContext<MultiBuffer>,
     ) where
-        T: IntoIterator<Item = (&'a ModelHandle<Buffer>, &'a language::Transaction)>,
+        T: IntoIterator<Item = (&'a Model<Buffer>, &'a language::Transaction)>,
     {
         assert_eq!(self.transaction_depth, 0);
         let transaction = Transaction {
@@ -4131,18 +4132,19 @@ where
 mod tests {
     use super::*;
     use futures::StreamExt;
-    use gpui::{AppContext, TestAppContext};
+    use gpui::{AppContext, Context, TestAppContext};
     use language::{Buffer, Rope};
+    use parking_lot::RwLock;
     use rand::prelude::*;
     use settings::SettingsStore;
-    use std::{env, rc::Rc};
+    use std::env;
     use util::test::sample_text;
 
     #[gpui::test]
     fn test_singleton(cx: &mut AppContext) {
         let buffer =
-            cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, sample_text(6, 6, 'a')));
-        let multibuffer = cx.add_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
+            cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(6, 6, 'a')));
+        let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
 
         let snapshot = multibuffer.read(cx).snapshot(cx);
         assert_eq!(snapshot.text(), buffer.read(cx).text());
@@ -4168,11 +4170,11 @@ mod tests {
 
     #[gpui::test]
     fn test_remote(cx: &mut AppContext) {
-        let host_buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "a"));
-        let guest_buffer = cx.add_model(|cx| {
+        let host_buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "a"));
+        let guest_buffer = cx.new_model(|cx| {
             let state = host_buffer.read(cx).to_proto();
             let ops = cx
-                .background()
+                .background_executor()
                 .block(host_buffer.read(cx).serialize_ops(None, cx));
             let mut buffer = Buffer::from_proto(1, state, None).unwrap();
             buffer
@@ -4184,7 +4186,7 @@ mod tests {
                 .unwrap();
             buffer
         });
-        let multibuffer = cx.add_model(|cx| MultiBuffer::singleton(guest_buffer.clone(), cx));
+        let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(guest_buffer.clone(), cx));
         let snapshot = multibuffer.read(cx).snapshot(cx);
         assert_eq!(snapshot.text(), "a");
 
@@ -4200,17 +4202,17 @@ mod tests {
     #[gpui::test]
     fn test_excerpt_boundaries_and_clipping(cx: &mut AppContext) {
         let buffer_1 =
-            cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, sample_text(6, 6, 'a')));
+            cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(6, 6, 'a')));
         let buffer_2 =
-            cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, sample_text(6, 6, 'g')));
-        let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
+            cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(6, 6, 'g')));
+        let multibuffer = cx.new_model(|_| MultiBuffer::new(0));
 
-        let events = Rc::new(RefCell::new(Vec::<Event>::new()));
+        let events = Arc::new(RwLock::new(Vec::<Event>::new()));
         multibuffer.update(cx, |_, cx| {
             let events = events.clone();
             cx.subscribe(&multibuffer, move |_, _, event, _| {
                 if let Event::Edited { .. } = event {
-                    events.borrow_mut().push(event.clone())
+                    events.write().push(event.clone())
                 }
             })
             .detach();
@@ -4263,7 +4265,7 @@ mod tests {
 
         // Adding excerpts emits an edited event.
         assert_eq!(
-            events.borrow().as_slice(),
+            events.read().as_slice(),
             &[
                 Event::Edited {
                     sigleton_buffer_edited: false
@@ -4436,13 +4438,13 @@ mod tests {
     #[gpui::test]
     fn test_excerpt_events(cx: &mut AppContext) {
         let buffer_1 =
-            cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, sample_text(10, 3, 'a')));
+            cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(10, 3, 'a')));
         let buffer_2 =
-            cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, sample_text(10, 3, 'm')));
+            cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(10, 3, 'm')));
 
-        let leader_multibuffer = cx.add_model(|_| MultiBuffer::new(0));
-        let follower_multibuffer = cx.add_model(|_| MultiBuffer::new(0));
-        let follower_edit_event_count = Rc::new(RefCell::new(0));
+        let leader_multibuffer = cx.new_model(|_| MultiBuffer::new(0));
+        let follower_multibuffer = cx.new_model(|_| MultiBuffer::new(0));
+        let follower_edit_event_count = Arc::new(RwLock::new(0));
 
         follower_multibuffer.update(cx, |_, cx| {
             let follower_edit_event_count = follower_edit_event_count.clone();
@@ -4456,7 +4458,7 @@ mod tests {
                     } => follower.insert_excerpts_with_ids_after(predecessor, buffer, excerpts, cx),
                     Event::ExcerptsRemoved { ids } => follower.remove_excerpts(ids, cx),
                     Event::Edited { .. } => {
-                        *follower_edit_event_count.borrow_mut() += 1;
+                        *follower_edit_event_count.write() += 1;
                     }
                     _ => {}
                 },
@@ -4499,7 +4501,7 @@ mod tests {
             leader_multibuffer.read(cx).snapshot(cx).text(),
             follower_multibuffer.read(cx).snapshot(cx).text(),
         );
-        assert_eq!(*follower_edit_event_count.borrow(), 2);
+        assert_eq!(*follower_edit_event_count.read(), 2);
 
         leader_multibuffer.update(cx, |leader, cx| {
             let excerpt_ids = leader.excerpt_ids();
@@ -4509,7 +4511,7 @@ mod tests {
             leader_multibuffer.read(cx).snapshot(cx).text(),
             follower_multibuffer.read(cx).snapshot(cx).text(),
         );
-        assert_eq!(*follower_edit_event_count.borrow(), 3);
+        assert_eq!(*follower_edit_event_count.read(), 3);
 
         // Removing an empty set of excerpts is a noop.
         leader_multibuffer.update(cx, |leader, cx| {
@@ -4519,7 +4521,7 @@ mod tests {
             leader_multibuffer.read(cx).snapshot(cx).text(),
             follower_multibuffer.read(cx).snapshot(cx).text(),
         );
-        assert_eq!(*follower_edit_event_count.borrow(), 3);
+        assert_eq!(*follower_edit_event_count.read(), 3);
 
         // Adding an empty set of excerpts is a noop.
         leader_multibuffer.update(cx, |leader, cx| {
@@ -4529,7 +4531,7 @@ mod tests {
             leader_multibuffer.read(cx).snapshot(cx).text(),
             follower_multibuffer.read(cx).snapshot(cx).text(),
         );
-        assert_eq!(*follower_edit_event_count.borrow(), 3);
+        assert_eq!(*follower_edit_event_count.read(), 3);
 
         leader_multibuffer.update(cx, |leader, cx| {
             leader.clear(cx);
@@ -4538,14 +4540,14 @@ mod tests {
             leader_multibuffer.read(cx).snapshot(cx).text(),
             follower_multibuffer.read(cx).snapshot(cx).text(),
         );
-        assert_eq!(*follower_edit_event_count.borrow(), 4);
+        assert_eq!(*follower_edit_event_count.read(), 4);
     }
 
     #[gpui::test]
     fn test_push_excerpts_with_context_lines(cx: &mut AppContext) {
         let buffer =
-            cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, sample_text(20, 3, 'a')));
-        let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
+            cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(20, 3, 'a')));
+        let multibuffer = cx.new_model(|_| MultiBuffer::new(0));
         let anchor_ranges = multibuffer.update(cx, |multibuffer, cx| {
             multibuffer.push_excerpts_with_context_lines(
                 buffer.clone(),
@@ -4581,8 +4583,8 @@ mod tests {
     #[gpui::test]
     async fn test_stream_excerpts_with_context_lines(cx: &mut TestAppContext) {
         let buffer =
-            cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, sample_text(20, 3, 'a')));
-        let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
+            cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(20, 3, 'a')));
+        let multibuffer = cx.new_model(|_| MultiBuffer::new(0));
         let anchor_ranges = multibuffer.update(cx, |multibuffer, cx| {
             let snapshot = buffer.read(cx);
             let ranges = vec![
@@ -4596,7 +4598,7 @@ mod tests {
 
         let anchor_ranges = anchor_ranges.collect::<Vec<_>>().await;
 
-        let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
+        let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
         assert_eq!(
             snapshot.text(),
             "bbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\n\nnnn\nooo\nppp\nqqq\nrrr\n"
@@ -4617,7 +4619,7 @@ mod tests {
 
     #[gpui::test]
     fn test_empty_multibuffer(cx: &mut AppContext) {
-        let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
+        let multibuffer = cx.new_model(|_| MultiBuffer::new(0));
 
         let snapshot = multibuffer.read(cx).snapshot(cx);
         assert_eq!(snapshot.text(), "");
@@ -4627,8 +4629,8 @@ mod tests {
 
     #[gpui::test]
     fn test_singleton_multibuffer_anchors(cx: &mut AppContext) {
-        let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "abcd"));
-        let multibuffer = cx.add_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
+        let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abcd"));
+        let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
         let old_snapshot = multibuffer.read(cx).snapshot(cx);
         buffer.update(cx, |buffer, cx| {
             buffer.edit([(0..0, "X")], None, cx);
@@ -4647,9 +4649,9 @@ mod tests {
 
     #[gpui::test]
     fn test_multibuffer_anchors(cx: &mut AppContext) {
-        let buffer_1 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "abcd"));
-        let buffer_2 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "efghi"));
-        let multibuffer = cx.add_model(|cx| {
+        let buffer_1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abcd"));
+        let buffer_2 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "efghi"));
+        let multibuffer = cx.new_model(|cx| {
             let mut multibuffer = MultiBuffer::new(0);
             multibuffer.push_excerpts(
                 buffer_1.clone(),
@@ -4705,9 +4707,10 @@ mod tests {
 
     #[gpui::test]
     fn test_resolving_anchors_after_replacing_their_excerpts(cx: &mut AppContext) {
-        let buffer_1 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "abcd"));
-        let buffer_2 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "ABCDEFGHIJKLMNOP"));
-        let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
+        let buffer_1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abcd"));
+        let buffer_2 =
+            cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "ABCDEFGHIJKLMNOP"));
+        let multibuffer = cx.new_model(|_| MultiBuffer::new(0));
 
         // Create an insertion id in buffer 1 that doesn't exist in buffer 2.
         // Add an excerpt from buffer 1 that spans this new insertion.
@@ -4840,10 +4843,10 @@ mod tests {
             .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
             .unwrap_or(10);
 
-        let mut buffers: Vec<ModelHandle<Buffer>> = Vec::new();
-        let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
+        let mut buffers: Vec<Model<Buffer>> = Vec::new();
+        let multibuffer = cx.new_model(|_| MultiBuffer::new(0));
         let mut excerpt_ids = Vec::<ExcerptId>::new();
-        let mut expected_excerpts = Vec::<(ModelHandle<Buffer>, Range<text::Anchor>)>::new();
+        let mut expected_excerpts = Vec::<(Model<Buffer>, Range<text::Anchor>)>::new();
         let mut anchors = Vec::new();
         let mut old_versions = Vec::new();
 
@@ -4918,7 +4921,7 @@ mod tests {
                             .take(10)
                             .collect::<String>();
                         buffers.push(
-                            cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, base_text)),
+                            cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), base_text)),
                         );
                         buffers.last().unwrap()
                     } else {
@@ -5258,11 +5261,12 @@ mod tests {
 
     #[gpui::test]
     fn test_history(cx: &mut AppContext) {
-        cx.set_global(SettingsStore::test(cx));
+        let test_settings = SettingsStore::test(cx);
+        cx.set_global(test_settings);
 
-        let buffer_1 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "1234"));
-        let buffer_2 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "5678"));
-        let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
+        let buffer_1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "1234"));
+        let buffer_2 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "5678"));
+        let multibuffer = cx.new_model(|_| MultiBuffer::new(0));
         let group_interval = multibuffer.read(cx).history.group_interval;
         multibuffer.update(cx, |multibuffer, cx| {
             multibuffer.push_excerpts(

crates/multi_buffer2/Cargo.toml 🔗

@@ -1,78 +0,0 @@
-[package]
-name = "multi_buffer2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/multi_buffer2.rs"
-doctest = false
-
-[features]
-test-support = [
-    "copilot/test-support",
-    "text/test-support",
-    "language/test-support",
-    "gpui/test-support",
-    "util/test-support",
-    "tree-sitter-rust",
-    "tree-sitter-typescript"
-]
-
-[dependencies]
-client = { package = "client2", path = "../client2" }
-clock = { path = "../clock" }
-collections = { path = "../collections" }
-git = { package = "git3", path = "../git3" }
-gpui = { package = "gpui2", path = "../gpui2" }
-language = { package = "language2", path = "../language2" }
-lsp = { package = "lsp2", path = "../lsp2" }
-rich_text = { package = "rich_text2", path = "../rich_text2" }
-settings = { package = "settings2", path = "../settings2" }
-snippet = { path = "../snippet" }
-sum_tree = { path = "../sum_tree" }
-text = { package = "text2", path = "../text2" }
-theme = { package = "theme2", path = "../theme2" }
-util = { path = "../util" }
-
-aho-corasick = "1.1"
-anyhow.workspace = true
-convert_case = "0.6.0"
-futures.workspace = true
-indoc = "1.0.4"
-itertools = "0.10"
-lazy_static.workspace = true
-log.workspace = true
-ordered-float.workspace = true
-parking_lot.workspace = true
-postage.workspace = true
-pulldown-cmark = { version = "0.9.2", default-features = false }
-rand.workspace = true
-schemars.workspace = true
-serde.workspace = true
-serde_derive.workspace = true
-smallvec.workspace = true
-smol.workspace = true
-
-tree-sitter-rust = { workspace = true, optional = true }
-tree-sitter-html = { workspace = true, optional = true }
-tree-sitter-typescript = { workspace = true, optional = true }
-
-[dev-dependencies]
-copilot = { package = "copilot2", path = "../copilot2", features = ["test-support"] }
-text = { package = "text2", path = "../text2", features = ["test-support"] }
-language = { package = "language2", path = "../language2", features = ["test-support"] }
-lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-util = { path = "../util", features = ["test-support"] }
-project = { package = "project2", path = "../project2", features = ["test-support"] }
-settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
-
-ctor.workspace = true
-env_logger.workspace = true
-rand.workspace = true
-unindent.workspace = true
-tree-sitter.workspace = true
-tree-sitter-rust.workspace = true
-tree-sitter-html.workspace = true
-tree-sitter-typescript.workspace = true

crates/multi_buffer2/src/anchor.rs 🔗

@@ -1,138 +0,0 @@
-use super::{ExcerptId, MultiBufferSnapshot, ToOffset, ToOffsetUtf16, ToPoint};
-use language::{OffsetUtf16, Point, TextDimension};
-use std::{
-    cmp::Ordering,
-    ops::{Range, Sub},
-};
-use sum_tree::Bias;
-
-#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
-pub struct Anchor {
-    pub buffer_id: Option<u64>,
-    pub excerpt_id: ExcerptId,
-    pub text_anchor: text::Anchor,
-}
-
-impl Anchor {
-    pub fn min() -> Self {
-        Self {
-            buffer_id: None,
-            excerpt_id: ExcerptId::min(),
-            text_anchor: text::Anchor::MIN,
-        }
-    }
-
-    pub fn max() -> Self {
-        Self {
-            buffer_id: None,
-            excerpt_id: ExcerptId::max(),
-            text_anchor: text::Anchor::MAX,
-        }
-    }
-
-    pub fn cmp(&self, other: &Anchor, snapshot: &MultiBufferSnapshot) -> Ordering {
-        let excerpt_id_cmp = self.excerpt_id.cmp(&other.excerpt_id, snapshot);
-        if excerpt_id_cmp.is_eq() {
-            if self.excerpt_id == ExcerptId::min() || self.excerpt_id == ExcerptId::max() {
-                Ordering::Equal
-            } else if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
-                self.text_anchor.cmp(&other.text_anchor, &excerpt.buffer)
-            } else {
-                Ordering::Equal
-            }
-        } else {
-            excerpt_id_cmp
-        }
-    }
-
-    pub fn bias(&self) -> Bias {
-        self.text_anchor.bias
-    }
-
-    pub fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> Anchor {
-        if self.text_anchor.bias != Bias::Left {
-            if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
-                return Self {
-                    buffer_id: self.buffer_id,
-                    excerpt_id: self.excerpt_id.clone(),
-                    text_anchor: self.text_anchor.bias_left(&excerpt.buffer),
-                };
-            }
-        }
-        self.clone()
-    }
-
-    pub fn bias_right(&self, snapshot: &MultiBufferSnapshot) -> Anchor {
-        if self.text_anchor.bias != Bias::Right {
-            if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
-                return Self {
-                    buffer_id: self.buffer_id,
-                    excerpt_id: self.excerpt_id.clone(),
-                    text_anchor: self.text_anchor.bias_right(&excerpt.buffer),
-                };
-            }
-        }
-        self.clone()
-    }
-
-    pub fn summary<D>(&self, snapshot: &MultiBufferSnapshot) -> D
-    where
-        D: TextDimension + Ord + Sub<D, Output = D>,
-    {
-        snapshot.summary_for_anchor(self)
-    }
-
-    pub fn is_valid(&self, snapshot: &MultiBufferSnapshot) -> bool {
-        if *self == Anchor::min() || *self == Anchor::max() {
-            true
-        } else if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
-            excerpt.contains(self)
-                && (self.text_anchor == excerpt.range.context.start
-                    || self.text_anchor == excerpt.range.context.end
-                    || self.text_anchor.is_valid(&excerpt.buffer))
-        } else {
-            false
-        }
-    }
-}
-
-impl ToOffset for Anchor {
-    fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> usize {
-        self.summary(snapshot)
-    }
-}
-
-impl ToOffsetUtf16 for Anchor {
-    fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> OffsetUtf16 {
-        self.summary(snapshot)
-    }
-}
-
-impl ToPoint for Anchor {
-    fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> Point {
-        self.summary(snapshot)
-    }
-}
-
-pub trait AnchorRangeExt {
-    fn cmp(&self, b: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> Ordering;
-    fn to_offset(&self, content: &MultiBufferSnapshot) -> Range<usize>;
-    fn to_point(&self, content: &MultiBufferSnapshot) -> Range<Point>;
-}
-
-impl AnchorRangeExt for Range<Anchor> {
-    fn cmp(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> Ordering {
-        match self.start.cmp(&other.start, buffer) {
-            Ordering::Equal => other.end.cmp(&self.end, buffer),
-            ord => ord,
-        }
-    }
-
-    fn to_offset(&self, content: &MultiBufferSnapshot) -> Range<usize> {
-        self.start.to_offset(content)..self.end.to_offset(content)
-    }
-
-    fn to_point(&self, content: &MultiBufferSnapshot) -> Range<Point> {
-        self.start.to_point(content)..self.end.to_point(content)
-    }
-}

crates/multi_buffer2/src/multi_buffer2.rs 🔗

@@ -1,5390 +0,0 @@
-mod anchor;
-
-pub use anchor::{Anchor, AnchorRangeExt};
-use anyhow::{anyhow, Result};
-use clock::ReplicaId;
-use collections::{BTreeMap, Bound, HashMap, HashSet};
-use futures::{channel::mpsc, SinkExt};
-use git::diff::DiffHunk;
-use gpui::{AppContext, EventEmitter, Model, ModelContext};
-pub use language::Completion;
-use language::{
-    char_kind,
-    language_settings::{language_settings, LanguageSettings},
-    AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape,
-    DiagnosticEntry, File, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16,
-    Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _,
-    ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped,
-};
-use std::{
-    borrow::Cow,
-    cell::{Ref, RefCell},
-    cmp, fmt,
-    future::Future,
-    io,
-    iter::{self, FromIterator},
-    mem,
-    ops::{Range, RangeBounds, Sub},
-    str,
-    sync::Arc,
-    time::{Duration, Instant},
-};
-use sum_tree::{Bias, Cursor, SumTree};
-use text::{
-    locator::Locator,
-    subscription::{Subscription, Topic},
-    Edit, TextSummary,
-};
-use theme::SyntaxTheme;
-use util::post_inc;
-
-#[cfg(any(test, feature = "test-support"))]
-use gpui::Context;
-
-const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize];
-
-#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
-pub struct ExcerptId(usize);
-
-pub struct MultiBuffer {
-    snapshot: RefCell<MultiBufferSnapshot>,
-    buffers: RefCell<HashMap<u64, BufferState>>,
-    next_excerpt_id: usize,
-    subscriptions: Topic,
-    singleton: bool,
-    replica_id: ReplicaId,
-    history: History,
-    title: Option<String>,
-}
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub enum Event {
-    ExcerptsAdded {
-        buffer: Model<Buffer>,
-        predecessor: ExcerptId,
-        excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
-    },
-    ExcerptsRemoved {
-        ids: Vec<ExcerptId>,
-    },
-    ExcerptsEdited {
-        ids: Vec<ExcerptId>,
-    },
-    Edited {
-        sigleton_buffer_edited: bool,
-    },
-    TransactionUndone {
-        transaction_id: TransactionId,
-    },
-    Reloaded,
-    DiffBaseChanged,
-    LanguageChanged,
-    Reparsed,
-    Saved,
-    FileHandleChanged,
-    Closed,
-    DirtyChanged,
-    DiagnosticsUpdated,
-}
-
-#[derive(Clone)]
-struct History {
-    next_transaction_id: TransactionId,
-    undo_stack: Vec<Transaction>,
-    redo_stack: Vec<Transaction>,
-    transaction_depth: usize,
-    group_interval: Duration,
-}
-
-#[derive(Clone)]
-struct Transaction {
-    id: TransactionId,
-    buffer_transactions: HashMap<u64, text::TransactionId>,
-    first_edit_at: Instant,
-    last_edit_at: Instant,
-    suppress_grouping: bool,
-}
-
-pub trait ToOffset: 'static + fmt::Debug {
-    fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> usize;
-}
-
-pub trait ToOffsetUtf16: 'static + fmt::Debug {
-    fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> OffsetUtf16;
-}
-
-pub trait ToPoint: 'static + fmt::Debug {
-    fn to_point(&self, snapshot: &MultiBufferSnapshot) -> Point;
-}
-
-pub trait ToPointUtf16: 'static + fmt::Debug {
-    fn to_point_utf16(&self, snapshot: &MultiBufferSnapshot) -> PointUtf16;
-}
-
-struct BufferState {
-    buffer: Model<Buffer>,
-    last_version: clock::Global,
-    last_parse_count: usize,
-    last_selections_update_count: usize,
-    last_diagnostics_update_count: usize,
-    last_file_update_count: usize,
-    last_git_diff_update_count: usize,
-    excerpts: Vec<Locator>,
-    _subscriptions: [gpui::Subscription; 2],
-}
-
-#[derive(Clone, Default)]
-pub struct MultiBufferSnapshot {
-    singleton: bool,
-    excerpts: SumTree<Excerpt>,
-    excerpt_ids: SumTree<ExcerptIdMapping>,
-    parse_count: usize,
-    diagnostics_update_count: usize,
-    trailing_excerpt_update_count: usize,
-    git_diff_update_count: usize,
-    edit_count: usize,
-    is_dirty: bool,
-    has_conflict: bool,
-}
-
-pub struct ExcerptBoundary {
-    pub id: ExcerptId,
-    pub row: u32,
-    pub buffer: BufferSnapshot,
-    pub range: ExcerptRange<text::Anchor>,
-    pub starts_new_buffer: bool,
-}
-
-#[derive(Clone)]
-struct Excerpt {
-    id: ExcerptId,
-    locator: Locator,
-    buffer_id: u64,
-    buffer: BufferSnapshot,
-    range: ExcerptRange<text::Anchor>,
-    max_buffer_row: u32,
-    text_summary: TextSummary,
-    has_trailing_newline: bool,
-}
-
-#[derive(Clone, Debug)]
-struct ExcerptIdMapping {
-    id: ExcerptId,
-    locator: Locator,
-}
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct ExcerptRange<T> {
-    pub context: Range<T>,
-    pub primary: Option<Range<T>>,
-}
-
-#[derive(Clone, Debug, Default)]
-struct ExcerptSummary {
-    excerpt_id: ExcerptId,
-    excerpt_locator: Locator,
-    max_buffer_row: u32,
-    text: TextSummary,
-}
-
-#[derive(Clone)]
-pub struct MultiBufferRows<'a> {
-    buffer_row_range: Range<u32>,
-    excerpts: Cursor<'a, Excerpt, Point>,
-}
-
-pub struct MultiBufferChunks<'a> {
-    range: Range<usize>,
-    excerpts: Cursor<'a, Excerpt, usize>,
-    excerpt_chunks: Option<ExcerptChunks<'a>>,
-    language_aware: bool,
-}
-
-pub struct MultiBufferBytes<'a> {
-    range: Range<usize>,
-    excerpts: Cursor<'a, Excerpt, usize>,
-    excerpt_bytes: Option<ExcerptBytes<'a>>,
-    chunk: &'a [u8],
-}
-
-pub struct ReversedMultiBufferBytes<'a> {
-    range: Range<usize>,
-    excerpts: Cursor<'a, Excerpt, usize>,
-    excerpt_bytes: Option<ExcerptBytes<'a>>,
-    chunk: &'a [u8],
-}
-
-struct ExcerptChunks<'a> {
-    content_chunks: BufferChunks<'a>,
-    footer_height: usize,
-}
-
-struct ExcerptBytes<'a> {
-    content_bytes: text::Bytes<'a>,
-    footer_height: usize,
-}
-
-impl MultiBuffer {
-    pub fn new(replica_id: ReplicaId) -> Self {
-        Self {
-            snapshot: Default::default(),
-            buffers: Default::default(),
-            next_excerpt_id: 1,
-            subscriptions: Default::default(),
-            singleton: false,
-            replica_id,
-            history: History {
-                next_transaction_id: Default::default(),
-                undo_stack: Default::default(),
-                redo_stack: Default::default(),
-                transaction_depth: 0,
-                group_interval: Duration::from_millis(300),
-            },
-            title: Default::default(),
-        }
-    }
-
-    pub fn clone(&self, new_cx: &mut ModelContext<Self>) -> Self {
-        let mut buffers = HashMap::default();
-        for (buffer_id, buffer_state) in self.buffers.borrow().iter() {
-            buffers.insert(
-                *buffer_id,
-                BufferState {
-                    buffer: buffer_state.buffer.clone(),
-                    last_version: buffer_state.last_version.clone(),
-                    last_parse_count: buffer_state.last_parse_count,
-                    last_selections_update_count: buffer_state.last_selections_update_count,
-                    last_diagnostics_update_count: buffer_state.last_diagnostics_update_count,
-                    last_file_update_count: buffer_state.last_file_update_count,
-                    last_git_diff_update_count: buffer_state.last_git_diff_update_count,
-                    excerpts: buffer_state.excerpts.clone(),
-                    _subscriptions: [
-                        new_cx.observe(&buffer_state.buffer, |_, _, cx| cx.notify()),
-                        new_cx.subscribe(&buffer_state.buffer, Self::on_buffer_event),
-                    ],
-                },
-            );
-        }
-        Self {
-            snapshot: RefCell::new(self.snapshot.borrow().clone()),
-            buffers: RefCell::new(buffers),
-            next_excerpt_id: 1,
-            subscriptions: Default::default(),
-            singleton: self.singleton,
-            replica_id: self.replica_id,
-            history: self.history.clone(),
-            title: self.title.clone(),
-        }
-    }
-
-    pub fn with_title(mut self, title: String) -> Self {
-        self.title = Some(title);
-        self
-    }
-
-    pub fn singleton(buffer: Model<Buffer>, cx: &mut ModelContext<Self>) -> Self {
-        let mut this = Self::new(buffer.read(cx).replica_id());
-        this.singleton = true;
-        this.push_excerpts(
-            buffer,
-            [ExcerptRange {
-                context: text::Anchor::MIN..text::Anchor::MAX,
-                primary: None,
-            }],
-            cx,
-        );
-        this.snapshot.borrow_mut().singleton = true;
-        this
-    }
-
-    pub fn replica_id(&self) -> ReplicaId {
-        self.replica_id
-    }
-
-    pub fn snapshot(&self, cx: &AppContext) -> MultiBufferSnapshot {
-        self.sync(cx);
-        self.snapshot.borrow().clone()
-    }
-
-    pub fn read(&self, cx: &AppContext) -> Ref<MultiBufferSnapshot> {
-        self.sync(cx);
-        self.snapshot.borrow()
-    }
-
-    pub fn as_singleton(&self) -> Option<Model<Buffer>> {
-        if self.singleton {
-            return Some(
-                self.buffers
-                    .borrow()
-                    .values()
-                    .next()
-                    .unwrap()
-                    .buffer
-                    .clone(),
-            );
-        } else {
-            None
-        }
-    }
-
-    pub fn is_singleton(&self) -> bool {
-        self.singleton
-    }
-
-    pub fn subscribe(&mut self) -> Subscription {
-        self.subscriptions.subscribe()
-    }
-
-    pub fn is_dirty(&self, cx: &AppContext) -> bool {
-        self.read(cx).is_dirty()
-    }
-
-    pub fn has_conflict(&self, cx: &AppContext) -> bool {
-        self.read(cx).has_conflict()
-    }
-
-    // The `is_empty` signature doesn't match what clippy expects
-    #[allow(clippy::len_without_is_empty)]
-    pub fn len(&self, cx: &AppContext) -> usize {
-        self.read(cx).len()
-    }
-
-    pub fn is_empty(&self, cx: &AppContext) -> bool {
-        self.len(cx) != 0
-    }
-
-    pub fn symbols_containing<T: ToOffset>(
-        &self,
-        offset: T,
-        theme: Option<&SyntaxTheme>,
-        cx: &AppContext,
-    ) -> Option<(u64, Vec<OutlineItem<Anchor>>)> {
-        self.read(cx).symbols_containing(offset, theme)
-    }
-
-    pub fn edit<I, S, T>(
-        &mut self,
-        edits: I,
-        mut autoindent_mode: Option<AutoindentMode>,
-        cx: &mut ModelContext<Self>,
-    ) where
-        I: IntoIterator<Item = (Range<S>, T)>,
-        S: ToOffset,
-        T: Into<Arc<str>>,
-    {
-        if self.buffers.borrow().is_empty() {
-            return;
-        }
-
-        let snapshot = self.read(cx);
-        let edits = edits.into_iter().map(|(range, new_text)| {
-            let mut range = range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot);
-            if range.start > range.end {
-                mem::swap(&mut range.start, &mut range.end);
-            }
-            (range, new_text)
-        });
-
-        if let Some(buffer) = self.as_singleton() {
-            return buffer.update(cx, |buffer, cx| {
-                buffer.edit(edits, autoindent_mode, cx);
-            });
-        }
-
-        let original_indent_columns = match &mut autoindent_mode {
-            Some(AutoindentMode::Block {
-                original_indent_columns,
-            }) => mem::take(original_indent_columns),
-            _ => Default::default(),
-        };
-
-        struct BufferEdit {
-            range: Range<usize>,
-            new_text: Arc<str>,
-            is_insertion: bool,
-            original_indent_column: u32,
-        }
-        let mut buffer_edits: HashMap<u64, Vec<BufferEdit>> = Default::default();
-        let mut edited_excerpt_ids = Vec::new();
-        let mut cursor = snapshot.excerpts.cursor::<usize>();
-        for (ix, (range, new_text)) in edits.enumerate() {
-            let new_text: Arc<str> = new_text.into();
-            let original_indent_column = original_indent_columns.get(ix).copied().unwrap_or(0);
-            cursor.seek(&range.start, Bias::Right, &());
-            if cursor.item().is_none() && range.start == *cursor.start() {
-                cursor.prev(&());
-            }
-            let start_excerpt = cursor.item().expect("start offset out of bounds");
-            let start_overshoot = range.start - cursor.start();
-            let buffer_start = start_excerpt
-                .range
-                .context
-                .start
-                .to_offset(&start_excerpt.buffer)
-                + start_overshoot;
-            edited_excerpt_ids.push(start_excerpt.id);
-
-            cursor.seek(&range.end, Bias::Right, &());
-            if cursor.item().is_none() && range.end == *cursor.start() {
-                cursor.prev(&());
-            }
-            let end_excerpt = cursor.item().expect("end offset out of bounds");
-            let end_overshoot = range.end - cursor.start();
-            let buffer_end = end_excerpt
-                .range
-                .context
-                .start
-                .to_offset(&end_excerpt.buffer)
-                + end_overshoot;
-
-            if start_excerpt.id == end_excerpt.id {
-                buffer_edits
-                    .entry(start_excerpt.buffer_id)
-                    .or_insert(Vec::new())
-                    .push(BufferEdit {
-                        range: buffer_start..buffer_end,
-                        new_text,
-                        is_insertion: true,
-                        original_indent_column,
-                    });
-            } else {
-                edited_excerpt_ids.push(end_excerpt.id);
-                let start_excerpt_range = buffer_start
-                    ..start_excerpt
-                        .range
-                        .context
-                        .end
-                        .to_offset(&start_excerpt.buffer);
-                let end_excerpt_range = end_excerpt
-                    .range
-                    .context
-                    .start
-                    .to_offset(&end_excerpt.buffer)
-                    ..buffer_end;
-                buffer_edits
-                    .entry(start_excerpt.buffer_id)
-                    .or_insert(Vec::new())
-                    .push(BufferEdit {
-                        range: start_excerpt_range,
-                        new_text: new_text.clone(),
-                        is_insertion: true,
-                        original_indent_column,
-                    });
-                buffer_edits
-                    .entry(end_excerpt.buffer_id)
-                    .or_insert(Vec::new())
-                    .push(BufferEdit {
-                        range: end_excerpt_range,
-                        new_text: new_text.clone(),
-                        is_insertion: false,
-                        original_indent_column,
-                    });
-
-                cursor.seek(&range.start, Bias::Right, &());
-                cursor.next(&());
-                while let Some(excerpt) = cursor.item() {
-                    if excerpt.id == end_excerpt.id {
-                        break;
-                    }
-                    buffer_edits
-                        .entry(excerpt.buffer_id)
-                        .or_insert(Vec::new())
-                        .push(BufferEdit {
-                            range: excerpt.range.context.to_offset(&excerpt.buffer),
-                            new_text: new_text.clone(),
-                            is_insertion: false,
-                            original_indent_column,
-                        });
-                    edited_excerpt_ids.push(excerpt.id);
-                    cursor.next(&());
-                }
-            }
-        }
-
-        drop(cursor);
-        drop(snapshot);
-        // Non-generic part of edit, hoisted out to avoid blowing up LLVM IR.
-        fn tail(
-            this: &mut MultiBuffer,
-            buffer_edits: HashMap<u64, Vec<BufferEdit>>,
-            autoindent_mode: Option<AutoindentMode>,
-            edited_excerpt_ids: Vec<ExcerptId>,
-            cx: &mut ModelContext<MultiBuffer>,
-        ) {
-            for (buffer_id, mut edits) in buffer_edits {
-                edits.sort_unstable_by_key(|edit| edit.range.start);
-                this.buffers.borrow()[&buffer_id]
-                    .buffer
-                    .update(cx, |buffer, cx| {
-                        let mut edits = edits.into_iter().peekable();
-                        let mut insertions = Vec::new();
-                        let mut original_indent_columns = Vec::new();
-                        let mut deletions = Vec::new();
-                        let empty_str: Arc<str> = "".into();
-                        while let Some(BufferEdit {
-                            mut range,
-                            new_text,
-                            mut is_insertion,
-                            original_indent_column,
-                        }) = edits.next()
-                        {
-                            while let Some(BufferEdit {
-                                range: next_range,
-                                is_insertion: next_is_insertion,
-                                ..
-                            }) = edits.peek()
-                            {
-                                if range.end >= next_range.start {
-                                    range.end = cmp::max(next_range.end, range.end);
-                                    is_insertion |= *next_is_insertion;
-                                    edits.next();
-                                } else {
-                                    break;
-                                }
-                            }
-
-                            if is_insertion {
-                                original_indent_columns.push(original_indent_column);
-                                insertions.push((
-                                    buffer.anchor_before(range.start)
-                                        ..buffer.anchor_before(range.end),
-                                    new_text.clone(),
-                                ));
-                            } else if !range.is_empty() {
-                                deletions.push((
-                                    buffer.anchor_before(range.start)
-                                        ..buffer.anchor_before(range.end),
-                                    empty_str.clone(),
-                                ));
-                            }
-                        }
-
-                        let deletion_autoindent_mode =
-                            if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
-                                Some(AutoindentMode::Block {
-                                    original_indent_columns: Default::default(),
-                                })
-                            } else {
-                                None
-                            };
-                        let insertion_autoindent_mode =
-                            if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
-                                Some(AutoindentMode::Block {
-                                    original_indent_columns,
-                                })
-                            } else {
-                                None
-                            };
-
-                        buffer.edit(deletions, deletion_autoindent_mode, cx);
-                        buffer.edit(insertions, insertion_autoindent_mode, cx);
-                    })
-            }
-
-            cx.emit(Event::ExcerptsEdited {
-                ids: edited_excerpt_ids,
-            });
-        }
-        tail(self, buffer_edits, autoindent_mode, edited_excerpt_ids, cx);
-    }
-
-    pub fn start_transaction(&mut self, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
-        self.start_transaction_at(Instant::now(), cx)
-    }
-
-    pub fn start_transaction_at(
-        &mut self,
-        now: Instant,
-        cx: &mut ModelContext<Self>,
-    ) -> Option<TransactionId> {
-        if let Some(buffer) = self.as_singleton() {
-            return buffer.update(cx, |buffer, _| buffer.start_transaction_at(now));
-        }
-
-        for BufferState { buffer, .. } in self.buffers.borrow().values() {
-            buffer.update(cx, |buffer, _| buffer.start_transaction_at(now));
-        }
-        self.history.start_transaction(now)
-    }
-
-    pub fn end_transaction(&mut self, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
-        self.end_transaction_at(Instant::now(), cx)
-    }
-
-    pub fn end_transaction_at(
-        &mut self,
-        now: Instant,
-        cx: &mut ModelContext<Self>,
-    ) -> Option<TransactionId> {
-        if let Some(buffer) = self.as_singleton() {
-            return buffer.update(cx, |buffer, cx| buffer.end_transaction_at(now, cx));
-        }
-
-        let mut buffer_transactions = HashMap::default();
-        for BufferState { buffer, .. } in self.buffers.borrow().values() {
-            if let Some(transaction_id) =
-                buffer.update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
-            {
-                buffer_transactions.insert(buffer.read(cx).remote_id(), transaction_id);
-            }
-        }
-
-        if self.history.end_transaction(now, buffer_transactions) {
-            let transaction_id = self.history.group().unwrap();
-            Some(transaction_id)
-        } else {
-            None
-        }
-    }
-
-    pub fn merge_transactions(
-        &mut self,
-        transaction: TransactionId,
-        destination: TransactionId,
-        cx: &mut ModelContext<Self>,
-    ) {
-        if let Some(buffer) = self.as_singleton() {
-            buffer.update(cx, |buffer, _| {
-                buffer.merge_transactions(transaction, destination)
-            });
-        } else {
-            if let Some(transaction) = self.history.forget(transaction) {
-                if let Some(destination) = self.history.transaction_mut(destination) {
-                    for (buffer_id, buffer_transaction_id) in transaction.buffer_transactions {
-                        if let Some(destination_buffer_transaction_id) =
-                            destination.buffer_transactions.get(&buffer_id)
-                        {
-                            if let Some(state) = self.buffers.borrow().get(&buffer_id) {
-                                state.buffer.update(cx, |buffer, _| {
-                                    buffer.merge_transactions(
-                                        buffer_transaction_id,
-                                        *destination_buffer_transaction_id,
-                                    )
-                                });
-                            }
-                        } else {
-                            destination
-                                .buffer_transactions
-                                .insert(buffer_id, buffer_transaction_id);
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    pub fn finalize_last_transaction(&mut self, cx: &mut ModelContext<Self>) {
-        self.history.finalize_last_transaction();
-        for BufferState { buffer, .. } in self.buffers.borrow().values() {
-            buffer.update(cx, |buffer, _| {
-                buffer.finalize_last_transaction();
-            });
-        }
-    }
-
-    pub fn push_transaction<'a, T>(&mut self, buffer_transactions: T, cx: &mut ModelContext<Self>)
-    where
-        T: IntoIterator<Item = (&'a Model<Buffer>, &'a language::Transaction)>,
-    {
-        self.history
-            .push_transaction(buffer_transactions, Instant::now(), cx);
-        self.history.finalize_last_transaction();
-    }
-
-    pub fn group_until_transaction(
-        &mut self,
-        transaction_id: TransactionId,
-        cx: &mut ModelContext<Self>,
-    ) {
-        if let Some(buffer) = self.as_singleton() {
-            buffer.update(cx, |buffer, _| {
-                buffer.group_until_transaction(transaction_id)
-            });
-        } else {
-            self.history.group_until(transaction_id);
-        }
-    }
-
-    pub fn set_active_selections(
-        &mut self,
-        selections: &[Selection<Anchor>],
-        line_mode: bool,
-        cursor_shape: CursorShape,
-        cx: &mut ModelContext<Self>,
-    ) {
-        let mut selections_by_buffer: HashMap<u64, Vec<Selection<text::Anchor>>> =
-            Default::default();
-        let snapshot = self.read(cx);
-        let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>();
-        for selection in selections {
-            let start_locator = snapshot.excerpt_locator_for_id(selection.start.excerpt_id);
-            let end_locator = snapshot.excerpt_locator_for_id(selection.end.excerpt_id);
-
-            cursor.seek(&Some(start_locator), Bias::Left, &());
-            while let Some(excerpt) = cursor.item() {
-                if excerpt.locator > *end_locator {
-                    break;
-                }
-
-                let mut start = excerpt.range.context.start;
-                let mut end = excerpt.range.context.end;
-                if excerpt.id == selection.start.excerpt_id {
-                    start = selection.start.text_anchor;
-                }
-                if excerpt.id == selection.end.excerpt_id {
-                    end = selection.end.text_anchor;
-                }
-                selections_by_buffer
-                    .entry(excerpt.buffer_id)
-                    .or_default()
-                    .push(Selection {
-                        id: selection.id,
-                        start,
-                        end,
-                        reversed: selection.reversed,
-                        goal: selection.goal,
-                    });
-
-                cursor.next(&());
-            }
-        }
-
-        for (buffer_id, buffer_state) in self.buffers.borrow().iter() {
-            if !selections_by_buffer.contains_key(buffer_id) {
-                buffer_state
-                    .buffer
-                    .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
-            }
-        }
-
-        for (buffer_id, mut selections) in selections_by_buffer {
-            self.buffers.borrow()[&buffer_id]
-                .buffer
-                .update(cx, |buffer, cx| {
-                    selections.sort_unstable_by(|a, b| a.start.cmp(&b.start, buffer));
-                    let mut selections = selections.into_iter().peekable();
-                    let merged_selections = Arc::from_iter(iter::from_fn(|| {
-                        let mut selection = selections.next()?;
-                        while let Some(next_selection) = selections.peek() {
-                            if selection.end.cmp(&next_selection.start, buffer).is_ge() {
-                                let next_selection = selections.next().unwrap();
-                                if next_selection.end.cmp(&selection.end, buffer).is_ge() {
-                                    selection.end = next_selection.end;
-                                }
-                            } else {
-                                break;
-                            }
-                        }
-                        Some(selection)
-                    }));
-                    buffer.set_active_selections(merged_selections, line_mode, cursor_shape, cx);
-                });
-        }
-    }
-
-    pub fn remove_active_selections(&mut self, cx: &mut ModelContext<Self>) {
-        for buffer in self.buffers.borrow().values() {
-            buffer
-                .buffer
-                .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
-        }
-    }
-
-    pub fn undo(&mut self, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
-        let mut transaction_id = None;
-        if let Some(buffer) = self.as_singleton() {
-            transaction_id = buffer.update(cx, |buffer, cx| buffer.undo(cx));
-        } else {
-            while let Some(transaction) = self.history.pop_undo() {
-                let mut undone = false;
-                for (buffer_id, buffer_transaction_id) in &mut transaction.buffer_transactions {
-                    if let Some(BufferState { buffer, .. }) = self.buffers.borrow().get(buffer_id) {
-                        undone |= buffer.update(cx, |buffer, cx| {
-                            let undo_to = *buffer_transaction_id;
-                            if let Some(entry) = buffer.peek_undo_stack() {
-                                *buffer_transaction_id = entry.transaction_id();
-                            }
-                            buffer.undo_to_transaction(undo_to, cx)
-                        });
-                    }
-                }
-
-                if undone {
-                    transaction_id = Some(transaction.id);
-                    break;
-                }
-            }
-        }
-
-        if let Some(transaction_id) = transaction_id {
-            cx.emit(Event::TransactionUndone { transaction_id });
-        }
-
-        transaction_id
-    }
-
-    pub fn redo(&mut self, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
-        if let Some(buffer) = self.as_singleton() {
-            return buffer.update(cx, |buffer, cx| buffer.redo(cx));
-        }
-
-        while let Some(transaction) = self.history.pop_redo() {
-            let mut redone = false;
-            for (buffer_id, buffer_transaction_id) in &mut transaction.buffer_transactions {
-                if let Some(BufferState { buffer, .. }) = self.buffers.borrow().get(buffer_id) {
-                    redone |= buffer.update(cx, |buffer, cx| {
-                        let redo_to = *buffer_transaction_id;
-                        if let Some(entry) = buffer.peek_redo_stack() {
-                            *buffer_transaction_id = entry.transaction_id();
-                        }
-                        buffer.redo_to_transaction(redo_to, cx)
-                    });
-                }
-            }
-
-            if redone {
-                return Some(transaction.id);
-            }
-        }
-
-        None
-    }
-
-    pub fn undo_transaction(&mut self, transaction_id: TransactionId, cx: &mut ModelContext<Self>) {
-        if let Some(buffer) = self.as_singleton() {
-            buffer.update(cx, |buffer, cx| buffer.undo_transaction(transaction_id, cx));
-        } else if let Some(transaction) = self.history.remove_from_undo(transaction_id) {
-            for (buffer_id, transaction_id) in &transaction.buffer_transactions {
-                if let Some(BufferState { buffer, .. }) = self.buffers.borrow().get(buffer_id) {
-                    buffer.update(cx, |buffer, cx| {
-                        buffer.undo_transaction(*transaction_id, cx)
-                    });
-                }
-            }
-        }
-    }
-
-    pub fn stream_excerpts_with_context_lines(
-        &mut self,
-        buffer: Model<Buffer>,
-        ranges: Vec<Range<text::Anchor>>,
-        context_line_count: u32,
-        cx: &mut ModelContext<Self>,
-    ) -> mpsc::Receiver<Range<Anchor>> {
-        let (buffer_id, buffer_snapshot) =
-            buffer.update(cx, |buffer, _| (buffer.remote_id(), buffer.snapshot()));
-
-        let (mut tx, rx) = mpsc::channel(256);
-        cx.spawn(move |this, mut cx| async move {
-            let mut excerpt_ranges = Vec::new();
-            let mut range_counts = Vec::new();
-            cx.background_executor()
-                .scoped(|scope| {
-                    scope.spawn(async {
-                        let (ranges, counts) =
-                            build_excerpt_ranges(&buffer_snapshot, &ranges, context_line_count);
-                        excerpt_ranges = ranges;
-                        range_counts = counts;
-                    });
-                })
-                .await;
-
-            let mut ranges = ranges.into_iter();
-            let mut range_counts = range_counts.into_iter();
-            for excerpt_ranges in excerpt_ranges.chunks(100) {
-                let excerpt_ids = match this.update(&mut cx, |this, cx| {
-                    this.push_excerpts(buffer.clone(), excerpt_ranges.iter().cloned(), cx)
-                }) {
-                    Ok(excerpt_ids) => excerpt_ids,
-                    Err(_) => return,
-                };
-
-                for (excerpt_id, range_count) in excerpt_ids.into_iter().zip(range_counts.by_ref())
-                {
-                    for range in ranges.by_ref().take(range_count) {
-                        let start = Anchor {
-                            buffer_id: Some(buffer_id),
-                            excerpt_id: excerpt_id.clone(),
-                            text_anchor: range.start,
-                        };
-                        let end = Anchor {
-                            buffer_id: Some(buffer_id),
-                            excerpt_id: excerpt_id.clone(),
-                            text_anchor: range.end,
-                        };
-                        if tx.send(start..end).await.is_err() {
-                            break;
-                        }
-                    }
-                }
-            }
-        })
-        .detach();
-
-        rx
-    }
-
-    pub fn push_excerpts<O>(
-        &mut self,
-        buffer: Model<Buffer>,
-        ranges: impl IntoIterator<Item = ExcerptRange<O>>,
-        cx: &mut ModelContext<Self>,
-    ) -> Vec<ExcerptId>
-    where
-        O: text::ToOffset,
-    {
-        self.insert_excerpts_after(ExcerptId::max(), buffer, ranges, cx)
-    }
-
-    pub fn push_excerpts_with_context_lines<O>(
-        &mut self,
-        buffer: Model<Buffer>,
-        ranges: Vec<Range<O>>,
-        context_line_count: u32,
-        cx: &mut ModelContext<Self>,
-    ) -> Vec<Range<Anchor>>
-    where
-        O: text::ToPoint + text::ToOffset,
-    {
-        let buffer_id = buffer.read(cx).remote_id();
-        let buffer_snapshot = buffer.read(cx).snapshot();
-        let (excerpt_ranges, range_counts) =
-            build_excerpt_ranges(&buffer_snapshot, &ranges, context_line_count);
-
-        let excerpt_ids = self.push_excerpts(buffer, excerpt_ranges, cx);
-
-        let mut anchor_ranges = Vec::new();
-        let mut ranges = ranges.into_iter();
-        for (excerpt_id, range_count) in excerpt_ids.into_iter().zip(range_counts.into_iter()) {
-            anchor_ranges.extend(ranges.by_ref().take(range_count).map(|range| {
-                let start = Anchor {
-                    buffer_id: Some(buffer_id),
-                    excerpt_id: excerpt_id.clone(),
-                    text_anchor: buffer_snapshot.anchor_after(range.start),
-                };
-                let end = Anchor {
-                    buffer_id: Some(buffer_id),
-                    excerpt_id: excerpt_id.clone(),
-                    text_anchor: buffer_snapshot.anchor_after(range.end),
-                };
-                start..end
-            }))
-        }
-        anchor_ranges
-    }
-
-    pub fn insert_excerpts_after<O>(
-        &mut self,
-        prev_excerpt_id: ExcerptId,
-        buffer: Model<Buffer>,
-        ranges: impl IntoIterator<Item = ExcerptRange<O>>,
-        cx: &mut ModelContext<Self>,
-    ) -> Vec<ExcerptId>
-    where
-        O: text::ToOffset,
-    {
-        let mut ids = Vec::new();
-        let mut next_excerpt_id = self.next_excerpt_id;
-        self.insert_excerpts_with_ids_after(
-            prev_excerpt_id,
-            buffer,
-            ranges.into_iter().map(|range| {
-                let id = ExcerptId(post_inc(&mut next_excerpt_id));
-                ids.push(id);
-                (id, range)
-            }),
-            cx,
-        );
-        ids
-    }
-
-    pub fn insert_excerpts_with_ids_after<O>(
-        &mut self,
-        prev_excerpt_id: ExcerptId,
-        buffer: Model<Buffer>,
-        ranges: impl IntoIterator<Item = (ExcerptId, ExcerptRange<O>)>,
-        cx: &mut ModelContext<Self>,
-    ) where
-        O: text::ToOffset,
-    {
-        assert_eq!(self.history.transaction_depth, 0);
-        let mut ranges = ranges.into_iter().peekable();
-        if ranges.peek().is_none() {
-            return Default::default();
-        }
-
-        self.sync(cx);
-
-        let buffer_id = buffer.read(cx).remote_id();
-        let buffer_snapshot = buffer.read(cx).snapshot();
-
-        let mut buffers = self.buffers.borrow_mut();
-        let buffer_state = buffers.entry(buffer_id).or_insert_with(|| BufferState {
-            last_version: buffer_snapshot.version().clone(),
-            last_parse_count: buffer_snapshot.parse_count(),
-            last_selections_update_count: buffer_snapshot.selections_update_count(),
-            last_diagnostics_update_count: buffer_snapshot.diagnostics_update_count(),
-            last_file_update_count: buffer_snapshot.file_update_count(),
-            last_git_diff_update_count: buffer_snapshot.git_diff_update_count(),
-            excerpts: Default::default(),
-            _subscriptions: [
-                cx.observe(&buffer, |_, _, cx| cx.notify()),
-                cx.subscribe(&buffer, Self::on_buffer_event),
-            ],
-            buffer: buffer.clone(),
-        });
-
-        let mut snapshot = self.snapshot.borrow_mut();
-
-        let mut prev_locator = snapshot.excerpt_locator_for_id(prev_excerpt_id).clone();
-        let mut new_excerpt_ids = mem::take(&mut snapshot.excerpt_ids);
-        let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>();
-        let mut new_excerpts = cursor.slice(&prev_locator, Bias::Right, &());
-        prev_locator = cursor.start().unwrap_or(Locator::min_ref()).clone();
-
-        let edit_start = new_excerpts.summary().text.len;
-        new_excerpts.update_last(
-            |excerpt| {
-                excerpt.has_trailing_newline = true;
-            },
-            &(),
-        );
-
-        let next_locator = if let Some(excerpt) = cursor.item() {
-            excerpt.locator.clone()
-        } else {
-            Locator::max()
-        };
-
-        let mut excerpts = Vec::new();
-        while let Some((id, range)) = ranges.next() {
-            let locator = Locator::between(&prev_locator, &next_locator);
-            if let Err(ix) = buffer_state.excerpts.binary_search(&locator) {
-                buffer_state.excerpts.insert(ix, locator.clone());
-            }
-            let range = ExcerptRange {
-                context: buffer_snapshot.anchor_before(&range.context.start)
-                    ..buffer_snapshot.anchor_after(&range.context.end),
-                primary: range.primary.map(|primary| {
-                    buffer_snapshot.anchor_before(&primary.start)
-                        ..buffer_snapshot.anchor_after(&primary.end)
-                }),
-            };
-            if id.0 >= self.next_excerpt_id {
-                self.next_excerpt_id = id.0 + 1;
-            }
-            excerpts.push((id, range.clone()));
-            let excerpt = Excerpt::new(
-                id,
-                locator.clone(),
-                buffer_id,
-                buffer_snapshot.clone(),
-                range,
-                ranges.peek().is_some() || cursor.item().is_some(),
-            );
-            new_excerpts.push(excerpt, &());
-            prev_locator = locator.clone();
-            new_excerpt_ids.push(ExcerptIdMapping { id, locator }, &());
-        }
-
-        let edit_end = new_excerpts.summary().text.len;
-
-        let suffix = cursor.suffix(&());
-        let changed_trailing_excerpt = suffix.is_empty();
-        new_excerpts.append(suffix, &());
-        drop(cursor);
-        snapshot.excerpts = new_excerpts;
-        snapshot.excerpt_ids = new_excerpt_ids;
-        if changed_trailing_excerpt {
-            snapshot.trailing_excerpt_update_count += 1;
-        }
-
-        self.subscriptions.publish_mut([Edit {
-            old: edit_start..edit_start,
-            new: edit_start..edit_end,
-        }]);
-        cx.emit(Event::Edited {
-            sigleton_buffer_edited: false,
-        });
-        cx.emit(Event::ExcerptsAdded {
-            buffer,
-            predecessor: prev_excerpt_id,
-            excerpts,
-        });
-        cx.notify();
-    }
-
-    pub fn clear(&mut self, cx: &mut ModelContext<Self>) {
-        self.sync(cx);
-        let ids = self.excerpt_ids();
-        self.buffers.borrow_mut().clear();
-        let mut snapshot = self.snapshot.borrow_mut();
-        let prev_len = snapshot.len();
-        snapshot.excerpts = Default::default();
-        snapshot.trailing_excerpt_update_count += 1;
-        snapshot.is_dirty = false;
-        snapshot.has_conflict = false;
-
-        self.subscriptions.publish_mut([Edit {
-            old: 0..prev_len,
-            new: 0..0,
-        }]);
-        cx.emit(Event::Edited {
-            sigleton_buffer_edited: false,
-        });
-        cx.emit(Event::ExcerptsRemoved { ids });
-        cx.notify();
-    }
-
-    pub fn excerpts_for_buffer(
-        &self,
-        buffer: &Model<Buffer>,
-        cx: &AppContext,
-    ) -> Vec<(ExcerptId, ExcerptRange<text::Anchor>)> {
-        let mut excerpts = Vec::new();
-        let snapshot = self.read(cx);
-        let buffers = self.buffers.borrow();
-        let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>();
-        for locator in buffers
-            .get(&buffer.read(cx).remote_id())
-            .map(|state| &state.excerpts)
-            .into_iter()
-            .flatten()
-        {
-            cursor.seek_forward(&Some(locator), Bias::Left, &());
-            if let Some(excerpt) = cursor.item() {
-                if excerpt.locator == *locator {
-                    excerpts.push((excerpt.id.clone(), excerpt.range.clone()));
-                }
-            }
-        }
-
-        excerpts
-    }
-
-    pub fn excerpt_ids(&self) -> Vec<ExcerptId> {
-        self.snapshot
-            .borrow()
-            .excerpts
-            .iter()
-            .map(|entry| entry.id)
-            .collect()
-    }
-
-    pub fn excerpt_containing(
-        &self,
-        position: impl ToOffset,
-        cx: &AppContext,
-    ) -> Option<(ExcerptId, Model<Buffer>, Range<text::Anchor>)> {
-        let snapshot = self.read(cx);
-        let position = position.to_offset(&snapshot);
-
-        let mut cursor = snapshot.excerpts.cursor::<usize>();
-        cursor.seek(&position, Bias::Right, &());
-        cursor
-            .item()
-            .or_else(|| snapshot.excerpts.last())
-            .map(|excerpt| {
-                (
-                    excerpt.id.clone(),
-                    self.buffers
-                        .borrow()
-                        .get(&excerpt.buffer_id)
-                        .unwrap()
-                        .buffer
-                        .clone(),
-                    excerpt.range.context.clone(),
-                )
-            })
-    }
-
-    // If point is at the end of the buffer, the last excerpt is returned
-    pub fn point_to_buffer_offset<T: ToOffset>(
-        &self,
-        point: T,
-        cx: &AppContext,
-    ) -> Option<(Model<Buffer>, usize, ExcerptId)> {
-        let snapshot = self.read(cx);
-        let offset = point.to_offset(&snapshot);
-        let mut cursor = snapshot.excerpts.cursor::<usize>();
-        cursor.seek(&offset, Bias::Right, &());
-        if cursor.item().is_none() {
-            cursor.prev(&());
-        }
-
-        cursor.item().map(|excerpt| {
-            let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
-            let buffer_point = excerpt_start + offset - *cursor.start();
-            let buffer = self.buffers.borrow()[&excerpt.buffer_id].buffer.clone();
-
-            (buffer, buffer_point, excerpt.id)
-        })
-    }
-
-    pub fn range_to_buffer_ranges<T: ToOffset>(
-        &self,
-        range: Range<T>,
-        cx: &AppContext,
-    ) -> Vec<(Model<Buffer>, Range<usize>, ExcerptId)> {
-        let snapshot = self.read(cx);
-        let start = range.start.to_offset(&snapshot);
-        let end = range.end.to_offset(&snapshot);
-
-        let mut result = Vec::new();
-        let mut cursor = snapshot.excerpts.cursor::<usize>();
-        cursor.seek(&start, Bias::Right, &());
-        if cursor.item().is_none() {
-            cursor.prev(&());
-        }
-
-        while let Some(excerpt) = cursor.item() {
-            if *cursor.start() > end {
-                break;
-            }
-
-            let mut end_before_newline = cursor.end(&());
-            if excerpt.has_trailing_newline {
-                end_before_newline -= 1;
-            }
-            let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
-            let start = excerpt_start + (cmp::max(start, *cursor.start()) - *cursor.start());
-            let end = excerpt_start + (cmp::min(end, end_before_newline) - *cursor.start());
-            let buffer = self.buffers.borrow()[&excerpt.buffer_id].buffer.clone();
-            result.push((buffer, start..end, excerpt.id));
-            cursor.next(&());
-        }
-
-        result
-    }
-
-    pub fn remove_excerpts(
-        &mut self,
-        excerpt_ids: impl IntoIterator<Item = ExcerptId>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        self.sync(cx);
-        let ids = excerpt_ids.into_iter().collect::<Vec<_>>();
-        if ids.is_empty() {
-            return;
-        }
-
-        let mut buffers = self.buffers.borrow_mut();
-        let mut snapshot = self.snapshot.borrow_mut();
-        let mut new_excerpts = SumTree::new();
-        let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, usize)>();
-        let mut edits = Vec::new();
-        let mut excerpt_ids = ids.iter().copied().peekable();
-
-        while let Some(excerpt_id) = excerpt_ids.next() {
-            // Seek to the next excerpt to remove, preserving any preceding excerpts.
-            let locator = snapshot.excerpt_locator_for_id(excerpt_id);
-            new_excerpts.append(cursor.slice(&Some(locator), Bias::Left, &()), &());
-
-            if let Some(mut excerpt) = cursor.item() {
-                if excerpt.id != excerpt_id {
-                    continue;
-                }
-                let mut old_start = cursor.start().1;
-
-                // Skip over the removed excerpt.
-                'remove_excerpts: loop {
-                    if let Some(buffer_state) = buffers.get_mut(&excerpt.buffer_id) {
-                        buffer_state.excerpts.retain(|l| l != &excerpt.locator);
-                        if buffer_state.excerpts.is_empty() {
-                            buffers.remove(&excerpt.buffer_id);
-                        }
-                    }
-                    cursor.next(&());
-
-                    // Skip over any subsequent excerpts that are also removed.
-                    while let Some(&next_excerpt_id) = excerpt_ids.peek() {
-                        let next_locator = snapshot.excerpt_locator_for_id(next_excerpt_id);
-                        if let Some(next_excerpt) = cursor.item() {
-                            if next_excerpt.locator == *next_locator {
-                                excerpt_ids.next();
-                                excerpt = next_excerpt;
-                                continue 'remove_excerpts;
-                            }
-                        }
-                        break;
-                    }
-
-                    break;
-                }
-
-                // When removing the last excerpt, remove the trailing newline from
-                // the previous excerpt.
-                if cursor.item().is_none() && old_start > 0 {
-                    old_start -= 1;
-                    new_excerpts.update_last(|e| e.has_trailing_newline = false, &());
-                }
-
-                // Push an edit for the removal of this run of excerpts.
-                let old_end = cursor.start().1;
-                let new_start = new_excerpts.summary().text.len;
-                edits.push(Edit {
-                    old: old_start..old_end,
-                    new: new_start..new_start,
-                });
-            }
-        }
-        let suffix = cursor.suffix(&());
-        let changed_trailing_excerpt = suffix.is_empty();
-        new_excerpts.append(suffix, &());
-        drop(cursor);
-        snapshot.excerpts = new_excerpts;
-
-        if changed_trailing_excerpt {
-            snapshot.trailing_excerpt_update_count += 1;
-        }
-
-        self.subscriptions.publish_mut(edits);
-        cx.emit(Event::Edited {
-            sigleton_buffer_edited: false,
-        });
-        cx.emit(Event::ExcerptsRemoved { ids });
-        cx.notify();
-    }
-
-    pub fn wait_for_anchors<'a>(
-        &self,
-        anchors: impl 'a + Iterator<Item = Anchor>,
-        cx: &mut ModelContext<Self>,
-    ) -> impl 'static + Future<Output = Result<()>> {
-        let borrow = self.buffers.borrow();
-        let mut error = None;
-        let mut futures = Vec::new();
-        for anchor in anchors {
-            if let Some(buffer_id) = anchor.buffer_id {
-                if let Some(buffer) = borrow.get(&buffer_id) {
-                    buffer.buffer.update(cx, |buffer, _| {
-                        futures.push(buffer.wait_for_anchors([anchor.text_anchor]))
-                    });
-                } else {
-                    error = Some(anyhow!(
-                        "buffer {buffer_id} is not part of this multi-buffer"
-                    ));
-                    break;
-                }
-            }
-        }
-        async move {
-            if let Some(error) = error {
-                Err(error)?;
-            }
-            for future in futures {
-                future.await?;
-            }
-            Ok(())
-        }
-    }
-
-    pub fn text_anchor_for_position<T: ToOffset>(
-        &self,
-        position: T,
-        cx: &AppContext,
-    ) -> Option<(Model<Buffer>, language::Anchor)> {
-        let snapshot = self.read(cx);
-        let anchor = snapshot.anchor_before(position);
-        let buffer = self
-            .buffers
-            .borrow()
-            .get(&anchor.buffer_id?)?
-            .buffer
-            .clone();
-        Some((buffer, anchor.text_anchor))
-    }
-
-    fn on_buffer_event(
-        &mut self,
-        _: Model<Buffer>,
-        event: &language::Event,
-        cx: &mut ModelContext<Self>,
-    ) {
-        cx.emit(match event {
-            language::Event::Edited => Event::Edited {
-                sigleton_buffer_edited: true,
-            },
-            language::Event::DirtyChanged => Event::DirtyChanged,
-            language::Event::Saved => Event::Saved,
-            language::Event::FileHandleChanged => Event::FileHandleChanged,
-            language::Event::Reloaded => Event::Reloaded,
-            language::Event::DiffBaseChanged => Event::DiffBaseChanged,
-            language::Event::LanguageChanged => Event::LanguageChanged,
-            language::Event::Reparsed => Event::Reparsed,
-            language::Event::DiagnosticsUpdated => Event::DiagnosticsUpdated,
-            language::Event::Closed => Event::Closed,
-
-            //
-            language::Event::Operation(_) => return,
-        });
-    }
-
-    pub fn all_buffers(&self) -> HashSet<Model<Buffer>> {
-        self.buffers
-            .borrow()
-            .values()
-            .map(|state| state.buffer.clone())
-            .collect()
-    }
-
-    pub fn buffer(&self, buffer_id: u64) -> Option<Model<Buffer>> {
-        self.buffers
-            .borrow()
-            .get(&buffer_id)
-            .map(|state| state.buffer.clone())
-    }
-
-    pub fn is_completion_trigger(&self, position: Anchor, text: &str, cx: &AppContext) -> bool {
-        let mut chars = text.chars();
-        let char = if let Some(char) = chars.next() {
-            char
-        } else {
-            return false;
-        };
-        if chars.next().is_some() {
-            return false;
-        }
-
-        let snapshot = self.snapshot(cx);
-        let position = position.to_offset(&snapshot);
-        let scope = snapshot.language_scope_at(position);
-        if char_kind(&scope, char) == CharKind::Word {
-            return true;
-        }
-
-        let anchor = snapshot.anchor_before(position);
-        anchor
-            .buffer_id
-            .and_then(|buffer_id| {
-                let buffer = self.buffers.borrow().get(&buffer_id)?.buffer.clone();
-                Some(
-                    buffer
-                        .read(cx)
-                        .completion_triggers()
-                        .iter()
-                        .any(|string| string == text),
-                )
-            })
-            .unwrap_or(false)
-    }
-
-    pub fn language_at<'a, T: ToOffset>(
-        &self,
-        point: T,
-        cx: &'a AppContext,
-    ) -> Option<Arc<Language>> {
-        self.point_to_buffer_offset(point, cx)
-            .and_then(|(buffer, offset, _)| buffer.read(cx).language_at(offset))
-    }
-
-    pub fn settings_at<'a, T: ToOffset>(
-        &self,
-        point: T,
-        cx: &'a AppContext,
-    ) -> &'a LanguageSettings {
-        let mut language = None;
-        let mut file = None;
-        if let Some((buffer, offset, _)) = self.point_to_buffer_offset(point, cx) {
-            let buffer = buffer.read(cx);
-            language = buffer.language_at(offset);
-            file = buffer.file();
-        }
-        language_settings(language.as_ref(), file, cx)
-    }
-
-    pub fn for_each_buffer(&self, mut f: impl FnMut(&Model<Buffer>)) {
-        self.buffers
-            .borrow()
-            .values()
-            .for_each(|state| f(&state.buffer))
-    }
-
-    pub fn title<'a>(&'a self, cx: &'a AppContext) -> Cow<'a, str> {
-        if let Some(title) = self.title.as_ref() {
-            return title.into();
-        }
-
-        if let Some(buffer) = self.as_singleton() {
-            if let Some(file) = buffer.read(cx).file() {
-                return file.file_name(cx).to_string_lossy();
-            }
-        }
-
-        "untitled".into()
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn is_parsing(&self, cx: &AppContext) -> bool {
-        self.as_singleton().unwrap().read(cx).is_parsing()
-    }
-
-    fn sync(&self, cx: &AppContext) {
-        let mut snapshot = self.snapshot.borrow_mut();
-        let mut excerpts_to_edit = Vec::new();
-        let mut reparsed = false;
-        let mut diagnostics_updated = false;
-        let mut git_diff_updated = false;
-        let mut is_dirty = false;
-        let mut has_conflict = false;
-        let mut edited = false;
-        let mut buffers = self.buffers.borrow_mut();
-        for buffer_state in buffers.values_mut() {
-            let buffer = buffer_state.buffer.read(cx);
-            let version = buffer.version();
-            let parse_count = buffer.parse_count();
-            let selections_update_count = buffer.selections_update_count();
-            let diagnostics_update_count = buffer.diagnostics_update_count();
-            let file_update_count = buffer.file_update_count();
-            let git_diff_update_count = buffer.git_diff_update_count();
-
-            let buffer_edited = version.changed_since(&buffer_state.last_version);
-            let buffer_reparsed = parse_count > buffer_state.last_parse_count;
-            let buffer_selections_updated =
-                selections_update_count > buffer_state.last_selections_update_count;
-            let buffer_diagnostics_updated =
-                diagnostics_update_count > buffer_state.last_diagnostics_update_count;
-            let buffer_file_updated = file_update_count > buffer_state.last_file_update_count;
-            let buffer_git_diff_updated =
-                git_diff_update_count > buffer_state.last_git_diff_update_count;
-            if buffer_edited
-                || buffer_reparsed
-                || buffer_selections_updated
-                || buffer_diagnostics_updated
-                || buffer_file_updated
-                || buffer_git_diff_updated
-            {
-                buffer_state.last_version = version;
-                buffer_state.last_parse_count = parse_count;
-                buffer_state.last_selections_update_count = selections_update_count;
-                buffer_state.last_diagnostics_update_count = diagnostics_update_count;
-                buffer_state.last_file_update_count = file_update_count;
-                buffer_state.last_git_diff_update_count = git_diff_update_count;
-                excerpts_to_edit.extend(
-                    buffer_state
-                        .excerpts
-                        .iter()
-                        .map(|locator| (locator, buffer_state.buffer.clone(), buffer_edited)),
-                );
-            }
-
-            edited |= buffer_edited;
-            reparsed |= buffer_reparsed;
-            diagnostics_updated |= buffer_diagnostics_updated;
-            git_diff_updated |= buffer_git_diff_updated;
-            is_dirty |= buffer.is_dirty();
-            has_conflict |= buffer.has_conflict();
-        }
-        if edited {
-            snapshot.edit_count += 1;
-        }
-        if reparsed {
-            snapshot.parse_count += 1;
-        }
-        if diagnostics_updated {
-            snapshot.diagnostics_update_count += 1;
-        }
-        if git_diff_updated {
-            snapshot.git_diff_update_count += 1;
-        }
-        snapshot.is_dirty = is_dirty;
-        snapshot.has_conflict = has_conflict;
-
-        excerpts_to_edit.sort_unstable_by_key(|(locator, _, _)| *locator);
-
-        let mut edits = Vec::new();
-        let mut new_excerpts = SumTree::new();
-        let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, usize)>();
-
-        for (locator, buffer, buffer_edited) in excerpts_to_edit {
-            new_excerpts.append(cursor.slice(&Some(locator), Bias::Left, &()), &());
-            let old_excerpt = cursor.item().unwrap();
-            let buffer = buffer.read(cx);
-            let buffer_id = buffer.remote_id();
-
-            let mut new_excerpt;
-            if buffer_edited {
-                edits.extend(
-                    buffer
-                        .edits_since_in_range::<usize>(
-                            old_excerpt.buffer.version(),
-                            old_excerpt.range.context.clone(),
-                        )
-                        .map(|mut edit| {
-                            let excerpt_old_start = cursor.start().1;
-                            let excerpt_new_start = new_excerpts.summary().text.len;
-                            edit.old.start += excerpt_old_start;
-                            edit.old.end += excerpt_old_start;
-                            edit.new.start += excerpt_new_start;
-                            edit.new.end += excerpt_new_start;
-                            edit
-                        }),
-                );
-
-                new_excerpt = Excerpt::new(
-                    old_excerpt.id,
-                    locator.clone(),
-                    buffer_id,
-                    buffer.snapshot(),
-                    old_excerpt.range.clone(),
-                    old_excerpt.has_trailing_newline,
-                );
-            } else {
-                new_excerpt = old_excerpt.clone();
-                new_excerpt.buffer = buffer.snapshot();
-            }
-
-            new_excerpts.push(new_excerpt, &());
-            cursor.next(&());
-        }
-        new_excerpts.append(cursor.suffix(&()), &());
-
-        drop(cursor);
-        snapshot.excerpts = new_excerpts;
-
-        self.subscriptions.publish(edits);
-    }
-}
-
-#[cfg(any(test, feature = "test-support"))]
-impl MultiBuffer {
-    pub fn build_simple(text: &str, cx: &mut gpui::AppContext) -> Model<Self> {
-        let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text));
-        cx.new_model(|cx| Self::singleton(buffer, cx))
-    }
-
-    pub fn build_multi<const COUNT: usize>(
-        excerpts: [(&str, Vec<Range<Point>>); COUNT],
-        cx: &mut gpui::AppContext,
-    ) -> Model<Self> {
-        let multi = cx.new_model(|_| Self::new(0));
-        for (text, ranges) in excerpts {
-            let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text));
-            let excerpt_ranges = ranges.into_iter().map(|range| ExcerptRange {
-                context: range,
-                primary: None,
-            });
-            multi.update(cx, |multi, cx| {
-                multi.push_excerpts(buffer, excerpt_ranges, cx)
-            });
-        }
-
-        multi
-    }
-
-    pub fn build_from_buffer(buffer: Model<Buffer>, cx: &mut gpui::AppContext) -> Model<Self> {
-        cx.new_model(|cx| Self::singleton(buffer, cx))
-    }
-
-    pub fn build_random(rng: &mut impl rand::Rng, cx: &mut gpui::AppContext) -> Model<Self> {
-        cx.new_model(|cx| {
-            let mut multibuffer = MultiBuffer::new(0);
-            let mutation_count = rng.gen_range(1..=5);
-            multibuffer.randomly_edit_excerpts(rng, mutation_count, cx);
-            multibuffer
-        })
-    }
-
-    pub fn randomly_edit(
-        &mut self,
-        rng: &mut impl rand::Rng,
-        edit_count: usize,
-        cx: &mut ModelContext<Self>,
-    ) {
-        use util::RandomCharIter;
-
-        let snapshot = self.read(cx);
-        let mut edits: Vec<(Range<usize>, Arc<str>)> = Vec::new();
-        let mut last_end = None;
-        for _ in 0..edit_count {
-            if last_end.map_or(false, |last_end| last_end >= snapshot.len()) {
-                break;
-            }
-
-            let new_start = last_end.map_or(0, |last_end| last_end + 1);
-            let end = snapshot.clip_offset(rng.gen_range(new_start..=snapshot.len()), Bias::Right);
-            let start = snapshot.clip_offset(rng.gen_range(new_start..=end), Bias::Right);
-            last_end = Some(end);
-
-            let mut range = start..end;
-            if rng.gen_bool(0.2) {
-                mem::swap(&mut range.start, &mut range.end);
-            }
-
-            let new_text_len = rng.gen_range(0..10);
-            let new_text: String = RandomCharIter::new(&mut *rng).take(new_text_len).collect();
-
-            edits.push((range, new_text.into()));
-        }
-        log::info!("mutating multi-buffer with {:?}", edits);
-        drop(snapshot);
-
-        self.edit(edits, None, cx);
-    }
-
-    pub fn randomly_edit_excerpts(
-        &mut self,
-        rng: &mut impl rand::Rng,
-        mutation_count: usize,
-        cx: &mut ModelContext<Self>,
-    ) {
-        use rand::prelude::*;
-        use std::env;
-        use util::RandomCharIter;
-
-        let max_excerpts = env::var("MAX_EXCERPTS")
-            .map(|i| i.parse().expect("invalid `MAX_EXCERPTS` variable"))
-            .unwrap_or(5);
-
-        let mut buffers = Vec::new();
-        for _ in 0..mutation_count {
-            if rng.gen_bool(0.05) {
-                log::info!("Clearing multi-buffer");
-                self.clear(cx);
-                continue;
-            }
-
-            let excerpt_ids = self.excerpt_ids();
-            if excerpt_ids.is_empty() || (rng.gen() && excerpt_ids.len() < max_excerpts) {
-                let buffer_handle = if rng.gen() || self.buffers.borrow().is_empty() {
-                    let text = RandomCharIter::new(&mut *rng).take(10).collect::<String>();
-                    buffers.push(cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text)));
-                    let buffer = buffers.last().unwrap().read(cx);
-                    log::info!(
-                        "Creating new buffer {} with text: {:?}",
-                        buffer.remote_id(),
-                        buffer.text()
-                    );
-                    buffers.last().unwrap().clone()
-                } else {
-                    self.buffers
-                        .borrow()
-                        .values()
-                        .choose(rng)
-                        .unwrap()
-                        .buffer
-                        .clone()
-                };
-
-                let buffer = buffer_handle.read(cx);
-                let buffer_text = buffer.text();
-                let ranges = (0..rng.gen_range(0..5))
-                    .map(|_| {
-                        let end_ix =
-                            buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Right);
-                        let start_ix = buffer.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
-                        ExcerptRange {
-                            context: start_ix..end_ix,
-                            primary: None,
-                        }
-                    })
-                    .collect::<Vec<_>>();
-                log::info!(
-                    "Inserting excerpts from buffer {} and ranges {:?}: {:?}",
-                    buffer_handle.read(cx).remote_id(),
-                    ranges.iter().map(|r| &r.context).collect::<Vec<_>>(),
-                    ranges
-                        .iter()
-                        .map(|r| &buffer_text[r.context.clone()])
-                        .collect::<Vec<_>>()
-                );
-
-                let excerpt_id = self.push_excerpts(buffer_handle.clone(), ranges, cx);
-                log::info!("Inserted with ids: {:?}", excerpt_id);
-            } else {
-                let remove_count = rng.gen_range(1..=excerpt_ids.len());
-                let mut excerpts_to_remove = excerpt_ids
-                    .choose_multiple(rng, remove_count)
-                    .cloned()
-                    .collect::<Vec<_>>();
-                let snapshot = self.snapshot.borrow();
-                excerpts_to_remove.sort_unstable_by(|a, b| a.cmp(b, &*snapshot));
-                drop(snapshot);
-                log::info!("Removing excerpts {:?}", excerpts_to_remove);
-                self.remove_excerpts(excerpts_to_remove, cx);
-            }
-        }
-    }
-
-    pub fn randomly_mutate(
-        &mut self,
-        rng: &mut impl rand::Rng,
-        mutation_count: usize,
-        cx: &mut ModelContext<Self>,
-    ) {
-        use rand::prelude::*;
-
-        if rng.gen_bool(0.7) || self.singleton {
-            let buffer = self
-                .buffers
-                .borrow()
-                .values()
-                .choose(rng)
-                .map(|state| state.buffer.clone());
-
-            if let Some(buffer) = buffer {
-                buffer.update(cx, |buffer, cx| {
-                    if rng.gen() {
-                        buffer.randomly_edit(rng, mutation_count, cx);
-                    } else {
-                        buffer.randomly_undo_redo(rng, cx);
-                    }
-                });
-            } else {
-                self.randomly_edit(rng, mutation_count, cx);
-            }
-        } else {
-            self.randomly_edit_excerpts(rng, mutation_count, cx);
-        }
-
-        self.check_invariants(cx);
-    }
-
-    fn check_invariants(&self, cx: &mut ModelContext<Self>) {
-        let snapshot = self.read(cx);
-        let excerpts = snapshot.excerpts.items(&());
-        let excerpt_ids = snapshot.excerpt_ids.items(&());
-
-        for (ix, excerpt) in excerpts.iter().enumerate() {
-            if ix == 0 {
-                if excerpt.locator <= Locator::min() {
-                    panic!("invalid first excerpt locator {:?}", excerpt.locator);
-                }
-            } else {
-                if excerpt.locator <= excerpts[ix - 1].locator {
-                    panic!("excerpts are out-of-order: {:?}", excerpts);
-                }
-            }
-        }
-
-        for (ix, entry) in excerpt_ids.iter().enumerate() {
-            if ix == 0 {
-                if entry.id.cmp(&ExcerptId::min(), &*snapshot).is_le() {
-                    panic!("invalid first excerpt id {:?}", entry.id);
-                }
-            } else {
-                if entry.id <= excerpt_ids[ix - 1].id {
-                    panic!("excerpt ids are out-of-order: {:?}", excerpt_ids);
-                }
-            }
-        }
-    }
-}
-
-impl EventEmitter<Event> for MultiBuffer {}
-
-impl MultiBufferSnapshot {
-    pub fn text(&self) -> String {
-        self.chunks(0..self.len(), false)
-            .map(|chunk| chunk.text)
-            .collect()
-    }
-
-    pub fn reversed_chars_at<T: ToOffset>(&self, position: T) -> impl Iterator<Item = char> + '_ {
-        let mut offset = position.to_offset(self);
-        let mut cursor = self.excerpts.cursor::<usize>();
-        cursor.seek(&offset, Bias::Left, &());
-        let mut excerpt_chunks = cursor.item().map(|excerpt| {
-            let end_before_footer = cursor.start() + excerpt.text_summary.len;
-            let start = excerpt.range.context.start.to_offset(&excerpt.buffer);
-            let end = start + (cmp::min(offset, end_before_footer) - cursor.start());
-            excerpt.buffer.reversed_chunks_in_range(start..end)
-        });
-        iter::from_fn(move || {
-            if offset == *cursor.start() {
-                cursor.prev(&());
-                let excerpt = cursor.item()?;
-                excerpt_chunks = Some(
-                    excerpt
-                        .buffer
-                        .reversed_chunks_in_range(excerpt.range.context.clone()),
-                );
-            }
-
-            let excerpt = cursor.item().unwrap();
-            if offset == cursor.end(&()) && excerpt.has_trailing_newline {
-                offset -= 1;
-                Some("\n")
-            } else {
-                let chunk = excerpt_chunks.as_mut().unwrap().next().unwrap();
-                offset -= chunk.len();
-                Some(chunk)
-            }
-        })
-        .flat_map(|c| c.chars().rev())
-    }
-
-    pub fn chars_at<T: ToOffset>(&self, position: T) -> impl Iterator<Item = char> + '_ {
-        let offset = position.to_offset(self);
-        self.text_for_range(offset..self.len())
-            .flat_map(|chunk| chunk.chars())
-    }
-
-    pub fn text_for_range<T: ToOffset>(&self, range: Range<T>) -> impl Iterator<Item = &str> + '_ {
-        self.chunks(range, false).map(|chunk| chunk.text)
-    }
-
-    pub fn is_line_blank(&self, row: u32) -> bool {
-        self.text_for_range(Point::new(row, 0)..Point::new(row, self.line_len(row)))
-            .all(|chunk| chunk.matches(|c: char| !c.is_whitespace()).next().is_none())
-    }
-
-    pub fn contains_str_at<T>(&self, position: T, needle: &str) -> bool
-    where
-        T: ToOffset,
-    {
-        let position = position.to_offset(self);
-        position == self.clip_offset(position, Bias::Left)
-            && self
-                .bytes_in_range(position..self.len())
-                .flatten()
-                .copied()
-                .take(needle.len())
-                .eq(needle.bytes())
-    }
-
-    pub fn surrounding_word<T: ToOffset>(&self, start: T) -> (Range<usize>, Option<CharKind>) {
-        let mut start = start.to_offset(self);
-        let mut end = start;
-        let mut next_chars = self.chars_at(start).peekable();
-        let mut prev_chars = self.reversed_chars_at(start).peekable();
-
-        let scope = self.language_scope_at(start);
-        let kind = |c| char_kind(&scope, c);
-        let word_kind = cmp::max(
-            prev_chars.peek().copied().map(kind),
-            next_chars.peek().copied().map(kind),
-        );
-
-        for ch in prev_chars {
-            if Some(kind(ch)) == word_kind && ch != '\n' {
-                start -= ch.len_utf8();
-            } else {
-                break;
-            }
-        }
-
-        for ch in next_chars {
-            if Some(kind(ch)) == word_kind && ch != '\n' {
-                end += ch.len_utf8();
-            } else {
-                break;
-            }
-        }
-
-        (start..end, word_kind)
-    }
-
-    pub fn as_singleton(&self) -> Option<(&ExcerptId, u64, &BufferSnapshot)> {
-        if self.singleton {
-            self.excerpts
-                .iter()
-                .next()
-                .map(|e| (&e.id, e.buffer_id, &e.buffer))
-        } else {
-            None
-        }
-    }
-
-    pub fn len(&self) -> usize {
-        self.excerpts.summary().text.len
-    }
-
-    pub fn is_empty(&self) -> bool {
-        self.excerpts.summary().text.len == 0
-    }
-
-    pub fn max_buffer_row(&self) -> u32 {
-        self.excerpts.summary().max_buffer_row
-    }
-
-    pub fn clip_offset(&self, offset: usize, bias: Bias) -> usize {
-        if let Some((_, _, buffer)) = self.as_singleton() {
-            return buffer.clip_offset(offset, bias);
-        }
-
-        let mut cursor = self.excerpts.cursor::<usize>();
-        cursor.seek(&offset, Bias::Right, &());
-        let overshoot = if let Some(excerpt) = cursor.item() {
-            let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
-            let buffer_offset = excerpt
-                .buffer
-                .clip_offset(excerpt_start + (offset - cursor.start()), bias);
-            buffer_offset.saturating_sub(excerpt_start)
-        } else {
-            0
-        };
-        cursor.start() + overshoot
-    }
-
-    pub fn clip_point(&self, point: Point, bias: Bias) -> Point {
-        if let Some((_, _, buffer)) = self.as_singleton() {
-            return buffer.clip_point(point, bias);
-        }
-
-        let mut cursor = self.excerpts.cursor::<Point>();
-        cursor.seek(&point, Bias::Right, &());
-        let overshoot = if let Some(excerpt) = cursor.item() {
-            let excerpt_start = excerpt.range.context.start.to_point(&excerpt.buffer);
-            let buffer_point = excerpt
-                .buffer
-                .clip_point(excerpt_start + (point - cursor.start()), bias);
-            buffer_point.saturating_sub(excerpt_start)
-        } else {
-            Point::zero()
-        };
-        *cursor.start() + overshoot
-    }
-
-    pub fn clip_offset_utf16(&self, offset: OffsetUtf16, bias: Bias) -> OffsetUtf16 {
-        if let Some((_, _, buffer)) = self.as_singleton() {
-            return buffer.clip_offset_utf16(offset, bias);
-        }
-
-        let mut cursor = self.excerpts.cursor::<OffsetUtf16>();
-        cursor.seek(&offset, Bias::Right, &());
-        let overshoot = if let Some(excerpt) = cursor.item() {
-            let excerpt_start = excerpt.range.context.start.to_offset_utf16(&excerpt.buffer);
-            let buffer_offset = excerpt
-                .buffer
-                .clip_offset_utf16(excerpt_start + (offset - cursor.start()), bias);
-            OffsetUtf16(buffer_offset.0.saturating_sub(excerpt_start.0))
-        } else {
-            OffsetUtf16(0)
-        };
-        *cursor.start() + overshoot
-    }
-
-    pub fn clip_point_utf16(&self, point: Unclipped<PointUtf16>, bias: Bias) -> PointUtf16 {
-        if let Some((_, _, buffer)) = self.as_singleton() {
-            return buffer.clip_point_utf16(point, bias);
-        }
-
-        let mut cursor = self.excerpts.cursor::<PointUtf16>();
-        cursor.seek(&point.0, Bias::Right, &());
-        let overshoot = if let Some(excerpt) = cursor.item() {
-            let excerpt_start = excerpt
-                .buffer
-                .offset_to_point_utf16(excerpt.range.context.start.to_offset(&excerpt.buffer));
-            let buffer_point = excerpt
-                .buffer
-                .clip_point_utf16(Unclipped(excerpt_start + (point.0 - cursor.start())), bias);
-            buffer_point.saturating_sub(excerpt_start)
-        } else {
-            PointUtf16::zero()
-        };
-        *cursor.start() + overshoot
-    }
-
-    pub fn bytes_in_range<T: ToOffset>(&self, range: Range<T>) -> MultiBufferBytes {
-        let range = range.start.to_offset(self)..range.end.to_offset(self);
-        let mut excerpts = self.excerpts.cursor::<usize>();
-        excerpts.seek(&range.start, Bias::Right, &());
-
-        let mut chunk = &[][..];
-        let excerpt_bytes = if let Some(excerpt) = excerpts.item() {
-            let mut excerpt_bytes = excerpt
-                .bytes_in_range(range.start - excerpts.start()..range.end - excerpts.start());
-            chunk = excerpt_bytes.next().unwrap_or(&[][..]);
-            Some(excerpt_bytes)
-        } else {
-            None
-        };
-        MultiBufferBytes {
-            range,
-            excerpts,
-            excerpt_bytes,
-            chunk,
-        }
-    }
-
-    pub fn reversed_bytes_in_range<T: ToOffset>(
-        &self,
-        range: Range<T>,
-    ) -> ReversedMultiBufferBytes {
-        let range = range.start.to_offset(self)..range.end.to_offset(self);
-        let mut excerpts = self.excerpts.cursor::<usize>();
-        excerpts.seek(&range.end, Bias::Left, &());
-
-        let mut chunk = &[][..];
-        let excerpt_bytes = if let Some(excerpt) = excerpts.item() {
-            let mut excerpt_bytes = excerpt.reversed_bytes_in_range(
-                range.start - excerpts.start()..range.end - excerpts.start(),
-            );
-            chunk = excerpt_bytes.next().unwrap_or(&[][..]);
-            Some(excerpt_bytes)
-        } else {
-            None
-        };
-
-        ReversedMultiBufferBytes {
-            range,
-            excerpts,
-            excerpt_bytes,
-            chunk,
-        }
-    }
-
-    pub fn buffer_rows(&self, start_row: u32) -> MultiBufferRows {
-        let mut result = MultiBufferRows {
-            buffer_row_range: 0..0,
-            excerpts: self.excerpts.cursor(),
-        };
-        result.seek(start_row);
-        result
-    }
-
-    pub fn chunks<T: ToOffset>(&self, range: Range<T>, language_aware: bool) -> MultiBufferChunks {
-        let range = range.start.to_offset(self)..range.end.to_offset(self);
-        let mut chunks = MultiBufferChunks {
-            range: range.clone(),
-            excerpts: self.excerpts.cursor(),
-            excerpt_chunks: None,
-            language_aware,
-        };
-        chunks.seek(range.start);
-        chunks
-    }
-
-    pub fn offset_to_point(&self, offset: usize) -> Point {
-        if let Some((_, _, buffer)) = self.as_singleton() {
-            return buffer.offset_to_point(offset);
-        }
-
-        let mut cursor = self.excerpts.cursor::<(usize, Point)>();
-        cursor.seek(&offset, Bias::Right, &());
-        if let Some(excerpt) = cursor.item() {
-            let (start_offset, start_point) = cursor.start();
-            let overshoot = offset - start_offset;
-            let excerpt_start_offset = excerpt.range.context.start.to_offset(&excerpt.buffer);
-            let excerpt_start_point = excerpt.range.context.start.to_point(&excerpt.buffer);
-            let buffer_point = excerpt
-                .buffer
-                .offset_to_point(excerpt_start_offset + overshoot);
-            *start_point + (buffer_point - excerpt_start_point)
-        } else {
-            self.excerpts.summary().text.lines
-        }
-    }
-
-    pub fn offset_to_point_utf16(&self, offset: usize) -> PointUtf16 {
-        if let Some((_, _, buffer)) = self.as_singleton() {
-            return buffer.offset_to_point_utf16(offset);
-        }
-
-        let mut cursor = self.excerpts.cursor::<(usize, PointUtf16)>();
-        cursor.seek(&offset, Bias::Right, &());
-        if let Some(excerpt) = cursor.item() {
-            let (start_offset, start_point) = cursor.start();
-            let overshoot = offset - start_offset;
-            let excerpt_start_offset = excerpt.range.context.start.to_offset(&excerpt.buffer);
-            let excerpt_start_point = excerpt.range.context.start.to_point_utf16(&excerpt.buffer);
-            let buffer_point = excerpt
-                .buffer
-                .offset_to_point_utf16(excerpt_start_offset + overshoot);
-            *start_point + (buffer_point - excerpt_start_point)
-        } else {
-            self.excerpts.summary().text.lines_utf16()
-        }
-    }
-
-    pub fn point_to_point_utf16(&self, point: Point) -> PointUtf16 {
-        if let Some((_, _, buffer)) = self.as_singleton() {
-            return buffer.point_to_point_utf16(point);
-        }
-
-        let mut cursor = self.excerpts.cursor::<(Point, PointUtf16)>();
-        cursor.seek(&point, Bias::Right, &());
-        if let Some(excerpt) = cursor.item() {
-            let (start_offset, start_point) = cursor.start();
-            let overshoot = point - start_offset;
-            let excerpt_start_point = excerpt.range.context.start.to_point(&excerpt.buffer);
-            let excerpt_start_point_utf16 =
-                excerpt.range.context.start.to_point_utf16(&excerpt.buffer);
-            let buffer_point = excerpt
-                .buffer
-                .point_to_point_utf16(excerpt_start_point + overshoot);
-            *start_point + (buffer_point - excerpt_start_point_utf16)
-        } else {
-            self.excerpts.summary().text.lines_utf16()
-        }
-    }
-
-    pub fn point_to_offset(&self, point: Point) -> usize {
-        if let Some((_, _, buffer)) = self.as_singleton() {
-            return buffer.point_to_offset(point);
-        }
-
-        let mut cursor = self.excerpts.cursor::<(Point, usize)>();
-        cursor.seek(&point, Bias::Right, &());
-        if let Some(excerpt) = cursor.item() {
-            let (start_point, start_offset) = cursor.start();
-            let overshoot = point - start_point;
-            let excerpt_start_offset = excerpt.range.context.start.to_offset(&excerpt.buffer);
-            let excerpt_start_point = excerpt.range.context.start.to_point(&excerpt.buffer);
-            let buffer_offset = excerpt
-                .buffer
-                .point_to_offset(excerpt_start_point + overshoot);
-            *start_offset + buffer_offset - excerpt_start_offset
-        } else {
-            self.excerpts.summary().text.len
-        }
-    }
-
-    pub fn offset_utf16_to_offset(&self, offset_utf16: OffsetUtf16) -> usize {
-        if let Some((_, _, buffer)) = self.as_singleton() {
-            return buffer.offset_utf16_to_offset(offset_utf16);
-        }
-
-        let mut cursor = self.excerpts.cursor::<(OffsetUtf16, usize)>();
-        cursor.seek(&offset_utf16, Bias::Right, &());
-        if let Some(excerpt) = cursor.item() {
-            let (start_offset_utf16, start_offset) = cursor.start();
-            let overshoot = offset_utf16 - start_offset_utf16;
-            let excerpt_start_offset = excerpt.range.context.start.to_offset(&excerpt.buffer);
-            let excerpt_start_offset_utf16 =
-                excerpt.buffer.offset_to_offset_utf16(excerpt_start_offset);
-            let buffer_offset = excerpt
-                .buffer
-                .offset_utf16_to_offset(excerpt_start_offset_utf16 + overshoot);
-            *start_offset + (buffer_offset - excerpt_start_offset)
-        } else {
-            self.excerpts.summary().text.len
-        }
-    }
-
-    pub fn offset_to_offset_utf16(&self, offset: usize) -> OffsetUtf16 {
-        if let Some((_, _, buffer)) = self.as_singleton() {
-            return buffer.offset_to_offset_utf16(offset);
-        }
-
-        let mut cursor = self.excerpts.cursor::<(usize, OffsetUtf16)>();
-        cursor.seek(&offset, Bias::Right, &());
-        if let Some(excerpt) = cursor.item() {
-            let (start_offset, start_offset_utf16) = cursor.start();
-            let overshoot = offset - start_offset;
-            let excerpt_start_offset_utf16 =
-                excerpt.range.context.start.to_offset_utf16(&excerpt.buffer);
-            let excerpt_start_offset = excerpt
-                .buffer
-                .offset_utf16_to_offset(excerpt_start_offset_utf16);
-            let buffer_offset_utf16 = excerpt
-                .buffer
-                .offset_to_offset_utf16(excerpt_start_offset + overshoot);
-            *start_offset_utf16 + (buffer_offset_utf16 - excerpt_start_offset_utf16)
-        } else {
-            self.excerpts.summary().text.len_utf16
-        }
-    }
-
-    pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize {
-        if let Some((_, _, buffer)) = self.as_singleton() {
-            return buffer.point_utf16_to_offset(point);
-        }
-
-        let mut cursor = self.excerpts.cursor::<(PointUtf16, usize)>();
-        cursor.seek(&point, Bias::Right, &());
-        if let Some(excerpt) = cursor.item() {
-            let (start_point, start_offset) = cursor.start();
-            let overshoot = point - start_point;
-            let excerpt_start_offset = excerpt.range.context.start.to_offset(&excerpt.buffer);
-            let excerpt_start_point = excerpt
-                .buffer
-                .offset_to_point_utf16(excerpt.range.context.start.to_offset(&excerpt.buffer));
-            let buffer_offset = excerpt
-                .buffer
-                .point_utf16_to_offset(excerpt_start_point + overshoot);
-            *start_offset + (buffer_offset - excerpt_start_offset)
-        } else {
-            self.excerpts.summary().text.len
-        }
-    }
-
-    pub fn point_to_buffer_offset<T: ToOffset>(
-        &self,
-        point: T,
-    ) -> Option<(&BufferSnapshot, usize)> {
-        let offset = point.to_offset(&self);
-        let mut cursor = self.excerpts.cursor::<usize>();
-        cursor.seek(&offset, Bias::Right, &());
-        if cursor.item().is_none() {
-            cursor.prev(&());
-        }
-
-        cursor.item().map(|excerpt| {
-            let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
-            let buffer_point = excerpt_start + offset - *cursor.start();
-            (&excerpt.buffer, buffer_point)
-        })
-    }
-
-    pub fn suggested_indents(
-        &self,
-        rows: impl IntoIterator<Item = u32>,
-        cx: &AppContext,
-    ) -> BTreeMap<u32, IndentSize> {
-        let mut result = BTreeMap::new();
-
-        let mut rows_for_excerpt = Vec::new();
-        let mut cursor = self.excerpts.cursor::<Point>();
-        let mut rows = rows.into_iter().peekable();
-        let mut prev_row = u32::MAX;
-        let mut prev_language_indent_size = IndentSize::default();
-
-        while let Some(row) = rows.next() {
-            cursor.seek(&Point::new(row, 0), Bias::Right, &());
-            let excerpt = match cursor.item() {
-                Some(excerpt) => excerpt,
-                _ => continue,
-            };
-
-            // Retrieve the language and indent size once for each disjoint region being indented.
-            let single_indent_size = if row.saturating_sub(1) == prev_row {
-                prev_language_indent_size
-            } else {
-                excerpt
-                    .buffer
-                    .language_indent_size_at(Point::new(row, 0), cx)
-            };
-            prev_language_indent_size = single_indent_size;
-            prev_row = row;
-
-            let start_buffer_row = excerpt.range.context.start.to_point(&excerpt.buffer).row;
-            let start_multibuffer_row = cursor.start().row;
-
-            rows_for_excerpt.push(row);
-            while let Some(next_row) = rows.peek().copied() {
-                if cursor.end(&()).row > next_row {
-                    rows_for_excerpt.push(next_row);
-                    rows.next();
-                } else {
-                    break;
-                }
-            }
-
-            let buffer_rows = rows_for_excerpt
-                .drain(..)
-                .map(|row| start_buffer_row + row - start_multibuffer_row);
-            let buffer_indents = excerpt
-                .buffer
-                .suggested_indents(buffer_rows, single_indent_size);
-            let multibuffer_indents = buffer_indents
-                .into_iter()
-                .map(|(row, indent)| (start_multibuffer_row + row - start_buffer_row, indent));
-            result.extend(multibuffer_indents);
-        }
-
-        result
-    }
-
-    pub fn indent_size_for_line(&self, row: u32) -> IndentSize {
-        if let Some((buffer, range)) = self.buffer_line_for_row(row) {
-            let mut size = buffer.indent_size_for_line(range.start.row);
-            size.len = size
-                .len
-                .min(range.end.column)
-                .saturating_sub(range.start.column);
-            size
-        } else {
-            IndentSize::spaces(0)
-        }
-    }
-
-    pub fn prev_non_blank_row(&self, mut row: u32) -> Option<u32> {
-        while row > 0 {
-            row -= 1;
-            if !self.is_line_blank(row) {
-                return Some(row);
-            }
-        }
-        None
-    }
-
-    pub fn line_len(&self, row: u32) -> u32 {
-        if let Some((_, range)) = self.buffer_line_for_row(row) {
-            range.end.column - range.start.column
-        } else {
-            0
-        }
-    }
-
-    pub fn buffer_line_for_row(&self, row: u32) -> Option<(&BufferSnapshot, Range<Point>)> {
-        let mut cursor = self.excerpts.cursor::<Point>();
-        let point = Point::new(row, 0);
-        cursor.seek(&point, Bias::Right, &());
-        if cursor.item().is_none() && *cursor.start() == point {
-            cursor.prev(&());
-        }
-        if let Some(excerpt) = cursor.item() {
-            let overshoot = row - cursor.start().row;
-            let excerpt_start = excerpt.range.context.start.to_point(&excerpt.buffer);
-            let excerpt_end = excerpt.range.context.end.to_point(&excerpt.buffer);
-            let buffer_row = excerpt_start.row + overshoot;
-            let line_start = Point::new(buffer_row, 0);
-            let line_end = Point::new(buffer_row, excerpt.buffer.line_len(buffer_row));
-            return Some((
-                &excerpt.buffer,
-                line_start.max(excerpt_start)..line_end.min(excerpt_end),
-            ));
-        }
-        None
-    }
-
-    pub fn max_point(&self) -> Point {
-        self.text_summary().lines
-    }
-
-    pub fn text_summary(&self) -> TextSummary {
-        self.excerpts.summary().text.clone()
-    }
-
-    pub fn text_summary_for_range<D, O>(&self, range: Range<O>) -> D
-    where
-        D: TextDimension,
-        O: ToOffset,
-    {
-        let mut summary = D::default();
-        let mut range = range.start.to_offset(self)..range.end.to_offset(self);
-        let mut cursor = self.excerpts.cursor::<usize>();
-        cursor.seek(&range.start, Bias::Right, &());
-        if let Some(excerpt) = cursor.item() {
-            let mut end_before_newline = cursor.end(&());
-            if excerpt.has_trailing_newline {
-                end_before_newline -= 1;
-            }
-
-            let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
-            let start_in_excerpt = excerpt_start + (range.start - cursor.start());
-            let end_in_excerpt =
-                excerpt_start + (cmp::min(end_before_newline, range.end) - cursor.start());
-            summary.add_assign(
-                &excerpt
-                    .buffer
-                    .text_summary_for_range(start_in_excerpt..end_in_excerpt),
-            );
-
-            if range.end > end_before_newline {
-                summary.add_assign(&D::from_text_summary(&TextSummary::from("\n")));
-            }
-
-            cursor.next(&());
-        }
-
-        if range.end > *cursor.start() {
-            summary.add_assign(&D::from_text_summary(&cursor.summary::<_, TextSummary>(
-                &range.end,
-                Bias::Right,
-                &(),
-            )));
-            if let Some(excerpt) = cursor.item() {
-                range.end = cmp::max(*cursor.start(), range.end);
-
-                let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
-                let end_in_excerpt = excerpt_start + (range.end - cursor.start());
-                summary.add_assign(
-                    &excerpt
-                        .buffer
-                        .text_summary_for_range(excerpt_start..end_in_excerpt),
-                );
-            }
-        }
-
-        summary
-    }
-
-    pub fn summary_for_anchor<D>(&self, anchor: &Anchor) -> D
-    where
-        D: TextDimension + Ord + Sub<D, Output = D>,
-    {
-        let mut cursor = self.excerpts.cursor::<ExcerptSummary>();
-        let locator = self.excerpt_locator_for_id(anchor.excerpt_id);
-
-        cursor.seek(locator, Bias::Left, &());
-        if cursor.item().is_none() {
-            cursor.next(&());
-        }
-
-        let mut position = D::from_text_summary(&cursor.start().text);
-        if let Some(excerpt) = cursor.item() {
-            if excerpt.id == anchor.excerpt_id {
-                let excerpt_buffer_start =
-                    excerpt.range.context.start.summary::<D>(&excerpt.buffer);
-                let excerpt_buffer_end = excerpt.range.context.end.summary::<D>(&excerpt.buffer);
-                let buffer_position = cmp::min(
-                    excerpt_buffer_end,
-                    anchor.text_anchor.summary::<D>(&excerpt.buffer),
-                );
-                if buffer_position > excerpt_buffer_start {
-                    position.add_assign(&(buffer_position - excerpt_buffer_start));
-                }
-            }
-        }
-        position
-    }
-
-    pub fn summaries_for_anchors<'a, D, I>(&'a self, anchors: I) -> Vec<D>
-    where
-        D: TextDimension + Ord + Sub<D, Output = D>,
-        I: 'a + IntoIterator<Item = &'a Anchor>,
-    {
-        if let Some((_, _, buffer)) = self.as_singleton() {
-            return buffer
-                .summaries_for_anchors(anchors.into_iter().map(|a| &a.text_anchor))
-                .collect();
-        }
-
-        let mut anchors = anchors.into_iter().peekable();
-        let mut cursor = self.excerpts.cursor::<ExcerptSummary>();
-        let mut summaries = Vec::new();
-        while let Some(anchor) = anchors.peek() {
-            let excerpt_id = anchor.excerpt_id;
-            let excerpt_anchors = iter::from_fn(|| {
-                let anchor = anchors.peek()?;
-                if anchor.excerpt_id == excerpt_id {
-                    Some(&anchors.next().unwrap().text_anchor)
-                } else {
-                    None
-                }
-            });
-
-            let locator = self.excerpt_locator_for_id(excerpt_id);
-            cursor.seek_forward(locator, Bias::Left, &());
-            if cursor.item().is_none() {
-                cursor.next(&());
-            }
-
-            let position = D::from_text_summary(&cursor.start().text);
-            if let Some(excerpt) = cursor.item() {
-                if excerpt.id == excerpt_id {
-                    let excerpt_buffer_start =
-                        excerpt.range.context.start.summary::<D>(&excerpt.buffer);
-                    let excerpt_buffer_end =
-                        excerpt.range.context.end.summary::<D>(&excerpt.buffer);
-                    summaries.extend(
-                        excerpt
-                            .buffer
-                            .summaries_for_anchors::<D, _>(excerpt_anchors)
-                            .map(move |summary| {
-                                let summary = cmp::min(excerpt_buffer_end.clone(), summary);
-                                let mut position = position.clone();
-                                let excerpt_buffer_start = excerpt_buffer_start.clone();
-                                if summary > excerpt_buffer_start {
-                                    position.add_assign(&(summary - excerpt_buffer_start));
-                                }
-                                position
-                            }),
-                    );
-                    continue;
-                }
-            }
-
-            summaries.extend(excerpt_anchors.map(|_| position.clone()));
-        }
-
-        summaries
-    }
-
-    pub fn refresh_anchors<'a, I>(&'a self, anchors: I) -> Vec<(usize, Anchor, bool)>
-    where
-        I: 'a + IntoIterator<Item = &'a Anchor>,
-    {
-        let mut anchors = anchors.into_iter().enumerate().peekable();
-        let mut cursor = self.excerpts.cursor::<Option<&Locator>>();
-        cursor.next(&());
-
-        let mut result = Vec::new();
-
-        while let Some((_, anchor)) = anchors.peek() {
-            let old_excerpt_id = anchor.excerpt_id;
-
-            // Find the location where this anchor's excerpt should be.
-            let old_locator = self.excerpt_locator_for_id(old_excerpt_id);
-            cursor.seek_forward(&Some(old_locator), Bias::Left, &());
-
-            if cursor.item().is_none() {
-                cursor.next(&());
-            }
-
-            let next_excerpt = cursor.item();
-            let prev_excerpt = cursor.prev_item();
-
-            // Process all of the anchors for this excerpt.
-            while let Some((_, anchor)) = anchors.peek() {
-                if anchor.excerpt_id != old_excerpt_id {
-                    break;
-                }
-                let (anchor_ix, anchor) = anchors.next().unwrap();
-                let mut anchor = *anchor;
-
-                // Leave min and max anchors unchanged if invalid or
-                // if the old excerpt still exists at this location
-                let mut kept_position = next_excerpt
-                    .map_or(false, |e| e.id == old_excerpt_id && e.contains(&anchor))
-                    || old_excerpt_id == ExcerptId::max()
-                    || old_excerpt_id == ExcerptId::min();
-
-                // If the old excerpt no longer exists at this location, then attempt to
-                // find an equivalent position for this anchor in an adjacent excerpt.
-                if !kept_position {
-                    for excerpt in [next_excerpt, prev_excerpt].iter().filter_map(|e| *e) {
-                        if excerpt.contains(&anchor) {
-                            anchor.excerpt_id = excerpt.id.clone();
-                            kept_position = true;
-                            break;
-                        }
-                    }
-                }
-
-                // If there's no adjacent excerpt that contains the anchor's position,
-                // then report that the anchor has lost its position.
-                if !kept_position {
-                    anchor = if let Some(excerpt) = next_excerpt {
-                        let mut text_anchor = excerpt
-                            .range
-                            .context
-                            .start
-                            .bias(anchor.text_anchor.bias, &excerpt.buffer);
-                        if text_anchor
-                            .cmp(&excerpt.range.context.end, &excerpt.buffer)
-                            .is_gt()
-                        {
-                            text_anchor = excerpt.range.context.end;
-                        }
-                        Anchor {
-                            buffer_id: Some(excerpt.buffer_id),
-                            excerpt_id: excerpt.id.clone(),
-                            text_anchor,
-                        }
-                    } else if let Some(excerpt) = prev_excerpt {
-                        let mut text_anchor = excerpt
-                            .range
-                            .context
-                            .end
-                            .bias(anchor.text_anchor.bias, &excerpt.buffer);
-                        if text_anchor
-                            .cmp(&excerpt.range.context.start, &excerpt.buffer)
-                            .is_lt()
-                        {
-                            text_anchor = excerpt.range.context.start;
-                        }
-                        Anchor {
-                            buffer_id: Some(excerpt.buffer_id),
-                            excerpt_id: excerpt.id.clone(),
-                            text_anchor,
-                        }
-                    } else if anchor.text_anchor.bias == Bias::Left {
-                        Anchor::min()
-                    } else {
-                        Anchor::max()
-                    };
-                }
-
-                result.push((anchor_ix, anchor, kept_position));
-            }
-        }
-        result.sort_unstable_by(|a, b| a.1.cmp(&b.1, self));
-        result
-    }
-
-    pub fn anchor_before<T: ToOffset>(&self, position: T) -> Anchor {
-        self.anchor_at(position, Bias::Left)
-    }
-
-    pub fn anchor_after<T: ToOffset>(&self, position: T) -> Anchor {
-        self.anchor_at(position, Bias::Right)
-    }
-
-    pub fn anchor_at<T: ToOffset>(&self, position: T, mut bias: Bias) -> Anchor {
-        let offset = position.to_offset(self);
-        if let Some((excerpt_id, buffer_id, buffer)) = self.as_singleton() {
-            return Anchor {
-                buffer_id: Some(buffer_id),
-                excerpt_id: excerpt_id.clone(),
-                text_anchor: buffer.anchor_at(offset, bias),
-            };
-        }
-
-        let mut cursor = self.excerpts.cursor::<(usize, Option<ExcerptId>)>();
-        cursor.seek(&offset, Bias::Right, &());
-        if cursor.item().is_none() && offset == cursor.start().0 && bias == Bias::Left {
-            cursor.prev(&());
-        }
-        if let Some(excerpt) = cursor.item() {
-            let mut overshoot = offset.saturating_sub(cursor.start().0);
-            if excerpt.has_trailing_newline && offset == cursor.end(&()).0 {
-                overshoot -= 1;
-                bias = Bias::Right;
-            }
-
-            let buffer_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
-            let text_anchor =
-                excerpt.clip_anchor(excerpt.buffer.anchor_at(buffer_start + overshoot, bias));
-            Anchor {
-                buffer_id: Some(excerpt.buffer_id),
-                excerpt_id: excerpt.id.clone(),
-                text_anchor,
-            }
-        } else if offset == 0 && bias == Bias::Left {
-            Anchor::min()
-        } else {
-            Anchor::max()
-        }
-    }
-
-    pub fn anchor_in_excerpt(&self, excerpt_id: ExcerptId, text_anchor: text::Anchor) -> Anchor {
-        let locator = self.excerpt_locator_for_id(excerpt_id);
-        let mut cursor = self.excerpts.cursor::<Option<&Locator>>();
-        cursor.seek(locator, Bias::Left, &());
-        if let Some(excerpt) = cursor.item() {
-            if excerpt.id == excerpt_id {
-                let text_anchor = excerpt.clip_anchor(text_anchor);
-                drop(cursor);
-                return Anchor {
-                    buffer_id: Some(excerpt.buffer_id),
-                    excerpt_id,
-                    text_anchor,
-                };
-            }
-        }
-        panic!("excerpt not found");
-    }
-
-    pub fn can_resolve(&self, anchor: &Anchor) -> bool {
-        if anchor.excerpt_id == ExcerptId::min() || anchor.excerpt_id == ExcerptId::max() {
-            true
-        } else if let Some(excerpt) = self.excerpt(anchor.excerpt_id) {
-            excerpt.buffer.can_resolve(&anchor.text_anchor)
-        } else {
-            false
-        }
-    }
-
-    pub fn excerpts(
-        &self,
-    ) -> impl Iterator<Item = (ExcerptId, &BufferSnapshot, ExcerptRange<text::Anchor>)> {
-        self.excerpts
-            .iter()
-            .map(|excerpt| (excerpt.id, &excerpt.buffer, excerpt.range.clone()))
-    }
-
-    pub fn excerpt_boundaries_in_range<R, T>(
-        &self,
-        range: R,
-    ) -> impl Iterator<Item = ExcerptBoundary> + '_
-    where
-        R: RangeBounds<T>,
-        T: ToOffset,
-    {
-        let start_offset;
-        let start = match range.start_bound() {
-            Bound::Included(start) => {
-                start_offset = start.to_offset(self);
-                Bound::Included(start_offset)
-            }
-            Bound::Excluded(start) => {
-                start_offset = start.to_offset(self);
-                Bound::Excluded(start_offset)
-            }
-            Bound::Unbounded => {
-                start_offset = 0;
-                Bound::Unbounded
-            }
-        };
-        let end = match range.end_bound() {
-            Bound::Included(end) => Bound::Included(end.to_offset(self)),
-            Bound::Excluded(end) => Bound::Excluded(end.to_offset(self)),
-            Bound::Unbounded => Bound::Unbounded,
-        };
-        let bounds = (start, end);
-
-        let mut cursor = self.excerpts.cursor::<(usize, Point)>();
-        cursor.seek(&start_offset, Bias::Right, &());
-        if cursor.item().is_none() {
-            cursor.prev(&());
-        }
-        if !bounds.contains(&cursor.start().0) {
-            cursor.next(&());
-        }
-
-        let mut prev_buffer_id = cursor.prev_item().map(|excerpt| excerpt.buffer_id);
-        std::iter::from_fn(move || {
-            if self.singleton {
-                None
-            } else if bounds.contains(&cursor.start().0) {
-                let excerpt = cursor.item()?;
-                let starts_new_buffer = Some(excerpt.buffer_id) != prev_buffer_id;
-                let boundary = ExcerptBoundary {
-                    id: excerpt.id.clone(),
-                    row: cursor.start().1.row,
-                    buffer: excerpt.buffer.clone(),
-                    range: excerpt.range.clone(),
-                    starts_new_buffer,
-                };
-
-                prev_buffer_id = Some(excerpt.buffer_id);
-                cursor.next(&());
-                Some(boundary)
-            } else {
-                None
-            }
-        })
-    }
-
-    pub fn edit_count(&self) -> usize {
-        self.edit_count
-    }
-
-    pub fn parse_count(&self) -> usize {
-        self.parse_count
-    }
-
-    /// Returns the smallest enclosing bracket ranges containing the given range or
-    /// None if no brackets contain range or the range is not contained in a single
-    /// excerpt
-    pub fn innermost_enclosing_bracket_ranges<T: ToOffset>(
-        &self,
-        range: Range<T>,
-    ) -> Option<(Range<usize>, Range<usize>)> {
-        let range = range.start.to_offset(self)..range.end.to_offset(self);
-
-        // Get the ranges of the innermost pair of brackets.
-        let mut result: Option<(Range<usize>, Range<usize>)> = None;
-
-        let Some(enclosing_bracket_ranges) = self.enclosing_bracket_ranges(range.clone()) else {
-            return None;
-        };
-
-        for (open, close) in enclosing_bracket_ranges {
-            let len = close.end - open.start;
-
-            if let Some((existing_open, existing_close)) = &result {
-                let existing_len = existing_close.end - existing_open.start;
-                if len > existing_len {
-                    continue;
-                }
-            }
-
-            result = Some((open, close));
-        }
-
-        result
-    }
-
-    /// Returns enclosing bracket ranges containing the given range or returns None if the range is
-    /// not contained in a single excerpt
-    pub fn enclosing_bracket_ranges<'a, T: ToOffset>(
-        &'a self,
-        range: Range<T>,
-    ) -> Option<impl Iterator<Item = (Range<usize>, Range<usize>)> + 'a> {
-        let range = range.start.to_offset(self)..range.end.to_offset(self);
-
-        self.bracket_ranges(range.clone()).map(|range_pairs| {
-            range_pairs
-                .filter(move |(open, close)| open.start <= range.start && close.end >= range.end)
-        })
-    }
-
-    /// Returns bracket range pairs overlapping the given `range` or returns None if the `range` is
-    /// not contained in a single excerpt
-    pub fn bracket_ranges<'a, T: ToOffset>(
-        &'a self,
-        range: Range<T>,
-    ) -> Option<impl Iterator<Item = (Range<usize>, Range<usize>)> + 'a> {
-        let range = range.start.to_offset(self)..range.end.to_offset(self);
-        let excerpt = self.excerpt_containing(range.clone());
-        excerpt.map(|(excerpt, excerpt_offset)| {
-            let excerpt_buffer_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
-            let excerpt_buffer_end = excerpt_buffer_start + excerpt.text_summary.len;
-
-            let start_in_buffer = excerpt_buffer_start + range.start.saturating_sub(excerpt_offset);
-            let end_in_buffer = excerpt_buffer_start + range.end.saturating_sub(excerpt_offset);
-
-            excerpt
-                .buffer
-                .bracket_ranges(start_in_buffer..end_in_buffer)
-                .filter_map(move |(start_bracket_range, end_bracket_range)| {
-                    if start_bracket_range.start < excerpt_buffer_start
-                        || end_bracket_range.end > excerpt_buffer_end
-                    {
-                        return None;
-                    }
-
-                    let mut start_bracket_range = start_bracket_range.clone();
-                    start_bracket_range.start =
-                        excerpt_offset + (start_bracket_range.start - excerpt_buffer_start);
-                    start_bracket_range.end =
-                        excerpt_offset + (start_bracket_range.end - excerpt_buffer_start);
-
-                    let mut end_bracket_range = end_bracket_range.clone();
-                    end_bracket_range.start =
-                        excerpt_offset + (end_bracket_range.start - excerpt_buffer_start);
-                    end_bracket_range.end =
-                        excerpt_offset + (end_bracket_range.end - excerpt_buffer_start);
-                    Some((start_bracket_range, end_bracket_range))
-                })
-        })
-    }
-
-    pub fn diagnostics_update_count(&self) -> usize {
-        self.diagnostics_update_count
-    }
-
-    pub fn git_diff_update_count(&self) -> usize {
-        self.git_diff_update_count
-    }
-
-    pub fn trailing_excerpt_update_count(&self) -> usize {
-        self.trailing_excerpt_update_count
-    }
-
-    pub fn file_at<'a, T: ToOffset>(&'a self, point: T) -> Option<&'a Arc<dyn File>> {
-        self.point_to_buffer_offset(point)
-            .and_then(|(buffer, _)| buffer.file())
-    }
-
-    pub fn language_at<'a, T: ToOffset>(&'a self, point: T) -> Option<&'a Arc<Language>> {
-        self.point_to_buffer_offset(point)
-            .and_then(|(buffer, offset)| buffer.language_at(offset))
-    }
-
-    pub fn settings_at<'a, T: ToOffset>(
-        &'a self,
-        point: T,
-        cx: &'a AppContext,
-    ) -> &'a LanguageSettings {
-        let mut language = None;
-        let mut file = None;
-        if let Some((buffer, offset)) = self.point_to_buffer_offset(point) {
-            language = buffer.language_at(offset);
-            file = buffer.file();
-        }
-        language_settings(language, file, cx)
-    }
-
-    pub fn language_scope_at<'a, T: ToOffset>(&'a self, point: T) -> Option<LanguageScope> {
-        self.point_to_buffer_offset(point)
-            .and_then(|(buffer, offset)| buffer.language_scope_at(offset))
-    }
-
-    pub fn language_indent_size_at<T: ToOffset>(
-        &self,
-        position: T,
-        cx: &AppContext,
-    ) -> Option<IndentSize> {
-        let (buffer_snapshot, offset) = self.point_to_buffer_offset(position)?;
-        Some(buffer_snapshot.language_indent_size_at(offset, cx))
-    }
-
-    pub fn is_dirty(&self) -> bool {
-        self.is_dirty
-    }
-
-    pub fn has_conflict(&self) -> bool {
-        self.has_conflict
-    }
-
-    pub fn diagnostic_group<'a, O>(
-        &'a self,
-        group_id: usize,
-    ) -> impl Iterator<Item = DiagnosticEntry<O>> + 'a
-    where
-        O: text::FromAnchor + 'a,
-    {
-        self.as_singleton()
-            .into_iter()
-            .flat_map(move |(_, _, buffer)| buffer.diagnostic_group(group_id))
-    }
-
-    pub fn diagnostics_in_range<'a, T, O>(
-        &'a self,
-        range: Range<T>,
-        reversed: bool,
-    ) -> impl Iterator<Item = DiagnosticEntry<O>> + 'a
-    where
-        T: 'a + ToOffset,
-        O: 'a + text::FromAnchor + Ord,
-    {
-        self.as_singleton()
-            .into_iter()
-            .flat_map(move |(_, _, buffer)| {
-                buffer.diagnostics_in_range(
-                    range.start.to_offset(self)..range.end.to_offset(self),
-                    reversed,
-                )
-            })
-    }
-
-    pub fn has_git_diffs(&self) -> bool {
-        for excerpt in self.excerpts.iter() {
-            if !excerpt.buffer.git_diff.is_empty() {
-                return true;
-            }
-        }
-        false
-    }
-
-    pub fn git_diff_hunks_in_range_rev<'a>(
-        &'a self,
-        row_range: Range<u32>,
-    ) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
-        let mut cursor = self.excerpts.cursor::<Point>();
-
-        cursor.seek(&Point::new(row_range.end, 0), Bias::Left, &());
-        if cursor.item().is_none() {
-            cursor.prev(&());
-        }
-
-        std::iter::from_fn(move || {
-            let excerpt = cursor.item()?;
-            let multibuffer_start = *cursor.start();
-            let multibuffer_end = multibuffer_start + excerpt.text_summary.lines;
-            if multibuffer_start.row >= row_range.end {
-                return None;
-            }
-
-            let mut buffer_start = excerpt.range.context.start;
-            let mut buffer_end = excerpt.range.context.end;
-            let excerpt_start_point = buffer_start.to_point(&excerpt.buffer);
-            let excerpt_end_point = excerpt_start_point + excerpt.text_summary.lines;
-
-            if row_range.start > multibuffer_start.row {
-                let buffer_start_point =
-                    excerpt_start_point + Point::new(row_range.start - multibuffer_start.row, 0);
-                buffer_start = excerpt.buffer.anchor_before(buffer_start_point);
-            }
-
-            if row_range.end < multibuffer_end.row {
-                let buffer_end_point =
-                    excerpt_start_point + Point::new(row_range.end - multibuffer_start.row, 0);
-                buffer_end = excerpt.buffer.anchor_before(buffer_end_point);
-            }
-
-            let buffer_hunks = excerpt
-                .buffer
-                .git_diff_hunks_intersecting_range_rev(buffer_start..buffer_end)
-                .filter_map(move |hunk| {
-                    let start = multibuffer_start.row
-                        + hunk
-                            .buffer_range
-                            .start
-                            .saturating_sub(excerpt_start_point.row);
-                    let end = multibuffer_start.row
-                        + hunk
-                            .buffer_range
-                            .end
-                            .min(excerpt_end_point.row + 1)
-                            .saturating_sub(excerpt_start_point.row);
-
-                    Some(DiffHunk {
-                        buffer_range: start..end,
-                        diff_base_byte_range: hunk.diff_base_byte_range.clone(),
-                    })
-                });
-
-            cursor.prev(&());
-
-            Some(buffer_hunks)
-        })
-        .flatten()
-    }
-
-    pub fn git_diff_hunks_in_range<'a>(
-        &'a self,
-        row_range: Range<u32>,
-    ) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
-        let mut cursor = self.excerpts.cursor::<Point>();
-
-        cursor.seek(&Point::new(row_range.start, 0), Bias::Right, &());
-
-        std::iter::from_fn(move || {
-            let excerpt = cursor.item()?;
-            let multibuffer_start = *cursor.start();
-            let multibuffer_end = multibuffer_start + excerpt.text_summary.lines;
-            if multibuffer_start.row >= row_range.end {
-                return None;
-            }
-
-            let mut buffer_start = excerpt.range.context.start;
-            let mut buffer_end = excerpt.range.context.end;
-            let excerpt_start_point = buffer_start.to_point(&excerpt.buffer);
-            let excerpt_end_point = excerpt_start_point + excerpt.text_summary.lines;
-
-            if row_range.start > multibuffer_start.row {
-                let buffer_start_point =
-                    excerpt_start_point + Point::new(row_range.start - multibuffer_start.row, 0);
-                buffer_start = excerpt.buffer.anchor_before(buffer_start_point);
-            }
-
-            if row_range.end < multibuffer_end.row {
-                let buffer_end_point =
-                    excerpt_start_point + Point::new(row_range.end - multibuffer_start.row, 0);
-                buffer_end = excerpt.buffer.anchor_before(buffer_end_point);
-            }
-
-            let buffer_hunks = excerpt
-                .buffer
-                .git_diff_hunks_intersecting_range(buffer_start..buffer_end)
-                .filter_map(move |hunk| {
-                    let start = multibuffer_start.row
-                        + hunk
-                            .buffer_range
-                            .start
-                            .saturating_sub(excerpt_start_point.row);
-                    let end = multibuffer_start.row
-                        + hunk
-                            .buffer_range
-                            .end
-                            .min(excerpt_end_point.row + 1)
-                            .saturating_sub(excerpt_start_point.row);
-
-                    Some(DiffHunk {
-                        buffer_range: start..end,
-                        diff_base_byte_range: hunk.diff_base_byte_range.clone(),
-                    })
-                });
-
-            cursor.next(&());
-
-            Some(buffer_hunks)
-        })
-        .flatten()
-    }
-
-    pub fn range_for_syntax_ancestor<T: ToOffset>(&self, range: Range<T>) -> Option<Range<usize>> {
-        let range = range.start.to_offset(self)..range.end.to_offset(self);
-
-        self.excerpt_containing(range.clone())
-            .and_then(|(excerpt, excerpt_offset)| {
-                let excerpt_buffer_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
-                let excerpt_buffer_end = excerpt_buffer_start + excerpt.text_summary.len;
-
-                let start_in_buffer =
-                    excerpt_buffer_start + range.start.saturating_sub(excerpt_offset);
-                let end_in_buffer = excerpt_buffer_start + range.end.saturating_sub(excerpt_offset);
-                let mut ancestor_buffer_range = excerpt
-                    .buffer
-                    .range_for_syntax_ancestor(start_in_buffer..end_in_buffer)?;
-                ancestor_buffer_range.start =
-                    cmp::max(ancestor_buffer_range.start, excerpt_buffer_start);
-                ancestor_buffer_range.end = cmp::min(ancestor_buffer_range.end, excerpt_buffer_end);
-
-                let start = excerpt_offset + (ancestor_buffer_range.start - excerpt_buffer_start);
-                let end = excerpt_offset + (ancestor_buffer_range.end - excerpt_buffer_start);
-                Some(start..end)
-            })
-    }
-
-    pub fn outline(&self, theme: Option<&SyntaxTheme>) -> Option<Outline<Anchor>> {
-        let (excerpt_id, _, buffer) = self.as_singleton()?;
-        let outline = buffer.outline(theme)?;
-        Some(Outline::new(
-            outline
-                .items
-                .into_iter()
-                .map(|item| OutlineItem {
-                    depth: item.depth,
-                    range: self.anchor_in_excerpt(excerpt_id.clone(), item.range.start)
-                        ..self.anchor_in_excerpt(excerpt_id.clone(), item.range.end),
-                    text: item.text,
-                    highlight_ranges: item.highlight_ranges,
-                    name_ranges: item.name_ranges,
-                })
-                .collect(),
-        ))
-    }
-
-    pub fn symbols_containing<T: ToOffset>(
-        &self,
-        offset: T,
-        theme: Option<&SyntaxTheme>,
-    ) -> Option<(u64, Vec<OutlineItem<Anchor>>)> {
-        let anchor = self.anchor_before(offset);
-        let excerpt_id = anchor.excerpt_id;
-        let excerpt = self.excerpt(excerpt_id)?;
-        Some((
-            excerpt.buffer_id,
-            excerpt
-                .buffer
-                .symbols_containing(anchor.text_anchor, theme)
-                .into_iter()
-                .flatten()
-                .map(|item| OutlineItem {
-                    depth: item.depth,
-                    range: self.anchor_in_excerpt(excerpt_id, item.range.start)
-                        ..self.anchor_in_excerpt(excerpt_id, item.range.end),
-                    text: item.text,
-                    highlight_ranges: item.highlight_ranges,
-                    name_ranges: item.name_ranges,
-                })
-                .collect(),
-        ))
-    }
-
-    fn excerpt_locator_for_id<'a>(&'a self, id: ExcerptId) -> &'a Locator {
-        if id == ExcerptId::min() {
-            Locator::min_ref()
-        } else if id == ExcerptId::max() {
-            Locator::max_ref()
-        } else {
-            let mut cursor = self.excerpt_ids.cursor::<ExcerptId>();
-            cursor.seek(&id, Bias::Left, &());
-            if let Some(entry) = cursor.item() {
-                if entry.id == id {
-                    return &entry.locator;
-                }
-            }
-            panic!("invalid excerpt id {:?}", id)
-        }
-    }
-
-    pub fn buffer_id_for_excerpt(&self, excerpt_id: ExcerptId) -> Option<u64> {
-        Some(self.excerpt(excerpt_id)?.buffer_id)
-    }
-
-    pub fn buffer_for_excerpt(&self, excerpt_id: ExcerptId) -> Option<&BufferSnapshot> {
-        Some(&self.excerpt(excerpt_id)?.buffer)
-    }
-
-    fn excerpt<'a>(&'a self, excerpt_id: ExcerptId) -> Option<&'a Excerpt> {
-        let mut cursor = self.excerpts.cursor::<Option<&Locator>>();
-        let locator = self.excerpt_locator_for_id(excerpt_id);
-        cursor.seek(&Some(locator), Bias::Left, &());
-        if let Some(excerpt) = cursor.item() {
-            if excerpt.id == excerpt_id {
-                return Some(excerpt);
-            }
-        }
-        None
-    }
-
-    /// Returns the excerpt containing range and its offset start within the multibuffer or none if `range` spans multiple excerpts
-    fn excerpt_containing<'a, T: ToOffset>(
-        &'a self,
-        range: Range<T>,
-    ) -> Option<(&'a Excerpt, usize)> {
-        let range = range.start.to_offset(self)..range.end.to_offset(self);
-
-        let mut cursor = self.excerpts.cursor::<usize>();
-        cursor.seek(&range.start, Bias::Right, &());
-        let start_excerpt = cursor.item();
-
-        if range.start == range.end {
-            return start_excerpt.map(|excerpt| (excerpt, *cursor.start()));
-        }
-
-        cursor.seek(&range.end, Bias::Right, &());
-        let end_excerpt = cursor.item();
-
-        start_excerpt
-            .zip(end_excerpt)
-            .and_then(|(start_excerpt, end_excerpt)| {
-                if start_excerpt.id != end_excerpt.id {
-                    return None;
-                }
-
-                Some((start_excerpt, *cursor.start()))
-            })
-    }
-
-    pub fn remote_selections_in_range<'a>(
-        &'a self,
-        range: &'a Range<Anchor>,
-    ) -> impl 'a + Iterator<Item = (ReplicaId, bool, CursorShape, Selection<Anchor>)> {
-        let mut cursor = self.excerpts.cursor::<ExcerptSummary>();
-        let start_locator = self.excerpt_locator_for_id(range.start.excerpt_id);
-        let end_locator = self.excerpt_locator_for_id(range.end.excerpt_id);
-        cursor.seek(start_locator, Bias::Left, &());
-        cursor
-            .take_while(move |excerpt| excerpt.locator <= *end_locator)
-            .flat_map(move |excerpt| {
-                let mut query_range = excerpt.range.context.start..excerpt.range.context.end;
-                if excerpt.id == range.start.excerpt_id {
-                    query_range.start = range.start.text_anchor;
-                }
-                if excerpt.id == range.end.excerpt_id {
-                    query_range.end = range.end.text_anchor;
-                }
-
-                excerpt
-                    .buffer
-                    .remote_selections_in_range(query_range)
-                    .flat_map(move |(replica_id, line_mode, cursor_shape, selections)| {
-                        selections.map(move |selection| {
-                            let mut start = Anchor {
-                                buffer_id: Some(excerpt.buffer_id),
-                                excerpt_id: excerpt.id.clone(),
-                                text_anchor: selection.start,
-                            };
-                            let mut end = Anchor {
-                                buffer_id: Some(excerpt.buffer_id),
-                                excerpt_id: excerpt.id.clone(),
-                                text_anchor: selection.end,
-                            };
-                            if range.start.cmp(&start, self).is_gt() {
-                                start = range.start.clone();
-                            }
-                            if range.end.cmp(&end, self).is_lt() {
-                                end = range.end.clone();
-                            }
-
-                            (
-                                replica_id,
-                                line_mode,
-                                cursor_shape,
-                                Selection {
-                                    id: selection.id,
-                                    start,
-                                    end,
-                                    reversed: selection.reversed,
-                                    goal: selection.goal,
-                                },
-                            )
-                        })
-                    })
-            })
-    }
-}
-
-#[cfg(any(test, feature = "test-support"))]
-impl MultiBufferSnapshot {
-    pub fn random_byte_range(&self, start_offset: usize, rng: &mut impl rand::Rng) -> Range<usize> {
-        let end = self.clip_offset(rng.gen_range(start_offset..=self.len()), Bias::Right);
-        let start = self.clip_offset(rng.gen_range(start_offset..=end), Bias::Right);
-        start..end
-    }
-}
-
-impl History {
-    fn start_transaction(&mut self, now: Instant) -> Option<TransactionId> {
-        self.transaction_depth += 1;
-        if self.transaction_depth == 1 {
-            let id = self.next_transaction_id.tick();
-            self.undo_stack.push(Transaction {
-                id,
-                buffer_transactions: Default::default(),
-                first_edit_at: now,
-                last_edit_at: now,
-                suppress_grouping: false,
-            });
-            Some(id)
-        } else {
-            None
-        }
-    }
-
-    fn end_transaction(
-        &mut self,
-        now: Instant,
-        buffer_transactions: HashMap<u64, TransactionId>,
-    ) -> bool {
-        assert_ne!(self.transaction_depth, 0);
-        self.transaction_depth -= 1;
-        if self.transaction_depth == 0 {
-            if buffer_transactions.is_empty() {
-                self.undo_stack.pop();
-                false
-            } else {
-                self.redo_stack.clear();
-                let transaction = self.undo_stack.last_mut().unwrap();
-                transaction.last_edit_at = now;
-                for (buffer_id, transaction_id) in buffer_transactions {
-                    transaction
-                        .buffer_transactions
-                        .entry(buffer_id)
-                        .or_insert(transaction_id);
-                }
-                true
-            }
-        } else {
-            false
-        }
-    }
-
-    fn push_transaction<'a, T>(
-        &mut self,
-        buffer_transactions: T,
-        now: Instant,
-        cx: &mut ModelContext<MultiBuffer>,
-    ) where
-        T: IntoIterator<Item = (&'a Model<Buffer>, &'a language::Transaction)>,
-    {
-        assert_eq!(self.transaction_depth, 0);
-        let transaction = Transaction {
-            id: self.next_transaction_id.tick(),
-            buffer_transactions: buffer_transactions
-                .into_iter()
-                .map(|(buffer, transaction)| (buffer.read(cx).remote_id(), transaction.id))
-                .collect(),
-            first_edit_at: now,
-            last_edit_at: now,
-            suppress_grouping: false,
-        };
-        if !transaction.buffer_transactions.is_empty() {
-            self.undo_stack.push(transaction);
-            self.redo_stack.clear();
-        }
-    }
-
-    fn finalize_last_transaction(&mut self) {
-        if let Some(transaction) = self.undo_stack.last_mut() {
-            transaction.suppress_grouping = true;
-        }
-    }
-
-    fn forget(&mut self, transaction_id: TransactionId) -> Option<Transaction> {
-        if let Some(ix) = self
-            .undo_stack
-            .iter()
-            .rposition(|transaction| transaction.id == transaction_id)
-        {
-            Some(self.undo_stack.remove(ix))
-        } else if let Some(ix) = self
-            .redo_stack
-            .iter()
-            .rposition(|transaction| transaction.id == transaction_id)
-        {
-            Some(self.redo_stack.remove(ix))
-        } else {
-            None
-        }
-    }
-
-    fn transaction_mut(&mut self, transaction_id: TransactionId) -> Option<&mut Transaction> {
-        self.undo_stack
-            .iter_mut()
-            .find(|transaction| transaction.id == transaction_id)
-            .or_else(|| {
-                self.redo_stack
-                    .iter_mut()
-                    .find(|transaction| transaction.id == transaction_id)
-            })
-    }
-
-    fn pop_undo(&mut self) -> Option<&mut Transaction> {
-        assert_eq!(self.transaction_depth, 0);
-        if let Some(transaction) = self.undo_stack.pop() {
-            self.redo_stack.push(transaction);
-            self.redo_stack.last_mut()
-        } else {
-            None
-        }
-    }
-
-    fn pop_redo(&mut self) -> Option<&mut Transaction> {
-        assert_eq!(self.transaction_depth, 0);
-        if let Some(transaction) = self.redo_stack.pop() {
-            self.undo_stack.push(transaction);
-            self.undo_stack.last_mut()
-        } else {
-            None
-        }
-    }
-
-    fn remove_from_undo(&mut self, transaction_id: TransactionId) -> Option<&Transaction> {
-        let ix = self
-            .undo_stack
-            .iter()
-            .rposition(|transaction| transaction.id == transaction_id)?;
-        let transaction = self.undo_stack.remove(ix);
-        self.redo_stack.push(transaction);
-        self.redo_stack.last()
-    }
-
-    fn group(&mut self) -> Option<TransactionId> {
-        let mut count = 0;
-        let mut transactions = self.undo_stack.iter();
-        if let Some(mut transaction) = transactions.next_back() {
-            while let Some(prev_transaction) = transactions.next_back() {
-                if !prev_transaction.suppress_grouping
-                    && transaction.first_edit_at - prev_transaction.last_edit_at
-                        <= self.group_interval
-                {
-                    transaction = prev_transaction;
-                    count += 1;
-                } else {
-                    break;
-                }
-            }
-        }
-        self.group_trailing(count)
-    }
-
-    fn group_until(&mut self, transaction_id: TransactionId) {
-        let mut count = 0;
-        for transaction in self.undo_stack.iter().rev() {
-            if transaction.id == transaction_id {
-                self.group_trailing(count);
-                break;
-            } else if transaction.suppress_grouping {
-                break;
-            } else {
-                count += 1;
-            }
-        }
-    }
-
-    fn group_trailing(&mut self, n: usize) -> Option<TransactionId> {
-        let new_len = self.undo_stack.len() - n;
-        let (transactions_to_keep, transactions_to_merge) = self.undo_stack.split_at_mut(new_len);
-        if let Some(last_transaction) = transactions_to_keep.last_mut() {
-            if let Some(transaction) = transactions_to_merge.last() {
-                last_transaction.last_edit_at = transaction.last_edit_at;
-            }
-            for to_merge in transactions_to_merge {
-                for (buffer_id, transaction_id) in &to_merge.buffer_transactions {
-                    last_transaction
-                        .buffer_transactions
-                        .entry(*buffer_id)
-                        .or_insert(*transaction_id);
-                }
-            }
-        }
-
-        self.undo_stack.truncate(new_len);
-        self.undo_stack.last().map(|t| t.id)
-    }
-}
-
-impl Excerpt {
-    fn new(
-        id: ExcerptId,
-        locator: Locator,
-        buffer_id: u64,
-        buffer: BufferSnapshot,
-        range: ExcerptRange<text::Anchor>,
-        has_trailing_newline: bool,
-    ) -> Self {
-        Excerpt {
-            id,
-            locator,
-            max_buffer_row: range.context.end.to_point(&buffer).row,
-            text_summary: buffer
-                .text_summary_for_range::<TextSummary, _>(range.context.to_offset(&buffer)),
-            buffer_id,
-            buffer,
-            range,
-            has_trailing_newline,
-        }
-    }
-
-    fn chunks_in_range(&self, range: Range<usize>, language_aware: bool) -> ExcerptChunks {
-        let content_start = self.range.context.start.to_offset(&self.buffer);
-        let chunks_start = content_start + range.start;
-        let chunks_end = content_start + cmp::min(range.end, self.text_summary.len);
-
-        let footer_height = if self.has_trailing_newline
-            && range.start <= self.text_summary.len
-            && range.end > self.text_summary.len
-        {
-            1
-        } else {
-            0
-        };
-
-        let content_chunks = self.buffer.chunks(chunks_start..chunks_end, language_aware);
-
-        ExcerptChunks {
-            content_chunks,
-            footer_height,
-        }
-    }
-
-    fn bytes_in_range(&self, range: Range<usize>) -> ExcerptBytes {
-        let content_start = self.range.context.start.to_offset(&self.buffer);
-        let bytes_start = content_start + range.start;
-        let bytes_end = content_start + cmp::min(range.end, self.text_summary.len);
-        let footer_height = if self.has_trailing_newline
-            && range.start <= self.text_summary.len
-            && range.end > self.text_summary.len
-        {
-            1
-        } else {
-            0
-        };
-        let content_bytes = self.buffer.bytes_in_range(bytes_start..bytes_end);
-
-        ExcerptBytes {
-            content_bytes,
-            footer_height,
-        }
-    }
-
-    fn reversed_bytes_in_range(&self, range: Range<usize>) -> ExcerptBytes {
-        let content_start = self.range.context.start.to_offset(&self.buffer);
-        let bytes_start = content_start + range.start;
-        let bytes_end = content_start + cmp::min(range.end, self.text_summary.len);
-        let footer_height = if self.has_trailing_newline
-            && range.start <= self.text_summary.len
-            && range.end > self.text_summary.len
-        {
-            1
-        } else {
-            0
-        };
-        let content_bytes = self.buffer.reversed_bytes_in_range(bytes_start..bytes_end);
-
-        ExcerptBytes {
-            content_bytes,
-            footer_height,
-        }
-    }
-
-    fn clip_anchor(&self, text_anchor: text::Anchor) -> text::Anchor {
-        if text_anchor
-            .cmp(&self.range.context.start, &self.buffer)
-            .is_lt()
-        {
-            self.range.context.start
-        } else if text_anchor
-            .cmp(&self.range.context.end, &self.buffer)
-            .is_gt()
-        {
-            self.range.context.end
-        } else {
-            text_anchor
-        }
-    }
-
-    fn contains(&self, anchor: &Anchor) -> bool {
-        Some(self.buffer_id) == anchor.buffer_id
-            && self
-                .range
-                .context
-                .start
-                .cmp(&anchor.text_anchor, &self.buffer)
-                .is_le()
-            && self
-                .range
-                .context
-                .end
-                .cmp(&anchor.text_anchor, &self.buffer)
-                .is_ge()
-    }
-}
-
-impl ExcerptId {
-    pub fn min() -> Self {
-        Self(0)
-    }
-
-    pub fn max() -> Self {
-        Self(usize::MAX)
-    }
-
-    pub fn to_proto(&self) -> u64 {
-        self.0 as _
-    }
-
-    pub fn from_proto(proto: u64) -> Self {
-        Self(proto as _)
-    }
-
-    pub fn cmp(&self, other: &Self, snapshot: &MultiBufferSnapshot) -> cmp::Ordering {
-        let a = snapshot.excerpt_locator_for_id(*self);
-        let b = snapshot.excerpt_locator_for_id(*other);
-        a.cmp(&b).then_with(|| self.0.cmp(&other.0))
-    }
-}
-
-impl Into<usize> for ExcerptId {
-    fn into(self) -> usize {
-        self.0
-    }
-}
-
-impl fmt::Debug for Excerpt {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_struct("Excerpt")
-            .field("id", &self.id)
-            .field("locator", &self.locator)
-            .field("buffer_id", &self.buffer_id)
-            .field("range", &self.range)
-            .field("text_summary", &self.text_summary)
-            .field("has_trailing_newline", &self.has_trailing_newline)
-            .finish()
-    }
-}
-
-impl sum_tree::Item for Excerpt {
-    type Summary = ExcerptSummary;
-
-    fn summary(&self) -> Self::Summary {
-        let mut text = self.text_summary.clone();
-        if self.has_trailing_newline {
-            text += TextSummary::from("\n");
-        }
-        ExcerptSummary {
-            excerpt_id: self.id,
-            excerpt_locator: self.locator.clone(),
-            max_buffer_row: self.max_buffer_row,
-            text,
-        }
-    }
-}
-
-impl sum_tree::Item for ExcerptIdMapping {
-    type Summary = ExcerptId;
-
-    fn summary(&self) -> Self::Summary {
-        self.id
-    }
-}
-
-impl sum_tree::KeyedItem for ExcerptIdMapping {
-    type Key = ExcerptId;
-
-    fn key(&self) -> Self::Key {
-        self.id
-    }
-}
-
-impl sum_tree::Summary for ExcerptId {
-    type Context = ();
-
-    fn add_summary(&mut self, other: &Self, _: &()) {
-        *self = *other;
-    }
-}
-
-impl sum_tree::Summary for ExcerptSummary {
-    type Context = ();
-
-    fn add_summary(&mut self, summary: &Self, _: &()) {
-        debug_assert!(summary.excerpt_locator > self.excerpt_locator);
-        self.excerpt_locator = summary.excerpt_locator.clone();
-        self.text.add_summary(&summary.text, &());
-        self.max_buffer_row = cmp::max(self.max_buffer_row, summary.max_buffer_row);
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for TextSummary {
-    fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
-        *self += &summary.text;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for usize {
-    fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
-        *self += summary.text.len;
-    }
-}
-
-impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for usize {
-    fn cmp(&self, cursor_location: &ExcerptSummary, _: &()) -> cmp::Ordering {
-        Ord::cmp(self, &cursor_location.text.len)
-    }
-}
-
-impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, Option<&'a Locator>> for Locator {
-    fn cmp(&self, cursor_location: &Option<&'a Locator>, _: &()) -> cmp::Ordering {
-        Ord::cmp(&Some(self), cursor_location)
-    }
-}
-
-impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for Locator {
-    fn cmp(&self, cursor_location: &ExcerptSummary, _: &()) -> cmp::Ordering {
-        Ord::cmp(self, &cursor_location.excerpt_locator)
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for OffsetUtf16 {
-    fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
-        *self += summary.text.len_utf16;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Point {
-    fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
-        *self += summary.text.lines;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for PointUtf16 {
-    fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
-        *self += summary.text.lines_utf16()
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option<&'a Locator> {
-    fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
-        *self = Some(&summary.excerpt_locator);
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option<ExcerptId> {
-    fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
-        *self = Some(summary.excerpt_id);
-    }
-}
-
-impl<'a> MultiBufferRows<'a> {
-    pub fn seek(&mut self, row: u32) {
-        self.buffer_row_range = 0..0;
-
-        self.excerpts
-            .seek_forward(&Point::new(row, 0), Bias::Right, &());
-        if self.excerpts.item().is_none() {
-            self.excerpts.prev(&());
-
-            if self.excerpts.item().is_none() && row == 0 {
-                self.buffer_row_range = 0..1;
-                return;
-            }
-        }
-
-        if let Some(excerpt) = self.excerpts.item() {
-            let overshoot = row - self.excerpts.start().row;
-            let excerpt_start = excerpt.range.context.start.to_point(&excerpt.buffer).row;
-            self.buffer_row_range.start = excerpt_start + overshoot;
-            self.buffer_row_range.end = excerpt_start + excerpt.text_summary.lines.row + 1;
-        }
-    }
-}
-
-impl<'a> Iterator for MultiBufferRows<'a> {
-    type Item = Option<u32>;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        loop {
-            if !self.buffer_row_range.is_empty() {
-                let row = Some(self.buffer_row_range.start);
-                self.buffer_row_range.start += 1;
-                return Some(row);
-            }
-            self.excerpts.item()?;
-            self.excerpts.next(&());
-            let excerpt = self.excerpts.item()?;
-            self.buffer_row_range.start = excerpt.range.context.start.to_point(&excerpt.buffer).row;
-            self.buffer_row_range.end =
-                self.buffer_row_range.start + excerpt.text_summary.lines.row + 1;
-        }
-    }
-}
-
-impl<'a> MultiBufferChunks<'a> {
-    pub fn offset(&self) -> usize {
-        self.range.start
-    }
-
-    pub fn seek(&mut self, offset: usize) {
-        self.range.start = offset;
-        self.excerpts.seek(&offset, Bias::Right, &());
-        if let Some(excerpt) = self.excerpts.item() {
-            self.excerpt_chunks = Some(excerpt.chunks_in_range(
-                self.range.start - self.excerpts.start()..self.range.end - self.excerpts.start(),
-                self.language_aware,
-            ));
-        } else {
-            self.excerpt_chunks = None;
-        }
-    }
-}
-
-impl<'a> Iterator for MultiBufferChunks<'a> {
-    type Item = Chunk<'a>;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if self.range.is_empty() {
-            None
-        } else if let Some(chunk) = self.excerpt_chunks.as_mut()?.next() {
-            self.range.start += chunk.text.len();
-            Some(chunk)
-        } else {
-            self.excerpts.next(&());
-            let excerpt = self.excerpts.item()?;
-            self.excerpt_chunks = Some(excerpt.chunks_in_range(
-                0..self.range.end - self.excerpts.start(),
-                self.language_aware,
-            ));
-            self.next()
-        }
-    }
-}
-
-impl<'a> MultiBufferBytes<'a> {
-    fn consume(&mut self, len: usize) {
-        self.range.start += len;
-        self.chunk = &self.chunk[len..];
-
-        if !self.range.is_empty() && self.chunk.is_empty() {
-            if let Some(chunk) = self.excerpt_bytes.as_mut().and_then(|bytes| bytes.next()) {
-                self.chunk = chunk;
-            } else {
-                self.excerpts.next(&());
-                if let Some(excerpt) = self.excerpts.item() {
-                    let mut excerpt_bytes =
-                        excerpt.bytes_in_range(0..self.range.end - self.excerpts.start());
-                    self.chunk = excerpt_bytes.next().unwrap();
-                    self.excerpt_bytes = Some(excerpt_bytes);
-                }
-            }
-        }
-    }
-}
-
-impl<'a> Iterator for MultiBufferBytes<'a> {
-    type Item = &'a [u8];
-
-    fn next(&mut self) -> Option<Self::Item> {
-        let chunk = self.chunk;
-        if chunk.is_empty() {
-            None
-        } else {
-            self.consume(chunk.len());
-            Some(chunk)
-        }
-    }
-}
-
-impl<'a> io::Read for MultiBufferBytes<'a> {
-    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
-        let len = cmp::min(buf.len(), self.chunk.len());
-        buf[..len].copy_from_slice(&self.chunk[..len]);
-        if len > 0 {
-            self.consume(len);
-        }
-        Ok(len)
-    }
-}
-
-impl<'a> ReversedMultiBufferBytes<'a> {
-    fn consume(&mut self, len: usize) {
-        self.range.end -= len;
-        self.chunk = &self.chunk[..self.chunk.len() - len];
-
-        if !self.range.is_empty() && self.chunk.is_empty() {
-            if let Some(chunk) = self.excerpt_bytes.as_mut().and_then(|bytes| bytes.next()) {
-                self.chunk = chunk;
-            } else {
-                self.excerpts.next(&());
-                if let Some(excerpt) = self.excerpts.item() {
-                    let mut excerpt_bytes =
-                        excerpt.bytes_in_range(0..self.range.end - self.excerpts.start());
-                    self.chunk = excerpt_bytes.next().unwrap();
-                    self.excerpt_bytes = Some(excerpt_bytes);
-                }
-            }
-        }
-    }
-}
-
-impl<'a> io::Read for ReversedMultiBufferBytes<'a> {
-    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
-        let len = cmp::min(buf.len(), self.chunk.len());
-        buf[..len].copy_from_slice(&self.chunk[..len]);
-        buf[..len].reverse();
-        if len > 0 {
-            self.consume(len);
-        }
-        Ok(len)
-    }
-}
-impl<'a> Iterator for ExcerptBytes<'a> {
-    type Item = &'a [u8];
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if let Some(chunk) = self.content_bytes.next() {
-            if !chunk.is_empty() {
-                return Some(chunk);
-            }
-        }
-
-        if self.footer_height > 0 {
-            let result = &NEWLINES[..self.footer_height];
-            self.footer_height = 0;
-            return Some(result);
-        }
-
-        None
-    }
-}
-
-impl<'a> Iterator for ExcerptChunks<'a> {
-    type Item = Chunk<'a>;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if let Some(chunk) = self.content_chunks.next() {
-            return Some(chunk);
-        }
-
-        if self.footer_height > 0 {
-            let text = unsafe { str::from_utf8_unchecked(&NEWLINES[..self.footer_height]) };
-            self.footer_height = 0;
-            return Some(Chunk {
-                text,
-                ..Default::default()
-            });
-        }
-
-        None
-    }
-}
-
-impl ToOffset for Point {
-    fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize {
-        snapshot.point_to_offset(*self)
-    }
-}
-
-impl ToOffset for usize {
-    fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize {
-        assert!(*self <= snapshot.len(), "offset is out of range");
-        *self
-    }
-}
-
-impl ToOffset for OffsetUtf16 {
-    fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize {
-        snapshot.offset_utf16_to_offset(*self)
-    }
-}
-
-impl ToOffset for PointUtf16 {
-    fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize {
-        snapshot.point_utf16_to_offset(*self)
-    }
-}
-
-impl ToOffsetUtf16 for OffsetUtf16 {
-    fn to_offset_utf16(&self, _snapshot: &MultiBufferSnapshot) -> OffsetUtf16 {
-        *self
-    }
-}
-
-impl ToOffsetUtf16 for usize {
-    fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> OffsetUtf16 {
-        snapshot.offset_to_offset_utf16(*self)
-    }
-}
-
-impl ToPoint for usize {
-    fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> Point {
-        snapshot.offset_to_point(*self)
-    }
-}
-
-impl ToPoint for Point {
-    fn to_point<'a>(&self, _: &MultiBufferSnapshot) -> Point {
-        *self
-    }
-}
-
-impl ToPointUtf16 for usize {
-    fn to_point_utf16<'a>(&self, snapshot: &MultiBufferSnapshot) -> PointUtf16 {
-        snapshot.offset_to_point_utf16(*self)
-    }
-}
-
-impl ToPointUtf16 for Point {
-    fn to_point_utf16<'a>(&self, snapshot: &MultiBufferSnapshot) -> PointUtf16 {
-        snapshot.point_to_point_utf16(*self)
-    }
-}
-
-impl ToPointUtf16 for PointUtf16 {
-    fn to_point_utf16<'a>(&self, _: &MultiBufferSnapshot) -> PointUtf16 {
-        *self
-    }
-}
-
-fn build_excerpt_ranges<T>(
-    buffer: &BufferSnapshot,
-    ranges: &[Range<T>],
-    context_line_count: u32,
-) -> (Vec<ExcerptRange<Point>>, Vec<usize>)
-where
-    T: text::ToPoint,
-{
-    let max_point = buffer.max_point();
-    let mut range_counts = Vec::new();
-    let mut excerpt_ranges = Vec::new();
-    let mut range_iter = ranges
-        .iter()
-        .map(|range| range.start.to_point(buffer)..range.end.to_point(buffer))
-        .peekable();
-    while let Some(range) = range_iter.next() {
-        let excerpt_start = Point::new(range.start.row.saturating_sub(context_line_count), 0);
-        let mut excerpt_end = Point::new(range.end.row + 1 + context_line_count, 0).min(max_point);
-        let mut ranges_in_excerpt = 1;
-
-        while let Some(next_range) = range_iter.peek() {
-            if next_range.start.row <= excerpt_end.row + context_line_count {
-                excerpt_end =
-                    Point::new(next_range.end.row + 1 + context_line_count, 0).min(max_point);
-                ranges_in_excerpt += 1;
-                range_iter.next();
-            } else {
-                break;
-            }
-        }
-
-        excerpt_ranges.push(ExcerptRange {
-            context: excerpt_start..excerpt_end,
-            primary: Some(range),
-        });
-        range_counts.push(ranges_in_excerpt);
-    }
-
-    (excerpt_ranges, range_counts)
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use futures::StreamExt;
-    use gpui::{AppContext, Context, TestAppContext};
-    use language::{Buffer, Rope};
-    use parking_lot::RwLock;
-    use rand::prelude::*;
-    use settings::SettingsStore;
-    use std::env;
-    use util::test::sample_text;
-
-    #[gpui::test]
-    fn test_singleton(cx: &mut AppContext) {
-        let buffer =
-            cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(6, 6, 'a')));
-        let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
-
-        let snapshot = multibuffer.read(cx).snapshot(cx);
-        assert_eq!(snapshot.text(), buffer.read(cx).text());
-
-        assert_eq!(
-            snapshot.buffer_rows(0).collect::<Vec<_>>(),
-            (0..buffer.read(cx).row_count())
-                .map(Some)
-                .collect::<Vec<_>>()
-        );
-
-        buffer.update(cx, |buffer, cx| buffer.edit([(1..3, "XXX\n")], None, cx));
-        let snapshot = multibuffer.read(cx).snapshot(cx);
-
-        assert_eq!(snapshot.text(), buffer.read(cx).text());
-        assert_eq!(
-            snapshot.buffer_rows(0).collect::<Vec<_>>(),
-            (0..buffer.read(cx).row_count())
-                .map(Some)
-                .collect::<Vec<_>>()
-        );
-    }
-
-    #[gpui::test]
-    fn test_remote(cx: &mut AppContext) {
-        let host_buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "a"));
-        let guest_buffer = cx.new_model(|cx| {
-            let state = host_buffer.read(cx).to_proto();
-            let ops = cx
-                .background_executor()
-                .block(host_buffer.read(cx).serialize_ops(None, cx));
-            let mut buffer = Buffer::from_proto(1, state, None).unwrap();
-            buffer
-                .apply_ops(
-                    ops.into_iter()
-                        .map(|op| language::proto::deserialize_operation(op).unwrap()),
-                    cx,
-                )
-                .unwrap();
-            buffer
-        });
-        let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(guest_buffer.clone(), cx));
-        let snapshot = multibuffer.read(cx).snapshot(cx);
-        assert_eq!(snapshot.text(), "a");
-
-        guest_buffer.update(cx, |buffer, cx| buffer.edit([(1..1, "b")], None, cx));
-        let snapshot = multibuffer.read(cx).snapshot(cx);
-        assert_eq!(snapshot.text(), "ab");
-
-        guest_buffer.update(cx, |buffer, cx| buffer.edit([(2..2, "c")], None, cx));
-        let snapshot = multibuffer.read(cx).snapshot(cx);
-        assert_eq!(snapshot.text(), "abc");
-    }
-
-    #[gpui::test]
-    fn test_excerpt_boundaries_and_clipping(cx: &mut AppContext) {
-        let buffer_1 =
-            cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(6, 6, 'a')));
-        let buffer_2 =
-            cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(6, 6, 'g')));
-        let multibuffer = cx.new_model(|_| MultiBuffer::new(0));
-
-        let events = Arc::new(RwLock::new(Vec::<Event>::new()));
-        multibuffer.update(cx, |_, cx| {
-            let events = events.clone();
-            cx.subscribe(&multibuffer, move |_, _, event, _| {
-                if let Event::Edited { .. } = event {
-                    events.write().push(event.clone())
-                }
-            })
-            .detach();
-        });
-
-        let subscription = multibuffer.update(cx, |multibuffer, cx| {
-            let subscription = multibuffer.subscribe();
-            multibuffer.push_excerpts(
-                buffer_1.clone(),
-                [ExcerptRange {
-                    context: Point::new(1, 2)..Point::new(2, 5),
-                    primary: None,
-                }],
-                cx,
-            );
-            assert_eq!(
-                subscription.consume().into_inner(),
-                [Edit {
-                    old: 0..0,
-                    new: 0..10
-                }]
-            );
-
-            multibuffer.push_excerpts(
-                buffer_1.clone(),
-                [ExcerptRange {
-                    context: Point::new(3, 3)..Point::new(4, 4),
-                    primary: None,
-                }],
-                cx,
-            );
-            multibuffer.push_excerpts(
-                buffer_2.clone(),
-                [ExcerptRange {
-                    context: Point::new(3, 1)..Point::new(3, 3),
-                    primary: None,
-                }],
-                cx,
-            );
-            assert_eq!(
-                subscription.consume().into_inner(),
-                [Edit {
-                    old: 10..10,
-                    new: 10..22
-                }]
-            );
-
-            subscription
-        });
-
-        // Adding excerpts emits an edited event.
-        assert_eq!(
-            events.read().as_slice(),
-            &[
-                Event::Edited {
-                    sigleton_buffer_edited: false
-                },
-                Event::Edited {
-                    sigleton_buffer_edited: false
-                },
-                Event::Edited {
-                    sigleton_buffer_edited: false
-                }
-            ]
-        );
-
-        let snapshot = multibuffer.read(cx).snapshot(cx);
-        assert_eq!(
-            snapshot.text(),
-            concat!(
-                "bbbb\n",  // Preserve newlines
-                "ccccc\n", //
-                "ddd\n",   //
-                "eeee\n",  //
-                "jj"       //
-            )
-        );
-        assert_eq!(
-            snapshot.buffer_rows(0).collect::<Vec<_>>(),
-            [Some(1), Some(2), Some(3), Some(4), Some(3)]
-        );
-        assert_eq!(
-            snapshot.buffer_rows(2).collect::<Vec<_>>(),
-            [Some(3), Some(4), Some(3)]
-        );
-        assert_eq!(snapshot.buffer_rows(4).collect::<Vec<_>>(), [Some(3)]);
-        assert_eq!(snapshot.buffer_rows(5).collect::<Vec<_>>(), []);
-
-        assert_eq!(
-            boundaries_in_range(Point::new(0, 0)..Point::new(4, 2), &snapshot),
-            &[
-                (0, "bbbb\nccccc".to_string(), true),
-                (2, "ddd\neeee".to_string(), false),
-                (4, "jj".to_string(), true),
-            ]
-        );
-        assert_eq!(
-            boundaries_in_range(Point::new(0, 0)..Point::new(2, 0), &snapshot),
-            &[(0, "bbbb\nccccc".to_string(), true)]
-        );
-        assert_eq!(
-            boundaries_in_range(Point::new(1, 0)..Point::new(1, 5), &snapshot),
-            &[]
-        );
-        assert_eq!(
-            boundaries_in_range(Point::new(1, 0)..Point::new(2, 0), &snapshot),
-            &[]
-        );
-        assert_eq!(
-            boundaries_in_range(Point::new(1, 0)..Point::new(4, 0), &snapshot),
-            &[(2, "ddd\neeee".to_string(), false)]
-        );
-        assert_eq!(
-            boundaries_in_range(Point::new(1, 0)..Point::new(4, 0), &snapshot),
-            &[(2, "ddd\neeee".to_string(), false)]
-        );
-        assert_eq!(
-            boundaries_in_range(Point::new(2, 0)..Point::new(3, 0), &snapshot),
-            &[(2, "ddd\neeee".to_string(), false)]
-        );
-        assert_eq!(
-            boundaries_in_range(Point::new(4, 0)..Point::new(4, 2), &snapshot),
-            &[(4, "jj".to_string(), true)]
-        );
-        assert_eq!(
-            boundaries_in_range(Point::new(4, 2)..Point::new(4, 2), &snapshot),
-            &[]
-        );
-
-        buffer_1.update(cx, |buffer, cx| {
-            let text = "\n";
-            buffer.edit(
-                [
-                    (Point::new(0, 0)..Point::new(0, 0), text),
-                    (Point::new(2, 1)..Point::new(2, 3), text),
-                ],
-                None,
-                cx,
-            );
-        });
-
-        let snapshot = multibuffer.read(cx).snapshot(cx);
-        assert_eq!(
-            snapshot.text(),
-            concat!(
-                "bbbb\n", // Preserve newlines
-                "c\n",    //
-                "cc\n",   //
-                "ddd\n",  //
-                "eeee\n", //
-                "jj"      //
-            )
-        );
-
-        assert_eq!(
-            subscription.consume().into_inner(),
-            [Edit {
-                old: 6..8,
-                new: 6..7
-            }]
-        );
-
-        let snapshot = multibuffer.read(cx).snapshot(cx);
-        assert_eq!(
-            snapshot.clip_point(Point::new(0, 5), Bias::Left),
-            Point::new(0, 4)
-        );
-        assert_eq!(
-            snapshot.clip_point(Point::new(0, 5), Bias::Right),
-            Point::new(0, 4)
-        );
-        assert_eq!(
-            snapshot.clip_point(Point::new(5, 1), Bias::Right),
-            Point::new(5, 1)
-        );
-        assert_eq!(
-            snapshot.clip_point(Point::new(5, 2), Bias::Right),
-            Point::new(5, 2)
-        );
-        assert_eq!(
-            snapshot.clip_point(Point::new(5, 3), Bias::Right),
-            Point::new(5, 2)
-        );
-
-        let snapshot = multibuffer.update(cx, |multibuffer, cx| {
-            let (buffer_2_excerpt_id, _) =
-                multibuffer.excerpts_for_buffer(&buffer_2, cx)[0].clone();
-            multibuffer.remove_excerpts([buffer_2_excerpt_id], cx);
-            multibuffer.snapshot(cx)
-        });
-
-        assert_eq!(
-            snapshot.text(),
-            concat!(
-                "bbbb\n", // Preserve newlines
-                "c\n",    //
-                "cc\n",   //
-                "ddd\n",  //
-                "eeee",   //
-            )
-        );
-
-        fn boundaries_in_range(
-            range: Range<Point>,
-            snapshot: &MultiBufferSnapshot,
-        ) -> Vec<(u32, String, bool)> {
-            snapshot
-                .excerpt_boundaries_in_range(range)
-                .map(|boundary| {
-                    (
-                        boundary.row,
-                        boundary
-                            .buffer
-                            .text_for_range(boundary.range.context)
-                            .collect::<String>(),
-                        boundary.starts_new_buffer,
-                    )
-                })
-                .collect::<Vec<_>>()
-        }
-    }
-
-    #[gpui::test]
-    fn test_excerpt_events(cx: &mut AppContext) {
-        let buffer_1 =
-            cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(10, 3, 'a')));
-        let buffer_2 =
-            cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(10, 3, 'm')));
-
-        let leader_multibuffer = cx.new_model(|_| MultiBuffer::new(0));
-        let follower_multibuffer = cx.new_model(|_| MultiBuffer::new(0));
-        let follower_edit_event_count = Arc::new(RwLock::new(0));
-
-        follower_multibuffer.update(cx, |_, cx| {
-            let follower_edit_event_count = follower_edit_event_count.clone();
-            cx.subscribe(
-                &leader_multibuffer,
-                move |follower, _, event, cx| match event.clone() {
-                    Event::ExcerptsAdded {
-                        buffer,
-                        predecessor,
-                        excerpts,
-                    } => follower.insert_excerpts_with_ids_after(predecessor, buffer, excerpts, cx),
-                    Event::ExcerptsRemoved { ids } => follower.remove_excerpts(ids, cx),
-                    Event::Edited { .. } => {
-                        *follower_edit_event_count.write() += 1;
-                    }
-                    _ => {}
-                },
-            )
-            .detach();
-        });
-
-        leader_multibuffer.update(cx, |leader, cx| {
-            leader.push_excerpts(
-                buffer_1.clone(),
-                [
-                    ExcerptRange {
-                        context: 0..8,
-                        primary: None,
-                    },
-                    ExcerptRange {
-                        context: 12..16,
-                        primary: None,
-                    },
-                ],
-                cx,
-            );
-            leader.insert_excerpts_after(
-                leader.excerpt_ids()[0],
-                buffer_2.clone(),
-                [
-                    ExcerptRange {
-                        context: 0..5,
-                        primary: None,
-                    },
-                    ExcerptRange {
-                        context: 10..15,
-                        primary: None,
-                    },
-                ],
-                cx,
-            )
-        });
-        assert_eq!(
-            leader_multibuffer.read(cx).snapshot(cx).text(),
-            follower_multibuffer.read(cx).snapshot(cx).text(),
-        );
-        assert_eq!(*follower_edit_event_count.read(), 2);
-
-        leader_multibuffer.update(cx, |leader, cx| {
-            let excerpt_ids = leader.excerpt_ids();
-            leader.remove_excerpts([excerpt_ids[1], excerpt_ids[3]], cx);
-        });
-        assert_eq!(
-            leader_multibuffer.read(cx).snapshot(cx).text(),
-            follower_multibuffer.read(cx).snapshot(cx).text(),
-        );
-        assert_eq!(*follower_edit_event_count.read(), 3);
-
-        // Removing an empty set of excerpts is a noop.
-        leader_multibuffer.update(cx, |leader, cx| {
-            leader.remove_excerpts([], cx);
-        });
-        assert_eq!(
-            leader_multibuffer.read(cx).snapshot(cx).text(),
-            follower_multibuffer.read(cx).snapshot(cx).text(),
-        );
-        assert_eq!(*follower_edit_event_count.read(), 3);
-
-        // Adding an empty set of excerpts is a noop.
-        leader_multibuffer.update(cx, |leader, cx| {
-            leader.push_excerpts::<usize>(buffer_2.clone(), [], cx);
-        });
-        assert_eq!(
-            leader_multibuffer.read(cx).snapshot(cx).text(),
-            follower_multibuffer.read(cx).snapshot(cx).text(),
-        );
-        assert_eq!(*follower_edit_event_count.read(), 3);
-
-        leader_multibuffer.update(cx, |leader, cx| {
-            leader.clear(cx);
-        });
-        assert_eq!(
-            leader_multibuffer.read(cx).snapshot(cx).text(),
-            follower_multibuffer.read(cx).snapshot(cx).text(),
-        );
-        assert_eq!(*follower_edit_event_count.read(), 4);
-    }
-
-    #[gpui::test]
-    fn test_push_excerpts_with_context_lines(cx: &mut AppContext) {
-        let buffer =
-            cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(20, 3, 'a')));
-        let multibuffer = cx.new_model(|_| MultiBuffer::new(0));
-        let anchor_ranges = multibuffer.update(cx, |multibuffer, cx| {
-            multibuffer.push_excerpts_with_context_lines(
-                buffer.clone(),
-                vec![
-                    Point::new(3, 2)..Point::new(4, 2),
-                    Point::new(7, 1)..Point::new(7, 3),
-                    Point::new(15, 0)..Point::new(15, 0),
-                ],
-                2,
-                cx,
-            )
-        });
-
-        let snapshot = multibuffer.read(cx).snapshot(cx);
-        assert_eq!(
-            snapshot.text(),
-            "bbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\n\nnnn\nooo\nppp\nqqq\nrrr\n"
-        );
-
-        assert_eq!(
-            anchor_ranges
-                .iter()
-                .map(|range| range.to_point(&snapshot))
-                .collect::<Vec<_>>(),
-            vec![
-                Point::new(2, 2)..Point::new(3, 2),
-                Point::new(6, 1)..Point::new(6, 3),
-                Point::new(12, 0)..Point::new(12, 0)
-            ]
-        );
-    }
-
-    #[gpui::test]
-    async fn test_stream_excerpts_with_context_lines(cx: &mut TestAppContext) {
-        let buffer =
-            cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(20, 3, 'a')));
-        let multibuffer = cx.new_model(|_| MultiBuffer::new(0));
-        let anchor_ranges = multibuffer.update(cx, |multibuffer, cx| {
-            let snapshot = buffer.read(cx);
-            let ranges = vec![
-                snapshot.anchor_before(Point::new(3, 2))..snapshot.anchor_before(Point::new(4, 2)),
-                snapshot.anchor_before(Point::new(7, 1))..snapshot.anchor_before(Point::new(7, 3)),
-                snapshot.anchor_before(Point::new(15, 0))
-                    ..snapshot.anchor_before(Point::new(15, 0)),
-            ];
-            multibuffer.stream_excerpts_with_context_lines(buffer.clone(), ranges, 2, cx)
-        });
-
-        let anchor_ranges = anchor_ranges.collect::<Vec<_>>().await;
-
-        let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
-        assert_eq!(
-            snapshot.text(),
-            "bbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\n\nnnn\nooo\nppp\nqqq\nrrr\n"
-        );
-
-        assert_eq!(
-            anchor_ranges
-                .iter()
-                .map(|range| range.to_point(&snapshot))
-                .collect::<Vec<_>>(),
-            vec![
-                Point::new(2, 2)..Point::new(3, 2),
-                Point::new(6, 1)..Point::new(6, 3),
-                Point::new(12, 0)..Point::new(12, 0)
-            ]
-        );
-    }
-
-    #[gpui::test]
-    fn test_empty_multibuffer(cx: &mut AppContext) {
-        let multibuffer = cx.new_model(|_| MultiBuffer::new(0));
-
-        let snapshot = multibuffer.read(cx).snapshot(cx);
-        assert_eq!(snapshot.text(), "");
-        assert_eq!(snapshot.buffer_rows(0).collect::<Vec<_>>(), &[Some(0)]);
-        assert_eq!(snapshot.buffer_rows(1).collect::<Vec<_>>(), &[]);
-    }
-
-    #[gpui::test]
-    fn test_singleton_multibuffer_anchors(cx: &mut AppContext) {
-        let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abcd"));
-        let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
-        let old_snapshot = multibuffer.read(cx).snapshot(cx);
-        buffer.update(cx, |buffer, cx| {
-            buffer.edit([(0..0, "X")], None, cx);
-            buffer.edit([(5..5, "Y")], None, cx);
-        });
-        let new_snapshot = multibuffer.read(cx).snapshot(cx);
-
-        assert_eq!(old_snapshot.text(), "abcd");
-        assert_eq!(new_snapshot.text(), "XabcdY");
-
-        assert_eq!(old_snapshot.anchor_before(0).to_offset(&new_snapshot), 0);
-        assert_eq!(old_snapshot.anchor_after(0).to_offset(&new_snapshot), 1);
-        assert_eq!(old_snapshot.anchor_before(4).to_offset(&new_snapshot), 5);
-        assert_eq!(old_snapshot.anchor_after(4).to_offset(&new_snapshot), 6);
-    }
-
-    #[gpui::test]
-    fn test_multibuffer_anchors(cx: &mut AppContext) {
-        let buffer_1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abcd"));
-        let buffer_2 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "efghi"));
-        let multibuffer = cx.new_model(|cx| {
-            let mut multibuffer = MultiBuffer::new(0);
-            multibuffer.push_excerpts(
-                buffer_1.clone(),
-                [ExcerptRange {
-                    context: 0..4,
-                    primary: None,
-                }],
-                cx,
-            );
-            multibuffer.push_excerpts(
-                buffer_2.clone(),
-                [ExcerptRange {
-                    context: 0..5,
-                    primary: None,
-                }],
-                cx,
-            );
-            multibuffer
-        });
-        let old_snapshot = multibuffer.read(cx).snapshot(cx);
-
-        assert_eq!(old_snapshot.anchor_before(0).to_offset(&old_snapshot), 0);
-        assert_eq!(old_snapshot.anchor_after(0).to_offset(&old_snapshot), 0);
-        assert_eq!(Anchor::min().to_offset(&old_snapshot), 0);
-        assert_eq!(Anchor::min().to_offset(&old_snapshot), 0);
-        assert_eq!(Anchor::max().to_offset(&old_snapshot), 10);
-        assert_eq!(Anchor::max().to_offset(&old_snapshot), 10);
-
-        buffer_1.update(cx, |buffer, cx| {
-            buffer.edit([(0..0, "W")], None, cx);
-            buffer.edit([(5..5, "X")], None, cx);
-        });
-        buffer_2.update(cx, |buffer, cx| {
-            buffer.edit([(0..0, "Y")], None, cx);
-            buffer.edit([(6..6, "Z")], None, cx);
-        });
-        let new_snapshot = multibuffer.read(cx).snapshot(cx);
-
-        assert_eq!(old_snapshot.text(), "abcd\nefghi");
-        assert_eq!(new_snapshot.text(), "WabcdX\nYefghiZ");
-
-        assert_eq!(old_snapshot.anchor_before(0).to_offset(&new_snapshot), 0);
-        assert_eq!(old_snapshot.anchor_after(0).to_offset(&new_snapshot), 1);
-        assert_eq!(old_snapshot.anchor_before(1).to_offset(&new_snapshot), 2);
-        assert_eq!(old_snapshot.anchor_after(1).to_offset(&new_snapshot), 2);
-        assert_eq!(old_snapshot.anchor_before(2).to_offset(&new_snapshot), 3);
-        assert_eq!(old_snapshot.anchor_after(2).to_offset(&new_snapshot), 3);
-        assert_eq!(old_snapshot.anchor_before(5).to_offset(&new_snapshot), 7);
-        assert_eq!(old_snapshot.anchor_after(5).to_offset(&new_snapshot), 8);
-        assert_eq!(old_snapshot.anchor_before(10).to_offset(&new_snapshot), 13);
-        assert_eq!(old_snapshot.anchor_after(10).to_offset(&new_snapshot), 14);
-    }
-
-    #[gpui::test]
-    fn test_resolving_anchors_after_replacing_their_excerpts(cx: &mut AppContext) {
-        let buffer_1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abcd"));
-        let buffer_2 =
-            cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "ABCDEFGHIJKLMNOP"));
-        let multibuffer = cx.new_model(|_| MultiBuffer::new(0));
-
-        // Create an insertion id in buffer 1 that doesn't exist in buffer 2.
-        // Add an excerpt from buffer 1 that spans this new insertion.
-        buffer_1.update(cx, |buffer, cx| buffer.edit([(4..4, "123")], None, cx));
-        let excerpt_id_1 = multibuffer.update(cx, |multibuffer, cx| {
-            multibuffer
-                .push_excerpts(
-                    buffer_1.clone(),
-                    [ExcerptRange {
-                        context: 0..7,
-                        primary: None,
-                    }],
-                    cx,
-                )
-                .pop()
-                .unwrap()
-        });
-
-        let snapshot_1 = multibuffer.read(cx).snapshot(cx);
-        assert_eq!(snapshot_1.text(), "abcd123");
-
-        // Replace the buffer 1 excerpt with new excerpts from buffer 2.
-        let (excerpt_id_2, excerpt_id_3) = multibuffer.update(cx, |multibuffer, cx| {
-            multibuffer.remove_excerpts([excerpt_id_1], cx);
-            let mut ids = multibuffer
-                .push_excerpts(
-                    buffer_2.clone(),
-                    [
-                        ExcerptRange {
-                            context: 0..4,
-                            primary: None,
-                        },
-                        ExcerptRange {
-                            context: 6..10,
-                            primary: None,
-                        },
-                        ExcerptRange {
-                            context: 12..16,
-                            primary: None,
-                        },
-                    ],
-                    cx,
-                )
-                .into_iter();
-            (ids.next().unwrap(), ids.next().unwrap())
-        });
-        let snapshot_2 = multibuffer.read(cx).snapshot(cx);
-        assert_eq!(snapshot_2.text(), "ABCD\nGHIJ\nMNOP");
-
-        // The old excerpt id doesn't get reused.
-        assert_ne!(excerpt_id_2, excerpt_id_1);
-
-        // Resolve some anchors from the previous snapshot in the new snapshot.
-        // The current excerpts are from a different buffer, so we don't attempt to
-        // resolve the old text anchor in the new buffer.
-        assert_eq!(
-            snapshot_2.summary_for_anchor::<usize>(&snapshot_1.anchor_before(2)),
-            0
-        );
-        assert_eq!(
-            snapshot_2.summaries_for_anchors::<usize, _>(&[
-                snapshot_1.anchor_before(2),
-                snapshot_1.anchor_after(3)
-            ]),
-            vec![0, 0]
-        );
-
-        // Refresh anchors from the old snapshot. The return value indicates that both
-        // anchors lost their original excerpt.
-        let refresh =
-            snapshot_2.refresh_anchors(&[snapshot_1.anchor_before(2), snapshot_1.anchor_after(3)]);
-        assert_eq!(
-            refresh,
-            &[
-                (0, snapshot_2.anchor_before(0), false),
-                (1, snapshot_2.anchor_after(0), false),
-            ]
-        );
-
-        // Replace the middle excerpt with a smaller excerpt in buffer 2,
-        // that intersects the old excerpt.
-        let excerpt_id_5 = multibuffer.update(cx, |multibuffer, cx| {
-            multibuffer.remove_excerpts([excerpt_id_3], cx);
-            multibuffer
-                .insert_excerpts_after(
-                    excerpt_id_2,
-                    buffer_2.clone(),
-                    [ExcerptRange {
-                        context: 5..8,
-                        primary: None,
-                    }],
-                    cx,
-                )
-                .pop()
-                .unwrap()
-        });
-
-        let snapshot_3 = multibuffer.read(cx).snapshot(cx);
-        assert_eq!(snapshot_3.text(), "ABCD\nFGH\nMNOP");
-        assert_ne!(excerpt_id_5, excerpt_id_3);
-
-        // Resolve some anchors from the previous snapshot in the new snapshot.
-        // The third anchor can't be resolved, since its excerpt has been removed,
-        // so it resolves to the same position as its predecessor.
-        let anchors = [
-            snapshot_2.anchor_before(0),
-            snapshot_2.anchor_after(2),
-            snapshot_2.anchor_after(6),
-            snapshot_2.anchor_after(14),
-        ];
-        assert_eq!(
-            snapshot_3.summaries_for_anchors::<usize, _>(&anchors),
-            &[0, 2, 9, 13]
-        );
-
-        let new_anchors = snapshot_3.refresh_anchors(&anchors);
-        assert_eq!(
-            new_anchors.iter().map(|a| (a.0, a.2)).collect::<Vec<_>>(),
-            &[(0, true), (1, true), (2, true), (3, true)]
-        );
-        assert_eq!(
-            snapshot_3.summaries_for_anchors::<usize, _>(new_anchors.iter().map(|a| &a.1)),
-            &[0, 2, 7, 13]
-        );
-    }
-
-    #[gpui::test(iterations = 100)]
-    fn test_random_multibuffer(cx: &mut AppContext, mut rng: StdRng) {
-        let operations = env::var("OPERATIONS")
-            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
-            .unwrap_or(10);
-
-        let mut buffers: Vec<Model<Buffer>> = Vec::new();
-        let multibuffer = cx.new_model(|_| MultiBuffer::new(0));
-        let mut excerpt_ids = Vec::<ExcerptId>::new();
-        let mut expected_excerpts = Vec::<(Model<Buffer>, Range<text::Anchor>)>::new();
-        let mut anchors = Vec::new();
-        let mut old_versions = Vec::new();
-
-        for _ in 0..operations {
-            match rng.gen_range(0..100) {
-                0..=19 if !buffers.is_empty() => {
-                    let buffer = buffers.choose(&mut rng).unwrap();
-                    buffer.update(cx, |buf, cx| buf.randomly_edit(&mut rng, 5, cx));
-                }
-                20..=29 if !expected_excerpts.is_empty() => {
-                    let mut ids_to_remove = vec![];
-                    for _ in 0..rng.gen_range(1..=3) {
-                        if expected_excerpts.is_empty() {
-                            break;
-                        }
-
-                        let ix = rng.gen_range(0..expected_excerpts.len());
-                        ids_to_remove.push(excerpt_ids.remove(ix));
-                        let (buffer, range) = expected_excerpts.remove(ix);
-                        let buffer = buffer.read(cx);
-                        log::info!(
-                            "Removing excerpt {}: {:?}",
-                            ix,
-                            buffer
-                                .text_for_range(range.to_offset(buffer))
-                                .collect::<String>(),
-                        );
-                    }
-                    let snapshot = multibuffer.read(cx).read(cx);
-                    ids_to_remove.sort_unstable_by(|a, b| a.cmp(&b, &snapshot));
-                    drop(snapshot);
-                    multibuffer.update(cx, |multibuffer, cx| {
-                        multibuffer.remove_excerpts(ids_to_remove, cx)
-                    });
-                }
-                30..=39 if !expected_excerpts.is_empty() => {
-                    let multibuffer = multibuffer.read(cx).read(cx);
-                    let offset =
-                        multibuffer.clip_offset(rng.gen_range(0..=multibuffer.len()), Bias::Left);
-                    let bias = if rng.gen() { Bias::Left } else { Bias::Right };
-                    log::info!("Creating anchor at {} with bias {:?}", offset, bias);
-                    anchors.push(multibuffer.anchor_at(offset, bias));
-                    anchors.sort_by(|a, b| a.cmp(b, &multibuffer));
-                }
-                40..=44 if !anchors.is_empty() => {
-                    let multibuffer = multibuffer.read(cx).read(cx);
-                    let prev_len = anchors.len();
-                    anchors = multibuffer
-                        .refresh_anchors(&anchors)
-                        .into_iter()
-                        .map(|a| a.1)
-                        .collect();
-
-                    // Ensure the newly-refreshed anchors point to a valid excerpt and don't
-                    // overshoot its boundaries.
-                    assert_eq!(anchors.len(), prev_len);
-                    for anchor in &anchors {
-                        if anchor.excerpt_id == ExcerptId::min()
-                            || anchor.excerpt_id == ExcerptId::max()
-                        {
-                            continue;
-                        }
-
-                        let excerpt = multibuffer.excerpt(anchor.excerpt_id).unwrap();
-                        assert_eq!(excerpt.id, anchor.excerpt_id);
-                        assert!(excerpt.contains(anchor));
-                    }
-                }
-                _ => {
-                    let buffer_handle = if buffers.is_empty() || rng.gen_bool(0.4) {
-                        let base_text = util::RandomCharIter::new(&mut rng)
-                            .take(10)
-                            .collect::<String>();
-                        buffers.push(
-                            cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), base_text)),
-                        );
-                        buffers.last().unwrap()
-                    } else {
-                        buffers.choose(&mut rng).unwrap()
-                    };
-
-                    let buffer = buffer_handle.read(cx);
-                    let end_ix = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Right);
-                    let start_ix = buffer.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
-                    let anchor_range = buffer.anchor_before(start_ix)..buffer.anchor_after(end_ix);
-                    let prev_excerpt_ix = rng.gen_range(0..=expected_excerpts.len());
-                    let prev_excerpt_id = excerpt_ids
-                        .get(prev_excerpt_ix)
-                        .cloned()
-                        .unwrap_or_else(ExcerptId::max);
-                    let excerpt_ix = (prev_excerpt_ix + 1).min(expected_excerpts.len());
-
-                    log::info!(
-                        "Inserting excerpt at {} of {} for buffer {}: {:?}[{:?}] = {:?}",
-                        excerpt_ix,
-                        expected_excerpts.len(),
-                        buffer_handle.read(cx).remote_id(),
-                        buffer.text(),
-                        start_ix..end_ix,
-                        &buffer.text()[start_ix..end_ix]
-                    );
-
-                    let excerpt_id = multibuffer.update(cx, |multibuffer, cx| {
-                        multibuffer
-                            .insert_excerpts_after(
-                                prev_excerpt_id,
-                                buffer_handle.clone(),
-                                [ExcerptRange {
-                                    context: start_ix..end_ix,
-                                    primary: None,
-                                }],
-                                cx,
-                            )
-                            .pop()
-                            .unwrap()
-                    });
-
-                    excerpt_ids.insert(excerpt_ix, excerpt_id);
-                    expected_excerpts.insert(excerpt_ix, (buffer_handle.clone(), anchor_range));
-                }
-            }
-
-            if rng.gen_bool(0.3) {
-                multibuffer.update(cx, |multibuffer, cx| {
-                    old_versions.push((multibuffer.snapshot(cx), multibuffer.subscribe()));
-                })
-            }
-
-            let snapshot = multibuffer.read(cx).snapshot(cx);
-
-            let mut excerpt_starts = Vec::new();
-            let mut expected_text = String::new();
-            let mut expected_buffer_rows = Vec::new();
-            for (buffer, range) in &expected_excerpts {
-                let buffer = buffer.read(cx);
-                let buffer_range = range.to_offset(buffer);
-
-                excerpt_starts.push(TextSummary::from(expected_text.as_str()));
-                expected_text.extend(buffer.text_for_range(buffer_range.clone()));
-                expected_text.push('\n');
-
-                let buffer_row_range = buffer.offset_to_point(buffer_range.start).row
-                    ..=buffer.offset_to_point(buffer_range.end).row;
-                for row in buffer_row_range {
-                    expected_buffer_rows.push(Some(row));
-                }
-            }
-            // Remove final trailing newline.
-            if !expected_excerpts.is_empty() {
-                expected_text.pop();
-            }
-
-            // Always report one buffer row
-            if expected_buffer_rows.is_empty() {
-                expected_buffer_rows.push(Some(0));
-            }
-
-            assert_eq!(snapshot.text(), expected_text);
-            log::info!("MultiBuffer text: {:?}", expected_text);
-
-            assert_eq!(
-                snapshot.buffer_rows(0).collect::<Vec<_>>(),
-                expected_buffer_rows,
-            );
-
-            for _ in 0..5 {
-                let start_row = rng.gen_range(0..=expected_buffer_rows.len());
-                assert_eq!(
-                    snapshot.buffer_rows(start_row as u32).collect::<Vec<_>>(),
-                    &expected_buffer_rows[start_row..],
-                    "buffer_rows({})",
-                    start_row
-                );
-            }
-
-            assert_eq!(
-                snapshot.max_buffer_row(),
-                expected_buffer_rows.into_iter().flatten().max().unwrap()
-            );
-
-            let mut excerpt_starts = excerpt_starts.into_iter();
-            for (buffer, range) in &expected_excerpts {
-                let buffer = buffer.read(cx);
-                let buffer_id = buffer.remote_id();
-                let buffer_range = range.to_offset(buffer);
-                let buffer_start_point = buffer.offset_to_point(buffer_range.start);
-                let buffer_start_point_utf16 =
-                    buffer.text_summary_for_range::<PointUtf16, _>(0..buffer_range.start);
-
-                let excerpt_start = excerpt_starts.next().unwrap();
-                let mut offset = excerpt_start.len;
-                let mut buffer_offset = buffer_range.start;
-                let mut point = excerpt_start.lines;
-                let mut buffer_point = buffer_start_point;
-                let mut point_utf16 = excerpt_start.lines_utf16();
-                let mut buffer_point_utf16 = buffer_start_point_utf16;
-                for ch in buffer
-                    .snapshot()
-                    .chunks(buffer_range.clone(), false)
-                    .flat_map(|c| c.text.chars())
-                {
-                    for _ in 0..ch.len_utf8() {
-                        let left_offset = snapshot.clip_offset(offset, Bias::Left);
-                        let right_offset = snapshot.clip_offset(offset, Bias::Right);
-                        let buffer_left_offset = buffer.clip_offset(buffer_offset, Bias::Left);
-                        let buffer_right_offset = buffer.clip_offset(buffer_offset, Bias::Right);
-                        assert_eq!(
-                            left_offset,
-                            excerpt_start.len + (buffer_left_offset - buffer_range.start),
-                            "clip_offset({:?}, Left). buffer: {:?}, buffer offset: {:?}",
-                            offset,
-                            buffer_id,
-                            buffer_offset,
-                        );
-                        assert_eq!(
-                            right_offset,
-                            excerpt_start.len + (buffer_right_offset - buffer_range.start),
-                            "clip_offset({:?}, Right). buffer: {:?}, buffer offset: {:?}",
-                            offset,
-                            buffer_id,
-                            buffer_offset,
-                        );
-
-                        let left_point = snapshot.clip_point(point, Bias::Left);
-                        let right_point = snapshot.clip_point(point, Bias::Right);
-                        let buffer_left_point = buffer.clip_point(buffer_point, Bias::Left);
-                        let buffer_right_point = buffer.clip_point(buffer_point, Bias::Right);
-                        assert_eq!(
-                            left_point,
-                            excerpt_start.lines + (buffer_left_point - buffer_start_point),
-                            "clip_point({:?}, Left). buffer: {:?}, buffer point: {:?}",
-                            point,
-                            buffer_id,
-                            buffer_point,
-                        );
-                        assert_eq!(
-                            right_point,
-                            excerpt_start.lines + (buffer_right_point - buffer_start_point),
-                            "clip_point({:?}, Right). buffer: {:?}, buffer point: {:?}",
-                            point,
-                            buffer_id,
-                            buffer_point,
-                        );
-
-                        assert_eq!(
-                            snapshot.point_to_offset(left_point),
-                            left_offset,
-                            "point_to_offset({:?})",
-                            left_point,
-                        );
-                        assert_eq!(
-                            snapshot.offset_to_point(left_offset),
-                            left_point,
-                            "offset_to_point({:?})",
-                            left_offset,
-                        );
-
-                        offset += 1;
-                        buffer_offset += 1;
-                        if ch == '\n' {
-                            point += Point::new(1, 0);
-                            buffer_point += Point::new(1, 0);
-                        } else {
-                            point += Point::new(0, 1);
-                            buffer_point += Point::new(0, 1);
-                        }
-                    }
-
-                    for _ in 0..ch.len_utf16() {
-                        let left_point_utf16 =
-                            snapshot.clip_point_utf16(Unclipped(point_utf16), Bias::Left);
-                        let right_point_utf16 =
-                            snapshot.clip_point_utf16(Unclipped(point_utf16), Bias::Right);
-                        let buffer_left_point_utf16 =
-                            buffer.clip_point_utf16(Unclipped(buffer_point_utf16), Bias::Left);
-                        let buffer_right_point_utf16 =
-                            buffer.clip_point_utf16(Unclipped(buffer_point_utf16), Bias::Right);
-                        assert_eq!(
-                            left_point_utf16,
-                            excerpt_start.lines_utf16()
-                                + (buffer_left_point_utf16 - buffer_start_point_utf16),
-                            "clip_point_utf16({:?}, Left). buffer: {:?}, buffer point_utf16: {:?}",
-                            point_utf16,
-                            buffer_id,
-                            buffer_point_utf16,
-                        );
-                        assert_eq!(
-                            right_point_utf16,
-                            excerpt_start.lines_utf16()
-                                + (buffer_right_point_utf16 - buffer_start_point_utf16),
-                            "clip_point_utf16({:?}, Right). buffer: {:?}, buffer point_utf16: {:?}",
-                            point_utf16,
-                            buffer_id,
-                            buffer_point_utf16,
-                        );
-
-                        if ch == '\n' {
-                            point_utf16 += PointUtf16::new(1, 0);
-                            buffer_point_utf16 += PointUtf16::new(1, 0);
-                        } else {
-                            point_utf16 += PointUtf16::new(0, 1);
-                            buffer_point_utf16 += PointUtf16::new(0, 1);
-                        }
-                    }
-                }
-            }
-
-            for (row, line) in expected_text.split('\n').enumerate() {
-                assert_eq!(
-                    snapshot.line_len(row as u32),
-                    line.len() as u32,
-                    "line_len({}).",
-                    row
-                );
-            }
-
-            let text_rope = Rope::from(expected_text.as_str());
-            for _ in 0..10 {
-                let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
-                let start_ix = text_rope.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
-
-                let text_for_range = snapshot
-                    .text_for_range(start_ix..end_ix)
-                    .collect::<String>();
-                assert_eq!(
-                    text_for_range,
-                    &expected_text[start_ix..end_ix],
-                    "incorrect text for range {:?}",
-                    start_ix..end_ix
-                );
-
-                let excerpted_buffer_ranges = multibuffer
-                    .read(cx)
-                    .range_to_buffer_ranges(start_ix..end_ix, cx);
-                let excerpted_buffers_text = excerpted_buffer_ranges
-                    .iter()
-                    .map(|(buffer, buffer_range, _)| {
-                        buffer
-                            .read(cx)
-                            .text_for_range(buffer_range.clone())
-                            .collect::<String>()
-                    })
-                    .collect::<Vec<_>>()
-                    .join("\n");
-                assert_eq!(excerpted_buffers_text, text_for_range);
-                if !expected_excerpts.is_empty() {
-                    assert!(!excerpted_buffer_ranges.is_empty());
-                }
-
-                let expected_summary = TextSummary::from(&expected_text[start_ix..end_ix]);
-                assert_eq!(
-                    snapshot.text_summary_for_range::<TextSummary, _>(start_ix..end_ix),
-                    expected_summary,
-                    "incorrect summary for range {:?}",
-                    start_ix..end_ix
-                );
-            }
-
-            // Anchor resolution
-            let summaries = snapshot.summaries_for_anchors::<usize, _>(&anchors);
-            assert_eq!(anchors.len(), summaries.len());
-            for (anchor, resolved_offset) in anchors.iter().zip(summaries) {
-                assert!(resolved_offset <= snapshot.len());
-                assert_eq!(
-                    snapshot.summary_for_anchor::<usize>(anchor),
-                    resolved_offset
-                );
-            }
-
-            for _ in 0..10 {
-                let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
-                assert_eq!(
-                    snapshot.reversed_chars_at(end_ix).collect::<String>(),
-                    expected_text[..end_ix].chars().rev().collect::<String>(),
-                );
-            }
-
-            for _ in 0..10 {
-                let end_ix = rng.gen_range(0..=text_rope.len());
-                let start_ix = rng.gen_range(0..=end_ix);
-                assert_eq!(
-                    snapshot
-                        .bytes_in_range(start_ix..end_ix)
-                        .flatten()
-                        .copied()
-                        .collect::<Vec<_>>(),
-                    expected_text.as_bytes()[start_ix..end_ix].to_vec(),
-                    "bytes_in_range({:?})",
-                    start_ix..end_ix,
-                );
-            }
-        }
-
-        let snapshot = multibuffer.read(cx).snapshot(cx);
-        for (old_snapshot, subscription) in old_versions {
-            let edits = subscription.consume().into_inner();
-
-            log::info!(
-                "applying subscription edits to old text: {:?}: {:?}",
-                old_snapshot.text(),
-                edits,
-            );
-
-            let mut text = old_snapshot.text();
-            for edit in edits {
-                let new_text: String = snapshot.text_for_range(edit.new.clone()).collect();
-                text.replace_range(edit.new.start..edit.new.start + edit.old.len(), &new_text);
-            }
-            assert_eq!(text.to_string(), snapshot.text());
-        }
-    }
-
-    #[gpui::test]
-    fn test_history(cx: &mut AppContext) {
-        let test_settings = SettingsStore::test(cx);
-        cx.set_global(test_settings);
-
-        let buffer_1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "1234"));
-        let buffer_2 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "5678"));
-        let multibuffer = cx.new_model(|_| MultiBuffer::new(0));
-        let group_interval = multibuffer.read(cx).history.group_interval;
-        multibuffer.update(cx, |multibuffer, cx| {
-            multibuffer.push_excerpts(
-                buffer_1.clone(),
-                [ExcerptRange {
-                    context: 0..buffer_1.read(cx).len(),
-                    primary: None,
-                }],
-                cx,
-            );
-            multibuffer.push_excerpts(
-                buffer_2.clone(),
-                [ExcerptRange {
-                    context: 0..buffer_2.read(cx).len(),
-                    primary: None,
-                }],
-                cx,
-            );
-        });
-
-        let mut now = Instant::now();
-
-        multibuffer.update(cx, |multibuffer, cx| {
-            let transaction_1 = multibuffer.start_transaction_at(now, cx).unwrap();
-            multibuffer.edit(
-                [
-                    (Point::new(0, 0)..Point::new(0, 0), "A"),
-                    (Point::new(1, 0)..Point::new(1, 0), "A"),
-                ],
-                None,
-                cx,
-            );
-            multibuffer.edit(
-                [
-                    (Point::new(0, 1)..Point::new(0, 1), "B"),
-                    (Point::new(1, 1)..Point::new(1, 1), "B"),
-                ],
-                None,
-                cx,
-            );
-            multibuffer.end_transaction_at(now, cx);
-            assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
-
-            // Edit buffer 1 through the multibuffer
-            now += 2 * group_interval;
-            multibuffer.start_transaction_at(now, cx);
-            multibuffer.edit([(2..2, "C")], None, cx);
-            multibuffer.end_transaction_at(now, cx);
-            assert_eq!(multibuffer.read(cx).text(), "ABC1234\nAB5678");
-
-            // Edit buffer 1 independently
-            buffer_1.update(cx, |buffer_1, cx| {
-                buffer_1.start_transaction_at(now);
-                buffer_1.edit([(3..3, "D")], None, cx);
-                buffer_1.end_transaction_at(now, cx);
-
-                now += 2 * group_interval;
-                buffer_1.start_transaction_at(now);
-                buffer_1.edit([(4..4, "E")], None, cx);
-                buffer_1.end_transaction_at(now, cx);
-            });
-            assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
-
-            // An undo in the multibuffer undoes the multibuffer transaction
-            // and also any individual buffer edits that have occurred since
-            // that transaction.
-            multibuffer.undo(cx);
-            assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
-
-            multibuffer.undo(cx);
-            assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
-
-            multibuffer.redo(cx);
-            assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
-
-            multibuffer.redo(cx);
-            assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
-
-            // Undo buffer 2 independently.
-            buffer_2.update(cx, |buffer_2, cx| buffer_2.undo(cx));
-            assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\n5678");
-
-            // An undo in the multibuffer undoes the components of the
-            // the last multibuffer transaction that are not already undone.
-            multibuffer.undo(cx);
-            assert_eq!(multibuffer.read(cx).text(), "AB1234\n5678");
-
-            multibuffer.undo(cx);
-            assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
-
-            multibuffer.redo(cx);
-            assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
-
-            buffer_1.update(cx, |buffer_1, cx| buffer_1.redo(cx));
-            assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
-
-            // Redo stack gets cleared after an edit.
-            now += 2 * group_interval;
-            multibuffer.start_transaction_at(now, cx);
-            multibuffer.edit([(0..0, "X")], None, cx);
-            multibuffer.end_transaction_at(now, cx);
-            assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
-            multibuffer.redo(cx);
-            assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
-            multibuffer.undo(cx);
-            assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
-            multibuffer.undo(cx);
-            assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
-
-            // Transactions can be grouped manually.
-            multibuffer.redo(cx);
-            multibuffer.redo(cx);
-            assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
-            multibuffer.group_until_transaction(transaction_1, cx);
-            multibuffer.undo(cx);
-            assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
-            multibuffer.redo(cx);
-            assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
-        });
-    }
-}

crates/notifications/Cargo.toml 🔗

@@ -5,7 +5,7 @@ edition = "2021"
 publish = false
 
 [lib]
-path = "src/notification_store.rs"
+path = "src/notification_store2.rs"
 doctest = false
 
 [features]

crates/notifications/src/notification_store.rs 🔗

@@ -1,459 +0,0 @@
-use anyhow::Result;
-use channel::{ChannelMessage, ChannelMessageId, ChannelStore};
-use client::{Client, UserStore};
-use collections::HashMap;
-use db::smol::stream::StreamExt;
-use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task};
-use rpc::{proto, Notification, TypedEnvelope};
-use std::{ops::Range, sync::Arc};
-use sum_tree::{Bias, SumTree};
-use time::OffsetDateTime;
-use util::ResultExt;
-
-pub fn init(client: Arc<Client>, user_store: ModelHandle<UserStore>, cx: &mut AppContext) {
-    let notification_store = cx.add_model(|cx| NotificationStore::new(client, user_store, cx));
-    cx.set_global(notification_store);
-}
-
-pub struct NotificationStore {
-    client: Arc<Client>,
-    user_store: ModelHandle<UserStore>,
-    channel_messages: HashMap<u64, ChannelMessage>,
-    channel_store: ModelHandle<ChannelStore>,
-    notifications: SumTree<NotificationEntry>,
-    loaded_all_notifications: bool,
-    _watch_connection_status: Task<Option<()>>,
-    _subscriptions: Vec<client::Subscription>,
-}
-
-#[derive(Clone, PartialEq, Eq, Debug)]
-pub enum NotificationEvent {
-    NotificationsUpdated {
-        old_range: Range<usize>,
-        new_count: usize,
-    },
-    NewNotification {
-        entry: NotificationEntry,
-    },
-    NotificationRemoved {
-        entry: NotificationEntry,
-    },
-    NotificationRead {
-        entry: NotificationEntry,
-    },
-}
-
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct NotificationEntry {
-    pub id: u64,
-    pub notification: Notification,
-    pub timestamp: OffsetDateTime,
-    pub is_read: bool,
-    pub response: Option<bool>,
-}
-
-#[derive(Clone, Debug, Default)]
-pub struct NotificationSummary {
-    max_id: u64,
-    count: usize,
-    unread_count: usize,
-}
-
-#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
-struct Count(usize);
-
-#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
-struct UnreadCount(usize);
-
-#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
-struct NotificationId(u64);
-
-impl NotificationStore {
-    pub fn global(cx: &AppContext) -> ModelHandle<Self> {
-        cx.global::<ModelHandle<Self>>().clone()
-    }
-
-    pub fn new(
-        client: Arc<Client>,
-        user_store: ModelHandle<UserStore>,
-        cx: &mut ModelContext<Self>,
-    ) -> Self {
-        let mut connection_status = client.status();
-        let watch_connection_status = cx.spawn_weak(|this, mut cx| async move {
-            while let Some(status) = connection_status.next().await {
-                let this = this.upgrade(&cx)?;
-                match status {
-                    client::Status::Connected { .. } => {
-                        if let Some(task) = this.update(&mut cx, |this, cx| this.handle_connect(cx))
-                        {
-                            task.await.log_err()?;
-                        }
-                    }
-                    _ => this.update(&mut cx, |this, cx| this.handle_disconnect(cx)),
-                }
-            }
-            Some(())
-        });
-
-        Self {
-            channel_store: ChannelStore::global(cx),
-            notifications: Default::default(),
-            loaded_all_notifications: false,
-            channel_messages: Default::default(),
-            _watch_connection_status: watch_connection_status,
-            _subscriptions: vec![
-                client.add_message_handler(cx.handle(), Self::handle_new_notification),
-                client.add_message_handler(cx.handle(), Self::handle_delete_notification),
-            ],
-            user_store,
-            client,
-        }
-    }
-
-    pub fn notification_count(&self) -> usize {
-        self.notifications.summary().count
-    }
-
-    pub fn unread_notification_count(&self) -> usize {
-        self.notifications.summary().unread_count
-    }
-
-    pub fn channel_message_for_id(&self, id: u64) -> Option<&ChannelMessage> {
-        self.channel_messages.get(&id)
-    }
-
-    // Get the nth newest notification.
-    pub fn notification_at(&self, ix: usize) -> Option<&NotificationEntry> {
-        let count = self.notifications.summary().count;
-        if ix >= count {
-            return None;
-        }
-        let ix = count - 1 - ix;
-        let mut cursor = self.notifications.cursor::<Count>();
-        cursor.seek(&Count(ix), Bias::Right, &());
-        cursor.item()
-    }
-
-    pub fn notification_for_id(&self, id: u64) -> Option<&NotificationEntry> {
-        let mut cursor = self.notifications.cursor::<NotificationId>();
-        cursor.seek(&NotificationId(id), Bias::Left, &());
-        if let Some(item) = cursor.item() {
-            if item.id == id {
-                return Some(item);
-            }
-        }
-        None
-    }
-
-    pub fn load_more_notifications(
-        &self,
-        clear_old: bool,
-        cx: &mut ModelContext<Self>,
-    ) -> Option<Task<Result<()>>> {
-        if self.loaded_all_notifications && !clear_old {
-            return None;
-        }
-
-        let before_id = if clear_old {
-            None
-        } else {
-            self.notifications.first().map(|entry| entry.id)
-        };
-        let request = self.client.request(proto::GetNotifications { before_id });
-        Some(cx.spawn(|this, mut cx| async move {
-            let response = request.await?;
-            this.update(&mut cx, |this, _| {
-                this.loaded_all_notifications = response.done
-            });
-            Self::add_notifications(
-                this,
-                response.notifications,
-                AddNotificationsOptions {
-                    is_new: false,
-                    clear_old,
-                    includes_first: response.done,
-                },
-                cx,
-            )
-            .await?;
-            Ok(())
-        }))
-    }
-
-    fn handle_connect(&mut self, cx: &mut ModelContext<Self>) -> Option<Task<Result<()>>> {
-        self.notifications = Default::default();
-        self.channel_messages = Default::default();
-        cx.notify();
-        self.load_more_notifications(true, cx)
-    }
-
-    fn handle_disconnect(&mut self, cx: &mut ModelContext<Self>) {
-        cx.notify()
-    }
-
-    async fn handle_new_notification(
-        this: ModelHandle<Self>,
-        envelope: TypedEnvelope<proto::AddNotification>,
-        _: Arc<Client>,
-        cx: AsyncAppContext,
-    ) -> Result<()> {
-        Self::add_notifications(
-            this,
-            envelope.payload.notification.into_iter().collect(),
-            AddNotificationsOptions {
-                is_new: true,
-                clear_old: false,
-                includes_first: false,
-            },
-            cx,
-        )
-        .await
-    }
-
-    async fn handle_delete_notification(
-        this: ModelHandle<Self>,
-        envelope: TypedEnvelope<proto::DeleteNotification>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        this.update(&mut cx, |this, cx| {
-            this.splice_notifications([(envelope.payload.notification_id, None)], false, cx);
-            Ok(())
-        })
-    }
-
-    async fn add_notifications(
-        this: ModelHandle<Self>,
-        notifications: Vec<proto::Notification>,
-        options: AddNotificationsOptions,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        let mut user_ids = Vec::new();
-        let mut message_ids = Vec::new();
-
-        let notifications = notifications
-            .into_iter()
-            .filter_map(|message| {
-                Some(NotificationEntry {
-                    id: message.id,
-                    is_read: message.is_read,
-                    timestamp: OffsetDateTime::from_unix_timestamp(message.timestamp as i64)
-                        .ok()?,
-                    notification: Notification::from_proto(&message)?,
-                    response: message.response,
-                })
-            })
-            .collect::<Vec<_>>();
-        if notifications.is_empty() {
-            return Ok(());
-        }
-
-        for entry in &notifications {
-            match entry.notification {
-                Notification::ChannelInvitation { inviter_id, .. } => {
-                    user_ids.push(inviter_id);
-                }
-                Notification::ContactRequest {
-                    sender_id: requester_id,
-                } => {
-                    user_ids.push(requester_id);
-                }
-                Notification::ContactRequestAccepted {
-                    responder_id: contact_id,
-                } => {
-                    user_ids.push(contact_id);
-                }
-                Notification::ChannelMessageMention {
-                    sender_id,
-                    message_id,
-                    ..
-                } => {
-                    user_ids.push(sender_id);
-                    message_ids.push(message_id);
-                }
-            }
-        }
-
-        let (user_store, channel_store) = this.read_with(&cx, |this, _| {
-            (this.user_store.clone(), this.channel_store.clone())
-        });
-
-        user_store
-            .update(&mut cx, |store, cx| store.get_users(user_ids, cx))
-            .await?;
-        let messages = channel_store
-            .update(&mut cx, |store, cx| {
-                store.fetch_channel_messages(message_ids, cx)
-            })
-            .await?;
-        this.update(&mut cx, |this, cx| {
-            if options.clear_old {
-                cx.emit(NotificationEvent::NotificationsUpdated {
-                    old_range: 0..this.notifications.summary().count,
-                    new_count: 0,
-                });
-                this.notifications = SumTree::default();
-                this.channel_messages.clear();
-                this.loaded_all_notifications = false;
-            }
-
-            if options.includes_first {
-                this.loaded_all_notifications = true;
-            }
-
-            this.channel_messages
-                .extend(messages.into_iter().filter_map(|message| {
-                    if let ChannelMessageId::Saved(id) = message.id {
-                        Some((id, message))
-                    } else {
-                        None
-                    }
-                }));
-
-            this.splice_notifications(
-                notifications
-                    .into_iter()
-                    .map(|notification| (notification.id, Some(notification))),
-                options.is_new,
-                cx,
-            );
-        });
-
-        Ok(())
-    }
-
-    fn splice_notifications(
-        &mut self,
-        notifications: impl IntoIterator<Item = (u64, Option<NotificationEntry>)>,
-        is_new: bool,
-        cx: &mut ModelContext<'_, NotificationStore>,
-    ) {
-        let mut cursor = self.notifications.cursor::<(NotificationId, Count)>();
-        let mut new_notifications = SumTree::new();
-        let mut old_range = 0..0;
-
-        for (i, (id, new_notification)) in notifications.into_iter().enumerate() {
-            new_notifications.append(cursor.slice(&NotificationId(id), Bias::Left, &()), &());
-
-            if i == 0 {
-                old_range.start = cursor.start().1 .0;
-            }
-
-            let old_notification = cursor.item();
-            if let Some(old_notification) = old_notification {
-                if old_notification.id == id {
-                    cursor.next(&());
-
-                    if let Some(new_notification) = &new_notification {
-                        if new_notification.is_read {
-                            cx.emit(NotificationEvent::NotificationRead {
-                                entry: new_notification.clone(),
-                            });
-                        }
-                    } else {
-                        cx.emit(NotificationEvent::NotificationRemoved {
-                            entry: old_notification.clone(),
-                        });
-                    }
-                }
-            } else if let Some(new_notification) = &new_notification {
-                if is_new {
-                    cx.emit(NotificationEvent::NewNotification {
-                        entry: new_notification.clone(),
-                    });
-                }
-            }
-
-            if let Some(notification) = new_notification {
-                new_notifications.push(notification, &());
-            }
-        }
-
-        old_range.end = cursor.start().1 .0;
-        let new_count = new_notifications.summary().count - old_range.start;
-        new_notifications.append(cursor.suffix(&()), &());
-        drop(cursor);
-
-        self.notifications = new_notifications;
-        cx.emit(NotificationEvent::NotificationsUpdated {
-            old_range,
-            new_count,
-        });
-    }
-
-    pub fn respond_to_notification(
-        &mut self,
-        notification: Notification,
-        response: bool,
-        cx: &mut ModelContext<Self>,
-    ) {
-        match notification {
-            Notification::ContactRequest { sender_id } => {
-                self.user_store
-                    .update(cx, |store, cx| {
-                        store.respond_to_contact_request(sender_id, response, cx)
-                    })
-                    .detach();
-            }
-            Notification::ChannelInvitation { channel_id, .. } => {
-                self.channel_store
-                    .update(cx, |store, cx| {
-                        store.respond_to_channel_invite(channel_id, response, cx)
-                    })
-                    .detach();
-            }
-            _ => {}
-        }
-    }
-}
-
-impl Entity for NotificationStore {
-    type Event = NotificationEvent;
-}
-
-impl sum_tree::Item for NotificationEntry {
-    type Summary = NotificationSummary;
-
-    fn summary(&self) -> Self::Summary {
-        NotificationSummary {
-            max_id: self.id,
-            count: 1,
-            unread_count: if self.is_read { 0 } else { 1 },
-        }
-    }
-}
-
-impl sum_tree::Summary for NotificationSummary {
-    type Context = ();
-
-    fn add_summary(&mut self, summary: &Self, _: &()) {
-        self.max_id = self.max_id.max(summary.max_id);
-        self.count += summary.count;
-        self.unread_count += summary.unread_count;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, NotificationSummary> for NotificationId {
-    fn add_summary(&mut self, summary: &NotificationSummary, _: &()) {
-        debug_assert!(summary.max_id > self.0);
-        self.0 = summary.max_id;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, NotificationSummary> for Count {
-    fn add_summary(&mut self, summary: &NotificationSummary, _: &()) {
-        self.0 += summary.count;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, NotificationSummary> for UnreadCount {
-    fn add_summary(&mut self, summary: &NotificationSummary, _: &()) {
-        self.0 += summary.unread_count;
-    }
-}
-
-struct AddNotificationsOptions {
-    is_new: bool,
-    clear_old: bool,
-    includes_first: bool,
-}

crates/notifications2/Cargo.toml 🔗

@@ -1,42 +0,0 @@
-[package]
-name = "notifications2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/notification_store2.rs"
-doctest = false
-
-[features]
-test-support = [
-    "channel/test-support",
-    "collections/test-support",
-    "gpui/test-support",
-    "rpc/test-support",
-]
-
-[dependencies]
-channel = { package = "channel2", path = "../channel2" }
-client = { package = "client2", path = "../client2" }
-clock = { path = "../clock" }
-collections = { path = "../collections" }
-db = { package = "db2", path = "../db2" }
-feature_flags = { package = "feature_flags2", path = "../feature_flags2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-rpc = { package = "rpc2", path = "../rpc2" }
-settings = { package = "settings2", path = "../settings2" }
-sum_tree = { path = "../sum_tree" }
-text = { package = "text2", path = "../text2" }
-util = { path = "../util" }
-
-anyhow.workspace = true
-time.workspace = true
-
-[dev-dependencies]
-client = { package = "client2", path = "../client2", features = ["test-support"] }
-collections = { path = "../collections", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
-settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
-util = { path = "../util", features = ["test-support"] }

crates/outline/Cargo.toml 🔗

@@ -10,14 +10,16 @@ doctest = false
 
 [dependencies]
 editor = { path = "../editor" }
-fuzzy = { path = "../fuzzy" }
+fuzzy = {  path = "../fuzzy" }
 gpui = { path = "../gpui" }
+ui = { path = "../ui" }
 language = { path = "../language" }
 picker = { path = "../picker" }
 settings = { path = "../settings" }
 text = { path = "../text" }
 theme = { path = "../theme" }
 workspace = { path = "../workspace" }
+util = { path = "../util" }
 
 ordered-float.workspace = true
 postage.workspace = true

crates/outline/src/outline.rs 🔗

@@ -1,68 +1,109 @@
 use editor::{
-    combine_syntax_and_fuzzy_match_highlights, display_map::ToDisplayPoint,
-    scroll::autoscroll::Autoscroll, Anchor, AnchorRangeExt, DisplayPoint, Editor, ToPoint,
+    display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Anchor, AnchorRangeExt,
+    DisplayPoint, Editor, EditorMode, ToPoint,
 };
 use fuzzy::StringMatch;
 use gpui::{
-    actions, elements::*, geometry::vector::Vector2F, AppContext, MouseState, Task, ViewContext,
-    ViewHandle, WindowContext,
+    actions, div, rems, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView,
+    FontStyle, FontWeight, HighlightStyle, ParentElement, Point, Render, Styled, StyledText, Task,
+    TextStyle, View, ViewContext, VisualContext, WeakView, WhiteSpace, WindowContext,
 };
 use language::Outline;
 use ordered_float::OrderedFloat;
-use picker::{Picker, PickerDelegate, PickerEvent};
+use picker::{Picker, PickerDelegate};
+use settings::Settings;
 use std::{
     cmp::{self, Reverse},
     sync::Arc,
 };
-use workspace::Workspace;
+
+use theme::{color_alpha, ActiveTheme, ThemeSettings};
+use ui::{prelude::*, ListItem, ListItemSpacing};
+use util::ResultExt;
+use workspace::ModalView;
 
 actions!(outline, [Toggle]);
 
 pub fn init(cx: &mut AppContext) {
-    cx.add_action(toggle);
-    OutlineView::init(cx);
+    cx.observe_new_views(OutlineView::register).detach();
 }
 
-pub fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
-    if let Some(editor) = workspace
-        .active_item(cx)
-        .and_then(|item| item.downcast::<Editor>())
-    {
-        let outline = editor
-            .read(cx)
-            .buffer()
-            .read(cx)
-            .snapshot(cx)
-            .outline(Some(theme::current(cx).editor.syntax.as_ref()));
-        if let Some(outline) = outline {
-            workspace.toggle_modal(cx, |_, cx| {
-                cx.add_view(|cx| {
-                    OutlineView::new(OutlineViewDelegate::new(outline, editor, cx), cx)
-                        .with_max_size(800., 1200.)
-                })
+pub fn toggle(editor: View<Editor>, _: &Toggle, cx: &mut WindowContext) {
+    let outline = editor
+        .read(cx)
+        .buffer()
+        .read(cx)
+        .snapshot(cx)
+        .outline(Some(&cx.theme().syntax()));
+
+    if let Some((workspace, outline)) = editor.read(cx).workspace().zip(outline) {
+        workspace.update(cx, |workspace, cx| {
+            workspace.toggle_modal(cx, |cx| OutlineView::new(outline, editor, cx));
+        })
+    }
+}
+
+pub struct OutlineView {
+    picker: View<Picker<OutlineViewDelegate>>,
+}
+
+impl FocusableView for OutlineView {
+    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
+        self.picker.focus_handle(cx)
+    }
+}
+
+impl EventEmitter<DismissEvent> for OutlineView {}
+impl ModalView for OutlineView {}
+
+impl Render for OutlineView {
+    fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
+        v_stack().w(rems(34.)).child(self.picker.clone())
+    }
+}
+
+impl OutlineView {
+    fn register(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
+        if editor.mode() == EditorMode::Full {
+            let handle = cx.view().downgrade();
+            editor.register_action(move |action, cx| {
+                if let Some(editor) = handle.upgrade() {
+                    toggle(editor, action, cx);
+                }
             });
         }
     }
-}
 
-type OutlineView = Picker<OutlineViewDelegate>;
+    fn new(
+        outline: Outline<Anchor>,
+        editor: View<Editor>,
+        cx: &mut ViewContext<Self>,
+    ) -> OutlineView {
+        let delegate = OutlineViewDelegate::new(cx.view().downgrade(), outline, editor, cx);
+        let picker = cx.new_view(|cx| Picker::new(delegate, cx).max_height(vh(0.75, cx)));
+        OutlineView { picker }
+    }
+}
 
 struct OutlineViewDelegate {
-    active_editor: ViewHandle<Editor>,
+    outline_view: WeakView<OutlineView>,
+    active_editor: View<Editor>,
     outline: Outline<Anchor>,
     selected_match_index: usize,
-    prev_scroll_position: Option<Vector2F>,
+    prev_scroll_position: Option<Point<f32>>,
     matches: Vec<StringMatch>,
     last_query: String,
 }
 
 impl OutlineViewDelegate {
     fn new(
+        outline_view: WeakView<OutlineView>,
         outline: Outline<Anchor>,
-        editor: ViewHandle<Editor>,
+        editor: View<Editor>,
         cx: &mut ViewContext<OutlineView>,
     ) -> Self {
         Self {
+            outline_view,
             last_query: Default::default(),
             matches: Default::default(),
             selected_match_index: 0,
@@ -81,11 +122,18 @@ impl OutlineViewDelegate {
         })
     }
 
-    fn set_selected_index(&mut self, ix: usize, navigate: bool, cx: &mut ViewContext<OutlineView>) {
+    fn set_selected_index(
+        &mut self,
+        ix: usize,
+        navigate: bool,
+        cx: &mut ViewContext<Picker<OutlineViewDelegate>>,
+    ) {
         self.selected_match_index = ix;
+
         if navigate && !self.matches.is_empty() {
             let selected_match = &self.matches[self.selected_match_index];
             let outline_item = &self.outline.items[selected_match.candidate_id];
+
             self.active_editor.update(cx, |active_editor, cx| {
                 let snapshot = active_editor.snapshot(cx).display_snapshot;
                 let buffer_snapshot = &snapshot.buffer_snapshot;
@@ -101,6 +149,8 @@ impl OutlineViewDelegate {
 }
 
 impl PickerDelegate for OutlineViewDelegate {
+    type ListItem = ListItem;
+
     fn placeholder_text(&self) -> Arc<str> {
         "Search buffer symbols...".into()
     }
@@ -113,15 +163,15 @@ impl PickerDelegate for OutlineViewDelegate {
         self.selected_match_index
     }
 
-    fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<OutlineView>) {
+    fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<OutlineViewDelegate>>) {
         self.set_selected_index(ix, true, cx);
     }
 
-    fn center_selection_after_match_updates(&self) -> bool {
-        true
-    }
-
-    fn update_matches(&mut self, query: String, cx: &mut ViewContext<OutlineView>) -> Task<()> {
+    fn update_matches(
+        &mut self,
+        query: String,
+        cx: &mut ViewContext<Picker<OutlineViewDelegate>>,
+    ) -> Task<()> {
         let selected_index;
         if query.is_empty() {
             self.restore_active_editor(cx);
@@ -163,7 +213,10 @@ impl PickerDelegate for OutlineViewDelegate {
                 .map(|(ix, _, _)| ix)
                 .unwrap_or(0);
         } else {
-            self.matches = smol::block_on(self.outline.search(&query, cx.background().clone()));
+            self.matches = smol::block_on(
+                self.outline
+                    .search(&query, cx.background_executor().clone()),
+            );
             selected_index = self
                 .matches
                 .iter()
@@ -177,8 +230,9 @@ impl PickerDelegate for OutlineViewDelegate {
         Task::ready(())
     }
 
-    fn confirm(&mut self, _: bool, cx: &mut ViewContext<OutlineView>) {
+    fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<OutlineViewDelegate>>) {
         self.prev_scroll_position.take();
+
         self.active_editor.update(cx, |active_editor, cx| {
             if let Some(rows) = active_editor.highlighted_rows() {
                 let snapshot = active_editor.snapshot(cx).display_snapshot;
@@ -187,39 +241,69 @@ impl PickerDelegate for OutlineViewDelegate {
                     s.select_ranges([position..position])
                 });
                 active_editor.highlight_rows(None);
+                active_editor.focus(cx);
             }
         });
-        cx.emit(PickerEvent::Dismiss);
+
+        self.dismissed(cx);
     }
 
-    fn dismissed(&mut self, cx: &mut ViewContext<OutlineView>) {
+    fn dismissed(&mut self, cx: &mut ViewContext<Picker<OutlineViewDelegate>>) {
+        self.outline_view
+            .update(cx, |_, cx| cx.emit(DismissEvent))
+            .log_err();
         self.restore_active_editor(cx);
     }
 
     fn render_match(
         &self,
         ix: usize,
-        mouse_state: &mut MouseState,
         selected: bool,
-        cx: &AppContext,
-    ) -> AnyElement<Picker<Self>> {
-        let theme = theme::current(cx);
-        let style = theme.picker.item.in_state(selected).style_for(mouse_state);
-        let string_match = &self.matches[ix];
-        let outline_item = &self.outline.items[string_match.candidate_id];
-
-        Text::new(outline_item.text.clone(), style.label.text.clone())
-            .with_soft_wrap(false)
-            .with_highlights(combine_syntax_and_fuzzy_match_highlights(
-                &outline_item.text,
-                style.label.text.clone().into(),
-                outline_item.highlight_ranges.iter().cloned(),
-                &string_match.positions,
-            ))
-            .contained()
-            .with_padding_left(20. * outline_item.depth as f32)
-            .contained()
-            .with_style(style.container)
-            .into_any()
+        cx: &mut ViewContext<Picker<Self>>,
+    ) -> Option<Self::ListItem> {
+        let settings = ThemeSettings::get_global(cx);
+
+        // TODO: We probably shouldn't need to build a whole new text style here
+        // but I'm not sure how to get the current one and modify it.
+        // Before this change TextStyle::default() was used here, which was giving us the wrong font and text color.
+        let text_style = TextStyle {
+            color: cx.theme().colors().text,
+            font_family: settings.buffer_font.family.clone(),
+            font_features: settings.buffer_font.features,
+            font_size: settings.buffer_font_size(cx).into(),
+            font_weight: FontWeight::NORMAL,
+            font_style: FontStyle::Normal,
+            line_height: relative(1.).into(),
+            background_color: None,
+            underline: None,
+            white_space: WhiteSpace::Normal,
+        };
+
+        let mut highlight_style = HighlightStyle::default();
+        highlight_style.background_color = Some(color_alpha(cx.theme().colors().text_accent, 0.3));
+
+        let mat = &self.matches[ix];
+        let outline_item = &self.outline.items[mat.candidate_id];
+
+        let highlights = gpui::combine_highlights(
+            mat.ranges().map(|range| (range, highlight_style)),
+            outline_item.highlight_ranges.iter().cloned(),
+        );
+
+        let styled_text =
+            StyledText::new(outline_item.text.clone()).with_highlights(&text_style, highlights);
+
+        Some(
+            ListItem::new(ix)
+                .inset(true)
+                .spacing(ListItemSpacing::Sparse)
+                .selected(selected)
+                .child(
+                    div()
+                        .text_ui()
+                        .pl(rems(outline_item.depth as f32))
+                        .child(styled_text),
+                ),
+        )
     }
 }

crates/outline2/Cargo.toml 🔗

@@ -1,29 +0,0 @@
-[package]
-name = "outline2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/outline.rs"
-doctest = false
-
-[dependencies]
-editor = { package = "editor2", path = "../editor2" }
-fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-ui = { package = "ui2", path = "../ui2" }
-language = { package = "language2", path = "../language2" }
-picker = { package = "picker2", path = "../picker2" }
-settings = { package = "settings2", path = "../settings2" }
-text = { package = "text2", path = "../text2" }
-theme = { package = "theme2", path = "../theme2" }
-workspace = { package = "workspace2", path = "../workspace2" }
-util = { path = "../util" }
-
-ordered-float.workspace = true
-postage.workspace = true
-smol.workspace = true
-
-[dev-dependencies]
-editor = { package = "editor2", path = "../editor2", features = ["test-support"] }

crates/outline2/src/outline.rs 🔗

@@ -1,309 +0,0 @@
-use editor::{
-    display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Anchor, AnchorRangeExt,
-    DisplayPoint, Editor, EditorMode, ToPoint,
-};
-use fuzzy::StringMatch;
-use gpui::{
-    actions, div, rems, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView,
-    FontStyle, FontWeight, HighlightStyle, ParentElement, Point, Render, Styled, StyledText, Task,
-    TextStyle, View, ViewContext, VisualContext, WeakView, WhiteSpace, WindowContext,
-};
-use language::Outline;
-use ordered_float::OrderedFloat;
-use picker::{Picker, PickerDelegate};
-use settings::Settings;
-use std::{
-    cmp::{self, Reverse},
-    sync::Arc,
-};
-
-use theme::{color_alpha, ActiveTheme, ThemeSettings};
-use ui::{prelude::*, ListItem, ListItemSpacing};
-use util::ResultExt;
-use workspace::ModalView;
-
-actions!(outline, [Toggle]);
-
-pub fn init(cx: &mut AppContext) {
-    cx.observe_new_views(OutlineView::register).detach();
-}
-
-pub fn toggle(editor: View<Editor>, _: &Toggle, cx: &mut WindowContext) {
-    let outline = editor
-        .read(cx)
-        .buffer()
-        .read(cx)
-        .snapshot(cx)
-        .outline(Some(&cx.theme().syntax()));
-
-    if let Some((workspace, outline)) = editor.read(cx).workspace().zip(outline) {
-        workspace.update(cx, |workspace, cx| {
-            workspace.toggle_modal(cx, |cx| OutlineView::new(outline, editor, cx));
-        })
-    }
-}
-
-pub struct OutlineView {
-    picker: View<Picker<OutlineViewDelegate>>,
-}
-
-impl FocusableView for OutlineView {
-    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
-        self.picker.focus_handle(cx)
-    }
-}
-
-impl EventEmitter<DismissEvent> for OutlineView {}
-impl ModalView for OutlineView {}
-
-impl Render for OutlineView {
-    fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
-        v_stack().w(rems(34.)).child(self.picker.clone())
-    }
-}
-
-impl OutlineView {
-    fn register(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
-        if editor.mode() == EditorMode::Full {
-            let handle = cx.view().downgrade();
-            editor.register_action(move |action, cx| {
-                if let Some(editor) = handle.upgrade() {
-                    toggle(editor, action, cx);
-                }
-            });
-        }
-    }
-
-    fn new(
-        outline: Outline<Anchor>,
-        editor: View<Editor>,
-        cx: &mut ViewContext<Self>,
-    ) -> OutlineView {
-        let delegate = OutlineViewDelegate::new(cx.view().downgrade(), outline, editor, cx);
-        let picker = cx.new_view(|cx| Picker::new(delegate, cx).max_height(vh(0.75, cx)));
-        OutlineView { picker }
-    }
-}
-
-struct OutlineViewDelegate {
-    outline_view: WeakView<OutlineView>,
-    active_editor: View<Editor>,
-    outline: Outline<Anchor>,
-    selected_match_index: usize,
-    prev_scroll_position: Option<Point<f32>>,
-    matches: Vec<StringMatch>,
-    last_query: String,
-}
-
-impl OutlineViewDelegate {
-    fn new(
-        outline_view: WeakView<OutlineView>,
-        outline: Outline<Anchor>,
-        editor: View<Editor>,
-        cx: &mut ViewContext<OutlineView>,
-    ) -> Self {
-        Self {
-            outline_view,
-            last_query: Default::default(),
-            matches: Default::default(),
-            selected_match_index: 0,
-            prev_scroll_position: Some(editor.update(cx, |editor, cx| editor.scroll_position(cx))),
-            active_editor: editor,
-            outline,
-        }
-    }
-
-    fn restore_active_editor(&mut self, cx: &mut WindowContext) {
-        self.active_editor.update(cx, |editor, cx| {
-            editor.highlight_rows(None);
-            if let Some(scroll_position) = self.prev_scroll_position {
-                editor.set_scroll_position(scroll_position, cx);
-            }
-        })
-    }
-
-    fn set_selected_index(
-        &mut self,
-        ix: usize,
-        navigate: bool,
-        cx: &mut ViewContext<Picker<OutlineViewDelegate>>,
-    ) {
-        self.selected_match_index = ix;
-
-        if navigate && !self.matches.is_empty() {
-            let selected_match = &self.matches[self.selected_match_index];
-            let outline_item = &self.outline.items[selected_match.candidate_id];
-
-            self.active_editor.update(cx, |active_editor, cx| {
-                let snapshot = active_editor.snapshot(cx).display_snapshot;
-                let buffer_snapshot = &snapshot.buffer_snapshot;
-                let start = outline_item.range.start.to_point(buffer_snapshot);
-                let end = outline_item.range.end.to_point(buffer_snapshot);
-                let display_rows = start.to_display_point(&snapshot).row()
-                    ..end.to_display_point(&snapshot).row() + 1;
-                active_editor.highlight_rows(Some(display_rows));
-                active_editor.request_autoscroll(Autoscroll::center(), cx);
-            });
-        }
-    }
-}
-
-impl PickerDelegate for OutlineViewDelegate {
-    type ListItem = ListItem;
-
-    fn placeholder_text(&self) -> Arc<str> {
-        "Search buffer symbols...".into()
-    }
-
-    fn match_count(&self) -> usize {
-        self.matches.len()
-    }
-
-    fn selected_index(&self) -> usize {
-        self.selected_match_index
-    }
-
-    fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<OutlineViewDelegate>>) {
-        self.set_selected_index(ix, true, cx);
-    }
-
-    fn update_matches(
-        &mut self,
-        query: String,
-        cx: &mut ViewContext<Picker<OutlineViewDelegate>>,
-    ) -> Task<()> {
-        let selected_index;
-        if query.is_empty() {
-            self.restore_active_editor(cx);
-            self.matches = self
-                .outline
-                .items
-                .iter()
-                .enumerate()
-                .map(|(index, _)| StringMatch {
-                    candidate_id: index,
-                    score: Default::default(),
-                    positions: Default::default(),
-                    string: Default::default(),
-                })
-                .collect();
-
-            let editor = self.active_editor.read(cx);
-            let cursor_offset = editor.selections.newest::<usize>(cx).head();
-            let buffer = editor.buffer().read(cx).snapshot(cx);
-            selected_index = self
-                .outline
-                .items
-                .iter()
-                .enumerate()
-                .map(|(ix, item)| {
-                    let range = item.range.to_offset(&buffer);
-                    let distance_to_closest_endpoint = cmp::min(
-                        (range.start as isize - cursor_offset as isize).abs(),
-                        (range.end as isize - cursor_offset as isize).abs(),
-                    );
-                    let depth = if range.contains(&cursor_offset) {
-                        Some(item.depth)
-                    } else {
-                        None
-                    };
-                    (ix, depth, distance_to_closest_endpoint)
-                })
-                .max_by_key(|(_, depth, distance)| (*depth, Reverse(*distance)))
-                .map(|(ix, _, _)| ix)
-                .unwrap_or(0);
-        } else {
-            self.matches = smol::block_on(
-                self.outline
-                    .search(&query, cx.background_executor().clone()),
-            );
-            selected_index = self
-                .matches
-                .iter()
-                .enumerate()
-                .max_by_key(|(_, m)| OrderedFloat(m.score))
-                .map(|(ix, _)| ix)
-                .unwrap_or(0);
-        }
-        self.last_query = query;
-        self.set_selected_index(selected_index, !self.last_query.is_empty(), cx);
-        Task::ready(())
-    }
-
-    fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<OutlineViewDelegate>>) {
-        self.prev_scroll_position.take();
-
-        self.active_editor.update(cx, |active_editor, cx| {
-            if let Some(rows) = active_editor.highlighted_rows() {
-                let snapshot = active_editor.snapshot(cx).display_snapshot;
-                let position = DisplayPoint::new(rows.start, 0).to_point(&snapshot);
-                active_editor.change_selections(Some(Autoscroll::center()), cx, |s| {
-                    s.select_ranges([position..position])
-                });
-                active_editor.highlight_rows(None);
-                active_editor.focus(cx);
-            }
-        });
-
-        self.dismissed(cx);
-    }
-
-    fn dismissed(&mut self, cx: &mut ViewContext<Picker<OutlineViewDelegate>>) {
-        self.outline_view
-            .update(cx, |_, cx| cx.emit(DismissEvent))
-            .log_err();
-        self.restore_active_editor(cx);
-    }
-
-    fn render_match(
-        &self,
-        ix: usize,
-        selected: bool,
-        cx: &mut ViewContext<Picker<Self>>,
-    ) -> Option<Self::ListItem> {
-        let settings = ThemeSettings::get_global(cx);
-
-        // TODO: We probably shouldn't need to build a whole new text style here
-        // but I'm not sure how to get the current one and modify it.
-        // Before this change TextStyle::default() was used here, which was giving us the wrong font and text color.
-        let text_style = TextStyle {
-            color: cx.theme().colors().text,
-            font_family: settings.buffer_font.family.clone(),
-            font_features: settings.buffer_font.features,
-            font_size: settings.buffer_font_size(cx).into(),
-            font_weight: FontWeight::NORMAL,
-            font_style: FontStyle::Normal,
-            line_height: relative(1.).into(),
-            background_color: None,
-            underline: None,
-            white_space: WhiteSpace::Normal,
-        };
-
-        let mut highlight_style = HighlightStyle::default();
-        highlight_style.background_color = Some(color_alpha(cx.theme().colors().text_accent, 0.3));
-
-        let mat = &self.matches[ix];
-        let outline_item = &self.outline.items[mat.candidate_id];
-
-        let highlights = gpui::combine_highlights(
-            mat.ranges().map(|range| (range, highlight_style)),
-            outline_item.highlight_ranges.iter().cloned(),
-        );
-
-        let styled_text =
-            StyledText::new(outline_item.text.clone()).with_highlights(&text_style, highlights);
-
-        Some(
-            ListItem::new(ix)
-                .inset(true)
-                .spacing(ListItemSpacing::Sparse)
-                .selected(selected)
-                .child(
-                    div()
-                        .text_ui()
-                        .pl(rems(outline_item.depth as f32))
-                        .child(styled_text),
-                ),
-        )
-    }
-}

crates/picker/Cargo.toml 🔗

@@ -10,12 +10,13 @@ doctest = false
 
 [dependencies]
 editor = { path = "../editor" }
+ui = { path = "../ui" }
 gpui = { path = "../gpui" }
 menu = { path = "../menu" }
 settings = { path = "../settings" }
 util = { path = "../util" }
 theme = { path = "../theme" }
-workspace = { path = "../workspace" }
+workspace = { path = "../workspace"}
 
 parking_lot.workspace = true
 
@@ -23,6 +24,5 @@ parking_lot.workspace = true
 editor = { path = "../editor", features = ["test-support"] }
 gpui = { path = "../gpui", features = ["test-support"] }
 serde_json.workspace = true
-workspace = { path = "../workspace", features = ["test-support"] }
 ctor.workspace = true
 env_logger.workspace = true

crates/picker/src/picker.rs 🔗

@@ -1,267 +1,204 @@
 use editor::Editor;
 use gpui::{
-    elements::*,
-    geometry::vector::{vec2f, Vector2F},
-    keymap_matcher::KeymapContext,
-    platform::{CursorStyle, MouseButton},
-    AnyElement, AnyViewHandle, AppContext, Axis, Entity, MouseState, Task, View, ViewContext,
-    ViewHandle,
+    div, prelude::*, uniform_list, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle,
+    FocusableView, Length, MouseButton, MouseDownEvent, Render, Task, UniformListScrollHandle,
+    View, ViewContext, WindowContext,
 };
-use menu::{Cancel, Confirm, SecondaryConfirm, SelectFirst, SelectLast, SelectNext, SelectPrev};
-use parking_lot::Mutex;
 use std::{cmp, sync::Arc};
-use util::ResultExt;
-use workspace::Modal;
-
-#[derive(Clone, Copy)]
-pub enum PickerEvent {
-    Dismiss,
-}
+use ui::{prelude::*, v_stack, Color, Divider, Label, ListItem, ListItemSpacing, ListSeparator};
+use workspace::ModalView;
 
 pub struct Picker<D: PickerDelegate> {
-    delegate: D,
-    query_editor: ViewHandle<Editor>,
-    list_state: UniformListState,
-    max_size: Vector2F,
-    theme: Arc<Mutex<Box<dyn Fn(&theme::Theme) -> theme::Picker>>>,
-    confirmed: bool,
-    pending_update_matches: Option<Task<Option<()>>>,
+    pub delegate: D,
+    scroll_handle: UniformListScrollHandle,
+    editor: View<Editor>,
+    pending_update_matches: Option<Task<()>>,
     confirm_on_update: Option<bool>,
-    has_focus: bool,
+    width: Option<Length>,
+    max_height: Option<Length>,
+
+    /// Whether the `Picker` is rendered as a self-contained modal.
+    ///
+    /// Set this to `false` when rendering the `Picker` as part of a larger modal.
+    is_modal: bool,
 }
 
 pub trait PickerDelegate: Sized + 'static {
-    fn placeholder_text(&self) -> Arc<str>;
+    type ListItem: IntoElement;
     fn match_count(&self) -> usize;
     fn selected_index(&self) -> usize;
+    fn separators_after_indices(&self) -> Vec<usize> {
+        Vec::new()
+    }
     fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>);
+
+    fn placeholder_text(&self) -> Arc<str>;
     fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()>;
+
     fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<Picker<Self>>);
     fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>);
+
     fn render_match(
         &self,
         ix: usize,
-        state: &mut MouseState,
         selected: bool,
-        cx: &AppContext,
-    ) -> AnyElement<Picker<Self>>;
-    fn center_selection_after_match_updates(&self) -> bool {
-        false
-    }
-    fn render_header(
-        &self,
-        _cx: &mut ViewContext<Picker<Self>>,
-    ) -> Option<AnyElement<Picker<Self>>> {
+        cx: &mut ViewContext<Picker<Self>>,
+    ) -> Option<Self::ListItem>;
+    fn render_header(&self, _: &mut ViewContext<Picker<Self>>) -> Option<AnyElement> {
         None
     }
-    fn render_footer(
-        &self,
-        _cx: &mut ViewContext<Picker<Self>>,
-    ) -> Option<AnyElement<Picker<Self>>> {
+    fn render_footer(&self, _: &mut ViewContext<Picker<Self>>) -> Option<AnyElement> {
         None
     }
 }
 
-impl<D: PickerDelegate> Entity for Picker<D> {
-    type Event = PickerEvent;
-}
-
-impl<D: PickerDelegate> View for Picker<D> {
-    fn ui_name() -> &'static str {
-        "Picker"
+impl<D: PickerDelegate> FocusableView for Picker<D> {
+    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
+        self.editor.focus_handle(cx)
     }
+}
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = (self.theme.lock())(theme::current(cx).as_ref());
-        let query = self.query(cx);
-        let match_count = self.delegate.match_count();
-
-        let container_style;
-        let editor_style;
-        if query.is_empty() && match_count == 0 {
-            container_style = theme.empty_container;
-            editor_style = theme.empty_input_editor.container;
-        } else {
-            container_style = theme.container;
-            editor_style = theme.input_editor.container;
+impl<D: PickerDelegate> Picker<D> {
+    pub fn new(delegate: D, cx: &mut ViewContext<Self>) -> Self {
+        let editor = cx.new_view(|cx| {
+            let mut editor = Editor::single_line(cx);
+            editor.set_placeholder_text(delegate.placeholder_text(), cx);
+            editor
+        });
+        cx.subscribe(&editor, Self::on_input_editor_event).detach();
+        let mut this = Self {
+            delegate,
+            editor,
+            scroll_handle: UniformListScrollHandle::new(),
+            pending_update_matches: None,
+            confirm_on_update: None,
+            width: None,
+            max_height: None,
+            is_modal: true,
         };
-
-        Flex::new(Axis::Vertical)
-            .with_child(
-                ChildView::new(&self.query_editor, cx)
-                    .contained()
-                    .with_style(editor_style),
-            )
-            .with_children(self.delegate.render_header(cx))
-            .with_children(if match_count == 0 {
-                if query.is_empty() {
-                    None
-                } else {
-                    Some(
-                        Label::new("No matches", theme.no_matches.label.clone())
-                            .contained()
-                            .with_style(theme.no_matches.container)
-                            .into_any(),
-                    )
-                }
-            } else {
-                Some(
-                    UniformList::new(
-                        self.list_state.clone(),
-                        match_count,
-                        cx,
-                        move |this, mut range, items, cx| {
-                            let selected_ix = this.delegate.selected_index();
-                            range.end = cmp::min(range.end, this.delegate.match_count());
-                            items.extend(range.map(move |ix| {
-                                MouseEventHandler::new::<D, _>(ix, cx, |state, cx| {
-                                    this.delegate.render_match(ix, state, ix == selected_ix, cx)
-                                })
-                                // Capture mouse events
-                                .on_down(MouseButton::Left, |_, _, _| {})
-                                .on_up(MouseButton::Left, |_, _, _| {})
-                                .on_click(MouseButton::Left, move |click, picker, cx| {
-                                    picker.select_index(ix, click.cmd, cx);
-                                })
-                                .with_cursor_style(CursorStyle::PointingHand)
-                                .into_any()
-                            }));
-                        },
-                    )
-                    .contained()
-                    .with_margin_top(6.0)
-                    .flex(1., false)
-                    .into_any(),
-                )
-            })
-            .with_children(self.delegate.render_footer(cx))
-            .contained()
-            .with_style(container_style)
-            .constrained()
-            .with_max_width(self.max_size.x())
-            .with_max_height(self.max_size.y())
-            .into_any_named("picker")
+        this.update_matches("".to_string(), cx);
+        this
     }
 
-    fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
-        Self::reset_to_default_keymap_context(keymap);
-        keymap.add_identifier("menu");
+    pub fn width(mut self, width: impl Into<gpui::Length>) -> Self {
+        self.width = Some(width.into());
+        self
     }
 
-    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
-        self.has_focus = true;
-        if cx.is_self_focused() {
-            cx.focus(&self.query_editor);
-        }
+    pub fn max_height(mut self, max_height: impl Into<gpui::Length>) -> Self {
+        self.max_height = Some(max_height.into());
+        self
     }
 
-    fn focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext<Self>) {
-        self.has_focus = false;
+    pub fn modal(mut self, modal: bool) -> Self {
+        self.is_modal = modal;
+        self
     }
-}
 
-impl<D: PickerDelegate> Modal for Picker<D> {
-    fn has_focus(&self) -> bool {
-        self.has_focus
+    pub fn focus(&self, cx: &mut WindowContext) {
+        self.editor.update(cx, |editor, cx| editor.focus(cx));
     }
 
-    fn dismiss_on_event(event: &Self::Event) -> bool {
-        matches!(event, PickerEvent::Dismiss)
+    pub fn select_next(&mut self, _: &menu::SelectNext, cx: &mut ViewContext<Self>) {
+        let count = self.delegate.match_count();
+        if count > 0 {
+            let index = self.delegate.selected_index();
+            let ix = cmp::min(index + 1, count - 1);
+            self.delegate.set_selected_index(ix, cx);
+            self.scroll_handle.scroll_to_item(ix);
+            cx.notify();
+        }
     }
-}
 
-impl<D: PickerDelegate> Picker<D> {
-    pub fn init(cx: &mut AppContext) {
-        cx.add_action(Self::select_first);
-        cx.add_action(Self::select_last);
-        cx.add_action(Self::select_next);
-        cx.add_action(Self::select_prev);
-        cx.add_action(Self::confirm);
-        cx.add_action(Self::secondary_confirm);
-        cx.add_action(Self::cancel);
+    fn select_prev(&mut self, _: &menu::SelectPrev, cx: &mut ViewContext<Self>) {
+        let count = self.delegate.match_count();
+        if count > 0 {
+            let index = self.delegate.selected_index();
+            let ix = index.saturating_sub(1);
+            self.delegate.set_selected_index(ix, cx);
+            self.scroll_handle.scroll_to_item(ix);
+            cx.notify();
+        }
     }
 
-    pub fn new(delegate: D, cx: &mut ViewContext<Self>) -> Self {
-        let theme = Arc::new(Mutex::new(
-            Box::new(|theme: &theme::Theme| theme.picker.clone())
-                as Box<dyn Fn(&theme::Theme) -> theme::Picker>,
-        ));
-        let placeholder_text = delegate.placeholder_text();
-        let query_editor = cx.add_view({
-            let picker_theme = theme.clone();
-            |cx| {
-                let mut editor = Editor::single_line(
-                    Some(Arc::new(move |theme| {
-                        (picker_theme.lock())(theme).input_editor.clone()
-                    })),
-                    cx,
-                );
-                editor.set_placeholder_text(placeholder_text, cx);
-                editor
-            }
-        });
-        cx.subscribe(&query_editor, Self::on_query_editor_event)
-            .detach();
-        let mut this = Self {
-            query_editor,
-            list_state: Default::default(),
-            delegate,
-            max_size: vec2f(540., 420.),
-            theme,
-            confirmed: false,
-            pending_update_matches: None,
-            confirm_on_update: None,
-            has_focus: false,
-        };
-        this.update_matches(String::new(), cx);
-        this
+    fn select_first(&mut self, _: &menu::SelectFirst, cx: &mut ViewContext<Self>) {
+        let count = self.delegate.match_count();
+        if count > 0 {
+            self.delegate.set_selected_index(0, cx);
+            self.scroll_handle.scroll_to_item(0);
+            cx.notify();
+        }
     }
 
-    pub fn with_max_size(mut self, width: f32, height: f32) -> Self {
-        self.max_size = vec2f(width, height);
-        self
+    fn select_last(&mut self, _: &menu::SelectLast, cx: &mut ViewContext<Self>) {
+        let count = self.delegate.match_count();
+        if count > 0 {
+            self.delegate.set_selected_index(count - 1, cx);
+            self.scroll_handle.scroll_to_item(count - 1);
+            cx.notify();
+        }
     }
 
-    pub fn with_theme<F>(self, theme: F) -> Self
-    where
-        F: 'static + Fn(&theme::Theme) -> theme::Picker,
-    {
-        *self.theme.lock() = Box::new(theme);
-        self
+    pub fn cycle_selection(&mut self, cx: &mut ViewContext<Self>) {
+        let count = self.delegate.match_count();
+        let index = self.delegate.selected_index();
+        let new_index = if index + 1 == count { 0 } else { index + 1 };
+        self.delegate.set_selected_index(new_index, cx);
+        self.scroll_handle.scroll_to_item(new_index);
+        cx.notify();
     }
 
-    pub fn delegate(&self) -> &D {
-        &self.delegate
+    pub fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
+        self.delegate.dismissed(cx);
+        cx.emit(DismissEvent);
     }
 
-    pub fn delegate_mut(&mut self) -> &mut D {
-        &mut self.delegate
+    fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
+        if self.pending_update_matches.is_some() {
+            self.confirm_on_update = Some(false)
+        } else {
+            self.delegate.confirm(false, cx);
+        }
     }
 
-    pub fn query(&self, cx: &AppContext) -> String {
-        self.query_editor.read(cx).text(cx)
+    fn secondary_confirm(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
+        if self.pending_update_matches.is_some() {
+            self.confirm_on_update = Some(true)
+        } else {
+            self.delegate.confirm(true, cx);
+        }
     }
 
-    pub fn set_query(&self, query: impl Into<Arc<str>>, cx: &mut ViewContext<Self>) {
-        self.query_editor
-            .update(cx, |editor, cx| editor.set_text(query, cx));
+    fn handle_click(&mut self, ix: usize, secondary: bool, cx: &mut ViewContext<Self>) {
+        cx.stop_propagation();
+        cx.prevent_default();
+        self.delegate.set_selected_index(ix, cx);
+        self.delegate.confirm(secondary, cx);
     }
 
-    fn on_query_editor_event(
+    fn on_input_editor_event(
         &mut self,
-        _: ViewHandle<Editor>,
-        event: &editor::Event,
+        _: View<Editor>,
+        event: &editor::EditorEvent,
         cx: &mut ViewContext<Self>,
     ) {
         match event {
-            editor::Event::BufferEdited { .. } => self.update_matches(self.query(cx), cx),
-            editor::Event::Blurred if !self.confirmed => {
-                self.dismiss(cx);
+            editor::EditorEvent::BufferEdited => {
+                let query = self.editor.read(cx).text(cx);
+                self.update_matches(query, cx);
+            }
+            editor::EditorEvent::Blurred => {
+                self.cancel(&menu::Cancel, cx);
             }
             _ => {}
         }
     }
 
+    pub fn refresh(&mut self, cx: &mut ViewContext<Self>) {
+        let query = self.editor.read(cx).text(cx);
+        self.update_matches(query, cx);
+    }
+
     pub fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) {
         let update = self.delegate.update_matches(query, cx);
         self.matches_updated(cx);
@@ -270,99 +207,117 @@ impl<D: PickerDelegate> Picker<D> {
             this.update(&mut cx, |this, cx| {
                 this.matches_updated(cx);
             })
-            .log_err()
+            .ok();
         }));
     }
 
     fn matches_updated(&mut self, cx: &mut ViewContext<Self>) {
         let index = self.delegate.selected_index();
-        let target = if self.delegate.center_selection_after_match_updates() {
-            ScrollTarget::Center(index)
-        } else {
-            ScrollTarget::Show(index)
-        };
-        self.list_state.scroll_to(target);
+        self.scroll_handle.scroll_to_item(index);
         self.pending_update_matches = None;
         if let Some(secondary) = self.confirm_on_update.take() {
-            self.confirmed = true;
-            self.delegate.confirm(secondary, cx)
+            self.delegate.confirm(secondary, cx);
         }
         cx.notify();
     }
 
-    pub fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext<Self>) {
-        if self.delegate.match_count() > 0 {
-            self.delegate.set_selected_index(0, cx);
-            self.list_state.scroll_to(ScrollTarget::Show(0));
-        }
-
-        cx.notify();
-    }
-
-    pub fn select_index(&mut self, index: usize, cmd: bool, cx: &mut ViewContext<Self>) {
-        if self.delegate.match_count() > 0 {
-            self.confirmed = true;
-            self.delegate.set_selected_index(index, cx);
-            self.delegate.confirm(cmd, cx);
-        }
-    }
-
-    pub fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext<Self>) {
-        let match_count = self.delegate.match_count();
-        if match_count > 0 {
-            let index = match_count - 1;
-            self.delegate.set_selected_index(index, cx);
-            self.list_state.scroll_to(ScrollTarget::Show(index));
-        }
-        cx.notify();
-    }
-
-    pub fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
-        let next_index = self.delegate.selected_index() + 1;
-        if next_index < self.delegate.match_count() {
-            self.delegate.set_selected_index(next_index, cx);
-            self.list_state.scroll_to(ScrollTarget::Show(next_index));
-        }
-
-        cx.notify();
-    }
-
-    pub fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
-        let mut selected_index = self.delegate.selected_index();
-        if selected_index > 0 {
-            selected_index -= 1;
-            self.delegate.set_selected_index(selected_index, cx);
-            self.list_state
-                .scroll_to(ScrollTarget::Show(selected_index));
-        }
-
-        cx.notify();
-    }
-
-    pub fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
-        if self.pending_update_matches.is_some() {
-            self.confirm_on_update = Some(false)
-        } else {
-            self.confirmed = true;
-            self.delegate.confirm(false, cx);
-        }
+    pub fn query(&self, cx: &AppContext) -> String {
+        self.editor.read(cx).text(cx)
     }
 
-    pub fn secondary_confirm(&mut self, _: &SecondaryConfirm, cx: &mut ViewContext<Self>) {
-        if self.pending_update_matches.is_some() {
-            self.confirm_on_update = Some(true)
-        } else {
-            self.confirmed = true;
-            self.delegate.confirm(true, cx);
-        }
+    pub fn set_query(&self, query: impl Into<Arc<str>>, cx: &mut ViewContext<Self>) {
+        self.editor
+            .update(cx, |editor, cx| editor.set_text(query, cx));
     }
+}
 
-    fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
-        self.dismiss(cx);
-    }
+impl<D: PickerDelegate> EventEmitter<DismissEvent> for Picker<D> {}
+impl<D: PickerDelegate> ModalView for Picker<D> {}
+
+impl<D: PickerDelegate> Render for Picker<D> {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        let picker_editor = h_stack()
+            .overflow_hidden()
+            .flex_none()
+            .h_9()
+            .px_4()
+            .child(self.editor.clone());
+
+        div()
+            .key_context("Picker")
+            .size_full()
+            .when_some(self.width, |el, width| el.w(width))
+            .overflow_hidden()
+            // This is a bit of a hack to remove the modal styling when we're rendering the `Picker`
+            // as a part of a modal rather than the entire modal.
+            //
+            // We should revisit how the `Picker` is styled to make it more composable.
+            .when(self.is_modal, |this| this.elevation_3(cx))
+            .on_action(cx.listener(Self::select_next))
+            .on_action(cx.listener(Self::select_prev))
+            .on_action(cx.listener(Self::select_first))
+            .on_action(cx.listener(Self::select_last))
+            .on_action(cx.listener(Self::cancel))
+            .on_action(cx.listener(Self::confirm))
+            .on_action(cx.listener(Self::secondary_confirm))
+            .child(picker_editor)
+            .child(Divider::horizontal())
+            .when(self.delegate.match_count() > 0, |el| {
+                el.child(
+                    v_stack()
+                        .flex_grow()
+                        .py_2()
+                        .max_h(self.max_height.unwrap_or(rems(18.).into()))
+                        .overflow_hidden()
+                        .children(self.delegate.render_header(cx))
+                        .child(
+                            uniform_list(
+                                cx.view().clone(),
+                                "candidates",
+                                self.delegate.match_count(),
+                                {
+                                    let separators_after_indices = self.delegate.separators_after_indices();
+                                    let selected_index = self.delegate.selected_index();
+                                    move |picker, visible_range, cx| {
+                                        visible_range
+                                            .map(|ix| {
+                                                div()
+                                                    .on_mouse_down(
+                                                        MouseButton::Left,
+                                                        cx.listener(move |this, event: &MouseDownEvent, cx| {
+                                                            this.handle_click(
+                                                                ix,
+                                                                event.modifiers.command,
+                                                                cx,
+                                                            )
+                                                        }),
+                                                    )
+                                                    .children(picker.delegate.render_match(
+                                                        ix,
+                                                        ix == selected_index,
+                                                        cx,
+                                                    )).when(separators_after_indices.contains(&ix), |picker| picker.child(ListSeparator))
+                                            })
+                                            .collect()
+                                    }
+                                },
+                            )
+                            .track_scroll(self.scroll_handle.clone())
+                        )
 
-    fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
-        cx.emit(PickerEvent::Dismiss);
-        self.delegate.dismissed(cx);
+                )
+            })
+            .when(self.delegate.match_count() == 0, |el| {
+                el.child(
+                    v_stack().flex_grow().py_2().child(
+                        ListItem::new("empty_state")
+                            .inset(true)
+                            .spacing(ListItemSpacing::Sparse)
+                            .disabled(true)
+                            .child(Label::new("No matches").color(Color::Muted)),
+                    ),
+                )
+            })
+            .children(self.delegate.render_footer(cx))
     }
 }

crates/picker2/Cargo.toml 🔗

@@ -1,28 +0,0 @@
-[package]
-name = "picker2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/picker2.rs"
-doctest = false
-
-[dependencies]
-editor = { package = "editor2", path = "../editor2" }
-ui = { package = "ui2", path = "../ui2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-menu = { package = "menu2", path = "../menu2" }
-settings = { package = "settings2", path = "../settings2" }
-util = { path = "../util" }
-theme = { package = "theme2", path = "../theme2" }
-workspace = { package = "workspace2", path = "../workspace2"}
-
-parking_lot.workspace = true
-
-[dev-dependencies]
-editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-serde_json.workspace = true
-ctor.workspace = true
-env_logger.workspace = true

crates/picker2/src/picker2.rs 🔗

@@ -1,323 +0,0 @@
-use editor::Editor;
-use gpui::{
-    div, prelude::*, uniform_list, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle,
-    FocusableView, Length, MouseButton, MouseDownEvent, Render, Task, UniformListScrollHandle,
-    View, ViewContext, WindowContext,
-};
-use std::{cmp, sync::Arc};
-use ui::{prelude::*, v_stack, Color, Divider, Label, ListItem, ListItemSpacing, ListSeparator};
-use workspace::ModalView;
-
-pub struct Picker<D: PickerDelegate> {
-    pub delegate: D,
-    scroll_handle: UniformListScrollHandle,
-    editor: View<Editor>,
-    pending_update_matches: Option<Task<()>>,
-    confirm_on_update: Option<bool>,
-    width: Option<Length>,
-    max_height: Option<Length>,
-
-    /// Whether the `Picker` is rendered as a self-contained modal.
-    ///
-    /// Set this to `false` when rendering the `Picker` as part of a larger modal.
-    is_modal: bool,
-}
-
-pub trait PickerDelegate: Sized + 'static {
-    type ListItem: IntoElement;
-    fn match_count(&self) -> usize;
-    fn selected_index(&self) -> usize;
-    fn separators_after_indices(&self) -> Vec<usize> {
-        Vec::new()
-    }
-    fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>);
-
-    fn placeholder_text(&self) -> Arc<str>;
-    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()>;
-
-    fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<Picker<Self>>);
-    fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>);
-
-    fn render_match(
-        &self,
-        ix: usize,
-        selected: bool,
-        cx: &mut ViewContext<Picker<Self>>,
-    ) -> Option<Self::ListItem>;
-    fn render_header(&self, _: &mut ViewContext<Picker<Self>>) -> Option<AnyElement> {
-        None
-    }
-    fn render_footer(&self, _: &mut ViewContext<Picker<Self>>) -> Option<AnyElement> {
-        None
-    }
-}
-
-impl<D: PickerDelegate> FocusableView for Picker<D> {
-    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
-        self.editor.focus_handle(cx)
-    }
-}
-
-impl<D: PickerDelegate> Picker<D> {
-    pub fn new(delegate: D, cx: &mut ViewContext<Self>) -> Self {
-        let editor = cx.new_view(|cx| {
-            let mut editor = Editor::single_line(cx);
-            editor.set_placeholder_text(delegate.placeholder_text(), cx);
-            editor
-        });
-        cx.subscribe(&editor, Self::on_input_editor_event).detach();
-        let mut this = Self {
-            delegate,
-            editor,
-            scroll_handle: UniformListScrollHandle::new(),
-            pending_update_matches: None,
-            confirm_on_update: None,
-            width: None,
-            max_height: None,
-            is_modal: true,
-        };
-        this.update_matches("".to_string(), cx);
-        this
-    }
-
-    pub fn width(mut self, width: impl Into<gpui::Length>) -> Self {
-        self.width = Some(width.into());
-        self
-    }
-
-    pub fn max_height(mut self, max_height: impl Into<gpui::Length>) -> Self {
-        self.max_height = Some(max_height.into());
-        self
-    }
-
-    pub fn modal(mut self, modal: bool) -> Self {
-        self.is_modal = modal;
-        self
-    }
-
-    pub fn focus(&self, cx: &mut WindowContext) {
-        self.editor.update(cx, |editor, cx| editor.focus(cx));
-    }
-
-    pub fn select_next(&mut self, _: &menu::SelectNext, cx: &mut ViewContext<Self>) {
-        let count = self.delegate.match_count();
-        if count > 0 {
-            let index = self.delegate.selected_index();
-            let ix = cmp::min(index + 1, count - 1);
-            self.delegate.set_selected_index(ix, cx);
-            self.scroll_handle.scroll_to_item(ix);
-            cx.notify();
-        }
-    }
-
-    fn select_prev(&mut self, _: &menu::SelectPrev, cx: &mut ViewContext<Self>) {
-        let count = self.delegate.match_count();
-        if count > 0 {
-            let index = self.delegate.selected_index();
-            let ix = index.saturating_sub(1);
-            self.delegate.set_selected_index(ix, cx);
-            self.scroll_handle.scroll_to_item(ix);
-            cx.notify();
-        }
-    }
-
-    fn select_first(&mut self, _: &menu::SelectFirst, cx: &mut ViewContext<Self>) {
-        let count = self.delegate.match_count();
-        if count > 0 {
-            self.delegate.set_selected_index(0, cx);
-            self.scroll_handle.scroll_to_item(0);
-            cx.notify();
-        }
-    }
-
-    fn select_last(&mut self, _: &menu::SelectLast, cx: &mut ViewContext<Self>) {
-        let count = self.delegate.match_count();
-        if count > 0 {
-            self.delegate.set_selected_index(count - 1, cx);
-            self.scroll_handle.scroll_to_item(count - 1);
-            cx.notify();
-        }
-    }
-
-    pub fn cycle_selection(&mut self, cx: &mut ViewContext<Self>) {
-        let count = self.delegate.match_count();
-        let index = self.delegate.selected_index();
-        let new_index = if index + 1 == count { 0 } else { index + 1 };
-        self.delegate.set_selected_index(new_index, cx);
-        self.scroll_handle.scroll_to_item(new_index);
-        cx.notify();
-    }
-
-    pub fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
-        self.delegate.dismissed(cx);
-        cx.emit(DismissEvent);
-    }
-
-    fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
-        if self.pending_update_matches.is_some() {
-            self.confirm_on_update = Some(false)
-        } else {
-            self.delegate.confirm(false, cx);
-        }
-    }
-
-    fn secondary_confirm(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
-        if self.pending_update_matches.is_some() {
-            self.confirm_on_update = Some(true)
-        } else {
-            self.delegate.confirm(true, cx);
-        }
-    }
-
-    fn handle_click(&mut self, ix: usize, secondary: bool, cx: &mut ViewContext<Self>) {
-        cx.stop_propagation();
-        cx.prevent_default();
-        self.delegate.set_selected_index(ix, cx);
-        self.delegate.confirm(secondary, cx);
-    }
-
-    fn on_input_editor_event(
-        &mut self,
-        _: View<Editor>,
-        event: &editor::EditorEvent,
-        cx: &mut ViewContext<Self>,
-    ) {
-        match event {
-            editor::EditorEvent::BufferEdited => {
-                let query = self.editor.read(cx).text(cx);
-                self.update_matches(query, cx);
-            }
-            editor::EditorEvent::Blurred => {
-                self.cancel(&menu::Cancel, cx);
-            }
-            _ => {}
-        }
-    }
-
-    pub fn refresh(&mut self, cx: &mut ViewContext<Self>) {
-        let query = self.editor.read(cx).text(cx);
-        self.update_matches(query, cx);
-    }
-
-    pub fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) {
-        let update = self.delegate.update_matches(query, cx);
-        self.matches_updated(cx);
-        self.pending_update_matches = Some(cx.spawn(|this, mut cx| async move {
-            update.await;
-            this.update(&mut cx, |this, cx| {
-                this.matches_updated(cx);
-            })
-            .ok();
-        }));
-    }
-
-    fn matches_updated(&mut self, cx: &mut ViewContext<Self>) {
-        let index = self.delegate.selected_index();
-        self.scroll_handle.scroll_to_item(index);
-        self.pending_update_matches = None;
-        if let Some(secondary) = self.confirm_on_update.take() {
-            self.delegate.confirm(secondary, cx);
-        }
-        cx.notify();
-    }
-
-    pub fn query(&self, cx: &AppContext) -> String {
-        self.editor.read(cx).text(cx)
-    }
-
-    pub fn set_query(&self, query: impl Into<Arc<str>>, cx: &mut ViewContext<Self>) {
-        self.editor
-            .update(cx, |editor, cx| editor.set_text(query, cx));
-    }
-}
-
-impl<D: PickerDelegate> EventEmitter<DismissEvent> for Picker<D> {}
-impl<D: PickerDelegate> ModalView for Picker<D> {}
-
-impl<D: PickerDelegate> Render for Picker<D> {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        let picker_editor = h_stack()
-            .overflow_hidden()
-            .flex_none()
-            .h_9()
-            .px_4()
-            .child(self.editor.clone());
-
-        div()
-            .key_context("Picker")
-            .size_full()
-            .when_some(self.width, |el, width| el.w(width))
-            .overflow_hidden()
-            // This is a bit of a hack to remove the modal styling when we're rendering the `Picker`
-            // as a part of a modal rather than the entire modal.
-            //
-            // We should revisit how the `Picker` is styled to make it more composable.
-            .when(self.is_modal, |this| this.elevation_3(cx))
-            .on_action(cx.listener(Self::select_next))
-            .on_action(cx.listener(Self::select_prev))
-            .on_action(cx.listener(Self::select_first))
-            .on_action(cx.listener(Self::select_last))
-            .on_action(cx.listener(Self::cancel))
-            .on_action(cx.listener(Self::confirm))
-            .on_action(cx.listener(Self::secondary_confirm))
-            .child(picker_editor)
-            .child(Divider::horizontal())
-            .when(self.delegate.match_count() > 0, |el| {
-                el.child(
-                    v_stack()
-                        .flex_grow()
-                        .py_2()
-                        .max_h(self.max_height.unwrap_or(rems(18.).into()))
-                        .overflow_hidden()
-                        .children(self.delegate.render_header(cx))
-                        .child(
-                            uniform_list(
-                                cx.view().clone(),
-                                "candidates",
-                                self.delegate.match_count(),
-                                {
-                                    let separators_after_indices = self.delegate.separators_after_indices();
-                                    let selected_index = self.delegate.selected_index();
-                                    move |picker, visible_range, cx| {
-                                        visible_range
-                                            .map(|ix| {
-                                                div()
-                                                    .on_mouse_down(
-                                                        MouseButton::Left,
-                                                        cx.listener(move |this, event: &MouseDownEvent, cx| {
-                                                            this.handle_click(
-                                                                ix,
-                                                                event.modifiers.command,
-                                                                cx,
-                                                            )
-                                                        }),
-                                                    )
-                                                    .children(picker.delegate.render_match(
-                                                        ix,
-                                                        ix == selected_index,
-                                                        cx,
-                                                    )).when(separators_after_indices.contains(&ix), |picker| picker.child(ListSeparator))
-                                            })
-                                            .collect()
-                                    }
-                                },
-                            )
-                            .track_scroll(self.scroll_handle.clone())
-                        )
-
-                )
-            })
-            .when(self.delegate.match_count() == 0, |el| {
-                el.child(
-                    v_stack().flex_grow().py_2().child(
-                        ListItem::new("empty_state")
-                            .inset(true)
-                            .spacing(ListItemSpacing::Sparse)
-                            .disabled(true)
-                            .child(Label::new("No matches").color(Color::Muted)),
-                    ),
-                )
-            })
-            .children(self.delegate.render_footer(cx))
-    }
-}

crates/prettier/src/prettier.rs 🔗

@@ -1,16 +1,16 @@
-use std::ops::ControlFlow;
-use std::path::{Path, PathBuf};
-use std::sync::Arc;
-
 use anyhow::Context;
 use collections::{HashMap, HashSet};
 use fs::Fs;
-use gpui::{AsyncAppContext, ModelHandle};
-use language::language_settings::language_settings;
-use language::{Buffer, Diff};
+use gpui::{AsyncAppContext, Model};
+use language::{language_settings::language_settings, Buffer, Diff};
 use lsp::{LanguageServer, LanguageServerId};
 use node_runtime::NodeRuntime;
 use serde::{Deserialize, Serialize};
+use std::{
+    ops::ControlFlow,
+    path::{Path, PathBuf},
+    sync::Arc,
+};
 use util::paths::{PathMatcher, DEFAULT_PRETTIER_DIR};
 
 #[derive(Clone)]
@@ -100,39 +100,39 @@ impl Prettier {
                     }
                 } else {
                     match package_json_contents.get("workspaces") {
-                        Some(serde_json::Value::Array(workspaces)) => {
-                            match &project_path_with_prettier_dependency {
-                                Some(project_path_with_prettier_dependency) => {
-                                    let subproject_path = project_path_with_prettier_dependency.strip_prefix(&path_to_check).expect("traversing path parents, should be able to strip prefix");
-                                    if workspaces.iter().filter_map(|value| {
-                                        if let serde_json::Value::String(s) = value {
-                                            Some(s.clone())
-                                        } else {
-                                            log::warn!("Skipping non-string 'workspaces' value: {value:?}");
-                                            None
-                                        }
-                                    }).any(|workspace_definition| {
-                                        if let Some(path_matcher) = PathMatcher::new(&workspace_definition).ok() {
-                                            path_matcher.is_match(subproject_path)
+                            Some(serde_json::Value::Array(workspaces)) => {
+                                match &project_path_with_prettier_dependency {
+                                    Some(project_path_with_prettier_dependency) => {
+                                        let subproject_path = project_path_with_prettier_dependency.strip_prefix(&path_to_check).expect("traversing path parents, should be able to strip prefix");
+                                        if workspaces.iter().filter_map(|value| {
+                                            if let serde_json::Value::String(s) = value {
+                                                Some(s.clone())
+                                            } else {
+                                                log::warn!("Skipping non-string 'workspaces' value: {value:?}");
+                                                None
+                                            }
+                                        }).any(|workspace_definition| {
+                                            if let Some(path_matcher) = PathMatcher::new(&workspace_definition).ok() {
+                                                path_matcher.is_match(subproject_path)
+                                            } else {
+                                                workspace_definition == subproject_path.to_string_lossy()
+                                            }
+                                        }) {
+                                            anyhow::ensure!(has_prettier_in_node_modules(fs, &path_to_check).await?, "Found prettier path {path_to_check:?} in the workspace root for project in {project_path_with_prettier_dependency:?}, but it's not installed into workspace root's node_modules");
+                                            log::info!("Found prettier path {path_to_check:?} in the workspace root for project in {project_path_with_prettier_dependency:?}");
+                                            return Ok(ControlFlow::Continue(Some(path_to_check)));
                                         } else {
-                                            workspace_definition == subproject_path.to_string_lossy()
+                                            log::warn!("Skipping path {path_to_check:?} that has prettier in its 'node_modules' subdirectory, but is not included in its package.json workspaces {workspaces:?}");
                                         }
-                                    }) {
-                                        anyhow::ensure!(has_prettier_in_node_modules(fs, &path_to_check).await?, "Found prettier path {path_to_check:?} in the workspace root for project in {project_path_with_prettier_dependency:?}, but it's not installed into workspace root's node_modules");
-                                        log::info!("Found prettier path {path_to_check:?} in the workspace root for project in {project_path_with_prettier_dependency:?}");
-                                        return Ok(ControlFlow::Continue(Some(path_to_check)));
-                                    } else {
-                                        log::warn!("Skipping path {path_to_check:?} that has prettier in its 'node_modules' subdirectory, but is not included in its package.json workspaces {workspaces:?}");
+                                    }
+                                    None => {
+                                        log::warn!("Skipping path {path_to_check:?} that has prettier in its 'node_modules' subdirectory, but has no prettier in its package.json");
                                     }
                                 }
-                                None => {
-                                    log::warn!("Skipping path {path_to_check:?} that has prettier in its 'node_modules' subdirectory, but has no prettier in its package.json");
-                                }
-                            }
-                        },
-                        Some(unknown) => log::error!("Failed to parse workspaces for {path_to_check:?} from package.json, got {unknown:?}. Skipping."),
-                        None => log::warn!("Skipping path {path_to_check:?} that has no prettier dependency and no workspaces section in its package.json"),
-                    }
+                            },
+                            Some(unknown) => log::error!("Failed to parse workspaces for {path_to_check:?} from package.json, got {unknown:?}. Skipping."),
+                            None => log::warn!("Skipping path {path_to_check:?} that has no prettier dependency and no workspaces section in its package.json"),
+                        }
                 }
             }
 
@@ -172,7 +172,7 @@ impl Prettier {
     ) -> anyhow::Result<Self> {
         use lsp::LanguageServerBinary;
 
-        let background = cx.background();
+        let executor = cx.background_executor().clone();
         anyhow::ensure!(
             prettier_dir.is_dir(),
             "Prettier dir {prettier_dir:?} is not a directory"
@@ -183,7 +183,7 @@ impl Prettier {
             "no prettier server package found at {prettier_server:?}"
         );
 
-        let node_path = background
+        let node_path = executor
             .spawn(async move { node.binary_path().await })
             .await?;
         let server = LanguageServer::new(
@@ -198,7 +198,7 @@ impl Prettier {
             cx,
         )
         .context("prettier server creation")?;
-        let server = background
+        let server = executor
             .spawn(server.initialize(None))
             .await
             .context("prettier server initialization")?;
@@ -211,124 +211,154 @@ impl Prettier {
 
     pub async fn format(
         &self,
-        buffer: &ModelHandle<Buffer>,
+        buffer: &Model<Buffer>,
         buffer_path: Option<PathBuf>,
-        cx: &AsyncAppContext,
+        cx: &mut AsyncAppContext,
     ) -> anyhow::Result<Diff> {
         match self {
             Self::Real(local) => {
-                let params = buffer.read_with(cx, |buffer, cx| {
-                    let buffer_language = buffer.language();
-                    let parser_with_plugins = buffer_language.and_then(|l| {
-                        let prettier_parser = l.prettier_parser_name()?;
-                        let mut prettier_plugins = l
-                            .lsp_adapters()
-                            .iter()
-                            .flat_map(|adapter| adapter.prettier_plugins())
-                            .collect::<Vec<_>>();
-                        prettier_plugins.dedup();
-                        Some((prettier_parser, prettier_plugins))
-                    });
-
-                    let prettier_node_modules = self.prettier_dir().join("node_modules");
-                    anyhow::ensure!(prettier_node_modules.is_dir(), "Prettier node_modules dir does not exist: {prettier_node_modules:?}");
-                    let plugin_name_into_path = |plugin_name: &str| {
-                        let prettier_plugin_dir = prettier_node_modules.join(plugin_name);
-                        for possible_plugin_path in [
-                            prettier_plugin_dir.join("dist").join("index.mjs"),
-                            prettier_plugin_dir.join("dist").join("index.js"),
-                            prettier_plugin_dir.join("dist").join("plugin.js"),
-                            prettier_plugin_dir.join("index.mjs"),
-                            prettier_plugin_dir.join("index.js"),
-                            prettier_plugin_dir.join("plugin.js"),
-                            prettier_plugin_dir,
-                        ] {
-                            if possible_plugin_path.is_file() {
-                                return Some(possible_plugin_path);
+                let params = buffer
+                    .update(cx, |buffer, cx| {
+                        let buffer_language = buffer.language();
+                        let parser_with_plugins = buffer_language.and_then(|l| {
+                            let prettier_parser = l.prettier_parser_name()?;
+                            let mut prettier_plugins = l
+                                .lsp_adapters()
+                                .iter()
+                                .flat_map(|adapter| adapter.prettier_plugins())
+                                .collect::<Vec<_>>();
+                            prettier_plugins.dedup();
+                            Some((prettier_parser, prettier_plugins))
+                        });
+
+                        let prettier_node_modules = self.prettier_dir().join("node_modules");
+                        anyhow::ensure!(
+                            prettier_node_modules.is_dir(),
+                            "Prettier node_modules dir does not exist: {prettier_node_modules:?}"
+                        );
+                        let plugin_name_into_path = |plugin_name: &str| {
+                            let prettier_plugin_dir = prettier_node_modules.join(plugin_name);
+                            for possible_plugin_path in [
+                                prettier_plugin_dir.join("dist").join("index.mjs"),
+                                prettier_plugin_dir.join("dist").join("index.js"),
+                                prettier_plugin_dir.join("dist").join("plugin.js"),
+                                prettier_plugin_dir.join("index.mjs"),
+                                prettier_plugin_dir.join("index.js"),
+                                prettier_plugin_dir.join("plugin.js"),
+                                prettier_plugin_dir,
+                            ] {
+                                if possible_plugin_path.is_file() {
+                                    return Some(possible_plugin_path);
+                                }
                             }
-                        }
-                        None
-                    };
-                    let (parser, located_plugins) = match parser_with_plugins {
-                        Some((parser, plugins)) => {
-                            // Tailwind plugin requires being added last
-                            // https://github.com/tailwindlabs/prettier-plugin-tailwindcss#compatibility-with-other-prettier-plugins
-                            let mut add_tailwind_back = false;
-
-                            let mut plugins = plugins.into_iter().filter(|&&plugin_name| {
-                                if plugin_name == TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME {
-                                    add_tailwind_back = true;
-                                    false
-                                } else {
-                                    true
+                            None
+                        };
+                        let (parser, located_plugins) = match parser_with_plugins {
+                            Some((parser, plugins)) => {
+                                // Tailwind plugin requires being added last
+                                // https://github.com/tailwindlabs/prettier-plugin-tailwindcss#compatibility-with-other-prettier-plugins
+                                let mut add_tailwind_back = false;
+
+                                let mut plugins = plugins
+                                    .into_iter()
+                                    .filter(|&&plugin_name| {
+                                        if plugin_name == TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME {
+                                            add_tailwind_back = true;
+                                            false
+                                        } else {
+                                            true
+                                        }
+                                    })
+                                    .map(|plugin_name| {
+                                        (plugin_name, plugin_name_into_path(plugin_name))
+                                    })
+                                    .collect::<Vec<_>>();
+                                if add_tailwind_back {
+                                    plugins.push((
+                                        &TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME,
+                                        plugin_name_into_path(
+                                            TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME,
+                                        ),
+                                    ));
                                 }
-                            }).map(|plugin_name| (plugin_name, plugin_name_into_path(plugin_name))).collect::<Vec<_>>();
-                            if add_tailwind_back {
-                                plugins.push((&TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME, plugin_name_into_path(TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME)));
+                                (Some(parser.to_string()), plugins)
                             }
-                            (Some(parser.to_string()), plugins)
-                        },
-                        None => (None, Vec::new()),
-                    };
-
-                    let prettier_options = if self.is_default() {
-                        let language_settings = language_settings(buffer_language, buffer.file(), cx);
-                        let mut options = language_settings.prettier.clone();
-                        if !options.contains_key("tabWidth") {
-                            options.insert(
-                                "tabWidth".to_string(),
-                                serde_json::Value::Number(serde_json::Number::from(
-                                    language_settings.tab_size.get(),
-                                )),
-                            );
-                        }
-                        if !options.contains_key("printWidth") {
-                            options.insert(
-                                "printWidth".to_string(),
-                                serde_json::Value::Number(serde_json::Number::from(
-                                    language_settings.preferred_line_length,
-                                )),
-                            );
-                        }
-                        Some(options)
-                    } else {
-                        None
-                    };
-
-                    let plugins = located_plugins.into_iter().filter_map(|(plugin_name, located_plugin_path)| {
-                        match located_plugin_path {
-                            Some(path) => Some(path),
-                            None => {
-                                log::error!("Have not found plugin path for {plugin_name:?} inside {prettier_node_modules:?}");
-                                None},
-                        }
-                    }).collect();
-                    log::debug!("Formatting file {:?} with prettier, plugins :{plugins:?}, options: {prettier_options:?}", buffer.file().map(|f| f.full_path(cx)));
-
-                    anyhow::Ok(FormatParams {
-                        text: buffer.text(),
-                        options: FormatOptions {
-                            parser,
+                            None => (None, Vec::new()),
+                        };
+
+                        let prettier_options = if self.is_default() {
+                            let language_settings =
+                                language_settings(buffer_language, buffer.file(), cx);
+                            let mut options = language_settings.prettier.clone();
+                            if !options.contains_key("tabWidth") {
+                                options.insert(
+                                    "tabWidth".to_string(),
+                                    serde_json::Value::Number(serde_json::Number::from(
+                                        language_settings.tab_size.get(),
+                                    )),
+                                );
+                            }
+                            if !options.contains_key("printWidth") {
+                                options.insert(
+                                    "printWidth".to_string(),
+                                    serde_json::Value::Number(serde_json::Number::from(
+                                        language_settings.preferred_line_length,
+                                    )),
+                                );
+                            }
+                            Some(options)
+                        } else {
+                            None
+                        };
+
+                        let plugins = located_plugins
+                            .into_iter()
+                            .filter_map(|(plugin_name, located_plugin_path)| {
+                                match located_plugin_path {
+                                    Some(path) => Some(path),
+                                    None => {
+                                        log::error!(
+                                            "Have not found plugin path for {:?} inside {:?}",
+                                            plugin_name,
+                                            prettier_node_modules
+                                        );
+                                        None
+                                    }
+                                }
+                            })
+                            .collect();
+                        log::debug!(
+                            "Formatting file {:?} with prettier, plugins :{:?}, options: {:?}",
                             plugins,
-                            path: buffer_path,
                             prettier_options,
-                        },
-                    })
-                }).context("prettier params calculation")?;
+                            buffer.file().map(|f| f.full_path(cx))
+                        );
+
+                        anyhow::Ok(FormatParams {
+                            text: buffer.text(),
+                            options: FormatOptions {
+                                parser,
+                                plugins,
+                                path: buffer_path,
+                                prettier_options,
+                            },
+                        })
+                    })?
+                    .context("prettier params calculation")?;
                 let response = local
                     .server
                     .request::<Format>(params)
                     .await
                     .context("prettier format request")?;
-                let diff_task = buffer.read_with(cx, |buffer, cx| buffer.diff(response.text, cx));
+                let diff_task = buffer.update(cx, |buffer, cx| buffer.diff(response.text, cx))?;
                 Ok(diff_task.await)
             }
             #[cfg(any(test, feature = "test-support"))]
             Self::Test(_) => Ok(buffer
-                .read_with(cx, |buffer, cx| {
+                .update(cx, |buffer, cx| {
                     let formatted_text = buffer.text() + FORMAT_SUFFIX;
                     buffer.diff(formatted_text, cx)
-                })
+                })?
                 .await),
         }
     }
@@ -471,7 +501,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_prettier_lookup_finds_nothing(cx: &mut gpui::TestAppContext) {
-        let fs = FakeFs::new(cx.background());
+        let fs = FakeFs::new(cx.executor());
         fs.insert_tree(
             "/root",
             json!({
@@ -547,7 +577,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_prettier_lookup_in_simple_npm_projects(cx: &mut gpui::TestAppContext) {
-        let fs = FakeFs::new(cx.background());
+        let fs = FakeFs::new(cx.executor());
         fs.insert_tree(
             "/root",
             json!({
@@ -612,7 +642,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_prettier_lookup_for_not_installed(cx: &mut gpui::TestAppContext) {
-        let fs = FakeFs::new(cx.background());
+        let fs = FakeFs::new(cx.executor());
         fs.insert_tree(
             "/root",
             json!({
@@ -662,6 +692,7 @@ mod tests {
                 assert!(message.contains("/root/work/web_blog"), "Error message should mention which project had prettier defined");
             },
         };
+
         assert_eq!(
             Prettier::locate_prettier_installation(
                 fs.as_ref(),
@@ -704,7 +735,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_prettier_lookup_in_npm_workspaces(cx: &mut gpui::TestAppContext) {
-        let fs = FakeFs::new(cx.background());
+        let fs = FakeFs::new(cx.executor());
         fs.insert_tree(
             "/root",
             json!({
@@ -785,7 +816,7 @@ mod tests {
     async fn test_prettier_lookup_in_npm_workspaces_for_not_installed(
         cx: &mut gpui::TestAppContext,
     ) {
-        let fs = FakeFs::new(cx.background());
+        let fs = FakeFs::new(cx.executor());
         fs.insert_tree(
             "/root",
             json!({

crates/prettier2/Cargo.toml 🔗

@@ -1,35 +0,0 @@
-[package]
-name = "prettier2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/prettier2.rs"
-doctest = false
-
-[features]
-test-support = []
-
-[dependencies]
-client = { package = "client2", path = "../client2" }
-collections = { path = "../collections"}
-language = { package = "language2", path = "../language2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-fs = { package = "fs2", path = "../fs2" }
-lsp = { package = "lsp2", path = "../lsp2" }
-node_runtime = { path = "../node_runtime"}
-util = { path = "../util" }
-
-log.workspace = true
-serde.workspace = true
-serde_derive.workspace = true
-serde_json.workspace = true
-anyhow.workspace = true
-futures.workspace = true
-parking_lot.workspace = true
-
-[dev-dependencies]
-language = { package = "language2", path = "../language2", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-fs = { package = "fs2", path = "../fs2",  features = ["test-support"] }

crates/prettier2/src/prettier2.rs 🔗

@@ -1,869 +0,0 @@
-use anyhow::Context;
-use collections::{HashMap, HashSet};
-use fs::Fs;
-use gpui::{AsyncAppContext, Model};
-use language::{language_settings::language_settings, Buffer, Diff};
-use lsp::{LanguageServer, LanguageServerId};
-use node_runtime::NodeRuntime;
-use serde::{Deserialize, Serialize};
-use std::{
-    ops::ControlFlow,
-    path::{Path, PathBuf},
-    sync::Arc,
-};
-use util::paths::{PathMatcher, DEFAULT_PRETTIER_DIR};
-
-#[derive(Clone)]
-pub enum Prettier {
-    Real(RealPrettier),
-    #[cfg(any(test, feature = "test-support"))]
-    Test(TestPrettier),
-}
-
-#[derive(Clone)]
-pub struct RealPrettier {
-    default: bool,
-    prettier_dir: PathBuf,
-    server: Arc<LanguageServer>,
-}
-
-#[cfg(any(test, feature = "test-support"))]
-#[derive(Clone)]
-pub struct TestPrettier {
-    prettier_dir: PathBuf,
-    default: bool,
-}
-
-pub const FAIL_THRESHOLD: usize = 4;
-pub const PRETTIER_SERVER_FILE: &str = "prettier_server.js";
-pub const PRETTIER_SERVER_JS: &str = include_str!("./prettier_server.js");
-const PRETTIER_PACKAGE_NAME: &str = "prettier";
-const TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME: &str = "prettier-plugin-tailwindcss";
-
-#[cfg(any(test, feature = "test-support"))]
-pub const FORMAT_SUFFIX: &str = "\nformatted by test prettier";
-
-impl Prettier {
-    pub const CONFIG_FILE_NAMES: &'static [&'static str] = &[
-        ".prettierrc",
-        ".prettierrc.json",
-        ".prettierrc.json5",
-        ".prettierrc.yaml",
-        ".prettierrc.yml",
-        ".prettierrc.toml",
-        ".prettierrc.js",
-        ".prettierrc.cjs",
-        "package.json",
-        "prettier.config.js",
-        "prettier.config.cjs",
-        ".editorconfig",
-    ];
-
-    pub async fn locate_prettier_installation(
-        fs: &dyn Fs,
-        installed_prettiers: &HashSet<PathBuf>,
-        locate_from: &Path,
-    ) -> anyhow::Result<ControlFlow<(), Option<PathBuf>>> {
-        let mut path_to_check = locate_from
-            .components()
-            .take_while(|component| component.as_os_str().to_string_lossy() != "node_modules")
-            .collect::<PathBuf>();
-        if path_to_check != locate_from {
-            log::debug!(
-                "Skipping prettier location for path {path_to_check:?} that is inside node_modules"
-            );
-            return Ok(ControlFlow::Break(()));
-        }
-        let path_to_check_metadata = fs
-            .metadata(&path_to_check)
-            .await
-            .with_context(|| format!("failed to get metadata for initial path {path_to_check:?}"))?
-            .with_context(|| format!("empty metadata for initial path {path_to_check:?}"))?;
-        if !path_to_check_metadata.is_dir {
-            path_to_check.pop();
-        }
-
-        let mut project_path_with_prettier_dependency = None;
-        loop {
-            if installed_prettiers.contains(&path_to_check) {
-                log::debug!("Found prettier path {path_to_check:?} in installed prettiers");
-                return Ok(ControlFlow::Continue(Some(path_to_check)));
-            } else if let Some(package_json_contents) =
-                read_package_json(fs, &path_to_check).await?
-            {
-                if has_prettier_in_package_json(&package_json_contents) {
-                    if has_prettier_in_node_modules(fs, &path_to_check).await? {
-                        log::debug!("Found prettier path {path_to_check:?} in both package.json and node_modules");
-                        return Ok(ControlFlow::Continue(Some(path_to_check)));
-                    } else if project_path_with_prettier_dependency.is_none() {
-                        project_path_with_prettier_dependency = Some(path_to_check.clone());
-                    }
-                } else {
-                    match package_json_contents.get("workspaces") {
-                            Some(serde_json::Value::Array(workspaces)) => {
-                                match &project_path_with_prettier_dependency {
-                                    Some(project_path_with_prettier_dependency) => {
-                                        let subproject_path = project_path_with_prettier_dependency.strip_prefix(&path_to_check).expect("traversing path parents, should be able to strip prefix");
-                                        if workspaces.iter().filter_map(|value| {
-                                            if let serde_json::Value::String(s) = value {
-                                                Some(s.clone())
-                                            } else {
-                                                log::warn!("Skipping non-string 'workspaces' value: {value:?}");
-                                                None
-                                            }
-                                        }).any(|workspace_definition| {
-                                            if let Some(path_matcher) = PathMatcher::new(&workspace_definition).ok() {
-                                                path_matcher.is_match(subproject_path)
-                                            } else {
-                                                workspace_definition == subproject_path.to_string_lossy()
-                                            }
-                                        }) {
-                                            anyhow::ensure!(has_prettier_in_node_modules(fs, &path_to_check).await?, "Found prettier path {path_to_check:?} in the workspace root for project in {project_path_with_prettier_dependency:?}, but it's not installed into workspace root's node_modules");
-                                            log::info!("Found prettier path {path_to_check:?} in the workspace root for project in {project_path_with_prettier_dependency:?}");
-                                            return Ok(ControlFlow::Continue(Some(path_to_check)));
-                                        } else {
-                                            log::warn!("Skipping path {path_to_check:?} that has prettier in its 'node_modules' subdirectory, but is not included in its package.json workspaces {workspaces:?}");
-                                        }
-                                    }
-                                    None => {
-                                        log::warn!("Skipping path {path_to_check:?} that has prettier in its 'node_modules' subdirectory, but has no prettier in its package.json");
-                                    }
-                                }
-                            },
-                            Some(unknown) => log::error!("Failed to parse workspaces for {path_to_check:?} from package.json, got {unknown:?}. Skipping."),
-                            None => log::warn!("Skipping path {path_to_check:?} that has no prettier dependency and no workspaces section in its package.json"),
-                        }
-                }
-            }
-
-            if !path_to_check.pop() {
-                match project_path_with_prettier_dependency {
-                    Some(closest_prettier_discovered) => {
-                        anyhow::bail!("No prettier found in node_modules for ancestors of {locate_from:?}, but discovered prettier package.json dependency in {closest_prettier_discovered:?}")
-                    }
-                    None => {
-                        log::debug!("Found no prettier in ancestors of {locate_from:?}");
-                        return Ok(ControlFlow::Continue(None));
-                    }
-                }
-            }
-        }
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub async fn start(
-        _: LanguageServerId,
-        prettier_dir: PathBuf,
-        _: Arc<dyn NodeRuntime>,
-        _: AsyncAppContext,
-    ) -> anyhow::Result<Self> {
-        Ok(Self::Test(TestPrettier {
-            default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(),
-            prettier_dir,
-        }))
-    }
-
-    #[cfg(not(any(test, feature = "test-support")))]
-    pub async fn start(
-        server_id: LanguageServerId,
-        prettier_dir: PathBuf,
-        node: Arc<dyn NodeRuntime>,
-        cx: AsyncAppContext,
-    ) -> anyhow::Result<Self> {
-        use lsp::LanguageServerBinary;
-
-        let executor = cx.background_executor().clone();
-        anyhow::ensure!(
-            prettier_dir.is_dir(),
-            "Prettier dir {prettier_dir:?} is not a directory"
-        );
-        let prettier_server = DEFAULT_PRETTIER_DIR.join(PRETTIER_SERVER_FILE);
-        anyhow::ensure!(
-            prettier_server.is_file(),
-            "no prettier server package found at {prettier_server:?}"
-        );
-
-        let node_path = executor
-            .spawn(async move { node.binary_path().await })
-            .await?;
-        let server = LanguageServer::new(
-            Arc::new(parking_lot::Mutex::new(None)),
-            server_id,
-            LanguageServerBinary {
-                path: node_path,
-                arguments: vec![prettier_server.into(), prettier_dir.as_path().into()],
-            },
-            Path::new("/"),
-            None,
-            cx,
-        )
-        .context("prettier server creation")?;
-        let server = executor
-            .spawn(server.initialize(None))
-            .await
-            .context("prettier server initialization")?;
-        Ok(Self::Real(RealPrettier {
-            server,
-            default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(),
-            prettier_dir,
-        }))
-    }
-
-    pub async fn format(
-        &self,
-        buffer: &Model<Buffer>,
-        buffer_path: Option<PathBuf>,
-        cx: &mut AsyncAppContext,
-    ) -> anyhow::Result<Diff> {
-        match self {
-            Self::Real(local) => {
-                let params = buffer
-                    .update(cx, |buffer, cx| {
-                        let buffer_language = buffer.language();
-                        let parser_with_plugins = buffer_language.and_then(|l| {
-                            let prettier_parser = l.prettier_parser_name()?;
-                            let mut prettier_plugins = l
-                                .lsp_adapters()
-                                .iter()
-                                .flat_map(|adapter| adapter.prettier_plugins())
-                                .collect::<Vec<_>>();
-                            prettier_plugins.dedup();
-                            Some((prettier_parser, prettier_plugins))
-                        });
-
-                        let prettier_node_modules = self.prettier_dir().join("node_modules");
-                        anyhow::ensure!(
-                            prettier_node_modules.is_dir(),
-                            "Prettier node_modules dir does not exist: {prettier_node_modules:?}"
-                        );
-                        let plugin_name_into_path = |plugin_name: &str| {
-                            let prettier_plugin_dir = prettier_node_modules.join(plugin_name);
-                            for possible_plugin_path in [
-                                prettier_plugin_dir.join("dist").join("index.mjs"),
-                                prettier_plugin_dir.join("dist").join("index.js"),
-                                prettier_plugin_dir.join("dist").join("plugin.js"),
-                                prettier_plugin_dir.join("index.mjs"),
-                                prettier_plugin_dir.join("index.js"),
-                                prettier_plugin_dir.join("plugin.js"),
-                                prettier_plugin_dir,
-                            ] {
-                                if possible_plugin_path.is_file() {
-                                    return Some(possible_plugin_path);
-                                }
-                            }
-                            None
-                        };
-                        let (parser, located_plugins) = match parser_with_plugins {
-                            Some((parser, plugins)) => {
-                                // Tailwind plugin requires being added last
-                                // https://github.com/tailwindlabs/prettier-plugin-tailwindcss#compatibility-with-other-prettier-plugins
-                                let mut add_tailwind_back = false;
-
-                                let mut plugins = plugins
-                                    .into_iter()
-                                    .filter(|&&plugin_name| {
-                                        if plugin_name == TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME {
-                                            add_tailwind_back = true;
-                                            false
-                                        } else {
-                                            true
-                                        }
-                                    })
-                                    .map(|plugin_name| {
-                                        (plugin_name, plugin_name_into_path(plugin_name))
-                                    })
-                                    .collect::<Vec<_>>();
-                                if add_tailwind_back {
-                                    plugins.push((
-                                        &TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME,
-                                        plugin_name_into_path(
-                                            TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME,
-                                        ),
-                                    ));
-                                }
-                                (Some(parser.to_string()), plugins)
-                            }
-                            None => (None, Vec::new()),
-                        };
-
-                        let prettier_options = if self.is_default() {
-                            let language_settings =
-                                language_settings(buffer_language, buffer.file(), cx);
-                            let mut options = language_settings.prettier.clone();
-                            if !options.contains_key("tabWidth") {
-                                options.insert(
-                                    "tabWidth".to_string(),
-                                    serde_json::Value::Number(serde_json::Number::from(
-                                        language_settings.tab_size.get(),
-                                    )),
-                                );
-                            }
-                            if !options.contains_key("printWidth") {
-                                options.insert(
-                                    "printWidth".to_string(),
-                                    serde_json::Value::Number(serde_json::Number::from(
-                                        language_settings.preferred_line_length,
-                                    )),
-                                );
-                            }
-                            Some(options)
-                        } else {
-                            None
-                        };
-
-                        let plugins = located_plugins
-                            .into_iter()
-                            .filter_map(|(plugin_name, located_plugin_path)| {
-                                match located_plugin_path {
-                                    Some(path) => Some(path),
-                                    None => {
-                                        log::error!(
-                                            "Have not found plugin path for {:?} inside {:?}",
-                                            plugin_name,
-                                            prettier_node_modules
-                                        );
-                                        None
-                                    }
-                                }
-                            })
-                            .collect();
-                        log::debug!(
-                            "Formatting file {:?} with prettier, plugins :{:?}, options: {:?}",
-                            plugins,
-                            prettier_options,
-                            buffer.file().map(|f| f.full_path(cx))
-                        );
-
-                        anyhow::Ok(FormatParams {
-                            text: buffer.text(),
-                            options: FormatOptions {
-                                parser,
-                                plugins,
-                                path: buffer_path,
-                                prettier_options,
-                            },
-                        })
-                    })?
-                    .context("prettier params calculation")?;
-                let response = local
-                    .server
-                    .request::<Format>(params)
-                    .await
-                    .context("prettier format request")?;
-                let diff_task = buffer.update(cx, |buffer, cx| buffer.diff(response.text, cx))?;
-                Ok(diff_task.await)
-            }
-            #[cfg(any(test, feature = "test-support"))]
-            Self::Test(_) => Ok(buffer
-                .update(cx, |buffer, cx| {
-                    let formatted_text = buffer.text() + FORMAT_SUFFIX;
-                    buffer.diff(formatted_text, cx)
-                })?
-                .await),
-        }
-    }
-
-    pub async fn clear_cache(&self) -> anyhow::Result<()> {
-        match self {
-            Self::Real(local) => local
-                .server
-                .request::<ClearCache>(())
-                .await
-                .context("prettier clear cache"),
-            #[cfg(any(test, feature = "test-support"))]
-            Self::Test(_) => Ok(()),
-        }
-    }
-
-    pub fn server(&self) -> Option<&Arc<LanguageServer>> {
-        match self {
-            Self::Real(local) => Some(&local.server),
-            #[cfg(any(test, feature = "test-support"))]
-            Self::Test(_) => None,
-        }
-    }
-
-    pub fn is_default(&self) -> bool {
-        match self {
-            Self::Real(local) => local.default,
-            #[cfg(any(test, feature = "test-support"))]
-            Self::Test(test_prettier) => test_prettier.default,
-        }
-    }
-
-    pub fn prettier_dir(&self) -> &Path {
-        match self {
-            Self::Real(local) => &local.prettier_dir,
-            #[cfg(any(test, feature = "test-support"))]
-            Self::Test(test_prettier) => &test_prettier.prettier_dir,
-        }
-    }
-}
-
-async fn has_prettier_in_node_modules(fs: &dyn Fs, path: &Path) -> anyhow::Result<bool> {
-    let possible_node_modules_location = path.join("node_modules").join(PRETTIER_PACKAGE_NAME);
-    if let Some(node_modules_location_metadata) = fs
-        .metadata(&possible_node_modules_location)
-        .await
-        .with_context(|| format!("fetching metadata for {possible_node_modules_location:?}"))?
-    {
-        return Ok(node_modules_location_metadata.is_dir);
-    }
-    Ok(false)
-}
-
-async fn read_package_json(
-    fs: &dyn Fs,
-    path: &Path,
-) -> anyhow::Result<Option<HashMap<String, serde_json::Value>>> {
-    let possible_package_json = path.join("package.json");
-    if let Some(package_json_metadata) = fs
-        .metadata(&possible_package_json)
-        .await
-        .with_context(|| format!("fetching metadata for package json {possible_package_json:?}"))?
-    {
-        if !package_json_metadata.is_dir && !package_json_metadata.is_symlink {
-            let package_json_contents = fs
-                .load(&possible_package_json)
-                .await
-                .with_context(|| format!("reading {possible_package_json:?} file contents"))?;
-            return serde_json::from_str::<HashMap<String, serde_json::Value>>(
-                &package_json_contents,
-            )
-            .map(Some)
-            .with_context(|| format!("parsing {possible_package_json:?} file contents"));
-        }
-    }
-    Ok(None)
-}
-
-fn has_prettier_in_package_json(
-    package_json_contents: &HashMap<String, serde_json::Value>,
-) -> bool {
-    if let Some(serde_json::Value::Object(o)) = package_json_contents.get("dependencies") {
-        if o.contains_key(PRETTIER_PACKAGE_NAME) {
-            return true;
-        }
-    }
-    if let Some(serde_json::Value::Object(o)) = package_json_contents.get("devDependencies") {
-        if o.contains_key(PRETTIER_PACKAGE_NAME) {
-            return true;
-        }
-    }
-    false
-}
-
-enum Format {}
-
-#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct FormatParams {
-    text: String,
-    options: FormatOptions,
-}
-
-#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct FormatOptions {
-    plugins: Vec<PathBuf>,
-    parser: Option<String>,
-    #[serde(rename = "filepath")]
-    path: Option<PathBuf>,
-    prettier_options: Option<HashMap<String, serde_json::Value>>,
-}
-
-#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct FormatResult {
-    text: String,
-}
-
-impl lsp::request::Request for Format {
-    type Params = FormatParams;
-    type Result = FormatResult;
-    const METHOD: &'static str = "prettier/format";
-}
-
-enum ClearCache {}
-
-impl lsp::request::Request for ClearCache {
-    type Params = ();
-    type Result = ();
-    const METHOD: &'static str = "prettier/clear_cache";
-}
-
-#[cfg(test)]
-mod tests {
-    use fs::FakeFs;
-    use serde_json::json;
-
-    use super::*;
-
-    #[gpui::test]
-    async fn test_prettier_lookup_finds_nothing(cx: &mut gpui::TestAppContext) {
-        let fs = FakeFs::new(cx.executor());
-        fs.insert_tree(
-            "/root",
-            json!({
-                ".config": {
-                    "zed": {
-                        "settings.json": r#"{ "formatter": "auto" }"#,
-                    },
-                },
-                "work": {
-                    "project": {
-                        "src": {
-                            "index.js": "// index.js file contents",
-                        },
-                        "node_modules": {
-                            "expect": {
-                                "build": {
-                                    "print.js": "// print.js file contents",
-                                },
-                                "package.json": r#"{
-                                    "devDependencies": {
-                                        "prettier": "2.5.1"
-                                    }
-                                }"#,
-                            },
-                            "prettier": {
-                                "index.js": "// Dummy prettier package file",
-                            },
-                        },
-                        "package.json": r#"{}"#
-                    },
-                }
-            }),
-        )
-        .await;
-
-        assert!(
-            matches!(
-                Prettier::locate_prettier_installation(
-                    fs.as_ref(),
-                    &HashSet::default(),
-                    Path::new("/root/.config/zed/settings.json"),
-                )
-                .await,
-                Ok(ControlFlow::Continue(None))
-            ),
-            "Should successfully find no prettier for path hierarchy without it"
-        );
-        assert!(
-            matches!(
-                Prettier::locate_prettier_installation(
-                    fs.as_ref(),
-                    &HashSet::default(),
-                    Path::new("/root/work/project/src/index.js")
-                )
-                .await,
-                Ok(ControlFlow::Continue(None))
-            ),
-            "Should successfully find no prettier for path hierarchy that has node_modules with prettier, but no package.json mentions of it"
-        );
-        assert!(
-            matches!(
-                Prettier::locate_prettier_installation(
-                    fs.as_ref(),
-                    &HashSet::default(),
-                    Path::new("/root/work/project/node_modules/expect/build/print.js")
-                )
-                .await,
-                Ok(ControlFlow::Break(()))
-            ),
-            "Should not format files inside node_modules/"
-        );
-    }
-
-    #[gpui::test]
-    async fn test_prettier_lookup_in_simple_npm_projects(cx: &mut gpui::TestAppContext) {
-        let fs = FakeFs::new(cx.executor());
-        fs.insert_tree(
-            "/root",
-            json!({
-                "web_blog": {
-                    "node_modules": {
-                        "prettier": {
-                            "index.js": "// Dummy prettier package file",
-                        },
-                        "expect": {
-                            "build": {
-                                "print.js": "// print.js file contents",
-                            },
-                            "package.json": r#"{
-                                "devDependencies": {
-                                    "prettier": "2.5.1"
-                                }
-                            }"#,
-                        },
-                    },
-                    "pages": {
-                        "[slug].tsx": "// [slug].tsx file contents",
-                    },
-                    "package.json": r#"{
-                        "devDependencies": {
-                            "prettier": "2.3.0"
-                        },
-                        "prettier": {
-                            "semi": false,
-                            "printWidth": 80,
-                            "htmlWhitespaceSensitivity": "strict",
-                            "tabWidth": 4
-                        }
-                    }"#
-                }
-            }),
-        )
-        .await;
-
-        assert_eq!(
-            Prettier::locate_prettier_installation(
-                fs.as_ref(),
-                &HashSet::default(),
-                Path::new("/root/web_blog/pages/[slug].tsx")
-            )
-            .await
-            .unwrap(),
-            ControlFlow::Continue(Some(PathBuf::from("/root/web_blog"))),
-            "Should find a preinstalled prettier in the project root"
-        );
-        assert_eq!(
-            Prettier::locate_prettier_installation(
-                fs.as_ref(),
-                &HashSet::default(),
-                Path::new("/root/web_blog/node_modules/expect/build/print.js")
-            )
-            .await
-            .unwrap(),
-            ControlFlow::Break(()),
-            "Should not allow formatting node_modules/ contents"
-        );
-    }
-
-    #[gpui::test]
-    async fn test_prettier_lookup_for_not_installed(cx: &mut gpui::TestAppContext) {
-        let fs = FakeFs::new(cx.executor());
-        fs.insert_tree(
-            "/root",
-            json!({
-                "work": {
-                    "web_blog": {
-                        "node_modules": {
-                            "expect": {
-                                "build": {
-                                    "print.js": "// print.js file contents",
-                                },
-                                "package.json": r#"{
-                                    "devDependencies": {
-                                        "prettier": "2.5.1"
-                                    }
-                                }"#,
-                            },
-                        },
-                        "pages": {
-                            "[slug].tsx": "// [slug].tsx file contents",
-                        },
-                        "package.json": r#"{
-                            "devDependencies": {
-                                "prettier": "2.3.0"
-                            },
-                            "prettier": {
-                                "semi": false,
-                                "printWidth": 80,
-                                "htmlWhitespaceSensitivity": "strict",
-                                "tabWidth": 4
-                            }
-                        }"#
-                    }
-                }
-            }),
-        )
-        .await;
-
-        match Prettier::locate_prettier_installation(
-            fs.as_ref(),
-            &HashSet::default(),
-            Path::new("/root/work/web_blog/pages/[slug].tsx")
-        )
-        .await {
-            Ok(path) => panic!("Expected to fail for prettier in package.json but not in node_modules found, but got path {path:?}"),
-            Err(e) => {
-                let message = e.to_string();
-                assert!(message.contains("/root/work/web_blog"), "Error message should mention which project had prettier defined");
-            },
-        };
-
-        assert_eq!(
-            Prettier::locate_prettier_installation(
-                fs.as_ref(),
-                &HashSet::from_iter(
-                    [PathBuf::from("/root"), PathBuf::from("/root/work")].into_iter()
-                ),
-                Path::new("/root/work/web_blog/pages/[slug].tsx")
-            )
-            .await
-            .unwrap(),
-            ControlFlow::Continue(Some(PathBuf::from("/root/work"))),
-            "Should return closest cached value found without path checks"
-        );
-
-        assert_eq!(
-            Prettier::locate_prettier_installation(
-                fs.as_ref(),
-                &HashSet::default(),
-                Path::new("/root/work/web_blog/node_modules/expect/build/print.js")
-            )
-            .await
-            .unwrap(),
-            ControlFlow::Break(()),
-            "Should not allow formatting files inside node_modules/"
-        );
-        assert_eq!(
-            Prettier::locate_prettier_installation(
-                fs.as_ref(),
-                &HashSet::from_iter(
-                    [PathBuf::from("/root"), PathBuf::from("/root/work")].into_iter()
-                ),
-                Path::new("/root/work/web_blog/node_modules/expect/build/print.js")
-            )
-            .await
-            .unwrap(),
-            ControlFlow::Break(()),
-            "Should ignore cache lookup for files inside node_modules/"
-        );
-    }
-
-    #[gpui::test]
-    async fn test_prettier_lookup_in_npm_workspaces(cx: &mut gpui::TestAppContext) {
-        let fs = FakeFs::new(cx.executor());
-        fs.insert_tree(
-            "/root",
-            json!({
-                "work": {
-                    "full-stack-foundations": {
-                        "exercises": {
-                            "03.loading": {
-                                "01.problem.loader": {
-                                    "app": {
-                                        "routes": {
-                                            "users+": {
-                                                "$username_+": {
-                                                    "notes.tsx": "// notes.tsx file contents",
-                                                },
-                                            },
-                                        },
-                                    },
-                                    "node_modules": {
-                                        "test.js": "// test.js contents",
-                                    },
-                                    "package.json": r#"{
-                                        "devDependencies": {
-                                            "prettier": "^3.0.3"
-                                        }
-                                    }"#
-                                },
-                            },
-                        },
-                        "package.json": r#"{
-                            "workspaces": ["exercises/*/*", "examples/*"]
-                        }"#,
-                        "node_modules": {
-                            "prettier": {
-                                "index.js": "// Dummy prettier package file",
-                            },
-                        },
-                    },
-                }
-            }),
-        )
-        .await;
-
-        assert_eq!(
-            Prettier::locate_prettier_installation(
-                fs.as_ref(),
-                &HashSet::default(),
-                Path::new("/root/work/full-stack-foundations/exercises/03.loading/01.problem.loader/app/routes/users+/$username_+/notes.tsx"),
-            ).await.unwrap(),
-            ControlFlow::Continue(Some(PathBuf::from("/root/work/full-stack-foundations"))),
-            "Should ascend to the multi-workspace root and find the prettier there",
-        );
-
-        assert_eq!(
-            Prettier::locate_prettier_installation(
-                fs.as_ref(),
-                &HashSet::default(),
-                Path::new("/root/work/full-stack-foundations/node_modules/prettier/index.js")
-            )
-            .await
-            .unwrap(),
-            ControlFlow::Break(()),
-            "Should not allow formatting files inside root node_modules/"
-        );
-        assert_eq!(
-            Prettier::locate_prettier_installation(
-                fs.as_ref(),
-                &HashSet::default(),
-                Path::new("/root/work/full-stack-foundations/exercises/03.loading/01.problem.loader/node_modules/test.js")
-            )
-            .await
-            .unwrap(),
-            ControlFlow::Break(()),
-            "Should not allow formatting files inside submodule's node_modules/"
-        );
-    }
-
-    #[gpui::test]
-    async fn test_prettier_lookup_in_npm_workspaces_for_not_installed(
-        cx: &mut gpui::TestAppContext,
-    ) {
-        let fs = FakeFs::new(cx.executor());
-        fs.insert_tree(
-            "/root",
-            json!({
-                "work": {
-                    "full-stack-foundations": {
-                        "exercises": {
-                            "03.loading": {
-                                "01.problem.loader": {
-                                    "app": {
-                                        "routes": {
-                                            "users+": {
-                                                "$username_+": {
-                                                    "notes.tsx": "// notes.tsx file contents",
-                                                },
-                                            },
-                                        },
-                                    },
-                                    "node_modules": {},
-                                    "package.json": r#"{
-                                        "devDependencies": {
-                                            "prettier": "^3.0.3"
-                                        }
-                                    }"#
-                                },
-                            },
-                        },
-                        "package.json": r#"{
-                            "workspaces": ["exercises/*/*", "examples/*"]
-                        }"#,
-                    },
-                }
-            }),
-        )
-        .await;
-
-        match Prettier::locate_prettier_installation(
-            fs.as_ref(),
-            &HashSet::default(),
-            Path::new("/root/work/full-stack-foundations/exercises/03.loading/01.problem.loader/app/routes/users+/$username_+/notes.tsx")
-        )
-        .await {
-            Ok(path) => panic!("Expected to fail for prettier in package.json but not in node_modules found, but got path {path:?}"),
-            Err(e) => {
-                let message = e.to_string();
-                assert!(message.contains("/root/work/full-stack-foundations/exercises/03.loading/01.problem.loader"), "Error message should mention which project had prettier defined");
-                assert!(message.contains("/root/work/full-stack-foundations"), "Error message should mention potential candidates without prettier node_modules contents");
-            },
-        };
-    }
-}

crates/prettier2/src/prettier_server.js 🔗

@@ -1,241 +0,0 @@
-const { Buffer } = require("buffer");
-const fs = require("fs");
-const path = require("path");
-const { once } = require("events");
-
-const prettierContainerPath = process.argv[2];
-if (prettierContainerPath == null || prettierContainerPath.length == 0) {
-    process.stderr.write(
-        `Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path\n`,
-    );
-    process.exit(1);
-}
-fs.stat(prettierContainerPath, (err, stats) => {
-    if (err) {
-        process.stderr.write(`Path '${prettierContainerPath}' does not exist\n`);
-        process.exit(1);
-    }
-
-    if (!stats.isDirectory()) {
-        process.stderr.write(`Path '${prettierContainerPath}' exists but is not a directory\n`);
-        process.exit(1);
-    }
-});
-const prettierPath = path.join(prettierContainerPath, "node_modules/prettier");
-
-class Prettier {
-    constructor(path, prettier, config) {
-        this.path = path;
-        this.prettier = prettier;
-        this.config = config;
-    }
-}
-
-(async () => {
-    let prettier;
-    let config;
-    try {
-        prettier = await loadPrettier(prettierPath);
-        config = (await prettier.resolveConfig(prettierPath)) || {};
-    } catch (e) {
-        process.stderr.write(`Failed to load prettier: ${e}\n`);
-        process.exit(1);
-    }
-    process.stderr.write(`Prettier at path '${prettierPath}' loaded successfully, config: ${JSON.stringify(config)}\n`);
-    process.stdin.resume();
-    handleBuffer(new Prettier(prettierPath, prettier, config));
-})();
-
-async function handleBuffer(prettier) {
-    for await (const messageText of readStdin()) {
-        let message;
-        try {
-            message = JSON.parse(messageText);
-        } catch (e) {
-            sendResponse(makeError(`Failed to parse message '${messageText}': ${e}`));
-            continue;
-        }
-        // allow concurrent request handling by not `await`ing the message handling promise (async function)
-        handleMessage(message, prettier).catch((e) => {
-            const errorMessage = message;
-            if ((errorMessage.params || {}).text !== undefined) {
-                errorMessage.params.text = "..snip..";
-            }
-            sendResponse({
-                id: message.id,
-                ...makeError(`error during message '${JSON.stringify(errorMessage)}' handling: ${e}`),
-            });
-        });
-    }
-}
-
-const headerSeparator = "\r\n";
-const contentLengthHeaderName = "Content-Length";
-
-async function* readStdin() {
-    let buffer = Buffer.alloc(0);
-    let streamEnded = false;
-    process.stdin.on("end", () => {
-        streamEnded = true;
-    });
-    process.stdin.on("data", (data) => {
-        buffer = Buffer.concat([buffer, data]);
-    });
-
-    async function handleStreamEnded(errorMessage) {
-        sendResponse(makeError(errorMessage));
-        buffer = Buffer.alloc(0);
-        messageLength = null;
-        await once(process.stdin, "readable");
-        streamEnded = false;
-    }
-
-    try {
-        let headersLength = null;
-        let messageLength = null;
-        main_loop: while (true) {
-            if (messageLength === null) {
-                while (buffer.indexOf(`${headerSeparator}${headerSeparator}`) === -1) {
-                    if (streamEnded) {
-                        await handleStreamEnded("Unexpected end of stream: headers not found");
-                        continue main_loop;
-                    } else if (buffer.length > contentLengthHeaderName.length * 10) {
-                        await handleStreamEnded(
-                            `Unexpected stream of bytes: no headers end found after ${buffer.length} bytes of input`,
-                        );
-                        continue main_loop;
-                    }
-                    await once(process.stdin, "readable");
-                }
-                const headers = buffer
-                    .subarray(0, buffer.indexOf(`${headerSeparator}${headerSeparator}`))
-                    .toString("ascii");
-                const contentLengthHeader = headers
-                    .split(headerSeparator)
-                    .map((header) => header.split(":"))
-                    .filter((header) => header[2] === undefined)
-                    .filter((header) => (header[1] || "").length > 0)
-                    .find((header) => (header[0] || "").trim() === contentLengthHeaderName);
-                const contentLength = (contentLengthHeader || [])[1];
-                if (contentLength === undefined) {
-                    await handleStreamEnded(`Missing or incorrect ${contentLengthHeaderName} header: ${headers}`);
-                    continue main_loop;
-                }
-                headersLength = headers.length + headerSeparator.length * 2;
-                messageLength = parseInt(contentLength, 10);
-            }
-
-            while (buffer.length < headersLength + messageLength) {
-                if (streamEnded) {
-                    await handleStreamEnded(
-                        `Unexpected end of stream: buffer length ${buffer.length} does not match expected header length ${headersLength} + body length ${messageLength}`,
-                    );
-                    continue main_loop;
-                }
-                await once(process.stdin, "readable");
-            }
-
-            const messageEnd = headersLength + messageLength;
-            const message = buffer.subarray(headersLength, messageEnd);
-            buffer = buffer.subarray(messageEnd);
-            headersLength = null;
-            messageLength = null;
-            yield message.toString("utf8");
-        }
-    } catch (e) {
-        sendResponse(makeError(`Error reading stdin: ${e}`));
-    } finally {
-        process.stdin.off("data", () => {});
-    }
-}
-
-async function handleMessage(message, prettier) {
-    const { method, id, params } = message;
-    if (method === undefined) {
-        throw new Error(`Message method is undefined: ${JSON.stringify(message)}`);
-    } else if (method == "initialized") {
-        return;
-    }
-
-    if (id === undefined) {
-        throw new Error(`Message id is undefined: ${JSON.stringify(message)}`);
-    }
-
-    if (method === "prettier/format") {
-        if (params === undefined || params.text === undefined) {
-            throw new Error(`Message params.text is undefined: ${JSON.stringify(message)}`);
-        }
-        if (params.options === undefined) {
-            throw new Error(`Message params.options is undefined: ${JSON.stringify(message)}`);
-        }
-
-        let resolvedConfig = {};
-        if (params.options.filepath !== undefined) {
-            resolvedConfig = (await prettier.prettier.resolveConfig(params.options.filepath)) || {};
-        }
-
-        const options = {
-            ...(params.options.prettierOptions || prettier.config),
-            ...resolvedConfig,
-            parser: params.options.parser,
-            plugins: params.options.plugins,
-            path: params.options.filepath,
-        };
-        process.stderr.write(
-            `Resolved config: ${JSON.stringify(resolvedConfig)}, will format file '${
-                params.options.filepath || ""
-            }' with options: ${JSON.stringify(options)}\n`,
-        );
-        const formattedText = await prettier.prettier.format(params.text, options);
-        sendResponse({ id, result: { text: formattedText } });
-    } else if (method === "prettier/clear_cache") {
-        prettier.prettier.clearConfigCache();
-        prettier.config = (await prettier.prettier.resolveConfig(prettier.path)) || {};
-        sendResponse({ id, result: null });
-    } else if (method === "initialize") {
-        sendResponse({
-            id,
-            result: {
-                capabilities: {},
-            },
-        });
-    } else {
-        throw new Error(`Unknown method: ${method}`);
-    }
-}
-
-function makeError(message) {
-    return {
-        error: {
-            code: -32600, // invalid request code
-            message,
-        },
-    };
-}
-
-function sendResponse(response) {
-    const responsePayloadString = JSON.stringify({
-        jsonrpc: "2.0",
-        ...response,
-    });
-    const headers = `${contentLengthHeaderName}: ${Buffer.byteLength(
-        responsePayloadString,
-    )}${headerSeparator}${headerSeparator}`;
-    process.stdout.write(headers + responsePayloadString);
-}
-
-function loadPrettier(prettierPath) {
-    return new Promise((resolve, reject) => {
-        fs.access(prettierPath, fs.constants.F_OK, (err) => {
-            if (err) {
-                reject(`Path '${prettierPath}' does not exist.Error: ${err}`);
-            } else {
-                try {
-                    resolve(require(prettierPath));
-                } catch (err) {
-                    reject(`Error requiring prettier module from path '${prettierPath}'.Error: ${err}`);
-                }
-            }
-        });
-    });
-}

crates/project/Cargo.toml 🔗

@@ -16,6 +16,7 @@ test-support = [
     "settings/test-support",
     "text/test-support",
     "prettier/test-support",
+    "gpui/test-support",
 ]
 
 [dependencies]
@@ -27,7 +28,7 @@ collections = { path = "../collections" }
 db = { path = "../db" }
 fs = { path = "../fs" }
 fsevent = { path = "../fsevent" }
-fuzzy = { path = "../fuzzy" }
+fuzzy = {  path = "../fuzzy" }
 git = { path = "../git" }
 gpui = { path = "../gpui" }
 language = { path = "../language" }

crates/project/src/lsp_command.rs 🔗

@@ -7,7 +7,7 @@ use anyhow::{anyhow, Context, Result};
 use async_trait::async_trait;
 use client::proto::{self, PeerId};
 use futures::future;
-use gpui::{AppContext, AsyncAppContext, ModelHandle};
+use gpui::{AppContext, AsyncAppContext, Model};
 use language::{
     language_settings::{language_settings, InlayHintKind},
     point_from_lsp, point_to_lsp, prepare_completion_documentation,
@@ -33,7 +33,7 @@ pub fn lsp_formatting_options(tab_size: u32) -> lsp::FormattingOptions {
 }
 
 #[async_trait(?Send)]
-pub trait LspCommand: 'static + Sized {
+pub trait LspCommand: 'static + Sized + Send {
     type Response: 'static + Default + Send;
     type LspRequest: 'static + Send + lsp::request::Request;
     type ProtoRequest: 'static + Send + proto::RequestMessage;
@@ -53,8 +53,8 @@ pub trait LspCommand: 'static + Sized {
     async fn response_from_lsp(
         self,
         message: <Self::LspRequest as lsp::request::Request>::Result,
-        project: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         cx: AsyncAppContext,
     ) -> Result<Self::Response>;
@@ -63,8 +63,8 @@ pub trait LspCommand: 'static + Sized {
 
     async fn from_proto(
         message: Self::ProtoRequest,
-        project: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         cx: AsyncAppContext,
     ) -> Result<Self>;
 
@@ -79,8 +79,8 @@ pub trait LspCommand: 'static + Sized {
     async fn response_from_proto(
         self,
         message: <Self::ProtoRequest as proto::RequestMessage>::Response,
-        project: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         cx: AsyncAppContext,
     ) -> Result<Self::Response>;
 
@@ -180,12 +180,12 @@ impl LspCommand for PrepareRename {
     async fn response_from_lsp(
         self,
         message: Option<lsp::PrepareRenameResponse>,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         _: LanguageServerId,
-        cx: AsyncAppContext,
+        mut cx: AsyncAppContext,
     ) -> Result<Option<Range<Anchor>>> {
-        buffer.read_with(&cx, |buffer, _| {
+        buffer.update(&mut cx, |buffer, _| {
             if let Some(
                 lsp::PrepareRenameResponse::Range(range)
                 | lsp::PrepareRenameResponse::RangeWithPlaceholder { range, .. },
@@ -199,7 +199,7 @@ impl LspCommand for PrepareRename {
                 }
             }
             Ok(None)
-        })
+        })?
     }
 
     fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::PrepareRename {
@@ -215,8 +215,8 @@ impl LspCommand for PrepareRename {
 
     async fn from_proto(
         message: proto::PrepareRename,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -226,11 +226,11 @@ impl LspCommand for PrepareRename {
         buffer
             .update(&mut cx, |buffer, _| {
                 buffer.wait_for_version(deserialize_version(&message.version))
-            })
+            })?
             .await?;
 
         Ok(Self {
-            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
+            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
         })
     }
 
@@ -256,15 +256,15 @@ impl LspCommand for PrepareRename {
     async fn response_from_proto(
         self,
         message: proto::PrepareRenameResponse,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Option<Range<Anchor>>> {
         if message.can_rename {
             buffer
                 .update(&mut cx, |buffer, _| {
                     buffer.wait_for_version(deserialize_version(&message.version))
-                })
+                })?
                 .await?;
             let start = message.start.and_then(deserialize_anchor);
             let end = message.end.and_then(deserialize_anchor);
@@ -307,8 +307,8 @@ impl LspCommand for PerformRename {
     async fn response_from_lsp(
         self,
         message: Option<lsp::WorkspaceEdit>,
-        project: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         mut cx: AsyncAppContext,
     ) -> Result<ProjectTransaction> {
@@ -343,8 +343,8 @@ impl LspCommand for PerformRename {
 
     async fn from_proto(
         message: proto::PerformRename,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -354,10 +354,10 @@ impl LspCommand for PerformRename {
         buffer
             .update(&mut cx, |buffer, _| {
                 buffer.wait_for_version(deserialize_version(&message.version))
-            })
+            })?
             .await?;
         Ok(Self {
-            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
+            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
             new_name: message.new_name,
             push_to_history: false,
         })
@@ -379,8 +379,8 @@ impl LspCommand for PerformRename {
     async fn response_from_proto(
         self,
         message: proto::PerformRenameResponse,
-        project: ModelHandle<Project>,
-        _: ModelHandle<Buffer>,
+        project: Model<Project>,
+        _: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<ProjectTransaction> {
         let message = message
@@ -389,7 +389,7 @@ impl LspCommand for PerformRename {
         project
             .update(&mut cx, |project, cx| {
                 project.deserialize_project_transaction(message, self.push_to_history, cx)
-            })
+            })?
             .await
     }
 
@@ -426,8 +426,8 @@ impl LspCommand for GetDefinition {
     async fn response_from_lsp(
         self,
         message: Option<lsp::GotoDefinitionResponse>,
-        project: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         cx: AsyncAppContext,
     ) -> Result<Vec<LocationLink>> {
@@ -447,8 +447,8 @@ impl LspCommand for GetDefinition {
 
     async fn from_proto(
         message: proto::GetDefinition,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -458,10 +458,10 @@ impl LspCommand for GetDefinition {
         buffer
             .update(&mut cx, |buffer, _| {
                 buffer.wait_for_version(deserialize_version(&message.version))
-            })
+            })?
             .await?;
         Ok(Self {
-            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
+            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
         })
     }
 
@@ -479,8 +479,8 @@ impl LspCommand for GetDefinition {
     async fn response_from_proto(
         self,
         message: proto::GetDefinitionResponse,
-        project: ModelHandle<Project>,
-        _: ModelHandle<Buffer>,
+        project: Model<Project>,
+        _: Model<Buffer>,
         cx: AsyncAppContext,
     ) -> Result<Vec<LocationLink>> {
         location_links_from_proto(message.links, project, cx).await
@@ -527,8 +527,8 @@ impl LspCommand for GetTypeDefinition {
     async fn response_from_lsp(
         self,
         message: Option<lsp::GotoTypeDefinitionResponse>,
-        project: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         cx: AsyncAppContext,
     ) -> Result<Vec<LocationLink>> {
@@ -548,8 +548,8 @@ impl LspCommand for GetTypeDefinition {
 
     async fn from_proto(
         message: proto::GetTypeDefinition,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -559,10 +559,10 @@ impl LspCommand for GetTypeDefinition {
         buffer
             .update(&mut cx, |buffer, _| {
                 buffer.wait_for_version(deserialize_version(&message.version))
-            })
+            })?
             .await?;
         Ok(Self {
-            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
+            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
         })
     }
 
@@ -580,8 +580,8 @@ impl LspCommand for GetTypeDefinition {
     async fn response_from_proto(
         self,
         message: proto::GetTypeDefinitionResponse,
-        project: ModelHandle<Project>,
-        _: ModelHandle<Buffer>,
+        project: Model<Project>,
+        _: Model<Buffer>,
         cx: AsyncAppContext,
     ) -> Result<Vec<LocationLink>> {
         location_links_from_proto(message.links, project, cx).await
@@ -593,23 +593,23 @@ impl LspCommand for GetTypeDefinition {
 }
 
 fn language_server_for_buffer(
-    project: &ModelHandle<Project>,
-    buffer: &ModelHandle<Buffer>,
+    project: &Model<Project>,
+    buffer: &Model<Buffer>,
     server_id: LanguageServerId,
     cx: &mut AsyncAppContext,
 ) -> Result<(Arc<CachedLspAdapter>, Arc<LanguageServer>)> {
     project
-        .read_with(cx, |project, cx| {
+        .update(cx, |project, cx| {
             project
                 .language_server_for_buffer(buffer.read(cx), server_id, cx)
                 .map(|(adapter, server)| (adapter.clone(), server.clone()))
-        })
+        })?
         .ok_or_else(|| anyhow!("no language server found for buffer"))
 }
 
 async fn location_links_from_proto(
     proto_links: Vec<proto::LocationLink>,
-    project: ModelHandle<Project>,
+    project: Model<Project>,
     mut cx: AsyncAppContext,
 ) -> Result<Vec<LocationLink>> {
     let mut links = Vec::new();
@@ -620,7 +620,7 @@ async fn location_links_from_proto(
                 let buffer = project
                     .update(&mut cx, |this, cx| {
                         this.wait_for_remote_buffer(origin.buffer_id, cx)
-                    })
+                    })?
                     .await?;
                 let start = origin
                     .start
@@ -631,7 +631,7 @@ async fn location_links_from_proto(
                     .and_then(deserialize_anchor)
                     .ok_or_else(|| anyhow!("missing origin end"))?;
                 buffer
-                    .update(&mut cx, |buffer, _| buffer.wait_for_anchors([start, end]))
+                    .update(&mut cx, |buffer, _| buffer.wait_for_anchors([start, end]))?
                     .await?;
                 Some(Location {
                     buffer,
@@ -645,7 +645,7 @@ async fn location_links_from_proto(
         let buffer = project
             .update(&mut cx, |this, cx| {
                 this.wait_for_remote_buffer(target.buffer_id, cx)
-            })
+            })?
             .await?;
         let start = target
             .start
@@ -656,7 +656,7 @@ async fn location_links_from_proto(
             .and_then(deserialize_anchor)
             .ok_or_else(|| anyhow!("missing target end"))?;
         buffer
-            .update(&mut cx, |buffer, _| buffer.wait_for_anchors([start, end]))
+            .update(&mut cx, |buffer, _| buffer.wait_for_anchors([start, end]))?
             .await?;
         let target = Location {
             buffer,
@@ -671,8 +671,8 @@ async fn location_links_from_proto(
 
 async fn location_links_from_lsp(
     message: Option<lsp::GotoDefinitionResponse>,
-    project: ModelHandle<Project>,
-    buffer: ModelHandle<Buffer>,
+    project: Model<Project>,
+    buffer: Model<Buffer>,
     server_id: LanguageServerId,
     mut cx: AsyncAppContext,
 ) -> Result<Vec<LocationLink>> {
@@ -714,10 +714,10 @@ async fn location_links_from_lsp(
                     lsp_adapter.name.clone(),
                     cx,
                 )
-            })
+            })?
             .await?;
 
-        cx.read(|cx| {
+        cx.update(|cx| {
             let origin_location = origin_range.map(|origin_range| {
                 let origin_buffer = buffer.read(cx);
                 let origin_start =
@@ -746,7 +746,7 @@ async fn location_links_from_lsp(
                 origin: origin_location,
                 target: target_location,
             })
-        });
+        })?;
     }
     Ok(definitions)
 }
@@ -815,8 +815,8 @@ impl LspCommand for GetReferences {
     async fn response_from_lsp(
         self,
         locations: Option<Vec<lsp::Location>>,
-        project: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         mut cx: AsyncAppContext,
     ) -> Result<Vec<Location>> {
@@ -834,21 +834,22 @@ impl LspCommand for GetReferences {
                             lsp_adapter.name.clone(),
                             cx,
                         )
-                    })
+                    })?
                     .await?;
 
-                cx.read(|cx| {
-                    let target_buffer = target_buffer_handle.read(cx);
-                    let target_start = target_buffer
-                        .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
-                    let target_end = target_buffer
-                        .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
-                    references.push(Location {
-                        buffer: target_buffer_handle,
-                        range: target_buffer.anchor_after(target_start)
-                            ..target_buffer.anchor_before(target_end),
-                    });
-                });
+                target_buffer_handle
+                    .clone()
+                    .update(&mut cx, |target_buffer, _| {
+                        let target_start = target_buffer
+                            .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
+                        let target_end = target_buffer
+                            .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
+                        references.push(Location {
+                            buffer: target_buffer_handle,
+                            range: target_buffer.anchor_after(target_start)
+                                ..target_buffer.anchor_before(target_end),
+                        });
+                    })?;
             }
         }
 
@@ -868,8 +869,8 @@ impl LspCommand for GetReferences {
 
     async fn from_proto(
         message: proto::GetReferences,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -879,10 +880,10 @@ impl LspCommand for GetReferences {
         buffer
             .update(&mut cx, |buffer, _| {
                 buffer.wait_for_version(deserialize_version(&message.version))
-            })
+            })?
             .await?;
         Ok(Self {
-            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
+            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
         })
     }
 
@@ -910,8 +911,8 @@ impl LspCommand for GetReferences {
     async fn response_from_proto(
         self,
         message: proto::GetReferencesResponse,
-        project: ModelHandle<Project>,
-        _: ModelHandle<Buffer>,
+        project: Model<Project>,
+        _: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Vec<Location>> {
         let mut locations = Vec::new();
@@ -919,7 +920,7 @@ impl LspCommand for GetReferences {
             let target_buffer = project
                 .update(&mut cx, |this, cx| {
                     this.wait_for_remote_buffer(location.buffer_id, cx)
-                })
+                })?
                 .await?;
             let start = location
                 .start
@@ -930,7 +931,7 @@ impl LspCommand for GetReferences {
                 .and_then(deserialize_anchor)
                 .ok_or_else(|| anyhow!("missing target end"))?;
             target_buffer
-                .update(&mut cx, |buffer, _| buffer.wait_for_anchors([start, end]))
+                .update(&mut cx, |buffer, _| buffer.wait_for_anchors([start, end]))?
                 .await?;
             locations.push(Location {
                 buffer: target_buffer,
@@ -977,15 +978,15 @@ impl LspCommand for GetDocumentHighlights {
     async fn response_from_lsp(
         self,
         lsp_highlights: Option<Vec<lsp::DocumentHighlight>>,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         _: LanguageServerId,
-        cx: AsyncAppContext,
+        mut cx: AsyncAppContext,
     ) -> Result<Vec<DocumentHighlight>> {
-        buffer.read_with(&cx, |buffer, _| {
+        buffer.update(&mut cx, |buffer, _| {
             let mut lsp_highlights = lsp_highlights.unwrap_or_default();
             lsp_highlights.sort_unstable_by_key(|h| (h.range.start, Reverse(h.range.end)));
-            Ok(lsp_highlights
+            lsp_highlights
                 .into_iter()
                 .map(|lsp_highlight| {
                     let start = buffer
@@ -999,7 +1000,7 @@ impl LspCommand for GetDocumentHighlights {
                             .unwrap_or(lsp::DocumentHighlightKind::READ),
                     }
                 })
-                .collect())
+                .collect()
         })
     }
 
@@ -1016,8 +1017,8 @@ impl LspCommand for GetDocumentHighlights {
 
     async fn from_proto(
         message: proto::GetDocumentHighlights,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -1027,10 +1028,10 @@ impl LspCommand for GetDocumentHighlights {
         buffer
             .update(&mut cx, |buffer, _| {
                 buffer.wait_for_version(deserialize_version(&message.version))
-            })
+            })?
             .await?;
         Ok(Self {
-            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
+            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
         })
     }
 
@@ -1060,8 +1061,8 @@ impl LspCommand for GetDocumentHighlights {
     async fn response_from_proto(
         self,
         message: proto::GetDocumentHighlightsResponse,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Vec<DocumentHighlight>> {
         let mut highlights = Vec::new();
@@ -1075,7 +1076,7 @@ impl LspCommand for GetDocumentHighlights {
                 .and_then(deserialize_anchor)
                 .ok_or_else(|| anyhow!("missing target end"))?;
             buffer
-                .update(&mut cx, |buffer, _| buffer.wait_for_anchors([start, end]))
+                .update(&mut cx, |buffer, _| buffer.wait_for_anchors([start, end]))?
                 .await?;
             let kind = match proto::document_highlight::Kind::from_i32(highlight.kind) {
                 Some(proto::document_highlight::Kind::Text) => DocumentHighlightKind::TEXT,
@@ -1123,73 +1124,71 @@ impl LspCommand for GetHover {
     async fn response_from_lsp(
         self,
         message: Option<lsp::Hover>,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         _: LanguageServerId,
-        cx: AsyncAppContext,
+        mut cx: AsyncAppContext,
     ) -> Result<Self::Response> {
-        Ok(message.and_then(|hover| {
-            let (language, range) = cx.read(|cx| {
-                let buffer = buffer.read(cx);
-                (
-                    buffer.language().cloned(),
-                    hover.range.map(|range| {
-                        let token_start =
-                            buffer.clip_point_utf16(point_from_lsp(range.start), Bias::Left);
-                        let token_end =
-                            buffer.clip_point_utf16(point_from_lsp(range.end), Bias::Left);
-                        buffer.anchor_after(token_start)..buffer.anchor_before(token_end)
-                    }),
-                )
-            });
+        let Some(hover) = message else {
+            return Ok(None);
+        };
 
-            fn hover_blocks_from_marked_string(
-                marked_string: lsp::MarkedString,
-            ) -> Option<HoverBlock> {
-                let block = match marked_string {
-                    lsp::MarkedString::String(content) => HoverBlock {
-                        text: content,
-                        kind: HoverBlockKind::Markdown,
-                    },
-                    lsp::MarkedString::LanguageString(lsp::LanguageString { language, value }) => {
-                        HoverBlock {
-                            text: value,
-                            kind: HoverBlockKind::Code { language },
-                        }
+        let (language, range) = buffer.update(&mut cx, |buffer, _| {
+            (
+                buffer.language().cloned(),
+                hover.range.map(|range| {
+                    let token_start =
+                        buffer.clip_point_utf16(point_from_lsp(range.start), Bias::Left);
+                    let token_end = buffer.clip_point_utf16(point_from_lsp(range.end), Bias::Left);
+                    buffer.anchor_after(token_start)..buffer.anchor_before(token_end)
+                }),
+            )
+        })?;
+
+        fn hover_blocks_from_marked_string(marked_string: lsp::MarkedString) -> Option<HoverBlock> {
+            let block = match marked_string {
+                lsp::MarkedString::String(content) => HoverBlock {
+                    text: content,
+                    kind: HoverBlockKind::Markdown,
+                },
+                lsp::MarkedString::LanguageString(lsp::LanguageString { language, value }) => {
+                    HoverBlock {
+                        text: value,
+                        kind: HoverBlockKind::Code { language },
                     }
-                };
-                if block.text.is_empty() {
-                    None
-                } else {
-                    Some(block)
                 }
+            };
+            if block.text.is_empty() {
+                None
+            } else {
+                Some(block)
             }
+        }
 
-            let contents = cx.read(|_| match hover.contents {
-                lsp::HoverContents::Scalar(marked_string) => {
-                    hover_blocks_from_marked_string(marked_string)
-                        .into_iter()
-                        .collect()
-                }
-                lsp::HoverContents::Array(marked_strings) => marked_strings
+        let contents = match hover.contents {
+            lsp::HoverContents::Scalar(marked_string) => {
+                hover_blocks_from_marked_string(marked_string)
                     .into_iter()
-                    .filter_map(hover_blocks_from_marked_string)
-                    .collect(),
-                lsp::HoverContents::Markup(markup_content) => vec![HoverBlock {
-                    text: markup_content.value,
-                    kind: if markup_content.kind == lsp::MarkupKind::Markdown {
-                        HoverBlockKind::Markdown
-                    } else {
-                        HoverBlockKind::PlainText
-                    },
-                }],
-            });
+                    .collect()
+            }
+            lsp::HoverContents::Array(marked_strings) => marked_strings
+                .into_iter()
+                .filter_map(hover_blocks_from_marked_string)
+                .collect(),
+            lsp::HoverContents::Markup(markup_content) => vec![HoverBlock {
+                text: markup_content.value,
+                kind: if markup_content.kind == lsp::MarkupKind::Markdown {
+                    HoverBlockKind::Markdown
+                } else {
+                    HoverBlockKind::PlainText
+                },
+            }],
+        };
 
-            Some(Hover {
-                contents,
-                range,
-                language,
-            })
+        Ok(Some(Hover {
+            contents,
+            range,
+            language,
         }))
     }
 
@@ -1206,8 +1205,8 @@ impl LspCommand for GetHover {
 
     async fn from_proto(
         message: Self::ProtoRequest,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -1217,10 +1216,10 @@ impl LspCommand for GetHover {
         buffer
             .update(&mut cx, |buffer, _| {
                 buffer.wait_for_version(deserialize_version(&message.version))
-            })
+            })?
             .await?;
         Ok(Self {
-            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
+            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
         })
     }
 
@@ -1272,9 +1271,9 @@ impl LspCommand for GetHover {
     async fn response_from_proto(
         self,
         message: proto::GetHoverResponse,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
-        cx: AsyncAppContext,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
+        mut cx: AsyncAppContext,
     ) -> Result<Self::Response> {
         let contents: Vec<_> = message
             .contents
@@ -1294,7 +1293,7 @@ impl LspCommand for GetHover {
             return Ok(None);
         }
 
-        let language = buffer.read_with(&cx, |buffer, _| buffer.language().cloned());
+        let language = buffer.update(&mut cx, |buffer, _| buffer.language().cloned())?;
         let range = if let (Some(start), Some(end)) = (message.start, message.end) {
             language::proto::deserialize_anchor(start)
                 .and_then(|start| language::proto::deserialize_anchor(end).map(|end| start..end))
@@ -1341,10 +1340,10 @@ impl LspCommand for GetCompletions {
     async fn response_from_lsp(
         self,
         completions: Option<lsp::CompletionResponse>,
-        project: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
-        cx: AsyncAppContext,
+        mut cx: AsyncAppContext,
     ) -> Result<Vec<Completion>> {
         let mut response_list = None;
         let completions = if let Some(completions) = completions {
@@ -1358,10 +1357,10 @@ impl LspCommand for GetCompletions {
                 }
             }
         } else {
-            Vec::new()
+            Default::default()
         };
 
-        let completions = buffer.read_with(&cx, |buffer, cx| {
+        let completions = buffer.update(&mut cx, |buffer, cx| {
             let language_registry = project.read(cx).languages().clone();
             let language = buffer.language().cloned();
             let snapshot = buffer.snapshot();
@@ -1371,14 +1370,6 @@ impl LspCommand for GetCompletions {
             completions
                 .into_iter()
                 .filter_map(move |mut lsp_completion| {
-                    if let Some(response_list) = &response_list {
-                        if let Some(item_defaults) = &response_list.item_defaults {
-                            if let Some(data) = &item_defaults.data {
-                                lsp_completion.data = Some(data.clone());
-                            }
-                        }
-                    }
-
                     let (old_range, mut new_text) = match lsp_completion.text_edit.as_ref() {
                         // If the language server provides a range to overwrite, then
                         // check that the range is valid.
@@ -1454,10 +1445,9 @@ impl LspCommand for GetCompletions {
                         }
                     };
 
-                    LineEnding::normalize(&mut new_text);
                     let language_registry = language_registry.clone();
                     let language = language.clone();
-
+                    LineEnding::normalize(&mut new_text);
                     Some(async move {
                         let mut label = None;
                         if let Some(language) = language.as_ref() {
@@ -1493,7 +1483,7 @@ impl LspCommand for GetCompletions {
                         }
                     })
                 })
-        });
+        })?;
 
         Ok(future::join_all(completions).await)
     }
@@ -1510,23 +1500,23 @@ impl LspCommand for GetCompletions {
 
     async fn from_proto(
         message: proto::GetCompletions,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let version = deserialize_version(&message.version);
         buffer
-            .update(&mut cx, |buffer, _| buffer.wait_for_version(version))
+            .update(&mut cx, |buffer, _| buffer.wait_for_version(version))?
             .await?;
         let position = message
             .position
             .and_then(language::proto::deserialize_anchor)
             .map(|p| {
-                buffer.read_with(&cx, |buffer, _| {
+                buffer.update(&mut cx, |buffer, _| {
                     buffer.clip_point_utf16(Unclipped(p.to_point_utf16(buffer)), Bias::Left)
                 })
             })
-            .ok_or_else(|| anyhow!("invalid position"))?;
+            .ok_or_else(|| anyhow!("invalid position"))??;
         Ok(Self { position })
     }
 
@@ -1549,17 +1539,17 @@ impl LspCommand for GetCompletions {
     async fn response_from_proto(
         self,
         message: proto::GetCompletionsResponse,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Vec<Completion>> {
         buffer
             .update(&mut cx, |buffer, _| {
                 buffer.wait_for_version(deserialize_version(&message.version))
-            })
+            })?
             .await?;
 
-        let language = buffer.read_with(&cx, |buffer, _| buffer.language().cloned());
+        let language = buffer.update(&mut cx, |buffer, _| buffer.language().cloned())?;
         let completions = message.completions.into_iter().map(|completion| {
             language::proto::deserialize_completion(completion, language.clone())
         });
@@ -1615,8 +1605,8 @@ impl LspCommand for GetCodeActions {
     async fn response_from_lsp(
         self,
         actions: Option<lsp::CodeActionResponse>,
-        _: ModelHandle<Project>,
-        _: ModelHandle<Buffer>,
+        _: Model<Project>,
+        _: Model<Buffer>,
         server_id: LanguageServerId,
         _: AsyncAppContext,
     ) -> Result<Vec<CodeAction>> {
@@ -1649,8 +1639,8 @@ impl LspCommand for GetCodeActions {
 
     async fn from_proto(
         message: proto::GetCodeActions,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let start = message
@@ -1664,7 +1654,7 @@ impl LspCommand for GetCodeActions {
         buffer
             .update(&mut cx, |buffer, _| {
                 buffer.wait_for_version(deserialize_version(&message.version))
-            })
+            })?
             .await?;
 
         Ok(Self { range: start..end })
@@ -1689,14 +1679,14 @@ impl LspCommand for GetCodeActions {
     async fn response_from_proto(
         self,
         message: proto::GetCodeActionsResponse,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Vec<CodeAction>> {
         buffer
             .update(&mut cx, |buffer, _| {
                 buffer.wait_for_version(deserialize_version(&message.version))
-            })
+            })?
             .await?;
         message
             .actions
@@ -1752,8 +1742,8 @@ impl LspCommand for OnTypeFormatting {
     async fn response_from_lsp(
         self,
         message: Option<Vec<lsp::TextEdit>>,
-        project: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         mut cx: AsyncAppContext,
     ) -> Result<Option<Transaction>> {
@@ -1789,8 +1779,8 @@ impl LspCommand for OnTypeFormatting {
 
     async fn from_proto(
         message: proto::OnTypeFormatting,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -1800,15 +1790,15 @@ impl LspCommand for OnTypeFormatting {
         buffer
             .update(&mut cx, |buffer, _| {
                 buffer.wait_for_version(deserialize_version(&message.version))
-            })
+            })?
             .await?;
 
-        let tab_size = buffer.read_with(&cx, |buffer, cx| {
+        let tab_size = buffer.update(&mut cx, |buffer, cx| {
             language_settings(buffer.language(), buffer.file(), cx).tab_size
-        });
+        })?;
 
         Ok(Self {
-            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
+            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
             trigger: message.trigger.clone(),
             options: lsp_formatting_options(tab_size.get()).into(),
             push_to_history: false,
@@ -1831,8 +1821,8 @@ impl LspCommand for OnTypeFormatting {
     async fn response_from_proto(
         self,
         message: proto::OnTypeFormattingResponse,
-        _: ModelHandle<Project>,
-        _: ModelHandle<Buffer>,
+        _: Model<Project>,
+        _: Model<Buffer>,
         _: AsyncAppContext,
     ) -> Result<Option<Transaction>> {
         let Some(transaction) = message.transaction else {
@@ -1849,7 +1839,7 @@ impl LspCommand for OnTypeFormatting {
 impl InlayHints {
     pub async fn lsp_to_project_hint(
         lsp_hint: lsp::InlayHint,
-        buffer_handle: &ModelHandle<Buffer>,
+        buffer_handle: &Model<Buffer>,
         server_id: LanguageServerId,
         resolve_state: ResolveState,
         force_no_type_left_padding: bool,
@@ -1861,15 +1851,14 @@ impl InlayHints {
             _ => None,
         });
 
-        let position = cx.update(|cx| {
-            let buffer = buffer_handle.read(cx);
+        let position = buffer_handle.update(cx, |buffer, _| {
             let position = buffer.clip_point_utf16(point_from_lsp(lsp_hint.position), Bias::Left);
             if kind == Some(InlayHintKind::Parameter) {
                 buffer.anchor_before(position)
             } else {
                 buffer.anchor_after(position)
             }
-        });
+        })?;
         let label = Self::lsp_inlay_label_to_project(lsp_hint.label, server_id)
             .await
             .context("lsp to project inlay hint conversion")?;
@@ -2255,8 +2244,8 @@ impl LspCommand for InlayHints {
     async fn response_from_lsp(
         self,
         message: Option<Vec<lsp::InlayHint>>,
-        project: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         mut cx: AsyncAppContext,
     ) -> anyhow::Result<Vec<InlayHint>> {
@@ -2280,7 +2269,7 @@ impl LspCommand for InlayHints {
             };
 
             let buffer = buffer.clone();
-            cx.spawn(|mut cx| async move {
+            cx.spawn(move |mut cx| async move {
                 InlayHints::lsp_to_project_hint(
                     lsp_hint,
                     &buffer,
@@ -2311,8 +2300,8 @@ impl LspCommand for InlayHints {
 
     async fn from_proto(
         message: proto::InlayHints,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let start = message
@@ -2326,7 +2315,7 @@ impl LspCommand for InlayHints {
         buffer
             .update(&mut cx, |buffer, _| {
                 buffer.wait_for_version(deserialize_version(&message.version))
-            })
+            })?
             .await?;
 
         Ok(Self { range: start..end })
@@ -2351,14 +2340,14 @@ impl LspCommand for InlayHints {
     async fn response_from_proto(
         self,
         message: proto::InlayHintsResponse,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> anyhow::Result<Vec<InlayHint>> {
         buffer
             .update(&mut cx, |buffer, _| {
                 buffer.wait_for_version(deserialize_version(&message.version))
-            })
+            })?
             .await?;
 
         let mut hints = Vec::new();

crates/project/src/lsp_ext_command.rs 🔗

@@ -2,7 +2,7 @@ use std::{path::Path, sync::Arc};
 
 use anyhow::Context;
 use async_trait::async_trait;
-use gpui::{AppContext, AsyncAppContext, ModelHandle};
+use gpui::{AppContext, AsyncAppContext, Model};
 use language::{point_to_lsp, proto::deserialize_anchor, Buffer};
 use lsp::{LanguageServer, LanguageServerId};
 use rpc::proto::{self, PeerId};
@@ -67,8 +67,8 @@ impl LspCommand for ExpandMacro {
     async fn response_from_lsp(
         self,
         message: Option<ExpandedMacro>,
-        _: ModelHandle<Project>,
-        _: ModelHandle<Buffer>,
+        _: Model<Project>,
+        _: Model<Buffer>,
         _: LanguageServerId,
         _: AsyncAppContext,
     ) -> anyhow::Result<ExpandedMacro> {
@@ -92,8 +92,8 @@ impl LspCommand for ExpandMacro {
 
     async fn from_proto(
         message: Self::ProtoRequest,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> anyhow::Result<Self> {
         let position = message
@@ -101,7 +101,7 @@ impl LspCommand for ExpandMacro {
             .and_then(deserialize_anchor)
             .context("invalid position")?;
         Ok(Self {
-            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer)),
+            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
         })
     }
 
@@ -121,8 +121,8 @@ impl LspCommand for ExpandMacro {
     async fn response_from_proto(
         self,
         message: proto::LspExtExpandMacroResponse,
-        _: ModelHandle<Project>,
-        _: ModelHandle<Buffer>,
+        _: Model<Project>,
+        _: Model<Buffer>,
         _: AsyncAppContext,
     ) -> anyhow::Result<ExpandedMacro> {
         Ok(ExpandedMacro {

crates/project/src/prettier_support.rs 🔗

@@ -11,7 +11,7 @@ use futures::{
     future::{self, Shared},
     FutureExt,
 };
-use gpui::{AsyncAppContext, ModelContext, ModelHandle, Task};
+use gpui::{AsyncAppContext, Model, ModelContext, Task, WeakModel};
 use language::{
     language_settings::{Formatter, LanguageSettings},
     Buffer, Language, LanguageServerName, LocalFile,
@@ -49,21 +49,24 @@ pub fn prettier_plugins_for_language(
 }
 
 pub(super) async fn format_with_prettier(
-    project: &ModelHandle<Project>,
-    buffer: &ModelHandle<Buffer>,
+    project: &WeakModel<Project>,
+    buffer: &Model<Buffer>,
     cx: &mut AsyncAppContext,
 ) -> Option<FormatOperation> {
     if let Some((prettier_path, prettier_task)) = project
         .update(cx, |project, cx| {
             project.prettier_instance_for_buffer(buffer, cx)
         })
+        .ok()?
         .await
     {
         match prettier_task.await {
             Ok(prettier) => {
-                let buffer_path = buffer.update(cx, |buffer, cx| {
-                    File::from_dyn(buffer.file()).map(|file| file.abs_path(cx))
-                });
+                let buffer_path = buffer
+                    .update(cx, |buffer, cx| {
+                        File::from_dyn(buffer.file()).map(|file| file.abs_path(cx))
+                    })
+                    .ok()?;
                 match prettier.format(buffer, buffer_path, cx).await {
                     Ok(new_diff) => return Some(FormatOperation::Prettier(new_diff)),
                     Err(e) => {
@@ -73,28 +76,30 @@ pub(super) async fn format_with_prettier(
                     }
                 }
             }
-            Err(e) => project.update(cx, |project, _| {
-                let instance_to_update = match prettier_path {
-                    Some(prettier_path) => {
-                        log::error!(
+            Err(e) => project
+                .update(cx, |project, _| {
+                    let instance_to_update = match prettier_path {
+                        Some(prettier_path) => {
+                            log::error!(
                             "Prettier instance from path {prettier_path:?} failed to spawn: {e:#}"
                         );
-                        project.prettier_instances.get_mut(&prettier_path)
-                    }
-                    None => {
-                        log::error!("Default prettier instance failed to spawn: {e:#}");
-                        match &mut project.default_prettier.prettier {
-                            PrettierInstallation::NotInstalled { .. } => None,
-                            PrettierInstallation::Installed(instance) => Some(instance),
+                            project.prettier_instances.get_mut(&prettier_path)
                         }
-                    }
-                };
+                        None => {
+                            log::error!("Default prettier instance failed to spawn: {e:#}");
+                            match &mut project.default_prettier.prettier {
+                                PrettierInstallation::NotInstalled { .. } => None,
+                                PrettierInstallation::Installed(instance) => Some(instance),
+                            }
+                        }
+                    };
 
-                if let Some(instance) = instance_to_update {
-                    instance.attempt += 1;
-                    instance.prettier = None;
-                }
-            }),
+                    if let Some(instance) = instance_to_update {
+                        instance.attempt += 1;
+                        instance.prettier = None;
+                    }
+                })
+                .ok()?,
         }
     }
 
@@ -200,7 +205,7 @@ impl PrettierInstance {
                         project
                             .update(&mut cx, |_, cx| {
                                 start_default_prettier(node, worktree_id, cx)
-                            })
+                            })?
                             .await
                     })
                 }
@@ -225,7 +230,7 @@ fn start_default_prettier(
                         ControlFlow::Break(default_prettier.clone())
                     }
                 }
-            });
+            })?;
             match installation_task {
                 ControlFlow::Continue(None) => {
                     anyhow::bail!("Default prettier is not installed and cannot be started")
@@ -243,7 +248,7 @@ fn start_default_prettier(
                                 *installation_task = None;
                                 *attempts += 1;
                             }
-                        });
+                        })?;
                         anyhow::bail!(
                             "Cannot start default prettier due to its installation failure: {e:#}"
                         );
@@ -257,7 +262,7 @@ fn start_default_prettier(
                                 prettier: Some(new_default_prettier.clone()),
                             });
                         new_default_prettier
-                    });
+                    })?;
                     return Ok(new_default_prettier);
                 }
                 ControlFlow::Break(instance) => match instance.prettier {
@@ -272,7 +277,7 @@ fn start_default_prettier(
                                     prettier: Some(new_default_prettier.clone()),
                                 });
                             new_default_prettier
-                        });
+                        })?;
                         return Ok(new_default_prettier);
                     }
                 },
@@ -291,7 +296,7 @@ fn start_prettier(
         log::info!("Starting prettier at path {prettier_dir:?}");
         let new_server_id = project.update(&mut cx, |project, _| {
             project.languages.next_language_server_id()
-        });
+        })?;
 
         let new_prettier = Prettier::start(new_server_id, prettier_dir, node, cx.clone())
             .await
@@ -305,7 +310,7 @@ fn start_prettier(
 }
 
 fn register_new_prettier(
-    project: &ModelHandle<Project>,
+    project: &WeakModel<Project>,
     prettier: &Prettier,
     worktree_id: Option<WorktreeId>,
     new_server_id: LanguageServerId,
@@ -319,38 +324,40 @@ fn register_new_prettier(
         log::info!("Started prettier in {prettier_dir:?}");
     }
     if let Some(prettier_server) = prettier.server() {
-        project.update(cx, |project, cx| {
-            let name = if is_default {
-                LanguageServerName(Arc::from("prettier (default)"))
-            } else {
-                let worktree_path = worktree_id
-                    .and_then(|id| project.worktree_for_id(id, cx))
-                    .map(|worktree| worktree.update(cx, |worktree, _| worktree.abs_path()));
-                let name = match worktree_path {
-                    Some(worktree_path) => {
-                        if prettier_dir == worktree_path.as_ref() {
-                            let name = prettier_dir
-                                .file_name()
-                                .and_then(|name| name.to_str())
-                                .unwrap_or_default();
-                            format!("prettier ({name})")
-                        } else {
-                            let dir_to_display = prettier_dir
-                                .strip_prefix(worktree_path.as_ref())
-                                .ok()
-                                .unwrap_or(prettier_dir);
-                            format!("prettier ({})", dir_to_display.display())
+        project
+            .update(cx, |project, cx| {
+                let name = if is_default {
+                    LanguageServerName(Arc::from("prettier (default)"))
+                } else {
+                    let worktree_path = worktree_id
+                        .and_then(|id| project.worktree_for_id(id, cx))
+                        .map(|worktree| worktree.update(cx, |worktree, _| worktree.abs_path()));
+                    let name = match worktree_path {
+                        Some(worktree_path) => {
+                            if prettier_dir == worktree_path.as_ref() {
+                                let name = prettier_dir
+                                    .file_name()
+                                    .and_then(|name| name.to_str())
+                                    .unwrap_or_default();
+                                format!("prettier ({name})")
+                            } else {
+                                let dir_to_display = prettier_dir
+                                    .strip_prefix(worktree_path.as_ref())
+                                    .ok()
+                                    .unwrap_or(prettier_dir);
+                                format!("prettier ({})", dir_to_display.display())
+                            }
                         }
-                    }
-                    None => format!("prettier ({})", prettier_dir.display()),
+                        None => format!("prettier ({})", prettier_dir.display()),
+                    };
+                    LanguageServerName(Arc::from(name))
                 };
-                LanguageServerName(Arc::from(name))
-            };
-            project
-                .supplementary_language_servers
-                .insert(new_server_id, (name, Arc::clone(prettier_server)));
-            cx.emit(Event::LanguageServerAdded(new_server_id));
-        });
+                project
+                    .supplementary_language_servers
+                    .insert(new_server_id, (name, Arc::clone(prettier_server)));
+                cx.emit(Event::LanguageServerAdded(new_server_id));
+            })
+            .ok();
     }
 }
 
@@ -405,7 +412,7 @@ async fn save_prettier_server_file(fs: &dyn Fs) -> Result<(), anyhow::Error> {
 impl Project {
     pub fn update_prettier_settings(
         &self,
-        worktree: &ModelHandle<Worktree>,
+        worktree: &Model<Worktree>,
         changes: &[(Arc<Path>, ProjectEntryId, PathChange)],
         cx: &mut ModelContext<'_, Project>,
     ) {
@@ -446,7 +453,7 @@ impl Project {
                     }))
                     .collect::<Vec<_>>();
 
-            cx.background()
+            cx.background_executor()
                 .spawn(async move {
                     let _: Vec<()> = future::join_all(prettiers_to_reload.into_iter().map(|(worktree_id, prettier_path, prettier_instance)| {
                         async move {
@@ -477,7 +484,7 @@ impl Project {
 
     fn prettier_instance_for_buffer(
         &mut self,
-        buffer: &ModelHandle<Buffer>,
+        buffer: &Model<Buffer>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Option<(Option<PathBuf>, PrettierTask)>> {
         let buffer = buffer.read(cx);
@@ -500,7 +507,7 @@ impl Project {
                     let installed_prettiers = self.prettier_instances.keys().cloned().collect();
                     return cx.spawn(|project, mut cx| async move {
                         match cx
-                            .background()
+                            .background_executor()
                             .spawn(async move {
                                 Prettier::locate_prettier_installation(
                                     fs.as_ref(),
@@ -515,30 +522,34 @@ impl Project {
                                 return None;
                             }
                             Ok(ControlFlow::Continue(None)) => {
-                                let default_instance = project.update(&mut cx, |project, cx| {
-                                    project
-                                        .prettiers_per_worktree
-                                        .entry(worktree_id)
-                                        .or_default()
-                                        .insert(None);
-                                    project.default_prettier.prettier_task(
-                                        &node,
-                                        Some(worktree_id),
-                                        cx,
-                                    )
-                                });
+                                let default_instance = project
+                                    .update(&mut cx, |project, cx| {
+                                        project
+                                            .prettiers_per_worktree
+                                            .entry(worktree_id)
+                                            .or_default()
+                                            .insert(None);
+                                        project.default_prettier.prettier_task(
+                                            &node,
+                                            Some(worktree_id),
+                                            cx,
+                                        )
+                                    })
+                                    .ok()?;
                                 Some((None, default_instance?.log_err().await?))
                             }
                             Ok(ControlFlow::Continue(Some(prettier_dir))) => {
-                                project.update(&mut cx, |project, _| {
-                                    project
-                                        .prettiers_per_worktree
-                                        .entry(worktree_id)
-                                        .or_default()
-                                        .insert(Some(prettier_dir.clone()))
-                                });
-                                if let Some(prettier_task) =
-                                    project.update(&mut cx, |project, cx| {
+                                project
+                                    .update(&mut cx, |project, _| {
+                                        project
+                                            .prettiers_per_worktree
+                                            .entry(worktree_id)
+                                            .or_default()
+                                            .insert(Some(prettier_dir.clone()))
+                                    })
+                                    .ok()?;
+                                if let Some(prettier_task) = project
+                                    .update(&mut cx, |project, cx| {
                                         project.prettier_instances.get_mut(&prettier_dir).map(
                                             |existing_instance| {
                                                 existing_instance.prettier_task(
@@ -550,6 +561,7 @@ impl Project {
                                             },
                                         )
                                     })
+                                    .ok()?
                                 {
                                     log::debug!(
                                         "Found already started prettier in {prettier_dir:?}"
@@ -561,22 +573,24 @@ impl Project {
                                 }
 
                                 log::info!("Found prettier in {prettier_dir:?}, starting.");
-                                let new_prettier_task = project.update(&mut cx, |project, cx| {
-                                    let new_prettier_task = start_prettier(
-                                        node,
-                                        prettier_dir.clone(),
-                                        Some(worktree_id),
-                                        cx,
-                                    );
-                                    project.prettier_instances.insert(
-                                        prettier_dir.clone(),
-                                        PrettierInstance {
-                                            attempt: 0,
-                                            prettier: Some(new_prettier_task.clone()),
-                                        },
-                                    );
-                                    new_prettier_task
-                                });
+                                let new_prettier_task = project
+                                    .update(&mut cx, |project, cx| {
+                                        let new_prettier_task = start_prettier(
+                                            node,
+                                            prettier_dir.clone(),
+                                            Some(worktree_id),
+                                            cx,
+                                        );
+                                        project.prettier_instances.insert(
+                                            prettier_dir.clone(),
+                                            PrettierInstance {
+                                                attempt: 0,
+                                                prettier: Some(new_prettier_task.clone()),
+                                            },
+                                        );
+                                        new_prettier_task
+                                    })
+                                    .ok()?;
                                 Some((Some(prettier_dir), new_prettier_task))
                             }
                             Err(e) => {
@@ -633,7 +647,7 @@ impl Project {
         }) {
             Some(locate_from) => {
                 let installed_prettiers = self.prettier_instances.keys().cloned().collect();
-                cx.background().spawn(async move {
+                cx.background_executor().spawn(async move {
                     Prettier::locate_prettier_installation(
                         fs.as_ref(),
                         &installed_prettiers,
@@ -696,7 +710,7 @@ impl Project {
                                         installation_attempt = *attempts;
                                         needs_install = true;
                                     };
-                                });
+                                })?;
                             }
                         };
                         if installation_attempt > prettier::FAIL_THRESHOLD {
@@ -704,7 +718,7 @@ impl Project {
                                 if let PrettierInstallation::NotInstalled { installation_task, .. } = &mut project.default_prettier.prettier {
                                     *installation_task = None;
                                 };
-                            });
+                            })?;
                             log::warn!(
                                 "Default prettier installation had failed {installation_attempt} times, not attempting again",
                             );
@@ -721,10 +735,10 @@ impl Project {
                                 not_installed_plugins.extend(new_plugins.iter());
                             }
                             needs_install |= !new_plugins.is_empty();
-                        });
+                        })?;
                         if needs_install {
                             let installed_plugins = new_plugins.clone();
-                            cx.background()
+                            cx.background_executor()
                                 .spawn(async move {
                                     save_prettier_server_file(fs.as_ref()).await?;
                                     install_prettier_packages(new_plugins, node).await
@@ -742,7 +756,7 @@ impl Project {
                                 project.default_prettier
                                     .installed_plugins
                                     .extend(installed_plugins);
-                            });
+                            })?;
                         }
                     }
                 }

crates/project/src/project.rs 🔗

@@ -12,7 +12,7 @@ mod project_tests;
 #[cfg(test)]
 mod worktree_tests;
 
-use anyhow::{anyhow, Context, Result};
+use anyhow::{anyhow, Context as _, Result};
 use client::{proto, Client, Collaborator, TypedEnvelope, UserStore};
 use clock::ReplicaId;
 use collections::{hash_map, BTreeMap, HashMap, HashSet, VecDeque};
@@ -28,8 +28,8 @@ use futures::{
 };
 use globset::{Glob, GlobSet, GlobSetBuilder};
 use gpui::{
-    executor::Background, AnyModelHandle, AppContext, AsyncAppContext, BorrowAppContext, Entity,
-    ModelContext, ModelHandle, Task, WeakModelHandle,
+    AnyModel, AppContext, AsyncAppContext, BackgroundExecutor, Context, Entity, EventEmitter,
+    Model, ModelContext, Task, WeakModel,
 };
 use itertools::Itertools;
 use language::{
@@ -59,13 +59,11 @@ use project_settings::{LspSettings, ProjectSettings};
 use rand::prelude::*;
 use search::SearchQuery;
 use serde::Serialize;
-use settings::SettingsStore;
+use settings::{Settings, SettingsStore};
 use sha2::{Digest, Sha256};
 use similar::{ChangeTag, TextDiff};
-use smol::{
-    channel::{Receiver, Sender},
-    lock::Semaphore,
-};
+use smol::channel::{Receiver, Sender};
+use smol::lock::Semaphore;
 use std::{
     cmp::{self, Ordering},
     convert::TryInto,
@@ -130,7 +128,7 @@ pub struct Project {
     next_entry_id: Arc<AtomicUsize>,
     join_project_response_message_id: u32,
     next_diagnostic_group_id: usize,
-    user_store: ModelHandle<UserStore>,
+    user_store: Model<UserStore>,
     fs: Arc<dyn Fs>,
     client_state: Option<ProjectClientState>,
     collaborators: HashMap<proto::PeerId, Collaborator>,
@@ -142,24 +140,24 @@ pub struct Project {
     #[allow(clippy::type_complexity)]
     loading_buffers_by_path: HashMap<
         ProjectPath,
-        postage::watch::Receiver<Option<Result<ModelHandle<Buffer>, Arc<anyhow::Error>>>>,
+        postage::watch::Receiver<Option<Result<Model<Buffer>, Arc<anyhow::Error>>>>,
     >,
     #[allow(clippy::type_complexity)]
     loading_local_worktrees:
-        HashMap<Arc<Path>, Shared<Task<Result<ModelHandle<Worktree>, Arc<anyhow::Error>>>>>,
+        HashMap<Arc<Path>, Shared<Task<Result<Model<Worktree>, Arc<anyhow::Error>>>>>,
     opened_buffers: HashMap<u64, OpenBuffer>,
     local_buffer_ids_by_path: HashMap<ProjectPath, u64>,
     local_buffer_ids_by_entry_id: HashMap<ProjectEntryId, u64>,
     /// A mapping from a buffer ID to None means that we've started waiting for an ID but haven't finished loading it.
     /// Used for re-issuing buffer requests when peers temporarily disconnect
-    incomplete_remote_buffers: HashMap<u64, Option<ModelHandle<Buffer>>>,
+    incomplete_remote_buffers: HashMap<u64, Option<Model<Buffer>>>,
     buffer_snapshots: HashMap<u64, HashMap<LanguageServerId, Vec<LspBufferSnapshot>>>, // buffer_id -> server_id -> vec of snapshots
     buffers_being_formatted: HashSet<u64>,
-    buffers_needing_diff: HashSet<WeakModelHandle<Buffer>>,
+    buffers_needing_diff: HashSet<WeakModel<Buffer>>,
     git_diff_debouncer: DelayedDebounced,
     nonce: u128,
     _maintain_buffer_languages: Task<()>,
-    _maintain_workspace_config: Task<()>,
+    _maintain_workspace_config: Task<Result<()>>,
     terminals: Terminals,
     copilot_lsp_subscription: Option<gpui::Subscription>,
     copilot_log_subscription: Option<lsp::Subscription>,
@@ -190,7 +188,7 @@ impl DelayedDebounced {
 
     fn fire_new<F>(&mut self, delay: Duration, cx: &mut ModelContext<Project>, func: F)
     where
-        F: 'static + FnOnce(&mut Project, &mut ModelContext<Project>) -> Task<()>,
+        F: 'static + Send + FnOnce(&mut Project, &mut ModelContext<Project>) -> Task<()>,
     {
         if let Some(channel) = self.cancel_channel.take() {
             _ = channel.send(());
@@ -200,8 +198,8 @@ impl DelayedDebounced {
         self.cancel_channel = Some(sender);
 
         let previous_task = self.task.take();
-        self.task = Some(cx.spawn(|workspace, mut cx| async move {
-            let mut timer = cx.background().timer(delay).fuse();
+        self.task = Some(cx.spawn(move |project, mut cx| async move {
+            let mut timer = cx.background_executor().timer(delay).fuse();
             if let Some(previous_task) = previous_task {
                 previous_task.await;
             }
@@ -211,9 +209,9 @@ impl DelayedDebounced {
                     _ = timer => {}
             }
 
-            workspace
-                .update(&mut cx, |workspace, cx| (func)(workspace, cx))
-                .await;
+            if let Ok(task) = project.update(&mut cx, |project, cx| (func)(project, cx)) {
+                task.await;
+            }
         }));
     }
 }
@@ -245,22 +243,22 @@ enum LocalProjectUpdate {
 }
 
 enum OpenBuffer {
-    Strong(ModelHandle<Buffer>),
-    Weak(WeakModelHandle<Buffer>),
+    Strong(Model<Buffer>),
+    Weak(WeakModel<Buffer>),
     Operations(Vec<Operation>),
 }
 
 #[derive(Clone)]
 enum WorktreeHandle {
-    Strong(ModelHandle<Worktree>),
-    Weak(WeakModelHandle<Worktree>),
+    Strong(Model<Worktree>),
+    Weak(WeakModel<Worktree>),
 }
 
 enum ProjectClientState {
     Local {
         remote_id: u64,
         updates_tx: mpsc::UnboundedSender<LocalProjectUpdate>,
-        _send_updates: Task<()>,
+        _send_updates: Task<Result<()>>,
     },
     Remote {
         sharing_has_stopped: bool,
@@ -346,7 +344,7 @@ pub struct DiagnosticSummary {
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct Location {
-    pub buffer: ModelHandle<Buffer>,
+    pub buffer: Model<Buffer>,
     pub range: Range<language::Anchor>,
 }
 
@@ -459,7 +457,7 @@ impl Hover {
 }
 
 #[derive(Default)]
-pub struct ProjectTransaction(pub HashMap<ModelHandle<Buffer>, language::Transaction>);
+pub struct ProjectTransaction(pub HashMap<Model<Buffer>, language::Transaction>);
 
 impl DiagnosticSummary {
     fn new<'a, T: 'a>(diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<T>>) -> Self {
@@ -529,7 +527,7 @@ pub enum FormatTrigger {
 }
 
 struct ProjectLspAdapterDelegate {
-    project: ModelHandle<Project>,
+    project: Model<Project>,
     http_client: Arc<dyn HttpClient>,
 }
 
@@ -553,7 +551,7 @@ impl FormatTrigger {
 #[derive(Clone, Debug, PartialEq)]
 enum SearchMatchCandidate {
     OpenBuffer {
-        buffer: ModelHandle<Buffer>,
+        buffer: Model<Buffer>,
         // This might be an unnamed file without representation on filesystem
         path: Option<Arc<Path>>,
     },
@@ -576,7 +574,7 @@ impl SearchMatchCandidate {
 
 impl Project {
     pub fn init_settings(cx: &mut AppContext) {
-        settings::register::<ProjectSettings>(cx);
+        ProjectSettings::register(cx);
     }
 
     pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
@@ -606,7 +604,6 @@ impl Project {
         client.add_model_request_handler(Self::handle_apply_code_action);
         client.add_model_request_handler(Self::handle_on_type_formatting);
         client.add_model_request_handler(Self::handle_inlay_hints);
-        client.add_model_request_handler(Self::handle_resolve_completion_documentation);
         client.add_model_request_handler(Self::handle_resolve_inlay_hint);
         client.add_model_request_handler(Self::handle_refresh_inlay_hints);
         client.add_model_request_handler(Self::handle_reload_buffers);
@@ -634,14 +631,14 @@ impl Project {
     pub fn local(
         client: Arc<Client>,
         node: Arc<dyn NodeRuntime>,
-        user_store: ModelHandle<UserStore>,
+        user_store: Model<UserStore>,
         languages: Arc<LanguageRegistry>,
         fs: Arc<dyn Fs>,
         cx: &mut AppContext,
-    ) -> ModelHandle<Self> {
-        cx.add_model(|cx: &mut ModelContext<Self>| {
+    ) -> Model<Self> {
+        cx.new_model(|cx: &mut ModelContext<Self>| {
             let (tx, rx) = mpsc::unbounded();
-            cx.spawn_weak(|this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
+            cx.spawn(move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
                 .detach();
             let copilot_lsp_subscription =
                 Copilot::global(cx).map(|copilot| subscribe_for_copilot_events(&copilot, cx));
@@ -663,7 +660,9 @@ impl Project {
                 opened_buffer: watch::channel(),
                 client_subscriptions: Vec::new(),
                 _subscriptions: vec![
-                    cx.observe_global::<SettingsStore, _>(Self::on_settings_changed)
+                    cx.observe_global::<SettingsStore>(Self::on_settings_changed),
+                    cx.on_release(Self::release),
+                    cx.on_app_quit(Self::shutdown_language_servers),
                 ],
                 _maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
                 _maintain_workspace_config: Self::maintain_workspace_config(cx),
@@ -688,7 +687,7 @@ impl Project {
                 },
                 copilot_lsp_subscription,
                 copilot_log_subscription: None,
-                current_lsp_settings: settings::get::<ProjectSettings>(cx).lsp.clone(),
+                current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
                 node: Some(node),
                 default_prettier: DefaultPrettier::default(),
                 prettiers_per_worktree: HashMap::default(),
@@ -700,11 +699,11 @@ impl Project {
     pub async fn remote(
         remote_id: u64,
         client: Arc<Client>,
-        user_store: ModelHandle<UserStore>,
+        user_store: Model<UserStore>,
         languages: Arc<LanguageRegistry>,
         fs: Arc<dyn Fs>,
         mut cx: AsyncAppContext,
-    ) -> Result<ModelHandle<Self>> {
+    ) -> Result<Model<Self>> {
         client.authenticate_and_connect(true, &cx).await?;
 
         let subscription = client.subscribe_to_entity(remote_id)?;
@@ -713,19 +712,18 @@ impl Project {
                 project_id: remote_id,
             })
             .await?;
-        let this = cx.add_model(|cx| {
+        let this = cx.new_model(|cx| {
             let replica_id = response.payload.replica_id as ReplicaId;
 
             let mut worktrees = Vec::new();
             for worktree in response.payload.worktrees {
-                let worktree = cx.update(|cx| {
-                    Worktree::remote(remote_id, replica_id, worktree, client.clone(), cx)
-                });
+                let worktree =
+                    Worktree::remote(remote_id, replica_id, worktree, client.clone(), cx);
                 worktrees.push(worktree);
             }
 
             let (tx, rx) = mpsc::unbounded();
-            cx.spawn_weak(|this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
+            cx.spawn(move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
                 .detach();
             let copilot_lsp_subscription =
                 Copilot::global(cx).map(|copilot| subscribe_for_copilot_events(&copilot, cx));
@@ -751,7 +749,10 @@ impl Project {
                 next_entry_id: Default::default(),
                 next_diagnostic_group_id: Default::default(),
                 client_subscriptions: Default::default(),
-                _subscriptions: Default::default(),
+                _subscriptions: vec![
+                    cx.on_release(Self::release),
+                    cx.on_app_quit(Self::shutdown_language_servers),
+                ],
                 client: client.clone(),
                 client_state: Some(ProjectClientState::Remote {
                     sharing_has_stopped: false,
@@ -789,7 +790,7 @@ impl Project {
                 },
                 copilot_lsp_subscription,
                 copilot_log_subscription: None,
-                current_lsp_settings: settings::get::<ProjectSettings>(cx).lsp.clone(),
+                current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
                 node: None,
                 default_prettier: DefaultPrettier::default(),
                 prettiers_per_worktree: HashMap::default(),
@@ -799,7 +800,7 @@ impl Project {
                 let _ = this.add_worktree(&worktree, cx);
             }
             this
-        });
+        })?;
         let subscription = subscription.set_model(&this, &mut cx);
 
         let user_ids = response
@@ -809,29 +810,65 @@ impl Project {
             .map(|peer| peer.user_id)
             .collect();
         user_store
-            .update(&mut cx, |user_store, cx| user_store.get_users(user_ids, cx))
+            .update(&mut cx, |user_store, cx| user_store.get_users(user_ids, cx))?
             .await?;
 
         this.update(&mut cx, |this, cx| {
             this.set_collaborators_from_proto(response.payload.collaborators, cx)?;
             this.client_subscriptions.push(subscription);
             anyhow::Ok(())
-        })?;
+        })??;
 
         Ok(this)
     }
 
+    fn release(&mut self, cx: &mut AppContext) {
+        match &self.client_state {
+            Some(ProjectClientState::Local { .. }) => {
+                let _ = self.unshare_internal(cx);
+            }
+            Some(ProjectClientState::Remote { remote_id, .. }) => {
+                let _ = self.client.send(proto::LeaveProject {
+                    project_id: *remote_id,
+                });
+                self.disconnected_from_host_internal(cx);
+            }
+            _ => {}
+        }
+    }
+
+    fn shutdown_language_servers(
+        &mut self,
+        _cx: &mut ModelContext<Self>,
+    ) -> impl Future<Output = ()> {
+        let shutdown_futures = self
+            .language_servers
+            .drain()
+            .map(|(_, server_state)| async {
+                use LanguageServerState::*;
+                match server_state {
+                    Running { server, .. } => server.shutdown()?.await,
+                    Starting(task) => task.await?.shutdown()?.await,
+                }
+            })
+            .collect::<Vec<_>>();
+
+        async move {
+            futures::future::join_all(shutdown_futures).await;
+        }
+    }
+
     #[cfg(any(test, feature = "test-support"))]
     pub async fn test(
         fs: Arc<dyn Fs>,
         root_paths: impl IntoIterator<Item = &Path>,
         cx: &mut gpui::TestAppContext,
-    ) -> ModelHandle<Project> {
+    ) -> Model<Project> {
         let mut languages = LanguageRegistry::test();
-        languages.set_executor(cx.background());
+        languages.set_executor(cx.executor());
         let http_client = util::http::FakeHttpClient::with_404_response();
         let client = cx.update(|cx| client::Client::new(http_client.clone(), cx));
-        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
+        let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
         let project = cx.update(|cx| {
             Project::local(
                 client,
@@ -849,7 +886,7 @@ impl Project {
                 })
                 .await
                 .unwrap();
-            tree.read_with(cx, |tree, _| tree.as_local().unwrap().scan_complete())
+            tree.update(cx, |tree, _| tree.as_local().unwrap().scan_complete())
                 .await;
         }
         project
@@ -859,7 +896,7 @@ impl Project {
         let mut language_servers_to_start = Vec::new();
         let mut language_formatters_to_check = Vec::new();
         for buffer in self.opened_buffers.values() {
-            if let Some(buffer) = buffer.upgrade(cx) {
+            if let Some(buffer) = buffer.upgrade() {
                 let buffer = buffer.read(cx);
                 let buffer_file = File::from_dyn(buffer.file());
                 let buffer_language = buffer.language();
@@ -884,7 +921,7 @@ impl Project {
         let mut language_servers_to_restart = Vec::new();
         let languages = self.languages.to_vec();
 
-        let new_lsp_settings = settings::get::<ProjectSettings>(cx).lsp.clone();
+        let new_lsp_settings = ProjectSettings::get_global(cx).lsp.clone();
         let current_lsp_settings = &self.current_lsp_settings;
         for (worktree_id, started_lsp_name) in self.language_server_ids.keys() {
             let language = languages.iter().find_map(|l| {
@@ -957,7 +994,7 @@ impl Project {
         if self.copilot_lsp_subscription.is_none() {
             if let Some(copilot) = Copilot::global(cx) {
                 for buffer in self.opened_buffers.values() {
-                    if let Some(buffer) = buffer.upgrade(cx) {
+                    if let Some(buffer) = buffer.upgrade() {
                         self.register_buffer_with_copilot(&buffer, cx);
                     }
                 }
@@ -968,10 +1005,10 @@ impl Project {
         cx.notify();
     }
 
-    pub fn buffer_for_id(&self, remote_id: u64, cx: &AppContext) -> Option<ModelHandle<Buffer>> {
+    pub fn buffer_for_id(&self, remote_id: u64) -> Option<Model<Buffer>> {
         self.opened_buffers
             .get(&remote_id)
-            .and_then(|buffer| buffer.upgrade(cx))
+            .and_then(|buffer| buffer.upgrade())
     }
 
     pub fn languages(&self) -> &Arc<LanguageRegistry> {
@@ -982,14 +1019,14 @@ impl Project {
         self.client.clone()
     }
 
-    pub fn user_store(&self) -> ModelHandle<UserStore> {
+    pub fn user_store(&self) -> Model<UserStore> {
         self.user_store.clone()
     }
 
-    pub fn opened_buffers(&self, cx: &AppContext) -> Vec<ModelHandle<Buffer>> {
+    pub fn opened_buffers(&self) -> Vec<Model<Buffer>> {
         self.opened_buffers
             .values()
-            .filter_map(|b| b.upgrade(cx))
+            .filter_map(|b| b.upgrade())
             .collect()
     }
 
@@ -998,7 +1035,7 @@ impl Project {
         let path = path.into();
         if let Some(worktree) = self.worktree_for_id(path.worktree_id, cx) {
             self.opened_buffers.iter().any(|(_, buffer)| {
-                if let Some(buffer) = buffer.upgrade(cx) {
+                if let Some(buffer) = buffer.upgrade() {
                     if let Some(file) = File::from_dyn(buffer.read(cx).file()) {
                         if file.worktree == worktree && file.path() == &path.path {
                             return true;
@@ -1048,22 +1085,19 @@ impl Project {
     }
 
     /// Collect all worktrees, including ones that don't appear in the project panel
-    pub fn worktrees<'a>(
-        &'a self,
-        cx: &'a AppContext,
-    ) -> impl 'a + DoubleEndedIterator<Item = ModelHandle<Worktree>> {
+    pub fn worktrees<'a>(&'a self) -> impl 'a + DoubleEndedIterator<Item = Model<Worktree>> {
         self.worktrees
             .iter()
-            .filter_map(move |worktree| worktree.upgrade(cx))
+            .filter_map(move |worktree| worktree.upgrade())
     }
 
     /// Collect all user-visible worktrees, the ones that appear in the project panel
     pub fn visible_worktrees<'a>(
         &'a self,
         cx: &'a AppContext,
-    ) -> impl 'a + DoubleEndedIterator<Item = ModelHandle<Worktree>> {
+    ) -> impl 'a + DoubleEndedIterator<Item = Model<Worktree>> {
         self.worktrees.iter().filter_map(|worktree| {
-            worktree.upgrade(cx).and_then(|worktree| {
+            worktree.upgrade().and_then(|worktree| {
                 if worktree.read(cx).is_visible() {
                     Some(worktree)
                 } else {
@@ -1078,12 +1112,8 @@ impl Project {
             .map(|tree| tree.read(cx).root_name())
     }
 
-    pub fn worktree_for_id(
-        &self,
-        id: WorktreeId,
-        cx: &AppContext,
-    ) -> Option<ModelHandle<Worktree>> {
-        self.worktrees(cx)
+    pub fn worktree_for_id(&self, id: WorktreeId, cx: &AppContext) -> Option<Model<Worktree>> {
+        self.worktrees()
             .find(|worktree| worktree.read(cx).id() == id)
     }
 
@@ -1091,8 +1121,8 @@ impl Project {
         &self,
         entry_id: ProjectEntryId,
         cx: &AppContext,
-    ) -> Option<ModelHandle<Worktree>> {
-        self.worktrees(cx)
+    ) -> Option<Model<Worktree>> {
+        self.worktrees()
             .find(|worktree| worktree.read(cx).contains_entry(entry_id))
     }
 
@@ -1110,7 +1140,7 @@ impl Project {
     }
 
     pub fn contains_path(&self, path: &Path, cx: &AppContext) -> bool {
-        for worktree in self.worktrees(cx) {
+        for worktree in self.worktrees() {
             let worktree = worktree.read(cx).as_local();
             if worktree.map_or(false, |w| w.contains_abs_path(path)) {
                 return true;
@@ -1139,7 +1169,7 @@ impl Project {
         } else {
             let client = self.client.clone();
             let project_id = self.remote_id().unwrap();
-            cx.spawn_weak(|_, mut cx| async move {
+            cx.spawn(move |_, mut cx| async move {
                 let response = client
                     .request(proto::CreateProjectEntry {
                         worktree_id: project_path.worktree_id.to_proto(),
@@ -1156,7 +1186,7 @@ impl Project {
                                 response.worktree_scan_id as usize,
                                 cx,
                             )
-                        })
+                        })?
                         .await
                         .map(Some),
                     None => Ok(None),
@@ -1186,7 +1216,7 @@ impl Project {
             let client = self.client.clone();
             let project_id = self.remote_id().unwrap();
 
-            cx.spawn_weak(|_, mut cx| async move {
+            cx.spawn(move |_, mut cx| async move {
                 let response = client
                     .request(proto::CopyProjectEntry {
                         project_id,
@@ -1202,7 +1232,7 @@ impl Project {
                                 response.worktree_scan_id as usize,
                                 cx,
                             )
-                        })
+                        })?
                         .await
                         .map(Some),
                     None => Ok(None),
@@ -1232,7 +1262,7 @@ impl Project {
             let client = self.client.clone();
             let project_id = self.remote_id().unwrap();
 
-            cx.spawn_weak(|_, mut cx| async move {
+            cx.spawn(move |_, mut cx| async move {
                 let response = client
                     .request(proto::RenameProjectEntry {
                         project_id,
@@ -1248,7 +1278,7 @@ impl Project {
                                 response.worktree_scan_id as usize,
                                 cx,
                             )
-                        })
+                        })?
                         .await
                         .map(Some),
                     None => Ok(None),
@@ -1273,7 +1303,7 @@ impl Project {
         } else {
             let client = self.client.clone();
             let project_id = self.remote_id().unwrap();
-            Some(cx.spawn_weak(|_, mut cx| async move {
+            Some(cx.spawn(move |_, mut cx| async move {
                 let response = client
                     .request(proto::DeleteProjectEntry {
                         project_id,
@@ -1287,7 +1317,7 @@ impl Project {
                             response.worktree_scan_id as usize,
                             cx,
                         )
-                    })
+                    })?
                     .await
             }))
         }
@@ -1310,16 +1340,16 @@ impl Project {
                 project_id: self.remote_id().unwrap(),
                 entry_id: entry_id.to_proto(),
             });
-            Some(cx.spawn_weak(|_, mut cx| async move {
+            Some(cx.spawn(move |_, mut cx| async move {
                 let response = request.await?;
-                if let Some(worktree) = worktree.upgrade(&cx) {
+                if let Some(worktree) = worktree.upgrade() {
                     worktree
                         .update(&mut cx, |worktree, _| {
                             worktree
                                 .as_remote_mut()
                                 .unwrap()
                                 .wait_for_snapshot(response.worktree_scan_id as usize)
-                        })
+                        })?
                         .await?;
                 }
                 Ok(())
@@ -1341,7 +1371,7 @@ impl Project {
             match open_buffer {
                 OpenBuffer::Strong(_) => {}
                 OpenBuffer::Weak(buffer) => {
-                    if let Some(buffer) = buffer.upgrade(cx) {
+                    if let Some(buffer) = buffer.upgrade() {
                         *open_buffer = OpenBuffer::Strong(buffer);
                     }
                 }
@@ -1353,7 +1383,7 @@ impl Project {
             match worktree_handle {
                 WorktreeHandle::Strong(_) => {}
                 WorktreeHandle::Weak(worktree) => {
-                    if let Some(worktree) = worktree.upgrade(cx) {
+                    if let Some(worktree) = worktree.upgrade() {
                         *worktree_handle = WorktreeHandle::Strong(worktree);
                     }
                 }
@@ -1373,9 +1403,9 @@ impl Project {
         }
 
         let store = cx.global::<SettingsStore>();
-        for worktree in self.worktrees(cx) {
+        for worktree in self.worktrees() {
             let worktree_id = worktree.read(cx).id().to_proto();
-            for (path, content) in store.local_settings(worktree.id()) {
+            for (path, content) in store.local_settings(worktree.entity_id().as_u64() as usize) {
                 self.client
                     .send(proto::UpdateWorktreeSettings {
                         project_id,
@@ -1392,28 +1422,27 @@ impl Project {
         self.client_state = Some(ProjectClientState::Local {
             remote_id: project_id,
             updates_tx,
-            _send_updates: cx.spawn_weak(move |this, mut cx| async move {
+            _send_updates: cx.spawn(move |this, mut cx| async move {
                 while let Some(update) = updates_rx.next().await {
-                    let Some(this) = this.upgrade(&cx) else { break };
-
                     match update {
                         LocalProjectUpdate::WorktreesChanged => {
-                            let worktrees = this
-                                .read_with(&cx, |this, cx| this.worktrees(cx).collect::<Vec<_>>());
+                            let worktrees = this.update(&mut cx, |this, _cx| {
+                                this.worktrees().collect::<Vec<_>>()
+                            })?;
                             let update_project = this
-                                .read_with(&cx, |this, cx| {
+                                .update(&mut cx, |this, cx| {
                                     this.client.request(proto::UpdateProject {
                                         project_id,
                                         worktrees: this.worktree_metadata_protos(cx),
                                     })
-                                })
+                                })?
                                 .await;
                             if update_project.is_ok() {
                                 for worktree in worktrees {
                                     worktree.update(&mut cx, |worktree, cx| {
                                         let worktree = worktree.as_local_mut().unwrap();
                                         worktree.share(project_id, cx).detach_and_log_err(cx)
-                                    });
+                                    })?;
                                 }
                             }
                         }
@@ -1431,13 +1460,13 @@ impl Project {
                                 } else {
                                     None
                                 }
-                            });
+                            })?;
 
                             let Some(buffer) = buffer else { continue };
                             let operations =
-                                buffer.read_with(&cx, |b, cx| b.serialize_ops(None, cx));
+                                buffer.update(&mut cx, |b, cx| b.serialize_ops(None, cx))?;
                             let operations = operations.await;
-                            let state = buffer.read_with(&cx, |buffer, _| buffer.to_proto());
+                            let state = buffer.update(&mut cx, |buffer, _| buffer.to_proto())?;
 
                             let initial_state = proto::CreateBufferForPeer {
                                 project_id,
@@ -1446,7 +1475,7 @@ impl Project {
                             };
                             if client.send(initial_state).log_err().is_some() {
                                 let client = client.clone();
-                                cx.background()
+                                cx.background_executor()
                                     .spawn(async move {
                                         let mut chunks = split_operations(operations).peekable();
                                         while let Some(chunk) = chunks.next() {
@@ -1473,6 +1502,7 @@ impl Project {
                         }
                     }
                 }
+                Ok(())
             }),
         });
 
@@ -1499,7 +1529,7 @@ impl Project {
         message_id: u32,
         cx: &mut ModelContext<Self>,
     ) -> Result<()> {
-        cx.update_global::<SettingsStore, _, _>(|store, cx| {
+        cx.update_global::<SettingsStore, _>(|store, cx| {
             for worktree in &self.worktrees {
                 store
                     .clear_local_settings(worktree.handle_id(), cx)
@@ -1563,7 +1593,7 @@ impl Project {
 
             for open_buffer in self.opened_buffers.values_mut() {
                 // Wake up any tasks waiting for peers' edits to this buffer.
-                if let Some(buffer) = open_buffer.upgrade(cx) {
+                if let Some(buffer) = open_buffer.upgrade() {
                     buffer.update(cx, |buffer, _| buffer.give_up_waiting());
                 }
 
@@ -1599,7 +1629,7 @@ impl Project {
             self.collaborators.clear();
 
             for worktree in &self.worktrees {
-                if let Some(worktree) = worktree.upgrade(cx) {
+                if let Some(worktree) = worktree.upgrade() {
                     worktree.update(cx, |worktree, _| {
                         if let Some(worktree) = worktree.as_remote_mut() {
                             worktree.disconnected_from_host();
@@ -1610,7 +1640,7 @@ impl Project {
 
             for open_buffer in self.opened_buffers.values_mut() {
                 // Wake up any tasks waiting for peers' edits to this buffer.
-                if let Some(buffer) = open_buffer.upgrade(cx) {
+                if let Some(buffer) = open_buffer.upgrade() {
                     buffer.update(cx, |buffer, _| buffer.give_up_waiting());
                 }
 
@@ -1655,12 +1685,12 @@ impl Project {
         text: &str,
         language: Option<Arc<Language>>,
         cx: &mut ModelContext<Self>,
-    ) -> Result<ModelHandle<Buffer>> {
+    ) -> Result<Model<Buffer>> {
         if self.is_remote() {
             return Err(anyhow!("creating buffers as a guest is not supported yet"));
         }
         let id = post_inc(&mut self.next_buffer_id);
-        let buffer = cx.add_model(|cx| {
+        let buffer = cx.new_model(|cx| {
             Buffer::new(self.replica_id(), id, text)
                 .with_language(language.unwrap_or_else(|| language::PLAIN_TEXT.clone()), cx)
         });
@@ -1672,14 +1702,15 @@ impl Project {
         &mut self,
         path: ProjectPath,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<(Option<ProjectEntryId>, AnyModelHandle)>> {
+    ) -> Task<Result<(Option<ProjectEntryId>, AnyModel)>> {
         let task = self.open_buffer(path.clone(), cx);
-        cx.spawn_weak(|_, cx| async move {
+        cx.spawn(move |_, cx| async move {
             let buffer = task.await?;
             let project_entry_id = buffer.read_with(&cx, |buffer, cx| {
                 File::from_dyn(buffer.file()).and_then(|file| file.project_entry_id(cx))
-            });
-            let buffer: &AnyModelHandle = &buffer;
+            })?;
+
+            let buffer: &AnyModel = &buffer;
             Ok((project_entry_id, buffer.clone()))
         })
     }
@@ -1688,7 +1719,7 @@ impl Project {
         &mut self,
         abs_path: impl AsRef<Path>,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<ModelHandle<Buffer>>> {
+    ) -> Task<Result<Model<Buffer>>> {
         if let Some((worktree, relative_path)) = self.find_local_worktree(abs_path.as_ref(), cx) {
             self.open_buffer((worktree.read(cx).id(), relative_path), cx)
         } else {
@@ -1700,7 +1731,7 @@ impl Project {
         &mut self,
         path: impl Into<ProjectPath>,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<ModelHandle<Buffer>>> {
+    ) -> Task<Result<Model<Buffer>>> {
         let project_path = path.into();
         let worktree = if let Some(worktree) = self.worktree_for_id(project_path.worktree_id, cx) {
             worktree
@@ -1738,14 +1769,15 @@ impl Project {
                         this.loading_buffers_by_path.remove(&project_path);
                         let buffer = load_result.map_err(Arc::new)?;
                         Ok(buffer)
-                    }));
+                    })?);
+                    anyhow::Ok(())
                 })
                 .detach();
                 rx
             }
         };
 
-        cx.foreground().spawn(async move {
+        cx.background_executor().spawn(async move {
             wait_for_loading_buffer(loading_watch)
                 .await
                 .map_err(|error| anyhow!("{project_path:?} opening failure: {error:#}"))
@@ -1755,17 +1787,17 @@ impl Project {
     fn open_local_buffer_internal(
         &mut self,
         path: &Arc<Path>,
-        worktree: &ModelHandle<Worktree>,
+        worktree: &Model<Worktree>,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<ModelHandle<Buffer>>> {
+    ) -> Task<Result<Model<Buffer>>> {
         let buffer_id = post_inc(&mut self.next_buffer_id);
         let load_buffer = worktree.update(cx, |worktree, cx| {
             let worktree = worktree.as_local_mut().unwrap();
             worktree.load_buffer(buffer_id, path, cx)
         });
-        cx.spawn(|this, mut cx| async move {
+        cx.spawn(move |this, mut cx| async move {
             let buffer = load_buffer.await?;
-            this.update(&mut cx, |this, cx| this.register_buffer(&buffer, cx))?;
+            this.update(&mut cx, |this, cx| this.register_buffer(&buffer, cx))??;
             Ok(buffer)
         })
     }
@@ -1773,15 +1805,15 @@ impl Project {
     fn open_remote_buffer_internal(
         &mut self,
         path: &Arc<Path>,
-        worktree: &ModelHandle<Worktree>,
+        worktree: &Model<Worktree>,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<ModelHandle<Buffer>>> {
+    ) -> Task<Result<Model<Buffer>>> {
         let rpc = self.client.clone();
         let project_id = self.remote_id().unwrap();
         let remote_worktree_id = worktree.read(cx).id();
         let path = path.clone();
         let path_string = path.to_string_lossy().to_string();
-        cx.spawn(|this, mut cx| async move {
+        cx.spawn(move |this, mut cx| async move {
             let response = rpc
                 .request(proto::OpenBufferByPath {
                     project_id,
@@ -1791,7 +1823,7 @@ impl Project {
                 .await?;
             this.update(&mut cx, |this, cx| {
                 this.wait_for_remote_buffer(response.buffer_id, cx)
-            })
+            })?
             .await
         })
     }
@@ -1803,35 +1835,36 @@ impl Project {
         language_server_id: LanguageServerId,
         language_server_name: LanguageServerName,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<ModelHandle<Buffer>>> {
-        cx.spawn(|this, mut cx| async move {
+    ) -> Task<Result<Model<Buffer>>> {
+        cx.spawn(move |this, mut cx| async move {
             let abs_path = abs_path
                 .to_file_path()
                 .map_err(|_| anyhow!("can't convert URI to path"))?;
             let (worktree, relative_path) = if let Some(result) =
-                this.read_with(&cx, |this, cx| this.find_local_worktree(&abs_path, cx))
+                this.update(&mut cx, |this, cx| this.find_local_worktree(&abs_path, cx))?
             {
                 result
             } else {
                 let worktree = this
                     .update(&mut cx, |this, cx| {
                         this.create_local_worktree(&abs_path, false, cx)
-                    })
+                    })?
                     .await?;
                 this.update(&mut cx, |this, cx| {
                     this.language_server_ids.insert(
                         (worktree.read(cx).id(), language_server_name),
                         language_server_id,
                     );
-                });
+                })
+                .ok();
                 (worktree, PathBuf::new())
             };
 
             let project_path = ProjectPath {
-                worktree_id: worktree.read_with(&cx, |worktree, _| worktree.id()),
+                worktree_id: worktree.update(&mut cx, |worktree, _| worktree.id())?,
                 path: relative_path.into(),
             };
-            this.update(&mut cx, |this, cx| this.open_buffer(project_path, cx))
+            this.update(&mut cx, |this, cx| this.open_buffer(project_path, cx))?
                 .await
         })
     }
@@ -1840,8 +1873,8 @@ impl Project {
         &mut self,
         id: u64,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<ModelHandle<Buffer>>> {
-        if let Some(buffer) = self.buffer_for_id(id, cx) {
+    ) -> Task<Result<Model<Buffer>>> {
+        if let Some(buffer) = self.buffer_for_id(id) {
             Task::ready(Ok(buffer))
         } else if self.is_local() {
             Task::ready(Err(anyhow!("buffer {} does not exist", id)))
@@ -1849,11 +1882,11 @@ impl Project {
             let request = self
                 .client
                 .request(proto::OpenBufferById { project_id, id });
-            cx.spawn(|this, mut cx| async move {
+            cx.spawn(move |this, mut cx| async move {
                 let buffer_id = request.await?.buffer_id;
                 this.update(&mut cx, |this, cx| {
                     this.wait_for_remote_buffer(buffer_id, cx)
-                })
+                })?
                 .await
             })
         } else {
@@ -1863,13 +1896,14 @@ impl Project {
 
     pub fn save_buffers(
         &self,
-        buffers: HashSet<ModelHandle<Buffer>>,
+        buffers: HashSet<Model<Buffer>>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
-        cx.spawn(|this, mut cx| async move {
-            let save_tasks = buffers
-                .into_iter()
-                .map(|buffer| this.update(&mut cx, |this, cx| this.save_buffer(buffer, cx)));
+        cx.spawn(move |this, mut cx| async move {
+            let save_tasks = buffers.into_iter().filter_map(|buffer| {
+                this.update(&mut cx, |this, cx| this.save_buffer(buffer, cx))
+                    .ok()
+            });
             try_join_all(save_tasks).await?;
             Ok(())
         })
@@ -1877,7 +1911,7 @@ impl Project {
 
     pub fn save_buffer(
         &self,
-        buffer: ModelHandle<Buffer>,
+        buffer: Model<Buffer>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
         let Some(file) = File::from_dyn(buffer.read(cx).file()) else {
@@ -1893,7 +1927,7 @@ impl Project {
 
     pub fn save_buffer_as(
         &mut self,
-        buffer: ModelHandle<Buffer>,
+        buffer: Model<Buffer>,
         abs_path: PathBuf,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
@@ -1901,11 +1935,11 @@ impl Project {
         let old_file = File::from_dyn(buffer.read(cx).file())
             .filter(|f| f.is_local())
             .cloned();
-        cx.spawn(|this, mut cx| async move {
+        cx.spawn(move |this, mut cx| async move {
             if let Some(old_file) = &old_file {
                 this.update(&mut cx, |this, cx| {
                     this.unregister_buffer_from_language_servers(&buffer, old_file, cx);
-                });
+                })?;
             }
             let (worktree, path) = worktree_task.await?;
             worktree
@@ -1914,13 +1948,13 @@ impl Project {
                         worktree.save_buffer(buffer.clone(), path.into(), true, cx)
                     }
                     Worktree::Remote(_) => panic!("cannot remote buffers as new files"),
-                })
+                })?
                 .await?;
 
             this.update(&mut cx, |this, cx| {
                 this.detect_language_for_buffer(&buffer, cx);
                 this.register_buffer_with_language_servers(&buffer, cx);
-            });
+            })?;
             Ok(())
         })
     }
@@ -1929,10 +1963,10 @@ impl Project {
         &mut self,
         path: &ProjectPath,
         cx: &mut ModelContext<Self>,
-    ) -> Option<ModelHandle<Buffer>> {
+    ) -> Option<Model<Buffer>> {
         let worktree = self.worktree_for_id(path.worktree_id, cx)?;
         self.opened_buffers.values().find_map(|buffer| {
-            let buffer = buffer.upgrade(cx)?;
+            let buffer = buffer.upgrade()?;
             let file = File::from_dyn(buffer.read(cx).file())?;
             if file.worktree == worktree && file.path() == &path.path {
                 Some(buffer)

crates/project/src/project_settings.rs 🔗

@@ -1,7 +1,8 @@
 use collections::HashMap;
+use gpui::AppContext;
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
-use settings::Setting;
+use settings::Settings;
 use std::sync::Arc;
 
 #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
@@ -34,7 +35,7 @@ pub struct LspSettings {
     pub initialization_options: Option<serde_json::Value>,
 }
 
-impl Setting for ProjectSettings {
+impl Settings for ProjectSettings {
     const KEY: Option<&'static str> = None;
 
     type FileContent = Self;
@@ -42,7 +43,7 @@ impl Setting for ProjectSettings {
     fn load(
         default_value: &Self::FileContent,
         user_values: &[&Self::FileContent],
-        _: &gpui::AppContext,
+        _: &mut AppContext,
     ) -> anyhow::Result<Self> {
         Self::load_via_json_merge(default_value, user_values)
     }

crates/project/src/project_tests.rs 🔗

@@ -1,7 +1,7 @@
-use crate::{worktree::WorktreeModelHandle, Event, *};
-use fs::{FakeFs, RealFs};
+use crate::{Event, *};
+use fs::FakeFs;
 use futures::{future, StreamExt};
-use gpui::{executor::Deterministic, test::subscribe, AppContext};
+use gpui::AppContext;
 use language::{
     language_settings::{AllLanguageSettings, LanguageSettingsContent},
     tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig,
@@ -11,22 +11,44 @@ use lsp::Url;
 use parking_lot::Mutex;
 use pretty_assertions::assert_eq;
 use serde_json::json;
-use std::{cell::RefCell, os::unix, rc::Rc, task::Poll};
+use std::{os, task::Poll};
 use unindent::Unindent as _;
 use util::{assert_set_eq, paths::PathMatcher, test::temp_tree};
 
-#[cfg(test)]
-#[ctor::ctor]
-fn init_logger() {
-    if std::env::var("RUST_LOG").is_ok() {
-        env_logger::init();
-    }
+#[gpui::test]
+async fn test_block_via_channel(cx: &mut gpui::TestAppContext) {
+    cx.executor().allow_parking();
+
+    let (tx, mut rx) = futures::channel::mpsc::unbounded();
+    let _thread = std::thread::spawn(move || {
+        std::fs::metadata("/Users").unwrap();
+        std::thread::sleep(Duration::from_millis(1000));
+        tx.unbounded_send(1).unwrap();
+    });
+    rx.next().await.unwrap();
+}
+
+#[gpui::test]
+async fn test_block_via_smol(cx: &mut gpui::TestAppContext) {
+    cx.executor().allow_parking();
+
+    let io_task = smol::unblock(move || {
+        println!("sleeping on thread {:?}", std::thread::current().id());
+        std::thread::sleep(Duration::from_millis(10));
+        1
+    });
+
+    let task = cx.foreground_executor().spawn(async move {
+        io_task.await;
+    });
+
+    task.await;
 }
 
 #[gpui::test]
 async fn test_symlinks(cx: &mut gpui::TestAppContext) {
     init_test(cx);
-    cx.foreground().allow_parking();
+    cx.executor().allow_parking();
 
     let dir = temp_tree(json!({
         "root": {
@@ -44,16 +66,17 @@ async fn test_symlinks(cx: &mut gpui::TestAppContext) {
     }));
 
     let root_link_path = dir.path().join("root_link");
-    unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
-    unix::fs::symlink(
+    os::unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
+    os::unix::fs::symlink(
         &dir.path().join("root/fennel"),
         &dir.path().join("root/finnochio"),
     )
     .unwrap();
 
     let project = Project::test(Arc::new(RealFs), [root_link_path.as_ref()], cx).await;
-    project.read_with(cx, |project, cx| {
-        let tree = project.worktrees(cx).next().unwrap().read(cx);
+
+    project.update(cx, |project, cx| {
+        let tree = project.worktrees().next().unwrap().read(cx);
         assert_eq!(tree.file_count(), 5);
         assert_eq!(
             tree.inode_for_path("fennel/grape"),
@@ -63,13 +86,10 @@ async fn test_symlinks(cx: &mut gpui::TestAppContext) {
 }
 
 #[gpui::test]
-async fn test_managing_project_specific_settings(
-    deterministic: Arc<Deterministic>,
-    cx: &mut gpui::TestAppContext,
-) {
+async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/the-root",
         json!({
@@ -90,10 +110,10 @@ async fn test_managing_project_specific_settings(
     .await;
 
     let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;
-    let worktree = project.read_with(cx, |project, cx| project.worktrees(cx).next().unwrap());
+    let worktree = project.update(cx, |project, _| project.worktrees().next().unwrap());
 
-    deterministic.run_until_parked();
-    cx.read(|cx| {
+    cx.executor().run_until_parked();
+    cx.update(|cx| {
         let tree = worktree.read(cx);
 
         let settings_a = language_settings(
@@ -123,10 +143,7 @@ async fn test_managing_project_specific_settings(
 }
 
 #[gpui::test]
-async fn test_managing_language_servers(
-    deterministic: Arc<Deterministic>,
-    cx: &mut gpui::TestAppContext,
-) {
+async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
     let mut rust_language = Language::new(
@@ -172,7 +189,7 @@ async fn test_managing_language_servers(
         }))
         .await;
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/the-root",
         json!({
@@ -201,7 +218,7 @@ async fn test_managing_language_servers(
         })
         .await
         .unwrap();
-    rust_buffer.read_with(cx, |buffer, _| {
+    rust_buffer.update(cx, |buffer, _| {
         assert_eq!(buffer.language().map(|l| l.name()), None);
     });
 
@@ -211,8 +228,8 @@ async fn test_managing_language_servers(
         project.languages.add(Arc::new(json_language));
         project.languages.add(Arc::new(rust_language));
     });
-    deterministic.run_until_parked();
-    rust_buffer.read_with(cx, |buffer, _| {
+    cx.executor().run_until_parked();
+    rust_buffer.update(cx, |buffer, _| {
         assert_eq!(buffer.language().map(|l| l.name()), Some("Rust".into()));
     });
 
@@ -232,13 +249,13 @@ async fn test_managing_language_servers(
     );
 
     // The buffer is configured based on the language server's capabilities.
-    rust_buffer.read_with(cx, |buffer, _| {
+    rust_buffer.update(cx, |buffer, _| {
         assert_eq!(
             buffer.completion_triggers(),
             &[".".to_string(), "::".to_string()]
         );
     });
-    toml_buffer.read_with(cx, |buffer, _| {
+    toml_buffer.update(cx, |buffer, _| {
         assert!(buffer.completion_triggers().is_empty());
     });
 
@@ -280,7 +297,7 @@ async fn test_managing_language_servers(
 
     // This buffer is configured based on the second language server's
     // capabilities.
-    json_buffer.read_with(cx, |buffer, _| {
+    json_buffer.update(cx, |buffer, _| {
         assert_eq!(buffer.completion_triggers(), &[":".to_string()]);
     });
 
@@ -292,7 +309,7 @@ async fn test_managing_language_servers(
         })
         .await
         .unwrap();
-    rust_buffer2.read_with(cx, |buffer, _| {
+    rust_buffer2.update(cx, |buffer, _| {
         assert_eq!(
             buffer.completion_triggers(),
             &[".".to_string(), "::".to_string()]
@@ -358,7 +375,7 @@ async fn test_managing_language_servers(
         lsp::TextDocumentItem {
             uri: lsp::Url::from_file_path("/the-root/test3.rs").unwrap(),
             version: 0,
-            text: rust_buffer2.read_with(cx, |buffer, _| buffer.text()),
+            text: rust_buffer2.update(cx, |buffer, _| buffer.text()),
             language_id: Default::default()
         },
     );
@@ -408,13 +425,13 @@ async fn test_managing_language_servers(
         lsp::TextDocumentItem {
             uri: lsp::Url::from_file_path("/the-root/test3.json").unwrap(),
             version: 0,
-            text: rust_buffer2.read_with(cx, |buffer, _| buffer.text()),
+            text: rust_buffer2.update(cx, |buffer, _| buffer.text()),
             language_id: Default::default()
         },
     );
 
     // We clear the diagnostics, since the language has changed.
-    rust_buffer2.read_with(cx, |buffer, _| {
+    rust_buffer2.update(cx, |buffer, _| {
         assert_eq!(
             buffer
                 .snapshot()
@@ -463,7 +480,7 @@ async fn test_managing_language_servers(
         lsp::TextDocumentItem {
             uri: lsp::Url::from_file_path("/the-root/test.rs").unwrap(),
             version: 0,
-            text: rust_buffer.read_with(cx, |buffer, _| buffer.text()),
+            text: rust_buffer.update(cx, |buffer, _| buffer.text()),
             language_id: Default::default()
         }
     );
@@ -484,13 +501,13 @@ async fn test_managing_language_servers(
             lsp::TextDocumentItem {
                 uri: lsp::Url::from_file_path("/the-root/package.json").unwrap(),
                 version: 0,
-                text: json_buffer.read_with(cx, |buffer, _| buffer.text()),
+                text: json_buffer.update(cx, |buffer, _| buffer.text()),
                 language_id: Default::default()
             },
             lsp::TextDocumentItem {
                 uri: lsp::Url::from_file_path("/the-root/test3.json").unwrap(),
                 version: 0,
-                text: rust_buffer2.read_with(cx, |buffer, _| buffer.text()),
+                text: rust_buffer2.update(cx, |buffer, _| buffer.text()),
                 language_id: Default::default()
             }
         ]
@@ -530,7 +547,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
         }))
         .await;
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/the-root",
         json!({
@@ -564,7 +581,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
     project.update(cx, |project, _| {
         project.languages.add(Arc::new(language));
     });
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
 
     // Start the language server by opening a buffer with a compatible file extension.
     let _buffer = project
@@ -575,8 +592,8 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
         .unwrap();
 
     // Initially, we don't load ignored files because the language server has not explicitly asked us to watch them.
-    project.read_with(cx, |project, cx| {
-        let worktree = project.worktrees(cx).next().unwrap();
+    project.update(cx, |project, cx| {
+        let worktree = project.worktrees().next().unwrap();
         assert_eq!(
             worktree
                 .read(cx)
@@ -643,14 +660,14 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
         }
     });
 
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
     assert_eq!(mem::take(&mut *file_changes.lock()), &[]);
     assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 4);
 
     // Now the language server has asked us to watch an ignored directory path,
     // so we recursively load it.
-    project.read_with(cx, |project, cx| {
-        let worktree = project.worktrees(cx).next().unwrap();
+    project.update(cx, |project, cx| {
+        let worktree = project.worktrees().next().unwrap();
         assert_eq!(
             worktree
                 .read(cx)
@@ -693,7 +710,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
         .unwrap();
 
     // The language server receives events for the FS mutations that match its watch patterns.
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
     assert_eq!(
         &*file_changes.lock(),
         &[
@@ -717,7 +734,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
 async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -775,7 +792,7 @@ async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
             .unwrap();
     });
 
-    buffer_a.read_with(cx, |buffer, _| {
+    buffer_a.update(cx, |buffer, _| {
         let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
         assert_eq!(
             chunks
@@ -789,7 +806,7 @@ async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
             ]
         );
     });
-    buffer_b.read_with(cx, |buffer, _| {
+    buffer_b.update(cx, |buffer, _| {
         let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
         assert_eq!(
             chunks
@@ -809,7 +826,7 @@ async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
 async fn test_omitted_diagnostics(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/root",
         json!({
@@ -841,7 +858,7 @@ async fn test_omitted_diagnostics(cx: &mut gpui::TestAppContext) {
         })
         .await
         .unwrap();
-    let other_worktree_id = worktree.read_with(cx, |tree, _| tree.id());
+    let other_worktree_id = worktree.update(cx, |tree, _| tree.id());
 
     let server_id = LanguageServerId(0);
     project.update(cx, |project, cx| {
@@ -887,7 +904,7 @@ async fn test_omitted_diagnostics(cx: &mut gpui::TestAppContext) {
         })
         .await
         .unwrap();
-    main_ignored_buffer.read_with(cx, |buffer, _| {
+    main_ignored_buffer.update(cx, |buffer, _| {
         let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
         assert_eq!(
             chunks
@@ -908,7 +925,7 @@ async fn test_omitted_diagnostics(cx: &mut gpui::TestAppContext) {
         })
         .await
         .unwrap();
-    other_buffer.read_with(cx, |buffer, _| {
+    other_buffer.update(cx, |buffer, _| {
         let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
         assert_eq!(
             chunks
@@ -924,7 +941,7 @@ async fn test_omitted_diagnostics(cx: &mut gpui::TestAppContext) {
         );
     });
 
-    project.read_with(cx, |project, cx| {
+    project.update(cx, |project, cx| {
         assert_eq!(project.diagnostic_summaries(false, cx).next(), None);
         assert_eq!(
             project.diagnostic_summaries(true, cx).collect::<Vec<_>>(),
@@ -966,7 +983,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
         }))
         .await;
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -978,7 +995,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
 
     let project = Project::test(fs, ["/dir".as_ref()], cx).await;
     project.update(cx, |project, _| project.languages.add(Arc::new(language)));
-    let worktree_id = project.read_with(cx, |p, cx| p.worktrees(cx).next().unwrap().read(cx).id());
+    let worktree_id = project.update(cx, |p, cx| p.worktrees().next().unwrap().read(cx).id());
 
     // Cause worktree to start the fake language server
     let _buffer = project
@@ -986,7 +1003,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
         .await
         .unwrap();
 
-    let mut events = subscribe(&project, cx);
+    let mut events = cx.events(&project);
 
     let fake_server = fake_servers.next().await.unwrap();
     assert_eq!(
@@ -1035,7 +1052,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
         .await
         .unwrap();
 
-    buffer.read_with(cx, |buffer, _| {
+    buffer.update(cx, |buffer, _| {
         let snapshot = buffer.snapshot();
         let diagnostics = snapshot
             .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
@@ -1074,7 +1091,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
         version: None,
         diagnostics: Default::default(),
     });
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
     assert_eq!(futures::poll!(events.next()), Poll::Pending);
 }
 
@@ -1098,7 +1115,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
         }))
         .await;
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree("/dir", json!({ "a.rs": "" })).await;
 
     let project = Project::test(fs, ["/dir".as_ref()], cx).await;
@@ -1117,7 +1134,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
     project.update(cx, |project, cx| {
         project.restart_language_servers_for_buffers([buffer], cx);
     });
-    let mut events = subscribe(&project, cx);
+    let mut events = cx.events(&project);
 
     // Simulate the newly started server sending more diagnostics.
     let fake_server = fake_servers.next().await.unwrap();
@@ -1132,7 +1149,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
             language_server_id: LanguageServerId(1)
         }
     );
-    project.read_with(cx, |project, _| {
+    project.update(cx, |project, _| {
         assert_eq!(
             project
                 .language_servers_running_disk_based_diagnostics()
@@ -1150,7 +1167,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
             language_server_id: LanguageServerId(1)
         }
     );
-    project.read_with(cx, |project, _| {
+    project.update(cx, |project, _| {
         assert_eq!(
             project
                 .language_servers_running_disk_based_diagnostics()
@@ -1177,7 +1194,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
         }))
         .await;
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree("/dir", json!({ "a.rs": "x" })).await;
 
     let project = Project::test(fs, ["/dir".as_ref()], cx).await;
@@ -1201,8 +1218,8 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
         }],
     });
 
-    cx.foreground().run_until_parked();
-    buffer.read_with(cx, |buffer, _| {
+    cx.executor().run_until_parked();
+    buffer.update(cx, |buffer, _| {
         assert_eq!(
             buffer
                 .snapshot()
@@ -1212,7 +1229,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
             ["the message".to_string()]
         );
     });
-    project.read_with(cx, |project, cx| {
+    project.update(cx, |project, cx| {
         assert_eq!(
             project.diagnostic_summary(false, cx),
             DiagnosticSummary {
@@ -1227,8 +1244,8 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
     });
 
     // The diagnostics are cleared.
-    cx.foreground().run_until_parked();
-    buffer.read_with(cx, |buffer, _| {
+    cx.executor().run_until_parked();
+    buffer.update(cx, |buffer, _| {
         assert_eq!(
             buffer
                 .snapshot()
@@ -1238,7 +1255,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
             Vec::<String>::new(),
         );
     });
-    project.read_with(cx, |project, cx| {
+    project.update(cx, |project, cx| {
         assert_eq!(
             project.diagnostic_summary(false, cx),
             DiagnosticSummary {
@@ -1267,7 +1284,7 @@ async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::T
         }))
         .await;
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree("/dir", json!({ "a.rs": "" })).await;
 
     let project = Project::test(fs, ["/dir".as_ref()], cx).await;
@@ -1285,7 +1302,7 @@ async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::T
         version: Some(10000),
         diagnostics: Vec::new(),
     });
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
 
     project.update(cx, |project, cx| {
         project.restart_language_servers_for_buffers([buffer.clone()], cx);
@@ -1331,7 +1348,7 @@ async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) {
         }))
         .await;
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree("/dir", json!({ "a.rs": "", "b.js": "" }))
         .await;
 
@@ -1453,7 +1470,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
     "
     .unindent();
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree("/dir", json!({ "a.rs": text })).await;
 
     let project = Project::test(fs, ["/dir".as_ref()], cx).await;
@@ -1506,9 +1523,8 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
     });
 
     // The diagnostics have moved down since they were created.
-    buffer.next_notification(cx).await;
-    cx.foreground().run_until_parked();
-    buffer.read_with(cx, |buffer, _| {
+    cx.executor().run_until_parked();
+    buffer.update(cx, |buffer, _| {
         assert_eq!(
             buffer
                 .snapshot()
@@ -1585,9 +1601,8 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
         ],
     });
 
-    buffer.next_notification(cx).await;
-    cx.foreground().run_until_parked();
-    buffer.read_with(cx, |buffer, _| {
+    cx.executor().run_until_parked();
+    buffer.update(cx, |buffer, _| {
         assert_eq!(
             buffer
                 .snapshot()
@@ -1678,9 +1693,8 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
         ],
     });
 
-    buffer.next_notification(cx).await;
-    cx.foreground().run_until_parked();
-    buffer.read_with(cx, |buffer, _| {
+    cx.executor().run_until_parked();
+    buffer.update(cx, |buffer, _| {
         assert_eq!(
             buffer
                 .snapshot()
@@ -1726,7 +1740,7 @@ async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) {
         "let three = 3;\n",
     );
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree("/dir", json!({ "a.rs": text })).await;
 
     let project = Project::test(fs, ["/dir".as_ref()], cx).await;
@@ -1767,7 +1781,7 @@ async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) {
     // An empty range is extended forward to include the following character.
     // At the end of a line, an empty range is extended backward to include
     // the preceding character.
-    buffer.read_with(cx, |buffer, _| {
+    buffer.update(cx, |buffer, _| {
         let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
         assert_eq!(
             chunks
@@ -1789,7 +1803,7 @@ async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) {
 async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree("/dir", json!({ "a.rs": "one two three" }))
         .await;
 
@@ -1842,7 +1856,7 @@ async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppC
 }
 
 #[gpui::test]
-async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) {
+async fn test_edits_from_lsp2_with_past_version(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
     let mut language = Language::new(
@@ -1868,7 +1882,7 @@ async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) {
     "
     .unindent();
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -2000,7 +2014,7 @@ async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) {
 }
 
 #[gpui::test]
-async fn test_edits_from_lsp_with_edits_on_adjacent_lines(cx: &mut gpui::TestAppContext) {
+async fn test_edits_from_lsp2_with_edits_on_adjacent_lines(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
     let text = "
@@ -2014,7 +2028,7 @@ async fn test_edits_from_lsp_with_edits_on_adjacent_lines(cx: &mut gpui::TestApp
     "
     .unindent();
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -2108,7 +2122,7 @@ async fn test_edits_from_lsp_with_edits_on_adjacent_lines(cx: &mut gpui::TestApp
 }
 
 #[gpui::test]
-async fn test_invalid_edits_from_lsp(cx: &mut gpui::TestAppContext) {
+async fn test_invalid_edits_from_lsp2(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
     let text = "
@@ -2122,7 +2136,7 @@ async fn test_invalid_edits_from_lsp(cx: &mut gpui::TestAppContext) {
     "
     .unindent();
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -2242,7 +2256,7 @@ async fn test_definition(cx: &mut gpui::TestAppContext) {
     );
     let mut fake_servers = language.set_fake_lsp_adapter(Default::default()).await;
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -2283,7 +2297,7 @@ async fn test_definition(cx: &mut gpui::TestAppContext) {
         .unwrap();
 
     // Assert no new language server started
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
     assert!(fake_servers.try_next().is_err());
 
     assert_eq!(definitions.len(), 1);
@@ -2307,17 +2321,17 @@ async fn test_definition(cx: &mut gpui::TestAppContext) {
 
         drop(definition);
     });
-    cx.read(|cx| {
+    cx.update(|cx| {
         assert_eq!(list_worktrees(&project, cx), [("/dir/b.rs".as_ref(), true)]);
     });
 
     fn list_worktrees<'a>(
-        project: &'a ModelHandle<Project>,
+        project: &'a Model<Project>,
         cx: &'a AppContext,
     ) -> Vec<(&'a Path, bool)> {
         project
             .read(cx)
-            .worktrees(cx)
+            .worktrees()
             .map(|worktree| {
                 let worktree = worktree.read(cx);
                 (
@@ -2354,7 +2368,7 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
         }))
         .await;
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -2391,7 +2405,7 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
         .next()
         .await;
     let completions = completions.await.unwrap();
-    let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
+    let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
     assert_eq!(completions.len(), 1);
     assert_eq!(completions[0].new_text, "fullyQualifiedName");
     assert_eq!(
@@ -2417,7 +2431,7 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
         .next()
         .await;
     let completions = completions.await.unwrap();
-    let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
+    let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
     assert_eq!(completions.len(), 1);
     assert_eq!(completions[0].new_text, "component");
     assert_eq!(
@@ -2451,7 +2465,7 @@ async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) {
         }))
         .await;
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -2506,7 +2520,7 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
     );
     let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -2614,7 +2628,7 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
 async fn test_save_file(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -2639,14 +2653,133 @@ async fn test_save_file(cx: &mut gpui::TestAppContext) {
         .unwrap();
 
     let new_text = fs.load(Path::new("/dir/file1")).await.unwrap();
-    assert_eq!(new_text, buffer.read_with(cx, |buffer, _| buffer.text()));
+    assert_eq!(new_text, buffer.update(cx, |buffer, _| buffer.text()));
+}
+
+#[gpui::test(iterations = 30)]
+async fn test_file_changes_multiple_times_on_disk(cx: &mut gpui::TestAppContext) {
+    init_test(cx);
+
+    let fs = FakeFs::new(cx.executor().clone());
+    fs.insert_tree(
+        "/dir",
+        json!({
+            "file1": "the original contents",
+        }),
+    )
+    .await;
+
+    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
+    let worktree = project.read_with(cx, |project, _| project.worktrees().next().unwrap());
+    let buffer = project
+        .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
+        .await
+        .unwrap();
+
+    // Simulate buffer diffs being slow, so that they don't complete before
+    // the next file change occurs.
+    cx.executor().deprioritize(*language::BUFFER_DIFF_TASK);
+
+    // Change the buffer's file on disk, and then wait for the file change
+    // to be detected by the worktree, so that the buffer starts reloading.
+    fs.save(
+        "/dir/file1".as_ref(),
+        &"the first contents".into(),
+        Default::default(),
+    )
+    .await
+    .unwrap();
+    worktree.next_event(cx);
+
+    // Change the buffer's file again. Depending on the random seed, the
+    // previous file change may still be in progress.
+    fs.save(
+        "/dir/file1".as_ref(),
+        &"the second contents".into(),
+        Default::default(),
+    )
+    .await
+    .unwrap();
+    worktree.next_event(cx);
+
+    cx.executor().run_until_parked();
+    let on_disk_text = fs.load(Path::new("/dir/file1")).await.unwrap();
+    buffer.read_with(cx, |buffer, _| {
+        assert_eq!(buffer.text(), on_disk_text);
+        assert!(!buffer.is_dirty(), "buffer should not be dirty");
+        assert!(!buffer.has_conflict(), "buffer should not be dirty");
+    });
+}
+
+#[gpui::test(iterations = 30)]
+async fn test_edit_buffer_while_it_reloads(cx: &mut gpui::TestAppContext) {
+    init_test(cx);
+
+    let fs = FakeFs::new(cx.executor().clone());
+    fs.insert_tree(
+        "/dir",
+        json!({
+            "file1": "the original contents",
+        }),
+    )
+    .await;
+
+    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
+    let worktree = project.read_with(cx, |project, _| project.worktrees().next().unwrap());
+    let buffer = project
+        .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
+        .await
+        .unwrap();
+
+    // Simulate buffer diffs being slow, so that they don't complete before
+    // the next file change occurs.
+    cx.executor().deprioritize(*language::BUFFER_DIFF_TASK);
+
+    // Change the buffer's file on disk, and then wait for the file change
+    // to be detected by the worktree, so that the buffer starts reloading.
+    fs.save(
+        "/dir/file1".as_ref(),
+        &"the first contents".into(),
+        Default::default(),
+    )
+    .await
+    .unwrap();
+    worktree.next_event(cx);
+
+    cx.executor()
+        .spawn(cx.executor().simulate_random_delay())
+        .await;
+
+    // Perform a noop edit, causing the buffer's version to increase.
+    buffer.update(cx, |buffer, cx| {
+        buffer.edit([(0..0, " ")], None, cx);
+        buffer.undo(cx);
+    });
+
+    cx.executor().run_until_parked();
+    let on_disk_text = fs.load(Path::new("/dir/file1")).await.unwrap();
+    buffer.read_with(cx, |buffer, _| {
+        let buffer_text = buffer.text();
+        if buffer_text == on_disk_text {
+            assert!(
+                !buffer.is_dirty() && !buffer.has_conflict(),
+                "buffer shouldn't be dirty. text: {buffer_text:?}, disk text: {on_disk_text:?}",
+            );
+        }
+        // If the file change occurred while the buffer was processing the first
+        // change, the buffer will be in a conflicting state.
+        else {
+            assert!(buffer.is_dirty(), "buffer should report that it is dirty. text: {buffer_text:?}, disk text: {on_disk_text:?}");
+            assert!(buffer.has_conflict(), "buffer should report that it is dirty. text: {buffer_text:?}, disk text: {on_disk_text:?}");
+        }
+    });
 }
 
 #[gpui::test]
 async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -2670,75 +2803,72 @@ async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) {
         .unwrap();
 
     let new_text = fs.load(Path::new("/dir/file1")).await.unwrap();
-    assert_eq!(new_text, buffer.read_with(cx, |buffer, _| buffer.text()));
+    assert_eq!(new_text, buffer.update(cx, |buffer, _| buffer.text()));
 }
 
-// #[gpui::test]
-// async fn test_save_as(cx: &mut gpui::TestAppContext) {
-//     init_test(cx);
-
-//     let fs = FakeFs::new(cx.background());
-//     fs.insert_tree("/dir", json!({})).await;
-
-//     let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-
-//     let languages = project.read_with(cx, |project, _| project.languages().clone());
-//     languages.register(
-//         "/some/path",
-//         LanguageConfig {
-//             name: "Rust".into(),
-//             path_suffixes: vec!["rs".into()],
-//             ..Default::default()
-//         },
-//         tree_sitter_rust::language(),
-//         vec![],
-//         |_| Default::default(),
-//     );
-
-//     let buffer = project.update(cx, |project, cx| {
-//         project.create_buffer("", None, cx).unwrap()
-//     });
-//     buffer.update(cx, |buffer, cx| {
-//         buffer.edit([(0..0, "abc")], None, cx);
-//         assert!(buffer.is_dirty());
-//         assert!(!buffer.has_conflict());
-//         assert_eq!(buffer.language().unwrap().name().as_ref(), "Plain Text");
-//     });
-//     project
-//         .update(cx, |project, cx| {
-//             project.save_buffer_as(buffer.clone(), "/dir/file1.rs".into(), cx)
-//         })
-//         .await
-//         .unwrap();
-//     assert_eq!(fs.load(Path::new("/dir/file1.rs")).await.unwrap(), "abc");
-
-//     cx.foreground().run_until_parked();
-//     buffer.read_with(cx, |buffer, cx| {
-//         assert_eq!(
-//             buffer.file().unwrap().full_path(cx),
-//             Path::new("dir/file1.rs")
-//         );
-//         assert!(!buffer.is_dirty());
-//         assert!(!buffer.has_conflict());
-//         assert_eq!(buffer.language().unwrap().name().as_ref(), "Rust");
-//     });
-
-//     let opened_buffer = project
-//         .update(cx, |project, cx| {
-//             project.open_local_buffer("/dir/file1.rs", cx)
-//         })
-//         .await
-//         .unwrap();
-//     assert_eq!(opened_buffer, buffer);
-// }
+#[gpui::test]
+async fn test_save_as(cx: &mut gpui::TestAppContext) {
+    init_test(cx);
+
+    let fs = FakeFs::new(cx.executor());
+    fs.insert_tree("/dir", json!({})).await;
+
+    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
+
+    let languages = project.update(cx, |project, _| project.languages().clone());
+    languages.register(
+        "/some/path",
+        LanguageConfig {
+            name: "Rust".into(),
+            path_suffixes: vec!["rs".into()],
+            ..Default::default()
+        },
+        tree_sitter_rust::language(),
+        vec![],
+        |_| Default::default(),
+    );
+
+    let buffer = project.update(cx, |project, cx| {
+        project.create_buffer("", None, cx).unwrap()
+    });
+    buffer.update(cx, |buffer, cx| {
+        buffer.edit([(0..0, "abc")], None, cx);
+        assert!(buffer.is_dirty());
+        assert!(!buffer.has_conflict());
+        assert_eq!(buffer.language().unwrap().name().as_ref(), "Plain Text");
+    });
+    project
+        .update(cx, |project, cx| {
+            project.save_buffer_as(buffer.clone(), "/dir/file1.rs".into(), cx)
+        })
+        .await
+        .unwrap();
+    assert_eq!(fs.load(Path::new("/dir/file1.rs")).await.unwrap(), "abc");
+
+    cx.executor().run_until_parked();
+    buffer.update(cx, |buffer, cx| {
+        assert_eq!(
+            buffer.file().unwrap().full_path(cx),
+            Path::new("dir/file1.rs")
+        );
+        assert!(!buffer.is_dirty());
+        assert!(!buffer.has_conflict());
+        assert_eq!(buffer.language().unwrap().name().as_ref(), "Rust");
+    });
+
+    let opened_buffer = project
+        .update(cx, |project, cx| {
+            project.open_local_buffer("/dir/file1.rs", cx)
+        })
+        .await
+        .unwrap();
+    assert_eq!(opened_buffer, buffer);
+}
 
 #[gpui::test(retries = 5)]
-async fn test_rescan_and_remote_updates(
-    deterministic: Arc<Deterministic>,
-    cx: &mut gpui::TestAppContext,
-) {
+async fn test_rescan_and_remote_updates(cx: &mut gpui::TestAppContext) {
     init_test(cx);
-    cx.foreground().allow_parking();
+    cx.executor().allow_parking();
 
     let dir = temp_tree(json!({
         "a": {

crates/project/src/terminals.rs 🔗

@@ -1,5 +1,6 @@
 use crate::Project;
-use gpui::{AnyWindowHandle, ModelContext, ModelHandle, WeakModelHandle};
+use gpui::{AnyWindowHandle, Context, Entity, Model, ModelContext, WeakModel};
+use settings::Settings;
 use std::path::{Path, PathBuf};
 use terminal::{
     terminal_settings::{self, TerminalSettings, VenvSettingsContent},
@@ -10,7 +11,7 @@ use terminal::{
 use std::os::unix::ffi::OsStrExt;
 
 pub struct Terminals {
-    pub(crate) local_handles: Vec<WeakModelHandle<terminal::Terminal>>,
+    pub(crate) local_handles: Vec<WeakModel<terminal::Terminal>>,
 }
 
 impl Project {
@@ -19,13 +20,13 @@ impl Project {
         working_directory: Option<PathBuf>,
         window: AnyWindowHandle,
         cx: &mut ModelContext<Self>,
-    ) -> anyhow::Result<ModelHandle<Terminal>> {
+    ) -> anyhow::Result<Model<Terminal>> {
         if self.is_remote() {
             return Err(anyhow::anyhow!(
                 "creating terminals as a guest is not supported yet"
             ));
         } else {
-            let settings = settings::get::<TerminalSettings>(cx);
+            let settings = TerminalSettings::get_global(cx);
             let python_settings = settings.detect_venv.clone();
             let shell = settings.shell.clone();
 
@@ -38,17 +39,20 @@ impl Project {
                 window,
             )
             .map(|builder| {
-                let terminal_handle = cx.add_model(|cx| builder.subscribe(cx));
+                let terminal_handle = cx.new_model(|cx| builder.subscribe(cx));
 
                 self.terminals
                     .local_handles
                     .push(terminal_handle.downgrade());
 
-                let id = terminal_handle.id();
+                let id = terminal_handle.entity_id();
                 cx.observe_release(&terminal_handle, move |project, _terminal, cx| {
                     let handles = &mut project.terminals.local_handles;
 
-                    if let Some(index) = handles.iter().position(|terminal| terminal.id() == id) {
+                    if let Some(index) = handles
+                        .iter()
+                        .position(|terminal| terminal.entity_id() == id)
+                    {
                         handles.remove(index);
                         cx.notify();
                     }
@@ -103,7 +107,7 @@ impl Project {
     fn activate_python_virtual_environment(
         &mut self,
         activate_script: Option<PathBuf>,
-        terminal_handle: &ModelHandle<Terminal>,
+        terminal_handle: &Model<Terminal>,
         cx: &mut ModelContext<Project>,
     ) {
         if let Some(activate_script) = activate_script {
@@ -116,7 +120,7 @@ impl Project {
         }
     }
 
-    pub fn local_terminal_handles(&self) -> &Vec<WeakModelHandle<terminal::Terminal>> {
+    pub fn local_terminal_handles(&self) -> &Vec<WeakModel<terminal::Terminal>> {
         &self.terminals.local_handles
     }
 }

crates/project/src/worktree.rs 🔗

@@ -3,7 +3,7 @@ use crate::{
     ProjectEntryId, RemoveOptions,
 };
 use ::ignore::gitignore::{Gitignore, GitignoreBuilder};
-use anyhow::{anyhow, Context, Result};
+use anyhow::{anyhow, Context as _, Result};
 use client::{proto, Client};
 use clock::ReplicaId;
 use collections::{HashMap, HashSet, VecDeque};
@@ -18,12 +18,13 @@ use futures::{
     },
     select_biased,
     task::Poll,
-    FutureExt, Stream, StreamExt,
+    FutureExt as _, Stream, StreamExt,
 };
 use fuzzy::CharBag;
 use git::{DOT_GIT, GITIGNORE};
 use gpui::{
-    executor, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Subscription, Task,
+    AppContext, AsyncAppContext, BackgroundExecutor, Context, EventEmitter, Model, ModelContext,
+    Task,
 };
 use itertools::Itertools;
 use language::{
@@ -40,7 +41,7 @@ use postage::{
     prelude::{Sink as _, Stream as _},
     watch,
 };
-use settings::SettingsStore;
+use settings::{Settings, SettingsStore};
 use smol::channel::{self, Sender};
 use std::{
     any::Any,
@@ -78,7 +79,6 @@ pub struct LocalWorktree {
     scan_requests_tx: channel::Sender<ScanRequest>,
     path_prefixes_to_scan_tx: channel::Sender<Arc<Path>>,
     is_scanning: (watch::Sender<bool>, watch::Receiver<bool>),
-    _settings_subscription: Subscription,
     _background_scanner_tasks: Vec<Task<()>>,
     share: Option<ShareState>,
     diagnostics: HashMap<
@@ -283,14 +283,13 @@ struct ShareState {
     _maintain_remote_snapshot: Task<Option<()>>,
 }
 
+#[derive(Clone)]
 pub enum Event {
     UpdatedEntries(UpdatedEntriesSet),
     UpdatedGitRepositories(UpdatedGitRepositoriesSet),
 }
 
-impl Entity for Worktree {
-    type Event = Event;
-}
+impl EventEmitter<Event> for Worktree {}
 
 impl Worktree {
     pub async fn local(
@@ -300,10 +299,11 @@ impl Worktree {
         fs: Arc<dyn Fs>,
         next_entry_id: Arc<AtomicUsize>,
         cx: &mut AsyncAppContext,
-    ) -> Result<ModelHandle<Self>> {
+    ) -> Result<Model<Self>> {
         // After determining whether the root entry is a file or a directory, populate the
         // snapshot's "root name", which will be used for the purpose of fuzzy matching.
         let abs_path = path.into();
+
         let metadata = fs
             .metadata(&abs_path)
             .await
@@ -312,11 +312,11 @@ impl Worktree {
         let closure_fs = Arc::clone(&fs);
         let closure_next_entry_id = Arc::clone(&next_entry_id);
         let closure_abs_path = abs_path.to_path_buf();
-        Ok(cx.add_model(move |cx: &mut ModelContext<Worktree>| {
-            let settings_subscription = cx.observe_global::<SettingsStore, _>(move |this, cx| {
+        cx.new_model(move |cx: &mut ModelContext<Worktree>| {
+            cx.observe_global::<SettingsStore>(move |this, cx| {
                 if let Self::Local(this) = this {
                     let new_file_scan_exclusions =
-                        file_scan_exclusions(settings::get::<ProjectSettings>(cx));
+                        file_scan_exclusions(ProjectSettings::get_global(cx));
                     if new_file_scan_exclusions != this.snapshot.file_scan_exclusions {
                         this.snapshot.file_scan_exclusions = new_file_scan_exclusions;
                         log::info!(
@@ -345,17 +345,19 @@ impl Worktree {
                         this.is_scanning = watch::channel_with(true);
                     }
                 }
-            });
+            })
+            .detach();
 
             let root_name = abs_path
                 .file_name()
                 .map_or(String::new(), |f| f.to_string_lossy().to_string());
+
             let mut snapshot = LocalSnapshot {
-                file_scan_exclusions: file_scan_exclusions(settings::get::<ProjectSettings>(cx)),
+                file_scan_exclusions: file_scan_exclusions(ProjectSettings::get_global(cx)),
                 ignores_by_parent_abs_path: Default::default(),
                 git_repositories: Default::default(),
                 snapshot: Snapshot {
-                    id: WorktreeId::from_usize(cx.model_id()),
+                    id: WorktreeId::from_usize(cx.entity_id().as_u64() as usize),
                     abs_path: abs_path.to_path_buf().into(),
                     root_name: root_name.clone(),
                     root_char_bag: root_name.chars().map(|c| c.to_ascii_lowercase()).collect(),
@@ -388,7 +390,6 @@ impl Worktree {
                 share: None,
                 scan_requests_tx,
                 path_prefixes_to_scan_tx,
-                _settings_subscription: settings_subscription,
                 _background_scanner_tasks: start_background_scan_tasks(
                     &abs_path,
                     task_snapshot,
@@ -404,18 +405,17 @@ impl Worktree {
                 fs,
                 visible,
             })
-        }))
+        })
     }
 
-    // abcdefghi
     pub fn remote(
         project_remote_id: u64,
         replica_id: ReplicaId,
         worktree: proto::WorktreeMetadata,
         client: Arc<Client>,
         cx: &mut AppContext,
-    ) -> ModelHandle<Self> {
-        cx.add_model(|cx: &mut ModelContext<Self>| {
+    ) -> Model<Self> {
+        cx.new_model(|cx: &mut ModelContext<Self>| {
             let snapshot = Snapshot {
                 id: WorktreeId(worktree.id as usize),
                 abs_path: Arc::from(PathBuf::from(worktree.abs_path)),
@@ -436,7 +436,7 @@ impl Worktree {
             let background_snapshot = Arc::new(Mutex::new(snapshot.clone()));
             let (mut snapshot_updated_tx, mut snapshot_updated_rx) = watch::channel();
 
-            cx.background()
+            cx.background_executor()
                 .spawn({
                     let background_snapshot = background_snapshot.clone();
                     async move {
@@ -452,27 +452,24 @@ impl Worktree {
                 })
                 .detach();
 
-            cx.spawn_weak(|this, mut cx| async move {
+            cx.spawn(|this, mut cx| async move {
                 while (snapshot_updated_rx.recv().await).is_some() {
-                    if let Some(this) = this.upgrade(&cx) {
-                        this.update(&mut cx, |this, cx| {
-                            let this = this.as_remote_mut().unwrap();
-                            this.snapshot = this.background_snapshot.lock().clone();
-                            cx.emit(Event::UpdatedEntries(Arc::from([])));
-                            cx.notify();
-                            while let Some((scan_id, _)) = this.snapshot_subscriptions.front() {
-                                if this.observed_snapshot(*scan_id) {
-                                    let (_, tx) = this.snapshot_subscriptions.pop_front().unwrap();
-                                    let _ = tx.send(());
-                                } else {
-                                    break;
-                                }
+                    this.update(&mut cx, |this, cx| {
+                        let this = this.as_remote_mut().unwrap();
+                        this.snapshot = this.background_snapshot.lock().clone();
+                        cx.emit(Event::UpdatedEntries(Arc::from([])));
+                        cx.notify();
+                        while let Some((scan_id, _)) = this.snapshot_subscriptions.front() {
+                            if this.observed_snapshot(*scan_id) {
+                                let (_, tx) = this.snapshot_subscriptions.pop_front().unwrap();
+                                let _ = tx.send(());
+                            } else {
+                                break;
                             }
-                        });
-                    } else {
-                        break;
-                    }
+                        }
+                    })?;
                 }
+                anyhow::Ok(())
             })
             .detach();
 
@@ -604,9 +601,9 @@ fn start_background_scan_tasks(
     cx: &mut ModelContext<'_, Worktree>,
 ) -> Vec<Task<()>> {
     let (scan_states_tx, mut scan_states_rx) = mpsc::unbounded();
-    let background_scanner = cx.background().spawn({
+    let background_scanner = cx.background_executor().spawn({
         let abs_path = abs_path.to_path_buf();
-        let background = cx.background().clone();
+        let background = cx.background_executor().clone();
         async move {
             let events = fs.watch(&abs_path, Duration::from_millis(100)).await;
             BackgroundScanner::new(
@@ -622,8 +619,8 @@ fn start_background_scan_tasks(
             .await;
         }
     });
-    let scan_state_updater = cx.spawn_weak(|this, mut cx| async move {
-        while let Some((state, this)) = scan_states_rx.next().await.zip(this.upgrade(&cx)) {
+    let scan_state_updater = cx.spawn(|this, mut cx| async move {
+        while let Some((state, this)) = scan_states_rx.next().await.zip(this.upgrade()) {
             this.update(&mut cx, |this, cx| {
                 let this = this.as_local_mut().unwrap();
                 match state {
@@ -642,7 +639,8 @@ fn start_background_scan_tasks(
                     }
                 }
                 cx.notify();
-            });
+            })
+            .ok();
         }
     });
     vec![background_scanner, scan_state_updater]
@@ -674,17 +672,17 @@ impl LocalWorktree {
         id: u64,
         path: &Path,
         cx: &mut ModelContext<Worktree>,
-    ) -> Task<Result<ModelHandle<Buffer>>> {
+    ) -> Task<Result<Model<Buffer>>> {
         let path = Arc::from(path);
         cx.spawn(move |this, mut cx| async move {
             let (file, contents, diff_base) = this
-                .update(&mut cx, |t, cx| t.as_local().unwrap().load(&path, cx))
+                .update(&mut cx, |t, cx| t.as_local().unwrap().load(&path, cx))?
                 .await?;
             let text_buffer = cx
-                .background()
+                .background_executor()
                 .spawn(async move { text::Buffer::new(0, id, contents) })
                 .await;
-            Ok(cx.add_model(|_| Buffer::build(text_buffer, diff_base, Some(Arc::new(file)))))
+            cx.new_model(|_| Buffer::build(text_buffer, diff_base, Some(Arc::new(file))))
         })
     }
 
@@ -958,24 +956,17 @@ impl LocalWorktree {
         let fs = self.fs.clone();
         let entry = self.refresh_entry(path.clone(), None, cx);
 
-        cx.spawn(|this, cx| async move {
+        cx.spawn(|this, mut cx| async move {
             let text = fs.load(&abs_path).await?;
             let mut index_task = None;
-            let snapshot = this.read_with(&cx, |this, _| this.as_local().unwrap().snapshot());
+            let snapshot = this.update(&mut cx, |this, _| this.as_local().unwrap().snapshot())?;
             if let Some(repo) = snapshot.repository_for_path(&path) {
-                if let Some(repo_path) = repo.work_directory.relativize(&snapshot, &path) {
-                    if let Some(repo) = snapshot.git_repositories.get(&*repo.work_directory) {
-                        let repo = repo.repo_ptr.clone();
-                        index_task = Some(
-                            cx.background()
-                                .spawn(async move { repo.lock().load_index_text(&repo_path) }),
-                        );
-                    }
-                } else {
-                    log::warn!(
-                        "Skipping loading index text from path {:?} is not in repository {:?}",
-                        path,
-                        repo.work_directory,
+                let repo_path = repo.work_directory.relativize(&snapshot, &path).unwrap();
+                if let Some(repo) = snapshot.git_repositories.get(&*repo.work_directory) {
+                    let repo = repo.repo_ptr.clone();
+                    index_task = Some(
+                        cx.background_executor()
+                            .spawn(async move { repo.lock().load_index_text(&repo_path) }),
                     );
                 }
             }
@@ -986,11 +977,14 @@ impl LocalWorktree {
                 None
             };
 
+            let worktree = this
+                .upgrade()
+                .ok_or_else(|| anyhow!("worktree was dropped"))?;
             match entry.await? {
                 Some(entry) => Ok((
                     File {
                         entry_id: Some(entry.id),
-                        worktree: this,
+                        worktree,
                         path: entry.path,
                         mtime: entry.mtime,
                         is_local: true,
@@ -1012,7 +1006,7 @@ impl LocalWorktree {
                     Ok((
                         File {
                             entry_id: None,
-                            worktree: this,
+                            worktree,
                             path,
                             mtime: metadata.mtime,
                             is_local: true,
@@ -1028,12 +1022,11 @@ impl LocalWorktree {
 
     pub fn save_buffer(
         &self,
-        buffer_handle: ModelHandle<Buffer>,
+        buffer_handle: Model<Buffer>,
         path: Arc<Path>,
         has_changed_file: bool,
         cx: &mut ModelContext<Worktree>,
     ) -> Task<Result<()>> {
-        let handle = cx.handle();
         let buffer = buffer_handle.read(cx);
 
         let rpc = self.client.clone();
@@ -1047,8 +1040,9 @@ impl LocalWorktree {
         let fs = Arc::clone(&self.fs);
         let abs_path = self.absolutize(&path);
 
-        cx.as_mut().spawn(|mut cx| async move {
+        cx.spawn(move |this, mut cx| async move {
             let entry = save.await?;
+            let this = this.upgrade().context("worktree dropped")?;
 
             let (entry_id, mtime, path) = match entry {
                 Some(entry) => (Some(entry.id), entry.mtime, entry.path),
@@ -1071,7 +1065,7 @@ impl LocalWorktree {
             if has_changed_file {
                 let new_file = Arc::new(File {
                     entry_id,
-                    worktree: handle,
+                    worktree: this,
                     path,
                     mtime,
                     is_local: true,
@@ -1091,7 +1085,7 @@ impl LocalWorktree {
                     if has_changed_file {
                         buffer.file_updated(new_file, cx);
                     }
-                });
+                })?;
             }
 
             if let Some(project_id) = project_id {
@@ -1106,7 +1100,7 @@ impl LocalWorktree {
 
             buffer_handle.update(&mut cx, |buffer, cx| {
                 buffer.did_save(version.clone(), fingerprint, mtime, cx);
-            });
+            })?;
 
             Ok(())
         })
@@ -1135,7 +1129,7 @@ impl LocalWorktree {
         let lowest_ancestor = self.lowest_ancestor(&path);
         let abs_path = self.absolutize(&path);
         let fs = self.fs.clone();
-        let write = cx.background().spawn(async move {
+        let write = cx.background_executor().spawn(async move {
             if is_dir {
                 fs.create_dir(&abs_path).await
             } else {
@@ -1165,7 +1159,7 @@ impl LocalWorktree {
                     this.as_local_mut().unwrap().refresh_entry(path, None, cx),
                     refreshes,
                 )
-            });
+            })?;
             for refresh in refreshes {
                 refresh.await.log_err();
             }
@@ -1185,14 +1179,14 @@ impl LocalWorktree {
         let abs_path = self.absolutize(&path);
         let fs = self.fs.clone();
         let write = cx
-            .background()
+            .background_executor()
             .spawn(async move { fs.save(&abs_path, &text, line_ending).await });
 
         cx.spawn(|this, mut cx| async move {
             write.await?;
             this.update(&mut cx, |this, cx| {
                 this.as_local_mut().unwrap().refresh_entry(path, None, cx)
-            })
+            })?
             .await
         })
     }
@@ -1206,7 +1200,7 @@ impl LocalWorktree {
         let abs_path = self.absolutize(&entry.path);
         let fs = self.fs.clone();
 
-        let delete = cx.background().spawn(async move {
+        let delete = cx.background_executor().spawn(async move {
             if entry.is_file() {
                 fs.remove_file(&abs_path, Default::default()).await?;
             } else {
@@ -1228,7 +1222,7 @@ impl LocalWorktree {
                 this.as_local_mut()
                     .unwrap()
                     .refresh_entries_for_paths(vec![path])
-            })
+            })?
             .recv()
             .await;
             Ok(())
@@ -1249,7 +1243,7 @@ impl LocalWorktree {
         let abs_old_path = self.absolutize(&old_path);
         let abs_new_path = self.absolutize(&new_path);
         let fs = self.fs.clone();
-        let rename = cx.background().spawn(async move {
+        let rename = cx.background_executor().spawn(async move {
             fs.rename(&abs_old_path, &abs_new_path, Default::default())
                 .await
         });
@@ -1260,7 +1254,7 @@ impl LocalWorktree {
                 this.as_local_mut()
                     .unwrap()
                     .refresh_entry(new_path.clone(), Some(old_path), cx)
-            })
+            })?
             .await
         })
     }
@@ -1279,7 +1273,7 @@ impl LocalWorktree {
         let abs_old_path = self.absolutize(&old_path);
         let abs_new_path = self.absolutize(&new_path);
         let fs = self.fs.clone();
-        let copy = cx.background().spawn(async move {
+        let copy = cx.background_executor().spawn(async move {
             copy_recursive(
                 fs.as_ref(),
                 &abs_old_path,
@@ -1295,7 +1289,7 @@ impl LocalWorktree {
                 this.as_local_mut()
                     .unwrap()
                     .refresh_entry(new_path.clone(), None, cx)
-            })
+            })?
             .await
         })
     }
@@ -1307,7 +1301,7 @@ impl LocalWorktree {
     ) -> Option<Task<Result<()>>> {
         let path = self.entry_for_id(entry_id)?.path.clone();
         let mut refresh = self.refresh_entries_for_paths(vec![path]);
-        Some(cx.background().spawn(async move {
+        Some(cx.background_executor().spawn(async move {
             refresh.next().await;
             Ok(())
         }))
@@ -1343,16 +1337,13 @@ impl LocalWorktree {
             vec![path.clone()]
         };
         let mut refresh = self.refresh_entries_for_paths(paths);
-        cx.spawn_weak(move |this, mut cx| async move {
+        cx.spawn(move |this, mut cx| async move {
             refresh.recv().await;
-            let new_entry = this
-                .upgrade(&cx)
-                .ok_or_else(|| anyhow!("worktree was dropped"))?
-                .update(&mut cx, |this, _| {
-                    this.entry_for_path(path)
-                        .cloned()
-                        .ok_or_else(|| anyhow!("failed to read path after update"))
-                })?;
+            let new_entry = this.update(&mut cx, |this, _| {
+                this.entry_for_path(path)
+                    .cloned()
+                    .ok_or_else(|| anyhow!("failed to read path after update"))
+            })??;
             Ok(Some(new_entry))
         })
     }
@@ -1387,8 +1378,8 @@ impl LocalWorktree {
             .unbounded_send((self.snapshot(), Arc::from([]), Arc::from([])))
             .ok();
 
-        let worktree_id = cx.model_id() as u64;
-        let _maintain_remote_snapshot = cx.background().spawn(async move {
+        let worktree_id = cx.entity_id().as_u64();
+        let _maintain_remote_snapshot = cx.background_executor().spawn(async move {
             let mut is_first = true;
             while let Some((snapshot, entry_changes, repo_changes)) = snapshots_rx.next().await {
                 let update;
@@ -1435,7 +1426,7 @@ impl LocalWorktree {
             for (&server_id, summary) in summaries {
                 if let Err(e) = self.client.send(proto::UpdateDiagnosticSummary {
                     project_id,
-                    worktree_id: cx.model_id() as u64,
+                    worktree_id: cx.entity_id().as_u64(),
                     summary: Some(summary.to_proto(server_id, &path)),
                 }) {
                     return Task::ready(Err(e));
@@ -1446,7 +1437,7 @@ impl LocalWorktree {
         let rx = self.observe_updates(project_id, cx, move |update| {
             client.request(update).map(|result| result.is_ok())
         });
-        cx.foreground()
+        cx.background_executor()
             .spawn(async move { rx.await.map_err(|_| anyhow!("share ended")) })
     }
 
@@ -1472,7 +1463,7 @@ impl RemoteWorktree {
 
     pub fn save_buffer(
         &self,
-        buffer_handle: ModelHandle<Buffer>,
+        buffer_handle: Model<Buffer>,
         cx: &mut ModelContext<Worktree>,
     ) -> Task<Result<()>> {
         let buffer = buffer_handle.read(cx);
@@ -1480,7 +1471,7 @@ impl RemoteWorktree {
         let version = buffer.version();
         let rpc = self.client.clone();
         let project_id = self.project_id;
-        cx.as_mut().spawn(|mut cx| async move {
+        cx.spawn(move |_, mut cx| async move {
             let response = rpc
                 .request(proto::SaveBuffer {
                     project_id,
@@ -1497,7 +1488,7 @@ impl RemoteWorktree {
 
             buffer_handle.update(&mut cx, |buffer, cx| {
                 buffer.did_save(version.clone(), fingerprint, mtime, cx);
-            });
+            })?;
 
             Ok(())
         })
@@ -1577,7 +1568,7 @@ impl RemoteWorktree {
                 let entry = snapshot.insert_entry(entry);
                 worktree.snapshot = snapshot.clone();
                 entry
-            })
+            })?
         })
     }
 
@@ -1588,14 +1579,14 @@ impl RemoteWorktree {
         cx: &mut ModelContext<Worktree>,
     ) -> Task<Result<()>> {
         let wait_for_snapshot = self.wait_for_snapshot(scan_id);
-        cx.spawn(|this, mut cx| async move {
+        cx.spawn(move |this, mut cx| async move {
             wait_for_snapshot.await?;
             this.update(&mut cx, |worktree, _| {
                 let worktree = worktree.as_remote_mut().unwrap();
                 let mut snapshot = worktree.background_snapshot.lock();
                 snapshot.delete_entry(id);
                 worktree.snapshot = snapshot.clone();
-            });
+            })?;
             Ok(())
         })
     }
@@ -2168,16 +2159,11 @@ impl LocalSnapshot {
 
     fn ignore_stack_for_abs_path(&self, abs_path: &Path, is_dir: bool) -> Arc<IgnoreStack> {
         let mut new_ignores = Vec::new();
-        for (index, ancestor) in abs_path.ancestors().enumerate() {
-            if index > 0 {
-                if let Some((ignore, _)) = self.ignores_by_parent_abs_path.get(ancestor) {
-                    new_ignores.push((ancestor, Some(ignore.clone())));
-                } else {
-                    new_ignores.push((ancestor, None));
-                }
-            }
-            if ancestor.join(&*DOT_GIT).is_dir() {
-                break;
+        for ancestor in abs_path.ancestors().skip(1) {
+            if let Some((ignore, _)) = self.ignores_by_parent_abs_path.get(ancestor) {
+                new_ignores.push((ancestor, Some(ignore.clone())));
+            } else {
+                new_ignores.push((ancestor, None));
             }
         }
 
@@ -2194,6 +2180,7 @@ impl LocalSnapshot {
         if ignore_stack.is_abs_path_ignored(abs_path, is_dir) {
             ignore_stack = IgnoreStack::all();
         }
+
         ignore_stack
     }
 
@@ -2471,6 +2458,7 @@ impl BackgroundScannerState {
 
     fn reload_repositories(&mut self, dot_git_dirs_to_reload: &HashSet<PathBuf>, fs: &dyn Fs) {
         let scan_id = self.snapshot.scan_id;
+
         for dot_git_dir in dot_git_dirs_to_reload {
             // If there is already a repository for this .git directory, reload
             // the status for all of its files.
@@ -2732,7 +2720,7 @@ impl fmt::Debug for Snapshot {
 
 #[derive(Clone, PartialEq)]
 pub struct File {
-    pub worktree: ModelHandle<Worktree>,
+    pub worktree: Model<Worktree>,
     pub path: Arc<Path>,
     pub mtime: SystemTime,
     pub(crate) entry_id: Option<ProjectEntryId>,
@@ -2790,7 +2778,7 @@ impl language::File for File {
     }
 
     fn worktree_id(&self) -> usize {
-        self.worktree.id()
+        self.worktree.entity_id().as_u64() as usize
     }
 
     fn is_deleted(&self) -> bool {
@@ -2803,7 +2791,7 @@ impl language::File for File {
 
     fn to_proto(&self) -> rpc::proto::File {
         rpc::proto::File {
-            worktree_id: self.worktree.id() as u64,
+            worktree_id: self.worktree.entity_id().as_u64(),
             entry_id: self.entry_id.map(|id| id.to_proto()),
             path: self.path.to_string_lossy().into(),
             mtime: Some(self.mtime.into()),
@@ -2826,7 +2814,7 @@ impl language::LocalFile for File {
         let worktree = self.worktree.read(cx).as_local().unwrap();
         let abs_path = worktree.absolutize(&self.path);
         let fs = worktree.fs.clone();
-        cx.background()
+        cx.background_executor()
             .spawn(async move { fs.load(&abs_path).await })
     }
 
@@ -2857,7 +2845,7 @@ impl language::LocalFile for File {
 }
 
 impl File {
-    pub fn for_entry(entry: Entry, worktree: ModelHandle<Worktree>) -> Arc<Self> {
+    pub fn for_entry(entry: Entry, worktree: Model<Worktree>) -> Arc<Self> {
         Arc::new(Self {
             worktree,
             path: entry.path.clone(),
@@ -2870,7 +2858,7 @@ impl File {
 
     pub fn from_proto(
         proto: rpc::proto::File,
-        worktree: ModelHandle<Worktree>,
+        worktree: Model<Worktree>,
         cx: &AppContext,
     ) -> Result<Self> {
         let worktree_id = worktree
@@ -3168,7 +3156,7 @@ struct BackgroundScanner {
     state: Mutex<BackgroundScannerState>,
     fs: Arc<dyn Fs>,
     status_updates_tx: UnboundedSender<ScanState>,
-    executor: Arc<executor::Background>,
+    executor: BackgroundExecutor,
     scan_requests_rx: channel::Receiver<ScanRequest>,
     path_prefixes_to_scan_rx: channel::Receiver<Arc<Path>>,
     next_entry_id: Arc<AtomicUsize>,
@@ -3188,7 +3176,7 @@ impl BackgroundScanner {
         next_entry_id: Arc<AtomicUsize>,
         fs: Arc<dyn Fs>,
         status_updates_tx: UnboundedSender<ScanState>,
-        executor: Arc<executor::Background>,
+        executor: BackgroundExecutor,
         scan_requests_rx: channel::Receiver<ScanRequest>,
         path_prefixes_to_scan_rx: channel::Receiver<Arc<Path>>,
     ) -> Self {
@@ -3220,21 +3208,14 @@ impl BackgroundScanner {
 
         // Populate ignores above the root.
         let root_abs_path = self.state.lock().snapshot.abs_path.clone();
-        for (index, ancestor) in root_abs_path.ancestors().enumerate() {
-            if index != 0 {
-                if let Ok(ignore) =
-                    build_gitignore(&ancestor.join(&*GITIGNORE), self.fs.as_ref()).await
-                {
-                    self.state
-                        .lock()
-                        .snapshot
-                        .ignores_by_parent_abs_path
-                        .insert(ancestor.into(), (ignore.into(), false));
-                }
-            }
-            if ancestor.join(&*DOT_GIT).is_dir() {
-                // Reached root of git repository.
-                break;
+        for ancestor in root_abs_path.ancestors().skip(1) {
+            if let Ok(ignore) = build_gitignore(&ancestor.join(&*GITIGNORE), self.fs.as_ref()).await
+            {
+                self.state
+                    .lock()
+                    .snapshot
+                    .ignores_by_parent_abs_path
+                    .insert(ancestor.into(), (ignore.into(), false));
             }
         }
 
@@ -3397,6 +3378,7 @@ impl BackgroundScanner {
                     );
                         return false;
                     };
+
                 let parent_dir_is_loaded = relative_path.parent().map_or(true, |parent| {
                     snapshot
                         .entry_for_path(parent)
@@ -3662,8 +3644,8 @@ impl BackgroundScanner {
             }
 
             {
-                let mut state = self.state.lock();
                 let relative_path = job.path.join(child_name);
+                let mut state = self.state.lock();
                 if state.snapshot.is_path_excluded(relative_path.clone()) {
                     log::debug!("skipping excluded child entry {relative_path:?}");
                     state.remove_path(&relative_path);
@@ -4240,11 +4222,11 @@ pub trait WorktreeModelHandle {
     #[cfg(any(test, feature = "test-support"))]
     fn flush_fs_events<'a>(
         &self,
-        cx: &'a gpui::TestAppContext,
+        cx: &'a mut gpui::TestAppContext,
     ) -> futures::future::LocalBoxFuture<'a, ()>;
 }
 
-impl WorktreeModelHandle for ModelHandle<Worktree> {
+impl WorktreeModelHandle for Model<Worktree> {
     // When the worktree's FS event stream sometimes delivers "redundant" events for FS changes that
     // occurred before the worktree was constructed. These events can cause the worktree to perform
     // extra directory scans, and emit extra scan-state notifications.
@@ -4254,29 +4236,31 @@ impl WorktreeModelHandle for ModelHandle<Worktree> {
     #[cfg(any(test, feature = "test-support"))]
     fn flush_fs_events<'a>(
         &self,
-        cx: &'a gpui::TestAppContext,
+        cx: &'a mut gpui::TestAppContext,
     ) -> futures::future::LocalBoxFuture<'a, ()> {
-        let filename = "fs-event-sentinel";
+        let file_name = "fs-event-sentinel";
+
         let tree = self.clone();
-        let (fs, root_path) = self.read_with(cx, |tree, _| {
+        let (fs, root_path) = self.update(cx, |tree, _| {
             let tree = tree.as_local().unwrap();
             (tree.fs.clone(), tree.abs_path().clone())
         });
 
         async move {
-            fs.create_file(&root_path.join(filename), Default::default())
+            fs.create_file(&root_path.join(file_name), Default::default())
                 .await
                 .unwrap();
-            tree.condition(cx, |tree, _| tree.entry_for_path(filename).is_some())
+
+            cx.condition(&tree, |tree, _| tree.entry_for_path(file_name).is_some())
                 .await;
 
-            fs.remove_file(&root_path.join(filename), Default::default())
+            fs.remove_file(&root_path.join(file_name), Default::default())
                 .await
                 .unwrap();
-            tree.condition(cx, |tree, _| tree.entry_for_path(filename).is_none())
+            cx.condition(&tree, |tree, _| tree.entry_for_path(file_name).is_none())
                 .await;
 
-            cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
+            cx.update(|cx| tree.read(cx).as_local().unwrap().scan_complete())
                 .await;
         }
         .boxed_local()

crates/project/src/worktree_tests.rs 🔗

@@ -7,7 +7,7 @@ use anyhow::Result;
 use client::Client;
 use fs::{repository::GitFileStatus, FakeFs, Fs, RealFs, RemoveOptions};
 use git::GITIGNORE;
-use gpui::{executor::Deterministic, ModelContext, Task, TestAppContext};
+use gpui::{ModelContext, Task, TestAppContext};
 use parking_lot::Mutex;
 use postage::stream::Stream;
 use pretty_assertions::assert_eq;
@@ -26,7 +26,7 @@ use util::{http::FakeHttpClient, test::temp_tree, ResultExt};
 #[gpui::test]
 async fn test_traversal(cx: &mut TestAppContext) {
     init_test(cx);
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.background_executor.clone());
     fs.insert_tree(
         "/root",
         json!({
@@ -82,7 +82,7 @@ async fn test_traversal(cx: &mut TestAppContext) {
 #[gpui::test]
 async fn test_descendent_entries(cx: &mut TestAppContext) {
     init_test(cx);
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.background_executor.clone());
     fs.insert_tree(
         "/root",
         json!({
@@ -188,9 +188,9 @@ async fn test_descendent_entries(cx: &mut TestAppContext) {
 }
 
 #[gpui::test(iterations = 10)]
-async fn test_circular_symlinks(executor: Arc<Deterministic>, cx: &mut TestAppContext) {
+async fn test_circular_symlinks(cx: &mut TestAppContext) {
     init_test(cx);
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.background_executor.clone());
     fs.insert_tree(
         "/root",
         json!({
@@ -247,7 +247,7 @@ async fn test_circular_symlinks(executor: Arc<Deterministic>, cx: &mut TestAppCo
     )
     .await
     .unwrap();
-    executor.run_until_parked();
+    cx.executor().run_until_parked();
     tree.read_with(cx, |tree, _| {
         assert_eq!(
             tree.entries(false)
@@ -270,7 +270,7 @@ async fn test_circular_symlinks(executor: Arc<Deterministic>, cx: &mut TestAppCo
 #[gpui::test]
 async fn test_symlinks_pointing_outside(cx: &mut TestAppContext) {
     init_test(cx);
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.background_executor.clone());
     fs.insert_tree(
         "/root",
         json!({
@@ -446,7 +446,7 @@ async fn test_symlinks_pointing_outside(cx: &mut TestAppContext) {
 #[gpui::test]
 async fn test_open_gitignored_files(cx: &mut TestAppContext) {
     init_test(cx);
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.background_executor.clone());
     fs.insert_tree(
         "/root",
         json!({
@@ -597,7 +597,7 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) {
     fs.create_dir("/root/one/node_modules/c/lib".as_ref())
         .await
         .unwrap();
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
     assert_eq!(
         fs.read_dir_call_count() + fs.metadata_call_count() - prev_fs_call_count,
         0
@@ -607,7 +607,7 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) {
 #[gpui::test]
 async fn test_dirs_no_longer_ignored(cx: &mut TestAppContext) {
     init_test(cx);
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.background_executor.clone());
     fs.insert_tree(
         "/root",
         json!({
@@ -693,7 +693,7 @@ async fn test_dirs_no_longer_ignored(cx: &mut TestAppContext) {
     fs.save("/root/.gitignore".as_ref(), &"e".into(), Default::default())
         .await
         .unwrap();
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
 
     // All of the directories that are no longer ignored are now loaded.
     tree.read_with(cx, |tree, _| {
@@ -732,13 +732,13 @@ async fn test_dirs_no_longer_ignored(cx: &mut TestAppContext) {
 async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
     init_test(cx);
     cx.update(|cx| {
-        cx.update_global::<SettingsStore, _, _>(|store, cx| {
+        cx.update_global::<SettingsStore, _>(|store, cx| {
             store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
                 project_settings.file_scan_exclusions = Some(Vec::new());
             });
         });
     });
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.background_executor.clone());
     fs.insert_tree(
         "/root",
         json!({
@@ -818,7 +818,7 @@ async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
     .await
     .unwrap();
 
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
     cx.read(|cx| {
         let tree = tree.read(cx);
         assert!(
@@ -844,6 +844,7 @@ async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
 #[gpui::test]
 async fn test_write_file(cx: &mut TestAppContext) {
     init_test(cx);
+    cx.executor().allow_parking();
     let dir = temp_tree(json!({
         ".git": {},
         ".gitignore": "ignored-dir\n",
@@ -897,6 +898,7 @@ async fn test_write_file(cx: &mut TestAppContext) {
 #[gpui::test]
 async fn test_file_scan_exclusions(cx: &mut TestAppContext) {
     init_test(cx);
+    cx.executor().allow_parking();
     let dir = temp_tree(json!({
         ".gitignore": "**/target\n/node_modules\n",
         "target": {
@@ -922,7 +924,7 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) {
         ".DS_Store": "",
     }));
     cx.update(|cx| {
-        cx.update_global::<SettingsStore, _, _>(|store, cx| {
+        cx.update_global::<SettingsStore, _>(|store, cx| {
             store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
                 project_settings.file_scan_exclusions =
                     Some(vec!["**/foo/**".to_string(), "**/.DS_Store".to_string()]);
@@ -959,7 +961,7 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) {
     });
 
     cx.update(|cx| {
-        cx.update_global::<SettingsStore, _, _>(|store, cx| {
+        cx.update_global::<SettingsStore, _>(|store, cx| {
             store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
                 project_settings.file_scan_exclusions =
                     Some(vec!["**/node_modules/**".to_string()]);
@@ -967,7 +969,7 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) {
         });
     });
     tree.flush_fs_events(cx).await;
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
     tree.read_with(cx, |tree, _| {
         check_worktree_entries(
             tree,
@@ -993,6 +995,7 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) {
 #[gpui::test]
 async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) {
     init_test(cx);
+    cx.executor().allow_parking();
     let dir = temp_tree(json!({
         ".git": {
             "HEAD": "ref: refs/heads/main\n",
@@ -1022,7 +1025,7 @@ async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) {
         ".DS_Store": "",
     }));
     cx.update(|cx| {
-        cx.update_global::<SettingsStore, _, _>(|store, cx| {
+        cx.update_global::<SettingsStore, _>(|store, cx| {
             store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
                 project_settings.file_scan_exclusions = Some(vec![
                     "**/.git".to_string(),
@@ -1134,7 +1137,7 @@ async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) {
 #[gpui::test(iterations = 30)]
 async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
     init_test(cx);
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.background_executor.clone());
     fs.insert_tree(
         "/root",
         json!({
@@ -1180,7 +1183,7 @@ async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
         .unwrap();
     assert!(entry.is_dir());
 
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
     tree.read_with(cx, |tree, _| {
         assert_eq!(tree.entry_for_path("a/e").unwrap().kind, EntryKind::Dir);
     });
@@ -1195,9 +1198,10 @@ async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
 #[gpui::test]
 async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
     init_test(cx);
-    let client_fake = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
+    cx.executor().allow_parking();
+    let client_fake = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
 
-    let fs_fake = FakeFs::new(cx.background());
+    let fs_fake = FakeFs::new(cx.background_executor.clone());
     fs_fake
         .insert_tree(
             "/root",
@@ -1229,14 +1233,14 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
         .unwrap();
     assert!(entry.is_file());
 
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
     tree_fake.read_with(cx, |tree, _| {
         assert!(tree.entry_for_path("a/b/c/d.txt").unwrap().is_file());
         assert!(tree.entry_for_path("a/b/c/").unwrap().is_dir());
         assert!(tree.entry_for_path("a/b/").unwrap().is_dir());
     });
 
-    let client_real = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
+    let client_real = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
 
     let fs_real = Arc::new(RealFs);
     let temp_root = temp_tree(json!({
@@ -1265,7 +1269,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
         .unwrap();
     assert!(entry.is_file());
 
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
     tree_real.read_with(cx, |tree, _| {
         assert!(tree.entry_for_path("a/b/c/d.txt").unwrap().is_file());
         assert!(tree.entry_for_path("a/b/c/").unwrap().is_dir());
@@ -1284,7 +1288,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
         .unwrap();
     assert!(entry.is_file());
 
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
     tree_real.read_with(cx, |tree, _| {
         assert!(tree.entry_for_path("a/b/c/e.txt").unwrap().is_file());
     });
@@ -1301,7 +1305,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
         .unwrap();
     assert!(entry.is_file());
 
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
     tree_real.read_with(cx, |tree, _| {
         assert!(tree.entry_for_path("d/e/f/g.txt").unwrap().is_file());
         assert!(tree.entry_for_path("d/e/f").unwrap().is_dir());
@@ -1324,7 +1328,7 @@ async fn test_random_worktree_operations_during_initial_scan(
         .unwrap_or(20);
 
     let root_dir = Path::new("/test");
-    let fs = FakeFs::new(cx.background()) as Arc<dyn Fs>;
+    let fs = FakeFs::new(cx.background_executor.clone()) as Arc<dyn Fs>;
     fs.as_fake().insert_tree(root_dir, json!({})).await;
     for _ in 0..initial_entries {
         randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await;
@@ -1376,7 +1380,7 @@ async fn test_random_worktree_operations_during_initial_scan(
         .update(cx, |tree, _| tree.as_local_mut().unwrap().scan_complete())
         .await;
 
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
 
     let final_snapshot = worktree.read_with(cx, |tree, _| {
         let tree = tree.as_local().unwrap();
@@ -1414,7 +1418,7 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng)
         .unwrap_or(20);
 
     let root_dir = Path::new("/test");
-    let fs = FakeFs::new(cx.background()) as Arc<dyn Fs>;
+    let fs = FakeFs::new(cx.background_executor.clone()) as Arc<dyn Fs>;
     fs.as_fake().insert_tree(root_dir, json!({})).await;
     for _ in 0..initial_entries {
         randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await;
@@ -1474,7 +1478,7 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng)
             mutations_len -= 1;
         }
 
-        cx.foreground().run_until_parked();
+        cx.executor().run_until_parked();
         if rng.gen_bool(0.2) {
             log::info!("storing snapshot {}", snapshots.len());
             let snapshot = worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
@@ -1484,7 +1488,7 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng)
 
     log::info!("quiescing");
     fs.as_fake().flush_events(usize::MAX);
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
 
     let snapshot = worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
     snapshot.check_invariants(true);
@@ -1624,7 +1628,7 @@ fn randomly_mutate_worktree(
                 new_path
             );
             let task = worktree.rename_entry(entry.id, new_path, cx);
-            cx.foreground().spawn(async move {
+            cx.background_executor().spawn(async move {
                 task.await?.unwrap();
                 Ok(())
             })
@@ -1639,7 +1643,7 @@ fn randomly_mutate_worktree(
                     child_path,
                 );
                 let task = worktree.create_entry(child_path, is_dir, cx);
-                cx.foreground().spawn(async move {
+                cx.background_executor().spawn(async move {
                     task.await?;
                     Ok(())
                 })
@@ -1647,7 +1651,7 @@ fn randomly_mutate_worktree(
                 log::info!("overwriting file {:?} ({})", entry.path, entry.id.0);
                 let task =
                     worktree.write_file(entry.path.clone(), "".into(), Default::default(), cx);
-                cx.foreground().spawn(async move {
+                cx.background_executor().spawn(async move {
                     task.await?;
                     Ok(())
                 })
@@ -1826,6 +1830,7 @@ fn random_filename(rng: &mut impl Rng) -> String {
 #[gpui::test]
 async fn test_rename_work_directory(cx: &mut TestAppContext) {
     init_test(cx);
+    cx.executor().allow_parking();
     let root = temp_tree(json!({
         "projects": {
             "project1": {
@@ -1897,6 +1902,7 @@ async fn test_rename_work_directory(cx: &mut TestAppContext) {
 #[gpui::test]
 async fn test_git_repository_for_path(cx: &mut TestAppContext) {
     init_test(cx);
+    cx.executor().allow_parking();
     let root = temp_tree(json!({
         "c.txt": "",
         "dir1": {
@@ -2016,16 +2022,9 @@ async fn test_git_repository_for_path(cx: &mut TestAppContext) {
 }
 
 #[gpui::test]
-async fn test_git_status(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
+async fn test_git_status(cx: &mut TestAppContext) {
     init_test(cx);
-    cx.update(|cx| {
-        cx.update_global::<SettingsStore, _, _>(|store, cx| {
-            store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
-                project_settings.file_scan_exclusions =
-                    Some(vec!["**/.git".to_string(), "**/.gitignore".to_string()]);
-            });
-        });
-    });
+    cx.executor().allow_parking();
     const IGNORE_RULE: &'static str = "**/target";
 
     let root = temp_tree(json!({
@@ -2077,7 +2076,7 @@ async fn test_git_status(deterministic: Arc<Deterministic>, cx: &mut TestAppCont
     tree.flush_fs_events(cx).await;
     cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
         .await;
-    deterministic.run_until_parked();
+    cx.executor().run_until_parked();
 
     // Check that the right git state is observed on startup
     tree.read_with(cx, |tree, _cx| {
@@ -2099,7 +2098,7 @@ async fn test_git_status(deterministic: Arc<Deterministic>, cx: &mut TestAppCont
     // Modify a file in the working copy.
     std::fs::write(work_dir.join(A_TXT), "aa").unwrap();
     tree.flush_fs_events(cx).await;
-    deterministic.run_until_parked();
+    cx.executor().run_until_parked();
 
     // The worktree detects that the file's git status has changed.
     tree.read_with(cx, |tree, _cx| {
@@ -2115,7 +2114,7 @@ async fn test_git_status(deterministic: Arc<Deterministic>, cx: &mut TestAppCont
     git_add(B_TXT, &repo);
     git_commit("Committing modified and added", &repo);
     tree.flush_fs_events(cx).await;
-    deterministic.run_until_parked();
+    cx.executor().run_until_parked();
 
     // The worktree detects that the files' git status have changed.
     tree.read_with(cx, |tree, _cx| {
@@ -2135,7 +2134,7 @@ async fn test_git_status(deterministic: Arc<Deterministic>, cx: &mut TestAppCont
     std::fs::write(work_dir.join(E_TXT), "eeee").unwrap();
     std::fs::write(work_dir.join(BUILD_FILE), "this should be ignored").unwrap();
     tree.flush_fs_events(cx).await;
-    deterministic.run_until_parked();
+    cx.executor().run_until_parked();
 
     // Check that more complex repo changes are tracked
     tree.read_with(cx, |tree, _cx| {
@@ -2164,7 +2163,7 @@ async fn test_git_status(deterministic: Arc<Deterministic>, cx: &mut TestAppCont
     git_commit("Committing modified git ignore", &repo);
 
     tree.flush_fs_events(cx).await;
-    deterministic.run_until_parked();
+    cx.executor().run_until_parked();
 
     let mut renamed_dir_name = "first_directory/second_directory";
     const RENAMED_FILE: &'static str = "rf.txt";
@@ -2177,7 +2176,7 @@ async fn test_git_status(deterministic: Arc<Deterministic>, cx: &mut TestAppCont
     .unwrap();
 
     tree.flush_fs_events(cx).await;
-    deterministic.run_until_parked();
+    cx.executor().run_until_parked();
 
     tree.read_with(cx, |tree, _cx| {
         let snapshot = tree.snapshot();
@@ -2196,7 +2195,7 @@ async fn test_git_status(deterministic: Arc<Deterministic>, cx: &mut TestAppCont
     .unwrap();
 
     tree.flush_fs_events(cx).await;
-    deterministic.run_until_parked();
+    cx.executor().run_until_parked();
 
     tree.read_with(cx, |tree, _cx| {
         let snapshot = tree.snapshot();
@@ -2215,7 +2214,7 @@ async fn test_git_status(deterministic: Arc<Deterministic>, cx: &mut TestAppCont
 #[gpui::test]
 async fn test_propagate_git_statuses(cx: &mut TestAppContext) {
     init_test(cx);
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.background_executor.clone());
     fs.insert_tree(
         "/root",
         json!({
@@ -2266,7 +2265,7 @@ async fn test_propagate_git_statuses(cx: &mut TestAppContext) {
     cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
         .await;
 
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
     let snapshot = tree.read_with(cx, |tree, _| tree.snapshot());
 
     check_propagated_statuses(
@@ -2334,7 +2333,7 @@ async fn test_propagate_git_statuses(cx: &mut TestAppContext) {
 
 fn build_client(cx: &mut TestAppContext) -> Arc<Client> {
     let http_client = FakeHttpClient::with_404_response();
-    cx.read(|cx| Client::new(http_client, cx))
+    cx.update(|cx| Client::new(http_client, cx))
 }
 
 #[track_caller]
@@ -2456,7 +2455,8 @@ fn check_worktree_entries(
 
 fn init_test(cx: &mut gpui::TestAppContext) {
     cx.update(|cx| {
-        cx.set_global(SettingsStore::test(cx));
+        let settings_store = SettingsStore::test(cx);
+        cx.set_global(settings_store);
         Project::init_settings(cx);
     });
 }

crates/project2/Cargo.toml 🔗

@@ -1,85 +0,0 @@
-[package]
-name = "project2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/project2.rs"
-doctest = false
-
-[features]
-test-support = [
-    "client/test-support",
-    "db/test-support",
-    "language/test-support",
-    "settings/test-support",
-    "text/test-support",
-    "prettier/test-support",
-    "gpui/test-support",
-]
-
-[dependencies]
-text = { package = "text2", path = "../text2" }
-copilot = { package = "copilot2", path = "../copilot2" }
-client = { package = "client2", path = "../client2" }
-clock = { path = "../clock" }
-collections = { path = "../collections" }
-db = { package = "db2", path = "../db2" }
-fs = { package = "fs2", path = "../fs2" }
-fsevent = { path = "../fsevent" }
-fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
-git = { package = "git3", path = "../git3" }
-gpui = { package = "gpui2", path = "../gpui2" }
-language = { package = "language2", path = "../language2" }
-lsp = { package = "lsp2", path = "../lsp2" }
-node_runtime = { path = "../node_runtime" }
-prettier = { package = "prettier2", path = "../prettier2" }
-rpc = { package = "rpc2", path = "../rpc2" }
-settings = { package = "settings2", path = "../settings2" }
-sum_tree = { path = "../sum_tree" }
-terminal = { package = "terminal2", path = "../terminal2" }
-util = { path = "../util" }
-
-aho-corasick = "1.1"
-anyhow.workspace = true
-async-trait.workspace = true
-backtrace = "0.3"
-futures.workspace = true
-globset.workspace = true
-ignore = "0.4"
-lazy_static.workspace = true
-log.workspace = true
-parking_lot.workspace = true
-postage.workspace = true
-rand.workspace = true
-regex.workspace = true
-schemars.workspace = true
-serde.workspace = true
-serde_derive.workspace = true
-serde_json.workspace = true
-sha2 = "0.10"
-similar = "1.3"
-smol.workspace = true
-thiserror.workspace = true
-toml.workspace = true
-itertools = "0.10"
-
-[dev-dependencies]
-ctor.workspace = true
-env_logger.workspace = true
-pretty_assertions.workspace = true
-client = { package = "client2", path = "../client2", features = ["test-support"] }
-collections = { path = "../collections", features = ["test-support"] }
-db = { package = "db2", path = "../db2", features = ["test-support"] }
-fs = { package = "fs2", path = "../fs2",  features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-language = { package = "language2", path = "../language2", features = ["test-support"] }
-lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
-settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
-prettier = { package = "prettier2", path = "../prettier2", features = ["test-support"] }
-util = { path = "../util", features = ["test-support"] }
-rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
-git2.workspace = true
-tempdir.workspace = true
-unindent.workspace = true

crates/project2/src/ignore.rs 🔗

@@ -1,53 +0,0 @@
-use ignore::gitignore::Gitignore;
-use std::{ffi::OsStr, path::Path, sync::Arc};
-
-pub enum IgnoreStack {
-    None,
-    Some {
-        abs_base_path: Arc<Path>,
-        ignore: Arc<Gitignore>,
-        parent: Arc<IgnoreStack>,
-    },
-    All,
-}
-
-impl IgnoreStack {
-    pub fn none() -> Arc<Self> {
-        Arc::new(Self::None)
-    }
-
-    pub fn all() -> Arc<Self> {
-        Arc::new(Self::All)
-    }
-
-    pub fn append(self: Arc<Self>, abs_base_path: Arc<Path>, ignore: Arc<Gitignore>) -> Arc<Self> {
-        match self.as_ref() {
-            IgnoreStack::All => self,
-            _ => Arc::new(Self::Some {
-                abs_base_path,
-                ignore,
-                parent: self,
-            }),
-        }
-    }
-
-    pub fn is_abs_path_ignored(&self, abs_path: &Path, is_dir: bool) -> bool {
-        if is_dir && abs_path.file_name() == Some(OsStr::new(".git")) {
-            return true;
-        }
-
-        match self {
-            Self::None => false,
-            Self::All => true,
-            Self::Some {
-                abs_base_path,
-                ignore,
-                parent: prev,
-            } => match ignore.matched(abs_path.strip_prefix(abs_base_path).unwrap(), is_dir) {
-                ignore::Match::None => prev.is_abs_path_ignored(abs_path, is_dir),
-                ignore::Match::Ignore(_) => true,
-                ignore::Match::Whitelist(_) => false,
-            },
-        }
-    }
-}

crates/project2/src/lsp_command.rs 🔗

@@ -1,2364 +0,0 @@
-use crate::{
-    DocumentHighlight, Hover, HoverBlock, HoverBlockKind, InlayHint, InlayHintLabel,
-    InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location, LocationLink,
-    MarkupContent, Project, ProjectTransaction, ResolveState,
-};
-use anyhow::{anyhow, Context, Result};
-use async_trait::async_trait;
-use client::proto::{self, PeerId};
-use futures::future;
-use gpui::{AppContext, AsyncAppContext, Model};
-use language::{
-    language_settings::{language_settings, InlayHintKind},
-    point_from_lsp, point_to_lsp, prepare_completion_documentation,
-    proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
-    range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind,
-    CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction,
-    Unclipped,
-};
-use lsp::{
-    CompletionListItemDefaultsEditRange, DocumentHighlightKind, LanguageServer, LanguageServerId,
-    OneOf, ServerCapabilities,
-};
-use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
-use text::LineEnding;
-
-pub fn lsp_formatting_options(tab_size: u32) -> lsp::FormattingOptions {
-    lsp::FormattingOptions {
-        tab_size,
-        insert_spaces: true,
-        insert_final_newline: Some(true),
-        ..lsp::FormattingOptions::default()
-    }
-}
-
-#[async_trait(?Send)]
-pub trait LspCommand: 'static + Sized + Send {
-    type Response: 'static + Default + Send;
-    type LspRequest: 'static + Send + lsp::request::Request;
-    type ProtoRequest: 'static + Send + proto::RequestMessage;
-
-    fn check_capabilities(&self, _: &lsp::ServerCapabilities) -> bool {
-        true
-    }
-
-    fn to_lsp(
-        &self,
-        path: &Path,
-        buffer: &Buffer,
-        language_server: &Arc<LanguageServer>,
-        cx: &AppContext,
-    ) -> <Self::LspRequest as lsp::request::Request>::Params;
-
-    async fn response_from_lsp(
-        self,
-        message: <Self::LspRequest as lsp::request::Request>::Result,
-        project: Model<Project>,
-        buffer: Model<Buffer>,
-        server_id: LanguageServerId,
-        cx: AsyncAppContext,
-    ) -> Result<Self::Response>;
-
-    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest;
-
-    async fn from_proto(
-        message: Self::ProtoRequest,
-        project: Model<Project>,
-        buffer: Model<Buffer>,
-        cx: AsyncAppContext,
-    ) -> Result<Self>;
-
-    fn response_to_proto(
-        response: Self::Response,
-        project: &mut Project,
-        peer_id: PeerId,
-        buffer_version: &clock::Global,
-        cx: &mut AppContext,
-    ) -> <Self::ProtoRequest as proto::RequestMessage>::Response;
-
-    async fn response_from_proto(
-        self,
-        message: <Self::ProtoRequest as proto::RequestMessage>::Response,
-        project: Model<Project>,
-        buffer: Model<Buffer>,
-        cx: AsyncAppContext,
-    ) -> Result<Self::Response>;
-
-    fn buffer_id_from_proto(message: &Self::ProtoRequest) -> u64;
-}
-
-pub(crate) struct PrepareRename {
-    pub position: PointUtf16,
-}
-
-pub(crate) struct PerformRename {
-    pub position: PointUtf16,
-    pub new_name: String,
-    pub push_to_history: bool,
-}
-
-pub(crate) struct GetDefinition {
-    pub position: PointUtf16,
-}
-
-pub(crate) struct GetTypeDefinition {
-    pub position: PointUtf16,
-}
-
-pub(crate) struct GetReferences {
-    pub position: PointUtf16,
-}
-
-pub(crate) struct GetDocumentHighlights {
-    pub position: PointUtf16,
-}
-
-pub(crate) struct GetHover {
-    pub position: PointUtf16,
-}
-
-pub(crate) struct GetCompletions {
-    pub position: PointUtf16,
-}
-
-pub(crate) struct GetCodeActions {
-    pub range: Range<Anchor>,
-}
-
-pub(crate) struct OnTypeFormatting {
-    pub position: PointUtf16,
-    pub trigger: String,
-    pub options: FormattingOptions,
-    pub push_to_history: bool,
-}
-
-pub(crate) struct InlayHints {
-    pub range: Range<Anchor>,
-}
-
-pub(crate) struct FormattingOptions {
-    tab_size: u32,
-}
-
-impl From<lsp::FormattingOptions> for FormattingOptions {
-    fn from(value: lsp::FormattingOptions) -> Self {
-        Self {
-            tab_size: value.tab_size,
-        }
-    }
-}
-
-#[async_trait(?Send)]
-impl LspCommand for PrepareRename {
-    type Response = Option<Range<Anchor>>;
-    type LspRequest = lsp::request::PrepareRenameRequest;
-    type ProtoRequest = proto::PrepareRename;
-
-    fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool {
-        if let Some(lsp::OneOf::Right(rename)) = &capabilities.rename_provider {
-            rename.prepare_provider == Some(true)
-        } else {
-            false
-        }
-    }
-
-    fn to_lsp(
-        &self,
-        path: &Path,
-        _: &Buffer,
-        _: &Arc<LanguageServer>,
-        _: &AppContext,
-    ) -> lsp::TextDocumentPositionParams {
-        lsp::TextDocumentPositionParams {
-            text_document: lsp::TextDocumentIdentifier {
-                uri: lsp::Url::from_file_path(path).unwrap(),
-            },
-            position: point_to_lsp(self.position),
-        }
-    }
-
-    async fn response_from_lsp(
-        self,
-        message: Option<lsp::PrepareRenameResponse>,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        _: LanguageServerId,
-        mut cx: AsyncAppContext,
-    ) -> Result<Option<Range<Anchor>>> {
-        buffer.update(&mut cx, |buffer, _| {
-            if let Some(
-                lsp::PrepareRenameResponse::Range(range)
-                | lsp::PrepareRenameResponse::RangeWithPlaceholder { range, .. },
-            ) = message
-            {
-                let Range { start, end } = range_from_lsp(range);
-                if buffer.clip_point_utf16(start, Bias::Left) == start.0
-                    && buffer.clip_point_utf16(end, Bias::Left) == end.0
-                {
-                    return Ok(Some(buffer.anchor_after(start)..buffer.anchor_before(end)));
-                }
-            }
-            Ok(None)
-        })?
-    }
-
-    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::PrepareRename {
-        proto::PrepareRename {
-            project_id,
-            buffer_id: buffer.remote_id(),
-            position: Some(language::proto::serialize_anchor(
-                &buffer.anchor_before(self.position),
-            )),
-            version: serialize_version(&buffer.version()),
-        }
-    }
-
-    async fn from_proto(
-        message: proto::PrepareRename,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Self> {
-        let position = message
-            .position
-            .and_then(deserialize_anchor)
-            .ok_or_else(|| anyhow!("invalid position"))?;
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(deserialize_version(&message.version))
-            })?
-            .await?;
-
-        Ok(Self {
-            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
-        })
-    }
-
-    fn response_to_proto(
-        range: Option<Range<Anchor>>,
-        _: &mut Project,
-        _: PeerId,
-        buffer_version: &clock::Global,
-        _: &mut AppContext,
-    ) -> proto::PrepareRenameResponse {
-        proto::PrepareRenameResponse {
-            can_rename: range.is_some(),
-            start: range
-                .as_ref()
-                .map(|range| language::proto::serialize_anchor(&range.start)),
-            end: range
-                .as_ref()
-                .map(|range| language::proto::serialize_anchor(&range.end)),
-            version: serialize_version(buffer_version),
-        }
-    }
-
-    async fn response_from_proto(
-        self,
-        message: proto::PrepareRenameResponse,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Option<Range<Anchor>>> {
-        if message.can_rename {
-            buffer
-                .update(&mut cx, |buffer, _| {
-                    buffer.wait_for_version(deserialize_version(&message.version))
-                })?
-                .await?;
-            let start = message.start.and_then(deserialize_anchor);
-            let end = message.end.and_then(deserialize_anchor);
-            Ok(start.zip(end).map(|(start, end)| start..end))
-        } else {
-            Ok(None)
-        }
-    }
-
-    fn buffer_id_from_proto(message: &proto::PrepareRename) -> u64 {
-        message.buffer_id
-    }
-}
-
-#[async_trait(?Send)]
-impl LspCommand for PerformRename {
-    type Response = ProjectTransaction;
-    type LspRequest = lsp::request::Rename;
-    type ProtoRequest = proto::PerformRename;
-
-    fn to_lsp(
-        &self,
-        path: &Path,
-        _: &Buffer,
-        _: &Arc<LanguageServer>,
-        _: &AppContext,
-    ) -> lsp::RenameParams {
-        lsp::RenameParams {
-            text_document_position: lsp::TextDocumentPositionParams {
-                text_document: lsp::TextDocumentIdentifier {
-                    uri: lsp::Url::from_file_path(path).unwrap(),
-                },
-                position: point_to_lsp(self.position),
-            },
-            new_name: self.new_name.clone(),
-            work_done_progress_params: Default::default(),
-        }
-    }
-
-    async fn response_from_lsp(
-        self,
-        message: Option<lsp::WorkspaceEdit>,
-        project: Model<Project>,
-        buffer: Model<Buffer>,
-        server_id: LanguageServerId,
-        mut cx: AsyncAppContext,
-    ) -> Result<ProjectTransaction> {
-        if let Some(edit) = message {
-            let (lsp_adapter, lsp_server) =
-                language_server_for_buffer(&project, &buffer, server_id, &mut cx)?;
-            Project::deserialize_workspace_edit(
-                project,
-                edit,
-                self.push_to_history,
-                lsp_adapter,
-                lsp_server,
-                &mut cx,
-            )
-            .await
-        } else {
-            Ok(ProjectTransaction::default())
-        }
-    }
-
-    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::PerformRename {
-        proto::PerformRename {
-            project_id,
-            buffer_id: buffer.remote_id(),
-            position: Some(language::proto::serialize_anchor(
-                &buffer.anchor_before(self.position),
-            )),
-            new_name: self.new_name.clone(),
-            version: serialize_version(&buffer.version()),
-        }
-    }
-
-    async fn from_proto(
-        message: proto::PerformRename,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Self> {
-        let position = message
-            .position
-            .and_then(deserialize_anchor)
-            .ok_or_else(|| anyhow!("invalid position"))?;
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(deserialize_version(&message.version))
-            })?
-            .await?;
-        Ok(Self {
-            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
-            new_name: message.new_name,
-            push_to_history: false,
-        })
-    }
-
-    fn response_to_proto(
-        response: ProjectTransaction,
-        project: &mut Project,
-        peer_id: PeerId,
-        _: &clock::Global,
-        cx: &mut AppContext,
-    ) -> proto::PerformRenameResponse {
-        let transaction = project.serialize_project_transaction_for_peer(response, peer_id, cx);
-        proto::PerformRenameResponse {
-            transaction: Some(transaction),
-        }
-    }
-
-    async fn response_from_proto(
-        self,
-        message: proto::PerformRenameResponse,
-        project: Model<Project>,
-        _: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<ProjectTransaction> {
-        let message = message
-            .transaction
-            .ok_or_else(|| anyhow!("missing transaction"))?;
-        project
-            .update(&mut cx, |project, cx| {
-                project.deserialize_project_transaction(message, self.push_to_history, cx)
-            })?
-            .await
-    }
-
-    fn buffer_id_from_proto(message: &proto::PerformRename) -> u64 {
-        message.buffer_id
-    }
-}
-
-#[async_trait(?Send)]
-impl LspCommand for GetDefinition {
-    type Response = Vec<LocationLink>;
-    type LspRequest = lsp::request::GotoDefinition;
-    type ProtoRequest = proto::GetDefinition;
-
-    fn to_lsp(
-        &self,
-        path: &Path,
-        _: &Buffer,
-        _: &Arc<LanguageServer>,
-        _: &AppContext,
-    ) -> lsp::GotoDefinitionParams {
-        lsp::GotoDefinitionParams {
-            text_document_position_params: lsp::TextDocumentPositionParams {
-                text_document: lsp::TextDocumentIdentifier {
-                    uri: lsp::Url::from_file_path(path).unwrap(),
-                },
-                position: point_to_lsp(self.position),
-            },
-            work_done_progress_params: Default::default(),
-            partial_result_params: Default::default(),
-        }
-    }
-
-    async fn response_from_lsp(
-        self,
-        message: Option<lsp::GotoDefinitionResponse>,
-        project: Model<Project>,
-        buffer: Model<Buffer>,
-        server_id: LanguageServerId,
-        cx: AsyncAppContext,
-    ) -> Result<Vec<LocationLink>> {
-        location_links_from_lsp(message, project, buffer, server_id, cx).await
-    }
-
-    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetDefinition {
-        proto::GetDefinition {
-            project_id,
-            buffer_id: buffer.remote_id(),
-            position: Some(language::proto::serialize_anchor(
-                &buffer.anchor_before(self.position),
-            )),
-            version: serialize_version(&buffer.version()),
-        }
-    }
-
-    async fn from_proto(
-        message: proto::GetDefinition,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Self> {
-        let position = message
-            .position
-            .and_then(deserialize_anchor)
-            .ok_or_else(|| anyhow!("invalid position"))?;
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(deserialize_version(&message.version))
-            })?
-            .await?;
-        Ok(Self {
-            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
-        })
-    }
-
-    fn response_to_proto(
-        response: Vec<LocationLink>,
-        project: &mut Project,
-        peer_id: PeerId,
-        _: &clock::Global,
-        cx: &mut AppContext,
-    ) -> proto::GetDefinitionResponse {
-        let links = location_links_to_proto(response, project, peer_id, cx);
-        proto::GetDefinitionResponse { links }
-    }
-
-    async fn response_from_proto(
-        self,
-        message: proto::GetDefinitionResponse,
-        project: Model<Project>,
-        _: Model<Buffer>,
-        cx: AsyncAppContext,
-    ) -> Result<Vec<LocationLink>> {
-        location_links_from_proto(message.links, project, cx).await
-    }
-
-    fn buffer_id_from_proto(message: &proto::GetDefinition) -> u64 {
-        message.buffer_id
-    }
-}
-
-#[async_trait(?Send)]
-impl LspCommand for GetTypeDefinition {
-    type Response = Vec<LocationLink>;
-    type LspRequest = lsp::request::GotoTypeDefinition;
-    type ProtoRequest = proto::GetTypeDefinition;
-
-    fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool {
-        match &capabilities.type_definition_provider {
-            None => false,
-            Some(lsp::TypeDefinitionProviderCapability::Simple(false)) => false,
-            _ => true,
-        }
-    }
-
-    fn to_lsp(
-        &self,
-        path: &Path,
-        _: &Buffer,
-        _: &Arc<LanguageServer>,
-        _: &AppContext,
-    ) -> lsp::GotoTypeDefinitionParams {
-        lsp::GotoTypeDefinitionParams {
-            text_document_position_params: lsp::TextDocumentPositionParams {
-                text_document: lsp::TextDocumentIdentifier {
-                    uri: lsp::Url::from_file_path(path).unwrap(),
-                },
-                position: point_to_lsp(self.position),
-            },
-            work_done_progress_params: Default::default(),
-            partial_result_params: Default::default(),
-        }
-    }
-
-    async fn response_from_lsp(
-        self,
-        message: Option<lsp::GotoTypeDefinitionResponse>,
-        project: Model<Project>,
-        buffer: Model<Buffer>,
-        server_id: LanguageServerId,
-        cx: AsyncAppContext,
-    ) -> Result<Vec<LocationLink>> {
-        location_links_from_lsp(message, project, buffer, server_id, cx).await
-    }
-
-    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetTypeDefinition {
-        proto::GetTypeDefinition {
-            project_id,
-            buffer_id: buffer.remote_id(),
-            position: Some(language::proto::serialize_anchor(
-                &buffer.anchor_before(self.position),
-            )),
-            version: serialize_version(&buffer.version()),
-        }
-    }
-
-    async fn from_proto(
-        message: proto::GetTypeDefinition,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Self> {
-        let position = message
-            .position
-            .and_then(deserialize_anchor)
-            .ok_or_else(|| anyhow!("invalid position"))?;
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(deserialize_version(&message.version))
-            })?
-            .await?;
-        Ok(Self {
-            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
-        })
-    }
-
-    fn response_to_proto(
-        response: Vec<LocationLink>,
-        project: &mut Project,
-        peer_id: PeerId,
-        _: &clock::Global,
-        cx: &mut AppContext,
-    ) -> proto::GetTypeDefinitionResponse {
-        let links = location_links_to_proto(response, project, peer_id, cx);
-        proto::GetTypeDefinitionResponse { links }
-    }
-
-    async fn response_from_proto(
-        self,
-        message: proto::GetTypeDefinitionResponse,
-        project: Model<Project>,
-        _: Model<Buffer>,
-        cx: AsyncAppContext,
-    ) -> Result<Vec<LocationLink>> {
-        location_links_from_proto(message.links, project, cx).await
-    }
-
-    fn buffer_id_from_proto(message: &proto::GetTypeDefinition) -> u64 {
-        message.buffer_id
-    }
-}
-
-fn language_server_for_buffer(
-    project: &Model<Project>,
-    buffer: &Model<Buffer>,
-    server_id: LanguageServerId,
-    cx: &mut AsyncAppContext,
-) -> Result<(Arc<CachedLspAdapter>, Arc<LanguageServer>)> {
-    project
-        .update(cx, |project, cx| {
-            project
-                .language_server_for_buffer(buffer.read(cx), server_id, cx)
-                .map(|(adapter, server)| (adapter.clone(), server.clone()))
-        })?
-        .ok_or_else(|| anyhow!("no language server found for buffer"))
-}
-
-async fn location_links_from_proto(
-    proto_links: Vec<proto::LocationLink>,
-    project: Model<Project>,
-    mut cx: AsyncAppContext,
-) -> Result<Vec<LocationLink>> {
-    let mut links = Vec::new();
-
-    for link in proto_links {
-        let origin = match link.origin {
-            Some(origin) => {
-                let buffer = project
-                    .update(&mut cx, |this, cx| {
-                        this.wait_for_remote_buffer(origin.buffer_id, cx)
-                    })?
-                    .await?;
-                let start = origin
-                    .start
-                    .and_then(deserialize_anchor)
-                    .ok_or_else(|| anyhow!("missing origin start"))?;
-                let end = origin
-                    .end
-                    .and_then(deserialize_anchor)
-                    .ok_or_else(|| anyhow!("missing origin end"))?;
-                buffer
-                    .update(&mut cx, |buffer, _| buffer.wait_for_anchors([start, end]))?
-                    .await?;
-                Some(Location {
-                    buffer,
-                    range: start..end,
-                })
-            }
-            None => None,
-        };
-
-        let target = link.target.ok_or_else(|| anyhow!("missing target"))?;
-        let buffer = project
-            .update(&mut cx, |this, cx| {
-                this.wait_for_remote_buffer(target.buffer_id, cx)
-            })?
-            .await?;
-        let start = target
-            .start
-            .and_then(deserialize_anchor)
-            .ok_or_else(|| anyhow!("missing target start"))?;
-        let end = target
-            .end
-            .and_then(deserialize_anchor)
-            .ok_or_else(|| anyhow!("missing target end"))?;
-        buffer
-            .update(&mut cx, |buffer, _| buffer.wait_for_anchors([start, end]))?
-            .await?;
-        let target = Location {
-            buffer,
-            range: start..end,
-        };
-
-        links.push(LocationLink { origin, target })
-    }
-
-    Ok(links)
-}
-
-async fn location_links_from_lsp(
-    message: Option<lsp::GotoDefinitionResponse>,
-    project: Model<Project>,
-    buffer: Model<Buffer>,
-    server_id: LanguageServerId,
-    mut cx: AsyncAppContext,
-) -> Result<Vec<LocationLink>> {
-    let message = match message {
-        Some(message) => message,
-        None => return Ok(Vec::new()),
-    };
-
-    let mut unresolved_links = Vec::new();
-    match message {
-        lsp::GotoDefinitionResponse::Scalar(loc) => {
-            unresolved_links.push((None, loc.uri, loc.range));
-        }
-
-        lsp::GotoDefinitionResponse::Array(locs) => {
-            unresolved_links.extend(locs.into_iter().map(|l| (None, l.uri, l.range)));
-        }
-
-        lsp::GotoDefinitionResponse::Link(links) => {
-            unresolved_links.extend(links.into_iter().map(|l| {
-                (
-                    l.origin_selection_range,
-                    l.target_uri,
-                    l.target_selection_range,
-                )
-            }));
-        }
-    }
-
-    let (lsp_adapter, language_server) =
-        language_server_for_buffer(&project, &buffer, server_id, &mut cx)?;
-    let mut definitions = Vec::new();
-    for (origin_range, target_uri, target_range) in unresolved_links {
-        let target_buffer_handle = project
-            .update(&mut cx, |this, cx| {
-                this.open_local_buffer_via_lsp(
-                    target_uri,
-                    language_server.server_id(),
-                    lsp_adapter.name.clone(),
-                    cx,
-                )
-            })?
-            .await?;
-
-        cx.update(|cx| {
-            let origin_location = origin_range.map(|origin_range| {
-                let origin_buffer = buffer.read(cx);
-                let origin_start =
-                    origin_buffer.clip_point_utf16(point_from_lsp(origin_range.start), Bias::Left);
-                let origin_end =
-                    origin_buffer.clip_point_utf16(point_from_lsp(origin_range.end), Bias::Left);
-                Location {
-                    buffer: buffer.clone(),
-                    range: origin_buffer.anchor_after(origin_start)
-                        ..origin_buffer.anchor_before(origin_end),
-                }
-            });
-
-            let target_buffer = target_buffer_handle.read(cx);
-            let target_start =
-                target_buffer.clip_point_utf16(point_from_lsp(target_range.start), Bias::Left);
-            let target_end =
-                target_buffer.clip_point_utf16(point_from_lsp(target_range.end), Bias::Left);
-            let target_location = Location {
-                buffer: target_buffer_handle,
-                range: target_buffer.anchor_after(target_start)
-                    ..target_buffer.anchor_before(target_end),
-            };
-
-            definitions.push(LocationLink {
-                origin: origin_location,
-                target: target_location,
-            })
-        })?;
-    }
-    Ok(definitions)
-}
-
-fn location_links_to_proto(
-    links: Vec<LocationLink>,
-    project: &mut Project,
-    peer_id: PeerId,
-    cx: &mut AppContext,
-) -> Vec<proto::LocationLink> {
-    links
-        .into_iter()
-        .map(|definition| {
-            let origin = definition.origin.map(|origin| {
-                let buffer_id = project.create_buffer_for_peer(&origin.buffer, peer_id, cx);
-                proto::Location {
-                    start: Some(serialize_anchor(&origin.range.start)),
-                    end: Some(serialize_anchor(&origin.range.end)),
-                    buffer_id,
-                }
-            });
-
-            let buffer_id = project.create_buffer_for_peer(&definition.target.buffer, peer_id, cx);
-            let target = proto::Location {
-                start: Some(serialize_anchor(&definition.target.range.start)),
-                end: Some(serialize_anchor(&definition.target.range.end)),
-                buffer_id,
-            };
-
-            proto::LocationLink {
-                origin,
-                target: Some(target),
-            }
-        })
-        .collect()
-}
-
-#[async_trait(?Send)]
-impl LspCommand for GetReferences {
-    type Response = Vec<Location>;
-    type LspRequest = lsp::request::References;
-    type ProtoRequest = proto::GetReferences;
-
-    fn to_lsp(
-        &self,
-        path: &Path,
-        _: &Buffer,
-        _: &Arc<LanguageServer>,
-        _: &AppContext,
-    ) -> lsp::ReferenceParams {
-        lsp::ReferenceParams {
-            text_document_position: lsp::TextDocumentPositionParams {
-                text_document: lsp::TextDocumentIdentifier {
-                    uri: lsp::Url::from_file_path(path).unwrap(),
-                },
-                position: point_to_lsp(self.position),
-            },
-            work_done_progress_params: Default::default(),
-            partial_result_params: Default::default(),
-            context: lsp::ReferenceContext {
-                include_declaration: true,
-            },
-        }
-    }
-
-    async fn response_from_lsp(
-        self,
-        locations: Option<Vec<lsp::Location>>,
-        project: Model<Project>,
-        buffer: Model<Buffer>,
-        server_id: LanguageServerId,
-        mut cx: AsyncAppContext,
-    ) -> Result<Vec<Location>> {
-        let mut references = Vec::new();
-        let (lsp_adapter, language_server) =
-            language_server_for_buffer(&project, &buffer, server_id, &mut cx)?;
-
-        if let Some(locations) = locations {
-            for lsp_location in locations {
-                let target_buffer_handle = project
-                    .update(&mut cx, |this, cx| {
-                        this.open_local_buffer_via_lsp(
-                            lsp_location.uri,
-                            language_server.server_id(),
-                            lsp_adapter.name.clone(),
-                            cx,
-                        )
-                    })?
-                    .await?;
-
-                target_buffer_handle
-                    .clone()
-                    .update(&mut cx, |target_buffer, _| {
-                        let target_start = target_buffer
-                            .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
-                        let target_end = target_buffer
-                            .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
-                        references.push(Location {
-                            buffer: target_buffer_handle,
-                            range: target_buffer.anchor_after(target_start)
-                                ..target_buffer.anchor_before(target_end),
-                        });
-                    })?;
-            }
-        }
-
-        Ok(references)
-    }
-
-    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetReferences {
-        proto::GetReferences {
-            project_id,
-            buffer_id: buffer.remote_id(),
-            position: Some(language::proto::serialize_anchor(
-                &buffer.anchor_before(self.position),
-            )),
-            version: serialize_version(&buffer.version()),
-        }
-    }
-
-    async fn from_proto(
-        message: proto::GetReferences,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Self> {
-        let position = message
-            .position
-            .and_then(deserialize_anchor)
-            .ok_or_else(|| anyhow!("invalid position"))?;
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(deserialize_version(&message.version))
-            })?
-            .await?;
-        Ok(Self {
-            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
-        })
-    }
-
-    fn response_to_proto(
-        response: Vec<Location>,
-        project: &mut Project,
-        peer_id: PeerId,
-        _: &clock::Global,
-        cx: &mut AppContext,
-    ) -> proto::GetReferencesResponse {
-        let locations = response
-            .into_iter()
-            .map(|definition| {
-                let buffer_id = project.create_buffer_for_peer(&definition.buffer, peer_id, cx);
-                proto::Location {
-                    start: Some(serialize_anchor(&definition.range.start)),
-                    end: Some(serialize_anchor(&definition.range.end)),
-                    buffer_id,
-                }
-            })
-            .collect();
-        proto::GetReferencesResponse { locations }
-    }
-
-    async fn response_from_proto(
-        self,
-        message: proto::GetReferencesResponse,
-        project: Model<Project>,
-        _: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Vec<Location>> {
-        let mut locations = Vec::new();
-        for location in message.locations {
-            let target_buffer = project
-                .update(&mut cx, |this, cx| {
-                    this.wait_for_remote_buffer(location.buffer_id, cx)
-                })?
-                .await?;
-            let start = location
-                .start
-                .and_then(deserialize_anchor)
-                .ok_or_else(|| anyhow!("missing target start"))?;
-            let end = location
-                .end
-                .and_then(deserialize_anchor)
-                .ok_or_else(|| anyhow!("missing target end"))?;
-            target_buffer
-                .update(&mut cx, |buffer, _| buffer.wait_for_anchors([start, end]))?
-                .await?;
-            locations.push(Location {
-                buffer: target_buffer,
-                range: start..end,
-            })
-        }
-        Ok(locations)
-    }
-
-    fn buffer_id_from_proto(message: &proto::GetReferences) -> u64 {
-        message.buffer_id
-    }
-}
-
-#[async_trait(?Send)]
-impl LspCommand for GetDocumentHighlights {
-    type Response = Vec<DocumentHighlight>;
-    type LspRequest = lsp::request::DocumentHighlightRequest;
-    type ProtoRequest = proto::GetDocumentHighlights;
-
-    fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool {
-        capabilities.document_highlight_provider.is_some()
-    }
-
-    fn to_lsp(
-        &self,
-        path: &Path,
-        _: &Buffer,
-        _: &Arc<LanguageServer>,
-        _: &AppContext,
-    ) -> lsp::DocumentHighlightParams {
-        lsp::DocumentHighlightParams {
-            text_document_position_params: lsp::TextDocumentPositionParams {
-                text_document: lsp::TextDocumentIdentifier {
-                    uri: lsp::Url::from_file_path(path).unwrap(),
-                },
-                position: point_to_lsp(self.position),
-            },
-            work_done_progress_params: Default::default(),
-            partial_result_params: Default::default(),
-        }
-    }
-
-    async fn response_from_lsp(
-        self,
-        lsp_highlights: Option<Vec<lsp::DocumentHighlight>>,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        _: LanguageServerId,
-        mut cx: AsyncAppContext,
-    ) -> Result<Vec<DocumentHighlight>> {
-        buffer.update(&mut cx, |buffer, _| {
-            let mut lsp_highlights = lsp_highlights.unwrap_or_default();
-            lsp_highlights.sort_unstable_by_key(|h| (h.range.start, Reverse(h.range.end)));
-            lsp_highlights
-                .into_iter()
-                .map(|lsp_highlight| {
-                    let start = buffer
-                        .clip_point_utf16(point_from_lsp(lsp_highlight.range.start), Bias::Left);
-                    let end = buffer
-                        .clip_point_utf16(point_from_lsp(lsp_highlight.range.end), Bias::Left);
-                    DocumentHighlight {
-                        range: buffer.anchor_after(start)..buffer.anchor_before(end),
-                        kind: lsp_highlight
-                            .kind
-                            .unwrap_or(lsp::DocumentHighlightKind::READ),
-                    }
-                })
-                .collect()
-        })
-    }
-
-    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetDocumentHighlights {
-        proto::GetDocumentHighlights {
-            project_id,
-            buffer_id: buffer.remote_id(),
-            position: Some(language::proto::serialize_anchor(
-                &buffer.anchor_before(self.position),
-            )),
-            version: serialize_version(&buffer.version()),
-        }
-    }
-
-    async fn from_proto(
-        message: proto::GetDocumentHighlights,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Self> {
-        let position = message
-            .position
-            .and_then(deserialize_anchor)
-            .ok_or_else(|| anyhow!("invalid position"))?;
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(deserialize_version(&message.version))
-            })?
-            .await?;
-        Ok(Self {
-            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
-        })
-    }
-
-    fn response_to_proto(
-        response: Vec<DocumentHighlight>,
-        _: &mut Project,
-        _: PeerId,
-        _: &clock::Global,
-        _: &mut AppContext,
-    ) -> proto::GetDocumentHighlightsResponse {
-        let highlights = response
-            .into_iter()
-            .map(|highlight| proto::DocumentHighlight {
-                start: Some(serialize_anchor(&highlight.range.start)),
-                end: Some(serialize_anchor(&highlight.range.end)),
-                kind: match highlight.kind {
-                    DocumentHighlightKind::TEXT => proto::document_highlight::Kind::Text.into(),
-                    DocumentHighlightKind::WRITE => proto::document_highlight::Kind::Write.into(),
-                    DocumentHighlightKind::READ => proto::document_highlight::Kind::Read.into(),
-                    _ => proto::document_highlight::Kind::Text.into(),
-                },
-            })
-            .collect();
-        proto::GetDocumentHighlightsResponse { highlights }
-    }
-
-    async fn response_from_proto(
-        self,
-        message: proto::GetDocumentHighlightsResponse,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Vec<DocumentHighlight>> {
-        let mut highlights = Vec::new();
-        for highlight in message.highlights {
-            let start = highlight
-                .start
-                .and_then(deserialize_anchor)
-                .ok_or_else(|| anyhow!("missing target start"))?;
-            let end = highlight
-                .end
-                .and_then(deserialize_anchor)
-                .ok_or_else(|| anyhow!("missing target end"))?;
-            buffer
-                .update(&mut cx, |buffer, _| buffer.wait_for_anchors([start, end]))?
-                .await?;
-            let kind = match proto::document_highlight::Kind::from_i32(highlight.kind) {
-                Some(proto::document_highlight::Kind::Text) => DocumentHighlightKind::TEXT,
-                Some(proto::document_highlight::Kind::Read) => DocumentHighlightKind::READ,
-                Some(proto::document_highlight::Kind::Write) => DocumentHighlightKind::WRITE,
-                None => DocumentHighlightKind::TEXT,
-            };
-            highlights.push(DocumentHighlight {
-                range: start..end,
-                kind,
-            });
-        }
-        Ok(highlights)
-    }
-
-    fn buffer_id_from_proto(message: &proto::GetDocumentHighlights) -> u64 {
-        message.buffer_id
-    }
-}
-
-#[async_trait(?Send)]
-impl LspCommand for GetHover {
-    type Response = Option<Hover>;
-    type LspRequest = lsp::request::HoverRequest;
-    type ProtoRequest = proto::GetHover;
-
-    fn to_lsp(
-        &self,
-        path: &Path,
-        _: &Buffer,
-        _: &Arc<LanguageServer>,
-        _: &AppContext,
-    ) -> lsp::HoverParams {
-        lsp::HoverParams {
-            text_document_position_params: lsp::TextDocumentPositionParams {
-                text_document: lsp::TextDocumentIdentifier {
-                    uri: lsp::Url::from_file_path(path).unwrap(),
-                },
-                position: point_to_lsp(self.position),
-            },
-            work_done_progress_params: Default::default(),
-        }
-    }
-
-    async fn response_from_lsp(
-        self,
-        message: Option<lsp::Hover>,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        _: LanguageServerId,
-        mut cx: AsyncAppContext,
-    ) -> Result<Self::Response> {
-        let Some(hover) = message else {
-            return Ok(None);
-        };
-
-        let (language, range) = buffer.update(&mut cx, |buffer, _| {
-            (
-                buffer.language().cloned(),
-                hover.range.map(|range| {
-                    let token_start =
-                        buffer.clip_point_utf16(point_from_lsp(range.start), Bias::Left);
-                    let token_end = buffer.clip_point_utf16(point_from_lsp(range.end), Bias::Left);
-                    buffer.anchor_after(token_start)..buffer.anchor_before(token_end)
-                }),
-            )
-        })?;
-
-        fn hover_blocks_from_marked_string(marked_string: lsp::MarkedString) -> Option<HoverBlock> {
-            let block = match marked_string {
-                lsp::MarkedString::String(content) => HoverBlock {
-                    text: content,
-                    kind: HoverBlockKind::Markdown,
-                },
-                lsp::MarkedString::LanguageString(lsp::LanguageString { language, value }) => {
-                    HoverBlock {
-                        text: value,
-                        kind: HoverBlockKind::Code { language },
-                    }
-                }
-            };
-            if block.text.is_empty() {
-                None
-            } else {
-                Some(block)
-            }
-        }
-
-        let contents = match hover.contents {
-            lsp::HoverContents::Scalar(marked_string) => {
-                hover_blocks_from_marked_string(marked_string)
-                    .into_iter()
-                    .collect()
-            }
-            lsp::HoverContents::Array(marked_strings) => marked_strings
-                .into_iter()
-                .filter_map(hover_blocks_from_marked_string)
-                .collect(),
-            lsp::HoverContents::Markup(markup_content) => vec![HoverBlock {
-                text: markup_content.value,
-                kind: if markup_content.kind == lsp::MarkupKind::Markdown {
-                    HoverBlockKind::Markdown
-                } else {
-                    HoverBlockKind::PlainText
-                },
-            }],
-        };
-
-        Ok(Some(Hover {
-            contents,
-            range,
-            language,
-        }))
-    }
-
-    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest {
-        proto::GetHover {
-            project_id,
-            buffer_id: buffer.remote_id(),
-            position: Some(language::proto::serialize_anchor(
-                &buffer.anchor_before(self.position),
-            )),
-            version: serialize_version(&buffer.version),
-        }
-    }
-
-    async fn from_proto(
-        message: Self::ProtoRequest,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Self> {
-        let position = message
-            .position
-            .and_then(deserialize_anchor)
-            .ok_or_else(|| anyhow!("invalid position"))?;
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(deserialize_version(&message.version))
-            })?
-            .await?;
-        Ok(Self {
-            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
-        })
-    }
-
-    fn response_to_proto(
-        response: Self::Response,
-        _: &mut Project,
-        _: PeerId,
-        _: &clock::Global,
-        _: &mut AppContext,
-    ) -> proto::GetHoverResponse {
-        if let Some(response) = response {
-            let (start, end) = if let Some(range) = response.range {
-                (
-                    Some(language::proto::serialize_anchor(&range.start)),
-                    Some(language::proto::serialize_anchor(&range.end)),
-                )
-            } else {
-                (None, None)
-            };
-
-            let contents = response
-                .contents
-                .into_iter()
-                .map(|block| proto::HoverBlock {
-                    text: block.text,
-                    is_markdown: block.kind == HoverBlockKind::Markdown,
-                    language: if let HoverBlockKind::Code { language } = block.kind {
-                        Some(language)
-                    } else {
-                        None
-                    },
-                })
-                .collect();
-
-            proto::GetHoverResponse {
-                start,
-                end,
-                contents,
-            }
-        } else {
-            proto::GetHoverResponse {
-                start: None,
-                end: None,
-                contents: Vec::new(),
-            }
-        }
-    }
-
-    async fn response_from_proto(
-        self,
-        message: proto::GetHoverResponse,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Self::Response> {
-        let contents: Vec<_> = message
-            .contents
-            .into_iter()
-            .map(|block| HoverBlock {
-                text: block.text,
-                kind: if let Some(language) = block.language {
-                    HoverBlockKind::Code { language }
-                } else if block.is_markdown {
-                    HoverBlockKind::Markdown
-                } else {
-                    HoverBlockKind::PlainText
-                },
-            })
-            .collect();
-        if contents.is_empty() {
-            return Ok(None);
-        }
-
-        let language = buffer.update(&mut cx, |buffer, _| buffer.language().cloned())?;
-        let range = if let (Some(start), Some(end)) = (message.start, message.end) {
-            language::proto::deserialize_anchor(start)
-                .and_then(|start| language::proto::deserialize_anchor(end).map(|end| start..end))
-        } else {
-            None
-        };
-
-        Ok(Some(Hover {
-            contents,
-            range,
-            language,
-        }))
-    }
-
-    fn buffer_id_from_proto(message: &Self::ProtoRequest) -> u64 {
-        message.buffer_id
-    }
-}
-
-#[async_trait(?Send)]
-impl LspCommand for GetCompletions {
-    type Response = Vec<Completion>;
-    type LspRequest = lsp::request::Completion;
-    type ProtoRequest = proto::GetCompletions;
-
-    fn to_lsp(
-        &self,
-        path: &Path,
-        _: &Buffer,
-        _: &Arc<LanguageServer>,
-        _: &AppContext,
-    ) -> lsp::CompletionParams {
-        lsp::CompletionParams {
-            text_document_position: lsp::TextDocumentPositionParams::new(
-                lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path(path).unwrap()),
-                point_to_lsp(self.position),
-            ),
-            context: Default::default(),
-            work_done_progress_params: Default::default(),
-            partial_result_params: Default::default(),
-        }
-    }
-
-    async fn response_from_lsp(
-        self,
-        completions: Option<lsp::CompletionResponse>,
-        project: Model<Project>,
-        buffer: Model<Buffer>,
-        server_id: LanguageServerId,
-        mut cx: AsyncAppContext,
-    ) -> Result<Vec<Completion>> {
-        let mut response_list = None;
-        let completions = if let Some(completions) = completions {
-            match completions {
-                lsp::CompletionResponse::Array(completions) => completions,
-
-                lsp::CompletionResponse::List(mut list) => {
-                    let items = std::mem::take(&mut list.items);
-                    response_list = Some(list);
-                    items
-                }
-            }
-        } else {
-            Default::default()
-        };
-
-        let completions = buffer.update(&mut cx, |buffer, cx| {
-            let language_registry = project.read(cx).languages().clone();
-            let language = buffer.language().cloned();
-            let snapshot = buffer.snapshot();
-            let clipped_position = buffer.clip_point_utf16(Unclipped(self.position), Bias::Left);
-
-            let mut range_for_token = None;
-            completions
-                .into_iter()
-                .filter_map(move |mut lsp_completion| {
-                    let (old_range, mut new_text) = match lsp_completion.text_edit.as_ref() {
-                        // If the language server provides a range to overwrite, then
-                        // check that the range is valid.
-                        Some(lsp::CompletionTextEdit::Edit(edit)) => {
-                            let range = range_from_lsp(edit.range);
-                            let start = snapshot.clip_point_utf16(range.start, Bias::Left);
-                            let end = snapshot.clip_point_utf16(range.end, Bias::Left);
-                            if start != range.start.0 || end != range.end.0 {
-                                log::info!("completion out of expected range");
-                                return None;
-                            }
-                            (
-                                snapshot.anchor_before(start)..snapshot.anchor_after(end),
-                                edit.new_text.clone(),
-                            )
-                        }
-
-                        // If the language server does not provide a range, then infer
-                        // the range based on the syntax tree.
-                        None => {
-                            if self.position != clipped_position {
-                                log::info!("completion out of expected range");
-                                return None;
-                            }
-
-                            let default_edit_range = response_list
-                                .as_ref()
-                                .and_then(|list| list.item_defaults.as_ref())
-                                .and_then(|defaults| defaults.edit_range.as_ref())
-                                .and_then(|range| match range {
-                                    CompletionListItemDefaultsEditRange::Range(r) => Some(r),
-                                    _ => None,
-                                });
-
-                            let range = if let Some(range) = default_edit_range {
-                                let range = range_from_lsp(range.clone());
-                                let start = snapshot.clip_point_utf16(range.start, Bias::Left);
-                                let end = snapshot.clip_point_utf16(range.end, Bias::Left);
-                                if start != range.start.0 || end != range.end.0 {
-                                    log::info!("completion out of expected range");
-                                    return None;
-                                }
-
-                                snapshot.anchor_before(start)..snapshot.anchor_after(end)
-                            } else {
-                                range_for_token
-                                    .get_or_insert_with(|| {
-                                        let offset = self.position.to_offset(&snapshot);
-                                        let (range, kind) = snapshot.surrounding_word(offset);
-                                        let range = if kind == Some(CharKind::Word) {
-                                            range
-                                        } else {
-                                            offset..offset
-                                        };
-
-                                        snapshot.anchor_before(range.start)
-                                            ..snapshot.anchor_after(range.end)
-                                    })
-                                    .clone()
-                            };
-
-                            let text = lsp_completion
-                                .insert_text
-                                .as_ref()
-                                .unwrap_or(&lsp_completion.label)
-                                .clone();
-                            (range, text)
-                        }
-
-                        Some(lsp::CompletionTextEdit::InsertAndReplace(_)) => {
-                            log::info!("unsupported insert/replace completion");
-                            return None;
-                        }
-                    };
-
-                    let language_registry = language_registry.clone();
-                    let language = language.clone();
-                    LineEnding::normalize(&mut new_text);
-                    Some(async move {
-                        let mut label = None;
-                        if let Some(language) = language.as_ref() {
-                            language.process_completion(&mut lsp_completion).await;
-                            label = language.label_for_completion(&lsp_completion).await;
-                        }
-
-                        let documentation = if let Some(lsp_docs) = &lsp_completion.documentation {
-                            Some(
-                                prepare_completion_documentation(
-                                    lsp_docs,
-                                    &language_registry,
-                                    language.clone(),
-                                )
-                                .await,
-                            )
-                        } else {
-                            None
-                        };
-
-                        Completion {
-                            old_range,
-                            new_text,
-                            label: label.unwrap_or_else(|| {
-                                language::CodeLabel::plain(
-                                    lsp_completion.label.clone(),
-                                    lsp_completion.filter_text.as_deref(),
-                                )
-                            }),
-                            documentation,
-                            server_id,
-                            lsp_completion,
-                        }
-                    })
-                })
-        })?;
-
-        Ok(future::join_all(completions).await)
-    }
-
-    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetCompletions {
-        let anchor = buffer.anchor_after(self.position);
-        proto::GetCompletions {
-            project_id,
-            buffer_id: buffer.remote_id(),
-            position: Some(language::proto::serialize_anchor(&anchor)),
-            version: serialize_version(&buffer.version()),
-        }
-    }
-
-    async fn from_proto(
-        message: proto::GetCompletions,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Self> {
-        let version = deserialize_version(&message.version);
-        buffer
-            .update(&mut cx, |buffer, _| buffer.wait_for_version(version))?
-            .await?;
-        let position = message
-            .position
-            .and_then(language::proto::deserialize_anchor)
-            .map(|p| {
-                buffer.update(&mut cx, |buffer, _| {
-                    buffer.clip_point_utf16(Unclipped(p.to_point_utf16(buffer)), Bias::Left)
-                })
-            })
-            .ok_or_else(|| anyhow!("invalid position"))??;
-        Ok(Self { position })
-    }
-
-    fn response_to_proto(
-        completions: Vec<Completion>,
-        _: &mut Project,
-        _: PeerId,
-        buffer_version: &clock::Global,
-        _: &mut AppContext,
-    ) -> proto::GetCompletionsResponse {
-        proto::GetCompletionsResponse {
-            completions: completions
-                .iter()
-                .map(language::proto::serialize_completion)
-                .collect(),
-            version: serialize_version(&buffer_version),
-        }
-    }
-
-    async fn response_from_proto(
-        self,
-        message: proto::GetCompletionsResponse,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Vec<Completion>> {
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(deserialize_version(&message.version))
-            })?
-            .await?;
-
-        let language = buffer.update(&mut cx, |buffer, _| buffer.language().cloned())?;
-        let completions = message.completions.into_iter().map(|completion| {
-            language::proto::deserialize_completion(completion, language.clone())
-        });
-        future::try_join_all(completions).await
-    }
-
-    fn buffer_id_from_proto(message: &proto::GetCompletions) -> u64 {
-        message.buffer_id
-    }
-}
-
-#[async_trait(?Send)]
-impl LspCommand for GetCodeActions {
-    type Response = Vec<CodeAction>;
-    type LspRequest = lsp::request::CodeActionRequest;
-    type ProtoRequest = proto::GetCodeActions;
-
-    fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool {
-        match &capabilities.code_action_provider {
-            None => false,
-            Some(lsp::CodeActionProviderCapability::Simple(false)) => false,
-            _ => true,
-        }
-    }
-
-    fn to_lsp(
-        &self,
-        path: &Path,
-        buffer: &Buffer,
-        language_server: &Arc<LanguageServer>,
-        _: &AppContext,
-    ) -> lsp::CodeActionParams {
-        let relevant_diagnostics = buffer
-            .snapshot()
-            .diagnostics_in_range::<_, usize>(self.range.clone(), false)
-            .map(|entry| entry.to_lsp_diagnostic_stub())
-            .collect();
-        lsp::CodeActionParams {
-            text_document: lsp::TextDocumentIdentifier::new(
-                lsp::Url::from_file_path(path).unwrap(),
-            ),
-            range: range_to_lsp(self.range.to_point_utf16(buffer)),
-            work_done_progress_params: Default::default(),
-            partial_result_params: Default::default(),
-            context: lsp::CodeActionContext {
-                diagnostics: relevant_diagnostics,
-                only: language_server.code_action_kinds(),
-                ..lsp::CodeActionContext::default()
-            },
-        }
-    }
-
-    async fn response_from_lsp(
-        self,
-        actions: Option<lsp::CodeActionResponse>,
-        _: Model<Project>,
-        _: Model<Buffer>,
-        server_id: LanguageServerId,
-        _: AsyncAppContext,
-    ) -> Result<Vec<CodeAction>> {
-        Ok(actions
-            .unwrap_or_default()
-            .into_iter()
-            .filter_map(|entry| {
-                if let lsp::CodeActionOrCommand::CodeAction(lsp_action) = entry {
-                    Some(CodeAction {
-                        server_id,
-                        range: self.range.clone(),
-                        lsp_action,
-                    })
-                } else {
-                    None
-                }
-            })
-            .collect())
-    }
-
-    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetCodeActions {
-        proto::GetCodeActions {
-            project_id,
-            buffer_id: buffer.remote_id(),
-            start: Some(language::proto::serialize_anchor(&self.range.start)),
-            end: Some(language::proto::serialize_anchor(&self.range.end)),
-            version: serialize_version(&buffer.version()),
-        }
-    }
-
-    async fn from_proto(
-        message: proto::GetCodeActions,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Self> {
-        let start = message
-            .start
-            .and_then(language::proto::deserialize_anchor)
-            .ok_or_else(|| anyhow!("invalid start"))?;
-        let end = message
-            .end
-            .and_then(language::proto::deserialize_anchor)
-            .ok_or_else(|| anyhow!("invalid end"))?;
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(deserialize_version(&message.version))
-            })?
-            .await?;
-
-        Ok(Self { range: start..end })
-    }
-
-    fn response_to_proto(
-        code_actions: Vec<CodeAction>,
-        _: &mut Project,
-        _: PeerId,
-        buffer_version: &clock::Global,
-        _: &mut AppContext,
-    ) -> proto::GetCodeActionsResponse {
-        proto::GetCodeActionsResponse {
-            actions: code_actions
-                .iter()
-                .map(language::proto::serialize_code_action)
-                .collect(),
-            version: serialize_version(&buffer_version),
-        }
-    }
-
-    async fn response_from_proto(
-        self,
-        message: proto::GetCodeActionsResponse,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Vec<CodeAction>> {
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(deserialize_version(&message.version))
-            })?
-            .await?;
-        message
-            .actions
-            .into_iter()
-            .map(language::proto::deserialize_code_action)
-            .collect()
-    }
-
-    fn buffer_id_from_proto(message: &proto::GetCodeActions) -> u64 {
-        message.buffer_id
-    }
-}
-
-#[async_trait(?Send)]
-impl LspCommand for OnTypeFormatting {
-    type Response = Option<Transaction>;
-    type LspRequest = lsp::request::OnTypeFormatting;
-    type ProtoRequest = proto::OnTypeFormatting;
-
-    fn check_capabilities(&self, server_capabilities: &lsp::ServerCapabilities) -> bool {
-        let Some(on_type_formatting_options) =
-            &server_capabilities.document_on_type_formatting_provider
-        else {
-            return false;
-        };
-        on_type_formatting_options
-            .first_trigger_character
-            .contains(&self.trigger)
-            || on_type_formatting_options
-                .more_trigger_character
-                .iter()
-                .flatten()
-                .any(|chars| chars.contains(&self.trigger))
-    }
-
-    fn to_lsp(
-        &self,
-        path: &Path,
-        _: &Buffer,
-        _: &Arc<LanguageServer>,
-        _: &AppContext,
-    ) -> lsp::DocumentOnTypeFormattingParams {
-        lsp::DocumentOnTypeFormattingParams {
-            text_document_position: lsp::TextDocumentPositionParams::new(
-                lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path(path).unwrap()),
-                point_to_lsp(self.position),
-            ),
-            ch: self.trigger.clone(),
-            options: lsp_formatting_options(self.options.tab_size),
-        }
-    }
-
-    async fn response_from_lsp(
-        self,
-        message: Option<Vec<lsp::TextEdit>>,
-        project: Model<Project>,
-        buffer: Model<Buffer>,
-        server_id: LanguageServerId,
-        mut cx: AsyncAppContext,
-    ) -> Result<Option<Transaction>> {
-        if let Some(edits) = message {
-            let (lsp_adapter, lsp_server) =
-                language_server_for_buffer(&project, &buffer, server_id, &mut cx)?;
-            Project::deserialize_edits(
-                project,
-                buffer,
-                edits,
-                self.push_to_history,
-                lsp_adapter,
-                lsp_server,
-                &mut cx,
-            )
-            .await
-        } else {
-            Ok(None)
-        }
-    }
-
-    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::OnTypeFormatting {
-        proto::OnTypeFormatting {
-            project_id,
-            buffer_id: buffer.remote_id(),
-            position: Some(language::proto::serialize_anchor(
-                &buffer.anchor_before(self.position),
-            )),
-            trigger: self.trigger.clone(),
-            version: serialize_version(&buffer.version()),
-        }
-    }
-
-    async fn from_proto(
-        message: proto::OnTypeFormatting,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Self> {
-        let position = message
-            .position
-            .and_then(deserialize_anchor)
-            .ok_or_else(|| anyhow!("invalid position"))?;
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(deserialize_version(&message.version))
-            })?
-            .await?;
-
-        let tab_size = buffer.update(&mut cx, |buffer, cx| {
-            language_settings(buffer.language(), buffer.file(), cx).tab_size
-        })?;
-
-        Ok(Self {
-            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
-            trigger: message.trigger.clone(),
-            options: lsp_formatting_options(tab_size.get()).into(),
-            push_to_history: false,
-        })
-    }
-
-    fn response_to_proto(
-        response: Option<Transaction>,
-        _: &mut Project,
-        _: PeerId,
-        _: &clock::Global,
-        _: &mut AppContext,
-    ) -> proto::OnTypeFormattingResponse {
-        proto::OnTypeFormattingResponse {
-            transaction: response
-                .map(|transaction| language::proto::serialize_transaction(&transaction)),
-        }
-    }
-
-    async fn response_from_proto(
-        self,
-        message: proto::OnTypeFormattingResponse,
-        _: Model<Project>,
-        _: Model<Buffer>,
-        _: AsyncAppContext,
-    ) -> Result<Option<Transaction>> {
-        let Some(transaction) = message.transaction else {
-            return Ok(None);
-        };
-        Ok(Some(language::proto::deserialize_transaction(transaction)?))
-    }
-
-    fn buffer_id_from_proto(message: &proto::OnTypeFormatting) -> u64 {
-        message.buffer_id
-    }
-}
-
-impl InlayHints {
-    pub async fn lsp_to_project_hint(
-        lsp_hint: lsp::InlayHint,
-        buffer_handle: &Model<Buffer>,
-        server_id: LanguageServerId,
-        resolve_state: ResolveState,
-        force_no_type_left_padding: bool,
-        cx: &mut AsyncAppContext,
-    ) -> anyhow::Result<InlayHint> {
-        let kind = lsp_hint.kind.and_then(|kind| match kind {
-            lsp::InlayHintKind::TYPE => Some(InlayHintKind::Type),
-            lsp::InlayHintKind::PARAMETER => Some(InlayHintKind::Parameter),
-            _ => None,
-        });
-
-        let position = buffer_handle.update(cx, |buffer, _| {
-            let position = buffer.clip_point_utf16(point_from_lsp(lsp_hint.position), Bias::Left);
-            if kind == Some(InlayHintKind::Parameter) {
-                buffer.anchor_before(position)
-            } else {
-                buffer.anchor_after(position)
-            }
-        })?;
-        let label = Self::lsp_inlay_label_to_project(lsp_hint.label, server_id)
-            .await
-            .context("lsp to project inlay hint conversion")?;
-        let padding_left = if force_no_type_left_padding && kind == Some(InlayHintKind::Type) {
-            false
-        } else {
-            lsp_hint.padding_left.unwrap_or(false)
-        };
-
-        Ok(InlayHint {
-            position,
-            padding_left,
-            padding_right: lsp_hint.padding_right.unwrap_or(false),
-            label,
-            kind,
-            tooltip: lsp_hint.tooltip.map(|tooltip| match tooltip {
-                lsp::InlayHintTooltip::String(s) => InlayHintTooltip::String(s),
-                lsp::InlayHintTooltip::MarkupContent(markup_content) => {
-                    InlayHintTooltip::MarkupContent(MarkupContent {
-                        kind: match markup_content.kind {
-                            lsp::MarkupKind::PlainText => HoverBlockKind::PlainText,
-                            lsp::MarkupKind::Markdown => HoverBlockKind::Markdown,
-                        },
-                        value: markup_content.value,
-                    })
-                }
-            }),
-            resolve_state,
-        })
-    }
-
-    async fn lsp_inlay_label_to_project(
-        lsp_label: lsp::InlayHintLabel,
-        server_id: LanguageServerId,
-    ) -> anyhow::Result<InlayHintLabel> {
-        let label = match lsp_label {
-            lsp::InlayHintLabel::String(s) => InlayHintLabel::String(s),
-            lsp::InlayHintLabel::LabelParts(lsp_parts) => {
-                let mut parts = Vec::with_capacity(lsp_parts.len());
-                for lsp_part in lsp_parts {
-                    parts.push(InlayHintLabelPart {
-                        value: lsp_part.value,
-                        tooltip: lsp_part.tooltip.map(|tooltip| match tooltip {
-                            lsp::InlayHintLabelPartTooltip::String(s) => {
-                                InlayHintLabelPartTooltip::String(s)
-                            }
-                            lsp::InlayHintLabelPartTooltip::MarkupContent(markup_content) => {
-                                InlayHintLabelPartTooltip::MarkupContent(MarkupContent {
-                                    kind: match markup_content.kind {
-                                        lsp::MarkupKind::PlainText => HoverBlockKind::PlainText,
-                                        lsp::MarkupKind::Markdown => HoverBlockKind::Markdown,
-                                    },
-                                    value: markup_content.value,
-                                })
-                            }
-                        }),
-                        location: Some(server_id).zip(lsp_part.location),
-                    });
-                }
-                InlayHintLabel::LabelParts(parts)
-            }
-        };
-
-        Ok(label)
-    }
-
-    pub fn project_to_proto_hint(response_hint: InlayHint) -> proto::InlayHint {
-        let (state, lsp_resolve_state) = match response_hint.resolve_state {
-            ResolveState::Resolved => (0, None),
-            ResolveState::CanResolve(server_id, resolve_data) => (
-                1,
-                resolve_data
-                    .map(|json_data| {
-                        serde_json::to_string(&json_data)
-                            .expect("failed to serialize resolve json data")
-                    })
-                    .map(|value| proto::resolve_state::LspResolveState {
-                        server_id: server_id.0 as u64,
-                        value,
-                    }),
-            ),
-            ResolveState::Resolving => (2, None),
-        };
-        let resolve_state = Some(proto::ResolveState {
-            state,
-            lsp_resolve_state,
-        });
-        proto::InlayHint {
-            position: Some(language::proto::serialize_anchor(&response_hint.position)),
-            padding_left: response_hint.padding_left,
-            padding_right: response_hint.padding_right,
-            label: Some(proto::InlayHintLabel {
-                label: Some(match response_hint.label {
-                    InlayHintLabel::String(s) => proto::inlay_hint_label::Label::Value(s),
-                    InlayHintLabel::LabelParts(label_parts) => {
-                        proto::inlay_hint_label::Label::LabelParts(proto::InlayHintLabelParts {
-                            parts: label_parts.into_iter().map(|label_part| {
-                                let location_url = label_part.location.as_ref().map(|(_, location)| location.uri.to_string());
-                                let location_range_start = label_part.location.as_ref().map(|(_, location)| point_from_lsp(location.range.start).0).map(|point| proto::PointUtf16 { row: point.row, column: point.column });
-                                let location_range_end = label_part.location.as_ref().map(|(_, location)| point_from_lsp(location.range.end).0).map(|point| proto::PointUtf16 { row: point.row, column: point.column });
-                                proto::InlayHintLabelPart {
-                                value: label_part.value,
-                                tooltip: label_part.tooltip.map(|tooltip| {
-                                    let proto_tooltip = match tooltip {
-                                        InlayHintLabelPartTooltip::String(s) => proto::inlay_hint_label_part_tooltip::Content::Value(s),
-                                        InlayHintLabelPartTooltip::MarkupContent(markup_content) => proto::inlay_hint_label_part_tooltip::Content::MarkupContent(proto::MarkupContent {
-                                            is_markdown: markup_content.kind == HoverBlockKind::Markdown,
-                                            value: markup_content.value,
-                                        }),
-                                    };
-                                    proto::InlayHintLabelPartTooltip {content: Some(proto_tooltip)}
-                                }),
-                                location_url,
-                                location_range_start,
-                                location_range_end,
-                                language_server_id: label_part.location.as_ref().map(|(server_id, _)| server_id.0 as u64),
-                            }}).collect()
-                        })
-                    }
-                }),
-            }),
-            kind: response_hint.kind.map(|kind| kind.name().to_string()),
-            tooltip: response_hint.tooltip.map(|response_tooltip| {
-                let proto_tooltip = match response_tooltip {
-                    InlayHintTooltip::String(s) => proto::inlay_hint_tooltip::Content::Value(s),
-                    InlayHintTooltip::MarkupContent(markup_content) => {
-                        proto::inlay_hint_tooltip::Content::MarkupContent(proto::MarkupContent {
-                            is_markdown: markup_content.kind == HoverBlockKind::Markdown,
-                            value: markup_content.value,
-                        })
-                    }
-                };
-                proto::InlayHintTooltip {
-                    content: Some(proto_tooltip),
-                }
-            }),
-            resolve_state,
-        }
-    }
-
-    pub fn proto_to_project_hint(message_hint: proto::InlayHint) -> anyhow::Result<InlayHint> {
-        let resolve_state = message_hint.resolve_state.as_ref().unwrap_or_else(|| {
-            panic!("incorrect proto inlay hint message: no resolve state in hint {message_hint:?}",)
-        });
-        let resolve_state_data = resolve_state
-            .lsp_resolve_state.as_ref()
-            .map(|lsp_resolve_state| {
-                serde_json::from_str::<Option<lsp::LSPAny>>(&lsp_resolve_state.value)
-                    .with_context(|| format!("incorrect proto inlay hint message: non-json resolve state {lsp_resolve_state:?}"))
-                    .map(|state| (LanguageServerId(lsp_resolve_state.server_id as usize), state))
-            })
-            .transpose()?;
-        let resolve_state = match resolve_state.state {
-            0 => ResolveState::Resolved,
-            1 => {
-                let (server_id, lsp_resolve_state) = resolve_state_data.with_context(|| {
-                    format!(
-                        "No lsp resolve data for the hint that can be resolved: {message_hint:?}"
-                    )
-                })?;
-                ResolveState::CanResolve(server_id, lsp_resolve_state)
-            }
-            2 => ResolveState::Resolving,
-            invalid => {
-                anyhow::bail!("Unexpected resolve state {invalid} for hint {message_hint:?}")
-            }
-        };
-        Ok(InlayHint {
-            position: message_hint
-                .position
-                .and_then(language::proto::deserialize_anchor)
-                .context("invalid position")?,
-            label: match message_hint
-                .label
-                .and_then(|label| label.label)
-                .context("missing label")?
-            {
-                proto::inlay_hint_label::Label::Value(s) => InlayHintLabel::String(s),
-                proto::inlay_hint_label::Label::LabelParts(parts) => {
-                    let mut label_parts = Vec::new();
-                    for part in parts.parts {
-                        label_parts.push(InlayHintLabelPart {
-                            value: part.value,
-                            tooltip: part.tooltip.map(|tooltip| match tooltip.content {
-                                Some(proto::inlay_hint_label_part_tooltip::Content::Value(s)) => {
-                                    InlayHintLabelPartTooltip::String(s)
-                                }
-                                Some(
-                                    proto::inlay_hint_label_part_tooltip::Content::MarkupContent(
-                                        markup_content,
-                                    ),
-                                ) => InlayHintLabelPartTooltip::MarkupContent(MarkupContent {
-                                    kind: if markup_content.is_markdown {
-                                        HoverBlockKind::Markdown
-                                    } else {
-                                        HoverBlockKind::PlainText
-                                    },
-                                    value: markup_content.value,
-                                }),
-                                None => InlayHintLabelPartTooltip::String(String::new()),
-                            }),
-                            location: {
-                                match part
-                                    .location_url
-                                    .zip(
-                                        part.location_range_start.and_then(|start| {
-                                            Some(start..part.location_range_end?)
-                                        }),
-                                    )
-                                    .zip(part.language_server_id)
-                                {
-                                    Some(((uri, range), server_id)) => Some((
-                                        LanguageServerId(server_id as usize),
-                                        lsp::Location {
-                                            uri: lsp::Url::parse(&uri)
-                                                .context("invalid uri in hint part {part:?}")?,
-                                            range: lsp::Range::new(
-                                                point_to_lsp(PointUtf16::new(
-                                                    range.start.row,
-                                                    range.start.column,
-                                                )),
-                                                point_to_lsp(PointUtf16::new(
-                                                    range.end.row,
-                                                    range.end.column,
-                                                )),
-                                            ),
-                                        },
-                                    )),
-                                    None => None,
-                                }
-                            },
-                        });
-                    }
-
-                    InlayHintLabel::LabelParts(label_parts)
-                }
-            },
-            padding_left: message_hint.padding_left,
-            padding_right: message_hint.padding_right,
-            kind: message_hint
-                .kind
-                .as_deref()
-                .and_then(InlayHintKind::from_name),
-            tooltip: message_hint.tooltip.and_then(|tooltip| {
-                Some(match tooltip.content? {
-                    proto::inlay_hint_tooltip::Content::Value(s) => InlayHintTooltip::String(s),
-                    proto::inlay_hint_tooltip::Content::MarkupContent(markup_content) => {
-                        InlayHintTooltip::MarkupContent(MarkupContent {
-                            kind: if markup_content.is_markdown {
-                                HoverBlockKind::Markdown
-                            } else {
-                                HoverBlockKind::PlainText
-                            },
-                            value: markup_content.value,
-                        })
-                    }
-                })
-            }),
-            resolve_state,
-        })
-    }
-
-    pub fn project_to_lsp_hint(hint: InlayHint, snapshot: &BufferSnapshot) -> lsp::InlayHint {
-        lsp::InlayHint {
-            position: point_to_lsp(hint.position.to_point_utf16(snapshot)),
-            kind: hint.kind.map(|kind| match kind {
-                InlayHintKind::Type => lsp::InlayHintKind::TYPE,
-                InlayHintKind::Parameter => lsp::InlayHintKind::PARAMETER,
-            }),
-            text_edits: None,
-            tooltip: hint.tooltip.and_then(|tooltip| {
-                Some(match tooltip {
-                    InlayHintTooltip::String(s) => lsp::InlayHintTooltip::String(s),
-                    InlayHintTooltip::MarkupContent(markup_content) => {
-                        lsp::InlayHintTooltip::MarkupContent(lsp::MarkupContent {
-                            kind: match markup_content.kind {
-                                HoverBlockKind::PlainText => lsp::MarkupKind::PlainText,
-                                HoverBlockKind::Markdown => lsp::MarkupKind::Markdown,
-                                HoverBlockKind::Code { .. } => return None,
-                            },
-                            value: markup_content.value,
-                        })
-                    }
-                })
-            }),
-            label: match hint.label {
-                InlayHintLabel::String(s) => lsp::InlayHintLabel::String(s),
-                InlayHintLabel::LabelParts(label_parts) => lsp::InlayHintLabel::LabelParts(
-                    label_parts
-                        .into_iter()
-                        .map(|part| lsp::InlayHintLabelPart {
-                            value: part.value,
-                            tooltip: part.tooltip.and_then(|tooltip| {
-                                Some(match tooltip {
-                                    InlayHintLabelPartTooltip::String(s) => {
-                                        lsp::InlayHintLabelPartTooltip::String(s)
-                                    }
-                                    InlayHintLabelPartTooltip::MarkupContent(markup_content) => {
-                                        lsp::InlayHintLabelPartTooltip::MarkupContent(
-                                            lsp::MarkupContent {
-                                                kind: match markup_content.kind {
-                                                    HoverBlockKind::PlainText => {
-                                                        lsp::MarkupKind::PlainText
-                                                    }
-                                                    HoverBlockKind::Markdown => {
-                                                        lsp::MarkupKind::Markdown
-                                                    }
-                                                    HoverBlockKind::Code { .. } => return None,
-                                                },
-                                                value: markup_content.value,
-                                            },
-                                        )
-                                    }
-                                })
-                            }),
-                            location: part.location.map(|(_, location)| location),
-                            command: None,
-                        })
-                        .collect(),
-                ),
-            },
-            padding_left: Some(hint.padding_left),
-            padding_right: Some(hint.padding_right),
-            data: match hint.resolve_state {
-                ResolveState::CanResolve(_, data) => data,
-                ResolveState::Resolving | ResolveState::Resolved => None,
-            },
-        }
-    }
-
-    pub fn can_resolve_inlays(capabilities: &ServerCapabilities) -> bool {
-        capabilities
-            .inlay_hint_provider
-            .as_ref()
-            .and_then(|options| match options {
-                OneOf::Left(_is_supported) => None,
-                OneOf::Right(capabilities) => match capabilities {
-                    lsp::InlayHintServerCapabilities::Options(o) => o.resolve_provider,
-                    lsp::InlayHintServerCapabilities::RegistrationOptions(o) => {
-                        o.inlay_hint_options.resolve_provider
-                    }
-                },
-            })
-            .unwrap_or(false)
-    }
-}
-
-#[async_trait(?Send)]
-impl LspCommand for InlayHints {
-    type Response = Vec<InlayHint>;
-    type LspRequest = lsp::InlayHintRequest;
-    type ProtoRequest = proto::InlayHints;
-
-    fn check_capabilities(&self, server_capabilities: &lsp::ServerCapabilities) -> bool {
-        let Some(inlay_hint_provider) = &server_capabilities.inlay_hint_provider else {
-            return false;
-        };
-        match inlay_hint_provider {
-            lsp::OneOf::Left(enabled) => *enabled,
-            lsp::OneOf::Right(inlay_hint_capabilities) => match inlay_hint_capabilities {
-                lsp::InlayHintServerCapabilities::Options(_) => true,
-                lsp::InlayHintServerCapabilities::RegistrationOptions(_) => false,
-            },
-        }
-    }
-
-    fn to_lsp(
-        &self,
-        path: &Path,
-        buffer: &Buffer,
-        _: &Arc<LanguageServer>,
-        _: &AppContext,
-    ) -> lsp::InlayHintParams {
-        lsp::InlayHintParams {
-            text_document: lsp::TextDocumentIdentifier {
-                uri: lsp::Url::from_file_path(path).unwrap(),
-            },
-            range: range_to_lsp(self.range.to_point_utf16(buffer)),
-            work_done_progress_params: Default::default(),
-        }
-    }
-
-    async fn response_from_lsp(
-        self,
-        message: Option<Vec<lsp::InlayHint>>,
-        project: Model<Project>,
-        buffer: Model<Buffer>,
-        server_id: LanguageServerId,
-        mut cx: AsyncAppContext,
-    ) -> anyhow::Result<Vec<InlayHint>> {
-        let (lsp_adapter, lsp_server) =
-            language_server_for_buffer(&project, &buffer, server_id, &mut cx)?;
-        // `typescript-language-server` adds padding to the left for type hints, turning
-        // `const foo: boolean` into `const foo : boolean` which looks odd.
-        // `rust-analyzer` does not have the padding for this case, and we have to accomodate both.
-        //
-        // We could trim the whole string, but being pessimistic on par with the situation above,
-        // there might be a hint with multiple whitespaces at the end(s) which we need to display properly.
-        // Hence let's use a heuristic first to handle the most awkward case and look for more.
-        let force_no_type_left_padding =
-            lsp_adapter.name.0.as_ref() == "typescript-language-server";
-
-        let hints = message.unwrap_or_default().into_iter().map(|lsp_hint| {
-            let resolve_state = if InlayHints::can_resolve_inlays(lsp_server.capabilities()) {
-                ResolveState::CanResolve(lsp_server.server_id(), lsp_hint.data.clone())
-            } else {
-                ResolveState::Resolved
-            };
-
-            let buffer = buffer.clone();
-            cx.spawn(move |mut cx| async move {
-                InlayHints::lsp_to_project_hint(
-                    lsp_hint,
-                    &buffer,
-                    server_id,
-                    resolve_state,
-                    force_no_type_left_padding,
-                    &mut cx,
-                )
-                .await
-            })
-        });
-        future::join_all(hints)
-            .await
-            .into_iter()
-            .collect::<anyhow::Result<_>>()
-            .context("lsp to project inlay hints conversion")
-    }
-
-    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::InlayHints {
-        proto::InlayHints {
-            project_id,
-            buffer_id: buffer.remote_id(),
-            start: Some(language::proto::serialize_anchor(&self.range.start)),
-            end: Some(language::proto::serialize_anchor(&self.range.end)),
-            version: serialize_version(&buffer.version()),
-        }
-    }
-
-    async fn from_proto(
-        message: proto::InlayHints,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Self> {
-        let start = message
-            .start
-            .and_then(language::proto::deserialize_anchor)
-            .context("invalid start")?;
-        let end = message
-            .end
-            .and_then(language::proto::deserialize_anchor)
-            .context("invalid end")?;
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(deserialize_version(&message.version))
-            })?
-            .await?;
-
-        Ok(Self { range: start..end })
-    }
-
-    fn response_to_proto(
-        response: Vec<InlayHint>,
-        _: &mut Project,
-        _: PeerId,
-        buffer_version: &clock::Global,
-        _: &mut AppContext,
-    ) -> proto::InlayHintsResponse {
-        proto::InlayHintsResponse {
-            hints: response
-                .into_iter()
-                .map(|response_hint| InlayHints::project_to_proto_hint(response_hint))
-                .collect(),
-            version: serialize_version(buffer_version),
-        }
-    }
-
-    async fn response_from_proto(
-        self,
-        message: proto::InlayHintsResponse,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> anyhow::Result<Vec<InlayHint>> {
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(deserialize_version(&message.version))
-            })?
-            .await?;
-
-        let mut hints = Vec::new();
-        for message_hint in message.hints {
-            hints.push(InlayHints::proto_to_project_hint(message_hint)?);
-        }
-
-        Ok(hints)
-    }
-
-    fn buffer_id_from_proto(message: &proto::InlayHints) -> u64 {
-        message.buffer_id
-    }
-}

crates/project2/src/lsp_ext_command.rs 🔗

@@ -1,137 +0,0 @@
-use std::{path::Path, sync::Arc};
-
-use anyhow::Context;
-use async_trait::async_trait;
-use gpui::{AppContext, AsyncAppContext, Model};
-use language::{point_to_lsp, proto::deserialize_anchor, Buffer};
-use lsp::{LanguageServer, LanguageServerId};
-use rpc::proto::{self, PeerId};
-use serde::{Deserialize, Serialize};
-use text::{PointUtf16, ToPointUtf16};
-
-use crate::{lsp_command::LspCommand, Project};
-
-pub enum LspExpandMacro {}
-
-impl lsp::request::Request for LspExpandMacro {
-    type Params = ExpandMacroParams;
-    type Result = Option<ExpandedMacro>;
-    const METHOD: &'static str = "rust-analyzer/expandMacro";
-}
-
-#[derive(Deserialize, Serialize, Debug)]
-#[serde(rename_all = "camelCase")]
-pub struct ExpandMacroParams {
-    pub text_document: lsp::TextDocumentIdentifier,
-    pub position: lsp::Position,
-}
-
-#[derive(Default, Deserialize, Serialize, Debug)]
-#[serde(rename_all = "camelCase")]
-pub struct ExpandedMacro {
-    pub name: String,
-    pub expansion: String,
-}
-
-impl ExpandedMacro {
-    pub fn is_empty(&self) -> bool {
-        self.name.is_empty() && self.expansion.is_empty()
-    }
-}
-
-pub struct ExpandMacro {
-    pub position: PointUtf16,
-}
-
-#[async_trait(?Send)]
-impl LspCommand for ExpandMacro {
-    type Response = ExpandedMacro;
-    type LspRequest = LspExpandMacro;
-    type ProtoRequest = proto::LspExtExpandMacro;
-
-    fn to_lsp(
-        &self,
-        path: &Path,
-        _: &Buffer,
-        _: &Arc<LanguageServer>,
-        _: &AppContext,
-    ) -> ExpandMacroParams {
-        ExpandMacroParams {
-            text_document: lsp::TextDocumentIdentifier {
-                uri: lsp::Url::from_file_path(path).unwrap(),
-            },
-            position: point_to_lsp(self.position),
-        }
-    }
-
-    async fn response_from_lsp(
-        self,
-        message: Option<ExpandedMacro>,
-        _: Model<Project>,
-        _: Model<Buffer>,
-        _: LanguageServerId,
-        _: AsyncAppContext,
-    ) -> anyhow::Result<ExpandedMacro> {
-        Ok(message
-            .map(|message| ExpandedMacro {
-                name: message.name,
-                expansion: message.expansion,
-            })
-            .unwrap_or_default())
-    }
-
-    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LspExtExpandMacro {
-        proto::LspExtExpandMacro {
-            project_id,
-            buffer_id: buffer.remote_id(),
-            position: Some(language::proto::serialize_anchor(
-                &buffer.anchor_before(self.position),
-            )),
-        }
-    }
-
-    async fn from_proto(
-        message: Self::ProtoRequest,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> anyhow::Result<Self> {
-        let position = message
-            .position
-            .and_then(deserialize_anchor)
-            .context("invalid position")?;
-        Ok(Self {
-            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
-        })
-    }
-
-    fn response_to_proto(
-        response: ExpandedMacro,
-        _: &mut Project,
-        _: PeerId,
-        _: &clock::Global,
-        _: &mut AppContext,
-    ) -> proto::LspExtExpandMacroResponse {
-        proto::LspExtExpandMacroResponse {
-            name: response.name,
-            expansion: response.expansion,
-        }
-    }
-
-    async fn response_from_proto(
-        self,
-        message: proto::LspExtExpandMacroResponse,
-        _: Model<Project>,
-        _: Model<Buffer>,
-        _: AsyncAppContext,
-    ) -> anyhow::Result<ExpandedMacro> {
-        Ok(ExpandedMacro {
-            name: message.name,
-            expansion: message.expansion,
-        })
-    }
-
-    fn buffer_id_from_proto(message: &proto::LspExtExpandMacro) -> u64 {
-        message.buffer_id
-    }
-}

crates/project2/src/prettier_support.rs 🔗

@@ -1,772 +0,0 @@
-use std::{
-    ops::ControlFlow,
-    path::{Path, PathBuf},
-    sync::Arc,
-};
-
-use anyhow::Context;
-use collections::HashSet;
-use fs::Fs;
-use futures::{
-    future::{self, Shared},
-    FutureExt,
-};
-use gpui::{AsyncAppContext, Model, ModelContext, Task, WeakModel};
-use language::{
-    language_settings::{Formatter, LanguageSettings},
-    Buffer, Language, LanguageServerName, LocalFile,
-};
-use lsp::LanguageServerId;
-use node_runtime::NodeRuntime;
-use prettier::Prettier;
-use util::{paths::DEFAULT_PRETTIER_DIR, ResultExt, TryFutureExt};
-
-use crate::{
-    Event, File, FormatOperation, PathChange, Project, ProjectEntryId, Worktree, WorktreeId,
-};
-
-pub fn prettier_plugins_for_language(
-    language: &Language,
-    language_settings: &LanguageSettings,
-) -> Option<HashSet<&'static str>> {
-    match &language_settings.formatter {
-        Formatter::Prettier { .. } | Formatter::Auto => {}
-        Formatter::LanguageServer | Formatter::External { .. } => return None,
-    };
-    let mut prettier_plugins = None;
-    if language.prettier_parser_name().is_some() {
-        prettier_plugins
-            .get_or_insert_with(|| HashSet::default())
-            .extend(
-                language
-                    .lsp_adapters()
-                    .iter()
-                    .flat_map(|adapter| adapter.prettier_plugins()),
-            )
-    }
-
-    prettier_plugins
-}
-
-pub(super) async fn format_with_prettier(
-    project: &WeakModel<Project>,
-    buffer: &Model<Buffer>,
-    cx: &mut AsyncAppContext,
-) -> Option<FormatOperation> {
-    if let Some((prettier_path, prettier_task)) = project
-        .update(cx, |project, cx| {
-            project.prettier_instance_for_buffer(buffer, cx)
-        })
-        .ok()?
-        .await
-    {
-        match prettier_task.await {
-            Ok(prettier) => {
-                let buffer_path = buffer
-                    .update(cx, |buffer, cx| {
-                        File::from_dyn(buffer.file()).map(|file| file.abs_path(cx))
-                    })
-                    .ok()?;
-                match prettier.format(buffer, buffer_path, cx).await {
-                    Ok(new_diff) => return Some(FormatOperation::Prettier(new_diff)),
-                    Err(e) => {
-                        log::error!(
-                            "Prettier instance from {prettier_path:?} failed to format a buffer: {e:#}"
-                        );
-                    }
-                }
-            }
-            Err(e) => project
-                .update(cx, |project, _| {
-                    let instance_to_update = match prettier_path {
-                        Some(prettier_path) => {
-                            log::error!(
-                            "Prettier instance from path {prettier_path:?} failed to spawn: {e:#}"
-                        );
-                            project.prettier_instances.get_mut(&prettier_path)
-                        }
-                        None => {
-                            log::error!("Default prettier instance failed to spawn: {e:#}");
-                            match &mut project.default_prettier.prettier {
-                                PrettierInstallation::NotInstalled { .. } => None,
-                                PrettierInstallation::Installed(instance) => Some(instance),
-                            }
-                        }
-                    };
-
-                    if let Some(instance) = instance_to_update {
-                        instance.attempt += 1;
-                        instance.prettier = None;
-                    }
-                })
-                .ok()?,
-        }
-    }
-
-    None
-}
-
-pub struct DefaultPrettier {
-    prettier: PrettierInstallation,
-    installed_plugins: HashSet<&'static str>,
-}
-
-pub enum PrettierInstallation {
-    NotInstalled {
-        attempts: usize,
-        installation_task: Option<Shared<Task<Result<(), Arc<anyhow::Error>>>>>,
-        not_installed_plugins: HashSet<&'static str>,
-    },
-    Installed(PrettierInstance),
-}
-
-pub type PrettierTask = Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>;
-
-#[derive(Clone)]
-pub struct PrettierInstance {
-    attempt: usize,
-    prettier: Option<PrettierTask>,
-}
-
-impl Default for DefaultPrettier {
-    fn default() -> Self {
-        Self {
-            prettier: PrettierInstallation::NotInstalled {
-                attempts: 0,
-                installation_task: None,
-                not_installed_plugins: HashSet::default(),
-            },
-            installed_plugins: HashSet::default(),
-        }
-    }
-}
-
-impl DefaultPrettier {
-    pub fn instance(&self) -> Option<&PrettierInstance> {
-        if let PrettierInstallation::Installed(instance) = &self.prettier {
-            Some(instance)
-        } else {
-            None
-        }
-    }
-
-    pub fn prettier_task(
-        &mut self,
-        node: &Arc<dyn NodeRuntime>,
-        worktree_id: Option<WorktreeId>,
-        cx: &mut ModelContext<'_, Project>,
-    ) -> Option<Task<anyhow::Result<PrettierTask>>> {
-        match &mut self.prettier {
-            PrettierInstallation::NotInstalled { .. } => {
-                Some(start_default_prettier(Arc::clone(node), worktree_id, cx))
-            }
-            PrettierInstallation::Installed(existing_instance) => {
-                existing_instance.prettier_task(node, None, worktree_id, cx)
-            }
-        }
-    }
-}
-
-impl PrettierInstance {
-    pub fn prettier_task(
-        &mut self,
-        node: &Arc<dyn NodeRuntime>,
-        prettier_dir: Option<&Path>,
-        worktree_id: Option<WorktreeId>,
-        cx: &mut ModelContext<'_, Project>,
-    ) -> Option<Task<anyhow::Result<PrettierTask>>> {
-        if self.attempt > prettier::FAIL_THRESHOLD {
-            match prettier_dir {
-                Some(prettier_dir) => log::warn!(
-                    "Prettier from path {prettier_dir:?} exceeded launch threshold, not starting"
-                ),
-                None => log::warn!("Default prettier exceeded launch threshold, not starting"),
-            }
-            return None;
-        }
-        Some(match &self.prettier {
-            Some(prettier_task) => Task::ready(Ok(prettier_task.clone())),
-            None => match prettier_dir {
-                Some(prettier_dir) => {
-                    let new_task = start_prettier(
-                        Arc::clone(node),
-                        prettier_dir.to_path_buf(),
-                        worktree_id,
-                        cx,
-                    );
-                    self.attempt += 1;
-                    self.prettier = Some(new_task.clone());
-                    Task::ready(Ok(new_task))
-                }
-                None => {
-                    self.attempt += 1;
-                    let node = Arc::clone(node);
-                    cx.spawn(|project, mut cx| async move {
-                        project
-                            .update(&mut cx, |_, cx| {
-                                start_default_prettier(node, worktree_id, cx)
-                            })?
-                            .await
-                    })
-                }
-            },
-        })
-    }
-}
-
-fn start_default_prettier(
-    node: Arc<dyn NodeRuntime>,
-    worktree_id: Option<WorktreeId>,
-    cx: &mut ModelContext<'_, Project>,
-) -> Task<anyhow::Result<PrettierTask>> {
-    cx.spawn(|project, mut cx| async move {
-        loop {
-            let installation_task = project.update(&mut cx, |project, _| {
-                match &project.default_prettier.prettier {
-                    PrettierInstallation::NotInstalled {
-                        installation_task, ..
-                    } => ControlFlow::Continue(installation_task.clone()),
-                    PrettierInstallation::Installed(default_prettier) => {
-                        ControlFlow::Break(default_prettier.clone())
-                    }
-                }
-            })?;
-            match installation_task {
-                ControlFlow::Continue(None) => {
-                    anyhow::bail!("Default prettier is not installed and cannot be started")
-                }
-                ControlFlow::Continue(Some(installation_task)) => {
-                    log::info!("Waiting for default prettier to install");
-                    if let Err(e) = installation_task.await {
-                        project.update(&mut cx, |project, _| {
-                            if let PrettierInstallation::NotInstalled {
-                                installation_task,
-                                attempts,
-                                ..
-                            } = &mut project.default_prettier.prettier
-                            {
-                                *installation_task = None;
-                                *attempts += 1;
-                            }
-                        })?;
-                        anyhow::bail!(
-                            "Cannot start default prettier due to its installation failure: {e:#}"
-                        );
-                    }
-                    let new_default_prettier = project.update(&mut cx, |project, cx| {
-                        let new_default_prettier =
-                            start_prettier(node, DEFAULT_PRETTIER_DIR.clone(), worktree_id, cx);
-                        project.default_prettier.prettier =
-                            PrettierInstallation::Installed(PrettierInstance {
-                                attempt: 0,
-                                prettier: Some(new_default_prettier.clone()),
-                            });
-                        new_default_prettier
-                    })?;
-                    return Ok(new_default_prettier);
-                }
-                ControlFlow::Break(instance) => match instance.prettier {
-                    Some(instance) => return Ok(instance),
-                    None => {
-                        let new_default_prettier = project.update(&mut cx, |project, cx| {
-                            let new_default_prettier =
-                                start_prettier(node, DEFAULT_PRETTIER_DIR.clone(), worktree_id, cx);
-                            project.default_prettier.prettier =
-                                PrettierInstallation::Installed(PrettierInstance {
-                                    attempt: instance.attempt + 1,
-                                    prettier: Some(new_default_prettier.clone()),
-                                });
-                            new_default_prettier
-                        })?;
-                        return Ok(new_default_prettier);
-                    }
-                },
-            }
-        }
-    })
-}
-
-fn start_prettier(
-    node: Arc<dyn NodeRuntime>,
-    prettier_dir: PathBuf,
-    worktree_id: Option<WorktreeId>,
-    cx: &mut ModelContext<'_, Project>,
-) -> PrettierTask {
-    cx.spawn(|project, mut cx| async move {
-        log::info!("Starting prettier at path {prettier_dir:?}");
-        let new_server_id = project.update(&mut cx, |project, _| {
-            project.languages.next_language_server_id()
-        })?;
-
-        let new_prettier = Prettier::start(new_server_id, prettier_dir, node, cx.clone())
-            .await
-            .context("default prettier spawn")
-            .map(Arc::new)
-            .map_err(Arc::new)?;
-        register_new_prettier(&project, &new_prettier, worktree_id, new_server_id, &mut cx);
-        Ok(new_prettier)
-    })
-    .shared()
-}
-
-fn register_new_prettier(
-    project: &WeakModel<Project>,
-    prettier: &Prettier,
-    worktree_id: Option<WorktreeId>,
-    new_server_id: LanguageServerId,
-    cx: &mut AsyncAppContext,
-) {
-    let prettier_dir = prettier.prettier_dir();
-    let is_default = prettier.is_default();
-    if is_default {
-        log::info!("Started default prettier in {prettier_dir:?}");
-    } else {
-        log::info!("Started prettier in {prettier_dir:?}");
-    }
-    if let Some(prettier_server) = prettier.server() {
-        project
-            .update(cx, |project, cx| {
-                let name = if is_default {
-                    LanguageServerName(Arc::from("prettier (default)"))
-                } else {
-                    let worktree_path = worktree_id
-                        .and_then(|id| project.worktree_for_id(id, cx))
-                        .map(|worktree| worktree.update(cx, |worktree, _| worktree.abs_path()));
-                    let name = match worktree_path {
-                        Some(worktree_path) => {
-                            if prettier_dir == worktree_path.as_ref() {
-                                let name = prettier_dir
-                                    .file_name()
-                                    .and_then(|name| name.to_str())
-                                    .unwrap_or_default();
-                                format!("prettier ({name})")
-                            } else {
-                                let dir_to_display = prettier_dir
-                                    .strip_prefix(worktree_path.as_ref())
-                                    .ok()
-                                    .unwrap_or(prettier_dir);
-                                format!("prettier ({})", dir_to_display.display())
-                            }
-                        }
-                        None => format!("prettier ({})", prettier_dir.display()),
-                    };
-                    LanguageServerName(Arc::from(name))
-                };
-                project
-                    .supplementary_language_servers
-                    .insert(new_server_id, (name, Arc::clone(prettier_server)));
-                cx.emit(Event::LanguageServerAdded(new_server_id));
-            })
-            .ok();
-    }
-}
-
-async fn install_prettier_packages(
-    plugins_to_install: HashSet<&'static str>,
-    node: Arc<dyn NodeRuntime>,
-) -> anyhow::Result<()> {
-    let packages_to_versions =
-        future::try_join_all(plugins_to_install.iter().chain(Some(&"prettier")).map(
-            |package_name| async {
-                let returned_package_name = package_name.to_string();
-                let latest_version = node
-                    .npm_package_latest_version(package_name)
-                    .await
-                    .with_context(|| {
-                        format!("fetching latest npm version for package {returned_package_name}")
-                    })?;
-                anyhow::Ok((returned_package_name, latest_version))
-            },
-        ))
-        .await
-        .context("fetching latest npm versions")?;
-
-    log::info!("Fetching default prettier and plugins: {packages_to_versions:?}");
-    let borrowed_packages = packages_to_versions
-        .iter()
-        .map(|(package, version)| (package.as_str(), version.as_str()))
-        .collect::<Vec<_>>();
-    node.npm_install_packages(DEFAULT_PRETTIER_DIR.as_path(), &borrowed_packages)
-        .await
-        .context("fetching formatter packages")?;
-    anyhow::Ok(())
-}
-
-async fn save_prettier_server_file(fs: &dyn Fs) -> Result<(), anyhow::Error> {
-    let prettier_wrapper_path = DEFAULT_PRETTIER_DIR.join(prettier::PRETTIER_SERVER_FILE);
-    fs.save(
-        &prettier_wrapper_path,
-        &text::Rope::from(prettier::PRETTIER_SERVER_JS),
-        text::LineEnding::Unix,
-    )
-    .await
-    .with_context(|| {
-        format!(
-            "writing {} file at {prettier_wrapper_path:?}",
-            prettier::PRETTIER_SERVER_FILE
-        )
-    })?;
-    Ok(())
-}
-
-impl Project {
-    pub fn update_prettier_settings(
-        &self,
-        worktree: &Model<Worktree>,
-        changes: &[(Arc<Path>, ProjectEntryId, PathChange)],
-        cx: &mut ModelContext<'_, Project>,
-    ) {
-        let prettier_config_files = Prettier::CONFIG_FILE_NAMES
-            .iter()
-            .map(Path::new)
-            .collect::<HashSet<_>>();
-
-        let prettier_config_file_changed = changes
-            .iter()
-            .filter(|(_, _, change)| !matches!(change, PathChange::Loaded))
-            .filter(|(path, _, _)| {
-                !path
-                    .components()
-                    .any(|component| component.as_os_str().to_string_lossy() == "node_modules")
-            })
-            .find(|(path, _, _)| prettier_config_files.contains(path.as_ref()));
-        let current_worktree_id = worktree.read(cx).id();
-        if let Some((config_path, _, _)) = prettier_config_file_changed {
-            log::info!(
-                "Prettier config file {config_path:?} changed, reloading prettier instances for worktree {current_worktree_id}"
-            );
-            let prettiers_to_reload =
-                self.prettiers_per_worktree
-                    .get(&current_worktree_id)
-                    .iter()
-                    .flat_map(|prettier_paths| prettier_paths.iter())
-                    .flatten()
-                    .filter_map(|prettier_path| {
-                        Some((
-                            current_worktree_id,
-                            Some(prettier_path.clone()),
-                            self.prettier_instances.get(prettier_path)?.clone(),
-                        ))
-                    })
-                    .chain(self.default_prettier.instance().map(|default_prettier| {
-                        (current_worktree_id, None, default_prettier.clone())
-                    }))
-                    .collect::<Vec<_>>();
-
-            cx.background_executor()
-                .spawn(async move {
-                    let _: Vec<()> = future::join_all(prettiers_to_reload.into_iter().map(|(worktree_id, prettier_path, prettier_instance)| {
-                        async move {
-                            if let Some(instance) = prettier_instance.prettier {
-                                match instance.await {
-                                    Ok(prettier) => {
-                                        prettier.clear_cache().log_err().await;
-                                    },
-                                    Err(e) => {
-                                        match prettier_path {
-                                            Some(prettier_path) => log::error!(
-                                                "Failed to clear prettier {prettier_path:?} cache for worktree {worktree_id:?} on prettier settings update: {e:#}"
-                                            ),
-                                            None => log::error!(
-                                                "Failed to clear default prettier cache for worktree {worktree_id:?} on prettier settings update: {e:#}"
-                                            ),
-                                        }
-                                    },
-                                }
-                            }
-                        }
-                    }))
-                    .await;
-                })
-                .detach();
-        }
-    }
-
-    fn prettier_instance_for_buffer(
-        &mut self,
-        buffer: &Model<Buffer>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Option<(Option<PathBuf>, PrettierTask)>> {
-        let buffer = buffer.read(cx);
-        let buffer_file = buffer.file();
-        let Some(buffer_language) = buffer.language() else {
-            return Task::ready(None);
-        };
-        if buffer_language.prettier_parser_name().is_none() {
-            return Task::ready(None);
-        }
-
-        if self.is_local() {
-            let Some(node) = self.node.as_ref().map(Arc::clone) else {
-                return Task::ready(None);
-            };
-            match File::from_dyn(buffer_file).map(|file| (file.worktree_id(cx), file.abs_path(cx)))
-            {
-                Some((worktree_id, buffer_path)) => {
-                    let fs = Arc::clone(&self.fs);
-                    let installed_prettiers = self.prettier_instances.keys().cloned().collect();
-                    return cx.spawn(|project, mut cx| async move {
-                        match cx
-                            .background_executor()
-                            .spawn(async move {
-                                Prettier::locate_prettier_installation(
-                                    fs.as_ref(),
-                                    &installed_prettiers,
-                                    &buffer_path,
-                                )
-                                .await
-                            })
-                            .await
-                        {
-                            Ok(ControlFlow::Break(())) => {
-                                return None;
-                            }
-                            Ok(ControlFlow::Continue(None)) => {
-                                let default_instance = project
-                                    .update(&mut cx, |project, cx| {
-                                        project
-                                            .prettiers_per_worktree
-                                            .entry(worktree_id)
-                                            .or_default()
-                                            .insert(None);
-                                        project.default_prettier.prettier_task(
-                                            &node,
-                                            Some(worktree_id),
-                                            cx,
-                                        )
-                                    })
-                                    .ok()?;
-                                Some((None, default_instance?.log_err().await?))
-                            }
-                            Ok(ControlFlow::Continue(Some(prettier_dir))) => {
-                                project
-                                    .update(&mut cx, |project, _| {
-                                        project
-                                            .prettiers_per_worktree
-                                            .entry(worktree_id)
-                                            .or_default()
-                                            .insert(Some(prettier_dir.clone()))
-                                    })
-                                    .ok()?;
-                                if let Some(prettier_task) = project
-                                    .update(&mut cx, |project, cx| {
-                                        project.prettier_instances.get_mut(&prettier_dir).map(
-                                            |existing_instance| {
-                                                existing_instance.prettier_task(
-                                                    &node,
-                                                    Some(&prettier_dir),
-                                                    Some(worktree_id),
-                                                    cx,
-                                                )
-                                            },
-                                        )
-                                    })
-                                    .ok()?
-                                {
-                                    log::debug!(
-                                        "Found already started prettier in {prettier_dir:?}"
-                                    );
-                                    return Some((
-                                        Some(prettier_dir),
-                                        prettier_task?.await.log_err()?,
-                                    ));
-                                }
-
-                                log::info!("Found prettier in {prettier_dir:?}, starting.");
-                                let new_prettier_task = project
-                                    .update(&mut cx, |project, cx| {
-                                        let new_prettier_task = start_prettier(
-                                            node,
-                                            prettier_dir.clone(),
-                                            Some(worktree_id),
-                                            cx,
-                                        );
-                                        project.prettier_instances.insert(
-                                            prettier_dir.clone(),
-                                            PrettierInstance {
-                                                attempt: 0,
-                                                prettier: Some(new_prettier_task.clone()),
-                                            },
-                                        );
-                                        new_prettier_task
-                                    })
-                                    .ok()?;
-                                Some((Some(prettier_dir), new_prettier_task))
-                            }
-                            Err(e) => {
-                                log::error!("Failed to determine prettier path for buffer: {e:#}");
-                                return None;
-                            }
-                        }
-                    });
-                }
-                None => {
-                    let new_task = self.default_prettier.prettier_task(&node, None, cx);
-                    return cx
-                        .spawn(|_, _| async move { Some((None, new_task?.log_err().await?)) });
-                }
-            }
-        } else {
-            return Task::ready(None);
-        }
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn install_default_prettier(
-        &mut self,
-        _worktree: Option<WorktreeId>,
-        plugins: HashSet<&'static str>,
-        _cx: &mut ModelContext<Self>,
-    ) {
-        // suppress unused code warnings
-        let _ = install_prettier_packages;
-        let _ = save_prettier_server_file;
-
-        self.default_prettier.installed_plugins.extend(plugins);
-        self.default_prettier.prettier = PrettierInstallation::Installed(PrettierInstance {
-            attempt: 0,
-            prettier: None,
-        });
-    }
-
-    #[cfg(not(any(test, feature = "test-support")))]
-    pub fn install_default_prettier(
-        &mut self,
-        worktree: Option<WorktreeId>,
-        mut new_plugins: HashSet<&'static str>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        let Some(node) = self.node.as_ref().cloned() else {
-            return;
-        };
-        log::info!("Initializing default prettier with plugins {new_plugins:?}");
-        let fs = Arc::clone(&self.fs);
-        let locate_prettier_installation = match worktree.and_then(|worktree_id| {
-            self.worktree_for_id(worktree_id, cx)
-                .map(|worktree| worktree.read(cx).abs_path())
-        }) {
-            Some(locate_from) => {
-                let installed_prettiers = self.prettier_instances.keys().cloned().collect();
-                cx.background_executor().spawn(async move {
-                    Prettier::locate_prettier_installation(
-                        fs.as_ref(),
-                        &installed_prettiers,
-                        locate_from.as_ref(),
-                    )
-                    .await
-                })
-            }
-            None => Task::ready(Ok(ControlFlow::Continue(None))),
-        };
-        new_plugins.retain(|plugin| !self.default_prettier.installed_plugins.contains(plugin));
-        let mut installation_attempt = 0;
-        let previous_installation_task = match &mut self.default_prettier.prettier {
-            PrettierInstallation::NotInstalled {
-                installation_task,
-                attempts,
-                not_installed_plugins,
-            } => {
-                installation_attempt = *attempts;
-                if installation_attempt > prettier::FAIL_THRESHOLD {
-                    *installation_task = None;
-                    log::warn!(
-                        "Default prettier installation had failed {installation_attempt} times, not attempting again",
-                    );
-                    return;
-                }
-                new_plugins.extend(not_installed_plugins.iter());
-                installation_task.clone()
-            }
-            PrettierInstallation::Installed { .. } => {
-                if new_plugins.is_empty() {
-                    return;
-                }
-                None
-            }
-        };
-
-        let plugins_to_install = new_plugins.clone();
-        let fs = Arc::clone(&self.fs);
-        let new_installation_task = cx
-            .spawn(|project, mut cx| async move {
-                match locate_prettier_installation
-                    .await
-                    .context("locate prettier installation")
-                    .map_err(Arc::new)?
-                {
-                    ControlFlow::Break(()) => return Ok(()),
-                    ControlFlow::Continue(prettier_path) => {
-                        if prettier_path.is_some() {
-                            new_plugins.clear();
-                        }
-                        let mut needs_install = false;
-                        if let Some(previous_installation_task) = previous_installation_task {
-                            if let Err(e) = previous_installation_task.await {
-                                log::error!("Failed to install default prettier: {e:#}");
-                                project.update(&mut cx, |project, _| {
-                                    if let PrettierInstallation::NotInstalled { attempts, not_installed_plugins, .. } = &mut project.default_prettier.prettier {
-                                        *attempts += 1;
-                                        new_plugins.extend(not_installed_plugins.iter());
-                                        installation_attempt = *attempts;
-                                        needs_install = true;
-                                    };
-                                })?;
-                            }
-                        };
-                        if installation_attempt > prettier::FAIL_THRESHOLD {
-                            project.update(&mut cx, |project, _| {
-                                if let PrettierInstallation::NotInstalled { installation_task, .. } = &mut project.default_prettier.prettier {
-                                    *installation_task = None;
-                                };
-                            })?;
-                            log::warn!(
-                                "Default prettier installation had failed {installation_attempt} times, not attempting again",
-                            );
-                            return Ok(());
-                        }
-                        project.update(&mut cx, |project, _| {
-                            new_plugins.retain(|plugin| {
-                                !project.default_prettier.installed_plugins.contains(plugin)
-                            });
-                            if let PrettierInstallation::NotInstalled { not_installed_plugins, .. } = &mut project.default_prettier.prettier {
-                                not_installed_plugins.retain(|plugin| {
-                                    !project.default_prettier.installed_plugins.contains(plugin)
-                                });
-                                not_installed_plugins.extend(new_plugins.iter());
-                            }
-                            needs_install |= !new_plugins.is_empty();
-                        })?;
-                        if needs_install {
-                            let installed_plugins = new_plugins.clone();
-                            cx.background_executor()
-                                .spawn(async move {
-                                    save_prettier_server_file(fs.as_ref()).await?;
-                                    install_prettier_packages(new_plugins, node).await
-                                })
-                                .await
-                                .context("prettier & plugins install")
-                                .map_err(Arc::new)?;
-                            log::info!("Initialized prettier with plugins: {installed_plugins:?}");
-                            project.update(&mut cx, |project, _| {
-                                project.default_prettier.prettier =
-                                    PrettierInstallation::Installed(PrettierInstance {
-                                        attempt: 0,
-                                        prettier: None,
-                                    });
-                                project.default_prettier
-                                    .installed_plugins
-                                    .extend(installed_plugins);
-                            })?;
-                        }
-                    }
-                }
-                Ok(())
-            })
-            .shared();
-        self.default_prettier.prettier = PrettierInstallation::NotInstalled {
-            attempts: installation_attempt,
-            installation_task: Some(new_installation_task),
-            not_installed_plugins: plugins_to_install,
-        };
-    }
-}

crates/project2/src/project2.rs 🔗

@@ -1,8737 +0,0 @@
-mod ignore;
-pub mod lsp_command;
-pub mod lsp_ext_command;
-mod prettier_support;
-pub mod project_settings;
-pub mod search;
-pub mod terminals;
-pub mod worktree;
-
-#[cfg(test)]
-mod project_tests;
-#[cfg(test)]
-mod worktree_tests;
-
-use anyhow::{anyhow, Context as _, Result};
-use client::{proto, Client, Collaborator, TypedEnvelope, UserStore};
-use clock::ReplicaId;
-use collections::{hash_map, BTreeMap, HashMap, HashSet, VecDeque};
-use copilot::Copilot;
-use futures::{
-    channel::{
-        mpsc::{self, UnboundedReceiver},
-        oneshot,
-    },
-    future::{try_join_all, Shared},
-    stream::FuturesUnordered,
-    AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt,
-};
-use globset::{Glob, GlobSet, GlobSetBuilder};
-use gpui::{
-    AnyModel, AppContext, AsyncAppContext, BackgroundExecutor, Context, Entity, EventEmitter,
-    Model, ModelContext, Task, WeakModel,
-};
-use itertools::Itertools;
-use language::{
-    language_settings::{language_settings, FormatOnSave, Formatter, InlayHintKind},
-    point_to_lsp,
-    proto::{
-        deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version,
-        serialize_anchor, serialize_version, split_operations,
-    },
-    range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeAction,
-    CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Event as BufferEvent,
-    File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, LspAdapterDelegate,
-    OffsetRangeExt, Operation, Patch, PendingLanguageServer, PointUtf16, TextBufferSnapshot,
-    ToOffset, ToPointUtf16, Transaction, Unclipped,
-};
-use log::error;
-use lsp::{
-    DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions,
-    DocumentHighlightKind, LanguageServer, LanguageServerBinary, LanguageServerId, OneOf,
-};
-use lsp_command::*;
-use node_runtime::NodeRuntime;
-use parking_lot::Mutex;
-use postage::watch;
-use prettier_support::{DefaultPrettier, PrettierInstance};
-use project_settings::{LspSettings, ProjectSettings};
-use rand::prelude::*;
-use search::SearchQuery;
-use serde::Serialize;
-use settings::{Settings, SettingsStore};
-use sha2::{Digest, Sha256};
-use similar::{ChangeTag, TextDiff};
-use smol::channel::{Receiver, Sender};
-use smol::lock::Semaphore;
-use std::{
-    cmp::{self, Ordering},
-    convert::TryInto,
-    hash::Hash,
-    mem,
-    num::NonZeroU32,
-    ops::Range,
-    path::{self, Component, Path, PathBuf},
-    process::Stdio,
-    str,
-    sync::{
-        atomic::{AtomicUsize, Ordering::SeqCst},
-        Arc,
-    },
-    time::{Duration, Instant},
-};
-use terminals::Terminals;
-use text::Anchor;
-use util::{
-    debug_panic, defer, http::HttpClient, merge_json_value_into,
-    paths::LOCAL_SETTINGS_RELATIVE_PATH, post_inc, ResultExt, TryFutureExt as _,
-};
-
-pub use fs::*;
-#[cfg(any(test, feature = "test-support"))]
-pub use prettier::FORMAT_SUFFIX as TEST_PRETTIER_FORMAT_SUFFIX;
-pub use worktree::*;
-
-const MAX_SERVER_REINSTALL_ATTEMPT_COUNT: u64 = 4;
-
-pub trait Item {
-    fn entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId>;
-    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
-}
-
-// Language server state is stored across 3 collections:
-//     language_servers =>
-//         a mapping from unique server id to LanguageServerState which can either be a task for a
-//         server in the process of starting, or a running server with adapter and language server arcs
-//     language_server_ids => a mapping from worktreeId and server name to the unique server id
-//     language_server_statuses => a mapping from unique server id to the current server status
-//
-// Multiple worktrees can map to the same language server for example when you jump to the definition
-// of a file in the standard library. So language_server_ids is used to look up which server is active
-// for a given worktree and language server name
-//
-// When starting a language server, first the id map is checked to make sure a server isn't already available
-// for that worktree. If there is one, it finishes early. Otherwise, a new id is allocated and and
-// the Starting variant of LanguageServerState is stored in the language_servers map.
-pub struct Project {
-    worktrees: Vec<WorktreeHandle>,
-    active_entry: Option<ProjectEntryId>,
-    buffer_ordered_messages_tx: mpsc::UnboundedSender<BufferOrderedMessage>,
-    languages: Arc<LanguageRegistry>,
-    supplementary_language_servers:
-        HashMap<LanguageServerId, (LanguageServerName, Arc<LanguageServer>)>,
-    language_servers: HashMap<LanguageServerId, LanguageServerState>,
-    language_server_ids: HashMap<(WorktreeId, LanguageServerName), LanguageServerId>,
-    language_server_statuses: BTreeMap<LanguageServerId, LanguageServerStatus>,
-    last_workspace_edits_by_language_server: HashMap<LanguageServerId, ProjectTransaction>,
-    client: Arc<client::Client>,
-    next_entry_id: Arc<AtomicUsize>,
-    join_project_response_message_id: u32,
-    next_diagnostic_group_id: usize,
-    user_store: Model<UserStore>,
-    fs: Arc<dyn Fs>,
-    client_state: Option<ProjectClientState>,
-    collaborators: HashMap<proto::PeerId, Collaborator>,
-    client_subscriptions: Vec<client::Subscription>,
-    _subscriptions: Vec<gpui::Subscription>,
-    next_buffer_id: u64,
-    opened_buffer: (watch::Sender<()>, watch::Receiver<()>),
-    shared_buffers: HashMap<proto::PeerId, HashSet<u64>>,
-    #[allow(clippy::type_complexity)]
-    loading_buffers_by_path: HashMap<
-        ProjectPath,
-        postage::watch::Receiver<Option<Result<Model<Buffer>, Arc<anyhow::Error>>>>,
-    >,
-    #[allow(clippy::type_complexity)]
-    loading_local_worktrees:
-        HashMap<Arc<Path>, Shared<Task<Result<Model<Worktree>, Arc<anyhow::Error>>>>>,
-    opened_buffers: HashMap<u64, OpenBuffer>,
-    local_buffer_ids_by_path: HashMap<ProjectPath, u64>,
-    local_buffer_ids_by_entry_id: HashMap<ProjectEntryId, u64>,
-    /// A mapping from a buffer ID to None means that we've started waiting for an ID but haven't finished loading it.
-    /// Used for re-issuing buffer requests when peers temporarily disconnect
-    incomplete_remote_buffers: HashMap<u64, Option<Model<Buffer>>>,
-    buffer_snapshots: HashMap<u64, HashMap<LanguageServerId, Vec<LspBufferSnapshot>>>, // buffer_id -> server_id -> vec of snapshots
-    buffers_being_formatted: HashSet<u64>,
-    buffers_needing_diff: HashSet<WeakModel<Buffer>>,
-    git_diff_debouncer: DelayedDebounced,
-    nonce: u128,
-    _maintain_buffer_languages: Task<()>,
-    _maintain_workspace_config: Task<Result<()>>,
-    terminals: Terminals,
-    copilot_lsp_subscription: Option<gpui::Subscription>,
-    copilot_log_subscription: Option<lsp::Subscription>,
-    current_lsp_settings: HashMap<Arc<str>, LspSettings>,
-    node: Option<Arc<dyn NodeRuntime>>,
-    default_prettier: DefaultPrettier,
-    prettiers_per_worktree: HashMap<WorktreeId, HashSet<Option<PathBuf>>>,
-    prettier_instances: HashMap<PathBuf, PrettierInstance>,
-}
-
-struct DelayedDebounced {
-    task: Option<Task<()>>,
-    cancel_channel: Option<oneshot::Sender<()>>,
-}
-
-pub enum LanguageServerToQuery {
-    Primary,
-    Other(LanguageServerId),
-}
-
-impl DelayedDebounced {
-    fn new() -> DelayedDebounced {
-        DelayedDebounced {
-            task: None,
-            cancel_channel: None,
-        }
-    }
-
-    fn fire_new<F>(&mut self, delay: Duration, cx: &mut ModelContext<Project>, func: F)
-    where
-        F: 'static + Send + FnOnce(&mut Project, &mut ModelContext<Project>) -> Task<()>,
-    {
-        if let Some(channel) = self.cancel_channel.take() {
-            _ = channel.send(());
-        }
-
-        let (sender, mut receiver) = oneshot::channel::<()>();
-        self.cancel_channel = Some(sender);
-
-        let previous_task = self.task.take();
-        self.task = Some(cx.spawn(move |project, mut cx| async move {
-            let mut timer = cx.background_executor().timer(delay).fuse();
-            if let Some(previous_task) = previous_task {
-                previous_task.await;
-            }
-
-            futures::select_biased! {
-                _ = receiver => return,
-                    _ = timer => {}
-            }
-
-            if let Ok(task) = project.update(&mut cx, |project, cx| (func)(project, cx)) {
-                task.await;
-            }
-        }));
-    }
-}
-
-struct LspBufferSnapshot {
-    version: i32,
-    snapshot: TextBufferSnapshot,
-}
-
-/// Message ordered with respect to buffer operations
-enum BufferOrderedMessage {
-    Operation {
-        buffer_id: u64,
-        operation: proto::Operation,
-    },
-    LanguageServerUpdate {
-        language_server_id: LanguageServerId,
-        message: proto::update_language_server::Variant,
-    },
-    Resync,
-}
-
-enum LocalProjectUpdate {
-    WorktreesChanged,
-    CreateBufferForPeer {
-        peer_id: proto::PeerId,
-        buffer_id: u64,
-    },
-}
-
-enum OpenBuffer {
-    Strong(Model<Buffer>),
-    Weak(WeakModel<Buffer>),
-    Operations(Vec<Operation>),
-}
-
-#[derive(Clone)]
-enum WorktreeHandle {
-    Strong(Model<Worktree>),
-    Weak(WeakModel<Worktree>),
-}
-
-enum ProjectClientState {
-    Local {
-        remote_id: u64,
-        updates_tx: mpsc::UnboundedSender<LocalProjectUpdate>,
-        _send_updates: Task<Result<()>>,
-    },
-    Remote {
-        sharing_has_stopped: bool,
-        remote_id: u64,
-        replica_id: ReplicaId,
-    },
-}
-
-#[derive(Clone, Debug, PartialEq)]
-pub enum Event {
-    LanguageServerAdded(LanguageServerId),
-    LanguageServerRemoved(LanguageServerId),
-    LanguageServerLog(LanguageServerId, String),
-    Notification(String),
-    ActiveEntryChanged(Option<ProjectEntryId>),
-    ActivateProjectPanel,
-    WorktreeAdded,
-    WorktreeRemoved(WorktreeId),
-    WorktreeUpdatedEntries(WorktreeId, UpdatedEntriesSet),
-    DiskBasedDiagnosticsStarted {
-        language_server_id: LanguageServerId,
-    },
-    DiskBasedDiagnosticsFinished {
-        language_server_id: LanguageServerId,
-    },
-    DiagnosticsUpdated {
-        path: ProjectPath,
-        language_server_id: LanguageServerId,
-    },
-    RemoteIdChanged(Option<u64>),
-    DisconnectedFromHost,
-    Closed,
-    DeletedEntry(ProjectEntryId),
-    CollaboratorUpdated {
-        old_peer_id: proto::PeerId,
-        new_peer_id: proto::PeerId,
-    },
-    CollaboratorJoined(proto::PeerId),
-    CollaboratorLeft(proto::PeerId),
-    RefreshInlayHints,
-    RevealInProjectPanel(ProjectEntryId),
-}
-
-pub enum LanguageServerState {
-    Starting(Task<Option<Arc<LanguageServer>>>),
-
-    Running {
-        language: Arc<Language>,
-        adapter: Arc<CachedLspAdapter>,
-        server: Arc<LanguageServer>,
-        watched_paths: HashMap<WorktreeId, GlobSet>,
-        simulate_disk_based_diagnostics_completion: Option<Task<()>>,
-    },
-}
-
-#[derive(Serialize)]
-pub struct LanguageServerStatus {
-    pub name: String,
-    pub pending_work: BTreeMap<String, LanguageServerProgress>,
-    pub has_pending_diagnostic_updates: bool,
-    progress_tokens: HashSet<String>,
-}
-
-#[derive(Clone, Debug, Serialize)]
-pub struct LanguageServerProgress {
-    pub message: Option<String>,
-    pub percentage: Option<usize>,
-    #[serde(skip_serializing)]
-    pub last_update_at: Instant,
-}
-
-#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
-pub struct ProjectPath {
-    pub worktree_id: WorktreeId,
-    pub path: Arc<Path>,
-}
-
-#[derive(Copy, Clone, Debug, Default, PartialEq, Serialize)]
-pub struct DiagnosticSummary {
-    pub error_count: usize,
-    pub warning_count: usize,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct Location {
-    pub buffer: Model<Buffer>,
-    pub range: Range<language::Anchor>,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct InlayHint {
-    pub position: language::Anchor,
-    pub label: InlayHintLabel,
-    pub kind: Option<InlayHintKind>,
-    pub padding_left: bool,
-    pub padding_right: bool,
-    pub tooltip: Option<InlayHintTooltip>,
-    pub resolve_state: ResolveState,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum ResolveState {
-    Resolved,
-    CanResolve(LanguageServerId, Option<lsp::LSPAny>),
-    Resolving,
-}
-
-impl InlayHint {
-    pub fn text(&self) -> String {
-        match &self.label {
-            InlayHintLabel::String(s) => s.to_owned(),
-            InlayHintLabel::LabelParts(parts) => parts.iter().map(|part| &part.value).join(""),
-        }
-    }
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum InlayHintLabel {
-    String(String),
-    LabelParts(Vec<InlayHintLabelPart>),
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct InlayHintLabelPart {
-    pub value: String,
-    pub tooltip: Option<InlayHintLabelPartTooltip>,
-    pub location: Option<(LanguageServerId, lsp::Location)>,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum InlayHintTooltip {
-    String(String),
-    MarkupContent(MarkupContent),
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum InlayHintLabelPartTooltip {
-    String(String),
-    MarkupContent(MarkupContent),
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct MarkupContent {
-    pub kind: HoverBlockKind,
-    pub value: String,
-}
-
-#[derive(Debug, Clone)]
-pub struct LocationLink {
-    pub origin: Option<Location>,
-    pub target: Location,
-}
-
-#[derive(Debug)]
-pub struct DocumentHighlight {
-    pub range: Range<language::Anchor>,
-    pub kind: DocumentHighlightKind,
-}
-
-#[derive(Clone, Debug)]
-pub struct Symbol {
-    pub language_server_name: LanguageServerName,
-    pub source_worktree_id: WorktreeId,
-    pub path: ProjectPath,
-    pub label: CodeLabel,
-    pub name: String,
-    pub kind: lsp::SymbolKind,
-    pub range: Range<Unclipped<PointUtf16>>,
-    pub signature: [u8; 32],
-}
-
-#[derive(Clone, Debug, PartialEq)]
-pub struct HoverBlock {
-    pub text: String,
-    pub kind: HoverBlockKind,
-}
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub enum HoverBlockKind {
-    PlainText,
-    Markdown,
-    Code { language: String },
-}
-
-#[derive(Debug)]
-pub struct Hover {
-    pub contents: Vec<HoverBlock>,
-    pub range: Option<Range<language::Anchor>>,
-    pub language: Option<Arc<Language>>,
-}
-
-impl Hover {
-    pub fn is_empty(&self) -> bool {
-        self.contents.iter().all(|block| block.text.is_empty())
-    }
-}
-
-#[derive(Default)]
-pub struct ProjectTransaction(pub HashMap<Model<Buffer>, language::Transaction>);
-
-impl DiagnosticSummary {
-    fn new<'a, T: 'a>(diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<T>>) -> Self {
-        let mut this = Self {
-            error_count: 0,
-            warning_count: 0,
-        };
-
-        for entry in diagnostics {
-            if entry.diagnostic.is_primary {
-                match entry.diagnostic.severity {
-                    DiagnosticSeverity::ERROR => this.error_count += 1,
-                    DiagnosticSeverity::WARNING => this.warning_count += 1,
-                    _ => {}
-                }
-            }
-        }
-
-        this
-    }
-
-    pub fn is_empty(&self) -> bool {
-        self.error_count == 0 && self.warning_count == 0
-    }
-
-    pub fn to_proto(
-        &self,
-        language_server_id: LanguageServerId,
-        path: &Path,
-    ) -> proto::DiagnosticSummary {
-        proto::DiagnosticSummary {
-            path: path.to_string_lossy().to_string(),
-            language_server_id: language_server_id.0 as u64,
-            error_count: self.error_count as u32,
-            warning_count: self.warning_count as u32,
-        }
-    }
-}
-
-#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
-pub struct ProjectEntryId(usize);
-
-impl ProjectEntryId {
-    pub const MAX: Self = Self(usize::MAX);
-
-    pub fn new(counter: &AtomicUsize) -> Self {
-        Self(counter.fetch_add(1, SeqCst))
-    }
-
-    pub fn from_proto(id: u64) -> Self {
-        Self(id as usize)
-    }
-
-    pub fn to_proto(&self) -> u64 {
-        self.0 as u64
-    }
-
-    pub fn to_usize(&self) -> usize {
-        self.0
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum FormatTrigger {
-    Save,
-    Manual,
-}
-
-struct ProjectLspAdapterDelegate {
-    project: Model<Project>,
-    http_client: Arc<dyn HttpClient>,
-}
-
-// Currently, formatting operations are represented differently depending on
-// whether they come from a language server or an external command.
-enum FormatOperation {
-    Lsp(Vec<(Range<Anchor>, String)>),
-    External(Diff),
-    Prettier(Diff),
-}
-
-impl FormatTrigger {
-    fn from_proto(value: i32) -> FormatTrigger {
-        match value {
-            0 => FormatTrigger::Save,
-            1 => FormatTrigger::Manual,
-            _ => FormatTrigger::Save,
-        }
-    }
-}
-#[derive(Clone, Debug, PartialEq)]
-enum SearchMatchCandidate {
-    OpenBuffer {
-        buffer: Model<Buffer>,
-        // This might be an unnamed file without representation on filesystem
-        path: Option<Arc<Path>>,
-    },
-    Path {
-        worktree_id: WorktreeId,
-        is_ignored: bool,
-        path: Arc<Path>,
-    },
-}
-
-type SearchMatchCandidateIndex = usize;
-impl SearchMatchCandidate {
-    fn path(&self) -> Option<Arc<Path>> {
-        match self {
-            SearchMatchCandidate::OpenBuffer { path, .. } => path.clone(),
-            SearchMatchCandidate::Path { path, .. } => Some(path.clone()),
-        }
-    }
-}
-
-impl Project {
-    pub fn init_settings(cx: &mut AppContext) {
-        ProjectSettings::register(cx);
-    }
-
-    pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
-        Self::init_settings(cx);
-
-        client.add_model_message_handler(Self::handle_add_collaborator);
-        client.add_model_message_handler(Self::handle_update_project_collaborator);
-        client.add_model_message_handler(Self::handle_remove_collaborator);
-        client.add_model_message_handler(Self::handle_buffer_reloaded);
-        client.add_model_message_handler(Self::handle_buffer_saved);
-        client.add_model_message_handler(Self::handle_start_language_server);
-        client.add_model_message_handler(Self::handle_update_language_server);
-        client.add_model_message_handler(Self::handle_update_project);
-        client.add_model_message_handler(Self::handle_unshare_project);
-        client.add_model_message_handler(Self::handle_create_buffer_for_peer);
-        client.add_model_message_handler(Self::handle_update_buffer_file);
-        client.add_model_request_handler(Self::handle_update_buffer);
-        client.add_model_message_handler(Self::handle_update_diagnostic_summary);
-        client.add_model_message_handler(Self::handle_update_worktree);
-        client.add_model_message_handler(Self::handle_update_worktree_settings);
-        client.add_model_request_handler(Self::handle_create_project_entry);
-        client.add_model_request_handler(Self::handle_rename_project_entry);
-        client.add_model_request_handler(Self::handle_copy_project_entry);
-        client.add_model_request_handler(Self::handle_delete_project_entry);
-        client.add_model_request_handler(Self::handle_expand_project_entry);
-        client.add_model_request_handler(Self::handle_apply_additional_edits_for_completion);
-        client.add_model_request_handler(Self::handle_apply_code_action);
-        client.add_model_request_handler(Self::handle_on_type_formatting);
-        client.add_model_request_handler(Self::handle_inlay_hints);
-        client.add_model_request_handler(Self::handle_resolve_inlay_hint);
-        client.add_model_request_handler(Self::handle_refresh_inlay_hints);
-        client.add_model_request_handler(Self::handle_reload_buffers);
-        client.add_model_request_handler(Self::handle_synchronize_buffers);
-        client.add_model_request_handler(Self::handle_format_buffers);
-        client.add_model_request_handler(Self::handle_lsp_command::<GetCodeActions>);
-        client.add_model_request_handler(Self::handle_lsp_command::<GetCompletions>);
-        client.add_model_request_handler(Self::handle_lsp_command::<GetHover>);
-        client.add_model_request_handler(Self::handle_lsp_command::<GetDefinition>);
-        client.add_model_request_handler(Self::handle_lsp_command::<GetTypeDefinition>);
-        client.add_model_request_handler(Self::handle_lsp_command::<GetDocumentHighlights>);
-        client.add_model_request_handler(Self::handle_lsp_command::<GetReferences>);
-        client.add_model_request_handler(Self::handle_lsp_command::<PrepareRename>);
-        client.add_model_request_handler(Self::handle_lsp_command::<PerformRename>);
-        client.add_model_request_handler(Self::handle_search_project);
-        client.add_model_request_handler(Self::handle_get_project_symbols);
-        client.add_model_request_handler(Self::handle_open_buffer_for_symbol);
-        client.add_model_request_handler(Self::handle_open_buffer_by_id);
-        client.add_model_request_handler(Self::handle_open_buffer_by_path);
-        client.add_model_request_handler(Self::handle_save_buffer);
-        client.add_model_message_handler(Self::handle_update_diff_base);
-        client.add_model_request_handler(Self::handle_lsp_command::<lsp_ext_command::ExpandMacro>);
-    }
-
-    pub fn local(
-        client: Arc<Client>,
-        node: Arc<dyn NodeRuntime>,
-        user_store: Model<UserStore>,
-        languages: Arc<LanguageRegistry>,
-        fs: Arc<dyn Fs>,
-        cx: &mut AppContext,
-    ) -> Model<Self> {
-        cx.new_model(|cx: &mut ModelContext<Self>| {
-            let (tx, rx) = mpsc::unbounded();
-            cx.spawn(move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
-                .detach();
-            let copilot_lsp_subscription =
-                Copilot::global(cx).map(|copilot| subscribe_for_copilot_events(&copilot, cx));
-            Self {
-                worktrees: Default::default(),
-                buffer_ordered_messages_tx: tx,
-                collaborators: Default::default(),
-                next_buffer_id: 0,
-                opened_buffers: Default::default(),
-                shared_buffers: Default::default(),
-                incomplete_remote_buffers: Default::default(),
-                loading_buffers_by_path: Default::default(),
-                loading_local_worktrees: Default::default(),
-                local_buffer_ids_by_path: Default::default(),
-                local_buffer_ids_by_entry_id: Default::default(),
-                buffer_snapshots: Default::default(),
-                join_project_response_message_id: 0,
-                client_state: None,
-                opened_buffer: watch::channel(),
-                client_subscriptions: Vec::new(),
-                _subscriptions: vec![
-                    cx.observe_global::<SettingsStore>(Self::on_settings_changed),
-                    cx.on_release(Self::release),
-                    cx.on_app_quit(Self::shutdown_language_servers),
-                ],
-                _maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
-                _maintain_workspace_config: Self::maintain_workspace_config(cx),
-                active_entry: None,
-                languages,
-                client,
-                user_store,
-                fs,
-                next_entry_id: Default::default(),
-                next_diagnostic_group_id: Default::default(),
-                supplementary_language_servers: HashMap::default(),
-                language_servers: Default::default(),
-                language_server_ids: Default::default(),
-                language_server_statuses: Default::default(),
-                last_workspace_edits_by_language_server: Default::default(),
-                buffers_being_formatted: Default::default(),
-                buffers_needing_diff: Default::default(),
-                git_diff_debouncer: DelayedDebounced::new(),
-                nonce: StdRng::from_entropy().gen(),
-                terminals: Terminals {
-                    local_handles: Vec::new(),
-                },
-                copilot_lsp_subscription,
-                copilot_log_subscription: None,
-                current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
-                node: Some(node),
-                default_prettier: DefaultPrettier::default(),
-                prettiers_per_worktree: HashMap::default(),
-                prettier_instances: HashMap::default(),
-            }
-        })
-    }
-
-    pub async fn remote(
-        remote_id: u64,
-        client: Arc<Client>,
-        user_store: Model<UserStore>,
-        languages: Arc<LanguageRegistry>,
-        fs: Arc<dyn Fs>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Model<Self>> {
-        client.authenticate_and_connect(true, &cx).await?;
-
-        let subscription = client.subscribe_to_entity(remote_id)?;
-        let response = client
-            .request_envelope(proto::JoinProject {
-                project_id: remote_id,
-            })
-            .await?;
-        let this = cx.new_model(|cx| {
-            let replica_id = response.payload.replica_id as ReplicaId;
-
-            let mut worktrees = Vec::new();
-            for worktree in response.payload.worktrees {
-                let worktree =
-                    Worktree::remote(remote_id, replica_id, worktree, client.clone(), cx);
-                worktrees.push(worktree);
-            }
-
-            let (tx, rx) = mpsc::unbounded();
-            cx.spawn(move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
-                .detach();
-            let copilot_lsp_subscription =
-                Copilot::global(cx).map(|copilot| subscribe_for_copilot_events(&copilot, cx));
-            let mut this = Self {
-                worktrees: Vec::new(),
-                buffer_ordered_messages_tx: tx,
-                loading_buffers_by_path: Default::default(),
-                next_buffer_id: 0,
-                opened_buffer: watch::channel(),
-                shared_buffers: Default::default(),
-                incomplete_remote_buffers: Default::default(),
-                loading_local_worktrees: Default::default(),
-                local_buffer_ids_by_path: Default::default(),
-                local_buffer_ids_by_entry_id: Default::default(),
-                active_entry: None,
-                collaborators: Default::default(),
-                join_project_response_message_id: response.message_id,
-                _maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
-                _maintain_workspace_config: Self::maintain_workspace_config(cx),
-                languages,
-                user_store: user_store.clone(),
-                fs,
-                next_entry_id: Default::default(),
-                next_diagnostic_group_id: Default::default(),
-                client_subscriptions: Default::default(),
-                _subscriptions: vec![
-                    cx.on_release(Self::release),
-                    cx.on_app_quit(Self::shutdown_language_servers),
-                ],
-                client: client.clone(),
-                client_state: Some(ProjectClientState::Remote {
-                    sharing_has_stopped: false,
-                    remote_id,
-                    replica_id,
-                }),
-                supplementary_language_servers: HashMap::default(),
-                language_servers: Default::default(),
-                language_server_ids: Default::default(),
-                language_server_statuses: response
-                    .payload
-                    .language_servers
-                    .into_iter()
-                    .map(|server| {
-                        (
-                            LanguageServerId(server.id as usize),
-                            LanguageServerStatus {
-                                name: server.name,
-                                pending_work: Default::default(),
-                                has_pending_diagnostic_updates: false,
-                                progress_tokens: Default::default(),
-                            },
-                        )
-                    })
-                    .collect(),
-                last_workspace_edits_by_language_server: Default::default(),
-                opened_buffers: Default::default(),
-                buffers_being_formatted: Default::default(),
-                buffers_needing_diff: Default::default(),
-                git_diff_debouncer: DelayedDebounced::new(),
-                buffer_snapshots: Default::default(),
-                nonce: StdRng::from_entropy().gen(),
-                terminals: Terminals {
-                    local_handles: Vec::new(),
-                },
-                copilot_lsp_subscription,
-                copilot_log_subscription: None,
-                current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
-                node: None,
-                default_prettier: DefaultPrettier::default(),
-                prettiers_per_worktree: HashMap::default(),
-                prettier_instances: HashMap::default(),
-            };
-            for worktree in worktrees {
-                let _ = this.add_worktree(&worktree, cx);
-            }
-            this
-        })?;
-        let subscription = subscription.set_model(&this, &mut cx);
-
-        let user_ids = response
-            .payload
-            .collaborators
-            .iter()
-            .map(|peer| peer.user_id)
-            .collect();
-        user_store
-            .update(&mut cx, |user_store, cx| user_store.get_users(user_ids, cx))?
-            .await?;
-
-        this.update(&mut cx, |this, cx| {
-            this.set_collaborators_from_proto(response.payload.collaborators, cx)?;
-            this.client_subscriptions.push(subscription);
-            anyhow::Ok(())
-        })??;
-
-        Ok(this)
-    }
-
-    fn release(&mut self, cx: &mut AppContext) {
-        match &self.client_state {
-            Some(ProjectClientState::Local { .. }) => {
-                let _ = self.unshare_internal(cx);
-            }
-            Some(ProjectClientState::Remote { remote_id, .. }) => {
-                let _ = self.client.send(proto::LeaveProject {
-                    project_id: *remote_id,
-                });
-                self.disconnected_from_host_internal(cx);
-            }
-            _ => {}
-        }
-    }
-
-    fn shutdown_language_servers(
-        &mut self,
-        _cx: &mut ModelContext<Self>,
-    ) -> impl Future<Output = ()> {
-        let shutdown_futures = self
-            .language_servers
-            .drain()
-            .map(|(_, server_state)| async {
-                use LanguageServerState::*;
-                match server_state {
-                    Running { server, .. } => server.shutdown()?.await,
-                    Starting(task) => task.await?.shutdown()?.await,
-                }
-            })
-            .collect::<Vec<_>>();
-
-        async move {
-            futures::future::join_all(shutdown_futures).await;
-        }
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub async fn test(
-        fs: Arc<dyn Fs>,
-        root_paths: impl IntoIterator<Item = &Path>,
-        cx: &mut gpui::TestAppContext,
-    ) -> Model<Project> {
-        let mut languages = LanguageRegistry::test();
-        languages.set_executor(cx.executor());
-        let http_client = util::http::FakeHttpClient::with_404_response();
-        let client = cx.update(|cx| client::Client::new(http_client.clone(), cx));
-        let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
-        let project = cx.update(|cx| {
-            Project::local(
-                client,
-                node_runtime::FakeNodeRuntime::new(),
-                user_store,
-                Arc::new(languages),
-                fs,
-                cx,
-            )
-        });
-        for path in root_paths {
-            let (tree, _) = project
-                .update(cx, |project, cx| {
-                    project.find_or_create_local_worktree(path, true, cx)
-                })
-                .await
-                .unwrap();
-            tree.update(cx, |tree, _| tree.as_local().unwrap().scan_complete())
-                .await;
-        }
-        project
-    }
-
-    fn on_settings_changed(&mut self, cx: &mut ModelContext<Self>) {
-        let mut language_servers_to_start = Vec::new();
-        let mut language_formatters_to_check = Vec::new();
-        for buffer in self.opened_buffers.values() {
-            if let Some(buffer) = buffer.upgrade() {
-                let buffer = buffer.read(cx);
-                let buffer_file = File::from_dyn(buffer.file());
-                let buffer_language = buffer.language();
-                let settings = language_settings(buffer_language, buffer.file(), cx);
-                if let Some(language) = buffer_language {
-                    if settings.enable_language_server {
-                        if let Some(file) = buffer_file {
-                            language_servers_to_start
-                                .push((file.worktree.clone(), Arc::clone(language)));
-                        }
-                    }
-                    language_formatters_to_check.push((
-                        buffer_file.map(|f| f.worktree_id(cx)),
-                        Arc::clone(language),
-                        settings.clone(),
-                    ));
-                }
-            }
-        }
-
-        let mut language_servers_to_stop = Vec::new();
-        let mut language_servers_to_restart = Vec::new();
-        let languages = self.languages.to_vec();
-
-        let new_lsp_settings = ProjectSettings::get_global(cx).lsp.clone();
-        let current_lsp_settings = &self.current_lsp_settings;
-        for (worktree_id, started_lsp_name) in self.language_server_ids.keys() {
-            let language = languages.iter().find_map(|l| {
-                let adapter = l
-                    .lsp_adapters()
-                    .iter()
-                    .find(|adapter| &adapter.name == started_lsp_name)?;
-                Some((l, adapter))
-            });
-            if let Some((language, adapter)) = language {
-                let worktree = self.worktree_for_id(*worktree_id, cx);
-                let file = worktree.as_ref().and_then(|tree| {
-                    tree.update(cx, |tree, cx| tree.root_file(cx).map(|f| f as _))
-                });
-                if !language_settings(Some(language), file.as_ref(), cx).enable_language_server {
-                    language_servers_to_stop.push((*worktree_id, started_lsp_name.clone()));
-                } else if let Some(worktree) = worktree {
-                    let server_name = &adapter.name.0;
-                    match (
-                        current_lsp_settings.get(server_name),
-                        new_lsp_settings.get(server_name),
-                    ) {
-                        (None, None) => {}
-                        (Some(_), None) | (None, Some(_)) => {
-                            language_servers_to_restart.push((worktree, Arc::clone(language)));
-                        }
-                        (Some(current_lsp_settings), Some(new_lsp_settings)) => {
-                            if current_lsp_settings != new_lsp_settings {
-                                language_servers_to_restart.push((worktree, Arc::clone(language)));
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        self.current_lsp_settings = new_lsp_settings;
-
-        // Stop all newly-disabled language servers.
-        for (worktree_id, adapter_name) in language_servers_to_stop {
-            self.stop_language_server(worktree_id, adapter_name, cx)
-                .detach();
-        }
-
-        let mut prettier_plugins_by_worktree = HashMap::default();
-        for (worktree, language, settings) in language_formatters_to_check {
-            if let Some(plugins) =
-                prettier_support::prettier_plugins_for_language(&language, &settings)
-            {
-                prettier_plugins_by_worktree
-                    .entry(worktree)
-                    .or_insert_with(|| HashSet::default())
-                    .extend(plugins);
-            }
-        }
-        for (worktree, prettier_plugins) in prettier_plugins_by_worktree {
-            self.install_default_prettier(worktree, prettier_plugins, cx);
-        }
-
-        // Start all the newly-enabled language servers.
-        for (worktree, language) in language_servers_to_start {
-            let worktree_path = worktree.read(cx).abs_path();
-            self.start_language_servers(&worktree, worktree_path, language, cx);
-        }
-
-        // Restart all language servers with changed initialization options.
-        for (worktree, language) in language_servers_to_restart {
-            self.restart_language_servers(worktree, language, cx);
-        }
-
-        if self.copilot_lsp_subscription.is_none() {
-            if let Some(copilot) = Copilot::global(cx) {
-                for buffer in self.opened_buffers.values() {
-                    if let Some(buffer) = buffer.upgrade() {
-                        self.register_buffer_with_copilot(&buffer, cx);
-                    }
-                }
-                self.copilot_lsp_subscription = Some(subscribe_for_copilot_events(&copilot, cx));
-            }
-        }
-
-        cx.notify();
-    }
-
-    pub fn buffer_for_id(&self, remote_id: u64) -> Option<Model<Buffer>> {
-        self.opened_buffers
-            .get(&remote_id)
-            .and_then(|buffer| buffer.upgrade())
-    }
-
-    pub fn languages(&self) -> &Arc<LanguageRegistry> {
-        &self.languages
-    }
-
-    pub fn client(&self) -> Arc<Client> {
-        self.client.clone()
-    }
-
-    pub fn user_store(&self) -> Model<UserStore> {
-        self.user_store.clone()
-    }
-
-    pub fn opened_buffers(&self) -> Vec<Model<Buffer>> {
-        self.opened_buffers
-            .values()
-            .filter_map(|b| b.upgrade())
-            .collect()
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn has_open_buffer(&self, path: impl Into<ProjectPath>, cx: &AppContext) -> bool {
-        let path = path.into();
-        if let Some(worktree) = self.worktree_for_id(path.worktree_id, cx) {
-            self.opened_buffers.iter().any(|(_, buffer)| {
-                if let Some(buffer) = buffer.upgrade() {
-                    if let Some(file) = File::from_dyn(buffer.read(cx).file()) {
-                        if file.worktree == worktree && file.path() == &path.path {
-                            return true;
-                        }
-                    }
-                }
-                false
-            })
-        } else {
-            false
-        }
-    }
-
-    pub fn fs(&self) -> &Arc<dyn Fs> {
-        &self.fs
-    }
-
-    pub fn remote_id(&self) -> Option<u64> {
-        match self.client_state.as_ref()? {
-            ProjectClientState::Local { remote_id, .. }
-            | ProjectClientState::Remote { remote_id, .. } => Some(*remote_id),
-        }
-    }
-
-    pub fn replica_id(&self) -> ReplicaId {
-        match &self.client_state {
-            Some(ProjectClientState::Remote { replica_id, .. }) => *replica_id,
-            _ => 0,
-        }
-    }
-
-    fn metadata_changed(&mut self, cx: &mut ModelContext<Self>) {
-        if let Some(ProjectClientState::Local { updates_tx, .. }) = &mut self.client_state {
-            updates_tx
-                .unbounded_send(LocalProjectUpdate::WorktreesChanged)
-                .ok();
-        }
-        cx.notify();
-    }
-
-    pub fn collaborators(&self) -> &HashMap<proto::PeerId, Collaborator> {
-        &self.collaborators
-    }
-
-    pub fn host(&self) -> Option<&Collaborator> {
-        self.collaborators.values().find(|c| c.replica_id == 0)
-    }
-
-    /// Collect all worktrees, including ones that don't appear in the project panel
-    pub fn worktrees<'a>(&'a self) -> impl 'a + DoubleEndedIterator<Item = Model<Worktree>> {
-        self.worktrees
-            .iter()
-            .filter_map(move |worktree| worktree.upgrade())
-    }
-
-    /// Collect all user-visible worktrees, the ones that appear in the project panel
-    pub fn visible_worktrees<'a>(
-        &'a self,
-        cx: &'a AppContext,
-    ) -> impl 'a + DoubleEndedIterator<Item = Model<Worktree>> {
-        self.worktrees.iter().filter_map(|worktree| {
-            worktree.upgrade().and_then(|worktree| {
-                if worktree.read(cx).is_visible() {
-                    Some(worktree)
-                } else {
-                    None
-                }
-            })
-        })
-    }
-
-    pub fn worktree_root_names<'a>(&'a self, cx: &'a AppContext) -> impl Iterator<Item = &'a str> {
-        self.visible_worktrees(cx)
-            .map(|tree| tree.read(cx).root_name())
-    }
-
-    pub fn worktree_for_id(&self, id: WorktreeId, cx: &AppContext) -> Option<Model<Worktree>> {
-        self.worktrees()
-            .find(|worktree| worktree.read(cx).id() == id)
-    }
-
-    pub fn worktree_for_entry(
-        &self,
-        entry_id: ProjectEntryId,
-        cx: &AppContext,
-    ) -> Option<Model<Worktree>> {
-        self.worktrees()
-            .find(|worktree| worktree.read(cx).contains_entry(entry_id))
-    }
-
-    pub fn worktree_id_for_entry(
-        &self,
-        entry_id: ProjectEntryId,
-        cx: &AppContext,
-    ) -> Option<WorktreeId> {
-        self.worktree_for_entry(entry_id, cx)
-            .map(|worktree| worktree.read(cx).id())
-    }
-
-    pub fn contains_paths(&self, paths: &[PathBuf], cx: &AppContext) -> bool {
-        paths.iter().all(|path| self.contains_path(path, cx))
-    }
-
-    pub fn contains_path(&self, path: &Path, cx: &AppContext) -> bool {
-        for worktree in self.worktrees() {
-            let worktree = worktree.read(cx).as_local();
-            if worktree.map_or(false, |w| w.contains_abs_path(path)) {
-                return true;
-            }
-        }
-        false
-    }
-
-    pub fn create_entry(
-        &mut self,
-        project_path: impl Into<ProjectPath>,
-        is_directory: bool,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Option<Entry>>> {
-        let project_path = project_path.into();
-        let Some(worktree) = self.worktree_for_id(project_path.worktree_id, cx) else {
-            return Task::ready(Ok(None));
-        };
-        if self.is_local() {
-            worktree.update(cx, |worktree, cx| {
-                worktree
-                    .as_local_mut()
-                    .unwrap()
-                    .create_entry(project_path.path, is_directory, cx)
-            })
-        } else {
-            let client = self.client.clone();
-            let project_id = self.remote_id().unwrap();
-            cx.spawn(move |_, mut cx| async move {
-                let response = client
-                    .request(proto::CreateProjectEntry {
-                        worktree_id: project_path.worktree_id.to_proto(),
-                        project_id,
-                        path: project_path.path.to_string_lossy().into(),
-                        is_directory,
-                    })
-                    .await?;
-                match response.entry {
-                    Some(entry) => worktree
-                        .update(&mut cx, |worktree, cx| {
-                            worktree.as_remote_mut().unwrap().insert_entry(
-                                entry,
-                                response.worktree_scan_id as usize,
-                                cx,
-                            )
-                        })?
-                        .await
-                        .map(Some),
-                    None => Ok(None),
-                }
-            })
-        }
-    }
-
-    pub fn copy_entry(
-        &mut self,
-        entry_id: ProjectEntryId,
-        new_path: impl Into<Arc<Path>>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Option<Entry>>> {
-        let Some(worktree) = self.worktree_for_entry(entry_id, cx) else {
-            return Task::ready(Ok(None));
-        };
-        let new_path = new_path.into();
-        if self.is_local() {
-            worktree.update(cx, |worktree, cx| {
-                worktree
-                    .as_local_mut()
-                    .unwrap()
-                    .copy_entry(entry_id, new_path, cx)
-            })
-        } else {
-            let client = self.client.clone();
-            let project_id = self.remote_id().unwrap();
-
-            cx.spawn(move |_, mut cx| async move {
-                let response = client
-                    .request(proto::CopyProjectEntry {
-                        project_id,
-                        entry_id: entry_id.to_proto(),
-                        new_path: new_path.to_string_lossy().into(),
-                    })
-                    .await?;
-                match response.entry {
-                    Some(entry) => worktree
-                        .update(&mut cx, |worktree, cx| {
-                            worktree.as_remote_mut().unwrap().insert_entry(
-                                entry,
-                                response.worktree_scan_id as usize,
-                                cx,
-                            )
-                        })?
-                        .await
-                        .map(Some),
-                    None => Ok(None),
-                }
-            })
-        }
-    }
-
-    pub fn rename_entry(
-        &mut self,
-        entry_id: ProjectEntryId,
-        new_path: impl Into<Arc<Path>>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Option<Entry>>> {
-        let Some(worktree) = self.worktree_for_entry(entry_id, cx) else {
-            return Task::ready(Ok(None));
-        };
-        let new_path = new_path.into();
-        if self.is_local() {
-            worktree.update(cx, |worktree, cx| {
-                worktree
-                    .as_local_mut()
-                    .unwrap()
-                    .rename_entry(entry_id, new_path, cx)
-            })
-        } else {
-            let client = self.client.clone();
-            let project_id = self.remote_id().unwrap();
-
-            cx.spawn(move |_, mut cx| async move {
-                let response = client
-                    .request(proto::RenameProjectEntry {
-                        project_id,
-                        entry_id: entry_id.to_proto(),
-                        new_path: new_path.to_string_lossy().into(),
-                    })
-                    .await?;
-                match response.entry {
-                    Some(entry) => worktree
-                        .update(&mut cx, |worktree, cx| {
-                            worktree.as_remote_mut().unwrap().insert_entry(
-                                entry,
-                                response.worktree_scan_id as usize,
-                                cx,
-                            )
-                        })?
-                        .await
-                        .map(Some),
-                    None => Ok(None),
-                }
-            })
-        }
-    }
-
-    pub fn delete_entry(
-        &mut self,
-        entry_id: ProjectEntryId,
-        cx: &mut ModelContext<Self>,
-    ) -> Option<Task<Result<()>>> {
-        let worktree = self.worktree_for_entry(entry_id, cx)?;
-
-        cx.emit(Event::DeletedEntry(entry_id));
-
-        if self.is_local() {
-            worktree.update(cx, |worktree, cx| {
-                worktree.as_local_mut().unwrap().delete_entry(entry_id, cx)
-            })
-        } else {
-            let client = self.client.clone();
-            let project_id = self.remote_id().unwrap();
-            Some(cx.spawn(move |_, mut cx| async move {
-                let response = client
-                    .request(proto::DeleteProjectEntry {
-                        project_id,
-                        entry_id: entry_id.to_proto(),
-                    })
-                    .await?;
-                worktree
-                    .update(&mut cx, move |worktree, cx| {
-                        worktree.as_remote_mut().unwrap().delete_entry(
-                            entry_id,
-                            response.worktree_scan_id as usize,
-                            cx,
-                        )
-                    })?
-                    .await
-            }))
-        }
-    }
-
-    pub fn expand_entry(
-        &mut self,
-        worktree_id: WorktreeId,
-        entry_id: ProjectEntryId,
-        cx: &mut ModelContext<Self>,
-    ) -> Option<Task<Result<()>>> {
-        let worktree = self.worktree_for_id(worktree_id, cx)?;
-        if self.is_local() {
-            worktree.update(cx, |worktree, cx| {
-                worktree.as_local_mut().unwrap().expand_entry(entry_id, cx)
-            })
-        } else {
-            let worktree = worktree.downgrade();
-            let request = self.client.request(proto::ExpandProjectEntry {
-                project_id: self.remote_id().unwrap(),
-                entry_id: entry_id.to_proto(),
-            });
-            Some(cx.spawn(move |_, mut cx| async move {
-                let response = request.await?;
-                if let Some(worktree) = worktree.upgrade() {
-                    worktree
-                        .update(&mut cx, |worktree, _| {
-                            worktree
-                                .as_remote_mut()
-                                .unwrap()
-                                .wait_for_snapshot(response.worktree_scan_id as usize)
-                        })?
-                        .await?;
-                }
-                Ok(())
-            }))
-        }
-    }
-
-    pub fn shared(&mut self, project_id: u64, cx: &mut ModelContext<Self>) -> Result<()> {
-        if self.client_state.is_some() {
-            return Err(anyhow!("project was already shared"));
-        }
-        self.client_subscriptions.push(
-            self.client
-                .subscribe_to_entity(project_id)?
-                .set_model(&cx.handle(), &mut cx.to_async()),
-        );
-
-        for open_buffer in self.opened_buffers.values_mut() {
-            match open_buffer {
-                OpenBuffer::Strong(_) => {}
-                OpenBuffer::Weak(buffer) => {
-                    if let Some(buffer) = buffer.upgrade() {
-                        *open_buffer = OpenBuffer::Strong(buffer);
-                    }
-                }
-                OpenBuffer::Operations(_) => unreachable!(),
-            }
-        }
-
-        for worktree_handle in self.worktrees.iter_mut() {
-            match worktree_handle {
-                WorktreeHandle::Strong(_) => {}
-                WorktreeHandle::Weak(worktree) => {
-                    if let Some(worktree) = worktree.upgrade() {
-                        *worktree_handle = WorktreeHandle::Strong(worktree);
-                    }
-                }
-            }
-        }
-
-        for (server_id, status) in &self.language_server_statuses {
-            self.client
-                .send(proto::StartLanguageServer {
-                    project_id,
-                    server: Some(proto::LanguageServer {
-                        id: server_id.0 as u64,
-                        name: status.name.clone(),
-                    }),
-                })
-                .log_err();
-        }
-
-        let store = cx.global::<SettingsStore>();
-        for worktree in self.worktrees() {
-            let worktree_id = worktree.read(cx).id().to_proto();
-            for (path, content) in store.local_settings(worktree.entity_id().as_u64() as usize) {
-                self.client
-                    .send(proto::UpdateWorktreeSettings {
-                        project_id,
-                        worktree_id,
-                        path: path.to_string_lossy().into(),
-                        content: Some(content),
-                    })
-                    .log_err();
-            }
-        }
-
-        let (updates_tx, mut updates_rx) = mpsc::unbounded();
-        let client = self.client.clone();
-        self.client_state = Some(ProjectClientState::Local {
-            remote_id: project_id,
-            updates_tx,
-            _send_updates: cx.spawn(move |this, mut cx| async move {
-                while let Some(update) = updates_rx.next().await {
-                    match update {
-                        LocalProjectUpdate::WorktreesChanged => {
-                            let worktrees = this.update(&mut cx, |this, _cx| {
-                                this.worktrees().collect::<Vec<_>>()
-                            })?;
-                            let update_project = this
-                                .update(&mut cx, |this, cx| {
-                                    this.client.request(proto::UpdateProject {
-                                        project_id,
-                                        worktrees: this.worktree_metadata_protos(cx),
-                                    })
-                                })?
-                                .await;
-                            if update_project.is_ok() {
-                                for worktree in worktrees {
-                                    worktree.update(&mut cx, |worktree, cx| {
-                                        let worktree = worktree.as_local_mut().unwrap();
-                                        worktree.share(project_id, cx).detach_and_log_err(cx)
-                                    })?;
-                                }
-                            }
-                        }
-                        LocalProjectUpdate::CreateBufferForPeer { peer_id, buffer_id } => {
-                            let buffer = this.update(&mut cx, |this, _| {
-                                let buffer = this.opened_buffers.get(&buffer_id).unwrap();
-                                let shared_buffers =
-                                    this.shared_buffers.entry(peer_id).or_default();
-                                if shared_buffers.insert(buffer_id) {
-                                    if let OpenBuffer::Strong(buffer) = buffer {
-                                        Some(buffer.clone())
-                                    } else {
-                                        None
-                                    }
-                                } else {
-                                    None
-                                }
-                            })?;
-
-                            let Some(buffer) = buffer else { continue };
-                            let operations =
-                                buffer.update(&mut cx, |b, cx| b.serialize_ops(None, cx))?;
-                            let operations = operations.await;
-                            let state = buffer.update(&mut cx, |buffer, _| buffer.to_proto())?;
-
-                            let initial_state = proto::CreateBufferForPeer {
-                                project_id,
-                                peer_id: Some(peer_id),
-                                variant: Some(proto::create_buffer_for_peer::Variant::State(state)),
-                            };
-                            if client.send(initial_state).log_err().is_some() {
-                                let client = client.clone();
-                                cx.background_executor()
-                                    .spawn(async move {
-                                        let mut chunks = split_operations(operations).peekable();
-                                        while let Some(chunk) = chunks.next() {
-                                            let is_last = chunks.peek().is_none();
-                                            client.send(proto::CreateBufferForPeer {
-                                                project_id,
-                                                peer_id: Some(peer_id),
-                                                variant: Some(
-                                                    proto::create_buffer_for_peer::Variant::Chunk(
-                                                        proto::BufferChunk {
-                                                            buffer_id,
-                                                            operations: chunk,
-                                                            is_last,
-                                                        },
-                                                    ),
-                                                ),
-                                            })?;
-                                        }
-                                        anyhow::Ok(())
-                                    })
-                                    .await
-                                    .log_err();
-                            }
-                        }
-                    }
-                }
-                Ok(())
-            }),
-        });
-
-        self.metadata_changed(cx);
-        cx.emit(Event::RemoteIdChanged(Some(project_id)));
-        cx.notify();
-        Ok(())
-    }
-
-    pub fn reshared(
-        &mut self,
-        message: proto::ResharedProject,
-        cx: &mut ModelContext<Self>,
-    ) -> Result<()> {
-        self.shared_buffers.clear();
-        self.set_collaborators_from_proto(message.collaborators, cx)?;
-        self.metadata_changed(cx);
-        Ok(())
-    }
-
-    pub fn rejoined(
-        &mut self,
-        message: proto::RejoinedProject,
-        message_id: u32,
-        cx: &mut ModelContext<Self>,
-    ) -> Result<()> {
-        cx.update_global::<SettingsStore, _>(|store, cx| {
-            for worktree in &self.worktrees {
-                store
-                    .clear_local_settings(worktree.handle_id(), cx)
-                    .log_err();
-            }
-        });
-
-        self.join_project_response_message_id = message_id;
-        self.set_worktrees_from_proto(message.worktrees, cx)?;
-        self.set_collaborators_from_proto(message.collaborators, cx)?;
-        self.language_server_statuses = message
-            .language_servers
-            .into_iter()
-            .map(|server| {
-                (
-                    LanguageServerId(server.id as usize),
-                    LanguageServerStatus {
-                        name: server.name,
-                        pending_work: Default::default(),
-                        has_pending_diagnostic_updates: false,
-                        progress_tokens: Default::default(),
-                    },
-                )
-            })
-            .collect();
-        self.buffer_ordered_messages_tx
-            .unbounded_send(BufferOrderedMessage::Resync)
-            .unwrap();
-        cx.notify();
-        Ok(())
-    }
-
-    pub fn unshare(&mut self, cx: &mut ModelContext<Self>) -> Result<()> {
-        self.unshare_internal(cx)?;
-        self.metadata_changed(cx);
-        cx.notify();
-        Ok(())
-    }
-
-    fn unshare_internal(&mut self, cx: &mut AppContext) -> Result<()> {
-        if self.is_remote() {
-            return Err(anyhow!("attempted to unshare a remote project"));
-        }
-
-        if let Some(ProjectClientState::Local { remote_id, .. }) = self.client_state.take() {
-            self.collaborators.clear();
-            self.shared_buffers.clear();
-            self.client_subscriptions.clear();
-
-            for worktree_handle in self.worktrees.iter_mut() {
-                if let WorktreeHandle::Strong(worktree) = worktree_handle {
-                    let is_visible = worktree.update(cx, |worktree, _| {
-                        worktree.as_local_mut().unwrap().unshare();
-                        worktree.is_visible()
-                    });
-                    if !is_visible {
-                        *worktree_handle = WorktreeHandle::Weak(worktree.downgrade());
-                    }
-                }
-            }
-
-            for open_buffer in self.opened_buffers.values_mut() {
-                // Wake up any tasks waiting for peers' edits to this buffer.
-                if let Some(buffer) = open_buffer.upgrade() {
-                    buffer.update(cx, |buffer, _| buffer.give_up_waiting());
-                }
-
-                if let OpenBuffer::Strong(buffer) = open_buffer {
-                    *open_buffer = OpenBuffer::Weak(buffer.downgrade());
-                }
-            }
-
-            self.client.send(proto::UnshareProject {
-                project_id: remote_id,
-            })?;
-
-            Ok(())
-        } else {
-            Err(anyhow!("attempted to unshare an unshared project"))
-        }
-    }
-
-    pub fn disconnected_from_host(&mut self, cx: &mut ModelContext<Self>) {
-        self.disconnected_from_host_internal(cx);
-        cx.emit(Event::DisconnectedFromHost);
-        cx.notify();
-    }
-
-    fn disconnected_from_host_internal(&mut self, cx: &mut AppContext) {
-        if let Some(ProjectClientState::Remote {
-            sharing_has_stopped,
-            ..
-        }) = &mut self.client_state
-        {
-            *sharing_has_stopped = true;
-
-            self.collaborators.clear();
-
-            for worktree in &self.worktrees {
-                if let Some(worktree) = worktree.upgrade() {
-                    worktree.update(cx, |worktree, _| {
-                        if let Some(worktree) = worktree.as_remote_mut() {
-                            worktree.disconnected_from_host();
-                        }
-                    });
-                }
-            }
-
-            for open_buffer in self.opened_buffers.values_mut() {
-                // Wake up any tasks waiting for peers' edits to this buffer.
-                if let Some(buffer) = open_buffer.upgrade() {
-                    buffer.update(cx, |buffer, _| buffer.give_up_waiting());
-                }
-
-                if let OpenBuffer::Strong(buffer) = open_buffer {
-                    *open_buffer = OpenBuffer::Weak(buffer.downgrade());
-                }
-            }
-
-            // Wake up all futures currently waiting on a buffer to get opened,
-            // to give them a chance to fail now that we've disconnected.
-            *self.opened_buffer.0.borrow_mut() = ();
-        }
-    }
-
-    pub fn close(&mut self, cx: &mut ModelContext<Self>) {
-        cx.emit(Event::Closed);
-    }
-
-    pub fn is_read_only(&self) -> bool {
-        match &self.client_state {
-            Some(ProjectClientState::Remote {
-                sharing_has_stopped,
-                ..
-            }) => *sharing_has_stopped,
-            _ => false,
-        }
-    }
-
-    pub fn is_local(&self) -> bool {
-        match &self.client_state {
-            Some(ProjectClientState::Remote { .. }) => false,
-            _ => true,
-        }
-    }
-
-    pub fn is_remote(&self) -> bool {
-        !self.is_local()
-    }
-
-    pub fn create_buffer(
-        &mut self,
-        text: &str,
-        language: Option<Arc<Language>>,
-        cx: &mut ModelContext<Self>,
-    ) -> Result<Model<Buffer>> {
-        if self.is_remote() {
-            return Err(anyhow!("creating buffers as a guest is not supported yet"));
-        }
-        let id = post_inc(&mut self.next_buffer_id);
-        let buffer = cx.new_model(|cx| {
-            Buffer::new(self.replica_id(), id, text)
-                .with_language(language.unwrap_or_else(|| language::PLAIN_TEXT.clone()), cx)
-        });
-        self.register_buffer(&buffer, cx)?;
-        Ok(buffer)
-    }
-
-    pub fn open_path(
-        &mut self,
-        path: ProjectPath,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<(Option<ProjectEntryId>, AnyModel)>> {
-        let task = self.open_buffer(path.clone(), cx);
-        cx.spawn(move |_, cx| async move {
-            let buffer = task.await?;
-            let project_entry_id = buffer.read_with(&cx, |buffer, cx| {
-                File::from_dyn(buffer.file()).and_then(|file| file.project_entry_id(cx))
-            })?;
-
-            let buffer: &AnyModel = &buffer;
-            Ok((project_entry_id, buffer.clone()))
-        })
-    }
-
-    pub fn open_local_buffer(
-        &mut self,
-        abs_path: impl AsRef<Path>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Model<Buffer>>> {
-        if let Some((worktree, relative_path)) = self.find_local_worktree(abs_path.as_ref(), cx) {
-            self.open_buffer((worktree.read(cx).id(), relative_path), cx)
-        } else {
-            Task::ready(Err(anyhow!("no such path")))
-        }
-    }
-
-    pub fn open_buffer(
-        &mut self,
-        path: impl Into<ProjectPath>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Model<Buffer>>> {
-        let project_path = path.into();
-        let worktree = if let Some(worktree) = self.worktree_for_id(project_path.worktree_id, cx) {
-            worktree
-        } else {
-            return Task::ready(Err(anyhow!("no such worktree")));
-        };
-
-        // If there is already a buffer for the given path, then return it.
-        let existing_buffer = self.get_open_buffer(&project_path, cx);
-        if let Some(existing_buffer) = existing_buffer {
-            return Task::ready(Ok(existing_buffer));
-        }
-
-        let loading_watch = match self.loading_buffers_by_path.entry(project_path.clone()) {
-            // If the given path is already being loaded, then wait for that existing
-            // task to complete and return the same buffer.
-            hash_map::Entry::Occupied(e) => e.get().clone(),
-
-            // Otherwise, record the fact that this path is now being loaded.
-            hash_map::Entry::Vacant(entry) => {
-                let (mut tx, rx) = postage::watch::channel();
-                entry.insert(rx.clone());
-
-                let load_buffer = if worktree.read(cx).is_local() {
-                    self.open_local_buffer_internal(&project_path.path, &worktree, cx)
-                } else {
-                    self.open_remote_buffer_internal(&project_path.path, &worktree, cx)
-                };
-
-                let project_path = project_path.clone();
-                cx.spawn(move |this, mut cx| async move {
-                    let load_result = load_buffer.await;
-                    *tx.borrow_mut() = Some(this.update(&mut cx, |this, _| {
-                        // Record the fact that the buffer is no longer loading.
-                        this.loading_buffers_by_path.remove(&project_path);
-                        let buffer = load_result.map_err(Arc::new)?;
-                        Ok(buffer)
-                    })?);
-                    anyhow::Ok(())
-                })
-                .detach();
-                rx
-            }
-        };
-
-        cx.background_executor().spawn(async move {
-            wait_for_loading_buffer(loading_watch)
-                .await
-                .map_err(|error| anyhow!("{project_path:?} opening failure: {error:#}"))
-        })
-    }
-
-    fn open_local_buffer_internal(
-        &mut self,
-        path: &Arc<Path>,
-        worktree: &Model<Worktree>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Model<Buffer>>> {
-        let buffer_id = post_inc(&mut self.next_buffer_id);
-        let load_buffer = worktree.update(cx, |worktree, cx| {
-            let worktree = worktree.as_local_mut().unwrap();
-            worktree.load_buffer(buffer_id, path, cx)
-        });
-        cx.spawn(move |this, mut cx| async move {
-            let buffer = load_buffer.await?;
-            this.update(&mut cx, |this, cx| this.register_buffer(&buffer, cx))??;
-            Ok(buffer)
-        })
-    }
-
-    fn open_remote_buffer_internal(
-        &mut self,
-        path: &Arc<Path>,
-        worktree: &Model<Worktree>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Model<Buffer>>> {
-        let rpc = self.client.clone();
-        let project_id = self.remote_id().unwrap();
-        let remote_worktree_id = worktree.read(cx).id();
-        let path = path.clone();
-        let path_string = path.to_string_lossy().to_string();
-        cx.spawn(move |this, mut cx| async move {
-            let response = rpc
-                .request(proto::OpenBufferByPath {
-                    project_id,
-                    worktree_id: remote_worktree_id.to_proto(),
-                    path: path_string,
-                })
-                .await?;
-            this.update(&mut cx, |this, cx| {
-                this.wait_for_remote_buffer(response.buffer_id, cx)
-            })?
-            .await
-        })
-    }
-
-    /// LanguageServerName is owned, because it is inserted into a map
-    pub fn open_local_buffer_via_lsp(
-        &mut self,
-        abs_path: lsp::Url,
-        language_server_id: LanguageServerId,
-        language_server_name: LanguageServerName,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Model<Buffer>>> {
-        cx.spawn(move |this, mut cx| async move {
-            let abs_path = abs_path
-                .to_file_path()
-                .map_err(|_| anyhow!("can't convert URI to path"))?;
-            let (worktree, relative_path) = if let Some(result) =
-                this.update(&mut cx, |this, cx| this.find_local_worktree(&abs_path, cx))?
-            {
-                result
-            } else {
-                let worktree = this
-                    .update(&mut cx, |this, cx| {
-                        this.create_local_worktree(&abs_path, false, cx)
-                    })?
-                    .await?;
-                this.update(&mut cx, |this, cx| {
-                    this.language_server_ids.insert(
-                        (worktree.read(cx).id(), language_server_name),
-                        language_server_id,
-                    );
-                })
-                .ok();
-                (worktree, PathBuf::new())
-            };
-
-            let project_path = ProjectPath {
-                worktree_id: worktree.update(&mut cx, |worktree, _| worktree.id())?,
-                path: relative_path.into(),
-            };
-            this.update(&mut cx, |this, cx| this.open_buffer(project_path, cx))?
-                .await
-        })
-    }
-
-    pub fn open_buffer_by_id(
-        &mut self,
-        id: u64,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Model<Buffer>>> {
-        if let Some(buffer) = self.buffer_for_id(id) {
-            Task::ready(Ok(buffer))
-        } else if self.is_local() {
-            Task::ready(Err(anyhow!("buffer {} does not exist", id)))
-        } else if let Some(project_id) = self.remote_id() {
-            let request = self
-                .client
-                .request(proto::OpenBufferById { project_id, id });
-            cx.spawn(move |this, mut cx| async move {
-                let buffer_id = request.await?.buffer_id;
-                this.update(&mut cx, |this, cx| {
-                    this.wait_for_remote_buffer(buffer_id, cx)
-                })?
-                .await
-            })
-        } else {
-            Task::ready(Err(anyhow!("cannot open buffer while disconnected")))
-        }
-    }
-
-    pub fn save_buffers(
-        &self,
-        buffers: HashSet<Model<Buffer>>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        cx.spawn(move |this, mut cx| async move {
-            let save_tasks = buffers.into_iter().filter_map(|buffer| {
-                this.update(&mut cx, |this, cx| this.save_buffer(buffer, cx))
-                    .ok()
-            });
-            try_join_all(save_tasks).await?;
-            Ok(())
-        })
-    }
-
-    pub fn save_buffer(
-        &self,
-        buffer: Model<Buffer>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        let Some(file) = File::from_dyn(buffer.read(cx).file()) else {
-            return Task::ready(Err(anyhow!("buffer doesn't have a file")));
-        };
-        let worktree = file.worktree.clone();
-        let path = file.path.clone();
-        worktree.update(cx, |worktree, cx| match worktree {
-            Worktree::Local(worktree) => worktree.save_buffer(buffer, path, false, cx),
-            Worktree::Remote(worktree) => worktree.save_buffer(buffer, cx),
-        })
-    }
-
-    pub fn save_buffer_as(
-        &mut self,
-        buffer: Model<Buffer>,
-        abs_path: PathBuf,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        let worktree_task = self.find_or_create_local_worktree(&abs_path, true, cx);
-        let old_file = File::from_dyn(buffer.read(cx).file())
-            .filter(|f| f.is_local())
-            .cloned();
-        cx.spawn(move |this, mut cx| async move {
-            if let Some(old_file) = &old_file {
-                this.update(&mut cx, |this, cx| {
-                    this.unregister_buffer_from_language_servers(&buffer, old_file, cx);
-                })?;
-            }
-            let (worktree, path) = worktree_task.await?;
-            worktree
-                .update(&mut cx, |worktree, cx| match worktree {
-                    Worktree::Local(worktree) => {
-                        worktree.save_buffer(buffer.clone(), path.into(), true, cx)
-                    }
-                    Worktree::Remote(_) => panic!("cannot remote buffers as new files"),
-                })?
-                .await?;
-
-            this.update(&mut cx, |this, cx| {
-                this.detect_language_for_buffer(&buffer, cx);
-                this.register_buffer_with_language_servers(&buffer, cx);
-            })?;
-            Ok(())
-        })
-    }
-
-    pub fn get_open_buffer(
-        &mut self,
-        path: &ProjectPath,
-        cx: &mut ModelContext<Self>,
-    ) -> Option<Model<Buffer>> {
-        let worktree = self.worktree_for_id(path.worktree_id, cx)?;
-        self.opened_buffers.values().find_map(|buffer| {
-            let buffer = buffer.upgrade()?;
-            let file = File::from_dyn(buffer.read(cx).file())?;
-            if file.worktree == worktree && file.path() == &path.path {
-                Some(buffer)
-            } else {
-                None
-            }
-        })
-    }
-
-    fn register_buffer(
-        &mut self,
-        buffer: &Model<Buffer>,
-        cx: &mut ModelContext<Self>,
-    ) -> Result<()> {
-        self.request_buffer_diff_recalculation(buffer, cx);
-        buffer.update(cx, |buffer, _| {
-            buffer.set_language_registry(self.languages.clone())
-        });
-
-        let remote_id = buffer.read(cx).remote_id();
-        let is_remote = self.is_remote();
-        let open_buffer = if is_remote || self.is_shared() {
-            OpenBuffer::Strong(buffer.clone())
-        } else {
-            OpenBuffer::Weak(buffer.downgrade())
-        };
-
-        match self.opened_buffers.entry(remote_id) {
-            hash_map::Entry::Vacant(entry) => {
-                entry.insert(open_buffer);
-            }
-            hash_map::Entry::Occupied(mut entry) => {
-                if let OpenBuffer::Operations(operations) = entry.get_mut() {
-                    buffer.update(cx, |b, cx| b.apply_ops(operations.drain(..), cx))?;
-                } else if entry.get().upgrade().is_some() {
-                    if is_remote {
-                        return Ok(());
-                    } else {
-                        debug_panic!("buffer {} was already registered", remote_id);
-                        Err(anyhow!("buffer {} was already registered", remote_id))?;
-                    }
-                }
-                entry.insert(open_buffer);
-            }
-        }
-        cx.subscribe(buffer, |this, buffer, event, cx| {
-            this.on_buffer_event(buffer, event, cx);
-        })
-        .detach();
-
-        if let Some(file) = File::from_dyn(buffer.read(cx).file()) {
-            if file.is_local {
-                self.local_buffer_ids_by_path.insert(
-                    ProjectPath {
-                        worktree_id: file.worktree_id(cx),
-                        path: file.path.clone(),
-                    },
-                    remote_id,
-                );
-
-                if let Some(entry_id) = file.entry_id {
-                    self.local_buffer_ids_by_entry_id
-                        .insert(entry_id, remote_id);
-                }
-            }
-        }
-
-        self.detect_language_for_buffer(buffer, cx);
-        self.register_buffer_with_language_servers(buffer, cx);
-        self.register_buffer_with_copilot(buffer, cx);
-        cx.observe_release(buffer, |this, buffer, cx| {
-            if let Some(file) = File::from_dyn(buffer.file()) {
-                if file.is_local() {
-                    let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap();
-                    for server in this.language_servers_for_buffer(buffer, cx) {
-                        server
-                            .1
-                            .notify::<lsp::notification::DidCloseTextDocument>(
-                                lsp::DidCloseTextDocumentParams {
-                                    text_document: lsp::TextDocumentIdentifier::new(uri.clone()),
-                                },
-                            )
-                            .log_err();
-                    }
-                }
-            }
-        })
-        .detach();
-
-        *self.opened_buffer.0.borrow_mut() = ();
-        Ok(())
-    }
-
-    fn register_buffer_with_language_servers(
-        &mut self,
-        buffer_handle: &Model<Buffer>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        let buffer = buffer_handle.read(cx);
-        let buffer_id = buffer.remote_id();
-
-        if let Some(file) = File::from_dyn(buffer.file()) {
-            if !file.is_local() {
-                return;
-            }
-
-            let abs_path = file.abs_path(cx);
-            let uri = lsp::Url::from_file_path(&abs_path)
-                .unwrap_or_else(|()| panic!("Failed to register file {abs_path:?}"));
-            let initial_snapshot = buffer.text_snapshot();
-            let language = buffer.language().cloned();
-            let worktree_id = file.worktree_id(cx);
-
-            if let Some(local_worktree) = file.worktree.read(cx).as_local() {
-                for (server_id, diagnostics) in local_worktree.diagnostics_for_path(file.path()) {
-                    self.update_buffer_diagnostics(buffer_handle, server_id, None, diagnostics, cx)
-                        .log_err();
-                }
-            }
-
-            if let Some(language) = language {
-                for adapter in language.lsp_adapters() {
-                    let language_id = adapter.language_ids.get(language.name().as_ref()).cloned();
-                    let server = self
-                        .language_server_ids
-                        .get(&(worktree_id, adapter.name.clone()))
-                        .and_then(|id| self.language_servers.get(id))
-                        .and_then(|server_state| {
-                            if let LanguageServerState::Running { server, .. } = server_state {
-                                Some(server.clone())
-                            } else {
-                                None
-                            }
-                        });
-                    let server = match server {
-                        Some(server) => server,
-                        None => continue,
-                    };
-
-                    server
-                        .notify::<lsp::notification::DidOpenTextDocument>(
-                            lsp::DidOpenTextDocumentParams {
-                                text_document: lsp::TextDocumentItem::new(
-                                    uri.clone(),
-                                    language_id.unwrap_or_default(),
-                                    0,
-                                    initial_snapshot.text(),
-                                ),
-                            },
-                        )
-                        .log_err();
-
-                    buffer_handle.update(cx, |buffer, cx| {
-                        buffer.set_completion_triggers(
-                            server
-                                .capabilities()
-                                .completion_provider
-                                .as_ref()
-                                .and_then(|provider| provider.trigger_characters.clone())
-                                .unwrap_or_default(),
-                            cx,
-                        );
-                    });
-
-                    let snapshot = LspBufferSnapshot {
-                        version: 0,
-                        snapshot: initial_snapshot.clone(),
-                    };
-                    self.buffer_snapshots
-                        .entry(buffer_id)
-                        .or_default()
-                        .insert(server.server_id(), vec![snapshot]);
-                }
-            }
-        }
-    }
-
-    fn unregister_buffer_from_language_servers(
-        &mut self,
-        buffer: &Model<Buffer>,
-        old_file: &File,
-        cx: &mut ModelContext<Self>,
-    ) {
-        let old_path = match old_file.as_local() {
-            Some(local) => local.abs_path(cx),
-            None => return,
-        };
-
-        buffer.update(cx, |buffer, cx| {
-            let worktree_id = old_file.worktree_id(cx);
-            let ids = &self.language_server_ids;
-
-            let language = buffer.language().cloned();
-            let adapters = language.iter().flat_map(|language| language.lsp_adapters());
-            for &server_id in adapters.flat_map(|a| ids.get(&(worktree_id, a.name.clone()))) {
-                buffer.update_diagnostics(server_id, Default::default(), cx);
-            }
-
-            self.buffer_snapshots.remove(&buffer.remote_id());
-            let file_url = lsp::Url::from_file_path(old_path).unwrap();
-            for (_, language_server) in self.language_servers_for_buffer(buffer, cx) {
-                language_server
-                    .notify::<lsp::notification::DidCloseTextDocument>(
-                        lsp::DidCloseTextDocumentParams {
-                            text_document: lsp::TextDocumentIdentifier::new(file_url.clone()),
-                        },
-                    )
-                    .log_err();
-            }
-        });
-    }
-
-    fn register_buffer_with_copilot(
-        &self,
-        buffer_handle: &Model<Buffer>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        if let Some(copilot) = Copilot::global(cx) {
-            copilot.update(cx, |copilot, cx| copilot.register_buffer(buffer_handle, cx));
-        }
-    }
-
-    async fn send_buffer_ordered_messages(
-        this: WeakModel<Self>,
-        rx: UnboundedReceiver<BufferOrderedMessage>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        const MAX_BATCH_SIZE: usize = 128;
-
-        let mut operations_by_buffer_id = HashMap::default();
-        async fn flush_operations(
-            this: &WeakModel<Project>,
-            operations_by_buffer_id: &mut HashMap<u64, Vec<proto::Operation>>,
-            needs_resync_with_host: &mut bool,
-            is_local: bool,
-            cx: &mut AsyncAppContext,
-        ) -> Result<()> {
-            for (buffer_id, operations) in operations_by_buffer_id.drain() {
-                let request = this.update(cx, |this, _| {
-                    let project_id = this.remote_id()?;
-                    Some(this.client.request(proto::UpdateBuffer {
-                        buffer_id,
-                        project_id,
-                        operations,
-                    }))
-                })?;
-                if let Some(request) = request {
-                    if request.await.is_err() && !is_local {
-                        *needs_resync_with_host = true;
-                        break;
-                    }
-                }
-            }
-            Ok(())
-        }
-
-        let mut needs_resync_with_host = false;
-        let mut changes = rx.ready_chunks(MAX_BATCH_SIZE);
-
-        while let Some(changes) = changes.next().await {
-            let is_local = this.update(&mut cx, |this, _| this.is_local())?;
-
-            for change in changes {
-                match change {
-                    BufferOrderedMessage::Operation {
-                        buffer_id,
-                        operation,
-                    } => {
-                        if needs_resync_with_host {
-                            continue;
-                        }
-
-                        operations_by_buffer_id
-                            .entry(buffer_id)
-                            .or_insert(Vec::new())
-                            .push(operation);
-                    }
-
-                    BufferOrderedMessage::Resync => {
-                        operations_by_buffer_id.clear();
-                        if this
-                            .update(&mut cx, |this, cx| this.synchronize_remote_buffers(cx))?
-                            .await
-                            .is_ok()
-                        {
-                            needs_resync_with_host = false;
-                        }
-                    }
-
-                    BufferOrderedMessage::LanguageServerUpdate {
-                        language_server_id,
-                        message,
-                    } => {
-                        flush_operations(
-                            &this,
-                            &mut operations_by_buffer_id,
-                            &mut needs_resync_with_host,
-                            is_local,
-                            &mut cx,
-                        )
-                        .await?;
-
-                        this.update(&mut cx, |this, _| {
-                            if let Some(project_id) = this.remote_id() {
-                                this.client
-                                    .send(proto::UpdateLanguageServer {
-                                        project_id,
-                                        language_server_id: language_server_id.0 as u64,
-                                        variant: Some(message),
-                                    })
-                                    .log_err();
-                            }
-                        })?;
-                    }
-                }
-            }
-
-            flush_operations(
-                &this,
-                &mut operations_by_buffer_id,
-                &mut needs_resync_with_host,
-                is_local,
-                &mut cx,
-            )
-            .await?;
-        }
-
-        Ok(())
-    }
-
-    fn on_buffer_event(
-        &mut self,
-        buffer: Model<Buffer>,
-        event: &BufferEvent,
-        cx: &mut ModelContext<Self>,
-    ) -> Option<()> {
-        if matches!(
-            event,
-            BufferEvent::Edited { .. } | BufferEvent::Reloaded | BufferEvent::DiffBaseChanged
-        ) {
-            self.request_buffer_diff_recalculation(&buffer, cx);
-        }
-
-        match event {
-            BufferEvent::Operation(operation) => {
-                self.buffer_ordered_messages_tx
-                    .unbounded_send(BufferOrderedMessage::Operation {
-                        buffer_id: buffer.read(cx).remote_id(),
-                        operation: language::proto::serialize_operation(operation),
-                    })
-                    .ok();
-            }
-
-            BufferEvent::Edited { .. } => {
-                let buffer = buffer.read(cx);
-                let file = File::from_dyn(buffer.file())?;
-                let abs_path = file.as_local()?.abs_path(cx);
-                let uri = lsp::Url::from_file_path(abs_path).unwrap();
-                let next_snapshot = buffer.text_snapshot();
-
-                let language_servers: Vec<_> = self
-                    .language_servers_for_buffer(buffer, cx)
-                    .map(|i| i.1.clone())
-                    .collect();
-
-                for language_server in language_servers {
-                    let language_server = language_server.clone();
-
-                    let buffer_snapshots = self
-                        .buffer_snapshots
-                        .get_mut(&buffer.remote_id())
-                        .and_then(|m| m.get_mut(&language_server.server_id()))?;
-                    let previous_snapshot = buffer_snapshots.last()?;
-
-                    let build_incremental_change = || {
-                        buffer
-                            .edits_since::<(PointUtf16, usize)>(
-                                previous_snapshot.snapshot.version(),
-                            )
-                            .map(|edit| {
-                                let edit_start = edit.new.start.0;
-                                let edit_end = edit_start + (edit.old.end.0 - edit.old.start.0);
-                                let new_text = next_snapshot
-                                    .text_for_range(edit.new.start.1..edit.new.end.1)
-                                    .collect();
-                                lsp::TextDocumentContentChangeEvent {
-                                    range: Some(lsp::Range::new(
-                                        point_to_lsp(edit_start),
-                                        point_to_lsp(edit_end),
-                                    )),
-                                    range_length: None,
-                                    text: new_text,
-                                }
-                            })
-                            .collect()
-                    };
-
-                    let document_sync_kind = language_server
-                        .capabilities()
-                        .text_document_sync
-                        .as_ref()
-                        .and_then(|sync| match sync {
-                            lsp::TextDocumentSyncCapability::Kind(kind) => Some(*kind),
-                            lsp::TextDocumentSyncCapability::Options(options) => options.change,
-                        });
-
-                    let content_changes: Vec<_> = match document_sync_kind {
-                        Some(lsp::TextDocumentSyncKind::FULL) => {
-                            vec![lsp::TextDocumentContentChangeEvent {
-                                range: None,
-                                range_length: None,
-                                text: next_snapshot.text(),
-                            }]
-                        }
-                        Some(lsp::TextDocumentSyncKind::INCREMENTAL) => build_incremental_change(),
-                        _ => {
-                            #[cfg(any(test, feature = "test-support"))]
-                            {
-                                build_incremental_change()
-                            }
-
-                            #[cfg(not(any(test, feature = "test-support")))]
-                            {
-                                continue;
-                            }
-                        }
-                    };
-
-                    let next_version = previous_snapshot.version + 1;
-
-                    buffer_snapshots.push(LspBufferSnapshot {
-                        version: next_version,
-                        snapshot: next_snapshot.clone(),
-                    });
-
-                    language_server
-                        .notify::<lsp::notification::DidChangeTextDocument>(
-                            lsp::DidChangeTextDocumentParams {
-                                text_document: lsp::VersionedTextDocumentIdentifier::new(
-                                    uri.clone(),
-                                    next_version,
-                                ),
-                                content_changes,
-                            },
-                        )
-                        .log_err();
-                }
-            }
-
-            BufferEvent::Saved => {
-                let file = File::from_dyn(buffer.read(cx).file())?;
-                let worktree_id = file.worktree_id(cx);
-                let abs_path = file.as_local()?.abs_path(cx);
-                let text_document = lsp::TextDocumentIdentifier {
-                    uri: lsp::Url::from_file_path(abs_path).unwrap(),
-                };
-
-                for (_, _, server) in self.language_servers_for_worktree(worktree_id) {
-                    let text = include_text(server.as_ref()).then(|| buffer.read(cx).text());
-
-                    server
-                        .notify::<lsp::notification::DidSaveTextDocument>(
-                            lsp::DidSaveTextDocumentParams {
-                                text_document: text_document.clone(),
-                                text,
-                            },
-                        )
-                        .log_err();
-                }
-
-                let language_server_ids = self.language_server_ids_for_buffer(buffer.read(cx), cx);
-                for language_server_id in language_server_ids {
-                    if let Some(LanguageServerState::Running {
-                        adapter,
-                        simulate_disk_based_diagnostics_completion,
-                        ..
-                    }) = self.language_servers.get_mut(&language_server_id)
-                    {
-                        // After saving a buffer using a language server that doesn't provide
-                        // a disk-based progress token, kick off a timer that will reset every
-                        // time the buffer is saved. If the timer eventually fires, simulate
-                        // disk-based diagnostics being finished so that other pieces of UI
-                        // (e.g., project diagnostics view, diagnostic status bar) can update.
-                        // We don't emit an event right away because the language server might take
-                        // some time to publish diagnostics.
-                        if adapter.disk_based_diagnostics_progress_token.is_none() {
-                            const DISK_BASED_DIAGNOSTICS_DEBOUNCE: Duration =
-                                Duration::from_secs(1);
-
-                            let task = cx.spawn(move |this, mut cx| async move {
-                                cx.background_executor().timer(DISK_BASED_DIAGNOSTICS_DEBOUNCE).await;
-                                if let Some(this) = this.upgrade() {
-                                    this.update(&mut cx, |this, cx| {
-                                        this.disk_based_diagnostics_finished(
-                                            language_server_id,
-                                            cx,
-                                        );
-                                        this.buffer_ordered_messages_tx
-                                            .unbounded_send(
-                                                BufferOrderedMessage::LanguageServerUpdate {
-                                                    language_server_id,
-                                                    message:proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(Default::default())
-                                                },
-                                            )
-                                            .ok();
-                                    }).ok();
-                                }
-                            });
-                            *simulate_disk_based_diagnostics_completion = Some(task);
-                        }
-                    }
-                }
-            }
-            BufferEvent::FileHandleChanged => {
-                let Some(file) = File::from_dyn(buffer.read(cx).file()) else {
-                    return None;
-                };
-
-                let remote_id = buffer.read(cx).remote_id();
-                if let Some(entry_id) = file.entry_id {
-                    match self.local_buffer_ids_by_entry_id.get(&entry_id) {
-                        Some(_) => {
-                            return None;
-                        }
-                        None => {
-                            self.local_buffer_ids_by_entry_id
-                                .insert(entry_id, remote_id);
-                        }
-                    }
-                };
-                self.local_buffer_ids_by_path.insert(
-                    ProjectPath {
-                        worktree_id: file.worktree_id(cx),
-                        path: file.path.clone(),
-                    },
-                    remote_id,
-                );
-            }
-            _ => {}
-        }
-
-        None
-    }
-
-    fn request_buffer_diff_recalculation(
-        &mut self,
-        buffer: &Model<Buffer>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        self.buffers_needing_diff.insert(buffer.downgrade());
-        let first_insertion = self.buffers_needing_diff.len() == 1;
-
-        let settings = ProjectSettings::get_global(cx);
-        let delay = if let Some(delay) = settings.git.gutter_debounce {
-            delay
-        } else {
-            if first_insertion {
-                let this = cx.weak_model();
-                cx.defer(move |cx| {
-                    if let Some(this) = this.upgrade() {
-                        this.update(cx, |this, cx| {
-                            this.recalculate_buffer_diffs(cx).detach();
-                        });
-                    }
-                });
-            }
-            return;
-        };
-
-        const MIN_DELAY: u64 = 50;
-        let delay = delay.max(MIN_DELAY);
-        let duration = Duration::from_millis(delay);
-
-        self.git_diff_debouncer
-            .fire_new(duration, cx, move |this, cx| {
-                this.recalculate_buffer_diffs(cx)
-            });
-    }
-
-    fn recalculate_buffer_diffs(&mut self, cx: &mut ModelContext<Self>) -> Task<()> {
-        let buffers = self.buffers_needing_diff.drain().collect::<Vec<_>>();
-        cx.spawn(move |this, mut cx| async move {
-            let tasks: Vec<_> = buffers
-                .iter()
-                .filter_map(|buffer| {
-                    let buffer = buffer.upgrade()?;
-                    buffer
-                        .update(&mut cx, |buffer, cx| buffer.git_diff_recalc(cx))
-                        .ok()
-                        .flatten()
-                })
-                .collect();
-
-            futures::future::join_all(tasks).await;
-
-            this.update(&mut cx, |this, cx| {
-                if !this.buffers_needing_diff.is_empty() {
-                    this.recalculate_buffer_diffs(cx).detach();
-                } else {
-                    // TODO: Would a `ModelContext<Project>.notify()` suffice here?
-                    for buffer in buffers {
-                        if let Some(buffer) = buffer.upgrade() {
-                            buffer.update(cx, |_, cx| cx.notify());
-                        }
-                    }
-                }
-            })
-            .ok();
-        })
-    }
-
-    fn language_servers_for_worktree(
-        &self,
-        worktree_id: WorktreeId,
-    ) -> impl Iterator<Item = (&Arc<CachedLspAdapter>, &Arc<Language>, &Arc<LanguageServer>)> {
-        self.language_server_ids
-            .iter()
-            .filter_map(move |((language_server_worktree_id, _), id)| {
-                if *language_server_worktree_id == worktree_id {
-                    if let Some(LanguageServerState::Running {
-                        adapter,
-                        language,
-                        server,
-                        ..
-                    }) = self.language_servers.get(id)
-                    {
-                        return Some((adapter, language, server));
-                    }
-                }
-                None
-            })
-    }
-
-    fn maintain_buffer_languages(
-        languages: Arc<LanguageRegistry>,
-        cx: &mut ModelContext<Project>,
-    ) -> Task<()> {
-        let mut subscription = languages.subscribe();
-        let mut prev_reload_count = languages.reload_count();
-        cx.spawn(move |project, mut cx| async move {
-            while let Some(()) = subscription.next().await {
-                if let Some(project) = project.upgrade() {
-                    // If the language registry has been reloaded, then remove and
-                    // re-assign the languages on all open buffers.
-                    let reload_count = languages.reload_count();
-                    if reload_count > prev_reload_count {
-                        prev_reload_count = reload_count;
-                        project
-                            .update(&mut cx, |this, cx| {
-                                let buffers = this
-                                    .opened_buffers
-                                    .values()
-                                    .filter_map(|b| b.upgrade())
-                                    .collect::<Vec<_>>();
-                                for buffer in buffers {
-                                    if let Some(f) = File::from_dyn(buffer.read(cx).file()).cloned()
-                                    {
-                                        this.unregister_buffer_from_language_servers(
-                                            &buffer, &f, cx,
-                                        );
-                                        buffer
-                                            .update(cx, |buffer, cx| buffer.set_language(None, cx));
-                                    }
-                                }
-                            })
-                            .ok();
-                    }
-
-                    project
-                        .update(&mut cx, |project, cx| {
-                            let mut plain_text_buffers = Vec::new();
-                            let mut buffers_with_unknown_injections = Vec::new();
-                            for buffer in project.opened_buffers.values() {
-                                if let Some(handle) = buffer.upgrade() {
-                                    let buffer = &handle.read(cx);
-                                    if buffer.language().is_none()
-                                        || buffer.language() == Some(&*language::PLAIN_TEXT)
-                                    {
-                                        plain_text_buffers.push(handle);
-                                    } else if buffer.contains_unknown_injections() {
-                                        buffers_with_unknown_injections.push(handle);
-                                    }
-                                }
-                            }
-
-                            for buffer in plain_text_buffers {
-                                project.detect_language_for_buffer(&buffer, cx);
-                                project.register_buffer_with_language_servers(&buffer, cx);
-                            }
-
-                            for buffer in buffers_with_unknown_injections {
-                                buffer.update(cx, |buffer, cx| buffer.reparse(cx));
-                            }
-                        })
-                        .ok();
-                }
-            }
-        })
-    }
-
-    fn maintain_workspace_config(cx: &mut ModelContext<Project>) -> Task<Result<()>> {
-        let (mut settings_changed_tx, mut settings_changed_rx) = watch::channel();
-        let _ = postage::stream::Stream::try_recv(&mut settings_changed_rx);
-
-        let settings_observation = cx.observe_global::<SettingsStore>(move |_, _| {
-            *settings_changed_tx.borrow_mut() = ();
-        });
-
-        cx.spawn(move |this, mut cx| async move {
-            while let Some(_) = settings_changed_rx.next().await {
-                let servers: Vec<_> = this.update(&mut cx, |this, _| {
-                    this.language_servers
-                        .values()
-                        .filter_map(|state| match state {
-                            LanguageServerState::Starting(_) => None,
-                            LanguageServerState::Running {
-                                adapter, server, ..
-                            } => Some((adapter.clone(), server.clone())),
-                        })
-                        .collect()
-                })?;
-
-                for (adapter, server) in servers {
-                    let workspace_config = cx
-                        .update(|cx| adapter.workspace_configuration(server.root_path(), cx))?
-                        .await;
-                    server
-                        .notify::<lsp::notification::DidChangeConfiguration>(
-                            lsp::DidChangeConfigurationParams {
-                                settings: workspace_config.clone(),
-                            },
-                        )
-                        .ok();
-                }
-            }
-
-            drop(settings_observation);
-            anyhow::Ok(())
-        })
-    }
-
-    fn detect_language_for_buffer(
-        &mut self,
-        buffer_handle: &Model<Buffer>,
-        cx: &mut ModelContext<Self>,
-    ) -> Option<()> {
-        // If the buffer has a language, set it and start the language server if we haven't already.
-        let buffer = buffer_handle.read(cx);
-        let full_path = buffer.file()?.full_path(cx);
-        let content = buffer.as_rope();
-        let new_language = self
-            .languages
-            .language_for_file(&full_path, Some(content))
-            .now_or_never()?
-            .ok()?;
-        self.set_language_for_buffer(buffer_handle, new_language, cx);
-        None
-    }
-
-    pub fn set_language_for_buffer(
-        &mut self,
-        buffer: &Model<Buffer>,
-        new_language: Arc<Language>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        buffer.update(cx, |buffer, cx| {
-            if buffer.language().map_or(true, |old_language| {
-                !Arc::ptr_eq(old_language, &new_language)
-            }) {
-                buffer.set_language(Some(new_language.clone()), cx);
-            }
-        });
-
-        let buffer_file = buffer.read(cx).file().cloned();
-        let settings = language_settings(Some(&new_language), buffer_file.as_ref(), cx).clone();
-        let buffer_file = File::from_dyn(buffer_file.as_ref());
-        let worktree = buffer_file.as_ref().map(|f| f.worktree_id(cx));
-        if let Some(prettier_plugins) =
-            prettier_support::prettier_plugins_for_language(&new_language, &settings)
-        {
-            self.install_default_prettier(worktree, prettier_plugins, cx);
-        };
-        if let Some(file) = buffer_file {
-            let worktree = file.worktree.clone();
-            if let Some(tree) = worktree.read(cx).as_local() {
-                self.start_language_servers(&worktree, tree.abs_path().clone(), new_language, cx);
-            }
-        }
-    }
-
-    fn start_language_servers(
-        &mut self,
-        worktree: &Model<Worktree>,
-        worktree_path: Arc<Path>,
-        language: Arc<Language>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        let root_file = worktree.update(cx, |tree, cx| tree.root_file(cx));
-        let settings = language_settings(Some(&language), root_file.map(|f| f as _).as_ref(), cx);
-        if !settings.enable_language_server {
-            return;
-        }
-
-        let worktree_id = worktree.read(cx).id();
-        for adapter in language.lsp_adapters() {
-            self.start_language_server(
-                worktree_id,
-                worktree_path.clone(),
-                adapter.clone(),
-                language.clone(),
-                cx,
-            );
-        }
-    }
-
-    fn start_language_server(
-        &mut self,
-        worktree_id: WorktreeId,
-        worktree_path: Arc<Path>,
-        adapter: Arc<CachedLspAdapter>,
-        language: Arc<Language>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        if adapter.reinstall_attempt_count.load(SeqCst) > MAX_SERVER_REINSTALL_ATTEMPT_COUNT {
-            return;
-        }
-
-        let key = (worktree_id, adapter.name.clone());
-        if self.language_server_ids.contains_key(&key) {
-            return;
-        }
-
-        let stderr_capture = Arc::new(Mutex::new(Some(String::new())));
-        let pending_server = match self.languages.create_pending_language_server(
-            stderr_capture.clone(),
-            language.clone(),
-            adapter.clone(),
-            Arc::clone(&worktree_path),
-            ProjectLspAdapterDelegate::new(self, cx),
-            cx,
-        ) {
-            Some(pending_server) => pending_server,
-            None => return,
-        };
-
-        let project_settings = ProjectSettings::get_global(cx);
-        let lsp = project_settings.lsp.get(&adapter.name.0);
-        let override_options = lsp.map(|s| s.initialization_options.clone()).flatten();
-
-        let mut initialization_options = adapter.initialization_options.clone();
-        match (&mut initialization_options, override_options) {
-            (Some(initialization_options), Some(override_options)) => {
-                merge_json_value_into(override_options, initialization_options);
-            }
-            (None, override_options) => initialization_options = override_options,
-            _ => {}
-        }
-
-        let server_id = pending_server.server_id;
-        let container_dir = pending_server.container_dir.clone();
-        let state = LanguageServerState::Starting({
-            let adapter = adapter.clone();
-            let server_name = adapter.name.0.clone();
-            let language = language.clone();
-            let key = key.clone();
-
-            cx.spawn(move |this, mut cx| async move {
-                let result = Self::setup_and_insert_language_server(
-                    this.clone(),
-                    &worktree_path,
-                    initialization_options,
-                    pending_server,
-                    adapter.clone(),
-                    language.clone(),
-                    server_id,
-                    key,
-                    &mut cx,
-                )
-                .await;
-
-                match result {
-                    Ok(server) => {
-                        stderr_capture.lock().take();
-                        server
-                    }
-
-                    Err(err) => {
-                        log::error!("failed to start language server {server_name:?}: {err}");
-                        log::error!("server stderr: {:?}", stderr_capture.lock().take());
-
-                        let this = this.upgrade()?;
-                        let container_dir = container_dir?;
-
-                        let attempt_count = adapter.reinstall_attempt_count.fetch_add(1, SeqCst);
-                        if attempt_count >= MAX_SERVER_REINSTALL_ATTEMPT_COUNT {
-                            let max = MAX_SERVER_REINSTALL_ATTEMPT_COUNT;
-                            log::error!("Hit {max} reinstallation attempts for {server_name:?}");
-                            return None;
-                        }
-
-                        let installation_test_binary = adapter
-                            .installation_test_binary(container_dir.to_path_buf())
-                            .await;
-
-                        this.update(&mut cx, |_, cx| {
-                            Self::check_errored_server(
-                                language,
-                                adapter,
-                                server_id,
-                                installation_test_binary,
-                                cx,
-                            )
-                        })
-                        .ok();
-
-                        None
-                    }
-                }
-            })
-        });
-
-        self.language_servers.insert(server_id, state);
-        self.language_server_ids.insert(key, server_id);
-    }
-
-    fn reinstall_language_server(
-        &mut self,
-        language: Arc<Language>,
-        adapter: Arc<CachedLspAdapter>,
-        server_id: LanguageServerId,
-        cx: &mut ModelContext<Self>,
-    ) -> Option<Task<()>> {
-        log::info!("beginning to reinstall server");
-
-        let existing_server = match self.language_servers.remove(&server_id) {
-            Some(LanguageServerState::Running { server, .. }) => Some(server),
-            _ => None,
-        };
-
-        for worktree in &self.worktrees {
-            if let Some(worktree) = worktree.upgrade() {
-                let key = (worktree.read(cx).id(), adapter.name.clone());
-                self.language_server_ids.remove(&key);
-            }
-        }
-
-        Some(cx.spawn(move |this, mut cx| async move {
-            if let Some(task) = existing_server.and_then(|server| server.shutdown()) {
-                log::info!("shutting down existing server");
-                task.await;
-            }
-
-            // TODO: This is race-safe with regards to preventing new instances from
-            // starting while deleting, but existing instances in other projects are going
-            // to be very confused and messed up
-            let Some(task) = this
-                .update(&mut cx, |this, cx| {
-                    this.languages.delete_server_container(adapter.clone(), cx)
-                })
-                .log_err()
-            else {
-                return;
-            };
-            task.await;
-
-            this.update(&mut cx, |this, mut cx| {
-                let worktrees = this.worktrees.clone();
-                for worktree in worktrees {
-                    let worktree = match worktree.upgrade() {
-                        Some(worktree) => worktree.read(cx),
-                        None => continue,
-                    };
-                    let worktree_id = worktree.id();
-                    let root_path = worktree.abs_path();
-
-                    this.start_language_server(
-                        worktree_id,
-                        root_path,
-                        adapter.clone(),
-                        language.clone(),
-                        &mut cx,
-                    );
-                }
-            })
-            .ok();
-        }))
-    }
-
-    async fn setup_and_insert_language_server(
-        this: WeakModel<Self>,
-        worktree_path: &Path,
-        initialization_options: Option<serde_json::Value>,
-        pending_server: PendingLanguageServer,
-        adapter: Arc<CachedLspAdapter>,
-        language: Arc<Language>,
-        server_id: LanguageServerId,
-        key: (WorktreeId, LanguageServerName),
-        cx: &mut AsyncAppContext,
-    ) -> Result<Option<Arc<LanguageServer>>> {
-        let language_server = Self::setup_pending_language_server(
-            this.clone(),
-            initialization_options,
-            pending_server,
-            worktree_path,
-            adapter.clone(),
-            server_id,
-            cx,
-        )
-        .await?;
-
-        let this = match this.upgrade() {
-            Some(this) => this,
-            None => return Err(anyhow!("failed to upgrade project handle")),
-        };
-
-        this.update(cx, |this, cx| {
-            this.insert_newly_running_language_server(
-                language,
-                adapter,
-                language_server.clone(),
-                server_id,
-                key,
-                cx,
-            )
-        })??;
-
-        Ok(Some(language_server))
-    }
-
-    async fn setup_pending_language_server(
-        this: WeakModel<Self>,
-        initialization_options: Option<serde_json::Value>,
-        pending_server: PendingLanguageServer,
-        worktree_path: &Path,
-        adapter: Arc<CachedLspAdapter>,
-        server_id: LanguageServerId,
-        cx: &mut AsyncAppContext,
-    ) -> Result<Arc<LanguageServer>> {
-        let workspace_config = cx
-            .update(|cx| adapter.workspace_configuration(worktree_path, cx))?
-            .await;
-        let language_server = pending_server.task.await?;
-
-        language_server
-            .on_notification::<lsp::notification::PublishDiagnostics, _>({
-                let adapter = adapter.clone();
-                let this = this.clone();
-                move |mut params, mut cx| {
-                    let adapter = adapter.clone();
-                    if let Some(this) = this.upgrade() {
-                        adapter.process_diagnostics(&mut params);
-                        this.update(&mut cx, |this, cx| {
-                            this.update_diagnostics(
-                                server_id,
-                                params,
-                                &adapter.disk_based_diagnostic_sources,
-                                cx,
-                            )
-                            .log_err();
-                        })
-                        .ok();
-                    }
-                }
-            })
-            .detach();
-
-        language_server
-            .on_request::<lsp::request::WorkspaceConfiguration, _, _>({
-                let adapter = adapter.clone();
-                let worktree_path = worktree_path.to_path_buf();
-                move |params, cx| {
-                    let adapter = adapter.clone();
-                    let worktree_path = worktree_path.clone();
-                    async move {
-                        let workspace_config = cx
-                            .update(|cx| adapter.workspace_configuration(&worktree_path, cx))?
-                            .await;
-                        Ok(params
-                            .items
-                            .into_iter()
-                            .map(|item| {
-                                if let Some(section) = &item.section {
-                                    workspace_config
-                                        .get(section)
-                                        .cloned()
-                                        .unwrap_or(serde_json::Value::Null)
-                                } else {
-                                    workspace_config.clone()
-                                }
-                            })
-                            .collect())
-                    }
-                }
-            })
-            .detach();
-
-        // Even though we don't have handling for these requests, respond to them to
-        // avoid stalling any language server like `gopls` which waits for a response
-        // to these requests when initializing.
-        language_server
-            .on_request::<lsp::request::WorkDoneProgressCreate, _, _>({
-                let this = this.clone();
-                move |params, mut cx| {
-                    let this = this.clone();
-                    async move {
-                        this.update(&mut cx, |this, _| {
-                            if let Some(status) = this.language_server_statuses.get_mut(&server_id)
-                            {
-                                if let lsp::NumberOrString::String(token) = params.token {
-                                    status.progress_tokens.insert(token);
-                                }
-                            }
-                        })?;
-
-                        Ok(())
-                    }
-                }
-            })
-            .detach();
-
-        language_server
-            .on_request::<lsp::request::RegisterCapability, _, _>({
-                let this = this.clone();
-                move |params, mut cx| {
-                    let this = this.clone();
-                    async move {
-                        for reg in params.registrations {
-                            if reg.method == "workspace/didChangeWatchedFiles" {
-                                if let Some(options) = reg.register_options {
-                                    let options = serde_json::from_value(options)?;
-                                    this.update(&mut cx, |this, cx| {
-                                        this.on_lsp_did_change_watched_files(
-                                            server_id, options, cx,
-                                        );
-                                    })?;
-                                }
-                            }
-                        }
-                        Ok(())
-                    }
-                }
-            })
-            .detach();
-
-        language_server
-            .on_request::<lsp::request::ApplyWorkspaceEdit, _, _>({
-                let adapter = adapter.clone();
-                let this = this.clone();
-                move |params, cx| {
-                    Self::on_lsp_workspace_edit(
-                        this.clone(),
-                        params,
-                        server_id,
-                        adapter.clone(),
-                        cx,
-                    )
-                }
-            })
-            .detach();
-
-        language_server
-            .on_request::<lsp::request::InlayHintRefreshRequest, _, _>({
-                let this = this.clone();
-                move |(), mut cx| {
-                    let this = this.clone();
-                    async move {
-                        this.update(&mut cx, |project, cx| {
-                            cx.emit(Event::RefreshInlayHints);
-                            project.remote_id().map(|project_id| {
-                                project.client.send(proto::RefreshInlayHints { project_id })
-                            })
-                        })?
-                        .transpose()?;
-                        Ok(())
-                    }
-                }
-            })
-            .detach();
-
-        let disk_based_diagnostics_progress_token =
-            adapter.disk_based_diagnostics_progress_token.clone();
-
-        language_server
-            .on_notification::<lsp::notification::Progress, _>(move |params, mut cx| {
-                if let Some(this) = this.upgrade() {
-                    this.update(&mut cx, |this, cx| {
-                        this.on_lsp_progress(
-                            params,
-                            server_id,
-                            disk_based_diagnostics_progress_token.clone(),
-                            cx,
-                        );
-                    })
-                    .ok();
-                }
-            })
-            .detach();
-
-        let language_server = language_server.initialize(initialization_options).await?;
-
-        language_server
-            .notify::<lsp::notification::DidChangeConfiguration>(
-                lsp::DidChangeConfigurationParams {
-                    settings: workspace_config,
-                },
-            )
-            .ok();
-
-        Ok(language_server)
-    }
-
-    fn insert_newly_running_language_server(
-        &mut self,
-        language: Arc<Language>,
-        adapter: Arc<CachedLspAdapter>,
-        language_server: Arc<LanguageServer>,
-        server_id: LanguageServerId,
-        key: (WorktreeId, LanguageServerName),
-        cx: &mut ModelContext<Self>,
-    ) -> Result<()> {
-        // If the language server for this key doesn't match the server id, don't store the
-        // server. Which will cause it to be dropped, killing the process
-        if self
-            .language_server_ids
-            .get(&key)
-            .map(|id| id != &server_id)
-            .unwrap_or(false)
-        {
-            return Ok(());
-        }
-
-        // Update language_servers collection with Running variant of LanguageServerState
-        // indicating that the server is up and running and ready
-        self.language_servers.insert(
-            server_id,
-            LanguageServerState::Running {
-                adapter: adapter.clone(),
-                language: language.clone(),
-                watched_paths: Default::default(),
-                server: language_server.clone(),
-                simulate_disk_based_diagnostics_completion: None,
-            },
-        );
-
-        self.language_server_statuses.insert(
-            server_id,
-            LanguageServerStatus {
-                name: language_server.name().to_string(),
-                pending_work: Default::default(),
-                has_pending_diagnostic_updates: false,
-                progress_tokens: Default::default(),
-            },
-        );
-
-        cx.emit(Event::LanguageServerAdded(server_id));
-
-        if let Some(project_id) = self.remote_id() {
-            self.client.send(proto::StartLanguageServer {
-                project_id,
-                server: Some(proto::LanguageServer {
-                    id: server_id.0 as u64,
-                    name: language_server.name().to_string(),
-                }),
-            })?;
-        }
-
-        // Tell the language server about every open buffer in the worktree that matches the language.
-        for buffer in self.opened_buffers.values() {
-            if let Some(buffer_handle) = buffer.upgrade() {
-                let buffer = buffer_handle.read(cx);
-                let file = match File::from_dyn(buffer.file()) {
-                    Some(file) => file,
-                    None => continue,
-                };
-                let language = match buffer.language() {
-                    Some(language) => language,
-                    None => continue,
-                };
-
-                if file.worktree.read(cx).id() != key.0
-                    || !language.lsp_adapters().iter().any(|a| a.name == key.1)
-                {
-                    continue;
-                }
-
-                let file = match file.as_local() {
-                    Some(file) => file,
-                    None => continue,
-                };
-
-                let versions = self
-                    .buffer_snapshots
-                    .entry(buffer.remote_id())
-                    .or_default()
-                    .entry(server_id)
-                    .or_insert_with(|| {
-                        vec![LspBufferSnapshot {
-                            version: 0,
-                            snapshot: buffer.text_snapshot(),
-                        }]
-                    });
-
-                let snapshot = versions.last().unwrap();
-                let version = snapshot.version;
-                let initial_snapshot = &snapshot.snapshot;
-                let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap();
-                language_server.notify::<lsp::notification::DidOpenTextDocument>(
-                    lsp::DidOpenTextDocumentParams {
-                        text_document: lsp::TextDocumentItem::new(
-                            uri,
-                            adapter
-                                .language_ids
-                                .get(language.name().as_ref())
-                                .cloned()
-                                .unwrap_or_default(),
-                            version,
-                            initial_snapshot.text(),
-                        ),
-                    },
-                )?;
-
-                buffer_handle.update(cx, |buffer, cx| {
-                    buffer.set_completion_triggers(
-                        language_server
-                            .capabilities()
-                            .completion_provider
-                            .as_ref()
-                            .and_then(|provider| provider.trigger_characters.clone())
-                            .unwrap_or_default(),
-                        cx,
-                    )
-                });
-            }
-        }
-
-        cx.notify();
-        Ok(())
-    }
-
-    // Returns a list of all of the worktrees which no longer have a language server and the root path
-    // for the stopped server
-    fn stop_language_server(
-        &mut self,
-        worktree_id: WorktreeId,
-        adapter_name: LanguageServerName,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<(Option<PathBuf>, Vec<WorktreeId>)> {
-        let key = (worktree_id, adapter_name);
-        if let Some(server_id) = self.language_server_ids.remove(&key) {
-            log::info!("stopping language server {}", key.1 .0);
-
-            // Remove other entries for this language server as well
-            let mut orphaned_worktrees = vec![worktree_id];
-            let other_keys = self.language_server_ids.keys().cloned().collect::<Vec<_>>();
-            for other_key in other_keys {
-                if self.language_server_ids.get(&other_key) == Some(&server_id) {
-                    self.language_server_ids.remove(&other_key);
-                    orphaned_worktrees.push(other_key.0);
-                }
-            }
-
-            for buffer in self.opened_buffers.values() {
-                if let Some(buffer) = buffer.upgrade() {
-                    buffer.update(cx, |buffer, cx| {
-                        buffer.update_diagnostics(server_id, Default::default(), cx);
-                    });
-                }
-            }
-            for worktree in &self.worktrees {
-                if let Some(worktree) = worktree.upgrade() {
-                    worktree.update(cx, |worktree, cx| {
-                        if let Some(worktree) = worktree.as_local_mut() {
-                            worktree.clear_diagnostics_for_language_server(server_id, cx);
-                        }
-                    });
-                }
-            }
-
-            self.language_server_statuses.remove(&server_id);
-            cx.notify();
-
-            let server_state = self.language_servers.remove(&server_id);
-            cx.emit(Event::LanguageServerRemoved(server_id));
-            cx.spawn(move |this, mut cx| async move {
-                let mut root_path = None;
-
-                let server = match server_state {
-                    Some(LanguageServerState::Starting(task)) => task.await,
-                    Some(LanguageServerState::Running { server, .. }) => Some(server),
-                    None => None,
-                };
-
-                if let Some(server) = server {
-                    root_path = Some(server.root_path().clone());
-                    if let Some(shutdown) = server.shutdown() {
-                        shutdown.await;
-                    }
-                }
-
-                if let Some(this) = this.upgrade() {
-                    this.update(&mut cx, |this, cx| {
-                        this.language_server_statuses.remove(&server_id);
-                        cx.notify();
-                    })
-                    .ok();
-                }
-
-                (root_path, orphaned_worktrees)
-            })
-        } else {
-            Task::ready((None, Vec::new()))
-        }
-    }
-
-    pub fn restart_language_servers_for_buffers(
-        &mut self,
-        buffers: impl IntoIterator<Item = Model<Buffer>>,
-        cx: &mut ModelContext<Self>,
-    ) -> Option<()> {
-        let language_server_lookup_info: HashSet<(Model<Worktree>, Arc<Language>)> = buffers
-            .into_iter()
-            .filter_map(|buffer| {
-                let buffer = buffer.read(cx);
-                let file = File::from_dyn(buffer.file())?;
-                let full_path = file.full_path(cx);
-                let language = self
-                    .languages
-                    .language_for_file(&full_path, Some(buffer.as_rope()))
-                    .now_or_never()?
-                    .ok()?;
-                Some((file.worktree.clone(), language))
-            })
-            .collect();
-        for (worktree, language) in language_server_lookup_info {
-            self.restart_language_servers(worktree, language, cx);
-        }
-
-        None
-    }
-
-    // TODO This will break in the case where the adapter's root paths and worktrees are not equal
-    fn restart_language_servers(
-        &mut self,
-        worktree: Model<Worktree>,
-        language: Arc<Language>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        let worktree_id = worktree.read(cx).id();
-        let fallback_path = worktree.read(cx).abs_path();
-
-        let mut stops = Vec::new();
-        for adapter in language.lsp_adapters() {
-            stops.push(self.stop_language_server(worktree_id, adapter.name.clone(), cx));
-        }
-
-        if stops.is_empty() {
-            return;
-        }
-        let mut stops = stops.into_iter();
-
-        cx.spawn(move |this, mut cx| async move {
-            let (original_root_path, mut orphaned_worktrees) = stops.next().unwrap().await;
-            for stop in stops {
-                let (_, worktrees) = stop.await;
-                orphaned_worktrees.extend_from_slice(&worktrees);
-            }
-
-            let this = match this.upgrade() {
-                Some(this) => this,
-                None => return,
-            };
-
-            this.update(&mut cx, |this, cx| {
-                // Attempt to restart using original server path. Fallback to passed in
-                // path if we could not retrieve the root path
-                let root_path = original_root_path
-                    .map(|path_buf| Arc::from(path_buf.as_path()))
-                    .unwrap_or(fallback_path);
-
-                this.start_language_servers(&worktree, root_path, language.clone(), cx);
-
-                // Lookup new server ids and set them for each of the orphaned worktrees
-                for adapter in language.lsp_adapters() {
-                    if let Some(new_server_id) = this
-                        .language_server_ids
-                        .get(&(worktree_id, adapter.name.clone()))
-                        .cloned()
-                    {
-                        for &orphaned_worktree in &orphaned_worktrees {
-                            this.language_server_ids
-                                .insert((orphaned_worktree, adapter.name.clone()), new_server_id);
-                        }
-                    }
-                }
-            })
-            .ok();
-        })
-        .detach();
-    }
-
-    fn check_errored_server(
-        language: Arc<Language>,
-        adapter: Arc<CachedLspAdapter>,
-        server_id: LanguageServerId,
-        installation_test_binary: Option<LanguageServerBinary>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        if !adapter.can_be_reinstalled() {
-            log::info!(
-                "Validation check requested for {:?} but it cannot be reinstalled",
-                adapter.name.0
-            );
-            return;
-        }
-
-        cx.spawn(move |this, mut cx| async move {
-            log::info!("About to spawn test binary");
-
-            // A lack of test binary counts as a failure
-            let process = installation_test_binary.and_then(|binary| {
-                smol::process::Command::new(&binary.path)
-                    .current_dir(&binary.path)
-                    .args(binary.arguments)
-                    .stdin(Stdio::piped())
-                    .stdout(Stdio::piped())
-                    .stderr(Stdio::inherit())
-                    .kill_on_drop(true)
-                    .spawn()
-                    .ok()
-            });
-
-            const PROCESS_TIMEOUT: Duration = Duration::from_secs(5);
-            let mut timeout = cx.background_executor().timer(PROCESS_TIMEOUT).fuse();
-
-            let mut errored = false;
-            if let Some(mut process) = process {
-                futures::select! {
-                    status = process.status().fuse() => match status {
-                        Ok(status) => errored = !status.success(),
-                        Err(_) => errored = true,
-                    },
-
-                    _ = timeout => {
-                        log::info!("test binary time-ed out, this counts as a success");
-                        _ = process.kill();
-                    }
-                }
-            } else {
-                log::warn!("test binary failed to launch");
-                errored = true;
-            }
-
-            if errored {
-                log::warn!("test binary check failed");
-                let task = this
-                    .update(&mut cx, move |this, mut cx| {
-                        this.reinstall_language_server(language, adapter, server_id, &mut cx)
-                    })
-                    .ok()
-                    .flatten();
-
-                if let Some(task) = task {
-                    task.await;
-                }
-            }
-        })
-        .detach();
-    }
-
-    fn on_lsp_progress(
-        &mut self,
-        progress: lsp::ProgressParams,
-        language_server_id: LanguageServerId,
-        disk_based_diagnostics_progress_token: Option<String>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        let token = match progress.token {
-            lsp::NumberOrString::String(token) => token,
-            lsp::NumberOrString::Number(token) => {
-                log::info!("skipping numeric progress token {}", token);
-                return;
-            }
-        };
-        let lsp::ProgressParamsValue::WorkDone(progress) = progress.value;
-        let language_server_status =
-            if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) {
-                status
-            } else {
-                return;
-            };
-
-        if !language_server_status.progress_tokens.contains(&token) {
-            return;
-        }
-
-        let is_disk_based_diagnostics_progress = disk_based_diagnostics_progress_token
-            .as_ref()
-            .map_or(false, |disk_based_token| {
-                token.starts_with(disk_based_token)
-            });
-
-        match progress {
-            lsp::WorkDoneProgress::Begin(report) => {
-                if is_disk_based_diagnostics_progress {
-                    language_server_status.has_pending_diagnostic_updates = true;
-                    self.disk_based_diagnostics_started(language_server_id, cx);
-                    self.buffer_ordered_messages_tx
-                        .unbounded_send(BufferOrderedMessage::LanguageServerUpdate {
-                            language_server_id,
-                            message: proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating(Default::default())
-                        })
-                        .ok();
-                } else {
-                    self.on_lsp_work_start(
-                        language_server_id,
-                        token.clone(),
-                        LanguageServerProgress {
-                            message: report.message.clone(),
-                            percentage: report.percentage.map(|p| p as usize),
-                            last_update_at: Instant::now(),
-                        },
-                        cx,
-                    );
-                    self.buffer_ordered_messages_tx
-                        .unbounded_send(BufferOrderedMessage::LanguageServerUpdate {
-                            language_server_id,
-                            message: proto::update_language_server::Variant::WorkStart(
-                                proto::LspWorkStart {
-                                    token,
-                                    message: report.message,
-                                    percentage: report.percentage.map(|p| p as u32),
-                                },
-                            ),
-                        })
-                        .ok();
-                }
-            }
-            lsp::WorkDoneProgress::Report(report) => {
-                if !is_disk_based_diagnostics_progress {
-                    self.on_lsp_work_progress(
-                        language_server_id,
-                        token.clone(),
-                        LanguageServerProgress {
-                            message: report.message.clone(),
-                            percentage: report.percentage.map(|p| p as usize),
-                            last_update_at: Instant::now(),
-                        },
-                        cx,
-                    );
-                    self.buffer_ordered_messages_tx
-                        .unbounded_send(BufferOrderedMessage::LanguageServerUpdate {
-                            language_server_id,
-                            message: proto::update_language_server::Variant::WorkProgress(
-                                proto::LspWorkProgress {
-                                    token,
-                                    message: report.message,
-                                    percentage: report.percentage.map(|p| p as u32),
-                                },
-                            ),
-                        })
-                        .ok();
-                }
-            }
-            lsp::WorkDoneProgress::End(_) => {
-                language_server_status.progress_tokens.remove(&token);
-
-                if is_disk_based_diagnostics_progress {
-                    language_server_status.has_pending_diagnostic_updates = false;
-                    self.disk_based_diagnostics_finished(language_server_id, cx);
-                    self.buffer_ordered_messages_tx
-                        .unbounded_send(BufferOrderedMessage::LanguageServerUpdate {
-                            language_server_id,
-                            message:
-                                proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(
-                                    Default::default(),
-                                ),
-                        })
-                        .ok();
-                } else {
-                    self.on_lsp_work_end(language_server_id, token.clone(), cx);
-                    self.buffer_ordered_messages_tx
-                        .unbounded_send(BufferOrderedMessage::LanguageServerUpdate {
-                            language_server_id,
-                            message: proto::update_language_server::Variant::WorkEnd(
-                                proto::LspWorkEnd { token },
-                            ),
-                        })
-                        .ok();
-                }
-            }
-        }
-    }
-
-    fn on_lsp_work_start(
-        &mut self,
-        language_server_id: LanguageServerId,
-        token: String,
-        progress: LanguageServerProgress,
-        cx: &mut ModelContext<Self>,
-    ) {
-        if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) {
-            status.pending_work.insert(token, progress);
-            cx.notify();
-        }
-    }
-
-    fn on_lsp_work_progress(
-        &mut self,
-        language_server_id: LanguageServerId,
-        token: String,
-        progress: LanguageServerProgress,
-        cx: &mut ModelContext<Self>,
-    ) {
-        if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) {
-            let entry = status
-                .pending_work
-                .entry(token)
-                .or_insert(LanguageServerProgress {
-                    message: Default::default(),
-                    percentage: Default::default(),
-                    last_update_at: progress.last_update_at,
-                });
-            if progress.message.is_some() {
-                entry.message = progress.message;
-            }
-            if progress.percentage.is_some() {
-                entry.percentage = progress.percentage;
-            }
-            entry.last_update_at = progress.last_update_at;
-            cx.notify();
-        }
-    }
-
-    fn on_lsp_work_end(
-        &mut self,
-        language_server_id: LanguageServerId,
-        token: String,
-        cx: &mut ModelContext<Self>,
-    ) {
-        if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) {
-            cx.emit(Event::RefreshInlayHints);
-            status.pending_work.remove(&token);
-            cx.notify();
-        }
-    }
-
-    fn on_lsp_did_change_watched_files(
-        &mut self,
-        language_server_id: LanguageServerId,
-        params: DidChangeWatchedFilesRegistrationOptions,
-        cx: &mut ModelContext<Self>,
-    ) {
-        if let Some(LanguageServerState::Running { watched_paths, .. }) =
-            self.language_servers.get_mut(&language_server_id)
-        {
-            let mut builders = HashMap::default();
-            for watcher in params.watchers {
-                for worktree in &self.worktrees {
-                    if let Some(worktree) = worktree.upgrade() {
-                        let glob_is_inside_worktree = worktree.update(cx, |tree, _| {
-                            if let Some(abs_path) = tree.abs_path().to_str() {
-                                let relative_glob_pattern = match &watcher.glob_pattern {
-                                    lsp::GlobPattern::String(s) => s
-                                        .strip_prefix(abs_path)
-                                        .and_then(|s| s.strip_prefix(std::path::MAIN_SEPARATOR)),
-                                    lsp::GlobPattern::Relative(rp) => {
-                                        let base_uri = match &rp.base_uri {
-                                            lsp::OneOf::Left(workspace_folder) => {
-                                                &workspace_folder.uri
-                                            }
-                                            lsp::OneOf::Right(base_uri) => base_uri,
-                                        };
-                                        base_uri.to_file_path().ok().and_then(|file_path| {
-                                            (file_path.to_str() == Some(abs_path))
-                                                .then_some(rp.pattern.as_str())
-                                        })
-                                    }
-                                };
-                                if let Some(relative_glob_pattern) = relative_glob_pattern {
-                                    let literal_prefix =
-                                        glob_literal_prefix(&relative_glob_pattern);
-                                    tree.as_local_mut()
-                                        .unwrap()
-                                        .add_path_prefix_to_scan(Path::new(literal_prefix).into());
-                                    if let Some(glob) = Glob::new(relative_glob_pattern).log_err() {
-                                        builders
-                                            .entry(tree.id())
-                                            .or_insert_with(|| GlobSetBuilder::new())
-                                            .add(glob);
-                                    }
-                                    return true;
-                                }
-                            }
-                            false
-                        });
-                        if glob_is_inside_worktree {
-                            break;
-                        }
-                    }
-                }
-            }
-
-            watched_paths.clear();
-            for (worktree_id, builder) in builders {
-                if let Ok(globset) = builder.build() {
-                    watched_paths.insert(worktree_id, globset);
-                }
-            }
-
-            cx.notify();
-        }
-    }
-
-    async fn on_lsp_workspace_edit(
-        this: WeakModel<Self>,
-        params: lsp::ApplyWorkspaceEditParams,
-        server_id: LanguageServerId,
-        adapter: Arc<CachedLspAdapter>,
-        mut cx: AsyncAppContext,
-    ) -> Result<lsp::ApplyWorkspaceEditResponse> {
-        let this = this
-            .upgrade()
-            .ok_or_else(|| anyhow!("project project closed"))?;
-        let language_server = this
-            .update(&mut cx, |this, _| this.language_server_for_id(server_id))?
-            .ok_or_else(|| anyhow!("language server not found"))?;
-        let transaction = Self::deserialize_workspace_edit(
-            this.clone(),
-            params.edit,
-            true,
-            adapter.clone(),
-            language_server.clone(),
-            &mut cx,
-        )
-        .await
-        .log_err();
-        this.update(&mut cx, |this, _| {
-            if let Some(transaction) = transaction {
-                this.last_workspace_edits_by_language_server
-                    .insert(server_id, transaction);
-            }
-        })?;
-        Ok(lsp::ApplyWorkspaceEditResponse {
-            applied: true,
-            failed_change: None,
-            failure_reason: None,
-        })
-    }
-
-    pub fn language_server_statuses(
-        &self,
-    ) -> impl DoubleEndedIterator<Item = &LanguageServerStatus> {
-        self.language_server_statuses.values()
-    }
-
-    pub fn update_diagnostics(
-        &mut self,
-        language_server_id: LanguageServerId,
-        mut params: lsp::PublishDiagnosticsParams,
-        disk_based_sources: &[String],
-        cx: &mut ModelContext<Self>,
-    ) -> Result<()> {
-        let abs_path = params
-            .uri
-            .to_file_path()
-            .map_err(|_| anyhow!("URI is not a file"))?;
-        let mut diagnostics = Vec::default();
-        let mut primary_diagnostic_group_ids = HashMap::default();
-        let mut sources_by_group_id = HashMap::default();
-        let mut supporting_diagnostics = HashMap::default();
-
-        // Ensure that primary diagnostics are always the most severe
-        params.diagnostics.sort_by_key(|item| item.severity);
-
-        for diagnostic in &params.diagnostics {
-            let source = diagnostic.source.as_ref();
-            let code = diagnostic.code.as_ref().map(|code| match code {
-                lsp::NumberOrString::Number(code) => code.to_string(),
-                lsp::NumberOrString::String(code) => code.clone(),
-            });
-            let range = range_from_lsp(diagnostic.range);
-            let is_supporting = diagnostic
-                .related_information
-                .as_ref()
-                .map_or(false, |infos| {
-                    infos.iter().any(|info| {
-                        primary_diagnostic_group_ids.contains_key(&(
-                            source,
-                            code.clone(),
-                            range_from_lsp(info.location.range),
-                        ))
-                    })
-                });
-
-            let is_unnecessary = diagnostic.tags.as_ref().map_or(false, |tags| {
-                tags.iter().any(|tag| *tag == DiagnosticTag::UNNECESSARY)
-            });
-
-            if is_supporting {
-                supporting_diagnostics.insert(
-                    (source, code.clone(), range),
-                    (diagnostic.severity, is_unnecessary),
-                );
-            } else {
-                let group_id = post_inc(&mut self.next_diagnostic_group_id);
-                let is_disk_based =
-                    source.map_or(false, |source| disk_based_sources.contains(source));
-
-                sources_by_group_id.insert(group_id, source);
-                primary_diagnostic_group_ids
-                    .insert((source, code.clone(), range.clone()), group_id);
-
-                diagnostics.push(DiagnosticEntry {
-                    range,
-                    diagnostic: Diagnostic {
-                        source: diagnostic.source.clone(),
-                        code: code.clone(),
-                        severity: diagnostic.severity.unwrap_or(DiagnosticSeverity::ERROR),
-                        message: diagnostic.message.clone(),
-                        group_id,
-                        is_primary: true,
-                        is_valid: true,
-                        is_disk_based,
-                        is_unnecessary,
-                    },
-                });
-                if let Some(infos) = &diagnostic.related_information {
-                    for info in infos {
-                        if info.location.uri == params.uri && !info.message.is_empty() {
-                            let range = range_from_lsp(info.location.range);
-                            diagnostics.push(DiagnosticEntry {
-                                range,
-                                diagnostic: Diagnostic {
-                                    source: diagnostic.source.clone(),
-                                    code: code.clone(),
-                                    severity: DiagnosticSeverity::INFORMATION,
-                                    message: info.message.clone(),
-                                    group_id,
-                                    is_primary: false,
-                                    is_valid: true,
-                                    is_disk_based,
-                                    is_unnecessary: false,
-                                },
-                            });
-                        }
-                    }
-                }
-            }
-        }
-
-        for entry in &mut diagnostics {
-            let diagnostic = &mut entry.diagnostic;
-            if !diagnostic.is_primary {
-                let source = *sources_by_group_id.get(&diagnostic.group_id).unwrap();
-                if let Some(&(severity, is_unnecessary)) = supporting_diagnostics.get(&(
-                    source,
-                    diagnostic.code.clone(),
-                    entry.range.clone(),
-                )) {
-                    if let Some(severity) = severity {
-                        diagnostic.severity = severity;
-                    }
-                    diagnostic.is_unnecessary = is_unnecessary;
-                }
-            }
-        }
-
-        self.update_diagnostic_entries(
-            language_server_id,
-            abs_path,
-            params.version,
-            diagnostics,
-            cx,
-        )?;
-        Ok(())
-    }
-
-    pub fn update_diagnostic_entries(
-        &mut self,
-        server_id: LanguageServerId,
-        abs_path: PathBuf,
-        version: Option<i32>,
-        diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
-        cx: &mut ModelContext<Project>,
-    ) -> Result<(), anyhow::Error> {
-        let (worktree, relative_path) = self
-            .find_local_worktree(&abs_path, cx)
-            .ok_or_else(|| anyhow!("no worktree found for diagnostics path {abs_path:?}"))?;
-
-        let project_path = ProjectPath {
-            worktree_id: worktree.read(cx).id(),
-            path: relative_path.into(),
-        };
-
-        if let Some(buffer) = self.get_open_buffer(&project_path, cx) {
-            self.update_buffer_diagnostics(&buffer, server_id, version, diagnostics.clone(), cx)?;
-        }
-
-        let updated = worktree.update(cx, |worktree, cx| {
-            worktree
-                .as_local_mut()
-                .ok_or_else(|| anyhow!("not a local worktree"))?
-                .update_diagnostics(server_id, project_path.path.clone(), diagnostics, cx)
-        })?;
-        if updated {
-            cx.emit(Event::DiagnosticsUpdated {
-                language_server_id: server_id,
-                path: project_path,
-            });
-        }
-        Ok(())
-    }
-
-    fn update_buffer_diagnostics(
-        &mut self,
-        buffer: &Model<Buffer>,
-        server_id: LanguageServerId,
-        version: Option<i32>,
-        mut diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
-        cx: &mut ModelContext<Self>,
-    ) -> Result<()> {
-        fn compare_diagnostics(a: &Diagnostic, b: &Diagnostic) -> Ordering {
-            Ordering::Equal
-                .then_with(|| b.is_primary.cmp(&a.is_primary))
-                .then_with(|| a.is_disk_based.cmp(&b.is_disk_based))
-                .then_with(|| a.severity.cmp(&b.severity))
-                .then_with(|| a.message.cmp(&b.message))
-        }
-
-        let snapshot = self.buffer_snapshot_for_lsp_version(buffer, server_id, version, cx)?;
-
-        diagnostics.sort_unstable_by(|a, b| {
-            Ordering::Equal
-                .then_with(|| a.range.start.cmp(&b.range.start))
-                .then_with(|| b.range.end.cmp(&a.range.end))
-                .then_with(|| compare_diagnostics(&a.diagnostic, &b.diagnostic))
-        });
-
-        let mut sanitized_diagnostics = Vec::new();
-        let edits_since_save = Patch::new(
-            snapshot
-                .edits_since::<Unclipped<PointUtf16>>(buffer.read(cx).saved_version())
-                .collect(),
-        );
-        for entry in diagnostics {
-            let start;
-            let end;
-            if entry.diagnostic.is_disk_based {
-                // Some diagnostics are based on files on disk instead of buffers'
-                // current contents. Adjust these diagnostics' ranges to reflect
-                // any unsaved edits.
-                start = edits_since_save.old_to_new(entry.range.start);
-                end = edits_since_save.old_to_new(entry.range.end);
-            } else {
-                start = entry.range.start;
-                end = entry.range.end;
-            }
-
-            let mut range = snapshot.clip_point_utf16(start, Bias::Left)
-                ..snapshot.clip_point_utf16(end, Bias::Right);
-
-            // Expand empty ranges by one codepoint
-            if range.start == range.end {
-                // This will be go to the next boundary when being clipped
-                range.end.column += 1;
-                range.end = snapshot.clip_point_utf16(Unclipped(range.end), Bias::Right);
-                if range.start == range.end && range.end.column > 0 {
-                    range.start.column -= 1;
-                    range.end = snapshot.clip_point_utf16(Unclipped(range.end), Bias::Left);
-                }
-            }
-
-            sanitized_diagnostics.push(DiagnosticEntry {
-                range,
-                diagnostic: entry.diagnostic,
-            });
-        }
-        drop(edits_since_save);
-
-        let set = DiagnosticSet::new(sanitized_diagnostics, &snapshot);
-        buffer.update(cx, |buffer, cx| {
-            buffer.update_diagnostics(server_id, set, cx)
-        });
-        Ok(())
-    }
-
-    pub fn reload_buffers(
-        &self,
-        buffers: HashSet<Model<Buffer>>,
-        push_to_history: bool,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<ProjectTransaction>> {
-        let mut local_buffers = Vec::new();
-        let mut remote_buffers = None;
-        for buffer_handle in buffers {
-            let buffer = buffer_handle.read(cx);
-            if buffer.is_dirty() {
-                if let Some(file) = File::from_dyn(buffer.file()) {
-                    if file.is_local() {
-                        local_buffers.push(buffer_handle);
-                    } else {
-                        remote_buffers.get_or_insert(Vec::new()).push(buffer_handle);
-                    }
-                }
-            }
-        }
-
-        let remote_buffers = self.remote_id().zip(remote_buffers);
-        let client = self.client.clone();
-
-        cx.spawn(move |this, mut cx| async move {
-            let mut project_transaction = ProjectTransaction::default();
-
-            if let Some((project_id, remote_buffers)) = remote_buffers {
-                let response = client
-                    .request(proto::ReloadBuffers {
-                        project_id,
-                        buffer_ids: remote_buffers
-                            .iter()
-                            .filter_map(|buffer| {
-                                buffer.update(&mut cx, |buffer, _| buffer.remote_id()).ok()
-                            })
-                            .collect(),
-                    })
-                    .await?
-                    .transaction
-                    .ok_or_else(|| anyhow!("missing transaction"))?;
-                project_transaction = this
-                    .update(&mut cx, |this, cx| {
-                        this.deserialize_project_transaction(response, push_to_history, cx)
-                    })?
-                    .await?;
-            }
-
-            for buffer in local_buffers {
-                let transaction = buffer
-                    .update(&mut cx, |buffer, cx| buffer.reload(cx))?
-                    .await?;
-                buffer.update(&mut cx, |buffer, cx| {
-                    if let Some(transaction) = transaction {
-                        if !push_to_history {
-                            buffer.forget_transaction(transaction.id);
-                        }
-                        project_transaction.0.insert(cx.handle(), transaction);
-                    }
-                })?;
-            }
-
-            Ok(project_transaction)
-        })
-    }
-
-    pub fn format(
-        &mut self,
-        buffers: HashSet<Model<Buffer>>,
-        push_to_history: bool,
-        trigger: FormatTrigger,
-        cx: &mut ModelContext<Project>,
-    ) -> Task<anyhow::Result<ProjectTransaction>> {
-        if self.is_local() {
-            let mut buffers_with_paths_and_servers = buffers
-                .into_iter()
-                .filter_map(|buffer_handle| {
-                    let buffer = buffer_handle.read(cx);
-                    let file = File::from_dyn(buffer.file())?;
-                    let buffer_abs_path = file.as_local().map(|f| f.abs_path(cx));
-                    let server = self
-                        .primary_language_server_for_buffer(buffer, cx)
-                        .map(|s| s.1.clone());
-                    Some((buffer_handle, buffer_abs_path, server))
-                })
-                .collect::<Vec<_>>();
-
-            cx.spawn(move |project, mut cx| async move {
-                // Do not allow multiple concurrent formatting requests for the
-                // same buffer.
-                project.update(&mut cx, |this, cx| {
-                    buffers_with_paths_and_servers.retain(|(buffer, _, _)| {
-                        this.buffers_being_formatted
-                            .insert(buffer.read(cx).remote_id())
-                    });
-                })?;
-
-                let _cleanup = defer({
-                    let this = project.clone();
-                    let mut cx = cx.clone();
-                    let buffers = &buffers_with_paths_and_servers;
-                    move || {
-                        this.update(&mut cx, |this, cx| {
-                            for (buffer, _, _) in buffers {
-                                this.buffers_being_formatted
-                                    .remove(&buffer.read(cx).remote_id());
-                            }
-                        })
-                        .ok();
-                    }
-                });
-
-                let mut project_transaction = ProjectTransaction::default();
-                for (buffer, buffer_abs_path, language_server) in &buffers_with_paths_and_servers {
-                    let settings = buffer.update(&mut cx, |buffer, cx| {
-                        language_settings(buffer.language(), buffer.file(), cx).clone()
-                    })?;
-
-                    let remove_trailing_whitespace = settings.remove_trailing_whitespace_on_save;
-                    let ensure_final_newline = settings.ensure_final_newline_on_save;
-                    let tab_size = settings.tab_size;
-
-                    // First, format buffer's whitespace according to the settings.
-                    let trailing_whitespace_diff = if remove_trailing_whitespace {
-                        Some(
-                            buffer
-                                .update(&mut cx, |b, cx| b.remove_trailing_whitespace(cx))?
-                                .await,
-                        )
-                    } else {
-                        None
-                    };
-                    let whitespace_transaction_id = buffer.update(&mut cx, |buffer, cx| {
-                        buffer.finalize_last_transaction();
-                        buffer.start_transaction();
-                        if let Some(diff) = trailing_whitespace_diff {
-                            buffer.apply_diff(diff, cx);
-                        }
-                        if ensure_final_newline {
-                            buffer.ensure_final_newline(cx);
-                        }
-                        buffer.end_transaction(cx)
-                    })?;
-
-                    // Apply language-specific formatting using either a language server
-                    // or external command.
-                    let mut format_operation = None;
-                    match (&settings.formatter, &settings.format_on_save) {
-                        (_, FormatOnSave::Off) if trigger == FormatTrigger::Save => {}
-
-                        (Formatter::LanguageServer, FormatOnSave::On | FormatOnSave::Off)
-                        | (_, FormatOnSave::LanguageServer) => {
-                            if let Some((language_server, buffer_abs_path)) =
-                                language_server.as_ref().zip(buffer_abs_path.as_ref())
-                            {
-                                format_operation = Some(FormatOperation::Lsp(
-                                    Self::format_via_lsp(
-                                        &project,
-                                        &buffer,
-                                        buffer_abs_path,
-                                        &language_server,
-                                        tab_size,
-                                        &mut cx,
-                                    )
-                                    .await
-                                    .context("failed to format via language server")?,
-                                ));
-                            }
-                        }
-
-                        (
-                            Formatter::External { command, arguments },
-                            FormatOnSave::On | FormatOnSave::Off,
-                        )
-                        | (_, FormatOnSave::External { command, arguments }) => {
-                            if let Some(buffer_abs_path) = buffer_abs_path {
-                                format_operation = Self::format_via_external_command(
-                                    buffer,
-                                    buffer_abs_path,
-                                    &command,
-                                    &arguments,
-                                    &mut cx,
-                                )
-                                .await
-                                .context(format!(
-                                    "failed to format via external command {:?}",
-                                    command
-                                ))?
-                                .map(FormatOperation::External);
-                            }
-                        }
-                        (Formatter::Auto, FormatOnSave::On | FormatOnSave::Off) => {
-                            if let Some(new_operation) =
-                                prettier_support::format_with_prettier(&project, buffer, &mut cx)
-                                    .await
-                            {
-                                format_operation = Some(new_operation);
-                            } else if let Some((language_server, buffer_abs_path)) =
-                                language_server.as_ref().zip(buffer_abs_path.as_ref())
-                            {
-                                format_operation = Some(FormatOperation::Lsp(
-                                    Self::format_via_lsp(
-                                        &project,
-                                        &buffer,
-                                        buffer_abs_path,
-                                        &language_server,
-                                        tab_size,
-                                        &mut cx,
-                                    )
-                                    .await
-                                    .context("failed to format via language server")?,
-                                ));
-                            }
-                        }
-                        (Formatter::Prettier, FormatOnSave::On | FormatOnSave::Off) => {
-                            if let Some(new_operation) =
-                                prettier_support::format_with_prettier(&project, buffer, &mut cx)
-                                    .await
-                            {
-                                format_operation = Some(new_operation);
-                            }
-                        }
-                    };
-
-                    buffer.update(&mut cx, |b, cx| {
-                        // If the buffer had its whitespace formatted and was edited while the language-specific
-                        // formatting was being computed, avoid applying the language-specific formatting, because
-                        // it can't be grouped with the whitespace formatting in the undo history.
-                        if let Some(transaction_id) = whitespace_transaction_id {
-                            if b.peek_undo_stack()
-                                .map_or(true, |e| e.transaction_id() != transaction_id)
-                            {
-                                format_operation.take();
-                            }
-                        }
-
-                        // Apply any language-specific formatting, and group the two formatting operations
-                        // in the buffer's undo history.
-                        if let Some(operation) = format_operation {
-                            match operation {
-                                FormatOperation::Lsp(edits) => {
-                                    b.edit(edits, None, cx);
-                                }
-                                FormatOperation::External(diff) => {
-                                    b.apply_diff(diff, cx);
-                                }
-                                FormatOperation::Prettier(diff) => {
-                                    b.apply_diff(diff, cx);
-                                }
-                            }
-
-                            if let Some(transaction_id) = whitespace_transaction_id {
-                                b.group_until_transaction(transaction_id);
-                            }
-                        }
-
-                        if let Some(transaction) = b.finalize_last_transaction().cloned() {
-                            if !push_to_history {
-                                b.forget_transaction(transaction.id);
-                            }
-                            project_transaction.0.insert(buffer.clone(), transaction);
-                        }
-                    })?;
-                }
-
-                Ok(project_transaction)
-            })
-        } else {
-            let remote_id = self.remote_id();
-            let client = self.client.clone();
-            cx.spawn(move |this, mut cx| async move {
-                let mut project_transaction = ProjectTransaction::default();
-                if let Some(project_id) = remote_id {
-                    let response = client
-                        .request(proto::FormatBuffers {
-                            project_id,
-                            trigger: trigger as i32,
-                            buffer_ids: buffers
-                                .iter()
-                                .map(|buffer| {
-                                    buffer.update(&mut cx, |buffer, _| buffer.remote_id())
-                                })
-                                .collect::<Result<_>>()?,
-                        })
-                        .await?
-                        .transaction
-                        .ok_or_else(|| anyhow!("missing transaction"))?;
-                    project_transaction = this
-                        .update(&mut cx, |this, cx| {
-                            this.deserialize_project_transaction(response, push_to_history, cx)
-                        })?
-                        .await?;
-                }
-                Ok(project_transaction)
-            })
-        }
-    }
-
-    async fn format_via_lsp(
-        this: &WeakModel<Self>,
-        buffer: &Model<Buffer>,
-        abs_path: &Path,
-        language_server: &Arc<LanguageServer>,
-        tab_size: NonZeroU32,
-        cx: &mut AsyncAppContext,
-    ) -> Result<Vec<(Range<Anchor>, String)>> {
-        let uri = lsp::Url::from_file_path(abs_path)
-            .map_err(|_| anyhow!("failed to convert abs path to uri"))?;
-        let text_document = lsp::TextDocumentIdentifier::new(uri);
-        let capabilities = &language_server.capabilities();
-
-        let formatting_provider = capabilities.document_formatting_provider.as_ref();
-        let range_formatting_provider = capabilities.document_range_formatting_provider.as_ref();
-
-        let lsp_edits = if matches!(formatting_provider, Some(p) if *p != OneOf::Left(false)) {
-            language_server
-                .request::<lsp::request::Formatting>(lsp::DocumentFormattingParams {
-                    text_document,
-                    options: lsp_command::lsp_formatting_options(tab_size.get()),
-                    work_done_progress_params: Default::default(),
-                })
-                .await?
-        } else if matches!(range_formatting_provider, Some(p) if *p != OneOf::Left(false)) {
-            let buffer_start = lsp::Position::new(0, 0);
-            let buffer_end = buffer.update(cx, |b, _| point_to_lsp(b.max_point_utf16()))?;
-
-            language_server
-                .request::<lsp::request::RangeFormatting>(lsp::DocumentRangeFormattingParams {
-                    text_document,
-                    range: lsp::Range::new(buffer_start, buffer_end),
-                    options: lsp_command::lsp_formatting_options(tab_size.get()),
-                    work_done_progress_params: Default::default(),
-                })
-                .await?
-        } else {
-            None
-        };
-
-        if let Some(lsp_edits) = lsp_edits {
-            this.update(cx, |this, cx| {
-                this.edits_from_lsp(buffer, lsp_edits, language_server.server_id(), None, cx)
-            })?
-            .await
-        } else {
-            Ok(Vec::new())
-        }
-    }
-
-    async fn format_via_external_command(
-        buffer: &Model<Buffer>,
-        buffer_abs_path: &Path,
-        command: &str,
-        arguments: &[String],
-        cx: &mut AsyncAppContext,
-    ) -> Result<Option<Diff>> {
-        let working_dir_path = buffer.update(cx, |buffer, cx| {
-            let file = File::from_dyn(buffer.file())?;
-            let worktree = file.worktree.read(cx).as_local()?;
-            let mut worktree_path = worktree.abs_path().to_path_buf();
-            if worktree.root_entry()?.is_file() {
-                worktree_path.pop();
-            }
-            Some(worktree_path)
-        })?;
-
-        if let Some(working_dir_path) = working_dir_path {
-            let mut child =
-                smol::process::Command::new(command)
-                    .args(arguments.iter().map(|arg| {
-                        arg.replace("{buffer_path}", &buffer_abs_path.to_string_lossy())
-                    }))
-                    .current_dir(&working_dir_path)
-                    .stdin(smol::process::Stdio::piped())
-                    .stdout(smol::process::Stdio::piped())
-                    .stderr(smol::process::Stdio::piped())
-                    .spawn()?;
-            let stdin = child
-                .stdin
-                .as_mut()
-                .ok_or_else(|| anyhow!("failed to acquire stdin"))?;
-            let text = buffer.update(cx, |buffer, _| buffer.as_rope().clone())?;
-            for chunk in text.chunks() {
-                stdin.write_all(chunk.as_bytes()).await?;
-            }
-            stdin.flush().await?;
-
-            let output = child.output().await?;
-            if !output.status.success() {
-                return Err(anyhow!(
-                    "command failed with exit code {:?}:\nstdout: {}\nstderr: {}",
-                    output.status.code(),
-                    String::from_utf8_lossy(&output.stdout),
-                    String::from_utf8_lossy(&output.stderr),
-                ));
-            }
-
-            let stdout = String::from_utf8(output.stdout)?;
-            Ok(Some(
-                buffer
-                    .update(cx, |buffer, cx| buffer.diff(stdout, cx))?
-                    .await,
-            ))
-        } else {
-            Ok(None)
-        }
-    }
-
-    pub fn definition<T: ToPointUtf16>(
-        &self,
-        buffer: &Model<Buffer>,
-        position: T,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<LocationLink>>> {
-        let position = position.to_point_utf16(buffer.read(cx));
-        self.request_lsp(
-            buffer.clone(),
-            LanguageServerToQuery::Primary,
-            GetDefinition { position },
-            cx,
-        )
-    }
-
-    pub fn type_definition<T: ToPointUtf16>(
-        &self,
-        buffer: &Model<Buffer>,
-        position: T,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<LocationLink>>> {
-        let position = position.to_point_utf16(buffer.read(cx));
-        self.request_lsp(
-            buffer.clone(),
-            LanguageServerToQuery::Primary,
-            GetTypeDefinition { position },
-            cx,
-        )
-    }
-
-    pub fn references<T: ToPointUtf16>(
-        &self,
-        buffer: &Model<Buffer>,
-        position: T,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<Location>>> {
-        let position = position.to_point_utf16(buffer.read(cx));
-        self.request_lsp(
-            buffer.clone(),
-            LanguageServerToQuery::Primary,
-            GetReferences { position },
-            cx,
-        )
-    }
-
-    pub fn document_highlights<T: ToPointUtf16>(
-        &self,
-        buffer: &Model<Buffer>,
-        position: T,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<DocumentHighlight>>> {
-        let position = position.to_point_utf16(buffer.read(cx));
-        self.request_lsp(
-            buffer.clone(),
-            LanguageServerToQuery::Primary,
-            GetDocumentHighlights { position },
-            cx,
-        )
-    }
-
-    pub fn symbols(&self, query: &str, cx: &mut ModelContext<Self>) -> Task<Result<Vec<Symbol>>> {
-        if self.is_local() {
-            let mut requests = Vec::new();
-            for ((worktree_id, _), server_id) in self.language_server_ids.iter() {
-                let worktree_id = *worktree_id;
-                let worktree_handle = self.worktree_for_id(worktree_id, cx);
-                let worktree = match worktree_handle.and_then(|tree| tree.read(cx).as_local()) {
-                    Some(worktree) => worktree,
-                    None => continue,
-                };
-                let worktree_abs_path = worktree.abs_path().clone();
-
-                let (adapter, language, server) = match self.language_servers.get(server_id) {
-                    Some(LanguageServerState::Running {
-                        adapter,
-                        language,
-                        server,
-                        ..
-                    }) => (adapter.clone(), language.clone(), server),
-
-                    _ => continue,
-                };
-
-                requests.push(
-                    server
-                        .request::<lsp::request::WorkspaceSymbolRequest>(
-                            lsp::WorkspaceSymbolParams {
-                                query: query.to_string(),
-                                ..Default::default()
-                            },
-                        )
-                        .log_err()
-                        .map(move |response| {
-                            let lsp_symbols = response.flatten().map(|symbol_response| match symbol_response {
-                                lsp::WorkspaceSymbolResponse::Flat(flat_responses) => {
-                                    flat_responses.into_iter().map(|lsp_symbol| {
-                                        (lsp_symbol.name, lsp_symbol.kind, lsp_symbol.location)
-                                    }).collect::<Vec<_>>()
-                                }
-                                lsp::WorkspaceSymbolResponse::Nested(nested_responses) => {
-                                    nested_responses.into_iter().filter_map(|lsp_symbol| {
-                                        let location = match lsp_symbol.location {
-                                            OneOf::Left(location) => location,
-                                            OneOf::Right(_) => {
-                                                error!("Unexpected: client capabilities forbid symbol resolutions in workspace.symbol.resolveSupport");
-                                                return None
-                                            }
-                                        };
-                                        Some((lsp_symbol.name, lsp_symbol.kind, location))
-                                    }).collect::<Vec<_>>()
-                                }
-                            }).unwrap_or_default();
-
-                            (
-                                adapter,
-                                language,
-                                worktree_id,
-                                worktree_abs_path,
-                                lsp_symbols,
-                            )
-                        }),
-                );
-            }
-
-            cx.spawn(move |this, mut cx| async move {
-                let responses = futures::future::join_all(requests).await;
-                let this = match this.upgrade() {
-                    Some(this) => this,
-                    None => return Ok(Vec::new()),
-                };
-
-                let symbols = this.update(&mut cx, |this, cx| {
-                    let mut symbols = Vec::new();
-                    for (
-                        adapter,
-                        adapter_language,
-                        source_worktree_id,
-                        worktree_abs_path,
-                        lsp_symbols,
-                    ) in responses
-                    {
-                        symbols.extend(lsp_symbols.into_iter().filter_map(
-                            |(symbol_name, symbol_kind, symbol_location)| {
-                                let abs_path = symbol_location.uri.to_file_path().ok()?;
-                                let mut worktree_id = source_worktree_id;
-                                let path;
-                                if let Some((worktree, rel_path)) =
-                                    this.find_local_worktree(&abs_path, cx)
-                                {
-                                    worktree_id = worktree.read(cx).id();
-                                    path = rel_path;
-                                } else {
-                                    path = relativize_path(&worktree_abs_path, &abs_path);
-                                }
-
-                                let project_path = ProjectPath {
-                                    worktree_id,
-                                    path: path.into(),
-                                };
-                                let signature = this.symbol_signature(&project_path);
-                                let adapter_language = adapter_language.clone();
-                                let language = this
-                                    .languages
-                                    .language_for_file(&project_path.path, None)
-                                    .unwrap_or_else(move |_| adapter_language);
-                                let language_server_name = adapter.name.clone();
-                                Some(async move {
-                                    let language = language.await;
-                                    let label =
-                                        language.label_for_symbol(&symbol_name, symbol_kind).await;
-
-                                    Symbol {
-                                        language_server_name,
-                                        source_worktree_id,
-                                        path: project_path,
-                                        label: label.unwrap_or_else(|| {
-                                            CodeLabel::plain(symbol_name.clone(), None)
-                                        }),
-                                        kind: symbol_kind,
-                                        name: symbol_name,
-                                        range: range_from_lsp(symbol_location.range),
-                                        signature,
-                                    }
-                                })
-                            },
-                        ));
-                    }
-
-                    symbols
-                })?;
-
-                Ok(futures::future::join_all(symbols).await)
-            })
-        } else if let Some(project_id) = self.remote_id() {
-            let request = self.client.request(proto::GetProjectSymbols {
-                project_id,
-                query: query.to_string(),
-            });
-            cx.spawn(move |this, mut cx| async move {
-                let response = request.await?;
-                let mut symbols = Vec::new();
-                if let Some(this) = this.upgrade() {
-                    let new_symbols = this.update(&mut cx, |this, _| {
-                        response
-                            .symbols
-                            .into_iter()
-                            .map(|symbol| this.deserialize_symbol(symbol))
-                            .collect::<Vec<_>>()
-                    })?;
-                    symbols = futures::future::join_all(new_symbols)
-                        .await
-                        .into_iter()
-                        .filter_map(|symbol| symbol.log_err())
-                        .collect::<Vec<_>>();
-                }
-                Ok(symbols)
-            })
-        } else {
-            Task::ready(Ok(Default::default()))
-        }
-    }
-
-    pub fn open_buffer_for_symbol(
-        &mut self,
-        symbol: &Symbol,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Model<Buffer>>> {
-        if self.is_local() {
-            let language_server_id = if let Some(id) = self.language_server_ids.get(&(
-                symbol.source_worktree_id,
-                symbol.language_server_name.clone(),
-            )) {
-                *id
-            } else {
-                return Task::ready(Err(anyhow!(
-                    "language server for worktree and language not found"
-                )));
-            };
-
-            let worktree_abs_path = if let Some(worktree_abs_path) = self
-                .worktree_for_id(symbol.path.worktree_id, cx)
-                .and_then(|worktree| worktree.read(cx).as_local())
-                .map(|local_worktree| local_worktree.abs_path())
-            {
-                worktree_abs_path
-            } else {
-                return Task::ready(Err(anyhow!("worktree not found for symbol")));
-            };
-            let symbol_abs_path = worktree_abs_path.join(&symbol.path.path);
-            let symbol_uri = if let Ok(uri) = lsp::Url::from_file_path(symbol_abs_path) {
-                uri
-            } else {
-                return Task::ready(Err(anyhow!("invalid symbol path")));
-            };
-
-            self.open_local_buffer_via_lsp(
-                symbol_uri,
-                language_server_id,
-                symbol.language_server_name.clone(),
-                cx,
-            )
-        } else if let Some(project_id) = self.remote_id() {
-            let request = self.client.request(proto::OpenBufferForSymbol {
-                project_id,
-                symbol: Some(serialize_symbol(symbol)),
-            });
-            cx.spawn(move |this, mut cx| async move {
-                let response = request.await?;
-                this.update(&mut cx, |this, cx| {
-                    this.wait_for_remote_buffer(response.buffer_id, cx)
-                })?
-                .await
-            })
-        } else {
-            Task::ready(Err(anyhow!("project does not have a remote id")))
-        }
-    }
-
-    pub fn hover<T: ToPointUtf16>(
-        &self,
-        buffer: &Model<Buffer>,
-        position: T,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Option<Hover>>> {
-        let position = position.to_point_utf16(buffer.read(cx));
-        self.request_lsp(
-            buffer.clone(),
-            LanguageServerToQuery::Primary,
-            GetHover { position },
-            cx,
-        )
-    }
-
-    pub fn completions<T: ToOffset + ToPointUtf16>(
-        &self,
-        buffer: &Model<Buffer>,
-        position: T,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<Completion>>> {
-        let position = position.to_point_utf16(buffer.read(cx));
-        if self.is_local() {
-            let snapshot = buffer.read(cx).snapshot();
-            let offset = position.to_offset(&snapshot);
-            let scope = snapshot.language_scope_at(offset);
-
-            let server_ids: Vec<_> = self
-                .language_servers_for_buffer(buffer.read(cx), cx)
-                .filter(|(_, server)| server.capabilities().completion_provider.is_some())
-                .filter(|(adapter, _)| {
-                    scope
-                        .as_ref()
-                        .map(|scope| scope.language_allowed(&adapter.name))
-                        .unwrap_or(true)
-                })
-                .map(|(_, server)| server.server_id())
-                .collect();
-
-            let buffer = buffer.clone();
-            cx.spawn(move |this, mut cx| async move {
-                let mut tasks = Vec::with_capacity(server_ids.len());
-                this.update(&mut cx, |this, cx| {
-                    for server_id in server_ids {
-                        tasks.push(this.request_lsp(
-                            buffer.clone(),
-                            LanguageServerToQuery::Other(server_id),
-                            GetCompletions { position },
-                            cx,
-                        ));
-                    }
-                })?;
-
-                let mut completions = Vec::new();
-                for task in tasks {
-                    if let Ok(new_completions) = task.await {
-                        completions.extend_from_slice(&new_completions);
-                    }
-                }
-
-                Ok(completions)
-            })
-        } else if let Some(project_id) = self.remote_id() {
-            self.send_lsp_proto_request(buffer.clone(), project_id, GetCompletions { position }, cx)
-        } else {
-            Task::ready(Ok(Default::default()))
-        }
-    }
-
-    pub fn apply_additional_edits_for_completion(
-        &self,
-        buffer_handle: Model<Buffer>,
-        completion: Completion,
-        push_to_history: bool,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Option<Transaction>>> {
-        let buffer = buffer_handle.read(cx);
-        let buffer_id = buffer.remote_id();
-
-        if self.is_local() {
-            let server_id = completion.server_id;
-            let lang_server = match self.language_server_for_buffer(buffer, server_id, cx) {
-                Some((_, server)) => server.clone(),
-                _ => return Task::ready(Ok(Default::default())),
-            };
-
-            cx.spawn(move |this, mut cx| async move {
-                let can_resolve = lang_server
-                    .capabilities()
-                    .completion_provider
-                    .as_ref()
-                    .and_then(|options| options.resolve_provider)
-                    .unwrap_or(false);
-                let additional_text_edits = if can_resolve {
-                    lang_server
-                        .request::<lsp::request::ResolveCompletionItem>(completion.lsp_completion)
-                        .await?
-                        .additional_text_edits
-                } else {
-                    completion.lsp_completion.additional_text_edits
-                };
-                if let Some(edits) = additional_text_edits {
-                    let edits = this
-                        .update(&mut cx, |this, cx| {
-                            this.edits_from_lsp(
-                                &buffer_handle,
-                                edits,
-                                lang_server.server_id(),
-                                None,
-                                cx,
-                            )
-                        })?
-                        .await?;
-
-                    buffer_handle.update(&mut cx, |buffer, cx| {
-                        buffer.finalize_last_transaction();
-                        buffer.start_transaction();
-
-                        for (range, text) in edits {
-                            let primary = &completion.old_range;
-                            let start_within = primary.start.cmp(&range.start, buffer).is_le()
-                                && primary.end.cmp(&range.start, buffer).is_ge();
-                            let end_within = range.start.cmp(&primary.end, buffer).is_le()
-                                && range.end.cmp(&primary.end, buffer).is_ge();
-
-                            //Skip additional edits which overlap with the primary completion edit
-                            //https://github.com/zed-industries/zed/pull/1871
-                            if !start_within && !end_within {
-                                buffer.edit([(range, text)], None, cx);
-                            }
-                        }
-
-                        let transaction = if buffer.end_transaction(cx).is_some() {
-                            let transaction = buffer.finalize_last_transaction().unwrap().clone();
-                            if !push_to_history {
-                                buffer.forget_transaction(transaction.id);
-                            }
-                            Some(transaction)
-                        } else {
-                            None
-                        };
-                        Ok(transaction)
-                    })?
-                } else {
-                    Ok(None)
-                }
-            })
-        } else if let Some(project_id) = self.remote_id() {
-            let client = self.client.clone();
-            cx.spawn(move |_, mut cx| async move {
-                let response = client
-                    .request(proto::ApplyCompletionAdditionalEdits {
-                        project_id,
-                        buffer_id,
-                        completion: Some(language::proto::serialize_completion(&completion)),
-                    })
-                    .await?;
-
-                if let Some(transaction) = response.transaction {
-                    let transaction = language::proto::deserialize_transaction(transaction)?;
-                    buffer_handle
-                        .update(&mut cx, |buffer, _| {
-                            buffer.wait_for_edits(transaction.edit_ids.iter().copied())
-                        })?
-                        .await?;
-                    if push_to_history {
-                        buffer_handle.update(&mut cx, |buffer, _| {
-                            buffer.push_transaction(transaction.clone(), Instant::now());
-                        })?;
-                    }
-                    Ok(Some(transaction))
-                } else {
-                    Ok(None)
-                }
-            })
-        } else {
-            Task::ready(Err(anyhow!("project does not have a remote id")))
-        }
-    }
-
-    pub fn code_actions<T: Clone + ToOffset>(
-        &self,
-        buffer_handle: &Model<Buffer>,
-        range: Range<T>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<CodeAction>>> {
-        let buffer = buffer_handle.read(cx);
-        let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
-        self.request_lsp(
-            buffer_handle.clone(),
-            LanguageServerToQuery::Primary,
-            GetCodeActions { range },
-            cx,
-        )
-    }
-
-    pub fn apply_code_action(
-        &self,
-        buffer_handle: Model<Buffer>,
-        mut action: CodeAction,
-        push_to_history: bool,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<ProjectTransaction>> {
-        if self.is_local() {
-            let buffer = buffer_handle.read(cx);
-            let (lsp_adapter, lang_server) = if let Some((adapter, server)) =
-                self.language_server_for_buffer(buffer, action.server_id, cx)
-            {
-                (adapter.clone(), server.clone())
-            } else {
-                return Task::ready(Ok(Default::default()));
-            };
-            let range = action.range.to_point_utf16(buffer);
-
-            cx.spawn(move |this, mut cx| async move {
-                if let Some(lsp_range) = action
-                    .lsp_action
-                    .data
-                    .as_mut()
-                    .and_then(|d| d.get_mut("codeActionParams"))
-                    .and_then(|d| d.get_mut("range"))
-                {
-                    *lsp_range = serde_json::to_value(&range_to_lsp(range)).unwrap();
-                    action.lsp_action = lang_server
-                        .request::<lsp::request::CodeActionResolveRequest>(action.lsp_action)
-                        .await?;
-                } else {
-                    let actions = this
-                        .update(&mut cx, |this, cx| {
-                            this.code_actions(&buffer_handle, action.range, cx)
-                        })?
-                        .await?;
-                    action.lsp_action = actions
-                        .into_iter()
-                        .find(|a| a.lsp_action.title == action.lsp_action.title)
-                        .ok_or_else(|| anyhow!("code action is outdated"))?
-                        .lsp_action;
-                }
-
-                if let Some(edit) = action.lsp_action.edit {
-                    if edit.changes.is_some() || edit.document_changes.is_some() {
-                        return Self::deserialize_workspace_edit(
-                            this.upgrade().ok_or_else(|| anyhow!("no app present"))?,
-                            edit,
-                            push_to_history,
-                            lsp_adapter.clone(),
-                            lang_server.clone(),
-                            &mut cx,
-                        )
-                        .await;
-                    }
-                }
-
-                if let Some(command) = action.lsp_action.command {
-                    this.update(&mut cx, |this, _| {
-                        this.last_workspace_edits_by_language_server
-                            .remove(&lang_server.server_id());
-                    })?;
-
-                    let result = lang_server
-                        .request::<lsp::request::ExecuteCommand>(lsp::ExecuteCommandParams {
-                            command: command.command,
-                            arguments: command.arguments.unwrap_or_default(),
-                            ..Default::default()
-                        })
-                        .await;
-
-                    if let Err(err) = result {
-                        // TODO: LSP ERROR
-                        return Err(err);
-                    }
-
-                    return Ok(this.update(&mut cx, |this, _| {
-                        this.last_workspace_edits_by_language_server
-                            .remove(&lang_server.server_id())
-                            .unwrap_or_default()
-                    })?);
-                }
-
-                Ok(ProjectTransaction::default())
-            })
-        } else if let Some(project_id) = self.remote_id() {
-            let client = self.client.clone();
-            let request = proto::ApplyCodeAction {
-                project_id,
-                buffer_id: buffer_handle.read(cx).remote_id(),
-                action: Some(language::proto::serialize_code_action(&action)),
-            };
-            cx.spawn(move |this, mut cx| async move {
-                let response = client
-                    .request(request)
-                    .await?
-                    .transaction
-                    .ok_or_else(|| anyhow!("missing transaction"))?;
-                this.update(&mut cx, |this, cx| {
-                    this.deserialize_project_transaction(response, push_to_history, cx)
-                })?
-                .await
-            })
-        } else {
-            Task::ready(Err(anyhow!("project does not have a remote id")))
-        }
-    }
-
-    fn apply_on_type_formatting(
-        &self,
-        buffer: Model<Buffer>,
-        position: Anchor,
-        trigger: String,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Option<Transaction>>> {
-        if self.is_local() {
-            cx.spawn(move |this, mut cx| async move {
-                // Do not allow multiple concurrent formatting requests for the
-                // same buffer.
-                this.update(&mut cx, |this, cx| {
-                    this.buffers_being_formatted
-                        .insert(buffer.read(cx).remote_id())
-                })?;
-
-                let _cleanup = defer({
-                    let this = this.clone();
-                    let mut cx = cx.clone();
-                    let closure_buffer = buffer.clone();
-                    move || {
-                        this.update(&mut cx, |this, cx| {
-                            this.buffers_being_formatted
-                                .remove(&closure_buffer.read(cx).remote_id());
-                        })
-                        .ok();
-                    }
-                });
-
-                buffer
-                    .update(&mut cx, |buffer, _| {
-                        buffer.wait_for_edits(Some(position.timestamp))
-                    })?
-                    .await?;
-                this.update(&mut cx, |this, cx| {
-                    let position = position.to_point_utf16(buffer.read(cx));
-                    this.on_type_format(buffer, position, trigger, false, cx)
-                })?
-                .await
-            })
-        } else if let Some(project_id) = self.remote_id() {
-            let client = self.client.clone();
-            let request = proto::OnTypeFormatting {
-                project_id,
-                buffer_id: buffer.read(cx).remote_id(),
-                position: Some(serialize_anchor(&position)),
-                trigger,
-                version: serialize_version(&buffer.read(cx).version()),
-            };
-            cx.spawn(move |_, _| async move {
-                client
-                    .request(request)
-                    .await?
-                    .transaction
-                    .map(language::proto::deserialize_transaction)
-                    .transpose()
-            })
-        } else {
-            Task::ready(Err(anyhow!("project does not have a remote id")))
-        }
-    }
-
-    async fn deserialize_edits(
-        this: Model<Self>,
-        buffer_to_edit: Model<Buffer>,
-        edits: Vec<lsp::TextEdit>,
-        push_to_history: bool,
-        _: Arc<CachedLspAdapter>,
-        language_server: Arc<LanguageServer>,
-        cx: &mut AsyncAppContext,
-    ) -> Result<Option<Transaction>> {
-        let edits = this
-            .update(cx, |this, cx| {
-                this.edits_from_lsp(
-                    &buffer_to_edit,
-                    edits,
-                    language_server.server_id(),
-                    None,
-                    cx,
-                )
-            })?
-            .await?;
-
-        let transaction = buffer_to_edit.update(cx, |buffer, cx| {
-            buffer.finalize_last_transaction();
-            buffer.start_transaction();
-            for (range, text) in edits {
-                buffer.edit([(range, text)], None, cx);
-            }
-
-            if buffer.end_transaction(cx).is_some() {
-                let transaction = buffer.finalize_last_transaction().unwrap().clone();
-                if !push_to_history {
-                    buffer.forget_transaction(transaction.id);
-                }
-                Some(transaction)
-            } else {
-                None
-            }
-        })?;
-
-        Ok(transaction)
-    }
-
-    async fn deserialize_workspace_edit(
-        this: Model<Self>,
-        edit: lsp::WorkspaceEdit,
-        push_to_history: bool,
-        lsp_adapter: Arc<CachedLspAdapter>,
-        language_server: Arc<LanguageServer>,
-        cx: &mut AsyncAppContext,
-    ) -> Result<ProjectTransaction> {
-        let fs = this.update(cx, |this, _| this.fs.clone())?;
-        let mut operations = Vec::new();
-        if let Some(document_changes) = edit.document_changes {
-            match document_changes {
-                lsp::DocumentChanges::Edits(edits) => {
-                    operations.extend(edits.into_iter().map(lsp::DocumentChangeOperation::Edit))
-                }
-                lsp::DocumentChanges::Operations(ops) => operations = ops,
-            }
-        } else if let Some(changes) = edit.changes {
-            operations.extend(changes.into_iter().map(|(uri, edits)| {
-                lsp::DocumentChangeOperation::Edit(lsp::TextDocumentEdit {
-                    text_document: lsp::OptionalVersionedTextDocumentIdentifier {
-                        uri,
-                        version: None,
-                    },
-                    edits: edits.into_iter().map(OneOf::Left).collect(),
-                })
-            }));
-        }
-
-        let mut project_transaction = ProjectTransaction::default();
-        for operation in operations {
-            match operation {
-                lsp::DocumentChangeOperation::Op(lsp::ResourceOp::Create(op)) => {
-                    let abs_path = op
-                        .uri
-                        .to_file_path()
-                        .map_err(|_| anyhow!("can't convert URI to path"))?;
-
-                    if let Some(parent_path) = abs_path.parent() {
-                        fs.create_dir(parent_path).await?;
-                    }
-                    if abs_path.ends_with("/") {
-                        fs.create_dir(&abs_path).await?;
-                    } else {
-                        fs.create_file(
-                            &abs_path,
-                            op.options
-                                .map(|options| fs::CreateOptions {
-                                    overwrite: options.overwrite.unwrap_or(false),
-                                    ignore_if_exists: options.ignore_if_exists.unwrap_or(false),
-                                })
-                                .unwrap_or_default(),
-                        )
-                        .await?;
-                    }
-                }
-
-                lsp::DocumentChangeOperation::Op(lsp::ResourceOp::Rename(op)) => {
-                    let source_abs_path = op
-                        .old_uri
-                        .to_file_path()
-                        .map_err(|_| anyhow!("can't convert URI to path"))?;
-                    let target_abs_path = op
-                        .new_uri
-                        .to_file_path()
-                        .map_err(|_| anyhow!("can't convert URI to path"))?;
-                    fs.rename(
-                        &source_abs_path,
-                        &target_abs_path,
-                        op.options
-                            .map(|options| fs::RenameOptions {
-                                overwrite: options.overwrite.unwrap_or(false),
-                                ignore_if_exists: options.ignore_if_exists.unwrap_or(false),
-                            })
-                            .unwrap_or_default(),
-                    )
-                    .await?;
-                }
-
-                lsp::DocumentChangeOperation::Op(lsp::ResourceOp::Delete(op)) => {
-                    let abs_path = op
-                        .uri
-                        .to_file_path()
-                        .map_err(|_| anyhow!("can't convert URI to path"))?;
-                    let options = op
-                        .options
-                        .map(|options| fs::RemoveOptions {
-                            recursive: options.recursive.unwrap_or(false),
-                            ignore_if_not_exists: options.ignore_if_not_exists.unwrap_or(false),
-                        })
-                        .unwrap_or_default();
-                    if abs_path.ends_with("/") {
-                        fs.remove_dir(&abs_path, options).await?;
-                    } else {
-                        fs.remove_file(&abs_path, options).await?;
-                    }
-                }
-
-                lsp::DocumentChangeOperation::Edit(op) => {
-                    let buffer_to_edit = this
-                        .update(cx, |this, cx| {
-                            this.open_local_buffer_via_lsp(
-                                op.text_document.uri,
-                                language_server.server_id(),
-                                lsp_adapter.name.clone(),
-                                cx,
-                            )
-                        })?
-                        .await?;
-
-                    let edits = this
-                        .update(cx, |this, cx| {
-                            let edits = op.edits.into_iter().map(|edit| match edit {
-                                OneOf::Left(edit) => edit,
-                                OneOf::Right(edit) => edit.text_edit,
-                            });
-                            this.edits_from_lsp(
-                                &buffer_to_edit,
-                                edits,
-                                language_server.server_id(),
-                                op.text_document.version,
-                                cx,
-                            )
-                        })?
-                        .await?;
-
-                    let transaction = buffer_to_edit.update(cx, |buffer, cx| {
-                        buffer.finalize_last_transaction();
-                        buffer.start_transaction();
-                        for (range, text) in edits {
-                            buffer.edit([(range, text)], None, cx);
-                        }
-                        let transaction = if buffer.end_transaction(cx).is_some() {
-                            let transaction = buffer.finalize_last_transaction().unwrap().clone();
-                            if !push_to_history {
-                                buffer.forget_transaction(transaction.id);
-                            }
-                            Some(transaction)
-                        } else {
-                            None
-                        };
-
-                        transaction
-                    })?;
-                    if let Some(transaction) = transaction {
-                        project_transaction.0.insert(buffer_to_edit, transaction);
-                    }
-                }
-            }
-        }
-
-        Ok(project_transaction)
-    }
-
-    pub fn prepare_rename<T: ToPointUtf16>(
-        &self,
-        buffer: Model<Buffer>,
-        position: T,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Option<Range<Anchor>>>> {
-        let position = position.to_point_utf16(buffer.read(cx));
-        self.request_lsp(
-            buffer,
-            LanguageServerToQuery::Primary,
-            PrepareRename { position },
-            cx,
-        )
-    }
-
-    pub fn perform_rename<T: ToPointUtf16>(
-        &self,
-        buffer: Model<Buffer>,
-        position: T,
-        new_name: String,
-        push_to_history: bool,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<ProjectTransaction>> {
-        let position = position.to_point_utf16(buffer.read(cx));
-        self.request_lsp(
-            buffer,
-            LanguageServerToQuery::Primary,
-            PerformRename {
-                position,
-                new_name,
-                push_to_history,
-            },
-            cx,
-        )
-    }
-
-    pub fn on_type_format<T: ToPointUtf16>(
-        &self,
-        buffer: Model<Buffer>,
-        position: T,
-        trigger: String,
-        push_to_history: bool,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Option<Transaction>>> {
-        let (position, tab_size) = buffer.update(cx, |buffer, cx| {
-            let position = position.to_point_utf16(buffer);
-            (
-                position,
-                language_settings(buffer.language_at(position).as_ref(), buffer.file(), cx)
-                    .tab_size,
-            )
-        });
-        self.request_lsp(
-            buffer.clone(),
-            LanguageServerToQuery::Primary,
-            OnTypeFormatting {
-                position,
-                trigger,
-                options: lsp_command::lsp_formatting_options(tab_size.get()).into(),
-                push_to_history,
-            },
-            cx,
-        )
-    }
-
-    pub fn inlay_hints<T: ToOffset>(
-        &self,
-        buffer_handle: Model<Buffer>,
-        range: Range<T>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<anyhow::Result<Vec<InlayHint>>> {
-        let buffer = buffer_handle.read(cx);
-        let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
-        let range_start = range.start;
-        let range_end = range.end;
-        let buffer_id = buffer.remote_id();
-        let buffer_version = buffer.version().clone();
-        let lsp_request = InlayHints { range };
-
-        if self.is_local() {
-            let lsp_request_task = self.request_lsp(
-                buffer_handle.clone(),
-                LanguageServerToQuery::Primary,
-                lsp_request,
-                cx,
-            );
-            cx.spawn(move |_, mut cx| async move {
-                buffer_handle
-                    .update(&mut cx, |buffer, _| {
-                        buffer.wait_for_edits(vec![range_start.timestamp, range_end.timestamp])
-                    })?
-                    .await
-                    .context("waiting for inlay hint request range edits")?;
-                lsp_request_task.await.context("inlay hints LSP request")
-            })
-        } else if let Some(project_id) = self.remote_id() {
-            let client = self.client.clone();
-            let request = proto::InlayHints {
-                project_id,
-                buffer_id,
-                start: Some(serialize_anchor(&range_start)),
-                end: Some(serialize_anchor(&range_end)),
-                version: serialize_version(&buffer_version),
-            };
-            cx.spawn(move |project, cx| async move {
-                let response = client
-                    .request(request)
-                    .await
-                    .context("inlay hints proto request")?;
-                let hints_request_result = LspCommand::response_from_proto(
-                    lsp_request,
-                    response,
-                    project.upgrade().ok_or_else(|| anyhow!("No project"))?,
-                    buffer_handle.clone(),
-                    cx,
-                )
-                .await;
-
-                hints_request_result.context("inlay hints proto response conversion")
-            })
-        } else {
-            Task::ready(Err(anyhow!("project does not have a remote id")))
-        }
-    }
-
-    pub fn resolve_inlay_hint(
-        &self,
-        hint: InlayHint,
-        buffer_handle: Model<Buffer>,
-        server_id: LanguageServerId,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<anyhow::Result<InlayHint>> {
-        if self.is_local() {
-            let buffer = buffer_handle.read(cx);
-            let (_, lang_server) = if let Some((adapter, server)) =
-                self.language_server_for_buffer(buffer, server_id, cx)
-            {
-                (adapter.clone(), server.clone())
-            } else {
-                return Task::ready(Ok(hint));
-            };
-            if !InlayHints::can_resolve_inlays(lang_server.capabilities()) {
-                return Task::ready(Ok(hint));
-            }
-
-            let buffer_snapshot = buffer.snapshot();
-            cx.spawn(move |_, mut cx| async move {
-                let resolve_task = lang_server.request::<lsp::request::InlayHintResolveRequest>(
-                    InlayHints::project_to_lsp_hint(hint, &buffer_snapshot),
-                );
-                let resolved_hint = resolve_task
-                    .await
-                    .context("inlay hint resolve LSP request")?;
-                let resolved_hint = InlayHints::lsp_to_project_hint(
-                    resolved_hint,
-                    &buffer_handle,
-                    server_id,
-                    ResolveState::Resolved,
-                    false,
-                    &mut cx,
-                )
-                .await?;
-                Ok(resolved_hint)
-            })
-        } else if let Some(project_id) = self.remote_id() {
-            let client = self.client.clone();
-            let request = proto::ResolveInlayHint {
-                project_id,
-                buffer_id: buffer_handle.read(cx).remote_id(),
-                language_server_id: server_id.0 as u64,
-                hint: Some(InlayHints::project_to_proto_hint(hint.clone())),
-            };
-            cx.spawn(move |_, _| async move {
-                let response = client
-                    .request(request)
-                    .await
-                    .context("inlay hints proto request")?;
-                match response.hint {
-                    Some(resolved_hint) => InlayHints::proto_to_project_hint(resolved_hint)
-                        .context("inlay hints proto resolve response conversion"),
-                    None => Ok(hint),
-                }
-            })
-        } else {
-            Task::ready(Err(anyhow!("project does not have a remote id")))
-        }
-    }
-
-    #[allow(clippy::type_complexity)]
-    pub fn search(
-        &self,
-        query: SearchQuery,
-        cx: &mut ModelContext<Self>,
-    ) -> Receiver<(Model<Buffer>, Vec<Range<Anchor>>)> {
-        if self.is_local() {
-            self.search_local(query, cx)
-        } else if let Some(project_id) = self.remote_id() {
-            let (tx, rx) = smol::channel::unbounded();
-            let request = self.client.request(query.to_proto(project_id));
-            cx.spawn(move |this, mut cx| async move {
-                let response = request.await?;
-                let mut result = HashMap::default();
-                for location in response.locations {
-                    let target_buffer = this
-                        .update(&mut cx, |this, cx| {
-                            this.wait_for_remote_buffer(location.buffer_id, cx)
-                        })?
-                        .await?;
-                    let start = location
-                        .start
-                        .and_then(deserialize_anchor)
-                        .ok_or_else(|| anyhow!("missing target start"))?;
-                    let end = location
-                        .end
-                        .and_then(deserialize_anchor)
-                        .ok_or_else(|| anyhow!("missing target end"))?;
-                    result
-                        .entry(target_buffer)
-                        .or_insert(Vec::new())
-                        .push(start..end)
-                }
-                for (buffer, ranges) in result {
-                    let _ = tx.send((buffer, ranges)).await;
-                }
-                Result::<(), anyhow::Error>::Ok(())
-            })
-            .detach_and_log_err(cx);
-            rx
-        } else {
-            unimplemented!();
-        }
-    }
-
-    pub fn search_local(
-        &self,
-        query: SearchQuery,
-        cx: &mut ModelContext<Self>,
-    ) -> Receiver<(Model<Buffer>, Vec<Range<Anchor>>)> {
-        // Local search is split into several phases.
-        // TL;DR is that we do 2 passes; initial pass to pick files which contain at least one match
-        // and the second phase that finds positions of all the matches found in the candidate files.
-        // The Receiver obtained from this function returns matches sorted by buffer path. Files without a buffer path are reported first.
-        //
-        // It gets a bit hairy though, because we must account for files that do not have a persistent representation
-        // on FS. Namely, if you have an untitled buffer or unsaved changes in a buffer, we want to scan that too.
-        //
-        // 1. We initialize a queue of match candidates and feed all opened buffers into it (== unsaved files / untitled buffers).
-        //    Then, we go through a worktree and check for files that do match a predicate. If the file had an opened version, we skip the scan
-        //    of FS version for that file altogether - after all, what we have in memory is more up-to-date than what's in FS.
-        // 2. At this point, we have a list of all potentially matching buffers/files.
-        //    We sort that list by buffer path - this list is retained for later use.
-        //    We ensure that all buffers are now opened and available in project.
-        // 3. We run a scan over all the candidate buffers on multiple background threads.
-        //    We cannot assume that there will even be a match - while at least one match
-        //    is guaranteed for files obtained from FS, the buffers we got from memory (unsaved files/unnamed buffers) might not have a match at all.
-        //    There is also an auxilliary background thread responsible for result gathering.
-        //    This is where the sorted list of buffers comes into play to maintain sorted order; Whenever this background thread receives a notification (buffer has/doesn't have matches),
-        //    it keeps it around. It reports matches in sorted order, though it accepts them in unsorted order as well.
-        //    As soon as the match info on next position in sorted order becomes available, it reports it (if it's a match) or skips to the next
-        //    entry - which might already be available thanks to out-of-order processing.
-        //
-        // We could also report matches fully out-of-order, without maintaining a sorted list of matching paths.
-        // This however would mean that project search (that is the main user of this function) would have to do the sorting itself, on the go.
-        // This isn't as straightforward as running an insertion sort sadly, and would also mean that it would have to care about maintaining match index
-        // in face of constantly updating list of sorted matches.
-        // Meanwhile, this implementation offers index stability, since the matches are already reported in a sorted order.
-        let snapshots = self
-            .visible_worktrees(cx)
-            .filter_map(|tree| {
-                let tree = tree.read(cx).as_local()?;
-                Some(tree.snapshot())
-            })
-            .collect::<Vec<_>>();
-
-        let background = cx.background_executor().clone();
-        let path_count: usize = snapshots
-            .iter()
-            .map(|s| {
-                if query.include_ignored() {
-                    s.file_count()
-                } else {
-                    s.visible_file_count()
-                }
-            })
-            .sum();
-        if path_count == 0 {
-            let (_, rx) = smol::channel::bounded(1024);
-            return rx;
-        }
-        let workers = background.num_cpus().min(path_count);
-        let (matching_paths_tx, matching_paths_rx) = smol::channel::bounded(1024);
-        let mut unnamed_files = vec![];
-        let opened_buffers = self
-            .opened_buffers
-            .iter()
-            .filter_map(|(_, b)| {
-                let buffer = b.upgrade()?;
-                let (is_ignored, snapshot) = buffer.update(cx, |buffer, cx| {
-                    let is_ignored = buffer
-                        .project_path(cx)
-                        .and_then(|path| self.entry_for_path(&path, cx))
-                        .map_or(false, |entry| entry.is_ignored);
-                    (is_ignored, buffer.snapshot())
-                });
-                if is_ignored && !query.include_ignored() {
-                    return None;
-                } else if let Some(path) = snapshot.file().map(|file| file.path()) {
-                    Some((path.clone(), (buffer, snapshot)))
-                } else {
-                    unnamed_files.push(buffer);
-                    None
-                }
-            })
-            .collect();
-        cx.background_executor()
-            .spawn(Self::background_search(
-                unnamed_files,
-                opened_buffers,
-                cx.background_executor().clone(),
-                self.fs.clone(),
-                workers,
-                query.clone(),
-                path_count,
-                snapshots,
-                matching_paths_tx,
-            ))
-            .detach();
-
-        let (buffers, buffers_rx) = Self::sort_candidates_and_open_buffers(matching_paths_rx, cx);
-        let background = cx.background_executor().clone();
-        let (result_tx, result_rx) = smol::channel::bounded(1024);
-        cx.background_executor()
-            .spawn(async move {
-                let Ok(buffers) = buffers.await else {
-                    return;
-                };
-
-                let buffers_len = buffers.len();
-                if buffers_len == 0 {
-                    return;
-                }
-                let query = &query;
-                let (finished_tx, mut finished_rx) = smol::channel::unbounded();
-                background
-                    .scoped(|scope| {
-                        #[derive(Clone)]
-                        struct FinishedStatus {
-                            entry: Option<(Model<Buffer>, Vec<Range<Anchor>>)>,
-                            buffer_index: SearchMatchCandidateIndex,
-                        }
-
-                        for _ in 0..workers {
-                            let finished_tx = finished_tx.clone();
-                            let mut buffers_rx = buffers_rx.clone();
-                            scope.spawn(async move {
-                                while let Some((entry, buffer_index)) = buffers_rx.next().await {
-                                    let buffer_matches = if let Some((_, snapshot)) = entry.as_ref()
-                                    {
-                                        if query.file_matches(
-                                            snapshot.file().map(|file| file.path().as_ref()),
-                                        ) {
-                                            query
-                                                .search(&snapshot, None)
-                                                .await
-                                                .iter()
-                                                .map(|range| {
-                                                    snapshot.anchor_before(range.start)
-                                                        ..snapshot.anchor_after(range.end)
-                                                })
-                                                .collect()
-                                        } else {
-                                            Vec::new()
-                                        }
-                                    } else {
-                                        Vec::new()
-                                    };
-
-                                    let status = if !buffer_matches.is_empty() {
-                                        let entry = if let Some((buffer, _)) = entry.as_ref() {
-                                            Some((buffer.clone(), buffer_matches))
-                                        } else {
-                                            None
-                                        };
-                                        FinishedStatus {
-                                            entry,
-                                            buffer_index,
-                                        }
-                                    } else {
-                                        FinishedStatus {
-                                            entry: None,
-                                            buffer_index,
-                                        }
-                                    };
-                                    if finished_tx.send(status).await.is_err() {
-                                        break;
-                                    }
-                                }
-                            });
-                        }
-                        // Report sorted matches
-                        scope.spawn(async move {
-                            let mut current_index = 0;
-                            let mut scratch = vec![None; buffers_len];
-                            while let Some(status) = finished_rx.next().await {
-                                debug_assert!(
-                                    scratch[status.buffer_index].is_none(),
-                                    "Got match status of position {} twice",
-                                    status.buffer_index
-                                );
-                                let index = status.buffer_index;
-                                scratch[index] = Some(status);
-                                while current_index < buffers_len {
-                                    let Some(current_entry) = scratch[current_index].take() else {
-                                        // We intentionally **do not** increment `current_index` here. When next element arrives
-                                        // from `finished_rx`, we will inspect the same position again, hoping for it to be Some(_)
-                                        // this time.
-                                        break;
-                                    };
-                                    if let Some(entry) = current_entry.entry {
-                                        result_tx.send(entry).await.log_err();
-                                    }
-                                    current_index += 1;
-                                }
-                                if current_index == buffers_len {
-                                    break;
-                                }
-                            }
-                        });
-                    })
-                    .await;
-            })
-            .detach();
-        result_rx
-    }
-
-    /// Pick paths that might potentially contain a match of a given search query.
-    async fn background_search(
-        unnamed_buffers: Vec<Model<Buffer>>,
-        opened_buffers: HashMap<Arc<Path>, (Model<Buffer>, BufferSnapshot)>,
-        executor: BackgroundExecutor,
-        fs: Arc<dyn Fs>,
-        workers: usize,
-        query: SearchQuery,
-        path_count: usize,
-        snapshots: Vec<LocalSnapshot>,
-        matching_paths_tx: Sender<SearchMatchCandidate>,
-    ) {
-        let fs = &fs;
-        let query = &query;
-        let matching_paths_tx = &matching_paths_tx;
-        let snapshots = &snapshots;
-        let paths_per_worker = (path_count + workers - 1) / workers;
-        for buffer in unnamed_buffers {
-            matching_paths_tx
-                .send(SearchMatchCandidate::OpenBuffer {
-                    buffer: buffer.clone(),
-                    path: None,
-                })
-                .await
-                .log_err();
-        }
-        for (path, (buffer, _)) in opened_buffers.iter() {
-            matching_paths_tx
-                .send(SearchMatchCandidate::OpenBuffer {
-                    buffer: buffer.clone(),
-                    path: Some(path.clone()),
-                })
-                .await
-                .log_err();
-        }
-        executor
-            .scoped(|scope| {
-                let max_concurrent_workers = Arc::new(Semaphore::new(workers));
-
-                for worker_ix in 0..workers {
-                    let worker_start_ix = worker_ix * paths_per_worker;
-                    let worker_end_ix = worker_start_ix + paths_per_worker;
-                    let unnamed_buffers = opened_buffers.clone();
-                    let limiter = Arc::clone(&max_concurrent_workers);
-                    scope.spawn(async move {
-                        let _guard = limiter.acquire().await;
-                        let mut snapshot_start_ix = 0;
-                        let mut abs_path = PathBuf::new();
-                        for snapshot in snapshots {
-                            let snapshot_end_ix = snapshot_start_ix
-                                + if query.include_ignored() {
-                                    snapshot.file_count()
-                                } else {
-                                    snapshot.visible_file_count()
-                                };
-                            if worker_end_ix <= snapshot_start_ix {
-                                break;
-                            } else if worker_start_ix > snapshot_end_ix {
-                                snapshot_start_ix = snapshot_end_ix;
-                                continue;
-                            } else {
-                                let start_in_snapshot =
-                                    worker_start_ix.saturating_sub(snapshot_start_ix);
-                                let end_in_snapshot =
-                                    cmp::min(worker_end_ix, snapshot_end_ix) - snapshot_start_ix;
-
-                                for entry in snapshot
-                                    .files(query.include_ignored(), start_in_snapshot)
-                                    .take(end_in_snapshot - start_in_snapshot)
-                                {
-                                    if matching_paths_tx.is_closed() {
-                                        break;
-                                    }
-                                    if unnamed_buffers.contains_key(&entry.path) {
-                                        continue;
-                                    }
-                                    let matches = if query.file_matches(Some(&entry.path)) {
-                                        abs_path.clear();
-                                        abs_path.push(&snapshot.abs_path());
-                                        abs_path.push(&entry.path);
-                                        if let Some(file) = fs.open_sync(&abs_path).await.log_err()
-                                        {
-                                            query.detect(file).unwrap_or(false)
-                                        } else {
-                                            false
-                                        }
-                                    } else {
-                                        false
-                                    };
-
-                                    if matches {
-                                        let project_path = SearchMatchCandidate::Path {
-                                            worktree_id: snapshot.id(),
-                                            path: entry.path.clone(),
-                                            is_ignored: entry.is_ignored,
-                                        };
-                                        if matching_paths_tx.send(project_path).await.is_err() {
-                                            break;
-                                        }
-                                    }
-                                }
-
-                                snapshot_start_ix = snapshot_end_ix;
-                            }
-                        }
-                    });
-                }
-
-                if query.include_ignored() {
-                    for snapshot in snapshots {
-                        for ignored_entry in snapshot
-                            .entries(query.include_ignored())
-                            .filter(|e| e.is_ignored)
-                        {
-                            let limiter = Arc::clone(&max_concurrent_workers);
-                            scope.spawn(async move {
-                                let _guard = limiter.acquire().await;
-                                let mut ignored_paths_to_process =
-                                    VecDeque::from([snapshot.abs_path().join(&ignored_entry.path)]);
-                                while let Some(ignored_abs_path) =
-                                    ignored_paths_to_process.pop_front()
-                                {
-                                    if let Some(fs_metadata) = fs
-                                        .metadata(&ignored_abs_path)
-                                        .await
-                                        .with_context(|| {
-                                            format!("fetching fs metadata for {ignored_abs_path:?}")
-                                        })
-                                        .log_err()
-                                        .flatten()
-                                    {
-                                        if fs_metadata.is_dir {
-                                            if let Some(mut subfiles) = fs
-                                                .read_dir(&ignored_abs_path)
-                                                .await
-                                                .with_context(|| {
-                                                    format!(
-                                                        "listing ignored path {ignored_abs_path:?}"
-                                                    )
-                                                })
-                                                .log_err()
-                                            {
-                                                while let Some(subfile) = subfiles.next().await {
-                                                    if let Some(subfile) = subfile.log_err() {
-                                                        ignored_paths_to_process.push_back(subfile);
-                                                    }
-                                                }
-                                            }
-                                        } else if !fs_metadata.is_symlink {
-                                            if !query.file_matches(Some(&ignored_abs_path))
-                                                || snapshot.is_path_excluded(
-                                                    ignored_entry.path.to_path_buf(),
-                                                )
-                                            {
-                                                continue;
-                                            }
-                                            let matches = if let Some(file) = fs
-                                                .open_sync(&ignored_abs_path)
-                                                .await
-                                                .with_context(|| {
-                                                    format!(
-                                                        "Opening ignored path {ignored_abs_path:?}"
-                                                    )
-                                                })
-                                                .log_err()
-                                            {
-                                                query.detect(file).unwrap_or(false)
-                                            } else {
-                                                false
-                                            };
-                                            if matches {
-                                                let project_path = SearchMatchCandidate::Path {
-                                                    worktree_id: snapshot.id(),
-                                                    path: Arc::from(
-                                                        ignored_abs_path
-                                                            .strip_prefix(snapshot.abs_path())
-                                                            .expect(
-                                                                "scanning worktree-related files",
-                                                            ),
-                                                    ),
-                                                    is_ignored: true,
-                                                };
-                                                if matching_paths_tx
-                                                    .send(project_path)
-                                                    .await
-                                                    .is_err()
-                                                {
-                                                    return;
-                                                }
-                                            }
-                                        }
-                                    }
-                                }
-                            });
-                        }
-                    }
-                }
-            })
-            .await;
-    }
-
-    pub fn request_lsp<R: LspCommand>(
-        &self,
-        buffer_handle: Model<Buffer>,
-        server: LanguageServerToQuery,
-        request: R,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<R::Response>>
-    where
-        <R::LspRequest as lsp::request::Request>::Result: Send,
-        <R::LspRequest as lsp::request::Request>::Params: Send,
-    {
-        let buffer = buffer_handle.read(cx);
-        if self.is_local() {
-            let language_server = match server {
-                LanguageServerToQuery::Primary => {
-                    match self.primary_language_server_for_buffer(buffer, cx) {
-                        Some((_, server)) => Some(Arc::clone(server)),
-                        None => return Task::ready(Ok(Default::default())),
-                    }
-                }
-                LanguageServerToQuery::Other(id) => self
-                    .language_server_for_buffer(buffer, id, cx)
-                    .map(|(_, server)| Arc::clone(server)),
-            };
-            let file = File::from_dyn(buffer.file()).and_then(File::as_local);
-            if let (Some(file), Some(language_server)) = (file, language_server) {
-                let lsp_params = request.to_lsp(&file.abs_path(cx), buffer, &language_server, cx);
-                return cx.spawn(move |this, cx| async move {
-                    if !request.check_capabilities(language_server.capabilities()) {
-                        return Ok(Default::default());
-                    }
-
-                    let result = language_server.request::<R::LspRequest>(lsp_params).await;
-                    let response = match result {
-                        Ok(response) => response,
-
-                        Err(err) => {
-                            log::warn!(
-                                "Generic lsp request to {} failed: {}",
-                                language_server.name(),
-                                err
-                            );
-                            return Err(err);
-                        }
-                    };
-
-                    request
-                        .response_from_lsp(
-                            response,
-                            this.upgrade().ok_or_else(|| anyhow!("no app context"))?,
-                            buffer_handle,
-                            language_server.server_id(),
-                            cx,
-                        )
-                        .await
-                });
-            }
-        } else if let Some(project_id) = self.remote_id() {
-            return self.send_lsp_proto_request(buffer_handle, project_id, request, cx);
-        }
-
-        Task::ready(Ok(Default::default()))
-    }
-
-    fn send_lsp_proto_request<R: LspCommand>(
-        &self,
-        buffer: Model<Buffer>,
-        project_id: u64,
-        request: R,
-        cx: &mut ModelContext<'_, Project>,
-    ) -> Task<anyhow::Result<<R as LspCommand>::Response>> {
-        let rpc = self.client.clone();
-        let message = request.to_proto(project_id, buffer.read(cx));
-        cx.spawn(move |this, mut cx| async move {
-            // Ensure the project is still alive by the time the task
-            // is scheduled.
-            this.upgrade().context("project dropped")?;
-            let response = rpc.request(message).await?;
-            let this = this.upgrade().context("project dropped")?;
-            if this.update(&mut cx, |this, _| this.is_read_only())? {
-                Err(anyhow!("disconnected before completing request"))
-            } else {
-                request
-                    .response_from_proto(response, this, buffer, cx)
-                    .await
-            }
-        })
-    }
-
-    fn sort_candidates_and_open_buffers(
-        mut matching_paths_rx: Receiver<SearchMatchCandidate>,
-        cx: &mut ModelContext<Self>,
-    ) -> (
-        futures::channel::oneshot::Receiver<Vec<SearchMatchCandidate>>,
-        Receiver<(
-            Option<(Model<Buffer>, BufferSnapshot)>,
-            SearchMatchCandidateIndex,
-        )>,
-    ) {
-        let (buffers_tx, buffers_rx) = smol::channel::bounded(1024);
-        let (sorted_buffers_tx, sorted_buffers_rx) = futures::channel::oneshot::channel();
-        cx.spawn(move |this, cx| async move {
-            let mut buffers = Vec::new();
-            let mut ignored_buffers = Vec::new();
-            while let Some(entry) = matching_paths_rx.next().await {
-                if matches!(
-                    entry,
-                    SearchMatchCandidate::Path {
-                        is_ignored: true,
-                        ..
-                    }
-                ) {
-                    ignored_buffers.push(entry);
-                } else {
-                    buffers.push(entry);
-                }
-            }
-            buffers.sort_by_key(|candidate| candidate.path());
-            ignored_buffers.sort_by_key(|candidate| candidate.path());
-            buffers.extend(ignored_buffers);
-            let matching_paths = buffers.clone();
-            let _ = sorted_buffers_tx.send(buffers);
-            for (index, candidate) in matching_paths.into_iter().enumerate() {
-                if buffers_tx.is_closed() {
-                    break;
-                }
-                let this = this.clone();
-                let buffers_tx = buffers_tx.clone();
-                cx.spawn(move |mut cx| async move {
-                    let buffer = match candidate {
-                        SearchMatchCandidate::OpenBuffer { buffer, .. } => Some(buffer),
-                        SearchMatchCandidate::Path {
-                            worktree_id, path, ..
-                        } => this
-                            .update(&mut cx, |this, cx| {
-                                this.open_buffer((worktree_id, path), cx)
-                            })?
-                            .await
-                            .log_err(),
-                    };
-                    if let Some(buffer) = buffer {
-                        let snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot())?;
-                        buffers_tx
-                            .send((Some((buffer, snapshot)), index))
-                            .await
-                            .log_err();
-                    } else {
-                        buffers_tx.send((None, index)).await.log_err();
-                    }
-
-                    Ok::<_, anyhow::Error>(())
-                })
-                .detach();
-            }
-        })
-        .detach();
-        (sorted_buffers_rx, buffers_rx)
-    }
-
-    pub fn find_or_create_local_worktree(
-        &mut self,
-        abs_path: impl AsRef<Path>,
-        visible: bool,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<(Model<Worktree>, PathBuf)>> {
-        let abs_path = abs_path.as_ref();
-        if let Some((tree, relative_path)) = self.find_local_worktree(abs_path, cx) {
-            Task::ready(Ok((tree, relative_path)))
-        } else {
-            let worktree = self.create_local_worktree(abs_path, visible, cx);
-            cx.background_executor()
-                .spawn(async move { Ok((worktree.await?, PathBuf::new())) })
-        }
-    }
-
-    pub fn find_local_worktree(
-        &self,
-        abs_path: &Path,
-        cx: &AppContext,
-    ) -> Option<(Model<Worktree>, PathBuf)> {
-        for tree in &self.worktrees {
-            if let Some(tree) = tree.upgrade() {
-                if let Some(relative_path) = tree
-                    .read(cx)
-                    .as_local()
-                    .and_then(|t| abs_path.strip_prefix(t.abs_path()).ok())
-                {
-                    return Some((tree.clone(), relative_path.into()));
-                }
-            }
-        }
-        None
-    }
-
-    pub fn is_shared(&self) -> bool {
-        match &self.client_state {
-            Some(ProjectClientState::Local { .. }) => true,
-            _ => false,
-        }
-    }
-
-    fn create_local_worktree(
-        &mut self,
-        abs_path: impl AsRef<Path>,
-        visible: bool,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Model<Worktree>>> {
-        let fs = self.fs.clone();
-        let client = self.client.clone();
-        let next_entry_id = self.next_entry_id.clone();
-        let path: Arc<Path> = abs_path.as_ref().into();
-        let task = self
-            .loading_local_worktrees
-            .entry(path.clone())
-            .or_insert_with(|| {
-                cx.spawn(move |project, mut cx| {
-                    async move {
-                        let worktree = Worktree::local(
-                            client.clone(),
-                            path.clone(),
-                            visible,
-                            fs,
-                            next_entry_id,
-                            &mut cx,
-                        )
-                        .await;
-
-                        project.update(&mut cx, |project, _| {
-                            project.loading_local_worktrees.remove(&path);
-                        })?;
-
-                        let worktree = worktree?;
-                        project
-                            .update(&mut cx, |project, cx| project.add_worktree(&worktree, cx))?;
-                        Ok(worktree)
-                    }
-                    .map_err(Arc::new)
-                })
-                .shared()
-            })
-            .clone();
-        cx.background_executor().spawn(async move {
-            match task.await {
-                Ok(worktree) => Ok(worktree),
-                Err(err) => Err(anyhow!("{}", err)),
-            }
-        })
-    }
-
-    pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut ModelContext<Self>) {
-        self.worktrees.retain(|worktree| {
-            if let Some(worktree) = worktree.upgrade() {
-                let id = worktree.read(cx).id();
-                if id == id_to_remove {
-                    cx.emit(Event::WorktreeRemoved(id));
-                    false
-                } else {
-                    true
-                }
-            } else {
-                false
-            }
-        });
-        self.metadata_changed(cx);
-    }
-
-    fn add_worktree(&mut self, worktree: &Model<Worktree>, cx: &mut ModelContext<Self>) {
-        cx.observe(worktree, |_, _, cx| cx.notify()).detach();
-        if worktree.read(cx).is_local() {
-            cx.subscribe(worktree, |this, worktree, event, cx| match event {
-                worktree::Event::UpdatedEntries(changes) => {
-                    this.update_local_worktree_buffers(&worktree, changes, cx);
-                    this.update_local_worktree_language_servers(&worktree, changes, cx);
-                    this.update_local_worktree_settings(&worktree, changes, cx);
-                    this.update_prettier_settings(&worktree, changes, cx);
-                    cx.emit(Event::WorktreeUpdatedEntries(
-                        worktree.read(cx).id(),
-                        changes.clone(),
-                    ));
-                }
-                worktree::Event::UpdatedGitRepositories(updated_repos) => {
-                    this.update_local_worktree_buffers_git_repos(worktree, updated_repos, cx)
-                }
-            })
-            .detach();
-        }
-
-        let push_strong_handle = {
-            let worktree = worktree.read(cx);
-            self.is_shared() || worktree.is_visible() || worktree.is_remote()
-        };
-        if push_strong_handle {
-            self.worktrees
-                .push(WorktreeHandle::Strong(worktree.clone()));
-        } else {
-            self.worktrees
-                .push(WorktreeHandle::Weak(worktree.downgrade()));
-        }
-
-        let handle_id = worktree.entity_id();
-        cx.observe_release(worktree, move |this, worktree, cx| {
-            let _ = this.remove_worktree(worktree.id(), cx);
-            cx.update_global::<SettingsStore, _>(|store, cx| {
-                store
-                    .clear_local_settings(handle_id.as_u64() as usize, cx)
-                    .log_err()
-            });
-        })
-        .detach();
-
-        cx.emit(Event::WorktreeAdded);
-        self.metadata_changed(cx);
-    }
-
-    fn update_local_worktree_buffers(
-        &mut self,
-        worktree_handle: &Model<Worktree>,
-        changes: &[(Arc<Path>, ProjectEntryId, PathChange)],
-        cx: &mut ModelContext<Self>,
-    ) {
-        let snapshot = worktree_handle.read(cx).snapshot();
-
-        let mut renamed_buffers = Vec::new();
-        for (path, entry_id, _) in changes {
-            let worktree_id = worktree_handle.read(cx).id();
-            let project_path = ProjectPath {
-                worktree_id,
-                path: path.clone(),
-            };
-
-            let buffer_id = match self.local_buffer_ids_by_entry_id.get(entry_id) {
-                Some(&buffer_id) => buffer_id,
-                None => match self.local_buffer_ids_by_path.get(&project_path) {
-                    Some(&buffer_id) => buffer_id,
-                    None => {
-                        continue;
-                    }
-                },
-            };
-
-            let open_buffer = self.opened_buffers.get(&buffer_id);
-            let buffer = if let Some(buffer) = open_buffer.and_then(|buffer| buffer.upgrade()) {
-                buffer
-            } else {
-                self.opened_buffers.remove(&buffer_id);
-                self.local_buffer_ids_by_path.remove(&project_path);
-                self.local_buffer_ids_by_entry_id.remove(entry_id);
-                continue;
-            };
-
-            buffer.update(cx, |buffer, cx| {
-                if let Some(old_file) = File::from_dyn(buffer.file()) {
-                    if old_file.worktree != *worktree_handle {
-                        return;
-                    }
-
-                    let new_file = if let Some(entry) = old_file
-                        .entry_id
-                        .and_then(|entry_id| snapshot.entry_for_id(entry_id))
-                    {
-                        File {
-                            is_local: true,
-                            entry_id: Some(entry.id),
-                            mtime: entry.mtime,
-                            path: entry.path.clone(),
-                            worktree: worktree_handle.clone(),
-                            is_deleted: false,
-                        }
-                    } else if let Some(entry) = snapshot.entry_for_path(old_file.path().as_ref()) {
-                        File {
-                            is_local: true,
-                            entry_id: Some(entry.id),
-                            mtime: entry.mtime,
-                            path: entry.path.clone(),
-                            worktree: worktree_handle.clone(),
-                            is_deleted: false,
-                        }
-                    } else {
-                        File {
-                            is_local: true,
-                            entry_id: old_file.entry_id,
-                            path: old_file.path().clone(),
-                            mtime: old_file.mtime(),
-                            worktree: worktree_handle.clone(),
-                            is_deleted: true,
-                        }
-                    };
-
-                    let old_path = old_file.abs_path(cx);
-                    if new_file.abs_path(cx) != old_path {
-                        renamed_buffers.push((cx.handle(), old_file.clone()));
-                        self.local_buffer_ids_by_path.remove(&project_path);
-                        self.local_buffer_ids_by_path.insert(
-                            ProjectPath {
-                                worktree_id,
-                                path: path.clone(),
-                            },
-                            buffer_id,
-                        );
-                    }
-
-                    if new_file.entry_id != Some(*entry_id) {
-                        self.local_buffer_ids_by_entry_id.remove(entry_id);
-                        if let Some(entry_id) = new_file.entry_id {
-                            self.local_buffer_ids_by_entry_id
-                                .insert(entry_id, buffer_id);
-                        }
-                    }
-
-                    if new_file != *old_file {
-                        if let Some(project_id) = self.remote_id() {
-                            self.client
-                                .send(proto::UpdateBufferFile {
-                                    project_id,
-                                    buffer_id: buffer_id as u64,
-                                    file: Some(new_file.to_proto()),
-                                })
-                                .log_err();
-                        }
-
-                        buffer.file_updated(Arc::new(new_file), cx);
-                    }
-                }
-            });
-        }
-
-        for (buffer, old_file) in renamed_buffers {
-            self.unregister_buffer_from_language_servers(&buffer, &old_file, cx);
-            self.detect_language_for_buffer(&buffer, cx);
-            self.register_buffer_with_language_servers(&buffer, cx);
-        }
-    }
-
-    fn update_local_worktree_language_servers(
-        &mut self,
-        worktree_handle: &Model<Worktree>,
-        changes: &[(Arc<Path>, ProjectEntryId, PathChange)],
-        cx: &mut ModelContext<Self>,
-    ) {
-        if changes.is_empty() {
-            return;
-        }
-
-        let worktree_id = worktree_handle.read(cx).id();
-        let mut language_server_ids = self
-            .language_server_ids
-            .iter()
-            .filter_map(|((server_worktree_id, _), server_id)| {
-                (*server_worktree_id == worktree_id).then_some(*server_id)
-            })
-            .collect::<Vec<_>>();
-        language_server_ids.sort();
-        language_server_ids.dedup();
-
-        let abs_path = worktree_handle.read(cx).abs_path();
-        for server_id in &language_server_ids {
-            if let Some(LanguageServerState::Running {
-                server,
-                watched_paths,
-                ..
-            }) = self.language_servers.get(server_id)
-            {
-                if let Some(watched_paths) = watched_paths.get(&worktree_id) {
-                    let params = lsp::DidChangeWatchedFilesParams {
-                        changes: changes
-                            .iter()
-                            .filter_map(|(path, _, change)| {
-                                if !watched_paths.is_match(&path) {
-                                    return None;
-                                }
-                                let typ = match change {
-                                    PathChange::Loaded => return None,
-                                    PathChange::Added => lsp::FileChangeType::CREATED,
-                                    PathChange::Removed => lsp::FileChangeType::DELETED,
-                                    PathChange::Updated => lsp::FileChangeType::CHANGED,
-                                    PathChange::AddedOrUpdated => lsp::FileChangeType::CHANGED,
-                                };
-                                Some(lsp::FileEvent {
-                                    uri: lsp::Url::from_file_path(abs_path.join(path)).unwrap(),
-                                    typ,
-                                })
-                            })
-                            .collect(),
-                    };
-
-                    if !params.changes.is_empty() {
-                        server
-                            .notify::<lsp::notification::DidChangeWatchedFiles>(params)
-                            .log_err();
-                    }
-                }
-            }
-        }
-    }
-
-    fn update_local_worktree_buffers_git_repos(
-        &mut self,
-        worktree_handle: Model<Worktree>,
-        changed_repos: &UpdatedGitRepositoriesSet,
-        cx: &mut ModelContext<Self>,
-    ) {
-        debug_assert!(worktree_handle.read(cx).is_local());
-
-        // Identify the loading buffers whose containing repository that has changed.
-        let future_buffers = self
-            .loading_buffers_by_path
-            .iter()
-            .filter_map(|(project_path, receiver)| {
-                if project_path.worktree_id != worktree_handle.read(cx).id() {
-                    return None;
-                }
-                let path = &project_path.path;
-                changed_repos
-                    .iter()
-                    .find(|(work_dir, _)| path.starts_with(work_dir))?;
-                let receiver = receiver.clone();
-                let path = path.clone();
-                Some(async move {
-                    wait_for_loading_buffer(receiver)
-                        .await
-                        .ok()
-                        .map(|buffer| (buffer, path))
-                })
-            })
-            .collect::<FuturesUnordered<_>>();
-
-        // Identify the current buffers whose containing repository has changed.
-        let current_buffers = self
-            .opened_buffers
-            .values()
-            .filter_map(|buffer| {
-                let buffer = buffer.upgrade()?;
-                let file = File::from_dyn(buffer.read(cx).file())?;
-                if file.worktree != worktree_handle {
-                    return None;
-                }
-                let path = file.path();
-                changed_repos
-                    .iter()
-                    .find(|(work_dir, _)| path.starts_with(work_dir))?;
-                Some((buffer, path.clone()))
-            })
-            .collect::<Vec<_>>();
-
-        if future_buffers.len() + current_buffers.len() == 0 {
-            return;
-        }
-
-        let remote_id = self.remote_id();
-        let client = self.client.clone();
-        cx.spawn(move |_, mut cx| async move {
-            // Wait for all of the buffers to load.
-            let future_buffers = future_buffers.collect::<Vec<_>>().await;
-
-            // Reload the diff base for every buffer whose containing git repository has changed.
-            let snapshot =
-                worktree_handle.update(&mut cx, |tree, _| tree.as_local().unwrap().snapshot())?;
-            let diff_bases_by_buffer = cx
-                .background_executor()
-                .spawn(async move {
-                    future_buffers
-                        .into_iter()
-                        .filter_map(|e| e)
-                        .chain(current_buffers)
-                        .filter_map(|(buffer, path)| {
-                            let (work_directory, repo) =
-                                snapshot.repository_and_work_directory_for_path(&path)?;
-                            let repo = snapshot.get_local_repo(&repo)?;
-                            let relative_path = path.strip_prefix(&work_directory).ok()?;
-                            let base_text = repo.repo_ptr.lock().load_index_text(&relative_path);
-                            Some((buffer, base_text))
-                        })
-                        .collect::<Vec<_>>()
-                })
-                .await;
-
-            // Assign the new diff bases on all of the buffers.
-            for (buffer, diff_base) in diff_bases_by_buffer {
-                let buffer_id = buffer.update(&mut cx, |buffer, cx| {
-                    buffer.set_diff_base(diff_base.clone(), cx);
-                    buffer.remote_id()
-                })?;
-                if let Some(project_id) = remote_id {
-                    client
-                        .send(proto::UpdateDiffBase {
-                            project_id,
-                            buffer_id,
-                            diff_base,
-                        })
-                        .log_err();
-                }
-            }
-
-            anyhow::Ok(())
-        })
-        .detach();
-    }
-
-    fn update_local_worktree_settings(
-        &mut self,
-        worktree: &Model<Worktree>,
-        changes: &UpdatedEntriesSet,
-        cx: &mut ModelContext<Self>,
-    ) {
-        let project_id = self.remote_id();
-        let worktree_id = worktree.entity_id();
-        let worktree = worktree.read(cx).as_local().unwrap();
-        let remote_worktree_id = worktree.id();
-
-        let mut settings_contents = Vec::new();
-        for (path, _, change) in changes.iter() {
-            if path.ends_with(&*LOCAL_SETTINGS_RELATIVE_PATH) {
-                let settings_dir = Arc::from(
-                    path.ancestors()
-                        .nth(LOCAL_SETTINGS_RELATIVE_PATH.components().count())
-                        .unwrap(),
-                );
-                let fs = self.fs.clone();
-                let removed = *change == PathChange::Removed;
-                let abs_path = worktree.absolutize(path);
-                settings_contents.push(async move {
-                    (settings_dir, (!removed).then_some(fs.load(&abs_path).await))
-                });
-            }
-        }
-
-        if settings_contents.is_empty() {
-            return;
-        }
-
-        let client = self.client.clone();
-        cx.spawn(move |_, cx| async move {
-            let settings_contents: Vec<(Arc<Path>, _)> =
-                futures::future::join_all(settings_contents).await;
-            cx.update(|cx| {
-                cx.update_global::<SettingsStore, _>(|store, cx| {
-                    for (directory, file_content) in settings_contents {
-                        let file_content = file_content.and_then(|content| content.log_err());
-                        store
-                            .set_local_settings(
-                                worktree_id.as_u64() as usize,
-                                directory.clone(),
-                                file_content.as_ref().map(String::as_str),
-                                cx,
-                            )
-                            .log_err();
-                        if let Some(remote_id) = project_id {
-                            client
-                                .send(proto::UpdateWorktreeSettings {
-                                    project_id: remote_id,
-                                    worktree_id: remote_worktree_id.to_proto(),
-                                    path: directory.to_string_lossy().into_owned(),
-                                    content: file_content,
-                                })
-                                .log_err();
-                        }
-                    }
-                });
-            })
-            .ok();
-        })
-        .detach();
-    }
-
-    pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut ModelContext<Self>) {
-        let new_active_entry = entry.and_then(|project_path| {
-            let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
-            let entry = worktree.read(cx).entry_for_path(project_path.path)?;
-            Some(entry.id)
-        });
-        if new_active_entry != self.active_entry {
-            self.active_entry = new_active_entry;
-            cx.emit(Event::ActiveEntryChanged(new_active_entry));
-        }
-    }
-
-    pub fn language_servers_running_disk_based_diagnostics(
-        &self,
-    ) -> impl Iterator<Item = LanguageServerId> + '_ {
-        self.language_server_statuses
-            .iter()
-            .filter_map(|(id, status)| {
-                if status.has_pending_diagnostic_updates {
-                    Some(*id)
-                } else {
-                    None
-                }
-            })
-    }
-
-    pub fn diagnostic_summary(&self, include_ignored: bool, cx: &AppContext) -> DiagnosticSummary {
-        let mut summary = DiagnosticSummary::default();
-        for (_, _, path_summary) in
-            self.diagnostic_summaries(include_ignored, cx)
-                .filter(|(path, _, _)| {
-                    let worktree = self.entry_for_path(&path, cx).map(|entry| entry.is_ignored);
-                    include_ignored || worktree == Some(false)
-                })
-        {
-            summary.error_count += path_summary.error_count;
-            summary.warning_count += path_summary.warning_count;
-        }
-        summary
-    }
-
-    pub fn diagnostic_summaries<'a>(
-        &'a self,
-        include_ignored: bool,
-        cx: &'a AppContext,
-    ) -> impl Iterator<Item = (ProjectPath, LanguageServerId, DiagnosticSummary)> + 'a {
-        self.visible_worktrees(cx)
-            .flat_map(move |worktree| {
-                let worktree = worktree.read(cx);
-                let worktree_id = worktree.id();
-                worktree
-                    .diagnostic_summaries()
-                    .map(move |(path, server_id, summary)| {
-                        (ProjectPath { worktree_id, path }, server_id, summary)
-                    })
-            })
-            .filter(move |(path, _, _)| {
-                let worktree = self.entry_for_path(&path, cx).map(|entry| entry.is_ignored);
-                include_ignored || worktree == Some(false)
-            })
-    }
-
-    pub fn disk_based_diagnostics_started(
-        &mut self,
-        language_server_id: LanguageServerId,
-        cx: &mut ModelContext<Self>,
-    ) {
-        cx.emit(Event::DiskBasedDiagnosticsStarted { language_server_id });
-    }
-
-    pub fn disk_based_diagnostics_finished(
-        &mut self,
-        language_server_id: LanguageServerId,
-        cx: &mut ModelContext<Self>,
-    ) {
-        cx.emit(Event::DiskBasedDiagnosticsFinished { language_server_id });
-    }
-
-    pub fn active_entry(&self) -> Option<ProjectEntryId> {
-        self.active_entry
-    }
-
-    pub fn entry_for_path(&self, path: &ProjectPath, cx: &AppContext) -> Option<Entry> {
-        self.worktree_for_id(path.worktree_id, cx)?
-            .read(cx)
-            .entry_for_path(&path.path)
-            .cloned()
-    }
-
-    pub fn path_for_entry(&self, entry_id: ProjectEntryId, cx: &AppContext) -> Option<ProjectPath> {
-        let worktree = self.worktree_for_entry(entry_id, cx)?;
-        let worktree = worktree.read(cx);
-        let worktree_id = worktree.id();
-        let path = worktree.entry_for_id(entry_id)?.path.clone();
-        Some(ProjectPath { worktree_id, path })
-    }
-
-    pub fn absolute_path(&self, project_path: &ProjectPath, cx: &AppContext) -> Option<PathBuf> {
-        let workspace_root = self
-            .worktree_for_id(project_path.worktree_id, cx)?
-            .read(cx)
-            .abs_path();
-        let project_path = project_path.path.as_ref();
-
-        Some(if project_path == Path::new("") {
-            workspace_root.to_path_buf()
-        } else {
-            workspace_root.join(project_path)
-        })
-    }
-
-    // RPC message handlers
-
-    async fn handle_unshare_project(
-        this: Model<Self>,
-        _: TypedEnvelope<proto::UnshareProject>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        this.update(&mut cx, |this, cx| {
-            if this.is_local() {
-                this.unshare(cx)?;
-            } else {
-                this.disconnected_from_host(cx);
-            }
-            Ok(())
-        })?
-    }
-
-    async fn handle_add_collaborator(
-        this: Model<Self>,
-        mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        let collaborator = envelope
-            .payload
-            .collaborator
-            .take()
-            .ok_or_else(|| anyhow!("empty collaborator"))?;
-
-        let collaborator = Collaborator::from_proto(collaborator)?;
-        this.update(&mut cx, |this, cx| {
-            this.shared_buffers.remove(&collaborator.peer_id);
-            cx.emit(Event::CollaboratorJoined(collaborator.peer_id));
-            this.collaborators
-                .insert(collaborator.peer_id, collaborator);
-            cx.notify();
-        })?;
-
-        Ok(())
-    }
-
-    async fn handle_update_project_collaborator(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::UpdateProjectCollaborator>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        let old_peer_id = envelope
-            .payload
-            .old_peer_id
-            .ok_or_else(|| anyhow!("missing old peer id"))?;
-        let new_peer_id = envelope
-            .payload
-            .new_peer_id
-            .ok_or_else(|| anyhow!("missing new peer id"))?;
-        this.update(&mut cx, |this, cx| {
-            let collaborator = this
-                .collaborators
-                .remove(&old_peer_id)
-                .ok_or_else(|| anyhow!("received UpdateProjectCollaborator for unknown peer"))?;
-            let is_host = collaborator.replica_id == 0;
-            this.collaborators.insert(new_peer_id, collaborator);
-
-            let buffers = this.shared_buffers.remove(&old_peer_id);
-            log::info!(
-                "peer {} became {}. moving buffers {:?}",
-                old_peer_id,
-                new_peer_id,
-                &buffers
-            );
-            if let Some(buffers) = buffers {
-                this.shared_buffers.insert(new_peer_id, buffers);
-            }
-
-            if is_host {
-                this.opened_buffers
-                    .retain(|_, buffer| !matches!(buffer, OpenBuffer::Operations(_)));
-                this.buffer_ordered_messages_tx
-                    .unbounded_send(BufferOrderedMessage::Resync)
-                    .unwrap();
-            }
-
-            cx.emit(Event::CollaboratorUpdated {
-                old_peer_id,
-                new_peer_id,
-            });
-            cx.notify();
-            Ok(())
-        })?
-    }
-
-    async fn handle_remove_collaborator(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        this.update(&mut cx, |this, cx| {
-            let peer_id = envelope
-                .payload
-                .peer_id
-                .ok_or_else(|| anyhow!("invalid peer id"))?;
-            let replica_id = this
-                .collaborators
-                .remove(&peer_id)
-                .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))?
-                .replica_id;
-            for buffer in this.opened_buffers.values() {
-                if let Some(buffer) = buffer.upgrade() {
-                    buffer.update(cx, |buffer, cx| buffer.remove_peer(replica_id, cx));
-                }
-            }
-            this.shared_buffers.remove(&peer_id);
-
-            cx.emit(Event::CollaboratorLeft(peer_id));
-            cx.notify();
-            Ok(())
-        })?
-    }
-
-    async fn handle_update_project(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::UpdateProject>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        this.update(&mut cx, |this, cx| {
-            // Don't handle messages that were sent before the response to us joining the project
-            if envelope.message_id > this.join_project_response_message_id {
-                this.set_worktrees_from_proto(envelope.payload.worktrees, cx)?;
-            }
-            Ok(())
-        })?
-    }
-
-    async fn handle_update_worktree(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::UpdateWorktree>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        this.update(&mut cx, |this, cx| {
-            let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
-            if let Some(worktree) = this.worktree_for_id(worktree_id, cx) {
-                worktree.update(cx, |worktree, _| {
-                    let worktree = worktree.as_remote_mut().unwrap();
-                    worktree.update_from_remote(envelope.payload);
-                });
-            }
-            Ok(())
-        })?
-    }
-
-    async fn handle_update_worktree_settings(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::UpdateWorktreeSettings>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        this.update(&mut cx, |this, cx| {
-            let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
-            if let Some(worktree) = this.worktree_for_id(worktree_id, cx) {
-                cx.update_global::<SettingsStore, _>(|store, cx| {
-                    store
-                        .set_local_settings(
-                            worktree.entity_id().as_u64() as usize,
-                            PathBuf::from(&envelope.payload.path).into(),
-                            envelope.payload.content.as_ref().map(String::as_str),
-                            cx,
-                        )
-                        .log_err();
-                });
-            }
-            Ok(())
-        })?
-    }
-
-    async fn handle_create_project_entry(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::CreateProjectEntry>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::ProjectEntryResponse> {
-        let worktree = this.update(&mut cx, |this, cx| {
-            let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
-            this.worktree_for_id(worktree_id, cx)
-                .ok_or_else(|| anyhow!("worktree not found"))
-        })??;
-        let worktree_scan_id = worktree.update(&mut cx, |worktree, _| worktree.scan_id())?;
-        let entry = worktree
-            .update(&mut cx, |worktree, cx| {
-                let worktree = worktree.as_local_mut().unwrap();
-                let path = PathBuf::from(envelope.payload.path);
-                worktree.create_entry(path, envelope.payload.is_directory, cx)
-            })?
-            .await?;
-        Ok(proto::ProjectEntryResponse {
-            entry: entry.as_ref().map(|e| e.into()),
-            worktree_scan_id: worktree_scan_id as u64,
-        })
-    }
-
-    async fn handle_rename_project_entry(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::RenameProjectEntry>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::ProjectEntryResponse> {
-        let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id);
-        let worktree = this.update(&mut cx, |this, cx| {
-            this.worktree_for_entry(entry_id, cx)
-                .ok_or_else(|| anyhow!("worktree not found"))
-        })??;
-        let worktree_scan_id = worktree.update(&mut cx, |worktree, _| worktree.scan_id())?;
-        let entry = worktree
-            .update(&mut cx, |worktree, cx| {
-                let new_path = PathBuf::from(envelope.payload.new_path);
-                worktree
-                    .as_local_mut()
-                    .unwrap()
-                    .rename_entry(entry_id, new_path, cx)
-            })?
-            .await?;
-        Ok(proto::ProjectEntryResponse {
-            entry: entry.as_ref().map(|e| e.into()),
-            worktree_scan_id: worktree_scan_id as u64,
-        })
-    }
-
-    async fn handle_copy_project_entry(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::CopyProjectEntry>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::ProjectEntryResponse> {
-        let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id);
-        let worktree = this.update(&mut cx, |this, cx| {
-            this.worktree_for_entry(entry_id, cx)
-                .ok_or_else(|| anyhow!("worktree not found"))
-        })??;
-        let worktree_scan_id = worktree.update(&mut cx, |worktree, _| worktree.scan_id())?;
-        let entry = worktree
-            .update(&mut cx, |worktree, cx| {
-                let new_path = PathBuf::from(envelope.payload.new_path);
-                worktree
-                    .as_local_mut()
-                    .unwrap()
-                    .copy_entry(entry_id, new_path, cx)
-            })?
-            .await?;
-        Ok(proto::ProjectEntryResponse {
-            entry: entry.as_ref().map(|e| e.into()),
-            worktree_scan_id: worktree_scan_id as u64,
-        })
-    }
-
-    async fn handle_delete_project_entry(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::DeleteProjectEntry>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::ProjectEntryResponse> {
-        let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id);
-
-        this.update(&mut cx, |_, cx| cx.emit(Event::DeletedEntry(entry_id)))?;
-
-        let worktree = this.update(&mut cx, |this, cx| {
-            this.worktree_for_entry(entry_id, cx)
-                .ok_or_else(|| anyhow!("worktree not found"))
-        })??;
-        let worktree_scan_id = worktree.update(&mut cx, |worktree, _| worktree.scan_id())?;
-        worktree
-            .update(&mut cx, |worktree, cx| {
-                worktree
-                    .as_local_mut()
-                    .unwrap()
-                    .delete_entry(entry_id, cx)
-                    .ok_or_else(|| anyhow!("invalid entry"))
-            })??
-            .await?;
-        Ok(proto::ProjectEntryResponse {
-            entry: None,
-            worktree_scan_id: worktree_scan_id as u64,
-        })
-    }
-
-    async fn handle_expand_project_entry(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::ExpandProjectEntry>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::ExpandProjectEntryResponse> {
-        let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id);
-        let worktree = this
-            .update(&mut cx, |this, cx| this.worktree_for_entry(entry_id, cx))?
-            .ok_or_else(|| anyhow!("invalid request"))?;
-        worktree
-            .update(&mut cx, |worktree, cx| {
-                worktree
-                    .as_local_mut()
-                    .unwrap()
-                    .expand_entry(entry_id, cx)
-                    .ok_or_else(|| anyhow!("invalid entry"))
-            })??
-            .await?;
-        let worktree_scan_id = worktree.update(&mut cx, |worktree, _| worktree.scan_id())? as u64;
-        Ok(proto::ExpandProjectEntryResponse { worktree_scan_id })
-    }
-
-    async fn handle_update_diagnostic_summary(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::UpdateDiagnosticSummary>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        this.update(&mut cx, |this, cx| {
-            let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
-            if let Some(worktree) = this.worktree_for_id(worktree_id, cx) {
-                if let Some(summary) = envelope.payload.summary {
-                    let project_path = ProjectPath {
-                        worktree_id,
-                        path: Path::new(&summary.path).into(),
-                    };
-                    worktree.update(cx, |worktree, _| {
-                        worktree
-                            .as_remote_mut()
-                            .unwrap()
-                            .update_diagnostic_summary(project_path.path.clone(), &summary);
-                    });
-                    cx.emit(Event::DiagnosticsUpdated {
-                        language_server_id: LanguageServerId(summary.language_server_id as usize),
-                        path: project_path,
-                    });
-                }
-            }
-            Ok(())
-        })?
-    }
-
-    async fn handle_start_language_server(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::StartLanguageServer>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        let server = envelope
-            .payload
-            .server
-            .ok_or_else(|| anyhow!("invalid server"))?;
-        this.update(&mut cx, |this, cx| {
-            this.language_server_statuses.insert(
-                LanguageServerId(server.id as usize),
-                LanguageServerStatus {
-                    name: server.name,
-                    pending_work: Default::default(),
-                    has_pending_diagnostic_updates: false,
-                    progress_tokens: Default::default(),
-                },
-            );
-            cx.notify();
-        })?;
-        Ok(())
-    }
-
-    async fn handle_update_language_server(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::UpdateLanguageServer>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        this.update(&mut cx, |this, cx| {
-            let language_server_id = LanguageServerId(envelope.payload.language_server_id as usize);
-
-            match envelope
-                .payload
-                .variant
-                .ok_or_else(|| anyhow!("invalid variant"))?
-            {
-                proto::update_language_server::Variant::WorkStart(payload) => {
-                    this.on_lsp_work_start(
-                        language_server_id,
-                        payload.token,
-                        LanguageServerProgress {
-                            message: payload.message,
-                            percentage: payload.percentage.map(|p| p as usize),
-                            last_update_at: Instant::now(),
-                        },
-                        cx,
-                    );
-                }
-
-                proto::update_language_server::Variant::WorkProgress(payload) => {
-                    this.on_lsp_work_progress(
-                        language_server_id,
-                        payload.token,
-                        LanguageServerProgress {
-                            message: payload.message,
-                            percentage: payload.percentage.map(|p| p as usize),
-                            last_update_at: Instant::now(),
-                        },
-                        cx,
-                    );
-                }
-
-                proto::update_language_server::Variant::WorkEnd(payload) => {
-                    this.on_lsp_work_end(language_server_id, payload.token, cx);
-                }
-
-                proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating(_) => {
-                    this.disk_based_diagnostics_started(language_server_id, cx);
-                }
-
-                proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(_) => {
-                    this.disk_based_diagnostics_finished(language_server_id, cx)
-                }
-            }
-
-            Ok(())
-        })?
-    }
-
-    async fn handle_update_buffer(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::UpdateBuffer>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::Ack> {
-        this.update(&mut cx, |this, cx| {
-            let payload = envelope.payload.clone();
-            let buffer_id = payload.buffer_id;
-            let ops = payload
-                .operations
-                .into_iter()
-                .map(language::proto::deserialize_operation)
-                .collect::<Result<Vec<_>, _>>()?;
-            let is_remote = this.is_remote();
-            match this.opened_buffers.entry(buffer_id) {
-                hash_map::Entry::Occupied(mut e) => match e.get_mut() {
-                    OpenBuffer::Strong(buffer) => {
-                        buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx))?;
-                    }
-                    OpenBuffer::Operations(operations) => operations.extend_from_slice(&ops),
-                    OpenBuffer::Weak(_) => {}
-                },
-                hash_map::Entry::Vacant(e) => {
-                    assert!(
-                        is_remote,
-                        "received buffer update from {:?}",
-                        envelope.original_sender_id
-                    );
-                    e.insert(OpenBuffer::Operations(ops));
-                }
-            }
-            Ok(proto::Ack {})
-        })?
-    }
-
-    async fn handle_create_buffer_for_peer(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::CreateBufferForPeer>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        this.update(&mut cx, |this, cx| {
-            match envelope
-                .payload
-                .variant
-                .ok_or_else(|| anyhow!("missing variant"))?
-            {
-                proto::create_buffer_for_peer::Variant::State(mut state) => {
-                    let mut buffer_file = None;
-                    if let Some(file) = state.file.take() {
-                        let worktree_id = WorktreeId::from_proto(file.worktree_id);
-                        let worktree = this.worktree_for_id(worktree_id, cx).ok_or_else(|| {
-                            anyhow!("no worktree found for id {}", file.worktree_id)
-                        })?;
-                        buffer_file = Some(Arc::new(File::from_proto(file, worktree.clone(), cx)?)
-                            as Arc<dyn language::File>);
-                    }
-
-                    let buffer_id = state.id;
-                    let buffer = cx.new_model(|_| {
-                        Buffer::from_proto(this.replica_id(), state, buffer_file).unwrap()
-                    });
-                    this.incomplete_remote_buffers
-                        .insert(buffer_id, Some(buffer));
-                }
-                proto::create_buffer_for_peer::Variant::Chunk(chunk) => {
-                    let buffer = this
-                        .incomplete_remote_buffers
-                        .get(&chunk.buffer_id)
-                        .cloned()
-                        .flatten()
-                        .ok_or_else(|| {
-                            anyhow!(
-                                "received chunk for buffer {} without initial state",
-                                chunk.buffer_id
-                            )
-                        })?;
-                    let operations = chunk
-                        .operations
-                        .into_iter()
-                        .map(language::proto::deserialize_operation)
-                        .collect::<Result<Vec<_>>>()?;
-                    buffer.update(cx, |buffer, cx| buffer.apply_ops(operations, cx))?;
-
-                    if chunk.is_last {
-                        this.incomplete_remote_buffers.remove(&chunk.buffer_id);
-                        this.register_buffer(&buffer, cx)?;
-                    }
-                }
-            }
-
-            Ok(())
-        })?
-    }
-
-    async fn handle_update_diff_base(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::UpdateDiffBase>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        this.update(&mut cx, |this, cx| {
-            let buffer_id = envelope.payload.buffer_id;
-            let diff_base = envelope.payload.diff_base;
-            if let Some(buffer) = this
-                .opened_buffers
-                .get_mut(&buffer_id)
-                .and_then(|b| b.upgrade())
-                .or_else(|| {
-                    this.incomplete_remote_buffers
-                        .get(&buffer_id)
-                        .cloned()
-                        .flatten()
-                })
-            {
-                buffer.update(cx, |buffer, cx| buffer.set_diff_base(diff_base, cx));
-            }
-            Ok(())
-        })?
-    }
-
-    async fn handle_update_buffer_file(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::UpdateBufferFile>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        let buffer_id = envelope.payload.buffer_id;
-
-        this.update(&mut cx, |this, cx| {
-            let payload = envelope.payload.clone();
-            if let Some(buffer) = this
-                .opened_buffers
-                .get(&buffer_id)
-                .and_then(|b| b.upgrade())
-                .or_else(|| {
-                    this.incomplete_remote_buffers
-                        .get(&buffer_id)
-                        .cloned()
-                        .flatten()
-                })
-            {
-                let file = payload.file.ok_or_else(|| anyhow!("invalid file"))?;
-                let worktree = this
-                    .worktree_for_id(WorktreeId::from_proto(file.worktree_id), cx)
-                    .ok_or_else(|| anyhow!("no such worktree"))?;
-                let file = File::from_proto(file, worktree, cx)?;
-                buffer.update(cx, |buffer, cx| {
-                    buffer.file_updated(Arc::new(file), cx);
-                });
-                this.detect_language_for_buffer(&buffer, cx);
-            }
-            Ok(())
-        })?
-    }
-
-    async fn handle_save_buffer(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::SaveBuffer>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::BufferSaved> {
-        let buffer_id = envelope.payload.buffer_id;
-        let (project_id, buffer) = this.update(&mut cx, |this, _cx| {
-            let project_id = this.remote_id().ok_or_else(|| anyhow!("not connected"))?;
-            let buffer = this
-                .opened_buffers
-                .get(&buffer_id)
-                .and_then(|buffer| buffer.upgrade())
-                .ok_or_else(|| anyhow!("unknown buffer id {}", buffer_id))?;
-            anyhow::Ok((project_id, buffer))
-        })??;
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(deserialize_version(&envelope.payload.version))
-            })?
-            .await?;
-        let buffer_id = buffer.update(&mut cx, |buffer, _| buffer.remote_id())?;
-
-        this.update(&mut cx, |this, cx| this.save_buffer(buffer.clone(), cx))?
-            .await?;
-        Ok(buffer.update(&mut cx, |buffer, _| proto::BufferSaved {
-            project_id,
-            buffer_id,
-            version: serialize_version(buffer.saved_version()),
-            mtime: Some(buffer.saved_mtime().into()),
-            fingerprint: language::proto::serialize_fingerprint(buffer.saved_version_fingerprint()),
-        })?)
-    }
-
-    async fn handle_reload_buffers(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::ReloadBuffers>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::ReloadBuffersResponse> {
-        let sender_id = envelope.original_sender_id()?;
-        let reload = this.update(&mut cx, |this, cx| {
-            let mut buffers = HashSet::default();
-            for buffer_id in &envelope.payload.buffer_ids {
-                buffers.insert(
-                    this.opened_buffers
-                        .get(buffer_id)
-                        .and_then(|buffer| buffer.upgrade())
-                        .ok_or_else(|| anyhow!("unknown buffer id {}", buffer_id))?,
-                );
-            }
-            Ok::<_, anyhow::Error>(this.reload_buffers(buffers, false, cx))
-        })??;
-
-        let project_transaction = reload.await?;
-        let project_transaction = this.update(&mut cx, |this, cx| {
-            this.serialize_project_transaction_for_peer(project_transaction, sender_id, cx)
-        })?;
-        Ok(proto::ReloadBuffersResponse {
-            transaction: Some(project_transaction),
-        })
-    }
-
-    async fn handle_synchronize_buffers(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::SynchronizeBuffers>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::SynchronizeBuffersResponse> {
-        let project_id = envelope.payload.project_id;
-        let mut response = proto::SynchronizeBuffersResponse {
-            buffers: Default::default(),
-        };
-
-        this.update(&mut cx, |this, cx| {
-            let Some(guest_id) = envelope.original_sender_id else {
-                error!("missing original_sender_id on SynchronizeBuffers request");
-                return;
-            };
-
-            this.shared_buffers.entry(guest_id).or_default().clear();
-            for buffer in envelope.payload.buffers {
-                let buffer_id = buffer.id;
-                let remote_version = language::proto::deserialize_version(&buffer.version);
-                if let Some(buffer) = this.buffer_for_id(buffer_id) {
-                    this.shared_buffers
-                        .entry(guest_id)
-                        .or_default()
-                        .insert(buffer_id);
-
-                    let buffer = buffer.read(cx);
-                    response.buffers.push(proto::BufferVersion {
-                        id: buffer_id,
-                        version: language::proto::serialize_version(&buffer.version),
-                    });
-
-                    let operations = buffer.serialize_ops(Some(remote_version), cx);
-                    let client = this.client.clone();
-                    if let Some(file) = buffer.file() {
-                        client
-                            .send(proto::UpdateBufferFile {
-                                project_id,
-                                buffer_id: buffer_id as u64,
-                                file: Some(file.to_proto()),
-                            })
-                            .log_err();
-                    }
-
-                    client
-                        .send(proto::UpdateDiffBase {
-                            project_id,
-                            buffer_id: buffer_id as u64,
-                            diff_base: buffer.diff_base().map(Into::into),
-                        })
-                        .log_err();
-
-                    client
-                        .send(proto::BufferReloaded {
-                            project_id,
-                            buffer_id,
-                            version: language::proto::serialize_version(buffer.saved_version()),
-                            mtime: Some(buffer.saved_mtime().into()),
-                            fingerprint: language::proto::serialize_fingerprint(
-                                buffer.saved_version_fingerprint(),
-                            ),
-                            line_ending: language::proto::serialize_line_ending(
-                                buffer.line_ending(),
-                            ) as i32,
-                        })
-                        .log_err();
-
-                    cx.background_executor()
-                        .spawn(
-                            async move {
-                                let operations = operations.await;
-                                for chunk in split_operations(operations) {
-                                    client
-                                        .request(proto::UpdateBuffer {
-                                            project_id,
-                                            buffer_id,
-                                            operations: chunk,
-                                        })
-                                        .await?;
-                                }
-                                anyhow::Ok(())
-                            }
-                            .log_err(),
-                        )
-                        .detach();
-                }
-            }
-        })?;
-
-        Ok(response)
-    }
-
-    async fn handle_format_buffers(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::FormatBuffers>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::FormatBuffersResponse> {
-        let sender_id = envelope.original_sender_id()?;
-        let format = this.update(&mut cx, |this, cx| {
-            let mut buffers = HashSet::default();
-            for buffer_id in &envelope.payload.buffer_ids {
-                buffers.insert(
-                    this.opened_buffers
-                        .get(buffer_id)
-                        .and_then(|buffer| buffer.upgrade())
-                        .ok_or_else(|| anyhow!("unknown buffer id {}", buffer_id))?,
-                );
-            }
-            let trigger = FormatTrigger::from_proto(envelope.payload.trigger);
-            Ok::<_, anyhow::Error>(this.format(buffers, false, trigger, cx))
-        })??;
-
-        let project_transaction = format.await?;
-        let project_transaction = this.update(&mut cx, |this, cx| {
-            this.serialize_project_transaction_for_peer(project_transaction, sender_id, cx)
-        })?;
-        Ok(proto::FormatBuffersResponse {
-            transaction: Some(project_transaction),
-        })
-    }
-
-    async fn handle_apply_additional_edits_for_completion(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::ApplyCompletionAdditionalEdits>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::ApplyCompletionAdditionalEditsResponse> {
-        let (buffer, completion) = this.update(&mut cx, |this, cx| {
-            let buffer = this
-                .opened_buffers
-                .get(&envelope.payload.buffer_id)
-                .and_then(|buffer| buffer.upgrade())
-                .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
-            let language = buffer.read(cx).language();
-            let completion = language::proto::deserialize_completion(
-                envelope
-                    .payload
-                    .completion
-                    .ok_or_else(|| anyhow!("invalid completion"))?,
-                language.cloned(),
-            );
-            Ok::<_, anyhow::Error>((buffer, completion))
-        })??;
-
-        let completion = completion.await?;
-
-        let apply_additional_edits = this.update(&mut cx, |this, cx| {
-            this.apply_additional_edits_for_completion(buffer, completion, false, cx)
-        })?;
-
-        Ok(proto::ApplyCompletionAdditionalEditsResponse {
-            transaction: apply_additional_edits
-                .await?
-                .as_ref()
-                .map(language::proto::serialize_transaction),
-        })
-    }
-
-    async fn handle_apply_code_action(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::ApplyCodeAction>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::ApplyCodeActionResponse> {
-        let sender_id = envelope.original_sender_id()?;
-        let action = language::proto::deserialize_code_action(
-            envelope
-                .payload
-                .action
-                .ok_or_else(|| anyhow!("invalid action"))?,
-        )?;
-        let apply_code_action = this.update(&mut cx, |this, cx| {
-            let buffer = this
-                .opened_buffers
-                .get(&envelope.payload.buffer_id)
-                .and_then(|buffer| buffer.upgrade())
-                .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
-            Ok::<_, anyhow::Error>(this.apply_code_action(buffer, action, false, cx))
-        })??;
-
-        let project_transaction = apply_code_action.await?;
-        let project_transaction = this.update(&mut cx, |this, cx| {
-            this.serialize_project_transaction_for_peer(project_transaction, sender_id, cx)
-        })?;
-        Ok(proto::ApplyCodeActionResponse {
-            transaction: Some(project_transaction),
-        })
-    }
-
-    async fn handle_on_type_formatting(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::OnTypeFormatting>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::OnTypeFormattingResponse> {
-        let on_type_formatting = this.update(&mut cx, |this, cx| {
-            let buffer = this
-                .opened_buffers
-                .get(&envelope.payload.buffer_id)
-                .and_then(|buffer| buffer.upgrade())
-                .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
-            let position = envelope
-                .payload
-                .position
-                .and_then(deserialize_anchor)
-                .ok_or_else(|| anyhow!("invalid position"))?;
-            Ok::<_, anyhow::Error>(this.apply_on_type_formatting(
-                buffer,
-                position,
-                envelope.payload.trigger.clone(),
-                cx,
-            ))
-        })??;
-
-        let transaction = on_type_formatting
-            .await?
-            .as_ref()
-            .map(language::proto::serialize_transaction);
-        Ok(proto::OnTypeFormattingResponse { transaction })
-    }
-
-    async fn handle_inlay_hints(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::InlayHints>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::InlayHintsResponse> {
-        let sender_id = envelope.original_sender_id()?;
-        let buffer = this.update(&mut cx, |this, _| {
-            this.opened_buffers
-                .get(&envelope.payload.buffer_id)
-                .and_then(|buffer| buffer.upgrade())
-                .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))
-        })??;
-        let buffer_version = deserialize_version(&envelope.payload.version);
-
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(buffer_version.clone())
-            })?
-            .await
-            .with_context(|| {
-                format!(
-                    "waiting for version {:?} for buffer {}",
-                    buffer_version,
-                    buffer.entity_id()
-                )
-            })?;
-
-        let start = envelope
-            .payload
-            .start
-            .and_then(deserialize_anchor)
-            .context("missing range start")?;
-        let end = envelope
-            .payload
-            .end
-            .and_then(deserialize_anchor)
-            .context("missing range end")?;
-        let buffer_hints = this
-            .update(&mut cx, |project, cx| {
-                project.inlay_hints(buffer, start..end, cx)
-            })?
-            .await
-            .context("inlay hints fetch")?;
-
-        Ok(this.update(&mut cx, |project, cx| {
-            InlayHints::response_to_proto(buffer_hints, project, sender_id, &buffer_version, cx)
-        })?)
-    }
-
-    async fn handle_resolve_inlay_hint(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::ResolveInlayHint>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::ResolveInlayHintResponse> {
-        let proto_hint = envelope
-            .payload
-            .hint
-            .expect("incorrect protobuf resolve inlay hint message: missing the inlay hint");
-        let hint = InlayHints::proto_to_project_hint(proto_hint)
-            .context("resolved proto inlay hint conversion")?;
-        let buffer = this.update(&mut cx, |this, _cx| {
-            this.opened_buffers
-                .get(&envelope.payload.buffer_id)
-                .and_then(|buffer| buffer.upgrade())
-                .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))
-        })??;
-        let response_hint = this
-            .update(&mut cx, |project, cx| {
-                project.resolve_inlay_hint(
-                    hint,
-                    buffer,
-                    LanguageServerId(envelope.payload.language_server_id as usize),
-                    cx,
-                )
-            })?
-            .await
-            .context("inlay hints fetch")?;
-        Ok(proto::ResolveInlayHintResponse {
-            hint: Some(InlayHints::project_to_proto_hint(response_hint)),
-        })
-    }
-
-    async fn handle_refresh_inlay_hints(
-        this: Model<Self>,
-        _: TypedEnvelope<proto::RefreshInlayHints>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::Ack> {
-        this.update(&mut cx, |_, cx| {
-            cx.emit(Event::RefreshInlayHints);
-        })?;
-        Ok(proto::Ack {})
-    }
-
-    async fn handle_lsp_command<T: LspCommand>(
-        this: Model<Self>,
-        envelope: TypedEnvelope<T::ProtoRequest>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<<T::ProtoRequest as proto::RequestMessage>::Response>
-    where
-        <T::LspRequest as lsp::request::Request>::Params: Send,
-        <T::LspRequest as lsp::request::Request>::Result: Send,
-    {
-        let sender_id = envelope.original_sender_id()?;
-        let buffer_id = T::buffer_id_from_proto(&envelope.payload);
-        let buffer_handle = this.update(&mut cx, |this, _cx| {
-            this.opened_buffers
-                .get(&buffer_id)
-                .and_then(|buffer| buffer.upgrade())
-                .ok_or_else(|| anyhow!("unknown buffer id {}", buffer_id))
-        })??;
-        let request = T::from_proto(
-            envelope.payload,
-            this.clone(),
-            buffer_handle.clone(),
-            cx.clone(),
-        )
-        .await?;
-        let buffer_version = buffer_handle.update(&mut cx, |buffer, _| buffer.version())?;
-        let response = this
-            .update(&mut cx, |this, cx| {
-                this.request_lsp(buffer_handle, LanguageServerToQuery::Primary, request, cx)
-            })?
-            .await?;
-        this.update(&mut cx, |this, cx| {
-            Ok(T::response_to_proto(
-                response,
-                this,
-                sender_id,
-                &buffer_version,
-                cx,
-            ))
-        })?
-    }
-
-    async fn handle_get_project_symbols(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::GetProjectSymbols>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::GetProjectSymbolsResponse> {
-        let symbols = this
-            .update(&mut cx, |this, cx| {
-                this.symbols(&envelope.payload.query, cx)
-            })?
-            .await?;
-
-        Ok(proto::GetProjectSymbolsResponse {
-            symbols: symbols.iter().map(serialize_symbol).collect(),
-        })
-    }
-
-    async fn handle_search_project(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::SearchProject>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::SearchProjectResponse> {
-        let peer_id = envelope.original_sender_id()?;
-        let query = SearchQuery::from_proto(envelope.payload)?;
-        let mut result = this.update(&mut cx, |this, cx| this.search(query, cx))?;
-
-        cx.spawn(move |mut cx| async move {
-            let mut locations = Vec::new();
-            while let Some((buffer, ranges)) = result.next().await {
-                for range in ranges {
-                    let start = serialize_anchor(&range.start);
-                    let end = serialize_anchor(&range.end);
-                    let buffer_id = this.update(&mut cx, |this, cx| {
-                        this.create_buffer_for_peer(&buffer, peer_id, cx)
-                    })?;
-                    locations.push(proto::Location {
-                        buffer_id,
-                        start: Some(start),
-                        end: Some(end),
-                    });
-                }
-            }
-            Ok(proto::SearchProjectResponse { locations })
-        })
-        .await
-    }
-
-    async fn handle_open_buffer_for_symbol(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::OpenBufferForSymbol>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::OpenBufferForSymbolResponse> {
-        let peer_id = envelope.original_sender_id()?;
-        let symbol = envelope
-            .payload
-            .symbol
-            .ok_or_else(|| anyhow!("invalid symbol"))?;
-        let symbol = this
-            .update(&mut cx, |this, _| this.deserialize_symbol(symbol))?
-            .await?;
-        let symbol = this.update(&mut cx, |this, _| {
-            let signature = this.symbol_signature(&symbol.path);
-            if signature == symbol.signature {
-                Ok(symbol)
-            } else {
-                Err(anyhow!("invalid symbol signature"))
-            }
-        })??;
-        let buffer = this
-            .update(&mut cx, |this, cx| this.open_buffer_for_symbol(&symbol, cx))?
-            .await?;
-
-        Ok(proto::OpenBufferForSymbolResponse {
-            buffer_id: this.update(&mut cx, |this, cx| {
-                this.create_buffer_for_peer(&buffer, peer_id, cx)
-            })?,
-        })
-    }
-
-    fn symbol_signature(&self, project_path: &ProjectPath) -> [u8; 32] {
-        let mut hasher = Sha256::new();
-        hasher.update(project_path.worktree_id.to_proto().to_be_bytes());
-        hasher.update(project_path.path.to_string_lossy().as_bytes());
-        hasher.update(self.nonce.to_be_bytes());
-        hasher.finalize().as_slice().try_into().unwrap()
-    }
-
-    async fn handle_open_buffer_by_id(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::OpenBufferById>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::OpenBufferResponse> {
-        let peer_id = envelope.original_sender_id()?;
-        let buffer = this
-            .update(&mut cx, |this, cx| {
-                this.open_buffer_by_id(envelope.payload.id, cx)
-            })?
-            .await?;
-        this.update(&mut cx, |this, cx| {
-            Ok(proto::OpenBufferResponse {
-                buffer_id: this.create_buffer_for_peer(&buffer, peer_id, cx),
-            })
-        })?
-    }
-
-    async fn handle_open_buffer_by_path(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::OpenBufferByPath>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::OpenBufferResponse> {
-        let peer_id = envelope.original_sender_id()?;
-        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
-        let open_buffer = this.update(&mut cx, |this, cx| {
-            this.open_buffer(
-                ProjectPath {
-                    worktree_id,
-                    path: PathBuf::from(envelope.payload.path).into(),
-                },
-                cx,
-            )
-        })?;
-
-        let buffer = open_buffer.await?;
-        this.update(&mut cx, |this, cx| {
-            Ok(proto::OpenBufferResponse {
-                buffer_id: this.create_buffer_for_peer(&buffer, peer_id, cx),
-            })
-        })?
-    }
-
-    fn serialize_project_transaction_for_peer(
-        &mut self,
-        project_transaction: ProjectTransaction,
-        peer_id: proto::PeerId,
-        cx: &mut AppContext,
-    ) -> proto::ProjectTransaction {
-        let mut serialized_transaction = proto::ProjectTransaction {
-            buffer_ids: Default::default(),
-            transactions: Default::default(),
-        };
-        for (buffer, transaction) in project_transaction.0 {
-            serialized_transaction
-                .buffer_ids
-                .push(self.create_buffer_for_peer(&buffer, peer_id, cx));
-            serialized_transaction
-                .transactions
-                .push(language::proto::serialize_transaction(&transaction));
-        }
-        serialized_transaction
-    }
-
-    fn deserialize_project_transaction(
-        &mut self,
-        message: proto::ProjectTransaction,
-        push_to_history: bool,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<ProjectTransaction>> {
-        cx.spawn(move |this, mut cx| async move {
-            let mut project_transaction = ProjectTransaction::default();
-            for (buffer_id, transaction) in message.buffer_ids.into_iter().zip(message.transactions)
-            {
-                let buffer = this
-                    .update(&mut cx, |this, cx| {
-                        this.wait_for_remote_buffer(buffer_id, cx)
-                    })?
-                    .await?;
-                let transaction = language::proto::deserialize_transaction(transaction)?;
-                project_transaction.0.insert(buffer, transaction);
-            }
-
-            for (buffer, transaction) in &project_transaction.0 {
-                buffer
-                    .update(&mut cx, |buffer, _| {
-                        buffer.wait_for_edits(transaction.edit_ids.iter().copied())
-                    })?
-                    .await?;
-
-                if push_to_history {
-                    buffer.update(&mut cx, |buffer, _| {
-                        buffer.push_transaction(transaction.clone(), Instant::now());
-                    })?;
-                }
-            }
-
-            Ok(project_transaction)
-        })
-    }
-
-    fn create_buffer_for_peer(
-        &mut self,
-        buffer: &Model<Buffer>,
-        peer_id: proto::PeerId,
-        cx: &mut AppContext,
-    ) -> u64 {
-        let buffer_id = buffer.read(cx).remote_id();
-        if let Some(ProjectClientState::Local { updates_tx, .. }) = &self.client_state {
-            updates_tx
-                .unbounded_send(LocalProjectUpdate::CreateBufferForPeer { peer_id, buffer_id })
-                .ok();
-        }
-        buffer_id
-    }
-
-    fn wait_for_remote_buffer(
-        &mut self,
-        id: u64,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Model<Buffer>>> {
-        let mut opened_buffer_rx = self.opened_buffer.1.clone();
-
-        cx.spawn(move |this, mut cx| async move {
-            let buffer = loop {
-                let Some(this) = this.upgrade() else {
-                    return Err(anyhow!("project dropped"));
-                };
-
-                let buffer = this.update(&mut cx, |this, _cx| {
-                    this.opened_buffers
-                        .get(&id)
-                        .and_then(|buffer| buffer.upgrade())
-                })?;
-
-                if let Some(buffer) = buffer {
-                    break buffer;
-                } else if this.update(&mut cx, |this, _| this.is_read_only())? {
-                    return Err(anyhow!("disconnected before buffer {} could be opened", id));
-                }
-
-                this.update(&mut cx, |this, _| {
-                    this.incomplete_remote_buffers.entry(id).or_default();
-                })?;
-                drop(this);
-
-                opened_buffer_rx
-                    .next()
-                    .await
-                    .ok_or_else(|| anyhow!("project dropped while waiting for buffer"))?;
-            };
-
-            Ok(buffer)
-        })
-    }
-
-    fn synchronize_remote_buffers(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
-        let project_id = match self.client_state.as_ref() {
-            Some(ProjectClientState::Remote {
-                sharing_has_stopped,
-                remote_id,
-                ..
-            }) => {
-                if *sharing_has_stopped {
-                    return Task::ready(Err(anyhow!(
-                        "can't synchronize remote buffers on a readonly project"
-                    )));
-                } else {
-                    *remote_id
-                }
-            }
-            Some(ProjectClientState::Local { .. }) | None => {
-                return Task::ready(Err(anyhow!(
-                    "can't synchronize remote buffers on a local project"
-                )))
-            }
-        };
-
-        let client = self.client.clone();
-        cx.spawn(move |this, mut cx| async move {
-            let (buffers, incomplete_buffer_ids) = this.update(&mut cx, |this, cx| {
-                let buffers = this
-                    .opened_buffers
-                    .iter()
-                    .filter_map(|(id, buffer)| {
-                        let buffer = buffer.upgrade()?;
-                        Some(proto::BufferVersion {
-                            id: *id,
-                            version: language::proto::serialize_version(&buffer.read(cx).version),
-                        })
-                    })
-                    .collect();
-                let incomplete_buffer_ids = this
-                    .incomplete_remote_buffers
-                    .keys()
-                    .copied()
-                    .collect::<Vec<_>>();
-
-                (buffers, incomplete_buffer_ids)
-            })?;
-            let response = client
-                .request(proto::SynchronizeBuffers {
-                    project_id,
-                    buffers,
-                })
-                .await?;
-
-            let send_updates_for_buffers = this.update(&mut cx, |this, cx| {
-                response
-                    .buffers
-                    .into_iter()
-                    .map(|buffer| {
-                        let client = client.clone();
-                        let buffer_id = buffer.id;
-                        let remote_version = language::proto::deserialize_version(&buffer.version);
-                        if let Some(buffer) = this.buffer_for_id(buffer_id) {
-                            let operations =
-                                buffer.read(cx).serialize_ops(Some(remote_version), cx);
-                            cx.background_executor().spawn(async move {
-                                let operations = operations.await;
-                                for chunk in split_operations(operations) {
-                                    client
-                                        .request(proto::UpdateBuffer {
-                                            project_id,
-                                            buffer_id,
-                                            operations: chunk,
-                                        })
-                                        .await?;
-                                }
-                                anyhow::Ok(())
-                            })
-                        } else {
-                            Task::ready(Ok(()))
-                        }
-                    })
-                    .collect::<Vec<_>>()
-            })?;
-
-            // Any incomplete buffers have open requests waiting. Request that the host sends
-            // creates these buffers for us again to unblock any waiting futures.
-            for id in incomplete_buffer_ids {
-                cx.background_executor()
-                    .spawn(client.request(proto::OpenBufferById { project_id, id }))
-                    .detach();
-            }
-
-            futures::future::join_all(send_updates_for_buffers)
-                .await
-                .into_iter()
-                .collect()
-        })
-    }
-
-    pub fn worktree_metadata_protos(&self, cx: &AppContext) -> Vec<proto::WorktreeMetadata> {
-        self.worktrees()
-            .map(|worktree| {
-                let worktree = worktree.read(cx);
-                proto::WorktreeMetadata {
-                    id: worktree.id().to_proto(),
-                    root_name: worktree.root_name().into(),
-                    visible: worktree.is_visible(),
-                    abs_path: worktree.abs_path().to_string_lossy().into(),
-                }
-            })
-            .collect()
-    }
-
-    fn set_worktrees_from_proto(
-        &mut self,
-        worktrees: Vec<proto::WorktreeMetadata>,
-        cx: &mut ModelContext<Project>,
-    ) -> Result<()> {
-        let replica_id = self.replica_id();
-        let remote_id = self.remote_id().ok_or_else(|| anyhow!("invalid project"))?;
-
-        let mut old_worktrees_by_id = self
-            .worktrees
-            .drain(..)
-            .filter_map(|worktree| {
-                let worktree = worktree.upgrade()?;
-                Some((worktree.read(cx).id(), worktree))
-            })
-            .collect::<HashMap<_, _>>();
-
-        for worktree in worktrees {
-            if let Some(old_worktree) =
-                old_worktrees_by_id.remove(&WorktreeId::from_proto(worktree.id))
-            {
-                self.worktrees.push(WorktreeHandle::Strong(old_worktree));
-            } else {
-                let worktree =
-                    Worktree::remote(remote_id, replica_id, worktree, self.client.clone(), cx);
-                let _ = self.add_worktree(&worktree, cx);
-            }
-        }
-
-        self.metadata_changed(cx);
-        for id in old_worktrees_by_id.keys() {
-            cx.emit(Event::WorktreeRemoved(*id));
-        }
-
-        Ok(())
-    }
-
-    fn set_collaborators_from_proto(
-        &mut self,
-        messages: Vec<proto::Collaborator>,
-        cx: &mut ModelContext<Self>,
-    ) -> Result<()> {
-        let mut collaborators = HashMap::default();
-        for message in messages {
-            let collaborator = Collaborator::from_proto(message)?;
-            collaborators.insert(collaborator.peer_id, collaborator);
-        }
-        for old_peer_id in self.collaborators.keys() {
-            if !collaborators.contains_key(old_peer_id) {
-                cx.emit(Event::CollaboratorLeft(*old_peer_id));
-            }
-        }
-        self.collaborators = collaborators;
-        Ok(())
-    }
-
-    fn deserialize_symbol(
-        &self,
-        serialized_symbol: proto::Symbol,
-    ) -> impl Future<Output = Result<Symbol>> {
-        let languages = self.languages.clone();
-        async move {
-            let source_worktree_id = WorktreeId::from_proto(serialized_symbol.source_worktree_id);
-            let worktree_id = WorktreeId::from_proto(serialized_symbol.worktree_id);
-            let start = serialized_symbol
-                .start
-                .ok_or_else(|| anyhow!("invalid start"))?;
-            let end = serialized_symbol
-                .end
-                .ok_or_else(|| anyhow!("invalid end"))?;
-            let kind = unsafe { mem::transmute(serialized_symbol.kind) };
-            let path = ProjectPath {
-                worktree_id,
-                path: PathBuf::from(serialized_symbol.path).into(),
-            };
-            let language = languages
-                .language_for_file(&path.path, None)
-                .await
-                .log_err();
-            Ok(Symbol {
-                language_server_name: LanguageServerName(
-                    serialized_symbol.language_server_name.into(),
-                ),
-                source_worktree_id,
-                path,
-                label: {
-                    match language {
-                        Some(language) => {
-                            language
-                                .label_for_symbol(&serialized_symbol.name, kind)
-                                .await
-                        }
-                        None => None,
-                    }
-                    .unwrap_or_else(|| CodeLabel::plain(serialized_symbol.name.clone(), None))
-                },
-
-                name: serialized_symbol.name,
-                range: Unclipped(PointUtf16::new(start.row, start.column))
-                    ..Unclipped(PointUtf16::new(end.row, end.column)),
-                kind,
-                signature: serialized_symbol
-                    .signature
-                    .try_into()
-                    .map_err(|_| anyhow!("invalid signature"))?,
-            })
-        }
-    }
-
-    async fn handle_buffer_saved(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::BufferSaved>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        let fingerprint = deserialize_fingerprint(&envelope.payload.fingerprint)?;
-        let version = deserialize_version(&envelope.payload.version);
-        let mtime = envelope
-            .payload
-            .mtime
-            .ok_or_else(|| anyhow!("missing mtime"))?
-            .into();
-
-        this.update(&mut cx, |this, cx| {
-            let buffer = this
-                .opened_buffers
-                .get(&envelope.payload.buffer_id)
-                .and_then(|buffer| buffer.upgrade())
-                .or_else(|| {
-                    this.incomplete_remote_buffers
-                        .get(&envelope.payload.buffer_id)
-                        .and_then(|b| b.clone())
-                });
-            if let Some(buffer) = buffer {
-                buffer.update(cx, |buffer, cx| {
-                    buffer.did_save(version, fingerprint, mtime, cx);
-                });
-            }
-            Ok(())
-        })?
-    }
-
-    async fn handle_buffer_reloaded(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::BufferReloaded>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        let payload = envelope.payload;
-        let version = deserialize_version(&payload.version);
-        let fingerprint = deserialize_fingerprint(&payload.fingerprint)?;
-        let line_ending = deserialize_line_ending(
-            proto::LineEnding::from_i32(payload.line_ending)
-                .ok_or_else(|| anyhow!("missing line ending"))?,
-        );
-        let mtime = payload
-            .mtime
-            .ok_or_else(|| anyhow!("missing mtime"))?
-            .into();
-        this.update(&mut cx, |this, cx| {
-            let buffer = this
-                .opened_buffers
-                .get(&payload.buffer_id)
-                .and_then(|buffer| buffer.upgrade())
-                .or_else(|| {
-                    this.incomplete_remote_buffers
-                        .get(&payload.buffer_id)
-                        .cloned()
-                        .flatten()
-                });
-            if let Some(buffer) = buffer {
-                buffer.update(cx, |buffer, cx| {
-                    buffer.did_reload(version, fingerprint, line_ending, mtime, cx);
-                });
-            }
-            Ok(())
-        })?
-    }
-
-    #[allow(clippy::type_complexity)]
-    fn edits_from_lsp(
-        &mut self,
-        buffer: &Model<Buffer>,
-        lsp_edits: impl 'static + Send + IntoIterator<Item = lsp::TextEdit>,
-        server_id: LanguageServerId,
-        version: Option<i32>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<(Range<Anchor>, String)>>> {
-        let snapshot = self.buffer_snapshot_for_lsp_version(buffer, server_id, version, cx);
-        cx.background_executor().spawn(async move {
-            let snapshot = snapshot?;
-            let mut lsp_edits = lsp_edits
-                .into_iter()
-                .map(|edit| (range_from_lsp(edit.range), edit.new_text))
-                .collect::<Vec<_>>();
-            lsp_edits.sort_by_key(|(range, _)| range.start);
-
-            let mut lsp_edits = lsp_edits.into_iter().peekable();
-            let mut edits = Vec::new();
-            while let Some((range, mut new_text)) = lsp_edits.next() {
-                // Clip invalid ranges provided by the language server.
-                let mut range = snapshot.clip_point_utf16(range.start, Bias::Left)
-                    ..snapshot.clip_point_utf16(range.end, Bias::Left);
-
-                // Combine any LSP edits that are adjacent.
-                //
-                // Also, combine LSP edits that are separated from each other by only
-                // a newline. This is important because for some code actions,
-                // Rust-analyzer rewrites the entire buffer via a series of edits that
-                // are separated by unchanged newline characters.
-                //
-                // In order for the diffing logic below to work properly, any edits that
-                // cancel each other out must be combined into one.
-                while let Some((next_range, next_text)) = lsp_edits.peek() {
-                    if next_range.start.0 > range.end {
-                        if next_range.start.0.row > range.end.row + 1
-                            || next_range.start.0.column > 0
-                            || snapshot.clip_point_utf16(
-                                Unclipped(PointUtf16::new(range.end.row, u32::MAX)),
-                                Bias::Left,
-                            ) > range.end
-                        {
-                            break;
-                        }
-                        new_text.push('\n');
-                    }
-                    range.end = snapshot.clip_point_utf16(next_range.end, Bias::Left);
-                    new_text.push_str(next_text);
-                    lsp_edits.next();
-                }
-
-                // For multiline edits, perform a diff of the old and new text so that
-                // we can identify the changes more precisely, preserving the locations
-                // of any anchors positioned in the unchanged regions.
-                if range.end.row > range.start.row {
-                    let mut offset = range.start.to_offset(&snapshot);
-                    let old_text = snapshot.text_for_range(range).collect::<String>();
-
-                    let diff = TextDiff::from_lines(old_text.as_str(), &new_text);
-                    let mut moved_since_edit = true;
-                    for change in diff.iter_all_changes() {
-                        let tag = change.tag();
-                        let value = change.value();
-                        match tag {
-                            ChangeTag::Equal => {
-                                offset += value.len();
-                                moved_since_edit = true;
-                            }
-                            ChangeTag::Delete => {
-                                let start = snapshot.anchor_after(offset);
-                                let end = snapshot.anchor_before(offset + value.len());
-                                if moved_since_edit {
-                                    edits.push((start..end, String::new()));
-                                } else {
-                                    edits.last_mut().unwrap().0.end = end;
-                                }
-                                offset += value.len();
-                                moved_since_edit = false;
-                            }
-                            ChangeTag::Insert => {
-                                if moved_since_edit {
-                                    let anchor = snapshot.anchor_after(offset);
-                                    edits.push((anchor..anchor, value.to_string()));
-                                } else {
-                                    edits.last_mut().unwrap().1.push_str(value);
-                                }
-                                moved_since_edit = false;
-                            }
-                        }
-                    }
-                } else if range.end == range.start {
-                    let anchor = snapshot.anchor_after(range.start);
-                    edits.push((anchor..anchor, new_text));
-                } else {
-                    let edit_start = snapshot.anchor_after(range.start);
-                    let edit_end = snapshot.anchor_before(range.end);
-                    edits.push((edit_start..edit_end, new_text));
-                }
-            }
-
-            Ok(edits)
-        })
-    }
-
-    fn buffer_snapshot_for_lsp_version(
-        &mut self,
-        buffer: &Model<Buffer>,
-        server_id: LanguageServerId,
-        version: Option<i32>,
-        cx: &AppContext,
-    ) -> Result<TextBufferSnapshot> {
-        const OLD_VERSIONS_TO_RETAIN: i32 = 10;
-
-        if let Some(version) = version {
-            let buffer_id = buffer.read(cx).remote_id();
-            let snapshots = self
-                .buffer_snapshots
-                .get_mut(&buffer_id)
-                .and_then(|m| m.get_mut(&server_id))
-                .ok_or_else(|| {
-                    anyhow!("no snapshots found for buffer {buffer_id} and server {server_id}")
-                })?;
-
-            let found_snapshot = snapshots
-                .binary_search_by_key(&version, |e| e.version)
-                .map(|ix| snapshots[ix].snapshot.clone())
-                .map_err(|_| {
-                    anyhow!("snapshot not found for buffer {buffer_id} server {server_id} at version {version}")
-                })?;
-
-            snapshots.retain(|snapshot| snapshot.version + OLD_VERSIONS_TO_RETAIN >= version);
-            Ok(found_snapshot)
-        } else {
-            Ok((buffer.read(cx)).text_snapshot())
-        }
-    }
-
-    pub fn language_servers(
-        &self,
-    ) -> impl '_ + Iterator<Item = (LanguageServerId, LanguageServerName, WorktreeId)> {
-        self.language_server_ids
-            .iter()
-            .map(|((worktree_id, server_name), server_id)| {
-                (*server_id, server_name.clone(), *worktree_id)
-            })
-    }
-
-    pub fn supplementary_language_servers(
-        &self,
-    ) -> impl '_
-           + Iterator<
-        Item = (
-            &LanguageServerId,
-            &(LanguageServerName, Arc<LanguageServer>),
-        ),
-    > {
-        self.supplementary_language_servers.iter()
-    }
-
-    pub fn language_server_for_id(&self, id: LanguageServerId) -> Option<Arc<LanguageServer>> {
-        if let Some(LanguageServerState::Running { server, .. }) = self.language_servers.get(&id) {
-            Some(server.clone())
-        } else if let Some((_, server)) = self.supplementary_language_servers.get(&id) {
-            Some(Arc::clone(server))
-        } else {
-            None
-        }
-    }
-
-    pub fn language_servers_for_buffer(
-        &self,
-        buffer: &Buffer,
-        cx: &AppContext,
-    ) -> impl Iterator<Item = (&Arc<CachedLspAdapter>, &Arc<LanguageServer>)> {
-        self.language_server_ids_for_buffer(buffer, cx)
-            .into_iter()
-            .filter_map(|server_id| match self.language_servers.get(&server_id)? {
-                LanguageServerState::Running {
-                    adapter, server, ..
-                } => Some((adapter, server)),
-                _ => None,
-            })
-    }
-
-    fn primary_language_server_for_buffer(
-        &self,
-        buffer: &Buffer,
-        cx: &AppContext,
-    ) -> Option<(&Arc<CachedLspAdapter>, &Arc<LanguageServer>)> {
-        self.language_servers_for_buffer(buffer, cx).next()
-    }
-
-    pub fn language_server_for_buffer(
-        &self,
-        buffer: &Buffer,
-        server_id: LanguageServerId,
-        cx: &AppContext,
-    ) -> Option<(&Arc<CachedLspAdapter>, &Arc<LanguageServer>)> {
-        self.language_servers_for_buffer(buffer, cx)
-            .find(|(_, s)| s.server_id() == server_id)
-    }
-
-    fn language_server_ids_for_buffer(
-        &self,
-        buffer: &Buffer,
-        cx: &AppContext,
-    ) -> Vec<LanguageServerId> {
-        if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) {
-            let worktree_id = file.worktree_id(cx);
-            language
-                .lsp_adapters()
-                .iter()
-                .flat_map(|adapter| {
-                    let key = (worktree_id, adapter.name.clone());
-                    self.language_server_ids.get(&key).copied()
-                })
-                .collect()
-        } else {
-            Vec::new()
-        }
-    }
-}
-
-fn subscribe_for_copilot_events(
-    copilot: &Model<Copilot>,
-    cx: &mut ModelContext<'_, Project>,
-) -> gpui::Subscription {
-    cx.subscribe(
-        copilot,
-        |project, copilot, copilot_event, cx| match copilot_event {
-            copilot::Event::CopilotLanguageServerStarted => {
-                match copilot.read(cx).language_server() {
-                    Some((name, copilot_server)) => {
-                        // Another event wants to re-add the server that was already added and subscribed to, avoid doing it again.
-                        if !copilot_server.has_notification_handler::<copilot::request::LogMessage>() {
-                            let new_server_id = copilot_server.server_id();
-                            let weak_project = cx.weak_model();
-                            let copilot_log_subscription = copilot_server
-                                .on_notification::<copilot::request::LogMessage, _>(
-                                    move |params, mut cx| {
-                                        weak_project.update(&mut cx, |_, cx| {
-                                            cx.emit(Event::LanguageServerLog(
-                                                new_server_id,
-                                                params.message,
-                                            ));
-                                        }).ok();
-                                    },
-                                );
-                            project.supplementary_language_servers.insert(new_server_id, (name.clone(), Arc::clone(copilot_server)));
-                            project.copilot_log_subscription = Some(copilot_log_subscription);
-                            cx.emit(Event::LanguageServerAdded(new_server_id));
-                        }
-                    }
-                    None => debug_panic!("Received Copilot language server started event, but no language server is running"),
-                }
-            }
-        },
-    )
-}
-
-fn glob_literal_prefix<'a>(glob: &'a str) -> &'a str {
-    let mut literal_end = 0;
-    for (i, part) in glob.split(path::MAIN_SEPARATOR).enumerate() {
-        if part.contains(&['*', '?', '{', '}']) {
-            break;
-        } else {
-            if i > 0 {
-                // Acount for separator prior to this part
-                literal_end += path::MAIN_SEPARATOR.len_utf8();
-            }
-            literal_end += part.len();
-        }
-    }
-    &glob[..literal_end]
-}
-
-impl WorktreeHandle {
-    pub fn upgrade(&self) -> Option<Model<Worktree>> {
-        match self {
-            WorktreeHandle::Strong(handle) => Some(handle.clone()),
-            WorktreeHandle::Weak(handle) => handle.upgrade(),
-        }
-    }
-
-    pub fn handle_id(&self) -> usize {
-        match self {
-            WorktreeHandle::Strong(handle) => handle.entity_id().as_u64() as usize,
-            WorktreeHandle::Weak(handle) => handle.entity_id().as_u64() as usize,
-        }
-    }
-}
-
-impl OpenBuffer {
-    pub fn upgrade(&self) -> Option<Model<Buffer>> {
-        match self {
-            OpenBuffer::Strong(handle) => Some(handle.clone()),
-            OpenBuffer::Weak(handle) => handle.upgrade(),
-            OpenBuffer::Operations(_) => None,
-        }
-    }
-}
-
-pub struct PathMatchCandidateSet {
-    pub snapshot: Snapshot,
-    pub include_ignored: bool,
-    pub include_root_name: bool,
-}
-
-impl<'a> fuzzy::PathMatchCandidateSet<'a> for PathMatchCandidateSet {
-    type Candidates = PathMatchCandidateSetIter<'a>;
-
-    fn id(&self) -> usize {
-        self.snapshot.id().to_usize()
-    }
-
-    fn len(&self) -> usize {
-        if self.include_ignored {
-            self.snapshot.file_count()
-        } else {
-            self.snapshot.visible_file_count()
-        }
-    }
-
-    fn prefix(&self) -> Arc<str> {
-        if self.snapshot.root_entry().map_or(false, |e| e.is_file()) {
-            self.snapshot.root_name().into()
-        } else if self.include_root_name {
-            format!("{}/", self.snapshot.root_name()).into()
-        } else {
-            "".into()
-        }
-    }
-
-    fn candidates(&'a self, start: usize) -> Self::Candidates {
-        PathMatchCandidateSetIter {
-            traversal: self.snapshot.files(self.include_ignored, start),
-        }
-    }
-}
-
-pub struct PathMatchCandidateSetIter<'a> {
-    traversal: Traversal<'a>,
-}
-
-impl<'a> Iterator for PathMatchCandidateSetIter<'a> {
-    type Item = fuzzy::PathMatchCandidate<'a>;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        self.traversal.next().map(|entry| {
-            if let EntryKind::File(char_bag) = entry.kind {
-                fuzzy::PathMatchCandidate {
-                    path: &entry.path,
-                    char_bag,
-                }
-            } else {
-                unreachable!()
-            }
-        })
-    }
-}
-
-impl EventEmitter<Event> for Project {}
-
-impl<P: AsRef<Path>> From<(WorktreeId, P)> for ProjectPath {
-    fn from((worktree_id, path): (WorktreeId, P)) -> Self {
-        Self {
-            worktree_id,
-            path: path.as_ref().into(),
-        }
-    }
-}
-
-impl ProjectLspAdapterDelegate {
-    fn new(project: &Project, cx: &ModelContext<Project>) -> Arc<Self> {
-        Arc::new(Self {
-            project: cx.handle(),
-            http_client: project.client.http_client(),
-        })
-    }
-}
-
-impl LspAdapterDelegate for ProjectLspAdapterDelegate {
-    fn show_notification(&self, message: &str, cx: &mut AppContext) {
-        self.project
-            .update(cx, |_, cx| cx.emit(Event::Notification(message.to_owned())));
-    }
-
-    fn http_client(&self) -> Arc<dyn HttpClient> {
-        self.http_client.clone()
-    }
-}
-
-fn serialize_symbol(symbol: &Symbol) -> proto::Symbol {
-    proto::Symbol {
-        language_server_name: symbol.language_server_name.0.to_string(),
-        source_worktree_id: symbol.source_worktree_id.to_proto(),
-        worktree_id: symbol.path.worktree_id.to_proto(),
-        path: symbol.path.path.to_string_lossy().to_string(),
-        name: symbol.name.clone(),
-        kind: unsafe { mem::transmute(symbol.kind) },
-        start: Some(proto::PointUtf16 {
-            row: symbol.range.start.0.row,
-            column: symbol.range.start.0.column,
-        }),
-        end: Some(proto::PointUtf16 {
-            row: symbol.range.end.0.row,
-            column: symbol.range.end.0.column,
-        }),
-        signature: symbol.signature.to_vec(),
-    }
-}
-
-fn relativize_path(base: &Path, path: &Path) -> PathBuf {
-    let mut path_components = path.components();
-    let mut base_components = base.components();
-    let mut components: Vec<Component> = Vec::new();
-    loop {
-        match (path_components.next(), base_components.next()) {
-            (None, None) => break,
-            (Some(a), None) => {
-                components.push(a);
-                components.extend(path_components.by_ref());
-                break;
-            }
-            (None, _) => components.push(Component::ParentDir),
-            (Some(a), Some(b)) if components.is_empty() && a == b => (),
-            (Some(a), Some(b)) if b == Component::CurDir => components.push(a),
-            (Some(a), Some(_)) => {
-                components.push(Component::ParentDir);
-                for _ in base_components {
-                    components.push(Component::ParentDir);
-                }
-                components.push(a);
-                components.extend(path_components.by_ref());
-                break;
-            }
-        }
-    }
-    components.iter().map(|c| c.as_os_str()).collect()
-}
-
-impl Item for Buffer {
-    fn entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId> {
-        File::from_dyn(self.file()).and_then(|file| file.project_entry_id(cx))
-    }
-
-    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
-        File::from_dyn(self.file()).map(|file| ProjectPath {
-            worktree_id: file.worktree_id(cx),
-            path: file.path().clone(),
-        })
-    }
-}
-
-async fn wait_for_loading_buffer(
-    mut receiver: postage::watch::Receiver<Option<Result<Model<Buffer>, Arc<anyhow::Error>>>>,
-) -> Result<Model<Buffer>, Arc<anyhow::Error>> {
-    loop {
-        if let Some(result) = receiver.borrow().as_ref() {
-            match result {
-                Ok(buffer) => return Ok(buffer.to_owned()),
-                Err(e) => return Err(e.to_owned()),
-            }
-        }
-        receiver.next().await;
-    }
-}
-
-fn include_text(server: &lsp::LanguageServer) -> bool {
-    server
-        .capabilities()
-        .text_document_sync
-        .as_ref()
-        .and_then(|sync| match sync {
-            lsp::TextDocumentSyncCapability::Kind(_) => None,
-            lsp::TextDocumentSyncCapability::Options(options) => options.save.as_ref(),
-        })
-        .and_then(|save_options| match save_options {
-            lsp::TextDocumentSyncSaveOptions::Supported(_) => None,
-            lsp::TextDocumentSyncSaveOptions::SaveOptions(options) => options.include_text,
-        })
-        .unwrap_or(false)
-}

crates/project2/src/project_settings.rs 🔗

@@ -1,50 +0,0 @@
-use collections::HashMap;
-use gpui::AppContext;
-use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
-use settings::Settings;
-use std::sync::Arc;
-
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
-pub struct ProjectSettings {
-    #[serde(default)]
-    pub lsp: HashMap<Arc<str>, LspSettings>,
-    #[serde(default)]
-    pub git: GitSettings,
-    #[serde(default)]
-    pub file_scan_exclusions: Option<Vec<String>>,
-}
-
-#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
-pub struct GitSettings {
-    pub git_gutter: Option<GitGutterSetting>,
-    pub gutter_debounce: Option<u64>,
-}
-
-#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum GitGutterSetting {
-    #[default]
-    TrackedFiles,
-    Hide,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub struct LspSettings {
-    pub initialization_options: Option<serde_json::Value>,
-}
-
-impl Settings for ProjectSettings {
-    const KEY: Option<&'static str> = None;
-
-    type FileContent = Self;
-
-    fn load(
-        default_value: &Self::FileContent,
-        user_values: &[&Self::FileContent],
-        _: &mut AppContext,
-    ) -> anyhow::Result<Self> {
-        Self::load_via_json_merge(default_value, user_values)
-    }
-}

crates/project2/src/project_tests.rs 🔗

@@ -1,4317 +0,0 @@
-use crate::{Event, *};
-use fs::FakeFs;
-use futures::{future, StreamExt};
-use gpui::AppContext;
-use language::{
-    language_settings::{AllLanguageSettings, LanguageSettingsContent},
-    tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig,
-    LineEnding, OffsetRangeExt, Point, ToPoint,
-};
-use lsp::Url;
-use parking_lot::Mutex;
-use pretty_assertions::assert_eq;
-use serde_json::json;
-use std::{os, task::Poll};
-use unindent::Unindent as _;
-use util::{assert_set_eq, paths::PathMatcher, test::temp_tree};
-
-#[gpui::test]
-async fn test_block_via_channel(cx: &mut gpui::TestAppContext) {
-    cx.executor().allow_parking();
-
-    let (tx, mut rx) = futures::channel::mpsc::unbounded();
-    let _thread = std::thread::spawn(move || {
-        std::fs::metadata("/Users").unwrap();
-        std::thread::sleep(Duration::from_millis(1000));
-        tx.unbounded_send(1).unwrap();
-    });
-    rx.next().await.unwrap();
-}
-
-#[gpui::test]
-async fn test_block_via_smol(cx: &mut gpui::TestAppContext) {
-    cx.executor().allow_parking();
-
-    let io_task = smol::unblock(move || {
-        println!("sleeping on thread {:?}", std::thread::current().id());
-        std::thread::sleep(Duration::from_millis(10));
-        1
-    });
-
-    let task = cx.foreground_executor().spawn(async move {
-        io_task.await;
-    });
-
-    task.await;
-}
-
-#[gpui::test]
-async fn test_symlinks(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-    cx.executor().allow_parking();
-
-    let dir = temp_tree(json!({
-        "root": {
-            "apple": "",
-            "banana": {
-                "carrot": {
-                    "date": "",
-                    "endive": "",
-                }
-            },
-            "fennel": {
-                "grape": "",
-            }
-        }
-    }));
-
-    let root_link_path = dir.path().join("root_link");
-    os::unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
-    os::unix::fs::symlink(
-        &dir.path().join("root/fennel"),
-        &dir.path().join("root/finnochio"),
-    )
-    .unwrap();
-
-    let project = Project::test(Arc::new(RealFs), [root_link_path.as_ref()], cx).await;
-
-    project.update(cx, |project, cx| {
-        let tree = project.worktrees().next().unwrap().read(cx);
-        assert_eq!(tree.file_count(), 5);
-        assert_eq!(
-            tree.inode_for_path("fennel/grape"),
-            tree.inode_for_path("finnochio/grape")
-        );
-    });
-}
-
-#[gpui::test]
-async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/the-root",
-        json!({
-            ".zed": {
-                "settings.json": r#"{ "tab_size": 8 }"#
-            },
-            "a": {
-                "a.rs": "fn a() {\n    A\n}"
-            },
-            "b": {
-                ".zed": {
-                    "settings.json": r#"{ "tab_size": 2 }"#
-                },
-                "b.rs": "fn b() {\n  B\n}"
-            }
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;
-    let worktree = project.update(cx, |project, _| project.worktrees().next().unwrap());
-
-    cx.executor().run_until_parked();
-    cx.update(|cx| {
-        let tree = worktree.read(cx);
-
-        let settings_a = language_settings(
-            None,
-            Some(
-                &(File::for_entry(
-                    tree.entry_for_path("a/a.rs").unwrap().clone(),
-                    worktree.clone(),
-                ) as _),
-            ),
-            cx,
-        );
-        let settings_b = language_settings(
-            None,
-            Some(
-                &(File::for_entry(
-                    tree.entry_for_path("b/b.rs").unwrap().clone(),
-                    worktree.clone(),
-                ) as _),
-            ),
-            cx,
-        );
-
-        assert_eq!(settings_a.tab_size.get(), 8);
-        assert_eq!(settings_b.tab_size.get(), 2);
-    });
-}
-
-#[gpui::test]
-async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let mut rust_language = Language::new(
-        LanguageConfig {
-            name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
-            ..Default::default()
-        },
-        Some(tree_sitter_rust::language()),
-    );
-    let mut json_language = Language::new(
-        LanguageConfig {
-            name: "JSON".into(),
-            path_suffixes: vec!["json".to_string()],
-            ..Default::default()
-        },
-        None,
-    );
-    let mut fake_rust_servers = rust_language
-        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-            name: "the-rust-language-server",
-            capabilities: lsp::ServerCapabilities {
-                completion_provider: Some(lsp::CompletionOptions {
-                    trigger_characters: Some(vec![".".to_string(), "::".to_string()]),
-                    ..Default::default()
-                }),
-                ..Default::default()
-            },
-            ..Default::default()
-        }))
-        .await;
-    let mut fake_json_servers = json_language
-        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-            name: "the-json-language-server",
-            capabilities: lsp::ServerCapabilities {
-                completion_provider: Some(lsp::CompletionOptions {
-                    trigger_characters: Some(vec![":".to_string()]),
-                    ..Default::default()
-                }),
-                ..Default::default()
-            },
-            ..Default::default()
-        }))
-        .await;
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/the-root",
-        json!({
-            "test.rs": "const A: i32 = 1;",
-            "test2.rs": "",
-            "Cargo.toml": "a = 1",
-            "package.json": "{\"a\": 1}",
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;
-
-    // Open a buffer without an associated language server.
-    let toml_buffer = project
-        .update(cx, |project, cx| {
-            project.open_local_buffer("/the-root/Cargo.toml", cx)
-        })
-        .await
-        .unwrap();
-
-    // Open a buffer with an associated language server before the language for it has been loaded.
-    let rust_buffer = project
-        .update(cx, |project, cx| {
-            project.open_local_buffer("/the-root/test.rs", cx)
-        })
-        .await
-        .unwrap();
-    rust_buffer.update(cx, |buffer, _| {
-        assert_eq!(buffer.language().map(|l| l.name()), None);
-    });
-
-    // Now we add the languages to the project, and ensure they get assigned to all
-    // the relevant open buffers.
-    project.update(cx, |project, _| {
-        project.languages.add(Arc::new(json_language));
-        project.languages.add(Arc::new(rust_language));
-    });
-    cx.executor().run_until_parked();
-    rust_buffer.update(cx, |buffer, _| {
-        assert_eq!(buffer.language().map(|l| l.name()), Some("Rust".into()));
-    });
-
-    // A server is started up, and it is notified about Rust files.
-    let mut fake_rust_server = fake_rust_servers.next().await.unwrap();
-    assert_eq!(
-        fake_rust_server
-            .receive_notification::<lsp::notification::DidOpenTextDocument>()
-            .await
-            .text_document,
-        lsp::TextDocumentItem {
-            uri: lsp::Url::from_file_path("/the-root/test.rs").unwrap(),
-            version: 0,
-            text: "const A: i32 = 1;".to_string(),
-            language_id: Default::default()
-        }
-    );
-
-    // The buffer is configured based on the language server's capabilities.
-    rust_buffer.update(cx, |buffer, _| {
-        assert_eq!(
-            buffer.completion_triggers(),
-            &[".".to_string(), "::".to_string()]
-        );
-    });
-    toml_buffer.update(cx, |buffer, _| {
-        assert!(buffer.completion_triggers().is_empty());
-    });
-
-    // Edit a buffer. The changes are reported to the language server.
-    rust_buffer.update(cx, |buffer, cx| buffer.edit([(16..16, "2")], None, cx));
-    assert_eq!(
-        fake_rust_server
-            .receive_notification::<lsp::notification::DidChangeTextDocument>()
-            .await
-            .text_document,
-        lsp::VersionedTextDocumentIdentifier::new(
-            lsp::Url::from_file_path("/the-root/test.rs").unwrap(),
-            1
-        )
-    );
-
-    // Open a third buffer with a different associated language server.
-    let json_buffer = project
-        .update(cx, |project, cx| {
-            project.open_local_buffer("/the-root/package.json", cx)
-        })
-        .await
-        .unwrap();
-
-    // A json language server is started up and is only notified about the json buffer.
-    let mut fake_json_server = fake_json_servers.next().await.unwrap();
-    assert_eq!(
-        fake_json_server
-            .receive_notification::<lsp::notification::DidOpenTextDocument>()
-            .await
-            .text_document,
-        lsp::TextDocumentItem {
-            uri: lsp::Url::from_file_path("/the-root/package.json").unwrap(),
-            version: 0,
-            text: "{\"a\": 1}".to_string(),
-            language_id: Default::default()
-        }
-    );
-
-    // This buffer is configured based on the second language server's
-    // capabilities.
-    json_buffer.update(cx, |buffer, _| {
-        assert_eq!(buffer.completion_triggers(), &[":".to_string()]);
-    });
-
-    // When opening another buffer whose language server is already running,
-    // it is also configured based on the existing language server's capabilities.
-    let rust_buffer2 = project
-        .update(cx, |project, cx| {
-            project.open_local_buffer("/the-root/test2.rs", cx)
-        })
-        .await
-        .unwrap();
-    rust_buffer2.update(cx, |buffer, _| {
-        assert_eq!(
-            buffer.completion_triggers(),
-            &[".".to_string(), "::".to_string()]
-        );
-    });
-
-    // Changes are reported only to servers matching the buffer's language.
-    toml_buffer.update(cx, |buffer, cx| buffer.edit([(5..5, "23")], None, cx));
-    rust_buffer2.update(cx, |buffer, cx| {
-        buffer.edit([(0..0, "let x = 1;")], None, cx)
-    });
-    assert_eq!(
-        fake_rust_server
-            .receive_notification::<lsp::notification::DidChangeTextDocument>()
-            .await
-            .text_document,
-        lsp::VersionedTextDocumentIdentifier::new(
-            lsp::Url::from_file_path("/the-root/test2.rs").unwrap(),
-            1
-        )
-    );
-
-    // Save notifications are reported to all servers.
-    project
-        .update(cx, |project, cx| project.save_buffer(toml_buffer, cx))
-        .await
-        .unwrap();
-    assert_eq!(
-        fake_rust_server
-            .receive_notification::<lsp::notification::DidSaveTextDocument>()
-            .await
-            .text_document,
-        lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path("/the-root/Cargo.toml").unwrap())
-    );
-    assert_eq!(
-        fake_json_server
-            .receive_notification::<lsp::notification::DidSaveTextDocument>()
-            .await
-            .text_document,
-        lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path("/the-root/Cargo.toml").unwrap())
-    );
-
-    // Renames are reported only to servers matching the buffer's language.
-    fs.rename(
-        Path::new("/the-root/test2.rs"),
-        Path::new("/the-root/test3.rs"),
-        Default::default(),
-    )
-    .await
-    .unwrap();
-    assert_eq!(
-        fake_rust_server
-            .receive_notification::<lsp::notification::DidCloseTextDocument>()
-            .await
-            .text_document,
-        lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path("/the-root/test2.rs").unwrap()),
-    );
-    assert_eq!(
-        fake_rust_server
-            .receive_notification::<lsp::notification::DidOpenTextDocument>()
-            .await
-            .text_document,
-        lsp::TextDocumentItem {
-            uri: lsp::Url::from_file_path("/the-root/test3.rs").unwrap(),
-            version: 0,
-            text: rust_buffer2.update(cx, |buffer, _| buffer.text()),
-            language_id: Default::default()
-        },
-    );
-
-    rust_buffer2.update(cx, |buffer, cx| {
-        buffer.update_diagnostics(
-            LanguageServerId(0),
-            DiagnosticSet::from_sorted_entries(
-                vec![DiagnosticEntry {
-                    diagnostic: Default::default(),
-                    range: Anchor::MIN..Anchor::MAX,
-                }],
-                &buffer.snapshot(),
-            ),
-            cx,
-        );
-        assert_eq!(
-            buffer
-                .snapshot()
-                .diagnostics_in_range::<_, usize>(0..buffer.len(), false)
-                .count(),
-            1
-        );
-    });
-
-    // When the rename changes the extension of the file, the buffer gets closed on the old
-    // language server and gets opened on the new one.
-    fs.rename(
-        Path::new("/the-root/test3.rs"),
-        Path::new("/the-root/test3.json"),
-        Default::default(),
-    )
-    .await
-    .unwrap();
-    assert_eq!(
-        fake_rust_server
-            .receive_notification::<lsp::notification::DidCloseTextDocument>()
-            .await
-            .text_document,
-        lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path("/the-root/test3.rs").unwrap(),),
-    );
-    assert_eq!(
-        fake_json_server
-            .receive_notification::<lsp::notification::DidOpenTextDocument>()
-            .await
-            .text_document,
-        lsp::TextDocumentItem {
-            uri: lsp::Url::from_file_path("/the-root/test3.json").unwrap(),
-            version: 0,
-            text: rust_buffer2.update(cx, |buffer, _| buffer.text()),
-            language_id: Default::default()
-        },
-    );
-
-    // We clear the diagnostics, since the language has changed.
-    rust_buffer2.update(cx, |buffer, _| {
-        assert_eq!(
-            buffer
-                .snapshot()
-                .diagnostics_in_range::<_, usize>(0..buffer.len(), false)
-                .count(),
-            0
-        );
-    });
-
-    // The renamed file's version resets after changing language server.
-    rust_buffer2.update(cx, |buffer, cx| buffer.edit([(0..0, "// ")], None, cx));
-    assert_eq!(
-        fake_json_server
-            .receive_notification::<lsp::notification::DidChangeTextDocument>()
-            .await
-            .text_document,
-        lsp::VersionedTextDocumentIdentifier::new(
-            lsp::Url::from_file_path("/the-root/test3.json").unwrap(),
-            1
-        )
-    );
-
-    // Restart language servers
-    project.update(cx, |project, cx| {
-        project.restart_language_servers_for_buffers(
-            vec![rust_buffer.clone(), json_buffer.clone()],
-            cx,
-        );
-    });
-
-    let mut rust_shutdown_requests = fake_rust_server
-        .handle_request::<lsp::request::Shutdown, _, _>(|_, _| future::ready(Ok(())));
-    let mut json_shutdown_requests = fake_json_server
-        .handle_request::<lsp::request::Shutdown, _, _>(|_, _| future::ready(Ok(())));
-    futures::join!(rust_shutdown_requests.next(), json_shutdown_requests.next());
-
-    let mut fake_rust_server = fake_rust_servers.next().await.unwrap();
-    let mut fake_json_server = fake_json_servers.next().await.unwrap();
-
-    // Ensure rust document is reopened in new rust language server
-    assert_eq!(
-        fake_rust_server
-            .receive_notification::<lsp::notification::DidOpenTextDocument>()
-            .await
-            .text_document,
-        lsp::TextDocumentItem {
-            uri: lsp::Url::from_file_path("/the-root/test.rs").unwrap(),
-            version: 0,
-            text: rust_buffer.update(cx, |buffer, _| buffer.text()),
-            language_id: Default::default()
-        }
-    );
-
-    // Ensure json documents are reopened in new json language server
-    assert_set_eq!(
-        [
-            fake_json_server
-                .receive_notification::<lsp::notification::DidOpenTextDocument>()
-                .await
-                .text_document,
-            fake_json_server
-                .receive_notification::<lsp::notification::DidOpenTextDocument>()
-                .await
-                .text_document,
-        ],
-        [
-            lsp::TextDocumentItem {
-                uri: lsp::Url::from_file_path("/the-root/package.json").unwrap(),
-                version: 0,
-                text: json_buffer.update(cx, |buffer, _| buffer.text()),
-                language_id: Default::default()
-            },
-            lsp::TextDocumentItem {
-                uri: lsp::Url::from_file_path("/the-root/test3.json").unwrap(),
-                version: 0,
-                text: rust_buffer2.update(cx, |buffer, _| buffer.text()),
-                language_id: Default::default()
-            }
-        ]
-    );
-
-    // Close notifications are reported only to servers matching the buffer's language.
-    cx.update(|_| drop(json_buffer));
-    let close_message = lsp::DidCloseTextDocumentParams {
-        text_document: lsp::TextDocumentIdentifier::new(
-            lsp::Url::from_file_path("/the-root/package.json").unwrap(),
-        ),
-    };
-    assert_eq!(
-        fake_json_server
-            .receive_notification::<lsp::notification::DidCloseTextDocument>()
-            .await,
-        close_message,
-    );
-}
-
-#[gpui::test]
-async fn test_reporting_fs_changes_to_language_servers(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 {
-            name: "the-language-server",
-            ..Default::default()
-        }))
-        .await;
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/the-root",
-        json!({
-            ".gitignore": "target\n",
-            "src": {
-                "a.rs": "",
-                "b.rs": "",
-            },
-            "target": {
-                "x": {
-                    "out": {
-                        "x.rs": ""
-                    }
-                },
-                "y": {
-                    "out": {
-                        "y.rs": "",
-                    }
-                },
-                "z": {
-                    "out": {
-                        "z.rs": ""
-                    }
-                }
-            }
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;
-    project.update(cx, |project, _| {
-        project.languages.add(Arc::new(language));
-    });
-    cx.executor().run_until_parked();
-
-    // Start the language server by opening a buffer with a compatible file extension.
-    let _buffer = project
-        .update(cx, |project, cx| {
-            project.open_local_buffer("/the-root/src/a.rs", cx)
-        })
-        .await
-        .unwrap();
-
-    // Initially, we don't load ignored files because the language server has not explicitly asked us to watch them.
-    project.update(cx, |project, cx| {
-        let worktree = project.worktrees().next().unwrap();
-        assert_eq!(
-            worktree
-                .read(cx)
-                .snapshot()
-                .entries(true)
-                .map(|entry| (entry.path.as_ref(), entry.is_ignored))
-                .collect::<Vec<_>>(),
-            &[
-                (Path::new(""), false),
-                (Path::new(".gitignore"), false),
-                (Path::new("src"), false),
-                (Path::new("src/a.rs"), false),
-                (Path::new("src/b.rs"), false),
-                (Path::new("target"), true),
-            ]
-        );
-    });
-
-    let prev_read_dir_count = fs.read_dir_call_count();
-
-    // Keep track of the FS events reported to the language server.
-    let fake_server = fake_servers.next().await.unwrap();
-    let file_changes = Arc::new(Mutex::new(Vec::new()));
-    fake_server
-        .request::<lsp::request::RegisterCapability>(lsp::RegistrationParams {
-            registrations: vec![lsp::Registration {
-                id: Default::default(),
-                method: "workspace/didChangeWatchedFiles".to_string(),
-                register_options: serde_json::to_value(
-                    lsp::DidChangeWatchedFilesRegistrationOptions {
-                        watchers: vec![
-                            lsp::FileSystemWatcher {
-                                glob_pattern: lsp::GlobPattern::String(
-                                    "/the-root/Cargo.toml".to_string(),
-                                ),
-                                kind: None,
-                            },
-                            lsp::FileSystemWatcher {
-                                glob_pattern: lsp::GlobPattern::String(
-                                    "/the-root/src/*.{rs,c}".to_string(),
-                                ),
-                                kind: None,
-                            },
-                            lsp::FileSystemWatcher {
-                                glob_pattern: lsp::GlobPattern::String(
-                                    "/the-root/target/y/**/*.rs".to_string(),
-                                ),
-                                kind: None,
-                            },
-                        ],
-                    },
-                )
-                .ok(),
-            }],
-        })
-        .await
-        .unwrap();
-    fake_server.handle_notification::<lsp::notification::DidChangeWatchedFiles, _>({
-        let file_changes = file_changes.clone();
-        move |params, _| {
-            let mut file_changes = file_changes.lock();
-            file_changes.extend(params.changes);
-            file_changes.sort_by(|a, b| a.uri.cmp(&b.uri));
-        }
-    });
-
-    cx.executor().run_until_parked();
-    assert_eq!(mem::take(&mut *file_changes.lock()), &[]);
-    assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 4);
-
-    // Now the language server has asked us to watch an ignored directory path,
-    // so we recursively load it.
-    project.update(cx, |project, cx| {
-        let worktree = project.worktrees().next().unwrap();
-        assert_eq!(
-            worktree
-                .read(cx)
-                .snapshot()
-                .entries(true)
-                .map(|entry| (entry.path.as_ref(), entry.is_ignored))
-                .collect::<Vec<_>>(),
-            &[
-                (Path::new(""), false),
-                (Path::new(".gitignore"), false),
-                (Path::new("src"), false),
-                (Path::new("src/a.rs"), false),
-                (Path::new("src/b.rs"), false),
-                (Path::new("target"), true),
-                (Path::new("target/x"), true),
-                (Path::new("target/y"), true),
-                (Path::new("target/y/out"), true),
-                (Path::new("target/y/out/y.rs"), true),
-                (Path::new("target/z"), true),
-            ]
-        );
-    });
-
-    // Perform some file system mutations, two of which match the watched patterns,
-    // and one of which does not.
-    fs.create_file("/the-root/src/c.rs".as_ref(), Default::default())
-        .await
-        .unwrap();
-    fs.create_file("/the-root/src/d.txt".as_ref(), Default::default())
-        .await
-        .unwrap();
-    fs.remove_file("/the-root/src/b.rs".as_ref(), Default::default())
-        .await
-        .unwrap();
-    fs.create_file("/the-root/target/x/out/x2.rs".as_ref(), Default::default())
-        .await
-        .unwrap();
-    fs.create_file("/the-root/target/y/out/y2.rs".as_ref(), Default::default())
-        .await
-        .unwrap();
-
-    // The language server receives events for the FS mutations that match its watch patterns.
-    cx.executor().run_until_parked();
-    assert_eq!(
-        &*file_changes.lock(),
-        &[
-            lsp::FileEvent {
-                uri: lsp::Url::from_file_path("/the-root/src/b.rs").unwrap(),
-                typ: lsp::FileChangeType::DELETED,
-            },
-            lsp::FileEvent {
-                uri: lsp::Url::from_file_path("/the-root/src/c.rs").unwrap(),
-                typ: lsp::FileChangeType::CREATED,
-            },
-            lsp::FileEvent {
-                uri: lsp::Url::from_file_path("/the-root/target/y/out/y2.rs").unwrap(),
-                typ: lsp::FileChangeType::CREATED,
-            },
-        ]
-    );
-}
-
-#[gpui::test]
-async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "a.rs": "let a = 1;",
-            "b.rs": "let b = 2;"
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs, ["/dir/a.rs".as_ref(), "/dir/b.rs".as_ref()], cx).await;
-
-    let buffer_a = project
-        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
-        .await
-        .unwrap();
-    let buffer_b = project
-        .update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx))
-        .await
-        .unwrap();
-
-    project.update(cx, |project, cx| {
-        project
-            .update_diagnostics(
-                LanguageServerId(0),
-                lsp::PublishDiagnosticsParams {
-                    uri: Url::from_file_path("/dir/a.rs").unwrap(),
-                    version: None,
-                    diagnostics: vec![lsp::Diagnostic {
-                        range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 5)),
-                        severity: Some(lsp::DiagnosticSeverity::ERROR),
-                        message: "error 1".to_string(),
-                        ..Default::default()
-                    }],
-                },
-                &[],
-                cx,
-            )
-            .unwrap();
-        project
-            .update_diagnostics(
-                LanguageServerId(0),
-                lsp::PublishDiagnosticsParams {
-                    uri: Url::from_file_path("/dir/b.rs").unwrap(),
-                    version: None,
-                    diagnostics: vec![lsp::Diagnostic {
-                        range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 5)),
-                        severity: Some(lsp::DiagnosticSeverity::WARNING),
-                        message: "error 2".to_string(),
-                        ..Default::default()
-                    }],
-                },
-                &[],
-                cx,
-            )
-            .unwrap();
-    });
-
-    buffer_a.update(cx, |buffer, _| {
-        let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
-        assert_eq!(
-            chunks
-                .iter()
-                .map(|(s, d)| (s.as_str(), *d))
-                .collect::<Vec<_>>(),
-            &[
-                ("let ", None),
-                ("a", Some(DiagnosticSeverity::ERROR)),
-                (" = 1;", None),
-            ]
-        );
-    });
-    buffer_b.update(cx, |buffer, _| {
-        let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
-        assert_eq!(
-            chunks
-                .iter()
-                .map(|(s, d)| (s.as_str(), *d))
-                .collect::<Vec<_>>(),
-            &[
-                ("let ", None),
-                ("b", Some(DiagnosticSeverity::WARNING)),
-                (" = 2;", None),
-            ]
-        );
-    });
-}
-
-#[gpui::test]
-async fn test_omitted_diagnostics(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/root",
-        json!({
-            "dir": {
-                ".git": {
-                    "HEAD": "ref: refs/heads/main",
-                },
-                ".gitignore": "b.rs",
-                "a.rs": "let a = 1;",
-                "b.rs": "let b = 2;",
-            },
-            "other.rs": "let b = c;"
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs, ["/root/dir".as_ref()], cx).await;
-    let (worktree, _) = project
-        .update(cx, |project, cx| {
-            project.find_or_create_local_worktree("/root/dir", true, cx)
-        })
-        .await
-        .unwrap();
-    let main_worktree_id = worktree.read_with(cx, |tree, _| tree.id());
-
-    let (worktree, _) = project
-        .update(cx, |project, cx| {
-            project.find_or_create_local_worktree("/root/other.rs", false, cx)
-        })
-        .await
-        .unwrap();
-    let other_worktree_id = worktree.update(cx, |tree, _| tree.id());
-
-    let server_id = LanguageServerId(0);
-    project.update(cx, |project, cx| {
-        project
-            .update_diagnostics(
-                server_id,
-                lsp::PublishDiagnosticsParams {
-                    uri: Url::from_file_path("/root/dir/b.rs").unwrap(),
-                    version: None,
-                    diagnostics: vec![lsp::Diagnostic {
-                        range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 5)),
-                        severity: Some(lsp::DiagnosticSeverity::ERROR),
-                        message: "unused variable 'b'".to_string(),
-                        ..Default::default()
-                    }],
-                },
-                &[],
-                cx,
-            )
-            .unwrap();
-        project
-            .update_diagnostics(
-                server_id,
-                lsp::PublishDiagnosticsParams {
-                    uri: Url::from_file_path("/root/other.rs").unwrap(),
-                    version: None,
-                    diagnostics: vec![lsp::Diagnostic {
-                        range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 9)),
-                        severity: Some(lsp::DiagnosticSeverity::ERROR),
-                        message: "unknown variable 'c'".to_string(),
-                        ..Default::default()
-                    }],
-                },
-                &[],
-                cx,
-            )
-            .unwrap();
-    });
-
-    let main_ignored_buffer = project
-        .update(cx, |project, cx| {
-            project.open_buffer((main_worktree_id, "b.rs"), cx)
-        })
-        .await
-        .unwrap();
-    main_ignored_buffer.update(cx, |buffer, _| {
-        let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
-        assert_eq!(
-            chunks
-                .iter()
-                .map(|(s, d)| (s.as_str(), *d))
-                .collect::<Vec<_>>(),
-            &[
-                ("let ", None),
-                ("b", Some(DiagnosticSeverity::ERROR)),
-                (" = 2;", None),
-            ],
-            "Gigitnored buffers should still get in-buffer diagnostics",
-        );
-    });
-    let other_buffer = project
-        .update(cx, |project, cx| {
-            project.open_buffer((other_worktree_id, ""), cx)
-        })
-        .await
-        .unwrap();
-    other_buffer.update(cx, |buffer, _| {
-        let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
-        assert_eq!(
-            chunks
-                .iter()
-                .map(|(s, d)| (s.as_str(), *d))
-                .collect::<Vec<_>>(),
-            &[
-                ("let b = ", None),
-                ("c", Some(DiagnosticSeverity::ERROR)),
-                (";", None),
-            ],
-            "Buffers from hidden projects should still get in-buffer diagnostics"
-        );
-    });
-
-    project.update(cx, |project, cx| {
-        assert_eq!(project.diagnostic_summaries(false, cx).next(), None);
-        assert_eq!(
-            project.diagnostic_summaries(true, cx).collect::<Vec<_>>(),
-            vec![(
-                ProjectPath {
-                    worktree_id: main_worktree_id,
-                    path: Arc::from(Path::new("b.rs")),
-                },
-                server_id,
-                DiagnosticSummary {
-                    error_count: 1,
-                    warning_count: 0,
-                }
-            )]
-        );
-        assert_eq!(project.diagnostic_summary(false, cx).error_count, 0);
-        assert_eq!(project.diagnostic_summary(true, cx).error_count, 1);
-    });
-}
-
-#[gpui::test]
-async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let progress_token = "the-progress-token";
-    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 {
-            disk_based_diagnostics_progress_token: Some(progress_token.into()),
-            disk_based_diagnostics_sources: vec!["disk".into()],
-            ..Default::default()
-        }))
-        .await;
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "a.rs": "fn a() { A }",
-            "b.rs": "const y: i32 = 1",
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
-    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
-    let worktree_id = project.update(cx, |p, cx| p.worktrees().next().unwrap().read(cx).id());
-
-    // Cause worktree to start the fake language server
-    let _buffer = project
-        .update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx))
-        .await
-        .unwrap();
-
-    let mut events = cx.events(&project);
-
-    let fake_server = fake_servers.next().await.unwrap();
-    assert_eq!(
-        events.next().await.unwrap(),
-        Event::LanguageServerAdded(LanguageServerId(0)),
-    );
-
-    fake_server
-        .start_progress(format!("{}/0", progress_token))
-        .await;
-    assert_eq!(
-        events.next().await.unwrap(),
-        Event::DiskBasedDiagnosticsStarted {
-            language_server_id: LanguageServerId(0),
-        }
-    );
-
-    fake_server.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
-        uri: Url::from_file_path("/dir/a.rs").unwrap(),
-        version: None,
-        diagnostics: vec![lsp::Diagnostic {
-            range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
-            severity: Some(lsp::DiagnosticSeverity::ERROR),
-            message: "undefined variable 'A'".to_string(),
-            ..Default::default()
-        }],
-    });
-    assert_eq!(
-        events.next().await.unwrap(),
-        Event::DiagnosticsUpdated {
-            language_server_id: LanguageServerId(0),
-            path: (worktree_id, Path::new("a.rs")).into()
-        }
-    );
-
-    fake_server.end_progress(format!("{}/0", progress_token));
-    assert_eq!(
-        events.next().await.unwrap(),
-        Event::DiskBasedDiagnosticsFinished {
-            language_server_id: LanguageServerId(0)
-        }
-    );
-
-    let buffer = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/a.rs", cx))
-        .await
-        .unwrap();
-
-    buffer.update(cx, |buffer, _| {
-        let snapshot = buffer.snapshot();
-        let diagnostics = snapshot
-            .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
-            .collect::<Vec<_>>();
-        assert_eq!(
-            diagnostics,
-            &[DiagnosticEntry {
-                range: Point::new(0, 9)..Point::new(0, 10),
-                diagnostic: Diagnostic {
-                    severity: lsp::DiagnosticSeverity::ERROR,
-                    message: "undefined variable 'A'".to_string(),
-                    group_id: 0,
-                    is_primary: true,
-                    ..Default::default()
-                }
-            }]
-        )
-    });
-
-    // Ensure publishing empty diagnostics twice only results in one update event.
-    fake_server.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
-        uri: Url::from_file_path("/dir/a.rs").unwrap(),
-        version: None,
-        diagnostics: Default::default(),
-    });
-    assert_eq!(
-        events.next().await.unwrap(),
-        Event::DiagnosticsUpdated {
-            language_server_id: LanguageServerId(0),
-            path: (worktree_id, Path::new("a.rs")).into()
-        }
-    );
-
-    fake_server.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
-        uri: Url::from_file_path("/dir/a.rs").unwrap(),
-        version: None,
-        diagnostics: Default::default(),
-    });
-    cx.executor().run_until_parked();
-    assert_eq!(futures::poll!(events.next()), Poll::Pending);
-}
-
-#[gpui::test]
-async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let progress_token = "the-progress-token";
-    let mut language = Language::new(
-        LanguageConfig {
-            path_suffixes: vec!["rs".to_string()],
-            ..Default::default()
-        },
-        None,
-    );
-    let mut fake_servers = language
-        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-            disk_based_diagnostics_sources: vec!["disk".into()],
-            disk_based_diagnostics_progress_token: Some(progress_token.into()),
-            ..Default::default()
-        }))
-        .await;
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree("/dir", json!({ "a.rs": "" })).await;
-
-    let project = Project::test(fs, ["/dir".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("/dir/a.rs", cx))
-        .await
-        .unwrap();
-
-    // Simulate diagnostics starting to update.
-    let fake_server = fake_servers.next().await.unwrap();
-    fake_server.start_progress(progress_token).await;
-
-    // Restart the server before the diagnostics finish updating.
-    project.update(cx, |project, cx| {
-        project.restart_language_servers_for_buffers([buffer], cx);
-    });
-    let mut events = cx.events(&project);
-
-    // Simulate the newly started server sending more diagnostics.
-    let fake_server = fake_servers.next().await.unwrap();
-    assert_eq!(
-        events.next().await.unwrap(),
-        Event::LanguageServerAdded(LanguageServerId(1))
-    );
-    fake_server.start_progress(progress_token).await;
-    assert_eq!(
-        events.next().await.unwrap(),
-        Event::DiskBasedDiagnosticsStarted {
-            language_server_id: LanguageServerId(1)
-        }
-    );
-    project.update(cx, |project, _| {
-        assert_eq!(
-            project
-                .language_servers_running_disk_based_diagnostics()
-                .collect::<Vec<_>>(),
-            [LanguageServerId(1)]
-        );
-    });
-
-    // All diagnostics are considered done, despite the old server's diagnostic
-    // task never completing.
-    fake_server.end_progress(progress_token);
-    assert_eq!(
-        events.next().await.unwrap(),
-        Event::DiskBasedDiagnosticsFinished {
-            language_server_id: LanguageServerId(1)
-        }
-    );
-    project.update(cx, |project, _| {
-        assert_eq!(
-            project
-                .language_servers_running_disk_based_diagnostics()
-                .collect::<Vec<_>>(),
-            [LanguageServerId(0); 0]
-        );
-    });
-}
-
-#[gpui::test]
-async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let mut language = Language::new(
-        LanguageConfig {
-            path_suffixes: vec!["rs".to_string()],
-            ..Default::default()
-        },
-        None,
-    );
-    let mut fake_servers = language
-        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-            ..Default::default()
-        }))
-        .await;
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree("/dir", json!({ "a.rs": "x" })).await;
-
-    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
-    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
-
-    let buffer = project
-        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
-        .await
-        .unwrap();
-
-    // Publish diagnostics
-    let fake_server = fake_servers.next().await.unwrap();
-    fake_server.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
-        uri: Url::from_file_path("/dir/a.rs").unwrap(),
-        version: None,
-        diagnostics: vec![lsp::Diagnostic {
-            range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
-            severity: Some(lsp::DiagnosticSeverity::ERROR),
-            message: "the message".to_string(),
-            ..Default::default()
-        }],
-    });
-
-    cx.executor().run_until_parked();
-    buffer.update(cx, |buffer, _| {
-        assert_eq!(
-            buffer
-                .snapshot()
-                .diagnostics_in_range::<_, usize>(0..1, false)
-                .map(|entry| entry.diagnostic.message.clone())
-                .collect::<Vec<_>>(),
-            ["the message".to_string()]
-        );
-    });
-    project.update(cx, |project, cx| {
-        assert_eq!(
-            project.diagnostic_summary(false, cx),
-            DiagnosticSummary {
-                error_count: 1,
-                warning_count: 0,
-            }
-        );
-    });
-
-    project.update(cx, |project, cx| {
-        project.restart_language_servers_for_buffers([buffer.clone()], cx);
-    });
-
-    // The diagnostics are cleared.
-    cx.executor().run_until_parked();
-    buffer.update(cx, |buffer, _| {
-        assert_eq!(
-            buffer
-                .snapshot()
-                .diagnostics_in_range::<_, usize>(0..1, false)
-                .map(|entry| entry.diagnostic.message.clone())
-                .collect::<Vec<_>>(),
-            Vec::<String>::new(),
-        );
-    });
-    project.update(cx, |project, cx| {
-        assert_eq!(
-            project.diagnostic_summary(false, cx),
-            DiagnosticSummary {
-                error_count: 0,
-                warning_count: 0,
-            }
-        );
-    });
-}
-
-#[gpui::test]
-async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let mut language = Language::new(
-        LanguageConfig {
-            path_suffixes: vec!["rs".to_string()],
-            ..Default::default()
-        },
-        None,
-    );
-    let mut fake_servers = language
-        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-            name: "the-lsp",
-            ..Default::default()
-        }))
-        .await;
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree("/dir", json!({ "a.rs": "" })).await;
-
-    let project = Project::test(fs, ["/dir".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("/dir/a.rs", cx))
-        .await
-        .unwrap();
-
-    // Before restarting the server, report diagnostics with an unknown buffer version.
-    let fake_server = fake_servers.next().await.unwrap();
-    fake_server.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
-        uri: lsp::Url::from_file_path("/dir/a.rs").unwrap(),
-        version: Some(10000),
-        diagnostics: Vec::new(),
-    });
-    cx.executor().run_until_parked();
-
-    project.update(cx, |project, cx| {
-        project.restart_language_servers_for_buffers([buffer.clone()], cx);
-    });
-    let mut fake_server = fake_servers.next().await.unwrap();
-    let notification = fake_server
-        .receive_notification::<lsp::notification::DidOpenTextDocument>()
-        .await
-        .text_document;
-    assert_eq!(notification.version, 0);
-}
-
-#[gpui::test]
-async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let mut rust = Language::new(
-        LanguageConfig {
-            name: Arc::from("Rust"),
-            path_suffixes: vec!["rs".to_string()],
-            ..Default::default()
-        },
-        None,
-    );
-    let mut fake_rust_servers = rust
-        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-            name: "rust-lsp",
-            ..Default::default()
-        }))
-        .await;
-    let mut js = Language::new(
-        LanguageConfig {
-            name: Arc::from("JavaScript"),
-            path_suffixes: vec!["js".to_string()],
-            ..Default::default()
-        },
-        None,
-    );
-    let mut fake_js_servers = js
-        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-            name: "js-lsp",
-            ..Default::default()
-        }))
-        .await;
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree("/dir", json!({ "a.rs": "", "b.js": "" }))
-        .await;
-
-    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
-    project.update(cx, |project, _| {
-        project.languages.add(Arc::new(rust));
-        project.languages.add(Arc::new(js));
-    });
-
-    let _rs_buffer = project
-        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
-        .await
-        .unwrap();
-    let _js_buffer = project
-        .update(cx, |project, cx| project.open_local_buffer("/dir/b.js", cx))
-        .await
-        .unwrap();
-
-    let mut fake_rust_server_1 = fake_rust_servers.next().await.unwrap();
-    assert_eq!(
-        fake_rust_server_1
-            .receive_notification::<lsp::notification::DidOpenTextDocument>()
-            .await
-            .text_document
-            .uri
-            .as_str(),
-        "file:///dir/a.rs"
-    );
-
-    let mut fake_js_server = fake_js_servers.next().await.unwrap();
-    assert_eq!(
-        fake_js_server
-            .receive_notification::<lsp::notification::DidOpenTextDocument>()
-            .await
-            .text_document
-            .uri
-            .as_str(),
-        "file:///dir/b.js"
-    );
-
-    // Disable Rust language server, ensuring only that server gets stopped.
-    cx.update(|cx| {
-        cx.update_global(|settings: &mut SettingsStore, cx| {
-            settings.update_user_settings::<AllLanguageSettings>(cx, |settings| {
-                settings.languages.insert(
-                    Arc::from("Rust"),
-                    LanguageSettingsContent {
-                        enable_language_server: Some(false),
-                        ..Default::default()
-                    },
-                );
-            });
-        })
-    });
-    fake_rust_server_1
-        .receive_notification::<lsp::notification::Exit>()
-        .await;
-
-    // Enable Rust and disable JavaScript language servers, ensuring that the
-    // former gets started again and that the latter stops.
-    cx.update(|cx| {
-        cx.update_global(|settings: &mut SettingsStore, cx| {
-            settings.update_user_settings::<AllLanguageSettings>(cx, |settings| {
-                settings.languages.insert(
-                    Arc::from("Rust"),
-                    LanguageSettingsContent {
-                        enable_language_server: Some(true),
-                        ..Default::default()
-                    },
-                );
-                settings.languages.insert(
-                    Arc::from("JavaScript"),
-                    LanguageSettingsContent {
-                        enable_language_server: Some(false),
-                        ..Default::default()
-                    },
-                );
-            });
-        })
-    });
-    let mut fake_rust_server_2 = fake_rust_servers.next().await.unwrap();
-    assert_eq!(
-        fake_rust_server_2
-            .receive_notification::<lsp::notification::DidOpenTextDocument>()
-            .await
-            .text_document
-            .uri
-            .as_str(),
-        "file:///dir/a.rs"
-    );
-    fake_js_server
-        .receive_notification::<lsp::notification::Exit>()
-        .await;
-}
-
-#[gpui::test(iterations = 3)]
-async fn test_transforming_diagnostics(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 {
-            disk_based_diagnostics_sources: vec!["disk".into()],
-            ..Default::default()
-        }))
-        .await;
-
-    let text = "
-        fn a() { A }
-        fn b() { BB }
-        fn c() { CCC }
-    "
-    .unindent();
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree("/dir", json!({ "a.rs": text })).await;
-
-    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
-    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
-
-    let buffer = project
-        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
-        .await
-        .unwrap();
-
-    let mut fake_server = fake_servers.next().await.unwrap();
-    let open_notification = fake_server
-        .receive_notification::<lsp::notification::DidOpenTextDocument>()
-        .await;
-
-    // Edit the buffer, moving the content down
-    buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "\n\n")], None, cx));
-    let change_notification_1 = fake_server
-        .receive_notification::<lsp::notification::DidChangeTextDocument>()
-        .await;
-    assert!(change_notification_1.text_document.version > open_notification.text_document.version);
-
-    // Report some diagnostics for the initial version of the buffer
-    fake_server.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
-        uri: lsp::Url::from_file_path("/dir/a.rs").unwrap(),
-        version: Some(open_notification.text_document.version),
-        diagnostics: vec![
-            lsp::Diagnostic {
-                range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
-                severity: Some(DiagnosticSeverity::ERROR),
-                message: "undefined variable 'A'".to_string(),
-                source: Some("disk".to_string()),
-                ..Default::default()
-            },
-            lsp::Diagnostic {
-                range: lsp::Range::new(lsp::Position::new(1, 9), lsp::Position::new(1, 11)),
-                severity: Some(DiagnosticSeverity::ERROR),
-                message: "undefined variable 'BB'".to_string(),
-                source: Some("disk".to_string()),
-                ..Default::default()
-            },
-            lsp::Diagnostic {
-                range: lsp::Range::new(lsp::Position::new(2, 9), lsp::Position::new(2, 12)),
-                severity: Some(DiagnosticSeverity::ERROR),
-                source: Some("disk".to_string()),
-                message: "undefined variable 'CCC'".to_string(),
-                ..Default::default()
-            },
-        ],
-    });
-
-    // The diagnostics have moved down since they were created.
-    cx.executor().run_until_parked();
-    buffer.update(cx, |buffer, _| {
-        assert_eq!(
-            buffer
-                .snapshot()
-                .diagnostics_in_range::<_, Point>(Point::new(3, 0)..Point::new(5, 0), false)
-                .collect::<Vec<_>>(),
-            &[
-                DiagnosticEntry {
-                    range: Point::new(3, 9)..Point::new(3, 11),
-                    diagnostic: Diagnostic {
-                        source: Some("disk".into()),
-                        severity: DiagnosticSeverity::ERROR,
-                        message: "undefined variable 'BB'".to_string(),
-                        is_disk_based: true,
-                        group_id: 1,
-                        is_primary: true,
-                        ..Default::default()
-                    },
-                },
-                DiagnosticEntry {
-                    range: Point::new(4, 9)..Point::new(4, 12),
-                    diagnostic: Diagnostic {
-                        source: Some("disk".into()),
-                        severity: DiagnosticSeverity::ERROR,
-                        message: "undefined variable 'CCC'".to_string(),
-                        is_disk_based: true,
-                        group_id: 2,
-                        is_primary: true,
-                        ..Default::default()
-                    }
-                }
-            ]
-        );
-        assert_eq!(
-            chunks_with_diagnostics(buffer, 0..buffer.len()),
-            [
-                ("\n\nfn a() { ".to_string(), None),
-                ("A".to_string(), Some(DiagnosticSeverity::ERROR)),
-                (" }\nfn b() { ".to_string(), None),
-                ("BB".to_string(), Some(DiagnosticSeverity::ERROR)),
-                (" }\nfn c() { ".to_string(), None),
-                ("CCC".to_string(), Some(DiagnosticSeverity::ERROR)),
-                (" }\n".to_string(), None),
-            ]
-        );
-        assert_eq!(
-            chunks_with_diagnostics(buffer, Point::new(3, 10)..Point::new(4, 11)),
-            [
-                ("B".to_string(), Some(DiagnosticSeverity::ERROR)),
-                (" }\nfn c() { ".to_string(), None),
-                ("CC".to_string(), Some(DiagnosticSeverity::ERROR)),
-            ]
-        );
-    });
-
-    // Ensure overlapping diagnostics are highlighted correctly.
-    fake_server.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
-        uri: lsp::Url::from_file_path("/dir/a.rs").unwrap(),
-        version: Some(open_notification.text_document.version),
-        diagnostics: vec![
-            lsp::Diagnostic {
-                range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
-                severity: Some(DiagnosticSeverity::ERROR),
-                message: "undefined variable 'A'".to_string(),
-                source: Some("disk".to_string()),
-                ..Default::default()
-            },
-            lsp::Diagnostic {
-                range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 12)),
-                severity: Some(DiagnosticSeverity::WARNING),
-                message: "unreachable statement".to_string(),
-                source: Some("disk".to_string()),
-                ..Default::default()
-            },
-        ],
-    });
-
-    cx.executor().run_until_parked();
-    buffer.update(cx, |buffer, _| {
-        assert_eq!(
-            buffer
-                .snapshot()
-                .diagnostics_in_range::<_, Point>(Point::new(2, 0)..Point::new(3, 0), false)
-                .collect::<Vec<_>>(),
-            &[
-                DiagnosticEntry {
-                    range: Point::new(2, 9)..Point::new(2, 12),
-                    diagnostic: Diagnostic {
-                        source: Some("disk".into()),
-                        severity: DiagnosticSeverity::WARNING,
-                        message: "unreachable statement".to_string(),
-                        is_disk_based: true,
-                        group_id: 4,
-                        is_primary: true,
-                        ..Default::default()
-                    }
-                },
-                DiagnosticEntry {
-                    range: Point::new(2, 9)..Point::new(2, 10),
-                    diagnostic: Diagnostic {
-                        source: Some("disk".into()),
-                        severity: DiagnosticSeverity::ERROR,
-                        message: "undefined variable 'A'".to_string(),
-                        is_disk_based: true,
-                        group_id: 3,
-                        is_primary: true,
-                        ..Default::default()
-                    },
-                }
-            ]
-        );
-        assert_eq!(
-            chunks_with_diagnostics(buffer, Point::new(2, 0)..Point::new(3, 0)),
-            [
-                ("fn a() { ".to_string(), None),
-                ("A".to_string(), Some(DiagnosticSeverity::ERROR)),
-                (" }".to_string(), Some(DiagnosticSeverity::WARNING)),
-                ("\n".to_string(), None),
-            ]
-        );
-        assert_eq!(
-            chunks_with_diagnostics(buffer, Point::new(2, 10)..Point::new(3, 0)),
-            [
-                (" }".to_string(), Some(DiagnosticSeverity::WARNING)),
-                ("\n".to_string(), None),
-            ]
-        );
-    });
-
-    // Keep editing the buffer and ensure disk-based diagnostics get translated according to the
-    // changes since the last save.
-    buffer.update(cx, |buffer, cx| {
-        buffer.edit([(Point::new(2, 0)..Point::new(2, 0), "    ")], None, cx);
-        buffer.edit(
-            [(Point::new(2, 8)..Point::new(2, 10), "(x: usize)")],
-            None,
-            cx,
-        );
-        buffer.edit([(Point::new(3, 10)..Point::new(3, 10), "xxx")], None, cx);
-    });
-    let change_notification_2 = fake_server
-        .receive_notification::<lsp::notification::DidChangeTextDocument>()
-        .await;
-    assert!(
-        change_notification_2.text_document.version > change_notification_1.text_document.version
-    );
-
-    // Handle out-of-order diagnostics
-    fake_server.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
-        uri: lsp::Url::from_file_path("/dir/a.rs").unwrap(),
-        version: Some(change_notification_2.text_document.version),
-        diagnostics: vec![
-            lsp::Diagnostic {
-                range: lsp::Range::new(lsp::Position::new(1, 9), lsp::Position::new(1, 11)),
-                severity: Some(DiagnosticSeverity::ERROR),
-                message: "undefined variable 'BB'".to_string(),
-                source: Some("disk".to_string()),
-                ..Default::default()
-            },
-            lsp::Diagnostic {
-                range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
-                severity: Some(DiagnosticSeverity::WARNING),
-                message: "undefined variable 'A'".to_string(),
-                source: Some("disk".to_string()),
-                ..Default::default()
-            },
-        ],
-    });
-
-    cx.executor().run_until_parked();
-    buffer.update(cx, |buffer, _| {
-        assert_eq!(
-            buffer
-                .snapshot()
-                .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
-                .collect::<Vec<_>>(),
-            &[
-                DiagnosticEntry {
-                    range: Point::new(2, 21)..Point::new(2, 22),
-                    diagnostic: Diagnostic {
-                        source: Some("disk".into()),
-                        severity: DiagnosticSeverity::WARNING,
-                        message: "undefined variable 'A'".to_string(),
-                        is_disk_based: true,
-                        group_id: 6,
-                        is_primary: true,
-                        ..Default::default()
-                    }
-                },
-                DiagnosticEntry {
-                    range: Point::new(3, 9)..Point::new(3, 14),
-                    diagnostic: Diagnostic {
-                        source: Some("disk".into()),
-                        severity: DiagnosticSeverity::ERROR,
-                        message: "undefined variable 'BB'".to_string(),
-                        is_disk_based: true,
-                        group_id: 5,
-                        is_primary: true,
-                        ..Default::default()
-                    },
-                }
-            ]
-        );
-    });
-}
-
-#[gpui::test]
-async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let text = concat!(
-        "let one = ;\n", //
-        "let two = \n",
-        "let three = 3;\n",
-    );
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree("/dir", json!({ "a.rs": text })).await;
-
-    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
-    let buffer = project
-        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
-        .await
-        .unwrap();
-
-    project.update(cx, |project, cx| {
-        project
-            .update_buffer_diagnostics(
-                &buffer,
-                LanguageServerId(0),
-                None,
-                vec![
-                    DiagnosticEntry {
-                        range: Unclipped(PointUtf16::new(0, 10))..Unclipped(PointUtf16::new(0, 10)),
-                        diagnostic: Diagnostic {
-                            severity: DiagnosticSeverity::ERROR,
-                            message: "syntax error 1".to_string(),
-                            ..Default::default()
-                        },
-                    },
-                    DiagnosticEntry {
-                        range: Unclipped(PointUtf16::new(1, 10))..Unclipped(PointUtf16::new(1, 10)),
-                        diagnostic: Diagnostic {
-                            severity: DiagnosticSeverity::ERROR,
-                            message: "syntax error 2".to_string(),
-                            ..Default::default()
-                        },
-                    },
-                ],
-                cx,
-            )
-            .unwrap();
-    });
-
-    // An empty range is extended forward to include the following character.
-    // At the end of a line, an empty range is extended backward to include
-    // the preceding character.
-    buffer.update(cx, |buffer, _| {
-        let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
-        assert_eq!(
-            chunks
-                .iter()
-                .map(|(s, d)| (s.as_str(), *d))
-                .collect::<Vec<_>>(),
-            &[
-                ("let one = ", None),
-                (";", Some(DiagnosticSeverity::ERROR)),
-                ("\nlet two =", None),
-                (" ", Some(DiagnosticSeverity::ERROR)),
-                ("\nlet three = 3;\n", None)
-            ]
-        );
-    });
-}
-
-#[gpui::test]
-async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree("/dir", json!({ "a.rs": "one two three" }))
-        .await;
-
-    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
-
-    project.update(cx, |project, cx| {
-        project
-            .update_diagnostic_entries(
-                LanguageServerId(0),
-                Path::new("/dir/a.rs").to_owned(),
-                None,
-                vec![DiagnosticEntry {
-                    range: Unclipped(PointUtf16::new(0, 0))..Unclipped(PointUtf16::new(0, 3)),
-                    diagnostic: Diagnostic {
-                        severity: DiagnosticSeverity::ERROR,
-                        is_primary: true,
-                        message: "syntax error a1".to_string(),
-                        ..Default::default()
-                    },
-                }],
-                cx,
-            )
-            .unwrap();
-        project
-            .update_diagnostic_entries(
-                LanguageServerId(1),
-                Path::new("/dir/a.rs").to_owned(),
-                None,
-                vec![DiagnosticEntry {
-                    range: Unclipped(PointUtf16::new(0, 0))..Unclipped(PointUtf16::new(0, 3)),
-                    diagnostic: Diagnostic {
-                        severity: DiagnosticSeverity::ERROR,
-                        is_primary: true,
-                        message: "syntax error b1".to_string(),
-                        ..Default::default()
-                    },
-                }],
-                cx,
-            )
-            .unwrap();
-
-        assert_eq!(
-            project.diagnostic_summary(false, cx),
-            DiagnosticSummary {
-                error_count: 2,
-                warning_count: 0,
-            }
-        );
-    });
-}
-
-#[gpui::test]
-async fn test_edits_from_lsp2_with_past_version(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(Default::default()).await;
-
-    let text = "
-        fn a() {
-            f1();
-        }
-        fn b() {
-            f2();
-        }
-        fn c() {
-            f3();
-        }
-    "
-    .unindent();
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "a.rs": text.clone(),
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs, ["/dir".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("/dir/a.rs", cx))
-        .await
-        .unwrap();
-
-    let mut fake_server = fake_servers.next().await.unwrap();
-    let lsp_document_version = fake_server
-        .receive_notification::<lsp::notification::DidOpenTextDocument>()
-        .await
-        .text_document
-        .version;
-
-    // Simulate editing the buffer after the language server computes some edits.
-    buffer.update(cx, |buffer, cx| {
-        buffer.edit(
-            [(
-                Point::new(0, 0)..Point::new(0, 0),
-                "// above first function\n",
-            )],
-            None,
-            cx,
-        );
-        buffer.edit(
-            [(
-                Point::new(2, 0)..Point::new(2, 0),
-                "    // inside first function\n",
-            )],
-            None,
-            cx,
-        );
-        buffer.edit(
-            [(
-                Point::new(6, 4)..Point::new(6, 4),
-                "// inside second function ",
-            )],
-            None,
-            cx,
-        );
-
-        assert_eq!(
-            buffer.text(),
-            "
-                // above first function
-                fn a() {
-                    // inside first function
-                    f1();
-                }
-                fn b() {
-                    // inside second function f2();
-                }
-                fn c() {
-                    f3();
-                }
-            "
-            .unindent()
-        );
-    });
-
-    let edits = project
-        .update(cx, |project, cx| {
-            project.edits_from_lsp(
-                &buffer,
-                vec![
-                    // replace body of first function
-                    lsp::TextEdit {
-                        range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(3, 0)),
-                        new_text: "
-                            fn a() {
-                                f10();
-                            }
-                            "
-                        .unindent(),
-                    },
-                    // edit inside second function
-                    lsp::TextEdit {
-                        range: lsp::Range::new(lsp::Position::new(4, 6), lsp::Position::new(4, 6)),
-                        new_text: "00".into(),
-                    },
-                    // edit inside third function via two distinct edits
-                    lsp::TextEdit {
-                        range: lsp::Range::new(lsp::Position::new(7, 5), lsp::Position::new(7, 5)),
-                        new_text: "4000".into(),
-                    },
-                    lsp::TextEdit {
-                        range: lsp::Range::new(lsp::Position::new(7, 5), lsp::Position::new(7, 6)),
-                        new_text: "".into(),
-                    },
-                ],
-                LanguageServerId(0),
-                Some(lsp_document_version),
-                cx,
-            )
-        })
-        .await
-        .unwrap();
-
-    buffer.update(cx, |buffer, cx| {
-        for (range, new_text) in edits {
-            buffer.edit([(range, new_text)], None, cx);
-        }
-        assert_eq!(
-            buffer.text(),
-            "
-                // above first function
-                fn a() {
-                    // inside first function
-                    f10();
-                }
-                fn b() {
-                    // inside second function f200();
-                }
-                fn c() {
-                    f4000();
-                }
-                "
-            .unindent()
-        );
-    });
-}
-
-#[gpui::test]
-async fn test_edits_from_lsp2_with_edits_on_adjacent_lines(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let text = "
-        use a::b;
-        use a::c;
-
-        fn f() {
-            b();
-            c();
-        }
-    "
-    .unindent();
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "a.rs": text.clone(),
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
-    let buffer = project
-        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
-        .await
-        .unwrap();
-
-    // Simulate the language server sending us a small edit in the form of a very large diff.
-    // Rust-analyzer does this when performing a merge-imports code action.
-    let edits = project
-        .update(cx, |project, cx| {
-            project.edits_from_lsp(
-                &buffer,
-                [
-                    // Replace the first use statement without editing the semicolon.
-                    lsp::TextEdit {
-                        range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 8)),
-                        new_text: "a::{b, c}".into(),
-                    },
-                    // Reinsert the remainder of the file between the semicolon and the final
-                    // newline of the file.
-                    lsp::TextEdit {
-                        range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 9)),
-                        new_text: "\n\n".into(),
-                    },
-                    lsp::TextEdit {
-                        range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 9)),
-                        new_text: "
-                            fn f() {
-                                b();
-                                c();
-                            }"
-                        .unindent(),
-                    },
-                    // Delete everything after the first newline of the file.
-                    lsp::TextEdit {
-                        range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(7, 0)),
-                        new_text: "".into(),
-                    },
-                ],
-                LanguageServerId(0),
-                None,
-                cx,
-            )
-        })
-        .await
-        .unwrap();
-
-    buffer.update(cx, |buffer, cx| {
-        let edits = edits
-            .into_iter()
-            .map(|(range, text)| {
-                (
-                    range.start.to_point(buffer)..range.end.to_point(buffer),
-                    text,
-                )
-            })
-            .collect::<Vec<_>>();
-
-        assert_eq!(
-            edits,
-            [
-                (Point::new(0, 4)..Point::new(0, 8), "a::{b, c}".into()),
-                (Point::new(1, 0)..Point::new(2, 0), "".into())
-            ]
-        );
-
-        for (range, new_text) in edits {
-            buffer.edit([(range, new_text)], None, cx);
-        }
-        assert_eq!(
-            buffer.text(),
-            "
-                use a::{b, c};
-
-                fn f() {
-                    b();
-                    c();
-                }
-            "
-            .unindent()
-        );
-    });
-}
-
-#[gpui::test]
-async fn test_invalid_edits_from_lsp2(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let text = "
-        use a::b;
-        use a::c;
-
-        fn f() {
-            b();
-            c();
-        }
-    "
-    .unindent();
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "a.rs": text.clone(),
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
-    let buffer = project
-        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
-        .await
-        .unwrap();
-
-    // Simulate the language server sending us edits in a non-ordered fashion,
-    // with ranges sometimes being inverted or pointing to invalid locations.
-    let edits = project
-        .update(cx, |project, cx| {
-            project.edits_from_lsp(
-                &buffer,
-                [
-                    lsp::TextEdit {
-                        range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 9)),
-                        new_text: "\n\n".into(),
-                    },
-                    lsp::TextEdit {
-                        range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 4)),
-                        new_text: "a::{b, c}".into(),
-                    },
-                    lsp::TextEdit {
-                        range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(99, 0)),
-                        new_text: "".into(),
-                    },
-                    lsp::TextEdit {
-                        range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 9)),
-                        new_text: "
-                            fn f() {
-                                b();
-                                c();
-                            }"
-                        .unindent(),
-                    },
-                ],
-                LanguageServerId(0),
-                None,
-                cx,
-            )
-        })
-        .await
-        .unwrap();
-
-    buffer.update(cx, |buffer, cx| {
-        let edits = edits
-            .into_iter()
-            .map(|(range, text)| {
-                (
-                    range.start.to_point(buffer)..range.end.to_point(buffer),
-                    text,
-                )
-            })
-            .collect::<Vec<_>>();
-
-        assert_eq!(
-            edits,
-            [
-                (Point::new(0, 4)..Point::new(0, 8), "a::{b, c}".into()),
-                (Point::new(1, 0)..Point::new(2, 0), "".into())
-            ]
-        );
-
-        for (range, new_text) in edits {
-            buffer.edit([(range, new_text)], None, cx);
-        }
-        assert_eq!(
-            buffer.text(),
-            "
-                use a::{b, c};
-
-                fn f() {
-                    b();
-                    c();
-                }
-            "
-            .unindent()
-        );
-    });
-}
-
-fn chunks_with_diagnostics<T: ToOffset + ToPoint>(
-    buffer: &Buffer,
-    range: Range<T>,
-) -> Vec<(String, Option<DiagnosticSeverity>)> {
-    let mut chunks: Vec<(String, Option<DiagnosticSeverity>)> = Vec::new();
-    for chunk in buffer.snapshot().chunks(range, true) {
-        if chunks.last().map_or(false, |prev_chunk| {
-            prev_chunk.1 == chunk.diagnostic_severity
-        }) {
-            chunks.last_mut().unwrap().0.push_str(chunk.text);
-        } else {
-            chunks.push((chunk.text.to_string(), chunk.diagnostic_severity));
-        }
-    }
-    chunks
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_definition(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(Default::default()).await;
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "a.rs": "const fn a() { A }",
-            "b.rs": "const y: i32 = crate::a()",
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs, ["/dir/b.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("/dir/b.rs", cx))
-        .await
-        .unwrap();
-
-    let fake_server = fake_servers.next().await.unwrap();
-    fake_server.handle_request::<lsp::request::GotoDefinition, _, _>(|params, _| async move {
-        let params = params.text_document_position_params;
-        assert_eq!(
-            params.text_document.uri.to_file_path().unwrap(),
-            Path::new("/dir/b.rs"),
-        );
-        assert_eq!(params.position, lsp::Position::new(0, 22));
-
-        Ok(Some(lsp::GotoDefinitionResponse::Scalar(
-            lsp::Location::new(
-                lsp::Url::from_file_path("/dir/a.rs").unwrap(),
-                lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
-            ),
-        )))
-    });
-
-    let mut definitions = project
-        .update(cx, |project, cx| project.definition(&buffer, 22, cx))
-        .await
-        .unwrap();
-
-    // Assert no new language server started
-    cx.executor().run_until_parked();
-    assert!(fake_servers.try_next().is_err());
-
-    assert_eq!(definitions.len(), 1);
-    let definition = definitions.pop().unwrap();
-    cx.update(|cx| {
-        let target_buffer = definition.target.buffer.read(cx);
-        assert_eq!(
-            target_buffer
-                .file()
-                .unwrap()
-                .as_local()
-                .unwrap()
-                .abs_path(cx),
-            Path::new("/dir/a.rs"),
-        );
-        assert_eq!(definition.target.range.to_offset(target_buffer), 9..10);
-        assert_eq!(
-            list_worktrees(&project, cx),
-            [("/dir/b.rs".as_ref(), true), ("/dir/a.rs".as_ref(), false)]
-        );
-
-        drop(definition);
-    });
-    cx.update(|cx| {
-        assert_eq!(list_worktrees(&project, cx), [("/dir/b.rs".as_ref(), true)]);
-    });
-
-    fn list_worktrees<'a>(
-        project: &'a Model<Project>,
-        cx: &'a AppContext,
-    ) -> Vec<(&'a Path, bool)> {
-        project
-            .read(cx)
-            .worktrees()
-            .map(|worktree| {
-                let worktree = worktree.read(cx);
-                (
-                    worktree.as_local().unwrap().abs_path().as_ref(),
-                    worktree.is_visible(),
-                )
-            })
-            .collect::<Vec<_>>()
-    }
-}
-
-#[gpui::test]
-async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let mut language = Language::new(
-        LanguageConfig {
-            name: "TypeScript".into(),
-            path_suffixes: vec!["ts".to_string()],
-            ..Default::default()
-        },
-        Some(tree_sitter_typescript::language_typescript()),
-    );
-    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()]),
-                    ..Default::default()
-                }),
-                ..Default::default()
-            },
-            ..Default::default()
-        }))
-        .await;
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "a.ts": "",
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
-    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
-    let buffer = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
-        .await
-        .unwrap();
-
-    let fake_server = fake_language_servers.next().await.unwrap();
-
-    let text = "let a = b.fqn";
-    buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
-    let completions = project.update(cx, |project, cx| {
-        project.completions(&buffer, text.len(), cx)
-    });
-
-    fake_server
-        .handle_request::<lsp::request::Completion, _, _>(|_, _| async move {
-            Ok(Some(lsp::CompletionResponse::Array(vec![
-                lsp::CompletionItem {
-                    label: "fullyQualifiedName?".into(),
-                    insert_text: Some("fullyQualifiedName".into()),
-                    ..Default::default()
-                },
-            ])))
-        })
-        .next()
-        .await;
-    let completions = completions.await.unwrap();
-    let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
-    assert_eq!(completions.len(), 1);
-    assert_eq!(completions[0].new_text, "fullyQualifiedName");
-    assert_eq!(
-        completions[0].old_range.to_offset(&snapshot),
-        text.len() - 3..text.len()
-    );
-
-    let text = "let a = \"atoms/cmp\"";
-    buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
-    let completions = project.update(cx, |project, cx| {
-        project.completions(&buffer, text.len() - 1, cx)
-    });
-
-    fake_server
-        .handle_request::<lsp::request::Completion, _, _>(|_, _| async move {
-            Ok(Some(lsp::CompletionResponse::Array(vec![
-                lsp::CompletionItem {
-                    label: "component".into(),
-                    ..Default::default()
-                },
-            ])))
-        })
-        .next()
-        .await;
-    let completions = completions.await.unwrap();
-    let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
-    assert_eq!(completions.len(), 1);
-    assert_eq!(completions[0].new_text, "component");
-    assert_eq!(
-        completions[0].old_range.to_offset(&snapshot),
-        text.len() - 4..text.len() - 1
-    );
-}
-
-#[gpui::test]
-async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let mut language = Language::new(
-        LanguageConfig {
-            name: "TypeScript".into(),
-            path_suffixes: vec!["ts".to_string()],
-            ..Default::default()
-        },
-        Some(tree_sitter_typescript::language_typescript()),
-    );
-    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()]),
-                    ..Default::default()
-                }),
-                ..Default::default()
-            },
-            ..Default::default()
-        }))
-        .await;
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "a.ts": "",
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
-    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
-    let buffer = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
-        .await
-        .unwrap();
-
-    let fake_server = fake_language_servers.next().await.unwrap();
-
-    let text = "let a = b.fqn";
-    buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
-    let completions = project.update(cx, |project, cx| {
-        project.completions(&buffer, text.len(), cx)
-    });
-
-    fake_server
-        .handle_request::<lsp::request::Completion, _, _>(|_, _| async move {
-            Ok(Some(lsp::CompletionResponse::Array(vec![
-                lsp::CompletionItem {
-                    label: "fullyQualifiedName?".into(),
-                    insert_text: Some("fully\rQualified\r\nName".into()),
-                    ..Default::default()
-                },
-            ])))
-        })
-        .next()
-        .await;
-    let completions = completions.await.unwrap();
-    assert_eq!(completions.len(), 1);
-    assert_eq!(completions[0].new_text, "fully\nQualified\nName");
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let mut language = Language::new(
-        LanguageConfig {
-            name: "TypeScript".into(),
-            path_suffixes: vec!["ts".to_string()],
-            ..Default::default()
-        },
-        None,
-    );
-    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "a.ts": "a",
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
-    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
-    let buffer = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
-        .await
-        .unwrap();
-
-    let fake_server = fake_language_servers.next().await.unwrap();
-
-    // Language server returns code actions that contain commands, and not edits.
-    let actions = project.update(cx, |project, cx| project.code_actions(&buffer, 0..0, cx));
-    fake_server
-        .handle_request::<lsp::request::CodeActionRequest, _, _>(|_, _| async move {
-            Ok(Some(vec![
-                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
-                    title: "The code action".into(),
-                    command: Some(lsp::Command {
-                        title: "The command".into(),
-                        command: "_the/command".into(),
-                        arguments: Some(vec![json!("the-argument")]),
-                    }),
-                    ..Default::default()
-                }),
-                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
-                    title: "two".into(),
-                    ..Default::default()
-                }),
-            ]))
-        })
-        .next()
-        .await;
-
-    let action = actions.await.unwrap()[0].clone();
-    let apply = project.update(cx, |project, cx| {
-        project.apply_code_action(buffer.clone(), action, true, cx)
-    });
-
-    // Resolving the code action does not populate its edits. In absence of
-    // edits, we must execute the given command.
-    fake_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
-        |action, _| async move { Ok(action) },
-    );
-
-    // While executing the command, the language server sends the editor
-    // a `workspaceEdit` request.
-    fake_server
-        .handle_request::<lsp::request::ExecuteCommand, _, _>({
-            let fake = fake_server.clone();
-            move |params, _| {
-                assert_eq!(params.command, "_the/command");
-                let fake = fake.clone();
-                async move {
-                    fake.server
-                        .request::<lsp::request::ApplyWorkspaceEdit>(
-                            lsp::ApplyWorkspaceEditParams {
-                                label: None,
-                                edit: lsp::WorkspaceEdit {
-                                    changes: Some(
-                                        [(
-                                            lsp::Url::from_file_path("/dir/a.ts").unwrap(),
-                                            vec![lsp::TextEdit {
-                                                range: lsp::Range::new(
-                                                    lsp::Position::new(0, 0),
-                                                    lsp::Position::new(0, 0),
-                                                ),
-                                                new_text: "X".into(),
-                                            }],
-                                        )]
-                                        .into_iter()
-                                        .collect(),
-                                    ),
-                                    ..Default::default()
-                                },
-                            },
-                        )
-                        .await
-                        .unwrap();
-                    Ok(Some(json!(null)))
-                }
-            }
-        })
-        .next()
-        .await;
-
-    // Applying the code action returns a project transaction containing the edits
-    // sent by the language server in its `workspaceEdit` request.
-    let transaction = apply.await.unwrap();
-    assert!(transaction.0.contains_key(&buffer));
-    buffer.update(cx, |buffer, cx| {
-        assert_eq!(buffer.text(), "Xa");
-        buffer.undo(cx);
-        assert_eq!(buffer.text(), "a");
-    });
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_save_file(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "file1": "the old contents",
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-    let buffer = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
-        .await
-        .unwrap();
-    buffer.update(cx, |buffer, cx| {
-        assert_eq!(buffer.text(), "the old contents");
-        buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], None, cx);
-    });
-
-    project
-        .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
-        .await
-        .unwrap();
-
-    let new_text = fs.load(Path::new("/dir/file1")).await.unwrap();
-    assert_eq!(new_text, buffer.update(cx, |buffer, _| buffer.text()));
-}
-
-#[gpui::test(iterations = 30)]
-async fn test_file_changes_multiple_times_on_disk(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor().clone());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "file1": "the original contents",
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-    let worktree = project.read_with(cx, |project, _| project.worktrees().next().unwrap());
-    let buffer = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
-        .await
-        .unwrap();
-
-    // Simulate buffer diffs being slow, so that they don't complete before
-    // the next file change occurs.
-    cx.executor().deprioritize(*language::BUFFER_DIFF_TASK);
-
-    // Change the buffer's file on disk, and then wait for the file change
-    // to be detected by the worktree, so that the buffer starts reloading.
-    fs.save(
-        "/dir/file1".as_ref(),
-        &"the first contents".into(),
-        Default::default(),
-    )
-    .await
-    .unwrap();
-    worktree.next_event(cx);
-
-    // Change the buffer's file again. Depending on the random seed, the
-    // previous file change may still be in progress.
-    fs.save(
-        "/dir/file1".as_ref(),
-        &"the second contents".into(),
-        Default::default(),
-    )
-    .await
-    .unwrap();
-    worktree.next_event(cx);
-
-    cx.executor().run_until_parked();
-    let on_disk_text = fs.load(Path::new("/dir/file1")).await.unwrap();
-    buffer.read_with(cx, |buffer, _| {
-        assert_eq!(buffer.text(), on_disk_text);
-        assert!(!buffer.is_dirty(), "buffer should not be dirty");
-        assert!(!buffer.has_conflict(), "buffer should not be dirty");
-    });
-}
-
-#[gpui::test(iterations = 30)]
-async fn test_edit_buffer_while_it_reloads(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor().clone());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "file1": "the original contents",
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-    let worktree = project.read_with(cx, |project, _| project.worktrees().next().unwrap());
-    let buffer = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
-        .await
-        .unwrap();
-
-    // Simulate buffer diffs being slow, so that they don't complete before
-    // the next file change occurs.
-    cx.executor().deprioritize(*language::BUFFER_DIFF_TASK);
-
-    // Change the buffer's file on disk, and then wait for the file change
-    // to be detected by the worktree, so that the buffer starts reloading.
-    fs.save(
-        "/dir/file1".as_ref(),
-        &"the first contents".into(),
-        Default::default(),
-    )
-    .await
-    .unwrap();
-    worktree.next_event(cx);
-
-    cx.executor()
-        .spawn(cx.executor().simulate_random_delay())
-        .await;
-
-    // Perform a noop edit, causing the buffer's version to increase.
-    buffer.update(cx, |buffer, cx| {
-        buffer.edit([(0..0, " ")], None, cx);
-        buffer.undo(cx);
-    });
-
-    cx.executor().run_until_parked();
-    let on_disk_text = fs.load(Path::new("/dir/file1")).await.unwrap();
-    buffer.read_with(cx, |buffer, _| {
-        let buffer_text = buffer.text();
-        if buffer_text == on_disk_text {
-            assert!(
-                !buffer.is_dirty() && !buffer.has_conflict(),
-                "buffer shouldn't be dirty. text: {buffer_text:?}, disk text: {on_disk_text:?}",
-            );
-        }
-        // If the file change occurred while the buffer was processing the first
-        // change, the buffer will be in a conflicting state.
-        else {
-            assert!(buffer.is_dirty(), "buffer should report that it is dirty. text: {buffer_text:?}, disk text: {on_disk_text:?}");
-            assert!(buffer.has_conflict(), "buffer should report that it is dirty. text: {buffer_text:?}, disk text: {on_disk_text:?}");
-        }
-    });
-}
-
-#[gpui::test]
-async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "file1": "the old contents",
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs.clone(), ["/dir/file1".as_ref()], cx).await;
-    let buffer = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
-        .await
-        .unwrap();
-    buffer.update(cx, |buffer, cx| {
-        buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], None, cx);
-    });
-
-    project
-        .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
-        .await
-        .unwrap();
-
-    let new_text = fs.load(Path::new("/dir/file1")).await.unwrap();
-    assert_eq!(new_text, buffer.update(cx, |buffer, _| buffer.text()));
-}
-
-#[gpui::test]
-async fn test_save_as(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree("/dir", json!({})).await;
-
-    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-
-    let languages = project.update(cx, |project, _| project.languages().clone());
-    languages.register(
-        "/some/path",
-        LanguageConfig {
-            name: "Rust".into(),
-            path_suffixes: vec!["rs".into()],
-            ..Default::default()
-        },
-        tree_sitter_rust::language(),
-        vec![],
-        |_| Default::default(),
-    );
-
-    let buffer = project.update(cx, |project, cx| {
-        project.create_buffer("", None, cx).unwrap()
-    });
-    buffer.update(cx, |buffer, cx| {
-        buffer.edit([(0..0, "abc")], None, cx);
-        assert!(buffer.is_dirty());
-        assert!(!buffer.has_conflict());
-        assert_eq!(buffer.language().unwrap().name().as_ref(), "Plain Text");
-    });
-    project
-        .update(cx, |project, cx| {
-            project.save_buffer_as(buffer.clone(), "/dir/file1.rs".into(), cx)
-        })
-        .await
-        .unwrap();
-    assert_eq!(fs.load(Path::new("/dir/file1.rs")).await.unwrap(), "abc");
-
-    cx.executor().run_until_parked();
-    buffer.update(cx, |buffer, cx| {
-        assert_eq!(
-            buffer.file().unwrap().full_path(cx),
-            Path::new("dir/file1.rs")
-        );
-        assert!(!buffer.is_dirty());
-        assert!(!buffer.has_conflict());
-        assert_eq!(buffer.language().unwrap().name().as_ref(), "Rust");
-    });
-
-    let opened_buffer = project
-        .update(cx, |project, cx| {
-            project.open_local_buffer("/dir/file1.rs", cx)
-        })
-        .await
-        .unwrap();
-    assert_eq!(opened_buffer, buffer);
-}
-
-#[gpui::test(retries = 5)]
-async fn test_rescan_and_remote_updates(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-    cx.executor().allow_parking();
-
-    let dir = temp_tree(json!({
-        "a": {
-            "file1": "",
-            "file2": "",
-            "file3": "",
-        },
-        "b": {
-            "c": {
-                "file4": "",
-                "file5": "",
-            }
-        }
-    }));
-
-    let project = Project::test(Arc::new(RealFs), [dir.path()], cx).await;
-    let rpc = project.update(cx, |p, _| p.client.clone());
-
-    let buffer_for_path = |path: &'static str, cx: &mut gpui::TestAppContext| {
-        let buffer = project.update(cx, |p, cx| p.open_local_buffer(dir.path().join(path), cx));
-        async move { buffer.await.unwrap() }
-    };
-    let id_for_path = |path: &'static str, cx: &mut gpui::TestAppContext| {
-        project.update(cx, |project, cx| {
-            let tree = project.worktrees().next().unwrap();
-            tree.read(cx)
-                .entry_for_path(path)
-                .unwrap_or_else(|| panic!("no entry for path {}", path))
-                .id
-        })
-    };
-
-    let buffer2 = buffer_for_path("a/file2", cx).await;
-    let buffer3 = buffer_for_path("a/file3", cx).await;
-    let buffer4 = buffer_for_path("b/c/file4", cx).await;
-    let buffer5 = buffer_for_path("b/c/file5", cx).await;
-
-    let file2_id = id_for_path("a/file2", cx);
-    let file3_id = id_for_path("a/file3", cx);
-    let file4_id = id_for_path("b/c/file4", cx);
-
-    // Create a remote copy of this worktree.
-    let tree = project.update(cx, |project, _| project.worktrees().next().unwrap());
-
-    let metadata = tree.update(cx, |tree, _| tree.as_local().unwrap().metadata_proto());
-
-    let updates = Arc::new(Mutex::new(Vec::new()));
-    tree.update(cx, |tree, cx| {
-        let _ = tree.as_local_mut().unwrap().observe_updates(0, cx, {
-            let updates = updates.clone();
-            move |update| {
-                updates.lock().push(update);
-                async { true }
-            }
-        });
-    });
-
-    let remote = cx.update(|cx| Worktree::remote(1, 1, metadata, rpc.clone(), cx));
-
-    cx.executor().run_until_parked();
-
-    cx.update(|cx| {
-        assert!(!buffer2.read(cx).is_dirty());
-        assert!(!buffer3.read(cx).is_dirty());
-        assert!(!buffer4.read(cx).is_dirty());
-        assert!(!buffer5.read(cx).is_dirty());
-    });
-
-    // Rename and delete files and directories.
-    tree.flush_fs_events(cx).await;
-    std::fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap();
-    std::fs::remove_file(dir.path().join("b/c/file5")).unwrap();
-    std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap();
-    std::fs::rename(dir.path().join("a/file2"), dir.path().join("a/file2.new")).unwrap();
-    tree.flush_fs_events(cx).await;
-
-    let expected_paths = vec![
-        "a",
-        "a/file1",
-        "a/file2.new",
-        "b",
-        "d",
-        "d/file3",
-        "d/file4",
-    ];
-
-    cx.update(|app| {
-        assert_eq!(
-            tree.read(app)
-                .paths()
-                .map(|p| p.to_str().unwrap())
-                .collect::<Vec<_>>(),
-            expected_paths
-        );
-    });
-
-    assert_eq!(id_for_path("a/file2.new", cx), file2_id);
-    assert_eq!(id_for_path("d/file3", cx), file3_id);
-    assert_eq!(id_for_path("d/file4", cx), file4_id);
-
-    cx.update(|cx| {
-        assert_eq!(
-            buffer2.read(cx).file().unwrap().path().as_ref(),
-            Path::new("a/file2.new")
-        );
-        assert_eq!(
-            buffer3.read(cx).file().unwrap().path().as_ref(),
-            Path::new("d/file3")
-        );
-        assert_eq!(
-            buffer4.read(cx).file().unwrap().path().as_ref(),
-            Path::new("d/file4")
-        );
-        assert_eq!(
-            buffer5.read(cx).file().unwrap().path().as_ref(),
-            Path::new("b/c/file5")
-        );
-
-        assert!(!buffer2.read(cx).file().unwrap().is_deleted());
-        assert!(!buffer3.read(cx).file().unwrap().is_deleted());
-        assert!(!buffer4.read(cx).file().unwrap().is_deleted());
-        assert!(buffer5.read(cx).file().unwrap().is_deleted());
-    });
-
-    // Update the remote worktree. Check that it becomes consistent with the
-    // local worktree.
-    cx.executor().run_until_parked();
-
-    remote.update(cx, |remote, _| {
-        for update in updates.lock().drain(..) {
-            remote.as_remote_mut().unwrap().update_from_remote(update);
-        }
-    });
-    cx.executor().run_until_parked();
-    remote.update(cx, |remote, _| {
-        assert_eq!(
-            remote
-                .paths()
-                .map(|p| p.to_str().unwrap())
-                .collect::<Vec<_>>(),
-            expected_paths
-        );
-    });
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_buffer_identity_across_renames(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "a": {
-                "file1": "",
-            }
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs, [Path::new("/dir")], cx).await;
-    let tree = project.update(cx, |project, _| project.worktrees().next().unwrap());
-    let tree_id = tree.update(cx, |tree, _| tree.id());
-
-    let id_for_path = |path: &'static str, cx: &mut gpui::TestAppContext| {
-        project.update(cx, |project, cx| {
-            let tree = project.worktrees().next().unwrap();
-            tree.read(cx)
-                .entry_for_path(path)
-                .unwrap_or_else(|| panic!("no entry for path {}", path))
-                .id
-        })
-    };
-
-    let dir_id = id_for_path("a", cx);
-    let file_id = id_for_path("a/file1", cx);
-    let buffer = project
-        .update(cx, |p, cx| p.open_buffer((tree_id, "a/file1"), cx))
-        .await
-        .unwrap();
-    buffer.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
-
-    project
-        .update(cx, |project, cx| {
-            project.rename_entry(dir_id, Path::new("b"), cx)
-        })
-        .unwrap()
-        .await
-        .unwrap();
-    cx.executor().run_until_parked();
-
-    assert_eq!(id_for_path("b", cx), dir_id);
-    assert_eq!(id_for_path("b/file1", cx), file_id);
-    buffer.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
-}
-
-#[gpui::test]
-async fn test_buffer_deduping(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "a.txt": "a-contents",
-            "b.txt": "b-contents",
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-
-    // Spawn multiple tasks to open paths, repeating some paths.
-    let (buffer_a_1, buffer_b, buffer_a_2) = project.update(cx, |p, cx| {
-        (
-            p.open_local_buffer("/dir/a.txt", cx),
-            p.open_local_buffer("/dir/b.txt", cx),
-            p.open_local_buffer("/dir/a.txt", cx),
-        )
-    });
-
-    let buffer_a_1 = buffer_a_1.await.unwrap();
-    let buffer_a_2 = buffer_a_2.await.unwrap();
-    let buffer_b = buffer_b.await.unwrap();
-    assert_eq!(buffer_a_1.update(cx, |b, _| b.text()), "a-contents");
-    assert_eq!(buffer_b.update(cx, |b, _| b.text()), "b-contents");
-
-    // There is only one buffer per path.
-    let buffer_a_id = buffer_a_1.entity_id();
-    assert_eq!(buffer_a_2.entity_id(), buffer_a_id);
-
-    // Open the same path again while it is still open.
-    drop(buffer_a_1);
-    let buffer_a_3 = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/a.txt", cx))
-        .await
-        .unwrap();
-
-    // There's still only one buffer per path.
-    assert_eq!(buffer_a_3.entity_id(), buffer_a_id);
-}
-
-#[gpui::test]
-async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "file1": "abc",
-            "file2": "def",
-            "file3": "ghi",
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-
-    let buffer1 = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
-        .await
-        .unwrap();
-    let events = Arc::new(Mutex::new(Vec::new()));
-
-    // initially, the buffer isn't dirty.
-    buffer1.update(cx, |buffer, cx| {
-        cx.subscribe(&buffer1, {
-            let events = events.clone();
-            move |_, _, event, _| match event {
-                BufferEvent::Operation(_) => {}
-                _ => events.lock().push(event.clone()),
-            }
-        })
-        .detach();
-
-        assert!(!buffer.is_dirty());
-        assert!(events.lock().is_empty());
-
-        buffer.edit([(1..2, "")], None, cx);
-    });
-
-    // after the first edit, the buffer is dirty, and emits a dirtied event.
-    buffer1.update(cx, |buffer, cx| {
-        assert!(buffer.text() == "ac");
-        assert!(buffer.is_dirty());
-        assert_eq!(
-            *events.lock(),
-            &[language::Event::Edited, language::Event::DirtyChanged]
-        );
-        events.lock().clear();
-        buffer.did_save(
-            buffer.version(),
-            buffer.as_rope().fingerprint(),
-            buffer.file().unwrap().mtime(),
-            cx,
-        );
-    });
-
-    // after saving, the buffer is not dirty, and emits a saved event.
-    buffer1.update(cx, |buffer, cx| {
-        assert!(!buffer.is_dirty());
-        assert_eq!(*events.lock(), &[language::Event::Saved]);
-        events.lock().clear();
-
-        buffer.edit([(1..1, "B")], None, cx);
-        buffer.edit([(2..2, "D")], None, cx);
-    });
-
-    // after editing again, the buffer is dirty, and emits another dirty event.
-    buffer1.update(cx, |buffer, cx| {
-        assert!(buffer.text() == "aBDc");
-        assert!(buffer.is_dirty());
-        assert_eq!(
-            *events.lock(),
-            &[
-                language::Event::Edited,
-                language::Event::DirtyChanged,
-                language::Event::Edited,
-            ],
-        );
-        events.lock().clear();
-
-        // After restoring the buffer to its previously-saved state,
-        // the buffer is not considered dirty anymore.
-        buffer.edit([(1..3, "")], None, cx);
-        assert!(buffer.text() == "ac");
-        assert!(!buffer.is_dirty());
-    });
-
-    assert_eq!(
-        *events.lock(),
-        &[language::Event::Edited, language::Event::DirtyChanged]
-    );
-
-    // When a file is deleted, the buffer is considered dirty.
-    let events = Arc::new(Mutex::new(Vec::new()));
-    let buffer2 = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/file2", cx))
-        .await
-        .unwrap();
-    buffer2.update(cx, |_, cx| {
-        cx.subscribe(&buffer2, {
-            let events = events.clone();
-            move |_, _, event, _| events.lock().push(event.clone())
-        })
-        .detach();
-    });
-
-    fs.remove_file("/dir/file2".as_ref(), Default::default())
-        .await
-        .unwrap();
-    cx.executor().run_until_parked();
-    buffer2.update(cx, |buffer, _| assert!(buffer.is_dirty()));
-    assert_eq!(
-        *events.lock(),
-        &[
-            language::Event::DirtyChanged,
-            language::Event::FileHandleChanged
-        ]
-    );
-
-    // When a file is already dirty when deleted, we don't emit a Dirtied event.
-    let events = Arc::new(Mutex::new(Vec::new()));
-    let buffer3 = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/file3", cx))
-        .await
-        .unwrap();
-    buffer3.update(cx, |_, cx| {
-        cx.subscribe(&buffer3, {
-            let events = events.clone();
-            move |_, _, event, _| events.lock().push(event.clone())
-        })
-        .detach();
-    });
-
-    buffer3.update(cx, |buffer, cx| {
-        buffer.edit([(0..0, "x")], None, cx);
-    });
-    events.lock().clear();
-    fs.remove_file("/dir/file3".as_ref(), Default::default())
-        .await
-        .unwrap();
-    cx.executor().run_until_parked();
-    assert_eq!(*events.lock(), &[language::Event::FileHandleChanged]);
-    cx.update(|cx| assert!(buffer3.read(cx).is_dirty()));
-}
-
-#[gpui::test]
-async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let initial_contents = "aaa\nbbbbb\nc\n";
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "the-file": initial_contents,
-        }),
-    )
-    .await;
-    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-    let buffer = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/the-file", cx))
-        .await
-        .unwrap();
-
-    let anchors = (0..3)
-        .map(|row| buffer.update(cx, |b, _| b.anchor_before(Point::new(row, 1))))
-        .collect::<Vec<_>>();
-
-    // Change the file on disk, adding two new lines of text, and removing
-    // one line.
-    buffer.update(cx, |buffer, _| {
-        assert!(!buffer.is_dirty());
-        assert!(!buffer.has_conflict());
-    });
-    let new_contents = "AAAA\naaa\nBB\nbbbbb\n";
-    fs.save(
-        "/dir/the-file".as_ref(),
-        &new_contents.into(),
-        LineEnding::Unix,
-    )
-    .await
-    .unwrap();
-
-    // Because the buffer was not modified, it is reloaded from disk. Its
-    // contents are edited according to the diff between the old and new
-    // file contents.
-    cx.executor().run_until_parked();
-    buffer.update(cx, |buffer, _| {
-        assert_eq!(buffer.text(), new_contents);
-        assert!(!buffer.is_dirty());
-        assert!(!buffer.has_conflict());
-
-        let anchor_positions = anchors
-            .iter()
-            .map(|anchor| anchor.to_point(&*buffer))
-            .collect::<Vec<_>>();
-        assert_eq!(
-            anchor_positions,
-            [Point::new(1, 1), Point::new(3, 1), Point::new(3, 5)]
-        );
-    });
-
-    // Modify the buffer
-    buffer.update(cx, |buffer, cx| {
-        buffer.edit([(0..0, " ")], None, cx);
-        assert!(buffer.is_dirty());
-        assert!(!buffer.has_conflict());
-    });
-
-    // Change the file on disk again, adding blank lines to the beginning.
-    fs.save(
-        "/dir/the-file".as_ref(),
-        &"\n\n\nAAAA\naaa\nBB\nbbbbb\n".into(),
-        LineEnding::Unix,
-    )
-    .await
-    .unwrap();
-
-    // Because the buffer is modified, it doesn't reload from disk, but is
-    // marked as having a conflict.
-    cx.executor().run_until_parked();
-    buffer.update(cx, |buffer, _| {
-        assert!(buffer.has_conflict());
-    });
-}
-
-#[gpui::test]
-async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "file1": "a\nb\nc\n",
-            "file2": "one\r\ntwo\r\nthree\r\n",
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-    let buffer1 = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
-        .await
-        .unwrap();
-    let buffer2 = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/file2", cx))
-        .await
-        .unwrap();
-
-    buffer1.update(cx, |buffer, _| {
-        assert_eq!(buffer.text(), "a\nb\nc\n");
-        assert_eq!(buffer.line_ending(), LineEnding::Unix);
-    });
-    buffer2.update(cx, |buffer, _| {
-        assert_eq!(buffer.text(), "one\ntwo\nthree\n");
-        assert_eq!(buffer.line_ending(), LineEnding::Windows);
-    });
-
-    // Change a file's line endings on disk from unix to windows. The buffer's
-    // state updates correctly.
-    fs.save(
-        "/dir/file1".as_ref(),
-        &"aaa\nb\nc\n".into(),
-        LineEnding::Windows,
-    )
-    .await
-    .unwrap();
-    cx.executor().run_until_parked();
-    buffer1.update(cx, |buffer, _| {
-        assert_eq!(buffer.text(), "aaa\nb\nc\n");
-        assert_eq!(buffer.line_ending(), LineEnding::Windows);
-    });
-
-    // Save a file with windows line endings. The file is written correctly.
-    buffer2.update(cx, |buffer, cx| {
-        buffer.set_text("one\ntwo\nthree\nfour\n", cx);
-    });
-    project
-        .update(cx, |project, cx| project.save_buffer(buffer2, cx))
-        .await
-        .unwrap();
-    assert_eq!(
-        fs.load("/dir/file2".as_ref()).await.unwrap(),
-        "one\r\ntwo\r\nthree\r\nfour\r\n",
-    );
-}
-
-#[gpui::test]
-async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/the-dir",
-        json!({
-            "a.rs": "
-                fn foo(mut v: Vec<usize>) {
-                    for x in &v {
-                        v.push(1);
-                    }
-                }
-            "
-            .unindent(),
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs.clone(), ["/the-dir".as_ref()], cx).await;
-    let buffer = project
-        .update(cx, |p, cx| p.open_local_buffer("/the-dir/a.rs", cx))
-        .await
-        .unwrap();
-
-    let buffer_uri = Url::from_file_path("/the-dir/a.rs").unwrap();
-    let message = lsp::PublishDiagnosticsParams {
-        uri: buffer_uri.clone(),
-        diagnostics: vec![
-            lsp::Diagnostic {
-                range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
-                severity: Some(DiagnosticSeverity::WARNING),
-                message: "error 1".to_string(),
-                related_information: Some(vec![lsp::DiagnosticRelatedInformation {
-                    location: lsp::Location {
-                        uri: buffer_uri.clone(),
-                        range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
-                    },
-                    message: "error 1 hint 1".to_string(),
-                }]),
-                ..Default::default()
-            },
-            lsp::Diagnostic {
-                range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
-                severity: Some(DiagnosticSeverity::HINT),
-                message: "error 1 hint 1".to_string(),
-                related_information: Some(vec![lsp::DiagnosticRelatedInformation {
-                    location: lsp::Location {
-                        uri: buffer_uri.clone(),
-                        range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
-                    },
-                    message: "original diagnostic".to_string(),
-                }]),
-                ..Default::default()
-            },
-            lsp::Diagnostic {
-                range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
-                severity: Some(DiagnosticSeverity::ERROR),
-                message: "error 2".to_string(),
-                related_information: Some(vec![
-                    lsp::DiagnosticRelatedInformation {
-                        location: lsp::Location {
-                            uri: buffer_uri.clone(),
-                            range: lsp::Range::new(
-                                lsp::Position::new(1, 13),
-                                lsp::Position::new(1, 15),
-                            ),
-                        },
-                        message: "error 2 hint 1".to_string(),
-                    },
-                    lsp::DiagnosticRelatedInformation {
-                        location: lsp::Location {
-                            uri: buffer_uri.clone(),
-                            range: lsp::Range::new(
-                                lsp::Position::new(1, 13),
-                                lsp::Position::new(1, 15),
-                            ),
-                        },
-                        message: "error 2 hint 2".to_string(),
-                    },
-                ]),
-                ..Default::default()
-            },
-            lsp::Diagnostic {
-                range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)),
-                severity: Some(DiagnosticSeverity::HINT),
-                message: "error 2 hint 1".to_string(),
-                related_information: Some(vec![lsp::DiagnosticRelatedInformation {
-                    location: lsp::Location {
-                        uri: buffer_uri.clone(),
-                        range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
-                    },
-                    message: "original diagnostic".to_string(),
-                }]),
-                ..Default::default()
-            },
-            lsp::Diagnostic {
-                range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)),
-                severity: Some(DiagnosticSeverity::HINT),
-                message: "error 2 hint 2".to_string(),
-                related_information: Some(vec![lsp::DiagnosticRelatedInformation {
-                    location: lsp::Location {
-                        uri: buffer_uri,
-                        range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
-                    },
-                    message: "original diagnostic".to_string(),
-                }]),
-                ..Default::default()
-            },
-        ],
-        version: None,
-    };
-
-    project
-        .update(cx, |p, cx| {
-            p.update_diagnostics(LanguageServerId(0), message, &[], cx)
-        })
-        .unwrap();
-    let buffer = buffer.update(cx, |buffer, _| buffer.snapshot());
-
-    assert_eq!(
-        buffer
-            .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
-            .collect::<Vec<_>>(),
-        &[
-            DiagnosticEntry {
-                range: Point::new(1, 8)..Point::new(1, 9),
-                diagnostic: Diagnostic {
-                    severity: DiagnosticSeverity::WARNING,
-                    message: "error 1".to_string(),
-                    group_id: 1,
-                    is_primary: true,
-                    ..Default::default()
-                }
-            },
-            DiagnosticEntry {
-                range: Point::new(1, 8)..Point::new(1, 9),
-                diagnostic: Diagnostic {
-                    severity: DiagnosticSeverity::HINT,
-                    message: "error 1 hint 1".to_string(),
-                    group_id: 1,
-                    is_primary: false,
-                    ..Default::default()
-                }
-            },
-            DiagnosticEntry {
-                range: Point::new(1, 13)..Point::new(1, 15),
-                diagnostic: Diagnostic {
-                    severity: DiagnosticSeverity::HINT,
-                    message: "error 2 hint 1".to_string(),
-                    group_id: 0,
-                    is_primary: false,
-                    ..Default::default()
-                }
-            },
-            DiagnosticEntry {
-                range: Point::new(1, 13)..Point::new(1, 15),
-                diagnostic: Diagnostic {
-                    severity: DiagnosticSeverity::HINT,
-                    message: "error 2 hint 2".to_string(),
-                    group_id: 0,
-                    is_primary: false,
-                    ..Default::default()
-                }
-            },
-            DiagnosticEntry {
-                range: Point::new(2, 8)..Point::new(2, 17),
-                diagnostic: Diagnostic {
-                    severity: DiagnosticSeverity::ERROR,
-                    message: "error 2".to_string(),
-                    group_id: 0,
-                    is_primary: true,
-                    ..Default::default()
-                }
-            }
-        ]
-    );
-
-    assert_eq!(
-        buffer.diagnostic_group::<Point>(0).collect::<Vec<_>>(),
-        &[
-            DiagnosticEntry {
-                range: Point::new(1, 13)..Point::new(1, 15),
-                diagnostic: Diagnostic {
-                    severity: DiagnosticSeverity::HINT,
-                    message: "error 2 hint 1".to_string(),
-                    group_id: 0,
-                    is_primary: false,
-                    ..Default::default()
-                }
-            },
-            DiagnosticEntry {
-                range: Point::new(1, 13)..Point::new(1, 15),
-                diagnostic: Diagnostic {
-                    severity: DiagnosticSeverity::HINT,
-                    message: "error 2 hint 2".to_string(),
-                    group_id: 0,
-                    is_primary: false,
-                    ..Default::default()
-                }
-            },
-            DiagnosticEntry {
-                range: Point::new(2, 8)..Point::new(2, 17),
-                diagnostic: Diagnostic {
-                    severity: DiagnosticSeverity::ERROR,
-                    message: "error 2".to_string(),
-                    group_id: 0,
-                    is_primary: true,
-                    ..Default::default()
-                }
-            }
-        ]
-    );
-
-    assert_eq!(
-        buffer.diagnostic_group::<Point>(1).collect::<Vec<_>>(),
-        &[
-            DiagnosticEntry {
-                range: Point::new(1, 8)..Point::new(1, 9),
-                diagnostic: Diagnostic {
-                    severity: DiagnosticSeverity::WARNING,
-                    message: "error 1".to_string(),
-                    group_id: 1,
-                    is_primary: true,
-                    ..Default::default()
-                }
-            },
-            DiagnosticEntry {
-                range: Point::new(1, 8)..Point::new(1, 9),
-                diagnostic: Diagnostic {
-                    severity: DiagnosticSeverity::HINT,
-                    message: "error 1 hint 1".to_string(),
-                    group_id: 1,
-                    is_primary: false,
-                    ..Default::default()
-                }
-            },
-        ]
-    );
-}
-
-#[gpui::test]
-async fn test_rename(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 {
-                rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
-                    prepare_provider: Some(true),
-                    work_done_progress_options: Default::default(),
-                })),
-                ..Default::default()
-            },
-            ..Default::default()
-        }))
-        .await;
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "one.rs": "const ONE: usize = 1;",
-            "two.rs": "const TWO: usize = one::ONE + one::ONE;"
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs.clone(), ["/dir".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("/dir/one.rs", cx)
-        })
-        .await
-        .unwrap();
-
-    let fake_server = fake_servers.next().await.unwrap();
-
-    let response = project.update(cx, |project, cx| {
-        project.prepare_rename(buffer.clone(), 7, cx)
-    });
-    fake_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();
-    let range = response.await.unwrap().unwrap();
-    let range = buffer.update(cx, |buffer, _| range.to_offset(buffer));
-    assert_eq!(range, 6..9);
-
-    let response = project.update(cx, |project, cx| {
-        project.perform_rename(buffer.clone(), 7, "THREE".to_string(), true, cx)
-    });
-    fake_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, 7)
-            );
-            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();
-    let mut transaction = response.await.unwrap().0;
-    assert_eq!(transaction.len(), 2);
-    assert_eq!(
-        transaction
-            .remove_entry(&buffer)
-            .unwrap()
-            .0
-            .update(cx, |buffer, _| buffer.text()),
-        "const THREE: usize = 1;"
-    );
-    assert_eq!(
-        transaction
-            .into_keys()
-            .next()
-            .unwrap()
-            .update(cx, |buffer, _| buffer.text()),
-        "const TWO: usize = one::THREE + one::THREE;"
-    );
-}
-
-#[gpui::test]
-async fn test_search(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "one.rs": "const ONE: usize = 1;",
-            "two.rs": "const TWO: usize = one::ONE + one::ONE;",
-            "three.rs": "const THREE: usize = one::ONE + two::TWO;",
-            "four.rs": "const FOUR: usize = one::ONE + three::THREE;",
-        }),
-    )
-    .await;
-    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-    assert_eq!(
-        search(
-            &project,
-            SearchQuery::text("TWO", false, true, false, Vec::new(), Vec::new()).unwrap(),
-            cx
-        )
-        .await
-        .unwrap(),
-        HashMap::from_iter([
-            ("two.rs".to_string(), vec![6..9]),
-            ("three.rs".to_string(), vec![37..40])
-        ])
-    );
-
-    let buffer_4 = project
-        .update(cx, |project, cx| {
-            project.open_local_buffer("/dir/four.rs", cx)
-        })
-        .await
-        .unwrap();
-    buffer_4.update(cx, |buffer, cx| {
-        let text = "two::TWO";
-        buffer.edit([(20..28, text), (31..43, text)], None, cx);
-    });
-
-    assert_eq!(
-        search(
-            &project,
-            SearchQuery::text("TWO", false, true, false, Vec::new(), Vec::new()).unwrap(),
-            cx
-        )
-        .await
-        .unwrap(),
-        HashMap::from_iter([
-            ("two.rs".to_string(), vec![6..9]),
-            ("three.rs".to_string(), vec![37..40]),
-            ("four.rs".to_string(), vec![25..28, 36..39])
-        ])
-    );
-}
-
-#[gpui::test]
-async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let search_query = "file";
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "one.rs": r#"// Rust file one"#,
-            "one.ts": r#"// TypeScript file one"#,
-            "two.rs": r#"// Rust file two"#,
-            "two.ts": r#"// TypeScript file two"#,
-        }),
-    )
-    .await;
-    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-
-    assert!(
-        search(
-            &project,
-            SearchQuery::text(
-                search_query,
-                false,
-                true,
-                false,
-                vec![PathMatcher::new("*.odd").unwrap()],
-                Vec::new()
-            )
-            .unwrap(),
-            cx
-        )
-        .await
-        .unwrap()
-        .is_empty(),
-        "If no inclusions match, no files should be returned"
-    );
-
-    assert_eq!(
-        search(
-            &project,
-            SearchQuery::text(
-                search_query,
-                false,
-                true,
-                false,
-                vec![PathMatcher::new("*.rs").unwrap()],
-                Vec::new()
-            )
-            .unwrap(),
-            cx
-        )
-        .await
-        .unwrap(),
-        HashMap::from_iter([
-            ("one.rs".to_string(), vec![8..12]),
-            ("two.rs".to_string(), vec![8..12]),
-        ]),
-        "Rust only search should give only Rust files"
-    );
-
-    assert_eq!(
-        search(
-            &project,
-            SearchQuery::text(
-                search_query,
-                false,
-                true,
-                false,
-                vec![
-                    PathMatcher::new("*.ts").unwrap(),
-                    PathMatcher::new("*.odd").unwrap(),
-                ],
-                Vec::new()
-            ).unwrap(),
-            cx
-        )
-        .await
-        .unwrap(),
-        HashMap::from_iter([
-            ("one.ts".to_string(), vec![14..18]),
-            ("two.ts".to_string(), vec![14..18]),
-        ]),
-        "TypeScript only search should give only TypeScript files, even if other inclusions don't match anything"
-    );
-
-    assert_eq!(
-        search(
-            &project,
-            SearchQuery::text(
-                search_query,
-                false,
-                true,
-                false,
-                vec![
-                    PathMatcher::new("*.rs").unwrap(),
-                    PathMatcher::new("*.ts").unwrap(),
-                    PathMatcher::new("*.odd").unwrap(),
-                ],
-                Vec::new()
-            ).unwrap(),
-            cx
-        )
-        .await
-        .unwrap(),
-        HashMap::from_iter([
-            ("one.rs".to_string(), vec![8..12]),
-            ("one.ts".to_string(), vec![14..18]),
-            ("two.rs".to_string(), vec![8..12]),
-            ("two.ts".to_string(), vec![14..18]),
-        ]),
-        "Rust and typescript search should give both Rust and TypeScript files, even if other inclusions don't match anything"
-    );
-}
-
-#[gpui::test]
-async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let search_query = "file";
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "one.rs": r#"// Rust file one"#,
-            "one.ts": r#"// TypeScript file one"#,
-            "two.rs": r#"// Rust file two"#,
-            "two.ts": r#"// TypeScript file two"#,
-        }),
-    )
-    .await;
-    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-
-    assert_eq!(
-        search(
-            &project,
-            SearchQuery::text(
-                search_query,
-                false,
-                true,
-                false,
-                Vec::new(),
-                vec![PathMatcher::new("*.odd").unwrap()],
-            )
-            .unwrap(),
-            cx
-        )
-        .await
-        .unwrap(),
-        HashMap::from_iter([
-            ("one.rs".to_string(), vec![8..12]),
-            ("one.ts".to_string(), vec![14..18]),
-            ("two.rs".to_string(), vec![8..12]),
-            ("two.ts".to_string(), vec![14..18]),
-        ]),
-        "If no exclusions match, all files should be returned"
-    );
-
-    assert_eq!(
-        search(
-            &project,
-            SearchQuery::text(
-                search_query,
-                false,
-                true,
-                false,
-                Vec::new(),
-                vec![PathMatcher::new("*.rs").unwrap()],
-            )
-            .unwrap(),
-            cx
-        )
-        .await
-        .unwrap(),
-        HashMap::from_iter([
-            ("one.ts".to_string(), vec![14..18]),
-            ("two.ts".to_string(), vec![14..18]),
-        ]),
-        "Rust exclusion search should give only TypeScript files"
-    );
-
-    assert_eq!(
-        search(
-            &project,
-            SearchQuery::text(
-                search_query,
-                false,
-                true,
-                false,
-                Vec::new(),
-                vec![
-                    PathMatcher::new("*.ts").unwrap(),
-                    PathMatcher::new("*.odd").unwrap(),
-                ],
-            ).unwrap(),
-            cx
-        )
-        .await
-        .unwrap(),
-        HashMap::from_iter([
-            ("one.rs".to_string(), vec![8..12]),
-            ("two.rs".to_string(), vec![8..12]),
-        ]),
-        "TypeScript exclusion search should give only Rust files, even if other exclusions don't match anything"
-    );
-
-    assert!(
-        search(
-            &project,
-            SearchQuery::text(
-                search_query,
-                false,
-                true,
-                false,
-                Vec::new(),
-                vec![
-                    PathMatcher::new("*.rs").unwrap(),
-                    PathMatcher::new("*.ts").unwrap(),
-                    PathMatcher::new("*.odd").unwrap(),
-                ],
-            ).unwrap(),
-            cx
-        )
-        .await
-        .unwrap().is_empty(),
-        "Rust and typescript exclusion should give no files, even if other exclusions don't match anything"
-    );
-}
-
-#[gpui::test]
-async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let search_query = "file";
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "one.rs": r#"// Rust file one"#,
-            "one.ts": r#"// TypeScript file one"#,
-            "two.rs": r#"// Rust file two"#,
-            "two.ts": r#"// TypeScript file two"#,
-        }),
-    )
-    .await;
-    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-
-    assert!(
-        search(
-            &project,
-            SearchQuery::text(
-                search_query,
-                false,
-                true,
-                false,
-                vec![PathMatcher::new("*.odd").unwrap()],
-                vec![PathMatcher::new("*.odd").unwrap()],
-            )
-            .unwrap(),
-            cx
-        )
-        .await
-        .unwrap()
-        .is_empty(),
-        "If both no exclusions and inclusions match, exclusions should win and return nothing"
-    );
-
-    assert!(
-        search(
-            &project,
-            SearchQuery::text(
-                search_query,
-                false,
-                true,
-                false,
-                vec![PathMatcher::new("*.ts").unwrap()],
-                vec![PathMatcher::new("*.ts").unwrap()],
-            ).unwrap(),
-            cx
-        )
-        .await
-        .unwrap()
-        .is_empty(),
-        "If both TypeScript exclusions and inclusions match, exclusions should win and return nothing files."
-    );
-
-    assert!(
-        search(
-            &project,
-            SearchQuery::text(
-                search_query,
-                false,
-                true,
-                false,
-                vec![
-                    PathMatcher::new("*.ts").unwrap(),
-                    PathMatcher::new("*.odd").unwrap()
-                ],
-                vec![
-                    PathMatcher::new("*.ts").unwrap(),
-                    PathMatcher::new("*.odd").unwrap()
-                ],
-            )
-            .unwrap(),
-            cx
-        )
-        .await
-        .unwrap()
-        .is_empty(),
-        "Non-matching inclusions and exclusions should not change that."
-    );
-
-    assert_eq!(
-        search(
-            &project,
-            SearchQuery::text(
-                search_query,
-                false,
-                true,
-                false,
-                vec![
-                    PathMatcher::new("*.ts").unwrap(),
-                    PathMatcher::new("*.odd").unwrap()
-                ],
-                vec![
-                    PathMatcher::new("*.rs").unwrap(),
-                    PathMatcher::new("*.odd").unwrap()
-                ],
-            )
-            .unwrap(),
-            cx
-        )
-        .await
-        .unwrap(),
-        HashMap::from_iter([
-            ("one.ts".to_string(), vec![14..18]),
-            ("two.ts".to_string(), vec![14..18]),
-        ]),
-        "Non-intersecting TypeScript inclusions and Rust exclusions should return TypeScript files"
-    );
-}
-
-#[gpui::test]
-async fn test_search_in_gitignored_dirs(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.background_executor.clone());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            ".git": {},
-            ".gitignore": "**/target\n/node_modules\n",
-            "target": {
-                "index.txt": "index_key:index_value"
-            },
-            "node_modules": {
-                "eslint": {
-                    "index.ts": "const eslint_key = 'eslint value'",
-                    "package.json": r#"{ "some_key": "some value" }"#,
-                },
-                "prettier": {
-                    "index.ts": "const prettier_key = 'prettier value'",
-                    "package.json": r#"{ "other_key": "other value" }"#,
-                },
-            },
-            "package.json": r#"{ "main_key": "main value" }"#,
-        }),
-    )
-    .await;
-    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-
-    let query = "key";
-    assert_eq!(
-        search(
-            &project,
-            SearchQuery::text(query, false, false, false, Vec::new(), Vec::new()).unwrap(),
-            cx
-        )
-        .await
-        .unwrap(),
-        HashMap::from_iter([("package.json".to_string(), vec![8..11])]),
-        "Only one non-ignored file should have the query"
-    );
-
-    assert_eq!(
-        search(
-            &project,
-            SearchQuery::text(query, false, false, true, Vec::new(), Vec::new()).unwrap(),
-            cx
-        )
-        .await
-        .unwrap(),
-        HashMap::from_iter([
-            ("package.json".to_string(), vec![8..11]),
-            ("target/index.txt".to_string(), vec![6..9]),
-            (
-                "node_modules/prettier/package.json".to_string(),
-                vec![9..12]
-            ),
-            ("node_modules/prettier/index.ts".to_string(), vec![15..18]),
-            ("node_modules/eslint/index.ts".to_string(), vec![13..16]),
-            ("node_modules/eslint/package.json".to_string(), vec![8..11]),
-        ]),
-        "Unrestricted search with ignored directories should find every file with the query"
-    );
-
-    assert_eq!(
-        search(
-            &project,
-            SearchQuery::text(
-                query,
-                false,
-                false,
-                true,
-                vec![PathMatcher::new("node_modules/prettier/**").unwrap()],
-                vec![PathMatcher::new("*.ts").unwrap()],
-            )
-            .unwrap(),
-            cx
-        )
-        .await
-        .unwrap(),
-        HashMap::from_iter([(
-            "node_modules/prettier/package.json".to_string(),
-            vec![9..12]
-        )]),
-        "With search including ignored prettier directory and excluding TS files, only one file should be found"
-    );
-}
-
-#[test]
-fn test_glob_literal_prefix() {
-    assert_eq!(glob_literal_prefix("**/*.js"), "");
-    assert_eq!(glob_literal_prefix("node_modules/**/*.js"), "node_modules");
-    assert_eq!(glob_literal_prefix("foo/{bar,baz}.js"), "foo");
-    assert_eq!(glob_literal_prefix("foo/bar/baz.js"), "foo/bar/baz.js");
-}
-
-async fn search(
-    project: &Model<Project>,
-    query: SearchQuery,
-    cx: &mut gpui::TestAppContext,
-) -> Result<HashMap<String, Vec<Range<usize>>>> {
-    let mut search_rx = project.update(cx, |project, cx| project.search(query, cx));
-    let mut result = HashMap::default();
-    while let Some((buffer, range)) = search_rx.next().await {
-        result.entry(buffer).or_insert(range);
-    }
-    Ok(result
-        .into_iter()
-        .map(|(buffer, ranges)| {
-            buffer.update(cx, |buffer, _| {
-                let path = buffer.file().unwrap().path().to_string_lossy().to_string();
-                let ranges = ranges
-                    .into_iter()
-                    .map(|range| range.to_offset(buffer))
-                    .collect::<Vec<_>>();
-                (path, ranges)
-            })
-        })
-        .collect())
-}
-
-fn init_test(cx: &mut gpui::TestAppContext) {
-    if std::env::var("RUST_LOG").is_ok() {
-        env_logger::try_init().ok();
-    }
-
-    cx.update(|cx| {
-        let settings_store = SettingsStore::test(cx);
-        cx.set_global(settings_store);
-        language::init(cx);
-        Project::init_settings(cx);
-    });
-}

crates/project2/src/search.rs 🔗

@@ -1,463 +0,0 @@
-use aho_corasick::{AhoCorasick, AhoCorasickBuilder};
-use anyhow::{Context, Result};
-use client::proto;
-use itertools::Itertools;
-use language::{char_kind, BufferSnapshot};
-use regex::{Regex, RegexBuilder};
-use smol::future::yield_now;
-use std::{
-    borrow::Cow,
-    io::{BufRead, BufReader, Read},
-    ops::Range,
-    path::Path,
-    sync::Arc,
-};
-use util::paths::PathMatcher;
-
-#[derive(Clone, Debug)]
-pub struct SearchInputs {
-    query: Arc<str>,
-    files_to_include: Vec<PathMatcher>,
-    files_to_exclude: Vec<PathMatcher>,
-}
-
-impl SearchInputs {
-    pub fn as_str(&self) -> &str {
-        self.query.as_ref()
-    }
-    pub fn files_to_include(&self) -> &[PathMatcher] {
-        &self.files_to_include
-    }
-    pub fn files_to_exclude(&self) -> &[PathMatcher] {
-        &self.files_to_exclude
-    }
-}
-#[derive(Clone, Debug)]
-pub enum SearchQuery {
-    Text {
-        search: Arc<AhoCorasick>,
-        replacement: Option<String>,
-        whole_word: bool,
-        case_sensitive: bool,
-        include_ignored: bool,
-        inner: SearchInputs,
-    },
-
-    Regex {
-        regex: Regex,
-        replacement: Option<String>,
-        multiline: bool,
-        whole_word: bool,
-        case_sensitive: bool,
-        include_ignored: bool,
-        inner: SearchInputs,
-    },
-}
-
-impl SearchQuery {
-    pub fn text(
-        query: impl ToString,
-        whole_word: bool,
-        case_sensitive: bool,
-        include_ignored: bool,
-        files_to_include: Vec<PathMatcher>,
-        files_to_exclude: Vec<PathMatcher>,
-    ) -> Result<Self> {
-        let query = query.to_string();
-        let search = AhoCorasickBuilder::new()
-            .ascii_case_insensitive(!case_sensitive)
-            .build(&[&query])?;
-        let inner = SearchInputs {
-            query: query.into(),
-            files_to_exclude,
-            files_to_include,
-        };
-        Ok(Self::Text {
-            search: Arc::new(search),
-            replacement: None,
-            whole_word,
-            case_sensitive,
-            include_ignored,
-            inner,
-        })
-    }
-
-    pub fn regex(
-        query: impl ToString,
-        whole_word: bool,
-        case_sensitive: bool,
-        include_ignored: bool,
-        files_to_include: Vec<PathMatcher>,
-        files_to_exclude: Vec<PathMatcher>,
-    ) -> Result<Self> {
-        let mut query = query.to_string();
-        let initial_query = Arc::from(query.as_str());
-        if whole_word {
-            let mut word_query = String::new();
-            word_query.push_str("\\b");
-            word_query.push_str(&query);
-            word_query.push_str("\\b");
-            query = word_query
-        }
-
-        let multiline = query.contains('\n') || query.contains("\\n");
-        let regex = RegexBuilder::new(&query)
-            .case_insensitive(!case_sensitive)
-            .multi_line(multiline)
-            .build()?;
-        let inner = SearchInputs {
-            query: initial_query,
-            files_to_exclude,
-            files_to_include,
-        };
-        Ok(Self::Regex {
-            regex,
-            replacement: None,
-            multiline,
-            whole_word,
-            case_sensitive,
-            include_ignored,
-            inner,
-        })
-    }
-
-    pub fn from_proto(message: proto::SearchProject) -> Result<Self> {
-        if message.regex {
-            Self::regex(
-                message.query,
-                message.whole_word,
-                message.case_sensitive,
-                message.include_ignored,
-                deserialize_path_matches(&message.files_to_include)?,
-                deserialize_path_matches(&message.files_to_exclude)?,
-            )
-        } else {
-            Self::text(
-                message.query,
-                message.whole_word,
-                message.case_sensitive,
-                message.include_ignored,
-                deserialize_path_matches(&message.files_to_include)?,
-                deserialize_path_matches(&message.files_to_exclude)?,
-            )
-        }
-    }
-    pub fn with_replacement(mut self, new_replacement: String) -> Self {
-        match self {
-            Self::Text {
-                ref mut replacement,
-                ..
-            }
-            | Self::Regex {
-                ref mut replacement,
-                ..
-            } => {
-                *replacement = Some(new_replacement);
-                self
-            }
-        }
-    }
-    pub fn to_proto(&self, project_id: u64) -> proto::SearchProject {
-        proto::SearchProject {
-            project_id,
-            query: self.as_str().to_string(),
-            regex: self.is_regex(),
-            whole_word: self.whole_word(),
-            case_sensitive: self.case_sensitive(),
-            include_ignored: self.include_ignored(),
-            files_to_include: self
-                .files_to_include()
-                .iter()
-                .map(|matcher| matcher.to_string())
-                .join(","),
-            files_to_exclude: self
-                .files_to_exclude()
-                .iter()
-                .map(|matcher| matcher.to_string())
-                .join(","),
-        }
-    }
-
-    pub fn detect<T: Read>(&self, stream: T) -> Result<bool> {
-        if self.as_str().is_empty() {
-            return Ok(false);
-        }
-
-        match self {
-            Self::Text { search, .. } => {
-                let mat = search.stream_find_iter(stream).next();
-                match mat {
-                    Some(Ok(_)) => Ok(true),
-                    Some(Err(err)) => Err(err.into()),
-                    None => Ok(false),
-                }
-            }
-            Self::Regex {
-                regex, multiline, ..
-            } => {
-                let mut reader = BufReader::new(stream);
-                if *multiline {
-                    let mut text = String::new();
-                    if let Err(err) = reader.read_to_string(&mut text) {
-                        Err(err.into())
-                    } else {
-                        Ok(regex.find(&text).is_some())
-                    }
-                } else {
-                    for line in reader.lines() {
-                        let line = line?;
-                        if regex.find(&line).is_some() {
-                            return Ok(true);
-                        }
-                    }
-                    Ok(false)
-                }
-            }
-        }
-    }
-    /// Returns the replacement text for this `SearchQuery`.
-    pub fn replacement(&self) -> Option<&str> {
-        match self {
-            SearchQuery::Text { replacement, .. } | SearchQuery::Regex { replacement, .. } => {
-                replacement.as_deref()
-            }
-        }
-    }
-    /// Replaces search hits if replacement is set. `text` is assumed to be a string that matches this `SearchQuery` exactly, without any leftovers on either side.
-    pub fn replacement_for<'a>(&self, text: &'a str) -> Option<Cow<'a, str>> {
-        match self {
-            SearchQuery::Text { replacement, .. } => replacement.clone().map(Cow::from),
-            SearchQuery::Regex {
-                regex, replacement, ..
-            } => {
-                if let Some(replacement) = replacement {
-                    Some(regex.replace(text, replacement))
-                } else {
-                    None
-                }
-            }
-        }
-    }
-    pub async fn search(
-        &self,
-        buffer: &BufferSnapshot,
-        subrange: Option<Range<usize>>,
-    ) -> Vec<Range<usize>> {
-        const YIELD_INTERVAL: usize = 20000;
-
-        if self.as_str().is_empty() {
-            return Default::default();
-        }
-
-        let range_offset = subrange.as_ref().map(|r| r.start).unwrap_or(0);
-        let rope = if let Some(range) = subrange {
-            buffer.as_rope().slice(range)
-        } else {
-            buffer.as_rope().clone()
-        };
-
-        let mut matches = Vec::new();
-        match self {
-            Self::Text {
-                search, whole_word, ..
-            } => {
-                for (ix, mat) in search
-                    .stream_find_iter(rope.bytes_in_range(0..rope.len()))
-                    .enumerate()
-                {
-                    if (ix + 1) % YIELD_INTERVAL == 0 {
-                        yield_now().await;
-                    }
-
-                    let mat = mat.unwrap();
-                    if *whole_word {
-                        let scope = buffer.language_scope_at(range_offset + mat.start());
-                        let kind = |c| char_kind(&scope, c);
-
-                        let prev_kind = rope.reversed_chars_at(mat.start()).next().map(kind);
-                        let start_kind = kind(rope.chars_at(mat.start()).next().unwrap());
-                        let end_kind = kind(rope.reversed_chars_at(mat.end()).next().unwrap());
-                        let next_kind = rope.chars_at(mat.end()).next().map(kind);
-                        if Some(start_kind) == prev_kind || Some(end_kind) == next_kind {
-                            continue;
-                        }
-                    }
-                    matches.push(mat.start()..mat.end())
-                }
-            }
-
-            Self::Regex {
-                regex, multiline, ..
-            } => {
-                if *multiline {
-                    let text = rope.to_string();
-                    for (ix, mat) in regex.find_iter(&text).enumerate() {
-                        if (ix + 1) % YIELD_INTERVAL == 0 {
-                            yield_now().await;
-                        }
-
-                        matches.push(mat.start()..mat.end());
-                    }
-                } else {
-                    let mut line = String::new();
-                    let mut line_offset = 0;
-                    for (chunk_ix, chunk) in rope.chunks().chain(["\n"]).enumerate() {
-                        if (chunk_ix + 1) % YIELD_INTERVAL == 0 {
-                            yield_now().await;
-                        }
-
-                        for (newline_ix, text) in chunk.split('\n').enumerate() {
-                            if newline_ix > 0 {
-                                for mat in regex.find_iter(&line) {
-                                    let start = line_offset + mat.start();
-                                    let end = line_offset + mat.end();
-                                    matches.push(start..end);
-                                }
-
-                                line_offset += line.len() + 1;
-                                line.clear();
-                            }
-                            line.push_str(text);
-                        }
-                    }
-                }
-            }
-        }
-
-        matches
-    }
-
-    pub fn as_str(&self) -> &str {
-        self.as_inner().as_str()
-    }
-
-    pub fn whole_word(&self) -> bool {
-        match self {
-            Self::Text { whole_word, .. } => *whole_word,
-            Self::Regex { whole_word, .. } => *whole_word,
-        }
-    }
-
-    pub fn case_sensitive(&self) -> bool {
-        match self {
-            Self::Text { case_sensitive, .. } => *case_sensitive,
-            Self::Regex { case_sensitive, .. } => *case_sensitive,
-        }
-    }
-
-    pub fn include_ignored(&self) -> bool {
-        match self {
-            Self::Text {
-                include_ignored, ..
-            } => *include_ignored,
-            Self::Regex {
-                include_ignored, ..
-            } => *include_ignored,
-        }
-    }
-
-    pub fn is_regex(&self) -> bool {
-        matches!(self, Self::Regex { .. })
-    }
-
-    pub fn files_to_include(&self) -> &[PathMatcher] {
-        self.as_inner().files_to_include()
-    }
-
-    pub fn files_to_exclude(&self) -> &[PathMatcher] {
-        self.as_inner().files_to_exclude()
-    }
-
-    pub fn file_matches(&self, file_path: Option<&Path>) -> bool {
-        match file_path {
-            Some(file_path) => {
-                let mut path = file_path.to_path_buf();
-                loop {
-                    if self
-                        .files_to_exclude()
-                        .iter()
-                        .any(|exclude_glob| exclude_glob.is_match(&path))
-                    {
-                        return false;
-                    } else if self.files_to_include().is_empty()
-                        || self
-                            .files_to_include()
-                            .iter()
-                            .any(|include_glob| include_glob.is_match(&path))
-                    {
-                        return true;
-                    } else if !path.pop() {
-                        return false;
-                    }
-                }
-            }
-            None => self.files_to_include().is_empty(),
-        }
-    }
-    pub fn as_inner(&self) -> &SearchInputs {
-        match self {
-            Self::Regex { inner, .. } | Self::Text { inner, .. } => inner,
-        }
-    }
-}
-
-fn deserialize_path_matches(glob_set: &str) -> anyhow::Result<Vec<PathMatcher>> {
-    glob_set
-        .split(',')
-        .map(str::trim)
-        .filter(|glob_str| !glob_str.is_empty())
-        .map(|glob_str| {
-            PathMatcher::new(glob_str)
-                .with_context(|| format!("deserializing path match glob {glob_str}"))
-        })
-        .collect()
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn path_matcher_creation_for_valid_paths() {
-        for valid_path in [
-            "file",
-            "Cargo.toml",
-            ".DS_Store",
-            "~/dir/another_dir/",
-            "./dir/file",
-            "dir/[a-z].txt",
-            "../dir/filé",
-        ] {
-            let path_matcher = PathMatcher::new(valid_path).unwrap_or_else(|e| {
-                panic!("Valid path {valid_path} should be accepted, but got: {e}")
-            });
-            assert!(
-                path_matcher.is_match(valid_path),
-                "Path matcher for valid path {valid_path} should match itself"
-            )
-        }
-    }
-
-    #[test]
-    fn path_matcher_creation_for_globs() {
-        for invalid_glob in ["dir/[].txt", "dir/[a-z.txt", "dir/{file"] {
-            match PathMatcher::new(invalid_glob) {
-                Ok(_) => panic!("Invalid glob {invalid_glob} should not be accepted"),
-                Err(_expected) => {}
-            }
-        }
-
-        for valid_glob in [
-            "dir/?ile",
-            "dir/*.txt",
-            "dir/**/file",
-            "dir/[a-z].txt",
-            "{dir,file}",
-        ] {
-            match PathMatcher::new(valid_glob) {
-                Ok(_expected) => {}
-                Err(e) => panic!("Valid glob {valid_glob} should be accepted, but got: {e}"),
-            }
-        }
-    }
-}

crates/project2/src/terminals.rs 🔗

@@ -1,128 +0,0 @@
-use crate::Project;
-use gpui::{AnyWindowHandle, Context, Entity, Model, ModelContext, WeakModel};
-use settings::Settings;
-use std::path::{Path, PathBuf};
-use terminal::{
-    terminal_settings::{self, TerminalSettings, VenvSettingsContent},
-    Terminal, TerminalBuilder,
-};
-
-#[cfg(target_os = "macos")]
-use std::os::unix::ffi::OsStrExt;
-
-pub struct Terminals {
-    pub(crate) local_handles: Vec<WeakModel<terminal::Terminal>>,
-}
-
-impl Project {
-    pub fn create_terminal(
-        &mut self,
-        working_directory: Option<PathBuf>,
-        window: AnyWindowHandle,
-        cx: &mut ModelContext<Self>,
-    ) -> anyhow::Result<Model<Terminal>> {
-        if self.is_remote() {
-            return Err(anyhow::anyhow!(
-                "creating terminals as a guest is not supported yet"
-            ));
-        } else {
-            let settings = TerminalSettings::get_global(cx);
-            let python_settings = settings.detect_venv.clone();
-            let shell = settings.shell.clone();
-
-            let terminal = TerminalBuilder::new(
-                working_directory.clone(),
-                shell.clone(),
-                settings.env.clone(),
-                Some(settings.blinking.clone()),
-                settings.alternate_scroll,
-                window,
-            )
-            .map(|builder| {
-                let terminal_handle = cx.new_model(|cx| builder.subscribe(cx));
-
-                self.terminals
-                    .local_handles
-                    .push(terminal_handle.downgrade());
-
-                let id = terminal_handle.entity_id();
-                cx.observe_release(&terminal_handle, move |project, _terminal, cx| {
-                    let handles = &mut project.terminals.local_handles;
-
-                    if let Some(index) = handles
-                        .iter()
-                        .position(|terminal| terminal.entity_id() == id)
-                    {
-                        handles.remove(index);
-                        cx.notify();
-                    }
-                })
-                .detach();
-
-                if let Some(python_settings) = &python_settings.as_option() {
-                    let activate_script_path =
-                        self.find_activate_script_path(&python_settings, working_directory);
-                    self.activate_python_virtual_environment(
-                        activate_script_path,
-                        &terminal_handle,
-                        cx,
-                    );
-                }
-                terminal_handle
-            });
-
-            terminal
-        }
-    }
-
-    pub fn find_activate_script_path(
-        &mut self,
-        settings: &VenvSettingsContent,
-        working_directory: Option<PathBuf>,
-    ) -> Option<PathBuf> {
-        // When we are unable to resolve the working directory, the terminal builder
-        // defaults to '/'. We should probably encode this directly somewhere, but for
-        // now, let's just hard code it here.
-        let working_directory = working_directory.unwrap_or_else(|| Path::new("/").to_path_buf());
-        let activate_script_name = match settings.activate_script {
-            terminal_settings::ActivateScript::Default => "activate",
-            terminal_settings::ActivateScript::Csh => "activate.csh",
-            terminal_settings::ActivateScript::Fish => "activate.fish",
-            terminal_settings::ActivateScript::Nushell => "activate.nu",
-        };
-
-        for virtual_environment_name in settings.directories {
-            let mut path = working_directory.join(virtual_environment_name);
-            path.push("bin/");
-            path.push(activate_script_name);
-
-            if path.exists() {
-                return Some(path);
-            }
-        }
-
-        None
-    }
-
-    fn activate_python_virtual_environment(
-        &mut self,
-        activate_script: Option<PathBuf>,
-        terminal_handle: &Model<Terminal>,
-        cx: &mut ModelContext<Project>,
-    ) {
-        if let Some(activate_script) = activate_script {
-            // Paths are not strings so we need to jump through some hoops to format the command without `format!`
-            let mut command = Vec::from("source ".as_bytes());
-            command.extend_from_slice(activate_script.as_os_str().as_bytes());
-            command.push(b'\n');
-
-            terminal_handle.update(cx, |this, _| this.input_bytes(command));
-        }
-    }
-
-    pub fn local_terminal_handles(&self) -> &Vec<WeakModel<terminal::Terminal>> {
-        &self.terminals.local_handles
-    }
-}
-
-// TODO: Add a few tests for adding and removing terminal tabs

crates/project2/src/worktree.rs 🔗

@@ -1,4576 +0,0 @@
-use crate::{
-    copy_recursive, ignore::IgnoreStack, project_settings::ProjectSettings, DiagnosticSummary,
-    ProjectEntryId, RemoveOptions,
-};
-use ::ignore::gitignore::{Gitignore, GitignoreBuilder};
-use anyhow::{anyhow, Context as _, Result};
-use client::{proto, Client};
-use clock::ReplicaId;
-use collections::{HashMap, HashSet, VecDeque};
-use fs::{
-    repository::{GitFileStatus, GitRepository, RepoPath},
-    Fs,
-};
-use futures::{
-    channel::{
-        mpsc::{self, UnboundedSender},
-        oneshot,
-    },
-    select_biased,
-    task::Poll,
-    FutureExt as _, Stream, StreamExt,
-};
-use fuzzy::CharBag;
-use git::{DOT_GIT, GITIGNORE};
-use gpui::{
-    AppContext, AsyncAppContext, BackgroundExecutor, Context, EventEmitter, Model, ModelContext,
-    Task,
-};
-use itertools::Itertools;
-use language::{
-    proto::{
-        deserialize_fingerprint, deserialize_version, serialize_fingerprint, serialize_line_ending,
-        serialize_version,
-    },
-    Buffer, DiagnosticEntry, File as _, LineEnding, PointUtf16, Rope, RopeFingerprint, Unclipped,
-};
-use lsp::LanguageServerId;
-use parking_lot::Mutex;
-use postage::{
-    barrier,
-    prelude::{Sink as _, Stream as _},
-    watch,
-};
-use settings::{Settings, SettingsStore};
-use smol::channel::{self, Sender};
-use std::{
-    any::Any,
-    cmp::{self, Ordering},
-    convert::TryFrom,
-    ffi::OsStr,
-    fmt,
-    future::Future,
-    mem,
-    ops::{AddAssign, Deref, DerefMut, Sub},
-    path::{Path, PathBuf},
-    pin::Pin,
-    sync::{
-        atomic::{AtomicUsize, Ordering::SeqCst},
-        Arc,
-    },
-    time::{Duration, SystemTime},
-};
-use sum_tree::{Bias, Edit, SeekTarget, SumTree, TreeMap, TreeSet};
-use util::{
-    paths::{PathMatcher, HOME},
-    ResultExt,
-};
-
-#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
-pub struct WorktreeId(usize);
-
-pub enum Worktree {
-    Local(LocalWorktree),
-    Remote(RemoteWorktree),
-}
-
-pub struct LocalWorktree {
-    snapshot: LocalSnapshot,
-    scan_requests_tx: channel::Sender<ScanRequest>,
-    path_prefixes_to_scan_tx: channel::Sender<Arc<Path>>,
-    is_scanning: (watch::Sender<bool>, watch::Receiver<bool>),
-    _background_scanner_tasks: Vec<Task<()>>,
-    share: Option<ShareState>,
-    diagnostics: HashMap<
-        Arc<Path>,
-        Vec<(
-            LanguageServerId,
-            Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
-        )>,
-    >,
-    diagnostic_summaries: HashMap<Arc<Path>, HashMap<LanguageServerId, DiagnosticSummary>>,
-    client: Arc<Client>,
-    fs: Arc<dyn Fs>,
-    visible: bool,
-}
-
-struct ScanRequest {
-    relative_paths: Vec<Arc<Path>>,
-    done: barrier::Sender,
-}
-
-pub struct RemoteWorktree {
-    snapshot: Snapshot,
-    background_snapshot: Arc<Mutex<Snapshot>>,
-    project_id: u64,
-    client: Arc<Client>,
-    updates_tx: Option<UnboundedSender<proto::UpdateWorktree>>,
-    snapshot_subscriptions: VecDeque<(usize, oneshot::Sender<()>)>,
-    replica_id: ReplicaId,
-    diagnostic_summaries: HashMap<Arc<Path>, HashMap<LanguageServerId, DiagnosticSummary>>,
-    visible: bool,
-    disconnected: bool,
-}
-
-#[derive(Clone)]
-pub struct Snapshot {
-    id: WorktreeId,
-    abs_path: Arc<Path>,
-    root_name: String,
-    root_char_bag: CharBag,
-    entries_by_path: SumTree<Entry>,
-    entries_by_id: SumTree<PathEntry>,
-    repository_entries: TreeMap<RepositoryWorkDirectory, RepositoryEntry>,
-
-    /// A number that increases every time the worktree begins scanning
-    /// a set of paths from the filesystem. This scanning could be caused
-    /// by some operation performed on the worktree, such as reading or
-    /// writing a file, or by an event reported by the filesystem.
-    scan_id: usize,
-
-    /// The latest scan id that has completed, and whose preceding scans
-    /// have all completed. The current `scan_id` could be more than one
-    /// greater than the `completed_scan_id` if operations are performed
-    /// on the worktree while it is processing a file-system event.
-    completed_scan_id: usize,
-}
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct RepositoryEntry {
-    pub(crate) work_directory: WorkDirectoryEntry,
-    pub(crate) branch: Option<Arc<str>>,
-}
-
-impl RepositoryEntry {
-    pub fn branch(&self) -> Option<Arc<str>> {
-        self.branch.clone()
-    }
-
-    pub fn work_directory_id(&self) -> ProjectEntryId {
-        *self.work_directory
-    }
-
-    pub fn work_directory(&self, snapshot: &Snapshot) -> Option<RepositoryWorkDirectory> {
-        snapshot
-            .entry_for_id(self.work_directory_id())
-            .map(|entry| RepositoryWorkDirectory(entry.path.clone()))
-    }
-
-    pub fn build_update(&self, _: &Self) -> proto::RepositoryEntry {
-        proto::RepositoryEntry {
-            work_directory_id: self.work_directory_id().to_proto(),
-            branch: self.branch.as_ref().map(|str| str.to_string()),
-        }
-    }
-}
-
-impl From<&RepositoryEntry> for proto::RepositoryEntry {
-    fn from(value: &RepositoryEntry) -> Self {
-        proto::RepositoryEntry {
-            work_directory_id: value.work_directory.to_proto(),
-            branch: value.branch.as_ref().map(|str| str.to_string()),
-        }
-    }
-}
-
-/// This path corresponds to the 'content path' (the folder that contains the .git)
-#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
-pub struct RepositoryWorkDirectory(pub(crate) Arc<Path>);
-
-impl Default for RepositoryWorkDirectory {
-    fn default() -> Self {
-        RepositoryWorkDirectory(Arc::from(Path::new("")))
-    }
-}
-
-impl AsRef<Path> for RepositoryWorkDirectory {
-    fn as_ref(&self) -> &Path {
-        self.0.as_ref()
-    }
-}
-
-#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
-pub struct WorkDirectoryEntry(ProjectEntryId);
-
-impl WorkDirectoryEntry {
-    pub(crate) fn relativize(&self, worktree: &Snapshot, path: &Path) -> Option<RepoPath> {
-        worktree.entry_for_id(self.0).and_then(|entry| {
-            path.strip_prefix(&entry.path)
-                .ok()
-                .map(move |path| path.into())
-        })
-    }
-}
-
-impl Deref for WorkDirectoryEntry {
-    type Target = ProjectEntryId;
-
-    fn deref(&self) -> &Self::Target {
-        &self.0
-    }
-}
-
-impl<'a> From<ProjectEntryId> for WorkDirectoryEntry {
-    fn from(value: ProjectEntryId) -> Self {
-        WorkDirectoryEntry(value)
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct LocalSnapshot {
-    snapshot: Snapshot,
-    /// All of the gitignore files in the worktree, indexed by their relative path.
-    /// The boolean indicates whether the gitignore needs to be updated.
-    ignores_by_parent_abs_path: HashMap<Arc<Path>, (Arc<Gitignore>, bool)>,
-    /// All of the git repositories in the worktree, indexed by the project entry
-    /// id of their parent directory.
-    git_repositories: TreeMap<ProjectEntryId, LocalRepositoryEntry>,
-    file_scan_exclusions: Vec<PathMatcher>,
-}
-
-struct BackgroundScannerState {
-    snapshot: LocalSnapshot,
-    scanned_dirs: HashSet<ProjectEntryId>,
-    path_prefixes_to_scan: HashSet<Arc<Path>>,
-    paths_to_scan: HashSet<Arc<Path>>,
-    /// The ids of all of the entries that were removed from the snapshot
-    /// as part of the current update. These entry ids may be re-used
-    /// if the same inode is discovered at a new path, or if the given
-    /// path is re-created after being deleted.
-    removed_entry_ids: HashMap<u64, ProjectEntryId>,
-    changed_paths: Vec<Arc<Path>>,
-    prev_snapshot: Snapshot,
-}
-
-#[derive(Debug, Clone)]
-pub struct LocalRepositoryEntry {
-    pub(crate) git_dir_scan_id: usize,
-    pub(crate) repo_ptr: Arc<Mutex<dyn GitRepository>>,
-    /// Path to the actual .git folder.
-    /// Note: if .git is a file, this points to the folder indicated by the .git file
-    pub(crate) git_dir_path: Arc<Path>,
-}
-
-impl Deref for LocalSnapshot {
-    type Target = Snapshot;
-
-    fn deref(&self) -> &Self::Target {
-        &self.snapshot
-    }
-}
-
-impl DerefMut for LocalSnapshot {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.snapshot
-    }
-}
-
-enum ScanState {
-    Started,
-    Updated {
-        snapshot: LocalSnapshot,
-        changes: UpdatedEntriesSet,
-        barrier: Option<barrier::Sender>,
-        scanning: bool,
-    },
-}
-
-struct ShareState {
-    project_id: u64,
-    snapshots_tx:
-        mpsc::UnboundedSender<(LocalSnapshot, UpdatedEntriesSet, UpdatedGitRepositoriesSet)>,
-    resume_updates: watch::Sender<()>,
-    _maintain_remote_snapshot: Task<Option<()>>,
-}
-
-#[derive(Clone)]
-pub enum Event {
-    UpdatedEntries(UpdatedEntriesSet),
-    UpdatedGitRepositories(UpdatedGitRepositoriesSet),
-}
-
-impl EventEmitter<Event> for Worktree {}
-
-impl Worktree {
-    pub async fn local(
-        client: Arc<Client>,
-        path: impl Into<Arc<Path>>,
-        visible: bool,
-        fs: Arc<dyn Fs>,
-        next_entry_id: Arc<AtomicUsize>,
-        cx: &mut AsyncAppContext,
-    ) -> Result<Model<Self>> {
-        // After determining whether the root entry is a file or a directory, populate the
-        // snapshot's "root name", which will be used for the purpose of fuzzy matching.
-        let abs_path = path.into();
-
-        let metadata = fs
-            .metadata(&abs_path)
-            .await
-            .context("failed to stat worktree path")?;
-
-        let closure_fs = Arc::clone(&fs);
-        let closure_next_entry_id = Arc::clone(&next_entry_id);
-        let closure_abs_path = abs_path.to_path_buf();
-        cx.new_model(move |cx: &mut ModelContext<Worktree>| {
-            cx.observe_global::<SettingsStore>(move |this, cx| {
-                if let Self::Local(this) = this {
-                    let new_file_scan_exclusions =
-                        file_scan_exclusions(ProjectSettings::get_global(cx));
-                    if new_file_scan_exclusions != this.snapshot.file_scan_exclusions {
-                        this.snapshot.file_scan_exclusions = new_file_scan_exclusions;
-                        log::info!(
-                            "Re-scanning directories, new scan exclude files: {:?}",
-                            this.snapshot
-                                .file_scan_exclusions
-                                .iter()
-                                .map(ToString::to_string)
-                                .collect::<Vec<_>>()
-                        );
-
-                        let (scan_requests_tx, scan_requests_rx) = channel::unbounded();
-                        let (path_prefixes_to_scan_tx, path_prefixes_to_scan_rx) =
-                            channel::unbounded();
-                        this.scan_requests_tx = scan_requests_tx;
-                        this.path_prefixes_to_scan_tx = path_prefixes_to_scan_tx;
-                        this._background_scanner_tasks = start_background_scan_tasks(
-                            &closure_abs_path,
-                            this.snapshot(),
-                            scan_requests_rx,
-                            path_prefixes_to_scan_rx,
-                            Arc::clone(&closure_next_entry_id),
-                            Arc::clone(&closure_fs),
-                            cx,
-                        );
-                        this.is_scanning = watch::channel_with(true);
-                    }
-                }
-            })
-            .detach();
-
-            let root_name = abs_path
-                .file_name()
-                .map_or(String::new(), |f| f.to_string_lossy().to_string());
-
-            let mut snapshot = LocalSnapshot {
-                file_scan_exclusions: file_scan_exclusions(ProjectSettings::get_global(cx)),
-                ignores_by_parent_abs_path: Default::default(),
-                git_repositories: Default::default(),
-                snapshot: Snapshot {
-                    id: WorktreeId::from_usize(cx.entity_id().as_u64() as usize),
-                    abs_path: abs_path.to_path_buf().into(),
-                    root_name: root_name.clone(),
-                    root_char_bag: root_name.chars().map(|c| c.to_ascii_lowercase()).collect(),
-                    entries_by_path: Default::default(),
-                    entries_by_id: Default::default(),
-                    repository_entries: Default::default(),
-                    scan_id: 1,
-                    completed_scan_id: 0,
-                },
-            };
-
-            if let Some(metadata) = metadata {
-                snapshot.insert_entry(
-                    Entry::new(
-                        Arc::from(Path::new("")),
-                        &metadata,
-                        &next_entry_id,
-                        snapshot.root_char_bag,
-                    ),
-                    fs.as_ref(),
-                );
-            }
-
-            let (scan_requests_tx, scan_requests_rx) = channel::unbounded();
-            let (path_prefixes_to_scan_tx, path_prefixes_to_scan_rx) = channel::unbounded();
-            let task_snapshot = snapshot.clone();
-            Worktree::Local(LocalWorktree {
-                snapshot,
-                is_scanning: watch::channel_with(true),
-                share: None,
-                scan_requests_tx,
-                path_prefixes_to_scan_tx,
-                _background_scanner_tasks: start_background_scan_tasks(
-                    &abs_path,
-                    task_snapshot,
-                    scan_requests_rx,
-                    path_prefixes_to_scan_rx,
-                    Arc::clone(&next_entry_id),
-                    Arc::clone(&fs),
-                    cx,
-                ),
-                diagnostics: Default::default(),
-                diagnostic_summaries: Default::default(),
-                client,
-                fs,
-                visible,
-            })
-        })
-    }
-
-    pub fn remote(
-        project_remote_id: u64,
-        replica_id: ReplicaId,
-        worktree: proto::WorktreeMetadata,
-        client: Arc<Client>,
-        cx: &mut AppContext,
-    ) -> Model<Self> {
-        cx.new_model(|cx: &mut ModelContext<Self>| {
-            let snapshot = Snapshot {
-                id: WorktreeId(worktree.id as usize),
-                abs_path: Arc::from(PathBuf::from(worktree.abs_path)),
-                root_name: worktree.root_name.clone(),
-                root_char_bag: worktree
-                    .root_name
-                    .chars()
-                    .map(|c| c.to_ascii_lowercase())
-                    .collect(),
-                entries_by_path: Default::default(),
-                entries_by_id: Default::default(),
-                repository_entries: Default::default(),
-                scan_id: 1,
-                completed_scan_id: 0,
-            };
-
-            let (updates_tx, mut updates_rx) = mpsc::unbounded();
-            let background_snapshot = Arc::new(Mutex::new(snapshot.clone()));
-            let (mut snapshot_updated_tx, mut snapshot_updated_rx) = watch::channel();
-
-            cx.background_executor()
-                .spawn({
-                    let background_snapshot = background_snapshot.clone();
-                    async move {
-                        while let Some(update) = updates_rx.next().await {
-                            if let Err(error) =
-                                background_snapshot.lock().apply_remote_update(update)
-                            {
-                                log::error!("error applying worktree update: {}", error);
-                            }
-                            snapshot_updated_tx.send(()).await.ok();
-                        }
-                    }
-                })
-                .detach();
-
-            cx.spawn(|this, mut cx| async move {
-                while (snapshot_updated_rx.recv().await).is_some() {
-                    this.update(&mut cx, |this, cx| {
-                        let this = this.as_remote_mut().unwrap();
-                        this.snapshot = this.background_snapshot.lock().clone();
-                        cx.emit(Event::UpdatedEntries(Arc::from([])));
-                        cx.notify();
-                        while let Some((scan_id, _)) = this.snapshot_subscriptions.front() {
-                            if this.observed_snapshot(*scan_id) {
-                                let (_, tx) = this.snapshot_subscriptions.pop_front().unwrap();
-                                let _ = tx.send(());
-                            } else {
-                                break;
-                            }
-                        }
-                    })?;
-                }
-                anyhow::Ok(())
-            })
-            .detach();
-
-            Worktree::Remote(RemoteWorktree {
-                project_id: project_remote_id,
-                replica_id,
-                snapshot: snapshot.clone(),
-                background_snapshot,
-                updates_tx: Some(updates_tx),
-                snapshot_subscriptions: Default::default(),
-                client: client.clone(),
-                diagnostic_summaries: Default::default(),
-                visible: worktree.visible,
-                disconnected: false,
-            })
-        })
-    }
-
-    pub fn as_local(&self) -> Option<&LocalWorktree> {
-        if let Worktree::Local(worktree) = self {
-            Some(worktree)
-        } else {
-            None
-        }
-    }
-
-    pub fn as_remote(&self) -> Option<&RemoteWorktree> {
-        if let Worktree::Remote(worktree) = self {
-            Some(worktree)
-        } else {
-            None
-        }
-    }
-
-    pub fn as_local_mut(&mut self) -> Option<&mut LocalWorktree> {
-        if let Worktree::Local(worktree) = self {
-            Some(worktree)
-        } else {
-            None
-        }
-    }
-
-    pub fn as_remote_mut(&mut self) -> Option<&mut RemoteWorktree> {
-        if let Worktree::Remote(worktree) = self {
-            Some(worktree)
-        } else {
-            None
-        }
-    }
-
-    pub fn is_local(&self) -> bool {
-        matches!(self, Worktree::Local(_))
-    }
-
-    pub fn is_remote(&self) -> bool {
-        !self.is_local()
-    }
-
-    pub fn snapshot(&self) -> Snapshot {
-        match self {
-            Worktree::Local(worktree) => worktree.snapshot().snapshot,
-            Worktree::Remote(worktree) => worktree.snapshot(),
-        }
-    }
-
-    pub fn scan_id(&self) -> usize {
-        match self {
-            Worktree::Local(worktree) => worktree.snapshot.scan_id,
-            Worktree::Remote(worktree) => worktree.snapshot.scan_id,
-        }
-    }
-
-    pub fn completed_scan_id(&self) -> usize {
-        match self {
-            Worktree::Local(worktree) => worktree.snapshot.completed_scan_id,
-            Worktree::Remote(worktree) => worktree.snapshot.completed_scan_id,
-        }
-    }
-
-    pub fn is_visible(&self) -> bool {
-        match self {
-            Worktree::Local(worktree) => worktree.visible,
-            Worktree::Remote(worktree) => worktree.visible,
-        }
-    }
-
-    pub fn replica_id(&self) -> ReplicaId {
-        match self {
-            Worktree::Local(_) => 0,
-            Worktree::Remote(worktree) => worktree.replica_id,
-        }
-    }
-
-    pub fn diagnostic_summaries(
-        &self,
-    ) -> impl Iterator<Item = (Arc<Path>, LanguageServerId, DiagnosticSummary)> + '_ {
-        match self {
-            Worktree::Local(worktree) => &worktree.diagnostic_summaries,
-            Worktree::Remote(worktree) => &worktree.diagnostic_summaries,
-        }
-        .iter()
-        .flat_map(|(path, summaries)| {
-            summaries
-                .iter()
-                .map(move |(&server_id, &summary)| (path.clone(), server_id, summary))
-        })
-    }
-
-    pub fn abs_path(&self) -> Arc<Path> {
-        match self {
-            Worktree::Local(worktree) => worktree.abs_path.clone(),
-            Worktree::Remote(worktree) => worktree.abs_path.clone(),
-        }
-    }
-
-    pub fn root_file(&self, cx: &mut ModelContext<Self>) -> Option<Arc<File>> {
-        let entry = self.root_entry()?;
-        Some(File::for_entry(entry.clone(), cx.handle()))
-    }
-}
-
-fn start_background_scan_tasks(
-    abs_path: &Path,
-    snapshot: LocalSnapshot,
-    scan_requests_rx: channel::Receiver<ScanRequest>,
-    path_prefixes_to_scan_rx: channel::Receiver<Arc<Path>>,
-    next_entry_id: Arc<AtomicUsize>,
-    fs: Arc<dyn Fs>,
-    cx: &mut ModelContext<'_, Worktree>,
-) -> Vec<Task<()>> {
-    let (scan_states_tx, mut scan_states_rx) = mpsc::unbounded();
-    let background_scanner = cx.background_executor().spawn({
-        let abs_path = abs_path.to_path_buf();
-        let background = cx.background_executor().clone();
-        async move {
-            let events = fs.watch(&abs_path, Duration::from_millis(100)).await;
-            BackgroundScanner::new(
-                snapshot,
-                next_entry_id,
-                fs,
-                scan_states_tx,
-                background,
-                scan_requests_rx,
-                path_prefixes_to_scan_rx,
-            )
-            .run(events)
-            .await;
-        }
-    });
-    let scan_state_updater = cx.spawn(|this, mut cx| async move {
-        while let Some((state, this)) = scan_states_rx.next().await.zip(this.upgrade()) {
-            this.update(&mut cx, |this, cx| {
-                let this = this.as_local_mut().unwrap();
-                match state {
-                    ScanState::Started => {
-                        *this.is_scanning.0.borrow_mut() = true;
-                    }
-                    ScanState::Updated {
-                        snapshot,
-                        changes,
-                        barrier,
-                        scanning,
-                    } => {
-                        *this.is_scanning.0.borrow_mut() = scanning;
-                        this.set_snapshot(snapshot, changes, cx);
-                        drop(barrier);
-                    }
-                }
-                cx.notify();
-            })
-            .ok();
-        }
-    });
-    vec![background_scanner, scan_state_updater]
-}
-
-fn file_scan_exclusions(project_settings: &ProjectSettings) -> Vec<PathMatcher> {
-    project_settings.file_scan_exclusions.as_deref().unwrap_or(&[]).iter()
-    .sorted()
-    .filter_map(|pattern| {
-        PathMatcher::new(pattern)
-            .map(Some)
-            .unwrap_or_else(|e| {
-                log::error!(
-                    "Skipping pattern {pattern} in `file_scan_exclusions` project settings due to parsing error: {e:#}"
-                );
-                None
-            })
-    })
-    .collect()
-}
-
-impl LocalWorktree {
-    pub fn contains_abs_path(&self, path: &Path) -> bool {
-        path.starts_with(&self.abs_path)
-    }
-
-    pub(crate) fn load_buffer(
-        &mut self,
-        id: u64,
-        path: &Path,
-        cx: &mut ModelContext<Worktree>,
-    ) -> Task<Result<Model<Buffer>>> {
-        let path = Arc::from(path);
-        cx.spawn(move |this, mut cx| async move {
-            let (file, contents, diff_base) = this
-                .update(&mut cx, |t, cx| t.as_local().unwrap().load(&path, cx))?
-                .await?;
-            let text_buffer = cx
-                .background_executor()
-                .spawn(async move { text::Buffer::new(0, id, contents) })
-                .await;
-            cx.new_model(|_| Buffer::build(text_buffer, diff_base, Some(Arc::new(file))))
-        })
-    }
-
-    pub fn diagnostics_for_path(
-        &self,
-        path: &Path,
-    ) -> Vec<(
-        LanguageServerId,
-        Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
-    )> {
-        self.diagnostics.get(path).cloned().unwrap_or_default()
-    }
-
-    pub fn clear_diagnostics_for_language_server(
-        &mut self,
-        server_id: LanguageServerId,
-        _: &mut ModelContext<Worktree>,
-    ) {
-        let worktree_id = self.id().to_proto();
-        self.diagnostic_summaries
-            .retain(|path, summaries_by_server_id| {
-                if summaries_by_server_id.remove(&server_id).is_some() {
-                    if let Some(share) = self.share.as_ref() {
-                        self.client
-                            .send(proto::UpdateDiagnosticSummary {
-                                project_id: share.project_id,
-                                worktree_id,
-                                summary: Some(proto::DiagnosticSummary {
-                                    path: path.to_string_lossy().to_string(),
-                                    language_server_id: server_id.0 as u64,
-                                    error_count: 0,
-                                    warning_count: 0,
-                                }),
-                            })
-                            .log_err();
-                    }
-                    !summaries_by_server_id.is_empty()
-                } else {
-                    true
-                }
-            });
-
-        self.diagnostics.retain(|_, diagnostics_by_server_id| {
-            if let Ok(ix) = diagnostics_by_server_id.binary_search_by_key(&server_id, |e| e.0) {
-                diagnostics_by_server_id.remove(ix);
-                !diagnostics_by_server_id.is_empty()
-            } else {
-                true
-            }
-        });
-    }
-
-    pub fn update_diagnostics(
-        &mut self,
-        server_id: LanguageServerId,
-        worktree_path: Arc<Path>,
-        diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
-        _: &mut ModelContext<Worktree>,
-    ) -> Result<bool> {
-        let summaries_by_server_id = self
-            .diagnostic_summaries
-            .entry(worktree_path.clone())
-            .or_default();
-
-        let old_summary = summaries_by_server_id
-            .remove(&server_id)
-            .unwrap_or_default();
-
-        let new_summary = DiagnosticSummary::new(&diagnostics);
-        if new_summary.is_empty() {
-            if let Some(diagnostics_by_server_id) = self.diagnostics.get_mut(&worktree_path) {
-                if let Ok(ix) = diagnostics_by_server_id.binary_search_by_key(&server_id, |e| e.0) {
-                    diagnostics_by_server_id.remove(ix);
-                }
-                if diagnostics_by_server_id.is_empty() {
-                    self.diagnostics.remove(&worktree_path);
-                }
-            }
-        } else {
-            summaries_by_server_id.insert(server_id, new_summary);
-            let diagnostics_by_server_id =
-                self.diagnostics.entry(worktree_path.clone()).or_default();
-            match diagnostics_by_server_id.binary_search_by_key(&server_id, |e| e.0) {
-                Ok(ix) => {
-                    diagnostics_by_server_id[ix] = (server_id, diagnostics);
-                }
-                Err(ix) => {
-                    diagnostics_by_server_id.insert(ix, (server_id, diagnostics));
-                }
-            }
-        }
-
-        if !old_summary.is_empty() || !new_summary.is_empty() {
-            if let Some(share) = self.share.as_ref() {
-                self.client
-                    .send(proto::UpdateDiagnosticSummary {
-                        project_id: share.project_id,
-                        worktree_id: self.id().to_proto(),
-                        summary: Some(proto::DiagnosticSummary {
-                            path: worktree_path.to_string_lossy().to_string(),
-                            language_server_id: server_id.0 as u64,
-                            error_count: new_summary.error_count as u32,
-                            warning_count: new_summary.warning_count as u32,
-                        }),
-                    })
-                    .log_err();
-            }
-        }
-
-        Ok(!old_summary.is_empty() || !new_summary.is_empty())
-    }
-
-    fn set_snapshot(
-        &mut self,
-        new_snapshot: LocalSnapshot,
-        entry_changes: UpdatedEntriesSet,
-        cx: &mut ModelContext<Worktree>,
-    ) {
-        let repo_changes = self.changed_repos(&self.snapshot, &new_snapshot);
-
-        self.snapshot = new_snapshot;
-
-        if let Some(share) = self.share.as_mut() {
-            share
-                .snapshots_tx
-                .unbounded_send((
-                    self.snapshot.clone(),
-                    entry_changes.clone(),
-                    repo_changes.clone(),
-                ))
-                .ok();
-        }
-
-        if !entry_changes.is_empty() {
-            cx.emit(Event::UpdatedEntries(entry_changes));
-        }
-        if !repo_changes.is_empty() {
-            cx.emit(Event::UpdatedGitRepositories(repo_changes));
-        }
-    }
-
-    fn changed_repos(
-        &self,
-        old_snapshot: &LocalSnapshot,
-        new_snapshot: &LocalSnapshot,
-    ) -> UpdatedGitRepositoriesSet {
-        let mut changes = Vec::new();
-        let mut old_repos = old_snapshot.git_repositories.iter().peekable();
-        let mut new_repos = new_snapshot.git_repositories.iter().peekable();
-        loop {
-            match (new_repos.peek().map(clone), old_repos.peek().map(clone)) {
-                (Some((new_entry_id, new_repo)), Some((old_entry_id, old_repo))) => {
-                    match Ord::cmp(&new_entry_id, &old_entry_id) {
-                        Ordering::Less => {
-                            if let Some(entry) = new_snapshot.entry_for_id(new_entry_id) {
-                                changes.push((
-                                    entry.path.clone(),
-                                    GitRepositoryChange {
-                                        old_repository: None,
-                                    },
-                                ));
-                            }
-                            new_repos.next();
-                        }
-                        Ordering::Equal => {
-                            if new_repo.git_dir_scan_id != old_repo.git_dir_scan_id {
-                                if let Some(entry) = new_snapshot.entry_for_id(new_entry_id) {
-                                    let old_repo = old_snapshot
-                                        .repository_entries
-                                        .get(&RepositoryWorkDirectory(entry.path.clone()))
-                                        .cloned();
-                                    changes.push((
-                                        entry.path.clone(),
-                                        GitRepositoryChange {
-                                            old_repository: old_repo,
-                                        },
-                                    ));
-                                }
-                            }
-                            new_repos.next();
-                            old_repos.next();
-                        }
-                        Ordering::Greater => {
-                            if let Some(entry) = old_snapshot.entry_for_id(old_entry_id) {
-                                let old_repo = old_snapshot
-                                    .repository_entries
-                                    .get(&RepositoryWorkDirectory(entry.path.clone()))
-                                    .cloned();
-                                changes.push((
-                                    entry.path.clone(),
-                                    GitRepositoryChange {
-                                        old_repository: old_repo,
-                                    },
-                                ));
-                            }
-                            old_repos.next();
-                        }
-                    }
-                }
-                (Some((entry_id, _)), None) => {
-                    if let Some(entry) = new_snapshot.entry_for_id(entry_id) {
-                        changes.push((
-                            entry.path.clone(),
-                            GitRepositoryChange {
-                                old_repository: None,
-                            },
-                        ));
-                    }
-                    new_repos.next();
-                }
-                (None, Some((entry_id, _))) => {
-                    if let Some(entry) = old_snapshot.entry_for_id(entry_id) {
-                        let old_repo = old_snapshot
-                            .repository_entries
-                            .get(&RepositoryWorkDirectory(entry.path.clone()))
-                            .cloned();
-                        changes.push((
-                            entry.path.clone(),
-                            GitRepositoryChange {
-                                old_repository: old_repo,
-                            },
-                        ));
-                    }
-                    old_repos.next();
-                }
-                (None, None) => break,
-            }
-        }
-
-        fn clone<T: Clone, U: Clone>(value: &(&T, &U)) -> (T, U) {
-            (value.0.clone(), value.1.clone())
-        }
-
-        changes.into()
-    }
-
-    pub fn scan_complete(&self) -> impl Future<Output = ()> {
-        let mut is_scanning_rx = self.is_scanning.1.clone();
-        async move {
-            let mut is_scanning = is_scanning_rx.borrow().clone();
-            while is_scanning {
-                if let Some(value) = is_scanning_rx.recv().await {
-                    is_scanning = value;
-                } else {
-                    break;
-                }
-            }
-        }
-    }
-
-    pub fn snapshot(&self) -> LocalSnapshot {
-        self.snapshot.clone()
-    }
-
-    pub fn metadata_proto(&self) -> proto::WorktreeMetadata {
-        proto::WorktreeMetadata {
-            id: self.id().to_proto(),
-            root_name: self.root_name().to_string(),
-            visible: self.visible,
-            abs_path: self.abs_path().as_os_str().to_string_lossy().into(),
-        }
-    }
-
-    fn load(
-        &self,
-        path: &Path,
-        cx: &mut ModelContext<Worktree>,
-    ) -> Task<Result<(File, String, Option<String>)>> {
-        let path = Arc::from(path);
-        let abs_path = self.absolutize(&path);
-        let fs = self.fs.clone();
-        let entry = self.refresh_entry(path.clone(), None, cx);
-
-        cx.spawn(|this, mut cx| async move {
-            let text = fs.load(&abs_path).await?;
-            let mut index_task = None;
-            let snapshot = this.update(&mut cx, |this, _| this.as_local().unwrap().snapshot())?;
-            if let Some(repo) = snapshot.repository_for_path(&path) {
-                let repo_path = repo.work_directory.relativize(&snapshot, &path).unwrap();
-                if let Some(repo) = snapshot.git_repositories.get(&*repo.work_directory) {
-                    let repo = repo.repo_ptr.clone();
-                    index_task = Some(
-                        cx.background_executor()
-                            .spawn(async move { repo.lock().load_index_text(&repo_path) }),
-                    );
-                }
-            }
-
-            let diff_base = if let Some(index_task) = index_task {
-                index_task.await
-            } else {
-                None
-            };
-
-            let worktree = this
-                .upgrade()
-                .ok_or_else(|| anyhow!("worktree was dropped"))?;
-            match entry.await? {
-                Some(entry) => Ok((
-                    File {
-                        entry_id: Some(entry.id),
-                        worktree,
-                        path: entry.path,
-                        mtime: entry.mtime,
-                        is_local: true,
-                        is_deleted: false,
-                    },
-                    text,
-                    diff_base,
-                )),
-                None => {
-                    let metadata = fs
-                        .metadata(&abs_path)
-                        .await
-                        .with_context(|| {
-                            format!("Loading metadata for excluded file {abs_path:?}")
-                        })?
-                        .with_context(|| {
-                            format!("Excluded file {abs_path:?} got removed during loading")
-                        })?;
-                    Ok((
-                        File {
-                            entry_id: None,
-                            worktree,
-                            path,
-                            mtime: metadata.mtime,
-                            is_local: true,
-                            is_deleted: false,
-                        },
-                        text,
-                        diff_base,
-                    ))
-                }
-            }
-        })
-    }
-
-    pub fn save_buffer(
-        &self,
-        buffer_handle: Model<Buffer>,
-        path: Arc<Path>,
-        has_changed_file: bool,
-        cx: &mut ModelContext<Worktree>,
-    ) -> Task<Result<()>> {
-        let buffer = buffer_handle.read(cx);
-
-        let rpc = self.client.clone();
-        let buffer_id = buffer.remote_id();
-        let project_id = self.share.as_ref().map(|share| share.project_id);
-
-        let text = buffer.as_rope().clone();
-        let fingerprint = text.fingerprint();
-        let version = buffer.version();
-        let save = self.write_file(path.as_ref(), text, buffer.line_ending(), cx);
-        let fs = Arc::clone(&self.fs);
-        let abs_path = self.absolutize(&path);
-
-        cx.spawn(move |this, mut cx| async move {
-            let entry = save.await?;
-            let this = this.upgrade().context("worktree dropped")?;
-
-            let (entry_id, mtime, path) = match entry {
-                Some(entry) => (Some(entry.id), entry.mtime, entry.path),
-                None => {
-                    let metadata = fs
-                        .metadata(&abs_path)
-                        .await
-                        .with_context(|| {
-                            format!(
-                                "Fetching metadata after saving the excluded buffer {abs_path:?}"
-                            )
-                        })?
-                        .with_context(|| {
-                            format!("Excluded buffer {path:?} got removed during saving")
-                        })?;
-                    (None, metadata.mtime, path)
-                }
-            };
-
-            if has_changed_file {
-                let new_file = Arc::new(File {
-                    entry_id,
-                    worktree: this,
-                    path,
-                    mtime,
-                    is_local: true,
-                    is_deleted: false,
-                });
-
-                if let Some(project_id) = project_id {
-                    rpc.send(proto::UpdateBufferFile {
-                        project_id,
-                        buffer_id,
-                        file: Some(new_file.to_proto()),
-                    })
-                    .log_err();
-                }
-
-                buffer_handle.update(&mut cx, |buffer, cx| {
-                    if has_changed_file {
-                        buffer.file_updated(new_file, cx);
-                    }
-                })?;
-            }
-
-            if let Some(project_id) = project_id {
-                rpc.send(proto::BufferSaved {
-                    project_id,
-                    buffer_id,
-                    version: serialize_version(&version),
-                    mtime: Some(mtime.into()),
-                    fingerprint: serialize_fingerprint(fingerprint),
-                })?;
-            }
-
-            buffer_handle.update(&mut cx, |buffer, cx| {
-                buffer.did_save(version.clone(), fingerprint, mtime, cx);
-            })?;
-
-            Ok(())
-        })
-    }
-
-    /// Find the lowest path in the worktree's datastructures that is an ancestor
-    fn lowest_ancestor(&self, path: &Path) -> PathBuf {
-        let mut lowest_ancestor = None;
-        for path in path.ancestors() {
-            if self.entry_for_path(path).is_some() {
-                lowest_ancestor = Some(path.to_path_buf());
-                break;
-            }
-        }
-
-        lowest_ancestor.unwrap_or_else(|| PathBuf::from(""))
-    }
-
-    pub fn create_entry(
-        &self,
-        path: impl Into<Arc<Path>>,
-        is_dir: bool,
-        cx: &mut ModelContext<Worktree>,
-    ) -> Task<Result<Option<Entry>>> {
-        let path = path.into();
-        let lowest_ancestor = self.lowest_ancestor(&path);
-        let abs_path = self.absolutize(&path);
-        let fs = self.fs.clone();
-        let write = cx.background_executor().spawn(async move {
-            if is_dir {
-                fs.create_dir(&abs_path).await
-            } else {
-                fs.save(&abs_path, &Default::default(), Default::default())
-                    .await
-            }
-        });
-
-        cx.spawn(|this, mut cx| async move {
-            write.await?;
-            let (result, refreshes) = this.update(&mut cx, |this, cx| {
-                let mut refreshes = Vec::new();
-                let refresh_paths = path.strip_prefix(&lowest_ancestor).unwrap();
-                for refresh_path in refresh_paths.ancestors() {
-                    if refresh_path == Path::new("") {
-                        continue;
-                    }
-                    let refresh_full_path = lowest_ancestor.join(refresh_path);
-
-                    refreshes.push(this.as_local_mut().unwrap().refresh_entry(
-                        refresh_full_path.into(),
-                        None,
-                        cx,
-                    ));
-                }
-                (
-                    this.as_local_mut().unwrap().refresh_entry(path, None, cx),
-                    refreshes,
-                )
-            })?;
-            for refresh in refreshes {
-                refresh.await.log_err();
-            }
-
-            result.await
-        })
-    }
-
-    pub(crate) fn write_file(
-        &self,
-        path: impl Into<Arc<Path>>,
-        text: Rope,
-        line_ending: LineEnding,
-        cx: &mut ModelContext<Worktree>,
-    ) -> Task<Result<Option<Entry>>> {
-        let path: Arc<Path> = path.into();
-        let abs_path = self.absolutize(&path);
-        let fs = self.fs.clone();
-        let write = cx
-            .background_executor()
-            .spawn(async move { fs.save(&abs_path, &text, line_ending).await });
-
-        cx.spawn(|this, mut cx| async move {
-            write.await?;
-            this.update(&mut cx, |this, cx| {
-                this.as_local_mut().unwrap().refresh_entry(path, None, cx)
-            })?
-            .await
-        })
-    }
-
-    pub fn delete_entry(
-        &self,
-        entry_id: ProjectEntryId,
-        cx: &mut ModelContext<Worktree>,
-    ) -> Option<Task<Result<()>>> {
-        let entry = self.entry_for_id(entry_id)?.clone();
-        let abs_path = self.absolutize(&entry.path);
-        let fs = self.fs.clone();
-
-        let delete = cx.background_executor().spawn(async move {
-            if entry.is_file() {
-                fs.remove_file(&abs_path, Default::default()).await?;
-            } else {
-                fs.remove_dir(
-                    &abs_path,
-                    RemoveOptions {
-                        recursive: true,
-                        ignore_if_not_exists: false,
-                    },
-                )
-                .await?;
-            }
-            anyhow::Ok(entry.path)
-        });
-
-        Some(cx.spawn(|this, mut cx| async move {
-            let path = delete.await?;
-            this.update(&mut cx, |this, _| {
-                this.as_local_mut()
-                    .unwrap()
-                    .refresh_entries_for_paths(vec![path])
-            })?
-            .recv()
-            .await;
-            Ok(())
-        }))
-    }
-
-    pub fn rename_entry(
-        &self,
-        entry_id: ProjectEntryId,
-        new_path: impl Into<Arc<Path>>,
-        cx: &mut ModelContext<Worktree>,
-    ) -> Task<Result<Option<Entry>>> {
-        let old_path = match self.entry_for_id(entry_id) {
-            Some(entry) => entry.path.clone(),
-            None => return Task::ready(Ok(None)),
-        };
-        let new_path = new_path.into();
-        let abs_old_path = self.absolutize(&old_path);
-        let abs_new_path = self.absolutize(&new_path);
-        let fs = self.fs.clone();
-        let rename = cx.background_executor().spawn(async move {
-            fs.rename(&abs_old_path, &abs_new_path, Default::default())
-                .await
-        });
-
-        cx.spawn(|this, mut cx| async move {
-            rename.await?;
-            this.update(&mut cx, |this, cx| {
-                this.as_local_mut()
-                    .unwrap()
-                    .refresh_entry(new_path.clone(), Some(old_path), cx)
-            })?
-            .await
-        })
-    }
-
-    pub fn copy_entry(
-        &self,
-        entry_id: ProjectEntryId,
-        new_path: impl Into<Arc<Path>>,
-        cx: &mut ModelContext<Worktree>,
-    ) -> Task<Result<Option<Entry>>> {
-        let old_path = match self.entry_for_id(entry_id) {
-            Some(entry) => entry.path.clone(),
-            None => return Task::ready(Ok(None)),
-        };
-        let new_path = new_path.into();
-        let abs_old_path = self.absolutize(&old_path);
-        let abs_new_path = self.absolutize(&new_path);
-        let fs = self.fs.clone();
-        let copy = cx.background_executor().spawn(async move {
-            copy_recursive(
-                fs.as_ref(),
-                &abs_old_path,
-                &abs_new_path,
-                Default::default(),
-            )
-            .await
-        });
-
-        cx.spawn(|this, mut cx| async move {
-            copy.await?;
-            this.update(&mut cx, |this, cx| {
-                this.as_local_mut()
-                    .unwrap()
-                    .refresh_entry(new_path.clone(), None, cx)
-            })?
-            .await
-        })
-    }
-
-    pub fn expand_entry(
-        &mut self,
-        entry_id: ProjectEntryId,
-        cx: &mut ModelContext<Worktree>,
-    ) -> Option<Task<Result<()>>> {
-        let path = self.entry_for_id(entry_id)?.path.clone();
-        let mut refresh = self.refresh_entries_for_paths(vec![path]);
-        Some(cx.background_executor().spawn(async move {
-            refresh.next().await;
-            Ok(())
-        }))
-    }
-
-    pub fn refresh_entries_for_paths(&self, paths: Vec<Arc<Path>>) -> barrier::Receiver {
-        let (tx, rx) = barrier::channel();
-        self.scan_requests_tx
-            .try_send(ScanRequest {
-                relative_paths: paths,
-                done: tx,
-            })
-            .ok();
-        rx
-    }
-
-    pub fn add_path_prefix_to_scan(&self, path_prefix: Arc<Path>) {
-        self.path_prefixes_to_scan_tx.try_send(path_prefix).ok();
-    }
-
-    fn refresh_entry(
-        &self,
-        path: Arc<Path>,
-        old_path: Option<Arc<Path>>,
-        cx: &mut ModelContext<Worktree>,
-    ) -> Task<Result<Option<Entry>>> {
-        if self.is_path_excluded(path.to_path_buf()) {
-            return Task::ready(Ok(None));
-        }
-        let paths = if let Some(old_path) = old_path.as_ref() {
-            vec![old_path.clone(), path.clone()]
-        } else {
-            vec![path.clone()]
-        };
-        let mut refresh = self.refresh_entries_for_paths(paths);
-        cx.spawn(move |this, mut cx| async move {
-            refresh.recv().await;
-            let new_entry = this.update(&mut cx, |this, _| {
-                this.entry_for_path(path)
-                    .cloned()
-                    .ok_or_else(|| anyhow!("failed to read path after update"))
-            })??;
-            Ok(Some(new_entry))
-        })
-    }
-
-    pub fn observe_updates<F, Fut>(
-        &mut self,
-        project_id: u64,
-        cx: &mut ModelContext<Worktree>,
-        callback: F,
-    ) -> oneshot::Receiver<()>
-    where
-        F: 'static + Send + Fn(proto::UpdateWorktree) -> Fut,
-        Fut: Send + Future<Output = bool>,
-    {
-        #[cfg(any(test, feature = "test-support"))]
-        const MAX_CHUNK_SIZE: usize = 2;
-        #[cfg(not(any(test, feature = "test-support")))]
-        const MAX_CHUNK_SIZE: usize = 256;
-
-        let (share_tx, share_rx) = oneshot::channel();
-
-        if let Some(share) = self.share.as_mut() {
-            share_tx.send(()).ok();
-            *share.resume_updates.borrow_mut() = ();
-            return share_rx;
-        }
-
-        let (resume_updates_tx, mut resume_updates_rx) = watch::channel::<()>();
-        let (snapshots_tx, mut snapshots_rx) =
-            mpsc::unbounded::<(LocalSnapshot, UpdatedEntriesSet, UpdatedGitRepositoriesSet)>();
-        snapshots_tx
-            .unbounded_send((self.snapshot(), Arc::from([]), Arc::from([])))
-            .ok();
-
-        let worktree_id = cx.entity_id().as_u64();
-        let _maintain_remote_snapshot = cx.background_executor().spawn(async move {
-            let mut is_first = true;
-            while let Some((snapshot, entry_changes, repo_changes)) = snapshots_rx.next().await {
-                let update;
-                if is_first {
-                    update = snapshot.build_initial_update(project_id, worktree_id);
-                    is_first = false;
-                } else {
-                    update =
-                        snapshot.build_update(project_id, worktree_id, entry_changes, repo_changes);
-                }
-
-                for update in proto::split_worktree_update(update, MAX_CHUNK_SIZE) {
-                    let _ = resume_updates_rx.try_recv();
-                    loop {
-                        let result = callback(update.clone());
-                        if result.await {
-                            break;
-                        } else {
-                            log::info!("waiting to resume updates");
-                            if resume_updates_rx.next().await.is_none() {
-                                return Some(());
-                            }
-                        }
-                    }
-                }
-            }
-            share_tx.send(()).ok();
-            Some(())
-        });
-
-        self.share = Some(ShareState {
-            project_id,
-            snapshots_tx,
-            resume_updates: resume_updates_tx,
-            _maintain_remote_snapshot,
-        });
-        share_rx
-    }
-
-    pub fn share(&mut self, project_id: u64, cx: &mut ModelContext<Worktree>) -> Task<Result<()>> {
-        let client = self.client.clone();
-
-        for (path, summaries) in &self.diagnostic_summaries {
-            for (&server_id, summary) in summaries {
-                if let Err(e) = self.client.send(proto::UpdateDiagnosticSummary {
-                    project_id,
-                    worktree_id: cx.entity_id().as_u64(),
-                    summary: Some(summary.to_proto(server_id, &path)),
-                }) {
-                    return Task::ready(Err(e));
-                }
-            }
-        }
-
-        let rx = self.observe_updates(project_id, cx, move |update| {
-            client.request(update).map(|result| result.is_ok())
-        });
-        cx.background_executor()
-            .spawn(async move { rx.await.map_err(|_| anyhow!("share ended")) })
-    }
-
-    pub fn unshare(&mut self) {
-        self.share.take();
-    }
-
-    pub fn is_shared(&self) -> bool {
-        self.share.is_some()
-    }
-}
-
-impl RemoteWorktree {
-    fn snapshot(&self) -> Snapshot {
-        self.snapshot.clone()
-    }
-
-    pub fn disconnected_from_host(&mut self) {
-        self.updates_tx.take();
-        self.snapshot_subscriptions.clear();
-        self.disconnected = true;
-    }
-
-    pub fn save_buffer(
-        &self,
-        buffer_handle: Model<Buffer>,
-        cx: &mut ModelContext<Worktree>,
-    ) -> Task<Result<()>> {
-        let buffer = buffer_handle.read(cx);
-        let buffer_id = buffer.remote_id();
-        let version = buffer.version();
-        let rpc = self.client.clone();
-        let project_id = self.project_id;
-        cx.spawn(move |_, mut cx| async move {
-            let response = rpc
-                .request(proto::SaveBuffer {
-                    project_id,
-                    buffer_id,
-                    version: serialize_version(&version),
-                })
-                .await?;
-            let version = deserialize_version(&response.version);
-            let fingerprint = deserialize_fingerprint(&response.fingerprint)?;
-            let mtime = response
-                .mtime
-                .ok_or_else(|| anyhow!("missing mtime"))?
-                .into();
-
-            buffer_handle.update(&mut cx, |buffer, cx| {
-                buffer.did_save(version.clone(), fingerprint, mtime, cx);
-            })?;
-
-            Ok(())
-        })
-    }
-
-    pub fn update_from_remote(&mut self, update: proto::UpdateWorktree) {
-        if let Some(updates_tx) = &self.updates_tx {
-            updates_tx
-                .unbounded_send(update)
-                .expect("consumer runs to completion");
-        }
-    }
-
-    fn observed_snapshot(&self, scan_id: usize) -> bool {
-        self.completed_scan_id >= scan_id
-    }
-
-    pub(crate) fn wait_for_snapshot(&mut self, scan_id: usize) -> impl Future<Output = Result<()>> {
-        let (tx, rx) = oneshot::channel();
-        if self.observed_snapshot(scan_id) {
-            let _ = tx.send(());
-        } else if self.disconnected {
-            drop(tx);
-        } else {
-            match self
-                .snapshot_subscriptions
-                .binary_search_by_key(&scan_id, |probe| probe.0)
-            {
-                Ok(ix) | Err(ix) => self.snapshot_subscriptions.insert(ix, (scan_id, tx)),
-            }
-        }
-
-        async move {
-            rx.await?;
-            Ok(())
-        }
-    }
-
-    pub fn update_diagnostic_summary(
-        &mut self,
-        path: Arc<Path>,
-        summary: &proto::DiagnosticSummary,
-    ) {
-        let server_id = LanguageServerId(summary.language_server_id as usize);
-        let summary = DiagnosticSummary {
-            error_count: summary.error_count as usize,
-            warning_count: summary.warning_count as usize,
-        };
-
-        if summary.is_empty() {
-            if let Some(summaries) = self.diagnostic_summaries.get_mut(&path) {
-                summaries.remove(&server_id);
-                if summaries.is_empty() {
-                    self.diagnostic_summaries.remove(&path);
-                }
-            }
-        } else {
-            self.diagnostic_summaries
-                .entry(path)
-                .or_default()
-                .insert(server_id, summary);
-        }
-    }
-
-    pub fn insert_entry(
-        &mut self,
-        entry: proto::Entry,
-        scan_id: usize,
-        cx: &mut ModelContext<Worktree>,
-    ) -> Task<Result<Entry>> {
-        let wait_for_snapshot = self.wait_for_snapshot(scan_id);
-        cx.spawn(|this, mut cx| async move {
-            wait_for_snapshot.await?;
-            this.update(&mut cx, |worktree, _| {
-                let worktree = worktree.as_remote_mut().unwrap();
-                let mut snapshot = worktree.background_snapshot.lock();
-                let entry = snapshot.insert_entry(entry);
-                worktree.snapshot = snapshot.clone();
-                entry
-            })?
-        })
-    }
-
-    pub(crate) fn delete_entry(
-        &mut self,
-        id: ProjectEntryId,
-        scan_id: usize,
-        cx: &mut ModelContext<Worktree>,
-    ) -> Task<Result<()>> {
-        let wait_for_snapshot = self.wait_for_snapshot(scan_id);
-        cx.spawn(move |this, mut cx| async move {
-            wait_for_snapshot.await?;
-            this.update(&mut cx, |worktree, _| {
-                let worktree = worktree.as_remote_mut().unwrap();
-                let mut snapshot = worktree.background_snapshot.lock();
-                snapshot.delete_entry(id);
-                worktree.snapshot = snapshot.clone();
-            })?;
-            Ok(())
-        })
-    }
-}
-
-impl Snapshot {
-    pub fn id(&self) -> WorktreeId {
-        self.id
-    }
-
-    pub fn abs_path(&self) -> &Arc<Path> {
-        &self.abs_path
-    }
-
-    pub fn absolutize(&self, path: &Path) -> PathBuf {
-        if path.file_name().is_some() {
-            self.abs_path.join(path)
-        } else {
-            self.abs_path.to_path_buf()
-        }
-    }
-
-    pub fn contains_entry(&self, entry_id: ProjectEntryId) -> bool {
-        self.entries_by_id.get(&entry_id, &()).is_some()
-    }
-
-    fn insert_entry(&mut self, entry: proto::Entry) -> Result<Entry> {
-        let entry = Entry::try_from((&self.root_char_bag, entry))?;
-        let old_entry = self.entries_by_id.insert_or_replace(
-            PathEntry {
-                id: entry.id,
-                path: entry.path.clone(),
-                is_ignored: entry.is_ignored,
-                scan_id: 0,
-            },
-            &(),
-        );
-        if let Some(old_entry) = old_entry {
-            self.entries_by_path.remove(&PathKey(old_entry.path), &());
-        }
-        self.entries_by_path.insert_or_replace(entry.clone(), &());
-        Ok(entry)
-    }
-
-    fn delete_entry(&mut self, entry_id: ProjectEntryId) -> Option<Arc<Path>> {
-        let removed_entry = self.entries_by_id.remove(&entry_id, &())?;
-        self.entries_by_path = {
-            let mut cursor = self.entries_by_path.cursor::<TraversalProgress>();
-            let mut new_entries_by_path =
-                cursor.slice(&TraversalTarget::Path(&removed_entry.path), Bias::Left, &());
-            while let Some(entry) = cursor.item() {
-                if entry.path.starts_with(&removed_entry.path) {
-                    self.entries_by_id.remove(&entry.id, &());
-                    cursor.next(&());
-                } else {
-                    break;
-                }
-            }
-            new_entries_by_path.append(cursor.suffix(&()), &());
-            new_entries_by_path
-        };
-
-        Some(removed_entry.path)
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn status_for_file(&self, path: impl Into<PathBuf>) -> Option<GitFileStatus> {
-        let path = path.into();
-        self.entries_by_path
-            .get(&PathKey(Arc::from(path)), &())
-            .and_then(|entry| entry.git_status)
-    }
-
-    pub(crate) fn apply_remote_update(&mut self, mut update: proto::UpdateWorktree) -> Result<()> {
-        let mut entries_by_path_edits = Vec::new();
-        let mut entries_by_id_edits = Vec::new();
-
-        for entry_id in update.removed_entries {
-            let entry_id = ProjectEntryId::from_proto(entry_id);
-            entries_by_id_edits.push(Edit::Remove(entry_id));
-            if let Some(entry) = self.entry_for_id(entry_id) {
-                entries_by_path_edits.push(Edit::Remove(PathKey(entry.path.clone())));
-            }
-        }
-
-        for entry in update.updated_entries {
-            let entry = Entry::try_from((&self.root_char_bag, entry))?;
-            if let Some(PathEntry { path, .. }) = self.entries_by_id.get(&entry.id, &()) {
-                entries_by_path_edits.push(Edit::Remove(PathKey(path.clone())));
-            }
-            if let Some(old_entry) = self.entries_by_path.get(&PathKey(entry.path.clone()), &()) {
-                if old_entry.id != entry.id {
-                    entries_by_id_edits.push(Edit::Remove(old_entry.id));
-                }
-            }
-            entries_by_id_edits.push(Edit::Insert(PathEntry {
-                id: entry.id,
-                path: entry.path.clone(),
-                is_ignored: entry.is_ignored,
-                scan_id: 0,
-            }));
-            entries_by_path_edits.push(Edit::Insert(entry));
-        }
-
-        self.entries_by_path.edit(entries_by_path_edits, &());
-        self.entries_by_id.edit(entries_by_id_edits, &());
-
-        update.removed_repositories.sort_unstable();
-        self.repository_entries.retain(|_, entry| {
-            if let Ok(_) = update
-                .removed_repositories
-                .binary_search(&entry.work_directory.to_proto())
-            {
-                false
-            } else {
-                true
-            }
-        });
-
-        for repository in update.updated_repositories {
-            let work_directory_entry: WorkDirectoryEntry =
-                ProjectEntryId::from_proto(repository.work_directory_id).into();
-
-            if let Some(entry) = self.entry_for_id(*work_directory_entry) {
-                let work_directory = RepositoryWorkDirectory(entry.path.clone());
-                if self.repository_entries.get(&work_directory).is_some() {
-                    self.repository_entries.update(&work_directory, |repo| {
-                        repo.branch = repository.branch.map(Into::into);
-                    });
-                } else {
-                    self.repository_entries.insert(
-                        work_directory,
-                        RepositoryEntry {
-                            work_directory: work_directory_entry,
-                            branch: repository.branch.map(Into::into),
-                        },
-                    )
-                }
-            } else {
-                log::error!("no work directory entry for repository {:?}", repository)
-            }
-        }
-
-        self.scan_id = update.scan_id as usize;
-        if update.is_last_update {
-            self.completed_scan_id = update.scan_id as usize;
-        }
-
-        Ok(())
-    }
-
-    pub fn file_count(&self) -> usize {
-        self.entries_by_path.summary().file_count
-    }
-
-    pub fn visible_file_count(&self) -> usize {
-        self.entries_by_path.summary().non_ignored_file_count
-    }
-
-    fn traverse_from_offset(
-        &self,
-        include_dirs: bool,
-        include_ignored: bool,
-        start_offset: usize,
-    ) -> Traversal {
-        let mut cursor = self.entries_by_path.cursor();
-        cursor.seek(
-            &TraversalTarget::Count {
-                count: start_offset,
-                include_dirs,
-                include_ignored,
-            },
-            Bias::Right,
-            &(),
-        );
-        Traversal {
-            cursor,
-            include_dirs,
-            include_ignored,
-        }
-    }
-
-    fn traverse_from_path(
-        &self,
-        include_dirs: bool,
-        include_ignored: bool,
-        path: &Path,
-    ) -> Traversal {
-        let mut cursor = self.entries_by_path.cursor();
-        cursor.seek(&TraversalTarget::Path(path), Bias::Left, &());
-        Traversal {
-            cursor,
-            include_dirs,
-            include_ignored,
-        }
-    }
-
-    pub fn files(&self, include_ignored: bool, start: usize) -> Traversal {
-        self.traverse_from_offset(false, include_ignored, start)
-    }
-
-    pub fn entries(&self, include_ignored: bool) -> Traversal {
-        self.traverse_from_offset(true, include_ignored, 0)
-    }
-
-    pub fn repositories(&self) -> impl Iterator<Item = (&Arc<Path>, &RepositoryEntry)> {
-        self.repository_entries
-            .iter()
-            .map(|(path, entry)| (&path.0, entry))
-    }
-
-    /// Get the repository whose work directory contains the given path.
-    pub fn repository_for_work_directory(&self, path: &Path) -> Option<RepositoryEntry> {
-        self.repository_entries
-            .get(&RepositoryWorkDirectory(path.into()))
-            .cloned()
-    }
-
-    /// Get the repository whose work directory contains the given path.
-    pub fn repository_for_path(&self, path: &Path) -> Option<RepositoryEntry> {
-        self.repository_and_work_directory_for_path(path)
-            .map(|e| e.1)
-    }
-
-    pub fn repository_and_work_directory_for_path(
-        &self,
-        path: &Path,
-    ) -> Option<(RepositoryWorkDirectory, RepositoryEntry)> {
-        self.repository_entries
-            .iter()
-            .filter(|(workdir_path, _)| path.starts_with(workdir_path))
-            .last()
-            .map(|(path, repo)| (path.clone(), repo.clone()))
-    }
-
-    /// Given an ordered iterator of entries, returns an iterator of those entries,
-    /// along with their containing git repository.
-    pub fn entries_with_repositories<'a>(
-        &'a self,
-        entries: impl 'a + Iterator<Item = &'a Entry>,
-    ) -> impl 'a + Iterator<Item = (&'a Entry, Option<&'a RepositoryEntry>)> {
-        let mut containing_repos = Vec::<(&Arc<Path>, &RepositoryEntry)>::new();
-        let mut repositories = self.repositories().peekable();
-        entries.map(move |entry| {
-            while let Some((repo_path, _)) = containing_repos.last() {
-                if !entry.path.starts_with(repo_path) {
-                    containing_repos.pop();
-                } else {
-                    break;
-                }
-            }
-            while let Some((repo_path, _)) = repositories.peek() {
-                if entry.path.starts_with(repo_path) {
-                    containing_repos.push(repositories.next().unwrap());
-                } else {
-                    break;
-                }
-            }
-            let repo = containing_repos.last().map(|(_, repo)| *repo);
-            (entry, repo)
-        })
-    }
-
-    /// Update the `git_status` of the given entries such that files'
-    /// statuses bubble up to their ancestor directories.
-    pub fn propagate_git_statuses(&self, result: &mut [Entry]) {
-        let mut cursor = self
-            .entries_by_path
-            .cursor::<(TraversalProgress, GitStatuses)>();
-        let mut entry_stack = Vec::<(usize, GitStatuses)>::new();
-
-        let mut result_ix = 0;
-        loop {
-            let next_entry = result.get(result_ix);
-            let containing_entry = entry_stack.last().map(|(ix, _)| &result[*ix]);
-
-            let entry_to_finish = match (containing_entry, next_entry) {
-                (Some(_), None) => entry_stack.pop(),
-                (Some(containing_entry), Some(next_path)) => {
-                    if !next_path.path.starts_with(&containing_entry.path) {
-                        entry_stack.pop()
-                    } else {
-                        None
-                    }
-                }
-                (None, Some(_)) => None,
-                (None, None) => break,
-            };
-
-            if let Some((entry_ix, prev_statuses)) = entry_to_finish {
-                cursor.seek_forward(
-                    &TraversalTarget::PathSuccessor(&result[entry_ix].path),
-                    Bias::Left,
-                    &(),
-                );
-
-                let statuses = cursor.start().1 - prev_statuses;
-
-                result[entry_ix].git_status = if statuses.conflict > 0 {
-                    Some(GitFileStatus::Conflict)
-                } else if statuses.modified > 0 {
-                    Some(GitFileStatus::Modified)
-                } else if statuses.added > 0 {
-                    Some(GitFileStatus::Added)
-                } else {
-                    None
-                };
-            } else {
-                if result[result_ix].is_dir() {
-                    cursor.seek_forward(
-                        &TraversalTarget::Path(&result[result_ix].path),
-                        Bias::Left,
-                        &(),
-                    );
-                    entry_stack.push((result_ix, cursor.start().1));
-                }
-                result_ix += 1;
-            }
-        }
-    }
-
-    pub fn paths(&self) -> impl Iterator<Item = &Arc<Path>> {
-        let empty_path = Path::new("");
-        self.entries_by_path
-            .cursor::<()>()
-            .filter(move |entry| entry.path.as_ref() != empty_path)
-            .map(|entry| &entry.path)
-    }
-
-    fn child_entries<'a>(&'a self, parent_path: &'a Path) -> ChildEntriesIter<'a> {
-        let mut cursor = self.entries_by_path.cursor();
-        cursor.seek(&TraversalTarget::Path(parent_path), Bias::Right, &());
-        let traversal = Traversal {
-            cursor,
-            include_dirs: true,
-            include_ignored: true,
-        };
-        ChildEntriesIter {
-            traversal,
-            parent_path,
-        }
-    }
-
-    pub fn descendent_entries<'a>(
-        &'a self,
-        include_dirs: bool,
-        include_ignored: bool,
-        parent_path: &'a Path,
-    ) -> DescendentEntriesIter<'a> {
-        let mut cursor = self.entries_by_path.cursor();
-        cursor.seek(&TraversalTarget::Path(parent_path), Bias::Left, &());
-        let mut traversal = Traversal {
-            cursor,
-            include_dirs,
-            include_ignored,
-        };
-
-        if traversal.end_offset() == traversal.start_offset() {
-            traversal.advance();
-        }
-
-        DescendentEntriesIter {
-            traversal,
-            parent_path,
-        }
-    }
-
-    pub fn root_entry(&self) -> Option<&Entry> {
-        self.entry_for_path("")
-    }
-
-    pub fn root_name(&self) -> &str {
-        &self.root_name
-    }
-
-    pub fn root_git_entry(&self) -> Option<RepositoryEntry> {
-        self.repository_entries
-            .get(&RepositoryWorkDirectory(Path::new("").into()))
-            .map(|entry| entry.to_owned())
-    }
-
-    pub fn git_entries(&self) -> impl Iterator<Item = &RepositoryEntry> {
-        self.repository_entries.values()
-    }
-
-    pub fn scan_id(&self) -> usize {
-        self.scan_id
-    }
-
-    pub fn entry_for_path(&self, path: impl AsRef<Path>) -> Option<&Entry> {
-        let path = path.as_ref();
-        self.traverse_from_path(true, true, path)
-            .entry()
-            .and_then(|entry| {
-                if entry.path.as_ref() == path {
-                    Some(entry)
-                } else {
-                    None
-                }
-            })
-    }
-
-    pub fn entry_for_id(&self, id: ProjectEntryId) -> Option<&Entry> {
-        let entry = self.entries_by_id.get(&id, &())?;
-        self.entry_for_path(&entry.path)
-    }
-
-    pub fn inode_for_path(&self, path: impl AsRef<Path>) -> Option<u64> {
-        self.entry_for_path(path.as_ref()).map(|e| e.inode)
-    }
-}
-
-impl LocalSnapshot {
-    pub(crate) fn get_local_repo(&self, repo: &RepositoryEntry) -> Option<&LocalRepositoryEntry> {
-        self.git_repositories.get(&repo.work_directory.0)
-    }
-
-    pub(crate) fn local_repo_for_path(
-        &self,
-        path: &Path,
-    ) -> Option<(RepositoryWorkDirectory, &LocalRepositoryEntry)> {
-        let (path, repo) = self.repository_and_work_directory_for_path(path)?;
-        Some((path, self.git_repositories.get(&repo.work_directory_id())?))
-    }
-
-    fn build_update(
-        &self,
-        project_id: u64,
-        worktree_id: u64,
-        entry_changes: UpdatedEntriesSet,
-        repo_changes: UpdatedGitRepositoriesSet,
-    ) -> proto::UpdateWorktree {
-        let mut updated_entries = Vec::new();
-        let mut removed_entries = Vec::new();
-        let mut updated_repositories = Vec::new();
-        let mut removed_repositories = Vec::new();
-
-        for (_, entry_id, path_change) in entry_changes.iter() {
-            if let PathChange::Removed = path_change {
-                removed_entries.push(entry_id.0 as u64);
-            } else if let Some(entry) = self.entry_for_id(*entry_id) {
-                updated_entries.push(proto::Entry::from(entry));
-            }
-        }
-
-        for (work_dir_path, change) in repo_changes.iter() {
-            let new_repo = self
-                .repository_entries
-                .get(&RepositoryWorkDirectory(work_dir_path.clone()));
-            match (&change.old_repository, new_repo) {
-                (Some(old_repo), Some(new_repo)) => {
-                    updated_repositories.push(new_repo.build_update(old_repo));
-                }
-                (None, Some(new_repo)) => {
-                    updated_repositories.push(proto::RepositoryEntry::from(new_repo));
-                }
-                (Some(old_repo), None) => {
-                    removed_repositories.push(old_repo.work_directory.0.to_proto());
-                }
-                _ => {}
-            }
-        }
-
-        removed_entries.sort_unstable();
-        updated_entries.sort_unstable_by_key(|e| e.id);
-        removed_repositories.sort_unstable();
-        updated_repositories.sort_unstable_by_key(|e| e.work_directory_id);
-
-        // TODO - optimize, knowing that removed_entries are sorted.
-        removed_entries.retain(|id| updated_entries.binary_search_by_key(id, |e| e.id).is_err());
-
-        proto::UpdateWorktree {
-            project_id,
-            worktree_id,
-            abs_path: self.abs_path().to_string_lossy().into(),
-            root_name: self.root_name().to_string(),
-            updated_entries,
-            removed_entries,
-            scan_id: self.scan_id as u64,
-            is_last_update: self.completed_scan_id == self.scan_id,
-            updated_repositories,
-            removed_repositories,
-        }
-    }
-
-    fn build_initial_update(&self, project_id: u64, worktree_id: u64) -> proto::UpdateWorktree {
-        let mut updated_entries = self
-            .entries_by_path
-            .iter()
-            .map(proto::Entry::from)
-            .collect::<Vec<_>>();
-        updated_entries.sort_unstable_by_key(|e| e.id);
-
-        let mut updated_repositories = self
-            .repository_entries
-            .values()
-            .map(proto::RepositoryEntry::from)
-            .collect::<Vec<_>>();
-        updated_repositories.sort_unstable_by_key(|e| e.work_directory_id);
-
-        proto::UpdateWorktree {
-            project_id,
-            worktree_id,
-            abs_path: self.abs_path().to_string_lossy().into(),
-            root_name: self.root_name().to_string(),
-            updated_entries,
-            removed_entries: Vec::new(),
-            scan_id: self.scan_id as u64,
-            is_last_update: self.completed_scan_id == self.scan_id,
-            updated_repositories,
-            removed_repositories: Vec::new(),
-        }
-    }
-
-    fn insert_entry(&mut self, mut entry: Entry, fs: &dyn Fs) -> Entry {
-        if entry.is_file() && entry.path.file_name() == Some(&GITIGNORE) {
-            let abs_path = self.abs_path.join(&entry.path);
-            match smol::block_on(build_gitignore(&abs_path, fs)) {
-                Ok(ignore) => {
-                    self.ignores_by_parent_abs_path
-                        .insert(abs_path.parent().unwrap().into(), (Arc::new(ignore), true));
-                }
-                Err(error) => {
-                    log::error!(
-                        "error loading .gitignore file {:?} - {:?}",
-                        &entry.path,
-                        error
-                    );
-                }
-            }
-        }
-
-        if entry.kind == EntryKind::PendingDir {
-            if let Some(existing_entry) =
-                self.entries_by_path.get(&PathKey(entry.path.clone()), &())
-            {
-                entry.kind = existing_entry.kind;
-            }
-        }
-
-        let scan_id = self.scan_id;
-        let removed = self.entries_by_path.insert_or_replace(entry.clone(), &());
-        if let Some(removed) = removed {
-            if removed.id != entry.id {
-                self.entries_by_id.remove(&removed.id, &());
-            }
-        }
-        self.entries_by_id.insert_or_replace(
-            PathEntry {
-                id: entry.id,
-                path: entry.path.clone(),
-                is_ignored: entry.is_ignored,
-                scan_id,
-            },
-            &(),
-        );
-
-        entry
-    }
-
-    fn ancestor_inodes_for_path(&self, path: &Path) -> TreeSet<u64> {
-        let mut inodes = TreeSet::default();
-        for ancestor in path.ancestors().skip(1) {
-            if let Some(entry) = self.entry_for_path(ancestor) {
-                inodes.insert(entry.inode);
-            }
-        }
-        inodes
-    }
-
-    fn ignore_stack_for_abs_path(&self, abs_path: &Path, is_dir: bool) -> Arc<IgnoreStack> {
-        let mut new_ignores = Vec::new();
-        for ancestor in abs_path.ancestors().skip(1) {
-            if let Some((ignore, _)) = self.ignores_by_parent_abs_path.get(ancestor) {
-                new_ignores.push((ancestor, Some(ignore.clone())));
-            } else {
-                new_ignores.push((ancestor, None));
-            }
-        }
-
-        let mut ignore_stack = IgnoreStack::none();
-        for (parent_abs_path, ignore) in new_ignores.into_iter().rev() {
-            if ignore_stack.is_abs_path_ignored(parent_abs_path, true) {
-                ignore_stack = IgnoreStack::all();
-                break;
-            } else if let Some(ignore) = ignore {
-                ignore_stack = ignore_stack.append(parent_abs_path.into(), ignore);
-            }
-        }
-
-        if ignore_stack.is_abs_path_ignored(abs_path, is_dir) {
-            ignore_stack = IgnoreStack::all();
-        }
-
-        ignore_stack
-    }
-
-    #[cfg(test)]
-    pub(crate) fn expanded_entries(&self) -> impl Iterator<Item = &Entry> {
-        self.entries_by_path
-            .cursor::<()>()
-            .filter(|entry| entry.kind == EntryKind::Dir && (entry.is_external || entry.is_ignored))
-    }
-
-    #[cfg(test)]
-    pub fn check_invariants(&self, git_state: bool) {
-        use pretty_assertions::assert_eq;
-
-        assert_eq!(
-            self.entries_by_path
-                .cursor::<()>()
-                .map(|e| (&e.path, e.id))
-                .collect::<Vec<_>>(),
-            self.entries_by_id
-                .cursor::<()>()
-                .map(|e| (&e.path, e.id))
-                .collect::<collections::BTreeSet<_>>()
-                .into_iter()
-                .collect::<Vec<_>>(),
-            "entries_by_path and entries_by_id are inconsistent"
-        );
-
-        let mut files = self.files(true, 0);
-        let mut visible_files = self.files(false, 0);
-        for entry in self.entries_by_path.cursor::<()>() {
-            if entry.is_file() {
-                assert_eq!(files.next().unwrap().inode, entry.inode);
-                if !entry.is_ignored && !entry.is_external {
-                    assert_eq!(visible_files.next().unwrap().inode, entry.inode);
-                }
-            }
-        }
-
-        assert!(files.next().is_none());
-        assert!(visible_files.next().is_none());
-
-        let mut bfs_paths = Vec::new();
-        let mut stack = self
-            .root_entry()
-            .map(|e| e.path.as_ref())
-            .into_iter()
-            .collect::<Vec<_>>();
-        while let Some(path) = stack.pop() {
-            bfs_paths.push(path);
-            let ix = stack.len();
-            for child_entry in self.child_entries(path) {
-                stack.insert(ix, &child_entry.path);
-            }
-        }
-
-        let dfs_paths_via_iter = self
-            .entries_by_path
-            .cursor::<()>()
-            .map(|e| e.path.as_ref())
-            .collect::<Vec<_>>();
-        assert_eq!(bfs_paths, dfs_paths_via_iter);
-
-        let dfs_paths_via_traversal = self
-            .entries(true)
-            .map(|e| e.path.as_ref())
-            .collect::<Vec<_>>();
-        assert_eq!(dfs_paths_via_traversal, dfs_paths_via_iter);
-
-        if git_state {
-            for ignore_parent_abs_path in self.ignores_by_parent_abs_path.keys() {
-                let ignore_parent_path =
-                    ignore_parent_abs_path.strip_prefix(&self.abs_path).unwrap();
-                assert!(self.entry_for_path(&ignore_parent_path).is_some());
-                assert!(self
-                    .entry_for_path(ignore_parent_path.join(&*GITIGNORE))
-                    .is_some());
-            }
-        }
-    }
-
-    #[cfg(test)]
-    pub fn entries_without_ids(&self, include_ignored: bool) -> Vec<(&Path, u64, bool)> {
-        let mut paths = Vec::new();
-        for entry in self.entries_by_path.cursor::<()>() {
-            if include_ignored || !entry.is_ignored {
-                paths.push((entry.path.as_ref(), entry.inode, entry.is_ignored));
-            }
-        }
-        paths.sort_by(|a, b| a.0.cmp(b.0));
-        paths
-    }
-
-    pub fn is_path_excluded(&self, mut path: PathBuf) -> bool {
-        loop {
-            if self
-                .file_scan_exclusions
-                .iter()
-                .any(|exclude_matcher| exclude_matcher.is_match(&path))
-            {
-                return true;
-            }
-            if !path.pop() {
-                return false;
-            }
-        }
-    }
-}
-
-impl BackgroundScannerState {
-    fn should_scan_directory(&self, entry: &Entry) -> bool {
-        (!entry.is_external && !entry.is_ignored)
-            || entry.path.file_name() == Some(&*DOT_GIT)
-            || self.scanned_dirs.contains(&entry.id) // If we've ever scanned it, keep scanning
-            || self
-                .paths_to_scan
-                .iter()
-                .any(|p| p.starts_with(&entry.path))
-            || self
-                .path_prefixes_to_scan
-                .iter()
-                .any(|p| entry.path.starts_with(p))
-    }
-
-    fn enqueue_scan_dir(&self, abs_path: Arc<Path>, entry: &Entry, scan_job_tx: &Sender<ScanJob>) {
-        let path = entry.path.clone();
-        let ignore_stack = self.snapshot.ignore_stack_for_abs_path(&abs_path, true);
-        let mut ancestor_inodes = self.snapshot.ancestor_inodes_for_path(&path);
-        let mut containing_repository = None;
-        if !ignore_stack.is_abs_path_ignored(&abs_path, true) {
-            if let Some((workdir_path, repo)) = self.snapshot.local_repo_for_path(&path) {
-                if let Ok(repo_path) = path.strip_prefix(&workdir_path.0) {
-                    containing_repository = Some((
-                        workdir_path,
-                        repo.repo_ptr.clone(),
-                        repo.repo_ptr.lock().staged_statuses(repo_path),
-                    ));
-                }
-            }
-        }
-        if !ancestor_inodes.contains(&entry.inode) {
-            ancestor_inodes.insert(entry.inode);
-            scan_job_tx
-                .try_send(ScanJob {
-                    abs_path,
-                    path,
-                    ignore_stack,
-                    scan_queue: scan_job_tx.clone(),
-                    ancestor_inodes,
-                    is_external: entry.is_external,
-                    containing_repository,
-                })
-                .unwrap();
-        }
-    }
-
-    fn reuse_entry_id(&mut self, entry: &mut Entry) {
-        if let Some(removed_entry_id) = self.removed_entry_ids.remove(&entry.inode) {
-            entry.id = removed_entry_id;
-        } else if let Some(existing_entry) = self.snapshot.entry_for_path(&entry.path) {
-            entry.id = existing_entry.id;
-        }
-    }
-
-    fn insert_entry(&mut self, mut entry: Entry, fs: &dyn Fs) -> Entry {
-        self.reuse_entry_id(&mut entry);
-        let entry = self.snapshot.insert_entry(entry, fs);
-        if entry.path.file_name() == Some(&DOT_GIT) {
-            self.build_git_repository(entry.path.clone(), fs);
-        }
-
-        #[cfg(test)]
-        self.snapshot.check_invariants(false);
-
-        entry
-    }
-
-    fn populate_dir(
-        &mut self,
-        parent_path: &Arc<Path>,
-        entries: impl IntoIterator<Item = Entry>,
-        ignore: Option<Arc<Gitignore>>,
-    ) {
-        let mut parent_entry = if let Some(parent_entry) = self
-            .snapshot
-            .entries_by_path
-            .get(&PathKey(parent_path.clone()), &())
-        {
-            parent_entry.clone()
-        } else {
-            log::warn!(
-                "populating a directory {:?} that has been removed",
-                parent_path
-            );
-            return;
-        };
-
-        match parent_entry.kind {
-            EntryKind::PendingDir | EntryKind::UnloadedDir => parent_entry.kind = EntryKind::Dir,
-            EntryKind::Dir => {}
-            _ => return,
-        }
-
-        if let Some(ignore) = ignore {
-            let abs_parent_path = self.snapshot.abs_path.join(&parent_path).into();
-            self.snapshot
-                .ignores_by_parent_abs_path
-                .insert(abs_parent_path, (ignore, false));
-        }
-
-        let parent_entry_id = parent_entry.id;
-        self.scanned_dirs.insert(parent_entry_id);
-        let mut entries_by_path_edits = vec![Edit::Insert(parent_entry)];
-        let mut entries_by_id_edits = Vec::new();
-
-        for entry in entries {
-            entries_by_id_edits.push(Edit::Insert(PathEntry {
-                id: entry.id,
-                path: entry.path.clone(),
-                is_ignored: entry.is_ignored,
-                scan_id: self.snapshot.scan_id,
-            }));
-            entries_by_path_edits.push(Edit::Insert(entry));
-        }
-
-        self.snapshot
-            .entries_by_path
-            .edit(entries_by_path_edits, &());
-        self.snapshot.entries_by_id.edit(entries_by_id_edits, &());
-
-        if let Err(ix) = self.changed_paths.binary_search(parent_path) {
-            self.changed_paths.insert(ix, parent_path.clone());
-        }
-
-        #[cfg(test)]
-        self.snapshot.check_invariants(false);
-    }
-
-    fn remove_path(&mut self, path: &Path) {
-        let mut new_entries;
-        let removed_entries;
-        {
-            let mut cursor = self.snapshot.entries_by_path.cursor::<TraversalProgress>();
-            new_entries = cursor.slice(&TraversalTarget::Path(path), Bias::Left, &());
-            removed_entries = cursor.slice(&TraversalTarget::PathSuccessor(path), Bias::Left, &());
-            new_entries.append(cursor.suffix(&()), &());
-        }
-        self.snapshot.entries_by_path = new_entries;
-
-        let mut entries_by_id_edits = Vec::new();
-        for entry in removed_entries.cursor::<()>() {
-            let removed_entry_id = self
-                .removed_entry_ids
-                .entry(entry.inode)
-                .or_insert(entry.id);
-            *removed_entry_id = cmp::max(*removed_entry_id, entry.id);
-            entries_by_id_edits.push(Edit::Remove(entry.id));
-        }
-        self.snapshot.entries_by_id.edit(entries_by_id_edits, &());
-
-        if path.file_name() == Some(&GITIGNORE) {
-            let abs_parent_path = self.snapshot.abs_path.join(path.parent().unwrap());
-            if let Some((_, needs_update)) = self
-                .snapshot
-                .ignores_by_parent_abs_path
-                .get_mut(abs_parent_path.as_path())
-            {
-                *needs_update = true;
-            }
-        }
-
-        #[cfg(test)]
-        self.snapshot.check_invariants(false);
-    }
-
-    fn reload_repositories(&mut self, dot_git_dirs_to_reload: &HashSet<PathBuf>, fs: &dyn Fs) {
-        let scan_id = self.snapshot.scan_id;
-
-        for dot_git_dir in dot_git_dirs_to_reload {
-            // If there is already a repository for this .git directory, reload
-            // the status for all of its files.
-            let repository = self
-                .snapshot
-                .git_repositories
-                .iter()
-                .find_map(|(entry_id, repo)| {
-                    (repo.git_dir_path.as_ref() == dot_git_dir).then(|| (*entry_id, repo.clone()))
-                });
-            match repository {
-                None => {
-                    self.build_git_repository(Arc::from(dot_git_dir.as_path()), fs);
-                }
-                Some((entry_id, repository)) => {
-                    if repository.git_dir_scan_id == scan_id {
-                        continue;
-                    }
-                    let Some(work_dir) = self
-                        .snapshot
-                        .entry_for_id(entry_id)
-                        .map(|entry| RepositoryWorkDirectory(entry.path.clone()))
-                    else {
-                        continue;
-                    };
-
-                    log::info!("reload git repository {dot_git_dir:?}");
-                    let repository = repository.repo_ptr.lock();
-                    let branch = repository.branch_name();
-                    repository.reload_index();
-
-                    self.snapshot
-                        .git_repositories
-                        .update(&entry_id, |entry| entry.git_dir_scan_id = scan_id);
-                    self.snapshot
-                        .snapshot
-                        .repository_entries
-                        .update(&work_dir, |entry| entry.branch = branch.map(Into::into));
-
-                    self.update_git_statuses(&work_dir, &*repository);
-                }
-            }
-        }
-
-        // Remove any git repositories whose .git entry no longer exists.
-        let snapshot = &mut self.snapshot;
-        let mut ids_to_preserve = HashSet::default();
-        for (&work_directory_id, entry) in snapshot.git_repositories.iter() {
-            let exists_in_snapshot = snapshot
-                .entry_for_id(work_directory_id)
-                .map_or(false, |entry| {
-                    snapshot.entry_for_path(entry.path.join(*DOT_GIT)).is_some()
-                });
-            if exists_in_snapshot {
-                ids_to_preserve.insert(work_directory_id);
-            } else {
-                let git_dir_abs_path = snapshot.abs_path().join(&entry.git_dir_path);
-                let git_dir_excluded = snapshot.is_path_excluded(entry.git_dir_path.to_path_buf());
-                if git_dir_excluded
-                    && !matches!(smol::block_on(fs.metadata(&git_dir_abs_path)), Ok(None))
-                {
-                    ids_to_preserve.insert(work_directory_id);
-                }
-            }
-        }
-        snapshot
-            .git_repositories
-            .retain(|work_directory_id, _| ids_to_preserve.contains(work_directory_id));
-        snapshot
-            .repository_entries
-            .retain(|_, entry| ids_to_preserve.contains(&entry.work_directory.0));
-    }
-
-    fn build_git_repository(
-        &mut self,
-        dot_git_path: Arc<Path>,
-        fs: &dyn Fs,
-    ) -> Option<(
-        RepositoryWorkDirectory,
-        Arc<Mutex<dyn GitRepository>>,
-        TreeMap<RepoPath, GitFileStatus>,
-    )> {
-        log::info!("build git repository {:?}", dot_git_path);
-
-        let work_dir_path: Arc<Path> = dot_git_path.parent().unwrap().into();
-
-        // Guard against repositories inside the repository metadata
-        if work_dir_path.iter().any(|component| component == *DOT_GIT) {
-            return None;
-        };
-
-        let work_dir_id = self
-            .snapshot
-            .entry_for_path(work_dir_path.clone())
-            .map(|entry| entry.id)?;
-
-        if self.snapshot.git_repositories.get(&work_dir_id).is_some() {
-            return None;
-        }
-
-        let abs_path = self.snapshot.abs_path.join(&dot_git_path);
-        let repository = fs.open_repo(abs_path.as_path())?;
-        let work_directory = RepositoryWorkDirectory(work_dir_path.clone());
-
-        let repo_lock = repository.lock();
-        self.snapshot.repository_entries.insert(
-            work_directory.clone(),
-            RepositoryEntry {
-                work_directory: work_dir_id.into(),
-                branch: repo_lock.branch_name().map(Into::into),
-            },
-        );
-
-        let staged_statuses = self.update_git_statuses(&work_directory, &*repo_lock);
-        drop(repo_lock);
-
-        self.snapshot.git_repositories.insert(
-            work_dir_id,
-            LocalRepositoryEntry {
-                git_dir_scan_id: 0,
-                repo_ptr: repository.clone(),
-                git_dir_path: dot_git_path.clone(),
-            },
-        );
-
-        Some((work_directory, repository, staged_statuses))
-    }
-
-    fn update_git_statuses(
-        &mut self,
-        work_directory: &RepositoryWorkDirectory,
-        repo: &dyn GitRepository,
-    ) -> TreeMap<RepoPath, GitFileStatus> {
-        let staged_statuses = repo.staged_statuses(Path::new(""));
-
-        let mut changes = vec![];
-        let mut edits = vec![];
-
-        for mut entry in self
-            .snapshot
-            .descendent_entries(false, false, &work_directory.0)
-            .cloned()
-        {
-            let Ok(repo_path) = entry.path.strip_prefix(&work_directory.0) else {
-                continue;
-            };
-            let repo_path = RepoPath(repo_path.to_path_buf());
-            let git_file_status = combine_git_statuses(
-                staged_statuses.get(&repo_path).copied(),
-                repo.unstaged_status(&repo_path, entry.mtime),
-            );
-            if entry.git_status != git_file_status {
-                entry.git_status = git_file_status;
-                changes.push(entry.path.clone());
-                edits.push(Edit::Insert(entry));
-            }
-        }
-
-        self.snapshot.entries_by_path.edit(edits, &());
-        util::extend_sorted(&mut self.changed_paths, changes, usize::MAX, Ord::cmp);
-        staged_statuses
-    }
-}
-
-async fn build_gitignore(abs_path: &Path, fs: &dyn Fs) -> Result<Gitignore> {
-    let contents = fs.load(abs_path).await?;
-    let parent = abs_path.parent().unwrap_or_else(|| Path::new("/"));
-    let mut builder = GitignoreBuilder::new(parent);
-    for line in contents.lines() {
-        builder.add_line(Some(abs_path.into()), line)?;
-    }
-    Ok(builder.build()?)
-}
-
-impl WorktreeId {
-    pub fn from_usize(handle_id: usize) -> Self {
-        Self(handle_id)
-    }
-
-    pub(crate) fn from_proto(id: u64) -> Self {
-        Self(id as usize)
-    }
-
-    pub fn to_proto(&self) -> u64 {
-        self.0 as u64
-    }
-
-    pub fn to_usize(&self) -> usize {
-        self.0
-    }
-}
-
-impl fmt::Display for WorktreeId {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        self.0.fmt(f)
-    }
-}
-
-impl Deref for Worktree {
-    type Target = Snapshot;
-
-    fn deref(&self) -> &Self::Target {
-        match self {
-            Worktree::Local(worktree) => &worktree.snapshot,
-            Worktree::Remote(worktree) => &worktree.snapshot,
-        }
-    }
-}
-
-impl Deref for LocalWorktree {
-    type Target = LocalSnapshot;
-
-    fn deref(&self) -> &Self::Target {
-        &self.snapshot
-    }
-}
-
-impl Deref for RemoteWorktree {
-    type Target = Snapshot;
-
-    fn deref(&self) -> &Self::Target {
-        &self.snapshot
-    }
-}
-
-impl fmt::Debug for LocalWorktree {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        self.snapshot.fmt(f)
-    }
-}
-
-impl fmt::Debug for Snapshot {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        struct EntriesById<'a>(&'a SumTree<PathEntry>);
-        struct EntriesByPath<'a>(&'a SumTree<Entry>);
-
-        impl<'a> fmt::Debug for EntriesByPath<'a> {
-            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-                f.debug_map()
-                    .entries(self.0.iter().map(|entry| (&entry.path, entry.id)))
-                    .finish()
-            }
-        }
-
-        impl<'a> fmt::Debug for EntriesById<'a> {
-            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-                f.debug_list().entries(self.0.iter()).finish()
-            }
-        }
-
-        f.debug_struct("Snapshot")
-            .field("id", &self.id)
-            .field("root_name", &self.root_name)
-            .field("entries_by_path", &EntriesByPath(&self.entries_by_path))
-            .field("entries_by_id", &EntriesById(&self.entries_by_id))
-            .finish()
-    }
-}
-
-#[derive(Clone, PartialEq)]
-pub struct File {
-    pub worktree: Model<Worktree>,
-    pub path: Arc<Path>,
-    pub mtime: SystemTime,
-    pub(crate) entry_id: Option<ProjectEntryId>,
-    pub(crate) is_local: bool,
-    pub(crate) is_deleted: bool,
-}
-
-impl language::File for File {
-    fn as_local(&self) -> Option<&dyn language::LocalFile> {
-        if self.is_local {
-            Some(self)
-        } else {
-            None
-        }
-    }
-
-    fn mtime(&self) -> SystemTime {
-        self.mtime
-    }
-
-    fn path(&self) -> &Arc<Path> {
-        &self.path
-    }
-
-    fn full_path(&self, cx: &AppContext) -> PathBuf {
-        let mut full_path = PathBuf::new();
-        let worktree = self.worktree.read(cx);
-
-        if worktree.is_visible() {
-            full_path.push(worktree.root_name());
-        } else {
-            let path = worktree.abs_path();
-
-            if worktree.is_local() && path.starts_with(HOME.as_path()) {
-                full_path.push("~");
-                full_path.push(path.strip_prefix(HOME.as_path()).unwrap());
-            } else {
-                full_path.push(path)
-            }
-        }
-
-        if self.path.components().next().is_some() {
-            full_path.push(&self.path);
-        }
-
-        full_path
-    }
-
-    /// Returns the last component of this handle's absolute path. If this handle refers to the root
-    /// of its worktree, then this method will return the name of the worktree itself.
-    fn file_name<'a>(&'a self, cx: &'a AppContext) -> &'a OsStr {
-        self.path
-            .file_name()
-            .unwrap_or_else(|| OsStr::new(&self.worktree.read(cx).root_name))
-    }
-
-    fn worktree_id(&self) -> usize {
-        self.worktree.entity_id().as_u64() as usize
-    }
-
-    fn is_deleted(&self) -> bool {
-        self.is_deleted
-    }
-
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
-    fn to_proto(&self) -> rpc::proto::File {
-        rpc::proto::File {
-            worktree_id: self.worktree.entity_id().as_u64(),
-            entry_id: self.entry_id.map(|id| id.to_proto()),
-            path: self.path.to_string_lossy().into(),
-            mtime: Some(self.mtime.into()),
-            is_deleted: self.is_deleted,
-        }
-    }
-}
-
-impl language::LocalFile for File {
-    fn abs_path(&self, cx: &AppContext) -> PathBuf {
-        let worktree_path = &self.worktree.read(cx).as_local().unwrap().abs_path;
-        if self.path.as_ref() == Path::new("") {
-            worktree_path.to_path_buf()
-        } else {
-            worktree_path.join(&self.path)
-        }
-    }
-
-    fn load(&self, cx: &AppContext) -> Task<Result<String>> {
-        let worktree = self.worktree.read(cx).as_local().unwrap();
-        let abs_path = worktree.absolutize(&self.path);
-        let fs = worktree.fs.clone();
-        cx.background_executor()
-            .spawn(async move { fs.load(&abs_path).await })
-    }
-
-    fn buffer_reloaded(
-        &self,
-        buffer_id: u64,
-        version: &clock::Global,
-        fingerprint: RopeFingerprint,
-        line_ending: LineEnding,
-        mtime: SystemTime,
-        cx: &mut AppContext,
-    ) {
-        let worktree = self.worktree.read(cx).as_local().unwrap();
-        if let Some(project_id) = worktree.share.as_ref().map(|share| share.project_id) {
-            worktree
-                .client
-                .send(proto::BufferReloaded {
-                    project_id,
-                    buffer_id,
-                    version: serialize_version(version),
-                    mtime: Some(mtime.into()),
-                    fingerprint: serialize_fingerprint(fingerprint),
-                    line_ending: serialize_line_ending(line_ending) as i32,
-                })
-                .log_err();
-        }
-    }
-}
-
-impl File {
-    pub fn for_entry(entry: Entry, worktree: Model<Worktree>) -> Arc<Self> {
-        Arc::new(Self {
-            worktree,
-            path: entry.path.clone(),
-            mtime: entry.mtime,
-            entry_id: Some(entry.id),
-            is_local: true,
-            is_deleted: false,
-        })
-    }
-
-    pub fn from_proto(
-        proto: rpc::proto::File,
-        worktree: Model<Worktree>,
-        cx: &AppContext,
-    ) -> Result<Self> {
-        let worktree_id = worktree
-            .read(cx)
-            .as_remote()
-            .ok_or_else(|| anyhow!("not remote"))?
-            .id();
-
-        if worktree_id.to_proto() != proto.worktree_id {
-            return Err(anyhow!("worktree id does not match file"));
-        }
-
-        Ok(Self {
-            worktree,
-            path: Path::new(&proto.path).into(),
-            mtime: proto.mtime.ok_or_else(|| anyhow!("no timestamp"))?.into(),
-            entry_id: proto.entry_id.map(ProjectEntryId::from_proto),
-            is_local: false,
-            is_deleted: proto.is_deleted,
-        })
-    }
-
-    pub fn from_dyn(file: Option<&Arc<dyn language::File>>) -> Option<&Self> {
-        file.and_then(|f| f.as_any().downcast_ref())
-    }
-
-    pub fn worktree_id(&self, cx: &AppContext) -> WorktreeId {
-        self.worktree.read(cx).id()
-    }
-
-    pub fn project_entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
-        if self.is_deleted {
-            None
-        } else {
-            self.entry_id
-        }
-    }
-}
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct Entry {
-    pub id: ProjectEntryId,
-    pub kind: EntryKind,
-    pub path: Arc<Path>,
-    pub inode: u64,
-    pub mtime: SystemTime,
-    pub is_symlink: bool,
-
-    /// Whether this entry is ignored by Git.
-    ///
-    /// We only scan ignored entries once the directory is expanded and
-    /// exclude them from searches.
-    pub is_ignored: bool,
-
-    /// Whether this entry's canonical path is outside of the worktree.
-    /// This means the entry is only accessible from the worktree root via a
-    /// symlink.
-    ///
-    /// We only scan entries outside of the worktree once the symlinked
-    /// directory is expanded. External entries are treated like gitignored
-    /// entries in that they are not included in searches.
-    pub is_external: bool,
-    pub git_status: Option<GitFileStatus>,
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub enum EntryKind {
-    UnloadedDir,
-    PendingDir,
-    Dir,
-    File(CharBag),
-}
-
-#[derive(Clone, Copy, Debug, PartialEq)]
-pub enum PathChange {
-    /// A filesystem entry was was created.
-    Added,
-    /// A filesystem entry was removed.
-    Removed,
-    /// A filesystem entry was updated.
-    Updated,
-    /// A filesystem entry was either updated or added. We don't know
-    /// whether or not it already existed, because the path had not
-    /// been loaded before the event.
-    AddedOrUpdated,
-    /// A filesystem entry was found during the initial scan of the worktree.
-    Loaded,
-}
-
-pub struct GitRepositoryChange {
-    /// The previous state of the repository, if it already existed.
-    pub old_repository: Option<RepositoryEntry>,
-}
-
-pub type UpdatedEntriesSet = Arc<[(Arc<Path>, ProjectEntryId, PathChange)]>;
-pub type UpdatedGitRepositoriesSet = Arc<[(Arc<Path>, GitRepositoryChange)]>;
-
-impl Entry {
-    fn new(
-        path: Arc<Path>,
-        metadata: &fs::Metadata,
-        next_entry_id: &AtomicUsize,
-        root_char_bag: CharBag,
-    ) -> Self {
-        Self {
-            id: ProjectEntryId::new(next_entry_id),
-            kind: if metadata.is_dir {
-                EntryKind::PendingDir
-            } else {
-                EntryKind::File(char_bag_for_path(root_char_bag, &path))
-            },
-            path,
-            inode: metadata.inode,
-            mtime: metadata.mtime,
-            is_symlink: metadata.is_symlink,
-            is_ignored: false,
-            is_external: false,
-            git_status: None,
-        }
-    }
-
-    pub fn is_dir(&self) -> bool {
-        self.kind.is_dir()
-    }
-
-    pub fn is_file(&self) -> bool {
-        self.kind.is_file()
-    }
-
-    pub fn git_status(&self) -> Option<GitFileStatus> {
-        self.git_status
-    }
-}
-
-impl EntryKind {
-    pub fn is_dir(&self) -> bool {
-        matches!(
-            self,
-            EntryKind::Dir | EntryKind::PendingDir | EntryKind::UnloadedDir
-        )
-    }
-
-    pub fn is_unloaded(&self) -> bool {
-        matches!(self, EntryKind::UnloadedDir)
-    }
-
-    pub fn is_file(&self) -> bool {
-        matches!(self, EntryKind::File(_))
-    }
-}
-
-impl sum_tree::Item for Entry {
-    type Summary = EntrySummary;
-
-    fn summary(&self) -> Self::Summary {
-        let non_ignored_count = if self.is_ignored || self.is_external {
-            0
-        } else {
-            1
-        };
-        let file_count;
-        let non_ignored_file_count;
-        if self.is_file() {
-            file_count = 1;
-            non_ignored_file_count = non_ignored_count;
-        } else {
-            file_count = 0;
-            non_ignored_file_count = 0;
-        }
-
-        let mut statuses = GitStatuses::default();
-        match self.git_status {
-            Some(status) => match status {
-                GitFileStatus::Added => statuses.added = 1,
-                GitFileStatus::Modified => statuses.modified = 1,
-                GitFileStatus::Conflict => statuses.conflict = 1,
-            },
-            None => {}
-        }
-
-        EntrySummary {
-            max_path: self.path.clone(),
-            count: 1,
-            non_ignored_count,
-            file_count,
-            non_ignored_file_count,
-            statuses,
-        }
-    }
-}
-
-impl sum_tree::KeyedItem for Entry {
-    type Key = PathKey;
-
-    fn key(&self) -> Self::Key {
-        PathKey(self.path.clone())
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct EntrySummary {
-    max_path: Arc<Path>,
-    count: usize,
-    non_ignored_count: usize,
-    file_count: usize,
-    non_ignored_file_count: usize,
-    statuses: GitStatuses,
-}
-
-impl Default for EntrySummary {
-    fn default() -> Self {
-        Self {
-            max_path: Arc::from(Path::new("")),
-            count: 0,
-            non_ignored_count: 0,
-            file_count: 0,
-            non_ignored_file_count: 0,
-            statuses: Default::default(),
-        }
-    }
-}
-
-impl sum_tree::Summary for EntrySummary {
-    type Context = ();
-
-    fn add_summary(&mut self, rhs: &Self, _: &()) {
-        self.max_path = rhs.max_path.clone();
-        self.count += rhs.count;
-        self.non_ignored_count += rhs.non_ignored_count;
-        self.file_count += rhs.file_count;
-        self.non_ignored_file_count += rhs.non_ignored_file_count;
-        self.statuses += rhs.statuses;
-    }
-}
-
-#[derive(Clone, Debug)]
-struct PathEntry {
-    id: ProjectEntryId,
-    path: Arc<Path>,
-    is_ignored: bool,
-    scan_id: usize,
-}
-
-impl sum_tree::Item for PathEntry {
-    type Summary = PathEntrySummary;
-
-    fn summary(&self) -> Self::Summary {
-        PathEntrySummary { max_id: self.id }
-    }
-}
-
-impl sum_tree::KeyedItem for PathEntry {
-    type Key = ProjectEntryId;
-
-    fn key(&self) -> Self::Key {
-        self.id
-    }
-}
-
-#[derive(Clone, Debug, Default)]
-struct PathEntrySummary {
-    max_id: ProjectEntryId,
-}
-
-impl sum_tree::Summary for PathEntrySummary {
-    type Context = ();
-
-    fn add_summary(&mut self, summary: &Self, _: &Self::Context) {
-        self.max_id = summary.max_id;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, PathEntrySummary> for ProjectEntryId {
-    fn add_summary(&mut self, summary: &'a PathEntrySummary, _: &()) {
-        *self = summary.max_id;
-    }
-}
-
-#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
-pub struct PathKey(Arc<Path>);
-
-impl Default for PathKey {
-    fn default() -> Self {
-        Self(Path::new("").into())
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, EntrySummary> for PathKey {
-    fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) {
-        self.0 = summary.max_path.clone();
-    }
-}
-
-struct BackgroundScanner {
-    state: Mutex<BackgroundScannerState>,
-    fs: Arc<dyn Fs>,
-    status_updates_tx: UnboundedSender<ScanState>,
-    executor: BackgroundExecutor,
-    scan_requests_rx: channel::Receiver<ScanRequest>,
-    path_prefixes_to_scan_rx: channel::Receiver<Arc<Path>>,
-    next_entry_id: Arc<AtomicUsize>,
-    phase: BackgroundScannerPhase,
-}
-
-#[derive(PartialEq)]
-enum BackgroundScannerPhase {
-    InitialScan,
-    EventsReceivedDuringInitialScan,
-    Events,
-}
-
-impl BackgroundScanner {
-    fn new(
-        snapshot: LocalSnapshot,
-        next_entry_id: Arc<AtomicUsize>,
-        fs: Arc<dyn Fs>,
-        status_updates_tx: UnboundedSender<ScanState>,
-        executor: BackgroundExecutor,
-        scan_requests_rx: channel::Receiver<ScanRequest>,
-        path_prefixes_to_scan_rx: channel::Receiver<Arc<Path>>,
-    ) -> Self {
-        Self {
-            fs,
-            status_updates_tx,
-            executor,
-            scan_requests_rx,
-            path_prefixes_to_scan_rx,
-            next_entry_id,
-            state: Mutex::new(BackgroundScannerState {
-                prev_snapshot: snapshot.snapshot.clone(),
-                snapshot,
-                scanned_dirs: Default::default(),
-                path_prefixes_to_scan: Default::default(),
-                paths_to_scan: Default::default(),
-                removed_entry_ids: Default::default(),
-                changed_paths: Default::default(),
-            }),
-            phase: BackgroundScannerPhase::InitialScan,
-        }
-    }
-
-    async fn run(
-        &mut self,
-        mut fs_events_rx: Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>>,
-    ) {
-        use futures::FutureExt as _;
-
-        // Populate ignores above the root.
-        let root_abs_path = self.state.lock().snapshot.abs_path.clone();
-        for ancestor in root_abs_path.ancestors().skip(1) {
-            if let Ok(ignore) = build_gitignore(&ancestor.join(&*GITIGNORE), self.fs.as_ref()).await
-            {
-                self.state
-                    .lock()
-                    .snapshot
-                    .ignores_by_parent_abs_path
-                    .insert(ancestor.into(), (ignore.into(), false));
-            }
-        }
-
-        let (scan_job_tx, scan_job_rx) = channel::unbounded();
-        {
-            let mut state = self.state.lock();
-            state.snapshot.scan_id += 1;
-            if let Some(mut root_entry) = state.snapshot.root_entry().cloned() {
-                let ignore_stack = state
-                    .snapshot
-                    .ignore_stack_for_abs_path(&root_abs_path, true);
-                if ignore_stack.is_abs_path_ignored(&root_abs_path, true) {
-                    root_entry.is_ignored = true;
-                    state.insert_entry(root_entry.clone(), self.fs.as_ref());
-                }
-                state.enqueue_scan_dir(root_abs_path, &root_entry, &scan_job_tx);
-            }
-        };
-
-        // Perform an initial scan of the directory.
-        drop(scan_job_tx);
-        self.scan_dirs(true, scan_job_rx).await;
-        {
-            let mut state = self.state.lock();
-            state.snapshot.completed_scan_id = state.snapshot.scan_id;
-        }
-
-        self.send_status_update(false, None);
-
-        // Process any any FS events that occurred while performing the initial scan.
-        // For these events, update events cannot be as precise, because we didn't
-        // have the previous state loaded yet.
-        self.phase = BackgroundScannerPhase::EventsReceivedDuringInitialScan;
-        if let Poll::Ready(Some(events)) = futures::poll!(fs_events_rx.next()) {
-            let mut paths = events.into_iter().map(|e| e.path).collect::<Vec<_>>();
-            while let Poll::Ready(Some(more_events)) = futures::poll!(fs_events_rx.next()) {
-                paths.extend(more_events.into_iter().map(|e| e.path));
-            }
-            self.process_events(paths).await;
-        }
-
-        // Continue processing events until the worktree is dropped.
-        self.phase = BackgroundScannerPhase::Events;
-        loop {
-            select_biased! {
-                // Process any path refresh requests from the worktree. Prioritize
-                // these before handling changes reported by the filesystem.
-                request = self.scan_requests_rx.recv().fuse() => {
-                    let Ok(request) = request else { break };
-                    if !self.process_scan_request(request, false).await {
-                        return;
-                    }
-                }
-
-                path_prefix = self.path_prefixes_to_scan_rx.recv().fuse() => {
-                    let Ok(path_prefix) = path_prefix else { break };
-                    log::trace!("adding path prefix {:?}", path_prefix);
-
-                    let did_scan = self.forcibly_load_paths(&[path_prefix.clone()]).await;
-                    if did_scan {
-                        let abs_path =
-                        {
-                            let mut state = self.state.lock();
-                            state.path_prefixes_to_scan.insert(path_prefix.clone());
-                            state.snapshot.abs_path.join(&path_prefix)
-                        };
-
-                        if let Some(abs_path) = self.fs.canonicalize(&abs_path).await.log_err() {
-                            self.process_events(vec![abs_path]).await;
-                        }
-                    }
-                }
-
-                events = fs_events_rx.next().fuse() => {
-                    let Some(events) = events else { break };
-                    let mut paths = events.into_iter().map(|e| e.path).collect::<Vec<_>>();
-                    while let Poll::Ready(Some(more_events)) = futures::poll!(fs_events_rx.next()) {
-                        paths.extend(more_events.into_iter().map(|e| e.path));
-                    }
-                    self.process_events(paths.clone()).await;
-                }
-            }
-        }
-    }
-
-    async fn process_scan_request(&self, mut request: ScanRequest, scanning: bool) -> bool {
-        log::debug!("rescanning paths {:?}", request.relative_paths);
-
-        request.relative_paths.sort_unstable();
-        self.forcibly_load_paths(&request.relative_paths).await;
-
-        let root_path = self.state.lock().snapshot.abs_path.clone();
-        let root_canonical_path = match self.fs.canonicalize(&root_path).await {
-            Ok(path) => path,
-            Err(err) => {
-                log::error!("failed to canonicalize root path: {}", err);
-                return false;
-            }
-        };
-        let abs_paths = request
-            .relative_paths
-            .iter()
-            .map(|path| {
-                if path.file_name().is_some() {
-                    root_canonical_path.join(path)
-                } else {
-                    root_canonical_path.clone()
-                }
-            })
-            .collect::<Vec<_>>();
-
-        self.reload_entries_for_paths(
-            root_path,
-            root_canonical_path,
-            &request.relative_paths,
-            abs_paths,
-            None,
-        )
-        .await;
-        self.send_status_update(scanning, Some(request.done))
-    }
-
-    async fn process_events(&mut self, mut abs_paths: Vec<PathBuf>) {
-        let root_path = self.state.lock().snapshot.abs_path.clone();
-        let root_canonical_path = match self.fs.canonicalize(&root_path).await {
-            Ok(path) => path,
-            Err(err) => {
-                log::error!("failed to canonicalize root path: {}", err);
-                return;
-            }
-        };
-
-        let mut relative_paths = Vec::with_capacity(abs_paths.len());
-        let mut dot_git_paths_to_reload = HashSet::default();
-        abs_paths.sort_unstable();
-        abs_paths.dedup_by(|a, b| a.starts_with(&b));
-        abs_paths.retain(|abs_path| {
-            let snapshot = &self.state.lock().snapshot;
-            {
-                let mut is_git_related = false;
-                if let Some(dot_git_dir) = abs_path
-                    .ancestors()
-                    .find(|ancestor| ancestor.file_name() == Some(&*DOT_GIT))
-                {
-                    let dot_git_path = dot_git_dir
-                        .strip_prefix(&root_canonical_path)
-                        .ok()
-                        .map(|path| path.to_path_buf())
-                        .unwrap_or_else(|| dot_git_dir.to_path_buf());
-                    dot_git_paths_to_reload.insert(dot_git_path.to_path_buf());
-                    is_git_related = true;
-                }
-
-                let relative_path: Arc<Path> =
-                    if let Ok(path) = abs_path.strip_prefix(&root_canonical_path) {
-                        path.into()
-                    } else {
-                        log::error!(
-                        "ignoring event {abs_path:?} outside of root path {root_canonical_path:?}",
-                    );
-                        return false;
-                    };
-
-                let parent_dir_is_loaded = relative_path.parent().map_or(true, |parent| {
-                    snapshot
-                        .entry_for_path(parent)
-                        .map_or(false, |entry| entry.kind == EntryKind::Dir)
-                });
-                if !parent_dir_is_loaded {
-                    log::debug!("ignoring event {relative_path:?} within unloaded directory");
-                    return false;
-                }
-
-                if snapshot.is_path_excluded(relative_path.to_path_buf()) {
-                    if !is_git_related {
-                        log::debug!("ignoring FS event for excluded path {relative_path:?}");
-                    }
-                    return false;
-                }
-
-                relative_paths.push(relative_path);
-                true
-            }
-        });
-
-        if dot_git_paths_to_reload.is_empty() && relative_paths.is_empty() {
-            return;
-        }
-
-        if !relative_paths.is_empty() {
-            log::debug!("received fs events {:?}", relative_paths);
-
-            let (scan_job_tx, scan_job_rx) = channel::unbounded();
-            self.reload_entries_for_paths(
-                root_path,
-                root_canonical_path,
-                &relative_paths,
-                abs_paths,
-                Some(scan_job_tx.clone()),
-            )
-            .await;
-            drop(scan_job_tx);
-            self.scan_dirs(false, scan_job_rx).await;
-
-            let (scan_job_tx, scan_job_rx) = channel::unbounded();
-            self.update_ignore_statuses(scan_job_tx).await;
-            self.scan_dirs(false, scan_job_rx).await;
-        }
-
-        {
-            let mut state = self.state.lock();
-            if !dot_git_paths_to_reload.is_empty() {
-                if relative_paths.is_empty() {
-                    state.snapshot.scan_id += 1;
-                }
-                log::debug!("reloading repositories: {dot_git_paths_to_reload:?}");
-                state.reload_repositories(&dot_git_paths_to_reload, self.fs.as_ref());
-            }
-            state.snapshot.completed_scan_id = state.snapshot.scan_id;
-            for (_, entry_id) in mem::take(&mut state.removed_entry_ids) {
-                state.scanned_dirs.remove(&entry_id);
-            }
-        }
-
-        self.send_status_update(false, None);
-    }
-
-    async fn forcibly_load_paths(&self, paths: &[Arc<Path>]) -> bool {
-        let (scan_job_tx, mut scan_job_rx) = channel::unbounded();
-        {
-            let mut state = self.state.lock();
-            let root_path = state.snapshot.abs_path.clone();
-            for path in paths {
-                for ancestor in path.ancestors() {
-                    if let Some(entry) = state.snapshot.entry_for_path(ancestor) {
-                        if entry.kind == EntryKind::UnloadedDir {
-                            let abs_path = root_path.join(ancestor);
-                            state.enqueue_scan_dir(abs_path.into(), entry, &scan_job_tx);
-                            state.paths_to_scan.insert(path.clone());
-                            break;
-                        }
-                    }
-                }
-            }
-            drop(scan_job_tx);
-        }
-        while let Some(job) = scan_job_rx.next().await {
-            self.scan_dir(&job).await.log_err();
-        }
-
-        mem::take(&mut self.state.lock().paths_to_scan).len() > 0
-    }
-
-    async fn scan_dirs(
-        &self,
-        enable_progress_updates: bool,
-        scan_jobs_rx: channel::Receiver<ScanJob>,
-    ) {
-        use futures::FutureExt as _;
-
-        if self
-            .status_updates_tx
-            .unbounded_send(ScanState::Started)
-            .is_err()
-        {
-            return;
-        }
-
-        let progress_update_count = AtomicUsize::new(0);
-        self.executor
-            .scoped(|scope| {
-                for _ in 0..self.executor.num_cpus() {
-                    scope.spawn(async {
-                        let mut last_progress_update_count = 0;
-                        let progress_update_timer = self.progress_timer(enable_progress_updates).fuse();
-                        futures::pin_mut!(progress_update_timer);
-
-                        loop {
-                            select_biased! {
-                                // Process any path refresh requests before moving on to process
-                                // the scan queue, so that user operations are prioritized.
-                                request = self.scan_requests_rx.recv().fuse() => {
-                                    let Ok(request) = request else { break };
-                                    if !self.process_scan_request(request, true).await {
-                                        return;
-                                    }
-                                }
-
-                                // Send periodic progress updates to the worktree. Use an atomic counter
-                                // to ensure that only one of the workers sends a progress update after
-                                // the update interval elapses.
-                                _ = progress_update_timer => {
-                                    match progress_update_count.compare_exchange(
-                                        last_progress_update_count,
-                                        last_progress_update_count + 1,
-                                        SeqCst,
-                                        SeqCst
-                                    ) {
-                                        Ok(_) => {
-                                            last_progress_update_count += 1;
-                                            self.send_status_update(true, None);
-                                        }
-                                        Err(count) => {
-                                            last_progress_update_count = count;
-                                        }
-                                    }
-                                    progress_update_timer.set(self.progress_timer(enable_progress_updates).fuse());
-                                }
-
-                                // Recursively load directories from the file system.
-                                job = scan_jobs_rx.recv().fuse() => {
-                                    let Ok(job) = job else { break };
-                                    if let Err(err) = self.scan_dir(&job).await {
-                                        if job.path.as_ref() != Path::new("") {
-                                            log::error!("error scanning directory {:?}: {}", job.abs_path, err);
-                                        }
-                                    }
-                                }
-                            }
-                        }
-                    })
-                }
-            })
-            .await;
-    }
-
-    fn send_status_update(&self, scanning: bool, barrier: Option<barrier::Sender>) -> bool {
-        let mut state = self.state.lock();
-        if state.changed_paths.is_empty() && scanning {
-            return true;
-        }
-
-        let new_snapshot = state.snapshot.clone();
-        let old_snapshot = mem::replace(&mut state.prev_snapshot, new_snapshot.snapshot.clone());
-        let changes = self.build_change_set(&old_snapshot, &new_snapshot, &state.changed_paths);
-        state.changed_paths.clear();
-
-        self.status_updates_tx
-            .unbounded_send(ScanState::Updated {
-                snapshot: new_snapshot,
-                changes,
-                scanning,
-                barrier,
-            })
-            .is_ok()
-    }
-
-    async fn scan_dir(&self, job: &ScanJob) -> Result<()> {
-        let root_abs_path;
-        let mut ignore_stack;
-        let mut new_ignore;
-        let root_char_bag;
-        let next_entry_id;
-        {
-            let state = self.state.lock();
-            let snapshot = &state.snapshot;
-            root_abs_path = snapshot.abs_path().clone();
-            if snapshot.is_path_excluded(job.path.to_path_buf()) {
-                log::error!("skipping excluded directory {:?}", job.path);
-                return Ok(());
-            }
-            log::debug!("scanning directory {:?}", job.path);
-            ignore_stack = job.ignore_stack.clone();
-            new_ignore = None;
-            root_char_bag = snapshot.root_char_bag;
-            next_entry_id = self.next_entry_id.clone();
-            drop(state);
-        }
-
-        let mut dotgit_path = None;
-        let mut root_canonical_path = None;
-        let mut new_entries: Vec<Entry> = Vec::new();
-        let mut new_jobs: Vec<Option<ScanJob>> = Vec::new();
-        let mut child_paths = self.fs.read_dir(&job.abs_path).await?;
-        while let Some(child_abs_path) = child_paths.next().await {
-            let child_abs_path: Arc<Path> = match child_abs_path {
-                Ok(child_abs_path) => child_abs_path.into(),
-                Err(error) => {
-                    log::error!("error processing entry {:?}", error);
-                    continue;
-                }
-            };
-            let child_name = child_abs_path.file_name().unwrap();
-            let child_path: Arc<Path> = job.path.join(child_name).into();
-            // If we find a .gitignore, add it to the stack of ignores used to determine which paths are ignored
-            if child_name == *GITIGNORE {
-                match build_gitignore(&child_abs_path, self.fs.as_ref()).await {
-                    Ok(ignore) => {
-                        let ignore = Arc::new(ignore);
-                        ignore_stack = ignore_stack.append(job.abs_path.clone(), ignore.clone());
-                        new_ignore = Some(ignore);
-                    }
-                    Err(error) => {
-                        log::error!(
-                            "error loading .gitignore file {:?} - {:?}",
-                            child_name,
-                            error
-                        );
-                    }
-                }
-
-                // Update ignore status of any child entries we've already processed to reflect the
-                // ignore file in the current directory. Because `.gitignore` starts with a `.`,
-                // there should rarely be too numerous. Update the ignore stack associated with any
-                // new jobs as well.
-                let mut new_jobs = new_jobs.iter_mut();
-                for entry in &mut new_entries {
-                    let entry_abs_path = root_abs_path.join(&entry.path);
-                    entry.is_ignored =
-                        ignore_stack.is_abs_path_ignored(&entry_abs_path, entry.is_dir());
-
-                    if entry.is_dir() {
-                        if let Some(job) = new_jobs.next().expect("missing scan job for entry") {
-                            job.ignore_stack = if entry.is_ignored {
-                                IgnoreStack::all()
-                            } else {
-                                ignore_stack.clone()
-                            };
-                        }
-                    }
-                }
-            }
-            // If we find a .git, we'll need to load the repository.
-            else if child_name == *DOT_GIT {
-                dotgit_path = Some(child_path.clone());
-            }
-
-            {
-                let relative_path = job.path.join(child_name);
-                let mut state = self.state.lock();
-                if state.snapshot.is_path_excluded(relative_path.clone()) {
-                    log::debug!("skipping excluded child entry {relative_path:?}");
-                    state.remove_path(&relative_path);
-                    continue;
-                }
-                drop(state);
-            }
-
-            let child_metadata = match self.fs.metadata(&child_abs_path).await {
-                Ok(Some(metadata)) => metadata,
-                Ok(None) => continue,
-                Err(err) => {
-                    log::error!("error processing {child_abs_path:?}: {err:?}");
-                    continue;
-                }
-            };
-
-            let mut child_entry = Entry::new(
-                child_path.clone(),
-                &child_metadata,
-                &next_entry_id,
-                root_char_bag,
-            );
-
-            if job.is_external {
-                child_entry.is_external = true;
-            } else if child_metadata.is_symlink {
-                let canonical_path = match self.fs.canonicalize(&child_abs_path).await {
-                    Ok(path) => path,
-                    Err(err) => {
-                        log::error!(
-                            "error reading target of symlink {:?}: {:?}",
-                            child_abs_path,
-                            err
-                        );
-                        continue;
-                    }
-                };
-
-                // lazily canonicalize the root path in order to determine if
-                // symlinks point outside of the worktree.
-                let root_canonical_path = match &root_canonical_path {
-                    Some(path) => path,
-                    None => match self.fs.canonicalize(&root_abs_path).await {
-                        Ok(path) => root_canonical_path.insert(path),
-                        Err(err) => {
-                            log::error!("error canonicalizing root {:?}: {:?}", root_abs_path, err);
-                            continue;
-                        }
-                    },
-                };
-
-                if !canonical_path.starts_with(root_canonical_path) {
-                    child_entry.is_external = true;
-                }
-            }
-
-            if child_entry.is_dir() {
-                child_entry.is_ignored = ignore_stack.is_abs_path_ignored(&child_abs_path, true);
-
-                // Avoid recursing until crash in the case of a recursive symlink
-                if !job.ancestor_inodes.contains(&child_entry.inode) {
-                    let mut ancestor_inodes = job.ancestor_inodes.clone();
-                    ancestor_inodes.insert(child_entry.inode);
-
-                    new_jobs.push(Some(ScanJob {
-                        abs_path: child_abs_path,
-                        path: child_path,
-                        is_external: child_entry.is_external,
-                        ignore_stack: if child_entry.is_ignored {
-                            IgnoreStack::all()
-                        } else {
-                            ignore_stack.clone()
-                        },
-                        ancestor_inodes,
-                        scan_queue: job.scan_queue.clone(),
-                        containing_repository: job.containing_repository.clone(),
-                    }));
-                } else {
-                    new_jobs.push(None);
-                }
-            } else {
-                child_entry.is_ignored = ignore_stack.is_abs_path_ignored(&child_abs_path, false);
-                if !child_entry.is_ignored {
-                    if let Some((repository_dir, repository, staged_statuses)) =
-                        &job.containing_repository
-                    {
-                        if let Ok(repo_path) = child_entry.path.strip_prefix(&repository_dir.0) {
-                            let repo_path = RepoPath(repo_path.into());
-                            child_entry.git_status = combine_git_statuses(
-                                staged_statuses.get(&repo_path).copied(),
-                                repository
-                                    .lock()
-                                    .unstaged_status(&repo_path, child_entry.mtime),
-                            );
-                        }
-                    }
-                }
-            }
-
-            new_entries.push(child_entry);
-        }
-
-        let mut state = self.state.lock();
-
-        // Identify any subdirectories that should not be scanned.
-        let mut job_ix = 0;
-        for entry in &mut new_entries {
-            state.reuse_entry_id(entry);
-            if entry.is_dir() {
-                if state.should_scan_directory(&entry) {
-                    job_ix += 1;
-                } else {
-                    log::debug!("defer scanning directory {:?}", entry.path);
-                    entry.kind = EntryKind::UnloadedDir;
-                    new_jobs.remove(job_ix);
-                }
-            }
-        }
-
-        state.populate_dir(&job.path, new_entries, new_ignore);
-
-        let repository =
-            dotgit_path.and_then(|path| state.build_git_repository(path, self.fs.as_ref()));
-
-        for new_job in new_jobs {
-            if let Some(mut new_job) = new_job {
-                if let Some(containing_repository) = &repository {
-                    new_job.containing_repository = Some(containing_repository.clone());
-                }
-
-                job.scan_queue
-                    .try_send(new_job)
-                    .expect("channel is unbounded");
-            }
-        }
-
-        Ok(())
-    }
-
-    async fn reload_entries_for_paths(
-        &self,
-        root_abs_path: Arc<Path>,
-        root_canonical_path: PathBuf,
-        relative_paths: &[Arc<Path>],
-        abs_paths: Vec<PathBuf>,
-        scan_queue_tx: Option<Sender<ScanJob>>,
-    ) {
-        let metadata = futures::future::join_all(
-            abs_paths
-                .iter()
-                .map(|abs_path| async move {
-                    let metadata = self.fs.metadata(&abs_path).await?;
-                    if let Some(metadata) = metadata {
-                        let canonical_path = self.fs.canonicalize(&abs_path).await?;
-                        anyhow::Ok(Some((metadata, canonical_path)))
-                    } else {
-                        Ok(None)
-                    }
-                })
-                .collect::<Vec<_>>(),
-        )
-        .await;
-
-        let mut state = self.state.lock();
-        let snapshot = &mut state.snapshot;
-        let is_idle = snapshot.completed_scan_id == snapshot.scan_id;
-        let doing_recursive_update = scan_queue_tx.is_some();
-        snapshot.scan_id += 1;
-        if is_idle && !doing_recursive_update {
-            snapshot.completed_scan_id = snapshot.scan_id;
-        }
-
-        // Remove any entries for paths that no longer exist or are being recursively
-        // refreshed. Do this before adding any new entries, so that renames can be
-        // detected regardless of the order of the paths.
-        for (path, metadata) in relative_paths.iter().zip(metadata.iter()) {
-            if matches!(metadata, Ok(None)) || doing_recursive_update {
-                log::trace!("remove path {:?}", path);
-                state.remove_path(path);
-            }
-        }
-
-        for (path, metadata) in relative_paths.iter().zip(metadata.iter()) {
-            let abs_path: Arc<Path> = root_abs_path.join(&path).into();
-            match metadata {
-                Ok(Some((metadata, canonical_path))) => {
-                    let ignore_stack = state
-                        .snapshot
-                        .ignore_stack_for_abs_path(&abs_path, metadata.is_dir);
-
-                    let mut fs_entry = Entry::new(
-                        path.clone(),
-                        metadata,
-                        self.next_entry_id.as_ref(),
-                        state.snapshot.root_char_bag,
-                    );
-                    let is_dir = fs_entry.is_dir();
-                    fs_entry.is_ignored = ignore_stack.is_abs_path_ignored(&abs_path, is_dir);
-                    fs_entry.is_external = !canonical_path.starts_with(&root_canonical_path);
-
-                    if !is_dir && !fs_entry.is_ignored {
-                        if let Some((work_dir, repo)) = state.snapshot.local_repo_for_path(&path) {
-                            if let Ok(repo_path) = path.strip_prefix(work_dir.0) {
-                                let repo_path = RepoPath(repo_path.into());
-                                let repo = repo.repo_ptr.lock();
-                                fs_entry.git_status = repo.status(&repo_path, fs_entry.mtime);
-                            }
-                        }
-                    }
-
-                    if let (Some(scan_queue_tx), true) = (&scan_queue_tx, fs_entry.is_dir()) {
-                        if state.should_scan_directory(&fs_entry) {
-                            state.enqueue_scan_dir(abs_path, &fs_entry, scan_queue_tx);
-                        } else {
-                            fs_entry.kind = EntryKind::UnloadedDir;
-                        }
-                    }
-
-                    state.insert_entry(fs_entry, self.fs.as_ref());
-                }
-                Ok(None) => {
-                    self.remove_repo_path(&path, &mut state.snapshot);
-                }
-                Err(err) => {
-                    // TODO - create a special 'error' entry in the entries tree to mark this
-                    log::error!("error reading file {abs_path:?} on event: {err:#}");
-                }
-            }
-        }
-
-        util::extend_sorted(
-            &mut state.changed_paths,
-            relative_paths.iter().cloned(),
-            usize::MAX,
-            Ord::cmp,
-        );
-    }
-
-    fn remove_repo_path(&self, path: &Path, snapshot: &mut LocalSnapshot) -> Option<()> {
-        if !path
-            .components()
-            .any(|component| component.as_os_str() == *DOT_GIT)
-        {
-            if let Some(repository) = snapshot.repository_for_work_directory(path) {
-                let entry = repository.work_directory.0;
-                snapshot.git_repositories.remove(&entry);
-                snapshot
-                    .snapshot
-                    .repository_entries
-                    .remove(&RepositoryWorkDirectory(path.into()));
-                return Some(());
-            }
-        }
-
-        // TODO statuses
-        // Track when a .git is removed and iterate over the file system there
-
-        Some(())
-    }
-
-    async fn update_ignore_statuses(&self, scan_job_tx: Sender<ScanJob>) {
-        use futures::FutureExt as _;
-
-        let mut snapshot = self.state.lock().snapshot.clone();
-        let mut ignores_to_update = Vec::new();
-        let mut ignores_to_delete = Vec::new();
-        let abs_path = snapshot.abs_path.clone();
-        for (parent_abs_path, (_, needs_update)) in &mut snapshot.ignores_by_parent_abs_path {
-            if let Ok(parent_path) = parent_abs_path.strip_prefix(&abs_path) {
-                if *needs_update {
-                    *needs_update = false;
-                    if snapshot.snapshot.entry_for_path(parent_path).is_some() {
-                        ignores_to_update.push(parent_abs_path.clone());
-                    }
-                }
-
-                let ignore_path = parent_path.join(&*GITIGNORE);
-                if snapshot.snapshot.entry_for_path(ignore_path).is_none() {
-                    ignores_to_delete.push(parent_abs_path.clone());
-                }
-            }
-        }
-
-        for parent_abs_path in ignores_to_delete {
-            snapshot.ignores_by_parent_abs_path.remove(&parent_abs_path);
-            self.state
-                .lock()
-                .snapshot
-                .ignores_by_parent_abs_path
-                .remove(&parent_abs_path);
-        }
-
-        let (ignore_queue_tx, ignore_queue_rx) = channel::unbounded();
-        ignores_to_update.sort_unstable();
-        let mut ignores_to_update = ignores_to_update.into_iter().peekable();
-        while let Some(parent_abs_path) = ignores_to_update.next() {
-            while ignores_to_update
-                .peek()
-                .map_or(false, |p| p.starts_with(&parent_abs_path))
-            {
-                ignores_to_update.next().unwrap();
-            }
-
-            let ignore_stack = snapshot.ignore_stack_for_abs_path(&parent_abs_path, true);
-            smol::block_on(ignore_queue_tx.send(UpdateIgnoreStatusJob {
-                abs_path: parent_abs_path,
-                ignore_stack,
-                ignore_queue: ignore_queue_tx.clone(),
-                scan_queue: scan_job_tx.clone(),
-            }))
-            .unwrap();
-        }
-        drop(ignore_queue_tx);
-
-        self.executor
-            .scoped(|scope| {
-                for _ in 0..self.executor.num_cpus() {
-                    scope.spawn(async {
-                        loop {
-                            select_biased! {
-                                // Process any path refresh requests before moving on to process
-                                // the queue of ignore statuses.
-                                request = self.scan_requests_rx.recv().fuse() => {
-                                    let Ok(request) = request else { break };
-                                    if !self.process_scan_request(request, true).await {
-                                        return;
-                                    }
-                                }
-
-                                // Recursively process directories whose ignores have changed.
-                                job = ignore_queue_rx.recv().fuse() => {
-                                    let Ok(job) = job else { break };
-                                    self.update_ignore_status(job, &snapshot).await;
-                                }
-                            }
-                        }
-                    });
-                }
-            })
-            .await;
-    }
-
-    async fn update_ignore_status(&self, job: UpdateIgnoreStatusJob, snapshot: &LocalSnapshot) {
-        log::trace!("update ignore status {:?}", job.abs_path);
-
-        let mut ignore_stack = job.ignore_stack;
-        if let Some((ignore, _)) = snapshot.ignores_by_parent_abs_path.get(&job.abs_path) {
-            ignore_stack = ignore_stack.append(job.abs_path.clone(), ignore.clone());
-        }
-
-        let mut entries_by_id_edits = Vec::new();
-        let mut entries_by_path_edits = Vec::new();
-        let path = job.abs_path.strip_prefix(&snapshot.abs_path).unwrap();
-        for mut entry in snapshot.child_entries(path).cloned() {
-            let was_ignored = entry.is_ignored;
-            let abs_path: Arc<Path> = snapshot.abs_path().join(&entry.path).into();
-            entry.is_ignored = ignore_stack.is_abs_path_ignored(&abs_path, entry.is_dir());
-            if entry.is_dir() {
-                let child_ignore_stack = if entry.is_ignored {
-                    IgnoreStack::all()
-                } else {
-                    ignore_stack.clone()
-                };
-
-                // Scan any directories that were previously ignored and weren't previously scanned.
-                if was_ignored && !entry.is_ignored && entry.kind.is_unloaded() {
-                    let state = self.state.lock();
-                    if state.should_scan_directory(&entry) {
-                        state.enqueue_scan_dir(abs_path.clone(), &entry, &job.scan_queue);
-                    }
-                }
-
-                job.ignore_queue
-                    .send(UpdateIgnoreStatusJob {
-                        abs_path: abs_path.clone(),
-                        ignore_stack: child_ignore_stack,
-                        ignore_queue: job.ignore_queue.clone(),
-                        scan_queue: job.scan_queue.clone(),
-                    })
-                    .await
-                    .unwrap();
-            }
-
-            if entry.is_ignored != was_ignored {
-                let mut path_entry = snapshot.entries_by_id.get(&entry.id, &()).unwrap().clone();
-                path_entry.scan_id = snapshot.scan_id;
-                path_entry.is_ignored = entry.is_ignored;
-                entries_by_id_edits.push(Edit::Insert(path_entry));
-                entries_by_path_edits.push(Edit::Insert(entry));
-            }
-        }
-
-        let state = &mut self.state.lock();
-        for edit in &entries_by_path_edits {
-            if let Edit::Insert(entry) = edit {
-                if let Err(ix) = state.changed_paths.binary_search(&entry.path) {
-                    state.changed_paths.insert(ix, entry.path.clone());
-                }
-            }
-        }
-
-        state
-            .snapshot
-            .entries_by_path
-            .edit(entries_by_path_edits, &());
-        state.snapshot.entries_by_id.edit(entries_by_id_edits, &());
-    }
-
-    fn build_change_set(
-        &self,
-        old_snapshot: &Snapshot,
-        new_snapshot: &Snapshot,
-        event_paths: &[Arc<Path>],
-    ) -> UpdatedEntriesSet {
-        use BackgroundScannerPhase::*;
-        use PathChange::{Added, AddedOrUpdated, Loaded, Removed, Updated};
-
-        // Identify which paths have changed. Use the known set of changed
-        // parent paths to optimize the search.
-        let mut changes = Vec::new();
-        let mut old_paths = old_snapshot.entries_by_path.cursor::<PathKey>();
-        let mut new_paths = new_snapshot.entries_by_path.cursor::<PathKey>();
-        let mut last_newly_loaded_dir_path = None;
-        old_paths.next(&());
-        new_paths.next(&());
-        for path in event_paths {
-            let path = PathKey(path.clone());
-            if old_paths.item().map_or(false, |e| e.path < path.0) {
-                old_paths.seek_forward(&path, Bias::Left, &());
-            }
-            if new_paths.item().map_or(false, |e| e.path < path.0) {
-                new_paths.seek_forward(&path, Bias::Left, &());
-            }
-            loop {
-                match (old_paths.item(), new_paths.item()) {
-                    (Some(old_entry), Some(new_entry)) => {
-                        if old_entry.path > path.0
-                            && new_entry.path > path.0
-                            && !old_entry.path.starts_with(&path.0)
-                            && !new_entry.path.starts_with(&path.0)
-                        {
-                            break;
-                        }
-
-                        match Ord::cmp(&old_entry.path, &new_entry.path) {
-                            Ordering::Less => {
-                                changes.push((old_entry.path.clone(), old_entry.id, Removed));
-                                old_paths.next(&());
-                            }
-                            Ordering::Equal => {
-                                if self.phase == EventsReceivedDuringInitialScan {
-                                    if old_entry.id != new_entry.id {
-                                        changes.push((
-                                            old_entry.path.clone(),
-                                            old_entry.id,
-                                            Removed,
-                                        ));
-                                    }
-                                    // If the worktree was not fully initialized when this event was generated,
-                                    // we can't know whether this entry was added during the scan or whether
-                                    // it was merely updated.
-                                    changes.push((
-                                        new_entry.path.clone(),
-                                        new_entry.id,
-                                        AddedOrUpdated,
-                                    ));
-                                } else if old_entry.id != new_entry.id {
-                                    changes.push((old_entry.path.clone(), old_entry.id, Removed));
-                                    changes.push((new_entry.path.clone(), new_entry.id, Added));
-                                } else if old_entry != new_entry {
-                                    if old_entry.kind.is_unloaded() {
-                                        last_newly_loaded_dir_path = Some(&new_entry.path);
-                                        changes.push((
-                                            new_entry.path.clone(),
-                                            new_entry.id,
-                                            Loaded,
-                                        ));
-                                    } else {
-                                        changes.push((
-                                            new_entry.path.clone(),
-                                            new_entry.id,
-                                            Updated,
-                                        ));
-                                    }
-                                }
-                                old_paths.next(&());
-                                new_paths.next(&());
-                            }
-                            Ordering::Greater => {
-                                let is_newly_loaded = self.phase == InitialScan
-                                    || last_newly_loaded_dir_path
-                                        .as_ref()
-                                        .map_or(false, |dir| new_entry.path.starts_with(&dir));
-                                changes.push((
-                                    new_entry.path.clone(),
-                                    new_entry.id,
-                                    if is_newly_loaded { Loaded } else { Added },
-                                ));
-                                new_paths.next(&());
-                            }
-                        }
-                    }
-                    (Some(old_entry), None) => {
-                        changes.push((old_entry.path.clone(), old_entry.id, Removed));
-                        old_paths.next(&());
-                    }
-                    (None, Some(new_entry)) => {
-                        let is_newly_loaded = self.phase == InitialScan
-                            || last_newly_loaded_dir_path
-                                .as_ref()
-                                .map_or(false, |dir| new_entry.path.starts_with(&dir));
-                        changes.push((
-                            new_entry.path.clone(),
-                            new_entry.id,
-                            if is_newly_loaded { Loaded } else { Added },
-                        ));
-                        new_paths.next(&());
-                    }
-                    (None, None) => break,
-                }
-            }
-        }
-
-        changes.into()
-    }
-
-    async fn progress_timer(&self, running: bool) {
-        if !running {
-            return futures::future::pending().await;
-        }
-
-        #[cfg(any(test, feature = "test-support"))]
-        if self.fs.is_fake() {
-            return self.executor.simulate_random_delay().await;
-        }
-
-        smol::Timer::after(Duration::from_millis(100)).await;
-    }
-}
-
-fn char_bag_for_path(root_char_bag: CharBag, path: &Path) -> CharBag {
-    let mut result = root_char_bag;
-    result.extend(
-        path.to_string_lossy()
-            .chars()
-            .map(|c| c.to_ascii_lowercase()),
-    );
-    result
-}
-
-struct ScanJob {
-    abs_path: Arc<Path>,
-    path: Arc<Path>,
-    ignore_stack: Arc<IgnoreStack>,
-    scan_queue: Sender<ScanJob>,
-    ancestor_inodes: TreeSet<u64>,
-    is_external: bool,
-    containing_repository: Option<(
-        RepositoryWorkDirectory,
-        Arc<Mutex<dyn GitRepository>>,
-        TreeMap<RepoPath, GitFileStatus>,
-    )>,
-}
-
-struct UpdateIgnoreStatusJob {
-    abs_path: Arc<Path>,
-    ignore_stack: Arc<IgnoreStack>,
-    ignore_queue: Sender<UpdateIgnoreStatusJob>,
-    scan_queue: Sender<ScanJob>,
-}
-
-pub trait WorktreeModelHandle {
-    #[cfg(any(test, feature = "test-support"))]
-    fn flush_fs_events<'a>(
-        &self,
-        cx: &'a mut gpui::TestAppContext,
-    ) -> futures::future::LocalBoxFuture<'a, ()>;
-}
-
-impl WorktreeModelHandle for Model<Worktree> {
-    // When the worktree's FS event stream sometimes delivers "redundant" events for FS changes that
-    // occurred before the worktree was constructed. These events can cause the worktree to perform
-    // extra directory scans, and emit extra scan-state notifications.
-    //
-    // This function mutates the worktree's directory and waits for those mutations to be picked up,
-    // to ensure that all redundant FS events have already been processed.
-    #[cfg(any(test, feature = "test-support"))]
-    fn flush_fs_events<'a>(
-        &self,
-        cx: &'a mut gpui::TestAppContext,
-    ) -> futures::future::LocalBoxFuture<'a, ()> {
-        let file_name = "fs-event-sentinel";
-
-        let tree = self.clone();
-        let (fs, root_path) = self.update(cx, |tree, _| {
-            let tree = tree.as_local().unwrap();
-            (tree.fs.clone(), tree.abs_path().clone())
-        });
-
-        async move {
-            fs.create_file(&root_path.join(file_name), Default::default())
-                .await
-                .unwrap();
-
-            cx.condition(&tree, |tree, _| tree.entry_for_path(file_name).is_some())
-                .await;
-
-            fs.remove_file(&root_path.join(file_name), Default::default())
-                .await
-                .unwrap();
-            cx.condition(&tree, |tree, _| tree.entry_for_path(file_name).is_none())
-                .await;
-
-            cx.update(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-                .await;
-        }
-        .boxed_local()
-    }
-}
-
-#[derive(Clone, Debug)]
-struct TraversalProgress<'a> {
-    max_path: &'a Path,
-    count: usize,
-    non_ignored_count: usize,
-    file_count: usize,
-    non_ignored_file_count: usize,
-}
-
-impl<'a> TraversalProgress<'a> {
-    fn count(&self, include_dirs: bool, include_ignored: bool) -> usize {
-        match (include_ignored, include_dirs) {
-            (true, true) => self.count,
-            (true, false) => self.file_count,
-            (false, true) => self.non_ignored_count,
-            (false, false) => self.non_ignored_file_count,
-        }
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, EntrySummary> for TraversalProgress<'a> {
-    fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) {
-        self.max_path = summary.max_path.as_ref();
-        self.count += summary.count;
-        self.non_ignored_count += summary.non_ignored_count;
-        self.file_count += summary.file_count;
-        self.non_ignored_file_count += summary.non_ignored_file_count;
-    }
-}
-
-impl<'a> Default for TraversalProgress<'a> {
-    fn default() -> Self {
-        Self {
-            max_path: Path::new(""),
-            count: 0,
-            non_ignored_count: 0,
-            file_count: 0,
-            non_ignored_file_count: 0,
-        }
-    }
-}
-
-#[derive(Clone, Debug, Default, Copy)]
-struct GitStatuses {
-    added: usize,
-    modified: usize,
-    conflict: usize,
-}
-
-impl AddAssign for GitStatuses {
-    fn add_assign(&mut self, rhs: Self) {
-        self.added += rhs.added;
-        self.modified += rhs.modified;
-        self.conflict += rhs.conflict;
-    }
-}
-
-impl Sub for GitStatuses {
-    type Output = GitStatuses;
-
-    fn sub(self, rhs: Self) -> Self::Output {
-        GitStatuses {
-            added: self.added - rhs.added,
-            modified: self.modified - rhs.modified,
-            conflict: self.conflict - rhs.conflict,
-        }
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, EntrySummary> for GitStatuses {
-    fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) {
-        *self += summary.statuses
-    }
-}
-
-pub struct Traversal<'a> {
-    cursor: sum_tree::Cursor<'a, Entry, TraversalProgress<'a>>,
-    include_ignored: bool,
-    include_dirs: bool,
-}
-
-impl<'a> Traversal<'a> {
-    pub fn advance(&mut self) -> bool {
-        self.cursor.seek_forward(
-            &TraversalTarget::Count {
-                count: self.end_offset() + 1,
-                include_dirs: self.include_dirs,
-                include_ignored: self.include_ignored,
-            },
-            Bias::Left,
-            &(),
-        )
-    }
-
-    pub fn advance_to_sibling(&mut self) -> bool {
-        while let Some(entry) = self.cursor.item() {
-            self.cursor.seek_forward(
-                &TraversalTarget::PathSuccessor(&entry.path),
-                Bias::Left,
-                &(),
-            );
-            if let Some(entry) = self.cursor.item() {
-                if (self.include_dirs || !entry.is_dir())
-                    && (self.include_ignored || !entry.is_ignored)
-                {
-                    return true;
-                }
-            }
-        }
-        false
-    }
-
-    pub fn entry(&self) -> Option<&'a Entry> {
-        self.cursor.item()
-    }
-
-    pub fn start_offset(&self) -> usize {
-        self.cursor
-            .start()
-            .count(self.include_dirs, self.include_ignored)
-    }
-
-    pub fn end_offset(&self) -> usize {
-        self.cursor
-            .end(&())
-            .count(self.include_dirs, self.include_ignored)
-    }
-}
-
-impl<'a> Iterator for Traversal<'a> {
-    type Item = &'a Entry;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if let Some(item) = self.entry() {
-            self.advance();
-            Some(item)
-        } else {
-            None
-        }
-    }
-}
-
-#[derive(Debug)]
-enum TraversalTarget<'a> {
-    Path(&'a Path),
-    PathSuccessor(&'a Path),
-    Count {
-        count: usize,
-        include_ignored: bool,
-        include_dirs: bool,
-    },
-}
-
-impl<'a, 'b> SeekTarget<'a, EntrySummary, TraversalProgress<'a>> for TraversalTarget<'b> {
-    fn cmp(&self, cursor_location: &TraversalProgress<'a>, _: &()) -> Ordering {
-        match self {
-            TraversalTarget::Path(path) => path.cmp(&cursor_location.max_path),
-            TraversalTarget::PathSuccessor(path) => {
-                if !cursor_location.max_path.starts_with(path) {
-                    Ordering::Equal
-                } else {
-                    Ordering::Greater
-                }
-            }
-            TraversalTarget::Count {
-                count,
-                include_dirs,
-                include_ignored,
-            } => Ord::cmp(
-                count,
-                &cursor_location.count(*include_dirs, *include_ignored),
-            ),
-        }
-    }
-}
-
-impl<'a, 'b> SeekTarget<'a, EntrySummary, (TraversalProgress<'a>, GitStatuses)>
-    for TraversalTarget<'b>
-{
-    fn cmp(&self, cursor_location: &(TraversalProgress<'a>, GitStatuses), _: &()) -> Ordering {
-        self.cmp(&cursor_location.0, &())
-    }
-}
-
-struct ChildEntriesIter<'a> {
-    parent_path: &'a Path,
-    traversal: Traversal<'a>,
-}
-
-impl<'a> Iterator for ChildEntriesIter<'a> {
-    type Item = &'a Entry;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if let Some(item) = self.traversal.entry() {
-            if item.path.starts_with(&self.parent_path) {
-                self.traversal.advance_to_sibling();
-                return Some(item);
-            }
-        }
-        None
-    }
-}
-
-pub struct DescendentEntriesIter<'a> {
-    parent_path: &'a Path,
-    traversal: Traversal<'a>,
-}
-
-impl<'a> Iterator for DescendentEntriesIter<'a> {
-    type Item = &'a Entry;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if let Some(item) = self.traversal.entry() {
-            if item.path.starts_with(&self.parent_path) {
-                self.traversal.advance();
-                return Some(item);
-            }
-        }
-        None
-    }
-}
-
-impl<'a> From<&'a Entry> for proto::Entry {
-    fn from(entry: &'a Entry) -> Self {
-        Self {
-            id: entry.id.to_proto(),
-            is_dir: entry.is_dir(),
-            path: entry.path.to_string_lossy().into(),
-            inode: entry.inode,
-            mtime: Some(entry.mtime.into()),
-            is_symlink: entry.is_symlink,
-            is_ignored: entry.is_ignored,
-            is_external: entry.is_external,
-            git_status: entry.git_status.map(git_status_to_proto),
-        }
-    }
-}
-
-impl<'a> TryFrom<(&'a CharBag, proto::Entry)> for Entry {
-    type Error = anyhow::Error;
-
-    fn try_from((root_char_bag, entry): (&'a CharBag, proto::Entry)) -> Result<Self> {
-        if let Some(mtime) = entry.mtime {
-            let kind = if entry.is_dir {
-                EntryKind::Dir
-            } else {
-                let mut char_bag = *root_char_bag;
-                char_bag.extend(entry.path.chars().map(|c| c.to_ascii_lowercase()));
-                EntryKind::File(char_bag)
-            };
-            let path: Arc<Path> = PathBuf::from(entry.path).into();
-            Ok(Entry {
-                id: ProjectEntryId::from_proto(entry.id),
-                kind,
-                path,
-                inode: entry.inode,
-                mtime: mtime.into(),
-                is_symlink: entry.is_symlink,
-                is_ignored: entry.is_ignored,
-                is_external: entry.is_external,
-                git_status: git_status_from_proto(entry.git_status),
-            })
-        } else {
-            Err(anyhow!(
-                "missing mtime in remote worktree entry {:?}",
-                entry.path
-            ))
-        }
-    }
-}
-
-fn combine_git_statuses(
-    staged: Option<GitFileStatus>,
-    unstaged: Option<GitFileStatus>,
-) -> Option<GitFileStatus> {
-    if let Some(staged) = staged {
-        if let Some(unstaged) = unstaged {
-            if unstaged != staged {
-                Some(GitFileStatus::Modified)
-            } else {
-                Some(staged)
-            }
-        } else {
-            Some(staged)
-        }
-    } else {
-        unstaged
-    }
-}
-
-fn git_status_from_proto(git_status: Option<i32>) -> Option<GitFileStatus> {
-    git_status.and_then(|status| {
-        proto::GitStatus::from_i32(status).map(|status| match status {
-            proto::GitStatus::Added => GitFileStatus::Added,
-            proto::GitStatus::Modified => GitFileStatus::Modified,
-            proto::GitStatus::Conflict => GitFileStatus::Conflict,
-        })
-    })
-}
-
-fn git_status_to_proto(status: GitFileStatus) -> i32 {
-    match status {
-        GitFileStatus::Added => proto::GitStatus::Added as i32,
-        GitFileStatus::Modified => proto::GitStatus::Modified as i32,
-        GitFileStatus::Conflict => proto::GitStatus::Conflict as i32,
-    }
-}

crates/project2/src/worktree_tests.rs 🔗

@@ -1,2462 +0,0 @@
-use crate::{
-    project_settings::ProjectSettings,
-    worktree::{Event, Snapshot, WorktreeModelHandle},
-    Entry, EntryKind, PathChange, Project, Worktree,
-};
-use anyhow::Result;
-use client::Client;
-use fs::{repository::GitFileStatus, FakeFs, Fs, RealFs, RemoveOptions};
-use git::GITIGNORE;
-use gpui::{ModelContext, Task, TestAppContext};
-use parking_lot::Mutex;
-use postage::stream::Stream;
-use pretty_assertions::assert_eq;
-use rand::prelude::*;
-use serde_json::json;
-use settings::SettingsStore;
-use std::{
-    env,
-    fmt::Write,
-    mem,
-    path::{Path, PathBuf},
-    sync::Arc,
-};
-use util::{http::FakeHttpClient, test::temp_tree, ResultExt};
-
-#[gpui::test]
-async fn test_traversal(cx: &mut TestAppContext) {
-    init_test(cx);
-    let fs = FakeFs::new(cx.background_executor.clone());
-    fs.insert_tree(
-        "/root",
-        json!({
-           ".gitignore": "a/b\n",
-           "a": {
-               "b": "",
-               "c": "",
-           }
-        }),
-    )
-    .await;
-
-    let tree = Worktree::local(
-        build_client(cx),
-        Path::new("/root"),
-        true,
-        fs,
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-        .await;
-
-    tree.read_with(cx, |tree, _| {
-        assert_eq!(
-            tree.entries(false)
-                .map(|entry| entry.path.as_ref())
-                .collect::<Vec<_>>(),
-            vec![
-                Path::new(""),
-                Path::new(".gitignore"),
-                Path::new("a"),
-                Path::new("a/c"),
-            ]
-        );
-        assert_eq!(
-            tree.entries(true)
-                .map(|entry| entry.path.as_ref())
-                .collect::<Vec<_>>(),
-            vec![
-                Path::new(""),
-                Path::new(".gitignore"),
-                Path::new("a"),
-                Path::new("a/b"),
-                Path::new("a/c"),
-            ]
-        );
-    })
-}
-
-#[gpui::test]
-async fn test_descendent_entries(cx: &mut TestAppContext) {
-    init_test(cx);
-    let fs = FakeFs::new(cx.background_executor.clone());
-    fs.insert_tree(
-        "/root",
-        json!({
-            "a": "",
-            "b": {
-               "c": {
-                   "d": ""
-               },
-               "e": {}
-            },
-            "f": "",
-            "g": {
-                "h": {}
-            },
-            "i": {
-                "j": {
-                    "k": ""
-                },
-                "l": {
-
-                }
-            },
-            ".gitignore": "i/j\n",
-        }),
-    )
-    .await;
-
-    let tree = Worktree::local(
-        build_client(cx),
-        Path::new("/root"),
-        true,
-        fs,
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-        .await;
-
-    tree.read_with(cx, |tree, _| {
-        assert_eq!(
-            tree.descendent_entries(false, false, Path::new("b"))
-                .map(|entry| entry.path.as_ref())
-                .collect::<Vec<_>>(),
-            vec![Path::new("b/c/d"),]
-        );
-        assert_eq!(
-            tree.descendent_entries(true, false, Path::new("b"))
-                .map(|entry| entry.path.as_ref())
-                .collect::<Vec<_>>(),
-            vec![
-                Path::new("b"),
-                Path::new("b/c"),
-                Path::new("b/c/d"),
-                Path::new("b/e"),
-            ]
-        );
-
-        assert_eq!(
-            tree.descendent_entries(false, false, Path::new("g"))
-                .map(|entry| entry.path.as_ref())
-                .collect::<Vec<_>>(),
-            Vec::<PathBuf>::new()
-        );
-        assert_eq!(
-            tree.descendent_entries(true, false, Path::new("g"))
-                .map(|entry| entry.path.as_ref())
-                .collect::<Vec<_>>(),
-            vec![Path::new("g"), Path::new("g/h"),]
-        );
-    });
-
-    // Expand gitignored directory.
-    tree.read_with(cx, |tree, _| {
-        tree.as_local()
-            .unwrap()
-            .refresh_entries_for_paths(vec![Path::new("i/j").into()])
-    })
-    .recv()
-    .await;
-
-    tree.read_with(cx, |tree, _| {
-        assert_eq!(
-            tree.descendent_entries(false, false, Path::new("i"))
-                .map(|entry| entry.path.as_ref())
-                .collect::<Vec<_>>(),
-            Vec::<PathBuf>::new()
-        );
-        assert_eq!(
-            tree.descendent_entries(false, true, Path::new("i"))
-                .map(|entry| entry.path.as_ref())
-                .collect::<Vec<_>>(),
-            vec![Path::new("i/j/k")]
-        );
-        assert_eq!(
-            tree.descendent_entries(true, false, Path::new("i"))
-                .map(|entry| entry.path.as_ref())
-                .collect::<Vec<_>>(),
-            vec![Path::new("i"), Path::new("i/l"),]
-        );
-    })
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_circular_symlinks(cx: &mut TestAppContext) {
-    init_test(cx);
-    let fs = FakeFs::new(cx.background_executor.clone());
-    fs.insert_tree(
-        "/root",
-        json!({
-            "lib": {
-                "a": {
-                    "a.txt": ""
-                },
-                "b": {
-                    "b.txt": ""
-                }
-            }
-        }),
-    )
-    .await;
-    fs.insert_symlink("/root/lib/a/lib", "..".into()).await;
-    fs.insert_symlink("/root/lib/b/lib", "..".into()).await;
-
-    let tree = Worktree::local(
-        build_client(cx),
-        Path::new("/root"),
-        true,
-        fs.clone(),
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-
-    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-        .await;
-
-    tree.read_with(cx, |tree, _| {
-        assert_eq!(
-            tree.entries(false)
-                .map(|entry| entry.path.as_ref())
-                .collect::<Vec<_>>(),
-            vec![
-                Path::new(""),
-                Path::new("lib"),
-                Path::new("lib/a"),
-                Path::new("lib/a/a.txt"),
-                Path::new("lib/a/lib"),
-                Path::new("lib/b"),
-                Path::new("lib/b/b.txt"),
-                Path::new("lib/b/lib"),
-            ]
-        );
-    });
-
-    fs.rename(
-        Path::new("/root/lib/a/lib"),
-        Path::new("/root/lib/a/lib-2"),
-        Default::default(),
-    )
-    .await
-    .unwrap();
-    cx.executor().run_until_parked();
-    tree.read_with(cx, |tree, _| {
-        assert_eq!(
-            tree.entries(false)
-                .map(|entry| entry.path.as_ref())
-                .collect::<Vec<_>>(),
-            vec![
-                Path::new(""),
-                Path::new("lib"),
-                Path::new("lib/a"),
-                Path::new("lib/a/a.txt"),
-                Path::new("lib/a/lib-2"),
-                Path::new("lib/b"),
-                Path::new("lib/b/b.txt"),
-                Path::new("lib/b/lib"),
-            ]
-        );
-    });
-}
-
-#[gpui::test]
-async fn test_symlinks_pointing_outside(cx: &mut TestAppContext) {
-    init_test(cx);
-    let fs = FakeFs::new(cx.background_executor.clone());
-    fs.insert_tree(
-        "/root",
-        json!({
-            "dir1": {
-                "deps": {
-                    // symlinks here
-                },
-                "src": {
-                    "a.rs": "",
-                    "b.rs": "",
-                },
-            },
-            "dir2": {
-                "src": {
-                    "c.rs": "",
-                    "d.rs": "",
-                }
-            },
-            "dir3": {
-                "deps": {},
-                "src": {
-                    "e.rs": "",
-                    "f.rs": "",
-                },
-            }
-        }),
-    )
-    .await;
-
-    // These symlinks point to directories outside of the worktree's root, dir1.
-    fs.insert_symlink("/root/dir1/deps/dep-dir2", "../../dir2".into())
-        .await;
-    fs.insert_symlink("/root/dir1/deps/dep-dir3", "../../dir3".into())
-        .await;
-
-    let tree = Worktree::local(
-        build_client(cx),
-        Path::new("/root/dir1"),
-        true,
-        fs.clone(),
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-
-    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-        .await;
-
-    let tree_updates = Arc::new(Mutex::new(Vec::new()));
-    tree.update(cx, |_, cx| {
-        let tree_updates = tree_updates.clone();
-        cx.subscribe(&tree, move |_, _, event, _| {
-            if let Event::UpdatedEntries(update) = event {
-                tree_updates.lock().extend(
-                    update
-                        .iter()
-                        .map(|(path, _, change)| (path.clone(), *change)),
-                );
-            }
-        })
-        .detach();
-    });
-
-    // The symlinked directories are not scanned by default.
-    tree.read_with(cx, |tree, _| {
-        assert_eq!(
-            tree.entries(true)
-                .map(|entry| (entry.path.as_ref(), entry.is_external))
-                .collect::<Vec<_>>(),
-            vec![
-                (Path::new(""), false),
-                (Path::new("deps"), false),
-                (Path::new("deps/dep-dir2"), true),
-                (Path::new("deps/dep-dir3"), true),
-                (Path::new("src"), false),
-                (Path::new("src/a.rs"), false),
-                (Path::new("src/b.rs"), false),
-            ]
-        );
-
-        assert_eq!(
-            tree.entry_for_path("deps/dep-dir2").unwrap().kind,
-            EntryKind::UnloadedDir
-        );
-    });
-
-    // Expand one of the symlinked directories.
-    tree.read_with(cx, |tree, _| {
-        tree.as_local()
-            .unwrap()
-            .refresh_entries_for_paths(vec![Path::new("deps/dep-dir3").into()])
-    })
-    .recv()
-    .await;
-
-    // The expanded directory's contents are loaded. Subdirectories are
-    // not scanned yet.
-    tree.read_with(cx, |tree, _| {
-        assert_eq!(
-            tree.entries(true)
-                .map(|entry| (entry.path.as_ref(), entry.is_external))
-                .collect::<Vec<_>>(),
-            vec![
-                (Path::new(""), false),
-                (Path::new("deps"), false),
-                (Path::new("deps/dep-dir2"), true),
-                (Path::new("deps/dep-dir3"), true),
-                (Path::new("deps/dep-dir3/deps"), true),
-                (Path::new("deps/dep-dir3/src"), true),
-                (Path::new("src"), false),
-                (Path::new("src/a.rs"), false),
-                (Path::new("src/b.rs"), false),
-            ]
-        );
-    });
-    assert_eq!(
-        mem::take(&mut *tree_updates.lock()),
-        &[
-            (Path::new("deps/dep-dir3").into(), PathChange::Loaded),
-            (Path::new("deps/dep-dir3/deps").into(), PathChange::Loaded),
-            (Path::new("deps/dep-dir3/src").into(), PathChange::Loaded)
-        ]
-    );
-
-    // Expand a subdirectory of one of the symlinked directories.
-    tree.read_with(cx, |tree, _| {
-        tree.as_local()
-            .unwrap()
-            .refresh_entries_for_paths(vec![Path::new("deps/dep-dir3/src").into()])
-    })
-    .recv()
-    .await;
-
-    // The expanded subdirectory's contents are loaded.
-    tree.read_with(cx, |tree, _| {
-        assert_eq!(
-            tree.entries(true)
-                .map(|entry| (entry.path.as_ref(), entry.is_external))
-                .collect::<Vec<_>>(),
-            vec![
-                (Path::new(""), false),
-                (Path::new("deps"), false),
-                (Path::new("deps/dep-dir2"), true),
-                (Path::new("deps/dep-dir3"), true),
-                (Path::new("deps/dep-dir3/deps"), true),
-                (Path::new("deps/dep-dir3/src"), true),
-                (Path::new("deps/dep-dir3/src/e.rs"), true),
-                (Path::new("deps/dep-dir3/src/f.rs"), true),
-                (Path::new("src"), false),
-                (Path::new("src/a.rs"), false),
-                (Path::new("src/b.rs"), false),
-            ]
-        );
-    });
-
-    assert_eq!(
-        mem::take(&mut *tree_updates.lock()),
-        &[
-            (Path::new("deps/dep-dir3/src").into(), PathChange::Loaded),
-            (
-                Path::new("deps/dep-dir3/src/e.rs").into(),
-                PathChange::Loaded
-            ),
-            (
-                Path::new("deps/dep-dir3/src/f.rs").into(),
-                PathChange::Loaded
-            )
-        ]
-    );
-}
-
-#[gpui::test]
-async fn test_open_gitignored_files(cx: &mut TestAppContext) {
-    init_test(cx);
-    let fs = FakeFs::new(cx.background_executor.clone());
-    fs.insert_tree(
-        "/root",
-        json!({
-            ".gitignore": "node_modules\n",
-            "one": {
-                "node_modules": {
-                    "a": {
-                        "a1.js": "a1",
-                        "a2.js": "a2",
-                    },
-                    "b": {
-                        "b1.js": "b1",
-                        "b2.js": "b2",
-                    },
-                    "c": {
-                        "c1.js": "c1",
-                        "c2.js": "c2",
-                    }
-                },
-            },
-            "two": {
-                "x.js": "",
-                "y.js": "",
-            },
-        }),
-    )
-    .await;
-
-    let tree = Worktree::local(
-        build_client(cx),
-        Path::new("/root"),
-        true,
-        fs.clone(),
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-
-    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-        .await;
-
-    tree.read_with(cx, |tree, _| {
-        assert_eq!(
-            tree.entries(true)
-                .map(|entry| (entry.path.as_ref(), entry.is_ignored))
-                .collect::<Vec<_>>(),
-            vec![
-                (Path::new(""), false),
-                (Path::new(".gitignore"), false),
-                (Path::new("one"), false),
-                (Path::new("one/node_modules"), true),
-                (Path::new("two"), false),
-                (Path::new("two/x.js"), false),
-                (Path::new("two/y.js"), false),
-            ]
-        );
-    });
-
-    // Open a file that is nested inside of a gitignored directory that
-    // has not yet been expanded.
-    let prev_read_dir_count = fs.read_dir_call_count();
-    let buffer = tree
-        .update(cx, |tree, cx| {
-            tree.as_local_mut()
-                .unwrap()
-                .load_buffer(0, "one/node_modules/b/b1.js".as_ref(), cx)
-        })
-        .await
-        .unwrap();
-
-    tree.read_with(cx, |tree, cx| {
-        assert_eq!(
-            tree.entries(true)
-                .map(|entry| (entry.path.as_ref(), entry.is_ignored))
-                .collect::<Vec<_>>(),
-            vec![
-                (Path::new(""), false),
-                (Path::new(".gitignore"), false),
-                (Path::new("one"), false),
-                (Path::new("one/node_modules"), true),
-                (Path::new("one/node_modules/a"), true),
-                (Path::new("one/node_modules/b"), true),
-                (Path::new("one/node_modules/b/b1.js"), true),
-                (Path::new("one/node_modules/b/b2.js"), true),
-                (Path::new("one/node_modules/c"), true),
-                (Path::new("two"), false),
-                (Path::new("two/x.js"), false),
-                (Path::new("two/y.js"), false),
-            ]
-        );
-
-        assert_eq!(
-            buffer.read(cx).file().unwrap().path().as_ref(),
-            Path::new("one/node_modules/b/b1.js")
-        );
-
-        // Only the newly-expanded directories are scanned.
-        assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 2);
-    });
-
-    // Open another file in a different subdirectory of the same
-    // gitignored directory.
-    let prev_read_dir_count = fs.read_dir_call_count();
-    let buffer = tree
-        .update(cx, |tree, cx| {
-            tree.as_local_mut()
-                .unwrap()
-                .load_buffer(0, "one/node_modules/a/a2.js".as_ref(), cx)
-        })
-        .await
-        .unwrap();
-
-    tree.read_with(cx, |tree, cx| {
-        assert_eq!(
-            tree.entries(true)
-                .map(|entry| (entry.path.as_ref(), entry.is_ignored))
-                .collect::<Vec<_>>(),
-            vec![
-                (Path::new(""), false),
-                (Path::new(".gitignore"), false),
-                (Path::new("one"), false),
-                (Path::new("one/node_modules"), true),
-                (Path::new("one/node_modules/a"), true),
-                (Path::new("one/node_modules/a/a1.js"), true),
-                (Path::new("one/node_modules/a/a2.js"), true),
-                (Path::new("one/node_modules/b"), true),
-                (Path::new("one/node_modules/b/b1.js"), true),
-                (Path::new("one/node_modules/b/b2.js"), true),
-                (Path::new("one/node_modules/c"), true),
-                (Path::new("two"), false),
-                (Path::new("two/x.js"), false),
-                (Path::new("two/y.js"), false),
-            ]
-        );
-
-        assert_eq!(
-            buffer.read(cx).file().unwrap().path().as_ref(),
-            Path::new("one/node_modules/a/a2.js")
-        );
-
-        // Only the newly-expanded directory is scanned.
-        assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 1);
-    });
-
-    // No work happens when files and directories change within an unloaded directory.
-    let prev_fs_call_count = fs.read_dir_call_count() + fs.metadata_call_count();
-    fs.create_dir("/root/one/node_modules/c/lib".as_ref())
-        .await
-        .unwrap();
-    cx.executor().run_until_parked();
-    assert_eq!(
-        fs.read_dir_call_count() + fs.metadata_call_count() - prev_fs_call_count,
-        0
-    );
-}
-
-#[gpui::test]
-async fn test_dirs_no_longer_ignored(cx: &mut TestAppContext) {
-    init_test(cx);
-    let fs = FakeFs::new(cx.background_executor.clone());
-    fs.insert_tree(
-        "/root",
-        json!({
-            ".gitignore": "node_modules\n",
-            "a": {
-                "a.js": "",
-            },
-            "b": {
-                "b.js": "",
-            },
-            "node_modules": {
-                "c": {
-                    "c.js": "",
-                },
-                "d": {
-                    "d.js": "",
-                    "e": {
-                        "e1.js": "",
-                        "e2.js": "",
-                    },
-                    "f": {
-                        "f1.js": "",
-                        "f2.js": "",
-                    }
-                },
-            },
-        }),
-    )
-    .await;
-
-    let tree = Worktree::local(
-        build_client(cx),
-        Path::new("/root"),
-        true,
-        fs.clone(),
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-
-    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-        .await;
-
-    // Open a file within the gitignored directory, forcing some of its
-    // subdirectories to be read, but not all.
-    let read_dir_count_1 = fs.read_dir_call_count();
-    tree.read_with(cx, |tree, _| {
-        tree.as_local()
-            .unwrap()
-            .refresh_entries_for_paths(vec![Path::new("node_modules/d/d.js").into()])
-    })
-    .recv()
-    .await;
-
-    // Those subdirectories are now loaded.
-    tree.read_with(cx, |tree, _| {
-        assert_eq!(
-            tree.entries(true)
-                .map(|e| (e.path.as_ref(), e.is_ignored))
-                .collect::<Vec<_>>(),
-            &[
-                (Path::new(""), false),
-                (Path::new(".gitignore"), false),
-                (Path::new("a"), false),
-                (Path::new("a/a.js"), false),
-                (Path::new("b"), false),
-                (Path::new("b/b.js"), false),
-                (Path::new("node_modules"), true),
-                (Path::new("node_modules/c"), true),
-                (Path::new("node_modules/d"), true),
-                (Path::new("node_modules/d/d.js"), true),
-                (Path::new("node_modules/d/e"), true),
-                (Path::new("node_modules/d/f"), true),
-            ]
-        );
-    });
-    let read_dir_count_2 = fs.read_dir_call_count();
-    assert_eq!(read_dir_count_2 - read_dir_count_1, 2);
-
-    // Update the gitignore so that node_modules is no longer ignored,
-    // but a subdirectory is ignored
-    fs.save("/root/.gitignore".as_ref(), &"e".into(), Default::default())
-        .await
-        .unwrap();
-    cx.executor().run_until_parked();
-
-    // All of the directories that are no longer ignored are now loaded.
-    tree.read_with(cx, |tree, _| {
-        assert_eq!(
-            tree.entries(true)
-                .map(|e| (e.path.as_ref(), e.is_ignored))
-                .collect::<Vec<_>>(),
-            &[
-                (Path::new(""), false),
-                (Path::new(".gitignore"), false),
-                (Path::new("a"), false),
-                (Path::new("a/a.js"), false),
-                (Path::new("b"), false),
-                (Path::new("b/b.js"), false),
-                // This directory is no longer ignored
-                (Path::new("node_modules"), false),
-                (Path::new("node_modules/c"), false),
-                (Path::new("node_modules/c/c.js"), false),
-                (Path::new("node_modules/d"), false),
-                (Path::new("node_modules/d/d.js"), false),
-                // This subdirectory is now ignored
-                (Path::new("node_modules/d/e"), true),
-                (Path::new("node_modules/d/f"), false),
-                (Path::new("node_modules/d/f/f1.js"), false),
-                (Path::new("node_modules/d/f/f2.js"), false),
-            ]
-        );
-    });
-
-    // Each of the newly-loaded directories is scanned only once.
-    let read_dir_count_3 = fs.read_dir_call_count();
-    assert_eq!(read_dir_count_3 - read_dir_count_2, 2);
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
-    init_test(cx);
-    cx.update(|cx| {
-        cx.update_global::<SettingsStore, _>(|store, cx| {
-            store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
-                project_settings.file_scan_exclusions = Some(Vec::new());
-            });
-        });
-    });
-    let fs = FakeFs::new(cx.background_executor.clone());
-    fs.insert_tree(
-        "/root",
-        json!({
-            ".gitignore": "ancestor-ignored-file1\nancestor-ignored-file2\n",
-            "tree": {
-                ".git": {},
-                ".gitignore": "ignored-dir\n",
-                "tracked-dir": {
-                    "tracked-file1": "",
-                    "ancestor-ignored-file1": "",
-                },
-                "ignored-dir": {
-                    "ignored-file1": ""
-                }
-            }
-        }),
-    )
-    .await;
-
-    let tree = Worktree::local(
-        build_client(cx),
-        "/root/tree".as_ref(),
-        true,
-        fs.clone(),
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-        .await;
-
-    tree.read_with(cx, |tree, _| {
-        tree.as_local()
-            .unwrap()
-            .refresh_entries_for_paths(vec![Path::new("ignored-dir").into()])
-    })
-    .recv()
-    .await;
-
-    cx.read(|cx| {
-        let tree = tree.read(cx);
-        assert!(
-            !tree
-                .entry_for_path("tracked-dir/tracked-file1")
-                .unwrap()
-                .is_ignored
-        );
-        assert!(
-            tree.entry_for_path("tracked-dir/ancestor-ignored-file1")
-                .unwrap()
-                .is_ignored
-        );
-        assert!(
-            tree.entry_for_path("ignored-dir/ignored-file1")
-                .unwrap()
-                .is_ignored
-        );
-    });
-
-    fs.create_file(
-        "/root/tree/tracked-dir/tracked-file2".as_ref(),
-        Default::default(),
-    )
-    .await
-    .unwrap();
-    fs.create_file(
-        "/root/tree/tracked-dir/ancestor-ignored-file2".as_ref(),
-        Default::default(),
-    )
-    .await
-    .unwrap();
-    fs.create_file(
-        "/root/tree/ignored-dir/ignored-file2".as_ref(),
-        Default::default(),
-    )
-    .await
-    .unwrap();
-
-    cx.executor().run_until_parked();
-    cx.read(|cx| {
-        let tree = tree.read(cx);
-        assert!(
-            !tree
-                .entry_for_path("tracked-dir/tracked-file2")
-                .unwrap()
-                .is_ignored
-        );
-        assert!(
-            tree.entry_for_path("tracked-dir/ancestor-ignored-file2")
-                .unwrap()
-                .is_ignored
-        );
-        assert!(
-            tree.entry_for_path("ignored-dir/ignored-file2")
-                .unwrap()
-                .is_ignored
-        );
-        assert!(tree.entry_for_path(".git").unwrap().is_ignored);
-    });
-}
-
-#[gpui::test]
-async fn test_write_file(cx: &mut TestAppContext) {
-    init_test(cx);
-    cx.executor().allow_parking();
-    let dir = temp_tree(json!({
-        ".git": {},
-        ".gitignore": "ignored-dir\n",
-        "tracked-dir": {},
-        "ignored-dir": {}
-    }));
-
-    let tree = Worktree::local(
-        build_client(cx),
-        dir.path(),
-        true,
-        Arc::new(RealFs),
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-        .await;
-    tree.flush_fs_events(cx).await;
-
-    tree.update(cx, |tree, cx| {
-        tree.as_local().unwrap().write_file(
-            Path::new("tracked-dir/file.txt"),
-            "hello".into(),
-            Default::default(),
-            cx,
-        )
-    })
-    .await
-    .unwrap();
-    tree.update(cx, |tree, cx| {
-        tree.as_local().unwrap().write_file(
-            Path::new("ignored-dir/file.txt"),
-            "world".into(),
-            Default::default(),
-            cx,
-        )
-    })
-    .await
-    .unwrap();
-
-    tree.read_with(cx, |tree, _| {
-        let tracked = tree.entry_for_path("tracked-dir/file.txt").unwrap();
-        let ignored = tree.entry_for_path("ignored-dir/file.txt").unwrap();
-        assert!(!tracked.is_ignored);
-        assert!(ignored.is_ignored);
-    });
-}
-
-#[gpui::test]
-async fn test_file_scan_exclusions(cx: &mut TestAppContext) {
-    init_test(cx);
-    cx.executor().allow_parking();
-    let dir = temp_tree(json!({
-        ".gitignore": "**/target\n/node_modules\n",
-        "target": {
-            "index": "blah2"
-        },
-        "node_modules": {
-            ".DS_Store": "",
-            "prettier": {
-                "package.json": "{}",
-            },
-        },
-        "src": {
-            ".DS_Store": "",
-            "foo": {
-                "foo.rs": "mod another;\n",
-                "another.rs": "// another",
-            },
-            "bar": {
-                "bar.rs": "// bar",
-            },
-            "lib.rs": "mod foo;\nmod bar;\n",
-        },
-        ".DS_Store": "",
-    }));
-    cx.update(|cx| {
-        cx.update_global::<SettingsStore, _>(|store, cx| {
-            store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
-                project_settings.file_scan_exclusions =
-                    Some(vec!["**/foo/**".to_string(), "**/.DS_Store".to_string()]);
-            });
-        });
-    });
-
-    let tree = Worktree::local(
-        build_client(cx),
-        dir.path(),
-        true,
-        Arc::new(RealFs),
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-        .await;
-    tree.flush_fs_events(cx).await;
-    tree.read_with(cx, |tree, _| {
-        check_worktree_entries(
-            tree,
-            &[
-                "src/foo/foo.rs",
-                "src/foo/another.rs",
-                "node_modules/.DS_Store",
-                "src/.DS_Store",
-                ".DS_Store",
-            ],
-            &["target", "node_modules"],
-            &["src/lib.rs", "src/bar/bar.rs", ".gitignore"],
-        )
-    });
-
-    cx.update(|cx| {
-        cx.update_global::<SettingsStore, _>(|store, cx| {
-            store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
-                project_settings.file_scan_exclusions =
-                    Some(vec!["**/node_modules/**".to_string()]);
-            });
-        });
-    });
-    tree.flush_fs_events(cx).await;
-    cx.executor().run_until_parked();
-    tree.read_with(cx, |tree, _| {
-        check_worktree_entries(
-            tree,
-            &[
-                "node_modules/prettier/package.json",
-                "node_modules/.DS_Store",
-                "node_modules",
-            ],
-            &["target"],
-            &[
-                ".gitignore",
-                "src/lib.rs",
-                "src/bar/bar.rs",
-                "src/foo/foo.rs",
-                "src/foo/another.rs",
-                "src/.DS_Store",
-                ".DS_Store",
-            ],
-        )
-    });
-}
-
-#[gpui::test]
-async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) {
-    init_test(cx);
-    cx.executor().allow_parking();
-    let dir = temp_tree(json!({
-        ".git": {
-            "HEAD": "ref: refs/heads/main\n",
-            "foo": "bar",
-        },
-        ".gitignore": "**/target\n/node_modules\ntest_output\n",
-        "target": {
-            "index": "blah2"
-        },
-        "node_modules": {
-            ".DS_Store": "",
-            "prettier": {
-                "package.json": "{}",
-            },
-        },
-        "src": {
-            ".DS_Store": "",
-            "foo": {
-                "foo.rs": "mod another;\n",
-                "another.rs": "// another",
-            },
-            "bar": {
-                "bar.rs": "// bar",
-            },
-            "lib.rs": "mod foo;\nmod bar;\n",
-        },
-        ".DS_Store": "",
-    }));
-    cx.update(|cx| {
-        cx.update_global::<SettingsStore, _>(|store, cx| {
-            store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
-                project_settings.file_scan_exclusions = Some(vec![
-                    "**/.git".to_string(),
-                    "node_modules/".to_string(),
-                    "build_output".to_string(),
-                ]);
-            });
-        });
-    });
-
-    let tree = Worktree::local(
-        build_client(cx),
-        dir.path(),
-        true,
-        Arc::new(RealFs),
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-        .await;
-    tree.flush_fs_events(cx).await;
-    tree.read_with(cx, |tree, _| {
-        check_worktree_entries(
-            tree,
-            &[
-                ".git/HEAD",
-                ".git/foo",
-                "node_modules",
-                "node_modules/.DS_Store",
-                "node_modules/prettier",
-                "node_modules/prettier/package.json",
-            ],
-            &["target"],
-            &[
-                ".DS_Store",
-                "src/.DS_Store",
-                "src/lib.rs",
-                "src/foo/foo.rs",
-                "src/foo/another.rs",
-                "src/bar/bar.rs",
-                ".gitignore",
-            ],
-        )
-    });
-
-    let new_excluded_dir = dir.path().join("build_output");
-    let new_ignored_dir = dir.path().join("test_output");
-    std::fs::create_dir_all(&new_excluded_dir)
-        .unwrap_or_else(|e| panic!("Failed to create a {new_excluded_dir:?} directory: {e}"));
-    std::fs::create_dir_all(&new_ignored_dir)
-        .unwrap_or_else(|e| panic!("Failed to create a {new_ignored_dir:?} directory: {e}"));
-    let node_modules_dir = dir.path().join("node_modules");
-    let dot_git_dir = dir.path().join(".git");
-    let src_dir = dir.path().join("src");
-    for existing_dir in [&node_modules_dir, &dot_git_dir, &src_dir] {
-        assert!(
-            existing_dir.is_dir(),
-            "Expect {existing_dir:?} to be present in the FS already"
-        );
-    }
-
-    for directory_for_new_file in [
-        new_excluded_dir,
-        new_ignored_dir,
-        node_modules_dir,
-        dot_git_dir,
-        src_dir,
-    ] {
-        std::fs::write(directory_for_new_file.join("new_file"), "new file contents")
-            .unwrap_or_else(|e| {
-                panic!("Failed to create in {directory_for_new_file:?} a new file: {e}")
-            });
-    }
-    tree.flush_fs_events(cx).await;
-
-    tree.read_with(cx, |tree, _| {
-        check_worktree_entries(
-            tree,
-            &[
-                ".git/HEAD",
-                ".git/foo",
-                ".git/new_file",
-                "node_modules",
-                "node_modules/.DS_Store",
-                "node_modules/prettier",
-                "node_modules/prettier/package.json",
-                "node_modules/new_file",
-                "build_output",
-                "build_output/new_file",
-                "test_output/new_file",
-            ],
-            &["target", "test_output"],
-            &[
-                ".DS_Store",
-                "src/.DS_Store",
-                "src/lib.rs",
-                "src/foo/foo.rs",
-                "src/foo/another.rs",
-                "src/bar/bar.rs",
-                "src/new_file",
-                ".gitignore",
-            ],
-        )
-    });
-}
-
-#[gpui::test(iterations = 30)]
-async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
-    init_test(cx);
-    let fs = FakeFs::new(cx.background_executor.clone());
-    fs.insert_tree(
-        "/root",
-        json!({
-            "b": {},
-            "c": {},
-            "d": {},
-        }),
-    )
-    .await;
-
-    let tree = Worktree::local(
-        build_client(cx),
-        "/root".as_ref(),
-        true,
-        fs,
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-
-    let snapshot1 = tree.update(cx, |tree, cx| {
-        let tree = tree.as_local_mut().unwrap();
-        let snapshot = Arc::new(Mutex::new(tree.snapshot()));
-        let _ = tree.observe_updates(0, cx, {
-            let snapshot = snapshot.clone();
-            move |update| {
-                snapshot.lock().apply_remote_update(update).unwrap();
-                async { true }
-            }
-        });
-        snapshot
-    });
-
-    let entry = tree
-        .update(cx, |tree, cx| {
-            tree.as_local_mut()
-                .unwrap()
-                .create_entry("a/e".as_ref(), true, cx)
-        })
-        .await
-        .unwrap()
-        .unwrap();
-    assert!(entry.is_dir());
-
-    cx.executor().run_until_parked();
-    tree.read_with(cx, |tree, _| {
-        assert_eq!(tree.entry_for_path("a/e").unwrap().kind, EntryKind::Dir);
-    });
-
-    let snapshot2 = tree.update(cx, |tree, _| tree.as_local().unwrap().snapshot());
-    assert_eq!(
-        snapshot1.lock().entries(true).collect::<Vec<_>>(),
-        snapshot2.entries(true).collect::<Vec<_>>()
-    );
-}
-
-#[gpui::test]
-async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
-    init_test(cx);
-    cx.executor().allow_parking();
-    let client_fake = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
-
-    let fs_fake = FakeFs::new(cx.background_executor.clone());
-    fs_fake
-        .insert_tree(
-            "/root",
-            json!({
-                "a": {},
-            }),
-        )
-        .await;
-
-    let tree_fake = Worktree::local(
-        client_fake,
-        "/root".as_ref(),
-        true,
-        fs_fake,
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-
-    let entry = tree_fake
-        .update(cx, |tree, cx| {
-            tree.as_local_mut()
-                .unwrap()
-                .create_entry("a/b/c/d.txt".as_ref(), false, cx)
-        })
-        .await
-        .unwrap()
-        .unwrap();
-    assert!(entry.is_file());
-
-    cx.executor().run_until_parked();
-    tree_fake.read_with(cx, |tree, _| {
-        assert!(tree.entry_for_path("a/b/c/d.txt").unwrap().is_file());
-        assert!(tree.entry_for_path("a/b/c/").unwrap().is_dir());
-        assert!(tree.entry_for_path("a/b/").unwrap().is_dir());
-    });
-
-    let client_real = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
-
-    let fs_real = Arc::new(RealFs);
-    let temp_root = temp_tree(json!({
-        "a": {}
-    }));
-
-    let tree_real = Worktree::local(
-        client_real,
-        temp_root.path(),
-        true,
-        fs_real,
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-
-    let entry = tree_real
-        .update(cx, |tree, cx| {
-            tree.as_local_mut()
-                .unwrap()
-                .create_entry("a/b/c/d.txt".as_ref(), false, cx)
-        })
-        .await
-        .unwrap()
-        .unwrap();
-    assert!(entry.is_file());
-
-    cx.executor().run_until_parked();
-    tree_real.read_with(cx, |tree, _| {
-        assert!(tree.entry_for_path("a/b/c/d.txt").unwrap().is_file());
-        assert!(tree.entry_for_path("a/b/c/").unwrap().is_dir());
-        assert!(tree.entry_for_path("a/b/").unwrap().is_dir());
-    });
-
-    // Test smallest change
-    let entry = tree_real
-        .update(cx, |tree, cx| {
-            tree.as_local_mut()
-                .unwrap()
-                .create_entry("a/b/c/e.txt".as_ref(), false, cx)
-        })
-        .await
-        .unwrap()
-        .unwrap();
-    assert!(entry.is_file());
-
-    cx.executor().run_until_parked();
-    tree_real.read_with(cx, |tree, _| {
-        assert!(tree.entry_for_path("a/b/c/e.txt").unwrap().is_file());
-    });
-
-    // Test largest change
-    let entry = tree_real
-        .update(cx, |tree, cx| {
-            tree.as_local_mut()
-                .unwrap()
-                .create_entry("d/e/f/g.txt".as_ref(), false, cx)
-        })
-        .await
-        .unwrap()
-        .unwrap();
-    assert!(entry.is_file());
-
-    cx.executor().run_until_parked();
-    tree_real.read_with(cx, |tree, _| {
-        assert!(tree.entry_for_path("d/e/f/g.txt").unwrap().is_file());
-        assert!(tree.entry_for_path("d/e/f").unwrap().is_dir());
-        assert!(tree.entry_for_path("d/e/").unwrap().is_dir());
-        assert!(tree.entry_for_path("d/").unwrap().is_dir());
-    });
-}
-
-#[gpui::test(iterations = 100)]
-async fn test_random_worktree_operations_during_initial_scan(
-    cx: &mut TestAppContext,
-    mut rng: StdRng,
-) {
-    init_test(cx);
-    let operations = env::var("OPERATIONS")
-        .map(|o| o.parse().unwrap())
-        .unwrap_or(5);
-    let initial_entries = env::var("INITIAL_ENTRIES")
-        .map(|o| o.parse().unwrap())
-        .unwrap_or(20);
-
-    let root_dir = Path::new("/test");
-    let fs = FakeFs::new(cx.background_executor.clone()) as Arc<dyn Fs>;
-    fs.as_fake().insert_tree(root_dir, json!({})).await;
-    for _ in 0..initial_entries {
-        randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await;
-    }
-    log::info!("generated initial tree");
-
-    let worktree = Worktree::local(
-        build_client(cx),
-        root_dir,
-        true,
-        fs.clone(),
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-
-    let mut snapshots = vec![worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot())];
-    let updates = Arc::new(Mutex::new(Vec::new()));
-    worktree.update(cx, |tree, cx| {
-        check_worktree_change_events(tree, cx);
-
-        let _ = tree.as_local_mut().unwrap().observe_updates(0, cx, {
-            let updates = updates.clone();
-            move |update| {
-                updates.lock().push(update);
-                async { true }
-            }
-        });
-    });
-
-    for _ in 0..operations {
-        worktree
-            .update(cx, |worktree, cx| {
-                randomly_mutate_worktree(worktree, &mut rng, cx)
-            })
-            .await
-            .log_err();
-        worktree.read_with(cx, |tree, _| {
-            tree.as_local().unwrap().snapshot().check_invariants(true)
-        });
-
-        if rng.gen_bool(0.6) {
-            snapshots.push(worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot()));
-        }
-    }
-
-    worktree
-        .update(cx, |tree, _| tree.as_local_mut().unwrap().scan_complete())
-        .await;
-
-    cx.executor().run_until_parked();
-
-    let final_snapshot = worktree.read_with(cx, |tree, _| {
-        let tree = tree.as_local().unwrap();
-        let snapshot = tree.snapshot();
-        snapshot.check_invariants(true);
-        snapshot
-    });
-
-    for (i, snapshot) in snapshots.into_iter().enumerate().rev() {
-        let mut updated_snapshot = snapshot.clone();
-        for update in updates.lock().iter() {
-            if update.scan_id >= updated_snapshot.scan_id() as u64 {
-                updated_snapshot
-                    .apply_remote_update(update.clone())
-                    .unwrap();
-            }
-        }
-
-        assert_eq!(
-            updated_snapshot.entries(true).collect::<Vec<_>>(),
-            final_snapshot.entries(true).collect::<Vec<_>>(),
-            "wrong updates after snapshot {i}: {snapshot:#?} {updates:#?}",
-        );
-    }
-}
-
-#[gpui::test(iterations = 100)]
-async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng) {
-    init_test(cx);
-    let operations = env::var("OPERATIONS")
-        .map(|o| o.parse().unwrap())
-        .unwrap_or(40);
-    let initial_entries = env::var("INITIAL_ENTRIES")
-        .map(|o| o.parse().unwrap())
-        .unwrap_or(20);
-
-    let root_dir = Path::new("/test");
-    let fs = FakeFs::new(cx.background_executor.clone()) as Arc<dyn Fs>;
-    fs.as_fake().insert_tree(root_dir, json!({})).await;
-    for _ in 0..initial_entries {
-        randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await;
-    }
-    log::info!("generated initial tree");
-
-    let worktree = Worktree::local(
-        build_client(cx),
-        root_dir,
-        true,
-        fs.clone(),
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-
-    let updates = Arc::new(Mutex::new(Vec::new()));
-    worktree.update(cx, |tree, cx| {
-        check_worktree_change_events(tree, cx);
-
-        let _ = tree.as_local_mut().unwrap().observe_updates(0, cx, {
-            let updates = updates.clone();
-            move |update| {
-                updates.lock().push(update);
-                async { true }
-            }
-        });
-    });
-
-    worktree
-        .update(cx, |tree, _| tree.as_local_mut().unwrap().scan_complete())
-        .await;
-
-    fs.as_fake().pause_events();
-    let mut snapshots = Vec::new();
-    let mut mutations_len = operations;
-    while mutations_len > 1 {
-        if rng.gen_bool(0.2) {
-            worktree
-                .update(cx, |worktree, cx| {
-                    randomly_mutate_worktree(worktree, &mut rng, cx)
-                })
-                .await
-                .log_err();
-        } else {
-            randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await;
-        }
-
-        let buffered_event_count = fs.as_fake().buffered_event_count();
-        if buffered_event_count > 0 && rng.gen_bool(0.3) {
-            let len = rng.gen_range(0..=buffered_event_count);
-            log::info!("flushing {} events", len);
-            fs.as_fake().flush_events(len);
-        } else {
-            randomly_mutate_fs(&fs, root_dir, 0.6, &mut rng).await;
-            mutations_len -= 1;
-        }
-
-        cx.executor().run_until_parked();
-        if rng.gen_bool(0.2) {
-            log::info!("storing snapshot {}", snapshots.len());
-            let snapshot = worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
-            snapshots.push(snapshot);
-        }
-    }
-
-    log::info!("quiescing");
-    fs.as_fake().flush_events(usize::MAX);
-    cx.executor().run_until_parked();
-
-    let snapshot = worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
-    snapshot.check_invariants(true);
-    let expanded_paths = snapshot
-        .expanded_entries()
-        .map(|e| e.path.clone())
-        .collect::<Vec<_>>();
-
-    {
-        let new_worktree = Worktree::local(
-            build_client(cx),
-            root_dir,
-            true,
-            fs.clone(),
-            Default::default(),
-            &mut cx.to_async(),
-        )
-        .await
-        .unwrap();
-        new_worktree
-            .update(cx, |tree, _| tree.as_local_mut().unwrap().scan_complete())
-            .await;
-        new_worktree
-            .update(cx, |tree, _| {
-                tree.as_local_mut()
-                    .unwrap()
-                    .refresh_entries_for_paths(expanded_paths)
-            })
-            .recv()
-            .await;
-        let new_snapshot =
-            new_worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
-        assert_eq!(
-            snapshot.entries_without_ids(true),
-            new_snapshot.entries_without_ids(true)
-        );
-    }
-
-    for (i, mut prev_snapshot) in snapshots.into_iter().enumerate().rev() {
-        for update in updates.lock().iter() {
-            if update.scan_id >= prev_snapshot.scan_id() as u64 {
-                prev_snapshot.apply_remote_update(update.clone()).unwrap();
-            }
-        }
-
-        assert_eq!(
-            prev_snapshot
-                .entries(true)
-                .map(ignore_pending_dir)
-                .collect::<Vec<_>>(),
-            snapshot
-                .entries(true)
-                .map(ignore_pending_dir)
-                .collect::<Vec<_>>(),
-            "wrong updates after snapshot {i}: {updates:#?}",
-        );
-    }
-
-    fn ignore_pending_dir(entry: &Entry) -> Entry {
-        let mut entry = entry.clone();
-        if entry.kind.is_dir() {
-            entry.kind = EntryKind::Dir
-        }
-        entry
-    }
-}
-
-// The worktree's `UpdatedEntries` event can be used to follow along with
-// all changes to the worktree's snapshot.
-fn check_worktree_change_events(tree: &mut Worktree, cx: &mut ModelContext<Worktree>) {
-    let mut entries = tree.entries(true).cloned().collect::<Vec<_>>();
-    cx.subscribe(&cx.handle(), move |tree, _, event, _| {
-        if let Event::UpdatedEntries(changes) = event {
-            for (path, _, change_type) in changes.iter() {
-                let entry = tree.entry_for_path(&path).cloned();
-                let ix = match entries.binary_search_by_key(&path, |e| &e.path) {
-                    Ok(ix) | Err(ix) => ix,
-                };
-                match change_type {
-                    PathChange::Added => entries.insert(ix, entry.unwrap()),
-                    PathChange::Removed => drop(entries.remove(ix)),
-                    PathChange::Updated => {
-                        let entry = entry.unwrap();
-                        let existing_entry = entries.get_mut(ix).unwrap();
-                        assert_eq!(existing_entry.path, entry.path);
-                        *existing_entry = entry;
-                    }
-                    PathChange::AddedOrUpdated | PathChange::Loaded => {
-                        let entry = entry.unwrap();
-                        if entries.get(ix).map(|e| &e.path) == Some(&entry.path) {
-                            *entries.get_mut(ix).unwrap() = entry;
-                        } else {
-                            entries.insert(ix, entry);
-                        }
-                    }
-                }
-            }
-
-            let new_entries = tree.entries(true).cloned().collect::<Vec<_>>();
-            assert_eq!(entries, new_entries, "incorrect changes: {:?}", changes);
-        }
-    })
-    .detach();
-}
-
-fn randomly_mutate_worktree(
-    worktree: &mut Worktree,
-    rng: &mut impl Rng,
-    cx: &mut ModelContext<Worktree>,
-) -> Task<Result<()>> {
-    log::info!("mutating worktree");
-    let worktree = worktree.as_local_mut().unwrap();
-    let snapshot = worktree.snapshot();
-    let entry = snapshot.entries(false).choose(rng).unwrap();
-
-    match rng.gen_range(0_u32..100) {
-        0..=33 if entry.path.as_ref() != Path::new("") => {
-            log::info!("deleting entry {:?} ({})", entry.path, entry.id.0);
-            worktree.delete_entry(entry.id, cx).unwrap()
-        }
-        ..=66 if entry.path.as_ref() != Path::new("") => {
-            let other_entry = snapshot.entries(false).choose(rng).unwrap();
-            let new_parent_path = if other_entry.is_dir() {
-                other_entry.path.clone()
-            } else {
-                other_entry.path.parent().unwrap().into()
-            };
-            let mut new_path = new_parent_path.join(random_filename(rng));
-            if new_path.starts_with(&entry.path) {
-                new_path = random_filename(rng).into();
-            }
-
-            log::info!(
-                "renaming entry {:?} ({}) to {:?}",
-                entry.path,
-                entry.id.0,
-                new_path
-            );
-            let task = worktree.rename_entry(entry.id, new_path, cx);
-            cx.background_executor().spawn(async move {
-                task.await?.unwrap();
-                Ok(())
-            })
-        }
-        _ => {
-            if entry.is_dir() {
-                let child_path = entry.path.join(random_filename(rng));
-                let is_dir = rng.gen_bool(0.3);
-                log::info!(
-                    "creating {} at {:?}",
-                    if is_dir { "dir" } else { "file" },
-                    child_path,
-                );
-                let task = worktree.create_entry(child_path, is_dir, cx);
-                cx.background_executor().spawn(async move {
-                    task.await?;
-                    Ok(())
-                })
-            } else {
-                log::info!("overwriting file {:?} ({})", entry.path, entry.id.0);
-                let task =
-                    worktree.write_file(entry.path.clone(), "".into(), Default::default(), cx);
-                cx.background_executor().spawn(async move {
-                    task.await?;
-                    Ok(())
-                })
-            }
-        }
-    }
-}
-
-async fn randomly_mutate_fs(
-    fs: &Arc<dyn Fs>,
-    root_path: &Path,
-    insertion_probability: f64,
-    rng: &mut impl Rng,
-) {
-    log::info!("mutating fs");
-    let mut files = Vec::new();
-    let mut dirs = Vec::new();
-    for path in fs.as_fake().paths(false) {
-        if path.starts_with(root_path) {
-            if fs.is_file(&path).await {
-                files.push(path);
-            } else {
-                dirs.push(path);
-            }
-        }
-    }
-
-    if (files.is_empty() && dirs.len() == 1) || rng.gen_bool(insertion_probability) {
-        let path = dirs.choose(rng).unwrap();
-        let new_path = path.join(random_filename(rng));
-
-        if rng.gen() {
-            log::info!(
-                "creating dir {:?}",
-                new_path.strip_prefix(root_path).unwrap()
-            );
-            fs.create_dir(&new_path).await.unwrap();
-        } else {
-            log::info!(
-                "creating file {:?}",
-                new_path.strip_prefix(root_path).unwrap()
-            );
-            fs.create_file(&new_path, Default::default()).await.unwrap();
-        }
-    } else if rng.gen_bool(0.05) {
-        let ignore_dir_path = dirs.choose(rng).unwrap();
-        let ignore_path = ignore_dir_path.join(&*GITIGNORE);
-
-        let subdirs = dirs
-            .iter()
-            .filter(|d| d.starts_with(&ignore_dir_path))
-            .cloned()
-            .collect::<Vec<_>>();
-        let subfiles = files
-            .iter()
-            .filter(|d| d.starts_with(&ignore_dir_path))
-            .cloned()
-            .collect::<Vec<_>>();
-        let files_to_ignore = {
-            let len = rng.gen_range(0..=subfiles.len());
-            subfiles.choose_multiple(rng, len)
-        };
-        let dirs_to_ignore = {
-            let len = rng.gen_range(0..subdirs.len());
-            subdirs.choose_multiple(rng, len)
-        };
-
-        let mut ignore_contents = String::new();
-        for path_to_ignore in files_to_ignore.chain(dirs_to_ignore) {
-            writeln!(
-                ignore_contents,
-                "{}",
-                path_to_ignore
-                    .strip_prefix(&ignore_dir_path)
-                    .unwrap()
-                    .to_str()
-                    .unwrap()
-            )
-            .unwrap();
-        }
-        log::info!(
-            "creating gitignore {:?} with contents:\n{}",
-            ignore_path.strip_prefix(&root_path).unwrap(),
-            ignore_contents
-        );
-        fs.save(
-            &ignore_path,
-            &ignore_contents.as_str().into(),
-            Default::default(),
-        )
-        .await
-        .unwrap();
-    } else {
-        let old_path = {
-            let file_path = files.choose(rng);
-            let dir_path = dirs[1..].choose(rng);
-            file_path.into_iter().chain(dir_path).choose(rng).unwrap()
-        };
-
-        let is_rename = rng.gen();
-        if is_rename {
-            let new_path_parent = dirs
-                .iter()
-                .filter(|d| !d.starts_with(old_path))
-                .choose(rng)
-                .unwrap();
-
-            let overwrite_existing_dir =
-                !old_path.starts_with(&new_path_parent) && rng.gen_bool(0.3);
-            let new_path = if overwrite_existing_dir {
-                fs.remove_dir(
-                    &new_path_parent,
-                    RemoveOptions {
-                        recursive: true,
-                        ignore_if_not_exists: true,
-                    },
-                )
-                .await
-                .unwrap();
-                new_path_parent.to_path_buf()
-            } else {
-                new_path_parent.join(random_filename(rng))
-            };
-
-            log::info!(
-                "renaming {:?} to {}{:?}",
-                old_path.strip_prefix(&root_path).unwrap(),
-                if overwrite_existing_dir {
-                    "overwrite "
-                } else {
-                    ""
-                },
-                new_path.strip_prefix(&root_path).unwrap()
-            );
-            fs.rename(
-                &old_path,
-                &new_path,
-                fs::RenameOptions {
-                    overwrite: true,
-                    ignore_if_exists: true,
-                },
-            )
-            .await
-            .unwrap();
-        } else if fs.is_file(&old_path).await {
-            log::info!(
-                "deleting file {:?}",
-                old_path.strip_prefix(&root_path).unwrap()
-            );
-            fs.remove_file(old_path, Default::default()).await.unwrap();
-        } else {
-            log::info!(
-                "deleting dir {:?}",
-                old_path.strip_prefix(&root_path).unwrap()
-            );
-            fs.remove_dir(
-                &old_path,
-                RemoveOptions {
-                    recursive: true,
-                    ignore_if_not_exists: true,
-                },
-            )
-            .await
-            .unwrap();
-        }
-    }
-}
-
-fn random_filename(rng: &mut impl Rng) -> String {
-    (0..6)
-        .map(|_| rng.sample(rand::distributions::Alphanumeric))
-        .map(char::from)
-        .collect()
-}
-
-#[gpui::test]
-async fn test_rename_work_directory(cx: &mut TestAppContext) {
-    init_test(cx);
-    cx.executor().allow_parking();
-    let root = temp_tree(json!({
-        "projects": {
-            "project1": {
-                "a": "",
-                "b": "",
-            }
-        },
-
-    }));
-    let root_path = root.path();
-
-    let tree = Worktree::local(
-        build_client(cx),
-        root_path,
-        true,
-        Arc::new(RealFs),
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-
-    let repo = git_init(&root_path.join("projects/project1"));
-    git_add("a", &repo);
-    git_commit("init", &repo);
-    std::fs::write(root_path.join("projects/project1/a"), "aa").ok();
-
-    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-        .await;
-
-    tree.flush_fs_events(cx).await;
-
-    cx.read(|cx| {
-        let tree = tree.read(cx);
-        let (work_dir, _) = tree.repositories().next().unwrap();
-        assert_eq!(work_dir.as_ref(), Path::new("projects/project1"));
-        assert_eq!(
-            tree.status_for_file(Path::new("projects/project1/a")),
-            Some(GitFileStatus::Modified)
-        );
-        assert_eq!(
-            tree.status_for_file(Path::new("projects/project1/b")),
-            Some(GitFileStatus::Added)
-        );
-    });
-
-    std::fs::rename(
-        root_path.join("projects/project1"),
-        root_path.join("projects/project2"),
-    )
-    .ok();
-    tree.flush_fs_events(cx).await;
-
-    cx.read(|cx| {
-        let tree = tree.read(cx);
-        let (work_dir, _) = tree.repositories().next().unwrap();
-        assert_eq!(work_dir.as_ref(), Path::new("projects/project2"));
-        assert_eq!(
-            tree.status_for_file(Path::new("projects/project2/a")),
-            Some(GitFileStatus::Modified)
-        );
-        assert_eq!(
-            tree.status_for_file(Path::new("projects/project2/b")),
-            Some(GitFileStatus::Added)
-        );
-    });
-}
-
-#[gpui::test]
-async fn test_git_repository_for_path(cx: &mut TestAppContext) {
-    init_test(cx);
-    cx.executor().allow_parking();
-    let root = temp_tree(json!({
-        "c.txt": "",
-        "dir1": {
-            ".git": {},
-            "deps": {
-                "dep1": {
-                    ".git": {},
-                    "src": {
-                        "a.txt": ""
-                    }
-                }
-            },
-            "src": {
-                "b.txt": ""
-            }
-        },
-    }));
-
-    let tree = Worktree::local(
-        build_client(cx),
-        root.path(),
-        true,
-        Arc::new(RealFs),
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-
-    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-        .await;
-    tree.flush_fs_events(cx).await;
-
-    tree.read_with(cx, |tree, _cx| {
-        let tree = tree.as_local().unwrap();
-
-        assert!(tree.repository_for_path("c.txt".as_ref()).is_none());
-
-        let entry = tree.repository_for_path("dir1/src/b.txt".as_ref()).unwrap();
-        assert_eq!(
-            entry
-                .work_directory(tree)
-                .map(|directory| directory.as_ref().to_owned()),
-            Some(Path::new("dir1").to_owned())
-        );
-
-        let entry = tree
-            .repository_for_path("dir1/deps/dep1/src/a.txt".as_ref())
-            .unwrap();
-        assert_eq!(
-            entry
-                .work_directory(tree)
-                .map(|directory| directory.as_ref().to_owned()),
-            Some(Path::new("dir1/deps/dep1").to_owned())
-        );
-
-        let entries = tree.files(false, 0);
-
-        let paths_with_repos = tree
-            .entries_with_repositories(entries)
-            .map(|(entry, repo)| {
-                (
-                    entry.path.as_ref(),
-                    repo.and_then(|repo| {
-                        repo.work_directory(&tree)
-                            .map(|work_directory| work_directory.0.to_path_buf())
-                    }),
-                )
-            })
-            .collect::<Vec<_>>();
-
-        assert_eq!(
-            paths_with_repos,
-            &[
-                (Path::new("c.txt"), None),
-                (
-                    Path::new("dir1/deps/dep1/src/a.txt"),
-                    Some(Path::new("dir1/deps/dep1").into())
-                ),
-                (Path::new("dir1/src/b.txt"), Some(Path::new("dir1").into())),
-            ]
-        );
-    });
-
-    let repo_update_events = Arc::new(Mutex::new(vec![]));
-    tree.update(cx, |_, cx| {
-        let repo_update_events = repo_update_events.clone();
-        cx.subscribe(&tree, move |_, _, event, _| {
-            if let Event::UpdatedGitRepositories(update) = event {
-                repo_update_events.lock().push(update.clone());
-            }
-        })
-        .detach();
-    });
-
-    std::fs::write(root.path().join("dir1/.git/random_new_file"), "hello").unwrap();
-    tree.flush_fs_events(cx).await;
-
-    assert_eq!(
-        repo_update_events.lock()[0]
-            .iter()
-            .map(|e| e.0.clone())
-            .collect::<Vec<Arc<Path>>>(),
-        vec![Path::new("dir1").into()]
-    );
-
-    std::fs::remove_dir_all(root.path().join("dir1/.git")).unwrap();
-    tree.flush_fs_events(cx).await;
-
-    tree.read_with(cx, |tree, _cx| {
-        let tree = tree.as_local().unwrap();
-
-        assert!(tree
-            .repository_for_path("dir1/src/b.txt".as_ref())
-            .is_none());
-    });
-}
-
-#[gpui::test]
-async fn test_git_status(cx: &mut TestAppContext) {
-    init_test(cx);
-    cx.executor().allow_parking();
-    const IGNORE_RULE: &'static str = "**/target";
-
-    let root = temp_tree(json!({
-        "project": {
-            "a.txt": "a",
-            "b.txt": "bb",
-            "c": {
-                "d": {
-                    "e.txt": "eee"
-                }
-            },
-            "f.txt": "ffff",
-            "target": {
-                "build_file": "???"
-            },
-            ".gitignore": IGNORE_RULE
-        },
-
-    }));
-
-    const A_TXT: &'static str = "a.txt";
-    const B_TXT: &'static str = "b.txt";
-    const E_TXT: &'static str = "c/d/e.txt";
-    const F_TXT: &'static str = "f.txt";
-    const DOTGITIGNORE: &'static str = ".gitignore";
-    const BUILD_FILE: &'static str = "target/build_file";
-    let project_path = Path::new("project");
-
-    // Set up git repository before creating the worktree.
-    let work_dir = root.path().join("project");
-    let mut repo = git_init(work_dir.as_path());
-    repo.add_ignore_rule(IGNORE_RULE).unwrap();
-    git_add(A_TXT, &repo);
-    git_add(E_TXT, &repo);
-    git_add(DOTGITIGNORE, &repo);
-    git_commit("Initial commit", &repo);
-
-    let tree = Worktree::local(
-        build_client(cx),
-        root.path(),
-        true,
-        Arc::new(RealFs),
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-
-    tree.flush_fs_events(cx).await;
-    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-        .await;
-    cx.executor().run_until_parked();
-
-    // Check that the right git state is observed on startup
-    tree.read_with(cx, |tree, _cx| {
-        let snapshot = tree.snapshot();
-        assert_eq!(snapshot.repositories().count(), 1);
-        let (dir, _) = snapshot.repositories().next().unwrap();
-        assert_eq!(dir.as_ref(), Path::new("project"));
-
-        assert_eq!(
-            snapshot.status_for_file(project_path.join(B_TXT)),
-            Some(GitFileStatus::Added)
-        );
-        assert_eq!(
-            snapshot.status_for_file(project_path.join(F_TXT)),
-            Some(GitFileStatus::Added)
-        );
-    });
-
-    // Modify a file in the working copy.
-    std::fs::write(work_dir.join(A_TXT), "aa").unwrap();
-    tree.flush_fs_events(cx).await;
-    cx.executor().run_until_parked();
-
-    // The worktree detects that the file's git status has changed.
-    tree.read_with(cx, |tree, _cx| {
-        let snapshot = tree.snapshot();
-        assert_eq!(
-            snapshot.status_for_file(project_path.join(A_TXT)),
-            Some(GitFileStatus::Modified)
-        );
-    });
-
-    // Create a commit in the git repository.
-    git_add(A_TXT, &repo);
-    git_add(B_TXT, &repo);
-    git_commit("Committing modified and added", &repo);
-    tree.flush_fs_events(cx).await;
-    cx.executor().run_until_parked();
-
-    // The worktree detects that the files' git status have changed.
-    tree.read_with(cx, |tree, _cx| {
-        let snapshot = tree.snapshot();
-        assert_eq!(
-            snapshot.status_for_file(project_path.join(F_TXT)),
-            Some(GitFileStatus::Added)
-        );
-        assert_eq!(snapshot.status_for_file(project_path.join(B_TXT)), None);
-        assert_eq!(snapshot.status_for_file(project_path.join(A_TXT)), None);
-    });
-
-    // Modify files in the working copy and perform git operations on other files.
-    git_reset(0, &repo);
-    git_remove_index(Path::new(B_TXT), &repo);
-    git_stash(&mut repo);
-    std::fs::write(work_dir.join(E_TXT), "eeee").unwrap();
-    std::fs::write(work_dir.join(BUILD_FILE), "this should be ignored").unwrap();
-    tree.flush_fs_events(cx).await;
-    cx.executor().run_until_parked();
-
-    // Check that more complex repo changes are tracked
-    tree.read_with(cx, |tree, _cx| {
-        let snapshot = tree.snapshot();
-
-        assert_eq!(snapshot.status_for_file(project_path.join(A_TXT)), None);
-        assert_eq!(
-            snapshot.status_for_file(project_path.join(B_TXT)),
-            Some(GitFileStatus::Added)
-        );
-        assert_eq!(
-            snapshot.status_for_file(project_path.join(E_TXT)),
-            Some(GitFileStatus::Modified)
-        );
-    });
-
-    std::fs::remove_file(work_dir.join(B_TXT)).unwrap();
-    std::fs::remove_dir_all(work_dir.join("c")).unwrap();
-    std::fs::write(
-        work_dir.join(DOTGITIGNORE),
-        [IGNORE_RULE, "f.txt"].join("\n"),
-    )
-    .unwrap();
-
-    git_add(Path::new(DOTGITIGNORE), &repo);
-    git_commit("Committing modified git ignore", &repo);
-
-    tree.flush_fs_events(cx).await;
-    cx.executor().run_until_parked();
-
-    let mut renamed_dir_name = "first_directory/second_directory";
-    const RENAMED_FILE: &'static str = "rf.txt";
-
-    std::fs::create_dir_all(work_dir.join(renamed_dir_name)).unwrap();
-    std::fs::write(
-        work_dir.join(renamed_dir_name).join(RENAMED_FILE),
-        "new-contents",
-    )
-    .unwrap();
-
-    tree.flush_fs_events(cx).await;
-    cx.executor().run_until_parked();
-
-    tree.read_with(cx, |tree, _cx| {
-        let snapshot = tree.snapshot();
-        assert_eq!(
-            snapshot.status_for_file(&project_path.join(renamed_dir_name).join(RENAMED_FILE)),
-            Some(GitFileStatus::Added)
-        );
-    });
-
-    renamed_dir_name = "new_first_directory/second_directory";
-
-    std::fs::rename(
-        work_dir.join("first_directory"),
-        work_dir.join("new_first_directory"),
-    )
-    .unwrap();
-
-    tree.flush_fs_events(cx).await;
-    cx.executor().run_until_parked();
-
-    tree.read_with(cx, |tree, _cx| {
-        let snapshot = tree.snapshot();
-
-        assert_eq!(
-            snapshot.status_for_file(
-                project_path
-                    .join(Path::new(renamed_dir_name))
-                    .join(RENAMED_FILE)
-            ),
-            Some(GitFileStatus::Added)
-        );
-    });
-}
-
-#[gpui::test]
-async fn test_propagate_git_statuses(cx: &mut TestAppContext) {
-    init_test(cx);
-    let fs = FakeFs::new(cx.background_executor.clone());
-    fs.insert_tree(
-        "/root",
-        json!({
-            ".git": {},
-            "a": {
-                "b": {
-                    "c1.txt": "",
-                    "c2.txt": "",
-                },
-                "d": {
-                    "e1.txt": "",
-                    "e2.txt": "",
-                    "e3.txt": "",
-                }
-            },
-            "f": {
-                "no-status.txt": ""
-            },
-            "g": {
-                "h1.txt": "",
-                "h2.txt": ""
-            },
-
-        }),
-    )
-    .await;
-
-    fs.set_status_for_repo_via_git_operation(
-        &Path::new("/root/.git"),
-        &[
-            (Path::new("a/b/c1.txt"), GitFileStatus::Added),
-            (Path::new("a/d/e2.txt"), GitFileStatus::Modified),
-            (Path::new("g/h2.txt"), GitFileStatus::Conflict),
-        ],
-    );
-
-    let tree = Worktree::local(
-        build_client(cx),
-        Path::new("/root"),
-        true,
-        fs.clone(),
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-
-    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-        .await;
-
-    cx.executor().run_until_parked();
-    let snapshot = tree.read_with(cx, |tree, _| tree.snapshot());
-
-    check_propagated_statuses(
-        &snapshot,
-        &[
-            (Path::new(""), Some(GitFileStatus::Conflict)),
-            (Path::new("a"), Some(GitFileStatus::Modified)),
-            (Path::new("a/b"), Some(GitFileStatus::Added)),
-            (Path::new("a/b/c1.txt"), Some(GitFileStatus::Added)),
-            (Path::new("a/b/c2.txt"), None),
-            (Path::new("a/d"), Some(GitFileStatus::Modified)),
-            (Path::new("a/d/e2.txt"), Some(GitFileStatus::Modified)),
-            (Path::new("f"), None),
-            (Path::new("f/no-status.txt"), None),
-            (Path::new("g"), Some(GitFileStatus::Conflict)),
-            (Path::new("g/h2.txt"), Some(GitFileStatus::Conflict)),
-        ],
-    );
-
-    check_propagated_statuses(
-        &snapshot,
-        &[
-            (Path::new("a/b"), Some(GitFileStatus::Added)),
-            (Path::new("a/b/c1.txt"), Some(GitFileStatus::Added)),
-            (Path::new("a/b/c2.txt"), None),
-            (Path::new("a/d"), Some(GitFileStatus::Modified)),
-            (Path::new("a/d/e1.txt"), None),
-            (Path::new("a/d/e2.txt"), Some(GitFileStatus::Modified)),
-            (Path::new("f"), None),
-            (Path::new("f/no-status.txt"), None),
-            (Path::new("g"), Some(GitFileStatus::Conflict)),
-        ],
-    );
-
-    check_propagated_statuses(
-        &snapshot,
-        &[
-            (Path::new("a/b/c1.txt"), Some(GitFileStatus::Added)),
-            (Path::new("a/b/c2.txt"), None),
-            (Path::new("a/d/e1.txt"), None),
-            (Path::new("a/d/e2.txt"), Some(GitFileStatus::Modified)),
-            (Path::new("f/no-status.txt"), None),
-        ],
-    );
-
-    #[track_caller]
-    fn check_propagated_statuses(
-        snapshot: &Snapshot,
-        expected_statuses: &[(&Path, Option<GitFileStatus>)],
-    ) {
-        let mut entries = expected_statuses
-            .iter()
-            .map(|(path, _)| snapshot.entry_for_path(path).unwrap().clone())
-            .collect::<Vec<_>>();
-        snapshot.propagate_git_statuses(&mut entries);
-        assert_eq!(
-            entries
-                .iter()
-                .map(|e| (e.path.as_ref(), e.git_status))
-                .collect::<Vec<_>>(),
-            expected_statuses
-        );
-    }
-}
-
-fn build_client(cx: &mut TestAppContext) -> Arc<Client> {
-    let http_client = FakeHttpClient::with_404_response();
-    cx.update(|cx| Client::new(http_client, cx))
-}
-
-#[track_caller]
-fn git_init(path: &Path) -> git2::Repository {
-    git2::Repository::init(path).expect("Failed to initialize git repository")
-}
-
-#[track_caller]
-fn git_add<P: AsRef<Path>>(path: P, repo: &git2::Repository) {
-    let path = path.as_ref();
-    let mut index = repo.index().expect("Failed to get index");
-    index.add_path(path).expect("Failed to add a.txt");
-    index.write().expect("Failed to write index");
-}
-
-#[track_caller]
-fn git_remove_index(path: &Path, repo: &git2::Repository) {
-    let mut index = repo.index().expect("Failed to get index");
-    index.remove_path(path).expect("Failed to add a.txt");
-    index.write().expect("Failed to write index");
-}
-
-#[track_caller]
-fn git_commit(msg: &'static str, repo: &git2::Repository) {
-    use git2::Signature;
-
-    let signature = Signature::now("test", "test@zed.dev").unwrap();
-    let oid = repo.index().unwrap().write_tree().unwrap();
-    let tree = repo.find_tree(oid).unwrap();
-    if let Some(head) = repo.head().ok() {
-        let parent_obj = head.peel(git2::ObjectType::Commit).unwrap();
-
-        let parent_commit = parent_obj.as_commit().unwrap();
-
-        repo.commit(
-            Some("HEAD"),
-            &signature,
-            &signature,
-            msg,
-            &tree,
-            &[parent_commit],
-        )
-        .expect("Failed to commit with parent");
-    } else {
-        repo.commit(Some("HEAD"), &signature, &signature, msg, &tree, &[])
-            .expect("Failed to commit");
-    }
-}
-
-#[track_caller]
-fn git_stash(repo: &mut git2::Repository) {
-    use git2::Signature;
-
-    let signature = Signature::now("test", "test@zed.dev").unwrap();
-    repo.stash_save(&signature, "N/A", None)
-        .expect("Failed to stash");
-}
-
-#[track_caller]
-fn git_reset(offset: usize, repo: &git2::Repository) {
-    let head = repo.head().expect("Couldn't get repo head");
-    let object = head.peel(git2::ObjectType::Commit).unwrap();
-    let commit = object.as_commit().unwrap();
-    let new_head = commit
-        .parents()
-        .inspect(|parnet| {
-            parnet.message();
-        })
-        .skip(offset)
-        .next()
-        .expect("Not enough history");
-    repo.reset(&new_head.as_object(), git2::ResetType::Soft, None)
-        .expect("Could not reset");
-}
-
-#[allow(dead_code)]
-#[track_caller]
-fn git_status(repo: &git2::Repository) -> collections::HashMap<String, git2::Status> {
-    repo.statuses(None)
-        .unwrap()
-        .iter()
-        .map(|status| (status.path().unwrap().to_string(), status.status()))
-        .collect()
-}
-
-#[track_caller]
-fn check_worktree_entries(
-    tree: &Worktree,
-    expected_excluded_paths: &[&str],
-    expected_ignored_paths: &[&str],
-    expected_tracked_paths: &[&str],
-) {
-    for path in expected_excluded_paths {
-        let entry = tree.entry_for_path(path);
-        assert!(
-            entry.is_none(),
-            "expected path '{path}' to be excluded, but got entry: {entry:?}",
-        );
-    }
-    for path in expected_ignored_paths {
-        let entry = tree
-            .entry_for_path(path)
-            .unwrap_or_else(|| panic!("Missing entry for expected ignored path '{path}'"));
-        assert!(
-            entry.is_ignored,
-            "expected path '{path}' to be ignored, but got entry: {entry:?}",
-        );
-    }
-    for path in expected_tracked_paths {
-        let entry = tree
-            .entry_for_path(path)
-            .unwrap_or_else(|| panic!("Missing entry for expected tracked path '{path}'"));
-        assert!(
-            !entry.is_ignored,
-            "expected path '{path}' to be tracked, but got entry: {entry:?}",
-        );
-    }
-}
-
-fn init_test(cx: &mut gpui::TestAppContext) {
-    cx.update(|cx| {
-        let settings_store = SettingsStore::test(cx);
-        cx.set_global(settings_store);
-        Project::init_settings(cx);
-    });
-}

crates/project_panel/Cargo.toml 🔗

@@ -9,25 +9,26 @@ path = "src/project_panel.rs"
 doctest = false
 
 [dependencies]
-context_menu = { path = "../context_menu" }
 collections = { path = "../collections" }
 db = { path = "../db" }
-drag_and_drop = { path = "../drag_and_drop" }
 editor = { path = "../editor" }
 gpui = { path = "../gpui" }
-menu = { path = "../menu" }
+menu = {  path = "../menu" }
 project = { path = "../project" }
+search = { path = "../search" }
 settings = { path = "../settings" }
 theme = { path = "../theme" }
+ui = { path = "../ui" }
 util = { path = "../util" }
-workspace = { path = "../workspace" }
+workspace = { path = "../workspace", package = "workspace" }
+anyhow.workspace = true
 postage.workspace = true
 futures.workspace = true
 serde.workspace = true
 serde_derive.workspace = true
 serde_json.workspace = true
-anyhow.workspace = true
 schemars.workspace = true
+smallvec.workspace = true
 pretty_assertions.workspace = true
 unicase = "2.6"
 

crates/project_panel/src/file_associations.rs 🔗

@@ -41,56 +41,47 @@ impl FileAssociations {
             })
     }
 
-    pub fn get_icon(path: &Path, cx: &AppContext) -> Arc<str> {
-        maybe!({
-            let this = cx.has_global::<Self>().then(|| cx.global::<Self>())?;
+    pub fn get_icon(path: &Path, cx: &AppContext) -> Option<Arc<str>> {
+        let this = cx.has_global::<Self>().then(|| cx.global::<Self>())?;
 
-            // FIXME: Associate a type with the languages and have the file's langauge
-            //        override these associations
-            maybe!({
-                let suffix = path.icon_suffix()?;
+        // FIXME: Associate a type with the languages and have the file's langauge
+        //        override these associations
+        maybe!({
+            let suffix = path.icon_suffix()?;
 
-                this.suffixes
-                    .get(suffix)
-                    .and_then(|type_str| this.types.get(type_str))
-                    .map(|type_config| type_config.icon.clone())
-            })
-            .or_else(|| this.types.get("default").map(|config| config.icon.clone()))
+            this.suffixes
+                .get(suffix)
+                .and_then(|type_str| this.types.get(type_str))
+                .map(|type_config| type_config.icon.clone())
         })
-        .unwrap_or_else(|| Arc::from("".to_string()))
+        .or_else(|| this.types.get("default").map(|config| config.icon.clone()))
     }
 
-    pub fn get_folder_icon(expanded: bool, cx: &AppContext) -> Arc<str> {
-        maybe!({
-            let this = cx.has_global::<Self>().then(|| cx.global::<Self>())?;
+    pub fn get_folder_icon(expanded: bool, cx: &AppContext) -> Option<Arc<str>> {
+        let this = cx.has_global::<Self>().then(|| cx.global::<Self>())?;
 
-            let key = if expanded {
-                EXPANDED_DIRECTORY_TYPE
-            } else {
-                COLLAPSED_DIRECTORY_TYPE
-            };
+        let key = if expanded {
+            EXPANDED_DIRECTORY_TYPE
+        } else {
+            COLLAPSED_DIRECTORY_TYPE
+        };
 
-            this.types
-                .get(key)
-                .map(|type_config| type_config.icon.clone())
-        })
-        .unwrap_or_else(|| Arc::from("".to_string()))
+        this.types
+            .get(key)
+            .map(|type_config| type_config.icon.clone())
     }
 
-    pub fn get_chevron_icon(expanded: bool, cx: &AppContext) -> Arc<str> {
-        maybe!({
-            let this = cx.has_global::<Self>().then(|| cx.global::<Self>())?;
+    pub fn get_chevron_icon(expanded: bool, cx: &AppContext) -> Option<Arc<str>> {
+        let this = cx.has_global::<Self>().then(|| cx.global::<Self>())?;
 
-            let key = if expanded {
-                EXPANDED_CHEVRON_TYPE
-            } else {
-                COLLAPSED_CHEVRON_TYPE
-            };
+        let key = if expanded {
+            EXPANDED_CHEVRON_TYPE
+        } else {
+            COLLAPSED_CHEVRON_TYPE
+        };
 
-            this.types
-                .get(key)
-                .map(|type_config| type_config.icon.clone())
-        })
-        .unwrap_or_else(|| Arc::from("".to_string()))
+        this.types
+            .get(key)
+            .map(|type_config| type_config.icon.clone())
     }
 }

crates/project_panel/src/project_panel.rs 🔗

@@ -1,25 +1,18 @@
 pub mod file_associations;
 mod project_panel_settings;
+use settings::{Settings, SettingsStore};
 
-use context_menu::{ContextMenu, ContextMenuItem};
 use db::kvp::KEY_VALUE_STORE;
-use drag_and_drop::{DragAndDrop, Draggable};
 use editor::{scroll::autoscroll::Autoscroll, Cancel, Editor};
 use file_associations::FileAssociations;
 
-use futures::stream::StreamExt;
+use anyhow::{anyhow, Result};
 use gpui::{
-    actions,
-    anyhow::{self, anyhow, Result},
-    elements::{
-        AnchorCorner, ChildView, ContainerStyle, Empty, Flex, Label, MouseEventHandler,
-        ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState,
-    },
-    geometry::vector::Vector2F,
-    keymap_matcher::KeymapContext,
-    platform::{CursorStyle, MouseButton, PromptLevel},
-    Action, AnyElement, AppContext, AssetSource, AsyncAppContext, ClipboardItem, Element, Entity,
-    ModelHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
+    actions, div, overlay, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext,
+    ClipboardItem, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
+    KeyContext, Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel,
+    Render, Stateful, Styled, Subscription, Task, UniformListScrollHandle, View, ViewContext,
+    VisualContext as _, WeakView, WindowContext,
 };
 use menu::{Confirm, SelectNext, SelectPrev};
 use project::{
@@ -28,7 +21,6 @@ use project::{
 };
 use project_panel_settings::{ProjectPanelDockPosition, ProjectPanelSettings};
 use serde::{Deserialize, Serialize};
-use settings::SettingsStore;
 use std::{
     cmp::Ordering,
     collections::{hash_map, HashMap},
@@ -37,11 +29,12 @@ use std::{
     path::Path,
     sync::Arc,
 };
-use theme::ProjectPanelEntry;
+use theme::ThemeSettings;
+use ui::{prelude::*, v_stack, ContextMenu, IconElement, Label, ListItem};
 use unicase::UniCase;
-use util::{ResultExt, TryFutureExt};
+use util::{maybe, ResultExt, TryFutureExt};
 use workspace::{
-    dock::{DockPosition, Panel},
+    dock::{DockPosition, Panel, PanelEvent},
     Workspace,
 };
 
@@ -49,21 +42,21 @@ const PROJECT_PANEL_KEY: &'static str = "ProjectPanel";
 const NEW_ENTRY_ID: ProjectEntryId = ProjectEntryId::MAX;
 
 pub struct ProjectPanel {
-    project: ModelHandle<Project>,
+    project: Model<Project>,
     fs: Arc<dyn Fs>,
-    list: UniformListState,
+    list: UniformListScrollHandle,
+    focus_handle: FocusHandle,
     visible_entries: Vec<(WorktreeId, Vec<Entry>)>,
     last_worktree_root_id: Option<ProjectEntryId>,
     expanded_dir_ids: HashMap<WorktreeId, Vec<ProjectEntryId>>,
     selection: Option<Selection>,
+    context_menu: Option<(View<ContextMenu>, Point<Pixels>, Subscription)>,
     edit_state: Option<EditState>,
-    filename_editor: ViewHandle<Editor>,
+    filename_editor: View<Editor>,
     clipboard_entry: Option<ClipboardEntry>,
-    context_menu: ViewHandle<ContextMenu>,
-    dragged_entry_destination: Option<Arc<Path>>,
-    workspace: WeakViewHandle<Workspace>,
-    has_focus: bool,
-    width: Option<f32>,
+    _dragged_entry_destination: Option<Arc<Path>>,
+    workspace: WeakView<Workspace>,
+    width: Option<Pixels>,
     pending_serialization: Task<Option<()>>,
 }
 
@@ -94,7 +87,7 @@ pub enum ClipboardEntry {
     },
 }
 
-#[derive(Debug, PartialEq, Eq)]
+#[derive(Debug, PartialEq, Eq, Clone)]
 pub struct EntryDetails {
     filename: String,
     icon: Option<Arc<str>>,
@@ -134,36 +127,19 @@ actions!(
 );
 
 pub fn init_settings(cx: &mut AppContext) {
-    settings::register::<ProjectPanelSettings>(cx);
+    ProjectPanelSettings::register(cx);
 }
 
 pub fn init(assets: impl AssetSource, cx: &mut AppContext) {
     init_settings(cx);
     file_associations::init(assets, cx);
-    cx.add_action(ProjectPanel::expand_selected_entry);
-    cx.add_action(ProjectPanel::collapse_selected_entry);
-    cx.add_action(ProjectPanel::collapse_all_entries);
-    cx.add_action(ProjectPanel::select_prev);
-    cx.add_action(ProjectPanel::select_next);
-    cx.add_action(ProjectPanel::new_file);
-    cx.add_action(ProjectPanel::new_directory);
-    cx.add_action(ProjectPanel::rename);
-    cx.add_async_action(ProjectPanel::delete);
-    cx.add_async_action(ProjectPanel::confirm);
-    cx.add_async_action(ProjectPanel::open_file);
-    cx.add_action(ProjectPanel::cancel);
-    cx.add_action(ProjectPanel::cut);
-    cx.add_action(ProjectPanel::copy);
-    cx.add_action(ProjectPanel::copy_path);
-    cx.add_action(ProjectPanel::copy_relative_path);
-    cx.add_action(ProjectPanel::reveal_in_finder);
-    cx.add_action(ProjectPanel::open_in_terminal);
-    cx.add_action(ProjectPanel::new_search_in_directory);
-    cx.add_action(
-        |this: &mut ProjectPanel, action: &Paste, cx: &mut ViewContext<ProjectPanel>| {
-            this.paste(action, cx);
-        },
-    );
+
+    cx.observe_new_views(|workspace: &mut Workspace, _| {
+        workspace.register_action(|workspace, _: &ToggleFocus, cx| {
+            workspace.toggle_panel_focus::<ProjectPanel>(cx);
+        });
+    })
+    .detach();
 }
 
 #[derive(Debug)]
@@ -175,40 +151,45 @@ pub enum Event {
     SplitEntry {
         entry_id: ProjectEntryId,
     },
-    DockPositionChanged,
     Focus,
-    NewSearchInDirectory {
-        dir_entry: Entry,
-    },
-    ActivatePanel,
 }
 
 #[derive(Serialize, Deserialize)]
 struct SerializedProjectPanel {
-    width: Option<f32>,
+    width: Option<Pixels>,
+}
+
+struct DraggedProjectEntryView {
+    entry_id: ProjectEntryId,
+    details: EntryDetails,
+    width: Pixels,
 }
 
 impl ProjectPanel {
-    fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> ViewHandle<Self> {
+    fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
         let project = workspace.project().clone();
-        let project_panel = cx.add_view(|cx: &mut ViewContext<Self>| {
+        let project_panel = cx.new_view(|cx: &mut ViewContext<Self>| {
             cx.observe(&project, |this, _, cx| {
                 this.update_visible_entries(None, cx);
                 cx.notify();
             })
             .detach();
+            let focus_handle = cx.focus_handle();
+
+            cx.on_focus(&focus_handle, Self::focus_in).detach();
+
             cx.subscribe(&project, |this, project, event, cx| match event {
                 project::Event::ActiveEntryChanged(Some(entry_id)) => {
-                    if settings::get::<ProjectPanelSettings>(cx).auto_reveal_entries {
+                    if ProjectPanelSettings::get_global(cx).auto_reveal_entries {
                         this.reveal_entry(project, *entry_id, true, cx);
                     }
                 }
                 project::Event::RevealInProjectPanel(entry_id) => {
                     this.reveal_entry(project, *entry_id, false, cx);
-                    cx.emit(Event::ActivatePanel);
+                    cx.emit(PanelEvent::Activate);
                 }
                 project::Event::ActivateProjectPanel => {
-                    cx.emit(Event::ActivatePanel);
+                    cx.emit(PanelEvent::Activate);
                 }
                 project::Event::WorktreeRemoved(id) => {
                     this.expanded_dir_ids.remove(id);
@@ -219,58 +200,47 @@ impl ProjectPanel {
             })
             .detach();
 
-            let filename_editor = cx.add_view(|cx| {
-                Editor::single_line(
-                    Some(Arc::new(|theme| {
-                        let mut style = theme.project_panel.filename_editor.clone();
-                        style.container.background_color.take();
-                        style
-                    })),
-                    cx,
-                )
-            });
+            let filename_editor = cx.new_view(|cx| Editor::single_line(cx));
 
             cx.subscribe(&filename_editor, |this, _, event, cx| match event {
-                editor::Event::BufferEdited | editor::Event::SelectionsChanged { .. } => {
+                editor::EditorEvent::BufferEdited
+                | editor::EditorEvent::SelectionsChanged { .. } => {
                     this.autoscroll(cx);
                 }
-                _ => {}
-            })
-            .detach();
-            cx.observe_focus(&filename_editor, |this, _, is_focused, cx| {
-                if !is_focused
-                    && this
+                editor::EditorEvent::Blurred => {
+                    if this
                         .edit_state
                         .as_ref()
                         .map_or(false, |state| state.processing_filename.is_none())
-                {
-                    this.edit_state = None;
-                    this.update_visible_entries(None, cx);
+                    {
+                        this.edit_state = None;
+                        this.update_visible_entries(None, cx);
+                    }
                 }
+                _ => {}
             })
             .detach();
 
-            cx.observe_global::<FileAssociations, _>(|_, cx| {
-                cx.notify();
-            })
-            .detach();
+            // cx.observe_global::<FileAssociations, _>(|_, cx| {
+            //     cx.notify();
+            // })
+            // .detach();
 
-            let view_id = cx.view_id();
             let mut this = Self {
                 project: project.clone(),
                 fs: workspace.app_state().fs.clone(),
-                list: Default::default(),
+                list: UniformListScrollHandle::new(),
+                focus_handle,
                 visible_entries: Default::default(),
                 last_worktree_root_id: Default::default(),
                 expanded_dir_ids: Default::default(),
                 selection: None,
                 edit_state: None,
+                context_menu: None,
                 filename_editor,
                 clipboard_entry: None,
-                context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)),
-                dragged_entry_destination: None,
+                _dragged_entry_destination: None,
                 workspace: workspace.weak_handle(),
-                has_focus: false,
                 width: None,
                 pending_serialization: Task::ready(None),
             };
@@ -278,11 +248,12 @@ impl ProjectPanel {
 
             // Update the dock position when the setting changes.
             let mut old_dock_position = this.position(cx);
-            cx.observe_global::<SettingsStore, _>(move |this, cx| {
+            ProjectPanelSettings::register(cx);
+            cx.observe_global::<SettingsStore>(move |this, cx| {
                 let new_dock_position = this.position(cx);
                 if new_dock_position != old_dock_position {
                     old_dock_position = new_dock_position;
-                    cx.emit(Event::DockPositionChanged);
+                    cx.emit(PanelEvent::ChangePosition);
                 }
             })
             .detach();
@@ -311,8 +282,9 @@ impl ProjectPanel {
                                 )
                                 .detach_and_log_err(cx);
                             if !focus_opened_item {
-                                if let Some(project_panel) = project_panel.upgrade(cx) {
-                                    cx.focus(&project_panel);
+                                if let Some(project_panel) = project_panel.upgrade() {
+                                    let focus_handle = project_panel.read(cx).focus_handle.clone();
+                                    cx.focus(&focus_handle);
                                 }
                             }
                         }
@@ -320,16 +292,16 @@ impl ProjectPanel {
                 }
                 &Event::SplitEntry { entry_id } => {
                     if let Some(worktree) = project.read(cx).worktree_for_entry(entry_id, cx) {
-                        if let Some(entry) = worktree.read(cx).entry_for_id(entry_id) {
-                            workspace
-                                .split_path(
-                                    ProjectPath {
-                                        worktree_id: worktree.read(cx).id(),
-                                        path: entry.path.clone(),
-                                    },
-                                    cx,
-                                )
-                                .detach_and_log_err(cx);
+                        if let Some(_entry) = worktree.read(cx).entry_for_id(entry_id) {
+                            // workspace
+                            //     .split_path(
+                            //         ProjectPath {
+                            //             worktree_id: worktree.read(cx).id(),
+                            //             path: entry.path.clone(),
+                            //         },
+                            //         cx,
+                            //     )
+                            //     .detach_and_log_err(cx);
                         }
                     }
                 }
@@ -341,38 +313,37 @@ impl ProjectPanel {
         project_panel
     }
 
-    pub fn load(
-        workspace: WeakViewHandle<Workspace>,
-        cx: AsyncAppContext,
-    ) -> Task<Result<ViewHandle<Self>>> {
-        cx.spawn(|mut cx| async move {
-            let serialized_panel = if let Some(panel) = cx
-                .background()
-                .spawn(async move { KEY_VALUE_STORE.read_kvp(PROJECT_PANEL_KEY) })
-                .await
-                .log_err()
-                .flatten()
-            {
-                Some(serde_json::from_str::<SerializedProjectPanel>(&panel)?)
-            } else {
-                None
-            };
-            workspace.update(&mut cx, |workspace, cx| {
-                let panel = ProjectPanel::new(workspace, cx);
-                if let Some(serialized_panel) = serialized_panel {
-                    panel.update(cx, |panel, cx| {
-                        panel.width = serialized_panel.width;
-                        cx.notify();
-                    });
-                }
-                panel
-            })
+    pub async fn load(
+        workspace: WeakView<Workspace>,
+        mut cx: AsyncWindowContext,
+    ) -> Result<View<Self>> {
+        let serialized_panel = cx
+            .background_executor()
+            .spawn(async move { KEY_VALUE_STORE.read_kvp(PROJECT_PANEL_KEY) })
+            .await
+            .map_err(|e| anyhow!("Failed to load project panel: {}", e))
+            .log_err()
+            .flatten()
+            .map(|panel| serde_json::from_str::<SerializedProjectPanel>(&panel))
+            .transpose()
+            .log_err()
+            .flatten();
+
+        workspace.update(&mut cx, |workspace, cx| {
+            let panel = ProjectPanel::new(workspace, cx);
+            if let Some(serialized_panel) = serialized_panel {
+                panel.update(cx, |panel, cx| {
+                    panel.width = serialized_panel.width;
+                    cx.notify();
+                });
+            }
+            panel
         })
     }
 
     fn serialize(&mut self, cx: &mut ViewContext<Self>) {
         let width = self.width;
-        self.pending_serialization = cx.background().spawn(
+        self.pending_serialization = cx.background_executor().spawn(
             async move {
                 KEY_VALUE_STORE
                     .write_kvp(
@@ -386,12 +357,19 @@ impl ProjectPanel {
         );
     }
 
+    fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
+        if !self.focus_handle.contains_focused(cx) {
+            cx.emit(Event::Focus);
+        }
+    }
+
     fn deploy_context_menu(
         &mut self,
-        position: Vector2F,
+        position: Point<Pixels>,
         entry_id: ProjectEntryId,
         cx: &mut ViewContext<Self>,
     ) {
+        let this = cx.view().clone();
         let project = self.project.read(cx);
 
         let worktree_id = if let Some(id) = project.worktree_id_for_entry(entry_id, cx) {
@@ -405,60 +383,73 @@ impl ProjectPanel {
             entry_id,
         });
 
-        let mut menu_entries = Vec::new();
         if let Some((worktree, entry)) = self.selected_entry(cx) {
             let is_root = Some(entry) == worktree.root_entry();
-            if !project.is_remote() {
-                menu_entries.push(ContextMenuItem::action(
-                    "Add Folder to Project",
-                    workspace::AddFolderToProject,
-                ));
-                if is_root {
-                    let project = self.project.clone();
-                    menu_entries.push(ContextMenuItem::handler("Remove from Project", move |cx| {
-                        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
-                    }));
+            let is_dir = entry.is_dir();
+            let worktree_id = worktree.id();
+            let is_local = project.is_local();
+
+            let context_menu = ContextMenu::build(cx, |mut menu, cx| {
+                if is_local {
+                    menu = menu.action(
+                        "Add Folder to Project",
+                        Box::new(workspace::AddFolderToProject),
+                    );
+                    if is_root {
+                        menu = menu.entry(
+                            "Remove from Project",
+                            None,
+                            cx.handler_for(&this, move |this, cx| {
+                                this.project.update(cx, |project, cx| {
+                                    project.remove_worktree(worktree_id, cx)
+                                });
+                            }),
+                        );
+                    }
                 }
-            }
-            menu_entries.push(ContextMenuItem::action("New File", NewFile));
-            menu_entries.push(ContextMenuItem::action("New Folder", NewDirectory));
-            menu_entries.push(ContextMenuItem::Separator);
-            menu_entries.push(ContextMenuItem::action("Cut", Cut));
-            menu_entries.push(ContextMenuItem::action("Copy", Copy));
-            if let Some(clipboard_entry) = self.clipboard_entry {
-                if clipboard_entry.worktree_id() == worktree.id() {
-                    menu_entries.push(ContextMenuItem::action("Paste", Paste));
+
+                menu = menu
+                    .action("New File", Box::new(NewFile))
+                    .action("New Folder", Box::new(NewDirectory))
+                    .separator()
+                    .action("Cut", Box::new(Cut))
+                    .action("Copy", Box::new(Copy));
+
+                if let Some(clipboard_entry) = self.clipboard_entry {
+                    if clipboard_entry.worktree_id() == worktree_id {
+                        menu = menu.action("Paste", Box::new(Paste));
+                    }
                 }
-            }
-            menu_entries.push(ContextMenuItem::Separator);
-            menu_entries.push(ContextMenuItem::action("Copy Path", CopyPath));
-            menu_entries.push(ContextMenuItem::action(
-                "Copy Relative Path",
-                CopyRelativePath,
-            ));
 
-            if entry.is_dir() {
-                menu_entries.push(ContextMenuItem::Separator);
-            }
-            menu_entries.push(ContextMenuItem::action("Reveal in Finder", RevealInFinder));
-            if entry.is_dir() {
-                menu_entries.push(ContextMenuItem::action("Open in Terminal", OpenInTerminal));
-                menu_entries.push(ContextMenuItem::action(
-                    "Search Inside",
-                    NewSearchInDirectory,
-                ));
-            }
+                menu = menu
+                    .separator()
+                    .action("Copy Path", Box::new(CopyPath))
+                    .action("Copy Relative Path", Box::new(CopyRelativePath))
+                    .separator()
+                    .action("Reveal in Finder", Box::new(RevealInFinder));
+
+                if is_dir {
+                    menu = menu
+                        .action("Open in Terminal", Box::new(OpenInTerminal))
+                        .action("Search Inside", Box::new(NewSearchInDirectory))
+                }
 
-            menu_entries.push(ContextMenuItem::Separator);
-            menu_entries.push(ContextMenuItem::action("Rename", Rename));
-            if !is_root {
-                menu_entries.push(ContextMenuItem::action("Delete", Delete));
-            }
-        }
+                menu = menu.separator().action("Rename", Box::new(Rename));
 
-        self.context_menu.update(cx, |menu, cx| {
-            menu.show(position, AnchorCorner::TopLeft, menu_entries, cx);
-        });
+                if !is_root {
+                    menu = menu.action("Delete", Box::new(Delete));
+                }
+
+                menu
+            });
+
+            cx.focus_view(&context_menu);
+            let subscription = cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| {
+                this.context_menu.take();
+                cx.notify();
+            });
+            self.context_menu = Some((context_menu, position, subscription));
+        }
 
         cx.notify();
     }
@@ -545,7 +536,7 @@ impl ProjectPanel {
                     }
                 });
                 self.update_visible_entries(Some((worktree_id, entry_id)), cx);
-                cx.focus_self();
+                cx.focus(&self.focus_handle);
                 cx.notify();
             }
         }
@@ -576,27 +567,23 @@ impl ProjectPanel {
         }
     }
 
-    fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
+    fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
         if let Some(task) = self.confirm_edit(cx) {
-            return Some(task);
+            task.detach_and_log_err(cx);
         }
-
-        None
     }
 
-    fn open_file(&mut self, _: &Open, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
+    fn open_file(&mut self, _: &Open, cx: &mut ViewContext<Self>) {
         if let Some((_, entry)) = self.selected_entry(cx) {
             if entry.is_file() {
                 self.open_entry(entry.id, true, cx);
             }
         }
-
-        None
     }
 
     fn confirm_edit(&mut self, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
         let edit_state = self.edit_state.as_mut()?;
-        cx.focus_self();
+        cx.focus(&self.focus_handle);
 
         let worktree_id = edit_state.worktree_id;
         let is_new_entry = edit_state.is_new_entry;
@@ -671,7 +658,7 @@ impl ProjectPanel {
     fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
         self.edit_state = None;
         self.update_visible_entries(None, cx);
-        cx.focus_self();
+        cx.focus(&self.focus_handle);
         cx.notify();
     }
 
@@ -745,9 +732,10 @@ impl ProjectPanel {
                 is_dir,
                 processing_filename: None,
             });
-            self.filename_editor
-                .update(cx, |editor, cx| editor.clear(cx));
-            cx.focus(&self.filename_editor);
+            self.filename_editor.update(cx, |editor, cx| {
+                editor.clear(cx);
+                editor.focus(cx);
+            });
             self.update_visible_entries(Some((worktree_id, NEW_ENTRY_ID)), cx);
             self.autoscroll(cx);
             cx.notify();
@@ -782,42 +770,47 @@ impl ProjectPanel {
                         editor.set_text(file_name, cx);
                         editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
                             s.select_ranges([0..selection_end])
-                        })
+                        });
+                        editor.focus(cx);
                     });
-                    cx.focus(&self.filename_editor);
                     self.update_visible_entries(None, cx);
                     self.autoscroll(cx);
                     cx.notify();
                 }
             }
 
-            cx.update_global(|drag_and_drop: &mut DragAndDrop<Workspace>, cx| {
-                drag_and_drop.cancel_dragging::<ProjectEntryId>(cx);
-            })
+            // cx.update_global(|drag_and_drop: &mut DragAndDrop<Workspace>, cx| {
+            //     drag_and_drop.cancel_dragging::<ProjectEntryId>(cx);
+            // })
         }
     }
 
-    fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
-        let Selection { entry_id, .. } = self.selection?;
-        let path = self.project.read(cx).path_for_entry(entry_id, cx)?.path;
-        let file_name = path.file_name()?;
+    fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) {
+        maybe!({
+            let Selection { entry_id, .. } = self.selection?;
+            let path = self.project.read(cx).path_for_entry(entry_id, cx)?.path;
+            let file_name = path.file_name()?;
 
-        let mut answer = cx.prompt(
-            PromptLevel::Info,
-            &format!("Delete {file_name:?}?"),
-            &["Delete", "Cancel"],
-        );
-        Some(cx.spawn(|this, mut cx| async move {
-            if answer.next().await != Some(0) {
-                return Ok(());
-            }
-            this.update(&mut cx, |this, cx| {
-                this.project
-                    .update(cx, |project, cx| project.delete_entry(entry_id, cx))
-                    .ok_or_else(|| anyhow!("no such entry"))
-            })??
-            .await
-        }))
+            let answer = cx.prompt(
+                PromptLevel::Info,
+                &format!("Delete {file_name:?}?"),
+                &["Delete", "Cancel"],
+            );
+
+            cx.spawn(|this, mut cx| async move {
+                if answer.await != Ok(0) {
+                    return Ok(());
+                }
+                this.update(&mut cx, |this, cx| {
+                    this.project
+                        .update(cx, |project, cx| project.delete_entry(entry_id, cx))
+                        .ok_or_else(|| anyhow!("no such entry"))
+                })??
+                .await
+            })
+            .detach_and_log_err(cx);
+            Some(())
+        });
     }
 
     fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
@@ -869,7 +862,7 @@ impl ProjectPanel {
 
     fn autoscroll(&mut self, cx: &mut ViewContext<Self>) {
         if let Some((_, _, index)) = self.selection.and_then(|s| self.index_for_selection(s)) {
-            self.list.scroll_to(ScrollTarget::Show(index));
+            self.list.scroll_to_item(index);
             cx.notify();
         }
     }
@@ -894,8 +887,9 @@ impl ProjectPanel {
         }
     }
 
-    fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) -> Option<()> {
-        if let Some((worktree, entry)) = self.selected_entry(cx) {
+    fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
+        maybe!({
+            let (worktree, entry) = self.selected_entry(cx)?;
             let clipboard_entry = self.clipboard_entry?;
             if clipboard_entry.worktree_id() != worktree.id() {
                 return None;
@@ -948,8 +942,9 @@ impl ProjectPanel {
                     })
                     .detach_and_log_err(cx)
             }
-        }
-        None
+
+            Some(())
+        });
     }
 
     fn copy_path(&mut self, _: &CopyPath, cx: &mut ViewContext<Self>) {
@@ -976,24 +971,25 @@ impl ProjectPanel {
         }
     }
 
-    fn open_in_terminal(&mut self, _: &OpenInTerminal, cx: &mut ViewContext<Self>) {
-        if let Some((worktree, entry)) = self.selected_entry(cx) {
-            let window = cx.window();
-            let view_id = cx.view_id();
-            let path = worktree.abs_path().join(&entry.path);
-
-            cx.app_context()
-                .spawn(|mut cx| async move {
-                    window.dispatch_action(
-                        view_id,
-                        &workspace::OpenTerminal {
-                            working_directory: path,
-                        },
-                        &mut cx,
-                    );
-                })
-                .detach();
-        }
+    fn open_in_terminal(&mut self, _: &OpenInTerminal, _cx: &mut ViewContext<Self>) {
+        todo!()
+        // if let Some((worktree, entry)) = self.selected_entry(cx) {
+        //     let window = cx.window();
+        //     let view_id = cx.view_id();
+        //     let path = worktree.abs_path().join(&entry.path);
+
+        //     cx.app_context()
+        //         .spawn(|mut cx| async move {
+        //             window.dispatch_action(
+        //                 view_id,
+        //                 &workspace::OpenTerminal {
+        //                     working_directory: path,
+        //                 },
+        //                 &mut cx,
+        //             );
+        //         })
+        //         .detach();
+        // }
     }
 
     pub fn new_search_in_directory(
@@ -1003,9 +999,12 @@ impl ProjectPanel {
     ) {
         if let Some((_, entry)) = self.selected_entry(cx) {
             if entry.is_dir() {
-                cx.emit(Event::NewSearchInDirectory {
-                    dir_entry: entry.clone(),
-                });
+                let entry = entry.clone();
+                self.workspace
+                    .update(cx, |workspace, cx| {
+                        search::ProjectSearchView::new_search_in_directory(workspace, &entry, cx);
+                    })
+                    .ok();
             }
         }
     }
@@ -1030,7 +1029,7 @@ impl ProjectPanel {
             new_path.push(entry_path.path.file_name()?);
             if new_path != entry_path.path.as_ref() {
                 let task = project.rename_entry(entry_to_move, new_path, cx);
-                cx.foreground().spawn(task).detach_and_log_err(cx);
+                cx.foreground_executor().spawn(task).detach_and_log_err(cx);
             }
 
             Some(project.worktree_id_for_entry(destination, cx)?)
@@ -1075,7 +1074,7 @@ impl ProjectPanel {
     fn selected_entry_handle<'a>(
         &self,
         cx: &'a AppContext,
-    ) -> Option<(ModelHandle<Worktree>, &'a project::Entry)> {
+    ) -> Option<(Model<Worktree>, &'a project::Entry)> {
         let selection = self.selection?;
         let project = self.project.read(cx);
         let worktree = project.worktree_for_id(selection.worktree_id, cx)?;
@@ -1262,7 +1261,7 @@ impl ProjectPanel {
 
             let end_ix = range.end.min(ix + visible_worktree_entries.len());
             let (git_status_setting, show_file_icons, show_folder_icons) = {
-                let settings = settings::get::<ProjectPanelSettings>(cx);
+                let settings = ProjectPanelSettings::get_global(cx);
                 (
                     settings.git_status,
                     settings.file_icons,
@@ -1285,16 +1284,16 @@ impl ProjectPanel {
                     let icon = match entry.kind {
                         EntryKind::File(_) => {
                             if show_file_icons {
-                                Some(FileAssociations::get_icon(&entry.path, cx))
+                                FileAssociations::get_icon(&entry.path, cx)
                             } else {
                                 None
                             }
                         }
                         _ => {
                             if show_folder_icons {
-                                Some(FileAssociations::get_folder_icon(is_expanded, cx))
+                                FileAssociations::get_folder_icon(is_expanded, cx)
                             } else {
-                                Some(FileAssociations::get_chevron_icon(is_expanded, cx))
+                                FileAssociations::get_chevron_icon(is_expanded, cx)
                             }
                         }
                     };
@@ -1351,193 +1350,115 @@ impl ProjectPanel {
         }
     }
 
-    fn render_entry_visual_element<V: 'static>(
-        details: &EntryDetails,
-        editor: Option<&ViewHandle<Editor>>,
-        padding: f32,
-        row_container_style: ContainerStyle,
-        style: &ProjectPanelEntry,
-        cx: &mut ViewContext<V>,
-    ) -> AnyElement<V> {
+    fn render_entry(
+        &self,
+        entry_id: ProjectEntryId,
+        details: EntryDetails,
+        cx: &mut ViewContext<Self>,
+    ) -> Stateful<Div> {
+        let kind = details.kind;
+        let settings = ProjectPanelSettings::get_global(cx);
         let show_editor = details.is_editing && !details.is_processing;
+        let is_selected = self
+            .selection
+            .map_or(false, |selection| selection.entry_id == entry_id);
+        let width = self.width.unwrap_or(px(0.));
 
-        let mut filename_text_style = style.text.clone();
-        filename_text_style.color = details
+        let filename_text_color = details
             .git_status
             .as_ref()
             .map(|status| match status {
-                GitFileStatus::Added => style.status.git.inserted,
-                GitFileStatus::Modified => style.status.git.modified,
-                GitFileStatus::Conflict => style.status.git.conflict,
+                GitFileStatus::Added => Color::Created,
+                GitFileStatus::Modified => Color::Modified,
+                GitFileStatus::Conflict => Color::Conflict,
             })
-            .unwrap_or(style.text.color);
-
-        Flex::row()
-            .with_child(if let Some(icon) = &details.icon {
-                Svg::new(icon.to_string())
-                    .with_color(style.icon_color)
-                    .constrained()
-                    .with_max_width(style.icon_size)
-                    .with_max_height(style.icon_size)
-                    .aligned()
-                    .constrained()
-                    .with_width(style.icon_size)
+            .unwrap_or(if is_selected {
+                Color::Default
             } else {
-                Empty::new()
-                    .constrained()
-                    .with_max_width(style.icon_size)
-                    .with_max_height(style.icon_size)
-                    .aligned()
-                    .constrained()
-                    .with_width(style.icon_size)
+                Color::Muted
+            });
+
+        let file_name = details.filename.clone();
+        let icon = details.icon.clone();
+        let depth = details.depth;
+        div()
+            .id(entry_id.to_proto() as usize)
+            .on_drag(entry_id, move |entry_id, cx| {
+                cx.new_view(|_| DraggedProjectEntryView {
+                    details: details.clone(),
+                    width,
+                    entry_id: *entry_id,
+                })
             })
-            .with_child(if show_editor && editor.is_some() {
-                ChildView::new(editor.as_ref().unwrap(), cx)
-                    .contained()
-                    .with_margin_left(style.icon_spacing)
-                    .aligned()
-                    .left()
-                    .flex(1.0, true)
-                    .into_any()
-            } else {
-                Label::new(details.filename.clone(), filename_text_style)
-                    .contained()
-                    .with_margin_left(style.icon_spacing)
-                    .aligned()
-                    .left()
-                    .into_any()
+            .drag_over::<ProjectEntryId>(|style| {
+                style.bg(cx.theme().colors().drop_target_background)
             })
-            .constrained()
-            .with_height(style.height)
-            .contained()
-            .with_style(row_container_style)
-            .with_padding_left(padding)
-            .into_any_named("project panel entry visual element")
+            .on_drop(cx.listener(move |this, dragged_id: &ProjectEntryId, cx| {
+                this.move_entry(*dragged_id, entry_id, kind.is_file(), cx);
+            }))
+            .child(
+                ListItem::new(entry_id.to_proto() as usize)
+                    .indent_level(depth)
+                    .indent_step_size(px(settings.indent_size))
+                    .selected(is_selected)
+                    .child(if let Some(icon) = &icon {
+                        div().child(IconElement::from_path(icon.to_string()).color(Color::Muted))
+                    } else {
+                        div()
+                    })
+                    .child(
+                        if let (Some(editor), true) = (Some(&self.filename_editor), show_editor) {
+                            div().h_full().w_full().child(editor.clone())
+                        } else {
+                            div().child(Label::new(file_name).color(filename_text_color))
+                        }
+                        .ml_1(),
+                    )
+                    .on_click(cx.listener(move |this, event: &gpui::ClickEvent, cx| {
+                        if event.down.button == MouseButton::Right {
+                            return;
+                        }
+                        if !show_editor {
+                            if kind.is_dir() {
+                                this.toggle_expanded(entry_id, cx);
+                            } else {
+                                if event.down.modifiers.command {
+                                    this.split_entry(entry_id, cx);
+                                } else {
+                                    this.open_entry(entry_id, event.up.click_count > 1, cx);
+                                }
+                            }
+                        }
+                    }))
+                    .on_secondary_mouse_down(cx.listener(
+                        move |this, event: &MouseDownEvent, cx| {
+                            this.deploy_context_menu(event.position, entry_id, cx);
+                        },
+                    )),
+            )
     }
 
-    fn render_entry(
-        entry_id: ProjectEntryId,
-        details: EntryDetails,
-        editor: &ViewHandle<Editor>,
-        dragged_entry_destination: &mut Option<Arc<Path>>,
-        theme: &theme::ProjectPanel,
-        cx: &mut ViewContext<Self>,
-    ) -> AnyElement<Self> {
-        let kind = details.kind;
-        let path = details.path.clone();
-        let settings = settings::get::<ProjectPanelSettings>(cx);
-        let padding = theme.container.padding.left + details.depth as f32 * settings.indent_size;
-
-        let entry_style = if details.is_cut {
-            &theme.cut_entry
-        } else if details.is_ignored {
-            &theme.ignored_entry
+    fn dispatch_context(&self, cx: &ViewContext<Self>) -> KeyContext {
+        let mut dispatch_context = KeyContext::default();
+        dispatch_context.add("ProjectPanel");
+        dispatch_context.add("menu");
+
+        let identifier = if self.filename_editor.focus_handle(cx).is_focused(cx) {
+            "editing"
         } else {
-            &theme.entry
+            "not_editing"
         };
 
-        let show_editor = details.is_editing && !details.is_processing;
-
-        MouseEventHandler::new::<Self, _>(entry_id.to_usize(), cx, |state, cx| {
-            let mut style = entry_style
-                .in_state(details.is_selected)
-                .style_for(state)
-                .clone();
-
-            if cx
-                .global::<DragAndDrop<Workspace>>()
-                .currently_dragged::<ProjectEntryId>(cx.window())
-                .is_some()
-                && dragged_entry_destination
-                    .as_ref()
-                    .filter(|destination| details.path.starts_with(destination))
-                    .is_some()
-            {
-                style = entry_style.active_state().default.clone();
-            }
-
-            let row_container_style = if show_editor {
-                theme.filename_editor.container
-            } else {
-                style.container
-            };
-
-            Self::render_entry_visual_element(
-                &details,
-                Some(editor),
-                padding,
-                row_container_style,
-                &style,
-                cx,
-            )
-        })
-        .on_click(MouseButton::Left, move |event, this, cx| {
-            if !show_editor {
-                if kind.is_dir() {
-                    this.toggle_expanded(entry_id, cx);
-                } else {
-                    if event.cmd {
-                        this.split_entry(entry_id, cx);
-                    } else if !event.cmd {
-                        this.open_entry(entry_id, event.click_count > 1, cx);
-                    }
-                }
-            }
-        })
-        .on_down(MouseButton::Right, move |event, this, cx| {
-            this.deploy_context_menu(event.position, entry_id, cx);
-        })
-        .on_up(MouseButton::Left, move |_, this, cx| {
-            if let Some((_, dragged_entry)) = cx
-                .global::<DragAndDrop<Workspace>>()
-                .currently_dragged::<ProjectEntryId>(cx.window())
-            {
-                this.move_entry(
-                    *dragged_entry,
-                    entry_id,
-                    matches!(details.kind, EntryKind::File(_)),
-                    cx,
-                );
-            }
-        })
-        .on_move(move |_, this, cx| {
-            if cx
-                .global::<DragAndDrop<Workspace>>()
-                .currently_dragged::<ProjectEntryId>(cx.window())
-                .is_some()
-            {
-                this.dragged_entry_destination = if matches!(kind, EntryKind::File(_)) {
-                    path.parent().map(|parent| Arc::from(parent))
-                } else {
-                    Some(path.clone())
-                };
-            }
-        })
-        .as_draggable(entry_id, {
-            let row_container_style = theme.dragged_entry.container;
-
-            move |_, _, cx: &mut ViewContext<Workspace>| {
-                let theme = theme::current(cx).clone();
-                Self::render_entry_visual_element(
-                    &details,
-                    None,
-                    padding,
-                    row_container_style,
-                    &theme.project_panel.dragged_entry,
-                    cx,
-                )
-            }
-        })
-        .with_cursor_style(CursorStyle::PointingHand)
-        .into_any_named("project panel entry")
+        dispatch_context.add(identifier);
+        dispatch_context
     }
 
     fn reveal_entry(
         &mut self,
-        project: ModelHandle<Project>,
+        project: Model<Project>,
         entry_id: ProjectEntryId,
         skip_ignored: bool,
-        cx: &mut ViewContext<'_, '_, ProjectPanel>,
+        cx: &mut ViewContext<'_, ProjectPanel>,
     ) {
         if let Some(worktree) = project.read(cx).worktree_for_entry(entry_id, cx) {
             let worktree = worktree.read(cx);

crates/project_panel/src/project_panel_settings.rs 🔗

@@ -1,7 +1,8 @@
 use anyhow;
+use gpui::Pixels;
 use schemars::JsonSchema;
 use serde_derive::{Deserialize, Serialize};
-use settings::Setting;
+use settings::Settings;
 
 #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
 #[serde(rename_all = "snake_case")]
@@ -12,7 +13,7 @@ pub enum ProjectPanelDockPosition {
 
 #[derive(Deserialize, Debug)]
 pub struct ProjectPanelSettings {
-    pub default_width: f32,
+    pub default_width: Pixels,
     pub dock: ProjectPanelDockPosition,
     pub file_icons: bool,
     pub folder_icons: bool,
@@ -32,7 +33,7 @@ pub struct ProjectPanelSettingsContent {
     pub auto_reveal_entries: Option<bool>,
 }
 
-impl Setting for ProjectPanelSettings {
+impl Settings for ProjectPanelSettings {
     const KEY: Option<&'static str> = Some("project_panel");
 
     type FileContent = ProjectPanelSettingsContent;
@@ -40,7 +41,7 @@ impl Setting for ProjectPanelSettings {
     fn load(
         default_value: &Self::FileContent,
         user_values: &[&Self::FileContent],
-        _: &gpui::AppContext,
+        _: &mut gpui::AppContext,
     ) -> anyhow::Result<Self> {
         Self::load_via_json_merge(default_value, user_values)
     }

crates/project_panel2/Cargo.toml 🔗

@@ -1,41 +0,0 @@
-[package]
-name = "project_panel2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/project_panel.rs"
-doctest = false
-
-[dependencies]
-collections = { path = "../collections" }
-db = { path = "../db2", package = "db2" }
-editor = { path = "../editor2", package = "editor2" }
-gpui = { path = "../gpui2", package = "gpui2" }
-menu = { path = "../menu2", package = "menu2" }
-project = { path = "../project2", package = "project2" }
-search = { package = "search2", path = "../search2" }
-settings = { path = "../settings2", package = "settings2" }
-theme = { path = "../theme2", package = "theme2" }
-ui = { path = "../ui2", package = "ui2" }
-util = { path = "../util" }
-workspace = { path = "../workspace2", package = "workspace2" }
-anyhow.workspace = true
-postage.workspace = true
-futures.workspace = true
-serde.workspace = true
-serde_derive.workspace = true
-serde_json.workspace = true
-schemars.workspace = true
-smallvec.workspace = true
-pretty_assertions.workspace = true
-unicase = "2.6"
-
-[dev-dependencies]
-client = { path = "../client2", package = "client2", features = ["test-support"] }
-language = { path = "../language2", package = "language2", features = ["test-support"] }
-editor = { path = "../editor2", package = "editor2", features = ["test-support"] }
-gpui = { path = "../gpui2", package = "gpui2", features = ["test-support"] }
-workspace = { path = "../workspace2", package = "workspace2", features = ["test-support"] }
-serde_json.workspace = true

crates/project_panel2/src/file_associations.rs 🔗

@@ -1,87 +0,0 @@
-use std::{path::Path, str, sync::Arc};
-
-use collections::HashMap;
-
-use gpui::{AppContext, AssetSource};
-use serde_derive::Deserialize;
-use util::{maybe, paths::PathExt};
-
-#[derive(Deserialize, Debug)]
-struct TypeConfig {
-    icon: Arc<str>,
-}
-
-#[derive(Deserialize, Debug)]
-pub struct FileAssociations {
-    suffixes: HashMap<String, String>,
-    types: HashMap<String, TypeConfig>,
-}
-
-const COLLAPSED_DIRECTORY_TYPE: &'static str = "collapsed_folder";
-const EXPANDED_DIRECTORY_TYPE: &'static str = "expanded_folder";
-const COLLAPSED_CHEVRON_TYPE: &'static str = "collapsed_chevron";
-const EXPANDED_CHEVRON_TYPE: &'static str = "expanded_chevron";
-pub const FILE_TYPES_ASSET: &'static str = "icons/file_icons/file_types.json";
-
-pub fn init(assets: impl AssetSource, cx: &mut AppContext) {
-    cx.set_global(FileAssociations::new(assets))
-}
-
-impl FileAssociations {
-    pub fn new(assets: impl AssetSource) -> Self {
-        assets
-            .load("icons/file_icons/file_types.json")
-            .and_then(|file| {
-                serde_json::from_str::<FileAssociations>(str::from_utf8(&file).unwrap())
-                    .map_err(Into::into)
-            })
-            .unwrap_or_else(|_| FileAssociations {
-                suffixes: HashMap::default(),
-                types: HashMap::default(),
-            })
-    }
-
-    pub fn get_icon(path: &Path, cx: &AppContext) -> Option<Arc<str>> {
-        let this = cx.has_global::<Self>().then(|| cx.global::<Self>())?;
-
-        // FIXME: Associate a type with the languages and have the file's langauge
-        //        override these associations
-        maybe!({
-            let suffix = path.icon_suffix()?;
-
-            this.suffixes
-                .get(suffix)
-                .and_then(|type_str| this.types.get(type_str))
-                .map(|type_config| type_config.icon.clone())
-        })
-        .or_else(|| this.types.get("default").map(|config| config.icon.clone()))
-    }
-
-    pub fn get_folder_icon(expanded: bool, cx: &AppContext) -> Option<Arc<str>> {
-        let this = cx.has_global::<Self>().then(|| cx.global::<Self>())?;
-
-        let key = if expanded {
-            EXPANDED_DIRECTORY_TYPE
-        } else {
-            COLLAPSED_DIRECTORY_TYPE
-        };
-
-        this.types
-            .get(key)
-            .map(|type_config| type_config.icon.clone())
-    }
-
-    pub fn get_chevron_icon(expanded: bool, cx: &AppContext) -> Option<Arc<str>> {
-        let this = cx.has_global::<Self>().then(|| cx.global::<Self>())?;
-
-        let key = if expanded {
-            EXPANDED_CHEVRON_TYPE
-        } else {
-            COLLAPSED_CHEVRON_TYPE
-        };
-
-        this.types
-            .get(key)
-            .map(|type_config| type_config.icon.clone())
-    }
-}

crates/project_panel2/src/project_panel.rs 🔗

@@ -1,3480 +0,0 @@
-pub mod file_associations;
-mod project_panel_settings;
-use settings::{Settings, SettingsStore};
-
-use db::kvp::KEY_VALUE_STORE;
-use editor::{scroll::autoscroll::Autoscroll, Cancel, Editor};
-use file_associations::FileAssociations;
-
-use anyhow::{anyhow, Result};
-use gpui::{
-    actions, div, overlay, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext,
-    ClipboardItem, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
-    KeyContext, Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel,
-    Render, Stateful, Styled, Subscription, Task, UniformListScrollHandle, View, ViewContext,
-    VisualContext as _, WeakView, WindowContext,
-};
-use menu::{Confirm, SelectNext, SelectPrev};
-use project::{
-    repository::GitFileStatus, Entry, EntryKind, Fs, Project, ProjectEntryId, ProjectPath,
-    Worktree, WorktreeId,
-};
-use project_panel_settings::{ProjectPanelDockPosition, ProjectPanelSettings};
-use serde::{Deserialize, Serialize};
-use std::{
-    cmp::Ordering,
-    collections::{hash_map, HashMap},
-    ffi::OsStr,
-    ops::Range,
-    path::Path,
-    sync::Arc,
-};
-use theme::ThemeSettings;
-use ui::{prelude::*, v_stack, ContextMenu, IconElement, Label, ListItem};
-use unicase::UniCase;
-use util::{maybe, ResultExt, TryFutureExt};
-use workspace::{
-    dock::{DockPosition, Panel, PanelEvent},
-    Workspace,
-};
-
-const PROJECT_PANEL_KEY: &'static str = "ProjectPanel";
-const NEW_ENTRY_ID: ProjectEntryId = ProjectEntryId::MAX;
-
-pub struct ProjectPanel {
-    project: Model<Project>,
-    fs: Arc<dyn Fs>,
-    list: UniformListScrollHandle,
-    focus_handle: FocusHandle,
-    visible_entries: Vec<(WorktreeId, Vec<Entry>)>,
-    last_worktree_root_id: Option<ProjectEntryId>,
-    expanded_dir_ids: HashMap<WorktreeId, Vec<ProjectEntryId>>,
-    selection: Option<Selection>,
-    context_menu: Option<(View<ContextMenu>, Point<Pixels>, Subscription)>,
-    edit_state: Option<EditState>,
-    filename_editor: View<Editor>,
-    clipboard_entry: Option<ClipboardEntry>,
-    _dragged_entry_destination: Option<Arc<Path>>,
-    workspace: WeakView<Workspace>,
-    width: Option<Pixels>,
-    pending_serialization: Task<Option<()>>,
-}
-
-#[derive(Copy, Clone, Debug)]
-struct Selection {
-    worktree_id: WorktreeId,
-    entry_id: ProjectEntryId,
-}
-
-#[derive(Clone, Debug)]
-struct EditState {
-    worktree_id: WorktreeId,
-    entry_id: ProjectEntryId,
-    is_new_entry: bool,
-    is_dir: bool,
-    processing_filename: Option<String>,
-}
-
-#[derive(Copy, Clone)]
-pub enum ClipboardEntry {
-    Copied {
-        worktree_id: WorktreeId,
-        entry_id: ProjectEntryId,
-    },
-    Cut {
-        worktree_id: WorktreeId,
-        entry_id: ProjectEntryId,
-    },
-}
-
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct EntryDetails {
-    filename: String,
-    icon: Option<Arc<str>>,
-    path: Arc<Path>,
-    depth: usize,
-    kind: EntryKind,
-    is_ignored: bool,
-    is_expanded: bool,
-    is_selected: bool,
-    is_editing: bool,
-    is_processing: bool,
-    is_cut: bool,
-    git_status: Option<GitFileStatus>,
-}
-
-actions!(
-    project_panel,
-    [
-        ExpandSelectedEntry,
-        CollapseSelectedEntry,
-        CollapseAllEntries,
-        NewDirectory,
-        NewFile,
-        Copy,
-        CopyPath,
-        CopyRelativePath,
-        RevealInFinder,
-        OpenInTerminal,
-        Cut,
-        Paste,
-        Delete,
-        Rename,
-        Open,
-        ToggleFocus,
-        NewSearchInDirectory,
-    ]
-);
-
-pub fn init_settings(cx: &mut AppContext) {
-    ProjectPanelSettings::register(cx);
-}
-
-pub fn init(assets: impl AssetSource, cx: &mut AppContext) {
-    init_settings(cx);
-    file_associations::init(assets, cx);
-
-    cx.observe_new_views(|workspace: &mut Workspace, _| {
-        workspace.register_action(|workspace, _: &ToggleFocus, cx| {
-            workspace.toggle_panel_focus::<ProjectPanel>(cx);
-        });
-    })
-    .detach();
-}
-
-#[derive(Debug)]
-pub enum Event {
-    OpenedEntry {
-        entry_id: ProjectEntryId,
-        focus_opened_item: bool,
-    },
-    SplitEntry {
-        entry_id: ProjectEntryId,
-    },
-    Focus,
-}
-
-#[derive(Serialize, Deserialize)]
-struct SerializedProjectPanel {
-    width: Option<Pixels>,
-}
-
-struct DraggedProjectEntryView {
-    entry_id: ProjectEntryId,
-    details: EntryDetails,
-    width: Pixels,
-}
-
-impl ProjectPanel {
-    fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
-        let project = workspace.project().clone();
-        let project_panel = cx.new_view(|cx: &mut ViewContext<Self>| {
-            cx.observe(&project, |this, _, cx| {
-                this.update_visible_entries(None, cx);
-                cx.notify();
-            })
-            .detach();
-            let focus_handle = cx.focus_handle();
-
-            cx.on_focus(&focus_handle, Self::focus_in).detach();
-
-            cx.subscribe(&project, |this, project, event, cx| match event {
-                project::Event::ActiveEntryChanged(Some(entry_id)) => {
-                    if ProjectPanelSettings::get_global(cx).auto_reveal_entries {
-                        this.reveal_entry(project, *entry_id, true, cx);
-                    }
-                }
-                project::Event::RevealInProjectPanel(entry_id) => {
-                    this.reveal_entry(project, *entry_id, false, cx);
-                    cx.emit(PanelEvent::Activate);
-                }
-                project::Event::ActivateProjectPanel => {
-                    cx.emit(PanelEvent::Activate);
-                }
-                project::Event::WorktreeRemoved(id) => {
-                    this.expanded_dir_ids.remove(id);
-                    this.update_visible_entries(None, cx);
-                    cx.notify();
-                }
-                _ => {}
-            })
-            .detach();
-
-            let filename_editor = cx.new_view(|cx| Editor::single_line(cx));
-
-            cx.subscribe(&filename_editor, |this, _, event, cx| match event {
-                editor::EditorEvent::BufferEdited
-                | editor::EditorEvent::SelectionsChanged { .. } => {
-                    this.autoscroll(cx);
-                }
-                editor::EditorEvent::Blurred => {
-                    if this
-                        .edit_state
-                        .as_ref()
-                        .map_or(false, |state| state.processing_filename.is_none())
-                    {
-                        this.edit_state = None;
-                        this.update_visible_entries(None, cx);
-                    }
-                }
-                _ => {}
-            })
-            .detach();
-
-            // cx.observe_global::<FileAssociations, _>(|_, cx| {
-            //     cx.notify();
-            // })
-            // .detach();
-
-            let mut this = Self {
-                project: project.clone(),
-                fs: workspace.app_state().fs.clone(),
-                list: UniformListScrollHandle::new(),
-                focus_handle,
-                visible_entries: Default::default(),
-                last_worktree_root_id: Default::default(),
-                expanded_dir_ids: Default::default(),
-                selection: None,
-                edit_state: None,
-                context_menu: None,
-                filename_editor,
-                clipboard_entry: None,
-                _dragged_entry_destination: None,
-                workspace: workspace.weak_handle(),
-                width: None,
-                pending_serialization: Task::ready(None),
-            };
-            this.update_visible_entries(None, cx);
-
-            // Update the dock position when the setting changes.
-            let mut old_dock_position = this.position(cx);
-            ProjectPanelSettings::register(cx);
-            cx.observe_global::<SettingsStore>(move |this, cx| {
-                let new_dock_position = this.position(cx);
-                if new_dock_position != old_dock_position {
-                    old_dock_position = new_dock_position;
-                    cx.emit(PanelEvent::ChangePosition);
-                }
-            })
-            .detach();
-
-            this
-        });
-
-        cx.subscribe(&project_panel, {
-            let project_panel = project_panel.downgrade();
-            move |workspace, _, event, cx| match event {
-                &Event::OpenedEntry {
-                    entry_id,
-                    focus_opened_item,
-                } => {
-                    if let Some(worktree) = project.read(cx).worktree_for_entry(entry_id, cx) {
-                        if let Some(entry) = worktree.read(cx).entry_for_id(entry_id) {
-                            workspace
-                                .open_path(
-                                    ProjectPath {
-                                        worktree_id: worktree.read(cx).id(),
-                                        path: entry.path.clone(),
-                                    },
-                                    None,
-                                    focus_opened_item,
-                                    cx,
-                                )
-                                .detach_and_log_err(cx);
-                            if !focus_opened_item {
-                                if let Some(project_panel) = project_panel.upgrade() {
-                                    let focus_handle = project_panel.read(cx).focus_handle.clone();
-                                    cx.focus(&focus_handle);
-                                }
-                            }
-                        }
-                    }
-                }
-                &Event::SplitEntry { entry_id } => {
-                    if let Some(worktree) = project.read(cx).worktree_for_entry(entry_id, cx) {
-                        if let Some(_entry) = worktree.read(cx).entry_for_id(entry_id) {
-                            // workspace
-                            //     .split_path(
-                            //         ProjectPath {
-                            //             worktree_id: worktree.read(cx).id(),
-                            //             path: entry.path.clone(),
-                            //         },
-                            //         cx,
-                            //     )
-                            //     .detach_and_log_err(cx);
-                        }
-                    }
-                }
-                _ => {}
-            }
-        })
-        .detach();
-
-        project_panel
-    }
-
-    pub async fn load(
-        workspace: WeakView<Workspace>,
-        mut cx: AsyncWindowContext,
-    ) -> Result<View<Self>> {
-        let serialized_panel = cx
-            .background_executor()
-            .spawn(async move { KEY_VALUE_STORE.read_kvp(PROJECT_PANEL_KEY) })
-            .await
-            .map_err(|e| anyhow!("Failed to load project panel: {}", e))
-            .log_err()
-            .flatten()
-            .map(|panel| serde_json::from_str::<SerializedProjectPanel>(&panel))
-            .transpose()
-            .log_err()
-            .flatten();
-
-        workspace.update(&mut cx, |workspace, cx| {
-            let panel = ProjectPanel::new(workspace, cx);
-            if let Some(serialized_panel) = serialized_panel {
-                panel.update(cx, |panel, cx| {
-                    panel.width = serialized_panel.width;
-                    cx.notify();
-                });
-            }
-            panel
-        })
-    }
-
-    fn serialize(&mut self, cx: &mut ViewContext<Self>) {
-        let width = self.width;
-        self.pending_serialization = cx.background_executor().spawn(
-            async move {
-                KEY_VALUE_STORE
-                    .write_kvp(
-                        PROJECT_PANEL_KEY.into(),
-                        serde_json::to_string(&SerializedProjectPanel { width })?,
-                    )
-                    .await?;
-                anyhow::Ok(())
-            }
-            .log_err(),
-        );
-    }
-
-    fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
-        if !self.focus_handle.contains_focused(cx) {
-            cx.emit(Event::Focus);
-        }
-    }
-
-    fn deploy_context_menu(
-        &mut self,
-        position: Point<Pixels>,
-        entry_id: ProjectEntryId,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let this = cx.view().clone();
-        let project = self.project.read(cx);
-
-        let worktree_id = if let Some(id) = project.worktree_id_for_entry(entry_id, cx) {
-            id
-        } else {
-            return;
-        };
-
-        self.selection = Some(Selection {
-            worktree_id,
-            entry_id,
-        });
-
-        if let Some((worktree, entry)) = self.selected_entry(cx) {
-            let is_root = Some(entry) == worktree.root_entry();
-            let is_dir = entry.is_dir();
-            let worktree_id = worktree.id();
-            let is_local = project.is_local();
-
-            let context_menu = ContextMenu::build(cx, |mut menu, cx| {
-                if is_local {
-                    menu = menu.action(
-                        "Add Folder to Project",
-                        Box::new(workspace::AddFolderToProject),
-                    );
-                    if is_root {
-                        menu = menu.entry(
-                            "Remove from Project",
-                            None,
-                            cx.handler_for(&this, move |this, cx| {
-                                this.project.update(cx, |project, cx| {
-                                    project.remove_worktree(worktree_id, cx)
-                                });
-                            }),
-                        );
-                    }
-                }
-
-                menu = menu
-                    .action("New File", Box::new(NewFile))
-                    .action("New Folder", Box::new(NewDirectory))
-                    .separator()
-                    .action("Cut", Box::new(Cut))
-                    .action("Copy", Box::new(Copy));
-
-                if let Some(clipboard_entry) = self.clipboard_entry {
-                    if clipboard_entry.worktree_id() == worktree_id {
-                        menu = menu.action("Paste", Box::new(Paste));
-                    }
-                }
-
-                menu = menu
-                    .separator()
-                    .action("Copy Path", Box::new(CopyPath))
-                    .action("Copy Relative Path", Box::new(CopyRelativePath))
-                    .separator()
-                    .action("Reveal in Finder", Box::new(RevealInFinder));
-
-                if is_dir {
-                    menu = menu
-                        .action("Open in Terminal", Box::new(OpenInTerminal))
-                        .action("Search Inside", Box::new(NewSearchInDirectory))
-                }
-
-                menu = menu.separator().action("Rename", Box::new(Rename));
-
-                if !is_root {
-                    menu = menu.action("Delete", Box::new(Delete));
-                }
-
-                menu
-            });
-
-            cx.focus_view(&context_menu);
-            let subscription = cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| {
-                this.context_menu.take();
-                cx.notify();
-            });
-            self.context_menu = Some((context_menu, position, subscription));
-        }
-
-        cx.notify();
-    }
-
-    fn expand_selected_entry(&mut self, _: &ExpandSelectedEntry, cx: &mut ViewContext<Self>) {
-        if let Some((worktree, entry)) = self.selected_entry(cx) {
-            if entry.is_dir() {
-                let worktree_id = worktree.id();
-                let entry_id = entry.id;
-                let expanded_dir_ids =
-                    if let Some(expanded_dir_ids) = self.expanded_dir_ids.get_mut(&worktree_id) {
-                        expanded_dir_ids
-                    } else {
-                        return;
-                    };
-
-                match expanded_dir_ids.binary_search(&entry_id) {
-                    Ok(_) => self.select_next(&SelectNext, cx),
-                    Err(ix) => {
-                        self.project.update(cx, |project, cx| {
-                            project.expand_entry(worktree_id, entry_id, cx);
-                        });
-
-                        expanded_dir_ids.insert(ix, entry_id);
-                        self.update_visible_entries(None, cx);
-                        cx.notify();
-                    }
-                }
-            }
-        }
-    }
-
-    fn collapse_selected_entry(&mut self, _: &CollapseSelectedEntry, cx: &mut ViewContext<Self>) {
-        if let Some((worktree, mut entry)) = self.selected_entry(cx) {
-            let worktree_id = worktree.id();
-            let expanded_dir_ids =
-                if let Some(expanded_dir_ids) = self.expanded_dir_ids.get_mut(&worktree_id) {
-                    expanded_dir_ids
-                } else {
-                    return;
-                };
-
-            loop {
-                let entry_id = entry.id;
-                match expanded_dir_ids.binary_search(&entry_id) {
-                    Ok(ix) => {
-                        expanded_dir_ids.remove(ix);
-                        self.update_visible_entries(Some((worktree_id, entry_id)), cx);
-                        cx.notify();
-                        break;
-                    }
-                    Err(_) => {
-                        if let Some(parent_entry) =
-                            entry.path.parent().and_then(|p| worktree.entry_for_path(p))
-                        {
-                            entry = parent_entry;
-                        } else {
-                            break;
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    pub fn collapse_all_entries(&mut self, _: &CollapseAllEntries, cx: &mut ViewContext<Self>) {
-        self.expanded_dir_ids.clear();
-        self.update_visible_entries(None, cx);
-        cx.notify();
-    }
-
-    fn toggle_expanded(&mut self, entry_id: ProjectEntryId, cx: &mut ViewContext<Self>) {
-        if let Some(worktree_id) = self.project.read(cx).worktree_id_for_entry(entry_id, cx) {
-            if let Some(expanded_dir_ids) = self.expanded_dir_ids.get_mut(&worktree_id) {
-                self.project.update(cx, |project, cx| {
-                    match expanded_dir_ids.binary_search(&entry_id) {
-                        Ok(ix) => {
-                            expanded_dir_ids.remove(ix);
-                        }
-                        Err(ix) => {
-                            project.expand_entry(worktree_id, entry_id, cx);
-                            expanded_dir_ids.insert(ix, entry_id);
-                        }
-                    }
-                });
-                self.update_visible_entries(Some((worktree_id, entry_id)), cx);
-                cx.focus(&self.focus_handle);
-                cx.notify();
-            }
-        }
-    }
-
-    fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
-        if let Some(selection) = self.selection {
-            let (mut worktree_ix, mut entry_ix, _) =
-                self.index_for_selection(selection).unwrap_or_default();
-            if entry_ix > 0 {
-                entry_ix -= 1;
-            } else if worktree_ix > 0 {
-                worktree_ix -= 1;
-                entry_ix = self.visible_entries[worktree_ix].1.len() - 1;
-            } else {
-                return;
-            }
-
-            let (worktree_id, worktree_entries) = &self.visible_entries[worktree_ix];
-            self.selection = Some(Selection {
-                worktree_id: *worktree_id,
-                entry_id: worktree_entries[entry_ix].id,
-            });
-            self.autoscroll(cx);
-            cx.notify();
-        } else {
-            self.select_first(cx);
-        }
-    }
-
-    fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
-        if let Some(task) = self.confirm_edit(cx) {
-            task.detach_and_log_err(cx);
-        }
-    }
-
-    fn open_file(&mut self, _: &Open, cx: &mut ViewContext<Self>) {
-        if let Some((_, entry)) = self.selected_entry(cx) {
-            if entry.is_file() {
-                self.open_entry(entry.id, true, cx);
-            }
-        }
-    }
-
-    fn confirm_edit(&mut self, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
-        let edit_state = self.edit_state.as_mut()?;
-        cx.focus(&self.focus_handle);
-
-        let worktree_id = edit_state.worktree_id;
-        let is_new_entry = edit_state.is_new_entry;
-        let is_dir = edit_state.is_dir;
-        let worktree = self.project.read(cx).worktree_for_id(worktree_id, cx)?;
-        let entry = worktree.read(cx).entry_for_id(edit_state.entry_id)?.clone();
-        let filename = self.filename_editor.read(cx).text(cx);
-
-        let path_already_exists = |path| worktree.read(cx).entry_for_path(path).is_some();
-        let edit_task;
-        let edited_entry_id;
-        if is_new_entry {
-            self.selection = Some(Selection {
-                worktree_id,
-                entry_id: NEW_ENTRY_ID,
-            });
-            let new_path = entry.path.join(&filename.trim_start_matches("/"));
-            if path_already_exists(new_path.as_path()) {
-                return None;
-            }
-
-            edited_entry_id = NEW_ENTRY_ID;
-            edit_task = self.project.update(cx, |project, cx| {
-                project.create_entry((worktree_id, &new_path), is_dir, cx)
-            });
-        } else {
-            let new_path = if let Some(parent) = entry.path.clone().parent() {
-                parent.join(&filename)
-            } else {
-                filename.clone().into()
-            };
-            if path_already_exists(new_path.as_path()) {
-                return None;
-            }
-
-            edited_entry_id = entry.id;
-            edit_task = self.project.update(cx, |project, cx| {
-                project.rename_entry(entry.id, new_path.as_path(), cx)
-            });
-        };
-
-        edit_state.processing_filename = Some(filename);
-        cx.notify();
-
-        Some(cx.spawn(|this, mut cx| async move {
-            let new_entry = edit_task.await;
-            this.update(&mut cx, |this, cx| {
-                this.edit_state.take();
-                cx.notify();
-            })?;
-
-            if let Some(new_entry) = new_entry? {
-                this.update(&mut cx, |this, cx| {
-                    if let Some(selection) = &mut this.selection {
-                        if selection.entry_id == edited_entry_id {
-                            selection.worktree_id = worktree_id;
-                            selection.entry_id = new_entry.id;
-                            this.expand_to_selection(cx);
-                        }
-                    }
-                    this.update_visible_entries(None, cx);
-                    if is_new_entry && !is_dir {
-                        this.open_entry(new_entry.id, true, cx);
-                    }
-                    cx.notify();
-                })?;
-            }
-            Ok(())
-        }))
-    }
-
-    fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
-        self.edit_state = None;
-        self.update_visible_entries(None, cx);
-        cx.focus(&self.focus_handle);
-        cx.notify();
-    }
-
-    fn open_entry(
-        &mut self,
-        entry_id: ProjectEntryId,
-        focus_opened_item: bool,
-        cx: &mut ViewContext<Self>,
-    ) {
-        cx.emit(Event::OpenedEntry {
-            entry_id,
-            focus_opened_item,
-        });
-    }
-
-    fn split_entry(&mut self, entry_id: ProjectEntryId, cx: &mut ViewContext<Self>) {
-        cx.emit(Event::SplitEntry { entry_id });
-    }
-
-    fn new_file(&mut self, _: &NewFile, cx: &mut ViewContext<Self>) {
-        self.add_entry(false, cx)
-    }
-
-    fn new_directory(&mut self, _: &NewDirectory, cx: &mut ViewContext<Self>) {
-        self.add_entry(true, cx)
-    }
-
-    fn add_entry(&mut self, is_dir: bool, cx: &mut ViewContext<Self>) {
-        if let Some(Selection {
-            worktree_id,
-            entry_id,
-        }) = self.selection
-        {
-            let directory_id;
-            if let Some((worktree, expanded_dir_ids)) = self
-                .project
-                .read(cx)
-                .worktree_for_id(worktree_id, cx)
-                .zip(self.expanded_dir_ids.get_mut(&worktree_id))
-            {
-                let worktree = worktree.read(cx);
-                if let Some(mut entry) = worktree.entry_for_id(entry_id) {
-                    loop {
-                        if entry.is_dir() {
-                            if let Err(ix) = expanded_dir_ids.binary_search(&entry.id) {
-                                expanded_dir_ids.insert(ix, entry.id);
-                            }
-                            directory_id = entry.id;
-                            break;
-                        } else {
-                            if let Some(parent_path) = entry.path.parent() {
-                                if let Some(parent_entry) = worktree.entry_for_path(parent_path) {
-                                    entry = parent_entry;
-                                    continue;
-                                }
-                            }
-                            return;
-                        }
-                    }
-                } else {
-                    return;
-                };
-            } else {
-                return;
-            };
-
-            self.edit_state = Some(EditState {
-                worktree_id,
-                entry_id: directory_id,
-                is_new_entry: true,
-                is_dir,
-                processing_filename: None,
-            });
-            self.filename_editor.update(cx, |editor, cx| {
-                editor.clear(cx);
-                editor.focus(cx);
-            });
-            self.update_visible_entries(Some((worktree_id, NEW_ENTRY_ID)), cx);
-            self.autoscroll(cx);
-            cx.notify();
-        }
-    }
-
-    fn rename(&mut self, _: &Rename, cx: &mut ViewContext<Self>) {
-        if let Some(Selection {
-            worktree_id,
-            entry_id,
-        }) = self.selection
-        {
-            if let Some(worktree) = self.project.read(cx).worktree_for_id(worktree_id, cx) {
-                if let Some(entry) = worktree.read(cx).entry_for_id(entry_id) {
-                    self.edit_state = Some(EditState {
-                        worktree_id,
-                        entry_id,
-                        is_new_entry: false,
-                        is_dir: entry.is_dir(),
-                        processing_filename: None,
-                    });
-                    let file_name = entry
-                        .path
-                        .file_name()
-                        .map(|s| s.to_string_lossy())
-                        .unwrap_or_default()
-                        .to_string();
-                    let file_stem = entry.path.file_stem().map(|s| s.to_string_lossy());
-                    let selection_end =
-                        file_stem.map_or(file_name.len(), |file_stem| file_stem.len());
-                    self.filename_editor.update(cx, |editor, cx| {
-                        editor.set_text(file_name, cx);
-                        editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                            s.select_ranges([0..selection_end])
-                        });
-                        editor.focus(cx);
-                    });
-                    self.update_visible_entries(None, cx);
-                    self.autoscroll(cx);
-                    cx.notify();
-                }
-            }
-
-            // cx.update_global(|drag_and_drop: &mut DragAndDrop<Workspace>, cx| {
-            //     drag_and_drop.cancel_dragging::<ProjectEntryId>(cx);
-            // })
-        }
-    }
-
-    fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) {
-        maybe!({
-            let Selection { entry_id, .. } = self.selection?;
-            let path = self.project.read(cx).path_for_entry(entry_id, cx)?.path;
-            let file_name = path.file_name()?;
-
-            let answer = cx.prompt(
-                PromptLevel::Info,
-                &format!("Delete {file_name:?}?"),
-                &["Delete", "Cancel"],
-            );
-
-            cx.spawn(|this, mut cx| async move {
-                if answer.await != Ok(0) {
-                    return Ok(());
-                }
-                this.update(&mut cx, |this, cx| {
-                    this.project
-                        .update(cx, |project, cx| project.delete_entry(entry_id, cx))
-                        .ok_or_else(|| anyhow!("no such entry"))
-                })??
-                .await
-            })
-            .detach_and_log_err(cx);
-            Some(())
-        });
-    }
-
-    fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
-        if let Some(selection) = self.selection {
-            let (mut worktree_ix, mut entry_ix, _) =
-                self.index_for_selection(selection).unwrap_or_default();
-            if let Some((_, worktree_entries)) = self.visible_entries.get(worktree_ix) {
-                if entry_ix + 1 < worktree_entries.len() {
-                    entry_ix += 1;
-                } else {
-                    worktree_ix += 1;
-                    entry_ix = 0;
-                }
-            }
-
-            if let Some((worktree_id, worktree_entries)) = self.visible_entries.get(worktree_ix) {
-                if let Some(entry) = worktree_entries.get(entry_ix) {
-                    self.selection = Some(Selection {
-                        worktree_id: *worktree_id,
-                        entry_id: entry.id,
-                    });
-                    self.autoscroll(cx);
-                    cx.notify();
-                }
-            }
-        } else {
-            self.select_first(cx);
-        }
-    }
-
-    fn select_first(&mut self, cx: &mut ViewContext<Self>) {
-        let worktree = self
-            .visible_entries
-            .first()
-            .and_then(|(worktree_id, _)| self.project.read(cx).worktree_for_id(*worktree_id, cx));
-        if let Some(worktree) = worktree {
-            let worktree = worktree.read(cx);
-            let worktree_id = worktree.id();
-            if let Some(root_entry) = worktree.root_entry() {
-                self.selection = Some(Selection {
-                    worktree_id,
-                    entry_id: root_entry.id,
-                });
-                self.autoscroll(cx);
-                cx.notify();
-            }
-        }
-    }
-
-    fn autoscroll(&mut self, cx: &mut ViewContext<Self>) {
-        if let Some((_, _, index)) = self.selection.and_then(|s| self.index_for_selection(s)) {
-            self.list.scroll_to_item(index);
-            cx.notify();
-        }
-    }
-
-    fn cut(&mut self, _: &Cut, cx: &mut ViewContext<Self>) {
-        if let Some((worktree, entry)) = self.selected_entry(cx) {
-            self.clipboard_entry = Some(ClipboardEntry::Cut {
-                worktree_id: worktree.id(),
-                entry_id: entry.id,
-            });
-            cx.notify();
-        }
-    }
-
-    fn copy(&mut self, _: &Copy, cx: &mut ViewContext<Self>) {
-        if let Some((worktree, entry)) = self.selected_entry(cx) {
-            self.clipboard_entry = Some(ClipboardEntry::Copied {
-                worktree_id: worktree.id(),
-                entry_id: entry.id,
-            });
-            cx.notify();
-        }
-    }
-
-    fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
-        maybe!({
-            let (worktree, entry) = self.selected_entry(cx)?;
-            let clipboard_entry = self.clipboard_entry?;
-            if clipboard_entry.worktree_id() != worktree.id() {
-                return None;
-            }
-
-            let clipboard_entry_file_name = self
-                .project
-                .read(cx)
-                .path_for_entry(clipboard_entry.entry_id(), cx)?
-                .path
-                .file_name()?
-                .to_os_string();
-
-            let mut new_path = entry.path.to_path_buf();
-            if entry.is_file() {
-                new_path.pop();
-            }
-
-            new_path.push(&clipboard_entry_file_name);
-            let extension = new_path.extension().map(|e| e.to_os_string());
-            let file_name_without_extension = Path::new(&clipboard_entry_file_name).file_stem()?;
-            let mut ix = 0;
-            while worktree.entry_for_path(&new_path).is_some() {
-                new_path.pop();
-
-                let mut new_file_name = file_name_without_extension.to_os_string();
-                new_file_name.push(" copy");
-                if ix > 0 {
-                    new_file_name.push(format!(" {}", ix));
-                }
-                if let Some(extension) = extension.as_ref() {
-                    new_file_name.push(".");
-                    new_file_name.push(extension);
-                }
-
-                new_path.push(new_file_name);
-                ix += 1;
-            }
-
-            if clipboard_entry.is_cut() {
-                self.project
-                    .update(cx, |project, cx| {
-                        project.rename_entry(clipboard_entry.entry_id(), new_path, cx)
-                    })
-                    .detach_and_log_err(cx)
-            } else {
-                self.project
-                    .update(cx, |project, cx| {
-                        project.copy_entry(clipboard_entry.entry_id(), new_path, cx)
-                    })
-                    .detach_and_log_err(cx)
-            }
-
-            Some(())
-        });
-    }
-
-    fn copy_path(&mut self, _: &CopyPath, cx: &mut ViewContext<Self>) {
-        if let Some((worktree, entry)) = self.selected_entry(cx) {
-            cx.write_to_clipboard(ClipboardItem::new(
-                worktree
-                    .abs_path()
-                    .join(&entry.path)
-                    .to_string_lossy()
-                    .to_string(),
-            ));
-        }
-    }
-
-    fn copy_relative_path(&mut self, _: &CopyRelativePath, cx: &mut ViewContext<Self>) {
-        if let Some((_, entry)) = self.selected_entry(cx) {
-            cx.write_to_clipboard(ClipboardItem::new(entry.path.to_string_lossy().to_string()));
-        }
-    }
-
-    fn reveal_in_finder(&mut self, _: &RevealInFinder, cx: &mut ViewContext<Self>) {
-        if let Some((worktree, entry)) = self.selected_entry(cx) {
-            cx.reveal_path(&worktree.abs_path().join(&entry.path));
-        }
-    }
-
-    fn open_in_terminal(&mut self, _: &OpenInTerminal, _cx: &mut ViewContext<Self>) {
-        todo!()
-        // if let Some((worktree, entry)) = self.selected_entry(cx) {
-        //     let window = cx.window();
-        //     let view_id = cx.view_id();
-        //     let path = worktree.abs_path().join(&entry.path);
-
-        //     cx.app_context()
-        //         .spawn(|mut cx| async move {
-        //             window.dispatch_action(
-        //                 view_id,
-        //                 &workspace::OpenTerminal {
-        //                     working_directory: path,
-        //                 },
-        //                 &mut cx,
-        //             );
-        //         })
-        //         .detach();
-        // }
-    }
-
-    pub fn new_search_in_directory(
-        &mut self,
-        _: &NewSearchInDirectory,
-        cx: &mut ViewContext<Self>,
-    ) {
-        if let Some((_, entry)) = self.selected_entry(cx) {
-            if entry.is_dir() {
-                let entry = entry.clone();
-                self.workspace
-                    .update(cx, |workspace, cx| {
-                        search::ProjectSearchView::new_search_in_directory(workspace, &entry, cx);
-                    })
-                    .ok();
-            }
-        }
-    }
-
-    fn move_entry(
-        &mut self,
-        entry_to_move: ProjectEntryId,
-        destination: ProjectEntryId,
-        destination_is_file: bool,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let destination_worktree = self.project.update(cx, |project, cx| {
-            let entry_path = project.path_for_entry(entry_to_move, cx)?;
-            let destination_entry_path = project.path_for_entry(destination, cx)?.path.clone();
-
-            let mut destination_path = destination_entry_path.as_ref();
-            if destination_is_file {
-                destination_path = destination_path.parent()?;
-            }
-
-            let mut new_path = destination_path.to_path_buf();
-            new_path.push(entry_path.path.file_name()?);
-            if new_path != entry_path.path.as_ref() {
-                let task = project.rename_entry(entry_to_move, new_path, cx);
-                cx.foreground_executor().spawn(task).detach_and_log_err(cx);
-            }
-
-            Some(project.worktree_id_for_entry(destination, cx)?)
-        });
-
-        if let Some(destination_worktree) = destination_worktree {
-            self.expand_entry(destination_worktree, destination, cx);
-        }
-    }
-
-    fn index_for_selection(&self, selection: Selection) -> Option<(usize, usize, usize)> {
-        let mut entry_index = 0;
-        let mut visible_entries_index = 0;
-        for (worktree_index, (worktree_id, worktree_entries)) in
-            self.visible_entries.iter().enumerate()
-        {
-            if *worktree_id == selection.worktree_id {
-                for entry in worktree_entries {
-                    if entry.id == selection.entry_id {
-                        return Some((worktree_index, entry_index, visible_entries_index));
-                    } else {
-                        visible_entries_index += 1;
-                        entry_index += 1;
-                    }
-                }
-                break;
-            } else {
-                visible_entries_index += worktree_entries.len();
-            }
-        }
-        None
-    }
-
-    pub fn selected_entry<'a>(
-        &self,
-        cx: &'a AppContext,
-    ) -> Option<(&'a Worktree, &'a project::Entry)> {
-        let (worktree, entry) = self.selected_entry_handle(cx)?;
-        Some((worktree.read(cx), entry))
-    }
-
-    fn selected_entry_handle<'a>(
-        &self,
-        cx: &'a AppContext,
-    ) -> Option<(Model<Worktree>, &'a project::Entry)> {
-        let selection = self.selection?;
-        let project = self.project.read(cx);
-        let worktree = project.worktree_for_id(selection.worktree_id, cx)?;
-        let entry = worktree.read(cx).entry_for_id(selection.entry_id)?;
-        Some((worktree, entry))
-    }
-
-    fn expand_to_selection(&mut self, cx: &mut ViewContext<Self>) -> Option<()> {
-        let (worktree, entry) = self.selected_entry(cx)?;
-        let expanded_dir_ids = self.expanded_dir_ids.entry(worktree.id()).or_default();
-
-        for path in entry.path.ancestors() {
-            let Some(entry) = worktree.entry_for_path(path) else {
-                continue;
-            };
-            if entry.is_dir() {
-                if let Err(idx) = expanded_dir_ids.binary_search(&entry.id) {
-                    expanded_dir_ids.insert(idx, entry.id);
-                }
-            }
-        }
-
-        Some(())
-    }
-
-    fn update_visible_entries(
-        &mut self,
-        new_selected_entry: Option<(WorktreeId, ProjectEntryId)>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let project = self.project.read(cx);
-        self.last_worktree_root_id = project
-            .visible_worktrees(cx)
-            .rev()
-            .next()
-            .and_then(|worktree| worktree.read(cx).root_entry())
-            .map(|entry| entry.id);
-
-        self.visible_entries.clear();
-        for worktree in project.visible_worktrees(cx) {
-            let snapshot = worktree.read(cx).snapshot();
-            let worktree_id = snapshot.id();
-
-            let expanded_dir_ids = match self.expanded_dir_ids.entry(worktree_id) {
-                hash_map::Entry::Occupied(e) => e.into_mut(),
-                hash_map::Entry::Vacant(e) => {
-                    // The first time a worktree's root entry becomes available,
-                    // mark that root entry as expanded.
-                    if let Some(entry) = snapshot.root_entry() {
-                        e.insert(vec![entry.id]).as_slice()
-                    } else {
-                        &[]
-                    }
-                }
-            };
-
-            let mut new_entry_parent_id = None;
-            let mut new_entry_kind = EntryKind::Dir;
-            if let Some(edit_state) = &self.edit_state {
-                if edit_state.worktree_id == worktree_id && edit_state.is_new_entry {
-                    new_entry_parent_id = Some(edit_state.entry_id);
-                    new_entry_kind = if edit_state.is_dir {
-                        EntryKind::Dir
-                    } else {
-                        EntryKind::File(Default::default())
-                    };
-                }
-            }
-
-            let mut visible_worktree_entries = Vec::new();
-            let mut entry_iter = snapshot.entries(true);
-
-            while let Some(entry) = entry_iter.entry() {
-                visible_worktree_entries.push(entry.clone());
-                if Some(entry.id) == new_entry_parent_id {
-                    visible_worktree_entries.push(Entry {
-                        id: NEW_ENTRY_ID,
-                        kind: new_entry_kind,
-                        path: entry.path.join("\0").into(),
-                        inode: 0,
-                        mtime: entry.mtime,
-                        is_symlink: false,
-                        is_ignored: false,
-                        is_external: false,
-                        git_status: entry.git_status,
-                    });
-                }
-                if expanded_dir_ids.binary_search(&entry.id).is_err()
-                    && entry_iter.advance_to_sibling()
-                {
-                    continue;
-                }
-                entry_iter.advance();
-            }
-
-            snapshot.propagate_git_statuses(&mut visible_worktree_entries);
-
-            visible_worktree_entries.sort_by(|entry_a, entry_b| {
-                let mut components_a = entry_a.path.components().peekable();
-                let mut components_b = entry_b.path.components().peekable();
-                loop {
-                    match (components_a.next(), components_b.next()) {
-                        (Some(component_a), Some(component_b)) => {
-                            let a_is_file = components_a.peek().is_none() && entry_a.is_file();
-                            let b_is_file = components_b.peek().is_none() && entry_b.is_file();
-                            let ordering = a_is_file.cmp(&b_is_file).then_with(|| {
-                                let name_a =
-                                    UniCase::new(component_a.as_os_str().to_string_lossy());
-                                let name_b =
-                                    UniCase::new(component_b.as_os_str().to_string_lossy());
-                                name_a.cmp(&name_b)
-                            });
-                            if !ordering.is_eq() {
-                                return ordering;
-                            }
-                        }
-                        (Some(_), None) => break Ordering::Greater,
-                        (None, Some(_)) => break Ordering::Less,
-                        (None, None) => break Ordering::Equal,
-                    }
-                }
-            });
-            self.visible_entries
-                .push((worktree_id, visible_worktree_entries));
-        }
-
-        if let Some((worktree_id, entry_id)) = new_selected_entry {
-            self.selection = Some(Selection {
-                worktree_id,
-                entry_id,
-            });
-        }
-    }
-
-    fn expand_entry(
-        &mut self,
-        worktree_id: WorktreeId,
-        entry_id: ProjectEntryId,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.project.update(cx, |project, cx| {
-            if let Some((worktree, expanded_dir_ids)) = project
-                .worktree_for_id(worktree_id, cx)
-                .zip(self.expanded_dir_ids.get_mut(&worktree_id))
-            {
-                project.expand_entry(worktree_id, entry_id, cx);
-                let worktree = worktree.read(cx);
-
-                if let Some(mut entry) = worktree.entry_for_id(entry_id) {
-                    loop {
-                        if let Err(ix) = expanded_dir_ids.binary_search(&entry.id) {
-                            expanded_dir_ids.insert(ix, entry.id);
-                        }
-
-                        if let Some(parent_entry) =
-                            entry.path.parent().and_then(|p| worktree.entry_for_path(p))
-                        {
-                            entry = parent_entry;
-                        } else {
-                            break;
-                        }
-                    }
-                }
-            }
-        });
-    }
-
-    fn for_each_visible_entry(
-        &self,
-        range: Range<usize>,
-        cx: &mut ViewContext<ProjectPanel>,
-        mut callback: impl FnMut(ProjectEntryId, EntryDetails, &mut ViewContext<ProjectPanel>),
-    ) {
-        let mut ix = 0;
-        for (worktree_id, visible_worktree_entries) in &self.visible_entries {
-            if ix >= range.end {
-                return;
-            }
-
-            if ix + visible_worktree_entries.len() <= range.start {
-                ix += visible_worktree_entries.len();
-                continue;
-            }
-
-            let end_ix = range.end.min(ix + visible_worktree_entries.len());
-            let (git_status_setting, show_file_icons, show_folder_icons) = {
-                let settings = ProjectPanelSettings::get_global(cx);
-                (
-                    settings.git_status,
-                    settings.file_icons,
-                    settings.folder_icons,
-                )
-            };
-            if let Some(worktree) = self.project.read(cx).worktree_for_id(*worktree_id, cx) {
-                let snapshot = worktree.read(cx).snapshot();
-                let root_name = OsStr::new(snapshot.root_name());
-                let expanded_entry_ids = self
-                    .expanded_dir_ids
-                    .get(&snapshot.id())
-                    .map(Vec::as_slice)
-                    .unwrap_or(&[]);
-
-                let entry_range = range.start.saturating_sub(ix)..end_ix - ix;
-                for entry in visible_worktree_entries[entry_range].iter() {
-                    let status = git_status_setting.then(|| entry.git_status).flatten();
-                    let is_expanded = expanded_entry_ids.binary_search(&entry.id).is_ok();
-                    let icon = match entry.kind {
-                        EntryKind::File(_) => {
-                            if show_file_icons {
-                                FileAssociations::get_icon(&entry.path, cx)
-                            } else {
-                                None
-                            }
-                        }
-                        _ => {
-                            if show_folder_icons {
-                                FileAssociations::get_folder_icon(is_expanded, cx)
-                            } else {
-                                FileAssociations::get_chevron_icon(is_expanded, cx)
-                            }
-                        }
-                    };
-
-                    let mut details = EntryDetails {
-                        filename: entry
-                            .path
-                            .file_name()
-                            .unwrap_or(root_name)
-                            .to_string_lossy()
-                            .to_string(),
-                        icon,
-                        path: entry.path.clone(),
-                        depth: entry.path.components().count(),
-                        kind: entry.kind,
-                        is_ignored: entry.is_ignored,
-                        is_expanded,
-                        is_selected: self.selection.map_or(false, |e| {
-                            e.worktree_id == snapshot.id() && e.entry_id == entry.id
-                        }),
-                        is_editing: false,
-                        is_processing: false,
-                        is_cut: self
-                            .clipboard_entry
-                            .map_or(false, |e| e.is_cut() && e.entry_id() == entry.id),
-                        git_status: status,
-                    };
-
-                    if let Some(edit_state) = &self.edit_state {
-                        let is_edited_entry = if edit_state.is_new_entry {
-                            entry.id == NEW_ENTRY_ID
-                        } else {
-                            entry.id == edit_state.entry_id
-                        };
-
-                        if is_edited_entry {
-                            if let Some(processing_filename) = &edit_state.processing_filename {
-                                details.is_processing = true;
-                                details.filename.clear();
-                                details.filename.push_str(processing_filename);
-                            } else {
-                                if edit_state.is_new_entry {
-                                    details.filename.clear();
-                                }
-                                details.is_editing = true;
-                            }
-                        }
-                    }
-
-                    callback(entry.id, details, cx);
-                }
-            }
-            ix = end_ix;
-        }
-    }
-
-    fn render_entry(
-        &self,
-        entry_id: ProjectEntryId,
-        details: EntryDetails,
-        cx: &mut ViewContext<Self>,
-    ) -> Stateful<Div> {
-        let kind = details.kind;
-        let settings = ProjectPanelSettings::get_global(cx);
-        let show_editor = details.is_editing && !details.is_processing;
-        let is_selected = self
-            .selection
-            .map_or(false, |selection| selection.entry_id == entry_id);
-        let width = self.width.unwrap_or(px(0.));
-
-        let filename_text_color = details
-            .git_status
-            .as_ref()
-            .map(|status| match status {
-                GitFileStatus::Added => Color::Created,
-                GitFileStatus::Modified => Color::Modified,
-                GitFileStatus::Conflict => Color::Conflict,
-            })
-            .unwrap_or(if is_selected {
-                Color::Default
-            } else {
-                Color::Muted
-            });
-
-        let file_name = details.filename.clone();
-        let icon = details.icon.clone();
-        let depth = details.depth;
-        div()
-            .id(entry_id.to_proto() as usize)
-            .on_drag(entry_id, move |entry_id, cx| {
-                cx.new_view(|_| DraggedProjectEntryView {
-                    details: details.clone(),
-                    width,
-                    entry_id: *entry_id,
-                })
-            })
-            .drag_over::<ProjectEntryId>(|style| {
-                style.bg(cx.theme().colors().drop_target_background)
-            })
-            .on_drop(cx.listener(move |this, dragged_id: &ProjectEntryId, cx| {
-                this.move_entry(*dragged_id, entry_id, kind.is_file(), cx);
-            }))
-            .child(
-                ListItem::new(entry_id.to_proto() as usize)
-                    .indent_level(depth)
-                    .indent_step_size(px(settings.indent_size))
-                    .selected(is_selected)
-                    .child(if let Some(icon) = &icon {
-                        div().child(IconElement::from_path(icon.to_string()).color(Color::Muted))
-                    } else {
-                        div()
-                    })
-                    .child(
-                        if let (Some(editor), true) = (Some(&self.filename_editor), show_editor) {
-                            div().h_full().w_full().child(editor.clone())
-                        } else {
-                            div().child(Label::new(file_name).color(filename_text_color))
-                        }
-                        .ml_1(),
-                    )
-                    .on_click(cx.listener(move |this, event: &gpui::ClickEvent, cx| {
-                        if event.down.button == MouseButton::Right {
-                            return;
-                        }
-                        if !show_editor {
-                            if kind.is_dir() {
-                                this.toggle_expanded(entry_id, cx);
-                            } else {
-                                if event.down.modifiers.command {
-                                    this.split_entry(entry_id, cx);
-                                } else {
-                                    this.open_entry(entry_id, event.up.click_count > 1, cx);
-                                }
-                            }
-                        }
-                    }))
-                    .on_secondary_mouse_down(cx.listener(
-                        move |this, event: &MouseDownEvent, cx| {
-                            this.deploy_context_menu(event.position, entry_id, cx);
-                        },
-                    )),
-            )
-    }
-
-    fn dispatch_context(&self, cx: &ViewContext<Self>) -> KeyContext {
-        let mut dispatch_context = KeyContext::default();
-        dispatch_context.add("ProjectPanel");
-        dispatch_context.add("menu");
-
-        let identifier = if self.filename_editor.focus_handle(cx).is_focused(cx) {
-            "editing"
-        } else {
-            "not_editing"
-        };
-
-        dispatch_context.add(identifier);
-        dispatch_context
-    }
-
-    fn reveal_entry(
-        &mut self,
-        project: Model<Project>,
-        entry_id: ProjectEntryId,
-        skip_ignored: bool,
-        cx: &mut ViewContext<'_, ProjectPanel>,
-    ) {
-        if let Some(worktree) = project.read(cx).worktree_for_entry(entry_id, cx) {
-            let worktree = worktree.read(cx);
-            if skip_ignored
-                && worktree
-                    .entry_for_id(entry_id)
-                    .map_or(true, |entry| entry.is_ignored)
-            {
-                return;
-            }
-
-            let worktree_id = worktree.id();
-            self.expand_entry(worktree_id, entry_id, cx);
-            self.update_visible_entries(Some((worktree_id, entry_id)), cx);
-            self.autoscroll(cx);
-            cx.notify();
-        }
-    }
-}
-
-impl Render for ProjectPanel {
-    fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
-        let has_worktree = self.visible_entries.len() != 0;
-
-        if has_worktree {
-            div()
-                .id("project-panel")
-                .size_full()
-                .relative()
-                .key_context(self.dispatch_context(cx))
-                .on_action(cx.listener(Self::select_next))
-                .on_action(cx.listener(Self::select_prev))
-                .on_action(cx.listener(Self::expand_selected_entry))
-                .on_action(cx.listener(Self::collapse_selected_entry))
-                .on_action(cx.listener(Self::collapse_all_entries))
-                .on_action(cx.listener(Self::new_file))
-                .on_action(cx.listener(Self::new_directory))
-                .on_action(cx.listener(Self::rename))
-                .on_action(cx.listener(Self::delete))
-                .on_action(cx.listener(Self::confirm))
-                .on_action(cx.listener(Self::open_file))
-                .on_action(cx.listener(Self::cancel))
-                .on_action(cx.listener(Self::cut))
-                .on_action(cx.listener(Self::copy))
-                .on_action(cx.listener(Self::copy_path))
-                .on_action(cx.listener(Self::copy_relative_path))
-                .on_action(cx.listener(Self::paste))
-                .on_action(cx.listener(Self::reveal_in_finder))
-                .on_action(cx.listener(Self::open_in_terminal))
-                .on_action(cx.listener(Self::new_search_in_directory))
-                .track_focus(&self.focus_handle)
-                .child(
-                    uniform_list(
-                        cx.view().clone(),
-                        "entries",
-                        self.visible_entries
-                            .iter()
-                            .map(|(_, worktree_entries)| worktree_entries.len())
-                            .sum(),
-                        {
-                            |this, range, cx| {
-                                let mut items = Vec::new();
-                                this.for_each_visible_entry(range, cx, |id, details, cx| {
-                                    items.push(this.render_entry(id, details, cx));
-                                });
-                                items
-                            }
-                        },
-                    )
-                    .size_full()
-                    .track_scroll(self.list.clone()),
-                )
-                .children(self.context_menu.as_ref().map(|(menu, position, _)| {
-                    overlay()
-                        .position(*position)
-                        .anchor(gpui::AnchorCorner::TopLeft)
-                        .child(menu.clone())
-                }))
-        } else {
-            v_stack()
-                .id("empty-project_panel")
-                .track_focus(&self.focus_handle)
-        }
-    }
-}
-
-impl Render for DraggedProjectEntryView {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element {
-        let settings = ProjectPanelSettings::get_global(cx);
-        let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone();
-        h_stack()
-            .font(ui_font)
-            .bg(cx.theme().colors().background)
-            .w(self.width)
-            .child(
-                ListItem::new(self.entry_id.to_proto() as usize)
-                    .indent_level(self.details.depth)
-                    .indent_step_size(px(settings.indent_size))
-                    .child(if let Some(icon) = &self.details.icon {
-                        div().child(IconElement::from_path(icon.to_string()))
-                    } else {
-                        div()
-                    })
-                    .child(Label::new(self.details.filename.clone())),
-            )
-    }
-}
-
-impl EventEmitter<Event> for ProjectPanel {}
-
-impl EventEmitter<PanelEvent> for ProjectPanel {}
-
-impl Panel for ProjectPanel {
-    fn position(&self, cx: &WindowContext) -> DockPosition {
-        match ProjectPanelSettings::get_global(cx).dock {
-            ProjectPanelDockPosition::Left => DockPosition::Left,
-            ProjectPanelDockPosition::Right => DockPosition::Right,
-        }
-    }
-
-    fn position_is_valid(&self, position: DockPosition) -> bool {
-        matches!(position, DockPosition::Left | DockPosition::Right)
-    }
-
-    fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
-        settings::update_settings_file::<ProjectPanelSettings>(
-            self.fs.clone(),
-            cx,
-            move |settings| {
-                let dock = match position {
-                    DockPosition::Left | DockPosition::Bottom => ProjectPanelDockPosition::Left,
-                    DockPosition::Right => ProjectPanelDockPosition::Right,
-                };
-                settings.dock = Some(dock);
-            },
-        );
-    }
-
-    fn size(&self, cx: &WindowContext) -> Pixels {
-        self.width
-            .unwrap_or_else(|| ProjectPanelSettings::get_global(cx).default_width)
-    }
-
-    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
-        self.width = size;
-        self.serialize(cx);
-        cx.notify();
-    }
-
-    fn icon(&self, _: &WindowContext) -> Option<ui::Icon> {
-        Some(ui::Icon::FileTree)
-    }
-
-    fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
-        Some("Project Panel")
-    }
-
-    fn toggle_action(&self) -> Box<dyn Action> {
-        Box::new(ToggleFocus)
-    }
-
-    fn persistent_name() -> &'static str {
-        "Project Panel"
-    }
-}
-
-impl FocusableView for ProjectPanel {
-    fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
-        self.focus_handle.clone()
-    }
-}
-
-impl ClipboardEntry {
-    fn is_cut(&self) -> bool {
-        matches!(self, Self::Cut { .. })
-    }
-
-    fn entry_id(&self) -> ProjectEntryId {
-        match self {
-            ClipboardEntry::Copied { entry_id, .. } | ClipboardEntry::Cut { entry_id, .. } => {
-                *entry_id
-            }
-        }
-    }
-
-    fn worktree_id(&self) -> WorktreeId {
-        match self {
-            ClipboardEntry::Copied { worktree_id, .. }
-            | ClipboardEntry::Cut { worktree_id, .. } => *worktree_id,
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use gpui::{TestAppContext, View, VisualTestContext, WindowHandle};
-    use pretty_assertions::assert_eq;
-    use project::{project_settings::ProjectSettings, FakeFs};
-    use serde_json::json;
-    use settings::SettingsStore;
-    use std::{
-        collections::HashSet,
-        path::{Path, PathBuf},
-    };
-    use workspace::AppState;
-
-    #[gpui::test]
-    async fn test_visible_list(cx: &mut gpui::TestAppContext) {
-        init_test(cx);
-
-        let fs = FakeFs::new(cx.executor().clone());
-        fs.insert_tree(
-            "/root1",
-            json!({
-                ".dockerignore": "",
-                ".git": {
-                    "HEAD": "",
-                },
-                "a": {
-                    "0": { "q": "", "r": "", "s": "" },
-                    "1": { "t": "", "u": "" },
-                    "2": { "v": "", "w": "", "x": "", "y": "" },
-                },
-                "b": {
-                    "3": { "Q": "" },
-                    "4": { "R": "", "S": "", "T": "", "U": "" },
-                },
-                "C": {
-                    "5": {},
-                    "6": { "V": "", "W": "" },
-                    "7": { "X": "" },
-                    "8": { "Y": {}, "Z": "" }
-                }
-            }),
-        )
-        .await;
-        fs.insert_tree(
-            "/root2",
-            json!({
-                "d": {
-                    "9": ""
-                },
-                "e": {}
-            }),
-        )
-        .await;
-
-        let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
-        let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let cx = &mut VisualTestContext::from_window(*workspace, cx);
-        let panel = workspace
-            .update(cx, |workspace, cx| ProjectPanel::new(workspace, cx))
-            .unwrap();
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..50, cx),
-            &[
-                "v root1",
-                "    > .git",
-                "    > a",
-                "    > b",
-                "    > C",
-                "      .dockerignore",
-                "v root2",
-                "    > d",
-                "    > e",
-            ]
-        );
-
-        toggle_expand_dir(&panel, "root1/b", cx);
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..50, cx),
-            &[
-                "v root1",
-                "    > .git",
-                "    > a",
-                "    v b  <== selected",
-                "        > 3",
-                "        > 4",
-                "    > C",
-                "      .dockerignore",
-                "v root2",
-                "    > d",
-                "    > e",
-            ]
-        );
-
-        assert_eq!(
-            visible_entries_as_strings(&panel, 6..9, cx),
-            &[
-                //
-                "    > C",
-                "      .dockerignore",
-                "v root2",
-            ]
-        );
-    }
-
-    #[gpui::test]
-    async fn test_exclusions_in_visible_list(cx: &mut gpui::TestAppContext) {
-        init_test(cx);
-        cx.update(|cx| {
-            cx.update_global::<SettingsStore, _>(|store, cx| {
-                store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
-                    project_settings.file_scan_exclusions =
-                        Some(vec!["**/.git".to_string(), "**/4/**".to_string()]);
-                });
-            });
-        });
-
-        let fs = FakeFs::new(cx.background_executor.clone());
-        fs.insert_tree(
-            "/root1",
-            json!({
-                ".dockerignore": "",
-                ".git": {
-                    "HEAD": "",
-                },
-                "a": {
-                    "0": { "q": "", "r": "", "s": "" },
-                    "1": { "t": "", "u": "" },
-                    "2": { "v": "", "w": "", "x": "", "y": "" },
-                },
-                "b": {
-                    "3": { "Q": "" },
-                    "4": { "R": "", "S": "", "T": "", "U": "" },
-                },
-                "C": {
-                    "5": {},
-                    "6": { "V": "", "W": "" },
-                    "7": { "X": "" },
-                    "8": { "Y": {}, "Z": "" }
-                }
-            }),
-        )
-        .await;
-        fs.insert_tree(
-            "/root2",
-            json!({
-                "d": {
-                    "4": ""
-                },
-                "e": {}
-            }),
-        )
-        .await;
-
-        let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
-        let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let cx = &mut VisualTestContext::from_window(*workspace, cx);
-        let panel = workspace
-            .update(cx, |workspace, cx| ProjectPanel::new(workspace, cx))
-            .unwrap();
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..50, cx),
-            &[
-                "v root1",
-                "    > a",
-                "    > b",
-                "    > C",
-                "      .dockerignore",
-                "v root2",
-                "    > d",
-                "    > e",
-            ]
-        );
-
-        toggle_expand_dir(&panel, "root1/b", cx);
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..50, cx),
-            &[
-                "v root1",
-                "    > a",
-                "    v b  <== selected",
-                "        > 3",
-                "    > C",
-                "      .dockerignore",
-                "v root2",
-                "    > d",
-                "    > e",
-            ]
-        );
-
-        toggle_expand_dir(&panel, "root2/d", cx);
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..50, cx),
-            &[
-                "v root1",
-                "    > a",
-                "    v b",
-                "        > 3",
-                "    > C",
-                "      .dockerignore",
-                "v root2",
-                "    v d  <== selected",
-                "    > e",
-            ]
-        );
-
-        toggle_expand_dir(&panel, "root2/e", cx);
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..50, cx),
-            &[
-                "v root1",
-                "    > a",
-                "    v b",
-                "        > 3",
-                "    > C",
-                "      .dockerignore",
-                "v root2",
-                "    v d",
-                "    v e  <== selected",
-            ]
-        );
-    }
-
-    #[gpui::test(iterations = 30)]
-    async fn test_editing_files(cx: &mut gpui::TestAppContext) {
-        init_test(cx);
-
-        let fs = FakeFs::new(cx.executor().clone());
-        fs.insert_tree(
-            "/root1",
-            json!({
-                ".dockerignore": "",
-                ".git": {
-                    "HEAD": "",
-                },
-                "a": {
-                    "0": { "q": "", "r": "", "s": "" },
-                    "1": { "t": "", "u": "" },
-                    "2": { "v": "", "w": "", "x": "", "y": "" },
-                },
-                "b": {
-                    "3": { "Q": "" },
-                    "4": { "R": "", "S": "", "T": "", "U": "" },
-                },
-                "C": {
-                    "5": {},
-                    "6": { "V": "", "W": "" },
-                    "7": { "X": "" },
-                    "8": { "Y": {}, "Z": "" }
-                }
-            }),
-        )
-        .await;
-        fs.insert_tree(
-            "/root2",
-            json!({
-                "d": {
-                    "9": ""
-                },
-                "e": {}
-            }),
-        )
-        .await;
-
-        let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
-        let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let cx = &mut VisualTestContext::from_window(*workspace, cx);
-        let panel = workspace
-            .update(cx, |workspace, cx| {
-                let panel = ProjectPanel::new(workspace, cx);
-                workspace.add_panel(panel.clone(), cx);
-                workspace.toggle_dock(panel.read(cx).position(cx), cx);
-                panel
-            })
-            .unwrap();
-
-        select_path(&panel, "root1", cx);
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v root1  <== selected",
-                "    > .git",
-                "    > a",
-                "    > b",
-                "    > C",
-                "      .dockerignore",
-                "v root2",
-                "    > d",
-                "    > e",
-            ]
-        );
-
-        // Add a file with the root folder selected. The filename editor is placed
-        // before the first file in the root folder.
-        panel.update(cx, |panel, cx| panel.new_file(&NewFile, cx));
-        panel.update(cx, |panel, cx| {
-            assert!(panel.filename_editor.read(cx).is_focused(cx));
-        });
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v root1",
-                "    > .git",
-                "    > a",
-                "    > b",
-                "    > C",
-                "      [EDITOR: '']  <== selected",
-                "      .dockerignore",
-                "v root2",
-                "    > d",
-                "    > e",
-            ]
-        );
-
-        let confirm = panel.update(cx, |panel, cx| {
-            panel
-                .filename_editor
-                .update(cx, |editor, cx| editor.set_text("the-new-filename", cx));
-            panel.confirm_edit(cx).unwrap()
-        });
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v root1",
-                "    > .git",
-                "    > a",
-                "    > b",
-                "    > C",
-                "      [PROCESSING: 'the-new-filename']  <== selected",
-                "      .dockerignore",
-                "v root2",
-                "    > d",
-                "    > e",
-            ]
-        );
-
-        confirm.await.unwrap();
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v root1",
-                "    > .git",
-                "    > a",
-                "    > b",
-                "    > C",
-                "      .dockerignore",
-                "      the-new-filename  <== selected",
-                "v root2",
-                "    > d",
-                "    > e",
-            ]
-        );
-
-        select_path(&panel, "root1/b", cx);
-        panel.update(cx, |panel, cx| panel.new_file(&NewFile, cx));
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v root1",
-                "    > .git",
-                "    > a",
-                "    v b",
-                "        > 3",
-                "        > 4",
-                "          [EDITOR: '']  <== selected",
-                "    > C",
-                "      .dockerignore",
-                "      the-new-filename",
-            ]
-        );
-
-        panel
-            .update(cx, |panel, cx| {
-                panel
-                    .filename_editor
-                    .update(cx, |editor, cx| editor.set_text("another-filename.txt", cx));
-                panel.confirm_edit(cx).unwrap()
-            })
-            .await
-            .unwrap();
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v root1",
-                "    > .git",
-                "    > a",
-                "    v b",
-                "        > 3",
-                "        > 4",
-                "          another-filename.txt  <== selected",
-                "    > C",
-                "      .dockerignore",
-                "      the-new-filename",
-            ]
-        );
-
-        select_path(&panel, "root1/b/another-filename.txt", cx);
-        panel.update(cx, |panel, cx| panel.rename(&Rename, cx));
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v root1",
-                "    > .git",
-                "    > a",
-                "    v b",
-                "        > 3",
-                "        > 4",
-                "          [EDITOR: 'another-filename.txt']  <== selected",
-                "    > C",
-                "      .dockerignore",
-                "      the-new-filename",
-            ]
-        );
-
-        let confirm = panel.update(cx, |panel, cx| {
-            panel.filename_editor.update(cx, |editor, cx| {
-                let file_name_selections = editor.selections.all::<usize>(cx);
-                assert_eq!(file_name_selections.len(), 1, "File editing should have a single selection, but got: {file_name_selections:?}");
-                let file_name_selection = &file_name_selections[0];
-                assert_eq!(file_name_selection.start, 0, "Should select the file name from the start");
-                assert_eq!(file_name_selection.end, "another-filename".len(), "Should not select file extension");
-
-                editor.set_text("a-different-filename.tar.gz", cx)
-            });
-            panel.confirm_edit(cx).unwrap()
-        });
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v root1",
-                "    > .git",
-                "    > a",
-                "    v b",
-                "        > 3",
-                "        > 4",
-                "          [PROCESSING: 'a-different-filename.tar.gz']  <== selected",
-                "    > C",
-                "      .dockerignore",
-                "      the-new-filename",
-            ]
-        );
-
-        confirm.await.unwrap();
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v root1",
-                "    > .git",
-                "    > a",
-                "    v b",
-                "        > 3",
-                "        > 4",
-                "          a-different-filename.tar.gz  <== selected",
-                "    > C",
-                "      .dockerignore",
-                "      the-new-filename",
-            ]
-        );
-
-        panel.update(cx, |panel, cx| panel.rename(&Rename, cx));
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v root1",
-                "    > .git",
-                "    > a",
-                "    v b",
-                "        > 3",
-                "        > 4",
-                "          [EDITOR: 'a-different-filename.tar.gz']  <== selected",
-                "    > C",
-                "      .dockerignore",
-                "      the-new-filename",
-            ]
-        );
-
-        panel.update(cx, |panel, cx| {
-            panel.filename_editor.update(cx, |editor, cx| {
-                let file_name_selections = editor.selections.all::<usize>(cx);
-                assert_eq!(file_name_selections.len(), 1, "File editing should have a single selection, but got: {file_name_selections:?}");
-                let file_name_selection = &file_name_selections[0];
-                assert_eq!(file_name_selection.start, 0, "Should select the file name from the start");
-                assert_eq!(file_name_selection.end, "a-different-filename.tar".len(), "Should not select file extension, but still may select anything up to the last dot..");
-
-            });
-            panel.cancel(&Cancel, cx)
-        });
-
-        panel.update(cx, |panel, cx| panel.new_directory(&NewDirectory, cx));
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v root1",
-                "    > .git",
-                "    > a",
-                "    v b",
-                "        > [EDITOR: '']  <== selected",
-                "        > 3",
-                "        > 4",
-                "          a-different-filename.tar.gz",
-                "    > C",
-                "      .dockerignore",
-            ]
-        );
-
-        let confirm = panel.update(cx, |panel, cx| {
-            panel
-                .filename_editor
-                .update(cx, |editor, cx| editor.set_text("new-dir", cx));
-            panel.confirm_edit(cx).unwrap()
-        });
-        panel.update(cx, |panel, cx| panel.select_next(&Default::default(), cx));
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v root1",
-                "    > .git",
-                "    > a",
-                "    v b",
-                "        > [PROCESSING: 'new-dir']",
-                "        > 3  <== selected",
-                "        > 4",
-                "          a-different-filename.tar.gz",
-                "    > C",
-                "      .dockerignore",
-            ]
-        );
-
-        confirm.await.unwrap();
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v root1",
-                "    > .git",
-                "    > a",
-                "    v b",
-                "        > 3  <== selected",
-                "        > 4",
-                "        > new-dir",
-                "          a-different-filename.tar.gz",
-                "    > C",
-                "      .dockerignore",
-            ]
-        );
-
-        panel.update(cx, |panel, cx| panel.rename(&Default::default(), cx));
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v root1",
-                "    > .git",
-                "    > a",
-                "    v b",
-                "        > [EDITOR: '3']  <== selected",
-                "        > 4",
-                "        > new-dir",
-                "          a-different-filename.tar.gz",
-                "    > C",
-                "      .dockerignore",
-            ]
-        );
-
-        // Dismiss the rename editor when it loses focus.
-        workspace.update(cx, |_, cx| cx.blur()).unwrap();
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v root1",
-                "    > .git",
-                "    > a",
-                "    v b",
-                "        > 3  <== selected",
-                "        > 4",
-                "        > new-dir",
-                "          a-different-filename.tar.gz",
-                "    > C",
-                "      .dockerignore",
-            ]
-        );
-    }
-
-    #[gpui::test(iterations = 10)]
-    async fn test_adding_directories_via_file(cx: &mut gpui::TestAppContext) {
-        init_test(cx);
-
-        let fs = FakeFs::new(cx.executor().clone());
-        fs.insert_tree(
-            "/root1",
-            json!({
-                ".dockerignore": "",
-                ".git": {
-                    "HEAD": "",
-                },
-                "a": {
-                    "0": { "q": "", "r": "", "s": "" },
-                    "1": { "t": "", "u": "" },
-                    "2": { "v": "", "w": "", "x": "", "y": "" },
-                },
-                "b": {
-                    "3": { "Q": "" },
-                    "4": { "R": "", "S": "", "T": "", "U": "" },
-                },
-                "C": {
-                    "5": {},
-                    "6": { "V": "", "W": "" },
-                    "7": { "X": "" },
-                    "8": { "Y": {}, "Z": "" }
-                }
-            }),
-        )
-        .await;
-        fs.insert_tree(
-            "/root2",
-            json!({
-                "d": {
-                    "9": ""
-                },
-                "e": {}
-            }),
-        )
-        .await;
-
-        let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
-        let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let cx = &mut VisualTestContext::from_window(*workspace, cx);
-        let panel = workspace
-            .update(cx, |workspace, cx| {
-                let panel = ProjectPanel::new(workspace, cx);
-                workspace.add_panel(panel.clone(), cx);
-                workspace.toggle_dock(panel.read(cx).position(cx), cx);
-                panel
-            })
-            .unwrap();
-
-        select_path(&panel, "root1", cx);
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v root1  <== selected",
-                "    > .git",
-                "    > a",
-                "    > b",
-                "    > C",
-                "      .dockerignore",
-                "v root2",
-                "    > d",
-                "    > e",
-            ]
-        );
-
-        // Add a file with the root folder selected. The filename editor is placed
-        // before the first file in the root folder.
-        panel.update(cx, |panel, cx| panel.new_file(&NewFile, cx));
-        panel.update(cx, |panel, cx| {
-            assert!(panel.filename_editor.read(cx).is_focused(cx));
-        });
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v root1",
-                "    > .git",
-                "    > a",
-                "    > b",
-                "    > C",
-                "      [EDITOR: '']  <== selected",
-                "      .dockerignore",
-                "v root2",
-                "    > d",
-                "    > e",
-            ]
-        );
-
-        let confirm = panel.update(cx, |panel, cx| {
-            panel.filename_editor.update(cx, |editor, cx| {
-                editor.set_text("/bdir1/dir2/the-new-filename", cx)
-            });
-            panel.confirm_edit(cx).unwrap()
-        });
-
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v root1",
-                "    > .git",
-                "    > a",
-                "    > b",
-                "    > C",
-                "      [PROCESSING: '/bdir1/dir2/the-new-filename']  <== selected",
-                "      .dockerignore",
-                "v root2",
-                "    > d",
-                "    > e",
-            ]
-        );
-
-        confirm.await.unwrap();
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..13, cx),
-            &[
-                "v root1",
-                "    > .git",
-                "    > a",
-                "    > b",
-                "    v bdir1",
-                "        v dir2",
-                "              the-new-filename  <== selected",
-                "    > C",
-                "      .dockerignore",
-                "v root2",
-                "    > d",
-                "    > e",
-            ]
-        );
-    }
-
-    #[gpui::test]
-    async fn test_copy_paste(cx: &mut gpui::TestAppContext) {
-        init_test(cx);
-
-        let fs = FakeFs::new(cx.executor().clone());
-        fs.insert_tree(
-            "/root1",
-            json!({
-                "one.two.txt": "",
-                "one.txt": ""
-            }),
-        )
-        .await;
-
-        let project = Project::test(fs.clone(), ["/root1".as_ref()], cx).await;
-        let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let cx = &mut VisualTestContext::from_window(*workspace, cx);
-        let panel = workspace
-            .update(cx, |workspace, cx| ProjectPanel::new(workspace, cx))
-            .unwrap();
-
-        panel.update(cx, |panel, cx| {
-            panel.select_next(&Default::default(), cx);
-            panel.select_next(&Default::default(), cx);
-        });
-
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..50, cx),
-            &[
-                //
-                "v root1",
-                "      one.two.txt  <== selected",
-                "      one.txt",
-            ]
-        );
-
-        // Regression test - file name is created correctly when
-        // the copied file's name contains multiple dots.
-        panel.update(cx, |panel, cx| {
-            panel.copy(&Default::default(), cx);
-            panel.paste(&Default::default(), cx);
-        });
-        cx.executor().run_until_parked();
-
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..50, cx),
-            &[
-                //
-                "v root1",
-                "      one.two copy.txt",
-                "      one.two.txt  <== selected",
-                "      one.txt",
-            ]
-        );
-
-        panel.update(cx, |panel, cx| {
-            panel.paste(&Default::default(), cx);
-        });
-        cx.executor().run_until_parked();
-
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..50, cx),
-            &[
-                //
-                "v root1",
-                "      one.two copy 1.txt",
-                "      one.two copy.txt",
-                "      one.two.txt  <== selected",
-                "      one.txt",
-            ]
-        );
-    }
-
-    #[gpui::test]
-    async fn test_remove_opened_file(cx: &mut gpui::TestAppContext) {
-        init_test_with_editor(cx);
-
-        let fs = FakeFs::new(cx.executor().clone());
-        fs.insert_tree(
-            "/src",
-            json!({
-                "test": {
-                    "first.rs": "// First Rust file",
-                    "second.rs": "// Second Rust file",
-                    "third.rs": "// Third Rust file",
-                }
-            }),
-        )
-        .await;
-
-        let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await;
-        let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let cx = &mut VisualTestContext::from_window(*workspace, cx);
-        let panel = workspace
-            .update(cx, |workspace, cx| ProjectPanel::new(workspace, cx))
-            .unwrap();
-
-        toggle_expand_dir(&panel, "src/test", cx);
-        select_path(&panel, "src/test/first.rs", cx);
-        panel.update(cx, |panel, cx| panel.open_file(&Open, cx));
-        cx.executor().run_until_parked();
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v src",
-                "    v test",
-                "          first.rs  <== selected",
-                "          second.rs",
-                "          third.rs"
-            ]
-        );
-        ensure_single_file_is_opened(&workspace, "test/first.rs", cx);
-
-        submit_deletion(&panel, cx);
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v src",
-                "    v test",
-                "          second.rs",
-                "          third.rs"
-            ],
-            "Project panel should have no deleted file, no other file is selected in it"
-        );
-        ensure_no_open_items_and_panes(&workspace, cx);
-
-        select_path(&panel, "src/test/second.rs", cx);
-        panel.update(cx, |panel, cx| panel.open_file(&Open, cx));
-        cx.executor().run_until_parked();
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v src",
-                "    v test",
-                "          second.rs  <== selected",
-                "          third.rs"
-            ]
-        );
-        ensure_single_file_is_opened(&workspace, "test/second.rs", cx);
-
-        workspace
-            .update(cx, |workspace, cx| {
-                let active_items = workspace
-                    .panes()
-                    .iter()
-                    .filter_map(|pane| pane.read(cx).active_item())
-                    .collect::<Vec<_>>();
-                assert_eq!(active_items.len(), 1);
-                let open_editor = active_items
-                    .into_iter()
-                    .next()
-                    .unwrap()
-                    .downcast::<Editor>()
-                    .expect("Open item should be an editor");
-                open_editor.update(cx, |editor, cx| editor.set_text("Another text!", cx));
-            })
-            .unwrap();
-        submit_deletion(&panel, cx);
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &["v src", "    v test", "          third.rs"],
-            "Project panel should have no deleted file, with one last file remaining"
-        );
-        ensure_no_open_items_and_panes(&workspace, cx);
-    }
-
-    #[gpui::test]
-    async fn test_create_duplicate_items(cx: &mut gpui::TestAppContext) {
-        init_test_with_editor(cx);
-
-        let fs = FakeFs::new(cx.executor().clone());
-        fs.insert_tree(
-            "/src",
-            json!({
-                "test": {
-                    "first.rs": "// First Rust file",
-                    "second.rs": "// Second Rust file",
-                    "third.rs": "// Third Rust file",
-                }
-            }),
-        )
-        .await;
-
-        let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await;
-        let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let cx = &mut VisualTestContext::from_window(*workspace, cx);
-        let panel = workspace
-            .update(cx, |workspace, cx| {
-                let panel = ProjectPanel::new(workspace, cx);
-                workspace.add_panel(panel.clone(), cx);
-                workspace.toggle_dock(panel.read(cx).position(cx), cx);
-                panel
-            })
-            .unwrap();
-
-        select_path(&panel, "src/", cx);
-        panel.update(cx, |panel, cx| panel.confirm(&Confirm, cx));
-        cx.executor().run_until_parked();
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                //
-                "v src  <== selected",
-                "    > test"
-            ]
-        );
-        panel.update(cx, |panel, cx| panel.new_directory(&NewDirectory, cx));
-        panel.update(cx, |panel, cx| {
-            assert!(panel.filename_editor.read(cx).is_focused(cx));
-        });
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                //
-                "v src",
-                "    > [EDITOR: '']  <== selected",
-                "    > test"
-            ]
-        );
-        panel.update(cx, |panel, cx| {
-            panel
-                .filename_editor
-                .update(cx, |editor, cx| editor.set_text("test", cx));
-            assert!(
-                panel.confirm_edit(cx).is_none(),
-                "Should not allow to confirm on conflicting new directory name"
-            )
-        });
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                //
-                "v src",
-                "    > test"
-            ],
-            "File list should be unchanged after failed folder create confirmation"
-        );
-
-        select_path(&panel, "src/test/", cx);
-        panel.update(cx, |panel, cx| panel.confirm(&Confirm, cx));
-        cx.executor().run_until_parked();
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                //
-                "v src",
-                "    > test  <== selected"
-            ]
-        );
-        panel.update(cx, |panel, cx| panel.new_file(&NewFile, cx));
-        panel.update(cx, |panel, cx| {
-            assert!(panel.filename_editor.read(cx).is_focused(cx));
-        });
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v src",
-                "    v test",
-                "          [EDITOR: '']  <== selected",
-                "          first.rs",
-                "          second.rs",
-                "          third.rs"
-            ]
-        );
-        panel.update(cx, |panel, cx| {
-            panel
-                .filename_editor
-                .update(cx, |editor, cx| editor.set_text("first.rs", cx));
-            assert!(
-                panel.confirm_edit(cx).is_none(),
-                "Should not allow to confirm on conflicting new file name"
-            )
-        });
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v src",
-                "    v test",
-                "          first.rs",
-                "          second.rs",
-                "          third.rs"
-            ],
-            "File list should be unchanged after failed file create confirmation"
-        );
-
-        select_path(&panel, "src/test/first.rs", cx);
-        panel.update(cx, |panel, cx| panel.confirm(&Confirm, cx));
-        cx.executor().run_until_parked();
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v src",
-                "    v test",
-                "          first.rs  <== selected",
-                "          second.rs",
-                "          third.rs"
-            ],
-        );
-        panel.update(cx, |panel, cx| panel.rename(&Rename, cx));
-        panel.update(cx, |panel, cx| {
-            assert!(panel.filename_editor.read(cx).is_focused(cx));
-        });
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v src",
-                "    v test",
-                "          [EDITOR: 'first.rs']  <== selected",
-                "          second.rs",
-                "          third.rs"
-            ]
-        );
-        panel.update(cx, |panel, cx| {
-            panel
-                .filename_editor
-                .update(cx, |editor, cx| editor.set_text("second.rs", cx));
-            assert!(
-                panel.confirm_edit(cx).is_none(),
-                "Should not allow to confirm on conflicting file rename"
-            )
-        });
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v src",
-                "    v test",
-                "          first.rs  <== selected",
-                "          second.rs",
-                "          third.rs"
-            ],
-            "File list should be unchanged after failed rename confirmation"
-        );
-    }
-
-    #[gpui::test]
-    async fn test_collapse_all_entries(cx: &mut gpui::TestAppContext) {
-        init_test_with_editor(cx);
-
-        let fs = FakeFs::new(cx.executor().clone());
-        fs.insert_tree(
-            "/project_root",
-            json!({
-                "dir_1": {
-                    "nested_dir": {
-                        "file_a.py": "# File contents",
-                        "file_b.py": "# File contents",
-                        "file_c.py": "# File contents",
-                    },
-                    "file_1.py": "# File contents",
-                    "file_2.py": "# File contents",
-                    "file_3.py": "# File contents",
-                },
-                "dir_2": {
-                    "file_1.py": "# File contents",
-                    "file_2.py": "# File contents",
-                    "file_3.py": "# File contents",
-                }
-            }),
-        )
-        .await;
-
-        let project = Project::test(fs.clone(), ["/project_root".as_ref()], cx).await;
-        let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let cx = &mut VisualTestContext::from_window(*workspace, cx);
-        let panel = workspace
-            .update(cx, |workspace, cx| ProjectPanel::new(workspace, cx))
-            .unwrap();
-
-        panel.update(cx, |panel, cx| {
-            panel.collapse_all_entries(&CollapseAllEntries, cx)
-        });
-        cx.executor().run_until_parked();
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &["v project_root", "    > dir_1", "    > dir_2",]
-        );
-
-        // Open dir_1 and make sure nested_dir was collapsed when running collapse_all_entries
-        toggle_expand_dir(&panel, "project_root/dir_1", cx);
-        cx.executor().run_until_parked();
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &[
-                "v project_root",
-                "    v dir_1  <== selected",
-                "        > nested_dir",
-                "          file_1.py",
-                "          file_2.py",
-                "          file_3.py",
-                "    > dir_2",
-            ]
-        );
-    }
-
-    #[gpui::test]
-    async fn test_new_file_move(cx: &mut gpui::TestAppContext) {
-        init_test(cx);
-
-        let fs = FakeFs::new(cx.executor().clone());
-        fs.as_fake().insert_tree("/root", json!({})).await;
-        let project = Project::test(fs, ["/root".as_ref()], cx).await;
-        let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let cx = &mut VisualTestContext::from_window(*workspace, cx);
-        let panel = workspace
-            .update(cx, |workspace, cx| ProjectPanel::new(workspace, cx))
-            .unwrap();
-
-        // Make a new buffer with no backing file
-        workspace
-            .update(cx, |workspace, cx| {
-                Editor::new_file(workspace, &Default::default(), cx)
-            })
-            .unwrap();
-
-        // "Save as"" the buffer, creating a new backing file for it
-        let save_task = workspace
-            .update(cx, |workspace, cx| {
-                workspace.save_active_item(workspace::SaveIntent::Save, cx)
-            })
-            .unwrap();
-
-        cx.executor().run_until_parked();
-        cx.simulate_new_path_selection(|_| Some(PathBuf::from("/root/new")));
-        save_task.await.unwrap();
-
-        // Rename the file
-        select_path(&panel, "root/new", cx);
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &["v root", "      new  <== selected"]
-        );
-        panel.update(cx, |panel, cx| panel.rename(&Rename, cx));
-        panel.update(cx, |panel, cx| {
-            panel
-                .filename_editor
-                .update(cx, |editor, cx| editor.set_text("newer", cx));
-        });
-        panel.update(cx, |panel, cx| panel.confirm(&Confirm, cx));
-
-        cx.executor().run_until_parked();
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &["v root", "      newer  <== selected"]
-        );
-
-        workspace
-            .update(cx, |workspace, cx| {
-                workspace.save_active_item(workspace::SaveIntent::Save, cx)
-            })
-            .unwrap()
-            .await
-            .unwrap();
-
-        cx.executor().run_until_parked();
-        // assert that saving the file doesn't restore "new"
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..10, cx),
-            &["v root", "      newer  <== selected"]
-        );
-    }
-
-    #[gpui::test]
-    async fn test_autoreveal_and_gitignored_files(cx: &mut gpui::TestAppContext) {
-        init_test_with_editor(cx);
-        cx.update(|cx| {
-            cx.update_global::<SettingsStore, _>(|store, cx| {
-                store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
-                    project_settings.file_scan_exclusions = Some(Vec::new());
-                });
-                store.update_user_settings::<ProjectPanelSettings>(cx, |project_panel_settings| {
-                    project_panel_settings.auto_reveal_entries = Some(false)
-                });
-            })
-        });
-
-        let fs = FakeFs::new(cx.background_executor.clone());
-        fs.insert_tree(
-            "/project_root",
-            json!({
-                ".git": {},
-                ".gitignore": "**/gitignored_dir",
-                "dir_1": {
-                    "file_1.py": "# File 1_1 contents",
-                    "file_2.py": "# File 1_2 contents",
-                    "file_3.py": "# File 1_3 contents",
-                    "gitignored_dir": {
-                        "file_a.py": "# File contents",
-                        "file_b.py": "# File contents",
-                        "file_c.py": "# File contents",
-                    },
-                },
-                "dir_2": {
-                    "file_1.py": "# File 2_1 contents",
-                    "file_2.py": "# File 2_2 contents",
-                    "file_3.py": "# File 2_3 contents",
-                }
-            }),
-        )
-        .await;
-
-        let project = Project::test(fs.clone(), ["/project_root".as_ref()], cx).await;
-        let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let cx = &mut VisualTestContext::from_window(*workspace, cx);
-        let panel = workspace
-            .update(cx, |workspace, cx| ProjectPanel::new(workspace, cx))
-            .unwrap();
-
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..20, cx),
-            &[
-                "v project_root",
-                "    > .git",
-                "    > dir_1",
-                "    > dir_2",
-                "      .gitignore",
-            ]
-        );
-
-        let dir_1_file = find_project_entry(&panel, "project_root/dir_1/file_1.py", cx)
-            .expect("dir 1 file is not ignored and should have an entry");
-        let dir_2_file = find_project_entry(&panel, "project_root/dir_2/file_1.py", cx)
-            .expect("dir 2 file is not ignored and should have an entry");
-        let gitignored_dir_file =
-            find_project_entry(&panel, "project_root/dir_1/gitignored_dir/file_a.py", cx);
-        assert_eq!(
-            gitignored_dir_file, None,
-            "File in the gitignored dir should not have an entry before its dir is toggled"
-        );
-
-        toggle_expand_dir(&panel, "project_root/dir_1", cx);
-        toggle_expand_dir(&panel, "project_root/dir_1/gitignored_dir", cx);
-        cx.executor().run_until_parked();
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..20, cx),
-            &[
-                "v project_root",
-                "    > .git",
-                "    v dir_1",
-                "        v gitignored_dir  <== selected",
-                "              file_a.py",
-                "              file_b.py",
-                "              file_c.py",
-                "          file_1.py",
-                "          file_2.py",
-                "          file_3.py",
-                "    > dir_2",
-                "      .gitignore",
-            ],
-            "Should show gitignored dir file list in the project panel"
-        );
-        let gitignored_dir_file =
-            find_project_entry(&panel, "project_root/dir_1/gitignored_dir/file_a.py", cx)
-                .expect("after gitignored dir got opened, a file entry should be present");
-
-        toggle_expand_dir(&panel, "project_root/dir_1/gitignored_dir", cx);
-        toggle_expand_dir(&panel, "project_root/dir_1", cx);
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..20, cx),
-            &[
-                "v project_root",
-                "    > .git",
-                "    > dir_1  <== selected",
-                "    > dir_2",
-                "      .gitignore",
-            ],
-            "Should hide all dir contents again and prepare for the auto reveal test"
-        );
-
-        for file_entry in [dir_1_file, dir_2_file, gitignored_dir_file] {
-            panel.update(cx, |panel, cx| {
-                panel.project.update(cx, |_, cx| {
-                    cx.emit(project::Event::ActiveEntryChanged(Some(file_entry)))
-                })
-            });
-            cx.run_until_parked();
-            assert_eq!(
-                visible_entries_as_strings(&panel, 0..20, cx),
-                &[
-                    "v project_root",
-                    "    > .git",
-                    "    > dir_1  <== selected",
-                    "    > dir_2",
-                    "      .gitignore",
-                ],
-                "When no auto reveal is enabled, the selected entry should not be revealed in the project panel"
-            );
-        }
-
-        cx.update(|cx| {
-            cx.update_global::<SettingsStore, _>(|store, cx| {
-                store.update_user_settings::<ProjectPanelSettings>(cx, |project_panel_settings| {
-                    project_panel_settings.auto_reveal_entries = Some(true)
-                });
-            })
-        });
-
-        panel.update(cx, |panel, cx| {
-            panel.project.update(cx, |_, cx| {
-                cx.emit(project::Event::ActiveEntryChanged(Some(dir_1_file)))
-            })
-        });
-        cx.run_until_parked();
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..20, cx),
-            &[
-                "v project_root",
-                "    > .git",
-                "    v dir_1",
-                "        > gitignored_dir",
-                "          file_1.py  <== selected",
-                "          file_2.py",
-                "          file_3.py",
-                "    > dir_2",
-                "      .gitignore",
-            ],
-            "When auto reveal is enabled, not ignored dir_1 entry should be revealed"
-        );
-
-        panel.update(cx, |panel, cx| {
-            panel.project.update(cx, |_, cx| {
-                cx.emit(project::Event::ActiveEntryChanged(Some(dir_2_file)))
-            })
-        });
-        cx.run_until_parked();
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..20, cx),
-            &[
-                "v project_root",
-                "    > .git",
-                "    v dir_1",
-                "        > gitignored_dir",
-                "          file_1.py",
-                "          file_2.py",
-                "          file_3.py",
-                "    v dir_2",
-                "          file_1.py  <== selected",
-                "          file_2.py",
-                "          file_3.py",
-                "      .gitignore",
-            ],
-            "When auto reveal is enabled, not ignored dir_2 entry should be revealed"
-        );
-
-        panel.update(cx, |panel, cx| {
-            panel.project.update(cx, |_, cx| {
-                cx.emit(project::Event::ActiveEntryChanged(Some(
-                    gitignored_dir_file,
-                )))
-            })
-        });
-        cx.run_until_parked();
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..20, cx),
-            &[
-                "v project_root",
-                "    > .git",
-                "    v dir_1",
-                "        > gitignored_dir",
-                "          file_1.py",
-                "          file_2.py",
-                "          file_3.py",
-                "    v dir_2",
-                "          file_1.py  <== selected",
-                "          file_2.py",
-                "          file_3.py",
-                "      .gitignore",
-            ],
-            "When auto reveal is enabled, a gitignored selected entry should not be revealed in the project panel"
-        );
-
-        panel.update(cx, |panel, cx| {
-            panel.project.update(cx, |_, cx| {
-                cx.emit(project::Event::RevealInProjectPanel(gitignored_dir_file))
-            })
-        });
-        cx.run_until_parked();
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..20, cx),
-            &[
-                "v project_root",
-                "    > .git",
-                "    v dir_1",
-                "        v gitignored_dir",
-                "              file_a.py  <== selected",
-                "              file_b.py",
-                "              file_c.py",
-                "          file_1.py",
-                "          file_2.py",
-                "          file_3.py",
-                "    v dir_2",
-                "          file_1.py",
-                "          file_2.py",
-                "          file_3.py",
-                "      .gitignore",
-            ],
-            "When a gitignored entry is explicitly revealed, it should be shown in the project tree"
-        );
-    }
-
-    #[gpui::test]
-    async fn test_explicit_reveal(cx: &mut gpui::TestAppContext) {
-        init_test_with_editor(cx);
-        cx.update(|cx| {
-            cx.update_global::<SettingsStore, _>(|store, cx| {
-                store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
-                    project_settings.file_scan_exclusions = Some(Vec::new());
-                });
-                store.update_user_settings::<ProjectPanelSettings>(cx, |project_panel_settings| {
-                    project_panel_settings.auto_reveal_entries = Some(false)
-                });
-            })
-        });
-
-        let fs = FakeFs::new(cx.background_executor.clone());
-        fs.insert_tree(
-            "/project_root",
-            json!({
-                ".git": {},
-                ".gitignore": "**/gitignored_dir",
-                "dir_1": {
-                    "file_1.py": "# File 1_1 contents",
-                    "file_2.py": "# File 1_2 contents",
-                    "file_3.py": "# File 1_3 contents",
-                    "gitignored_dir": {
-                        "file_a.py": "# File contents",
-                        "file_b.py": "# File contents",
-                        "file_c.py": "# File contents",
-                    },
-                },
-                "dir_2": {
-                    "file_1.py": "# File 2_1 contents",
-                    "file_2.py": "# File 2_2 contents",
-                    "file_3.py": "# File 2_3 contents",
-                }
-            }),
-        )
-        .await;
-
-        let project = Project::test(fs.clone(), ["/project_root".as_ref()], cx).await;
-        let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let cx = &mut VisualTestContext::from_window(*workspace, cx);
-        let panel = workspace
-            .update(cx, |workspace, cx| ProjectPanel::new(workspace, cx))
-            .unwrap();
-
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..20, cx),
-            &[
-                "v project_root",
-                "    > .git",
-                "    > dir_1",
-                "    > dir_2",
-                "      .gitignore",
-            ]
-        );
-
-        let dir_1_file = find_project_entry(&panel, "project_root/dir_1/file_1.py", cx)
-            .expect("dir 1 file is not ignored and should have an entry");
-        let dir_2_file = find_project_entry(&panel, "project_root/dir_2/file_1.py", cx)
-            .expect("dir 2 file is not ignored and should have an entry");
-        let gitignored_dir_file =
-            find_project_entry(&panel, "project_root/dir_1/gitignored_dir/file_a.py", cx);
-        assert_eq!(
-            gitignored_dir_file, None,
-            "File in the gitignored dir should not have an entry before its dir is toggled"
-        );
-
-        toggle_expand_dir(&panel, "project_root/dir_1", cx);
-        toggle_expand_dir(&panel, "project_root/dir_1/gitignored_dir", cx);
-        cx.run_until_parked();
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..20, cx),
-            &[
-                "v project_root",
-                "    > .git",
-                "    v dir_1",
-                "        v gitignored_dir  <== selected",
-                "              file_a.py",
-                "              file_b.py",
-                "              file_c.py",
-                "          file_1.py",
-                "          file_2.py",
-                "          file_3.py",
-                "    > dir_2",
-                "      .gitignore",
-            ],
-            "Should show gitignored dir file list in the project panel"
-        );
-        let gitignored_dir_file =
-            find_project_entry(&panel, "project_root/dir_1/gitignored_dir/file_a.py", cx)
-                .expect("after gitignored dir got opened, a file entry should be present");
-
-        toggle_expand_dir(&panel, "project_root/dir_1/gitignored_dir", cx);
-        toggle_expand_dir(&panel, "project_root/dir_1", cx);
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..20, cx),
-            &[
-                "v project_root",
-                "    > .git",
-                "    > dir_1  <== selected",
-                "    > dir_2",
-                "      .gitignore",
-            ],
-            "Should hide all dir contents again and prepare for the explicit reveal test"
-        );
-
-        for file_entry in [dir_1_file, dir_2_file, gitignored_dir_file] {
-            panel.update(cx, |panel, cx| {
-                panel.project.update(cx, |_, cx| {
-                    cx.emit(project::Event::ActiveEntryChanged(Some(file_entry)))
-                })
-            });
-            cx.run_until_parked();
-            assert_eq!(
-                visible_entries_as_strings(&panel, 0..20, cx),
-                &[
-                    "v project_root",
-                    "    > .git",
-                    "    > dir_1  <== selected",
-                    "    > dir_2",
-                    "      .gitignore",
-                ],
-                "When no auto reveal is enabled, the selected entry should not be revealed in the project panel"
-            );
-        }
-
-        panel.update(cx, |panel, cx| {
-            panel.project.update(cx, |_, cx| {
-                cx.emit(project::Event::RevealInProjectPanel(dir_1_file))
-            })
-        });
-        cx.run_until_parked();
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..20, cx),
-            &[
-                "v project_root",
-                "    > .git",
-                "    v dir_1",
-                "        > gitignored_dir",
-                "          file_1.py  <== selected",
-                "          file_2.py",
-                "          file_3.py",
-                "    > dir_2",
-                "      .gitignore",
-            ],
-            "With no auto reveal, explicit reveal should show the dir_1 entry in the project panel"
-        );
-
-        panel.update(cx, |panel, cx| {
-            panel.project.update(cx, |_, cx| {
-                cx.emit(project::Event::RevealInProjectPanel(dir_2_file))
-            })
-        });
-        cx.run_until_parked();
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..20, cx),
-            &[
-                "v project_root",
-                "    > .git",
-                "    v dir_1",
-                "        > gitignored_dir",
-                "          file_1.py",
-                "          file_2.py",
-                "          file_3.py",
-                "    v dir_2",
-                "          file_1.py  <== selected",
-                "          file_2.py",
-                "          file_3.py",
-                "      .gitignore",
-            ],
-            "With no auto reveal, explicit reveal should show the dir_2 entry in the project panel"
-        );
-
-        panel.update(cx, |panel, cx| {
-            panel.project.update(cx, |_, cx| {
-                cx.emit(project::Event::RevealInProjectPanel(gitignored_dir_file))
-            })
-        });
-        cx.run_until_parked();
-        assert_eq!(
-            visible_entries_as_strings(&panel, 0..20, cx),
-            &[
-                "v project_root",
-                "    > .git",
-                "    v dir_1",
-                "        v gitignored_dir",
-                "              file_a.py  <== selected",
-                "              file_b.py",
-                "              file_c.py",
-                "          file_1.py",
-                "          file_2.py",
-                "          file_3.py",
-                "    v dir_2",
-                "          file_1.py",
-                "          file_2.py",
-                "          file_3.py",
-                "      .gitignore",
-            ],
-            "With no auto reveal, explicit reveal should show the gitignored entry in the project panel"
-        );
-    }
-
-    fn toggle_expand_dir(
-        panel: &View<ProjectPanel>,
-        path: impl AsRef<Path>,
-        cx: &mut VisualTestContext,
-    ) {
-        let path = path.as_ref();
-        panel.update(cx, |panel, cx| {
-            for worktree in panel.project.read(cx).worktrees().collect::<Vec<_>>() {
-                let worktree = worktree.read(cx);
-                if let Ok(relative_path) = path.strip_prefix(worktree.root_name()) {
-                    let entry_id = worktree.entry_for_path(relative_path).unwrap().id;
-                    panel.toggle_expanded(entry_id, cx);
-                    return;
-                }
-            }
-            panic!("no worktree for path {:?}", path);
-        });
-    }
-
-    fn select_path(panel: &View<ProjectPanel>, path: impl AsRef<Path>, cx: &mut VisualTestContext) {
-        let path = path.as_ref();
-        panel.update(cx, |panel, cx| {
-            for worktree in panel.project.read(cx).worktrees().collect::<Vec<_>>() {
-                let worktree = worktree.read(cx);
-                if let Ok(relative_path) = path.strip_prefix(worktree.root_name()) {
-                    let entry_id = worktree.entry_for_path(relative_path).unwrap().id;
-                    panel.selection = Some(crate::Selection {
-                        worktree_id: worktree.id(),
-                        entry_id,
-                    });
-                    return;
-                }
-            }
-            panic!("no worktree for path {:?}", path);
-        });
-    }
-
-    fn find_project_entry(
-        panel: &View<ProjectPanel>,
-        path: impl AsRef<Path>,
-        cx: &mut VisualTestContext,
-    ) -> Option<ProjectEntryId> {
-        let path = path.as_ref();
-        panel.update(cx, |panel, cx| {
-            for worktree in panel.project.read(cx).worktrees().collect::<Vec<_>>() {
-                let worktree = worktree.read(cx);
-                if let Ok(relative_path) = path.strip_prefix(worktree.root_name()) {
-                    return worktree.entry_for_path(relative_path).map(|entry| entry.id);
-                }
-            }
-            panic!("no worktree for path {path:?}");
-        })
-    }
-
-    fn visible_entries_as_strings(
-        panel: &View<ProjectPanel>,
-        range: Range<usize>,
-        cx: &mut VisualTestContext,
-    ) -> Vec<String> {
-        let mut result = Vec::new();
-        let mut project_entries = HashSet::new();
-        let mut has_editor = false;
-
-        panel.update(cx, |panel, cx| {
-            panel.for_each_visible_entry(range, cx, |project_entry, details, _| {
-                if details.is_editing {
-                    assert!(!has_editor, "duplicate editor entry");
-                    has_editor = true;
-                } else {
-                    assert!(
-                        project_entries.insert(project_entry),
-                        "duplicate project entry {:?} {:?}",
-                        project_entry,
-                        details
-                    );
-                }
-
-                let indent = "    ".repeat(details.depth);
-                let icon = if details.kind.is_dir() {
-                    if details.is_expanded {
-                        "v "
-                    } else {
-                        "> "
-                    }
-                } else {
-                    "  "
-                };
-                let name = if details.is_editing {
-                    format!("[EDITOR: '{}']", details.filename)
-                } else if details.is_processing {
-                    format!("[PROCESSING: '{}']", details.filename)
-                } else {
-                    details.filename.clone()
-                };
-                let selected = if details.is_selected {
-                    "  <== selected"
-                } else {
-                    ""
-                };
-                result.push(format!("{indent}{icon}{name}{selected}"));
-            });
-        });
-
-        result
-    }
-
-    fn init_test(cx: &mut TestAppContext) {
-        cx.update(|cx| {
-            let settings_store = SettingsStore::test(cx);
-            cx.set_global(settings_store);
-            init_settings(cx);
-            theme::init(theme::LoadThemes::JustBase, cx);
-            language::init(cx);
-            editor::init_settings(cx);
-            crate::init((), cx);
-            workspace::init_settings(cx);
-            client::init_settings(cx);
-            Project::init_settings(cx);
-
-            cx.update_global::<SettingsStore, _>(|store, cx| {
-                store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
-                    project_settings.file_scan_exclusions = Some(Vec::new());
-                });
-            });
-        });
-    }
-
-    fn init_test_with_editor(cx: &mut TestAppContext) {
-        cx.update(|cx| {
-            let app_state = AppState::test(cx);
-            theme::init(theme::LoadThemes::JustBase, cx);
-            init_settings(cx);
-            language::init(cx);
-            editor::init(cx);
-            crate::init((), cx);
-            workspace::init(app_state.clone(), cx);
-            Project::init_settings(cx);
-        });
-    }
-
-    fn ensure_single_file_is_opened(
-        window: &WindowHandle<Workspace>,
-        expected_path: &str,
-        cx: &mut TestAppContext,
-    ) {
-        window
-            .update(cx, |workspace, cx| {
-                let worktrees = workspace.worktrees(cx).collect::<Vec<_>>();
-                assert_eq!(worktrees.len(), 1);
-                let worktree_id = worktrees[0].read(cx).id();
-
-                let open_project_paths = workspace
-                    .panes()
-                    .iter()
-                    .filter_map(|pane| pane.read(cx).active_item()?.project_path(cx))
-                    .collect::<Vec<_>>();
-                assert_eq!(
-                    open_project_paths,
-                    vec![ProjectPath {
-                        worktree_id,
-                        path: Arc::from(Path::new(expected_path))
-                    }],
-                    "Should have opened file, selected in project panel"
-                );
-            })
-            .unwrap();
-    }
-
-    fn submit_deletion(panel: &View<ProjectPanel>, cx: &mut VisualTestContext) {
-        assert!(
-            !cx.has_pending_prompt(),
-            "Should have no prompts before the deletion"
-        );
-        panel.update(cx, |panel, cx| panel.delete(&Delete, cx));
-        assert!(
-            cx.has_pending_prompt(),
-            "Should have a prompt after the deletion"
-        );
-        cx.simulate_prompt_answer(0);
-        assert!(
-            !cx.has_pending_prompt(),
-            "Should have no prompts after prompt was replied to"
-        );
-        cx.executor().run_until_parked();
-    }
-
-    fn ensure_no_open_items_and_panes(
-        workspace: &WindowHandle<Workspace>,
-        cx: &mut VisualTestContext,
-    ) {
-        assert!(
-            !cx.has_pending_prompt(),
-            "Should have no prompts after deletion operation closes the file"
-        );
-        workspace
-            .read_with(cx, |workspace, cx| {
-                let open_project_paths = workspace
-                    .panes()
-                    .iter()
-                    .filter_map(|pane| pane.read(cx).active_item()?.project_path(cx))
-                    .collect::<Vec<_>>();
-                assert!(
-                    open_project_paths.is_empty(),
-                    "Deleted file's buffer should be closed, but got open files: {open_project_paths:?}"
-                );
-            })
-            .unwrap();
-    }
-}

crates/project_panel2/src/project_panel_settings.rs 🔗

@@ -1,48 +0,0 @@
-use anyhow;
-use gpui::Pixels;
-use schemars::JsonSchema;
-use serde_derive::{Deserialize, Serialize};
-use settings::Settings;
-
-#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum ProjectPanelDockPosition {
-    Left,
-    Right,
-}
-
-#[derive(Deserialize, Debug)]
-pub struct ProjectPanelSettings {
-    pub default_width: Pixels,
-    pub dock: ProjectPanelDockPosition,
-    pub file_icons: bool,
-    pub folder_icons: bool,
-    pub git_status: bool,
-    pub indent_size: f32,
-    pub auto_reveal_entries: bool,
-}
-
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
-pub struct ProjectPanelSettingsContent {
-    pub default_width: Option<f32>,
-    pub dock: Option<ProjectPanelDockPosition>,
-    pub file_icons: Option<bool>,
-    pub folder_icons: Option<bool>,
-    pub git_status: Option<bool>,
-    pub indent_size: Option<f32>,
-    pub auto_reveal_entries: Option<bool>,
-}
-
-impl Settings for ProjectPanelSettings {
-    const KEY: Option<&'static str> = Some("project_panel");
-
-    type FileContent = ProjectPanelSettingsContent;
-
-    fn load(
-        default_value: &Self::FileContent,
-        user_values: &[&Self::FileContent],
-        _: &mut gpui::AppContext,
-    ) -> anyhow::Result<Self> {
-        Self::load_via_json_merge(default_value, user_values)
-    }
-}

crates/project_symbols/Cargo.toml 🔗

@@ -10,7 +10,7 @@ doctest = false
 
 [dependencies]
 editor = { path = "../editor" }
-fuzzy = { path = "../fuzzy" }
+fuzzy = {   path = "../fuzzy" }
 gpui = { path = "../gpui" }
 picker = { path = "../picker" }
 project = { path = "../project" }

crates/project_symbols/src/project_symbols.rs 🔗

@@ -1,38 +1,43 @@
-use editor::{
-    combine_syntax_and_fuzzy_match_highlights, scroll::autoscroll::Autoscroll,
-    styled_runs_for_code_label, Bias, Editor,
-};
+use editor::{scroll::autoscroll::Autoscroll, styled_runs_for_code_label, Bias, Editor};
 use fuzzy::{StringMatch, StringMatchCandidate};
 use gpui::{
-    actions, elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext, WeakViewHandle,
+    actions, rems, AppContext, DismissEvent, FontWeight, Model, ParentElement, StyledText, Task,
+    View, ViewContext, WeakView,
 };
 use ordered_float::OrderedFloat;
-use picker::{Picker, PickerDelegate, PickerEvent};
+use picker::{Picker, PickerDelegate};
 use project::{Project, Symbol};
 use std::{borrow::Cow, cmp::Reverse, sync::Arc};
+use theme::ActiveTheme;
 use util::ResultExt;
-use workspace::Workspace;
+use workspace::{
+    ui::{v_stack, Color, Label, LabelCommon, LabelLike, ListItem, ListItemSpacing, Selectable},
+    Workspace,
+};
 
 actions!(project_symbols, [Toggle]);
 
 pub fn init(cx: &mut AppContext) {
-    cx.add_action(toggle);
-    ProjectSymbols::init(cx);
-}
-
-fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
-    workspace.toggle_modal(cx, |workspace, cx| {
-        let project = workspace.project().clone();
-        let workspace = cx.weak_handle();
-        cx.add_view(|cx| ProjectSymbols::new(ProjectSymbolsDelegate::new(workspace, project), cx))
-    });
+    cx.observe_new_views(
+        |workspace: &mut Workspace, _: &mut ViewContext<Workspace>| {
+            workspace.register_action(|workspace, _: &Toggle, cx| {
+                let project = workspace.project().clone();
+                let handle = cx.view().downgrade();
+                workspace.toggle_modal(cx, move |cx| {
+                    let delegate = ProjectSymbolsDelegate::new(handle, project);
+                    Picker::new(delegate, cx).width(rems(34.))
+                })
+            });
+        },
+    )
+    .detach();
 }
 
-pub type ProjectSymbols = Picker<ProjectSymbolsDelegate>;
+pub type ProjectSymbols = View<Picker<ProjectSymbolsDelegate>>;
 
 pub struct ProjectSymbolsDelegate {
-    workspace: WeakViewHandle<Workspace>,
-    project: ModelHandle<Project>,
+    workspace: WeakView<Workspace>,
+    project: Model<Project>,
     selected_match_index: usize,
     symbols: Vec<Symbol>,
     visible_match_candidates: Vec<StringMatchCandidate>,
@@ -42,7 +47,7 @@ pub struct ProjectSymbolsDelegate {
 }
 
 impl ProjectSymbolsDelegate {
-    fn new(workspace: WeakViewHandle<Workspace>, project: ModelHandle<Project>) -> Self {
+    fn new(workspace: WeakView<Workspace>, project: Model<Project>) -> Self {
         Self {
             workspace,
             project,
@@ -55,7 +60,7 @@ impl ProjectSymbolsDelegate {
         }
     }
 
-    fn filter(&mut self, query: &str, cx: &mut ViewContext<ProjectSymbols>) {
+    fn filter(&mut self, query: &str, cx: &mut ViewContext<Picker<Self>>) {
         const MAX_MATCHES: usize = 100;
         let mut visible_matches = cx.background_executor().block(fuzzy::match_strings(
             &self.visible_match_candidates,
@@ -63,7 +68,7 @@ impl ProjectSymbolsDelegate {
             false,
             MAX_MATCHES,
             &Default::default(),
-            cx.background().clone(),
+            cx.background_executor().clone(),
         ));
         let mut external_matches = cx.background_executor().block(fuzzy::match_strings(
             &self.external_match_candidates,
@@ -71,7 +76,7 @@ impl ProjectSymbolsDelegate {
             false,
             MAX_MATCHES - visible_matches.len().min(MAX_MATCHES),
             &Default::default(),
-            cx.background().clone(),
+            cx.background_executor().clone(),
         ));
         let sort_key_for_match = |mat: &StringMatch| {
             let symbol = &self.symbols[mat.candidate_id];
@@ -100,11 +105,12 @@ impl ProjectSymbolsDelegate {
 }
 
 impl PickerDelegate for ProjectSymbolsDelegate {
+    type ListItem = ListItem;
     fn placeholder_text(&self) -> Arc<str> {
         "Search project symbols...".into()
     }
 
-    fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<ProjectSymbols>) {
+    fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
         if let Some(symbol) = self
             .matches
             .get(self.selected_match_index)
@@ -137,11 +143,11 @@ impl PickerDelegate for ProjectSymbolsDelegate {
                 Ok::<_, anyhow::Error>(())
             })
             .detach_and_log_err(cx);
-            cx.emit(PickerEvent::Dismiss);
+            cx.emit(DismissEvent);
         }
     }
 
-    fn dismissed(&mut self, _cx: &mut ViewContext<ProjectSymbols>) {}
+    fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
 
     fn match_count(&self) -> usize {
         self.matches.len()
@@ -151,11 +157,11 @@ impl PickerDelegate for ProjectSymbolsDelegate {
         self.selected_match_index
     }
 
-    fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<ProjectSymbols>) {
+    fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
         self.selected_match_index = ix;
     }
 
-    fn update_matches(&mut self, query: String, cx: &mut ViewContext<ProjectSymbols>) -> Task<()> {
+    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
         self.filter(&query, cx);
         self.show_worktree_root_name = self.project.read(cx).visible_worktrees(cx).count() > 1;
         let symbols = self
@@ -165,7 +171,7 @@ impl PickerDelegate for ProjectSymbolsDelegate {
             let symbols = symbols.await.log_err();
             if let Some(symbols) = symbols {
                 this.update(&mut cx, |this, cx| {
-                    let delegate = this.delegate_mut();
+                    let delegate = &mut this.delegate;
                     let project = delegate.project.read(cx);
                     let (visible_match_candidates, external_match_candidates) = symbols
                         .iter()
@@ -195,17 +201,12 @@ impl PickerDelegate for ProjectSymbolsDelegate {
     fn render_match(
         &self,
         ix: usize,
-        mouse_state: &mut MouseState,
         selected: bool,
-        cx: &AppContext,
-    ) -> AnyElement<Picker<Self>> {
-        let theme = theme::current(cx);
-        let style = &theme.picker.item;
-        let current_style = style.in_state(selected).style_for(mouse_state);
-
+        cx: &mut ViewContext<Picker<Self>>,
+    ) -> Option<Self::ListItem> {
         let string_match = &self.matches[ix];
         let symbol = &self.symbols[string_match.candidate_id];
-        let syntax_runs = styled_runs_for_code_label(&symbol.label, &theme.editor.syntax);
+        let syntax_runs = styled_runs_for_code_label(&symbol.label, cx.theme().syntax());
 
         let mut path = symbol.path.path.to_string_lossy();
         if self.show_worktree_root_name {
@@ -219,29 +220,39 @@ impl PickerDelegate for ProjectSymbolsDelegate {
                 ));
             }
         }
+        let label = symbol.label.text.clone();
+        let path = path.to_string().clone();
+
+        let highlights = gpui::combine_highlights(
+            string_match
+                .positions
+                .iter()
+                .map(|pos| (*pos..pos + 1, FontWeight::BOLD.into())),
+            syntax_runs.map(|(range, mut highlight)| {
+                // Ignore font weight for syntax highlighting, as we'll use it
+                // for fuzzy matches.
+                highlight.font_weight = None;
+                (range, highlight)
+            }),
+        );
 
-        Flex::column()
-            .with_child(
-                Text::new(symbol.label.text.clone(), current_style.label.text.clone())
-                    .with_soft_wrap(false)
-                    .with_highlights(combine_syntax_and_fuzzy_match_highlights(
-                        &symbol.label.text,
-                        current_style.label.text.clone().into(),
-                        syntax_runs,
-                        &string_match.positions,
-                    )),
-            )
-            .with_child(
-                // Avoid styling the path differently when it is selected, since
-                // the symbol's syntax highlighting doesn't change when selected.
-                Label::new(
-                    path.to_string(),
-                    style.inactive_state().default.label.clone(),
+        Some(
+            ListItem::new(ix)
+                .inset(true)
+                .spacing(ListItemSpacing::Sparse)
+                .selected(selected)
+                .child(
+                    // todo!() combine_syntax_and_fuzzy_match_highlights()
+                    v_stack()
+                        .child(
+                            LabelLike::new().child(
+                                StyledText::new(label)
+                                    .with_highlights(&cx.text_style().clone(), highlights),
+                            ),
+                        )
+                        .child(Label::new(path).color(Color::Muted)),
                 ),
-            )
-            .contained()
-            .with_style(current_style.container)
-            .into_any()
+        )
     }
 }
 
@@ -249,7 +260,7 @@ impl PickerDelegate for ProjectSymbolsDelegate {
 mod tests {
     use super::*;
     use futures::StreamExt;
-    use gpui::{serde_json::json, TestAppContext};
+    use gpui::{serde_json::json, TestAppContext, VisualContext};
     use language::{FakeLspAdapter, Language, LanguageConfig};
     use project::FakeFs;
     use settings::SettingsStore;
@@ -271,7 +282,7 @@ mod tests {
             .set_fake_lsp_adapter(Arc::<FakeLspAdapter>::default())
             .await;
 
-        let fs = FakeFs::new(cx.background());
+        let fs = FakeFs::new(cx.executor());
         fs.insert_tree("/dir", json!({ "test.rs": "" })).await;
 
         let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
@@ -294,7 +305,7 @@ mod tests {
         let fake_server = fake_servers.next().await.unwrap();
         fake_server.handle_request::<lsp::WorkspaceSymbolRequest, _, _>(
             move |params: lsp::WorkspaceSymbolParams, cx| {
-                let executor = cx.background();
+                let executor = cx.background_executor().clone();
                 let fake_symbols = fake_symbols.clone();
                 async move {
                     let candidates = fake_symbols
@@ -326,12 +337,11 @@ mod tests {
             },
         );
 
-        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let workspace = window.root(cx);
+        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
 
         // Create the project symbols view.
-        let symbols = window.add_view(cx, |cx| {
-            ProjectSymbols::new(
+        let symbols = cx.new_view(|cx| {
+            Picker::new(
                 ProjectSymbolsDelegate::new(workspace.downgrade(), project.clone()),
                 cx,
             )
@@ -346,9 +356,9 @@ mod tests {
             p.update_matches("onex".to_string(), cx);
         });
 
-        cx.foreground().run_until_parked();
-        symbols.read_with(cx, |symbols, _| {
-            assert_eq!(symbols.delegate().matches.len(), 0);
+        cx.run_until_parked();
+        symbols.update(cx, |symbols, _| {
+            assert_eq!(symbols.delegate.matches.len(), 0);
         });
 
         // Spawn more updates such that in the end, there are matches.
@@ -357,9 +367,9 @@ mod tests {
             p.update_matches("on".to_string(), cx);
         });
 
-        cx.foreground().run_until_parked();
-        symbols.read_with(cx, |symbols, _| {
-            let delegate = symbols.delegate();
+        cx.run_until_parked();
+        symbols.update(cx, |symbols, _| {
+            let delegate = &symbols.delegate;
             assert_eq!(delegate.matches.len(), 2);
             assert_eq!(delegate.matches[0].string, "ton");
             assert_eq!(delegate.matches[1].string, "one");
@@ -371,17 +381,17 @@ mod tests {
             p.update_matches("".to_string(), cx);
         });
 
-        cx.foreground().run_until_parked();
-        symbols.read_with(cx, |symbols, _| {
-            assert_eq!(symbols.delegate().matches.len(), 0);
+        cx.run_until_parked();
+        symbols.update(cx, |symbols, _| {
+            assert_eq!(symbols.delegate.matches.len(), 0);
         });
     }
 
     fn init_test(cx: &mut TestAppContext) {
-        cx.foreground().forbid_parking();
         cx.update(|cx| {
-            cx.set_global(SettingsStore::test(cx));
-            theme::init((), cx);
+            let store = SettingsStore::test(cx);
+            cx.set_global(store);
+            theme::init(theme::LoadThemes::JustBase, cx);
             language::init(cx);
             Project::init_settings(cx);
             workspace::init_settings(cx);

crates/project_symbols2/Cargo.toml 🔗

@@ -1,37 +0,0 @@
-[package]
-name = "project_symbols2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/project_symbols.rs"
-doctest = false
-
-[dependencies]
-editor = { package = "editor2", path = "../editor2" }
-fuzzy = {package = "fuzzy2",  path = "../fuzzy2" }
-gpui = {package = "gpui2",  path = "../gpui2" }
-picker = {package = "picker2",  path = "../picker2" }
-project = { package = "project2", path = "../project2" }
-text = {package = "text2",  path = "../text2" }
-settings = {package = "settings2",  path = "../settings2" }
-workspace = {package = "workspace2",  path = "../workspace2" }
-theme = { package = "theme2", path = "../theme2" }
-util = { path = "../util" }
-
-anyhow.workspace = true
-ordered-float.workspace = true
-postage.workspace = true
-smol.workspace = true
-
-[dev-dependencies]
-futures.workspace = true
-editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
-settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-language = { package = "language2", path = "../language2", features = ["test-support"] }
-lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
-project = { package = "project2", path = "../project2", features = ["test-support"] }
-theme = { package = "theme2", path = "../theme2", features = ["test-support"] }
-workspace = { package = "workspace2",  path = "../workspace2", features = ["test-support"] }

crates/project_symbols2/src/project_symbols.rs 🔗

@@ -1,415 +0,0 @@
-use editor::{scroll::autoscroll::Autoscroll, styled_runs_for_code_label, Bias, Editor};
-use fuzzy::{StringMatch, StringMatchCandidate};
-use gpui::{
-    actions, rems, AppContext, DismissEvent, FontWeight, Model, ParentElement, StyledText, Task,
-    View, ViewContext, WeakView,
-};
-use ordered_float::OrderedFloat;
-use picker::{Picker, PickerDelegate};
-use project::{Project, Symbol};
-use std::{borrow::Cow, cmp::Reverse, sync::Arc};
-use theme::ActiveTheme;
-use util::ResultExt;
-use workspace::{
-    ui::{v_stack, Color, Label, LabelCommon, LabelLike, ListItem, ListItemSpacing, Selectable},
-    Workspace,
-};
-
-actions!(project_symbols, [Toggle]);
-
-pub fn init(cx: &mut AppContext) {
-    cx.observe_new_views(
-        |workspace: &mut Workspace, _: &mut ViewContext<Workspace>| {
-            workspace.register_action(|workspace, _: &Toggle, cx| {
-                let project = workspace.project().clone();
-                let handle = cx.view().downgrade();
-                workspace.toggle_modal(cx, move |cx| {
-                    let delegate = ProjectSymbolsDelegate::new(handle, project);
-                    Picker::new(delegate, cx).width(rems(34.))
-                })
-            });
-        },
-    )
-    .detach();
-}
-
-pub type ProjectSymbols = View<Picker<ProjectSymbolsDelegate>>;
-
-pub struct ProjectSymbolsDelegate {
-    workspace: WeakView<Workspace>,
-    project: Model<Project>,
-    selected_match_index: usize,
-    symbols: Vec<Symbol>,
-    visible_match_candidates: Vec<StringMatchCandidate>,
-    external_match_candidates: Vec<StringMatchCandidate>,
-    show_worktree_root_name: bool,
-    matches: Vec<StringMatch>,
-}
-
-impl ProjectSymbolsDelegate {
-    fn new(workspace: WeakView<Workspace>, project: Model<Project>) -> Self {
-        Self {
-            workspace,
-            project,
-            selected_match_index: 0,
-            symbols: Default::default(),
-            visible_match_candidates: Default::default(),
-            external_match_candidates: Default::default(),
-            matches: Default::default(),
-            show_worktree_root_name: false,
-        }
-    }
-
-    fn filter(&mut self, query: &str, cx: &mut ViewContext<Picker<Self>>) {
-        const MAX_MATCHES: usize = 100;
-        let mut visible_matches = cx.background_executor().block(fuzzy::match_strings(
-            &self.visible_match_candidates,
-            query,
-            false,
-            MAX_MATCHES,
-            &Default::default(),
-            cx.background_executor().clone(),
-        ));
-        let mut external_matches = cx.background_executor().block(fuzzy::match_strings(
-            &self.external_match_candidates,
-            query,
-            false,
-            MAX_MATCHES - visible_matches.len().min(MAX_MATCHES),
-            &Default::default(),
-            cx.background_executor().clone(),
-        ));
-        let sort_key_for_match = |mat: &StringMatch| {
-            let symbol = &self.symbols[mat.candidate_id];
-            (
-                Reverse(OrderedFloat(mat.score)),
-                &symbol.label.text[symbol.label.filter_range.clone()],
-            )
-        };
-
-        visible_matches.sort_unstable_by_key(sort_key_for_match);
-        external_matches.sort_unstable_by_key(sort_key_for_match);
-        let mut matches = visible_matches;
-        matches.append(&mut external_matches);
-
-        for mat in &mut matches {
-            let symbol = &self.symbols[mat.candidate_id];
-            let filter_start = symbol.label.filter_range.start;
-            for position in &mut mat.positions {
-                *position += filter_start;
-            }
-        }
-
-        self.matches = matches;
-        self.set_selected_index(0, cx);
-    }
-}
-
-impl PickerDelegate for ProjectSymbolsDelegate {
-    type ListItem = ListItem;
-    fn placeholder_text(&self) -> Arc<str> {
-        "Search project symbols...".into()
-    }
-
-    fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
-        if let Some(symbol) = self
-            .matches
-            .get(self.selected_match_index)
-            .map(|mat| self.symbols[mat.candidate_id].clone())
-        {
-            let buffer = self.project.update(cx, |project, cx| {
-                project.open_buffer_for_symbol(&symbol, cx)
-            });
-            let symbol = symbol.clone();
-            let workspace = self.workspace.clone();
-            cx.spawn(|_, mut cx| async move {
-                let buffer = buffer.await?;
-                workspace.update(&mut cx, |workspace, cx| {
-                    let position = buffer
-                        .read(cx)
-                        .clip_point_utf16(symbol.range.start, Bias::Left);
-
-                    let editor = if secondary {
-                        workspace.split_project_item::<Editor>(buffer, cx)
-                    } else {
-                        workspace.open_project_item::<Editor>(buffer, cx)
-                    };
-
-                    editor.update(cx, |editor, cx| {
-                        editor.change_selections(Some(Autoscroll::center()), cx, |s| {
-                            s.select_ranges([position..position])
-                        });
-                    });
-                })?;
-                Ok::<_, anyhow::Error>(())
-            })
-            .detach_and_log_err(cx);
-            cx.emit(DismissEvent);
-        }
-    }
-
-    fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
-
-    fn match_count(&self) -> usize {
-        self.matches.len()
-    }
-
-    fn selected_index(&self) -> usize {
-        self.selected_match_index
-    }
-
-    fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
-        self.selected_match_index = ix;
-    }
-
-    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
-        self.filter(&query, cx);
-        self.show_worktree_root_name = self.project.read(cx).visible_worktrees(cx).count() > 1;
-        let symbols = self
-            .project
-            .update(cx, |project, cx| project.symbols(&query, cx));
-        cx.spawn(|this, mut cx| async move {
-            let symbols = symbols.await.log_err();
-            if let Some(symbols) = symbols {
-                this.update(&mut cx, |this, cx| {
-                    let delegate = &mut this.delegate;
-                    let project = delegate.project.read(cx);
-                    let (visible_match_candidates, external_match_candidates) = symbols
-                        .iter()
-                        .enumerate()
-                        .map(|(id, symbol)| {
-                            StringMatchCandidate::new(
-                                id,
-                                symbol.label.text[symbol.label.filter_range.clone()].to_string(),
-                            )
-                        })
-                        .partition(|candidate| {
-                            project
-                                .entry_for_path(&symbols[candidate.id].path, cx)
-                                .map_or(false, |e| !e.is_ignored)
-                        });
-
-                    delegate.visible_match_candidates = visible_match_candidates;
-                    delegate.external_match_candidates = external_match_candidates;
-                    delegate.symbols = symbols;
-                    delegate.filter(&query, cx);
-                })
-                .log_err();
-            }
-        })
-    }
-
-    fn render_match(
-        &self,
-        ix: usize,
-        selected: bool,
-        cx: &mut ViewContext<Picker<Self>>,
-    ) -> Option<Self::ListItem> {
-        let string_match = &self.matches[ix];
-        let symbol = &self.symbols[string_match.candidate_id];
-        let syntax_runs = styled_runs_for_code_label(&symbol.label, cx.theme().syntax());
-
-        let mut path = symbol.path.path.to_string_lossy();
-        if self.show_worktree_root_name {
-            let project = self.project.read(cx);
-            if let Some(worktree) = project.worktree_for_id(symbol.path.worktree_id, cx) {
-                path = Cow::Owned(format!(
-                    "{}{}{}",
-                    worktree.read(cx).root_name(),
-                    std::path::MAIN_SEPARATOR,
-                    path.as_ref()
-                ));
-            }
-        }
-        let label = symbol.label.text.clone();
-        let path = path.to_string().clone();
-
-        let highlights = gpui::combine_highlights(
-            string_match
-                .positions
-                .iter()
-                .map(|pos| (*pos..pos + 1, FontWeight::BOLD.into())),
-            syntax_runs.map(|(range, mut highlight)| {
-                // Ignore font weight for syntax highlighting, as we'll use it
-                // for fuzzy matches.
-                highlight.font_weight = None;
-                (range, highlight)
-            }),
-        );
-
-        Some(
-            ListItem::new(ix)
-                .inset(true)
-                .spacing(ListItemSpacing::Sparse)
-                .selected(selected)
-                .child(
-                    // todo!() combine_syntax_and_fuzzy_match_highlights()
-                    v_stack()
-                        .child(
-                            LabelLike::new().child(
-                                StyledText::new(label)
-                                    .with_highlights(&cx.text_style().clone(), highlights),
-                            ),
-                        )
-                        .child(Label::new(path).color(Color::Muted)),
-                ),
-        )
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use futures::StreamExt;
-    use gpui::{serde_json::json, TestAppContext, VisualContext};
-    use language::{FakeLspAdapter, Language, LanguageConfig};
-    use project::FakeFs;
-    use settings::SettingsStore;
-    use std::{path::Path, sync::Arc};
-
-    #[gpui::test]
-    async fn test_project_symbols(cx: &mut TestAppContext) {
-        init_test(cx);
-
-        let mut language = Language::new(
-            LanguageConfig {
-                name: "Rust".into(),
-                path_suffixes: vec!["rs".to_string()],
-                ..Default::default()
-            },
-            None,
-        );
-        let mut fake_servers = language
-            .set_fake_lsp_adapter(Arc::<FakeLspAdapter>::default())
-            .await;
-
-        let fs = FakeFs::new(cx.executor());
-        fs.insert_tree("/dir", json!({ "test.rs": "" })).await;
-
-        let project = Project::test(fs.clone(), ["/dir".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("/dir/test.rs", cx)
-            })
-            .await
-            .unwrap();
-
-        // Set up fake language server to return fuzzy matches against
-        // a fixed set of symbol names.
-        let fake_symbols = [
-            symbol("one", "/external"),
-            symbol("ton", "/dir/test.rs"),
-            symbol("uno", "/dir/test.rs"),
-        ];
-        let fake_server = fake_servers.next().await.unwrap();
-        fake_server.handle_request::<lsp::WorkspaceSymbolRequest, _, _>(
-            move |params: lsp::WorkspaceSymbolParams, cx| {
-                let executor = cx.background_executor().clone();
-                let fake_symbols = fake_symbols.clone();
-                async move {
-                    let candidates = fake_symbols
-                        .iter()
-                        .enumerate()
-                        .map(|(id, symbol)| StringMatchCandidate::new(id, symbol.name.clone()))
-                        .collect::<Vec<_>>();
-                    let matches = if params.query.is_empty() {
-                        Vec::new()
-                    } else {
-                        fuzzy::match_strings(
-                            &candidates,
-                            &params.query,
-                            true,
-                            100,
-                            &Default::default(),
-                            executor.clone(),
-                        )
-                        .await
-                    };
-
-                    Ok(Some(lsp::WorkspaceSymbolResponse::Flat(
-                        matches
-                            .into_iter()
-                            .map(|mat| fake_symbols[mat.candidate_id].clone())
-                            .collect(),
-                    )))
-                }
-            },
-        );
-
-        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
-
-        // Create the project symbols view.
-        let symbols = cx.new_view(|cx| {
-            Picker::new(
-                ProjectSymbolsDelegate::new(workspace.downgrade(), project.clone()),
-                cx,
-            )
-        });
-
-        // Spawn multiples updates before the first update completes,
-        // such that in the end, there are no matches. Testing for regression:
-        // https://github.com/zed-industries/zed/issues/861
-        symbols.update(cx, |p, cx| {
-            p.update_matches("o".to_string(), cx);
-            p.update_matches("on".to_string(), cx);
-            p.update_matches("onex".to_string(), cx);
-        });
-
-        cx.run_until_parked();
-        symbols.update(cx, |symbols, _| {
-            assert_eq!(symbols.delegate.matches.len(), 0);
-        });
-
-        // Spawn more updates such that in the end, there are matches.
-        symbols.update(cx, |p, cx| {
-            p.update_matches("one".to_string(), cx);
-            p.update_matches("on".to_string(), cx);
-        });
-
-        cx.run_until_parked();
-        symbols.update(cx, |symbols, _| {
-            let delegate = &symbols.delegate;
-            assert_eq!(delegate.matches.len(), 2);
-            assert_eq!(delegate.matches[0].string, "ton");
-            assert_eq!(delegate.matches[1].string, "one");
-        });
-
-        // Spawn more updates such that in the end, there are again no matches.
-        symbols.update(cx, |p, cx| {
-            p.update_matches("o".to_string(), cx);
-            p.update_matches("".to_string(), cx);
-        });
-
-        cx.run_until_parked();
-        symbols.update(cx, |symbols, _| {
-            assert_eq!(symbols.delegate.matches.len(), 0);
-        });
-    }
-
-    fn init_test(cx: &mut TestAppContext) {
-        cx.update(|cx| {
-            let store = SettingsStore::test(cx);
-            cx.set_global(store);
-            theme::init(theme::LoadThemes::JustBase, cx);
-            language::init(cx);
-            Project::init_settings(cx);
-            workspace::init_settings(cx);
-        });
-    }
-
-    fn symbol(name: &str, path: impl AsRef<Path>) -> lsp::SymbolInformation {
-        #[allow(deprecated)]
-        lsp::SymbolInformation {
-            name: name.to_string(),
-            kind: lsp::SymbolKind::FUNCTION,
-            tags: None,
-            deprecated: None,
-            container_name: None,
-            location: lsp::Location::new(
-                lsp::Url::from_file_path(path.as_ref()).unwrap(),
-                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
-            ),
-        }
-    }
-}

crates/quick_action_bar/Cargo.toml 🔗

@@ -13,11 +13,10 @@ assistant = { path = "../assistant" }
 editor = { path = "../editor" }
 gpui = { path = "../gpui" }
 search = { path = "../search" }
-theme = { path = "../theme" }
 workspace = { path = "../workspace" }
+ui = { path = "../ui" }
 
 [dev-dependencies]
 editor = { path = "../editor", features = ["test-support"] }
 gpui = { path = "../gpui", features = ["test-support"] }
-theme = { path = "../theme", features = ["test-support"] }
 workspace = { path = "../workspace", features = ["test-support"] }

crates/quick_action_bar/src/quick_action_bar.rs 🔗

@@ -1,155 +1,153 @@
-use assistant::{assistant_panel::InlineAssist, AssistantPanel};
+use assistant::{AssistantPanel, InlineAssist};
 use editor::Editor;
+
 use gpui::{
-    elements::{Empty, Flex, MouseEventHandler, ParentElement, Svg},
-    platform::{CursorStyle, MouseButton},
-    Action, AnyElement, Element, Entity, EventContext, Subscription, View, ViewContext, ViewHandle,
-    WeakViewHandle,
+    Action, ClickEvent, ElementId, EventEmitter, InteractiveElement, ParentElement, Render, Styled,
+    Subscription, View, ViewContext, WeakView,
 };
-
 use search::{buffer_search, BufferSearchBar};
-use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView, Workspace};
+use ui::{prelude::*, ButtonSize, ButtonStyle, Icon, IconButton, IconSize, Tooltip};
+use workspace::{
+    item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
+};
 
 pub struct QuickActionBar {
-    buffer_search_bar: ViewHandle<BufferSearchBar>,
+    buffer_search_bar: View<BufferSearchBar>,
     active_item: Option<Box<dyn ItemHandle>>,
-    inlay_hints_enabled_subscription: Option<Subscription>,
-    workspace: WeakViewHandle<Workspace>,
+    _inlay_hints_enabled_subscription: Option<Subscription>,
+    workspace: WeakView<Workspace>,
 }
 
 impl QuickActionBar {
-    pub fn new(buffer_search_bar: ViewHandle<BufferSearchBar>, workspace: &Workspace) -> Self {
+    pub fn new(buffer_search_bar: View<BufferSearchBar>, workspace: &Workspace) -> Self {
         Self {
             buffer_search_bar,
             active_item: None,
-            inlay_hints_enabled_subscription: None,
+            _inlay_hints_enabled_subscription: None,
             workspace: workspace.weak_handle(),
         }
     }
 
-    fn active_editor(&self) -> Option<ViewHandle<Editor>> {
+    fn active_editor(&self) -> Option<View<Editor>> {
         self.active_item
             .as_ref()
             .and_then(|item| item.downcast::<Editor>())
     }
 }
 
-impl Entity for QuickActionBar {
-    type Event = ();
-}
-
-impl View for QuickActionBar {
-    fn ui_name() -> &'static str {
-        "QuickActionsBar"
-    }
-
-    fn render(&mut self, cx: &mut gpui::ViewContext<'_, '_, Self>) -> gpui::AnyElement<Self> {
+impl Render for QuickActionBar {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
         let Some(editor) = self.active_editor() else {
-            return Empty::new().into_any();
+            return div().id("empty quick action bar");
         };
 
-        let mut bar = Flex::row();
-        if editor.read(cx).supports_inlay_hints(cx) {
-            bar = bar.with_child(render_quick_action_bar_button(
-                0,
-                "icons/inlay_hint.svg",
-                editor.read(cx).inlay_hints_enabled(),
-                (
-                    "Toggle Inlay Hints".to_string(),
-                    Some(Box::new(editor::ToggleInlayHints)),
-                ),
-                cx,
-                |this, cx| {
-                    if let Some(editor) = this.active_editor() {
-                        editor.update(cx, |editor, cx| {
-                            editor.toggle_inlay_hints(&editor::ToggleInlayHints, cx);
-                        });
-                    }
-                },
-            ));
-        }
-
-        if editor.read(cx).buffer().read(cx).is_singleton() {
-            let search_bar_shown = !self.buffer_search_bar.read(cx).is_dismissed();
-            let search_action = buffer_search::Deploy { focus: true };
-
-            bar = bar.with_child(render_quick_action_bar_button(
-                1,
-                "icons/magnifying_glass.svg",
-                search_bar_shown,
-                (
-                    "Buffer Search".to_string(),
-                    Some(Box::new(search_action.clone())),
-                ),
-                cx,
-                move |this, cx| {
-                    this.buffer_search_bar.update(cx, |buffer_search_bar, cx| {
-                        if search_bar_shown {
-                            buffer_search_bar.dismiss(&buffer_search::Dismiss, cx);
-                        } else {
-                            buffer_search_bar.deploy(&search_action, cx);
-                        }
+        let inlay_hints_button = Some(QuickActionBarButton::new(
+            "toggle inlay hints",
+            Icon::InlayHint,
+            editor.read(cx).inlay_hints_enabled(),
+            Box::new(editor::ToggleInlayHints),
+            "Toggle Inlay Hints",
+            {
+                let editor = editor.clone();
+                move |_, cx| {
+                    editor.update(cx, |editor, cx| {
+                        editor.toggle_inlay_hints(&editor::ToggleInlayHints, cx);
                     });
-                },
-            ));
-        }
-
-        bar.add_child(render_quick_action_bar_button(
-            2,
-            "icons/magic-wand.svg",
-            false,
-            ("Inline Assist".into(), Some(Box::new(InlineAssist))),
-            cx,
-            move |this, cx| {
-                if let Some(workspace) = this.workspace.upgrade(cx) {
-                    workspace.update(cx, |workspace, cx| {
-                        AssistantPanel::inline_assist(workspace, &Default::default(), cx);
+                }
+            },
+        ))
+        .filter(|_| editor.read(cx).supports_inlay_hints(cx));
+
+        let search_button = Some(QuickActionBarButton::new(
+            "toggle buffer search",
+            Icon::MagnifyingGlass,
+            !self.buffer_search_bar.read(cx).is_dismissed(),
+            Box::new(buffer_search::Deploy { focus: false }),
+            "Buffer Search",
+            {
+                let buffer_search_bar = self.buffer_search_bar.clone();
+                move |_, cx| {
+                    buffer_search_bar.update(cx, |search_bar, cx| {
+                        search_bar.toggle(&buffer_search::Deploy { focus: true }, cx)
                     });
                 }
             },
-        ));
+        ))
+        .filter(|_| editor.is_singleton(cx));
 
-        bar.into_any()
+        let assistant_button = QuickActionBarButton::new(
+            "toggle inline assistant",
+            Icon::MagicWand,
+            false,
+            Box::new(InlineAssist),
+            "Inline Assist",
+            {
+                let workspace = self.workspace.clone();
+                move |_, cx| {
+                    if let Some(workspace) = workspace.upgrade() {
+                        workspace.update(cx, |workspace, cx| {
+                            AssistantPanel::inline_assist(workspace, &InlineAssist, cx);
+                        });
+                    }
+                }
+            },
+        );
+
+        h_stack()
+            .id("quick action bar")
+            .p_1()
+            .gap_2()
+            .children(inlay_hints_button)
+            .children(search_button)
+            .child(assistant_button)
     }
 }
 
-fn render_quick_action_bar_button<
-    F: 'static + Fn(&mut QuickActionBar, &mut EventContext<QuickActionBar>),
->(
-    index: usize,
-    icon: &'static str,
+impl EventEmitter<ToolbarItemEvent> for QuickActionBar {}
+
+#[derive(IntoElement)]
+struct QuickActionBarButton {
+    id: ElementId,
+    icon: Icon,
     toggled: bool,
-    tooltip: (String, Option<Box<dyn Action>>),
-    cx: &mut ViewContext<QuickActionBar>,
-    on_click: F,
-) -> AnyElement<QuickActionBar> {
-    enum QuickActionBarButton {}
-
-    let theme = theme::current(cx);
-    let (tooltip_text, action) = tooltip;
-
-    MouseEventHandler::new::<QuickActionBarButton, _>(index, cx, |mouse_state, _| {
-        let style = theme
-            .workspace
-            .toolbar
-            .toggleable_tool
-            .in_state(toggled)
-            .style_for(mouse_state);
-        Svg::new(icon)
-            .with_color(style.color)
-            .constrained()
-            .with_width(style.icon_width)
-            .aligned()
-            .constrained()
-            .with_width(style.button_width)
-            .with_height(style.button_width)
-            .contained()
-            .with_style(style.container)
-    })
-    .with_cursor_style(CursorStyle::PointingHand)
-    .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx))
-    .with_tooltip::<QuickActionBarButton>(index, tooltip_text, action, theme.tooltip.clone(), cx)
-    .into_any_named("quick action bar button")
+    action: Box<dyn Action>,
+    tooltip: SharedString,
+    on_click: Box<dyn Fn(&ClickEvent, &mut WindowContext)>,
+}
+
+impl QuickActionBarButton {
+    fn new(
+        id: impl Into<ElementId>,
+        icon: Icon,
+        toggled: bool,
+        action: Box<dyn Action>,
+        tooltip: impl Into<SharedString>,
+        on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
+    ) -> Self {
+        Self {
+            id: id.into(),
+            icon,
+            toggled,
+            action,
+            tooltip: tooltip.into(),
+            on_click: Box::new(on_click),
+        }
+    }
+}
+
+impl RenderOnce for QuickActionBarButton {
+    fn render(self, _: &mut WindowContext) -> impl IntoElement {
+        let tooltip = self.tooltip.clone();
+        let action = self.action.boxed_clone();
+
+        IconButton::new(self.id.clone(), self.icon)
+            .size(ButtonSize::Compact)
+            .icon_size(IconSize::Small)
+            .style(ButtonStyle::Subtle)
+            .selected(self.toggled)
+            .tooltip(move |cx| Tooltip::for_action(tooltip.clone(), &*action, cx))
+            .on_click(move |event, cx| (self.on_click)(event, cx))
+    }
 }
 
 impl ToolbarItemView for QuickActionBar {
@@ -161,12 +159,12 @@ impl ToolbarItemView for QuickActionBar {
         match active_pane_item {
             Some(active_item) => {
                 self.active_item = Some(active_item.boxed_clone());
-                self.inlay_hints_enabled_subscription.take();
+                self._inlay_hints_enabled_subscription.take();
 
                 if let Some(editor) = active_item.downcast::<Editor>() {
                     let mut inlay_hints_enabled = editor.read(cx).inlay_hints_enabled();
                     let mut supports_inlay_hints = editor.read(cx).supports_inlay_hints(cx);
-                    self.inlay_hints_enabled_subscription =
+                    self._inlay_hints_enabled_subscription =
                         Some(cx.observe(&editor, move |_, editor, cx| {
                             let editor = editor.read(cx);
                             let new_inlay_hints_enabled = editor.inlay_hints_enabled();
@@ -179,7 +177,7 @@ impl ToolbarItemView for QuickActionBar {
                                 cx.notify()
                             }
                         }));
-                    ToolbarItemLocation::PrimaryRight { flex: None }
+                    ToolbarItemLocation::PrimaryRight
                 } else {
                     ToolbarItemLocation::Hidden
                 }

crates/quick_action_bar2/Cargo.toml 🔗

@@ -1,22 +0,0 @@
-[package]
-name = "quick_action_bar2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/quick_action_bar.rs"
-doctest = false
-
-[dependencies]
-assistant = { package = "assistant2", path = "../assistant2" }
-editor = { package = "editor2", path = "../editor2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-search = { package = "search2", path = "../search2" }
-workspace = { package = "workspace2", path = "../workspace2" }
-ui = { package = "ui2", path = "../ui2" }
-
-[dev-dependencies]
-editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }

crates/quick_action_bar2/src/quick_action_bar.rs 🔗

@@ -1,191 +0,0 @@
-use assistant::{AssistantPanel, InlineAssist};
-use editor::Editor;
-
-use gpui::{
-    Action, ClickEvent, ElementId, EventEmitter, InteractiveElement, ParentElement, Render, Styled,
-    Subscription, View, ViewContext, WeakView,
-};
-use search::{buffer_search, BufferSearchBar};
-use ui::{prelude::*, ButtonSize, ButtonStyle, Icon, IconButton, IconSize, Tooltip};
-use workspace::{
-    item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
-};
-
-pub struct QuickActionBar {
-    buffer_search_bar: View<BufferSearchBar>,
-    active_item: Option<Box<dyn ItemHandle>>,
-    _inlay_hints_enabled_subscription: Option<Subscription>,
-    workspace: WeakView<Workspace>,
-}
-
-impl QuickActionBar {
-    pub fn new(buffer_search_bar: View<BufferSearchBar>, workspace: &Workspace) -> Self {
-        Self {
-            buffer_search_bar,
-            active_item: None,
-            _inlay_hints_enabled_subscription: None,
-            workspace: workspace.weak_handle(),
-        }
-    }
-
-    fn active_editor(&self) -> Option<View<Editor>> {
-        self.active_item
-            .as_ref()
-            .and_then(|item| item.downcast::<Editor>())
-    }
-}
-
-impl Render for QuickActionBar {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        let Some(editor) = self.active_editor() else {
-            return div().id("empty quick action bar");
-        };
-
-        let inlay_hints_button = Some(QuickActionBarButton::new(
-            "toggle inlay hints",
-            Icon::InlayHint,
-            editor.read(cx).inlay_hints_enabled(),
-            Box::new(editor::ToggleInlayHints),
-            "Toggle Inlay Hints",
-            {
-                let editor = editor.clone();
-                move |_, cx| {
-                    editor.update(cx, |editor, cx| {
-                        editor.toggle_inlay_hints(&editor::ToggleInlayHints, cx);
-                    });
-                }
-            },
-        ))
-        .filter(|_| editor.read(cx).supports_inlay_hints(cx));
-
-        let search_button = Some(QuickActionBarButton::new(
-            "toggle buffer search",
-            Icon::MagnifyingGlass,
-            !self.buffer_search_bar.read(cx).is_dismissed(),
-            Box::new(buffer_search::Deploy { focus: false }),
-            "Buffer Search",
-            {
-                let buffer_search_bar = self.buffer_search_bar.clone();
-                move |_, cx| {
-                    buffer_search_bar.update(cx, |search_bar, cx| {
-                        search_bar.toggle(&buffer_search::Deploy { focus: true }, cx)
-                    });
-                }
-            },
-        ))
-        .filter(|_| editor.is_singleton(cx));
-
-        let assistant_button = QuickActionBarButton::new(
-            "toggle inline assistant",
-            Icon::MagicWand,
-            false,
-            Box::new(InlineAssist),
-            "Inline Assist",
-            {
-                let workspace = self.workspace.clone();
-                move |_, cx| {
-                    if let Some(workspace) = workspace.upgrade() {
-                        workspace.update(cx, |workspace, cx| {
-                            AssistantPanel::inline_assist(workspace, &InlineAssist, cx);
-                        });
-                    }
-                }
-            },
-        );
-
-        h_stack()
-            .id("quick action bar")
-            .p_1()
-            .gap_2()
-            .children(inlay_hints_button)
-            .children(search_button)
-            .child(assistant_button)
-    }
-}
-
-impl EventEmitter<ToolbarItemEvent> for QuickActionBar {}
-
-#[derive(IntoElement)]
-struct QuickActionBarButton {
-    id: ElementId,
-    icon: Icon,
-    toggled: bool,
-    action: Box<dyn Action>,
-    tooltip: SharedString,
-    on_click: Box<dyn Fn(&ClickEvent, &mut WindowContext)>,
-}
-
-impl QuickActionBarButton {
-    fn new(
-        id: impl Into<ElementId>,
-        icon: Icon,
-        toggled: bool,
-        action: Box<dyn Action>,
-        tooltip: impl Into<SharedString>,
-        on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
-    ) -> Self {
-        Self {
-            id: id.into(),
-            icon,
-            toggled,
-            action,
-            tooltip: tooltip.into(),
-            on_click: Box::new(on_click),
-        }
-    }
-}
-
-impl RenderOnce for QuickActionBarButton {
-    fn render(self, _: &mut WindowContext) -> impl IntoElement {
-        let tooltip = self.tooltip.clone();
-        let action = self.action.boxed_clone();
-
-        IconButton::new(self.id.clone(), self.icon)
-            .size(ButtonSize::Compact)
-            .icon_size(IconSize::Small)
-            .style(ButtonStyle::Subtle)
-            .selected(self.toggled)
-            .tooltip(move |cx| Tooltip::for_action(tooltip.clone(), &*action, cx))
-            .on_click(move |event, cx| (self.on_click)(event, cx))
-    }
-}
-
-impl ToolbarItemView for QuickActionBar {
-    fn set_active_pane_item(
-        &mut self,
-        active_pane_item: Option<&dyn ItemHandle>,
-        cx: &mut ViewContext<Self>,
-    ) -> ToolbarItemLocation {
-        match active_pane_item {
-            Some(active_item) => {
-                self.active_item = Some(active_item.boxed_clone());
-                self._inlay_hints_enabled_subscription.take();
-
-                if let Some(editor) = active_item.downcast::<Editor>() {
-                    let mut inlay_hints_enabled = editor.read(cx).inlay_hints_enabled();
-                    let mut supports_inlay_hints = editor.read(cx).supports_inlay_hints(cx);
-                    self._inlay_hints_enabled_subscription =
-                        Some(cx.observe(&editor, move |_, editor, cx| {
-                            let editor = editor.read(cx);
-                            let new_inlay_hints_enabled = editor.inlay_hints_enabled();
-                            let new_supports_inlay_hints = editor.supports_inlay_hints(cx);
-                            let should_notify = inlay_hints_enabled != new_inlay_hints_enabled
-                                || supports_inlay_hints != new_supports_inlay_hints;
-                            inlay_hints_enabled = new_inlay_hints_enabled;
-                            supports_inlay_hints = new_supports_inlay_hints;
-                            if should_notify {
-                                cx.notify()
-                            }
-                        }));
-                    ToolbarItemLocation::PrimaryRight
-                } else {
-                    ToolbarItemLocation::Hidden
-                }
-            }
-            None => {
-                self.active_item = None;
-                ToolbarItemLocation::Hidden
-            }
-        }
-    }
-}

crates/recent_projects/Cargo.toml 🔗

@@ -9,9 +9,8 @@ path = "src/recent_projects.rs"
 doctest = false
 
 [dependencies]
-db = { path = "../db" }
 editor = { path = "../editor" }
-fuzzy = { path = "../fuzzy" }
+fuzzy = {  path = "../fuzzy" }
 gpui = { path = "../gpui" }
 language = { path = "../language" }
 picker = { path = "../picker" }
@@ -19,6 +18,7 @@ settings = { path = "../settings" }
 text = { path = "../text" }
 util = { path = "../util"}
 theme = { path = "../theme" }
+ui = { path = "../ui" }
 workspace = { path = "../workspace" }
 
 futures.workspace = true

crates/recent_projects/src/highlighted_workspace_location.rs 🔗

@@ -1,13 +1,11 @@
 use std::path::Path;
 
 use fuzzy::StringMatch;
-use gpui::{
-    elements::{Label, LabelStyle},
-    AnyElement, Element,
-};
+use ui::{prelude::*, HighlightedLabel};
 use util::paths::PathExt;
 use workspace::WorkspaceLocation;
 
+#[derive(IntoElement)]
 pub struct HighlightedText {
     pub text: String,
     pub highlight_positions: Vec<usize>,
@@ -42,11 +40,11 @@ impl HighlightedText {
             char_count,
         }
     }
+}
 
-    pub fn render<V: 'static>(self, style: impl Into<LabelStyle>) -> AnyElement<V> {
-        Label::new(self.text, style)
-            .with_highlights(self.highlight_positions)
-            .into_any()
+impl RenderOnce for HighlightedText {
+    fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
+        HighlightedLabel::new(self.text, self.highlight_positions)
     }
 }
 

crates/recent_projects/src/recent_projects.rs 🔗

@@ -1,86 +1,122 @@
 mod highlighted_workspace_location;
+mod projects;
 
 use fuzzy::{StringMatch, StringMatchCandidate};
 use gpui::{
-    actions,
-    anyhow::Result,
-    elements::{Flex, ParentElement},
-    AnyElement, AppContext, Element, Task, ViewContext, WeakViewHandle,
+    AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Result, Subscription, Task,
+    View, ViewContext, WeakView,
 };
 use highlighted_workspace_location::HighlightedWorkspaceLocation;
 use ordered_float::OrderedFloat;
-use picker::{Picker, PickerDelegate, PickerEvent};
+use picker::{Picker, PickerDelegate};
 use std::sync::Arc;
+use ui::{prelude::*, ListItem, ListItemSpacing};
 use util::paths::PathExt;
-use workspace::{
-    notifications::simple_message_notification::MessageNotification, Workspace, WorkspaceLocation,
-    WORKSPACE_DB,
-};
+use workspace::{ModalView, Workspace, WorkspaceLocation, WORKSPACE_DB};
 
-actions!(projects, [OpenRecent]);
+pub use projects::OpenRecent;
 
 pub fn init(cx: &mut AppContext) {
-    cx.add_async_action(toggle);
-    RecentProjects::init(cx);
+    cx.observe_new_views(RecentProjects::register).detach();
+}
+
+pub struct RecentProjects {
+    pub picker: View<Picker<RecentProjectsDelegate>>,
+    rem_width: f32,
+    _subscription: Subscription,
 }
 
-fn toggle(
-    _: &mut Workspace,
-    _: &OpenRecent,
-    cx: &mut ViewContext<Workspace>,
-) -> Option<Task<Result<()>>> {
-    Some(cx.spawn(|workspace, mut cx| async move {
-        let workspace_locations: Vec<_> = cx
-            .background()
-            .spawn(async {
-                WORKSPACE_DB
-                    .recent_workspaces_on_disk()
-                    .await
-                    .unwrap_or_default()
-                    .into_iter()
-                    .map(|(_, location)| location)
-                    .collect()
+impl ModalView for RecentProjects {}
+
+impl RecentProjects {
+    fn new(delegate: RecentProjectsDelegate, rem_width: f32, cx: &mut ViewContext<Self>) -> Self {
+        let picker = cx.new_view(|cx| Picker::new(delegate, cx));
+        let _subscription = cx.subscribe(&picker, |_, _, _, cx| cx.emit(DismissEvent));
+        // We do not want to block the UI on a potentially lenghty call to DB, so we're gonna swap
+        // out workspace locations once the future runs to completion.
+        cx.spawn(|this, mut cx| async move {
+            let workspaces = WORKSPACE_DB
+                .recent_workspaces_on_disk()
+                .await
+                .unwrap_or_default()
+                .into_iter()
+                .map(|(_, location)| location)
+                .collect();
+            this.update(&mut cx, move |this, cx| {
+                this.picker.update(cx, move |picker, cx| {
+                    picker.delegate.workspace_locations = workspaces;
+                    picker.update_matches(picker.query(cx), cx)
+                })
             })
-            .await;
-
-        workspace.update(&mut cx, |workspace, cx| {
-            if !workspace_locations.is_empty() {
-                workspace.toggle_modal(cx, |_, cx| {
-                    let workspace = cx.weak_handle();
-                    cx.add_view(|cx| {
-                        RecentProjects::new(
-                            RecentProjectsDelegate::new(workspace, workspace_locations, true),
-                            cx,
-                        )
-                        .with_max_size(800., 1200.)
-                    })
+            .ok()
+        })
+        .detach();
+        Self {
+            picker,
+            rem_width,
+            _subscription,
+        }
+    }
+
+    fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+        workspace.register_action(|workspace, _: &OpenRecent, cx| {
+            let Some(recent_projects) = workspace.active_modal::<Self>(cx) else {
+                if let Some(handler) = Self::open(workspace, cx) {
+                    handler.detach_and_log_err(cx);
+                }
+                return;
+            };
+
+            recent_projects.update(cx, |recent_projects, cx| {
+                recent_projects
+                    .picker
+                    .update(cx, |picker, cx| picker.cycle_selection(cx))
+            });
+        });
+    }
+
+    fn open(_: &mut Workspace, cx: &mut ViewContext<Workspace>) -> Option<Task<Result<()>>> {
+        Some(cx.spawn(|workspace, mut cx| async move {
+            workspace.update(&mut cx, |workspace, cx| {
+                let weak_workspace = cx.view().downgrade();
+                workspace.toggle_modal(cx, |cx| {
+                    let delegate = RecentProjectsDelegate::new(weak_workspace, true);
+
+                    let modal = RecentProjects::new(delegate, 34., cx);
+                    modal
                 });
-            } else {
-                workspace.show_notification(0, cx, |cx| {
-                    cx.add_view(|_| MessageNotification::new("No recent projects to open."))
-                })
-            }
-        })?;
-        Ok(())
-    }))
+            })?;
+            Ok(())
+        }))
+    }
+    pub fn open_popover(workspace: WeakView<Workspace>, cx: &mut WindowContext<'_>) -> View<Self> {
+        cx.new_view(|cx| Self::new(RecentProjectsDelegate::new(workspace, false), 20., cx))
+    }
 }
 
-pub fn build_recent_projects(
-    workspace: WeakViewHandle<Workspace>,
-    workspaces: Vec<WorkspaceLocation>,
-    cx: &mut ViewContext<RecentProjects>,
-) -> RecentProjects {
-    Picker::new(
-        RecentProjectsDelegate::new(workspace, workspaces, false),
-        cx,
-    )
-    .with_theme(|theme| theme.picker.clone())
+impl EventEmitter<DismissEvent> for RecentProjects {}
+
+impl FocusableView for RecentProjects {
+    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
+        self.picker.focus_handle(cx)
+    }
 }
 
-pub type RecentProjects = Picker<RecentProjectsDelegate>;
+impl Render for RecentProjects {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        v_stack()
+            .w(rems(self.rem_width))
+            .child(self.picker.clone())
+            .on_mouse_down_out(cx.listener(|this, _, cx| {
+                this.picker.update(cx, |this, cx| {
+                    this.cancel(&Default::default(), cx);
+                })
+            }))
+    }
+}
 
 pub struct RecentProjectsDelegate {
-    workspace: WeakViewHandle<Workspace>,
+    workspace: WeakView<Workspace>,
     workspace_locations: Vec<WorkspaceLocation>,
     selected_match_index: usize,
     matches: Vec<StringMatch>,
@@ -88,22 +124,20 @@ pub struct RecentProjectsDelegate {
 }
 
 impl RecentProjectsDelegate {
-    fn new(
-        workspace: WeakViewHandle<Workspace>,
-        workspace_locations: Vec<WorkspaceLocation>,
-        render_paths: bool,
-    ) -> Self {
+    fn new(workspace: WeakView<Workspace>, render_paths: bool) -> Self {
         Self {
             workspace,
-            workspace_locations,
+            workspace_locations: vec![],
             selected_match_index: 0,
             matches: Default::default(),
             render_paths,
         }
     }
 }
-
+impl EventEmitter<DismissEvent> for RecentProjectsDelegate {}
 impl PickerDelegate for RecentProjectsDelegate {
+    type ListItem = ListItem;
+
     fn placeholder_text(&self) -> Arc<str> {
         "Recent Projects...".into()
     }
@@ -116,14 +150,14 @@ impl PickerDelegate for RecentProjectsDelegate {
         self.selected_match_index
     }
 
-    fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<RecentProjects>) {
+    fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
         self.selected_match_index = ix;
     }
 
     fn update_matches(
         &mut self,
         query: String,
-        cx: &mut ViewContext<RecentProjects>,
+        cx: &mut ViewContext<Picker<Self>>,
     ) -> gpui::Task<()> {
         let query = query.trim_start();
         let smart_case = query.chars().any(|c| c.is_uppercase());
@@ -147,7 +181,7 @@ impl PickerDelegate for RecentProjectsDelegate {
             smart_case,
             100,
             &Default::default(),
-            cx.background().clone(),
+            cx.background_executor().clone(),
         ));
         self.matches.sort_unstable_by_key(|m| m.candidate_id);
 
@@ -162,11 +196,11 @@ impl PickerDelegate for RecentProjectsDelegate {
         Task::ready(())
     }
 
-    fn confirm(&mut self, _: bool, cx: &mut ViewContext<RecentProjects>) {
+    fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
         if let Some((selected_match, workspace)) = self
             .matches
             .get(self.selected_index())
-            .zip(self.workspace.upgrade(cx))
+            .zip(self.workspace.upgrade())
         {
             let workspace_location = &self.workspace_locations[selected_match.candidate_id];
             workspace
@@ -175,41 +209,39 @@ impl PickerDelegate for RecentProjectsDelegate {
                         .open_workspace_for_paths(workspace_location.paths().as_ref().clone(), cx)
                 })
                 .detach_and_log_err(cx);
-            cx.emit(PickerEvent::Dismiss);
+            cx.emit(DismissEvent);
         }
     }
 
-    fn dismissed(&mut self, _cx: &mut ViewContext<RecentProjects>) {}
+    fn dismissed(&mut self, _: &mut ViewContext<Picker<Self>>) {}
 
     fn render_match(
         &self,
         ix: usize,
-        mouse_state: &mut gpui::MouseState,
         selected: bool,
-        cx: &gpui::AppContext,
-    ) -> AnyElement<Picker<Self>> {
-        let theme = theme::current(cx);
-        let style = theme.picker.item.in_state(selected).style_for(mouse_state);
-
-        let string_match = &self.matches[ix];
+        _cx: &mut ViewContext<Picker<Self>>,
+    ) -> Option<Self::ListItem> {
+        let Some(r#match) = self.matches.get(ix) else {
+            return None;
+        };
 
         let highlighted_location = HighlightedWorkspaceLocation::new(
-            &string_match,
-            &self.workspace_locations[string_match.candidate_id],
+            &r#match,
+            &self.workspace_locations[r#match.candidate_id],
         );
 
-        Flex::column()
-            .with_child(highlighted_location.names.render(style.label.clone()))
-            .with_children(
-                highlighted_location
-                    .paths
-                    .into_iter()
-                    .filter(|_| self.render_paths)
-                    .map(|highlighted_path| highlighted_path.render(style.label.clone())),
-            )
-            .flex(1., false)
-            .contained()
-            .with_style(style.container)
-            .into_any_named("match")
+        Some(
+            ListItem::new(ix)
+                .inset(true)
+                .spacing(ListItemSpacing::Sparse)
+                .selected(selected)
+                .child(
+                    v_stack()
+                        .child(highlighted_location.names)
+                        .when(self.render_paths, |this| {
+                            this.children(highlighted_location.paths)
+                        }),
+                ),
+        )
     }
 }

crates/recent_projects2/Cargo.toml 🔗

@@ -1,30 +0,0 @@
-[package]
-name = "recent_projects2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/recent_projects.rs"
-doctest = false
-
-[dependencies]
-editor = { package = "editor2", path = "../editor2" }
-fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-language = { package = "language2", path = "../language2" }
-picker = { package = "picker2", path = "../picker2" }
-settings = { package = "settings2", path = "../settings2" }
-text = { package = "text2", path = "../text2" }
-util = { path = "../util"}
-theme = { package = "theme2", path = "../theme2" }
-ui = { package = "ui2", path = "../ui2" }
-workspace = { package = "workspace2", path = "../workspace2" }
-
-futures.workspace = true
-ordered-float.workspace = true
-postage.workspace = true
-smol.workspace = true
-
-[dev-dependencies]
-editor = { package = "editor2", path = "../editor2", features = ["test-support"] }

crates/recent_projects2/src/highlighted_workspace_location.rs 🔗

@@ -1,129 +0,0 @@
-use std::path::Path;
-
-use fuzzy::StringMatch;
-use ui::{prelude::*, HighlightedLabel};
-use util::paths::PathExt;
-use workspace::WorkspaceLocation;
-
-#[derive(IntoElement)]
-pub struct HighlightedText {
-    pub text: String,
-    pub highlight_positions: Vec<usize>,
-    char_count: usize,
-}
-
-impl HighlightedText {
-    fn join(components: impl Iterator<Item = Self>, separator: &str) -> Self {
-        let mut char_count = 0;
-        let separator_char_count = separator.chars().count();
-        let mut text = String::new();
-        let mut highlight_positions = Vec::new();
-        for component in components {
-            if char_count != 0 {
-                text.push_str(separator);
-                char_count += separator_char_count;
-            }
-
-            highlight_positions.extend(
-                component
-                    .highlight_positions
-                    .iter()
-                    .map(|position| position + char_count),
-            );
-            text.push_str(&component.text);
-            char_count += component.text.chars().count();
-        }
-
-        Self {
-            text,
-            highlight_positions,
-            char_count,
-        }
-    }
-}
-
-impl RenderOnce for HighlightedText {
-    fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
-        HighlightedLabel::new(self.text, self.highlight_positions)
-    }
-}
-
-pub struct HighlightedWorkspaceLocation {
-    pub names: HighlightedText,
-    pub paths: Vec<HighlightedText>,
-}
-
-impl HighlightedWorkspaceLocation {
-    pub fn new(string_match: &StringMatch, location: &WorkspaceLocation) -> Self {
-        let mut path_start_offset = 0;
-        let (names, paths): (Vec<_>, Vec<_>) = location
-            .paths()
-            .iter()
-            .map(|path| {
-                let path = path.compact();
-                let highlighted_text = Self::highlights_for_path(
-                    path.as_ref(),
-                    &string_match.positions,
-                    path_start_offset,
-                );
-
-                path_start_offset += highlighted_text.1.char_count;
-
-                highlighted_text
-            })
-            .unzip();
-
-        Self {
-            names: HighlightedText::join(names.into_iter().filter_map(|name| name), ", "),
-            paths,
-        }
-    }
-
-    // Compute the highlighted text for the name and path
-    fn highlights_for_path(
-        path: &Path,
-        match_positions: &Vec<usize>,
-        path_start_offset: usize,
-    ) -> (Option<HighlightedText>, HighlightedText) {
-        let path_string = path.to_string_lossy();
-        let path_char_count = path_string.chars().count();
-        // Get the subset of match highlight positions that line up with the given path.
-        // Also adjusts them to start at the path start
-        let path_positions = match_positions
-            .iter()
-            .copied()
-            .skip_while(|position| *position < path_start_offset)
-            .take_while(|position| *position < path_start_offset + path_char_count)
-            .map(|position| position - path_start_offset)
-            .collect::<Vec<_>>();
-
-        // Again subset the highlight positions to just those that line up with the file_name
-        // again adjusted to the start of the file_name
-        let file_name_text_and_positions = path.file_name().map(|file_name| {
-            let text = file_name.to_string_lossy();
-            let char_count = text.chars().count();
-            let file_name_start = path_char_count - char_count;
-            let highlight_positions = path_positions
-                .iter()
-                .copied()
-                .skip_while(|position| *position < file_name_start)
-                .take_while(|position| *position < file_name_start + char_count)
-                .map(|position| position - file_name_start)
-                .collect::<Vec<_>>();
-            HighlightedText {
-                text: text.to_string(),
-                highlight_positions,
-                char_count,
-            }
-        });
-
-        (
-            file_name_text_and_positions,
-            HighlightedText {
-                text: path_string.to_string(),
-                highlight_positions: path_positions,
-                char_count: path_char_count,
-            },
-        )
-    }
-}

crates/recent_projects2/src/recent_projects.rs 🔗

@@ -1,247 +0,0 @@
-mod highlighted_workspace_location;
-mod projects;
-
-use fuzzy::{StringMatch, StringMatchCandidate};
-use gpui::{
-    AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Result, Subscription, Task,
-    View, ViewContext, WeakView,
-};
-use highlighted_workspace_location::HighlightedWorkspaceLocation;
-use ordered_float::OrderedFloat;
-use picker::{Picker, PickerDelegate};
-use std::sync::Arc;
-use ui::{prelude::*, ListItem, ListItemSpacing};
-use util::paths::PathExt;
-use workspace::{ModalView, Workspace, WorkspaceLocation, WORKSPACE_DB};
-
-pub use projects::OpenRecent;
-
-pub fn init(cx: &mut AppContext) {
-    cx.observe_new_views(RecentProjects::register).detach();
-}
-
-pub struct RecentProjects {
-    pub picker: View<Picker<RecentProjectsDelegate>>,
-    rem_width: f32,
-    _subscription: Subscription,
-}
-
-impl ModalView for RecentProjects {}
-
-impl RecentProjects {
-    fn new(delegate: RecentProjectsDelegate, rem_width: f32, cx: &mut ViewContext<Self>) -> Self {
-        let picker = cx.new_view(|cx| Picker::new(delegate, cx));
-        let _subscription = cx.subscribe(&picker, |_, _, _, cx| cx.emit(DismissEvent));
-        // We do not want to block the UI on a potentially lenghty call to DB, so we're gonna swap
-        // out workspace locations once the future runs to completion.
-        cx.spawn(|this, mut cx| async move {
-            let workspaces = WORKSPACE_DB
-                .recent_workspaces_on_disk()
-                .await
-                .unwrap_or_default()
-                .into_iter()
-                .map(|(_, location)| location)
-                .collect();
-            this.update(&mut cx, move |this, cx| {
-                this.picker.update(cx, move |picker, cx| {
-                    picker.delegate.workspace_locations = workspaces;
-                    picker.update_matches(picker.query(cx), cx)
-                })
-            })
-            .ok()
-        })
-        .detach();
-        Self {
-            picker,
-            rem_width,
-            _subscription,
-        }
-    }
-
-    fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
-        workspace.register_action(|workspace, _: &OpenRecent, cx| {
-            let Some(recent_projects) = workspace.active_modal::<Self>(cx) else {
-                if let Some(handler) = Self::open(workspace, cx) {
-                    handler.detach_and_log_err(cx);
-                }
-                return;
-            };
-
-            recent_projects.update(cx, |recent_projects, cx| {
-                recent_projects
-                    .picker
-                    .update(cx, |picker, cx| picker.cycle_selection(cx))
-            });
-        });
-    }
-
-    fn open(_: &mut Workspace, cx: &mut ViewContext<Workspace>) -> Option<Task<Result<()>>> {
-        Some(cx.spawn(|workspace, mut cx| async move {
-            workspace.update(&mut cx, |workspace, cx| {
-                let weak_workspace = cx.view().downgrade();
-                workspace.toggle_modal(cx, |cx| {
-                    let delegate = RecentProjectsDelegate::new(weak_workspace, true);
-
-                    let modal = RecentProjects::new(delegate, 34., cx);
-                    modal
-                });
-            })?;
-            Ok(())
-        }))
-    }
-    pub fn open_popover(workspace: WeakView<Workspace>, cx: &mut WindowContext<'_>) -> View<Self> {
-        cx.new_view(|cx| Self::new(RecentProjectsDelegate::new(workspace, false), 20., cx))
-    }
-}
-
-impl EventEmitter<DismissEvent> for RecentProjects {}
-
-impl FocusableView for RecentProjects {
-    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
-        self.picker.focus_handle(cx)
-    }
-}
-
-impl Render for RecentProjects {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        v_stack()
-            .w(rems(self.rem_width))
-            .child(self.picker.clone())
-            .on_mouse_down_out(cx.listener(|this, _, cx| {
-                this.picker.update(cx, |this, cx| {
-                    this.cancel(&Default::default(), cx);
-                })
-            }))
-    }
-}
-
-pub struct RecentProjectsDelegate {
-    workspace: WeakView<Workspace>,
-    workspace_locations: Vec<WorkspaceLocation>,
-    selected_match_index: usize,
-    matches: Vec<StringMatch>,
-    render_paths: bool,
-}
-
-impl RecentProjectsDelegate {
-    fn new(workspace: WeakView<Workspace>, render_paths: bool) -> Self {
-        Self {
-            workspace,
-            workspace_locations: vec![],
-            selected_match_index: 0,
-            matches: Default::default(),
-            render_paths,
-        }
-    }
-}
-impl EventEmitter<DismissEvent> for RecentProjectsDelegate {}
-impl PickerDelegate for RecentProjectsDelegate {
-    type ListItem = ListItem;
-
-    fn placeholder_text(&self) -> Arc<str> {
-        "Recent Projects...".into()
-    }
-
-    fn match_count(&self) -> usize {
-        self.matches.len()
-    }
-
-    fn selected_index(&self) -> usize {
-        self.selected_match_index
-    }
-
-    fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
-        self.selected_match_index = ix;
-    }
-
-    fn update_matches(
-        &mut self,
-        query: String,
-        cx: &mut ViewContext<Picker<Self>>,
-    ) -> gpui::Task<()> {
-        let query = query.trim_start();
-        let smart_case = query.chars().any(|c| c.is_uppercase());
-        let candidates = self
-            .workspace_locations
-            .iter()
-            .enumerate()
-            .map(|(id, location)| {
-                let combined_string = location
-                    .paths()
-                    .iter()
-                    .map(|path| path.compact().to_string_lossy().into_owned())
-                    .collect::<Vec<_>>()
-                    .join("");
-                StringMatchCandidate::new(id, combined_string)
-            })
-            .collect::<Vec<_>>();
-        self.matches = smol::block_on(fuzzy::match_strings(
-            candidates.as_slice(),
-            query,
-            smart_case,
-            100,
-            &Default::default(),
-            cx.background_executor().clone(),
-        ));
-        self.matches.sort_unstable_by_key(|m| m.candidate_id);
-
-        self.selected_match_index = self
-            .matches
-            .iter()
-            .enumerate()
-            .rev()
-            .max_by_key(|(_, m)| OrderedFloat(m.score))
-            .map(|(ix, _)| ix)
-            .unwrap_or(0);
-        Task::ready(())
-    }
-
-    fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
-        if let Some((selected_match, workspace)) = self
-            .matches
-            .get(self.selected_index())
-            .zip(self.workspace.upgrade())
-        {
-            let workspace_location = &self.workspace_locations[selected_match.candidate_id];
-            workspace
-                .update(cx, |workspace, cx| {
-                    workspace
-                        .open_workspace_for_paths(workspace_location.paths().as_ref().clone(), cx)
-                })
-                .detach_and_log_err(cx);
-            cx.emit(DismissEvent);
-        }
-    }
-
-    fn dismissed(&mut self, _: &mut ViewContext<Picker<Self>>) {}
-
-    fn render_match(
-        &self,
-        ix: usize,
-        selected: bool,
-        _cx: &mut ViewContext<Picker<Self>>,
-    ) -> Option<Self::ListItem> {
-        let Some(r#match) = self.matches.get(ix) else {
-            return None;
-        };
-
-        let highlighted_location = HighlightedWorkspaceLocation::new(
-            &r#match,
-            &self.workspace_locations[r#match.candidate_id],
-        );
-
-        Some(
-            ListItem::new(ix)
-                .inset(true)
-                .spacing(ListItemSpacing::Sparse)
-                .selected(selected)
-                .child(
-                    v_stack()
-                        .child(highlighted_location.names)
-                        .when(self.render_paths, |this| {
-                            this.children(highlighted_location.paths)
-                        }),
-                ),
-        )
-    }
-}

crates/rich_text/Cargo.toml 🔗

@@ -14,7 +14,6 @@ test-support = [
     "util/test-support",
 ]
 
-
 [dependencies]
 collections = { path = "../collections" }
 gpui = { path = "../gpui" }

crates/rich_text/src/rich_text.rs 🔗

@@ -1,19 +1,16 @@
-use std::{ops::Range, sync::Arc};
-
-use anyhow::bail;
 use futures::FutureExt;
 use gpui::{
-    elements::Text,
-    fonts::{HighlightStyle, Underline, Weight},
-    platform::{CursorStyle, MouseButton},
-    AnyElement, CursorRegion, Element, MouseRegion, ViewContext,
+    AnyElement, ElementId, FontStyle, FontWeight, HighlightStyle, InteractiveText, IntoElement,
+    SharedString, StyledText, UnderlineStyle, WindowContext,
 };
 use language::{HighlightId, Language, LanguageRegistry};
-use theme::{RichTextStyle, SyntaxTheme};
+use std::{ops::Range, sync::Arc};
+use theme::ActiveTheme;
 use util::RangeExt;
 
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub enum Highlight {
+    Code,
     Id(HighlightId),
     Highlight(HighlightStyle),
     Mention,
@@ -34,24 +31,10 @@ impl From<HighlightId> for Highlight {
 
 #[derive(Debug, Clone)]
 pub struct RichText {
-    pub text: String,
+    pub text: SharedString,
     pub highlights: Vec<(Range<usize>, Highlight)>,
-    pub region_ranges: Vec<Range<usize>>,
-    pub regions: Vec<RenderedRegion>,
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub enum BackgroundKind {
-    Code,
-    /// A mention background for non-self user.
-    Mention,
-    SelfMention,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct RenderedRegion {
-    pub background_kind: Option<BackgroundKind>,
-    pub link_url: Option<String>,
+    pub link_ranges: Vec<Range<usize>>,
+    pub link_urls: Arc<[String]>,
 }
 
 /// Allows one to specify extra links to the rendered markdown, which can be used
@@ -62,92 +45,71 @@ pub struct Mention {
 }
 
 impl RichText {
-    pub fn element<V: 'static>(
-        &self,
-        syntax: Arc<SyntaxTheme>,
-        style: RichTextStyle,
-        cx: &mut ViewContext<V>,
-    ) -> AnyElement<V> {
-        let mut region_id = 0;
-        let view_id = cx.view_id();
-
-        let regions = self.regions.clone();
+    pub fn element(&self, id: ElementId, cx: &mut WindowContext) -> AnyElement {
+        let theme = cx.theme();
+        let code_background = theme.colors().surface_background;
 
-        enum Markdown {}
-        Text::new(self.text.clone(), style.text.clone())
-            .with_highlights(
-                self.highlights
-                    .iter()
-                    .filter_map(|(range, highlight)| {
-                        let style = match highlight {
-                            Highlight::Id(id) => id.style(&syntax)?,
-                            Highlight::Highlight(style) => style.clone(),
-                            Highlight::Mention => style.mention_highlight,
-                            Highlight::SelfMention => style.self_mention_highlight,
-                        };
-                        Some((range.clone(), style))
-                    })
-                    .collect::<Vec<_>>(),
-            )
-            .with_custom_runs(self.region_ranges.clone(), move |ix, bounds, cx| {
-                region_id += 1;
-                let region = regions[ix].clone();
-                if let Some(url) = region.link_url {
-                    cx.scene().push_cursor_region(CursorRegion {
-                        bounds,
-                        style: CursorStyle::PointingHand,
-                    });
-                    cx.scene().push_mouse_region(
-                        MouseRegion::new::<Markdown>(view_id, region_id, bounds)
-                            .on_click::<V, _>(MouseButton::Left, move |_, _, cx| {
-                                cx.platform().open_url(&url)
-                            }),
-                    );
-                }
-                if let Some(region_kind) = &region.background_kind {
-                    let background = match region_kind {
-                        BackgroundKind::Code => style.code_background,
-                        BackgroundKind::Mention => style.mention_background,
-                        BackgroundKind::SelfMention => style.self_mention_background,
-                    };
-                    if background.is_some() {
-                        cx.scene().push_quad(gpui::Quad {
-                            bounds,
-                            background,
-                            border: Default::default(),
-                            corner_radii: (2.0).into(),
-                        });
-                    }
-                }
-            })
-            .with_soft_wrap(true)
-            .into_any()
+        InteractiveText::new(
+            id,
+            StyledText::new(self.text.clone()).with_highlights(
+                &cx.text_style(),
+                self.highlights.iter().map(|(range, highlight)| {
+                    (
+                        range.clone(),
+                        match highlight {
+                            Highlight::Code => HighlightStyle {
+                                background_color: Some(code_background),
+                                ..Default::default()
+                            },
+                            Highlight::Id(id) => HighlightStyle {
+                                background_color: Some(code_background),
+                                ..id.style(&theme.syntax()).unwrap_or_default()
+                            },
+                            Highlight::Highlight(highlight) => *highlight,
+                            Highlight::Mention => HighlightStyle {
+                                font_weight: Some(FontWeight::BOLD),
+                                ..Default::default()
+                            },
+                            Highlight::SelfMention => HighlightStyle {
+                                font_weight: Some(FontWeight::BOLD),
+                                ..Default::default()
+                            },
+                        },
+                    )
+                }),
+            ),
+        )
+        .on_click(self.link_ranges.clone(), {
+            let link_urls = self.link_urls.clone();
+            move |ix, cx| cx.open_url(&link_urls[ix])
+        })
+        .into_any_element()
     }
 
-    pub fn add_mention(
-        &mut self,
-        range: Range<usize>,
-        is_current_user: bool,
-        mention_style: HighlightStyle,
-    ) -> anyhow::Result<()> {
-        if range.end > self.text.len() {
-            bail!(
-                "Mention in range {range:?} is outside of bounds for a message of length {}",
-                self.text.len()
-            );
-        }
+    // pub fn add_mention(
+    //     &mut self,
+    //     range: Range<usize>,
+    //     is_current_user: bool,
+    //     mention_style: HighlightStyle,
+    // ) -> anyhow::Result<()> {
+    //     if range.end > self.text.len() {
+    //         bail!(
+    //             "Mention in range {range:?} is outside of bounds for a message of length {}",
+    //             self.text.len()
+    //         );
+    //     }
 
-        if is_current_user {
-            self.region_ranges.push(range.clone());
-            self.regions.push(RenderedRegion {
-                background_kind: Some(BackgroundKind::Mention),
-                link_url: None,
-            });
-        }
-        self.highlights
-            .push((range, Highlight::Highlight(mention_style)));
-        Ok(())
-    }
+    //     if is_current_user {
+    //         self.region_ranges.push(range.clone());
+    //         self.regions.push(RenderedRegion {
+    //             background_kind: Some(BackgroundKind::Mention),
+    //             link_url: None,
+    //         });
+    //     }
+    //     self.highlights
+    //         .push((range, Highlight::Highlight(mention_style)));
+    //     Ok(())
+    // }
 }
 
 pub fn render_markdown_mut(
@@ -155,7 +117,10 @@ pub fn render_markdown_mut(
     mut mentions: &[Mention],
     language_registry: &Arc<LanguageRegistry>,
     language: Option<&Arc<Language>>,
-    data: &mut RichText,
+    text: &mut String,
+    highlights: &mut Vec<(Range<usize>, Highlight)>,
+    link_ranges: &mut Vec<Range<usize>>,
+    link_urls: &mut Vec<String>,
 ) {
     use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};
 
@@ -167,18 +132,18 @@ pub fn render_markdown_mut(
 
     let options = Options::all();
     for (event, source_range) in Parser::new_ext(&block, options).into_offset_iter() {
-        let prev_len = data.text.len();
+        let prev_len = text.len();
         match event {
             Event::Text(t) => {
                 if let Some(language) = &current_language {
-                    render_code(&mut data.text, &mut data.highlights, t.as_ref(), language);
+                    render_code(text, highlights, t.as_ref(), language);
                 } else {
                     if let Some(mention) = mentions.first() {
                         if source_range.contains_inclusive(&mention.range) {
                             mentions = &mentions[1..];
                             let range = (prev_len + mention.range.start - source_range.start)
                                 ..(prev_len + mention.range.end - source_range.start);
-                            data.highlights.push((
+                            highlights.push((
                                 range.clone(),
                                 if mention.is_self_mention {
                                     Highlight::SelfMention
@@ -186,33 +151,21 @@ pub fn render_markdown_mut(
                                     Highlight::Mention
                                 },
                             ));
-                            data.region_ranges.push(range);
-                            data.regions.push(RenderedRegion {
-                                background_kind: Some(if mention.is_self_mention {
-                                    BackgroundKind::SelfMention
-                                } else {
-                                    BackgroundKind::Mention
-                                }),
-                                link_url: None,
-                            });
                         }
                     }
 
-                    data.text.push_str(t.as_ref());
+                    text.push_str(t.as_ref());
                     let mut style = HighlightStyle::default();
                     if bold_depth > 0 {
-                        style.weight = Some(Weight::BOLD);
+                        style.font_weight = Some(FontWeight::BOLD);
                     }
                     if italic_depth > 0 {
-                        style.italic = Some(true);
+                        style.font_style = Some(FontStyle::Italic);
                     }
                     if let Some(link_url) = link_url.clone() {
-                        data.region_ranges.push(prev_len..data.text.len());
-                        data.regions.push(RenderedRegion {
-                            link_url: Some(link_url),
-                            background_kind: None,
-                        });
-                        style.underline = Some(Underline {
+                        link_ranges.push(prev_len..text.len());
+                        link_urls.push(link_url);
+                        style.underline = Some(UnderlineStyle {
                             thickness: 1.0.into(),
                             ..Default::default()
                         });
@@ -220,29 +173,27 @@ pub fn render_markdown_mut(
 
                     if style != HighlightStyle::default() {
                         let mut new_highlight = true;
-                        if let Some((last_range, last_style)) = data.highlights.last_mut() {
+                        if let Some((last_range, last_style)) = highlights.last_mut() {
                             if last_range.end == prev_len
                                 && last_style == &Highlight::Highlight(style)
                             {
-                                last_range.end = data.text.len();
+                                last_range.end = text.len();
                                 new_highlight = false;
                             }
                         }
                         if new_highlight {
-                            data.highlights
-                                .push((prev_len..data.text.len(), Highlight::Highlight(style)));
+                            highlights.push((prev_len..text.len(), Highlight::Highlight(style)));
                         }
                     }
                 }
             }
             Event::Code(t) => {
-                data.text.push_str(t.as_ref());
-                data.region_ranges.push(prev_len..data.text.len());
+                text.push_str(t.as_ref());
                 if link_url.is_some() {
-                    data.highlights.push((
-                        prev_len..data.text.len(),
+                    highlights.push((
+                        prev_len..text.len(),
                         Highlight::Highlight(HighlightStyle {
-                            underline: Some(Underline {
+                            underline: Some(UnderlineStyle {
                                 thickness: 1.0.into(),
                                 ..Default::default()
                             }),
@@ -250,19 +201,19 @@ pub fn render_markdown_mut(
                         }),
                     ));
                 }
-                data.regions.push(RenderedRegion {
-                    background_kind: Some(BackgroundKind::Code),
-                    link_url: link_url.clone(),
-                });
+                if let Some(link_url) = link_url.clone() {
+                    link_ranges.push(prev_len..text.len());
+                    link_urls.push(link_url);
+                }
             }
             Event::Start(tag) => match tag {
-                Tag::Paragraph => new_paragraph(&mut data.text, &mut list_stack),
+                Tag::Paragraph => new_paragraph(text, &mut list_stack),
                 Tag::Heading(_, _, _) => {
-                    new_paragraph(&mut data.text, &mut list_stack);
+                    new_paragraph(text, &mut list_stack);
                     bold_depth += 1;
                 }
                 Tag::CodeBlock(kind) => {
-                    new_paragraph(&mut data.text, &mut list_stack);
+                    new_paragraph(text, &mut list_stack);
                     current_language = if let CodeBlockKind::Fenced(language) = kind {
                         language_registry
                             .language_for_name(language.as_ref())
@@ -282,18 +233,18 @@ pub fn render_markdown_mut(
                     let len = list_stack.len();
                     if let Some((list_number, has_content)) = list_stack.last_mut() {
                         *has_content = false;
-                        if !data.text.is_empty() && !data.text.ends_with('\n') {
-                            data.text.push('\n');
+                        if !text.is_empty() && !text.ends_with('\n') {
+                            text.push('\n');
                         }
                         for _ in 0..len - 1 {
-                            data.text.push_str("  ");
+                            text.push_str("  ");
                         }
                         if let Some(number) = list_number {
-                            data.text.push_str(&format!("{}. ", number));
+                            text.push_str(&format!("{}. ", number));
                             *number += 1;
                             *has_content = false;
                         } else {
-                            data.text.push_str("- ");
+                            text.push_str("- ");
                         }
                     }
                 }
@@ -308,8 +259,8 @@ pub fn render_markdown_mut(
                 Tag::List(_) => drop(list_stack.pop()),
                 _ => {}
             },
-            Event::HardBreak => data.text.push('\n'),
-            Event::SoftBreak => data.text.push(' '),
+            Event::HardBreak => text.push('\n'),
+            Event::SoftBreak => text.push(' '),
             _ => {}
         }
     }
@@ -321,18 +272,35 @@ pub fn render_markdown(
     language_registry: &Arc<LanguageRegistry>,
     language: Option<&Arc<Language>>,
 ) -> RichText {
-    let mut data = RichText {
-        text: Default::default(),
-        highlights: Default::default(),
-        region_ranges: Default::default(),
-        regions: Default::default(),
-    };
+    // let mut data = RichText {
+    //     text: Default::default(),
+    //     highlights: Default::default(),
+    //     region_ranges: Default::default(),
+    //     regions: Default::default(),
+    // };
 
-    render_markdown_mut(&block, mentions, language_registry, language, &mut data);
+    let mut text = String::new();
+    let mut highlights = Vec::new();
+    let mut link_ranges = Vec::new();
+    let mut link_urls = Vec::new();
+    render_markdown_mut(
+        &block,
+        mentions,
+        language_registry,
+        language,
+        &mut text,
+        &mut highlights,
+        &mut link_ranges,
+        &mut link_urls,
+    );
+    text.truncate(text.trim_end().len());
 
-    data.text = data.text.trim().to_string();
-
-    data
+    RichText {
+        text: SharedString::from(text),
+        link_urls: link_urls.into(),
+        link_ranges,
+        highlights,
+    }
 }
 
 pub fn render_code(
@@ -343,11 +311,19 @@ pub fn render_code(
 ) {
     let prev_len = text.len();
     text.push_str(content);
+    let mut offset = 0;
     for (range, highlight_id) in language.highlight_text(&content.into(), 0..content.len()) {
+        if range.start > offset {
+            highlights.push((prev_len + offset..prev_len + range.start, Highlight::Code));
+        }
         highlights.push((
             prev_len + range.start..prev_len + range.end,
             Highlight::Id(highlight_id),
         ));
+        offset = range.end;
+    }
+    if offset < content.len() {
+        highlights.push((prev_len + offset..prev_len + content.len(), Highlight::Code));
     }
 }
 

crates/rich_text2/Cargo.toml 🔗

@@ -1,29 +0,0 @@
-[package]
-name = "rich_text2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/rich_text.rs"
-doctest = false
-
-[features]
-test-support = [
-    "gpui/test-support",
-    "util/test-support",
-]
-
-[dependencies]
-collections = { path = "../collections" }
-gpui = { package = "gpui2", path = "../gpui2" }
-sum_tree = { path = "../sum_tree" }
-theme = { package = "theme2", path = "../theme2" }
-language = { package = "language2", path = "../language2" }
-util = { path = "../util" }
-anyhow.workspace = true
-futures.workspace = true
-lazy_static.workspace = true
-pulldown-cmark = { version = "0.9.2", default-features = false }
-smallvec.workspace = true
-smol.workspace = true

crates/rich_text2/src/rich_text.rs 🔗

@@ -1,353 +0,0 @@
-use futures::FutureExt;
-use gpui::{
-    AnyElement, ElementId, FontStyle, FontWeight, HighlightStyle, InteractiveText, IntoElement,
-    SharedString, StyledText, UnderlineStyle, WindowContext,
-};
-use language::{HighlightId, Language, LanguageRegistry};
-use std::{ops::Range, sync::Arc};
-use theme::ActiveTheme;
-use util::RangeExt;
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum Highlight {
-    Code,
-    Id(HighlightId),
-    Highlight(HighlightStyle),
-    Mention,
-    SelfMention,
-}
-
-impl From<HighlightStyle> for Highlight {
-    fn from(style: HighlightStyle) -> Self {
-        Self::Highlight(style)
-    }
-}
-
-impl From<HighlightId> for Highlight {
-    fn from(style: HighlightId) -> Self {
-        Self::Id(style)
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct RichText {
-    pub text: SharedString,
-    pub highlights: Vec<(Range<usize>, Highlight)>,
-    pub link_ranges: Vec<Range<usize>>,
-    pub link_urls: Arc<[String]>,
-}
-
-/// Allows one to specify extra links to the rendered markdown, which can be used
-/// for e.g. mentions.
-pub struct Mention {
-    pub range: Range<usize>,
-    pub is_self_mention: bool,
-}
-
-impl RichText {
-    pub fn element(&self, id: ElementId, cx: &mut WindowContext) -> AnyElement {
-        let theme = cx.theme();
-        let code_background = theme.colors().surface_background;
-
-        InteractiveText::new(
-            id,
-            StyledText::new(self.text.clone()).with_highlights(
-                &cx.text_style(),
-                self.highlights.iter().map(|(range, highlight)| {
-                    (
-                        range.clone(),
-                        match highlight {
-                            Highlight::Code => HighlightStyle {
-                                background_color: Some(code_background),
-                                ..Default::default()
-                            },
-                            Highlight::Id(id) => HighlightStyle {
-                                background_color: Some(code_background),
-                                ..id.style(&theme.syntax()).unwrap_or_default()
-                            },
-                            Highlight::Highlight(highlight) => *highlight,
-                            Highlight::Mention => HighlightStyle {
-                                font_weight: Some(FontWeight::BOLD),
-                                ..Default::default()
-                            },
-                            Highlight::SelfMention => HighlightStyle {
-                                font_weight: Some(FontWeight::BOLD),
-                                ..Default::default()
-                            },
-                        },
-                    )
-                }),
-            ),
-        )
-        .on_click(self.link_ranges.clone(), {
-            let link_urls = self.link_urls.clone();
-            move |ix, cx| cx.open_url(&link_urls[ix])
-        })
-        .into_any_element()
-    }
-
-    // pub fn add_mention(
-    //     &mut self,
-    //     range: Range<usize>,
-    //     is_current_user: bool,
-    //     mention_style: HighlightStyle,
-    // ) -> anyhow::Result<()> {
-    //     if range.end > self.text.len() {
-    //         bail!(
-    //             "Mention in range {range:?} is outside of bounds for a message of length {}",
-    //             self.text.len()
-    //         );
-    //     }
-
-    //     if is_current_user {
-    //         self.region_ranges.push(range.clone());
-    //         self.regions.push(RenderedRegion {
-    //             background_kind: Some(BackgroundKind::Mention),
-    //             link_url: None,
-    //         });
-    //     }
-    //     self.highlights
-    //         .push((range, Highlight::Highlight(mention_style)));
-    //     Ok(())
-    // }
-}
-
-pub fn render_markdown_mut(
-    block: &str,
-    mut mentions: &[Mention],
-    language_registry: &Arc<LanguageRegistry>,
-    language: Option<&Arc<Language>>,
-    text: &mut String,
-    highlights: &mut Vec<(Range<usize>, Highlight)>,
-    link_ranges: &mut Vec<Range<usize>>,
-    link_urls: &mut Vec<String>,
-) {
-    use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};
-
-    let mut bold_depth = 0;
-    let mut italic_depth = 0;
-    let mut link_url = None;
-    let mut current_language = None;
-    let mut list_stack = Vec::new();
-
-    let options = Options::all();
-    for (event, source_range) in Parser::new_ext(&block, options).into_offset_iter() {
-        let prev_len = text.len();
-        match event {
-            Event::Text(t) => {
-                if let Some(language) = &current_language {
-                    render_code(text, highlights, t.as_ref(), language);
-                } else {
-                    if let Some(mention) = mentions.first() {
-                        if source_range.contains_inclusive(&mention.range) {
-                            mentions = &mentions[1..];
-                            let range = (prev_len + mention.range.start - source_range.start)
-                                ..(prev_len + mention.range.end - source_range.start);
-                            highlights.push((
-                                range.clone(),
-                                if mention.is_self_mention {
-                                    Highlight::SelfMention
-                                } else {
-                                    Highlight::Mention
-                                },
-                            ));
-                        }
-                    }
-
-                    text.push_str(t.as_ref());
-                    let mut style = HighlightStyle::default();
-                    if bold_depth > 0 {
-                        style.font_weight = Some(FontWeight::BOLD);
-                    }
-                    if italic_depth > 0 {
-                        style.font_style = Some(FontStyle::Italic);
-                    }
-                    if let Some(link_url) = link_url.clone() {
-                        link_ranges.push(prev_len..text.len());
-                        link_urls.push(link_url);
-                        style.underline = Some(UnderlineStyle {
-                            thickness: 1.0.into(),
-                            ..Default::default()
-                        });
-                    }
-
-                    if style != HighlightStyle::default() {
-                        let mut new_highlight = true;
-                        if let Some((last_range, last_style)) = highlights.last_mut() {
-                            if last_range.end == prev_len
-                                && last_style == &Highlight::Highlight(style)
-                            {
-                                last_range.end = text.len();
-                                new_highlight = false;
-                            }
-                        }
-                        if new_highlight {
-                            highlights.push((prev_len..text.len(), Highlight::Highlight(style)));
-                        }
-                    }
-                }
-            }
-            Event::Code(t) => {
-                text.push_str(t.as_ref());
-                if link_url.is_some() {
-                    highlights.push((
-                        prev_len..text.len(),
-                        Highlight::Highlight(HighlightStyle {
-                            underline: Some(UnderlineStyle {
-                                thickness: 1.0.into(),
-                                ..Default::default()
-                            }),
-                            ..Default::default()
-                        }),
-                    ));
-                }
-                if let Some(link_url) = link_url.clone() {
-                    link_ranges.push(prev_len..text.len());
-                    link_urls.push(link_url);
-                }
-            }
-            Event::Start(tag) => match tag {
-                Tag::Paragraph => new_paragraph(text, &mut list_stack),
-                Tag::Heading(_, _, _) => {
-                    new_paragraph(text, &mut list_stack);
-                    bold_depth += 1;
-                }
-                Tag::CodeBlock(kind) => {
-                    new_paragraph(text, &mut list_stack);
-                    current_language = if let CodeBlockKind::Fenced(language) = kind {
-                        language_registry
-                            .language_for_name(language.as_ref())
-                            .now_or_never()
-                            .and_then(Result::ok)
-                    } else {
-                        language.cloned()
-                    }
-                }
-                Tag::Emphasis => italic_depth += 1,
-                Tag::Strong => bold_depth += 1,
-                Tag::Link(_, url, _) => link_url = Some(url.to_string()),
-                Tag::List(number) => {
-                    list_stack.push((number, false));
-                }
-                Tag::Item => {
-                    let len = list_stack.len();
-                    if let Some((list_number, has_content)) = list_stack.last_mut() {
-                        *has_content = false;
-                        if !text.is_empty() && !text.ends_with('\n') {
-                            text.push('\n');
-                        }
-                        for _ in 0..len - 1 {
-                            text.push_str("  ");
-                        }
-                        if let Some(number) = list_number {
-                            text.push_str(&format!("{}. ", number));
-                            *number += 1;
-                            *has_content = false;
-                        } else {
-                            text.push_str("- ");
-                        }
-                    }
-                }
-                _ => {}
-            },
-            Event::End(tag) => match tag {
-                Tag::Heading(_, _, _) => bold_depth -= 1,
-                Tag::CodeBlock(_) => current_language = None,
-                Tag::Emphasis => italic_depth -= 1,
-                Tag::Strong => bold_depth -= 1,
-                Tag::Link(_, _, _) => link_url = None,
-                Tag::List(_) => drop(list_stack.pop()),
-                _ => {}
-            },
-            Event::HardBreak => text.push('\n'),
-            Event::SoftBreak => text.push(' '),
-            _ => {}
-        }
-    }
-}
-
-pub fn render_markdown(
-    block: String,
-    mentions: &[Mention],
-    language_registry: &Arc<LanguageRegistry>,
-    language: Option<&Arc<Language>>,
-) -> RichText {
-    // let mut data = RichText {
-    //     text: Default::default(),
-    //     highlights: Default::default(),
-    //     region_ranges: Default::default(),
-    //     regions: Default::default(),
-    // };
-
-    let mut text = String::new();
-    let mut highlights = Vec::new();
-    let mut link_ranges = Vec::new();
-    let mut link_urls = Vec::new();
-    render_markdown_mut(
-        &block,
-        mentions,
-        language_registry,
-        language,
-        &mut text,
-        &mut highlights,
-        &mut link_ranges,
-        &mut link_urls,
-    );
-    text.truncate(text.trim_end().len());
-
-    RichText {
-        text: SharedString::from(text),
-        link_urls: link_urls.into(),
-        link_ranges,
-        highlights,
-    }
-}
-
-pub fn render_code(
-    text: &mut String,
-    highlights: &mut Vec<(Range<usize>, Highlight)>,
-    content: &str,
-    language: &Arc<Language>,
-) {
-    let prev_len = text.len();
-    text.push_str(content);
-    let mut offset = 0;
-    for (range, highlight_id) in language.highlight_text(&content.into(), 0..content.len()) {
-        if range.start > offset {
-            highlights.push((prev_len + offset..prev_len + range.start, Highlight::Code));
-        }
-        highlights.push((
-            prev_len + range.start..prev_len + range.end,
-            Highlight::Id(highlight_id),
-        ));
-        offset = range.end;
-    }
-    if offset < content.len() {
-        highlights.push((prev_len + offset..prev_len + content.len(), Highlight::Code));
-    }
-}
-
-pub fn new_paragraph(text: &mut String, list_stack: &mut Vec<(Option<u64>, bool)>) {
-    let mut is_subsequent_paragraph_of_list = false;
-    if let Some((_, has_content)) = list_stack.last_mut() {
-        if *has_content {
-            is_subsequent_paragraph_of_list = true;
-        } else {
-            *has_content = true;
-            return;
-        }
-    }
-
-    if !text.is_empty() {
-        if !text.ends_with('\n') {
-            text.push('\n');
-        }
-        text.push('\n');
-    }
-    for _ in 0..list_stack.len().saturating_sub(1) {
-        text.push_str("  ");
-    }
-    if is_subsequent_paragraph_of_list {
-        text.push_str("  ");
-    }
-}

crates/rope/src/rope.rs 🔗

@@ -906,7 +906,7 @@ impl Chunk {
 
     fn clip_offset_utf16(&self, target: OffsetUtf16, bias: Bias) -> OffsetUtf16 {
         let mut code_units = self.0.encode_utf16();
-        let mut offset = code_units.by_ref().take(target.0 as usize).count();
+        let mut offset = code_units.by_ref().take(target.0).count();
         if char::decode_utf16(code_units).next().transpose().is_err() {
             match bias {
                 Bias::Left => offset -= 1,

crates/rope2/Cargo.toml 🔗

@@ -1,21 +0,0 @@
-[package]
-name = "rope2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/rope2.rs"
-
-[dependencies]
-bromberg_sl2 = { git = "https://github.com/zed-industries/bromberg_sl2", rev = "950bc5482c216c395049ae33ae4501e08975f17f" }
-smallvec.workspace = true
-sum_tree = { path = "../sum_tree" }
-arrayvec = "0.7.1"
-log.workspace = true
-util = { path = "../util" }
-
-[dev-dependencies]
-rand.workspace = true
-util = { path = "../util", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"]  }

crates/rope2/src/offset_utf16.rs 🔗

@@ -1,50 +0,0 @@
-use std::ops::{Add, AddAssign, Sub};
-
-#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
-pub struct OffsetUtf16(pub usize);
-
-impl<'a> Add<&'a Self> for OffsetUtf16 {
-    type Output = Self;
-
-    fn add(self, other: &'a Self) -> Self::Output {
-        Self(self.0 + other.0)
-    }
-}
-
-impl Add for OffsetUtf16 {
-    type Output = Self;
-
-    fn add(self, other: Self) -> Self::Output {
-        Self(self.0 + other.0)
-    }
-}
-
-impl<'a> Sub<&'a Self> for OffsetUtf16 {
-    type Output = Self;
-
-    fn sub(self, other: &'a Self) -> Self::Output {
-        debug_assert!(*other <= self);
-        Self(self.0 - other.0)
-    }
-}
-
-impl Sub for OffsetUtf16 {
-    type Output = OffsetUtf16;
-
-    fn sub(self, other: Self) -> Self::Output {
-        debug_assert!(other <= self);
-        Self(self.0 - other.0)
-    }
-}
-
-impl<'a> AddAssign<&'a Self> for OffsetUtf16 {
-    fn add_assign(&mut self, other: &'a Self) {
-        self.0 += other.0;
-    }
-}
-
-impl AddAssign<Self> for OffsetUtf16 {
-    fn add_assign(&mut self, other: Self) {
-        self.0 += other.0;
-    }
-}

crates/rope2/src/point.rs 🔗

@@ -1,128 +0,0 @@
-use std::{
-    cmp::Ordering,
-    ops::{Add, AddAssign, Sub},
-};
-
-#[derive(Clone, Copy, Default, Eq, PartialEq, Debug, Hash)]
-pub struct Point {
-    pub row: u32,
-    pub column: u32,
-}
-
-impl Point {
-    pub const MAX: Self = Self {
-        row: u32::MAX,
-        column: u32::MAX,
-    };
-
-    pub fn new(row: u32, column: u32) -> Self {
-        Point { row, column }
-    }
-
-    pub fn zero() -> Self {
-        Point::new(0, 0)
-    }
-
-    pub fn parse_str(s: &str) -> Self {
-        let mut point = Self::zero();
-        for (row, line) in s.split('\n').enumerate() {
-            point.row = row as u32;
-            point.column = line.len() as u32;
-        }
-        point
-    }
-
-    pub fn is_zero(&self) -> bool {
-        self.row == 0 && self.column == 0
-    }
-
-    pub fn saturating_sub(self, other: Self) -> Self {
-        if self < other {
-            Self::zero()
-        } else {
-            self - other
-        }
-    }
-}
-
-impl<'a> Add<&'a Self> for Point {
-    type Output = Point;
-
-    fn add(self, other: &'a Self) -> Self::Output {
-        self + *other
-    }
-}
-
-impl Add for Point {
-    type Output = Point;
-
-    fn add(self, other: Self) -> Self::Output {
-        if other.row == 0 {
-            Point::new(self.row, self.column + other.column)
-        } else {
-            Point::new(self.row + other.row, other.column)
-        }
-    }
-}
-
-impl<'a> Sub<&'a Self> for Point {
-    type Output = Point;
-
-    fn sub(self, other: &'a Self) -> Self::Output {
-        self - *other
-    }
-}
-
-impl Sub for Point {
-    type Output = Point;
-
-    fn sub(self, other: Self) -> Self::Output {
-        debug_assert!(other <= self);
-
-        if self.row == other.row {
-            Point::new(0, self.column - other.column)
-        } else {
-            Point::new(self.row - other.row, self.column)
-        }
-    }
-}
-
-impl<'a> AddAssign<&'a Self> for Point {
-    fn add_assign(&mut self, other: &'a Self) {
-        *self += *other;
-    }
-}
-
-impl AddAssign<Self> for Point {
-    fn add_assign(&mut self, other: Self) {
-        if other.row == 0 {
-            self.column += other.column;
-        } else {
-            self.row += other.row;
-            self.column = other.column;
-        }
-    }
-}
-
-impl PartialOrd for Point {
-    fn partial_cmp(&self, other: &Point) -> Option<Ordering> {
-        Some(self.cmp(other))
-    }
-}
-
-impl Ord for Point {
-    #[cfg(target_pointer_width = "64")]
-    fn cmp(&self, other: &Point) -> Ordering {
-        let a = (self.row as usize) << 32 | self.column as usize;
-        let b = (other.row as usize) << 32 | other.column as usize;
-        a.cmp(&b)
-    }
-
-    #[cfg(target_pointer_width = "32")]
-    fn cmp(&self, other: &Point) -> Ordering {
-        match self.row.cmp(&other.row) {
-            Ordering::Equal => self.column.cmp(&other.column),
-            comparison @ _ => comparison,
-        }
-    }
-}

crates/rope2/src/point_utf16.rs 🔗

@@ -1,119 +0,0 @@
-use std::{
-    cmp::Ordering,
-    ops::{Add, AddAssign, Sub},
-};
-
-#[derive(Clone, Copy, Default, Eq, PartialEq, Debug, Hash)]
-pub struct PointUtf16 {
-    pub row: u32,
-    pub column: u32,
-}
-
-impl PointUtf16 {
-    pub const MAX: Self = Self {
-        row: u32::MAX,
-        column: u32::MAX,
-    };
-
-    pub fn new(row: u32, column: u32) -> Self {
-        PointUtf16 { row, column }
-    }
-
-    pub fn zero() -> Self {
-        PointUtf16::new(0, 0)
-    }
-
-    pub fn is_zero(&self) -> bool {
-        self.row == 0 && self.column == 0
-    }
-
-    pub fn saturating_sub(self, other: Self) -> Self {
-        if self < other {
-            Self::zero()
-        } else {
-            self - other
-        }
-    }
-}
-
-impl<'a> Add<&'a Self> for PointUtf16 {
-    type Output = PointUtf16;
-
-    fn add(self, other: &'a Self) -> Self::Output {
-        self + *other
-    }
-}
-
-impl Add for PointUtf16 {
-    type Output = PointUtf16;
-
-    fn add(self, other: Self) -> Self::Output {
-        if other.row == 0 {
-            PointUtf16::new(self.row, self.column + other.column)
-        } else {
-            PointUtf16::new(self.row + other.row, other.column)
-        }
-    }
-}
-
-impl<'a> Sub<&'a Self> for PointUtf16 {
-    type Output = PointUtf16;
-
-    fn sub(self, other: &'a Self) -> Self::Output {
-        self - *other
-    }
-}
-
-impl Sub for PointUtf16 {
-    type Output = PointUtf16;
-
-    fn sub(self, other: Self) -> Self::Output {
-        debug_assert!(other <= self);
-
-        if self.row == other.row {
-            PointUtf16::new(0, self.column - other.column)
-        } else {
-            PointUtf16::new(self.row - other.row, self.column)
-        }
-    }
-}
-
-impl<'a> AddAssign<&'a Self> for PointUtf16 {
-    fn add_assign(&mut self, other: &'a Self) {
-        *self += *other;
-    }
-}
-
-impl AddAssign<Self> for PointUtf16 {
-    fn add_assign(&mut self, other: Self) {
-        if other.row == 0 {
-            self.column += other.column;
-        } else {
-            self.row += other.row;
-            self.column = other.column;
-        }
-    }
-}
-
-impl PartialOrd for PointUtf16 {
-    fn partial_cmp(&self, other: &PointUtf16) -> Option<Ordering> {
-        Some(self.cmp(other))
-    }
-}
-
-impl Ord for PointUtf16 {
-    #[cfg(target_pointer_width = "64")]
-    fn cmp(&self, other: &PointUtf16) -> Ordering {
-        let a = (self.row as usize) << 32 | self.column as usize;
-        let b = (other.row as usize) << 32 | other.column as usize;
-        a.cmp(&b)
-    }
-
-    #[cfg(target_pointer_width = "32")]
-    fn cmp(&self, other: &PointUtf16) -> Ordering {
-        match self.row.cmp(&other.row) {
-            Ordering::Equal => self.column.cmp(&other.column),
-            comparison @ _ => comparison,
-        }
-    }
-}

crates/rope2/src/rope2.rs 🔗

@@ -1,1437 +0,0 @@
-mod offset_utf16;
-mod point;
-mod point_utf16;
-mod unclipped;
-
-use arrayvec::ArrayString;
-use bromberg_sl2::HashMatrix;
-use smallvec::SmallVec;
-use std::{
-    cmp, fmt, io, mem,
-    ops::{AddAssign, Range},
-    str,
-};
-use sum_tree::{Bias, Dimension, SumTree};
-use util::debug_panic;
-
-pub use offset_utf16::OffsetUtf16;
-pub use point::Point;
-pub use point_utf16::PointUtf16;
-pub use unclipped::Unclipped;
-
-#[cfg(test)]
-const CHUNK_BASE: usize = 6;
-
-#[cfg(not(test))]
-const CHUNK_BASE: usize = 16;
-
-/// Type alias to [HashMatrix], an implementation of a homomorphic hash function. Two [Rope] instances
-/// containing the same text will produce the same fingerprint. This hash function is special in that
-/// it allows us to hash individual chunks and aggregate them up the [Rope]'s tree, with the resulting
-/// hash being equivalent to hashing all the text contained in the [Rope] at once.
-pub type RopeFingerprint = HashMatrix;
-
-#[derive(Clone, Default)]
-pub struct Rope {
-    chunks: SumTree<Chunk>,
-}
-
-impl Rope {
-    pub fn new() -> Self {
-        Self::default()
-    }
-
-    pub fn text_fingerprint(text: &str) -> RopeFingerprint {
-        bromberg_sl2::hash_strict(text.as_bytes())
-    }
-
-    pub fn append(&mut self, rope: Rope) {
-        let mut chunks = rope.chunks.cursor::<()>();
-        chunks.next(&());
-        if let Some(chunk) = chunks.item() {
-            if self.chunks.last().map_or(false, |c| c.0.len() < CHUNK_BASE)
-                || chunk.0.len() < CHUNK_BASE
-            {
-                self.push(&chunk.0);
-                chunks.next(&());
-            }
-        }
-
-        self.chunks.append(chunks.suffix(&()), &());
-        self.check_invariants();
-    }
-
-    pub fn replace(&mut self, range: Range<usize>, text: &str) {
-        let mut new_rope = Rope::new();
-        let mut cursor = self.cursor(0);
-        new_rope.append(cursor.slice(range.start));
-        cursor.seek_forward(range.end);
-        new_rope.push(text);
-        new_rope.append(cursor.suffix());
-        *self = new_rope;
-    }
-
-    pub fn slice(&self, range: Range<usize>) -> Rope {
-        let mut cursor = self.cursor(0);
-        cursor.seek_forward(range.start);
-        cursor.slice(range.end)
-    }
-
-    pub fn slice_rows(&self, range: Range<u32>) -> Rope {
-        //This would be more efficient with a forward advance after the first, but it's fine
-        let start = self.point_to_offset(Point::new(range.start, 0));
-        let end = self.point_to_offset(Point::new(range.end, 0));
-        self.slice(start..end)
-    }
-
-    pub fn push(&mut self, text: &str) {
-        let mut new_chunks = SmallVec::<[_; 16]>::new();
-        let mut new_chunk = ArrayString::new();
-        for ch in text.chars() {
-            if new_chunk.len() + ch.len_utf8() > 2 * CHUNK_BASE {
-                new_chunks.push(Chunk(new_chunk));
-                new_chunk = ArrayString::new();
-            }
-
-            new_chunk.push(ch);
-        }
-        if !new_chunk.is_empty() {
-            new_chunks.push(Chunk(new_chunk));
-        }
-
-        let mut new_chunks = new_chunks.into_iter();
-        let mut first_new_chunk = new_chunks.next();
-        self.chunks.update_last(
-            |last_chunk| {
-                if let Some(first_new_chunk_ref) = first_new_chunk.as_mut() {
-                    if last_chunk.0.len() + first_new_chunk_ref.0.len() <= 2 * CHUNK_BASE {
-                        last_chunk.0.push_str(&first_new_chunk.take().unwrap().0);
-                    } else {
-                        let mut text = ArrayString::<{ 4 * CHUNK_BASE }>::new();
-                        text.push_str(&last_chunk.0);
-                        text.push_str(&first_new_chunk_ref.0);
-                        let (left, right) = text.split_at(find_split_ix(&text));
-                        last_chunk.0.clear();
-                        last_chunk.0.push_str(left);
-                        first_new_chunk_ref.0.clear();
-                        first_new_chunk_ref.0.push_str(right);
-                    }
-                }
-            },
-            &(),
-        );
-
-        self.chunks
-            .extend(first_new_chunk.into_iter().chain(new_chunks), &());
-        self.check_invariants();
-    }
-
-    pub fn push_front(&mut self, text: &str) {
-        let suffix = mem::replace(self, Rope::from(text));
-        self.append(suffix);
-    }
-
-    fn check_invariants(&self) {
-        #[cfg(test)]
-        {
-            // Ensure all chunks except maybe the last one are not underflowing.
-            // Allow some wiggle room for multibyte characters at chunk boundaries.
-            let mut chunks = self.chunks.cursor::<()>().peekable();
-            while let Some(chunk) = chunks.next() {
-                if chunks.peek().is_some() {
-                    assert!(chunk.0.len() + 3 >= CHUNK_BASE);
-                }
-            }
-        }
-    }
-
-    pub fn summary(&self) -> TextSummary {
-        self.chunks.summary().text.clone()
-    }
-
-    pub fn len(&self) -> usize {
-        self.chunks.extent(&())
-    }
-
-    pub fn is_empty(&self) -> bool {
-        self.len() == 0
-    }
-
-    pub fn max_point(&self) -> Point {
-        self.chunks.extent(&())
-    }
-
-    pub fn max_point_utf16(&self) -> PointUtf16 {
-        self.chunks.extent(&())
-    }
-
-    pub fn cursor(&self, offset: usize) -> Cursor {
-        Cursor::new(self, offset)
-    }
-
-    pub fn chars(&self) -> impl Iterator<Item = char> + '_ {
-        self.chars_at(0)
-    }
-
-    pub fn chars_at(&self, start: usize) -> impl Iterator<Item = char> + '_ {
-        self.chunks_in_range(start..self.len()).flat_map(str::chars)
-    }
-
-    pub fn reversed_chars_at(&self, start: usize) -> impl Iterator<Item = char> + '_ {
-        self.reversed_chunks_in_range(0..start)
-            .flat_map(|chunk| chunk.chars().rev())
-    }
-
-    pub fn bytes_in_range(&self, range: Range<usize>) -> Bytes {
-        Bytes::new(self, range, false)
-    }
-
-    pub fn reversed_bytes_in_range(&self, range: Range<usize>) -> Bytes {
-        Bytes::new(self, range, true)
-    }
-
-    pub fn chunks(&self) -> Chunks {
-        self.chunks_in_range(0..self.len())
-    }
-
-    pub fn chunks_in_range(&self, range: Range<usize>) -> Chunks {
-        Chunks::new(self, range, false)
-    }
-
-    pub fn reversed_chunks_in_range(&self, range: Range<usize>) -> Chunks {
-        Chunks::new(self, range, true)
-    }
-
-    pub fn offset_to_offset_utf16(&self, offset: usize) -> OffsetUtf16 {
-        if offset >= self.summary().len {
-            return self.summary().len_utf16;
-        }
-        let mut cursor = self.chunks.cursor::<(usize, OffsetUtf16)>();
-        cursor.seek(&offset, Bias::Left, &());
-        let overshoot = offset - cursor.start().0;
-        cursor.start().1
-            + cursor.item().map_or(Default::default(), |chunk| {
-                chunk.offset_to_offset_utf16(overshoot)
-            })
-    }
-
-    pub fn offset_utf16_to_offset(&self, offset: OffsetUtf16) -> usize {
-        if offset >= self.summary().len_utf16 {
-            return self.summary().len;
-        }
-        let mut cursor = self.chunks.cursor::<(OffsetUtf16, usize)>();
-        cursor.seek(&offset, Bias::Left, &());
-        let overshoot = offset - cursor.start().0;
-        cursor.start().1
-            + cursor.item().map_or(Default::default(), |chunk| {
-                chunk.offset_utf16_to_offset(overshoot)
-            })
-    }
-
-    pub fn offset_to_point(&self, offset: usize) -> Point {
-        if offset >= self.summary().len {
-            return self.summary().lines;
-        }
-        let mut cursor = self.chunks.cursor::<(usize, Point)>();
-        cursor.seek(&offset, Bias::Left, &());
-        let overshoot = offset - cursor.start().0;
-        cursor.start().1
-            + cursor
-                .item()
-                .map_or(Point::zero(), |chunk| chunk.offset_to_point(overshoot))
-    }
-
-    pub fn offset_to_point_utf16(&self, offset: usize) -> PointUtf16 {
-        if offset >= self.summary().len {
-            return self.summary().lines_utf16();
-        }
-        let mut cursor = self.chunks.cursor::<(usize, PointUtf16)>();
-        cursor.seek(&offset, Bias::Left, &());
-        let overshoot = offset - cursor.start().0;
-        cursor.start().1
-            + cursor.item().map_or(PointUtf16::zero(), |chunk| {
-                chunk.offset_to_point_utf16(overshoot)
-            })
-    }
-
-    pub fn point_to_point_utf16(&self, point: Point) -> PointUtf16 {
-        if point >= self.summary().lines {
-            return self.summary().lines_utf16();
-        }
-        let mut cursor = self.chunks.cursor::<(Point, PointUtf16)>();
-        cursor.seek(&point, Bias::Left, &());
-        let overshoot = point - cursor.start().0;
-        cursor.start().1
-            + cursor.item().map_or(PointUtf16::zero(), |chunk| {
-                chunk.point_to_point_utf16(overshoot)
-            })
-    }
-
-    pub fn point_to_offset(&self, point: Point) -> usize {
-        if point >= self.summary().lines {
-            return self.summary().len;
-        }
-        let mut cursor = self.chunks.cursor::<(Point, usize)>();
-        cursor.seek(&point, Bias::Left, &());
-        let overshoot = point - cursor.start().0;
-        cursor.start().1
-            + cursor
-                .item()
-                .map_or(0, |chunk| chunk.point_to_offset(overshoot))
-    }
-
-    pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize {
-        self.point_utf16_to_offset_impl(point, false)
-    }
-
-    pub fn unclipped_point_utf16_to_offset(&self, point: Unclipped<PointUtf16>) -> usize {
-        self.point_utf16_to_offset_impl(point.0, true)
-    }
-
-    fn point_utf16_to_offset_impl(&self, point: PointUtf16, clip: bool) -> usize {
-        if point >= self.summary().lines_utf16() {
-            return self.summary().len;
-        }
-        let mut cursor = self.chunks.cursor::<(PointUtf16, usize)>();
-        cursor.seek(&point, Bias::Left, &());
-        let overshoot = point - cursor.start().0;
-        cursor.start().1
-            + cursor
-                .item()
-                .map_or(0, |chunk| chunk.point_utf16_to_offset(overshoot, clip))
-    }
-
-    pub fn unclipped_point_utf16_to_point(&self, point: Unclipped<PointUtf16>) -> Point {
-        if point.0 >= self.summary().lines_utf16() {
-            return self.summary().lines;
-        }
-        let mut cursor = self.chunks.cursor::<(PointUtf16, Point)>();
-        cursor.seek(&point.0, Bias::Left, &());
-        let overshoot = Unclipped(point.0 - cursor.start().0);
-        cursor.start().1
-            + cursor.item().map_or(Point::zero(), |chunk| {
-                chunk.unclipped_point_utf16_to_point(overshoot)
-            })
-    }
-
-    pub fn clip_offset(&self, mut offset: usize, bias: Bias) -> usize {
-        let mut cursor = self.chunks.cursor::<usize>();
-        cursor.seek(&offset, Bias::Left, &());
-        if let Some(chunk) = cursor.item() {
-            let mut ix = offset - cursor.start();
-            while !chunk.0.is_char_boundary(ix) {
-                match bias {
-                    Bias::Left => {
-                        ix -= 1;
-                        offset -= 1;
-                    }
-                    Bias::Right => {
-                        ix += 1;
-                        offset += 1;
-                    }
-                }
-            }
-            offset
-        } else {
-            self.summary().len
-        }
-    }
-
-    pub fn clip_offset_utf16(&self, offset: OffsetUtf16, bias: Bias) -> OffsetUtf16 {
-        let mut cursor = self.chunks.cursor::<OffsetUtf16>();
-        cursor.seek(&offset, Bias::Right, &());
-        if let Some(chunk) = cursor.item() {
-            let overshoot = offset - cursor.start();
-            *cursor.start() + chunk.clip_offset_utf16(overshoot, bias)
-        } else {
-            self.summary().len_utf16
-        }
-    }
-
-    pub fn clip_point(&self, point: Point, bias: Bias) -> Point {
-        let mut cursor = self.chunks.cursor::<Point>();
-        cursor.seek(&point, Bias::Right, &());
-        if let Some(chunk) = cursor.item() {
-            let overshoot = point - cursor.start();
-            *cursor.start() + chunk.clip_point(overshoot, bias)
-        } else {
-            self.summary().lines
-        }
-    }
-
-    pub fn clip_point_utf16(&self, point: Unclipped<PointUtf16>, bias: Bias) -> PointUtf16 {
-        let mut cursor = self.chunks.cursor::<PointUtf16>();
-        cursor.seek(&point.0, Bias::Right, &());
-        if let Some(chunk) = cursor.item() {
-            let overshoot = Unclipped(point.0 - cursor.start());
-            *cursor.start() + chunk.clip_point_utf16(overshoot, bias)
-        } else {
-            self.summary().lines_utf16()
-        }
-    }
-
-    pub fn line_len(&self, row: u32) -> u32 {
-        self.clip_point(Point::new(row, u32::MAX), Bias::Left)
-            .column
-    }
-
-    pub fn fingerprint(&self) -> RopeFingerprint {
-        self.chunks.summary().fingerprint
-    }
-}
-
-impl<'a> From<&'a str> for Rope {
-    fn from(text: &'a str) -> Self {
-        let mut rope = Self::new();
-        rope.push(text);
-        rope
-    }
-}
-
-impl<'a> FromIterator<&'a str> for Rope {
-    fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
-        let mut rope = Rope::new();
-        for chunk in iter {
-            rope.push(chunk);
-        }
-        rope
-    }
-}
-
-impl From<String> for Rope {
-    fn from(text: String) -> Self {
-        Rope::from(text.as_str())
-    }
-}
-
-impl fmt::Display for Rope {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        for chunk in self.chunks() {
-            write!(f, "{}", chunk)?;
-        }
-        Ok(())
-    }
-}
-
-impl fmt::Debug for Rope {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        use std::fmt::Write as _;
-
-        write!(f, "\"")?;
-        let mut format_string = String::new();
-        for chunk in self.chunks() {
-            write!(&mut format_string, "{:?}", chunk)?;
-            write!(f, "{}", &format_string[1..format_string.len() - 1])?;
-            format_string.clear();
-        }
-        write!(f, "\"")?;
-        Ok(())
-    }
-}
-
-pub struct Cursor<'a> {
-    rope: &'a Rope,
-    chunks: sum_tree::Cursor<'a, Chunk, usize>,
-    offset: usize,
-}
-
-impl<'a> Cursor<'a> {
-    pub fn new(rope: &'a Rope, offset: usize) -> Self {
-        let mut chunks = rope.chunks.cursor();
-        chunks.seek(&offset, Bias::Right, &());
-        Self {
-            rope,
-            chunks,
-            offset,
-        }
-    }
-
-    pub fn seek_forward(&mut self, end_offset: usize) {
-        debug_assert!(end_offset >= self.offset);
-
-        self.chunks.seek_forward(&end_offset, Bias::Right, &());
-        self.offset = end_offset;
-    }
-
-    pub fn slice(&mut self, end_offset: usize) -> Rope {
-        debug_assert!(
-            end_offset >= self.offset,
-            "cannot slice backwards from {} to {}",
-            self.offset,
-            end_offset
-        );
-
-        let mut slice = Rope::new();
-        if let Some(start_chunk) = self.chunks.item() {
-            let start_ix = self.offset - self.chunks.start();
-            let end_ix = cmp::min(end_offset, self.chunks.end(&())) - self.chunks.start();
-            slice.push(&start_chunk.0[start_ix..end_ix]);
-        }
-
-        if end_offset > self.chunks.end(&()) {
-            self.chunks.next(&());
-            slice.append(Rope {
-                chunks: self.chunks.slice(&end_offset, Bias::Right, &()),
-            });
-            if let Some(end_chunk) = self.chunks.item() {
-                let end_ix = end_offset - self.chunks.start();
-                slice.push(&end_chunk.0[..end_ix]);
-            }
-        }
-
-        self.offset = end_offset;
-        slice
-    }
-
-    pub fn summary<D: TextDimension>(&mut self, end_offset: usize) -> D {
-        debug_assert!(end_offset >= self.offset);
-
-        let mut summary = D::default();
-        if let Some(start_chunk) = self.chunks.item() {
-            let start_ix = self.offset - self.chunks.start();
-            let end_ix = cmp::min(end_offset, self.chunks.end(&())) - self.chunks.start();
-            summary.add_assign(&D::from_text_summary(&TextSummary::from(
-                &start_chunk.0[start_ix..end_ix],
-            )));
-        }
-
-        if end_offset > self.chunks.end(&()) {
-            self.chunks.next(&());
-            summary.add_assign(&self.chunks.summary(&end_offset, Bias::Right, &()));
-            if let Some(end_chunk) = self.chunks.item() {
-                let end_ix = end_offset - self.chunks.start();
-                summary.add_assign(&D::from_text_summary(&TextSummary::from(
-                    &end_chunk.0[..end_ix],
-                )));
-            }
-        }
-
-        self.offset = end_offset;
-        summary
-    }
-
-    pub fn suffix(mut self) -> Rope {
-        self.slice(self.rope.chunks.extent(&()))
-    }
-
-    pub fn offset(&self) -> usize {
-        self.offset
-    }
-}
-
-pub struct Chunks<'a> {
-    chunks: sum_tree::Cursor<'a, Chunk, usize>,
-    range: Range<usize>,
-    reversed: bool,
-}
-
-impl<'a> Chunks<'a> {
-    pub fn new(rope: &'a Rope, range: Range<usize>, reversed: bool) -> Self {
-        let mut chunks = rope.chunks.cursor();
-        if reversed {
-            chunks.seek(&range.end, Bias::Left, &());
-        } else {
-            chunks.seek(&range.start, Bias::Right, &());
-        }
-        Self {
-            chunks,
-            range,
-            reversed,
-        }
-    }
-
-    pub fn offset(&self) -> usize {
-        if self.reversed {
-            self.range.end.min(self.chunks.end(&()))
-        } else {
-            self.range.start.max(*self.chunks.start())
-        }
-    }
-
-    pub fn seek(&mut self, offset: usize) {
-        let bias = if self.reversed {
-            Bias::Left
-        } else {
-            Bias::Right
-        };
-
-        if offset >= self.chunks.end(&()) {
-            self.chunks.seek_forward(&offset, bias, &());
-        } else {
-            self.chunks.seek(&offset, bias, &());
-        }
-
-        if self.reversed {
-            self.range.end = offset;
-        } else {
-            self.range.start = offset;
-        }
-    }
-
-    pub fn peek(&self) -> Option<&'a str> {
-        let chunk = self.chunks.item()?;
-        if self.reversed && self.range.start >= self.chunks.end(&()) {
-            return None;
-        }
-        let chunk_start = *self.chunks.start();
-        if self.range.end <= chunk_start {
-            return None;
-        }
-
-        let start = self.range.start.saturating_sub(chunk_start);
-        let end = self.range.end - chunk_start;
-        Some(&chunk.0[start..chunk.0.len().min(end)])
-    }
-}
-
-impl<'a> Iterator for Chunks<'a> {
-    type Item = &'a str;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        let result = self.peek();
-        if result.is_some() {
-            if self.reversed {
-                self.chunks.prev(&());
-            } else {
-                self.chunks.next(&());
-            }
-        }
-        result
-    }
-}
-
-pub struct Bytes<'a> {
-    chunks: sum_tree::Cursor<'a, Chunk, usize>,
-    range: Range<usize>,
-    reversed: bool,
-}
-
-impl<'a> Bytes<'a> {
-    pub fn new(rope: &'a Rope, range: Range<usize>, reversed: bool) -> Self {
-        let mut chunks = rope.chunks.cursor();
-        if reversed {
-            chunks.seek(&range.end, Bias::Left, &());
-        } else {
-            chunks.seek(&range.start, Bias::Right, &());
-        }
-        Self {
-            chunks,
-            range,
-            reversed,
-        }
-    }
-
-    pub fn peek(&self) -> Option<&'a [u8]> {
-        let chunk = self.chunks.item()?;
-        if self.reversed && self.range.start >= self.chunks.end(&()) {
-            return None;
-        }
-        let chunk_start = *self.chunks.start();
-        if self.range.end <= chunk_start {
-            return None;
-        }
-        let start = self.range.start.saturating_sub(chunk_start);
-        let end = self.range.end - chunk_start;
-        Some(&chunk.0.as_bytes()[start..chunk.0.len().min(end)])
-    }
-}
-
-impl<'a> Iterator for Bytes<'a> {
-    type Item = &'a [u8];
-
-    fn next(&mut self) -> Option<Self::Item> {
-        let result = self.peek();
-        if result.is_some() {
-            if self.reversed {
-                self.chunks.prev(&());
-            } else {
-                self.chunks.next(&());
-            }
-        }
-        result
-    }
-}
-
-impl<'a> io::Read for Bytes<'a> {
-    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
-        if let Some(chunk) = self.peek() {
-            let len = cmp::min(buf.len(), chunk.len());
-            if self.reversed {
-                buf[..len].copy_from_slice(&chunk[chunk.len() - len..]);
-                buf[..len].reverse();
-                self.range.end -= len;
-            } else {
-                buf[..len].copy_from_slice(&chunk[..len]);
-                self.range.start += len;
-            }
-
-            if len == chunk.len() {
-                if self.reversed {
-                    self.chunks.prev(&());
-                } else {
-                    self.chunks.next(&());
-                }
-            }
-            Ok(len)
-        } else {
-            Ok(0)
-        }
-    }
-}
-
-#[derive(Clone, Debug, Default)]
-struct Chunk(ArrayString<{ 2 * CHUNK_BASE }>);
-
-impl Chunk {
-    fn offset_to_offset_utf16(&self, target: usize) -> OffsetUtf16 {
-        let mut offset = 0;
-        let mut offset_utf16 = OffsetUtf16(0);
-        for ch in self.0.chars() {
-            if offset >= target {
-                break;
-            }
-
-            offset += ch.len_utf8();
-            offset_utf16.0 += ch.len_utf16();
-        }
-        offset_utf16
-    }
-
-    fn offset_utf16_to_offset(&self, target: OffsetUtf16) -> usize {
-        let mut offset_utf16 = OffsetUtf16(0);
-        let mut offset = 0;
-        for ch in self.0.chars() {
-            if offset_utf16 >= target {
-                break;
-            }
-
-            offset += ch.len_utf8();
-            offset_utf16.0 += ch.len_utf16();
-        }
-        offset
-    }
-
-    fn offset_to_point(&self, target: usize) -> Point {
-        let mut offset = 0;
-        let mut point = Point::new(0, 0);
-        for ch in self.0.chars() {
-            if offset >= target {
-                break;
-            }
-
-            if ch == '\n' {
-                point.row += 1;
-                point.column = 0;
-            } else {
-                point.column += ch.len_utf8() as u32;
-            }
-            offset += ch.len_utf8();
-        }
-        point
-    }
-
-    fn offset_to_point_utf16(&self, target: usize) -> PointUtf16 {
-        let mut offset = 0;
-        let mut point = PointUtf16::new(0, 0);
-        for ch in self.0.chars() {
-            if offset >= target {
-                break;
-            }
-
-            if ch == '\n' {
-                point.row += 1;
-                point.column = 0;
-            } else {
-                point.column += ch.len_utf16() as u32;
-            }
-            offset += ch.len_utf8();
-        }
-        point
-    }
-
-    fn point_to_offset(&self, target: Point) -> usize {
-        let mut offset = 0;
-        let mut point = Point::new(0, 0);
-
-        for ch in self.0.chars() {
-            if point >= target {
-                if point > target {
-                    debug_panic!("point {target:?} is inside of character {ch:?}");
-                }
-                break;
-            }
-
-            if ch == '\n' {
-                point.row += 1;
-                point.column = 0;
-
-                if point.row > target.row {
-                    debug_panic!(
-                        "point {target:?} is beyond the end of a line with length {}",
-                        point.column
-                    );
-                    break;
-                }
-            } else {
-                point.column += ch.len_utf8() as u32;
-            }
-
-            offset += ch.len_utf8();
-        }
-
-        offset
-    }
-
-    fn point_to_point_utf16(&self, target: Point) -> PointUtf16 {
-        let mut point = Point::zero();
-        let mut point_utf16 = PointUtf16::new(0, 0);
-        for ch in self.0.chars() {
-            if point >= target {
-                break;
-            }
-
-            if ch == '\n' {
-                point_utf16.row += 1;
-                point_utf16.column = 0;
-                point.row += 1;
-                point.column = 0;
-            } else {
-                point_utf16.column += ch.len_utf16() as u32;
-                point.column += ch.len_utf8() as u32;
-            }
-        }
-        point_utf16
-    }
-
-    fn point_utf16_to_offset(&self, target: PointUtf16, clip: bool) -> usize {
-        let mut offset = 0;
-        let mut point = PointUtf16::new(0, 0);
-
-        for ch in self.0.chars() {
-            if point == target {
-                break;
-            }
-
-            if ch == '\n' {
-                point.row += 1;
-                point.column = 0;
-
-                if point.row > target.row {
-                    if !clip {
-                        debug_panic!(
-                            "point {target:?} is beyond the end of a line with length {}",
-                            point.column
-                        );
-                    }
-                    // Return the offset of the newline
-                    return offset;
-                }
-            } else {
-                point.column += ch.len_utf16() as u32;
-            }
-
-            if point > target {
-                if !clip {
-                    debug_panic!("point {target:?} is inside of codepoint {ch:?}");
-                }
-                // Return the offset of the codepoint which we have landed within, bias left
-                return offset;
-            }
-
-            offset += ch.len_utf8();
-        }
-
-        offset
-    }
-
-    fn unclipped_point_utf16_to_point(&self, target: Unclipped<PointUtf16>) -> Point {
-        let mut point = Point::zero();
-        let mut point_utf16 = PointUtf16::zero();
-
-        for ch in self.0.chars() {
-            if point_utf16 == target.0 {
-                break;
-            }
-
-            if point_utf16 > target.0 {
-                // If the point is past the end of a line or inside of a code point,
-                // return the last valid point before the target.
-                return point;
-            }
-
-            if ch == '\n' {
-                point_utf16 += PointUtf16::new(1, 0);
-                point += Point::new(1, 0);
-            } else {
-                point_utf16 += PointUtf16::new(0, ch.len_utf16() as u32);
-                point += Point::new(0, ch.len_utf8() as u32);
-            }
-        }
-
-        point
-    }
-
-    fn clip_point(&self, target: Point, bias: Bias) -> Point {
-        for (row, line) in self.0.split('\n').enumerate() {
-            if row == target.row as usize {
-                let mut column = target.column.min(line.len() as u32);
-                while !line.is_char_boundary(column as usize) {
-                    match bias {
-                        Bias::Left => column -= 1,
-                        Bias::Right => column += 1,
-                    }
-                }
-                return Point::new(row as u32, column);
-            }
-        }
-        unreachable!()
-    }
-
-    fn clip_point_utf16(&self, target: Unclipped<PointUtf16>, bias: Bias) -> PointUtf16 {
-        for (row, line) in self.0.split('\n').enumerate() {
-            if row == target.0.row as usize {
-                let mut code_units = line.encode_utf16();
-                let mut column = code_units.by_ref().take(target.0.column as usize).count();
-                if char::decode_utf16(code_units).next().transpose().is_err() {
-                    match bias {
-                        Bias::Left => column -= 1,
-                        Bias::Right => column += 1,
-                    }
-                }
-                return PointUtf16::new(row as u32, column as u32);
-            }
-        }
-        unreachable!()
-    }
-
-    fn clip_offset_utf16(&self, target: OffsetUtf16, bias: Bias) -> OffsetUtf16 {
-        let mut code_units = self.0.encode_utf16();
-        let mut offset = code_units.by_ref().take(target.0).count();
-        if char::decode_utf16(code_units).next().transpose().is_err() {
-            match bias {
-                Bias::Left => offset -= 1,
-                Bias::Right => offset += 1,
-            }
-        }
-        OffsetUtf16(offset)
-    }
-}
-
-impl sum_tree::Item for Chunk {
-    type Summary = ChunkSummary;
-
-    fn summary(&self) -> Self::Summary {
-        ChunkSummary::from(self.0.as_str())
-    }
-}
-
-#[derive(Clone, Debug, Default, Eq, PartialEq)]
-pub struct ChunkSummary {
-    text: TextSummary,
-    fingerprint: RopeFingerprint,
-}
-
-impl<'a> From<&'a str> for ChunkSummary {
-    fn from(text: &'a str) -> Self {
-        Self {
-            text: TextSummary::from(text),
-            fingerprint: Rope::text_fingerprint(text),
-        }
-    }
-}
-
-impl sum_tree::Summary for ChunkSummary {
-    type Context = ();
-
-    fn add_summary(&mut self, summary: &Self, _: &()) {
-        self.text += &summary.text;
-        self.fingerprint = self.fingerprint * summary.fingerprint;
-    }
-}
-
-#[derive(Clone, Debug, Default, Eq, PartialEq)]
-pub struct TextSummary {
-    pub len: usize,
-    pub len_utf16: OffsetUtf16,
-    pub lines: Point,
-    pub first_line_chars: u32,
-    pub last_line_chars: u32,
-    pub last_line_len_utf16: u32,
-    pub longest_row: u32,
-    pub longest_row_chars: u32,
-}
-
-impl TextSummary {
-    pub fn lines_utf16(&self) -> PointUtf16 {
-        PointUtf16 {
-            row: self.lines.row,
-            column: self.last_line_len_utf16,
-        }
-    }
-}
-
-impl<'a> From<&'a str> for TextSummary {
-    fn from(text: &'a str) -> Self {
-        let mut len_utf16 = OffsetUtf16(0);
-        let mut lines = Point::new(0, 0);
-        let mut first_line_chars = 0;
-        let mut last_line_chars = 0;
-        let mut last_line_len_utf16 = 0;
-        let mut longest_row = 0;
-        let mut longest_row_chars = 0;
-        for c in text.chars() {
-            len_utf16.0 += c.len_utf16();
-
-            if c == '\n' {
-                lines += Point::new(1, 0);
-                last_line_len_utf16 = 0;
-                last_line_chars = 0;
-            } else {
-                lines.column += c.len_utf8() as u32;
-                last_line_len_utf16 += c.len_utf16() as u32;
-                last_line_chars += 1;
-            }
-
-            if lines.row == 0 {
-                first_line_chars = last_line_chars;
-            }
-
-            if last_line_chars > longest_row_chars {
-                longest_row = lines.row;
-                longest_row_chars = last_line_chars;
-            }
-        }
-
-        TextSummary {
-            len: text.len(),
-            len_utf16,
-            lines,
-            first_line_chars,
-            last_line_chars,
-            last_line_len_utf16,
-            longest_row,
-            longest_row_chars,
-        }
-    }
-}
-
-impl sum_tree::Summary for TextSummary {
-    type Context = ();
-
-    fn add_summary(&mut self, summary: &Self, _: &Self::Context) {
-        *self += summary;
-    }
-}
-
-impl std::ops::Add<Self> for TextSummary {
-    type Output = Self;
-
-    fn add(mut self, rhs: Self) -> Self::Output {
-        AddAssign::add_assign(&mut self, &rhs);
-        self
-    }
-}
-
-impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
-    fn add_assign(&mut self, other: &'a Self) {
-        let joined_chars = self.last_line_chars + other.first_line_chars;
-        if joined_chars > self.longest_row_chars {
-            self.longest_row = self.lines.row;
-            self.longest_row_chars = joined_chars;
-        }
-        if other.longest_row_chars > self.longest_row_chars {
-            self.longest_row = self.lines.row + other.longest_row;
-            self.longest_row_chars = other.longest_row_chars;
-        }
-
-        if self.lines.row == 0 {
-            self.first_line_chars += other.first_line_chars;
-        }
-
-        if other.lines.row == 0 {
-            self.last_line_chars += other.first_line_chars;
-            self.last_line_len_utf16 += other.last_line_len_utf16;
-        } else {
-            self.last_line_chars = other.last_line_chars;
-            self.last_line_len_utf16 = other.last_line_len_utf16;
-        }
-
-        self.len += other.len;
-        self.len_utf16 += other.len_utf16;
-        self.lines += other.lines;
-    }
-}
-
-impl std::ops::AddAssign<Self> for TextSummary {
-    fn add_assign(&mut self, other: Self) {
-        *self += &other;
-    }
-}
-
-pub trait TextDimension: 'static + for<'a> Dimension<'a, ChunkSummary> {
-    fn from_text_summary(summary: &TextSummary) -> Self;
-    fn add_assign(&mut self, other: &Self);
-}
-
-impl<D1: TextDimension, D2: TextDimension> TextDimension for (D1, D2) {
-    fn from_text_summary(summary: &TextSummary) -> Self {
-        (
-            D1::from_text_summary(summary),
-            D2::from_text_summary(summary),
-        )
-    }
-
-    fn add_assign(&mut self, other: &Self) {
-        self.0.add_assign(&other.0);
-        self.1.add_assign(&other.1);
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, ChunkSummary> for TextSummary {
-    fn add_summary(&mut self, summary: &'a ChunkSummary, _: &()) {
-        *self += &summary.text;
-    }
-}
-
-impl TextDimension for TextSummary {
-    fn from_text_summary(summary: &TextSummary) -> Self {
-        summary.clone()
-    }
-
-    fn add_assign(&mut self, other: &Self) {
-        *self += other;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, ChunkSummary> for usize {
-    fn add_summary(&mut self, summary: &'a ChunkSummary, _: &()) {
-        *self += summary.text.len;
-    }
-}
-
-impl TextDimension for usize {
-    fn from_text_summary(summary: &TextSummary) -> Self {
-        summary.len
-    }
-
-    fn add_assign(&mut self, other: &Self) {
-        *self += other;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, ChunkSummary> for OffsetUtf16 {
-    fn add_summary(&mut self, summary: &'a ChunkSummary, _: &()) {
-        *self += summary.text.len_utf16;
-    }
-}
-
-impl TextDimension for OffsetUtf16 {
-    fn from_text_summary(summary: &TextSummary) -> Self {
-        summary.len_utf16
-    }
-
-    fn add_assign(&mut self, other: &Self) {
-        *self += other;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, ChunkSummary> for Point {
-    fn add_summary(&mut self, summary: &'a ChunkSummary, _: &()) {
-        *self += summary.text.lines;
-    }
-}
-
-impl TextDimension for Point {
-    fn from_text_summary(summary: &TextSummary) -> Self {
-        summary.lines
-    }
-
-    fn add_assign(&mut self, other: &Self) {
-        *self += other;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, ChunkSummary> for PointUtf16 {
-    fn add_summary(&mut self, summary: &'a ChunkSummary, _: &()) {
-        *self += summary.text.lines_utf16();
-    }
-}
-
-impl TextDimension for PointUtf16 {
-    fn from_text_summary(summary: &TextSummary) -> Self {
-        summary.lines_utf16()
-    }
-
-    fn add_assign(&mut self, other: &Self) {
-        *self += other;
-    }
-}
-
-fn find_split_ix(text: &str) -> usize {
-    let mut ix = text.len() / 2;
-    while !text.is_char_boundary(ix) {
-        if ix < 2 * CHUNK_BASE {
-            ix += 1;
-        } else {
-            ix = (text.len() / 2) - 1;
-            break;
-        }
-    }
-    while !text.is_char_boundary(ix) {
-        ix -= 1;
-    }
-
-    debug_assert!(ix <= 2 * CHUNK_BASE);
-    debug_assert!(text.len() - ix <= 2 * CHUNK_BASE);
-    ix
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use rand::prelude::*;
-    use std::{cmp::Ordering, env, io::Read};
-    use util::RandomCharIter;
-    use Bias::{Left, Right};
-
-    #[test]
-    fn test_all_4_byte_chars() {
-        let mut rope = Rope::new();
-        let text = "🏀".repeat(256);
-        rope.push(&text);
-        assert_eq!(rope.text(), text);
-    }
-
-    #[test]
-    fn test_clip() {
-        let rope = Rope::from("🧘");
-
-        assert_eq!(rope.clip_offset(1, Bias::Left), 0);
-        assert_eq!(rope.clip_offset(1, Bias::Right), 4);
-        assert_eq!(rope.clip_offset(5, Bias::Right), 4);
-
-        assert_eq!(
-            rope.clip_point(Point::new(0, 1), Bias::Left),
-            Point::new(0, 0)
-        );
-        assert_eq!(
-            rope.clip_point(Point::new(0, 1), Bias::Right),
-            Point::new(0, 4)
-        );
-        assert_eq!(
-            rope.clip_point(Point::new(0, 5), Bias::Right),
-            Point::new(0, 4)
-        );
-
-        assert_eq!(
-            rope.clip_point_utf16(Unclipped(PointUtf16::new(0, 1)), Bias::Left),
-            PointUtf16::new(0, 0)
-        );
-        assert_eq!(
-            rope.clip_point_utf16(Unclipped(PointUtf16::new(0, 1)), Bias::Right),
-            PointUtf16::new(0, 2)
-        );
-        assert_eq!(
-            rope.clip_point_utf16(Unclipped(PointUtf16::new(0, 3)), Bias::Right),
-            PointUtf16::new(0, 2)
-        );
-
-        assert_eq!(
-            rope.clip_offset_utf16(OffsetUtf16(1), Bias::Left),
-            OffsetUtf16(0)
-        );
-        assert_eq!(
-            rope.clip_offset_utf16(OffsetUtf16(1), Bias::Right),
-            OffsetUtf16(2)
-        );
-        assert_eq!(
-            rope.clip_offset_utf16(OffsetUtf16(3), Bias::Right),
-            OffsetUtf16(2)
-        );
-    }
-
-    #[gpui::test(iterations = 100)]
-    fn test_random_rope(mut rng: StdRng) {
-        let operations = env::var("OPERATIONS")
-            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
-            .unwrap_or(10);
-
-        let mut expected = String::new();
-        let mut actual = Rope::new();
-        for _ in 0..operations {
-            let end_ix = clip_offset(&expected, rng.gen_range(0..=expected.len()), Right);
-            let start_ix = clip_offset(&expected, rng.gen_range(0..=end_ix), Left);
-            let len = rng.gen_range(0..=64);
-            let new_text: String = RandomCharIter::new(&mut rng).take(len).collect();
-
-            let mut new_actual = Rope::new();
-            let mut cursor = actual.cursor(0);
-            new_actual.append(cursor.slice(start_ix));
-            new_actual.push(&new_text);
-            cursor.seek_forward(end_ix);
-            new_actual.append(cursor.suffix());
-            actual = new_actual;
-
-            expected.replace_range(start_ix..end_ix, &new_text);
-
-            assert_eq!(actual.text(), expected);
-            log::info!("text: {:?}", expected);
-
-            for _ in 0..5 {
-                let end_ix = clip_offset(&expected, rng.gen_range(0..=expected.len()), Right);
-                let start_ix = clip_offset(&expected, rng.gen_range(0..=end_ix), Left);
-
-                let actual_text = actual.chunks_in_range(start_ix..end_ix).collect::<String>();
-                assert_eq!(actual_text, &expected[start_ix..end_ix]);
-
-                let mut actual_text = String::new();
-                actual
-                    .bytes_in_range(start_ix..end_ix)
-                    .read_to_string(&mut actual_text)
-                    .unwrap();
-                assert_eq!(actual_text, &expected[start_ix..end_ix]);
-
-                assert_eq!(
-                    actual
-                        .reversed_chunks_in_range(start_ix..end_ix)
-                        .collect::<Vec<&str>>()
-                        .into_iter()
-                        .rev()
-                        .collect::<String>(),
-                    &expected[start_ix..end_ix]
-                );
-            }
-
-            let mut offset_utf16 = OffsetUtf16(0);
-            let mut point = Point::new(0, 0);
-            let mut point_utf16 = PointUtf16::new(0, 0);
-            for (ix, ch) in expected.char_indices().chain(Some((expected.len(), '\0'))) {
-                assert_eq!(actual.offset_to_point(ix), point, "offset_to_point({})", ix);
-                assert_eq!(
-                    actual.offset_to_point_utf16(ix),
-                    point_utf16,
-                    "offset_to_point_utf16({})",
-                    ix
-                );
-                assert_eq!(
-                    actual.point_to_offset(point),
-                    ix,
-                    "point_to_offset({:?})",
-                    point
-                );
-                assert_eq!(
-                    actual.point_utf16_to_offset(point_utf16),
-                    ix,
-                    "point_utf16_to_offset({:?})",
-                    point_utf16
-                );
-                assert_eq!(
-                    actual.offset_to_offset_utf16(ix),
-                    offset_utf16,
-                    "offset_to_offset_utf16({:?})",
-                    ix
-                );
-                assert_eq!(
-                    actual.offset_utf16_to_offset(offset_utf16),
-                    ix,
-                    "offset_utf16_to_offset({:?})",
-                    offset_utf16
-                );
-                if ch == '\n' {
-                    point += Point::new(1, 0);
-                    point_utf16 += PointUtf16::new(1, 0);
-                } else {
-                    point.column += ch.len_utf8() as u32;
-                    point_utf16.column += ch.len_utf16() as u32;
-                }
-                offset_utf16.0 += ch.len_utf16();
-            }
-
-            let mut offset_utf16 = OffsetUtf16(0);
-            let mut point_utf16 = Unclipped(PointUtf16::zero());
-            for unit in expected.encode_utf16() {
-                let left_offset = actual.clip_offset_utf16(offset_utf16, Bias::Left);
-                let right_offset = actual.clip_offset_utf16(offset_utf16, Bias::Right);
-                assert!(right_offset >= left_offset);
-                // Ensure translating UTF-16 offsets to UTF-8 offsets doesn't panic.
-                actual.offset_utf16_to_offset(left_offset);
-                actual.offset_utf16_to_offset(right_offset);
-
-                let left_point = actual.clip_point_utf16(point_utf16, Bias::Left);
-                let right_point = actual.clip_point_utf16(point_utf16, Bias::Right);
-                assert!(right_point >= left_point);
-                // Ensure translating valid UTF-16 points to offsets doesn't panic.
-                actual.point_utf16_to_offset(left_point);
-                actual.point_utf16_to_offset(right_point);
-
-                offset_utf16.0 += 1;
-                if unit == b'\n' as u16 {
-                    point_utf16.0 += PointUtf16::new(1, 0);
-                } else {
-                    point_utf16.0 += PointUtf16::new(0, 1);
-                }
-            }
-
-            for _ in 0..5 {
-                let end_ix = clip_offset(&expected, rng.gen_range(0..=expected.len()), Right);
-                let start_ix = clip_offset(&expected, rng.gen_range(0..=end_ix), Left);
-                assert_eq!(
-                    actual.cursor(start_ix).summary::<TextSummary>(end_ix),
-                    TextSummary::from(&expected[start_ix..end_ix])
-                );
-            }
-
-            let mut expected_longest_rows = Vec::new();
-            let mut longest_line_len = -1_isize;
-            for (row, line) in expected.split('\n').enumerate() {
-                let row = row as u32;
-                assert_eq!(
-                    actual.line_len(row),
-                    line.len() as u32,
-                    "invalid line len for row {}",
-                    row
-                );
-
-                let line_char_count = line.chars().count() as isize;
-                match line_char_count.cmp(&longest_line_len) {
-                    Ordering::Less => {}
-                    Ordering::Equal => expected_longest_rows.push(row),
-                    Ordering::Greater => {
-                        longest_line_len = line_char_count;
-                        expected_longest_rows.clear();
-                        expected_longest_rows.push(row);
-                    }
-                }
-            }
-
-            let longest_row = actual.summary().longest_row;
-            assert!(
-                expected_longest_rows.contains(&longest_row),
-                "incorrect longest row {}. expected {:?} with length {}",
-                longest_row,
-                expected_longest_rows,
-                longest_line_len,
-            );
-        }
-    }
-
-    fn clip_offset(text: &str, mut offset: usize, bias: Bias) -> usize {
-        while !text.is_char_boundary(offset) {
-            match bias {
-                Bias::Left => offset -= 1,
-                Bias::Right => offset += 1,
-            }
-        }
-        offset
-    }
-
-    impl Rope {
-        fn text(&self) -> String {
-            let mut text = String::new();
-            for chunk in self.chunks.cursor::<()>() {
-                text.push_str(&chunk.0);
-            }
-            text
-        }
-    }
-}

crates/rope2/src/unclipped.rs 🔗

@@ -1,57 +0,0 @@
-use crate::{ChunkSummary, TextDimension, TextSummary};
-use std::ops::{Add, AddAssign, Sub, SubAssign};
-
-#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub struct Unclipped<T>(pub T);
-
-impl<T> From<T> for Unclipped<T> {
-    fn from(value: T) -> Self {
-        Unclipped(value)
-    }
-}
-
-impl<'a, T: sum_tree::Dimension<'a, ChunkSummary>> sum_tree::Dimension<'a, ChunkSummary>
-    for Unclipped<T>
-{
-    fn add_summary(&mut self, summary: &'a ChunkSummary, _: &()) {
-        self.0.add_summary(summary, &());
-    }
-}
-
-impl<T: TextDimension> TextDimension for Unclipped<T> {
-    fn from_text_summary(summary: &TextSummary) -> Self {
-        Unclipped(T::from_text_summary(summary))
-    }
-
-    fn add_assign(&mut self, other: &Self) {
-        TextDimension::add_assign(&mut self.0, &other.0);
-    }
-}
-
-impl<T: Add<T, Output = T>> Add<Unclipped<T>> for Unclipped<T> {
-    type Output = Unclipped<T>;
-
-    fn add(self, rhs: Unclipped<T>) -> Self::Output {
-        Unclipped(self.0 + rhs.0)
-    }
-}
-
-impl<T: Sub<T, Output = T>> Sub<Unclipped<T>> for Unclipped<T> {
-    type Output = Unclipped<T>;
-
-    fn sub(self, rhs: Unclipped<T>) -> Self::Output {
-        Unclipped(self.0 - rhs.0)
-    }
-}
-
-impl<T: AddAssign<T>> AddAssign<Unclipped<T>> for Unclipped<T> {
-    fn add_assign(&mut self, rhs: Unclipped<T>) {
-        self.0 += rhs.0;
-    }
-}
-
-impl<T: SubAssign<T>> SubAssign<Unclipped<T>> for Unclipped<T> {
-    fn sub_assign(&mut self, rhs: Unclipped<T>) {
-        self.0 -= rhs.0;
-    }
-}

crates/rpc/Cargo.toml 🔗

@@ -17,7 +17,6 @@ clock = { path = "../clock" }
 collections = { path = "../collections" }
 gpui = { path = "../gpui", optional = true }
 util = { path = "../util" }
-
 anyhow.workspace = true
 async-lock = "2.4"
 async-tungstenite = "0.16"
@@ -27,8 +26,8 @@ parking_lot.workspace = true
 prost.workspace = true
 rand.workspace = true
 rsa = "0.4"
-serde.workspace = true
 serde_json.workspace = true
+serde.workspace = true
 serde_derive.workspace = true
 smol-timeout = "0.6"
 strum.workspace = true

crates/rpc/src/conn.rs 🔗

@@ -34,7 +34,7 @@ impl Connection {
 
     #[cfg(any(test, feature = "test-support"))]
     pub fn in_memory(
-        executor: std::sync::Arc<gpui::executor::Background>,
+        executor: gpui::BackgroundExecutor,
     ) -> (Self, Self, std::sync::Arc<std::sync::atomic::AtomicBool>) {
         use std::sync::{
             atomic::{AtomicBool, Ordering::SeqCst},
@@ -53,7 +53,7 @@ impl Connection {
         #[allow(clippy::type_complexity)]
         fn channel(
             killed: Arc<AtomicBool>,
-            executor: Arc<gpui::executor::Background>,
+            executor: gpui::BackgroundExecutor,
         ) -> (
             Box<dyn Send + Unpin + futures::Sink<WebSocketMessage, Error = anyhow::Error>>,
             Box<dyn Send + Unpin + futures::Stream<Item = Result<WebSocketMessage, anyhow::Error>>>,
@@ -66,14 +66,12 @@ impl Connection {
 
             let tx = tx.sink_map_err(|error| anyhow!(error)).with({
                 let killed = killed.clone();
-                let executor = Arc::downgrade(&executor);
+                let executor = executor.clone();
                 move |msg| {
                     let killed = killed.clone();
                     let executor = executor.clone();
                     Box::pin(async move {
-                        if let Some(executor) = executor.upgrade() {
-                            executor.simulate_random_delay().await;
-                        }
+                        executor.simulate_random_delay().await;
 
                         // Writes to a half-open TCP connection will error.
                         if killed.load(SeqCst) {
@@ -87,14 +85,12 @@ impl Connection {
 
             let rx = rx.then({
                 let killed = killed;
-                let executor = Arc::downgrade(&executor);
+                let executor = executor.clone();
                 move |msg| {
                     let killed = killed.clone();
                     let executor = executor.clone();
                     Box::pin(async move {
-                        if let Some(executor) = executor.upgrade() {
-                            executor.simulate_random_delay().await;
-                        }
+                        executor.simulate_random_delay().await;
 
                         // Reads from a half-open TCP connection will hang.
                         if killed.load(SeqCst) {

crates/rpc/src/peer.rs 🔗

@@ -342,7 +342,7 @@ impl Peer {
     pub fn add_test_connection(
         self: &Arc<Self>,
         connection: Connection,
-        executor: Arc<gpui::executor::Background>,
+        executor: gpui::BackgroundExecutor,
     ) -> (
         ConnectionId,
         impl Future<Output = anyhow::Result<()>> + Send,
@@ -559,7 +559,6 @@ mod tests {
     use async_tungstenite::tungstenite::Message as WebSocketMessage;
     use gpui::TestAppContext;
 
-    #[ctor::ctor]
     fn init_logger() {
         if std::env::var("RUST_LOG").is_ok() {
             env_logger::init();
@@ -568,7 +567,9 @@ mod tests {
 
     #[gpui::test(iterations = 50)]
     async fn test_request_response(cx: &mut TestAppContext) {
-        let executor = cx.foreground();
+        init_logger();
+
+        let executor = cx.executor();
 
         // create 2 clients connected to 1 server
         let server = Peer::new(0);
@@ -576,18 +577,18 @@ mod tests {
         let client2 = Peer::new(0);
 
         let (client1_to_server_conn, server_to_client_1_conn, _kill) =
-            Connection::in_memory(cx.background());
+            Connection::in_memory(cx.executor());
         let (client1_conn_id, io_task1, client1_incoming) =
-            client1.add_test_connection(client1_to_server_conn, cx.background());
+            client1.add_test_connection(client1_to_server_conn, cx.executor());
         let (_, io_task2, server_incoming1) =
-            server.add_test_connection(server_to_client_1_conn, cx.background());
+            server.add_test_connection(server_to_client_1_conn, cx.executor());
 
         let (client2_to_server_conn, server_to_client_2_conn, _kill) =
-            Connection::in_memory(cx.background());
+            Connection::in_memory(cx.executor());
         let (client2_conn_id, io_task3, client2_incoming) =
-            client2.add_test_connection(client2_to_server_conn, cx.background());
+            client2.add_test_connection(client2_to_server_conn, cx.executor());
         let (_, io_task4, server_incoming2) =
-            server.add_test_connection(server_to_client_2_conn, cx.background());
+            server.add_test_connection(server_to_client_2_conn, cx.executor());
 
         executor.spawn(io_task1).detach();
         executor.spawn(io_task2).detach();
@@ -664,25 +665,25 @@ mod tests {
 
     #[gpui::test(iterations = 50)]
     async fn test_order_of_response_and_incoming(cx: &mut TestAppContext) {
-        let executor = cx.foreground();
+        let executor = cx.executor();
         let server = Peer::new(0);
         let client = Peer::new(0);
 
         let (client_to_server_conn, server_to_client_conn, _kill) =
-            Connection::in_memory(cx.background());
+            Connection::in_memory(executor.clone());
         let (client_to_server_conn_id, io_task1, mut client_incoming) =
-            client.add_test_connection(client_to_server_conn, cx.background());
+            client.add_test_connection(client_to_server_conn, executor.clone());
+
         let (server_to_client_conn_id, io_task2, mut server_incoming) =
-            server.add_test_connection(server_to_client_conn, cx.background());
+            server.add_test_connection(server_to_client_conn, executor.clone());
 
         executor.spawn(io_task1).detach();
         executor.spawn(io_task2).detach();
 
         executor
             .spawn(async move {
-                let request = server_incoming
-                    .next()
-                    .await
+                let future = server_incoming.next().await;
+                let request = future
                     .unwrap()
                     .into_any()
                     .downcast::<TypedEnvelope<proto::Ping>>()
@@ -762,16 +763,16 @@ mod tests {
 
     #[gpui::test(iterations = 50)]
     async fn test_dropping_request_before_completion(cx: &mut TestAppContext) {
-        let executor = cx.foreground();
+        let executor = cx.executor();
         let server = Peer::new(0);
         let client = Peer::new(0);
 
         let (client_to_server_conn, server_to_client_conn, _kill) =
-            Connection::in_memory(cx.background());
+            Connection::in_memory(cx.executor());
         let (client_to_server_conn_id, io_task1, mut client_incoming) =
-            client.add_test_connection(client_to_server_conn, cx.background());
+            client.add_test_connection(client_to_server_conn, cx.executor());
         let (server_to_client_conn_id, io_task2, mut server_incoming) =
-            server.add_test_connection(server_to_client_conn, cx.background());
+            server.add_test_connection(server_to_client_conn, cx.executor());
 
         executor.spawn(io_task1).detach();
         executor.spawn(io_task2).detach();
@@ -858,7 +859,7 @@ mod tests {
             .detach();
 
         // Allow the request to make some progress before dropping it.
-        cx.background().simulate_random_delay().await;
+        cx.executor().simulate_random_delay().await;
         drop(request1_task);
 
         request2_task.await;
@@ -874,13 +875,13 @@ mod tests {
 
     #[gpui::test(iterations = 50)]
     async fn test_disconnect(cx: &mut TestAppContext) {
-        let executor = cx.foreground();
+        let executor = cx.executor();
 
-        let (client_conn, mut server_conn, _kill) = Connection::in_memory(cx.background());
+        let (client_conn, mut server_conn, _kill) = Connection::in_memory(executor.clone());
 
         let client = Peer::new(0);
         let (connection_id, io_handler, mut incoming) =
-            client.add_test_connection(client_conn, cx.background());
+            client.add_test_connection(client_conn, executor.clone());
 
         let (io_ended_tx, io_ended_rx) = oneshot::channel();
         executor
@@ -910,12 +911,12 @@ mod tests {
 
     #[gpui::test(iterations = 50)]
     async fn test_io_error(cx: &mut TestAppContext) {
-        let executor = cx.foreground();
-        let (client_conn, mut server_conn, _kill) = Connection::in_memory(cx.background());
+        let executor = cx.executor();
+        let (client_conn, mut server_conn, _kill) = Connection::in_memory(executor.clone());
 
         let client = Peer::new(0);
         let (connection_id, io_handler, mut incoming) =
-            client.add_test_connection(client_conn, cx.background());
+            client.add_test_connection(client_conn, executor.clone());
         executor.spawn(io_handler).detach();
         executor
             .spawn(async move { incoming.next().await })

crates/rpc2/Cargo.toml 🔗

@@ -1,46 +0,0 @@
-[package]
-description = "Shared logic for communication between the Zed app and the zed.dev server"
-edition = "2021"
-name = "rpc2"
-version = "0.1.0"
-publish = false
-
-[lib]
-path = "src/rpc.rs"
-doctest = false
-
-[features]
-test-support = ["collections/test-support", "gpui/test-support"]
-
-[dependencies]
-clock = { path = "../clock" }
-collections = { path = "../collections" }
-gpui = { package = "gpui2", path = "../gpui2", optional = true }
-util = { path = "../util" }
-anyhow.workspace = true
-async-lock = "2.4"
-async-tungstenite = "0.16"
-base64 = "0.13"
-futures.workspace = true
-parking_lot.workspace = true
-prost.workspace = true
-rand.workspace = true
-rsa = "0.4"
-serde_json.workspace = true
-serde.workspace = true
-serde_derive.workspace = true
-smol-timeout = "0.6"
-strum.workspace = true
-tracing = { version = "0.1.34", features = ["log"] }
-zstd = "0.11"
-
-[build-dependencies]
-prost-build = "0.9"
-
-[dev-dependencies]
-collections = { path = "../collections", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-smol.workspace = true
-tempdir.workspace = true
-ctor.workspace = true
-env_logger.workspace = true

crates/rpc2/build.rs 🔗

@@ -1,8 +0,0 @@
-fn main() {
-    let mut build = prost_build::Config::new();
-    // build.protoc_arg("--experimental_allow_proto3_optional");
-    build
-        .type_attribute(".", "#[derive(serde::Serialize)]")
-        .compile_protos(&["proto/zed.proto"], &["proto"])
-        .unwrap();
-}

crates/rpc2/proto/zed.proto 🔗

@@ -1,1634 +0,0 @@
-syntax = "proto3";
-package zed.messages;
-
-// Looking for a number? Search "// Current max"
-
-message PeerId {
-    uint32 owner_id = 1;
-    uint32 id = 2;
-}
-
-message Envelope {
-    uint32 id = 1;
-    optional uint32 responding_to = 2;
-    optional PeerId original_sender_id = 3;
-    oneof payload {
-        Hello hello = 4;
-        Ack ack = 5;
-        Error error = 6;
-        Ping ping = 7;
-        Test test = 8;
-
-        CreateRoom create_room = 9;
-        CreateRoomResponse create_room_response = 10;
-        JoinRoom join_room = 11;
-        JoinRoomResponse join_room_response = 12;
-        RejoinRoom rejoin_room = 13;
-        RejoinRoomResponse rejoin_room_response = 14;
-        LeaveRoom leave_room = 15;
-        Call call = 16;
-        IncomingCall incoming_call = 17;
-        CallCanceled call_canceled = 18;
-        CancelCall cancel_call = 19;
-        DeclineCall decline_call = 20;
-        UpdateParticipantLocation update_participant_location = 21;
-        RoomUpdated room_updated = 22;
-
-        ShareProject share_project = 23;
-        ShareProjectResponse share_project_response = 24;
-        UnshareProject unshare_project = 25;
-        JoinProject join_project = 26;
-        JoinProjectResponse join_project_response = 27;
-        LeaveProject leave_project = 28;
-        AddProjectCollaborator add_project_collaborator = 29;
-        UpdateProjectCollaborator update_project_collaborator = 30;
-        RemoveProjectCollaborator remove_project_collaborator = 31;
-
-        GetDefinition get_definition = 32;
-        GetDefinitionResponse get_definition_response = 33;
-        GetTypeDefinition get_type_definition = 34;
-        GetTypeDefinitionResponse get_type_definition_response = 35;
-        GetReferences get_references = 36;
-        GetReferencesResponse get_references_response = 37;
-        GetDocumentHighlights get_document_highlights = 38;
-        GetDocumentHighlightsResponse get_document_highlights_response = 39;
-        GetProjectSymbols get_project_symbols = 40;
-        GetProjectSymbolsResponse get_project_symbols_response = 41;
-        OpenBufferForSymbol open_buffer_for_symbol = 42;
-        OpenBufferForSymbolResponse open_buffer_for_symbol_response = 43;
-
-        UpdateProject update_project = 44;
-        UpdateWorktree update_worktree = 45;
-
-        CreateProjectEntry create_project_entry = 46;
-        RenameProjectEntry rename_project_entry = 47;
-        CopyProjectEntry copy_project_entry = 48;
-        DeleteProjectEntry delete_project_entry = 49;
-        ProjectEntryResponse project_entry_response = 50;
-        ExpandProjectEntry expand_project_entry = 51;
-        ExpandProjectEntryResponse expand_project_entry_response = 52;
-
-        UpdateDiagnosticSummary update_diagnostic_summary = 53;
-        StartLanguageServer start_language_server = 54;
-        UpdateLanguageServer update_language_server = 55;
-
-        OpenBufferById open_buffer_by_id = 56;
-        OpenBufferByPath open_buffer_by_path = 57;
-        OpenBufferResponse open_buffer_response = 58;
-        CreateBufferForPeer create_buffer_for_peer = 59;
-        UpdateBuffer update_buffer = 60;
-        UpdateBufferFile update_buffer_file = 61;
-        SaveBuffer save_buffer = 62;
-        BufferSaved buffer_saved = 63;
-        BufferReloaded buffer_reloaded = 64;
-        ReloadBuffers reload_buffers = 65;
-        ReloadBuffersResponse reload_buffers_response = 66;
-        SynchronizeBuffers synchronize_buffers = 67;
-        SynchronizeBuffersResponse synchronize_buffers_response = 68;
-        FormatBuffers format_buffers = 69;
-        FormatBuffersResponse format_buffers_response = 70;
-        GetCompletions get_completions = 71;
-        GetCompletionsResponse get_completions_response = 72;
-        ResolveCompletionDocumentation resolve_completion_documentation = 73;
-        ResolveCompletionDocumentationResponse resolve_completion_documentation_response = 74;
-        ApplyCompletionAdditionalEdits apply_completion_additional_edits = 75;
-        ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 76;
-        GetCodeActions get_code_actions = 77;
-        GetCodeActionsResponse get_code_actions_response = 78;
-        GetHover get_hover = 79;
-        GetHoverResponse get_hover_response = 80;
-        ApplyCodeAction apply_code_action = 81;
-        ApplyCodeActionResponse apply_code_action_response = 82;
-        PrepareRename prepare_rename = 83;
-        PrepareRenameResponse prepare_rename_response = 84;
-        PerformRename perform_rename = 85;
-        PerformRenameResponse perform_rename_response = 86;
-        SearchProject search_project = 87;
-        SearchProjectResponse search_project_response = 88;
-
-        UpdateContacts update_contacts = 89;
-        UpdateInviteInfo update_invite_info = 90;
-        ShowContacts show_contacts = 91;
-
-        GetUsers get_users = 92;
-        FuzzySearchUsers fuzzy_search_users = 93;
-        UsersResponse users_response = 94;
-        RequestContact request_contact = 95;
-        RespondToContactRequest respond_to_contact_request = 96;
-        RemoveContact remove_contact = 97;
-
-        Follow follow = 98;
-        FollowResponse follow_response = 99;
-        UpdateFollowers update_followers = 100;
-        Unfollow unfollow = 101;
-        GetPrivateUserInfo get_private_user_info = 102;
-        GetPrivateUserInfoResponse get_private_user_info_response = 103;
-        UpdateDiffBase update_diff_base = 104;
-
-        OnTypeFormatting on_type_formatting = 105;
-        OnTypeFormattingResponse on_type_formatting_response = 106;
-
-        UpdateWorktreeSettings update_worktree_settings = 107;
-
-        InlayHints inlay_hints = 108;
-        InlayHintsResponse inlay_hints_response = 109;
-        ResolveInlayHint resolve_inlay_hint = 110;
-        ResolveInlayHintResponse resolve_inlay_hint_response = 111;
-        RefreshInlayHints refresh_inlay_hints = 112;
-
-        CreateChannel create_channel = 113;
-        CreateChannelResponse create_channel_response = 114;
-        InviteChannelMember invite_channel_member = 115;
-        RemoveChannelMember remove_channel_member = 116;
-        RespondToChannelInvite respond_to_channel_invite = 117;
-        UpdateChannels update_channels = 118;
-        JoinChannel join_channel = 119;
-        DeleteChannel delete_channel = 120;
-        GetChannelMembers get_channel_members = 121;
-        GetChannelMembersResponse get_channel_members_response = 122;
-        SetChannelMemberRole set_channel_member_role = 123;
-        RenameChannel rename_channel = 124;
-        RenameChannelResponse rename_channel_response = 125;
-
-        JoinChannelBuffer join_channel_buffer = 126;
-        JoinChannelBufferResponse join_channel_buffer_response = 127;
-        UpdateChannelBuffer update_channel_buffer = 128;
-        LeaveChannelBuffer leave_channel_buffer = 129;
-        UpdateChannelBufferCollaborators update_channel_buffer_collaborators = 130;
-        RejoinChannelBuffers rejoin_channel_buffers = 131;
-        RejoinChannelBuffersResponse rejoin_channel_buffers_response = 132;
-        AckBufferOperation ack_buffer_operation = 133;
-
-        JoinChannelChat join_channel_chat = 134;
-        JoinChannelChatResponse join_channel_chat_response = 135;
-        LeaveChannelChat leave_channel_chat = 136;
-        SendChannelMessage send_channel_message = 137;
-        SendChannelMessageResponse send_channel_message_response = 138;
-        ChannelMessageSent channel_message_sent = 139;
-        GetChannelMessages get_channel_messages = 140;
-        GetChannelMessagesResponse get_channel_messages_response = 141;
-        RemoveChannelMessage remove_channel_message = 142;
-        AckChannelMessage ack_channel_message = 143;
-        GetChannelMessagesById get_channel_messages_by_id = 144;
-
-        MoveChannel move_channel = 147;
-        SetChannelVisibility set_channel_visibility = 148;
-
-        AddNotification add_notification = 149;
-        GetNotifications get_notifications = 150;
-        GetNotificationsResponse get_notifications_response = 151;
-        DeleteNotification delete_notification = 152;
-        MarkNotificationRead mark_notification_read = 153;
-        LspExtExpandMacro lsp_ext_expand_macro = 154;
-        LspExtExpandMacroResponse lsp_ext_expand_macro_response = 155; // Current max
-    }
-}
-
-// Messages
-
-message Hello {
-    PeerId peer_id = 1;
-}
-
-message Ping {}
-
-message Ack {}
-
-message Error {
-    string message = 1;
-}
-
-message Test {
-    uint64 id = 1;
-}
-
-message CreateRoom {}
-
-message CreateRoomResponse {
-    Room room = 1;
-    optional LiveKitConnectionInfo live_kit_connection_info = 2;
-}
-
-message JoinRoom {
-    uint64 id = 1;
-}
-
-message JoinRoomResponse {
-    Room room = 1;
-    optional uint64 channel_id = 2;
-    optional LiveKitConnectionInfo live_kit_connection_info = 3;
-}
-
-message RejoinRoom {
-    uint64 id = 1;
-    repeated UpdateProject reshared_projects = 2;
-    repeated RejoinProject rejoined_projects = 3;
-}
-
-message RejoinProject {
-    uint64 id = 1;
-    repeated RejoinWorktree worktrees = 2;
-}
-
-message RejoinWorktree {
-    uint64 id = 1;
-    uint64 scan_id = 2;
-}
-
-message RejoinRoomResponse {
-    Room room = 1;
-    repeated ResharedProject reshared_projects = 2;
-    repeated RejoinedProject rejoined_projects = 3;
-}
-
-message ResharedProject {
-    uint64 id = 1;
-    repeated Collaborator collaborators = 2;
-}
-
-message RejoinedProject {
-    uint64 id = 1;
-    repeated WorktreeMetadata worktrees = 2;
-    repeated Collaborator collaborators = 3;
-    repeated LanguageServer language_servers = 4;
-}
-
-message LeaveRoom {}
-
-message Room {
-    uint64 id = 1;
-    repeated Participant participants = 2;
-    repeated PendingParticipant pending_participants = 3;
-    repeated Follower followers = 4;
-    string live_kit_room = 5;
-}
-
-message Participant {
-    uint64 user_id = 1;
-    PeerId peer_id = 2;
-    repeated ParticipantProject projects = 3;
-    ParticipantLocation location = 4;
-    uint32 participant_index = 5;
-}
-
-message PendingParticipant {
-    uint64 user_id = 1;
-    uint64 calling_user_id = 2;
-    optional uint64 initial_project_id = 3;
-}
-
-message ParticipantProject {
-    uint64 id = 1;
-    repeated string worktree_root_names = 2;
-}
-
-message Follower {
-    PeerId leader_id = 1;
-    PeerId follower_id = 2;
-    uint64 project_id = 3;
-}
-
-message ParticipantLocation {
-    oneof variant {
-        SharedProject shared_project = 1;
-        UnsharedProject unshared_project = 2;
-        External external = 3;
-    }
-
-    message SharedProject {
-        uint64 id = 1;
-    }
-
-    message UnsharedProject {}
-
-    message External {}
-}
-
-message Call {
-    uint64 room_id = 1;
-    uint64 called_user_id = 2;
-    optional uint64 initial_project_id = 3;
-}
-
-message IncomingCall {
-    uint64 room_id = 1;
-    uint64 calling_user_id = 2;
-    repeated uint64 participant_user_ids = 3;
-    optional ParticipantProject initial_project = 4;
-}
-
-message CallCanceled {
-    uint64 room_id = 1;
-}
-
-message CancelCall {
-    uint64 room_id = 1;
-    uint64 called_user_id = 2;
-}
-
-message DeclineCall {
-    uint64 room_id = 1;
-}
-
-message UpdateParticipantLocation {
-    uint64 room_id = 1;
-    ParticipantLocation location = 2;
-}
-
-message RoomUpdated {
-    Room room = 1;
-}
-
-message LiveKitConnectionInfo {
-    string server_url = 1;
-    string token = 2;
-    bool can_publish = 3;
-}
-
-message ShareProject {
-    uint64 room_id = 1;
-    repeated WorktreeMetadata worktrees = 2;
-}
-
-message ShareProjectResponse {
-    uint64 project_id = 1;
-}
-
-message UnshareProject {
-    uint64 project_id = 1;
-}
-
-message UpdateProject {
-    uint64 project_id = 1;
-    repeated WorktreeMetadata worktrees = 2;
-}
-
-message JoinProject {
-    uint64 project_id = 1;
-}
-
-message JoinProjectResponse {
-    uint32 replica_id = 1;
-    repeated WorktreeMetadata worktrees = 2;
-    repeated Collaborator collaborators = 3;
-    repeated LanguageServer language_servers = 4;
-}
-
-message LeaveProject {
-    uint64 project_id = 1;
-}
-
-message UpdateWorktree {
-    uint64 project_id = 1;
-    uint64 worktree_id = 2;
-    string root_name = 3;
-    repeated Entry updated_entries = 4;
-    repeated uint64 removed_entries = 5;
-    repeated RepositoryEntry updated_repositories = 6;
-    repeated uint64 removed_repositories = 7;
-    uint64 scan_id = 8;
-    bool is_last_update = 9;
-    string abs_path = 10;
-}
-
-message UpdateWorktreeSettings {
-    uint64 project_id = 1;
-    uint64 worktree_id = 2;
-    string path = 3;
-    optional string content = 4;
-}
-
-message CreateProjectEntry {
-    uint64 project_id = 1;
-    uint64 worktree_id = 2;
-    string path = 3;
-    bool is_directory = 4;
-}
-
-message RenameProjectEntry {
-    uint64 project_id = 1;
-    uint64 entry_id = 2;
-    string new_path = 3;
-}
-
-message CopyProjectEntry {
-    uint64 project_id = 1;
-    uint64 entry_id = 2;
-    string new_path = 3;
-}
-
-message DeleteProjectEntry {
-    uint64 project_id = 1;
-    uint64 entry_id = 2;
-}
-
-message ExpandProjectEntry {
-    uint64 project_id = 1;
-    uint64 entry_id = 2;
-}
-
-message ExpandProjectEntryResponse {
-    uint64 worktree_scan_id = 1;
-}
-
-message ProjectEntryResponse {
-    optional Entry entry = 1;
-    uint64 worktree_scan_id = 2;
-}
-
-message AddProjectCollaborator {
-    uint64 project_id = 1;
-    Collaborator collaborator = 2;
-}
-
-message UpdateProjectCollaborator {
-    uint64 project_id = 1;
-    PeerId old_peer_id = 2;
-    PeerId new_peer_id = 3;
-}
-
-message RemoveProjectCollaborator {
-    uint64 project_id = 1;
-    PeerId peer_id = 2;
-}
-
-message UpdateChannelBufferCollaborators {
-    uint64 channel_id = 1;
-    repeated Collaborator collaborators = 2;
-}
-
-message GetDefinition {
-     uint64 project_id = 1;
-     uint64 buffer_id = 2;
-     Anchor position = 3;
-     repeated VectorClockEntry version = 4;
- }
-
-message GetDefinitionResponse {
-    repeated LocationLink links = 1;
-}
-
-message GetTypeDefinition {
-     uint64 project_id = 1;
-     uint64 buffer_id = 2;
-     Anchor position = 3;
-     repeated VectorClockEntry version = 4;
- }
-
-message GetTypeDefinitionResponse {
-    repeated LocationLink links = 1;
-}
-
-message GetReferences {
-     uint64 project_id = 1;
-     uint64 buffer_id = 2;
-     Anchor position = 3;
-     repeated VectorClockEntry version = 4;
- }
-
-message GetReferencesResponse {
-    repeated Location locations = 1;
-}
-
-message GetDocumentHighlights {
-     uint64 project_id = 1;
-     uint64 buffer_id = 2;
-     Anchor position = 3;
-     repeated VectorClockEntry version = 4;
- }
-
-message GetDocumentHighlightsResponse {
-    repeated DocumentHighlight highlights = 1;
-}
-
-message Location {
-    uint64 buffer_id = 1;
-    Anchor start = 2;
-    Anchor end = 3;
-}
-
-message LocationLink {
-    optional Location origin = 1;
-    Location target = 2;
-}
-
-message DocumentHighlight {
-    Kind kind = 1;
-    Anchor start = 2;
-    Anchor end = 3;
-
-    enum Kind {
-        Text = 0;
-        Read = 1;
-        Write = 2;
-    }
-}
-
-message GetProjectSymbols {
-    uint64 project_id = 1;
-    string query = 2;
-}
-
-message GetProjectSymbolsResponse {
-    repeated Symbol symbols = 4;
-}
-
-message Symbol {
-    uint64 source_worktree_id = 1;
-    uint64 worktree_id = 2;
-    string language_server_name = 3;
-    string name = 4;
-    int32 kind = 5;
-    string path = 6;
-    // Cannot use generate anchors for unopened files,
-    // so we are forced to use point coords instead
-    PointUtf16 start = 7;
-    PointUtf16 end = 8;
-    bytes signature = 9;
-}
-
-message OpenBufferForSymbol {
-    uint64 project_id = 1;
-    Symbol symbol = 2;
-}
-
-message OpenBufferForSymbolResponse {
-    uint64 buffer_id = 1;
-}
-
-message OpenBufferByPath {
-    uint64 project_id = 1;
-    uint64 worktree_id = 2;
-    string path = 3;
-}
-
-message OpenBufferById {
-    uint64 project_id = 1;
-    uint64 id = 2;
-}
-
-message OpenBufferResponse {
-    uint64 buffer_id = 1;
-}
-
-message CreateBufferForPeer {
-    uint64 project_id = 1;
-    PeerId peer_id = 2;
-    oneof variant {
-        BufferState state = 3;
-        BufferChunk chunk = 4;
-    }
-}
-
-message UpdateBuffer {
-    uint64 project_id = 1;
-    uint64 buffer_id = 2;
-    repeated Operation operations = 3;
-}
-
-message UpdateChannelBuffer {
-    uint64 channel_id = 1;
-    repeated Operation operations = 2;
-}
-
-message UpdateBufferFile {
-    uint64 project_id = 1;
-    uint64 buffer_id = 2;
-    File file = 3;
-}
-
-message SaveBuffer {
-    uint64 project_id = 1;
-    uint64 buffer_id = 2;
-    repeated VectorClockEntry version = 3;
-}
-
-message BufferSaved {
-    uint64 project_id = 1;
-    uint64 buffer_id = 2;
-    repeated VectorClockEntry version = 3;
-    Timestamp mtime = 4;
-    string fingerprint = 5;
-}
-
-message BufferReloaded {
-    uint64 project_id = 1;
-    uint64 buffer_id = 2;
-    repeated VectorClockEntry version = 3;
-    Timestamp mtime = 4;
-    string fingerprint = 5;
-    LineEnding line_ending = 6;
-}
-
-message ReloadBuffers {
-    uint64 project_id = 1;
-    repeated uint64 buffer_ids = 2;
-}
-
-message ReloadBuffersResponse {
-    ProjectTransaction transaction = 1;
-}
-
-message SynchronizeBuffers {
-    uint64 project_id = 1;
-    repeated BufferVersion buffers = 2;
-}
-
-message SynchronizeBuffersResponse {
-    repeated BufferVersion buffers = 1;
-}
-
-message BufferVersion {
-    uint64 id = 1;
-    repeated VectorClockEntry version = 2;
-}
-
-message ChannelBufferVersion {
-    uint64 channel_id = 1;
-    repeated VectorClockEntry version = 2;
-    uint64 epoch = 3;
-}
-
-enum FormatTrigger {
-    Save = 0;
-    Manual = 1;
-}
-
-message FormatBuffers {
-    uint64 project_id = 1;
-    FormatTrigger trigger = 2;
-    repeated uint64 buffer_ids = 3;
-}
-
-message FormatBuffersResponse {
-    ProjectTransaction transaction = 1;
-}
-
-message GetCompletions {
-    uint64 project_id = 1;
-    uint64 buffer_id = 2;
-    Anchor position = 3;
-    repeated VectorClockEntry version = 4;
-}
-
-message GetCompletionsResponse {
-    repeated Completion completions = 1;
-    repeated VectorClockEntry version = 2;
-}
-
-message ApplyCompletionAdditionalEdits {
-    uint64 project_id = 1;
-    uint64 buffer_id = 2;
-    Completion completion = 3;
-}
-
-message ApplyCompletionAdditionalEditsResponse {
-    Transaction transaction = 1;
-}
-
-message Completion {
-    Anchor old_start = 1;
-    Anchor old_end = 2;
-    string new_text = 3;
-    uint64 server_id = 4;
-    bytes lsp_completion = 5;
-}
-
-message GetCodeActions {
-    uint64 project_id = 1;
-    uint64 buffer_id = 2;
-    Anchor start = 3;
-    Anchor end = 4;
-    repeated VectorClockEntry version = 5;
-}
-
-message GetCodeActionsResponse {
-    repeated CodeAction actions = 1;
-    repeated VectorClockEntry version = 2;
-}
-
-message GetHover {
-    uint64 project_id = 1;
-    uint64 buffer_id = 2;
-    Anchor position = 3;
-    repeated VectorClockEntry version = 5;
-}
-
-message GetHoverResponse {
-    optional Anchor start = 1;
-    optional Anchor end = 2;
-    repeated HoverBlock contents = 3;
-}
-
-message HoverBlock {
-    string text = 1;
-    optional string language = 2;
-    bool is_markdown = 3;
-}
-
-message ApplyCodeAction {
-    uint64 project_id = 1;
-    uint64 buffer_id = 2;
-    CodeAction action = 3;
-}
-
-message ApplyCodeActionResponse {
-    ProjectTransaction transaction = 1;
-}
-
-message PrepareRename {
-    uint64 project_id = 1;
-    uint64 buffer_id = 2;
-    Anchor position = 3;
-    repeated VectorClockEntry version = 4;
-}
-
-message PrepareRenameResponse {
-    bool can_rename = 1;
-    Anchor start = 2;
-    Anchor end = 3;
-    repeated VectorClockEntry version = 4;
-}
-
-message PerformRename {
-    uint64 project_id = 1;
-    uint64 buffer_id = 2;
-    Anchor position = 3;
-    string new_name = 4;
-    repeated VectorClockEntry version = 5;
-}
-
-message OnTypeFormatting {
-    uint64 project_id = 1;
-    uint64 buffer_id = 2;
-    Anchor position = 3;
-    string trigger = 4;
-    repeated VectorClockEntry version = 5;
-}
-
-message OnTypeFormattingResponse {
-    Transaction transaction = 1;
-}
-
-message InlayHints {
-    uint64 project_id = 1;
-    uint64 buffer_id = 2;
-    Anchor start = 3;
-    Anchor end = 4;
-    repeated VectorClockEntry version = 5;
-}
-
-message InlayHintsResponse {
-    repeated InlayHint hints = 1;
-    repeated VectorClockEntry version = 2;
-}
-
-message InlayHint {
-    Anchor position = 1;
-    InlayHintLabel label = 2;
-    optional string kind = 3;
-    bool padding_left = 4;
-    bool padding_right = 5;
-    InlayHintTooltip tooltip = 6;
-    ResolveState resolve_state = 7;
-}
-
-message InlayHintLabel {
-    oneof label {
-        string value = 1;
-        InlayHintLabelParts label_parts = 2;
-    }
-}
-
-message InlayHintLabelParts {
-    repeated InlayHintLabelPart parts = 1;
-}
-
-message InlayHintLabelPart {
-    string value = 1;
-    InlayHintLabelPartTooltip tooltip = 2;
-    optional string location_url = 3;
-    PointUtf16 location_range_start = 4;
-    PointUtf16 location_range_end = 5;
-    optional uint64 language_server_id = 6;
-}
-
-message InlayHintTooltip {
-    oneof content {
-        string value = 1;
-        MarkupContent markup_content = 2;
-    }
-}
-
-message InlayHintLabelPartTooltip {
-    oneof content {
-        string value = 1;
-        MarkupContent markup_content = 2;
-    }
-}
-
-message ResolveState {
-    State state = 1;
-    LspResolveState lsp_resolve_state = 2;
-
-    enum State {
-        Resolved = 0;
-        CanResolve = 1;
-        Resolving = 2;
-    }
-
-    message LspResolveState {
-        string value = 1;
-        uint64 server_id = 2;
-    }
-}
-
-message ResolveCompletionDocumentation {
-    uint64 project_id = 1;
-    uint64 language_server_id = 2;
-    bytes lsp_completion = 3;
-}
-
-message ResolveCompletionDocumentationResponse {
-    string text = 1;
-    bool is_markdown = 2;
-}
-
-message ResolveInlayHint {
-    uint64 project_id = 1;
-    uint64 buffer_id = 2;
-    uint64 language_server_id = 3;
-    InlayHint hint = 4;
-}
-
-message ResolveInlayHintResponse {
-    InlayHint hint = 1;
-}
-
-message RefreshInlayHints {
-    uint64 project_id = 1;
-}
-
-message MarkupContent {
-    bool is_markdown = 1;
-    string value = 2;
-}
-
-message PerformRenameResponse {
-    ProjectTransaction transaction = 2;
-}
-
-message SearchProject {
-    uint64 project_id = 1;
-    string query = 2;
-    bool regex = 3;
-    bool whole_word = 4;
-    bool case_sensitive = 5;
-    string files_to_include = 6;
-    string files_to_exclude = 7;
-    bool include_ignored = 8;
-}
-
-message SearchProjectResponse {
-    repeated Location locations = 1;
-}
-
-message CodeAction {
-    uint64 server_id = 1;
-    Anchor start = 2;
-    Anchor end = 3;
-    bytes lsp_action = 4;
-}
-
-message ProjectTransaction {
-    repeated uint64 buffer_ids = 1;
-    repeated Transaction transactions = 2;
-}
-
-message Transaction {
-    LamportTimestamp id = 1;
-    repeated LamportTimestamp edit_ids = 2;
-    repeated VectorClockEntry start = 3;
-}
-
-message LamportTimestamp {
-    uint32 replica_id = 1;
-    uint32 value = 2;
-}
-
-message LanguageServer {
-    uint64 id = 1;
-    string name = 2;
-}
-
-message StartLanguageServer {
-    uint64 project_id = 1;
-    LanguageServer server = 2;
-}
-
-message UpdateDiagnosticSummary {
-    uint64 project_id = 1;
-    uint64 worktree_id = 2;
-    DiagnosticSummary summary = 3;
-}
-
-message DiagnosticSummary {
-    string path = 1;
-    uint64 language_server_id = 2;
-    uint32 error_count = 3;
-    uint32 warning_count = 4;
-}
-
-message UpdateLanguageServer {
-    uint64 project_id = 1;
-    uint64 language_server_id = 2;
-    oneof variant {
-        LspWorkStart work_start = 3;
-        LspWorkProgress work_progress = 4;
-        LspWorkEnd work_end = 5;
-        LspDiskBasedDiagnosticsUpdating disk_based_diagnostics_updating = 6;
-        LspDiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 7;
-    }
-}
-
-message LspWorkStart {
-    string token = 1;
-    optional string message = 2;
-    optional uint32 percentage = 3;
-}
-
-message LspWorkProgress {
-    string token = 1;
-    optional string message = 2;
-    optional uint32 percentage = 3;
-}
-
-message LspWorkEnd {
-    string token = 1;
-}
-
-message LspDiskBasedDiagnosticsUpdating {}
-
-message LspDiskBasedDiagnosticsUpdated {}
-
-message UpdateChannels {
-    repeated Channel channels = 1;
-    repeated uint64 delete_channels = 4;
-    repeated Channel channel_invitations = 5;
-    repeated uint64 remove_channel_invitations = 6;
-    repeated ChannelParticipants channel_participants = 7;
-    repeated UnseenChannelMessage unseen_channel_messages = 9;
-    repeated UnseenChannelBufferChange unseen_channel_buffer_changes = 10;
-}
-
-message UnseenChannelMessage {
-    uint64 channel_id = 1;
-    uint64 message_id = 2;
-}
-
-message UnseenChannelBufferChange {
-    uint64 channel_id = 1;
-    uint64 epoch = 2;
-    repeated VectorClockEntry version = 3;
-}
-
-message ChannelPermission {
-    uint64 channel_id = 1;
-    ChannelRole role = 3;
-}
-
-message ChannelParticipants {
-    uint64 channel_id = 1;
-    repeated uint64 participant_user_ids = 2;
-}
-
-message JoinChannel {
-    uint64 channel_id = 1;
-}
-
-message DeleteChannel {
-    uint64 channel_id = 1;
-}
-
-message GetChannelMembers {
-    uint64 channel_id = 1;
-}
-
-message GetChannelMembersResponse {
-    repeated ChannelMember members = 1;
-}
-
-message ChannelMember {
-    uint64 user_id = 1;
-    Kind kind = 3;
-    ChannelRole role = 4;
-
-    enum Kind {
-        Member = 0;
-        Invitee = 1;
-        AncestorMember = 2;
-    }
-}
-
-message CreateChannel {
-    string name = 1;
-    optional uint64 parent_id = 2;
-}
-
-message CreateChannelResponse {
-    Channel channel = 1;
-    optional uint64 parent_id = 2;
-}
-
-message InviteChannelMember {
-    uint64 channel_id = 1;
-    uint64 user_id = 2;
-    ChannelRole role = 4;
-}
-
-message RemoveChannelMember {
-    uint64 channel_id = 1;
-    uint64 user_id = 2;
-}
-
-enum ChannelRole {
-    Admin = 0;
-    Member = 1;
-    Guest = 2;
-    Banned = 3;
-}
-
-message SetChannelMemberRole {
-    uint64 channel_id = 1;
-    uint64 user_id = 2;
-    ChannelRole role = 3;
-}
-
-message SetChannelVisibility {
-    uint64 channel_id = 1;
-    ChannelVisibility visibility = 2;
-}
-
-message RenameChannel {
-    uint64 channel_id = 1;
-    string name = 2;
-}
-
-message RenameChannelResponse {
-    Channel channel = 1;
-}
-
-message JoinChannelChat {
-    uint64 channel_id = 1;
-}
-
-message JoinChannelChatResponse {
-    repeated ChannelMessage messages = 1;
-    bool done = 2;
-}
-
-message LeaveChannelChat {
-    uint64 channel_id = 1;
-}
-
-message SendChannelMessage {
-    uint64 channel_id = 1;
-    string body = 2;
-    Nonce nonce = 3;
-    repeated ChatMention mentions = 4;
-}
-
-message RemoveChannelMessage {
-    uint64 channel_id = 1;
-    uint64 message_id = 2;
-}
-
-message AckChannelMessage {
-    uint64 channel_id = 1;
-    uint64 message_id = 2;
-}
-
-message SendChannelMessageResponse {
-    ChannelMessage message = 1;
-}
-
-message ChannelMessageSent {
-    uint64 channel_id = 1;
-    ChannelMessage message = 2;
-}
-
-message GetChannelMessages {
-    uint64 channel_id = 1;
-    uint64 before_message_id = 2;
-}
-
-message GetChannelMessagesResponse {
-    repeated ChannelMessage messages = 1;
-    bool done = 2;
-}
-
-message GetChannelMessagesById {
-    repeated uint64 message_ids = 1;
-}
-
-message MoveChannel {
-    uint64 channel_id = 1;
-    optional uint64 to = 2;
-}
-
-message JoinChannelBuffer {
-    uint64 channel_id = 1;
-}
-
-message ChannelMessage {
-    uint64 id = 1;
-    string body = 2;
-    uint64 timestamp = 3;
-    uint64 sender_id = 4;
-    Nonce nonce = 5;
-    repeated ChatMention mentions = 6;
-}
-
-message ChatMention {
-    Range range = 1;
-    uint64 user_id = 2;
-}
-
-message RejoinChannelBuffers {
-    repeated ChannelBufferVersion buffers = 1;
-}
-
-message RejoinChannelBuffersResponse {
-    repeated RejoinedChannelBuffer buffers = 1;
-}
-
-message AckBufferOperation {
-    uint64 buffer_id = 1;
-    uint64 epoch = 2;
-    repeated VectorClockEntry version = 3;
-}
-
-message JoinChannelBufferResponse {
-    uint64 buffer_id = 1;
-    uint32 replica_id = 2;
-    string base_text = 3;
-    repeated Operation operations = 4;
-    repeated Collaborator collaborators = 5;
-    uint64 epoch = 6;
-}
-
-message RejoinedChannelBuffer {
-    uint64 channel_id = 1;
-    repeated VectorClockEntry version = 2;
-    repeated Operation operations = 3;
-    repeated Collaborator collaborators = 4;
-}
-
-message LeaveChannelBuffer {
-    uint64 channel_id = 1;
-}
-
-message RespondToChannelInvite {
-    uint64 channel_id = 1;
-    bool accept = 2;
-}
-
-message GetUsers {
-    repeated uint64 user_ids = 1;
-}
-
-message FuzzySearchUsers {
-    string query = 1;
-}
-
-message UsersResponse {
-    repeated User users = 1;
-}
-
-message RequestContact {
-    uint64 responder_id = 1;
-}
-
-message RemoveContact {
-    uint64 user_id = 1;
-}
-
-message RespondToContactRequest {
-    uint64 requester_id = 1;
-    ContactRequestResponse response = 2;
-}
-
-enum ContactRequestResponse {
-    Accept = 0;
-    Decline = 1;
-    Block = 2;
-    Dismiss = 3;
-}
-
-message UpdateContacts {
-    repeated Contact contacts = 1;
-    repeated uint64 remove_contacts = 2;
-    repeated IncomingContactRequest incoming_requests = 3;
-    repeated uint64 remove_incoming_requests = 4;
-    repeated uint64 outgoing_requests = 5;
-    repeated uint64 remove_outgoing_requests = 6;
-}
-
-message UpdateInviteInfo {
-    string url = 1;
-    uint32 count = 2;
-}
-
-message ShowContacts {}
-
-message IncomingContactRequest {
-    uint64 requester_id = 1;
-}
-
-message UpdateDiagnostics {
-    uint32 replica_id = 1;
-    uint32 lamport_timestamp = 2;
-    uint64 server_id = 3;
-    repeated Diagnostic diagnostics = 4;
-}
-
-message Follow {
-    uint64 room_id = 1;
-    optional uint64 project_id = 2;
-    PeerId leader_id = 3;
-}
-
-message FollowResponse {
-    optional ViewId active_view_id = 1;
-    repeated View views = 2;
-}
-
-message UpdateFollowers {
-    uint64 room_id = 1;
-    optional uint64 project_id = 2;
-    repeated PeerId follower_ids = 3;
-    oneof variant {
-        UpdateActiveView update_active_view = 4;
-        View create_view = 5;
-        UpdateView update_view = 6;
-    }
-}
-
-message Unfollow {
-    uint64 room_id = 1;
-    optional uint64 project_id = 2;
-    PeerId leader_id = 3;
-}
-
-message GetPrivateUserInfo {}
-
-message GetPrivateUserInfoResponse {
-    string metrics_id = 1;
-    bool staff = 2;
-    repeated string flags = 3;
-}
-
-// Entities
-
-message ViewId {
-    PeerId creator = 1;
-    uint64 id = 2;
-}
-
-message UpdateActiveView {
-    optional ViewId id = 1;
-    optional PeerId leader_id = 2;
-}
-
-message UpdateView {
-    ViewId id = 1;
-    optional PeerId leader_id = 2;
-
-    oneof variant {
-        Editor editor = 3;
-    }
-
-    message Editor {
-        repeated ExcerptInsertion inserted_excerpts = 1;
-        repeated uint64 deleted_excerpts = 2;
-        repeated Selection selections = 3;
-        optional Selection pending_selection = 4;
-        EditorAnchor scroll_top_anchor = 5;
-        float scroll_x = 6;
-        float scroll_y = 7;
-    }
-}
-
-message View {
-    ViewId id = 1;
-    optional PeerId leader_id = 2;
-
-    oneof variant {
-        Editor editor = 3;
-        ChannelView channel_view = 4;
-    }
-
-    message Editor {
-        bool singleton = 1;
-        optional string title = 2;
-        repeated Excerpt excerpts = 3;
-        repeated Selection selections = 4;
-        optional Selection pending_selection = 5;
-        EditorAnchor scroll_top_anchor = 6;
-        float scroll_x = 7;
-        float scroll_y = 8;
-    }
-
-    message ChannelView {
-        uint64 channel_id = 1;
-        Editor editor = 2;
-    }
-}
-
-message Collaborator {
-    PeerId peer_id = 1;
-    uint32 replica_id = 2;
-    uint64 user_id = 3;
-}
-
-message User {
-    uint64 id = 1;
-    string github_login = 2;
-    string avatar_url = 3;
-}
-
-message File {
-    uint64 worktree_id = 1;
-    optional uint64 entry_id = 2;
-    string path = 3;
-    Timestamp mtime = 4;
-    bool is_deleted = 5;
-}
-
-message Entry {
-    uint64 id = 1;
-    bool is_dir = 2;
-    string path = 3;
-    uint64 inode = 4;
-    Timestamp mtime = 5;
-    bool is_symlink = 6;
-    bool is_ignored = 7;
-    bool is_external = 8;
-    optional GitStatus git_status = 9;
-}
-
-message RepositoryEntry {
-    uint64 work_directory_id = 1;
-    optional string branch = 2;
-}
-
-message StatusEntry {
-    string repo_path = 1;
-    GitStatus status = 2;
-}
-
-enum GitStatus {
-    Added = 0;
-    Modified = 1;
-    Conflict = 2;
-}
-
-message BufferState {
-    uint64 id = 1;
-    optional File file = 2;
-    string base_text = 3;
-    optional string diff_base = 4;
-    LineEnding line_ending = 5;
-    repeated VectorClockEntry saved_version = 6;
-    string saved_version_fingerprint = 7;
-    Timestamp saved_mtime = 8;
-}
-
-message BufferChunk {
-    uint64 buffer_id = 1;
-    repeated Operation operations = 2;
-    bool is_last = 3;
-}
-
-enum LineEnding {
-    Unix = 0;
-    Windows = 1;
-}
-
-message Selection {
-    uint64 id = 1;
-    EditorAnchor start = 2;
-    EditorAnchor end = 3;
-    bool reversed = 4;
-}
-
-message EditorAnchor {
-    uint64 excerpt_id = 1;
-    Anchor anchor = 2;
-}
-
-enum CursorShape {
-    CursorBar = 0;
-    CursorBlock = 1;
-    CursorUnderscore = 2;
-    CursorHollow = 3;
-}
-
-message ExcerptInsertion {
-    Excerpt excerpt = 1;
-    optional uint64 previous_excerpt_id = 2;
-}
-
-message Excerpt {
-    uint64 id = 1;
-    uint64 buffer_id = 2;
-    Anchor context_start = 3;
-    Anchor context_end = 4;
-    Anchor primary_start = 5;
-    Anchor primary_end = 6;
-}
-
-message Anchor {
-    uint32 replica_id = 1;
-    uint32 timestamp = 2;
-    uint64 offset = 3;
-    Bias bias = 4;
-    optional uint64 buffer_id = 5;
-}
-
-enum Bias {
-    Left = 0;
-    Right = 1;
-}
-
-message Diagnostic {
-    Anchor start = 1;
-    Anchor end = 2;
-    optional string source = 3;
-    Severity severity = 4;
-    string message = 5;
-    optional string code = 6;
-    uint64 group_id = 7;
-    bool is_primary = 8;
-    bool is_valid = 9;
-    bool is_disk_based = 10;
-    bool is_unnecessary = 11;
-
-    enum Severity {
-        None = 0;
-        Error = 1;
-        Warning = 2;
-        Information = 3;
-        Hint = 4;
-    }
-}
-
-message Operation {
-    oneof variant {
-        Edit edit = 1;
-        Undo undo = 2;
-        UpdateSelections update_selections = 3;
-        UpdateDiagnostics update_diagnostics = 4;
-        UpdateCompletionTriggers update_completion_triggers = 5;
-    }
-
-    message Edit {
-        uint32 replica_id = 1;
-        uint32 lamport_timestamp = 2;
-        repeated VectorClockEntry version = 3;
-        repeated Range ranges = 4;
-        repeated string new_text = 5;
-    }
-
-    message Undo {
-        uint32 replica_id = 1;
-        uint32 lamport_timestamp = 2;
-        repeated VectorClockEntry version = 3;
-        repeated UndoCount counts = 4;
-    }
-
-    message UpdateSelections {
-        uint32 replica_id = 1;
-        uint32 lamport_timestamp = 2;
-        repeated Selection selections = 3;
-        bool line_mode = 4;
-        CursorShape cursor_shape = 5;
-    }
-
-    message UpdateCompletionTriggers {
-        uint32 replica_id = 1;
-        uint32 lamport_timestamp = 2;
-        repeated string triggers = 3;
-    }
-}
-
-message UndoMapEntry {
-    uint32 replica_id = 1;
-    uint32 local_timestamp = 2;
-    repeated UndoCount counts = 3;
-}
-
-message UndoCount {
-    uint32 replica_id = 1;
-    uint32 lamport_timestamp = 2;
-    uint32 count = 3;
-}
-
-message VectorClockEntry {
-    uint32 replica_id = 1;
-    uint32 timestamp = 2;
-}
-
-message Timestamp {
-    uint64 seconds = 1;
-    uint32 nanos = 2;
-}
-
-message Range {
-    uint64 start = 1;
-    uint64 end = 2;
-}
-
-message PointUtf16 {
-    uint32 row = 1;
-    uint32 column = 2;
-}
-
-message Nonce {
-    uint64 upper_half = 1;
-    uint64 lower_half = 2;
-}
-
-enum ChannelVisibility {
-    Public = 0;
-    Members = 1;
-}
-
-message Channel {
-    uint64 id = 1;
-    string name = 2;
-    ChannelVisibility visibility = 3;
-    ChannelRole role = 4;
-    repeated uint64 parent_path = 5;
-}
-
-message Contact {
-    uint64 user_id = 1;
-    bool online = 2;
-    bool busy = 3;
-}
-
-message WorktreeMetadata {
-    uint64 id = 1;
-    string root_name = 2;
-    bool visible = 3;
-    string abs_path = 4;
-}
-
-message UpdateDiffBase {
-    uint64 project_id = 1;
-    uint64 buffer_id = 2;
-    optional string diff_base = 3;
-}
-
-message GetNotifications {
-    optional uint64 before_id = 1;
-}
-
-message AddNotification {
-    Notification notification = 1;
-}
-
-message GetNotificationsResponse {
-    repeated Notification notifications = 1;
-    bool done = 2;
-}
-
-message DeleteNotification {
-    uint64 notification_id = 1;
-}
-
-message MarkNotificationRead {
-    uint64 notification_id = 1;
-}
-
-message Notification {
-    uint64 id = 1;
-    uint64 timestamp = 2;
-    string kind = 3;
-    optional uint64 entity_id = 4;
-    string content = 5;
-    bool is_read = 6;
-    optional bool response = 7;
-}
-
-message LspExtExpandMacro {
-    uint64 project_id = 1;
-    uint64 buffer_id = 2;
-    Anchor position = 3;
-}
-
-message LspExtExpandMacroResponse {
-    string name = 1;
-    string expansion = 2;
-}

crates/rpc2/src/auth.rs 🔗

@@ -1,136 +0,0 @@
-use anyhow::{Context, Result};
-use rand::{thread_rng, Rng as _};
-use rsa::{PublicKey as _, PublicKeyEncoding, RSAPrivateKey, RSAPublicKey};
-use std::convert::TryFrom;
-
-pub struct PublicKey(RSAPublicKey);
-
-pub struct PrivateKey(RSAPrivateKey);
-
-/// Generate a public and private key for asymmetric encryption.
-pub fn keypair() -> Result<(PublicKey, PrivateKey)> {
-    let mut rng = thread_rng();
-    let bits = 1024;
-    let private_key = RSAPrivateKey::new(&mut rng, bits)?;
-    let public_key = RSAPublicKey::from(&private_key);
-    Ok((PublicKey(public_key), PrivateKey(private_key)))
-}
-
-/// Generate a random 64-character base64 string.
-pub fn random_token() -> String {
-    let mut rng = thread_rng();
-    let mut token_bytes = [0; 48];
-    for byte in token_bytes.iter_mut() {
-        *byte = rng.gen();
-    }
-    base64::encode_config(token_bytes, base64::URL_SAFE)
-}
-
-impl PublicKey {
-    /// Convert a string to a base64-encoded string that can only be decoded with the corresponding
-    /// private key.
-    pub fn encrypt_string(&self, string: &str) -> Result<String> {
-        let mut rng = thread_rng();
-        let bytes = string.as_bytes();
-        let encrypted_bytes = self
-            .0
-            .encrypt(&mut rng, PADDING_SCHEME, bytes)
-            .context("failed to encrypt string with public key")?;
-        let encrypted_string = base64::encode_config(&encrypted_bytes, base64::URL_SAFE);
-        Ok(encrypted_string)
-    }
-}
-
-impl PrivateKey {
-    /// Decrypt a base64-encoded string that was encrypted by the corresponding public key.
-    pub fn decrypt_string(&self, encrypted_string: &str) -> Result<String> {
-        let encrypted_bytes = base64::decode_config(encrypted_string, base64::URL_SAFE)
-            .context("failed to base64-decode encrypted string")?;
-        let bytes = self
-            .0
-            .decrypt(PADDING_SCHEME, &encrypted_bytes)
-            .context("failed to decrypt string with private key")?;
-        let string = String::from_utf8(bytes).context("decrypted content was not valid utf8")?;
-        Ok(string)
-    }
-}
-
-impl TryFrom<PublicKey> for String {
-    type Error = anyhow::Error;
-    fn try_from(key: PublicKey) -> Result<Self> {
-        let bytes = key.0.to_pkcs1().context("failed to serialize public key")?;
-        let string = base64::encode_config(&bytes, base64::URL_SAFE);
-        Ok(string)
-    }
-}
-
-impl TryFrom<String> for PublicKey {
-    type Error = anyhow::Error;
-    fn try_from(value: String) -> Result<Self> {
-        let bytes = base64::decode_config(&value, base64::URL_SAFE)
-            .context("failed to base64-decode public key string")?;
-        let key = Self(RSAPublicKey::from_pkcs1(&bytes).context("failed to parse public key")?);
-        Ok(key)
-    }
-}
-
-const PADDING_SCHEME: rsa::PaddingScheme = rsa::PaddingScheme::PKCS1v15Encrypt;
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn test_generate_encrypt_and_decrypt_token() {
-        // CLIENT:
-        // * generate a keypair for asymmetric encryption
-        // * serialize the public key to send it to the server.
-        let (public, private) = keypair().unwrap();
-        let public_string = String::try_from(public).unwrap();
-        assert_printable(&public_string);
-
-        // SERVER:
-        // * parse the public key
-        // * generate a random token.
-        // * encrypt the token using the public key.
-        let public = PublicKey::try_from(public_string).unwrap();
-        let token = random_token();
-        let encrypted_token = public.encrypt_string(&token).unwrap();
-        assert_eq!(token.len(), 64);
-        assert_ne!(encrypted_token, token);
-        assert_printable(&token);
-        assert_printable(&encrypted_token);
-
-        // CLIENT:
-        // * decrypt the token using the private key.
-        let decrypted_token = private.decrypt_string(&encrypted_token).unwrap();
-        assert_eq!(decrypted_token, token);
-    }
-
-    #[test]
-    fn test_tokens_are_always_url_safe() {
-        for _ in 0..5 {
-            let token = random_token();
-            let (public_key, _) = keypair().unwrap();
-            let encrypted_token = public_key.encrypt_string(&token).unwrap();
-            let public_key_str = String::try_from(public_key).unwrap();
-
-            assert_printable(&token);
-            assert_printable(&public_key_str);
-            assert_printable(&encrypted_token);
-        }
-    }
-
-    fn assert_printable(token: &str) {
-        for c in token.chars() {
-            assert!(
-                c.is_ascii_graphic(),
-                "token {:?} has non-printable char {}",
-                token,
-                c
-            );
-            assert_ne!(c, '/', "token {:?} is not URL-safe", token);
-            assert_ne!(c, '&', "token {:?} is not URL-safe", token);
-        }
-    }
-}

crates/rpc2/src/conn.rs 🔗

@@ -1,108 +0,0 @@
-use async_tungstenite::tungstenite::Message as WebSocketMessage;
-use futures::{SinkExt as _, StreamExt as _};
-
-pub struct Connection {
-    pub(crate) tx:
-        Box<dyn 'static + Send + Unpin + futures::Sink<WebSocketMessage, Error = anyhow::Error>>,
-    pub(crate) rx: Box<
-        dyn 'static
-            + Send
-            + Unpin
-            + futures::Stream<Item = Result<WebSocketMessage, anyhow::Error>>,
-    >,
-}
-
-impl Connection {
-    pub fn new<S>(stream: S) -> Self
-    where
-        S: 'static
-            + Send
-            + Unpin
-            + futures::Sink<WebSocketMessage, Error = anyhow::Error>
-            + futures::Stream<Item = Result<WebSocketMessage, anyhow::Error>>,
-    {
-        let (tx, rx) = stream.split();
-        Self {
-            tx: Box::new(tx),
-            rx: Box::new(rx),
-        }
-    }
-
-    pub async fn send(&mut self, message: WebSocketMessage) -> Result<(), anyhow::Error> {
-        self.tx.send(message).await
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn in_memory(
-        executor: gpui::BackgroundExecutor,
-    ) -> (Self, Self, std::sync::Arc<std::sync::atomic::AtomicBool>) {
-        use std::sync::{
-            atomic::{AtomicBool, Ordering::SeqCst},
-            Arc,
-        };
-
-        let killed = Arc::new(AtomicBool::new(false));
-        let (a_tx, a_rx) = channel(killed.clone(), executor.clone());
-        let (b_tx, b_rx) = channel(killed.clone(), executor);
-        return (
-            Self { tx: a_tx, rx: b_rx },
-            Self { tx: b_tx, rx: a_rx },
-            killed,
-        );
-
-        #[allow(clippy::type_complexity)]
-        fn channel(
-            killed: Arc<AtomicBool>,
-            executor: gpui::BackgroundExecutor,
-        ) -> (
-            Box<dyn Send + Unpin + futures::Sink<WebSocketMessage, Error = anyhow::Error>>,
-            Box<dyn Send + Unpin + futures::Stream<Item = Result<WebSocketMessage, anyhow::Error>>>,
-        ) {
-            use anyhow::anyhow;
-            use futures::channel::mpsc;
-            use std::io::{Error, ErrorKind};
-
-            let (tx, rx) = mpsc::unbounded::<WebSocketMessage>();
-
-            let tx = tx.sink_map_err(|error| anyhow!(error)).with({
-                let killed = killed.clone();
-                let executor = executor.clone();
-                move |msg| {
-                    let killed = killed.clone();
-                    let executor = executor.clone();
-                    Box::pin(async move {
-                        executor.simulate_random_delay().await;
-
-                        // Writes to a half-open TCP connection will error.
-                        if killed.load(SeqCst) {
-                            std::io::Result::Err(Error::new(ErrorKind::Other, "connection lost"))?;
-                        }
-
-                        Ok(msg)
-                    })
-                }
-            });
-
-            let rx = rx.then({
-                let killed = killed;
-                let executor = executor.clone();
-                move |msg| {
-                    let killed = killed.clone();
-                    let executor = executor.clone();
-                    Box::pin(async move {
-                        executor.simulate_random_delay().await;
-
-                        // Reads from a half-open TCP connection will hang.
-                        if killed.load(SeqCst) {
-                            futures::future::pending::<()>().await;
-                        }
-
-                        Ok(msg)
-                    })
-                }
-            });
-
-            (Box::new(tx), Box::new(rx))
-        }
-    }
-}

crates/rpc2/src/macros.rs 🔗

@@ -1,70 +0,0 @@
-#[macro_export]
-macro_rules! messages {
-    ($(($name:ident, $priority:ident)),* $(,)?) => {
-        pub fn build_typed_envelope(sender_id: ConnectionId, envelope: Envelope) -> Option<Box<dyn AnyTypedEnvelope>> {
-            match envelope.payload {
-                $(Some(envelope::Payload::$name(payload)) => {
-                    Some(Box::new(TypedEnvelope {
-                        sender_id,
-                        original_sender_id: envelope.original_sender_id.map(|original_sender| PeerId {
-                            owner_id: original_sender.owner_id,
-                            id: original_sender.id
-                        }),
-                        message_id: envelope.id,
-                        payload,
-                    }))
-                }, )*
-                _ => None
-            }
-        }
-
-        $(
-            impl EnvelopedMessage for $name {
-                const NAME: &'static str = std::stringify!($name);
-                const PRIORITY: MessagePriority = MessagePriority::$priority;
-
-                fn into_envelope(
-                    self,
-                    id: u32,
-                    responding_to: Option<u32>,
-                    original_sender_id: Option<PeerId>,
-                ) -> Envelope {
-                    Envelope {
-                        id,
-                        responding_to,
-                        original_sender_id,
-                        payload: Some(envelope::Payload::$name(self)),
-                    }
-                }
-
-                fn from_envelope(envelope: Envelope) -> Option<Self> {
-                    if let Some(envelope::Payload::$name(msg)) = envelope.payload {
-                        Some(msg)
-                    } else {
-                        None
-                    }
-                }
-            }
-        )*
-    };
-}
-
-#[macro_export]
-macro_rules! request_messages {
-    ($(($request_name:ident, $response_name:ident)),* $(,)?) => {
-        $(impl RequestMessage for $request_name {
-            type Response = $response_name;
-        })*
-    };
-}
-
-#[macro_export]
-macro_rules! entity_messages {
-    ($id_field:ident, $($name:ident),* $(,)?) => {
-        $(impl EntityMessage for $name {
-            fn remote_entity_id(&self) -> u64 {
-                self.$id_field
-            }
-        })*
-    };
-}

crates/rpc2/src/notification.rs 🔗

@@ -1,105 +0,0 @@
-use crate::proto;
-use serde::{Deserialize, Serialize};
-use serde_json::{map, Value};
-use strum::{EnumVariantNames, VariantNames as _};
-
-const KIND: &'static str = "kind";
-const ENTITY_ID: &'static str = "entity_id";
-
-/// A notification that can be stored, associated with a given recipient.
-///
-/// This struct is stored in the collab database as JSON, so it shouldn't be
-/// changed in a backward-incompatible way. For example, when renaming a
-/// variant, add a serde alias for the old name.
-///
-/// Most notification types have a special field which is aliased to
-/// `entity_id`. This field is stored in its own database column, and can
-/// be used to query the notification.
-#[derive(Debug, Clone, PartialEq, Eq, EnumVariantNames, Serialize, Deserialize)]
-#[serde(tag = "kind")]
-pub enum Notification {
-    ContactRequest {
-        #[serde(rename = "entity_id")]
-        sender_id: u64,
-    },
-    ContactRequestAccepted {
-        #[serde(rename = "entity_id")]
-        responder_id: u64,
-    },
-    ChannelInvitation {
-        #[serde(rename = "entity_id")]
-        channel_id: u64,
-        channel_name: String,
-        inviter_id: u64,
-    },
-    ChannelMessageMention {
-        #[serde(rename = "entity_id")]
-        message_id: u64,
-        sender_id: u64,
-        channel_id: u64,
-    },
-}
-
-impl Notification {
-    pub fn to_proto(&self) -> proto::Notification {
-        let mut value = serde_json::to_value(self).unwrap();
-        let mut entity_id = None;
-        let value = value.as_object_mut().unwrap();
-        let Some(Value::String(kind)) = value.remove(KIND) else {
-            unreachable!("kind is the enum tag")
-        };
-        if let map::Entry::Occupied(e) = value.entry(ENTITY_ID) {
-            if e.get().is_u64() {
-                entity_id = e.remove().as_u64();
-            }
-        }
-        proto::Notification {
-            kind,
-            entity_id,
-            content: serde_json::to_string(&value).unwrap(),
-            ..Default::default()
-        }
-    }
-
-    pub fn from_proto(notification: &proto::Notification) -> Option<Self> {
-        let mut value = serde_json::from_str::<Value>(&notification.content).ok()?;
-        let object = value.as_object_mut()?;
-        object.insert(KIND.into(), notification.kind.to_string().into());
-        if let Some(entity_id) = notification.entity_id {
-            object.insert(ENTITY_ID.into(), entity_id.into());
-        }
-        serde_json::from_value(value).ok()
-    }
-
-    pub fn all_variant_names() -> &'static [&'static str] {
-        Self::VARIANTS
-    }
-}
-
-#[test]
-fn test_notification() {
-    // Notifications can be serialized and deserialized.
-    for notification in [
-        Notification::ContactRequest { sender_id: 1 },
-        Notification::ContactRequestAccepted { responder_id: 2 },
-        Notification::ChannelInvitation {
-            channel_id: 100,
-            channel_name: "the-channel".into(),
-            inviter_id: 50,
-        },
-        Notification::ChannelMessageMention {
-            sender_id: 200,
-            channel_id: 30,
-            message_id: 1,
-        },
-    ] {
-        let message = notification.to_proto();
-        let deserialized = Notification::from_proto(&message).unwrap();
-        assert_eq!(deserialized, notification);
-    }
-
-    // When notifications are serialized, the `kind` and `actor_id` fields are
-    // stored separately, and do not appear redundantly in the JSON.
-    let notification = Notification::ContactRequest { sender_id: 1 };
-    assert_eq!(notification.to_proto().content, "{}");
-}

crates/rpc2/src/peer.rs 🔗

@@ -1,934 +0,0 @@
-use super::{
-    proto::{self, AnyTypedEnvelope, EnvelopedMessage, MessageStream, PeerId, RequestMessage},
-    Connection,
-};
-use anyhow::{anyhow, Context, Result};
-use collections::HashMap;
-use futures::{
-    channel::{mpsc, oneshot},
-    stream::BoxStream,
-    FutureExt, SinkExt, StreamExt, TryFutureExt,
-};
-use parking_lot::{Mutex, RwLock};
-use serde::{ser::SerializeStruct, Serialize};
-use std::{fmt, sync::atomic::Ordering::SeqCst};
-use std::{
-    future::Future,
-    marker::PhantomData,
-    sync::{
-        atomic::{self, AtomicU32},
-        Arc,
-    },
-    time::Duration,
-};
-use tracing::instrument;
-
-#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize)]
-pub struct ConnectionId {
-    pub owner_id: u32,
-    pub id: u32,
-}
-
-impl Into<PeerId> for ConnectionId {
-    fn into(self) -> PeerId {
-        PeerId {
-            owner_id: self.owner_id,
-            id: self.id,
-        }
-    }
-}
-
-impl From<PeerId> for ConnectionId {
-    fn from(peer_id: PeerId) -> Self {
-        Self {
-            owner_id: peer_id.owner_id,
-            id: peer_id.id,
-        }
-    }
-}
-
-impl fmt::Display for ConnectionId {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "{}/{}", self.owner_id, self.id)
-    }
-}
-
-pub struct Receipt<T> {
-    pub sender_id: ConnectionId,
-    pub message_id: u32,
-    payload_type: PhantomData<T>,
-}
-
-impl<T> Clone for Receipt<T> {
-    fn clone(&self) -> Self {
-        Self {
-            sender_id: self.sender_id,
-            message_id: self.message_id,
-            payload_type: PhantomData,
-        }
-    }
-}
-
-impl<T> Copy for Receipt<T> {}
-
-#[derive(Clone, Debug)]
-pub struct TypedEnvelope<T> {
-    pub sender_id: ConnectionId,
-    pub original_sender_id: Option<PeerId>,
-    pub message_id: u32,
-    pub payload: T,
-}
-
-impl<T> TypedEnvelope<T> {
-    pub fn original_sender_id(&self) -> Result<PeerId> {
-        self.original_sender_id
-            .ok_or_else(|| anyhow!("missing original_sender_id"))
-    }
-}
-
-impl<T: RequestMessage> TypedEnvelope<T> {
-    pub fn receipt(&self) -> Receipt<T> {
-        Receipt {
-            sender_id: self.sender_id,
-            message_id: self.message_id,
-            payload_type: PhantomData,
-        }
-    }
-}
-
-pub struct Peer {
-    epoch: AtomicU32,
-    pub connections: RwLock<HashMap<ConnectionId, ConnectionState>>,
-    next_connection_id: AtomicU32,
-}
-
-#[derive(Clone, Serialize)]
-pub struct ConnectionState {
-    #[serde(skip)]
-    outgoing_tx: mpsc::UnboundedSender<proto::Message>,
-    next_message_id: Arc<AtomicU32>,
-    #[allow(clippy::type_complexity)]
-    #[serde(skip)]
-    response_channels:
-        Arc<Mutex<Option<HashMap<u32, oneshot::Sender<(proto::Envelope, oneshot::Sender<()>)>>>>>,
-}
-
-const KEEPALIVE_INTERVAL: Duration = Duration::from_secs(1);
-const WRITE_TIMEOUT: Duration = Duration::from_secs(2);
-pub const RECEIVE_TIMEOUT: Duration = Duration::from_secs(10);
-
-impl Peer {
-    pub fn new(epoch: u32) -> Arc<Self> {
-        Arc::new(Self {
-            epoch: AtomicU32::new(epoch),
-            connections: Default::default(),
-            next_connection_id: Default::default(),
-        })
-    }
-
-    pub fn epoch(&self) -> u32 {
-        self.epoch.load(SeqCst)
-    }
-
-    #[instrument(skip_all)]
-    pub fn add_connection<F, Fut, Out>(
-        self: &Arc<Self>,
-        connection: Connection,
-        create_timer: F,
-    ) -> (
-        ConnectionId,
-        impl Future<Output = anyhow::Result<()>> + Send,
-        BoxStream<'static, Box<dyn AnyTypedEnvelope>>,
-    )
-    where
-        F: Send + Fn(Duration) -> Fut,
-        Fut: Send + Future<Output = Out>,
-        Out: Send,
-    {
-        // For outgoing messages, use an unbounded channel so that application code
-        // can always send messages without yielding. For incoming messages, use a
-        // bounded channel so that other peers will receive backpressure if they send
-        // messages faster than this peer can process them.
-        #[cfg(any(test, feature = "test-support"))]
-        const INCOMING_BUFFER_SIZE: usize = 1;
-        #[cfg(not(any(test, feature = "test-support")))]
-        const INCOMING_BUFFER_SIZE: usize = 64;
-        let (mut incoming_tx, incoming_rx) = mpsc::channel(INCOMING_BUFFER_SIZE);
-        let (outgoing_tx, mut outgoing_rx) = mpsc::unbounded();
-
-        let connection_id = ConnectionId {
-            owner_id: self.epoch.load(SeqCst),
-            id: self.next_connection_id.fetch_add(1, SeqCst),
-        };
-        let connection_state = ConnectionState {
-            outgoing_tx,
-            next_message_id: Default::default(),
-            response_channels: Arc::new(Mutex::new(Some(Default::default()))),
-        };
-        let mut writer = MessageStream::new(connection.tx);
-        let mut reader = MessageStream::new(connection.rx);
-
-        let this = self.clone();
-        let response_channels = connection_state.response_channels.clone();
-        let handle_io = async move {
-            tracing::trace!(%connection_id, "handle io future: start");
-
-            let _end_connection = util::defer(|| {
-                response_channels.lock().take();
-                this.connections.write().remove(&connection_id);
-                tracing::trace!(%connection_id, "handle io future: end");
-            });
-
-            // Send messages on this frequency so the connection isn't closed.
-            let keepalive_timer = create_timer(KEEPALIVE_INTERVAL).fuse();
-            futures::pin_mut!(keepalive_timer);
-
-            // Disconnect if we don't receive messages at least this frequently.
-            let receive_timeout = create_timer(RECEIVE_TIMEOUT).fuse();
-            futures::pin_mut!(receive_timeout);
-
-            loop {
-                tracing::trace!(%connection_id, "outer loop iteration start");
-                let read_message = reader.read().fuse();
-                futures::pin_mut!(read_message);
-
-                loop {
-                    tracing::trace!(%connection_id, "inner loop iteration start");
-                    futures::select_biased! {
-                        outgoing = outgoing_rx.next().fuse() => match outgoing {
-                            Some(outgoing) => {
-                                tracing::trace!(%connection_id, "outgoing rpc message: writing");
-                                futures::select_biased! {
-                                    result = writer.write(outgoing).fuse() => {
-                                        tracing::trace!(%connection_id, "outgoing rpc message: done writing");
-                                        result.context("failed to write RPC message")?;
-                                        tracing::trace!(%connection_id, "keepalive interval: resetting after sending message");
-                                        keepalive_timer.set(create_timer(KEEPALIVE_INTERVAL).fuse());
-                                    }
-                                    _ = create_timer(WRITE_TIMEOUT).fuse() => {
-                                        tracing::trace!(%connection_id, "outgoing rpc message: writing timed out");
-                                        Err(anyhow!("timed out writing message"))?;
-                                    }
-                                }
-                            }
-                            None => {
-                                tracing::trace!(%connection_id, "outgoing rpc message: channel closed");
-                                return Ok(())
-                            },
-                        },
-                        _ = keepalive_timer => {
-                            tracing::trace!(%connection_id, "keepalive interval: pinging");
-                            futures::select_biased! {
-                                result = writer.write(proto::Message::Ping).fuse() => {
-                                    tracing::trace!(%connection_id, "keepalive interval: done pinging");
-                                    result.context("failed to send keepalive")?;
-                                    tracing::trace!(%connection_id, "keepalive interval: resetting after pinging");
-                                    keepalive_timer.set(create_timer(KEEPALIVE_INTERVAL).fuse());
-                                }
-                                _ = create_timer(WRITE_TIMEOUT).fuse() => {
-                                    tracing::trace!(%connection_id, "keepalive interval: pinging timed out");
-                                    Err(anyhow!("timed out sending keepalive"))?;
-                                }
-                            }
-                        }
-                        incoming = read_message => {
-                            let incoming = incoming.context("error reading rpc message from socket")?;
-                            tracing::trace!(%connection_id, "incoming rpc message: received");
-                            tracing::trace!(%connection_id, "receive timeout: resetting");
-                            receive_timeout.set(create_timer(RECEIVE_TIMEOUT).fuse());
-                            if let proto::Message::Envelope(incoming) = incoming {
-                                tracing::trace!(%connection_id, "incoming rpc message: processing");
-                                futures::select_biased! {
-                                    result = incoming_tx.send(incoming).fuse() => match result {
-                                        Ok(_) => {
-                                            tracing::trace!(%connection_id, "incoming rpc message: processed");
-                                        }
-                                        Err(_) => {
-                                            tracing::trace!(%connection_id, "incoming rpc message: channel closed");
-                                            return Ok(())
-                                        }
-                                    },
-                                    _ = create_timer(WRITE_TIMEOUT).fuse() => {
-                                        tracing::trace!(%connection_id, "incoming rpc message: processing timed out");
-                                        Err(anyhow!("timed out processing incoming message"))?
-                                    }
-                                }
-                            }
-                            break;
-                        },
-                        _ = receive_timeout => {
-                            tracing::trace!(%connection_id, "receive timeout: delay between messages too long");
-                            Err(anyhow!("delay between messages too long"))?
-                        }
-                    }
-                }
-            }
-        };
-
-        let response_channels = connection_state.response_channels.clone();
-        self.connections
-            .write()
-            .insert(connection_id, connection_state);
-
-        let incoming_rx = incoming_rx.filter_map(move |incoming| {
-            let response_channels = response_channels.clone();
-            async move {
-                let message_id = incoming.id;
-                tracing::trace!(?incoming, "incoming message future: start");
-                let _end = util::defer(move || {
-                    tracing::trace!(%connection_id, message_id, "incoming message future: end");
-                });
-
-                if let Some(responding_to) = incoming.responding_to {
-                    tracing::trace!(
-                        %connection_id,
-                        message_id,
-                        responding_to,
-                        "incoming response: received"
-                    );
-                    let channel = response_channels.lock().as_mut()?.remove(&responding_to);
-                    if let Some(tx) = channel {
-                        let requester_resumed = oneshot::channel();
-                        if let Err(error) = tx.send((incoming, requester_resumed.0)) {
-                            tracing::trace!(
-                                %connection_id,
-                                message_id,
-                                responding_to = responding_to,
-                                ?error,
-                                "incoming response: request future dropped",
-                            );
-                        }
-
-                        tracing::trace!(
-                            %connection_id,
-                            message_id,
-                            responding_to,
-                            "incoming response: waiting to resume requester"
-                        );
-                        let _ = requester_resumed.1.await;
-                        tracing::trace!(
-                            %connection_id,
-                            message_id,
-                            responding_to,
-                            "incoming response: requester resumed"
-                        );
-                    } else {
-                        tracing::warn!(
-                            %connection_id,
-                            message_id,
-                            responding_to,
-                            "incoming response: unknown request"
-                        );
-                    }
-
-                    None
-                } else {
-                    tracing::trace!(%connection_id, message_id, "incoming message: received");
-                    proto::build_typed_envelope(connection_id, incoming).or_else(|| {
-                        tracing::error!(
-                            %connection_id,
-                            message_id,
-                            "unable to construct a typed envelope"
-                        );
-                        None
-                    })
-                }
-            }
-        });
-        (connection_id, handle_io, incoming_rx.boxed())
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn add_test_connection(
-        self: &Arc<Self>,
-        connection: Connection,
-        executor: gpui::BackgroundExecutor,
-    ) -> (
-        ConnectionId,
-        impl Future<Output = anyhow::Result<()>> + Send,
-        BoxStream<'static, Box<dyn AnyTypedEnvelope>>,
-    ) {
-        let executor = executor.clone();
-        self.add_connection(connection, move |duration| executor.timer(duration))
-    }
-
-    pub fn disconnect(&self, connection_id: ConnectionId) {
-        self.connections.write().remove(&connection_id);
-    }
-
-    pub fn reset(&self, epoch: u32) {
-        self.teardown();
-        self.next_connection_id.store(0, SeqCst);
-        self.epoch.store(epoch, SeqCst);
-    }
-
-    pub fn teardown(&self) {
-        self.connections.write().clear();
-    }
-
-    pub fn request<T: RequestMessage>(
-        &self,
-        receiver_id: ConnectionId,
-        request: T,
-    ) -> impl Future<Output = Result<T::Response>> {
-        self.request_internal(None, receiver_id, request)
-            .map_ok(|envelope| envelope.payload)
-    }
-
-    pub fn request_envelope<T: RequestMessage>(
-        &self,
-        receiver_id: ConnectionId,
-        request: T,
-    ) -> impl Future<Output = Result<TypedEnvelope<T::Response>>> {
-        self.request_internal(None, receiver_id, request)
-    }
-
-    pub fn forward_request<T: RequestMessage>(
-        &self,
-        sender_id: ConnectionId,
-        receiver_id: ConnectionId,
-        request: T,
-    ) -> impl Future<Output = Result<T::Response>> {
-        self.request_internal(Some(sender_id), receiver_id, request)
-            .map_ok(|envelope| envelope.payload)
-    }
-
-    pub fn request_internal<T: RequestMessage>(
-        &self,
-        original_sender_id: Option<ConnectionId>,
-        receiver_id: ConnectionId,
-        request: T,
-    ) -> impl Future<Output = Result<TypedEnvelope<T::Response>>> {
-        let (tx, rx) = oneshot::channel();
-        let send = self.connection_state(receiver_id).and_then(|connection| {
-            let message_id = connection.next_message_id.fetch_add(1, SeqCst);
-            connection
-                .response_channels
-                .lock()
-                .as_mut()
-                .ok_or_else(|| anyhow!("connection was closed"))?
-                .insert(message_id, tx);
-            connection
-                .outgoing_tx
-                .unbounded_send(proto::Message::Envelope(request.into_envelope(
-                    message_id,
-                    None,
-                    original_sender_id.map(Into::into),
-                )))
-                .map_err(|_| anyhow!("connection was closed"))?;
-            Ok(())
-        });
-        async move {
-            send?;
-            let (response, _barrier) = rx.await.map_err(|_| anyhow!("connection was closed"))?;
-
-            if let Some(proto::envelope::Payload::Error(error)) = &response.payload {
-                Err(anyhow!(
-                    "RPC request {} failed - {}",
-                    T::NAME,
-                    error.message
-                ))
-            } else {
-                Ok(TypedEnvelope {
-                    message_id: response.id,
-                    sender_id: receiver_id,
-                    original_sender_id: response.original_sender_id,
-                    payload: T::Response::from_envelope(response)
-                        .ok_or_else(|| anyhow!("received response of the wrong type"))?,
-                })
-            }
-        }
-    }
-
-    pub fn send<T: EnvelopedMessage>(&self, receiver_id: ConnectionId, message: T) -> Result<()> {
-        let connection = self.connection_state(receiver_id)?;
-        let message_id = connection
-            .next_message_id
-            .fetch_add(1, atomic::Ordering::SeqCst);
-        connection
-            .outgoing_tx
-            .unbounded_send(proto::Message::Envelope(
-                message.into_envelope(message_id, None, None),
-            ))?;
-        Ok(())
-    }
-
-    pub fn forward_send<T: EnvelopedMessage>(
-        &self,
-        sender_id: ConnectionId,
-        receiver_id: ConnectionId,
-        message: T,
-    ) -> Result<()> {
-        let connection = self.connection_state(receiver_id)?;
-        let message_id = connection
-            .next_message_id
-            .fetch_add(1, atomic::Ordering::SeqCst);
-        connection
-            .outgoing_tx
-            .unbounded_send(proto::Message::Envelope(message.into_envelope(
-                message_id,
-                None,
-                Some(sender_id.into()),
-            )))?;
-        Ok(())
-    }
-
-    pub fn respond<T: RequestMessage>(
-        &self,
-        receipt: Receipt<T>,
-        response: T::Response,
-    ) -> Result<()> {
-        let connection = self.connection_state(receipt.sender_id)?;
-        let message_id = connection
-            .next_message_id
-            .fetch_add(1, atomic::Ordering::SeqCst);
-        connection
-            .outgoing_tx
-            .unbounded_send(proto::Message::Envelope(response.into_envelope(
-                message_id,
-                Some(receipt.message_id),
-                None,
-            )))?;
-        Ok(())
-    }
-
-    pub fn respond_with_error<T: RequestMessage>(
-        &self,
-        receipt: Receipt<T>,
-        response: proto::Error,
-    ) -> Result<()> {
-        let connection = self.connection_state(receipt.sender_id)?;
-        let message_id = connection
-            .next_message_id
-            .fetch_add(1, atomic::Ordering::SeqCst);
-        connection
-            .outgoing_tx
-            .unbounded_send(proto::Message::Envelope(response.into_envelope(
-                message_id,
-                Some(receipt.message_id),
-                None,
-            )))?;
-        Ok(())
-    }
-
-    pub fn respond_with_unhandled_message(
-        &self,
-        envelope: Box<dyn AnyTypedEnvelope>,
-    ) -> Result<()> {
-        let connection = self.connection_state(envelope.sender_id())?;
-        let response = proto::Error {
-            message: format!("message {} was not handled", envelope.payload_type_name()),
-        };
-        let message_id = connection
-            .next_message_id
-            .fetch_add(1, atomic::Ordering::SeqCst);
-        connection
-            .outgoing_tx
-            .unbounded_send(proto::Message::Envelope(response.into_envelope(
-                message_id,
-                Some(envelope.message_id()),
-                None,
-            )))?;
-        Ok(())
-    }
-
-    fn connection_state(&self, connection_id: ConnectionId) -> Result<ConnectionState> {
-        let connections = self.connections.read();
-        let connection = connections
-            .get(&connection_id)
-            .ok_or_else(|| anyhow!("no such connection: {}", connection_id))?;
-        Ok(connection.clone())
-    }
-}
-
-impl Serialize for Peer {
-    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
-    where
-        S: serde::Serializer,
-    {
-        let mut state = serializer.serialize_struct("Peer", 2)?;
-        state.serialize_field("connections", &*self.connections.read())?;
-        state.end()
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::TypedEnvelope;
-    use async_tungstenite::tungstenite::Message as WebSocketMessage;
-    use gpui::TestAppContext;
-
-    fn init_logger() {
-        if std::env::var("RUST_LOG").is_ok() {
-            env_logger::init();
-        }
-    }
-
-    #[gpui::test(iterations = 50)]
-    async fn test_request_response(cx: &mut TestAppContext) {
-        init_logger();
-
-        let executor = cx.executor();
-
-        // create 2 clients connected to 1 server
-        let server = Peer::new(0);
-        let client1 = Peer::new(0);
-        let client2 = Peer::new(0);
-
-        let (client1_to_server_conn, server_to_client_1_conn, _kill) =
-            Connection::in_memory(cx.executor());
-        let (client1_conn_id, io_task1, client1_incoming) =
-            client1.add_test_connection(client1_to_server_conn, cx.executor());
-        let (_, io_task2, server_incoming1) =
-            server.add_test_connection(server_to_client_1_conn, cx.executor());
-
-        let (client2_to_server_conn, server_to_client_2_conn, _kill) =
-            Connection::in_memory(cx.executor());
-        let (client2_conn_id, io_task3, client2_incoming) =
-            client2.add_test_connection(client2_to_server_conn, cx.executor());
-        let (_, io_task4, server_incoming2) =
-            server.add_test_connection(server_to_client_2_conn, cx.executor());
-
-        executor.spawn(io_task1).detach();
-        executor.spawn(io_task2).detach();
-        executor.spawn(io_task3).detach();
-        executor.spawn(io_task4).detach();
-        executor
-            .spawn(handle_messages(server_incoming1, server.clone()))
-            .detach();
-        executor
-            .spawn(handle_messages(client1_incoming, client1.clone()))
-            .detach();
-        executor
-            .spawn(handle_messages(server_incoming2, server.clone()))
-            .detach();
-        executor
-            .spawn(handle_messages(client2_incoming, client2.clone()))
-            .detach();
-
-        assert_eq!(
-            client1
-                .request(client1_conn_id, proto::Ping {},)
-                .await
-                .unwrap(),
-            proto::Ack {}
-        );
-
-        assert_eq!(
-            client2
-                .request(client2_conn_id, proto::Ping {},)
-                .await
-                .unwrap(),
-            proto::Ack {}
-        );
-
-        assert_eq!(
-            client1
-                .request(client1_conn_id, proto::Test { id: 1 },)
-                .await
-                .unwrap(),
-            proto::Test { id: 1 }
-        );
-
-        assert_eq!(
-            client2
-                .request(client2_conn_id, proto::Test { id: 2 })
-                .await
-                .unwrap(),
-            proto::Test { id: 2 }
-        );
-
-        client1.disconnect(client1_conn_id);
-        client2.disconnect(client1_conn_id);
-
-        async fn handle_messages(
-            mut messages: BoxStream<'static, Box<dyn AnyTypedEnvelope>>,
-            peer: Arc<Peer>,
-        ) -> Result<()> {
-            while let Some(envelope) = messages.next().await {
-                let envelope = envelope.into_any();
-                if let Some(envelope) = envelope.downcast_ref::<TypedEnvelope<proto::Ping>>() {
-                    let receipt = envelope.receipt();
-                    peer.respond(receipt, proto::Ack {})?
-                } else if let Some(envelope) = envelope.downcast_ref::<TypedEnvelope<proto::Test>>()
-                {
-                    peer.respond(envelope.receipt(), envelope.payload.clone())?
-                } else {
-                    panic!("unknown message type");
-                }
-            }
-
-            Ok(())
-        }
-    }
-
-    #[gpui::test(iterations = 50)]
-    async fn test_order_of_response_and_incoming(cx: &mut TestAppContext) {
-        let executor = cx.executor();
-        let server = Peer::new(0);
-        let client = Peer::new(0);
-
-        let (client_to_server_conn, server_to_client_conn, _kill) =
-            Connection::in_memory(executor.clone());
-        let (client_to_server_conn_id, io_task1, mut client_incoming) =
-            client.add_test_connection(client_to_server_conn, executor.clone());
-
-        let (server_to_client_conn_id, io_task2, mut server_incoming) =
-            server.add_test_connection(server_to_client_conn, executor.clone());
-
-        executor.spawn(io_task1).detach();
-        executor.spawn(io_task2).detach();
-
-        executor
-            .spawn(async move {
-                let future = server_incoming.next().await;
-                let request = future
-                    .unwrap()
-                    .into_any()
-                    .downcast::<TypedEnvelope<proto::Ping>>()
-                    .unwrap();
-
-                server
-                    .send(
-                        server_to_client_conn_id,
-                        proto::Error {
-                            message: "message 1".to_string(),
-                        },
-                    )
-                    .unwrap();
-                server
-                    .send(
-                        server_to_client_conn_id,
-                        proto::Error {
-                            message: "message 2".to_string(),
-                        },
-                    )
-                    .unwrap();
-                server.respond(request.receipt(), proto::Ack {}).unwrap();
-
-                // Prevent the connection from being dropped
-                server_incoming.next().await;
-            })
-            .detach();
-
-        let events = Arc::new(Mutex::new(Vec::new()));
-
-        let response = client.request(client_to_server_conn_id, proto::Ping {});
-        let response_task = executor.spawn({
-            let events = events.clone();
-            async move {
-                response.await.unwrap();
-                events.lock().push("response".to_string());
-            }
-        });
-
-        executor
-            .spawn({
-                let events = events.clone();
-                async move {
-                    let incoming1 = client_incoming
-                        .next()
-                        .await
-                        .unwrap()
-                        .into_any()
-                        .downcast::<TypedEnvelope<proto::Error>>()
-                        .unwrap();
-                    events.lock().push(incoming1.payload.message);
-                    let incoming2 = client_incoming
-                        .next()
-                        .await
-                        .unwrap()
-                        .into_any()
-                        .downcast::<TypedEnvelope<proto::Error>>()
-                        .unwrap();
-                    events.lock().push(incoming2.payload.message);
-
-                    // Prevent the connection from being dropped
-                    client_incoming.next().await;
-                }
-            })
-            .detach();
-
-        response_task.await;
-        assert_eq!(
-            &*events.lock(),
-            &[
-                "message 1".to_string(),
-                "message 2".to_string(),
-                "response".to_string()
-            ]
-        );
-    }
-
-    #[gpui::test(iterations = 50)]
-    async fn test_dropping_request_before_completion(cx: &mut TestAppContext) {
-        let executor = cx.executor();
-        let server = Peer::new(0);
-        let client = Peer::new(0);
-
-        let (client_to_server_conn, server_to_client_conn, _kill) =
-            Connection::in_memory(cx.executor());
-        let (client_to_server_conn_id, io_task1, mut client_incoming) =
-            client.add_test_connection(client_to_server_conn, cx.executor());
-        let (server_to_client_conn_id, io_task2, mut server_incoming) =
-            server.add_test_connection(server_to_client_conn, cx.executor());
-
-        executor.spawn(io_task1).detach();
-        executor.spawn(io_task2).detach();
-
-        executor
-            .spawn(async move {
-                let request1 = server_incoming
-                    .next()
-                    .await
-                    .unwrap()
-                    .into_any()
-                    .downcast::<TypedEnvelope<proto::Ping>>()
-                    .unwrap();
-                let request2 = server_incoming
-                    .next()
-                    .await
-                    .unwrap()
-                    .into_any()
-                    .downcast::<TypedEnvelope<proto::Ping>>()
-                    .unwrap();
-
-                server
-                    .send(
-                        server_to_client_conn_id,
-                        proto::Error {
-                            message: "message 1".to_string(),
-                        },
-                    )
-                    .unwrap();
-                server
-                    .send(
-                        server_to_client_conn_id,
-                        proto::Error {
-                            message: "message 2".to_string(),
-                        },
-                    )
-                    .unwrap();
-                server.respond(request1.receipt(), proto::Ack {}).unwrap();
-                server.respond(request2.receipt(), proto::Ack {}).unwrap();
-
-                // Prevent the connection from being dropped
-                server_incoming.next().await;
-            })
-            .detach();
-
-        let events = Arc::new(Mutex::new(Vec::new()));
-
-        let request1 = client.request(client_to_server_conn_id, proto::Ping {});
-        let request1_task = executor.spawn(request1);
-        let request2 = client.request(client_to_server_conn_id, proto::Ping {});
-        let request2_task = executor.spawn({
-            let events = events.clone();
-            async move {
-                request2.await.unwrap();
-                events.lock().push("response 2".to_string());
-            }
-        });
-
-        executor
-            .spawn({
-                let events = events.clone();
-                async move {
-                    let incoming1 = client_incoming
-                        .next()
-                        .await
-                        .unwrap()
-                        .into_any()
-                        .downcast::<TypedEnvelope<proto::Error>>()
-                        .unwrap();
-                    events.lock().push(incoming1.payload.message);
-                    let incoming2 = client_incoming
-                        .next()
-                        .await
-                        .unwrap()
-                        .into_any()
-                        .downcast::<TypedEnvelope<proto::Error>>()
-                        .unwrap();
-                    events.lock().push(incoming2.payload.message);
-
-                    // Prevent the connection from being dropped
-                    client_incoming.next().await;
-                }
-            })
-            .detach();
-
-        // Allow the request to make some progress before dropping it.
-        cx.executor().simulate_random_delay().await;
-        drop(request1_task);
-
-        request2_task.await;
-        assert_eq!(
-            &*events.lock(),
-            &[
-                "message 1".to_string(),
-                "message 2".to_string(),
-                "response 2".to_string()
-            ]
-        );
-    }
-
-    #[gpui::test(iterations = 50)]
-    async fn test_disconnect(cx: &mut TestAppContext) {
-        let executor = cx.executor();
-
-        let (client_conn, mut server_conn, _kill) = Connection::in_memory(executor.clone());
-
-        let client = Peer::new(0);
-        let (connection_id, io_handler, mut incoming) =
-            client.add_test_connection(client_conn, executor.clone());
-
-        let (io_ended_tx, io_ended_rx) = oneshot::channel();
-        executor
-            .spawn(async move {
-                io_handler.await.ok();
-                io_ended_tx.send(()).unwrap();
-            })
-            .detach();
-
-        let (messages_ended_tx, messages_ended_rx) = oneshot::channel();
-        executor
-            .spawn(async move {
-                incoming.next().await;
-                messages_ended_tx.send(()).unwrap();
-            })
-            .detach();
-
-        client.disconnect(connection_id);
-
-        let _ = io_ended_rx.await;
-        let _ = messages_ended_rx.await;
-        assert!(server_conn
-            .send(WebSocketMessage::Binary(vec![]))
-            .await
-            .is_err());
-    }
-
-    #[gpui::test(iterations = 50)]
-    async fn test_io_error(cx: &mut TestAppContext) {
-        let executor = cx.executor();
-        let (client_conn, mut server_conn, _kill) = Connection::in_memory(executor.clone());
-
-        let client = Peer::new(0);
-        let (connection_id, io_handler, mut incoming) =
-            client.add_test_connection(client_conn, executor.clone());
-        executor.spawn(io_handler).detach();
-        executor
-            .spawn(async move { incoming.next().await })
-            .detach();
-
-        let response = executor.spawn(client.request(connection_id, proto::Ping {}));
-        let _request = server_conn.rx.next().await.unwrap().unwrap();
-
-        drop(server_conn);
-        assert_eq!(
-            response.await.unwrap_err().to_string(),
-            "connection was closed"
-        );
-    }
-}

crates/rpc2/src/proto.rs 🔗

@@ -1,692 +0,0 @@
-#![allow(non_snake_case)]
-
-use super::{entity_messages, messages, request_messages, ConnectionId, TypedEnvelope};
-use anyhow::{anyhow, Result};
-use async_tungstenite::tungstenite::Message as WebSocketMessage;
-use collections::HashMap;
-use futures::{SinkExt as _, StreamExt as _};
-use prost::Message as _;
-use serde::Serialize;
-use std::any::{Any, TypeId};
-use std::{
-    cmp,
-    fmt::Debug,
-    io, iter,
-    time::{Duration, SystemTime, UNIX_EPOCH},
-};
-use std::{fmt, mem};
-
-include!(concat!(env!("OUT_DIR"), "/zed.messages.rs"));
-
-pub trait EnvelopedMessage: Clone + Debug + Serialize + Sized + Send + Sync + 'static {
-    const NAME: &'static str;
-    const PRIORITY: MessagePriority;
-    fn into_envelope(
-        self,
-        id: u32,
-        responding_to: Option<u32>,
-        original_sender_id: Option<PeerId>,
-    ) -> Envelope;
-    fn from_envelope(envelope: Envelope) -> Option<Self>;
-}
-
-pub trait EntityMessage: EnvelopedMessage {
-    fn remote_entity_id(&self) -> u64;
-}
-
-pub trait RequestMessage: EnvelopedMessage {
-    type Response: EnvelopedMessage;
-}
-
-pub trait AnyTypedEnvelope: 'static + Send + Sync {
-    fn payload_type_id(&self) -> TypeId;
-    fn payload_type_name(&self) -> &'static str;
-    fn as_any(&self) -> &dyn Any;
-    fn into_any(self: Box<Self>) -> Box<dyn Any + Send + Sync>;
-    fn is_background(&self) -> bool;
-    fn original_sender_id(&self) -> Option<PeerId>;
-    fn sender_id(&self) -> ConnectionId;
-    fn message_id(&self) -> u32;
-}
-
-pub enum MessagePriority {
-    Foreground,
-    Background,
-}
-
-impl<T: EnvelopedMessage> AnyTypedEnvelope for TypedEnvelope<T> {
-    fn payload_type_id(&self) -> TypeId {
-        TypeId::of::<T>()
-    }
-
-    fn payload_type_name(&self) -> &'static str {
-        T::NAME
-    }
-
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
-    fn into_any(self: Box<Self>) -> Box<dyn Any + Send + Sync> {
-        self
-    }
-
-    fn is_background(&self) -> bool {
-        matches!(T::PRIORITY, MessagePriority::Background)
-    }
-
-    fn original_sender_id(&self) -> Option<PeerId> {
-        self.original_sender_id
-    }
-
-    fn sender_id(&self) -> ConnectionId {
-        self.sender_id
-    }
-
-    fn message_id(&self) -> u32 {
-        self.message_id
-    }
-}
-
-impl PeerId {
-    pub fn from_u64(peer_id: u64) -> Self {
-        let owner_id = (peer_id >> 32) as u32;
-        let id = peer_id as u32;
-        Self { owner_id, id }
-    }
-
-    pub fn as_u64(self) -> u64 {
-        ((self.owner_id as u64) << 32) | (self.id as u64)
-    }
-}
-
-impl Copy for PeerId {}
-
-impl Eq for PeerId {}
-
-impl Ord for PeerId {
-    fn cmp(&self, other: &Self) -> cmp::Ordering {
-        self.owner_id
-            .cmp(&other.owner_id)
-            .then_with(|| self.id.cmp(&other.id))
-    }
-}
-
-impl PartialOrd for PeerId {
-    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
-        Some(self.cmp(other))
-    }
-}
-
-impl std::hash::Hash for PeerId {
-    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
-        self.owner_id.hash(state);
-        self.id.hash(state);
-    }
-}
-
-impl fmt::Display for PeerId {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "{}/{}", self.owner_id, self.id)
-    }
-}
-
-messages!(
-    (Ack, Foreground),
-    (AckBufferOperation, Background),
-    (AckChannelMessage, Background),
-    (AddNotification, Foreground),
-    (AddProjectCollaborator, Foreground),
-    (ApplyCodeAction, Background),
-    (ApplyCodeActionResponse, Background),
-    (ApplyCompletionAdditionalEdits, Background),
-    (ApplyCompletionAdditionalEditsResponse, Background),
-    (BufferReloaded, Foreground),
-    (BufferSaved, Foreground),
-    (Call, Foreground),
-    (CallCanceled, Foreground),
-    (CancelCall, Foreground),
-    (ChannelMessageSent, Foreground),
-    (CopyProjectEntry, Foreground),
-    (CreateBufferForPeer, Foreground),
-    (CreateChannel, Foreground),
-    (CreateChannelResponse, Foreground),
-    (CreateProjectEntry, Foreground),
-    (CreateRoom, Foreground),
-    (CreateRoomResponse, Foreground),
-    (DeclineCall, Foreground),
-    (DeleteChannel, Foreground),
-    (DeleteNotification, Foreground),
-    (DeleteProjectEntry, Foreground),
-    (Error, Foreground),
-    (ExpandProjectEntry, Foreground),
-    (ExpandProjectEntryResponse, Foreground),
-    (Follow, Foreground),
-    (FollowResponse, Foreground),
-    (FormatBuffers, Foreground),
-    (FormatBuffersResponse, Foreground),
-    (FuzzySearchUsers, Foreground),
-    (GetChannelMembers, Foreground),
-    (GetChannelMembersResponse, Foreground),
-    (GetChannelMessages, Background),
-    (GetChannelMessagesById, Background),
-    (GetChannelMessagesResponse, Background),
-    (GetCodeActions, Background),
-    (GetCodeActionsResponse, Background),
-    (GetCompletions, Background),
-    (GetCompletionsResponse, Background),
-    (GetDefinition, Background),
-    (GetDefinitionResponse, Background),
-    (GetDocumentHighlights, Background),
-    (GetDocumentHighlightsResponse, Background),
-    (GetHover, Background),
-    (GetHoverResponse, Background),
-    (GetNotifications, Foreground),
-    (GetNotificationsResponse, Foreground),
-    (GetPrivateUserInfo, Foreground),
-    (GetPrivateUserInfoResponse, Foreground),
-    (GetProjectSymbols, Background),
-    (GetProjectSymbolsResponse, Background),
-    (GetReferences, Background),
-    (GetReferencesResponse, Background),
-    (GetTypeDefinition, Background),
-    (GetTypeDefinitionResponse, Background),
-    (GetUsers, Foreground),
-    (Hello, Foreground),
-    (IncomingCall, Foreground),
-    (InlayHints, Background),
-    (InlayHintsResponse, Background),
-    (InviteChannelMember, Foreground),
-    (JoinChannel, Foreground),
-    (JoinChannelBuffer, Foreground),
-    (JoinChannelBufferResponse, Foreground),
-    (JoinChannelChat, Foreground),
-    (JoinChannelChatResponse, Foreground),
-    (JoinProject, Foreground),
-    (JoinProjectResponse, Foreground),
-    (JoinRoom, Foreground),
-    (JoinRoomResponse, Foreground),
-    (LeaveChannelBuffer, Background),
-    (LeaveChannelChat, Foreground),
-    (LeaveProject, Foreground),
-    (LeaveRoom, Foreground),
-    (MarkNotificationRead, Foreground),
-    (MoveChannel, Foreground),
-    (OnTypeFormatting, Background),
-    (OnTypeFormattingResponse, Background),
-    (OpenBufferById, Background),
-    (OpenBufferByPath, Background),
-    (OpenBufferForSymbol, Background),
-    (OpenBufferForSymbolResponse, Background),
-    (OpenBufferResponse, Background),
-    (PerformRename, Background),
-    (PerformRenameResponse, Background),
-    (Ping, Foreground),
-    (PrepareRename, Background),
-    (PrepareRenameResponse, Background),
-    (ProjectEntryResponse, Foreground),
-    (RefreshInlayHints, Foreground),
-    (RejoinChannelBuffers, Foreground),
-    (RejoinChannelBuffersResponse, Foreground),
-    (RejoinRoom, Foreground),
-    (RejoinRoomResponse, Foreground),
-    (ReloadBuffers, Foreground),
-    (ReloadBuffersResponse, Foreground),
-    (RemoveChannelMember, Foreground),
-    (RemoveChannelMessage, Foreground),
-    (RemoveContact, Foreground),
-    (RemoveProjectCollaborator, Foreground),
-    (RenameChannel, Foreground),
-    (RenameChannelResponse, Foreground),
-    (RenameProjectEntry, Foreground),
-    (RequestContact, Foreground),
-    (ResolveCompletionDocumentation, Background),
-    (ResolveCompletionDocumentationResponse, Background),
-    (ResolveInlayHint, Background),
-    (ResolveInlayHintResponse, Background),
-    (RespondToChannelInvite, Foreground),
-    (RespondToContactRequest, Foreground),
-    (RoomUpdated, Foreground),
-    (SaveBuffer, Foreground),
-    (SetChannelMemberRole, Foreground),
-    (SetChannelVisibility, Foreground),
-    (SearchProject, Background),
-    (SearchProjectResponse, Background),
-    (SendChannelMessage, Background),
-    (SendChannelMessageResponse, Background),
-    (ShareProject, Foreground),
-    (ShareProjectResponse, Foreground),
-    (ShowContacts, Foreground),
-    (StartLanguageServer, Foreground),
-    (SynchronizeBuffers, Foreground),
-    (SynchronizeBuffersResponse, Foreground),
-    (Test, Foreground),
-    (Unfollow, Foreground),
-    (UnshareProject, Foreground),
-    (UpdateBuffer, Foreground),
-    (UpdateBufferFile, Foreground),
-    (UpdateChannelBuffer, Foreground),
-    (UpdateChannelBufferCollaborators, Foreground),
-    (UpdateChannels, Foreground),
-    (UpdateContacts, Foreground),
-    (UpdateDiagnosticSummary, Foreground),
-    (UpdateDiffBase, Foreground),
-    (UpdateFollowers, Foreground),
-    (UpdateInviteInfo, Foreground),
-    (UpdateLanguageServer, Foreground),
-    (UpdateParticipantLocation, Foreground),
-    (UpdateProject, Foreground),
-    (UpdateProjectCollaborator, Foreground),
-    (UpdateWorktree, Foreground),
-    (UpdateWorktreeSettings, Foreground),
-    (UsersResponse, Foreground),
-    (LspExtExpandMacro, Background),
-    (LspExtExpandMacroResponse, Background),
-);
-
-request_messages!(
-    (ApplyCodeAction, ApplyCodeActionResponse),
-    (
-        ApplyCompletionAdditionalEdits,
-        ApplyCompletionAdditionalEditsResponse
-    ),
-    (Call, Ack),
-    (CancelCall, Ack),
-    (CopyProjectEntry, ProjectEntryResponse),
-    (CreateChannel, CreateChannelResponse),
-    (CreateProjectEntry, ProjectEntryResponse),
-    (CreateRoom, CreateRoomResponse),
-    (DeclineCall, Ack),
-    (DeleteChannel, Ack),
-    (DeleteProjectEntry, ProjectEntryResponse),
-    (ExpandProjectEntry, ExpandProjectEntryResponse),
-    (Follow, FollowResponse),
-    (FormatBuffers, FormatBuffersResponse),
-    (FuzzySearchUsers, UsersResponse),
-    (GetChannelMembers, GetChannelMembersResponse),
-    (GetChannelMessages, GetChannelMessagesResponse),
-    (GetChannelMessagesById, GetChannelMessagesResponse),
-    (GetCodeActions, GetCodeActionsResponse),
-    (GetCompletions, GetCompletionsResponse),
-    (GetDefinition, GetDefinitionResponse),
-    (GetDocumentHighlights, GetDocumentHighlightsResponse),
-    (GetHover, GetHoverResponse),
-    (GetNotifications, GetNotificationsResponse),
-    (GetPrivateUserInfo, GetPrivateUserInfoResponse),
-    (GetProjectSymbols, GetProjectSymbolsResponse),
-    (GetReferences, GetReferencesResponse),
-    (GetTypeDefinition, GetTypeDefinitionResponse),
-    (GetUsers, UsersResponse),
-    (IncomingCall, Ack),
-    (InlayHints, InlayHintsResponse),
-    (InviteChannelMember, Ack),
-    (JoinChannel, JoinRoomResponse),
-    (JoinChannelBuffer, JoinChannelBufferResponse),
-    (JoinChannelChat, JoinChannelChatResponse),
-    (JoinProject, JoinProjectResponse),
-    (JoinRoom, JoinRoomResponse),
-    (LeaveChannelBuffer, Ack),
-    (LeaveRoom, Ack),
-    (MarkNotificationRead, Ack),
-    (MoveChannel, Ack),
-    (OnTypeFormatting, OnTypeFormattingResponse),
-    (OpenBufferById, OpenBufferResponse),
-    (OpenBufferByPath, OpenBufferResponse),
-    (OpenBufferForSymbol, OpenBufferForSymbolResponse),
-    (PerformRename, PerformRenameResponse),
-    (Ping, Ack),
-    (PrepareRename, PrepareRenameResponse),
-    (RefreshInlayHints, Ack),
-    (RejoinChannelBuffers, RejoinChannelBuffersResponse),
-    (RejoinRoom, RejoinRoomResponse),
-    (ReloadBuffers, ReloadBuffersResponse),
-    (RemoveChannelMember, Ack),
-    (RemoveChannelMessage, Ack),
-    (RemoveContact, Ack),
-    (RenameChannel, RenameChannelResponse),
-    (RenameProjectEntry, ProjectEntryResponse),
-    (RequestContact, Ack),
-    (
-        ResolveCompletionDocumentation,
-        ResolveCompletionDocumentationResponse
-    ),
-    (ResolveInlayHint, ResolveInlayHintResponse),
-    (RespondToChannelInvite, Ack),
-    (RespondToContactRequest, Ack),
-    (SaveBuffer, BufferSaved),
-    (SearchProject, SearchProjectResponse),
-    (SendChannelMessage, SendChannelMessageResponse),
-    (SetChannelMemberRole, Ack),
-    (SetChannelVisibility, Ack),
-    (ShareProject, ShareProjectResponse),
-    (SynchronizeBuffers, SynchronizeBuffersResponse),
-    (Test, Test),
-    (UpdateBuffer, Ack),
-    (UpdateParticipantLocation, Ack),
-    (UpdateProject, Ack),
-    (UpdateWorktree, Ack),
-    (LspExtExpandMacro, LspExtExpandMacroResponse),
-);
-
-entity_messages!(
-    project_id,
-    AddProjectCollaborator,
-    ApplyCodeAction,
-    ApplyCompletionAdditionalEdits,
-    BufferReloaded,
-    BufferSaved,
-    CopyProjectEntry,
-    CreateBufferForPeer,
-    CreateProjectEntry,
-    DeleteProjectEntry,
-    ExpandProjectEntry,
-    FormatBuffers,
-    GetCodeActions,
-    GetCompletions,
-    GetDefinition,
-    GetDocumentHighlights,
-    GetHover,
-    GetProjectSymbols,
-    GetReferences,
-    GetTypeDefinition,
-    InlayHints,
-    JoinProject,
-    LeaveProject,
-    OnTypeFormatting,
-    OpenBufferById,
-    OpenBufferByPath,
-    OpenBufferForSymbol,
-    PerformRename,
-    PrepareRename,
-    RefreshInlayHints,
-    ReloadBuffers,
-    RemoveProjectCollaborator,
-    RenameProjectEntry,
-    ResolveCompletionDocumentation,
-    ResolveInlayHint,
-    SaveBuffer,
-    SearchProject,
-    StartLanguageServer,
-    SynchronizeBuffers,
-    UnshareProject,
-    UpdateBuffer,
-    UpdateBufferFile,
-    UpdateDiagnosticSummary,
-    UpdateDiffBase,
-    UpdateLanguageServer,
-    UpdateProject,
-    UpdateProjectCollaborator,
-    UpdateWorktree,
-    UpdateWorktreeSettings,
-    LspExtExpandMacro,
-);
-
-entity_messages!(
-    channel_id,
-    ChannelMessageSent,
-    RemoveChannelMessage,
-    UpdateChannelBuffer,
-    UpdateChannelBufferCollaborators,
-);
-
-const KIB: usize = 1024;
-const MIB: usize = KIB * 1024;
-const MAX_BUFFER_LEN: usize = MIB;
-
-/// A stream of protobuf messages.
-pub struct MessageStream<S> {
-    stream: S,
-    encoding_buffer: Vec<u8>,
-}
-
-#[allow(clippy::large_enum_variant)]
-#[derive(Debug)]
-pub enum Message {
-    Envelope(Envelope),
-    Ping,
-    Pong,
-}
-
-impl<S> MessageStream<S> {
-    pub fn new(stream: S) -> Self {
-        Self {
-            stream,
-            encoding_buffer: Vec::new(),
-        }
-    }
-
-    pub fn inner_mut(&mut self) -> &mut S {
-        &mut self.stream
-    }
-}
-
-impl<S> MessageStream<S>
-where
-    S: futures::Sink<WebSocketMessage, Error = anyhow::Error> + Unpin,
-{
-    pub async fn write(&mut self, message: Message) -> Result<(), anyhow::Error> {
-        #[cfg(any(test, feature = "test-support"))]
-        const COMPRESSION_LEVEL: i32 = -7;
-
-        #[cfg(not(any(test, feature = "test-support")))]
-        const COMPRESSION_LEVEL: i32 = 4;
-
-        match message {
-            Message::Envelope(message) => {
-                self.encoding_buffer.reserve(message.encoded_len());
-                message
-                    .encode(&mut self.encoding_buffer)
-                    .map_err(io::Error::from)?;
-                let buffer =
-                    zstd::stream::encode_all(self.encoding_buffer.as_slice(), COMPRESSION_LEVEL)
-                        .unwrap();
-
-                self.encoding_buffer.clear();
-                self.encoding_buffer.shrink_to(MAX_BUFFER_LEN);
-                self.stream.send(WebSocketMessage::Binary(buffer)).await?;
-            }
-            Message::Ping => {
-                self.stream
-                    .send(WebSocketMessage::Ping(Default::default()))
-                    .await?;
-            }
-            Message::Pong => {
-                self.stream
-                    .send(WebSocketMessage::Pong(Default::default()))
-                    .await?;
-            }
-        }
-
-        Ok(())
-    }
-}
-
-impl<S> MessageStream<S>
-where
-    S: futures::Stream<Item = Result<WebSocketMessage, anyhow::Error>> + Unpin,
-{
-    pub async fn read(&mut self) -> Result<Message, anyhow::Error> {
-        while let Some(bytes) = self.stream.next().await {
-            match bytes? {
-                WebSocketMessage::Binary(bytes) => {
-                    zstd::stream::copy_decode(bytes.as_slice(), &mut self.encoding_buffer).unwrap();
-                    let envelope = Envelope::decode(self.encoding_buffer.as_slice())
-                        .map_err(io::Error::from)?;
-
-                    self.encoding_buffer.clear();
-                    self.encoding_buffer.shrink_to(MAX_BUFFER_LEN);
-                    return Ok(Message::Envelope(envelope));
-                }
-                WebSocketMessage::Ping(_) => return Ok(Message::Ping),
-                WebSocketMessage::Pong(_) => return Ok(Message::Pong),
-                WebSocketMessage::Close(_) => break,
-                _ => {}
-            }
-        }
-        Err(anyhow!("connection closed"))
-    }
-}
-
-impl From<Timestamp> for SystemTime {
-    fn from(val: Timestamp) -> Self {
-        UNIX_EPOCH
-            .checked_add(Duration::new(val.seconds, val.nanos))
-            .unwrap()
-    }
-}
-
-impl From<SystemTime> for Timestamp {
-    fn from(time: SystemTime) -> Self {
-        let duration = time.duration_since(UNIX_EPOCH).unwrap();
-        Self {
-            seconds: duration.as_secs(),
-            nanos: duration.subsec_nanos(),
-        }
-    }
-}
-
-impl From<u128> for Nonce {
-    fn from(nonce: u128) -> Self {
-        let upper_half = (nonce >> 64) as u64;
-        let lower_half = nonce as u64;
-        Self {
-            upper_half,
-            lower_half,
-        }
-    }
-}
-
-impl From<Nonce> for u128 {
-    fn from(nonce: Nonce) -> Self {
-        let upper_half = (nonce.upper_half as u128) << 64;
-        let lower_half = nonce.lower_half as u128;
-        upper_half | lower_half
-    }
-}
-
-pub fn split_worktree_update(
-    mut message: UpdateWorktree,
-    max_chunk_size: usize,
-) -> impl Iterator<Item = UpdateWorktree> {
-    let mut done_files = false;
-
-    let mut repository_map = message
-        .updated_repositories
-        .into_iter()
-        .map(|repo| (repo.work_directory_id, repo))
-        .collect::<HashMap<_, _>>();
-
-    iter::from_fn(move || {
-        if done_files {
-            return None;
-        }
-
-        let updated_entries_chunk_size = cmp::min(message.updated_entries.len(), max_chunk_size);
-        let updated_entries: Vec<_> = message
-            .updated_entries
-            .drain(..updated_entries_chunk_size)
-            .collect();
-
-        let removed_entries_chunk_size = cmp::min(message.removed_entries.len(), max_chunk_size);
-        let removed_entries = message
-            .removed_entries
-            .drain(..removed_entries_chunk_size)
-            .collect();
-
-        done_files = message.updated_entries.is_empty() && message.removed_entries.is_empty();
-
-        let mut updated_repositories = Vec::new();
-
-        if !repository_map.is_empty() {
-            for entry in &updated_entries {
-                if let Some(repo) = repository_map.remove(&entry.id) {
-                    updated_repositories.push(repo)
-                }
-            }
-        }
-
-        let removed_repositories = if done_files {
-            mem::take(&mut message.removed_repositories)
-        } else {
-            Default::default()
-        };
-
-        if done_files {
-            updated_repositories.extend(mem::take(&mut repository_map).into_values());
-        }
-
-        Some(UpdateWorktree {
-            project_id: message.project_id,
-            worktree_id: message.worktree_id,
-            root_name: message.root_name.clone(),
-            abs_path: message.abs_path.clone(),
-            updated_entries,
-            removed_entries,
-            scan_id: message.scan_id,
-            is_last_update: done_files && message.is_last_update,
-            updated_repositories,
-            removed_repositories,
-        })
-    })
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[gpui::test]
-    async fn test_buffer_size() {
-        let (tx, rx) = futures::channel::mpsc::unbounded();
-        let mut sink = MessageStream::new(tx.sink_map_err(|_| anyhow!("")));
-        sink.write(Message::Envelope(Envelope {
-            payload: Some(envelope::Payload::UpdateWorktree(UpdateWorktree {
-                root_name: "abcdefg".repeat(10),
-                ..Default::default()
-            })),
-            ..Default::default()
-        }))
-        .await
-        .unwrap();
-        assert!(sink.encoding_buffer.capacity() <= MAX_BUFFER_LEN);
-        sink.write(Message::Envelope(Envelope {
-            payload: Some(envelope::Payload::UpdateWorktree(UpdateWorktree {
-                root_name: "abcdefg".repeat(1000000),
-                ..Default::default()
-            })),
-            ..Default::default()
-        }))
-        .await
-        .unwrap();
-        assert!(sink.encoding_buffer.capacity() <= MAX_BUFFER_LEN);
-
-        let mut stream = MessageStream::new(rx.map(anyhow::Ok));
-        stream.read().await.unwrap();
-        assert!(stream.encoding_buffer.capacity() <= MAX_BUFFER_LEN);
-        stream.read().await.unwrap();
-        assert!(stream.encoding_buffer.capacity() <= MAX_BUFFER_LEN);
-    }
-
-    #[gpui::test]
-    fn test_converting_peer_id_from_and_to_u64() {
-        let peer_id = PeerId {
-            owner_id: 10,
-            id: 3,
-        };
-        assert_eq!(PeerId::from_u64(peer_id.as_u64()), peer_id);
-        let peer_id = PeerId {
-            owner_id: u32::MAX,
-            id: 3,
-        };
-        assert_eq!(PeerId::from_u64(peer_id.as_u64()), peer_id);
-        let peer_id = PeerId {
-            owner_id: 10,
-            id: u32::MAX,
-        };
-        assert_eq!(PeerId::from_u64(peer_id.as_u64()), peer_id);
-        let peer_id = PeerId {
-            owner_id: u32::MAX,
-            id: u32::MAX,
-        };
-        assert_eq!(PeerId::from_u64(peer_id.as_u64()), peer_id);
-    }
-}

crates/rpc2/src/rpc.rs 🔗

@@ -1,12 +0,0 @@
-pub mod auth;
-mod conn;
-mod notification;
-mod peer;
-pub mod proto;
-
-pub use conn::Connection;
-pub use notification::*;
-pub use peer::*;
-mod macros;
-
-pub const PROTOCOL_VERSION: u32 = 67;

crates/search/Cargo.toml 🔗

@@ -19,6 +19,7 @@ project = { path = "../project" }
 settings = { path = "../settings" }
 theme = { path = "../theme" }
 util = { path = "../util" }
+ui = {path = "../ui"}
 workspace = { path = "../workspace" }
 semantic_index = { path = "../semantic_index" }
 anyhow.workspace = true

crates/search/src/buffer_search.rs 🔗

@@ -1,82 +1,55 @@
 use crate::{
     history::SearchHistory,
-    mode::{next_mode, SearchMode, Side},
-    search_bar::{render_nav_button, render_search_mode_button},
-    CycleMode, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions,
-    SelectAllMatches, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleReplace,
-    ToggleWholeWord,
+    mode::{next_mode, SearchMode},
+    search_bar::render_nav_button,
+    ActivateRegexMode, ActivateTextMode, CycleMode, NextHistoryQuery, PreviousHistoryQuery,
+    ReplaceAll, ReplaceNext, SearchOptions, SelectAllMatches, SelectNextMatch, SelectPrevMatch,
+    ToggleCaseSensitive, ToggleReplace, ToggleWholeWord,
 };
 use collections::HashMap;
-use editor::Editor;
+use editor::{Editor, EditorElement, EditorStyle, Tab};
 use futures::channel::oneshot;
 use gpui::{
-    actions, elements::*, impl_actions, Action, AnyViewHandle, AppContext, Entity, Subscription,
-    Task, View, ViewContext, ViewHandle, WindowContext,
+    actions, div, impl_actions, Action, AppContext, ClickEvent, EventEmitter, FocusableView,
+    FontStyle, FontWeight, InteractiveElement as _, IntoElement, KeyContext, ParentElement as _,
+    Render, Styled, Subscription, Task, TextStyle, View, ViewContext, VisualContext as _,
+    WhiteSpace, WindowContext,
 };
 use project::search::SearchQuery;
 use serde::Deserialize;
+use settings::Settings;
 use std::{any::Any, sync::Arc};
+use theme::ThemeSettings;
 
+use ui::{h_stack, prelude::*, Icon, IconButton, IconElement, ToggleButton, Tooltip};
 use util::ResultExt;
 use workspace::{
     item::ItemHandle,
     searchable::{Direction, SearchEvent, SearchableItemHandle, WeakSearchableItemHandle},
-    Pane, ToolbarItemLocation, ToolbarItemView,
+    ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
 };
 
-#[derive(Clone, Deserialize, PartialEq)]
+#[derive(PartialEq, Clone, Deserialize)]
 pub struct Deploy {
     pub focus: bool,
 }
 
-actions!(buffer_search, [Dismiss, FocusEditor]);
 impl_actions!(buffer_search, [Deploy]);
 
+actions!(buffer_search, [Dismiss, FocusEditor]);
+
 pub enum Event {
     UpdateLocation,
 }
 
 pub fn init(cx: &mut AppContext) {
-    cx.add_action(BufferSearchBar::deploy_bar);
-    cx.add_action(BufferSearchBar::dismiss);
-    cx.add_action(BufferSearchBar::focus_editor);
-    cx.add_action(BufferSearchBar::select_next_match);
-    cx.add_action(BufferSearchBar::select_prev_match);
-    cx.add_action(BufferSearchBar::select_all_matches);
-    cx.add_action(BufferSearchBar::select_next_match_on_pane);
-    cx.add_action(BufferSearchBar::select_prev_match_on_pane);
-    cx.add_action(BufferSearchBar::select_all_matches_on_pane);
-    cx.add_action(BufferSearchBar::handle_editor_cancel);
-    cx.add_action(BufferSearchBar::next_history_query);
-    cx.add_action(BufferSearchBar::previous_history_query);
-    cx.add_action(BufferSearchBar::cycle_mode);
-    cx.add_action(BufferSearchBar::cycle_mode_on_pane);
-    cx.add_action(BufferSearchBar::replace_all);
-    cx.add_action(BufferSearchBar::replace_next);
-    cx.add_action(BufferSearchBar::replace_all_on_pane);
-    cx.add_action(BufferSearchBar::replace_next_on_pane);
-    cx.add_action(BufferSearchBar::toggle_replace);
-    cx.add_action(BufferSearchBar::toggle_replace_on_a_pane);
-    add_toggle_option_action::<ToggleCaseSensitive>(SearchOptions::CASE_SENSITIVE, cx);
-    add_toggle_option_action::<ToggleWholeWord>(SearchOptions::WHOLE_WORD, cx);
-}
-
-fn add_toggle_option_action<A: Action>(option: SearchOptions, cx: &mut AppContext) {
-    cx.add_action(move |pane: &mut Pane, _: &A, cx: &mut ViewContext<Pane>| {
-        if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
-            search_bar.update(cx, |search_bar, cx| {
-                if search_bar.show(cx) {
-                    search_bar.toggle_search_option(option, cx);
-                }
-            });
-        }
-        cx.propagate_action();
-    });
+    cx.observe_new_views(|editor: &mut Workspace, _| BufferSearchBar::register(editor))
+        .detach();
 }
 
 pub struct BufferSearchBar {
-    query_editor: ViewHandle<Editor>,
-    replacement_editor: ViewHandle<Editor>,
+    query_editor: View<Editor>,
+    replacement_editor: View<Editor>,
     active_searchable_item: Option<Box<dyn SearchableItemHandle>>,
     active_match_index: Option<usize>,
     active_searchable_item_subscription: Option<Subscription>,
@@ -93,51 +66,54 @@ pub struct BufferSearchBar {
     replace_enabled: bool,
 }
 
-impl Entity for BufferSearchBar {
-    type Event = Event;
-}
+impl BufferSearchBar {
+    fn render_text_input(&self, editor: &View<Editor>, cx: &ViewContext<Self>) -> impl IntoElement {
+        let settings = ThemeSettings::get_global(cx);
+        let text_style = TextStyle {
+            color: if editor.read(cx).read_only() {
+                cx.theme().colors().text_disabled
+            } else {
+                cx.theme().colors().text
+            },
+            font_family: settings.ui_font.family.clone(),
+            font_features: settings.ui_font.features,
+            font_size: rems(0.875).into(),
+            font_weight: FontWeight::NORMAL,
+            font_style: FontStyle::Normal,
+            line_height: relative(1.3).into(),
+            background_color: None,
+            underline: None,
+            white_space: WhiteSpace::Normal,
+        };
 
-impl View for BufferSearchBar {
-    fn ui_name() -> &'static str {
-        "BufferSearchBar"
+        EditorElement::new(
+            &editor,
+            EditorStyle {
+                background: cx.theme().colors().editor_background,
+                local_player: cx.theme().players().local(),
+                text: text_style,
+                ..Default::default()
+            },
+        )
     }
+}
 
-    fn update_keymap_context(
-        &self,
-        keymap: &mut gpui::keymap_matcher::KeymapContext,
-        cx: &AppContext,
-    ) {
-        Self::reset_to_default_keymap_context(keymap);
-        let in_replace = self
-            .replacement_editor
-            .read_with(cx, |_, cx| cx.is_self_focused())
-            .unwrap_or(false);
-        if in_replace {
-            keymap.add_identifier("in_replace");
+impl EventEmitter<Event> for BufferSearchBar {}
+impl EventEmitter<workspace::ToolbarItemEvent> for BufferSearchBar {}
+impl Render for BufferSearchBar {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        if self.dismissed {
+            return div();
         }
-    }
 
-    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
-        if cx.is_self_focused() {
-            cx.focus(&self.query_editor);
-        }
-    }
+        let supported_options = self.supported_options();
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = theme::current(cx).clone();
-        let query_container_style = if self.query_contains_error {
-            theme.search.invalid_editor
-        } else {
-            theme.search.editor.input.container
-        };
-        let supported_options = self
-            .active_searchable_item
-            .as_ref()
-            .map(|active_searchable_item| active_searchable_item.supported_options())
-            .unwrap_or_default();
-
-        let previous_query_keystrokes =
-            cx.binding_for_action(&PreviousHistoryQuery {})
+        if self.query_editor.read(cx).placeholder_text().is_none() {
+            let query_focus_handle = self.query_editor.focus_handle(cx);
+            let up_keystrokes = cx
+                .bindings_for_action_in(&PreviousHistoryQuery {}, &query_focus_handle)
+                .into_iter()
+                .next()
                 .map(|binding| {
                     binding
                         .keystrokes()
@@ -145,62 +121,40 @@ impl View for BufferSearchBar {
                         .map(|k| k.to_string())
                         .collect::<Vec<_>>()
                 });
-        let next_query_keystrokes = cx.binding_for_action(&NextHistoryQuery {}).map(|binding| {
-            binding
-                .keystrokes()
-                .iter()
-                .map(|k| k.to_string())
-                .collect::<Vec<_>>()
-        });
-        let new_placeholder_text = match (previous_query_keystrokes, next_query_keystrokes) {
-            (Some(previous_query_keystrokes), Some(next_query_keystrokes)) => {
-                format!(
-                    "Search ({}/{} for previous/next query)",
-                    previous_query_keystrokes.join(" "),
-                    next_query_keystrokes.join(" ")
-                )
-            }
-            (None, Some(next_query_keystrokes)) => {
-                format!(
-                    "Search ({} for next query)",
-                    next_query_keystrokes.join(" ")
-                )
-            }
-            (Some(previous_query_keystrokes), None) => {
-                format!(
-                    "Search ({} for previous query)",
-                    previous_query_keystrokes.join(" ")
-                )
+            let down_keystrokes = cx
+                .bindings_for_action_in(&NextHistoryQuery {}, &query_focus_handle)
+                .into_iter()
+                .next()
+                .map(|binding| {
+                    binding
+                        .keystrokes()
+                        .iter()
+                        .map(|k| k.to_string())
+                        .collect::<Vec<_>>()
+                });
+
+            let placeholder_text =
+                up_keystrokes
+                    .zip(down_keystrokes)
+                    .map(|(up_keystrokes, down_keystrokes)| {
+                        Arc::from(format!(
+                            "Search ({}/{} for previous/next query)",
+                            up_keystrokes.join(" "),
+                            down_keystrokes.join(" ")
+                        ))
+                    });
+
+            if let Some(placeholder_text) = placeholder_text {
+                self.query_editor.update(cx, |editor, cx| {
+                    editor.set_placeholder_text(placeholder_text, cx);
+                });
             }
-            (None, None) => String::new(),
-        };
-        self.query_editor.update(cx, |editor, cx| {
-            editor.set_placeholder_text(new_placeholder_text, cx);
-        });
+        }
+
         self.replacement_editor.update(cx, |editor, cx| {
             editor.set_placeholder_text("Replace with...", cx);
         });
-        let search_button_for_mode = |mode, side, cx: &mut ViewContext<BufferSearchBar>| {
-            let is_active = self.current_mode == mode;
-
-            render_search_mode_button(
-                mode,
-                side,
-                is_active,
-                move |_, this, cx| {
-                    this.activate_search_mode(mode, cx);
-                },
-                cx,
-            )
-        };
-        let search_option_button = |option| {
-            let is_active = self.search_options.contains(option);
-            option.as_button(
-                is_active,
-                theme.tooltip.clone(),
-                theme.search.option_button_component.clone(),
-            )
-        };
+
         let match_count = self
             .active_searchable_item
             .as_ref()
@@ -217,143 +171,210 @@ impl View for BufferSearchBar {
                     "No matches".to_string()
                 };
 
-                Some(
-                    Label::new(message, theme.search.match_index.text.clone())
-                        .contained()
-                        .with_style(theme.search.match_index.container)
-                        .aligned(),
-                )
+                Some(ui::Label::new(message))
             });
-        let nav_button_for_direction = |label, direction, cx: &mut ViewContext<Self>| {
-            render_nav_button(
-                label,
-                direction,
-                self.active_match_index.is_some(),
-                move |_, this, cx| match direction {
-                    Direction::Prev => this.select_prev_match(&Default::default(), cx),
-                    Direction::Next => this.select_next_match(&Default::default(), cx),
-                },
-                cx,
-            )
+        let should_show_replace_input = self.replace_enabled && supported_options.replacement;
+        let in_replace = self.replacement_editor.focus_handle(cx).is_focused(cx);
+
+        let mut key_context = KeyContext::default();
+        key_context.add("BufferSearchBar");
+        if in_replace {
+            key_context.add("in_replace");
+        }
+        let editor_border = if self.query_contains_error {
+            Color::Error.color(cx)
+        } else {
+            cx.theme().colors().border
         };
-        let query_column = Flex::row()
-            .with_child(
-                Svg::for_style(theme.search.editor_icon.clone().icon)
-                    .contained()
-                    .with_style(theme.search.editor_icon.clone().container),
+        h_stack()
+            .w_full()
+            .gap_2()
+            .key_context(key_context)
+            .capture_action(cx.listener(Self::tab))
+            .on_action(cx.listener(Self::previous_history_query))
+            .on_action(cx.listener(Self::next_history_query))
+            .on_action(cx.listener(Self::dismiss))
+            .on_action(cx.listener(Self::select_next_match))
+            .on_action(cx.listener(Self::select_prev_match))
+            .on_action(cx.listener(|this, _: &ActivateRegexMode, cx| {
+                this.activate_search_mode(SearchMode::Regex, cx);
+            }))
+            .on_action(cx.listener(|this, _: &ActivateTextMode, cx| {
+                this.activate_search_mode(SearchMode::Text, cx);
+            }))
+            .when(self.supported_options().replacement, |this| {
+                this.on_action(cx.listener(Self::toggle_replace))
+                    .when(in_replace, |this| {
+                        this.on_action(cx.listener(Self::replace_next))
+                            .on_action(cx.listener(Self::replace_all))
+                    })
+            })
+            .when(self.supported_options().case, |this| {
+                this.on_action(cx.listener(Self::toggle_case_sensitive))
+            })
+            .when(self.supported_options().word, |this| {
+                this.on_action(cx.listener(Self::toggle_whole_word))
+            })
+            .child(
+                h_stack()
+                    .flex_1()
+                    .px_2()
+                    .py_1()
+                    .gap_2()
+                    .border_1()
+                    .border_color(editor_border)
+                    .rounded_lg()
+                    .child(IconElement::new(Icon::MagnifyingGlass))
+                    .child(self.render_text_input(&self.query_editor, cx))
+                    .children(supported_options.case.then(|| {
+                        self.render_search_option_button(
+                            SearchOptions::CASE_SENSITIVE,
+                            cx.listener(|this, _, cx| {
+                                this.toggle_case_sensitive(&ToggleCaseSensitive, cx)
+                            }),
+                        )
+                    }))
+                    .children(supported_options.word.then(|| {
+                        self.render_search_option_button(
+                            SearchOptions::WHOLE_WORD,
+                            cx.listener(|this, _, cx| this.toggle_whole_word(&ToggleWholeWord, cx)),
+                        )
+                    })),
             )
-            .with_child(ChildView::new(&self.query_editor, cx).flex(1., true))
-            .with_child(
-                Flex::row()
-                    .with_children(
-                        supported_options
-                            .case
-                            .then(|| search_option_button(SearchOptions::CASE_SENSITIVE)),
+            .child(
+                h_stack()
+                    .gap_2()
+                    .flex_none()
+                    .child(
+                        h_stack()
+                            .child(
+                                ToggleButton::new("search-mode-text", SearchMode::Text.label())
+                                    .style(ButtonStyle::Filled)
+                                    .size(ButtonSize::Large)
+                                    .selected(self.current_mode == SearchMode::Text)
+                                    .on_click(cx.listener(move |_, _event, cx| {
+                                        cx.dispatch_action(SearchMode::Text.action())
+                                    }))
+                                    .tooltip(|cx| {
+                                        Tooltip::for_action(
+                                            SearchMode::Text.tooltip(),
+                                            &*SearchMode::Text.action(),
+                                            cx,
+                                        )
+                                    })
+                                    .first(),
+                            )
+                            .child(
+                                ToggleButton::new("search-mode-regex", SearchMode::Regex.label())
+                                    .style(ButtonStyle::Filled)
+                                    .size(ButtonSize::Large)
+                                    .selected(self.current_mode == SearchMode::Regex)
+                                    .on_click(cx.listener(move |_, _event, cx| {
+                                        cx.dispatch_action(SearchMode::Regex.action())
+                                    }))
+                                    .tooltip(|cx| {
+                                        Tooltip::for_action(
+                                            SearchMode::Regex.tooltip(),
+                                            &*SearchMode::Regex.action(),
+                                            cx,
+                                        )
+                                    })
+                                    .last(),
+                            ),
                     )
-                    .with_children(
-                        supported_options
-                            .word
-                            .then(|| search_option_button(SearchOptions::WHOLE_WORD)),
-                    )
-                    .flex_float()
-                    .contained(),
+                    .when(supported_options.replacement, |this| {
+                        this.child(
+                            IconButton::new(
+                                "buffer-search-bar-toggle-replace-button",
+                                Icon::Replace,
+                            )
+                            .style(ButtonStyle::Subtle)
+                            .when(self.replace_enabled, |button| {
+                                button.style(ButtonStyle::Filled)
+                            })
+                            .on_click(cx.listener(|this, _: &ClickEvent, cx| {
+                                this.toggle_replace(&ToggleReplace, cx);
+                            }))
+                            .tooltip(|cx| {
+                                Tooltip::for_action("Toggle replace", &ToggleReplace, cx)
+                            }),
+                        )
+                    }),
             )
-            .align_children_center()
-            .contained()
-            .with_style(query_container_style)
-            .constrained()
-            .with_min_width(theme.search.editor.min_width)
-            .with_max_width(theme.search.editor.max_width)
-            .with_height(theme.search.search_bar_row_height)
-            .flex(1., false);
-        let should_show_replace_input = self.replace_enabled && supported_options.replacement;
-
-        let replacement = should_show_replace_input.then(|| {
-            Flex::row()
-                .with_child(
-                    Svg::for_style(theme.search.replace_icon.clone().icon)
-                        .contained()
-                        .with_style(theme.search.replace_icon.clone().container),
-                )
-                .with_child(ChildView::new(&self.replacement_editor, cx).flex(1., true))
-                .align_children_center()
-                .flex(1., true)
-                .contained()
-                .with_style(query_container_style)
-                .constrained()
-                .with_min_width(theme.search.editor.min_width)
-                .with_max_width(theme.search.editor.max_width)
-                .with_height(theme.search.search_bar_row_height)
-                .flex(1., false)
-        });
-        let replace_all = should_show_replace_input.then(|| {
-            super::replace_action(
-                ReplaceAll,
-                "Replace all",
-                "icons/replace_all.svg",
-                theme.tooltip.clone(),
-                theme.search.action_button.clone(),
+            .child(
+                h_stack()
+                    .gap_0p5()
+                    .flex_1()
+                    .when(self.replace_enabled, |this| {
+                        this.child(
+                            h_stack()
+                                .flex_1()
+                                // We're giving this a fixed height to match the height of the search input,
+                                // which has an icon inside that is increasing its height.
+                                .h_8()
+                                .px_2()
+                                .py_1()
+                                .gap_2()
+                                .border_1()
+                                .border_color(cx.theme().colors().border)
+                                .rounded_lg()
+                                .child(self.render_text_input(&self.replacement_editor, cx)),
+                        )
+                        .when(should_show_replace_input, |this| {
+                            this.child(
+                                IconButton::new("search-replace-next", ui::Icon::ReplaceNext)
+                                    .tooltip(move |cx| {
+                                        Tooltip::for_action("Replace next", &ReplaceNext, cx)
+                                    })
+                                    .on_click(cx.listener(|this, _, cx| {
+                                        this.replace_next(&ReplaceNext, cx)
+                                    })),
+                            )
+                            .child(
+                                IconButton::new("search-replace-all", ui::Icon::ReplaceAll)
+                                    .tooltip(move |cx| {
+                                        Tooltip::for_action("Replace all", &ReplaceAll, cx)
+                                    })
+                                    .on_click(
+                                        cx.listener(|this, _, cx| {
+                                            this.replace_all(&ReplaceAll, cx)
+                                        }),
+                                    ),
+                            )
+                        })
+                    }),
             )
-        });
-        let replace_next = should_show_replace_input.then(|| {
-            super::replace_action(
-                ReplaceNext,
-                "Replace next",
-                "icons/replace_next.svg",
-                theme.tooltip.clone(),
-                theme.search.action_button.clone(),
+            .child(
+                h_stack()
+                    .gap_0p5()
+                    .flex_none()
+                    .child(
+                        IconButton::new("select-all", ui::Icon::SelectAll)
+                            .on_click(|_, cx| cx.dispatch_action(SelectAllMatches.boxed_clone()))
+                            .tooltip(|cx| {
+                                Tooltip::for_action("Select all matches", &SelectAllMatches, cx)
+                            }),
+                    )
+                    .children(match_count)
+                    .child(render_nav_button(
+                        ui::Icon::ChevronLeft,
+                        self.active_match_index.is_some(),
+                        "Select previous match",
+                        &SelectPrevMatch,
+                    ))
+                    .child(render_nav_button(
+                        ui::Icon::ChevronRight,
+                        self.active_match_index.is_some(),
+                        "Select next match",
+                        &SelectNextMatch,
+                    )),
             )
-        });
-        let switches_column = supported_options.replacement.then(|| {
-            Flex::row()
-                .align_children_center()
-                .with_child(super::toggle_replace_button(
-                    self.replace_enabled,
-                    theme.tooltip.clone(),
-                    theme.search.option_button_component.clone(),
-                ))
-                .constrained()
-                .with_height(theme.search.search_bar_row_height)
-                .contained()
-                .with_style(theme.search.option_button_group)
-        });
-        let mode_column = Flex::row()
-            .with_child(search_button_for_mode(
-                SearchMode::Text,
-                Some(Side::Left),
-                cx,
-            ))
-            .with_child(search_button_for_mode(
-                SearchMode::Regex,
-                Some(Side::Right),
-                cx,
-            ))
-            .contained()
-            .with_style(theme.search.modes_container)
-            .constrained()
-            .with_height(theme.search.search_bar_row_height);
-
-        let nav_column = Flex::row()
-            .align_children_center()
-            .with_children(replace_next)
-            .with_children(replace_all)
-            .with_child(self.render_action_button("icons/select-all.svg", cx))
-            .with_child(Flex::row().with_children(match_count))
-            .with_child(nav_button_for_direction("<", Direction::Prev, cx))
-            .with_child(nav_button_for_direction(">", Direction::Next, cx))
-            .constrained()
-            .with_height(theme.search.search_bar_row_height)
-            .flex_float();
-
-        Flex::row()
-            .with_child(query_column)
-            .with_child(mode_column)
-            .with_children(switches_column)
-            .with_children(replacement)
-            .with_child(nav_column)
-            .contained()
-            .with_style(theme.search.container)
-            .into_any_named("search bar")
+    }
+}
+
+impl FocusableView for BufferSearchBar {
+    fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
+        self.query_editor.focus_handle(cx)
     }
 }
 
@@ -366,23 +387,26 @@ impl ToolbarItemView for BufferSearchBar {
         cx.notify();
         self.active_searchable_item_subscription.take();
         self.active_searchable_item.take();
+
         self.pending_search.take();
 
         if let Some(searchable_item_handle) =
             item.and_then(|item| item.to_searchable_item_handle(cx))
         {
-            let this = cx.weak_handle();
-            self.active_searchable_item_subscription =
-                Some(searchable_item_handle.subscribe_to_search_events(
+            let this = cx.view().downgrade();
+
+            searchable_item_handle
+                .subscribe_to_search_events(
                     cx,
                     Box::new(move |search_event, cx| {
-                        if let Some(this) = this.upgrade(cx) {
+                        if let Some(this) = this.upgrade() {
                             this.update(cx, |this, cx| {
                                 this.on_active_searchable_item_event(search_event, cx)
                             });
                         }
                     }),
-                ));
+                )
+                .detach();
 
             self.active_searchable_item = Some(searchable_item_handle);
             let _ = self.update_matches(cx);
@@ -390,48 +414,105 @@ impl ToolbarItemView for BufferSearchBar {
                 return ToolbarItemLocation::Secondary;
             }
         }
-
         ToolbarItemLocation::Hidden
     }
 
-    fn location_for_event(
-        &self,
-        _: &Self::Event,
-        _: ToolbarItemLocation,
-        _: &AppContext,
-    ) -> ToolbarItemLocation {
-        if self.active_searchable_item.is_some() && !self.dismissed {
-            ToolbarItemLocation::Secondary
-        } else {
-            ToolbarItemLocation::Hidden
-        }
-    }
-
-    fn row_count(&self, _: &ViewContext<Self>) -> usize {
+    fn row_count(&self, _: &WindowContext<'_>) -> usize {
         1
     }
 }
 
 impl BufferSearchBar {
-    pub fn new(cx: &mut ViewContext<Self>) -> Self {
-        let query_editor = cx.add_view(|cx| {
-            Editor::auto_height(
-                2,
-                Some(Arc::new(|theme| theme.search.editor.input.clone())),
-                cx,
-            )
+    fn register(workspace: &mut Workspace) {
+        workspace.register_action(move |workspace, deploy: &Deploy, cx| {
+            let pane = workspace.active_pane();
+
+            pane.update(cx, |this, cx| {
+                this.toolbar().update(cx, |this, cx| {
+                    if let Some(search_bar) = this.item_of_type::<BufferSearchBar>() {
+                        search_bar.update(cx, |this, cx| {
+                            this.deploy(deploy, cx);
+                        });
+                        return;
+                    }
+                    let view = cx.new_view(|cx| BufferSearchBar::new(cx));
+                    this.add_item(view.clone(), cx);
+                    view.update(cx, |this, cx| this.deploy(deploy, cx));
+                    cx.notify();
+                })
+            });
+        });
+        fn register_action<A: Action>(
+            workspace: &mut Workspace,
+            update: fn(&mut BufferSearchBar, &A, &mut ViewContext<BufferSearchBar>),
+        ) {
+            workspace.register_action(move |workspace, action: &A, cx| {
+                let pane = workspace.active_pane();
+                pane.update(cx, move |this, cx| {
+                    this.toolbar().update(cx, move |this, cx| {
+                        if let Some(search_bar) = this.item_of_type::<BufferSearchBar>() {
+                            search_bar.update(cx, move |this, cx| update(this, action, cx));
+                            cx.notify();
+                        }
+                    })
+                });
+            });
+        }
+
+        register_action(workspace, |this, action: &ToggleCaseSensitive, cx| {
+            if this.supported_options().case {
+                this.toggle_case_sensitive(action, cx);
+            }
         });
+        register_action(workspace, |this, action: &ToggleWholeWord, cx| {
+            if this.supported_options().word {
+                this.toggle_whole_word(action, cx);
+            }
+        });
+        register_action(workspace, |this, action: &ToggleReplace, cx| {
+            if this.supported_options().replacement {
+                this.toggle_replace(action, cx);
+            }
+        });
+        register_action(workspace, |this, _: &ActivateRegexMode, cx| {
+            if this.supported_options().regex {
+                this.activate_search_mode(SearchMode::Regex, cx);
+            }
+        });
+        register_action(workspace, |this, _: &ActivateTextMode, cx| {
+            this.activate_search_mode(SearchMode::Text, cx);
+        });
+        register_action(workspace, |this, action: &CycleMode, cx| {
+            if this.supported_options().regex {
+                // If regex is not supported then search has just one mode (text) - in that case there's no point in supporting
+                // cycling.
+                this.cycle_mode(action, cx)
+            }
+        });
+        register_action(workspace, |this, action: &SelectNextMatch, cx| {
+            this.select_next_match(action, cx);
+        });
+        register_action(workspace, |this, action: &SelectPrevMatch, cx| {
+            this.select_prev_match(action, cx);
+        });
+        register_action(workspace, |this, action: &SelectAllMatches, cx| {
+            this.select_all_matches(action, cx);
+        });
+        register_action(workspace, |this, _: &editor::Cancel, cx| {
+            if !this.dismissed {
+                this.dismiss(&Dismiss, cx);
+                return;
+            }
+            cx.propagate();
+        });
+    }
+    pub fn new(cx: &mut ViewContext<Self>) -> Self {
+        let query_editor = cx.new_view(|cx| Editor::single_line(cx));
         cx.subscribe(&query_editor, Self::on_query_editor_event)
             .detach();
-        let replacement_editor = cx.add_view(|cx| {
-            Editor::auto_height(
-                2,
-                Some(Arc::new(|theme| theme.search.editor.input.clone())),
-                cx,
-            )
-        });
-        // cx.subscribe(&replacement_editor, Self::on_query_editor_event)
-        //     .detach();
+        let replacement_editor = cx.new_view(|cx| Editor::single_line(cx));
+        cx.subscribe(&replacement_editor, Self::on_query_editor_event)
+            .detach();
         Self {
             query_editor,
             replacement_editor,
@@ -465,9 +546,13 @@ impl BufferSearchBar {
             }
         }
         if let Some(active_editor) = self.active_searchable_item.as_ref() {
-            cx.focus(active_editor.as_any());
+            let handle = active_editor.focus_handle(cx);
+            cx.focus(&handle);
         }
         cx.emit(Event::UpdateLocation);
+        cx.emit(ToolbarItemEvent::ChangeLocation(
+            ToolbarItemLocation::Hidden,
+        ));
         cx.notify();
     }
 
@@ -476,7 +561,8 @@ impl BufferSearchBar {
             self.search_suggested(cx);
             if deploy.focus {
                 self.select_query(cx);
-                cx.focus_self();
+                let handle = self.query_editor.focus_handle(cx);
+                cx.focus(&handle);
             }
             return true;
         }
@@ -484,6 +570,14 @@ impl BufferSearchBar {
         false
     }
 
+    pub fn toggle(&mut self, action: &Deploy, cx: &mut ViewContext<Self>) {
+        if self.is_dismissed() {
+            self.deploy(action, cx);
+        } else {
+            self.dismiss(&Dismiss, cx);
+        }
+    }
+
     pub fn show(&mut self, cx: &mut ViewContext<Self>) -> bool {
         if self.active_searchable_item.is_none() {
             return false;
@@ -491,9 +585,18 @@ impl BufferSearchBar {
         self.dismissed = false;
         cx.notify();
         cx.emit(Event::UpdateLocation);
+        cx.emit(ToolbarItemEvent::ChangeLocation(
+            ToolbarItemLocation::Secondary,
+        ));
         true
     }
 
+    fn supported_options(&self) -> workspace::searchable::SearchOptions {
+        self.active_searchable_item
+            .as_deref()
+            .map(SearchableItemHandle::supported_options)
+            .unwrap_or_default()
+    }
     pub fn search_suggested(&mut self, cx: &mut ViewContext<Self>) {
         let search = self
             .query_suggestion(cx)
@@ -579,26 +682,14 @@ impl BufferSearchBar {
         self.update_matches(cx)
     }
 
-    fn render_action_button(
+    fn render_search_option_button(
         &self,
-        icon: &'static str,
-        cx: &mut ViewContext<Self>,
-    ) -> AnyElement<Self> {
-        let tooltip = "Select All Matches";
-        let tooltip_style = theme::current(cx).tooltip.clone();
-
-        let theme = theme::current(cx);
-        let style = theme.search.action_button.clone();
-
-        gpui::elements::Component::element(SafeStylable::with_style(
-            theme::components::action_button::Button::action(SelectAllMatches)
-                .with_tooltip(tooltip, tooltip_style)
-                .with_contents(theme::components::svg::Svg::new(icon)),
-            style,
-        ))
-        .into_any()
+        option: SearchOptions,
+        action: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
+    ) -> impl IntoElement {
+        let is_active = self.search_options.contains(option);
+        option.as_button(is_active, action)
     }
-
     pub fn activate_search_mode(&mut self, mode: SearchMode, cx: &mut ViewContext<Self>) {
         assert_ne!(
             mode,
@@ -613,33 +704,10 @@ impl BufferSearchBar {
         cx.notify();
     }
 
-    fn deploy_bar(pane: &mut Pane, action: &Deploy, cx: &mut ViewContext<Pane>) {
-        let mut propagate_action = true;
-        if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
-            search_bar.update(cx, |search_bar, cx| {
-                if search_bar.deploy(action, cx) {
-                    propagate_action = false;
-                }
-            });
-        }
-        if propagate_action {
-            cx.propagate_action();
-        }
-    }
-
-    fn handle_editor_cancel(pane: &mut Pane, _: &editor::Cancel, cx: &mut ViewContext<Pane>) {
-        if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
-            if !search_bar.read(cx).dismissed {
-                search_bar.update(cx, |search_bar, cx| search_bar.dismiss(&Dismiss, cx));
-                return;
-            }
-        }
-        cx.propagate_action();
-    }
-
     pub fn focus_editor(&mut self, _: &FocusEditor, cx: &mut ViewContext<Self>) {
         if let Some(active_editor) = self.active_searchable_item.as_ref() {
-            cx.focus(active_editor.as_any());
+            let handle = active_editor.focus_handle(cx);
+            cx.focus(&handle);
         }
     }
 
@@ -690,6 +758,7 @@ impl BufferSearchBar {
                 {
                     let new_match_index = searchable_item
                         .match_index_for_direction(matches, index, direction, count, cx);
+
                     searchable_item.update_matches(matches, cx);
                     searchable_item.activate_match(new_match_index, matches, cx);
                 }
@@ -713,43 +782,13 @@ impl BufferSearchBar {
         }
     }
 
-    fn select_next_match_on_pane(
-        pane: &mut Pane,
-        action: &SelectNextMatch,
-        cx: &mut ViewContext<Pane>,
-    ) {
-        if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
-            search_bar.update(cx, |bar, cx| bar.select_next_match(action, cx));
-        }
-    }
-
-    fn select_prev_match_on_pane(
-        pane: &mut Pane,
-        action: &SelectPrevMatch,
-        cx: &mut ViewContext<Pane>,
-    ) {
-        if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
-            search_bar.update(cx, |bar, cx| bar.select_prev_match(action, cx));
-        }
-    }
-
-    fn select_all_matches_on_pane(
-        pane: &mut Pane,
-        action: &SelectAllMatches,
-        cx: &mut ViewContext<Pane>,
-    ) {
-        if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
-            search_bar.update(cx, |bar, cx| bar.select_all_matches(action, cx));
-        }
-    }
-
     fn on_query_editor_event(
         &mut self,
-        _: ViewHandle<Editor>,
-        event: &editor::Event,
+        _: View<Editor>,
+        event: &editor::EditorEvent,
         cx: &mut ViewContext<Self>,
     ) {
-        if let editor::Event::Edited { .. } = event {
+        if let editor::EditorEvent::Edited { .. } = event {
             self.query_contains_error = false;
             self.clear_matches(cx);
             let search = self.update_matches(cx);
@@ -761,7 +800,7 @@ impl BufferSearchBar {
         }
     }
 
-    fn on_active_searchable_item_event(&mut self, event: SearchEvent, cx: &mut ViewContext<Self>) {
+    fn on_active_searchable_item_event(&mut self, event: &SearchEvent, cx: &mut ViewContext<Self>) {
         match event {
             SearchEvent::MatchesInvalidated => {
                 let _ = self.update_matches(cx);
@@ -770,6 +809,12 @@ impl BufferSearchBar {
         }
     }
 
+    fn toggle_case_sensitive(&mut self, _: &ToggleCaseSensitive, cx: &mut ViewContext<Self>) {
+        self.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx)
+    }
+    fn toggle_whole_word(&mut self, _: &ToggleWholeWord, cx: &mut ViewContext<Self>) {
+        self.toggle_search_option(SearchOptions::WHOLE_WORD, cx)
+    }
     fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
         let mut active_item_matches = None;
         for (searchable_item, matches) in self.searchable_items_with_matches.drain() {

crates/search/src/mode.rs 🔗

@@ -1,6 +1,7 @@
-use gpui::Action;
+use gpui::{Action, SharedString};
 
 use crate::{ActivateRegexMode, ActivateSemanticMode, ActivateTextMode};
+
 // TODO: Update the default search mode to get from config
 #[derive(Copy, Clone, Debug, Default, PartialEq)]
 pub enum SearchMode {
@@ -10,12 +11,6 @@ pub enum SearchMode {
     Regex,
 }
 
-#[derive(Copy, Clone, Debug, PartialEq)]
-pub(crate) enum Side {
-    Left,
-    Right,
-}
-
 impl SearchMode {
     pub(crate) fn label(&self) -> &'static str {
         match self {
@@ -24,28 +19,14 @@ impl SearchMode {
             SearchMode::Regex => "Regex",
         }
     }
-
-    pub(crate) fn region_id(&self) -> usize {
-        match self {
-            SearchMode::Text => 3,
-            SearchMode::Semantic => 4,
-            SearchMode::Regex => 5,
-        }
+    pub(crate) fn tooltip(&self) -> SharedString {
+        format!("Activate {} Mode", self.label()).into()
     }
-
-    pub(crate) fn tooltip_text(&self) -> &'static str {
-        match self {
-            SearchMode::Text => "Activate Text Search",
-            SearchMode::Semantic => "Activate Semantic Search",
-            SearchMode::Regex => "Activate Regex Search",
-        }
-    }
-
-    pub(crate) fn activate_action(&self) -> Box<dyn Action> {
+    pub(crate) fn action(&self) -> Box<dyn Action> {
         match self {
-            SearchMode::Text => Box::new(ActivateTextMode),
-            SearchMode::Semantic => Box::new(ActivateSemanticMode),
-            SearchMode::Regex => Box::new(ActivateRegexMode),
+            SearchMode::Text => ActivateTextMode.boxed_clone(),
+            SearchMode::Semantic => ActivateSemanticMode.boxed_clone(),
+            SearchMode::Regex => ActivateRegexMode.boxed_clone(),
         }
     }
 }

crates/search/src/project_search.rs 🔗

@@ -1,24 +1,22 @@
 use crate::{
-    history::SearchHistory,
-    mode::{SearchMode, Side},
-    search_bar::{render_nav_button, render_option_button_icon, render_search_mode_button},
-    ActivateRegexMode, ActivateSemanticMode, ActivateTextMode, CycleMode, NextHistoryQuery,
-    PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions, SelectNextMatch, SelectPrevMatch,
-    ToggleCaseSensitive, ToggleIncludeIgnored, ToggleReplace, ToggleWholeWord,
+    history::SearchHistory, mode::SearchMode, ActivateRegexMode, ActivateSemanticMode,
+    ActivateTextMode, CycleMode, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext,
+    SearchOptions, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleIncludeIgnored,
+    ToggleReplace, ToggleWholeWord,
 };
-use anyhow::{Context, Result};
+use anyhow::{Context as _, Result};
 use collections::HashMap;
 use editor::{
-    items::active_match_index, scroll::autoscroll::Autoscroll, Anchor, Editor, MultiBuffer,
-    SelectAll, MAX_TAB_TITLE_LEN,
+    items::active_match_index, scroll::autoscroll::Autoscroll, Anchor, Editor, EditorEvent,
+    MultiBuffer, SelectAll, MAX_TAB_TITLE_LEN,
 };
-use futures::StreamExt;
+use editor::{EditorElement, EditorStyle};
 use gpui::{
-    actions,
-    elements::*,
-    platform::{MouseButton, PromptLevel},
-    Action, AnyElement, AnyViewHandle, AppContext, Entity, ModelContext, ModelHandle, Subscription,
-    Task, View, ViewContext, ViewHandle, WeakModelHandle, WeakViewHandle,
+    actions, div, AnyElement, AnyView, AppContext, Context as _, Element, EntityId, EventEmitter,
+    FocusHandle, FocusableView, FontStyle, FontWeight, Hsla, InteractiveElement, IntoElement,
+    KeyContext, Model, ModelContext, ParentElement, PromptLevel, Render, SharedString, Styled,
+    Subscription, Task, TextStyle, View, ViewContext, VisualContext, WeakModel, WeakView,
+    WhiteSpace, WindowContext,
 };
 use menu::Confirm;
 use project::{
@@ -26,96 +24,57 @@ use project::{
     Entry, Project,
 };
 use semantic_index::{SemanticIndex, SemanticIndexStatus};
-use smallvec::SmallVec;
+
+use settings::Settings;
+use smol::stream::StreamExt;
 use std::{
     any::{Any, TypeId},
-    borrow::Cow,
     collections::HashSet,
     mem,
     ops::{Not, Range},
     path::PathBuf,
-    sync::Arc,
     time::{Duration, Instant},
 };
+use theme::ThemeSettings;
+
+use ui::{
+    h_stack, prelude::*, v_stack, Button, Icon, IconButton, IconElement, Label, LabelCommon,
+    LabelSize, Selectable, Tooltip,
+};
 use util::{paths::PathMatcher, ResultExt as _};
 use workspace::{
     item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
     searchable::{Direction, SearchableItem, SearchableItemHandle},
-    ItemNavHistory, Pane, ToolbarItemLocation, ToolbarItemView, Workspace, WorkspaceId,
+    ItemNavHistory, Pane, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
+    WorkspaceId,
 };
 
 actions!(
     project_search,
-    [SearchInNew, ToggleFocus, NextField, ToggleFilters,]
+    [SearchInNew, ToggleFocus, NextField, ToggleFilters]
 );
 
 #[derive(Default)]
-struct ActiveSearches(HashMap<WeakModelHandle<Project>, WeakViewHandle<ProjectSearchView>>);
+struct ActiveSearches(HashMap<WeakModel<Project>, WeakView<ProjectSearchView>>);
 
 #[derive(Default)]
-struct ActiveSettings(HashMap<WeakModelHandle<Project>, ProjectSearchSettings>);
+struct ActiveSettings(HashMap<WeakModel<Project>, ProjectSearchSettings>);
 
 pub fn init(cx: &mut AppContext) {
+    // todo!() po
     cx.set_global(ActiveSearches::default());
     cx.set_global(ActiveSettings::default());
-    cx.add_action(ProjectSearchView::deploy);
-    cx.add_action(ProjectSearchView::move_focus_to_results);
-    cx.add_action(ProjectSearchBar::confirm);
-    cx.add_action(ProjectSearchBar::search_in_new);
-    cx.add_action(ProjectSearchBar::select_next_match);
-    cx.add_action(ProjectSearchBar::select_prev_match);
-    cx.add_action(ProjectSearchBar::replace_next);
-    cx.add_action(ProjectSearchBar::replace_all);
-    cx.add_action(ProjectSearchBar::cycle_mode);
-    cx.add_action(ProjectSearchBar::next_history_query);
-    cx.add_action(ProjectSearchBar::previous_history_query);
-    cx.add_action(ProjectSearchBar::activate_regex_mode);
-    cx.add_action(ProjectSearchBar::toggle_replace);
-    cx.add_action(ProjectSearchBar::toggle_replace_on_a_pane);
-    cx.add_action(ProjectSearchBar::activate_text_mode);
-
-    // This action should only be registered if the semantic index is enabled
-    // We are registering it all the time, as I dont want to introduce a dependency
-    // for Semantic Index Settings globally whenever search is tested.
-    cx.add_action(ProjectSearchBar::activate_semantic_mode);
-
-    cx.capture_action(ProjectSearchBar::tab);
-    cx.capture_action(ProjectSearchBar::tab_previous);
-    cx.capture_action(ProjectSearchView::replace_all);
-    cx.capture_action(ProjectSearchView::replace_next);
-    add_toggle_option_action::<ToggleCaseSensitive>(SearchOptions::CASE_SENSITIVE, cx);
-    add_toggle_option_action::<ToggleWholeWord>(SearchOptions::WHOLE_WORD, cx);
-    add_toggle_option_action::<ToggleIncludeIgnored>(SearchOptions::INCLUDE_IGNORED, cx);
-    add_toggle_filters_action::<ToggleFilters>(cx);
-}
-
-fn add_toggle_filters_action<A: Action>(cx: &mut AppContext) {
-    cx.add_action(move |pane: &mut Pane, _: &A, cx: &mut ViewContext<Pane>| {
-        if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<ProjectSearchBar>() {
-            if search_bar.update(cx, |search_bar, cx| search_bar.toggle_filters(cx)) {
-                return;
-            }
-        }
-        cx.propagate_action();
-    });
-}
-
-fn add_toggle_option_action<A: Action>(option: SearchOptions, cx: &mut AppContext) {
-    cx.add_action(move |pane: &mut Pane, _: &A, cx: &mut ViewContext<Pane>| {
-        if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<ProjectSearchBar>() {
-            if search_bar.update(cx, |search_bar, cx| {
-                search_bar.toggle_search_option(option, cx)
-            }) {
-                return;
-            }
-        }
-        cx.propagate_action();
-    });
+    cx.observe_new_views(|workspace: &mut Workspace, _cx| {
+        workspace
+            .register_action(ProjectSearchView::deploy)
+            .register_action(ProjectSearchBar::search_in_new);
+    })
+    .detach();
 }
 
 struct ProjectSearch {
-    project: ModelHandle<Project>,
-    excerpts: ModelHandle<MultiBuffer>,
+    project: Model<Project>,
+    excerpts: Model<MultiBuffer>,
     pending_search: Option<Task<Option<()>>>,
     match_ranges: Vec<Range<Anchor>>,
     active_query: Option<SearchQuery>,
@@ -132,10 +91,11 @@ enum InputPanel {
 }
 
 pub struct ProjectSearchView {
-    model: ModelHandle<ProjectSearch>,
-    query_editor: ViewHandle<Editor>,
-    replacement_editor: ViewHandle<Editor>,
-    results_editor: ViewHandle<Editor>,
+    focus_handle: FocusHandle,
+    model: Model<ProjectSearch>,
+    query_editor: View<Editor>,
+    replacement_editor: View<Editor>,
+    results_editor: View<Editor>,
     semantic_state: Option<SemanticState>,
     semantic_permissioned: Option<bool>,
     search_options: SearchOptions,
@@ -143,11 +103,12 @@ pub struct ProjectSearchView {
     active_match_index: Option<usize>,
     search_id: usize,
     query_editor_was_focused: bool,
-    included_files_editor: ViewHandle<Editor>,
-    excluded_files_editor: ViewHandle<Editor>,
+    included_files_editor: View<Editor>,
+    excluded_files_editor: View<Editor>,
     filters_enabled: bool,
     replace_enabled: bool,
     current_mode: SearchMode,
+    _subscriptions: Vec<Subscription>,
 }
 
 struct SemanticState {
@@ -164,20 +125,16 @@ struct ProjectSearchSettings {
 }
 
 pub struct ProjectSearchBar {
-    active_project_search: Option<ViewHandle<ProjectSearchView>>,
+    active_project_search: Option<View<ProjectSearchView>>,
     subscription: Option<Subscription>,
 }
 
-impl Entity for ProjectSearch {
-    type Event = ();
-}
-
 impl ProjectSearch {
-    fn new(project: ModelHandle<Project>, cx: &mut ModelContext<Self>) -> Self {
+    fn new(project: Model<Project>, cx: &mut ModelContext<Self>) -> Self {
         let replica_id = project.read(cx).replica_id();
         Self {
             project,
-            excerpts: cx.add_model(|_| MultiBuffer::new(replica_id)),
+            excerpts: cx.new_model(|_| MultiBuffer::new(replica_id)),
             pending_search: Default::default(),
             match_ranges: Default::default(),
             active_query: None,
@@ -187,12 +144,12 @@ impl ProjectSearch {
         }
     }
 
-    fn clone(&self, cx: &mut ModelContext<Self>) -> ModelHandle<Self> {
-        cx.add_model(|cx| Self {
+    fn clone(&self, cx: &mut ModelContext<Self>) -> Model<Self> {
+        cx.new_model(|cx| Self {
             project: self.project.clone(),
             excerpts: self
                 .excerpts
-                .update(cx, |excerpts, cx| cx.add_model(|cx| excerpts.clone(cx))),
+                .update(cx, |excerpts, cx| cx.new_model(|cx| excerpts.clone(cx))),
             pending_search: Default::default(),
             match_ranges: self.match_ranges.clone(),
             active_query: self.active_query.clone(),
@@ -210,33 +167,38 @@ impl ProjectSearch {
         self.search_history.add(query.as_str().to_string());
         self.active_query = Some(query);
         self.match_ranges.clear();
-        self.pending_search = Some(cx.spawn_weak(|this, mut cx| async move {
+        self.pending_search = Some(cx.spawn(|this, mut cx| async move {
             let mut matches = search;
-            let this = this.upgrade(&cx)?;
+            let this = this.upgrade()?;
             this.update(&mut cx, |this, cx| {
                 this.match_ranges.clear();
                 this.excerpts.update(cx, |this, cx| this.clear(cx));
                 this.no_results = Some(true);
-            });
+            })
+            .ok()?;
 
             while let Some((buffer, anchors)) = matches.next().await {
-                let mut ranges = this.update(&mut cx, |this, cx| {
-                    this.no_results = Some(false);
-                    this.excerpts.update(cx, |excerpts, cx| {
-                        excerpts.stream_excerpts_with_context_lines(buffer, anchors, 1, cx)
+                let mut ranges = this
+                    .update(&mut cx, |this, cx| {
+                        this.no_results = Some(false);
+                        this.excerpts.update(cx, |excerpts, cx| {
+                            excerpts.stream_excerpts_with_context_lines(buffer, anchors, 1, cx)
+                        })
                     })
-                });
+                    .ok()?;
 
                 while let Some(range) = ranges.next().await {
-                    this.update(&mut cx, |this, _| this.match_ranges.push(range));
+                    this.update(&mut cx, |this, _| this.match_ranges.push(range))
+                        .ok()?;
                 }
-                this.update(&mut cx, |_, cx| cx.notify());
+                this.update(&mut cx, |_, cx| cx.notify()).ok()?;
             }
 
             this.update(&mut cx, |this, cx| {
                 this.pending_search.take();
                 cx.notify();
-            });
+            })
+            .ok()?;
 
             None
         }));
@@ -271,14 +233,17 @@ impl ProjectSearch {
                 this.excerpts.update(cx, |excerpts, cx| {
                     excerpts.clear(cx);
                 });
-            });
+            })
+            .ok()?;
             for (buffer, ranges) in matches {
-                let mut match_ranges = this.update(&mut cx, |this, cx| {
-                    this.no_results = Some(false);
-                    this.excerpts.update(cx, |excerpts, cx| {
-                        excerpts.stream_excerpts_with_context_lines(buffer, ranges, 3, cx)
+                let mut match_ranges = this
+                    .update(&mut cx, |this, cx| {
+                        this.no_results = Some(false);
+                        this.excerpts.update(cx, |excerpts, cx| {
+                            excerpts.stream_excerpts_with_context_lines(buffer, ranges, 3, cx)
+                        })
                     })
-                });
+                    .ok()?;
                 while let Some(match_range) = match_ranges.next().await {
                     this.update(&mut cx, |this, cx| {
                         this.match_ranges.push(match_range);
@@ -286,14 +251,16 @@ impl ProjectSearch {
                             this.match_ranges.push(match_range);
                         }
                         cx.notify();
-                    });
+                    })
+                    .ok()?;
                 }
             }
 
             this.update(&mut cx, |this, cx| {
                 this.pending_search.take();
                 cx.notify();
-            });
+            })
+            .ok()?;
 
             None
         }));
@@ -305,221 +272,120 @@ impl ProjectSearch {
 pub enum ViewEvent {
     UpdateTab,
     Activate,
-    EditorEvent(editor::Event),
+    EditorEvent(editor::EditorEvent),
     Dismiss,
 }
 
-impl Entity for ProjectSearchView {
-    type Event = ViewEvent;
-}
-
-impl View for ProjectSearchView {
-    fn ui_name() -> &'static str {
-        "ProjectSearchView"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let model = &self.model.read(cx);
-        if model.match_ranges.is_empty() {
-            enum Status {}
-
-            let theme = theme::current(cx).clone();
+impl EventEmitter<ViewEvent> for ProjectSearchView {}
 
-            // If Search is Active -> Major: Searching..., Minor: None
-            // If Semantic -> Major: "Search using Natural Language", Minor: {Status}/n{ex...}/n{ex...}
-            // If Regex -> Major: "Search using Regex", Minor: {ex...}
-            // If Text -> Major: "Text search all files and folders", Minor: {...}
-
-            let current_mode = self.current_mode;
-            let mut major_text = if model.pending_search.is_some() {
-                Cow::Borrowed("Searching...")
-            } else if model.no_results.is_some_and(|v| v) {
-                Cow::Borrowed("No Results")
+impl Render for ProjectSearchView {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        if self.has_matches() {
+            div()
+                .flex_1()
+                .size_full()
+                .track_focus(&self.focus_handle)
+                .child(self.results_editor.clone())
+                .into_any()
+        } else {
+            let model = self.model.read(cx);
+            let has_no_results = model.no_results.unwrap_or(false);
+            let is_search_underway = model.pending_search.is_some();
+            let mut major_text = if is_search_underway {
+                Label::new("Searching...")
+            } else if has_no_results {
+                Label::new("No results")
             } else {
-                match current_mode {
-                    SearchMode::Text => Cow::Borrowed("Text search all files and folders"),
-                    SearchMode::Semantic => {
-                        Cow::Borrowed("Search all code objects using Natural Language")
-                    }
-                    SearchMode::Regex => Cow::Borrowed("Regex search all files and folders"),
-                }
+                Label::new(format!("{} search all files", self.current_mode.label()))
             };
 
             let mut show_minor_text = true;
             let semantic_status = self.semantic_state.as_ref().and_then(|semantic| {
                 let status = semantic.index_status;
-                match status {
-                    SemanticIndexStatus::NotAuthenticated => {
-                        major_text = Cow::Borrowed("Not Authenticated");
-                        show_minor_text = false;
-                        Some(vec![
-                            "API Key Missing: Please set 'OPENAI_API_KEY' in Environment Variables."
-                                .to_string(), "If you authenticated using the Assistant Panel, please restart Zed to Authenticate.".to_string()])
-                    }
-                    SemanticIndexStatus::Indexed => Some(vec!["Indexing complete".to_string()]),
-                    SemanticIndexStatus::Indexing {
-                        remaining_files,
-                        rate_limit_expiry,
-                    } => {
-                        if remaining_files == 0 {
-                            Some(vec![format!("Indexing...")])
-                        } else {
-                            if let Some(rate_limit_expiry) = rate_limit_expiry {
-                                let remaining_seconds =
-                                    rate_limit_expiry.duration_since(Instant::now());
-                                if remaining_seconds > Duration::from_secs(0) {
-                                    Some(vec![format!(
-                                        "Remaining files to index (rate limit resets in {}s): {}",
-                                        remaining_seconds.as_secs(),
-                                        remaining_files
-                                    )])
-                                } else {
-                                    Some(vec![format!("Remaining files to index: {}", remaining_files)])
+                                match status {
+                                    SemanticIndexStatus::NotAuthenticated => {
+                                        major_text = Label::new("Not Authenticated");
+                                        show_minor_text = false;
+                                        Some(
+                                            "API Key Missing: Please set 'OPENAI_API_KEY' in Environment Variables. If you authenticated using the Assistant Panel, please restart Zed to Authenticate.".to_string())
+                                    }
+                                    SemanticIndexStatus::Indexed => Some("Indexing complete".to_string()),
+                                    SemanticIndexStatus::Indexing {
+                                        remaining_files,
+                                        rate_limit_expiry,
+                                    } => {
+                                        if remaining_files == 0 {
+                                            Some("Indexing...".to_string())
+                                        } else {
+                                            if let Some(rate_limit_expiry) = rate_limit_expiry {
+                                                let remaining_seconds =
+                                                    rate_limit_expiry.duration_since(Instant::now());
+                                                if remaining_seconds > Duration::from_secs(0) {
+                                                    Some(format!(
+                                                        "Remaining files to index (rate limit resets in {}s): {}",
+                                                        remaining_seconds.as_secs(),
+                                                        remaining_files
+                                                    ))
+                                                } else {
+                                                    Some(format!("Remaining files to index: {}", remaining_files))
+                                                }
+                                            } else {
+                                                Some(format!("Remaining files to index: {}", remaining_files))
+                                            }
+                                        }
+                                    }
+                                    SemanticIndexStatus::NotIndexed => None,
                                 }
-                            } else {
-                                Some(vec![format!("Remaining files to index: {}", remaining_files)])
-                            }
-                        }
-                    }
-                    SemanticIndexStatus::NotIndexed => None,
-                }
             });
+            let major_text = div().justify_center().max_w_96().child(major_text);
 
-            let minor_text = if let Some(no_results) = model.no_results {
+            let minor_text: Option<SharedString> = if let Some(no_results) = model.no_results {
                 if model.pending_search.is_none() && no_results {
-                    vec!["No results found in this project for the provided query".to_owned()]
+                    Some("No results found in this project for the provided query".into())
                 } else {
-                    vec![]
+                    None
                 }
             } else {
-                match current_mode {
-                    SearchMode::Semantic => {
-                        let mut minor_text: Vec<String> = Vec::new();
-                        minor_text.push("".into());
-                        if let Some(semantic_status) = semantic_status {
-                            minor_text.extend(semantic_status);
-                        }
-                        if show_minor_text {
-                            minor_text
-                                .push("Simply explain the code you are looking to find.".into());
-                            minor_text.push(
-                                "ex. 'prompt user for permissions to index their project'".into(),
-                            );
-                        }
-                        minor_text
-                    }
-                    _ => vec![
-                        "".to_owned(),
-                        "Include/exclude specific paths with the filter option.".to_owned(),
-                        "Matching exact word and/or casing is available too.".to_owned(),
-                    ],
-                }
-            };
-
-            let previous_query_keystrokes =
-                cx.binding_for_action(&PreviousHistoryQuery {})
-                    .map(|binding| {
-                        binding
-                            .keystrokes()
-                            .iter()
-                            .map(|k| k.to_string())
-                            .collect::<Vec<_>>()
-                    });
-            let next_query_keystrokes =
-                cx.binding_for_action(&NextHistoryQuery {}).map(|binding| {
-                    binding
-                        .keystrokes()
-                        .iter()
-                        .map(|k| k.to_string())
-                        .collect::<Vec<_>>()
-                });
-            let new_placeholder_text = match (previous_query_keystrokes, next_query_keystrokes) {
-                (Some(previous_query_keystrokes), Some(next_query_keystrokes)) => {
-                    format!(
-                        "Search ({}/{} for previous/next query)",
-                        previous_query_keystrokes.join(" "),
-                        next_query_keystrokes.join(" ")
-                    )
-                }
-                (None, Some(next_query_keystrokes)) => {
-                    format!(
-                        "Search ({} for next query)",
-                        next_query_keystrokes.join(" ")
-                    )
-                }
-                (Some(previous_query_keystrokes), None) => {
-                    format!(
-                        "Search ({} for previous query)",
-                        previous_query_keystrokes.join(" ")
-                    )
+                if let Some(mut semantic_status) = semantic_status {
+                    semantic_status.extend(self.landing_text_minor().chars());
+                    Some(semantic_status.into())
+                } else {
+                    Some(self.landing_text_minor())
                 }
-                (None, None) => String::new(),
             };
-            self.query_editor.update(cx, |editor, cx| {
-                editor.set_placeholder_text(new_placeholder_text, cx);
+            let minor_text = minor_text.map(|text| {
+                div()
+                    .items_center()
+                    .max_w_96()
+                    .child(Label::new(text).size(LabelSize::Small))
             });
-
-            MouseEventHandler::new::<Status, _>(0, cx, |_, _| {
-                Flex::column()
-                    .with_child(Flex::column().contained().flex(1., true))
-                    .with_child(
-                        Flex::column()
-                            .align_children_center()
-                            .with_child(Label::new(
-                                major_text,
-                                theme.search.major_results_status.clone(),
-                            ))
-                            .with_children(
-                                minor_text.into_iter().map(|x| {
-                                    Label::new(x, theme.search.minor_results_status.clone())
-                                }),
-                            )
-                            .aligned()
-                            .top()
-                            .contained()
-                            .flex(7., true),
-                    )
-                    .contained()
-                    .with_background_color(theme.editor.background)
-            })
-            .on_down(MouseButton::Left, |_, _, cx| {
-                cx.focus_parent();
-            })
-            .into_any_named("project search view")
-        } else {
-            ChildView::new(&self.results_editor, cx)
-                .flex(1., true)
-                .into_any_named("project search view")
+            v_stack()
+                .flex_1()
+                .size_full()
+                .justify_center()
+                .track_focus(&self.focus_handle)
+                .child(
+                    h_stack()
+                        .size_full()
+                        .justify_center()
+                        .child(h_stack().flex_1())
+                        .child(v_stack().child(major_text).children(minor_text))
+                        .child(h_stack().flex_1()),
+                )
+                .into_any()
         }
     }
+}
 
-    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
-        let handle = cx.weak_handle();
-        cx.update_global(|state: &mut ActiveSearches, cx| {
-            state
-                .0
-                .insert(self.model.read(cx).project.downgrade(), handle)
-        });
-
-        cx.update_global(|state: &mut ActiveSettings, cx| {
-            state.0.insert(
-                self.model.read(cx).project.downgrade(),
-                self.current_settings(),
-            );
-        });
-
-        if cx.is_self_focused() {
-            if self.query_editor_was_focused {
-                cx.focus(&self.query_editor);
-            } else {
-                cx.focus(&self.results_editor);
-            }
-        }
+impl FocusableView for ProjectSearchView {
+    fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
+        self.focus_handle.clone()
     }
 }
 
 impl Item for ProjectSearchView {
-    fn tab_tooltip_text(&self, cx: &AppContext) -> Option<Cow<str>> {
+    type Event = ViewEvent;
+    fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
         let query_text = self.query_editor.read(cx).text(cx);
 
         query_text
@@ -528,20 +394,17 @@ impl Item for ProjectSearchView {
             .then(|| query_text.into())
             .or_else(|| Some("Project Search".into()))
     }
-    fn should_close_item_on_event(event: &Self::Event) -> bool {
-        event == &Self::Event::Dismiss
-    }
 
     fn act_as_type<'a>(
         &'a self,
         type_id: TypeId,
-        self_handle: &'a ViewHandle<Self>,
+        self_handle: &'a View<Self>,
         _: &'a AppContext,
-    ) -> Option<&'a AnyViewHandle> {
+    ) -> Option<AnyView> {
         if type_id == TypeId::of::<Self>() {
-            Some(self_handle)
+            Some(self_handle.clone().into())
         } else if type_id == TypeId::of::<Editor>() {
-            Some(&self.results_editor)
+            Some(self.results_editor.clone().into())
         } else {
             None
         }
@@ -552,45 +415,40 @@ impl Item for ProjectSearchView {
             .update(cx, |editor, cx| editor.deactivated(cx));
     }
 
-    fn tab_content<T: 'static>(
-        &self,
-        _detail: Option<usize>,
-        tab_theme: &theme::Tab,
-        cx: &AppContext,
-    ) -> AnyElement<T> {
-        Flex::row()
-            .with_child(
-                Svg::new("icons/magnifying_glass.svg")
-                    .with_color(tab_theme.label.text.color)
-                    .constrained()
-                    .with_width(tab_theme.type_icon_width)
-                    .aligned()
-                    .contained()
-                    .with_margin_right(tab_theme.spacing),
-            )
-            .with_child({
-                let tab_name: Option<Cow<_>> = self
-                    .model
-                    .read(cx)
-                    .search_history
-                    .current()
-                    .as_ref()
-                    .map(|query| {
-                        let query_text = util::truncate_and_trailoff(query, MAX_TAB_TITLE_LEN);
-                        query_text.into()
-                    });
-                Label::new(
-                    tab_name
-                        .filter(|name| !name.is_empty())
-                        .unwrap_or("Project search".into()),
-                    tab_theme.label.clone(),
-                )
-                .aligned()
-            })
+    fn tab_content(&self, _: Option<usize>, selected: bool, cx: &WindowContext<'_>) -> AnyElement {
+        let last_query: Option<SharedString> = self
+            .model
+            .read(cx)
+            .search_history
+            .current()
+            .as_ref()
+            .map(|query| {
+                let query_text = util::truncate_and_trailoff(query, MAX_TAB_TITLE_LEN);
+                query_text.into()
+            });
+        let tab_name = last_query
+            .filter(|query| !query.is_empty())
+            .unwrap_or_else(|| "Project search".into());
+        h_stack()
+            .gap_2()
+            .child(IconElement::new(Icon::MagnifyingGlass).color(if selected {
+                Color::Default
+            } else {
+                Color::Muted
+            }))
+            .child(Label::new(tab_name).color(if selected {
+                Color::Default
+            } else {
+                Color::Muted
+            }))
             .into_any()
     }
 
-    fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) {
+    fn for_each_project_item(
+        &self,
+        cx: &AppContext,
+        f: &mut dyn FnMut(EntityId, &dyn project::Item),
+    ) {
         self.results_editor.for_each_project_item(cx, f)
     }
 
@@ -612,7 +470,7 @@ impl Item for ProjectSearchView {
 
     fn save(
         &mut self,
-        project: ModelHandle<Project>,
+        project: Model<Project>,
         cx: &mut ViewContext<Self>,
     ) -> Task<anyhow::Result<()>> {
         self.results_editor
@@ -621,7 +479,7 @@ impl Item for ProjectSearchView {
 
     fn save_as(
         &mut self,
-        _: ModelHandle<Project>,
+        _: Model<Project>,
         _: PathBuf,
         _: &mut ViewContext<Self>,
     ) -> Task<anyhow::Result<()>> {
@@ -630,19 +488,23 @@ impl Item for ProjectSearchView {
 
     fn reload(
         &mut self,
-        project: ModelHandle<Project>,
+        project: Model<Project>,
         cx: &mut ViewContext<Self>,
     ) -> Task<anyhow::Result<()>> {
         self.results_editor
             .update(cx, |editor, cx| editor.reload(project, cx))
     }
 
-    fn clone_on_split(&self, _workspace_id: WorkspaceId, cx: &mut ViewContext<Self>) -> Option<Self>
+    fn clone_on_split(
+        &self,
+        _workspace_id: WorkspaceId,
+        cx: &mut ViewContext<Self>,
+    ) -> Option<View<Self>>
     where
         Self: Sized,
     {
         let model = self.model.update(cx, |model, cx| model.clone(cx));
-        Some(Self::new(model, cx, None))
+        Some(cx.new_view(|cx| Self::new(model, cx, None)))
     }
 
     fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
@@ -661,14 +523,17 @@ impl Item for ProjectSearchView {
             .update(cx, |editor, cx| editor.navigate(data, cx))
     }
 
-    fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
+    fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) {
         match event {
             ViewEvent::UpdateTab => {
-                smallvec::smallvec![ItemEvent::UpdateBreadcrumbs, ItemEvent::UpdateTab]
+                f(ItemEvent::UpdateBreadcrumbs);
+                f(ItemEvent::UpdateTab);
+            }
+            ViewEvent::EditorEvent(editor_event) => {
+                Editor::to_item_events(editor_event, f);
             }
-            ViewEvent::EditorEvent(editor_event) => Editor::to_item_events(editor_event),
-            ViewEvent::Dismiss => smallvec::smallvec![ItemEvent::CloseItem],
-            _ => SmallVec::new(),
+            ViewEvent::Dismiss => f(ItemEvent::CloseItem),
+            _ => {}
         }
     }
 
@@ -689,12 +554,12 @@ impl Item for ProjectSearchView {
     }
 
     fn deserialize(
-        _project: ModelHandle<Project>,
-        _workspace: WeakViewHandle<Workspace>,
+        _project: Model<Project>,
+        _workspace: WeakView<Workspace>,
         _workspace_id: workspace::WorkspaceId,
         _item_id: workspace::ItemId,
         _cx: &mut ViewContext<Pane>,
-    ) -> Task<anyhow::Result<ViewHandle<Self>>> {
+    ) -> Task<anyhow::Result<View<Self>>> {
         unimplemented!()
     }
 }
@@ -751,7 +616,7 @@ impl ProjectSearchView {
 
     fn semantic_index_changed(
         &mut self,
-        semantic_index: ModelHandle<SemanticIndex>,
+        semantic_index: Model<SemanticIndex>,
         cx: &mut ViewContext<Self>,
     ) {
         let project = self.model.read(cx).project.clone();
@@ -767,7 +632,7 @@ impl ProjectSearchView {
                     semantic_state.maintain_rate_limit =
                         Some(cx.spawn(|this, mut cx| async move {
                             loop {
-                                cx.background().timer(Duration::from_secs(1)).await;
+                                cx.background_executor().timer(Duration::from_secs(1)).await;
                                 this.update(&mut cx, |_, cx| cx.notify()).log_err();
                             }
                         }));
@@ -809,7 +674,7 @@ impl ProjectSearchView {
                     let has_permission = has_permission.await?;
 
                     if !has_permission {
-                        let mut answer = this.update(&mut cx, |this, cx| {
+                        let answer = this.update(&mut cx, |this, cx| {
                             let project = this.model.read(cx).project.clone();
                             let project_name = project
                                 .read(cx)
@@ -829,7 +694,7 @@ impl ProjectSearchView {
                             )
                         })?;
 
-                        if answer.next().await == Some(0) {
+                        if answer.await? == 0 {
                             this.update(&mut cx, |this, _| {
                                 this.semantic_permissioned = Some(true);
                             })?;
@@ -907,7 +772,7 @@ impl ProjectSearchView {
     }
 
     fn new(
-        model: ModelHandle<ProjectSearch>,
+        model: Model<ProjectSearch>,
         cx: &mut ViewContext<Self>,
         settings: Option<ProjectSearchSettings>,
     ) -> Self {
@@ -915,6 +780,7 @@ impl ProjectSearchView {
         let excerpts;
         let mut replacement_text = None;
         let mut query_text = String::new();
+        let mut subscriptions = Vec::new();
 
         // Read in settings if available
         let (mut options, current_mode, filters_enabled) = if let Some(settings) = settings {
@@ -937,87 +803,85 @@ impl ProjectSearchView {
                 options = SearchOptions::from_query(active_query);
             }
         }
-        cx.observe(&model, |this, _, cx| this.model_changed(cx))
-            .detach();
+        subscriptions.push(cx.observe(&model, |this, _, cx| this.model_changed(cx)));
 
-        let query_editor = cx.add_view(|cx| {
-            let mut editor = Editor::single_line(
-                Some(Arc::new(|theme| theme.search.editor.input.clone())),
-                cx,
-            );
+        let query_editor = cx.new_view(|cx| {
+            let mut editor = Editor::single_line(cx);
             editor.set_placeholder_text("Text search all files", cx);
             editor.set_text(query_text, cx);
             editor
         });
         // Subscribe to query_editor in order to reraise editor events for workspace item activation purposes
-        cx.subscribe(&query_editor, |_, _, event, cx| {
-            cx.emit(ViewEvent::EditorEvent(event.clone()))
-        })
-        .detach();
-        let replacement_editor = cx.add_view(|cx| {
-            let mut editor = Editor::single_line(
-                Some(Arc::new(|theme| theme.search.editor.input.clone())),
-                cx,
-            );
+        subscriptions.push(
+            cx.subscribe(&query_editor, |_, _, event: &EditorEvent, cx| {
+                cx.emit(ViewEvent::EditorEvent(event.clone()))
+            }),
+        );
+        let replacement_editor = cx.new_view(|cx| {
+            let mut editor = Editor::single_line(cx);
             editor.set_placeholder_text("Replace in project..", cx);
             if let Some(text) = replacement_text {
                 editor.set_text(text, cx);
             }
             editor
         });
-        let results_editor = cx.add_view(|cx| {
+        let results_editor = cx.new_view(|cx| {
             let mut editor = Editor::for_multibuffer(excerpts, Some(project.clone()), cx);
             editor.set_searchable(false);
             editor
         });
-        cx.observe(&results_editor, |_, _, cx| cx.emit(ViewEvent::UpdateTab))
-            .detach();
+        subscriptions.push(cx.observe(&results_editor, |_, _, cx| cx.emit(ViewEvent::UpdateTab)));
 
-        cx.subscribe(&results_editor, |this, _, event, cx| {
-            if matches!(event, editor::Event::SelectionsChanged { .. }) {
-                this.update_match_index(cx);
-            }
-            // Reraise editor events for workspace item activation purposes
-            cx.emit(ViewEvent::EditorEvent(event.clone()));
-        })
-        .detach();
+        subscriptions.push(
+            cx.subscribe(&results_editor, |this, _, event: &EditorEvent, cx| {
+                if matches!(event, editor::EditorEvent::SelectionsChanged { .. }) {
+                    this.update_match_index(cx);
+                }
+                // Reraise editor events for workspace item activation purposes
+                cx.emit(ViewEvent::EditorEvent(event.clone()));
+            }),
+        );
 
-        let included_files_editor = cx.add_view(|cx| {
-            let mut editor = Editor::single_line(
-                Some(Arc::new(|theme| {
-                    theme.search.include_exclude_editor.input.clone()
-                })),
-                cx,
-            );
+        let included_files_editor = cx.new_view(|cx| {
+            let mut editor = Editor::single_line(cx);
             editor.set_placeholder_text("Include: crates/**/*.toml", cx);
 
             editor
         });
         // Subscribe to include_files_editor in order to reraise editor events for workspace item activation purposes
-        cx.subscribe(&included_files_editor, |_, _, event, cx| {
-            cx.emit(ViewEvent::EditorEvent(event.clone()))
-        })
-        .detach();
+        subscriptions.push(
+            cx.subscribe(&included_files_editor, |_, _, event: &EditorEvent, cx| {
+                cx.emit(ViewEvent::EditorEvent(event.clone()))
+            }),
+        );
 
-        let excluded_files_editor = cx.add_view(|cx| {
-            let mut editor = Editor::single_line(
-                Some(Arc::new(|theme| {
-                    theme.search.include_exclude_editor.input.clone()
-                })),
-                cx,
-            );
+        let excluded_files_editor = cx.new_view(|cx| {
+            let mut editor = Editor::single_line(cx);
             editor.set_placeholder_text("Exclude: vendor/*, *.lock", cx);
 
             editor
         });
         // Subscribe to excluded_files_editor in order to reraise editor events for workspace item activation purposes
-        cx.subscribe(&excluded_files_editor, |_, _, event, cx| {
-            cx.emit(ViewEvent::EditorEvent(event.clone()))
-        })
-        .detach();
+        subscriptions.push(
+            cx.subscribe(&excluded_files_editor, |_, _, event: &EditorEvent, cx| {
+                cx.emit(ViewEvent::EditorEvent(event.clone()))
+            }),
+        );
+
+        let focus_handle = cx.focus_handle();
+        subscriptions.push(cx.on_focus_in(&focus_handle, |this, cx| {
+            if this.focus_handle.is_focused(cx) {
+                if this.has_matches() {
+                    this.results_editor.focus_handle(cx).focus(cx);
+                } else {
+                    this.query_editor.focus_handle(cx).focus(cx);
+                }
+            }
+        }));
 
         // Check if Worktrees have all been previously indexed
         let mut this = ProjectSearchView {
+            focus_handle,
             replacement_editor,
             search_id: model.read(cx).search_id,
             model,

crates/search/src/search.rs 🔗

@@ -1,16 +1,11 @@
 use bitflags::bitflags;
 pub use buffer_search::BufferSearchBar;
-use gpui::{
-    actions,
-    elements::{Component, SafeStylable, TooltipStyle},
-    Action, AnyElement, AppContext, Element, View,
-};
+use gpui::{actions, Action, AppContext, IntoElement};
 pub use mode::SearchMode;
 use project::search::SearchQuery;
-pub use project_search::{ProjectSearchBar, ProjectSearchView};
-use theme::components::{
-    action_button::Button, svg::Svg, ComponentExt, IconButtonStyle, ToggleIconButtonStyle,
-};
+pub use project_search::ProjectSearchView;
+use ui::{prelude::*, Tooltip};
+use ui::{ButtonStyle, IconButton};
 
 pub mod buffer_search;
 mod history;
@@ -19,6 +14,7 @@ pub mod project_search;
 pub(crate) mod search_bar;
 
 pub fn init(cx: &mut AppContext) {
+    menu::init();
     buffer_search::init(cx);
     project_search::init(cx);
 }
@@ -57,28 +53,28 @@ bitflags! {
 impl SearchOptions {
     pub fn label(&self) -> &'static str {
         match *self {
-            Self::WHOLE_WORD => "Match Whole Word",
-            Self::CASE_SENSITIVE => "Match Case",
-            Self::INCLUDE_IGNORED => "Include Ignored",
-            _ => panic!("{self:?} is not a named SearchOption"),
+            SearchOptions::WHOLE_WORD => "Match Whole Word",
+            SearchOptions::CASE_SENSITIVE => "Match Case",
+            SearchOptions::INCLUDE_IGNORED => "Include ignored",
+            _ => panic!("{:?} is not a named SearchOption", self),
         }
     }
 
-    pub fn icon(&self) -> &'static str {
+    pub fn icon(&self) -> ui::Icon {
         match *self {
-            Self::WHOLE_WORD => "icons/word_search.svg",
-            Self::CASE_SENSITIVE => "icons/case_insensitive.svg",
-            Self::INCLUDE_IGNORED => "icons/case_insensitive.svg",
-            _ => panic!("{self:?} is not a named SearchOption"),
+            SearchOptions::WHOLE_WORD => ui::Icon::WholeWord,
+            SearchOptions::CASE_SENSITIVE => ui::Icon::CaseSensitive,
+            SearchOptions::INCLUDE_IGNORED => ui::Icon::FileGit,
+            _ => panic!("{:?} is not a named SearchOption", self),
         }
     }
 
-    pub fn to_toggle_action(&self) -> Box<dyn Action> {
+    pub fn to_toggle_action(&self) -> Box<dyn Action + Sync + Send + 'static> {
         match *self {
-            Self::WHOLE_WORD => Box::new(ToggleWholeWord),
-            Self::CASE_SENSITIVE => Box::new(ToggleCaseSensitive),
-            Self::INCLUDE_IGNORED => Box::new(ToggleIncludeIgnored),
-            _ => panic!("{self:?} is not a named SearchOption"),
+            SearchOptions::WHOLE_WORD => Box::new(ToggleWholeWord),
+            SearchOptions::CASE_SENSITIVE => Box::new(ToggleCaseSensitive),
+            SearchOptions::INCLUDE_IGNORED => Box::new(ToggleIncludeIgnored),
+            _ => panic!("{:?} is not a named SearchOption", self),
         }
     }
 
@@ -94,47 +90,19 @@ impl SearchOptions {
         options
     }
 
-    pub fn as_button<V: View>(
+    pub fn as_button(
         &self,
         active: bool,
-        tooltip_style: TooltipStyle,
-        button_style: ToggleIconButtonStyle,
-    ) -> AnyElement<V> {
-        Button::dynamic_action(self.to_toggle_action())
-            .with_tooltip(format!("Toggle {}", self.label()), tooltip_style)
-            .with_contents(Svg::new(self.icon()))
-            .toggleable(active)
-            .with_style(button_style)
-            .element()
-            .into_any()
+        action: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static,
+    ) -> impl IntoElement {
+        IconButton::new(self.label(), self.icon())
+            .on_click(action)
+            .style(ButtonStyle::Subtle)
+            .when(active, |button| button.style(ButtonStyle::Filled))
+            .tooltip({
+                let action = self.to_toggle_action();
+                let label: SharedString = format!("Toggle {}", self.label()).into();
+                move |cx| Tooltip::for_action(label.clone(), &*action, cx)
+            })
     }
 }
-
-fn toggle_replace_button<V: View>(
-    active: bool,
-    tooltip_style: TooltipStyle,
-    button_style: ToggleIconButtonStyle,
-) -> AnyElement<V> {
-    Button::dynamic_action(Box::new(ToggleReplace))
-        .with_tooltip("Toggle Replace", tooltip_style)
-        .with_contents(theme::components::svg::Svg::new("icons/replace.svg"))
-        .toggleable(active)
-        .with_style(button_style)
-        .element()
-        .into_any()
-}
-
-fn replace_action<V: View>(
-    action: impl Action,
-    name: &'static str,
-    icon_path: &'static str,
-    tooltip_style: TooltipStyle,
-    button_style: IconButtonStyle,
-) -> AnyElement<V> {
-    Button::dynamic_action(Box::new(action))
-        .with_tooltip(name, tooltip_style)
-        .with_contents(theme::components::svg::Svg::new(icon_path))
-        .with_style(button_style)
-        .element()
-        .into_any()
-}

crates/search/src/search_bar.rs 🔗

@@ -1,174 +1,18 @@
-use std::borrow::Cow;
+use gpui::{Action, IntoElement};
+use ui::IconButton;
+use ui::{prelude::*, Tooltip};
 
-use gpui::{
-    elements::{Label, MouseEventHandler, Svg},
-    platform::{CursorStyle, MouseButton},
-    scene::{CornerRadii, MouseClick},
-    Action, AnyElement, Element, EventContext, View, ViewContext,
-};
-use workspace::searchable::Direction;
-
-use crate::{
-    mode::{SearchMode, Side},
-    SelectNextMatch, SelectPrevMatch,
-};
-
-pub(super) fn render_nav_button<V: View>(
-    icon: &'static str,
-    direction: Direction,
+pub(super) fn render_nav_button(
+    icon: ui::Icon,
     active: bool,
-    on_click: impl Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
-    cx: &mut ViewContext<V>,
-) -> AnyElement<V> {
-    let action: Box<dyn Action>;
-    let tooltip;
-
-    match direction {
-        Direction::Prev => {
-            action = Box::new(SelectPrevMatch);
-            tooltip = "Select Previous Match";
-        }
-        Direction::Next => {
-            action = Box::new(SelectNextMatch);
-            tooltip = "Select Next Match";
-        }
-    };
-    let tooltip_style = theme::current(cx).tooltip.clone();
-    let cursor_style = if active {
-        CursorStyle::PointingHand
-    } else {
-        CursorStyle::default()
-    };
-    enum NavButton {}
-    MouseEventHandler::new::<NavButton, _>(direction as usize, cx, |state, cx| {
-        let theme = theme::current(cx);
-        let style = theme
-            .search
-            .nav_button
-            .in_state(active)
-            .style_for(state)
-            .clone();
-        let mut container_style = style.container.clone();
-        let label = Label::new(icon, style.label.clone()).aligned().contained();
-        container_style.corner_radii = match direction {
-            Direction::Prev => CornerRadii {
-                bottom_right: 0.,
-                top_right: 0.,
-                ..container_style.corner_radii
-            },
-            Direction::Next => CornerRadii {
-                bottom_left: 0.,
-                top_left: 0.,
-                ..container_style.corner_radii
-            },
-        };
-        if direction == Direction::Prev {
-            // Remove right border so that when both Next and Prev buttons are
-            // next to one another, there's no double border between them.
-            container_style.border.right = false;
-        }
-        label.with_style(container_style)
-    })
-    .on_click(MouseButton::Left, on_click)
-    .with_cursor_style(cursor_style)
-    .with_tooltip::<NavButton>(
-        direction as usize,
-        tooltip.to_string(),
-        Some(action),
-        tooltip_style,
-        cx,
-    )
-    .into_any()
-}
-
-pub(crate) fn render_search_mode_button<V: View>(
-    mode: SearchMode,
-    side: Option<Side>,
-    is_active: bool,
-    on_click: impl Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
-    cx: &mut ViewContext<V>,
-) -> AnyElement<V> {
-    let tooltip_style = theme::current(cx).tooltip.clone();
-    enum SearchModeButton {}
-    MouseEventHandler::new::<SearchModeButton, _>(mode.region_id(), cx, |state, cx| {
-        let theme = theme::current(cx);
-        let style = theme
-            .search
-            .mode_button
-            .in_state(is_active)
-            .style_for(state)
-            .clone();
-
-        let mut container_style = style.container;
-        if let Some(button_side) = side {
-            if button_side == Side::Left {
-                container_style.border.left = true;
-                container_style.corner_radii = CornerRadii {
-                    bottom_right: 0.,
-                    top_right: 0.,
-                    ..container_style.corner_radii
-                };
-            } else {
-                container_style.border.left = false;
-                container_style.corner_radii = CornerRadii {
-                    bottom_left: 0.,
-                    top_left: 0.,
-                    ..container_style.corner_radii
-                };
-            }
-        } else {
-            container_style.border.left = false;
-            container_style.corner_radii = CornerRadii::default();
-        }
-
-        Label::new(mode.label(), style.text)
-            .aligned()
-            .contained()
-            .with_style(container_style)
-            .constrained()
-            .with_height(theme.search.search_bar_row_height)
-    })
-    .on_click(MouseButton::Left, on_click)
-    .with_cursor_style(CursorStyle::PointingHand)
-    .with_tooltip::<SearchModeButton>(
-        mode.region_id(),
-        mode.tooltip_text().to_owned(),
-        Some(mode.activate_action()),
-        tooltip_style,
-        cx,
+    tooltip: &'static str,
+    action: &'static dyn Action,
+) -> impl IntoElement {
+    IconButton::new(
+        SharedString::from(format!("search-nav-button-{}", action.name())),
+        icon,
     )
-    .into_any()
-}
-
-pub(crate) fn render_option_button_icon<V: View>(
-    is_active: bool,
-    icon: &'static str,
-    id: usize,
-    label: impl Into<Cow<'static, str>>,
-    action: Box<dyn Action>,
-    on_click: impl Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
-    cx: &mut ViewContext<V>,
-) -> AnyElement<V> {
-    let tooltip_style = theme::current(cx).tooltip.clone();
-    MouseEventHandler::new::<V, _>(id, cx, |state, cx| {
-        let theme = theme::current(cx);
-        let style = theme
-            .search
-            .option_button
-            .in_state(is_active)
-            .style_for(state);
-        Svg::new(icon)
-            .with_color(style.color.clone())
-            .constrained()
-            .with_width(style.icon_width)
-            .contained()
-            .with_style(style.container)
-            .constrained()
-            .with_height(theme.search.option_button_height)
-            .with_width(style.button_width)
-    })
-    .on_click(MouseButton::Left, on_click)
-    .with_cursor_style(CursorStyle::PointingHand)
-    .with_tooltip::<V>(id, label, Some(action), tooltip_style, cx)
-    .into_any()
+    .on_click(|_, cx| cx.dispatch_action(action.boxed_clone()))
+    .tooltip(move |cx| Tooltip::for_action(tooltip, action, cx))
+    .disabled(!active)
 }

crates/search2/Cargo.toml 🔗

@@ -1,40 +0,0 @@
-[package]
-name = "search2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/search.rs"
-doctest = false
-
-[dependencies]
-bitflags = "1"
-collections = { path = "../collections" }
-editor = { package = "editor2", path = "../editor2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-language = { package = "language2", path = "../language2" }
-menu = { package = "menu2", path = "../menu2" }
-project = { package = "project2", path = "../project2" }
-settings = { package = "settings2", path = "../settings2" }
-theme = { package = "theme2", path = "../theme2" }
-util = { path = "../util" }
-ui = {package = "ui2", path = "../ui2"}
-workspace = { package = "workspace2", path = "../workspace2" }
-semantic_index = { package = "semantic_index2", path = "../semantic_index2" }
-anyhow.workspace = true
-futures.workspace = true
-log.workspace = true
-postage.workspace = true
-serde.workspace = true
-serde_derive.workspace = true
-smallvec.workspace = true
-smol.workspace = true
-serde_json.workspace = true
-[dev-dependencies]
-client = { package = "client2", path = "../client2", features = ["test-support"] }
-editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-
-workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
-unindent.workspace = true

crates/search2/src/buffer_search.rs 🔗

@@ -1,1858 +0,0 @@
-use crate::{
-    history::SearchHistory,
-    mode::{next_mode, SearchMode},
-    search_bar::render_nav_button,
-    ActivateRegexMode, ActivateTextMode, CycleMode, NextHistoryQuery, PreviousHistoryQuery,
-    ReplaceAll, ReplaceNext, SearchOptions, SelectAllMatches, SelectNextMatch, SelectPrevMatch,
-    ToggleCaseSensitive, ToggleReplace, ToggleWholeWord,
-};
-use collections::HashMap;
-use editor::{Editor, EditorElement, EditorStyle, Tab};
-use futures::channel::oneshot;
-use gpui::{
-    actions, div, impl_actions, Action, AppContext, ClickEvent, EventEmitter, FocusableView,
-    FontStyle, FontWeight, InteractiveElement as _, IntoElement, KeyContext, ParentElement as _,
-    Render, Styled, Subscription, Task, TextStyle, View, ViewContext, VisualContext as _,
-    WhiteSpace, WindowContext,
-};
-use project::search::SearchQuery;
-use serde::Deserialize;
-use settings::Settings;
-use std::{any::Any, sync::Arc};
-use theme::ThemeSettings;
-
-use ui::{h_stack, prelude::*, Icon, IconButton, IconElement, ToggleButton, Tooltip};
-use util::ResultExt;
-use workspace::{
-    item::ItemHandle,
-    searchable::{Direction, SearchEvent, SearchableItemHandle, WeakSearchableItemHandle},
-    ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
-};
-
-#[derive(PartialEq, Clone, Deserialize)]
-pub struct Deploy {
-    pub focus: bool,
-}
-
-impl_actions!(buffer_search, [Deploy]);
-
-actions!(buffer_search, [Dismiss, FocusEditor]);
-
-pub enum Event {
-    UpdateLocation,
-}
-
-pub fn init(cx: &mut AppContext) {
-    cx.observe_new_views(|editor: &mut Workspace, _| BufferSearchBar::register(editor))
-        .detach();
-}
-
-pub struct BufferSearchBar {
-    query_editor: View<Editor>,
-    replacement_editor: View<Editor>,
-    active_searchable_item: Option<Box<dyn SearchableItemHandle>>,
-    active_match_index: Option<usize>,
-    active_searchable_item_subscription: Option<Subscription>,
-    active_search: Option<Arc<SearchQuery>>,
-    searchable_items_with_matches:
-        HashMap<Box<dyn WeakSearchableItemHandle>, Vec<Box<dyn Any + Send>>>,
-    pending_search: Option<Task<()>>,
-    search_options: SearchOptions,
-    default_options: SearchOptions,
-    query_contains_error: bool,
-    dismissed: bool,
-    search_history: SearchHistory,
-    current_mode: SearchMode,
-    replace_enabled: bool,
-}
-
-impl BufferSearchBar {
-    fn render_text_input(&self, editor: &View<Editor>, cx: &ViewContext<Self>) -> impl IntoElement {
-        let settings = ThemeSettings::get_global(cx);
-        let text_style = TextStyle {
-            color: if editor.read(cx).read_only() {
-                cx.theme().colors().text_disabled
-            } else {
-                cx.theme().colors().text
-            },
-            font_family: settings.ui_font.family.clone(),
-            font_features: settings.ui_font.features,
-            font_size: rems(0.875).into(),
-            font_weight: FontWeight::NORMAL,
-            font_style: FontStyle::Normal,
-            line_height: relative(1.3).into(),
-            background_color: None,
-            underline: None,
-            white_space: WhiteSpace::Normal,
-        };
-
-        EditorElement::new(
-            &editor,
-            EditorStyle {
-                background: cx.theme().colors().editor_background,
-                local_player: cx.theme().players().local(),
-                text: text_style,
-                ..Default::default()
-            },
-        )
-    }
-}
-
-impl EventEmitter<Event> for BufferSearchBar {}
-impl EventEmitter<workspace::ToolbarItemEvent> for BufferSearchBar {}
-impl Render for BufferSearchBar {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        if self.dismissed {
-            return div();
-        }
-
-        let supported_options = self.supported_options();
-
-        if self.query_editor.read(cx).placeholder_text().is_none() {
-            let query_focus_handle = self.query_editor.focus_handle(cx);
-            let up_keystrokes = cx
-                .bindings_for_action_in(&PreviousHistoryQuery {}, &query_focus_handle)
-                .into_iter()
-                .next()
-                .map(|binding| {
-                    binding
-                        .keystrokes()
-                        .iter()
-                        .map(|k| k.to_string())
-                        .collect::<Vec<_>>()
-                });
-            let down_keystrokes = cx
-                .bindings_for_action_in(&NextHistoryQuery {}, &query_focus_handle)
-                .into_iter()
-                .next()
-                .map(|binding| {
-                    binding
-                        .keystrokes()
-                        .iter()
-                        .map(|k| k.to_string())
-                        .collect::<Vec<_>>()
-                });
-
-            let placeholder_text =
-                up_keystrokes
-                    .zip(down_keystrokes)
-                    .map(|(up_keystrokes, down_keystrokes)| {
-                        Arc::from(format!(
-                            "Search ({}/{} for previous/next query)",
-                            up_keystrokes.join(" "),
-                            down_keystrokes.join(" ")
-                        ))
-                    });
-
-            if let Some(placeholder_text) = placeholder_text {
-                self.query_editor.update(cx, |editor, cx| {
-                    editor.set_placeholder_text(placeholder_text, cx);
-                });
-            }
-        }
-
-        self.replacement_editor.update(cx, |editor, cx| {
-            editor.set_placeholder_text("Replace with...", cx);
-        });
-
-        let match_count = self
-            .active_searchable_item
-            .as_ref()
-            .and_then(|searchable_item| {
-                if self.query(cx).is_empty() {
-                    return None;
-                }
-                let matches = self
-                    .searchable_items_with_matches
-                    .get(&searchable_item.downgrade())?;
-                let message = if let Some(match_ix) = self.active_match_index {
-                    format!("{}/{}", match_ix + 1, matches.len())
-                } else {
-                    "No matches".to_string()
-                };
-
-                Some(ui::Label::new(message))
-            });
-        let should_show_replace_input = self.replace_enabled && supported_options.replacement;
-        let in_replace = self.replacement_editor.focus_handle(cx).is_focused(cx);
-
-        let mut key_context = KeyContext::default();
-        key_context.add("BufferSearchBar");
-        if in_replace {
-            key_context.add("in_replace");
-        }
-        let editor_border = if self.query_contains_error {
-            Color::Error.color(cx)
-        } else {
-            cx.theme().colors().border
-        };
-        h_stack()
-            .w_full()
-            .gap_2()
-            .key_context(key_context)
-            .capture_action(cx.listener(Self::tab))
-            .on_action(cx.listener(Self::previous_history_query))
-            .on_action(cx.listener(Self::next_history_query))
-            .on_action(cx.listener(Self::dismiss))
-            .on_action(cx.listener(Self::select_next_match))
-            .on_action(cx.listener(Self::select_prev_match))
-            .on_action(cx.listener(|this, _: &ActivateRegexMode, cx| {
-                this.activate_search_mode(SearchMode::Regex, cx);
-            }))
-            .on_action(cx.listener(|this, _: &ActivateTextMode, cx| {
-                this.activate_search_mode(SearchMode::Text, cx);
-            }))
-            .when(self.supported_options().replacement, |this| {
-                this.on_action(cx.listener(Self::toggle_replace))
-                    .when(in_replace, |this| {
-                        this.on_action(cx.listener(Self::replace_next))
-                            .on_action(cx.listener(Self::replace_all))
-                    })
-            })
-            .when(self.supported_options().case, |this| {
-                this.on_action(cx.listener(Self::toggle_case_sensitive))
-            })
-            .when(self.supported_options().word, |this| {
-                this.on_action(cx.listener(Self::toggle_whole_word))
-            })
-            .child(
-                h_stack()
-                    .flex_1()
-                    .px_2()
-                    .py_1()
-                    .gap_2()
-                    .border_1()
-                    .border_color(editor_border)
-                    .rounded_lg()
-                    .child(IconElement::new(Icon::MagnifyingGlass))
-                    .child(self.render_text_input(&self.query_editor, cx))
-                    .children(supported_options.case.then(|| {
-                        self.render_search_option_button(
-                            SearchOptions::CASE_SENSITIVE,
-                            cx.listener(|this, _, cx| {
-                                this.toggle_case_sensitive(&ToggleCaseSensitive, cx)
-                            }),
-                        )
-                    }))
-                    .children(supported_options.word.then(|| {
-                        self.render_search_option_button(
-                            SearchOptions::WHOLE_WORD,
-                            cx.listener(|this, _, cx| this.toggle_whole_word(&ToggleWholeWord, cx)),
-                        )
-                    })),
-            )
-            .child(
-                h_stack()
-                    .gap_2()
-                    .flex_none()
-                    .child(
-                        h_stack()
-                            .child(
-                                ToggleButton::new("search-mode-text", SearchMode::Text.label())
-                                    .style(ButtonStyle::Filled)
-                                    .size(ButtonSize::Large)
-                                    .selected(self.current_mode == SearchMode::Text)
-                                    .on_click(cx.listener(move |_, _event, cx| {
-                                        cx.dispatch_action(SearchMode::Text.action())
-                                    }))
-                                    .tooltip(|cx| {
-                                        Tooltip::for_action(
-                                            SearchMode::Text.tooltip(),
-                                            &*SearchMode::Text.action(),
-                                            cx,
-                                        )
-                                    })
-                                    .first(),
-                            )
-                            .child(
-                                ToggleButton::new("search-mode-regex", SearchMode::Regex.label())
-                                    .style(ButtonStyle::Filled)
-                                    .size(ButtonSize::Large)
-                                    .selected(self.current_mode == SearchMode::Regex)
-                                    .on_click(cx.listener(move |_, _event, cx| {
-                                        cx.dispatch_action(SearchMode::Regex.action())
-                                    }))
-                                    .tooltip(|cx| {
-                                        Tooltip::for_action(
-                                            SearchMode::Regex.tooltip(),
-                                            &*SearchMode::Regex.action(),
-                                            cx,
-                                        )
-                                    })
-                                    .last(),
-                            ),
-                    )
-                    .when(supported_options.replacement, |this| {
-                        this.child(
-                            IconButton::new(
-                                "buffer-search-bar-toggle-replace-button",
-                                Icon::Replace,
-                            )
-                            .style(ButtonStyle::Subtle)
-                            .when(self.replace_enabled, |button| {
-                                button.style(ButtonStyle::Filled)
-                            })
-                            .on_click(cx.listener(|this, _: &ClickEvent, cx| {
-                                this.toggle_replace(&ToggleReplace, cx);
-                            }))
-                            .tooltip(|cx| {
-                                Tooltip::for_action("Toggle replace", &ToggleReplace, cx)
-                            }),
-                        )
-                    }),
-            )
-            .child(
-                h_stack()
-                    .gap_0p5()
-                    .flex_1()
-                    .when(self.replace_enabled, |this| {
-                        this.child(
-                            h_stack()
-                                .flex_1()
-                                // We're giving this a fixed height to match the height of the search input,
-                                // which has an icon inside that is increasing its height.
-                                .h_8()
-                                .px_2()
-                                .py_1()
-                                .gap_2()
-                                .border_1()
-                                .border_color(cx.theme().colors().border)
-                                .rounded_lg()
-                                .child(self.render_text_input(&self.replacement_editor, cx)),
-                        )
-                        .when(should_show_replace_input, |this| {
-                            this.child(
-                                IconButton::new("search-replace-next", ui::Icon::ReplaceNext)
-                                    .tooltip(move |cx| {
-                                        Tooltip::for_action("Replace next", &ReplaceNext, cx)
-                                    })
-                                    .on_click(cx.listener(|this, _, cx| {
-                                        this.replace_next(&ReplaceNext, cx)
-                                    })),
-                            )
-                            .child(
-                                IconButton::new("search-replace-all", ui::Icon::ReplaceAll)
-                                    .tooltip(move |cx| {
-                                        Tooltip::for_action("Replace all", &ReplaceAll, cx)
-                                    })
-                                    .on_click(
-                                        cx.listener(|this, _, cx| {
-                                            this.replace_all(&ReplaceAll, cx)
-                                        }),
-                                    ),
-                            )
-                        })
-                    }),
-            )
-            .child(
-                h_stack()
-                    .gap_0p5()
-                    .flex_none()
-                    .child(
-                        IconButton::new("select-all", ui::Icon::SelectAll)
-                            .on_click(|_, cx| cx.dispatch_action(SelectAllMatches.boxed_clone()))
-                            .tooltip(|cx| {
-                                Tooltip::for_action("Select all matches", &SelectAllMatches, cx)
-                            }),
-                    )
-                    .children(match_count)
-                    .child(render_nav_button(
-                        ui::Icon::ChevronLeft,
-                        self.active_match_index.is_some(),
-                        "Select previous match",
-                        &SelectPrevMatch,
-                    ))
-                    .child(render_nav_button(
-                        ui::Icon::ChevronRight,
-                        self.active_match_index.is_some(),
-                        "Select next match",
-                        &SelectNextMatch,
-                    )),
-            )
-    }
-}
-
-impl FocusableView for BufferSearchBar {
-    fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
-        self.query_editor.focus_handle(cx)
-    }
-}
-
-impl ToolbarItemView for BufferSearchBar {
-    fn set_active_pane_item(
-        &mut self,
-        item: Option<&dyn ItemHandle>,
-        cx: &mut ViewContext<Self>,
-    ) -> ToolbarItemLocation {
-        cx.notify();
-        self.active_searchable_item_subscription.take();
-        self.active_searchable_item.take();
-
-        self.pending_search.take();
-
-        if let Some(searchable_item_handle) =
-            item.and_then(|item| item.to_searchable_item_handle(cx))
-        {
-            let this = cx.view().downgrade();
-
-            searchable_item_handle
-                .subscribe_to_search_events(
-                    cx,
-                    Box::new(move |search_event, cx| {
-                        if let Some(this) = this.upgrade() {
-                            this.update(cx, |this, cx| {
-                                this.on_active_searchable_item_event(search_event, cx)
-                            });
-                        }
-                    }),
-                )
-                .detach();
-
-            self.active_searchable_item = Some(searchable_item_handle);
-            let _ = self.update_matches(cx);
-            if !self.dismissed {
-                return ToolbarItemLocation::Secondary;
-            }
-        }
-        ToolbarItemLocation::Hidden
-    }
-
-    fn row_count(&self, _: &WindowContext<'_>) -> usize {
-        1
-    }
-}
-
-impl BufferSearchBar {
-    fn register(workspace: &mut Workspace) {
-        workspace.register_action(move |workspace, deploy: &Deploy, cx| {
-            let pane = workspace.active_pane();
-
-            pane.update(cx, |this, cx| {
-                this.toolbar().update(cx, |this, cx| {
-                    if let Some(search_bar) = this.item_of_type::<BufferSearchBar>() {
-                        search_bar.update(cx, |this, cx| {
-                            this.deploy(deploy, cx);
-                        });
-                        return;
-                    }
-                    let view = cx.new_view(|cx| BufferSearchBar::new(cx));
-                    this.add_item(view.clone(), cx);
-                    view.update(cx, |this, cx| this.deploy(deploy, cx));
-                    cx.notify();
-                })
-            });
-        });
-        fn register_action<A: Action>(
-            workspace: &mut Workspace,
-            update: fn(&mut BufferSearchBar, &A, &mut ViewContext<BufferSearchBar>),
-        ) {
-            workspace.register_action(move |workspace, action: &A, cx| {
-                let pane = workspace.active_pane();
-                pane.update(cx, move |this, cx| {
-                    this.toolbar().update(cx, move |this, cx| {
-                        if let Some(search_bar) = this.item_of_type::<BufferSearchBar>() {
-                            search_bar.update(cx, move |this, cx| update(this, action, cx));
-                            cx.notify();
-                        }
-                    })
-                });
-            });
-        }
-
-        register_action(workspace, |this, action: &ToggleCaseSensitive, cx| {
-            if this.supported_options().case {
-                this.toggle_case_sensitive(action, cx);
-            }
-        });
-        register_action(workspace, |this, action: &ToggleWholeWord, cx| {
-            if this.supported_options().word {
-                this.toggle_whole_word(action, cx);
-            }
-        });
-        register_action(workspace, |this, action: &ToggleReplace, cx| {
-            if this.supported_options().replacement {
-                this.toggle_replace(action, cx);
-            }
-        });
-        register_action(workspace, |this, _: &ActivateRegexMode, cx| {
-            if this.supported_options().regex {
-                this.activate_search_mode(SearchMode::Regex, cx);
-            }
-        });
-        register_action(workspace, |this, _: &ActivateTextMode, cx| {
-            this.activate_search_mode(SearchMode::Text, cx);
-        });
-        register_action(workspace, |this, action: &CycleMode, cx| {
-            if this.supported_options().regex {
-                // If regex is not supported then search has just one mode (text) - in that case there's no point in supporting
-                // cycling.
-                this.cycle_mode(action, cx)
-            }
-        });
-        register_action(workspace, |this, action: &SelectNextMatch, cx| {
-            this.select_next_match(action, cx);
-        });
-        register_action(workspace, |this, action: &SelectPrevMatch, cx| {
-            this.select_prev_match(action, cx);
-        });
-        register_action(workspace, |this, action: &SelectAllMatches, cx| {
-            this.select_all_matches(action, cx);
-        });
-        register_action(workspace, |this, _: &editor::Cancel, cx| {
-            if !this.dismissed {
-                this.dismiss(&Dismiss, cx);
-                return;
-            }
-            cx.propagate();
-        });
-    }
-    pub fn new(cx: &mut ViewContext<Self>) -> Self {
-        let query_editor = cx.new_view(|cx| Editor::single_line(cx));
-        cx.subscribe(&query_editor, Self::on_query_editor_event)
-            .detach();
-        let replacement_editor = cx.new_view(|cx| Editor::single_line(cx));
-        cx.subscribe(&replacement_editor, Self::on_query_editor_event)
-            .detach();
-        Self {
-            query_editor,
-            replacement_editor,
-            active_searchable_item: None,
-            active_searchable_item_subscription: None,
-            active_match_index: None,
-            searchable_items_with_matches: Default::default(),
-            default_options: SearchOptions::NONE,
-            search_options: SearchOptions::NONE,
-            pending_search: None,
-            query_contains_error: false,
-            dismissed: true,
-            search_history: SearchHistory::default(),
-            current_mode: SearchMode::default(),
-            active_search: None,
-            replace_enabled: false,
-        }
-    }
-
-    pub fn is_dismissed(&self) -> bool {
-        self.dismissed
-    }
-
-    pub fn dismiss(&mut self, _: &Dismiss, cx: &mut ViewContext<Self>) {
-        self.dismissed = true;
-        for searchable_item in self.searchable_items_with_matches.keys() {
-            if let Some(searchable_item) =
-                WeakSearchableItemHandle::upgrade(searchable_item.as_ref(), cx)
-            {
-                searchable_item.clear_matches(cx);
-            }
-        }
-        if let Some(active_editor) = self.active_searchable_item.as_ref() {
-            let handle = active_editor.focus_handle(cx);
-            cx.focus(&handle);
-        }
-        cx.emit(Event::UpdateLocation);
-        cx.emit(ToolbarItemEvent::ChangeLocation(
-            ToolbarItemLocation::Hidden,
-        ));
-        cx.notify();
-    }
-
-    pub fn deploy(&mut self, deploy: &Deploy, cx: &mut ViewContext<Self>) -> bool {
-        if self.show(cx) {
-            self.search_suggested(cx);
-            if deploy.focus {
-                self.select_query(cx);
-                let handle = self.query_editor.focus_handle(cx);
-                cx.focus(&handle);
-            }
-            return true;
-        }
-
-        false
-    }
-
-    pub fn toggle(&mut self, action: &Deploy, cx: &mut ViewContext<Self>) {
-        if self.is_dismissed() {
-            self.deploy(action, cx);
-        } else {
-            self.dismiss(&Dismiss, cx);
-        }
-    }
-
-    pub fn show(&mut self, cx: &mut ViewContext<Self>) -> bool {
-        if self.active_searchable_item.is_none() {
-            return false;
-        }
-        self.dismissed = false;
-        cx.notify();
-        cx.emit(Event::UpdateLocation);
-        cx.emit(ToolbarItemEvent::ChangeLocation(
-            ToolbarItemLocation::Secondary,
-        ));
-        true
-    }
-
-    fn supported_options(&self) -> workspace::searchable::SearchOptions {
-        self.active_searchable_item
-            .as_deref()
-            .map(SearchableItemHandle::supported_options)
-            .unwrap_or_default()
-    }
-    pub fn search_suggested(&mut self, cx: &mut ViewContext<Self>) {
-        let search = self
-            .query_suggestion(cx)
-            .map(|suggestion| self.search(&suggestion, Some(self.default_options), cx));
-
-        if let Some(search) = search {
-            cx.spawn(|this, mut cx| async move {
-                search.await?;
-                this.update(&mut cx, |this, cx| this.activate_current_match(cx))
-            })
-            .detach_and_log_err(cx);
-        }
-    }
-
-    pub fn activate_current_match(&mut self, cx: &mut ViewContext<Self>) {
-        if let Some(match_ix) = self.active_match_index {
-            if let Some(active_searchable_item) = self.active_searchable_item.as_ref() {
-                if let Some(matches) = self
-                    .searchable_items_with_matches
-                    .get(&active_searchable_item.downgrade())
-                {
-                    active_searchable_item.activate_match(match_ix, matches, cx)
-                }
-            }
-        }
-    }
-
-    pub fn select_query(&mut self, cx: &mut ViewContext<Self>) {
-        self.query_editor.update(cx, |query_editor, cx| {
-            query_editor.select_all(&Default::default(), cx);
-        });
-    }
-
-    pub fn query(&self, cx: &WindowContext) -> String {
-        self.query_editor.read(cx).text(cx)
-    }
-    pub fn replacement(&self, cx: &WindowContext) -> String {
-        self.replacement_editor.read(cx).text(cx)
-    }
-    pub fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> Option<String> {
-        self.active_searchable_item
-            .as_ref()
-            .map(|searchable_item| searchable_item.query_suggestion(cx))
-            .filter(|suggestion| !suggestion.is_empty())
-    }
-
-    pub fn set_replacement(&mut self, replacement: Option<&str>, cx: &mut ViewContext<Self>) {
-        if replacement.is_none() {
-            self.replace_enabled = false;
-            return;
-        }
-        self.replace_enabled = true;
-        self.replacement_editor
-            .update(cx, |replacement_editor, cx| {
-                replacement_editor
-                    .buffer()
-                    .update(cx, |replacement_buffer, cx| {
-                        let len = replacement_buffer.len(cx);
-                        replacement_buffer.edit([(0..len, replacement.unwrap())], None, cx);
-                    });
-            });
-    }
-
-    pub fn search(
-        &mut self,
-        query: &str,
-        options: Option<SearchOptions>,
-        cx: &mut ViewContext<Self>,
-    ) -> oneshot::Receiver<()> {
-        let options = options.unwrap_or(self.default_options);
-        if query != self.query(cx) || self.search_options != options {
-            self.query_editor.update(cx, |query_editor, cx| {
-                query_editor.buffer().update(cx, |query_buffer, cx| {
-                    let len = query_buffer.len(cx);
-                    query_buffer.edit([(0..len, query)], None, cx);
-                });
-            });
-            self.search_options = options;
-            self.query_contains_error = false;
-            self.clear_matches(cx);
-            cx.notify();
-        }
-        self.update_matches(cx)
-    }
-
-    fn render_search_option_button(
-        &self,
-        option: SearchOptions,
-        action: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
-    ) -> impl IntoElement {
-        let is_active = self.search_options.contains(option);
-        option.as_button(is_active, action)
-    }
-    pub fn activate_search_mode(&mut self, mode: SearchMode, cx: &mut ViewContext<Self>) {
-        assert_ne!(
-            mode,
-            SearchMode::Semantic,
-            "Semantic search is not supported in buffer search"
-        );
-        if mode == self.current_mode {
-            return;
-        }
-        self.current_mode = mode;
-        let _ = self.update_matches(cx);
-        cx.notify();
-    }
-
-    pub fn focus_editor(&mut self, _: &FocusEditor, cx: &mut ViewContext<Self>) {
-        if let Some(active_editor) = self.active_searchable_item.as_ref() {
-            let handle = active_editor.focus_handle(cx);
-            cx.focus(&handle);
-        }
-    }
-
-    fn toggle_search_option(&mut self, search_option: SearchOptions, cx: &mut ViewContext<Self>) {
-        self.search_options.toggle(search_option);
-        self.default_options = self.search_options;
-        let _ = self.update_matches(cx);
-        cx.notify();
-    }
-
-    pub fn set_search_options(
-        &mut self,
-        search_options: SearchOptions,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.search_options = search_options;
-        cx.notify();
-    }
-
-    fn select_next_match(&mut self, _: &SelectNextMatch, cx: &mut ViewContext<Self>) {
-        self.select_match(Direction::Next, 1, cx);
-    }
-
-    fn select_prev_match(&mut self, _: &SelectPrevMatch, cx: &mut ViewContext<Self>) {
-        self.select_match(Direction::Prev, 1, cx);
-    }
-
-    fn select_all_matches(&mut self, _: &SelectAllMatches, cx: &mut ViewContext<Self>) {
-        if !self.dismissed && self.active_match_index.is_some() {
-            if let Some(searchable_item) = self.active_searchable_item.as_ref() {
-                if let Some(matches) = self
-                    .searchable_items_with_matches
-                    .get(&searchable_item.downgrade())
-                {
-                    searchable_item.select_matches(matches, cx);
-                    self.focus_editor(&FocusEditor, cx);
-                }
-            }
-        }
-    }
-
-    pub fn select_match(&mut self, direction: Direction, count: usize, cx: &mut ViewContext<Self>) {
-        if let Some(index) = self.active_match_index {
-            if let Some(searchable_item) = self.active_searchable_item.as_ref() {
-                if let Some(matches) = self
-                    .searchable_items_with_matches
-                    .get(&searchable_item.downgrade())
-                {
-                    let new_match_index = searchable_item
-                        .match_index_for_direction(matches, index, direction, count, cx);
-
-                    searchable_item.update_matches(matches, cx);
-                    searchable_item.activate_match(new_match_index, matches, cx);
-                }
-            }
-        }
-    }
-
-    pub fn select_last_match(&mut self, cx: &mut ViewContext<Self>) {
-        if let Some(searchable_item) = self.active_searchable_item.as_ref() {
-            if let Some(matches) = self
-                .searchable_items_with_matches
-                .get(&searchable_item.downgrade())
-            {
-                if matches.len() == 0 {
-                    return;
-                }
-                let new_match_index = matches.len() - 1;
-                searchable_item.update_matches(matches, cx);
-                searchable_item.activate_match(new_match_index, matches, cx);
-            }
-        }
-    }
-
-    fn on_query_editor_event(
-        &mut self,
-        _: View<Editor>,
-        event: &editor::EditorEvent,
-        cx: &mut ViewContext<Self>,
-    ) {
-        if let editor::EditorEvent::Edited { .. } = event {
-            self.query_contains_error = false;
-            self.clear_matches(cx);
-            let search = self.update_matches(cx);
-            cx.spawn(|this, mut cx| async move {
-                search.await?;
-                this.update(&mut cx, |this, cx| this.activate_current_match(cx))
-            })
-            .detach_and_log_err(cx);
-        }
-    }
-
-    fn on_active_searchable_item_event(&mut self, event: &SearchEvent, cx: &mut ViewContext<Self>) {
-        match event {
-            SearchEvent::MatchesInvalidated => {
-                let _ = self.update_matches(cx);
-            }
-            SearchEvent::ActiveMatchChanged => self.update_match_index(cx),
-        }
-    }
-
-    fn toggle_case_sensitive(&mut self, _: &ToggleCaseSensitive, cx: &mut ViewContext<Self>) {
-        self.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx)
-    }
-    fn toggle_whole_word(&mut self, _: &ToggleWholeWord, cx: &mut ViewContext<Self>) {
-        self.toggle_search_option(SearchOptions::WHOLE_WORD, cx)
-    }
-    fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
-        let mut active_item_matches = None;
-        for (searchable_item, matches) in self.searchable_items_with_matches.drain() {
-            if let Some(searchable_item) =
-                WeakSearchableItemHandle::upgrade(searchable_item.as_ref(), cx)
-            {
-                if Some(&searchable_item) == self.active_searchable_item.as_ref() {
-                    active_item_matches = Some((searchable_item.downgrade(), matches));
-                } else {
-                    searchable_item.clear_matches(cx);
-                }
-            }
-        }
-
-        self.searchable_items_with_matches
-            .extend(active_item_matches);
-    }
-
-    fn update_matches(&mut self, cx: &mut ViewContext<Self>) -> oneshot::Receiver<()> {
-        let (done_tx, done_rx) = oneshot::channel();
-        let query = self.query(cx);
-        self.pending_search.take();
-
-        if let Some(active_searchable_item) = self.active_searchable_item.as_ref() {
-            if query.is_empty() {
-                self.active_match_index.take();
-                active_searchable_item.clear_matches(cx);
-                let _ = done_tx.send(());
-                cx.notify();
-            } else {
-                let query: Arc<_> = if self.current_mode == SearchMode::Regex {
-                    match SearchQuery::regex(
-                        query,
-                        self.search_options.contains(SearchOptions::WHOLE_WORD),
-                        self.search_options.contains(SearchOptions::CASE_SENSITIVE),
-                        false,
-                        Vec::new(),
-                        Vec::new(),
-                    ) {
-                        Ok(query) => query.with_replacement(self.replacement(cx)),
-                        Err(_) => {
-                            self.query_contains_error = true;
-                            self.active_match_index = None;
-                            cx.notify();
-                            return done_rx;
-                        }
-                    }
-                } else {
-                    match SearchQuery::text(
-                        query,
-                        self.search_options.contains(SearchOptions::WHOLE_WORD),
-                        self.search_options.contains(SearchOptions::CASE_SENSITIVE),
-                        false,
-                        Vec::new(),
-                        Vec::new(),
-                    ) {
-                        Ok(query) => query.with_replacement(self.replacement(cx)),
-                        Err(_) => {
-                            self.query_contains_error = true;
-                            self.active_match_index = None;
-                            cx.notify();
-                            return done_rx;
-                        }
-                    }
-                }
-                .into();
-                self.active_search = Some(query.clone());
-                let query_text = query.as_str().to_string();
-
-                let matches = active_searchable_item.find_matches(query, cx);
-
-                let active_searchable_item = active_searchable_item.downgrade();
-                self.pending_search = Some(cx.spawn(|this, mut cx| async move {
-                    let matches = matches.await;
-
-                    this.update(&mut cx, |this, cx| {
-                        if let Some(active_searchable_item) =
-                            WeakSearchableItemHandle::upgrade(active_searchable_item.as_ref(), cx)
-                        {
-                            this.searchable_items_with_matches
-                                .insert(active_searchable_item.downgrade(), matches);
-
-                            this.update_match_index(cx);
-                            this.search_history.add(query_text);
-                            if !this.dismissed {
-                                let matches = this
-                                    .searchable_items_with_matches
-                                    .get(&active_searchable_item.downgrade())
-                                    .unwrap();
-                                active_searchable_item.update_matches(matches, cx);
-                                let _ = done_tx.send(());
-                            }
-                            cx.notify();
-                        }
-                    })
-                    .log_err();
-                }));
-            }
-        }
-        done_rx
-    }
-
-    fn update_match_index(&mut self, cx: &mut ViewContext<Self>) {
-        let new_index = self
-            .active_searchable_item
-            .as_ref()
-            .and_then(|searchable_item| {
-                let matches = self
-                    .searchable_items_with_matches
-                    .get(&searchable_item.downgrade())?;
-                searchable_item.active_match_index(matches, cx)
-            });
-        if new_index != self.active_match_index {
-            self.active_match_index = new_index;
-            cx.notify();
-        }
-    }
-
-    fn tab(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
-        if let Some(item) = self.active_searchable_item.as_ref() {
-            let focus_handle = item.focus_handle(cx);
-            cx.focus(&focus_handle);
-            cx.stop_propagation();
-        }
-    }
-
-    fn next_history_query(&mut self, _: &NextHistoryQuery, cx: &mut ViewContext<Self>) {
-        if let Some(new_query) = self.search_history.next().map(str::to_string) {
-            let _ = self.search(&new_query, Some(self.search_options), cx);
-        } else {
-            self.search_history.reset_selection();
-            let _ = self.search("", Some(self.search_options), cx);
-        }
-    }
-
-    fn previous_history_query(&mut self, _: &PreviousHistoryQuery, cx: &mut ViewContext<Self>) {
-        if self.query(cx).is_empty() {
-            if let Some(new_query) = self.search_history.current().map(str::to_string) {
-                let _ = self.search(&new_query, Some(self.search_options), cx);
-                return;
-            }
-        }
-
-        if let Some(new_query) = self.search_history.previous().map(str::to_string) {
-            let _ = self.search(&new_query, Some(self.search_options), cx);
-        }
-    }
-    fn cycle_mode(&mut self, _: &CycleMode, cx: &mut ViewContext<Self>) {
-        self.activate_search_mode(next_mode(&self.current_mode, false), cx);
-    }
-    fn toggle_replace(&mut self, _: &ToggleReplace, cx: &mut ViewContext<Self>) {
-        if let Some(_) = &self.active_searchable_item {
-            self.replace_enabled = !self.replace_enabled;
-            if !self.replace_enabled {
-                let handle = self.query_editor.focus_handle(cx);
-                cx.focus(&handle);
-            }
-            cx.notify();
-        }
-    }
-    fn replace_next(&mut self, _: &ReplaceNext, cx: &mut ViewContext<Self>) {
-        let mut should_propagate = true;
-        if !self.dismissed && self.active_search.is_some() {
-            if let Some(searchable_item) = self.active_searchable_item.as_ref() {
-                if let Some(query) = self.active_search.as_ref() {
-                    if let Some(matches) = self
-                        .searchable_items_with_matches
-                        .get(&searchable_item.downgrade())
-                    {
-                        if let Some(active_index) = self.active_match_index {
-                            let query = query
-                                .as_ref()
-                                .clone()
-                                .with_replacement(self.replacement(cx));
-                            searchable_item.replace(&matches[active_index], &query, cx);
-                            self.select_next_match(&SelectNextMatch, cx);
-                        }
-                        should_propagate = false;
-                        self.focus_editor(&FocusEditor, cx);
-                    }
-                }
-            }
-        }
-        if !should_propagate {
-            cx.stop_propagation();
-        }
-    }
-    pub fn replace_all(&mut self, _: &ReplaceAll, cx: &mut ViewContext<Self>) {
-        if !self.dismissed && self.active_search.is_some() {
-            if let Some(searchable_item) = self.active_searchable_item.as_ref() {
-                if let Some(query) = self.active_search.as_ref() {
-                    if let Some(matches) = self
-                        .searchable_items_with_matches
-                        .get(&searchable_item.downgrade())
-                    {
-                        let query = query
-                            .as_ref()
-                            .clone()
-                            .with_replacement(self.replacement(cx));
-                        for m in matches {
-                            searchable_item.replace(m, &query, cx);
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use std::ops::Range;
-
-    use super::*;
-    use editor::{DisplayPoint, Editor};
-    use gpui::{Context, EmptyView, Hsla, TestAppContext, VisualTestContext};
-    use language::Buffer;
-    use smol::stream::StreamExt as _;
-    use unindent::Unindent as _;
-
-    fn init_globals(cx: &mut TestAppContext) {
-        cx.update(|cx| {
-            let store = settings::SettingsStore::test(cx);
-            cx.set_global(store);
-            editor::init(cx);
-
-            language::init(cx);
-            theme::init(theme::LoadThemes::JustBase, cx);
-        });
-    }
-    fn init_test(
-        cx: &mut TestAppContext,
-    ) -> (
-        View<Editor>,
-        View<BufferSearchBar>,
-        &mut VisualTestContext<'_>,
-    ) {
-        init_globals(cx);
-        let buffer = cx.new_model(|cx| {
-            Buffer::new(
-                0,
-                cx.entity_id().as_u64(),
-                r#"
-                A regular expression (shortened as regex or regexp;[1] also referred to as
-                rational expression[2][3]) is a sequence of characters that specifies a search
-                pattern in text. Usually such patterns are used by string-searching algorithms
-                for "find" or "find and replace" operations on strings, or for input validation.
-                "#
-                .unindent(),
-            )
-        });
-        let (_, cx) = cx.add_window_view(|_| EmptyView {});
-        let editor = cx.new_view(|cx| Editor::for_buffer(buffer.clone(), None, cx));
-
-        let search_bar = cx.new_view(|cx| {
-            let mut search_bar = BufferSearchBar::new(cx);
-            search_bar.set_active_pane_item(Some(&editor), cx);
-            search_bar.show(cx);
-            search_bar
-        });
-
-        (editor, search_bar, cx)
-    }
-
-    #[gpui::test]
-    async fn test_search_simple(cx: &mut TestAppContext) {
-        let (editor, search_bar, cx) = init_test(cx);
-        // todo! osiewicz: these tests asserted on background color as well, that should be brought back.
-        let display_points_of = |background_highlights: Vec<(Range<DisplayPoint>, Hsla)>| {
-            background_highlights
-                .into_iter()
-                .map(|(range, _)| range)
-                .collect::<Vec<_>>()
-        };
-        // Search for a string that appears with different casing.
-        // By default, search is case-insensitive.
-        search_bar
-            .update(cx, |search_bar, cx| search_bar.search("us", None, cx))
-            .await
-            .unwrap();
-        editor.update(cx, |editor, cx| {
-            assert_eq!(
-                display_points_of(editor.all_text_background_highlights(cx)),
-                &[
-                    DisplayPoint::new(2, 17)..DisplayPoint::new(2, 19),
-                    DisplayPoint::new(2, 43)..DisplayPoint::new(2, 45),
-                ]
-            );
-        });
-
-        // Switch to a case sensitive search.
-        search_bar.update(cx, |search_bar, cx| {
-            search_bar.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx);
-        });
-        let mut editor_notifications = cx.notifications(&editor);
-        editor_notifications.next().await;
-        editor.update(cx, |editor, cx| {
-            assert_eq!(
-                display_points_of(editor.all_text_background_highlights(cx)),
-                &[DisplayPoint::new(2, 43)..DisplayPoint::new(2, 45),]
-            );
-        });
-
-        // Search for a string that appears both as a whole word and
-        // within other words. By default, all results are found.
-        search_bar
-            .update(cx, |search_bar, cx| search_bar.search("or", None, cx))
-            .await
-            .unwrap();
-        editor.update(cx, |editor, cx| {
-            assert_eq!(
-                display_points_of(editor.all_text_background_highlights(cx)),
-                &[
-                    DisplayPoint::new(0, 24)..DisplayPoint::new(0, 26),
-                    DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43),
-                    DisplayPoint::new(2, 71)..DisplayPoint::new(2, 73),
-                    DisplayPoint::new(3, 1)..DisplayPoint::new(3, 3),
-                    DisplayPoint::new(3, 11)..DisplayPoint::new(3, 13),
-                    DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58),
-                    DisplayPoint::new(3, 60)..DisplayPoint::new(3, 62),
-                ]
-            );
-        });
-
-        // Switch to a whole word search.
-        search_bar.update(cx, |search_bar, cx| {
-            search_bar.toggle_search_option(SearchOptions::WHOLE_WORD, cx);
-        });
-        let mut editor_notifications = cx.notifications(&editor);
-        editor_notifications.next().await;
-        editor.update(cx, |editor, cx| {
-            assert_eq!(
-                display_points_of(editor.all_text_background_highlights(cx)),
-                &[
-                    DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43),
-                    DisplayPoint::new(3, 11)..DisplayPoint::new(3, 13),
-                    DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58),
-                ]
-            );
-        });
-
-        editor.update(cx, |editor, cx| {
-            editor.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)])
-            });
-        });
-        search_bar.update(cx, |search_bar, cx| {
-            assert_eq!(search_bar.active_match_index, Some(0));
-            search_bar.select_next_match(&SelectNextMatch, cx);
-            assert_eq!(
-                editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)]
-            );
-        });
-        search_bar.update(cx, |search_bar, _| {
-            assert_eq!(search_bar.active_match_index, Some(0));
-        });
-
-        search_bar.update(cx, |search_bar, cx| {
-            search_bar.select_next_match(&SelectNextMatch, cx);
-            assert_eq!(
-                editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                [DisplayPoint::new(3, 11)..DisplayPoint::new(3, 13)]
-            );
-        });
-        search_bar.update(cx, |search_bar, _| {
-            assert_eq!(search_bar.active_match_index, Some(1));
-        });
-
-        search_bar.update(cx, |search_bar, cx| {
-            search_bar.select_next_match(&SelectNextMatch, cx);
-            assert_eq!(
-                editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                [DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58)]
-            );
-        });
-        search_bar.update(cx, |search_bar, _| {
-            assert_eq!(search_bar.active_match_index, Some(2));
-        });
-
-        search_bar.update(cx, |search_bar, cx| {
-            search_bar.select_next_match(&SelectNextMatch, cx);
-            assert_eq!(
-                editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)]
-            );
-        });
-        search_bar.update(cx, |search_bar, _| {
-            assert_eq!(search_bar.active_match_index, Some(0));
-        });
-
-        search_bar.update(cx, |search_bar, cx| {
-            search_bar.select_prev_match(&SelectPrevMatch, cx);
-            assert_eq!(
-                editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                [DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58)]
-            );
-        });
-        search_bar.update(cx, |search_bar, _| {
-            assert_eq!(search_bar.active_match_index, Some(2));
-        });
-
-        search_bar.update(cx, |search_bar, cx| {
-            search_bar.select_prev_match(&SelectPrevMatch, cx);
-            assert_eq!(
-                editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                [DisplayPoint::new(3, 11)..DisplayPoint::new(3, 13)]
-            );
-        });
-        search_bar.update(cx, |search_bar, _| {
-            assert_eq!(search_bar.active_match_index, Some(1));
-        });
-
-        search_bar.update(cx, |search_bar, cx| {
-            search_bar.select_prev_match(&SelectPrevMatch, cx);
-            assert_eq!(
-                editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)]
-            );
-        });
-        search_bar.update(cx, |search_bar, _| {
-            assert_eq!(search_bar.active_match_index, Some(0));
-        });
-
-        // Park the cursor in between matches and ensure that going to the previous match selects
-        // the closest match to the left.
-        editor.update(cx, |editor, cx| {
-            editor.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
-            });
-        });
-        search_bar.update(cx, |search_bar, cx| {
-            assert_eq!(search_bar.active_match_index, Some(1));
-            search_bar.select_prev_match(&SelectPrevMatch, cx);
-            assert_eq!(
-                editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)]
-            );
-        });
-        search_bar.update(cx, |search_bar, _| {
-            assert_eq!(search_bar.active_match_index, Some(0));
-        });
-
-        // Park the cursor in between matches and ensure that going to the next match selects the
-        // closest match to the right.
-        editor.update(cx, |editor, cx| {
-            editor.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
-            });
-        });
-        search_bar.update(cx, |search_bar, cx| {
-            assert_eq!(search_bar.active_match_index, Some(1));
-            search_bar.select_next_match(&SelectNextMatch, cx);
-            assert_eq!(
-                editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                [DisplayPoint::new(3, 11)..DisplayPoint::new(3, 13)]
-            );
-        });
-        search_bar.update(cx, |search_bar, _| {
-            assert_eq!(search_bar.active_match_index, Some(1));
-        });
-
-        // Park the cursor after the last match and ensure that going to the previous match selects
-        // the last match.
-        editor.update(cx, |editor, cx| {
-            editor.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(3, 60)..DisplayPoint::new(3, 60)])
-            });
-        });
-        search_bar.update(cx, |search_bar, cx| {
-            assert_eq!(search_bar.active_match_index, Some(2));
-            search_bar.select_prev_match(&SelectPrevMatch, cx);
-            assert_eq!(
-                editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                [DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58)]
-            );
-        });
-        search_bar.update(cx, |search_bar, _| {
-            assert_eq!(search_bar.active_match_index, Some(2));
-        });
-
-        // Park the cursor after the last match and ensure that going to the next match selects the
-        // first match.
-        editor.update(cx, |editor, cx| {
-            editor.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(3, 60)..DisplayPoint::new(3, 60)])
-            });
-        });
-        search_bar.update(cx, |search_bar, cx| {
-            assert_eq!(search_bar.active_match_index, Some(2));
-            search_bar.select_next_match(&SelectNextMatch, cx);
-            assert_eq!(
-                editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)]
-            );
-        });
-        search_bar.update(cx, |search_bar, _| {
-            assert_eq!(search_bar.active_match_index, Some(0));
-        });
-
-        // Park the cursor before the first match and ensure that going to the previous match
-        // selects the last match.
-        editor.update(cx, |editor, cx| {
-            editor.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)])
-            });
-        });
-        search_bar.update(cx, |search_bar, cx| {
-            assert_eq!(search_bar.active_match_index, Some(0));
-            search_bar.select_prev_match(&SelectPrevMatch, cx);
-            assert_eq!(
-                editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                [DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58)]
-            );
-        });
-        search_bar.update(cx, |search_bar, _| {
-            assert_eq!(search_bar.active_match_index, Some(2));
-        });
-    }
-
-    #[gpui::test]
-    async fn test_search_option_handling(cx: &mut TestAppContext) {
-        let (editor, search_bar, cx) = init_test(cx);
-
-        // show with options should make current search case sensitive
-        search_bar
-            .update(cx, |search_bar, cx| {
-                search_bar.show(cx);
-                search_bar.search("us", Some(SearchOptions::CASE_SENSITIVE), cx)
-            })
-            .await
-            .unwrap();
-        // todo! osiewicz: these tests previously asserted on background color highlights; that should be introduced back.
-        let display_points_of = |background_highlights: Vec<(Range<DisplayPoint>, Hsla)>| {
-            background_highlights
-                .into_iter()
-                .map(|(range, _)| range)
-                .collect::<Vec<_>>()
-        };
-        editor.update(cx, |editor, cx| {
-            assert_eq!(
-                display_points_of(editor.all_text_background_highlights(cx)),
-                &[DisplayPoint::new(2, 43)..DisplayPoint::new(2, 45),]
-            );
-        });
-
-        // search_suggested should restore default options
-        search_bar.update(cx, |search_bar, cx| {
-            search_bar.search_suggested(cx);
-            assert_eq!(search_bar.search_options, SearchOptions::NONE)
-        });
-
-        // toggling a search option should update the defaults
-        search_bar
-            .update(cx, |search_bar, cx| {
-                search_bar.search("regex", Some(SearchOptions::CASE_SENSITIVE), cx)
-            })
-            .await
-            .unwrap();
-        search_bar.update(cx, |search_bar, cx| {
-            search_bar.toggle_search_option(SearchOptions::WHOLE_WORD, cx)
-        });
-        let mut editor_notifications = cx.notifications(&editor);
-        editor_notifications.next().await;
-        editor.update(cx, |editor, cx| {
-            assert_eq!(
-                display_points_of(editor.all_text_background_highlights(cx)),
-                &[DisplayPoint::new(0, 35)..DisplayPoint::new(0, 40),]
-            );
-        });
-
-        // defaults should still include whole word
-        search_bar.update(cx, |search_bar, cx| {
-            search_bar.search_suggested(cx);
-            assert_eq!(
-                search_bar.search_options,
-                SearchOptions::CASE_SENSITIVE | SearchOptions::WHOLE_WORD
-            )
-        });
-    }
-
-    #[gpui::test]
-    async fn test_search_select_all_matches(cx: &mut TestAppContext) {
-        init_globals(cx);
-        let buffer_text = r#"
-        A regular expression (shortened as regex or regexp;[1] also referred to as
-        rational expression[2][3]) is a sequence of characters that specifies a search
-        pattern in text. Usually such patterns are used by string-searching algorithms
-        for "find" or "find and replace" operations on strings, or for input validation.
-        "#
-        .unindent();
-        let expected_query_matches_count = buffer_text
-            .chars()
-            .filter(|c| c.to_ascii_lowercase() == 'a')
-            .count();
-        assert!(
-            expected_query_matches_count > 1,
-            "Should pick a query with multiple results"
-        );
-        let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), buffer_text));
-        let window = cx.add_window(|_| EmptyView {});
-
-        let editor = window.build_view(cx, |cx| Editor::for_buffer(buffer.clone(), None, cx));
-
-        let search_bar = window.build_view(cx, |cx| {
-            let mut search_bar = BufferSearchBar::new(cx);
-            search_bar.set_active_pane_item(Some(&editor), cx);
-            search_bar.show(cx);
-            search_bar
-        });
-
-        window
-            .update(cx, |_, cx| {
-                search_bar.update(cx, |search_bar, cx| search_bar.search("a", None, cx))
-            })
-            .unwrap()
-            .await
-            .unwrap();
-        let initial_selections = window
-            .update(cx, |_, cx| {
-                search_bar.update(cx, |search_bar, cx| {
-                    let handle = search_bar.query_editor.focus_handle(cx);
-                    cx.focus(&handle);
-                    search_bar.activate_current_match(cx);
-                });
-                assert!(
-                    !editor.read(cx).is_focused(cx),
-                    "Initially, the editor should not be focused"
-                );
-                let initial_selections = editor.update(cx, |editor, cx| {
-                    let initial_selections = editor.selections.display_ranges(cx);
-                    assert_eq!(
-                        initial_selections.len(), 1,
-                        "Expected to have only one selection before adding carets to all matches, but got: {initial_selections:?}",
-                    );
-                    initial_selections
-                });
-                search_bar.update(cx, |search_bar, cx| {
-                    assert_eq!(search_bar.active_match_index, Some(0));
-                    let handle = search_bar.query_editor.focus_handle(cx);
-                    cx.focus(&handle);
-                    search_bar.select_all_matches(&SelectAllMatches, cx);
-                });
-                assert!(
-                    editor.read(cx).is_focused(cx),
-                    "Should focus editor after successful SelectAllMatches"
-                );
-                search_bar.update(cx, |search_bar, cx| {
-                    let all_selections =
-                        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx));
-                    assert_eq!(
-                        all_selections.len(),
-                        expected_query_matches_count,
-                        "Should select all `a` characters in the buffer, but got: {all_selections:?}"
-                    );
-                    assert_eq!(
-                        search_bar.active_match_index,
-                        Some(0),
-                        "Match index should not change after selecting all matches"
-                    );
-                });
-
-                search_bar.update(cx, |this, cx| this.select_next_match(&SelectNextMatch, cx));
-                initial_selections
-            }).unwrap();
-
-        window
-            .update(cx, |_, cx| {
-                assert!(
-                    editor.read(cx).is_focused(cx),
-                    "Should still have editor focused after SelectNextMatch"
-                );
-                search_bar.update(cx, |search_bar, cx| {
-                    let all_selections =
-                        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx));
-                    assert_eq!(
-                        all_selections.len(),
-                        1,
-                        "On next match, should deselect items and select the next match"
-                    );
-                    assert_ne!(
-                        all_selections, initial_selections,
-                        "Next match should be different from the first selection"
-                    );
-                    assert_eq!(
-                        search_bar.active_match_index,
-                        Some(1),
-                        "Match index should be updated to the next one"
-                    );
-                    let handle = search_bar.query_editor.focus_handle(cx);
-                    cx.focus(&handle);
-                    search_bar.select_all_matches(&SelectAllMatches, cx);
-                });
-            })
-            .unwrap();
-        window
-            .update(cx, |_, cx| {
-                assert!(
-                    editor.read(cx).is_focused(cx),
-                    "Should focus editor after successful SelectAllMatches"
-                );
-                search_bar.update(cx, |search_bar, cx| {
-                    let all_selections =
-                        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx));
-                    assert_eq!(
-                    all_selections.len(),
-                    expected_query_matches_count,
-                    "Should select all `a` characters in the buffer, but got: {all_selections:?}"
-                );
-                    assert_eq!(
-                        search_bar.active_match_index,
-                        Some(1),
-                        "Match index should not change after selecting all matches"
-                    );
-                });
-                search_bar.update(cx, |search_bar, cx| {
-                    search_bar.select_prev_match(&SelectPrevMatch, cx);
-                });
-            })
-            .unwrap();
-        let last_match_selections = window
-            .update(cx, |_, cx| {
-                assert!(
-                    editor.read(cx).is_focused(&cx),
-                    "Should still have editor focused after SelectPrevMatch"
-                );
-
-                search_bar.update(cx, |search_bar, cx| {
-                    let all_selections =
-                        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx));
-                    assert_eq!(
-                        all_selections.len(),
-                        1,
-                        "On previous match, should deselect items and select the previous item"
-                    );
-                    assert_eq!(
-                        all_selections, initial_selections,
-                        "Previous match should be the same as the first selection"
-                    );
-                    assert_eq!(
-                        search_bar.active_match_index,
-                        Some(0),
-                        "Match index should be updated to the previous one"
-                    );
-                    all_selections
-                })
-            })
-            .unwrap();
-
-        window
-            .update(cx, |_, cx| {
-                search_bar.update(cx, |search_bar, cx| {
-                    let handle = search_bar.query_editor.focus_handle(cx);
-                    cx.focus(&handle);
-                    search_bar.search("abas_nonexistent_match", None, cx)
-                })
-            })
-            .unwrap()
-            .await
-            .unwrap();
-        window
-            .update(cx, |_, cx| {
-                search_bar.update(cx, |search_bar, cx| {
-                    search_bar.select_all_matches(&SelectAllMatches, cx);
-                });
-                assert!(
-                    editor.update(cx, |this, cx| !this.is_focused(cx.window_context())),
-                    "Should not switch focus to editor if SelectAllMatches does not find any matches"
-                );
-                search_bar.update(cx, |search_bar, cx| {
-                    let all_selections =
-                        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx));
-                    assert_eq!(
-                        all_selections, last_match_selections,
-                        "Should not select anything new if there are no matches"
-                    );
-                    assert!(
-                        search_bar.active_match_index.is_none(),
-                        "For no matches, there should be no active match index"
-                    );
-                });
-            })
-            .unwrap();
-    }
-
-    #[gpui::test]
-    async fn test_search_query_history(cx: &mut TestAppContext) {
-        //crate::project_search::tests::init_test(cx);
-        init_globals(cx);
-        let buffer_text = r#"
-        A regular expression (shortened as regex or regexp;[1] also referred to as
-        rational expression[2][3]) is a sequence of characters that specifies a search
-        pattern in text. Usually such patterns are used by string-searching algorithms
-        for "find" or "find and replace" operations on strings, or for input validation.
-        "#
-        .unindent();
-        let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), buffer_text));
-        let (_, cx) = cx.add_window_view(|_| EmptyView {});
-
-        let editor = cx.new_view(|cx| Editor::for_buffer(buffer.clone(), None, cx));
-
-        let search_bar = cx.new_view(|cx| {
-            let mut search_bar = BufferSearchBar::new(cx);
-            search_bar.set_active_pane_item(Some(&editor), cx);
-            search_bar.show(cx);
-            search_bar
-        });
-
-        // Add 3 search items into the history.
-        search_bar
-            .update(cx, |search_bar, cx| search_bar.search("a", None, cx))
-            .await
-            .unwrap();
-        search_bar
-            .update(cx, |search_bar, cx| search_bar.search("b", None, cx))
-            .await
-            .unwrap();
-        search_bar
-            .update(cx, |search_bar, cx| {
-                search_bar.search("c", Some(SearchOptions::CASE_SENSITIVE), cx)
-            })
-            .await
-            .unwrap();
-        // Ensure that the latest search is active.
-        search_bar.update(cx, |search_bar, cx| {
-            assert_eq!(search_bar.query(cx), "c");
-            assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
-        });
-
-        // Next history query after the latest should set the query to the empty string.
-        search_bar.update(cx, |search_bar, cx| {
-            search_bar.next_history_query(&NextHistoryQuery, cx);
-        });
-        search_bar.update(cx, |search_bar, cx| {
-            assert_eq!(search_bar.query(cx), "");
-            assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
-        });
-        search_bar.update(cx, |search_bar, cx| {
-            search_bar.next_history_query(&NextHistoryQuery, cx);
-        });
-        search_bar.update(cx, |search_bar, cx| {
-            assert_eq!(search_bar.query(cx), "");
-            assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
-        });
-
-        // First previous query for empty current query should set the query to the latest.
-        search_bar.update(cx, |search_bar, cx| {
-            search_bar.previous_history_query(&PreviousHistoryQuery, cx);
-        });
-        search_bar.update(cx, |search_bar, cx| {
-            assert_eq!(search_bar.query(cx), "c");
-            assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
-        });
-
-        // Further previous items should go over the history in reverse order.
-        search_bar.update(cx, |search_bar, cx| {
-            search_bar.previous_history_query(&PreviousHistoryQuery, cx);
-        });
-        search_bar.update(cx, |search_bar, cx| {
-            assert_eq!(search_bar.query(cx), "b");
-            assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
-        });
-
-        // Previous items should never go behind the first history item.
-        search_bar.update(cx, |search_bar, cx| {
-            search_bar.previous_history_query(&PreviousHistoryQuery, cx);
-        });
-        search_bar.update(cx, |search_bar, cx| {
-            assert_eq!(search_bar.query(cx), "a");
-            assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
-        });
-        search_bar.update(cx, |search_bar, cx| {
-            search_bar.previous_history_query(&PreviousHistoryQuery, cx);
-        });
-        search_bar.update(cx, |search_bar, cx| {
-            assert_eq!(search_bar.query(cx), "a");
-            assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
-        });
-
-        // Next items should go over the history in the original order.
-        search_bar.update(cx, |search_bar, cx| {
-            search_bar.next_history_query(&NextHistoryQuery, cx);
-        });
-        search_bar.update(cx, |search_bar, cx| {
-            assert_eq!(search_bar.query(cx), "b");
-            assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
-        });
-
-        search_bar
-            .update(cx, |search_bar, cx| search_bar.search("ba", None, cx))
-            .await
-            .unwrap();
-        search_bar.update(cx, |search_bar, cx| {
-            assert_eq!(search_bar.query(cx), "ba");
-            assert_eq!(search_bar.search_options, SearchOptions::NONE);
-        });
-
-        // New search input should add another entry to history and move the selection to the end of the history.
-        search_bar.update(cx, |search_bar, cx| {
-            search_bar.previous_history_query(&PreviousHistoryQuery, cx);
-        });
-        search_bar.update(cx, |search_bar, cx| {
-            assert_eq!(search_bar.query(cx), "c");
-            assert_eq!(search_bar.search_options, SearchOptions::NONE);
-        });
-        search_bar.update(cx, |search_bar, cx| {
-            search_bar.previous_history_query(&PreviousHistoryQuery, cx);
-        });
-        search_bar.update(cx, |search_bar, cx| {
-            assert_eq!(search_bar.query(cx), "b");
-            assert_eq!(search_bar.search_options, SearchOptions::NONE);
-        });
-        search_bar.update(cx, |search_bar, cx| {
-            search_bar.next_history_query(&NextHistoryQuery, cx);
-        });
-        search_bar.update(cx, |search_bar, cx| {
-            assert_eq!(search_bar.query(cx), "c");
-            assert_eq!(search_bar.search_options, SearchOptions::NONE);
-        });
-        search_bar.update(cx, |search_bar, cx| {
-            search_bar.next_history_query(&NextHistoryQuery, cx);
-        });
-        search_bar.update(cx, |search_bar, cx| {
-            assert_eq!(search_bar.query(cx), "ba");
-            assert_eq!(search_bar.search_options, SearchOptions::NONE);
-        });
-        search_bar.update(cx, |search_bar, cx| {
-            search_bar.next_history_query(&NextHistoryQuery, cx);
-        });
-        search_bar.update(cx, |search_bar, cx| {
-            assert_eq!(search_bar.query(cx), "");
-            assert_eq!(search_bar.search_options, SearchOptions::NONE);
-        });
-    }
-
-    #[gpui::test]
-    async fn test_replace_simple(cx: &mut TestAppContext) {
-        let (editor, search_bar, cx) = init_test(cx);
-
-        search_bar
-            .update(cx, |search_bar, cx| {
-                search_bar.search("expression", None, cx)
-            })
-            .await
-            .unwrap();
-
-        search_bar.update(cx, |search_bar, cx| {
-            search_bar.replacement_editor.update(cx, |editor, cx| {
-                // We use $1 here as initially we should be in Text mode, where `$1` should be treated literally.
-                editor.set_text("expr$1", cx);
-            });
-            search_bar.replace_all(&ReplaceAll, cx)
-        });
-        assert_eq!(
-            editor.update(cx, |this, cx| { this.text(cx) }),
-            r#"
-        A regular expr$1 (shortened as regex or regexp;[1] also referred to as
-        rational expr$1[2][3]) is a sequence of characters that specifies a search
-        pattern in text. Usually such patterns are used by string-searching algorithms
-        for "find" or "find and replace" operations on strings, or for input validation.
-        "#
-            .unindent()
-        );
-
-        // Search for word boundaries and replace just a single one.
-        search_bar
-            .update(cx, |search_bar, cx| {
-                search_bar.search("or", Some(SearchOptions::WHOLE_WORD), cx)
-            })
-            .await
-            .unwrap();
-
-        search_bar.update(cx, |search_bar, cx| {
-            search_bar.replacement_editor.update(cx, |editor, cx| {
-                editor.set_text("banana", cx);
-            });
-            search_bar.replace_next(&ReplaceNext, cx)
-        });
-        // Notice how the first or in the text (shORtened) is not replaced. Neither are the remaining hits of `or` in the text.
-        assert_eq!(
-            editor.update(cx, |this, cx| { this.text(cx) }),
-            r#"
-        A regular expr$1 (shortened as regex banana regexp;[1] also referred to as
-        rational expr$1[2][3]) is a sequence of characters that specifies a search
-        pattern in text. Usually such patterns are used by string-searching algorithms
-        for "find" or "find and replace" operations on strings, or for input validation.
-        "#
-            .unindent()
-        );
-        // Let's turn on regex mode.
-        search_bar
-            .update(cx, |search_bar, cx| {
-                search_bar.activate_search_mode(SearchMode::Regex, cx);
-                search_bar.search("\\[([^\\]]+)\\]", None, cx)
-            })
-            .await
-            .unwrap();
-        search_bar.update(cx, |search_bar, cx| {
-            search_bar.replacement_editor.update(cx, |editor, cx| {
-                editor.set_text("${1}number", cx);
-            });
-            search_bar.replace_all(&ReplaceAll, cx)
-        });
-        assert_eq!(
-            editor.update(cx, |this, cx| { this.text(cx) }),
-            r#"
-        A regular expr$1 (shortened as regex banana regexp;1number also referred to as
-        rational expr$12number3number) is a sequence of characters that specifies a search
-        pattern in text. Usually such patterns are used by string-searching algorithms
-        for "find" or "find and replace" operations on strings, or for input validation.
-        "#
-            .unindent()
-        );
-        // Now with a whole-word twist.
-        search_bar
-            .update(cx, |search_bar, cx| {
-                search_bar.activate_search_mode(SearchMode::Regex, cx);
-                search_bar.search("a\\w+s", Some(SearchOptions::WHOLE_WORD), cx)
-            })
-            .await
-            .unwrap();
-        search_bar.update(cx, |search_bar, cx| {
-            search_bar.replacement_editor.update(cx, |editor, cx| {
-                editor.set_text("things", cx);
-            });
-            search_bar.replace_all(&ReplaceAll, cx)
-        });
-        // The only word affected by this edit should be `algorithms`, even though there's a bunch
-        // of words in this text that would match this regex if not for WHOLE_WORD.
-        assert_eq!(
-            editor.update(cx, |this, cx| { this.text(cx) }),
-            r#"
-        A regular expr$1 (shortened as regex banana regexp;1number also referred to as
-        rational expr$12number3number) is a sequence of characters that specifies a search
-        pattern in text. Usually such patterns are used by string-searching things
-        for "find" or "find and replace" operations on strings, or for input validation.
-        "#
-            .unindent()
-        );
-    }
-}

crates/search2/src/history.rs 🔗

@@ -1,184 +0,0 @@
-use smallvec::SmallVec;
-const SEARCH_HISTORY_LIMIT: usize = 20;
-
-#[derive(Default, Debug, Clone)]
-pub struct SearchHistory {
-    history: SmallVec<[String; SEARCH_HISTORY_LIMIT]>,
-    selected: Option<usize>,
-}
-
-impl SearchHistory {
-    pub fn add(&mut self, search_string: String) {
-        if let Some(i) = self.selected {
-            if search_string == self.history[i] {
-                return;
-            }
-        }
-
-        if let Some(previously_searched) = self.history.last_mut() {
-            if search_string.find(previously_searched.as_str()).is_some() {
-                *previously_searched = search_string;
-                self.selected = Some(self.history.len() - 1);
-                return;
-            }
-        }
-
-        self.history.push(search_string);
-        if self.history.len() > SEARCH_HISTORY_LIMIT {
-            self.history.remove(0);
-        }
-        self.selected = Some(self.history.len() - 1);
-    }
-
-    pub fn next(&mut self) -> Option<&str> {
-        let history_size = self.history.len();
-        if history_size == 0 {
-            return None;
-        }
-
-        let selected = self.selected?;
-        if selected == history_size - 1 {
-            return None;
-        }
-        let next_index = selected + 1;
-        self.selected = Some(next_index);
-        Some(&self.history[next_index])
-    }
-
-    pub fn current(&self) -> Option<&str> {
-        Some(&self.history[self.selected?])
-    }
-
-    pub fn previous(&mut self) -> Option<&str> {
-        let history_size = self.history.len();
-        if history_size == 0 {
-            return None;
-        }
-
-        let prev_index = match self.selected {
-            Some(selected_index) => {
-                if selected_index == 0 {
-                    return None;
-                } else {
-                    selected_index - 1
-                }
-            }
-            None => history_size - 1,
-        };
-
-        self.selected = Some(prev_index);
-        Some(&self.history[prev_index])
-    }
-
-    pub fn reset_selection(&mut self) {
-        self.selected = None;
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn test_add() {
-        let mut search_history = SearchHistory::default();
-        assert_eq!(
-            search_history.current(),
-            None,
-            "No current selection should be set fo the default search history"
-        );
-
-        search_history.add("rust".to_string());
-        assert_eq!(
-            search_history.current(),
-            Some("rust"),
-            "Newly added item should be selected"
-        );
-
-        // check if duplicates are not added
-        search_history.add("rust".to_string());
-        assert_eq!(
-            search_history.history.len(),
-            1,
-            "Should not add a duplicate"
-        );
-        assert_eq!(search_history.current(), Some("rust"));
-
-        // check if new string containing the previous string replaces it
-        search_history.add("rustlang".to_string());
-        assert_eq!(
-            search_history.history.len(),
-            1,
-            "Should replace previous item if it's a substring"
-        );
-        assert_eq!(search_history.current(), Some("rustlang"));
-
-        // push enough items to test SEARCH_HISTORY_LIMIT
-        for i in 0..SEARCH_HISTORY_LIMIT * 2 {
-            search_history.add(format!("item{i}"));
-        }
-        assert!(search_history.history.len() <= SEARCH_HISTORY_LIMIT);
-    }
-
-    #[test]
-    fn test_next_and_previous() {
-        let mut search_history = SearchHistory::default();
-        assert_eq!(
-            search_history.next(),
-            None,
-            "Default search history should not have a next item"
-        );
-
-        search_history.add("Rust".to_string());
-        assert_eq!(search_history.next(), None);
-        search_history.add("JavaScript".to_string());
-        assert_eq!(search_history.next(), None);
-        search_history.add("TypeScript".to_string());
-        assert_eq!(search_history.next(), None);
-
-        assert_eq!(search_history.current(), Some("TypeScript"));
-
-        assert_eq!(search_history.previous(), Some("JavaScript"));
-        assert_eq!(search_history.current(), Some("JavaScript"));
-
-        assert_eq!(search_history.previous(), Some("Rust"));
-        assert_eq!(search_history.current(), Some("Rust"));
-
-        assert_eq!(search_history.previous(), None);
-        assert_eq!(search_history.current(), Some("Rust"));
-
-        assert_eq!(search_history.next(), Some("JavaScript"));
-        assert_eq!(search_history.current(), Some("JavaScript"));
-
-        assert_eq!(search_history.next(), Some("TypeScript"));
-        assert_eq!(search_history.current(), Some("TypeScript"));
-
-        assert_eq!(search_history.next(), None);
-        assert_eq!(search_history.current(), Some("TypeScript"));
-    }
-
-    #[test]
-    fn test_reset_selection() {
-        let mut search_history = SearchHistory::default();
-        search_history.add("Rust".to_string());
-        search_history.add("JavaScript".to_string());
-        search_history.add("TypeScript".to_string());
-
-        assert_eq!(search_history.current(), Some("TypeScript"));
-        search_history.reset_selection();
-        assert_eq!(search_history.current(), None);
-        assert_eq!(
-            search_history.previous(),
-            Some("TypeScript"),
-            "Should start from the end after reset on previous item query"
-        );
-
-        search_history.previous();
-        assert_eq!(search_history.current(), Some("JavaScript"));
-        search_history.previous();
-        assert_eq!(search_history.current(), Some("Rust"));
-
-        search_history.reset_selection();
-        assert_eq!(search_history.current(), None);
-    }
-}

crates/search2/src/mode.rs 🔗

@@ -1,46 +0,0 @@
-use gpui::{Action, SharedString};
-
-use crate::{ActivateRegexMode, ActivateSemanticMode, ActivateTextMode};
-
-// TODO: Update the default search mode to get from config
-#[derive(Copy, Clone, Debug, Default, PartialEq)]
-pub enum SearchMode {
-    #[default]
-    Text,
-    Semantic,
-    Regex,
-}
-
-impl SearchMode {
-    pub(crate) fn label(&self) -> &'static str {
-        match self {
-            SearchMode::Text => "Text",
-            SearchMode::Semantic => "Semantic",
-            SearchMode::Regex => "Regex",
-        }
-    }
-    pub(crate) fn tooltip(&self) -> SharedString {
-        format!("Activate {} Mode", self.label()).into()
-    }
-    pub(crate) fn action(&self) -> Box<dyn Action> {
-        match self {
-            SearchMode::Text => ActivateTextMode.boxed_clone(),
-            SearchMode::Semantic => ActivateSemanticMode.boxed_clone(),
-            SearchMode::Regex => ActivateRegexMode.boxed_clone(),
-        }
-    }
-}
-
-pub(crate) fn next_mode(mode: &SearchMode, semantic_enabled: bool) -> SearchMode {
-    match mode {
-        SearchMode::Text => SearchMode::Regex,
-        SearchMode::Regex => {
-            if semantic_enabled {
-                SearchMode::Semantic
-            } else {
-                SearchMode::Text
-            }
-        }
-        SearchMode::Semantic => SearchMode::Text,
-    }
-}

crates/search2/src/project_search.rs 🔗

@@ -1,2844 +0,0 @@
-use crate::{
-    history::SearchHistory, mode::SearchMode, ActivateRegexMode, ActivateSemanticMode,
-    ActivateTextMode, CycleMode, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext,
-    SearchOptions, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleIncludeIgnored,
-    ToggleReplace, ToggleWholeWord,
-};
-use anyhow::{Context as _, Result};
-use collections::HashMap;
-use editor::{
-    items::active_match_index, scroll::autoscroll::Autoscroll, Anchor, Editor, EditorEvent,
-    MultiBuffer, SelectAll, MAX_TAB_TITLE_LEN,
-};
-use editor::{EditorElement, EditorStyle};
-use gpui::{
-    actions, div, AnyElement, AnyView, AppContext, Context as _, Element, EntityId, EventEmitter,
-    FocusHandle, FocusableView, FontStyle, FontWeight, Hsla, InteractiveElement, IntoElement,
-    KeyContext, Model, ModelContext, ParentElement, PromptLevel, Render, SharedString, Styled,
-    Subscription, Task, TextStyle, View, ViewContext, VisualContext, WeakModel, WeakView,
-    WhiteSpace, WindowContext,
-};
-use menu::Confirm;
-use project::{
-    search::{SearchInputs, SearchQuery},
-    Entry, Project,
-};
-use semantic_index::{SemanticIndex, SemanticIndexStatus};
-
-use settings::Settings;
-use smol::stream::StreamExt;
-use std::{
-    any::{Any, TypeId},
-    collections::HashSet,
-    mem,
-    ops::{Not, Range},
-    path::PathBuf,
-    time::{Duration, Instant},
-};
-use theme::ThemeSettings;
-
-use ui::{
-    h_stack, prelude::*, v_stack, Button, Icon, IconButton, IconElement, Label, LabelCommon,
-    LabelSize, Selectable, Tooltip,
-};
-use util::{paths::PathMatcher, ResultExt as _};
-use workspace::{
-    item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
-    searchable::{Direction, SearchableItem, SearchableItemHandle},
-    ItemNavHistory, Pane, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
-    WorkspaceId,
-};
-
-actions!(
-    project_search,
-    [SearchInNew, ToggleFocus, NextField, ToggleFilters]
-);
-
-#[derive(Default)]
-struct ActiveSearches(HashMap<WeakModel<Project>, WeakView<ProjectSearchView>>);
-
-#[derive(Default)]
-struct ActiveSettings(HashMap<WeakModel<Project>, ProjectSearchSettings>);
-
-pub fn init(cx: &mut AppContext) {
-    // todo!() po
-    cx.set_global(ActiveSearches::default());
-    cx.set_global(ActiveSettings::default());
-    cx.observe_new_views(|workspace: &mut Workspace, _cx| {
-        workspace
-            .register_action(ProjectSearchView::deploy)
-            .register_action(ProjectSearchBar::search_in_new);
-    })
-    .detach();
-}
-
-struct ProjectSearch {
-    project: Model<Project>,
-    excerpts: Model<MultiBuffer>,
-    pending_search: Option<Task<Option<()>>>,
-    match_ranges: Vec<Range<Anchor>>,
-    active_query: Option<SearchQuery>,
-    search_id: usize,
-    search_history: SearchHistory,
-    no_results: Option<bool>,
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-enum InputPanel {
-    Query,
-    Exclude,
-    Include,
-}
-
-pub struct ProjectSearchView {
-    focus_handle: FocusHandle,
-    model: Model<ProjectSearch>,
-    query_editor: View<Editor>,
-    replacement_editor: View<Editor>,
-    results_editor: View<Editor>,
-    semantic_state: Option<SemanticState>,
-    semantic_permissioned: Option<bool>,
-    search_options: SearchOptions,
-    panels_with_errors: HashSet<InputPanel>,
-    active_match_index: Option<usize>,
-    search_id: usize,
-    query_editor_was_focused: bool,
-    included_files_editor: View<Editor>,
-    excluded_files_editor: View<Editor>,
-    filters_enabled: bool,
-    replace_enabled: bool,
-    current_mode: SearchMode,
-    _subscriptions: Vec<Subscription>,
-}
-
-struct SemanticState {
-    index_status: SemanticIndexStatus,
-    maintain_rate_limit: Option<Task<()>>,
-    _subscription: Subscription,
-}
-
-#[derive(Debug, Clone)]
-struct ProjectSearchSettings {
-    search_options: SearchOptions,
-    filters_enabled: bool,
-    current_mode: SearchMode,
-}
-
-pub struct ProjectSearchBar {
-    active_project_search: Option<View<ProjectSearchView>>,
-    subscription: Option<Subscription>,
-}
-
-impl ProjectSearch {
-    fn new(project: Model<Project>, cx: &mut ModelContext<Self>) -> Self {
-        let replica_id = project.read(cx).replica_id();
-        Self {
-            project,
-            excerpts: cx.new_model(|_| MultiBuffer::new(replica_id)),
-            pending_search: Default::default(),
-            match_ranges: Default::default(),
-            active_query: None,
-            search_id: 0,
-            search_history: SearchHistory::default(),
-            no_results: None,
-        }
-    }
-
-    fn clone(&self, cx: &mut ModelContext<Self>) -> Model<Self> {
-        cx.new_model(|cx| Self {
-            project: self.project.clone(),
-            excerpts: self
-                .excerpts
-                .update(cx, |excerpts, cx| cx.new_model(|cx| excerpts.clone(cx))),
-            pending_search: Default::default(),
-            match_ranges: self.match_ranges.clone(),
-            active_query: self.active_query.clone(),
-            search_id: self.search_id,
-            search_history: self.search_history.clone(),
-            no_results: self.no_results.clone(),
-        })
-    }
-
-    fn search(&mut self, query: SearchQuery, cx: &mut ModelContext<Self>) {
-        let search = self
-            .project
-            .update(cx, |project, cx| project.search(query.clone(), cx));
-        self.search_id += 1;
-        self.search_history.add(query.as_str().to_string());
-        self.active_query = Some(query);
-        self.match_ranges.clear();
-        self.pending_search = Some(cx.spawn(|this, mut cx| async move {
-            let mut matches = search;
-            let this = this.upgrade()?;
-            this.update(&mut cx, |this, cx| {
-                this.match_ranges.clear();
-                this.excerpts.update(cx, |this, cx| this.clear(cx));
-                this.no_results = Some(true);
-            })
-            .ok()?;
-
-            while let Some((buffer, anchors)) = matches.next().await {
-                let mut ranges = this
-                    .update(&mut cx, |this, cx| {
-                        this.no_results = Some(false);
-                        this.excerpts.update(cx, |excerpts, cx| {
-                            excerpts.stream_excerpts_with_context_lines(buffer, anchors, 1, cx)
-                        })
-                    })
-                    .ok()?;
-
-                while let Some(range) = ranges.next().await {
-                    this.update(&mut cx, |this, _| this.match_ranges.push(range))
-                        .ok()?;
-                }
-                this.update(&mut cx, |_, cx| cx.notify()).ok()?;
-            }
-
-            this.update(&mut cx, |this, cx| {
-                this.pending_search.take();
-                cx.notify();
-            })
-            .ok()?;
-
-            None
-        }));
-        cx.notify();
-    }
-
-    fn semantic_search(&mut self, inputs: &SearchInputs, cx: &mut ModelContext<Self>) {
-        let search = SemanticIndex::global(cx).map(|index| {
-            index.update(cx, |semantic_index, cx| {
-                semantic_index.search_project(
-                    self.project.clone(),
-                    inputs.as_str().to_owned(),
-                    10,
-                    inputs.files_to_include().to_vec(),
-                    inputs.files_to_exclude().to_vec(),
-                    cx,
-                )
-            })
-        });
-        self.search_id += 1;
-        self.match_ranges.clear();
-        self.search_history.add(inputs.as_str().to_string());
-        self.no_results = None;
-        self.pending_search = Some(cx.spawn(|this, mut cx| async move {
-            let results = search?.await.log_err()?;
-            let matches = results
-                .into_iter()
-                .map(|result| (result.buffer, vec![result.range.start..result.range.start]));
-
-            this.update(&mut cx, |this, cx| {
-                this.no_results = Some(true);
-                this.excerpts.update(cx, |excerpts, cx| {
-                    excerpts.clear(cx);
-                });
-            })
-            .ok()?;
-            for (buffer, ranges) in matches {
-                let mut match_ranges = this
-                    .update(&mut cx, |this, cx| {
-                        this.no_results = Some(false);
-                        this.excerpts.update(cx, |excerpts, cx| {
-                            excerpts.stream_excerpts_with_context_lines(buffer, ranges, 3, cx)
-                        })
-                    })
-                    .ok()?;
-                while let Some(match_range) = match_ranges.next().await {
-                    this.update(&mut cx, |this, cx| {
-                        this.match_ranges.push(match_range);
-                        while let Ok(Some(match_range)) = match_ranges.try_next() {
-                            this.match_ranges.push(match_range);
-                        }
-                        cx.notify();
-                    })
-                    .ok()?;
-                }
-            }
-
-            this.update(&mut cx, |this, cx| {
-                this.pending_search.take();
-                cx.notify();
-            })
-            .ok()?;
-
-            None
-        }));
-        cx.notify();
-    }
-}
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub enum ViewEvent {
-    UpdateTab,
-    Activate,
-    EditorEvent(editor::EditorEvent),
-    Dismiss,
-}
-
-impl EventEmitter<ViewEvent> for ProjectSearchView {}
-
-impl Render for ProjectSearchView {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        if self.has_matches() {
-            div()
-                .flex_1()
-                .size_full()
-                .track_focus(&self.focus_handle)
-                .child(self.results_editor.clone())
-                .into_any()
-        } else {
-            let model = self.model.read(cx);
-            let has_no_results = model.no_results.unwrap_or(false);
-            let is_search_underway = model.pending_search.is_some();
-            let mut major_text = if is_search_underway {
-                Label::new("Searching...")
-            } else if has_no_results {
-                Label::new("No results")
-            } else {
-                Label::new(format!("{} search all files", self.current_mode.label()))
-            };
-
-            let mut show_minor_text = true;
-            let semantic_status = self.semantic_state.as_ref().and_then(|semantic| {
-                let status = semantic.index_status;
-                                match status {
-                                    SemanticIndexStatus::NotAuthenticated => {
-                                        major_text = Label::new("Not Authenticated");
-                                        show_minor_text = false;
-                                        Some(
-                                            "API Key Missing: Please set 'OPENAI_API_KEY' in Environment Variables. If you authenticated using the Assistant Panel, please restart Zed to Authenticate.".to_string())
-                                    }
-                                    SemanticIndexStatus::Indexed => Some("Indexing complete".to_string()),
-                                    SemanticIndexStatus::Indexing {
-                                        remaining_files,
-                                        rate_limit_expiry,
-                                    } => {
-                                        if remaining_files == 0 {
-                                            Some("Indexing...".to_string())
-                                        } else {
-                                            if let Some(rate_limit_expiry) = rate_limit_expiry {
-                                                let remaining_seconds =
-                                                    rate_limit_expiry.duration_since(Instant::now());
-                                                if remaining_seconds > Duration::from_secs(0) {
-                                                    Some(format!(
-                                                        "Remaining files to index (rate limit resets in {}s): {}",
-                                                        remaining_seconds.as_secs(),
-                                                        remaining_files
-                                                    ))
-                                                } else {
-                                                    Some(format!("Remaining files to index: {}", remaining_files))
-                                                }
-                                            } else {
-                                                Some(format!("Remaining files to index: {}", remaining_files))
-                                            }
-                                        }
-                                    }
-                                    SemanticIndexStatus::NotIndexed => None,
-                                }
-            });
-            let major_text = div().justify_center().max_w_96().child(major_text);
-
-            let minor_text: Option<SharedString> = if let Some(no_results) = model.no_results {
-                if model.pending_search.is_none() && no_results {
-                    Some("No results found in this project for the provided query".into())
-                } else {
-                    None
-                }
-            } else {
-                if let Some(mut semantic_status) = semantic_status {
-                    semantic_status.extend(self.landing_text_minor().chars());
-                    Some(semantic_status.into())
-                } else {
-                    Some(self.landing_text_minor())
-                }
-            };
-            let minor_text = minor_text.map(|text| {
-                div()
-                    .items_center()
-                    .max_w_96()
-                    .child(Label::new(text).size(LabelSize::Small))
-            });
-            v_stack()
-                .flex_1()
-                .size_full()
-                .justify_center()
-                .track_focus(&self.focus_handle)
-                .child(
-                    h_stack()
-                        .size_full()
-                        .justify_center()
-                        .child(h_stack().flex_1())
-                        .child(v_stack().child(major_text).children(minor_text))
-                        .child(h_stack().flex_1()),
-                )
-                .into_any()
-        }
-    }
-}
-
-impl FocusableView for ProjectSearchView {
-    fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
-        self.focus_handle.clone()
-    }
-}
-
-impl Item for ProjectSearchView {
-    type Event = ViewEvent;
-    fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
-        let query_text = self.query_editor.read(cx).text(cx);
-
-        query_text
-            .is_empty()
-            .not()
-            .then(|| query_text.into())
-            .or_else(|| Some("Project Search".into()))
-    }
-
-    fn act_as_type<'a>(
-        &'a self,
-        type_id: TypeId,
-        self_handle: &'a View<Self>,
-        _: &'a AppContext,
-    ) -> Option<AnyView> {
-        if type_id == TypeId::of::<Self>() {
-            Some(self_handle.clone().into())
-        } else if type_id == TypeId::of::<Editor>() {
-            Some(self.results_editor.clone().into())
-        } else {
-            None
-        }
-    }
-
-    fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
-        self.results_editor
-            .update(cx, |editor, cx| editor.deactivated(cx));
-    }
-
-    fn tab_content(&self, _: Option<usize>, selected: bool, cx: &WindowContext<'_>) -> AnyElement {
-        let last_query: Option<SharedString> = self
-            .model
-            .read(cx)
-            .search_history
-            .current()
-            .as_ref()
-            .map(|query| {
-                let query_text = util::truncate_and_trailoff(query, MAX_TAB_TITLE_LEN);
-                query_text.into()
-            });
-        let tab_name = last_query
-            .filter(|query| !query.is_empty())
-            .unwrap_or_else(|| "Project search".into());
-        h_stack()
-            .gap_2()
-            .child(IconElement::new(Icon::MagnifyingGlass).color(if selected {
-                Color::Default
-            } else {
-                Color::Muted
-            }))
-            .child(Label::new(tab_name).color(if selected {
-                Color::Default
-            } else {
-                Color::Muted
-            }))
-            .into_any()
-    }
-
-    fn for_each_project_item(
-        &self,
-        cx: &AppContext,
-        f: &mut dyn FnMut(EntityId, &dyn project::Item),
-    ) {
-        self.results_editor.for_each_project_item(cx, f)
-    }
-
-    fn is_singleton(&self, _: &AppContext) -> bool {
-        false
-    }
-
-    fn can_save(&self, _: &AppContext) -> bool {
-        true
-    }
-
-    fn is_dirty(&self, cx: &AppContext) -> bool {
-        self.results_editor.read(cx).is_dirty(cx)
-    }
-
-    fn has_conflict(&self, cx: &AppContext) -> bool {
-        self.results_editor.read(cx).has_conflict(cx)
-    }
-
-    fn save(
-        &mut self,
-        project: Model<Project>,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<anyhow::Result<()>> {
-        self.results_editor
-            .update(cx, |editor, cx| editor.save(project, cx))
-    }
-
-    fn save_as(
-        &mut self,
-        _: Model<Project>,
-        _: PathBuf,
-        _: &mut ViewContext<Self>,
-    ) -> Task<anyhow::Result<()>> {
-        unreachable!("save_as should not have been called")
-    }
-
-    fn reload(
-        &mut self,
-        project: Model<Project>,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<anyhow::Result<()>> {
-        self.results_editor
-            .update(cx, |editor, cx| editor.reload(project, cx))
-    }
-
-    fn clone_on_split(
-        &self,
-        _workspace_id: WorkspaceId,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<View<Self>>
-    where
-        Self: Sized,
-    {
-        let model = self.model.update(cx, |model, cx| model.clone(cx));
-        Some(cx.new_view(|cx| Self::new(model, cx, None)))
-    }
-
-    fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
-        self.results_editor
-            .update(cx, |editor, cx| editor.added_to_workspace(workspace, cx));
-    }
-
-    fn set_nav_history(&mut self, nav_history: ItemNavHistory, cx: &mut ViewContext<Self>) {
-        self.results_editor.update(cx, |editor, _| {
-            editor.set_nav_history(Some(nav_history));
-        });
-    }
-
-    fn navigate(&mut self, data: Box<dyn Any>, cx: &mut ViewContext<Self>) -> bool {
-        self.results_editor
-            .update(cx, |editor, cx| editor.navigate(data, cx))
-    }
-
-    fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) {
-        match event {
-            ViewEvent::UpdateTab => {
-                f(ItemEvent::UpdateBreadcrumbs);
-                f(ItemEvent::UpdateTab);
-            }
-            ViewEvent::EditorEvent(editor_event) => {
-                Editor::to_item_events(editor_event, f);
-            }
-            ViewEvent::Dismiss => f(ItemEvent::CloseItem),
-            _ => {}
-        }
-    }
-
-    fn breadcrumb_location(&self) -> ToolbarItemLocation {
-        if self.has_matches() {
-            ToolbarItemLocation::Secondary
-        } else {
-            ToolbarItemLocation::Hidden
-        }
-    }
-
-    fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
-        self.results_editor.breadcrumbs(theme, cx)
-    }
-
-    fn serialized_item_kind() -> Option<&'static str> {
-        None
-    }
-
-    fn deserialize(
-        _project: Model<Project>,
-        _workspace: WeakView<Workspace>,
-        _workspace_id: workspace::WorkspaceId,
-        _item_id: workspace::ItemId,
-        _cx: &mut ViewContext<Pane>,
-    ) -> Task<anyhow::Result<View<Self>>> {
-        unimplemented!()
-    }
-}
-
-impl ProjectSearchView {
-    fn toggle_filters(&mut self, cx: &mut ViewContext<Self>) {
-        self.filters_enabled = !self.filters_enabled;
-        cx.update_global(|state: &mut ActiveSettings, cx| {
-            state.0.insert(
-                self.model.read(cx).project.downgrade(),
-                self.current_settings(),
-            );
-        });
-    }
-
-    fn current_settings(&self) -> ProjectSearchSettings {
-        ProjectSearchSettings {
-            search_options: self.search_options,
-            filters_enabled: self.filters_enabled,
-            current_mode: self.current_mode,
-        }
-    }
-    fn toggle_search_option(&mut self, option: SearchOptions, cx: &mut ViewContext<Self>) {
-        self.search_options.toggle(option);
-        cx.update_global(|state: &mut ActiveSettings, cx| {
-            state.0.insert(
-                self.model.read(cx).project.downgrade(),
-                self.current_settings(),
-            );
-        });
-    }
-
-    fn index_project(&mut self, cx: &mut ViewContext<Self>) {
-        if let Some(semantic_index) = SemanticIndex::global(cx) {
-            // Semantic search uses no options
-            self.search_options = SearchOptions::none();
-
-            let project = self.model.read(cx).project.clone();
-
-            semantic_index.update(cx, |semantic_index, cx| {
-                semantic_index
-                    .index_project(project.clone(), cx)
-                    .detach_and_log_err(cx);
-            });
-
-            self.semantic_state = Some(SemanticState {
-                index_status: semantic_index.read(cx).status(&project),
-                maintain_rate_limit: None,
-                _subscription: cx.observe(&semantic_index, Self::semantic_index_changed),
-            });
-            self.semantic_index_changed(semantic_index, cx);
-        }
-    }
-
-    fn semantic_index_changed(
-        &mut self,
-        semantic_index: Model<SemanticIndex>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let project = self.model.read(cx).project.clone();
-        if let Some(semantic_state) = self.semantic_state.as_mut() {
-            cx.notify();
-            semantic_state.index_status = semantic_index.read(cx).status(&project);
-            if let SemanticIndexStatus::Indexing {
-                rate_limit_expiry: Some(_),
-                ..
-            } = &semantic_state.index_status
-            {
-                if semantic_state.maintain_rate_limit.is_none() {
-                    semantic_state.maintain_rate_limit =
-                        Some(cx.spawn(|this, mut cx| async move {
-                            loop {
-                                cx.background_executor().timer(Duration::from_secs(1)).await;
-                                this.update(&mut cx, |_, cx| cx.notify()).log_err();
-                            }
-                        }));
-                    return;
-                }
-            } else {
-                semantic_state.maintain_rate_limit = None;
-            }
-        }
-    }
-
-    fn clear_search(&mut self, cx: &mut ViewContext<Self>) {
-        self.model.update(cx, |model, cx| {
-            model.pending_search = None;
-            model.no_results = None;
-            model.match_ranges.clear();
-
-            model.excerpts.update(cx, |excerpts, cx| {
-                excerpts.clear(cx);
-            });
-        });
-    }
-
-    fn activate_search_mode(&mut self, mode: SearchMode, cx: &mut ViewContext<Self>) {
-        let previous_mode = self.current_mode;
-        if previous_mode == mode {
-            return;
-        }
-
-        self.clear_search(cx);
-        self.current_mode = mode;
-        self.active_match_index = None;
-
-        match mode {
-            SearchMode::Semantic => {
-                let has_permission = self.semantic_permissioned(cx);
-                self.active_match_index = None;
-                cx.spawn(|this, mut cx| async move {
-                    let has_permission = has_permission.await?;
-
-                    if !has_permission {
-                        let answer = this.update(&mut cx, |this, cx| {
-                            let project = this.model.read(cx).project.clone();
-                            let project_name = project
-                                .read(cx)
-                                .worktree_root_names(cx)
-                                .collect::<Vec<&str>>()
-                                .join("/");
-                            let is_plural =
-                                project_name.chars().filter(|letter| *letter == '/').count() > 0;
-                            let prompt_text = format!("Would you like to index the '{}' project{} for semantic search? This requires sending code to the OpenAI API", project_name,
-                                if is_plural {
-                                    "s"
-                                } else {""});
-                            cx.prompt(
-                                PromptLevel::Info,
-                                prompt_text.as_str(),
-                                &["Continue", "Cancel"],
-                            )
-                        })?;
-
-                        if answer.await? == 0 {
-                            this.update(&mut cx, |this, _| {
-                                this.semantic_permissioned = Some(true);
-                            })?;
-                        } else {
-                            this.update(&mut cx, |this, cx| {
-                                this.semantic_permissioned = Some(false);
-                                debug_assert_ne!(previous_mode, SearchMode::Semantic, "Tried to re-enable semantic search mode after user modal was rejected");
-                                this.activate_search_mode(previous_mode, cx);
-                            })?;
-                            return anyhow::Ok(());
-                        }
-                    }
-
-                    this.update(&mut cx, |this, cx| {
-                        this.index_project(cx);
-                    })?;
-
-                    anyhow::Ok(())
-                }).detach_and_log_err(cx);
-            }
-            SearchMode::Regex | SearchMode::Text => {
-                self.semantic_state = None;
-                self.active_match_index = None;
-                self.search(cx);
-            }
-        }
-
-        cx.update_global(|state: &mut ActiveSettings, cx| {
-            state.0.insert(
-                self.model.read(cx).project.downgrade(),
-                self.current_settings(),
-            );
-        });
-
-        cx.notify();
-    }
-    fn replace_next(&mut self, _: &ReplaceNext, cx: &mut ViewContext<Self>) {
-        let model = self.model.read(cx);
-        if let Some(query) = model.active_query.as_ref() {
-            if model.match_ranges.is_empty() {
-                return;
-            }
-            if let Some(active_index) = self.active_match_index {
-                let query = query.clone().with_replacement(self.replacement(cx));
-                self.results_editor.replace(
-                    &(Box::new(model.match_ranges[active_index].clone()) as _),
-                    &query,
-                    cx,
-                );
-                self.select_match(Direction::Next, cx)
-            }
-        }
-    }
-    pub fn replacement(&self, cx: &AppContext) -> String {
-        self.replacement_editor.read(cx).text(cx)
-    }
-    fn replace_all(&mut self, _: &ReplaceAll, cx: &mut ViewContext<Self>) {
-        let model = self.model.read(cx);
-        if let Some(query) = model.active_query.as_ref() {
-            if model.match_ranges.is_empty() {
-                return;
-            }
-            if self.active_match_index.is_some() {
-                let query = query.clone().with_replacement(self.replacement(cx));
-                let matches = model
-                    .match_ranges
-                    .iter()
-                    .map(|item| Box::new(item.clone()) as _)
-                    .collect::<Vec<_>>();
-                for item in matches {
-                    self.results_editor.replace(&item, &query, cx);
-                }
-            }
-        }
-    }
-
-    fn new(
-        model: Model<ProjectSearch>,
-        cx: &mut ViewContext<Self>,
-        settings: Option<ProjectSearchSettings>,
-    ) -> Self {
-        let project;
-        let excerpts;
-        let mut replacement_text = None;
-        let mut query_text = String::new();
-        let mut subscriptions = Vec::new();
-
-        // Read in settings if available
-        let (mut options, current_mode, filters_enabled) = if let Some(settings) = settings {
-            (
-                settings.search_options,
-                settings.current_mode,
-                settings.filters_enabled,
-            )
-        } else {
-            (SearchOptions::NONE, Default::default(), false)
-        };
-
-        {
-            let model = model.read(cx);
-            project = model.project.clone();
-            excerpts = model.excerpts.clone();
-            if let Some(active_query) = model.active_query.as_ref() {
-                query_text = active_query.as_str().to_string();
-                replacement_text = active_query.replacement().map(ToOwned::to_owned);
-                options = SearchOptions::from_query(active_query);
-            }
-        }
-        subscriptions.push(cx.observe(&model, |this, _, cx| this.model_changed(cx)));
-
-        let query_editor = cx.new_view(|cx| {
-            let mut editor = Editor::single_line(cx);
-            editor.set_placeholder_text("Text search all files", cx);
-            editor.set_text(query_text, cx);
-            editor
-        });
-        // Subscribe to query_editor in order to reraise editor events for workspace item activation purposes
-        subscriptions.push(
-            cx.subscribe(&query_editor, |_, _, event: &EditorEvent, cx| {
-                cx.emit(ViewEvent::EditorEvent(event.clone()))
-            }),
-        );
-        let replacement_editor = cx.new_view(|cx| {
-            let mut editor = Editor::single_line(cx);
-            editor.set_placeholder_text("Replace in project..", cx);
-            if let Some(text) = replacement_text {
-                editor.set_text(text, cx);
-            }
-            editor
-        });
-        let results_editor = cx.new_view(|cx| {
-            let mut editor = Editor::for_multibuffer(excerpts, Some(project.clone()), cx);
-            editor.set_searchable(false);
-            editor
-        });
-        subscriptions.push(cx.observe(&results_editor, |_, _, cx| cx.emit(ViewEvent::UpdateTab)));
-
-        subscriptions.push(
-            cx.subscribe(&results_editor, |this, _, event: &EditorEvent, cx| {
-                if matches!(event, editor::EditorEvent::SelectionsChanged { .. }) {
-                    this.update_match_index(cx);
-                }
-                // Reraise editor events for workspace item activation purposes
-                cx.emit(ViewEvent::EditorEvent(event.clone()));
-            }),
-        );
-
-        let included_files_editor = cx.new_view(|cx| {
-            let mut editor = Editor::single_line(cx);
-            editor.set_placeholder_text("Include: crates/**/*.toml", cx);
-
-            editor
-        });
-        // Subscribe to include_files_editor in order to reraise editor events for workspace item activation purposes
-        subscriptions.push(
-            cx.subscribe(&included_files_editor, |_, _, event: &EditorEvent, cx| {
-                cx.emit(ViewEvent::EditorEvent(event.clone()))
-            }),
-        );
-
-        let excluded_files_editor = cx.new_view(|cx| {
-            let mut editor = Editor::single_line(cx);
-            editor.set_placeholder_text("Exclude: vendor/*, *.lock", cx);
-
-            editor
-        });
-        // Subscribe to excluded_files_editor in order to reraise editor events for workspace item activation purposes
-        subscriptions.push(
-            cx.subscribe(&excluded_files_editor, |_, _, event: &EditorEvent, cx| {
-                cx.emit(ViewEvent::EditorEvent(event.clone()))
-            }),
-        );
-
-        let focus_handle = cx.focus_handle();
-        subscriptions.push(cx.on_focus_in(&focus_handle, |this, cx| {
-            if this.focus_handle.is_focused(cx) {
-                if this.has_matches() {
-                    this.results_editor.focus_handle(cx).focus(cx);
-                } else {
-                    this.query_editor.focus_handle(cx).focus(cx);
-                }
-            }
-        }));
-
-        // Check if Worktrees have all been previously indexed
-        let mut this = ProjectSearchView {
-            focus_handle,
-            replacement_editor,
-            search_id: model.read(cx).search_id,
-            model,
-            query_editor,
-            results_editor,
-            semantic_state: None,
-            semantic_permissioned: None,
-            search_options: options,
-            panels_with_errors: HashSet::new(),
-            active_match_index: None,
-            query_editor_was_focused: false,
-            included_files_editor,
-            excluded_files_editor,
-            filters_enabled,
-            current_mode,
-            replace_enabled: false,
-            _subscriptions: subscriptions,
-        };
-        this.model_changed(cx);
-        this
-    }
-
-    fn semantic_permissioned(&mut self, cx: &mut ViewContext<Self>) -> Task<Result<bool>> {
-        if let Some(value) = self.semantic_permissioned {
-            return Task::ready(Ok(value));
-        }
-
-        SemanticIndex::global(cx)
-            .map(|semantic| {
-                let project = self.model.read(cx).project.clone();
-                semantic.update(cx, |this, cx| this.project_previously_indexed(&project, cx))
-            })
-            .unwrap_or(Task::ready(Ok(false)))
-    }
-
-    pub fn new_search_in_directory(
-        workspace: &mut Workspace,
-        dir_entry: &Entry,
-        cx: &mut ViewContext<Workspace>,
-    ) {
-        if !dir_entry.is_dir() {
-            return;
-        }
-        let Some(filter_str) = dir_entry.path.to_str() else {
-            return;
-        };
-
-        let model = cx.new_model(|cx| ProjectSearch::new(workspace.project().clone(), cx));
-        let search = cx.new_view(|cx| ProjectSearchView::new(model, cx, None));
-        workspace.add_item(Box::new(search.clone()), cx);
-        search.update(cx, |search, cx| {
-            search
-                .included_files_editor
-                .update(cx, |editor, cx| editor.set_text(filter_str, cx));
-            search.filters_enabled = true;
-            search.focus_query_editor(cx)
-        });
-    }
-
-    // Add another search tab to the workspace.
-    fn deploy(
-        workspace: &mut Workspace,
-        _: &workspace::NewSearch,
-        cx: &mut ViewContext<Workspace>,
-    ) {
-        // Clean up entries for dropped projects
-        cx.update_global(|state: &mut ActiveSearches, _cx| {
-            state.0.retain(|project, _| project.is_upgradable())
-        });
-
-        let query = workspace.active_item(cx).and_then(|item| {
-            let editor = item.act_as::<Editor>(cx)?;
-            let query = editor.query_suggestion(cx);
-            if query.is_empty() {
-                None
-            } else {
-                Some(query)
-            }
-        });
-
-        let settings = cx
-            .global::<ActiveSettings>()
-            .0
-            .get(&workspace.project().downgrade());
-
-        let settings = if let Some(settings) = settings {
-            Some(settings.clone())
-        } else {
-            None
-        };
-
-        let model = cx.new_model(|cx| ProjectSearch::new(workspace.project().clone(), cx));
-        let search = cx.new_view(|cx| ProjectSearchView::new(model, cx, settings));
-
-        workspace.add_item(Box::new(search.clone()), cx);
-
-        search.update(cx, |search, cx| {
-            if let Some(query) = query {
-                search.set_query(&query, cx);
-            }
-            search.focus_query_editor(cx)
-        });
-    }
-
-    fn search(&mut self, cx: &mut ViewContext<Self>) {
-        let mode = self.current_mode;
-        match mode {
-            SearchMode::Semantic => {
-                if self.semantic_state.is_some() {
-                    if let Some(query) = self.build_search_query(cx) {
-                        self.model
-                            .update(cx, |model, cx| model.semantic_search(query.as_inner(), cx));
-                    }
-                }
-            }
-
-            _ => {
-                if let Some(query) = self.build_search_query(cx) {
-                    self.model.update(cx, |model, cx| model.search(query, cx));
-                }
-            }
-        }
-    }
-
-    fn build_search_query(&mut self, cx: &mut ViewContext<Self>) -> Option<SearchQuery> {
-        // Do not bail early in this function, as we want to fill out `self.panels_with_errors`.
-        let text = self.query_editor.read(cx).text(cx);
-        let included_files =
-            match Self::parse_path_matches(&self.included_files_editor.read(cx).text(cx)) {
-                Ok(included_files) => {
-                    let should_unmark_error = self.panels_with_errors.remove(&InputPanel::Include);
-                    if should_unmark_error {
-                        cx.notify();
-                    }
-                    included_files
-                }
-                Err(_e) => {
-                    let should_mark_error = self.panels_with_errors.insert(InputPanel::Include);
-                    if should_mark_error {
-                        cx.notify();
-                    }
-                    vec![]
-                }
-            };
-        let excluded_files =
-            match Self::parse_path_matches(&self.excluded_files_editor.read(cx).text(cx)) {
-                Ok(excluded_files) => {
-                    let should_unmark_error = self.panels_with_errors.remove(&InputPanel::Exclude);
-                    if should_unmark_error {
-                        cx.notify();
-                    }
-
-                    excluded_files
-                }
-                Err(_e) => {
-                    let should_mark_error = self.panels_with_errors.insert(InputPanel::Exclude);
-                    if should_mark_error {
-                        cx.notify();
-                    }
-                    vec![]
-                }
-            };
-
-        let current_mode = self.current_mode;
-        let query = match current_mode {
-            SearchMode::Regex => {
-                match SearchQuery::regex(
-                    text,
-                    self.search_options.contains(SearchOptions::WHOLE_WORD),
-                    self.search_options.contains(SearchOptions::CASE_SENSITIVE),
-                    self.search_options.contains(SearchOptions::INCLUDE_IGNORED),
-                    included_files,
-                    excluded_files,
-                ) {
-                    Ok(query) => {
-                        let should_unmark_error =
-                            self.panels_with_errors.remove(&InputPanel::Query);
-                        if should_unmark_error {
-                            cx.notify();
-                        }
-
-                        Some(query)
-                    }
-                    Err(_e) => {
-                        let should_mark_error = self.panels_with_errors.insert(InputPanel::Query);
-                        if should_mark_error {
-                            cx.notify();
-                        }
-
-                        None
-                    }
-                }
-            }
-            _ => match SearchQuery::text(
-                text,
-                self.search_options.contains(SearchOptions::WHOLE_WORD),
-                self.search_options.contains(SearchOptions::CASE_SENSITIVE),
-                self.search_options.contains(SearchOptions::INCLUDE_IGNORED),
-                included_files,
-                excluded_files,
-            ) {
-                Ok(query) => {
-                    let should_unmark_error = self.panels_with_errors.remove(&InputPanel::Query);
-                    if should_unmark_error {
-                        cx.notify();
-                    }
-
-                    Some(query)
-                }
-                Err(_e) => {
-                    let should_mark_error = self.panels_with_errors.insert(InputPanel::Query);
-                    if should_mark_error {
-                        cx.notify();
-                    }
-
-                    None
-                }
-            },
-        };
-        if !self.panels_with_errors.is_empty() {
-            return None;
-        }
-        query
-    }
-
-    fn parse_path_matches(text: &str) -> anyhow::Result<Vec<PathMatcher>> {
-        text.split(',')
-            .map(str::trim)
-            .filter(|maybe_glob_str| !maybe_glob_str.is_empty())
-            .map(|maybe_glob_str| {
-                PathMatcher::new(maybe_glob_str)
-                    .with_context(|| format!("parsing {maybe_glob_str} as path matcher"))
-            })
-            .collect()
-    }
-
-    fn select_match(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
-        if let Some(index) = self.active_match_index {
-            let match_ranges = self.model.read(cx).match_ranges.clone();
-            let new_index = self.results_editor.update(cx, |editor, cx| {
-                editor.match_index_for_direction(&match_ranges, index, direction, 1, cx)
-            });
-
-            let range_to_select = match_ranges[new_index].clone();
-            self.results_editor.update(cx, |editor, cx| {
-                let range_to_select = editor.range_for_match(&range_to_select);
-                editor.unfold_ranges([range_to_select.clone()], false, true, cx);
-                editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                    s.select_ranges([range_to_select])
-                });
-            });
-        }
-    }
-
-    fn focus_query_editor(&mut self, cx: &mut ViewContext<Self>) {
-        self.query_editor.update(cx, |query_editor, cx| {
-            query_editor.select_all(&SelectAll, cx);
-        });
-        self.query_editor_was_focused = true;
-        let editor_handle = self.query_editor.focus_handle(cx);
-        cx.focus(&editor_handle);
-    }
-
-    fn set_query(&mut self, query: &str, cx: &mut ViewContext<Self>) {
-        self.query_editor
-            .update(cx, |query_editor, cx| query_editor.set_text(query, cx));
-    }
-
-    fn focus_results_editor(&mut self, cx: &mut ViewContext<Self>) {
-        self.query_editor.update(cx, |query_editor, cx| {
-            let cursor = query_editor.selections.newest_anchor().head();
-            query_editor.change_selections(None, cx, |s| s.select_ranges([cursor.clone()..cursor]));
-        });
-        self.query_editor_was_focused = false;
-        let results_handle = self.results_editor.focus_handle(cx);
-        cx.focus(&results_handle);
-    }
-
-    fn model_changed(&mut self, cx: &mut ViewContext<Self>) {
-        let match_ranges = self.model.read(cx).match_ranges.clone();
-        if match_ranges.is_empty() {
-            self.active_match_index = None;
-        } else {
-            self.active_match_index = Some(0);
-            self.update_match_index(cx);
-            let prev_search_id = mem::replace(&mut self.search_id, self.model.read(cx).search_id);
-            let is_new_search = self.search_id != prev_search_id;
-            self.results_editor.update(cx, |editor, cx| {
-                if is_new_search {
-                    let range_to_select = match_ranges
-                        .first()
-                        .clone()
-                        .map(|range| editor.range_for_match(range));
-                    editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                        s.select_ranges(range_to_select)
-                    });
-                }
-                editor.highlight_background::<Self>(
-                    match_ranges,
-                    |theme| theme.search_match_background,
-                    cx,
-                );
-            });
-            if is_new_search && self.query_editor.focus_handle(cx).is_focused(cx) {
-                self.focus_results_editor(cx);
-            }
-        }
-
-        cx.emit(ViewEvent::UpdateTab);
-        cx.notify();
-    }
-
-    fn update_match_index(&mut self, cx: &mut ViewContext<Self>) {
-        let results_editor = self.results_editor.read(cx);
-        let new_index = active_match_index(
-            &self.model.read(cx).match_ranges,
-            &results_editor.selections.newest_anchor().head(),
-            &results_editor.buffer().read(cx).snapshot(cx),
-        );
-        if self.active_match_index != new_index {
-            self.active_match_index = new_index;
-            cx.notify();
-        }
-    }
-
-    pub fn has_matches(&self) -> bool {
-        self.active_match_index.is_some()
-    }
-
-    fn landing_text_minor(&self) -> SharedString {
-        match self.current_mode {
-            SearchMode::Text | SearchMode::Regex => "Include/exclude specific paths with the filter option. Matching exact word and/or casing is available too.".into(),
-            SearchMode::Semantic => "\nSimply explain the code you are looking to find. ex. 'prompt user for permissions to index their project'".into()
-        }
-    }
-    fn border_color_for(&self, panel: InputPanel, cx: &WindowContext) -> Hsla {
-        if self.panels_with_errors.contains(&panel) {
-            Color::Error.color(cx)
-        } else {
-            cx.theme().colors().border
-        }
-    }
-    fn move_focus_to_results(&mut self, cx: &mut ViewContext<Self>) {
-        if !self.results_editor.focus_handle(cx).is_focused(cx)
-            && !self.model.read(cx).match_ranges.is_empty()
-        {
-            cx.stop_propagation();
-            return self.focus_results_editor(cx);
-        }
-    }
-}
-
-impl Default for ProjectSearchBar {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
-impl ProjectSearchBar {
-    pub fn new() -> Self {
-        Self {
-            active_project_search: Default::default(),
-            subscription: Default::default(),
-        }
-    }
-
-    fn cycle_mode(&self, _: &CycleMode, cx: &mut ViewContext<Self>) {
-        if let Some(view) = self.active_project_search.as_ref() {
-            view.update(cx, |this, cx| {
-                let new_mode =
-                    crate::mode::next_mode(&this.current_mode, SemanticIndex::enabled(cx));
-                this.activate_search_mode(new_mode, cx);
-                let editor_handle = this.query_editor.focus_handle(cx);
-                cx.focus(&editor_handle);
-            });
-        }
-    }
-
-    fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
-        if let Some(search_view) = self.active_project_search.as_ref() {
-            search_view.update(cx, |search_view, cx| {
-                if !search_view
-                    .replacement_editor
-                    .focus_handle(cx)
-                    .is_focused(cx)
-                {
-                    cx.stop_propagation();
-                    search_view.search(cx);
-                }
-            });
-        }
-    }
-
-    fn search_in_new(workspace: &mut Workspace, _: &SearchInNew, cx: &mut ViewContext<Workspace>) {
-        if let Some(search_view) = workspace
-            .active_item(cx)
-            .and_then(|item| item.downcast::<ProjectSearchView>())
-        {
-            let new_query = search_view.update(cx, |search_view, cx| {
-                let new_query = search_view.build_search_query(cx);
-                if new_query.is_some() {
-                    if let Some(old_query) = search_view.model.read(cx).active_query.clone() {
-                        search_view.query_editor.update(cx, |editor, cx| {
-                            editor.set_text(old_query.as_str(), cx);
-                        });
-                        search_view.search_options = SearchOptions::from_query(&old_query);
-                    }
-                }
-                new_query
-            });
-            if let Some(new_query) = new_query {
-                let model = cx.new_model(|cx| {
-                    let mut model = ProjectSearch::new(workspace.project().clone(), cx);
-                    model.search(new_query, cx);
-                    model
-                });
-                workspace.add_item(
-                    Box::new(cx.new_view(|cx| ProjectSearchView::new(model, cx, None))),
-                    cx,
-                );
-            }
-        }
-    }
-
-    fn tab(&mut self, _: &editor::Tab, cx: &mut ViewContext<Self>) {
-        self.cycle_field(Direction::Next, cx);
-    }
-
-    fn tab_previous(&mut self, _: &editor::TabPrev, cx: &mut ViewContext<Self>) {
-        self.cycle_field(Direction::Prev, cx);
-    }
-
-    fn cycle_field(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
-        let active_project_search = match &self.active_project_search {
-            Some(active_project_search) => active_project_search,
-
-            None => {
-                return;
-            }
-        };
-
-        active_project_search.update(cx, |project_view, cx| {
-            let mut views = vec![&project_view.query_editor];
-            if project_view.filters_enabled {
-                views.extend([
-                    &project_view.included_files_editor,
-                    &project_view.excluded_files_editor,
-                ]);
-            }
-            if project_view.replace_enabled {
-                views.push(&project_view.replacement_editor);
-            }
-            let current_index = match views
-                .iter()
-                .enumerate()
-                .find(|(_, view)| view.focus_handle(cx).is_focused(cx))
-            {
-                Some((index, _)) => index,
-
-                None => {
-                    return;
-                }
-            };
-
-            let new_index = match direction {
-                Direction::Next => (current_index + 1) % views.len(),
-                Direction::Prev if current_index == 0 => views.len() - 1,
-                Direction::Prev => (current_index - 1) % views.len(),
-            };
-            let next_focus_handle = views[new_index].focus_handle(cx);
-            cx.focus(&next_focus_handle);
-            cx.stop_propagation();
-        });
-    }
-
-    fn toggle_search_option(&mut self, option: SearchOptions, cx: &mut ViewContext<Self>) -> bool {
-        if let Some(search_view) = self.active_project_search.as_ref() {
-            search_view.update(cx, |search_view, cx| {
-                search_view.toggle_search_option(option, cx);
-                search_view.search(cx);
-            });
-
-            cx.notify();
-            true
-        } else {
-            false
-        }
-    }
-
-    fn toggle_replace(&mut self, _: &ToggleReplace, cx: &mut ViewContext<Self>) {
-        if let Some(search) = &self.active_project_search {
-            search.update(cx, |this, cx| {
-                this.replace_enabled = !this.replace_enabled;
-                let editor_to_focus = if !this.replace_enabled {
-                    this.query_editor.focus_handle(cx)
-                } else {
-                    this.replacement_editor.focus_handle(cx)
-                };
-                cx.focus(&editor_to_focus);
-                cx.notify();
-            });
-        }
-    }
-
-    fn toggle_filters(&mut self, cx: &mut ViewContext<Self>) -> bool {
-        if let Some(search_view) = self.active_project_search.as_ref() {
-            search_view.update(cx, |search_view, cx| {
-                search_view.toggle_filters(cx);
-                search_view
-                    .included_files_editor
-                    .update(cx, |_, cx| cx.notify());
-                search_view
-                    .excluded_files_editor
-                    .update(cx, |_, cx| cx.notify());
-                cx.refresh();
-                cx.notify();
-            });
-            cx.notify();
-            true
-        } else {
-            false
-        }
-    }
-
-    fn move_focus_to_results(&self, cx: &mut ViewContext<Self>) {
-        if let Some(search_view) = self.active_project_search.as_ref() {
-            search_view.update(cx, |search_view, cx| {
-                search_view.move_focus_to_results(cx);
-            });
-            cx.notify();
-        }
-    }
-
-    fn activate_search_mode(&self, mode: SearchMode, cx: &mut ViewContext<Self>) {
-        // Update Current Mode
-        if let Some(search_view) = self.active_project_search.as_ref() {
-            search_view.update(cx, |search_view, cx| {
-                search_view.activate_search_mode(mode, cx);
-            });
-            cx.notify();
-        }
-    }
-
-    fn is_option_enabled(&self, option: SearchOptions, cx: &AppContext) -> bool {
-        if let Some(search) = self.active_project_search.as_ref() {
-            search.read(cx).search_options.contains(option)
-        } else {
-            false
-        }
-    }
-
-    fn next_history_query(&mut self, _: &NextHistoryQuery, cx: &mut ViewContext<Self>) {
-        if let Some(search_view) = self.active_project_search.as_ref() {
-            search_view.update(cx, |search_view, cx| {
-                let new_query = search_view.model.update(cx, |model, _| {
-                    if let Some(new_query) = model.search_history.next().map(str::to_string) {
-                        new_query
-                    } else {
-                        model.search_history.reset_selection();
-                        String::new()
-                    }
-                });
-                search_view.set_query(&new_query, cx);
-            });
-        }
-    }
-
-    fn previous_history_query(&mut self, _: &PreviousHistoryQuery, cx: &mut ViewContext<Self>) {
-        if let Some(search_view) = self.active_project_search.as_ref() {
-            search_view.update(cx, |search_view, cx| {
-                if search_view.query_editor.read(cx).text(cx).is_empty() {
-                    if let Some(new_query) = search_view
-                        .model
-                        .read(cx)
-                        .search_history
-                        .current()
-                        .map(str::to_string)
-                    {
-                        search_view.set_query(&new_query, cx);
-                        return;
-                    }
-                }
-
-                if let Some(new_query) = search_view.model.update(cx, |model, _| {
-                    model.search_history.previous().map(str::to_string)
-                }) {
-                    search_view.set_query(&new_query, cx);
-                }
-            });
-        }
-    }
-
-    fn new_placeholder_text(&self, cx: &mut ViewContext<Self>) -> Option<String> {
-        let previous_query_keystrokes = cx
-            .bindings_for_action(&PreviousHistoryQuery {})
-            .into_iter()
-            .next()
-            .map(|binding| {
-                binding
-                    .keystrokes()
-                    .iter()
-                    .map(|k| k.to_string())
-                    .collect::<Vec<_>>()
-            });
-        let next_query_keystrokes = cx
-            .bindings_for_action(&NextHistoryQuery {})
-            .into_iter()
-            .next()
-            .map(|binding| {
-                binding
-                    .keystrokes()
-                    .iter()
-                    .map(|k| k.to_string())
-                    .collect::<Vec<_>>()
-            });
-        let new_placeholder_text = match (previous_query_keystrokes, next_query_keystrokes) {
-            (Some(previous_query_keystrokes), Some(next_query_keystrokes)) => Some(format!(
-                "Search ({}/{} for previous/next query)",
-                previous_query_keystrokes.join(" "),
-                next_query_keystrokes.join(" ")
-            )),
-            (None, Some(next_query_keystrokes)) => Some(format!(
-                "Search ({} for next query)",
-                next_query_keystrokes.join(" ")
-            )),
-            (Some(previous_query_keystrokes), None) => Some(format!(
-                "Search ({} for previous query)",
-                previous_query_keystrokes.join(" ")
-            )),
-            (None, None) => None,
-        };
-        new_placeholder_text
-    }
-
-    fn render_text_input(&self, editor: &View<Editor>, cx: &ViewContext<Self>) -> impl IntoElement {
-        let settings = ThemeSettings::get_global(cx);
-        let text_style = TextStyle {
-            color: if editor.read(cx).read_only() {
-                cx.theme().colors().text_disabled
-            } else {
-                cx.theme().colors().text
-            },
-            font_family: settings.ui_font.family.clone(),
-            font_features: settings.ui_font.features,
-            font_size: rems(0.875).into(),
-            font_weight: FontWeight::NORMAL,
-            font_style: FontStyle::Normal,
-            line_height: relative(1.3).into(),
-            background_color: None,
-            underline: None,
-            white_space: WhiteSpace::Normal,
-        };
-
-        EditorElement::new(
-            &editor,
-            EditorStyle {
-                background: cx.theme().colors().editor_background,
-                local_player: cx.theme().players().local(),
-                text: text_style,
-                ..Default::default()
-            },
-        )
-    }
-}
-
-impl Render for ProjectSearchBar {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        let Some(search) = self.active_project_search.clone() else {
-            return div();
-        };
-        let mut key_context = KeyContext::default();
-        key_context.add("ProjectSearchBar");
-        if let Some(placeholder_text) = self.new_placeholder_text(cx) {
-            search.update(cx, |search, cx| {
-                search.query_editor.update(cx, |this, cx| {
-                    this.set_placeholder_text(placeholder_text, cx)
-                })
-            });
-        }
-        let search = search.read(cx);
-        let semantic_is_available = SemanticIndex::enabled(cx);
-
-        let query_column = v_stack().child(
-            h_stack()
-                .min_w(rems(512. / 16.))
-                .px_2()
-                .py_1()
-                .gap_2()
-                .bg(cx.theme().colors().editor_background)
-                .border_1()
-                .border_color(search.border_color_for(InputPanel::Query, cx))
-                .rounded_lg()
-                .on_action(cx.listener(|this, action, cx| this.confirm(action, cx)))
-                .on_action(cx.listener(|this, action, cx| this.previous_history_query(action, cx)))
-                .on_action(cx.listener(|this, action, cx| this.next_history_query(action, cx)))
-                .child(IconElement::new(Icon::MagnifyingGlass))
-                .child(self.render_text_input(&search.query_editor, cx))
-                .child(
-                    h_stack()
-                        .child(
-                            IconButton::new("project-search-filter-button", Icon::Filter)
-                                .tooltip(|cx| {
-                                    Tooltip::for_action("Toggle filters", &ToggleFilters, cx)
-                                })
-                                .on_click(cx.listener(|this, _, cx| {
-                                    this.toggle_filters(cx);
-                                }))
-                                .selected(
-                                    self.active_project_search
-                                        .as_ref()
-                                        .map(|search| search.read(cx).filters_enabled)
-                                        .unwrap_or_default(),
-                                ),
-                        )
-                        .when(search.current_mode != SearchMode::Semantic, |this| {
-                            this.child(
-                                IconButton::new(
-                                    "project-search-case-sensitive",
-                                    Icon::CaseSensitive,
-                                )
-                                .tooltip(|cx| {
-                                    Tooltip::for_action(
-                                        "Toggle case sensitive",
-                                        &ToggleCaseSensitive,
-                                        cx,
-                                    )
-                                })
-                                .selected(self.is_option_enabled(SearchOptions::CASE_SENSITIVE, cx))
-                                .on_click(cx.listener(
-                                    |this, _, cx| {
-                                        this.toggle_search_option(
-                                            SearchOptions::CASE_SENSITIVE,
-                                            cx,
-                                        );
-                                    },
-                                )),
-                            )
-                            .child(
-                                IconButton::new("project-search-whole-word", Icon::WholeWord)
-                                    .tooltip(|cx| {
-                                        Tooltip::for_action(
-                                            "Toggle whole word",
-                                            &ToggleWholeWord,
-                                            cx,
-                                        )
-                                    })
-                                    .selected(self.is_option_enabled(SearchOptions::WHOLE_WORD, cx))
-                                    .on_click(cx.listener(|this, _, cx| {
-                                        this.toggle_search_option(SearchOptions::WHOLE_WORD, cx);
-                                    })),
-                            )
-                        }),
-                ),
-        );
-
-        let mode_column = v_stack().items_start().justify_start().child(
-            h_stack()
-                .child(
-                    h_stack()
-                        .child(
-                            Button::new("project-search-text-button", "Text")
-                                .selected(search.current_mode == SearchMode::Text)
-                                .on_click(cx.listener(|this, _, cx| {
-                                    this.activate_search_mode(SearchMode::Text, cx)
-                                }))
-                                .tooltip(|cx| {
-                                    Tooltip::for_action("Toggle text search", &ActivateTextMode, cx)
-                                }),
-                        )
-                        .child(
-                            Button::new("project-search-regex-button", "Regex")
-                                .selected(search.current_mode == SearchMode::Regex)
-                                .on_click(cx.listener(|this, _, cx| {
-                                    this.activate_search_mode(SearchMode::Regex, cx)
-                                }))
-                                .tooltip(|cx| {
-                                    Tooltip::for_action(
-                                        "Toggle regular expression search",
-                                        &ActivateRegexMode,
-                                        cx,
-                                    )
-                                }),
-                        )
-                        .when(semantic_is_available, |this| {
-                            this.child(
-                                Button::new("project-search-semantic-button", "Semantic")
-                                    .selected(search.current_mode == SearchMode::Semantic)
-                                    .on_click(cx.listener(|this, _, cx| {
-                                        this.activate_search_mode(SearchMode::Semantic, cx)
-                                    }))
-                                    .tooltip(|cx| {
-                                        Tooltip::for_action(
-                                            "Toggle semantic search",
-                                            &ActivateSemanticMode,
-                                            cx,
-                                        )
-                                    }),
-                            )
-                        }),
-                )
-                .child(
-                    IconButton::new("project-search-toggle-replace", Icon::Replace)
-                        .on_click(cx.listener(|this, _, cx| {
-                            this.toggle_replace(&ToggleReplace, cx);
-                        }))
-                        .tooltip(|cx| Tooltip::for_action("Toggle replace", &ToggleReplace, cx)),
-                ),
-        );
-        let replace_column = if search.replace_enabled {
-            h_stack()
-                .flex_1()
-                .h_full()
-                .gap_2()
-                .px_2()
-                .py_1()
-                .border_1()
-                .border_color(cx.theme().colors().border)
-                .rounded_lg()
-                .child(IconElement::new(Icon::Replace).size(ui::IconSize::Small))
-                .child(self.render_text_input(&search.replacement_editor, cx))
-        } else {
-            // Fill out the space if we don't have a replacement editor.
-            h_stack().flex_1()
-        };
-        let actions_column = h_stack()
-            .when(search.replace_enabled, |this| {
-                this.child(
-                    IconButton::new("project-search-replace-next", Icon::ReplaceNext)
-                        .on_click(cx.listener(|this, _, cx| {
-                            if let Some(search) = this.active_project_search.as_ref() {
-                                search.update(cx, |this, cx| {
-                                    this.replace_next(&ReplaceNext, cx);
-                                })
-                            }
-                        }))
-                        .tooltip(|cx| Tooltip::for_action("Replace next match", &ReplaceNext, cx)),
-                )
-                .child(
-                    IconButton::new("project-search-replace-all", Icon::ReplaceAll)
-                        .on_click(cx.listener(|this, _, cx| {
-                            if let Some(search) = this.active_project_search.as_ref() {
-                                search.update(cx, |this, cx| {
-                                    this.replace_all(&ReplaceAll, cx);
-                                })
-                            }
-                        }))
-                        .tooltip(|cx| Tooltip::for_action("Replace all matches", &ReplaceAll, cx)),
-                )
-            })
-            .when_some(search.active_match_index, |mut this, index| {
-                let index = index + 1;
-                let match_quantity = search.model.read(cx).match_ranges.len();
-                if match_quantity > 0 {
-                    debug_assert!(match_quantity >= index);
-                    this = this.child(Label::new(format!("{index}/{match_quantity}")))
-                }
-                this
-            })
-            .child(
-                IconButton::new("project-search-prev-match", Icon::ChevronLeft)
-                    .disabled(search.active_match_index.is_none())
-                    .on_click(cx.listener(|this, _, cx| {
-                        if let Some(search) = this.active_project_search.as_ref() {
-                            search.update(cx, |this, cx| {
-                                this.select_match(Direction::Prev, cx);
-                            })
-                        }
-                    }))
-                    .tooltip(|cx| {
-                        Tooltip::for_action("Go to previous match", &SelectPrevMatch, cx)
-                    }),
-            )
-            .child(
-                IconButton::new("project-search-next-match", Icon::ChevronRight)
-                    .disabled(search.active_match_index.is_none())
-                    .on_click(cx.listener(|this, _, cx| {
-                        if let Some(search) = this.active_project_search.as_ref() {
-                            search.update(cx, |this, cx| {
-                                this.select_match(Direction::Next, cx);
-                            })
-                        }
-                    }))
-                    .tooltip(|cx| Tooltip::for_action("Go to next match", &SelectNextMatch, cx)),
-            );
-
-        v_stack()
-            .key_context(key_context)
-            .flex_grow()
-            .gap_2()
-            .on_action(cx.listener(|this, _: &ToggleFocus, cx| this.move_focus_to_results(cx)))
-            .on_action(cx.listener(|this, _: &ToggleFilters, cx| {
-                this.toggle_filters(cx);
-            }))
-            .on_action(cx.listener(|this, _: &ActivateTextMode, cx| {
-                this.activate_search_mode(SearchMode::Text, cx)
-            }))
-            .on_action(cx.listener(|this, _: &ActivateRegexMode, cx| {
-                this.activate_search_mode(SearchMode::Regex, cx)
-            }))
-            .on_action(cx.listener(|this, _: &ActivateSemanticMode, cx| {
-                this.activate_search_mode(SearchMode::Semantic, cx)
-            }))
-            .capture_action(cx.listener(|this, action, cx| {
-                this.tab(action, cx);
-                cx.stop_propagation();
-            }))
-            .capture_action(cx.listener(|this, action, cx| {
-                this.tab_previous(action, cx);
-                cx.stop_propagation();
-            }))
-            .on_action(cx.listener(|this, action, cx| this.confirm(action, cx)))
-            .on_action(cx.listener(|this, action, cx| {
-                this.cycle_mode(action, cx);
-            }))
-            .when(search.current_mode != SearchMode::Semantic, |this| {
-                this.on_action(cx.listener(|this, action, cx| {
-                    this.toggle_replace(action, cx);
-                }))
-                .on_action(cx.listener(|this, _: &ToggleWholeWord, cx| {
-                    this.toggle_search_option(SearchOptions::WHOLE_WORD, cx);
-                }))
-                .on_action(cx.listener(|this, _: &ToggleCaseSensitive, cx| {
-                    this.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx);
-                }))
-                .on_action(cx.listener(|this, action, cx| {
-                    if let Some(search) = this.active_project_search.as_ref() {
-                        search.update(cx, |this, cx| {
-                            this.replace_next(action, cx);
-                        })
-                    }
-                }))
-                .on_action(cx.listener(|this, action, cx| {
-                    if let Some(search) = this.active_project_search.as_ref() {
-                        search.update(cx, |this, cx| {
-                            this.replace_all(action, cx);
-                        })
-                    }
-                }))
-                .when(search.filters_enabled, |this| {
-                    this.on_action(cx.listener(|this, _: &ToggleIncludeIgnored, cx| {
-                        this.toggle_search_option(SearchOptions::INCLUDE_IGNORED, cx);
-                    }))
-                })
-            })
-            .child(
-                h_stack()
-                    .justify_between()
-                    .child(query_column)
-                    .child(mode_column)
-                    .child(replace_column)
-                    .child(actions_column),
-            )
-            .when(search.filters_enabled, |this| {
-                this.child(
-                    h_stack()
-                        .flex_1()
-                        .gap_2()
-                        .justify_between()
-                        .child(
-                            h_stack()
-                                .flex_1()
-                                .h_full()
-                                .px_2()
-                                .py_1()
-                                .border_1()
-                                .border_color(search.border_color_for(InputPanel::Include, cx))
-                                .rounded_lg()
-                                .child(self.render_text_input(&search.included_files_editor, cx))
-                                .when(search.current_mode != SearchMode::Semantic, |this| {
-                                    this.child(
-                                        SearchOptions::INCLUDE_IGNORED.as_button(
-                                            search
-                                                .search_options
-                                                .contains(SearchOptions::INCLUDE_IGNORED),
-                                            cx.listener(|this, _, cx| {
-                                                this.toggle_search_option(
-                                                    SearchOptions::INCLUDE_IGNORED,
-                                                    cx,
-                                                );
-                                            }),
-                                        ),
-                                    )
-                                }),
-                        )
-                        .child(
-                            h_stack()
-                                .flex_1()
-                                .h_full()
-                                .px_2()
-                                .py_1()
-                                .border_1()
-                                .border_color(search.border_color_for(InputPanel::Exclude, cx))
-                                .rounded_lg()
-                                .child(self.render_text_input(&search.excluded_files_editor, cx)),
-                        ),
-                )
-            })
-    }
-}
-
-impl EventEmitter<ToolbarItemEvent> for ProjectSearchBar {}
-
-impl ToolbarItemView for ProjectSearchBar {
-    fn set_active_pane_item(
-        &mut self,
-        active_pane_item: Option<&dyn ItemHandle>,
-        cx: &mut ViewContext<Self>,
-    ) -> ToolbarItemLocation {
-        cx.notify();
-        self.subscription = None;
-        self.active_project_search = None;
-        if let Some(search) = active_pane_item.and_then(|i| i.downcast::<ProjectSearchView>()) {
-            search.update(cx, |search, cx| {
-                if search.current_mode == SearchMode::Semantic {
-                    search.index_project(cx);
-                }
-            });
-
-            self.subscription = Some(cx.observe(&search, |_, _, cx| cx.notify()));
-            self.active_project_search = Some(search);
-            ToolbarItemLocation::PrimaryLeft {}
-        } else {
-            ToolbarItemLocation::Hidden
-        }
-    }
-
-    fn row_count(&self, cx: &WindowContext<'_>) -> usize {
-        if let Some(search) = self.active_project_search.as_ref() {
-            if search.read(cx).filters_enabled {
-                return 2;
-            }
-        }
-        1
-    }
-}
-
-#[cfg(test)]
-pub mod tests {
-    use super::*;
-    use editor::DisplayPoint;
-    use gpui::{Action, TestAppContext};
-    use project::FakeFs;
-    use semantic_index::semantic_index_settings::SemanticIndexSettings;
-    use serde_json::json;
-    use settings::{Settings, SettingsStore};
-
-    #[gpui::test]
-    async fn test_project_search(cx: &mut TestAppContext) {
-        init_test(cx);
-
-        let fs = FakeFs::new(cx.background_executor.clone());
-        fs.insert_tree(
-            "/dir",
-            json!({
-                "one.rs": "const ONE: usize = 1;",
-                "two.rs": "const TWO: usize = one::ONE + one::ONE;",
-                "three.rs": "const THREE: usize = one::ONE + two::TWO;",
-                "four.rs": "const FOUR: usize = one::ONE + three::THREE;",
-            }),
-        )
-        .await;
-        let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-        let search = cx.new_model(|cx| ProjectSearch::new(project, cx));
-        let search_view = cx.add_window(|cx| ProjectSearchView::new(search.clone(), cx, None));
-
-        search_view
-            .update(cx, |search_view, cx| {
-                search_view
-                    .query_editor
-                    .update(cx, |query_editor, cx| query_editor.set_text("TWO", cx));
-                search_view.search(cx);
-            })
-            .unwrap();
-        cx.background_executor.run_until_parked();
-        search_view.update(cx, |search_view, cx| {
-            assert_eq!(
-                search_view
-                    .results_editor
-                    .update(cx, |editor, cx| editor.display_text(cx)),
-                "\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\nconst TWO: usize = one::ONE + one::ONE;"
-            );
-            let match_background_color = cx.theme().colors().search_match_background;
-            assert_eq!(
-                search_view
-                    .results_editor
-                    .update(cx, |editor, cx| editor.all_text_background_highlights(cx)),
-                &[
-                    (
-                        DisplayPoint::new(2, 32)..DisplayPoint::new(2, 35),
-                        match_background_color
-                    ),
-                    (
-                        DisplayPoint::new(2, 37)..DisplayPoint::new(2, 40),
-                        match_background_color
-                    ),
-                    (
-                        DisplayPoint::new(5, 6)..DisplayPoint::new(5, 9),
-                        match_background_color
-                    )
-                ]
-            );
-            assert_eq!(search_view.active_match_index, Some(0));
-            assert_eq!(
-                search_view
-                    .results_editor
-                    .update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                [DisplayPoint::new(2, 32)..DisplayPoint::new(2, 35)]
-            );
-
-            search_view.select_match(Direction::Next, cx);
-        }).unwrap();
-
-        search_view
-            .update(cx, |search_view, cx| {
-                assert_eq!(search_view.active_match_index, Some(1));
-                assert_eq!(
-                    search_view
-                        .results_editor
-                        .update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                    [DisplayPoint::new(2, 37)..DisplayPoint::new(2, 40)]
-                );
-                search_view.select_match(Direction::Next, cx);
-            })
-            .unwrap();
-
-        search_view
-            .update(cx, |search_view, cx| {
-                assert_eq!(search_view.active_match_index, Some(2));
-                assert_eq!(
-                    search_view
-                        .results_editor
-                        .update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                    [DisplayPoint::new(5, 6)..DisplayPoint::new(5, 9)]
-                );
-                search_view.select_match(Direction::Next, cx);
-            })
-            .unwrap();
-
-        search_view
-            .update(cx, |search_view, cx| {
-                assert_eq!(search_view.active_match_index, Some(0));
-                assert_eq!(
-                    search_view
-                        .results_editor
-                        .update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                    [DisplayPoint::new(2, 32)..DisplayPoint::new(2, 35)]
-                );
-                search_view.select_match(Direction::Prev, cx);
-            })
-            .unwrap();
-
-        search_view
-            .update(cx, |search_view, cx| {
-                assert_eq!(search_view.active_match_index, Some(2));
-                assert_eq!(
-                    search_view
-                        .results_editor
-                        .update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                    [DisplayPoint::new(5, 6)..DisplayPoint::new(5, 9)]
-                );
-                search_view.select_match(Direction::Prev, cx);
-            })
-            .unwrap();
-
-        search_view
-            .update(cx, |search_view, cx| {
-                assert_eq!(search_view.active_match_index, Some(1));
-                assert_eq!(
-                    search_view
-                        .results_editor
-                        .update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                    [DisplayPoint::new(2, 37)..DisplayPoint::new(2, 40)]
-                );
-            })
-            .unwrap();
-    }
-
-    #[gpui::test]
-    async fn test_project_search_focus(cx: &mut TestAppContext) {
-        init_test(cx);
-
-        let fs = FakeFs::new(cx.background_executor.clone());
-        fs.insert_tree(
-            "/dir",
-            json!({
-                "one.rs": "const ONE: usize = 1;",
-                "two.rs": "const TWO: usize = one::ONE + one::ONE;",
-                "three.rs": "const THREE: usize = one::ONE + two::TWO;",
-                "four.rs": "const FOUR: usize = one::ONE + three::THREE;",
-            }),
-        )
-        .await;
-        let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-        let workspace = window.clone();
-        let search_bar = window.build_view(cx, |_| ProjectSearchBar::new());
-
-        let active_item = cx.read(|cx| {
-            workspace
-                .read(cx)
-                .unwrap()
-                .active_pane()
-                .read(cx)
-                .active_item()
-                .and_then(|item| item.downcast::<ProjectSearchView>())
-        });
-        assert!(
-            active_item.is_none(),
-            "Expected no search panel to be active"
-        );
-
-        window
-            .update(cx, move |workspace, cx| {
-                assert_eq!(workspace.panes().len(), 1);
-                workspace.panes()[0].update(cx, move |pane, cx| {
-                    pane.toolbar()
-                        .update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx))
-                });
-
-                ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx)
-            })
-            .unwrap();
-
-        let Some(search_view) = cx.read(|cx| {
-            workspace
-                .read(cx)
-                .unwrap()
-                .active_pane()
-                .read(cx)
-                .active_item()
-                .and_then(|item| item.downcast::<ProjectSearchView>())
-        }) else {
-            panic!("Search view expected to appear after new search event trigger")
-        };
-
-        cx.spawn(|mut cx| async move {
-            window
-                .update(&mut cx, |_, cx| {
-                    cx.dispatch_action(ToggleFocus.boxed_clone())
-                })
-                .unwrap();
-        })
-        .detach();
-        cx.background_executor.run_until_parked();
-
-        window.update(cx, |_, cx| {
-            search_view.update(cx, |search_view, cx| {
-                    assert!(
-                        search_view.query_editor.focus_handle(cx).is_focused(cx),
-                        "Empty search view should be focused after the toggle focus event: no results panel to focus on",
-                    );
-                });
-        }).unwrap();
-
-        window
-            .update(cx, |_, cx| {
-                search_view.update(cx, |search_view, cx| {
-                    let query_editor = &search_view.query_editor;
-                    assert!(
-                        query_editor.focus_handle(cx).is_focused(cx),
-                        "Search view should be focused after the new search view is activated",
-                    );
-                    let query_text = query_editor.read(cx).text(cx);
-                    assert!(
-                        query_text.is_empty(),
-                        "New search query should be empty but got '{query_text}'",
-                    );
-                    let results_text = search_view
-                        .results_editor
-                        .update(cx, |editor, cx| editor.display_text(cx));
-                    assert!(
-                        results_text.is_empty(),
-                        "Empty search view should have no results but got '{results_text}'"
-                    );
-                });
-            })
-            .unwrap();
-
-        window
-            .update(cx, |_, cx| {
-                search_view.update(cx, |search_view, cx| {
-                    search_view.query_editor.update(cx, |query_editor, cx| {
-                        query_editor.set_text("sOMETHINGtHATsURELYdOESnOTeXIST", cx)
-                    });
-                    search_view.search(cx);
-                });
-            })
-            .unwrap();
-
-        cx.background_executor.run_until_parked();
-        window
-            .update(cx, |_, cx| {
-                search_view.update(cx, |search_view, cx| {
-                    let results_text = search_view
-                        .results_editor
-                        .update(cx, |editor, cx| editor.display_text(cx));
-                    assert!(
-                results_text.is_empty(),
-                "Search view for mismatching query should have no results but got '{results_text}'"
-            );
-                    assert!(
-                search_view.query_editor.focus_handle(cx).is_focused(cx),
-                "Search view should be focused after mismatching query had been used in search",
-            );
-                });
-            })
-            .unwrap();
-        cx.spawn(|mut cx| async move {
-            window.update(&mut cx, |_, cx| {
-                cx.dispatch_action(ToggleFocus.boxed_clone())
-            })
-        })
-        .detach();
-        cx.background_executor.run_until_parked();
-        window.update(cx, |_, cx| {
-            search_view.update(cx, |search_view, cx| {
-                    assert!(
-                        search_view.query_editor.focus_handle(cx).is_focused(cx),
-                        "Search view with mismatching query should be focused after the toggle focus event: still no results panel to focus on",
-                    );
-                });
-        }).unwrap();
-
-        window
-            .update(cx, |_, cx| {
-                search_view.update(cx, |search_view, cx| {
-                    search_view
-                        .query_editor
-                        .update(cx, |query_editor, cx| query_editor.set_text("TWO", cx));
-                    search_view.search(cx);
-                })
-            })
-            .unwrap();
-        cx.background_executor.run_until_parked();
-        window.update(cx, |_, cx|
-        search_view.update(cx, |search_view, cx| {
-                assert_eq!(
-                    search_view
-                        .results_editor
-                        .update(cx, |editor, cx| editor.display_text(cx)),
-                    "\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\nconst TWO: usize = one::ONE + one::ONE;",
-                    "Search view results should match the query"
-                );
-                assert!(
-                    search_view.results_editor.focus_handle(cx).is_focused(cx),
-                    "Search view with mismatching query should be focused after search results are available",
-                );
-            })).unwrap();
-        cx.spawn(|mut cx| async move {
-            window
-                .update(&mut cx, |_, cx| {
-                    cx.dispatch_action(ToggleFocus.boxed_clone())
-                })
-                .unwrap();
-        })
-        .detach();
-        cx.background_executor.run_until_parked();
-        window.update(cx, |_, cx| {
-            search_view.update(cx, |search_view, cx| {
-                    assert!(
-                        search_view.results_editor.focus_handle(cx).is_focused(cx),
-                        "Search view with matching query should still have its results editor focused after the toggle focus event",
-                    );
-                });
-        }).unwrap();
-
-        workspace
-            .update(cx, |workspace, cx| {
-                ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx)
-            })
-            .unwrap();
-        cx.background_executor.run_until_parked();
-        let Some(search_view_2) = cx.read(|cx| {
-            workspace
-                .read(cx)
-                .unwrap()
-                .active_pane()
-                .read(cx)
-                .active_item()
-                .and_then(|item| item.downcast::<ProjectSearchView>())
-        }) else {
-            panic!("Search view expected to appear after new search event trigger")
-        };
-        assert!(
-            search_view_2 != search_view,
-            "New search view should be open after `workspace::NewSearch` event"
-        );
-
-        window.update(cx, |_, cx| {
-            search_view.update(cx, |search_view, cx| {
-                    assert_eq!(search_view.query_editor.read(cx).text(cx), "TWO", "First search view should not have an updated query");
-                    assert_eq!(
-                        search_view
-                            .results_editor
-                            .update(cx, |editor, cx| editor.display_text(cx)),
-                        "\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\nconst TWO: usize = one::ONE + one::ONE;",
-                        "Results of the first search view should not update too"
-                    );
-                    assert!(
-                        !search_view.query_editor.focus_handle(cx).is_focused(cx),
-                        "Focus should be moved away from the first search view"
-                    );
-                });
-        }).unwrap();
-
-        window.update(cx, |_, cx| {
-            search_view_2.update(cx, |search_view_2, cx| {
-                    assert_eq!(
-                        search_view_2.query_editor.read(cx).text(cx),
-                        "two",
-                        "New search view should get the query from the text cursor was at during the event spawn (first search view's first result)"
-                    );
-                    assert_eq!(
-                        search_view_2
-                            .results_editor
-                            .update(cx, |editor, cx| editor.display_text(cx)),
-                        "",
-                        "No search results should be in the 2nd view yet, as we did not spawn a search for it"
-                    );
-                    assert!(
-                        search_view_2.query_editor.focus_handle(cx).is_focused(cx),
-                        "Focus should be moved into query editor fo the new window"
-                    );
-                });
-        }).unwrap();
-
-        window
-            .update(cx, |_, cx| {
-                search_view_2.update(cx, |search_view_2, cx| {
-                    search_view_2
-                        .query_editor
-                        .update(cx, |query_editor, cx| query_editor.set_text("FOUR", cx));
-                    search_view_2.search(cx);
-                });
-            })
-            .unwrap();
-
-        cx.background_executor.run_until_parked();
-        window.update(cx, |_, cx| {
-            search_view_2.update(cx, |search_view_2, cx| {
-                    assert_eq!(
-                        search_view_2
-                            .results_editor
-                            .update(cx, |editor, cx| editor.display_text(cx)),
-                        "\n\nconst FOUR: usize = one::ONE + three::THREE;",
-                        "New search view with the updated query should have new search results"
-                    );
-                    assert!(
-                        search_view_2.results_editor.focus_handle(cx).is_focused(cx),
-                        "Search view with mismatching query should be focused after search results are available",
-                    );
-                });
-        }).unwrap();
-
-        cx.spawn(|mut cx| async move {
-            window
-                .update(&mut cx, |_, cx| {
-                    cx.dispatch_action(ToggleFocus.boxed_clone())
-                })
-                .unwrap();
-        })
-        .detach();
-        cx.background_executor.run_until_parked();
-        window.update(cx, |_, cx| {
-            search_view_2.update(cx, |search_view_2, cx| {
-                    assert!(
-                        search_view_2.results_editor.focus_handle(cx).is_focused(cx),
-                        "Search view with matching query should switch focus to the results editor after the toggle focus event",
-                    );
-                });}).unwrap();
-    }
-
-    #[gpui::test]
-    async fn test_new_project_search_in_directory(cx: &mut TestAppContext) {
-        init_test(cx);
-
-        let fs = FakeFs::new(cx.background_executor.clone());
-        fs.insert_tree(
-            "/dir",
-            json!({
-                "a": {
-                    "one.rs": "const ONE: usize = 1;",
-                    "two.rs": "const TWO: usize = one::ONE + one::ONE;",
-                },
-                "b": {
-                    "three.rs": "const THREE: usize = one::ONE + two::TWO;",
-                    "four.rs": "const FOUR: usize = one::ONE + three::THREE;",
-                },
-            }),
-        )
-        .await;
-        let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-        let worktree_id = project.read_with(cx, |project, cx| {
-            project.worktrees().next().unwrap().read(cx).id()
-        });
-        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-        let workspace = window.root(cx).unwrap();
-        let search_bar = window.build_view(cx, |_| ProjectSearchBar::new());
-
-        let active_item = cx.read(|cx| {
-            workspace
-                .read(cx)
-                .active_pane()
-                .read(cx)
-                .active_item()
-                .and_then(|item| item.downcast::<ProjectSearchView>())
-        });
-        assert!(
-            active_item.is_none(),
-            "Expected no search panel to be active"
-        );
-
-        window
-            .update(cx, move |workspace, cx| {
-                assert_eq!(workspace.panes().len(), 1);
-                workspace.panes()[0].update(cx, move |pane, cx| {
-                    pane.toolbar()
-                        .update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx))
-                });
-            })
-            .unwrap();
-
-        let one_file_entry = cx.update(|cx| {
-            workspace
-                .read(cx)
-                .project()
-                .read(cx)
-                .entry_for_path(&(worktree_id, "a/one.rs").into(), cx)
-                .expect("no entry for /a/one.rs file")
-        });
-        assert!(one_file_entry.is_file());
-        window
-            .update(cx, |workspace, cx| {
-                ProjectSearchView::new_search_in_directory(workspace, &one_file_entry, cx)
-            })
-            .unwrap();
-        let active_search_entry = cx.read(|cx| {
-            workspace
-                .read(cx)
-                .active_pane()
-                .read(cx)
-                .active_item()
-                .and_then(|item| item.downcast::<ProjectSearchView>())
-        });
-        assert!(
-            active_search_entry.is_none(),
-            "Expected no search panel to be active for file entry"
-        );
-
-        let a_dir_entry = cx.update(|cx| {
-            workspace
-                .read(cx)
-                .project()
-                .read(cx)
-                .entry_for_path(&(worktree_id, "a").into(), cx)
-                .expect("no entry for /a/ directory")
-        });
-        assert!(a_dir_entry.is_dir());
-        window
-            .update(cx, |workspace, cx| {
-                ProjectSearchView::new_search_in_directory(workspace, &a_dir_entry, cx)
-            })
-            .unwrap();
-
-        let Some(search_view) = cx.read(|cx| {
-            workspace
-                .read(cx)
-                .active_pane()
-                .read(cx)
-                .active_item()
-                .and_then(|item| item.downcast::<ProjectSearchView>())
-        }) else {
-            panic!("Search view expected to appear after new search in directory event trigger")
-        };
-        cx.background_executor.run_until_parked();
-        window
-            .update(cx, |_, cx| {
-                search_view.update(cx, |search_view, cx| {
-                    assert!(
-                        search_view.query_editor.focus_handle(cx).is_focused(cx),
-                        "On new search in directory, focus should be moved into query editor"
-                    );
-                    search_view.excluded_files_editor.update(cx, |editor, cx| {
-                        assert!(
-                            editor.display_text(cx).is_empty(),
-                            "New search in directory should not have any excluded files"
-                        );
-                    });
-                    search_view.included_files_editor.update(cx, |editor, cx| {
-                        assert_eq!(
-                            editor.display_text(cx),
-                            a_dir_entry.path.to_str().unwrap(),
-                            "New search in directory should have included dir entry path"
-                        );
-                    });
-                });
-            })
-            .unwrap();
-        window
-            .update(cx, |_, cx| {
-                search_view.update(cx, |search_view, cx| {
-                    search_view
-                        .query_editor
-                        .update(cx, |query_editor, cx| query_editor.set_text("const", cx));
-                    search_view.search(cx);
-                });
-            })
-            .unwrap();
-        cx.background_executor.run_until_parked();
-        window
-            .update(cx, |_, cx| {
-                search_view.update(cx, |search_view, cx| {
-                    assert_eq!(
-                search_view
-                    .results_editor
-                    .update(cx, |editor, cx| editor.display_text(cx)),
-                "\n\nconst ONE: usize = 1;\n\n\nconst TWO: usize = one::ONE + one::ONE;",
-                "New search in directory should have a filter that matches a certain directory"
-            );
-                })
-            })
-            .unwrap();
-    }
-
-    #[gpui::test]
-    async fn test_search_query_history(cx: &mut TestAppContext) {
-        init_test(cx);
-
-        let fs = FakeFs::new(cx.background_executor.clone());
-        fs.insert_tree(
-            "/dir",
-            json!({
-                "one.rs": "const ONE: usize = 1;",
-                "two.rs": "const TWO: usize = one::ONE + one::ONE;",
-                "three.rs": "const THREE: usize = one::ONE + two::TWO;",
-                "four.rs": "const FOUR: usize = one::ONE + three::THREE;",
-            }),
-        )
-        .await;
-        let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-        let workspace = window.root(cx).unwrap();
-        let search_bar = window.build_view(cx, |_| ProjectSearchBar::new());
-
-        window
-            .update(cx, {
-                let search_bar = search_bar.clone();
-                move |workspace, cx| {
-                    assert_eq!(workspace.panes().len(), 1);
-                    workspace.panes()[0].update(cx, move |pane, cx| {
-                        pane.toolbar()
-                            .update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx))
-                    });
-
-                    ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx)
-                }
-            })
-            .unwrap();
-
-        let search_view = cx.read(|cx| {
-            workspace
-                .read(cx)
-                .active_pane()
-                .read(cx)
-                .active_item()
-                .and_then(|item| item.downcast::<ProjectSearchView>())
-                .expect("Search view expected to appear after new search event trigger")
-        });
-
-        // Add 3 search items into the history + another unsubmitted one.
-        window
-            .update(cx, |_, cx| {
-                search_view.update(cx, |search_view, cx| {
-                    search_view.search_options = SearchOptions::CASE_SENSITIVE;
-                    search_view
-                        .query_editor
-                        .update(cx, |query_editor, cx| query_editor.set_text("ONE", cx));
-                    search_view.search(cx);
-                });
-            })
-            .unwrap();
-
-        cx.background_executor.run_until_parked();
-        window
-            .update(cx, |_, cx| {
-                search_view.update(cx, |search_view, cx| {
-                    search_view
-                        .query_editor
-                        .update(cx, |query_editor, cx| query_editor.set_text("TWO", cx));
-                    search_view.search(cx);
-                });
-            })
-            .unwrap();
-        cx.background_executor.run_until_parked();
-        window
-            .update(cx, |_, cx| {
-                search_view.update(cx, |search_view, cx| {
-                    search_view
-                        .query_editor
-                        .update(cx, |query_editor, cx| query_editor.set_text("THREE", cx));
-                    search_view.search(cx);
-                })
-            })
-            .unwrap();
-        cx.background_executor.run_until_parked();
-        window
-            .update(cx, |_, cx| {
-                search_view.update(cx, |search_view, cx| {
-                    search_view.query_editor.update(cx, |query_editor, cx| {
-                        query_editor.set_text("JUST_TEXT_INPUT", cx)
-                    });
-                })
-            })
-            .unwrap();
-        cx.background_executor.run_until_parked();
-
-        // Ensure that the latest input with search settings is active.
-        window
-            .update(cx, |_, cx| {
-                search_view.update(cx, |search_view, cx| {
-                    assert_eq!(
-                        search_view.query_editor.read(cx).text(cx),
-                        "JUST_TEXT_INPUT"
-                    );
-                    assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
-                });
-            })
-            .unwrap();
-
-        // Next history query after the latest should set the query to the empty string.
-        window
-            .update(cx, |_, cx| {
-                search_bar.update(cx, |search_bar, cx| {
-                    search_bar.next_history_query(&NextHistoryQuery, cx);
-                })
-            })
-            .unwrap();
-        window
-            .update(cx, |_, cx| {
-                search_view.update(cx, |search_view, cx| {
-                    assert_eq!(search_view.query_editor.read(cx).text(cx), "");
-                    assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
-                });
-            })
-            .unwrap();
-        window
-            .update(cx, |_, cx| {
-                search_bar.update(cx, |search_bar, cx| {
-                    search_bar.next_history_query(&NextHistoryQuery, cx);
-                })
-            })
-            .unwrap();
-        window
-            .update(cx, |_, cx| {
-                search_view.update(cx, |search_view, cx| {
-                    assert_eq!(search_view.query_editor.read(cx).text(cx), "");
-                    assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
-                });
-            })
-            .unwrap();
-
-        // First previous query for empty current query should set the query to the latest submitted one.
-        window
-            .update(cx, |_, cx| {
-                search_bar.update(cx, |search_bar, cx| {
-                    search_bar.previous_history_query(&PreviousHistoryQuery, cx);
-                });
-            })
-            .unwrap();
-        window
-            .update(cx, |_, cx| {
-                search_view.update(cx, |search_view, cx| {
-                    assert_eq!(search_view.query_editor.read(cx).text(cx), "THREE");
-                    assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
-                });
-            })
-            .unwrap();
-
-        // Further previous items should go over the history in reverse order.
-        window
-            .update(cx, |_, cx| {
-                search_bar.update(cx, |search_bar, cx| {
-                    search_bar.previous_history_query(&PreviousHistoryQuery, cx);
-                });
-            })
-            .unwrap();
-        window
-            .update(cx, |_, cx| {
-                search_view.update(cx, |search_view, cx| {
-                    assert_eq!(search_view.query_editor.read(cx).text(cx), "TWO");
-                    assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
-                });
-            })
-            .unwrap();
-
-        // Previous items should never go behind the first history item.
-        window
-            .update(cx, |_, cx| {
-                search_bar.update(cx, |search_bar, cx| {
-                    search_bar.previous_history_query(&PreviousHistoryQuery, cx);
-                });
-            })
-            .unwrap();
-        window
-            .update(cx, |_, cx| {
-                search_view.update(cx, |search_view, cx| {
-                    assert_eq!(search_view.query_editor.read(cx).text(cx), "ONE");
-                    assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
-                });
-            })
-            .unwrap();
-        window
-            .update(cx, |_, cx| {
-                search_bar.update(cx, |search_bar, cx| {
-                    search_bar.previous_history_query(&PreviousHistoryQuery, cx);
-                });
-            })
-            .unwrap();
-        window
-            .update(cx, |_, cx| {
-                search_view.update(cx, |search_view, cx| {
-                    assert_eq!(search_view.query_editor.read(cx).text(cx), "ONE");
-                    assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
-                });
-            })
-            .unwrap();
-
-        // Next items should go over the history in the original order.
-        window
-            .update(cx, |_, cx| {
-                search_bar.update(cx, |search_bar, cx| {
-                    search_bar.next_history_query(&NextHistoryQuery, cx);
-                });
-            })
-            .unwrap();
-        window
-            .update(cx, |_, cx| {
-                search_view.update(cx, |search_view, cx| {
-                    assert_eq!(search_view.query_editor.read(cx).text(cx), "TWO");
-                    assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
-                });
-            })
-            .unwrap();
-
-        window
-            .update(cx, |_, cx| {
-                search_view.update(cx, |search_view, cx| {
-                    search_view
-                        .query_editor
-                        .update(cx, |query_editor, cx| query_editor.set_text("TWO_NEW", cx));
-                    search_view.search(cx);
-                });
-            })
-            .unwrap();
-        cx.background_executor.run_until_parked();
-        window
-            .update(cx, |_, cx| {
-                search_view.update(cx, |search_view, cx| {
-                    assert_eq!(search_view.query_editor.read(cx).text(cx), "TWO_NEW");
-                    assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
-                });
-            })
-            .unwrap();
-
-        // New search input should add another entry to history and move the selection to the end of the history.
-        window
-            .update(cx, |_, cx| {
-                search_bar.update(cx, |search_bar, cx| {
-                    search_bar.previous_history_query(&PreviousHistoryQuery, cx);
-                });
-            })
-            .unwrap();
-        window
-            .update(cx, |_, cx| {
-                search_view.update(cx, |search_view, cx| {
-                    assert_eq!(search_view.query_editor.read(cx).text(cx), "THREE");
-                    assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
-                });
-            })
-            .unwrap();
-        window
-            .update(cx, |_, cx| {
-                search_bar.update(cx, |search_bar, cx| {
-                    search_bar.previous_history_query(&PreviousHistoryQuery, cx);
-                });
-            })
-            .unwrap();
-        window
-            .update(cx, |_, cx| {
-                search_view.update(cx, |search_view, cx| {
-                    assert_eq!(search_view.query_editor.read(cx).text(cx), "TWO");
-                    assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
-                });
-            })
-            .unwrap();
-        window
-            .update(cx, |_, cx| {
-                search_bar.update(cx, |search_bar, cx| {
-                    search_bar.next_history_query(&NextHistoryQuery, cx);
-                });
-            })
-            .unwrap();
-        window
-            .update(cx, |_, cx| {
-                search_view.update(cx, |search_view, cx| {
-                    assert_eq!(search_view.query_editor.read(cx).text(cx), "THREE");
-                    assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
-                });
-            })
-            .unwrap();
-        window
-            .update(cx, |_, cx| {
-                search_bar.update(cx, |search_bar, cx| {
-                    search_bar.next_history_query(&NextHistoryQuery, cx);
-                });
-            })
-            .unwrap();
-        window
-            .update(cx, |_, cx| {
-                search_view.update(cx, |search_view, cx| {
-                    assert_eq!(search_view.query_editor.read(cx).text(cx), "TWO_NEW");
-                    assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
-                });
-            })
-            .unwrap();
-        window
-            .update(cx, |_, cx| {
-                search_bar.update(cx, |search_bar, cx| {
-                    search_bar.next_history_query(&NextHistoryQuery, cx);
-                });
-            })
-            .unwrap();
-        window
-            .update(cx, |_, cx| {
-                search_view.update(cx, |search_view, cx| {
-                    assert_eq!(search_view.query_editor.read(cx).text(cx), "");
-                    assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
-                });
-            })
-            .unwrap();
-    }
-
-    pub fn init_test(cx: &mut TestAppContext) {
-        cx.update(|cx| {
-            let settings = SettingsStore::test(cx);
-            cx.set_global(settings);
-            cx.set_global(ActiveSearches::default());
-            SemanticIndexSettings::register(cx);
-
-            theme::init(theme::LoadThemes::JustBase, cx);
-
-            language::init(cx);
-            client::init_settings(cx);
-            editor::init(cx);
-            workspace::init_settings(cx);
-            Project::init_settings(cx);
-            super::init(cx);
-        });
-    }
-}

crates/search2/src/search.rs 🔗

@@ -1,108 +0,0 @@
-use bitflags::bitflags;
-pub use buffer_search::BufferSearchBar;
-use gpui::{actions, Action, AppContext, IntoElement};
-pub use mode::SearchMode;
-use project::search::SearchQuery;
-pub use project_search::ProjectSearchView;
-use ui::{prelude::*, Tooltip};
-use ui::{ButtonStyle, IconButton};
-
-pub mod buffer_search;
-mod history;
-mod mode;
-pub mod project_search;
-pub(crate) mod search_bar;
-
-pub fn init(cx: &mut AppContext) {
-    menu::init();
-    buffer_search::init(cx);
-    project_search::init(cx);
-}
-
-actions!(
-    search,
-    [
-        CycleMode,
-        ToggleWholeWord,
-        ToggleCaseSensitive,
-        ToggleIncludeIgnored,
-        ToggleReplace,
-        SelectNextMatch,
-        SelectPrevMatch,
-        SelectAllMatches,
-        NextHistoryQuery,
-        PreviousHistoryQuery,
-        ActivateTextMode,
-        ActivateSemanticMode,
-        ActivateRegexMode,
-        ReplaceAll,
-        ReplaceNext,
-    ]
-);
-
-bitflags! {
-    #[derive(Default)]
-    pub struct SearchOptions: u8 {
-        const NONE = 0b000;
-        const WHOLE_WORD = 0b001;
-        const CASE_SENSITIVE = 0b010;
-        const INCLUDE_IGNORED = 0b100;
-    }
-}
-
-impl SearchOptions {
-    pub fn label(&self) -> &'static str {
-        match *self {
-            SearchOptions::WHOLE_WORD => "Match Whole Word",
-            SearchOptions::CASE_SENSITIVE => "Match Case",
-            SearchOptions::INCLUDE_IGNORED => "Include ignored",
-            _ => panic!("{:?} is not a named SearchOption", self),
-        }
-    }
-
-    pub fn icon(&self) -> ui::Icon {
-        match *self {
-            SearchOptions::WHOLE_WORD => ui::Icon::WholeWord,
-            SearchOptions::CASE_SENSITIVE => ui::Icon::CaseSensitive,
-            SearchOptions::INCLUDE_IGNORED => ui::Icon::FileGit,
-            _ => panic!("{:?} is not a named SearchOption", self),
-        }
-    }
-
-    pub fn to_toggle_action(&self) -> Box<dyn Action + Sync + Send + 'static> {
-        match *self {
-            SearchOptions::WHOLE_WORD => Box::new(ToggleWholeWord),
-            SearchOptions::CASE_SENSITIVE => Box::new(ToggleCaseSensitive),
-            SearchOptions::INCLUDE_IGNORED => Box::new(ToggleIncludeIgnored),
-            _ => panic!("{:?} is not a named SearchOption", self),
-        }
-    }
-
-    pub fn none() -> SearchOptions {
-        SearchOptions::NONE
-    }
-
-    pub fn from_query(query: &SearchQuery) -> SearchOptions {
-        let mut options = SearchOptions::NONE;
-        options.set(SearchOptions::WHOLE_WORD, query.whole_word());
-        options.set(SearchOptions::CASE_SENSITIVE, query.case_sensitive());
-        options.set(SearchOptions::INCLUDE_IGNORED, query.include_ignored());
-        options
-    }
-
-    pub fn as_button(
-        &self,
-        active: bool,
-        action: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static,
-    ) -> impl IntoElement {
-        IconButton::new(self.label(), self.icon())
-            .on_click(action)
-            .style(ButtonStyle::Subtle)
-            .when(active, |button| button.style(ButtonStyle::Filled))
-            .tooltip({
-                let action = self.to_toggle_action();
-                let label: SharedString = format!("Toggle {}", self.label()).into();
-                move |cx| Tooltip::for_action(label.clone(), &*action, cx)
-            })
-    }
-}

crates/search2/src/search_bar.rs 🔗

@@ -1,18 +0,0 @@
-use gpui::{Action, IntoElement};
-use ui::IconButton;
-use ui::{prelude::*, Tooltip};
-
-pub(super) fn render_nav_button(
-    icon: ui::Icon,
-    active: bool,
-    tooltip: &'static str,
-    action: &'static dyn Action,
-) -> impl IntoElement {
-    IconButton::new(
-        SharedString::from(format!("search-nav-button-{}", action.name())),
-        icon,
-    )
-    .on_click(|_, cx| cx.dispatch_action(action.boxed_clone()))
-    .tooltip(move |cx| Tooltip::for_action(tooltip, action, cx))
-    .disabled(!active)
-}

crates/semantic_index/Cargo.toml 🔗

@@ -16,9 +16,6 @@ language = { path = "../language" }
 project = { path = "../project" }
 workspace = { path = "../workspace" }
 util = { path = "../util" }
-picker = { path = "../picker" }
-theme = { path = "../theme" }
-editor = { path = "../editor" }
 rpc = { path = "../rpc" }
 settings = { path = "../settings" }
 anyhow.workspace = true

crates/semantic_index/src/db.rs 🔗

@@ -6,7 +6,7 @@ use ai::embedding::Embedding;
 use anyhow::{anyhow, Context, Result};
 use collections::HashMap;
 use futures::channel::oneshot;
-use gpui::executor;
+use gpui::BackgroundExecutor;
 use ndarray::{Array1, Array2};
 use ordered_float::OrderedFloat;
 use project::Fs;
@@ -48,7 +48,7 @@ impl VectorDatabase {
     pub async fn new(
         fs: Arc<dyn Fs>,
         path: Arc<Path>,
-        executor: Arc<executor::Background>,
+        executor: BackgroundExecutor,
     ) -> Result<Self> {
         if let Some(db_directory) = path.parent() {
             fs.create_dir(db_directory).await?;

crates/semantic_index/src/embedding_queue.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{parsing::Span, JobHandle};
 use ai::embedding::EmbeddingProvider;
-use gpui::executor::Background;
+use gpui::BackgroundExecutor;
 use parking_lot::Mutex;
 use smol::channel;
 use std::{mem, ops::Range, path::Path, sync::Arc, time::SystemTime};
@@ -37,7 +37,7 @@ impl PartialEq for FileToEmbed {
 pub struct EmbeddingQueue {
     embedding_provider: Arc<dyn EmbeddingProvider>,
     pending_batch: Vec<FileFragmentToEmbed>,
-    executor: Arc<Background>,
+    executor: BackgroundExecutor,
     pending_batch_token_count: usize,
     finished_files_tx: channel::Sender<FileToEmbed>,
     finished_files_rx: channel::Receiver<FileToEmbed>,
@@ -50,7 +50,10 @@ pub struct FileFragmentToEmbed {
 }
 
 impl EmbeddingQueue {
-    pub fn new(embedding_provider: Arc<dyn EmbeddingProvider>, executor: Arc<Background>) -> Self {
+    pub fn new(
+        embedding_provider: Arc<dyn EmbeddingProvider>,
+        executor: BackgroundExecutor,
+    ) -> Self {
         let (finished_files_tx, finished_files_rx) = channel::unbounded();
         Self {
             embedding_provider,

crates/semantic_index/src/semantic_index.rs 🔗

@@ -9,12 +9,15 @@ mod semantic_index_tests;
 use crate::semantic_index_settings::SemanticIndexSettings;
 use ai::embedding::{Embedding, EmbeddingProvider};
 use ai::providers::open_ai::OpenAIEmbeddingProvider;
-use anyhow::{anyhow, Result};
+use anyhow::{anyhow, Context as _, Result};
 use collections::{BTreeMap, HashMap, HashSet};
 use db::VectorDatabase;
 use embedding_queue::{EmbeddingQueue, FileToEmbed};
 use futures::{future, FutureExt, StreamExt};
-use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle};
+use gpui::{
+    AppContext, AsyncAppContext, BorrowWindow, Context, Model, ModelContext, Task, ViewContext,
+    WeakModel,
+};
 use language::{Anchor, Bias, Buffer, Language, LanguageRegistry};
 use lazy_static::lazy_static;
 use ordered_float::OrderedFloat;
@@ -22,6 +25,7 @@ use parking_lot::Mutex;
 use parsing::{CodeContextRetriever, Span, SpanDigest, PARSEABLE_ENTIRE_FILE_TYPES};
 use postage::watch;
 use project::{Fs, PathChange, Project, ProjectEntryId, Worktree, WorktreeId};
+use settings::Settings;
 use smol::channel;
 use std::{
     cmp::Reverse,
@@ -35,7 +39,7 @@ use std::{
 };
 use util::paths::PathMatcher;
 use util::{channel::RELEASE_CHANNEL_NAME, http::HttpClient, paths::EMBEDDINGS_DIR, ResultExt};
-use workspace::WorkspaceCreated;
+use workspace::Workspace;
 
 const SEMANTIC_INDEX_VERSION: usize = 11;
 const BACKGROUND_INDEXING_DELAY: Duration = Duration::from_secs(5 * 60);
@@ -51,54 +55,54 @@ pub fn init(
     language_registry: Arc<LanguageRegistry>,
     cx: &mut AppContext,
 ) {
-    settings::register::<SemanticIndexSettings>(cx);
+    SemanticIndexSettings::register(cx);
 
     let db_file_path = EMBEDDINGS_DIR
         .join(Path::new(RELEASE_CHANNEL_NAME.as_str()))
         .join("embeddings_db");
 
-    cx.subscribe_global::<WorkspaceCreated, _>({
-        move |event, cx| {
+    cx.observe_new_views(
+        |workspace: &mut Workspace, cx: &mut ViewContext<Workspace>| {
             let Some(semantic_index) = SemanticIndex::global(cx) else {
                 return;
             };
-            let workspace = &event.0;
-            if let Some(workspace) = workspace.upgrade(cx) {
-                let project = workspace.read(cx).project().clone();
-                if project.read(cx).is_local() {
-                    cx.spawn(|mut cx| async move {
+            let project = workspace.project().clone();
+
+            if project.read(cx).is_local() {
+                cx.app_mut()
+                    .spawn(|mut cx| async move {
                         let previously_indexed = semantic_index
                             .update(&mut cx, |index, cx| {
                                 index.project_previously_indexed(&project, cx)
-                            })
+                            })?
                             .await?;
                         if previously_indexed {
                             semantic_index
-                                .update(&mut cx, |index, cx| index.index_project(project, cx))
+                                .update(&mut cx, |index, cx| index.index_project(project, cx))?
                                 .await?;
                         }
                         anyhow::Ok(())
                     })
                     .detach_and_log_err(cx);
-                }
             }
-        }
-    })
+        },
+    )
     .detach();
 
-    cx.spawn(move |mut cx| async move {
+    cx.spawn(move |cx| async move {
         let semantic_index = SemanticIndex::new(
             fs,
             db_file_path,
-            Arc::new(OpenAIEmbeddingProvider::new(http_client, cx.background())),
+            Arc::new(OpenAIEmbeddingProvider::new(
+                http_client,
+                cx.background_executor().clone(),
+            )),
             language_registry,
             cx.clone(),
         )
         .await?;
 
-        cx.update(|cx| {
-            cx.set_global(semantic_index.clone());
-        });
+        cx.update(|cx| cx.set_global(semantic_index.clone()))?;
 
         anyhow::Ok(())
     })
@@ -124,7 +128,7 @@ pub struct SemanticIndex {
     parsing_files_tx: channel::Sender<(Arc<HashMap<SpanDigest, Embedding>>, PendingFile)>,
     _embedding_task: Task<()>,
     _parsing_files_tasks: Vec<Task<()>>,
-    projects: HashMap<WeakModelHandle<Project>, ProjectState>,
+    projects: HashMap<WeakModel<Project>, ProjectState>,
 }
 
 struct ProjectState {
@@ -229,12 +233,12 @@ impl ProjectState {
             pending_file_count_tx,
             pending_index: 0,
             _subscription: subscription,
-            _observe_pending_file_count: cx.spawn_weak({
+            _observe_pending_file_count: cx.spawn({
                 let mut pending_file_count_rx = pending_file_count_rx.clone();
                 |this, mut cx| async move {
                     while let Some(_) = pending_file_count_rx.next().await {
-                        if let Some(this) = this.upgrade(&cx) {
-                            this.update(&mut cx, |_, cx| cx.notify());
+                        if this.update(&mut cx, |_, cx| cx.notify()).is_err() {
+                            break;
                         }
                     }
                 }
@@ -264,21 +268,21 @@ pub struct PendingFile {
 
 #[derive(Clone)]
 pub struct SearchResult {
-    pub buffer: ModelHandle<Buffer>,
+    pub buffer: Model<Buffer>,
     pub range: Range<Anchor>,
     pub similarity: OrderedFloat<f32>,
 }
 
 impl SemanticIndex {
-    pub fn global(cx: &mut AppContext) -> Option<ModelHandle<SemanticIndex>> {
-        if cx.has_global::<ModelHandle<Self>>() {
-            Some(cx.global::<ModelHandle<SemanticIndex>>().clone())
+    pub fn global(cx: &mut AppContext) -> Option<Model<SemanticIndex>> {
+        if cx.has_global::<Model<Self>>() {
+            Some(cx.global::<Model<SemanticIndex>>().clone())
         } else {
             None
         }
     }
 
-    pub fn authenticate(&mut self, cx: &AppContext) -> bool {
+    pub fn authenticate(&mut self, cx: &mut AppContext) -> bool {
         if !self.embedding_provider.has_credentials() {
             self.embedding_provider.retrieve_credentials(cx);
         } else {
@@ -293,10 +297,10 @@ impl SemanticIndex {
     }
 
     pub fn enabled(cx: &AppContext) -> bool {
-        settings::get::<SemanticIndexSettings>(cx).enabled
+        SemanticIndexSettings::get_global(cx).enabled
     }
 
-    pub fn status(&self, project: &ModelHandle<Project>) -> SemanticIndexStatus {
+    pub fn status(&self, project: &Model<Project>) -> SemanticIndexStatus {
         if !self.is_authenticated() {
             return SemanticIndexStatus::NotAuthenticated;
         }
@@ -326,21 +330,22 @@ impl SemanticIndex {
         embedding_provider: Arc<dyn EmbeddingProvider>,
         language_registry: Arc<LanguageRegistry>,
         mut cx: AsyncAppContext,
-    ) -> Result<ModelHandle<Self>> {
+    ) -> Result<Model<Self>> {
         let t0 = Instant::now();
         let database_path = Arc::from(database_path);
-        let db = VectorDatabase::new(fs.clone(), database_path, cx.background()).await?;
+        let db = VectorDatabase::new(fs.clone(), database_path, cx.background_executor().clone())
+            .await?;
 
         log::trace!(
             "db initialization took {:?} milliseconds",
             t0.elapsed().as_millis()
         );
 
-        Ok(cx.add_model(|cx| {
+        cx.new_model(|cx| {
             let t0 = Instant::now();
             let embedding_queue =
-                EmbeddingQueue::new(embedding_provider.clone(), cx.background().clone());
-            let _embedding_task = cx.background().spawn({
+                EmbeddingQueue::new(embedding_provider.clone(), cx.background_executor().clone());
+            let _embedding_task = cx.background_executor().spawn({
                 let embedded_files = embedding_queue.finished_files();
                 let db = db.clone();
                 async move {
@@ -357,13 +362,13 @@ impl SemanticIndex {
                 channel::unbounded::<(Arc<HashMap<SpanDigest, Embedding>>, PendingFile)>();
             let embedding_queue = Arc::new(Mutex::new(embedding_queue));
             let mut _parsing_files_tasks = Vec::new();
-            for _ in 0..cx.background().num_cpus() {
+            for _ in 0..cx.background_executor().num_cpus() {
                 let fs = fs.clone();
                 let mut parsing_files_rx = parsing_files_rx.clone();
                 let embedding_provider = embedding_provider.clone();
                 let embedding_queue = embedding_queue.clone();
-                let background = cx.background().clone();
-                _parsing_files_tasks.push(cx.background().spawn(async move {
+                let background = cx.background_executor().clone();
+                _parsing_files_tasks.push(cx.background_executor().spawn(async move {
                     let mut retriever = CodeContextRetriever::new(embedding_provider.clone());
                     loop {
                         let mut timer = background.timer(EMBEDDING_QUEUE_FLUSH_TIMEOUT).fuse();
@@ -405,7 +410,7 @@ impl SemanticIndex {
                 _parsing_files_tasks,
                 projects: Default::default(),
             }
-        }))
+        })
     }
 
     async fn parse_file(
@@ -449,12 +454,12 @@ impl SemanticIndex {
 
     pub fn project_previously_indexed(
         &mut self,
-        project: &ModelHandle<Project>,
+        project: &Model<Project>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<bool>> {
         let worktrees_indexed_previously = project
             .read(cx)
-            .worktrees(cx)
+            .worktrees()
             .map(|worktree| {
                 self.db
                     .worktree_previously_indexed(&worktree.read(cx).abs_path())
@@ -473,7 +478,7 @@ impl SemanticIndex {
 
     fn project_entries_changed(
         &mut self,
-        project: ModelHandle<Project>,
+        project: Model<Project>,
         worktree_id: WorktreeId,
         changes: Arc<[(Arc<Path>, ProjectEntryId, PathChange)]>,
         cx: &mut ModelContext<Self>,
@@ -495,22 +500,25 @@ impl SemanticIndex {
             };
         worktree_state.paths_changed(changes, worktree);
         if let WorktreeState::Registered(_) = worktree_state {
-            cx.spawn_weak(|this, mut cx| async move {
-                cx.background().timer(BACKGROUND_INDEXING_DELAY).await;
-                if let Some((this, project)) = this.upgrade(&cx).zip(project.upgrade(&cx)) {
+            cx.spawn(|this, mut cx| async move {
+                cx.background_executor()
+                    .timer(BACKGROUND_INDEXING_DELAY)
+                    .await;
+                if let Some((this, project)) = this.upgrade().zip(project.upgrade()) {
                     this.update(&mut cx, |this, cx| {
                         this.index_project(project, cx).detach_and_log_err(cx)
-                    });
+                    })?;
                 }
+                anyhow::Ok(())
             })
-            .detach();
+            .detach_and_log_err(cx);
         }
     }
 
     fn register_worktree(
         &mut self,
-        project: ModelHandle<Project>,
-        worktree: ModelHandle<Worktree>,
+        project: Model<Project>,
+        worktree: Model<Worktree>,
         cx: &mut ModelContext<Self>,
     ) {
         let project = project.downgrade();
@@ -536,16 +544,18 @@ impl SemanticIndex {
                     scan_complete.await;
                     let db_id = db.find_or_create_worktree(worktree_abs_path).await?;
                     let mut file_mtimes = db.get_file_mtimes(db_id).await?;
-                    let worktree = if let Some(project) = project.upgrade(&cx) {
+                    let worktree = if let Some(project) = project.upgrade() {
                         project
                             .read_with(&cx, |project, cx| project.worktree_for_id(worktree_id, cx))
-                            .ok_or_else(|| anyhow!("worktree not found"))?
+                            .ok()
+                            .flatten()
+                            .context("worktree not found")?
                     } else {
                         return anyhow::Ok(());
                     };
-                    let worktree = worktree.read_with(&cx, |worktree, _| worktree.snapshot());
+                    let worktree = worktree.read_with(&cx, |worktree, _| worktree.snapshot())?;
                     let mut changed_paths = cx
-                        .background()
+                        .background_executor()
                         .spawn(async move {
                             let mut changed_paths = BTreeMap::new();
                             for file in worktree.files(false, 0) {
@@ -607,10 +617,8 @@ impl SemanticIndex {
                         let project_state = this
                             .projects
                             .get_mut(&project)
-                            .ok_or_else(|| anyhow!("project not registered"))?;
-                        let project = project
-                            .upgrade(cx)
-                            .ok_or_else(|| anyhow!("project was dropped"))?;
+                            .context("project not registered")?;
+                        let project = project.upgrade().context("project was dropped")?;
 
                         if let Some(WorktreeState::Registering(state)) =
                             project_state.worktrees.remove(&worktree_id)
@@ -627,7 +635,7 @@ impl SemanticIndex {
                         this.index_project(project, cx).detach_and_log_err(cx);
 
                         anyhow::Ok(())
-                    })?;
+                    })??;
 
                     anyhow::Ok(())
                 };
@@ -639,6 +647,7 @@ impl SemanticIndex {
                             project_state.worktrees.remove(&worktree_id);
                         });
                     })
+                    .ok();
                 }
 
                 *done_tx.borrow_mut() = Some(());
@@ -654,11 +663,7 @@ impl SemanticIndex {
         );
     }
 
-    fn project_worktrees_changed(
-        &mut self,
-        project: ModelHandle<Project>,
-        cx: &mut ModelContext<Self>,
-    ) {
+    fn project_worktrees_changed(&mut self, project: Model<Project>, cx: &mut ModelContext<Self>) {
         let project_state = if let Some(project_state) = self.projects.get_mut(&project.downgrade())
         {
             project_state
@@ -668,7 +673,7 @@ impl SemanticIndex {
 
         let mut worktrees = project
             .read(cx)
-            .worktrees(cx)
+            .worktrees()
             .filter(|worktree| worktree.read(cx).is_local())
             .collect::<Vec<_>>();
         let worktree_ids = worktrees
@@ -691,10 +696,7 @@ impl SemanticIndex {
         }
     }
 
-    pub fn pending_file_count(
-        &self,
-        project: &ModelHandle<Project>,
-    ) -> Option<watch::Receiver<usize>> {
+    pub fn pending_file_count(&self, project: &Model<Project>) -> Option<watch::Receiver<usize>> {
         Some(
             self.projects
                 .get(&project.downgrade())?
@@ -705,7 +707,7 @@ impl SemanticIndex {
 
     pub fn search_project(
         &mut self,
-        project: ModelHandle<Project>,
+        project: Model<Project>,
         query: String,
         limit: usize,
         includes: Vec<PathMatcher>,
@@ -727,7 +729,7 @@ impl SemanticIndex {
                 .embed_batch(vec![query])
                 .await?
                 .pop()
-                .ok_or_else(|| anyhow!("could not embed query"))?;
+                .context("could not embed query")?;
             log::trace!("Embedding Search Query: {:?}ms", t0.elapsed().as_millis());
 
             let search_start = Instant::now();
@@ -740,10 +742,10 @@ impl SemanticIndex {
                     &excludes,
                     cx,
                 )
-            });
+            })?;
             let file_results = this.update(&mut cx, |this, cx| {
                 this.search_files(project, query, limit, includes, excludes, cx)
-            });
+            })?;
             let (modified_buffer_results, file_results) =
                 futures::join!(modified_buffer_results, file_results);
 
@@ -768,7 +770,7 @@ impl SemanticIndex {
 
     pub fn search_files(
         &mut self,
-        project: ModelHandle<Project>,
+        project: Model<Project>,
         query: Embedding,
         limit: usize,
         includes: Vec<PathMatcher>,
@@ -778,14 +780,18 @@ impl SemanticIndex {
         let db_path = self.db.path().clone();
         let fs = self.fs.clone();
         cx.spawn(|this, mut cx| async move {
-            let database =
-                VectorDatabase::new(fs.clone(), db_path.clone(), cx.background()).await?;
+            let database = VectorDatabase::new(
+                fs.clone(),
+                db_path.clone(),
+                cx.background_executor().clone(),
+            )
+            .await?;
 
             let worktree_db_ids = this.read_with(&cx, |this, _| {
                 let project_state = this
                     .projects
                     .get(&project.downgrade())
-                    .ok_or_else(|| anyhow!("project was not indexed"))?;
+                    .context("project was not indexed")?;
                 let worktree_db_ids = project_state
                     .worktrees
                     .values()
@@ -798,13 +804,13 @@ impl SemanticIndex {
                     })
                     .collect::<Vec<i64>>();
                 anyhow::Ok(worktree_db_ids)
-            })?;
+            })??;
 
             let file_ids = database
                 .retrieve_included_file_ids(&worktree_db_ids, &includes, &excludes)
                 .await?;
 
-            let batch_n = cx.background().num_cpus();
+            let batch_n = cx.background_executor().num_cpus();
             let ids_len = file_ids.clone().len();
             let minimum_batch_size = 50;
 
@@ -824,9 +830,10 @@ impl SemanticIndex {
                 let fs = fs.clone();
                 let db_path = db_path.clone();
                 let query = query.clone();
-                if let Some(db) = VectorDatabase::new(fs, db_path.clone(), cx.background())
-                    .await
-                    .log_err()
+                if let Some(db) =
+                    VectorDatabase::new(fs, db_path.clone(), cx.background_executor().clone())
+                        .await
+                        .log_err()
                 {
                     batch_results.push(async move {
                         db.top_k_search(&query, limit, batch.as_slice()).await
@@ -864,6 +871,7 @@ impl SemanticIndex {
             let mut ranges = Vec::new();
             let weak_project = project.downgrade();
             project.update(&mut cx, |project, cx| {
+                let this = this.upgrade().context("index was dropped")?;
                 for (worktree_db_id, file_path, byte_range) in spans {
                     let project_state =
                         if let Some(state) = this.read(cx).projects.get(&weak_project) {
@@ -878,7 +886,7 @@ impl SemanticIndex {
                 }
 
                 Ok(())
-            })?;
+            })??;
 
             let buffers = futures::future::join_all(tasks).await;
             Ok(buffers
@@ -887,11 +895,13 @@ impl SemanticIndex {
                 .zip(scores)
                 .filter_map(|((buffer, range), similarity)| {
                     let buffer = buffer.log_err()?;
-                    let range = buffer.read_with(&cx, |buffer, _| {
-                        let start = buffer.clip_offset(range.start, Bias::Left);
-                        let end = buffer.clip_offset(range.end, Bias::Right);
-                        buffer.anchor_before(start)..buffer.anchor_after(end)
-                    });
+                    let range = buffer
+                        .read_with(&cx, |buffer, _| {
+                            let start = buffer.clip_offset(range.start, Bias::Left);
+                            let end = buffer.clip_offset(range.end, Bias::Right);
+                            buffer.anchor_before(start)..buffer.anchor_after(end)
+                        })
+                        .log_err()?;
                     Some(SearchResult {
                         buffer,
                         range,
@@ -904,7 +914,7 @@ impl SemanticIndex {
 
     fn search_modified_buffers(
         &self,
-        project: &ModelHandle<Project>,
+        project: &Model<Project>,
         query: Embedding,
         limit: usize,
         includes: &[PathMatcher],
@@ -913,7 +923,7 @@ impl SemanticIndex {
     ) -> Task<Result<Vec<SearchResult>>> {
         let modified_buffers = project
             .read(cx)
-            .opened_buffers(cx)
+            .opened_buffers()
             .into_iter()
             .filter_map(|buffer_handle| {
                 let buffer = buffer_handle.read(cx);
@@ -941,8 +951,8 @@ impl SemanticIndex {
         let embedding_provider = self.embedding_provider.clone();
         let fs = self.fs.clone();
         let db_path = self.db.path().clone();
-        let background = cx.background().clone();
-        cx.background().spawn(async move {
+        let background = cx.background_executor().clone();
+        cx.background_executor().spawn(async move {
             let db = VectorDatabase::new(fs, db_path.clone(), background).await?;
             let mut results = Vec::<SearchResult>::new();
 
@@ -996,7 +1006,7 @@ impl SemanticIndex {
 
     pub fn index_project(
         &mut self,
-        project: ModelHandle<Project>,
+        project: Model<Project>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
         if !self.is_authenticated() {
@@ -1038,7 +1048,7 @@ impl SemanticIndex {
                 let project_state = this
                     .projects
                     .get_mut(&project.downgrade())
-                    .ok_or_else(|| anyhow!("project was dropped"))?;
+                    .context("project was dropped")?;
                 let pending_file_count_tx = &project_state.pending_file_count_tx;
 
                 project_state
@@ -1080,9 +1090,9 @@ impl SemanticIndex {
                     });
 
                 anyhow::Ok(())
-            })?;
+            })??;
 
-            cx.background()
+            cx.background_executor()
                 .spawn(async move {
                     for (worktree_db_id, path) in files_to_delete {
                         db.delete_file(worktree_db_id, path).await.log_err();
@@ -1138,11 +1148,11 @@ impl SemanticIndex {
                 let project_state = this
                     .projects
                     .get_mut(&project.downgrade())
-                    .ok_or_else(|| anyhow!("project was dropped"))?;
+                    .context("project was dropped")?;
                 project_state.pending_index -= 1;
                 cx.notify();
                 anyhow::Ok(())
-            })?;
+            })??;
 
             Ok(())
         })
@@ -1150,15 +1160,15 @@ impl SemanticIndex {
 
     fn wait_for_worktree_registration(
         &self,
-        project: &ModelHandle<Project>,
+        project: &Model<Project>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
         let project = project.downgrade();
-        cx.spawn_weak(|this, cx| async move {
+        cx.spawn(|this, cx| async move {
             loop {
                 let mut pending_worktrees = Vec::new();
-                this.upgrade(&cx)
-                    .ok_or_else(|| anyhow!("semantic index dropped"))?
+                this.upgrade()
+                    .context("semantic index dropped")?
                     .read_with(&cx, |this, _| {
                         if let Some(project) = this.projects.get(&project) {
                             for worktree in project.worktrees.values() {
@@ -1167,7 +1177,7 @@ impl SemanticIndex {
                                 }
                             }
                         }
-                    });
+                    })?;
 
                 if pending_worktrees.is_empty() {
                     break;
@@ -1230,17 +1240,13 @@ impl SemanticIndex {
             } else {
                 embeddings.next()
             };
-            let embedding = embedding.ok_or_else(|| anyhow!("failed to embed spans"))?;
+            let embedding = embedding.context("failed to embed spans")?;
             span.embedding = Some(embedding);
         }
         Ok(())
     }
 }
 
-impl Entity for SemanticIndex {
-    type Event = ();
-}
-
 impl Drop for JobHandle {
     fn drop(&mut self) {
         if let Some(inner) = Arc::get_mut(&mut self.tx) {

crates/semantic_index/src/semantic_index_settings.rs 🔗

@@ -1,7 +1,7 @@
 use anyhow;
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
-use settings::Setting;
+use settings::Settings;
 
 #[derive(Deserialize, Debug)]
 pub struct SemanticIndexSettings {
@@ -13,7 +13,7 @@ pub struct SemanticIndexSettingsContent {
     pub enabled: Option<bool>,
 }
 
-impl Setting for SemanticIndexSettings {
+impl Settings for SemanticIndexSettings {
     const KEY: Option<&'static str> = Some("semantic_index");
 
     type FileContent = SemanticIndexSettingsContent;
@@ -21,7 +21,7 @@ impl Setting for SemanticIndexSettings {
     fn load(
         default_value: &Self::FileContent,
         user_values: &[&Self::FileContent],
-        _: &gpui::AppContext,
+        _: &mut gpui::AppContext,
     ) -> anyhow::Result<Self> {
         Self::load_via_json_merge(default_value, user_values)
     }

crates/semantic_index/src/semantic_index_tests.rs 🔗

@@ -6,14 +6,14 @@ use crate::{
 };
 use ai::test::FakeEmbeddingProvider;
 
-use gpui::{executor::Deterministic, Task, TestAppContext};
+use gpui::{Task, TestAppContext};
 use language::{Language, LanguageConfig, LanguageRegistry, ToOffset};
 use parking_lot::Mutex;
 use pretty_assertions::assert_eq;
 use project::{project_settings::ProjectSettings, FakeFs, Fs, Project};
 use rand::{rngs::StdRng, Rng};
 use serde_json::json;
-use settings::SettingsStore;
+use settings::{Settings, SettingsStore};
 use std::{path::Path, sync::Arc, time::SystemTime};
 use unindent::Unindent;
 use util::{paths::PathMatcher, RandomCharIter};
@@ -26,10 +26,10 @@ fn init_logger() {
 }
 
 #[gpui::test]
-async fn test_semantic_index(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
+async fn test_semantic_index(cx: &mut TestAppContext) {
     init_test(cx);
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.background_executor.clone());
     fs.insert_tree(
         "/the-root",
         json!({
@@ -91,9 +91,10 @@ async fn test_semantic_index(deterministic: Arc<Deterministic>, cx: &mut TestApp
     });
     let pending_file_count =
         semantic_index.read_with(cx, |index, _| index.pending_file_count(&project).unwrap());
-    deterministic.run_until_parked();
+    cx.background_executor.run_until_parked();
     assert_eq!(*pending_file_count.borrow(), 3);
-    deterministic.advance_clock(EMBEDDING_QUEUE_FLUSH_TIMEOUT);
+    cx.background_executor
+        .advance_clock(EMBEDDING_QUEUE_FLUSH_TIMEOUT);
     assert_eq!(*pending_file_count.borrow(), 0);
 
     let search_results = search_results.await.unwrap();
@@ -170,13 +171,15 @@ async fn test_semantic_index(deterministic: Arc<Deterministic>, cx: &mut TestApp
     .await
     .unwrap();
 
-    deterministic.advance_clock(EMBEDDING_QUEUE_FLUSH_TIMEOUT);
+    cx.background_executor
+        .advance_clock(EMBEDDING_QUEUE_FLUSH_TIMEOUT);
 
     let prev_embedding_count = embedding_provider.embedding_count();
     let index = semantic_index.update(cx, |store, cx| store.index_project(project.clone(), cx));
-    deterministic.run_until_parked();
+    cx.background_executor.run_until_parked();
     assert_eq!(*pending_file_count.borrow(), 1);
-    deterministic.advance_clock(EMBEDDING_QUEUE_FLUSH_TIMEOUT);
+    cx.background_executor
+        .advance_clock(EMBEDDING_QUEUE_FLUSH_TIMEOUT);
     assert_eq!(*pending_file_count.borrow(), 0);
     index.await.unwrap();
 
@@ -220,13 +223,13 @@ async fn test_embedding_batching(cx: &mut TestAppContext, mut rng: StdRng) {
 
     let embedding_provider = Arc::new(FakeEmbeddingProvider::default());
 
-    let mut queue = EmbeddingQueue::new(embedding_provider.clone(), cx.background());
+    let mut queue = EmbeddingQueue::new(embedding_provider.clone(), cx.background_executor.clone());
     for file in &files {
         queue.push(file.clone());
     }
     queue.flush();
 
-    cx.foreground().run_until_parked();
+    cx.background_executor.run_until_parked();
     let finished_files = queue.finished_files();
     let mut embedded_files: Vec<_> = files
         .iter()
@@ -1686,8 +1689,9 @@ fn test_subtract_ranges() {
 
 fn init_test(cx: &mut TestAppContext) {
     cx.update(|cx| {
-        cx.set_global(SettingsStore::test(cx));
-        settings::register::<SemanticIndexSettings>(cx);
-        settings::register::<ProjectSettings>(cx);
+        let settings_store = SettingsStore::test(cx);
+        cx.set_global(settings_store);
+        SemanticIndexSettings::register(cx);
+        ProjectSettings::register(cx);
     });
 }

crates/semantic_index2/Cargo.toml 🔗

@@ -1,69 +0,0 @@
-[package]
-name = "semantic_index2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/semantic_index.rs"
-doctest = false
-
-[dependencies]
-ai = { package = "ai2", path = "../ai2" }
-collections = { path = "../collections" }
-gpui = { package = "gpui2", path = "../gpui2" }
-language = { package = "language2", path = "../language2" }
-project = { package = "project2", path = "../project2" }
-workspace = { package = "workspace2", path = "../workspace2" }
-util = { path = "../util" }
-rpc = { package = "rpc2", path = "../rpc2" }
-settings = { package = "settings2", path = "../settings2" }
-anyhow.workspace = true
-postage.workspace = true
-futures.workspace = true
-ordered-float.workspace = true
-smol.workspace = true
-rusqlite.workspace = true
-log.workspace = true
-tree-sitter.workspace = true
-lazy_static.workspace = true
-serde.workspace = true
-serde_json.workspace = true
-async-trait.workspace = true
-tiktoken-rs.workspace = true
-parking_lot.workspace = true
-rand.workspace = true
-schemars.workspace = true
-globset.workspace = true
-sha1 = "0.10.5"
-ndarray = { version = "0.15.0" }
-
-[dev-dependencies]
-ai = { package = "ai2", path = "../ai2", features = ["test-support"] }
-collections = { path = "../collections", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-language = { package = "language2", path = "../language2", features = ["test-support"] }
-project = { package = "project2", path = "../project2", features = ["test-support"] }
-rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
-workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
-settings = { package = "settings2", path = "../settings2", features = ["test-support"]}
-rust-embed = { version = "8.0", features = ["include-exclude"] }
-client = { package = "client2", path = "../client2" }
-node_runtime = { path = "../node_runtime"}
-
-pretty_assertions.workspace = true
-rand.workspace = true
-unindent.workspace = true
-tempdir.workspace = true
-ctor.workspace = true
-env_logger.workspace = true
-
-tree-sitter-typescript.workspace = true
-tree-sitter-json.workspace = true
-tree-sitter-rust.workspace = true
-tree-sitter-toml.workspace = true
-tree-sitter-cpp.workspace = true
-tree-sitter-elixir.workspace = true
-tree-sitter-lua.workspace = true
-tree-sitter-ruby.workspace = true
-tree-sitter-php.workspace = true

crates/semantic_index2/README.md 🔗

@@ -1,20 +0,0 @@
-
-# Semantic Index
-
-## Evaluation
-
-### Metrics
-
-nDCG@k:
-- "The value of NDCG is determined by comparing the relevance of the items returned by the search engine to the relevance of the item that a hypothetical "ideal" search engine would return.
-- "The relevance of result is represented by a score (also known as a 'grade') that is assigned to the search query. The scores of these results are then discounted based on their position in the search results -- did they get recommended first or last?"
-
-MRR@k:
-- "Mean reciprocal rank quantifies the rank of the first relevant item found in teh recommendation list."
-
-MAP@k:
-- "Mean average precision averages the precision@k metric at each relevant item position in the recommendation list.
-
-Resources:
-- [Evaluating recommendation metrics](https://www.shaped.ai/blog/evaluating-recommendation-systems-map-mmr-ndcg)
-- [Math Walkthrough](https://towardsdatascience.com/demystifying-ndcg-bee3be58cfe0)

crates/semantic_index2/eval/gpt-engineer.json 🔗

@@ -1,114 +0,0 @@
-{
-  "repo": "https://github.com/AntonOsika/gpt-engineer.git",
-  "commit": "7735a6445bae3611c62f521e6464c67c957f87c2",
-  "assertions": [
-    {
-      "query": "How do I contribute to this project?",
-      "matches": [
-        ".github/CONTRIBUTING.md:1",
-        "ROADMAP.md:48"
-      ]
-    },
-    {
-      "query": "What version of the openai package is active?",
-      "matches": [
-        "pyproject.toml:14"
-      ]
-    },
-    {
-      "query": "Ask user for clarification",
-      "matches": [
-        "gpt_engineer/steps.py:69"
-      ]
-    },
-    {
-      "query": "generate tests for python code",
-      "matches": [
-        "gpt_engineer/steps.py:153"
-      ]
-    },
-    {
-      "query": "get item from database based on key",
-      "matches": [
-        "gpt_engineer/db.py:42",
-        "gpt_engineer/db.py:68"
-      ]
-    },
-    {
-      "query": "prompt user to select files",
-      "matches": [
-        "gpt_engineer/file_selector.py:171",
-        "gpt_engineer/file_selector.py:306",
-        "gpt_engineer/file_selector.py:289",
-        "gpt_engineer/file_selector.py:234"
-      ]
-    },
-    {
-      "query": "send to rudderstack",
-      "matches": [
-        "gpt_engineer/collect.py:11",
-        "gpt_engineer/collect.py:38"
-      ]
-    },
-    {
-      "query": "parse code blocks from chat messages",
-      "matches": [
-        "gpt_engineer/chat_to_files.py:10",
-        "docs/intro/chat_parsing.md:1"
-      ]
-    },
-    {
-      "query": "how do I use the docker cli?",
-      "matches": [
-        "docker/README.md:1"
-      ]
-    },
-    {
-      "query": "ask the user if the code ran successfully?",
-      "matches": [
-        "gpt_engineer/learning.py:54"
-      ]
-    },
-    {
-      "query": "how is consent granted by the user?",
-      "matches": [
-        "gpt_engineer/learning.py:107",
-        "gpt_engineer/learning.py:130",
-        "gpt_engineer/learning.py:152"
-      ]
-    },
-    {
-      "query": "what are all the different steps the agent can take?",
-      "matches": [
-        "docs/intro/steps_module.md:1",
-        "gpt_engineer/steps.py:391"
-      ]
-    },
-    {
-      "query": "ask the user for clarification?",
-      "matches": [
-        "gpt_engineer/steps.py:69"
-      ]
-    },
-    {
-      "query": "what models are available?",
-      "matches": [
-        "gpt_engineer/ai.py:315",
-        "gpt_engineer/ai.py:341",
-        "docs/open-models.md:1"
-      ]
-    },
-    {
-      "query": "what is the current focus of the project?",
-      "matches": [
-        "ROADMAP.md:11"
-      ]
-    },
-    {
-      "query": "does the agent know how to fix code?",
-      "matches": [
-        "gpt_engineer/steps.py:367"
-      ]
-    }
-  ]
-}

crates/semantic_index2/eval/tree-sitter.json 🔗

@@ -1,104 +0,0 @@
-{
-  "repo": "https://github.com/tree-sitter/tree-sitter.git",
-  "commit": "46af27796a76c72d8466627d499f2bca4af958ee",
-  "assertions": [
-    {
-      "query": "What attributes are available for the tags configuration struct?",
-      "matches": [
-        "tags/src/lib.rs:24"
-      ]
-    },
-    {
-      "query": "create a new tag configuration",
-      "matches": [
-        "tags/src/lib.rs:119"
-      ]
-    },
-    {
-      "query": "generate tags based on config",
-      "matches": [
-        "tags/src/lib.rs:261"
-      ]
-    },
-    {
-      "query": "match on ts quantifier in rust",
-      "matches": [
-        "lib/binding_rust/lib.rs:139"
-      ]
-    },
-    {
-      "query": "cli command to generate tags",
-      "matches": [
-        "cli/src/tags.rs:10"
-      ]
-    },
-    {
-      "query": "what version of the tree-sitter-tags package is active?",
-      "matches": [
-        "tags/Cargo.toml:4"
-      ]
-    },
-    {
-      "query": "Insert a new parse state",
-      "matches": [
-        "cli/src/generate/build_tables/build_parse_table.rs:153"
-      ]
-    },
-    {
-      "query": "Handle conflict when numerous actions occur on the same symbol",
-      "matches": [
-        "cli/src/generate/build_tables/build_parse_table.rs:363",
-        "cli/src/generate/build_tables/build_parse_table.rs:442"
-      ]
-    },
-    {
-      "query": "Match based on associativity of actions",
-      "matches": [
-        "cri/src/generate/build_tables/build_parse_table.rs:542"
-      ]
-    },
-    {
-      "query": "Format token set display",
-      "matches": [
-        "cli/src/generate/build_tables/item.rs:246"
-      ]
-    },
-    {
-      "query": "extract choices from rule",
-      "matches": [
-        "cli/src/generate/prepare_grammar/flatten_grammar.rs:124"
-      ]
-    },
-    {
-      "query": "How do we identify if a symbol is being used?",
-      "matches": [
-        "cli/src/generate/prepare_grammar/flatten_grammar.rs:175"
-      ]
-    },
-    {
-      "query": "How do we launch the playground?",
-      "matches": [
-        "cli/src/playground.rs:46"
-      ]
-    },
-    {
-      "query": "How do we test treesitter query matches in rust?",
-      "matches": [
-        "cli/src/query_testing.rs:152",
-        "cli/src/tests/query_test.rs:781",
-        "cli/src/tests/query_test.rs:2163",
-        "cli/src/tests/query_test.rs:3781",
-        "cli/src/tests/query_test.rs:887"
-      ]
-    },
-    {
-      "query": "What does the CLI do?",
-      "matches": [
-        "cli/README.md:10",
-        "cli/loader/README.md:3",
-        "docs/section-5-implementation.md:14",
-        "docs/section-5-implementation.md:18"
-      ]
-    }
-  ]
-}

crates/semantic_index2/src/db.rs 🔗

@@ -1,603 +0,0 @@
-use crate::{
-    parsing::{Span, SpanDigest},
-    SEMANTIC_INDEX_VERSION,
-};
-use ai::embedding::Embedding;
-use anyhow::{anyhow, Context, Result};
-use collections::HashMap;
-use futures::channel::oneshot;
-use gpui::BackgroundExecutor;
-use ndarray::{Array1, Array2};
-use ordered_float::OrderedFloat;
-use project::Fs;
-use rpc::proto::Timestamp;
-use rusqlite::params;
-use rusqlite::types::Value;
-use std::{
-    future::Future,
-    ops::Range,
-    path::{Path, PathBuf},
-    rc::Rc,
-    sync::Arc,
-    time::SystemTime,
-};
-use util::{paths::PathMatcher, TryFutureExt};
-
-pub fn argsort<T: Ord>(data: &[T]) -> Vec<usize> {
-    let mut indices = (0..data.len()).collect::<Vec<_>>();
-    indices.sort_by_key(|&i| &data[i]);
-    indices.reverse();
-    indices
-}
-
-#[derive(Debug)]
-pub struct FileRecord {
-    pub id: usize,
-    pub relative_path: String,
-    pub mtime: Timestamp,
-}
-
-#[derive(Clone)]
-pub struct VectorDatabase {
-    path: Arc<Path>,
-    transactions:
-        smol::channel::Sender<Box<dyn 'static + Send + FnOnce(&mut rusqlite::Connection)>>,
-}
-
-impl VectorDatabase {
-    pub async fn new(
-        fs: Arc<dyn Fs>,
-        path: Arc<Path>,
-        executor: BackgroundExecutor,
-    ) -> Result<Self> {
-        if let Some(db_directory) = path.parent() {
-            fs.create_dir(db_directory).await?;
-        }
-
-        let (transactions_tx, transactions_rx) = smol::channel::unbounded::<
-            Box<dyn 'static + Send + FnOnce(&mut rusqlite::Connection)>,
-        >();
-        executor
-            .spawn({
-                let path = path.clone();
-                async move {
-                    let mut connection = rusqlite::Connection::open(&path)?;
-
-                    connection.pragma_update(None, "journal_mode", "wal")?;
-                    connection.pragma_update(None, "synchronous", "normal")?;
-                    connection.pragma_update(None, "cache_size", 1000000)?;
-                    connection.pragma_update(None, "temp_store", "MEMORY")?;
-
-                    while let Ok(transaction) = transactions_rx.recv().await {
-                        transaction(&mut connection);
-                    }
-
-                    anyhow::Ok(())
-                }
-                .log_err()
-            })
-            .detach();
-        let this = Self {
-            transactions: transactions_tx,
-            path,
-        };
-        this.initialize_database().await?;
-        Ok(this)
-    }
-
-    pub fn path(&self) -> &Arc<Path> {
-        &self.path
-    }
-
-    fn transact<F, T>(&self, f: F) -> impl Future<Output = Result<T>>
-    where
-        F: 'static + Send + FnOnce(&rusqlite::Transaction) -> Result<T>,
-        T: 'static + Send,
-    {
-        let (tx, rx) = oneshot::channel();
-        let transactions = self.transactions.clone();
-        async move {
-            if transactions
-                .send(Box::new(|connection| {
-                    let result = connection
-                        .transaction()
-                        .map_err(|err| anyhow!(err))
-                        .and_then(|transaction| {
-                            let result = f(&transaction)?;
-                            transaction.commit()?;
-                            Ok(result)
-                        });
-                    let _ = tx.send(result);
-                }))
-                .await
-                .is_err()
-            {
-                return Err(anyhow!("connection was dropped"))?;
-            }
-            rx.await?
-        }
-    }
-
-    fn initialize_database(&self) -> impl Future<Output = Result<()>> {
-        self.transact(|db| {
-            rusqlite::vtab::array::load_module(&db)?;
-
-            // Delete existing tables, if SEMANTIC_INDEX_VERSION is bumped
-            let version_query = db.prepare("SELECT version from semantic_index_config");
-            let version = version_query
-                .and_then(|mut query| query.query_row([], |row| Ok(row.get::<_, i64>(0)?)));
-            if version.map_or(false, |version| version == SEMANTIC_INDEX_VERSION as i64) {
-                log::trace!("vector database schema up to date");
-                return Ok(());
-            }
-
-            log::trace!("vector database schema out of date. updating...");
-            // We renamed the `documents` table to `spans`, so we want to drop
-            // `documents` without recreating it if it exists.
-            db.execute("DROP TABLE IF EXISTS documents", [])
-                .context("failed to drop 'documents' table")?;
-            db.execute("DROP TABLE IF EXISTS spans", [])
-                .context("failed to drop 'spans' table")?;
-            db.execute("DROP TABLE IF EXISTS files", [])
-                .context("failed to drop 'files' table")?;
-            db.execute("DROP TABLE IF EXISTS worktrees", [])
-                .context("failed to drop 'worktrees' table")?;
-            db.execute("DROP TABLE IF EXISTS semantic_index_config", [])
-                .context("failed to drop 'semantic_index_config' table")?;
-
-            // Initialize Vector Databasing Tables
-            db.execute(
-                "CREATE TABLE semantic_index_config (
-                    version INTEGER NOT NULL
-                )",
-                [],
-            )?;
-
-            db.execute(
-                "INSERT INTO semantic_index_config (version) VALUES (?1)",
-                params![SEMANTIC_INDEX_VERSION],
-            )?;
-
-            db.execute(
-                "CREATE TABLE worktrees (
-                    id INTEGER PRIMARY KEY AUTOINCREMENT,
-                    absolute_path VARCHAR NOT NULL
-                );
-                CREATE UNIQUE INDEX worktrees_absolute_path ON worktrees (absolute_path);
-                ",
-                [],
-            )?;
-
-            db.execute(
-                "CREATE TABLE files (
-                    id INTEGER PRIMARY KEY AUTOINCREMENT,
-                    worktree_id INTEGER NOT NULL,
-                    relative_path VARCHAR NOT NULL,
-                    mtime_seconds INTEGER NOT NULL,
-                    mtime_nanos INTEGER NOT NULL,
-                    FOREIGN KEY(worktree_id) REFERENCES worktrees(id) ON DELETE CASCADE
-                )",
-                [],
-            )?;
-
-            db.execute(
-                "CREATE UNIQUE INDEX files_worktree_id_and_relative_path ON files (worktree_id, relative_path)",
-                [],
-            )?;
-
-            db.execute(
-                "CREATE TABLE spans (
-                    id INTEGER PRIMARY KEY AUTOINCREMENT,
-                    file_id INTEGER NOT NULL,
-                    start_byte INTEGER NOT NULL,
-                    end_byte INTEGER NOT NULL,
-                    name VARCHAR NOT NULL,
-                    embedding BLOB NOT NULL,
-                    digest BLOB NOT NULL,
-                    FOREIGN KEY(file_id) REFERENCES files(id) ON DELETE CASCADE
-                )",
-                [],
-            )?;
-            db.execute(
-                "CREATE INDEX spans_digest ON spans (digest)",
-                [],
-            )?;
-
-            log::trace!("vector database initialized with updated schema.");
-            Ok(())
-        })
-    }
-
-    pub fn delete_file(
-        &self,
-        worktree_id: i64,
-        delete_path: Arc<Path>,
-    ) -> impl Future<Output = Result<()>> {
-        self.transact(move |db| {
-            db.execute(
-                "DELETE FROM files WHERE worktree_id = ?1 AND relative_path = ?2",
-                params![worktree_id, delete_path.to_str()],
-            )?;
-            Ok(())
-        })
-    }
-
-    pub fn insert_file(
-        &self,
-        worktree_id: i64,
-        path: Arc<Path>,
-        mtime: SystemTime,
-        spans: Vec<Span>,
-    ) -> impl Future<Output = Result<()>> {
-        self.transact(move |db| {
-            // Return the existing ID, if both the file and mtime match
-            let mtime = Timestamp::from(mtime);
-
-            db.execute(
-                "
-                REPLACE INTO files
-                (worktree_id, relative_path, mtime_seconds, mtime_nanos)
-                VALUES (?1, ?2, ?3, ?4)
-                ",
-                params![worktree_id, path.to_str(), mtime.seconds, mtime.nanos],
-            )?;
-
-            let file_id = db.last_insert_rowid();
-
-            let mut query = db.prepare(
-                "
-                INSERT INTO spans
-                (file_id, start_byte, end_byte, name, embedding, digest)
-                VALUES (?1, ?2, ?3, ?4, ?5, ?6)
-                ",
-            )?;
-
-            for span in spans {
-                query.execute(params![
-                    file_id,
-                    span.range.start.to_string(),
-                    span.range.end.to_string(),
-                    span.name,
-                    span.embedding,
-                    span.digest
-                ])?;
-            }
-
-            Ok(())
-        })
-    }
-
-    pub fn worktree_previously_indexed(
-        &self,
-        worktree_root_path: &Path,
-    ) -> impl Future<Output = Result<bool>> {
-        let worktree_root_path = worktree_root_path.to_string_lossy().into_owned();
-        self.transact(move |db| {
-            let mut worktree_query =
-                db.prepare("SELECT id FROM worktrees WHERE absolute_path = ?1")?;
-            let worktree_id = worktree_query
-                .query_row(params![worktree_root_path], |row| Ok(row.get::<_, i64>(0)?));
-
-            if worktree_id.is_ok() {
-                return Ok(true);
-            } else {
-                return Ok(false);
-            }
-        })
-    }
-
-    pub fn embeddings_for_digests(
-        &self,
-        digests: Vec<SpanDigest>,
-    ) -> impl Future<Output = Result<HashMap<SpanDigest, Embedding>>> {
-        self.transact(move |db| {
-            let mut query = db.prepare(
-                "
-                SELECT digest, embedding
-                FROM spans
-                WHERE digest IN rarray(?)
-                ",
-            )?;
-            let mut embeddings_by_digest = HashMap::default();
-            let digests = Rc::new(
-                digests
-                    .into_iter()
-                    .map(|p| Value::Blob(p.0.to_vec()))
-                    .collect::<Vec<_>>(),
-            );
-            let rows = query.query_map(params![digests], |row| {
-                Ok((row.get::<_, SpanDigest>(0)?, row.get::<_, Embedding>(1)?))
-            })?;
-
-            for row in rows {
-                if let Ok(row) = row {
-                    embeddings_by_digest.insert(row.0, row.1);
-                }
-            }
-
-            Ok(embeddings_by_digest)
-        })
-    }
-
-    pub fn embeddings_for_files(
-        &self,
-        worktree_id_file_paths: HashMap<i64, Vec<Arc<Path>>>,
-    ) -> impl Future<Output = Result<HashMap<SpanDigest, Embedding>>> {
-        self.transact(move |db| {
-            let mut query = db.prepare(
-                "
-                SELECT digest, embedding
-                FROM spans
-                LEFT JOIN files ON files.id = spans.file_id
-                WHERE files.worktree_id = ? AND files.relative_path IN rarray(?)
-            ",
-            )?;
-            let mut embeddings_by_digest = HashMap::default();
-            for (worktree_id, file_paths) in worktree_id_file_paths {
-                let file_paths = Rc::new(
-                    file_paths
-                        .into_iter()
-                        .map(|p| Value::Text(p.to_string_lossy().into_owned()))
-                        .collect::<Vec<_>>(),
-                );
-                let rows = query.query_map(params![worktree_id, file_paths], |row| {
-                    Ok((row.get::<_, SpanDigest>(0)?, row.get::<_, Embedding>(1)?))
-                })?;
-
-                for row in rows {
-                    if let Ok(row) = row {
-                        embeddings_by_digest.insert(row.0, row.1);
-                    }
-                }
-            }
-
-            Ok(embeddings_by_digest)
-        })
-    }
-
-    pub fn find_or_create_worktree(
-        &self,
-        worktree_root_path: Arc<Path>,
-    ) -> impl Future<Output = Result<i64>> {
-        self.transact(move |db| {
-            let mut worktree_query =
-                db.prepare("SELECT id FROM worktrees WHERE absolute_path = ?1")?;
-            let worktree_id = worktree_query
-                .query_row(params![worktree_root_path.to_string_lossy()], |row| {
-                    Ok(row.get::<_, i64>(0)?)
-                });
-
-            if worktree_id.is_ok() {
-                return Ok(worktree_id?);
-            }
-
-            // If worktree_id is Err, insert new worktree
-            db.execute(
-                "INSERT into worktrees (absolute_path) VALUES (?1)",
-                params![worktree_root_path.to_string_lossy()],
-            )?;
-            Ok(db.last_insert_rowid())
-        })
-    }
-
-    pub fn get_file_mtimes(
-        &self,
-        worktree_id: i64,
-    ) -> impl Future<Output = Result<HashMap<PathBuf, SystemTime>>> {
-        self.transact(move |db| {
-            let mut statement = db.prepare(
-                "
-                SELECT relative_path, mtime_seconds, mtime_nanos
-                FROM files
-                WHERE worktree_id = ?1
-                ORDER BY relative_path",
-            )?;
-            let mut result: HashMap<PathBuf, SystemTime> = HashMap::default();
-            for row in statement.query_map(params![worktree_id], |row| {
-                Ok((
-                    row.get::<_, String>(0)?.into(),
-                    Timestamp {
-                        seconds: row.get(1)?,
-                        nanos: row.get(2)?,
-                    }
-                    .into(),
-                ))
-            })? {
-                let row = row?;
-                result.insert(row.0, row.1);
-            }
-            Ok(result)
-        })
-    }
-
-    pub fn top_k_search(
-        &self,
-        query_embedding: &Embedding,
-        limit: usize,
-        file_ids: &[i64],
-    ) -> impl Future<Output = Result<Vec<(i64, OrderedFloat<f32>)>>> {
-        let file_ids = file_ids.to_vec();
-        let query = query_embedding.clone().0;
-        let query = Array1::from_vec(query);
-        self.transact(move |db| {
-            let mut query_statement = db.prepare(
-                "
-                    SELECT
-                        id, embedding
-                    FROM
-                        spans
-                    WHERE
-                        file_id IN rarray(?)
-                    ",
-            )?;
-
-            let deserialized_rows = query_statement
-                .query_map(params![ids_to_sql(&file_ids)], |row| {
-                    Ok((row.get::<_, usize>(0)?, row.get::<_, Embedding>(1)?))
-                })?
-                .filter_map(|row| row.ok())
-                .collect::<Vec<(usize, Embedding)>>();
-
-            if deserialized_rows.len() == 0 {
-                return Ok(Vec::new());
-            }
-
-            // Get Length of Embeddings Returned
-            let embedding_len = deserialized_rows[0].1 .0.len();
-
-            let batch_n = 1000;
-            let mut batches = Vec::new();
-            let mut batch_ids = Vec::new();
-            let mut batch_embeddings: Vec<f32> = Vec::new();
-            deserialized_rows.iter().for_each(|(id, embedding)| {
-                batch_ids.push(id);
-                batch_embeddings.extend(&embedding.0);
-
-                if batch_ids.len() == batch_n {
-                    let embeddings = std::mem::take(&mut batch_embeddings);
-                    let ids = std::mem::take(&mut batch_ids);
-                    let array =
-                        Array2::from_shape_vec((ids.len(), embedding_len.clone()), embeddings);
-                    match array {
-                        Ok(array) => {
-                            batches.push((ids, array));
-                        }
-                        Err(err) => log::error!("Failed to deserialize to ndarray: {:?}", err),
-                    }
-                }
-            });
-
-            if batch_ids.len() > 0 {
-                let array = Array2::from_shape_vec(
-                    (batch_ids.len(), embedding_len),
-                    batch_embeddings.clone(),
-                );
-                match array {
-                    Ok(array) => {
-                        batches.push((batch_ids.clone(), array));
-                    }
-                    Err(err) => log::error!("Failed to deserialize to ndarray: {:?}", err),
-                }
-            }
-
-            let mut ids: Vec<usize> = Vec::new();
-            let mut results = Vec::new();
-            for (batch_ids, array) in batches {
-                let scores = array
-                    .dot(&query.t())
-                    .to_vec()
-                    .iter()
-                    .map(|score| OrderedFloat(*score))
-                    .collect::<Vec<OrderedFloat<f32>>>();
-                results.extend(scores);
-                ids.extend(batch_ids);
-            }
-
-            let sorted_idx = argsort(&results);
-            let mut sorted_results = Vec::new();
-            let last_idx = limit.min(sorted_idx.len());
-            for idx in &sorted_idx[0..last_idx] {
-                sorted_results.push((ids[*idx] as i64, results[*idx]))
-            }
-
-            Ok(sorted_results)
-        })
-    }
-
-    pub fn retrieve_included_file_ids(
-        &self,
-        worktree_ids: &[i64],
-        includes: &[PathMatcher],
-        excludes: &[PathMatcher],
-    ) -> impl Future<Output = Result<Vec<i64>>> {
-        let worktree_ids = worktree_ids.to_vec();
-        let includes = includes.to_vec();
-        let excludes = excludes.to_vec();
-        self.transact(move |db| {
-            let mut file_query = db.prepare(
-                "
-                SELECT
-                    id, relative_path
-                FROM
-                    files
-                WHERE
-                    worktree_id IN rarray(?)
-                ",
-            )?;
-
-            let mut file_ids = Vec::<i64>::new();
-            let mut rows = file_query.query([ids_to_sql(&worktree_ids)])?;
-
-            while let Some(row) = rows.next()? {
-                let file_id = row.get(0)?;
-                let relative_path = row.get_ref(1)?.as_str()?;
-                let included =
-                    includes.is_empty() || includes.iter().any(|glob| glob.is_match(relative_path));
-                let excluded = excludes.iter().any(|glob| glob.is_match(relative_path));
-                if included && !excluded {
-                    file_ids.push(file_id);
-                }
-            }
-
-            anyhow::Ok(file_ids)
-        })
-    }
-
-    pub fn spans_for_ids(
-        &self,
-        ids: &[i64],
-    ) -> impl Future<Output = Result<Vec<(i64, PathBuf, Range<usize>)>>> {
-        let ids = ids.to_vec();
-        self.transact(move |db| {
-            let mut statement = db.prepare(
-                "
-                    SELECT
-                        spans.id,
-                        files.worktree_id,
-                        files.relative_path,
-                        spans.start_byte,
-                        spans.end_byte
-                    FROM
-                        spans, files
-                    WHERE
-                        spans.file_id = files.id AND
-                        spans.id in rarray(?)
-                ",
-            )?;
-
-            let result_iter = statement.query_map(params![ids_to_sql(&ids)], |row| {
-                Ok((
-                    row.get::<_, i64>(0)?,
-                    row.get::<_, i64>(1)?,
-                    row.get::<_, String>(2)?.into(),
-                    row.get(3)?..row.get(4)?,
-                ))
-            })?;
-
-            let mut values_by_id = HashMap::<i64, (i64, PathBuf, Range<usize>)>::default();
-            for row in result_iter {
-                let (id, worktree_id, path, range) = row?;
-                values_by_id.insert(id, (worktree_id, path, range));
-            }
-
-            let mut results = Vec::with_capacity(ids.len());
-            for id in &ids {
-                let value = values_by_id
-                    .remove(id)
-                    .ok_or(anyhow!("missing span id {}", id))?;
-                results.push(value);
-            }
-
-            Ok(results)
-        })
-    }
-}
-
-fn ids_to_sql(ids: &[i64]) -> Rc<Vec<rusqlite::types::Value>> {
-    Rc::new(
-        ids.iter()
-            .copied()
-            .map(|v| rusqlite::types::Value::from(v))
-            .collect::<Vec<_>>(),
-    )
-}

crates/semantic_index2/src/embedding_queue.rs 🔗

@@ -1,169 +0,0 @@
-use crate::{parsing::Span, JobHandle};
-use ai::embedding::EmbeddingProvider;
-use gpui::BackgroundExecutor;
-use parking_lot::Mutex;
-use smol::channel;
-use std::{mem, ops::Range, path::Path, sync::Arc, time::SystemTime};
-
-#[derive(Clone)]
-pub struct FileToEmbed {
-    pub worktree_id: i64,
-    pub path: Arc<Path>,
-    pub mtime: SystemTime,
-    pub spans: Vec<Span>,
-    pub job_handle: JobHandle,
-}
-
-impl std::fmt::Debug for FileToEmbed {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("FileToEmbed")
-            .field("worktree_id", &self.worktree_id)
-            .field("path", &self.path)
-            .field("mtime", &self.mtime)
-            .field("spans", &self.spans)
-            .finish_non_exhaustive()
-    }
-}
-
-impl PartialEq for FileToEmbed {
-    fn eq(&self, other: &Self) -> bool {
-        self.worktree_id == other.worktree_id
-            && self.path == other.path
-            && self.mtime == other.mtime
-            && self.spans == other.spans
-    }
-}
-
-pub struct EmbeddingQueue {
-    embedding_provider: Arc<dyn EmbeddingProvider>,
-    pending_batch: Vec<FileFragmentToEmbed>,
-    executor: BackgroundExecutor,
-    pending_batch_token_count: usize,
-    finished_files_tx: channel::Sender<FileToEmbed>,
-    finished_files_rx: channel::Receiver<FileToEmbed>,
-}
-
-#[derive(Clone)]
-pub struct FileFragmentToEmbed {
-    file: Arc<Mutex<FileToEmbed>>,
-    span_range: Range<usize>,
-}
-
-impl EmbeddingQueue {
-    pub fn new(
-        embedding_provider: Arc<dyn EmbeddingProvider>,
-        executor: BackgroundExecutor,
-    ) -> Self {
-        let (finished_files_tx, finished_files_rx) = channel::unbounded();
-        Self {
-            embedding_provider,
-            executor,
-            pending_batch: Vec::new(),
-            pending_batch_token_count: 0,
-            finished_files_tx,
-            finished_files_rx,
-        }
-    }
-
-    pub fn push(&mut self, file: FileToEmbed) {
-        if file.spans.is_empty() {
-            self.finished_files_tx.try_send(file).unwrap();
-            return;
-        }
-
-        let file = Arc::new(Mutex::new(file));
-
-        self.pending_batch.push(FileFragmentToEmbed {
-            file: file.clone(),
-            span_range: 0..0,
-        });
-
-        let mut fragment_range = &mut self.pending_batch.last_mut().unwrap().span_range;
-        for (ix, span) in file.lock().spans.iter().enumerate() {
-            let span_token_count = if span.embedding.is_none() {
-                span.token_count
-            } else {
-                0
-            };
-
-            let next_token_count = self.pending_batch_token_count + span_token_count;
-            if next_token_count > self.embedding_provider.max_tokens_per_batch() {
-                let range_end = fragment_range.end;
-                self.flush();
-                self.pending_batch.push(FileFragmentToEmbed {
-                    file: file.clone(),
-                    span_range: range_end..range_end,
-                });
-                fragment_range = &mut self.pending_batch.last_mut().unwrap().span_range;
-            }
-
-            fragment_range.end = ix + 1;
-            self.pending_batch_token_count += span_token_count;
-        }
-    }
-
-    pub fn flush(&mut self) {
-        let batch = mem::take(&mut self.pending_batch);
-        self.pending_batch_token_count = 0;
-        if batch.is_empty() {
-            return;
-        }
-
-        let finished_files_tx = self.finished_files_tx.clone();
-        let embedding_provider = self.embedding_provider.clone();
-
-        self.executor
-            .spawn(async move {
-                let mut spans = Vec::new();
-                for fragment in &batch {
-                    let file = fragment.file.lock();
-                    spans.extend(
-                        file.spans[fragment.span_range.clone()]
-                            .iter()
-                            .filter(|d| d.embedding.is_none())
-                            .map(|d| d.content.clone()),
-                    );
-                }
-
-                // If spans is 0, just send the fragment to the finished files if its the last one.
-                if spans.is_empty() {
-                    for fragment in batch.clone() {
-                        if let Some(file) = Arc::into_inner(fragment.file) {
-                            finished_files_tx.try_send(file.into_inner()).unwrap();
-                        }
-                    }
-                    return;
-                };
-
-                match embedding_provider.embed_batch(spans).await {
-                    Ok(embeddings) => {
-                        let mut embeddings = embeddings.into_iter();
-                        for fragment in batch {
-                            for span in &mut fragment.file.lock().spans[fragment.span_range.clone()]
-                                .iter_mut()
-                                .filter(|d| d.embedding.is_none())
-                            {
-                                if let Some(embedding) = embeddings.next() {
-                                    span.embedding = Some(embedding);
-                                } else {
-                                    log::error!("number of embeddings != number of documents");
-                                }
-                            }
-
-                            if let Some(file) = Arc::into_inner(fragment.file) {
-                                finished_files_tx.try_send(file.into_inner()).unwrap();
-                            }
-                        }
-                    }
-                    Err(error) => {
-                        log::error!("{:?}", error);
-                    }
-                }
-            })
-            .detach();
-    }
-
-    pub fn finished_files(&self) -> channel::Receiver<FileToEmbed> {
-        self.finished_files_rx.clone()
-    }
-}

crates/semantic_index2/src/parsing.rs 🔗

@@ -1,414 +0,0 @@
-use ai::{
-    embedding::{Embedding, EmbeddingProvider},
-    models::TruncationDirection,
-};
-use anyhow::{anyhow, Result};
-use language::{Grammar, Language};
-use rusqlite::{
-    types::{FromSql, FromSqlResult, ToSqlOutput, ValueRef},
-    ToSql,
-};
-use sha1::{Digest, Sha1};
-use std::{
-    borrow::Cow,
-    cmp::{self, Reverse},
-    collections::HashSet,
-    ops::Range,
-    path::Path,
-    sync::Arc,
-};
-use tree_sitter::{Parser, QueryCursor};
-
-#[derive(Debug, PartialEq, Eq, Clone, Hash)]
-pub struct SpanDigest(pub [u8; 20]);
-
-impl FromSql for SpanDigest {
-    fn column_result(value: ValueRef) -> FromSqlResult<Self> {
-        let blob = value.as_blob()?;
-        let bytes =
-            blob.try_into()
-                .map_err(|_| rusqlite::types::FromSqlError::InvalidBlobSize {
-                    expected_size: 20,
-                    blob_size: blob.len(),
-                })?;
-        return Ok(SpanDigest(bytes));
-    }
-}
-
-impl ToSql for SpanDigest {
-    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput> {
-        self.0.to_sql()
-    }
-}
-
-impl From<&'_ str> for SpanDigest {
-    fn from(value: &'_ str) -> Self {
-        let mut sha1 = Sha1::new();
-        sha1.update(value);
-        Self(sha1.finalize().into())
-    }
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct Span {
-    pub name: String,
-    pub range: Range<usize>,
-    pub content: String,
-    pub embedding: Option<Embedding>,
-    pub digest: SpanDigest,
-    pub token_count: usize,
-}
-
-const CODE_CONTEXT_TEMPLATE: &str =
-    "The below code snippet is from file '<path>'\n\n```<language>\n<item>\n```";
-const ENTIRE_FILE_TEMPLATE: &str =
-    "The below snippet is from file '<path>'\n\n```<language>\n<item>\n```";
-const MARKDOWN_CONTEXT_TEMPLATE: &str = "The below file contents is from file '<path>'\n\n<item>";
-pub const PARSEABLE_ENTIRE_FILE_TYPES: &[&str] = &[
-    "TOML", "YAML", "CSS", "HEEX", "ERB", "SVELTE", "HTML", "Scheme",
-];
-
-pub struct CodeContextRetriever {
-    pub parser: Parser,
-    pub cursor: QueryCursor,
-    pub embedding_provider: Arc<dyn EmbeddingProvider>,
-}
-
-// Every match has an item, this represents the fundamental treesitter symbol and anchors the search
-// Every match has one or more 'name' captures. These indicate the display range of the item for deduplication.
-// If there are preceeding comments, we track this with a context capture
-// If there is a piece that should be collapsed in hierarchical queries, we capture it with a collapse capture
-// If there is a piece that should be kept inside a collapsed node, we capture it with a keep capture
-#[derive(Debug, Clone)]
-pub struct CodeContextMatch {
-    pub start_col: usize,
-    pub item_range: Option<Range<usize>>,
-    pub name_range: Option<Range<usize>>,
-    pub context_ranges: Vec<Range<usize>>,
-    pub collapse_ranges: Vec<Range<usize>>,
-}
-
-impl CodeContextRetriever {
-    pub fn new(embedding_provider: Arc<dyn EmbeddingProvider>) -> Self {
-        Self {
-            parser: Parser::new(),
-            cursor: QueryCursor::new(),
-            embedding_provider,
-        }
-    }
-
-    fn parse_entire_file(
-        &self,
-        relative_path: Option<&Path>,
-        language_name: Arc<str>,
-        content: &str,
-    ) -> Result<Vec<Span>> {
-        let document_span = ENTIRE_FILE_TEMPLATE
-            .replace(
-                "<path>",
-                &relative_path.map_or(Cow::Borrowed("untitled"), |path| path.to_string_lossy()),
-            )
-            .replace("<language>", language_name.as_ref())
-            .replace("<item>", &content);
-        let digest = SpanDigest::from(document_span.as_str());
-        let model = self.embedding_provider.base_model();
-        let document_span = model.truncate(
-            &document_span,
-            model.capacity()?,
-            ai::models::TruncationDirection::End,
-        )?;
-        let token_count = model.count_tokens(&document_span)?;
-
-        Ok(vec![Span {
-            range: 0..content.len(),
-            content: document_span,
-            embedding: Default::default(),
-            name: language_name.to_string(),
-            digest,
-            token_count,
-        }])
-    }
-
-    fn parse_markdown_file(
-        &self,
-        relative_path: Option<&Path>,
-        content: &str,
-    ) -> Result<Vec<Span>> {
-        let document_span = MARKDOWN_CONTEXT_TEMPLATE
-            .replace(
-                "<path>",
-                &relative_path.map_or(Cow::Borrowed("untitled"), |path| path.to_string_lossy()),
-            )
-            .replace("<item>", &content);
-        let digest = SpanDigest::from(document_span.as_str());
-
-        let model = self.embedding_provider.base_model();
-        let document_span = model.truncate(
-            &document_span,
-            model.capacity()?,
-            ai::models::TruncationDirection::End,
-        )?;
-        let token_count = model.count_tokens(&document_span)?;
-
-        Ok(vec![Span {
-            range: 0..content.len(),
-            content: document_span,
-            embedding: None,
-            name: "Markdown".to_string(),
-            digest,
-            token_count,
-        }])
-    }
-
-    fn get_matches_in_file(
-        &mut self,
-        content: &str,
-        grammar: &Arc<Grammar>,
-    ) -> Result<Vec<CodeContextMatch>> {
-        let embedding_config = grammar
-            .embedding_config
-            .as_ref()
-            .ok_or_else(|| anyhow!("no embedding queries"))?;
-        self.parser.set_language(&grammar.ts_language).unwrap();
-
-        let tree = self
-            .parser
-            .parse(&content, None)
-            .ok_or_else(|| anyhow!("parsing failed"))?;
-
-        let mut captures: Vec<CodeContextMatch> = Vec::new();
-        let mut collapse_ranges: Vec<Range<usize>> = Vec::new();
-        let mut keep_ranges: Vec<Range<usize>> = Vec::new();
-        for mat in self.cursor.matches(
-            &embedding_config.query,
-            tree.root_node(),
-            content.as_bytes(),
-        ) {
-            let mut start_col = 0;
-            let mut item_range: Option<Range<usize>> = None;
-            let mut name_range: Option<Range<usize>> = None;
-            let mut context_ranges: Vec<Range<usize>> = Vec::new();
-            collapse_ranges.clear();
-            keep_ranges.clear();
-            for capture in mat.captures {
-                if capture.index == embedding_config.item_capture_ix {
-                    item_range = Some(capture.node.byte_range());
-                    start_col = capture.node.start_position().column;
-                } else if Some(capture.index) == embedding_config.name_capture_ix {
-                    name_range = Some(capture.node.byte_range());
-                } else if Some(capture.index) == embedding_config.context_capture_ix {
-                    context_ranges.push(capture.node.byte_range());
-                } else if Some(capture.index) == embedding_config.collapse_capture_ix {
-                    collapse_ranges.push(capture.node.byte_range());
-                } else if Some(capture.index) == embedding_config.keep_capture_ix {
-                    keep_ranges.push(capture.node.byte_range());
-                }
-            }
-
-            captures.push(CodeContextMatch {
-                start_col,
-                item_range,
-                name_range,
-                context_ranges,
-                collapse_ranges: subtract_ranges(&collapse_ranges, &keep_ranges),
-            });
-        }
-        Ok(captures)
-    }
-
-    pub fn parse_file_with_template(
-        &mut self,
-        relative_path: Option<&Path>,
-        content: &str,
-        language: Arc<Language>,
-    ) -> Result<Vec<Span>> {
-        let language_name = language.name();
-
-        if PARSEABLE_ENTIRE_FILE_TYPES.contains(&language_name.as_ref()) {
-            return self.parse_entire_file(relative_path, language_name, &content);
-        } else if ["Markdown", "Plain Text"].contains(&language_name.as_ref()) {
-            return self.parse_markdown_file(relative_path, &content);
-        }
-
-        let mut spans = self.parse_file(content, language)?;
-        for span in &mut spans {
-            let document_content = CODE_CONTEXT_TEMPLATE
-                .replace(
-                    "<path>",
-                    &relative_path.map_or(Cow::Borrowed("untitled"), |path| path.to_string_lossy()),
-                )
-                .replace("<language>", language_name.as_ref())
-                .replace("item", &span.content);
-
-            let model = self.embedding_provider.base_model();
-            let document_content = model.truncate(
-                &document_content,
-                model.capacity()?,
-                TruncationDirection::End,
-            )?;
-            let token_count = model.count_tokens(&document_content)?;
-
-            span.content = document_content;
-            span.token_count = token_count;
-        }
-        Ok(spans)
-    }
-
-    pub fn parse_file(&mut self, content: &str, language: Arc<Language>) -> Result<Vec<Span>> {
-        let grammar = language
-            .grammar()
-            .ok_or_else(|| anyhow!("no grammar for language"))?;
-
-        // Iterate through query matches
-        let matches = self.get_matches_in_file(content, grammar)?;
-
-        let language_scope = language.default_scope();
-        let placeholder = language_scope.collapsed_placeholder();
-
-        let mut spans = Vec::new();
-        let mut collapsed_ranges_within = Vec::new();
-        let mut parsed_name_ranges = HashSet::new();
-        for (i, context_match) in matches.iter().enumerate() {
-            // Items which are collapsible but not embeddable have no item range
-            let item_range = if let Some(item_range) = context_match.item_range.clone() {
-                item_range
-            } else {
-                continue;
-            };
-
-            // Checks for deduplication
-            let name;
-            if let Some(name_range) = context_match.name_range.clone() {
-                name = content
-                    .get(name_range.clone())
-                    .map_or(String::new(), |s| s.to_string());
-                if parsed_name_ranges.contains(&name_range) {
-                    continue;
-                }
-                parsed_name_ranges.insert(name_range);
-            } else {
-                name = String::new();
-            }
-
-            collapsed_ranges_within.clear();
-            'outer: for remaining_match in &matches[(i + 1)..] {
-                for collapsed_range in &remaining_match.collapse_ranges {
-                    if item_range.start <= collapsed_range.start
-                        && item_range.end >= collapsed_range.end
-                    {
-                        collapsed_ranges_within.push(collapsed_range.clone());
-                    } else {
-                        break 'outer;
-                    }
-                }
-            }
-
-            collapsed_ranges_within.sort_by_key(|r| (r.start, Reverse(r.end)));
-
-            let mut span_content = String::new();
-            for context_range in &context_match.context_ranges {
-                add_content_from_range(
-                    &mut span_content,
-                    content,
-                    context_range.clone(),
-                    context_match.start_col,
-                );
-                span_content.push_str("\n");
-            }
-
-            let mut offset = item_range.start;
-            for collapsed_range in &collapsed_ranges_within {
-                if collapsed_range.start > offset {
-                    add_content_from_range(
-                        &mut span_content,
-                        content,
-                        offset..collapsed_range.start,
-                        context_match.start_col,
-                    );
-                    offset = collapsed_range.start;
-                }
-
-                if collapsed_range.end > offset {
-                    span_content.push_str(placeholder);
-                    offset = collapsed_range.end;
-                }
-            }
-
-            if offset < item_range.end {
-                add_content_from_range(
-                    &mut span_content,
-                    content,
-                    offset..item_range.end,
-                    context_match.start_col,
-                );
-            }
-
-            let sha1 = SpanDigest::from(span_content.as_str());
-            spans.push(Span {
-                name,
-                content: span_content,
-                range: item_range.clone(),
-                embedding: None,
-                digest: sha1,
-                token_count: 0,
-            })
-        }
-
-        return Ok(spans);
-    }
-}
-
-pub(crate) fn subtract_ranges(
-    ranges: &[Range<usize>],
-    ranges_to_subtract: &[Range<usize>],
-) -> Vec<Range<usize>> {
-    let mut result = Vec::new();
-
-    let mut ranges_to_subtract = ranges_to_subtract.iter().peekable();
-
-    for range in ranges {
-        let mut offset = range.start;
-
-        while offset < range.end {
-            if let Some(range_to_subtract) = ranges_to_subtract.peek() {
-                if offset < range_to_subtract.start {
-                    let next_offset = cmp::min(range_to_subtract.start, range.end);
-                    result.push(offset..next_offset);
-                    offset = next_offset;
-                } else {
-                    let next_offset = cmp::min(range_to_subtract.end, range.end);
-                    offset = next_offset;
-                }
-
-                if offset >= range_to_subtract.end {
-                    ranges_to_subtract.next();
-                }
-            } else {
-                result.push(offset..range.end);
-                offset = range.end;
-            }
-        }
-    }
-
-    result
-}
-
-fn add_content_from_range(
-    output: &mut String,
-    content: &str,
-    range: Range<usize>,
-    start_col: usize,
-) {
-    for mut line in content.get(range.clone()).unwrap_or("").lines() {
-        for _ in 0..start_col {
-            if line.starts_with(' ') {
-                line = &line[1..];
-            } else {
-                break;
-            }
-        }
-        output.push_str(line);
-        output.push('\n');
-    }
-    output.pop();
-}

crates/semantic_index2/src/semantic_index.rs 🔗

@@ -1,1280 +0,0 @@
-mod db;
-mod embedding_queue;
-mod parsing;
-pub mod semantic_index_settings;
-
-#[cfg(test)]
-mod semantic_index_tests;
-
-use crate::semantic_index_settings::SemanticIndexSettings;
-use ai::embedding::{Embedding, EmbeddingProvider};
-use ai::providers::open_ai::OpenAIEmbeddingProvider;
-use anyhow::{anyhow, Context as _, Result};
-use collections::{BTreeMap, HashMap, HashSet};
-use db::VectorDatabase;
-use embedding_queue::{EmbeddingQueue, FileToEmbed};
-use futures::{future, FutureExt, StreamExt};
-use gpui::{
-    AppContext, AsyncAppContext, BorrowWindow, Context, Model, ModelContext, Task, ViewContext,
-    WeakModel,
-};
-use language::{Anchor, Bias, Buffer, Language, LanguageRegistry};
-use lazy_static::lazy_static;
-use ordered_float::OrderedFloat;
-use parking_lot::Mutex;
-use parsing::{CodeContextRetriever, Span, SpanDigest, PARSEABLE_ENTIRE_FILE_TYPES};
-use postage::watch;
-use project::{Fs, PathChange, Project, ProjectEntryId, Worktree, WorktreeId};
-use settings::Settings;
-use smol::channel;
-use std::{
-    cmp::Reverse,
-    env,
-    future::Future,
-    mem,
-    ops::Range,
-    path::{Path, PathBuf},
-    sync::{Arc, Weak},
-    time::{Duration, Instant, SystemTime},
-};
-use util::paths::PathMatcher;
-use util::{channel::RELEASE_CHANNEL_NAME, http::HttpClient, paths::EMBEDDINGS_DIR, ResultExt};
-use workspace::Workspace;
-
-const SEMANTIC_INDEX_VERSION: usize = 11;
-const BACKGROUND_INDEXING_DELAY: Duration = Duration::from_secs(5 * 60);
-const EMBEDDING_QUEUE_FLUSH_TIMEOUT: Duration = Duration::from_millis(250);
-
-lazy_static! {
-    static ref OPENAI_API_KEY: Option<String> = env::var("OPENAI_API_KEY").ok();
-}
-
-pub fn init(
-    fs: Arc<dyn Fs>,
-    http_client: Arc<dyn HttpClient>,
-    language_registry: Arc<LanguageRegistry>,
-    cx: &mut AppContext,
-) {
-    SemanticIndexSettings::register(cx);
-
-    let db_file_path = EMBEDDINGS_DIR
-        .join(Path::new(RELEASE_CHANNEL_NAME.as_str()))
-        .join("embeddings_db");
-
-    cx.observe_new_views(
-        |workspace: &mut Workspace, cx: &mut ViewContext<Workspace>| {
-            let Some(semantic_index) = SemanticIndex::global(cx) else {
-                return;
-            };
-            let project = workspace.project().clone();
-
-            if project.read(cx).is_local() {
-                cx.app_mut()
-                    .spawn(|mut cx| async move {
-                        let previously_indexed = semantic_index
-                            .update(&mut cx, |index, cx| {
-                                index.project_previously_indexed(&project, cx)
-                            })?
-                            .await?;
-                        if previously_indexed {
-                            semantic_index
-                                .update(&mut cx, |index, cx| index.index_project(project, cx))?
-                                .await?;
-                        }
-                        anyhow::Ok(())
-                    })
-                    .detach_and_log_err(cx);
-            }
-        },
-    )
-    .detach();
-
-    cx.spawn(move |cx| async move {
-        let semantic_index = SemanticIndex::new(
-            fs,
-            db_file_path,
-            Arc::new(OpenAIEmbeddingProvider::new(
-                http_client,
-                cx.background_executor().clone(),
-            )),
-            language_registry,
-            cx.clone(),
-        )
-        .await?;
-
-        cx.update(|cx| cx.set_global(semantic_index.clone()))?;
-
-        anyhow::Ok(())
-    })
-    .detach();
-}
-
-#[derive(Copy, Clone, Debug)]
-pub enum SemanticIndexStatus {
-    NotAuthenticated,
-    NotIndexed,
-    Indexed,
-    Indexing {
-        remaining_files: usize,
-        rate_limit_expiry: Option<Instant>,
-    },
-}
-
-pub struct SemanticIndex {
-    fs: Arc<dyn Fs>,
-    db: VectorDatabase,
-    embedding_provider: Arc<dyn EmbeddingProvider>,
-    language_registry: Arc<LanguageRegistry>,
-    parsing_files_tx: channel::Sender<(Arc<HashMap<SpanDigest, Embedding>>, PendingFile)>,
-    _embedding_task: Task<()>,
-    _parsing_files_tasks: Vec<Task<()>>,
-    projects: HashMap<WeakModel<Project>, ProjectState>,
-}
-
-struct ProjectState {
-    worktrees: HashMap<WorktreeId, WorktreeState>,
-    pending_file_count_rx: watch::Receiver<usize>,
-    pending_file_count_tx: Arc<Mutex<watch::Sender<usize>>>,
-    pending_index: usize,
-    _subscription: gpui::Subscription,
-    _observe_pending_file_count: Task<()>,
-}
-
-enum WorktreeState {
-    Registering(RegisteringWorktreeState),
-    Registered(RegisteredWorktreeState),
-}
-
-impl WorktreeState {
-    fn is_registered(&self) -> bool {
-        matches!(self, Self::Registered(_))
-    }
-
-    fn paths_changed(
-        &mut self,
-        changes: Arc<[(Arc<Path>, ProjectEntryId, PathChange)]>,
-        worktree: &Worktree,
-    ) {
-        let changed_paths = match self {
-            Self::Registering(state) => &mut state.changed_paths,
-            Self::Registered(state) => &mut state.changed_paths,
-        };
-
-        for (path, entry_id, change) in changes.iter() {
-            let Some(entry) = worktree.entry_for_id(*entry_id) else {
-                continue;
-            };
-            if entry.is_ignored || entry.is_symlink || entry.is_external || entry.is_dir() {
-                continue;
-            }
-            changed_paths.insert(
-                path.clone(),
-                ChangedPathInfo {
-                    mtime: entry.mtime,
-                    is_deleted: *change == PathChange::Removed,
-                },
-            );
-        }
-    }
-}
-
-struct RegisteringWorktreeState {
-    changed_paths: BTreeMap<Arc<Path>, ChangedPathInfo>,
-    done_rx: watch::Receiver<Option<()>>,
-    _registration: Task<()>,
-}
-
-impl RegisteringWorktreeState {
-    fn done(&self) -> impl Future<Output = ()> {
-        let mut done_rx = self.done_rx.clone();
-        async move {
-            while let Some(result) = done_rx.next().await {
-                if result.is_some() {
-                    break;
-                }
-            }
-        }
-    }
-}
-
-struct RegisteredWorktreeState {
-    db_id: i64,
-    changed_paths: BTreeMap<Arc<Path>, ChangedPathInfo>,
-}
-
-struct ChangedPathInfo {
-    mtime: SystemTime,
-    is_deleted: bool,
-}
-
-#[derive(Clone)]
-pub struct JobHandle {
-    /// The outer Arc is here to count the clones of a JobHandle instance;
-    /// when the last handle to a given job is dropped, we decrement a counter (just once).
-    tx: Arc<Weak<Mutex<watch::Sender<usize>>>>,
-}
-
-impl JobHandle {
-    fn new(tx: &Arc<Mutex<watch::Sender<usize>>>) -> Self {
-        *tx.lock().borrow_mut() += 1;
-        Self {
-            tx: Arc::new(Arc::downgrade(&tx)),
-        }
-    }
-}
-
-impl ProjectState {
-    fn new(subscription: gpui::Subscription, cx: &mut ModelContext<SemanticIndex>) -> Self {
-        let (pending_file_count_tx, pending_file_count_rx) = watch::channel_with(0);
-        let pending_file_count_tx = Arc::new(Mutex::new(pending_file_count_tx));
-        Self {
-            worktrees: Default::default(),
-            pending_file_count_rx: pending_file_count_rx.clone(),
-            pending_file_count_tx,
-            pending_index: 0,
-            _subscription: subscription,
-            _observe_pending_file_count: cx.spawn({
-                let mut pending_file_count_rx = pending_file_count_rx.clone();
-                |this, mut cx| async move {
-                    while let Some(_) = pending_file_count_rx.next().await {
-                        if this.update(&mut cx, |_, cx| cx.notify()).is_err() {
-                            break;
-                        }
-                    }
-                }
-            }),
-        }
-    }
-
-    fn worktree_id_for_db_id(&self, id: i64) -> Option<WorktreeId> {
-        self.worktrees
-            .iter()
-            .find_map(|(worktree_id, worktree_state)| match worktree_state {
-                WorktreeState::Registered(state) if state.db_id == id => Some(*worktree_id),
-                _ => None,
-            })
-    }
-}
-
-#[derive(Clone)]
-pub struct PendingFile {
-    worktree_db_id: i64,
-    relative_path: Arc<Path>,
-    absolute_path: PathBuf,
-    language: Option<Arc<Language>>,
-    modified_time: SystemTime,
-    job_handle: JobHandle,
-}
-
-#[derive(Clone)]
-pub struct SearchResult {
-    pub buffer: Model<Buffer>,
-    pub range: Range<Anchor>,
-    pub similarity: OrderedFloat<f32>,
-}
-
-impl SemanticIndex {
-    pub fn global(cx: &mut AppContext) -> Option<Model<SemanticIndex>> {
-        if cx.has_global::<Model<Self>>() {
-            Some(cx.global::<Model<SemanticIndex>>().clone())
-        } else {
-            None
-        }
-    }
-
-    pub fn authenticate(&mut self, cx: &mut AppContext) -> bool {
-        if !self.embedding_provider.has_credentials() {
-            self.embedding_provider.retrieve_credentials(cx);
-        } else {
-            return true;
-        }
-
-        self.embedding_provider.has_credentials()
-    }
-
-    pub fn is_authenticated(&self) -> bool {
-        self.embedding_provider.has_credentials()
-    }
-
-    pub fn enabled(cx: &AppContext) -> bool {
-        SemanticIndexSettings::get_global(cx).enabled
-    }
-
-    pub fn status(&self, project: &Model<Project>) -> SemanticIndexStatus {
-        if !self.is_authenticated() {
-            return SemanticIndexStatus::NotAuthenticated;
-        }
-
-        if let Some(project_state) = self.projects.get(&project.downgrade()) {
-            if project_state
-                .worktrees
-                .values()
-                .all(|worktree| worktree.is_registered())
-                && project_state.pending_index == 0
-            {
-                SemanticIndexStatus::Indexed
-            } else {
-                SemanticIndexStatus::Indexing {
-                    remaining_files: project_state.pending_file_count_rx.borrow().clone(),
-                    rate_limit_expiry: self.embedding_provider.rate_limit_expiration(),
-                }
-            }
-        } else {
-            SemanticIndexStatus::NotIndexed
-        }
-    }
-
-    pub async fn new(
-        fs: Arc<dyn Fs>,
-        database_path: PathBuf,
-        embedding_provider: Arc<dyn EmbeddingProvider>,
-        language_registry: Arc<LanguageRegistry>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Model<Self>> {
-        let t0 = Instant::now();
-        let database_path = Arc::from(database_path);
-        let db = VectorDatabase::new(fs.clone(), database_path, cx.background_executor().clone())
-            .await?;
-
-        log::trace!(
-            "db initialization took {:?} milliseconds",
-            t0.elapsed().as_millis()
-        );
-
-        cx.new_model(|cx| {
-            let t0 = Instant::now();
-            let embedding_queue =
-                EmbeddingQueue::new(embedding_provider.clone(), cx.background_executor().clone());
-            let _embedding_task = cx.background_executor().spawn({
-                let embedded_files = embedding_queue.finished_files();
-                let db = db.clone();
-                async move {
-                    while let Ok(file) = embedded_files.recv().await {
-                        db.insert_file(file.worktree_id, file.path, file.mtime, file.spans)
-                            .await
-                            .log_err();
-                    }
-                }
-            });
-
-            // Parse files into embeddable spans.
-            let (parsing_files_tx, parsing_files_rx) =
-                channel::unbounded::<(Arc<HashMap<SpanDigest, Embedding>>, PendingFile)>();
-            let embedding_queue = Arc::new(Mutex::new(embedding_queue));
-            let mut _parsing_files_tasks = Vec::new();
-            for _ in 0..cx.background_executor().num_cpus() {
-                let fs = fs.clone();
-                let mut parsing_files_rx = parsing_files_rx.clone();
-                let embedding_provider = embedding_provider.clone();
-                let embedding_queue = embedding_queue.clone();
-                let background = cx.background_executor().clone();
-                _parsing_files_tasks.push(cx.background_executor().spawn(async move {
-                    let mut retriever = CodeContextRetriever::new(embedding_provider.clone());
-                    loop {
-                        let mut timer = background.timer(EMBEDDING_QUEUE_FLUSH_TIMEOUT).fuse();
-                        let mut next_file_to_parse = parsing_files_rx.next().fuse();
-                        futures::select_biased! {
-                            next_file_to_parse = next_file_to_parse => {
-                                if let Some((embeddings_for_digest, pending_file)) = next_file_to_parse {
-                                    Self::parse_file(
-                                        &fs,
-                                        pending_file,
-                                        &mut retriever,
-                                        &embedding_queue,
-                                        &embeddings_for_digest,
-                                    )
-                                    .await
-                                } else {
-                                    break;
-                                }
-                            },
-                            _ = timer => {
-                                embedding_queue.lock().flush();
-                            }
-                        }
-                    }
-                }));
-            }
-
-            log::trace!(
-                "semantic index task initialization took {:?} milliseconds",
-                t0.elapsed().as_millis()
-            );
-            Self {
-                fs,
-                db,
-                embedding_provider,
-                language_registry,
-                parsing_files_tx,
-                _embedding_task,
-                _parsing_files_tasks,
-                projects: Default::default(),
-            }
-        })
-    }
-
-    async fn parse_file(
-        fs: &Arc<dyn Fs>,
-        pending_file: PendingFile,
-        retriever: &mut CodeContextRetriever,
-        embedding_queue: &Arc<Mutex<EmbeddingQueue>>,
-        embeddings_for_digest: &HashMap<SpanDigest, Embedding>,
-    ) {
-        let Some(language) = pending_file.language else {
-            return;
-        };
-
-        if let Some(content) = fs.load(&pending_file.absolute_path).await.log_err() {
-            if let Some(mut spans) = retriever
-                .parse_file_with_template(Some(&pending_file.relative_path), &content, language)
-                .log_err()
-            {
-                log::trace!(
-                    "parsed path {:?}: {} spans",
-                    pending_file.relative_path,
-                    spans.len()
-                );
-
-                for span in &mut spans {
-                    if let Some(embedding) = embeddings_for_digest.get(&span.digest) {
-                        span.embedding = Some(embedding.to_owned());
-                    }
-                }
-
-                embedding_queue.lock().push(FileToEmbed {
-                    worktree_id: pending_file.worktree_db_id,
-                    path: pending_file.relative_path,
-                    mtime: pending_file.modified_time,
-                    job_handle: pending_file.job_handle,
-                    spans,
-                });
-            }
-        }
-    }
-
-    pub fn project_previously_indexed(
-        &mut self,
-        project: &Model<Project>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<bool>> {
-        let worktrees_indexed_previously = project
-            .read(cx)
-            .worktrees()
-            .map(|worktree| {
-                self.db
-                    .worktree_previously_indexed(&worktree.read(cx).abs_path())
-            })
-            .collect::<Vec<_>>();
-        cx.spawn(|_, _cx| async move {
-            let worktree_indexed_previously =
-                futures::future::join_all(worktrees_indexed_previously).await;
-
-            Ok(worktree_indexed_previously
-                .iter()
-                .filter(|worktree| worktree.is_ok())
-                .all(|v| v.as_ref().log_err().is_some_and(|v| v.to_owned())))
-        })
-    }
-
-    fn project_entries_changed(
-        &mut self,
-        project: Model<Project>,
-        worktree_id: WorktreeId,
-        changes: Arc<[(Arc<Path>, ProjectEntryId, PathChange)]>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        let Some(worktree) = project.read(cx).worktree_for_id(worktree_id.clone(), cx) else {
-            return;
-        };
-        let project = project.downgrade();
-        let Some(project_state) = self.projects.get_mut(&project) else {
-            return;
-        };
-
-        let worktree = worktree.read(cx);
-        let worktree_state =
-            if let Some(worktree_state) = project_state.worktrees.get_mut(&worktree_id) {
-                worktree_state
-            } else {
-                return;
-            };
-        worktree_state.paths_changed(changes, worktree);
-        if let WorktreeState::Registered(_) = worktree_state {
-            cx.spawn(|this, mut cx| async move {
-                cx.background_executor()
-                    .timer(BACKGROUND_INDEXING_DELAY)
-                    .await;
-                if let Some((this, project)) = this.upgrade().zip(project.upgrade()) {
-                    this.update(&mut cx, |this, cx| {
-                        this.index_project(project, cx).detach_and_log_err(cx)
-                    })?;
-                }
-                anyhow::Ok(())
-            })
-            .detach_and_log_err(cx);
-        }
-    }
-
-    fn register_worktree(
-        &mut self,
-        project: Model<Project>,
-        worktree: Model<Worktree>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        let project = project.downgrade();
-        let project_state = if let Some(project_state) = self.projects.get_mut(&project) {
-            project_state
-        } else {
-            return;
-        };
-        let worktree = if let Some(worktree) = worktree.read(cx).as_local() {
-            worktree
-        } else {
-            return;
-        };
-        let worktree_abs_path = worktree.abs_path().clone();
-        let scan_complete = worktree.scan_complete();
-        let worktree_id = worktree.id();
-        let db = self.db.clone();
-        let language_registry = self.language_registry.clone();
-        let (mut done_tx, done_rx) = watch::channel();
-        let registration = cx.spawn(|this, mut cx| {
-            async move {
-                let register = async {
-                    scan_complete.await;
-                    let db_id = db.find_or_create_worktree(worktree_abs_path).await?;
-                    let mut file_mtimes = db.get_file_mtimes(db_id).await?;
-                    let worktree = if let Some(project) = project.upgrade() {
-                        project
-                            .read_with(&cx, |project, cx| project.worktree_for_id(worktree_id, cx))
-                            .ok()
-                            .flatten()
-                            .context("worktree not found")?
-                    } else {
-                        return anyhow::Ok(());
-                    };
-                    let worktree = worktree.read_with(&cx, |worktree, _| worktree.snapshot())?;
-                    let mut changed_paths = cx
-                        .background_executor()
-                        .spawn(async move {
-                            let mut changed_paths = BTreeMap::new();
-                            for file in worktree.files(false, 0) {
-                                let absolute_path = worktree.absolutize(&file.path);
-
-                                if file.is_external || file.is_ignored || file.is_symlink {
-                                    continue;
-                                }
-
-                                if let Ok(language) = language_registry
-                                    .language_for_file(&absolute_path, None)
-                                    .await
-                                {
-                                    // Test if file is valid parseable file
-                                    if !PARSEABLE_ENTIRE_FILE_TYPES
-                                        .contains(&language.name().as_ref())
-                                        && &language.name().as_ref() != &"Markdown"
-                                        && language
-                                            .grammar()
-                                            .and_then(|grammar| grammar.embedding_config.as_ref())
-                                            .is_none()
-                                    {
-                                        continue;
-                                    }
-
-                                    let stored_mtime = file_mtimes.remove(&file.path.to_path_buf());
-                                    let already_stored = stored_mtime
-                                        .map_or(false, |existing_mtime| {
-                                            existing_mtime == file.mtime
-                                        });
-
-                                    if !already_stored {
-                                        changed_paths.insert(
-                                            file.path.clone(),
-                                            ChangedPathInfo {
-                                                mtime: file.mtime,
-                                                is_deleted: false,
-                                            },
-                                        );
-                                    }
-                                }
-                            }
-
-                            // Clean up entries from database that are no longer in the worktree.
-                            for (path, mtime) in file_mtimes {
-                                changed_paths.insert(
-                                    path.into(),
-                                    ChangedPathInfo {
-                                        mtime,
-                                        is_deleted: true,
-                                    },
-                                );
-                            }
-
-                            anyhow::Ok(changed_paths)
-                        })
-                        .await?;
-                    this.update(&mut cx, |this, cx| {
-                        let project_state = this
-                            .projects
-                            .get_mut(&project)
-                            .context("project not registered")?;
-                        let project = project.upgrade().context("project was dropped")?;
-
-                        if let Some(WorktreeState::Registering(state)) =
-                            project_state.worktrees.remove(&worktree_id)
-                        {
-                            changed_paths.extend(state.changed_paths);
-                        }
-                        project_state.worktrees.insert(
-                            worktree_id,
-                            WorktreeState::Registered(RegisteredWorktreeState {
-                                db_id,
-                                changed_paths,
-                            }),
-                        );
-                        this.index_project(project, cx).detach_and_log_err(cx);
-
-                        anyhow::Ok(())
-                    })??;
-
-                    anyhow::Ok(())
-                };
-
-                if register.await.log_err().is_none() {
-                    // Stop tracking this worktree if the registration failed.
-                    this.update(&mut cx, |this, _| {
-                        this.projects.get_mut(&project).map(|project_state| {
-                            project_state.worktrees.remove(&worktree_id);
-                        });
-                    })
-                    .ok();
-                }
-
-                *done_tx.borrow_mut() = Some(());
-            }
-        });
-        project_state.worktrees.insert(
-            worktree_id,
-            WorktreeState::Registering(RegisteringWorktreeState {
-                changed_paths: Default::default(),
-                done_rx,
-                _registration: registration,
-            }),
-        );
-    }
-
-    fn project_worktrees_changed(&mut self, project: Model<Project>, cx: &mut ModelContext<Self>) {
-        let project_state = if let Some(project_state) = self.projects.get_mut(&project.downgrade())
-        {
-            project_state
-        } else {
-            return;
-        };
-
-        let mut worktrees = project
-            .read(cx)
-            .worktrees()
-            .filter(|worktree| worktree.read(cx).is_local())
-            .collect::<Vec<_>>();
-        let worktree_ids = worktrees
-            .iter()
-            .map(|worktree| worktree.read(cx).id())
-            .collect::<HashSet<_>>();
-
-        // Remove worktrees that are no longer present
-        project_state
-            .worktrees
-            .retain(|worktree_id, _| worktree_ids.contains(worktree_id));
-
-        // Register new worktrees
-        worktrees.retain(|worktree| {
-            let worktree_id = worktree.read(cx).id();
-            !project_state.worktrees.contains_key(&worktree_id)
-        });
-        for worktree in worktrees {
-            self.register_worktree(project.clone(), worktree, cx);
-        }
-    }
-
-    pub fn pending_file_count(&self, project: &Model<Project>) -> Option<watch::Receiver<usize>> {
-        Some(
-            self.projects
-                .get(&project.downgrade())?
-                .pending_file_count_rx
-                .clone(),
-        )
-    }
-
-    pub fn search_project(
-        &mut self,
-        project: Model<Project>,
-        query: String,
-        limit: usize,
-        includes: Vec<PathMatcher>,
-        excludes: Vec<PathMatcher>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<SearchResult>>> {
-        if query.is_empty() {
-            return Task::ready(Ok(Vec::new()));
-        }
-
-        let index = self.index_project(project.clone(), cx);
-        let embedding_provider = self.embedding_provider.clone();
-
-        cx.spawn(|this, mut cx| async move {
-            index.await?;
-            let t0 = Instant::now();
-
-            let query = embedding_provider
-                .embed_batch(vec![query])
-                .await?
-                .pop()
-                .context("could not embed query")?;
-            log::trace!("Embedding Search Query: {:?}ms", t0.elapsed().as_millis());
-
-            let search_start = Instant::now();
-            let modified_buffer_results = this.update(&mut cx, |this, cx| {
-                this.search_modified_buffers(
-                    &project,
-                    query.clone(),
-                    limit,
-                    &includes,
-                    &excludes,
-                    cx,
-                )
-            })?;
-            let file_results = this.update(&mut cx, |this, cx| {
-                this.search_files(project, query, limit, includes, excludes, cx)
-            })?;
-            let (modified_buffer_results, file_results) =
-                futures::join!(modified_buffer_results, file_results);
-
-            // Weave together the results from modified buffers and files.
-            let mut results = Vec::new();
-            let mut modified_buffers = HashSet::default();
-            for result in modified_buffer_results.log_err().unwrap_or_default() {
-                modified_buffers.insert(result.buffer.clone());
-                results.push(result);
-            }
-            for result in file_results.log_err().unwrap_or_default() {
-                if !modified_buffers.contains(&result.buffer) {
-                    results.push(result);
-                }
-            }
-            results.sort_by_key(|result| Reverse(result.similarity));
-            results.truncate(limit);
-            log::trace!("Semantic search took {:?}", search_start.elapsed());
-            Ok(results)
-        })
-    }
-
-    pub fn search_files(
-        &mut self,
-        project: Model<Project>,
-        query: Embedding,
-        limit: usize,
-        includes: Vec<PathMatcher>,
-        excludes: Vec<PathMatcher>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<SearchResult>>> {
-        let db_path = self.db.path().clone();
-        let fs = self.fs.clone();
-        cx.spawn(|this, mut cx| async move {
-            let database = VectorDatabase::new(
-                fs.clone(),
-                db_path.clone(),
-                cx.background_executor().clone(),
-            )
-            .await?;
-
-            let worktree_db_ids = this.read_with(&cx, |this, _| {
-                let project_state = this
-                    .projects
-                    .get(&project.downgrade())
-                    .context("project was not indexed")?;
-                let worktree_db_ids = project_state
-                    .worktrees
-                    .values()
-                    .filter_map(|worktree| {
-                        if let WorktreeState::Registered(worktree) = worktree {
-                            Some(worktree.db_id)
-                        } else {
-                            None
-                        }
-                    })
-                    .collect::<Vec<i64>>();
-                anyhow::Ok(worktree_db_ids)
-            })??;
-
-            let file_ids = database
-                .retrieve_included_file_ids(&worktree_db_ids, &includes, &excludes)
-                .await?;
-
-            let batch_n = cx.background_executor().num_cpus();
-            let ids_len = file_ids.clone().len();
-            let minimum_batch_size = 50;
-
-            let batch_size = {
-                let size = ids_len / batch_n;
-                if size < minimum_batch_size {
-                    minimum_batch_size
-                } else {
-                    size
-                }
-            };
-
-            let mut batch_results = Vec::new();
-            for batch in file_ids.chunks(batch_size) {
-                let batch = batch.into_iter().map(|v| *v).collect::<Vec<i64>>();
-                let limit = limit.clone();
-                let fs = fs.clone();
-                let db_path = db_path.clone();
-                let query = query.clone();
-                if let Some(db) =
-                    VectorDatabase::new(fs, db_path.clone(), cx.background_executor().clone())
-                        .await
-                        .log_err()
-                {
-                    batch_results.push(async move {
-                        db.top_k_search(&query, limit, batch.as_slice()).await
-                    });
-                }
-            }
-
-            let batch_results = futures::future::join_all(batch_results).await;
-
-            let mut results = Vec::new();
-            for batch_result in batch_results {
-                if batch_result.is_ok() {
-                    for (id, similarity) in batch_result.unwrap() {
-                        let ix = match results
-                            .binary_search_by_key(&Reverse(similarity), |(_, s)| Reverse(*s))
-                        {
-                            Ok(ix) => ix,
-                            Err(ix) => ix,
-                        };
-
-                        results.insert(ix, (id, similarity));
-                        results.truncate(limit);
-                    }
-                }
-            }
-
-            let ids = results.iter().map(|(id, _)| *id).collect::<Vec<i64>>();
-            let scores = results
-                .into_iter()
-                .map(|(_, score)| score)
-                .collect::<Vec<_>>();
-            let spans = database.spans_for_ids(ids.as_slice()).await?;
-
-            let mut tasks = Vec::new();
-            let mut ranges = Vec::new();
-            let weak_project = project.downgrade();
-            project.update(&mut cx, |project, cx| {
-                let this = this.upgrade().context("index was dropped")?;
-                for (worktree_db_id, file_path, byte_range) in spans {
-                    let project_state =
-                        if let Some(state) = this.read(cx).projects.get(&weak_project) {
-                            state
-                        } else {
-                            return Err(anyhow!("project not added"));
-                        };
-                    if let Some(worktree_id) = project_state.worktree_id_for_db_id(worktree_db_id) {
-                        tasks.push(project.open_buffer((worktree_id, file_path), cx));
-                        ranges.push(byte_range);
-                    }
-                }
-
-                Ok(())
-            })??;
-
-            let buffers = futures::future::join_all(tasks).await;
-            Ok(buffers
-                .into_iter()
-                .zip(ranges)
-                .zip(scores)
-                .filter_map(|((buffer, range), similarity)| {
-                    let buffer = buffer.log_err()?;
-                    let range = buffer
-                        .read_with(&cx, |buffer, _| {
-                            let start = buffer.clip_offset(range.start, Bias::Left);
-                            let end = buffer.clip_offset(range.end, Bias::Right);
-                            buffer.anchor_before(start)..buffer.anchor_after(end)
-                        })
-                        .log_err()?;
-                    Some(SearchResult {
-                        buffer,
-                        range,
-                        similarity,
-                    })
-                })
-                .collect())
-        })
-    }
-
-    fn search_modified_buffers(
-        &self,
-        project: &Model<Project>,
-        query: Embedding,
-        limit: usize,
-        includes: &[PathMatcher],
-        excludes: &[PathMatcher],
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<SearchResult>>> {
-        let modified_buffers = project
-            .read(cx)
-            .opened_buffers()
-            .into_iter()
-            .filter_map(|buffer_handle| {
-                let buffer = buffer_handle.read(cx);
-                let snapshot = buffer.snapshot();
-                let excluded = snapshot.resolve_file_path(cx, false).map_or(false, |path| {
-                    excludes.iter().any(|matcher| matcher.is_match(&path))
-                });
-
-                let included = if includes.len() == 0 {
-                    true
-                } else {
-                    snapshot.resolve_file_path(cx, false).map_or(false, |path| {
-                        includes.iter().any(|matcher| matcher.is_match(&path))
-                    })
-                };
-
-                if buffer.is_dirty() && !excluded && included {
-                    Some((buffer_handle, snapshot))
-                } else {
-                    None
-                }
-            })
-            .collect::<HashMap<_, _>>();
-
-        let embedding_provider = self.embedding_provider.clone();
-        let fs = self.fs.clone();
-        let db_path = self.db.path().clone();
-        let background = cx.background_executor().clone();
-        cx.background_executor().spawn(async move {
-            let db = VectorDatabase::new(fs, db_path.clone(), background).await?;
-            let mut results = Vec::<SearchResult>::new();
-
-            let mut retriever = CodeContextRetriever::new(embedding_provider.clone());
-            for (buffer, snapshot) in modified_buffers {
-                let language = snapshot
-                    .language_at(0)
-                    .cloned()
-                    .unwrap_or_else(|| language::PLAIN_TEXT.clone());
-                let mut spans = retriever
-                    .parse_file_with_template(None, &snapshot.text(), language)
-                    .log_err()
-                    .unwrap_or_default();
-                if Self::embed_spans(&mut spans, embedding_provider.as_ref(), &db)
-                    .await
-                    .log_err()
-                    .is_some()
-                {
-                    for span in spans {
-                        let similarity = span.embedding.unwrap().similarity(&query);
-                        let ix = match results
-                            .binary_search_by_key(&Reverse(similarity), |result| {
-                                Reverse(result.similarity)
-                            }) {
-                            Ok(ix) => ix,
-                            Err(ix) => ix,
-                        };
-
-                        let range = {
-                            let start = snapshot.clip_offset(span.range.start, Bias::Left);
-                            let end = snapshot.clip_offset(span.range.end, Bias::Right);
-                            snapshot.anchor_before(start)..snapshot.anchor_after(end)
-                        };
-
-                        results.insert(
-                            ix,
-                            SearchResult {
-                                buffer: buffer.clone(),
-                                range,
-                                similarity,
-                            },
-                        );
-                        results.truncate(limit);
-                    }
-                }
-            }
-
-            Ok(results)
-        })
-    }
-
-    pub fn index_project(
-        &mut self,
-        project: Model<Project>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        if !self.is_authenticated() {
-            if !self.authenticate(cx) {
-                return Task::ready(Err(anyhow!("user is not authenticated")));
-            }
-        }
-
-        if !self.projects.contains_key(&project.downgrade()) {
-            let subscription = cx.subscribe(&project, |this, project, event, cx| match event {
-                project::Event::WorktreeAdded | project::Event::WorktreeRemoved(_) => {
-                    this.project_worktrees_changed(project.clone(), cx);
-                }
-                project::Event::WorktreeUpdatedEntries(worktree_id, changes) => {
-                    this.project_entries_changed(project, *worktree_id, changes.clone(), cx);
-                }
-                _ => {}
-            });
-            let project_state = ProjectState::new(subscription, cx);
-            self.projects.insert(project.downgrade(), project_state);
-            self.project_worktrees_changed(project.clone(), cx);
-        }
-        let project_state = self.projects.get_mut(&project.downgrade()).unwrap();
-        project_state.pending_index += 1;
-        cx.notify();
-
-        let mut pending_file_count_rx = project_state.pending_file_count_rx.clone();
-        let db = self.db.clone();
-        let language_registry = self.language_registry.clone();
-        let parsing_files_tx = self.parsing_files_tx.clone();
-        let worktree_registration = self.wait_for_worktree_registration(&project, cx);
-
-        cx.spawn(|this, mut cx| async move {
-            worktree_registration.await?;
-
-            let mut pending_files = Vec::new();
-            let mut files_to_delete = Vec::new();
-            this.update(&mut cx, |this, cx| {
-                let project_state = this
-                    .projects
-                    .get_mut(&project.downgrade())
-                    .context("project was dropped")?;
-                let pending_file_count_tx = &project_state.pending_file_count_tx;
-
-                project_state
-                    .worktrees
-                    .retain(|worktree_id, worktree_state| {
-                        let worktree = if let Some(worktree) =
-                            project.read(cx).worktree_for_id(*worktree_id, cx)
-                        {
-                            worktree
-                        } else {
-                            return false;
-                        };
-                        let worktree_state =
-                            if let WorktreeState::Registered(worktree_state) = worktree_state {
-                                worktree_state
-                            } else {
-                                return true;
-                            };
-
-                        worktree_state.changed_paths.retain(|path, info| {
-                            if info.is_deleted {
-                                files_to_delete.push((worktree_state.db_id, path.clone()));
-                            } else {
-                                let absolute_path = worktree.read(cx).absolutize(path);
-                                let job_handle = JobHandle::new(pending_file_count_tx);
-                                pending_files.push(PendingFile {
-                                    absolute_path,
-                                    relative_path: path.clone(),
-                                    language: None,
-                                    job_handle,
-                                    modified_time: info.mtime,
-                                    worktree_db_id: worktree_state.db_id,
-                                });
-                            }
-
-                            false
-                        });
-                        true
-                    });
-
-                anyhow::Ok(())
-            })??;
-
-            cx.background_executor()
-                .spawn(async move {
-                    for (worktree_db_id, path) in files_to_delete {
-                        db.delete_file(worktree_db_id, path).await.log_err();
-                    }
-
-                    let embeddings_for_digest = {
-                        let mut files = HashMap::default();
-                        for pending_file in &pending_files {
-                            files
-                                .entry(pending_file.worktree_db_id)
-                                .or_insert(Vec::new())
-                                .push(pending_file.relative_path.clone());
-                        }
-                        Arc::new(
-                            db.embeddings_for_files(files)
-                                .await
-                                .log_err()
-                                .unwrap_or_default(),
-                        )
-                    };
-
-                    for mut pending_file in pending_files {
-                        if let Ok(language) = language_registry
-                            .language_for_file(&pending_file.relative_path, None)
-                            .await
-                        {
-                            if !PARSEABLE_ENTIRE_FILE_TYPES.contains(&language.name().as_ref())
-                                && &language.name().as_ref() != &"Markdown"
-                                && language
-                                    .grammar()
-                                    .and_then(|grammar| grammar.embedding_config.as_ref())
-                                    .is_none()
-                            {
-                                continue;
-                            }
-                            pending_file.language = Some(language);
-                        }
-                        parsing_files_tx
-                            .try_send((embeddings_for_digest.clone(), pending_file))
-                            .ok();
-                    }
-
-                    // Wait until we're done indexing.
-                    while let Some(count) = pending_file_count_rx.next().await {
-                        if count == 0 {
-                            break;
-                        }
-                    }
-                })
-                .await;
-
-            this.update(&mut cx, |this, cx| {
-                let project_state = this
-                    .projects
-                    .get_mut(&project.downgrade())
-                    .context("project was dropped")?;
-                project_state.pending_index -= 1;
-                cx.notify();
-                anyhow::Ok(())
-            })??;
-
-            Ok(())
-        })
-    }
-
-    fn wait_for_worktree_registration(
-        &self,
-        project: &Model<Project>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        let project = project.downgrade();
-        cx.spawn(|this, cx| async move {
-            loop {
-                let mut pending_worktrees = Vec::new();
-                this.upgrade()
-                    .context("semantic index dropped")?
-                    .read_with(&cx, |this, _| {
-                        if let Some(project) = this.projects.get(&project) {
-                            for worktree in project.worktrees.values() {
-                                if let WorktreeState::Registering(worktree) = worktree {
-                                    pending_worktrees.push(worktree.done());
-                                }
-                            }
-                        }
-                    })?;
-
-                if pending_worktrees.is_empty() {
-                    break;
-                } else {
-                    future::join_all(pending_worktrees).await;
-                }
-            }
-            Ok(())
-        })
-    }
-
-    async fn embed_spans(
-        spans: &mut [Span],
-        embedding_provider: &dyn EmbeddingProvider,
-        db: &VectorDatabase,
-    ) -> Result<()> {
-        let mut batch = Vec::new();
-        let mut batch_tokens = 0;
-        let mut embeddings = Vec::new();
-
-        let digests = spans
-            .iter()
-            .map(|span| span.digest.clone())
-            .collect::<Vec<_>>();
-        let embeddings_for_digests = db
-            .embeddings_for_digests(digests)
-            .await
-            .log_err()
-            .unwrap_or_default();
-
-        for span in &*spans {
-            if embeddings_for_digests.contains_key(&span.digest) {
-                continue;
-            };
-
-            if batch_tokens + span.token_count > embedding_provider.max_tokens_per_batch() {
-                let batch_embeddings = embedding_provider
-                    .embed_batch(mem::take(&mut batch))
-                    .await?;
-                embeddings.extend(batch_embeddings);
-                batch_tokens = 0;
-            }
-
-            batch_tokens += span.token_count;
-            batch.push(span.content.clone());
-        }
-
-        if !batch.is_empty() {
-            let batch_embeddings = embedding_provider
-                .embed_batch(mem::take(&mut batch))
-                .await?;
-
-            embeddings.extend(batch_embeddings);
-        }
-
-        let mut embeddings = embeddings.into_iter();
-        for span in spans {
-            let embedding = if let Some(embedding) = embeddings_for_digests.get(&span.digest) {
-                Some(embedding.clone())
-            } else {
-                embeddings.next()
-            };
-            let embedding = embedding.context("failed to embed spans")?;
-            span.embedding = Some(embedding);
-        }
-        Ok(())
-    }
-}
-
-impl Drop for JobHandle {
-    fn drop(&mut self) {
-        if let Some(inner) = Arc::get_mut(&mut self.tx) {
-            // This is the last instance of the JobHandle (regardless of it's origin - whether it was cloned or not)
-            if let Some(tx) = inner.upgrade() {
-                let mut tx = tx.lock();
-                *tx.borrow_mut() -= 1;
-            }
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-
-    use super::*;
-    #[test]
-    fn test_job_handle() {
-        let (job_count_tx, job_count_rx) = watch::channel_with(0);
-        let tx = Arc::new(Mutex::new(job_count_tx));
-        let job_handle = JobHandle::new(&tx);
-
-        assert_eq!(1, *job_count_rx.borrow());
-        let new_job_handle = job_handle.clone();
-        assert_eq!(1, *job_count_rx.borrow());
-        drop(job_handle);
-        assert_eq!(1, *job_count_rx.borrow());
-        drop(new_job_handle);
-        assert_eq!(0, *job_count_rx.borrow());
-    }
-}

crates/semantic_index2/src/semantic_index_settings.rs 🔗

@@ -1,28 +0,0 @@
-use anyhow;
-use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
-use settings::Settings;
-
-#[derive(Deserialize, Debug)]
-pub struct SemanticIndexSettings {
-    pub enabled: bool,
-}
-
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
-pub struct SemanticIndexSettingsContent {
-    pub enabled: Option<bool>,
-}
-
-impl Settings for SemanticIndexSettings {
-    const KEY: Option<&'static str> = Some("semantic_index");
-
-    type FileContent = SemanticIndexSettingsContent;
-
-    fn load(
-        default_value: &Self::FileContent,
-        user_values: &[&Self::FileContent],
-        _: &mut gpui::AppContext,
-    ) -> anyhow::Result<Self> {
-        Self::load_via_json_merge(default_value, user_values)
-    }
-}

crates/semantic_index2/src/semantic_index_tests.rs 🔗

@@ -1,1697 +0,0 @@
-use crate::{
-    embedding_queue::EmbeddingQueue,
-    parsing::{subtract_ranges, CodeContextRetriever, Span, SpanDigest},
-    semantic_index_settings::SemanticIndexSettings,
-    FileToEmbed, JobHandle, SearchResult, SemanticIndex, EMBEDDING_QUEUE_FLUSH_TIMEOUT,
-};
-use ai::test::FakeEmbeddingProvider;
-
-use gpui::{Task, TestAppContext};
-use language::{Language, LanguageConfig, LanguageRegistry, ToOffset};
-use parking_lot::Mutex;
-use pretty_assertions::assert_eq;
-use project::{project_settings::ProjectSettings, FakeFs, Fs, Project};
-use rand::{rngs::StdRng, Rng};
-use serde_json::json;
-use settings::{Settings, SettingsStore};
-use std::{path::Path, sync::Arc, time::SystemTime};
-use unindent::Unindent;
-use util::{paths::PathMatcher, RandomCharIter};
-
-#[ctor::ctor]
-fn init_logger() {
-    if std::env::var("RUST_LOG").is_ok() {
-        env_logger::init();
-    }
-}
-
-#[gpui::test]
-async fn test_semantic_index(cx: &mut TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.background_executor.clone());
-    fs.insert_tree(
-        "/the-root",
-        json!({
-            "src": {
-                "file1.rs": "
-                    fn aaa() {
-                        println!(\"aaaaaaaaaaaa!\");
-                    }
-
-                    fn zzzzz() {
-                        println!(\"SLEEPING\");
-                    }
-                ".unindent(),
-                "file2.rs": "
-                    fn bbb() {
-                        println!(\"bbbbbbbbbbbbb!\");
-                    }
-                    struct pqpqpqp {}
-                ".unindent(),
-                "file3.toml": "
-                    ZZZZZZZZZZZZZZZZZZ = 5
-                ".unindent(),
-            }
-        }),
-    )
-    .await;
-
-    let languages = Arc::new(LanguageRegistry::new(Task::ready(())));
-    let rust_language = rust_lang();
-    let toml_language = toml_lang();
-    languages.add(rust_language);
-    languages.add(toml_language);
-
-    let db_dir = tempdir::TempDir::new("vector-store").unwrap();
-    let db_path = db_dir.path().join("db.sqlite");
-
-    let embedding_provider = Arc::new(FakeEmbeddingProvider::default());
-    let semantic_index = SemanticIndex::new(
-        fs.clone(),
-        db_path,
-        embedding_provider.clone(),
-        languages,
-        cx.to_async(),
-    )
-    .await
-    .unwrap();
-
-    let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;
-
-    let search_results = semantic_index.update(cx, |store, cx| {
-        store.search_project(
-            project.clone(),
-            "aaaaaabbbbzz".to_string(),
-            5,
-            vec![],
-            vec![],
-            cx,
-        )
-    });
-    let pending_file_count =
-        semantic_index.read_with(cx, |index, _| index.pending_file_count(&project).unwrap());
-    cx.background_executor.run_until_parked();
-    assert_eq!(*pending_file_count.borrow(), 3);
-    cx.background_executor
-        .advance_clock(EMBEDDING_QUEUE_FLUSH_TIMEOUT);
-    assert_eq!(*pending_file_count.borrow(), 0);
-
-    let search_results = search_results.await.unwrap();
-    assert_search_results(
-        &search_results,
-        &[
-            (Path::new("src/file1.rs").into(), 0),
-            (Path::new("src/file2.rs").into(), 0),
-            (Path::new("src/file3.toml").into(), 0),
-            (Path::new("src/file1.rs").into(), 45),
-            (Path::new("src/file2.rs").into(), 45),
-        ],
-        cx,
-    );
-
-    // Test Include Files Functonality
-    let include_files = vec![PathMatcher::new("*.rs").unwrap()];
-    let exclude_files = vec![PathMatcher::new("*.rs").unwrap()];
-    let rust_only_search_results = semantic_index
-        .update(cx, |store, cx| {
-            store.search_project(
-                project.clone(),
-                "aaaaaabbbbzz".to_string(),
-                5,
-                include_files,
-                vec![],
-                cx,
-            )
-        })
-        .await
-        .unwrap();
-
-    assert_search_results(
-        &rust_only_search_results,
-        &[
-            (Path::new("src/file1.rs").into(), 0),
-            (Path::new("src/file2.rs").into(), 0),
-            (Path::new("src/file1.rs").into(), 45),
-            (Path::new("src/file2.rs").into(), 45),
-        ],
-        cx,
-    );
-
-    let no_rust_search_results = semantic_index
-        .update(cx, |store, cx| {
-            store.search_project(
-                project.clone(),
-                "aaaaaabbbbzz".to_string(),
-                5,
-                vec![],
-                exclude_files,
-                cx,
-            )
-        })
-        .await
-        .unwrap();
-
-    assert_search_results(
-        &no_rust_search_results,
-        &[(Path::new("src/file3.toml").into(), 0)],
-        cx,
-    );
-
-    fs.save(
-        "/the-root/src/file2.rs".as_ref(),
-        &"
-            fn dddd() { println!(\"ddddd!\"); }
-            struct pqpqpqp {}
-        "
-        .unindent()
-        .into(),
-        Default::default(),
-    )
-    .await
-    .unwrap();
-
-    cx.background_executor
-        .advance_clock(EMBEDDING_QUEUE_FLUSH_TIMEOUT);
-
-    let prev_embedding_count = embedding_provider.embedding_count();
-    let index = semantic_index.update(cx, |store, cx| store.index_project(project.clone(), cx));
-    cx.background_executor.run_until_parked();
-    assert_eq!(*pending_file_count.borrow(), 1);
-    cx.background_executor
-        .advance_clock(EMBEDDING_QUEUE_FLUSH_TIMEOUT);
-    assert_eq!(*pending_file_count.borrow(), 0);
-    index.await.unwrap();
-
-    assert_eq!(
-        embedding_provider.embedding_count() - prev_embedding_count,
-        1
-    );
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_embedding_batching(cx: &mut TestAppContext, mut rng: StdRng) {
-    let (outstanding_job_count, _) = postage::watch::channel_with(0);
-    let outstanding_job_count = Arc::new(Mutex::new(outstanding_job_count));
-
-    let files = (1..=3)
-        .map(|file_ix| FileToEmbed {
-            worktree_id: 5,
-            path: Path::new(&format!("path-{file_ix}")).into(),
-            mtime: SystemTime::now(),
-            spans: (0..rng.gen_range(4..22))
-                .map(|document_ix| {
-                    let content_len = rng.gen_range(10..100);
-                    let content = RandomCharIter::new(&mut rng)
-                        .with_simple_text()
-                        .take(content_len)
-                        .collect::<String>();
-                    let digest = SpanDigest::from(content.as_str());
-                    Span {
-                        range: 0..10,
-                        embedding: None,
-                        name: format!("document {document_ix}"),
-                        content,
-                        digest,
-                        token_count: rng.gen_range(10..30),
-                    }
-                })
-                .collect(),
-            job_handle: JobHandle::new(&outstanding_job_count),
-        })
-        .collect::<Vec<_>>();
-
-    let embedding_provider = Arc::new(FakeEmbeddingProvider::default());
-
-    let mut queue = EmbeddingQueue::new(embedding_provider.clone(), cx.background_executor.clone());
-    for file in &files {
-        queue.push(file.clone());
-    }
-    queue.flush();
-
-    cx.background_executor.run_until_parked();
-    let finished_files = queue.finished_files();
-    let mut embedded_files: Vec<_> = files
-        .iter()
-        .map(|_| finished_files.try_recv().expect("no finished file"))
-        .collect();
-
-    let expected_files: Vec<_> = files
-        .iter()
-        .map(|file| {
-            let mut file = file.clone();
-            for doc in &mut file.spans {
-                doc.embedding = Some(embedding_provider.embed_sync(doc.content.as_ref()));
-            }
-            file
-        })
-        .collect();
-
-    embedded_files.sort_by_key(|f| f.path.clone());
-
-    assert_eq!(embedded_files, expected_files);
-}
-
-#[track_caller]
-fn assert_search_results(
-    actual: &[SearchResult],
-    expected: &[(Arc<Path>, usize)],
-    cx: &TestAppContext,
-) {
-    let actual = actual
-        .iter()
-        .map(|search_result| {
-            search_result.buffer.read_with(cx, |buffer, _cx| {
-                (
-                    buffer.file().unwrap().path().clone(),
-                    search_result.range.start.to_offset(buffer),
-                )
-            })
-        })
-        .collect::<Vec<_>>();
-    assert_eq!(actual, expected);
-}
-
-#[gpui::test]
-async fn test_code_context_retrieval_rust() {
-    let language = rust_lang();
-    let embedding_provider = Arc::new(FakeEmbeddingProvider::default());
-    let mut retriever = CodeContextRetriever::new(embedding_provider);
-
-    let text = "
-        /// A doc comment
-        /// that spans multiple lines
-        #[gpui::test]
-        fn a() {
-            b
-        }
-
-        impl C for D {
-        }
-
-        impl E {
-            // This is also a preceding comment
-            pub fn function_1() -> Option<()> {
-                unimplemented!();
-            }
-
-            // This is a preceding comment
-            fn function_2() -> Result<()> {
-                unimplemented!();
-            }
-        }
-
-        #[derive(Clone)]
-        struct D {
-            name: String
-        }
-    "
-    .unindent();
-
-    let documents = retriever.parse_file(&text, language).unwrap();
-
-    assert_documents_eq(
-        &documents,
-        &[
-            (
-                "
-                /// A doc comment
-                /// that spans multiple lines
-                #[gpui::test]
-                fn a() {
-                    b
-                }"
-                .unindent(),
-                text.find("fn a").unwrap(),
-            ),
-            (
-                "
-                impl C for D {
-                }"
-                .unindent(),
-                text.find("impl C").unwrap(),
-            ),
-            (
-                "
-                impl E {
-                    // This is also a preceding comment
-                    pub fn function_1() -> Option<()> { /* ... */ }
-
-                    // This is a preceding comment
-                    fn function_2() -> Result<()> { /* ... */ }
-                }"
-                .unindent(),
-                text.find("impl E").unwrap(),
-            ),
-            (
-                "
-                // This is also a preceding comment
-                pub fn function_1() -> Option<()> {
-                    unimplemented!();
-                }"
-                .unindent(),
-                text.find("pub fn function_1").unwrap(),
-            ),
-            (
-                "
-                // This is a preceding comment
-                fn function_2() -> Result<()> {
-                    unimplemented!();
-                }"
-                .unindent(),
-                text.find("fn function_2").unwrap(),
-            ),
-            (
-                "
-                #[derive(Clone)]
-                struct D {
-                    name: String
-                }"
-                .unindent(),
-                text.find("struct D").unwrap(),
-            ),
-        ],
-    );
-}
-
-#[gpui::test]
-async fn test_code_context_retrieval_json() {
-    let language = json_lang();
-    let embedding_provider = Arc::new(FakeEmbeddingProvider::default());
-    let mut retriever = CodeContextRetriever::new(embedding_provider);
-
-    let text = r#"
-        {
-            "array": [1, 2, 3, 4],
-            "string": "abcdefg",
-            "nested_object": {
-                "array_2": [5, 6, 7, 8],
-                "string_2": "hijklmnop",
-                "boolean": true,
-                "none": null
-            }
-        }
-    "#
-    .unindent();
-
-    let documents = retriever.parse_file(&text, language.clone()).unwrap();
-
-    assert_documents_eq(
-        &documents,
-        &[(
-            r#"
-                {
-                    "array": [],
-                    "string": "",
-                    "nested_object": {
-                        "array_2": [],
-                        "string_2": "",
-                        "boolean": true,
-                        "none": null
-                    }
-                }"#
-            .unindent(),
-            text.find("{").unwrap(),
-        )],
-    );
-
-    let text = r#"
-        [
-            {
-                "name": "somebody",
-                "age": 42
-            },
-            {
-                "name": "somebody else",
-                "age": 43
-            }
-        ]
-    "#
-    .unindent();
-
-    let documents = retriever.parse_file(&text, language.clone()).unwrap();
-
-    assert_documents_eq(
-        &documents,
-        &[(
-            r#"
-            [{
-                    "name": "",
-                    "age": 42
-                }]"#
-            .unindent(),
-            text.find("[").unwrap(),
-        )],
-    );
-}
-
-fn assert_documents_eq(
-    documents: &[Span],
-    expected_contents_and_start_offsets: &[(String, usize)],
-) {
-    assert_eq!(
-        documents
-            .iter()
-            .map(|document| (document.content.clone(), document.range.start))
-            .collect::<Vec<_>>(),
-        expected_contents_and_start_offsets
-    );
-}
-
-#[gpui::test]
-async fn test_code_context_retrieval_javascript() {
-    let language = js_lang();
-    let embedding_provider = Arc::new(FakeEmbeddingProvider::default());
-    let mut retriever = CodeContextRetriever::new(embedding_provider);
-
-    let text = "
-        /* globals importScripts, backend */
-        function _authorize() {}
-
-        /**
-         * Sometimes the frontend build is way faster than backend.
-         */
-        export async function authorizeBank() {
-            _authorize(pushModal, upgradingAccountId, {});
-        }
-
-        export class SettingsPage {
-            /* This is a test setting */
-            constructor(page) {
-                this.page = page;
-            }
-        }
-
-        /* This is a test comment */
-        class TestClass {}
-
-        /* Schema for editor_events in Clickhouse. */
-        export interface ClickhouseEditorEvent {
-            installation_id: string
-            operation: string
-        }
-        "
-    .unindent();
-
-    let documents = retriever.parse_file(&text, language.clone()).unwrap();
-
-    assert_documents_eq(
-        &documents,
-        &[
-            (
-                "
-            /* globals importScripts, backend */
-            function _authorize() {}"
-                    .unindent(),
-                37,
-            ),
-            (
-                "
-            /**
-             * Sometimes the frontend build is way faster than backend.
-             */
-            export async function authorizeBank() {
-                _authorize(pushModal, upgradingAccountId, {});
-            }"
-                .unindent(),
-                131,
-            ),
-            (
-                "
-                export class SettingsPage {
-                    /* This is a test setting */
-                    constructor(page) {
-                        this.page = page;
-                    }
-                }"
-                .unindent(),
-                225,
-            ),
-            (
-                "
-                /* This is a test setting */
-                constructor(page) {
-                    this.page = page;
-                }"
-                .unindent(),
-                290,
-            ),
-            (
-                "
-                /* This is a test comment */
-                class TestClass {}"
-                    .unindent(),
-                374,
-            ),
-            (
-                "
-                /* Schema for editor_events in Clickhouse. */
-                export interface ClickhouseEditorEvent {
-                    installation_id: string
-                    operation: string
-                }"
-                .unindent(),
-                440,
-            ),
-        ],
-    )
-}
-
-#[gpui::test]
-async fn test_code_context_retrieval_lua() {
-    let language = lua_lang();
-    let embedding_provider = Arc::new(FakeEmbeddingProvider::default());
-    let mut retriever = CodeContextRetriever::new(embedding_provider);
-
-    let text = r#"
-        -- Creates a new class
-        -- @param baseclass The Baseclass of this class, or nil.
-        -- @return A new class reference.
-        function classes.class(baseclass)
-            -- Create the class definition and metatable.
-            local classdef = {}
-            -- Find the super class, either Object or user-defined.
-            baseclass = baseclass or classes.Object
-            -- If this class definition does not know of a function, it will 'look up' to the Baseclass via the __index of the metatable.
-            setmetatable(classdef, { __index = baseclass })
-            -- All class instances have a reference to the class object.
-            classdef.class = classdef
-            --- Recursivly allocates the inheritance tree of the instance.
-            -- @param mastertable The 'root' of the inheritance tree.
-            -- @return Returns the instance with the allocated inheritance tree.
-            function classdef.alloc(mastertable)
-                -- All class instances have a reference to a superclass object.
-                local instance = { super = baseclass.alloc(mastertable) }
-                -- Any functions this instance does not know of will 'look up' to the superclass definition.
-                setmetatable(instance, { __index = classdef, __newindex = mastertable })
-                return instance
-            end
-        end
-        "#.unindent();
-
-    let documents = retriever.parse_file(&text, language.clone()).unwrap();
-
-    assert_documents_eq(
-        &documents,
-        &[
-            (r#"
-                -- Creates a new class
-                -- @param baseclass The Baseclass of this class, or nil.
-                -- @return A new class reference.
-                function classes.class(baseclass)
-                    -- Create the class definition and metatable.
-                    local classdef = {}
-                    -- Find the super class, either Object or user-defined.
-                    baseclass = baseclass or classes.Object
-                    -- If this class definition does not know of a function, it will 'look up' to the Baseclass via the __index of the metatable.
-                    setmetatable(classdef, { __index = baseclass })
-                    -- All class instances have a reference to the class object.
-                    classdef.class = classdef
-                    --- Recursivly allocates the inheritance tree of the instance.
-                    -- @param mastertable The 'root' of the inheritance tree.
-                    -- @return Returns the instance with the allocated inheritance tree.
-                    function classdef.alloc(mastertable)
-                        --[ ... ]--
-                        --[ ... ]--
-                    end
-                end"#.unindent(),
-            114),
-            (r#"
-            --- Recursivly allocates the inheritance tree of the instance.
-            -- @param mastertable The 'root' of the inheritance tree.
-            -- @return Returns the instance with the allocated inheritance tree.
-            function classdef.alloc(mastertable)
-                -- All class instances have a reference to a superclass object.
-                local instance = { super = baseclass.alloc(mastertable) }
-                -- Any functions this instance does not know of will 'look up' to the superclass definition.
-                setmetatable(instance, { __index = classdef, __newindex = mastertable })
-                return instance
-            end"#.unindent(), 809),
-        ]
-    );
-}
-
-#[gpui::test]
-async fn test_code_context_retrieval_elixir() {
-    let language = elixir_lang();
-    let embedding_provider = Arc::new(FakeEmbeddingProvider::default());
-    let mut retriever = CodeContextRetriever::new(embedding_provider);
-
-    let text = r#"
-        defmodule File.Stream do
-            @moduledoc """
-            Defines a `File.Stream` struct returned by `File.stream!/3`.
-
-            The following fields are public:
-
-            * `path`          - the file path
-            * `modes`         - the file modes
-            * `raw`           - a boolean indicating if bin functions should be used
-            * `line_or_bytes` - if reading should read lines or a given number of bytes
-            * `node`          - the node the file belongs to
-
-            """
-
-            defstruct path: nil, modes: [], line_or_bytes: :line, raw: true, node: nil
-
-            @type t :: %__MODULE__{}
-
-            @doc false
-            def __build__(path, modes, line_or_bytes) do
-            raw = :lists.keyfind(:encoding, 1, modes) == false
-
-            modes =
-                case raw do
-                true ->
-                    case :lists.keyfind(:read_ahead, 1, modes) do
-                    {:read_ahead, false} -> [:raw | :lists.keydelete(:read_ahead, 1, modes)]
-                    {:read_ahead, _} -> [:raw | modes]
-                    false -> [:raw, :read_ahead | modes]
-                    end
-
-                false ->
-                    modes
-                end
-
-            %File.Stream{path: path, modes: modes, raw: raw, line_or_bytes: line_or_bytes, node: node()}
-
-            end"#
-    .unindent();
-
-    let documents = retriever.parse_file(&text, language.clone()).unwrap();
-
-    assert_documents_eq(
-        &documents,
-        &[(
-            r#"
-        defmodule File.Stream do
-            @moduledoc """
-            Defines a `File.Stream` struct returned by `File.stream!/3`.
-
-            The following fields are public:
-
-            * `path`          - the file path
-            * `modes`         - the file modes
-            * `raw`           - a boolean indicating if bin functions should be used
-            * `line_or_bytes` - if reading should read lines or a given number of bytes
-            * `node`          - the node the file belongs to
-
-            """
-
-            defstruct path: nil, modes: [], line_or_bytes: :line, raw: true, node: nil
-
-            @type t :: %__MODULE__{}
-
-            @doc false
-            def __build__(path, modes, line_or_bytes) do
-            raw = :lists.keyfind(:encoding, 1, modes) == false
-
-            modes =
-                case raw do
-                true ->
-                    case :lists.keyfind(:read_ahead, 1, modes) do
-                    {:read_ahead, false} -> [:raw | :lists.keydelete(:read_ahead, 1, modes)]
-                    {:read_ahead, _} -> [:raw | modes]
-                    false -> [:raw, :read_ahead | modes]
-                    end
-
-                false ->
-                    modes
-                end
-
-            %File.Stream{path: path, modes: modes, raw: raw, line_or_bytes: line_or_bytes, node: node()}
-
-            end"#
-                .unindent(),
-            0,
-        ),(r#"
-            @doc false
-            def __build__(path, modes, line_or_bytes) do
-            raw = :lists.keyfind(:encoding, 1, modes) == false
-
-            modes =
-                case raw do
-                true ->
-                    case :lists.keyfind(:read_ahead, 1, modes) do
-                    {:read_ahead, false} -> [:raw | :lists.keydelete(:read_ahead, 1, modes)]
-                    {:read_ahead, _} -> [:raw | modes]
-                    false -> [:raw, :read_ahead | modes]
-                    end
-
-                false ->
-                    modes
-                end
-
-            %File.Stream{path: path, modes: modes, raw: raw, line_or_bytes: line_or_bytes, node: node()}
-
-            end"#.unindent(), 574)],
-    );
-}
-
-#[gpui::test]
-async fn test_code_context_retrieval_cpp() {
-    let language = cpp_lang();
-    let embedding_provider = Arc::new(FakeEmbeddingProvider::default());
-    let mut retriever = CodeContextRetriever::new(embedding_provider);
-
-    let text = "
-    /**
-     * @brief Main function
-     * @returns 0 on exit
-     */
-    int main() { return 0; }
-
-    /**
-    * This is a test comment
-    */
-    class MyClass {       // The class
-        public:           // Access specifier
-        int myNum;        // Attribute (int variable)
-        string myString;  // Attribute (string variable)
-    };
-
-    // This is a test comment
-    enum Color { red, green, blue };
-
-    /** This is a preceding block comment
-     * This is the second line
-     */
-    struct {           // Structure declaration
-        int myNum;       // Member (int variable)
-        string myString; // Member (string variable)
-    } myStructure;
-
-    /**
-     * @brief Matrix class.
-     */
-    template <typename T,
-              typename = typename std::enable_if<
-                std::is_integral<T>::value || std::is_floating_point<T>::value,
-                bool>::type>
-    class Matrix2 {
-        std::vector<std::vector<T>> _mat;
-
-        public:
-            /**
-            * @brief Constructor
-            * @tparam Integer ensuring integers are being evaluated and not other
-            * data types.
-            * @param size denoting the size of Matrix as size x size
-            */
-            template <typename Integer,
-                    typename = typename std::enable_if<std::is_integral<Integer>::value,
-                    Integer>::type>
-            explicit Matrix(const Integer size) {
-                for (size_t i = 0; i < size; ++i) {
-                    _mat.emplace_back(std::vector<T>(size, 0));
-                }
-            }
-    }"
-    .unindent();
-
-    let documents = retriever.parse_file(&text, language.clone()).unwrap();
-
-    assert_documents_eq(
-        &documents,
-        &[
-            (
-                "
-        /**
-         * @brief Main function
-         * @returns 0 on exit
-         */
-        int main() { return 0; }"
-                    .unindent(),
-                54,
-            ),
-            (
-                "
-                /**
-                * This is a test comment
-                */
-                class MyClass {       // The class
-                    public:           // Access specifier
-                    int myNum;        // Attribute (int variable)
-                    string myString;  // Attribute (string variable)
-                }"
-                .unindent(),
-                112,
-            ),
-            (
-                "
-                // This is a test comment
-                enum Color { red, green, blue }"
-                    .unindent(),
-                322,
-            ),
-            (
-                "
-                /** This is a preceding block comment
-                 * This is the second line
-                 */
-                struct {           // Structure declaration
-                    int myNum;       // Member (int variable)
-                    string myString; // Member (string variable)
-                } myStructure;"
-                    .unindent(),
-                425,
-            ),
-            (
-                "
-                /**
-                 * @brief Matrix class.
-                 */
-                template <typename T,
-                          typename = typename std::enable_if<
-                            std::is_integral<T>::value || std::is_floating_point<T>::value,
-                            bool>::type>
-                class Matrix2 {
-                    std::vector<std::vector<T>> _mat;
-
-                    public:
-                        /**
-                        * @brief Constructor
-                        * @tparam Integer ensuring integers are being evaluated and not other
-                        * data types.
-                        * @param size denoting the size of Matrix as size x size
-                        */
-                        template <typename Integer,
-                                typename = typename std::enable_if<std::is_integral<Integer>::value,
-                                Integer>::type>
-                        explicit Matrix(const Integer size) {
-                            for (size_t i = 0; i < size; ++i) {
-                                _mat.emplace_back(std::vector<T>(size, 0));
-                            }
-                        }
-                }"
-                .unindent(),
-                612,
-            ),
-            (
-                "
-                explicit Matrix(const Integer size) {
-                    for (size_t i = 0; i < size; ++i) {
-                        _mat.emplace_back(std::vector<T>(size, 0));
-                    }
-                }"
-                .unindent(),
-                1226,
-            ),
-        ],
-    );
-}
-
-#[gpui::test]
-async fn test_code_context_retrieval_ruby() {
-    let language = ruby_lang();
-    let embedding_provider = Arc::new(FakeEmbeddingProvider::default());
-    let mut retriever = CodeContextRetriever::new(embedding_provider);
-
-    let text = r#"
-        # This concern is inspired by "sudo mode" on GitHub. It
-        # is a way to re-authenticate a user before allowing them
-        # to see or perform an action.
-        #
-        # Add `before_action :require_challenge!` to actions you
-        # want to protect.
-        #
-        # The user will be shown a page to enter the challenge (which
-        # is either the password, or just the username when no
-        # password exists). Upon passing, there is a grace period
-        # during which no challenge will be asked from the user.
-        #
-        # Accessing challenge-protected resources during the grace
-        # period will refresh the grace period.
-        module ChallengableConcern
-            extend ActiveSupport::Concern
-
-            CHALLENGE_TIMEOUT = 1.hour.freeze
-
-            def require_challenge!
-                return if skip_challenge?
-
-                if challenge_passed_recently?
-                    session[:challenge_passed_at] = Time.now.utc
-                    return
-                end
-
-                @challenge = Form::Challenge.new(return_to: request.url)
-
-                if params.key?(:form_challenge)
-                    if challenge_passed?
-                        session[:challenge_passed_at] = Time.now.utc
-                    else
-                        flash.now[:alert] = I18n.t('challenge.invalid_password')
-                        render_challenge
-                    end
-                else
-                    render_challenge
-                end
-            end
-
-            def challenge_passed?
-                current_user.valid_password?(challenge_params[:current_password])
-            end
-        end
-
-        class Animal
-            include Comparable
-
-            attr_reader :legs
-
-            def initialize(name, legs)
-                @name, @legs = name, legs
-            end
-
-            def <=>(other)
-                legs <=> other.legs
-            end
-        end
-
-        # Singleton method for car object
-        def car.wheels
-            puts "There are four wheels"
-        end"#
-        .unindent();
-
-    let documents = retriever.parse_file(&text, language.clone()).unwrap();
-
-    assert_documents_eq(
-        &documents,
-        &[
-            (
-                r#"
-        # This concern is inspired by "sudo mode" on GitHub. It
-        # is a way to re-authenticate a user before allowing them
-        # to see or perform an action.
-        #
-        # Add `before_action :require_challenge!` to actions you
-        # want to protect.
-        #
-        # The user will be shown a page to enter the challenge (which
-        # is either the password, or just the username when no
-        # password exists). Upon passing, there is a grace period
-        # during which no challenge will be asked from the user.
-        #
-        # Accessing challenge-protected resources during the grace
-        # period will refresh the grace period.
-        module ChallengableConcern
-            extend ActiveSupport::Concern
-
-            CHALLENGE_TIMEOUT = 1.hour.freeze
-
-            def require_challenge!
-                # ...
-            end
-
-            def challenge_passed?
-                # ...
-            end
-        end"#
-                    .unindent(),
-                558,
-            ),
-            (
-                r#"
-            def require_challenge!
-                return if skip_challenge?
-
-                if challenge_passed_recently?
-                    session[:challenge_passed_at] = Time.now.utc
-                    return
-                end
-
-                @challenge = Form::Challenge.new(return_to: request.url)
-
-                if params.key?(:form_challenge)
-                    if challenge_passed?
-                        session[:challenge_passed_at] = Time.now.utc
-                    else
-                        flash.now[:alert] = I18n.t('challenge.invalid_password')
-                        render_challenge
-                    end
-                else
-                    render_challenge
-                end
-            end"#
-                    .unindent(),
-                663,
-            ),
-            (
-                r#"
-                def challenge_passed?
-                    current_user.valid_password?(challenge_params[:current_password])
-                end"#
-                    .unindent(),
-                1254,
-            ),
-            (
-                r#"
-                class Animal
-                    include Comparable
-
-                    attr_reader :legs
-
-                    def initialize(name, legs)
-                        # ...
-                    end
-
-                    def <=>(other)
-                        # ...
-                    end
-                end"#
-                    .unindent(),
-                1363,
-            ),
-            (
-                r#"
-                def initialize(name, legs)
-                    @name, @legs = name, legs
-                end"#
-                    .unindent(),
-                1427,
-            ),
-            (
-                r#"
-                def <=>(other)
-                    legs <=> other.legs
-                end"#
-                    .unindent(),
-                1501,
-            ),
-            (
-                r#"
-                # Singleton method for car object
-                def car.wheels
-                    puts "There are four wheels"
-                end"#
-                    .unindent(),
-                1591,
-            ),
-        ],
-    );
-}
-
-#[gpui::test]
-async fn test_code_context_retrieval_php() {
-    let language = php_lang();
-    let embedding_provider = Arc::new(FakeEmbeddingProvider::default());
-    let mut retriever = CodeContextRetriever::new(embedding_provider);
-
-    let text = r#"
-        <?php
-
-        namespace LevelUp\Experience\Concerns;
-
-        /*
-        This is a multiple-lines comment block
-        that spans over multiple
-        lines
-        */
-        function functionName() {
-            echo "Hello world!";
-        }
-
-        trait HasAchievements
-        {
-            /**
-            * @throws \Exception
-            */
-            public function grantAchievement(Achievement $achievement, $progress = null): void
-            {
-                if ($progress > 100) {
-                    throw new Exception(message: 'Progress cannot be greater than 100');
-                }
-
-                if ($this->achievements()->find($achievement->id)) {
-                    throw new Exception(message: 'User already has this Achievement');
-                }
-
-                $this->achievements()->attach($achievement, [
-                    'progress' => $progress ?? null,
-                ]);
-
-                $this->when(value: ($progress === null) || ($progress === 100), callback: fn (): ?array => event(new AchievementAwarded(achievement: $achievement, user: $this)));
-            }
-
-            public function achievements(): BelongsToMany
-            {
-                return $this->belongsToMany(related: Achievement::class)
-                ->withPivot(columns: 'progress')
-                ->where('is_secret', false)
-                ->using(AchievementUser::class);
-            }
-        }
-
-        interface Multiplier
-        {
-            public function qualifies(array $data): bool;
-
-            public function setMultiplier(): int;
-        }
-
-        enum AuditType: string
-        {
-            case Add = 'add';
-            case Remove = 'remove';
-            case Reset = 'reset';
-            case LevelUp = 'level_up';
-        }
-
-        ?>"#
-    .unindent();
-
-    let documents = retriever.parse_file(&text, language.clone()).unwrap();
-
-    assert_documents_eq(
-        &documents,
-        &[
-            (
-                r#"
-        /*
-        This is a multiple-lines comment block
-        that spans over multiple
-        lines
-        */
-        function functionName() {
-            echo "Hello world!";
-        }"#
-                .unindent(),
-                123,
-            ),
-            (
-                r#"
-        trait HasAchievements
-        {
-            /**
-            * @throws \Exception
-            */
-            public function grantAchievement(Achievement $achievement, $progress = null): void
-            {/* ... */}
-
-            public function achievements(): BelongsToMany
-            {/* ... */}
-        }"#
-                .unindent(),
-                177,
-            ),
-            (r#"
-            /**
-            * @throws \Exception
-            */
-            public function grantAchievement(Achievement $achievement, $progress = null): void
-            {
-                if ($progress > 100) {
-                    throw new Exception(message: 'Progress cannot be greater than 100');
-                }
-
-                if ($this->achievements()->find($achievement->id)) {
-                    throw new Exception(message: 'User already has this Achievement');
-                }
-
-                $this->achievements()->attach($achievement, [
-                    'progress' => $progress ?? null,
-                ]);
-
-                $this->when(value: ($progress === null) || ($progress === 100), callback: fn (): ?array => event(new AchievementAwarded(achievement: $achievement, user: $this)));
-            }"#.unindent(), 245),
-            (r#"
-                public function achievements(): BelongsToMany
-                {
-                    return $this->belongsToMany(related: Achievement::class)
-                    ->withPivot(columns: 'progress')
-                    ->where('is_secret', false)
-                    ->using(AchievementUser::class);
-                }"#.unindent(), 902),
-            (r#"
-                interface Multiplier
-                {
-                    public function qualifies(array $data): bool;
-
-                    public function setMultiplier(): int;
-                }"#.unindent(),
-                1146),
-            (r#"
-                enum AuditType: string
-                {
-                    case Add = 'add';
-                    case Remove = 'remove';
-                    case Reset = 'reset';
-                    case LevelUp = 'level_up';
-                }"#.unindent(), 1265)
-        ],
-    );
-}
-
-fn js_lang() -> Arc<Language> {
-    Arc::new(
-        Language::new(
-            LanguageConfig {
-                name: "Javascript".into(),
-                path_suffixes: vec!["js".into()],
-                ..Default::default()
-            },
-            Some(tree_sitter_typescript::language_tsx()),
-        )
-        .with_embedding_query(
-            &r#"
-
-            (
-                (comment)* @context
-                .
-                [
-                (export_statement
-                    (function_declaration
-                        "async"? @name
-                        "function" @name
-                        name: (_) @name))
-                (function_declaration
-                    "async"? @name
-                    "function" @name
-                    name: (_) @name)
-                ] @item
-            )
-
-            (
-                (comment)* @context
-                .
-                [
-                (export_statement
-                    (class_declaration
-                        "class" @name
-                        name: (_) @name))
-                (class_declaration
-                    "class" @name
-                    name: (_) @name)
-                ] @item
-            )
-
-            (
-                (comment)* @context
-                .
-                [
-                (export_statement
-                    (interface_declaration
-                        "interface" @name
-                        name: (_) @name))
-                (interface_declaration
-                    "interface" @name
-                    name: (_) @name)
-                ] @item
-            )
-
-            (
-                (comment)* @context
-                .
-                [
-                (export_statement
-                    (enum_declaration
-                        "enum" @name
-                        name: (_) @name))
-                (enum_declaration
-                    "enum" @name
-                    name: (_) @name)
-                ] @item
-            )
-
-            (
-                (comment)* @context
-                .
-                (method_definition
-                    [
-                        "get"
-                        "set"
-                        "async"
-                        "*"
-                        "static"
-                    ]* @name
-                    name: (_) @name) @item
-            )
-
-                    "#
-            .unindent(),
-        )
-        .unwrap(),
-    )
-}
-
-fn rust_lang() -> Arc<Language> {
-    Arc::new(
-        Language::new(
-            LanguageConfig {
-                name: "Rust".into(),
-                path_suffixes: vec!["rs".into()],
-                collapsed_placeholder: " /* ... */ ".to_string(),
-                ..Default::default()
-            },
-            Some(tree_sitter_rust::language()),
-        )
-        .with_embedding_query(
-            r#"
-            (
-                [(line_comment) (attribute_item)]* @context
-                .
-                [
-                    (struct_item
-                        name: (_) @name)
-
-                    (enum_item
-                        name: (_) @name)
-
-                    (impl_item
-                        trait: (_)? @name
-                        "for"? @name
-                        type: (_) @name)
-
-                    (trait_item
-                        name: (_) @name)
-
-                    (function_item
-                        name: (_) @name
-                        body: (block
-                            "{" @keep
-                            "}" @keep) @collapse)
-
-                    (macro_definition
-                        name: (_) @name)
-                ] @item
-            )
-
-            (attribute_item) @collapse
-            (use_declaration) @collapse
-            "#,
-        )
-        .unwrap(),
-    )
-}
-
-fn json_lang() -> Arc<Language> {
-    Arc::new(
-        Language::new(
-            LanguageConfig {
-                name: "JSON".into(),
-                path_suffixes: vec!["json".into()],
-                ..Default::default()
-            },
-            Some(tree_sitter_json::language()),
-        )
-        .with_embedding_query(
-            r#"
-            (document) @item
-
-            (array
-                "[" @keep
-                .
-                (object)? @keep
-                "]" @keep) @collapse
-
-            (pair value: (string
-                "\"" @keep
-                "\"" @keep) @collapse)
-            "#,
-        )
-        .unwrap(),
-    )
-}
-
-fn toml_lang() -> Arc<Language> {
-    Arc::new(Language::new(
-        LanguageConfig {
-            name: "TOML".into(),
-            path_suffixes: vec!["toml".into()],
-            ..Default::default()
-        },
-        Some(tree_sitter_toml::language()),
-    ))
-}
-
-fn cpp_lang() -> Arc<Language> {
-    Arc::new(
-        Language::new(
-            LanguageConfig {
-                name: "CPP".into(),
-                path_suffixes: vec!["cpp".into()],
-                ..Default::default()
-            },
-            Some(tree_sitter_cpp::language()),
-        )
-        .with_embedding_query(
-            r#"
-            (
-                (comment)* @context
-                .
-                (function_definition
-                    (type_qualifier)? @name
-                    type: (_)? @name
-                    declarator: [
-                        (function_declarator
-                            declarator: (_) @name)
-                        (pointer_declarator
-                            "*" @name
-                            declarator: (function_declarator
-                            declarator: (_) @name))
-                        (pointer_declarator
-                            "*" @name
-                            declarator: (pointer_declarator
-                                "*" @name
-                            declarator: (function_declarator
-                                declarator: (_) @name)))
-                        (reference_declarator
-                            ["&" "&&"] @name
-                            (function_declarator
-                            declarator: (_) @name))
-                    ]
-                    (type_qualifier)? @name) @item
-                )
-
-            (
-                (comment)* @context
-                .
-                (template_declaration
-                    (class_specifier
-                        "class" @name
-                        name: (_) @name)
-                        ) @item
-            )
-
-            (
-                (comment)* @context
-                .
-                (class_specifier
-                    "class" @name
-                    name: (_) @name) @item
-                )
-
-            (
-                (comment)* @context
-                .
-                (enum_specifier
-                    "enum" @name
-                    name: (_) @name) @item
-                )
-
-            (
-                (comment)* @context
-                .
-                (declaration
-                    type: (struct_specifier
-                    "struct" @name)
-                    declarator: (_) @name) @item
-            )
-
-            "#,
-        )
-        .unwrap(),
-    )
-}
-
-fn lua_lang() -> Arc<Language> {
-    Arc::new(
-        Language::new(
-            LanguageConfig {
-                name: "Lua".into(),
-                path_suffixes: vec!["lua".into()],
-                collapsed_placeholder: "--[ ... ]--".to_string(),
-                ..Default::default()
-            },
-            Some(tree_sitter_lua::language()),
-        )
-        .with_embedding_query(
-            r#"
-            (
-                (comment)* @context
-                .
-                (function_declaration
-                    "function" @name
-                    name: (_) @name
-                    (comment)* @collapse
-                    body: (block) @collapse
-                ) @item
-            )
-        "#,
-        )
-        .unwrap(),
-    )
-}
-
-fn php_lang() -> Arc<Language> {
-    Arc::new(
-        Language::new(
-            LanguageConfig {
-                name: "PHP".into(),
-                path_suffixes: vec!["php".into()],
-                collapsed_placeholder: "/* ... */".into(),
-                ..Default::default()
-            },
-            Some(tree_sitter_php::language()),
-        )
-        .with_embedding_query(
-            r#"
-            (
-                (comment)* @context
-                .
-                [
-                    (function_definition
-                        "function" @name
-                        name: (_) @name
-                        body: (_
-                            "{" @keep
-                            "}" @keep) @collapse
-                        )
-
-                    (trait_declaration
-                        "trait" @name
-                        name: (_) @name)
-
-                    (method_declaration
-                        "function" @name
-                        name: (_) @name
-                        body: (_
-                            "{" @keep
-                            "}" @keep) @collapse
-                        )
-
-                    (interface_declaration
-                        "interface" @name
-                        name: (_) @name
-                        )
-
-                    (enum_declaration
-                        "enum" @name
-                        name: (_) @name
-                        )
-
-                ] @item
-            )
-            "#,
-        )
-        .unwrap(),
-    )
-}
-
-fn ruby_lang() -> Arc<Language> {
-    Arc::new(
-        Language::new(
-            LanguageConfig {
-                name: "Ruby".into(),
-                path_suffixes: vec!["rb".into()],
-                collapsed_placeholder: "# ...".to_string(),
-                ..Default::default()
-            },
-            Some(tree_sitter_ruby::language()),
-        )
-        .with_embedding_query(
-            r#"
-            (
-                (comment)* @context
-                .
-                [
-                (module
-                    "module" @name
-                    name: (_) @name)
-                (method
-                    "def" @name
-                    name: (_) @name
-                    body: (body_statement) @collapse)
-                (class
-                    "class" @name
-                    name: (_) @name)
-                (singleton_method
-                    "def" @name
-                    object: (_) @name
-                    "." @name
-                    name: (_) @name
-                    body: (body_statement) @collapse)
-                ] @item
-            )
-            "#,
-        )
-        .unwrap(),
-    )
-}
-
-fn elixir_lang() -> Arc<Language> {
-    Arc::new(
-        Language::new(
-            LanguageConfig {
-                name: "Elixir".into(),
-                path_suffixes: vec!["rs".into()],
-                ..Default::default()
-            },
-            Some(tree_sitter_elixir::language()),
-        )
-        .with_embedding_query(
-            r#"
-            (
-                (unary_operator
-                    operator: "@"
-                    operand: (call
-                        target: (identifier) @unary
-                        (#match? @unary "^(doc)$"))
-                    ) @context
-                .
-                (call
-                target: (identifier) @name
-                (arguments
-                [
-                (identifier) @name
-                (call
-                target: (identifier) @name)
-                (binary_operator
-                left: (call
-                target: (identifier) @name)
-                operator: "when")
-                ])
-                (#any-match? @name "^(def|defp|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp)$")) @item
-                )
-
-            (call
-                target: (identifier) @name
-                (arguments (alias) @name)
-                (#any-match? @name "^(defmodule|defprotocol)$")) @item
-            "#,
-        )
-        .unwrap(),
-    )
-}
-
-#[gpui::test]
-fn test_subtract_ranges() {
-    // collapsed_ranges: Vec<Range<usize>>, keep_ranges: Vec<Range<usize>>
-
-    assert_eq!(
-        subtract_ranges(&[0..5, 10..21], &[0..1, 4..5]),
-        vec![1..4, 10..21]
-    );
-
-    assert_eq!(subtract_ranges(&[0..5], &[1..2]), &[0..1, 2..5]);
-}
-
-fn init_test(cx: &mut TestAppContext) {
-    cx.update(|cx| {
-        let settings_store = SettingsStore::test(cx);
-        cx.set_global(settings_store);
-        SemanticIndexSettings::register(cx);
-        ProjectSettings::register(cx);
-    });
-}

crates/settings/src/keymap_file.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{settings_store::parse_json_with_comments, SettingsAssets};
 use anyhow::{anyhow, Context, Result};
 use collections::BTreeMap;
-use gpui::{keymap_matcher::Binding, AppContext, NoAction};
+use gpui::{Action, AppContext, KeyBinding, SharedString};
 use schemars::{
     gen::{SchemaGenerator, SchemaSettings},
     schema::{InstanceType, Schema, SchemaObject, SingleOrVec, SubschemaValidation},
@@ -73,9 +73,9 @@ impl KeymapFile {
                                     "Expected first item in array to be a string."
                                 )));
                             };
-                            cx.deserialize_action(&name, Some(data))
+                            cx.build_action(&name, Some(data))
                         }
-                        Value::String(name) => cx.deserialize_action(&name, None),
+                        Value::String(name) => cx.build_action(&name, None),
                         Value::Null => Ok(no_action()),
                         _ => {
                             return Some(Err(anyhow!("Expected two-element array, got {action:?}")))
@@ -87,16 +87,16 @@ impl KeymapFile {
                         )
                     })
                     .log_err()
-                    .map(|action| Binding::load(&keystroke, action, context.as_deref()))
+                    .map(|action| KeyBinding::load(&keystroke, action, context.as_deref()))
                 })
                 .collect::<Result<Vec<_>>>()?;
 
-            cx.add_bindings(bindings);
+            cx.bind_keys(bindings);
         }
         Ok(())
     }
 
-    pub fn generate_json_schema(action_names: &[&'static str]) -> serde_json::Value {
+    pub fn generate_json_schema(action_names: &[SharedString]) -> serde_json::Value {
         let mut root_schema = SchemaSettings::draft07()
             .with(|settings| settings.option_add_null_type = false)
             .into_generator()
@@ -138,7 +138,7 @@ impl KeymapFile {
 }
 
 fn no_action() -> Box<dyn gpui::Action> {
-    Box::new(NoAction {})
+    gpui::NoAction.boxed_clone()
 }
 
 #[cfg(test)]

crates/settings/src/settings.rs 🔗

@@ -8,7 +8,7 @@ use util::asset_str;
 
 pub use keymap_file::KeymapFile;
 pub use settings_file::*;
-pub use settings_store::{Setting, SettingsJsonSchemaParams, SettingsStore};
+pub use settings_store::{Settings, SettingsJsonSchemaParams, SettingsStore};
 
 #[derive(RustEmbed)]
 #[folder = "../../assets"]

crates/settings/src/settings_file.rs 🔗

@@ -1,31 +1,11 @@
-use crate::{settings_store::SettingsStore, Setting};
+use crate::{settings_store::SettingsStore, KeymapFile, Settings};
 use anyhow::Result;
 use fs::Fs;
 use futures::{channel::mpsc, StreamExt};
-use gpui::{executor::Background, AppContext};
-use std::{
-    io::ErrorKind,
-    path::{Path, PathBuf},
-    str,
-    sync::Arc,
-    time::Duration,
-};
+use gpui::{AppContext, BackgroundExecutor};
+use std::{io::ErrorKind, path::PathBuf, str, sync::Arc, time::Duration};
 use util::{paths, ResultExt};
 
-pub fn register<T: Setting>(cx: &mut AppContext) {
-    cx.update_global::<SettingsStore, _, _>(|store, cx| {
-        store.register_setting::<T>(cx);
-    });
-}
-
-pub fn get<'a, T: Setting>(cx: &'a AppContext) -> &'a T {
-    cx.global::<SettingsStore>().get(None)
-}
-
-pub fn get_local<'a, T: Setting>(location: Option<(usize, &Path)>, cx: &'a AppContext) -> &'a T {
-    cx.global::<SettingsStore>().get(location)
-}
-
 pub const EMPTY_THEME_NAME: &'static str = "empty-theme";
 
 #[cfg(any(test, feature = "test-support"))]
@@ -36,6 +16,9 @@ pub fn test_settings() -> String {
     .unwrap();
     util::merge_non_null_json_value_into(
         serde_json::json!({
+            "ui_font_family": "Courier",
+            "ui_font_features": {},
+            "ui_font_size": 14,
             "buffer_font_family": "Courier",
             "buffer_font_features": {},
             "buffer_font_size": 14,
@@ -48,7 +31,7 @@ pub fn test_settings() -> String {
 }
 
 pub fn watch_config_file(
-    executor: Arc<Background>,
+    executor: &BackgroundExecutor,
     fs: Arc<dyn Fs>,
     path: PathBuf,
 ) -> mpsc::UnboundedReceiver<String> {
@@ -83,22 +66,27 @@ pub fn handle_settings_file_changes(
     mut user_settings_file_rx: mpsc::UnboundedReceiver<String>,
     cx: &mut AppContext,
 ) {
-    let user_settings_content = cx.background().block(user_settings_file_rx.next()).unwrap();
-    cx.update_global::<SettingsStore, _, _>(|store, cx| {
+    let user_settings_content = cx
+        .background_executor()
+        .block(user_settings_file_rx.next())
+        .unwrap();
+    cx.update_global(|store: &mut SettingsStore, cx| {
         store
             .set_user_settings(&user_settings_content, cx)
             .log_err();
     });
     cx.spawn(move |mut cx| async move {
         while let Some(user_settings_content) = user_settings_file_rx.next().await {
-            cx.update(|cx| {
-                cx.update_global::<SettingsStore, _, _>(|store, cx| {
-                    store
-                        .set_user_settings(&user_settings_content, cx)
-                        .log_err();
-                });
-                cx.refresh_windows();
+            eprintln!("settings file changed");
+            let result = cx.update_global(|store: &mut SettingsStore, cx| {
+                store
+                    .set_user_settings(&user_settings_content, cx)
+                    .log_err();
+                cx.refresh();
             });
+            if result.is_err() {
+                break; // App dropped
+            }
         }
     })
     .detach();
@@ -118,29 +106,29 @@ async fn load_settings(fs: &Arc<dyn Fs>) -> Result<String> {
     }
 }
 
-pub fn update_settings_file<T: Setting>(
+pub fn update_settings_file<T: Settings>(
     fs: Arc<dyn Fs>,
     cx: &mut AppContext,
     update: impl 'static + Send + FnOnce(&mut T::FileContent),
 ) {
     cx.spawn(|cx| async move {
-        let old_text = cx
-            .background()
-            .spawn({
-                let fs = fs.clone();
-                async move { load_settings(&fs).await }
-            })
-            .await?;
-
-        let new_text = cx.read(|cx| {
-            cx.global::<SettingsStore>()
-                .new_text_for_update::<T>(old_text, update)
-        });
-
-        cx.background()
-            .spawn(async move { fs.atomic_write(paths::SETTINGS.clone(), new_text).await })
-            .await?;
+        let old_text = load_settings(&fs).await?;
+        let new_text = cx.read_global(|store: &SettingsStore, _cx| {
+            store.new_text_for_update::<T>(old_text, update)
+        })?;
+        fs.atomic_write(paths::SETTINGS.clone(), new_text).await?;
         anyhow::Ok(())
     })
     .detach_and_log_err(cx);
 }
+
+pub fn load_default_keymap(cx: &mut AppContext) {
+    for path in ["keymaps/default.json", "keymaps/vim.json"] {
+        KeymapFile::load_asset(path, cx).unwrap();
+    }
+
+    // todo!()
+    // if let Some(asset_path) = settings::get::<BaseKeymap>(cx).asset_path() {
+    //     KeymapFile::load_asset(asset_path, cx).unwrap();
+    // }
+}

crates/settings/src/settings_store.rs 🔗

@@ -18,7 +18,7 @@ use util::{merge_non_null_json_value_into, RangeExt, ResultExt as _};
 /// A value that can be defined as a user setting.
 ///
 /// Settings can be loaded from a combination of multiple JSON files.
-pub trait Setting: 'static {
+pub trait Settings: 'static + Send + Sync {
     /// The name of a key within the JSON file from which this setting should
     /// be deserialized. If this is `None`, then the setting will be deserialized
     /// from the root object.
@@ -35,7 +35,7 @@ pub trait Setting: 'static {
     fn load(
         default_value: &Self::FileContent,
         user_values: &[&Self::FileContent],
-        cx: &AppContext,
+        cx: &mut AppContext,
     ) -> Result<Self>
     where
         Self: Sized;
@@ -76,6 +76,39 @@ pub trait Setting: 'static {
     fn missing_default() -> anyhow::Error {
         anyhow::anyhow!("missing default")
     }
+
+    fn register(cx: &mut AppContext)
+    where
+        Self: Sized,
+    {
+        cx.update_global(|store: &mut SettingsStore, cx| {
+            store.register_setting::<Self>(cx);
+        });
+    }
+
+    #[track_caller]
+    fn get<'a>(path: Option<(usize, &Path)>, cx: &'a AppContext) -> &'a Self
+    where
+        Self: Sized,
+    {
+        cx.global::<SettingsStore>().get(path)
+    }
+
+    #[track_caller]
+    fn get_global<'a>(cx: &'a AppContext) -> &'a Self
+    where
+        Self: Sized,
+    {
+        cx.global::<SettingsStore>().get(None)
+    }
+
+    #[track_caller]
+    fn override_global<'a>(settings: Self, cx: &'a mut AppContext)
+    where
+        Self: Sized,
+    {
+        cx.global_mut::<SettingsStore>().override_global(settings)
+    }
 }
 
 pub struct SettingsJsonSchemaParams<'a> {
@@ -89,7 +122,10 @@ pub struct SettingsStore {
     raw_default_settings: serde_json::Value,
     raw_user_settings: serde_json::Value,
     raw_local_settings: BTreeMap<(usize, Arc<Path>), serde_json::Value>,
-    tab_size_callback: Option<(TypeId, Box<dyn Fn(&dyn Any) -> Option<usize>>)>,
+    tab_size_callback: Option<(
+        TypeId,
+        Box<dyn Fn(&dyn Any) -> Option<usize> + Send + Sync + 'static>,
+    )>,
 }
 
 impl Default for SettingsStore {
@@ -110,7 +146,7 @@ struct SettingValue<T> {
     local_values: Vec<(usize, Arc<Path>, T)>,
 }
 
-trait AnySettingValue {
+trait AnySettingValue: 'static + Send + Sync {
     fn key(&self) -> Option<&'static str>;
     fn setting_type_name(&self) -> &'static str;
     fn deserialize_setting(&self, json: &serde_json::Value) -> Result<DeserializedSetting>;
@@ -118,7 +154,7 @@ trait AnySettingValue {
         &self,
         default_value: &DeserializedSetting,
         custom: &[DeserializedSetting],
-        cx: &AppContext,
+        cx: &mut AppContext,
     ) -> Result<Box<dyn Any>>;
     fn value_for_path(&self, path: Option<(usize, &Path)>) -> &dyn Any;
     fn set_global_value(&mut self, value: Box<dyn Any>);
@@ -135,7 +171,7 @@ struct DeserializedSetting(Box<dyn Any>);
 
 impl SettingsStore {
     /// Add a new type of setting to the store.
-    pub fn register_setting<T: Setting>(&mut self, cx: &AppContext) {
+    pub fn register_setting<T: Settings>(&mut self, cx: &mut AppContext) {
         let setting_type_id = TypeId::of::<T>();
         let entry = self.setting_values.entry(setting_type_id);
         if matches!(entry, hash_map::Entry::Occupied(_)) {
@@ -174,7 +210,7 @@ impl SettingsStore {
     ///
     /// Panics if the given setting type has not been registered, or if there is no
     /// value for this setting.
-    pub fn get<T: Setting>(&self, path: Option<(usize, &Path)>) -> &T {
+    pub fn get<T: Settings>(&self, path: Option<(usize, &Path)>) -> &T {
         self.setting_values
             .get(&TypeId::of::<T>())
             .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
@@ -186,7 +222,7 @@ impl SettingsStore {
     /// Override the global value for a setting.
     ///
     /// The given value will be overwritten if the user settings file changes.
-    pub fn override_global<T: Setting>(&mut self, value: T) {
+    pub fn override_global<T: Settings>(&mut self, value: T) {
         self.setting_values
             .get_mut(&TypeId::of::<T>())
             .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
@@ -202,7 +238,7 @@ impl SettingsStore {
     }
 
     #[cfg(any(test, feature = "test-support"))]
-    pub fn test(cx: &AppContext) -> Self {
+    pub fn test(cx: &mut AppContext) -> Self {
         let mut this = Self::default();
         this.set_default_settings(&crate::test_settings(), cx)
             .unwrap();
@@ -215,9 +251,9 @@ impl SettingsStore {
     /// This is only for tests. Normally, settings are only loaded from
     /// JSON files.
     #[cfg(any(test, feature = "test-support"))]
-    pub fn update_user_settings<T: Setting>(
+    pub fn update_user_settings<T: Settings>(
         &mut self,
-        cx: &AppContext,
+        cx: &mut AppContext,
         update: impl FnOnce(&mut T::FileContent),
     ) {
         let old_text = serde_json::to_string(&self.raw_user_settings).unwrap();
@@ -227,7 +263,7 @@ impl SettingsStore {
 
     /// Update the value of a setting in a JSON file, returning the new text
     /// for that JSON file.
-    pub fn new_text_for_update<T: Setting>(
+    pub fn new_text_for_update<T: Settings>(
         &self,
         old_text: String,
         update: impl FnOnce(&mut T::FileContent),
@@ -242,7 +278,7 @@ impl SettingsStore {
 
     /// Update the value of a setting in a JSON file, returning a list
     /// of edits to apply to the JSON file.
-    pub fn edits_for_update<T: Setting>(
+    pub fn edits_for_update<T: Settings>(
         &self,
         text: &str,
         update: impl FnOnce(&mut T::FileContent),
@@ -284,7 +320,7 @@ impl SettingsStore {
     }
 
     /// Configure the tab sized when updating JSON files.
-    pub fn set_json_tab_size_callback<T: Setting>(
+    pub fn set_json_tab_size_callback<T: Settings>(
         &mut self,
         get_tab_size: fn(&T) -> Option<usize>,
     ) {
@@ -314,7 +350,7 @@ impl SettingsStore {
     pub fn set_default_settings(
         &mut self,
         default_settings_content: &str,
-        cx: &AppContext,
+        cx: &mut AppContext,
     ) -> Result<()> {
         let settings: serde_json::Value = parse_json_with_comments(default_settings_content)?;
         if settings.is_object() {
@@ -330,7 +366,7 @@ impl SettingsStore {
     pub fn set_user_settings(
         &mut self,
         user_settings_content: &str,
-        cx: &AppContext,
+        cx: &mut AppContext,
     ) -> Result<()> {
         let settings: serde_json::Value = parse_json_with_comments(user_settings_content)?;
         if settings.is_object() {
@@ -348,7 +384,7 @@ impl SettingsStore {
         root_id: usize,
         path: Arc<Path>,
         settings_content: Option<&str>,
-        cx: &AppContext,
+        cx: &mut AppContext,
     ) -> Result<()> {
         if let Some(content) = settings_content {
             self.raw_local_settings
@@ -361,7 +397,7 @@ impl SettingsStore {
     }
 
     /// Add or remove a set of local settings via a JSON string.
-    pub fn clear_local_settings(&mut self, root_id: usize, cx: &AppContext) -> Result<()> {
+    pub fn clear_local_settings(&mut self, root_id: usize, cx: &mut AppContext) -> Result<()> {
         self.raw_local_settings.retain(|k, _| k.0 != root_id);
         self.recompute_values(Some((root_id, "".as_ref())), cx)?;
         Ok(())
@@ -453,7 +489,7 @@ impl SettingsStore {
     fn recompute_values(
         &mut self,
         changed_local_path: Option<(usize, &Path)>,
-        cx: &AppContext,
+        cx: &mut AppContext,
     ) -> Result<()> {
         // Reload the global and local values for every setting.
         let mut user_settings_stack = Vec::<DeserializedSetting>::new();
@@ -541,7 +577,7 @@ impl Debug for SettingsStore {
     }
 }
 
-impl<T: Setting> AnySettingValue for SettingValue<T> {
+impl<T: Settings> AnySettingValue for SettingValue<T> {
     fn key(&self) -> Option<&'static str> {
         T::KEY
     }
@@ -554,7 +590,7 @@ impl<T: Setting> AnySettingValue for SettingValue<T> {
         &self,
         default_value: &DeserializedSetting,
         user_values: &[DeserializedSetting],
-        cx: &AppContext,
+        cx: &mut AppContext,
     ) -> Result<Box<dyn Any>> {
         let default_value = default_value.0.downcast_ref::<T::FileContent>().unwrap();
         let values: SmallVec<[&T::FileContent; 6]> = user_values
@@ -837,6 +873,7 @@ fn to_pretty_json(value: &impl Serialize, indent_size: usize, indent_prefix_len:
 pub fn parse_json_with_comments<T: DeserializeOwned>(content: &str) -> Result<T> {
     Ok(serde_json_lenient::from_str(content)?)
 }
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -1123,7 +1160,7 @@ mod tests {
         );
     }
 
-    fn check_settings_update<T: Setting>(
+    fn check_settings_update<T: Settings>(
         store: &mut SettingsStore,
         old_json: String,
         update: fn(&mut T::FileContent),
@@ -1153,14 +1190,14 @@ mod tests {
         staff: Option<bool>,
     }
 
-    impl Setting for UserSettings {
+    impl Settings for UserSettings {
         const KEY: Option<&'static str> = Some("user");
         type FileContent = UserSettingsJson;
 
         fn load(
             default_value: &UserSettingsJson,
             user_values: &[&UserSettingsJson],
-            _: &AppContext,
+            _: &mut AppContext,
         ) -> Result<Self> {
             Self::load_via_json_merge(default_value, user_values)
         }
@@ -1169,14 +1206,14 @@ mod tests {
     #[derive(Debug, Deserialize, PartialEq)]
     struct TurboSetting(bool);
 
-    impl Setting for TurboSetting {
+    impl Settings for TurboSetting {
         const KEY: Option<&'static str> = Some("turbo");
         type FileContent = Option<bool>;
 
         fn load(
             default_value: &Option<bool>,
             user_values: &[&Option<bool>],
-            _: &AppContext,
+            _: &mut AppContext,
         ) -> Result<Self> {
             Self::load_via_json_merge(default_value, user_values)
         }
@@ -1196,7 +1233,7 @@ mod tests {
         key2: Option<String>,
     }
 
-    impl Setting for MultiKeySettings {
+    impl Settings for MultiKeySettings {
         const KEY: Option<&'static str> = None;
 
         type FileContent = MultiKeySettingsJson;
@@ -1204,7 +1241,7 @@ mod tests {
         fn load(
             default_value: &MultiKeySettingsJson,
             user_values: &[&MultiKeySettingsJson],
-            _: &AppContext,
+            _: &mut AppContext,
         ) -> Result<Self> {
             Self::load_via_json_merge(default_value, user_values)
         }
@@ -1229,7 +1266,7 @@ mod tests {
         pub hour_format: Option<HourFormat>,
     }
 
-    impl Setting for JournalSettings {
+    impl Settings for JournalSettings {
         const KEY: Option<&'static str> = Some("journal");
 
         type FileContent = JournalSettingsJson;
@@ -1237,7 +1274,7 @@ mod tests {
         fn load(
             default_value: &JournalSettingsJson,
             user_values: &[&JournalSettingsJson],
-            _: &AppContext,
+            _: &mut AppContext,
         ) -> Result<Self> {
             Self::load_via_json_merge(default_value, user_values)
         }
@@ -1255,12 +1292,12 @@ mod tests {
         language_setting_2: Option<bool>,
     }
 
-    impl Setting for LanguageSettings {
+    impl Settings for LanguageSettings {
         const KEY: Option<&'static str> = None;
 
         type FileContent = Self;
 
-        fn load(default_value: &Self, user_values: &[&Self], _: &AppContext) -> Result<Self> {
+        fn load(default_value: &Self, user_values: &[&Self], _: &mut AppContext) -> Result<Self> {
             Self::load_via_json_merge(default_value, user_values)
         }
     }

crates/settings2/Cargo.toml 🔗

@@ -1,42 +0,0 @@
-[package]
-name = "settings2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/settings2.rs"
-doctest = false
-
-[features]
-test-support = ["gpui/test-support", "fs/test-support"]
-
-[dependencies]
-collections = { path = "../collections" }
-gpui = {package = "gpui2",  path = "../gpui2" }
-sqlez = { path = "../sqlez" }
-fs = {package = "fs2",  path = "../fs2" }
-feature_flags = {package = "feature_flags2",  path = "../feature_flags2" }
-util = { path = "../util" }
-
-anyhow.workspace = true
-futures.workspace = true
-serde_json_lenient = {version = "0.1", features = ["preserve_order", "raw_value"]}
-lazy_static.workspace = true
-postage.workspace = true
-rust-embed.workspace = true
-schemars.workspace = true
-serde.workspace = true
-serde_derive.workspace = true
-serde_json.workspace = true
-smallvec.workspace = true
-toml.workspace = true
-tree-sitter.workspace = true
-tree-sitter-json = "*"
-
-[dev-dependencies]
-gpui = {package = "gpui2",  path = "../gpui2", features = ["test-support"] }
-fs = { package = "fs2", path = "../fs2", features = ["test-support"] }
-indoc.workspace = true
-pretty_assertions.workspace = true
-unindent.workspace = true

crates/settings2/src/keymap_file.rs 🔗

@@ -1,163 +0,0 @@
-use crate::{settings_store::parse_json_with_comments, SettingsAssets};
-use anyhow::{anyhow, Context, Result};
-use collections::BTreeMap;
-use gpui::{Action, AppContext, KeyBinding, SharedString};
-use schemars::{
-    gen::{SchemaGenerator, SchemaSettings},
-    schema::{InstanceType, Schema, SchemaObject, SingleOrVec, SubschemaValidation},
-    JsonSchema,
-};
-use serde::Deserialize;
-use serde_json::Value;
-use util::{asset_str, ResultExt};
-
-#[derive(Debug, Deserialize, Default, Clone, JsonSchema)]
-#[serde(transparent)]
-pub struct KeymapFile(Vec<KeymapBlock>);
-
-#[derive(Debug, Deserialize, Default, Clone, JsonSchema)]
-pub struct KeymapBlock {
-    #[serde(default)]
-    context: Option<String>,
-    bindings: BTreeMap<String, KeymapAction>,
-}
-
-#[derive(Debug, Deserialize, Default, Clone)]
-#[serde(transparent)]
-pub struct KeymapAction(Value);
-
-impl JsonSchema for KeymapAction {
-    fn schema_name() -> String {
-        "KeymapAction".into()
-    }
-
-    fn json_schema(_: &mut SchemaGenerator) -> Schema {
-        Schema::Bool(true)
-    }
-}
-
-#[derive(Deserialize)]
-struct ActionWithData(Box<str>, Value);
-
-impl KeymapFile {
-    pub fn load_asset(asset_path: &str, cx: &mut AppContext) -> Result<()> {
-        let content = asset_str::<SettingsAssets>(asset_path);
-
-        Self::parse(content.as_ref())?.add_to_cx(cx)
-    }
-
-    pub fn parse(content: &str) -> Result<Self> {
-        parse_json_with_comments::<Self>(content)
-    }
-
-    pub fn add_to_cx(self, cx: &mut AppContext) -> Result<()> {
-        for KeymapBlock { context, bindings } in self.0 {
-            let bindings = bindings
-                .into_iter()
-                .filter_map(|(keystroke, action)| {
-                    let action = action.0;
-
-                    // This is a workaround for a limitation in serde: serde-rs/json#497
-                    // We want to deserialize the action data as a `RawValue` so that we can
-                    // deserialize the action itself dynamically directly from the JSON
-                    // string. But `RawValue` currently does not work inside of an untagged enum.
-                    match action {
-                        Value::Array(items) => {
-                            let Ok([name, data]): Result<[serde_json::Value; 2], _> =
-                                items.try_into()
-                            else {
-                                return Some(Err(anyhow!("Expected array of length 2")));
-                            };
-                            let serde_json::Value::String(name) = name else {
-                                return Some(Err(anyhow!(
-                                    "Expected first item in array to be a string."
-                                )));
-                            };
-                            cx.build_action(&name, Some(data))
-                        }
-                        Value::String(name) => cx.build_action(&name, None),
-                        Value::Null => Ok(no_action()),
-                        _ => {
-                            return Some(Err(anyhow!("Expected two-element array, got {action:?}")))
-                        }
-                    }
-                    .with_context(|| {
-                        format!(
-                            "invalid binding value for keystroke {keystroke}, context {context:?}"
-                        )
-                    })
-                    .log_err()
-                    .map(|action| KeyBinding::load(&keystroke, action, context.as_deref()))
-                })
-                .collect::<Result<Vec<_>>>()?;
-
-            cx.bind_keys(bindings);
-        }
-        Ok(())
-    }
-
-    pub fn generate_json_schema(action_names: &[SharedString]) -> serde_json::Value {
-        let mut root_schema = SchemaSettings::draft07()
-            .with(|settings| settings.option_add_null_type = false)
-            .into_generator()
-            .into_root_schema_for::<KeymapFile>();
-
-        let action_schema = Schema::Object(SchemaObject {
-            subschemas: Some(Box::new(SubschemaValidation {
-                one_of: Some(vec![
-                    Schema::Object(SchemaObject {
-                        instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
-                        enum_values: Some(
-                            action_names
-                                .iter()
-                                .map(|name| Value::String(name.to_string()))
-                                .collect(),
-                        ),
-                        ..Default::default()
-                    }),
-                    Schema::Object(SchemaObject {
-                        instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Array))),
-                        ..Default::default()
-                    }),
-                    Schema::Object(SchemaObject {
-                        instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Null))),
-                        ..Default::default()
-                    }),
-                ]),
-                ..Default::default()
-            })),
-            ..Default::default()
-        });
-
-        root_schema
-            .definitions
-            .insert("KeymapAction".to_owned(), action_schema);
-
-        serde_json::to_value(root_schema).unwrap()
-    }
-}
-
-fn no_action() -> Box<dyn gpui::Action> {
-    gpui::NoAction.boxed_clone()
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::KeymapFile;
-
-    #[test]
-    fn can_deserialize_keymap_with_trailing_comma() {
-        let json = indoc::indoc! {"[
-              // Standard macOS bindings
-              {
-                \"bindings\": {
-                  \"up\": \"menu::SelectPrev\",
-                },
-              },
-            ]
-                  "
-
-        };
-        KeymapFile::parse(json).unwrap();
-    }
-}

crates/settings2/src/settings2.rs 🔗

@@ -1,38 +0,0 @@
-mod keymap_file;
-mod settings_file;
-mod settings_store;
-
-use rust_embed::RustEmbed;
-use std::{borrow::Cow, str};
-use util::asset_str;
-
-pub use keymap_file::KeymapFile;
-pub use settings_file::*;
-pub use settings_store::{Settings, SettingsJsonSchemaParams, SettingsStore};
-
-#[derive(RustEmbed)]
-#[folder = "../../assets"]
-#[include = "settings/*"]
-#[include = "keymaps/*"]
-#[exclude = "*.DS_Store"]
-pub struct SettingsAssets;
-
-pub fn default_settings() -> Cow<'static, str> {
-    asset_str::<SettingsAssets>("settings/default.json")
-}
-
-pub fn default_keymap() -> Cow<'static, str> {
-    asset_str::<SettingsAssets>("keymaps/default.json")
-}
-
-pub fn vim_keymap() -> Cow<'static, str> {
-    asset_str::<SettingsAssets>("keymaps/vim.json")
-}
-
-pub fn initial_user_settings_content() -> Cow<'static, str> {
-    asset_str::<SettingsAssets>("settings/initial_user_settings.json")
-}
-
-pub fn initial_local_settings_content() -> Cow<'static, str> {
-    asset_str::<SettingsAssets>("settings/initial_local_settings.json")
-}

crates/settings2/src/settings_file.rs 🔗

@@ -1,134 +0,0 @@
-use crate::{settings_store::SettingsStore, KeymapFile, Settings};
-use anyhow::Result;
-use fs::Fs;
-use futures::{channel::mpsc, StreamExt};
-use gpui::{AppContext, BackgroundExecutor};
-use std::{io::ErrorKind, path::PathBuf, str, sync::Arc, time::Duration};
-use util::{paths, ResultExt};
-
-pub const EMPTY_THEME_NAME: &'static str = "empty-theme";
-
-#[cfg(any(test, feature = "test-support"))]
-pub fn test_settings() -> String {
-    let mut value = crate::settings_store::parse_json_with_comments::<serde_json::Value>(
-        crate::default_settings().as_ref(),
-    )
-    .unwrap();
-    util::merge_non_null_json_value_into(
-        serde_json::json!({
-            "ui_font_family": "Courier",
-            "ui_font_features": {},
-            "ui_font_size": 14,
-            "buffer_font_family": "Courier",
-            "buffer_font_features": {},
-            "buffer_font_size": 14,
-            "theme": EMPTY_THEME_NAME,
-        }),
-        &mut value,
-    );
-    value.as_object_mut().unwrap().remove("languages");
-    serde_json::to_string(&value).unwrap()
-}
-
-pub fn watch_config_file(
-    executor: &BackgroundExecutor,
-    fs: Arc<dyn Fs>,
-    path: PathBuf,
-) -> mpsc::UnboundedReceiver<String> {
-    let (tx, rx) = mpsc::unbounded();
-    executor
-        .spawn(async move {
-            let events = fs.watch(&path, Duration::from_millis(100)).await;
-            futures::pin_mut!(events);
-
-            let contents = fs.load(&path).await.unwrap_or_default();
-            if tx.unbounded_send(contents).is_err() {
-                return;
-            }
-
-            loop {
-                if events.next().await.is_none() {
-                    break;
-                }
-
-                if let Ok(contents) = fs.load(&path).await {
-                    if !tx.unbounded_send(contents).is_ok() {
-                        break;
-                    }
-                }
-            }
-        })
-        .detach();
-    rx
-}
-
-pub fn handle_settings_file_changes(
-    mut user_settings_file_rx: mpsc::UnboundedReceiver<String>,
-    cx: &mut AppContext,
-) {
-    let user_settings_content = cx
-        .background_executor()
-        .block(user_settings_file_rx.next())
-        .unwrap();
-    cx.update_global(|store: &mut SettingsStore, cx| {
-        store
-            .set_user_settings(&user_settings_content, cx)
-            .log_err();
-    });
-    cx.spawn(move |mut cx| async move {
-        while let Some(user_settings_content) = user_settings_file_rx.next().await {
-            eprintln!("settings file changed");
-            let result = cx.update_global(|store: &mut SettingsStore, cx| {
-                store
-                    .set_user_settings(&user_settings_content, cx)
-                    .log_err();
-                cx.refresh();
-            });
-            if result.is_err() {
-                break; // App dropped
-            }
-        }
-    })
-    .detach();
-}
-
-async fn load_settings(fs: &Arc<dyn Fs>) -> Result<String> {
-    match fs.load(&paths::SETTINGS).await {
-        result @ Ok(_) => result,
-        Err(err) => {
-            if let Some(e) = err.downcast_ref::<std::io::Error>() {
-                if e.kind() == ErrorKind::NotFound {
-                    return Ok(crate::initial_user_settings_content().to_string());
-                }
-            }
-            return Err(err);
-        }
-    }
-}
-
-pub fn update_settings_file<T: Settings>(
-    fs: Arc<dyn Fs>,
-    cx: &mut AppContext,
-    update: impl 'static + Send + FnOnce(&mut T::FileContent),
-) {
-    cx.spawn(|cx| async move {
-        let old_text = load_settings(&fs).await?;
-        let new_text = cx.read_global(|store: &SettingsStore, _cx| {
-            store.new_text_for_update::<T>(old_text, update)
-        })?;
-        fs.atomic_write(paths::SETTINGS.clone(), new_text).await?;
-        anyhow::Ok(())
-    })
-    .detach_and_log_err(cx);
-}
-
-pub fn load_default_keymap(cx: &mut AppContext) {
-    for path in ["keymaps/default.json", "keymaps/vim.json"] {
-        KeymapFile::load_asset(path, cx).unwrap();
-    }
-
-    // todo!()
-    // if let Some(asset_path) = settings::get::<BaseKeymap>(cx).asset_path() {
-    //     KeymapFile::load_asset(asset_path, cx).unwrap();
-    // }
-}

crates/settings2/src/settings_store.rs 🔗

@@ -1,1304 +0,0 @@
-use anyhow::{anyhow, Context, Result};
-use collections::{btree_map, hash_map, BTreeMap, HashMap};
-use gpui::AppContext;
-use lazy_static::lazy_static;
-use schemars::{gen::SchemaGenerator, schema::RootSchema, JsonSchema};
-use serde::{de::DeserializeOwned, Deserialize as _, Serialize};
-use smallvec::SmallVec;
-use std::{
-    any::{type_name, Any, TypeId},
-    fmt::Debug,
-    ops::Range,
-    path::Path,
-    str,
-    sync::Arc,
-};
-use util::{merge_non_null_json_value_into, RangeExt, ResultExt as _};
-
-/// A value that can be defined as a user setting.
-///
-/// Settings can be loaded from a combination of multiple JSON files.
-pub trait Settings: 'static + Send + Sync {
-    /// The name of a key within the JSON file from which this setting should
-    /// be deserialized. If this is `None`, then the setting will be deserialized
-    /// from the root object.
-    const KEY: Option<&'static str>;
-
-    /// The type that is stored in an individual JSON file.
-    type FileContent: Clone + Default + Serialize + DeserializeOwned + JsonSchema;
-
-    /// The logic for combining together values from one or more JSON files into the
-    /// final value for this setting.
-    ///
-    /// The user values are ordered from least specific (the global settings file)
-    /// to most specific (the innermost local settings file).
-    fn load(
-        default_value: &Self::FileContent,
-        user_values: &[&Self::FileContent],
-        cx: &mut AppContext,
-    ) -> Result<Self>
-    where
-        Self: Sized;
-
-    fn json_schema(
-        generator: &mut SchemaGenerator,
-        _: &SettingsJsonSchemaParams,
-        _: &AppContext,
-    ) -> RootSchema {
-        generator.root_schema_for::<Self::FileContent>()
-    }
-
-    fn json_merge(
-        default_value: &Self::FileContent,
-        user_values: &[&Self::FileContent],
-    ) -> Result<Self::FileContent> {
-        let mut merged = serde_json::Value::Null;
-        for value in [default_value].iter().chain(user_values) {
-            merge_non_null_json_value_into(serde_json::to_value(value).unwrap(), &mut merged);
-        }
-        Ok(serde_json::from_value(merged)?)
-    }
-
-    fn load_via_json_merge(
-        default_value: &Self::FileContent,
-        user_values: &[&Self::FileContent],
-    ) -> Result<Self>
-    where
-        Self: DeserializeOwned,
-    {
-        let mut merged = serde_json::Value::Null;
-        for value in [default_value].iter().chain(user_values) {
-            merge_non_null_json_value_into(serde_json::to_value(value).unwrap(), &mut merged);
-        }
-        Ok(serde_json::from_value(merged)?)
-    }
-
-    fn missing_default() -> anyhow::Error {
-        anyhow::anyhow!("missing default")
-    }
-
-    fn register(cx: &mut AppContext)
-    where
-        Self: Sized,
-    {
-        cx.update_global(|store: &mut SettingsStore, cx| {
-            store.register_setting::<Self>(cx);
-        });
-    }
-
-    #[track_caller]
-    fn get<'a>(path: Option<(usize, &Path)>, cx: &'a AppContext) -> &'a Self
-    where
-        Self: Sized,
-    {
-        cx.global::<SettingsStore>().get(path)
-    }
-
-    #[track_caller]
-    fn get_global<'a>(cx: &'a AppContext) -> &'a Self
-    where
-        Self: Sized,
-    {
-        cx.global::<SettingsStore>().get(None)
-    }
-
-    #[track_caller]
-    fn override_global<'a>(settings: Self, cx: &'a mut AppContext)
-    where
-        Self: Sized,
-    {
-        cx.global_mut::<SettingsStore>().override_global(settings)
-    }
-}
-
-pub struct SettingsJsonSchemaParams<'a> {
-    pub staff_mode: bool,
-    pub language_names: &'a [String],
-}
-
-/// A set of strongly-typed setting values defined via multiple JSON files.
-pub struct SettingsStore {
-    setting_values: HashMap<TypeId, Box<dyn AnySettingValue>>,
-    raw_default_settings: serde_json::Value,
-    raw_user_settings: serde_json::Value,
-    raw_local_settings: BTreeMap<(usize, Arc<Path>), serde_json::Value>,
-    tab_size_callback: Option<(
-        TypeId,
-        Box<dyn Fn(&dyn Any) -> Option<usize> + Send + Sync + 'static>,
-    )>,
-}
-
-impl Default for SettingsStore {
-    fn default() -> Self {
-        SettingsStore {
-            setting_values: Default::default(),
-            raw_default_settings: serde_json::json!({}),
-            raw_user_settings: serde_json::json!({}),
-            raw_local_settings: Default::default(),
-            tab_size_callback: Default::default(),
-        }
-    }
-}
-
-#[derive(Debug)]
-struct SettingValue<T> {
-    global_value: Option<T>,
-    local_values: Vec<(usize, Arc<Path>, T)>,
-}
-
-trait AnySettingValue: 'static + Send + Sync {
-    fn key(&self) -> Option<&'static str>;
-    fn setting_type_name(&self) -> &'static str;
-    fn deserialize_setting(&self, json: &serde_json::Value) -> Result<DeserializedSetting>;
-    fn load_setting(
-        &self,
-        default_value: &DeserializedSetting,
-        custom: &[DeserializedSetting],
-        cx: &mut AppContext,
-    ) -> Result<Box<dyn Any>>;
-    fn value_for_path(&self, path: Option<(usize, &Path)>) -> &dyn Any;
-    fn set_global_value(&mut self, value: Box<dyn Any>);
-    fn set_local_value(&mut self, root_id: usize, path: Arc<Path>, value: Box<dyn Any>);
-    fn json_schema(
-        &self,
-        generator: &mut SchemaGenerator,
-        _: &SettingsJsonSchemaParams,
-        cx: &AppContext,
-    ) -> RootSchema;
-}
-
-struct DeserializedSetting(Box<dyn Any>);
-
-impl SettingsStore {
-    /// Add a new type of setting to the store.
-    pub fn register_setting<T: Settings>(&mut self, cx: &mut AppContext) {
-        let setting_type_id = TypeId::of::<T>();
-        let entry = self.setting_values.entry(setting_type_id);
-        if matches!(entry, hash_map::Entry::Occupied(_)) {
-            return;
-        }
-
-        let setting_value = entry.or_insert(Box::new(SettingValue::<T> {
-            global_value: None,
-            local_values: Vec::new(),
-        }));
-
-        if let Some(default_settings) = setting_value
-            .deserialize_setting(&self.raw_default_settings)
-            .log_err()
-        {
-            let mut user_values_stack = Vec::new();
-
-            if let Some(user_settings) = setting_value
-                .deserialize_setting(&self.raw_user_settings)
-                .log_err()
-            {
-                user_values_stack = vec![user_settings];
-            }
-
-            if let Some(setting) = setting_value
-                .load_setting(&default_settings, &user_values_stack, cx)
-                .context("A default setting must be added to the `default.json` file")
-                .log_err()
-            {
-                setting_value.set_global_value(setting);
-            }
-        }
-    }
-
-    /// Get the value of a setting.
-    ///
-    /// Panics if the given setting type has not been registered, or if there is no
-    /// value for this setting.
-    pub fn get<T: Settings>(&self, path: Option<(usize, &Path)>) -> &T {
-        self.setting_values
-            .get(&TypeId::of::<T>())
-            .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
-            .value_for_path(path)
-            .downcast_ref::<T>()
-            .expect("no default value for setting type")
-    }
-
-    /// Override the global value for a setting.
-    ///
-    /// The given value will be overwritten if the user settings file changes.
-    pub fn override_global<T: Settings>(&mut self, value: T) {
-        self.setting_values
-            .get_mut(&TypeId::of::<T>())
-            .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
-            .set_global_value(Box::new(value))
-    }
-
-    /// Get the user's settings as a raw JSON value.
-    ///
-    /// This is only for debugging and reporting. For user-facing functionality,
-    /// use the typed setting interface.
-    pub fn raw_user_settings(&self) -> &serde_json::Value {
-        &self.raw_user_settings
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn test(cx: &mut AppContext) -> Self {
-        let mut this = Self::default();
-        this.set_default_settings(&crate::test_settings(), cx)
-            .unwrap();
-        this.set_user_settings("{}", cx).unwrap();
-        this
-    }
-
-    /// Update the value of a setting in the user's global configuration.
-    ///
-    /// This is only for tests. Normally, settings are only loaded from
-    /// JSON files.
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn update_user_settings<T: Settings>(
-        &mut self,
-        cx: &mut AppContext,
-        update: impl FnOnce(&mut T::FileContent),
-    ) {
-        let old_text = serde_json::to_string(&self.raw_user_settings).unwrap();
-        let new_text = self.new_text_for_update::<T>(old_text, update);
-        self.set_user_settings(&new_text, cx).unwrap();
-    }
-
-    /// Update the value of a setting in a JSON file, returning the new text
-    /// for that JSON file.
-    pub fn new_text_for_update<T: Settings>(
-        &self,
-        old_text: String,
-        update: impl FnOnce(&mut T::FileContent),
-    ) -> String {
-        let edits = self.edits_for_update::<T>(&old_text, update);
-        let mut new_text = old_text;
-        for (range, replacement) in edits.into_iter() {
-            new_text.replace_range(range, &replacement);
-        }
-        new_text
-    }
-
-    /// Update the value of a setting in a JSON file, returning a list
-    /// of edits to apply to the JSON file.
-    pub fn edits_for_update<T: Settings>(
-        &self,
-        text: &str,
-        update: impl FnOnce(&mut T::FileContent),
-    ) -> Vec<(Range<usize>, String)> {
-        let setting_type_id = TypeId::of::<T>();
-
-        let setting = self
-            .setting_values
-            .get(&setting_type_id)
-            .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()));
-        let raw_settings = parse_json_with_comments::<serde_json::Value>(text).unwrap_or_default();
-        let old_content = match setting.deserialize_setting(&raw_settings) {
-            Ok(content) => content.0.downcast::<T::FileContent>().unwrap(),
-            Err(_) => Box::new(T::FileContent::default()),
-        };
-        let mut new_content = old_content.clone();
-        update(&mut new_content);
-
-        let old_value = serde_json::to_value(&old_content).unwrap();
-        let new_value = serde_json::to_value(new_content).unwrap();
-
-        let mut key_path = Vec::new();
-        if let Some(key) = T::KEY {
-            key_path.push(key);
-        }
-
-        let mut edits = Vec::new();
-        let tab_size = self.json_tab_size();
-        let mut text = text.to_string();
-        update_value_in_json_text(
-            &mut text,
-            &mut key_path,
-            tab_size,
-            &old_value,
-            &new_value,
-            &mut edits,
-        );
-        return edits;
-    }
-
-    /// Configure the tab sized when updating JSON files.
-    pub fn set_json_tab_size_callback<T: Settings>(
-        &mut self,
-        get_tab_size: fn(&T) -> Option<usize>,
-    ) {
-        self.tab_size_callback = Some((
-            TypeId::of::<T>(),
-            Box::new(move |value| get_tab_size(value.downcast_ref::<T>().unwrap())),
-        ));
-    }
-
-    fn json_tab_size(&self) -> usize {
-        const DEFAULT_JSON_TAB_SIZE: usize = 2;
-
-        if let Some((setting_type_id, callback)) = &self.tab_size_callback {
-            let setting_value = self.setting_values.get(setting_type_id).unwrap();
-            let value = setting_value.value_for_path(None);
-            if let Some(value) = callback(value) {
-                return value;
-            }
-        }
-
-        DEFAULT_JSON_TAB_SIZE
-    }
-
-    /// Set the default settings via a JSON string.
-    ///
-    /// The string should contain a JSON object with a default value for every setting.
-    pub fn set_default_settings(
-        &mut self,
-        default_settings_content: &str,
-        cx: &mut AppContext,
-    ) -> Result<()> {
-        let settings: serde_json::Value = parse_json_with_comments(default_settings_content)?;
-        if settings.is_object() {
-            self.raw_default_settings = settings;
-            self.recompute_values(None, cx)?;
-            Ok(())
-        } else {
-            Err(anyhow!("settings must be an object"))
-        }
-    }
-
-    /// Set the user settings via a JSON string.
-    pub fn set_user_settings(
-        &mut self,
-        user_settings_content: &str,
-        cx: &mut AppContext,
-    ) -> Result<()> {
-        let settings: serde_json::Value = parse_json_with_comments(user_settings_content)?;
-        if settings.is_object() {
-            self.raw_user_settings = settings;
-            self.recompute_values(None, cx)?;
-            Ok(())
-        } else {
-            Err(anyhow!("settings must be an object"))
-        }
-    }
-
-    /// Add or remove a set of local settings via a JSON string.
-    pub fn set_local_settings(
-        &mut self,
-        root_id: usize,
-        path: Arc<Path>,
-        settings_content: Option<&str>,
-        cx: &mut AppContext,
-    ) -> Result<()> {
-        if let Some(content) = settings_content {
-            self.raw_local_settings
-                .insert((root_id, path.clone()), parse_json_with_comments(content)?);
-        } else {
-            self.raw_local_settings.remove(&(root_id, path.clone()));
-        }
-        self.recompute_values(Some((root_id, &path)), cx)?;
-        Ok(())
-    }
-
-    /// Add or remove a set of local settings via a JSON string.
-    pub fn clear_local_settings(&mut self, root_id: usize, cx: &mut AppContext) -> Result<()> {
-        self.raw_local_settings.retain(|k, _| k.0 != root_id);
-        self.recompute_values(Some((root_id, "".as_ref())), cx)?;
-        Ok(())
-    }
-
-    pub fn local_settings(&self, root_id: usize) -> impl '_ + Iterator<Item = (Arc<Path>, String)> {
-        self.raw_local_settings
-            .range((root_id, Path::new("").into())..(root_id + 1, Path::new("").into()))
-            .map(|((_, path), content)| (path.clone(), serde_json::to_string(content).unwrap()))
-    }
-
-    pub fn json_schema(
-        &self,
-        schema_params: &SettingsJsonSchemaParams,
-        cx: &AppContext,
-    ) -> serde_json::Value {
-        use schemars::{
-            gen::SchemaSettings,
-            schema::{Schema, SchemaObject},
-        };
-
-        let settings = SchemaSettings::draft07().with(|settings| {
-            settings.option_add_null_type = false;
-        });
-        let mut generator = SchemaGenerator::new(settings);
-        let mut combined_schema = RootSchema::default();
-
-        for setting_value in self.setting_values.values() {
-            let setting_schema = setting_value.json_schema(&mut generator, schema_params, cx);
-            combined_schema
-                .definitions
-                .extend(setting_schema.definitions);
-
-            let target_schema = if let Some(key) = setting_value.key() {
-                let key_schema = combined_schema
-                    .schema
-                    .object()
-                    .properties
-                    .entry(key.to_string())
-                    .or_insert_with(|| Schema::Object(SchemaObject::default()));
-                if let Schema::Object(key_schema) = key_schema {
-                    key_schema
-                } else {
-                    continue;
-                }
-            } else {
-                &mut combined_schema.schema
-            };
-
-            merge_schema(target_schema, setting_schema.schema);
-        }
-
-        fn merge_schema(target: &mut SchemaObject, source: SchemaObject) {
-            if let Some(source) = source.object {
-                let target_properties = &mut target.object().properties;
-                for (key, value) in source.properties {
-                    match target_properties.entry(key) {
-                        btree_map::Entry::Vacant(e) => {
-                            e.insert(value);
-                        }
-                        btree_map::Entry::Occupied(e) => {
-                            if let (Schema::Object(target), Schema::Object(src)) =
-                                (e.into_mut(), value)
-                            {
-                                merge_schema(target, src);
-                            }
-                        }
-                    }
-                }
-            }
-
-            overwrite(&mut target.instance_type, source.instance_type);
-            overwrite(&mut target.string, source.string);
-            overwrite(&mut target.number, source.number);
-            overwrite(&mut target.reference, source.reference);
-            overwrite(&mut target.array, source.array);
-            overwrite(&mut target.enum_values, source.enum_values);
-
-            fn overwrite<T>(target: &mut Option<T>, source: Option<T>) {
-                if let Some(source) = source {
-                    *target = Some(source);
-                }
-            }
-        }
-
-        serde_json::to_value(&combined_schema).unwrap()
-    }
-
-    fn recompute_values(
-        &mut self,
-        changed_local_path: Option<(usize, &Path)>,
-        cx: &mut AppContext,
-    ) -> Result<()> {
-        // Reload the global and local values for every setting.
-        let mut user_settings_stack = Vec::<DeserializedSetting>::new();
-        let mut paths_stack = Vec::<Option<(usize, &Path)>>::new();
-        for setting_value in self.setting_values.values_mut() {
-            let default_settings = setting_value.deserialize_setting(&self.raw_default_settings)?;
-
-            user_settings_stack.clear();
-            paths_stack.clear();
-
-            if let Some(user_settings) = setting_value
-                .deserialize_setting(&self.raw_user_settings)
-                .log_err()
-            {
-                user_settings_stack.push(user_settings);
-                paths_stack.push(None);
-            }
-
-            // If the global settings file changed, reload the global value for the field.
-            if changed_local_path.is_none() {
-                if let Some(value) = setting_value
-                    .load_setting(&default_settings, &user_settings_stack, cx)
-                    .log_err()
-                {
-                    setting_value.set_global_value(value);
-                }
-            }
-
-            // Reload the local values for the setting.
-            for ((root_id, path), local_settings) in &self.raw_local_settings {
-                // Build a stack of all of the local values for that setting.
-                while let Some(prev_entry) = paths_stack.last() {
-                    if let Some((prev_root_id, prev_path)) = prev_entry {
-                        if root_id != prev_root_id || !path.starts_with(prev_path) {
-                            paths_stack.pop();
-                            user_settings_stack.pop();
-                            continue;
-                        }
-                    }
-                    break;
-                }
-
-                if let Some(local_settings) =
-                    setting_value.deserialize_setting(&local_settings).log_err()
-                {
-                    paths_stack.push(Some((*root_id, path.as_ref())));
-                    user_settings_stack.push(local_settings);
-
-                    // If a local settings file changed, then avoid recomputing local
-                    // settings for any path outside of that directory.
-                    if changed_local_path.map_or(false, |(changed_root_id, changed_local_path)| {
-                        *root_id != changed_root_id || !path.starts_with(changed_local_path)
-                    }) {
-                        continue;
-                    }
-
-                    if let Some(value) = setting_value
-                        .load_setting(&default_settings, &user_settings_stack, cx)
-                        .log_err()
-                    {
-                        setting_value.set_local_value(*root_id, path.clone(), value);
-                    }
-                }
-            }
-        }
-        Ok(())
-    }
-}
-
-impl Debug for SettingsStore {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("SettingsStore")
-            .field(
-                "types",
-                &self
-                    .setting_values
-                    .values()
-                    .map(|value| value.setting_type_name())
-                    .collect::<Vec<_>>(),
-            )
-            .field("default_settings", &self.raw_default_settings)
-            .field("user_settings", &self.raw_user_settings)
-            .field("local_settings", &self.raw_local_settings)
-            .finish_non_exhaustive()
-    }
-}
-
-impl<T: Settings> AnySettingValue for SettingValue<T> {
-    fn key(&self) -> Option<&'static str> {
-        T::KEY
-    }
-
-    fn setting_type_name(&self) -> &'static str {
-        type_name::<T>()
-    }
-
-    fn load_setting(
-        &self,
-        default_value: &DeserializedSetting,
-        user_values: &[DeserializedSetting],
-        cx: &mut AppContext,
-    ) -> Result<Box<dyn Any>> {
-        let default_value = default_value.0.downcast_ref::<T::FileContent>().unwrap();
-        let values: SmallVec<[&T::FileContent; 6]> = user_values
-            .iter()
-            .map(|value| value.0.downcast_ref().unwrap())
-            .collect();
-        Ok(Box::new(T::load(default_value, &values, cx)?))
-    }
-
-    fn deserialize_setting(&self, mut json: &serde_json::Value) -> Result<DeserializedSetting> {
-        if let Some(key) = T::KEY {
-            if let Some(value) = json.get(key) {
-                json = value;
-            } else {
-                let value = T::FileContent::default();
-                return Ok(DeserializedSetting(Box::new(value)));
-            }
-        }
-        let value = T::FileContent::deserialize(json)?;
-        Ok(DeserializedSetting(Box::new(value)))
-    }
-
-    fn value_for_path(&self, path: Option<(usize, &Path)>) -> &dyn Any {
-        if let Some((root_id, path)) = path {
-            for (settings_root_id, settings_path, value) in self.local_values.iter().rev() {
-                if root_id == *settings_root_id && path.starts_with(&settings_path) {
-                    return value;
-                }
-            }
-        }
-        self.global_value
-            .as_ref()
-            .unwrap_or_else(|| panic!("no default value for setting {}", self.setting_type_name()))
-    }
-
-    fn set_global_value(&mut self, value: Box<dyn Any>) {
-        self.global_value = Some(*value.downcast().unwrap());
-    }
-
-    fn set_local_value(&mut self, root_id: usize, path: Arc<Path>, value: Box<dyn Any>) {
-        let value = *value.downcast().unwrap();
-        match self
-            .local_values
-            .binary_search_by_key(&(root_id, &path), |e| (e.0, &e.1))
-        {
-            Ok(ix) => self.local_values[ix].2 = value,
-            Err(ix) => self.local_values.insert(ix, (root_id, path, value)),
-        }
-    }
-
-    fn json_schema(
-        &self,
-        generator: &mut SchemaGenerator,
-        params: &SettingsJsonSchemaParams,
-        cx: &AppContext,
-    ) -> RootSchema {
-        T::json_schema(generator, params, cx)
-    }
-}
-
-fn update_value_in_json_text<'a>(
-    text: &mut String,
-    key_path: &mut Vec<&'a str>,
-    tab_size: usize,
-    old_value: &'a serde_json::Value,
-    new_value: &'a serde_json::Value,
-    edits: &mut Vec<(Range<usize>, String)>,
-) {
-    // If the old and new values are both objects, then compare them key by key,
-    // preserving the comments and formatting of the unchanged parts. Otherwise,
-    // replace the old value with the new value.
-    if let (serde_json::Value::Object(old_object), serde_json::Value::Object(new_object)) =
-        (old_value, new_value)
-    {
-        for (key, old_sub_value) in old_object.iter() {
-            key_path.push(key);
-            let new_sub_value = new_object.get(key).unwrap_or(&serde_json::Value::Null);
-            update_value_in_json_text(
-                text,
-                key_path,
-                tab_size,
-                old_sub_value,
-                new_sub_value,
-                edits,
-            );
-            key_path.pop();
-        }
-        for (key, new_sub_value) in new_object.iter() {
-            key_path.push(key);
-            if !old_object.contains_key(key) {
-                update_value_in_json_text(
-                    text,
-                    key_path,
-                    tab_size,
-                    &serde_json::Value::Null,
-                    new_sub_value,
-                    edits,
-                );
-            }
-            key_path.pop();
-        }
-    } else if old_value != new_value {
-        let mut new_value = new_value.clone();
-        if let Some(new_object) = new_value.as_object_mut() {
-            new_object.retain(|_, v| !v.is_null());
-        }
-        let (range, replacement) =
-            replace_value_in_json_text(text, &key_path, tab_size, &new_value);
-        text.replace_range(range.clone(), &replacement);
-        edits.push((range, replacement));
-    }
-}
-
-fn replace_value_in_json_text(
-    text: &str,
-    key_path: &[&str],
-    tab_size: usize,
-    new_value: &serde_json::Value,
-) -> (Range<usize>, String) {
-    const LANGUAGE_OVERRIDES: &'static str = "language_overrides";
-    const LANGUAGES: &'static str = "languages";
-
-    lazy_static! {
-        static ref PAIR_QUERY: tree_sitter::Query = tree_sitter::Query::new(
-            &tree_sitter_json::language(),
-            "(pair key: (string) @key value: (_) @value)",
-        )
-        .unwrap();
-    }
-
-    let mut parser = tree_sitter::Parser::new();
-    parser.set_language(&tree_sitter_json::language()).unwrap();
-    let syntax_tree = parser.parse(text, None).unwrap();
-
-    let mut cursor = tree_sitter::QueryCursor::new();
-
-    let has_language_overrides = text.contains(LANGUAGE_OVERRIDES);
-
-    let mut depth = 0;
-    let mut last_value_range = 0..0;
-    let mut first_key_start = None;
-    let mut existing_value_range = 0..text.len();
-    let matches = cursor.matches(&PAIR_QUERY, syntax_tree.root_node(), text.as_bytes());
-    for mat in matches {
-        if mat.captures.len() != 2 {
-            continue;
-        }
-
-        let key_range = mat.captures[0].node.byte_range();
-        let value_range = mat.captures[1].node.byte_range();
-
-        // Don't enter sub objects until we find an exact
-        // match for the current keypath
-        if last_value_range.contains_inclusive(&value_range) {
-            continue;
-        }
-
-        last_value_range = value_range.clone();
-
-        if key_range.start > existing_value_range.end {
-            break;
-        }
-
-        first_key_start.get_or_insert_with(|| key_range.start);
-
-        let found_key = text
-            .get(key_range.clone())
-            .map(|key_text| {
-                if key_path[depth] == LANGUAGES && has_language_overrides {
-                    return key_text == format!("\"{}\"", LANGUAGE_OVERRIDES);
-                } else {
-                    return key_text == format!("\"{}\"", key_path[depth]);
-                }
-            })
-            .unwrap_or(false);
-
-        if found_key {
-            existing_value_range = value_range;
-            // Reset last value range when increasing in depth
-            last_value_range = existing_value_range.start..existing_value_range.start;
-            depth += 1;
-
-            if depth == key_path.len() {
-                break;
-            } else {
-                first_key_start = None;
-            }
-        }
-    }
-
-    // We found the exact key we want, insert the new value
-    if depth == key_path.len() {
-        let new_val = to_pretty_json(&new_value, tab_size, tab_size * depth);
-        (existing_value_range, new_val)
-    } else {
-        // We have key paths, construct the sub objects
-        let new_key = if has_language_overrides && key_path[depth] == LANGUAGES {
-            LANGUAGE_OVERRIDES
-        } else {
-            key_path[depth]
-        };
-
-        // We don't have the key, construct the nested objects
-        let mut new_value = serde_json::to_value(new_value).unwrap();
-        for key in key_path[(depth + 1)..].iter().rev() {
-            if has_language_overrides && key == &LANGUAGES {
-                new_value = serde_json::json!({ LANGUAGE_OVERRIDES.to_string(): new_value });
-            } else {
-                new_value = serde_json::json!({ key.to_string(): new_value });
-            }
-        }
-
-        if let Some(first_key_start) = first_key_start {
-            let mut row = 0;
-            let mut column = 0;
-            for (ix, char) in text.char_indices() {
-                if ix == first_key_start {
-                    break;
-                }
-                if char == '\n' {
-                    row += 1;
-                    column = 0;
-                } else {
-                    column += char.len_utf8();
-                }
-            }
-
-            if row > 0 {
-                // depth is 0 based, but division needs to be 1 based.
-                let new_val = to_pretty_json(&new_value, column / (depth + 1), column);
-                let space = ' ';
-                let content = format!("\"{new_key}\": {new_val},\n{space:width$}", width = column);
-                (first_key_start..first_key_start, content)
-            } else {
-                let new_val = serde_json::to_string(&new_value).unwrap();
-                let mut content = format!(r#""{new_key}": {new_val},"#);
-                content.push(' ');
-                (first_key_start..first_key_start, content)
-            }
-        } else {
-            new_value = serde_json::json!({ new_key.to_string(): new_value });
-            let indent_prefix_len = 4 * depth;
-            let mut new_val = to_pretty_json(&new_value, 4, indent_prefix_len);
-            if depth == 0 {
-                new_val.push('\n');
-            }
-
-            (existing_value_range, new_val)
-        }
-    }
-}
-
-fn to_pretty_json(value: &impl Serialize, indent_size: usize, indent_prefix_len: usize) -> String {
-    const SPACES: [u8; 32] = [b' '; 32];
-
-    debug_assert!(indent_size <= SPACES.len());
-    debug_assert!(indent_prefix_len <= SPACES.len());
-
-    let mut output = Vec::new();
-    let mut ser = serde_json::Serializer::with_formatter(
-        &mut output,
-        serde_json::ser::PrettyFormatter::with_indent(&SPACES[0..indent_size.min(SPACES.len())]),
-    );
-
-    value.serialize(&mut ser).unwrap();
-    let text = String::from_utf8(output).unwrap();
-
-    let mut adjusted_text = String::new();
-    for (i, line) in text.split('\n').enumerate() {
-        if i > 0 {
-            adjusted_text.push_str(str::from_utf8(&SPACES[0..indent_prefix_len]).unwrap());
-        }
-        adjusted_text.push_str(line);
-        adjusted_text.push('\n');
-    }
-    adjusted_text.pop();
-    adjusted_text
-}
-
-pub fn parse_json_with_comments<T: DeserializeOwned>(content: &str) -> Result<T> {
-    Ok(serde_json_lenient::from_str(content)?)
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use serde_derive::Deserialize;
-    use unindent::Unindent;
-
-    #[gpui::test]
-    fn test_settings_store_basic(cx: &mut AppContext) {
-        let mut store = SettingsStore::default();
-        store.register_setting::<UserSettings>(cx);
-        store.register_setting::<TurboSetting>(cx);
-        store.register_setting::<MultiKeySettings>(cx);
-        store
-            .set_default_settings(
-                r#"{
-                    "turbo": false,
-                    "user": {
-                        "name": "John Doe",
-                        "age": 30,
-                        "staff": false
-                    }
-                }"#,
-                cx,
-            )
-            .unwrap();
-
-        assert_eq!(store.get::<TurboSetting>(None), &TurboSetting(false));
-        assert_eq!(
-            store.get::<UserSettings>(None),
-            &UserSettings {
-                name: "John Doe".to_string(),
-                age: 30,
-                staff: false,
-            }
-        );
-        assert_eq!(
-            store.get::<MultiKeySettings>(None),
-            &MultiKeySettings {
-                key1: String::new(),
-                key2: String::new(),
-            }
-        );
-
-        store
-            .set_user_settings(
-                r#"{
-                    "turbo": true,
-                    "user": { "age": 31 },
-                    "key1": "a"
-                }"#,
-                cx,
-            )
-            .unwrap();
-
-        assert_eq!(store.get::<TurboSetting>(None), &TurboSetting(true));
-        assert_eq!(
-            store.get::<UserSettings>(None),
-            &UserSettings {
-                name: "John Doe".to_string(),
-                age: 31,
-                staff: false
-            }
-        );
-
-        store
-            .set_local_settings(
-                1,
-                Path::new("/root1").into(),
-                Some(r#"{ "user": { "staff": true } }"#),
-                cx,
-            )
-            .unwrap();
-        store
-            .set_local_settings(
-                1,
-                Path::new("/root1/subdir").into(),
-                Some(r#"{ "user": { "name": "Jane Doe" } }"#),
-                cx,
-            )
-            .unwrap();
-
-        store
-            .set_local_settings(
-                1,
-                Path::new("/root2").into(),
-                Some(r#"{ "user": { "age": 42 }, "key2": "b" }"#),
-                cx,
-            )
-            .unwrap();
-
-        assert_eq!(
-            store.get::<UserSettings>(Some((1, Path::new("/root1/something")))),
-            &UserSettings {
-                name: "John Doe".to_string(),
-                age: 31,
-                staff: true
-            }
-        );
-        assert_eq!(
-            store.get::<UserSettings>(Some((1, Path::new("/root1/subdir/something")))),
-            &UserSettings {
-                name: "Jane Doe".to_string(),
-                age: 31,
-                staff: true
-            }
-        );
-        assert_eq!(
-            store.get::<UserSettings>(Some((1, Path::new("/root2/something")))),
-            &UserSettings {
-                name: "John Doe".to_string(),
-                age: 42,
-                staff: false
-            }
-        );
-        assert_eq!(
-            store.get::<MultiKeySettings>(Some((1, Path::new("/root2/something")))),
-            &MultiKeySettings {
-                key1: "a".to_string(),
-                key2: "b".to_string(),
-            }
-        );
-    }
-
-    #[gpui::test]
-    fn test_setting_store_assign_json_before_register(cx: &mut AppContext) {
-        let mut store = SettingsStore::default();
-        store
-            .set_default_settings(
-                r#"{
-                    "turbo": true,
-                    "user": {
-                        "name": "John Doe",
-                        "age": 30,
-                        "staff": false
-                    },
-                    "key1": "x"
-                }"#,
-                cx,
-            )
-            .unwrap();
-        store
-            .set_user_settings(r#"{ "turbo": false }"#, cx)
-            .unwrap();
-        store.register_setting::<UserSettings>(cx);
-        store.register_setting::<TurboSetting>(cx);
-
-        assert_eq!(store.get::<TurboSetting>(None), &TurboSetting(false));
-        assert_eq!(
-            store.get::<UserSettings>(None),
-            &UserSettings {
-                name: "John Doe".to_string(),
-                age: 30,
-                staff: false,
-            }
-        );
-
-        store.register_setting::<MultiKeySettings>(cx);
-        assert_eq!(
-            store.get::<MultiKeySettings>(None),
-            &MultiKeySettings {
-                key1: "x".into(),
-                key2: String::new(),
-            }
-        );
-    }
-
-    #[gpui::test]
-    fn test_setting_store_update(cx: &mut AppContext) {
-        let mut store = SettingsStore::default();
-        store.register_setting::<MultiKeySettings>(cx);
-        store.register_setting::<UserSettings>(cx);
-        store.register_setting::<LanguageSettings>(cx);
-
-        // entries added and updated
-        check_settings_update::<LanguageSettings>(
-            &mut store,
-            r#"{
-                "languages": {
-                    "JSON": {
-                        "language_setting_1": true
-                    }
-                }
-            }"#
-            .unindent(),
-            |settings| {
-                settings
-                    .languages
-                    .get_mut("JSON")
-                    .unwrap()
-                    .language_setting_1 = Some(false);
-                settings.languages.insert(
-                    "Rust".into(),
-                    LanguageSettingEntry {
-                        language_setting_2: Some(true),
-                        ..Default::default()
-                    },
-                );
-            },
-            r#"{
-                "languages": {
-                    "Rust": {
-                        "language_setting_2": true
-                    },
-                    "JSON": {
-                        "language_setting_1": false
-                    }
-                }
-            }"#
-            .unindent(),
-            cx,
-        );
-
-        // weird formatting
-        check_settings_update::<UserSettings>(
-            &mut store,
-            r#"{
-                "user":   { "age": 36, "name": "Max", "staff": true }
-            }"#
-            .unindent(),
-            |settings| settings.age = Some(37),
-            r#"{
-                "user":   { "age": 37, "name": "Max", "staff": true }
-            }"#
-            .unindent(),
-            cx,
-        );
-
-        // single-line formatting, other keys
-        check_settings_update::<MultiKeySettings>(
-            &mut store,
-            r#"{ "one": 1, "two": 2 }"#.unindent(),
-            |settings| settings.key1 = Some("x".into()),
-            r#"{ "key1": "x", "one": 1, "two": 2 }"#.unindent(),
-            cx,
-        );
-
-        // empty object
-        check_settings_update::<UserSettings>(
-            &mut store,
-            r#"{
-                "user": {}
-            }"#
-            .unindent(),
-            |settings| settings.age = Some(37),
-            r#"{
-                "user": {
-                    "age": 37
-                }
-            }"#
-            .unindent(),
-            cx,
-        );
-
-        // no content
-        check_settings_update::<UserSettings>(
-            &mut store,
-            r#""#.unindent(),
-            |settings| settings.age = Some(37),
-            r#"{
-                "user": {
-                    "age": 37
-                }
-            }
-            "#
-            .unindent(),
-            cx,
-        );
-
-        check_settings_update::<UserSettings>(
-            &mut store,
-            r#"{
-            }
-            "#
-            .unindent(),
-            |settings| settings.age = Some(37),
-            r#"{
-                "user": {
-                    "age": 37
-                }
-            }
-            "#
-            .unindent(),
-            cx,
-        );
-    }
-
-    fn check_settings_update<T: Settings>(
-        store: &mut SettingsStore,
-        old_json: String,
-        update: fn(&mut T::FileContent),
-        expected_new_json: String,
-        cx: &mut AppContext,
-    ) {
-        store.set_user_settings(&old_json, cx).ok();
-        let edits = store.edits_for_update::<T>(&old_json, update);
-        let mut new_json = old_json;
-        for (range, replacement) in edits.into_iter() {
-            new_json.replace_range(range, &replacement);
-        }
-        pretty_assertions::assert_eq!(new_json, expected_new_json);
-    }
-
-    #[derive(Debug, PartialEq, Deserialize)]
-    struct UserSettings {
-        name: String,
-        age: u32,
-        staff: bool,
-    }
-
-    #[derive(Default, Clone, Serialize, Deserialize, JsonSchema)]
-    struct UserSettingsJson {
-        name: Option<String>,
-        age: Option<u32>,
-        staff: Option<bool>,
-    }
-
-    impl Settings for UserSettings {
-        const KEY: Option<&'static str> = Some("user");
-        type FileContent = UserSettingsJson;
-
-        fn load(
-            default_value: &UserSettingsJson,
-            user_values: &[&UserSettingsJson],
-            _: &mut AppContext,
-        ) -> Result<Self> {
-            Self::load_via_json_merge(default_value, user_values)
-        }
-    }
-
-    #[derive(Debug, Deserialize, PartialEq)]
-    struct TurboSetting(bool);
-
-    impl Settings for TurboSetting {
-        const KEY: Option<&'static str> = Some("turbo");
-        type FileContent = Option<bool>;
-
-        fn load(
-            default_value: &Option<bool>,
-            user_values: &[&Option<bool>],
-            _: &mut AppContext,
-        ) -> Result<Self> {
-            Self::load_via_json_merge(default_value, user_values)
-        }
-    }
-
-    #[derive(Clone, Debug, PartialEq, Deserialize)]
-    struct MultiKeySettings {
-        #[serde(default)]
-        key1: String,
-        #[serde(default)]
-        key2: String,
-    }
-
-    #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
-    struct MultiKeySettingsJson {
-        key1: Option<String>,
-        key2: Option<String>,
-    }
-
-    impl Settings for MultiKeySettings {
-        const KEY: Option<&'static str> = None;
-
-        type FileContent = MultiKeySettingsJson;
-
-        fn load(
-            default_value: &MultiKeySettingsJson,
-            user_values: &[&MultiKeySettingsJson],
-            _: &mut AppContext,
-        ) -> Result<Self> {
-            Self::load_via_json_merge(default_value, user_values)
-        }
-    }
-
-    #[derive(Debug, Deserialize)]
-    struct JournalSettings {
-        pub path: String,
-        pub hour_format: HourFormat,
-    }
-
-    #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
-    #[serde(rename_all = "snake_case")]
-    enum HourFormat {
-        Hour12,
-        Hour24,
-    }
-
-    #[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
-    struct JournalSettingsJson {
-        pub path: Option<String>,
-        pub hour_format: Option<HourFormat>,
-    }
-
-    impl Settings for JournalSettings {
-        const KEY: Option<&'static str> = Some("journal");
-
-        type FileContent = JournalSettingsJson;
-
-        fn load(
-            default_value: &JournalSettingsJson,
-            user_values: &[&JournalSettingsJson],
-            _: &mut AppContext,
-        ) -> Result<Self> {
-            Self::load_via_json_merge(default_value, user_values)
-        }
-    }
-
-    #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
-    struct LanguageSettings {
-        #[serde(default)]
-        languages: HashMap<String, LanguageSettingEntry>,
-    }
-
-    #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
-    struct LanguageSettingEntry {
-        language_setting_1: Option<bool>,
-        language_setting_2: Option<bool>,
-    }
-
-    impl Settings for LanguageSettings {
-        const KEY: Option<&'static str> = None;
-
-        type FileContent = Self;
-
-        fn load(default_value: &Self, user_values: &[&Self], _: &mut AppContext) -> Result<Self> {
-            Self::load_via_json_merge(default_value, user_values)
-        }
-    }
-}

crates/story/Cargo.toml 🔗

@@ -7,6 +7,6 @@ publish = false
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
 smallvec.workspace = true
 itertools = {package = "itertools", version = "0.10"}

crates/storybook2/Cargo.toml → crates/storybook/Cargo.toml 🔗

@@ -1,12 +1,12 @@
 [package]
-name = "storybook2"
+name = "storybook"
 version = "0.1.0"
 edition = "2021"
 publish = false
 
 [[bin]]
 name = "storybook"
-path = "src/storybook2.rs"
+path = "src/storybook.rs"
 
 [dependencies]
 anyhow.workspace = true
@@ -14,26 +14,26 @@ anyhow.workspace = true
 backtrace-on-stack-overflow = "0.3.0"
 chrono = "0.4"
 clap = { version = "4.4", features = ["derive", "string"] }
+strum = { version = "0.25.0", features = ["derive"] }
 dialoguer = { version = "0.11.0", features = ["fuzzy-select"] }
-editor = { package = "editor2", path = "../editor2" }
-fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
-gpui = { package = "gpui2", path = "../gpui2" }
+editor = { path = "../editor" }
+fuzzy = {  path = "../fuzzy" }
+gpui = { path = "../gpui" }
 indoc.workspace = true
 itertools = "0.11.0"
-language = { package = "language2", path = "../language2" }
+language = { path = "../language" }
 log.workspace = true
 rust-embed.workspace = true
 serde.workspace = true
-settings2 = { path = "../settings2" }
+settings = { path = "../settings" }
 simplelog = "0.9"
 smallvec.workspace = true
 story = { path = "../story" }
-strum = { version = "0.25.0", features = ["derive"] }
-theme2 = { path = "../theme2" }
-menu = { package = "menu2", path = "../menu2" }
-ui = { package = "ui2", path = "../ui2", features = ["stories"] }
+theme = { path = "../theme" }
+menu = { path = "../menu" }
+ui = { path = "../ui", features = ["stories"] }
 util = { path = "../util" }
-picker = { package = "picker2", path = "../picker2" }
+picker = { path = "../picker" }
 
 [dev-dependencies]
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }

crates/storybook2/src/stories/text.rs → crates/storybook/src/stories/text.rs 🔗

@@ -15,7 +15,7 @@ impl TextStory {
 
 impl Render for TextStory {
     fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
-        StoryContainer::new("Text Story", "crates/storybook2/src/stories/text.rs")
+        StoryContainer::new("Text Story", "crates/storybook/src/stories/text.rs")
             .children(
                 vec![
 

crates/storybook2/src/storybook2.rs → crates/storybook/src/storybook.rs 🔗

@@ -11,10 +11,10 @@ use gpui::{
     WindowOptions,
 };
 use log::LevelFilter;
-use settings2::{default_settings, Settings, SettingsStore};
+use settings::{default_settings, Settings, SettingsStore};
 use simplelog::SimpleLogger;
 use strum::IntoEnumIterator;
-use theme2::{ThemeRegistry, ThemeSettings};
+use theme::{ThemeRegistry, ThemeSettings};
 use ui::prelude::*;
 
 use crate::assets::Assets;
@@ -69,7 +69,7 @@ fn main() {
             .unwrap();
         cx.set_global(store);
 
-        theme2::init(theme2::LoadThemes::All, cx);
+        theme::init(theme::LoadThemes::All, cx);
 
         let selector = story_selector;
 

crates/terminal/src/mappings/colors.rs 🔗

@@ -1,130 +1,12 @@
-use alacritty_terminal::{ansi::Color as AnsiColor, term::color::Rgb as AlacRgb};
-use gpui::color::Color;
-use theme::TerminalStyle;
+use alacritty_terminal::term::color::Rgb as AlacRgb;
 
-///Converts a 2, 8, or 24 bit color ANSI color to the GPUI equivalent
-pub fn convert_color(alac_color: &AnsiColor, style: &TerminalStyle) -> Color {
-    match alac_color {
-        //Named and theme defined colors
-        alacritty_terminal::ansi::Color::Named(n) => match n {
-            alacritty_terminal::ansi::NamedColor::Black => style.black,
-            alacritty_terminal::ansi::NamedColor::Red => style.red,
-            alacritty_terminal::ansi::NamedColor::Green => style.green,
-            alacritty_terminal::ansi::NamedColor::Yellow => style.yellow,
-            alacritty_terminal::ansi::NamedColor::Blue => style.blue,
-            alacritty_terminal::ansi::NamedColor::Magenta => style.magenta,
-            alacritty_terminal::ansi::NamedColor::Cyan => style.cyan,
-            alacritty_terminal::ansi::NamedColor::White => style.white,
-            alacritty_terminal::ansi::NamedColor::BrightBlack => style.bright_black,
-            alacritty_terminal::ansi::NamedColor::BrightRed => style.bright_red,
-            alacritty_terminal::ansi::NamedColor::BrightGreen => style.bright_green,
-            alacritty_terminal::ansi::NamedColor::BrightYellow => style.bright_yellow,
-            alacritty_terminal::ansi::NamedColor::BrightBlue => style.bright_blue,
-            alacritty_terminal::ansi::NamedColor::BrightMagenta => style.bright_magenta,
-            alacritty_terminal::ansi::NamedColor::BrightCyan => style.bright_cyan,
-            alacritty_terminal::ansi::NamedColor::BrightWhite => style.bright_white,
-            alacritty_terminal::ansi::NamedColor::Foreground => style.foreground,
-            alacritty_terminal::ansi::NamedColor::Background => style.background,
-            alacritty_terminal::ansi::NamedColor::Cursor => style.cursor,
-            alacritty_terminal::ansi::NamedColor::DimBlack => style.dim_black,
-            alacritty_terminal::ansi::NamedColor::DimRed => style.dim_red,
-            alacritty_terminal::ansi::NamedColor::DimGreen => style.dim_green,
-            alacritty_terminal::ansi::NamedColor::DimYellow => style.dim_yellow,
-            alacritty_terminal::ansi::NamedColor::DimBlue => style.dim_blue,
-            alacritty_terminal::ansi::NamedColor::DimMagenta => style.dim_magenta,
-            alacritty_terminal::ansi::NamedColor::DimCyan => style.dim_cyan,
-            alacritty_terminal::ansi::NamedColor::DimWhite => style.dim_white,
-            alacritty_terminal::ansi::NamedColor::BrightForeground => style.bright_foreground,
-            alacritty_terminal::ansi::NamedColor::DimForeground => style.dim_foreground,
-        },
-        //'True' colors
-        alacritty_terminal::ansi::Color::Spec(rgb) => Color::new(rgb.r, rgb.g, rgb.b, u8::MAX),
-        //8 bit, indexed colors
-        alacritty_terminal::ansi::Color::Indexed(i) => get_color_at_index(&(*i as usize), style),
-    }
-}
-
-///Converts an 8 bit ANSI color to it's GPUI equivalent.
-///Accepts usize for compatibility with the alacritty::Colors interface,
-///Other than that use case, should only be called with values in the [0,255] range
-pub fn get_color_at_index(index: &usize, style: &TerminalStyle) -> Color {
-    match index {
-        //0-15 are the same as the named colors above
-        0 => style.black,
-        1 => style.red,
-        2 => style.green,
-        3 => style.yellow,
-        4 => style.blue,
-        5 => style.magenta,
-        6 => style.cyan,
-        7 => style.white,
-        8 => style.bright_black,
-        9 => style.bright_red,
-        10 => style.bright_green,
-        11 => style.bright_yellow,
-        12 => style.bright_blue,
-        13 => style.bright_magenta,
-        14 => style.bright_cyan,
-        15 => style.bright_white,
-        //16-231 are mapped to their RGB colors on a 0-5 range per channel
-        16..=231 => {
-            let (r, g, b) = rgb_for_index(&(*index as u8)); //Split the index into it's ANSI-RGB components
-            let step = (u8::MAX as f32 / 5.).floor() as u8; //Split the RGB range into 5 chunks, with floor so no overflow
-            Color::new(r * step, g * step, b * step, u8::MAX) //Map the ANSI-RGB components to an RGB color
-        }
-        //232-255 are a 24 step grayscale from black to white
-        232..=255 => {
-            let i = *index as u8 - 232; //Align index to 0..24
-            let step = (u8::MAX as f32 / 24.).floor() as u8; //Split the RGB grayscale values into 24 chunks
-            Color::new(i * step, i * step, i * step, u8::MAX) //Map the ANSI-grayscale components to the RGB-grayscale
-        }
-        //For compatibility with the alacritty::Colors interface
-        256 => style.foreground,
-        257 => style.background,
-        258 => style.cursor,
-        259 => style.dim_black,
-        260 => style.dim_red,
-        261 => style.dim_green,
-        262 => style.dim_yellow,
-        263 => style.dim_blue,
-        264 => style.dim_magenta,
-        265 => style.dim_cyan,
-        266 => style.dim_white,
-        267 => style.bright_foreground,
-        268 => style.black, //'Dim Background', non-standard color
-        _ => Color::new(0, 0, 0, 255),
-    }
-}
-///Generates the rgb channels in [0, 5] for a given index into the 6x6x6 ANSI color cube
-///See: [8 bit ansi color](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit).
-///
-///Wikipedia gives a formula for calculating the index for a given color:
-///
-///index = 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5)
-///
-///This function does the reverse, calculating the r, g, and b components from a given index.
-fn rgb_for_index(i: &u8) -> (u8, u8, u8) {
-    debug_assert!((&16..=&231).contains(&i));
-    let i = i - 16;
-    let r = (i - (i % 36)) / 36;
-    let g = ((i % 36) - (i % 6)) / 6;
-    let b = (i % 36) % 6;
-    (r, g, b)
-}
+use gpui::Rgba;
 
 //Convenience method to convert from a GPUI color to an alacritty Rgb
-pub fn to_alac_rgb(color: Color) -> AlacRgb {
-    AlacRgb::new(color.r, color.g, color.g)
-}
-
-#[cfg(test)]
-mod tests {
-    #[test]
-    fn test_rgb_for_index() {
-        //Test every possible value in the color cube
-        for i in 16..=231 {
-            let (r, g, b) = crate::mappings::colors::rgb_for_index(&(i as u8));
-            assert_eq!(i, 16 + 36 * r + 6 * g + b);
-        }
-    }
+pub fn to_alac_rgb(color: impl Into<Rgba>) -> AlacRgb {
+    let color = color.into();
+    let r = ((color.r * color.a) * 255.) as u8;
+    let g = ((color.g * color.a) * 255.) as u8;
+    let b = ((color.b * color.a) * 255.) as u8;
+    AlacRgb::new(r, g, b)
 }

crates/terminal/src/mappings/keys.rs 🔗

@@ -1,9 +1,9 @@
 /// The mappings defined in this file where created from reading the alacritty source
 use alacritty_terminal::term::TermMode;
-use gpui::keymap_matcher::Keystroke;
+use gpui::Keystroke;
 
 #[derive(Debug, PartialEq, Eq)]
-pub enum Modifiers {
+enum AlacModifiers {
     None,
     Alt,
     Ctrl,
@@ -12,179 +12,184 @@ pub enum Modifiers {
     Other,
 }
 
-impl Modifiers {
+impl AlacModifiers {
     fn new(ks: &Keystroke) -> Self {
-        match (ks.alt, ks.ctrl, ks.shift, ks.cmd) {
-            (false, false, false, false) => Modifiers::None,
-            (true, false, false, false) => Modifiers::Alt,
-            (false, true, false, false) => Modifiers::Ctrl,
-            (false, false, true, false) => Modifiers::Shift,
-            (false, true, true, false) => Modifiers::CtrlShift,
-            _ => Modifiers::Other,
+        match (
+            ks.modifiers.alt,
+            ks.modifiers.control,
+            ks.modifiers.shift,
+            ks.modifiers.command,
+        ) {
+            (false, false, false, false) => AlacModifiers::None,
+            (true, false, false, false) => AlacModifiers::Alt,
+            (false, true, false, false) => AlacModifiers::Ctrl,
+            (false, false, true, false) => AlacModifiers::Shift,
+            (false, true, true, false) => AlacModifiers::CtrlShift,
+            _ => AlacModifiers::Other,
         }
     }
 
     fn any(&self) -> bool {
         match &self {
-            Modifiers::None => false,
-            Modifiers::Alt => true,
-            Modifiers::Ctrl => true,
-            Modifiers::Shift => true,
-            Modifiers::CtrlShift => true,
-            Modifiers::Other => true,
+            AlacModifiers::None => false,
+            AlacModifiers::Alt => true,
+            AlacModifiers::Ctrl => true,
+            AlacModifiers::Shift => true,
+            AlacModifiers::CtrlShift => true,
+            AlacModifiers::Other => true,
         }
     }
 }
 
 pub fn to_esc_str(keystroke: &Keystroke, mode: &TermMode, alt_is_meta: bool) -> Option<String> {
-    let modifiers = Modifiers::new(keystroke);
+    let modifiers = AlacModifiers::new(keystroke);
 
     // Manual Bindings including modifiers
     let manual_esc_str = match (keystroke.key.as_ref(), &modifiers) {
         //Basic special keys
-        ("tab", Modifiers::None) => Some("\x09".to_string()),
-        ("escape", Modifiers::None) => Some("\x1b".to_string()),
-        ("enter", Modifiers::None) => Some("\x0d".to_string()),
-        ("enter", Modifiers::Shift) => Some("\x0d".to_string()),
-        ("backspace", Modifiers::None) => Some("\x7f".to_string()),
+        ("tab", AlacModifiers::None) => Some("\x09".to_string()),
+        ("escape", AlacModifiers::None) => Some("\x1b".to_string()),
+        ("enter", AlacModifiers::None) => Some("\x0d".to_string()),
+        ("enter", AlacModifiers::Shift) => Some("\x0d".to_string()),
+        ("backspace", AlacModifiers::None) => Some("\x7f".to_string()),
         //Interesting escape codes
-        ("tab", Modifiers::Shift) => Some("\x1b[Z".to_string()),
-        ("backspace", Modifiers::Alt) => Some("\x1b\x7f".to_string()),
-        ("backspace", Modifiers::Shift) => Some("\x7f".to_string()),
-        ("home", Modifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => {
+        ("tab", AlacModifiers::Shift) => Some("\x1b[Z".to_string()),
+        ("backspace", AlacModifiers::Alt) => Some("\x1b\x7f".to_string()),
+        ("backspace", AlacModifiers::Shift) => Some("\x7f".to_string()),
+        ("home", AlacModifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => {
             Some("\x1b[1;2H".to_string())
         }
-        ("end", Modifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => {
+        ("end", AlacModifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => {
             Some("\x1b[1;2F".to_string())
         }
-        ("pageup", Modifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => {
+        ("pageup", AlacModifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => {
             Some("\x1b[5;2~".to_string())
         }
-        ("pagedown", Modifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => {
+        ("pagedown", AlacModifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => {
             Some("\x1b[6;2~".to_string())
         }
-        ("home", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
+        ("home", AlacModifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
             Some("\x1bOH".to_string())
         }
-        ("home", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
+        ("home", AlacModifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
             Some("\x1b[H".to_string())
         }
-        ("end", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
+        ("end", AlacModifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
             Some("\x1bOF".to_string())
         }
-        ("end", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
+        ("end", AlacModifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
             Some("\x1b[F".to_string())
         }
-        ("up", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
+        ("up", AlacModifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
             Some("\x1bOA".to_string())
         }
-        ("up", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
+        ("up", AlacModifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
             Some("\x1b[A".to_string())
         }
-        ("down", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
+        ("down", AlacModifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
             Some("\x1bOB".to_string())
         }
-        ("down", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
+        ("down", AlacModifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
             Some("\x1b[B".to_string())
         }
-        ("right", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
+        ("right", AlacModifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
             Some("\x1bOC".to_string())
         }
-        ("right", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
+        ("right", AlacModifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
             Some("\x1b[C".to_string())
         }
-        ("left", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
+        ("left", AlacModifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
             Some("\x1bOD".to_string())
         }
-        ("left", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
+        ("left", AlacModifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
             Some("\x1b[D".to_string())
         }
-        ("back", Modifiers::None) => Some("\x7f".to_string()),
-        ("insert", Modifiers::None) => Some("\x1b[2~".to_string()),
-        ("delete", Modifiers::None) => Some("\x1b[3~".to_string()),
-        ("pageup", Modifiers::None) => Some("\x1b[5~".to_string()),
-        ("pagedown", Modifiers::None) => Some("\x1b[6~".to_string()),
-        ("f1", Modifiers::None) => Some("\x1bOP".to_string()),
-        ("f2", Modifiers::None) => Some("\x1bOQ".to_string()),
-        ("f3", Modifiers::None) => Some("\x1bOR".to_string()),
-        ("f4", Modifiers::None) => Some("\x1bOS".to_string()),
-        ("f5", Modifiers::None) => Some("\x1b[15~".to_string()),
-        ("f6", Modifiers::None) => Some("\x1b[17~".to_string()),
-        ("f7", Modifiers::None) => Some("\x1b[18~".to_string()),
-        ("f8", Modifiers::None) => Some("\x1b[19~".to_string()),
-        ("f9", Modifiers::None) => Some("\x1b[20~".to_string()),
-        ("f10", Modifiers::None) => Some("\x1b[21~".to_string()),
-        ("f11", Modifiers::None) => Some("\x1b[23~".to_string()),
-        ("f12", Modifiers::None) => Some("\x1b[24~".to_string()),
-        ("f13", Modifiers::None) => Some("\x1b[25~".to_string()),
-        ("f14", Modifiers::None) => Some("\x1b[26~".to_string()),
-        ("f15", Modifiers::None) => Some("\x1b[28~".to_string()),
-        ("f16", Modifiers::None) => Some("\x1b[29~".to_string()),
-        ("f17", Modifiers::None) => Some("\x1b[31~".to_string()),
-        ("f18", Modifiers::None) => Some("\x1b[32~".to_string()),
-        ("f19", Modifiers::None) => Some("\x1b[33~".to_string()),
-        ("f20", Modifiers::None) => Some("\x1b[34~".to_string()),
+        ("back", AlacModifiers::None) => Some("\x7f".to_string()),
+        ("insert", AlacModifiers::None) => Some("\x1b[2~".to_string()),
+        ("delete", AlacModifiers::None) => Some("\x1b[3~".to_string()),
+        ("pageup", AlacModifiers::None) => Some("\x1b[5~".to_string()),
+        ("pagedown", AlacModifiers::None) => Some("\x1b[6~".to_string()),
+        ("f1", AlacModifiers::None) => Some("\x1bOP".to_string()),
+        ("f2", AlacModifiers::None) => Some("\x1bOQ".to_string()),
+        ("f3", AlacModifiers::None) => Some("\x1bOR".to_string()),
+        ("f4", AlacModifiers::None) => Some("\x1bOS".to_string()),
+        ("f5", AlacModifiers::None) => Some("\x1b[15~".to_string()),
+        ("f6", AlacModifiers::None) => Some("\x1b[17~".to_string()),
+        ("f7", AlacModifiers::None) => Some("\x1b[18~".to_string()),
+        ("f8", AlacModifiers::None) => Some("\x1b[19~".to_string()),
+        ("f9", AlacModifiers::None) => Some("\x1b[20~".to_string()),
+        ("f10", AlacModifiers::None) => Some("\x1b[21~".to_string()),
+        ("f11", AlacModifiers::None) => Some("\x1b[23~".to_string()),
+        ("f12", AlacModifiers::None) => Some("\x1b[24~".to_string()),
+        ("f13", AlacModifiers::None) => Some("\x1b[25~".to_string()),
+        ("f14", AlacModifiers::None) => Some("\x1b[26~".to_string()),
+        ("f15", AlacModifiers::None) => Some("\x1b[28~".to_string()),
+        ("f16", AlacModifiers::None) => Some("\x1b[29~".to_string()),
+        ("f17", AlacModifiers::None) => Some("\x1b[31~".to_string()),
+        ("f18", AlacModifiers::None) => Some("\x1b[32~".to_string()),
+        ("f19", AlacModifiers::None) => Some("\x1b[33~".to_string()),
+        ("f20", AlacModifiers::None) => Some("\x1b[34~".to_string()),
         // NumpadEnter, Action::Esc("\n".into());
         //Mappings for caret notation keys
-        ("a", Modifiers::Ctrl) => Some("\x01".to_string()), //1
-        ("A", Modifiers::CtrlShift) => Some("\x01".to_string()), //1
-        ("b", Modifiers::Ctrl) => Some("\x02".to_string()), //2
-        ("B", Modifiers::CtrlShift) => Some("\x02".to_string()), //2
-        ("c", Modifiers::Ctrl) => Some("\x03".to_string()), //3
-        ("C", Modifiers::CtrlShift) => Some("\x03".to_string()), //3
-        ("d", Modifiers::Ctrl) => Some("\x04".to_string()), //4
-        ("D", Modifiers::CtrlShift) => Some("\x04".to_string()), //4
-        ("e", Modifiers::Ctrl) => Some("\x05".to_string()), //5
-        ("E", Modifiers::CtrlShift) => Some("\x05".to_string()), //5
-        ("f", Modifiers::Ctrl) => Some("\x06".to_string()), //6
-        ("F", Modifiers::CtrlShift) => Some("\x06".to_string()), //6
-        ("g", Modifiers::Ctrl) => Some("\x07".to_string()), //7
-        ("G", Modifiers::CtrlShift) => Some("\x07".to_string()), //7
-        ("h", Modifiers::Ctrl) => Some("\x08".to_string()), //8
-        ("H", Modifiers::CtrlShift) => Some("\x08".to_string()), //8
-        ("i", Modifiers::Ctrl) => Some("\x09".to_string()), //9
-        ("I", Modifiers::CtrlShift) => Some("\x09".to_string()), //9
-        ("j", Modifiers::Ctrl) => Some("\x0a".to_string()), //10
-        ("J", Modifiers::CtrlShift) => Some("\x0a".to_string()), //10
-        ("k", Modifiers::Ctrl) => Some("\x0b".to_string()), //11
-        ("K", Modifiers::CtrlShift) => Some("\x0b".to_string()), //11
-        ("l", Modifiers::Ctrl) => Some("\x0c".to_string()), //12
-        ("L", Modifiers::CtrlShift) => Some("\x0c".to_string()), //12
-        ("m", Modifiers::Ctrl) => Some("\x0d".to_string()), //13
-        ("M", Modifiers::CtrlShift) => Some("\x0d".to_string()), //13
-        ("n", Modifiers::Ctrl) => Some("\x0e".to_string()), //14
-        ("N", Modifiers::CtrlShift) => Some("\x0e".to_string()), //14
-        ("o", Modifiers::Ctrl) => Some("\x0f".to_string()), //15
-        ("O", Modifiers::CtrlShift) => Some("\x0f".to_string()), //15
-        ("p", Modifiers::Ctrl) => Some("\x10".to_string()), //16
-        ("P", Modifiers::CtrlShift) => Some("\x10".to_string()), //16
-        ("q", Modifiers::Ctrl) => Some("\x11".to_string()), //17
-        ("Q", Modifiers::CtrlShift) => Some("\x11".to_string()), //17
-        ("r", Modifiers::Ctrl) => Some("\x12".to_string()), //18
-        ("R", Modifiers::CtrlShift) => Some("\x12".to_string()), //18
-        ("s", Modifiers::Ctrl) => Some("\x13".to_string()), //19
-        ("S", Modifiers::CtrlShift) => Some("\x13".to_string()), //19
-        ("t", Modifiers::Ctrl) => Some("\x14".to_string()), //20
-        ("T", Modifiers::CtrlShift) => Some("\x14".to_string()), //20
-        ("u", Modifiers::Ctrl) => Some("\x15".to_string()), //21
-        ("U", Modifiers::CtrlShift) => Some("\x15".to_string()), //21
-        ("v", Modifiers::Ctrl) => Some("\x16".to_string()), //22
-        ("V", Modifiers::CtrlShift) => Some("\x16".to_string()), //22
-        ("w", Modifiers::Ctrl) => Some("\x17".to_string()), //23
-        ("W", Modifiers::CtrlShift) => Some("\x17".to_string()), //23
-        ("x", Modifiers::Ctrl) => Some("\x18".to_string()), //24
-        ("X", Modifiers::CtrlShift) => Some("\x18".to_string()), //24
-        ("y", Modifiers::Ctrl) => Some("\x19".to_string()), //25
-        ("Y", Modifiers::CtrlShift) => Some("\x19".to_string()), //25
-        ("z", Modifiers::Ctrl) => Some("\x1a".to_string()), //26
-        ("Z", Modifiers::CtrlShift) => Some("\x1a".to_string()), //26
-        ("@", Modifiers::Ctrl) => Some("\x00".to_string()), //0
-        ("[", Modifiers::Ctrl) => Some("\x1b".to_string()), //27
-        ("\\", Modifiers::Ctrl) => Some("\x1c".to_string()), //28
-        ("]", Modifiers::Ctrl) => Some("\x1d".to_string()), //29
-        ("^", Modifiers::Ctrl) => Some("\x1e".to_string()), //30
-        ("_", Modifiers::Ctrl) => Some("\x1f".to_string()), //31
-        ("?", Modifiers::Ctrl) => Some("\x7f".to_string()), //127
+        ("a", AlacModifiers::Ctrl) => Some("\x01".to_string()), //1
+        ("A", AlacModifiers::CtrlShift) => Some("\x01".to_string()), //1
+        ("b", AlacModifiers::Ctrl) => Some("\x02".to_string()), //2
+        ("B", AlacModifiers::CtrlShift) => Some("\x02".to_string()), //2
+        ("c", AlacModifiers::Ctrl) => Some("\x03".to_string()), //3
+        ("C", AlacModifiers::CtrlShift) => Some("\x03".to_string()), //3
+        ("d", AlacModifiers::Ctrl) => Some("\x04".to_string()), //4
+        ("D", AlacModifiers::CtrlShift) => Some("\x04".to_string()), //4
+        ("e", AlacModifiers::Ctrl) => Some("\x05".to_string()), //5
+        ("E", AlacModifiers::CtrlShift) => Some("\x05".to_string()), //5
+        ("f", AlacModifiers::Ctrl) => Some("\x06".to_string()), //6
+        ("F", AlacModifiers::CtrlShift) => Some("\x06".to_string()), //6
+        ("g", AlacModifiers::Ctrl) => Some("\x07".to_string()), //7
+        ("G", AlacModifiers::CtrlShift) => Some("\x07".to_string()), //7
+        ("h", AlacModifiers::Ctrl) => Some("\x08".to_string()), //8
+        ("H", AlacModifiers::CtrlShift) => Some("\x08".to_string()), //8
+        ("i", AlacModifiers::Ctrl) => Some("\x09".to_string()), //9
+        ("I", AlacModifiers::CtrlShift) => Some("\x09".to_string()), //9
+        ("j", AlacModifiers::Ctrl) => Some("\x0a".to_string()), //10
+        ("J", AlacModifiers::CtrlShift) => Some("\x0a".to_string()), //10
+        ("k", AlacModifiers::Ctrl) => Some("\x0b".to_string()), //11
+        ("K", AlacModifiers::CtrlShift) => Some("\x0b".to_string()), //11
+        ("l", AlacModifiers::Ctrl) => Some("\x0c".to_string()), //12
+        ("L", AlacModifiers::CtrlShift) => Some("\x0c".to_string()), //12
+        ("m", AlacModifiers::Ctrl) => Some("\x0d".to_string()), //13
+        ("M", AlacModifiers::CtrlShift) => Some("\x0d".to_string()), //13
+        ("n", AlacModifiers::Ctrl) => Some("\x0e".to_string()), //14
+        ("N", AlacModifiers::CtrlShift) => Some("\x0e".to_string()), //14
+        ("o", AlacModifiers::Ctrl) => Some("\x0f".to_string()), //15
+        ("O", AlacModifiers::CtrlShift) => Some("\x0f".to_string()), //15
+        ("p", AlacModifiers::Ctrl) => Some("\x10".to_string()), //16
+        ("P", AlacModifiers::CtrlShift) => Some("\x10".to_string()), //16
+        ("q", AlacModifiers::Ctrl) => Some("\x11".to_string()), //17
+        ("Q", AlacModifiers::CtrlShift) => Some("\x11".to_string()), //17
+        ("r", AlacModifiers::Ctrl) => Some("\x12".to_string()), //18
+        ("R", AlacModifiers::CtrlShift) => Some("\x12".to_string()), //18
+        ("s", AlacModifiers::Ctrl) => Some("\x13".to_string()), //19
+        ("S", AlacModifiers::CtrlShift) => Some("\x13".to_string()), //19
+        ("t", AlacModifiers::Ctrl) => Some("\x14".to_string()), //20
+        ("T", AlacModifiers::CtrlShift) => Some("\x14".to_string()), //20
+        ("u", AlacModifiers::Ctrl) => Some("\x15".to_string()), //21
+        ("U", AlacModifiers::CtrlShift) => Some("\x15".to_string()), //21
+        ("v", AlacModifiers::Ctrl) => Some("\x16".to_string()), //22
+        ("V", AlacModifiers::CtrlShift) => Some("\x16".to_string()), //22
+        ("w", AlacModifiers::Ctrl) => Some("\x17".to_string()), //23
+        ("W", AlacModifiers::CtrlShift) => Some("\x17".to_string()), //23
+        ("x", AlacModifiers::Ctrl) => Some("\x18".to_string()), //24
+        ("X", AlacModifiers::CtrlShift) => Some("\x18".to_string()), //24
+        ("y", AlacModifiers::Ctrl) => Some("\x19".to_string()), //25
+        ("Y", AlacModifiers::CtrlShift) => Some("\x19".to_string()), //25
+        ("z", AlacModifiers::Ctrl) => Some("\x1a".to_string()), //26
+        ("Z", AlacModifiers::CtrlShift) => Some("\x1a".to_string()), //26
+        ("@", AlacModifiers::Ctrl) => Some("\x00".to_string()), //0
+        ("[", AlacModifiers::Ctrl) => Some("\x1b".to_string()), //27
+        ("\\", AlacModifiers::Ctrl) => Some("\x1c".to_string()), //28
+        ("]", AlacModifiers::Ctrl) => Some("\x1d".to_string()), //29
+        ("^", AlacModifiers::Ctrl) => Some("\x1e".to_string()), //30
+        ("_", AlacModifiers::Ctrl) => Some("\x1f".to_string()), //31
+        ("?", AlacModifiers::Ctrl) => Some("\x7f".to_string()), //127
         _ => None,
     };
     if manual_esc_str.is_some() {
@@ -232,12 +237,12 @@ pub fn to_esc_str(keystroke: &Keystroke, mode: &TermMode, alt_is_meta: bool) ->
         }
     }
 
-    let alt_meta_binding = if alt_is_meta && modifiers == Modifiers::Alt && keystroke.key.is_ascii()
-    {
-        Some(format!("\x1b{}", keystroke.key))
-    } else {
-        None
-    };
+    let alt_meta_binding =
+        if alt_is_meta && modifiers == AlacModifiers::Alt && keystroke.key.is_ascii() {
+            Some(format!("\x1b{}", keystroke.key))
+        } else {
+            None
+        };
 
     if alt_meta_binding.is_some() {
         return alt_meta_binding;
@@ -259,13 +264,13 @@ pub fn to_esc_str(keystroke: &Keystroke, mode: &TermMode, alt_is_meta: bool) ->
 /// from: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys
 fn modifier_code(keystroke: &Keystroke) -> u32 {
     let mut modifier_code = 0;
-    if keystroke.shift {
+    if keystroke.modifiers.shift {
         modifier_code |= 1;
     }
-    if keystroke.alt {
+    if keystroke.modifiers.alt {
         modifier_code |= 1 << 1;
     }
-    if keystroke.ctrl {
+    if keystroke.modifiers.control {
         modifier_code |= 1 << 2;
     }
     modifier_code + 1
@@ -273,7 +278,7 @@ fn modifier_code(keystroke: &Keystroke) -> u32 {
 
 #[cfg(test)]
 mod test {
-    use gpui::keymap_matcher::Keystroke;
+    use gpui::Modifiers;
 
     use super::*;
 
@@ -327,11 +332,13 @@ mod test {
     #[test]
     fn test_plain_inputs() {
         let ks = Keystroke {
-            ctrl: false,
-            alt: false,
-            shift: false,
-            cmd: false,
-            function: false,
+            modifiers: Modifiers {
+                control: false,
+                alt: false,
+                shift: false,
+                command: false,
+                function: false,
+            },
             key: "🖖🏻".to_string(), //2 char string
             ime_key: None,
         };

crates/terminal/src/mappings/mouse.rs 🔗

@@ -1,52 +1,15 @@
-use std::cmp::{max, min};
+use std::cmp::{self, max, min};
 use std::iter::repeat;
 
 use alacritty_terminal::grid::Dimensions;
 /// Most of the code, and specifically the constants, in this are copied from Alacritty,
 /// with modifications for our circumstances
-use alacritty_terminal::index::{Column as GridCol, Line as GridLine, Point, Side};
+use alacritty_terminal::index::{Column as GridCol, Line as GridLine, Point as AlacPoint, Side};
 use alacritty_terminal::term::TermMode;
-use gpui::platform;
-use gpui::scene::MouseScrollWheel;
-use gpui::{
-    geometry::vector::Vector2F,
-    platform::{MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent},
-};
+use gpui::{px, Modifiers, MouseButton, MouseMoveEvent, Pixels, Point, ScrollWheelEvent};
 
 use crate::TerminalSize;
 
-struct Modifiers {
-    ctrl: bool,
-    shift: bool,
-    alt: bool,
-}
-
-impl Modifiers {
-    fn from_moved(e: &MouseMovedEvent) -> Self {
-        Modifiers {
-            ctrl: e.ctrl,
-            shift: e.shift,
-            alt: e.alt,
-        }
-    }
-
-    fn from_button(e: &MouseButtonEvent) -> Self {
-        Modifiers {
-            ctrl: e.ctrl,
-            shift: e.shift,
-            alt: e.alt,
-        }
-    }
-
-    fn from_scroll(scroll: &ScrollWheelEvent) -> Self {
-        Modifiers {
-            ctrl: scroll.ctrl,
-            shift: scroll.shift,
-            alt: scroll.alt,
-        }
-    }
-}
-
 enum MouseFormat {
     SGR,
     Normal(bool),
@@ -65,7 +28,7 @@ impl MouseFormat {
 }
 
 #[derive(Debug)]
-enum MouseButton {
+enum AlacMouseButton {
     LeftButton = 0,
     MiddleButton = 1,
     RightButton = 2,
@@ -78,56 +41,61 @@ enum MouseButton {
     Other = 99,
 }
 
-impl MouseButton {
-    fn from_move(e: &MouseMovedEvent) -> Self {
+impl AlacMouseButton {
+    fn from_move(e: &MouseMoveEvent) -> Self {
         match e.pressed_button {
             Some(b) => match b {
-                platform::MouseButton::Left => MouseButton::LeftMove,
-                platform::MouseButton::Middle => MouseButton::MiddleMove,
-                platform::MouseButton::Right => MouseButton::RightMove,
-                platform::MouseButton::Navigate(_) => MouseButton::Other,
+                gpui::MouseButton::Left => AlacMouseButton::LeftMove,
+                gpui::MouseButton::Middle => AlacMouseButton::MiddleMove,
+                gpui::MouseButton::Right => AlacMouseButton::RightMove,
+                gpui::MouseButton::Navigate(_) => AlacMouseButton::Other,
             },
-            None => MouseButton::NoneMove,
+            None => AlacMouseButton::NoneMove,
         }
     }
 
-    fn from_button(e: &MouseButtonEvent) -> Self {
-        match e.button {
-            platform::MouseButton::Left => MouseButton::LeftButton,
-            platform::MouseButton::Right => MouseButton::MiddleButton,
-            platform::MouseButton::Middle => MouseButton::RightButton,
-            platform::MouseButton::Navigate(_) => MouseButton::Other,
+    fn from_button(e: MouseButton) -> Self {
+        match e {
+            gpui::MouseButton::Left => AlacMouseButton::LeftButton,
+            gpui::MouseButton::Right => AlacMouseButton::MiddleButton,
+            gpui::MouseButton::Middle => AlacMouseButton::RightButton,
+            gpui::MouseButton::Navigate(_) => AlacMouseButton::Other,
         }
     }
 
     fn from_scroll(e: &ScrollWheelEvent) -> Self {
-        if e.delta.raw().y() > 0. {
-            MouseButton::ScrollUp
+        let is_positive = match e.delta {
+            gpui::ScrollDelta::Pixels(pixels) => pixels.y > px(0.),
+            gpui::ScrollDelta::Lines(lines) => lines.y > 0.,
+        };
+
+        if is_positive {
+            AlacMouseButton::ScrollUp
         } else {
-            MouseButton::ScrollDown
+            AlacMouseButton::ScrollDown
         }
     }
 
     fn is_other(&self) -> bool {
         match self {
-            MouseButton::Other => true,
+            AlacMouseButton::Other => true,
             _ => false,
         }
     }
 }
 
 pub fn scroll_report(
-    point: Point,
+    point: AlacPoint,
     scroll_lines: i32,
-    e: &MouseScrollWheel,
+    e: &ScrollWheelEvent,
     mode: TermMode,
 ) -> Option<impl Iterator<Item = Vec<u8>>> {
     if mode.intersects(TermMode::MOUSE_MODE) {
         mouse_report(
             point,
-            MouseButton::from_scroll(e),
+            AlacMouseButton::from_scroll(e),
             true,
-            Modifiers::from_scroll(e),
+            e.modifiers,
             MouseFormat::from_mode(mode),
         )
         .map(|report| repeat(report).take(max(scroll_lines, 1) as usize))
@@ -149,18 +117,19 @@ pub fn alt_scroll(scroll_lines: i32) -> Vec<u8> {
 }
 
 pub fn mouse_button_report(
-    point: Point,
-    e: &MouseButtonEvent,
+    point: AlacPoint,
+    button: gpui::MouseButton,
+    modifiers: Modifiers,
     pressed: bool,
     mode: TermMode,
 ) -> Option<Vec<u8>> {
-    let button = MouseButton::from_button(e);
+    let button = AlacMouseButton::from_button(button);
     if !button.is_other() && mode.intersects(TermMode::MOUSE_MODE) {
         mouse_report(
             point,
             button,
             pressed,
-            Modifiers::from_button(e),
+            modifiers,
             MouseFormat::from_mode(mode),
         )
     } else {
@@ -168,19 +137,19 @@ pub fn mouse_button_report(
     }
 }
 
-pub fn mouse_moved_report(point: Point, e: &MouseMovedEvent, mode: TermMode) -> Option<Vec<u8>> {
-    let button = MouseButton::from_move(e);
+pub fn mouse_moved_report(point: AlacPoint, e: &MouseMoveEvent, mode: TermMode) -> Option<Vec<u8>> {
+    let button = AlacMouseButton::from_move(e);
 
     if !button.is_other() && mode.intersects(TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG) {
         //Only drags are reported in drag mode, so block NoneMove.
-        if mode.contains(TermMode::MOUSE_DRAG) && matches!(button, MouseButton::NoneMove) {
+        if mode.contains(TermMode::MOUSE_DRAG) && matches!(button, AlacMouseButton::NoneMove) {
             None
         } else {
             mouse_report(
                 point,
                 button,
                 true,
-                Modifiers::from_moved(e),
+                e.modifiers,
                 MouseFormat::from_mode(mode),
             )
         }
@@ -189,19 +158,26 @@ pub fn mouse_moved_report(point: Point, e: &MouseMovedEvent, mode: TermMode) ->
     }
 }
 
-pub fn mouse_side(pos: Vector2F, cur_size: TerminalSize) -> alacritty_terminal::index::Direction {
-    if cur_size.cell_width as usize == 0 {
+pub fn mouse_side(
+    pos: Point<Pixels>,
+    cur_size: TerminalSize,
+) -> alacritty_terminal::index::Direction {
+    let cell_width = cur_size.cell_width.floor();
+    if cell_width == px(0.) {
         return Side::Right;
     }
-    let x = pos.0.x() as usize;
-    let cell_x = x.saturating_sub(cur_size.cell_width as usize) % cur_size.cell_width as usize;
-    let half_cell_width = (cur_size.cell_width / 2.0) as usize;
+
+    let x = pos.x.floor();
+
+    let cell_x = cmp::max(px(0.), x - cell_width) % cell_width;
+    let half_cell_width = (cur_size.cell_width / 2.0).floor();
     let additional_padding = (cur_size.width() - cur_size.cell_width * 2.) % cur_size.cell_width;
     let end_of_grid = cur_size.width() - cur_size.cell_width - additional_padding;
+
     //Width: Pixels or columns?
     if cell_x > half_cell_width
     // Edge case when mouse leaves the window.
-    || x as f32 >= end_of_grid
+    || x >= end_of_grid
     {
         Side::Right
     } else {
@@ -209,18 +185,18 @@ pub fn mouse_side(pos: Vector2F, cur_size: TerminalSize) -> alacritty_terminal::
     }
 }
 
-pub fn grid_point(pos: Vector2F, cur_size: TerminalSize, display_offset: usize) -> Point {
-    let col = pos.x() / cur_size.cell_width;
-    let col = min(GridCol(col as usize), cur_size.last_column());
-    let line = pos.y() / cur_size.line_height;
-    let line = min(line as i32, cur_size.bottommost_line().0);
-    Point::new(GridLine(line - display_offset as i32), col)
+pub fn grid_point(pos: Point<Pixels>, cur_size: TerminalSize, display_offset: usize) -> AlacPoint {
+    let col = GridCol((pos.x / cur_size.cell_width) as usize);
+    let col = min(col, cur_size.last_column());
+    let line = (pos.y / cur_size.line_height) as i32;
+    let line = min(line, cur_size.bottommost_line().0);
+    AlacPoint::new(GridLine(line - display_offset as i32), col)
 }
 
 ///Generate the bytes to send to the terminal, from the cell location, a mouse event, and the terminal mode
 fn mouse_report(
-    point: Point,
-    button: MouseButton,
+    point: AlacPoint,
+    button: AlacMouseButton,
     pressed: bool,
     modifiers: Modifiers,
     format: MouseFormat,
@@ -236,7 +212,7 @@ fn mouse_report(
     if modifiers.alt {
         mods += 8;
     }
-    if modifiers.ctrl {
+    if modifiers.control {
         mods += 16;
     }
 
@@ -254,8 +230,8 @@ fn mouse_report(
     }
 }
 
-fn normal_mouse_report(point: Point, button: u8, utf8: bool) -> Option<Vec<u8>> {
-    let Point { line, column } = point;
+fn normal_mouse_report(point: AlacPoint, button: u8, utf8: bool) -> Option<Vec<u8>> {
+    let AlacPoint { line, column } = point;
     let max_point = if utf8 { 2015 } else { 223 };
 
     if line >= max_point || column >= max_point {
@@ -286,7 +262,7 @@ fn normal_mouse_report(point: Point, button: u8, utf8: bool) -> Option<Vec<u8>>
     Some(msg)
 }
 
-fn sgr_mouse_report(point: Point, button: u8, pressed: bool) -> String {
+fn sgr_mouse_report(point: AlacPoint, button: u8, pressed: bool) -> String {
     let c = if pressed { 'M' } else { 'm' };
 
     let msg = format!(
@@ -299,38 +275,3 @@ fn sgr_mouse_report(point: Point, button: u8, pressed: bool) -> String {
 
     msg
 }
-
-#[cfg(test)]
-mod test {
-    use crate::mappings::mouse::grid_point;
-
-    #[test]
-    fn test_mouse_to_selection() {
-        let term_width = 100.;
-        let term_height = 200.;
-        let cell_width = 10.;
-        let line_height = 20.;
-        let mouse_pos_x = 100.; //Window relative
-        let mouse_pos_y = 100.; //Window relative
-        let origin_x = 10.;
-        let origin_y = 20.;
-
-        let cur_size = crate::TerminalSize::new(
-            line_height,
-            cell_width,
-            gpui::geometry::vector::vec2f(term_width, term_height),
-        );
-
-        let mouse_pos = gpui::geometry::vector::vec2f(mouse_pos_x, mouse_pos_y);
-        let origin = gpui::geometry::vector::vec2f(origin_x, origin_y); //Position of terminal window, 1 'cell' in
-        let mouse_pos = mouse_pos - origin;
-        let point = grid_point(mouse_pos, cur_size, 0);
-        assert_eq!(
-            point,
-            alacritty_terminal::index::Point::new(
-                alacritty_terminal::index::Line(((mouse_pos_y - origin_y) / line_height) as i32),
-                alacritty_terminal::index::Column(((mouse_pos_x - origin_x) / cell_width) as usize),
-            )
-        );
-    }
-}

crates/terminal/src/terminal.rs 🔗

@@ -8,7 +8,7 @@ use alacritty_terminal::{
     event::{Event as AlacTermEvent, EventListener, Notify, WindowSize},
     event_loop::{EventLoop, Msg, Notifier},
     grid::{Dimensions, Scroll as AlacScroll},
-    index::{Boundary, Column, Direction as AlacDirection, Line, Point},
+    index::{Boundary, Column, Direction as AlacDirection, Line, Point as AlacPoint},
     selection::{Selection, SelectionRange, SelectionType},
     sync::FairMutex,
     term::{
@@ -33,14 +33,16 @@ use mappings::mouse::{
 
 use procinfo::LocalProcessInfo;
 use serde::{Deserialize, Serialize};
+use settings::Settings;
 use terminal_settings::{AlternateScroll, Shell, TerminalBlink, TerminalSettings};
+use theme::{ActiveTheme, Theme};
 use util::truncate_and_trailoff;
 
 use std::{
-    cmp::min,
+    cmp::{self, min},
     collections::{HashMap, VecDeque},
     fmt::Display,
-    ops::{Deref, Index, RangeInclusive, Sub},
+    ops::{Deref, Index, RangeInclusive},
     os::unix::prelude::AsRawFd,
     path::PathBuf,
     sync::Arc,
@@ -49,28 +51,36 @@ use std::{
 use thiserror::Error;
 
 use gpui::{
-    geometry::vector::{vec2f, Vector2F},
-    keymap_matcher::Keystroke,
-    platform::{Modifiers, MouseButton, MouseMovedEvent, TouchPhase},
-    scene::{MouseDown, MouseDrag, MouseScrollWheel, MouseUp},
-    AnyWindowHandle, AppContext, ClipboardItem, Entity, ModelContext, Task,
+    actions, black, px, red, AnyWindowHandle, AppContext, Bounds, ClipboardItem, EventEmitter,
+    Hsla, Keystroke, ModelContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent,
+    MouseUpEvent, Pixels, Point, Rgba, ScrollWheelEvent, Size, Task, TouchPhase,
 };
 
-use crate::mappings::{
-    colors::{get_color_at_index, to_alac_rgb},
-    keys::to_esc_str,
-};
+use crate::mappings::{colors::to_alac_rgb, keys::to_esc_str};
 use lazy_static::lazy_static;
 
+actions!(
+    terminal,
+    [
+        Clear,
+        Copy,
+        Paste,
+        ShowCharacterPalette,
+        SearchTest,
+        SendText,
+        SendKeystroke,
+    ]
+);
+
 ///Scrolling is unbearably sluggish by default. Alacritty supports a configurable
 ///Scroll multiplier that is set to 3 by default. This will be removed when I
 ///Implement scroll bars.
 const SCROLL_MULTIPLIER: f32 = 4.;
 const MAX_SEARCH_LINES: usize = 100;
-const DEBUG_TERMINAL_WIDTH: f32 = 500.;
-const DEBUG_TERMINAL_HEIGHT: f32 = 30.;
-const DEBUG_CELL_WIDTH: f32 = 5.;
-const DEBUG_LINE_HEIGHT: f32 = 5.;
+const DEBUG_TERMINAL_WIDTH: Pixels = px(500.);
+const DEBUG_TERMINAL_HEIGHT: Pixels = px(30.);
+const DEBUG_CELL_WIDTH: Pixels = px(5.);
+const DEBUG_LINE_HEIGHT: Pixels = px(5.);
 
 lazy_static! {
     // Regex Copied from alacritty's ui_config.rs and modified its declaration slightly:
@@ -112,11 +122,11 @@ enum InternalEvent {
     Clear,
     // FocusNextMatch,
     Scroll(AlacScroll),
-    ScrollToPoint(Point),
-    SetSelection(Option<(Selection, Point)>),
-    UpdateSelection(Vector2F),
+    ScrollToAlacPoint(AlacPoint),
+    SetSelection(Option<(Selection, AlacPoint)>),
+    UpdateSelection(Point<Pixels>),
     // Adjusted mouse position, should open
-    FindHyperlink(Vector2F, bool),
+    FindHyperlink(Point<Pixels>, bool),
     Copy,
 }
 
@@ -131,48 +141,46 @@ impl EventListener for ZedListener {
 }
 
 pub fn init(cx: &mut AppContext) {
-    settings::register::<TerminalSettings>(cx);
+    TerminalSettings::register(cx);
 }
 
 #[derive(Clone, Copy, Debug, Serialize, Deserialize)]
 pub struct TerminalSize {
-    pub cell_width: f32,
-    pub line_height: f32,
-    pub height: f32,
-    pub width: f32,
+    pub cell_width: Pixels,
+    pub line_height: Pixels,
+    pub size: Size<Pixels>,
 }
 
 impl TerminalSize {
-    pub fn new(line_height: f32, cell_width: f32, size: Vector2F) -> Self {
+    pub fn new(line_height: Pixels, cell_width: Pixels, size: Size<Pixels>) -> Self {
         TerminalSize {
             cell_width,
             line_height,
-            width: size.x(),
-            height: size.y(),
+            size,
         }
     }
 
     pub fn num_lines(&self) -> usize {
-        (self.height / self.line_height).floor() as usize
+        f32::from((self.size.height / self.line_height).floor()) as usize
     }
 
     pub fn num_columns(&self) -> usize {
-        (self.width / self.cell_width).floor() as usize
+        f32::from((self.size.width / self.cell_width).floor()) as usize
     }
 
-    pub fn height(&self) -> f32 {
-        self.height
+    pub fn height(&self) -> Pixels {
+        self.size.height
     }
 
-    pub fn width(&self) -> f32 {
-        self.width
+    pub fn width(&self) -> Pixels {
+        self.size.width
     }
 
-    pub fn cell_width(&self) -> f32 {
+    pub fn cell_width(&self) -> Pixels {
         self.cell_width
     }
 
-    pub fn line_height(&self) -> f32 {
+    pub fn line_height(&self) -> Pixels {
         self.line_height
     }
 }
@@ -181,7 +189,10 @@ impl Default for TerminalSize {
         TerminalSize::new(
             DEBUG_LINE_HEIGHT,
             DEBUG_CELL_WIDTH,
-            vec2f(DEBUG_TERMINAL_WIDTH, DEBUG_TERMINAL_HEIGHT),
+            Size {
+                width: DEBUG_TERMINAL_WIDTH,
+                height: DEBUG_TERMINAL_HEIGHT,
+            },
         )
     }
 }
@@ -191,8 +202,8 @@ impl From<TerminalSize> for WindowSize {
         WindowSize {
             num_lines: val.num_lines() as u16,
             num_cols: val.num_columns() as u16,
-            cell_width: val.cell_width() as u16,
-            cell_height: val.line_height() as u16,
+            cell_width: f32::from(val.cell_width()) as u16,
+            cell_height: f32::from(val.line_height()) as u16,
         }
     }
 }
@@ -346,7 +357,7 @@ impl TerminalBuilder {
         let pty = match tty::new(
             &pty_config,
             TerminalSize::default().into(),
-            window.id() as u64,
+            window.window_id().as_u64(),
         ) {
             Ok(pty) => pty,
             Err(error) => {
@@ -388,7 +399,7 @@ impl TerminalBuilder {
             shell_pid,
             foreground_process_info: None,
             breadcrumb_text: String::new(),
-            scroll_px: 0.,
+            scroll_px: px(0.),
             last_mouse_position: None,
             next_link_id: 0,
             selection_phase: SelectionPhase::Ended,
@@ -404,18 +415,21 @@ impl TerminalBuilder {
 
     pub fn subscribe(mut self, cx: &mut ModelContext<Terminal>) -> Terminal {
         //Event loop
-        cx.spawn_weak(|this, mut cx| async move {
+        cx.spawn(|this, mut cx| async move {
             use futures::StreamExt;
 
             while let Some(event) = self.events_rx.next().await {
-                this.upgrade(&cx)?.update(&mut cx, |this, cx| {
+                this.update(&mut cx, |this, cx| {
                     //Process the first event immediately for lowered latency
                     this.process_event(&event, cx);
-                });
+                })?;
 
                 'outer: loop {
                     let mut events = vec![];
-                    let mut timer = cx.background().timer(Duration::from_millis(4)).fuse();
+                    let mut timer = cx
+                        .background_executor()
+                        .timer(Duration::from_millis(4))
+                        .fuse();
                     let mut wakeup = false;
                     loop {
                         futures::select_biased! {
@@ -442,7 +456,7 @@ impl TerminalBuilder {
                         smol::future::yield_now().await;
                         break 'outer;
                     } else {
-                        this.upgrade(&cx)?.update(&mut cx, |this, cx| {
+                        this.update(&mut cx, |this, cx| {
                             if wakeup {
                                 this.process_event(&AlacTermEvent::Wakeup, cx);
                             }
@@ -450,13 +464,13 @@ impl TerminalBuilder {
                             for event in events {
                                 this.process_event(&event, cx);
                             }
-                        });
+                        })?;
                         smol::future::yield_now().await;
                     }
                 }
             }
 
-            Some(())
+            anyhow::Ok(())
         })
         .detach();
 
@@ -466,7 +480,7 @@ impl TerminalBuilder {
 
 #[derive(Debug, Clone, Deserialize, Serialize)]
 pub struct IndexedCell {
-    pub point: Point,
+    pub point: AlacPoint,
     pub cell: Cell,
 }
 
@@ -496,7 +510,7 @@ pub struct TerminalContent {
 #[derive(Clone)]
 pub struct HoveredWord {
     pub word: String,
-    pub word_match: RangeInclusive<Point>,
+    pub word_match: RangeInclusive<AlacPoint>,
     pub id: usize,
 }
 
@@ -510,7 +524,7 @@ impl Default for TerminalContent {
             selection: Default::default(),
             cursor: RenderableCursor {
                 shape: alacritty_terminal::ansi::CursorShape::Block,
-                point: Point::new(Line(0), Column(0)),
+                point: AlacPoint::new(Line(0), Column(0)),
             },
             cursor_char: Default::default(),
             size: Default::default(),
@@ -530,19 +544,19 @@ pub struct Terminal {
     term: Arc<FairMutex<Term<ZedListener>>>,
     events: VecDeque<InternalEvent>,
     /// This is only used for mouse mode cell change detection
-    last_mouse: Option<(Point, AlacDirection)>,
+    last_mouse: Option<(AlacPoint, AlacDirection)>,
     /// This is only used for terminal hovered word checking
-    last_mouse_position: Option<Vector2F>,
-    pub matches: Vec<RangeInclusive<Point>>,
+    last_mouse_position: Option<Point<Pixels>>,
+    pub matches: Vec<RangeInclusive<AlacPoint>>,
     pub last_content: TerminalContent,
     last_synced: Instant,
     sync_task: Option<Task<()>>,
-    pub selection_head: Option<Point>,
+    pub selection_head: Option<AlacPoint>,
     pub breadcrumb_text: String,
     shell_pid: u32,
     shell_fd: u32,
     pub foreground_process_info: Option<LocalProcessInfo>,
-    scroll_px: f32,
+    scroll_px: Pixels,
     next_link_id: usize,
     selection_phase: SelectionPhase,
     cmd_pressed: bool,
@@ -630,18 +644,17 @@ impl Terminal {
         match event {
             InternalEvent::ColorRequest(index, format) => {
                 let color = term.colors()[*index].unwrap_or_else(|| {
-                    let term_style = &theme::current(cx).terminal;
-                    to_alac_rgb(get_color_at_index(index, &term_style))
+                    to_alac_rgb(get_color_at_index(*index, cx.theme().as_ref()))
                 });
                 self.write_to_pty(format(color))
             }
             InternalEvent::Resize(mut new_size) => {
-                new_size.height = f32::max(new_size.line_height, new_size.height);
-                new_size.width = f32::max(new_size.cell_width, new_size.width);
+                new_size.size.height = cmp::max(new_size.line_height, new_size.height());
+                new_size.size.width = cmp::max(new_size.cell_width, new_size.width());
 
                 self.last_content.size = new_size.clone();
 
-                self.pty_tx.0.send(Msg::Resize((new_size).into())).ok();
+                self.pty_tx.0.send(Msg::Resize(new_size.into())).ok();
 
                 term.resize(new_size);
             }
@@ -667,7 +680,7 @@ impl Terminal {
 
                 // Reset the cursor
                 term.grid_mut().cursor.point =
-                    Point::new(Line(0), term.grid_mut().cursor.point.column);
+                    AlacPoint::new(Line(0), term.grid_mut().cursor.point.column);
                 let new_cursor = term.grid().cursor.point;
 
                 // Clear the lines below the new cursor
@@ -712,7 +725,7 @@ impl Terminal {
                     cx.write_to_clipboard(ClipboardItem::new(txt))
                 }
             }
-            InternalEvent::ScrollToPoint(point) => {
+            InternalEvent::ScrollToAlacPoint(point) => {
                 term.scroll_to_point(*point);
                 self.refresh_hovered_word();
             }
@@ -825,7 +838,7 @@ impl Terminal {
     fn update_selected_word(
         &mut self,
         prev_word: Option<HoveredWord>,
-        word_match: RangeInclusive<Point>,
+        word_match: RangeInclusive<AlacPoint>,
         word: String,
         is_url: bool,
         cx: &mut ModelContext<Self>,
@@ -873,11 +886,11 @@ impl Terminal {
             self.set_selection(Some((make_selection(&search_match), *search_match.end())));
 
             self.events
-                .push_back(InternalEvent::ScrollToPoint(*search_match.start()));
+                .push_back(InternalEvent::ScrollToAlacPoint(*search_match.start()));
         }
     }
 
-    pub fn select_matches(&mut self, matches: Vec<RangeInclusive<Point>>) {
+    pub fn select_matches(&mut self, matches: Vec<RangeInclusive<AlacPoint>>) {
         let matches_to_select = self
             .matches
             .iter()
@@ -894,13 +907,13 @@ impl Terminal {
 
     pub fn select_all(&mut self) {
         let term = self.term.lock();
-        let start = Point::new(term.topmost_line(), Column(0));
-        let end = Point::new(term.bottommost_line(), term.last_column());
+        let start = AlacPoint::new(term.topmost_line(), Column(0));
+        let end = AlacPoint::new(term.bottommost_line(), term.last_column());
         drop(term);
         self.set_selection(Some((make_selection(&(start..=end)), end)));
     }
 
-    fn set_selection(&mut self, selection: Option<(Selection, Point)>) {
+    fn set_selection(&mut self, selection: Option<(Selection, AlacPoint)>) {
         self.events
             .push_back(InternalEvent::SetSelection(selection));
     }
@@ -954,11 +967,11 @@ impl Terminal {
     }
 
     pub fn try_modifiers_change(&mut self, modifiers: &Modifiers) -> bool {
-        let changed = self.cmd_pressed != modifiers.cmd;
-        if !self.cmd_pressed && modifiers.cmd {
+        let changed = self.cmd_pressed != modifiers.command;
+        if !self.cmd_pressed && modifiers.command {
             self.refresh_hovered_word();
         }
-        self.cmd_pressed = modifiers.cmd;
+        self.cmd_pressed = modifiers.command;
         changed
     }
 
@@ -982,17 +995,17 @@ impl Terminal {
             term.lock_unfair() //It's been too long, force block
         } else if let None = self.sync_task {
             //Skip this frame
-            let delay = cx.background().timer(Duration::from_millis(16));
-            self.sync_task = Some(cx.spawn_weak(|weak_handle, mut cx| async move {
+            let delay = cx.background_executor().timer(Duration::from_millis(16));
+            self.sync_task = Some(cx.spawn(|weak_handle, mut cx| async move {
                 delay.await;
-                cx.update(|cx| {
-                    if let Some(handle) = weak_handle.upgrade(cx) {
-                        handle.update(cx, |terminal, cx| {
+                if let Some(handle) = weak_handle.upgrade() {
+                    handle
+                        .update(&mut cx, |terminal, cx| {
                             terminal.sync_task.take();
                             cx.notify();
-                        });
-                    }
-                });
+                        })
+                        .ok();
+                }
             }));
             return;
         } else {
@@ -1050,7 +1063,7 @@ impl Terminal {
         }
     }
 
-    pub fn mouse_changed(&mut self, point: Point, side: AlacDirection) -> bool {
+    pub fn mouse_changed(&mut self, point: AlacPoint, side: AlacDirection) -> bool {
         match self.last_mouse {
             Some((old_point, old_side)) => {
                 if old_point == point && old_side == side {
@@ -1071,10 +1084,10 @@ impl Terminal {
         self.last_content.mode.intersects(TermMode::MOUSE_MODE) && !shift
     }
 
-    pub fn mouse_move(&mut self, e: &MouseMovedEvent, origin: Vector2F) {
-        let position = e.position.sub(origin);
+    pub fn mouse_move(&mut self, e: &MouseMoveEvent, origin: Point<Pixels>) {
+        let position = e.position - origin;
         self.last_mouse_position = Some(position);
-        if self.mouse_mode(e.shift) {
+        if self.mouse_mode(e.modifiers.shift) {
             let point = grid_point(
                 position,
                 self.last_content.size,
@@ -1092,7 +1105,7 @@ impl Terminal {
         }
     }
 
-    fn word_from_position(&mut self, position: Option<Vector2F>) {
+    fn word_from_position(&mut self, position: Option<Point<Pixels>>) {
         if self.selection_phase == SelectionPhase::Selecting {
             self.last_content.last_hovered_word = None;
         } else if let Some(position) = position {
@@ -1101,11 +1114,16 @@ impl Terminal {
         }
     }
 
-    pub fn mouse_drag(&mut self, e: MouseDrag, origin: Vector2F) {
-        let position = e.position.sub(origin);
+    pub fn mouse_drag(
+        &mut self,
+        e: &MouseMoveEvent,
+        origin: Point<Pixels>,
+        region: Bounds<Pixels>,
+    ) {
+        let position = e.position - origin;
         self.last_mouse_position = Some(position);
 
-        if !self.mouse_mode(e.shift) {
+        if !self.mouse_mode(e.modifiers.shift) {
             self.selection_phase = SelectionPhase::Selecting;
             // Alacritty has the same ordering, of first updating the selection
             // then scrolling 15ms later
@@ -1114,7 +1132,7 @@ impl Terminal {
 
             // Doesn't make sense to scroll the alt screen
             if !self.last_content.mode.contains(TermMode::ALT_SCREEN) {
-                let scroll_delta = match self.drag_line_delta(e) {
+                let scroll_delta = match self.drag_line_delta(e, region) {
                     Some(value) => value,
                     None => return,
                 };
@@ -1127,34 +1145,36 @@ impl Terminal {
         }
     }
 
-    fn drag_line_delta(&mut self, e: MouseDrag) -> Option<f32> {
+    fn drag_line_delta(&mut self, e: &MouseMoveEvent, region: Bounds<Pixels>) -> Option<Pixels> {
         //TODO: Why do these need to be doubled? Probably the same problem that the IME has
-        let top = e.region.origin_y() + (self.last_content.size.line_height * 2.);
-        let bottom = e.region.lower_left().y() - (self.last_content.size.line_height * 2.);
-        let scroll_delta = if e.position.y() < top {
-            (top - e.position.y()).powf(1.1)
-        } else if e.position.y() > bottom {
-            -((e.position.y() - bottom).powf(1.1))
+        let top = region.origin.y + (self.last_content.size.line_height * 2.);
+        let bottom = region.lower_left().y - (self.last_content.size.line_height * 2.);
+        let scroll_delta = if e.position.y < top {
+            (top - e.position.y).pow(1.1)
+        } else if e.position.y > bottom {
+            -((e.position.y - bottom).pow(1.1))
         } else {
             return None; //Nothing to do
         };
         Some(scroll_delta)
     }
 
-    pub fn mouse_down(&mut self, e: &MouseDown, origin: Vector2F) {
-        let position = e.position.sub(origin);
+    pub fn mouse_down(&mut self, e: &MouseDownEvent, origin: Point<Pixels>) {
+        let position = e.position - origin;
         let point = grid_point(
             position,
             self.last_content.size,
             self.last_content.display_offset,
         );
 
-        if self.mouse_mode(e.shift) {
-            if let Some(bytes) = mouse_button_report(point, e, true, self.last_content.mode) {
+        if self.mouse_mode(e.modifiers.shift) {
+            if let Some(bytes) =
+                mouse_button_report(point, e.button, e.modifiers, true, self.last_content.mode)
+            {
                 self.pty_tx.notify(bytes);
             }
         } else if e.button == MouseButton::Left {
-            let position = e.position.sub(origin);
+            let position = e.position - origin;
             let point = grid_point(
                 position,
                 self.last_content.size,
@@ -1182,18 +1202,25 @@ impl Terminal {
         }
     }
 
-    pub fn mouse_up(&mut self, e: &MouseUp, origin: Vector2F, cx: &mut ModelContext<Self>) {
-        let setting = settings::get::<TerminalSettings>(cx);
+    pub fn mouse_up(
+        &mut self,
+        e: &MouseUpEvent,
+        origin: Point<Pixels>,
+        cx: &mut ModelContext<Self>,
+    ) {
+        let setting = TerminalSettings::get_global(cx);
 
-        let position = e.position.sub(origin);
-        if self.mouse_mode(e.shift) {
+        let position = e.position - origin;
+        if self.mouse_mode(e.modifiers.shift) {
             let point = grid_point(
                 position,
                 self.last_content.size,
                 self.last_content.display_offset,
             );
 
-            if let Some(bytes) = mouse_button_report(point, e, false, self.last_content.mode) {
+            if let Some(bytes) =
+                mouse_button_report(point, e.button, e.modifiers, false, self.last_content.mode)
+            {
                 self.pty_tx.notify(bytes);
             }
         } else {
@@ -1205,7 +1232,7 @@ impl Terminal {
             if self.selection_phase == SelectionPhase::Ended {
                 let mouse_cell_index = content_index_for_mouse(position, &self.last_content.size);
                 if let Some(link) = self.last_content.cells[mouse_cell_index].hyperlink() {
-                    cx.platform().open_url(link.uri());
+                    cx.open_url(link.uri());
                 } else if self.cmd_pressed {
                     self.events
                         .push_back(InternalEvent::FindHyperlink(position, true));
@@ -1218,13 +1245,13 @@ impl Terminal {
     }
 
     ///Scroll the terminal
-    pub fn scroll_wheel(&mut self, e: MouseScrollWheel, origin: Vector2F) {
+    pub fn scroll_wheel(&mut self, e: &ScrollWheelEvent, origin: Point<Pixels>) {
         let mouse_mode = self.mouse_mode(e.shift);
 
         if let Some(scroll_lines) = self.determine_scroll_lines(&e, mouse_mode) {
             if mouse_mode {
                 let point = grid_point(
-                    e.position.sub(origin),
+                    e.position - origin,
                     self.last_content.size,
                     self.last_content.display_offset,
                 );
@@ -1257,34 +1284,30 @@ impl Terminal {
         self.word_from_position(self.last_mouse_position);
     }
 
-    fn determine_scroll_lines(&mut self, e: &MouseScrollWheel, mouse_mode: bool) -> Option<i32> {
+    fn determine_scroll_lines(&mut self, e: &ScrollWheelEvent, mouse_mode: bool) -> Option<i32> {
         let scroll_multiplier = if mouse_mode { 1. } else { SCROLL_MULTIPLIER };
         let line_height = self.last_content.size.line_height;
-        match e.phase {
+        match e.touch_phase {
             /* Reset scroll state on started */
-            Some(TouchPhase::Started) => {
-                self.scroll_px = 0.;
+            TouchPhase::Started => {
+                self.scroll_px = px(0.);
                 None
             }
             /* Calculate the appropriate scroll lines */
-            Some(gpui::platform::TouchPhase::Moved) => {
+            TouchPhase::Moved => {
                 let old_offset = (self.scroll_px / line_height) as i32;
 
-                self.scroll_px += e.delta.pixel_delta(line_height).y() * scroll_multiplier;
+                self.scroll_px += e.delta.pixel_delta(line_height).y * scroll_multiplier;
 
                 let new_offset = (self.scroll_px / line_height) as i32;
 
                 // Whenever we hit the edges, reset our stored scroll to 0
                 // so we can respond to changes in direction quickly
-                self.scroll_px %= self.last_content.size.height;
+                self.scroll_px %= self.last_content.size.height();
 
                 Some(new_offset - old_offset)
             }
-            /* Fall back to delta / line_height */
-            None => Some(
-                ((e.delta.pixel_delta(line_height).y() * scroll_multiplier) / line_height) as i32,
-            ),
-            _ => None,
+            TouchPhase::Ended => None,
         }
     }
 
@@ -1292,9 +1315,9 @@ impl Terminal {
         &mut self,
         searcher: RegexSearch,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Vec<RangeInclusive<Point>>> {
+    ) -> Task<Vec<RangeInclusive<AlacPoint>>> {
         let term = self.term.clone();
-        cx.background().spawn(async move {
+        cx.background_executor().spawn(async move {
             let term = term.lock();
 
             all_search_matches(&term, &searcher).collect()
@@ -1344,13 +1367,11 @@ impl Drop for Terminal {
     }
 }
 
-impl Entity for Terminal {
-    type Event = Event;
-}
+impl EventEmitter<Event> for Terminal {}
 
 /// Based on alacritty/src/display/hint.rs > regex_match_at
 /// Retrieve the match, if the specified point is inside the content matching the regex.
-fn regex_match_at<T>(term: &Term<T>, point: Point, regex: &RegexSearch) -> Option<Match> {
+fn regex_match_at<T>(term: &Term<T>, point: AlacPoint, regex: &RegexSearch) -> Option<Match> {
     visible_regex_match_iter(term, regex).find(|rm| rm.contains(&point))
 }
 
@@ -1362,8 +1383,8 @@ pub fn visible_regex_match_iter<'a, T>(
 ) -> impl Iterator<Item = Match> + 'a {
     let viewport_start = Line(-(term.grid().display_offset() as i32));
     let viewport_end = viewport_start + term.bottommost_line();
-    let mut start = term.line_search_left(Point::new(viewport_start, Column(0)));
-    let mut end = term.line_search_right(Point::new(viewport_end, Column(0)));
+    let mut start = term.line_search_left(AlacPoint::new(viewport_start, Column(0)));
+    let mut end = term.line_search_right(AlacPoint::new(viewport_end, Column(0)));
     start.line = start.line.max(viewport_start - MAX_SEARCH_LINES);
     end.line = end.line.min(viewport_end + MAX_SEARCH_LINES);
 
@@ -1372,7 +1393,7 @@ pub fn visible_regex_match_iter<'a, T>(
         .take_while(move |rm| rm.start().line <= viewport_end)
 }
 
-fn make_selection(range: &RangeInclusive<Point>) -> Selection {
+fn make_selection(range: &RangeInclusive<AlacPoint>) -> Selection {
     let mut selection = Selection::new(SelectionType::Simple, *range.start(), AlacDirection::Left);
     selection.update(*range.end(), AlacDirection::Right);
     selection
@@ -1382,33 +1403,124 @@ fn all_search_matches<'a, T>(
     term: &'a Term<T>,
     regex: &'a RegexSearch,
 ) -> impl Iterator<Item = Match> + 'a {
-    let start = Point::new(term.grid().topmost_line(), Column(0));
-    let end = Point::new(term.grid().bottommost_line(), term.grid().last_column());
+    let start = AlacPoint::new(term.grid().topmost_line(), Column(0));
+    let end = AlacPoint::new(term.grid().bottommost_line(), term.grid().last_column());
     RegexIter::new(start, end, AlacDirection::Right, term, regex)
 }
 
-fn content_index_for_mouse(pos: Vector2F, size: &TerminalSize) -> usize {
-    let col = (pos.x() / size.cell_width()).round() as usize;
-
+fn content_index_for_mouse(pos: Point<Pixels>, size: &TerminalSize) -> usize {
+    let col = (pos.x / size.cell_width()).round() as usize;
     let clamped_col = min(col, size.columns() - 1);
+    let row = (pos.y / size.line_height()).round() as usize;
+    let clamped_row = min(row, size.screen_lines() - 1);
+    clamped_row * size.columns() + clamped_col
+}
 
-    let row = (pos.y() / size.line_height()).round() as usize;
+///Converts an 8 bit ANSI color to it's GPUI equivalent.
+///Accepts usize for compatibility with the alacritty::Colors interface,
+///Other than that use case, should only be called with values in the [0,255] range
+pub fn get_color_at_index(index: usize, theme: &Theme) -> Hsla {
+    let colors = theme.colors();
+
+    match index {
+        //0-15 are the same as the named colors above
+        0 => colors.terminal_ansi_black,
+        1 => colors.terminal_ansi_red,
+        2 => colors.terminal_ansi_green,
+        3 => colors.terminal_ansi_yellow,
+        4 => colors.terminal_ansi_blue,
+        5 => colors.terminal_ansi_magenta,
+        6 => colors.terminal_ansi_cyan,
+        7 => colors.terminal_ansi_white,
+        8 => colors.terminal_ansi_bright_black,
+        9 => colors.terminal_ansi_bright_red,
+        10 => colors.terminal_ansi_bright_green,
+        11 => colors.terminal_ansi_bright_yellow,
+        12 => colors.terminal_ansi_bright_blue,
+        13 => colors.terminal_ansi_bright_magenta,
+        14 => colors.terminal_ansi_bright_cyan,
+        15 => colors.terminal_ansi_bright_white,
+        //16-231 are mapped to their RGB colors on a 0-5 range per channel
+        16..=231 => {
+            let (r, g, b) = rgb_for_index(&(index as u8)); //Split the index into it's ANSI-RGB components
+            let step = (u8::MAX as f32 / 5.).floor() as u8; //Split the RGB range into 5 chunks, with floor so no overflow
+            rgba_color(r * step, g * step, b * step) //Map the ANSI-RGB components to an RGB color
+        }
+        //232-255 are a 24 step grayscale from black to white
+        232..=255 => {
+            let i = index as u8 - 232; //Align index to 0..24
+            let step = (u8::MAX as f32 / 24.).floor() as u8; //Split the RGB grayscale values into 24 chunks
+            rgba_color(i * step, i * step, i * step) //Map the ANSI-grayscale components to the RGB-grayscale
+        }
+        //For compatibility with the alacritty::Colors interface
+        256 => colors.text,
+        257 => colors.background,
+        258 => theme.players().local().cursor,
+
+        // todo!(more colors)
+        259 => red(),                      //style.dim_black,
+        260 => red(),                      //style.dim_red,
+        261 => red(),                      //style.dim_green,
+        262 => red(),                      //style.dim_yellow,
+        263 => red(),                      //style.dim_blue,
+        264 => red(),                      //style.dim_magenta,
+        265 => red(),                      //style.dim_cyan,
+        266 => red(),                      //style.dim_white,
+        267 => red(),                      //style.bright_foreground,
+        268 => colors.terminal_ansi_black, //'Dim Background', non-standard color
+
+        _ => black(),
+    }
+}
 
-    let clamped_row = min(row, size.screen_lines() - 1);
+///Generates the rgb channels in [0, 5] for a given index into the 6x6x6 ANSI color cube
+///See: [8 bit ansi color](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit).
+///
+///Wikipedia gives a formula for calculating the index for a given color:
+///
+///index = 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5)
+///
+///This function does the reverse, calculating the r, g, and b components from a given index.
+fn rgb_for_index(i: &u8) -> (u8, u8, u8) {
+    debug_assert!((&16..=&231).contains(&i));
+    let i = i - 16;
+    let r = (i - (i % 36)) / 36;
+    let g = ((i % 36) - (i % 6)) / 6;
+    let b = (i % 36) % 6;
+    (r, g, b)
+}
 
-    clamped_row * size.columns() + clamped_col
+pub fn rgba_color(r: u8, g: u8, b: u8) -> Hsla {
+    Rgba {
+        r: (r as f32 / 255.) as f32,
+        g: (g as f32 / 255.) as f32,
+        b: (b as f32 / 255.) as f32,
+        a: 1.,
+    }
+    .into()
 }
 
 #[cfg(test)]
 mod tests {
     use alacritty_terminal::{
-        index::{Column, Line, Point},
+        index::{Column, Line, Point as AlacPoint},
         term::cell::Cell,
     };
-    use gpui::geometry::vector::vec2f;
+    use gpui::{point, size, Pixels};
     use rand::{distributions::Alphanumeric, rngs::ThreadRng, thread_rng, Rng};
 
-    use crate::{content_index_for_mouse, IndexedCell, TerminalContent, TerminalSize};
+    use crate::{
+        content_index_for_mouse, rgb_for_index, IndexedCell, TerminalContent, TerminalSize,
+    };
+
+    #[test]
+    fn test_rgb_for_index() {
+        //Test every possible value in the color cube
+        for i in 16..=231 {
+            let (r, g, b) = rgb_for_index(&(i as u8));
+            assert_eq!(i, 16 + 36 * r + 6 * g + b);
+        }
+    }
 
     #[test]
     fn test_mouse_to_cell_test() {
@@ -1421,10 +1533,12 @@ mod tests {
             let cell_size = rng.gen_range(5 * PRECISION..20 * PRECISION) as f32 / PRECISION as f32;
 
             let size = crate::TerminalSize {
-                cell_width: cell_size,
-                line_height: cell_size,
-                height: cell_size * (viewport_cells as f32),
-                width: cell_size * (viewport_cells as f32),
+                cell_width: Pixels::from(cell_size),
+                line_height: Pixels::from(cell_size),
+                size: size(
+                    Pixels::from(cell_size * (viewport_cells as f32)),
+                    Pixels::from(cell_size * (viewport_cells as f32)),
+                ),
             };
 
             let cells = get_cells(size, &mut rng);
@@ -1438,9 +1552,9 @@ mod tests {
                     let row_offset = rng.gen_range(0..PRECISION) as f32 / PRECISION as f32;
                     let col_offset = rng.gen_range(0..PRECISION) as f32 / PRECISION as f32;
 
-                    let mouse_pos = vec2f(
-                        col as f32 * cell_size + col_offset,
-                        row as f32 * cell_size + row_offset,
+                    let mouse_pos = point(
+                        Pixels::from(col as f32 * cell_size + col_offset),
+                        Pixels::from(row as f32 * cell_size + row_offset),
                     );
 
                     let content_index = content_index_for_mouse(mouse_pos, &content.size);
@@ -1458,21 +1572,28 @@ mod tests {
         let mut rng = thread_rng();
 
         let size = crate::TerminalSize {
-            cell_width: 10.,
-            line_height: 10.,
-            height: 100.,
-            width: 100.,
+            cell_width: Pixels::from(10.),
+            line_height: Pixels::from(10.),
+            size: size(Pixels::from(100.), Pixels::from(100.)),
         };
 
         let cells = get_cells(size, &mut rng);
         let content = convert_cells_to_content(size, &cells);
 
         assert_eq!(
-            content.cells[content_index_for_mouse(vec2f(-10., -10.), &content.size)].c,
+            content.cells[content_index_for_mouse(
+                point(Pixels::from(-10.), Pixels::from(-10.)),
+                &content.size
+            )]
+            .c,
             cells[0][0]
         );
         assert_eq!(
-            content.cells[content_index_for_mouse(vec2f(1000., 1000.), &content.size)].c,
+            content.cells[content_index_for_mouse(
+                point(Pixels::from(1000.), Pixels::from(1000.)),
+                &content.size
+            )]
+            .c,
             cells[9][9]
         );
     }
@@ -1480,9 +1601,9 @@ mod tests {
     fn get_cells(size: TerminalSize, rng: &mut ThreadRng) -> Vec<Vec<char>> {
         let mut cells = Vec::new();
 
-        for _ in 0..((size.height() / size.line_height()) as usize) {
+        for _ in 0..(f32::from(size.height() / size.line_height()) as usize) {
             let mut row_vec = Vec::new();
-            for _ in 0..((size.width() / size.cell_width()) as usize) {
+            for _ in 0..(f32::from(size.width() / size.cell_width()) as usize) {
                 let cell_char = rng.sample(Alphanumeric) as char;
                 row_vec.push(cell_char)
             }
@@ -1499,7 +1620,7 @@ mod tests {
             for col in 0..cells[row].len() {
                 let cell_char = cells[row][col];
                 ic.push(IndexedCell {
-                    point: Point::new(Line(row as i32), Column(col)),
+                    point: AlacPoint::new(Line(row as i32), Column(col)),
                     cell: Cell {
                         c: cell_char,
                         ..Default::default()

crates/terminal/src/terminal_settings.rs 🔗

@@ -1,8 +1,7 @@
-use std::{collections::HashMap, path::PathBuf};
-
-use gpui::{fonts, AppContext};
+use gpui::{px, AbsoluteLength, AppContext, FontFeatures, Pixels};
 use schemars::JsonSchema;
 use serde_derive::{Deserialize, Serialize};
+use std::{collections::HashMap, path::PathBuf};
 
 #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
 #[serde(rename_all = "snake_case")]
@@ -16,18 +15,18 @@ pub enum TerminalDockPosition {
 pub struct TerminalSettings {
     pub shell: Shell,
     pub working_directory: WorkingDirectory,
-    font_size: Option<f32>,
+    pub font_size: Option<Pixels>,
     pub font_family: Option<String>,
     pub line_height: TerminalLineHeight,
-    pub font_features: Option<fonts::Features>,
+    pub font_features: Option<FontFeatures>,
     pub env: HashMap<String, String>,
     pub blinking: TerminalBlink,
     pub alternate_scroll: AlternateScroll,
     pub option_as_meta: bool,
     pub copy_on_select: bool,
     pub dock: TerminalDockPosition,
-    pub default_width: f32,
-    pub default_height: f32,
+    pub default_width: Pixels,
+    pub default_height: Pixels,
     pub detect_venv: VenvSettings,
 }
 
@@ -79,7 +78,7 @@ pub struct TerminalSettingsContent {
     pub font_size: Option<f32>,
     pub font_family: Option<String>,
     pub line_height: Option<TerminalLineHeight>,
-    pub font_features: Option<fonts::Features>,
+    pub font_features: Option<FontFeatures>,
     pub env: Option<HashMap<String, String>>,
     pub blinking: Option<TerminalBlink>,
     pub alternate_scroll: Option<AlternateScroll>,
@@ -91,14 +90,7 @@ pub struct TerminalSettingsContent {
     pub detect_venv: Option<VenvSettings>,
 }
 
-impl TerminalSettings {
-    pub fn font_size(&self, cx: &AppContext) -> Option<f32> {
-        self.font_size
-            .map(|size| theme::adjusted_font_size(size, cx))
-    }
-}
-
-impl settings::Setting for TerminalSettings {
+impl settings::Settings for TerminalSettings {
     const KEY: Option<&'static str> = Some("terminal");
 
     type FileContent = TerminalSettingsContent;
@@ -106,7 +98,7 @@ impl settings::Setting for TerminalSettings {
     fn load(
         default_value: &Self::FileContent,
         user_values: &[&Self::FileContent],
-        _: &AppContext,
+        _: &mut AppContext,
     ) -> anyhow::Result<Self> {
         Self::load_via_json_merge(default_value, user_values)
     }
@@ -122,12 +114,13 @@ pub enum TerminalLineHeight {
 }
 
 impl TerminalLineHeight {
-    pub fn value(&self) -> f32 {
-        match self {
+    pub fn value(&self) -> AbsoluteLength {
+        let value = match self {
             TerminalLineHeight::Comfortable => 1.618,
             TerminalLineHeight::Standard => 1.3,
             TerminalLineHeight::Custom(line_height) => f32::max(*line_height, 1.),
-        }
+        };
+        px(value).into()
     }
 }
 

crates/terminal2/Cargo.toml 🔗

@@ -1,38 +0,0 @@
-[package]
-name = "terminal2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/terminal2.rs"
-doctest = false
-
-
-[dependencies]
-gpui = { package = "gpui2", path = "../gpui2" }
-settings = { package = "settings2", path = "../settings2" }
-db = { package = "db2", path = "../db2" }
-theme = { package = "theme2", path = "../theme2" }
-util = { path = "../util" }
-
-alacritty_terminal = { git = "https://github.com/zed-industries/alacritty", rev = "33306142195b354ef3485ca2b1d8a85dfc6605ca" }
-procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "5cd757e5f2eb039ed0c6bb6512223e69d5efc64d", default-features = false }
-smallvec.workspace = true
-smol.workspace = true
-mio-extras = "2.0.6"
-futures.workspace = true
-ordered-float.workspace = true
-itertools = "0.10"
-dirs = "4.0.0"
-shellexpand = "2.1.0"
-libc = "0.2"
-anyhow.workspace = true
-schemars.workspace = true
-thiserror.workspace = true
-lazy_static.workspace = true
-serde.workspace = true
-serde_derive.workspace = true
-
-[dev-dependencies]
-rand.workspace = true

crates/terminal2/src/mappings/colors.rs 🔗

@@ -1,12 +0,0 @@
-use alacritty_terminal::term::color::Rgb as AlacRgb;
-
-use gpui::Rgba;
-
-//Convenience method to convert from a GPUI color to an alacritty Rgb
-pub fn to_alac_rgb(color: impl Into<Rgba>) -> AlacRgb {
-    let color = color.into();
-    let r = ((color.r * color.a) * 255.) as u8;
-    let g = ((color.g * color.a) * 255.) as u8;
-    let b = ((color.b * color.a) * 255.) as u8;
-    AlacRgb::new(r, g, b)
-}

crates/terminal2/src/mappings/keys.rs 🔗

@@ -1,464 +0,0 @@
-/// The mappings defined in this file where created from reading the alacritty source
-use alacritty_terminal::term::TermMode;
-use gpui::Keystroke;
-
-#[derive(Debug, PartialEq, Eq)]
-enum AlacModifiers {
-    None,
-    Alt,
-    Ctrl,
-    Shift,
-    CtrlShift,
-    Other,
-}
-
-impl AlacModifiers {
-    fn new(ks: &Keystroke) -> Self {
-        match (
-            ks.modifiers.alt,
-            ks.modifiers.control,
-            ks.modifiers.shift,
-            ks.modifiers.command,
-        ) {
-            (false, false, false, false) => AlacModifiers::None,
-            (true, false, false, false) => AlacModifiers::Alt,
-            (false, true, false, false) => AlacModifiers::Ctrl,
-            (false, false, true, false) => AlacModifiers::Shift,
-            (false, true, true, false) => AlacModifiers::CtrlShift,
-            _ => AlacModifiers::Other,
-        }
-    }
-
-    fn any(&self) -> bool {
-        match &self {
-            AlacModifiers::None => false,
-            AlacModifiers::Alt => true,
-            AlacModifiers::Ctrl => true,
-            AlacModifiers::Shift => true,
-            AlacModifiers::CtrlShift => true,
-            AlacModifiers::Other => true,
-        }
-    }
-}
-
-pub fn to_esc_str(keystroke: &Keystroke, mode: &TermMode, alt_is_meta: bool) -> Option<String> {
-    let modifiers = AlacModifiers::new(keystroke);
-
-    // Manual Bindings including modifiers
-    let manual_esc_str = match (keystroke.key.as_ref(), &modifiers) {
-        //Basic special keys
-        ("tab", AlacModifiers::None) => Some("\x09".to_string()),
-        ("escape", AlacModifiers::None) => Some("\x1b".to_string()),
-        ("enter", AlacModifiers::None) => Some("\x0d".to_string()),
-        ("enter", AlacModifiers::Shift) => Some("\x0d".to_string()),
-        ("backspace", AlacModifiers::None) => Some("\x7f".to_string()),
-        //Interesting escape codes
-        ("tab", AlacModifiers::Shift) => Some("\x1b[Z".to_string()),
-        ("backspace", AlacModifiers::Alt) => Some("\x1b\x7f".to_string()),
-        ("backspace", AlacModifiers::Shift) => Some("\x7f".to_string()),
-        ("home", AlacModifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => {
-            Some("\x1b[1;2H".to_string())
-        }
-        ("end", AlacModifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => {
-            Some("\x1b[1;2F".to_string())
-        }
-        ("pageup", AlacModifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => {
-            Some("\x1b[5;2~".to_string())
-        }
-        ("pagedown", AlacModifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => {
-            Some("\x1b[6;2~".to_string())
-        }
-        ("home", AlacModifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
-            Some("\x1bOH".to_string())
-        }
-        ("home", AlacModifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
-            Some("\x1b[H".to_string())
-        }
-        ("end", AlacModifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
-            Some("\x1bOF".to_string())
-        }
-        ("end", AlacModifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
-            Some("\x1b[F".to_string())
-        }
-        ("up", AlacModifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
-            Some("\x1bOA".to_string())
-        }
-        ("up", AlacModifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
-            Some("\x1b[A".to_string())
-        }
-        ("down", AlacModifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
-            Some("\x1bOB".to_string())
-        }
-        ("down", AlacModifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
-            Some("\x1b[B".to_string())
-        }
-        ("right", AlacModifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
-            Some("\x1bOC".to_string())
-        }
-        ("right", AlacModifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
-            Some("\x1b[C".to_string())
-        }
-        ("left", AlacModifiers::None) if mode.contains(TermMode::APP_CURSOR) => {
-            Some("\x1bOD".to_string())
-        }
-        ("left", AlacModifiers::None) if !mode.contains(TermMode::APP_CURSOR) => {
-            Some("\x1b[D".to_string())
-        }
-        ("back", AlacModifiers::None) => Some("\x7f".to_string()),
-        ("insert", AlacModifiers::None) => Some("\x1b[2~".to_string()),
-        ("delete", AlacModifiers::None) => Some("\x1b[3~".to_string()),
-        ("pageup", AlacModifiers::None) => Some("\x1b[5~".to_string()),
-        ("pagedown", AlacModifiers::None) => Some("\x1b[6~".to_string()),
-        ("f1", AlacModifiers::None) => Some("\x1bOP".to_string()),
-        ("f2", AlacModifiers::None) => Some("\x1bOQ".to_string()),
-        ("f3", AlacModifiers::None) => Some("\x1bOR".to_string()),
-        ("f4", AlacModifiers::None) => Some("\x1bOS".to_string()),
-        ("f5", AlacModifiers::None) => Some("\x1b[15~".to_string()),
-        ("f6", AlacModifiers::None) => Some("\x1b[17~".to_string()),
-        ("f7", AlacModifiers::None) => Some("\x1b[18~".to_string()),
-        ("f8", AlacModifiers::None) => Some("\x1b[19~".to_string()),
-        ("f9", AlacModifiers::None) => Some("\x1b[20~".to_string()),
-        ("f10", AlacModifiers::None) => Some("\x1b[21~".to_string()),
-        ("f11", AlacModifiers::None) => Some("\x1b[23~".to_string()),
-        ("f12", AlacModifiers::None) => Some("\x1b[24~".to_string()),
-        ("f13", AlacModifiers::None) => Some("\x1b[25~".to_string()),
-        ("f14", AlacModifiers::None) => Some("\x1b[26~".to_string()),
-        ("f15", AlacModifiers::None) => Some("\x1b[28~".to_string()),
-        ("f16", AlacModifiers::None) => Some("\x1b[29~".to_string()),
-        ("f17", AlacModifiers::None) => Some("\x1b[31~".to_string()),
-        ("f18", AlacModifiers::None) => Some("\x1b[32~".to_string()),
-        ("f19", AlacModifiers::None) => Some("\x1b[33~".to_string()),
-        ("f20", AlacModifiers::None) => Some("\x1b[34~".to_string()),
-        // NumpadEnter, Action::Esc("\n".into());
-        //Mappings for caret notation keys
-        ("a", AlacModifiers::Ctrl) => Some("\x01".to_string()), //1
-        ("A", AlacModifiers::CtrlShift) => Some("\x01".to_string()), //1
-        ("b", AlacModifiers::Ctrl) => Some("\x02".to_string()), //2
-        ("B", AlacModifiers::CtrlShift) => Some("\x02".to_string()), //2
-        ("c", AlacModifiers::Ctrl) => Some("\x03".to_string()), //3
-        ("C", AlacModifiers::CtrlShift) => Some("\x03".to_string()), //3
-        ("d", AlacModifiers::Ctrl) => Some("\x04".to_string()), //4
-        ("D", AlacModifiers::CtrlShift) => Some("\x04".to_string()), //4
-        ("e", AlacModifiers::Ctrl) => Some("\x05".to_string()), //5
-        ("E", AlacModifiers::CtrlShift) => Some("\x05".to_string()), //5
-        ("f", AlacModifiers::Ctrl) => Some("\x06".to_string()), //6
-        ("F", AlacModifiers::CtrlShift) => Some("\x06".to_string()), //6
-        ("g", AlacModifiers::Ctrl) => Some("\x07".to_string()), //7
-        ("G", AlacModifiers::CtrlShift) => Some("\x07".to_string()), //7
-        ("h", AlacModifiers::Ctrl) => Some("\x08".to_string()), //8
-        ("H", AlacModifiers::CtrlShift) => Some("\x08".to_string()), //8
-        ("i", AlacModifiers::Ctrl) => Some("\x09".to_string()), //9
-        ("I", AlacModifiers::CtrlShift) => Some("\x09".to_string()), //9
-        ("j", AlacModifiers::Ctrl) => Some("\x0a".to_string()), //10
-        ("J", AlacModifiers::CtrlShift) => Some("\x0a".to_string()), //10
-        ("k", AlacModifiers::Ctrl) => Some("\x0b".to_string()), //11
-        ("K", AlacModifiers::CtrlShift) => Some("\x0b".to_string()), //11
-        ("l", AlacModifiers::Ctrl) => Some("\x0c".to_string()), //12
-        ("L", AlacModifiers::CtrlShift) => Some("\x0c".to_string()), //12
-        ("m", AlacModifiers::Ctrl) => Some("\x0d".to_string()), //13
-        ("M", AlacModifiers::CtrlShift) => Some("\x0d".to_string()), //13
-        ("n", AlacModifiers::Ctrl) => Some("\x0e".to_string()), //14
-        ("N", AlacModifiers::CtrlShift) => Some("\x0e".to_string()), //14
-        ("o", AlacModifiers::Ctrl) => Some("\x0f".to_string()), //15
-        ("O", AlacModifiers::CtrlShift) => Some("\x0f".to_string()), //15
-        ("p", AlacModifiers::Ctrl) => Some("\x10".to_string()), //16
-        ("P", AlacModifiers::CtrlShift) => Some("\x10".to_string()), //16
-        ("q", AlacModifiers::Ctrl) => Some("\x11".to_string()), //17
-        ("Q", AlacModifiers::CtrlShift) => Some("\x11".to_string()), //17
-        ("r", AlacModifiers::Ctrl) => Some("\x12".to_string()), //18
-        ("R", AlacModifiers::CtrlShift) => Some("\x12".to_string()), //18
-        ("s", AlacModifiers::Ctrl) => Some("\x13".to_string()), //19
-        ("S", AlacModifiers::CtrlShift) => Some("\x13".to_string()), //19
-        ("t", AlacModifiers::Ctrl) => Some("\x14".to_string()), //20
-        ("T", AlacModifiers::CtrlShift) => Some("\x14".to_string()), //20
-        ("u", AlacModifiers::Ctrl) => Some("\x15".to_string()), //21
-        ("U", AlacModifiers::CtrlShift) => Some("\x15".to_string()), //21
-        ("v", AlacModifiers::Ctrl) => Some("\x16".to_string()), //22
-        ("V", AlacModifiers::CtrlShift) => Some("\x16".to_string()), //22
-        ("w", AlacModifiers::Ctrl) => Some("\x17".to_string()), //23
-        ("W", AlacModifiers::CtrlShift) => Some("\x17".to_string()), //23
-        ("x", AlacModifiers::Ctrl) => Some("\x18".to_string()), //24
-        ("X", AlacModifiers::CtrlShift) => Some("\x18".to_string()), //24
-        ("y", AlacModifiers::Ctrl) => Some("\x19".to_string()), //25
-        ("Y", AlacModifiers::CtrlShift) => Some("\x19".to_string()), //25
-        ("z", AlacModifiers::Ctrl) => Some("\x1a".to_string()), //26
-        ("Z", AlacModifiers::CtrlShift) => Some("\x1a".to_string()), //26
-        ("@", AlacModifiers::Ctrl) => Some("\x00".to_string()), //0
-        ("[", AlacModifiers::Ctrl) => Some("\x1b".to_string()), //27
-        ("\\", AlacModifiers::Ctrl) => Some("\x1c".to_string()), //28
-        ("]", AlacModifiers::Ctrl) => Some("\x1d".to_string()), //29
-        ("^", AlacModifiers::Ctrl) => Some("\x1e".to_string()), //30
-        ("_", AlacModifiers::Ctrl) => Some("\x1f".to_string()), //31
-        ("?", AlacModifiers::Ctrl) => Some("\x7f".to_string()), //127
-        _ => None,
-    };
-    if manual_esc_str.is_some() {
-        return manual_esc_str;
-    }
-
-    // Automated bindings applying modifiers
-    if modifiers.any() {
-        let modifier_code = modifier_code(keystroke);
-        let modified_esc_str = match keystroke.key.as_ref() {
-            "up" => Some(format!("\x1b[1;{}A", modifier_code)),
-            "down" => Some(format!("\x1b[1;{}B", modifier_code)),
-            "right" => Some(format!("\x1b[1;{}C", modifier_code)),
-            "left" => Some(format!("\x1b[1;{}D", modifier_code)),
-            "f1" => Some(format!("\x1b[1;{}P", modifier_code)),
-            "f2" => Some(format!("\x1b[1;{}Q", modifier_code)),
-            "f3" => Some(format!("\x1b[1;{}R", modifier_code)),
-            "f4" => Some(format!("\x1b[1;{}S", modifier_code)),
-            "F5" => Some(format!("\x1b[15;{}~", modifier_code)),
-            "f6" => Some(format!("\x1b[17;{}~", modifier_code)),
-            "f7" => Some(format!("\x1b[18;{}~", modifier_code)),
-            "f8" => Some(format!("\x1b[19;{}~", modifier_code)),
-            "f9" => Some(format!("\x1b[20;{}~", modifier_code)),
-            "f10" => Some(format!("\x1b[21;{}~", modifier_code)),
-            "f11" => Some(format!("\x1b[23;{}~", modifier_code)),
-            "f12" => Some(format!("\x1b[24;{}~", modifier_code)),
-            "f13" => Some(format!("\x1b[25;{}~", modifier_code)),
-            "f14" => Some(format!("\x1b[26;{}~", modifier_code)),
-            "f15" => Some(format!("\x1b[28;{}~", modifier_code)),
-            "f16" => Some(format!("\x1b[29;{}~", modifier_code)),
-            "f17" => Some(format!("\x1b[31;{}~", modifier_code)),
-            "f18" => Some(format!("\x1b[32;{}~", modifier_code)),
-            "f19" => Some(format!("\x1b[33;{}~", modifier_code)),
-            "f20" => Some(format!("\x1b[34;{}~", modifier_code)),
-            _ if modifier_code == 2 => None,
-            "insert" => Some(format!("\x1b[2;{}~", modifier_code)),
-            "pageup" => Some(format!("\x1b[5;{}~", modifier_code)),
-            "pagedown" => Some(format!("\x1b[6;{}~", modifier_code)),
-            "end" => Some(format!("\x1b[1;{}F", modifier_code)),
-            "home" => Some(format!("\x1b[1;{}H", modifier_code)),
-            _ => None,
-        };
-        if modified_esc_str.is_some() {
-            return modified_esc_str;
-        }
-    }
-
-    let alt_meta_binding =
-        if alt_is_meta && modifiers == AlacModifiers::Alt && keystroke.key.is_ascii() {
-            Some(format!("\x1b{}", keystroke.key))
-        } else {
-            None
-        };
-
-    if alt_meta_binding.is_some() {
-        return alt_meta_binding;
-    }
-
-    None
-}
-
-///   Code     Modifiers
-/// ---------+---------------------------
-///    2     | Shift
-///    3     | Alt
-///    4     | Shift + Alt
-///    5     | Control
-///    6     | Shift + Control
-///    7     | Alt + Control
-///    8     | Shift + Alt + Control
-/// ---------+---------------------------
-/// from: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys
-fn modifier_code(keystroke: &Keystroke) -> u32 {
-    let mut modifier_code = 0;
-    if keystroke.modifiers.shift {
-        modifier_code |= 1;
-    }
-    if keystroke.modifiers.alt {
-        modifier_code |= 1 << 1;
-    }
-    if keystroke.modifiers.control {
-        modifier_code |= 1 << 2;
-    }
-    modifier_code + 1
-}
-
-#[cfg(test)]
-mod test {
-    use gpui::Modifiers;
-
-    use super::*;
-
-    #[test]
-    fn test_scroll_keys() {
-        //These keys should be handled by the scrolling element directly
-        //Need to signify this by returning 'None'
-        let shift_pageup = Keystroke::parse("shift-pageup").unwrap();
-        let shift_pagedown = Keystroke::parse("shift-pagedown").unwrap();
-        let shift_home = Keystroke::parse("shift-home").unwrap();
-        let shift_end = Keystroke::parse("shift-end").unwrap();
-
-        let none = TermMode::NONE;
-        assert_eq!(to_esc_str(&shift_pageup, &none, false), None);
-        assert_eq!(to_esc_str(&shift_pagedown, &none, false), None);
-        assert_eq!(to_esc_str(&shift_home, &none, false), None);
-        assert_eq!(to_esc_str(&shift_end, &none, false), None);
-
-        let alt_screen = TermMode::ALT_SCREEN;
-        assert_eq!(
-            to_esc_str(&shift_pageup, &alt_screen, false),
-            Some("\x1b[5;2~".to_string())
-        );
-        assert_eq!(
-            to_esc_str(&shift_pagedown, &alt_screen, false),
-            Some("\x1b[6;2~".to_string())
-        );
-        assert_eq!(
-            to_esc_str(&shift_home, &alt_screen, false),
-            Some("\x1b[1;2H".to_string())
-        );
-        assert_eq!(
-            to_esc_str(&shift_end, &alt_screen, false),
-            Some("\x1b[1;2F".to_string())
-        );
-
-        let pageup = Keystroke::parse("pageup").unwrap();
-        let pagedown = Keystroke::parse("pagedown").unwrap();
-        let any = TermMode::ANY;
-
-        assert_eq!(
-            to_esc_str(&pageup, &any, false),
-            Some("\x1b[5~".to_string())
-        );
-        assert_eq!(
-            to_esc_str(&pagedown, &any, false),
-            Some("\x1b[6~".to_string())
-        );
-    }
-
-    #[test]
-    fn test_plain_inputs() {
-        let ks = Keystroke {
-            modifiers: Modifiers {
-                control: false,
-                alt: false,
-                shift: false,
-                command: false,
-                function: false,
-            },
-            key: "🖖🏻".to_string(), //2 char string
-            ime_key: None,
-        };
-        assert_eq!(to_esc_str(&ks, &TermMode::NONE, false), None);
-    }
-
-    #[test]
-    fn test_application_mode() {
-        let app_cursor = TermMode::APP_CURSOR;
-        let none = TermMode::NONE;
-
-        let up = Keystroke::parse("up").unwrap();
-        let down = Keystroke::parse("down").unwrap();
-        let left = Keystroke::parse("left").unwrap();
-        let right = Keystroke::parse("right").unwrap();
-
-        assert_eq!(to_esc_str(&up, &none, false), Some("\x1b[A".to_string()));
-        assert_eq!(to_esc_str(&down, &none, false), Some("\x1b[B".to_string()));
-        assert_eq!(to_esc_str(&right, &none, false), Some("\x1b[C".to_string()));
-        assert_eq!(to_esc_str(&left, &none, false), Some("\x1b[D".to_string()));
-
-        assert_eq!(
-            to_esc_str(&up, &app_cursor, false),
-            Some("\x1bOA".to_string())
-        );
-        assert_eq!(
-            to_esc_str(&down, &app_cursor, false),
-            Some("\x1bOB".to_string())
-        );
-        assert_eq!(
-            to_esc_str(&right, &app_cursor, false),
-            Some("\x1bOC".to_string())
-        );
-        assert_eq!(
-            to_esc_str(&left, &app_cursor, false),
-            Some("\x1bOD".to_string())
-        );
-    }
-
-    #[test]
-    fn test_ctrl_codes() {
-        let letters_lower = 'a'..='z';
-        let letters_upper = 'A'..='Z';
-        let mode = TermMode::ANY;
-
-        for (lower, upper) in letters_lower.zip(letters_upper) {
-            assert_eq!(
-                to_esc_str(
-                    &Keystroke::parse(&format!("ctrl-{}", lower)).unwrap(),
-                    &mode,
-                    false
-                ),
-                to_esc_str(
-                    &Keystroke::parse(&format!("ctrl-shift-{}", upper)).unwrap(),
-                    &mode,
-                    false
-                ),
-                "On letter: {}/{}",
-                lower,
-                upper
-            )
-        }
-    }
-
-    #[test]
-    fn alt_is_meta() {
-        let ascii_printable = ' '..='~';
-        for character in ascii_printable {
-            assert_eq!(
-                to_esc_str(
-                    &Keystroke::parse(&format!("alt-{}", character)).unwrap(),
-                    &TermMode::NONE,
-                    true
-                )
-                .unwrap(),
-                format!("\x1b{}", character)
-            );
-        }
-
-        let gpui_keys = [
-            "up", "down", "right", "left", "f1", "f2", "f3", "f4", "F5", "f6", "f7", "f8", "f9",
-            "f10", "f11", "f12", "f13", "f14", "f15", "f16", "f17", "f18", "f19", "f20", "insert",
-            "pageup", "pagedown", "end", "home",
-        ];
-
-        for key in gpui_keys {
-            assert_ne!(
-                to_esc_str(
-                    &Keystroke::parse(&format!("alt-{}", key)).unwrap(),
-                    &TermMode::NONE,
-                    true
-                )
-                .unwrap(),
-                format!("\x1b{}", key)
-            );
-        }
-    }
-
-    #[test]
-    fn test_modifier_code_calc() {
-        //   Code     Modifiers
-        // ---------+---------------------------
-        //    2     | Shift
-        //    3     | Alt
-        //    4     | Shift + Alt
-        //    5     | Control
-        //    6     | Shift + Control
-        //    7     | Alt + Control
-        //    8     | Shift + Alt + Control
-        // ---------+---------------------------
-        // from: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys
-        assert_eq!(2, modifier_code(&Keystroke::parse("shift-A").unwrap()));
-        assert_eq!(3, modifier_code(&Keystroke::parse("alt-A").unwrap()));
-        assert_eq!(4, modifier_code(&Keystroke::parse("shift-alt-A").unwrap()));
-        assert_eq!(5, modifier_code(&Keystroke::parse("ctrl-A").unwrap()));
-        assert_eq!(6, modifier_code(&Keystroke::parse("shift-ctrl-A").unwrap()));
-        assert_eq!(7, modifier_code(&Keystroke::parse("alt-ctrl-A").unwrap()));
-        assert_eq!(
-            8,
-            modifier_code(&Keystroke::parse("shift-ctrl-alt-A").unwrap())
-        );
-    }
-}

crates/terminal2/src/mappings/mouse.rs 🔗

@@ -1,277 +0,0 @@
-use std::cmp::{self, max, min};
-use std::iter::repeat;
-
-use alacritty_terminal::grid::Dimensions;
-/// Most of the code, and specifically the constants, in this are copied from Alacritty,
-/// with modifications for our circumstances
-use alacritty_terminal::index::{Column as GridCol, Line as GridLine, Point as AlacPoint, Side};
-use alacritty_terminal::term::TermMode;
-use gpui::{px, Modifiers, MouseButton, MouseMoveEvent, Pixels, Point, ScrollWheelEvent};
-
-use crate::TerminalSize;
-
-enum MouseFormat {
-    SGR,
-    Normal(bool),
-}
-
-impl MouseFormat {
-    fn from_mode(mode: TermMode) -> Self {
-        if mode.contains(TermMode::SGR_MOUSE) {
-            MouseFormat::SGR
-        } else if mode.contains(TermMode::UTF8_MOUSE) {
-            MouseFormat::Normal(true)
-        } else {
-            MouseFormat::Normal(false)
-        }
-    }
-}
-
-#[derive(Debug)]
-enum AlacMouseButton {
-    LeftButton = 0,
-    MiddleButton = 1,
-    RightButton = 2,
-    LeftMove = 32,
-    MiddleMove = 33,
-    RightMove = 34,
-    NoneMove = 35,
-    ScrollUp = 64,
-    ScrollDown = 65,
-    Other = 99,
-}
-
-impl AlacMouseButton {
-    fn from_move(e: &MouseMoveEvent) -> Self {
-        match e.pressed_button {
-            Some(b) => match b {
-                gpui::MouseButton::Left => AlacMouseButton::LeftMove,
-                gpui::MouseButton::Middle => AlacMouseButton::MiddleMove,
-                gpui::MouseButton::Right => AlacMouseButton::RightMove,
-                gpui::MouseButton::Navigate(_) => AlacMouseButton::Other,
-            },
-            None => AlacMouseButton::NoneMove,
-        }
-    }
-
-    fn from_button(e: MouseButton) -> Self {
-        match e {
-            gpui::MouseButton::Left => AlacMouseButton::LeftButton,
-            gpui::MouseButton::Right => AlacMouseButton::MiddleButton,
-            gpui::MouseButton::Middle => AlacMouseButton::RightButton,
-            gpui::MouseButton::Navigate(_) => AlacMouseButton::Other,
-        }
-    }
-
-    fn from_scroll(e: &ScrollWheelEvent) -> Self {
-        let is_positive = match e.delta {
-            gpui::ScrollDelta::Pixels(pixels) => pixels.y > px(0.),
-            gpui::ScrollDelta::Lines(lines) => lines.y > 0.,
-        };
-
-        if is_positive {
-            AlacMouseButton::ScrollUp
-        } else {
-            AlacMouseButton::ScrollDown
-        }
-    }
-
-    fn is_other(&self) -> bool {
-        match self {
-            AlacMouseButton::Other => true,
-            _ => false,
-        }
-    }
-}
-
-pub fn scroll_report(
-    point: AlacPoint,
-    scroll_lines: i32,
-    e: &ScrollWheelEvent,
-    mode: TermMode,
-) -> Option<impl Iterator<Item = Vec<u8>>> {
-    if mode.intersects(TermMode::MOUSE_MODE) {
-        mouse_report(
-            point,
-            AlacMouseButton::from_scroll(e),
-            true,
-            e.modifiers,
-            MouseFormat::from_mode(mode),
-        )
-        .map(|report| repeat(report).take(max(scroll_lines, 1) as usize))
-    } else {
-        None
-    }
-}
-
-pub fn alt_scroll(scroll_lines: i32) -> Vec<u8> {
-    let cmd = if scroll_lines > 0 { b'A' } else { b'B' };
-
-    let mut content = Vec::with_capacity(scroll_lines.abs() as usize * 3);
-    for _ in 0..scroll_lines.abs() {
-        content.push(0x1b);
-        content.push(b'O');
-        content.push(cmd);
-    }
-    content
-}
-
-pub fn mouse_button_report(
-    point: AlacPoint,
-    button: gpui::MouseButton,
-    modifiers: Modifiers,
-    pressed: bool,
-    mode: TermMode,
-) -> Option<Vec<u8>> {
-    let button = AlacMouseButton::from_button(button);
-    if !button.is_other() && mode.intersects(TermMode::MOUSE_MODE) {
-        mouse_report(
-            point,
-            button,
-            pressed,
-            modifiers,
-            MouseFormat::from_mode(mode),
-        )
-    } else {
-        None
-    }
-}
-
-pub fn mouse_moved_report(point: AlacPoint, e: &MouseMoveEvent, mode: TermMode) -> Option<Vec<u8>> {
-    let button = AlacMouseButton::from_move(e);
-
-    if !button.is_other() && mode.intersects(TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG) {
-        //Only drags are reported in drag mode, so block NoneMove.
-        if mode.contains(TermMode::MOUSE_DRAG) && matches!(button, AlacMouseButton::NoneMove) {
-            None
-        } else {
-            mouse_report(
-                point,
-                button,
-                true,
-                e.modifiers,
-                MouseFormat::from_mode(mode),
-            )
-        }
-    } else {
-        None
-    }
-}
-
-pub fn mouse_side(
-    pos: Point<Pixels>,
-    cur_size: TerminalSize,
-) -> alacritty_terminal::index::Direction {
-    let cell_width = cur_size.cell_width.floor();
-    if cell_width == px(0.) {
-        return Side::Right;
-    }
-
-    let x = pos.x.floor();
-
-    let cell_x = cmp::max(px(0.), x - cell_width) % cell_width;
-    let half_cell_width = (cur_size.cell_width / 2.0).floor();
-    let additional_padding = (cur_size.width() - cur_size.cell_width * 2.) % cur_size.cell_width;
-    let end_of_grid = cur_size.width() - cur_size.cell_width - additional_padding;
-
-    //Width: Pixels or columns?
-    if cell_x > half_cell_width
-    // Edge case when mouse leaves the window.
-    || x >= end_of_grid
-    {
-        Side::Right
-    } else {
-        Side::Left
-    }
-}
-
-pub fn grid_point(pos: Point<Pixels>, cur_size: TerminalSize, display_offset: usize) -> AlacPoint {
-    let col = GridCol((pos.x / cur_size.cell_width) as usize);
-    let col = min(col, cur_size.last_column());
-    let line = (pos.y / cur_size.line_height) as i32;
-    let line = min(line, cur_size.bottommost_line().0);
-    AlacPoint::new(GridLine(line - display_offset as i32), col)
-}
-
-///Generate the bytes to send to the terminal, from the cell location, a mouse event, and the terminal mode
-fn mouse_report(
-    point: AlacPoint,
-    button: AlacMouseButton,
-    pressed: bool,
-    modifiers: Modifiers,
-    format: MouseFormat,
-) -> Option<Vec<u8>> {
-    if point.line < 0 {
-        return None;
-    }
-
-    let mut mods = 0;
-    if modifiers.shift {
-        mods += 4;
-    }
-    if modifiers.alt {
-        mods += 8;
-    }
-    if modifiers.control {
-        mods += 16;
-    }
-
-    match format {
-        MouseFormat::SGR => {
-            Some(sgr_mouse_report(point, button as u8 + mods, pressed).into_bytes())
-        }
-        MouseFormat::Normal(utf8) => {
-            if pressed {
-                normal_mouse_report(point, button as u8 + mods, utf8)
-            } else {
-                normal_mouse_report(point, 3 + mods, utf8)
-            }
-        }
-    }
-}
-
-fn normal_mouse_report(point: AlacPoint, button: u8, utf8: bool) -> Option<Vec<u8>> {
-    let AlacPoint { line, column } = point;
-    let max_point = if utf8 { 2015 } else { 223 };
-
-    if line >= max_point || column >= max_point {
-        return None;
-    }
-
-    let mut msg = vec![b'\x1b', b'[', b'M', 32 + button];
-
-    let mouse_pos_encode = |pos: usize| -> Vec<u8> {
-        let pos = 32 + 1 + pos;
-        let first = 0xC0 + pos / 64;
-        let second = 0x80 + (pos & 63);
-        vec![first as u8, second as u8]
-    };
-
-    if utf8 && column >= 95 {
-        msg.append(&mut mouse_pos_encode(column.0));
-    } else {
-        msg.push(32 + 1 + column.0 as u8);
-    }
-
-    if utf8 && line >= 95 {
-        msg.append(&mut mouse_pos_encode(line.0 as usize));
-    } else {
-        msg.push(32 + 1 + line.0 as u8);
-    }
-
-    Some(msg)
-}
-
-fn sgr_mouse_report(point: AlacPoint, button: u8, pressed: bool) -> String {
-    let c = if pressed { 'M' } else { 'm' };
-
-    let msg = format!(
-        "\x1b[<{};{};{}{}",
-        button,
-        point.column + 1,
-        point.line + 1,
-        c
-    );
-
-    msg
-}

crates/terminal2/src/terminal2.rs 🔗

@@ -1,1638 +0,0 @@
-pub mod mappings;
-pub use alacritty_terminal;
-pub mod terminal_settings;
-
-use alacritty_terminal::{
-    ansi::{ClearMode, Handler},
-    config::{Config, Program, PtyConfig, Scrolling},
-    event::{Event as AlacTermEvent, EventListener, Notify, WindowSize},
-    event_loop::{EventLoop, Msg, Notifier},
-    grid::{Dimensions, Scroll as AlacScroll},
-    index::{Boundary, Column, Direction as AlacDirection, Line, Point as AlacPoint},
-    selection::{Selection, SelectionRange, SelectionType},
-    sync::FairMutex,
-    term::{
-        cell::Cell,
-        color::Rgb,
-        search::{Match, RegexIter, RegexSearch},
-        RenderableCursor, TermMode,
-    },
-    tty::{self, setup_env},
-    Term,
-};
-use anyhow::{bail, Result};
-
-use futures::{
-    channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender},
-    FutureExt,
-};
-
-use mappings::mouse::{
-    alt_scroll, grid_point, mouse_button_report, mouse_moved_report, mouse_side, scroll_report,
-};
-
-use procinfo::LocalProcessInfo;
-use serde::{Deserialize, Serialize};
-use settings::Settings;
-use terminal_settings::{AlternateScroll, Shell, TerminalBlink, TerminalSettings};
-use theme::{ActiveTheme, Theme};
-use util::truncate_and_trailoff;
-
-use std::{
-    cmp::{self, min},
-    collections::{HashMap, VecDeque},
-    fmt::Display,
-    ops::{Deref, Index, RangeInclusive},
-    os::unix::prelude::AsRawFd,
-    path::PathBuf,
-    sync::Arc,
-    time::{Duration, Instant},
-};
-use thiserror::Error;
-
-use gpui::{
-    actions, black, px, red, AnyWindowHandle, AppContext, Bounds, ClipboardItem, EventEmitter,
-    Hsla, Keystroke, ModelContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent,
-    MouseUpEvent, Pixels, Point, Rgba, ScrollWheelEvent, Size, Task, TouchPhase,
-};
-
-use crate::mappings::{colors::to_alac_rgb, keys::to_esc_str};
-use lazy_static::lazy_static;
-
-actions!(
-    terminal,
-    [
-        Clear,
-        Copy,
-        Paste,
-        ShowCharacterPalette,
-        SearchTest,
-        SendText,
-        SendKeystroke,
-    ]
-);
-
-///Scrolling is unbearably sluggish by default. Alacritty supports a configurable
-///Scroll multiplier that is set to 3 by default. This will be removed when I
-///Implement scroll bars.
-const SCROLL_MULTIPLIER: f32 = 4.;
-const MAX_SEARCH_LINES: usize = 100;
-const DEBUG_TERMINAL_WIDTH: Pixels = px(500.);
-const DEBUG_TERMINAL_HEIGHT: Pixels = px(30.);
-const DEBUG_CELL_WIDTH: Pixels = px(5.);
-const DEBUG_LINE_HEIGHT: Pixels = px(5.);
-
-lazy_static! {
-    // Regex Copied from alacritty's ui_config.rs and modified its declaration slightly:
-    // * avoid Rust-specific escaping.
-    // * use more strict regex for `file://` protocol matching: original regex has `file:` inside, but we want to avoid matching `some::file::module` strings.
-    static ref URL_REGEX: RegexSearch = RegexSearch::new(r#"(ipfs:|ipns:|magnet:|mailto:|gemini://|gopher://|https://|http://|news:|file://|git://|ssh:|ftp://)[^\u{0000}-\u{001F}\u{007F}-\u{009F}<>"\s{-}\^⟨⟩`]+"#).unwrap();
-
-    static ref WORD_REGEX: RegexSearch = RegexSearch::new(r#"[\w.\[\]:/@\-~]+"#).unwrap();
-}
-
-///Upward flowing events, for changing the title and such
-#[derive(Clone, Debug)]
-pub enum Event {
-    TitleChanged,
-    BreadcrumbsChanged,
-    CloseTerminal,
-    Bell,
-    Wakeup,
-    BlinkChanged,
-    SelectionsChanged,
-    NewNavigationTarget(Option<MaybeNavigationTarget>),
-    Open(MaybeNavigationTarget),
-}
-
-/// A string inside terminal, potentially useful as a URI that can be opened.
-#[derive(Clone, Debug)]
-pub enum MaybeNavigationTarget {
-    /// HTTP, git, etc. string determined by the [`URL_REGEX`] regex.
-    Url(String),
-    /// File system path, absolute or relative, existing or not.
-    /// Might have line and column number(s) attached as `file.rs:1:23`
-    PathLike(String),
-}
-
-#[derive(Clone)]
-enum InternalEvent {
-    ColorRequest(usize, Arc<dyn Fn(Rgb) -> String + Sync + Send + 'static>),
-    Resize(TerminalSize),
-    Clear,
-    // FocusNextMatch,
-    Scroll(AlacScroll),
-    ScrollToAlacPoint(AlacPoint),
-    SetSelection(Option<(Selection, AlacPoint)>),
-    UpdateSelection(Point<Pixels>),
-    // Adjusted mouse position, should open
-    FindHyperlink(Point<Pixels>, bool),
-    Copy,
-}
-
-///A translation struct for Alacritty to communicate with us from their event loop
-#[derive(Clone)]
-pub struct ZedListener(UnboundedSender<AlacTermEvent>);
-
-impl EventListener for ZedListener {
-    fn send_event(&self, event: AlacTermEvent) {
-        self.0.unbounded_send(event).ok();
-    }
-}
-
-pub fn init(cx: &mut AppContext) {
-    TerminalSettings::register(cx);
-}
-
-#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
-pub struct TerminalSize {
-    pub cell_width: Pixels,
-    pub line_height: Pixels,
-    pub size: Size<Pixels>,
-}
-
-impl TerminalSize {
-    pub fn new(line_height: Pixels, cell_width: Pixels, size: Size<Pixels>) -> Self {
-        TerminalSize {
-            cell_width,
-            line_height,
-            size,
-        }
-    }
-
-    pub fn num_lines(&self) -> usize {
-        f32::from((self.size.height / self.line_height).floor()) as usize
-    }
-
-    pub fn num_columns(&self) -> usize {
-        f32::from((self.size.width / self.cell_width).floor()) as usize
-    }
-
-    pub fn height(&self) -> Pixels {
-        self.size.height
-    }
-
-    pub fn width(&self) -> Pixels {
-        self.size.width
-    }
-
-    pub fn cell_width(&self) -> Pixels {
-        self.cell_width
-    }
-
-    pub fn line_height(&self) -> Pixels {
-        self.line_height
-    }
-}
-impl Default for TerminalSize {
-    fn default() -> Self {
-        TerminalSize::new(
-            DEBUG_LINE_HEIGHT,
-            DEBUG_CELL_WIDTH,
-            Size {
-                width: DEBUG_TERMINAL_WIDTH,
-                height: DEBUG_TERMINAL_HEIGHT,
-            },
-        )
-    }
-}
-
-impl From<TerminalSize> for WindowSize {
-    fn from(val: TerminalSize) -> Self {
-        WindowSize {
-            num_lines: val.num_lines() as u16,
-            num_cols: val.num_columns() as u16,
-            cell_width: f32::from(val.cell_width()) as u16,
-            cell_height: f32::from(val.line_height()) as u16,
-        }
-    }
-}
-
-impl Dimensions for TerminalSize {
-    /// Note: this is supposed to be for the back buffer's length,
-    /// but we exclusively use it to resize the terminal, which does not
-    /// use this method. We still have to implement it for the trait though,
-    /// hence, this comment.
-    fn total_lines(&self) -> usize {
-        self.screen_lines()
-    }
-
-    fn screen_lines(&self) -> usize {
-        self.num_lines()
-    }
-
-    fn columns(&self) -> usize {
-        self.num_columns()
-    }
-}
-
-#[derive(Error, Debug)]
-pub struct TerminalError {
-    pub directory: Option<PathBuf>,
-    pub shell: Shell,
-    pub source: std::io::Error,
-}
-
-impl TerminalError {
-    pub fn fmt_directory(&self) -> String {
-        self.directory
-            .clone()
-            .map(|path| {
-                match path
-                    .into_os_string()
-                    .into_string()
-                    .map_err(|os_str| format!("<non-utf8 path> {}", os_str.to_string_lossy()))
-                {
-                    Ok(s) => s,
-                    Err(s) => s,
-                }
-            })
-            .unwrap_or_else(|| {
-                let default_dir =
-                    dirs::home_dir().map(|buf| buf.into_os_string().to_string_lossy().to_string());
-                match default_dir {
-                    Some(dir) => format!("<none specified, using home directory> {}", dir),
-                    None => "<none specified, could not find home directory>".to_string(),
-                }
-            })
-    }
-
-    pub fn shell_to_string(&self) -> String {
-        match &self.shell {
-            Shell::System => "<system shell>".to_string(),
-            Shell::Program(p) => p.to_string(),
-            Shell::WithArguments { program, args } => format!("{} {}", program, args.join(" ")),
-        }
-    }
-
-    pub fn fmt_shell(&self) -> String {
-        match &self.shell {
-            Shell::System => "<system defined shell>".to_string(),
-            Shell::Program(s) => s.to_string(),
-            Shell::WithArguments { program, args } => format!("{} {}", program, args.join(" ")),
-        }
-    }
-}
-
-impl Display for TerminalError {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        let dir_string: String = self.fmt_directory();
-        let shell = self.fmt_shell();
-
-        write!(
-            f,
-            "Working directory: {} Shell command: `{}`, IOError: {}",
-            dir_string, shell, self.source
-        )
-    }
-}
-
-pub struct TerminalBuilder {
-    terminal: Terminal,
-    events_rx: UnboundedReceiver<AlacTermEvent>,
-}
-
-impl TerminalBuilder {
-    pub fn new(
-        working_directory: Option<PathBuf>,
-        shell: Shell,
-        mut env: HashMap<String, String>,
-        blink_settings: Option<TerminalBlink>,
-        alternate_scroll: AlternateScroll,
-        window: AnyWindowHandle,
-    ) -> Result<TerminalBuilder> {
-        let pty_config = {
-            let alac_shell = match shell.clone() {
-                Shell::System => None,
-                Shell::Program(program) => Some(Program::Just(program)),
-                Shell::WithArguments { program, args } => Some(Program::WithArgs { program, args }),
-            };
-
-            PtyConfig {
-                shell: alac_shell,
-                working_directory: working_directory.clone(),
-                hold: false,
-            }
-        };
-
-        //TODO: Properly set the current locale,
-        env.insert("LC_ALL".to_string(), "en_US.UTF-8".to_string());
-        env.insert("ZED_TERM".to_string(), true.to_string());
-
-        let alac_scrolling = Scrolling::default();
-        // alac_scrolling.set_history((BACK_BUFFER_SIZE * 2) as u32);
-
-        let config = Config {
-            pty_config: pty_config.clone(),
-            env,
-            scrolling: alac_scrolling,
-            ..Default::default()
-        };
-
-        setup_env(&config);
-
-        //Spawn a task so the Alacritty EventLoop can communicate with us in a view context
-        //TODO: Remove with a bounded sender which can be dispatched on &self
-        let (events_tx, events_rx) = unbounded();
-        //Set up the terminal...
-        let mut term = Term::new(
-            &config,
-            &TerminalSize::default(),
-            ZedListener(events_tx.clone()),
-        );
-
-        //Start off blinking if we need to
-        if let Some(TerminalBlink::On) = blink_settings {
-            term.set_mode(alacritty_terminal::ansi::Mode::BlinkingCursor)
-        }
-
-        //Alacritty defaults to alternate scrolling being on, so we just need to turn it off.
-        if let AlternateScroll::Off = alternate_scroll {
-            term.unset_mode(alacritty_terminal::ansi::Mode::AlternateScroll)
-        }
-
-        let term = Arc::new(FairMutex::new(term));
-
-        //Setup the pty...
-        let pty = match tty::new(
-            &pty_config,
-            TerminalSize::default().into(),
-            window.window_id().as_u64(),
-        ) {
-            Ok(pty) => pty,
-            Err(error) => {
-                bail!(TerminalError {
-                    directory: working_directory,
-                    shell,
-                    source: error,
-                });
-            }
-        };
-
-        let fd = pty.file().as_raw_fd();
-        let shell_pid = pty.child().id();
-
-        //And connect them together
-        let event_loop = EventLoop::new(
-            term.clone(),
-            ZedListener(events_tx.clone()),
-            pty,
-            pty_config.hold,
-            false,
-        );
-
-        //Kick things off
-        let pty_tx = event_loop.channel();
-        let _io_thread = event_loop.spawn();
-
-        let terminal = Terminal {
-            pty_tx: Notifier(pty_tx),
-            term,
-            events: VecDeque::with_capacity(10), //Should never get this high.
-            last_content: Default::default(),
-            last_mouse: None,
-            matches: Vec::new(),
-            last_synced: Instant::now(),
-            sync_task: None,
-            selection_head: None,
-            shell_fd: fd as u32,
-            shell_pid,
-            foreground_process_info: None,
-            breadcrumb_text: String::new(),
-            scroll_px: px(0.),
-            last_mouse_position: None,
-            next_link_id: 0,
-            selection_phase: SelectionPhase::Ended,
-            cmd_pressed: false,
-            hovered_word: false,
-        };
-
-        Ok(TerminalBuilder {
-            terminal,
-            events_rx,
-        })
-    }
-
-    pub fn subscribe(mut self, cx: &mut ModelContext<Terminal>) -> Terminal {
-        //Event loop
-        cx.spawn(|this, mut cx| async move {
-            use futures::StreamExt;
-
-            while let Some(event) = self.events_rx.next().await {
-                this.update(&mut cx, |this, cx| {
-                    //Process the first event immediately for lowered latency
-                    this.process_event(&event, cx);
-                })?;
-
-                'outer: loop {
-                    let mut events = vec![];
-                    let mut timer = cx
-                        .background_executor()
-                        .timer(Duration::from_millis(4))
-                        .fuse();
-                    let mut wakeup = false;
-                    loop {
-                        futures::select_biased! {
-                            _ = timer => break,
-                            event = self.events_rx.next() => {
-                                if let Some(event) = event {
-                                    if matches!(event, AlacTermEvent::Wakeup) {
-                                        wakeup = true;
-                                    } else {
-                                        events.push(event);
-                                    }
-
-                                    if events.len() > 100 {
-                                        break;
-                                    }
-                                } else {
-                                    break;
-                                }
-                            },
-                        }
-                    }
-
-                    if events.is_empty() && wakeup == false {
-                        smol::future::yield_now().await;
-                        break 'outer;
-                    } else {
-                        this.update(&mut cx, |this, cx| {
-                            if wakeup {
-                                this.process_event(&AlacTermEvent::Wakeup, cx);
-                            }
-
-                            for event in events {
-                                this.process_event(&event, cx);
-                            }
-                        })?;
-                        smol::future::yield_now().await;
-                    }
-                }
-            }
-
-            anyhow::Ok(())
-        })
-        .detach();
-
-        self.terminal
-    }
-}
-
-#[derive(Debug, Clone, Deserialize, Serialize)]
-pub struct IndexedCell {
-    pub point: AlacPoint,
-    pub cell: Cell,
-}
-
-impl Deref for IndexedCell {
-    type Target = Cell;
-
-    #[inline]
-    fn deref(&self) -> &Cell {
-        &self.cell
-    }
-}
-
-// TODO: Un-pub
-#[derive(Clone)]
-pub struct TerminalContent {
-    pub cells: Vec<IndexedCell>,
-    pub mode: TermMode,
-    pub display_offset: usize,
-    pub selection_text: Option<String>,
-    pub selection: Option<SelectionRange>,
-    pub cursor: RenderableCursor,
-    pub cursor_char: char,
-    pub size: TerminalSize,
-    pub last_hovered_word: Option<HoveredWord>,
-}
-
-#[derive(Clone)]
-pub struct HoveredWord {
-    pub word: String,
-    pub word_match: RangeInclusive<AlacPoint>,
-    pub id: usize,
-}
-
-impl Default for TerminalContent {
-    fn default() -> Self {
-        TerminalContent {
-            cells: Default::default(),
-            mode: Default::default(),
-            display_offset: Default::default(),
-            selection_text: Default::default(),
-            selection: Default::default(),
-            cursor: RenderableCursor {
-                shape: alacritty_terminal::ansi::CursorShape::Block,
-                point: AlacPoint::new(Line(0), Column(0)),
-            },
-            cursor_char: Default::default(),
-            size: Default::default(),
-            last_hovered_word: None,
-        }
-    }
-}
-
-#[derive(PartialEq, Eq)]
-pub enum SelectionPhase {
-    Selecting,
-    Ended,
-}
-
-pub struct Terminal {
-    pty_tx: Notifier,
-    term: Arc<FairMutex<Term<ZedListener>>>,
-    events: VecDeque<InternalEvent>,
-    /// This is only used for mouse mode cell change detection
-    last_mouse: Option<(AlacPoint, AlacDirection)>,
-    /// This is only used for terminal hovered word checking
-    last_mouse_position: Option<Point<Pixels>>,
-    pub matches: Vec<RangeInclusive<AlacPoint>>,
-    pub last_content: TerminalContent,
-    last_synced: Instant,
-    sync_task: Option<Task<()>>,
-    pub selection_head: Option<AlacPoint>,
-    pub breadcrumb_text: String,
-    shell_pid: u32,
-    shell_fd: u32,
-    pub foreground_process_info: Option<LocalProcessInfo>,
-    scroll_px: Pixels,
-    next_link_id: usize,
-    selection_phase: SelectionPhase,
-    cmd_pressed: bool,
-    hovered_word: bool,
-}
-
-impl Terminal {
-    fn process_event(&mut self, event: &AlacTermEvent, cx: &mut ModelContext<Self>) {
-        match event {
-            AlacTermEvent::Title(title) => {
-                self.breadcrumb_text = title.to_string();
-                cx.emit(Event::BreadcrumbsChanged);
-            }
-            AlacTermEvent::ResetTitle => {
-                self.breadcrumb_text = String::new();
-                cx.emit(Event::BreadcrumbsChanged);
-            }
-            AlacTermEvent::ClipboardStore(_, data) => {
-                cx.write_to_clipboard(ClipboardItem::new(data.to_string()))
-            }
-            AlacTermEvent::ClipboardLoad(_, format) => self.write_to_pty(format(
-                &cx.read_from_clipboard()
-                    .map(|ci| ci.text().to_string())
-                    .unwrap_or_else(|| "".to_string()),
-            )),
-            AlacTermEvent::PtyWrite(out) => self.write_to_pty(out.clone()),
-            AlacTermEvent::TextAreaSizeRequest(format) => {
-                self.write_to_pty(format(self.last_content.size.into()))
-            }
-            AlacTermEvent::CursorBlinkingChange => {
-                cx.emit(Event::BlinkChanged);
-            }
-            AlacTermEvent::Bell => {
-                cx.emit(Event::Bell);
-            }
-            AlacTermEvent::Exit => cx.emit(Event::CloseTerminal),
-            AlacTermEvent::MouseCursorDirty => {
-                //NOOP, Handled in render
-            }
-            AlacTermEvent::Wakeup => {
-                cx.emit(Event::Wakeup);
-
-                if self.update_process_info() {
-                    cx.emit(Event::TitleChanged);
-                }
-            }
-            AlacTermEvent::ColorRequest(idx, fun_ptr) => {
-                self.events
-                    .push_back(InternalEvent::ColorRequest(*idx, fun_ptr.clone()));
-            }
-        }
-    }
-
-    /// Update the cached process info, returns whether the Zed-relevant info has changed
-    fn update_process_info(&mut self) -> bool {
-        let mut pid = unsafe { libc::tcgetpgrp(self.shell_fd as i32) };
-        if pid < 0 {
-            pid = self.shell_pid as i32;
-        }
-
-        if let Some(process_info) = LocalProcessInfo::with_root_pid(pid as u32) {
-            let res = self
-                .foreground_process_info
-                .as_ref()
-                .map(|old_info| {
-                    process_info.cwd != old_info.cwd || process_info.name != old_info.name
-                })
-                .unwrap_or(true);
-
-            self.foreground_process_info = Some(process_info.clone());
-
-            res
-        } else {
-            false
-        }
-    }
-
-    ///Takes events from Alacritty and translates them to behavior on this view
-    fn process_terminal_event(
-        &mut self,
-        event: &InternalEvent,
-        term: &mut Term<ZedListener>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        match event {
-            InternalEvent::ColorRequest(index, format) => {
-                let color = term.colors()[*index].unwrap_or_else(|| {
-                    to_alac_rgb(get_color_at_index(*index, cx.theme().as_ref()))
-                });
-                self.write_to_pty(format(color))
-            }
-            InternalEvent::Resize(mut new_size) => {
-                new_size.size.height = cmp::max(new_size.line_height, new_size.height());
-                new_size.size.width = cmp::max(new_size.cell_width, new_size.width());
-
-                self.last_content.size = new_size.clone();
-
-                self.pty_tx.0.send(Msg::Resize(new_size.into())).ok();
-
-                term.resize(new_size);
-            }
-            InternalEvent::Clear => {
-                // Clear back buffer
-                term.clear_screen(ClearMode::Saved);
-
-                let cursor = term.grid().cursor.point;
-
-                // Clear the lines above
-                term.grid_mut().reset_region(..cursor.line);
-
-                // Copy the current line up
-                let line = term.grid()[cursor.line][..Column(term.grid().columns())]
-                    .iter()
-                    .cloned()
-                    .enumerate()
-                    .collect::<Vec<(usize, Cell)>>();
-
-                for (i, cell) in line {
-                    term.grid_mut()[Line(0)][Column(i)] = cell;
-                }
-
-                // Reset the cursor
-                term.grid_mut().cursor.point =
-                    AlacPoint::new(Line(0), term.grid_mut().cursor.point.column);
-                let new_cursor = term.grid().cursor.point;
-
-                // Clear the lines below the new cursor
-                if (new_cursor.line.0 as usize) < term.screen_lines() - 1 {
-                    term.grid_mut().reset_region((new_cursor.line + 1)..);
-                }
-
-                cx.emit(Event::Wakeup);
-            }
-            InternalEvent::Scroll(scroll) => {
-                term.scroll_display(*scroll);
-                self.refresh_hovered_word();
-            }
-            InternalEvent::SetSelection(selection) => {
-                term.selection = selection.as_ref().map(|(sel, _)| sel.clone());
-
-                if let Some((_, head)) = selection {
-                    self.selection_head = Some(*head);
-                }
-                cx.emit(Event::SelectionsChanged)
-            }
-            InternalEvent::UpdateSelection(position) => {
-                if let Some(mut selection) = term.selection.take() {
-                    let point = grid_point(
-                        *position,
-                        self.last_content.size,
-                        term.grid().display_offset(),
-                    );
-
-                    let side = mouse_side(*position, self.last_content.size);
-
-                    selection.update(point, side);
-                    term.selection = Some(selection);
-
-                    self.selection_head = Some(point);
-                    cx.emit(Event::SelectionsChanged)
-                }
-            }
-
-            InternalEvent::Copy => {
-                if let Some(txt) = term.selection_to_string() {
-                    cx.write_to_clipboard(ClipboardItem::new(txt))
-                }
-            }
-            InternalEvent::ScrollToAlacPoint(point) => {
-                term.scroll_to_point(*point);
-                self.refresh_hovered_word();
-            }
-            InternalEvent::FindHyperlink(position, open) => {
-                let prev_hovered_word = self.last_content.last_hovered_word.take();
-
-                let point = grid_point(
-                    *position,
-                    self.last_content.size,
-                    term.grid().display_offset(),
-                )
-                .grid_clamp(term, Boundary::Grid);
-
-                let link = term.grid().index(point).hyperlink();
-                let found_word = if link.is_some() {
-                    let mut min_index = point;
-                    loop {
-                        let new_min_index = min_index.sub(term, Boundary::Cursor, 1);
-                        if new_min_index == min_index {
-                            break;
-                        } else if term.grid().index(new_min_index).hyperlink() != link {
-                            break;
-                        } else {
-                            min_index = new_min_index
-                        }
-                    }
-
-                    let mut max_index = point;
-                    loop {
-                        let new_max_index = max_index.add(term, Boundary::Cursor, 1);
-                        if new_max_index == max_index {
-                            break;
-                        } else if term.grid().index(new_max_index).hyperlink() != link {
-                            break;
-                        } else {
-                            max_index = new_max_index
-                        }
-                    }
-
-                    let url = link.unwrap().uri().to_owned();
-                    let url_match = min_index..=max_index;
-
-                    Some((url, true, url_match))
-                } else if let Some(word_match) = regex_match_at(term, point, &WORD_REGEX) {
-                    let maybe_url_or_path =
-                        term.bounds_to_string(*word_match.start(), *word_match.end());
-                    let original_match = word_match.clone();
-                    let (sanitized_match, sanitized_word) =
-                        if maybe_url_or_path.starts_with('[') && maybe_url_or_path.ends_with(']') {
-                            (
-                                Match::new(
-                                    word_match.start().add(term, Boundary::Cursor, 1),
-                                    word_match.end().sub(term, Boundary::Cursor, 1),
-                                ),
-                                maybe_url_or_path[1..maybe_url_or_path.len() - 1].to_owned(),
-                            )
-                        } else {
-                            (word_match, maybe_url_or_path)
-                        };
-
-                    let is_url = match regex_match_at(term, point, &URL_REGEX) {
-                        Some(url_match) => {
-                            // `]` is a valid symbol in the `file://` URL, so the regex match will include it
-                            // consider that when ensuring that the URL match is the same as the original word
-                            if sanitized_match != original_match {
-                                url_match.start() == sanitized_match.start()
-                                    && url_match.end() == original_match.end()
-                            } else {
-                                url_match == sanitized_match
-                            }
-                        }
-                        None => false,
-                    };
-                    Some((sanitized_word, is_url, sanitized_match))
-                } else {
-                    None
-                };
-
-                match found_word {
-                    Some((maybe_url_or_path, is_url, url_match)) => {
-                        if *open {
-                            let target = if is_url {
-                                MaybeNavigationTarget::Url(maybe_url_or_path)
-                            } else {
-                                MaybeNavigationTarget::PathLike(maybe_url_or_path)
-                            };
-                            cx.emit(Event::Open(target));
-                        } else {
-                            self.update_selected_word(
-                                prev_hovered_word,
-                                url_match,
-                                maybe_url_or_path,
-                                is_url,
-                                cx,
-                            );
-                        }
-                        self.hovered_word = true;
-                    }
-                    None => {
-                        if self.hovered_word {
-                            cx.emit(Event::NewNavigationTarget(None));
-                        }
-                        self.hovered_word = false;
-                    }
-                }
-            }
-        }
-    }
-
-    fn update_selected_word(
-        &mut self,
-        prev_word: Option<HoveredWord>,
-        word_match: RangeInclusive<AlacPoint>,
-        word: String,
-        is_url: bool,
-        cx: &mut ModelContext<Self>,
-    ) {
-        if let Some(prev_word) = prev_word {
-            if prev_word.word == word && prev_word.word_match == word_match {
-                self.last_content.last_hovered_word = Some(HoveredWord {
-                    word,
-                    word_match,
-                    id: prev_word.id,
-                });
-                return;
-            }
-        }
-
-        self.last_content.last_hovered_word = Some(HoveredWord {
-            word: word.clone(),
-            word_match,
-            id: self.next_link_id(),
-        });
-        let navigation_target = if is_url {
-            MaybeNavigationTarget::Url(word)
-        } else {
-            MaybeNavigationTarget::PathLike(word)
-        };
-        cx.emit(Event::NewNavigationTarget(Some(navigation_target)));
-    }
-
-    fn next_link_id(&mut self) -> usize {
-        let res = self.next_link_id;
-        self.next_link_id = self.next_link_id.wrapping_add(1);
-        res
-    }
-
-    pub fn last_content(&self) -> &TerminalContent {
-        &self.last_content
-    }
-
-    //To test:
-    //- Activate match on terminal (scrolling and selection)
-    //- Editor search snapping behavior
-
-    pub fn activate_match(&mut self, index: usize) {
-        if let Some(search_match) = self.matches.get(index).cloned() {
-            self.set_selection(Some((make_selection(&search_match), *search_match.end())));
-
-            self.events
-                .push_back(InternalEvent::ScrollToAlacPoint(*search_match.start()));
-        }
-    }
-
-    pub fn select_matches(&mut self, matches: Vec<RangeInclusive<AlacPoint>>) {
-        let matches_to_select = self
-            .matches
-            .iter()
-            .filter(|self_match| matches.contains(self_match))
-            .cloned()
-            .collect::<Vec<_>>();
-        for match_to_select in matches_to_select {
-            self.set_selection(Some((
-                make_selection(&match_to_select),
-                *match_to_select.end(),
-            )));
-        }
-    }
-
-    pub fn select_all(&mut self) {
-        let term = self.term.lock();
-        let start = AlacPoint::new(term.topmost_line(), Column(0));
-        let end = AlacPoint::new(term.bottommost_line(), term.last_column());
-        drop(term);
-        self.set_selection(Some((make_selection(&(start..=end)), end)));
-    }
-
-    fn set_selection(&mut self, selection: Option<(Selection, AlacPoint)>) {
-        self.events
-            .push_back(InternalEvent::SetSelection(selection));
-    }
-
-    pub fn copy(&mut self) {
-        self.events.push_back(InternalEvent::Copy);
-    }
-
-    pub fn clear(&mut self) {
-        self.events.push_back(InternalEvent::Clear)
-    }
-
-    ///Resize the terminal and the PTY.
-    pub fn set_size(&mut self, new_size: TerminalSize) {
-        self.events.push_back(InternalEvent::Resize(new_size))
-    }
-
-    ///Write the Input payload to the tty.
-    fn write_to_pty(&self, input: String) {
-        self.pty_tx.notify(input.into_bytes());
-    }
-
-    fn write_bytes_to_pty(&self, input: Vec<u8>) {
-        self.pty_tx.notify(input);
-    }
-
-    pub fn input(&mut self, input: String) {
-        self.events
-            .push_back(InternalEvent::Scroll(AlacScroll::Bottom));
-        self.events.push_back(InternalEvent::SetSelection(None));
-
-        self.write_to_pty(input);
-    }
-
-    pub fn input_bytes(&mut self, input: Vec<u8>) {
-        self.events
-            .push_back(InternalEvent::Scroll(AlacScroll::Bottom));
-        self.events.push_back(InternalEvent::SetSelection(None));
-
-        self.write_bytes_to_pty(input);
-    }
-
-    pub fn try_keystroke(&mut self, keystroke: &Keystroke, alt_is_meta: bool) -> bool {
-        let esc = to_esc_str(keystroke, &self.last_content.mode, alt_is_meta);
-        if let Some(esc) = esc {
-            self.input(esc);
-            true
-        } else {
-            false
-        }
-    }
-
-    pub fn try_modifiers_change(&mut self, modifiers: &Modifiers) -> bool {
-        let changed = self.cmd_pressed != modifiers.command;
-        if !self.cmd_pressed && modifiers.command {
-            self.refresh_hovered_word();
-        }
-        self.cmd_pressed = modifiers.command;
-        changed
-    }
-
-    ///Paste text into the terminal
-    pub fn paste(&mut self, text: &str) {
-        let paste_text = if self.last_content.mode.contains(TermMode::BRACKETED_PASTE) {
-            format!("{}{}{}", "\x1b[200~", text.replace('\x1b', ""), "\x1b[201~")
-        } else {
-            text.replace("\r\n", "\r").replace('\n', "\r")
-        };
-
-        self.input(paste_text);
-    }
-
-    pub fn try_sync(&mut self, cx: &mut ModelContext<Self>) {
-        let term = self.term.clone();
-
-        let mut terminal = if let Some(term) = term.try_lock_unfair() {
-            term
-        } else if self.last_synced.elapsed().as_secs_f32() > 0.25 {
-            term.lock_unfair() //It's been too long, force block
-        } else if let None = self.sync_task {
-            //Skip this frame
-            let delay = cx.background_executor().timer(Duration::from_millis(16));
-            self.sync_task = Some(cx.spawn(|weak_handle, mut cx| async move {
-                delay.await;
-                if let Some(handle) = weak_handle.upgrade() {
-                    handle
-                        .update(&mut cx, |terminal, cx| {
-                            terminal.sync_task.take();
-                            cx.notify();
-                        })
-                        .ok();
-                }
-            }));
-            return;
-        } else {
-            //No lock and delayed rendering already scheduled, nothing to do
-            return;
-        };
-
-        //Note that the ordering of events matters for event processing
-        while let Some(e) = self.events.pop_front() {
-            self.process_terminal_event(&e, &mut terminal, cx)
-        }
-
-        self.last_content = Self::make_content(&terminal, &self.last_content);
-        self.last_synced = Instant::now();
-    }
-
-    fn make_content(term: &Term<ZedListener>, last_content: &TerminalContent) -> TerminalContent {
-        let content = term.renderable_content();
-        TerminalContent {
-            cells: content
-                .display_iter
-                //TODO: Add this once there's a way to retain empty lines
-                // .filter(|ic| {
-                //     !ic.flags.contains(Flags::HIDDEN)
-                //         && !(ic.bg == Named(NamedColor::Background)
-                //             && ic.c == ' '
-                //             && !ic.flags.contains(Flags::INVERSE))
-                // })
-                .map(|ic| IndexedCell {
-                    point: ic.point,
-                    cell: ic.cell.clone(),
-                })
-                .collect::<Vec<IndexedCell>>(),
-            mode: content.mode,
-            display_offset: content.display_offset,
-            selection_text: term.selection_to_string(),
-            selection: content.selection,
-            cursor: content.cursor,
-            cursor_char: term.grid()[content.cursor.point].c,
-            size: last_content.size,
-            last_hovered_word: last_content.last_hovered_word.clone(),
-        }
-    }
-
-    pub fn focus_in(&self) {
-        if self.last_content.mode.contains(TermMode::FOCUS_IN_OUT) {
-            self.write_to_pty("\x1b[I".to_string());
-        }
-    }
-
-    pub fn focus_out(&mut self) {
-        self.last_mouse_position = None;
-        if self.last_content.mode.contains(TermMode::FOCUS_IN_OUT) {
-            self.write_to_pty("\x1b[O".to_string());
-        }
-    }
-
-    pub fn mouse_changed(&mut self, point: AlacPoint, side: AlacDirection) -> bool {
-        match self.last_mouse {
-            Some((old_point, old_side)) => {
-                if old_point == point && old_side == side {
-                    false
-                } else {
-                    self.last_mouse = Some((point, side));
-                    true
-                }
-            }
-            None => {
-                self.last_mouse = Some((point, side));
-                true
-            }
-        }
-    }
-
-    pub fn mouse_mode(&self, shift: bool) -> bool {
-        self.last_content.mode.intersects(TermMode::MOUSE_MODE) && !shift
-    }
-
-    pub fn mouse_move(&mut self, e: &MouseMoveEvent, origin: Point<Pixels>) {
-        let position = e.position - origin;
-        self.last_mouse_position = Some(position);
-        if self.mouse_mode(e.modifiers.shift) {
-            let point = grid_point(
-                position,
-                self.last_content.size,
-                self.last_content.display_offset,
-            );
-            let side = mouse_side(position, self.last_content.size);
-
-            if self.mouse_changed(point, side) {
-                if let Some(bytes) = mouse_moved_report(point, e, self.last_content.mode) {
-                    self.pty_tx.notify(bytes);
-                }
-            }
-        } else if self.cmd_pressed {
-            self.word_from_position(Some(position));
-        }
-    }
-
-    fn word_from_position(&mut self, position: Option<Point<Pixels>>) {
-        if self.selection_phase == SelectionPhase::Selecting {
-            self.last_content.last_hovered_word = None;
-        } else if let Some(position) = position {
-            self.events
-                .push_back(InternalEvent::FindHyperlink(position, false));
-        }
-    }
-
-    pub fn mouse_drag(
-        &mut self,
-        e: &MouseMoveEvent,
-        origin: Point<Pixels>,
-        region: Bounds<Pixels>,
-    ) {
-        let position = e.position - origin;
-        self.last_mouse_position = Some(position);
-
-        if !self.mouse_mode(e.modifiers.shift) {
-            self.selection_phase = SelectionPhase::Selecting;
-            // Alacritty has the same ordering, of first updating the selection
-            // then scrolling 15ms later
-            self.events
-                .push_back(InternalEvent::UpdateSelection(position));
-
-            // Doesn't make sense to scroll the alt screen
-            if !self.last_content.mode.contains(TermMode::ALT_SCREEN) {
-                let scroll_delta = match self.drag_line_delta(e, region) {
-                    Some(value) => value,
-                    None => return,
-                };
-
-                let scroll_lines = (scroll_delta / self.last_content.size.line_height) as i32;
-
-                self.events
-                    .push_back(InternalEvent::Scroll(AlacScroll::Delta(scroll_lines)));
-            }
-        }
-    }
-
-    fn drag_line_delta(&mut self, e: &MouseMoveEvent, region: Bounds<Pixels>) -> Option<Pixels> {
-        //TODO: Why do these need to be doubled? Probably the same problem that the IME has
-        let top = region.origin.y + (self.last_content.size.line_height * 2.);
-        let bottom = region.lower_left().y - (self.last_content.size.line_height * 2.);
-        let scroll_delta = if e.position.y < top {
-            (top - e.position.y).pow(1.1)
-        } else if e.position.y > bottom {
-            -((e.position.y - bottom).pow(1.1))
-        } else {
-            return None; //Nothing to do
-        };
-        Some(scroll_delta)
-    }
-
-    pub fn mouse_down(&mut self, e: &MouseDownEvent, origin: Point<Pixels>) {
-        let position = e.position - origin;
-        let point = grid_point(
-            position,
-            self.last_content.size,
-            self.last_content.display_offset,
-        );
-
-        if self.mouse_mode(e.modifiers.shift) {
-            if let Some(bytes) =
-                mouse_button_report(point, e.button, e.modifiers, true, self.last_content.mode)
-            {
-                self.pty_tx.notify(bytes);
-            }
-        } else if e.button == MouseButton::Left {
-            let position = e.position - origin;
-            let point = grid_point(
-                position,
-                self.last_content.size,
-                self.last_content.display_offset,
-            );
-
-            // Use .opposite so that selection is inclusive of the cell clicked.
-            let side = mouse_side(position, self.last_content.size);
-
-            let selection_type = match e.click_count {
-                0 => return, //This is a release
-                1 => Some(SelectionType::Simple),
-                2 => Some(SelectionType::Semantic),
-                3 => Some(SelectionType::Lines),
-                _ => None,
-            };
-
-            let selection =
-                selection_type.map(|selection_type| Selection::new(selection_type, point, side));
-
-            if let Some(sel) = selection {
-                self.events
-                    .push_back(InternalEvent::SetSelection(Some((sel, point))));
-            }
-        }
-    }
-
-    pub fn mouse_up(
-        &mut self,
-        e: &MouseUpEvent,
-        origin: Point<Pixels>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        let setting = TerminalSettings::get_global(cx);
-
-        let position = e.position - origin;
-        if self.mouse_mode(e.modifiers.shift) {
-            let point = grid_point(
-                position,
-                self.last_content.size,
-                self.last_content.display_offset,
-            );
-
-            if let Some(bytes) =
-                mouse_button_report(point, e.button, e.modifiers, false, self.last_content.mode)
-            {
-                self.pty_tx.notify(bytes);
-            }
-        } else {
-            if e.button == MouseButton::Left && setting.copy_on_select {
-                self.copy();
-            }
-
-            //Hyperlinks
-            if self.selection_phase == SelectionPhase::Ended {
-                let mouse_cell_index = content_index_for_mouse(position, &self.last_content.size);
-                if let Some(link) = self.last_content.cells[mouse_cell_index].hyperlink() {
-                    cx.open_url(link.uri());
-                } else if self.cmd_pressed {
-                    self.events
-                        .push_back(InternalEvent::FindHyperlink(position, true));
-                }
-            }
-        }
-
-        self.selection_phase = SelectionPhase::Ended;
-        self.last_mouse = None;
-    }
-
-    ///Scroll the terminal
-    pub fn scroll_wheel(&mut self, e: &ScrollWheelEvent, origin: Point<Pixels>) {
-        let mouse_mode = self.mouse_mode(e.shift);
-
-        if let Some(scroll_lines) = self.determine_scroll_lines(&e, mouse_mode) {
-            if mouse_mode {
-                let point = grid_point(
-                    e.position - origin,
-                    self.last_content.size,
-                    self.last_content.display_offset,
-                );
-
-                if let Some(scrolls) =
-                    scroll_report(point, scroll_lines as i32, &e, self.last_content.mode)
-                {
-                    for scroll in scrolls {
-                        self.pty_tx.notify(scroll);
-                    }
-                };
-            } else if self
-                .last_content
-                .mode
-                .contains(TermMode::ALT_SCREEN | TermMode::ALTERNATE_SCROLL)
-                && !e.shift
-            {
-                self.pty_tx.notify(alt_scroll(scroll_lines))
-            } else {
-                if scroll_lines != 0 {
-                    let scroll = AlacScroll::Delta(scroll_lines);
-
-                    self.events.push_back(InternalEvent::Scroll(scroll));
-                }
-            }
-        }
-    }
-
-    fn refresh_hovered_word(&mut self) {
-        self.word_from_position(self.last_mouse_position);
-    }
-
-    fn determine_scroll_lines(&mut self, e: &ScrollWheelEvent, mouse_mode: bool) -> Option<i32> {
-        let scroll_multiplier = if mouse_mode { 1. } else { SCROLL_MULTIPLIER };
-        let line_height = self.last_content.size.line_height;
-        match e.touch_phase {
-            /* Reset scroll state on started */
-            TouchPhase::Started => {
-                self.scroll_px = px(0.);
-                None
-            }
-            /* Calculate the appropriate scroll lines */
-            TouchPhase::Moved => {
-                let old_offset = (self.scroll_px / line_height) as i32;
-
-                self.scroll_px += e.delta.pixel_delta(line_height).y * scroll_multiplier;
-
-                let new_offset = (self.scroll_px / line_height) as i32;
-
-                // Whenever we hit the edges, reset our stored scroll to 0
-                // so we can respond to changes in direction quickly
-                self.scroll_px %= self.last_content.size.height();
-
-                Some(new_offset - old_offset)
-            }
-            TouchPhase::Ended => None,
-        }
-    }
-
-    pub fn find_matches(
-        &mut self,
-        searcher: RegexSearch,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Vec<RangeInclusive<AlacPoint>>> {
-        let term = self.term.clone();
-        cx.background_executor().spawn(async move {
-            let term = term.lock();
-
-            all_search_matches(&term, &searcher).collect()
-        })
-    }
-
-    pub fn title(&self) -> String {
-        self.foreground_process_info
-            .as_ref()
-            .map(|fpi| {
-                format!(
-                    "{} — {}",
-                    truncate_and_trailoff(
-                        &fpi.cwd
-                            .file_name()
-                            .map(|name| name.to_string_lossy().to_string())
-                            .unwrap_or_default(),
-                        25
-                    ),
-                    truncate_and_trailoff(
-                        &{
-                            format!(
-                                "{}{}",
-                                fpi.name,
-                                if fpi.argv.len() >= 1 {
-                                    format!(" {}", (&fpi.argv[1..]).join(" "))
-                                } else {
-                                    "".to_string()
-                                }
-                            )
-                        },
-                        25
-                    )
-                )
-            })
-            .unwrap_or_else(|| "Terminal".to_string())
-    }
-
-    pub fn can_navigate_to_selected_word(&self) -> bool {
-        self.cmd_pressed && self.hovered_word
-    }
-}
-
-impl Drop for Terminal {
-    fn drop(&mut self) {
-        self.pty_tx.0.send(Msg::Shutdown).ok();
-    }
-}
-
-impl EventEmitter<Event> for Terminal {}
-
-/// Based on alacritty/src/display/hint.rs > regex_match_at
-/// Retrieve the match, if the specified point is inside the content matching the regex.
-fn regex_match_at<T>(term: &Term<T>, point: AlacPoint, regex: &RegexSearch) -> Option<Match> {
-    visible_regex_match_iter(term, regex).find(|rm| rm.contains(&point))
-}
-
-/// Copied from alacritty/src/display/hint.rs:
-/// Iterate over all visible regex matches.
-pub fn visible_regex_match_iter<'a, T>(
-    term: &'a Term<T>,
-    regex: &'a RegexSearch,
-) -> impl Iterator<Item = Match> + 'a {
-    let viewport_start = Line(-(term.grid().display_offset() as i32));
-    let viewport_end = viewport_start + term.bottommost_line();
-    let mut start = term.line_search_left(AlacPoint::new(viewport_start, Column(0)));
-    let mut end = term.line_search_right(AlacPoint::new(viewport_end, Column(0)));
-    start.line = start.line.max(viewport_start - MAX_SEARCH_LINES);
-    end.line = end.line.min(viewport_end + MAX_SEARCH_LINES);
-
-    RegexIter::new(start, end, AlacDirection::Right, term, regex)
-        .skip_while(move |rm| rm.end().line < viewport_start)
-        .take_while(move |rm| rm.start().line <= viewport_end)
-}
-
-fn make_selection(range: &RangeInclusive<AlacPoint>) -> Selection {
-    let mut selection = Selection::new(SelectionType::Simple, *range.start(), AlacDirection::Left);
-    selection.update(*range.end(), AlacDirection::Right);
-    selection
-}
-
-fn all_search_matches<'a, T>(
-    term: &'a Term<T>,
-    regex: &'a RegexSearch,
-) -> impl Iterator<Item = Match> + 'a {
-    let start = AlacPoint::new(term.grid().topmost_line(), Column(0));
-    let end = AlacPoint::new(term.grid().bottommost_line(), term.grid().last_column());
-    RegexIter::new(start, end, AlacDirection::Right, term, regex)
-}
-
-fn content_index_for_mouse(pos: Point<Pixels>, size: &TerminalSize) -> usize {
-    let col = (pos.x / size.cell_width()).round() as usize;
-    let clamped_col = min(col, size.columns() - 1);
-    let row = (pos.y / size.line_height()).round() as usize;
-    let clamped_row = min(row, size.screen_lines() - 1);
-    clamped_row * size.columns() + clamped_col
-}
-
-///Converts an 8 bit ANSI color to it's GPUI equivalent.
-///Accepts usize for compatibility with the alacritty::Colors interface,
-///Other than that use case, should only be called with values in the [0,255] range
-pub fn get_color_at_index(index: usize, theme: &Theme) -> Hsla {
-    let colors = theme.colors();
-
-    match index {
-        //0-15 are the same as the named colors above
-        0 => colors.terminal_ansi_black,
-        1 => colors.terminal_ansi_red,
-        2 => colors.terminal_ansi_green,
-        3 => colors.terminal_ansi_yellow,
-        4 => colors.terminal_ansi_blue,
-        5 => colors.terminal_ansi_magenta,
-        6 => colors.terminal_ansi_cyan,
-        7 => colors.terminal_ansi_white,
-        8 => colors.terminal_ansi_bright_black,
-        9 => colors.terminal_ansi_bright_red,
-        10 => colors.terminal_ansi_bright_green,
-        11 => colors.terminal_ansi_bright_yellow,
-        12 => colors.terminal_ansi_bright_blue,
-        13 => colors.terminal_ansi_bright_magenta,
-        14 => colors.terminal_ansi_bright_cyan,
-        15 => colors.terminal_ansi_bright_white,
-        //16-231 are mapped to their RGB colors on a 0-5 range per channel
-        16..=231 => {
-            let (r, g, b) = rgb_for_index(&(index as u8)); //Split the index into it's ANSI-RGB components
-            let step = (u8::MAX as f32 / 5.).floor() as u8; //Split the RGB range into 5 chunks, with floor so no overflow
-            rgba_color(r * step, g * step, b * step) //Map the ANSI-RGB components to an RGB color
-        }
-        //232-255 are a 24 step grayscale from black to white
-        232..=255 => {
-            let i = index as u8 - 232; //Align index to 0..24
-            let step = (u8::MAX as f32 / 24.).floor() as u8; //Split the RGB grayscale values into 24 chunks
-            rgba_color(i * step, i * step, i * step) //Map the ANSI-grayscale components to the RGB-grayscale
-        }
-        //For compatibility with the alacritty::Colors interface
-        256 => colors.text,
-        257 => colors.background,
-        258 => theme.players().local().cursor,
-
-        // todo!(more colors)
-        259 => red(),                      //style.dim_black,
-        260 => red(),                      //style.dim_red,
-        261 => red(),                      //style.dim_green,
-        262 => red(),                      //style.dim_yellow,
-        263 => red(),                      //style.dim_blue,
-        264 => red(),                      //style.dim_magenta,
-        265 => red(),                      //style.dim_cyan,
-        266 => red(),                      //style.dim_white,
-        267 => red(),                      //style.bright_foreground,
-        268 => colors.terminal_ansi_black, //'Dim Background', non-standard color
-
-        _ => black(),
-    }
-}
-
-///Generates the rgb channels in [0, 5] for a given index into the 6x6x6 ANSI color cube
-///See: [8 bit ansi color](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit).
-///
-///Wikipedia gives a formula for calculating the index for a given color:
-///
-///index = 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5)
-///
-///This function does the reverse, calculating the r, g, and b components from a given index.
-fn rgb_for_index(i: &u8) -> (u8, u8, u8) {
-    debug_assert!((&16..=&231).contains(&i));
-    let i = i - 16;
-    let r = (i - (i % 36)) / 36;
-    let g = ((i % 36) - (i % 6)) / 6;
-    let b = (i % 36) % 6;
-    (r, g, b)
-}
-
-pub fn rgba_color(r: u8, g: u8, b: u8) -> Hsla {
-    Rgba {
-        r: (r as f32 / 255.) as f32,
-        g: (g as f32 / 255.) as f32,
-        b: (b as f32 / 255.) as f32,
-        a: 1.,
-    }
-    .into()
-}
-
-#[cfg(test)]
-mod tests {
-    use alacritty_terminal::{
-        index::{Column, Line, Point as AlacPoint},
-        term::cell::Cell,
-    };
-    use gpui::{point, size, Pixels};
-    use rand::{distributions::Alphanumeric, rngs::ThreadRng, thread_rng, Rng};
-
-    use crate::{
-        content_index_for_mouse, rgb_for_index, IndexedCell, TerminalContent, TerminalSize,
-    };
-
-    #[test]
-    fn test_rgb_for_index() {
-        //Test every possible value in the color cube
-        for i in 16..=231 {
-            let (r, g, b) = rgb_for_index(&(i as u8));
-            assert_eq!(i, 16 + 36 * r + 6 * g + b);
-        }
-    }
-
-    #[test]
-    fn test_mouse_to_cell_test() {
-        let mut rng = thread_rng();
-        const ITERATIONS: usize = 10;
-        const PRECISION: usize = 1000;
-
-        for _ in 0..ITERATIONS {
-            let viewport_cells = rng.gen_range(15..20);
-            let cell_size = rng.gen_range(5 * PRECISION..20 * PRECISION) as f32 / PRECISION as f32;
-
-            let size = crate::TerminalSize {
-                cell_width: Pixels::from(cell_size),
-                line_height: Pixels::from(cell_size),
-                size: size(
-                    Pixels::from(cell_size * (viewport_cells as f32)),
-                    Pixels::from(cell_size * (viewport_cells as f32)),
-                ),
-            };
-
-            let cells = get_cells(size, &mut rng);
-            let content = convert_cells_to_content(size, &cells);
-
-            for row in 0..(viewport_cells - 1) {
-                let row = row as usize;
-                for col in 0..(viewport_cells - 1) {
-                    let col = col as usize;
-
-                    let row_offset = rng.gen_range(0..PRECISION) as f32 / PRECISION as f32;
-                    let col_offset = rng.gen_range(0..PRECISION) as f32 / PRECISION as f32;
-
-                    let mouse_pos = point(
-                        Pixels::from(col as f32 * cell_size + col_offset),
-                        Pixels::from(row as f32 * cell_size + row_offset),
-                    );
-
-                    let content_index = content_index_for_mouse(mouse_pos, &content.size);
-                    let mouse_cell = content.cells[content_index].c;
-                    let real_cell = cells[row][col];
-
-                    assert_eq!(mouse_cell, real_cell);
-                }
-            }
-        }
-    }
-
-    #[test]
-    fn test_mouse_to_cell_clamp() {
-        let mut rng = thread_rng();
-
-        let size = crate::TerminalSize {
-            cell_width: Pixels::from(10.),
-            line_height: Pixels::from(10.),
-            size: size(Pixels::from(100.), Pixels::from(100.)),
-        };
-
-        let cells = get_cells(size, &mut rng);
-        let content = convert_cells_to_content(size, &cells);
-
-        assert_eq!(
-            content.cells[content_index_for_mouse(
-                point(Pixels::from(-10.), Pixels::from(-10.)),
-                &content.size
-            )]
-            .c,
-            cells[0][0]
-        );
-        assert_eq!(
-            content.cells[content_index_for_mouse(
-                point(Pixels::from(1000.), Pixels::from(1000.)),
-                &content.size
-            )]
-            .c,
-            cells[9][9]
-        );
-    }
-
-    fn get_cells(size: TerminalSize, rng: &mut ThreadRng) -> Vec<Vec<char>> {
-        let mut cells = Vec::new();
-
-        for _ in 0..(f32::from(size.height() / size.line_height()) as usize) {
-            let mut row_vec = Vec::new();
-            for _ in 0..(f32::from(size.width() / size.cell_width()) as usize) {
-                let cell_char = rng.sample(Alphanumeric) as char;
-                row_vec.push(cell_char)
-            }
-            cells.push(row_vec)
-        }
-
-        cells
-    }
-
-    fn convert_cells_to_content(size: TerminalSize, cells: &Vec<Vec<char>>) -> TerminalContent {
-        let mut ic = Vec::new();
-
-        for row in 0..cells.len() {
-            for col in 0..cells[row].len() {
-                let cell_char = cells[row][col];
-                ic.push(IndexedCell {
-                    point: AlacPoint::new(Line(row as i32), Column(col)),
-                    cell: Cell {
-                        c: cell_char,
-                        ..Default::default()
-                    },
-                });
-            }
-        }
-
-        TerminalContent {
-            cells: ic,
-            size,
-            ..Default::default()
-        }
-    }
-}

crates/terminal2/src/terminal_settings.rs 🔗

@@ -1,157 +0,0 @@
-use gpui::{px, AbsoluteLength, AppContext, FontFeatures, Pixels};
-use schemars::JsonSchema;
-use serde_derive::{Deserialize, Serialize};
-use std::{collections::HashMap, path::PathBuf};
-
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
-#[serde(rename_all = "snake_case")]
-pub enum TerminalDockPosition {
-    Left,
-    Bottom,
-    Right,
-}
-
-#[derive(Deserialize)]
-pub struct TerminalSettings {
-    pub shell: Shell,
-    pub working_directory: WorkingDirectory,
-    pub font_size: Option<Pixels>,
-    pub font_family: Option<String>,
-    pub line_height: TerminalLineHeight,
-    pub font_features: Option<FontFeatures>,
-    pub env: HashMap<String, String>,
-    pub blinking: TerminalBlink,
-    pub alternate_scroll: AlternateScroll,
-    pub option_as_meta: bool,
-    pub copy_on_select: bool,
-    pub dock: TerminalDockPosition,
-    pub default_width: Pixels,
-    pub default_height: Pixels,
-    pub detect_venv: VenvSettings,
-}
-
-#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum VenvSettings {
-    #[default]
-    Off,
-    On {
-        activate_script: Option<ActivateScript>,
-        directories: Option<Vec<PathBuf>>,
-    },
-}
-
-pub struct VenvSettingsContent<'a> {
-    pub activate_script: ActivateScript,
-    pub directories: &'a [PathBuf],
-}
-
-impl VenvSettings {
-    pub fn as_option(&self) -> Option<VenvSettingsContent> {
-        match self {
-            VenvSettings::Off => None,
-            VenvSettings::On {
-                activate_script,
-                directories,
-            } => Some(VenvSettingsContent {
-                activate_script: activate_script.unwrap_or(ActivateScript::Default),
-                directories: directories.as_deref().unwrap_or(&[]),
-            }),
-        }
-    }
-}
-
-#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum ActivateScript {
-    #[default]
-    Default,
-    Csh,
-    Fish,
-    Nushell,
-}
-
-#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
-pub struct TerminalSettingsContent {
-    pub shell: Option<Shell>,
-    pub working_directory: Option<WorkingDirectory>,
-    pub font_size: Option<f32>,
-    pub font_family: Option<String>,
-    pub line_height: Option<TerminalLineHeight>,
-    pub font_features: Option<FontFeatures>,
-    pub env: Option<HashMap<String, String>>,
-    pub blinking: Option<TerminalBlink>,
-    pub alternate_scroll: Option<AlternateScroll>,
-    pub option_as_meta: Option<bool>,
-    pub copy_on_select: Option<bool>,
-    pub dock: Option<TerminalDockPosition>,
-    pub default_width: Option<f32>,
-    pub default_height: Option<f32>,
-    pub detect_venv: Option<VenvSettings>,
-}
-
-impl settings::Settings for TerminalSettings {
-    const KEY: Option<&'static str> = Some("terminal");
-
-    type FileContent = TerminalSettingsContent;
-
-    fn load(
-        default_value: &Self::FileContent,
-        user_values: &[&Self::FileContent],
-        _: &mut AppContext,
-    ) -> anyhow::Result<Self> {
-        Self::load_via_json_merge(default_value, user_values)
-    }
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
-#[serde(rename_all = "snake_case")]
-pub enum TerminalLineHeight {
-    #[default]
-    Comfortable,
-    Standard,
-    Custom(f32),
-}
-
-impl TerminalLineHeight {
-    pub fn value(&self) -> AbsoluteLength {
-        let value = match self {
-            TerminalLineHeight::Comfortable => 1.618,
-            TerminalLineHeight::Standard => 1.3,
-            TerminalLineHeight::Custom(line_height) => f32::max(*line_height, 1.),
-        };
-        px(value).into()
-    }
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum TerminalBlink {
-    Off,
-    TerminalControlled,
-    On,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum Shell {
-    System,
-    Program(String),
-    WithArguments { program: String, args: Vec<String> },
-}
-
-#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum AlternateScroll {
-    On,
-    Off,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum WorkingDirectory {
-    CurrentProjectDirectory,
-    FirstProjectDirectory,
-    AlwaysHome,
-    Always { directory: String },
-}

crates/terminal_view/Cargo.toml 🔗

@@ -9,12 +9,11 @@ path = "src/terminal_view.rs"
 doctest = false
 
 [dependencies]
-context_menu = { path = "../context_menu" }
 editor = { path = "../editor" }
 language = { path = "../language" }
 gpui = { path = "../gpui" }
 project = { path = "../project" }
-search = { path = "../search" }
+# search = { path = "../search" }
 settings = { path = "../settings" }
 theme = { path = "../theme" }
 util = { path = "../util" }
@@ -22,6 +21,7 @@ workspace = { path = "../workspace" }
 db = { path = "../db" }
 procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "5cd757e5f2eb039ed0c6bb6512223e69d5efc64d", default-features = false }
 terminal = { path = "../terminal" }
+ui = { path = "../ui" }
 smallvec.workspace = true
 smol.workspace = true
 mio-extras = "2.0.6"

crates/terminal_view/src/terminal_element.rs 🔗

@@ -1,52 +1,44 @@
 use editor::{Cursor, HighlightedRange, HighlightedRangeLine};
 use gpui::{
-    color::Color,
-    elements::{Empty, Overlay},
-    fonts::{HighlightStyle, Properties, Style::Italic, TextStyle, Underline, Weight},
-    geometry::{
-        rect::RectF,
-        vector::{vec2f, Vector2F},
-    },
-    platform::{CursorStyle, MouseButton},
-    serde_json::json,
-    text_layout::{Line, RunStyle},
-    AnyElement, Element, EventContext, FontCache, ModelContext, MouseRegion, Quad, SizeConstraint,
-    TextLayoutCache, ViewContext, WeakModelHandle, WindowContext,
+    div, fill, point, px, red, relative, AnyElement, AsyncWindowContext, AvailableSpace,
+    BorrowWindow, Bounds, DispatchPhase, Element, ElementId, ExternalPaths, FocusHandle, Font,
+    FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveElement, InteractiveElementState,
+    Interactivity, IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton,
+    Pixels, PlatformInputHandler, Point, ShapedLine, StatefulInteractiveElement, StyleRefinement,
+    Styled, TextRun, TextStyle, TextSystem, UnderlineStyle, WhiteSpace, WindowContext,
 };
 use itertools::Itertools;
 use language::CursorShape;
-use ordered_float::OrderedFloat;
+use settings::Settings;
 use terminal::{
+    alacritty_terminal::ansi::NamedColor,
     alacritty_terminal::{
-        ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape, NamedColor},
+        ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape},
         grid::Dimensions,
-        index::Point,
+        index::Point as AlacPoint,
         term::{cell::Flags, TermMode},
     },
-    mappings::colors::convert_color,
     terminal_settings::TerminalSettings,
     IndexedCell, Terminal, TerminalContent, TerminalSize,
 };
-use theme::{TerminalStyle, ThemeSettings};
-use util::ResultExt;
+use theme::{ActiveTheme, Theme, ThemeSettings};
+use ui::Tooltip;
 
+use std::{any::TypeId, mem};
 use std::{fmt::Debug, ops::RangeInclusive};
-use std::{mem, ops::Range};
-
-use crate::TerminalView;
 
 ///The information generated during layout that is necessary for painting
 pub struct LayoutState {
     cells: Vec<LayoutCell>,
     rects: Vec<LayoutRect>,
-    relative_highlighted_ranges: Vec<(RangeInclusive<Point>, Color)>,
+    relative_highlighted_ranges: Vec<(RangeInclusive<AlacPoint>, Hsla)>,
     cursor: Option<Cursor>,
-    background_color: Color,
-    size: TerminalSize,
+    background_color: Hsla,
+    dimensions: TerminalSize,
     mode: TermMode,
     display_offset: usize,
-    hyperlink_tooltip: Option<AnyElement<TerminalView>>,
-    gutter: f32,
+    hyperlink_tooltip: Option<AnyElement>,
+    gutter: Pixels,
 }
 
 ///Helper struct for converting data between alacritty's cursor points, and displayed cursor points
@@ -56,7 +48,7 @@ struct DisplayCursor {
 }
 
 impl DisplayCursor {
-    fn from(cursor_point: Point, display_offset: usize) -> Self {
+    fn from(cursor_point: AlacPoint, display_offset: usize) -> Self {
         Self {
             line: cursor_point.line.0 + display_offset as i32,
             col: cursor_point.column.0,
@@ -72,47 +64,46 @@ impl DisplayCursor {
     }
 }
 
-#[derive(Clone, Debug, Default)]
+#[derive(Debug, Default)]
 struct LayoutCell {
-    point: Point<i32, i32>,
-    text: Line,
+    point: AlacPoint<i32, i32>,
+    text: gpui::ShapedLine,
 }
 
 impl LayoutCell {
-    fn new(point: Point<i32, i32>, text: Line) -> LayoutCell {
+    fn new(point: AlacPoint<i32, i32>, text: gpui::ShapedLine) -> LayoutCell {
         LayoutCell { point, text }
     }
 
     fn paint(
         &self,
-        origin: Vector2F,
+        origin: Point<Pixels>,
         layout: &LayoutState,
-        visible_bounds: RectF,
-        _view: &mut TerminalView,
+        _visible_bounds: Bounds<Pixels>,
         cx: &mut WindowContext,
     ) {
         let pos = {
             let point = self.point;
-            vec2f(
-                (origin.x() + point.column as f32 * layout.size.cell_width).floor(),
-                origin.y() + point.line as f32 * layout.size.line_height,
+
+            Point::new(
+                (origin.x + point.column as f32 * layout.dimensions.cell_width).floor(),
+                origin.y + point.line as f32 * layout.dimensions.line_height,
             )
         };
 
-        self.text
-            .paint(pos, visible_bounds, layout.size.line_height, cx);
+        self.text.paint(pos, layout.dimensions.line_height, cx).ok();
     }
 }
 
 #[derive(Clone, Debug, Default)]
 struct LayoutRect {
-    point: Point<i32, i32>,
+    point: AlacPoint<i32, i32>,
     num_of_cells: usize,
-    color: Color,
+    color: Hsla,
 }
 
 impl LayoutRect {
-    fn new(point: Point<i32, i32>, num_of_cells: usize, color: Color) -> LayoutRect {
+    fn new(point: AlacPoint<i32, i32>, num_of_cells: usize, color: Hsla) -> LayoutRect {
         LayoutRect {
             point,
             num_of_cells,
@@ -128,46 +119,47 @@ impl LayoutRect {
         }
     }
 
-    fn paint(
-        &self,
-        origin: Vector2F,
-        layout: &LayoutState,
-        _view: &mut TerminalView,
-        cx: &mut ViewContext<TerminalView>,
-    ) {
+    fn paint(&self, origin: Point<Pixels>, layout: &LayoutState, cx: &mut WindowContext) {
         let position = {
-            let point = self.point;
-            vec2f(
-                (origin.x() + point.column as f32 * layout.size.cell_width).floor(),
-                origin.y() + point.line as f32 * layout.size.line_height,
+            let alac_point = self.point;
+            point(
+                (origin.x + alac_point.column as f32 * layout.dimensions.cell_width).floor(),
+                origin.y + alac_point.line as f32 * layout.dimensions.line_height,
             )
         };
-        let size = vec2f(
-            (layout.size.cell_width * self.num_of_cells as f32).ceil(),
-            layout.size.line_height,
-        );
+        let size = point(
+            (layout.dimensions.cell_width * self.num_of_cells as f32).ceil(),
+            layout.dimensions.line_height,
+        )
+        .into();
 
-        cx.scene().push_quad(Quad {
-            bounds: RectF::new(position, size),
-            background: Some(self.color),
-            border: Default::default(),
-            corner_radii: Default::default(),
-        })
+        cx.paint_quad(fill(Bounds::new(position, size), self.color));
     }
 }
 
 ///The GPUI element that paints the terminal.
 ///We need to keep a reference to the view for mouse events, do we need it for any other terminal stuff, or can we move that to connection?
 pub struct TerminalElement {
-    terminal: WeakModelHandle<Terminal>,
+    terminal: Model<Terminal>,
+    focus: FocusHandle,
     focused: bool,
     cursor_visible: bool,
     can_navigate_to_selected_word: bool,
+    interactivity: Interactivity,
+}
+
+impl InteractiveElement for TerminalElement {
+    fn interactivity(&mut self) -> &mut Interactivity {
+        &mut self.interactivity
+    }
 }
 
+impl StatefulInteractiveElement for TerminalElement {}
+
 impl TerminalElement {
     pub fn new(
-        terminal: WeakModelHandle<Terminal>,
+        terminal: Model<Terminal>,
+        focus: FocusHandle,
         focused: bool,
         cursor_visible: bool,
         can_navigate_to_selected_word: bool,
@@ -175,21 +167,26 @@ impl TerminalElement {
         TerminalElement {
             terminal,
             focused,
+            focus: focus.clone(),
             cursor_visible,
             can_navigate_to_selected_word,
+            interactivity: Default::default(),
         }
+        .track_focus(&focus)
+        .element
     }
 
-    //Vec<Range<Point>> -> Clip out the parts of the ranges
+    //Vec<Range<AlacPoint>> -> Clip out the parts of the ranges
 
     fn layout_grid(
         grid: &Vec<IndexedCell>,
         text_style: &TextStyle,
-        terminal_theme: &TerminalStyle,
-        text_layout_cache: &TextLayoutCache,
-        font_cache: &FontCache,
-        hyperlink: Option<(HighlightStyle, &RangeInclusive<Point>)>,
+        // terminal_theme: &TerminalStyle,
+        text_system: &TextSystem,
+        hyperlink: Option<(HighlightStyle, &RangeInclusive<AlacPoint>)>,
+        cx: &WindowContext<'_>,
     ) -> (Vec<LayoutCell>, Vec<LayoutRect>) {
+        let theme = cx.theme();
         let mut cells = vec![];
         let mut rects = vec![];
 
@@ -225,18 +222,21 @@ impl TerminalElement {
                                         rects.push(cur_rect.take().unwrap());
                                     }
                                     cur_rect = Some(LayoutRect::new(
-                                        Point::new(line_index as i32, cell.point.column.0 as i32),
+                                        AlacPoint::new(
+                                            line_index as i32,
+                                            cell.point.column.0 as i32,
+                                        ),
                                         1,
-                                        convert_color(&bg, &terminal_theme),
+                                        convert_color(&bg, theme),
                                     ));
                                 }
                             }
                             None => {
                                 cur_alac_color = Some(bg);
                                 cur_rect = Some(LayoutRect::new(
-                                    Point::new(line_index as i32, cell.point.column.0 as i32),
+                                    AlacPoint::new(line_index as i32, cell.point.column.0 as i32),
                                     1,
-                                    convert_color(&bg, &terminal_theme),
+                                    convert_color(&bg, &theme),
                                 ));
                             }
                         }
@@ -245,25 +245,21 @@ impl TerminalElement {
 
                 //Layout current cell text
                 {
-                    let cell_text = &cell.c.to_string();
+                    let cell_text = cell.c.to_string();
                     if !is_blank(&cell) {
-                        let cell_style = TerminalElement::cell_style(
-                            &cell,
-                            fg,
-                            terminal_theme,
-                            text_style,
-                            font_cache,
-                            hyperlink,
-                        );
-
-                        let layout_cell = text_layout_cache.layout_str(
-                            cell_text,
-                            text_style.font_size,
-                            &[(cell_text.len(), cell_style)],
-                        );
+                        let cell_style =
+                            TerminalElement::cell_style(&cell, fg, theme, text_style, hyperlink);
+
+                        let layout_cell = text_system
+                            .shape_line(
+                                cell_text.into(),
+                                text_style.font_size.to_pixels(cx.rem_size()),
+                                &[cell_style],
+                            )
+                            .unwrap();
 
                         cells.push(LayoutCell::new(
-                            Point::new(line_index as i32, cell.point.column.0 as i32),
+                            AlacPoint::new(line_index as i32, cell.point.column.0 as i32),
                             layout_cell,
                         ))
                     };
@@ -282,19 +278,19 @@ impl TerminalElement {
     fn shape_cursor(
         cursor_point: DisplayCursor,
         size: TerminalSize,
-        text_fragment: &Line,
-    ) -> Option<(Vector2F, f32)> {
+        text_fragment: &ShapedLine,
+    ) -> Option<(Point<Pixels>, Pixels)> {
         if cursor_point.line() < size.total_lines() as i32 {
-            let cursor_width = if text_fragment.width() == 0. {
+            let cursor_width = if text_fragment.width == Pixels::ZERO {
                 size.cell_width()
             } else {
-                text_fragment.width()
+                text_fragment.width
             };
 
-            //Cursor should always surround as much of the text as possible,
-            //hence when on pixel boundaries round the origin down and the width up
+            // Cursor should always surround as much of the text as possible,
+            // hence when on pixel boundaries round the origin down and the width up
             Some((
-                vec2f(
+                point(
                     (cursor_point.col() as f32 * size.cell_width()).floor(),
                     (cursor_point.line() as f32 * size.line_height()).floor(),
                 ),
@@ -305,55 +301,55 @@ impl TerminalElement {
         }
     }
 
-    ///Convert the Alacritty cell styles to GPUI text styles and background color
+    /// Convert the Alacritty cell styles to GPUI text styles and background color
     fn cell_style(
         indexed: &IndexedCell,
         fg: terminal::alacritty_terminal::ansi::Color,
-        style: &TerminalStyle,
+        // bg: terminal::alacritty_terminal::ansi::Color,
+        colors: &Theme,
         text_style: &TextStyle,
-        font_cache: &FontCache,
-        hyperlink: Option<(HighlightStyle, &RangeInclusive<Point>)>,
-    ) -> RunStyle {
+        hyperlink: Option<(HighlightStyle, &RangeInclusive<AlacPoint>)>,
+    ) -> TextRun {
         let flags = indexed.cell.flags;
-        let fg = convert_color(&fg, &style);
-
-        let mut underline = flags
-            .intersects(Flags::ALL_UNDERLINES)
-            .then(|| Underline {
-                color: Some(fg),
-                squiggly: flags.contains(Flags::UNDERCURL),
-                thickness: OrderedFloat(1.),
-            })
-            .unwrap_or_default();
-
-        if indexed.cell.hyperlink().is_some() {
-            if underline.thickness == OrderedFloat(0.) {
-                underline.thickness = OrderedFloat(1.);
-            }
-        }
+        let fg = convert_color(&fg, &colors);
+        // let bg = convert_color(&bg, &colors);
+
+        let underline = (flags.intersects(Flags::ALL_UNDERLINES)
+            || indexed.cell.hyperlink().is_some())
+        .then(|| UnderlineStyle {
+            color: Some(fg),
+            thickness: Pixels::from(1.0),
+            wavy: flags.contains(Flags::UNDERCURL),
+        });
 
-        let mut properties = Properties::new();
-        if indexed.flags.intersects(Flags::BOLD | Flags::DIM_BOLD) {
-            properties = *properties.weight(Weight::BOLD);
-        }
-        if indexed.flags.intersects(Flags::ITALIC) {
-            properties = *properties.style(Italic);
-        }
+        let weight = if flags.intersects(Flags::BOLD | Flags::DIM_BOLD) {
+            FontWeight::BOLD
+        } else {
+            FontWeight::NORMAL
+        };
 
-        let font_id = font_cache
-            .select_font(text_style.font_family_id, &properties)
-            .unwrap_or(text_style.font_id);
+        let style = if flags.intersects(Flags::ITALIC) {
+            FontStyle::Italic
+        } else {
+            FontStyle::Normal
+        };
 
-        let mut result = RunStyle {
+        let mut result = TextRun {
+            len: indexed.c.len_utf8() as usize,
             color: fg,
-            font_id,
+            background_color: None,
+            font: Font {
+                weight,
+                style,
+                ..text_style.font()
+            },
             underline,
         };
 
         if let Some((style, range)) = hyperlink {
             if range.contains(&indexed.point) {
                 if let Some(underline) = style.underline {
-                    result.underline = underline;
+                    result.underline = Some(underline);
                 }
 
                 if let Some(color) = style.color {
@@ -365,229 +361,86 @@ impl TerminalElement {
         result
     }
 
-    fn generic_button_handler<E>(
-        connection: WeakModelHandle<Terminal>,
-        origin: Vector2F,
-        f: impl Fn(&mut Terminal, Vector2F, E, &mut ModelContext<Terminal>),
-    ) -> impl Fn(E, &mut TerminalView, &mut EventContext<TerminalView>) {
-        move |event, _: &mut TerminalView, cx| {
-            cx.focus_parent();
-            if let Some(conn_handle) = connection.upgrade(cx) {
-                conn_handle.update(cx, |terminal, cx| {
-                    f(terminal, origin, event, cx);
-
-                    cx.notify();
-                })
-            }
-        }
-    }
-
-    fn attach_mouse_handlers(
-        &self,
-        origin: Vector2F,
-        visible_bounds: RectF,
-        mode: TermMode,
-        cx: &mut ViewContext<TerminalView>,
-    ) {
-        let connection = self.terminal;
-
-        let mut region = MouseRegion::new::<Self>(cx.view_id(), 0, visible_bounds);
-
-        // Terminal Emulator controlled behavior:
-        region = region
-            // Start selections
-            .on_down(MouseButton::Left, move |event, v: &mut TerminalView, cx| {
-                let terminal_view = cx.handle();
-                cx.focus(&terminal_view);
-                v.context_menu.update(cx, |menu, _cx| menu.delay_cancel());
-                if let Some(conn_handle) = connection.upgrade(cx) {
-                    conn_handle.update(cx, |terminal, cx| {
-                        terminal.mouse_down(&event, origin);
-
-                        cx.notify();
-                    })
-                }
-            })
-            // Update drag selections
-            .on_drag(MouseButton::Left, move |event, _: &mut TerminalView, cx| {
-                if event.end {
-                    return;
-                }
-
-                if cx.is_self_focused() {
-                    if let Some(conn_handle) = connection.upgrade(cx) {
-                        conn_handle.update(cx, |terminal, cx| {
-                            terminal.mouse_drag(event, origin);
-                            cx.notify();
-                        })
-                    }
-                }
-            })
-            // Copy on up behavior
-            .on_up(
-                MouseButton::Left,
-                TerminalElement::generic_button_handler(
-                    connection,
-                    origin,
-                    move |terminal, origin, e, cx| {
-                        terminal.mouse_up(&e, origin, cx);
-                    },
-                ),
-            )
-            // Context menu
-            .on_click(
-                MouseButton::Right,
-                move |event, view: &mut TerminalView, cx| {
-                    let mouse_mode = if let Some(conn_handle) = connection.upgrade(cx) {
-                        conn_handle.update(cx, |terminal, _cx| terminal.mouse_mode(event.shift))
-                    } else {
-                        // If we can't get the model handle, probably can't deploy the context menu
-                        true
-                    };
-                    if !mouse_mode {
-                        view.deploy_context_menu(event.position, cx);
-                    }
-                },
-            )
-            .on_move(move |event, _: &mut TerminalView, cx| {
-                if cx.is_self_focused() {
-                    if let Some(conn_handle) = connection.upgrade(cx) {
-                        conn_handle.update(cx, |terminal, cx| {
-                            terminal.mouse_move(&event, origin);
-                            cx.notify();
-                        })
-                    }
-                }
-            })
-            .on_scroll(move |event, _: &mut TerminalView, cx| {
-                if let Some(conn_handle) = connection.upgrade(cx) {
-                    conn_handle.update(cx, |terminal, cx| {
-                        terminal.scroll_wheel(event, origin);
-                        cx.notify();
-                    })
-                }
-            });
-
-        // Mouse mode handlers:
-        // All mouse modes need the extra click handlers
-        if mode.intersects(TermMode::MOUSE_MODE) {
-            region = region
-                .on_down(
-                    MouseButton::Right,
-                    TerminalElement::generic_button_handler(
-                        connection,
-                        origin,
-                        move |terminal, origin, e, _cx| {
-                            terminal.mouse_down(&e, origin);
-                        },
-                    ),
-                )
-                .on_down(
-                    MouseButton::Middle,
-                    TerminalElement::generic_button_handler(
-                        connection,
-                        origin,
-                        move |terminal, origin, e, _cx| {
-                            terminal.mouse_down(&e, origin);
-                        },
-                    ),
-                )
-                .on_up(
-                    MouseButton::Right,
-                    TerminalElement::generic_button_handler(
-                        connection,
-                        origin,
-                        move |terminal, origin, e, cx| {
-                            terminal.mouse_up(&e, origin, cx);
-                        },
-                    ),
-                )
-                .on_up(
-                    MouseButton::Middle,
-                    TerminalElement::generic_button_handler(
-                        connection,
-                        origin,
-                        move |terminal, origin, e, cx| {
-                            terminal.mouse_up(&e, origin, cx);
-                        },
-                    ),
-                )
-        }
-
-        cx.scene().push_mouse_region(region);
-    }
-}
+    fn compute_layout(&self, bounds: Bounds<gpui::Pixels>, cx: &mut WindowContext) -> LayoutState {
+        let settings = ThemeSettings::get_global(cx).clone();
 
-impl Element<TerminalView> for TerminalElement {
-    type LayoutState = LayoutState;
-    type PaintState = ();
+        let buffer_font_size = settings.buffer_font_size(cx);
 
-    fn layout(
-        &mut self,
-        constraint: gpui::SizeConstraint,
-        view: &mut TerminalView,
-        cx: &mut ViewContext<TerminalView>,
-    ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
-        let settings = settings::get::<ThemeSettings>(cx);
-        let terminal_settings = settings::get::<TerminalSettings>(cx);
-
-        //Setup layout information
-        let terminal_theme = settings.theme.terminal.clone(); //TODO: Try to minimize this clone.
-        let link_style = settings.theme.editor.link_definition;
-        let tooltip_style = settings.theme.tooltip.clone();
-
-        let font_cache = cx.font_cache();
-        let font_size = terminal_settings
-            .font_size(cx)
-            .unwrap_or(settings.buffer_font_size(cx));
-        let font_family_name = terminal_settings
+        let terminal_settings = TerminalSettings::get_global(cx);
+        let font_family = terminal_settings
             .font_family
             .as_ref()
-            .unwrap_or(&settings.buffer_font_family_name);
+            .map(|string| string.clone().into())
+            .unwrap_or(settings.buffer_font.family);
+
         let font_features = terminal_settings
             .font_features
-            .as_ref()
-            .unwrap_or(&settings.buffer_font_features);
-        let family_id = font_cache
-            .load_family(&[font_family_name], &font_features)
-            .log_err()
-            .unwrap_or(settings.buffer_font_family);
-        let font_id = font_cache
-            .select_font(family_id, &Default::default())
-            .unwrap();
+            .clone()
+            .unwrap_or(settings.buffer_font.features.clone());
+
+        let line_height = terminal_settings.line_height.value();
+        let font_size = terminal_settings.font_size.clone();
+
+        let font_size =
+            font_size.map_or(buffer_font_size, |size| theme::adjusted_font_size(size, cx));
+
+        let theme = cx.theme().clone();
+
+        let link_style = HighlightStyle {
+            color: Some(theme.colors().link_text_hover),
+            font_weight: None,
+            font_style: None,
+            background_color: None,
+            underline: Some(UnderlineStyle {
+                thickness: px(1.0),
+                color: Some(theme.colors().link_text_hover),
+                wavy: false,
+            }),
+            fade_out: None,
+        };
 
         let text_style = TextStyle {
-            color: settings.theme.editor.text_color,
-            font_family_id: family_id,
-            font_family_name: font_cache.family_name(family_id).unwrap(),
-            font_id,
-            font_size,
-            font_properties: Default::default(),
-            underline: Default::default(),
-            soft_wrap: false,
+            font_family,
+            font_features,
+            font_size: font_size.into(),
+            font_style: FontStyle::Normal,
+            line_height: line_height.into(),
+            background_color: None,
+            white_space: WhiteSpace::Normal,
+            // These are going to be overridden per-cell
+            underline: None,
+            color: theme.colors().text,
+            font_weight: FontWeight::NORMAL,
         };
-        let selection_color = settings.theme.editor.selection.selection;
-        let match_color = settings.theme.search.match_background;
+
+        let text_system = cx.text_system();
+        let selection_color = theme.players().local();
+        let match_color = theme.colors().search_match_background;
         let gutter;
         let dimensions = {
-            let line_height = text_style.font_size * terminal_settings.line_height.value();
-            let cell_width = font_cache.em_advance(text_style.font_id, text_style.font_size);
+            let rem_size = cx.rem_size();
+            let font_pixels = text_style.font_size.to_pixels(rem_size);
+            let line_height = font_pixels * line_height.to_pixels(rem_size);
+            let font_id = cx.text_system().font_id(&text_style.font()).unwrap();
+
+            // todo!(do we need to keep this unwrap?)
+            let cell_width = text_system
+                .advance(font_id, font_pixels, 'm')
+                .unwrap()
+                .width;
             gutter = cell_width;
 
-            let size = constraint.max - vec2f(gutter, 0.);
+            let mut size = bounds.size.clone();
+            size.width -= gutter;
+
             TerminalSize::new(line_height, cell_width, size)
         };
 
-        let search_matches = if let Some(terminal_model) = self.terminal.upgrade(cx) {
-            terminal_model.read(cx).matches.clone()
-        } else {
-            Default::default()
-        };
+        let search_matches = self.terminal.read(cx).matches.clone();
 
-        let background_color = terminal_theme.background;
-        let terminal_handle = self.terminal.upgrade(cx).unwrap();
+        let background_color = theme.colors().terminal_background;
 
-        let last_hovered_word = terminal_handle.update(cx, |terminal, cx| {
+        let last_hovered_word = self.terminal.update(cx, |terminal, cx| {
             terminal.set_size(dimensions);
             terminal.try_sync(cx);
             if self.can_navigate_to_selected_word && terminal.can_navigate_to_selected_word() {
@@ -598,29 +451,11 @@ impl Element<TerminalView> for TerminalElement {
         });
 
         let hyperlink_tooltip = last_hovered_word.clone().map(|hovered_word| {
-            let mut tooltip = Overlay::new(
-                Empty::new()
-                    .contained()
-                    .constrained()
-                    .with_width(dimensions.width())
-                    .with_height(dimensions.height())
-                    .with_tooltip::<TerminalElement>(
-                        hovered_word.id,
-                        hovered_word.word,
-                        None,
-                        tooltip_style,
-                        cx,
-                    ),
-            )
-            .with_position_mode(gpui::elements::OverlayPositionMode::Local)
-            .into_any();
-
-            tooltip.layout(
-                SizeConstraint::new(Vector2F::zero(), cx.window_size()),
-                view,
-                cx,
-            );
-            tooltip
+            div()
+                .size_full()
+                .id("terminal-element")
+                .tooltip(move |cx| Tooltip::text(hovered_word.word.clone(), cx))
+                .into_any_element()
         });
 
         let TerminalContent {
@@ -631,7 +466,7 @@ impl Element<TerminalView> for TerminalElement {
             selection,
             cursor,
             ..
-        } = { &terminal_handle.read(cx).last_content };
+        } = &self.terminal.read(cx).last_content;
 
         // searches, highlights to a single range representations
         let mut relative_highlighted_ranges = Vec::new();
@@ -639,7 +474,8 @@ impl Element<TerminalView> for TerminalElement {
             relative_highlighted_ranges.push((search_match, match_color))
         }
         if let Some(selection) = selection {
-            relative_highlighted_ranges.push((selection.start..=selection.end, selection_color));
+            relative_highlighted_ranges
+                .push((selection.start..=selection.end, selection_color.cursor));
         }
 
         // then have that representation be converted to the appropriate highlight data structure
@@ -647,12 +483,11 @@ impl Element<TerminalView> for TerminalElement {
         let (cells, rects) = TerminalElement::layout_grid(
             cells,
             &text_style,
-            &terminal_theme,
-            cx.text_layout_cache(),
-            cx.font_cache(),
+            &cx.text_system(),
             last_hovered_word
                 .as_ref()
                 .map(|last_hovered_word| (link_style, &last_hovered_word.word_match)),
+            cx,
         );
 
         //Layout cursor. Rectangle is used for IME, so we should lay it out even
@@ -663,25 +498,21 @@ impl Element<TerminalView> for TerminalElement {
             let cursor_point = DisplayCursor::from(cursor.point, *display_offset);
             let cursor_text = {
                 let str_trxt = cursor_char.to_string();
-
-                let color = if self.focused {
-                    terminal_theme.background
-                } else {
-                    terminal_theme.foreground
-                };
-
-                cx.text_layout_cache().layout_str(
-                    &str_trxt,
-                    text_style.font_size,
-                    &[(
-                        str_trxt.len(),
-                        RunStyle {
-                            font_id: text_style.font_id,
-                            color,
+                let len = str_trxt.len();
+                cx.text_system()
+                    .shape_line(
+                        str_trxt.into(),
+                        text_style.font_size.to_pixels(cx.rem_size()),
+                        &[TextRun {
+                            len,
+                            font: text_style.font(),
+                            color: theme.colors().terminal_background,
+                            background_color: None,
                             underline: Default::default(),
-                        },
-                    )],
-                )
+                        }],
+                    )
+                    //todo!(do we need to keep this unwrap?)
+                    .unwrap()
             };
 
             let focused = self.focused;
@@ -701,7 +532,7 @@ impl Element<TerminalView> for TerminalElement {
                         cursor_position,
                         block_width,
                         dimensions.line_height,
-                        terminal_theme.cursor,
+                        theme.players().local().cursor,
                         shape,
                         text,
                     )
@@ -710,144 +541,377 @@ impl Element<TerminalView> for TerminalElement {
         };
 
         //Done!
-        (
-            constraint.max,
-            LayoutState {
-                cells,
-                cursor,
-                background_color,
-                size: dimensions,
-                rects,
-                relative_highlighted_ranges,
-                mode: *mode,
-                display_offset: *display_offset,
-                hyperlink_tooltip,
-                gutter,
-            },
-        )
+        LayoutState {
+            cells,
+            cursor,
+            background_color,
+            dimensions,
+            rects,
+            relative_highlighted_ranges,
+            mode: *mode,
+            display_offset: *display_offset,
+            hyperlink_tooltip,
+            gutter,
+        }
     }
 
-    fn paint(
+    fn generic_button_handler<E>(
+        connection: Model<Terminal>,
+        origin: Point<Pixels>,
+        focus_handle: FocusHandle,
+        f: impl Fn(&mut Terminal, Point<Pixels>, &E, &mut ModelContext<Terminal>),
+    ) -> impl Fn(&E, &mut WindowContext) {
+        move |event, cx| {
+            cx.focus(&focus_handle);
+            connection.update(cx, |terminal, cx| {
+                f(terminal, origin, event, cx);
+
+                cx.notify();
+            })
+        }
+    }
+
+    fn register_key_listeners(&self, cx: &mut WindowContext) {
+        cx.on_key_event({
+            let this = self.terminal.clone();
+            move |event: &ModifiersChangedEvent, phase, cx| {
+                if phase != DispatchPhase::Bubble {
+                    return;
+                }
+
+                let handled =
+                    this.update(cx, |term, _| term.try_modifiers_change(&event.modifiers));
+
+                if handled {
+                    cx.notify();
+                }
+            }
+        });
+    }
+
+    fn register_mouse_listeners(
         &mut self,
-        bounds: RectF,
-        visible_bounds: RectF,
-        layout: &mut Self::LayoutState,
-        view: &mut TerminalView,
-        cx: &mut ViewContext<TerminalView>,
-    ) -> Self::PaintState {
-        let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
-
-        //Setup element stuff
-        let clip_bounds = Some(visible_bounds);
-
-        cx.paint_layer(clip_bounds, |cx| {
-            let origin = bounds.origin() + vec2f(layout.gutter, 0.);
-
-            // Elements are ephemeral, only at paint time do we know what could be clicked by a mouse
-            self.attach_mouse_handlers(origin, visible_bounds, layout.mode, cx);
-
-            cx.scene().push_cursor_region(gpui::CursorRegion {
-                bounds,
-                style: if layout.hyperlink_tooltip.is_some() {
-                    CursorStyle::PointingHand
-                } else {
-                    CursorStyle::IBeam
+        origin: Point<Pixels>,
+        mode: TermMode,
+        bounds: Bounds<Pixels>,
+        cx: &mut WindowContext,
+    ) {
+        let focus = self.focus.clone();
+        let terminal = self.terminal.clone();
+
+        self.interactivity.on_mouse_down(MouseButton::Left, {
+            let terminal = terminal.clone();
+            let focus = focus.clone();
+            move |e, cx| {
+                cx.focus(&focus);
+                //todo!(context menu)
+                // v.context_menu.update(cx, |menu, _cx| menu.delay_cancel());
+                terminal.update(cx, |terminal, cx| {
+                    terminal.mouse_down(&e, origin);
+
+                    cx.notify();
+                })
+            }
+        });
+        self.interactivity.on_mouse_move({
+            let terminal = terminal.clone();
+            let focus = focus.clone();
+            move |e, cx| {
+                if e.pressed_button.is_some() && focus.is_focused(cx) && !cx.has_active_drag() {
+                    terminal.update(cx, |terminal, cx| {
+                        terminal.mouse_drag(e, origin, bounds);
+                        cx.notify();
+                    })
+                }
+            }
+        });
+        self.interactivity.on_mouse_up(
+            MouseButton::Left,
+            TerminalElement::generic_button_handler(
+                terminal.clone(),
+                origin,
+                focus.clone(),
+                move |terminal, origin, e, cx| {
+                    terminal.mouse_up(&e, origin, cx);
                 },
-            });
+            ),
+        );
+        self.interactivity.on_click({
+            let terminal = terminal.clone();
+            move |e, cx| {
+                if e.down.button == MouseButton::Right {
+                    let mouse_mode = terminal.update(cx, |terminal, _cx| {
+                        terminal.mouse_mode(e.down.modifiers.shift)
+                    });
 
-            cx.paint_layer(clip_bounds, |cx| {
-                //Start with a background color
-                cx.scene().push_quad(Quad {
-                    bounds: RectF::new(bounds.origin(), bounds.size()),
-                    background: Some(layout.background_color),
-                    border: Default::default(),
-                    corner_radii: Default::default(),
-                });
+                    if !mouse_mode {
+                        //todo!(context menu)
+                        // view.deploy_context_menu(e.position, cx);
+                    }
+                }
+            }
+        });
 
-                for rect in &layout.rects {
-                    rect.paint(origin, layout, view, cx);
+        self.interactivity.on_mouse_move({
+            let terminal = terminal.clone();
+            let focus = focus.clone();
+            move |e, cx| {
+                if focus.is_focused(cx) {
+                    terminal.update(cx, |terminal, cx| {
+                        terminal.mouse_move(&e, origin);
+                        cx.notify();
+                    })
                 }
-            });
+            }
+        });
+        self.interactivity.on_scroll_wheel({
+            let terminal = terminal.clone();
+            move |e, cx| {
+                terminal.update(cx, |terminal, cx| {
+                    terminal.scroll_wheel(e, origin);
+                    cx.notify();
+                })
+            }
+        });
 
-            //Draw Highlighted Backgrounds
-            cx.paint_layer(clip_bounds, |cx| {
+        self.interactivity.drag_over_styles.push((
+            TypeId::of::<ExternalPaths>(),
+            StyleRefinement::default().bg(cx.theme().colors().drop_target_background),
+        ));
+        self.interactivity.on_drop::<ExternalPaths>({
+            let focus = focus.clone();
+            let terminal = terminal.clone();
+            move |external_paths, cx| {
+                cx.focus(&focus);
+                let mut new_text = external_paths
+                    .paths()
+                    .iter()
+                    .map(|path| format!(" {path:?}"))
+                    .join("");
+                new_text.push(' ');
+                terminal.update(cx, |terminal, _| {
+                    // todo!() long paths are not displayed properly albeit the text is there
+                    terminal.paste(&new_text);
+                });
+            }
+        });
+
+        // Mouse mode handlers:
+        // All mouse modes need the extra click handlers
+        if mode.intersects(TermMode::MOUSE_MODE) {
+            self.interactivity.on_mouse_down(
+                MouseButton::Right,
+                TerminalElement::generic_button_handler(
+                    terminal.clone(),
+                    origin,
+                    focus.clone(),
+                    move |terminal, origin, e, _cx| {
+                        terminal.mouse_down(&e, origin);
+                    },
+                ),
+            );
+            self.interactivity.on_mouse_down(
+                MouseButton::Middle,
+                TerminalElement::generic_button_handler(
+                    terminal.clone(),
+                    origin,
+                    focus.clone(),
+                    move |terminal, origin, e, _cx| {
+                        terminal.mouse_down(&e, origin);
+                    },
+                ),
+            );
+            self.interactivity.on_mouse_up(
+                MouseButton::Right,
+                TerminalElement::generic_button_handler(
+                    terminal.clone(),
+                    origin,
+                    focus.clone(),
+                    move |terminal, origin, e, cx| {
+                        terminal.mouse_up(&e, origin, cx);
+                    },
+                ),
+            );
+            self.interactivity.on_mouse_up(
+                MouseButton::Middle,
+                TerminalElement::generic_button_handler(
+                    terminal,
+                    origin,
+                    focus,
+                    move |terminal, origin, e, cx| {
+                        terminal.mouse_up(&e, origin, cx);
+                    },
+                ),
+            );
+        }
+    }
+}
+
+impl Element for TerminalElement {
+    type State = InteractiveElementState;
+
+    fn request_layout(
+        &mut self,
+        element_state: Option<Self::State>,
+        cx: &mut WindowContext<'_>,
+    ) -> (LayoutId, Self::State) {
+        let (layout_id, interactive_state) =
+            self.interactivity
+                .layout(element_state, cx, |mut style, cx| {
+                    style.size.width = relative(1.).into();
+                    style.size.height = relative(1.).into();
+                    let layout_id = cx.request_layout(&style, None);
+
+                    layout_id
+                });
+
+        (layout_id, interactive_state)
+    }
+
+    fn paint(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        state: &mut Self::State,
+        cx: &mut WindowContext<'_>,
+    ) {
+        let mut layout = self.compute_layout(bounds, cx);
+
+        cx.paint_quad(fill(bounds, layout.background_color));
+        let origin = bounds.origin + Point::new(layout.gutter, px(0.));
+
+        let terminal_input_handler = TerminalInputHandler {
+            cx: cx.to_async(),
+            terminal: self.terminal.clone(),
+            cursor_bounds: layout
+                .cursor
+                .as_ref()
+                .map(|cursor| cursor.bounding_rect(origin)),
+        };
+
+        self.register_mouse_listeners(origin, layout.mode, bounds, cx);
+
+        let mut interactivity = mem::take(&mut self.interactivity);
+        interactivity.paint(bounds, bounds.size, state, cx, |_, _, cx| {
+            cx.handle_input(&self.focus, terminal_input_handler);
+
+            self.register_key_listeners(cx);
+
+            for rect in &layout.rects {
+                rect.paint(origin, &layout, cx);
+            }
+
+            cx.with_z_index(1, |cx| {
                 for (relative_highlighted_range, color) in layout.relative_highlighted_ranges.iter()
                 {
                     if let Some((start_y, highlighted_range_lines)) =
-                        to_highlighted_range_lines(relative_highlighted_range, layout, origin)
+                        to_highlighted_range_lines(relative_highlighted_range, &layout, origin)
                     {
                         let hr = HighlightedRange {
                             start_y, //Need to change this
-                            line_height: layout.size.line_height,
+                            line_height: layout.dimensions.line_height,
                             lines: highlighted_range_lines,
                             color: color.clone(),
                             //Copied from editor. TODO: move to theme or something
-                            corner_radius: 0.15 * layout.size.line_height,
+                            corner_radius: 0.15 * layout.dimensions.line_height,
                         };
                         hr.paint(bounds, cx);
                     }
                 }
             });
 
-            //Draw the text cells
-            cx.paint_layer(clip_bounds, |cx| {
+            cx.with_z_index(2, |cx| {
                 for cell in &layout.cells {
-                    cell.paint(origin, layout, visible_bounds, view, cx);
+                    cell.paint(origin, &layout, bounds, cx);
                 }
             });
 
-            //Draw cursor
             if self.cursor_visible {
-                if let Some(cursor) = &layout.cursor {
-                    cx.paint_layer(clip_bounds, |cx| {
+                cx.with_z_index(3, |cx| {
+                    if let Some(cursor) = &layout.cursor {
                         cursor.paint(origin, cx);
-                    })
-                }
+                    }
+                });
             }
 
-            if let Some(element) = &mut layout.hyperlink_tooltip {
-                element.paint(origin, visible_bounds, view, cx)
+            if let Some(mut element) = layout.hyperlink_tooltip.take() {
+                element.draw(origin, bounds.size.map(AvailableSpace::Definite), cx)
             }
         });
     }
+}
+
+impl IntoElement for TerminalElement {
+    type Element = Self;
+
+    fn element_id(&self) -> Option<ElementId> {
+        Some("terminal".into())
+    }
+
+    fn into_element(self) -> Self::Element {
+        self
+    }
+}
+
+struct TerminalInputHandler {
+    cx: AsyncWindowContext,
+    terminal: Model<Terminal>,
+    cursor_bounds: Option<Bounds<Pixels>>,
+}
 
-    fn metadata(&self) -> Option<&dyn std::any::Any> {
+impl PlatformInputHandler for TerminalInputHandler {
+    fn selected_text_range(&mut self) -> Option<std::ops::Range<usize>> {
+        self.cx
+            .update(|_, cx| {
+                if self
+                    .terminal
+                    .read(cx)
+                    .last_content
+                    .mode
+                    .contains(TermMode::ALT_SCREEN)
+                {
+                    None
+                } else {
+                    Some(0..0)
+                }
+            })
+            .ok()
+            .flatten()
+    }
+
+    fn marked_text_range(&mut self) -> Option<std::ops::Range<usize>> {
         None
     }
 
-    fn debug(
-        &self,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        _: &TerminalView,
-        _: &gpui::ViewContext<TerminalView>,
-    ) -> gpui::serde_json::Value {
-        json!({
-            "type": "TerminalElement",
-        })
-    }
-
-    fn rect_for_text_range(
-        &self,
-        _: Range<usize>,
-        bounds: RectF,
-        _: RectF,
-        layout: &Self::LayoutState,
-        _: &Self::PaintState,
-        _: &TerminalView,
-        _: &gpui::ViewContext<TerminalView>,
-    ) -> Option<RectF> {
-        // Use the same origin that's passed to `Cursor::paint` in the paint
-        // method bove.
-        let mut origin = bounds.origin() + vec2f(layout.size.cell_width, 0.);
-
-        // TODO - Why is it necessary to move downward one line to get correct
-        // positioning? I would think that we'd want the same rect that is
-        // painted for the cursor.
-        origin += vec2f(0., layout.size.line_height);
-
-        Some(layout.cursor.as_ref()?.bounding_rect(origin))
+    fn text_for_range(&mut self, _: std::ops::Range<usize>) -> Option<String> {
+        None
+    }
+
+    fn replace_text_in_range(
+        &mut self,
+        _replacement_range: Option<std::ops::Range<usize>>,
+        text: &str,
+    ) {
+        self.cx
+            .update(|_, cx| {
+                self.terminal.update(cx, |terminal, _| {
+                    terminal.input(text.into());
+                })
+            })
+            .ok();
+    }
+
+    fn replace_and_mark_text_in_range(
+        &mut self,
+        _range_utf16: Option<std::ops::Range<usize>>,
+        _new_text: &str,
+        _new_selected_range: Option<std::ops::Range<usize>>,
+    ) {
+    }
+
+    fn unmark_text(&mut self) {}
+
+    fn bounds_for_range(&mut self, _range_utf16: std::ops::Range<usize>) -> Option<Bounds<Pixels>> {
+        self.cursor_bounds
     }
 }
 

crates/terminal_view/src/terminal_panel.rs 🔗

@@ -3,110 +3,107 @@ use std::{path::PathBuf, sync::Arc};
 use crate::TerminalView;
 use db::kvp::KEY_VALUE_STORE;
 use gpui::{
-    actions, anyhow::Result, elements::*, serde_json, Action, AppContext, AsyncAppContext, Entity,
-    Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
+    actions, div, serde_json, AppContext, AsyncWindowContext, Entity, EventEmitter, ExternalPaths,
+    FocusHandle, FocusableView, IntoElement, ParentElement, Pixels, Render, Styled, Subscription,
+    Task, View, ViewContext, VisualContext, WeakView, WindowContext,
 };
 use project::Fs;
 use serde::{Deserialize, Serialize};
-use settings::SettingsStore;
+use settings::{Settings, SettingsStore};
 use terminal::terminal_settings::{TerminalDockPosition, TerminalSettings};
+use ui::{h_stack, ButtonCommon, Clickable, IconButton, IconSize, Selectable, Tooltip};
 use util::{ResultExt, TryFutureExt};
 use workspace::{
-    dock::{DockPosition, Panel},
+    dock::{DockPosition, Panel, PanelEvent},
     item::Item,
-    pane, DraggedItem, Pane, Workspace,
+    pane,
+    ui::Icon,
+    Pane, Workspace,
 };
 
+use anyhow::Result;
+
 const TERMINAL_PANEL_KEY: &'static str = "TerminalPanel";
 
 actions!(terminal_panel, [ToggleFocus]);
 
 pub fn init(cx: &mut AppContext) {
-    cx.add_action(TerminalPanel::new_terminal);
-    cx.add_action(TerminalPanel::open_terminal);
-}
-
-#[derive(Debug)]
-pub enum Event {
-    Close,
-    DockPositionChanged,
-    ZoomIn,
-    ZoomOut,
-    Focus,
+    cx.observe_new_views(
+        |workspace: &mut Workspace, _: &mut ViewContext<Workspace>| {
+            workspace.register_action(TerminalPanel::new_terminal);
+            workspace.register_action(TerminalPanel::open_terminal);
+            workspace.register_action(|workspace, _: &ToggleFocus, cx| {
+                workspace.toggle_panel_focus::<TerminalPanel>(cx);
+            });
+        },
+    )
+    .detach();
 }
 
 pub struct TerminalPanel {
-    pane: ViewHandle<Pane>,
+    pane: View<Pane>,
     fs: Arc<dyn Fs>,
-    workspace: WeakViewHandle<Workspace>,
-    width: Option<f32>,
-    height: Option<f32>,
+    workspace: WeakView<Workspace>,
+    width: Option<Pixels>,
+    height: Option<Pixels>,
     pending_serialization: Task<Option<()>>,
     _subscriptions: Vec<Subscription>,
 }
 
 impl TerminalPanel {
     fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
-        let weak_self = cx.weak_handle();
-        let pane = cx.add_view(|cx| {
-            let window = cx.window();
+        let terminal_panel = cx.view().clone();
+        let pane = cx.new_view(|cx| {
             let mut pane = Pane::new(
                 workspace.weak_handle(),
                 workspace.project().clone(),
-                workspace.app_state().background_actions,
                 Default::default(),
+                Some(Arc::new(|a, cx| {
+                    if let Some(tab) = a.downcast_ref::<workspace::pane::DraggedTab>() {
+                        if let Some(item) = tab.pane.read(cx).item_for_index(tab.ix) {
+                            return item.downcast::<TerminalView>().is_some();
+                        }
+                    }
+                    if a.downcast_ref::<ExternalPaths>().is_some() {
+                        return true;
+                    }
+
+                    false
+                })),
                 cx,
             );
             pane.set_can_split(false, cx);
             pane.set_can_navigate(false, cx);
-            pane.on_can_drop(move |drag_and_drop, cx| {
-                drag_and_drop
-                    .currently_dragged::<DraggedItem>(window)
-                    .map_or(false, |(_, item)| {
-                        item.handle.act_as::<TerminalView>(cx).is_some()
-                    })
-            });
+            pane.display_nav_history_buttons(false);
             pane.set_render_tab_bar_buttons(cx, move |pane, cx| {
-                let this = weak_self.clone();
-                Flex::row()
-                    .with_child(Pane::render_tab_bar_button(
-                        0,
-                        "icons/plus.svg",
-                        false,
-                        Some(("New Terminal", Some(Box::new(workspace::NewTerminal)))),
-                        cx,
-                        move |_, cx| {
-                            let this = this.clone();
-                            cx.window_context().defer(move |cx| {
-                                if let Some(this) = this.upgrade(cx) {
-                                    this.update(cx, |this, cx| {
-                                        this.add_terminal(None, cx);
-                                    });
-                                }
+                h_stack()
+                    .gap_2()
+                    .child(
+                        IconButton::new("plus", Icon::Plus)
+                            .icon_size(IconSize::Small)
+                            .on_click(cx.listener_for(&terminal_panel, |terminal_panel, _, cx| {
+                                terminal_panel.add_terminal(None, cx);
+                            }))
+                            .tooltip(|cx| Tooltip::text("New Terminal", cx)),
+                    )
+                    .child({
+                        let zoomed = pane.is_zoomed();
+                        IconButton::new("toggle_zoom", Icon::Maximize)
+                            .icon_size(IconSize::Small)
+                            .selected(zoomed)
+                            .selected_icon(Icon::Minimize)
+                            .on_click(cx.listener(|pane, _, cx| {
+                                pane.toggle_zoom(&workspace::ToggleZoom, cx);
+                            }))
+                            .tooltip(move |cx| {
+                                Tooltip::text(if zoomed { "Zoom Out" } else { "Zoom In" }, cx)
                             })
-                        },
-                        |_, _| {},
-                        None,
-                    ))
-                    .with_child(Pane::render_tab_bar_button(
-                        1,
-                        if pane.is_zoomed() {
-                            "icons/minimize.svg"
-                        } else {
-                            "icons/maximize.svg"
-                        },
-                        pane.is_zoomed(),
-                        Some(("Toggle Zoom".into(), Some(Box::new(workspace::ToggleZoom)))),
-                        cx,
-                        move |pane, cx| pane.toggle_zoom(&Default::default(), cx),
-                        |_, _| {},
-                        None,
-                    ))
-                    .into_any()
+                    })
+                    .into_any_element()
             });
-            let buffer_search_bar = cx.add_view(search::BufferSearchBar::new);
-            pane.toolbar()
-                .update(cx, |toolbar, cx| toolbar.add_item(buffer_search_bar, cx));
+            // let buffer_search_bar = cx.build_view(search::BufferSearchBar::new);
+            // pane.toolbar()
+            //     .update(cx, |toolbar, cx| toolbar.add_item(buffer_search_bar, cx));
             pane
         });
         let subscriptions = vec![
@@ -123,105 +120,103 @@ impl TerminalPanel {
             _subscriptions: subscriptions,
         };
         let mut old_dock_position = this.position(cx);
-        cx.observe_global::<SettingsStore, _>(move |this, cx| {
+        cx.observe_global::<SettingsStore>(move |this, cx| {
             let new_dock_position = this.position(cx);
             if new_dock_position != old_dock_position {
                 old_dock_position = new_dock_position;
-                cx.emit(Event::DockPositionChanged);
+                cx.emit(PanelEvent::ChangePosition);
             }
         })
         .detach();
         this
     }
 
-    pub fn load(
-        workspace: WeakViewHandle<Workspace>,
-        cx: AsyncAppContext,
-    ) -> Task<Result<ViewHandle<Self>>> {
-        cx.spawn(|mut cx| async move {
-            let serialized_panel = if let Some(panel) = cx
-                .background()
-                .spawn(async move { KEY_VALUE_STORE.read_kvp(TERMINAL_PANEL_KEY) })
-                .await
-                .log_err()
-                .flatten()
-            {
-                Some(serde_json::from_str::<SerializedTerminalPanel>(&panel)?)
+    pub async fn load(
+        workspace: WeakView<Workspace>,
+        mut cx: AsyncWindowContext,
+    ) -> Result<View<Self>> {
+        let serialized_panel = cx
+            .background_executor()
+            .spawn(async move { KEY_VALUE_STORE.read_kvp(TERMINAL_PANEL_KEY) })
+            .await
+            .log_err()
+            .flatten()
+            .map(|panel| serde_json::from_str::<SerializedTerminalPanel>(&panel))
+            .transpose()
+            .log_err()
+            .flatten();
+
+        let (panel, pane, items) = workspace.update(&mut cx, |workspace, cx| {
+            let panel = cx.new_view(|cx| TerminalPanel::new(workspace, cx));
+            let items = if let Some(serialized_panel) = serialized_panel.as_ref() {
+                panel.update(cx, |panel, cx| {
+                    cx.notify();
+                    panel.height = serialized_panel.height;
+                    panel.width = serialized_panel.width;
+                    panel.pane.update(cx, |_, cx| {
+                        serialized_panel
+                            .items
+                            .iter()
+                            .map(|item_id| {
+                                TerminalView::deserialize(
+                                    workspace.project().clone(),
+                                    workspace.weak_handle(),
+                                    workspace.database_id(),
+                                    *item_id,
+                                    cx,
+                                )
+                            })
+                            .collect::<Vec<_>>()
+                    })
+                })
             } else {
-                None
+                Default::default()
             };
-            let (panel, pane, items) = workspace.update(&mut cx, |workspace, cx| {
-                let panel = cx.add_view(|cx| TerminalPanel::new(workspace, cx));
-                let items = if let Some(serialized_panel) = serialized_panel.as_ref() {
-                    panel.update(cx, |panel, cx| {
-                        cx.notify();
-                        panel.height = serialized_panel.height;
-                        panel.width = serialized_panel.width;
-                        panel.pane.update(cx, |_, cx| {
-                            serialized_panel
-                                .items
-                                .iter()
-                                .map(|item_id| {
-                                    TerminalView::deserialize(
-                                        workspace.project().clone(),
-                                        workspace.weak_handle(),
-                                        workspace.database_id(),
-                                        *item_id,
-                                        cx,
-                                    )
-                                })
-                                .collect::<Vec<_>>()
-                        })
-                    })
-                } else {
-                    Default::default()
-                };
-                let pane = panel.read(cx).pane.clone();
-                (panel, pane, items)
-            })?;
-
-            let pane = pane.downgrade();
-            let items = futures::future::join_all(items).await;
-            pane.update(&mut cx, |pane, cx| {
-                let active_item_id = serialized_panel
-                    .as_ref()
-                    .and_then(|panel| panel.active_item_id);
-                let mut active_ix = None;
-                for item in items {
-                    if let Some(item) = item.log_err() {
-                        let item_id = item.id();
-                        pane.add_item(Box::new(item), false, false, None, cx);
-                        if Some(item_id) == active_item_id {
-                            active_ix = Some(pane.items_len() - 1);
-                        }
+            let pane = panel.read(cx).pane.clone();
+            (panel, pane, items)
+        })?;
+
+        let pane = pane.downgrade();
+        let items = futures::future::join_all(items).await;
+        pane.update(&mut cx, |pane, cx| {
+            let active_item_id = serialized_panel
+                .as_ref()
+                .and_then(|panel| panel.active_item_id);
+            let mut active_ix = None;
+            for item in items {
+                if let Some(item) = item.log_err() {
+                    let item_id = item.entity_id().as_u64();
+                    pane.add_item(Box::new(item), false, false, None, cx);
+                    if Some(item_id) == active_item_id {
+                        active_ix = Some(pane.items_len() - 1);
                     }
                 }
+            }
 
-                if let Some(active_ix) = active_ix {
-                    pane.activate_item(active_ix, false, false, cx)
-                }
-            })?;
+            if let Some(active_ix) = active_ix {
+                pane.activate_item(active_ix, false, false, cx)
+            }
+        })?;
 
-            Ok(panel)
-        })
+        Ok(panel)
     }
 
     fn handle_pane_event(
         &mut self,
-        _pane: ViewHandle<Pane>,
+        _pane: View<Pane>,
         event: &pane::Event,
         cx: &mut ViewContext<Self>,
     ) {
         match event {
             pane::Event::ActivateItem { .. } => self.serialize(cx),
             pane::Event::RemoveItem { .. } => self.serialize(cx),
-            pane::Event::Remove => cx.emit(Event::Close),
-            pane::Event::ZoomIn => cx.emit(Event::ZoomIn),
-            pane::Event::ZoomOut => cx.emit(Event::ZoomOut),
-            pane::Event::Focus => cx.emit(Event::Focus),
+            pane::Event::Remove => cx.emit(PanelEvent::Close),
+            pane::Event::ZoomIn => cx.emit(PanelEvent::ZoomIn),
+            pane::Event::ZoomOut => cx.emit(PanelEvent::ZoomOut),
+            pane::Event::Focus => cx.emit(PanelEvent::Focus),
 
             pane::Event::AddItem { item } => {
-                if let Some(workspace) = self.workspace.upgrade(cx) {
+                if let Some(workspace) = self.workspace.upgrade() {
                     let pane = self.pane.clone();
                     workspace.update(cx, |workspace, cx| item.added_to_pane(workspace, pane, cx))
                 }
@@ -261,24 +256,23 @@ impl TerminalPanel {
     fn add_terminal(&mut self, working_directory: Option<PathBuf>, cx: &mut ViewContext<Self>) {
         let workspace = self.workspace.clone();
         cx.spawn(|this, mut cx| async move {
-            let pane = this.read_with(&cx, |this, _| this.pane.clone())?;
+            let pane = this.update(&mut cx, |this, _| this.pane.clone())?;
             workspace.update(&mut cx, |workspace, cx| {
                 let working_directory = if let Some(working_directory) = working_directory {
                     Some(working_directory)
                 } else {
-                    let working_directory_strategy = settings::get::<TerminalSettings>(cx)
-                        .working_directory
-                        .clone();
+                    let working_directory_strategy =
+                        TerminalSettings::get_global(cx).working_directory.clone();
                     crate::get_working_directory(workspace, cx, working_directory_strategy)
                 };
 
-                let window = cx.window();
+                let window = cx.window_handle();
                 if let Some(terminal) = workspace.project().update(cx, |project, cx| {
                     project
                         .create_terminal(working_directory, window, cx)
                         .log_err()
                 }) {
-                    let terminal = Box::new(cx.add_view(|cx| {
+                    let terminal = Box::new(cx.new_view(|cx| {
                         TerminalView::new(
                             terminal,
                             workspace.weak_handle(),
@@ -287,7 +281,7 @@ impl TerminalPanel {
                         )
                     }));
                     pane.update(cx, |pane, cx| {
-                        let focus = pane.has_focus();
+                        let focus = pane.has_focus(cx);
                         pane.add_item(terminal, true, focus, None, cx);
                     });
                 }
@@ -303,12 +297,16 @@ impl TerminalPanel {
             .pane
             .read(cx)
             .items()
-            .map(|item| item.id())
+            .map(|item| item.item_id().as_u64())
             .collect::<Vec<_>>();
-        let active_item_id = self.pane.read(cx).active_item().map(|item| item.id());
+        let active_item_id = self
+            .pane
+            .read(cx)
+            .active_item()
+            .map(|item| item.item_id().as_u64());
         let height = self.height;
         let width = self.width;
-        self.pending_serialization = cx.background().spawn(
+        self.pending_serialization = cx.background_executor().spawn(
             async move {
                 KEY_VALUE_STORE
                     .write_kvp(
@@ -328,29 +326,23 @@ impl TerminalPanel {
     }
 }
 
-impl Entity for TerminalPanel {
-    type Event = Event;
-}
+impl EventEmitter<PanelEvent> for TerminalPanel {}
 
-impl View for TerminalPanel {
-    fn ui_name() -> &'static str {
-        "TerminalPanel"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> gpui::AnyElement<Self> {
-        ChildView::new(&self.pane, cx).into_any()
+impl Render for TerminalPanel {
+    fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
+        div().size_full().child(self.pane.clone())
     }
+}
 
-    fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
-        if cx.is_self_focused() {
-            cx.focus(&self.pane);
-        }
+impl FocusableView for TerminalPanel {
+    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
+        self.pane.focus_handle(cx)
     }
 }
 
 impl Panel for TerminalPanel {
     fn position(&self, cx: &WindowContext) -> DockPosition {
-        match settings::get::<TerminalSettings>(cx).dock {
+        match TerminalSettings::get_global(cx).dock {
             TerminalDockPosition::Left => DockPosition::Left,
             TerminalDockPosition::Bottom => DockPosition::Bottom,
             TerminalDockPosition::Right => DockPosition::Right,
@@ -372,8 +364,8 @@ impl Panel for TerminalPanel {
         });
     }
 
-    fn size(&self, cx: &WindowContext) -> f32 {
-        let settings = settings::get::<TerminalSettings>(cx);
+    fn size(&self, cx: &WindowContext) -> Pixels {
+        let settings = TerminalSettings::get_global(cx);
         match self.position(cx) {
             DockPosition::Left | DockPosition::Right => {
                 self.width.unwrap_or_else(|| settings.default_width)
@@ -382,7 +374,7 @@ impl Panel for TerminalPanel {
         }
     }
 
-    fn set_size(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>) {
+    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
         match self.position(cx) {
             DockPosition::Left | DockPosition::Right => self.width = size,
             DockPosition::Bottom => self.height = size,
@@ -391,14 +383,6 @@ impl Panel for TerminalPanel {
         cx.notify();
     }
 
-    fn should_zoom_in_on_event(event: &Event) -> bool {
-        matches!(event, Event::ZoomIn)
-    }
-
-    fn should_zoom_out_on_event(event: &Event) -> bool {
-        matches!(event, Event::ZoomOut)
-    }
-
     fn is_zoomed(&self, cx: &WindowContext) -> bool {
         self.pane.read(cx).is_zoomed()
     }
@@ -413,14 +397,6 @@ impl Panel for TerminalPanel {
         }
     }
 
-    fn icon_path(&self, _: &WindowContext) -> Option<&'static str> {
-        Some("icons/terminal.svg")
-    }
-
-    fn icon_tooltip(&self) -> (String, Option<Box<dyn Action>>) {
-        ("Terminal Panel".into(), Some(Box::new(ToggleFocus)))
-    }
-
     fn icon_label(&self, cx: &WindowContext) -> Option<String> {
         let count = self.pane.read(cx).items_len();
         if count == 0 {
@@ -430,31 +406,32 @@ impl Panel for TerminalPanel {
         }
     }
 
-    fn should_change_position_on_event(event: &Self::Event) -> bool {
-        matches!(event, Event::DockPositionChanged)
+    fn persistent_name() -> &'static str {
+        "TerminalPanel"
     }
 
-    fn should_activate_on_event(_: &Self::Event) -> bool {
-        false
-    }
+    // todo!()
+    // fn icon_tooltip(&self) -> (String, Option<Box<dyn Action>>) {
+    //     ("Terminal Panel".into(), Some(Box::new(ToggleFocus)))
+    // }
 
-    fn should_close_on_event(event: &Event) -> bool {
-        matches!(event, Event::Close)
+    fn icon(&self, _cx: &WindowContext) -> Option<Icon> {
+        Some(Icon::Terminal)
     }
 
-    fn has_focus(&self, cx: &WindowContext) -> bool {
-        self.pane.read(cx).has_focus()
+    fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
+        Some("Terminal Panel")
     }
 
-    fn is_focus_event(event: &Self::Event) -> bool {
-        matches!(event, Event::Focus)
+    fn toggle_action(&self) -> Box<dyn gpui::Action> {
+        Box::new(ToggleFocus)
     }
 }
 
 #[derive(Serialize, Deserialize)]
 struct SerializedTerminalPanel {
-    items: Vec<usize>,
-    active_item_id: Option<usize>,
-    width: Option<f32>,
-    height: Option<f32>,
+    items: Vec<u64>,
+    active_item_id: Option<u64>,
+    width: Option<Pixels>,
+    height: Option<Pixels>,
 }

crates/terminal_view/src/terminal_view.rs 🔗

@@ -2,48 +2,47 @@ mod persistence;
 pub mod terminal_element;
 pub mod terminal_panel;
 
-use crate::{persistence::TERMINAL_DB, terminal_element::TerminalElement};
-use anyhow::Context;
-use context_menu::{ContextMenu, ContextMenuItem};
-use dirs::home_dir;
+// todo!()
+// use crate::terminal_element::TerminalElement;
 use editor::{scroll::autoscroll::Autoscroll, Editor};
 use gpui::{
-    actions,
-    elements::{AnchorCorner, ChildView, Flex, Label, ParentElement, Stack},
-    geometry::vector::Vector2F,
-    impl_actions,
-    keymap_matcher::{KeymapContext, Keystroke},
-    platform::{KeyDownEvent, ModifiersChangedEvent},
-    AnyElement, AnyViewHandle, AppContext, Element, Entity, ModelHandle, Task, View, ViewContext,
-    ViewHandle, WeakViewHandle,
+    div, impl_actions, overlay, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle,
+    FocusableView, KeyContext, KeyDownEvent, Keystroke, Model, MouseButton, MouseDownEvent, Pixels,
+    Render, Styled, Subscription, Task, View, VisualContext, WeakView,
 };
 use language::Bias;
+use persistence::TERMINAL_DB;
 use project::{search::SearchQuery, LocalWorktree, Project};
-use serde::Deserialize;
-use smallvec::{smallvec, SmallVec};
-use smol::Timer;
-use std::{
-    borrow::Cow,
-    ops::RangeInclusive,
-    path::{Path, PathBuf},
-    sync::Arc,
-    time::Duration,
-};
 use terminal::{
     alacritty_terminal::{
         index::Point,
         term::{search::RegexSearch, TermMode},
     },
     terminal_settings::{TerminalBlink, TerminalSettings, WorkingDirectory},
-    Event, MaybeNavigationTarget, Terminal,
+    Clear, Copy, Event, MaybeNavigationTarget, Paste, ShowCharacterPalette, Terminal,
 };
+use terminal_element::TerminalElement;
+use ui::{h_stack, prelude::*, ContextMenu, Icon, IconElement, Label};
 use util::{paths::PathLikeWithPosition, ResultExt};
 use workspace::{
     item::{BreadcrumbText, Item, ItemEvent},
     notifications::NotifyResultExt,
-    pane, register_deserializable_item,
-    searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle},
-    NewCenterTerminal, Pane, ToolbarItemLocation, Workspace, WorkspaceId,
+    register_deserializable_item,
+    searchable::{SearchEvent, SearchOptions, SearchableItem},
+    CloseActiveItem, NewCenterTerminal, Pane, ToolbarItemLocation, Workspace, WorkspaceId,
+};
+
+use anyhow::Context;
+use dirs::home_dir;
+use serde::Deserialize;
+use settings::Settings;
+use smol::Timer;
+
+use std::{
+    ops::RangeInclusive,
+    path::{Path, PathBuf},
+    sync::Arc,
+    time::Duration,
 };
 
 const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
@@ -52,18 +51,13 @@ const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
 #[derive(Clone, Debug, PartialEq)]
 pub struct ScrollTerminal(pub i32);
 
-#[derive(Clone, Default, Deserialize, PartialEq)]
+#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
 pub struct SendText(String);
 
-#[derive(Clone, Default, Deserialize, PartialEq)]
+#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
 pub struct SendKeystroke(String);
 
-actions!(
-    terminal,
-    [Clear, Copy, Paste, ShowCharacterPalette, SearchTest]
-);
-
-impl_actions!(terminal, [SendText, SendKeystroke]);
+impl_actions!(terminal_view, [SendText, SendKeystroke]);
 
 pub fn init(cx: &mut AppContext) {
     terminal_panel::init(cx);
@@ -71,35 +65,37 @@ pub fn init(cx: &mut AppContext) {
 
     register_deserializable_item::<TerminalView>(cx);
 
-    cx.add_action(TerminalView::deploy);
-
-    //Useful terminal views
-    cx.add_action(TerminalView::send_text);
-    cx.add_action(TerminalView::send_keystroke);
-    cx.add_action(TerminalView::copy);
-    cx.add_action(TerminalView::paste);
-    cx.add_action(TerminalView::clear);
-    cx.add_action(TerminalView::show_character_palette);
-    cx.add_action(TerminalView::select_all)
+    cx.observe_new_views(|workspace: &mut Workspace, _| {
+        workspace.register_action(TerminalView::deploy);
+    })
+    .detach();
 }
 
 ///A terminal view, maintains the PTY's file handles and communicates with the terminal
 pub struct TerminalView {
-    terminal: ModelHandle<Terminal>,
+    terminal: Model<Terminal>,
+    focus_handle: FocusHandle,
     has_new_content: bool,
     //Currently using iTerm bell, show bell emoji in tab until input is received
     has_bell: bool,
-    context_menu: ViewHandle<ContextMenu>,
+    context_menu: Option<(View<ContextMenu>, gpui::Point<Pixels>, Subscription)>,
     blink_state: bool,
     blinking_on: bool,
     blinking_paused: bool,
     blink_epoch: usize,
     can_navigate_to_selected_word: bool,
     workspace_id: WorkspaceId,
+    _subscriptions: Vec<Subscription>,
 }
 
-impl Entity for TerminalView {
-    type Event = Event;
+impl EventEmitter<Event> for TerminalView {}
+impl EventEmitter<ItemEvent> for TerminalView {}
+impl EventEmitter<SearchEvent> for TerminalView {}
+
+impl FocusableView for TerminalView {
+    fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
+        self.focus_handle.clone()
+    }
 }
 
 impl TerminalView {
@@ -109,11 +105,11 @@ impl TerminalView {
         _: &NewCenterTerminal,
         cx: &mut ViewContext<Workspace>,
     ) {
-        let strategy = settings::get::<TerminalSettings>(cx);
+        let strategy = TerminalSettings::get_global(cx);
         let working_directory =
             get_working_directory(workspace, cx, strategy.working_directory.clone());
 
-        let window = cx.window();
+        let window = cx.window_handle();
         let terminal = workspace
             .project()
             .update(cx, |project, cx| {
@@ -122,7 +118,7 @@ impl TerminalView {
             .notify_err(workspace, cx);
 
         if let Some(terminal) = terminal {
-            let view = cx.add_view(|cx| {
+            let view = cx.new_view(|cx| {
                 TerminalView::new(
                     terminal,
                     workspace.weak_handle(),
@@ -135,20 +131,21 @@ impl TerminalView {
     }
 
     pub fn new(
-        terminal: ModelHandle<Terminal>,
-        workspace: WeakViewHandle<Workspace>,
+        terminal: Model<Terminal>,
+        workspace: WeakView<Workspace>,
         workspace_id: WorkspaceId,
         cx: &mut ViewContext<Self>,
     ) -> Self {
-        let view_id = cx.view_id();
         cx.observe(&terminal, |_, _, cx| cx.notify()).detach();
         cx.subscribe(&terminal, move |this, _, event, cx| match event {
             Event::Wakeup => {
-                if !cx.is_self_focused() {
+                if !this.focus_handle.is_focused(cx) {
                     this.has_new_content = true;
                 }
                 cx.notify();
                 cx.emit(Event::Wakeup);
+                cx.emit(ItemEvent::UpdateTab);
+                cx.emit(SearchEvent::MatchesInvalidated);
             }
 
             Event::Bell => {
@@ -159,15 +156,16 @@ impl TerminalView {
             Event::BlinkChanged => this.blinking_on = !this.blinking_on,
 
             Event::TitleChanged => {
+                cx.emit(ItemEvent::UpdateTab);
                 if let Some(foreground_info) = &this.terminal().read(cx).foreground_process_info {
                     let cwd = foreground_info.cwd.clone();
 
-                    let item_id = cx.view_id();
+                    let item_id = cx.entity_id();
                     let workspace_id = this.workspace_id;
-                    cx.background()
+                    cx.background_executor()
                         .spawn(async move {
                             TERMINAL_DB
-                                .save_working_directory(item_id, workspace_id, cwd)
+                                .save_working_directory(item_id.as_u64(), workspace_id, cwd)
                                 .await
                                 .log_err();
                         })
@@ -186,7 +184,7 @@ impl TerminalView {
             }
 
             Event::Open(maybe_navigation_target) => match maybe_navigation_target {
-                MaybeNavigationTarget::Url(url) => cx.platform().open_url(url),
+                MaybeNavigationTarget::Url(url) => cx.open_url(url),
 
                 MaybeNavigationTarget::PathLike(maybe_path) => {
                     if !this.can_navigate_to_selected_word {
@@ -252,26 +250,37 @@ impl TerminalView {
                     }
                 }
             },
-
-            _ => cx.emit(event.clone()),
+            Event::BreadcrumbsChanged => cx.emit(ItemEvent::UpdateBreadcrumbs),
+            Event::CloseTerminal => cx.emit(ItemEvent::CloseItem),
+            Event::SelectionsChanged => cx.emit(SearchEvent::ActiveMatchChanged),
         })
         .detach();
 
+        let focus_handle = cx.focus_handle();
+        let focus_in = cx.on_focus_in(&focus_handle, |terminal_view, cx| {
+            terminal_view.focus_in(cx);
+        });
+        let focus_out = cx.on_focus_out(&focus_handle, |terminal_view, cx| {
+            terminal_view.focus_out(cx);
+        });
+
         Self {
             terminal,
             has_new_content: true,
             has_bell: false,
-            context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)),
+            focus_handle: cx.focus_handle(),
+            context_menu: None,
             blink_state: true,
             blinking_on: false,
             blinking_paused: false,
             blink_epoch: 0,
             can_navigate_to_selected_word: false,
             workspace_id,
+            _subscriptions: vec![focus_in, focus_out],
         }
     }
 
-    pub fn model(&self) -> &ModelHandle<Terminal> {
+    pub fn model(&self) -> &Model<Terminal> {
         &self.terminal
     }
 
@@ -288,17 +297,29 @@ impl TerminalView {
         cx.emit(Event::Wakeup);
     }
 
-    pub fn deploy_context_menu(&mut self, position: Vector2F, cx: &mut ViewContext<Self>) {
-        let menu_entries = vec![
-            ContextMenuItem::action("Clear", Clear),
-            ContextMenuItem::action("Close", pane::CloseActiveItem { save_intent: None }),
-        ];
-
-        self.context_menu.update(cx, |menu, cx| {
-            menu.show(position, AnchorCorner::TopLeft, menu_entries, cx)
+    pub fn deploy_context_menu(
+        &mut self,
+        position: gpui::Point<Pixels>,
+        cx: &mut ViewContext<Self>,
+    ) {
+        let context_menu = ContextMenu::build(cx, |menu, _| {
+            menu.action("Clear", Box::new(Clear))
+                .action("Close", Box::new(CloseActiveItem { save_intent: None }))
         });
 
-        cx.notify();
+        cx.focus_view(&context_menu);
+        let subscription =
+            cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| {
+                if this.context_menu.as_ref().is_some_and(|context_menu| {
+                    context_menu.0.focus_handle(cx).contains_focused(cx)
+                }) {
+                    cx.focus_self();
+                }
+                this.context_menu.take();
+                cx.notify();
+            });
+
+        self.context_menu = Some((context_menu, position, subscription));
     }
 
     fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
@@ -314,7 +335,7 @@ impl TerminalView {
             self.terminal.update(cx, |term, cx| {
                 term.try_keystroke(
                     &Keystroke::parse("ctrl-cmd-space").unwrap(),
-                    settings::get::<TerminalSettings>(cx).option_as_meta,
+                    TerminalSettings::get_global(cx).option_as_meta,
                 )
             });
         }
@@ -345,7 +366,7 @@ impl TerminalView {
             return true;
         }
 
-        match settings::get::<TerminalSettings>(cx).blinking {
+        match TerminalSettings::get_global(cx).blinking {
             //If the user requested to never blink, don't blink it.
             TerminalBlink::Off => true,
             //If the terminal is controlling it, check terminal mode
@@ -392,11 +413,11 @@ impl TerminalView {
             self.terminal
                 .update(cx, |term, cx| term.find_matches(searcher, cx))
         } else {
-            cx.background().spawn(async { Vec::new() })
+            cx.background_executor().spawn(async { Vec::new() })
         }
     }
 
-    pub fn terminal(&self) -> &ModelHandle<Terminal> {
+    pub fn terminal(&self) -> &Model<Terminal> {
         &self.terminal
     }
 
@@ -436,19 +457,91 @@ impl TerminalView {
         if let Some(keystroke) = Keystroke::parse(&text.0).log_err() {
             self.clear_bel(cx);
             self.terminal.update(cx, |term, cx| {
-                term.try_keystroke(
-                    &keystroke,
-                    settings::get::<TerminalSettings>(cx).option_as_meta,
-                );
+                term.try_keystroke(&keystroke, TerminalSettings::get_global(cx).option_as_meta);
             });
         }
     }
+
+    fn dispatch_context(&self, cx: &AppContext) -> KeyContext {
+        let mut dispatch_context = KeyContext::default();
+        dispatch_context.add("Terminal");
+
+        let mode = self.terminal.read(cx).last_content.mode;
+        dispatch_context.set(
+            "screen",
+            if mode.contains(TermMode::ALT_SCREEN) {
+                "alt"
+            } else {
+                "normal"
+            },
+        );
+
+        if mode.contains(TermMode::APP_CURSOR) {
+            dispatch_context.add("DECCKM");
+        }
+        if mode.contains(TermMode::APP_KEYPAD) {
+            dispatch_context.add("DECPAM");
+        } else {
+            dispatch_context.add("DECPNM");
+        }
+        if mode.contains(TermMode::SHOW_CURSOR) {
+            dispatch_context.add("DECTCEM");
+        }
+        if mode.contains(TermMode::LINE_WRAP) {
+            dispatch_context.add("DECAWM");
+        }
+        if mode.contains(TermMode::ORIGIN) {
+            dispatch_context.add("DECOM");
+        }
+        if mode.contains(TermMode::INSERT) {
+            dispatch_context.add("IRM");
+        }
+        //LNM is apparently the name for this. https://vt100.net/docs/vt510-rm/LNM.html
+        if mode.contains(TermMode::LINE_FEED_NEW_LINE) {
+            dispatch_context.add("LNM");
+        }
+        if mode.contains(TermMode::FOCUS_IN_OUT) {
+            dispatch_context.add("report_focus");
+        }
+        if mode.contains(TermMode::ALTERNATE_SCROLL) {
+            dispatch_context.add("alternate_scroll");
+        }
+        if mode.contains(TermMode::BRACKETED_PASTE) {
+            dispatch_context.add("bracketed_paste");
+        }
+        if mode.intersects(TermMode::MOUSE_MODE) {
+            dispatch_context.add("any_mouse_reporting");
+        }
+        {
+            let mouse_reporting = if mode.contains(TermMode::MOUSE_REPORT_CLICK) {
+                "click"
+            } else if mode.contains(TermMode::MOUSE_DRAG) {
+                "drag"
+            } else if mode.contains(TermMode::MOUSE_MOTION) {
+                "motion"
+            } else {
+                "off"
+            };
+            dispatch_context.set("mouse_reporting", mouse_reporting);
+        }
+        {
+            let format = if mode.contains(TermMode::SGR_MOUSE) {
+                "sgr"
+            } else if mode.contains(TermMode::UTF8_MOUSE) {
+                "utf8"
+            } else {
+                "normal"
+            };
+            dispatch_context.set("mouse_format", format);
+        };
+        dispatch_context
+    }
 }
 
 fn possible_open_targets(
-    workspace: &WeakViewHandle<Workspace>,
+    workspace: &WeakView<Workspace>,
     maybe_path: &String,
-    cx: &mut ViewContext<'_, '_, TerminalView>,
+    cx: &mut ViewContext<'_, TerminalView>,
 ) -> Vec<PathLikeWithPosition<PathBuf>> {
     let path_like = PathLikeWithPosition::parse_str(maybe_path.as_str(), |path_str| {
         Ok::<_, std::convert::Infallible>(Path::new(path_str).to_path_buf())
@@ -467,7 +560,7 @@ fn possible_open_targets(
         } else {
             Vec::new()
         }
-    } else if let Some(workspace) = workspace.upgrade(cx) {
+    } else if let Some(workspace) = workspace.upgrade() {
         workspace.update(cx, |workspace, cx| {
             workspace
                 .worktrees(cx)
@@ -495,198 +588,102 @@ pub fn regex_search_for_query(query: &project::search::SearchQuery) -> Option<Re
     searcher.ok()
 }
 
-impl View for TerminalView {
-    fn ui_name() -> &'static str {
-        "Terminal"
-    }
-
-    fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> AnyElement<Self> {
-        let terminal_handle = self.terminal.clone().downgrade();
-
-        let self_id = cx.view_id();
-        let focused = cx
-            .focused_view_id()
-            .filter(|view_id| *view_id == self_id)
-            .is_some();
+impl TerminalView {
+    fn key_down(&mut self, event: &KeyDownEvent, cx: &mut ViewContext<Self>) {
+        self.clear_bel(cx);
+        self.pause_cursor_blinking(cx);
 
-        Stack::new()
-            .with_child(
-                TerminalElement::new(
-                    terminal_handle,
-                    focused,
-                    self.should_show_cursor(focused, cx),
-                    self.can_navigate_to_selected_word,
-                )
-                .contained(),
+        self.terminal.update(cx, |term, cx| {
+            term.try_keystroke(
+                &event.keystroke,
+                TerminalSettings::get_global(cx).option_as_meta,
             )
-            .with_child(ChildView::new(&self.context_menu, cx))
-            .into_any()
+        });
     }
 
-    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
+    fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
         self.has_new_content = false;
         self.terminal.read(cx).focus_in();
         self.blink_cursors(self.blink_epoch, cx);
         cx.notify();
     }
 
-    fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
+    fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
         self.terminal.update(cx, |terminal, _| {
             terminal.focus_out();
         });
         cx.notify();
     }
+}
 
-    fn modifiers_changed(
-        &mut self,
-        event: &ModifiersChangedEvent,
-        cx: &mut ViewContext<Self>,
-    ) -> bool {
-        let handled = self
-            .terminal()
-            .update(cx, |term, _| term.try_modifiers_change(&event.modifiers));
-        if handled {
-            cx.notify();
-        }
-        handled
-    }
-
-    fn key_down(&mut self, event: &KeyDownEvent, cx: &mut ViewContext<Self>) -> bool {
-        self.clear_bel(cx);
-        self.pause_cursor_blinking(cx);
-
-        self.terminal.update(cx, |term, cx| {
-            term.try_keystroke(
-                &event.keystroke,
-                settings::get::<TerminalSettings>(cx).option_as_meta,
+impl Render for TerminalView {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        let terminal_handle = self.terminal.clone();
+
+        let focused = self.focus_handle.is_focused(cx);
+
+        div()
+            .size_full()
+            .relative()
+            .track_focus(&self.focus_handle)
+            .key_context(self.dispatch_context(cx))
+            .on_action(cx.listener(TerminalView::send_text))
+            .on_action(cx.listener(TerminalView::send_keystroke))
+            .on_action(cx.listener(TerminalView::copy))
+            .on_action(cx.listener(TerminalView::paste))
+            .on_action(cx.listener(TerminalView::clear))
+            .on_action(cx.listener(TerminalView::show_character_palette))
+            .on_action(cx.listener(TerminalView::select_all))
+            .on_key_down(cx.listener(Self::key_down))
+            .on_mouse_down(
+                MouseButton::Right,
+                cx.listener(|this, event: &MouseDownEvent, cx| {
+                    this.deploy_context_menu(event.position, cx);
+                    cx.notify();
+                }),
             )
-        })
-    }
-
-    //IME stuff
-    fn selected_text_range(&self, cx: &AppContext) -> Option<std::ops::Range<usize>> {
-        if self
-            .terminal
-            .read(cx)
-            .last_content
-            .mode
-            .contains(TermMode::ALT_SCREEN)
-        {
-            None
-        } else {
-            Some(0..0)
-        }
-    }
-
-    fn replace_text_in_range(
-        &mut self,
-        _: Option<std::ops::Range<usize>>,
-        text: &str,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.terminal.update(cx, |terminal, _| {
-            terminal.input(text.into());
-        });
-    }
-
-    fn update_keymap_context(&self, keymap: &mut KeymapContext, cx: &gpui::AppContext) {
-        Self::reset_to_default_keymap_context(keymap);
-
-        let mode = self.terminal.read(cx).last_content.mode;
-        keymap.add_key(
-            "screen",
-            if mode.contains(TermMode::ALT_SCREEN) {
-                "alt"
-            } else {
-                "normal"
-            },
-        );
-
-        if mode.contains(TermMode::APP_CURSOR) {
-            keymap.add_identifier("DECCKM");
-        }
-        if mode.contains(TermMode::APP_KEYPAD) {
-            keymap.add_identifier("DECPAM");
-        } else {
-            keymap.add_identifier("DECPNM");
-        }
-        if mode.contains(TermMode::SHOW_CURSOR) {
-            keymap.add_identifier("DECTCEM");
-        }
-        if mode.contains(TermMode::LINE_WRAP) {
-            keymap.add_identifier("DECAWM");
-        }
-        if mode.contains(TermMode::ORIGIN) {
-            keymap.add_identifier("DECOM");
-        }
-        if mode.contains(TermMode::INSERT) {
-            keymap.add_identifier("IRM");
-        }
-        //LNM is apparently the name for this. https://vt100.net/docs/vt510-rm/LNM.html
-        if mode.contains(TermMode::LINE_FEED_NEW_LINE) {
-            keymap.add_identifier("LNM");
-        }
-        if mode.contains(TermMode::FOCUS_IN_OUT) {
-            keymap.add_identifier("report_focus");
-        }
-        if mode.contains(TermMode::ALTERNATE_SCROLL) {
-            keymap.add_identifier("alternate_scroll");
-        }
-        if mode.contains(TermMode::BRACKETED_PASTE) {
-            keymap.add_identifier("bracketed_paste");
-        }
-        if mode.intersects(TermMode::MOUSE_MODE) {
-            keymap.add_identifier("any_mouse_reporting");
-        }
-        {
-            let mouse_reporting = if mode.contains(TermMode::MOUSE_REPORT_CLICK) {
-                "click"
-            } else if mode.contains(TermMode::MOUSE_DRAG) {
-                "drag"
-            } else if mode.contains(TermMode::MOUSE_MOTION) {
-                "motion"
-            } else {
-                "off"
-            };
-            keymap.add_key("mouse_reporting", mouse_reporting);
-        }
-        {
-            let format = if mode.contains(TermMode::SGR_MOUSE) {
-                "sgr"
-            } else if mode.contains(TermMode::UTF8_MOUSE) {
-                "utf8"
-            } else {
-                "normal"
-            };
-            keymap.add_key("mouse_format", format);
-        }
+            .child(
+                // TODO: Oddly this wrapper div is needed for TerminalElement to not steal events from the context menu
+                div().size_full().child(TerminalElement::new(
+                    terminal_handle,
+                    self.focus_handle.clone(),
+                    focused,
+                    self.should_show_cursor(focused, cx),
+                    self.can_navigate_to_selected_word,
+                )),
+            )
+            .children(self.context_menu.as_ref().map(|(menu, positon, _)| {
+                overlay()
+                    .position(*positon)
+                    .anchor(gpui::AnchorCorner::TopLeft)
+                    .child(menu.clone())
+            }))
     }
 }
 
 impl Item for TerminalView {
-    fn tab_tooltip_text(&self, cx: &AppContext) -> Option<Cow<str>> {
+    type Event = ItemEvent;
+
+    fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
         Some(self.terminal().read(cx).title().into())
     }
 
-    fn tab_content<T: 'static>(
+    fn tab_content(
         &self,
         _detail: Option<usize>,
-        tab_theme: &theme::Tab,
-        cx: &gpui::AppContext,
-    ) -> AnyElement<T> {
+        selected: bool,
+        cx: &WindowContext,
+    ) -> AnyElement {
         let title = self.terminal().read(cx).title();
 
-        Flex::row()
-            .with_child(
-                gpui::elements::Svg::new("icons/terminal.svg")
-                    .with_color(tab_theme.label.text.color)
-                    .constrained()
-                    .with_width(tab_theme.type_icon_width)
-                    .aligned()
-                    .contained()
-                    .with_margin_right(tab_theme.spacing),
-            )
-            .with_child(Label::new(title, tab_theme.label.clone()).aligned())
+        h_stack()
+            .gap_2()
+            .child(IconElement::new(Icon::Terminal))
+            .child(Label::new(title).color(if selected {
+                Color::Default
+            } else {
+                Color::Muted
+            }))
             .into_any()
     }
 
@@ -694,7 +691,7 @@ impl Item for TerminalView {
         &self,
         _workspace_id: WorkspaceId,
         _cx: &mut ViewContext<Self>,
-    ) -> Option<Self> {
+    ) -> Option<View<Self>> {
         //From what I can tell, there's no  way to tell the current working
         //Directory of the terminal from outside the shell. There might be
         //solutions to this, but they are non-trivial and require more IPC
@@ -717,21 +714,13 @@ impl Item for TerminalView {
         false
     }
 
-    fn as_searchable(&self, handle: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
-        Some(Box::new(handle.clone()))
-    }
-
-    fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
-        match event {
-            Event::BreadcrumbsChanged => smallvec![ItemEvent::UpdateBreadcrumbs],
-            Event::TitleChanged | Event::Wakeup => smallvec![ItemEvent::UpdateTab],
-            Event::CloseTerminal => smallvec![ItemEvent::CloseItem],
-            _ => smallvec![],
-        }
-    }
+    // todo!(search)
+    // fn as_searchable(&self, handle: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
+    //     Some(Box::new(handle.clone()))
+    // }
 
     fn breadcrumb_location(&self) -> ToolbarItemLocation {
-        ToolbarItemLocation::PrimaryLeft { flex: None }
+        ToolbarItemLocation::PrimaryLeft
     }
 
     fn breadcrumbs(&self, _: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
@@ -746,51 +735,55 @@ impl Item for TerminalView {
     }
 
     fn deserialize(
-        project: ModelHandle<Project>,
-        workspace: WeakViewHandle<Workspace>,
+        project: Model<Project>,
+        workspace: WeakView<Workspace>,
         workspace_id: workspace::WorkspaceId,
         item_id: workspace::ItemId,
         cx: &mut ViewContext<Pane>,
-    ) -> Task<anyhow::Result<ViewHandle<Self>>> {
-        let window = cx.window();
+    ) -> Task<anyhow::Result<View<Self>>> {
+        let window = cx.window_handle();
         cx.spawn(|pane, mut cx| async move {
             let cwd = TERMINAL_DB
                 .get_working_directory(item_id, workspace_id)
                 .log_err()
                 .flatten()
                 .or_else(|| {
-                    cx.read(|cx| {
-                        let strategy = settings::get::<TerminalSettings>(cx)
-                            .working_directory
-                            .clone();
+                    cx.update(|_, cx| {
+                        let strategy = TerminalSettings::get_global(cx).working_directory.clone();
                         workspace
-                            .upgrade(cx)
+                            .upgrade()
                             .map(|workspace| {
                                 get_working_directory(workspace.read(cx), cx, strategy)
                             })
                             .flatten()
                     })
+                    .ok()
+                    .flatten()
                 });
 
             let terminal = project.update(&mut cx, |project, cx| {
                 project.create_terminal(cwd, window, cx)
-            })?;
-            Ok(pane.update(&mut cx, |_, cx| {
-                cx.add_view(|cx| TerminalView::new(terminal, workspace, workspace_id, cx))
-            })?)
+            })??;
+            pane.update(&mut cx, |_, cx| {
+                cx.new_view(|cx| TerminalView::new(terminal, workspace, workspace_id, cx))
+            })
         })
     }
 
     fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
-        cx.background()
+        cx.background_executor()
             .spawn(TERMINAL_DB.update_workspace_id(
                 workspace.database_id(),
                 self.workspace_id,
-                cx.view_id(),
+                cx.entity_id().as_u64(),
             ))
             .detach();
         self.workspace_id = workspace.database_id();
     }
+
+    fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) {
+        f(*event)
+    }
 }
 
 impl SearchableItem for TerminalView {
@@ -805,19 +798,6 @@ impl SearchableItem for TerminalView {
         }
     }
 
-    /// Convert events raised by this item into search-relevant events (if applicable)
-    fn to_search_event(
-        &mut self,
-        event: &Self::Event,
-        _: &mut ViewContext<Self>,
-    ) -> Option<SearchEvent> {
-        match event {
-            Event::Wakeup => Some(SearchEvent::MatchesInvalidated),
-            Event::SelectionsChanged => Some(SearchEvent::ActiveMatchChanged),
-            _ => None,
-        }
-    }
-
     /// Clear stored matches
     fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
         self.terminal().update(cx, |term, _| term.matches.clear())
@@ -1074,12 +1054,10 @@ mod tests {
     }
 
     /// Creates a worktree with 1 file: /root.txt
-    pub async fn init_test(
-        cx: &mut TestAppContext,
-    ) -> (ModelHandle<Project>, ViewHandle<Workspace>) {
+    pub async fn init_test(cx: &mut TestAppContext) -> (Model<Project>, View<Workspace>) {
         let params = cx.update(AppState::test);
         cx.update(|cx| {
-            theme::init((), cx);
+            theme::init(theme::LoadThemes::JustBase, cx);
             Project::init_settings(cx);
             language::init(cx);
         });
@@ -1087,35 +1065,36 @@ mod tests {
         let project = Project::test(params.fs.clone(), [], cx).await;
         let workspace = cx
             .add_window(|cx| Workspace::test_new(project.clone(), cx))
-            .root(cx);
+            .root_view(cx)
+            .unwrap();
 
         (project, workspace)
     }
 
     /// Creates a worktree with 1 folder: /root{suffix}/
     async fn create_folder_wt(
-        project: ModelHandle<Project>,
+        project: Model<Project>,
         path: impl AsRef<Path>,
         cx: &mut TestAppContext,
-    ) -> (ModelHandle<Worktree>, Entry) {
+    ) -> (Model<Worktree>, Entry) {
         create_wt(project, true, path, cx).await
     }
 
     /// Creates a worktree with 1 file: /root{suffix}.txt
     async fn create_file_wt(
-        project: ModelHandle<Project>,
+        project: Model<Project>,
         path: impl AsRef<Path>,
         cx: &mut TestAppContext,
-    ) -> (ModelHandle<Worktree>, Entry) {
+    ) -> (Model<Worktree>, Entry) {
         create_wt(project, false, path, cx).await
     }
 
     async fn create_wt(
-        project: ModelHandle<Project>,
+        project: Model<Project>,
         is_dir: bool,
         path: impl AsRef<Path>,
         cx: &mut TestAppContext,
-    ) -> (ModelHandle<Worktree>, Entry) {
+    ) -> (Model<Worktree>, Entry) {
         let (wt, _) = project
             .update(cx, |project, cx| {
                 project.find_or_create_local_worktree(path, true, cx)
@@ -1139,9 +1118,9 @@ mod tests {
     }
 
     pub fn insert_active_entry_for(
-        wt: ModelHandle<Worktree>,
+        wt: Model<Worktree>,
         entry: Entry,
-        project: ModelHandle<Project>,
+        project: Model<Project>,
         cx: &mut TestAppContext,
     ) {
         cx.update(|cx| {

crates/terminal_view2/Cargo.toml 🔗

@@ -1,46 +0,0 @@
-[package]
-name = "terminal_view2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/terminal_view.rs"
-doctest = false
-
-[dependencies]
-editor = { package = "editor2", path = "../editor2" }
-language = { package = "language2", path = "../language2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-project = { package = "project2", path = "../project2" }
-# search = { package = "search2", path = "../search2" }
-settings = { package = "settings2", path = "../settings2" }
-theme = { package = "theme2", path = "../theme2" }
-util = { path = "../util" }
-workspace = { package = "workspace2", path = "../workspace2" }
-db = { package = "db2", path = "../db2" }
-procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "5cd757e5f2eb039ed0c6bb6512223e69d5efc64d", default-features = false }
-terminal = { package = "terminal2", path = "../terminal2" }
-ui = { package = "ui2", path = "../ui2" }
-smallvec.workspace = true
-smol.workspace = true
-mio-extras = "2.0.6"
-futures.workspace = true
-ordered-float.workspace = true
-itertools = "0.10"
-dirs = "4.0.0"
-shellexpand = "2.1.0"
-libc = "0.2"
-anyhow.workspace = true
-thiserror.workspace = true
-lazy_static.workspace = true
-serde.workspace = true
-serde_derive.workspace = true
-
-[dev-dependencies]
-editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-client = { package = "client2", path = "../client2", features = ["test-support"]}
-project = { package = "project2", path = "../project2", features = ["test-support"]}
-workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
-rand.workspace = true

crates/terminal_view2/README.md 🔗

@@ -1,23 +0,0 @@
-Design notes:
-
-This crate is split into two conceptual halves:
-- The terminal.rs file and the src/mappings/ folder, these contain the code for interacting with Alacritty and maintaining the pty event loop. Some behavior in this file is constrained by terminal protocols and standards. The Zed init function is also placed here.
-- Everything else. These other files integrate the `Terminal` struct created in terminal.rs into the rest of GPUI. The main entry point for GPUI is the terminal_view.rs file and the modal.rs file.
-
-ttys are created externally, and so can fail in unexpected ways. However, GPUI currently does not have an API for models than can fail to instantiate. `TerminalBuilder` solves this by using Rust's type system to split tty instantiation into a 2 step process: first attempt to create the file handles with `TerminalBuilder::new()`, check the result, then call `TerminalBuilder::subscribe(cx)` from within a model context.
-
-The TerminalView struct abstracts over failed and successful terminals, passing focus through to the associated view and allowing clients to build a terminal without worrying about errors.
-
-#Input
-
-There are currently many distinct paths for getting keystrokes to the terminal:
-
-1. Terminal specific characters and bindings. Things like ctrl-a mapping to ASCII control character 1, ANSI escape codes associated with the function keys, etc. These are caught with a raw key-down handler in the element and are processed immediately. This is done with the `try_keystroke()` method on Terminal
-
-2. GPU Action handlers. GPUI clobbers a few vital keys by adding bindings to them in the global context. These keys are synthesized and then dispatched through the same `try_keystroke()` API as the above mappings
-
-3. IME text. When the special character mappings fail, we pass the keystroke back to GPUI to hand it to the IME system. This comes back to us in the `View::replace_text_in_range()` method, and we then send that to the terminal directly, bypassing `try_keystroke()`.
-
-4. Pasted text has a separate pathway.
-
-Generally, there's a distinction between 'keystrokes that need to be mapped' and 'strings which need to be written'. I've attempted to unify these under the '.try_keystroke()' API and the `.input()` API (which try_keystroke uses) so we have consistent input handling across the terminal

crates/terminal_view2/scripts/print256color.sh 🔗

@@ -1,96 +0,0 @@
-#!/bin/bash
-
-# Tom Hale, 2016. MIT Licence.
-# Print out 256 colours, with each number printed in its corresponding colour
-# See http://askubuntu.com/questions/821157/print-a-256-color-test-pattern-in-the-terminal/821163#821163
-
-set -eu # Fail on errors or undeclared variables
-
-printable_colours=256
-
-# Return a colour that contrasts with the given colour
-# Bash only does integer division, so keep it integral
-function contrast_colour {
-    local r g b luminance
-    colour="$1"
-
-    if (( colour < 16 )); then # Initial 16 ANSI colours
-        (( colour == 0 )) && printf "15" || printf "0"
-        return
-    fi
-
-    # Greyscale # rgb_R = rgb_G = rgb_B = (number - 232) * 10 + 8
-    if (( colour > 231 )); then # Greyscale ramp
-        (( colour < 244 )) && printf "15" || printf "0"
-        return
-    fi
-
-    # All other colours:
-    # 6x6x6 colour cube = 16 + 36*R + 6*G + B  # Where RGB are [0..5]
-    # See http://stackoverflow.com/a/27165165/5353461
-
-    # r=$(( (colour-16) / 36 ))
-    g=$(( ((colour-16) % 36) / 6 ))
-    # b=$(( (colour-16) % 6 ))
-
-    # If luminance is bright, print number in black, white otherwise.
-    # Green contributes 587/1000 to human perceived luminance - ITU R-REC-BT.601
-    (( g > 2)) && printf "0" || printf "15"
-    return
-
-    # Uncomment the below for more precise luminance calculations
-
-    # # Calculate perceived brightness
-    # # See https://www.w3.org/TR/AERT#color-contrast
-    # # and http://www.itu.int/rec/R-REC-BT.601
-    # # Luminance is in range 0..5000 as each value is 0..5
-    # luminance=$(( (r * 299) + (g * 587) + (b * 114) ))
-    # (( $luminance > 2500 )) && printf "0" || printf "15"
-}
-
-# Print a coloured block with the number of that colour
-function print_colour {
-    local colour="$1" contrast
-    contrast=$(contrast_colour "$1")
-    printf "\e[48;5;%sm" "$colour"                # Start block of colour
-    printf "\e[38;5;%sm%3d" "$contrast" "$colour" # In contrast, print number
-    printf "\e[0m "                               # Reset colour
-}
-
-# Starting at $1, print a run of $2 colours
-function print_run {
-    local i
-    for (( i = "$1"; i < "$1" + "$2" && i < printable_colours; i++ )) do
-        print_colour "$i"
-    done
-    printf "  "
-}
-
-# Print blocks of colours
-function print_blocks {
-    local start="$1" i
-    local end="$2" # inclusive
-    local block_cols="$3"
-    local block_rows="$4"
-    local blocks_per_line="$5"
-    local block_length=$((block_cols * block_rows))
-
-    # Print sets of blocks
-    for (( i = start; i <= end; i += (blocks_per_line-1) * block_length )) do
-        printf "\n" # Space before each set of blocks
-        # For each block row
-        for (( row = 0; row < block_rows; row++ )) do
-            # Print block columns for all blocks on the line
-            for (( block = 0; block < blocks_per_line; block++ )) do
-                print_run $(( i + (block * block_length) )) "$block_cols"
-            done
-            (( i += block_cols )) # Prepare to print the next row
-            printf "\n"
-        done
-    done
-}
-
-print_run 0 16 # The first 16 colours are spread over the whole spectrum
-printf "\n"
-print_blocks 16 231 6 6 3 # 6x6x6 colour cube between 16 and 231 inclusive
-print_blocks 232 255 12 2 1 # Not 50, but 24 Shades of Grey

crates/terminal_view2/scripts/truecolor.sh 🔗

@@ -1,19 +0,0 @@
-#!/bin/bash
-# Copied from: https://unix.stackexchange.com/a/696756
-# Based on: https://gist.github.com/XVilka/8346728 and https://unix.stackexchange.com/a/404415/395213
-
-awk -v term_cols="${width:-$(tput cols || echo 80)}" -v term_lines="${height:-1}" 'BEGIN{
-    s="/\\";
-    total_cols=term_cols*term_lines;
-    for (colnum = 0; colnum<total_cols; colnum++) {
-        r = 255-(colnum*255/total_cols);
-        g = (colnum*510/total_cols);
-        b = (colnum*255/total_cols);
-        if (g>255) g = 510-g;
-        printf "\033[48;2;%d;%d;%dm", r,g,b;
-        printf "\033[38;2;%d;%d;%dm", 255-r,255-g,255-b;
-        printf "%s\033[0m", substr(s,colnum%2+1,1);
-        if (colnum%term_cols==term_cols) printf "\n";
-    }
-    printf "\n";
-}'

crates/terminal_view2/src/persistence.rs 🔗

@@ -1,71 +0,0 @@
-use std::path::PathBuf;
-
-use db::{define_connection, query, sqlez_macros::sql};
-use workspace::{ItemId, WorkspaceDb, WorkspaceId};
-
-define_connection! {
-    pub static ref TERMINAL_DB: TerminalDb<WorkspaceDb> =
-        &[sql!(
-            CREATE TABLE terminals (
-                workspace_id INTEGER,
-                item_id INTEGER UNIQUE,
-                working_directory BLOB,
-                PRIMARY KEY(workspace_id, item_id),
-                FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
-                ON DELETE CASCADE
-            ) STRICT;
-        ),
-        // Remove the unique constraint on the item_id table
-        // SQLite doesn't have a way of doing this automatically, so
-        // we have to do this silly copying.
-        sql!(
-            CREATE TABLE terminals2 (
-                workspace_id INTEGER,
-                item_id INTEGER,
-                working_directory BLOB,
-                PRIMARY KEY(workspace_id, item_id),
-                FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
-                ON DELETE CASCADE
-            ) STRICT;
-
-            INSERT INTO terminals2 (workspace_id, item_id, working_directory)
-            SELECT workspace_id, item_id, working_directory FROM terminals;
-
-            DROP TABLE terminals;
-
-            ALTER TABLE terminals2 RENAME TO terminals;
-        )];
-}
-
-impl TerminalDb {
-    query! {
-       pub async fn update_workspace_id(
-            new_id: WorkspaceId,
-            old_id: WorkspaceId,
-            item_id: ItemId
-        ) -> Result<()> {
-            UPDATE terminals
-            SET workspace_id = ?
-            WHERE workspace_id = ? AND item_id = ?
-        }
-    }
-
-    query! {
-        pub async fn save_working_directory(
-            item_id: ItemId,
-            workspace_id: WorkspaceId,
-            working_directory: PathBuf
-        ) -> Result<()> {
-            INSERT OR REPLACE INTO terminals(item_id, workspace_id, working_directory)
-            VALUES (?, ?, ?)
-        }
-    }
-
-    query! {
-        pub fn get_working_directory(item_id: ItemId, workspace_id: WorkspaceId) -> Result<Option<PathBuf>> {
-            SELECT working_directory
-            FROM terminals
-            WHERE item_id = ? AND workspace_id = ?
-        }
-    }
-}

crates/terminal_view2/src/terminal_element.rs 🔗

@@ -1,1054 +0,0 @@
-use editor::{Cursor, HighlightedRange, HighlightedRangeLine};
-use gpui::{
-    div, fill, point, px, red, relative, AnyElement, AsyncWindowContext, AvailableSpace,
-    BorrowWindow, Bounds, DispatchPhase, Element, ElementId, ExternalPaths, FocusHandle, Font,
-    FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveElement, InteractiveElementState,
-    Interactivity, IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton,
-    Pixels, PlatformInputHandler, Point, ShapedLine, StatefulInteractiveElement, StyleRefinement,
-    Styled, TextRun, TextStyle, TextSystem, UnderlineStyle, WhiteSpace, WindowContext,
-};
-use itertools::Itertools;
-use language::CursorShape;
-use settings::Settings;
-use terminal::{
-    alacritty_terminal::ansi::NamedColor,
-    alacritty_terminal::{
-        ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape},
-        grid::Dimensions,
-        index::Point as AlacPoint,
-        term::{cell::Flags, TermMode},
-    },
-    terminal_settings::TerminalSettings,
-    IndexedCell, Terminal, TerminalContent, TerminalSize,
-};
-use theme::{ActiveTheme, Theme, ThemeSettings};
-use ui::Tooltip;
-
-use std::{any::TypeId, mem};
-use std::{fmt::Debug, ops::RangeInclusive};
-
-///The information generated during layout that is necessary for painting
-pub struct LayoutState {
-    cells: Vec<LayoutCell>,
-    rects: Vec<LayoutRect>,
-    relative_highlighted_ranges: Vec<(RangeInclusive<AlacPoint>, Hsla)>,
-    cursor: Option<Cursor>,
-    background_color: Hsla,
-    dimensions: TerminalSize,
-    mode: TermMode,
-    display_offset: usize,
-    hyperlink_tooltip: Option<AnyElement>,
-    gutter: Pixels,
-}
-
-///Helper struct for converting data between alacritty's cursor points, and displayed cursor points
-struct DisplayCursor {
-    line: i32,
-    col: usize,
-}
-
-impl DisplayCursor {
-    fn from(cursor_point: AlacPoint, display_offset: usize) -> Self {
-        Self {
-            line: cursor_point.line.0 + display_offset as i32,
-            col: cursor_point.column.0,
-        }
-    }
-
-    pub fn line(&self) -> i32 {
-        self.line
-    }
-
-    pub fn col(&self) -> usize {
-        self.col
-    }
-}
-
-#[derive(Debug, Default)]
-struct LayoutCell {
-    point: AlacPoint<i32, i32>,
-    text: gpui::ShapedLine,
-}
-
-impl LayoutCell {
-    fn new(point: AlacPoint<i32, i32>, text: gpui::ShapedLine) -> LayoutCell {
-        LayoutCell { point, text }
-    }
-
-    fn paint(
-        &self,
-        origin: Point<Pixels>,
-        layout: &LayoutState,
-        _visible_bounds: Bounds<Pixels>,
-        cx: &mut WindowContext,
-    ) {
-        let pos = {
-            let point = self.point;
-
-            Point::new(
-                (origin.x + point.column as f32 * layout.dimensions.cell_width).floor(),
-                origin.y + point.line as f32 * layout.dimensions.line_height,
-            )
-        };
-
-        self.text.paint(pos, layout.dimensions.line_height, cx).ok();
-    }
-}
-
-#[derive(Clone, Debug, Default)]
-struct LayoutRect {
-    point: AlacPoint<i32, i32>,
-    num_of_cells: usize,
-    color: Hsla,
-}
-
-impl LayoutRect {
-    fn new(point: AlacPoint<i32, i32>, num_of_cells: usize, color: Hsla) -> LayoutRect {
-        LayoutRect {
-            point,
-            num_of_cells,
-            color,
-        }
-    }
-
-    fn extend(&self) -> Self {
-        LayoutRect {
-            point: self.point,
-            num_of_cells: self.num_of_cells + 1,
-            color: self.color,
-        }
-    }
-
-    fn paint(&self, origin: Point<Pixels>, layout: &LayoutState, cx: &mut WindowContext) {
-        let position = {
-            let alac_point = self.point;
-            point(
-                (origin.x + alac_point.column as f32 * layout.dimensions.cell_width).floor(),
-                origin.y + alac_point.line as f32 * layout.dimensions.line_height,
-            )
-        };
-        let size = point(
-            (layout.dimensions.cell_width * self.num_of_cells as f32).ceil(),
-            layout.dimensions.line_height,
-        )
-        .into();
-
-        cx.paint_quad(fill(Bounds::new(position, size), self.color));
-    }
-}
-
-///The GPUI element that paints the terminal.
-///We need to keep a reference to the view for mouse events, do we need it for any other terminal stuff, or can we move that to connection?
-pub struct TerminalElement {
-    terminal: Model<Terminal>,
-    focus: FocusHandle,
-    focused: bool,
-    cursor_visible: bool,
-    can_navigate_to_selected_word: bool,
-    interactivity: Interactivity,
-}
-
-impl InteractiveElement for TerminalElement {
-    fn interactivity(&mut self) -> &mut Interactivity {
-        &mut self.interactivity
-    }
-}
-
-impl StatefulInteractiveElement for TerminalElement {}
-
-impl TerminalElement {
-    pub fn new(
-        terminal: Model<Terminal>,
-        focus: FocusHandle,
-        focused: bool,
-        cursor_visible: bool,
-        can_navigate_to_selected_word: bool,
-    ) -> TerminalElement {
-        TerminalElement {
-            terminal,
-            focused,
-            focus: focus.clone(),
-            cursor_visible,
-            can_navigate_to_selected_word,
-            interactivity: Default::default(),
-        }
-        .track_focus(&focus)
-        .element
-    }
-
-    //Vec<Range<AlacPoint>> -> Clip out the parts of the ranges
-
-    fn layout_grid(
-        grid: &Vec<IndexedCell>,
-        text_style: &TextStyle,
-        // terminal_theme: &TerminalStyle,
-        text_system: &TextSystem,
-        hyperlink: Option<(HighlightStyle, &RangeInclusive<AlacPoint>)>,
-        cx: &WindowContext<'_>,
-    ) -> (Vec<LayoutCell>, Vec<LayoutRect>) {
-        let theme = cx.theme();
-        let mut cells = vec![];
-        let mut rects = vec![];
-
-        let mut cur_rect: Option<LayoutRect> = None;
-        let mut cur_alac_color = None;
-
-        let linegroups = grid.into_iter().group_by(|i| i.point.line);
-        for (line_index, (_, line)) in linegroups.into_iter().enumerate() {
-            for cell in line {
-                let mut fg = cell.fg;
-                let mut bg = cell.bg;
-                if cell.flags.contains(Flags::INVERSE) {
-                    mem::swap(&mut fg, &mut bg);
-                }
-
-                //Expand background rect range
-                {
-                    if matches!(bg, Named(NamedColor::Background)) {
-                        //Continue to next cell, resetting variables if necessary
-                        cur_alac_color = None;
-                        if let Some(rect) = cur_rect {
-                            rects.push(rect);
-                            cur_rect = None
-                        }
-                    } else {
-                        match cur_alac_color {
-                            Some(cur_color) => {
-                                if bg == cur_color {
-                                    cur_rect = cur_rect.take().map(|rect| rect.extend());
-                                } else {
-                                    cur_alac_color = Some(bg);
-                                    if cur_rect.is_some() {
-                                        rects.push(cur_rect.take().unwrap());
-                                    }
-                                    cur_rect = Some(LayoutRect::new(
-                                        AlacPoint::new(
-                                            line_index as i32,
-                                            cell.point.column.0 as i32,
-                                        ),
-                                        1,
-                                        convert_color(&bg, theme),
-                                    ));
-                                }
-                            }
-                            None => {
-                                cur_alac_color = Some(bg);
-                                cur_rect = Some(LayoutRect::new(
-                                    AlacPoint::new(line_index as i32, cell.point.column.0 as i32),
-                                    1,
-                                    convert_color(&bg, &theme),
-                                ));
-                            }
-                        }
-                    }
-                }
-
-                //Layout current cell text
-                {
-                    let cell_text = cell.c.to_string();
-                    if !is_blank(&cell) {
-                        let cell_style =
-                            TerminalElement::cell_style(&cell, fg, theme, text_style, hyperlink);
-
-                        let layout_cell = text_system
-                            .shape_line(
-                                cell_text.into(),
-                                text_style.font_size.to_pixels(cx.rem_size()),
-                                &[cell_style],
-                            )
-                            .unwrap();
-
-                        cells.push(LayoutCell::new(
-                            AlacPoint::new(line_index as i32, cell.point.column.0 as i32),
-                            layout_cell,
-                        ))
-                    };
-                }
-            }
-
-            if cur_rect.is_some() {
-                rects.push(cur_rect.take().unwrap());
-            }
-        }
-        (cells, rects)
-    }
-
-    // Compute the cursor position and expected block width, may return a zero width if x_for_index returns
-    // the same position for sequential indexes. Use em_width instead
-    fn shape_cursor(
-        cursor_point: DisplayCursor,
-        size: TerminalSize,
-        text_fragment: &ShapedLine,
-    ) -> Option<(Point<Pixels>, Pixels)> {
-        if cursor_point.line() < size.total_lines() as i32 {
-            let cursor_width = if text_fragment.width == Pixels::ZERO {
-                size.cell_width()
-            } else {
-                text_fragment.width
-            };
-
-            // Cursor should always surround as much of the text as possible,
-            // hence when on pixel boundaries round the origin down and the width up
-            Some((
-                point(
-                    (cursor_point.col() as f32 * size.cell_width()).floor(),
-                    (cursor_point.line() as f32 * size.line_height()).floor(),
-                ),
-                cursor_width.ceil(),
-            ))
-        } else {
-            None
-        }
-    }
-
-    /// Convert the Alacritty cell styles to GPUI text styles and background color
-    fn cell_style(
-        indexed: &IndexedCell,
-        fg: terminal::alacritty_terminal::ansi::Color,
-        // bg: terminal::alacritty_terminal::ansi::Color,
-        colors: &Theme,
-        text_style: &TextStyle,
-        hyperlink: Option<(HighlightStyle, &RangeInclusive<AlacPoint>)>,
-    ) -> TextRun {
-        let flags = indexed.cell.flags;
-        let fg = convert_color(&fg, &colors);
-        // let bg = convert_color(&bg, &colors);
-
-        let underline = (flags.intersects(Flags::ALL_UNDERLINES)
-            || indexed.cell.hyperlink().is_some())
-        .then(|| UnderlineStyle {
-            color: Some(fg),
-            thickness: Pixels::from(1.0),
-            wavy: flags.contains(Flags::UNDERCURL),
-        });
-
-        let weight = if flags.intersects(Flags::BOLD | Flags::DIM_BOLD) {
-            FontWeight::BOLD
-        } else {
-            FontWeight::NORMAL
-        };
-
-        let style = if flags.intersects(Flags::ITALIC) {
-            FontStyle::Italic
-        } else {
-            FontStyle::Normal
-        };
-
-        let mut result = TextRun {
-            len: indexed.c.len_utf8() as usize,
-            color: fg,
-            background_color: None,
-            font: Font {
-                weight,
-                style,
-                ..text_style.font()
-            },
-            underline,
-        };
-
-        if let Some((style, range)) = hyperlink {
-            if range.contains(&indexed.point) {
-                if let Some(underline) = style.underline {
-                    result.underline = Some(underline);
-                }
-
-                if let Some(color) = style.color {
-                    result.color = color;
-                }
-            }
-        }
-
-        result
-    }
-
-    fn compute_layout(&self, bounds: Bounds<gpui::Pixels>, cx: &mut WindowContext) -> LayoutState {
-        let settings = ThemeSettings::get_global(cx).clone();
-
-        let buffer_font_size = settings.buffer_font_size(cx);
-
-        let terminal_settings = TerminalSettings::get_global(cx);
-        let font_family = terminal_settings
-            .font_family
-            .as_ref()
-            .map(|string| string.clone().into())
-            .unwrap_or(settings.buffer_font.family);
-
-        let font_features = terminal_settings
-            .font_features
-            .clone()
-            .unwrap_or(settings.buffer_font.features.clone());
-
-        let line_height = terminal_settings.line_height.value();
-        let font_size = terminal_settings.font_size.clone();
-
-        let font_size =
-            font_size.map_or(buffer_font_size, |size| theme::adjusted_font_size(size, cx));
-
-        let theme = cx.theme().clone();
-
-        let link_style = HighlightStyle {
-            color: Some(theme.colors().link_text_hover),
-            font_weight: None,
-            font_style: None,
-            background_color: None,
-            underline: Some(UnderlineStyle {
-                thickness: px(1.0),
-                color: Some(theme.colors().link_text_hover),
-                wavy: false,
-            }),
-            fade_out: None,
-        };
-
-        let text_style = TextStyle {
-            font_family,
-            font_features,
-            font_size: font_size.into(),
-            font_style: FontStyle::Normal,
-            line_height: line_height.into(),
-            background_color: None,
-            white_space: WhiteSpace::Normal,
-            // These are going to be overridden per-cell
-            underline: None,
-            color: theme.colors().text,
-            font_weight: FontWeight::NORMAL,
-        };
-
-        let text_system = cx.text_system();
-        let selection_color = theme.players().local();
-        let match_color = theme.colors().search_match_background;
-        let gutter;
-        let dimensions = {
-            let rem_size = cx.rem_size();
-            let font_pixels = text_style.font_size.to_pixels(rem_size);
-            let line_height = font_pixels * line_height.to_pixels(rem_size);
-            let font_id = cx.text_system().font_id(&text_style.font()).unwrap();
-
-            // todo!(do we need to keep this unwrap?)
-            let cell_width = text_system
-                .advance(font_id, font_pixels, 'm')
-                .unwrap()
-                .width;
-            gutter = cell_width;
-
-            let mut size = bounds.size.clone();
-            size.width -= gutter;
-
-            TerminalSize::new(line_height, cell_width, size)
-        };
-
-        let search_matches = self.terminal.read(cx).matches.clone();
-
-        let background_color = theme.colors().terminal_background;
-
-        let last_hovered_word = self.terminal.update(cx, |terminal, cx| {
-            terminal.set_size(dimensions);
-            terminal.try_sync(cx);
-            if self.can_navigate_to_selected_word && terminal.can_navigate_to_selected_word() {
-                terminal.last_content.last_hovered_word.clone()
-            } else {
-                None
-            }
-        });
-
-        let hyperlink_tooltip = last_hovered_word.clone().map(|hovered_word| {
-            div()
-                .size_full()
-                .id("terminal-element")
-                .tooltip(move |cx| Tooltip::text(hovered_word.word.clone(), cx))
-                .into_any_element()
-        });
-
-        let TerminalContent {
-            cells,
-            mode,
-            display_offset,
-            cursor_char,
-            selection,
-            cursor,
-            ..
-        } = &self.terminal.read(cx).last_content;
-
-        // searches, highlights to a single range representations
-        let mut relative_highlighted_ranges = Vec::new();
-        for search_match in search_matches {
-            relative_highlighted_ranges.push((search_match, match_color))
-        }
-        if let Some(selection) = selection {
-            relative_highlighted_ranges
-                .push((selection.start..=selection.end, selection_color.cursor));
-        }
-
-        // then have that representation be converted to the appropriate highlight data structure
-
-        let (cells, rects) = TerminalElement::layout_grid(
-            cells,
-            &text_style,
-            &cx.text_system(),
-            last_hovered_word
-                .as_ref()
-                .map(|last_hovered_word| (link_style, &last_hovered_word.word_match)),
-            cx,
-        );
-
-        //Layout cursor. Rectangle is used for IME, so we should lay it out even
-        //if we don't end up showing it.
-        let cursor = if let AlacCursorShape::Hidden = cursor.shape {
-            None
-        } else {
-            let cursor_point = DisplayCursor::from(cursor.point, *display_offset);
-            let cursor_text = {
-                let str_trxt = cursor_char.to_string();
-                let len = str_trxt.len();
-                cx.text_system()
-                    .shape_line(
-                        str_trxt.into(),
-                        text_style.font_size.to_pixels(cx.rem_size()),
-                        &[TextRun {
-                            len,
-                            font: text_style.font(),
-                            color: theme.colors().terminal_background,
-                            background_color: None,
-                            underline: Default::default(),
-                        }],
-                    )
-                    //todo!(do we need to keep this unwrap?)
-                    .unwrap()
-            };
-
-            let focused = self.focused;
-            TerminalElement::shape_cursor(cursor_point, dimensions, &cursor_text).map(
-                move |(cursor_position, block_width)| {
-                    let (shape, text) = match cursor.shape {
-                        AlacCursorShape::Block if !focused => (CursorShape::Hollow, None),
-                        AlacCursorShape::Block => (CursorShape::Block, Some(cursor_text)),
-                        AlacCursorShape::Underline => (CursorShape::Underscore, None),
-                        AlacCursorShape::Beam => (CursorShape::Bar, None),
-                        AlacCursorShape::HollowBlock => (CursorShape::Hollow, None),
-                        //This case is handled in the if wrapping the whole cursor layout
-                        AlacCursorShape::Hidden => unreachable!(),
-                    };
-
-                    Cursor::new(
-                        cursor_position,
-                        block_width,
-                        dimensions.line_height,
-                        theme.players().local().cursor,
-                        shape,
-                        text,
-                    )
-                },
-            )
-        };
-
-        //Done!
-        LayoutState {
-            cells,
-            cursor,
-            background_color,
-            dimensions,
-            rects,
-            relative_highlighted_ranges,
-            mode: *mode,
-            display_offset: *display_offset,
-            hyperlink_tooltip,
-            gutter,
-        }
-    }
-
-    fn generic_button_handler<E>(
-        connection: Model<Terminal>,
-        origin: Point<Pixels>,
-        focus_handle: FocusHandle,
-        f: impl Fn(&mut Terminal, Point<Pixels>, &E, &mut ModelContext<Terminal>),
-    ) -> impl Fn(&E, &mut WindowContext) {
-        move |event, cx| {
-            cx.focus(&focus_handle);
-            connection.update(cx, |terminal, cx| {
-                f(terminal, origin, event, cx);
-
-                cx.notify();
-            })
-        }
-    }
-
-    fn register_key_listeners(&self, cx: &mut WindowContext) {
-        cx.on_key_event({
-            let this = self.terminal.clone();
-            move |event: &ModifiersChangedEvent, phase, cx| {
-                if phase != DispatchPhase::Bubble {
-                    return;
-                }
-
-                let handled =
-                    this.update(cx, |term, _| term.try_modifiers_change(&event.modifiers));
-
-                if handled {
-                    cx.notify();
-                }
-            }
-        });
-    }
-
-    fn register_mouse_listeners(
-        &mut self,
-        origin: Point<Pixels>,
-        mode: TermMode,
-        bounds: Bounds<Pixels>,
-        cx: &mut WindowContext,
-    ) {
-        let focus = self.focus.clone();
-        let terminal = self.terminal.clone();
-
-        self.interactivity.on_mouse_down(MouseButton::Left, {
-            let terminal = terminal.clone();
-            let focus = focus.clone();
-            move |e, cx| {
-                cx.focus(&focus);
-                //todo!(context menu)
-                // v.context_menu.update(cx, |menu, _cx| menu.delay_cancel());
-                terminal.update(cx, |terminal, cx| {
-                    terminal.mouse_down(&e, origin);
-
-                    cx.notify();
-                })
-            }
-        });
-        self.interactivity.on_mouse_move({
-            let terminal = terminal.clone();
-            let focus = focus.clone();
-            move |e, cx| {
-                if e.pressed_button.is_some() && focus.is_focused(cx) && !cx.has_active_drag() {
-                    terminal.update(cx, |terminal, cx| {
-                        terminal.mouse_drag(e, origin, bounds);
-                        cx.notify();
-                    })
-                }
-            }
-        });
-        self.interactivity.on_mouse_up(
-            MouseButton::Left,
-            TerminalElement::generic_button_handler(
-                terminal.clone(),
-                origin,
-                focus.clone(),
-                move |terminal, origin, e, cx| {
-                    terminal.mouse_up(&e, origin, cx);
-                },
-            ),
-        );
-        self.interactivity.on_click({
-            let terminal = terminal.clone();
-            move |e, cx| {
-                if e.down.button == MouseButton::Right {
-                    let mouse_mode = terminal.update(cx, |terminal, _cx| {
-                        terminal.mouse_mode(e.down.modifiers.shift)
-                    });
-
-                    if !mouse_mode {
-                        //todo!(context menu)
-                        // view.deploy_context_menu(e.position, cx);
-                    }
-                }
-            }
-        });
-
-        self.interactivity.on_mouse_move({
-            let terminal = terminal.clone();
-            let focus = focus.clone();
-            move |e, cx| {
-                if focus.is_focused(cx) {
-                    terminal.update(cx, |terminal, cx| {
-                        terminal.mouse_move(&e, origin);
-                        cx.notify();
-                    })
-                }
-            }
-        });
-        self.interactivity.on_scroll_wheel({
-            let terminal = terminal.clone();
-            move |e, cx| {
-                terminal.update(cx, |terminal, cx| {
-                    terminal.scroll_wheel(e, origin);
-                    cx.notify();
-                })
-            }
-        });
-
-        self.interactivity.drag_over_styles.push((
-            TypeId::of::<ExternalPaths>(),
-            StyleRefinement::default().bg(cx.theme().colors().drop_target_background),
-        ));
-        self.interactivity.on_drop::<ExternalPaths>({
-            let focus = focus.clone();
-            let terminal = terminal.clone();
-            move |external_paths, cx| {
-                cx.focus(&focus);
-                let mut new_text = external_paths
-                    .paths()
-                    .iter()
-                    .map(|path| format!(" {path:?}"))
-                    .join("");
-                new_text.push(' ');
-                terminal.update(cx, |terminal, _| {
-                    // todo!() long paths are not displayed properly albeit the text is there
-                    terminal.paste(&new_text);
-                });
-            }
-        });
-
-        // Mouse mode handlers:
-        // All mouse modes need the extra click handlers
-        if mode.intersects(TermMode::MOUSE_MODE) {
-            self.interactivity.on_mouse_down(
-                MouseButton::Right,
-                TerminalElement::generic_button_handler(
-                    terminal.clone(),
-                    origin,
-                    focus.clone(),
-                    move |terminal, origin, e, _cx| {
-                        terminal.mouse_down(&e, origin);
-                    },
-                ),
-            );
-            self.interactivity.on_mouse_down(
-                MouseButton::Middle,
-                TerminalElement::generic_button_handler(
-                    terminal.clone(),
-                    origin,
-                    focus.clone(),
-                    move |terminal, origin, e, _cx| {
-                        terminal.mouse_down(&e, origin);
-                    },
-                ),
-            );
-            self.interactivity.on_mouse_up(
-                MouseButton::Right,
-                TerminalElement::generic_button_handler(
-                    terminal.clone(),
-                    origin,
-                    focus.clone(),
-                    move |terminal, origin, e, cx| {
-                        terminal.mouse_up(&e, origin, cx);
-                    },
-                ),
-            );
-            self.interactivity.on_mouse_up(
-                MouseButton::Middle,
-                TerminalElement::generic_button_handler(
-                    terminal,
-                    origin,
-                    focus,
-                    move |terminal, origin, e, cx| {
-                        terminal.mouse_up(&e, origin, cx);
-                    },
-                ),
-            );
-        }
-    }
-}
-
-impl Element for TerminalElement {
-    type State = InteractiveElementState;
-
-    fn request_layout(
-        &mut self,
-        element_state: Option<Self::State>,
-        cx: &mut WindowContext<'_>,
-    ) -> (LayoutId, Self::State) {
-        let (layout_id, interactive_state) =
-            self.interactivity
-                .layout(element_state, cx, |mut style, cx| {
-                    style.size.width = relative(1.).into();
-                    style.size.height = relative(1.).into();
-                    let layout_id = cx.request_layout(&style, None);
-
-                    layout_id
-                });
-
-        (layout_id, interactive_state)
-    }
-
-    fn paint(
-        &mut self,
-        bounds: Bounds<Pixels>,
-        state: &mut Self::State,
-        cx: &mut WindowContext<'_>,
-    ) {
-        let mut layout = self.compute_layout(bounds, cx);
-
-        cx.paint_quad(fill(bounds, layout.background_color));
-        let origin = bounds.origin + Point::new(layout.gutter, px(0.));
-
-        let terminal_input_handler = TerminalInputHandler {
-            cx: cx.to_async(),
-            terminal: self.terminal.clone(),
-            cursor_bounds: layout
-                .cursor
-                .as_ref()
-                .map(|cursor| cursor.bounding_rect(origin)),
-        };
-
-        self.register_mouse_listeners(origin, layout.mode, bounds, cx);
-
-        let mut interactivity = mem::take(&mut self.interactivity);
-        interactivity.paint(bounds, bounds.size, state, cx, |_, _, cx| {
-            cx.handle_input(&self.focus, terminal_input_handler);
-
-            self.register_key_listeners(cx);
-
-            for rect in &layout.rects {
-                rect.paint(origin, &layout, cx);
-            }
-
-            cx.with_z_index(1, |cx| {
-                for (relative_highlighted_range, color) in layout.relative_highlighted_ranges.iter()
-                {
-                    if let Some((start_y, highlighted_range_lines)) =
-                        to_highlighted_range_lines(relative_highlighted_range, &layout, origin)
-                    {
-                        let hr = HighlightedRange {
-                            start_y, //Need to change this
-                            line_height: layout.dimensions.line_height,
-                            lines: highlighted_range_lines,
-                            color: color.clone(),
-                            //Copied from editor. TODO: move to theme or something
-                            corner_radius: 0.15 * layout.dimensions.line_height,
-                        };
-                        hr.paint(bounds, cx);
-                    }
-                }
-            });
-
-            cx.with_z_index(2, |cx| {
-                for cell in &layout.cells {
-                    cell.paint(origin, &layout, bounds, cx);
-                }
-            });
-
-            if self.cursor_visible {
-                cx.with_z_index(3, |cx| {
-                    if let Some(cursor) = &layout.cursor {
-                        cursor.paint(origin, cx);
-                    }
-                });
-            }
-
-            if let Some(mut element) = layout.hyperlink_tooltip.take() {
-                element.draw(origin, bounds.size.map(AvailableSpace::Definite), cx)
-            }
-        });
-    }
-}
-
-impl IntoElement for TerminalElement {
-    type Element = Self;
-
-    fn element_id(&self) -> Option<ElementId> {
-        Some("terminal".into())
-    }
-
-    fn into_element(self) -> Self::Element {
-        self
-    }
-}
-
-struct TerminalInputHandler {
-    cx: AsyncWindowContext,
-    terminal: Model<Terminal>,
-    cursor_bounds: Option<Bounds<Pixels>>,
-}
-
-impl PlatformInputHandler for TerminalInputHandler {
-    fn selected_text_range(&mut self) -> Option<std::ops::Range<usize>> {
-        self.cx
-            .update(|_, cx| {
-                if self
-                    .terminal
-                    .read(cx)
-                    .last_content
-                    .mode
-                    .contains(TermMode::ALT_SCREEN)
-                {
-                    None
-                } else {
-                    Some(0..0)
-                }
-            })
-            .ok()
-            .flatten()
-    }
-
-    fn marked_text_range(&mut self) -> Option<std::ops::Range<usize>> {
-        None
-    }
-
-    fn text_for_range(&mut self, _: std::ops::Range<usize>) -> Option<String> {
-        None
-    }
-
-    fn replace_text_in_range(
-        &mut self,
-        _replacement_range: Option<std::ops::Range<usize>>,
-        text: &str,
-    ) {
-        self.cx
-            .update(|_, cx| {
-                self.terminal.update(cx, |terminal, _| {
-                    terminal.input(text.into());
-                })
-            })
-            .ok();
-    }
-
-    fn replace_and_mark_text_in_range(
-        &mut self,
-        _range_utf16: Option<std::ops::Range<usize>>,
-        _new_text: &str,
-        _new_selected_range: Option<std::ops::Range<usize>>,
-    ) {
-    }
-
-    fn unmark_text(&mut self) {}
-
-    fn bounds_for_range(&mut self, _range_utf16: std::ops::Range<usize>) -> Option<Bounds<Pixels>> {
-        self.cursor_bounds
-    }
-}
-
-fn is_blank(cell: &IndexedCell) -> bool {
-    if cell.c != ' ' {
-        return false;
-    }
-
-    if cell.bg != AnsiColor::Named(NamedColor::Background) {
-        return false;
-    }
-
-    if cell.hyperlink().is_some() {
-        return false;
-    }
-
-    if cell
-        .flags
-        .intersects(Flags::ALL_UNDERLINES | Flags::INVERSE | Flags::STRIKEOUT)
-    {
-        return false;
-    }
-
-    return true;
-}
-
-fn to_highlighted_range_lines(
-    range: &RangeInclusive<AlacPoint>,
-    layout: &LayoutState,
-    origin: Point<Pixels>,
-) -> Option<(Pixels, Vec<HighlightedRangeLine>)> {
-    // Step 1. Normalize the points to be viewport relative.
-    // When display_offset = 1, here's how the grid is arranged:
-    //-2,0 -2,1...
-    //--- Viewport top
-    //-1,0 -1,1...
-    //--------- Terminal Top
-    // 0,0  0,1...
-    // 1,0  1,1...
-    //--- Viewport Bottom
-    // 2,0  2,1...
-    //--------- Terminal Bottom
-
-    // Normalize to viewport relative, from terminal relative.
-    // lines are i32s, which are negative above the top left corner of the terminal
-    // If the user has scrolled, we use the display_offset to tell us which offset
-    // of the grid data we should be looking at. But for the rendering step, we don't
-    // want negatives. We want things relative to the 'viewport' (the area of the grid
-    // which is currently shown according to the display offset)
-    let unclamped_start = AlacPoint::new(
-        range.start().line + layout.display_offset,
-        range.start().column,
-    );
-    let unclamped_end =
-        AlacPoint::new(range.end().line + layout.display_offset, range.end().column);
-
-    // Step 2. Clamp range to viewport, and return None if it doesn't overlap
-    if unclamped_end.line.0 < 0 || unclamped_start.line.0 > layout.dimensions.num_lines() as i32 {
-        return None;
-    }
-
-    let clamped_start_line = unclamped_start.line.0.max(0) as usize;
-    let clamped_end_line = unclamped_end
-        .line
-        .0
-        .min(layout.dimensions.num_lines() as i32) as usize;
-    //Convert the start of the range to pixels
-    let start_y = origin.y + clamped_start_line as f32 * layout.dimensions.line_height;
-
-    // Step 3. Expand ranges that cross lines into a collection of single-line ranges.
-    //  (also convert to pixels)
-    let mut highlighted_range_lines = Vec::new();
-    for line in clamped_start_line..=clamped_end_line {
-        let mut line_start = 0;
-        let mut line_end = layout.dimensions.columns();
-
-        if line == clamped_start_line {
-            line_start = unclamped_start.column.0 as usize;
-        }
-        if line == clamped_end_line {
-            line_end = unclamped_end.column.0 as usize + 1; //+1 for inclusive
-        }
-
-        highlighted_range_lines.push(HighlightedRangeLine {
-            start_x: origin.x + line_start as f32 * layout.dimensions.cell_width,
-            end_x: origin.x + line_end as f32 * layout.dimensions.cell_width,
-        });
-    }
-
-    Some((start_y, highlighted_range_lines))
-}
-
-///Converts a 2, 8, or 24 bit color ANSI color to the GPUI equivalent
-fn convert_color(fg: &terminal::alacritty_terminal::ansi::Color, theme: &Theme) -> Hsla {
-    let colors = theme.colors();
-    match fg {
-        //Named and theme defined colors
-        terminal::alacritty_terminal::ansi::Color::Named(n) => match n {
-            NamedColor::Black => colors.terminal_ansi_black,
-            NamedColor::Red => colors.terminal_ansi_red,
-            NamedColor::Green => colors.terminal_ansi_green,
-            NamedColor::Yellow => colors.terminal_ansi_yellow,
-            NamedColor::Blue => colors.terminal_ansi_blue,
-            NamedColor::Magenta => colors.terminal_ansi_magenta,
-            NamedColor::Cyan => colors.terminal_ansi_cyan,
-            NamedColor::White => colors.terminal_ansi_white,
-            NamedColor::BrightBlack => colors.terminal_ansi_bright_black,
-            NamedColor::BrightRed => colors.terminal_ansi_bright_red,
-            NamedColor::BrightGreen => colors.terminal_ansi_bright_green,
-            NamedColor::BrightYellow => colors.terminal_ansi_bright_yellow,
-            NamedColor::BrightBlue => colors.terminal_ansi_bright_blue,
-            NamedColor::BrightMagenta => colors.terminal_ansi_bright_magenta,
-            NamedColor::BrightCyan => colors.terminal_ansi_bright_cyan,
-            NamedColor::BrightWhite => colors.terminal_ansi_bright_white,
-            NamedColor::Foreground => colors.text,
-            NamedColor::Background => colors.background,
-            NamedColor::Cursor => theme.players().local().cursor,
-
-            // todo!(more colors)
-            NamedColor::DimBlack => red(),
-            NamedColor::DimRed => red(),
-            NamedColor::DimGreen => red(),
-            NamedColor::DimYellow => red(),
-            NamedColor::DimBlue => red(),
-            NamedColor::DimMagenta => red(),
-            NamedColor::DimCyan => red(),
-            NamedColor::DimWhite => red(),
-            NamedColor::BrightForeground => red(),
-            NamedColor::DimForeground => red(),
-        },
-        //'True' colors
-        terminal::alacritty_terminal::ansi::Color::Spec(rgb) => {
-            terminal::rgba_color(rgb.r, rgb.g, rgb.b)
-        }
-        //8 bit, indexed colors
-        terminal::alacritty_terminal::ansi::Color::Indexed(i) => {
-            terminal::get_color_at_index(*i as usize, theme)
-        }
-    }
-}

crates/terminal_view2/src/terminal_panel.rs 🔗

@@ -1,437 +0,0 @@
-use std::{path::PathBuf, sync::Arc};
-
-use crate::TerminalView;
-use db::kvp::KEY_VALUE_STORE;
-use gpui::{
-    actions, div, serde_json, AppContext, AsyncWindowContext, Entity, EventEmitter, ExternalPaths,
-    FocusHandle, FocusableView, IntoElement, ParentElement, Pixels, Render, Styled, Subscription,
-    Task, View, ViewContext, VisualContext, WeakView, WindowContext,
-};
-use project::Fs;
-use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsStore};
-use terminal::terminal_settings::{TerminalDockPosition, TerminalSettings};
-use ui::{h_stack, ButtonCommon, Clickable, IconButton, IconSize, Selectable, Tooltip};
-use util::{ResultExt, TryFutureExt};
-use workspace::{
-    dock::{DockPosition, Panel, PanelEvent},
-    item::Item,
-    pane,
-    ui::Icon,
-    Pane, Workspace,
-};
-
-use anyhow::Result;
-
-const TERMINAL_PANEL_KEY: &'static str = "TerminalPanel";
-
-actions!(terminal_panel, [ToggleFocus]);
-
-pub fn init(cx: &mut AppContext) {
-    cx.observe_new_views(
-        |workspace: &mut Workspace, _: &mut ViewContext<Workspace>| {
-            workspace.register_action(TerminalPanel::new_terminal);
-            workspace.register_action(TerminalPanel::open_terminal);
-            workspace.register_action(|workspace, _: &ToggleFocus, cx| {
-                workspace.toggle_panel_focus::<TerminalPanel>(cx);
-            });
-        },
-    )
-    .detach();
-}
-
-pub struct TerminalPanel {
-    pane: View<Pane>,
-    fs: Arc<dyn Fs>,
-    workspace: WeakView<Workspace>,
-    width: Option<Pixels>,
-    height: Option<Pixels>,
-    pending_serialization: Task<Option<()>>,
-    _subscriptions: Vec<Subscription>,
-}
-
-impl TerminalPanel {
-    fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
-        let terminal_panel = cx.view().clone();
-        let pane = cx.new_view(|cx| {
-            let mut pane = Pane::new(
-                workspace.weak_handle(),
-                workspace.project().clone(),
-                Default::default(),
-                Some(Arc::new(|a, cx| {
-                    if let Some(tab) = a.downcast_ref::<workspace::pane::DraggedTab>() {
-                        if let Some(item) = tab.pane.read(cx).item_for_index(tab.ix) {
-                            return item.downcast::<TerminalView>().is_some();
-                        }
-                    }
-                    if a.downcast_ref::<ExternalPaths>().is_some() {
-                        return true;
-                    }
-
-                    false
-                })),
-                cx,
-            );
-            pane.set_can_split(false, cx);
-            pane.set_can_navigate(false, cx);
-            pane.display_nav_history_buttons(false);
-            pane.set_render_tab_bar_buttons(cx, move |pane, cx| {
-                h_stack()
-                    .gap_2()
-                    .child(
-                        IconButton::new("plus", Icon::Plus)
-                            .icon_size(IconSize::Small)
-                            .on_click(cx.listener_for(&terminal_panel, |terminal_panel, _, cx| {
-                                terminal_panel.add_terminal(None, cx);
-                            }))
-                            .tooltip(|cx| Tooltip::text("New Terminal", cx)),
-                    )
-                    .child({
-                        let zoomed = pane.is_zoomed();
-                        IconButton::new("toggle_zoom", Icon::Maximize)
-                            .icon_size(IconSize::Small)
-                            .selected(zoomed)
-                            .selected_icon(Icon::Minimize)
-                            .on_click(cx.listener(|pane, _, cx| {
-                                pane.toggle_zoom(&workspace::ToggleZoom, cx);
-                            }))
-                            .tooltip(move |cx| {
-                                Tooltip::text(if zoomed { "Zoom Out" } else { "Zoom In" }, cx)
-                            })
-                    })
-                    .into_any_element()
-            });
-            // let buffer_search_bar = cx.build_view(search::BufferSearchBar::new);
-            // pane.toolbar()
-            //     .update(cx, |toolbar, cx| toolbar.add_item(buffer_search_bar, cx));
-            pane
-        });
-        let subscriptions = vec![
-            cx.observe(&pane, |_, _, cx| cx.notify()),
-            cx.subscribe(&pane, Self::handle_pane_event),
-        ];
-        let this = Self {
-            pane,
-            fs: workspace.app_state().fs.clone(),
-            workspace: workspace.weak_handle(),
-            pending_serialization: Task::ready(None),
-            width: None,
-            height: None,
-            _subscriptions: subscriptions,
-        };
-        let mut old_dock_position = this.position(cx);
-        cx.observe_global::<SettingsStore>(move |this, cx| {
-            let new_dock_position = this.position(cx);
-            if new_dock_position != old_dock_position {
-                old_dock_position = new_dock_position;
-                cx.emit(PanelEvent::ChangePosition);
-            }
-        })
-        .detach();
-        this
-    }
-
-    pub async fn load(
-        workspace: WeakView<Workspace>,
-        mut cx: AsyncWindowContext,
-    ) -> Result<View<Self>> {
-        let serialized_panel = cx
-            .background_executor()
-            .spawn(async move { KEY_VALUE_STORE.read_kvp(TERMINAL_PANEL_KEY) })
-            .await
-            .log_err()
-            .flatten()
-            .map(|panel| serde_json::from_str::<SerializedTerminalPanel>(&panel))
-            .transpose()
-            .log_err()
-            .flatten();
-
-        let (panel, pane, items) = workspace.update(&mut cx, |workspace, cx| {
-            let panel = cx.new_view(|cx| TerminalPanel::new(workspace, cx));
-            let items = if let Some(serialized_panel) = serialized_panel.as_ref() {
-                panel.update(cx, |panel, cx| {
-                    cx.notify();
-                    panel.height = serialized_panel.height;
-                    panel.width = serialized_panel.width;
-                    panel.pane.update(cx, |_, cx| {
-                        serialized_panel
-                            .items
-                            .iter()
-                            .map(|item_id| {
-                                TerminalView::deserialize(
-                                    workspace.project().clone(),
-                                    workspace.weak_handle(),
-                                    workspace.database_id(),
-                                    *item_id,
-                                    cx,
-                                )
-                            })
-                            .collect::<Vec<_>>()
-                    })
-                })
-            } else {
-                Default::default()
-            };
-            let pane = panel.read(cx).pane.clone();
-            (panel, pane, items)
-        })?;
-
-        let pane = pane.downgrade();
-        let items = futures::future::join_all(items).await;
-        pane.update(&mut cx, |pane, cx| {
-            let active_item_id = serialized_panel
-                .as_ref()
-                .and_then(|panel| panel.active_item_id);
-            let mut active_ix = None;
-            for item in items {
-                if let Some(item) = item.log_err() {
-                    let item_id = item.entity_id().as_u64();
-                    pane.add_item(Box::new(item), false, false, None, cx);
-                    if Some(item_id) == active_item_id {
-                        active_ix = Some(pane.items_len() - 1);
-                    }
-                }
-            }
-
-            if let Some(active_ix) = active_ix {
-                pane.activate_item(active_ix, false, false, cx)
-            }
-        })?;
-
-        Ok(panel)
-    }
-
-    fn handle_pane_event(
-        &mut self,
-        _pane: View<Pane>,
-        event: &pane::Event,
-        cx: &mut ViewContext<Self>,
-    ) {
-        match event {
-            pane::Event::ActivateItem { .. } => self.serialize(cx),
-            pane::Event::RemoveItem { .. } => self.serialize(cx),
-            pane::Event::Remove => cx.emit(PanelEvent::Close),
-            pane::Event::ZoomIn => cx.emit(PanelEvent::ZoomIn),
-            pane::Event::ZoomOut => cx.emit(PanelEvent::ZoomOut),
-            pane::Event::Focus => cx.emit(PanelEvent::Focus),
-
-            pane::Event::AddItem { item } => {
-                if let Some(workspace) = self.workspace.upgrade() {
-                    let pane = self.pane.clone();
-                    workspace.update(cx, |workspace, cx| item.added_to_pane(workspace, pane, cx))
-                }
-            }
-
-            _ => {}
-        }
-    }
-
-    pub fn open_terminal(
-        workspace: &mut Workspace,
-        action: &workspace::OpenTerminal,
-        cx: &mut ViewContext<Workspace>,
-    ) {
-        let Some(this) = workspace.focus_panel::<Self>(cx) else {
-            return;
-        };
-
-        this.update(cx, |this, cx| {
-            this.add_terminal(Some(action.working_directory.clone()), cx)
-        })
-    }
-
-    ///Create a new Terminal in the current working directory or the user's home directory
-    fn new_terminal(
-        workspace: &mut Workspace,
-        _: &workspace::NewTerminal,
-        cx: &mut ViewContext<Workspace>,
-    ) {
-        let Some(this) = workspace.focus_panel::<Self>(cx) else {
-            return;
-        };
-
-        this.update(cx, |this, cx| this.add_terminal(None, cx))
-    }
-
-    fn add_terminal(&mut self, working_directory: Option<PathBuf>, cx: &mut ViewContext<Self>) {
-        let workspace = self.workspace.clone();
-        cx.spawn(|this, mut cx| async move {
-            let pane = this.update(&mut cx, |this, _| this.pane.clone())?;
-            workspace.update(&mut cx, |workspace, cx| {
-                let working_directory = if let Some(working_directory) = working_directory {
-                    Some(working_directory)
-                } else {
-                    let working_directory_strategy =
-                        TerminalSettings::get_global(cx).working_directory.clone();
-                    crate::get_working_directory(workspace, cx, working_directory_strategy)
-                };
-
-                let window = cx.window_handle();
-                if let Some(terminal) = workspace.project().update(cx, |project, cx| {
-                    project
-                        .create_terminal(working_directory, window, cx)
-                        .log_err()
-                }) {
-                    let terminal = Box::new(cx.new_view(|cx| {
-                        TerminalView::new(
-                            terminal,
-                            workspace.weak_handle(),
-                            workspace.database_id(),
-                            cx,
-                        )
-                    }));
-                    pane.update(cx, |pane, cx| {
-                        let focus = pane.has_focus(cx);
-                        pane.add_item(terminal, true, focus, None, cx);
-                    });
-                }
-            })?;
-            this.update(&mut cx, |this, cx| this.serialize(cx))?;
-            anyhow::Ok(())
-        })
-        .detach_and_log_err(cx);
-    }
-
-    fn serialize(&mut self, cx: &mut ViewContext<Self>) {
-        let items = self
-            .pane
-            .read(cx)
-            .items()
-            .map(|item| item.item_id().as_u64())
-            .collect::<Vec<_>>();
-        let active_item_id = self
-            .pane
-            .read(cx)
-            .active_item()
-            .map(|item| item.item_id().as_u64());
-        let height = self.height;
-        let width = self.width;
-        self.pending_serialization = cx.background_executor().spawn(
-            async move {
-                KEY_VALUE_STORE
-                    .write_kvp(
-                        TERMINAL_PANEL_KEY.into(),
-                        serde_json::to_string(&SerializedTerminalPanel {
-                            items,
-                            active_item_id,
-                            height,
-                            width,
-                        })?,
-                    )
-                    .await?;
-                anyhow::Ok(())
-            }
-            .log_err(),
-        );
-    }
-}
-
-impl EventEmitter<PanelEvent> for TerminalPanel {}
-
-impl Render for TerminalPanel {
-    fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
-        div().size_full().child(self.pane.clone())
-    }
-}
-
-impl FocusableView for TerminalPanel {
-    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
-        self.pane.focus_handle(cx)
-    }
-}
-
-impl Panel for TerminalPanel {
-    fn position(&self, cx: &WindowContext) -> DockPosition {
-        match TerminalSettings::get_global(cx).dock {
-            TerminalDockPosition::Left => DockPosition::Left,
-            TerminalDockPosition::Bottom => DockPosition::Bottom,
-            TerminalDockPosition::Right => DockPosition::Right,
-        }
-    }
-
-    fn position_is_valid(&self, _: DockPosition) -> bool {
-        true
-    }
-
-    fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
-        settings::update_settings_file::<TerminalSettings>(self.fs.clone(), cx, move |settings| {
-            let dock = match position {
-                DockPosition::Left => TerminalDockPosition::Left,
-                DockPosition::Bottom => TerminalDockPosition::Bottom,
-                DockPosition::Right => TerminalDockPosition::Right,
-            };
-            settings.dock = Some(dock);
-        });
-    }
-
-    fn size(&self, cx: &WindowContext) -> Pixels {
-        let settings = TerminalSettings::get_global(cx);
-        match self.position(cx) {
-            DockPosition::Left | DockPosition::Right => {
-                self.width.unwrap_or_else(|| settings.default_width)
-            }
-            DockPosition::Bottom => self.height.unwrap_or_else(|| settings.default_height),
-        }
-    }
-
-    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
-        match self.position(cx) {
-            DockPosition::Left | DockPosition::Right => self.width = size,
-            DockPosition::Bottom => self.height = size,
-        }
-        self.serialize(cx);
-        cx.notify();
-    }
-
-    fn is_zoomed(&self, cx: &WindowContext) -> bool {
-        self.pane.read(cx).is_zoomed()
-    }
-
-    fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>) {
-        self.pane.update(cx, |pane, cx| pane.set_zoomed(zoomed, cx));
-    }
-
-    fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
-        if active && self.pane.read(cx).items_len() == 0 {
-            self.add_terminal(None, cx)
-        }
-    }
-
-    fn icon_label(&self, cx: &WindowContext) -> Option<String> {
-        let count = self.pane.read(cx).items_len();
-        if count == 0 {
-            None
-        } else {
-            Some(count.to_string())
-        }
-    }
-
-    fn persistent_name() -> &'static str {
-        "TerminalPanel"
-    }
-
-    // todo!()
-    // fn icon_tooltip(&self) -> (String, Option<Box<dyn Action>>) {
-    //     ("Terminal Panel".into(), Some(Box::new(ToggleFocus)))
-    // }
-
-    fn icon(&self, _cx: &WindowContext) -> Option<Icon> {
-        Some(Icon::Terminal)
-    }
-
-    fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
-        Some("Terminal Panel")
-    }
-
-    fn toggle_action(&self) -> Box<dyn gpui::Action> {
-        Box::new(ToggleFocus)
-    }
-}
-
-#[derive(Serialize, Deserialize)]
-struct SerializedTerminalPanel {
-    items: Vec<u64>,
-    active_item_id: Option<u64>,
-    width: Option<Pixels>,
-    height: Option<Pixels>,
-}

crates/terminal_view2/src/terminal_view.rs 🔗

@@ -1,1134 +0,0 @@
-mod persistence;
-pub mod terminal_element;
-pub mod terminal_panel;
-
-// todo!()
-// use crate::terminal_element::TerminalElement;
-use editor::{scroll::autoscroll::Autoscroll, Editor};
-use gpui::{
-    div, impl_actions, overlay, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle,
-    FocusableView, KeyContext, KeyDownEvent, Keystroke, Model, MouseButton, MouseDownEvent, Pixels,
-    Render, Styled, Subscription, Task, View, VisualContext, WeakView,
-};
-use language::Bias;
-use persistence::TERMINAL_DB;
-use project::{search::SearchQuery, LocalWorktree, Project};
-use terminal::{
-    alacritty_terminal::{
-        index::Point,
-        term::{search::RegexSearch, TermMode},
-    },
-    terminal_settings::{TerminalBlink, TerminalSettings, WorkingDirectory},
-    Clear, Copy, Event, MaybeNavigationTarget, Paste, ShowCharacterPalette, Terminal,
-};
-use terminal_element::TerminalElement;
-use ui::{h_stack, prelude::*, ContextMenu, Icon, IconElement, Label};
-use util::{paths::PathLikeWithPosition, ResultExt};
-use workspace::{
-    item::{BreadcrumbText, Item, ItemEvent},
-    notifications::NotifyResultExt,
-    register_deserializable_item,
-    searchable::{SearchEvent, SearchOptions, SearchableItem},
-    CloseActiveItem, NewCenterTerminal, Pane, ToolbarItemLocation, Workspace, WorkspaceId,
-};
-
-use anyhow::Context;
-use dirs::home_dir;
-use serde::Deserialize;
-use settings::Settings;
-use smol::Timer;
-
-use std::{
-    ops::RangeInclusive,
-    path::{Path, PathBuf},
-    sync::Arc,
-    time::Duration,
-};
-
-const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
-
-///Event to transmit the scroll from the element to the view
-#[derive(Clone, Debug, PartialEq)]
-pub struct ScrollTerminal(pub i32);
-
-#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
-pub struct SendText(String);
-
-#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
-pub struct SendKeystroke(String);
-
-impl_actions!(terminal_view, [SendText, SendKeystroke]);
-
-pub fn init(cx: &mut AppContext) {
-    terminal_panel::init(cx);
-    terminal::init(cx);
-
-    register_deserializable_item::<TerminalView>(cx);
-
-    cx.observe_new_views(|workspace: &mut Workspace, _| {
-        workspace.register_action(TerminalView::deploy);
-    })
-    .detach();
-}
-
-///A terminal view, maintains the PTY's file handles and communicates with the terminal
-pub struct TerminalView {
-    terminal: Model<Terminal>,
-    focus_handle: FocusHandle,
-    has_new_content: bool,
-    //Currently using iTerm bell, show bell emoji in tab until input is received
-    has_bell: bool,
-    context_menu: Option<(View<ContextMenu>, gpui::Point<Pixels>, Subscription)>,
-    blink_state: bool,
-    blinking_on: bool,
-    blinking_paused: bool,
-    blink_epoch: usize,
-    can_navigate_to_selected_word: bool,
-    workspace_id: WorkspaceId,
-    _subscriptions: Vec<Subscription>,
-}
-
-impl EventEmitter<Event> for TerminalView {}
-impl EventEmitter<ItemEvent> for TerminalView {}
-impl EventEmitter<SearchEvent> for TerminalView {}
-
-impl FocusableView for TerminalView {
-    fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
-        self.focus_handle.clone()
-    }
-}
-
-impl TerminalView {
-    ///Create a new Terminal in the current working directory or the user's home directory
-    pub fn deploy(
-        workspace: &mut Workspace,
-        _: &NewCenterTerminal,
-        cx: &mut ViewContext<Workspace>,
-    ) {
-        let strategy = TerminalSettings::get_global(cx);
-        let working_directory =
-            get_working_directory(workspace, cx, strategy.working_directory.clone());
-
-        let window = cx.window_handle();
-        let terminal = workspace
-            .project()
-            .update(cx, |project, cx| {
-                project.create_terminal(working_directory, window, cx)
-            })
-            .notify_err(workspace, cx);
-
-        if let Some(terminal) = terminal {
-            let view = cx.new_view(|cx| {
-                TerminalView::new(
-                    terminal,
-                    workspace.weak_handle(),
-                    workspace.database_id(),
-                    cx,
-                )
-            });
-            workspace.add_item(Box::new(view), cx)
-        }
-    }
-
-    pub fn new(
-        terminal: Model<Terminal>,
-        workspace: WeakView<Workspace>,
-        workspace_id: WorkspaceId,
-        cx: &mut ViewContext<Self>,
-    ) -> Self {
-        cx.observe(&terminal, |_, _, cx| cx.notify()).detach();
-        cx.subscribe(&terminal, move |this, _, event, cx| match event {
-            Event::Wakeup => {
-                if !this.focus_handle.is_focused(cx) {
-                    this.has_new_content = true;
-                }
-                cx.notify();
-                cx.emit(Event::Wakeup);
-                cx.emit(ItemEvent::UpdateTab);
-                cx.emit(SearchEvent::MatchesInvalidated);
-            }
-
-            Event::Bell => {
-                this.has_bell = true;
-                cx.emit(Event::Wakeup);
-            }
-
-            Event::BlinkChanged => this.blinking_on = !this.blinking_on,
-
-            Event::TitleChanged => {
-                cx.emit(ItemEvent::UpdateTab);
-                if let Some(foreground_info) = &this.terminal().read(cx).foreground_process_info {
-                    let cwd = foreground_info.cwd.clone();
-
-                    let item_id = cx.entity_id();
-                    let workspace_id = this.workspace_id;
-                    cx.background_executor()
-                        .spawn(async move {
-                            TERMINAL_DB
-                                .save_working_directory(item_id.as_u64(), workspace_id, cwd)
-                                .await
-                                .log_err();
-                        })
-                        .detach();
-                }
-            }
-
-            Event::NewNavigationTarget(maybe_navigation_target) => {
-                this.can_navigate_to_selected_word = match maybe_navigation_target {
-                    Some(MaybeNavigationTarget::Url(_)) => true,
-                    Some(MaybeNavigationTarget::PathLike(maybe_path)) => {
-                        !possible_open_targets(&workspace, maybe_path, cx).is_empty()
-                    }
-                    None => false,
-                }
-            }
-
-            Event::Open(maybe_navigation_target) => match maybe_navigation_target {
-                MaybeNavigationTarget::Url(url) => cx.open_url(url),
-
-                MaybeNavigationTarget::PathLike(maybe_path) => {
-                    if !this.can_navigate_to_selected_word {
-                        return;
-                    }
-                    let potential_abs_paths = possible_open_targets(&workspace, maybe_path, cx);
-                    if let Some(path) = potential_abs_paths.into_iter().next() {
-                        let is_dir = path.path_like.is_dir();
-                        let task_workspace = workspace.clone();
-                        cx.spawn(|_, mut cx| async move {
-                            let opened_items = task_workspace
-                                .update(&mut cx, |workspace, cx| {
-                                    workspace.open_paths(vec![path.path_like], is_dir, cx)
-                                })
-                                .context("workspace update")?
-                                .await;
-                            anyhow::ensure!(
-                                opened_items.len() == 1,
-                                "For a single path open, expected single opened item"
-                            );
-                            let opened_item = opened_items
-                                .into_iter()
-                                .next()
-                                .unwrap()
-                                .transpose()
-                                .context("path open")?;
-                            if is_dir {
-                                task_workspace.update(&mut cx, |workspace, cx| {
-                                    workspace.project().update(cx, |_, cx| {
-                                        cx.emit(project::Event::ActivateProjectPanel);
-                                    })
-                                })?;
-                            } else {
-                                if let Some(row) = path.row {
-                                    let col = path.column.unwrap_or(0);
-                                    if let Some(active_editor) =
-                                        opened_item.and_then(|item| item.downcast::<Editor>())
-                                    {
-                                        active_editor
-                                            .downgrade()
-                                            .update(&mut cx, |editor, cx| {
-                                                let snapshot = editor.snapshot(cx).display_snapshot;
-                                                let point = snapshot.buffer_snapshot.clip_point(
-                                                    language::Point::new(
-                                                        row.saturating_sub(1),
-                                                        col.saturating_sub(1),
-                                                    ),
-                                                    Bias::Left,
-                                                );
-                                                editor.change_selections(
-                                                    Some(Autoscroll::center()),
-                                                    cx,
-                                                    |s| s.select_ranges([point..point]),
-                                                );
-                                            })
-                                            .log_err();
-                                    }
-                                }
-                            }
-                            anyhow::Ok(())
-                        })
-                        .detach_and_log_err(cx);
-                    }
-                }
-            },
-            Event::BreadcrumbsChanged => cx.emit(ItemEvent::UpdateBreadcrumbs),
-            Event::CloseTerminal => cx.emit(ItemEvent::CloseItem),
-            Event::SelectionsChanged => cx.emit(SearchEvent::ActiveMatchChanged),
-        })
-        .detach();
-
-        let focus_handle = cx.focus_handle();
-        let focus_in = cx.on_focus_in(&focus_handle, |terminal_view, cx| {
-            terminal_view.focus_in(cx);
-        });
-        let focus_out = cx.on_focus_out(&focus_handle, |terminal_view, cx| {
-            terminal_view.focus_out(cx);
-        });
-
-        Self {
-            terminal,
-            has_new_content: true,
-            has_bell: false,
-            focus_handle: cx.focus_handle(),
-            context_menu: None,
-            blink_state: true,
-            blinking_on: false,
-            blinking_paused: false,
-            blink_epoch: 0,
-            can_navigate_to_selected_word: false,
-            workspace_id,
-            _subscriptions: vec![focus_in, focus_out],
-        }
-    }
-
-    pub fn model(&self) -> &Model<Terminal> {
-        &self.terminal
-    }
-
-    pub fn has_new_content(&self) -> bool {
-        self.has_new_content
-    }
-
-    pub fn has_bell(&self) -> bool {
-        self.has_bell
-    }
-
-    pub fn clear_bel(&mut self, cx: &mut ViewContext<TerminalView>) {
-        self.has_bell = false;
-        cx.emit(Event::Wakeup);
-    }
-
-    pub fn deploy_context_menu(
-        &mut self,
-        position: gpui::Point<Pixels>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let context_menu = ContextMenu::build(cx, |menu, _| {
-            menu.action("Clear", Box::new(Clear))
-                .action("Close", Box::new(CloseActiveItem { save_intent: None }))
-        });
-
-        cx.focus_view(&context_menu);
-        let subscription =
-            cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| {
-                if this.context_menu.as_ref().is_some_and(|context_menu| {
-                    context_menu.0.focus_handle(cx).contains_focused(cx)
-                }) {
-                    cx.focus_self();
-                }
-                this.context_menu.take();
-                cx.notify();
-            });
-
-        self.context_menu = Some((context_menu, position, subscription));
-    }
-
-    fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
-        if !self
-            .terminal
-            .read(cx)
-            .last_content
-            .mode
-            .contains(TermMode::ALT_SCREEN)
-        {
-            cx.show_character_palette();
-        } else {
-            self.terminal.update(cx, |term, cx| {
-                term.try_keystroke(
-                    &Keystroke::parse("ctrl-cmd-space").unwrap(),
-                    TerminalSettings::get_global(cx).option_as_meta,
-                )
-            });
-        }
-    }
-
-    fn select_all(&mut self, _: &editor::SelectAll, cx: &mut ViewContext<Self>) {
-        self.terminal.update(cx, |term, _| term.select_all());
-        cx.notify();
-    }
-
-    fn clear(&mut self, _: &Clear, cx: &mut ViewContext<Self>) {
-        self.terminal.update(cx, |term, _| term.clear());
-        cx.notify();
-    }
-
-    pub fn should_show_cursor(&self, focused: bool, cx: &mut gpui::ViewContext<Self>) -> bool {
-        //Don't blink the cursor when not focused, blinking is disabled, or paused
-        if !focused
-            || !self.blinking_on
-            || self.blinking_paused
-            || self
-                .terminal
-                .read(cx)
-                .last_content
-                .mode
-                .contains(TermMode::ALT_SCREEN)
-        {
-            return true;
-        }
-
-        match TerminalSettings::get_global(cx).blinking {
-            //If the user requested to never blink, don't blink it.
-            TerminalBlink::Off => true,
-            //If the terminal is controlling it, check terminal mode
-            TerminalBlink::TerminalControlled | TerminalBlink::On => self.blink_state,
-        }
-    }
-
-    fn blink_cursors(&mut self, epoch: usize, cx: &mut ViewContext<Self>) {
-        if epoch == self.blink_epoch && !self.blinking_paused {
-            self.blink_state = !self.blink_state;
-            cx.notify();
-
-            let epoch = self.next_blink_epoch();
-            cx.spawn(|this, mut cx| async move {
-                Timer::after(CURSOR_BLINK_INTERVAL).await;
-                this.update(&mut cx, |this, cx| this.blink_cursors(epoch, cx))
-                    .log_err();
-            })
-            .detach();
-        }
-    }
-
-    pub fn pause_cursor_blinking(&mut self, cx: &mut ViewContext<Self>) {
-        self.blink_state = true;
-        cx.notify();
-
-        let epoch = self.next_blink_epoch();
-        cx.spawn(|this, mut cx| async move {
-            Timer::after(CURSOR_BLINK_INTERVAL).await;
-            this.update(&mut cx, |this, cx| this.resume_cursor_blinking(epoch, cx))
-                .ok();
-        })
-        .detach();
-    }
-
-    pub fn find_matches(
-        &mut self,
-        query: Arc<project::search::SearchQuery>,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<Vec<RangeInclusive<Point>>> {
-        let searcher = regex_search_for_query(&query);
-
-        if let Some(searcher) = searcher {
-            self.terminal
-                .update(cx, |term, cx| term.find_matches(searcher, cx))
-        } else {
-            cx.background_executor().spawn(async { Vec::new() })
-        }
-    }
-
-    pub fn terminal(&self) -> &Model<Terminal> {
-        &self.terminal
-    }
-
-    fn next_blink_epoch(&mut self) -> usize {
-        self.blink_epoch += 1;
-        self.blink_epoch
-    }
-
-    fn resume_cursor_blinking(&mut self, epoch: usize, cx: &mut ViewContext<Self>) {
-        if epoch == self.blink_epoch {
-            self.blinking_paused = false;
-            self.blink_cursors(epoch, cx);
-        }
-    }
-
-    ///Attempt to paste the clipboard into the terminal
-    fn copy(&mut self, _: &Copy, cx: &mut ViewContext<Self>) {
-        self.terminal.update(cx, |term, _| term.copy())
-    }
-
-    ///Attempt to paste the clipboard into the terminal
-    fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
-        if let Some(item) = cx.read_from_clipboard() {
-            self.terminal
-                .update(cx, |terminal, _cx| terminal.paste(item.text()));
-        }
-    }
-
-    fn send_text(&mut self, text: &SendText, cx: &mut ViewContext<Self>) {
-        self.clear_bel(cx);
-        self.terminal.update(cx, |term, _| {
-            term.input(text.0.to_string());
-        });
-    }
-
-    fn send_keystroke(&mut self, text: &SendKeystroke, cx: &mut ViewContext<Self>) {
-        if let Some(keystroke) = Keystroke::parse(&text.0).log_err() {
-            self.clear_bel(cx);
-            self.terminal.update(cx, |term, cx| {
-                term.try_keystroke(&keystroke, TerminalSettings::get_global(cx).option_as_meta);
-            });
-        }
-    }
-
-    fn dispatch_context(&self, cx: &AppContext) -> KeyContext {
-        let mut dispatch_context = KeyContext::default();
-        dispatch_context.add("Terminal");
-
-        let mode = self.terminal.read(cx).last_content.mode;
-        dispatch_context.set(
-            "screen",
-            if mode.contains(TermMode::ALT_SCREEN) {
-                "alt"
-            } else {
-                "normal"
-            },
-        );
-
-        if mode.contains(TermMode::APP_CURSOR) {
-            dispatch_context.add("DECCKM");
-        }
-        if mode.contains(TermMode::APP_KEYPAD) {
-            dispatch_context.add("DECPAM");
-        } else {
-            dispatch_context.add("DECPNM");
-        }
-        if mode.contains(TermMode::SHOW_CURSOR) {
-            dispatch_context.add("DECTCEM");
-        }
-        if mode.contains(TermMode::LINE_WRAP) {
-            dispatch_context.add("DECAWM");
-        }
-        if mode.contains(TermMode::ORIGIN) {
-            dispatch_context.add("DECOM");
-        }
-        if mode.contains(TermMode::INSERT) {
-            dispatch_context.add("IRM");
-        }
-        //LNM is apparently the name for this. https://vt100.net/docs/vt510-rm/LNM.html
-        if mode.contains(TermMode::LINE_FEED_NEW_LINE) {
-            dispatch_context.add("LNM");
-        }
-        if mode.contains(TermMode::FOCUS_IN_OUT) {
-            dispatch_context.add("report_focus");
-        }
-        if mode.contains(TermMode::ALTERNATE_SCROLL) {
-            dispatch_context.add("alternate_scroll");
-        }
-        if mode.contains(TermMode::BRACKETED_PASTE) {
-            dispatch_context.add("bracketed_paste");
-        }
-        if mode.intersects(TermMode::MOUSE_MODE) {
-            dispatch_context.add("any_mouse_reporting");
-        }
-        {
-            let mouse_reporting = if mode.contains(TermMode::MOUSE_REPORT_CLICK) {
-                "click"
-            } else if mode.contains(TermMode::MOUSE_DRAG) {
-                "drag"
-            } else if mode.contains(TermMode::MOUSE_MOTION) {
-                "motion"
-            } else {
-                "off"
-            };
-            dispatch_context.set("mouse_reporting", mouse_reporting);
-        }
-        {
-            let format = if mode.contains(TermMode::SGR_MOUSE) {
-                "sgr"
-            } else if mode.contains(TermMode::UTF8_MOUSE) {
-                "utf8"
-            } else {
-                "normal"
-            };
-            dispatch_context.set("mouse_format", format);
-        };
-        dispatch_context
-    }
-}
-
-fn possible_open_targets(
-    workspace: &WeakView<Workspace>,
-    maybe_path: &String,
-    cx: &mut ViewContext<'_, TerminalView>,
-) -> Vec<PathLikeWithPosition<PathBuf>> {
-    let path_like = PathLikeWithPosition::parse_str(maybe_path.as_str(), |path_str| {
-        Ok::<_, std::convert::Infallible>(Path::new(path_str).to_path_buf())
-    })
-    .expect("infallible");
-    let maybe_path = path_like.path_like;
-    let potential_abs_paths = if maybe_path.is_absolute() {
-        vec![maybe_path]
-    } else if maybe_path.starts_with("~") {
-        if let Some(abs_path) = maybe_path
-            .strip_prefix("~")
-            .ok()
-            .and_then(|maybe_path| Some(dirs::home_dir()?.join(maybe_path)))
-        {
-            vec![abs_path]
-        } else {
-            Vec::new()
-        }
-    } else if let Some(workspace) = workspace.upgrade() {
-        workspace.update(cx, |workspace, cx| {
-            workspace
-                .worktrees(cx)
-                .map(|worktree| worktree.read(cx).abs_path().join(&maybe_path))
-                .collect()
-        })
-    } else {
-        Vec::new()
-    };
-
-    potential_abs_paths
-        .into_iter()
-        .filter(|path| path.exists())
-        .map(|path| PathLikeWithPosition {
-            path_like: path,
-            row: path_like.row,
-            column: path_like.column,
-        })
-        .collect()
-}
-
-pub fn regex_search_for_query(query: &project::search::SearchQuery) -> Option<RegexSearch> {
-    let query = query.as_str();
-    let searcher = RegexSearch::new(&query);
-    searcher.ok()
-}
-
-impl TerminalView {
-    fn key_down(&mut self, event: &KeyDownEvent, cx: &mut ViewContext<Self>) {
-        self.clear_bel(cx);
-        self.pause_cursor_blinking(cx);
-
-        self.terminal.update(cx, |term, cx| {
-            term.try_keystroke(
-                &event.keystroke,
-                TerminalSettings::get_global(cx).option_as_meta,
-            )
-        });
-    }
-
-    fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
-        self.has_new_content = false;
-        self.terminal.read(cx).focus_in();
-        self.blink_cursors(self.blink_epoch, cx);
-        cx.notify();
-    }
-
-    fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
-        self.terminal.update(cx, |terminal, _| {
-            terminal.focus_out();
-        });
-        cx.notify();
-    }
-}
-
-impl Render for TerminalView {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        let terminal_handle = self.terminal.clone();
-
-        let focused = self.focus_handle.is_focused(cx);
-
-        div()
-            .size_full()
-            .relative()
-            .track_focus(&self.focus_handle)
-            .key_context(self.dispatch_context(cx))
-            .on_action(cx.listener(TerminalView::send_text))
-            .on_action(cx.listener(TerminalView::send_keystroke))
-            .on_action(cx.listener(TerminalView::copy))
-            .on_action(cx.listener(TerminalView::paste))
-            .on_action(cx.listener(TerminalView::clear))
-            .on_action(cx.listener(TerminalView::show_character_palette))
-            .on_action(cx.listener(TerminalView::select_all))
-            .on_key_down(cx.listener(Self::key_down))
-            .on_mouse_down(
-                MouseButton::Right,
-                cx.listener(|this, event: &MouseDownEvent, cx| {
-                    this.deploy_context_menu(event.position, cx);
-                    cx.notify();
-                }),
-            )
-            .child(
-                // TODO: Oddly this wrapper div is needed for TerminalElement to not steal events from the context menu
-                div().size_full().child(TerminalElement::new(
-                    terminal_handle,
-                    self.focus_handle.clone(),
-                    focused,
-                    self.should_show_cursor(focused, cx),
-                    self.can_navigate_to_selected_word,
-                )),
-            )
-            .children(self.context_menu.as_ref().map(|(menu, positon, _)| {
-                overlay()
-                    .position(*positon)
-                    .anchor(gpui::AnchorCorner::TopLeft)
-                    .child(menu.clone())
-            }))
-    }
-}
-
-impl Item for TerminalView {
-    type Event = ItemEvent;
-
-    fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
-        Some(self.terminal().read(cx).title().into())
-    }
-
-    fn tab_content(
-        &self,
-        _detail: Option<usize>,
-        selected: bool,
-        cx: &WindowContext,
-    ) -> AnyElement {
-        let title = self.terminal().read(cx).title();
-
-        h_stack()
-            .gap_2()
-            .child(IconElement::new(Icon::Terminal))
-            .child(Label::new(title).color(if selected {
-                Color::Default
-            } else {
-                Color::Muted
-            }))
-            .into_any()
-    }
-
-    fn clone_on_split(
-        &self,
-        _workspace_id: WorkspaceId,
-        _cx: &mut ViewContext<Self>,
-    ) -> Option<View<Self>> {
-        //From what I can tell, there's no  way to tell the current working
-        //Directory of the terminal from outside the shell. There might be
-        //solutions to this, but they are non-trivial and require more IPC
-
-        // Some(TerminalContainer::new(
-        //     Err(anyhow::anyhow!("failed to instantiate terminal")),
-        //     workspace_id,
-        //     cx,
-        // ))
-
-        // TODO
-        None
-    }
-
-    fn is_dirty(&self, _cx: &gpui::AppContext) -> bool {
-        self.has_bell()
-    }
-
-    fn has_conflict(&self, _cx: &AppContext) -> bool {
-        false
-    }
-
-    // todo!(search)
-    // fn as_searchable(&self, handle: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
-    //     Some(Box::new(handle.clone()))
-    // }
-
-    fn breadcrumb_location(&self) -> ToolbarItemLocation {
-        ToolbarItemLocation::PrimaryLeft
-    }
-
-    fn breadcrumbs(&self, _: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
-        Some(vec![BreadcrumbText {
-            text: self.terminal().read(cx).breadcrumb_text.clone(),
-            highlights: None,
-        }])
-    }
-
-    fn serialized_item_kind() -> Option<&'static str> {
-        Some("Terminal")
-    }
-
-    fn deserialize(
-        project: Model<Project>,
-        workspace: WeakView<Workspace>,
-        workspace_id: workspace::WorkspaceId,
-        item_id: workspace::ItemId,
-        cx: &mut ViewContext<Pane>,
-    ) -> Task<anyhow::Result<View<Self>>> {
-        let window = cx.window_handle();
-        cx.spawn(|pane, mut cx| async move {
-            let cwd = TERMINAL_DB
-                .get_working_directory(item_id, workspace_id)
-                .log_err()
-                .flatten()
-                .or_else(|| {
-                    cx.update(|_, cx| {
-                        let strategy = TerminalSettings::get_global(cx).working_directory.clone();
-                        workspace
-                            .upgrade()
-                            .map(|workspace| {
-                                get_working_directory(workspace.read(cx), cx, strategy)
-                            })
-                            .flatten()
-                    })
-                    .ok()
-                    .flatten()
-                });
-
-            let terminal = project.update(&mut cx, |project, cx| {
-                project.create_terminal(cwd, window, cx)
-            })??;
-            pane.update(&mut cx, |_, cx| {
-                cx.new_view(|cx| TerminalView::new(terminal, workspace, workspace_id, cx))
-            })
-        })
-    }
-
-    fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
-        cx.background_executor()
-            .spawn(TERMINAL_DB.update_workspace_id(
-                workspace.database_id(),
-                self.workspace_id,
-                cx.entity_id().as_u64(),
-            ))
-            .detach();
-        self.workspace_id = workspace.database_id();
-    }
-
-    fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) {
-        f(*event)
-    }
-}
-
-impl SearchableItem for TerminalView {
-    type Match = RangeInclusive<Point>;
-
-    fn supported_options() -> SearchOptions {
-        SearchOptions {
-            case: false,
-            word: false,
-            regex: false,
-            replacement: false,
-        }
-    }
-
-    /// Clear stored matches
-    fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
-        self.terminal().update(cx, |term, _| term.matches.clear())
-    }
-
-    /// Store matches returned from find_matches somewhere for rendering
-    fn update_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) {
-        self.terminal().update(cx, |term, _| term.matches = matches)
-    }
-
-    /// Return the selection content to pre-load into this search
-    fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
-        self.terminal()
-            .read(cx)
-            .last_content
-            .selection_text
-            .clone()
-            .unwrap_or_default()
-    }
-
-    /// Focus match at given index into the Vec of matches
-    fn activate_match(&mut self, index: usize, _: Vec<Self::Match>, cx: &mut ViewContext<Self>) {
-        self.terminal()
-            .update(cx, |term, _| term.activate_match(index));
-        cx.notify();
-    }
-
-    /// Add selections for all matches given.
-    fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) {
-        self.terminal()
-            .update(cx, |term, _| term.select_matches(matches));
-        cx.notify();
-    }
-
-    /// Get all of the matches for this query, should be done on the background
-    fn find_matches(
-        &mut self,
-        query: Arc<project::search::SearchQuery>,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<Vec<Self::Match>> {
-        if let Some(searcher) = regex_search_for_query(&query) {
-            self.terminal()
-                .update(cx, |term, cx| term.find_matches(searcher, cx))
-        } else {
-            Task::ready(vec![])
-        }
-    }
-
-    /// Reports back to the search toolbar what the active match should be (the selection)
-    fn active_match_index(
-        &mut self,
-        matches: Vec<Self::Match>,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<usize> {
-        // Selection head might have a value if there's a selection that isn't
-        // associated with a match. Therefore, if there are no matches, we should
-        // report None, no matter the state of the terminal
-        let res = if matches.len() > 0 {
-            if let Some(selection_head) = self.terminal().read(cx).selection_head {
-                // If selection head is contained in a match. Return that match
-                if let Some(ix) = matches
-                    .iter()
-                    .enumerate()
-                    .find(|(_, search_match)| {
-                        search_match.contains(&selection_head)
-                            || search_match.start() > &selection_head
-                    })
-                    .map(|(ix, _)| ix)
-                {
-                    Some(ix)
-                } else {
-                    // If no selection after selection head, return the last match
-                    Some(matches.len().saturating_sub(1))
-                }
-            } else {
-                // Matches found but no active selection, return the first last one (closest to cursor)
-                Some(matches.len().saturating_sub(1))
-            }
-        } else {
-            None
-        };
-
-        res
-    }
-    fn replace(&mut self, _: &Self::Match, _: &SearchQuery, _: &mut ViewContext<Self>) {
-        // Replacement is not supported in terminal view, so this is a no-op.
-    }
-}
-
-///Get's the working directory for the given workspace, respecting the user's settings.
-pub fn get_working_directory(
-    workspace: &Workspace,
-    cx: &AppContext,
-    strategy: WorkingDirectory,
-) -> Option<PathBuf> {
-    let res = match strategy {
-        WorkingDirectory::CurrentProjectDirectory => current_project_directory(workspace, cx)
-            .or_else(|| first_project_directory(workspace, cx)),
-        WorkingDirectory::FirstProjectDirectory => first_project_directory(workspace, cx),
-        WorkingDirectory::AlwaysHome => None,
-        WorkingDirectory::Always { directory } => {
-            shellexpand::full(&directory) //TODO handle this better
-                .ok()
-                .map(|dir| Path::new(&dir.to_string()).to_path_buf())
-                .filter(|dir| dir.is_dir())
-        }
-    };
-    res.or_else(home_dir)
-}
-
-///Get's the first project's home directory, or the home directory
-fn first_project_directory(workspace: &Workspace, cx: &AppContext) -> Option<PathBuf> {
-    workspace
-        .worktrees(cx)
-        .next()
-        .and_then(|worktree_handle| worktree_handle.read(cx).as_local())
-        .and_then(get_path_from_wt)
-}
-
-///Gets the intuitively correct working directory from the given workspace
-///If there is an active entry for this project, returns that entry's worktree root.
-///If there's no active entry but there is a worktree, returns that worktrees root.
-///If either of these roots are files, or if there are any other query failures,
-///  returns the user's home directory
-fn current_project_directory(workspace: &Workspace, cx: &AppContext) -> Option<PathBuf> {
-    let project = workspace.project().read(cx);
-
-    project
-        .active_entry()
-        .and_then(|entry_id| project.worktree_for_entry(entry_id, cx))
-        .or_else(|| workspace.worktrees(cx).next())
-        .and_then(|worktree_handle| worktree_handle.read(cx).as_local())
-        .and_then(get_path_from_wt)
-}
-
-fn get_path_from_wt(wt: &LocalWorktree) -> Option<PathBuf> {
-    wt.root_entry()
-        .filter(|re| re.is_dir())
-        .map(|_| wt.abs_path().to_path_buf())
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use gpui::TestAppContext;
-    use project::{Entry, Project, ProjectPath, Worktree};
-    use std::path::Path;
-    use workspace::AppState;
-
-    // Working directory calculation tests
-
-    // No Worktrees in project -> home_dir()
-    #[gpui::test]
-    async fn no_worktree(cx: &mut TestAppContext) {
-        let (project, workspace) = init_test(cx).await;
-        cx.read(|cx| {
-            let workspace = workspace.read(cx);
-            let active_entry = project.read(cx).active_entry();
-
-            //Make sure environment is as expected
-            assert!(active_entry.is_none());
-            assert!(workspace.worktrees(cx).next().is_none());
-
-            let res = current_project_directory(workspace, cx);
-            assert_eq!(res, None);
-            let res = first_project_directory(workspace, cx);
-            assert_eq!(res, None);
-        });
-    }
-
-    // No active entry, but a worktree, worktree is a file -> home_dir()
-    #[gpui::test]
-    async fn no_active_entry_worktree_is_file(cx: &mut TestAppContext) {
-        let (project, workspace) = init_test(cx).await;
-
-        create_file_wt(project.clone(), "/root.txt", cx).await;
-        cx.read(|cx| {
-            let workspace = workspace.read(cx);
-            let active_entry = project.read(cx).active_entry();
-
-            //Make sure environment is as expected
-            assert!(active_entry.is_none());
-            assert!(workspace.worktrees(cx).next().is_some());
-
-            let res = current_project_directory(workspace, cx);
-            assert_eq!(res, None);
-            let res = first_project_directory(workspace, cx);
-            assert_eq!(res, None);
-        });
-    }
-
-    // No active entry, but a worktree, worktree is a folder -> worktree_folder
-    #[gpui::test]
-    async fn no_active_entry_worktree_is_dir(cx: &mut TestAppContext) {
-        let (project, workspace) = init_test(cx).await;
-
-        let (_wt, _entry) = create_folder_wt(project.clone(), "/root/", cx).await;
-        cx.update(|cx| {
-            let workspace = workspace.read(cx);
-            let active_entry = project.read(cx).active_entry();
-
-            assert!(active_entry.is_none());
-            assert!(workspace.worktrees(cx).next().is_some());
-
-            let res = current_project_directory(workspace, cx);
-            assert_eq!(res, Some((Path::new("/root/")).to_path_buf()));
-            let res = first_project_directory(workspace, cx);
-            assert_eq!(res, Some((Path::new("/root/")).to_path_buf()));
-        });
-    }
-
-    // Active entry with a work tree, worktree is a file -> home_dir()
-    #[gpui::test]
-    async fn active_entry_worktree_is_file(cx: &mut TestAppContext) {
-        let (project, workspace) = init_test(cx).await;
-
-        let (_wt, _entry) = create_folder_wt(project.clone(), "/root1/", cx).await;
-        let (wt2, entry2) = create_file_wt(project.clone(), "/root2.txt", cx).await;
-        insert_active_entry_for(wt2, entry2, project.clone(), cx);
-
-        cx.update(|cx| {
-            let workspace = workspace.read(cx);
-            let active_entry = project.read(cx).active_entry();
-
-            assert!(active_entry.is_some());
-
-            let res = current_project_directory(workspace, cx);
-            assert_eq!(res, None);
-            let res = first_project_directory(workspace, cx);
-            assert_eq!(res, Some((Path::new("/root1/")).to_path_buf()));
-        });
-    }
-
-    // Active entry, with a worktree, worktree is a folder -> worktree_folder
-    #[gpui::test]
-    async fn active_entry_worktree_is_dir(cx: &mut TestAppContext) {
-        let (project, workspace) = init_test(cx).await;
-
-        let (_wt, _entry) = create_folder_wt(project.clone(), "/root1/", cx).await;
-        let (wt2, entry2) = create_folder_wt(project.clone(), "/root2/", cx).await;
-        insert_active_entry_for(wt2, entry2, project.clone(), cx);
-
-        cx.update(|cx| {
-            let workspace = workspace.read(cx);
-            let active_entry = project.read(cx).active_entry();
-
-            assert!(active_entry.is_some());
-
-            let res = current_project_directory(workspace, cx);
-            assert_eq!(res, Some((Path::new("/root2/")).to_path_buf()));
-            let res = first_project_directory(workspace, cx);
-            assert_eq!(res, Some((Path::new("/root1/")).to_path_buf()));
-        });
-    }
-
-    /// Creates a worktree with 1 file: /root.txt
-    pub async fn init_test(cx: &mut TestAppContext) -> (Model<Project>, View<Workspace>) {
-        let params = cx.update(AppState::test);
-        cx.update(|cx| {
-            theme::init(theme::LoadThemes::JustBase, cx);
-            Project::init_settings(cx);
-            language::init(cx);
-        });
-
-        let project = Project::test(params.fs.clone(), [], cx).await;
-        let workspace = cx
-            .add_window(|cx| Workspace::test_new(project.clone(), cx))
-            .root_view(cx)
-            .unwrap();
-
-        (project, workspace)
-    }
-
-    /// Creates a worktree with 1 folder: /root{suffix}/
-    async fn create_folder_wt(
-        project: Model<Project>,
-        path: impl AsRef<Path>,
-        cx: &mut TestAppContext,
-    ) -> (Model<Worktree>, Entry) {
-        create_wt(project, true, path, cx).await
-    }
-
-    /// Creates a worktree with 1 file: /root{suffix}.txt
-    async fn create_file_wt(
-        project: Model<Project>,
-        path: impl AsRef<Path>,
-        cx: &mut TestAppContext,
-    ) -> (Model<Worktree>, Entry) {
-        create_wt(project, false, path, cx).await
-    }
-
-    async fn create_wt(
-        project: Model<Project>,
-        is_dir: bool,
-        path: impl AsRef<Path>,
-        cx: &mut TestAppContext,
-    ) -> (Model<Worktree>, Entry) {
-        let (wt, _) = project
-            .update(cx, |project, cx| {
-                project.find_or_create_local_worktree(path, true, cx)
-            })
-            .await
-            .unwrap();
-
-        let entry = cx
-            .update(|cx| {
-                wt.update(cx, |wt, cx| {
-                    wt.as_local()
-                        .unwrap()
-                        .create_entry(Path::new(""), is_dir, cx)
-                })
-            })
-            .await
-            .unwrap()
-            .unwrap();
-
-        (wt, entry)
-    }
-
-    pub fn insert_active_entry_for(
-        wt: Model<Worktree>,
-        entry: Entry,
-        project: Model<Project>,
-        cx: &mut TestAppContext,
-    ) {
-        cx.update(|cx| {
-            let p = ProjectPath {
-                worktree_id: wt.read(cx).id(),
-                path: entry.path,
-            };
-            project.update(cx, |project, cx| project.set_active_path(Some(p), cx));
-        });
-    }
-}

crates/text/src/locator.rs 🔗

@@ -20,11 +20,11 @@ impl Locator {
     }
 
     pub fn min_ref() -> &'static Self {
-        &*MIN
+        &MIN
     }
 
     pub fn max_ref() -> &'static Self {
-        &*MAX
+        &MAX
     }
 
     pub fn assign(&mut self, other: &Self) {

crates/text/src/selection.rs 🔗

@@ -5,7 +5,7 @@ use std::ops::Range;
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum SelectionGoal {
     None,
-    HorizontalPosition(f32),
+    HorizontalPosition(f32), // todo!("Can we use pixels here without adding a runtime gpui dependency?")
     HorizontalRange { start: f32, end: f32 },
     WrappedHorizontalPosition((u32, f32)),
 }

crates/text/src/subscription.rs 🔗

@@ -18,7 +18,7 @@ impl Topic {
     }
 
     pub fn publish(&self, edits: impl Clone + IntoIterator<Item = Edit<usize>>) {
-        publish(&mut *self.0.lock(), edits);
+        publish(&mut self.0.lock(), edits);
     }
 
     pub fn publish_mut(&mut self, edits: impl Clone + IntoIterator<Item = Edit<usize>>) {

crates/text/src/text.rs 🔗

@@ -1189,7 +1189,6 @@ impl Buffer {
         self.undo_or_redo(transaction).log_err()
     }
 
-    #[allow(clippy::needless_collect)]
     pub fn undo_to_transaction(&mut self, transaction_id: TransactionId) -> Vec<Operation> {
         let transactions = self
             .history
@@ -1223,7 +1222,6 @@ impl Buffer {
         }
     }
 
-    #[allow(clippy::needless_collect)]
     pub fn redo_to_transaction(&mut self, transaction_id: TransactionId) -> Vec<Operation> {
         let transactions = self
             .history
@@ -1536,7 +1534,6 @@ impl Buffer {
         edits
     }
 
-    #[allow(clippy::type_complexity)]
     pub fn randomly_edit<T>(
         &mut self,
         rng: &mut T,
@@ -2655,7 +2652,7 @@ impl LineEnding {
             max_ix -= 1;
         }
 
-        if let Some(ix) = text[..max_ix].find(&['\n']) {
+        if let Some(ix) = text[..max_ix].find(['\n']) {
             if ix > 0 && text.as_bytes()[ix - 1] == b'\r' {
                 Self::Windows
             } else {

crates/text2/Cargo.toml 🔗

@@ -1,37 +0,0 @@
-[package]
-name = "text2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/text2.rs"
-doctest = false
-
-[features]
-test-support = ["rand"]
-
-[dependencies]
-clock = { path = "../clock" }
-collections = { path = "../collections" }
-rope = { package = "rope2", path = "../rope2" }
-sum_tree = { path = "../sum_tree" }
-util = { path = "../util" }
-
-anyhow.workspace = true
-digest = { version = "0.9", features = ["std"] }
-lazy_static.workspace = true
-log.workspace = true
-parking_lot.workspace = true
-postage.workspace = true
-rand = { workspace = true, optional = true }
-smallvec.workspace = true
-regex.workspace = true
-
-[dev-dependencies]
-collections = { path = "../collections", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-util = { path = "../util", features = ["test-support"] }
-ctor.workspace = true
-env_logger.workspace = true
-rand.workspace = true

crates/text2/src/anchor.rs 🔗

@@ -1,144 +0,0 @@
-use crate::{
-    locator::Locator, BufferSnapshot, Point, PointUtf16, TextDimension, ToOffset, ToPoint,
-    ToPointUtf16,
-};
-use anyhow::Result;
-use std::{cmp::Ordering, fmt::Debug, ops::Range};
-use sum_tree::Bias;
-
-#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, Default)]
-pub struct Anchor {
-    pub timestamp: clock::Lamport,
-    pub offset: usize,
-    pub bias: Bias,
-    pub buffer_id: Option<u64>,
-}
-
-impl Anchor {
-    pub const MIN: Self = Self {
-        timestamp: clock::Lamport::MIN,
-        offset: usize::MIN,
-        bias: Bias::Left,
-        buffer_id: None,
-    };
-
-    pub const MAX: Self = Self {
-        timestamp: clock::Lamport::MAX,
-        offset: usize::MAX,
-        bias: Bias::Right,
-        buffer_id: None,
-    };
-
-    pub fn cmp(&self, other: &Anchor, buffer: &BufferSnapshot) -> Ordering {
-        let fragment_id_comparison = if self.timestamp == other.timestamp {
-            Ordering::Equal
-        } else {
-            buffer
-                .fragment_id_for_anchor(self)
-                .cmp(buffer.fragment_id_for_anchor(other))
-        };
-
-        fragment_id_comparison
-            .then_with(|| self.offset.cmp(&other.offset))
-            .then_with(|| self.bias.cmp(&other.bias))
-    }
-
-    pub fn min(&self, other: &Self, buffer: &BufferSnapshot) -> Self {
-        if self.cmp(other, buffer).is_le() {
-            *self
-        } else {
-            *other
-        }
-    }
-
-    pub fn max(&self, other: &Self, buffer: &BufferSnapshot) -> Self {
-        if self.cmp(other, buffer).is_ge() {
-            *self
-        } else {
-            *other
-        }
-    }
-
-    pub fn bias(&self, bias: Bias, buffer: &BufferSnapshot) -> Anchor {
-        if bias == Bias::Left {
-            self.bias_left(buffer)
-        } else {
-            self.bias_right(buffer)
-        }
-    }
-
-    pub fn bias_left(&self, buffer: &BufferSnapshot) -> Anchor {
-        if self.bias == Bias::Left {
-            *self
-        } else {
-            buffer.anchor_before(self)
-        }
-    }
-
-    pub fn bias_right(&self, buffer: &BufferSnapshot) -> Anchor {
-        if self.bias == Bias::Right {
-            *self
-        } else {
-            buffer.anchor_after(self)
-        }
-    }
-
-    pub fn summary<D>(&self, content: &BufferSnapshot) -> D
-    where
-        D: TextDimension,
-    {
-        content.summary_for_anchor(self)
-    }
-
-    /// Returns true when the [Anchor] is located inside a visible fragment.
-    pub fn is_valid(&self, buffer: &BufferSnapshot) -> bool {
-        if *self == Anchor::MIN || *self == Anchor::MAX {
-            true
-        } else {
-            let fragment_id = buffer.fragment_id_for_anchor(self);
-            let mut fragment_cursor = buffer.fragments.cursor::<(Option<&Locator>, usize)>();
-            fragment_cursor.seek(&Some(fragment_id), Bias::Left, &None);
-            fragment_cursor
-                .item()
-                .map_or(false, |fragment| fragment.visible)
-        }
-    }
-}
-
-pub trait OffsetRangeExt {
-    fn to_offset(&self, snapshot: &BufferSnapshot) -> Range<usize>;
-    fn to_point(&self, snapshot: &BufferSnapshot) -> Range<Point>;
-    fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> Range<PointUtf16>;
-}
-
-impl<T> OffsetRangeExt for Range<T>
-where
-    T: ToOffset,
-{
-    fn to_offset(&self, snapshot: &BufferSnapshot) -> Range<usize> {
-        self.start.to_offset(snapshot)..self.end.to_offset(snapshot)
-    }
-
-    fn to_point(&self, snapshot: &BufferSnapshot) -> Range<Point> {
-        self.start.to_offset(snapshot).to_point(snapshot)
-            ..self.end.to_offset(snapshot).to_point(snapshot)
-    }
-
-    fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> Range<PointUtf16> {
-        self.start.to_offset(snapshot).to_point_utf16(snapshot)
-            ..self.end.to_offset(snapshot).to_point_utf16(snapshot)
-    }
-}
-
-pub trait AnchorRangeExt {
-    fn cmp(&self, b: &Range<Anchor>, buffer: &BufferSnapshot) -> Result<Ordering>;
-}
-
-impl AnchorRangeExt for Range<Anchor> {
-    fn cmp(&self, other: &Range<Anchor>, buffer: &BufferSnapshot) -> Result<Ordering> {
-        Ok(match self.start.cmp(&other.start, buffer) {
-            Ordering::Equal => other.end.cmp(&self.end, buffer),
-            ord => ord,
-        })
-    }
-}

crates/text2/src/locator.rs 🔗

@@ -1,125 +0,0 @@
-use lazy_static::lazy_static;
-use smallvec::{smallvec, SmallVec};
-use std::iter;
-
-lazy_static! {
-    static ref MIN: Locator = Locator::min();
-    static ref MAX: Locator = Locator::max();
-}
-
-#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub struct Locator(SmallVec<[u64; 4]>);
-
-impl Locator {
-    pub fn min() -> Self {
-        Self(smallvec![u64::MIN])
-    }
-
-    pub fn max() -> Self {
-        Self(smallvec![u64::MAX])
-    }
-
-    pub fn min_ref() -> &'static Self {
-        &MIN
-    }
-
-    pub fn max_ref() -> &'static Self {
-        &MAX
-    }
-
-    pub fn assign(&mut self, other: &Self) {
-        self.0.resize(other.0.len(), 0);
-        self.0.copy_from_slice(&other.0);
-    }
-
-    pub fn between(lhs: &Self, rhs: &Self) -> Self {
-        let lhs = lhs.0.iter().copied().chain(iter::repeat(u64::MIN));
-        let rhs = rhs.0.iter().copied().chain(iter::repeat(u64::MAX));
-        let mut location = SmallVec::new();
-        for (lhs, rhs) in lhs.zip(rhs) {
-            let mid = lhs + ((rhs.saturating_sub(lhs)) >> 48);
-            location.push(mid);
-            if mid > lhs {
-                break;
-            }
-        }
-        Self(location)
-    }
-
-    pub fn len(&self) -> usize {
-        self.0.len()
-    }
-
-    pub fn is_empty(&self) -> bool {
-        self.len() == 0
-    }
-}
-
-impl Default for Locator {
-    fn default() -> Self {
-        Self::min()
-    }
-}
-
-impl sum_tree::Item for Locator {
-    type Summary = Locator;
-
-    fn summary(&self) -> Self::Summary {
-        self.clone()
-    }
-}
-
-impl sum_tree::KeyedItem for Locator {
-    type Key = Locator;
-
-    fn key(&self) -> Self::Key {
-        self.clone()
-    }
-}
-
-impl sum_tree::Summary for Locator {
-    type Context = ();
-
-    fn add_summary(&mut self, summary: &Self, _: &()) {
-        self.assign(summary);
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use rand::prelude::*;
-    use std::mem;
-
-    #[gpui::test(iterations = 100)]
-    fn test_locators(mut rng: StdRng) {
-        let mut lhs = Default::default();
-        let mut rhs = Default::default();
-        while lhs == rhs {
-            lhs = Locator(
-                (0..rng.gen_range(1..=5))
-                    .map(|_| rng.gen_range(0..=100))
-                    .collect(),
-            );
-            rhs = Locator(
-                (0..rng.gen_range(1..=5))
-                    .map(|_| rng.gen_range(0..=100))
-                    .collect(),
-            );
-        }
-
-        if lhs > rhs {
-            mem::swap(&mut lhs, &mut rhs);
-        }
-
-        let middle = Locator::between(&lhs, &rhs);
-        assert!(middle > lhs);
-        assert!(middle < rhs);
-        for ix in 0..middle.0.len() - 1 {
-            assert!(
-                middle.0[ix] == *lhs.0.get(ix).unwrap_or(&0)
-                    || middle.0[ix] == *rhs.0.get(ix).unwrap_or(&0)
-            );
-        }
-    }
-}

crates/text2/src/network.rs 🔗

@@ -1,69 +0,0 @@
-use clock::ReplicaId;
-
-pub struct Network<T: Clone, R: rand::Rng> {
-    inboxes: std::collections::BTreeMap<ReplicaId, Vec<Envelope<T>>>,
-    all_messages: Vec<T>,
-    rng: R,
-}
-
-#[derive(Clone)]
-struct Envelope<T: Clone> {
-    message: T,
-}
-
-impl<T: Clone, R: rand::Rng> Network<T, R> {
-    pub fn new(rng: R) -> Self {
-        Network {
-            inboxes: Default::default(),
-            all_messages: Vec::new(),
-            rng,
-        }
-    }
-
-    pub fn add_peer(&mut self, id: ReplicaId) {
-        self.inboxes.insert(id, Vec::new());
-    }
-
-    pub fn replicate(&mut self, old_replica_id: ReplicaId, new_replica_id: ReplicaId) {
-        self.inboxes
-            .insert(new_replica_id, self.inboxes[&old_replica_id].clone());
-    }
-
-    pub fn is_idle(&self) -> bool {
-        self.inboxes.values().all(|i| i.is_empty())
-    }
-
-    pub fn broadcast(&mut self, sender: ReplicaId, messages: Vec<T>) {
-        for (replica, inbox) in self.inboxes.iter_mut() {
-            if *replica != sender {
-                for message in &messages {
-                    // Insert one or more duplicates of this message, potentially *before* the previous
-                    // message sent by this peer to simulate out-of-order delivery.
-                    for _ in 0..self.rng.gen_range(1..4) {
-                        let insertion_index = self.rng.gen_range(0..inbox.len() + 1);
-                        inbox.insert(
-                            insertion_index,
-                            Envelope {
-                                message: message.clone(),
-                            },
-                        );
-                    }
-                }
-            }
-        }
-        self.all_messages.extend(messages);
-    }
-
-    pub fn has_unreceived(&self, receiver: ReplicaId) -> bool {
-        !self.inboxes[&receiver].is_empty()
-    }
-
-    pub fn receive(&mut self, receiver: ReplicaId) -> Vec<T> {
-        let inbox = self.inboxes.get_mut(&receiver).unwrap();
-        let count = self.rng.gen_range(0..inbox.len() + 1);
-        inbox
-            .drain(0..count)
-            .map(|envelope| envelope.message)
-            .collect()
-    }
-}

crates/text2/src/operation_queue.rs 🔗

@@ -1,153 +0,0 @@
-use std::{fmt::Debug, ops::Add};
-use sum_tree::{Dimension, Edit, Item, KeyedItem, SumTree, Summary};
-
-pub trait Operation: Clone + Debug {
-    fn lamport_timestamp(&self) -> clock::Lamport;
-}
-
-#[derive(Clone, Debug)]
-struct OperationItem<T>(T);
-
-#[derive(Clone, Debug)]
-pub struct OperationQueue<T: Operation>(SumTree<OperationItem<T>>);
-
-#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
-pub struct OperationKey(clock::Lamport);
-
-#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
-pub struct OperationSummary {
-    pub key: OperationKey,
-    pub len: usize,
-}
-
-impl OperationKey {
-    pub fn new(timestamp: clock::Lamport) -> Self {
-        Self(timestamp)
-    }
-}
-
-impl<T: Operation> Default for OperationQueue<T> {
-    fn default() -> Self {
-        OperationQueue::new()
-    }
-}
-
-impl<T: Operation> OperationQueue<T> {
-    pub fn new() -> Self {
-        OperationQueue(SumTree::new())
-    }
-
-    pub fn len(&self) -> usize {
-        self.0.summary().len
-    }
-
-    pub fn is_empty(&self) -> bool {
-        self.len() == 0
-    }
-
-    pub fn insert(&mut self, mut ops: Vec<T>) {
-        ops.sort_by_key(|op| op.lamport_timestamp());
-        ops.dedup_by_key(|op| op.lamport_timestamp());
-        self.0.edit(
-            ops.into_iter()
-                .map(|op| Edit::Insert(OperationItem(op)))
-                .collect(),
-            &(),
-        );
-    }
-
-    pub fn drain(&mut self) -> Self {
-        let clone = self.clone();
-        self.0 = SumTree::new();
-        clone
-    }
-
-    pub fn iter(&self) -> impl Iterator<Item = &T> {
-        self.0.iter().map(|i| &i.0)
-    }
-}
-
-impl Summary for OperationSummary {
-    type Context = ();
-
-    fn add_summary(&mut self, other: &Self, _: &()) {
-        assert!(self.key < other.key);
-        self.key = other.key;
-        self.len += other.len;
-    }
-}
-
-impl<'a> Add<&'a Self> for OperationSummary {
-    type Output = Self;
-
-    fn add(self, other: &Self) -> Self {
-        assert!(self.key < other.key);
-        OperationSummary {
-            key: other.key,
-            len: self.len + other.len,
-        }
-    }
-}
-
-impl<'a> Dimension<'a, OperationSummary> for OperationKey {
-    fn add_summary(&mut self, summary: &OperationSummary, _: &()) {
-        assert!(*self <= summary.key);
-        *self = summary.key;
-    }
-}
-
-impl<T: Operation> Item for OperationItem<T> {
-    type Summary = OperationSummary;
-
-    fn summary(&self) -> Self::Summary {
-        OperationSummary {
-            key: OperationKey::new(self.0.lamport_timestamp()),
-            len: 1,
-        }
-    }
-}
-
-impl<T: Operation> KeyedItem for OperationItem<T> {
-    type Key = OperationKey;
-
-    fn key(&self) -> Self::Key {
-        OperationKey::new(self.0.lamport_timestamp())
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn test_len() {
-        let mut clock = clock::Lamport::new(0);
-
-        let mut queue = OperationQueue::new();
-        assert_eq!(queue.len(), 0);
-
-        queue.insert(vec![
-            TestOperation(clock.tick()),
-            TestOperation(clock.tick()),
-        ]);
-        assert_eq!(queue.len(), 2);
-
-        queue.insert(vec![TestOperation(clock.tick())]);
-        assert_eq!(queue.len(), 3);
-
-        drop(queue.drain());
-        assert_eq!(queue.len(), 0);
-
-        queue.insert(vec![TestOperation(clock.tick())]);
-        assert_eq!(queue.len(), 1);
-    }
-
-    #[derive(Clone, Debug, Eq, PartialEq)]
-    struct TestOperation(clock::Lamport);
-
-    impl Operation for TestOperation {
-        fn lamport_timestamp(&self) -> clock::Lamport {
-            self.0
-        }
-    }
-}

crates/text2/src/patch.rs 🔗

@@ -1,594 +0,0 @@
-use crate::Edit;
-use std::{
-    cmp, mem,
-    ops::{Add, AddAssign, Sub},
-};
-
-#[derive(Clone, Default, Debug, PartialEq, Eq)]
-pub struct Patch<T>(Vec<Edit<T>>);
-
-impl<T> Patch<T>
-where
-    T: 'static
-        + Clone
-        + Copy
-        + Ord
-        + Sub<T, Output = T>
-        + Add<T, Output = T>
-        + AddAssign
-        + Default
-        + PartialEq,
-{
-    pub fn new(edits: Vec<Edit<T>>) -> Self {
-        #[cfg(debug_assertions)]
-        {
-            let mut last_edit: Option<&Edit<T>> = None;
-            for edit in &edits {
-                if let Some(last_edit) = last_edit {
-                    assert!(edit.old.start > last_edit.old.end);
-                    assert!(edit.new.start > last_edit.new.end);
-                }
-                last_edit = Some(edit);
-            }
-        }
-        Self(edits)
-    }
-
-    pub fn edits(&self) -> &[Edit<T>] {
-        &self.0
-    }
-
-    pub fn into_inner(self) -> Vec<Edit<T>> {
-        self.0
-    }
-
-    pub fn compose(&self, new_edits_iter: impl IntoIterator<Item = Edit<T>>) -> Self {
-        let mut old_edits_iter = self.0.iter().cloned().peekable();
-        let mut new_edits_iter = new_edits_iter.into_iter().peekable();
-        let mut composed = Patch(Vec::new());
-
-        let mut old_start = T::default();
-        let mut new_start = T::default();
-        loop {
-            let old_edit = old_edits_iter.peek_mut();
-            let new_edit = new_edits_iter.peek_mut();
-
-            // Push the old edit if its new end is before the new edit's old start.
-            if let Some(old_edit) = old_edit.as_ref() {
-                let new_edit = new_edit.as_ref();
-                if new_edit.map_or(true, |new_edit| old_edit.new.end < new_edit.old.start) {
-                    let catchup = old_edit.old.start - old_start;
-                    old_start += catchup;
-                    new_start += catchup;
-
-                    let old_end = old_start + old_edit.old_len();
-                    let new_end = new_start + old_edit.new_len();
-                    composed.push(Edit {
-                        old: old_start..old_end,
-                        new: new_start..new_end,
-                    });
-                    old_start = old_end;
-                    new_start = new_end;
-                    old_edits_iter.next();
-                    continue;
-                }
-            }
-
-            // Push the new edit if its old end is before the old edit's new start.
-            if let Some(new_edit) = new_edit.as_ref() {
-                let old_edit = old_edit.as_ref();
-                if old_edit.map_or(true, |old_edit| new_edit.old.end < old_edit.new.start) {
-                    let catchup = new_edit.new.start - new_start;
-                    old_start += catchup;
-                    new_start += catchup;
-
-                    let old_end = old_start + new_edit.old_len();
-                    let new_end = new_start + new_edit.new_len();
-                    composed.push(Edit {
-                        old: old_start..old_end,
-                        new: new_start..new_end,
-                    });
-                    old_start = old_end;
-                    new_start = new_end;
-                    new_edits_iter.next();
-                    continue;
-                }
-            }
-
-            // If we still have edits by this point then they must intersect, so we compose them.
-            if let Some((old_edit, new_edit)) = old_edit.zip(new_edit) {
-                if old_edit.new.start < new_edit.old.start {
-                    let catchup = old_edit.old.start - old_start;
-                    old_start += catchup;
-                    new_start += catchup;
-
-                    let overshoot = new_edit.old.start - old_edit.new.start;
-                    let old_end = cmp::min(old_start + overshoot, old_edit.old.end);
-                    let new_end = new_start + overshoot;
-                    composed.push(Edit {
-                        old: old_start..old_end,
-                        new: new_start..new_end,
-                    });
-
-                    old_edit.old.start = old_end;
-                    old_edit.new.start += overshoot;
-                    old_start = old_end;
-                    new_start = new_end;
-                } else {
-                    let catchup = new_edit.new.start - new_start;
-                    old_start += catchup;
-                    new_start += catchup;
-
-                    let overshoot = old_edit.new.start - new_edit.old.start;
-                    let old_end = old_start + overshoot;
-                    let new_end = cmp::min(new_start + overshoot, new_edit.new.end);
-                    composed.push(Edit {
-                        old: old_start..old_end,
-                        new: new_start..new_end,
-                    });
-
-                    new_edit.old.start += overshoot;
-                    new_edit.new.start = new_end;
-                    old_start = old_end;
-                    new_start = new_end;
-                }
-
-                if old_edit.new.end > new_edit.old.end {
-                    let old_end = old_start + cmp::min(old_edit.old_len(), new_edit.old_len());
-                    let new_end = new_start + new_edit.new_len();
-                    composed.push(Edit {
-                        old: old_start..old_end,
-                        new: new_start..new_end,
-                    });
-
-                    old_edit.old.start = old_end;
-                    old_edit.new.start = new_edit.old.end;
-                    old_start = old_end;
-                    new_start = new_end;
-                    new_edits_iter.next();
-                } else {
-                    let old_end = old_start + old_edit.old_len();
-                    let new_end = new_start + cmp::min(old_edit.new_len(), new_edit.new_len());
-                    composed.push(Edit {
-                        old: old_start..old_end,
-                        new: new_start..new_end,
-                    });
-
-                    new_edit.old.start = old_edit.new.end;
-                    new_edit.new.start = new_end;
-                    old_start = old_end;
-                    new_start = new_end;
-                    old_edits_iter.next();
-                }
-            } else {
-                break;
-            }
-        }
-
-        composed
-    }
-
-    pub fn invert(&mut self) -> &mut Self {
-        for edit in &mut self.0 {
-            mem::swap(&mut edit.old, &mut edit.new);
-        }
-        self
-    }
-
-    pub fn clear(&mut self) {
-        self.0.clear();
-    }
-
-    pub fn is_empty(&self) -> bool {
-        self.0.is_empty()
-    }
-
-    pub fn push(&mut self, edit: Edit<T>) {
-        if edit.is_empty() {
-            return;
-        }
-
-        if let Some(last) = self.0.last_mut() {
-            if last.old.end >= edit.old.start {
-                last.old.end = edit.old.end;
-                last.new.end = edit.new.end;
-            } else {
-                self.0.push(edit);
-            }
-        } else {
-            self.0.push(edit);
-        }
-    }
-
-    pub fn old_to_new(&self, old: T) -> T {
-        let ix = match self.0.binary_search_by(|probe| probe.old.start.cmp(&old)) {
-            Ok(ix) => ix,
-            Err(ix) => {
-                if ix == 0 {
-                    return old;
-                } else {
-                    ix - 1
-                }
-            }
-        };
-        if let Some(edit) = self.0.get(ix) {
-            if old >= edit.old.end {
-                edit.new.end + (old - edit.old.end)
-            } else {
-                edit.new.start
-            }
-        } else {
-            old
-        }
-    }
-}
-
-impl<T: Clone> IntoIterator for Patch<T> {
-    type Item = Edit<T>;
-    type IntoIter = std::vec::IntoIter<Edit<T>>;
-
-    fn into_iter(self) -> Self::IntoIter {
-        self.0.into_iter()
-    }
-}
-
-impl<'a, T: Clone> IntoIterator for &'a Patch<T> {
-    type Item = Edit<T>;
-    type IntoIter = std::iter::Cloned<std::slice::Iter<'a, Edit<T>>>;
-
-    fn into_iter(self) -> Self::IntoIter {
-        self.0.iter().cloned()
-    }
-}
-
-impl<'a, T: Clone> IntoIterator for &'a mut Patch<T> {
-    type Item = Edit<T>;
-    type IntoIter = std::iter::Cloned<std::slice::Iter<'a, Edit<T>>>;
-
-    fn into_iter(self) -> Self::IntoIter {
-        self.0.iter().cloned()
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use rand::prelude::*;
-    use std::env;
-
-    #[gpui::test]
-    fn test_one_disjoint_edit() {
-        assert_patch_composition(
-            Patch(vec![Edit {
-                old: 1..3,
-                new: 1..4,
-            }]),
-            Patch(vec![Edit {
-                old: 0..0,
-                new: 0..4,
-            }]),
-            Patch(vec![
-                Edit {
-                    old: 0..0,
-                    new: 0..4,
-                },
-                Edit {
-                    old: 1..3,
-                    new: 5..8,
-                },
-            ]),
-        );
-
-        assert_patch_composition(
-            Patch(vec![Edit {
-                old: 1..3,
-                new: 1..4,
-            }]),
-            Patch(vec![Edit {
-                old: 5..9,
-                new: 5..7,
-            }]),
-            Patch(vec![
-                Edit {
-                    old: 1..3,
-                    new: 1..4,
-                },
-                Edit {
-                    old: 4..8,
-                    new: 5..7,
-                },
-            ]),
-        );
-    }
-
-    #[gpui::test]
-    fn test_one_overlapping_edit() {
-        assert_patch_composition(
-            Patch(vec![Edit {
-                old: 1..3,
-                new: 1..4,
-            }]),
-            Patch(vec![Edit {
-                old: 3..5,
-                new: 3..6,
-            }]),
-            Patch(vec![Edit {
-                old: 1..4,
-                new: 1..6,
-            }]),
-        );
-    }
-
-    #[gpui::test]
-    fn test_two_disjoint_and_overlapping() {
-        assert_patch_composition(
-            Patch(vec![
-                Edit {
-                    old: 1..3,
-                    new: 1..4,
-                },
-                Edit {
-                    old: 8..12,
-                    new: 9..11,
-                },
-            ]),
-            Patch(vec![
-                Edit {
-                    old: 0..0,
-                    new: 0..4,
-                },
-                Edit {
-                    old: 3..10,
-                    new: 7..9,
-                },
-            ]),
-            Patch(vec![
-                Edit {
-                    old: 0..0,
-                    new: 0..4,
-                },
-                Edit {
-                    old: 1..12,
-                    new: 5..10,
-                },
-            ]),
-        );
-    }
-
-    #[gpui::test]
-    fn test_two_new_edits_overlapping_one_old_edit() {
-        assert_patch_composition(
-            Patch(vec![Edit {
-                old: 0..0,
-                new: 0..3,
-            }]),
-            Patch(vec![
-                Edit {
-                    old: 0..0,
-                    new: 0..1,
-                },
-                Edit {
-                    old: 1..2,
-                    new: 2..2,
-                },
-            ]),
-            Patch(vec![Edit {
-                old: 0..0,
-                new: 0..3,
-            }]),
-        );
-
-        assert_patch_composition(
-            Patch(vec![Edit {
-                old: 2..3,
-                new: 2..4,
-            }]),
-            Patch(vec![
-                Edit {
-                    old: 0..2,
-                    new: 0..1,
-                },
-                Edit {
-                    old: 3..3,
-                    new: 2..5,
-                },
-            ]),
-            Patch(vec![Edit {
-                old: 0..3,
-                new: 0..6,
-            }]),
-        );
-
-        assert_patch_composition(
-            Patch(vec![Edit {
-                old: 0..0,
-                new: 0..2,
-            }]),
-            Patch(vec![
-                Edit {
-                    old: 0..0,
-                    new: 0..2,
-                },
-                Edit {
-                    old: 2..5,
-                    new: 4..4,
-                },
-            ]),
-            Patch(vec![Edit {
-                old: 0..3,
-                new: 0..4,
-            }]),
-        );
-    }
-
-    #[gpui::test]
-    fn test_two_new_edits_touching_one_old_edit() {
-        assert_patch_composition(
-            Patch(vec![
-                Edit {
-                    old: 2..3,
-                    new: 2..4,
-                },
-                Edit {
-                    old: 7..7,
-                    new: 8..11,
-                },
-            ]),
-            Patch(vec![
-                Edit {
-                    old: 2..3,
-                    new: 2..2,
-                },
-                Edit {
-                    old: 4..4,
-                    new: 3..4,
-                },
-            ]),
-            Patch(vec![
-                Edit {
-                    old: 2..3,
-                    new: 2..4,
-                },
-                Edit {
-                    old: 7..7,
-                    new: 8..11,
-                },
-            ]),
-        );
-    }
-
-    #[gpui::test]
-    fn test_old_to_new() {
-        let patch = Patch(vec![
-            Edit {
-                old: 2..4,
-                new: 2..4,
-            },
-            Edit {
-                old: 7..8,
-                new: 7..11,
-            },
-        ]);
-        assert_eq!(patch.old_to_new(0), 0);
-        assert_eq!(patch.old_to_new(1), 1);
-        assert_eq!(patch.old_to_new(2), 2);
-        assert_eq!(patch.old_to_new(3), 2);
-        assert_eq!(patch.old_to_new(4), 4);
-        assert_eq!(patch.old_to_new(5), 5);
-        assert_eq!(patch.old_to_new(6), 6);
-        assert_eq!(patch.old_to_new(7), 7);
-        assert_eq!(patch.old_to_new(8), 11);
-        assert_eq!(patch.old_to_new(9), 12);
-    }
-
-    #[gpui::test(iterations = 100)]
-    fn test_random_patch_compositions(mut rng: StdRng) {
-        let operations = env::var("OPERATIONS")
-            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
-            .unwrap_or(20);
-
-        let initial_chars = (0..rng.gen_range(0..=100))
-            .map(|_| rng.gen_range(b'a'..=b'z') as char)
-            .collect::<Vec<_>>();
-        log::info!("initial chars: {:?}", initial_chars);
-
-        // Generate two sequential patches
-        let mut patches = Vec::new();
-        let mut expected_chars = initial_chars.clone();
-        for i in 0..2 {
-            log::info!("patch {}:", i);
-
-            let mut delta = 0i32;
-            let mut last_edit_end = 0;
-            let mut edits = Vec::new();
-
-            for _ in 0..operations {
-                if last_edit_end >= expected_chars.len() {
-                    break;
-                }
-
-                let end = rng.gen_range(last_edit_end..=expected_chars.len());
-                let start = rng.gen_range(last_edit_end..=end);
-                let old_len = end - start;
-
-                let mut new_len = rng.gen_range(0..=3);
-                if start == end && new_len == 0 {
-                    new_len += 1;
-                }
-
-                last_edit_end = start + new_len + 1;
-
-                let new_chars = (0..new_len)
-                    .map(|_| rng.gen_range(b'A'..=b'Z') as char)
-                    .collect::<Vec<_>>();
-                log::info!(
-                    "  editing {:?}: {:?}",
-                    start..end,
-                    new_chars.iter().collect::<String>()
-                );
-                edits.push(Edit {
-                    old: (start as i32 - delta) as u32..(end as i32 - delta) as u32,
-                    new: start as u32..(start + new_len) as u32,
-                });
-                expected_chars.splice(start..end, new_chars);
-
-                delta += new_len as i32 - old_len as i32;
-            }
-
-            patches.push(Patch(edits));
-        }
-
-        log::info!("old patch: {:?}", &patches[0]);
-        log::info!("new patch: {:?}", &patches[1]);
-        log::info!("initial chars: {:?}", initial_chars);
-        log::info!("final chars: {:?}", expected_chars);
-
-        // Compose the patches, and verify that it has the same effect as applying the
-        // two patches separately.
-        let composed = patches[0].compose(&patches[1]);
-        log::info!("composed patch: {:?}", &composed);
-
-        let mut actual_chars = initial_chars;
-        for edit in composed.0 {
-            actual_chars.splice(
-                edit.new.start as usize..edit.new.start as usize + edit.old.len(),
-                expected_chars[edit.new.start as usize..edit.new.end as usize]
-                    .iter()
-                    .copied(),
-            );
-        }
-
-        assert_eq!(actual_chars, expected_chars);
-    }
-
-    #[track_caller]
-    fn assert_patch_composition(old: Patch<u32>, new: Patch<u32>, composed: Patch<u32>) {
-        let original = ('a'..'z').collect::<Vec<_>>();
-        let inserted = ('A'..'Z').collect::<Vec<_>>();
-
-        let mut expected = original.clone();
-        apply_patch(&mut expected, &old, &inserted);
-        apply_patch(&mut expected, &new, &inserted);
-
-        let mut actual = original;
-        apply_patch(&mut actual, &composed, &expected);
-        assert_eq!(
-            actual.into_iter().collect::<String>(),
-            expected.into_iter().collect::<String>(),
-            "expected patch is incorrect"
-        );
-
-        assert_eq!(old.compose(&new), composed);
-    }
-
-    fn apply_patch(text: &mut Vec<char>, patch: &Patch<u32>, new_text: &[char]) {
-        for edit in patch.0.iter().rev() {
-            text.splice(
-                edit.old.start as usize..edit.old.end as usize,
-                new_text[edit.new.start as usize..edit.new.end as usize]
-                    .iter()
-                    .copied(),
-            );
-        }
-    }
-}

crates/text2/src/selection.rs 🔗

@@ -1,123 +0,0 @@
-use crate::{Anchor, BufferSnapshot, TextDimension};
-use std::cmp::Ordering;
-use std::ops::Range;
-
-#[derive(Copy, Clone, Debug, PartialEq)]
-pub enum SelectionGoal {
-    None,
-    HorizontalPosition(f32), // todo!("Can we use pixels here without adding a runtime gpui dependency?")
-    HorizontalRange { start: f32, end: f32 },
-    WrappedHorizontalPosition((u32, f32)),
-}
-
-#[derive(Clone, Debug, PartialEq)]
-pub struct Selection<T> {
-    pub id: usize,
-    pub start: T,
-    pub end: T,
-    pub reversed: bool,
-    pub goal: SelectionGoal,
-}
-
-impl Default for SelectionGoal {
-    fn default() -> Self {
-        Self::None
-    }
-}
-
-impl<T: Clone> Selection<T> {
-    pub fn head(&self) -> T {
-        if self.reversed {
-            self.start.clone()
-        } else {
-            self.end.clone()
-        }
-    }
-
-    pub fn tail(&self) -> T {
-        if self.reversed {
-            self.end.clone()
-        } else {
-            self.start.clone()
-        }
-    }
-
-    pub fn map<F, S>(&self, f: F) -> Selection<S>
-    where
-        F: Fn(T) -> S,
-    {
-        Selection::<S> {
-            id: self.id,
-            start: f(self.start.clone()),
-            end: f(self.end.clone()),
-            reversed: self.reversed,
-            goal: self.goal,
-        }
-    }
-
-    pub fn collapse_to(&mut self, point: T, new_goal: SelectionGoal) {
-        self.start = point.clone();
-        self.end = point;
-        self.goal = new_goal;
-        self.reversed = false;
-    }
-}
-
-impl<T: Copy + Ord> Selection<T> {
-    pub fn is_empty(&self) -> bool {
-        self.start == self.end
-    }
-
-    pub fn set_head(&mut self, head: T, new_goal: SelectionGoal) {
-        if head.cmp(&self.tail()) < Ordering::Equal {
-            if !self.reversed {
-                self.end = self.start;
-                self.reversed = true;
-            }
-            self.start = head;
-        } else {
-            if self.reversed {
-                self.start = self.end;
-                self.reversed = false;
-            }
-            self.end = head;
-        }
-        self.goal = new_goal;
-    }
-
-    pub fn range(&self) -> Range<T> {
-        self.start..self.end
-    }
-}
-
-impl Selection<usize> {
-    #[cfg(feature = "test-support")]
-    pub fn from_offset(offset: usize) -> Self {
-        Selection {
-            id: 0,
-            start: offset,
-            end: offset,
-            goal: SelectionGoal::None,
-            reversed: false,
-        }
-    }
-
-    pub fn equals(&self, offset_range: &Range<usize>) -> bool {
-        self.start == offset_range.start && self.end == offset_range.end
-    }
-}
-
-impl Selection<Anchor> {
-    pub fn resolve<'a, D: 'a + TextDimension>(
-        &'a self,
-        snapshot: &'a BufferSnapshot,
-    ) -> Selection<D> {
-        Selection {
-            id: self.id,
-            start: snapshot.summary_for_anchor(&self.start),
-            end: snapshot.summary_for_anchor(&self.end),
-            reversed: self.reversed,
-            goal: self.goal,
-        }
-    }
-}

crates/text2/src/subscription.rs 🔗

@@ -1,48 +0,0 @@
-use crate::{Edit, Patch};
-use parking_lot::Mutex;
-use std::{
-    mem,
-    sync::{Arc, Weak},
-};
-
-#[derive(Default)]
-pub struct Topic(Mutex<Vec<Weak<Mutex<Patch<usize>>>>>);
-
-pub struct Subscription(Arc<Mutex<Patch<usize>>>);
-
-impl Topic {
-    pub fn subscribe(&mut self) -> Subscription {
-        let subscription = Subscription(Default::default());
-        self.0.get_mut().push(Arc::downgrade(&subscription.0));
-        subscription
-    }
-
-    pub fn publish(&self, edits: impl Clone + IntoIterator<Item = Edit<usize>>) {
-        publish(&mut self.0.lock(), edits);
-    }
-
-    pub fn publish_mut(&mut self, edits: impl Clone + IntoIterator<Item = Edit<usize>>) {
-        publish(self.0.get_mut(), edits);
-    }
-}
-
-impl Subscription {
-    pub fn consume(&self) -> Patch<usize> {
-        mem::take(&mut *self.0.lock())
-    }
-}
-
-fn publish(
-    subscriptions: &mut Vec<Weak<Mutex<Patch<usize>>>>,
-    edits: impl Clone + IntoIterator<Item = Edit<usize>>,
-) {
-    subscriptions.retain(|subscription| {
-        if let Some(subscription) = subscription.upgrade() {
-            let mut patch = subscription.lock();
-            *patch = patch.compose(edits.clone());
-            true
-        } else {
-            false
-        }
-    });
-}

crates/text2/src/tests.rs 🔗

@@ -1,764 +0,0 @@
-use super::{network::Network, *};
-use clock::ReplicaId;
-use rand::prelude::*;
-use std::{
-    cmp::Ordering,
-    env,
-    iter::Iterator,
-    time::{Duration, Instant},
-};
-
-#[cfg(test)]
-#[ctor::ctor]
-fn init_logger() {
-    if std::env::var("RUST_LOG").is_ok() {
-        env_logger::init();
-    }
-}
-
-#[test]
-fn test_edit() {
-    let mut buffer = Buffer::new(0, 0, "abc".into());
-    assert_eq!(buffer.text(), "abc");
-    buffer.edit([(3..3, "def")]);
-    assert_eq!(buffer.text(), "abcdef");
-    buffer.edit([(0..0, "ghi")]);
-    assert_eq!(buffer.text(), "ghiabcdef");
-    buffer.edit([(5..5, "jkl")]);
-    assert_eq!(buffer.text(), "ghiabjklcdef");
-    buffer.edit([(6..7, "")]);
-    assert_eq!(buffer.text(), "ghiabjlcdef");
-    buffer.edit([(4..9, "mno")]);
-    assert_eq!(buffer.text(), "ghiamnoef");
-}
-
-#[gpui::test(iterations = 100)]
-fn test_random_edits(mut rng: StdRng) {
-    let operations = env::var("OPERATIONS")
-        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
-        .unwrap_or(10);
-
-    let reference_string_len = rng.gen_range(0..3);
-    let mut reference_string = RandomCharIter::new(&mut rng)
-        .take(reference_string_len)
-        .collect::<String>();
-    let mut buffer = Buffer::new(0, 0, reference_string.clone());
-    LineEnding::normalize(&mut reference_string);
-
-    buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
-    let mut buffer_versions = Vec::new();
-    log::info!(
-        "buffer text {:?}, version: {:?}",
-        buffer.text(),
-        buffer.version()
-    );
-
-    for _i in 0..operations {
-        let (edits, _) = buffer.randomly_edit(&mut rng, 5);
-        for (old_range, new_text) in edits.iter().rev() {
-            reference_string.replace_range(old_range.clone(), new_text);
-        }
-
-        assert_eq!(buffer.text(), reference_string);
-        log::info!(
-            "buffer text {:?}, version: {:?}",
-            buffer.text(),
-            buffer.version()
-        );
-
-        if rng.gen_bool(0.25) {
-            buffer.randomly_undo_redo(&mut rng);
-            reference_string = buffer.text();
-            log::info!(
-                "buffer text {:?}, version: {:?}",
-                buffer.text(),
-                buffer.version()
-            );
-        }
-
-        let range = buffer.random_byte_range(0, &mut rng);
-        assert_eq!(
-            buffer.text_summary_for_range::<TextSummary, _>(range.clone()),
-            TextSummary::from(&reference_string[range])
-        );
-
-        buffer.check_invariants();
-
-        if rng.gen_bool(0.3) {
-            buffer_versions.push((buffer.clone(), buffer.subscribe()));
-        }
-    }
-
-    for (old_buffer, subscription) in buffer_versions {
-        let edits = buffer
-            .edits_since::<usize>(&old_buffer.version)
-            .collect::<Vec<_>>();
-
-        log::info!(
-            "applying edits since version {:?} to old text: {:?}: {:?}",
-            old_buffer.version(),
-            old_buffer.text(),
-            edits,
-        );
-
-        let mut text = old_buffer.visible_text.clone();
-        for edit in edits {
-            let new_text: String = buffer.text_for_range(edit.new.clone()).collect();
-            text.replace(edit.new.start..edit.new.start + edit.old.len(), &new_text);
-        }
-        assert_eq!(text.to_string(), buffer.text());
-
-        for _ in 0..5 {
-            let end_ix = old_buffer.clip_offset(rng.gen_range(0..=old_buffer.len()), Bias::Right);
-            let start_ix = old_buffer.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
-            let range = old_buffer.anchor_before(start_ix)..old_buffer.anchor_after(end_ix);
-            let mut old_text = old_buffer.text_for_range(range.clone()).collect::<String>();
-            let edits = buffer
-                .edits_since_in_range::<usize>(&old_buffer.version, range.clone())
-                .collect::<Vec<_>>();
-            log::info!(
-                "applying edits since version {:?} to old text in range {:?}: {:?}: {:?}",
-                old_buffer.version(),
-                start_ix..end_ix,
-                old_text,
-                edits,
-            );
-
-            let new_text = buffer.text_for_range(range).collect::<String>();
-            for edit in edits {
-                old_text.replace_range(
-                    edit.new.start..edit.new.start + edit.old_len(),
-                    &new_text[edit.new],
-                );
-            }
-            assert_eq!(old_text, new_text);
-        }
-
-        let subscription_edits = subscription.consume();
-        log::info!(
-            "applying subscription edits since version {:?} to old text: {:?}: {:?}",
-            old_buffer.version(),
-            old_buffer.text(),
-            subscription_edits,
-        );
-
-        let mut text = old_buffer.visible_text.clone();
-        for edit in subscription_edits.into_inner() {
-            let new_text: String = buffer.text_for_range(edit.new.clone()).collect();
-            text.replace(edit.new.start..edit.new.start + edit.old.len(), &new_text);
-        }
-        assert_eq!(text.to_string(), buffer.text());
-    }
-}
-
-#[test]
-fn test_line_endings() {
-    assert_eq!(LineEnding::detect(&"🍐✅\n".repeat(1000)), LineEnding::Unix);
-    assert_eq!(LineEnding::detect(&"abcd\n".repeat(1000)), LineEnding::Unix);
-    assert_eq!(
-        LineEnding::detect(&"🍐✅\r\n".repeat(1000)),
-        LineEnding::Windows
-    );
-    assert_eq!(
-        LineEnding::detect(&"abcd\r\n".repeat(1000)),
-        LineEnding::Windows
-    );
-
-    let mut buffer = Buffer::new(0, 0, "one\r\ntwo\rthree".into());
-    assert_eq!(buffer.text(), "one\ntwo\nthree");
-    assert_eq!(buffer.line_ending(), LineEnding::Windows);
-    buffer.check_invariants();
-
-    buffer.edit([(buffer.len()..buffer.len(), "\r\nfour")]);
-    buffer.edit([(0..0, "zero\r\n")]);
-    assert_eq!(buffer.text(), "zero\none\ntwo\nthree\nfour");
-    assert_eq!(buffer.line_ending(), LineEnding::Windows);
-    buffer.check_invariants();
-}
-
-#[test]
-fn test_line_len() {
-    let mut buffer = Buffer::new(0, 0, "".into());
-    buffer.edit([(0..0, "abcd\nefg\nhij")]);
-    buffer.edit([(12..12, "kl\nmno")]);
-    buffer.edit([(18..18, "\npqrs\n")]);
-    buffer.edit([(18..21, "\nPQ")]);
-
-    assert_eq!(buffer.line_len(0), 4);
-    assert_eq!(buffer.line_len(1), 3);
-    assert_eq!(buffer.line_len(2), 5);
-    assert_eq!(buffer.line_len(3), 3);
-    assert_eq!(buffer.line_len(4), 4);
-    assert_eq!(buffer.line_len(5), 0);
-}
-
-#[test]
-fn test_common_prefix_at_position() {
-    let text = "a = str; b = δα";
-    let buffer = Buffer::new(0, 0, text.into());
-
-    let offset1 = offset_after(text, "str");
-    let offset2 = offset_after(text, "δα");
-
-    // the preceding word is a prefix of the suggestion
-    assert_eq!(
-        buffer.common_prefix_at(offset1, "string"),
-        range_of(text, "str"),
-    );
-    // a suffix of the preceding word is a prefix of the suggestion
-    assert_eq!(
-        buffer.common_prefix_at(offset1, "tree"),
-        range_of(text, "tr"),
-    );
-    // the preceding word is a substring of the suggestion, but not a prefix
-    assert_eq!(
-        buffer.common_prefix_at(offset1, "astro"),
-        empty_range_after(text, "str"),
-    );
-
-    // prefix matching is case insensitive.
-    assert_eq!(
-        buffer.common_prefix_at(offset1, "Strαngε"),
-        range_of(text, "str"),
-    );
-    assert_eq!(
-        buffer.common_prefix_at(offset2, "ΔΑΜΝ"),
-        range_of(text, "δα"),
-    );
-
-    fn offset_after(text: &str, part: &str) -> usize {
-        text.find(part).unwrap() + part.len()
-    }
-
-    fn empty_range_after(text: &str, part: &str) -> Range<usize> {
-        let offset = offset_after(text, part);
-        offset..offset
-    }
-
-    fn range_of(text: &str, part: &str) -> Range<usize> {
-        let start = text.find(part).unwrap();
-        start..start + part.len()
-    }
-}
-
-#[test]
-fn test_text_summary_for_range() {
-    let buffer = Buffer::new(0, 0, "ab\nefg\nhklm\nnopqrs\ntuvwxyz".into());
-    assert_eq!(
-        buffer.text_summary_for_range::<TextSummary, _>(1..3),
-        TextSummary {
-            len: 2,
-            len_utf16: OffsetUtf16(2),
-            lines: Point::new(1, 0),
-            first_line_chars: 1,
-            last_line_chars: 0,
-            last_line_len_utf16: 0,
-            longest_row: 0,
-            longest_row_chars: 1,
-        }
-    );
-    assert_eq!(
-        buffer.text_summary_for_range::<TextSummary, _>(1..12),
-        TextSummary {
-            len: 11,
-            len_utf16: OffsetUtf16(11),
-            lines: Point::new(3, 0),
-            first_line_chars: 1,
-            last_line_chars: 0,
-            last_line_len_utf16: 0,
-            longest_row: 2,
-            longest_row_chars: 4,
-        }
-    );
-    assert_eq!(
-        buffer.text_summary_for_range::<TextSummary, _>(0..20),
-        TextSummary {
-            len: 20,
-            len_utf16: OffsetUtf16(20),
-            lines: Point::new(4, 1),
-            first_line_chars: 2,
-            last_line_chars: 1,
-            last_line_len_utf16: 1,
-            longest_row: 3,
-            longest_row_chars: 6,
-        }
-    );
-    assert_eq!(
-        buffer.text_summary_for_range::<TextSummary, _>(0..22),
-        TextSummary {
-            len: 22,
-            len_utf16: OffsetUtf16(22),
-            lines: Point::new(4, 3),
-            first_line_chars: 2,
-            last_line_chars: 3,
-            last_line_len_utf16: 3,
-            longest_row: 3,
-            longest_row_chars: 6,
-        }
-    );
-    assert_eq!(
-        buffer.text_summary_for_range::<TextSummary, _>(7..22),
-        TextSummary {
-            len: 15,
-            len_utf16: OffsetUtf16(15),
-            lines: Point::new(2, 3),
-            first_line_chars: 4,
-            last_line_chars: 3,
-            last_line_len_utf16: 3,
-            longest_row: 1,
-            longest_row_chars: 6,
-        }
-    );
-}
-
-#[test]
-fn test_chars_at() {
-    let mut buffer = Buffer::new(0, 0, "".into());
-    buffer.edit([(0..0, "abcd\nefgh\nij")]);
-    buffer.edit([(12..12, "kl\nmno")]);
-    buffer.edit([(18..18, "\npqrs")]);
-    buffer.edit([(18..21, "\nPQ")]);
-
-    let chars = buffer.chars_at(Point::new(0, 0));
-    assert_eq!(chars.collect::<String>(), "abcd\nefgh\nijkl\nmno\nPQrs");
-
-    let chars = buffer.chars_at(Point::new(1, 0));
-    assert_eq!(chars.collect::<String>(), "efgh\nijkl\nmno\nPQrs");
-
-    let chars = buffer.chars_at(Point::new(2, 0));
-    assert_eq!(chars.collect::<String>(), "ijkl\nmno\nPQrs");
-
-    let chars = buffer.chars_at(Point::new(3, 0));
-    assert_eq!(chars.collect::<String>(), "mno\nPQrs");
-
-    let chars = buffer.chars_at(Point::new(4, 0));
-    assert_eq!(chars.collect::<String>(), "PQrs");
-
-    // Regression test:
-    let mut buffer = Buffer::new(0, 0, "".into());
-    buffer.edit([(0..0, "[workspace]\nmembers = [\n    \"xray_core\",\n    \"xray_server\",\n    \"xray_cli\",\n    \"xray_wasm\",\n]\n")]);
-    buffer.edit([(60..60, "\n")]);
-
-    let chars = buffer.chars_at(Point::new(6, 0));
-    assert_eq!(chars.collect::<String>(), "    \"xray_wasm\",\n]\n");
-}
-
-#[test]
-fn test_anchors() {
-    let mut buffer = Buffer::new(0, 0, "".into());
-    buffer.edit([(0..0, "abc")]);
-    let left_anchor = buffer.anchor_before(2);
-    let right_anchor = buffer.anchor_after(2);
-
-    buffer.edit([(1..1, "def\n")]);
-    assert_eq!(buffer.text(), "adef\nbc");
-    assert_eq!(left_anchor.to_offset(&buffer), 6);
-    assert_eq!(right_anchor.to_offset(&buffer), 6);
-    assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 });
-    assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 });
-
-    buffer.edit([(2..3, "")]);
-    assert_eq!(buffer.text(), "adf\nbc");
-    assert_eq!(left_anchor.to_offset(&buffer), 5);
-    assert_eq!(right_anchor.to_offset(&buffer), 5);
-    assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 });
-    assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 });
-
-    buffer.edit([(5..5, "ghi\n")]);
-    assert_eq!(buffer.text(), "adf\nbghi\nc");
-    assert_eq!(left_anchor.to_offset(&buffer), 5);
-    assert_eq!(right_anchor.to_offset(&buffer), 9);
-    assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 });
-    assert_eq!(right_anchor.to_point(&buffer), Point { row: 2, column: 0 });
-
-    buffer.edit([(7..9, "")]);
-    assert_eq!(buffer.text(), "adf\nbghc");
-    assert_eq!(left_anchor.to_offset(&buffer), 5);
-    assert_eq!(right_anchor.to_offset(&buffer), 7);
-    assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 },);
-    assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 3 });
-
-    // Ensure anchoring to a point is equivalent to anchoring to an offset.
-    assert_eq!(
-        buffer.anchor_before(Point { row: 0, column: 0 }),
-        buffer.anchor_before(0)
-    );
-    assert_eq!(
-        buffer.anchor_before(Point { row: 0, column: 1 }),
-        buffer.anchor_before(1)
-    );
-    assert_eq!(
-        buffer.anchor_before(Point { row: 0, column: 2 }),
-        buffer.anchor_before(2)
-    );
-    assert_eq!(
-        buffer.anchor_before(Point { row: 0, column: 3 }),
-        buffer.anchor_before(3)
-    );
-    assert_eq!(
-        buffer.anchor_before(Point { row: 1, column: 0 }),
-        buffer.anchor_before(4)
-    );
-    assert_eq!(
-        buffer.anchor_before(Point { row: 1, column: 1 }),
-        buffer.anchor_before(5)
-    );
-    assert_eq!(
-        buffer.anchor_before(Point { row: 1, column: 2 }),
-        buffer.anchor_before(6)
-    );
-    assert_eq!(
-        buffer.anchor_before(Point { row: 1, column: 3 }),
-        buffer.anchor_before(7)
-    );
-    assert_eq!(
-        buffer.anchor_before(Point { row: 1, column: 4 }),
-        buffer.anchor_before(8)
-    );
-
-    // Comparison between anchors.
-    let anchor_at_offset_0 = buffer.anchor_before(0);
-    let anchor_at_offset_1 = buffer.anchor_before(1);
-    let anchor_at_offset_2 = buffer.anchor_before(2);
-
-    assert_eq!(
-        anchor_at_offset_0.cmp(&anchor_at_offset_0, &buffer),
-        Ordering::Equal
-    );
-    assert_eq!(
-        anchor_at_offset_1.cmp(&anchor_at_offset_1, &buffer),
-        Ordering::Equal
-    );
-    assert_eq!(
-        anchor_at_offset_2.cmp(&anchor_at_offset_2, &buffer),
-        Ordering::Equal
-    );
-
-    assert_eq!(
-        anchor_at_offset_0.cmp(&anchor_at_offset_1, &buffer),
-        Ordering::Less
-    );
-    assert_eq!(
-        anchor_at_offset_1.cmp(&anchor_at_offset_2, &buffer),
-        Ordering::Less
-    );
-    assert_eq!(
-        anchor_at_offset_0.cmp(&anchor_at_offset_2, &buffer),
-        Ordering::Less
-    );
-
-    assert_eq!(
-        anchor_at_offset_1.cmp(&anchor_at_offset_0, &buffer),
-        Ordering::Greater
-    );
-    assert_eq!(
-        anchor_at_offset_2.cmp(&anchor_at_offset_1, &buffer),
-        Ordering::Greater
-    );
-    assert_eq!(
-        anchor_at_offset_2.cmp(&anchor_at_offset_0, &buffer),
-        Ordering::Greater
-    );
-}
-
-#[test]
-fn test_anchors_at_start_and_end() {
-    let mut buffer = Buffer::new(0, 0, "".into());
-    let before_start_anchor = buffer.anchor_before(0);
-    let after_end_anchor = buffer.anchor_after(0);
-
-    buffer.edit([(0..0, "abc")]);
-    assert_eq!(buffer.text(), "abc");
-    assert_eq!(before_start_anchor.to_offset(&buffer), 0);
-    assert_eq!(after_end_anchor.to_offset(&buffer), 3);
-
-    let after_start_anchor = buffer.anchor_after(0);
-    let before_end_anchor = buffer.anchor_before(3);
-
-    buffer.edit([(3..3, "def")]);
-    buffer.edit([(0..0, "ghi")]);
-    assert_eq!(buffer.text(), "ghiabcdef");
-    assert_eq!(before_start_anchor.to_offset(&buffer), 0);
-    assert_eq!(after_start_anchor.to_offset(&buffer), 3);
-    assert_eq!(before_end_anchor.to_offset(&buffer), 6);
-    assert_eq!(after_end_anchor.to_offset(&buffer), 9);
-}
-
-#[test]
-fn test_undo_redo() {
-    let mut buffer = Buffer::new(0, 0, "1234".into());
-    // Set group interval to zero so as to not group edits in the undo stack.
-    buffer.set_group_interval(Duration::from_secs(0));
-
-    buffer.edit([(1..1, "abx")]);
-    buffer.edit([(3..4, "yzef")]);
-    buffer.edit([(3..5, "cd")]);
-    assert_eq!(buffer.text(), "1abcdef234");
-
-    let entries = buffer.history.undo_stack.clone();
-    assert_eq!(entries.len(), 3);
-
-    buffer.undo_or_redo(entries[0].transaction.clone()).unwrap();
-    assert_eq!(buffer.text(), "1cdef234");
-    buffer.undo_or_redo(entries[0].transaction.clone()).unwrap();
-    assert_eq!(buffer.text(), "1abcdef234");
-
-    buffer.undo_or_redo(entries[1].transaction.clone()).unwrap();
-    assert_eq!(buffer.text(), "1abcdx234");
-    buffer.undo_or_redo(entries[2].transaction.clone()).unwrap();
-    assert_eq!(buffer.text(), "1abx234");
-    buffer.undo_or_redo(entries[1].transaction.clone()).unwrap();
-    assert_eq!(buffer.text(), "1abyzef234");
-    buffer.undo_or_redo(entries[2].transaction.clone()).unwrap();
-    assert_eq!(buffer.text(), "1abcdef234");
-
-    buffer.undo_or_redo(entries[2].transaction.clone()).unwrap();
-    assert_eq!(buffer.text(), "1abyzef234");
-    buffer.undo_or_redo(entries[0].transaction.clone()).unwrap();
-    assert_eq!(buffer.text(), "1yzef234");
-    buffer.undo_or_redo(entries[1].transaction.clone()).unwrap();
-    assert_eq!(buffer.text(), "1234");
-}
-
-#[test]
-fn test_history() {
-    let mut now = Instant::now();
-    let mut buffer = Buffer::new(0, 0, "123456".into());
-    buffer.set_group_interval(Duration::from_millis(300));
-
-    let transaction_1 = buffer.start_transaction_at(now).unwrap();
-    buffer.edit([(2..4, "cd")]);
-    buffer.end_transaction_at(now);
-    assert_eq!(buffer.text(), "12cd56");
-
-    buffer.start_transaction_at(now);
-    buffer.edit([(4..5, "e")]);
-    buffer.end_transaction_at(now).unwrap();
-    assert_eq!(buffer.text(), "12cde6");
-
-    now += buffer.transaction_group_interval() + Duration::from_millis(1);
-    buffer.start_transaction_at(now);
-    buffer.edit([(0..1, "a")]);
-    buffer.edit([(1..1, "b")]);
-    buffer.end_transaction_at(now).unwrap();
-    assert_eq!(buffer.text(), "ab2cde6");
-
-    // Last transaction happened past the group interval, undo it on its own.
-    buffer.undo();
-    assert_eq!(buffer.text(), "12cde6");
-
-    // First two transactions happened within the group interval, undo them together.
-    buffer.undo();
-    assert_eq!(buffer.text(), "123456");
-
-    // Redo the first two transactions together.
-    buffer.redo();
-    assert_eq!(buffer.text(), "12cde6");
-
-    // Redo the last transaction on its own.
-    buffer.redo();
-    assert_eq!(buffer.text(), "ab2cde6");
-
-    buffer.start_transaction_at(now);
-    assert!(buffer.end_transaction_at(now).is_none());
-    buffer.undo();
-    assert_eq!(buffer.text(), "12cde6");
-
-    // Redo stack gets cleared after performing an edit.
-    buffer.start_transaction_at(now);
-    buffer.edit([(0..0, "X")]);
-    buffer.end_transaction_at(now);
-    assert_eq!(buffer.text(), "X12cde6");
-    buffer.redo();
-    assert_eq!(buffer.text(), "X12cde6");
-    buffer.undo();
-    assert_eq!(buffer.text(), "12cde6");
-    buffer.undo();
-    assert_eq!(buffer.text(), "123456");
-
-    // Transactions can be grouped manually.
-    buffer.redo();
-    buffer.redo();
-    assert_eq!(buffer.text(), "X12cde6");
-    buffer.group_until_transaction(transaction_1);
-    buffer.undo();
-    assert_eq!(buffer.text(), "123456");
-    buffer.redo();
-    assert_eq!(buffer.text(), "X12cde6");
-}
-
-#[test]
-fn test_finalize_last_transaction() {
-    let now = Instant::now();
-    let mut buffer = Buffer::new(0, 0, "123456".into());
-
-    buffer.start_transaction_at(now);
-    buffer.edit([(2..4, "cd")]);
-    buffer.end_transaction_at(now);
-    assert_eq!(buffer.text(), "12cd56");
-
-    buffer.finalize_last_transaction();
-    buffer.start_transaction_at(now);
-    buffer.edit([(4..5, "e")]);
-    buffer.end_transaction_at(now).unwrap();
-    assert_eq!(buffer.text(), "12cde6");
-
-    buffer.start_transaction_at(now);
-    buffer.edit([(0..1, "a")]);
-    buffer.edit([(1..1, "b")]);
-    buffer.end_transaction_at(now).unwrap();
-    assert_eq!(buffer.text(), "ab2cde6");
-
-    buffer.undo();
-    assert_eq!(buffer.text(), "12cd56");
-
-    buffer.undo();
-    assert_eq!(buffer.text(), "123456");
-
-    buffer.redo();
-    assert_eq!(buffer.text(), "12cd56");
-
-    buffer.redo();
-    assert_eq!(buffer.text(), "ab2cde6");
-}
-
-#[test]
-fn test_edited_ranges_for_transaction() {
-    let now = Instant::now();
-    let mut buffer = Buffer::new(0, 0, "1234567".into());
-
-    buffer.start_transaction_at(now);
-    buffer.edit([(2..4, "cd")]);
-    buffer.edit([(6..6, "efg")]);
-    buffer.end_transaction_at(now);
-    assert_eq!(buffer.text(), "12cd56efg7");
-
-    let tx = buffer.finalize_last_transaction().unwrap().clone();
-    assert_eq!(
-        buffer
-            .edited_ranges_for_transaction::<usize>(&tx)
-            .collect::<Vec<_>>(),
-        [2..4, 6..9]
-    );
-
-    buffer.edit([(5..5, "hijk")]);
-    assert_eq!(buffer.text(), "12cd5hijk6efg7");
-    assert_eq!(
-        buffer
-            .edited_ranges_for_transaction::<usize>(&tx)
-            .collect::<Vec<_>>(),
-        [2..4, 10..13]
-    );
-
-    buffer.edit([(4..4, "l")]);
-    assert_eq!(buffer.text(), "12cdl5hijk6efg7");
-    assert_eq!(
-        buffer
-            .edited_ranges_for_transaction::<usize>(&tx)
-            .collect::<Vec<_>>(),
-        [2..4, 11..14]
-    );
-}
-
-#[test]
-fn test_concurrent_edits() {
-    let text = "abcdef";
-
-    let mut buffer1 = Buffer::new(1, 0, text.into());
-    let mut buffer2 = Buffer::new(2, 0, text.into());
-    let mut buffer3 = Buffer::new(3, 0, text.into());
-
-    let buf1_op = buffer1.edit([(1..2, "12")]);
-    assert_eq!(buffer1.text(), "a12cdef");
-    let buf2_op = buffer2.edit([(3..4, "34")]);
-    assert_eq!(buffer2.text(), "abc34ef");
-    let buf3_op = buffer3.edit([(5..6, "56")]);
-    assert_eq!(buffer3.text(), "abcde56");
-
-    buffer1.apply_op(buf2_op.clone()).unwrap();
-    buffer1.apply_op(buf3_op.clone()).unwrap();
-    buffer2.apply_op(buf1_op.clone()).unwrap();
-    buffer2.apply_op(buf3_op).unwrap();
-    buffer3.apply_op(buf1_op).unwrap();
-    buffer3.apply_op(buf2_op).unwrap();
-
-    assert_eq!(buffer1.text(), "a12c34e56");
-    assert_eq!(buffer2.text(), "a12c34e56");
-    assert_eq!(buffer3.text(), "a12c34e56");
-}
-
-#[gpui::test(iterations = 100)]
-fn test_random_concurrent_edits(mut rng: StdRng) {
-    let peers = env::var("PEERS")
-        .map(|i| i.parse().expect("invalid `PEERS` variable"))
-        .unwrap_or(5);
-    let operations = env::var("OPERATIONS")
-        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
-        .unwrap_or(10);
-
-    let base_text_len = rng.gen_range(0..10);
-    let base_text = RandomCharIter::new(&mut rng)
-        .take(base_text_len)
-        .collect::<String>();
-    let mut replica_ids = Vec::new();
-    let mut buffers = Vec::new();
-    let mut network = Network::new(rng.clone());
-
-    for i in 0..peers {
-        let mut buffer = Buffer::new(i as ReplicaId, 0, base_text.clone());
-        buffer.history.group_interval = Duration::from_millis(rng.gen_range(0..=200));
-        buffers.push(buffer);
-        replica_ids.push(i as u16);
-        network.add_peer(i as u16);
-    }
-
-    log::info!("initial text: {:?}", base_text);
-
-    let mut mutation_count = operations;
-    loop {
-        let replica_index = rng.gen_range(0..peers);
-        let replica_id = replica_ids[replica_index];
-        let buffer = &mut buffers[replica_index];
-        match rng.gen_range(0..=100) {
-            0..=50 if mutation_count != 0 => {
-                let op = buffer.randomly_edit(&mut rng, 5).1;
-                network.broadcast(buffer.replica_id, vec![op]);
-                log::info!("buffer {} text: {:?}", buffer.replica_id, buffer.text());
-                mutation_count -= 1;
-            }
-            51..=70 if mutation_count != 0 => {
-                let ops = buffer.randomly_undo_redo(&mut rng);
-                network.broadcast(buffer.replica_id, ops);
-                mutation_count -= 1;
-            }
-            71..=100 if network.has_unreceived(replica_id) => {
-                let ops = network.receive(replica_id);
-                if !ops.is_empty() {
-                    log::info!(
-                        "peer {} applying {} ops from the network.",
-                        replica_id,
-                        ops.len()
-                    );
-                    buffer.apply_ops(ops).unwrap();
-                }
-            }
-            _ => {}
-        }
-        buffer.check_invariants();
-
-        if mutation_count == 0 && network.is_idle() {
-            break;
-        }
-    }
-
-    let first_buffer = &buffers[0];
-    for buffer in &buffers[1..] {
-        assert_eq!(
-            buffer.text(),
-            first_buffer.text(),
-            "Replica {} text != Replica 0 text",
-            buffer.replica_id
-        );
-        buffer.check_invariants();
-    }
-}

crates/text2/src/text2.rs 🔗

@@ -1,2679 +0,0 @@
-mod anchor;
-pub mod locator;
-#[cfg(any(test, feature = "test-support"))]
-pub mod network;
-pub mod operation_queue;
-mod patch;
-mod selection;
-pub mod subscription;
-#[cfg(test)]
-mod tests;
-mod undo_map;
-
-pub use anchor::*;
-use anyhow::{anyhow, Result};
-pub use clock::ReplicaId;
-use collections::{HashMap, HashSet};
-use locator::Locator;
-use operation_queue::OperationQueue;
-pub use patch::Patch;
-use postage::{oneshot, prelude::*};
-
-use lazy_static::lazy_static;
-use regex::Regex;
-pub use rope::*;
-pub use selection::*;
-use std::{
-    borrow::Cow,
-    cmp::{self, Ordering, Reverse},
-    future::Future,
-    iter::Iterator,
-    ops::{self, Deref, Range, Sub},
-    str,
-    sync::Arc,
-    time::{Duration, Instant},
-};
-pub use subscription::*;
-pub use sum_tree::Bias;
-use sum_tree::{FilterCursor, SumTree, TreeMap};
-use undo_map::UndoMap;
-use util::ResultExt;
-
-#[cfg(any(test, feature = "test-support"))]
-use util::RandomCharIter;
-
-lazy_static! {
-    static ref LINE_SEPARATORS_REGEX: Regex = Regex::new("\r\n|\r|\u{2028}|\u{2029}").unwrap();
-}
-
-pub type TransactionId = clock::Lamport;
-
-pub struct Buffer {
-    snapshot: BufferSnapshot,
-    history: History,
-    deferred_ops: OperationQueue<Operation>,
-    deferred_replicas: HashSet<ReplicaId>,
-    pub lamport_clock: clock::Lamport,
-    subscriptions: Topic,
-    edit_id_resolvers: HashMap<clock::Lamport, Vec<oneshot::Sender<()>>>,
-    wait_for_version_txs: Vec<(clock::Global, oneshot::Sender<()>)>,
-}
-
-#[derive(Clone)]
-pub struct BufferSnapshot {
-    replica_id: ReplicaId,
-    remote_id: u64,
-    visible_text: Rope,
-    deleted_text: Rope,
-    line_ending: LineEnding,
-    undo_map: UndoMap,
-    fragments: SumTree<Fragment>,
-    insertions: SumTree<InsertionFragment>,
-    pub version: clock::Global,
-}
-
-#[derive(Clone, Debug)]
-pub struct HistoryEntry {
-    transaction: Transaction,
-    first_edit_at: Instant,
-    last_edit_at: Instant,
-    suppress_grouping: bool,
-}
-
-#[derive(Clone, Debug)]
-pub struct Transaction {
-    pub id: TransactionId,
-    pub edit_ids: Vec<clock::Lamport>,
-    pub start: clock::Global,
-}
-
-impl HistoryEntry {
-    pub fn transaction_id(&self) -> TransactionId {
-        self.transaction.id
-    }
-}
-
-struct History {
-    base_text: Rope,
-    operations: TreeMap<clock::Lamport, Operation>,
-    insertion_slices: HashMap<clock::Lamport, Vec<InsertionSlice>>,
-    undo_stack: Vec<HistoryEntry>,
-    redo_stack: Vec<HistoryEntry>,
-    transaction_depth: usize,
-    group_interval: Duration,
-}
-
-#[derive(Clone, Debug)]
-struct InsertionSlice {
-    insertion_id: clock::Lamport,
-    range: Range<usize>,
-}
-
-impl History {
-    pub fn new(base_text: Rope) -> Self {
-        Self {
-            base_text,
-            operations: Default::default(),
-            insertion_slices: Default::default(),
-            undo_stack: Vec::new(),
-            redo_stack: Vec::new(),
-            transaction_depth: 0,
-            // Don't group transactions in tests unless we opt in, because it's a footgun.
-            #[cfg(any(test, feature = "test-support"))]
-            group_interval: Duration::ZERO,
-            #[cfg(not(any(test, feature = "test-support")))]
-            group_interval: Duration::from_millis(300),
-        }
-    }
-
-    fn push(&mut self, op: Operation) {
-        self.operations.insert(op.timestamp(), op);
-    }
-
-    fn start_transaction(
-        &mut self,
-        start: clock::Global,
-        now: Instant,
-        clock: &mut clock::Lamport,
-    ) -> Option<TransactionId> {
-        self.transaction_depth += 1;
-        if self.transaction_depth == 1 {
-            let id = clock.tick();
-            self.undo_stack.push(HistoryEntry {
-                transaction: Transaction {
-                    id,
-                    start,
-                    edit_ids: Default::default(),
-                },
-                first_edit_at: now,
-                last_edit_at: now,
-                suppress_grouping: false,
-            });
-            Some(id)
-        } else {
-            None
-        }
-    }
-
-    fn end_transaction(&mut self, now: Instant) -> Option<&HistoryEntry> {
-        assert_ne!(self.transaction_depth, 0);
-        self.transaction_depth -= 1;
-        if self.transaction_depth == 0 {
-            if self
-                .undo_stack
-                .last()
-                .unwrap()
-                .transaction
-                .edit_ids
-                .is_empty()
-            {
-                self.undo_stack.pop();
-                None
-            } else {
-                self.redo_stack.clear();
-                let entry = self.undo_stack.last_mut().unwrap();
-                entry.last_edit_at = now;
-                Some(entry)
-            }
-        } else {
-            None
-        }
-    }
-
-    fn group(&mut self) -> Option<TransactionId> {
-        let mut count = 0;
-        let mut entries = self.undo_stack.iter();
-        if let Some(mut entry) = entries.next_back() {
-            while let Some(prev_entry) = entries.next_back() {
-                if !prev_entry.suppress_grouping
-                    && entry.first_edit_at - prev_entry.last_edit_at <= self.group_interval
-                {
-                    entry = prev_entry;
-                    count += 1;
-                } else {
-                    break;
-                }
-            }
-        }
-        self.group_trailing(count)
-    }
-
-    fn group_until(&mut self, transaction_id: TransactionId) {
-        let mut count = 0;
-        for entry in self.undo_stack.iter().rev() {
-            if entry.transaction_id() == transaction_id {
-                self.group_trailing(count);
-                break;
-            } else if entry.suppress_grouping {
-                break;
-            } else {
-                count += 1;
-            }
-        }
-    }
-
-    fn group_trailing(&mut self, n: usize) -> Option<TransactionId> {
-        let new_len = self.undo_stack.len() - n;
-        let (entries_to_keep, entries_to_merge) = self.undo_stack.split_at_mut(new_len);
-        if let Some(last_entry) = entries_to_keep.last_mut() {
-            for entry in &*entries_to_merge {
-                for edit_id in &entry.transaction.edit_ids {
-                    last_entry.transaction.edit_ids.push(*edit_id);
-                }
-            }
-
-            if let Some(entry) = entries_to_merge.last_mut() {
-                last_entry.last_edit_at = entry.last_edit_at;
-            }
-        }
-
-        self.undo_stack.truncate(new_len);
-        self.undo_stack.last().map(|e| e.transaction.id)
-    }
-
-    fn finalize_last_transaction(&mut self) -> Option<&Transaction> {
-        self.undo_stack.last_mut().map(|entry| {
-            entry.suppress_grouping = true;
-            &entry.transaction
-        })
-    }
-
-    fn push_transaction(&mut self, transaction: Transaction, now: Instant) {
-        assert_eq!(self.transaction_depth, 0);
-        self.undo_stack.push(HistoryEntry {
-            transaction,
-            first_edit_at: now,
-            last_edit_at: now,
-            suppress_grouping: false,
-        });
-        self.redo_stack.clear();
-    }
-
-    fn push_undo(&mut self, op_id: clock::Lamport) {
-        assert_ne!(self.transaction_depth, 0);
-        if let Some(Operation::Edit(_)) = self.operations.get(&op_id) {
-            let last_transaction = self.undo_stack.last_mut().unwrap();
-            last_transaction.transaction.edit_ids.push(op_id);
-        }
-    }
-
-    fn pop_undo(&mut self) -> Option<&HistoryEntry> {
-        assert_eq!(self.transaction_depth, 0);
-        if let Some(entry) = self.undo_stack.pop() {
-            self.redo_stack.push(entry);
-            self.redo_stack.last()
-        } else {
-            None
-        }
-    }
-
-    fn remove_from_undo(&mut self, transaction_id: TransactionId) -> Option<&HistoryEntry> {
-        assert_eq!(self.transaction_depth, 0);
-
-        let entry_ix = self
-            .undo_stack
-            .iter()
-            .rposition(|entry| entry.transaction.id == transaction_id)?;
-        let entry = self.undo_stack.remove(entry_ix);
-        self.redo_stack.push(entry);
-        self.redo_stack.last()
-    }
-
-    fn remove_from_undo_until(&mut self, transaction_id: TransactionId) -> &[HistoryEntry] {
-        assert_eq!(self.transaction_depth, 0);
-
-        let redo_stack_start_len = self.redo_stack.len();
-        if let Some(entry_ix) = self
-            .undo_stack
-            .iter()
-            .rposition(|entry| entry.transaction.id == transaction_id)
-        {
-            self.redo_stack
-                .extend(self.undo_stack.drain(entry_ix..).rev());
-        }
-        &self.redo_stack[redo_stack_start_len..]
-    }
-
-    fn forget(&mut self, transaction_id: TransactionId) -> Option<Transaction> {
-        assert_eq!(self.transaction_depth, 0);
-        if let Some(entry_ix) = self
-            .undo_stack
-            .iter()
-            .rposition(|entry| entry.transaction.id == transaction_id)
-        {
-            Some(self.undo_stack.remove(entry_ix).transaction)
-        } else if let Some(entry_ix) = self
-            .redo_stack
-            .iter()
-            .rposition(|entry| entry.transaction.id == transaction_id)
-        {
-            Some(self.redo_stack.remove(entry_ix).transaction)
-        } else {
-            None
-        }
-    }
-
-    fn transaction_mut(&mut self, transaction_id: TransactionId) -> Option<&mut Transaction> {
-        let entry = self
-            .undo_stack
-            .iter_mut()
-            .rfind(|entry| entry.transaction.id == transaction_id)
-            .or_else(|| {
-                self.redo_stack
-                    .iter_mut()
-                    .rfind(|entry| entry.transaction.id == transaction_id)
-            })?;
-        Some(&mut entry.transaction)
-    }
-
-    fn merge_transactions(&mut self, transaction: TransactionId, destination: TransactionId) {
-        if let Some(transaction) = self.forget(transaction) {
-            if let Some(destination) = self.transaction_mut(destination) {
-                destination.edit_ids.extend(transaction.edit_ids);
-            }
-        }
-    }
-
-    fn pop_redo(&mut self) -> Option<&HistoryEntry> {
-        assert_eq!(self.transaction_depth, 0);
-        if let Some(entry) = self.redo_stack.pop() {
-            self.undo_stack.push(entry);
-            self.undo_stack.last()
-        } else {
-            None
-        }
-    }
-
-    fn remove_from_redo(&mut self, transaction_id: TransactionId) -> &[HistoryEntry] {
-        assert_eq!(self.transaction_depth, 0);
-
-        let undo_stack_start_len = self.undo_stack.len();
-        if let Some(entry_ix) = self
-            .redo_stack
-            .iter()
-            .rposition(|entry| entry.transaction.id == transaction_id)
-        {
-            self.undo_stack
-                .extend(self.redo_stack.drain(entry_ix..).rev());
-        }
-        &self.undo_stack[undo_stack_start_len..]
-    }
-}
-
-struct Edits<'a, D: TextDimension, F: FnMut(&FragmentSummary) -> bool> {
-    visible_cursor: rope::Cursor<'a>,
-    deleted_cursor: rope::Cursor<'a>,
-    fragments_cursor: Option<FilterCursor<'a, F, Fragment, FragmentTextSummary>>,
-    undos: &'a UndoMap,
-    since: &'a clock::Global,
-    old_end: D,
-    new_end: D,
-    range: Range<(&'a Locator, usize)>,
-    buffer_id: u64,
-}
-
-#[derive(Clone, Debug, Default, Eq, PartialEq)]
-pub struct Edit<D> {
-    pub old: Range<D>,
-    pub new: Range<D>,
-}
-
-impl<D> Edit<D>
-where
-    D: Sub<D, Output = D> + PartialEq + Copy,
-{
-    pub fn old_len(&self) -> D {
-        self.old.end - self.old.start
-    }
-
-    pub fn new_len(&self) -> D {
-        self.new.end - self.new.start
-    }
-
-    pub fn is_empty(&self) -> bool {
-        self.old.start == self.old.end && self.new.start == self.new.end
-    }
-}
-
-impl<D1, D2> Edit<(D1, D2)> {
-    pub fn flatten(self) -> (Edit<D1>, Edit<D2>) {
-        (
-            Edit {
-                old: self.old.start.0..self.old.end.0,
-                new: self.new.start.0..self.new.end.0,
-            },
-            Edit {
-                old: self.old.start.1..self.old.end.1,
-                new: self.new.start.1..self.new.end.1,
-            },
-        )
-    }
-}
-
-#[derive(Eq, PartialEq, Clone, Debug)]
-pub struct Fragment {
-    pub id: Locator,
-    pub timestamp: clock::Lamport,
-    pub insertion_offset: usize,
-    pub len: usize,
-    pub visible: bool,
-    pub deletions: HashSet<clock::Lamport>,
-    pub max_undos: clock::Global,
-}
-
-#[derive(Eq, PartialEq, Clone, Debug)]
-pub struct FragmentSummary {
-    text: FragmentTextSummary,
-    max_id: Locator,
-    max_version: clock::Global,
-    min_insertion_version: clock::Global,
-    max_insertion_version: clock::Global,
-}
-
-#[derive(Copy, Default, Clone, Debug, PartialEq, Eq)]
-struct FragmentTextSummary {
-    visible: usize,
-    deleted: usize,
-}
-
-impl<'a> sum_tree::Dimension<'a, FragmentSummary> for FragmentTextSummary {
-    fn add_summary(&mut self, summary: &'a FragmentSummary, _: &Option<clock::Global>) {
-        self.visible += summary.text.visible;
-        self.deleted += summary.text.deleted;
-    }
-}
-
-#[derive(Eq, PartialEq, Clone, Debug)]
-struct InsertionFragment {
-    timestamp: clock::Lamport,
-    split_offset: usize,
-    fragment_id: Locator,
-}
-
-#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
-struct InsertionFragmentKey {
-    timestamp: clock::Lamport,
-    split_offset: usize,
-}
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub enum Operation {
-    Edit(EditOperation),
-    Undo(UndoOperation),
-}
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct EditOperation {
-    pub timestamp: clock::Lamport,
-    pub version: clock::Global,
-    pub ranges: Vec<Range<FullOffset>>,
-    pub new_text: Vec<Arc<str>>,
-}
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct UndoOperation {
-    pub timestamp: clock::Lamport,
-    pub version: clock::Global,
-    pub counts: HashMap<clock::Lamport, u32>,
-}
-
-impl Buffer {
-    pub fn new(replica_id: u16, remote_id: u64, mut base_text: String) -> Buffer {
-        let line_ending = LineEnding::detect(&base_text);
-        LineEnding::normalize(&mut base_text);
-
-        let history = History::new(Rope::from(base_text.as_ref()));
-        let mut fragments = SumTree::new();
-        let mut insertions = SumTree::new();
-
-        let mut lamport_clock = clock::Lamport::new(replica_id);
-        let mut version = clock::Global::new();
-
-        let visible_text = history.base_text.clone();
-        if !visible_text.is_empty() {
-            let insertion_timestamp = clock::Lamport {
-                replica_id: 0,
-                value: 1,
-            };
-            lamport_clock.observe(insertion_timestamp);
-            version.observe(insertion_timestamp);
-            let fragment_id = Locator::between(&Locator::min(), &Locator::max());
-            let fragment = Fragment {
-                id: fragment_id,
-                timestamp: insertion_timestamp,
-                insertion_offset: 0,
-                len: visible_text.len(),
-                visible: true,
-                deletions: Default::default(),
-                max_undos: Default::default(),
-            };
-            insertions.push(InsertionFragment::new(&fragment), &());
-            fragments.push(fragment, &None);
-        }
-
-        Buffer {
-            snapshot: BufferSnapshot {
-                replica_id,
-                remote_id,
-                visible_text,
-                deleted_text: Rope::new(),
-                line_ending,
-                fragments,
-                insertions,
-                version,
-                undo_map: Default::default(),
-            },
-            history,
-            deferred_ops: OperationQueue::new(),
-            deferred_replicas: HashSet::default(),
-            lamport_clock,
-            subscriptions: Default::default(),
-            edit_id_resolvers: Default::default(),
-            wait_for_version_txs: Default::default(),
-        }
-    }
-
-    pub fn version(&self) -> clock::Global {
-        self.version.clone()
-    }
-
-    pub fn snapshot(&self) -> BufferSnapshot {
-        self.snapshot.clone()
-    }
-
-    pub fn replica_id(&self) -> ReplicaId {
-        self.lamport_clock.replica_id
-    }
-
-    pub fn remote_id(&self) -> u64 {
-        self.remote_id
-    }
-
-    pub fn deferred_ops_len(&self) -> usize {
-        self.deferred_ops.len()
-    }
-
-    pub fn transaction_group_interval(&self) -> Duration {
-        self.history.group_interval
-    }
-
-    pub fn edit<R, I, S, T>(&mut self, edits: R) -> Operation
-    where
-        R: IntoIterator<IntoIter = I>,
-        I: ExactSizeIterator<Item = (Range<S>, T)>,
-        S: ToOffset,
-        T: Into<Arc<str>>,
-    {
-        let edits = edits
-            .into_iter()
-            .map(|(range, new_text)| (range, new_text.into()));
-
-        self.start_transaction();
-        let timestamp = self.lamport_clock.tick();
-        let operation = Operation::Edit(self.apply_local_edit(edits, timestamp));
-
-        self.history.push(operation.clone());
-        self.history.push_undo(operation.timestamp());
-        self.snapshot.version.observe(operation.timestamp());
-        self.end_transaction();
-        operation
-    }
-
-    fn apply_local_edit<S: ToOffset, T: Into<Arc<str>>>(
-        &mut self,
-        edits: impl ExactSizeIterator<Item = (Range<S>, T)>,
-        timestamp: clock::Lamport,
-    ) -> EditOperation {
-        let mut edits_patch = Patch::default();
-        let mut edit_op = EditOperation {
-            timestamp,
-            version: self.version(),
-            ranges: Vec::with_capacity(edits.len()),
-            new_text: Vec::with_capacity(edits.len()),
-        };
-        let mut new_insertions = Vec::new();
-        let mut insertion_offset = 0;
-        let mut insertion_slices = Vec::new();
-
-        let mut edits = edits
-            .map(|(range, new_text)| (range.to_offset(&*self), new_text))
-            .peekable();
-
-        let mut new_ropes =
-            RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0));
-        let mut old_fragments = self.fragments.cursor::<FragmentTextSummary>();
-        let mut new_fragments =
-            old_fragments.slice(&edits.peek().unwrap().0.start, Bias::Right, &None);
-        new_ropes.append(new_fragments.summary().text);
-
-        let mut fragment_start = old_fragments.start().visible;
-        for (range, new_text) in edits {
-            let new_text = LineEnding::normalize_arc(new_text.into());
-            let fragment_end = old_fragments.end(&None).visible;
-
-            // If the current fragment ends before this range, then jump ahead to the first fragment
-            // that extends past the start of this range, reusing any intervening fragments.
-            if fragment_end < range.start {
-                // If the current fragment has been partially consumed, then consume the rest of it
-                // and advance to the next fragment before slicing.
-                if fragment_start > old_fragments.start().visible {
-                    if fragment_end > fragment_start {
-                        let mut suffix = old_fragments.item().unwrap().clone();
-                        suffix.len = fragment_end - fragment_start;
-                        suffix.insertion_offset += fragment_start - old_fragments.start().visible;
-                        new_insertions.push(InsertionFragment::insert_new(&suffix));
-                        new_ropes.push_fragment(&suffix, suffix.visible);
-                        new_fragments.push(suffix, &None);
-                    }
-                    old_fragments.next(&None);
-                }
-
-                let slice = old_fragments.slice(&range.start, Bias::Right, &None);
-                new_ropes.append(slice.summary().text);
-                new_fragments.append(slice, &None);
-                fragment_start = old_fragments.start().visible;
-            }
-
-            let full_range_start = FullOffset(range.start + old_fragments.start().deleted);
-
-            // Preserve any portion of the current fragment that precedes this range.
-            if fragment_start < range.start {
-                let mut prefix = old_fragments.item().unwrap().clone();
-                prefix.len = range.start - fragment_start;
-                prefix.insertion_offset += fragment_start - old_fragments.start().visible;
-                prefix.id = Locator::between(&new_fragments.summary().max_id, &prefix.id);
-                new_insertions.push(InsertionFragment::insert_new(&prefix));
-                new_ropes.push_fragment(&prefix, prefix.visible);
-                new_fragments.push(prefix, &None);
-                fragment_start = range.start;
-            }
-
-            // Insert the new text before any existing fragments within the range.
-            if !new_text.is_empty() {
-                let new_start = new_fragments.summary().text.visible;
-
-                let fragment = Fragment {
-                    id: Locator::between(
-                        &new_fragments.summary().max_id,
-                        old_fragments
-                            .item()
-                            .map_or(&Locator::max(), |old_fragment| &old_fragment.id),
-                    ),
-                    timestamp,
-                    insertion_offset,
-                    len: new_text.len(),
-                    deletions: Default::default(),
-                    max_undos: Default::default(),
-                    visible: true,
-                };
-                edits_patch.push(Edit {
-                    old: fragment_start..fragment_start,
-                    new: new_start..new_start + new_text.len(),
-                });
-                insertion_slices.push(fragment.insertion_slice());
-                new_insertions.push(InsertionFragment::insert_new(&fragment));
-                new_ropes.push_str(new_text.as_ref());
-                new_fragments.push(fragment, &None);
-                insertion_offset += new_text.len();
-            }
-
-            // Advance through every fragment that intersects this range, marking the intersecting
-            // portions as deleted.
-            while fragment_start < range.end {
-                let fragment = old_fragments.item().unwrap();
-                let fragment_end = old_fragments.end(&None).visible;
-                let mut intersection = fragment.clone();
-                let intersection_end = cmp::min(range.end, fragment_end);
-                if fragment.visible {
-                    intersection.len = intersection_end - fragment_start;
-                    intersection.insertion_offset += fragment_start - old_fragments.start().visible;
-                    intersection.id =
-                        Locator::between(&new_fragments.summary().max_id, &intersection.id);
-                    intersection.deletions.insert(timestamp);
-                    intersection.visible = false;
-                }
-                if intersection.len > 0 {
-                    if fragment.visible && !intersection.visible {
-                        let new_start = new_fragments.summary().text.visible;
-                        edits_patch.push(Edit {
-                            old: fragment_start..intersection_end,
-                            new: new_start..new_start,
-                        });
-                        insertion_slices.push(intersection.insertion_slice());
-                    }
-                    new_insertions.push(InsertionFragment::insert_new(&intersection));
-                    new_ropes.push_fragment(&intersection, fragment.visible);
-                    new_fragments.push(intersection, &None);
-                    fragment_start = intersection_end;
-                }
-                if fragment_end <= range.end {
-                    old_fragments.next(&None);
-                }
-            }
-
-            let full_range_end = FullOffset(range.end + old_fragments.start().deleted);
-            edit_op.ranges.push(full_range_start..full_range_end);
-            edit_op.new_text.push(new_text);
-        }
-
-        // If the current fragment has been partially consumed, then consume the rest of it
-        // and advance to the next fragment before slicing.
-        if fragment_start > old_fragments.start().visible {
-            let fragment_end = old_fragments.end(&None).visible;
-            if fragment_end > fragment_start {
-                let mut suffix = old_fragments.item().unwrap().clone();
-                suffix.len = fragment_end - fragment_start;
-                suffix.insertion_offset += fragment_start - old_fragments.start().visible;
-                new_insertions.push(InsertionFragment::insert_new(&suffix));
-                new_ropes.push_fragment(&suffix, suffix.visible);
-                new_fragments.push(suffix, &None);
-            }
-            old_fragments.next(&None);
-        }
-
-        let suffix = old_fragments.suffix(&None);
-        new_ropes.append(suffix.summary().text);
-        new_fragments.append(suffix, &None);
-        let (visible_text, deleted_text) = new_ropes.finish();
-        drop(old_fragments);
-
-        self.snapshot.fragments = new_fragments;
-        self.snapshot.insertions.edit(new_insertions, &());
-        self.snapshot.visible_text = visible_text;
-        self.snapshot.deleted_text = deleted_text;
-        self.subscriptions.publish_mut(&edits_patch);
-        self.history
-            .insertion_slices
-            .insert(timestamp, insertion_slices);
-        edit_op
-    }
-
-    pub fn set_line_ending(&mut self, line_ending: LineEnding) {
-        self.snapshot.line_ending = line_ending;
-    }
-
-    pub fn apply_ops<I: IntoIterator<Item = Operation>>(&mut self, ops: I) -> Result<()> {
-        let mut deferred_ops = Vec::new();
-        for op in ops {
-            self.history.push(op.clone());
-            if self.can_apply_op(&op) {
-                self.apply_op(op)?;
-            } else {
-                self.deferred_replicas.insert(op.replica_id());
-                deferred_ops.push(op);
-            }
-        }
-        self.deferred_ops.insert(deferred_ops);
-        self.flush_deferred_ops()?;
-        Ok(())
-    }
-
-    fn apply_op(&mut self, op: Operation) -> Result<()> {
-        match op {
-            Operation::Edit(edit) => {
-                if !self.version.observed(edit.timestamp) {
-                    self.apply_remote_edit(
-                        &edit.version,
-                        &edit.ranges,
-                        &edit.new_text,
-                        edit.timestamp,
-                    );
-                    self.snapshot.version.observe(edit.timestamp);
-                    self.lamport_clock.observe(edit.timestamp);
-                    self.resolve_edit(edit.timestamp);
-                }
-            }
-            Operation::Undo(undo) => {
-                if !self.version.observed(undo.timestamp) {
-                    self.apply_undo(&undo)?;
-                    self.snapshot.version.observe(undo.timestamp);
-                    self.lamport_clock.observe(undo.timestamp);
-                }
-            }
-        }
-        self.wait_for_version_txs.retain_mut(|(version, tx)| {
-            if self.snapshot.version().observed_all(version) {
-                tx.try_send(()).ok();
-                false
-            } else {
-                true
-            }
-        });
-        Ok(())
-    }
-
-    fn apply_remote_edit(
-        &mut self,
-        version: &clock::Global,
-        ranges: &[Range<FullOffset>],
-        new_text: &[Arc<str>],
-        timestamp: clock::Lamport,
-    ) {
-        if ranges.is_empty() {
-            return;
-        }
-
-        let edits = ranges.iter().zip(new_text.iter());
-        let mut edits_patch = Patch::default();
-        let mut insertion_slices = Vec::new();
-        let cx = Some(version.clone());
-        let mut new_insertions = Vec::new();
-        let mut insertion_offset = 0;
-        let mut new_ropes =
-            RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0));
-        let mut old_fragments = self.fragments.cursor::<(VersionedFullOffset, usize)>();
-        let mut new_fragments = old_fragments.slice(
-            &VersionedFullOffset::Offset(ranges[0].start),
-            Bias::Left,
-            &cx,
-        );
-        new_ropes.append(new_fragments.summary().text);
-
-        let mut fragment_start = old_fragments.start().0.full_offset();
-        for (range, new_text) in edits {
-            let fragment_end = old_fragments.end(&cx).0.full_offset();
-
-            // If the current fragment ends before this range, then jump ahead to the first fragment
-            // that extends past the start of this range, reusing any intervening fragments.
-            if fragment_end < range.start {
-                // If the current fragment has been partially consumed, then consume the rest of it
-                // and advance to the next fragment before slicing.
-                if fragment_start > old_fragments.start().0.full_offset() {
-                    if fragment_end > fragment_start {
-                        let mut suffix = old_fragments.item().unwrap().clone();
-                        suffix.len = fragment_end.0 - fragment_start.0;
-                        suffix.insertion_offset +=
-                            fragment_start - old_fragments.start().0.full_offset();
-                        new_insertions.push(InsertionFragment::insert_new(&suffix));
-                        new_ropes.push_fragment(&suffix, suffix.visible);
-                        new_fragments.push(suffix, &None);
-                    }
-                    old_fragments.next(&cx);
-                }
-
-                let slice =
-                    old_fragments.slice(&VersionedFullOffset::Offset(range.start), Bias::Left, &cx);
-                new_ropes.append(slice.summary().text);
-                new_fragments.append(slice, &None);
-                fragment_start = old_fragments.start().0.full_offset();
-            }
-
-            // If we are at the end of a non-concurrent fragment, advance to the next one.
-            let fragment_end = old_fragments.end(&cx).0.full_offset();
-            if fragment_end == range.start && fragment_end > fragment_start {
-                let mut fragment = old_fragments.item().unwrap().clone();
-                fragment.len = fragment_end.0 - fragment_start.0;
-                fragment.insertion_offset += fragment_start - old_fragments.start().0.full_offset();
-                new_insertions.push(InsertionFragment::insert_new(&fragment));
-                new_ropes.push_fragment(&fragment, fragment.visible);
-                new_fragments.push(fragment, &None);
-                old_fragments.next(&cx);
-                fragment_start = old_fragments.start().0.full_offset();
-            }
-
-            // Skip over insertions that are concurrent to this edit, but have a lower lamport
-            // timestamp.
-            while let Some(fragment) = old_fragments.item() {
-                if fragment_start == range.start && fragment.timestamp > timestamp {
-                    new_ropes.push_fragment(fragment, fragment.visible);
-                    new_fragments.push(fragment.clone(), &None);
-                    old_fragments.next(&cx);
-                    debug_assert_eq!(fragment_start, range.start);
-                } else {
-                    break;
-                }
-            }
-            debug_assert!(fragment_start <= range.start);
-
-            // Preserve any portion of the current fragment that precedes this range.
-            if fragment_start < range.start {
-                let mut prefix = old_fragments.item().unwrap().clone();
-                prefix.len = range.start.0 - fragment_start.0;
-                prefix.insertion_offset += fragment_start - old_fragments.start().0.full_offset();
-                prefix.id = Locator::between(&new_fragments.summary().max_id, &prefix.id);
-                new_insertions.push(InsertionFragment::insert_new(&prefix));
-                fragment_start = range.start;
-                new_ropes.push_fragment(&prefix, prefix.visible);
-                new_fragments.push(prefix, &None);
-            }
-
-            // Insert the new text before any existing fragments within the range.
-            if !new_text.is_empty() {
-                let mut old_start = old_fragments.start().1;
-                if old_fragments.item().map_or(false, |f| f.visible) {
-                    old_start += fragment_start.0 - old_fragments.start().0.full_offset().0;
-                }
-                let new_start = new_fragments.summary().text.visible;
-                let fragment = Fragment {
-                    id: Locator::between(
-                        &new_fragments.summary().max_id,
-                        old_fragments
-                            .item()
-                            .map_or(&Locator::max(), |old_fragment| &old_fragment.id),
-                    ),
-                    timestamp,
-                    insertion_offset,
-                    len: new_text.len(),
-                    deletions: Default::default(),
-                    max_undos: Default::default(),
-                    visible: true,
-                };
-                edits_patch.push(Edit {
-                    old: old_start..old_start,
-                    new: new_start..new_start + new_text.len(),
-                });
-                insertion_slices.push(fragment.insertion_slice());
-                new_insertions.push(InsertionFragment::insert_new(&fragment));
-                new_ropes.push_str(new_text);
-                new_fragments.push(fragment, &None);
-                insertion_offset += new_text.len();
-            }
-
-            // Advance through every fragment that intersects this range, marking the intersecting
-            // portions as deleted.
-            while fragment_start < range.end {
-                let fragment = old_fragments.item().unwrap();
-                let fragment_end = old_fragments.end(&cx).0.full_offset();
-                let mut intersection = fragment.clone();
-                let intersection_end = cmp::min(range.end, fragment_end);
-                if fragment.was_visible(version, &self.undo_map) {
-                    intersection.len = intersection_end.0 - fragment_start.0;
-                    intersection.insertion_offset +=
-                        fragment_start - old_fragments.start().0.full_offset();
-                    intersection.id =
-                        Locator::between(&new_fragments.summary().max_id, &intersection.id);
-                    intersection.deletions.insert(timestamp);
-                    intersection.visible = false;
-                    insertion_slices.push(intersection.insertion_slice());
-                }
-                if intersection.len > 0 {
-                    if fragment.visible && !intersection.visible {
-                        let old_start = old_fragments.start().1
-                            + (fragment_start.0 - old_fragments.start().0.full_offset().0);
-                        let new_start = new_fragments.summary().text.visible;
-                        edits_patch.push(Edit {
-                            old: old_start..old_start + intersection.len,
-                            new: new_start..new_start,
-                        });
-                    }
-                    new_insertions.push(InsertionFragment::insert_new(&intersection));
-                    new_ropes.push_fragment(&intersection, fragment.visible);
-                    new_fragments.push(intersection, &None);
-                    fragment_start = intersection_end;
-                }
-                if fragment_end <= range.end {
-                    old_fragments.next(&cx);
-                }
-            }
-        }
-
-        // If the current fragment has been partially consumed, then consume the rest of it
-        // and advance to the next fragment before slicing.
-        if fragment_start > old_fragments.start().0.full_offset() {
-            let fragment_end = old_fragments.end(&cx).0.full_offset();
-            if fragment_end > fragment_start {
-                let mut suffix = old_fragments.item().unwrap().clone();
-                suffix.len = fragment_end.0 - fragment_start.0;
-                suffix.insertion_offset += fragment_start - old_fragments.start().0.full_offset();
-                new_insertions.push(InsertionFragment::insert_new(&suffix));
-                new_ropes.push_fragment(&suffix, suffix.visible);
-                new_fragments.push(suffix, &None);
-            }
-            old_fragments.next(&cx);
-        }
-
-        let suffix = old_fragments.suffix(&cx);
-        new_ropes.append(suffix.summary().text);
-        new_fragments.append(suffix, &None);
-        let (visible_text, deleted_text) = new_ropes.finish();
-        drop(old_fragments);
-
-        self.snapshot.fragments = new_fragments;
-        self.snapshot.visible_text = visible_text;
-        self.snapshot.deleted_text = deleted_text;
-        self.snapshot.insertions.edit(new_insertions, &());
-        self.history
-            .insertion_slices
-            .insert(timestamp, insertion_slices);
-        self.subscriptions.publish_mut(&edits_patch)
-    }
-
-    fn fragment_ids_for_edits<'a>(
-        &'a self,
-        edit_ids: impl Iterator<Item = &'a clock::Lamport>,
-    ) -> Vec<&'a Locator> {
-        // Get all of the insertion slices changed by the given edits.
-        let mut insertion_slices = Vec::new();
-        for edit_id in edit_ids {
-            if let Some(slices) = self.history.insertion_slices.get(edit_id) {
-                insertion_slices.extend_from_slice(slices)
-            }
-        }
-        insertion_slices
-            .sort_unstable_by_key(|s| (s.insertion_id, s.range.start, Reverse(s.range.end)));
-
-        // Get all of the fragments corresponding to these insertion slices.
-        let mut fragment_ids = Vec::new();
-        let mut insertions_cursor = self.insertions.cursor::<InsertionFragmentKey>();
-        for insertion_slice in &insertion_slices {
-            if insertion_slice.insertion_id != insertions_cursor.start().timestamp
-                || insertion_slice.range.start > insertions_cursor.start().split_offset
-            {
-                insertions_cursor.seek_forward(
-                    &InsertionFragmentKey {
-                        timestamp: insertion_slice.insertion_id,
-                        split_offset: insertion_slice.range.start,
-                    },
-                    Bias::Left,
-                    &(),
-                );
-            }
-            while let Some(item) = insertions_cursor.item() {
-                if item.timestamp != insertion_slice.insertion_id
-                    || item.split_offset >= insertion_slice.range.end
-                {
-                    break;
-                }
-                fragment_ids.push(&item.fragment_id);
-                insertions_cursor.next(&());
-            }
-        }
-        fragment_ids.sort_unstable();
-        fragment_ids
-    }
-
-    fn apply_undo(&mut self, undo: &UndoOperation) -> Result<()> {
-        self.snapshot.undo_map.insert(undo);
-
-        let mut edits = Patch::default();
-        let mut old_fragments = self.fragments.cursor::<(Option<&Locator>, usize)>();
-        let mut new_fragments = SumTree::new();
-        let mut new_ropes =
-            RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0));
-
-        for fragment_id in self.fragment_ids_for_edits(undo.counts.keys()) {
-            let preceding_fragments = old_fragments.slice(&Some(fragment_id), Bias::Left, &None);
-            new_ropes.append(preceding_fragments.summary().text);
-            new_fragments.append(preceding_fragments, &None);
-
-            if let Some(fragment) = old_fragments.item() {
-                let mut fragment = fragment.clone();
-                let fragment_was_visible = fragment.visible;
-
-                fragment.visible = fragment.is_visible(&self.undo_map);
-                fragment.max_undos.observe(undo.timestamp);
-
-                let old_start = old_fragments.start().1;
-                let new_start = new_fragments.summary().text.visible;
-                if fragment_was_visible && !fragment.visible {
-                    edits.push(Edit {
-                        old: old_start..old_start + fragment.len,
-                        new: new_start..new_start,
-                    });
-                } else if !fragment_was_visible && fragment.visible {
-                    edits.push(Edit {
-                        old: old_start..old_start,
-                        new: new_start..new_start + fragment.len,
-                    });
-                }
-                new_ropes.push_fragment(&fragment, fragment_was_visible);
-                new_fragments.push(fragment, &None);
-
-                old_fragments.next(&None);
-            }
-        }
-
-        let suffix = old_fragments.suffix(&None);
-        new_ropes.append(suffix.summary().text);
-        new_fragments.append(suffix, &None);
-
-        drop(old_fragments);
-        let (visible_text, deleted_text) = new_ropes.finish();
-        self.snapshot.fragments = new_fragments;
-        self.snapshot.visible_text = visible_text;
-        self.snapshot.deleted_text = deleted_text;
-        self.subscriptions.publish_mut(&edits);
-        Ok(())
-    }
-
-    fn flush_deferred_ops(&mut self) -> Result<()> {
-        self.deferred_replicas.clear();
-        let mut deferred_ops = Vec::new();
-        for op in self.deferred_ops.drain().iter().cloned() {
-            if self.can_apply_op(&op) {
-                self.apply_op(op)?;
-            } else {
-                self.deferred_replicas.insert(op.replica_id());
-                deferred_ops.push(op);
-            }
-        }
-        self.deferred_ops.insert(deferred_ops);
-        Ok(())
-    }
-
-    fn can_apply_op(&self, op: &Operation) -> bool {
-        if self.deferred_replicas.contains(&op.replica_id()) {
-            false
-        } else {
-            self.version.observed_all(match op {
-                Operation::Edit(edit) => &edit.version,
-                Operation::Undo(undo) => &undo.version,
-            })
-        }
-    }
-
-    pub fn peek_undo_stack(&self) -> Option<&HistoryEntry> {
-        self.history.undo_stack.last()
-    }
-
-    pub fn peek_redo_stack(&self) -> Option<&HistoryEntry> {
-        self.history.redo_stack.last()
-    }
-
-    pub fn start_transaction(&mut self) -> Option<TransactionId> {
-        self.start_transaction_at(Instant::now())
-    }
-
-    pub fn start_transaction_at(&mut self, now: Instant) -> Option<TransactionId> {
-        self.history
-            .start_transaction(self.version.clone(), now, &mut self.lamport_clock)
-    }
-
-    pub fn end_transaction(&mut self) -> Option<(TransactionId, clock::Global)> {
-        self.end_transaction_at(Instant::now())
-    }
-
-    pub fn end_transaction_at(&mut self, now: Instant) -> Option<(TransactionId, clock::Global)> {
-        if let Some(entry) = self.history.end_transaction(now) {
-            let since = entry.transaction.start.clone();
-            let id = self.history.group().unwrap();
-            Some((id, since))
-        } else {
-            None
-        }
-    }
-
-    pub fn finalize_last_transaction(&mut self) -> Option<&Transaction> {
-        self.history.finalize_last_transaction()
-    }
-
-    pub fn group_until_transaction(&mut self, transaction_id: TransactionId) {
-        self.history.group_until(transaction_id);
-    }
-
-    pub fn base_text(&self) -> &Rope {
-        &self.history.base_text
-    }
-
-    pub fn operations(&self) -> &TreeMap<clock::Lamport, Operation> {
-        &self.history.operations
-    }
-
-    pub fn undo(&mut self) -> Option<(TransactionId, Operation)> {
-        if let Some(entry) = self.history.pop_undo() {
-            let transaction = entry.transaction.clone();
-            let transaction_id = transaction.id;
-            let op = self.undo_or_redo(transaction).unwrap();
-            Some((transaction_id, op))
-        } else {
-            None
-        }
-    }
-
-    pub fn undo_transaction(&mut self, transaction_id: TransactionId) -> Option<Operation> {
-        let transaction = self
-            .history
-            .remove_from_undo(transaction_id)?
-            .transaction
-            .clone();
-        self.undo_or_redo(transaction).log_err()
-    }
-
-    pub fn undo_to_transaction(&mut self, transaction_id: TransactionId) -> Vec<Operation> {
-        let transactions = self
-            .history
-            .remove_from_undo_until(transaction_id)
-            .iter()
-            .map(|entry| entry.transaction.clone())
-            .collect::<Vec<_>>();
-
-        transactions
-            .into_iter()
-            .map(|transaction| self.undo_or_redo(transaction).unwrap())
-            .collect()
-    }
-
-    pub fn forget_transaction(&mut self, transaction_id: TransactionId) {
-        self.history.forget(transaction_id);
-    }
-
-    pub fn merge_transactions(&mut self, transaction: TransactionId, destination: TransactionId) {
-        self.history.merge_transactions(transaction, destination);
-    }
-
-    pub fn redo(&mut self) -> Option<(TransactionId, Operation)> {
-        if let Some(entry) = self.history.pop_redo() {
-            let transaction = entry.transaction.clone();
-            let transaction_id = transaction.id;
-            let op = self.undo_or_redo(transaction).unwrap();
-            Some((transaction_id, op))
-        } else {
-            None
-        }
-    }
-
-    pub fn redo_to_transaction(&mut self, transaction_id: TransactionId) -> Vec<Operation> {
-        let transactions = self
-            .history
-            .remove_from_redo(transaction_id)
-            .iter()
-            .map(|entry| entry.transaction.clone())
-            .collect::<Vec<_>>();
-
-        transactions
-            .into_iter()
-            .map(|transaction| self.undo_or_redo(transaction).unwrap())
-            .collect()
-    }
-
-    fn undo_or_redo(&mut self, transaction: Transaction) -> Result<Operation> {
-        let mut counts = HashMap::default();
-        for edit_id in transaction.edit_ids {
-            counts.insert(edit_id, self.undo_map.undo_count(edit_id) + 1);
-        }
-
-        let undo = UndoOperation {
-            timestamp: self.lamport_clock.tick(),
-            version: self.version(),
-            counts,
-        };
-        self.apply_undo(&undo)?;
-        self.snapshot.version.observe(undo.timestamp);
-        let operation = Operation::Undo(undo);
-        self.history.push(operation.clone());
-        Ok(operation)
-    }
-
-    pub fn push_transaction(&mut self, transaction: Transaction, now: Instant) {
-        self.history.push_transaction(transaction, now);
-        self.history.finalize_last_transaction();
-    }
-
-    pub fn edited_ranges_for_transaction<'a, D>(
-        &'a self,
-        transaction: &'a Transaction,
-    ) -> impl 'a + Iterator<Item = Range<D>>
-    where
-        D: TextDimension,
-    {
-        // get fragment ranges
-        let mut cursor = self.fragments.cursor::<(Option<&Locator>, usize)>();
-        let offset_ranges = self
-            .fragment_ids_for_edits(transaction.edit_ids.iter())
-            .into_iter()
-            .filter_map(move |fragment_id| {
-                cursor.seek_forward(&Some(fragment_id), Bias::Left, &None);
-                let fragment = cursor.item()?;
-                let start_offset = cursor.start().1;
-                let end_offset = start_offset + if fragment.visible { fragment.len } else { 0 };
-                Some(start_offset..end_offset)
-            });
-
-        // combine adjacent ranges
-        let mut prev_range: Option<Range<usize>> = None;
-        let disjoint_ranges = offset_ranges
-            .map(Some)
-            .chain([None])
-            .filter_map(move |range| {
-                if let Some((range, prev_range)) = range.as_ref().zip(prev_range.as_mut()) {
-                    if prev_range.end == range.start {
-                        prev_range.end = range.end;
-                        return None;
-                    }
-                }
-                let result = prev_range.clone();
-                prev_range = range;
-                result
-            });
-
-        // convert to the desired text dimension.
-        let mut position = D::default();
-        let mut rope_cursor = self.visible_text.cursor(0);
-        disjoint_ranges.map(move |range| {
-            position.add_assign(&rope_cursor.summary(range.start));
-            let start = position.clone();
-            position.add_assign(&rope_cursor.summary(range.end));
-            let end = position.clone();
-            start..end
-        })
-    }
-
-    pub fn subscribe(&mut self) -> Subscription {
-        self.subscriptions.subscribe()
-    }
-
-    pub fn wait_for_edits(
-        &mut self,
-        edit_ids: impl IntoIterator<Item = clock::Lamport>,
-    ) -> impl 'static + Future<Output = Result<()>> {
-        let mut futures = Vec::new();
-        for edit_id in edit_ids {
-            if !self.version.observed(edit_id) {
-                let (tx, rx) = oneshot::channel();
-                self.edit_id_resolvers.entry(edit_id).or_default().push(tx);
-                futures.push(rx);
-            }
-        }
-
-        async move {
-            for mut future in futures {
-                if future.recv().await.is_none() {
-                    Err(anyhow!("gave up waiting for edits"))?;
-                }
-            }
-            Ok(())
-        }
-    }
-
-    pub fn wait_for_anchors(
-        &mut self,
-        anchors: impl IntoIterator<Item = Anchor>,
-    ) -> impl 'static + Future<Output = Result<()>> {
-        let mut futures = Vec::new();
-        for anchor in anchors {
-            if !self.version.observed(anchor.timestamp)
-                && anchor != Anchor::MAX
-                && anchor != Anchor::MIN
-            {
-                let (tx, rx) = oneshot::channel();
-                self.edit_id_resolvers
-                    .entry(anchor.timestamp)
-                    .or_default()
-                    .push(tx);
-                futures.push(rx);
-            }
-        }
-
-        async move {
-            for mut future in futures {
-                if future.recv().await.is_none() {
-                    Err(anyhow!("gave up waiting for anchors"))?;
-                }
-            }
-            Ok(())
-        }
-    }
-
-    pub fn wait_for_version(&mut self, version: clock::Global) -> impl Future<Output = Result<()>> {
-        let mut rx = None;
-        if !self.snapshot.version.observed_all(&version) {
-            let channel = oneshot::channel();
-            self.wait_for_version_txs.push((version, channel.0));
-            rx = Some(channel.1);
-        }
-        async move {
-            if let Some(mut rx) = rx {
-                if rx.recv().await.is_none() {
-                    Err(anyhow!("gave up waiting for version"))?;
-                }
-            }
-            Ok(())
-        }
-    }
-
-    pub fn give_up_waiting(&mut self) {
-        self.edit_id_resolvers.clear();
-        self.wait_for_version_txs.clear();
-    }
-
-    fn resolve_edit(&mut self, edit_id: clock::Lamport) {
-        for mut tx in self
-            .edit_id_resolvers
-            .remove(&edit_id)
-            .into_iter()
-            .flatten()
-        {
-            tx.try_send(()).ok();
-        }
-    }
-}
-
-#[cfg(any(test, feature = "test-support"))]
-impl Buffer {
-    pub fn edit_via_marked_text(&mut self, marked_string: &str) {
-        let edits = self.edits_for_marked_text(marked_string);
-        self.edit(edits);
-    }
-
-    pub fn edits_for_marked_text(&self, marked_string: &str) -> Vec<(Range<usize>, String)> {
-        let old_text = self.text();
-        let (new_text, mut ranges) = util::test::marked_text_ranges(marked_string, false);
-        if ranges.is_empty() {
-            ranges.push(0..new_text.len());
-        }
-
-        assert_eq!(
-            old_text[..ranges[0].start],
-            new_text[..ranges[0].start],
-            "invalid edit"
-        );
-
-        let mut delta = 0;
-        let mut edits = Vec::new();
-        let mut ranges = ranges.into_iter().peekable();
-
-        while let Some(inserted_range) = ranges.next() {
-            let new_start = inserted_range.start;
-            let old_start = (new_start as isize - delta) as usize;
-
-            let following_text = if let Some(next_range) = ranges.peek() {
-                &new_text[inserted_range.end..next_range.start]
-            } else {
-                &new_text[inserted_range.end..]
-            };
-
-            let inserted_len = inserted_range.len();
-            let deleted_len = old_text[old_start..]
-                .find(following_text)
-                .expect("invalid edit");
-
-            let old_range = old_start..old_start + deleted_len;
-            edits.push((old_range, new_text[inserted_range].to_string()));
-            delta += inserted_len as isize - deleted_len as isize;
-        }
-
-        assert_eq!(
-            old_text.len() as isize + delta,
-            new_text.len() as isize,
-            "invalid edit"
-        );
-
-        edits
-    }
-
-    pub fn check_invariants(&self) {
-        // Ensure every fragment is ordered by locator in the fragment tree and corresponds
-        // to an insertion fragment in the insertions tree.
-        let mut prev_fragment_id = Locator::min();
-        for fragment in self.snapshot.fragments.items(&None) {
-            assert!(fragment.id > prev_fragment_id);
-            prev_fragment_id = fragment.id.clone();
-
-            let insertion_fragment = self
-                .snapshot
-                .insertions
-                .get(
-                    &InsertionFragmentKey {
-                        timestamp: fragment.timestamp,
-                        split_offset: fragment.insertion_offset,
-                    },
-                    &(),
-                )
-                .unwrap();
-            assert_eq!(
-                insertion_fragment.fragment_id, fragment.id,
-                "fragment: {:?}\ninsertion: {:?}",
-                fragment, insertion_fragment
-            );
-        }
-
-        let mut cursor = self.snapshot.fragments.cursor::<Option<&Locator>>();
-        for insertion_fragment in self.snapshot.insertions.cursor::<()>() {
-            cursor.seek(&Some(&insertion_fragment.fragment_id), Bias::Left, &None);
-            let fragment = cursor.item().unwrap();
-            assert_eq!(insertion_fragment.fragment_id, fragment.id);
-            assert_eq!(insertion_fragment.split_offset, fragment.insertion_offset);
-        }
-
-        let fragment_summary = self.snapshot.fragments.summary();
-        assert_eq!(
-            fragment_summary.text.visible,
-            self.snapshot.visible_text.len()
-        );
-        assert_eq!(
-            fragment_summary.text.deleted,
-            self.snapshot.deleted_text.len()
-        );
-
-        assert!(!self.text().contains("\r\n"));
-    }
-
-    pub fn set_group_interval(&mut self, group_interval: Duration) {
-        self.history.group_interval = group_interval;
-    }
-
-    pub fn random_byte_range(&self, start_offset: usize, rng: &mut impl rand::Rng) -> Range<usize> {
-        let end = self.clip_offset(rng.gen_range(start_offset..=self.len()), Bias::Right);
-        let start = self.clip_offset(rng.gen_range(start_offset..=end), Bias::Right);
-        start..end
-    }
-
-    pub fn get_random_edits<T>(
-        &self,
-        rng: &mut T,
-        edit_count: usize,
-    ) -> Vec<(Range<usize>, Arc<str>)>
-    where
-        T: rand::Rng,
-    {
-        let mut edits: Vec<(Range<usize>, Arc<str>)> = Vec::new();
-        let mut last_end = None;
-        for _ in 0..edit_count {
-            if last_end.map_or(false, |last_end| last_end >= self.len()) {
-                break;
-            }
-            let new_start = last_end.map_or(0, |last_end| last_end + 1);
-            let range = self.random_byte_range(new_start, rng);
-            last_end = Some(range.end);
-
-            let new_text_len = rng.gen_range(0..10);
-            let new_text: String = RandomCharIter::new(&mut *rng).take(new_text_len).collect();
-
-            edits.push((range, new_text.into()));
-        }
-        edits
-    }
-
-    pub fn randomly_edit<T>(
-        &mut self,
-        rng: &mut T,
-        edit_count: usize,
-    ) -> (Vec<(Range<usize>, Arc<str>)>, Operation)
-    where
-        T: rand::Rng,
-    {
-        let mut edits = self.get_random_edits(rng, edit_count);
-        log::info!("mutating buffer {} with {:?}", self.replica_id, edits);
-
-        let op = self.edit(edits.iter().cloned());
-        if let Operation::Edit(edit) = &op {
-            assert_eq!(edits.len(), edit.new_text.len());
-            for (edit, new_text) in edits.iter_mut().zip(&edit.new_text) {
-                edit.1 = new_text.clone();
-            }
-        } else {
-            unreachable!()
-        }
-
-        (edits, op)
-    }
-
-    pub fn randomly_undo_redo(&mut self, rng: &mut impl rand::Rng) -> Vec<Operation> {
-        use rand::prelude::*;
-
-        let mut ops = Vec::new();
-        for _ in 0..rng.gen_range(1..=5) {
-            if let Some(entry) = self.history.undo_stack.choose(rng) {
-                let transaction = entry.transaction.clone();
-                log::info!(
-                    "undoing buffer {} transaction {:?}",
-                    self.replica_id,
-                    transaction
-                );
-                ops.push(self.undo_or_redo(transaction).unwrap());
-            }
-        }
-        ops
-    }
-}
-
-impl Deref for Buffer {
-    type Target = BufferSnapshot;
-
-    fn deref(&self) -> &Self::Target {
-        &self.snapshot
-    }
-}
-
-impl BufferSnapshot {
-    pub fn as_rope(&self) -> &Rope {
-        &self.visible_text
-    }
-
-    pub fn remote_id(&self) -> u64 {
-        self.remote_id
-    }
-
-    pub fn replica_id(&self) -> ReplicaId {
-        self.replica_id
-    }
-
-    pub fn row_count(&self) -> u32 {
-        self.max_point().row + 1
-    }
-
-    pub fn len(&self) -> usize {
-        self.visible_text.len()
-    }
-
-    pub fn is_empty(&self) -> bool {
-        self.len() == 0
-    }
-
-    pub fn chars(&self) -> impl Iterator<Item = char> + '_ {
-        self.chars_at(0)
-    }
-
-    pub fn chars_for_range<T: ToOffset>(&self, range: Range<T>) -> impl Iterator<Item = char> + '_ {
-        self.text_for_range(range).flat_map(str::chars)
-    }
-
-    pub fn reversed_chars_for_range<T: ToOffset>(
-        &self,
-        range: Range<T>,
-    ) -> impl Iterator<Item = char> + '_ {
-        self.reversed_chunks_in_range(range)
-            .flat_map(|chunk| chunk.chars().rev())
-    }
-
-    pub fn contains_str_at<T>(&self, position: T, needle: &str) -> bool
-    where
-        T: ToOffset,
-    {
-        let position = position.to_offset(self);
-        position == self.clip_offset(position, Bias::Left)
-            && self
-                .bytes_in_range(position..self.len())
-                .flatten()
-                .copied()
-                .take(needle.len())
-                .eq(needle.bytes())
-    }
-
-    pub fn common_prefix_at<T>(&self, position: T, needle: &str) -> Range<T>
-    where
-        T: ToOffset + TextDimension,
-    {
-        let offset = position.to_offset(self);
-        let common_prefix_len = needle
-            .char_indices()
-            .map(|(index, _)| index)
-            .chain([needle.len()])
-            .take_while(|&len| len <= offset)
-            .filter(|&len| {
-                let left = self
-                    .chars_for_range(offset - len..offset)
-                    .flat_map(char::to_lowercase);
-                let right = needle[..len].chars().flat_map(char::to_lowercase);
-                left.eq(right)
-            })
-            .last()
-            .unwrap_or(0);
-        let start_offset = offset - common_prefix_len;
-        let start = self.text_summary_for_range(0..start_offset);
-        start..position
-    }
-
-    pub fn text(&self) -> String {
-        self.visible_text.to_string()
-    }
-
-    pub fn line_ending(&self) -> LineEnding {
-        self.line_ending
-    }
-
-    pub fn deleted_text(&self) -> String {
-        self.deleted_text.to_string()
-    }
-
-    pub fn fragments(&self) -> impl Iterator<Item = &Fragment> {
-        self.fragments.iter()
-    }
-
-    pub fn text_summary(&self) -> TextSummary {
-        self.visible_text.summary()
-    }
-
-    pub fn max_point(&self) -> Point {
-        self.visible_text.max_point()
-    }
-
-    pub fn max_point_utf16(&self) -> PointUtf16 {
-        self.visible_text.max_point_utf16()
-    }
-
-    pub fn point_to_offset(&self, point: Point) -> usize {
-        self.visible_text.point_to_offset(point)
-    }
-
-    pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize {
-        self.visible_text.point_utf16_to_offset(point)
-    }
-
-    pub fn unclipped_point_utf16_to_offset(&self, point: Unclipped<PointUtf16>) -> usize {
-        self.visible_text.unclipped_point_utf16_to_offset(point)
-    }
-
-    pub fn unclipped_point_utf16_to_point(&self, point: Unclipped<PointUtf16>) -> Point {
-        self.visible_text.unclipped_point_utf16_to_point(point)
-    }
-
-    pub fn offset_utf16_to_offset(&self, offset: OffsetUtf16) -> usize {
-        self.visible_text.offset_utf16_to_offset(offset)
-    }
-
-    pub fn offset_to_offset_utf16(&self, offset: usize) -> OffsetUtf16 {
-        self.visible_text.offset_to_offset_utf16(offset)
-    }
-
-    pub fn offset_to_point(&self, offset: usize) -> Point {
-        self.visible_text.offset_to_point(offset)
-    }
-
-    pub fn offset_to_point_utf16(&self, offset: usize) -> PointUtf16 {
-        self.visible_text.offset_to_point_utf16(offset)
-    }
-
-    pub fn point_to_point_utf16(&self, point: Point) -> PointUtf16 {
-        self.visible_text.point_to_point_utf16(point)
-    }
-
-    pub fn version(&self) -> &clock::Global {
-        &self.version
-    }
-
-    pub fn chars_at<T: ToOffset>(&self, position: T) -> impl Iterator<Item = char> + '_ {
-        let offset = position.to_offset(self);
-        self.visible_text.chars_at(offset)
-    }
-
-    pub fn reversed_chars_at<T: ToOffset>(&self, position: T) -> impl Iterator<Item = char> + '_ {
-        let offset = position.to_offset(self);
-        self.visible_text.reversed_chars_at(offset)
-    }
-
-    pub fn reversed_chunks_in_range<T: ToOffset>(&self, range: Range<T>) -> rope::Chunks {
-        let range = range.start.to_offset(self)..range.end.to_offset(self);
-        self.visible_text.reversed_chunks_in_range(range)
-    }
-
-    pub fn bytes_in_range<T: ToOffset>(&self, range: Range<T>) -> rope::Bytes<'_> {
-        let start = range.start.to_offset(self);
-        let end = range.end.to_offset(self);
-        self.visible_text.bytes_in_range(start..end)
-    }
-
-    pub fn reversed_bytes_in_range<T: ToOffset>(&self, range: Range<T>) -> rope::Bytes<'_> {
-        let start = range.start.to_offset(self);
-        let end = range.end.to_offset(self);
-        self.visible_text.reversed_bytes_in_range(start..end)
-    }
-
-    pub fn text_for_range<T: ToOffset>(&self, range: Range<T>) -> Chunks<'_> {
-        let start = range.start.to_offset(self);
-        let end = range.end.to_offset(self);
-        self.visible_text.chunks_in_range(start..end)
-    }
-
-    pub fn line_len(&self, row: u32) -> u32 {
-        let row_start_offset = Point::new(row, 0).to_offset(self);
-        let row_end_offset = if row >= self.max_point().row {
-            self.len()
-        } else {
-            Point::new(row + 1, 0).to_offset(self) - 1
-        };
-        (row_end_offset - row_start_offset) as u32
-    }
-
-    pub fn is_line_blank(&self, row: u32) -> bool {
-        self.text_for_range(Point::new(row, 0)..Point::new(row, self.line_len(row)))
-            .all(|chunk| chunk.matches(|c: char| !c.is_whitespace()).next().is_none())
-    }
-
-    pub fn text_summary_for_range<D, O: ToOffset>(&self, range: Range<O>) -> D
-    where
-        D: TextDimension,
-    {
-        self.visible_text
-            .cursor(range.start.to_offset(self))
-            .summary(range.end.to_offset(self))
-    }
-
-    pub fn summaries_for_anchors<'a, D, A>(&'a self, anchors: A) -> impl 'a + Iterator<Item = D>
-    where
-        D: 'a + TextDimension,
-        A: 'a + IntoIterator<Item = &'a Anchor>,
-    {
-        let anchors = anchors.into_iter();
-        self.summaries_for_anchors_with_payload::<D, _, ()>(anchors.map(|a| (a, ())))
-            .map(|d| d.0)
-    }
-
-    pub fn summaries_for_anchors_with_payload<'a, D, A, T>(
-        &'a self,
-        anchors: A,
-    ) -> impl 'a + Iterator<Item = (D, T)>
-    where
-        D: 'a + TextDimension,
-        A: 'a + IntoIterator<Item = (&'a Anchor, T)>,
-    {
-        let anchors = anchors.into_iter();
-        let mut insertion_cursor = self.insertions.cursor::<InsertionFragmentKey>();
-        let mut fragment_cursor = self.fragments.cursor::<(Option<&Locator>, usize)>();
-        let mut text_cursor = self.visible_text.cursor(0);
-        let mut position = D::default();
-
-        anchors.map(move |(anchor, payload)| {
-            if *anchor == Anchor::MIN {
-                return (D::default(), payload);
-            } else if *anchor == Anchor::MAX {
-                return (D::from_text_summary(&self.visible_text.summary()), payload);
-            }
-
-            let anchor_key = InsertionFragmentKey {
-                timestamp: anchor.timestamp,
-                split_offset: anchor.offset,
-            };
-            insertion_cursor.seek(&anchor_key, anchor.bias, &());
-            if let Some(insertion) = insertion_cursor.item() {
-                let comparison = sum_tree::KeyedItem::key(insertion).cmp(&anchor_key);
-                if comparison == Ordering::Greater
-                    || (anchor.bias == Bias::Left
-                        && comparison == Ordering::Equal
-                        && anchor.offset > 0)
-                {
-                    insertion_cursor.prev(&());
-                }
-            } else {
-                insertion_cursor.prev(&());
-            }
-            let insertion = insertion_cursor.item().expect("invalid insertion");
-            assert_eq!(insertion.timestamp, anchor.timestamp, "invalid insertion");
-
-            fragment_cursor.seek_forward(&Some(&insertion.fragment_id), Bias::Left, &None);
-            let fragment = fragment_cursor.item().unwrap();
-            let mut fragment_offset = fragment_cursor.start().1;
-            if fragment.visible {
-                fragment_offset += anchor.offset - insertion.split_offset;
-            }
-
-            position.add_assign(&text_cursor.summary(fragment_offset));
-            (position.clone(), payload)
-        })
-    }
-
-    fn summary_for_anchor<D>(&self, anchor: &Anchor) -> D
-    where
-        D: TextDimension,
-    {
-        if *anchor == Anchor::MIN {
-            D::default()
-        } else if *anchor == Anchor::MAX {
-            D::from_text_summary(&self.visible_text.summary())
-        } else {
-            let anchor_key = InsertionFragmentKey {
-                timestamp: anchor.timestamp,
-                split_offset: anchor.offset,
-            };
-            let mut insertion_cursor = self.insertions.cursor::<InsertionFragmentKey>();
-            insertion_cursor.seek(&anchor_key, anchor.bias, &());
-            if let Some(insertion) = insertion_cursor.item() {
-                let comparison = sum_tree::KeyedItem::key(insertion).cmp(&anchor_key);
-                if comparison == Ordering::Greater
-                    || (anchor.bias == Bias::Left
-                        && comparison == Ordering::Equal
-                        && anchor.offset > 0)
-                {
-                    insertion_cursor.prev(&());
-                }
-            } else {
-                insertion_cursor.prev(&());
-            }
-            let insertion = insertion_cursor.item().expect("invalid insertion");
-            assert_eq!(insertion.timestamp, anchor.timestamp, "invalid insertion");
-
-            let mut fragment_cursor = self.fragments.cursor::<(Option<&Locator>, usize)>();
-            fragment_cursor.seek(&Some(&insertion.fragment_id), Bias::Left, &None);
-            let fragment = fragment_cursor.item().unwrap();
-            let mut fragment_offset = fragment_cursor.start().1;
-            if fragment.visible {
-                fragment_offset += anchor.offset - insertion.split_offset;
-            }
-            self.text_summary_for_range(0..fragment_offset)
-        }
-    }
-
-    fn fragment_id_for_anchor(&self, anchor: &Anchor) -> &Locator {
-        if *anchor == Anchor::MIN {
-            Locator::min_ref()
-        } else if *anchor == Anchor::MAX {
-            Locator::max_ref()
-        } else {
-            let anchor_key = InsertionFragmentKey {
-                timestamp: anchor.timestamp,
-                split_offset: anchor.offset,
-            };
-            let mut insertion_cursor = self.insertions.cursor::<InsertionFragmentKey>();
-            insertion_cursor.seek(&anchor_key, anchor.bias, &());
-            if let Some(insertion) = insertion_cursor.item() {
-                let comparison = sum_tree::KeyedItem::key(insertion).cmp(&anchor_key);
-                if comparison == Ordering::Greater
-                    || (anchor.bias == Bias::Left
-                        && comparison == Ordering::Equal
-                        && anchor.offset > 0)
-                {
-                    insertion_cursor.prev(&());
-                }
-            } else {
-                insertion_cursor.prev(&());
-            }
-            let insertion = insertion_cursor.item().expect("invalid insertion");
-            debug_assert_eq!(insertion.timestamp, anchor.timestamp, "invalid insertion");
-            &insertion.fragment_id
-        }
-    }
-
-    pub fn anchor_before<T: ToOffset>(&self, position: T) -> Anchor {
-        self.anchor_at(position, Bias::Left)
-    }
-
-    pub fn anchor_after<T: ToOffset>(&self, position: T) -> Anchor {
-        self.anchor_at(position, Bias::Right)
-    }
-
-    pub fn anchor_at<T: ToOffset>(&self, position: T, bias: Bias) -> Anchor {
-        self.anchor_at_offset(position.to_offset(self), bias)
-    }
-
-    fn anchor_at_offset(&self, offset: usize, bias: Bias) -> Anchor {
-        if bias == Bias::Left && offset == 0 {
-            Anchor::MIN
-        } else if bias == Bias::Right && offset == self.len() {
-            Anchor::MAX
-        } else {
-            let mut fragment_cursor = self.fragments.cursor::<usize>();
-            fragment_cursor.seek(&offset, bias, &None);
-            let fragment = fragment_cursor.item().unwrap();
-            let overshoot = offset - *fragment_cursor.start();
-            Anchor {
-                timestamp: fragment.timestamp,
-                offset: fragment.insertion_offset + overshoot,
-                bias,
-                buffer_id: Some(self.remote_id),
-            }
-        }
-    }
-
-    pub fn can_resolve(&self, anchor: &Anchor) -> bool {
-        *anchor == Anchor::MIN
-            || *anchor == Anchor::MAX
-            || (Some(self.remote_id) == anchor.buffer_id && self.version.observed(anchor.timestamp))
-    }
-
-    pub fn clip_offset(&self, offset: usize, bias: Bias) -> usize {
-        self.visible_text.clip_offset(offset, bias)
-    }
-
-    pub fn clip_point(&self, point: Point, bias: Bias) -> Point {
-        self.visible_text.clip_point(point, bias)
-    }
-
-    pub fn clip_offset_utf16(&self, offset: OffsetUtf16, bias: Bias) -> OffsetUtf16 {
-        self.visible_text.clip_offset_utf16(offset, bias)
-    }
-
-    pub fn clip_point_utf16(&self, point: Unclipped<PointUtf16>, bias: Bias) -> PointUtf16 {
-        self.visible_text.clip_point_utf16(point, bias)
-    }
-
-    pub fn edits_since<'a, D>(
-        &'a self,
-        since: &'a clock::Global,
-    ) -> impl 'a + Iterator<Item = Edit<D>>
-    where
-        D: TextDimension + Ord,
-    {
-        self.edits_since_in_range(since, Anchor::MIN..Anchor::MAX)
-    }
-
-    pub fn anchored_edits_since<'a, D>(
-        &'a self,
-        since: &'a clock::Global,
-    ) -> impl 'a + Iterator<Item = (Edit<D>, Range<Anchor>)>
-    where
-        D: TextDimension + Ord,
-    {
-        self.anchored_edits_since_in_range(since, Anchor::MIN..Anchor::MAX)
-    }
-
-    pub fn edits_since_in_range<'a, D>(
-        &'a self,
-        since: &'a clock::Global,
-        range: Range<Anchor>,
-    ) -> impl 'a + Iterator<Item = Edit<D>>
-    where
-        D: TextDimension + Ord,
-    {
-        self.anchored_edits_since_in_range(since, range)
-            .map(|item| item.0)
-    }
-
-    pub fn anchored_edits_since_in_range<'a, D>(
-        &'a self,
-        since: &'a clock::Global,
-        range: Range<Anchor>,
-    ) -> impl 'a + Iterator<Item = (Edit<D>, Range<Anchor>)>
-    where
-        D: TextDimension + Ord,
-    {
-        let fragments_cursor = if *since == self.version {
-            None
-        } else {
-            let mut cursor = self
-                .fragments
-                .filter(move |summary| !since.observed_all(&summary.max_version));
-            cursor.next(&None);
-            Some(cursor)
-        };
-        let mut cursor = self
-            .fragments
-            .cursor::<(Option<&Locator>, FragmentTextSummary)>();
-
-        let start_fragment_id = self.fragment_id_for_anchor(&range.start);
-        cursor.seek(&Some(start_fragment_id), Bias::Left, &None);
-        let mut visible_start = cursor.start().1.visible;
-        let mut deleted_start = cursor.start().1.deleted;
-        if let Some(fragment) = cursor.item() {
-            let overshoot = range.start.offset - fragment.insertion_offset;
-            if fragment.visible {
-                visible_start += overshoot;
-            } else {
-                deleted_start += overshoot;
-            }
-        }
-        let end_fragment_id = self.fragment_id_for_anchor(&range.end);
-
-        Edits {
-            visible_cursor: self.visible_text.cursor(visible_start),
-            deleted_cursor: self.deleted_text.cursor(deleted_start),
-            fragments_cursor,
-            undos: &self.undo_map,
-            since,
-            old_end: Default::default(),
-            new_end: Default::default(),
-            range: (start_fragment_id, range.start.offset)..(end_fragment_id, range.end.offset),
-            buffer_id: self.remote_id,
-        }
-    }
-}
-
-struct RopeBuilder<'a> {
-    old_visible_cursor: rope::Cursor<'a>,
-    old_deleted_cursor: rope::Cursor<'a>,
-    new_visible: Rope,
-    new_deleted: Rope,
-}
-
-impl<'a> RopeBuilder<'a> {
-    fn new(old_visible_cursor: rope::Cursor<'a>, old_deleted_cursor: rope::Cursor<'a>) -> Self {
-        Self {
-            old_visible_cursor,
-            old_deleted_cursor,
-            new_visible: Rope::new(),
-            new_deleted: Rope::new(),
-        }
-    }
-
-    fn append(&mut self, len: FragmentTextSummary) {
-        self.push(len.visible, true, true);
-        self.push(len.deleted, false, false);
-    }
-
-    fn push_fragment(&mut self, fragment: &Fragment, was_visible: bool) {
-        debug_assert!(fragment.len > 0);
-        self.push(fragment.len, was_visible, fragment.visible)
-    }
-
-    fn push(&mut self, len: usize, was_visible: bool, is_visible: bool) {
-        let text = if was_visible {
-            self.old_visible_cursor
-                .slice(self.old_visible_cursor.offset() + len)
-        } else {
-            self.old_deleted_cursor
-                .slice(self.old_deleted_cursor.offset() + len)
-        };
-        if is_visible {
-            self.new_visible.append(text);
-        } else {
-            self.new_deleted.append(text);
-        }
-    }
-
-    fn push_str(&mut self, text: &str) {
-        self.new_visible.push(text);
-    }
-
-    fn finish(mut self) -> (Rope, Rope) {
-        self.new_visible.append(self.old_visible_cursor.suffix());
-        self.new_deleted.append(self.old_deleted_cursor.suffix());
-        (self.new_visible, self.new_deleted)
-    }
-}
-
-impl<'a, D: TextDimension + Ord, F: FnMut(&FragmentSummary) -> bool> Iterator for Edits<'a, D, F> {
-    type Item = (Edit<D>, Range<Anchor>);
-
-    fn next(&mut self) -> Option<Self::Item> {
-        let mut pending_edit: Option<Self::Item> = None;
-        let cursor = self.fragments_cursor.as_mut()?;
-
-        while let Some(fragment) = cursor.item() {
-            if fragment.id < *self.range.start.0 {
-                cursor.next(&None);
-                continue;
-            } else if fragment.id > *self.range.end.0 {
-                break;
-            }
-
-            if cursor.start().visible > self.visible_cursor.offset() {
-                let summary = self.visible_cursor.summary(cursor.start().visible);
-                self.old_end.add_assign(&summary);
-                self.new_end.add_assign(&summary);
-            }
-
-            if pending_edit
-                .as_ref()
-                .map_or(false, |(change, _)| change.new.end < self.new_end)
-            {
-                break;
-            }
-
-            let start_anchor = Anchor {
-                timestamp: fragment.timestamp,
-                offset: fragment.insertion_offset,
-                bias: Bias::Right,
-                buffer_id: Some(self.buffer_id),
-            };
-            let end_anchor = Anchor {
-                timestamp: fragment.timestamp,
-                offset: fragment.insertion_offset + fragment.len,
-                bias: Bias::Left,
-                buffer_id: Some(self.buffer_id),
-            };
-
-            if !fragment.was_visible(self.since, self.undos) && fragment.visible {
-                let mut visible_end = cursor.end(&None).visible;
-                if fragment.id == *self.range.end.0 {
-                    visible_end = cmp::min(
-                        visible_end,
-                        cursor.start().visible + (self.range.end.1 - fragment.insertion_offset),
-                    );
-                }
-
-                let fragment_summary = self.visible_cursor.summary(visible_end);
-                let mut new_end = self.new_end.clone();
-                new_end.add_assign(&fragment_summary);
-                if let Some((edit, range)) = pending_edit.as_mut() {
-                    edit.new.end = new_end.clone();
-                    range.end = end_anchor;
-                } else {
-                    pending_edit = Some((
-                        Edit {
-                            old: self.old_end.clone()..self.old_end.clone(),
-                            new: self.new_end.clone()..new_end.clone(),
-                        },
-                        start_anchor..end_anchor,
-                    ));
-                }
-
-                self.new_end = new_end;
-            } else if fragment.was_visible(self.since, self.undos) && !fragment.visible {
-                let mut deleted_end = cursor.end(&None).deleted;
-                if fragment.id == *self.range.end.0 {
-                    deleted_end = cmp::min(
-                        deleted_end,
-                        cursor.start().deleted + (self.range.end.1 - fragment.insertion_offset),
-                    );
-                }
-
-                if cursor.start().deleted > self.deleted_cursor.offset() {
-                    self.deleted_cursor.seek_forward(cursor.start().deleted);
-                }
-                let fragment_summary = self.deleted_cursor.summary(deleted_end);
-                let mut old_end = self.old_end.clone();
-                old_end.add_assign(&fragment_summary);
-                if let Some((edit, range)) = pending_edit.as_mut() {
-                    edit.old.end = old_end.clone();
-                    range.end = end_anchor;
-                } else {
-                    pending_edit = Some((
-                        Edit {
-                            old: self.old_end.clone()..old_end.clone(),
-                            new: self.new_end.clone()..self.new_end.clone(),
-                        },
-                        start_anchor..end_anchor,
-                    ));
-                }
-
-                self.old_end = old_end;
-            }
-
-            cursor.next(&None);
-        }
-
-        pending_edit
-    }
-}
-
-impl Fragment {
-    fn insertion_slice(&self) -> InsertionSlice {
-        InsertionSlice {
-            insertion_id: self.timestamp,
-            range: self.insertion_offset..self.insertion_offset + self.len,
-        }
-    }
-
-    fn is_visible(&self, undos: &UndoMap) -> bool {
-        !undos.is_undone(self.timestamp) && self.deletions.iter().all(|d| undos.is_undone(*d))
-    }
-
-    fn was_visible(&self, version: &clock::Global, undos: &UndoMap) -> bool {
-        (version.observed(self.timestamp) && !undos.was_undone(self.timestamp, version))
-            && self
-                .deletions
-                .iter()
-                .all(|d| !version.observed(*d) || undos.was_undone(*d, version))
-    }
-}
-
-impl sum_tree::Item for Fragment {
-    type Summary = FragmentSummary;
-
-    fn summary(&self) -> Self::Summary {
-        let mut max_version = clock::Global::new();
-        max_version.observe(self.timestamp);
-        for deletion in &self.deletions {
-            max_version.observe(*deletion);
-        }
-        max_version.join(&self.max_undos);
-
-        let mut min_insertion_version = clock::Global::new();
-        min_insertion_version.observe(self.timestamp);
-        let max_insertion_version = min_insertion_version.clone();
-        if self.visible {
-            FragmentSummary {
-                max_id: self.id.clone(),
-                text: FragmentTextSummary {
-                    visible: self.len,
-                    deleted: 0,
-                },
-                max_version,
-                min_insertion_version,
-                max_insertion_version,
-            }
-        } else {
-            FragmentSummary {
-                max_id: self.id.clone(),
-                text: FragmentTextSummary {
-                    visible: 0,
-                    deleted: self.len,
-                },
-                max_version,
-                min_insertion_version,
-                max_insertion_version,
-            }
-        }
-    }
-}
-
-impl sum_tree::Summary for FragmentSummary {
-    type Context = Option<clock::Global>;
-
-    fn add_summary(&mut self, other: &Self, _: &Self::Context) {
-        self.max_id.assign(&other.max_id);
-        self.text.visible += &other.text.visible;
-        self.text.deleted += &other.text.deleted;
-        self.max_version.join(&other.max_version);
-        self.min_insertion_version
-            .meet(&other.min_insertion_version);
-        self.max_insertion_version
-            .join(&other.max_insertion_version);
-    }
-}
-
-impl Default for FragmentSummary {
-    fn default() -> Self {
-        FragmentSummary {
-            max_id: Locator::min(),
-            text: FragmentTextSummary::default(),
-            max_version: clock::Global::new(),
-            min_insertion_version: clock::Global::new(),
-            max_insertion_version: clock::Global::new(),
-        }
-    }
-}
-
-impl sum_tree::Item for InsertionFragment {
-    type Summary = InsertionFragmentKey;
-
-    fn summary(&self) -> Self::Summary {
-        InsertionFragmentKey {
-            timestamp: self.timestamp,
-            split_offset: self.split_offset,
-        }
-    }
-}
-
-impl sum_tree::KeyedItem for InsertionFragment {
-    type Key = InsertionFragmentKey;
-
-    fn key(&self) -> Self::Key {
-        sum_tree::Item::summary(self)
-    }
-}
-
-impl InsertionFragment {
-    fn new(fragment: &Fragment) -> Self {
-        Self {
-            timestamp: fragment.timestamp,
-            split_offset: fragment.insertion_offset,
-            fragment_id: fragment.id.clone(),
-        }
-    }
-
-    fn insert_new(fragment: &Fragment) -> sum_tree::Edit<Self> {
-        sum_tree::Edit::Insert(Self::new(fragment))
-    }
-}
-
-impl sum_tree::Summary for InsertionFragmentKey {
-    type Context = ();
-
-    fn add_summary(&mut self, summary: &Self, _: &()) {
-        *self = *summary;
-    }
-}
-
-#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub struct FullOffset(pub usize);
-
-impl ops::AddAssign<usize> for FullOffset {
-    fn add_assign(&mut self, rhs: usize) {
-        self.0 += rhs;
-    }
-}
-
-impl ops::Add<usize> for FullOffset {
-    type Output = Self;
-
-    fn add(mut self, rhs: usize) -> Self::Output {
-        self += rhs;
-        self
-    }
-}
-
-impl ops::Sub for FullOffset {
-    type Output = usize;
-
-    fn sub(self, rhs: Self) -> Self::Output {
-        self.0 - rhs.0
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, FragmentSummary> for usize {
-    fn add_summary(&mut self, summary: &FragmentSummary, _: &Option<clock::Global>) {
-        *self += summary.text.visible;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, FragmentSummary> for FullOffset {
-    fn add_summary(&mut self, summary: &FragmentSummary, _: &Option<clock::Global>) {
-        self.0 += summary.text.visible + summary.text.deleted;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, FragmentSummary> for Option<&'a Locator> {
-    fn add_summary(&mut self, summary: &'a FragmentSummary, _: &Option<clock::Global>) {
-        *self = Some(&summary.max_id);
-    }
-}
-
-impl<'a> sum_tree::SeekTarget<'a, FragmentSummary, FragmentTextSummary> for usize {
-    fn cmp(
-        &self,
-        cursor_location: &FragmentTextSummary,
-        _: &Option<clock::Global>,
-    ) -> cmp::Ordering {
-        Ord::cmp(self, &cursor_location.visible)
-    }
-}
-
-#[derive(Copy, Clone, Debug, Eq, PartialEq)]
-enum VersionedFullOffset {
-    Offset(FullOffset),
-    Invalid,
-}
-
-impl VersionedFullOffset {
-    fn full_offset(&self) -> FullOffset {
-        if let Self::Offset(position) = self {
-            *position
-        } else {
-            panic!("invalid version")
-        }
-    }
-}
-
-impl Default for VersionedFullOffset {
-    fn default() -> Self {
-        Self::Offset(Default::default())
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, FragmentSummary> for VersionedFullOffset {
-    fn add_summary(&mut self, summary: &'a FragmentSummary, cx: &Option<clock::Global>) {
-        if let Self::Offset(offset) = self {
-            let version = cx.as_ref().unwrap();
-            if version.observed_all(&summary.max_insertion_version) {
-                *offset += summary.text.visible + summary.text.deleted;
-            } else if version.observed_any(&summary.min_insertion_version) {
-                *self = Self::Invalid;
-            }
-        }
-    }
-}
-
-impl<'a> sum_tree::SeekTarget<'a, FragmentSummary, Self> for VersionedFullOffset {
-    fn cmp(&self, cursor_position: &Self, _: &Option<clock::Global>) -> cmp::Ordering {
-        match (self, cursor_position) {
-            (Self::Offset(a), Self::Offset(b)) => Ord::cmp(a, b),
-            (Self::Offset(_), Self::Invalid) => cmp::Ordering::Less,
-            (Self::Invalid, _) => unreachable!(),
-        }
-    }
-}
-
-impl Operation {
-    fn replica_id(&self) -> ReplicaId {
-        operation_queue::Operation::lamport_timestamp(self).replica_id
-    }
-
-    pub fn timestamp(&self) -> clock::Lamport {
-        match self {
-            Operation::Edit(edit) => edit.timestamp,
-            Operation::Undo(undo) => undo.timestamp,
-        }
-    }
-
-    pub fn as_edit(&self) -> Option<&EditOperation> {
-        match self {
-            Operation::Edit(edit) => Some(edit),
-            _ => None,
-        }
-    }
-
-    pub fn is_edit(&self) -> bool {
-        matches!(self, Operation::Edit { .. })
-    }
-}
-
-impl operation_queue::Operation for Operation {
-    fn lamport_timestamp(&self) -> clock::Lamport {
-        match self {
-            Operation::Edit(edit) => edit.timestamp,
-            Operation::Undo(undo) => undo.timestamp,
-        }
-    }
-}
-
-pub trait ToOffset {
-    fn to_offset(&self, snapshot: &BufferSnapshot) -> usize;
-}
-
-impl ToOffset for Point {
-    fn to_offset(&self, snapshot: &BufferSnapshot) -> usize {
-        snapshot.point_to_offset(*self)
-    }
-}
-
-impl ToOffset for usize {
-    fn to_offset(&self, snapshot: &BufferSnapshot) -> usize {
-        assert!(
-            *self <= snapshot.len(),
-            "offset {} is out of range, max allowed is {}",
-            self,
-            snapshot.len()
-        );
-        *self
-    }
-}
-
-impl ToOffset for Anchor {
-    fn to_offset(&self, snapshot: &BufferSnapshot) -> usize {
-        snapshot.summary_for_anchor(self)
-    }
-}
-
-impl<'a, T: ToOffset> ToOffset for &'a T {
-    fn to_offset(&self, content: &BufferSnapshot) -> usize {
-        (*self).to_offset(content)
-    }
-}
-
-impl ToOffset for PointUtf16 {
-    fn to_offset(&self, snapshot: &BufferSnapshot) -> usize {
-        snapshot.point_utf16_to_offset(*self)
-    }
-}
-
-impl ToOffset for Unclipped<PointUtf16> {
-    fn to_offset(&self, snapshot: &BufferSnapshot) -> usize {
-        snapshot.unclipped_point_utf16_to_offset(*self)
-    }
-}
-
-pub trait ToPoint {
-    fn to_point(&self, snapshot: &BufferSnapshot) -> Point;
-}
-
-impl ToPoint for Anchor {
-    fn to_point(&self, snapshot: &BufferSnapshot) -> Point {
-        snapshot.summary_for_anchor(self)
-    }
-}
-
-impl ToPoint for usize {
-    fn to_point(&self, snapshot: &BufferSnapshot) -> Point {
-        snapshot.offset_to_point(*self)
-    }
-}
-
-impl ToPoint for Point {
-    fn to_point(&self, _: &BufferSnapshot) -> Point {
-        *self
-    }
-}
-
-impl ToPoint for Unclipped<PointUtf16> {
-    fn to_point(&self, snapshot: &BufferSnapshot) -> Point {
-        snapshot.unclipped_point_utf16_to_point(*self)
-    }
-}
-
-pub trait ToPointUtf16 {
-    fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> PointUtf16;
-}
-
-impl ToPointUtf16 for Anchor {
-    fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> PointUtf16 {
-        snapshot.summary_for_anchor(self)
-    }
-}
-
-impl ToPointUtf16 for usize {
-    fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> PointUtf16 {
-        snapshot.offset_to_point_utf16(*self)
-    }
-}
-
-impl ToPointUtf16 for PointUtf16 {
-    fn to_point_utf16(&self, _: &BufferSnapshot) -> PointUtf16 {
-        *self
-    }
-}
-
-impl ToPointUtf16 for Point {
-    fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> PointUtf16 {
-        snapshot.point_to_point_utf16(*self)
-    }
-}
-
-pub trait ToOffsetUtf16 {
-    fn to_offset_utf16(&self, snapshot: &BufferSnapshot) -> OffsetUtf16;
-}
-
-impl ToOffsetUtf16 for Anchor {
-    fn to_offset_utf16(&self, snapshot: &BufferSnapshot) -> OffsetUtf16 {
-        snapshot.summary_for_anchor(self)
-    }
-}
-
-impl ToOffsetUtf16 for usize {
-    fn to_offset_utf16(&self, snapshot: &BufferSnapshot) -> OffsetUtf16 {
-        snapshot.offset_to_offset_utf16(*self)
-    }
-}
-
-impl ToOffsetUtf16 for OffsetUtf16 {
-    fn to_offset_utf16(&self, _snapshot: &BufferSnapshot) -> OffsetUtf16 {
-        *self
-    }
-}
-
-pub trait FromAnchor {
-    fn from_anchor(anchor: &Anchor, snapshot: &BufferSnapshot) -> Self;
-}
-
-impl FromAnchor for Point {
-    fn from_anchor(anchor: &Anchor, snapshot: &BufferSnapshot) -> Self {
-        snapshot.summary_for_anchor(anchor)
-    }
-}
-
-impl FromAnchor for PointUtf16 {
-    fn from_anchor(anchor: &Anchor, snapshot: &BufferSnapshot) -> Self {
-        snapshot.summary_for_anchor(anchor)
-    }
-}
-
-impl FromAnchor for usize {
-    fn from_anchor(anchor: &Anchor, snapshot: &BufferSnapshot) -> Self {
-        snapshot.summary_for_anchor(anchor)
-    }
-}
-
-#[derive(Clone, Copy, Debug, PartialEq)]
-pub enum LineEnding {
-    Unix,
-    Windows,
-}
-
-impl Default for LineEnding {
-    fn default() -> Self {
-        #[cfg(unix)]
-        return Self::Unix;
-
-        #[cfg(not(unix))]
-        return Self::CRLF;
-    }
-}
-
-impl LineEnding {
-    pub fn as_str(&self) -> &'static str {
-        match self {
-            LineEnding::Unix => "\n",
-            LineEnding::Windows => "\r\n",
-        }
-    }
-
-    pub fn detect(text: &str) -> Self {
-        let mut max_ix = cmp::min(text.len(), 1000);
-        while !text.is_char_boundary(max_ix) {
-            max_ix -= 1;
-        }
-
-        if let Some(ix) = text[..max_ix].find(['\n']) {
-            if ix > 0 && text.as_bytes()[ix - 1] == b'\r' {
-                Self::Windows
-            } else {
-                Self::Unix
-            }
-        } else {
-            Self::default()
-        }
-    }
-
-    pub fn normalize(text: &mut String) {
-        if let Cow::Owned(replaced) = LINE_SEPARATORS_REGEX.replace_all(text, "\n") {
-            *text = replaced;
-        }
-    }
-
-    pub fn normalize_arc(text: Arc<str>) -> Arc<str> {
-        if let Cow::Owned(replaced) = LINE_SEPARATORS_REGEX.replace_all(&text, "\n") {
-            replaced.into()
-        } else {
-            text
-        }
-    }
-}

crates/text2/src/undo_map.rs 🔗

@@ -1,112 +0,0 @@
-use crate::UndoOperation;
-use std::cmp;
-use sum_tree::{Bias, SumTree};
-
-#[derive(Copy, Clone, Debug)]
-struct UndoMapEntry {
-    key: UndoMapKey,
-    undo_count: u32,
-}
-
-impl sum_tree::Item for UndoMapEntry {
-    type Summary = UndoMapKey;
-
-    fn summary(&self) -> Self::Summary {
-        self.key
-    }
-}
-
-impl sum_tree::KeyedItem for UndoMapEntry {
-    type Key = UndoMapKey;
-
-    fn key(&self) -> Self::Key {
-        self.key
-    }
-}
-
-#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
-struct UndoMapKey {
-    edit_id: clock::Lamport,
-    undo_id: clock::Lamport,
-}
-
-impl sum_tree::Summary for UndoMapKey {
-    type Context = ();
-
-    fn add_summary(&mut self, summary: &Self, _: &Self::Context) {
-        *self = cmp::max(*self, *summary);
-    }
-}
-
-#[derive(Clone, Default)]
-pub struct UndoMap(SumTree<UndoMapEntry>);
-
-impl UndoMap {
-    pub fn insert(&mut self, undo: &UndoOperation) {
-        let edits = undo
-            .counts
-            .iter()
-            .map(|(edit_id, count)| {
-                sum_tree::Edit::Insert(UndoMapEntry {
-                    key: UndoMapKey {
-                        edit_id: *edit_id,
-                        undo_id: undo.timestamp,
-                    },
-                    undo_count: *count,
-                })
-            })
-            .collect::<Vec<_>>();
-        self.0.edit(edits, &());
-    }
-
-    pub fn is_undone(&self, edit_id: clock::Lamport) -> bool {
-        self.undo_count(edit_id) % 2 == 1
-    }
-
-    pub fn was_undone(&self, edit_id: clock::Lamport, version: &clock::Global) -> bool {
-        let mut cursor = self.0.cursor::<UndoMapKey>();
-        cursor.seek(
-            &UndoMapKey {
-                edit_id,
-                undo_id: Default::default(),
-            },
-            Bias::Left,
-            &(),
-        );
-
-        let mut undo_count = 0;
-        for entry in cursor {
-            if entry.key.edit_id != edit_id {
-                break;
-            }
-
-            if version.observed(entry.key.undo_id) {
-                undo_count = cmp::max(undo_count, entry.undo_count);
-            }
-        }
-
-        undo_count % 2 == 1
-    }
-
-    pub fn undo_count(&self, edit_id: clock::Lamport) -> u32 {
-        let mut cursor = self.0.cursor::<UndoMapKey>();
-        cursor.seek(
-            &UndoMapKey {
-                edit_id,
-                undo_id: Default::default(),
-            },
-            Bias::Left,
-            &(),
-        );
-
-        let mut undo_count = 0;
-        for entry in cursor {
-            if entry.key.edit_id != edit_id {
-                break;
-            }
-
-            undo_count = cmp::max(undo_count, entry.undo_count);
-        }
-        undo_count
-    }
-}

crates/theme/Cargo.toml 🔗

@@ -5,6 +5,9 @@ edition = "2021"
 publish = false
 
 [features]
+default = []
+importing-themes = []
+stories = ["dep:itertools", "dep:story"]
 test-support = [
     "gpui/test-support",
     "fs/test-support",
@@ -16,19 +19,22 @@ path = "src/theme.rs"
 doctest = false
 
 [dependencies]
-gpui = { path = "../gpui" }
-fs = { path = "../fs" }
-settings = { path = "../settings" }
-util = { path = "../util" }
-
 anyhow.workspace = true
+fs = { path = "../fs" }
+gpui = { path = "../gpui" }
 indexmap = "1.6.2"
 parking_lot.workspace = true
+refineable.workspace = true
 schemars.workspace = true
 serde.workspace = true
 serde_derive.workspace = true
 serde_json.workspace = true
+settings = { path = "../settings" }
+story = { path = "../story", optional = true }
 toml.workspace = true
+uuid.workspace = true
+util = { path = "../util" }
+itertools = { version = "0.11.0", optional = true }
 
 [dev-dependencies]
 gpui = { path = "../gpui", features = ["test-support"] }

crates/theme/src/components.rs 🔗

@@ -1,480 +0,0 @@
-use gpui::{elements::SafeStylable, Action};
-
-use crate::{Interactive, Toggleable};
-
-use self::{action_button::ButtonStyle, disclosure::Disclosable, svg::SvgStyle, toggle::Toggle};
-
-pub type IconButtonStyle = Interactive<ButtonStyle<SvgStyle>>;
-pub type ToggleIconButtonStyle = Toggleable<IconButtonStyle>;
-
-pub trait ComponentExt<C: SafeStylable> {
-    fn toggleable(self, active: bool) -> Toggle<C, ()>;
-    fn disclosable(self, disclosed: Option<bool>, action: Box<dyn Action>) -> Disclosable<C, ()>;
-}
-
-impl<C: SafeStylable> ComponentExt<C> for C {
-    fn toggleable(self, active: bool) -> Toggle<C, ()> {
-        Toggle::new(self, active)
-    }
-
-    /// Some(True) => disclosed => content is visible
-    /// Some(false) => closed => content is hidden
-    /// None => No disclosure button, but reserve disclosure spacing
-    fn disclosable(self, disclosed: Option<bool>, action: Box<dyn Action>) -> Disclosable<C, ()> {
-        Disclosable::new(disclosed, self, action)
-    }
-}
-
-pub mod disclosure {
-    use gpui::{
-        elements::{Component, ContainerStyle, Empty, Flex, ParentElement, SafeStylable},
-        Action, Element,
-    };
-    use schemars::JsonSchema;
-    use serde_derive::Deserialize;
-
-    use super::{action_button::Button, svg::Svg, IconButtonStyle};
-
-    #[derive(Clone, Default, Deserialize, JsonSchema)]
-    pub struct DisclosureStyle<S> {
-        pub button: IconButtonStyle,
-        #[serde(flatten)]
-        pub container: ContainerStyle,
-        pub spacing: f32,
-        #[serde(flatten)]
-        content: S,
-    }
-
-    impl<S> DisclosureStyle<S> {
-        pub fn button_space(&self) -> f32 {
-            self.spacing + self.button.button_width.unwrap()
-        }
-    }
-
-    pub struct Disclosable<C, S> {
-        disclosed: Option<bool>,
-        action: Box<dyn Action>,
-        id: usize,
-        content: C,
-        style: S,
-    }
-
-    impl Disclosable<(), ()> {
-        pub fn new<C>(
-            disclosed: Option<bool>,
-            content: C,
-            action: Box<dyn Action>,
-        ) -> Disclosable<C, ()> {
-            Disclosable {
-                disclosed,
-                content,
-                action,
-                id: 0,
-                style: (),
-            }
-        }
-    }
-
-    impl<C> Disclosable<C, ()> {
-        pub fn with_id(mut self, id: usize) -> Disclosable<C, ()> {
-            self.id = id;
-            self
-        }
-    }
-
-    impl<C: SafeStylable> SafeStylable for Disclosable<C, ()> {
-        type Style = DisclosureStyle<C::Style>;
-
-        type Output = Disclosable<C, Self::Style>;
-
-        fn with_style(self, style: Self::Style) -> Self::Output {
-            Disclosable {
-                disclosed: self.disclosed,
-                action: self.action,
-                content: self.content,
-                id: self.id,
-                style,
-            }
-        }
-    }
-
-    impl<C: SafeStylable> Component for Disclosable<C, DisclosureStyle<C::Style>> {
-        fn render<V: 'static>(self, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
-            Flex::row()
-                .with_spacing(self.style.spacing)
-                .with_child(if let Some(disclosed) = self.disclosed {
-                    Button::dynamic_action(self.action)
-                        .with_id(self.id)
-                        .with_contents(Svg::new(if disclosed {
-                            "icons/file_icons/chevron_down.svg"
-                        } else {
-                            "icons/file_icons/chevron_right.svg"
-                        }))
-                        .with_style(self.style.button)
-                        .element()
-                        .into_any()
-                } else {
-                    Empty::new()
-                        .into_any()
-                        .constrained()
-                        // TODO: Why is this optional at all?
-                        .with_width(self.style.button.button_width.unwrap())
-                        .into_any()
-                })
-                .with_child(
-                    self.content
-                        .with_style(self.style.content)
-                        .render(cx)
-                        .flex(1., true),
-                )
-                .align_children_center()
-                .contained()
-                .with_style(self.style.container)
-                .into_any()
-        }
-    }
-}
-
-pub mod toggle {
-    use gpui::elements::{Component, SafeStylable};
-
-    use crate::Toggleable;
-
-    pub struct Toggle<C, S> {
-        style: S,
-        active: bool,
-        component: C,
-    }
-
-    impl<C: SafeStylable> Toggle<C, ()> {
-        pub fn new(component: C, active: bool) -> Self {
-            Toggle {
-                active,
-                component,
-                style: (),
-            }
-        }
-    }
-
-    impl<C: SafeStylable> SafeStylable for Toggle<C, ()> {
-        type Style = Toggleable<C::Style>;
-
-        type Output = Toggle<C, Self::Style>;
-
-        fn with_style(self, style: Self::Style) -> Self::Output {
-            Toggle {
-                active: self.active,
-                component: self.component,
-                style,
-            }
-        }
-    }
-
-    impl<C: SafeStylable> Component for Toggle<C, Toggleable<C::Style>> {
-        fn render<V: 'static>(self, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
-            self.component
-                .with_style(self.style.in_state(self.active).clone())
-                .render(cx)
-        }
-    }
-}
-
-pub mod action_button {
-    use std::borrow::Cow;
-
-    use gpui::{
-        elements::{Component, ContainerStyle, MouseEventHandler, SafeStylable, TooltipStyle},
-        platform::{CursorStyle, MouseButton},
-        Action, Element, TypeTag,
-    };
-    use schemars::JsonSchema;
-    use serde_derive::Deserialize;
-
-    use crate::Interactive;
-
-    #[derive(Clone, Deserialize, Default, JsonSchema)]
-    pub struct ButtonStyle<C> {
-        #[serde(flatten)]
-        pub container: ContainerStyle,
-        // TODO: These are incorrect for the intended usage of the buttons.
-        // The size should be constant, but putting them here duplicates them
-        // across the states the buttons can be in
-        pub button_width: Option<f32>,
-        pub button_height: Option<f32>,
-        #[serde(flatten)]
-        contents: C,
-    }
-
-    pub struct Button<C, S> {
-        action: Box<dyn Action>,
-        tooltip: Option<(Cow<'static, str>, TooltipStyle)>,
-        tag: TypeTag,
-        id: usize,
-        contents: C,
-        style: Interactive<S>,
-    }
-
-    impl Button<(), ()> {
-        pub fn dynamic_action(action: Box<dyn Action>) -> Button<(), ()> {
-            Self {
-                contents: (),
-                tag: action.type_tag(),
-                action,
-                style: Interactive::new_blank(),
-                tooltip: None,
-                id: 0,
-            }
-        }
-
-        pub fn action<A: Action + Clone>(action: A) -> Self {
-            Self::dynamic_action(Box::new(action))
-        }
-
-        pub fn with_tooltip(
-            mut self,
-            tooltip: impl Into<Cow<'static, str>>,
-            tooltip_style: TooltipStyle,
-        ) -> Self {
-            self.tooltip = Some((tooltip.into(), tooltip_style));
-            self
-        }
-
-        pub fn with_id(mut self, id: usize) -> Self {
-            self.id = id;
-            self
-        }
-
-        pub fn with_contents<C: SafeStylable>(self, contents: C) -> Button<C, ()> {
-            Button {
-                action: self.action,
-                tag: self.tag,
-                style: self.style,
-                tooltip: self.tooltip,
-                id: self.id,
-                contents,
-            }
-        }
-    }
-
-    impl<C: SafeStylable> SafeStylable for Button<C, ()> {
-        type Style = Interactive<ButtonStyle<C::Style>>;
-        type Output = Button<C, ButtonStyle<C::Style>>;
-
-        fn with_style(self, style: Self::Style) -> Self::Output {
-            Button {
-                action: self.action,
-                tag: self.tag,
-                contents: self.contents,
-                tooltip: self.tooltip,
-                id: self.id,
-                style,
-            }
-        }
-    }
-
-    impl<C: SafeStylable> Component for Button<C, ButtonStyle<C::Style>> {
-        fn render<V: 'static>(self, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
-            let mut button = MouseEventHandler::new_dynamic(self.tag, self.id, cx, |state, cx| {
-                let style = self.style.style_for(state);
-                let mut contents = self
-                    .contents
-                    .with_style(style.contents.to_owned())
-                    .render(cx)
-                    .contained()
-                    .with_style(style.container)
-                    .constrained();
-
-                if let Some(height) = style.button_height {
-                    contents = contents.with_height(height);
-                }
-
-                if let Some(width) = style.button_width {
-                    contents = contents.with_width(width);
-                }
-
-                contents.into_any()
-            })
-            .on_click(MouseButton::Left, {
-                let action = self.action.boxed_clone();
-                move |_, _, cx| {
-                    let window = cx.window();
-                    let view = cx.view_id();
-                    let action = action.boxed_clone();
-                    cx.spawn(|_, mut cx| async move {
-                        window.dispatch_action(view, action.as_ref(), &mut cx)
-                    })
-                    .detach();
-                }
-            })
-            .with_cursor_style(CursorStyle::PointingHand)
-            .into_any();
-
-            if let Some((tooltip, style)) = self.tooltip {
-                button = button
-                    .with_dynamic_tooltip(self.tag, 0, tooltip, Some(self.action), style, cx)
-                    .into_any()
-            }
-
-            button
-        }
-    }
-}
-
-pub mod svg {
-    use std::borrow::Cow;
-
-    use gpui::{
-        elements::{Component, Empty, SafeStylable},
-        Element,
-    };
-    use schemars::JsonSchema;
-    use serde::Deserialize;
-
-    #[derive(Clone, Default, JsonSchema)]
-    pub struct SvgStyle {
-        icon_width: f32,
-        icon_height: f32,
-        color: gpui::color::Color,
-    }
-
-    impl<'de> Deserialize<'de> for SvgStyle {
-        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-        where
-            D: serde::Deserializer<'de>,
-        {
-            #[derive(Deserialize)]
-            #[serde(untagged)]
-            pub enum IconSize {
-                IconSize { icon_size: f32 },
-                Dimensions { width: f32, height: f32 },
-                IconDimensions { icon_width: f32, icon_height: f32 },
-            }
-
-            #[derive(Deserialize)]
-            struct SvgStyleHelper {
-                #[serde(flatten)]
-                size: IconSize,
-                color: gpui::color::Color,
-            }
-
-            let json = SvgStyleHelper::deserialize(deserializer)?;
-            let color = json.color;
-
-            let result = match json.size {
-                IconSize::IconSize { icon_size } => SvgStyle {
-                    icon_width: icon_size,
-                    icon_height: icon_size,
-                    color,
-                },
-                IconSize::Dimensions { width, height } => SvgStyle {
-                    icon_width: width,
-                    icon_height: height,
-                    color,
-                },
-                IconSize::IconDimensions {
-                    icon_width,
-                    icon_height,
-                } => SvgStyle {
-                    icon_width,
-                    icon_height,
-                    color,
-                },
-            };
-
-            Ok(result)
-        }
-    }
-
-    pub struct Svg<S> {
-        path: Option<Cow<'static, str>>,
-        style: S,
-    }
-
-    impl Svg<()> {
-        pub fn new(path: impl Into<Cow<'static, str>>) -> Self {
-            Self {
-                path: Some(path.into()),
-                style: (),
-            }
-        }
-
-        pub fn optional(path: Option<impl Into<Cow<'static, str>>>) -> Self {
-            Self {
-                path: path.map(Into::into),
-                style: (),
-            }
-        }
-    }
-
-    impl SafeStylable for Svg<()> {
-        type Style = SvgStyle;
-
-        type Output = Svg<SvgStyle>;
-
-        fn with_style(self, style: Self::Style) -> Self::Output {
-            Svg {
-                path: self.path,
-                style,
-            }
-        }
-    }
-
-    impl Component for Svg<SvgStyle> {
-        fn render<V: 'static>(self, _: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
-            if let Some(path) = self.path {
-                gpui::elements::Svg::new(path)
-                    .with_color(self.style.color)
-                    .constrained()
-            } else {
-                Empty::new().constrained()
-            }
-            .constrained()
-            .with_width(self.style.icon_width)
-            .with_height(self.style.icon_height)
-            .into_any()
-        }
-    }
-}
-
-pub mod label {
-    use std::borrow::Cow;
-
-    use gpui::{
-        elements::{Component, LabelStyle, SafeStylable},
-        fonts::TextStyle,
-        Element,
-    };
-
-    pub struct Label<S> {
-        text: Cow<'static, str>,
-        style: S,
-    }
-
-    impl Label<()> {
-        pub fn new(text: impl Into<Cow<'static, str>>) -> Self {
-            Self {
-                text: text.into(),
-                style: (),
-            }
-        }
-    }
-
-    impl SafeStylable for Label<()> {
-        type Style = TextStyle;
-
-        type Output = Label<LabelStyle>;
-
-        fn with_style(self, style: Self::Style) -> Self::Output {
-            Label {
-                text: self.text,
-                style: style.into(),
-            }
-        }
-    }
-
-    impl Component for Label<LabelStyle> {
-        fn render<V: 'static>(self, _: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
-            gpui::elements::Label::new(self.text, self.style).into_any()
-        }
-    }
-}

crates/theme/src/theme.rs 🔗

@@ -1,1329 +1,148 @@
-pub mod components;
-mod theme_registry;
-mod theme_settings;
-pub mod ui;
-
-use components::{
-    action_button::ButtonStyle, disclosure::DisclosureStyle, IconButtonStyle, ToggleIconButtonStyle,
-};
-use gpui::{
-    color::Color,
-    elements::{Border, ContainerStyle, ImageStyle, LabelStyle, SvgStyle, TooltipStyle},
-    fonts::{HighlightStyle, TextStyle},
-    platform, AppContext, AssetSource, MouseState,
-};
-use parking_lot::Mutex;
-use schemars::JsonSchema;
-use serde::{de::DeserializeOwned, Deserialize};
-use serde_json::Value;
-use settings::SettingsStore;
-use std::{any::Any, collections::HashMap, ops::Deref, sync::Arc};
-use ui::{CheckboxStyle, CopilotCTAButton, IconStyle, ModalStyle};
-
-pub use theme_registry::*;
-pub use theme_settings::*;
-
-pub fn current(cx: &AppContext) -> Arc<Theme> {
-    settings::get::<ThemeSettings>(cx).theme.clone()
-}
-
-pub fn init(source: impl AssetSource, cx: &mut AppContext) {
-    cx.set_global(ThemeRegistry::new(source, cx.font_cache().clone()));
-    settings::register::<ThemeSettings>(cx);
-
-    let mut prev_buffer_font_size = settings::get::<ThemeSettings>(cx).buffer_font_size;
-    cx.observe_global::<SettingsStore, _>(move |cx| {
-        let buffer_font_size = settings::get::<ThemeSettings>(cx).buffer_font_size;
-        if buffer_font_size != prev_buffer_font_size {
-            prev_buffer_font_size = buffer_font_size;
-            reset_font_size(cx);
-        }
-    })
-    .detach();
-}
-
-#[derive(Deserialize, Default, JsonSchema)]
-pub struct Theme {
-    #[serde(default)]
-    pub meta: ThemeMeta,
-    pub workspace: Workspace,
-    pub context_menu: ContextMenu,
-    pub toolbar_dropdown_menu: DropdownMenu,
-    pub copilot: Copilot,
-    pub collab_panel: CollabPanel,
-    pub project_panel: ProjectPanel,
-    pub chat_panel: ChatPanel,
-    pub notification_panel: NotificationPanel,
-    pub command_palette: CommandPalette,
-    pub picker: Picker,
-    pub editor: Editor,
-    pub search: Search,
-    pub project_diagnostics: ProjectDiagnostics,
-    pub shared_screen: ContainerStyle,
-    pub contact_notification: ContactNotification,
-    pub update_notification: UpdateNotification,
-    pub simple_message_notification: MessageNotification,
-    pub project_shared_notification: ProjectSharedNotification,
-    pub incoming_call_notification: IncomingCallNotification,
-    pub tooltip: TooltipStyle,
-    pub terminal: TerminalStyle,
-    pub assistant: AssistantStyle,
-    pub feedback: FeedbackStyle,
-    pub welcome: WelcomeStyle,
-    pub titlebar: Titlebar,
-    pub component_test: ComponentTest,
-    // Nathan: New elements are styled in Rust, directly from the base theme.
-    // We store it on the legacy theme so we can mix both kinds of elements during the transition.
-    #[schemars(skip)]
-    pub base_theme: serde_json::Value,
-    // A place to cache deserialized base theme.
-    #[serde(skip_deserializing)]
-    #[schemars(skip)]
-    pub deserialized_base_theme: Mutex<Option<Box<dyn Any + Send + Sync>>>,
-}
-
-#[derive(Deserialize, Default, Clone, JsonSchema)]
-pub struct ThemeMeta {
-    #[serde(skip_deserializing)]
-    pub id: usize,
-    pub name: String,
-    pub is_light: bool,
-}
-
-#[derive(Deserialize, Default, JsonSchema)]
-pub struct Workspace {
-    pub background: Color,
-    pub blank_pane: BlankPaneStyle,
-    pub tab_bar: TabBar,
-    pub pane_divider: Border,
-    pub leader_border_opacity: f32,
-    pub leader_border_width: f32,
-    pub dock: Dock,
-    pub status_bar: StatusBar,
-    pub toolbar: Toolbar,
-    pub disconnected_overlay: ContainedText,
-    pub modal: ContainerStyle,
-    pub zoomed_panel_foreground: ContainerStyle,
-    pub zoomed_pane_foreground: ContainerStyle,
-    pub zoomed_background: ContainerStyle,
-    pub notification: ContainerStyle,
-    pub notifications: Notifications,
-    pub joining_project_avatar: ImageStyle,
-    pub joining_project_message: ContainedText,
-    pub external_location_message: ContainedText,
-    pub drop_target_overlay_color: Color,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct BlankPaneStyle {
-    pub logo: SvgStyle,
-    pub logo_shadow: SvgStyle,
-    pub logo_container: ContainerStyle,
-    pub keyboard_hints: ContainerStyle,
-    pub keyboard_hint: Interactive<ContainedText>,
-    pub keyboard_hint_width: f32,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct Titlebar {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub height: f32,
-    pub menu: TitlebarMenu,
-    pub project_menu_button: Toggleable<Interactive<ContainedText>>,
-    pub git_menu_button: Toggleable<Interactive<ContainedText>>,
-    pub project_host: Interactive<ContainedText>,
-    pub item_spacing: f32,
-    pub face_pile_spacing: f32,
-    pub avatar_ribbon: AvatarRibbon,
-    pub follower_avatar_overlap: f32,
-    pub leader_selection: ContainerStyle,
-    pub offline_icon: OfflineIcon,
-    pub leader_avatar: AvatarStyle,
-    pub follower_avatar: AvatarStyle,
-    pub inactive_avatar_grayscale: bool,
-    pub sign_in_button: Toggleable<Interactive<ContainedText>>,
-    pub outdated_warning: ContainedText,
-    pub share_button: Toggleable<Interactive<ContainedText>>,
-    pub muted: Color,
-    pub speaking: Color,
-    pub screen_share_button: Toggleable<Interactive<IconButton>>,
-    pub toggle_contacts_button: Toggleable<Interactive<IconButton>>,
-    pub toggle_microphone_button: Toggleable<Interactive<IconButton>>,
-    pub toggle_speakers_button: Toggleable<Interactive<IconButton>>,
-    pub leave_call_button: Interactive<IconButton>,
-    pub toggle_contacts_badge: ContainerStyle,
-    pub user_menu: UserMenu,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct TitlebarMenu {
-    pub width: f32,
-    pub height: f32,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct UserMenu {
-    pub user_menu_button_online: UserMenuButton,
-    pub user_menu_button_offline: UserMenuButton,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct UserMenuButton {
-    pub user_menu: Toggleable<Interactive<Icon>>,
-    pub avatar: AvatarStyle,
-    pub icon: Icon,
-}
-
-#[derive(Copy, Clone, Deserialize, Default, JsonSchema)]
-pub struct AvatarStyle {
-    #[serde(flatten)]
-    pub image: ImageStyle,
-    pub outer_width: f32,
-    pub outer_corner_radius: f32,
-}
-
-#[derive(Deserialize, Default, Clone, JsonSchema)]
-pub struct Copilot {
-    pub out_link_icon: Interactive<IconStyle>,
-    pub modal: ModalStyle,
-    pub auth: CopilotAuth,
-}
-
-#[derive(Deserialize, Default, Clone, JsonSchema)]
-pub struct CopilotAuth {
-    pub content_width: f32,
-    pub prompting: CopilotAuthPrompting,
-    pub not_authorized: CopilotAuthNotAuthorized,
-    pub authorized: CopilotAuthAuthorized,
-    pub cta_button: CopilotCTAButton,
-    pub header: IconStyle,
-}
-
-#[derive(Deserialize, Default, Clone, JsonSchema)]
-pub struct CopilotAuthPrompting {
-    pub subheading: ContainedText,
-    pub hint: ContainedText,
-    pub device_code: DeviceCode,
-}
-
-#[derive(Deserialize, Default, Clone, JsonSchema)]
-pub struct DeviceCode {
-    pub text: TextStyle,
-    pub cta: CopilotCTAButton,
-    pub left: f32,
-    pub left_container: ContainerStyle,
-    pub right: f32,
-    pub right_container: Interactive<ContainerStyle>,
-}
-
-#[derive(Deserialize, Default, Clone, JsonSchema)]
-pub struct CopilotAuthNotAuthorized {
-    pub subheading: ContainedText,
-    pub warning: ContainedText,
-}
-
-#[derive(Deserialize, Default, Clone, JsonSchema)]
-pub struct CopilotAuthAuthorized {
-    pub subheading: ContainedText,
-    pub hint: ContainedText,
-}
-
-#[derive(Deserialize, Default, JsonSchema)]
-pub struct CollabPanel {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub disclosure: DisclosureStyle<()>,
-    pub list_empty_state: Toggleable<Interactive<ContainedText>>,
-    pub list_empty_icon: Icon,
-    pub list_empty_label_container: ContainerStyle,
-    pub log_in_button: Interactive<ContainedText>,
-    pub channel_editor: ContainerStyle,
-    pub channel_hash: Icon,
-    pub channel_note_active_color: Color,
-    pub tabbed_modal: TabbedModal,
-    pub contact_finder: ContactFinder,
-    pub channel_modal: ChannelModal,
-    pub user_query_editor: FieldEditor,
-    pub user_query_editor_height: f32,
-    pub leave_call_button: Toggleable<Interactive<IconButton>>,
-    pub add_contact_button: Toggleable<Interactive<IconButton>>,
-    pub add_channel_button: Toggleable<Interactive<IconButton>>,
-    pub header_row: ContainedText,
-    pub dragged_over_header: ContainerStyle,
-    pub subheader_row: Toggleable<Interactive<ContainedText>>,
-    pub leave_call: Interactive<ContainedText>,
-    pub contact_row: Toggleable<Interactive<ContainerStyle>>,
-    pub channel_row: Toggleable<Interactive<ContainerStyle>>,
-    pub channel_name: Toggleable<ContainedText>,
-    pub row_height: f32,
-    pub project_row: Toggleable<Interactive<ProjectRow>>,
-    pub tree_branch: Toggleable<Interactive<TreeBranch>>,
-    pub contact_avatar: ImageStyle,
-    pub channel_avatar: ImageStyle,
-    pub extra_participant_label: ContainedText,
-    pub contact_status_free: ContainerStyle,
-    pub contact_status_busy: ContainerStyle,
-    pub contact_username: ContainedText,
-    pub contact_button: Interactive<IconButton>,
-    pub contact_button_spacing: f32,
-    pub channel_indent: f32,
-    pub disabled_button: IconButton,
-    pub section_icon_size: f32,
-    pub calling_indicator: ContainedText,
-    pub face_overlap: f32,
-}
-
-#[derive(Deserialize, Default, JsonSchema)]
-pub struct ComponentTest {
-    pub button: Interactive<ButtonStyle<TextStyle>>,
-    pub toggle: Toggleable<Interactive<ButtonStyle<TextStyle>>>,
-    pub disclosure: DisclosureStyle<TextStyle>,
-}
-
-#[derive(Deserialize, Default, JsonSchema)]
-pub struct TabbedModal {
-    pub tab_button: Toggleable<Interactive<ContainedText>>,
-    pub modal: ContainerStyle,
-    pub header: ContainerStyle,
-    pub body: ContainerStyle,
-    pub title: ContainedText,
-    pub visibility_toggle: Interactive<ContainedText>,
-    pub channel_link: Interactive<ContainedText>,
-    pub picker: Picker,
-    pub max_height: f32,
-    pub max_width: f32,
-    pub row_height: f32,
-}
-
-#[derive(Deserialize, Default, JsonSchema)]
-pub struct ChannelModal {
-    pub contact_avatar: ImageStyle,
-    pub contact_username: ContainerStyle,
-    pub remove_member_button: ContainedText,
-    pub cancel_invite_button: ContainedText,
-    pub member_icon: IconButton,
-    pub invitee_icon: IconButton,
-    pub member_tag: ContainedText,
-}
-
-#[derive(Deserialize, Default, JsonSchema)]
-pub struct ProjectRow {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub icon: Icon,
-    pub name: ContainedText,
-}
-
-#[derive(Deserialize, Default, Clone, Copy, JsonSchema)]
-pub struct TreeBranch {
-    pub width: f32,
-    pub color: Color,
-}
-
-#[derive(Deserialize, Default, JsonSchema)]
-pub struct ContactFinder {
-    pub contact_avatar: ImageStyle,
-    pub contact_username: ContainerStyle,
-    pub contact_button: IconButton,
-    pub disabled_contact_button: IconButton,
-}
-
-#[derive(Deserialize, Default, JsonSchema)]
-pub struct DropdownMenu {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub header: Interactive<DropdownMenuItem>,
-    pub section_header: ContainedText,
-    pub item: Toggleable<Interactive<DropdownMenuItem>>,
-    pub row_height: f32,
-}
-
-#[derive(Deserialize, Default, JsonSchema)]
-pub struct DropdownMenuItem {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    #[serde(flatten)]
-    pub text: TextStyle,
-    pub secondary_text: Option<TextStyle>,
-    #[serde(default)]
-    pub secondary_text_spacing: f32,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct TabBar {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub pane_button: Toggleable<Interactive<IconButton>>,
-    pub pane_button_container: ContainerStyle,
-    pub active_pane: TabStyles,
-    pub inactive_pane: TabStyles,
-    pub dragged_tab: Tab,
-    pub height: f32,
-    pub nav_button: Interactive<IconButton>,
-}
-
-impl TabBar {
-    pub fn tab_style(&self, pane_active: bool, tab_active: bool) -> &Tab {
-        let tabs = if pane_active {
-            &self.active_pane
-        } else {
-            &self.inactive_pane
-        };
-
-        if tab_active {
-            &tabs.active_tab
-        } else {
-            &tabs.inactive_tab
+mod default_colors;
+mod default_theme;
+mod one_themes;
+pub mod prelude;
+mod registry;
+mod scale;
+mod settings;
+mod styles;
+#[cfg(not(feature = "importing-themes"))]
+mod themes;
+mod user_theme;
+
+use std::sync::Arc;
+
+use ::settings::Settings;
+pub use default_colors::*;
+pub use default_theme::*;
+pub use registry::*;
+pub use scale::*;
+pub use settings::*;
+pub use styles::*;
+#[cfg(not(feature = "importing-themes"))]
+pub use themes::*;
+pub use user_theme::*;
+
+use gpui::{AppContext, Hsla, SharedString};
+use serde::Deserialize;
+
+#[derive(Debug, PartialEq, Clone, Copy, Deserialize)]
+pub enum Appearance {
+    Light,
+    Dark,
+}
+
+impl Appearance {
+    pub fn is_light(&self) -> bool {
+        match self {
+            Self::Light => true,
+            Self::Dark => false,
         }
     }
 }
 
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct TabStyles {
-    pub active_tab: Tab,
-    pub inactive_tab: Tab,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct AvatarRibbon {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub width: f32,
-    pub height: f32,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct OfflineIcon {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub width: f32,
-    pub color: Color,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct Tab {
-    pub height: f32,
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    #[serde(flatten)]
-    pub label: LabelStyle,
-    pub description: ContainedText,
-    pub spacing: f32,
-    pub close_icon_width: f32,
-    pub type_icon_width: f32,
-    pub icon_close: Color,
-    pub icon_close_active: Color,
-    pub icon_dirty: Color,
-    pub icon_conflict: Color,
-    pub git: GitProjectStatus,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct Toolbar {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub height: f32,
-    pub item_spacing: f32,
-    pub toggleable_tool: Toggleable<Interactive<IconButton>>,
-    pub toggleable_text_tool: Toggleable<Interactive<ContainedText>>,
-    pub breadcrumb_height: f32,
-    pub breadcrumbs: Interactive<ContainedText>,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct Notifications {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub width: f32,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct Search {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub editor: FindEditor,
-    pub invalid_editor: ContainerStyle,
-    pub option_button_group: ContainerStyle,
-    pub include_exclude_editor: FindEditor,
-    pub invalid_include_exclude_editor: ContainerStyle,
-    pub include_exclude_inputs: ContainedText,
-    pub option_button_component: ToggleIconButtonStyle,
-    pub match_background: Color,
-    pub match_index: ContainedText,
-    pub major_results_status: TextStyle,
-    pub minor_results_status: TextStyle,
-    pub editor_icon: IconStyle,
-    pub mode_button: Toggleable<Interactive<ContainedText>>,
-    pub nav_button: Toggleable<Interactive<ContainedLabel>>,
-    pub search_bar_row_height: f32,
-    pub search_row_spacing: f32,
-    pub option_button_height: f32,
-    pub modes_container: ContainerStyle,
-    pub replace_icon: IconStyle,
-    // Used for filters and replace
-    pub option_button: Toggleable<Interactive<IconButton>>,
-    pub action_button: IconButtonStyle,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct FindEditor {
-    #[serde(flatten)]
-    pub input: FieldEditor,
-    pub min_width: f32,
-    pub max_width: f32,
-}
-
-#[derive(Deserialize, Default, JsonSchema)]
-pub struct StatusBar {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub height: f32,
-    pub item_spacing: f32,
-    pub cursor_position: TextStyle,
-    pub vim_mode_indicator: ContainedText,
-    pub active_language: Interactive<ContainedText>,
-    pub auto_update_progress_message: TextStyle,
-    pub auto_update_done_message: TextStyle,
-    pub lsp_status: Interactive<StatusBarLspStatus>,
-    pub panel_buttons: StatusBarPanelButtons,
-    pub diagnostic_summary: Interactive<StatusBarDiagnosticSummary>,
-    pub diagnostic_message: Interactive<ContainedText>,
-}
-
-#[derive(Deserialize, Default, JsonSchema)]
-pub struct StatusBarPanelButtons {
-    pub group_left: ContainerStyle,
-    pub group_bottom: ContainerStyle,
-    pub group_right: ContainerStyle,
-    pub button: Toggleable<Interactive<PanelButton>>,
-}
-
-#[derive(Deserialize, Default, JsonSchema)]
-pub struct StatusBarDiagnosticSummary {
-    pub container_ok: ContainerStyle,
-    pub container_warning: ContainerStyle,
-    pub container_error: ContainerStyle,
-    pub text: TextStyle,
-    pub icon_color_ok: Color,
-    pub icon_color_warning: Color,
-    pub icon_color_error: Color,
-    pub height: f32,
-    pub icon_width: f32,
-    pub icon_spacing: f32,
-    pub summary_spacing: f32,
-}
-
-#[derive(Deserialize, Default, JsonSchema)]
-pub struct StatusBarLspStatus {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub height: f32,
-    pub icon_spacing: f32,
-    pub icon_color: Color,
-    pub icon_width: f32,
-    pub message: TextStyle,
-}
-
-#[derive(Deserialize, Default, JsonSchema)]
-pub struct Dock {
-    pub left: ContainerStyle,
-    pub bottom: ContainerStyle,
-    pub right: ContainerStyle,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct PanelButton {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub icon_color: Color,
-    pub icon_size: f32,
-    pub label: ContainedText,
-}
-
-#[derive(Deserialize, Default, JsonSchema)]
-pub struct ProjectPanel {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub entry: Toggleable<Interactive<ProjectPanelEntry>>,
-    pub dragged_entry: ProjectPanelEntry,
-    pub ignored_entry: Toggleable<Interactive<ProjectPanelEntry>>,
-    pub cut_entry: Toggleable<Interactive<ProjectPanelEntry>>,
-    pub filename_editor: FieldEditor,
-    pub indent_width: f32,
-    pub open_project_button: Interactive<ContainedText>,
-}
-
-#[derive(Clone, Debug, Deserialize, Default, JsonSchema)]
-pub struct ProjectPanelEntry {
-    pub height: f32,
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub text: TextStyle,
-    pub icon_size: f32,
-    pub icon_color: Color,
-    pub chevron_color: Color,
-    pub chevron_size: f32,
-    pub icon_spacing: f32,
-    pub status: EntryStatus,
-}
-
-#[derive(Clone, Debug, Deserialize, Default, JsonSchema)]
-pub struct EntryStatus {
-    pub git: GitProjectStatus,
-}
-
-#[derive(Clone, Debug, Deserialize, Default, JsonSchema)]
-pub struct GitProjectStatus {
-    pub modified: Color,
-    pub inserted: Color,
-    pub conflict: Color,
-}
-
-#[derive(Clone, Debug, Deserialize, Default, JsonSchema)]
-pub struct ContextMenu {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub item: Toggleable<Interactive<ContextMenuItem>>,
-    pub keystroke_margin: f32,
-    pub separator: ContainerStyle,
-}
-
-#[derive(Clone, Debug, Deserialize, Default, JsonSchema)]
-pub struct ContextMenuItem {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub label: TextStyle,
-    pub keystroke: ContainedText,
-    pub icon_width: f32,
-    pub icon_spacing: f32,
-}
-
-#[derive(Debug, Deserialize, Default, JsonSchema)]
-pub struct CommandPalette {
-    pub key: Toggleable<ContainedLabel>,
-    pub keystroke_spacing: f32,
-}
-
-#[derive(Deserialize, Default, JsonSchema)]
-pub struct InviteLink {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    #[serde(flatten)]
-    pub label: LabelStyle,
-    pub icon: Icon,
-}
-
-#[derive(Deserialize, Clone, Copy, Default, JsonSchema)]
-pub struct Icon {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub color: Color,
-    pub width: f32,
-}
-
-#[derive(Deserialize, Clone, Copy, Default, JsonSchema)]
-pub struct IconButton {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub color: Color,
-    pub icon_width: f32,
-    pub button_width: f32,
-}
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum LoadThemes {
+    /// Only load the base theme.
+    ///
+    /// No user themes will be loaded.
+    JustBase,
 
-#[derive(Deserialize, Default, JsonSchema)]
-pub struct ChatPanel {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub list: ContainerStyle,
-    pub channel_select: ChannelSelect,
-    pub input_editor: FieldEditor,
-    pub avatar: AvatarStyle,
-    pub avatar_container: ContainerStyle,
-    pub rich_text: RichTextStyle,
-    pub message_sender: ContainedText,
-    pub message_timestamp: ContainedText,
-    pub message: Interactive<ContainerStyle>,
-    pub continuation_message: Interactive<ContainerStyle>,
-    pub pending_message: Interactive<ContainerStyle>,
-    pub last_message_bottom_spacing: f32,
-    pub sign_in_prompt: Interactive<TextStyle>,
-    pub icon_button: Interactive<IconButton>,
+    /// Load all of the built-in themes.
+    All,
 }
 
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct RichTextStyle {
-    pub text: TextStyle,
-    pub mention_highlight: HighlightStyle,
-    pub mention_background: Option<Color>,
-    pub self_mention_highlight: HighlightStyle,
-    pub self_mention_background: Option<Color>,
-    pub code_background: Option<Color>,
-}
+pub fn init(themes_to_load: LoadThemes, cx: &mut AppContext) {
+    cx.set_global(ThemeRegistry::default());
 
-#[derive(Deserialize, Default, JsonSchema)]
-pub struct NotificationPanel {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub title: ContainedText,
-    pub title_icon: SvgStyle,
-    pub title_height: f32,
-    pub list: ContainerStyle,
-    pub avatar: AvatarStyle,
-    pub avatar_container: ContainerStyle,
-    pub sign_in_prompt: Interactive<TextStyle>,
-    pub icon_button: Interactive<IconButton>,
-    pub unread_text: ContainedText,
-    pub read_text: ContainedText,
-    pub timestamp: ContainedText,
-    pub button: Interactive<ContainedText>,
-}
-
-#[derive(Deserialize, Default, JsonSchema)]
-pub struct ChannelSelect {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub header: ChannelName,
-    pub item: ChannelName,
-    pub active_item: ChannelName,
-    pub hovered_item: ChannelName,
-    pub menu: ContainerStyle,
-}
-
-#[derive(Deserialize, Default, JsonSchema)]
-pub struct ChannelName {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub hash: ContainedText,
-    pub name: TextStyle,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct Picker {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub empty_container: ContainerStyle,
-    pub input_editor: FieldEditor,
-    pub empty_input_editor: FieldEditor,
-    pub no_matches: ContainedLabel,
-    pub item: Toggleable<Interactive<ContainedLabel>>,
-    pub header: ContainedLabel,
-    pub footer: Interactive<ContainedLabel>,
-}
-
-#[derive(Clone, Debug, Deserialize, Default, JsonSchema)]
-pub struct ContainedText {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    #[serde(flatten)]
-    pub text: TextStyle,
-}
-
-#[derive(Clone, Debug, Deserialize, Default, JsonSchema)]
-pub struct ContainedLabel {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    #[serde(flatten)]
-    pub label: LabelStyle,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct ProjectDiagnostics {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub empty_message: TextStyle,
-    pub tab_icon_width: f32,
-    pub tab_icon_spacing: f32,
-    pub tab_summary_spacing: f32,
-}
-
-#[derive(Deserialize, Default, JsonSchema)]
-pub struct ContactNotification {
-    pub header_avatar: ImageStyle,
-    pub header_message: ContainedText,
-    pub header_height: f32,
-    pub body_message: ContainedText,
-    pub button: Interactive<ContainedText>,
-    pub dismiss_button: Interactive<IconButton>,
-}
-
-#[derive(Deserialize, Default, JsonSchema)]
-pub struct UpdateNotification {
-    pub message: ContainedText,
-    pub action_message: Interactive<ContainedText>,
-    pub dismiss_button: Interactive<IconButton>,
-}
-
-#[derive(Deserialize, Default, JsonSchema)]
-pub struct MessageNotification {
-    pub message: ContainedText,
-    pub action_message: Interactive<ContainedText>,
-    pub dismiss_button: Interactive<IconButton>,
-}
-
-#[derive(Deserialize, Default, JsonSchema)]
-pub struct ProjectSharedNotification {
-    pub window_height: f32,
-    pub window_width: f32,
-    #[serde(default)]
-    pub background: Color,
-    pub owner_container: ContainerStyle,
-    pub owner_avatar: ImageStyle,
-    pub owner_metadata: ContainerStyle,
-    pub owner_username: ContainedText,
-    pub message: ContainedText,
-    pub worktree_roots: ContainedText,
-    pub button_width: f32,
-    pub open_button: ContainedText,
-    pub dismiss_button: ContainedText,
-}
-
-#[derive(Deserialize, Default, JsonSchema)]
-pub struct IncomingCallNotification {
-    pub window_height: f32,
-    pub window_width: f32,
-    #[serde(default)]
-    pub background: Color,
-    pub caller_container: ContainerStyle,
-    pub caller_avatar: ImageStyle,
-    pub caller_metadata: ContainerStyle,
-    pub caller_username: ContainedText,
-    pub caller_message: ContainedText,
-    pub worktree_roots: ContainedText,
-    pub button_width: f32,
-    pub accept_button: ContainedText,
-    pub decline_button: ContainedText,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct Editor {
-    pub text_color: Color,
-    #[serde(default)]
-    pub background: Color,
-    pub selection: SelectionStyle,
-    pub gutter_background: Color,
-    pub gutter_padding_factor: f32,
-    pub active_line_background: Color,
-    pub highlighted_line_background: Color,
-    pub rename_fade: f32,
-    pub document_highlight_read_background: Color,
-    pub document_highlight_write_background: Color,
-    pub diff: DiffStyle,
-    pub wrap_guide: Color,
-    pub active_wrap_guide: Color,
-    pub line_number: Color,
-    pub line_number_active: Color,
-    pub guest_selections: Vec<SelectionStyle>,
-    pub absent_selection: SelectionStyle,
-    pub syntax: Arc<SyntaxTheme>,
-    pub hint: HighlightStyle,
-    pub suggestion: HighlightStyle,
-    pub diagnostic_path_header: DiagnosticPathHeader,
-    pub diagnostic_header: DiagnosticHeader,
-    pub error_diagnostic: DiagnosticStyle,
-    pub invalid_error_diagnostic: DiagnosticStyle,
-    pub warning_diagnostic: DiagnosticStyle,
-    pub invalid_warning_diagnostic: DiagnosticStyle,
-    pub information_diagnostic: DiagnosticStyle,
-    pub invalid_information_diagnostic: DiagnosticStyle,
-    pub hint_diagnostic: DiagnosticStyle,
-    pub invalid_hint_diagnostic: DiagnosticStyle,
-    pub autocomplete: AutocompleteStyle,
-    pub code_actions: CodeActions,
-    pub folds: Folds,
-    pub unnecessary_code_fade: f32,
-    pub hover_popover: HoverPopover,
-    pub link_definition: HighlightStyle,
-    pub composition_mark: HighlightStyle,
-    pub jump_icon: Interactive<IconButton>,
-    pub scrollbar: Scrollbar,
-    pub whitespace: Color,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct Scrollbar {
-    pub track: ContainerStyle,
-    pub thumb: ContainerStyle,
-    pub width: f32,
-    pub min_height_factor: f32,
-    pub git: BufferGitDiffColors,
-    pub selections: Color,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct BufferGitDiffColors {
-    pub inserted: Color,
-    pub modified: Color,
-    pub deleted: Color,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct DiagnosticPathHeader {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub filename: ContainedText,
-    pub path: ContainedText,
-    pub text_scale_factor: f32,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct DiagnosticHeader {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub source: ContainedLabel,
-    pub message: ContainedLabel,
-    pub code: ContainedText,
-    pub text_scale_factor: f32,
-    pub icon_width_factor: f32,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct DiagnosticStyle {
-    pub message: LabelStyle,
-    #[serde(default)]
-    pub header: ContainerStyle,
-    pub text_scale_factor: f32,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct AutocompleteStyle {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub item: ContainerStyle,
-    pub selected_item: ContainerStyle,
-    pub hovered_item: ContainerStyle,
-    pub match_highlight: HighlightStyle,
-    pub completion_min_width: f32,
-    pub completion_max_width: f32,
-    pub inline_docs_container: ContainerStyle,
-    pub inline_docs_color: Color,
-    pub inline_docs_size_percent: f32,
-    pub alongside_docs_max_width: f32,
-    pub alongside_docs_container: ContainerStyle,
-}
-
-#[derive(Clone, Copy, Default, Deserialize, JsonSchema)]
-pub struct SelectionStyle {
-    pub cursor: Color,
-    pub selection: Color,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct FieldEditor {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub text: TextStyle,
-    #[serde(default)]
-    pub placeholder_text: Option<TextStyle>,
-    pub selection: SelectionStyle,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct InteractiveColor {
-    pub color: Color,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct CodeActions {
-    #[serde(default)]
-    pub indicator: Toggleable<Interactive<InteractiveColor>>,
-    pub vertical_scale: f32,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct Folds {
-    pub indicator: Toggleable<Interactive<InteractiveColor>>,
-    pub ellipses: FoldEllipses,
-    pub fold_background: Color,
-    pub icon_margin_scale: f32,
-    pub folded_icon: String,
-    pub foldable_icon: String,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct FoldEllipses {
-    pub text_color: Color,
-    pub background: Interactive<InteractiveColor>,
-    pub corner_radius_factor: f32,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct DiffStyle {
-    pub inserted: Color,
-    pub modified: Color,
-    pub deleted: Color,
-    pub removed_width_em: f32,
-    pub width_em: f32,
-    pub corner_radius: f32,
-}
-
-#[derive(Debug, Default, Clone, Copy, JsonSchema)]
-pub struct Interactive<T> {
-    pub default: T,
-    pub hovered: Option<T>,
-    pub clicked: Option<T>,
-    pub disabled: Option<T>,
-}
-
-impl<T> Deref for Interactive<T> {
-    type Target = T;
-
-    fn deref(&self) -> &Self::Target {
-        &self.default
+    match themes_to_load {
+        LoadThemes::JustBase => (),
+        LoadThemes::All => cx.global_mut::<ThemeRegistry>().load_user_themes(),
     }
-}
 
-impl Interactive<()> {
-    pub fn new_blank() -> Self {
-        Self {
-            default: (),
-            hovered: None,
-            clicked: None,
-            disabled: None,
-        }
-    }
+    ThemeSettings::register(cx);
 }
 
-#[derive(Clone, Copy, Debug, Default, Deserialize, JsonSchema)]
-pub struct Toggleable<T> {
-    active: T,
-    inactive: T,
+pub trait ActiveTheme {
+    fn theme(&self) -> &Arc<Theme>;
 }
 
-impl<T> Deref for Toggleable<T> {
-    type Target = T;
-
-    fn deref(&self) -> &Self::Target {
-        &self.inactive
+impl ActiveTheme for AppContext {
+    fn theme(&self) -> &Arc<Theme> {
+        &ThemeSettings::get_global(self).active_theme
     }
 }
 
-impl Toggleable<()> {
-    pub fn new_blank() -> Self {
-        Self {
-            active: (),
-            inactive: (),
-        }
-    }
+// todo!()
+// impl<'a> ActiveTheme for WindowContext<'a> {
+//     fn theme(&self) -> &Arc<Theme> {
+//         &ThemeSettings::get_global(self.app()).active_theme
+//     }
+// }
+
+pub struct ThemeFamily {
+    pub id: String,
+    pub name: SharedString,
+    pub author: SharedString,
+    pub themes: Vec<Theme>,
+    pub scales: ColorScales,
 }
 
-impl<T> Toggleable<T> {
-    pub fn new(active: T, inactive: T) -> Self {
-        Self { active, inactive }
-    }
-    pub fn in_state(&self, active: bool) -> &T {
-        if active {
-            &self.active
-        } else {
-            &self.inactive
-        }
-    }
-    pub fn active_state(&self) -> &T {
-        self.in_state(true)
-    }
+impl ThemeFamily {}
 
-    pub fn inactive_state(&self) -> &T {
-        self.in_state(false)
-    }
+pub struct Theme {
+    pub id: String,
+    pub name: SharedString,
+    pub appearance: Appearance,
+    pub styles: ThemeStyles,
 }
 
-impl<T> Interactive<T> {
-    pub fn style_for(&self, state: &mut MouseState) -> &T {
-        if state.clicked() == Some(platform::MouseButton::Left) && self.clicked.is_some() {
-            self.clicked.as_ref().unwrap()
-        } else if state.hovered() {
-            self.hovered.as_ref().unwrap_or(&self.default)
-        } else {
-            &self.default
-        }
-    }
-    pub fn disabled_style(&self) -> &T {
-        self.disabled.as_ref().unwrap_or(&self.default)
+impl Theme {
+    /// Returns the [`SystemColors`] for the theme.
+    #[inline(always)]
+    pub fn system(&self) -> &SystemColors {
+        &self.styles.system
     }
-}
 
-impl<T> Toggleable<Interactive<T>> {
-    pub fn style_for(&self, active: bool, state: &mut MouseState) -> &T {
-        self.in_state(active).style_for(state)
+    /// Returns the [`PlayerColors`] for the theme.
+    #[inline(always)]
+    pub fn players(&self) -> &PlayerColors {
+        &self.styles.player
     }
 
-    pub fn default_style(&self) -> &T {
-        &self.inactive.default
+    /// Returns the [`ThemeColors`] for the theme.
+    #[inline(always)]
+    pub fn colors(&self) -> &ThemeColors {
+        &self.styles.colors
     }
-}
-
-impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive<T> {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        #[derive(Deserialize)]
-        struct Helper {
-            default: Value,
-            hovered: Option<Value>,
-            clicked: Option<Value>,
-            disabled: Option<Value>,
-        }
-
-        let json = Helper::deserialize(deserializer)?;
-
-        let deserialize_state = |state_json: Option<Value>| -> Result<Option<T>, D::Error> {
-            if let Some(mut state_json) = state_json {
-                if let Value::Object(state_json) = &mut state_json {
-                    if let Value::Object(default) = &json.default {
-                        for (key, value) in default {
-                            if !state_json.contains_key(key) {
-                                state_json.insert(key.clone(), value.clone());
-                            }
-                        }
-                    }
-                }
-                Ok(Some(
-                    serde_json::from_value::<T>(state_json).map_err(serde::de::Error::custom)?,
-                ))
-            } else {
-                Ok(None)
-            }
-        };
-
-        let hovered = deserialize_state(json.hovered)?;
-        let clicked = deserialize_state(json.clicked)?;
-        let disabled = deserialize_state(json.disabled)?;
-        let default = serde_json::from_value(json.default).map_err(serde::de::Error::custom)?;
 
-        Ok(Interactive {
-            default,
-            hovered,
-            clicked,
-            disabled,
-        })
+    /// Returns the [`SyntaxTheme`] for the theme.
+    #[inline(always)]
+    pub fn syntax(&self) -> &Arc<SyntaxTheme> {
+        &self.styles.syntax
     }
-}
 
-impl Editor {
-    pub fn selection_style_for_room_participant(&self, participant_index: u32) -> SelectionStyle {
-        if self.guest_selections.is_empty() {
-            return SelectionStyle::default();
-        }
-        let style_ix = participant_index as usize % self.guest_selections.len();
-        self.guest_selections[style_ix]
+    /// Returns the [`StatusColors`] for the theme.
+    #[inline(always)]
+    pub fn status(&self) -> &StatusColors {
+        &self.styles.status
     }
-}
 
-#[derive(Default, JsonSchema)]
-pub struct SyntaxTheme {
-    pub highlights: Vec<(String, HighlightStyle)>,
-}
-
-impl SyntaxTheme {
-    pub fn new(highlights: Vec<(String, HighlightStyle)>) -> Self {
-        Self { highlights }
+    /// Returns the color for the syntax node with the given name.
+    #[inline(always)]
+    pub fn syntax_color(&self, name: &str) -> Hsla {
+        self.syntax().color(name)
     }
-}
-
-impl<'de> Deserialize<'de> for SyntaxTheme {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        let syntax_data: HashMap<String, HighlightStyle> = Deserialize::deserialize(deserializer)?;
 
-        let mut result = Self::new(Vec::new());
-        for (key, style) in syntax_data {
-            match result
-                .highlights
-                .binary_search_by(|(needle, _)| needle.cmp(&key))
-            {
-                Ok(i) | Err(i) => {
-                    result.highlights.insert(i, (key, style));
-                }
-            }
-        }
-
-        Ok(result)
+    /// Returns the [`Appearance`] for the theme.
+    #[inline(always)]
+    pub fn appearance(&self) -> Appearance {
+        self.appearance
     }
 }
 
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct HoverPopover {
-    pub container: ContainerStyle,
-    pub info_container: ContainerStyle,
-    pub warning_container: ContainerStyle,
-    pub error_container: ContainerStyle,
-    pub block_style: ContainerStyle,
-    pub prose: TextStyle,
-    pub diagnostic_source_highlight: HighlightStyle,
-    pub highlight: Color,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct TerminalStyle {
-    pub black: Color,
-    pub red: Color,
-    pub green: Color,
-    pub yellow: Color,
-    pub blue: Color,
-    pub magenta: Color,
-    pub cyan: Color,
-    pub white: Color,
-    pub bright_black: Color,
-    pub bright_red: Color,
-    pub bright_green: Color,
-    pub bright_yellow: Color,
-    pub bright_blue: Color,
-    pub bright_magenta: Color,
-    pub bright_cyan: Color,
-    pub bright_white: Color,
-    pub foreground: Color,
-    pub background: Color,
-    pub modal_background: Color,
-    pub cursor: Color,
-    pub dim_black: Color,
-    pub dim_red: Color,
-    pub dim_green: Color,
-    pub dim_yellow: Color,
-    pub dim_blue: Color,
-    pub dim_magenta: Color,
-    pub dim_cyan: Color,
-    pub dim_white: Color,
-    pub bright_foreground: Color,
-    pub dim_foreground: Color,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct AssistantStyle {
-    pub container: ContainerStyle,
-    pub hamburger_button: Interactive<IconStyle>,
-    pub split_button: Interactive<IconStyle>,
-    pub assist_button: Interactive<IconStyle>,
-    pub quote_button: Interactive<IconStyle>,
-    pub zoom_in_button: Interactive<IconStyle>,
-    pub zoom_out_button: Interactive<IconStyle>,
-    pub plus_button: Interactive<IconStyle>,
-    pub title: ContainedText,
-    pub message_header: ContainerStyle,
-    pub sent_at: ContainedText,
-    pub user_sender: Interactive<ContainedText>,
-    pub assistant_sender: Interactive<ContainedText>,
-    pub system_sender: Interactive<ContainedText>,
-    pub model: Interactive<ContainedText>,
-    pub remaining_tokens: ContainedText,
-    pub low_remaining_tokens: ContainedText,
-    pub no_remaining_tokens: ContainedText,
-    pub error_icon: Icon,
-    pub api_key_editor: FieldEditor,
-    pub api_key_prompt: ContainedText,
-    pub saved_conversation: SavedConversation,
-    pub inline: InlineAssistantStyle,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct InlineAssistantStyle {
-    #[serde(flatten)]
-    pub container: ContainerStyle,
-    pub editor: FieldEditor,
-    pub disabled_editor: FieldEditor,
-    pub pending_edit_background: Color,
-    pub include_conversation: ToggleIconButtonStyle,
-    pub retrieve_context: ToggleIconButtonStyle,
-    pub context_status: ContextStatusStyle,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct ContextStatusStyle {
-    pub error_icon: Icon,
-    pub in_progress_icon: Icon,
-    pub complete_icon: Icon,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct Contained<T> {
-    container: ContainerStyle,
-    contained: T,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct SavedConversation {
-    pub container: Interactive<ContainerStyle>,
-    pub saved_at: ContainedText,
-    pub title: ContainedText,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct FeedbackStyle {
-    pub submit_button: Interactive<ContainedText>,
-    pub button_margin: f32,
-    pub info_text_default: ContainedText,
-    pub link_text_default: ContainedText,
-    pub link_text_hover: ContainedText,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct WelcomeStyle {
-    pub page_width: f32,
-    pub logo: SvgStyle,
-    pub logo_subheading: ContainedText,
-    pub usage_note: ContainedText,
-    pub checkbox: CheckboxStyle,
-    pub checkbox_container: ContainerStyle,
-    pub button: Interactive<ContainedText>,
-    pub button_group: ContainerStyle,
-    pub heading_group: ContainerStyle,
-    pub checkbox_group: ContainerStyle,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct ColorScheme {
-    pub name: String,
-    pub is_light: bool,
-    pub lowest: Layer,
-    pub middle: Layer,
-    pub highest: Layer,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct Player {
-    pub cursor: Color,
-    pub selection: Color,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct RampSet {
-    pub neutral: Vec<Color>,
-    pub red: Vec<Color>,
-    pub orange: Vec<Color>,
-    pub yellow: Vec<Color>,
-    pub green: Vec<Color>,
-    pub cyan: Vec<Color>,
-    pub blue: Vec<Color>,
-    pub violet: Vec<Color>,
-    pub magenta: Vec<Color>,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct Layer {
-    pub base: StyleSet,
-    pub variant: StyleSet,
-    pub on: StyleSet,
-    pub accent: StyleSet,
-    pub positive: StyleSet,
-    pub warning: StyleSet,
-    pub negative: StyleSet,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct StyleSet {
-    pub default: Style,
-    pub active: Style,
-    pub disabled: Style,
-    pub hovered: Style,
-    pub pressed: Style,
-    pub inverted: Style,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct Style {
-    pub background: Color,
-    pub border: Color,
-    pub foreground: Color,
+pub fn color_alpha(color: Hsla, alpha: f32) -> Hsla {
+    let mut color = color;
+    color.a = alpha;
+    color
 }

crates/theme/src/theme_registry.rs 🔗

@@ -1,106 +0,0 @@
-use crate::{Theme, ThemeMeta};
-use anyhow::{Context, Result};
-use gpui::{fonts, AssetSource, FontCache};
-use parking_lot::Mutex;
-use serde::Deserialize;
-use serde_json::Value;
-use std::{
-    borrow::Cow,
-    collections::HashMap,
-    sync::{
-        atomic::{AtomicUsize, Ordering::SeqCst},
-        Arc,
-    },
-};
-
-pub struct ThemeRegistry {
-    assets: Box<dyn AssetSource>,
-    themes: Mutex<HashMap<String, Arc<Theme>>>,
-    theme_data: Mutex<HashMap<String, Arc<Value>>>,
-    font_cache: Arc<FontCache>,
-    next_theme_id: AtomicUsize,
-}
-
-impl ThemeRegistry {
-    pub fn new(source: impl AssetSource, font_cache: Arc<FontCache>) -> Arc<Self> {
-        let this = Arc::new(Self {
-            assets: Box::new(source),
-            themes: Default::default(),
-            theme_data: Default::default(),
-            next_theme_id: Default::default(),
-            font_cache,
-        });
-
-        this.themes.lock().insert(
-            settings::EMPTY_THEME_NAME.to_string(),
-            gpui::fonts::with_font_cache(this.font_cache.clone(), || {
-                let mut theme = Theme::default();
-                theme.meta.id = this.next_theme_id.fetch_add(1, SeqCst);
-                theme.meta.name = settings::EMPTY_THEME_NAME.into();
-                Arc::new(theme)
-            }),
-        );
-
-        this
-    }
-
-    pub fn list_names(&self, staff: bool) -> impl Iterator<Item = Cow<str>> + '_ {
-        let mut dirs = self.assets.list("themes/");
-
-        if !staff {
-            dirs = dirs
-                .into_iter()
-                .filter(|path| !path.starts_with("themes/staff"))
-                .collect()
-        }
-
-        fn get_name(path: &str) -> Option<&str> {
-            path.strip_prefix("themes/")?.strip_suffix(".json")
-        }
-
-        dirs.into_iter().filter_map(|path| match path {
-            Cow::Borrowed(path) => Some(Cow::Borrowed(get_name(path)?)),
-            Cow::Owned(path) => Some(Cow::Owned(get_name(&path)?.to_string())),
-        })
-    }
-
-    pub fn list(&self, staff: bool) -> impl Iterator<Item = ThemeMeta> + '_ {
-        self.list_names(staff).filter_map(|theme_name| {
-            self.get(theme_name.as_ref())
-                .ok()
-                .map(|theme| theme.meta.clone())
-        })
-    }
-
-    pub fn clear(&self) {
-        self.theme_data.lock().clear();
-        self.themes.lock().clear();
-    }
-
-    pub fn get(&self, name: &str) -> Result<Arc<Theme>> {
-        if let Some(theme) = self.themes.lock().get(name) {
-            return Ok(theme.clone());
-        }
-
-        let asset_path = format!("themes/{}.json", name);
-        let theme_json = self
-            .assets
-            .load(&asset_path)
-            .with_context(|| format!("failed to load theme file {}", asset_path))?;
-
-        // Allocate into the heap directly, the Theme struct is too large to fit in the stack.
-        let mut theme = fonts::with_font_cache(self.font_cache.clone(), || {
-            let mut theme = Box::new(Theme::default());
-            let mut deserializer = serde_json::Deserializer::from_slice(&theme_json);
-            let result = Theme::deserialize_in_place(&mut deserializer, &mut theme);
-            result.map(|_| theme)
-        })?;
-
-        // Reset name to be the file path, so that we can use it to access the stored themes
-        theme.meta.name = name.into();
-        theme.meta.id = self.next_theme_id.fetch_add(1, SeqCst);
-        let theme: Arc<Theme> = theme.into();
-        self.themes.lock().insert(name.to_string(), theme.clone());
-        Ok(theme)
-    }
-}

crates/theme/src/theme_settings.rs 🔗

@@ -1,214 +0,0 @@
-use crate::{Theme, ThemeRegistry};
-use anyhow::Result;
-use gpui::{font_cache::FamilyId, fonts, AppContext};
-use schemars::{
-    gen::SchemaGenerator,
-    schema::{InstanceType, Schema, SchemaObject},
-    JsonSchema,
-};
-use serde::{Deserialize, Serialize};
-use serde_json::Value;
-use settings::SettingsJsonSchemaParams;
-use std::sync::Arc;
-use util::ResultExt as _;
-
-const MIN_FONT_SIZE: f32 = 6.0;
-const MIN_LINE_HEIGHT: f32 = 1.0;
-
-#[derive(Clone, JsonSchema)]
-pub struct ThemeSettings {
-    pub buffer_font_family_name: String,
-    pub buffer_font_features: fonts::Features,
-    pub buffer_font_family: FamilyId,
-    pub(crate) buffer_font_size: f32,
-    pub(crate) buffer_line_height: BufferLineHeight,
-    #[serde(skip)]
-    pub theme: Arc<Theme>,
-}
-
-pub struct AdjustedBufferFontSize(pub f32);
-
-#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
-pub struct ThemeSettingsContent {
-    #[serde(default)]
-    pub buffer_font_family: Option<String>,
-    #[serde(default)]
-    pub buffer_font_size: Option<f32>,
-    #[serde(default)]
-    pub buffer_line_height: Option<BufferLineHeight>,
-    #[serde(default)]
-    pub buffer_font_features: Option<fonts::Features>,
-    #[serde(default)]
-    pub theme: Option<String>,
-}
-
-#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
-#[serde(rename_all = "snake_case")]
-pub enum BufferLineHeight {
-    #[default]
-    Comfortable,
-    Standard,
-    Custom(f32),
-}
-
-impl BufferLineHeight {
-    pub fn value(&self) -> f32 {
-        match self {
-            BufferLineHeight::Comfortable => 1.618,
-            BufferLineHeight::Standard => 1.3,
-            BufferLineHeight::Custom(line_height) => *line_height,
-        }
-    }
-}
-
-impl ThemeSettings {
-    pub fn buffer_font_size(&self, cx: &AppContext) -> f32 {
-        if cx.has_global::<AdjustedBufferFontSize>() {
-            cx.global::<AdjustedBufferFontSize>().0
-        } else {
-            self.buffer_font_size
-        }
-        .max(MIN_FONT_SIZE)
-    }
-
-    pub fn line_height(&self) -> f32 {
-        f32::max(self.buffer_line_height.value(), MIN_LINE_HEIGHT)
-    }
-}
-
-pub fn adjusted_font_size(size: f32, cx: &AppContext) -> f32 {
-    if cx.has_global::<AdjustedBufferFontSize>() {
-        let buffer_font_size = settings::get::<ThemeSettings>(cx).buffer_font_size;
-        let delta = cx.global::<AdjustedBufferFontSize>().0 - buffer_font_size;
-        size + delta
-    } else {
-        size
-    }
-    .max(MIN_FONT_SIZE)
-}
-
-pub fn adjust_font_size(cx: &mut AppContext, f: fn(&mut f32)) {
-    if !cx.has_global::<AdjustedBufferFontSize>() {
-        let buffer_font_size = settings::get::<ThemeSettings>(cx).buffer_font_size;
-        cx.set_global(AdjustedBufferFontSize(buffer_font_size));
-    }
-
-    cx.update_global::<AdjustedBufferFontSize, _, _>(|delta, cx| {
-        f(&mut delta.0);
-        delta.0 = delta
-            .0
-            .max(MIN_FONT_SIZE - settings::get::<ThemeSettings>(cx).buffer_font_size);
-    });
-    cx.refresh_windows();
-}
-
-pub fn reset_font_size(cx: &mut AppContext) {
-    if cx.has_global::<AdjustedBufferFontSize>() {
-        cx.remove_global::<AdjustedBufferFontSize>();
-        cx.refresh_windows();
-    }
-}
-
-impl settings::Setting for ThemeSettings {
-    const KEY: Option<&'static str> = None;
-
-    type FileContent = ThemeSettingsContent;
-
-    fn load(
-        defaults: &Self::FileContent,
-        user_values: &[&Self::FileContent],
-        cx: &AppContext,
-    ) -> Result<Self> {
-        let buffer_font_features = defaults.buffer_font_features.clone().unwrap();
-        let themes = cx.global::<Arc<ThemeRegistry>>();
-
-        let mut this = Self {
-            buffer_font_family: cx
-                .font_cache()
-                .load_family(
-                    &[defaults.buffer_font_family.as_ref().unwrap()],
-                    &buffer_font_features,
-                )
-                .unwrap(),
-            buffer_font_family_name: defaults.buffer_font_family.clone().unwrap(),
-            buffer_font_features,
-            buffer_font_size: defaults.buffer_font_size.unwrap(),
-            buffer_line_height: defaults.buffer_line_height.unwrap(),
-            theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(),
-        };
-
-        for value in user_values.into_iter().copied().cloned() {
-            let font_cache = cx.font_cache();
-            let mut family_changed = false;
-            if let Some(value) = value.buffer_font_family {
-                this.buffer_font_family_name = value;
-                family_changed = true;
-            }
-            if let Some(value) = value.buffer_font_features {
-                this.buffer_font_features = value;
-                family_changed = true;
-            }
-            if family_changed {
-                if let Some(id) = font_cache
-                    .load_family(&[&this.buffer_font_family_name], &this.buffer_font_features)
-                    .log_err()
-                {
-                    this.buffer_font_family = id;
-                }
-            }
-
-            if let Some(value) = &value.theme {
-                if let Some(theme) = themes.get(value).log_err() {
-                    this.theme = theme;
-                }
-            }
-
-            merge(&mut this.buffer_font_size, value.buffer_font_size);
-            merge(&mut this.buffer_line_height, value.buffer_line_height);
-        }
-
-        Ok(this)
-    }
-
-    fn json_schema(
-        generator: &mut SchemaGenerator,
-        params: &SettingsJsonSchemaParams,
-        cx: &AppContext,
-    ) -> schemars::schema::RootSchema {
-        let mut root_schema = generator.root_schema_for::<ThemeSettingsContent>();
-        let theme_names = cx
-            .global::<Arc<ThemeRegistry>>()
-            .list_names(params.staff_mode)
-            .map(|theme_name| Value::String(theme_name.to_string()))
-            .collect();
-
-        let theme_name_schema = SchemaObject {
-            instance_type: Some(InstanceType::String.into()),
-            enum_values: Some(theme_names),
-            ..Default::default()
-        };
-
-        root_schema
-            .definitions
-            .extend([("ThemeName".into(), theme_name_schema.into())]);
-
-        root_schema
-            .schema
-            .object
-            .as_mut()
-            .unwrap()
-            .properties
-            .extend([(
-                "theme".to_owned(),
-                Schema::new_ref("#/definitions/ThemeName".into()),
-            )]);
-
-        root_schema
-    }
-}
-
-fn merge<T: Copy>(target: &mut T, value: Option<T>) {
-    if let Some(value) = value {
-        *target = value;
-    }
-}

crates/theme/src/ui.rs 🔗

@@ -1,244 +0,0 @@
-use std::borrow::Cow;
-
-use gpui::{
-    elements::{
-        ConstrainedBox, Container, ContainerStyle, Dimensions, Empty, Flex, KeystrokeLabel, Label,
-        MouseEventHandler, ParentElement, Stack, Svg, SvgStyle,
-    },
-    fonts::TextStyle,
-    geometry::vector::Vector2F,
-    platform,
-    platform::MouseButton,
-    scene::MouseClick,
-    Action, Element, EventContext, MouseState, ViewContext,
-};
-use schemars::JsonSchema;
-use serde::Deserialize;
-
-use crate::{ContainedText, Interactive};
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct CheckboxStyle {
-    pub icon: SvgStyle,
-    pub label: ContainedText,
-    pub default: ContainerStyle,
-    pub checked: ContainerStyle,
-    pub hovered: ContainerStyle,
-    pub hovered_and_checked: ContainerStyle,
-}
-
-pub fn checkbox<Tag, V, F>(
-    label: &'static str,
-    style: &CheckboxStyle,
-    checked: bool,
-    id: usize,
-    cx: &mut ViewContext<V>,
-    change: F,
-) -> MouseEventHandler<V>
-where
-    Tag: 'static,
-    V: 'static,
-    F: 'static + Fn(&mut V, bool, &mut EventContext<V>),
-{
-    let label = Label::new(label, style.label.text.clone())
-        .contained()
-        .with_style(style.label.container);
-    checkbox_with_label::<Tag, _, _, _>(label, style, checked, id, cx, change)
-}
-
-pub fn checkbox_with_label<Tag, D, V, F>(
-    label: D,
-    style: &CheckboxStyle,
-    checked: bool,
-    id: usize,
-    cx: &mut ViewContext<V>,
-    change: F,
-) -> MouseEventHandler<V>
-where
-    Tag: 'static,
-    D: Element<V>,
-    V: 'static,
-    F: 'static + Fn(&mut V, bool, &mut EventContext<V>),
-{
-    MouseEventHandler::new::<Tag, _>(id, cx, |state, _| {
-        let indicator = if checked {
-            svg(&style.icon)
-        } else {
-            Empty::new()
-                .constrained()
-                .with_width(style.icon.dimensions.width)
-                .with_height(style.icon.dimensions.height)
-        };
-
-        Flex::row()
-            .with_child(indicator.contained().with_style(if checked {
-                if state.hovered() {
-                    style.hovered_and_checked
-                } else {
-                    style.checked
-                }
-            } else {
-                if state.hovered() {
-                    style.hovered
-                } else {
-                    style.default
-                }
-            }))
-            .with_child(label)
-            .align_children_center()
-    })
-    .on_click(platform::MouseButton::Left, move |_, view, cx| {
-        change(view, !checked, cx)
-    })
-    .with_cursor_style(platform::CursorStyle::PointingHand)
-}
-
-pub fn svg<V: 'static>(style: &SvgStyle) -> ConstrainedBox<V> {
-    Svg::new(style.asset.clone())
-        .with_color(style.color)
-        .constrained()
-        .with_width(style.dimensions.width)
-        .with_height(style.dimensions.height)
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct IconStyle {
-    pub icon: SvgStyle,
-    pub container: ContainerStyle,
-}
-
-impl IconStyle {
-    pub fn width(&self) -> f32 {
-        self.icon.dimensions.width
-            + self.container.padding.left
-            + self.container.padding.right
-            + self.container.margin.left
-            + self.container.margin.right
-    }
-}
-
-pub fn icon<V: 'static>(style: &IconStyle) -> Container<V> {
-    svg(&style.icon).contained().with_style(style.container)
-}
-
-pub fn keystroke_label<V: 'static>(
-    label_text: &'static str,
-    label_style: &ContainedText,
-    keystroke_style: &ContainedText,
-    action: Box<dyn Action>,
-    cx: &mut ViewContext<V>,
-) -> Container<V> {
-    // FIXME: Put the theme in it's own global so we can
-    // query the keystroke style on our own
-    Flex::row()
-        .with_child(Label::new(label_text, label_style.text.clone()).contained())
-        .with_child(
-            KeystrokeLabel::new(
-                cx.view_id(),
-                action,
-                keystroke_style.container,
-                keystroke_style.text.clone(),
-            )
-            .flex_float(),
-        )
-        .contained()
-        .with_style(label_style.container)
-}
-
-pub type CopilotCTAButton = Interactive<ContainedText>;
-
-pub fn cta_button<Tag, L, V, F>(
-    label: L,
-    max_width: f32,
-    style: &CopilotCTAButton,
-    cx: &mut ViewContext<V>,
-    f: F,
-) -> MouseEventHandler<V>
-where
-    Tag: 'static,
-    L: Into<Cow<'static, str>>,
-    V: 'static,
-    F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
-{
-    MouseEventHandler::new::<Tag, _>(0, cx, |state, _| {
-        let style = style.style_for(state);
-        Label::new(label, style.text.to_owned())
-            .aligned()
-            .contained()
-            .with_style(style.container)
-            .constrained()
-            .with_max_width(max_width)
-    })
-    .on_click(MouseButton::Left, f)
-    .with_cursor_style(platform::CursorStyle::PointingHand)
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct ModalStyle {
-    close_icon: Interactive<IconStyle>,
-    container: ContainerStyle,
-    titlebar: ContainerStyle,
-    title_text: Interactive<TextStyle>,
-    dimensions: Dimensions,
-}
-
-impl ModalStyle {
-    pub fn dimensions(&self) -> Vector2F {
-        self.dimensions.to_vec()
-    }
-}
-
-pub fn modal<Tag, V, I, D, F>(
-    title: I,
-    style: &ModalStyle,
-    cx: &mut ViewContext<V>,
-    build_modal: F,
-) -> impl Element<V>
-where
-    Tag: 'static,
-    I: Into<Cow<'static, str>>,
-    D: Element<V>,
-    V: 'static,
-    F: FnOnce(&mut gpui::ViewContext<V>) -> D,
-{
-    const TITLEBAR_HEIGHT: f32 = 28.;
-
-    Flex::column()
-        .with_child(
-            Stack::new()
-                .with_child(Label::new(
-                    title,
-                    style
-                        .title_text
-                        .style_for(&mut MouseState::default())
-                        .clone(),
-                ))
-                .with_child(
-                    // FIXME: Get a better tag type
-                    MouseEventHandler::new::<Tag, _>(999999, cx, |state, _cx| {
-                        let style = style.close_icon.style_for(state);
-                        icon(style)
-                    })
-                    .on_click(platform::MouseButton::Left, move |_, _, cx| {
-                        cx.remove_window();
-                    })
-                    .with_cursor_style(platform::CursorStyle::PointingHand)
-                    .aligned()
-                    .right(),
-                )
-                .contained()
-                .with_style(style.titlebar)
-                .constrained()
-                .with_height(TITLEBAR_HEIGHT),
-        )
-        .with_child(
-            build_modal(cx)
-                .contained()
-                .with_style(style.container)
-                .constrained()
-                .with_width(style.dimensions().x())
-                .with_height(style.dimensions().y() - TITLEBAR_HEIGHT),
-        )
-        .constrained()
-        .with_height(style.dimensions().y())
-}

crates/theme2/Cargo.toml 🔗

@@ -1,42 +0,0 @@
-[package]
-name = "theme2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[features]
-default = []
-importing-themes = []
-stories = ["dep:itertools", "dep:story"]
-test-support = [
-    "gpui/test-support",
-    "fs/test-support",
-    "settings/test-support"
-]
-
-[lib]
-path = "src/theme2.rs"
-doctest = false
-
-[dependencies]
-anyhow.workspace = true
-fs = { package = "fs2", path = "../fs2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-indexmap = "1.6.2"
-parking_lot.workspace = true
-refineable.workspace = true
-schemars.workspace = true
-serde.workspace = true
-serde_derive.workspace = true
-serde_json.workspace = true
-settings = { package = "settings2", path = "../settings2" }
-story = { path = "../story", optional = true }
-toml.workspace = true
-uuid.workspace = true
-util = { path = "../util" }
-itertools = { version = "0.11.0", optional = true }
-
-[dev-dependencies]
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-fs = { package = "fs2", path = "../fs2", features = ["test-support"] }
-settings = { package = "settings2", path = "../settings2", features = ["test-support"] }

crates/theme2/src/theme2.rs 🔗

@@ -1,148 +0,0 @@
-mod default_colors;
-mod default_theme;
-mod one_themes;
-pub mod prelude;
-mod registry;
-mod scale;
-mod settings;
-mod styles;
-#[cfg(not(feature = "importing-themes"))]
-mod themes;
-mod user_theme;
-
-use std::sync::Arc;
-
-use ::settings::Settings;
-pub use default_colors::*;
-pub use default_theme::*;
-pub use registry::*;
-pub use scale::*;
-pub use settings::*;
-pub use styles::*;
-#[cfg(not(feature = "importing-themes"))]
-pub use themes::*;
-pub use user_theme::*;
-
-use gpui::{AppContext, Hsla, SharedString};
-use serde::Deserialize;
-
-#[derive(Debug, PartialEq, Clone, Copy, Deserialize)]
-pub enum Appearance {
-    Light,
-    Dark,
-}
-
-impl Appearance {
-    pub fn is_light(&self) -> bool {
-        match self {
-            Self::Light => true,
-            Self::Dark => false,
-        }
-    }
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum LoadThemes {
-    /// Only load the base theme.
-    ///
-    /// No user themes will be loaded.
-    JustBase,
-
-    /// Load all of the built-in themes.
-    All,
-}
-
-pub fn init(themes_to_load: LoadThemes, cx: &mut AppContext) {
-    cx.set_global(ThemeRegistry::default());
-
-    match themes_to_load {
-        LoadThemes::JustBase => (),
-        LoadThemes::All => cx.global_mut::<ThemeRegistry>().load_user_themes(),
-    }
-
-    ThemeSettings::register(cx);
-}
-
-pub trait ActiveTheme {
-    fn theme(&self) -> &Arc<Theme>;
-}
-
-impl ActiveTheme for AppContext {
-    fn theme(&self) -> &Arc<Theme> {
-        &ThemeSettings::get_global(self).active_theme
-    }
-}
-
-// todo!()
-// impl<'a> ActiveTheme for WindowContext<'a> {
-//     fn theme(&self) -> &Arc<Theme> {
-//         &ThemeSettings::get_global(self.app()).active_theme
-//     }
-// }
-
-pub struct ThemeFamily {
-    pub id: String,
-    pub name: SharedString,
-    pub author: SharedString,
-    pub themes: Vec<Theme>,
-    pub scales: ColorScales,
-}
-
-impl ThemeFamily {}
-
-pub struct Theme {
-    pub id: String,
-    pub name: SharedString,
-    pub appearance: Appearance,
-    pub styles: ThemeStyles,
-}
-
-impl Theme {
-    /// Returns the [`SystemColors`] for the theme.
-    #[inline(always)]
-    pub fn system(&self) -> &SystemColors {
-        &self.styles.system
-    }
-
-    /// Returns the [`PlayerColors`] for the theme.
-    #[inline(always)]
-    pub fn players(&self) -> &PlayerColors {
-        &self.styles.player
-    }
-
-    /// Returns the [`ThemeColors`] for the theme.
-    #[inline(always)]
-    pub fn colors(&self) -> &ThemeColors {
-        &self.styles.colors
-    }
-
-    /// Returns the [`SyntaxTheme`] for the theme.
-    #[inline(always)]
-    pub fn syntax(&self) -> &Arc<SyntaxTheme> {
-        &self.styles.syntax
-    }
-
-    /// Returns the [`StatusColors`] for the theme.
-    #[inline(always)]
-    pub fn status(&self) -> &StatusColors {
-        &self.styles.status
-    }
-
-    /// Returns the color for the syntax node with the given name.
-    #[inline(always)]
-    pub fn syntax_color(&self, name: &str) -> Hsla {
-        self.syntax().color(name)
-    }
-
-    /// Returns the [`Appearance`] for the theme.
-    #[inline(always)]
-    pub fn appearance(&self) -> Appearance {
-        self.appearance
-    }
-}
-
-pub fn color_alpha(color: Hsla, alpha: f32) -> Hsla {
-    let mut color = color;
-    color.a = alpha;
-    color
-}

crates/theme_importer/Cargo.toml 🔗

@@ -9,7 +9,7 @@ any_ascii = "0.3.2"
 anyhow.workspace = true
 clap = { version = "4.4", features = ["derive"] }
 convert_case = "0.6.0"
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
 indexmap = { version = "1.6.2", features = ["serde"] }
 json_comments = "0.2.2"
 log.workspace = true
@@ -19,5 +19,5 @@ rust-embed.workspace = true
 serde.workspace = true
 simplelog = "0.9"
 strum = { version = "0.25.0", features = ["derive"] }
-theme = { package = "theme2", path = "../theme2", features = ["importing-themes"] }
+theme = { path = "../theme", features = ["importing-themes"] }
 uuid.workspace = true

crates/theme_importer/src/main.rs 🔗

@@ -78,7 +78,7 @@ struct Args {
 
 fn main() -> Result<()> {
     const SOURCE_PATH: &str = "assets/themes/src/vscode";
-    const OUT_PATH: &str = "crates/theme2/src/themes";
+    const OUT_PATH: &str = "crates/theme/src/themes";
 
     let args = Args::parse();
 
@@ -376,6 +376,6 @@ fn main() -> Result<()> {
 
 fn format_themes_crate() -> std::io::Result<std::process::Output> {
     Command::new("cargo")
-        .args(["fmt", "--package", "theme2"])
+        .args(["fmt", "--package", "theme"])
         .output()
 }

crates/theme_selector/Cargo.toml 🔗

@@ -11,15 +11,16 @@ doctest = false
 [dependencies]
 client = { path = "../client" }
 editor = { path = "../editor" }
-fuzzy = { path = "../fuzzy" }
+feature_flags = { path = "../feature_flags" }
 fs = { path = "../fs" }
+fuzzy = {  path = "../fuzzy" }
 gpui = { path = "../gpui" }
 picker = { path = "../picker" }
-theme = { path = "../theme" }
 settings = { path = "../settings" }
-feature_flags = { path = "../feature_flags" }
-workspace = { path = "../workspace" }
+theme = { path = "../theme" }
+ui = { path = "../ui" }
 util = { path = "../util" }
+workspace = { path = "../workspace" }
 log.workspace = true
 parking_lot.workspace = true
 postage.workspace = true

crates/theme_selector/src/theme_selector.rs 🔗

@@ -2,35 +2,48 @@ use client::{telemetry::Telemetry, TelemetrySettings};
 use feature_flags::FeatureFlagAppExt;
 use fs::Fs;
 use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
-use gpui::{actions, elements::*, AnyElement, AppContext, Element, MouseState, ViewContext};
-use picker::{Picker, PickerDelegate, PickerEvent};
-use settings::{update_settings_file, SettingsStore};
+use gpui::{
+    actions, AppContext, DismissEvent, EventEmitter, FocusableView, Render, View, ViewContext,
+    VisualContext, WeakView,
+};
+use picker::{Picker, PickerDelegate};
+use settings::{update_settings_file, Settings, SettingsStore};
 use std::sync::Arc;
 use theme::{Theme, ThemeMeta, ThemeRegistry, ThemeSettings};
+use ui::{prelude::*, v_stack, ListItem, ListItemSpacing};
 use util::ResultExt;
-use workspace::Workspace;
+use workspace::{ui::HighlightedLabel, ModalView, Workspace};
 
 actions!(theme_selector, [Toggle, Reload]);
 
 pub fn init(cx: &mut AppContext) {
-    cx.add_action(toggle);
-    ThemeSelector::init(cx);
+    cx.observe_new_views(
+        |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
+            workspace.register_action(toggle);
+        },
+    )
+    .detach();
 }
 
 pub fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
-    workspace.toggle_modal(cx, |workspace, cx| {
-        let fs = workspace.app_state().fs.clone();
-        let telemetry = workspace.client().telemetry().clone();
-        cx.add_view(|cx| ThemeSelector::new(ThemeSelectorDelegate::new(fs, telemetry, cx), cx))
+    let fs = workspace.app_state().fs.clone();
+    let telemetry = workspace.client().telemetry().clone();
+    workspace.toggle_modal(cx, |cx| {
+        ThemeSelector::new(
+            ThemeSelectorDelegate::new(cx.view().downgrade(), fs, telemetry, cx),
+            cx,
+        )
     });
 }
 
 #[cfg(debug_assertions)]
 pub fn reload(cx: &mut AppContext) {
-    let current_theme_name = theme::current(cx).meta.name.clone();
-    let registry = cx.global::<Arc<ThemeRegistry>>();
-    registry.clear();
-    match registry.get(&current_theme_name) {
+    let current_theme_name = cx.theme().name.clone();
+    let current_theme = cx.update_global(|registry: &mut ThemeRegistry, _cx| {
+        registry.clear();
+        registry.get(&current_theme_name)
+    });
+    match current_theme {
         Ok(theme) => {
             ThemeSelectorDelegate::set_theme(theme, cx);
             log::info!("reloaded theme {}", current_theme_name);
@@ -41,55 +54,88 @@ pub fn reload(cx: &mut AppContext) {
     }
 }
 
-pub type ThemeSelector = Picker<ThemeSelectorDelegate>;
+impl ModalView for ThemeSelector {}
+
+pub struct ThemeSelector {
+    picker: View<Picker<ThemeSelectorDelegate>>,
+}
+
+impl EventEmitter<DismissEvent> for ThemeSelector {}
+
+impl FocusableView for ThemeSelector {
+    fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
+        self.picker.focus_handle(cx)
+    }
+}
+
+impl Render for ThemeSelector {
+    fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
+        v_stack().w(rems(34.)).child(self.picker.clone())
+    }
+}
+
+impl ThemeSelector {
+    pub fn new(delegate: ThemeSelectorDelegate, cx: &mut ViewContext<Self>) -> Self {
+        let picker = cx.new_view(|cx| Picker::new(delegate, cx));
+        Self { picker }
+    }
+}
 
 pub struct ThemeSelectorDelegate {
     fs: Arc<dyn Fs>,
-    theme_data: Vec<ThemeMeta>,
+    themes: Vec<ThemeMeta>,
     matches: Vec<StringMatch>,
     original_theme: Arc<Theme>,
     selection_completed: bool,
     selected_index: usize,
     telemetry: Arc<Telemetry>,
+    view: WeakView<ThemeSelector>,
 }
 
 impl ThemeSelectorDelegate {
     fn new(
+        weak_view: WeakView<ThemeSelector>,
         fs: Arc<dyn Fs>,
         telemetry: Arc<Telemetry>,
         cx: &mut ViewContext<ThemeSelector>,
     ) -> Self {
-        let original_theme = theme::current(cx).clone();
+        let original_theme = cx.theme().clone();
 
         let staff_mode = cx.is_staff();
-        let registry = cx.global::<Arc<ThemeRegistry>>();
-        let mut theme_names = registry.list(staff_mode).collect::<Vec<_>>();
-        theme_names.sort_unstable_by(|a, b| a.is_light.cmp(&b.is_light).then(a.name.cmp(&b.name)));
-        let matches = theme_names
+        let registry = cx.global::<ThemeRegistry>();
+        let mut themes = registry.list(staff_mode).collect::<Vec<_>>();
+        themes.sort_unstable_by(|a, b| {
+            a.appearance
+                .is_light()
+                .cmp(&b.appearance.is_light())
+                .then(a.name.cmp(&b.name))
+        });
+        let matches = themes
             .iter()
             .map(|meta| StringMatch {
                 candidate_id: 0,
                 score: 0.0,
                 positions: Default::default(),
-                string: meta.name.clone(),
+                string: meta.name.to_string(),
             })
             .collect();
         let mut this = Self {
             fs,
-            theme_data: theme_names,
+            themes,
             matches,
             original_theme: original_theme.clone(),
             selected_index: 0,
             selection_completed: false,
             telemetry,
+            view: weak_view,
         };
-        this.select_if_matching(&original_theme.meta.name);
+        this.select_if_matching(&original_theme.name);
         this
     }
 
-    fn show_selected_theme(&mut self, cx: &mut ViewContext<ThemeSelector>) {
+    fn show_selected_theme(&mut self, cx: &mut ViewContext<Picker<ThemeSelectorDelegate>>) {
         if let Some(mat) = self.matches.get(self.selected_index) {
-            let registry = cx.global::<Arc<ThemeRegistry>>();
+            let registry = cx.global::<ThemeRegistry>();
             match registry.get(&mat.string) {
                 Ok(theme) => {
                     Self::set_theme(theme, cx);
@@ -110,16 +156,18 @@ impl ThemeSelectorDelegate {
     }
 
     fn set_theme(theme: Arc<Theme>, cx: &mut AppContext) {
-        cx.update_global::<SettingsStore, _, _>(|store, cx| {
+        cx.update_global(|store: &mut SettingsStore, cx| {
             let mut theme_settings = store.get::<ThemeSettings>(None).clone();
-            theme_settings.theme = theme;
+            theme_settings.active_theme = theme;
             store.override_global(theme_settings);
-            cx.refresh_windows();
+            cx.refresh();
         });
     }
 }
 
 impl PickerDelegate for ThemeSelectorDelegate {
+    type ListItem = ui::ListItem;
+
     fn placeholder_text(&self) -> Arc<str> {
         "Select Theme...".into()
     }
@@ -128,34 +176,46 @@ impl PickerDelegate for ThemeSelectorDelegate {
         self.matches.len()
     }
 
-    fn confirm(&mut self, _: bool, cx: &mut ViewContext<ThemeSelector>) {
+    fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<ThemeSelectorDelegate>>) {
         self.selection_completed = true;
 
-        let theme_name = theme::current(cx).meta.name.clone();
+        let theme_name = cx.theme().name.clone();
 
-        let telemetry_settings = *settings::get::<TelemetrySettings>(cx);
+        let telemetry_settings = TelemetrySettings::get_global(cx).clone();
         self.telemetry
             .report_setting_event(telemetry_settings, "theme", theme_name.to_string());
 
-        update_settings_file::<ThemeSettings>(self.fs.clone(), cx, |settings| {
-            settings.theme = Some(theme_name);
+        update_settings_file::<ThemeSettings>(self.fs.clone(), cx, move |settings| {
+            settings.theme = Some(theme_name.to_string());
         });
 
-        cx.emit(PickerEvent::Dismiss);
+        self.view
+            .update(cx, |_, cx| {
+                cx.emit(DismissEvent);
+            })
+            .ok();
     }
 
-    fn dismissed(&mut self, cx: &mut ViewContext<ThemeSelector>) {
+    fn dismissed(&mut self, cx: &mut ViewContext<Picker<ThemeSelectorDelegate>>) {
         if !self.selection_completed {
             Self::set_theme(self.original_theme.clone(), cx);
             self.selection_completed = true;
         }
+
+        self.view
+            .update(cx, |_, cx| cx.emit(DismissEvent))
+            .log_err();
     }
 
     fn selected_index(&self) -> usize {
         self.selected_index
     }
 
-    fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<ThemeSelector>) {
+    fn set_selected_index(
+        &mut self,
+        ix: usize,
+        cx: &mut ViewContext<Picker<ThemeSelectorDelegate>>,
+    ) {
         self.selected_index = ix;
         self.show_selected_theme(cx);
     }
@@ -163,17 +223,17 @@ impl PickerDelegate for ThemeSelectorDelegate {
     fn update_matches(
         &mut self,
         query: String,
-        cx: &mut ViewContext<ThemeSelector>,
+        cx: &mut ViewContext<Picker<ThemeSelectorDelegate>>,
     ) -> gpui::Task<()> {
-        let background = cx.background().clone();
+        let background = cx.background_executor().clone();
         let candidates = self
-            .theme_data
+            .themes
             .iter()
             .enumerate()
             .map(|(id, meta)| StringMatchCandidate {
                 id,
-                char_bag: meta.name.as_str().into(),
-                string: meta.name.clone(),
+                char_bag: meta.name.as_ref().into(),
+                string: meta.name.to_string(),
             })
             .collect::<Vec<_>>();
 
@@ -202,12 +262,12 @@ impl PickerDelegate for ThemeSelectorDelegate {
             };
 
             this.update(&mut cx, |this, cx| {
-                let delegate = this.delegate_mut();
-                delegate.matches = matches;
-                delegate.selected_index = delegate
+                this.delegate.matches = matches;
+                this.delegate.selected_index = this
+                    .delegate
                     .selected_index
-                    .min(delegate.matches.len().saturating_sub(1));
-                delegate.show_selected_theme(cx);
+                    .min(this.delegate.matches.len().saturating_sub(1));
+                this.delegate.show_selected_theme(cx);
             })
             .log_err();
         })
@@ -216,18 +276,20 @@ impl PickerDelegate for ThemeSelectorDelegate {
     fn render_match(
         &self,
         ix: usize,
-        mouse_state: &mut MouseState,
         selected: bool,
-        cx: &AppContext,
-    ) -> AnyElement<Picker<Self>> {
-        let theme = theme::current(cx);
-        let style = theme.picker.item.in_state(selected).style_for(mouse_state);
-
+        _cx: &mut ViewContext<Picker<Self>>,
+    ) -> Option<Self::ListItem> {
         let theme_match = &self.matches[ix];
-        Label::new(theme_match.string.clone(), style.label.clone())
-            .with_highlights(theme_match.positions.clone())
-            .contained()
-            .with_style(style.container)
-            .into_any()
+
+        Some(
+            ListItem::new(ix)
+                .inset(true)
+                .spacing(ListItemSpacing::Sparse)
+                .selected(selected)
+                .child(HighlightedLabel::new(
+                    theme_match.string.clone(),
+                    theme_match.positions.clone(),
+                )),
+        )
     }
 }

crates/theme_selector2/Cargo.toml 🔗

@@ -1,30 +0,0 @@
-[package]
-name = "theme_selector2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/theme_selector.rs"
-doctest = false
-
-[dependencies]
-client = { package = "client2", path = "../client2" }
-editor = { package = "editor2", path = "../editor2" }
-feature_flags = { package = "feature_flags2", path = "../feature_flags2" }
-fs = { package = "fs2", path = "../fs2" }
-fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-picker = { package = "picker2", path = "../picker2" }
-settings = { package = "settings2", path = "../settings2" }
-theme = { package = "theme2", path = "../theme2" }
-ui = { package = "ui2", path = "../ui2" }
-util = { path = "../util" }
-workspace = { package = "workspace2", path = "../workspace2" }
-log.workspace = true
-parking_lot.workspace = true
-postage.workspace = true
-smol.workspace = true
-
-[dev-dependencies]
-editor = { package = "editor2", path = "../editor2", features = ["test-support"] }

crates/theme_selector2/src/theme_selector.rs 🔗

@@ -1,295 +0,0 @@
-use client::{telemetry::Telemetry, TelemetrySettings};
-use feature_flags::FeatureFlagAppExt;
-use fs::Fs;
-use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
-use gpui::{
-    actions, AppContext, DismissEvent, EventEmitter, FocusableView, Render, View, ViewContext,
-    VisualContext, WeakView,
-};
-use picker::{Picker, PickerDelegate};
-use settings::{update_settings_file, Settings, SettingsStore};
-use std::sync::Arc;
-use theme::{Theme, ThemeMeta, ThemeRegistry, ThemeSettings};
-use ui::{prelude::*, v_stack, ListItem, ListItemSpacing};
-use util::ResultExt;
-use workspace::{ui::HighlightedLabel, ModalView, Workspace};
-
-actions!(theme_selector, [Toggle, Reload]);
-
-pub fn init(cx: &mut AppContext) {
-    cx.observe_new_views(
-        |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
-            workspace.register_action(toggle);
-        },
-    )
-    .detach();
-}
-
-pub fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
-    let fs = workspace.app_state().fs.clone();
-    let telemetry = workspace.client().telemetry().clone();
-    workspace.toggle_modal(cx, |cx| {
-        ThemeSelector::new(
-            ThemeSelectorDelegate::new(cx.view().downgrade(), fs, telemetry, cx),
-            cx,
-        )
-    });
-}
-
-#[cfg(debug_assertions)]
-pub fn reload(cx: &mut AppContext) {
-    let current_theme_name = cx.theme().name.clone();
-    let current_theme = cx.update_global(|registry: &mut ThemeRegistry, _cx| {
-        registry.clear();
-        registry.get(&current_theme_name)
-    });
-    match current_theme {
-        Ok(theme) => {
-            ThemeSelectorDelegate::set_theme(theme, cx);
-            log::info!("reloaded theme {}", current_theme_name);
-        }
-        Err(error) => {
-            log::error!("failed to load theme {}: {:?}", current_theme_name, error)
-        }
-    }
-}
-
-impl ModalView for ThemeSelector {}
-
-pub struct ThemeSelector {
-    picker: View<Picker<ThemeSelectorDelegate>>,
-}
-
-impl EventEmitter<DismissEvent> for ThemeSelector {}
-
-impl FocusableView for ThemeSelector {
-    fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
-        self.picker.focus_handle(cx)
-    }
-}
-
-impl Render for ThemeSelector {
-    fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
-        v_stack().w(rems(34.)).child(self.picker.clone())
-    }
-}
-
-impl ThemeSelector {
-    pub fn new(delegate: ThemeSelectorDelegate, cx: &mut ViewContext<Self>) -> Self {
-        let picker = cx.new_view(|cx| Picker::new(delegate, cx));
-        Self { picker }
-    }
-}
-
-pub struct ThemeSelectorDelegate {
-    fs: Arc<dyn Fs>,
-    themes: Vec<ThemeMeta>,
-    matches: Vec<StringMatch>,
-    original_theme: Arc<Theme>,
-    selection_completed: bool,
-    selected_index: usize,
-    telemetry: Arc<Telemetry>,
-    view: WeakView<ThemeSelector>,
-}
-
-impl ThemeSelectorDelegate {
-    fn new(
-        weak_view: WeakView<ThemeSelector>,
-        fs: Arc<dyn Fs>,
-        telemetry: Arc<Telemetry>,
-        cx: &mut ViewContext<ThemeSelector>,
-    ) -> Self {
-        let original_theme = cx.theme().clone();
-
-        let staff_mode = cx.is_staff();
-        let registry = cx.global::<ThemeRegistry>();
-        let mut themes = registry.list(staff_mode).collect::<Vec<_>>();
-        themes.sort_unstable_by(|a, b| {
-            a.appearance
-                .is_light()
-                .cmp(&b.appearance.is_light())
-                .then(a.name.cmp(&b.name))
-        });
-        let matches = themes
-            .iter()
-            .map(|meta| StringMatch {
-                candidate_id: 0,
-                score: 0.0,
-                positions: Default::default(),
-                string: meta.name.to_string(),
-            })
-            .collect();
-        let mut this = Self {
-            fs,
-            themes,
-            matches,
-            original_theme: original_theme.clone(),
-            selected_index: 0,
-            selection_completed: false,
-            telemetry,
-            view: weak_view,
-        };
-        this.select_if_matching(&original_theme.name);
-        this
-    }
-
-    fn show_selected_theme(&mut self, cx: &mut ViewContext<Picker<ThemeSelectorDelegate>>) {
-        if let Some(mat) = self.matches.get(self.selected_index) {
-            let registry = cx.global::<ThemeRegistry>();
-            match registry.get(&mat.string) {
-                Ok(theme) => {
-                    Self::set_theme(theme, cx);
-                }
-                Err(error) => {
-                    log::error!("error loading theme {}: {}", mat.string, error)
-                }
-            }
-        }
-    }
-
-    fn select_if_matching(&mut self, theme_name: &str) {
-        self.selected_index = self
-            .matches
-            .iter()
-            .position(|mat| mat.string == theme_name)
-            .unwrap_or(self.selected_index);
-    }
-
-    fn set_theme(theme: Arc<Theme>, cx: &mut AppContext) {
-        cx.update_global(|store: &mut SettingsStore, cx| {
-            let mut theme_settings = store.get::<ThemeSettings>(None).clone();
-            theme_settings.active_theme = theme;
-            store.override_global(theme_settings);
-            cx.refresh();
-        });
-    }
-}
-
-impl PickerDelegate for ThemeSelectorDelegate {
-    type ListItem = ui::ListItem;
-
-    fn placeholder_text(&self) -> Arc<str> {
-        "Select Theme...".into()
-    }
-
-    fn match_count(&self) -> usize {
-        self.matches.len()
-    }
-
-    fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<ThemeSelectorDelegate>>) {
-        self.selection_completed = true;
-
-        let theme_name = cx.theme().name.clone();
-
-        let telemetry_settings = TelemetrySettings::get_global(cx).clone();
-        self.telemetry
-            .report_setting_event(telemetry_settings, "theme", theme_name.to_string());
-
-        update_settings_file::<ThemeSettings>(self.fs.clone(), cx, move |settings| {
-            settings.theme = Some(theme_name.to_string());
-        });
-
-        self.view
-            .update(cx, |_, cx| {
-                cx.emit(DismissEvent);
-            })
-            .ok();
-    }
-
-    fn dismissed(&mut self, cx: &mut ViewContext<Picker<ThemeSelectorDelegate>>) {
-        if !self.selection_completed {
-            Self::set_theme(self.original_theme.clone(), cx);
-            self.selection_completed = true;
-        }
-
-        self.view
-            .update(cx, |_, cx| cx.emit(DismissEvent))
-            .log_err();
-    }
-
-    fn selected_index(&self) -> usize {
-        self.selected_index
-    }
-
-    fn set_selected_index(
-        &mut self,
-        ix: usize,
-        cx: &mut ViewContext<Picker<ThemeSelectorDelegate>>,
-    ) {
-        self.selected_index = ix;
-        self.show_selected_theme(cx);
-    }
-
-    fn update_matches(
-        &mut self,
-        query: String,
-        cx: &mut ViewContext<Picker<ThemeSelectorDelegate>>,
-    ) -> gpui::Task<()> {
-        let background = cx.background_executor().clone();
-        let candidates = self
-            .themes
-            .iter()
-            .enumerate()
-            .map(|(id, meta)| StringMatchCandidate {
-                id,
-                char_bag: meta.name.as_ref().into(),
-                string: meta.name.to_string(),
-            })
-            .collect::<Vec<_>>();
-
-        cx.spawn(|this, mut cx| async move {
-            let matches = if query.is_empty() {
-                candidates
-                    .into_iter()
-                    .enumerate()
-                    .map(|(index, candidate)| StringMatch {
-                        candidate_id: index,
-                        string: candidate.string,
-                        positions: Vec::new(),
-                        score: 0.0,
-                    })
-                    .collect()
-            } else {
-                match_strings(
-                    &candidates,
-                    &query,
-                    false,
-                    100,
-                    &Default::default(),
-                    background,
-                )
-                .await
-            };
-
-            this.update(&mut cx, |this, cx| {
-                this.delegate.matches = matches;
-                this.delegate.selected_index = this
-                    .delegate
-                    .selected_index
-                    .min(this.delegate.matches.len().saturating_sub(1));
-                this.delegate.show_selected_theme(cx);
-            })
-            .log_err();
-        })
-    }
-
-    fn render_match(
-        &self,
-        ix: usize,
-        selected: bool,
-        _cx: &mut ViewContext<Picker<Self>>,
-    ) -> Option<Self::ListItem> {
-        let theme_match = &self.matches[ix];
-
-        Some(
-            ListItem::new(ix)
-                .inset(true)
-                .spacing(ListItemSpacing::Sparse)
-                .selected(selected)
-                .child(HighlightedLabel::new(
-                    theme_match.string.clone(),
-                    theme_match.positions.clone(),
-                )),
-        )
-    }
-}

crates/ui2/Cargo.toml → crates/ui/Cargo.toml 🔗

@@ -1,25 +1,25 @@
 [package]
-name = "ui2"
+name = "ui"
 version = "0.1.0"
 edition = "2021"
 publish = false
 
 [lib]
-name = "ui2"
-path = "src/ui2.rs"
+name = "ui"
+path = "src/ui.rs"
 
 [dependencies]
 anyhow.workspace = true
 chrono = "0.4"
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
 itertools = { version = "0.11.0", optional = true }
-menu = { package = "menu2", path = "../menu2"}
+menu = { path = "../menu"}
 serde.workspace = true
-settings = { package = "settings2", path = "../settings2" }
+settings = { path = "../settings" }
 smallvec.workspace = true
 story = { path = "../story", optional = true }
 strum = { version = "0.25.0", features = ["derive"] }
-theme = { package = "theme2", path = "../theme2" }
+theme = { path = "../theme" }
 rand = "0.8"
 
 [features]

crates/ui2/docs/hello-world.md → crates/ui/docs/hello-world.md 🔗

@@ -80,7 +80,7 @@ This may change in the future, but this is a little trick that let's you use fam
 
 ## Building out the container
 
-Let's grab our [theme2::colors::ThemeColors] from the theme and start building out a basic container.
+Let's grab our [theme::colors::ThemeColors] from the theme and start building out a basic container.
 
 We can access the current theme's colors like this:
 

crates/ui2/src/components/popover.rs → crates/ui/src/components/popover.rs 🔗

@@ -1,12 +1,10 @@
+use crate::prelude::*;
+use crate::v_stack;
 use gpui::{
-    div, AnyElement, Element, ElementId, IntoElement, ParentElement, RenderOnce, Styled,
-    WindowContext,
+    div, AnyElement, Element, IntoElement, ParentElement, RenderOnce, Styled, WindowContext,
 };
 use smallvec::SmallVec;
 
-use crate::prelude::*;
-use crate::v_stack;
-
 /// A popover is used to display a menu or show some options.
 ///
 /// Clicking the element that launches the popover should not change the current view,

crates/ui2/src/styled_ext.rs → crates/ui/src/styled_ext.rs 🔗

@@ -78,7 +78,7 @@ pub trait StyledExt: Styled + Sized {
         self.text_size(settings.buffer_font_size(cx))
     }
 
-    /// The [`Surface`](ui2::ElevationIndex::Surface) elevation level, located above the app background, is the standard level for all elements
+    /// The [`Surface`](ui::ElevationIndex::Surface) elevation level, located above the app background, is the standard level for all elements
     ///
     /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
     ///
@@ -87,7 +87,7 @@ pub trait StyledExt: Styled + Sized {
         elevated(self, cx, ElevationIndex::Surface)
     }
 
-    /// Non-Modal Elevated Surfaces appear above the [`Surface`](ui2::ElevationIndex::Surface) layer and is used for things that should appear above most UI elements like an editor or panel, but not elements like popovers, context menus, modals, etc.
+    /// Non-Modal Elevated Surfaces appear above the [`Surface`](ui::ElevationIndex::Surface) layer and is used for things that should appear above most UI elements like an editor or panel, but not elements like popovers, context menus, modals, etc.
     ///
     /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
     ///
@@ -100,7 +100,7 @@ pub trait StyledExt: Styled + Sized {
     ///
     /// Elements rendered at this layer should have an enforced behavior: Any interaction outside of the modal will either dismiss the modal or prompt an action (Save your progress, etc) then dismiss the modal.
     ///
-    /// If the element does not have this behavior, it should be rendered at the [`Elevated Surface`](ui2::ElevationIndex::ElevatedSurface) layer.
+    /// If the element does not have this behavior, it should be rendered at the [`Elevated Surface`](ui::ElevationIndex::ElevatedSurface) layer.
     ///
     /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
     ///

crates/ui2/src/utils/format_distance.rs → crates/ui/src/utils/format_distance.rs 🔗

@@ -246,7 +246,7 @@ fn distance_string(
 ///
 /// ```rust
 /// use chrono::DateTime;
-/// use ui2::utils::naive_format_distance;
+/// use ui::utils::format_distance;
 ///
 /// fn time_between_moon_landings() -> String {
 ///     let date = DateTime::parse_from_rfc3339("1969-07-20T00:00:00Z").unwrap().naive_local();
@@ -282,7 +282,7 @@ pub fn format_distance(
 ///
 /// ```rust
 /// use chrono::DateTime;
-/// use ui2::utils::naive_format_distance_from_now;
+/// use ui::utils::naive_format_distance_from_now;
 ///
 /// fn time_since_first_moon_landing() -> String {
 ///     let date = DateTime::parse_from_rfc3339("1969-07-20T00:00:00Z").unwrap().naive_local();

crates/util/Cargo.toml 🔗

@@ -11,12 +11,6 @@ doctest = true
 [features]
 test-support = ["tempdir", "git2"]
 
-# Suppress a panic when both GPUI1 and GPUI2 are loaded.
-#
-# This is used in the `theme_importer` where we need to depend on both
-# GPUI1 and GPUI2 in order to convert Zed1 themes to Zed2 themes.
-allow-multiple-gpui-versions = []
-
 [dependencies]
 anyhow.workspace = true
 backtrace = "0.3"

crates/util/src/util.rs 🔗

@@ -16,9 +16,6 @@ use std::{
     task::{Context, Poll},
 };
 
-#[cfg(not(feature = "allow-multiple-gpui-versions"))]
-use std::sync::atomic::AtomicU32;
-
 pub use backtrace::Backtrace;
 use futures::Future;
 use rand::{seq::SliceRandom, Rng};
@@ -436,23 +433,6 @@ impl<T: Ord + Clone> RangeExt<T> for RangeInclusive<T> {
     }
 }
 
-#[cfg(not(feature = "allow-multiple-gpui-versions"))]
-static GPUI_LOADED: AtomicU32 = AtomicU32::new(0);
-
-pub fn gpui2_loaded() {
-    #[cfg(not(feature = "allow-multiple-gpui-versions"))]
-    if GPUI_LOADED.fetch_add(2, std::sync::atomic::Ordering::SeqCst) != 0 {
-        panic!("=========\nYou are loading both GPUI1 and GPUI2 in the same build!\nFix Your Dependencies with cargo tree!\n=========")
-    }
-}
-
-pub fn gpui1_loaded() {
-    #[cfg(not(feature = "allow-multiple-gpui-versions"))]
-    if GPUI_LOADED.fetch_add(1, std::sync::atomic::Ordering::SeqCst) != 0 {
-        panic!("=========\nYou are loading both GPUI1 and GPUI2 in the same build!\nFix Your Dependencies with cargo tree!\n=========")
-    }
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;

crates/vcs_menu/Cargo.toml 🔗

@@ -6,12 +6,12 @@ publish = false
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-fuzzy = {path = "../fuzzy"}
+fuzzy = { path = "../fuzzy"}
 fs = {path = "../fs"}
 gpui = {path = "../gpui"}
 picker = {path = "../picker"}
 util = {path = "../util"}
-theme = {path = "../theme"}
-workspace = {path = "../workspace"}
+ui = {path = "../ui"}
+workspace = { path = "../workspace" }
 
 anyhow.workspace = true

crates/vcs_menu/src/lib.rs 🔗

@@ -2,57 +2,95 @@ use anyhow::{anyhow, bail, Result};
 use fs::repository::Branch;
 use fuzzy::{StringMatch, StringMatchCandidate};
 use gpui::{
-    actions,
-    elements::*,
-    platform::{CursorStyle, MouseButton},
-    AppContext, MouseState, Task, ViewContext, ViewHandle,
+    actions, rems, AnyElement, AppContext, DismissEvent, Element, EventEmitter, FocusHandle,
+    FocusableView, InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled,
+    Subscription, Task, View, ViewContext, VisualContext, WindowContext,
 };
-use picker::{Picker, PickerDelegate, PickerEvent};
+use picker::{Picker, PickerDelegate};
 use std::{ops::Not, sync::Arc};
+use ui::{
+    h_stack, v_stack, Button, ButtonCommon, Clickable, HighlightedLabel, Label, LabelCommon,
+    LabelSize, ListItem, ListItemSpacing, Selectable,
+};
 use util::ResultExt;
-use workspace::{Toast, Workspace};
+use workspace::{ModalView, Toast, Workspace};
 
 actions!(branches, [OpenRecent]);
 
 pub fn init(cx: &mut AppContext) {
-    Picker::<BranchListDelegate>::init(cx);
-    cx.add_action(toggle);
+    // todo!() po
+    cx.observe_new_views(|workspace: &mut Workspace, _| {
+        workspace.register_action(|workspace, action, cx| {
+            BranchList::toggle_modal(workspace, action, cx).log_err();
+        });
+    })
+    .detach();
 }
-pub type BranchList = Picker<BranchListDelegate>;
 
-pub fn build_branch_list(
-    workspace: ViewHandle<Workspace>,
-    cx: &mut ViewContext<BranchList>,
-) -> Result<BranchList> {
-    let delegate = workspace.read_with(cx, |workspace, cx| {
-        BranchListDelegate::new(workspace, cx.handle(), 29, cx)
-    })?;
+pub struct BranchList {
+    pub picker: View<Picker<BranchListDelegate>>,
+    rem_width: f32,
+    _subscription: Subscription,
+}
+
+impl BranchList {
+    fn new(delegate: BranchListDelegate, rem_width: f32, cx: &mut ViewContext<Self>) -> Self {
+        let picker = cx.new_view(|cx| Picker::new(delegate, cx));
+        let _subscription = cx.subscribe(&picker, |_, _, _, cx| cx.emit(DismissEvent));
+        Self {
+            picker,
+            rem_width,
+            _subscription,
+        }
+    }
+    fn toggle_modal(
+        workspace: &mut Workspace,
+        _: &OpenRecent,
+        cx: &mut ViewContext<Workspace>,
+    ) -> Result<()> {
+        // Modal branch picker has a longer trailoff than a popover one.
+        let delegate = BranchListDelegate::new(workspace, cx.view().clone(), 70, cx)?;
+        workspace.toggle_modal(cx, |cx| BranchList::new(delegate, 34., cx));
 
-    Ok(Picker::new(delegate, cx).with_theme(|theme| theme.picker.clone()))
+        Ok(())
+    }
 }
+impl ModalView for BranchList {}
+impl EventEmitter<DismissEvent> for BranchList {}
 
-fn toggle(
-    workspace: &mut Workspace,
-    _: &OpenRecent,
-    cx: &mut ViewContext<Workspace>,
-) -> Result<()> {
-    // Modal branch picker has a longer trailoff than a popover one.
-    let delegate = BranchListDelegate::new(workspace, cx.handle(), 70, cx)?;
-    workspace.toggle_modal(cx, |_, cx| {
-        cx.add_view(|cx| {
-            Picker::new(delegate, cx)
-                .with_theme(|theme| theme.picker.clone())
-                .with_max_size(800., 1200.)
-        })
-    });
+impl FocusableView for BranchList {
+    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
+        self.picker.focus_handle(cx)
+    }
+}
 
-    Ok(())
+impl Render for BranchList {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        v_stack()
+            .w(rems(self.rem_width))
+            .child(self.picker.clone())
+            .on_mouse_down_out(cx.listener(|this, _, cx| {
+                this.picker.update(cx, |this, cx| {
+                    this.cancel(&Default::default(), cx);
+                })
+            }))
+    }
+}
+
+pub fn build_branch_list(
+    workspace: View<Workspace>,
+    cx: &mut WindowContext<'_>,
+) -> Result<View<BranchList>> {
+    let delegate = workspace.update(cx, |workspace, cx| {
+        BranchListDelegate::new(workspace, cx.view().clone(), 29, cx)
+    })?;
+    Ok(cx.new_view(move |cx| BranchList::new(delegate, 20., cx)))
 }
 
 pub struct BranchListDelegate {
     matches: Vec<StringMatch>,
     all_branches: Vec<Branch>,
-    workspace: ViewHandle<Workspace>,
+    workspace: View<Workspace>,
     selected_index: usize,
     last_query: String,
     /// Max length of branch name before we truncate it and add a trailing `...`.
@@ -62,7 +100,7 @@ pub struct BranchListDelegate {
 impl BranchListDelegate {
     fn new(
         workspace: &Workspace,
-        handle: ViewHandle<Workspace>,
+        handle: View<Workspace>,
         branch_name_trailoff_after: usize,
         cx: &AppContext,
     ) -> Result<Self> {
@@ -87,7 +125,7 @@ impl BranchListDelegate {
         })
     }
 
-    fn display_error_toast(&self, message: String, cx: &mut ViewContext<BranchList>) {
+    fn display_error_toast(&self, message: String, cx: &mut WindowContext<'_>) {
         const GIT_CHECKOUT_FAILURE_ID: usize = 2048;
         self.workspace.update(cx, |model, ctx| {
             model.show_toast(Toast::new(GIT_CHECKOUT_FAILURE_ID, message), ctx)
@@ -96,6 +134,8 @@ impl BranchListDelegate {
 }
 
 impl PickerDelegate for BranchListDelegate {
+    type ListItem = ListItem;
+
     fn placeholder_text(&self) -> Arc<str> {
         "Select branch...".into()
     }
@@ -114,9 +154,9 @@ impl PickerDelegate for BranchListDelegate {
 
     fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
         cx.spawn(move |picker, mut cx| async move {
-            let candidates = picker.read_with(&mut cx, |view, _| {
+            let candidates = picker.update(&mut cx, |view, _| {
                 const RECENT_BRANCHES_COUNT: usize = 10;
-                let mut branches = view.delegate().all_branches.clone();
+                let mut branches = view.delegate.all_branches.clone();
                 if query.is_empty() && branches.len() > RECENT_BRANCHES_COUNT {
                     // Truncate list of recent branches
                     // Do a partial sort to show recent-ish branches first.
@@ -157,13 +197,13 @@ impl PickerDelegate for BranchListDelegate {
                     true,
                     10000,
                     &Default::default(),
-                    cx.background(),
+                    cx.background_executor().clone(),
                 )
                 .await
             };
             picker
                 .update(&mut cx, |picker, _| {
-                    let delegate = picker.delegate_mut();
+                    let delegate = &mut picker.delegate;
                     delegate.matches = matches;
                     if delegate.matches.is_empty() {
                         delegate.selected_index = 0;
@@ -189,7 +229,7 @@ impl PickerDelegate for BranchListDelegate {
         cx.spawn(|picker, mut cx| async move {
             picker
                 .update(&mut cx, |this, cx| {
-                    let project = this.delegate().workspace.read(cx).project().read(cx);
+                    let project = this.delegate.workspace.read(cx).project().read(cx);
                     let mut cwd = project
                         .visible_worktrees(cx)
                         .next()
@@ -210,10 +250,10 @@ impl PickerDelegate for BranchListDelegate {
                         .lock()
                         .change_branch(&current_pick);
                     if status.is_err() {
-                        this.delegate().display_error_toast(format!("Failed to checkout branch '{current_pick}', check for conflicts or unstashed files"), cx);
+                        this.delegate.display_error_toast(format!("Failed to checkout branch '{current_pick}', check for conflicts or unstashed files"), cx);
                         status?;
                     }
-                    cx.emit(PickerEvent::Dismiss);
+                    cx.emit(DismissEvent);
 
                     Ok::<(), anyhow::Error>(())
                 })
@@ -223,123 +263,96 @@ impl PickerDelegate for BranchListDelegate {
     }
 
     fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
-        cx.emit(PickerEvent::Dismiss);
+        cx.emit(DismissEvent);
     }
 
     fn render_match(
         &self,
         ix: usize,
-        mouse_state: &mut MouseState,
         selected: bool,
-        cx: &gpui::AppContext,
-    ) -> AnyElement<Picker<Self>> {
-        let theme = &theme::current(cx);
+        _cx: &mut ViewContext<Picker<Self>>,
+    ) -> Option<Self::ListItem> {
         let hit = &self.matches[ix];
         let shortened_branch_name =
             util::truncate_and_trailoff(&hit.string, self.branch_name_trailoff_after);
-        let highlights = hit
+        let highlights: Vec<_> = hit
             .positions
             .iter()
+            .filter(|index| index < &&self.branch_name_trailoff_after)
             .copied()
-            .filter(|index| index < &self.branch_name_trailoff_after)
             .collect();
-        let style = theme.picker.item.in_state(selected).style_for(mouse_state);
-        Flex::row()
-            .with_child(
-                Label::new(shortened_branch_name.clone(), style.label.clone())
-                    .with_highlights(highlights)
-                    .contained()
-                    .aligned()
-                    .left(),
-            )
-            .contained()
-            .with_style(style.container)
-            .constrained()
-            .with_height(theme.collab_panel.tabbed_modal.row_height)
-            .into_any()
+        Some(
+            ListItem::new(SharedString::from(format!("vcs-menu-{ix}")))
+                .inset(true)
+                .spacing(ListItemSpacing::Sparse)
+                .selected(selected)
+                .start_slot(HighlightedLabel::new(shortened_branch_name, highlights)),
+        )
     }
-    fn render_header(
-        &self,
-        cx: &mut ViewContext<Picker<Self>>,
-    ) -> Option<AnyElement<Picker<Self>>> {
-        let theme = &theme::current(cx);
-        let style = theme.picker.header.clone();
+    fn render_header(&self, _: &mut ViewContext<Picker<Self>>) -> Option<AnyElement> {
         let label = if self.last_query.is_empty() {
-            Flex::row()
-                .with_child(Label::new("Recent branches", style.label.clone()))
-                .contained()
-                .with_style(style.container)
+            h_stack()
+                .ml_3()
+                .child(Label::new("Recent branches").size(LabelSize::Small))
         } else {
-            Flex::row()
-                .with_child(Label::new("Branches", style.label.clone()))
-                .with_children(self.matches.is_empty().not().then(|| {
-                    let suffix = if self.matches.len() == 1 { "" } else { "es" };
-                    Label::new(
-                        format!("{} match{}", self.matches.len(), suffix),
-                        style.label,
-                    )
-                    .flex_float()
-                }))
-                .contained()
-                .with_style(style.container)
+            let match_label = self.matches.is_empty().not().then(|| {
+                let suffix = if self.matches.len() == 1 { "" } else { "es" };
+                Label::new(format!("{} match{}", self.matches.len(), suffix)).size(LabelSize::Small)
+            });
+            h_stack()
+                .px_3()
+                .h_full()
+                .justify_between()
+                .child(Label::new("Branches").size(LabelSize::Small))
+                .children(match_label)
         };
         Some(label.into_any())
     }
-    fn render_footer(
-        &self,
-        cx: &mut ViewContext<Picker<Self>>,
-    ) -> Option<AnyElement<Picker<Self>>> {
-        if !self.last_query.is_empty() {
-            let theme = &theme::current(cx);
-            let style = theme.picker.footer.clone();
-            enum BranchCreateButton {}
-            Some(
-                Flex::row().with_child(MouseEventHandler::new::<BranchCreateButton, _>(0, cx, |state, _| {
-                    let style = style.style_for(state);
-                    Label::new("Create branch", style.label.clone())
-                        .contained()
-                        .with_style(style.container)
-                })
-                .with_cursor_style(CursorStyle::PointingHand)
-                .on_down(MouseButton::Left, |_, _, cx| {
-                    cx.spawn(|picker, mut cx| async move {
-                        picker.update(&mut cx, |this, cx| {
-                            let project = this.delegate().workspace.read(cx).project().read(cx);
-                            let current_pick = &this.delegate().last_query;
-                            let mut cwd = project
-                            .visible_worktrees(cx)
-                            .next()
-                            .ok_or_else(|| anyhow!("There are no visisible worktrees."))?
-                            .read(cx)
-                            .abs_path()
-                            .to_path_buf();
-                            cwd.push(".git");
-                            let repo = project
-                                .fs()
-                                .open_repo(&cwd)
-                                .ok_or_else(|| anyhow!("Could not open repository at path `{}`", cwd.as_os_str().to_string_lossy()))?;
-                            let repo = repo
-                                .lock();
-                            let status = repo
-                                .create_branch(&current_pick);
-                            if status.is_err() {
-                                this.delegate().display_error_toast(format!("Failed to create branch '{current_pick}', check for conflicts or unstashed files"), cx);
-                                status?;
-                            }
-                            let status = repo.change_branch(&current_pick);
-                            if status.is_err() {
-                                this.delegate().display_error_toast(format!("Failed to chec branch '{current_pick}', check for conflicts or unstashed files"), cx);
-                                status?;
-                            }
-                            cx.emit(PickerEvent::Dismiss);
-                            Ok::<(), anyhow::Error>(())
-                })
-                    }).detach();
-                })).aligned().right()
-                .into_any(),
-            )
-        } else {
-            None
+    fn render_footer(&self, cx: &mut ViewContext<Picker<Self>>) -> Option<AnyElement> {
+        if self.last_query.is_empty() {
+            return None;
         }
+
+        Some(
+            h_stack().mr_3().pb_2().child(h_stack().w_full()).child(
+            Button::new("branch-picker-create-branch-button", "Create branch").on_click(
+                cx.listener(|_, _, cx| {
+                    cx.spawn(|picker, mut cx| async move {
+                                        picker.update(&mut cx, |this, cx| {
+                                            let project = this.delegate.workspace.read(cx).project().read(cx);
+                                            let current_pick = &this.delegate.last_query;
+                                            let mut cwd = project
+                                            .visible_worktrees(cx)
+                                            .next()
+                                            .ok_or_else(|| anyhow!("There are no visisible worktrees."))?
+                                            .read(cx)
+                                            .abs_path()
+                                            .to_path_buf();
+                                            cwd.push(".git");
+                                            let repo = project
+                                                .fs()
+                                                .open_repo(&cwd)
+                                                .ok_or_else(|| anyhow!("Could not open repository at path `{}`", cwd.as_os_str().to_string_lossy()))?;
+                                            let repo = repo
+                                                .lock();
+                                            let status = repo
+                                                .create_branch(&current_pick);
+                                            if status.is_err() {
+                                                this.delegate.display_error_toast(format!("Failed to create branch '{current_pick}', check for conflicts or unstashed files"), cx);
+                                                status?;
+                                            }
+                                            let status = repo.change_branch(&current_pick);
+                                            if status.is_err() {
+                                                this.delegate.display_error_toast(format!("Failed to chec branch '{current_pick}', check for conflicts or unstashed files"), cx);
+                                                status?;
+                                            }
+                                            this.cancel(&Default::default(), cx);
+                                            Ok::<(), anyhow::Error>(())
+                                })
+
+                    }).detach_and_log_err(cx);
+                }),
+            ).style(ui::ButtonStyle::Filled)).into_any_element(),
+        )
     }
 }

crates/vcs_menu2/Cargo.toml 🔗

@@ -1,17 +0,0 @@
-[package]
-name = "vcs_menu2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-fuzzy = {package = "fuzzy2", path = "../fuzzy2"}
-fs = {package = "fs2", path = "../fs2"}
-gpui = {package = "gpui2", path = "../gpui2"}
-picker = {package = "picker2", path = "../picker2"}
-util = {path = "../util"}
-ui = {package = "ui2", path = "../ui2"}
-workspace = {package = "workspace2", path = "../workspace2"}
-
-anyhow.workspace = true

crates/vcs_menu2/src/lib.rs 🔗

@@ -1,358 +0,0 @@
-use anyhow::{anyhow, bail, Result};
-use fs::repository::Branch;
-use fuzzy::{StringMatch, StringMatchCandidate};
-use gpui::{
-    actions, rems, AnyElement, AppContext, DismissEvent, Element, EventEmitter, FocusHandle,
-    FocusableView, InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled,
-    Subscription, Task, View, ViewContext, VisualContext, WindowContext,
-};
-use picker::{Picker, PickerDelegate};
-use std::{ops::Not, sync::Arc};
-use ui::{
-    h_stack, v_stack, Button, ButtonCommon, Clickable, HighlightedLabel, Label, LabelCommon,
-    LabelSize, ListItem, ListItemSpacing, Selectable,
-};
-use util::ResultExt;
-use workspace::{ModalView, Toast, Workspace};
-
-actions!(branches, [OpenRecent]);
-
-pub fn init(cx: &mut AppContext) {
-    // todo!() po
-    cx.observe_new_views(|workspace: &mut Workspace, _| {
-        workspace.register_action(|workspace, action, cx| {
-            BranchList::toggle_modal(workspace, action, cx).log_err();
-        });
-    })
-    .detach();
-}
-
-pub struct BranchList {
-    pub picker: View<Picker<BranchListDelegate>>,
-    rem_width: f32,
-    _subscription: Subscription,
-}
-
-impl BranchList {
-    fn new(delegate: BranchListDelegate, rem_width: f32, cx: &mut ViewContext<Self>) -> Self {
-        let picker = cx.new_view(|cx| Picker::new(delegate, cx));
-        let _subscription = cx.subscribe(&picker, |_, _, _, cx| cx.emit(DismissEvent));
-        Self {
-            picker,
-            rem_width,
-            _subscription,
-        }
-    }
-    fn toggle_modal(
-        workspace: &mut Workspace,
-        _: &OpenRecent,
-        cx: &mut ViewContext<Workspace>,
-    ) -> Result<()> {
-        // Modal branch picker has a longer trailoff than a popover one.
-        let delegate = BranchListDelegate::new(workspace, cx.view().clone(), 70, cx)?;
-        workspace.toggle_modal(cx, |cx| BranchList::new(delegate, 34., cx));
-
-        Ok(())
-    }
-}
-impl ModalView for BranchList {}
-impl EventEmitter<DismissEvent> for BranchList {}
-
-impl FocusableView for BranchList {
-    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
-        self.picker.focus_handle(cx)
-    }
-}
-
-impl Render for BranchList {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        v_stack()
-            .w(rems(self.rem_width))
-            .child(self.picker.clone())
-            .on_mouse_down_out(cx.listener(|this, _, cx| {
-                this.picker.update(cx, |this, cx| {
-                    this.cancel(&Default::default(), cx);
-                })
-            }))
-    }
-}
-
-pub fn build_branch_list(
-    workspace: View<Workspace>,
-    cx: &mut WindowContext<'_>,
-) -> Result<View<BranchList>> {
-    let delegate = workspace.update(cx, |workspace, cx| {
-        BranchListDelegate::new(workspace, cx.view().clone(), 29, cx)
-    })?;
-    Ok(cx.new_view(move |cx| BranchList::new(delegate, 20., cx)))
-}
-
-pub struct BranchListDelegate {
-    matches: Vec<StringMatch>,
-    all_branches: Vec<Branch>,
-    workspace: View<Workspace>,
-    selected_index: usize,
-    last_query: String,
-    /// Max length of branch name before we truncate it and add a trailing `...`.
-    branch_name_trailoff_after: usize,
-}
-
-impl BranchListDelegate {
-    fn new(
-        workspace: &Workspace,
-        handle: View<Workspace>,
-        branch_name_trailoff_after: usize,
-        cx: &AppContext,
-    ) -> Result<Self> {
-        let project = workspace.project().read(&cx);
-        let Some(worktree) = project.visible_worktrees(cx).next() else {
-            bail!("Cannot update branch list as there are no visible worktrees")
-        };
-
-        let mut cwd = worktree.read(cx).abs_path().to_path_buf();
-        cwd.push(".git");
-        let Some(repo) = project.fs().open_repo(&cwd) else {
-            bail!("Project does not have associated git repository.")
-        };
-        let all_branches = repo.lock().branches()?;
-        Ok(Self {
-            matches: vec![],
-            workspace: handle,
-            all_branches,
-            selected_index: 0,
-            last_query: Default::default(),
-            branch_name_trailoff_after,
-        })
-    }
-
-    fn display_error_toast(&self, message: String, cx: &mut WindowContext<'_>) {
-        const GIT_CHECKOUT_FAILURE_ID: usize = 2048;
-        self.workspace.update(cx, |model, ctx| {
-            model.show_toast(Toast::new(GIT_CHECKOUT_FAILURE_ID, message), ctx)
-        });
-    }
-}
-
-impl PickerDelegate for BranchListDelegate {
-    type ListItem = ListItem;
-
-    fn placeholder_text(&self) -> Arc<str> {
-        "Select branch...".into()
-    }
-
-    fn match_count(&self) -> usize {
-        self.matches.len()
-    }
-
-    fn selected_index(&self) -> usize {
-        self.selected_index
-    }
-
-    fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
-        self.selected_index = ix;
-    }
-
-    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
-        cx.spawn(move |picker, mut cx| async move {
-            let candidates = picker.update(&mut cx, |view, _| {
-                const RECENT_BRANCHES_COUNT: usize = 10;
-                let mut branches = view.delegate.all_branches.clone();
-                if query.is_empty() && branches.len() > RECENT_BRANCHES_COUNT {
-                    // Truncate list of recent branches
-                    // Do a partial sort to show recent-ish branches first.
-                    branches.select_nth_unstable_by(RECENT_BRANCHES_COUNT - 1, |lhs, rhs| {
-                        rhs.unix_timestamp.cmp(&lhs.unix_timestamp)
-                    });
-                    branches.truncate(RECENT_BRANCHES_COUNT);
-                    branches.sort_unstable_by(|lhs, rhs| lhs.name.cmp(&rhs.name));
-                }
-                branches
-                    .into_iter()
-                    .enumerate()
-                    .map(|(ix, command)| StringMatchCandidate {
-                        id: ix,
-                        char_bag: command.name.chars().collect(),
-                        string: command.name.into(),
-                    })
-                    .collect::<Vec<StringMatchCandidate>>()
-            });
-            let Some(candidates) = candidates.log_err() else {
-                return;
-            };
-            let matches = if query.is_empty() {
-                candidates
-                    .into_iter()
-                    .enumerate()
-                    .map(|(index, candidate)| StringMatch {
-                        candidate_id: index,
-                        string: candidate.string,
-                        positions: Vec::new(),
-                        score: 0.0,
-                    })
-                    .collect()
-            } else {
-                fuzzy::match_strings(
-                    &candidates,
-                    &query,
-                    true,
-                    10000,
-                    &Default::default(),
-                    cx.background_executor().clone(),
-                )
-                .await
-            };
-            picker
-                .update(&mut cx, |picker, _| {
-                    let delegate = &mut picker.delegate;
-                    delegate.matches = matches;
-                    if delegate.matches.is_empty() {
-                        delegate.selected_index = 0;
-                    } else {
-                        delegate.selected_index =
-                            core::cmp::min(delegate.selected_index, delegate.matches.len() - 1);
-                    }
-                    delegate.last_query = query;
-                })
-                .log_err();
-        })
-    }
-
-    fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
-        let current_pick = self.selected_index();
-        let Some(current_pick) = self
-            .matches
-            .get(current_pick)
-            .map(|pick| pick.string.clone())
-        else {
-            return;
-        };
-        cx.spawn(|picker, mut cx| async move {
-            picker
-                .update(&mut cx, |this, cx| {
-                    let project = this.delegate.workspace.read(cx).project().read(cx);
-                    let mut cwd = project
-                        .visible_worktrees(cx)
-                        .next()
-                        .ok_or_else(|| anyhow!("There are no visisible worktrees."))?
-                        .read(cx)
-                        .abs_path()
-                        .to_path_buf();
-                    cwd.push(".git");
-                    let status = project
-                        .fs()
-                        .open_repo(&cwd)
-                        .ok_or_else(|| {
-                            anyhow!(
-                                "Could not open repository at path `{}`",
-                                cwd.as_os_str().to_string_lossy()
-                            )
-                        })?
-                        .lock()
-                        .change_branch(&current_pick);
-                    if status.is_err() {
-                        this.delegate.display_error_toast(format!("Failed to checkout branch '{current_pick}', check for conflicts or unstashed files"), cx);
-                        status?;
-                    }
-                    cx.emit(DismissEvent);
-
-                    Ok::<(), anyhow::Error>(())
-                })
-                .log_err();
-        })
-        .detach();
-    }
-
-    fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
-        cx.emit(DismissEvent);
-    }
-
-    fn render_match(
-        &self,
-        ix: usize,
-        selected: bool,
-        _cx: &mut ViewContext<Picker<Self>>,
-    ) -> Option<Self::ListItem> {
-        let hit = &self.matches[ix];
-        let shortened_branch_name =
-            util::truncate_and_trailoff(&hit.string, self.branch_name_trailoff_after);
-        let highlights: Vec<_> = hit
-            .positions
-            .iter()
-            .filter(|index| index < &&self.branch_name_trailoff_after)
-            .copied()
-            .collect();
-        Some(
-            ListItem::new(SharedString::from(format!("vcs-menu-{ix}")))
-                .inset(true)
-                .spacing(ListItemSpacing::Sparse)
-                .selected(selected)
-                .start_slot(HighlightedLabel::new(shortened_branch_name, highlights)),
-        )
-    }
-    fn render_header(&self, _: &mut ViewContext<Picker<Self>>) -> Option<AnyElement> {
-        let label = if self.last_query.is_empty() {
-            h_stack()
-                .ml_3()
-                .child(Label::new("Recent branches").size(LabelSize::Small))
-        } else {
-            let match_label = self.matches.is_empty().not().then(|| {
-                let suffix = if self.matches.len() == 1 { "" } else { "es" };
-                Label::new(format!("{} match{}", self.matches.len(), suffix)).size(LabelSize::Small)
-            });
-            h_stack()
-                .px_3()
-                .h_full()
-                .justify_between()
-                .child(Label::new("Branches").size(LabelSize::Small))
-                .children(match_label)
-        };
-        Some(label.into_any())
-    }
-    fn render_footer(&self, cx: &mut ViewContext<Picker<Self>>) -> Option<AnyElement> {
-        if self.last_query.is_empty() {
-            return None;
-        }
-
-        Some(
-            h_stack().mr_3().pb_2().child(h_stack().w_full()).child(
-            Button::new("branch-picker-create-branch-button", "Create branch").on_click(
-                cx.listener(|_, _, cx| {
-                    cx.spawn(|picker, mut cx| async move {
-                                        picker.update(&mut cx, |this, cx| {
-                                            let project = this.delegate.workspace.read(cx).project().read(cx);
-                                            let current_pick = &this.delegate.last_query;
-                                            let mut cwd = project
-                                            .visible_worktrees(cx)
-                                            .next()
-                                            .ok_or_else(|| anyhow!("There are no visisible worktrees."))?
-                                            .read(cx)
-                                            .abs_path()
-                                            .to_path_buf();
-                                            cwd.push(".git");
-                                            let repo = project
-                                                .fs()
-                                                .open_repo(&cwd)
-                                                .ok_or_else(|| anyhow!("Could not open repository at path `{}`", cwd.as_os_str().to_string_lossy()))?;
-                                            let repo = repo
-                                                .lock();
-                                            let status = repo
-                                                .create_branch(&current_pick);
-                                            if status.is_err() {
-                                                this.delegate.display_error_toast(format!("Failed to create branch '{current_pick}', check for conflicts or unstashed files"), cx);
-                                                status?;
-                                            }
-                                            let status = repo.change_branch(&current_pick);
-                                            if status.is_err() {
-                                                this.delegate.display_error_toast(format!("Failed to chec branch '{current_pick}', check for conflicts or unstashed files"), cx);
-                                                status?;
-                                            }
-                                            this.cancel(&Default::default(), cx);
-                                            Ok::<(), anyhow::Error>(())
-                                })
-
-                    }).detach_and_log_err(cx);
-                }),
-            ).style(ui::ButtonStyle::Filled)).into_any_element(),
-        )
-    }
-}

crates/vim/Cargo.toml 🔗

@@ -33,9 +33,9 @@ search = { path = "../search" }
 settings = { path = "../settings" }
 workspace = { path = "../workspace" }
 theme = { path = "../theme" }
-language_selector = { path = "../language_selector"}
+ui = { path = "../ui"}
 diagnostics = { path = "../diagnostics" }
-zed-actions = { path = "../zed-actions" }
+zed_actions = { path = "../zed_actions" }
 
 [dev-dependencies]
 indoc.workspace = true

crates/vim/src/command.rs 🔗

@@ -1,6 +1,6 @@
 use command_palette::CommandInterceptResult;
 use editor::{SortLinesCaseInsensitive, SortLinesCaseSensitive};
-use gpui::{impl_actions, Action, AppContext};
+use gpui::{impl_actions, Action, AppContext, ViewContext};
 use serde_derive::Deserialize;
 use workspace::{SaveIntent, Workspace};
 
@@ -22,8 +22,8 @@ pub struct GoToLine {
 
 impl_actions!(vim, [GoToLine]);
 
-pub fn init(cx: &mut AppContext) {
-    cx.add_action(|_: &mut Workspace, action: &GoToLine, cx| {
+pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+    workspace.register_action(|_: &mut Workspace, action: &GoToLine, cx| {
         Vim::update(cx, |vim, cx| {
             vim.switch_mode(Mode::Normal, false, cx);
             move_cursor(vim, Motion::StartOfDocument, Some(action.line as usize), cx);
@@ -293,14 +293,11 @@ mod test {
     use std::path::Path;
 
     use crate::test::{NeovimBackedTestContext, VimTestContext};
-    use gpui::{executor::Foreground, TestAppContext};
+    use gpui::TestAppContext;
     use indoc::indoc;
 
     #[gpui::test]
     async fn test_command_basics(cx: &mut TestAppContext) {
-        if let Foreground::Deterministic { cx_id: _, executor } = cx.foreground().as_ref() {
-            executor.run_until_parked();
-        }
         let mut cx = NeovimBackedTestContext::new(cx).await;
 
         cx.set_shared_state(indoc! {"
@@ -410,15 +407,14 @@ mod test {
         // conflict!
         cx.simulate_keystrokes(["i", "@", "escape"]);
         cx.simulate_keystrokes([":", "w", "enter"]);
-        let window = cx.window;
-        assert!(window.has_pending_prompt(cx.cx));
+        assert!(cx.has_pending_prompt());
         // "Cancel"
-        window.simulate_prompt_answer(0, cx.cx);
+        cx.simulate_prompt_answer(0);
         assert_eq!(fs.load(&path).await.unwrap(), "oops\n");
-        assert!(!window.has_pending_prompt(cx.cx));
+        assert!(!cx.has_pending_prompt());
         // force overwrite
         cx.simulate_keystrokes([":", "w", "!", "enter"]);
-        assert!(!window.has_pending_prompt(cx.cx));
+        assert!(!cx.has_pending_prompt());
         assert_eq!(fs.load(&path).await.unwrap(), "@@\n");
     }
 

crates/vim/src/editor_events.rs 🔗

@@ -1,65 +1,67 @@
-use crate::{Vim, VimEvent};
-use editor::{EditorBlurred, EditorFocused, EditorReleased};
-use gpui::AppContext;
+use crate::Vim;
+use editor::{Editor, EditorEvent};
+use gpui::{AppContext, Entity, EntityId, View, ViewContext, WindowContext};
 
 pub fn init(cx: &mut AppContext) {
-    cx.subscribe_global(focused).detach();
-    cx.subscribe_global(blurred).detach();
-    cx.subscribe_global(released).detach();
+    cx.observe_new_views(|_, cx: &mut ViewContext<Editor>| {
+        let editor = cx.view().clone();
+        cx.subscribe(&editor, |_, editor, event: &EditorEvent, cx| match event {
+            EditorEvent::Focused => cx.window_context().defer(|cx| focused(editor, cx)),
+            EditorEvent::Blurred => cx.window_context().defer(|cx| blurred(editor, cx)),
+            _ => {}
+        })
+        .detach();
+
+        let id = cx.view().entity_id();
+        cx.on_release(move |_, _, cx| released(id, cx)).detach();
+    })
+    .detach();
 }
 
-fn focused(EditorFocused(editor): &EditorFocused, cx: &mut AppContext) {
-    if let Some(previously_active_editor) = Vim::read(cx).active_editor.clone() {
-        previously_active_editor.window().update(cx, |cx| {
-            Vim::update(cx, |vim, cx| {
-                vim.update_active_editor(cx, |previously_active_editor, cx| {
-                    vim.unhook_vim_settings(previously_active_editor, cx)
-                });
+fn focused(editor: View<Editor>, cx: &mut WindowContext) {
+    if Vim::read(cx).active_editor.clone().is_some() {
+        Vim::update(cx, |vim, cx| {
+            vim.update_active_editor(cx, |previously_active_editor, cx| {
+                vim.unhook_vim_settings(previously_active_editor, cx)
             });
         });
     }
 
-    editor.window().update(cx, |cx| {
-        Vim::update(cx, |vim, cx| {
-            vim.set_active_editor(editor.clone(), cx);
-            if vim.enabled {
-                cx.emit_global(VimEvent::ModeChanged {
-                    mode: vim.state().mode,
-                });
-            }
-        });
+    Vim::update(cx, |vim, cx| {
+        vim.set_active_editor(editor.clone(), cx);
     });
 }
 
-fn blurred(EditorBlurred(editor): &EditorBlurred, cx: &mut AppContext) {
-    editor.window().update(cx, |cx| {
-        Vim::update(cx, |vim, cx| {
-            vim.workspace_state.recording = false;
-            vim.workspace_state.recorded_actions.clear();
-            if let Some(previous_editor) = vim.active_editor.clone() {
-                if previous_editor == editor.clone() {
-                    vim.clear_operator(cx);
-                    vim.active_editor = None;
-                    vim.editor_subscription = None;
-                }
+fn blurred(editor: View<Editor>, cx: &mut WindowContext) {
+    Vim::update(cx, |vim, cx| {
+        vim.workspace_state.recording = false;
+        vim.workspace_state.recorded_actions.clear();
+        if let Some(previous_editor) = vim.active_editor.clone() {
+            if previous_editor
+                .upgrade()
+                .is_some_and(|previous| previous == editor.clone())
+            {
+                vim.clear_operator(cx);
+                vim.active_editor = None;
+                vim.editor_subscription = None;
             }
+        }
 
-            editor.update(cx, |editor, cx| vim.unhook_vim_settings(editor, cx))
-        });
+        editor.update(cx, |editor, cx| vim.unhook_vim_settings(editor, cx))
     });
 }
 
-fn released(EditorReleased(editor): &EditorReleased, cx: &mut AppContext) {
-    editor.window().update(cx, |cx| {
-        Vim::update(cx, |vim, _| {
-            if let Some(previous_editor) = vim.active_editor.clone() {
-                if previous_editor == editor.clone() {
-                    vim.active_editor = None;
-                    vim.editor_subscription = None;
-                }
-            }
-            vim.editor_states.remove(&editor.id())
-        });
+fn released(entity_id: EntityId, cx: &mut AppContext) {
+    cx.update_global(|vim: &mut Vim, _| {
+        if vim
+            .active_editor
+            .as_ref()
+            .is_some_and(|previous| previous.entity_id() == entity_id)
+        {
+            vim.active_editor = None;
+            vim.editor_subscription = None;
+        }
+        vim.editor_states.remove(&entity_id)
     });
 }
 
@@ -67,7 +69,7 @@ fn released(EditorReleased(editor): &EditorReleased, cx: &mut AppContext) {
 mod test {
     use crate::{test::VimTestContext, Vim};
     use editor::Editor;
-    use gpui::View;
+    use gpui::{Context, Entity};
     use language::Buffer;
 
     // regression test for blur called with a different active editor
@@ -75,18 +77,28 @@ mod test {
     async fn test_blur_focus(cx: &mut gpui::TestAppContext) {
         let mut cx = VimTestContext::new(cx, true).await;
 
-        let buffer = cx.add_model(|_| Buffer::new(0, 0, "a = 1\nb = 2\n"));
+        let buffer = cx.new_model(|_| Buffer::new(0, 0, "a = 1\nb = 2\n"));
         let window2 = cx.add_window(|cx| Editor::for_buffer(buffer, None, cx));
-        let editor2 = cx.read(|cx| window2.root(cx)).unwrap();
+        let editor2 = cx
+            .update(|cx| {
+                window2.update(cx, |_, cx| {
+                    cx.focus_self();
+                    cx.view().clone()
+                })
+            })
+            .unwrap();
 
         cx.update(|cx| {
             let vim = Vim::read(cx);
-            assert_eq!(vim.active_editor.unwrap().id(), editor2.id())
+            assert_eq!(
+                vim.active_editor.as_ref().unwrap().entity_id(),
+                editor2.entity_id(),
+            )
         });
 
         // no panic when blurring an editor in a different window.
         cx.update_editor(|editor1, cx| {
-            editor1.focus_out(cx.handle().into_any(), cx);
+            editor1.handle_blur(cx);
         });
     }
 }

crates/vim/src/insert.rs 🔗

@@ -1,13 +1,13 @@
 use crate::{normal::repeat, state::Mode, Vim};
 use editor::{scroll::autoscroll::Autoscroll, Bias};
-use gpui::{actions, Action, AppContext, ViewContext};
+use gpui::{actions, Action, ViewContext};
 use language::SelectionGoal;
 use workspace::Workspace;
 
 actions!(vim, [NormalBefore]);
 
-pub fn init(cx: &mut AppContext) {
-    cx.add_action(normal_before);
+pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+    workspace.register_action(normal_before);
 }
 
 fn normal_before(_: &mut Workspace, action: &NormalBefore, cx: &mut ViewContext<Workspace>) {
@@ -38,10 +38,6 @@ fn normal_before(_: &mut Workspace, action: &NormalBefore, cx: &mut ViewContext<
 
 #[cfg(test)]
 mod test {
-    use std::sync::Arc;
-
-    use gpui::executor::Deterministic;
-
     use crate::{
         state::Mode,
         test::{NeovimBackedTestContext, VimTestContext},
@@ -60,76 +56,70 @@ mod test {
     }
 
     #[gpui::test]
-    async fn test_insert_with_counts(
-        deterministic: Arc<Deterministic>,
-        cx: &mut gpui::TestAppContext,
-    ) {
+    async fn test_insert_with_counts(cx: &mut gpui::TestAppContext) {
         let mut cx = NeovimBackedTestContext::new(cx).await;
 
         cx.set_shared_state("ˇhello\n").await;
         cx.simulate_shared_keystrokes(["5", "i", "-", "escape"])
             .await;
-        deterministic.run_until_parked();
+        cx.run_until_parked();
         cx.assert_shared_state("----ˇ-hello\n").await;
 
         cx.set_shared_state("ˇhello\n").await;
         cx.simulate_shared_keystrokes(["5", "a", "-", "escape"])
             .await;
-        deterministic.run_until_parked();
+        cx.run_until_parked();
         cx.assert_shared_state("h----ˇ-ello\n").await;
 
         cx.simulate_shared_keystrokes(["4", "shift-i", "-", "escape"])
             .await;
-        deterministic.run_until_parked();
+        cx.run_until_parked();
         cx.assert_shared_state("---ˇ-h-----ello\n").await;
 
         cx.simulate_shared_keystrokes(["3", "shift-a", "-", "escape"])
             .await;
-        deterministic.run_until_parked();
+        cx.run_until_parked();
         cx.assert_shared_state("----h-----ello--ˇ-\n").await;
 
         cx.set_shared_state("ˇhello\n").await;
         cx.simulate_shared_keystrokes(["3", "o", "o", "i", "escape"])
             .await;
-        deterministic.run_until_parked();
+        cx.run_until_parked();
         cx.assert_shared_state("hello\noi\noi\noˇi\n").await;
 
         cx.set_shared_state("ˇhello\n").await;
         cx.simulate_shared_keystrokes(["3", "shift-o", "o", "i", "escape"])
             .await;
-        deterministic.run_until_parked();
+        cx.run_until_parked();
         cx.assert_shared_state("oi\noi\noˇi\nhello\n").await;
     }
 
     #[gpui::test]
-    async fn test_insert_with_repeat(
-        deterministic: Arc<Deterministic>,
-        cx: &mut gpui::TestAppContext,
-    ) {
+    async fn test_insert_with_repeat(cx: &mut gpui::TestAppContext) {
         let mut cx = NeovimBackedTestContext::new(cx).await;
 
         cx.set_shared_state("ˇhello\n").await;
         cx.simulate_shared_keystrokes(["3", "i", "-", "escape"])
             .await;
-        deterministic.run_until_parked();
+        cx.run_until_parked();
         cx.assert_shared_state("--ˇ-hello\n").await;
         cx.simulate_shared_keystrokes(["."]).await;
-        deterministic.run_until_parked();
+        cx.run_until_parked();
         cx.assert_shared_state("----ˇ--hello\n").await;
         cx.simulate_shared_keystrokes(["2", "."]).await;
-        deterministic.run_until_parked();
+        cx.run_until_parked();
         cx.assert_shared_state("-----ˇ---hello\n").await;
 
         cx.set_shared_state("ˇhello\n").await;
         cx.simulate_shared_keystrokes(["2", "o", "k", "k", "escape"])
             .await;
-        deterministic.run_until_parked();
+        cx.run_until_parked();
         cx.assert_shared_state("hello\nkk\nkˇk\n").await;
         cx.simulate_shared_keystrokes(["."]).await;
-        deterministic.run_until_parked();
+        cx.run_until_parked();
         cx.assert_shared_state("hello\nkk\nkk\nkk\nkˇk\n").await;
         cx.simulate_shared_keystrokes(["1", "."]).await;
-        deterministic.run_until_parked();
+        cx.run_until_parked();
         cx.assert_shared_state("hello\nkk\nkk\nkk\nkk\nkˇk\n").await;
     }
 }

crates/vim/src/mode_indicator.rs 🔗

@@ -1,58 +1,40 @@
-use gpui::{
-    elements::{Empty, Label},
-    AnyElement, Element, Entity, Subscription, View, ViewContext,
-};
+use gpui::{div, Element, Render, Subscription, ViewContext};
 use settings::SettingsStore;
-use workspace::{item::ItemHandle, StatusItemView};
+use workspace::{item::ItemHandle, ui::prelude::*, StatusItemView};
 
-use crate::{state::Mode, Vim, VimEvent, VimModeSetting};
+use crate::{state::Mode, Vim};
 
 pub struct ModeIndicator {
     pub mode: Option<Mode>,
-    _subscription: Subscription,
+    _subscriptions: Vec<Subscription>,
 }
 
 impl ModeIndicator {
     pub fn new(cx: &mut ViewContext<Self>) -> Self {
-        let handle = cx.handle().downgrade();
+        let _subscriptions = vec![
+            cx.observe_global::<Vim>(|this, cx| this.update_mode(cx)),
+            cx.observe_global::<SettingsStore>(|this, cx| this.update_mode(cx)),
+        ];
 
-        let _subscription = cx.subscribe_global::<VimEvent, _>(move |&event, cx| {
-            if let Some(mode_indicator) = handle.upgrade(cx) {
-                match event {
-                    VimEvent::ModeChanged { mode } => {
-                        mode_indicator.window().update(cx, |cx| {
-                            mode_indicator.update(cx, move |mode_indicator, cx| {
-                                mode_indicator.set_mode(mode, cx);
-                            })
-                        });
-                    }
-                }
-            }
-        });
-
-        cx.observe_global::<SettingsStore, _>(move |mode_indicator, cx| {
-            if settings::get::<VimModeSetting>(cx).0 {
-                mode_indicator.mode = cx
-                    .has_global::<Vim>()
-                    .then(|| cx.global::<Vim>().state().mode);
-            } else {
-                mode_indicator.mode.take();
-            }
-        })
-        .detach();
+        let mut this = Self {
+            mode: None,
+            _subscriptions,
+        };
+        this.update_mode(cx);
+        this
+    }
 
+    fn update_mode(&mut self, cx: &mut ViewContext<Self>) {
         // Vim doesn't exist in some tests
-        let mode = cx
-            .has_global::<Vim>()
-            .then(|| {
-                let vim = cx.global::<Vim>();
-                vim.enabled.then(|| vim.state().mode)
-            })
-            .flatten();
+        if !cx.has_global::<Vim>() {
+            return;
+        }
 
-        Self {
-            mode,
-            _subscription,
+        let vim = Vim::read(cx);
+        if vim.enabled {
+            self.mode = Some(vim.state().mode);
+        } else {
+            self.mode = None;
         }
     }
 
@@ -64,22 +46,12 @@ impl ModeIndicator {
     }
 }
 
-impl Entity for ModeIndicator {
-    type Event = ();
-}
-
-impl View for ModeIndicator {
-    fn ui_name() -> &'static str {
-        "ModeIndicatorView"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+impl Render for ModeIndicator {
+    fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
         let Some(mode) = self.mode.as_ref() else {
-            return Empty::new().into_any();
+            return div().into_any();
         };
 
-        let theme = &theme::current(cx).workspace.status_bar;
-
         let text = match mode {
             Mode::Normal => "-- NORMAL --",
             Mode::Insert => "-- INSERT --",
@@ -87,10 +59,7 @@ impl View for ModeIndicator {
             Mode::VisualLine => "-- VISUAL LINE --",
             Mode::VisualBlock => "-- VISUAL BLOCK --",
         };
-        Label::new(text, theme.vim_mode_indicator.text.clone())
-            .contained()
-            .with_style(theme.vim_mode_indicator.container)
-            .into_any()
+        Label::new(text).size(LabelSize::Small).into_any_element()
     }
 }
 

crates/vim/src/motion.rs 🔗

@@ -4,7 +4,7 @@ use editor::{
     movement::{self, find_boundary, find_preceding_boundary, FindRange, TextLayoutDetails},
     Bias, CharKind, DisplayPoint, ToOffset,
 };
-use gpui::{actions, impl_actions, AppContext, WindowContext};
+use gpui::{actions, impl_actions, px, ViewContext, WindowContext};
 use language::{Point, Selection, SelectionGoal};
 use serde::Deserialize;
 use workspace::Workspace;
@@ -105,6 +105,21 @@ struct RepeatFind {
     backwards: bool,
 }
 
+impl_actions!(
+    vim,
+    [
+        RepeatFind,
+        StartOfLine,
+        EndOfLine,
+        FirstNonWhitespace,
+        Down,
+        Up,
+        PreviousWordStart,
+        NextWordEnd,
+        NextWordStart
+    ]
+);
+
 actions!(
     vim,
     [
@@ -123,25 +138,12 @@ actions!(
         GoToColumn,
     ]
 );
-impl_actions!(
-    vim,
-    [
-        NextWordStart,
-        NextWordEnd,
-        PreviousWordStart,
-        RepeatFind,
-        Up,
-        Down,
-        FirstNonWhitespace,
-        EndOfLine,
-        StartOfLine,
-    ]
-);
 
-pub fn init(cx: &mut AppContext) {
-    cx.add_action(|_: &mut Workspace, _: &Left, cx: _| motion(Motion::Left, cx));
-    cx.add_action(|_: &mut Workspace, _: &Backspace, cx: _| motion(Motion::Backspace, cx));
-    cx.add_action(|_: &mut Workspace, action: &Down, cx: _| {
+pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+    workspace.register_action(|_: &mut Workspace, _: &Left, cx: _| motion(Motion::Left, cx));
+    workspace
+        .register_action(|_: &mut Workspace, _: &Backspace, cx: _| motion(Motion::Backspace, cx));
+    workspace.register_action(|_: &mut Workspace, action: &Down, cx: _| {
         motion(
             Motion::Down {
                 display_lines: action.display_lines,
@@ -149,7 +151,7 @@ pub fn init(cx: &mut AppContext) {
             cx,
         )
     });
-    cx.add_action(|_: &mut Workspace, action: &Up, cx: _| {
+    workspace.register_action(|_: &mut Workspace, action: &Up, cx: _| {
         motion(
             Motion::Up {
                 display_lines: action.display_lines,
@@ -157,8 +159,8 @@ pub fn init(cx: &mut AppContext) {
             cx,
         )
     });
-    cx.add_action(|_: &mut Workspace, _: &Right, cx: _| motion(Motion::Right, cx));
-    cx.add_action(|_: &mut Workspace, action: &FirstNonWhitespace, cx: _| {
+    workspace.register_action(|_: &mut Workspace, _: &Right, cx: _| motion(Motion::Right, cx));
+    workspace.register_action(|_: &mut Workspace, action: &FirstNonWhitespace, cx: _| {
         motion(
             Motion::FirstNonWhitespace {
                 display_lines: action.display_lines,
@@ -166,7 +168,7 @@ pub fn init(cx: &mut AppContext) {
             cx,
         )
     });
-    cx.add_action(|_: &mut Workspace, action: &StartOfLine, cx: _| {
+    workspace.register_action(|_: &mut Workspace, action: &StartOfLine, cx: _| {
         motion(
             Motion::StartOfLine {
                 display_lines: action.display_lines,
@@ -174,7 +176,7 @@ pub fn init(cx: &mut AppContext) {
             cx,
         )
     });
-    cx.add_action(|_: &mut Workspace, action: &EndOfLine, cx: _| {
+    workspace.register_action(|_: &mut Workspace, action: &EndOfLine, cx: _| {
         motion(
             Motion::EndOfLine {
                 display_lines: action.display_lines,
@@ -182,45 +184,53 @@ pub fn init(cx: &mut AppContext) {
             cx,
         )
     });
-    cx.add_action(|_: &mut Workspace, _: &CurrentLine, cx: _| motion(Motion::CurrentLine, cx));
-    cx.add_action(|_: &mut Workspace, _: &StartOfParagraph, cx: _| {
+    workspace.register_action(|_: &mut Workspace, _: &CurrentLine, cx: _| {
+        motion(Motion::CurrentLine, cx)
+    });
+    workspace.register_action(|_: &mut Workspace, _: &StartOfParagraph, cx: _| {
         motion(Motion::StartOfParagraph, cx)
     });
-    cx.add_action(|_: &mut Workspace, _: &EndOfParagraph, cx: _| {
+    workspace.register_action(|_: &mut Workspace, _: &EndOfParagraph, cx: _| {
         motion(Motion::EndOfParagraph, cx)
     });
-    cx.add_action(|_: &mut Workspace, _: &StartOfDocument, cx: _| {
+    workspace.register_action(|_: &mut Workspace, _: &StartOfDocument, cx: _| {
         motion(Motion::StartOfDocument, cx)
     });
-    cx.add_action(|_: &mut Workspace, _: &EndOfDocument, cx: _| motion(Motion::EndOfDocument, cx));
-    cx.add_action(|_: &mut Workspace, _: &Matching, cx: _| motion(Motion::Matching, cx));
+    workspace.register_action(|_: &mut Workspace, _: &EndOfDocument, cx: _| {
+        motion(Motion::EndOfDocument, cx)
+    });
+    workspace
+        .register_action(|_: &mut Workspace, _: &Matching, cx: _| motion(Motion::Matching, cx));
 
-    cx.add_action(
+    workspace.register_action(
         |_: &mut Workspace, &NextWordStart { ignore_punctuation }: &NextWordStart, cx: _| {
             motion(Motion::NextWordStart { ignore_punctuation }, cx)
         },
     );
-    cx.add_action(
+    workspace.register_action(
         |_: &mut Workspace, &NextWordEnd { ignore_punctuation }: &NextWordEnd, cx: _| {
             motion(Motion::NextWordEnd { ignore_punctuation }, cx)
         },
     );
-    cx.add_action(
+    workspace.register_action(
         |_: &mut Workspace,
          &PreviousWordStart { ignore_punctuation }: &PreviousWordStart,
          cx: _| { motion(Motion::PreviousWordStart { ignore_punctuation }, cx) },
     );
-    cx.add_action(|_: &mut Workspace, &NextLineStart, cx: _| motion(Motion::NextLineStart, cx));
-    cx.add_action(|_: &mut Workspace, &StartOfLineDownward, cx: _| {
+    workspace.register_action(|_: &mut Workspace, &NextLineStart, cx: _| {
+        motion(Motion::NextLineStart, cx)
+    });
+    workspace.register_action(|_: &mut Workspace, &StartOfLineDownward, cx: _| {
         motion(Motion::StartOfLineDownward, cx)
     });
-    cx.add_action(|_: &mut Workspace, &EndOfLineDownward, cx: _| {
+    workspace.register_action(|_: &mut Workspace, &EndOfLineDownward, cx: _| {
         motion(Motion::EndOfLineDownward, cx)
     });
-    cx.add_action(|_: &mut Workspace, &GoToColumn, cx: _| motion(Motion::GoToColumn, cx));
-    cx.add_action(|_: &mut Workspace, action: &RepeatFind, cx: _| {
+    workspace
+        .register_action(|_: &mut Workspace, &GoToColumn, cx: _| motion(Motion::GoToColumn, cx));
+    workspace.register_action(|_: &mut Workspace, action: &RepeatFind, cx: _| {
         repeat_motion(action.backwards, cx)
-    })
+    });
 }
 
 pub(crate) fn motion(motion: Motion, cx: &mut WindowContext) {
@@ -578,9 +588,9 @@ fn up_down_buffer_rows(
         SelectionGoal::HorizontalRange { end, .. } => (select_nth_wrapped_row, end),
         SelectionGoal::HorizontalPosition(x) => (select_nth_wrapped_row, x),
         _ => {
-            let x = map.x_for_point(point, text_layout_details);
-            goal = SelectionGoal::WrappedHorizontalPosition((select_nth_wrapped_row, x));
-            (select_nth_wrapped_row, x)
+            let x = map.x_for_display_point(point, text_layout_details);
+            goal = SelectionGoal::WrappedHorizontalPosition((select_nth_wrapped_row, x.0));
+            (select_nth_wrapped_row, x.0)
         }
     };
 
@@ -608,7 +618,7 @@ fn up_down_buffer_rows(
     }
 
     let new_col = if i == goal_wrap {
-        map.column_for_x(begin_folded_line.row(), goal_x, text_layout_details)
+        map.display_column_for_x(begin_folded_line.row(), px(goal_x), text_layout_details)
     } else {
         map.line_len(begin_folded_line.row())
     };
@@ -943,7 +953,6 @@ pub(crate) fn next_line_end(
 }
 
 #[cfg(test)]
-
 mod test {
 
     use crate::test::NeovimBackedTestContext;

crates/vim/src/normal.rs 🔗

@@ -20,7 +20,7 @@ use crate::{
 use collections::HashSet;
 use editor::scroll::autoscroll::Autoscroll;
 use editor::{Bias, DisplayPoint};
-use gpui::{actions, AppContext, ViewContext, WindowContext};
+use gpui::{actions, ViewContext, WindowContext};
 use language::SelectionGoal;
 use log::error;
 use workspace::Workspace;
@@ -52,38 +52,31 @@ actions!(
     ]
 );
 
-pub fn init(cx: &mut AppContext) {
-    paste::init(cx);
-    repeat::init(cx);
-    scroll::init(cx);
-    search::init(cx);
-    substitute::init(cx);
-    increment::init(cx);
-
-    cx.add_action(insert_after);
-    cx.add_action(insert_before);
-    cx.add_action(insert_first_non_whitespace);
-    cx.add_action(insert_end_of_line);
-    cx.add_action(insert_line_above);
-    cx.add_action(insert_line_below);
-    cx.add_action(change_case);
-    cx.add_action(yank_line);
-
-    cx.add_action(|_: &mut Workspace, _: &DeleteLeft, cx| {
+pub(crate) fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
+    workspace.register_action(insert_after);
+    workspace.register_action(insert_before);
+    workspace.register_action(insert_first_non_whitespace);
+    workspace.register_action(insert_end_of_line);
+    workspace.register_action(insert_line_above);
+    workspace.register_action(insert_line_below);
+    workspace.register_action(change_case);
+    workspace.register_action(yank_line);
+
+    workspace.register_action(|_: &mut Workspace, _: &DeleteLeft, cx| {
         Vim::update(cx, |vim, cx| {
             vim.record_current_action(cx);
             let times = vim.take_count(cx);
             delete_motion(vim, Motion::Left, times, cx);
         })
     });
-    cx.add_action(|_: &mut Workspace, _: &DeleteRight, cx| {
+    workspace.register_action(|_: &mut Workspace, _: &DeleteRight, cx| {
         Vim::update(cx, |vim, cx| {
             vim.record_current_action(cx);
             let times = vim.take_count(cx);
             delete_motion(vim, Motion::Right, times, cx);
         })
     });
-    cx.add_action(|_: &mut Workspace, _: &ChangeToEndOfLine, cx| {
+    workspace.register_action(|_: &mut Workspace, _: &ChangeToEndOfLine, cx| {
         Vim::update(cx, |vim, cx| {
             vim.start_recording(cx);
             let times = vim.take_count(cx);
@@ -97,7 +90,7 @@ pub fn init(cx: &mut AppContext) {
             );
         })
     });
-    cx.add_action(|_: &mut Workspace, _: &DeleteToEndOfLine, cx| {
+    workspace.register_action(|_: &mut Workspace, _: &DeleteToEndOfLine, cx| {
         Vim::update(cx, |vim, cx| {
             vim.record_current_action(cx);
             let times = vim.take_count(cx);
@@ -111,7 +104,7 @@ pub fn init(cx: &mut AppContext) {
             );
         })
     });
-    cx.add_action(|_: &mut Workspace, _: &JoinLines, cx| {
+    workspace.register_action(|_: &mut Workspace, _: &JoinLines, cx| {
         Vim::update(cx, |vim, cx| {
             vim.record_current_action(cx);
             let mut times = vim.take_count(cx).unwrap_or(1);
@@ -129,8 +122,15 @@ pub fn init(cx: &mut AppContext) {
                     }
                 })
             })
-        })
-    })
+        });
+    });
+
+    paste::register(workspace, cx);
+    repeat::register(workspace, cx);
+    scroll::register(workspace, cx);
+    search::register(workspace, cx);
+    substitute::register(workspace, cx);
+    increment::register(workspace, cx);
 }
 
 pub fn normal_motion(

crates/vim/src/normal/increment.rs 🔗

@@ -1,7 +1,7 @@
 use std::ops::Range;
 
 use editor::{scroll::autoscroll::Autoscroll, MultiBufferSnapshot, ToOffset, ToPoint};
-use gpui::{impl_actions, AppContext, WindowContext};
+use gpui::{impl_actions, ViewContext, WindowContext};
 use language::{Bias, Point};
 use serde::Deserialize;
 use workspace::Workspace;
@@ -24,8 +24,8 @@ struct Decrement {
 
 impl_actions!(vim, [Increment, Decrement]);
 
-pub fn init(cx: &mut AppContext) {
-    cx.add_action(|_: &mut Workspace, action: &Increment, cx| {
+pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+    workspace.register_action(|_: &mut Workspace, action: &Increment, cx| {
         Vim::update(cx, |vim, cx| {
             vim.record_current_action(cx);
             let count = vim.take_count(cx).unwrap_or(1);
@@ -33,7 +33,7 @@ pub fn init(cx: &mut AppContext) {
             increment(vim, count as i32, step, cx)
         })
     });
-    cx.add_action(|_: &mut Workspace, action: &Decrement, cx| {
+    workspace.register_action(|_: &mut Workspace, action: &Decrement, cx| {
         Vim::update(cx, |vim, cx| {
             vim.record_current_action(cx);
             let count = vim.take_count(cx).unwrap_or(1);

crates/vim/src/normal/paste.rs 🔗

@@ -4,7 +4,7 @@ use editor::{
     display_map::ToDisplayPoint, movement, scroll::autoscroll::Autoscroll, ClipboardSelection,
     DisplayPoint,
 };
-use gpui::{impl_actions, AppContext, ViewContext};
+use gpui::{impl_actions, ViewContext};
 use language::{Bias, SelectionGoal};
 use serde::Deserialize;
 use workspace::Workspace;
@@ -22,8 +22,8 @@ struct Paste {
 
 impl_actions!(vim, [Paste]);
 
-pub(crate) fn init(cx: &mut AppContext) {
-    cx.add_action(paste);
+pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+    workspace.register_action(paste);
 }
 
 fn paste(_: &mut Workspace, action: &Paste, cx: &mut ViewContext<Workspace>) {

crates/vim/src/normal/repeat.rs 🔗

@@ -5,14 +5,14 @@ use crate::{
     visual::visual_motion,
     Vim,
 };
-use gpui::{actions, Action, AppContext, WindowContext};
+use gpui::{actions, Action, ViewContext, WindowContext};
 use workspace::Workspace;
 
-actions!(vim, [Repeat, EndRepeat,]);
+actions!(vim, [Repeat, EndRepeat]);
 
 fn should_replay(action: &Box<dyn Action>) -> bool {
     // skip so that we don't leave the character palette open
-    if editor::ShowCharacterPalette.id() == action.id() {
+    if editor::ShowCharacterPalette.partial_eq(&**action) {
         return false;
     }
     true
@@ -21,14 +21,14 @@ fn should_replay(action: &Box<dyn Action>) -> bool {
 fn repeatable_insert(action: &ReplayableAction) -> Option<Box<dyn Action>> {
     match action {
         ReplayableAction::Action(action) => {
-            if super::InsertBefore.id() == action.id()
-                || super::InsertAfter.id() == action.id()
-                || super::InsertFirstNonWhitespace.id() == action.id()
-                || super::InsertEndOfLine.id() == action.id()
+            if super::InsertBefore.partial_eq(&**action)
+                || super::InsertAfter.partial_eq(&**action)
+                || super::InsertFirstNonWhitespace.partial_eq(&**action)
+                || super::InsertEndOfLine.partial_eq(&**action)
             {
                 Some(super::InsertBefore.boxed_clone())
-            } else if super::InsertLineAbove.id() == action.id()
-                || super::InsertLineBelow.id() == action.id()
+            } else if super::InsertLineAbove.partial_eq(&**action)
+                || super::InsertLineBelow.partial_eq(&**action)
             {
                 Some(super::InsertLineBelow.boxed_clone())
             } else {
@@ -39,15 +39,15 @@ fn repeatable_insert(action: &ReplayableAction) -> Option<Box<dyn Action>> {
     }
 }
 
-pub(crate) fn init(cx: &mut AppContext) {
-    cx.add_action(|_: &mut Workspace, _: &EndRepeat, cx| {
+pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+    workspace.register_action(|_: &mut Workspace, _: &EndRepeat, cx| {
         Vim::update(cx, |vim, cx| {
             vim.workspace_state.replaying = false;
             vim.switch_mode(Mode::Normal, false, cx)
         });
     });
 
-    cx.add_action(|_: &mut Workspace, _: &Repeat, cx| repeat(cx, false));
+    workspace.register_action(|_: &mut Workspace, _: &Repeat, cx| repeat(cx, false));
 }
 
 pub(crate) fn repeat(cx: &mut WindowContext, from_insert_mode: bool) {
@@ -142,7 +142,7 @@ pub(crate) fn repeat(cx: &mut WindowContext, from_insert_mode: bool) {
     // 3 times, instead it inserts the content thrice at the insert position.
     if let Some(to_repeat) = repeatable_insert(&actions[0]) {
         if let Some(ReplayableAction::Action(action)) = actions.last() {
-            if action.id() == NormalBefore.id() {
+            if NormalBefore.partial_eq(&**action) {
                 actions.pop();
             }
         }
@@ -166,50 +166,43 @@ pub(crate) fn repeat(cx: &mut WindowContext, from_insert_mode: bool) {
     }
 
     Vim::update(cx, |vim, _| vim.workspace_state.replaying = true);
-    let window = cx.window();
-    cx.app_context()
-        .spawn(move |mut cx| async move {
-            editor.update(&mut cx, |editor, _| {
-                editor.show_local_selections = false;
-            })?;
-            for action in actions {
-                match action {
-                    ReplayableAction::Action(action) => {
-                        if should_replay(&action) {
-                            window
-                                .dispatch_action(editor.id(), action.as_ref(), &mut cx)
-                                .ok_or_else(|| anyhow::anyhow!("window was closed"))
-                        } else {
-                            Ok(())
-                        }
+    let window = cx.window_handle();
+    cx.spawn(move |mut cx| async move {
+        editor.update(&mut cx, |editor, _| {
+            editor.show_local_selections = false;
+        })?;
+        for action in actions {
+            match action {
+                ReplayableAction::Action(action) => {
+                    if should_replay(&action) {
+                        window.update(&mut cx, |_, cx| cx.dispatch_action(action))
+                    } else {
+                        Ok(())
                     }
-                    ReplayableAction::Insertion {
-                        text,
-                        utf16_range_to_replace,
-                    } => editor.update(&mut cx, |editor, cx| {
-                        editor.replay_insert_event(&text, utf16_range_to_replace.clone(), cx)
-                    }),
-                }?
-            }
-            editor.update(&mut cx, |editor, _| {
-                editor.show_local_selections = true;
-            })?;
-            window
-                .dispatch_action(editor.id(), &EndRepeat, &mut cx)
-                .ok_or_else(|| anyhow::anyhow!("window was closed"))
-        })
-        .detach_and_log_err(cx);
+                }
+                ReplayableAction::Insertion {
+                    text,
+                    utf16_range_to_replace,
+                } => editor.update(&mut cx, |editor, cx| {
+                    editor.replay_insert_event(&text, utf16_range_to_replace.clone(), cx)
+                }),
+            }?
+        }
+        editor.update(&mut cx, |editor, _| {
+            editor.show_local_selections = true;
+        })?;
+        window.update(&mut cx, |_, cx| cx.dispatch_action(EndRepeat.boxed_clone()))
+    })
+    .detach_and_log_err(cx);
 }
 
 #[cfg(test)]
 mod test {
-    use std::sync::Arc;
-
     use editor::test::editor_lsp_test_context::EditorLspTestContext;
     use futures::StreamExt;
     use indoc::indoc;
 
-    use gpui::{executor::Deterministic, View};
+    use gpui::InputHandler;
 
     use crate::{
         state::Mode,
@@ -217,7 +210,7 @@ mod test {
     };
 
     #[gpui::test]
-    async fn test_dot_repeat(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
+    async fn test_dot_repeat(cx: &mut gpui::TestAppContext) {
         let mut cx = NeovimBackedTestContext::new(cx).await;
 
         // "o"
@@ -226,38 +219,32 @@ mod test {
             .await;
         cx.assert_shared_state("hello\nworlˇd").await;
         cx.simulate_shared_keystrokes(["."]).await;
-        deterministic.run_until_parked();
         cx.assert_shared_state("hello\nworld\nworlˇd").await;
 
         // "d"
         cx.simulate_shared_keystrokes(["^", "d", "f", "o"]).await;
         cx.simulate_shared_keystrokes(["g", "g", "."]).await;
-        deterministic.run_until_parked();
         cx.assert_shared_state("ˇ\nworld\nrld").await;
 
         // "p" (note that it pastes the current clipboard)
         cx.simulate_shared_keystrokes(["j", "y", "y", "p"]).await;
         cx.simulate_shared_keystrokes(["shift-g", "y", "y", "."])
             .await;
-        deterministic.run_until_parked();
         cx.assert_shared_state("\nworld\nworld\nrld\nˇrld").await;
 
         // "~" (note that counts apply to the action taken, not . itself)
         cx.set_shared_state("ˇthe quick brown fox").await;
         cx.simulate_shared_keystrokes(["2", "~", "."]).await;
-        deterministic.run_until_parked();
         cx.set_shared_state("THE ˇquick brown fox").await;
         cx.simulate_shared_keystrokes(["3", "."]).await;
-        deterministic.run_until_parked();
         cx.set_shared_state("THE QUIˇck brown fox").await;
-        deterministic.run_until_parked();
+        cx.run_until_parked();
         cx.simulate_shared_keystrokes(["."]).await;
-        deterministic.run_until_parked();
         cx.assert_shared_state("THE QUICK ˇbrown fox").await;
     }
 
     #[gpui::test]
-    async fn test_repeat_ime(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
+    async fn test_repeat_ime(cx: &mut gpui::TestAppContext) {
         let mut cx = VimTestContext::new(cx, true).await;
 
         cx.set_state("hˇllo", Mode::Normal);
@@ -271,15 +258,12 @@ mod test {
         cx.simulate_keystrokes(["escape"]);
         cx.assert_state("hˇällo", Mode::Normal);
         cx.simulate_keystrokes(["."]);
-        deterministic.run_until_parked();
         cx.assert_state("hˇäällo", Mode::Normal);
     }
 
     #[gpui::test]
-    async fn test_repeat_completion(
-        deterministic: Arc<Deterministic>,
-        cx: &mut gpui::TestAppContext,
-    ) {
+    async fn test_repeat_completion(cx: &mut gpui::TestAppContext) {
+        VimTestContext::init(cx);
         let cx = EditorLspTestContext::new_rust(
             lsp::ServerCapabilities {
                 completion_provider: Some(lsp::CompletionOptions {
@@ -340,7 +324,6 @@ mod test {
             Mode::Normal,
         );
         cx.simulate_keystrokes(["j", "."]);
-        deterministic.run_until_parked();
         cx.assert_state(
             indoc! {"
                 one.second!
@@ -352,7 +335,7 @@ mod test {
     }
 
     #[gpui::test]
-    async fn test_repeat_visual(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
+    async fn test_repeat_visual(cx: &mut gpui::TestAppContext) {
         let mut cx = NeovimBackedTestContext::new(cx).await;
 
         // single-line (3 columns)
@@ -371,7 +354,6 @@ mod test {
         })
         .await;
         cx.simulate_shared_keystrokes(["j", "w", "."]).await;
-        deterministic.run_until_parked();
         cx.assert_shared_state(indoc! {
             "o quick brown
             fox ˇops over
@@ -379,7 +361,6 @@ mod test {
         })
         .await;
         cx.simulate_shared_keystrokes(["f", "r", "."]).await;
-        deterministic.run_until_parked();
         cx.assert_shared_state(indoc! {
             "o quick brown
             fox ops oveˇothe lazy dog"
@@ -404,7 +385,6 @@ mod test {
         })
         .await;
         cx.simulate_shared_keystrokes(["."]).await;
-        deterministic.run_until_parked();
         cx.assert_shared_state(indoc! {
             "the ˇumps over
             fox jumps over
@@ -412,14 +392,12 @@ mod test {
         })
         .await;
         cx.simulate_shared_keystrokes(["w", "."]).await;
-        deterministic.run_until_parked();
         cx.assert_shared_state(indoc! {
             "the umps ˇumps over
             the lazy dog"
         })
         .await;
         cx.simulate_shared_keystrokes(["j", "."]).await;
-        deterministic.run_until_parked();
         cx.assert_shared_state(indoc! {
             "the umps umps over
             the ˇog"
@@ -442,7 +420,6 @@ mod test {
         })
         .await;
         cx.simulate_shared_keystrokes(["j", "4", "l", "."]).await;
-        deterministic.run_until_parked();
         cx.assert_shared_state(indoc! {
             "othe quick brown
             ofoxˇo jumps over
@@ -466,7 +443,6 @@ mod test {
         })
         .await;
         cx.simulate_shared_keystrokes(["j", "."]).await;
-        deterministic.run_until_parked();
         cx.assert_shared_state(indoc! {
             "o
             ˇo
@@ -476,10 +452,7 @@ mod test {
     }
 
     #[gpui::test]
-    async fn test_repeat_motion_counts(
-        deterministic: Arc<Deterministic>,
-        cx: &mut gpui::TestAppContext,
-    ) {
+    async fn test_repeat_motion_counts(cx: &mut gpui::TestAppContext) {
         let mut cx = NeovimBackedTestContext::new(cx).await;
 
         cx.set_shared_state(indoc! {
@@ -496,7 +469,6 @@ mod test {
         })
         .await;
         cx.simulate_shared_keystrokes(["j", "."]).await;
-        deterministic.run_until_parked();
         cx.assert_shared_state(indoc! {
             " brown
             ˇ over
@@ -504,7 +476,6 @@ mod test {
         })
         .await;
         cx.simulate_shared_keystrokes(["j", "2", "."]).await;
-        deterministic.run_until_parked();
         cx.assert_shared_state(indoc! {
             " brown
              over
@@ -514,15 +485,12 @@ mod test {
     }
 
     #[gpui::test]
-    async fn test_record_interrupted(
-        deterministic: Arc<Deterministic>,
-        cx: &mut gpui::TestAppContext,
-    ) {
+    async fn test_record_interrupted(cx: &mut gpui::TestAppContext) {
         let mut cx = VimTestContext::new(cx, true).await;
 
         cx.set_state("ˇhello\n", Mode::Normal);
-        cx.simulate_keystrokes(["4", "i", "j", "cmd-shift-p", "escape", "escape"]);
-        deterministic.run_until_parked();
+        cx.simulate_keystrokes(["4", "i", "j", "cmd-shift-p", "escape"]);
+        cx.simulate_keystrokes(["escape"]);
         cx.assert_state("ˇjhello\n", Mode::Normal);
     }
 }

crates/vim/src/normal/scroll.rs 🔗

@@ -4,29 +4,29 @@ use editor::{
     scroll::{scroll_amount::ScrollAmount, VERTICAL_SCROLL_MARGIN},
     DisplayPoint, Editor,
 };
-use gpui::{actions, AppContext, ViewContext};
+use gpui::{actions, ViewContext};
 use language::Bias;
 use workspace::Workspace;
 
 actions!(
     vim,
-    [LineUp, LineDown, ScrollUp, ScrollDown, PageUp, PageDown,]
+    [LineUp, LineDown, ScrollUp, ScrollDown, PageUp, PageDown]
 );
 
-pub fn init(cx: &mut AppContext) {
-    cx.add_action(|_: &mut Workspace, _: &LineDown, cx| {
+pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+    workspace.register_action(|_: &mut Workspace, _: &LineDown, cx| {
         scroll(cx, false, |c| ScrollAmount::Line(c.unwrap_or(1.)))
     });
-    cx.add_action(|_: &mut Workspace, _: &LineUp, cx| {
+    workspace.register_action(|_: &mut Workspace, _: &LineUp, cx| {
         scroll(cx, false, |c| ScrollAmount::Line(-c.unwrap_or(1.)))
     });
-    cx.add_action(|_: &mut Workspace, _: &PageDown, cx| {
+    workspace.register_action(|_: &mut Workspace, _: &PageDown, cx| {
         scroll(cx, false, |c| ScrollAmount::Page(c.unwrap_or(1.)))
     });
-    cx.add_action(|_: &mut Workspace, _: &PageUp, cx| {
+    workspace.register_action(|_: &mut Workspace, _: &PageUp, cx| {
         scroll(cx, false, |c| ScrollAmount::Page(-c.unwrap_or(1.)))
     });
-    cx.add_action(|_: &mut Workspace, _: &ScrollDown, cx| {
+    workspace.register_action(|_: &mut Workspace, _: &ScrollDown, cx| {
         scroll(cx, true, |c| {
             if let Some(c) = c {
                 ScrollAmount::Line(c)
@@ -35,7 +35,7 @@ pub fn init(cx: &mut AppContext) {
             }
         })
     });
-    cx.add_action(|_: &mut Workspace, _: &ScrollUp, cx| {
+    workspace.register_action(|_: &mut Workspace, _: &ScrollUp, cx| {
         scroll(cx, true, |c| {
             if let Some(c) = c {
                 ScrollAmount::Line(-c)
@@ -114,7 +114,7 @@ mod test {
         state::Mode,
         test::{NeovimBackedTestContext, VimTestContext},
     };
-    use gpui::geometry::vector::vec2f;
+    use gpui::{point, px, size, Context};
     use indoc::indoc;
     use language::Point;
 
@@ -122,10 +122,27 @@ mod test {
     async fn test_scroll(cx: &mut gpui::TestAppContext) {
         let mut cx = VimTestContext::new(cx, true).await;
 
+        let (line_height, visible_line_count) = cx.editor(|editor, cx| {
+            (
+                editor
+                    .style()
+                    .unwrap()
+                    .text
+                    .line_height_in_pixels(cx.rem_size()),
+                editor.visible_line_count().unwrap(),
+            )
+        });
+
         let window = cx.window;
-        let line_height =
-            cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
-        window.simulate_resize(vec2f(1000., 8.0 * line_height - 1.0), &mut cx);
+        let margin = cx
+            .update_window(window, |_, cx| {
+                cx.viewport_size().height - line_height * visible_line_count
+            })
+            .unwrap();
+        cx.simulate_window_resize(
+            cx.window,
+            size(px(1000.), margin + 8. * line_height - px(1.0)),
+        );
 
         cx.set_state(
             indoc!(
@@ -147,29 +164,29 @@ mod test {
         );
 
         cx.update_editor(|editor, cx| {
-            assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 0.))
+            assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 0.))
         });
         cx.simulate_keystrokes(["ctrl-e"]);
         cx.update_editor(|editor, cx| {
-            assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 1.))
+            assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 1.))
         });
         cx.simulate_keystrokes(["2", "ctrl-e"]);
         cx.update_editor(|editor, cx| {
-            assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.))
+            assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 3.))
         });
         cx.simulate_keystrokes(["ctrl-y"]);
         cx.update_editor(|editor, cx| {
-            assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 2.))
+            assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 2.))
         });
 
         // does not select in normal mode
         cx.simulate_keystrokes(["g", "g"]);
         cx.update_editor(|editor, cx| {
-            assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 0.))
+            assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 0.))
         });
         cx.simulate_keystrokes(["ctrl-d"]);
         cx.update_editor(|editor, cx| {
-            assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.0));
+            assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 3.0));
             assert_eq!(
                 editor.selections.newest(cx).range(),
                 Point::new(6, 0)..Point::new(6, 0)
@@ -179,11 +196,11 @@ mod test {
         // does select in visual mode
         cx.simulate_keystrokes(["g", "g"]);
         cx.update_editor(|editor, cx| {
-            assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 0.))
+            assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 0.))
         });
         cx.simulate_keystrokes(["v", "ctrl-d"]);
         cx.update_editor(|editor, cx| {
-            assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.0));
+            assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 3.0));
             assert_eq!(
                 editor.selections.newest(cx).range(),
                 Point::new(0, 0)..Point::new(6, 1)

crates/vim/src/normal/search.rs 🔗

@@ -1,7 +1,7 @@
-use gpui::{actions, impl_actions, AppContext, ViewContext};
+use gpui::{actions, impl_actions, ViewContext};
 use search::{buffer_search, BufferSearchBar, SearchMode, SearchOptions};
 use serde_derive::Deserialize;
-use workspace::{searchable::Direction, Pane, Workspace};
+use workspace::{searchable::Direction, Workspace};
 
 use crate::{motion::Motion, normal::move_cursor, state::SearchState, Vim};
 
@@ -44,21 +44,21 @@ struct Replacement {
     is_case_sensitive: bool,
 }
 
+actions!(vim, [SearchSubmit]);
 impl_actions!(
     vim,
-    [MoveToNext, MoveToPrev, Search, FindCommand, ReplaceCommand]
+    [FindCommand, ReplaceCommand, Search, MoveToPrev, MoveToNext]
 );
-actions!(vim, [SearchSubmit]);
 
-pub(crate) fn init(cx: &mut AppContext) {
-    cx.add_action(move_to_next);
-    cx.add_action(move_to_prev);
-    cx.add_action(search);
-    cx.add_action(search_submit);
-    cx.add_action(search_deploy);
+pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+    workspace.register_action(move_to_next);
+    workspace.register_action(move_to_prev);
+    workspace.register_action(search);
+    workspace.register_action(search_submit);
+    workspace.register_action(search_deploy);
 
-    cx.add_action(find_command);
-    cx.add_action(replace_command);
+    workspace.register_action(find_command);
+    workspace.register_action(replace_command);
 }
 
 fn move_to_next(workspace: &mut Workspace, action: &MoveToNext, cx: &mut ViewContext<Workspace>) {
@@ -106,9 +106,9 @@ fn search(workspace: &mut Workspace, action: &Search, cx: &mut ViewContext<Works
 }
 
 // hook into the existing to clear out any vim search state on cmd+f or edit -> find.
-fn search_deploy(_: &mut Pane, _: &buffer_search::Deploy, cx: &mut ViewContext<Pane>) {
+fn search_deploy(_: &mut Workspace, _: &buffer_search::Deploy, cx: &mut ViewContext<Workspace>) {
     Vim::update(cx, |vim, _| vim.workspace_state.search = Default::default());
-    cx.propagate_action();
+    cx.propagate();
 }
 
 fn search_submit(workspace: &mut Workspace, _: &SearchSubmit, cx: &mut ViewContext<Workspace>) {
@@ -347,58 +347,50 @@ fn parse_replace_all(query: &str) -> Replacement {
 
 #[cfg(test)]
 mod test {
-    use std::sync::Arc;
-
     use editor::DisplayPoint;
     use search::BufferSearchBar;
 
     use crate::{state::Mode, test::VimTestContext};
 
     #[gpui::test]
-    async fn test_move_to_next(
-        cx: &mut gpui::TestAppContext,
-        deterministic: Arc<gpui::executor::Deterministic>,
-    ) {
+    async fn test_move_to_next(cx: &mut gpui::TestAppContext) {
         let mut cx = VimTestContext::new(cx, true).await;
         cx.set_state("ˇhi\nhigh\nhi\n", Mode::Normal);
 
         cx.simulate_keystrokes(["*"]);
-        deterministic.run_until_parked();
+        cx.run_until_parked();
         cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);
 
         cx.simulate_keystrokes(["*"]);
-        deterministic.run_until_parked();
+        cx.run_until_parked();
         cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);
 
         cx.simulate_keystrokes(["#"]);
-        deterministic.run_until_parked();
+        cx.run_until_parked();
         cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);
 
         cx.simulate_keystrokes(["#"]);
-        deterministic.run_until_parked();
+        cx.run_until_parked();
         cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);
 
         cx.simulate_keystrokes(["2", "*"]);
-        deterministic.run_until_parked();
+        cx.run_until_parked();
         cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);
 
         cx.simulate_keystrokes(["g", "*"]);
-        deterministic.run_until_parked();
+        cx.run_until_parked();
         cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal);
 
         cx.simulate_keystrokes(["n"]);
         cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);
 
         cx.simulate_keystrokes(["g", "#"]);
-        deterministic.run_until_parked();
+        cx.run_until_parked();
         cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal);
     }
 
     #[gpui::test]
-    async fn test_search(
-        cx: &mut gpui::TestAppContext,
-        deterministic: Arc<gpui::executor::Deterministic>,
-    ) {
+    async fn test_search(cx: &mut gpui::TestAppContext) {
         let mut cx = VimTestContext::new(cx, true).await;
 
         cx.set_state("aa\nbˇb\ncc\ncc\ncc\n", Mode::Normal);
@@ -414,11 +406,11 @@ mod test {
                 .expect("Buffer search bar should be deployed")
         });
 
-        search_bar.read_with(cx.cx, |bar, cx| {
+        cx.update_view(search_bar, |bar, cx| {
             assert_eq!(bar.query(cx), "cc");
         });
 
-        deterministic.run_until_parked();
+        cx.run_until_parked();
 
         cx.update_editor(|editor, cx| {
             let highlights = editor.all_text_background_highlights(cx);
@@ -440,51 +432,41 @@ mod test {
 
         // ?<enter> to go to previous
         cx.simulate_keystrokes(["?", "enter"]);
-        deterministic.run_until_parked();
         cx.assert_state("aa\nbb\ncc\ncc\nˇcc\n", Mode::Normal);
         cx.simulate_keystrokes(["?", "enter"]);
-        deterministic.run_until_parked();
         cx.assert_state("aa\nbb\ncc\nˇcc\ncc\n", Mode::Normal);
 
         // /<enter> to go to next
         cx.simulate_keystrokes(["/", "enter"]);
-        deterministic.run_until_parked();
         cx.assert_state("aa\nbb\ncc\ncc\nˇcc\n", Mode::Normal);
 
         // ?{search}<enter> to search backwards
         cx.simulate_keystrokes(["?", "b", "enter"]);
-        deterministic.run_until_parked();
         cx.assert_state("aa\nbˇb\ncc\ncc\ncc\n", Mode::Normal);
 
         // works with counts
         cx.simulate_keystrokes(["4", "/", "c"]);
-        deterministic.run_until_parked();
         cx.simulate_keystrokes(["enter"]);
         cx.assert_state("aa\nbb\ncc\ncˇc\ncc\n", Mode::Normal);
 
         // check that searching resumes from cursor, not previous match
         cx.set_state("ˇaa\nbb\ndd\ncc\nbb\n", Mode::Normal);
         cx.simulate_keystrokes(["/", "d"]);
-        deterministic.run_until_parked();
         cx.simulate_keystrokes(["enter"]);
         cx.assert_state("aa\nbb\nˇdd\ncc\nbb\n", Mode::Normal);
         cx.update_editor(|editor, cx| editor.move_to_beginning(&Default::default(), cx));
         cx.assert_state("ˇaa\nbb\ndd\ncc\nbb\n", Mode::Normal);
         cx.simulate_keystrokes(["/", "b"]);
-        deterministic.run_until_parked();
         cx.simulate_keystrokes(["enter"]);
         cx.assert_state("aa\nˇbb\ndd\ncc\nbb\n", Mode::Normal);
     }
 
     #[gpui::test]
-    async fn test_non_vim_search(
-        cx: &mut gpui::TestAppContext,
-        deterministic: Arc<gpui::executor::Deterministic>,
-    ) {
+    async fn test_non_vim_search(cx: &mut gpui::TestAppContext) {
         let mut cx = VimTestContext::new(cx, false).await;
         cx.set_state("ˇone one one one", Mode::Normal);
         cx.simulate_keystrokes(["cmd-f"]);
-        deterministic.run_until_parked();
+        cx.run_until_parked();
 
         cx.assert_editor_state("«oneˇ» one one one");
         cx.simulate_keystrokes(["enter"]);

crates/vim/src/normal/substitute.rs 🔗

@@ -1,5 +1,5 @@
 use editor::movement;
-use gpui::{actions, AppContext, WindowContext};
+use gpui::{actions, ViewContext, WindowContext};
 use language::Point;
 use workspace::Workspace;
 
@@ -7,8 +7,8 @@ use crate::{motion::Motion, utils::copy_selections_content, Mode, Vim};
 
 actions!(vim, [Substitute, SubstituteLine]);
 
-pub(crate) fn init(cx: &mut AppContext) {
-    cx.add_action(|_: &mut Workspace, _: &Substitute, cx| {
+pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+    workspace.register_action(|_: &mut Workspace, _: &Substitute, cx| {
         Vim::update(cx, |vim, cx| {
             vim.start_recording(cx);
             let count = vim.take_count(cx);
@@ -16,7 +16,7 @@ pub(crate) fn init(cx: &mut AppContext) {
         })
     });
 
-    cx.add_action(|_: &mut Workspace, _: &SubstituteLine, cx| {
+    workspace.register_action(|_: &mut Workspace, _: &SubstituteLine, cx| {
         Vim::update(cx, |vim, cx| {
             vim.start_recording(cx);
             if matches!(vim.state().mode, Mode::VisualBlock | Mode::Visual) {

crates/vim/src/object.rs 🔗

@@ -6,7 +6,7 @@ use editor::{
     movement::{self, FindRange},
     Bias, CharKind, DisplayPoint,
 };
-use gpui::{actions, impl_actions, AppContext, WindowContext};
+use gpui::{actions, impl_actions, ViewContext, WindowContext};
 use language::Selection;
 use serde::Deserialize;
 use workspace::Workspace;
@@ -34,6 +34,8 @@ struct Word {
     ignore_punctuation: bool,
 }
 
+impl_actions!(vim, [Word]);
+
 actions!(
     vim,
     [
@@ -48,25 +50,36 @@ actions!(
         AngleBrackets
     ]
 );
-impl_actions!(vim, [Word]);
 
-pub fn init(cx: &mut AppContext) {
-    cx.add_action(
+pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+    workspace.register_action(
         |_: &mut Workspace, &Word { ignore_punctuation }: &Word, cx: _| {
             object(Object::Word { ignore_punctuation }, cx)
         },
     );
-    cx.add_action(|_: &mut Workspace, _: &Sentence, cx: _| object(Object::Sentence, cx));
-    cx.add_action(|_: &mut Workspace, _: &Quotes, cx: _| object(Object::Quotes, cx));
-    cx.add_action(|_: &mut Workspace, _: &BackQuotes, cx: _| object(Object::BackQuotes, cx));
-    cx.add_action(|_: &mut Workspace, _: &DoubleQuotes, cx: _| object(Object::DoubleQuotes, cx));
-    cx.add_action(|_: &mut Workspace, _: &Parentheses, cx: _| object(Object::Parentheses, cx));
-    cx.add_action(|_: &mut Workspace, _: &SquareBrackets, cx: _| {
+    workspace
+        .register_action(|_: &mut Workspace, _: &Sentence, cx: _| object(Object::Sentence, cx));
+    workspace.register_action(|_: &mut Workspace, _: &Quotes, cx: _| object(Object::Quotes, cx));
+    workspace
+        .register_action(|_: &mut Workspace, _: &BackQuotes, cx: _| object(Object::BackQuotes, cx));
+    workspace.register_action(|_: &mut Workspace, _: &DoubleQuotes, cx: _| {
+        object(Object::DoubleQuotes, cx)
+    });
+    workspace.register_action(|_: &mut Workspace, _: &Parentheses, cx: _| {
+        object(Object::Parentheses, cx)
+    });
+    workspace.register_action(|_: &mut Workspace, _: &SquareBrackets, cx: _| {
         object(Object::SquareBrackets, cx)
     });
-    cx.add_action(|_: &mut Workspace, _: &CurlyBrackets, cx: _| object(Object::CurlyBrackets, cx));
-    cx.add_action(|_: &mut Workspace, _: &AngleBrackets, cx: _| object(Object::AngleBrackets, cx));
-    cx.add_action(|_: &mut Workspace, _: &VerticalBars, cx: _| object(Object::VerticalBars, cx));
+    workspace.register_action(|_: &mut Workspace, _: &CurlyBrackets, cx: _| {
+        object(Object::CurlyBrackets, cx)
+    });
+    workspace.register_action(|_: &mut Workspace, _: &AngleBrackets, cx: _| {
+        object(Object::AngleBrackets, cx)
+    });
+    workspace.register_action(|_: &mut Workspace, _: &VerticalBars, cx: _| {
+        object(Object::VerticalBars, cx)
+    });
 }
 
 fn object(object: Object, cx: &mut WindowContext) {

crates/vim/src/state.rs 🔗

@@ -1,6 +1,6 @@
 use std::{ops::Range, sync::Arc};
 
-use gpui::{keymap_matcher::KeymapContext, Action};
+use gpui::{Action, KeyContext};
 use language::CursorShape;
 use serde::{Deserialize, Serialize};
 use workspace::searchable::Direction;
@@ -167,10 +167,10 @@ impl EditorState {
         self.operator_stack.last().copied()
     }
 
-    pub fn keymap_context_layer(&self) -> KeymapContext {
-        let mut context = KeymapContext::default();
-        context.add_identifier("VimEnabled");
-        context.add_key(
+    pub fn keymap_context_layer(&self) -> KeyContext {
+        let mut context = KeyContext::default();
+        context.add("VimEnabled");
+        context.set(
             "vim_mode",
             match self.mode {
                 Mode::Normal => "normal",
@@ -180,24 +180,24 @@ impl EditorState {
         );
 
         if self.vim_controlled() {
-            context.add_identifier("VimControl");
+            context.add("VimControl");
         }
 
         if self.active_operator().is_none() && self.pre_count.is_some()
             || self.active_operator().is_some() && self.post_count.is_some()
         {
-            context.add_identifier("VimCount");
+            context.add("VimCount");
         }
 
         let active_operator = self.active_operator();
 
         if let Some(active_operator) = active_operator {
             for context_flag in active_operator.context_flags().into_iter() {
-                context.add_identifier(*context_flag);
+                context.add(*context_flag);
             }
         }
 
-        context.add_key(
+        context.set(
             "vim_operator",
             active_operator.map(|op| op.id()).unwrap_or_else(|| "none"),
         );

crates/vim/src/test.rs 🔗

@@ -3,8 +3,6 @@ mod neovim_backed_test_context;
 mod neovim_connection;
 mod vim_test_context;
 
-use std::sync::Arc;
-
 use command_palette::CommandPalette;
 use editor::DisplayPoint;
 pub use neovim_backed_binding_test_context::*;
@@ -96,7 +94,7 @@ async fn test_buffer_search(cx: &mut gpui::TestAppContext) {
             .expect("Buffer search bar should be deployed")
     });
 
-    search_bar.read_with(cx.cx, |bar, cx| {
+    cx.update_view(search_bar, |bar, cx| {
         assert_eq!(bar.query(cx), "");
     })
 }
@@ -149,9 +147,10 @@ async fn test_escape_command_palette(cx: &mut gpui::TestAppContext) {
     cx.set_state("aˇbc\n", Mode::Normal);
     cx.simulate_keystrokes(["i", "cmd-shift-p"]);
 
-    assert!(cx.workspace(|workspace, _| workspace.modal::<CommandPalette>().is_some()));
+    assert!(cx.workspace(|workspace, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
     cx.simulate_keystroke("escape");
-    assert!(!cx.workspace(|workspace, _| workspace.modal::<CommandPalette>().is_some()));
+    cx.run_until_parked();
+    assert!(!cx.workspace(|workspace, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
     cx.assert_state("aˇbc\n", Mode::Insert);
 }
 
@@ -182,7 +181,7 @@ async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
             .expect("Buffer search bar should be deployed")
     });
 
-    search_bar.read_with(cx.cx, |bar, cx| {
+    cx.update_view(search_bar, |bar, cx| {
         assert_eq!(bar.query(cx), "cc");
     });
 
@@ -204,12 +203,8 @@ async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
 }
 
 #[gpui::test]
-async fn test_status_indicator(
-    cx: &mut gpui::TestAppContext,
-    deterministic: Arc<gpui::executor::Deterministic>,
-) {
+async fn test_status_indicator(cx: &mut gpui::TestAppContext) {
     let mut cx = VimTestContext::new(cx, true).await;
-    deterministic.run_until_parked();
 
     let mode_indicator = cx.workspace(|workspace, cx| {
         let status_bar = workspace.status_bar().read(cx);
@@ -225,7 +220,6 @@ async fn test_status_indicator(
 
     // shows the correct mode
     cx.simulate_keystrokes(["i"]);
-    deterministic.run_until_parked();
     assert_eq!(
         cx.workspace(|_, cx| mode_indicator.read(cx).mode),
         Some(Mode::Insert)
@@ -233,7 +227,6 @@ async fn test_status_indicator(
 
     // shows even in search
     cx.simulate_keystrokes(["escape", "v", "/"]);
-    deterministic.run_until_parked();
     assert_eq!(
         cx.workspace(|_, cx| mode_indicator.read(cx).mode),
         Some(Mode::Visual)
@@ -241,7 +234,7 @@ async fn test_status_indicator(
 
     // hides if vim mode is disabled
     cx.disable_vim();
-    deterministic.run_until_parked();
+    cx.run_until_parked();
     cx.workspace(|workspace, cx| {
         let status_bar = workspace.status_bar().read(cx);
         let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
@@ -249,7 +242,7 @@ async fn test_status_indicator(
     });
 
     cx.enable_vim();
-    deterministic.run_until_parked();
+    cx.run_until_parked();
     cx.workspace(|workspace, cx| {
         let status_bar = workspace.status_bar().read(cx);
         let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();

crates/vim/src/test/neovim_backed_test_context.rs 🔗

@@ -1,4 +1,5 @@
-use editor::scroll::VERTICAL_SCROLL_MARGIN;
+use editor::{scroll::VERTICAL_SCROLL_MARGIN, test::editor_test_context::ContextHandle};
+use gpui::{px, size, Context};
 use indoc::indoc;
 use settings::SettingsStore;
 use std::{
@@ -7,7 +8,6 @@ use std::{
 };
 
 use collections::{HashMap, HashSet};
-use gpui::{geometry::vector::vec2f, ContextHandle};
 use language::language_settings::{AllLanguageSettings, SoftWrap};
 use util::test::marked_text_offsets;
 
@@ -158,11 +158,28 @@ impl<'a> NeovimBackedTestContext<'a> {
             .await;
         // +2 to account for the vim command UI at the bottom.
         self.neovim.set_option(&format!("lines={}", rows + 2)).await;
+        let (line_height, visible_line_count) = self.editor(|editor, cx| {
+            (
+                editor
+                    .style()
+                    .unwrap()
+                    .text
+                    .line_height_in_pixels(cx.rem_size()),
+                editor.visible_line_count().unwrap(),
+            )
+        });
+
         let window = self.window;
-        let line_height =
-            self.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
+        let margin = self
+            .update_window(window, |_, cx| {
+                cx.viewport_size().height - line_height * visible_line_count
+            })
+            .unwrap();
 
-        window.simulate_resize(vec2f(1000., (rows as f32) * line_height), &mut self.cx);
+        self.simulate_window_resize(
+            self.window,
+            size(px(1000.), margin + (rows as f32) * line_height),
+        );
     }
 
     pub async fn set_neovim_option(&mut self, option: &str) {
@@ -211,12 +228,7 @@ impl<'a> NeovimBackedTestContext<'a> {
 
     pub async fn assert_shared_clipboard(&mut self, text: &str) {
         let neovim = self.neovim.read_register('"').await;
-        let editor = self
-            .platform()
-            .read_from_clipboard()
-            .unwrap()
-            .text()
-            .clone();
+        let editor = self.read_from_clipboard().unwrap().text().clone();
 
         if text == neovim && text == editor {
             return;

crates/vim/src/test/neovim_connection.rs 🔗

@@ -10,7 +10,7 @@ use async_compat::Compat;
 #[cfg(feature = "neovim")]
 use async_trait::async_trait;
 #[cfg(feature = "neovim")]
-use gpui::keymap_matcher::Keystroke;
+use gpui::Keystroke;
 
 #[cfg(feature = "neovim")]
 use language::Point;
@@ -116,16 +116,24 @@ impl NeovimConnection {
             keystroke.key = "lt".to_string()
         }
 
-        let special = keystroke.shift
-            || keystroke.ctrl
-            || keystroke.alt
-            || keystroke.cmd
+        let special = keystroke.modifiers.shift
+            || keystroke.modifiers.control
+            || keystroke.modifiers.alt
+            || keystroke.modifiers.command
             || keystroke.key.len() > 1;
         let start = if special { "<" } else { "" };
-        let shift = if keystroke.shift { "S-" } else { "" };
-        let ctrl = if keystroke.ctrl { "C-" } else { "" };
-        let alt = if keystroke.alt { "M-" } else { "" };
-        let cmd = if keystroke.cmd { "D-" } else { "" };
+        let shift = if keystroke.modifiers.shift { "S-" } else { "" };
+        let ctrl = if keystroke.modifiers.control {
+            "C-"
+        } else {
+            ""
+        };
+        let alt = if keystroke.modifiers.alt { "M-" } else { "" };
+        let cmd = if keystroke.modifiers.command {
+            "D-"
+        } else {
+            ""
+        };
         let end = if special { ">" } else { "" };
 
         let key = format!("{start}{shift}{ctrl}{alt}{cmd}{}{end}", keystroke.key);

crates/vim/src/test/vim_test_context.rs 🔗

@@ -4,9 +4,9 @@ use editor::test::{
     editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext,
 };
 use futures::Future;
-use gpui::ContextHandle;
+use gpui::{Context, View, VisualContext};
 use lsp::request;
-use search::{BufferSearchBar, ProjectSearchBar};
+use search::BufferSearchBar;
 
 use crate::{state::Operator, *};
 
@@ -15,12 +15,28 @@ pub struct VimTestContext<'a> {
 }
 
 impl<'a> VimTestContext<'a> {
+    pub fn init(cx: &mut gpui::TestAppContext) {
+        if cx.has_global::<Vim>() {
+            dbg!("OOPS");
+            return;
+        }
+        cx.update(|cx| {
+            search::init(cx);
+            let settings = SettingsStore::test(cx);
+            cx.set_global(settings);
+            command_palette::init(cx);
+            crate::init(cx);
+        });
+    }
+
     pub async fn new(cx: &'a mut gpui::TestAppContext, enabled: bool) -> VimTestContext<'a> {
+        Self::init(cx);
         let lsp = EditorLspTestContext::new_rust(Default::default(), cx).await;
         Self::new_with_lsp(lsp, enabled)
     }
 
     pub async fn new_typescript(cx: &'a mut gpui::TestAppContext) -> VimTestContext<'a> {
+        Self::init(cx);
         Self::new_with_lsp(
             EditorLspTestContext::new_typescript(Default::default(), cx).await,
             true,
@@ -28,12 +44,6 @@ impl<'a> VimTestContext<'a> {
     }
 
     pub fn new_with_lsp(mut cx: EditorLspTestContext<'a>, enabled: bool) -> VimTestContext<'a> {
-        cx.update(|cx| {
-            search::init(cx);
-            crate::init(cx);
-            command_palette::init(cx);
-        });
-
         cx.update(|cx| {
             cx.update_global(|store: &mut SettingsStore, cx| {
                 store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(enabled));
@@ -47,14 +57,15 @@ impl<'a> VimTestContext<'a> {
             observe_keystrokes(cx);
             workspace.active_pane().update(cx, |pane, cx| {
                 pane.toolbar().update(cx, |toolbar, cx| {
-                    let buffer_search_bar = cx.add_view(BufferSearchBar::new);
+                    let buffer_search_bar = cx.new_view(BufferSearchBar::new);
                     toolbar.add_item(buffer_search_bar, cx);
-                    let project_search_bar = cx.add_view(|_| ProjectSearchBar::new());
-                    toolbar.add_item(project_search_bar, cx);
+                    // todo!();
+                    // let project_search_bar = cx.add_view(|_| ProjectSearchBar::new());
+                    // toolbar.add_item(project_search_bar, cx);
                 })
             });
             workspace.status_bar().update(cx, |status_bar, cx| {
-                let vim_mode_indicator = cx.add_view(ModeIndicator::new);
+                let vim_mode_indicator = cx.new_view(ModeIndicator::new);
                 status_bar.add_right_item(vim_mode_indicator, cx);
             });
         });
@@ -62,11 +73,21 @@ impl<'a> VimTestContext<'a> {
         Self { cx }
     }
 
-    pub fn workspace<F, T>(&mut self, read: F) -> T
+    pub fn update_view<F, T, R>(&mut self, view: View<T>, update: F) -> R
     where
-        F: FnOnce(&Workspace, &ViewContext<Workspace>) -> T,
+        T: 'static,
+        F: FnOnce(&mut T, &mut ViewContext<T>) -> R + 'static,
     {
-        self.cx.workspace.read_with(self.cx.cx.cx, read)
+        let window = self.window.clone();
+        self.update_window(window, move |_, cx| view.update(cx, update))
+            .unwrap()
+    }
+
+    pub fn workspace<F, T>(&mut self, update: F) -> T
+    where
+        F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
+    {
+        self.cx.update_workspace(update)
     }
 
     pub fn enable_vim(&mut self) {
@@ -94,16 +115,16 @@ impl<'a> VimTestContext<'a> {
             .read(|cx| cx.global::<Vim>().state().operator_stack.last().copied())
     }
 
-    pub fn set_state(&mut self, text: &str, mode: Mode) -> ContextHandle {
+    pub fn set_state(&mut self, text: &str, mode: Mode) {
         let window = self.window;
-        let context_handle = self.cx.set_state(text);
-        window.update(self.cx.cx.cx, |cx| {
+        self.cx.set_state(text);
+        self.update_window(window, |_, cx| {
             Vim::update(cx, |vim, cx| {
                 vim.switch_mode(mode, true, cx);
             })
-        });
-        self.cx.foreground().run_until_parked();
-        context_handle
+        })
+        .unwrap();
+        self.cx.cx.cx.run_until_parked();
     }
 
     #[track_caller]

crates/vim/src/vim.rs 🔗

@@ -1,5 +1,3 @@
-#![allow(unused)]
-
 #[cfg(test)]
 mod test;
 
@@ -17,17 +15,17 @@ mod visual;
 use anyhow::Result;
 use collections::{CommandPaletteFilter, HashMap};
 use command_palette::CommandPaletteInterceptor;
-use editor::{movement, Editor, EditorMode, Event};
+use editor::{movement, Editor, EditorEvent, EditorMode};
 use gpui::{
-    actions, impl_actions, keymap_matcher::KeymapContext, keymap_matcher::MatchResult, Action,
-    AppContext, Subscription, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
+    actions, impl_actions, Action, AppContext, EntityId, KeyContext, Subscription, View,
+    ViewContext, WeakView, WindowContext,
 };
 use language::{CursorShape, Point, Selection, SelectionGoal};
 pub use mode_indicator::ModeIndicator;
 use motion::Motion;
 use normal::normal_replace;
 use serde::Deserialize;
-use settings::{update_settings_file, Setting, SettingsStore};
+use settings::{update_settings_file, Settings, SettingsStore};
 use state::{EditorState, Mode, Operator, RecordedSelection, WorkspaceState};
 use std::{ops::Range, sync::Arc};
 use visual::{visual_block_motion, visual_replace};
@@ -50,83 +48,86 @@ actions!(
     vim,
     [Tab, Enter, Object, InnerObject, FindForward, FindBackward]
 );
+// in the workspace namespace so it's not filtered out when vim is disabled.
 actions!(workspace, [ToggleVimMode]);
-impl_actions!(vim, [Number, SwitchMode, PushOperator]);
 
-#[derive(Copy, Clone, Debug)]
-enum VimEvent {
-    ModeChanged { mode: Mode },
-}
+impl_actions!(vim, [SwitchMode, PushOperator, Number]);
 
 pub fn init(cx: &mut AppContext) {
     cx.set_global(Vim::default());
-    settings::register::<VimModeSetting>(cx);
+    VimModeSetting::register(cx);
 
     editor_events::init(cx);
-    normal::init(cx);
-    visual::init(cx);
-    insert::init(cx);
-    object::init(cx);
-    motion::init(cx);
-    command::init(cx);
-
-    // Vim Actions
-    cx.add_action(|_: &mut Workspace, &SwitchMode(mode): &SwitchMode, cx| {
+
+    cx.observe_new_views(|workspace: &mut Workspace, cx| register(workspace, cx))
+        .detach();
+
+    // Any time settings change, update vim mode to match. The Vim struct
+    // will be initialized as disabled by default, so we filter its commands
+    // out when starting up.
+    cx.update_global::<CommandPaletteFilter, _>(|filter, _| {
+        filter.hidden_namespaces.insert("vim");
+    });
+    cx.update_global(|vim: &mut Vim, cx: &mut AppContext| {
+        vim.set_enabled(VimModeSetting::get_global(cx).0, cx)
+    });
+    cx.observe_global::<SettingsStore>(|cx| {
+        cx.update_global(|vim: &mut Vim, cx: &mut AppContext| {
+            vim.set_enabled(VimModeSetting::get_global(cx).0, cx)
+        });
+    })
+    .detach();
+}
+
+fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
+    workspace.register_action(|_: &mut Workspace, &SwitchMode(mode): &SwitchMode, cx| {
         Vim::update(cx, |vim, cx| vim.switch_mode(mode, false, cx))
     });
-    cx.add_action(
+    workspace.register_action(
         |_: &mut Workspace, &PushOperator(operator): &PushOperator, cx| {
             Vim::update(cx, |vim, cx| vim.push_operator(operator, cx))
         },
     );
-    cx.add_action(|_: &mut Workspace, n: &Number, cx: _| {
+    workspace.register_action(|_: &mut Workspace, n: &Number, cx: _| {
         Vim::update(cx, |vim, cx| vim.push_count_digit(n.0, cx));
     });
 
-    cx.add_action(|_: &mut Workspace, _: &Tab, cx| {
+    workspace.register_action(|_: &mut Workspace, _: &Tab, cx| {
         Vim::active_editor_input_ignored(" ".into(), cx)
     });
 
-    cx.add_action(|_: &mut Workspace, _: &Enter, cx| {
+    workspace.register_action(|_: &mut Workspace, _: &Enter, cx| {
         Vim::active_editor_input_ignored("\n".into(), cx)
     });
 
-    cx.add_action(|workspace: &mut Workspace, _: &ToggleVimMode, cx| {
+    workspace.register_action(|workspace: &mut Workspace, _: &ToggleVimMode, cx| {
         let fs = workspace.app_state().fs.clone();
-        let currently_enabled = settings::get::<VimModeSetting>(cx).0;
+        let currently_enabled = VimModeSetting::get_global(cx).0;
         update_settings_file::<VimModeSetting>(fs, cx, move |setting| {
             *setting = Some(!currently_enabled)
         })
     });
 
-    // Any time settings change, update vim mode to match. The Vim struct
-    // will be initialized as disabled by default, so we filter its commands
-    // out when starting up.
-    cx.update_default_global::<CommandPaletteFilter, _, _>(|filter, _| {
-        filter.hidden_namespaces.insert("vim");
-    });
-    cx.update_global(|vim: &mut Vim, cx: &mut AppContext| {
-        vim.set_enabled(settings::get::<VimModeSetting>(cx).0, cx)
-    });
-    cx.observe_global::<SettingsStore, _>(|cx| {
-        cx.update_global(|vim: &mut Vim, cx: &mut AppContext| {
-            vim.set_enabled(settings::get::<VimModeSetting>(cx).0, cx)
-        });
-    })
-    .detach();
+    normal::register(workspace, cx);
+    insert::register(workspace, cx);
+    motion::register(workspace, cx);
+    command::register(workspace, cx);
+    object::register(workspace, cx);
+    visual::register(workspace, cx);
 }
 
 pub fn observe_keystrokes(cx: &mut WindowContext) {
-    cx.observe_keystrokes(|_keystroke, result, handled_by, cx| {
-        if result == &MatchResult::Pending {
-            return true;
-        }
-        if let Some(handled_by) = handled_by {
+    cx.observe_keystrokes(|keystroke_event, cx| {
+        if let Some(action) = keystroke_event
+            .action
+            .as_ref()
+            .map(|action| action.boxed_clone())
+        {
             Vim::update(cx, |vim, _| {
                 if vim.workspace_state.recording {
                     vim.workspace_state
                         .recorded_actions
-                        .push(ReplayableAction::Action(handled_by.boxed_clone()));
+                        .push(ReplayableAction::Action(action.boxed_clone()));
 
                     if vim.workspace_state.stop_recording_after_next_action {
                         vim.workspace_state.recording = false;
@@ -136,9 +137,11 @@ pub fn observe_keystrokes(cx: &mut WindowContext) {
             });
 
             // Keystroke is handled by the vim system, so continue forward
-            if handled_by.namespace() == "vim" {
-                return true;
+            if action.name().starts_with("vim::") {
+                return;
             }
+        } else if cx.has_pending_keystrokes() {
+            return;
         }
 
         Vim::update(cx, |vim, cx| match vim.active_operator() {
@@ -150,24 +153,23 @@ pub fn observe_keystrokes(cx: &mut WindowContext) {
             }
             _ => {}
         });
-        true
     })
     .detach()
 }
 
 #[derive(Default)]
 pub struct Vim {
-    active_editor: Option<WeakViewHandle<Editor>>,
+    active_editor: Option<WeakView<Editor>>,
     editor_subscription: Option<Subscription>,
     enabled: bool,
-    editor_states: HashMap<usize, EditorState>,
+    editor_states: HashMap<EntityId, EditorState>,
     workspace_state: WorkspaceState,
     default_state: EditorState,
 }
 
 impl Vim {
     fn read(cx: &mut AppContext) -> &Self {
-        cx.default_global()
+        cx.global::<Self>()
     }
 
     fn update<F, S>(cx: &mut WindowContext, update: F) -> S
@@ -177,21 +179,21 @@ impl Vim {
         cx.update_global(update)
     }
 
-    fn set_active_editor(&mut self, editor: ViewHandle<Editor>, cx: &mut WindowContext) {
+    fn set_active_editor(&mut self, editor: View<Editor>, cx: &mut WindowContext) {
         self.active_editor = Some(editor.clone().downgrade());
         self.editor_subscription = Some(cx.subscribe(&editor, |editor, event, cx| match event {
-            Event::SelectionsChanged { local: true } => {
+            EditorEvent::SelectionsChanged { local: true } => {
                 let editor = editor.read(cx);
                 if editor.leader_peer_id().is_none() {
                     let newest = editor.selections.newest::<usize>(cx);
                     local_selections_changed(newest, cx);
                 }
             }
-            Event::InputIgnored { text } => {
+            EditorEvent::InputIgnored { text } => {
                 Vim::active_editor_input_ignored(text.clone(), cx);
                 Vim::record_insertion(text, None, cx)
             }
-            Event::InputHandled {
+            EditorEvent::InputHandled {
                 text,
                 utf16_range_to_replace: range_to_replace,
             } => Vim::record_insertion(text, range_to_replace.clone(), cx),
@@ -242,7 +244,7 @@ impl Vim {
         cx: &mut WindowContext,
         update: impl FnOnce(&mut Editor, &mut ViewContext<Editor>) -> S,
     ) -> Option<S> {
-        let editor = self.active_editor.clone()?.upgrade(cx)?;
+        let editor = self.active_editor.clone()?.upgrade()?;
         Some(editor.update(cx, update))
     }
 
@@ -254,7 +256,8 @@ impl Vim {
 
             let selections = self
                 .active_editor
-                .and_then(|editor| editor.upgrade(cx))
+                .as_ref()
+                .and_then(|editor| editor.upgrade())
                 .map(|editor| {
                     let editor = editor.read(cx);
                     (
@@ -323,8 +326,6 @@ impl Vim {
             self.take_count(cx);
         }
 
-        cx.emit_global(VimEvent::ModeChanged { mode });
-
         // Sync editor settings like clip mode
         self.sync_vim_settings(cx);
 
@@ -477,7 +478,7 @@ impl Vim {
         if self.enabled != enabled {
             self.enabled = enabled;
 
-            cx.update_default_global::<CommandPaletteFilter, _, _>(|filter, _| {
+            cx.update_global::<CommandPaletteFilter, _>(|filter, _| {
                 if self.enabled {
                     filter.hidden_namespaces.remove("vim");
                 } else {
@@ -491,26 +492,30 @@ impl Vim {
                 let _ = cx.remove_global::<CommandPaletteInterceptor>();
             }
 
-            cx.update_active_window(|cx| {
-                if self.enabled {
-                    let active_editor = cx
-                        .root_view()
-                        .downcast_ref::<Workspace>()
-                        .and_then(|workspace| workspace.read(cx).active_item(cx))
-                        .and_then(|item| item.downcast::<Editor>());
-                    if let Some(active_editor) = active_editor {
-                        self.set_active_editor(active_editor, cx);
-                    }
-                    self.switch_mode(Mode::Normal, false, cx);
-                }
-                self.sync_vim_settings(cx);
-            });
+            if let Some(active_window) = cx.active_window() {
+                active_window
+                    .update(cx, |root_view, cx| {
+                        if self.enabled {
+                            let active_editor = root_view
+                                .downcast::<Workspace>()
+                                .ok()
+                                .and_then(|workspace| workspace.read(cx).active_item(cx))
+                                .and_then(|item| item.downcast::<Editor>());
+                            if let Some(active_editor) = active_editor {
+                                self.set_active_editor(active_editor, cx);
+                            }
+                            self.switch_mode(Mode::Normal, false, cx);
+                        }
+                        self.sync_vim_settings(cx);
+                    })
+                    .ok();
+            }
         }
     }
 
     pub fn state(&self) -> &EditorState {
         if let Some(active_editor) = self.active_editor.as_ref() {
-            if let Some(state) = self.editor_states.get(&active_editor.id()) {
+            if let Some(state) = self.editor_states.get(&active_editor.entity_id()) {
                 return state;
             }
         }
@@ -523,7 +528,7 @@ impl Vim {
         let ret = func(&mut state);
 
         if let Some(active_editor) = self.active_editor.as_ref() {
-            self.editor_states.insert(active_editor.id(), state);
+            self.editor_states.insert(active_editor.entity_id(), state);
         }
 
         ret
@@ -564,8 +569,8 @@ impl Vim {
         // This is a bit of a hack, but currently the search crate does not depend on vim,
         // and it seems nice to keep it that way.
         if self.enabled {
-            let mut context = KeymapContext::default();
-            context.add_identifier("VimEnabled");
+            let mut context = KeyContext::default();
+            context.add("VimEnabled");
             editor.set_keymap_context_layer::<Self>(context, cx)
         } else {
             editor.remove_keymap_context_layer::<Self>(cx);
@@ -573,7 +578,7 @@ impl Vim {
     }
 }
 
-impl Setting for VimModeSetting {
+impl Settings for VimModeSetting {
     const KEY: Option<&'static str> = Some("vim_mode");
 
     type FileContent = Option<bool>;
@@ -581,7 +586,7 @@ impl Setting for VimModeSetting {
     fn load(
         default_value: &Self::FileContent,
         user_values: &[&Self::FileContent],
-        _: &AppContext,
+        _: &mut AppContext,
     ) -> Result<Self> {
         Ok(Self(user_values.iter().rev().find_map(|v| **v).unwrap_or(
             default_value.ok_or_else(Self::missing_default)?,

crates/vim/src/visual.rs 🔗

@@ -8,7 +8,7 @@ use editor::{
     scroll::autoscroll::Autoscroll,
     Bias, DisplayPoint, Editor,
 };
-use gpui::{actions, AppContext, ViewContext, WindowContext};
+use gpui::{actions, ViewContext, WindowContext};
 use language::{Selection, SelectionGoal};
 use workspace::Workspace;
 
@@ -34,24 +34,28 @@ actions!(
     ]
 );
 
-pub fn init(cx: &mut AppContext) {
-    cx.add_action(|_, _: &ToggleVisual, cx: &mut ViewContext<Workspace>| {
+pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+    workspace.register_action(|_, _: &ToggleVisual, cx: &mut ViewContext<Workspace>| {
         toggle_mode(Mode::Visual, cx)
     });
-    cx.add_action(|_, _: &ToggleVisualLine, cx: &mut ViewContext<Workspace>| {
+    workspace.register_action(|_, _: &ToggleVisualLine, cx: &mut ViewContext<Workspace>| {
         toggle_mode(Mode::VisualLine, cx)
     });
-    cx.add_action(
+    workspace.register_action(
         |_, _: &ToggleVisualBlock, cx: &mut ViewContext<Workspace>| {
             toggle_mode(Mode::VisualBlock, cx)
         },
     );
-    cx.add_action(other_end);
-    cx.add_action(delete);
-    cx.add_action(yank);
+    workspace.register_action(other_end);
+    workspace.register_action(delete);
+    workspace.register_action(yank);
 
-    cx.add_action(select_next);
-    cx.add_action(select_previous);
+    workspace.register_action(|workspace, action, cx| {
+        select_next(workspace, action, cx).ok();
+    });
+    workspace.register_action(|workspace, action, cx| {
+        select_previous(workspace, action, cx).ok();
+    });
 }
 
 pub fn visual_motion(motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
@@ -146,13 +150,13 @@ pub fn visual_block_motion(
         let mut head = s.newest_anchor().head().to_display_point(map);
         let mut tail = s.oldest_anchor().tail().to_display_point(map);
 
-        let mut head_x = map.x_for_point(head, &text_layout_details);
-        let mut tail_x = map.x_for_point(tail, &text_layout_details);
+        let mut head_x = map.x_for_display_point(head, &text_layout_details);
+        let mut tail_x = map.x_for_display_point(tail, &text_layout_details);
 
         let (start, end) = match s.newest_anchor().goal {
             SelectionGoal::HorizontalRange { start, end } if preserve_goal => (start, end),
             SelectionGoal::HorizontalPosition(start) if preserve_goal => (start, start),
-            _ => (tail_x, head_x),
+            _ => (tail_x.0, head_x.0),
         };
         let mut goal = SelectionGoal::HorizontalRange { start, end };
 
@@ -165,19 +169,19 @@ pub fn visual_block_motion(
             return;
         };
         head = new_head;
-        head_x = map.x_for_point(head, &text_layout_details);
+        head_x = map.x_for_display_point(head, &text_layout_details);
 
         let is_reversed = tail_x > head_x;
         if was_reversed && !is_reversed {
             tail = movement::saturating_left(map, tail);
-            tail_x = map.x_for_point(tail, &text_layout_details);
+            tail_x = map.x_for_display_point(tail, &text_layout_details);
         } else if !was_reversed && is_reversed {
             tail = movement::saturating_right(map, tail);
-            tail_x = map.x_for_point(tail, &text_layout_details);
+            tail_x = map.x_for_display_point(tail, &text_layout_details);
         }
         if !is_reversed && !preserve_goal {
             head = movement::saturating_right(map, head);
-            head_x = map.x_for_point(head, &text_layout_details);
+            head_x = map.x_for_display_point(head, &text_layout_details);
         }
 
         let positions = if is_reversed {
@@ -188,8 +192,8 @@ pub fn visual_block_motion(
 
         if !preserve_goal {
             goal = SelectionGoal::HorizontalRange {
-                start: positions.start,
-                end: positions.end,
+                start: positions.start.0,
+                end: positions.end.0,
             };
         }
 
@@ -197,7 +201,7 @@ pub fn visual_block_motion(
         let mut row = tail.row();
 
         loop {
-            let layed_out_line = map.lay_out_line_for_row(row, &text_layout_details);
+            let layed_out_line = map.layout_row(row, &text_layout_details);
             let start = DisplayPoint::new(
                 row,
                 layed_out_line.closest_index_for_x(positions.start) as u32,
@@ -214,7 +218,7 @@ pub fn visual_block_motion(
                 }
             }
 
-            if positions.start <= layed_out_line.width() {
+            if positions.start <= layed_out_line.width {
                 let selection = Selection {
                     id: s.new_selection_id(),
                     start: start.to_point(map),
@@ -749,7 +753,12 @@ mod test {
                     fox jumps over
                     the lazy dog"})
             .await;
-        cx.assert_clipboard_content(Some("The q"));
+        assert_eq!(
+            cx.read_from_clipboard()
+                .map(|item| item.text().clone())
+                .unwrap(),
+            "The q"
+        );
 
         cx.set_shared_state(indoc! {"
                     The quick brown

crates/vim2/Cargo.toml 🔗

@@ -1,53 +0,0 @@
-[package]
-name = "vim2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/vim.rs"
-doctest = false
-
-[features]
-neovim = ["nvim-rs", "async-compat", "async-trait", "tokio"]
-
-[dependencies]
-anyhow.workspace = true
-serde.workspace = true
-serde_derive.workspace = true
-itertools = "0.10"
-log.workspace = true
-
-async-compat = { version = "0.2.1", "optional" = true }
-async-trait = { workspace = true, "optional" = true }
-nvim-rs = { git = "https://github.com/KillTheMule/nvim-rs", branch = "master", features = ["use_tokio"], optional = true }
-tokio = { version = "1.15", "optional" = true }
-serde_json.workspace = true
-
-collections = { path = "../collections" }
-command_palette = { package = "command_palette2", path = "../command_palette2" }
-editor = { package = "editor2", path = "../editor2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-language = { package = "language2", path = "../language2" }
-search = { package = "search2", path = "../search2" }
-settings = { package = "settings2", path = "../settings2" }
-workspace = { package = "workspace2", path = "../workspace2" }
-theme = { package = "theme2", path = "../theme2" }
-ui = { package = "ui2", path = "../ui2"}
-diagnostics = { package = "diagnostics2", path = "../diagnostics2" }
-zed_actions = { package = "zed_actions2", path = "../zed_actions2" }
-
-[dev-dependencies]
-indoc.workspace = true
-parking_lot.workspace = true
-futures.workspace = true
-
-editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-language = { package = "language2", path = "../language2", features = ["test-support"] }
-project = { package = "project2", path = "../project2", features = ["test-support"] }
-util = { path = "../util", features = ["test-support"] }
-settings = { package = "settings2", path = "../settings2" }
-workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
-theme = { package = "theme2", path = "../theme2", features = ["test-support"] }
-lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }

crates/vim2/src/command.rs 🔗

@@ -1,434 +0,0 @@
-use command_palette::CommandInterceptResult;
-use editor::{SortLinesCaseInsensitive, SortLinesCaseSensitive};
-use gpui::{impl_actions, Action, AppContext, ViewContext};
-use serde_derive::Deserialize;
-use workspace::{SaveIntent, Workspace};
-
-use crate::{
-    motion::{EndOfDocument, Motion},
-    normal::{
-        move_cursor,
-        search::{FindCommand, ReplaceCommand},
-        JoinLines,
-    },
-    state::Mode,
-    Vim,
-};
-
-#[derive(Debug, Clone, PartialEq, Deserialize)]
-pub struct GoToLine {
-    pub line: u32,
-}
-
-impl_actions!(vim, [GoToLine]);
-
-pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
-    workspace.register_action(|_: &mut Workspace, action: &GoToLine, cx| {
-        Vim::update(cx, |vim, cx| {
-            vim.switch_mode(Mode::Normal, false, cx);
-            move_cursor(vim, Motion::StartOfDocument, Some(action.line as usize), cx);
-        });
-    });
-}
-
-pub fn command_interceptor(mut query: &str, _: &AppContext) -> Option<CommandInterceptResult> {
-    // Note: this is a very poor simulation of vim's command palette.
-    // In the future we should adjust it to handle parsing range syntax,
-    // and then calling the appropriate commands with/without ranges.
-    //
-    // We also need to support passing arguments to commands like :w
-    // (ideally with filename autocompletion).
-    //
-    // For now, you can only do a replace on the % range, and you can
-    // only use a specific line number range to "go to line"
-    while query.starts_with(":") {
-        query = &query[1..];
-    }
-
-    let (name, action) = match query {
-        // save and quit
-        "w" | "wr" | "wri" | "writ" | "write" => (
-            "write",
-            workspace::Save {
-                save_intent: Some(SaveIntent::Save),
-            }
-            .boxed_clone(),
-        ),
-        "w!" | "wr!" | "wri!" | "writ!" | "write!" => (
-            "write!",
-            workspace::Save {
-                save_intent: Some(SaveIntent::Overwrite),
-            }
-            .boxed_clone(),
-        ),
-        "q" | "qu" | "qui" | "quit" => (
-            "quit",
-            workspace::CloseActiveItem {
-                save_intent: Some(SaveIntent::Close),
-            }
-            .boxed_clone(),
-        ),
-        "q!" | "qu!" | "qui!" | "quit!" => (
-            "quit!",
-            workspace::CloseActiveItem {
-                save_intent: Some(SaveIntent::Skip),
-            }
-            .boxed_clone(),
-        ),
-        "wq" => (
-            "wq",
-            workspace::CloseActiveItem {
-                save_intent: Some(SaveIntent::Save),
-            }
-            .boxed_clone(),
-        ),
-        "wq!" => (
-            "wq!",
-            workspace::CloseActiveItem {
-                save_intent: Some(SaveIntent::Overwrite),
-            }
-            .boxed_clone(),
-        ),
-        "x" | "xi" | "xit" | "exi" | "exit" => (
-            "exit",
-            workspace::CloseActiveItem {
-                save_intent: Some(SaveIntent::SaveAll),
-            }
-            .boxed_clone(),
-        ),
-        "x!" | "xi!" | "xit!" | "exi!" | "exit!" => (
-            "exit!",
-            workspace::CloseActiveItem {
-                save_intent: Some(SaveIntent::Overwrite),
-            }
-            .boxed_clone(),
-        ),
-        "up" | "upd" | "upda" | "updat" | "update" => (
-            "update",
-            workspace::Save {
-                save_intent: Some(SaveIntent::SaveAll),
-            }
-            .boxed_clone(),
-        ),
-        "wa" | "wal" | "wall" => (
-            "wall",
-            workspace::SaveAll {
-                save_intent: Some(SaveIntent::SaveAll),
-            }
-            .boxed_clone(),
-        ),
-        "wa!" | "wal!" | "wall!" => (
-            "wall!",
-            workspace::SaveAll {
-                save_intent: Some(SaveIntent::Overwrite),
-            }
-            .boxed_clone(),
-        ),
-        "qa" | "qal" | "qall" | "quita" | "quital" | "quitall" => (
-            "quitall",
-            workspace::CloseAllItemsAndPanes {
-                save_intent: Some(SaveIntent::Close),
-            }
-            .boxed_clone(),
-        ),
-        "qa!" | "qal!" | "qall!" | "quita!" | "quital!" | "quitall!" => (
-            "quitall!",
-            workspace::CloseAllItemsAndPanes {
-                save_intent: Some(SaveIntent::Skip),
-            }
-            .boxed_clone(),
-        ),
-        "xa" | "xal" | "xall" => (
-            "xall",
-            workspace::CloseAllItemsAndPanes {
-                save_intent: Some(SaveIntent::SaveAll),
-            }
-            .boxed_clone(),
-        ),
-        "xa!" | "xal!" | "xall!" => (
-            "xall!",
-            workspace::CloseAllItemsAndPanes {
-                save_intent: Some(SaveIntent::Overwrite),
-            }
-            .boxed_clone(),
-        ),
-        "wqa" | "wqal" | "wqall" => (
-            "wqall",
-            workspace::CloseAllItemsAndPanes {
-                save_intent: Some(SaveIntent::SaveAll),
-            }
-            .boxed_clone(),
-        ),
-        "wqa!" | "wqal!" | "wqall!" => (
-            "wqall!",
-            workspace::CloseAllItemsAndPanes {
-                save_intent: Some(SaveIntent::Overwrite),
-            }
-            .boxed_clone(),
-        ),
-        "cq" | "cqu" | "cqui" | "cquit" | "cq!" | "cqu!" | "cqui!" | "cquit!" => {
-            ("cquit!", zed_actions::Quit.boxed_clone())
-        }
-
-        // pane management
-        "sp" | "spl" | "spli" | "split" => ("split", workspace::SplitUp.boxed_clone()),
-        "vs" | "vsp" | "vspl" | "vspli" | "vsplit" => {
-            ("vsplit", workspace::SplitLeft.boxed_clone())
-        }
-        "new" => (
-            "new",
-            workspace::NewFileInDirection(workspace::SplitDirection::Up).boxed_clone(),
-        ),
-        "vne" | "vnew" => (
-            "vnew",
-            workspace::NewFileInDirection(workspace::SplitDirection::Left).boxed_clone(),
-        ),
-        "tabe" | "tabed" | "tabedi" | "tabedit" => ("tabedit", workspace::NewFile.boxed_clone()),
-        "tabnew" => ("tabnew", workspace::NewFile.boxed_clone()),
-
-        "tabn" | "tabne" | "tabnex" | "tabnext" => {
-            ("tabnext", workspace::ActivateNextItem.boxed_clone())
-        }
-        "tabp" | "tabpr" | "tabpre" | "tabprev" | "tabprevi" | "tabprevio" | "tabpreviou"
-        | "tabprevious" => ("tabprevious", workspace::ActivatePrevItem.boxed_clone()),
-        "tabN" | "tabNe" | "tabNex" | "tabNext" => {
-            ("tabNext", workspace::ActivatePrevItem.boxed_clone())
-        }
-        "tabc" | "tabcl" | "tabclo" | "tabclos" | "tabclose" => (
-            "tabclose",
-            workspace::CloseActiveItem {
-                save_intent: Some(SaveIntent::Close),
-            }
-            .boxed_clone(),
-        ),
-
-        // quickfix / loclist (merged together for now)
-        "cl" | "cli" | "clis" | "clist" => ("clist", diagnostics::Deploy.boxed_clone()),
-        "cc" => ("cc", editor::Hover.boxed_clone()),
-        "ll" => ("ll", editor::Hover.boxed_clone()),
-        "cn" | "cne" | "cnex" | "cnext" => ("cnext", editor::GoToDiagnostic.boxed_clone()),
-        "lne" | "lnex" | "lnext" => ("cnext", editor::GoToDiagnostic.boxed_clone()),
-
-        "cpr" | "cpre" | "cprev" | "cprevi" | "cprevio" | "cpreviou" | "cprevious" => {
-            ("cprevious", editor::GoToPrevDiagnostic.boxed_clone())
-        }
-        "cN" | "cNe" | "cNex" | "cNext" => ("cNext", editor::GoToPrevDiagnostic.boxed_clone()),
-        "lp" | "lpr" | "lpre" | "lprev" | "lprevi" | "lprevio" | "lpreviou" | "lprevious" => {
-            ("lprevious", editor::GoToPrevDiagnostic.boxed_clone())
-        }
-        "lN" | "lNe" | "lNex" | "lNext" => ("lNext", editor::GoToPrevDiagnostic.boxed_clone()),
-
-        // modify the buffer (should accept [range])
-        "j" | "jo" | "joi" | "join" => ("join", JoinLines.boxed_clone()),
-        "d" | "de" | "del" | "dele" | "delet" | "delete" | "dl" | "dell" | "delel" | "deletl"
-        | "deletel" | "dp" | "dep" | "delp" | "delep" | "deletp" | "deletep" => {
-            ("delete", editor::DeleteLine.boxed_clone())
-        }
-        "sor" | "sor " | "sort" | "sort " => ("sort", SortLinesCaseSensitive.boxed_clone()),
-        "sor i" | "sort i" => ("sort i", SortLinesCaseInsensitive.boxed_clone()),
-
-        // goto (other ranges handled under _ => )
-        "$" => ("$", EndOfDocument.boxed_clone()),
-
-        _ => {
-            if query.starts_with("/") || query.starts_with("?") {
-                (
-                    query,
-                    FindCommand {
-                        query: query[1..].to_string(),
-                        backwards: query.starts_with("?"),
-                    }
-                    .boxed_clone(),
-                )
-            } else if query.starts_with("%") {
-                (
-                    query,
-                    ReplaceCommand {
-                        query: query.to_string(),
-                    }
-                    .boxed_clone(),
-                )
-            } else if let Ok(line) = query.parse::<u32>() {
-                (query, GoToLine { line }.boxed_clone())
-            } else {
-                return None;
-            }
-        }
-    };
-
-    let string = ":".to_owned() + name;
-    let positions = generate_positions(&string, query);
-
-    Some(CommandInterceptResult {
-        action,
-        string,
-        positions,
-    })
-}
-
-fn generate_positions(string: &str, query: &str) -> Vec<usize> {
-    let mut positions = Vec::new();
-    let mut chars = query.chars().into_iter();
-
-    let Some(mut current) = chars.next() else {
-        return positions;
-    };
-
-    for (i, c) in string.chars().enumerate() {
-        if c == current {
-            positions.push(i);
-            if let Some(c) = chars.next() {
-                current = c;
-            } else {
-                break;
-            }
-        }
-    }
-
-    positions
-}
-
-#[cfg(test)]
-mod test {
-    use std::path::Path;
-
-    use crate::test::{NeovimBackedTestContext, VimTestContext};
-    use gpui::TestAppContext;
-    use indoc::indoc;
-
-    #[gpui::test]
-    async fn test_command_basics(cx: &mut TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.set_shared_state(indoc! {"
-            ˇa
-            b
-            c"})
-            .await;
-
-        cx.simulate_shared_keystrokes([":", "j", "enter"]).await;
-
-        // hack: our cursor positionining after a join command is wrong
-        cx.simulate_shared_keystrokes(["^"]).await;
-        cx.assert_shared_state(indoc! {
-            "ˇa b
-            c"
-        })
-        .await;
-    }
-
-    #[gpui::test]
-    async fn test_command_goto(cx: &mut TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.set_shared_state(indoc! {"
-            ˇa
-            b
-            c"})
-            .await;
-        cx.simulate_shared_keystrokes([":", "3", "enter"]).await;
-        cx.assert_shared_state(indoc! {"
-            a
-            b
-            ˇc"})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_command_replace(cx: &mut TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.set_shared_state(indoc! {"
-            ˇa
-            b
-            c"})
-            .await;
-        cx.simulate_shared_keystrokes([":", "%", "s", "/", "b", "/", "d", "enter"])
-            .await;
-        cx.assert_shared_state(indoc! {"
-            a
-            ˇd
-            c"})
-            .await;
-        cx.simulate_shared_keystrokes([
-            ":", "%", "s", ":", ".", ":", "\\", "0", "\\", "0", "enter",
-        ])
-        .await;
-        cx.assert_shared_state(indoc! {"
-            aa
-            dd
-            ˇcc"})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_command_search(cx: &mut TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.set_shared_state(indoc! {"
-                ˇa
-                b
-                a
-                c"})
-            .await;
-        cx.simulate_shared_keystrokes([":", "/", "b", "enter"])
-            .await;
-        cx.assert_shared_state(indoc! {"
-                a
-                ˇb
-                a
-                c"})
-            .await;
-        cx.simulate_shared_keystrokes([":", "?", "a", "enter"])
-            .await;
-        cx.assert_shared_state(indoc! {"
-                ˇa
-                b
-                a
-                c"})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_command_write(cx: &mut TestAppContext) {
-        let mut cx = VimTestContext::new(cx, true).await;
-        let path = Path::new("/root/dir/file.rs");
-        let fs = cx.workspace(|workspace, cx| workspace.project().read(cx).fs().clone());
-
-        cx.simulate_keystrokes(["i", "@", "escape"]);
-        cx.simulate_keystrokes([":", "w", "enter"]);
-
-        assert_eq!(fs.load(&path).await.unwrap(), "@\n");
-
-        fs.as_fake()
-            .write_file_internal(path, "oops\n".to_string())
-            .unwrap();
-
-        // conflict!
-        cx.simulate_keystrokes(["i", "@", "escape"]);
-        cx.simulate_keystrokes([":", "w", "enter"]);
-        assert!(cx.has_pending_prompt());
-        // "Cancel"
-        cx.simulate_prompt_answer(0);
-        assert_eq!(fs.load(&path).await.unwrap(), "oops\n");
-        assert!(!cx.has_pending_prompt());
-        // force overwrite
-        cx.simulate_keystrokes([":", "w", "!", "enter"]);
-        assert!(!cx.has_pending_prompt());
-        assert_eq!(fs.load(&path).await.unwrap(), "@@\n");
-    }
-
-    #[gpui::test]
-    async fn test_command_quit(cx: &mut TestAppContext) {
-        let mut cx = VimTestContext::new(cx, true).await;
-
-        cx.simulate_keystrokes([":", "n", "e", "w", "enter"]);
-        cx.workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 2));
-        cx.simulate_keystrokes([":", "q", "enter"]);
-        cx.workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 1));
-        cx.simulate_keystrokes([":", "n", "e", "w", "enter"]);
-        cx.workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 2));
-        cx.simulate_keystrokes([":", "q", "a", "enter"]);
-        cx.workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 0));
-    }
-}

crates/vim2/src/editor_events.rs 🔗

@@ -1,104 +0,0 @@
-use crate::Vim;
-use editor::{Editor, EditorEvent};
-use gpui::{AppContext, Entity, EntityId, View, ViewContext, WindowContext};
-
-pub fn init(cx: &mut AppContext) {
-    cx.observe_new_views(|_, cx: &mut ViewContext<Editor>| {
-        let editor = cx.view().clone();
-        cx.subscribe(&editor, |_, editor, event: &EditorEvent, cx| match event {
-            EditorEvent::Focused => cx.window_context().defer(|cx| focused(editor, cx)),
-            EditorEvent::Blurred => cx.window_context().defer(|cx| blurred(editor, cx)),
-            _ => {}
-        })
-        .detach();
-
-        let id = cx.view().entity_id();
-        cx.on_release(move |_, _, cx| released(id, cx)).detach();
-    })
-    .detach();
-}
-
-fn focused(editor: View<Editor>, cx: &mut WindowContext) {
-    if Vim::read(cx).active_editor.clone().is_some() {
-        Vim::update(cx, |vim, cx| {
-            vim.update_active_editor(cx, |previously_active_editor, cx| {
-                vim.unhook_vim_settings(previously_active_editor, cx)
-            });
-        });
-    }
-
-    Vim::update(cx, |vim, cx| {
-        vim.set_active_editor(editor.clone(), cx);
-    });
-}
-
-fn blurred(editor: View<Editor>, cx: &mut WindowContext) {
-    Vim::update(cx, |vim, cx| {
-        vim.workspace_state.recording = false;
-        vim.workspace_state.recorded_actions.clear();
-        if let Some(previous_editor) = vim.active_editor.clone() {
-            if previous_editor
-                .upgrade()
-                .is_some_and(|previous| previous == editor.clone())
-            {
-                vim.clear_operator(cx);
-                vim.active_editor = None;
-                vim.editor_subscription = None;
-            }
-        }
-
-        editor.update(cx, |editor, cx| vim.unhook_vim_settings(editor, cx))
-    });
-}
-
-fn released(entity_id: EntityId, cx: &mut AppContext) {
-    cx.update_global(|vim: &mut Vim, _| {
-        if vim
-            .active_editor
-            .as_ref()
-            .is_some_and(|previous| previous.entity_id() == entity_id)
-        {
-            vim.active_editor = None;
-            vim.editor_subscription = None;
-        }
-        vim.editor_states.remove(&entity_id)
-    });
-}
-
-#[cfg(test)]
-mod test {
-    use crate::{test::VimTestContext, Vim};
-    use editor::Editor;
-    use gpui::{Context, Entity};
-    use language::Buffer;
-
-    // regression test for blur called with a different active editor
-    #[gpui::test]
-    async fn test_blur_focus(cx: &mut gpui::TestAppContext) {
-        let mut cx = VimTestContext::new(cx, true).await;
-
-        let buffer = cx.new_model(|_| Buffer::new(0, 0, "a = 1\nb = 2\n"));
-        let window2 = cx.add_window(|cx| Editor::for_buffer(buffer, None, cx));
-        let editor2 = cx
-            .update(|cx| {
-                window2.update(cx, |_, cx| {
-                    cx.focus_self();
-                    cx.view().clone()
-                })
-            })
-            .unwrap();
-
-        cx.update(|cx| {
-            let vim = Vim::read(cx);
-            assert_eq!(
-                vim.active_editor.as_ref().unwrap().entity_id(),
-                editor2.entity_id(),
-            )
-        });
-
-        // no panic when blurring an editor in a different window.
-        cx.update_editor(|editor1, cx| {
-            editor1.handle_blur(cx);
-        });
-    }
-}

crates/vim2/src/insert.rs 🔗

@@ -1,125 +0,0 @@
-use crate::{normal::repeat, state::Mode, Vim};
-use editor::{scroll::autoscroll::Autoscroll, Bias};
-use gpui::{actions, Action, ViewContext};
-use language::SelectionGoal;
-use workspace::Workspace;
-
-actions!(vim, [NormalBefore]);
-
-pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
-    workspace.register_action(normal_before);
-}
-
-fn normal_before(_: &mut Workspace, action: &NormalBefore, cx: &mut ViewContext<Workspace>) {
-    let should_repeat = Vim::update(cx, |vim, cx| {
-        let count = vim.take_count(cx).unwrap_or(1);
-        vim.stop_recording_immediately(action.boxed_clone());
-        if count <= 1 || vim.workspace_state.replaying {
-            vim.update_active_editor(cx, |editor, cx| {
-                editor.cancel(&Default::default(), cx);
-                editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                    s.move_cursors_with(|map, mut cursor, _| {
-                        *cursor.column_mut() = cursor.column().saturating_sub(1);
-                        (map.clip_point(cursor, Bias::Left), SelectionGoal::None)
-                    });
-                });
-            });
-            vim.switch_mode(Mode::Normal, false, cx);
-            false
-        } else {
-            true
-        }
-    });
-
-    if should_repeat {
-        repeat::repeat(cx, true)
-    }
-}
-
-#[cfg(test)]
-mod test {
-    use crate::{
-        state::Mode,
-        test::{NeovimBackedTestContext, VimTestContext},
-    };
-
-    #[gpui::test]
-    async fn test_enter_and_exit_insert_mode(cx: &mut gpui::TestAppContext) {
-        let mut cx = VimTestContext::new(cx, true).await;
-        cx.simulate_keystroke("i");
-        assert_eq!(cx.mode(), Mode::Insert);
-        cx.simulate_keystrokes(["T", "e", "s", "t"]);
-        cx.assert_editor_state("Testˇ");
-        cx.simulate_keystroke("escape");
-        assert_eq!(cx.mode(), Mode::Normal);
-        cx.assert_editor_state("Tesˇt");
-    }
-
-    #[gpui::test]
-    async fn test_insert_with_counts(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.set_shared_state("ˇhello\n").await;
-        cx.simulate_shared_keystrokes(["5", "i", "-", "escape"])
-            .await;
-        cx.run_until_parked();
-        cx.assert_shared_state("----ˇ-hello\n").await;
-
-        cx.set_shared_state("ˇhello\n").await;
-        cx.simulate_shared_keystrokes(["5", "a", "-", "escape"])
-            .await;
-        cx.run_until_parked();
-        cx.assert_shared_state("h----ˇ-ello\n").await;
-
-        cx.simulate_shared_keystrokes(["4", "shift-i", "-", "escape"])
-            .await;
-        cx.run_until_parked();
-        cx.assert_shared_state("---ˇ-h-----ello\n").await;
-
-        cx.simulate_shared_keystrokes(["3", "shift-a", "-", "escape"])
-            .await;
-        cx.run_until_parked();
-        cx.assert_shared_state("----h-----ello--ˇ-\n").await;
-
-        cx.set_shared_state("ˇhello\n").await;
-        cx.simulate_shared_keystrokes(["3", "o", "o", "i", "escape"])
-            .await;
-        cx.run_until_parked();
-        cx.assert_shared_state("hello\noi\noi\noˇi\n").await;
-
-        cx.set_shared_state("ˇhello\n").await;
-        cx.simulate_shared_keystrokes(["3", "shift-o", "o", "i", "escape"])
-            .await;
-        cx.run_until_parked();
-        cx.assert_shared_state("oi\noi\noˇi\nhello\n").await;
-    }
-
-    #[gpui::test]
-    async fn test_insert_with_repeat(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.set_shared_state("ˇhello\n").await;
-        cx.simulate_shared_keystrokes(["3", "i", "-", "escape"])
-            .await;
-        cx.run_until_parked();
-        cx.assert_shared_state("--ˇ-hello\n").await;
-        cx.simulate_shared_keystrokes(["."]).await;
-        cx.run_until_parked();
-        cx.assert_shared_state("----ˇ--hello\n").await;
-        cx.simulate_shared_keystrokes(["2", "."]).await;
-        cx.run_until_parked();
-        cx.assert_shared_state("-----ˇ---hello\n").await;
-
-        cx.set_shared_state("ˇhello\n").await;
-        cx.simulate_shared_keystrokes(["2", "o", "k", "k", "escape"])
-            .await;
-        cx.run_until_parked();
-        cx.assert_shared_state("hello\nkk\nkˇk\n").await;
-        cx.simulate_shared_keystrokes(["."]).await;
-        cx.run_until_parked();
-        cx.assert_shared_state("hello\nkk\nkk\nkk\nkˇk\n").await;
-        cx.simulate_shared_keystrokes(["1", "."]).await;
-        cx.run_until_parked();
-        cx.assert_shared_state("hello\nkk\nkk\nkk\nkk\nkˇk\n").await;
-    }
-}

crates/vim2/src/mode_indicator.rs 🔗

@@ -1,74 +0,0 @@
-use gpui::{div, Element, Render, Subscription, ViewContext};
-use settings::SettingsStore;
-use workspace::{item::ItemHandle, ui::prelude::*, StatusItemView};
-
-use crate::{state::Mode, Vim};
-
-pub struct ModeIndicator {
-    pub mode: Option<Mode>,
-    _subscriptions: Vec<Subscription>,
-}
-
-impl ModeIndicator {
-    pub fn new(cx: &mut ViewContext<Self>) -> Self {
-        let _subscriptions = vec![
-            cx.observe_global::<Vim>(|this, cx| this.update_mode(cx)),
-            cx.observe_global::<SettingsStore>(|this, cx| this.update_mode(cx)),
-        ];
-
-        let mut this = Self {
-            mode: None,
-            _subscriptions,
-        };
-        this.update_mode(cx);
-        this
-    }
-
-    fn update_mode(&mut self, cx: &mut ViewContext<Self>) {
-        // Vim doesn't exist in some tests
-        if !cx.has_global::<Vim>() {
-            return;
-        }
-
-        let vim = Vim::read(cx);
-        if vim.enabled {
-            self.mode = Some(vim.state().mode);
-        } else {
-            self.mode = None;
-        }
-    }
-
-    pub fn set_mode(&mut self, mode: Mode, cx: &mut ViewContext<Self>) {
-        if self.mode != Some(mode) {
-            self.mode = Some(mode);
-            cx.notify();
-        }
-    }
-}
-
-impl Render for ModeIndicator {
-    fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
-        let Some(mode) = self.mode.as_ref() else {
-            return div().into_any();
-        };
-
-        let text = match mode {
-            Mode::Normal => "-- NORMAL --",
-            Mode::Insert => "-- INSERT --",
-            Mode::Visual => "-- VISUAL --",
-            Mode::VisualLine => "-- VISUAL LINE --",
-            Mode::VisualBlock => "-- VISUAL BLOCK --",
-        };
-        Label::new(text).size(LabelSize::Small).into_any_element()
-    }
-}
-
-impl StatusItemView for ModeIndicator {
-    fn set_active_pane_item(
-        &mut self,
-        _active_pane_item: Option<&dyn ItemHandle>,
-        _cx: &mut ViewContext<Self>,
-    ) {
-        // nothing to do.
-    }
-}

crates/vim2/src/motion.rs 🔗

@@ -1,1107 +0,0 @@
-use editor::{
-    char_kind,
-    display_map::{DisplaySnapshot, FoldPoint, ToDisplayPoint},
-    movement::{self, find_boundary, find_preceding_boundary, FindRange, TextLayoutDetails},
-    Bias, CharKind, DisplayPoint, ToOffset,
-};
-use gpui::{actions, impl_actions, px, ViewContext, WindowContext};
-use language::{Point, Selection, SelectionGoal};
-use serde::Deserialize;
-use workspace::Workspace;
-
-use crate::{
-    normal::normal_motion,
-    state::{Mode, Operator},
-    visual::visual_motion,
-    Vim,
-};
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub enum Motion {
-    Left,
-    Backspace,
-    Down { display_lines: bool },
-    Up { display_lines: bool },
-    Right,
-    NextWordStart { ignore_punctuation: bool },
-    NextWordEnd { ignore_punctuation: bool },
-    PreviousWordStart { ignore_punctuation: bool },
-    FirstNonWhitespace { display_lines: bool },
-    CurrentLine,
-    StartOfLine { display_lines: bool },
-    EndOfLine { display_lines: bool },
-    StartOfParagraph,
-    EndOfParagraph,
-    StartOfDocument,
-    EndOfDocument,
-    Matching,
-    FindForward { before: bool, char: char },
-    FindBackward { after: bool, char: char },
-    NextLineStart,
-    StartOfLineDownward,
-    EndOfLineDownward,
-    GoToColumn,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-struct NextWordStart {
-    #[serde(default)]
-    ignore_punctuation: bool,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-struct NextWordEnd {
-    #[serde(default)]
-    ignore_punctuation: bool,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-struct PreviousWordStart {
-    #[serde(default)]
-    ignore_punctuation: bool,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-pub(crate) struct Up {
-    #[serde(default)]
-    pub(crate) display_lines: bool,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-struct Down {
-    #[serde(default)]
-    display_lines: bool,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-struct FirstNonWhitespace {
-    #[serde(default)]
-    display_lines: bool,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-struct EndOfLine {
-    #[serde(default)]
-    display_lines: bool,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-pub struct StartOfLine {
-    #[serde(default)]
-    pub(crate) display_lines: bool,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-struct RepeatFind {
-    #[serde(default)]
-    backwards: bool,
-}
-
-impl_actions!(
-    vim,
-    [
-        RepeatFind,
-        StartOfLine,
-        EndOfLine,
-        FirstNonWhitespace,
-        Down,
-        Up,
-        PreviousWordStart,
-        NextWordEnd,
-        NextWordStart
-    ]
-);
-
-actions!(
-    vim,
-    [
-        Left,
-        Backspace,
-        Right,
-        CurrentLine,
-        StartOfParagraph,
-        EndOfParagraph,
-        StartOfDocument,
-        EndOfDocument,
-        Matching,
-        NextLineStart,
-        StartOfLineDownward,
-        EndOfLineDownward,
-        GoToColumn,
-    ]
-);
-
-pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
-    workspace.register_action(|_: &mut Workspace, _: &Left, cx: _| motion(Motion::Left, cx));
-    workspace
-        .register_action(|_: &mut Workspace, _: &Backspace, cx: _| motion(Motion::Backspace, cx));
-    workspace.register_action(|_: &mut Workspace, action: &Down, cx: _| {
-        motion(
-            Motion::Down {
-                display_lines: action.display_lines,
-            },
-            cx,
-        )
-    });
-    workspace.register_action(|_: &mut Workspace, action: &Up, cx: _| {
-        motion(
-            Motion::Up {
-                display_lines: action.display_lines,
-            },
-            cx,
-        )
-    });
-    workspace.register_action(|_: &mut Workspace, _: &Right, cx: _| motion(Motion::Right, cx));
-    workspace.register_action(|_: &mut Workspace, action: &FirstNonWhitespace, cx: _| {
-        motion(
-            Motion::FirstNonWhitespace {
-                display_lines: action.display_lines,
-            },
-            cx,
-        )
-    });
-    workspace.register_action(|_: &mut Workspace, action: &StartOfLine, cx: _| {
-        motion(
-            Motion::StartOfLine {
-                display_lines: action.display_lines,
-            },
-            cx,
-        )
-    });
-    workspace.register_action(|_: &mut Workspace, action: &EndOfLine, cx: _| {
-        motion(
-            Motion::EndOfLine {
-                display_lines: action.display_lines,
-            },
-            cx,
-        )
-    });
-    workspace.register_action(|_: &mut Workspace, _: &CurrentLine, cx: _| {
-        motion(Motion::CurrentLine, cx)
-    });
-    workspace.register_action(|_: &mut Workspace, _: &StartOfParagraph, cx: _| {
-        motion(Motion::StartOfParagraph, cx)
-    });
-    workspace.register_action(|_: &mut Workspace, _: &EndOfParagraph, cx: _| {
-        motion(Motion::EndOfParagraph, cx)
-    });
-    workspace.register_action(|_: &mut Workspace, _: &StartOfDocument, cx: _| {
-        motion(Motion::StartOfDocument, cx)
-    });
-    workspace.register_action(|_: &mut Workspace, _: &EndOfDocument, cx: _| {
-        motion(Motion::EndOfDocument, cx)
-    });
-    workspace
-        .register_action(|_: &mut Workspace, _: &Matching, cx: _| motion(Motion::Matching, cx));
-
-    workspace.register_action(
-        |_: &mut Workspace, &NextWordStart { ignore_punctuation }: &NextWordStart, cx: _| {
-            motion(Motion::NextWordStart { ignore_punctuation }, cx)
-        },
-    );
-    workspace.register_action(
-        |_: &mut Workspace, &NextWordEnd { ignore_punctuation }: &NextWordEnd, cx: _| {
-            motion(Motion::NextWordEnd { ignore_punctuation }, cx)
-        },
-    );
-    workspace.register_action(
-        |_: &mut Workspace,
-         &PreviousWordStart { ignore_punctuation }: &PreviousWordStart,
-         cx: _| { motion(Motion::PreviousWordStart { ignore_punctuation }, cx) },
-    );
-    workspace.register_action(|_: &mut Workspace, &NextLineStart, cx: _| {
-        motion(Motion::NextLineStart, cx)
-    });
-    workspace.register_action(|_: &mut Workspace, &StartOfLineDownward, cx: _| {
-        motion(Motion::StartOfLineDownward, cx)
-    });
-    workspace.register_action(|_: &mut Workspace, &EndOfLineDownward, cx: _| {
-        motion(Motion::EndOfLineDownward, cx)
-    });
-    workspace
-        .register_action(|_: &mut Workspace, &GoToColumn, cx: _| motion(Motion::GoToColumn, cx));
-    workspace.register_action(|_: &mut Workspace, action: &RepeatFind, cx: _| {
-        repeat_motion(action.backwards, cx)
-    });
-}
-
-pub(crate) fn motion(motion: Motion, cx: &mut WindowContext) {
-    if let Some(Operator::FindForward { .. }) | Some(Operator::FindBackward { .. }) =
-        Vim::read(cx).active_operator()
-    {
-        Vim::update(cx, |vim, cx| vim.pop_operator(cx));
-    }
-
-    let count = Vim::update(cx, |vim, cx| vim.take_count(cx));
-    let operator = Vim::read(cx).active_operator();
-    match Vim::read(cx).state().mode {
-        Mode::Normal => normal_motion(motion, operator, count, cx),
-        Mode::Visual | Mode::VisualLine | Mode::VisualBlock => visual_motion(motion, count, cx),
-        Mode::Insert => {
-            // Shouldn't execute a motion in insert mode. Ignoring
-        }
-    }
-    Vim::update(cx, |vim, cx| vim.clear_operator(cx));
-}
-
-fn repeat_motion(backwards: bool, cx: &mut WindowContext) {
-    let find = match Vim::read(cx).workspace_state.last_find.clone() {
-        Some(Motion::FindForward { before, char }) => {
-            if backwards {
-                Motion::FindBackward {
-                    after: before,
-                    char,
-                }
-            } else {
-                Motion::FindForward { before, char }
-            }
-        }
-
-        Some(Motion::FindBackward { after, char }) => {
-            if backwards {
-                Motion::FindForward {
-                    before: after,
-                    char,
-                }
-            } else {
-                Motion::FindBackward { after, char }
-            }
-        }
-        _ => return,
-    };
-
-    motion(find, cx)
-}
-
-// Motion handling is specified here:
-// https://github.com/vim/vim/blob/master/runtime/doc/motion.txt
-impl Motion {
-    pub fn linewise(&self) -> bool {
-        use Motion::*;
-        match self {
-            Down { .. }
-            | Up { .. }
-            | StartOfDocument
-            | EndOfDocument
-            | CurrentLine
-            | NextLineStart
-            | StartOfLineDownward
-            | StartOfParagraph
-            | EndOfParagraph => true,
-            EndOfLine { .. }
-            | NextWordEnd { .. }
-            | Matching
-            | FindForward { .. }
-            | Left
-            | Backspace
-            | Right
-            | StartOfLine { .. }
-            | EndOfLineDownward
-            | GoToColumn
-            | NextWordStart { .. }
-            | PreviousWordStart { .. }
-            | FirstNonWhitespace { .. }
-            | FindBackward { .. } => false,
-        }
-    }
-
-    pub fn infallible(&self) -> bool {
-        use Motion::*;
-        match self {
-            StartOfDocument | EndOfDocument | CurrentLine => true,
-            Down { .. }
-            | Up { .. }
-            | EndOfLine { .. }
-            | NextWordEnd { .. }
-            | Matching
-            | FindForward { .. }
-            | Left
-            | Backspace
-            | Right
-            | StartOfLine { .. }
-            | StartOfParagraph
-            | EndOfParagraph
-            | StartOfLineDownward
-            | EndOfLineDownward
-            | GoToColumn
-            | NextWordStart { .. }
-            | PreviousWordStart { .. }
-            | FirstNonWhitespace { .. }
-            | FindBackward { .. }
-            | NextLineStart => false,
-        }
-    }
-
-    pub fn inclusive(&self) -> bool {
-        use Motion::*;
-        match self {
-            Down { .. }
-            | Up { .. }
-            | StartOfDocument
-            | EndOfDocument
-            | CurrentLine
-            | EndOfLine { .. }
-            | EndOfLineDownward
-            | NextWordEnd { .. }
-            | Matching
-            | FindForward { .. }
-            | NextLineStart => true,
-            Left
-            | Backspace
-            | Right
-            | StartOfLine { .. }
-            | StartOfLineDownward
-            | StartOfParagraph
-            | EndOfParagraph
-            | GoToColumn
-            | NextWordStart { .. }
-            | PreviousWordStart { .. }
-            | FirstNonWhitespace { .. }
-            | FindBackward { .. } => false,
-        }
-    }
-
-    pub fn move_point(
-        &self,
-        map: &DisplaySnapshot,
-        point: DisplayPoint,
-        goal: SelectionGoal,
-        maybe_times: Option<usize>,
-        text_layout_details: &TextLayoutDetails,
-    ) -> Option<(DisplayPoint, SelectionGoal)> {
-        let times = maybe_times.unwrap_or(1);
-        use Motion::*;
-        let infallible = self.infallible();
-        let (new_point, goal) = match self {
-            Left => (left(map, point, times), SelectionGoal::None),
-            Backspace => (backspace(map, point, times), SelectionGoal::None),
-            Down {
-                display_lines: false,
-            } => up_down_buffer_rows(map, point, goal, times as isize, &text_layout_details),
-            Down {
-                display_lines: true,
-            } => down_display(map, point, goal, times, &text_layout_details),
-            Up {
-                display_lines: false,
-            } => up_down_buffer_rows(map, point, goal, 0 - times as isize, &text_layout_details),
-            Up {
-                display_lines: true,
-            } => up_display(map, point, goal, times, &text_layout_details),
-            Right => (right(map, point, times), SelectionGoal::None),
-            NextWordStart { ignore_punctuation } => (
-                next_word_start(map, point, *ignore_punctuation, times),
-                SelectionGoal::None,
-            ),
-            NextWordEnd { ignore_punctuation } => (
-                next_word_end(map, point, *ignore_punctuation, times),
-                SelectionGoal::None,
-            ),
-            PreviousWordStart { ignore_punctuation } => (
-                previous_word_start(map, point, *ignore_punctuation, times),
-                SelectionGoal::None,
-            ),
-            FirstNonWhitespace { display_lines } => (
-                first_non_whitespace(map, *display_lines, point),
-                SelectionGoal::None,
-            ),
-            StartOfLine { display_lines } => (
-                start_of_line(map, *display_lines, point),
-                SelectionGoal::None,
-            ),
-            EndOfLine { display_lines } => {
-                (end_of_line(map, *display_lines, point), SelectionGoal::None)
-            }
-            StartOfParagraph => (
-                movement::start_of_paragraph(map, point, times),
-                SelectionGoal::None,
-            ),
-            EndOfParagraph => (
-                map.clip_at_line_end(movement::end_of_paragraph(map, point, times)),
-                SelectionGoal::None,
-            ),
-            CurrentLine => (next_line_end(map, point, times), SelectionGoal::None),
-            StartOfDocument => (start_of_document(map, point, times), SelectionGoal::None),
-            EndOfDocument => (
-                end_of_document(map, point, maybe_times),
-                SelectionGoal::None,
-            ),
-            Matching => (matching(map, point), SelectionGoal::None),
-            FindForward { before, char } => (
-                find_forward(map, point, *before, *char, times),
-                SelectionGoal::None,
-            ),
-            FindBackward { after, char } => (
-                find_backward(map, point, *after, *char, times),
-                SelectionGoal::None,
-            ),
-            NextLineStart => (next_line_start(map, point, times), SelectionGoal::None),
-            StartOfLineDownward => (next_line_start(map, point, times - 1), SelectionGoal::None),
-            EndOfLineDownward => (next_line_end(map, point, times), SelectionGoal::None),
-            GoToColumn => (go_to_column(map, point, times), SelectionGoal::None),
-        };
-
-        (new_point != point || infallible).then_some((new_point, goal))
-    }
-
-    // Expands a selection using self motion for an operator
-    pub fn expand_selection(
-        &self,
-        map: &DisplaySnapshot,
-        selection: &mut Selection<DisplayPoint>,
-        times: Option<usize>,
-        expand_to_surrounding_newline: bool,
-        text_layout_details: &TextLayoutDetails,
-    ) -> bool {
-        if let Some((new_head, goal)) = self.move_point(
-            map,
-            selection.head(),
-            selection.goal,
-            times,
-            &text_layout_details,
-        ) {
-            selection.set_head(new_head, goal);
-
-            if self.linewise() {
-                selection.start = map.prev_line_boundary(selection.start.to_point(map)).1;
-
-                if expand_to_surrounding_newline {
-                    if selection.end.row() < map.max_point().row() {
-                        *selection.end.row_mut() += 1;
-                        *selection.end.column_mut() = 0;
-                        selection.end = map.clip_point(selection.end, Bias::Right);
-                        // Don't reset the end here
-                        return true;
-                    } else if selection.start.row() > 0 {
-                        *selection.start.row_mut() -= 1;
-                        *selection.start.column_mut() = map.line_len(selection.start.row());
-                        selection.start = map.clip_point(selection.start, Bias::Left);
-                    }
-                }
-
-                (_, selection.end) = map.next_line_boundary(selection.end.to_point(map));
-            } else {
-                // Another special case: When using the "w" motion in combination with an
-                // operator and the last word moved over is at the end of a line, the end of
-                // that word becomes the end of the operated text, not the first word in the
-                // next line.
-                if let Motion::NextWordStart {
-                    ignore_punctuation: _,
-                } = self
-                {
-                    let start_row = selection.start.to_point(&map).row;
-                    if selection.end.to_point(&map).row > start_row {
-                        selection.end =
-                            Point::new(start_row, map.buffer_snapshot.line_len(start_row))
-                                .to_display_point(&map)
-                    }
-                }
-
-                // If the motion is exclusive and the end of the motion is in column 1, the
-                // end of the motion is moved to the end of the previous line and the motion
-                // becomes inclusive. Example: "}" moves to the first line after a paragraph,
-                // but "d}" will not include that line.
-                let mut inclusive = self.inclusive();
-                if !inclusive
-                    && self != &Motion::Backspace
-                    && selection.end.row() > selection.start.row()
-                    && selection.end.column() == 0
-                {
-                    inclusive = true;
-                    *selection.end.row_mut() -= 1;
-                    *selection.end.column_mut() = 0;
-                    selection.end = map.clip_point(
-                        map.next_line_boundary(selection.end.to_point(map)).1,
-                        Bias::Left,
-                    );
-                }
-
-                if inclusive && selection.end.column() < map.line_len(selection.end.row()) {
-                    *selection.end.column_mut() += 1;
-                }
-            }
-            true
-        } else {
-            false
-        }
-    }
-}
-
-fn left(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> DisplayPoint {
-    for _ in 0..times {
-        point = movement::saturating_left(map, point);
-        if point.column() == 0 {
-            break;
-        }
-    }
-    point
-}
-
-fn backspace(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> DisplayPoint {
-    for _ in 0..times {
-        point = movement::left(map, point);
-    }
-    point
-}
-
-pub(crate) fn start_of_relative_buffer_row(
-    map: &DisplaySnapshot,
-    point: DisplayPoint,
-    times: isize,
-) -> DisplayPoint {
-    let start = map.display_point_to_fold_point(point, Bias::Left);
-    let target = start.row() as isize + times;
-    let new_row = (target.max(0) as u32).min(map.fold_snapshot.max_point().row());
-
-    map.clip_point(
-        map.fold_point_to_display_point(
-            map.fold_snapshot
-                .clip_point(FoldPoint::new(new_row, 0), Bias::Right),
-        ),
-        Bias::Right,
-    )
-}
-
-fn up_down_buffer_rows(
-    map: &DisplaySnapshot,
-    point: DisplayPoint,
-    mut goal: SelectionGoal,
-    times: isize,
-    text_layout_details: &TextLayoutDetails,
-) -> (DisplayPoint, SelectionGoal) {
-    let start = map.display_point_to_fold_point(point, Bias::Left);
-    let begin_folded_line = map.fold_point_to_display_point(
-        map.fold_snapshot
-            .clip_point(FoldPoint::new(start.row(), 0), Bias::Left),
-    );
-    let select_nth_wrapped_row = point.row() - begin_folded_line.row();
-
-    let (goal_wrap, goal_x) = match goal {
-        SelectionGoal::WrappedHorizontalPosition((row, x)) => (row, x),
-        SelectionGoal::HorizontalRange { end, .. } => (select_nth_wrapped_row, end),
-        SelectionGoal::HorizontalPosition(x) => (select_nth_wrapped_row, x),
-        _ => {
-            let x = map.x_for_display_point(point, text_layout_details);
-            goal = SelectionGoal::WrappedHorizontalPosition((select_nth_wrapped_row, x.0));
-            (select_nth_wrapped_row, x.0)
-        }
-    };
-
-    let target = start.row() as isize + times;
-    let new_row = (target.max(0) as u32).min(map.fold_snapshot.max_point().row());
-
-    let mut begin_folded_line = map.fold_point_to_display_point(
-        map.fold_snapshot
-            .clip_point(FoldPoint::new(new_row, 0), Bias::Left),
-    );
-
-    let mut i = 0;
-    while i < goal_wrap && begin_folded_line.row() < map.max_point().row() {
-        let next_folded_line = DisplayPoint::new(begin_folded_line.row() + 1, 0);
-        if map
-            .display_point_to_fold_point(next_folded_line, Bias::Right)
-            .row()
-            == new_row
-        {
-            i += 1;
-            begin_folded_line = next_folded_line;
-        } else {
-            break;
-        }
-    }
-
-    let new_col = if i == goal_wrap {
-        map.display_column_for_x(begin_folded_line.row(), px(goal_x), text_layout_details)
-    } else {
-        map.line_len(begin_folded_line.row())
-    };
-
-    (
-        map.clip_point(
-            DisplayPoint::new(begin_folded_line.row(), new_col),
-            Bias::Left,
-        ),
-        goal,
-    )
-}
-
-fn down_display(
-    map: &DisplaySnapshot,
-    mut point: DisplayPoint,
-    mut goal: SelectionGoal,
-    times: usize,
-    text_layout_details: &TextLayoutDetails,
-) -> (DisplayPoint, SelectionGoal) {
-    for _ in 0..times {
-        (point, goal) = movement::down(map, point, goal, true, text_layout_details);
-    }
-
-    (point, goal)
-}
-
-fn up_display(
-    map: &DisplaySnapshot,
-    mut point: DisplayPoint,
-    mut goal: SelectionGoal,
-    times: usize,
-    text_layout_details: &TextLayoutDetails,
-) -> (DisplayPoint, SelectionGoal) {
-    for _ in 0..times {
-        (point, goal) = movement::up(map, point, goal, true, &text_layout_details);
-    }
-
-    (point, goal)
-}
-
-pub(crate) fn right(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> DisplayPoint {
-    for _ in 0..times {
-        let new_point = movement::saturating_right(map, point);
-        if point == new_point {
-            break;
-        }
-        point = new_point;
-    }
-    point
-}
-
-pub(crate) fn next_word_start(
-    map: &DisplaySnapshot,
-    mut point: DisplayPoint,
-    ignore_punctuation: bool,
-    times: usize,
-) -> DisplayPoint {
-    let scope = map.buffer_snapshot.language_scope_at(point.to_point(map));
-    for _ in 0..times {
-        let mut crossed_newline = false;
-        point = movement::find_boundary(map, point, FindRange::MultiLine, |left, right| {
-            let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation);
-            let right_kind = char_kind(&scope, right).coerce_punctuation(ignore_punctuation);
-            let at_newline = right == '\n';
-
-            let found = (left_kind != right_kind && right_kind != CharKind::Whitespace)
-                || at_newline && crossed_newline
-                || at_newline && left == '\n'; // Prevents skipping repeated empty lines
-
-            crossed_newline |= at_newline;
-            found
-        })
-    }
-    point
-}
-
-fn next_word_end(
-    map: &DisplaySnapshot,
-    mut point: DisplayPoint,
-    ignore_punctuation: bool,
-    times: usize,
-) -> DisplayPoint {
-    let scope = map.buffer_snapshot.language_scope_at(point.to_point(map));
-    for _ in 0..times {
-        if point.column() < map.line_len(point.row()) {
-            *point.column_mut() += 1;
-        } else if point.row() < map.max_buffer_row() {
-            *point.row_mut() += 1;
-            *point.column_mut() = 0;
-        }
-        point = movement::find_boundary(map, point, FindRange::MultiLine, |left, right| {
-            let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation);
-            let right_kind = char_kind(&scope, right).coerce_punctuation(ignore_punctuation);
-
-            left_kind != right_kind && left_kind != CharKind::Whitespace
-        });
-
-        // find_boundary clips, so if the character after the next character is a newline or at the end of the document, we know
-        // we have backtracked already
-        if !map
-            .chars_at(point)
-            .nth(1)
-            .map(|(c, _)| c == '\n')
-            .unwrap_or(true)
-        {
-            *point.column_mut() = point.column().saturating_sub(1);
-        }
-        point = map.clip_point(point, Bias::Left);
-    }
-    point
-}
-
-fn previous_word_start(
-    map: &DisplaySnapshot,
-    mut point: DisplayPoint,
-    ignore_punctuation: bool,
-    times: usize,
-) -> DisplayPoint {
-    let scope = map.buffer_snapshot.language_scope_at(point.to_point(map));
-    for _ in 0..times {
-        // This works even though find_preceding_boundary is called for every character in the line containing
-        // cursor because the newline is checked only once.
-        point =
-            movement::find_preceding_boundary(map, point, FindRange::MultiLine, |left, right| {
-                let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation);
-                let right_kind = char_kind(&scope, right).coerce_punctuation(ignore_punctuation);
-
-                (left_kind != right_kind && !right.is_whitespace()) || left == '\n'
-            });
-    }
-    point
-}
-
-pub(crate) fn first_non_whitespace(
-    map: &DisplaySnapshot,
-    display_lines: bool,
-    from: DisplayPoint,
-) -> DisplayPoint {
-    let mut last_point = start_of_line(map, display_lines, from);
-    let scope = map.buffer_snapshot.language_scope_at(from.to_point(map));
-    for (ch, point) in map.chars_at(last_point) {
-        if ch == '\n' {
-            return from;
-        }
-
-        last_point = point;
-
-        if char_kind(&scope, ch) != CharKind::Whitespace {
-            break;
-        }
-    }
-
-    map.clip_point(last_point, Bias::Left)
-}
-
-pub(crate) fn start_of_line(
-    map: &DisplaySnapshot,
-    display_lines: bool,
-    point: DisplayPoint,
-) -> DisplayPoint {
-    if display_lines {
-        map.clip_point(DisplayPoint::new(point.row(), 0), Bias::Right)
-    } else {
-        map.prev_line_boundary(point.to_point(map)).1
-    }
-}
-
-pub(crate) fn end_of_line(
-    map: &DisplaySnapshot,
-    display_lines: bool,
-    point: DisplayPoint,
-) -> DisplayPoint {
-    if display_lines {
-        map.clip_point(
-            DisplayPoint::new(point.row(), map.line_len(point.row())),
-            Bias::Left,
-        )
-    } else {
-        map.clip_point(map.next_line_boundary(point.to_point(map)).1, Bias::Left)
-    }
-}
-
-fn start_of_document(map: &DisplaySnapshot, point: DisplayPoint, line: usize) -> DisplayPoint {
-    let mut new_point = Point::new((line - 1) as u32, 0).to_display_point(map);
-    *new_point.column_mut() = point.column();
-    map.clip_point(new_point, Bias::Left)
-}
-
-fn end_of_document(
-    map: &DisplaySnapshot,
-    point: DisplayPoint,
-    line: Option<usize>,
-) -> DisplayPoint {
-    let new_row = if let Some(line) = line {
-        (line - 1) as u32
-    } else {
-        map.max_buffer_row()
-    };
-
-    let new_point = Point::new(new_row, point.column());
-    map.clip_point(new_point.to_display_point(map), Bias::Left)
-}
-
-fn matching(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint {
-    // https://github.com/vim/vim/blob/1d87e11a1ef201b26ed87585fba70182ad0c468a/runtime/doc/motion.txt#L1200
-    let point = display_point.to_point(map);
-    let offset = point.to_offset(&map.buffer_snapshot);
-
-    // Ensure the range is contained by the current line.
-    let mut line_end = map.next_line_boundary(point).0;
-    if line_end == point {
-        line_end = map.max_point().to_point(map);
-    }
-
-    let line_range = map.prev_line_boundary(point).0..line_end;
-    let visible_line_range =
-        line_range.start..Point::new(line_range.end.row, line_range.end.column.saturating_sub(1));
-    let ranges = map
-        .buffer_snapshot
-        .bracket_ranges(visible_line_range.clone());
-    if let Some(ranges) = ranges {
-        let line_range = line_range.start.to_offset(&map.buffer_snapshot)
-            ..line_range.end.to_offset(&map.buffer_snapshot);
-        let mut closest_pair_destination = None;
-        let mut closest_distance = usize::MAX;
-
-        for (open_range, close_range) in ranges {
-            if open_range.start >= offset && line_range.contains(&open_range.start) {
-                let distance = open_range.start - offset;
-                if distance < closest_distance {
-                    closest_pair_destination = Some(close_range.start);
-                    closest_distance = distance;
-                    continue;
-                }
-            }
-
-            if close_range.start >= offset && line_range.contains(&close_range.start) {
-                let distance = close_range.start - offset;
-                if distance < closest_distance {
-                    closest_pair_destination = Some(open_range.start);
-                    closest_distance = distance;
-                    continue;
-                }
-            }
-
-            continue;
-        }
-
-        closest_pair_destination
-            .map(|destination| destination.to_display_point(map))
-            .unwrap_or(display_point)
-    } else {
-        display_point
-    }
-}
-
-fn find_forward(
-    map: &DisplaySnapshot,
-    from: DisplayPoint,
-    before: bool,
-    target: char,
-    times: usize,
-) -> DisplayPoint {
-    let mut to = from;
-    let mut found = false;
-
-    for _ in 0..times {
-        found = false;
-        to = find_boundary(map, to, FindRange::SingleLine, |_, right| {
-            found = right == target;
-            found
-        });
-    }
-
-    if found {
-        if before && to.column() > 0 {
-            *to.column_mut() -= 1;
-            map.clip_point(to, Bias::Left)
-        } else {
-            to
-        }
-    } else {
-        from
-    }
-}
-
-fn find_backward(
-    map: &DisplaySnapshot,
-    from: DisplayPoint,
-    after: bool,
-    target: char,
-    times: usize,
-) -> DisplayPoint {
-    let mut to = from;
-
-    for _ in 0..times {
-        to = find_preceding_boundary(map, to, FindRange::SingleLine, |_, right| right == target);
-    }
-
-    if map.buffer_snapshot.chars_at(to.to_point(map)).next() == Some(target) {
-        if after {
-            *to.column_mut() += 1;
-            map.clip_point(to, Bias::Right)
-        } else {
-            to
-        }
-    } else {
-        from
-    }
-}
-
-fn next_line_start(map: &DisplaySnapshot, point: DisplayPoint, times: usize) -> DisplayPoint {
-    let correct_line = start_of_relative_buffer_row(map, point, times as isize);
-    first_non_whitespace(map, false, correct_line)
-}
-
-fn go_to_column(map: &DisplaySnapshot, point: DisplayPoint, times: usize) -> DisplayPoint {
-    let correct_line = start_of_relative_buffer_row(map, point, 0);
-    right(map, correct_line, times.saturating_sub(1))
-}
-
-pub(crate) fn next_line_end(
-    map: &DisplaySnapshot,
-    mut point: DisplayPoint,
-    times: usize,
-) -> DisplayPoint {
-    if times > 1 {
-        point = start_of_relative_buffer_row(map, point, times as isize - 1);
-    }
-    end_of_line(map, false, point)
-}
-
-#[cfg(test)]
-mod test {
-
-    use crate::test::NeovimBackedTestContext;
-    use indoc::indoc;
-
-    #[gpui::test]
-    async fn test_start_end_of_paragraph(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        let initial_state = indoc! {r"ˇabc
-            def
-
-            paragraph
-            the second
-
-
-
-            third and
-            final"};
-
-        // goes down once
-        cx.set_shared_state(initial_state).await;
-        cx.simulate_shared_keystrokes(["}"]).await;
-        cx.assert_shared_state(indoc! {r"abc
-            def
-            ˇ
-            paragraph
-            the second
-
-
-
-            third and
-            final"})
-            .await;
-
-        // goes up once
-        cx.simulate_shared_keystrokes(["{"]).await;
-        cx.assert_shared_state(initial_state).await;
-
-        // goes down twice
-        cx.simulate_shared_keystrokes(["2", "}"]).await;
-        cx.assert_shared_state(indoc! {r"abc
-            def
-
-            paragraph
-            the second
-            ˇ
-
-
-            third and
-            final"})
-            .await;
-
-        // goes down over multiple blanks
-        cx.simulate_shared_keystrokes(["}"]).await;
-        cx.assert_shared_state(indoc! {r"abc
-                def
-
-                paragraph
-                the second
-
-
-
-                third and
-                finaˇl"})
-            .await;
-
-        // goes up twice
-        cx.simulate_shared_keystrokes(["2", "{"]).await;
-        cx.assert_shared_state(indoc! {r"abc
-                def
-                ˇ
-                paragraph
-                the second
-
-
-
-                third and
-                final"})
-            .await
-    }
-
-    #[gpui::test]
-    async fn test_matching(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.set_shared_state(indoc! {r"func ˇ(a string) {
-                do(something(with<Types>.and_arrays[0, 2]))
-            }"})
-            .await;
-        cx.simulate_shared_keystrokes(["%"]).await;
-        cx.assert_shared_state(indoc! {r"func (a stringˇ) {
-                do(something(with<Types>.and_arrays[0, 2]))
-            }"})
-            .await;
-
-        // test it works on the last character of the line
-        cx.set_shared_state(indoc! {r"func (a string) ˇ{
-            do(something(with<Types>.and_arrays[0, 2]))
-            }"})
-            .await;
-        cx.simulate_shared_keystrokes(["%"]).await;
-        cx.assert_shared_state(indoc! {r"func (a string) {
-            do(something(with<Types>.and_arrays[0, 2]))
-            ˇ}"})
-            .await;
-
-        // test it works on immediate nesting
-        cx.set_shared_state("ˇ{()}").await;
-        cx.simulate_shared_keystrokes(["%"]).await;
-        cx.assert_shared_state("{()ˇ}").await;
-        cx.simulate_shared_keystrokes(["%"]).await;
-        cx.assert_shared_state("ˇ{()}").await;
-
-        // test it works on immediate nesting inside braces
-        cx.set_shared_state("{\n    ˇ{()}\n}").await;
-        cx.simulate_shared_keystrokes(["%"]).await;
-        cx.assert_shared_state("{\n    {()ˇ}\n}").await;
-
-        // test it jumps to the next paren on a line
-        cx.set_shared_state("func ˇboop() {\n}").await;
-        cx.simulate_shared_keystrokes(["%"]).await;
-        cx.assert_shared_state("func boop(ˇ) {\n}").await;
-    }
-
-    #[gpui::test]
-    async fn test_comma_semicolon(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.set_shared_state("ˇone two three four").await;
-        cx.simulate_shared_keystrokes(["f", "o"]).await;
-        cx.assert_shared_state("one twˇo three four").await;
-        cx.simulate_shared_keystrokes([","]).await;
-        cx.assert_shared_state("ˇone two three four").await;
-        cx.simulate_shared_keystrokes(["2", ";"]).await;
-        cx.assert_shared_state("one two three fˇour").await;
-        cx.simulate_shared_keystrokes(["shift-t", "e"]).await;
-        cx.assert_shared_state("one two threeˇ four").await;
-        cx.simulate_shared_keystrokes(["3", ";"]).await;
-        cx.assert_shared_state("oneˇ two three four").await;
-        cx.simulate_shared_keystrokes([","]).await;
-        cx.assert_shared_state("one two thˇree four").await;
-    }
-
-    #[gpui::test]
-    async fn test_next_line_start(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-        cx.set_shared_state("ˇone\n  two\nthree").await;
-        cx.simulate_shared_keystrokes(["enter"]).await;
-        cx.assert_shared_state("one\n  ˇtwo\nthree").await;
-    }
-}

crates/vim2/src/normal.rs 🔗

@@ -1,910 +0,0 @@
-mod case;
-mod change;
-mod delete;
-mod increment;
-mod paste;
-pub(crate) mod repeat;
-mod scroll;
-pub(crate) mod search;
-pub mod substitute;
-mod yank;
-
-use std::sync::Arc;
-
-use crate::{
-    motion::{self, first_non_whitespace, next_line_end, right, Motion},
-    object::Object,
-    state::{Mode, Operator},
-    Vim,
-};
-use collections::HashSet;
-use editor::scroll::autoscroll::Autoscroll;
-use editor::{Bias, DisplayPoint};
-use gpui::{actions, ViewContext, WindowContext};
-use language::SelectionGoal;
-use log::error;
-use workspace::Workspace;
-
-use self::{
-    case::change_case,
-    change::{change_motion, change_object},
-    delete::{delete_motion, delete_object},
-    yank::{yank_motion, yank_object},
-};
-
-actions!(
-    vim,
-    [
-        InsertAfter,
-        InsertBefore,
-        InsertFirstNonWhitespace,
-        InsertEndOfLine,
-        InsertLineAbove,
-        InsertLineBelow,
-        DeleteLeft,
-        DeleteRight,
-        ChangeToEndOfLine,
-        DeleteToEndOfLine,
-        Yank,
-        YankLine,
-        ChangeCase,
-        JoinLines,
-    ]
-);
-
-pub(crate) fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
-    workspace.register_action(insert_after);
-    workspace.register_action(insert_before);
-    workspace.register_action(insert_first_non_whitespace);
-    workspace.register_action(insert_end_of_line);
-    workspace.register_action(insert_line_above);
-    workspace.register_action(insert_line_below);
-    workspace.register_action(change_case);
-    workspace.register_action(yank_line);
-
-    workspace.register_action(|_: &mut Workspace, _: &DeleteLeft, cx| {
-        Vim::update(cx, |vim, cx| {
-            vim.record_current_action(cx);
-            let times = vim.take_count(cx);
-            delete_motion(vim, Motion::Left, times, cx);
-        })
-    });
-    workspace.register_action(|_: &mut Workspace, _: &DeleteRight, cx| {
-        Vim::update(cx, |vim, cx| {
-            vim.record_current_action(cx);
-            let times = vim.take_count(cx);
-            delete_motion(vim, Motion::Right, times, cx);
-        })
-    });
-    workspace.register_action(|_: &mut Workspace, _: &ChangeToEndOfLine, cx| {
-        Vim::update(cx, |vim, cx| {
-            vim.start_recording(cx);
-            let times = vim.take_count(cx);
-            change_motion(
-                vim,
-                Motion::EndOfLine {
-                    display_lines: false,
-                },
-                times,
-                cx,
-            );
-        })
-    });
-    workspace.register_action(|_: &mut Workspace, _: &DeleteToEndOfLine, cx| {
-        Vim::update(cx, |vim, cx| {
-            vim.record_current_action(cx);
-            let times = vim.take_count(cx);
-            delete_motion(
-                vim,
-                Motion::EndOfLine {
-                    display_lines: false,
-                },
-                times,
-                cx,
-            );
-        })
-    });
-    workspace.register_action(|_: &mut Workspace, _: &JoinLines, cx| {
-        Vim::update(cx, |vim, cx| {
-            vim.record_current_action(cx);
-            let mut times = vim.take_count(cx).unwrap_or(1);
-            if vim.state().mode.is_visual() {
-                times = 1;
-            } else if times > 1 {
-                // 2J joins two lines together (same as J or 1J)
-                times -= 1;
-            }
-
-            vim.update_active_editor(cx, |editor, cx| {
-                editor.transact(cx, |editor, cx| {
-                    for _ in 0..times {
-                        editor.join_lines(&Default::default(), cx)
-                    }
-                })
-            })
-        });
-    });
-
-    paste::register(workspace, cx);
-    repeat::register(workspace, cx);
-    scroll::register(workspace, cx);
-    search::register(workspace, cx);
-    substitute::register(workspace, cx);
-    increment::register(workspace, cx);
-}
-
-pub fn normal_motion(
-    motion: Motion,
-    operator: Option<Operator>,
-    times: Option<usize>,
-    cx: &mut WindowContext,
-) {
-    Vim::update(cx, |vim, cx| {
-        match operator {
-            None => move_cursor(vim, motion, times, cx),
-            Some(Operator::Change) => change_motion(vim, motion, times, cx),
-            Some(Operator::Delete) => delete_motion(vim, motion, times, cx),
-            Some(Operator::Yank) => yank_motion(vim, motion, times, cx),
-            Some(operator) => {
-                // Can't do anything for text objects, Ignoring
-                error!("Unexpected normal mode motion operator: {:?}", operator)
-            }
-        }
-    });
-}
-
-pub fn normal_object(object: Object, cx: &mut WindowContext) {
-    Vim::update(cx, |vim, cx| {
-        match vim.maybe_pop_operator() {
-            Some(Operator::Object { around }) => match vim.maybe_pop_operator() {
-                Some(Operator::Change) => change_object(vim, object, around, cx),
-                Some(Operator::Delete) => delete_object(vim, object, around, cx),
-                Some(Operator::Yank) => yank_object(vim, object, around, cx),
-                _ => {
-                    // Can't do anything for namespace operators. Ignoring
-                }
-            },
-            _ => {
-                // Can't do anything with change/delete/yank and text objects. Ignoring
-            }
-        }
-        vim.clear_operator(cx);
-    })
-}
-
-pub(crate) fn move_cursor(
-    vim: &mut Vim,
-    motion: Motion,
-    times: Option<usize>,
-    cx: &mut WindowContext,
-) {
-    vim.update_active_editor(cx, |editor, cx| {
-        let text_layout_details = editor.text_layout_details(cx);
-        editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-            s.move_cursors_with(|map, cursor, goal| {
-                motion
-                    .move_point(map, cursor, goal, times, &text_layout_details)
-                    .unwrap_or((cursor, goal))
-            })
-        })
-    });
-}
-
-fn insert_after(_: &mut Workspace, _: &InsertAfter, cx: &mut ViewContext<Workspace>) {
-    Vim::update(cx, |vim, cx| {
-        vim.start_recording(cx);
-        vim.switch_mode(Mode::Insert, false, cx);
-        vim.update_active_editor(cx, |editor, cx| {
-            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                s.move_cursors_with(|map, cursor, _| (right(map, cursor, 1), SelectionGoal::None));
-            });
-        });
-    });
-}
-
-fn insert_before(_: &mut Workspace, _: &InsertBefore, cx: &mut ViewContext<Workspace>) {
-    Vim::update(cx, |vim, cx| {
-        vim.start_recording(cx);
-        vim.switch_mode(Mode::Insert, false, cx);
-    });
-}
-
-fn insert_first_non_whitespace(
-    _: &mut Workspace,
-    _: &InsertFirstNonWhitespace,
-    cx: &mut ViewContext<Workspace>,
-) {
-    Vim::update(cx, |vim, cx| {
-        vim.start_recording(cx);
-        vim.switch_mode(Mode::Insert, false, cx);
-        vim.update_active_editor(cx, |editor, cx| {
-            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                s.move_cursors_with(|map, cursor, _| {
-                    (
-                        first_non_whitespace(map, false, cursor),
-                        SelectionGoal::None,
-                    )
-                });
-            });
-        });
-    });
-}
-
-fn insert_end_of_line(_: &mut Workspace, _: &InsertEndOfLine, cx: &mut ViewContext<Workspace>) {
-    Vim::update(cx, |vim, cx| {
-        vim.start_recording(cx);
-        vim.switch_mode(Mode::Insert, false, cx);
-        vim.update_active_editor(cx, |editor, cx| {
-            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                s.move_cursors_with(|map, cursor, _| {
-                    (next_line_end(map, cursor, 1), SelectionGoal::None)
-                });
-            });
-        });
-    });
-}
-
-fn insert_line_above(_: &mut Workspace, _: &InsertLineAbove, cx: &mut ViewContext<Workspace>) {
-    Vim::update(cx, |vim, cx| {
-        vim.start_recording(cx);
-        vim.switch_mode(Mode::Insert, false, cx);
-        vim.update_active_editor(cx, |editor, cx| {
-            editor.transact(cx, |editor, cx| {
-                let (map, old_selections) = editor.selections.all_display(cx);
-                let selection_start_rows: HashSet<u32> = old_selections
-                    .into_iter()
-                    .map(|selection| selection.start.row())
-                    .collect();
-                let edits = selection_start_rows.into_iter().map(|row| {
-                    let (indent, _) = map.line_indent(row);
-                    let start_of_line =
-                        motion::start_of_line(&map, false, DisplayPoint::new(row, 0))
-                            .to_point(&map);
-                    let mut new_text = " ".repeat(indent as usize);
-                    new_text.push('\n');
-                    (start_of_line..start_of_line, new_text)
-                });
-                editor.edit_with_autoindent(edits, cx);
-                editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                    s.move_cursors_with(|map, cursor, _| {
-                        let previous_line = motion::start_of_relative_buffer_row(map, cursor, -1);
-                        let insert_point = motion::end_of_line(map, false, previous_line);
-                        (insert_point, SelectionGoal::None)
-                    });
-                });
-            });
-        });
-    });
-}
-
-fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContext<Workspace>) {
-    Vim::update(cx, |vim, cx| {
-        vim.start_recording(cx);
-        vim.switch_mode(Mode::Insert, false, cx);
-        vim.update_active_editor(cx, |editor, cx| {
-            let text_layout_details = editor.text_layout_details(cx);
-            editor.transact(cx, |editor, cx| {
-                let (map, old_selections) = editor.selections.all_display(cx);
-
-                let selection_end_rows: HashSet<u32> = old_selections
-                    .into_iter()
-                    .map(|selection| selection.end.row())
-                    .collect();
-                let edits = selection_end_rows.into_iter().map(|row| {
-                    let (indent, _) = map.line_indent(row);
-                    let end_of_line =
-                        motion::end_of_line(&map, false, DisplayPoint::new(row, 0)).to_point(&map);
-
-                    let mut new_text = "\n".to_string();
-                    new_text.push_str(&" ".repeat(indent as usize));
-                    (end_of_line..end_of_line, new_text)
-                });
-                editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                    s.maybe_move_cursors_with(|map, cursor, goal| {
-                        Motion::CurrentLine.move_point(
-                            map,
-                            cursor,
-                            goal,
-                            None,
-                            &text_layout_details,
-                        )
-                    });
-                });
-                editor.edit_with_autoindent(edits, cx);
-            });
-        });
-    });
-}
-
-fn yank_line(_: &mut Workspace, _: &YankLine, cx: &mut ViewContext<Workspace>) {
-    Vim::update(cx, |vim, cx| {
-        let count = vim.take_count(cx);
-        yank_motion(vim, motion::Motion::CurrentLine, count, cx)
-    })
-}
-
-pub(crate) fn normal_replace(text: Arc<str>, cx: &mut WindowContext) {
-    Vim::update(cx, |vim, cx| {
-        vim.stop_recording();
-        vim.update_active_editor(cx, |editor, cx| {
-            editor.transact(cx, |editor, cx| {
-                editor.set_clip_at_line_ends(false, cx);
-                let (map, display_selections) = editor.selections.all_display(cx);
-                // Selections are biased right at the start. So we need to store
-                // anchors that are biased left so that we can restore the selections
-                // after the change
-                let stable_anchors = editor
-                    .selections
-                    .disjoint_anchors()
-                    .into_iter()
-                    .map(|selection| {
-                        let start = selection.start.bias_left(&map.buffer_snapshot);
-                        start..start
-                    })
-                    .collect::<Vec<_>>();
-
-                let edits = display_selections
-                    .into_iter()
-                    .map(|selection| {
-                        let mut range = selection.range();
-                        *range.end.column_mut() += 1;
-                        range.end = map.clip_point(range.end, Bias::Right);
-
-                        (
-                            range.start.to_offset(&map, Bias::Left)
-                                ..range.end.to_offset(&map, Bias::Left),
-                            text.clone(),
-                        )
-                    })
-                    .collect::<Vec<_>>();
-
-                editor.buffer().update(cx, |buffer, cx| {
-                    buffer.edit(edits, None, cx);
-                });
-                editor.set_clip_at_line_ends(true, cx);
-                editor.change_selections(None, cx, |s| {
-                    s.select_anchor_ranges(stable_anchors);
-                });
-            });
-        });
-        vim.pop_operator(cx)
-    });
-}
-
-#[cfg(test)]
-mod test {
-    use gpui::TestAppContext;
-    use indoc::indoc;
-
-    use crate::{
-        state::Mode::{self},
-        test::NeovimBackedTestContext,
-    };
-
-    #[gpui::test]
-    async fn test_h(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["h"]);
-        cx.assert_all(indoc! {"
-            ˇThe qˇuick
-            ˇbrown"
-        })
-        .await;
-    }
-
-    #[gpui::test]
-    async fn test_backspace(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx)
-            .await
-            .binding(["backspace"]);
-        cx.assert_all(indoc! {"
-            ˇThe qˇuick
-            ˇbrown"
-        })
-        .await;
-    }
-
-    #[gpui::test]
-    async fn test_j(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.set_shared_state(indoc! {"
-                    aaˇaa
-                    😃😃"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["j"]).await;
-        cx.assert_shared_state(indoc! {"
-                    aaaa
-                    😃ˇ😃"
-        })
-        .await;
-
-        for marked_position in cx.each_marked_position(indoc! {"
-                    ˇThe qˇuick broˇwn
-                    ˇfox jumps"
-        }) {
-            cx.assert_neovim_compatible(&marked_position, ["j"]).await;
-        }
-    }
-
-    #[gpui::test]
-    async fn test_enter(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["enter"]);
-        cx.assert_all(indoc! {"
-            ˇThe qˇuick broˇwn
-            ˇfox jumps"
-        })
-        .await;
-    }
-
-    #[gpui::test]
-    async fn test_k(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["k"]);
-        cx.assert_all(indoc! {"
-            ˇThe qˇuick
-            ˇbrown fˇox jumˇps"
-        })
-        .await;
-    }
-
-    #[gpui::test]
-    async fn test_l(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["l"]);
-        cx.assert_all(indoc! {"
-            ˇThe qˇuicˇk
-            ˇbrowˇn"})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_jump_to_line_boundaries(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-        cx.assert_binding_matches_all(
-            ["$"],
-            indoc! {"
-            ˇThe qˇuicˇk
-            ˇbrowˇn"},
-        )
-        .await;
-        cx.assert_binding_matches_all(
-            ["0"],
-            indoc! {"
-                ˇThe qˇuicˇk
-                ˇbrowˇn"},
-        )
-        .await;
-    }
-
-    #[gpui::test]
-    async fn test_jump_to_end(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-g"]);
-
-        cx.assert_all(indoc! {"
-                The ˇquick
-
-                brown fox jumps
-                overˇ the lazy doˇg"})
-            .await;
-        cx.assert(indoc! {"
-            The quiˇck
-
-            brown"})
-            .await;
-        cx.assert(indoc! {"
-            The quiˇck
-
-            "})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_w(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["w"]);
-        cx.assert_all(indoc! {"
-            The ˇquickˇ-ˇbrown
-            ˇ
-            ˇ
-            ˇfox_jumps ˇover
-            ˇthˇe"})
-            .await;
-        let mut cx = cx.binding(["shift-w"]);
-        cx.assert_all(indoc! {"
-            The ˇquickˇ-ˇbrown
-            ˇ
-            ˇ
-            ˇfox_jumps ˇover
-            ˇthˇe"})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_end_of_word(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["e"]);
-        cx.assert_all(indoc! {"
-            Thˇe quicˇkˇ-browˇn
-
-
-            fox_jumpˇs oveˇr
-            thˇe"})
-            .await;
-        let mut cx = cx.binding(["shift-e"]);
-        cx.assert_all(indoc! {"
-            Thˇe quicˇkˇ-browˇn
-
-
-            fox_jumpˇs oveˇr
-            thˇe"})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_b(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["b"]);
-        cx.assert_all(indoc! {"
-            ˇThe ˇquickˇ-ˇbrown
-            ˇ
-            ˇ
-            ˇfox_jumps ˇover
-            ˇthe"})
-            .await;
-        let mut cx = cx.binding(["shift-b"]);
-        cx.assert_all(indoc! {"
-            ˇThe ˇquickˇ-ˇbrown
-            ˇ
-            ˇ
-            ˇfox_jumps ˇover
-            ˇthe"})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_gg(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-        cx.assert_binding_matches_all(
-            ["g", "g"],
-            indoc! {"
-                The qˇuick
-
-                brown fox jumps
-                over ˇthe laˇzy dog"},
-        )
-        .await;
-        cx.assert_binding_matches(
-            ["g", "g"],
-            indoc! {"
-
-
-                brown fox jumps
-                over the laˇzy dog"},
-        )
-        .await;
-        cx.assert_binding_matches(
-            ["2", "g", "g"],
-            indoc! {"
-                ˇ
-
-                brown fox jumps
-                over the lazydog"},
-        )
-        .await;
-    }
-
-    #[gpui::test]
-    async fn test_end_of_document(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-        cx.assert_binding_matches_all(
-            ["shift-g"],
-            indoc! {"
-                The qˇuick
-
-                brown fox jumps
-                over ˇthe laˇzy dog"},
-        )
-        .await;
-        cx.assert_binding_matches(
-            ["shift-g"],
-            indoc! {"
-
-
-                brown fox jumps
-                over the laˇzy dog"},
-        )
-        .await;
-        cx.assert_binding_matches(
-            ["2", "shift-g"],
-            indoc! {"
-                ˇ
-
-                brown fox jumps
-                over the lazydog"},
-        )
-        .await;
-    }
-
-    #[gpui::test]
-    async fn test_a(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["a"]);
-        cx.assert_all("The qˇuicˇk").await;
-    }
-
-    #[gpui::test]
-    async fn test_insert_end_of_line(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-a"]);
-        cx.assert_all(indoc! {"
-            ˇ
-            The qˇuick
-            brown ˇfox "})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_jump_to_first_non_whitespace(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["^"]);
-        cx.assert("The qˇuick").await;
-        cx.assert(" The qˇuick").await;
-        cx.assert("ˇ").await;
-        cx.assert(indoc! {"
-                The qˇuick
-                brown fox"})
-            .await;
-        cx.assert(indoc! {"
-                ˇ
-                The quick"})
-            .await;
-        // Indoc disallows trailing whitespace.
-        cx.assert("   ˇ \nThe quick").await;
-    }
-
-    #[gpui::test]
-    async fn test_insert_first_non_whitespace(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-i"]);
-        cx.assert("The qˇuick").await;
-        cx.assert(" The qˇuick").await;
-        cx.assert("ˇ").await;
-        cx.assert(indoc! {"
-                The qˇuick
-                brown fox"})
-            .await;
-        cx.assert(indoc! {"
-                ˇ
-                The quick"})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_delete_to_end_of_line(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-d"]);
-        cx.assert(indoc! {"
-                The qˇuick
-                brown fox"})
-            .await;
-        cx.assert(indoc! {"
-                The quick
-                ˇ
-                brown fox"})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_x(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["x"]);
-        cx.assert_all("ˇTeˇsˇt").await;
-        cx.assert(indoc! {"
-                Tesˇt
-                test"})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_delete_left(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-x"]);
-        cx.assert_all("ˇTˇeˇsˇt").await;
-        cx.assert(indoc! {"
-                Test
-                ˇtest"})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_o(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["o"]);
-        cx.assert("ˇ").await;
-        cx.assert("The ˇquick").await;
-        cx.assert_all(indoc! {"
-                The qˇuick
-                brown ˇfox
-                jumps ˇover"})
-            .await;
-        cx.assert(indoc! {"
-                The quick
-                ˇ
-                brown fox"})
-            .await;
-
-        cx.assert_manual(
-            indoc! {"
-                fn test() {
-                    println!(ˇ);
-                }"},
-            Mode::Normal,
-            indoc! {"
-                fn test() {
-                    println!();
-                    ˇ
-                }"},
-            Mode::Insert,
-        );
-
-        cx.assert_manual(
-            indoc! {"
-                fn test(ˇ) {
-                    println!();
-                }"},
-            Mode::Normal,
-            indoc! {"
-                fn test() {
-                    ˇ
-                    println!();
-                }"},
-            Mode::Insert,
-        );
-    }
-
-    #[gpui::test]
-    async fn test_insert_line_above(cx: &mut gpui::TestAppContext) {
-        let cx = NeovimBackedTestContext::new(cx).await;
-        let mut cx = cx.binding(["shift-o"]);
-        cx.assert("ˇ").await;
-        cx.assert("The ˇquick").await;
-        cx.assert_all(indoc! {"
-            The qˇuick
-            brown ˇfox
-            jumps ˇover"})
-            .await;
-        cx.assert(indoc! {"
-            The quick
-            ˇ
-            brown fox"})
-            .await;
-
-        // Our indentation is smarter than vims. So we don't match here
-        cx.assert_manual(
-            indoc! {"
-                fn test() {
-                    println!(ˇ);
-                }"},
-            Mode::Normal,
-            indoc! {"
-                fn test() {
-                    ˇ
-                    println!();
-                }"},
-            Mode::Insert,
-        );
-        cx.assert_manual(
-            indoc! {"
-                fn test(ˇ) {
-                    println!();
-                }"},
-            Mode::Normal,
-            indoc! {"
-                ˇ
-                fn test() {
-                    println!();
-                }"},
-            Mode::Insert,
-        );
-    }
-
-    #[gpui::test]
-    async fn test_dd(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-        cx.assert_neovim_compatible("ˇ", ["d", "d"]).await;
-        cx.assert_neovim_compatible("The ˇquick", ["d", "d"]).await;
-        for marked_text in cx.each_marked_position(indoc! {"
-            The qˇuick
-            brown ˇfox
-            jumps ˇover"})
-        {
-            cx.assert_neovim_compatible(&marked_text, ["d", "d"]).await;
-        }
-        cx.assert_neovim_compatible(
-            indoc! {"
-                The quick
-                ˇ
-                brown fox"},
-            ["d", "d"],
-        )
-        .await;
-    }
-
-    #[gpui::test]
-    async fn test_cc(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["c", "c"]);
-        cx.assert("ˇ").await;
-        cx.assert("The ˇquick").await;
-        cx.assert_all(indoc! {"
-                The quˇick
-                brown ˇfox
-                jumps ˇover"})
-            .await;
-        cx.assert(indoc! {"
-                The quick
-                ˇ
-                brown fox"})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_repeated_word(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        for count in 1..=5 {
-            cx.assert_binding_matches_all(
-                [&count.to_string(), "w"],
-                indoc! {"
-                    ˇThe quˇickˇ browˇn
-                    ˇ
-                    ˇfox ˇjumpsˇ-ˇoˇver
-                    ˇthe lazy dog
-                "},
-            )
-            .await;
-        }
-    }
-
-    #[gpui::test]
-    async fn test_h_through_unicode(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["h"]);
-        cx.assert_all("Testˇ├ˇ──ˇ┐ˇTest").await;
-    }
-
-    #[gpui::test]
-    async fn test_f_and_t(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        for count in 1..=3 {
-            let test_case = indoc! {"
-                ˇaaaˇbˇ ˇbˇ   ˇbˇbˇ aˇaaˇbaaa
-                ˇ    ˇbˇaaˇa ˇbˇbˇb
-                ˇ
-                ˇb
-            "};
-
-            cx.assert_binding_matches_all([&count.to_string(), "f", "b"], test_case)
-                .await;
-
-            cx.assert_binding_matches_all([&count.to_string(), "t", "b"], test_case)
-                .await;
-        }
-    }
-
-    #[gpui::test]
-    async fn test_capital_f_and_capital_t(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-        let test_case = indoc! {"
-            ˇaaaˇbˇ ˇbˇ   ˇbˇbˇ aˇaaˇbaaa
-            ˇ    ˇbˇaaˇa ˇbˇbˇb
-            ˇ•••
-            ˇb
-            "
-        };
-
-        for count in 1..=3 {
-            cx.assert_binding_matches_all([&count.to_string(), "shift-f", "b"], test_case)
-                .await;
-
-            cx.assert_binding_matches_all([&count.to_string(), "shift-t", "b"], test_case)
-                .await;
-        }
-    }
-
-    #[gpui::test]
-    async fn test_percent(cx: &mut TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["%"]);
-        cx.assert_all("ˇconsole.logˇ(ˇvaˇrˇ)ˇ;").await;
-        cx.assert_all("ˇconsole.logˇ(ˇ'var', ˇ[ˇ1, ˇ2, 3ˇ]ˇ)ˇ;")
-            .await;
-        cx.assert_all("let result = curried_funˇ(ˇ)ˇ(ˇ)ˇ;").await;
-    }
-}

crates/vim2/src/normal/case.rs 🔗

@@ -1,116 +0,0 @@
-use editor::scroll::autoscroll::Autoscroll;
-use gpui::ViewContext;
-use language::{Bias, Point};
-use workspace::Workspace;
-
-use crate::{normal::ChangeCase, state::Mode, Vim};
-
-pub fn change_case(_: &mut Workspace, _: &ChangeCase, cx: &mut ViewContext<Workspace>) {
-    Vim::update(cx, |vim, cx| {
-        vim.record_current_action(cx);
-        let count = vim.take_count(cx).unwrap_or(1) as u32;
-        vim.update_active_editor(cx, |editor, cx| {
-            let mut ranges = Vec::new();
-            let mut cursor_positions = Vec::new();
-            let snapshot = editor.buffer().read(cx).snapshot(cx);
-            for selection in editor.selections.all::<Point>(cx) {
-                match vim.state().mode {
-                    Mode::VisualLine => {
-                        let start = Point::new(selection.start.row, 0);
-                        let end =
-                            Point::new(selection.end.row, snapshot.line_len(selection.end.row));
-                        ranges.push(start..end);
-                        cursor_positions.push(start..start);
-                    }
-                    Mode::Visual => {
-                        ranges.push(selection.start..selection.end);
-                        cursor_positions.push(selection.start..selection.start);
-                    }
-                    Mode::VisualBlock => {
-                        ranges.push(selection.start..selection.end);
-                        if cursor_positions.len() == 0 {
-                            cursor_positions.push(selection.start..selection.start);
-                        }
-                    }
-                    Mode::Insert | Mode::Normal => {
-                        let start = selection.start;
-                        let mut end = start;
-                        for _ in 0..count {
-                            end = snapshot.clip_point(end + Point::new(0, 1), Bias::Right);
-                        }
-                        ranges.push(start..end);
-
-                        if end.column == snapshot.line_len(end.row) {
-                            end = snapshot.clip_point(end - Point::new(0, 1), Bias::Left);
-                        }
-                        cursor_positions.push(end..end)
-                    }
-                }
-            }
-            editor.transact(cx, |editor, cx| {
-                for range in ranges.into_iter().rev() {
-                    let snapshot = editor.buffer().read(cx).snapshot(cx);
-                    editor.buffer().update(cx, |buffer, cx| {
-                        let text = snapshot
-                            .text_for_range(range.start..range.end)
-                            .flat_map(|s| s.chars())
-                            .flat_map(|c| {
-                                if c.is_lowercase() {
-                                    c.to_uppercase().collect::<Vec<char>>()
-                                } else {
-                                    c.to_lowercase().collect::<Vec<char>>()
-                                }
-                            })
-                            .collect::<String>();
-
-                        buffer.edit([(range, text)], None, cx)
-                    })
-                }
-                editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                    s.select_ranges(cursor_positions)
-                })
-            });
-        });
-        vim.switch_mode(Mode::Normal, true, cx)
-    })
-}
-#[cfg(test)]
-mod test {
-    use crate::{state::Mode, test::NeovimBackedTestContext};
-
-    #[gpui::test]
-    async fn test_change_case(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-        cx.set_shared_state("ˇabC\n").await;
-        cx.simulate_shared_keystrokes(["~"]).await;
-        cx.assert_shared_state("AˇbC\n").await;
-        cx.simulate_shared_keystrokes(["2", "~"]).await;
-        cx.assert_shared_state("ABˇc\n").await;
-
-        // works in visual mode
-        cx.set_shared_state("a😀C«dÉ1*fˇ»\n").await;
-        cx.simulate_shared_keystrokes(["~"]).await;
-        cx.assert_shared_state("a😀CˇDé1*F\n").await;
-
-        // works with multibyte characters
-        cx.simulate_shared_keystrokes(["~"]).await;
-        cx.set_shared_state("aˇC😀é1*F\n").await;
-        cx.simulate_shared_keystrokes(["4", "~"]).await;
-        cx.assert_shared_state("ac😀É1ˇ*F\n").await;
-
-        // works with line selections
-        cx.set_shared_state("abˇC\n").await;
-        cx.simulate_shared_keystrokes(["shift-v", "~"]).await;
-        cx.assert_shared_state("ˇABc\n").await;
-
-        // works in visual block mode
-        cx.set_shared_state("ˇaa\nbb\ncc").await;
-        cx.simulate_shared_keystrokes(["ctrl-v", "j", "~"]).await;
-        cx.assert_shared_state("ˇAa\nBb\ncc").await;
-
-        // works with multiple cursors (zed only)
-        cx.set_state("aˇßcdˇe\n", Mode::Normal);
-        cx.simulate_keystroke("~");
-        cx.assert_state("aSSˇcdˇE\n", Mode::Normal);
-    }
-}

crates/vim2/src/normal/change.rs 🔗

@@ -1,502 +0,0 @@
-use crate::{motion::Motion, object::Object, state::Mode, utils::copy_selections_content, Vim};
-use editor::{
-    char_kind,
-    display_map::DisplaySnapshot,
-    movement::{self, FindRange, TextLayoutDetails},
-    scroll::autoscroll::Autoscroll,
-    CharKind, DisplayPoint,
-};
-use gpui::WindowContext;
-use language::Selection;
-
-pub fn change_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
-    // Some motions ignore failure when switching to normal mode
-    let mut motion_succeeded = matches!(
-        motion,
-        Motion::Left
-            | Motion::Right
-            | Motion::EndOfLine { .. }
-            | Motion::Backspace
-            | Motion::StartOfLine { .. }
-    );
-    vim.update_active_editor(cx, |editor, cx| {
-        let text_layout_details = editor.text_layout_details(cx);
-        editor.transact(cx, |editor, cx| {
-            // We are swapping to insert mode anyway. Just set the line end clipping behavior now
-            editor.set_clip_at_line_ends(false, cx);
-            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                s.move_with(|map, selection| {
-                    motion_succeeded |= if let Motion::NextWordStart { ignore_punctuation } = motion
-                    {
-                        expand_changed_word_selection(
-                            map,
-                            selection,
-                            times,
-                            ignore_punctuation,
-                            &text_layout_details,
-                        )
-                    } else {
-                        motion.expand_selection(map, selection, times, false, &text_layout_details)
-                    };
-                });
-            });
-            copy_selections_content(editor, motion.linewise(), cx);
-            editor.insert("", cx);
-        });
-    });
-
-    if motion_succeeded {
-        vim.switch_mode(Mode::Insert, false, cx)
-    } else {
-        vim.switch_mode(Mode::Normal, false, cx)
-    }
-}
-
-pub fn change_object(vim: &mut Vim, object: Object, around: bool, cx: &mut WindowContext) {
-    let mut objects_found = false;
-    vim.update_active_editor(cx, |editor, cx| {
-        // We are swapping to insert mode anyway. Just set the line end clipping behavior now
-        editor.set_clip_at_line_ends(false, cx);
-        editor.transact(cx, |editor, cx| {
-            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                s.move_with(|map, selection| {
-                    objects_found |= object.expand_selection(map, selection, around);
-                });
-            });
-            if objects_found {
-                copy_selections_content(editor, false, cx);
-                editor.insert("", cx);
-            }
-        });
-    });
-
-    if objects_found {
-        vim.switch_mode(Mode::Insert, false, cx);
-    } else {
-        vim.switch_mode(Mode::Normal, false, cx);
-    }
-}
-
-// From the docs https://vimdoc.sourceforge.net/htmldoc/motion.html
-// Special case: "cw" and "cW" are treated like "ce" and "cE" if the cursor is
-// on a non-blank.  This is because "cw" is interpreted as change-word, and a
-// word does not include the following white space.  {Vi: "cw" when on a blank
-//     followed by other blanks changes only the first blank; this is probably a
-//     bug, because "dw" deletes all the blanks}
-fn expand_changed_word_selection(
-    map: &DisplaySnapshot,
-    selection: &mut Selection<DisplayPoint>,
-    times: Option<usize>,
-    ignore_punctuation: bool,
-    text_layout_details: &TextLayoutDetails,
-) -> bool {
-    if times.is_none() || times.unwrap() == 1 {
-        let scope = map
-            .buffer_snapshot
-            .language_scope_at(selection.start.to_point(map));
-        let in_word = map
-            .chars_at(selection.head())
-            .next()
-            .map(|(c, _)| char_kind(&scope, c) != CharKind::Whitespace)
-            .unwrap_or_default();
-
-        if in_word {
-            selection.end =
-                movement::find_boundary(map, selection.end, FindRange::MultiLine, |left, right| {
-                    let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation);
-                    let right_kind =
-                        char_kind(&scope, right).coerce_punctuation(ignore_punctuation);
-
-                    left_kind != right_kind && left_kind != CharKind::Whitespace
-                });
-            true
-        } else {
-            Motion::NextWordStart { ignore_punctuation }.expand_selection(
-                map,
-                selection,
-                None,
-                false,
-                &text_layout_details,
-            )
-        }
-    } else {
-        Motion::NextWordStart { ignore_punctuation }.expand_selection(
-            map,
-            selection,
-            times,
-            false,
-            &text_layout_details,
-        )
-    }
-}
-
-#[cfg(test)]
-mod test {
-    use indoc::indoc;
-
-    use crate::test::NeovimBackedTestContext;
-
-    #[gpui::test]
-    async fn test_change_h(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["c", "h"]);
-        cx.assert("Teˇst").await;
-        cx.assert("Tˇest").await;
-        cx.assert("ˇTest").await;
-        cx.assert(indoc! {"
-            Test
-            ˇtest"})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_change_backspace(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx)
-            .await
-            .binding(["c", "backspace"]);
-        cx.assert("Teˇst").await;
-        cx.assert("Tˇest").await;
-        cx.assert("ˇTest").await;
-        cx.assert(indoc! {"
-            Test
-            ˇtest"})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_change_l(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["c", "l"]);
-        cx.assert("Teˇst").await;
-        cx.assert("Tesˇt").await;
-    }
-
-    #[gpui::test]
-    async fn test_change_w(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["c", "w"]);
-        cx.assert("Teˇst").await;
-        cx.assert("Tˇest test").await;
-        cx.assert("Testˇ  test").await;
-        cx.assert(indoc! {"
-                Test teˇst
-                test"})
-            .await;
-        cx.assert(indoc! {"
-                Test tesˇt
-                test"})
-            .await;
-        cx.assert(indoc! {"
-                Test test
-                ˇ
-                test"})
-            .await;
-
-        let mut cx = cx.binding(["c", "shift-w"]);
-        cx.assert("Test teˇst-test test").await;
-    }
-
-    #[gpui::test]
-    async fn test_change_e(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["c", "e"]);
-        cx.assert("Teˇst Test").await;
-        cx.assert("Tˇest test").await;
-        cx.assert(indoc! {"
-                Test teˇst
-                test"})
-            .await;
-        cx.assert(indoc! {"
-                Test tesˇt
-                test"})
-            .await;
-        cx.assert(indoc! {"
-                Test test
-                ˇ
-                test"})
-            .await;
-
-        let mut cx = cx.binding(["c", "shift-e"]);
-        cx.assert("Test teˇst-test test").await;
-    }
-
-    #[gpui::test]
-    async fn test_change_b(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["c", "b"]);
-        cx.assert("Teˇst Test").await;
-        cx.assert("Test ˇtest").await;
-        cx.assert("Test1 test2 ˇtest3").await;
-        cx.assert(indoc! {"
-                Test test
-                ˇtest"})
-            .await;
-        cx.assert(indoc! {"
-                Test test
-                ˇ
-                test"})
-            .await;
-
-        let mut cx = cx.binding(["c", "shift-b"]);
-        cx.assert("Test test-test ˇtest").await;
-    }
-
-    #[gpui::test]
-    async fn test_change_end_of_line(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["c", "$"]);
-        cx.assert(indoc! {"
-            The qˇuick
-            brown fox"})
-            .await;
-        cx.assert(indoc! {"
-            The quick
-            ˇ
-            brown fox"})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_change_0(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.assert_neovim_compatible(
-            indoc! {"
-            The qˇuick
-            brown fox"},
-            ["c", "0"],
-        )
-        .await;
-        cx.assert_neovim_compatible(
-            indoc! {"
-            The quick
-            ˇ
-            brown fox"},
-            ["c", "0"],
-        )
-        .await;
-    }
-
-    #[gpui::test]
-    async fn test_change_k(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.assert_neovim_compatible(
-            indoc! {"
-            The quick
-            brown ˇfox
-            jumps over"},
-            ["c", "k"],
-        )
-        .await;
-        cx.assert_neovim_compatible(
-            indoc! {"
-            The quick
-            brown fox
-            jumps ˇover"},
-            ["c", "k"],
-        )
-        .await;
-        cx.assert_neovim_compatible(
-            indoc! {"
-            The qˇuick
-            brown fox
-            jumps over"},
-            ["c", "k"],
-        )
-        .await;
-        cx.assert_neovim_compatible(
-            indoc! {"
-            ˇ
-            brown fox
-            jumps over"},
-            ["c", "k"],
-        )
-        .await;
-    }
-
-    #[gpui::test]
-    async fn test_change_j(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-        cx.assert_neovim_compatible(
-            indoc! {"
-            The quick
-            brown ˇfox
-            jumps over"},
-            ["c", "j"],
-        )
-        .await;
-        cx.assert_neovim_compatible(
-            indoc! {"
-            The quick
-            brown fox
-            jumps ˇover"},
-            ["c", "j"],
-        )
-        .await;
-        cx.assert_neovim_compatible(
-            indoc! {"
-            The qˇuick
-            brown fox
-            jumps over"},
-            ["c", "j"],
-        )
-        .await;
-        cx.assert_neovim_compatible(
-            indoc! {"
-            The quick
-            brown fox
-            ˇ"},
-            ["c", "j"],
-        )
-        .await;
-    }
-
-    #[gpui::test]
-    async fn test_change_end_of_document(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-        cx.assert_neovim_compatible(
-            indoc! {"
-            The quick
-            brownˇ fox
-            jumps over
-            the lazy"},
-            ["c", "shift-g"],
-        )
-        .await;
-        cx.assert_neovim_compatible(
-            indoc! {"
-            The quick
-            brownˇ fox
-            jumps over
-            the lazy"},
-            ["c", "shift-g"],
-        )
-        .await;
-        cx.assert_neovim_compatible(
-            indoc! {"
-            The quick
-            brown fox
-            jumps over
-            the lˇazy"},
-            ["c", "shift-g"],
-        )
-        .await;
-        cx.assert_neovim_compatible(
-            indoc! {"
-            The quick
-            brown fox
-            jumps over
-            ˇ"},
-            ["c", "shift-g"],
-        )
-        .await;
-    }
-
-    #[gpui::test]
-    async fn test_change_gg(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-        cx.assert_neovim_compatible(
-            indoc! {"
-            The quick
-            brownˇ fox
-            jumps over
-            the lazy"},
-            ["c", "g", "g"],
-        )
-        .await;
-        cx.assert_neovim_compatible(
-            indoc! {"
-            The quick
-            brown fox
-            jumps over
-            the lˇazy"},
-            ["c", "g", "g"],
-        )
-        .await;
-        cx.assert_neovim_compatible(
-            indoc! {"
-            The qˇuick
-            brown fox
-            jumps over
-            the lazy"},
-            ["c", "g", "g"],
-        )
-        .await;
-        cx.assert_neovim_compatible(
-            indoc! {"
-            ˇ
-            brown fox
-            jumps over
-            the lazy"},
-            ["c", "g", "g"],
-        )
-        .await;
-    }
-
-    #[gpui::test]
-    async fn test_repeated_cj(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        for count in 1..=5 {
-            cx.assert_binding_matches_all(
-                ["c", &count.to_string(), "j"],
-                indoc! {"
-                    ˇThe quˇickˇ browˇn
-                    ˇ
-                    ˇfox ˇjumpsˇ-ˇoˇver
-                    ˇthe lazy dog
-                    "},
-            )
-            .await;
-        }
-    }
-
-    #[gpui::test]
-    async fn test_repeated_cl(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        for count in 1..=5 {
-            cx.assert_binding_matches_all(
-                ["c", &count.to_string(), "l"],
-                indoc! {"
-                    ˇThe quˇickˇ browˇn
-                    ˇ
-                    ˇfox ˇjumpsˇ-ˇoˇver
-                    ˇthe lazy dog
-                    "},
-            )
-            .await;
-        }
-    }
-
-    #[gpui::test]
-    async fn test_repeated_cb(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        for count in 1..=5 {
-            for marked_text in cx.each_marked_position(indoc! {"
-                ˇThe quˇickˇ browˇn
-                ˇ
-                ˇfox ˇjumpsˇ-ˇoˇver
-                ˇthe lazy dog
-                "})
-            {
-                cx.assert_neovim_compatible(&marked_text, ["c", &count.to_string(), "b"])
-                    .await;
-            }
-        }
-    }
-
-    #[gpui::test]
-    async fn test_repeated_ce(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        for count in 1..=5 {
-            cx.assert_binding_matches_all(
-                ["c", &count.to_string(), "e"],
-                indoc! {"
-                    ˇThe quˇickˇ browˇn
-                    ˇ
-                    ˇfox ˇjumpsˇ-ˇoˇver
-                    ˇthe lazy dog
-                    "},
-            )
-            .await;
-        }
-    }
-}

crates/vim2/src/normal/delete.rs 🔗

@@ -1,475 +0,0 @@
-use crate::{motion::Motion, object::Object, utils::copy_selections_content, Vim};
-use collections::{HashMap, HashSet};
-use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Bias};
-use gpui::WindowContext;
-use language::Point;
-
-pub fn delete_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
-    vim.stop_recording();
-    vim.update_active_editor(cx, |editor, cx| {
-        let text_layout_details = editor.text_layout_details(cx);
-        editor.transact(cx, |editor, cx| {
-            editor.set_clip_at_line_ends(false, cx);
-            let mut original_columns: HashMap<_, _> = Default::default();
-            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                s.move_with(|map, selection| {
-                    let original_head = selection.head();
-                    original_columns.insert(selection.id, original_head.column());
-                    motion.expand_selection(map, selection, times, true, &text_layout_details);
-
-                    // Motion::NextWordStart on an empty line should delete it.
-                    if let Motion::NextWordStart {
-                        ignore_punctuation: _,
-                    } = motion
-                    {
-                        if selection.is_empty()
-                            && map
-                                .buffer_snapshot
-                                .line_len(selection.start.to_point(&map).row)
-                                == 0
-                        {
-                            selection.end = map
-                                .buffer_snapshot
-                                .clip_point(
-                                    Point::new(selection.start.to_point(&map).row + 1, 0),
-                                    Bias::Left,
-                                )
-                                .to_display_point(map)
-                        }
-                    }
-                });
-            });
-            copy_selections_content(editor, motion.linewise(), cx);
-            editor.insert("", cx);
-
-            // Fixup cursor position after the deletion
-            editor.set_clip_at_line_ends(true, cx);
-            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                s.move_with(|map, selection| {
-                    let mut cursor = selection.head();
-                    if motion.linewise() {
-                        if let Some(column) = original_columns.get(&selection.id) {
-                            *cursor.column_mut() = *column
-                        }
-                    }
-                    cursor = map.clip_point(cursor, Bias::Left);
-                    selection.collapse_to(cursor, selection.goal)
-                });
-            });
-        });
-    });
-}
-
-pub fn delete_object(vim: &mut Vim, object: Object, around: bool, cx: &mut WindowContext) {
-    vim.stop_recording();
-    vim.update_active_editor(cx, |editor, cx| {
-        editor.transact(cx, |editor, cx| {
-            editor.set_clip_at_line_ends(false, cx);
-            // Emulates behavior in vim where if we expanded backwards to include a newline
-            // the cursor gets set back to the start of the line
-            let mut should_move_to_start: HashSet<_> = Default::default();
-            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                s.move_with(|map, selection| {
-                    object.expand_selection(map, selection, around);
-                    let offset_range = selection.map(|p| p.to_offset(map, Bias::Left)).range();
-                    let contains_only_newlines = map
-                        .chars_at(selection.start)
-                        .take_while(|(_, p)| p < &selection.end)
-                        .all(|(char, _)| char == '\n')
-                        && !offset_range.is_empty();
-                    let end_at_newline = map
-                        .chars_at(selection.end)
-                        .next()
-                        .map(|(c, _)| c == '\n')
-                        .unwrap_or(false);
-
-                    // If expanded range contains only newlines and
-                    // the object is around or sentence, expand to include a newline
-                    // at the end or start
-                    if (around || object == Object::Sentence) && contains_only_newlines {
-                        if end_at_newline {
-                            selection.end =
-                                (offset_range.end + '\n'.len_utf8()).to_display_point(map);
-                        } else if selection.start.row() > 0 {
-                            should_move_to_start.insert(selection.id);
-                            selection.start =
-                                (offset_range.start - '\n'.len_utf8()).to_display_point(map);
-                        }
-                    }
-                });
-            });
-            copy_selections_content(editor, false, cx);
-            editor.insert("", cx);
-
-            // Fixup cursor position after the deletion
-            editor.set_clip_at_line_ends(true, cx);
-            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                s.move_with(|map, selection| {
-                    let mut cursor = selection.head();
-                    if should_move_to_start.contains(&selection.id) {
-                        *cursor.column_mut() = 0;
-                    }
-                    cursor = map.clip_point(cursor, Bias::Left);
-                    selection.collapse_to(cursor, selection.goal)
-                });
-            });
-        });
-    });
-}
-
-#[cfg(test)]
-mod test {
-    use indoc::indoc;
-
-    use crate::{
-        state::Mode,
-        test::{ExemptionFeatures, NeovimBackedTestContext, VimTestContext},
-    };
-
-    #[gpui::test]
-    async fn test_delete_h(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["d", "h"]);
-        cx.assert("Teˇst").await;
-        cx.assert("Tˇest").await;
-        cx.assert("ˇTest").await;
-        cx.assert(indoc! {"
-            Test
-            ˇtest"})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_delete_l(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["d", "l"]);
-        cx.assert("ˇTest").await;
-        cx.assert("Teˇst").await;
-        cx.assert("Tesˇt").await;
-        cx.assert(indoc! {"
-                Tesˇt
-                test"})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_delete_w(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-        cx.assert_neovim_compatible(
-            indoc! {"
-            Test tesˇt
-                test"},
-            ["d", "w"],
-        )
-        .await;
-
-        cx.assert_neovim_compatible("Teˇst", ["d", "w"]).await;
-        cx.assert_neovim_compatible("Tˇest test", ["d", "w"]).await;
-        cx.assert_neovim_compatible(
-            indoc! {"
-            Test teˇst
-            test"},
-            ["d", "w"],
-        )
-        .await;
-        cx.assert_neovim_compatible(
-            indoc! {"
-            Test tesˇt
-            test"},
-            ["d", "w"],
-        )
-        .await;
-
-        cx.assert_neovim_compatible(
-            indoc! {"
-            Test test
-            ˇ
-            test"},
-            ["d", "w"],
-        )
-        .await;
-
-        let mut cx = cx.binding(["d", "shift-w"]);
-        cx.assert_neovim_compatible("Test teˇst-test test", ["d", "shift-w"])
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_delete_next_word_end(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["d", "e"]);
-        // cx.assert("Teˇst Test").await;
-        // cx.assert("Tˇest test").await;
-        cx.assert(indoc! {"
-            Test teˇst
-            test"})
-            .await;
-        cx.assert(indoc! {"
-            Test tesˇt
-            test"})
-            .await;
-        cx.assert_exempted(
-            indoc! {"
-            Test test
-            ˇ
-            test"},
-            ExemptionFeatures::OperatorLastNewlineRemains,
-        )
-        .await;
-
-        let mut cx = cx.binding(["d", "shift-e"]);
-        cx.assert("Test teˇst-test test").await;
-    }
-
-    #[gpui::test]
-    async fn test_delete_b(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["d", "b"]);
-        cx.assert("Teˇst Test").await;
-        cx.assert("Test ˇtest").await;
-        cx.assert("Test1 test2 ˇtest3").await;
-        cx.assert(indoc! {"
-            Test test
-            ˇtest"})
-            .await;
-        cx.assert(indoc! {"
-            Test test
-            ˇ
-            test"})
-            .await;
-
-        let mut cx = cx.binding(["d", "shift-b"]);
-        cx.assert("Test test-test ˇtest").await;
-    }
-
-    #[gpui::test]
-    async fn test_delete_end_of_line(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["d", "$"]);
-        cx.assert(indoc! {"
-            The qˇuick
-            brown fox"})
-            .await;
-        cx.assert(indoc! {"
-            The quick
-            ˇ
-            brown fox"})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_delete_0(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["d", "0"]);
-        cx.assert(indoc! {"
-            The qˇuick
-            brown fox"})
-            .await;
-        cx.assert(indoc! {"
-            The quick
-            ˇ
-            brown fox"})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_delete_k(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["d", "k"]);
-        cx.assert(indoc! {"
-            The quick
-            brown ˇfox
-            jumps over"})
-            .await;
-        cx.assert(indoc! {"
-            The quick
-            brown fox
-            jumps ˇover"})
-            .await;
-        cx.assert(indoc! {"
-            The qˇuick
-            brown fox
-            jumps over"})
-            .await;
-        cx.assert(indoc! {"
-            ˇbrown fox
-            jumps over"})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_delete_j(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["d", "j"]);
-        cx.assert(indoc! {"
-            The quick
-            brown ˇfox
-            jumps over"})
-            .await;
-        cx.assert(indoc! {"
-            The quick
-            brown fox
-            jumps ˇover"})
-            .await;
-        cx.assert(indoc! {"
-            The qˇuick
-            brown fox
-            jumps over"})
-            .await;
-        cx.assert(indoc! {"
-            The quick
-            brown fox
-            ˇ"})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_delete_end_of_document(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-        cx.assert_neovim_compatible(
-            indoc! {"
-            The quick
-            brownˇ fox
-            jumps over
-            the lazy"},
-            ["d", "shift-g"],
-        )
-        .await;
-        cx.assert_neovim_compatible(
-            indoc! {"
-            The quick
-            brownˇ fox
-            jumps over
-            the lazy"},
-            ["d", "shift-g"],
-        )
-        .await;
-        cx.assert_neovim_compatible(
-            indoc! {"
-            The quick
-            brown fox
-            jumps over
-            the lˇazy"},
-            ["d", "shift-g"],
-        )
-        .await;
-        cx.assert_neovim_compatible(
-            indoc! {"
-            The quick
-            brown fox
-            jumps over
-            ˇ"},
-            ["d", "shift-g"],
-        )
-        .await;
-    }
-
-    #[gpui::test]
-    async fn test_delete_gg(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx)
-            .await
-            .binding(["d", "g", "g"]);
-        cx.assert_neovim_compatible(
-            indoc! {"
-            The quick
-            brownˇ fox
-            jumps over
-            the lazy"},
-            ["d", "g", "g"],
-        )
-        .await;
-        cx.assert_neovim_compatible(
-            indoc! {"
-            The quick
-            brown fox
-            jumps over
-            the lˇazy"},
-            ["d", "g", "g"],
-        )
-        .await;
-        cx.assert_neovim_compatible(
-            indoc! {"
-            The qˇuick
-            brown fox
-            jumps over
-            the lazy"},
-            ["d", "g", "g"],
-        )
-        .await;
-        cx.assert_neovim_compatible(
-            indoc! {"
-            ˇ
-            brown fox
-            jumps over
-            the lazy"},
-            ["d", "g", "g"],
-        )
-        .await;
-    }
-
-    #[gpui::test]
-    async fn test_cancel_delete_operator(cx: &mut gpui::TestAppContext) {
-        let mut cx = VimTestContext::new(cx, true).await;
-        cx.set_state(
-            indoc! {"
-                The quick brown
-                fox juˇmps over
-                the lazy dog"},
-            Mode::Normal,
-        );
-
-        // Canceling operator twice reverts to normal mode with no active operator
-        cx.simulate_keystrokes(["d", "escape", "k"]);
-        assert_eq!(cx.active_operator(), None);
-        assert_eq!(cx.mode(), Mode::Normal);
-        cx.assert_editor_state(indoc! {"
-            The quˇick brown
-            fox jumps over
-            the lazy dog"});
-    }
-
-    #[gpui::test]
-    async fn test_unbound_command_cancels_pending_operator(cx: &mut gpui::TestAppContext) {
-        let mut cx = VimTestContext::new(cx, true).await;
-        cx.set_state(
-            indoc! {"
-                The quick brown
-                fox juˇmps over
-                the lazy dog"},
-            Mode::Normal,
-        );
-
-        // Canceling operator twice reverts to normal mode with no active operator
-        cx.simulate_keystrokes(["d", "y"]);
-        assert_eq!(cx.active_operator(), None);
-        assert_eq!(cx.mode(), Mode::Normal);
-    }
-
-    #[gpui::test]
-    async fn test_delete_with_counts(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-        cx.set_shared_state(indoc! {"
-                The ˇquick brown
-                fox jumps over
-                the lazy dog"})
-            .await;
-        cx.simulate_shared_keystrokes(["d", "2", "d"]).await;
-        cx.assert_shared_state(indoc! {"
-        the ˇlazy dog"})
-            .await;
-
-        cx.set_shared_state(indoc! {"
-                The ˇquick brown
-                fox jumps over
-                the lazy dog"})
-            .await;
-        cx.simulate_shared_keystrokes(["2", "d", "d"]).await;
-        cx.assert_shared_state(indoc! {"
-        the ˇlazy dog"})
-            .await;
-
-        cx.set_shared_state(indoc! {"
-                The ˇquick brown
-                fox jumps over
-                the moon,
-                a star, and
-                the lazy dog"})
-            .await;
-        cx.simulate_shared_keystrokes(["2", "d", "2", "d"]).await;
-        cx.assert_shared_state(indoc! {"
-        the ˇlazy dog"})
-            .await;
-    }
-}

crates/vim2/src/normal/increment.rs 🔗

@@ -1,278 +0,0 @@
-use std::ops::Range;
-
-use editor::{scroll::autoscroll::Autoscroll, MultiBufferSnapshot, ToOffset, ToPoint};
-use gpui::{impl_actions, ViewContext, WindowContext};
-use language::{Bias, Point};
-use serde::Deserialize;
-use workspace::Workspace;
-
-use crate::{state::Mode, Vim};
-
-#[derive(Clone, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-struct Increment {
-    #[serde(default)]
-    step: bool,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-struct Decrement {
-    #[serde(default)]
-    step: bool,
-}
-
-impl_actions!(vim, [Increment, Decrement]);
-
-pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
-    workspace.register_action(|_: &mut Workspace, action: &Increment, cx| {
-        Vim::update(cx, |vim, cx| {
-            vim.record_current_action(cx);
-            let count = vim.take_count(cx).unwrap_or(1);
-            let step = if action.step { 1 } else { 0 };
-            increment(vim, count as i32, step, cx)
-        })
-    });
-    workspace.register_action(|_: &mut Workspace, action: &Decrement, cx| {
-        Vim::update(cx, |vim, cx| {
-            vim.record_current_action(cx);
-            let count = vim.take_count(cx).unwrap_or(1);
-            let step = if action.step { -1 } else { 0 };
-            increment(vim, count as i32 * -1, step, cx)
-        })
-    });
-}
-
-fn increment(vim: &mut Vim, mut delta: i32, step: i32, cx: &mut WindowContext) {
-    vim.update_active_editor(cx, |editor, cx| {
-        let mut edits = Vec::new();
-        let mut new_anchors = Vec::new();
-
-        let snapshot = editor.buffer().read(cx).snapshot(cx);
-        for selection in editor.selections.all_adjusted(cx) {
-            if !selection.is_empty() {
-                if vim.state().mode != Mode::VisualBlock || new_anchors.is_empty() {
-                    new_anchors.push((true, snapshot.anchor_before(selection.start)))
-                }
-            }
-            for row in selection.start.row..=selection.end.row {
-                let start = if row == selection.start.row {
-                    selection.start
-                } else {
-                    Point::new(row, 0)
-                };
-
-                if let Some((range, num, radix)) = find_number(&snapshot, start) {
-                    if let Ok(val) = i32::from_str_radix(&num, radix) {
-                        let result = val + delta;
-                        delta += step;
-                        let replace = match radix {
-                            10 => format!("{}", result),
-                            16 => {
-                                if num.to_ascii_lowercase() == num {
-                                    format!("{:x}", result)
-                                } else {
-                                    format!("{:X}", result)
-                                }
-                            }
-                            2 => format!("{:b}", result),
-                            _ => unreachable!(),
-                        };
-                        edits.push((range.clone(), replace));
-                    }
-                    if selection.is_empty() {
-                        new_anchors.push((false, snapshot.anchor_after(range.end)))
-                    }
-                } else {
-                    if selection.is_empty() {
-                        new_anchors.push((true, snapshot.anchor_after(start)))
-                    }
-                }
-            }
-        }
-        editor.transact(cx, |editor, cx| {
-            editor.edit(edits, cx);
-
-            let snapshot = editor.buffer().read(cx).snapshot(cx);
-            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                let mut new_ranges = Vec::new();
-                for (visual, anchor) in new_anchors.iter() {
-                    let mut point = anchor.to_point(&snapshot);
-                    if !*visual && point.column > 0 {
-                        point.column -= 1;
-                        point = snapshot.clip_point(point, Bias::Left)
-                    }
-                    new_ranges.push(point..point);
-                }
-                s.select_ranges(new_ranges)
-            })
-        });
-    });
-    vim.switch_mode(Mode::Normal, true, cx)
-}
-
-fn find_number(
-    snapshot: &MultiBufferSnapshot,
-    start: Point,
-) -> Option<(Range<Point>, String, u32)> {
-    let mut offset = start.to_offset(snapshot);
-
-    // go backwards to the start of any number the selection is within
-    for ch in snapshot.reversed_chars_at(offset) {
-        if ch.is_ascii_digit() || ch == '-' || ch == 'b' || ch == 'x' {
-            offset -= ch.len_utf8();
-            continue;
-        }
-        break;
-    }
-
-    let mut begin = None;
-    let mut end = None;
-    let mut num = String::new();
-    let mut radix = 10;
-
-    let mut chars = snapshot.chars_at(offset).peekable();
-    // find the next number on the line (may start after the original cursor position)
-    while let Some(ch) = chars.next() {
-        if num == "0" && ch == 'b' && chars.peek().is_some() && chars.peek().unwrap().is_digit(2) {
-            radix = 2;
-            begin = None;
-            num = String::new();
-        }
-        if num == "0" && ch == 'x' && chars.peek().is_some() && chars.peek().unwrap().is_digit(16) {
-            radix = 16;
-            begin = None;
-            num = String::new();
-        }
-
-        if ch.is_digit(radix)
-            || (begin.is_none()
-                && ch == '-'
-                && chars.peek().is_some()
-                && chars.peek().unwrap().is_digit(radix))
-        {
-            if begin.is_none() {
-                begin = Some(offset);
-            }
-            num.push(ch);
-        } else {
-            if begin.is_some() {
-                end = Some(offset);
-                break;
-            } else if ch == '\n' {
-                break;
-            }
-        }
-        offset += ch.len_utf8();
-    }
-    if let Some(begin) = begin {
-        let end = end.unwrap_or(offset);
-        Some((begin.to_point(snapshot)..end.to_point(snapshot), num, radix))
-    } else {
-        None
-    }
-}
-
-#[cfg(test)]
-mod test {
-    use indoc::indoc;
-
-    use crate::test::NeovimBackedTestContext;
-
-    #[gpui::test]
-    async fn test_increment(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.set_shared_state(indoc! {"
-            1ˇ2
-            "})
-            .await;
-
-        cx.simulate_shared_keystrokes(["ctrl-a"]).await;
-        cx.assert_shared_state(indoc! {"
-            1ˇ3
-            "})
-            .await;
-        cx.simulate_shared_keystrokes(["ctrl-x"]).await;
-        cx.assert_shared_state(indoc! {"
-            1ˇ2
-            "})
-            .await;
-
-        cx.simulate_shared_keystrokes(["9", "9", "ctrl-a"]).await;
-        cx.assert_shared_state(indoc! {"
-            11ˇ1
-            "})
-            .await;
-        cx.simulate_shared_keystrokes(["1", "1", "1", "ctrl-x"])
-            .await;
-        cx.assert_shared_state(indoc! {"
-            ˇ0
-            "})
-            .await;
-        cx.simulate_shared_keystrokes(["."]).await;
-        cx.assert_shared_state(indoc! {"
-            -11ˇ1
-            "})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_increment_radix(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.assert_matches_neovim("ˇ total: 0xff", ["ctrl-a"], " total: 0x10ˇ0")
-            .await;
-        cx.assert_matches_neovim("ˇ total: 0xff", ["ctrl-x"], " total: 0xfˇe")
-            .await;
-        cx.assert_matches_neovim("ˇ total: 0xFF", ["ctrl-x"], " total: 0xFˇE")
-            .await;
-        cx.assert_matches_neovim("(ˇ0b10f)", ["ctrl-a"], "(0b1ˇ1f)")
-            .await;
-        cx.assert_matches_neovim("ˇ-1", ["ctrl-a"], "ˇ0").await;
-        cx.assert_matches_neovim("banˇana", ["ctrl-a"], "banˇana")
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_increment_steps(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.set_shared_state(indoc! {"
-            ˇ1
-            1
-            1  2
-            1
-            1"})
-            .await;
-
-        cx.simulate_shared_keystrokes(["j", "v", "shift-g", "g", "ctrl-a"])
-            .await;
-        cx.assert_shared_state(indoc! {"
-            1
-            ˇ2
-            3  2
-            4
-            5"})
-            .await;
-
-        cx.simulate_shared_keystrokes(["shift-g", "ctrl-v", "g", "g"])
-            .await;
-        cx.assert_shared_state(indoc! {"
-            «1ˇ»
-            «2ˇ»
-            «3ˇ»  2
-            «4ˇ»
-            «5ˇ»"})
-            .await;
-
-        cx.simulate_shared_keystrokes(["g", "ctrl-x"]).await;
-        cx.assert_shared_state(indoc! {"
-            ˇ0
-            0
-            0  2
-            0
-            0"})
-            .await;
-    }
-}

crates/vim2/src/normal/paste.rs 🔗

@@ -1,476 +0,0 @@
-use std::{borrow::Cow, cmp};
-
-use editor::{
-    display_map::ToDisplayPoint, movement, scroll::autoscroll::Autoscroll, ClipboardSelection,
-    DisplayPoint,
-};
-use gpui::{impl_actions, ViewContext};
-use language::{Bias, SelectionGoal};
-use serde::Deserialize;
-use workspace::Workspace;
-
-use crate::{state::Mode, utils::copy_selections_content, Vim};
-
-#[derive(Clone, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-struct Paste {
-    #[serde(default)]
-    before: bool,
-    #[serde(default)]
-    preserve_clipboard: bool,
-}
-
-impl_actions!(vim, [Paste]);
-
-pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
-    workspace.register_action(paste);
-}
-
-fn paste(_: &mut Workspace, action: &Paste, cx: &mut ViewContext<Workspace>) {
-    Vim::update(cx, |vim, cx| {
-        vim.record_current_action(cx);
-        vim.update_active_editor(cx, |editor, cx| {
-            let text_layout_details = editor.text_layout_details(cx);
-            editor.transact(cx, |editor, cx| {
-                editor.set_clip_at_line_ends(false, cx);
-
-                let Some(item) = cx.read_from_clipboard() else {
-                    return;
-                };
-                let clipboard_text = Cow::Borrowed(item.text());
-                if clipboard_text.is_empty() {
-                    return;
-                }
-
-                if !action.preserve_clipboard && vim.state().mode.is_visual() {
-                    copy_selections_content(editor, vim.state().mode == Mode::VisualLine, cx);
-                }
-
-                // if we are copying from multi-cursor (of visual block mode), we want
-                // to
-                let clipboard_selections =
-                    item.metadata::<Vec<ClipboardSelection>>()
-                        .filter(|clipboard_selections| {
-                            clipboard_selections.len() > 1 && vim.state().mode != Mode::VisualLine
-                        });
-
-                let (display_map, current_selections) = editor.selections.all_adjusted_display(cx);
-
-                // unlike zed, if you have a multi-cursor selection from vim block mode,
-                // pasting it will paste it on subsequent lines, even if you don't yet
-                // have a cursor there.
-                let mut selections_to_process = Vec::new();
-                let mut i = 0;
-                while i < current_selections.len() {
-                    selections_to_process
-                        .push((current_selections[i].start..current_selections[i].end, true));
-                    i += 1;
-                }
-                if let Some(clipboard_selections) = clipboard_selections.as_ref() {
-                    let left = current_selections
-                        .iter()
-                        .map(|selection| cmp::min(selection.start.column(), selection.end.column()))
-                        .min()
-                        .unwrap();
-                    let mut row = current_selections.last().unwrap().end.row() + 1;
-                    while i < clipboard_selections.len() {
-                        let cursor =
-                            display_map.clip_point(DisplayPoint::new(row, left), Bias::Left);
-                        selections_to_process.push((cursor..cursor, false));
-                        i += 1;
-                        row += 1;
-                    }
-                }
-
-                let first_selection_indent_column =
-                    clipboard_selections.as_ref().and_then(|zed_selections| {
-                        zed_selections
-                            .first()
-                            .map(|selection| selection.first_line_indent)
-                    });
-                let before = action.before || vim.state().mode == Mode::VisualLine;
-
-                let mut edits = Vec::new();
-                let mut new_selections = Vec::new();
-                let mut original_indent_columns = Vec::new();
-                let mut start_offset = 0;
-
-                for (ix, (selection, preserve)) in selections_to_process.iter().enumerate() {
-                    let (mut to_insert, original_indent_column) =
-                        if let Some(clipboard_selections) = &clipboard_selections {
-                            if let Some(clipboard_selection) = clipboard_selections.get(ix) {
-                                let end_offset = start_offset + clipboard_selection.len;
-                                let text = clipboard_text[start_offset..end_offset].to_string();
-                                start_offset = end_offset + 1;
-                                (text, Some(clipboard_selection.first_line_indent))
-                            } else {
-                                ("".to_string(), first_selection_indent_column)
-                            }
-                        } else {
-                            (clipboard_text.to_string(), first_selection_indent_column)
-                        };
-                    let line_mode = to_insert.ends_with("\n");
-                    let is_multiline = to_insert.contains("\n");
-
-                    if line_mode && !before {
-                        if selection.is_empty() {
-                            to_insert =
-                                "\n".to_owned() + &to_insert[..to_insert.len() - "\n".len()];
-                        } else {
-                            to_insert = "\n".to_owned() + &to_insert;
-                        }
-                    } else if !line_mode && vim.state().mode == Mode::VisualLine {
-                        to_insert = to_insert + "\n";
-                    }
-
-                    let display_range = if !selection.is_empty() {
-                        selection.start..selection.end
-                    } else if line_mode {
-                        let point = if before {
-                            movement::line_beginning(&display_map, selection.start, false)
-                        } else {
-                            movement::line_end(&display_map, selection.start, false)
-                        };
-                        point..point
-                    } else {
-                        let point = if before {
-                            selection.start
-                        } else {
-                            movement::saturating_right(&display_map, selection.start)
-                        };
-                        point..point
-                    };
-
-                    let point_range = display_range.start.to_point(&display_map)
-                        ..display_range.end.to_point(&display_map);
-                    let anchor = if is_multiline || vim.state().mode == Mode::VisualLine {
-                        display_map.buffer_snapshot.anchor_before(point_range.start)
-                    } else {
-                        display_map.buffer_snapshot.anchor_after(point_range.end)
-                    };
-
-                    if *preserve {
-                        new_selections.push((anchor, line_mode, is_multiline));
-                    }
-                    edits.push((point_range, to_insert));
-                    original_indent_columns.extend(original_indent_column);
-                }
-
-                editor.edit_with_block_indent(edits, original_indent_columns, cx);
-
-                // in line_mode vim will insert the new text on the next (or previous if before) line
-                // and put the cursor on the first non-blank character of the first inserted line (or at the end if the first line is blank).
-                // otherwise vim will insert the next text at (or before) the current cursor position,
-                // the cursor will go to the last (or first, if is_multiline) inserted character.
-                editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                    s.replace_cursors_with(|map| {
-                        let mut cursors = Vec::new();
-                        for (anchor, line_mode, is_multiline) in &new_selections {
-                            let mut cursor = anchor.to_display_point(map);
-                            if *line_mode {
-                                if !before {
-                                    cursor = movement::down(
-                                        map,
-                                        cursor,
-                                        SelectionGoal::None,
-                                        false,
-                                        &text_layout_details,
-                                    )
-                                    .0;
-                                }
-                                cursor = movement::indented_line_beginning(map, cursor, true);
-                            } else if !is_multiline {
-                                cursor = movement::saturating_left(map, cursor)
-                            }
-                            cursors.push(cursor);
-                            if vim.state().mode == Mode::VisualBlock {
-                                break;
-                            }
-                        }
-
-                        cursors
-                    });
-                })
-            });
-        });
-        vim.switch_mode(Mode::Normal, true, cx);
-    });
-}
-
-#[cfg(test)]
-mod test {
-    use crate::{
-        state::Mode,
-        test::{NeovimBackedTestContext, VimTestContext},
-    };
-    use indoc::indoc;
-
-    #[gpui::test]
-    async fn test_paste(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        // single line
-        cx.set_shared_state(indoc! {"
-            The quick brown
-            fox ˇjumps over
-            the lazy dog"})
-            .await;
-        cx.simulate_shared_keystrokes(["v", "w", "y"]).await;
-        cx.assert_shared_clipboard("jumps o").await;
-        cx.set_shared_state(indoc! {"
-            The quick brown
-            fox jumps oveˇr
-            the lazy dog"})
-            .await;
-        cx.simulate_shared_keystroke("p").await;
-        cx.assert_shared_state(indoc! {"
-            The quick brown
-            fox jumps overjumps ˇo
-            the lazy dog"})
-            .await;
-
-        cx.set_shared_state(indoc! {"
-            The quick brown
-            fox jumps oveˇr
-            the lazy dog"})
-            .await;
-        cx.simulate_shared_keystroke("shift-p").await;
-        cx.assert_shared_state(indoc! {"
-            The quick brown
-            fox jumps ovejumps ˇor
-            the lazy dog"})
-            .await;
-
-        // line mode
-        cx.set_shared_state(indoc! {"
-            The quick brown
-            fox juˇmps over
-            the lazy dog"})
-            .await;
-        cx.simulate_shared_keystrokes(["d", "d"]).await;
-        cx.assert_shared_clipboard("fox jumps over\n").await;
-        cx.assert_shared_state(indoc! {"
-            The quick brown
-            the laˇzy dog"})
-            .await;
-        cx.simulate_shared_keystroke("p").await;
-        cx.assert_shared_state(indoc! {"
-            The quick brown
-            the lazy dog
-            ˇfox jumps over"})
-            .await;
-        cx.simulate_shared_keystrokes(["k", "shift-p"]).await;
-        cx.assert_shared_state(indoc! {"
-            The quick brown
-            ˇfox jumps over
-            the lazy dog
-            fox jumps over"})
-            .await;
-
-        // multiline, cursor to first character of pasted text.
-        cx.set_shared_state(indoc! {"
-            The quick brown
-            fox jumps ˇover
-            the lazy dog"})
-            .await;
-        cx.simulate_shared_keystrokes(["v", "j", "y"]).await;
-        cx.assert_shared_clipboard("over\nthe lazy do").await;
-
-        cx.simulate_shared_keystroke("p").await;
-        cx.assert_shared_state(indoc! {"
-            The quick brown
-            fox jumps oˇover
-            the lazy dover
-            the lazy dog"})
-            .await;
-        cx.simulate_shared_keystrokes(["u", "shift-p"]).await;
-        cx.assert_shared_state(indoc! {"
-            The quick brown
-            fox jumps ˇover
-            the lazy doover
-            the lazy dog"})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_paste_visual(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        // copy in visual mode
-        cx.set_shared_state(indoc! {"
-                The quick brown
-                fox jˇumps over
-                the lazy dog"})
-            .await;
-        cx.simulate_shared_keystrokes(["v", "i", "w", "y"]).await;
-        cx.assert_shared_state(indoc! {"
-                The quick brown
-                fox ˇjumps over
-                the lazy dog"})
-            .await;
-        // paste in visual mode
-        cx.simulate_shared_keystrokes(["w", "v", "i", "w", "p"])
-            .await;
-        cx.assert_shared_state(indoc! {"
-                The quick brown
-                fox jumps jumpˇs
-                the lazy dog"})
-            .await;
-        cx.assert_shared_clipboard("over").await;
-        // paste in visual line mode
-        cx.simulate_shared_keystrokes(["up", "shift-v", "shift-p"])
-            .await;
-        cx.assert_shared_state(indoc! {"
-            ˇover
-            fox jumps jumps
-            the lazy dog"})
-            .await;
-        cx.assert_shared_clipboard("over").await;
-        // paste in visual block mode
-        cx.simulate_shared_keystrokes(["ctrl-v", "down", "down", "p"])
-            .await;
-        cx.assert_shared_state(indoc! {"
-            oveˇrver
-            overox jumps jumps
-            overhe lazy dog"})
-            .await;
-
-        // copy in visual line mode
-        cx.set_shared_state(indoc! {"
-                The quick brown
-                fox juˇmps over
-                the lazy dog"})
-            .await;
-        cx.simulate_shared_keystrokes(["shift-v", "d"]).await;
-        cx.assert_shared_state(indoc! {"
-                The quick brown
-                the laˇzy dog"})
-            .await;
-        // paste in visual mode
-        cx.simulate_shared_keystrokes(["v", "i", "w", "p"]).await;
-        cx.assert_shared_state(
-            &indoc! {"
-                The quick brown
-                the_
-                ˇfox jumps over
-                _dog"}
-            .replace("_", " "), // Hack for trailing whitespace
-        )
-        .await;
-        cx.assert_shared_clipboard("lazy").await;
-        cx.set_shared_state(indoc! {"
-            The quick brown
-            fox juˇmps over
-            the lazy dog"})
-            .await;
-        cx.simulate_shared_keystrokes(["shift-v", "d"]).await;
-        cx.assert_shared_state(indoc! {"
-            The quick brown
-            the laˇzy dog"})
-            .await;
-        // paste in visual line mode
-        cx.simulate_shared_keystrokes(["k", "shift-v", "p"]).await;
-        cx.assert_shared_state(indoc! {"
-            ˇfox jumps over
-            the lazy dog"})
-            .await;
-        cx.assert_shared_clipboard("The quick brown\n").await;
-    }
-
-    #[gpui::test]
-    async fn test_paste_visual_block(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-        // copy in visual block mode
-        cx.set_shared_state(indoc! {"
-            The ˇquick brown
-            fox jumps over
-            the lazy dog"})
-            .await;
-        cx.simulate_shared_keystrokes(["ctrl-v", "2", "j", "y"])
-            .await;
-        cx.assert_shared_clipboard("q\nj\nl").await;
-        cx.simulate_shared_keystrokes(["p"]).await;
-        cx.assert_shared_state(indoc! {"
-            The qˇquick brown
-            fox jjumps over
-            the llazy dog"})
-            .await;
-        cx.simulate_shared_keystrokes(["v", "i", "w", "shift-p"])
-            .await;
-        cx.assert_shared_state(indoc! {"
-            The ˇq brown
-            fox jjjumps over
-            the lllazy dog"})
-            .await;
-        cx.simulate_shared_keystrokes(["v", "i", "w", "shift-p"])
-            .await;
-
-        cx.set_shared_state(indoc! {"
-            The ˇquick brown
-            fox jumps over
-            the lazy dog"})
-            .await;
-        cx.simulate_shared_keystrokes(["ctrl-v", "j", "y"]).await;
-        cx.assert_shared_clipboard("q\nj").await;
-        cx.simulate_shared_keystrokes(["l", "ctrl-v", "2", "j", "shift-p"])
-            .await;
-        cx.assert_shared_state(indoc! {"
-            The qˇqick brown
-            fox jjmps over
-            the lzy dog"})
-            .await;
-
-        cx.simulate_shared_keystrokes(["shift-v", "p"]).await;
-        cx.assert_shared_state(indoc! {"
-            ˇq
-            j
-            fox jjmps over
-            the lzy dog"})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_paste_indent(cx: &mut gpui::TestAppContext) {
-        let mut cx = VimTestContext::new_typescript(cx).await;
-
-        cx.set_state(
-            indoc! {"
-            class A {ˇ
-            }
-        "},
-            Mode::Normal,
-        );
-        cx.simulate_keystrokes(["o", "a", "(", ")", "{", "escape"]);
-        cx.assert_state(
-            indoc! {"
-            class A {
-                a()ˇ{}
-            }
-            "},
-            Mode::Normal,
-        );
-        // cursor goes to the first non-blank character in the line;
-        cx.simulate_keystrokes(["y", "y", "p"]);
-        cx.assert_state(
-            indoc! {"
-            class A {
-                a(){}
-                ˇa(){}
-            }
-            "},
-            Mode::Normal,
-        );
-        // indentation is preserved when pasting
-        cx.simulate_keystrokes(["u", "shift-v", "up", "y", "shift-p"]);
-        cx.assert_state(
-            indoc! {"
-                ˇclass A {
-                    a(){}
-                class A {
-                    a(){}
-                }
-                "},
-            Mode::Normal,
-        );
-    }
-}

crates/vim2/src/normal/repeat.rs 🔗

@@ -1,496 +0,0 @@
-use crate::{
-    insert::NormalBefore,
-    motion::Motion,
-    state::{Mode, RecordedSelection, ReplayableAction},
-    visual::visual_motion,
-    Vim,
-};
-use gpui::{actions, Action, ViewContext, WindowContext};
-use workspace::Workspace;
-
-actions!(vim, [Repeat, EndRepeat]);
-
-fn should_replay(action: &Box<dyn Action>) -> bool {
-    // skip so that we don't leave the character palette open
-    if editor::ShowCharacterPalette.partial_eq(&**action) {
-        return false;
-    }
-    true
-}
-
-fn repeatable_insert(action: &ReplayableAction) -> Option<Box<dyn Action>> {
-    match action {
-        ReplayableAction::Action(action) => {
-            if super::InsertBefore.partial_eq(&**action)
-                || super::InsertAfter.partial_eq(&**action)
-                || super::InsertFirstNonWhitespace.partial_eq(&**action)
-                || super::InsertEndOfLine.partial_eq(&**action)
-            {
-                Some(super::InsertBefore.boxed_clone())
-            } else if super::InsertLineAbove.partial_eq(&**action)
-                || super::InsertLineBelow.partial_eq(&**action)
-            {
-                Some(super::InsertLineBelow.boxed_clone())
-            } else {
-                None
-            }
-        }
-        ReplayableAction::Insertion { .. } => None,
-    }
-}
-
-pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
-    workspace.register_action(|_: &mut Workspace, _: &EndRepeat, cx| {
-        Vim::update(cx, |vim, cx| {
-            vim.workspace_state.replaying = false;
-            vim.switch_mode(Mode::Normal, false, cx)
-        });
-    });
-
-    workspace.register_action(|_: &mut Workspace, _: &Repeat, cx| repeat(cx, false));
-}
-
-pub(crate) fn repeat(cx: &mut WindowContext, from_insert_mode: bool) {
-    let Some((mut actions, editor, selection)) = Vim::update(cx, |vim, cx| {
-        let actions = vim.workspace_state.recorded_actions.clone();
-        if actions.is_empty() {
-            return None;
-        }
-
-        let Some(editor) = vim.active_editor.clone() else {
-            return None;
-        };
-        let count = vim.take_count(cx);
-
-        let selection = vim.workspace_state.recorded_selection.clone();
-        match selection {
-            RecordedSelection::SingleLine { .. } | RecordedSelection::Visual { .. } => {
-                vim.workspace_state.recorded_count = None;
-                vim.switch_mode(Mode::Visual, false, cx)
-            }
-            RecordedSelection::VisualLine { .. } => {
-                vim.workspace_state.recorded_count = None;
-                vim.switch_mode(Mode::VisualLine, false, cx)
-            }
-            RecordedSelection::VisualBlock { .. } => {
-                vim.workspace_state.recorded_count = None;
-                vim.switch_mode(Mode::VisualBlock, false, cx)
-            }
-            RecordedSelection::None => {
-                if let Some(count) = count {
-                    vim.workspace_state.recorded_count = Some(count);
-                }
-            }
-        }
-
-        Some((actions, editor, selection))
-    }) else {
-        return;
-    };
-
-    match selection {
-        RecordedSelection::SingleLine { cols } => {
-            if cols > 1 {
-                visual_motion(Motion::Right, Some(cols as usize - 1), cx)
-            }
-        }
-        RecordedSelection::Visual { rows, cols } => {
-            visual_motion(
-                Motion::Down {
-                    display_lines: false,
-                },
-                Some(rows as usize),
-                cx,
-            );
-            visual_motion(
-                Motion::StartOfLine {
-                    display_lines: false,
-                },
-                None,
-                cx,
-            );
-            if cols > 1 {
-                visual_motion(Motion::Right, Some(cols as usize - 1), cx)
-            }
-        }
-        RecordedSelection::VisualBlock { rows, cols } => {
-            visual_motion(
-                Motion::Down {
-                    display_lines: false,
-                },
-                Some(rows as usize),
-                cx,
-            );
-            if cols > 1 {
-                visual_motion(Motion::Right, Some(cols as usize - 1), cx);
-            }
-        }
-        RecordedSelection::VisualLine { rows } => {
-            visual_motion(
-                Motion::Down {
-                    display_lines: false,
-                },
-                Some(rows as usize),
-                cx,
-            );
-        }
-        RecordedSelection::None => {}
-    }
-
-    // insert internally uses repeat to handle counts
-    // vim doesn't treat 3a1 as though you literally repeated a1
-    // 3 times, instead it inserts the content thrice at the insert position.
-    if let Some(to_repeat) = repeatable_insert(&actions[0]) {
-        if let Some(ReplayableAction::Action(action)) = actions.last() {
-            if NormalBefore.partial_eq(&**action) {
-                actions.pop();
-            }
-        }
-
-        let mut new_actions = actions.clone();
-        actions[0] = ReplayableAction::Action(to_repeat.boxed_clone());
-
-        let mut count = Vim::read(cx).workspace_state.recorded_count.unwrap_or(1);
-
-        // if we came from insert mode we're just doing repititions 2 onwards.
-        if from_insert_mode {
-            count -= 1;
-            new_actions[0] = actions[0].clone();
-        }
-
-        for _ in 1..count {
-            new_actions.append(actions.clone().as_mut());
-        }
-        new_actions.push(ReplayableAction::Action(NormalBefore.boxed_clone()));
-        actions = new_actions;
-    }
-
-    Vim::update(cx, |vim, _| vim.workspace_state.replaying = true);
-    let window = cx.window_handle();
-    cx.spawn(move |mut cx| async move {
-        editor.update(&mut cx, |editor, _| {
-            editor.show_local_selections = false;
-        })?;
-        for action in actions {
-            match action {
-                ReplayableAction::Action(action) => {
-                    if should_replay(&action) {
-                        window.update(&mut cx, |_, cx| cx.dispatch_action(action))
-                    } else {
-                        Ok(())
-                    }
-                }
-                ReplayableAction::Insertion {
-                    text,
-                    utf16_range_to_replace,
-                } => editor.update(&mut cx, |editor, cx| {
-                    editor.replay_insert_event(&text, utf16_range_to_replace.clone(), cx)
-                }),
-            }?
-        }
-        editor.update(&mut cx, |editor, _| {
-            editor.show_local_selections = true;
-        })?;
-        window.update(&mut cx, |_, cx| cx.dispatch_action(EndRepeat.boxed_clone()))
-    })
-    .detach_and_log_err(cx);
-}
-
-#[cfg(test)]
-mod test {
-    use editor::test::editor_lsp_test_context::EditorLspTestContext;
-    use futures::StreamExt;
-    use indoc::indoc;
-
-    use gpui::InputHandler;
-
-    use crate::{
-        state::Mode,
-        test::{NeovimBackedTestContext, VimTestContext},
-    };
-
-    #[gpui::test]
-    async fn test_dot_repeat(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        // "o"
-        cx.set_shared_state("ˇhello").await;
-        cx.simulate_shared_keystrokes(["o", "w", "o", "r", "l", "d", "escape"])
-            .await;
-        cx.assert_shared_state("hello\nworlˇd").await;
-        cx.simulate_shared_keystrokes(["."]).await;
-        cx.assert_shared_state("hello\nworld\nworlˇd").await;
-
-        // "d"
-        cx.simulate_shared_keystrokes(["^", "d", "f", "o"]).await;
-        cx.simulate_shared_keystrokes(["g", "g", "."]).await;
-        cx.assert_shared_state("ˇ\nworld\nrld").await;
-
-        // "p" (note that it pastes the current clipboard)
-        cx.simulate_shared_keystrokes(["j", "y", "y", "p"]).await;
-        cx.simulate_shared_keystrokes(["shift-g", "y", "y", "."])
-            .await;
-        cx.assert_shared_state("\nworld\nworld\nrld\nˇrld").await;
-
-        // "~" (note that counts apply to the action taken, not . itself)
-        cx.set_shared_state("ˇthe quick brown fox").await;
-        cx.simulate_shared_keystrokes(["2", "~", "."]).await;
-        cx.set_shared_state("THE ˇquick brown fox").await;
-        cx.simulate_shared_keystrokes(["3", "."]).await;
-        cx.set_shared_state("THE QUIˇck brown fox").await;
-        cx.run_until_parked();
-        cx.simulate_shared_keystrokes(["."]).await;
-        cx.assert_shared_state("THE QUICK ˇbrown fox").await;
-    }
-
-    #[gpui::test]
-    async fn test_repeat_ime(cx: &mut gpui::TestAppContext) {
-        let mut cx = VimTestContext::new(cx, true).await;
-
-        cx.set_state("hˇllo", Mode::Normal);
-        cx.simulate_keystrokes(["i"]);
-
-        // simulate brazilian input for ä.
-        cx.update_editor(|editor, cx| {
-            editor.replace_and_mark_text_in_range(None, "\"", Some(1..1), cx);
-            editor.replace_text_in_range(None, "ä", cx);
-        });
-        cx.simulate_keystrokes(["escape"]);
-        cx.assert_state("hˇällo", Mode::Normal);
-        cx.simulate_keystrokes(["."]);
-        cx.assert_state("hˇäällo", Mode::Normal);
-    }
-
-    #[gpui::test]
-    async fn test_repeat_completion(cx: &mut gpui::TestAppContext) {
-        VimTestContext::init(cx);
-        let 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;
-        let mut cx = VimTestContext::new_with_lsp(cx, true);
-
-        cx.set_state(
-            indoc! {"
-            onˇe
-            two
-            three
-        "},
-            Mode::Normal,
-        );
-
-        let mut request =
-            cx.handle_request::<lsp::request::Completion, _, _>(move |_, params, _| async move {
-                let position = params.text_document_position.position;
-                Ok(Some(lsp::CompletionResponse::Array(vec![
-                    lsp::CompletionItem {
-                        label: "first".to_string(),
-                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
-                            range: lsp::Range::new(position.clone(), position.clone()),
-                            new_text: "first".to_string(),
-                        })),
-                        ..Default::default()
-                    },
-                    lsp::CompletionItem {
-                        label: "second".to_string(),
-                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
-                            range: lsp::Range::new(position.clone(), position.clone()),
-                            new_text: "second".to_string(),
-                        })),
-                        ..Default::default()
-                    },
-                ])))
-            });
-        cx.simulate_keystrokes(["a", "."]);
-        request.next().await;
-        cx.condition(|editor, _| editor.context_menu_visible())
-            .await;
-        cx.simulate_keystrokes(["down", "enter", "!", "escape"]);
-
-        cx.assert_state(
-            indoc! {"
-                one.secondˇ!
-                two
-                three
-            "},
-            Mode::Normal,
-        );
-        cx.simulate_keystrokes(["j", "."]);
-        cx.assert_state(
-            indoc! {"
-                one.second!
-                two.secondˇ!
-                three
-            "},
-            Mode::Normal,
-        );
-    }
-
-    #[gpui::test]
-    async fn test_repeat_visual(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        // single-line (3 columns)
-        cx.set_shared_state(indoc! {
-            "ˇthe quick brown
-            fox jumps over
-            the lazy dog"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["v", "i", "w", "s", "o", "escape"])
-            .await;
-        cx.assert_shared_state(indoc! {
-            "ˇo quick brown
-            fox jumps over
-            the lazy dog"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["j", "w", "."]).await;
-        cx.assert_shared_state(indoc! {
-            "o quick brown
-            fox ˇops over
-            the lazy dog"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["f", "r", "."]).await;
-        cx.assert_shared_state(indoc! {
-            "o quick brown
-            fox ops oveˇothe lazy dog"
-        })
-        .await;
-
-        // visual
-        cx.set_shared_state(indoc! {
-            "the ˇquick brown
-            fox jumps over
-            fox jumps over
-            fox jumps over
-            the lazy dog"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["v", "j", "x"]).await;
-        cx.assert_shared_state(indoc! {
-            "the ˇumps over
-            fox jumps over
-            fox jumps over
-            the lazy dog"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["."]).await;
-        cx.assert_shared_state(indoc! {
-            "the ˇumps over
-            fox jumps over
-            the lazy dog"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["w", "."]).await;
-        cx.assert_shared_state(indoc! {
-            "the umps ˇumps over
-            the lazy dog"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["j", "."]).await;
-        cx.assert_shared_state(indoc! {
-            "the umps umps over
-            the ˇog"
-        })
-        .await;
-
-        // block mode (3 rows)
-        cx.set_shared_state(indoc! {
-            "ˇthe quick brown
-            fox jumps over
-            the lazy dog"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["ctrl-v", "j", "j", "shift-i", "o", "escape"])
-            .await;
-        cx.assert_shared_state(indoc! {
-            "ˇothe quick brown
-            ofox jumps over
-            othe lazy dog"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["j", "4", "l", "."]).await;
-        cx.assert_shared_state(indoc! {
-            "othe quick brown
-            ofoxˇo jumps over
-            otheo lazy dog"
-        })
-        .await;
-
-        // line mode
-        cx.set_shared_state(indoc! {
-            "ˇthe quick brown
-            fox jumps over
-            the lazy dog"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["shift-v", "shift-r", "o", "escape"])
-            .await;
-        cx.assert_shared_state(indoc! {
-            "ˇo
-            fox jumps over
-            the lazy dog"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["j", "."]).await;
-        cx.assert_shared_state(indoc! {
-            "o
-            ˇo
-            the lazy dog"
-        })
-        .await;
-    }
-
-    #[gpui::test]
-    async fn test_repeat_motion_counts(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.set_shared_state(indoc! {
-            "ˇthe quick brown
-            fox jumps over
-            the lazy dog"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["3", "d", "3", "l"]).await;
-        cx.assert_shared_state(indoc! {
-            "ˇ brown
-            fox jumps over
-            the lazy dog"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["j", "."]).await;
-        cx.assert_shared_state(indoc! {
-            " brown
-            ˇ over
-            the lazy dog"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["j", "2", "."]).await;
-        cx.assert_shared_state(indoc! {
-            " brown
-             over
-            ˇe lazy dog"
-        })
-        .await;
-    }
-
-    #[gpui::test]
-    async fn test_record_interrupted(cx: &mut gpui::TestAppContext) {
-        let mut cx = VimTestContext::new(cx, true).await;
-
-        cx.set_state("ˇhello\n", Mode::Normal);
-        cx.simulate_keystrokes(["4", "i", "j", "cmd-shift-p", "escape"]);
-        cx.simulate_keystrokes(["escape"]);
-        cx.assert_state("ˇjhello\n", Mode::Normal);
-    }
-}

crates/vim2/src/normal/scroll.rs 🔗

@@ -1,247 +0,0 @@
-use crate::Vim;
-use editor::{
-    display_map::ToDisplayPoint,
-    scroll::{scroll_amount::ScrollAmount, VERTICAL_SCROLL_MARGIN},
-    DisplayPoint, Editor,
-};
-use gpui::{actions, ViewContext};
-use language::Bias;
-use workspace::Workspace;
-
-actions!(
-    vim,
-    [LineUp, LineDown, ScrollUp, ScrollDown, PageUp, PageDown]
-);
-
-pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
-    workspace.register_action(|_: &mut Workspace, _: &LineDown, cx| {
-        scroll(cx, false, |c| ScrollAmount::Line(c.unwrap_or(1.)))
-    });
-    workspace.register_action(|_: &mut Workspace, _: &LineUp, cx| {
-        scroll(cx, false, |c| ScrollAmount::Line(-c.unwrap_or(1.)))
-    });
-    workspace.register_action(|_: &mut Workspace, _: &PageDown, cx| {
-        scroll(cx, false, |c| ScrollAmount::Page(c.unwrap_or(1.)))
-    });
-    workspace.register_action(|_: &mut Workspace, _: &PageUp, cx| {
-        scroll(cx, false, |c| ScrollAmount::Page(-c.unwrap_or(1.)))
-    });
-    workspace.register_action(|_: &mut Workspace, _: &ScrollDown, cx| {
-        scroll(cx, true, |c| {
-            if let Some(c) = c {
-                ScrollAmount::Line(c)
-            } else {
-                ScrollAmount::Page(0.5)
-            }
-        })
-    });
-    workspace.register_action(|_: &mut Workspace, _: &ScrollUp, cx| {
-        scroll(cx, true, |c| {
-            if let Some(c) = c {
-                ScrollAmount::Line(-c)
-            } else {
-                ScrollAmount::Page(-0.5)
-            }
-        })
-    });
-}
-
-fn scroll(
-    cx: &mut ViewContext<Workspace>,
-    move_cursor: bool,
-    by: fn(c: Option<f32>) -> ScrollAmount,
-) {
-    Vim::update(cx, |vim, cx| {
-        let amount = by(vim.take_count(cx).map(|c| c as f32));
-        vim.update_active_editor(cx, |editor, cx| {
-            scroll_editor(editor, move_cursor, &amount, cx)
-        });
-    })
-}
-
-fn scroll_editor(
-    editor: &mut Editor,
-    preserve_cursor_position: bool,
-    amount: &ScrollAmount,
-    cx: &mut ViewContext<Editor>,
-) {
-    let should_move_cursor = editor.newest_selection_on_screen(cx).is_eq();
-    let old_top_anchor = editor.scroll_manager.anchor().anchor;
-
-    editor.scroll_screen(amount, cx);
-    if should_move_cursor {
-        let visible_rows = if let Some(visible_rows) = editor.visible_line_count() {
-            visible_rows as u32
-        } else {
-            return;
-        };
-
-        let top_anchor = editor.scroll_manager.anchor().anchor;
-
-        editor.change_selections(None, cx, |s| {
-            s.move_with(|map, selection| {
-                let mut head = selection.head();
-                let top = top_anchor.to_display_point(map);
-
-                if preserve_cursor_position {
-                    let old_top = old_top_anchor.to_display_point(map);
-                    let new_row = top.row() + selection.head().row() - old_top.row();
-                    head = map.clip_point(DisplayPoint::new(new_row, head.column()), Bias::Left)
-                }
-                let min_row = top.row() + VERTICAL_SCROLL_MARGIN as u32;
-                let max_row = top.row() + visible_rows - VERTICAL_SCROLL_MARGIN as u32 - 1;
-
-                let new_head = if head.row() < min_row {
-                    map.clip_point(DisplayPoint::new(min_row, head.column()), Bias::Left)
-                } else if head.row() > max_row {
-                    map.clip_point(DisplayPoint::new(max_row, head.column()), Bias::Left)
-                } else {
-                    head
-                };
-                if selection.is_empty() {
-                    selection.collapse_to(new_head, selection.goal)
-                } else {
-                    selection.set_head(new_head, selection.goal)
-                };
-            })
-        });
-    }
-}
-
-#[cfg(test)]
-mod test {
-    use crate::{
-        state::Mode,
-        test::{NeovimBackedTestContext, VimTestContext},
-    };
-    use gpui::{point, px, size, Context};
-    use indoc::indoc;
-    use language::Point;
-
-    #[gpui::test]
-    async fn test_scroll(cx: &mut gpui::TestAppContext) {
-        let mut cx = VimTestContext::new(cx, true).await;
-
-        let (line_height, visible_line_count) = cx.editor(|editor, cx| {
-            (
-                editor
-                    .style()
-                    .unwrap()
-                    .text
-                    .line_height_in_pixels(cx.rem_size()),
-                editor.visible_line_count().unwrap(),
-            )
-        });
-
-        let window = cx.window;
-        let margin = cx
-            .update_window(window, |_, cx| {
-                cx.viewport_size().height - line_height * visible_line_count
-            })
-            .unwrap();
-        cx.simulate_window_resize(
-            cx.window,
-            size(px(1000.), margin + 8. * line_height - px(1.0)),
-        );
-
-        cx.set_state(
-            indoc!(
-                "ˇone
-                two
-                three
-                four
-                five
-                six
-                seven
-                eight
-                nine
-                ten
-                eleven
-                twelve
-            "
-            ),
-            Mode::Normal,
-        );
-
-        cx.update_editor(|editor, cx| {
-            assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 0.))
-        });
-        cx.simulate_keystrokes(["ctrl-e"]);
-        cx.update_editor(|editor, cx| {
-            assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 1.))
-        });
-        cx.simulate_keystrokes(["2", "ctrl-e"]);
-        cx.update_editor(|editor, cx| {
-            assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 3.))
-        });
-        cx.simulate_keystrokes(["ctrl-y"]);
-        cx.update_editor(|editor, cx| {
-            assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 2.))
-        });
-
-        // does not select in normal mode
-        cx.simulate_keystrokes(["g", "g"]);
-        cx.update_editor(|editor, cx| {
-            assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 0.))
-        });
-        cx.simulate_keystrokes(["ctrl-d"]);
-        cx.update_editor(|editor, cx| {
-            assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 3.0));
-            assert_eq!(
-                editor.selections.newest(cx).range(),
-                Point::new(6, 0)..Point::new(6, 0)
-            )
-        });
-
-        // does select in visual mode
-        cx.simulate_keystrokes(["g", "g"]);
-        cx.update_editor(|editor, cx| {
-            assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 0.))
-        });
-        cx.simulate_keystrokes(["v", "ctrl-d"]);
-        cx.update_editor(|editor, cx| {
-            assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 3.0));
-            assert_eq!(
-                editor.selections.newest(cx).range(),
-                Point::new(0, 0)..Point::new(6, 1)
-            )
-        });
-    }
-    #[gpui::test]
-    async fn test_ctrl_d_u(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.set_scroll_height(10).await;
-
-        pub fn sample_text(rows: usize, cols: usize, start_char: char) -> String {
-            let mut text = String::new();
-            for row in 0..rows {
-                let c: char = (start_char as u32 + row as u32) as u8 as char;
-                let mut line = c.to_string().repeat(cols);
-                if row < rows - 1 {
-                    line.push('\n');
-                }
-                text += &line;
-            }
-            text
-        }
-        let content = "ˇ".to_owned() + &sample_text(26, 2, 'a');
-        cx.set_shared_state(&content).await;
-
-        // skip over the scrolloff at the top
-        // test ctrl-d
-        cx.simulate_shared_keystrokes(["4", "j", "ctrl-d"]).await;
-        cx.assert_state_matches().await;
-        cx.simulate_shared_keystrokes(["ctrl-d"]).await;
-        cx.assert_state_matches().await;
-        cx.simulate_shared_keystrokes(["g", "g", "ctrl-d"]).await;
-        cx.assert_state_matches().await;
-
-        // test ctrl-u
-        cx.simulate_shared_keystrokes(["ctrl-u"]).await;
-        cx.assert_state_matches().await;
-        cx.simulate_shared_keystrokes(["ctrl-d", "ctrl-d", "4", "j", "ctrl-u", "ctrl-u"])
-            .await;
-        cx.assert_state_matches().await;
-    }
-}

crates/vim2/src/normal/search.rs 🔗

@@ -1,477 +0,0 @@
-use gpui::{actions, impl_actions, ViewContext};
-use search::{buffer_search, BufferSearchBar, SearchMode, SearchOptions};
-use serde_derive::Deserialize;
-use workspace::{searchable::Direction, Workspace};
-
-use crate::{motion::Motion, normal::move_cursor, state::SearchState, Vim};
-
-#[derive(Clone, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-pub(crate) struct MoveToNext {
-    #[serde(default)]
-    partial_word: bool,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-pub(crate) struct MoveToPrev {
-    #[serde(default)]
-    partial_word: bool,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-pub(crate) struct Search {
-    #[serde(default)]
-    backwards: bool,
-}
-
-#[derive(Debug, Clone, PartialEq, Deserialize)]
-pub struct FindCommand {
-    pub query: String,
-    pub backwards: bool,
-}
-
-#[derive(Debug, Clone, PartialEq, Deserialize)]
-pub struct ReplaceCommand {
-    pub query: String,
-}
-
-#[derive(Debug, Default)]
-struct Replacement {
-    search: String,
-    replacement: String,
-    should_replace_all: bool,
-    is_case_sensitive: bool,
-}
-
-actions!(vim, [SearchSubmit]);
-impl_actions!(
-    vim,
-    [FindCommand, ReplaceCommand, Search, MoveToPrev, MoveToNext]
-);
-
-pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
-    workspace.register_action(move_to_next);
-    workspace.register_action(move_to_prev);
-    workspace.register_action(search);
-    workspace.register_action(search_submit);
-    workspace.register_action(search_deploy);
-
-    workspace.register_action(find_command);
-    workspace.register_action(replace_command);
-}
-
-fn move_to_next(workspace: &mut Workspace, action: &MoveToNext, cx: &mut ViewContext<Workspace>) {
-    move_to_internal(workspace, Direction::Next, !action.partial_word, cx)
-}
-
-fn move_to_prev(workspace: &mut Workspace, action: &MoveToPrev, cx: &mut ViewContext<Workspace>) {
-    move_to_internal(workspace, Direction::Prev, !action.partial_word, cx)
-}
-
-fn search(workspace: &mut Workspace, action: &Search, cx: &mut ViewContext<Workspace>) {
-    let pane = workspace.active_pane().clone();
-    let direction = if action.backwards {
-        Direction::Prev
-    } else {
-        Direction::Next
-    };
-    Vim::update(cx, |vim, cx| {
-        let count = vim.take_count(cx).unwrap_or(1);
-        pane.update(cx, |pane, cx| {
-            if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
-                search_bar.update(cx, |search_bar, cx| {
-                    if !search_bar.show(cx) {
-                        return;
-                    }
-                    let query = search_bar.query(cx);
-
-                    search_bar.select_query(cx);
-                    cx.focus_self();
-
-                    if query.is_empty() {
-                        search_bar.set_replacement(None, cx);
-                        search_bar.set_search_options(SearchOptions::CASE_SENSITIVE, cx);
-                        search_bar.activate_search_mode(SearchMode::Regex, cx);
-                    }
-                    vim.workspace_state.search = SearchState {
-                        direction,
-                        count,
-                        initial_query: query.clone(),
-                    };
-                });
-            }
-        })
-    })
-}
-
-// hook into the existing to clear out any vim search state on cmd+f or edit -> find.
-fn search_deploy(_: &mut Workspace, _: &buffer_search::Deploy, cx: &mut ViewContext<Workspace>) {
-    Vim::update(cx, |vim, _| vim.workspace_state.search = Default::default());
-    cx.propagate();
-}
-
-fn search_submit(workspace: &mut Workspace, _: &SearchSubmit, cx: &mut ViewContext<Workspace>) {
-    Vim::update(cx, |vim, cx| {
-        let pane = workspace.active_pane().clone();
-        pane.update(cx, |pane, cx| {
-            if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
-                search_bar.update(cx, |search_bar, cx| {
-                    let state = &mut vim.workspace_state.search;
-                    let mut count = state.count;
-                    let direction = state.direction;
-
-                    // in the case that the query has changed, the search bar
-                    // will have selected the next match already.
-                    if (search_bar.query(cx) != state.initial_query)
-                        && state.direction == Direction::Next
-                    {
-                        count = count.saturating_sub(1)
-                    }
-                    state.count = 1;
-                    search_bar.select_match(direction, count, cx);
-                    search_bar.focus_editor(&Default::default(), cx);
-                });
-            }
-        });
-    })
-}
-
-pub fn move_to_internal(
-    workspace: &mut Workspace,
-    direction: Direction,
-    whole_word: bool,
-    cx: &mut ViewContext<Workspace>,
-) {
-    Vim::update(cx, |vim, cx| {
-        let pane = workspace.active_pane().clone();
-        let count = vim.take_count(cx).unwrap_or(1);
-        pane.update(cx, |pane, cx| {
-            if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
-                let search = search_bar.update(cx, |search_bar, cx| {
-                    let mut options = SearchOptions::CASE_SENSITIVE;
-                    options.set(SearchOptions::WHOLE_WORD, whole_word);
-                    if search_bar.show(cx) {
-                        search_bar
-                            .query_suggestion(cx)
-                            .map(|query| search_bar.search(&query, Some(options), cx))
-                    } else {
-                        None
-                    }
-                });
-
-                if let Some(search) = search {
-                    let search_bar = search_bar.downgrade();
-                    cx.spawn(|_, mut cx| async move {
-                        search.await?;
-                        search_bar.update(&mut cx, |search_bar, cx| {
-                            search_bar.select_match(direction, count, cx)
-                        })?;
-                        anyhow::Ok(())
-                    })
-                    .detach_and_log_err(cx);
-                }
-            }
-        });
-        vim.clear_operator(cx);
-    });
-}
-
-fn find_command(workspace: &mut Workspace, action: &FindCommand, cx: &mut ViewContext<Workspace>) {
-    let pane = workspace.active_pane().clone();
-    pane.update(cx, |pane, cx| {
-        if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
-            let search = search_bar.update(cx, |search_bar, cx| {
-                if !search_bar.show(cx) {
-                    return None;
-                }
-                let mut query = action.query.clone();
-                if query == "" {
-                    query = search_bar.query(cx);
-                };
-
-                search_bar.activate_search_mode(SearchMode::Regex, cx);
-                Some(search_bar.search(&query, Some(SearchOptions::CASE_SENSITIVE), cx))
-            });
-            let Some(search) = search else { return };
-            let search_bar = search_bar.downgrade();
-            let direction = if action.backwards {
-                Direction::Prev
-            } else {
-                Direction::Next
-            };
-            cx.spawn(|_, mut cx| async move {
-                search.await?;
-                search_bar.update(&mut cx, |search_bar, cx| {
-                    search_bar.select_match(direction, 1, cx)
-                })?;
-                anyhow::Ok(())
-            })
-            .detach_and_log_err(cx);
-        }
-    })
-}
-
-fn replace_command(
-    workspace: &mut Workspace,
-    action: &ReplaceCommand,
-    cx: &mut ViewContext<Workspace>,
-) {
-    let replacement = parse_replace_all(&action.query);
-    let pane = workspace.active_pane().clone();
-    pane.update(cx, |pane, cx| {
-        let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() else {
-            return;
-        };
-        let search = search_bar.update(cx, |search_bar, cx| {
-            if !search_bar.show(cx) {
-                return None;
-            }
-
-            let mut options = SearchOptions::default();
-            if replacement.is_case_sensitive {
-                options.set(SearchOptions::CASE_SENSITIVE, true)
-            }
-            let search = if replacement.search == "" {
-                search_bar.query(cx)
-            } else {
-                replacement.search
-            };
-
-            search_bar.set_replacement(Some(&replacement.replacement), cx);
-            search_bar.activate_search_mode(SearchMode::Regex, cx);
-            Some(search_bar.search(&search, Some(options), cx))
-        });
-        let Some(search) = search else { return };
-        let search_bar = search_bar.downgrade();
-        cx.spawn(|_, mut cx| async move {
-            search.await?;
-            search_bar.update(&mut cx, |search_bar, cx| {
-                if replacement.should_replace_all {
-                    search_bar.select_last_match(cx);
-                    search_bar.replace_all(&Default::default(), cx);
-                    Vim::update(cx, |vim, cx| {
-                        move_cursor(
-                            vim,
-                            Motion::StartOfLine {
-                                display_lines: false,
-                            },
-                            None,
-                            cx,
-                        )
-                    })
-                }
-            })?;
-            anyhow::Ok(())
-        })
-        .detach_and_log_err(cx);
-    })
-}
-
-// convert a vim query into something more usable by zed.
-// we don't attempt to fully convert between the two regex syntaxes,
-// but we do flip \( and \) to ( and ) (and vice-versa) in the pattern,
-// and convert \0..\9 to $0..$9 in the replacement so that common idioms work.
-fn parse_replace_all(query: &str) -> Replacement {
-    let mut chars = query.chars();
-    if Some('%') != chars.next() || Some('s') != chars.next() {
-        return Replacement::default();
-    }
-
-    let Some(delimeter) = chars.next() else {
-        return Replacement::default();
-    };
-
-    let mut search = String::new();
-    let mut replacement = String::new();
-    let mut flags = String::new();
-
-    let mut buffer = &mut search;
-
-    let mut escaped = false;
-    // 0 - parsing search
-    // 1 - parsing replacement
-    // 2 - parsing flags
-    let mut phase = 0;
-
-    for c in chars {
-        if escaped {
-            escaped = false;
-            if phase == 1 && c.is_digit(10) {
-                buffer.push('$')
-            // unescape escaped parens
-            } else if phase == 0 && c == '(' || c == ')' {
-            } else if c != delimeter {
-                buffer.push('\\')
-            }
-            buffer.push(c)
-        } else if c == '\\' {
-            escaped = true;
-        } else if c == delimeter {
-            if phase == 0 {
-                buffer = &mut replacement;
-                phase = 1;
-            } else if phase == 1 {
-                buffer = &mut flags;
-                phase = 2;
-            } else {
-                break;
-            }
-        } else {
-            // escape unescaped parens
-            if phase == 0 && c == '(' || c == ')' {
-                buffer.push('\\')
-            }
-            buffer.push(c)
-        }
-    }
-
-    let mut replacement = Replacement {
-        search,
-        replacement,
-        should_replace_all: true,
-        is_case_sensitive: true,
-    };
-
-    for c in flags.chars() {
-        match c {
-            'g' | 'I' => {}
-            'c' | 'n' => replacement.should_replace_all = false,
-            'i' => replacement.is_case_sensitive = false,
-            _ => {}
-        }
-    }
-
-    replacement
-}
-
-#[cfg(test)]
-mod test {
-    use editor::DisplayPoint;
-    use search::BufferSearchBar;
-
-    use crate::{state::Mode, test::VimTestContext};
-
-    #[gpui::test]
-    async fn test_move_to_next(cx: &mut gpui::TestAppContext) {
-        let mut cx = VimTestContext::new(cx, true).await;
-        cx.set_state("ˇhi\nhigh\nhi\n", Mode::Normal);
-
-        cx.simulate_keystrokes(["*"]);
-        cx.run_until_parked();
-        cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);
-
-        cx.simulate_keystrokes(["*"]);
-        cx.run_until_parked();
-        cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);
-
-        cx.simulate_keystrokes(["#"]);
-        cx.run_until_parked();
-        cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);
-
-        cx.simulate_keystrokes(["#"]);
-        cx.run_until_parked();
-        cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);
-
-        cx.simulate_keystrokes(["2", "*"]);
-        cx.run_until_parked();
-        cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);
-
-        cx.simulate_keystrokes(["g", "*"]);
-        cx.run_until_parked();
-        cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal);
-
-        cx.simulate_keystrokes(["n"]);
-        cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);
-
-        cx.simulate_keystrokes(["g", "#"]);
-        cx.run_until_parked();
-        cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal);
-    }
-
-    #[gpui::test]
-    async fn test_search(cx: &mut gpui::TestAppContext) {
-        let mut cx = VimTestContext::new(cx, true).await;
-
-        cx.set_state("aa\nbˇb\ncc\ncc\ncc\n", Mode::Normal);
-        cx.simulate_keystrokes(["/", "c", "c"]);
-
-        let search_bar = cx.workspace(|workspace, cx| {
-            workspace
-                .active_pane()
-                .read(cx)
-                .toolbar()
-                .read(cx)
-                .item_of_type::<BufferSearchBar>()
-                .expect("Buffer search bar should be deployed")
-        });
-
-        cx.update_view(search_bar, |bar, cx| {
-            assert_eq!(bar.query(cx), "cc");
-        });
-
-        cx.run_until_parked();
-
-        cx.update_editor(|editor, cx| {
-            let highlights = editor.all_text_background_highlights(cx);
-            assert_eq!(3, highlights.len());
-            assert_eq!(
-                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 2),
-                highlights[0].0
-            )
-        });
-
-        cx.simulate_keystrokes(["enter"]);
-        cx.assert_state("aa\nbb\nˇcc\ncc\ncc\n", Mode::Normal);
-
-        // n to go to next/N to go to previous
-        cx.simulate_keystrokes(["n"]);
-        cx.assert_state("aa\nbb\ncc\nˇcc\ncc\n", Mode::Normal);
-        cx.simulate_keystrokes(["shift-n"]);
-        cx.assert_state("aa\nbb\nˇcc\ncc\ncc\n", Mode::Normal);
-
-        // ?<enter> to go to previous
-        cx.simulate_keystrokes(["?", "enter"]);
-        cx.assert_state("aa\nbb\ncc\ncc\nˇcc\n", Mode::Normal);
-        cx.simulate_keystrokes(["?", "enter"]);
-        cx.assert_state("aa\nbb\ncc\nˇcc\ncc\n", Mode::Normal);
-
-        // /<enter> to go to next
-        cx.simulate_keystrokes(["/", "enter"]);
-        cx.assert_state("aa\nbb\ncc\ncc\nˇcc\n", Mode::Normal);
-
-        // ?{search}<enter> to search backwards
-        cx.simulate_keystrokes(["?", "b", "enter"]);
-        cx.assert_state("aa\nbˇb\ncc\ncc\ncc\n", Mode::Normal);
-
-        // works with counts
-        cx.simulate_keystrokes(["4", "/", "c"]);
-        cx.simulate_keystrokes(["enter"]);
-        cx.assert_state("aa\nbb\ncc\ncˇc\ncc\n", Mode::Normal);
-
-        // check that searching resumes from cursor, not previous match
-        cx.set_state("ˇaa\nbb\ndd\ncc\nbb\n", Mode::Normal);
-        cx.simulate_keystrokes(["/", "d"]);
-        cx.simulate_keystrokes(["enter"]);
-        cx.assert_state("aa\nbb\nˇdd\ncc\nbb\n", Mode::Normal);
-        cx.update_editor(|editor, cx| editor.move_to_beginning(&Default::default(), cx));
-        cx.assert_state("ˇaa\nbb\ndd\ncc\nbb\n", Mode::Normal);
-        cx.simulate_keystrokes(["/", "b"]);
-        cx.simulate_keystrokes(["enter"]);
-        cx.assert_state("aa\nˇbb\ndd\ncc\nbb\n", Mode::Normal);
-    }
-
-    #[gpui::test]
-    async fn test_non_vim_search(cx: &mut gpui::TestAppContext) {
-        let mut cx = VimTestContext::new(cx, false).await;
-        cx.set_state("ˇone one one one", Mode::Normal);
-        cx.simulate_keystrokes(["cmd-f"]);
-        cx.run_until_parked();
-
-        cx.assert_editor_state("«oneˇ» one one one");
-        cx.simulate_keystrokes(["enter"]);
-        cx.assert_editor_state("one «oneˇ» one one");
-        cx.simulate_keystrokes(["shift-enter"]);
-        cx.assert_editor_state("«oneˇ» one one one");
-    }
-}

crates/vim2/src/normal/substitute.rs 🔗

@@ -1,276 +0,0 @@
-use editor::movement;
-use gpui::{actions, ViewContext, WindowContext};
-use language::Point;
-use workspace::Workspace;
-
-use crate::{motion::Motion, utils::copy_selections_content, Mode, Vim};
-
-actions!(vim, [Substitute, SubstituteLine]);
-
-pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
-    workspace.register_action(|_: &mut Workspace, _: &Substitute, cx| {
-        Vim::update(cx, |vim, cx| {
-            vim.start_recording(cx);
-            let count = vim.take_count(cx);
-            substitute(vim, count, vim.state().mode == Mode::VisualLine, cx);
-        })
-    });
-
-    workspace.register_action(|_: &mut Workspace, _: &SubstituteLine, cx| {
-        Vim::update(cx, |vim, cx| {
-            vim.start_recording(cx);
-            if matches!(vim.state().mode, Mode::VisualBlock | Mode::Visual) {
-                vim.switch_mode(Mode::VisualLine, false, cx)
-            }
-            let count = vim.take_count(cx);
-            substitute(vim, count, true, cx)
-        })
-    });
-}
-
-pub fn substitute(vim: &mut Vim, count: Option<usize>, line_mode: bool, cx: &mut WindowContext) {
-    vim.update_active_editor(cx, |editor, cx| {
-        editor.set_clip_at_line_ends(false, cx);
-        editor.transact(cx, |editor, cx| {
-            let text_layout_details = editor.text_layout_details(cx);
-            editor.change_selections(None, cx, |s| {
-                s.move_with(|map, selection| {
-                    if selection.start == selection.end {
-                        Motion::Right.expand_selection(
-                            map,
-                            selection,
-                            count,
-                            true,
-                            &text_layout_details,
-                        );
-                    }
-                    if line_mode {
-                        // in Visual mode when the selection contains the newline at the end
-                        // of the line, we should exclude it.
-                        if !selection.is_empty() && selection.end.column() == 0 {
-                            selection.end = movement::left(map, selection.end);
-                        }
-                        Motion::CurrentLine.expand_selection(
-                            map,
-                            selection,
-                            None,
-                            false,
-                            &text_layout_details,
-                        );
-                        if let Some((point, _)) = (Motion::FirstNonWhitespace {
-                            display_lines: false,
-                        })
-                        .move_point(
-                            map,
-                            selection.start,
-                            selection.goal,
-                            None,
-                            &text_layout_details,
-                        ) {
-                            selection.start = point;
-                        }
-                    }
-                })
-            });
-            copy_selections_content(editor, line_mode, cx);
-            let selections = editor.selections.all::<Point>(cx).into_iter();
-            let edits = selections.map(|selection| (selection.start..selection.end, ""));
-            editor.edit(edits, cx);
-        });
-    });
-    vim.switch_mode(Mode::Insert, true, cx);
-}
-
-#[cfg(test)]
-mod test {
-    use crate::{
-        state::Mode,
-        test::{NeovimBackedTestContext, VimTestContext},
-    };
-    use indoc::indoc;
-
-    #[gpui::test]
-    async fn test_substitute(cx: &mut gpui::TestAppContext) {
-        let mut cx = VimTestContext::new(cx, true).await;
-
-        // supports a single cursor
-        cx.set_state(indoc! {"ˇabc\n"}, Mode::Normal);
-        cx.simulate_keystrokes(["s", "x"]);
-        cx.assert_editor_state("xˇbc\n");
-
-        // supports a selection
-        cx.set_state(indoc! {"a«bcˇ»\n"}, Mode::Visual);
-        cx.assert_editor_state("a«bcˇ»\n");
-        cx.simulate_keystrokes(["s", "x"]);
-        cx.assert_editor_state("axˇ\n");
-
-        // supports counts
-        cx.set_state(indoc! {"ˇabc\n"}, Mode::Normal);
-        cx.simulate_keystrokes(["2", "s", "x"]);
-        cx.assert_editor_state("xˇc\n");
-
-        // supports multiple cursors
-        cx.set_state(indoc! {"a«bcˇ»deˇffg\n"}, Mode::Normal);
-        cx.simulate_keystrokes(["2", "s", "x"]);
-        cx.assert_editor_state("axˇdexˇg\n");
-
-        // does not read beyond end of line
-        cx.set_state(indoc! {"ˇabc\n"}, Mode::Normal);
-        cx.simulate_keystrokes(["5", "s", "x"]);
-        cx.assert_editor_state("xˇ\n");
-
-        // it handles multibyte characters
-        cx.set_state(indoc! {"ˇcàfé\n"}, Mode::Normal);
-        cx.simulate_keystrokes(["4", "s"]);
-        cx.assert_editor_state("ˇ\n");
-
-        // should transactionally undo selection changes
-        cx.simulate_keystrokes(["escape", "u"]);
-        cx.assert_editor_state("ˇcàfé\n");
-
-        // it handles visual line mode
-        cx.set_state(
-            indoc! {"
-            alpha
-              beˇta
-            gamma"},
-            Mode::Normal,
-        );
-        cx.simulate_keystrokes(["shift-v", "s"]);
-        cx.assert_editor_state(indoc! {"
-            alpha
-              ˇ
-            gamma"});
-    }
-
-    #[gpui::test]
-    async fn test_visual_change(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.set_shared_state("The quick ˇbrown").await;
-        cx.simulate_shared_keystrokes(["v", "w", "c"]).await;
-        cx.assert_shared_state("The quick ˇ").await;
-
-        cx.set_shared_state(indoc! {"
-            The ˇquick brown
-            fox jumps over
-            the lazy dog"})
-            .await;
-        cx.simulate_shared_keystrokes(["v", "w", "j", "c"]).await;
-        cx.assert_shared_state(indoc! {"
-            The ˇver
-            the lazy dog"})
-            .await;
-
-        let cases = cx.each_marked_position(indoc! {"
-            The ˇquick brown
-            fox jumps ˇover
-            the ˇlazy dog"});
-        for initial_state in cases {
-            cx.assert_neovim_compatible(&initial_state, ["v", "w", "j", "c"])
-                .await;
-            cx.assert_neovim_compatible(&initial_state, ["v", "w", "k", "c"])
-                .await;
-        }
-    }
-
-    #[gpui::test]
-    async fn test_visual_line_change(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx)
-            .await
-            .binding(["shift-v", "c"]);
-        cx.assert(indoc! {"
-            The quˇick brown
-            fox jumps over
-            the lazy dog"})
-            .await;
-        // Test pasting code copied on change
-        cx.simulate_shared_keystrokes(["escape", "j", "p"]).await;
-        cx.assert_state_matches().await;
-
-        cx.assert_all(indoc! {"
-            The quick brown
-            fox juˇmps over
-            the laˇzy dog"})
-            .await;
-        let mut cx = cx.binding(["shift-v", "j", "c"]);
-        cx.assert(indoc! {"
-            The quˇick brown
-            fox jumps over
-            the lazy dog"})
-            .await;
-        // Test pasting code copied on delete
-        cx.simulate_shared_keystrokes(["escape", "j", "p"]).await;
-        cx.assert_state_matches().await;
-
-        cx.assert_all(indoc! {"
-            The quick brown
-            fox juˇmps over
-            the laˇzy dog"})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_substitute_line(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        let initial_state = indoc! {"
-                    The quick brown
-                    fox juˇmps over
-                    the lazy dog
-                    "};
-
-        // normal mode
-        cx.set_shared_state(initial_state).await;
-        cx.simulate_shared_keystrokes(["shift-s", "o"]).await;
-        cx.assert_shared_state(indoc! {"
-            The quick brown
-            oˇ
-            the lazy dog
-            "})
-            .await;
-
-        // visual mode
-        cx.set_shared_state(initial_state).await;
-        cx.simulate_shared_keystrokes(["v", "k", "shift-s", "o"])
-            .await;
-        cx.assert_shared_state(indoc! {"
-            oˇ
-            the lazy dog
-            "})
-            .await;
-
-        // visual block mode
-        cx.set_shared_state(initial_state).await;
-        cx.simulate_shared_keystrokes(["ctrl-v", "j", "shift-s", "o"])
-            .await;
-        cx.assert_shared_state(indoc! {"
-            The quick brown
-            oˇ
-            "})
-            .await;
-
-        // visual mode including newline
-        cx.set_shared_state(initial_state).await;
-        cx.simulate_shared_keystrokes(["v", "$", "shift-s", "o"])
-            .await;
-        cx.assert_shared_state(indoc! {"
-            The quick brown
-            oˇ
-            the lazy dog
-            "})
-            .await;
-
-        // indentation
-        cx.set_neovim_option("shiftwidth=4").await;
-        cx.set_shared_state(initial_state).await;
-        cx.simulate_shared_keystrokes([">", ">", "shift-s", "o"])
-            .await;
-        cx.assert_shared_state(indoc! {"
-            The quick brown
-                oˇ
-            the lazy dog
-            "})
-            .await;
-    }
-}

crates/vim2/src/normal/yank.rs 🔗

@@ -1,50 +0,0 @@
-use crate::{motion::Motion, object::Object, utils::copy_selections_content, Vim};
-use collections::HashMap;
-use gpui::WindowContext;
-
-pub fn yank_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
-    vim.update_active_editor(cx, |editor, cx| {
-        let text_layout_details = editor.text_layout_details(cx);
-        editor.transact(cx, |editor, cx| {
-            editor.set_clip_at_line_ends(false, cx);
-            let mut original_positions: HashMap<_, _> = Default::default();
-            editor.change_selections(None, cx, |s| {
-                s.move_with(|map, selection| {
-                    let original_position = (selection.head(), selection.goal);
-                    original_positions.insert(selection.id, original_position);
-                    motion.expand_selection(map, selection, times, true, &text_layout_details);
-                });
-            });
-            copy_selections_content(editor, motion.linewise(), cx);
-            editor.change_selections(None, cx, |s| {
-                s.move_with(|_, selection| {
-                    let (head, goal) = original_positions.remove(&selection.id).unwrap();
-                    selection.collapse_to(head, goal);
-                });
-            });
-        });
-    });
-}
-
-pub fn yank_object(vim: &mut Vim, object: Object, around: bool, cx: &mut WindowContext) {
-    vim.update_active_editor(cx, |editor, cx| {
-        editor.transact(cx, |editor, cx| {
-            editor.set_clip_at_line_ends(false, cx);
-            let mut original_positions: HashMap<_, _> = Default::default();
-            editor.change_selections(None, cx, |s| {
-                s.move_with(|map, selection| {
-                    let original_position = (selection.head(), selection.goal);
-                    object.expand_selection(map, selection, around);
-                    original_positions.insert(selection.id, original_position);
-                });
-            });
-            copy_selections_content(editor, false, cx);
-            editor.change_selections(None, cx, |s| {
-                s.move_with(|_, selection| {
-                    let (head, goal) = original_positions.remove(&selection.id).unwrap();
-                    selection.collapse_to(head, goal);
-                });
-            });
-        });
-    });
-}

crates/vim2/src/object.rs 🔗

@@ -1,1025 +0,0 @@
-use std::ops::Range;
-
-use editor::{
-    char_kind,
-    display_map::{DisplaySnapshot, ToDisplayPoint},
-    movement::{self, FindRange},
-    Bias, CharKind, DisplayPoint,
-};
-use gpui::{actions, impl_actions, ViewContext, WindowContext};
-use language::Selection;
-use serde::Deserialize;
-use workspace::Workspace;
-
-use crate::{motion::right, normal::normal_object, state::Mode, visual::visual_object, Vim};
-
-#[derive(Copy, Clone, Debug, PartialEq)]
-pub enum Object {
-    Word { ignore_punctuation: bool },
-    Sentence,
-    Quotes,
-    BackQuotes,
-    DoubleQuotes,
-    VerticalBars,
-    Parentheses,
-    SquareBrackets,
-    CurlyBrackets,
-    AngleBrackets,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-struct Word {
-    #[serde(default)]
-    ignore_punctuation: bool,
-}
-
-impl_actions!(vim, [Word]);
-
-actions!(
-    vim,
-    [
-        Sentence,
-        Quotes,
-        BackQuotes,
-        DoubleQuotes,
-        VerticalBars,
-        Parentheses,
-        SquareBrackets,
-        CurlyBrackets,
-        AngleBrackets
-    ]
-);
-
-pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
-    workspace.register_action(
-        |_: &mut Workspace, &Word { ignore_punctuation }: &Word, cx: _| {
-            object(Object::Word { ignore_punctuation }, cx)
-        },
-    );
-    workspace
-        .register_action(|_: &mut Workspace, _: &Sentence, cx: _| object(Object::Sentence, cx));
-    workspace.register_action(|_: &mut Workspace, _: &Quotes, cx: _| object(Object::Quotes, cx));
-    workspace
-        .register_action(|_: &mut Workspace, _: &BackQuotes, cx: _| object(Object::BackQuotes, cx));
-    workspace.register_action(|_: &mut Workspace, _: &DoubleQuotes, cx: _| {
-        object(Object::DoubleQuotes, cx)
-    });
-    workspace.register_action(|_: &mut Workspace, _: &Parentheses, cx: _| {
-        object(Object::Parentheses, cx)
-    });
-    workspace.register_action(|_: &mut Workspace, _: &SquareBrackets, cx: _| {
-        object(Object::SquareBrackets, cx)
-    });
-    workspace.register_action(|_: &mut Workspace, _: &CurlyBrackets, cx: _| {
-        object(Object::CurlyBrackets, cx)
-    });
-    workspace.register_action(|_: &mut Workspace, _: &AngleBrackets, cx: _| {
-        object(Object::AngleBrackets, cx)
-    });
-    workspace.register_action(|_: &mut Workspace, _: &VerticalBars, cx: _| {
-        object(Object::VerticalBars, cx)
-    });
-}
-
-fn object(object: Object, cx: &mut WindowContext) {
-    match Vim::read(cx).state().mode {
-        Mode::Normal => normal_object(object, cx),
-        Mode::Visual | Mode::VisualLine | Mode::VisualBlock => visual_object(object, cx),
-        Mode::Insert => {
-            // Shouldn't execute a text object in insert mode. Ignoring
-        }
-    }
-}
-
-impl Object {
-    pub fn is_multiline(self) -> bool {
-        match self {
-            Object::Word { .. }
-            | Object::Quotes
-            | Object::BackQuotes
-            | Object::VerticalBars
-            | Object::DoubleQuotes => false,
-            Object::Sentence
-            | Object::Parentheses
-            | Object::AngleBrackets
-            | Object::CurlyBrackets
-            | Object::SquareBrackets => true,
-        }
-    }
-
-    pub fn always_expands_both_ways(self) -> bool {
-        match self {
-            Object::Word { .. } | Object::Sentence => false,
-            Object::Quotes
-            | Object::BackQuotes
-            | Object::DoubleQuotes
-            | Object::VerticalBars
-            | Object::Parentheses
-            | Object::SquareBrackets
-            | Object::CurlyBrackets
-            | Object::AngleBrackets => true,
-        }
-    }
-
-    pub fn target_visual_mode(self, current_mode: Mode) -> Mode {
-        match self {
-            Object::Word { .. } if current_mode == Mode::VisualLine => Mode::Visual,
-            Object::Word { .. } => current_mode,
-            Object::Sentence
-            | Object::Quotes
-            | Object::BackQuotes
-            | Object::DoubleQuotes
-            | Object::VerticalBars
-            | Object::Parentheses
-            | Object::SquareBrackets
-            | Object::CurlyBrackets
-            | Object::AngleBrackets => Mode::Visual,
-        }
-    }
-
-    pub fn range(
-        self,
-        map: &DisplaySnapshot,
-        relative_to: DisplayPoint,
-        around: bool,
-    ) -> Option<Range<DisplayPoint>> {
-        match self {
-            Object::Word { ignore_punctuation } => {
-                if around {
-                    around_word(map, relative_to, ignore_punctuation)
-                } else {
-                    in_word(map, relative_to, ignore_punctuation)
-                }
-            }
-            Object::Sentence => sentence(map, relative_to, around),
-            Object::Quotes => {
-                surrounding_markers(map, relative_to, around, self.is_multiline(), '\'', '\'')
-            }
-            Object::BackQuotes => {
-                surrounding_markers(map, relative_to, around, self.is_multiline(), '`', '`')
-            }
-            Object::DoubleQuotes => {
-                surrounding_markers(map, relative_to, around, self.is_multiline(), '"', '"')
-            }
-            Object::VerticalBars => {
-                surrounding_markers(map, relative_to, around, self.is_multiline(), '|', '|')
-            }
-            Object::Parentheses => {
-                surrounding_markers(map, relative_to, around, self.is_multiline(), '(', ')')
-            }
-            Object::SquareBrackets => {
-                surrounding_markers(map, relative_to, around, self.is_multiline(), '[', ']')
-            }
-            Object::CurlyBrackets => {
-                surrounding_markers(map, relative_to, around, self.is_multiline(), '{', '}')
-            }
-            Object::AngleBrackets => {
-                surrounding_markers(map, relative_to, around, self.is_multiline(), '<', '>')
-            }
-        }
-    }
-
-    pub fn expand_selection(
-        self,
-        map: &DisplaySnapshot,
-        selection: &mut Selection<DisplayPoint>,
-        around: bool,
-    ) -> bool {
-        if let Some(range) = self.range(map, selection.head(), around) {
-            selection.start = range.start;
-            selection.end = range.end;
-            true
-        } else {
-            false
-        }
-    }
-}
-
-/// Return a range that surrounds the word relative_to is in
-/// If relative_to is at the start of a word, return the word.
-/// If relative_to is between words, return the space between
-fn in_word(
-    map: &DisplaySnapshot,
-    relative_to: DisplayPoint,
-    ignore_punctuation: bool,
-) -> Option<Range<DisplayPoint>> {
-    // Use motion::right so that we consider the character under the cursor when looking for the start
-    let scope = map
-        .buffer_snapshot
-        .language_scope_at(relative_to.to_point(map));
-    let start = movement::find_preceding_boundary(
-        map,
-        right(map, relative_to, 1),
-        movement::FindRange::SingleLine,
-        |left, right| {
-            char_kind(&scope, left).coerce_punctuation(ignore_punctuation)
-                != char_kind(&scope, right).coerce_punctuation(ignore_punctuation)
-        },
-    );
-
-    let end = movement::find_boundary(map, relative_to, FindRange::SingleLine, |left, right| {
-        char_kind(&scope, left).coerce_punctuation(ignore_punctuation)
-            != char_kind(&scope, right).coerce_punctuation(ignore_punctuation)
-    });
-
-    Some(start..end)
-}
-
-/// Return a range that surrounds the word and following whitespace
-/// relative_to is in.
-/// If relative_to is at the start of a word, return the word and following whitespace.
-/// If relative_to is between words, return the whitespace back and the following word
-
-/// if in word
-///   delete that word
-///   if there is whitespace following the word, delete that as well
-///   otherwise, delete any preceding whitespace
-/// otherwise
-///   delete whitespace around cursor
-///   delete word following the cursor
-fn around_word(
-    map: &DisplaySnapshot,
-    relative_to: DisplayPoint,
-    ignore_punctuation: bool,
-) -> Option<Range<DisplayPoint>> {
-    let scope = map
-        .buffer_snapshot
-        .language_scope_at(relative_to.to_point(map));
-    let in_word = map
-        .chars_at(relative_to)
-        .next()
-        .map(|(c, _)| char_kind(&scope, c) != CharKind::Whitespace)
-        .unwrap_or(false);
-
-    if in_word {
-        around_containing_word(map, relative_to, ignore_punctuation)
-    } else {
-        around_next_word(map, relative_to, ignore_punctuation)
-    }
-}
-
-fn around_containing_word(
-    map: &DisplaySnapshot,
-    relative_to: DisplayPoint,
-    ignore_punctuation: bool,
-) -> Option<Range<DisplayPoint>> {
-    in_word(map, relative_to, ignore_punctuation)
-        .map(|range| expand_to_include_whitespace(map, range, true))
-}
-
-fn around_next_word(
-    map: &DisplaySnapshot,
-    relative_to: DisplayPoint,
-    ignore_punctuation: bool,
-) -> Option<Range<DisplayPoint>> {
-    let scope = map
-        .buffer_snapshot
-        .language_scope_at(relative_to.to_point(map));
-    // Get the start of the word
-    let start = movement::find_preceding_boundary(
-        map,
-        right(map, relative_to, 1),
-        FindRange::SingleLine,
-        |left, right| {
-            char_kind(&scope, left).coerce_punctuation(ignore_punctuation)
-                != char_kind(&scope, right).coerce_punctuation(ignore_punctuation)
-        },
-    );
-
-    let mut word_found = false;
-    let end = movement::find_boundary(map, relative_to, FindRange::MultiLine, |left, right| {
-        let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation);
-        let right_kind = char_kind(&scope, right).coerce_punctuation(ignore_punctuation);
-
-        let found = (word_found && left_kind != right_kind) || right == '\n' && left == '\n';
-
-        if right_kind != CharKind::Whitespace {
-            word_found = true;
-        }
-
-        found
-    });
-
-    Some(start..end)
-}
-
-fn sentence(
-    map: &DisplaySnapshot,
-    relative_to: DisplayPoint,
-    around: bool,
-) -> Option<Range<DisplayPoint>> {
-    let mut start = None;
-    let mut previous_end = relative_to;
-
-    let mut chars = map.chars_at(relative_to).peekable();
-
-    // Search backwards for the previous sentence end or current sentence start. Include the character under relative_to
-    for (char, point) in chars
-        .peek()
-        .cloned()
-        .into_iter()
-        .chain(map.reverse_chars_at(relative_to))
-    {
-        if is_sentence_end(map, point) {
-            break;
-        }
-
-        if is_possible_sentence_start(char) {
-            start = Some(point);
-        }
-
-        previous_end = point;
-    }
-
-    // Search forward for the end of the current sentence or if we are between sentences, the start of the next one
-    let mut end = relative_to;
-    for (char, point) in chars {
-        if start.is_none() && is_possible_sentence_start(char) {
-            if around {
-                start = Some(point);
-                continue;
-            } else {
-                end = point;
-                break;
-            }
-        }
-
-        end = point;
-        *end.column_mut() += char.len_utf8() as u32;
-        end = map.clip_point(end, Bias::Left);
-
-        if is_sentence_end(map, end) {
-            break;
-        }
-    }
-
-    let mut range = start.unwrap_or(previous_end)..end;
-    if around {
-        range = expand_to_include_whitespace(map, range, false);
-    }
-
-    Some(range)
-}
-
-fn is_possible_sentence_start(character: char) -> bool {
-    !character.is_whitespace() && character != '.'
-}
-
-const SENTENCE_END_PUNCTUATION: &[char] = &['.', '!', '?'];
-const SENTENCE_END_FILLERS: &[char] = &[')', ']', '"', '\''];
-const SENTENCE_END_WHITESPACE: &[char] = &[' ', '\t', '\n'];
-fn is_sentence_end(map: &DisplaySnapshot, point: DisplayPoint) -> bool {
-    let mut next_chars = map.chars_at(point).peekable();
-    if let Some((char, _)) = next_chars.next() {
-        // We are at a double newline. This position is a sentence end.
-        if char == '\n' && next_chars.peek().map(|(c, _)| c == &'\n').unwrap_or(false) {
-            return true;
-        }
-
-        // The next text is not a valid whitespace. This is not a sentence end
-        if !SENTENCE_END_WHITESPACE.contains(&char) {
-            return false;
-        }
-    }
-
-    for (char, _) in map.reverse_chars_at(point) {
-        if SENTENCE_END_PUNCTUATION.contains(&char) {
-            return true;
-        }
-
-        if !SENTENCE_END_FILLERS.contains(&char) {
-            return false;
-        }
-    }
-
-    return false;
-}
-
-/// Expands the passed range to include whitespace on one side or the other in a line. Attempts to add the
-/// whitespace to the end first and falls back to the start if there was none.
-fn expand_to_include_whitespace(
-    map: &DisplaySnapshot,
-    mut range: Range<DisplayPoint>,
-    stop_at_newline: bool,
-) -> Range<DisplayPoint> {
-    let mut whitespace_included = false;
-
-    let mut chars = map.chars_at(range.end).peekable();
-    while let Some((char, point)) = chars.next() {
-        if char == '\n' && stop_at_newline {
-            break;
-        }
-
-        if char.is_whitespace() {
-            // Set end to the next display_point or the character position after the current display_point
-            range.end = chars.peek().map(|(_, point)| *point).unwrap_or_else(|| {
-                let mut end = point;
-                *end.column_mut() += char.len_utf8() as u32;
-                map.clip_point(end, Bias::Left)
-            });
-
-            if char != '\n' {
-                whitespace_included = true;
-            }
-        } else {
-            // Found non whitespace. Quit out.
-            break;
-        }
-    }
-
-    if !whitespace_included {
-        for (char, point) in map.reverse_chars_at(range.start) {
-            if char == '\n' && stop_at_newline {
-                break;
-            }
-
-            if !char.is_whitespace() {
-                break;
-            }
-
-            range.start = point;
-        }
-    }
-
-    range
-}
-
-fn surrounding_markers(
-    map: &DisplaySnapshot,
-    relative_to: DisplayPoint,
-    around: bool,
-    search_across_lines: bool,
-    open_marker: char,
-    close_marker: char,
-) -> Option<Range<DisplayPoint>> {
-    let point = relative_to.to_offset(map, Bias::Left);
-
-    let mut matched_closes = 0;
-    let mut opening = None;
-
-    if let Some((ch, range)) = movement::chars_after(map, point).next() {
-        if ch == open_marker {
-            if open_marker == close_marker {
-                let mut total = 0;
-                for (ch, _) in movement::chars_before(map, point) {
-                    if ch == '\n' {
-                        break;
-                    }
-                    if ch == open_marker {
-                        total += 1;
-                    }
-                }
-                if total % 2 == 0 {
-                    opening = Some(range)
-                }
-            } else {
-                opening = Some(range)
-            }
-        }
-    }
-
-    if opening.is_none() {
-        for (ch, range) in movement::chars_before(map, point) {
-            if ch == '\n' && !search_across_lines {
-                break;
-            }
-
-            if ch == open_marker {
-                if matched_closes == 0 {
-                    opening = Some(range);
-                    break;
-                }
-                matched_closes -= 1;
-            } else if ch == close_marker {
-                matched_closes += 1
-            }
-        }
-    }
-
-    if opening.is_none() {
-        for (ch, range) in movement::chars_after(map, point) {
-            if ch == open_marker {
-                opening = Some(range);
-                break;
-            } else if ch == close_marker {
-                break;
-            }
-        }
-    }
-
-    let Some(mut opening) = opening else {
-        return None;
-    };
-
-    let mut matched_opens = 0;
-    let mut closing = None;
-
-    for (ch, range) in movement::chars_after(map, opening.end) {
-        if ch == '\n' && !search_across_lines {
-            break;
-        }
-
-        if ch == close_marker {
-            if matched_opens == 0 {
-                closing = Some(range);
-                break;
-            }
-            matched_opens -= 1;
-        } else if ch == open_marker {
-            matched_opens += 1;
-        }
-    }
-
-    let Some(mut closing) = closing else {
-        return None;
-    };
-
-    if around && !search_across_lines {
-        let mut found = false;
-
-        for (ch, range) in movement::chars_after(map, closing.end) {
-            if ch.is_whitespace() && ch != '\n' {
-                found = true;
-                closing.end = range.end;
-            } else {
-                break;
-            }
-        }
-
-        if !found {
-            for (ch, range) in movement::chars_before(map, opening.start) {
-                if ch.is_whitespace() && ch != '\n' {
-                    opening.start = range.start
-                } else {
-                    break;
-                }
-            }
-        }
-    }
-
-    if !around && search_across_lines {
-        if let Some((ch, range)) = movement::chars_after(map, opening.end).next() {
-            if ch == '\n' {
-                opening.end = range.end
-            }
-        }
-
-        for (ch, range) in movement::chars_before(map, closing.start) {
-            if !ch.is_whitespace() {
-                break;
-            }
-            if ch != '\n' {
-                closing.start = range.start
-            }
-        }
-    }
-
-    let result = if around {
-        opening.start..closing.end
-    } else {
-        opening.end..closing.start
-    };
-
-    Some(
-        map.clip_point(result.start.to_display_point(map), Bias::Left)
-            ..map.clip_point(result.end.to_display_point(map), Bias::Right),
-    )
-}
-
-#[cfg(test)]
-mod test {
-    use indoc::indoc;
-
-    use crate::{
-        state::Mode,
-        test::{ExemptionFeatures, NeovimBackedTestContext, VimTestContext},
-    };
-
-    const WORD_LOCATIONS: &'static str = indoc! {"
-        The quick ˇbrowˇnˇ•••
-        fox ˇjuˇmpsˇ over
-        the lazy dogˇ••
-        ˇ
-        ˇ
-        ˇ
-        Thˇeˇ-ˇquˇickˇ ˇbrownˇ•
-        ˇ••
-        ˇ••
-        ˇ  fox-jumpˇs over
-        the lazy dogˇ•
-        ˇ
-        "
-    };
-
-    #[gpui::test]
-    async fn test_change_word_object(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.assert_binding_matches_all(["c", "i", "w"], WORD_LOCATIONS)
-            .await;
-        cx.assert_binding_matches_all(["c", "i", "shift-w"], WORD_LOCATIONS)
-            .await;
-        cx.assert_binding_matches_all(["c", "a", "w"], WORD_LOCATIONS)
-            .await;
-        cx.assert_binding_matches_all(["c", "a", "shift-w"], WORD_LOCATIONS)
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_delete_word_object(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.assert_binding_matches_all(["d", "i", "w"], WORD_LOCATIONS)
-            .await;
-        cx.assert_binding_matches_all(["d", "i", "shift-w"], WORD_LOCATIONS)
-            .await;
-        cx.assert_binding_matches_all(["d", "a", "w"], WORD_LOCATIONS)
-            .await;
-        cx.assert_binding_matches_all(["d", "a", "shift-w"], WORD_LOCATIONS)
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_visual_word_object(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        /*
-                cx.set_shared_state("The quick ˇbrown\nfox").await;
-                cx.simulate_shared_keystrokes(["v"]).await;
-                cx.assert_shared_state("The quick «bˇ»rown\nfox").await;
-                cx.simulate_shared_keystrokes(["i", "w"]).await;
-                cx.assert_shared_state("The quick «brownˇ»\nfox").await;
-        */
-        cx.set_shared_state("The quick brown\nˇ\nfox").await;
-        cx.simulate_shared_keystrokes(["v"]).await;
-        cx.assert_shared_state("The quick brown\n«\nˇ»fox").await;
-        cx.simulate_shared_keystrokes(["i", "w"]).await;
-        cx.assert_shared_state("The quick brown\n«\nˇ»fox").await;
-
-        cx.assert_binding_matches_all(["v", "i", "w"], WORD_LOCATIONS)
-            .await;
-        cx.assert_binding_matches_all_exempted(
-            ["v", "h", "i", "w"],
-            WORD_LOCATIONS,
-            ExemptionFeatures::NonEmptyVisualTextObjects,
-        )
-        .await;
-        cx.assert_binding_matches_all_exempted(
-            ["v", "l", "i", "w"],
-            WORD_LOCATIONS,
-            ExemptionFeatures::NonEmptyVisualTextObjects,
-        )
-        .await;
-        cx.assert_binding_matches_all(["v", "i", "shift-w"], WORD_LOCATIONS)
-            .await;
-
-        cx.assert_binding_matches_all_exempted(
-            ["v", "i", "h", "shift-w"],
-            WORD_LOCATIONS,
-            ExemptionFeatures::NonEmptyVisualTextObjects,
-        )
-        .await;
-        cx.assert_binding_matches_all_exempted(
-            ["v", "i", "l", "shift-w"],
-            WORD_LOCATIONS,
-            ExemptionFeatures::NonEmptyVisualTextObjects,
-        )
-        .await;
-
-        cx.assert_binding_matches_all_exempted(
-            ["v", "a", "w"],
-            WORD_LOCATIONS,
-            ExemptionFeatures::AroundObjectLeavesWhitespaceAtEndOfLine,
-        )
-        .await;
-        cx.assert_binding_matches_all_exempted(
-            ["v", "a", "shift-w"],
-            WORD_LOCATIONS,
-            ExemptionFeatures::AroundObjectLeavesWhitespaceAtEndOfLine,
-        )
-        .await;
-    }
-
-    const SENTENCE_EXAMPLES: &[&'static str] = &[
-        "ˇThe quick ˇbrownˇ?ˇ ˇFox Jˇumpsˇ!ˇ Ovˇer theˇ lazyˇ.",
-        indoc! {"
-            ˇThe quick ˇbrownˇ
-            fox jumps over
-            the lazy doˇgˇ.ˇ ˇThe quick ˇ
-            brown fox jumps over
-        "},
-        indoc! {"
-            The quick brown fox jumps.
-            Over the lazy dog
-            ˇ
-            ˇ
-            ˇ  fox-jumpˇs over
-            the lazy dog.ˇ
-            ˇ
-        "},
-        r#"ˇThe ˇquick brownˇ.)ˇ]ˇ'ˇ" Brown ˇfox jumpsˇ.ˇ "#,
-    ];
-
-    #[gpui::test]
-    async fn test_change_sentence_object(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx)
-            .await
-            .binding(["c", "i", "s"]);
-        cx.add_initial_state_exemptions(
-            "The quick brown fox jumps.\nOver the lazy dog\nˇ\nˇ\n  fox-jumps over\nthe lazy dog.\n\n",
-            ExemptionFeatures::SentenceOnEmptyLines);
-        cx.add_initial_state_exemptions(
-            "The quick brown fox jumps.\nOver the lazy dog\n\n\nˇ  foxˇ-ˇjumpˇs over\nthe lazy dog.\n\n",
-            ExemptionFeatures::SentenceAtStartOfLineWithWhitespace);
-        cx.add_initial_state_exemptions(
-            "The quick brown fox jumps.\nOver the lazy dog\n\n\n  fox-jumps over\nthe lazy dog.ˇ\nˇ\n",
-            ExemptionFeatures::SentenceAfterPunctuationAtEndOfFile);
-        for sentence_example in SENTENCE_EXAMPLES {
-            cx.assert_all(sentence_example).await;
-        }
-
-        let mut cx = cx.binding(["c", "a", "s"]);
-        cx.add_initial_state_exemptions(
-            "The quick brown?ˇ Fox Jumps! Over the lazy.",
-            ExemptionFeatures::IncorrectLandingPosition,
-        );
-        cx.add_initial_state_exemptions(
-            "The quick brown.)]\'\" Brown fox jumps.ˇ ",
-            ExemptionFeatures::AroundObjectLeavesWhitespaceAtEndOfLine,
-        );
-
-        for sentence_example in SENTENCE_EXAMPLES {
-            cx.assert_all(sentence_example).await;
-        }
-    }
-
-    #[gpui::test]
-    async fn test_delete_sentence_object(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx)
-            .await
-            .binding(["d", "i", "s"]);
-        cx.add_initial_state_exemptions(
-            "The quick brown fox jumps.\nOver the lazy dog\nˇ\nˇ\n  fox-jumps over\nthe lazy dog.\n\n",
-            ExemptionFeatures::SentenceOnEmptyLines);
-        cx.add_initial_state_exemptions(
-            "The quick brown fox jumps.\nOver the lazy dog\n\n\nˇ  foxˇ-ˇjumpˇs over\nthe lazy dog.\n\n",
-            ExemptionFeatures::SentenceAtStartOfLineWithWhitespace);
-        cx.add_initial_state_exemptions(
-            "The quick brown fox jumps.\nOver the lazy dog\n\n\n  fox-jumps over\nthe lazy dog.ˇ\nˇ\n",
-            ExemptionFeatures::SentenceAfterPunctuationAtEndOfFile);
-
-        for sentence_example in SENTENCE_EXAMPLES {
-            cx.assert_all(sentence_example).await;
-        }
-
-        let mut cx = cx.binding(["d", "a", "s"]);
-        cx.add_initial_state_exemptions(
-            "The quick brown?ˇ Fox Jumps! Over the lazy.",
-            ExemptionFeatures::IncorrectLandingPosition,
-        );
-        cx.add_initial_state_exemptions(
-            "The quick brown.)]\'\" Brown fox jumps.ˇ ",
-            ExemptionFeatures::AroundObjectLeavesWhitespaceAtEndOfLine,
-        );
-
-        for sentence_example in SENTENCE_EXAMPLES {
-            cx.assert_all(sentence_example).await;
-        }
-    }
-
-    #[gpui::test]
-    async fn test_visual_sentence_object(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx)
-            .await
-            .binding(["v", "i", "s"]);
-        for sentence_example in SENTENCE_EXAMPLES {
-            cx.assert_all_exempted(sentence_example, ExemptionFeatures::SentenceOnEmptyLines)
-                .await;
-        }
-
-        let mut cx = cx.binding(["v", "a", "s"]);
-        for sentence_example in SENTENCE_EXAMPLES {
-            cx.assert_all_exempted(
-                sentence_example,
-                ExemptionFeatures::AroundSentenceStartingBetweenIncludesWrongWhitespace,
-            )
-            .await;
-        }
-    }
-
-    // Test string with "`" for opening surrounders and "'" for closing surrounders
-    const SURROUNDING_MARKER_STRING: &str = indoc! {"
-        ˇTh'ˇe ˇ`ˇ'ˇquˇi`ˇck broˇ'wn`
-        'ˇfox juˇmps ovˇ`ˇer
-        the ˇlazy dˇ'ˇoˇ`ˇg"};
-
-    const SURROUNDING_OBJECTS: &[(char, char)] = &[
-        ('\'', '\''), // Quote
-        ('`', '`'),   // Back Quote
-        ('"', '"'),   // Double Quote
-        ('(', ')'),   // Parentheses
-        ('[', ']'),   // SquareBrackets
-        ('{', '}'),   // CurlyBrackets
-        ('<', '>'),   // AngleBrackets
-    ];
-
-    #[gpui::test]
-    async fn test_change_surrounding_character_objects(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        for (start, end) in SURROUNDING_OBJECTS {
-            let marked_string = SURROUNDING_MARKER_STRING
-                .replace('`', &start.to_string())
-                .replace('\'', &end.to_string());
-
-            cx.assert_binding_matches_all(["c", "i", &start.to_string()], &marked_string)
-                .await;
-            cx.assert_binding_matches_all(["c", "i", &end.to_string()], &marked_string)
-                .await;
-            cx.assert_binding_matches_all(["c", "a", &start.to_string()], &marked_string)
-                .await;
-            cx.assert_binding_matches_all(["c", "a", &end.to_string()], &marked_string)
-                .await;
-        }
-    }
-    #[gpui::test]
-    async fn test_singleline_surrounding_character_objects(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-        cx.set_shared_wrap(12).await;
-
-        cx.set_shared_state(indoc! {
-            "helˇlo \"world\"!"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["v", "i", "\""]).await;
-        cx.assert_shared_state(indoc! {
-            "hello \"«worldˇ»\"!"
-        })
-        .await;
-
-        cx.set_shared_state(indoc! {
-            "hello \"wˇorld\"!"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["v", "i", "\""]).await;
-        cx.assert_shared_state(indoc! {
-            "hello \"«worldˇ»\"!"
-        })
-        .await;
-
-        cx.set_shared_state(indoc! {
-            "hello \"wˇorld\"!"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["v", "a", "\""]).await;
-        cx.assert_shared_state(indoc! {
-            "hello« \"world\"ˇ»!"
-        })
-        .await;
-
-        cx.set_shared_state(indoc! {
-            "hello \"wˇorld\" !"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["v", "a", "\""]).await;
-        cx.assert_shared_state(indoc! {
-            "hello «\"world\" ˇ»!"
-        })
-        .await;
-
-        cx.set_shared_state(indoc! {
-            "hello \"wˇorld\"•
-            goodbye"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["v", "a", "\""]).await;
-        cx.assert_shared_state(indoc! {
-            "hello «\"world\" ˇ»
-            goodbye"
-        })
-        .await;
-    }
-
-    #[gpui::test]
-    async fn test_multiline_surrounding_character_objects(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.set_shared_state(indoc! {
-            "func empty(a string) bool {
-               if a == \"\" {
-                  return true
-               }
-               ˇreturn false
-            }"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["v", "i", "{"]).await;
-        cx.assert_shared_state(indoc! {"
-            func empty(a string) bool {
-            «   if a == \"\" {
-                  return true
-               }
-               return false
-            ˇ»}"})
-            .await;
-        cx.set_shared_state(indoc! {
-            "func empty(a string) bool {
-                 if a == \"\" {
-                     ˇreturn true
-                 }
-                 return false
-            }"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["v", "i", "{"]).await;
-        cx.assert_shared_state(indoc! {"
-            func empty(a string) bool {
-                 if a == \"\" {
-            «         return true
-            ˇ»     }
-                 return false
-            }"})
-            .await;
-
-        cx.set_shared_state(indoc! {
-            "func empty(a string) bool {
-                 if a == \"\" ˇ{
-                     return true
-                 }
-                 return false
-            }"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["v", "i", "{"]).await;
-        cx.assert_shared_state(indoc! {"
-            func empty(a string) bool {
-                 if a == \"\" {
-            «         return true
-            ˇ»     }
-                 return false
-            }"})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_vertical_bars(cx: &mut gpui::TestAppContext) {
-        let mut cx = VimTestContext::new(cx, true).await;
-        cx.set_state(
-            indoc! {"
-            fn boop() {
-                baz(ˇ|a, b| { bar(|j, k| { })})
-            }"
-            },
-            Mode::Normal,
-        );
-        cx.simulate_keystrokes(["c", "i", "|"]);
-        cx.assert_state(
-            indoc! {"
-            fn boop() {
-                baz(|ˇ| { bar(|j, k| { })})
-            }"
-            },
-            Mode::Insert,
-        );
-        cx.simulate_keystrokes(["escape", "1", "8", "|"]);
-        cx.assert_state(
-            indoc! {"
-            fn boop() {
-                baz(|| { bar(ˇ|j, k| { })})
-            }"
-            },
-            Mode::Normal,
-        );
-
-        cx.simulate_keystrokes(["v", "a", "|"]);
-        cx.assert_state(
-            indoc! {"
-            fn boop() {
-                baz(|| { bar(«|j, k| ˇ»{ })})
-            }"
-            },
-            Mode::Visual,
-        );
-    }
-
-    #[gpui::test]
-    async fn test_delete_surrounding_character_objects(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        for (start, end) in SURROUNDING_OBJECTS {
-            let marked_string = SURROUNDING_MARKER_STRING
-                .replace('`', &start.to_string())
-                .replace('\'', &end.to_string());
-
-            cx.assert_binding_matches_all(["d", "i", &start.to_string()], &marked_string)
-                .await;
-            cx.assert_binding_matches_all(["d", "i", &end.to_string()], &marked_string)
-                .await;
-            cx.assert_binding_matches_all(["d", "a", &start.to_string()], &marked_string)
-                .await;
-            cx.assert_binding_matches_all(["d", "a", &end.to_string()], &marked_string)
-                .await;
-        }
-    }
-}

crates/vim2/src/state.rs 🔗

@@ -1,234 +0,0 @@
-use std::{ops::Range, sync::Arc};
-
-use gpui::{Action, KeyContext};
-use language::CursorShape;
-use serde::{Deserialize, Serialize};
-use workspace::searchable::Direction;
-
-use crate::motion::Motion;
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
-pub enum Mode {
-    Normal,
-    Insert,
-    Visual,
-    VisualLine,
-    VisualBlock,
-}
-
-impl Mode {
-    pub fn is_visual(&self) -> bool {
-        match self {
-            Mode::Normal | Mode::Insert => false,
-            Mode::Visual | Mode::VisualLine | Mode::VisualBlock => true,
-        }
-    }
-}
-
-impl Default for Mode {
-    fn default() -> Self {
-        Self::Normal
-    }
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
-pub enum Operator {
-    Change,
-    Delete,
-    Yank,
-    Replace,
-    Object { around: bool },
-    FindForward { before: bool },
-    FindBackward { after: bool },
-}
-
-#[derive(Default, Clone)]
-pub struct EditorState {
-    pub mode: Mode,
-    pub last_mode: Mode,
-
-    /// pre_count is the number before an operator is specified (3 in 3d2d)
-    pub pre_count: Option<usize>,
-    /// post_count is the number after an operator is specified (2 in 3d2d)
-    pub post_count: Option<usize>,
-
-    pub operator_stack: Vec<Operator>,
-}
-
-#[derive(Default, Clone, Debug)]
-pub enum RecordedSelection {
-    #[default]
-    None,
-    Visual {
-        rows: u32,
-        cols: u32,
-    },
-    SingleLine {
-        cols: u32,
-    },
-    VisualBlock {
-        rows: u32,
-        cols: u32,
-    },
-    VisualLine {
-        rows: u32,
-    },
-}
-
-#[derive(Default, Clone)]
-pub struct WorkspaceState {
-    pub search: SearchState,
-    pub last_find: Option<Motion>,
-
-    pub recording: bool,
-    pub stop_recording_after_next_action: bool,
-    pub replaying: bool,
-    pub recorded_count: Option<usize>,
-    pub recorded_actions: Vec<ReplayableAction>,
-    pub recorded_selection: RecordedSelection,
-}
-
-#[derive(Debug)]
-pub enum ReplayableAction {
-    Action(Box<dyn Action>),
-    Insertion {
-        text: Arc<str>,
-        utf16_range_to_replace: Option<Range<isize>>,
-    },
-}
-
-impl Clone for ReplayableAction {
-    fn clone(&self) -> Self {
-        match self {
-            Self::Action(action) => Self::Action(action.boxed_clone()),
-            Self::Insertion {
-                text,
-                utf16_range_to_replace,
-            } => Self::Insertion {
-                text: text.clone(),
-                utf16_range_to_replace: utf16_range_to_replace.clone(),
-            },
-        }
-    }
-}
-
-#[derive(Clone)]
-pub struct SearchState {
-    pub direction: Direction,
-    pub count: usize,
-    pub initial_query: String,
-}
-
-impl Default for SearchState {
-    fn default() -> Self {
-        Self {
-            direction: Direction::Next,
-            count: 1,
-            initial_query: "".to_string(),
-        }
-    }
-}
-
-impl EditorState {
-    pub fn cursor_shape(&self) -> CursorShape {
-        match self.mode {
-            Mode::Normal => {
-                if self.operator_stack.is_empty() {
-                    CursorShape::Block
-                } else {
-                    CursorShape::Underscore
-                }
-            }
-            Mode::Visual | Mode::VisualLine | Mode::VisualBlock => CursorShape::Block,
-            Mode::Insert => CursorShape::Bar,
-        }
-    }
-
-    pub fn vim_controlled(&self) -> bool {
-        !matches!(self.mode, Mode::Insert)
-            || matches!(
-                self.operator_stack.last(),
-                Some(Operator::FindForward { .. }) | Some(Operator::FindBackward { .. })
-            )
-    }
-
-    pub fn should_autoindent(&self) -> bool {
-        !(self.mode == Mode::Insert && self.last_mode == Mode::VisualBlock)
-    }
-
-    pub fn clip_at_line_ends(&self) -> bool {
-        match self.mode {
-            Mode::Insert | Mode::Visual | Mode::VisualLine | Mode::VisualBlock => false,
-            Mode::Normal => true,
-        }
-    }
-
-    pub fn active_operator(&self) -> Option<Operator> {
-        self.operator_stack.last().copied()
-    }
-
-    pub fn keymap_context_layer(&self) -> KeyContext {
-        let mut context = KeyContext::default();
-        context.add("VimEnabled");
-        context.set(
-            "vim_mode",
-            match self.mode {
-                Mode::Normal => "normal",
-                Mode::Visual | Mode::VisualLine | Mode::VisualBlock => "visual",
-                Mode::Insert => "insert",
-            },
-        );
-
-        if self.vim_controlled() {
-            context.add("VimControl");
-        }
-
-        if self.active_operator().is_none() && self.pre_count.is_some()
-            || self.active_operator().is_some() && self.post_count.is_some()
-        {
-            context.add("VimCount");
-        }
-
-        let active_operator = self.active_operator();
-
-        if let Some(active_operator) = active_operator {
-            for context_flag in active_operator.context_flags().into_iter() {
-                context.add(*context_flag);
-            }
-        }
-
-        context.set(
-            "vim_operator",
-            active_operator.map(|op| op.id()).unwrap_or_else(|| "none"),
-        );
-
-        context
-    }
-}
-
-impl Operator {
-    pub fn id(&self) -> &'static str {
-        match self {
-            Operator::Object { around: false } => "i",
-            Operator::Object { around: true } => "a",
-            Operator::Change => "c",
-            Operator::Delete => "d",
-            Operator::Yank => "y",
-            Operator::Replace => "r",
-            Operator::FindForward { before: false } => "f",
-            Operator::FindForward { before: true } => "t",
-            Operator::FindBackward { after: false } => "F",
-            Operator::FindBackward { after: true } => "T",
-        }
-    }
-
-    pub fn context_flags(&self) -> &'static [&'static str] {
-        match self {
-            Operator::Object { .. } => &["VimObject"],
-            Operator::FindForward { .. } | Operator::FindBackward { .. } | Operator::Replace => {
-                &["VimWaiting"]
-            }
-            _ => &[],
-        }
-    }
-}

crates/vim2/src/test.rs 🔗

@@ -1,752 +0,0 @@
-mod neovim_backed_binding_test_context;
-mod neovim_backed_test_context;
-mod neovim_connection;
-mod vim_test_context;
-
-use command_palette::CommandPalette;
-use editor::DisplayPoint;
-pub use neovim_backed_binding_test_context::*;
-pub use neovim_backed_test_context::*;
-pub use vim_test_context::*;
-
-use indoc::indoc;
-use search::BufferSearchBar;
-
-use crate::{state::Mode, ModeIndicator};
-
-#[gpui::test]
-async fn test_initially_disabled(cx: &mut gpui::TestAppContext) {
-    let mut cx = VimTestContext::new(cx, false).await;
-    cx.simulate_keystrokes(["h", "j", "k", "l"]);
-    cx.assert_editor_state("hjklˇ");
-}
-
-#[gpui::test]
-async fn test_neovim(cx: &mut gpui::TestAppContext) {
-    let mut cx = NeovimBackedTestContext::new(cx).await;
-
-    cx.simulate_shared_keystroke("i").await;
-    cx.assert_state_matches().await;
-    cx.simulate_shared_keystrokes([
-        "shift-T", "e", "s", "t", " ", "t", "e", "s", "t", "escape", "0", "d", "w",
-    ])
-    .await;
-    cx.assert_state_matches().await;
-    cx.assert_editor_state("ˇtest");
-}
-
-#[gpui::test]
-async fn test_toggle_through_settings(cx: &mut gpui::TestAppContext) {
-    let mut cx = VimTestContext::new(cx, true).await;
-
-    cx.simulate_keystroke("i");
-    assert_eq!(cx.mode(), Mode::Insert);
-
-    // Editor acts as though vim is disabled
-    cx.disable_vim();
-    cx.simulate_keystrokes(["h", "j", "k", "l"]);
-    cx.assert_editor_state("hjklˇ");
-
-    // Selections aren't changed if editor is blurred but vim-mode is still disabled.
-    cx.set_state("«hjklˇ»", Mode::Normal);
-    cx.assert_editor_state("«hjklˇ»");
-    cx.update_editor(|_, cx| cx.blur());
-    cx.assert_editor_state("«hjklˇ»");
-    cx.update_editor(|_, cx| cx.focus_self());
-    cx.assert_editor_state("«hjklˇ»");
-
-    // Enabling dynamically sets vim mode again and restores normal mode
-    cx.enable_vim();
-    assert_eq!(cx.mode(), Mode::Normal);
-    cx.simulate_keystrokes(["h", "h", "h", "l"]);
-    assert_eq!(cx.buffer_text(), "hjkl".to_owned());
-    cx.assert_editor_state("hˇjkl");
-    cx.simulate_keystrokes(["i", "T", "e", "s", "t"]);
-    cx.assert_editor_state("hTestˇjkl");
-
-    // Disabling and enabling resets to normal mode
-    assert_eq!(cx.mode(), Mode::Insert);
-    cx.disable_vim();
-    cx.enable_vim();
-    assert_eq!(cx.mode(), Mode::Normal);
-}
-
-#[gpui::test]
-async fn test_buffer_search(cx: &mut gpui::TestAppContext) {
-    let mut cx = VimTestContext::new(cx, true).await;
-
-    cx.set_state(
-        indoc! {"
-            The quick brown
-            fox juˇmps over
-            the lazy dog"},
-        Mode::Normal,
-    );
-    cx.simulate_keystroke("/");
-
-    let search_bar = cx.workspace(|workspace, cx| {
-        workspace
-            .active_pane()
-            .read(cx)
-            .toolbar()
-            .read(cx)
-            .item_of_type::<BufferSearchBar>()
-            .expect("Buffer search bar should be deployed")
-    });
-
-    cx.update_view(search_bar, |bar, cx| {
-        assert_eq!(bar.query(cx), "");
-    })
-}
-
-#[gpui::test]
-async fn test_count_down(cx: &mut gpui::TestAppContext) {
-    let mut cx = VimTestContext::new(cx, true).await;
-
-    cx.set_state(indoc! {"aˇa\nbb\ncc\ndd\nee"}, Mode::Normal);
-    cx.simulate_keystrokes(["2", "down"]);
-    cx.assert_editor_state("aa\nbb\ncˇc\ndd\nee");
-    cx.simulate_keystrokes(["9", "down"]);
-    cx.assert_editor_state("aa\nbb\ncc\ndd\neˇe");
-}
-
-#[gpui::test]
-async fn test_end_of_document_710(cx: &mut gpui::TestAppContext) {
-    let mut cx = VimTestContext::new(cx, true).await;
-
-    // goes to end by default
-    cx.set_state(indoc! {"aˇa\nbb\ncc"}, Mode::Normal);
-    cx.simulate_keystrokes(["shift-g"]);
-    cx.assert_editor_state("aa\nbb\ncˇc");
-
-    // can go to line 1 (https://github.com/zed-industries/community/issues/710)
-    cx.simulate_keystrokes(["1", "shift-g"]);
-    cx.assert_editor_state("aˇa\nbb\ncc");
-}
-
-#[gpui::test]
-async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
-    let mut cx = VimTestContext::new(cx, true).await;
-
-    // works in normal mode
-    cx.set_state(indoc! {"aa\nbˇb\ncc"}, Mode::Normal);
-    cx.simulate_keystrokes([">", ">"]);
-    cx.assert_editor_state("aa\n    bˇb\ncc");
-    cx.simulate_keystrokes(["<", "<"]);
-    cx.assert_editor_state("aa\nbˇb\ncc");
-
-    // works in visuial mode
-    cx.simulate_keystrokes(["shift-v", "down", ">"]);
-    cx.assert_editor_state("aa\n    b«b\n    ccˇ»");
-}
-
-#[gpui::test]
-async fn test_escape_command_palette(cx: &mut gpui::TestAppContext) {
-    let mut cx = VimTestContext::new(cx, true).await;
-
-    cx.set_state("aˇbc\n", Mode::Normal);
-    cx.simulate_keystrokes(["i", "cmd-shift-p"]);
-
-    assert!(cx.workspace(|workspace, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
-    cx.simulate_keystroke("escape");
-    cx.run_until_parked();
-    assert!(!cx.workspace(|workspace, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
-    cx.assert_state("aˇbc\n", Mode::Insert);
-}
-
-#[gpui::test]
-async fn test_escape_cancels(cx: &mut gpui::TestAppContext) {
-    let mut cx = VimTestContext::new(cx, true).await;
-
-    cx.set_state("aˇbˇc", Mode::Normal);
-    cx.simulate_keystrokes(["escape"]);
-
-    cx.assert_state("aˇbc", Mode::Normal);
-}
-
-#[gpui::test]
-async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
-    let mut cx = VimTestContext::new(cx, true).await;
-
-    cx.set_state(indoc! {"aa\nbˇb\ncc\ncc\ncc\n"}, Mode::Normal);
-    cx.simulate_keystrokes(["/", "c", "c"]);
-
-    let search_bar = cx.workspace(|workspace, cx| {
-        workspace
-            .active_pane()
-            .read(cx)
-            .toolbar()
-            .read(cx)
-            .item_of_type::<BufferSearchBar>()
-            .expect("Buffer search bar should be deployed")
-    });
-
-    cx.update_view(search_bar, |bar, cx| {
-        assert_eq!(bar.query(cx), "cc");
-    });
-
-    cx.update_editor(|editor, cx| {
-        let highlights = editor.all_text_background_highlights(cx);
-        assert_eq!(3, highlights.len());
-        assert_eq!(
-            DisplayPoint::new(2, 0)..DisplayPoint::new(2, 2),
-            highlights[0].0
-        )
-    });
-    cx.simulate_keystrokes(["enter"]);
-
-    cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
-    cx.simulate_keystrokes(["n"]);
-    cx.assert_state(indoc! {"aa\nbb\ncc\nˇcc\ncc\n"}, Mode::Normal);
-    cx.simulate_keystrokes(["shift-n"]);
-    cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
-}
-
-#[gpui::test]
-async fn test_status_indicator(cx: &mut gpui::TestAppContext) {
-    let mut cx = VimTestContext::new(cx, true).await;
-
-    let mode_indicator = cx.workspace(|workspace, cx| {
-        let status_bar = workspace.status_bar().read(cx);
-        let mode_indicator = status_bar.item_of_type::<ModeIndicator>();
-        assert!(mode_indicator.is_some());
-        mode_indicator.unwrap()
-    });
-
-    assert_eq!(
-        cx.workspace(|_, cx| mode_indicator.read(cx).mode),
-        Some(Mode::Normal)
-    );
-
-    // shows the correct mode
-    cx.simulate_keystrokes(["i"]);
-    assert_eq!(
-        cx.workspace(|_, cx| mode_indicator.read(cx).mode),
-        Some(Mode::Insert)
-    );
-
-    // shows even in search
-    cx.simulate_keystrokes(["escape", "v", "/"]);
-    assert_eq!(
-        cx.workspace(|_, cx| mode_indicator.read(cx).mode),
-        Some(Mode::Visual)
-    );
-
-    // hides if vim mode is disabled
-    cx.disable_vim();
-    cx.run_until_parked();
-    cx.workspace(|workspace, cx| {
-        let status_bar = workspace.status_bar().read(cx);
-        let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
-        assert!(mode_indicator.read(cx).mode.is_none());
-    });
-
-    cx.enable_vim();
-    cx.run_until_parked();
-    cx.workspace(|workspace, cx| {
-        let status_bar = workspace.status_bar().read(cx);
-        let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
-        assert!(mode_indicator.read(cx).mode.is_some());
-    });
-}
-
-#[gpui::test]
-async fn test_word_characters(cx: &mut gpui::TestAppContext) {
-    let mut cx = VimTestContext::new_typescript(cx).await;
-    cx.set_state(
-        indoc! { "
-        class A {
-            #ˇgoop = 99;
-            $ˇgoop () { return this.#gˇoop };
-        };
-        console.log(new A().$gooˇp())
-    "},
-        Mode::Normal,
-    );
-    cx.simulate_keystrokes(["v", "i", "w"]);
-    cx.assert_state(
-        indoc! {"
-        class A {
-            «#goopˇ» = 99;
-            «$goopˇ» () { return this.«#goopˇ» };
-        };
-        console.log(new A().«$goopˇ»())
-    "},
-        Mode::Visual,
-    )
-}
-
-#[gpui::test]
-async fn test_join_lines(cx: &mut gpui::TestAppContext) {
-    let mut cx = NeovimBackedTestContext::new(cx).await;
-
-    cx.set_shared_state(indoc! {"
-      ˇone
-      two
-      three
-      four
-      five
-      six
-      "})
-        .await;
-    cx.simulate_shared_keystrokes(["shift-j"]).await;
-    cx.assert_shared_state(indoc! {"
-          oneˇ two
-          three
-          four
-          five
-          six
-          "})
-        .await;
-    cx.simulate_shared_keystrokes(["3", "shift-j"]).await;
-    cx.assert_shared_state(indoc! {"
-          one two threeˇ four
-          five
-          six
-          "})
-        .await;
-
-    cx.set_shared_state(indoc! {"
-      ˇone
-      two
-      three
-      four
-      five
-      six
-      "})
-        .await;
-    cx.simulate_shared_keystrokes(["j", "v", "3", "j", "shift-j"])
-        .await;
-    cx.assert_shared_state(indoc! {"
-      one
-      two three fourˇ five
-      six
-      "})
-        .await;
-}
-
-#[gpui::test]
-async fn test_wrapped_lines(cx: &mut gpui::TestAppContext) {
-    let mut cx = NeovimBackedTestContext::new(cx).await;
-
-    cx.set_shared_wrap(12).await;
-    // tests line wrap as follows:
-    //  1: twelve char
-    //     twelve char
-    //  2: twelve char
-    cx.set_shared_state(indoc! { "
-        tˇwelve char twelve char
-        twelve char
-    "})
-        .await;
-    cx.simulate_shared_keystrokes(["j"]).await;
-    cx.assert_shared_state(indoc! { "
-        twelve char twelve char
-        tˇwelve char
-    "})
-        .await;
-    cx.simulate_shared_keystrokes(["k"]).await;
-    cx.assert_shared_state(indoc! { "
-        tˇwelve char twelve char
-        twelve char
-    "})
-        .await;
-    cx.simulate_shared_keystrokes(["g", "j"]).await;
-    cx.assert_shared_state(indoc! { "
-        twelve char tˇwelve char
-        twelve char
-    "})
-        .await;
-    cx.simulate_shared_keystrokes(["g", "j"]).await;
-    cx.assert_shared_state(indoc! { "
-        twelve char twelve char
-        tˇwelve char
-    "})
-        .await;
-
-    cx.simulate_shared_keystrokes(["g", "k"]).await;
-    cx.assert_shared_state(indoc! { "
-        twelve char tˇwelve char
-        twelve char
-    "})
-        .await;
-
-    cx.simulate_shared_keystrokes(["g", "^"]).await;
-    cx.assert_shared_state(indoc! { "
-        twelve char ˇtwelve char
-        twelve char
-    "})
-        .await;
-
-    cx.simulate_shared_keystrokes(["^"]).await;
-    cx.assert_shared_state(indoc! { "
-        ˇtwelve char twelve char
-        twelve char
-    "})
-        .await;
-
-    cx.simulate_shared_keystrokes(["g", "$"]).await;
-    cx.assert_shared_state(indoc! { "
-        twelve charˇ twelve char
-        twelve char
-    "})
-        .await;
-    cx.simulate_shared_keystrokes(["$"]).await;
-    cx.assert_shared_state(indoc! { "
-        twelve char twelve chaˇr
-        twelve char
-    "})
-        .await;
-
-    cx.set_shared_state(indoc! { "
-        tˇwelve char twelve char
-        twelve char
-    "})
-        .await;
-    cx.simulate_shared_keystrokes(["enter"]).await;
-    cx.assert_shared_state(indoc! { "
-            twelve char twelve char
-            ˇtwelve char
-        "})
-        .await;
-
-    cx.set_shared_state(indoc! { "
-        twelve char
-        tˇwelve char twelve char
-        twelve char
-    "})
-        .await;
-    cx.simulate_shared_keystrokes(["o", "o", "escape"]).await;
-    cx.assert_shared_state(indoc! { "
-        twelve char
-        twelve char twelve char
-        ˇo
-        twelve char
-    "})
-        .await;
-
-    cx.set_shared_state(indoc! { "
-        twelve char
-        tˇwelve char twelve char
-        twelve char
-    "})
-        .await;
-    cx.simulate_shared_keystrokes(["shift-a", "a", "escape"])
-        .await;
-    cx.assert_shared_state(indoc! { "
-        twelve char
-        twelve char twelve charˇa
-        twelve char
-    "})
-        .await;
-    cx.simulate_shared_keystrokes(["shift-i", "i", "escape"])
-        .await;
-    cx.assert_shared_state(indoc! { "
-        twelve char
-        ˇitwelve char twelve chara
-        twelve char
-    "})
-        .await;
-    cx.simulate_shared_keystrokes(["shift-d"]).await;
-    cx.assert_shared_state(indoc! { "
-        twelve char
-        ˇ
-        twelve char
-    "})
-        .await;
-
-    cx.set_shared_state(indoc! { "
-        twelve char
-        twelve char tˇwelve char
-        twelve char
-    "})
-        .await;
-    cx.simulate_shared_keystrokes(["shift-o", "o", "escape"])
-        .await;
-    cx.assert_shared_state(indoc! { "
-        twelve char
-        ˇo
-        twelve char twelve char
-        twelve char
-    "})
-        .await;
-
-    // line wraps as:
-    // fourteen ch
-    // ar
-    // fourteen ch
-    // ar
-    cx.set_shared_state(indoc! { "
-        fourteen chaˇr
-        fourteen char
-    "})
-        .await;
-
-    cx.simulate_shared_keystrokes(["d", "i", "w"]).await;
-    cx.assert_shared_state(indoc! {"
-        fourteenˇ•
-        fourteen char
-    "})
-        .await;
-    cx.simulate_shared_keystrokes(["j", "shift-f", "e", "f", "r"])
-        .await;
-    cx.assert_shared_state(indoc! {"
-        fourteen•
-        fourteen chaˇr
-    "})
-        .await;
-}
-
-#[gpui::test]
-async fn test_folds(cx: &mut gpui::TestAppContext) {
-    let mut cx = NeovimBackedTestContext::new(cx).await;
-    cx.set_neovim_option("foldmethod=manual").await;
-
-    cx.set_shared_state(indoc! { "
-        fn boop() {
-          ˇbarp()
-          bazp()
-        }
-    "})
-        .await;
-    cx.simulate_shared_keystrokes(["shift-v", "j", "z", "f"])
-        .await;
-
-    // visual display is now:
-    // fn boop () {
-    //  [FOLDED]
-    // }
-
-    // TODO: this should not be needed but currently zf does not
-    // return to normal mode.
-    cx.simulate_shared_keystrokes(["escape"]).await;
-
-    // skip over fold downward
-    cx.simulate_shared_keystrokes(["g", "g"]).await;
-    cx.assert_shared_state(indoc! { "
-        ˇfn boop() {
-          barp()
-          bazp()
-        }
-    "})
-        .await;
-
-    cx.simulate_shared_keystrokes(["j", "j"]).await;
-    cx.assert_shared_state(indoc! { "
-        fn boop() {
-          barp()
-          bazp()
-        ˇ}
-    "})
-        .await;
-
-    // skip over fold upward
-    cx.simulate_shared_keystrokes(["2", "k"]).await;
-    cx.assert_shared_state(indoc! { "
-        ˇfn boop() {
-          barp()
-          bazp()
-        }
-    "})
-        .await;
-
-    // yank the fold
-    cx.simulate_shared_keystrokes(["down", "y", "y"]).await;
-    cx.assert_shared_clipboard("  barp()\n  bazp()\n").await;
-
-    // re-open
-    cx.simulate_shared_keystrokes(["z", "o"]).await;
-    cx.assert_shared_state(indoc! { "
-        fn boop() {
-        ˇ  barp()
-          bazp()
-        }
-    "})
-        .await;
-}
-
-#[gpui::test]
-async fn test_folds_panic(cx: &mut gpui::TestAppContext) {
-    let mut cx = NeovimBackedTestContext::new(cx).await;
-    cx.set_neovim_option("foldmethod=manual").await;
-
-    cx.set_shared_state(indoc! { "
-        fn boop() {
-          ˇbarp()
-          bazp()
-        }
-    "})
-        .await;
-    cx.simulate_shared_keystrokes(["shift-v", "j", "z", "f"])
-        .await;
-    cx.simulate_shared_keystrokes(["escape"]).await;
-    cx.simulate_shared_keystrokes(["g", "g"]).await;
-    cx.simulate_shared_keystrokes(["5", "d", "j"]).await;
-    cx.assert_shared_state(indoc! { "ˇ"}).await;
-}
-
-#[gpui::test]
-async fn test_clear_counts(cx: &mut gpui::TestAppContext) {
-    let mut cx = NeovimBackedTestContext::new(cx).await;
-
-    cx.set_shared_state(indoc! {"
-        The quick brown
-        fox juˇmps over
-        the lazy dog"})
-        .await;
-
-    cx.simulate_shared_keystrokes(["4", "escape", "3", "d", "l"])
-        .await;
-    cx.assert_shared_state(indoc! {"
-        The quick brown
-        fox juˇ over
-        the lazy dog"})
-        .await;
-}
-
-#[gpui::test]
-async fn test_zero(cx: &mut gpui::TestAppContext) {
-    let mut cx = NeovimBackedTestContext::new(cx).await;
-
-    cx.set_shared_state(indoc! {"
-        The quˇick brown
-        fox jumps over
-        the lazy dog"})
-        .await;
-
-    cx.simulate_shared_keystrokes(["0"]).await;
-    cx.assert_shared_state(indoc! {"
-        ˇThe quick brown
-        fox jumps over
-        the lazy dog"})
-        .await;
-
-    cx.simulate_shared_keystrokes(["1", "0", "l"]).await;
-    cx.assert_shared_state(indoc! {"
-        The quick ˇbrown
-        fox jumps over
-        the lazy dog"})
-        .await;
-}
-
-#[gpui::test]
-async fn test_selection_goal(cx: &mut gpui::TestAppContext) {
-    let mut cx = NeovimBackedTestContext::new(cx).await;
-
-    cx.set_shared_state(indoc! {"
-        ;;ˇ;
-        Lorem Ipsum"})
-        .await;
-
-    cx.simulate_shared_keystrokes(["a", "down", "up", ";", "down", "up"])
-        .await;
-    cx.assert_shared_state(indoc! {"
-        ;;;;ˇ
-        Lorem Ipsum"})
-        .await;
-}
-
-#[gpui::test]
-async fn test_wrapped_motions(cx: &mut gpui::TestAppContext) {
-    let mut cx = NeovimBackedTestContext::new(cx).await;
-
-    cx.set_shared_wrap(12).await;
-
-    cx.set_shared_state(indoc! {"
-                aaˇaa
-                😃😃"
-    })
-    .await;
-    cx.simulate_shared_keystrokes(["j"]).await;
-    cx.assert_shared_state(indoc! {"
-                aaaa
-                😃ˇ😃"
-    })
-    .await;
-
-    cx.set_shared_state(indoc! {"
-                123456789012aaˇaa
-                123456789012😃😃"
-    })
-    .await;
-    cx.simulate_shared_keystrokes(["j"]).await;
-    cx.assert_shared_state(indoc! {"
-        123456789012aaaa
-        123456789012😃ˇ😃"
-    })
-    .await;
-
-    cx.set_shared_state(indoc! {"
-                123456789012aaˇaa
-                123456789012😃😃"
-    })
-    .await;
-    cx.simulate_shared_keystrokes(["j"]).await;
-    cx.assert_shared_state(indoc! {"
-        123456789012aaaa
-        123456789012😃ˇ😃"
-    })
-    .await;
-
-    cx.set_shared_state(indoc! {"
-        123456789012aaaaˇaaaaaaaa123456789012
-        wow
-        123456789012😃😃😃😃😃😃123456789012"
-    })
-    .await;
-    cx.simulate_shared_keystrokes(["j", "j"]).await;
-    cx.assert_shared_state(indoc! {"
-        123456789012aaaaaaaaaaaa123456789012
-        wow
-        123456789012😃😃ˇ😃😃😃😃123456789012"
-    })
-    .await;
-}
-
-#[gpui::test]
-async fn test_paragraphs_dont_wrap(cx: &mut gpui::TestAppContext) {
-    let mut cx = NeovimBackedTestContext::new(cx).await;
-
-    cx.set_shared_state(indoc! {"
-        one
-        ˇ
-        two"})
-        .await;
-
-    cx.simulate_shared_keystrokes(["}", "}"]).await;
-    cx.assert_shared_state(indoc! {"
-        one
-
-        twˇo"})
-        .await;
-
-    cx.simulate_shared_keystrokes(["{", "{", "{"]).await;
-    cx.assert_shared_state(indoc! {"
-        ˇone
-
-        two"})
-        .await;
-}
-
-#[gpui::test]
-async fn test_select_all_issue_2170(cx: &mut gpui::TestAppContext) {
-    let mut cx = VimTestContext::new(cx, true).await;
-
-    cx.set_state(
-        indoc! {"
-        defmodule Test do
-            def test(a, ˇ[_, _] = b), do: IO.puts('hi')
-        end
-    "},
-        Mode::Normal,
-    );
-    cx.simulate_keystrokes(["g", "a"]);
-    cx.assert_state(
-        indoc! {"
-        defmodule Test do
-            def test(a, «[ˇ»_, _] = b), do: IO.puts('hi')
-        end
-    "},
-        Mode::Visual,
-    );
-}

crates/vim2/src/test/neovim_backed_binding_test_context.rs 🔗

@@ -1,95 +0,0 @@
-use std::ops::{Deref, DerefMut};
-
-use crate::state::Mode;
-
-use super::{ExemptionFeatures, NeovimBackedTestContext, SUPPORTED_FEATURES};
-
-pub struct NeovimBackedBindingTestContext<'a, const COUNT: usize> {
-    cx: NeovimBackedTestContext<'a>,
-    keystrokes_under_test: [&'static str; COUNT],
-}
-
-impl<'a, const COUNT: usize> NeovimBackedBindingTestContext<'a, COUNT> {
-    pub fn new(
-        keystrokes_under_test: [&'static str; COUNT],
-        cx: NeovimBackedTestContext<'a>,
-    ) -> Self {
-        Self {
-            cx,
-            keystrokes_under_test,
-        }
-    }
-
-    pub fn consume(self) -> NeovimBackedTestContext<'a> {
-        self.cx
-    }
-
-    pub fn binding<const NEW_COUNT: usize>(
-        self,
-        keystrokes: [&'static str; NEW_COUNT],
-    ) -> NeovimBackedBindingTestContext<'a, NEW_COUNT> {
-        self.consume().binding(keystrokes)
-    }
-
-    pub async fn assert(&mut self, marked_positions: &str) {
-        self.cx
-            .assert_binding_matches(self.keystrokes_under_test, marked_positions)
-            .await;
-    }
-
-    pub async fn assert_exempted(&mut self, marked_positions: &str, feature: ExemptionFeatures) {
-        if SUPPORTED_FEATURES.contains(&feature) {
-            self.cx
-                .assert_binding_matches(self.keystrokes_under_test, marked_positions)
-                .await
-        }
-    }
-
-    pub fn assert_manual(
-        &mut self,
-        initial_state: &str,
-        mode_before: Mode,
-        state_after: &str,
-        mode_after: Mode,
-    ) {
-        self.cx.assert_binding(
-            self.keystrokes_under_test,
-            initial_state,
-            mode_before,
-            state_after,
-            mode_after,
-        );
-    }
-
-    pub async fn assert_all(&mut self, marked_positions: &str) {
-        self.cx
-            .assert_binding_matches_all(self.keystrokes_under_test, marked_positions)
-            .await
-    }
-
-    pub async fn assert_all_exempted(
-        &mut self,
-        marked_positions: &str,
-        feature: ExemptionFeatures,
-    ) {
-        if SUPPORTED_FEATURES.contains(&feature) {
-            self.cx
-                .assert_binding_matches_all(self.keystrokes_under_test, marked_positions)
-                .await
-        }
-    }
-}
-
-impl<'a, const COUNT: usize> Deref for NeovimBackedBindingTestContext<'a, COUNT> {
-    type Target = NeovimBackedTestContext<'a>;
-
-    fn deref(&self) -> &Self::Target {
-        &self.cx
-    }
-}
-
-impl<'a, const COUNT: usize> DerefMut for NeovimBackedBindingTestContext<'a, COUNT> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.cx
-    }
-}

crates/vim2/src/test/neovim_backed_test_context.rs 🔗

@@ -1,439 +0,0 @@
-use editor::{scroll::VERTICAL_SCROLL_MARGIN, test::editor_test_context::ContextHandle};
-use gpui::{px, size, Context};
-use indoc::indoc;
-use settings::SettingsStore;
-use std::{
-    ops::{Deref, DerefMut},
-    panic, thread,
-};
-
-use collections::{HashMap, HashSet};
-use language::language_settings::{AllLanguageSettings, SoftWrap};
-use util::test::marked_text_offsets;
-
-use super::{neovim_connection::NeovimConnection, NeovimBackedBindingTestContext, VimTestContext};
-use crate::state::Mode;
-
-pub const SUPPORTED_FEATURES: &[ExemptionFeatures] = &[];
-
-/// Enum representing features we have tests for but which don't work, yet. Used
-/// to add exemptions and automatically
-#[derive(PartialEq, Eq)]
-pub enum ExemptionFeatures {
-    // MOTIONS
-    // When an operator completes at the end of the file, an extra newline is left
-    OperatorLastNewlineRemains,
-
-    // OBJECTS
-    // Resulting position after the operation is slightly incorrect for unintuitive reasons.
-    IncorrectLandingPosition,
-    // Operator around the text object at the end of the line doesn't remove whitespace.
-    AroundObjectLeavesWhitespaceAtEndOfLine,
-    // Sentence object on empty lines
-    SentenceOnEmptyLines,
-    // Whitespace isn't included with text objects at the start of the line
-    SentenceAtStartOfLineWithWhitespace,
-    // Whitespace around sentences is slightly incorrect when starting between sentences
-    AroundSentenceStartingBetweenIncludesWrongWhitespace,
-    // Non empty selection with text objects in visual mode
-    NonEmptyVisualTextObjects,
-    // Sentence Doesn't backtrack when its at the end of the file
-    SentenceAfterPunctuationAtEndOfFile,
-}
-
-impl ExemptionFeatures {
-    pub fn supported(&self) -> bool {
-        SUPPORTED_FEATURES.contains(self)
-    }
-}
-
-pub struct NeovimBackedTestContext<'a> {
-    cx: VimTestContext<'a>,
-    // Lookup for exempted assertions. Keyed by the insertion text, and with a value indicating which
-    // bindings are exempted. If None, all bindings are ignored for that insertion text.
-    exemptions: HashMap<String, Option<HashSet<String>>>,
-    neovim: NeovimConnection,
-
-    last_set_state: Option<String>,
-    recent_keystrokes: Vec<String>,
-
-    is_dirty: bool,
-}
-
-impl<'a> NeovimBackedTestContext<'a> {
-    pub async fn new(cx: &'a mut gpui::TestAppContext) -> NeovimBackedTestContext<'a> {
-        // rust stores the name of the test on the current thread.
-        // We use this to automatically name a file that will store
-        // the neovim connection's requests/responses so that we can
-        // run without neovim on CI.
-        let thread = thread::current();
-        let test_name = thread
-            .name()
-            .expect("thread is not named")
-            .split(":")
-            .last()
-            .unwrap()
-            .to_string();
-        Self {
-            cx: VimTestContext::new(cx, true).await,
-            exemptions: Default::default(),
-            neovim: NeovimConnection::new(test_name).await,
-
-            last_set_state: None,
-            recent_keystrokes: Default::default(),
-            is_dirty: false,
-        }
-    }
-
-    pub fn add_initial_state_exemptions(
-        &mut self,
-        marked_positions: &str,
-        missing_feature: ExemptionFeatures, // Feature required to support this exempted test case
-    ) {
-        if !missing_feature.supported() {
-            let (unmarked_text, cursor_offsets) = marked_text_offsets(marked_positions);
-
-            for cursor_offset in cursor_offsets.iter() {
-                let mut marked_text = unmarked_text.clone();
-                marked_text.insert(*cursor_offset, 'ˇ');
-
-                // None represents all key bindings being exempted for that initial state
-                self.exemptions.insert(marked_text, None);
-            }
-        }
-    }
-
-    pub async fn simulate_shared_keystroke(&mut self, keystroke_text: &str) -> ContextHandle {
-        self.neovim.send_keystroke(keystroke_text).await;
-        self.simulate_keystroke(keystroke_text)
-    }
-
-    pub async fn simulate_shared_keystrokes<const COUNT: usize>(
-        &mut self,
-        keystroke_texts: [&str; COUNT],
-    ) {
-        for keystroke_text in keystroke_texts.into_iter() {
-            self.recent_keystrokes.push(keystroke_text.to_string());
-            self.neovim.send_keystroke(keystroke_text).await;
-        }
-        self.simulate_keystrokes(keystroke_texts);
-    }
-
-    pub async fn set_shared_state(&mut self, marked_text: &str) {
-        let mode = if marked_text.contains("»") {
-            Mode::Visual
-        } else {
-            Mode::Normal
-        };
-        self.set_state(marked_text, mode);
-        self.last_set_state = Some(marked_text.to_string());
-        self.recent_keystrokes = Vec::new();
-        self.neovim.set_state(marked_text).await;
-        self.is_dirty = true;
-    }
-
-    pub async fn set_shared_wrap(&mut self, columns: u32) {
-        if columns < 12 {
-            panic!("nvim doesn't support columns < 12")
-        }
-        self.neovim.set_option("wrap").await;
-        self.neovim
-            .set_option(&format!("columns={}", columns))
-            .await;
-
-        self.update(|cx| {
-            cx.update_global(|settings: &mut SettingsStore, cx| {
-                settings.update_user_settings::<AllLanguageSettings>(cx, |settings| {
-                    settings.defaults.soft_wrap = Some(SoftWrap::PreferredLineLength);
-                    settings.defaults.preferred_line_length = Some(columns);
-                });
-            })
-        })
-    }
-
-    pub async fn set_scroll_height(&mut self, rows: u32) {
-        // match Zed's scrolling behavior
-        self.neovim
-            .set_option(&format!("scrolloff={}", VERTICAL_SCROLL_MARGIN))
-            .await;
-        // +2 to account for the vim command UI at the bottom.
-        self.neovim.set_option(&format!("lines={}", rows + 2)).await;
-        let (line_height, visible_line_count) = self.editor(|editor, cx| {
-            (
-                editor
-                    .style()
-                    .unwrap()
-                    .text
-                    .line_height_in_pixels(cx.rem_size()),
-                editor.visible_line_count().unwrap(),
-            )
-        });
-
-        let window = self.window;
-        let margin = self
-            .update_window(window, |_, cx| {
-                cx.viewport_size().height - line_height * visible_line_count
-            })
-            .unwrap();
-
-        self.simulate_window_resize(
-            self.window,
-            size(px(1000.), margin + (rows as f32) * line_height),
-        );
-    }
-
-    pub async fn set_neovim_option(&mut self, option: &str) {
-        self.neovim.set_option(option).await;
-    }
-
-    pub async fn assert_shared_state(&mut self, marked_text: &str) {
-        self.is_dirty = false;
-        let marked_text = marked_text.replace("•", " ");
-        let neovim = self.neovim_state().await;
-        let editor = self.editor_state();
-        if neovim == marked_text && neovim == editor {
-            return;
-        }
-        let initial_state = self
-            .last_set_state
-            .as_ref()
-            .unwrap_or(&"N/A".to_string())
-            .clone();
-
-        let message = if neovim != marked_text {
-            "Test is incorrect (currently expected != neovim_state)"
-        } else {
-            "Editor does not match nvim behaviour"
-        };
-        panic!(
-            indoc! {"{}
-                # initial state:
-                {}
-                # keystrokes:
-                {}
-                # currently expected:
-                {}
-                # neovim state:
-                {}
-                # zed state:
-                {}"},
-            message,
-            initial_state,
-            self.recent_keystrokes.join(" "),
-            marked_text.replace(" \n", "•\n"),
-            neovim.replace(" \n", "•\n"),
-            editor.replace(" \n", "•\n")
-        )
-    }
-
-    pub async fn assert_shared_clipboard(&mut self, text: &str) {
-        let neovim = self.neovim.read_register('"').await;
-        let editor = self.read_from_clipboard().unwrap().text().clone();
-
-        if text == neovim && text == editor {
-            return;
-        }
-
-        let message = if neovim != text {
-            "Test is incorrect (currently expected != neovim)"
-        } else {
-            "Editor does not match nvim behaviour"
-        };
-
-        let initial_state = self
-            .last_set_state
-            .as_ref()
-            .unwrap_or(&"N/A".to_string())
-            .clone();
-
-        panic!(
-            indoc! {"{}
-                # initial state:
-                {}
-                # keystrokes:
-                {}
-                # currently expected:
-                {}
-                # neovim clipboard:
-                {}
-                # zed clipboard:
-                {}"},
-            message,
-            initial_state,
-            self.recent_keystrokes.join(" "),
-            text,
-            neovim,
-            editor
-        )
-    }
-
-    pub async fn neovim_state(&mut self) -> String {
-        self.neovim.marked_text().await
-    }
-
-    pub async fn neovim_mode(&mut self) -> Mode {
-        self.neovim.mode().await.unwrap()
-    }
-
-    pub async fn assert_state_matches(&mut self) {
-        self.is_dirty = false;
-        let neovim = self.neovim_state().await;
-        let editor = self.editor_state();
-        let initial_state = self
-            .last_set_state
-            .as_ref()
-            .unwrap_or(&"N/A".to_string())
-            .clone();
-
-        if neovim != editor {
-            panic!(
-                indoc! {"Test failed (zed does not match nvim behaviour)
-                    # initial state:
-                    {}
-                    # keystrokes:
-                    {}
-                    # neovim state:
-                    {}
-                    # zed state:
-                    {}"},
-                initial_state,
-                self.recent_keystrokes.join(" "),
-                neovim,
-                editor,
-            )
-        }
-    }
-
-    pub async fn assert_binding_matches<const COUNT: usize>(
-        &mut self,
-        keystrokes: [&str; COUNT],
-        initial_state: &str,
-    ) {
-        if let Some(possible_exempted_keystrokes) = self.exemptions.get(initial_state) {
-            match possible_exempted_keystrokes {
-                Some(exempted_keystrokes) => {
-                    if exempted_keystrokes.contains(&format!("{keystrokes:?}")) {
-                        // This keystroke was exempted for this insertion text
-                        return;
-                    }
-                }
-                None => {
-                    // All keystrokes for this insertion text are exempted
-                    return;
-                }
-            }
-        }
-
-        let _state_context = self.set_shared_state(initial_state).await;
-        let _keystroke_context = self.simulate_shared_keystrokes(keystrokes).await;
-        self.assert_state_matches().await;
-    }
-
-    pub async fn assert_binding_matches_all<const COUNT: usize>(
-        &mut self,
-        keystrokes: [&str; COUNT],
-        marked_positions: &str,
-    ) {
-        let (unmarked_text, cursor_offsets) = marked_text_offsets(marked_positions);
-
-        for cursor_offset in cursor_offsets.iter() {
-            let mut marked_text = unmarked_text.clone();
-            marked_text.insert(*cursor_offset, 'ˇ');
-
-            self.assert_binding_matches(keystrokes, &marked_text).await;
-        }
-    }
-
-    pub fn each_marked_position(&self, marked_positions: &str) -> Vec<String> {
-        let (unmarked_text, cursor_offsets) = marked_text_offsets(marked_positions);
-        let mut ret = Vec::with_capacity(cursor_offsets.len());
-
-        for cursor_offset in cursor_offsets.iter() {
-            let mut marked_text = unmarked_text.clone();
-            marked_text.insert(*cursor_offset, 'ˇ');
-            ret.push(marked_text)
-        }
-
-        ret
-    }
-
-    pub async fn assert_neovim_compatible<const COUNT: usize>(
-        &mut self,
-        marked_positions: &str,
-        keystrokes: [&str; COUNT],
-    ) {
-        self.set_shared_state(&marked_positions).await;
-        self.simulate_shared_keystrokes(keystrokes).await;
-        self.assert_state_matches().await;
-    }
-
-    pub async fn assert_matches_neovim<const COUNT: usize>(
-        &mut self,
-        marked_positions: &str,
-        keystrokes: [&str; COUNT],
-        result: &str,
-    ) {
-        self.set_shared_state(marked_positions).await;
-        self.simulate_shared_keystrokes(keystrokes).await;
-        self.assert_shared_state(result).await;
-    }
-
-    pub async fn assert_binding_matches_all_exempted<const COUNT: usize>(
-        &mut self,
-        keystrokes: [&str; COUNT],
-        marked_positions: &str,
-        feature: ExemptionFeatures,
-    ) {
-        if SUPPORTED_FEATURES.contains(&feature) {
-            self.assert_binding_matches_all(keystrokes, marked_positions)
-                .await
-        }
-    }
-
-    pub fn binding<const COUNT: usize>(
-        self,
-        keystrokes: [&'static str; COUNT],
-    ) -> NeovimBackedBindingTestContext<'a, COUNT> {
-        NeovimBackedBindingTestContext::new(keystrokes, self)
-    }
-}
-
-impl<'a> Deref for NeovimBackedTestContext<'a> {
-    type Target = VimTestContext<'a>;
-
-    fn deref(&self) -> &Self::Target {
-        &self.cx
-    }
-}
-
-impl<'a> DerefMut for NeovimBackedTestContext<'a> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.cx
-    }
-}
-
-// a common mistake in tests is to call set_shared_state when
-// you mean asswert_shared_state. This notices that and lets
-// you know.
-impl<'a> Drop for NeovimBackedTestContext<'a> {
-    fn drop(&mut self) {
-        if self.is_dirty {
-            panic!("Test context was dropped after set_shared_state before assert_shared_state")
-        }
-    }
-}
-
-#[cfg(test)]
-mod test {
-    use gpui::TestAppContext;
-
-    use crate::test::NeovimBackedTestContext;
-
-    #[gpui::test]
-    async fn neovim_backed_test_context_works(cx: &mut TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-        cx.assert_state_matches().await;
-        cx.set_shared_state("This is a tesˇt").await;
-        cx.assert_state_matches().await;
-    }
-}

crates/vim2/src/test/neovim_connection.rs 🔗

@@ -1,599 +0,0 @@
-use std::path::PathBuf;
-#[cfg(feature = "neovim")]
-use std::{
-    cmp,
-    ops::{Deref, DerefMut, Range},
-};
-
-#[cfg(feature = "neovim")]
-use async_compat::Compat;
-#[cfg(feature = "neovim")]
-use async_trait::async_trait;
-#[cfg(feature = "neovim")]
-use gpui::Keystroke;
-
-#[cfg(feature = "neovim")]
-use language::Point;
-
-#[cfg(feature = "neovim")]
-use nvim_rs::{
-    create::tokio::new_child_cmd, error::LoopError, Handler, Neovim, UiAttachOptions, Value,
-};
-#[cfg(feature = "neovim")]
-use parking_lot::ReentrantMutex;
-use serde::{Deserialize, Serialize};
-#[cfg(feature = "neovim")]
-use tokio::{
-    process::{Child, ChildStdin, Command},
-    task::JoinHandle,
-};
-
-use crate::state::Mode;
-use collections::VecDeque;
-
-// Neovim doesn't like to be started simultaneously from multiple threads. We use this lock
-// to ensure we are only constructing one neovim connection at a time.
-#[cfg(feature = "neovim")]
-static NEOVIM_LOCK: ReentrantMutex<()> = ReentrantMutex::new(());
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-pub enum NeovimData {
-    Put { state: String },
-    Key(String),
-    Get { state: String, mode: Option<Mode> },
-    ReadRegister { name: char, value: String },
-    SetOption { value: String },
-}
-
-pub struct NeovimConnection {
-    data: VecDeque<NeovimData>,
-    #[cfg(feature = "neovim")]
-    test_case_id: String,
-    #[cfg(feature = "neovim")]
-    nvim: Neovim<nvim_rs::compat::tokio::Compat<ChildStdin>>,
-    #[cfg(feature = "neovim")]
-    _join_handle: JoinHandle<Result<(), Box<LoopError>>>,
-    #[cfg(feature = "neovim")]
-    _child: Child,
-}
-
-impl NeovimConnection {
-    pub async fn new(test_case_id: String) -> Self {
-        #[cfg(feature = "neovim")]
-        let handler = NvimHandler {};
-        #[cfg(feature = "neovim")]
-        let (nvim, join_handle, child) = Compat::new(async {
-            // Ensure we don't create neovim connections in parallel
-            let _lock = NEOVIM_LOCK.lock();
-            let (nvim, join_handle, child) = new_child_cmd(
-                &mut Command::new("nvim")
-                    .arg("--embed")
-                    .arg("--clean")
-                    // disable swap (otherwise after about 1000 test runs you run out of swap file names)
-                    .arg("-n")
-                    // disable writing files (just in case)
-                    .arg("-m"),
-                handler,
-            )
-            .await
-            .expect("Could not connect to neovim process");
-
-            nvim.ui_attach(100, 100, &UiAttachOptions::default())
-                .await
-                .expect("Could not attach to ui");
-
-            // Makes system act a little more like zed in terms of indentation
-            nvim.set_option("smartindent", nvim_rs::Value::Boolean(true))
-                .await
-                .expect("Could not set smartindent on startup");
-
-            (nvim, join_handle, child)
-        })
-        .await;
-
-        Self {
-            #[cfg(feature = "neovim")]
-            data: Default::default(),
-            #[cfg(not(feature = "neovim"))]
-            data: Self::read_test_data(&test_case_id),
-            #[cfg(feature = "neovim")]
-            test_case_id,
-            #[cfg(feature = "neovim")]
-            nvim,
-            #[cfg(feature = "neovim")]
-            _join_handle: join_handle,
-            #[cfg(feature = "neovim")]
-            _child: child,
-        }
-    }
-
-    // Sends a keystroke to the neovim process.
-    #[cfg(feature = "neovim")]
-    pub async fn send_keystroke(&mut self, keystroke_text: &str) {
-        let mut keystroke = Keystroke::parse(keystroke_text).unwrap();
-
-        if keystroke.key == "<" {
-            keystroke.key = "lt".to_string()
-        }
-
-        let special = keystroke.modifiers.shift
-            || keystroke.modifiers.control
-            || keystroke.modifiers.alt
-            || keystroke.modifiers.command
-            || keystroke.key.len() > 1;
-        let start = if special { "<" } else { "" };
-        let shift = if keystroke.modifiers.shift { "S-" } else { "" };
-        let ctrl = if keystroke.modifiers.control {
-            "C-"
-        } else {
-            ""
-        };
-        let alt = if keystroke.modifiers.alt { "M-" } else { "" };
-        let cmd = if keystroke.modifiers.command {
-            "D-"
-        } else {
-            ""
-        };
-        let end = if special { ">" } else { "" };
-
-        let key = format!("{start}{shift}{ctrl}{alt}{cmd}{}{end}", keystroke.key);
-
-        self.data
-            .push_back(NeovimData::Key(keystroke_text.to_string()));
-        self.nvim
-            .input(&key)
-            .await
-            .expect("Could not input keystroke");
-    }
-
-    #[cfg(not(feature = "neovim"))]
-    pub async fn send_keystroke(&mut self, keystroke_text: &str) {
-        if matches!(self.data.front(), Some(NeovimData::Get { .. })) {
-            self.data.pop_front();
-        }
-        assert_eq!(
-            self.data.pop_front(),
-            Some(NeovimData::Key(keystroke_text.to_string())),
-            "operation does not match recorded script. re-record with --features=neovim"
-        );
-    }
-
-    #[cfg(feature = "neovim")]
-    pub async fn set_state(&mut self, marked_text: &str) {
-        let (text, selections) = parse_state(&marked_text);
-
-        let nvim_buffer = self
-            .nvim
-            .get_current_buf()
-            .await
-            .expect("Could not get neovim buffer");
-        let lines = text
-            .split('\n')
-            .map(|line| line.to_string())
-            .collect::<Vec<_>>();
-
-        nvim_buffer
-            .set_lines(0, -1, false, lines)
-            .await
-            .expect("Could not set nvim buffer text");
-
-        self.nvim
-            .input("<escape>")
-            .await
-            .expect("Could not send escape to nvim");
-        self.nvim
-            .input("<escape>")
-            .await
-            .expect("Could not send escape to nvim");
-
-        let nvim_window = self
-            .nvim
-            .get_current_win()
-            .await
-            .expect("Could not get neovim window");
-
-        if selections.len() != 1 {
-            panic!("must have one selection");
-        }
-        let selection = &selections[0];
-
-        let cursor = selection.start;
-        nvim_window
-            .set_cursor((cursor.row as i64 + 1, cursor.column as i64))
-            .await
-            .expect("Could not set nvim cursor position");
-
-        if !selection.is_empty() {
-            self.nvim
-                .input("v")
-                .await
-                .expect("could not enter visual mode");
-
-            let cursor = selection.end;
-            nvim_window
-                .set_cursor((cursor.row as i64 + 1, cursor.column as i64))
-                .await
-                .expect("Could not set nvim cursor position");
-        }
-
-        if let Some(NeovimData::Get { mode, state }) = self.data.back() {
-            if *mode == Some(Mode::Normal) && *state == marked_text {
-                return;
-            }
-        }
-        self.data.push_back(NeovimData::Put {
-            state: marked_text.to_string(),
-        })
-    }
-
-    #[cfg(not(feature = "neovim"))]
-    pub async fn set_state(&mut self, marked_text: &str) {
-        if let Some(NeovimData::Get { mode, state: text }) = self.data.front() {
-            if *mode == Some(Mode::Normal) && *text == marked_text {
-                return;
-            }
-            self.data.pop_front();
-        }
-        assert_eq!(
-            self.data.pop_front(),
-            Some(NeovimData::Put {
-                state: marked_text.to_string()
-            }),
-            "operation does not match recorded script. re-record with --features=neovim"
-        );
-    }
-
-    #[cfg(feature = "neovim")]
-    pub async fn set_option(&mut self, value: &str) {
-        self.nvim
-            .command_output(format!("set {}", value).as_str())
-            .await
-            .unwrap();
-
-        self.data.push_back(NeovimData::SetOption {
-            value: value.to_string(),
-        })
-    }
-
-    #[cfg(not(feature = "neovim"))]
-    pub async fn set_option(&mut self, value: &str) {
-        if let Some(NeovimData::Get { .. }) = self.data.front() {
-            self.data.pop_front();
-        };
-        assert_eq!(
-            self.data.pop_front(),
-            Some(NeovimData::SetOption {
-                value: value.to_string(),
-            }),
-            "operation does not match recorded script. re-record with --features=neovim"
-        );
-    }
-
-    #[cfg(not(feature = "neovim"))]
-    pub async fn read_register(&mut self, register: char) -> String {
-        if let Some(NeovimData::Get { .. }) = self.data.front() {
-            self.data.pop_front();
-        };
-        if let Some(NeovimData::ReadRegister { name, value }) = self.data.pop_front() {
-            if name == register {
-                return value;
-            }
-        }
-
-        panic!("operation does not match recorded script. re-record with --features=neovim")
-    }
-
-    #[cfg(feature = "neovim")]
-    pub async fn read_register(&mut self, name: char) -> String {
-        let value = self
-            .nvim
-            .command_output(format!("echo getreg('{}')", name).as_str())
-            .await
-            .unwrap();
-
-        self.data.push_back(NeovimData::ReadRegister {
-            name,
-            value: value.clone(),
-        });
-
-        value
-    }
-
-    #[cfg(feature = "neovim")]
-    async fn read_position(&mut self, cmd: &str) -> u32 {
-        self.nvim
-            .command_output(cmd)
-            .await
-            .unwrap()
-            .parse::<u32>()
-            .unwrap()
-    }
-
-    #[cfg(feature = "neovim")]
-    pub async fn state(&mut self) -> (Option<Mode>, String) {
-        let nvim_buffer = self
-            .nvim
-            .get_current_buf()
-            .await
-            .expect("Could not get neovim buffer");
-        let text = nvim_buffer
-            .get_lines(0, -1, false)
-            .await
-            .expect("Could not get buffer text")
-            .join("\n");
-
-        // nvim columns are 1-based, so -1.
-        let mut cursor_row = self.read_position("echo line('.')").await - 1;
-        let mut cursor_col = self.read_position("echo col('.')").await - 1;
-        let mut selection_row = self.read_position("echo line('v')").await - 1;
-        let mut selection_col = self.read_position("echo col('v')").await - 1;
-        let total_rows = self.read_position("echo line('$')").await - 1;
-
-        let nvim_mode_text = self
-            .nvim
-            .get_mode()
-            .await
-            .expect("Could not get mode")
-            .into_iter()
-            .find_map(|(key, value)| {
-                if key.as_str() == Some("mode") {
-                    Some(value.as_str().unwrap().to_owned())
-                } else {
-                    None
-                }
-            })
-            .expect("Could not find mode value");
-
-        let mode = match nvim_mode_text.as_ref() {
-            "i" => Some(Mode::Insert),
-            "n" => Some(Mode::Normal),
-            "v" => Some(Mode::Visual),
-            "V" => Some(Mode::VisualLine),
-            "\x16" => Some(Mode::VisualBlock),
-            _ => None,
-        };
-
-        let mut selections = Vec::new();
-        // Vim uses the index of the first and last character in the selection
-        // Zed uses the index of the positions between the characters, so we need
-        // to add one to the end in visual mode.
-        match mode {
-            Some(Mode::VisualBlock) if selection_row != cursor_row => {
-                // in zed we fake a block selecrtion by using multiple cursors (one per line)
-                // this code emulates that.
-                // to deal with casees where the selection is not perfectly rectangular we extract
-                // the content of the selection via the "a register to get the shape correctly.
-                self.nvim.input("\"aygv").await.unwrap();
-                let content = self.nvim.command_output("echo getreg('a')").await.unwrap();
-                let lines = content.split("\n").collect::<Vec<_>>();
-                let top = cmp::min(selection_row, cursor_row);
-                let left = cmp::min(selection_col, cursor_col);
-                for row in top..=cmp::max(selection_row, cursor_row) {
-                    let content = if row - top >= lines.len() as u32 {
-                        ""
-                    } else {
-                        lines[(row - top) as usize]
-                    };
-                    let line_len = self
-                        .read_position(format!("echo strlen(getline({}))", row + 1).as_str())
-                        .await;
-
-                    if left > line_len {
-                        continue;
-                    }
-
-                    let start = Point::new(row, left);
-                    let end = Point::new(row, left + content.len() as u32);
-                    if cursor_col >= selection_col {
-                        selections.push(start..end)
-                    } else {
-                        selections.push(end..start)
-                    }
-                }
-            }
-            Some(Mode::Visual) | Some(Mode::VisualLine) | Some(Mode::VisualBlock) => {
-                if selection_col > cursor_col {
-                    let selection_line_length =
-                        self.read_position("echo strlen(getline(line('v')))").await;
-                    if selection_line_length > selection_col {
-                        selection_col += 1;
-                    } else if selection_row < total_rows {
-                        selection_col = 0;
-                        selection_row += 1;
-                    }
-                } else {
-                    let cursor_line_length =
-                        self.read_position("echo strlen(getline(line('.')))").await;
-                    if cursor_line_length > cursor_col {
-                        cursor_col += 1;
-                    } else if cursor_row < total_rows {
-                        cursor_col = 0;
-                        cursor_row += 1;
-                    }
-                }
-                selections.push(
-                    Point::new(selection_row, selection_col)..Point::new(cursor_row, cursor_col),
-                )
-            }
-            Some(Mode::Insert) | Some(Mode::Normal) | None => selections
-                .push(Point::new(selection_row, selection_col)..Point::new(cursor_row, cursor_col)),
-        }
-
-        let ranges = encode_ranges(&text, &selections);
-        let state = NeovimData::Get {
-            mode,
-            state: ranges.clone(),
-        };
-
-        if self.data.back() != Some(&state) {
-            self.data.push_back(state.clone());
-        }
-
-        (mode, ranges)
-    }
-
-    #[cfg(not(feature = "neovim"))]
-    pub async fn state(&mut self) -> (Option<Mode>, String) {
-        if let Some(NeovimData::Get { state: raw, mode }) = self.data.front() {
-            (*mode, raw.to_string())
-        } else {
-            panic!("operation does not match recorded script. re-record with --features=neovim");
-        }
-    }
-
-    pub async fn mode(&mut self) -> Option<Mode> {
-        self.state().await.0
-    }
-
-    pub async fn marked_text(&mut self) -> String {
-        self.state().await.1
-    }
-
-    fn test_data_path(test_case_id: &str) -> PathBuf {
-        let mut data_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
-        data_path.push("test_data");
-        data_path.push(format!("{}.json", test_case_id));
-        data_path
-    }
-
-    #[cfg(not(feature = "neovim"))]
-    fn read_test_data(test_case_id: &str) -> VecDeque<NeovimData> {
-        let path = Self::test_data_path(test_case_id);
-        let json = std::fs::read_to_string(path).expect(
-            "Could not read test data. Is it generated? Try running test with '--features neovim'",
-        );
-
-        let mut result = VecDeque::new();
-        for line in json.lines() {
-            result.push_back(
-                serde_json::from_str(line)
-                    .expect("invalid test data. regenerate it with '--features neovim'"),
-            );
-        }
-        result
-    }
-
-    #[cfg(feature = "neovim")]
-    fn write_test_data(test_case_id: &str, data: &VecDeque<NeovimData>) {
-        let path = Self::test_data_path(test_case_id);
-        let mut json = Vec::new();
-        for entry in data {
-            serde_json::to_writer(&mut json, entry).unwrap();
-            json.push(b'\n');
-        }
-        std::fs::create_dir_all(path.parent().unwrap())
-            .expect("could not create test data directory");
-        std::fs::write(path, json).expect("could not write out test data");
-    }
-}
-
-#[cfg(feature = "neovim")]
-impl Deref for NeovimConnection {
-    type Target = Neovim<nvim_rs::compat::tokio::Compat<ChildStdin>>;
-
-    fn deref(&self) -> &Self::Target {
-        &self.nvim
-    }
-}
-
-#[cfg(feature = "neovim")]
-impl DerefMut for NeovimConnection {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.nvim
-    }
-}
-
-#[cfg(feature = "neovim")]
-impl Drop for NeovimConnection {
-    fn drop(&mut self) {
-        Self::write_test_data(&self.test_case_id, &self.data);
-    }
-}
-
-#[cfg(feature = "neovim")]
-#[derive(Clone)]
-struct NvimHandler {}
-
-#[cfg(feature = "neovim")]
-#[async_trait]
-impl Handler for NvimHandler {
-    type Writer = nvim_rs::compat::tokio::Compat<ChildStdin>;
-
-    async fn handle_request(
-        &self,
-        _event_name: String,
-        _arguments: Vec<Value>,
-        _neovim: Neovim<Self::Writer>,
-    ) -> Result<Value, Value> {
-        unimplemented!();
-    }
-
-    async fn handle_notify(
-        &self,
-        _event_name: String,
-        _arguments: Vec<Value>,
-        _neovim: Neovim<Self::Writer>,
-    ) {
-    }
-}
-
-#[cfg(feature = "neovim")]
-fn parse_state(marked_text: &str) -> (String, Vec<Range<Point>>) {
-    let (text, ranges) = util::test::marked_text_ranges(marked_text, true);
-    let point_ranges = ranges
-        .into_iter()
-        .map(|byte_range| {
-            let mut point_range = Point::zero()..Point::zero();
-            let mut ix = 0;
-            let mut position = Point::zero();
-            for c in text.chars().chain(['\0']) {
-                if ix == byte_range.start {
-                    point_range.start = position;
-                }
-                if ix == byte_range.end {
-                    point_range.end = position;
-                }
-                let len_utf8 = c.len_utf8();
-                ix += len_utf8;
-                if c == '\n' {
-                    position.row += 1;
-                    position.column = 0;
-                } else {
-                    position.column += len_utf8 as u32;
-                }
-            }
-            point_range
-        })
-        .collect::<Vec<_>>();
-    (text, point_ranges)
-}
-
-#[cfg(feature = "neovim")]
-fn encode_ranges(text: &str, point_ranges: &Vec<Range<Point>>) -> String {
-    let byte_ranges = point_ranges
-        .into_iter()
-        .map(|range| {
-            let mut byte_range = 0..0;
-            let mut ix = 0;
-            let mut position = Point::zero();
-            for c in text.chars().chain(['\0']) {
-                if position == range.start {
-                    byte_range.start = ix;
-                }
-                if position == range.end {
-                    byte_range.end = ix;
-                }
-                let len_utf8 = c.len_utf8();
-                ix += len_utf8;
-                if c == '\n' {
-                    position.row += 1;
-                    position.column = 0;
-                } else {
-                    position.column += len_utf8 as u32;
-                }
-            }
-            byte_range
-        })
-        .collect::<Vec<_>>();
-    util::test::generate_marked_text(text, &byte_ranges[..], true)
-}

crates/vim2/src/test/vim_test_context.rs 🔗

@@ -1,177 +0,0 @@
-use std::ops::{Deref, DerefMut};
-
-use editor::test::{
-    editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext,
-};
-use futures::Future;
-use gpui::{Context, View, VisualContext};
-use lsp::request;
-use search::BufferSearchBar;
-
-use crate::{state::Operator, *};
-
-pub struct VimTestContext<'a> {
-    cx: EditorLspTestContext<'a>,
-}
-
-impl<'a> VimTestContext<'a> {
-    pub fn init(cx: &mut gpui::TestAppContext) {
-        if cx.has_global::<Vim>() {
-            dbg!("OOPS");
-            return;
-        }
-        cx.update(|cx| {
-            search::init(cx);
-            let settings = SettingsStore::test(cx);
-            cx.set_global(settings);
-            command_palette::init(cx);
-            crate::init(cx);
-        });
-    }
-
-    pub async fn new(cx: &'a mut gpui::TestAppContext, enabled: bool) -> VimTestContext<'a> {
-        Self::init(cx);
-        let lsp = EditorLspTestContext::new_rust(Default::default(), cx).await;
-        Self::new_with_lsp(lsp, enabled)
-    }
-
-    pub async fn new_typescript(cx: &'a mut gpui::TestAppContext) -> VimTestContext<'a> {
-        Self::init(cx);
-        Self::new_with_lsp(
-            EditorLspTestContext::new_typescript(Default::default(), cx).await,
-            true,
-        )
-    }
-
-    pub fn new_with_lsp(mut cx: EditorLspTestContext<'a>, enabled: bool) -> VimTestContext<'a> {
-        cx.update(|cx| {
-            cx.update_global(|store: &mut SettingsStore, cx| {
-                store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(enabled));
-            });
-            settings::KeymapFile::load_asset("keymaps/default.json", cx).unwrap();
-            settings::KeymapFile::load_asset("keymaps/vim.json", cx).unwrap();
-        });
-
-        // Setup search toolbars and keypress hook
-        cx.update_workspace(|workspace, cx| {
-            observe_keystrokes(cx);
-            workspace.active_pane().update(cx, |pane, cx| {
-                pane.toolbar().update(cx, |toolbar, cx| {
-                    let buffer_search_bar = cx.new_view(BufferSearchBar::new);
-                    toolbar.add_item(buffer_search_bar, cx);
-                    // todo!();
-                    // let project_search_bar = cx.add_view(|_| ProjectSearchBar::new());
-                    // toolbar.add_item(project_search_bar, cx);
-                })
-            });
-            workspace.status_bar().update(cx, |status_bar, cx| {
-                let vim_mode_indicator = cx.new_view(ModeIndicator::new);
-                status_bar.add_right_item(vim_mode_indicator, cx);
-            });
-        });
-
-        Self { cx }
-    }
-
-    pub fn update_view<F, T, R>(&mut self, view: View<T>, update: F) -> R
-    where
-        T: 'static,
-        F: FnOnce(&mut T, &mut ViewContext<T>) -> R + 'static,
-    {
-        let window = self.window.clone();
-        self.update_window(window, move |_, cx| view.update(cx, update))
-            .unwrap()
-    }
-
-    pub fn workspace<F, T>(&mut self, update: F) -> T
-    where
-        F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
-    {
-        self.cx.update_workspace(update)
-    }
-
-    pub fn enable_vim(&mut self) {
-        self.cx.update(|cx| {
-            cx.update_global(|store: &mut SettingsStore, cx| {
-                store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(true));
-            });
-        })
-    }
-
-    pub fn disable_vim(&mut self) {
-        self.cx.update(|cx| {
-            cx.update_global(|store: &mut SettingsStore, cx| {
-                store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(false));
-            });
-        })
-    }
-
-    pub fn mode(&mut self) -> Mode {
-        self.cx.read(|cx| cx.global::<Vim>().state().mode)
-    }
-
-    pub fn active_operator(&mut self) -> Option<Operator> {
-        self.cx
-            .read(|cx| cx.global::<Vim>().state().operator_stack.last().copied())
-    }
-
-    pub fn set_state(&mut self, text: &str, mode: Mode) {
-        let window = self.window;
-        self.cx.set_state(text);
-        self.update_window(window, |_, cx| {
-            Vim::update(cx, |vim, cx| {
-                vim.switch_mode(mode, true, cx);
-            })
-        })
-        .unwrap();
-        self.cx.cx.cx.run_until_parked();
-    }
-
-    #[track_caller]
-    pub fn assert_state(&mut self, text: &str, mode: Mode) {
-        self.assert_editor_state(text);
-        assert_eq!(self.mode(), mode, "{}", self.assertion_context());
-    }
-
-    pub fn assert_binding<const COUNT: usize>(
-        &mut self,
-        keystrokes: [&str; COUNT],
-        initial_state: &str,
-        initial_mode: Mode,
-        state_after: &str,
-        mode_after: Mode,
-    ) {
-        self.set_state(initial_state, initial_mode);
-        self.cx.simulate_keystrokes(keystrokes);
-        self.cx.assert_editor_state(state_after);
-        assert_eq!(self.mode(), mode_after, "{}", self.assertion_context());
-        assert_eq!(self.active_operator(), None, "{}", self.assertion_context());
-    }
-
-    pub fn handle_request<T, F, Fut>(
-        &self,
-        handler: F,
-    ) -> futures::channel::mpsc::UnboundedReceiver<()>
-    where
-        T: 'static + request::Request,
-        T::Params: 'static + Send,
-        F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut,
-        Fut: 'static + Send + Future<Output = Result<T::Result>>,
-    {
-        self.cx.handle_request::<T, F, Fut>(handler)
-    }
-}
-
-impl<'a> Deref for VimTestContext<'a> {
-    type Target = EditorTestContext<'a>;
-
-    fn deref(&self) -> &Self::Target {
-        &self.cx
-    }
-}
-
-impl<'a> DerefMut for VimTestContext<'a> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.cx
-    }
-}

crates/vim2/src/utils.rs 🔗

@@ -1,50 +0,0 @@
-use editor::{ClipboardSelection, Editor};
-use gpui::{AppContext, ClipboardItem};
-use language::Point;
-
-pub fn copy_selections_content(editor: &mut Editor, linewise: bool, cx: &mut AppContext) {
-    let selections = editor.selections.all_adjusted(cx);
-    let buffer = editor.buffer().read(cx).snapshot(cx);
-    let mut text = String::new();
-    let mut clipboard_selections = Vec::with_capacity(selections.len());
-    {
-        let mut is_first = true;
-        for selection in selections.iter() {
-            let mut start = selection.start;
-            let end = selection.end;
-            if is_first {
-                is_first = false;
-            } else {
-                text.push_str("\n");
-            }
-            let initial_len = text.len();
-
-            // if the file does not end with \n, and our line-mode selection ends on
-            // that line, we will have expanded the start of the selection to ensure it
-            // contains a newline (so that delete works as expected). We undo that change
-            // here.
-            let is_last_line = linewise
-                && end.row == buffer.max_buffer_row()
-                && buffer.max_point().column > 0
-                && start.row < buffer.max_buffer_row()
-                && start == Point::new(start.row, buffer.line_len(start.row));
-
-            if is_last_line {
-                start = Point::new(start.row + 1, 0);
-            }
-            for chunk in buffer.text_for_range(start..end) {
-                text.push_str(chunk);
-            }
-            if is_last_line {
-                text.push_str("\n");
-            }
-            clipboard_selections.push(ClipboardSelection {
-                len: text.len() - initial_len,
-                is_entire_line: linewise,
-                first_line_indent: buffer.indent_size_for_line(start.row).len,
-            });
-        }
-    }
-
-    cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
-}

crates/vim2/src/vim.rs 🔗

@@ -1,607 +0,0 @@
-#[cfg(test)]
-mod test;
-
-mod command;
-mod editor_events;
-mod insert;
-mod mode_indicator;
-mod motion;
-mod normal;
-mod object;
-mod state;
-mod utils;
-mod visual;
-
-use anyhow::Result;
-use collections::{CommandPaletteFilter, HashMap};
-use command_palette::CommandPaletteInterceptor;
-use editor::{movement, Editor, EditorEvent, EditorMode};
-use gpui::{
-    actions, impl_actions, Action, AppContext, EntityId, KeyContext, Subscription, View,
-    ViewContext, WeakView, WindowContext,
-};
-use language::{CursorShape, Point, Selection, SelectionGoal};
-pub use mode_indicator::ModeIndicator;
-use motion::Motion;
-use normal::normal_replace;
-use serde::Deserialize;
-use settings::{update_settings_file, Settings, SettingsStore};
-use state::{EditorState, Mode, Operator, RecordedSelection, WorkspaceState};
-use std::{ops::Range, sync::Arc};
-use visual::{visual_block_motion, visual_replace};
-use workspace::{self, Workspace};
-
-use crate::state::ReplayableAction;
-
-pub struct VimModeSetting(pub bool);
-
-#[derive(Clone, Deserialize, PartialEq)]
-pub struct SwitchMode(pub Mode);
-
-#[derive(Clone, Deserialize, PartialEq)]
-pub struct PushOperator(pub Operator);
-
-#[derive(Clone, Deserialize, PartialEq)]
-struct Number(usize);
-
-actions!(
-    vim,
-    [Tab, Enter, Object, InnerObject, FindForward, FindBackward]
-);
-// in the workspace namespace so it's not filtered out when vim is disabled.
-actions!(workspace, [ToggleVimMode]);
-
-impl_actions!(vim, [SwitchMode, PushOperator, Number]);
-
-pub fn init(cx: &mut AppContext) {
-    cx.set_global(Vim::default());
-    VimModeSetting::register(cx);
-
-    editor_events::init(cx);
-
-    cx.observe_new_views(|workspace: &mut Workspace, cx| register(workspace, cx))
-        .detach();
-
-    // Any time settings change, update vim mode to match. The Vim struct
-    // will be initialized as disabled by default, so we filter its commands
-    // out when starting up.
-    cx.update_global::<CommandPaletteFilter, _>(|filter, _| {
-        filter.hidden_namespaces.insert("vim");
-    });
-    cx.update_global(|vim: &mut Vim, cx: &mut AppContext| {
-        vim.set_enabled(VimModeSetting::get_global(cx).0, cx)
-    });
-    cx.observe_global::<SettingsStore>(|cx| {
-        cx.update_global(|vim: &mut Vim, cx: &mut AppContext| {
-            vim.set_enabled(VimModeSetting::get_global(cx).0, cx)
-        });
-    })
-    .detach();
-}
-
-fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
-    workspace.register_action(|_: &mut Workspace, &SwitchMode(mode): &SwitchMode, cx| {
-        Vim::update(cx, |vim, cx| vim.switch_mode(mode, false, cx))
-    });
-    workspace.register_action(
-        |_: &mut Workspace, &PushOperator(operator): &PushOperator, cx| {
-            Vim::update(cx, |vim, cx| vim.push_operator(operator, cx))
-        },
-    );
-    workspace.register_action(|_: &mut Workspace, n: &Number, cx: _| {
-        Vim::update(cx, |vim, cx| vim.push_count_digit(n.0, cx));
-    });
-
-    workspace.register_action(|_: &mut Workspace, _: &Tab, cx| {
-        Vim::active_editor_input_ignored(" ".into(), cx)
-    });
-
-    workspace.register_action(|_: &mut Workspace, _: &Enter, cx| {
-        Vim::active_editor_input_ignored("\n".into(), cx)
-    });
-
-    workspace.register_action(|workspace: &mut Workspace, _: &ToggleVimMode, cx| {
-        let fs = workspace.app_state().fs.clone();
-        let currently_enabled = VimModeSetting::get_global(cx).0;
-        update_settings_file::<VimModeSetting>(fs, cx, move |setting| {
-            *setting = Some(!currently_enabled)
-        })
-    });
-
-    normal::register(workspace, cx);
-    insert::register(workspace, cx);
-    motion::register(workspace, cx);
-    command::register(workspace, cx);
-    object::register(workspace, cx);
-    visual::register(workspace, cx);
-}
-
-pub fn observe_keystrokes(cx: &mut WindowContext) {
-    cx.observe_keystrokes(|keystroke_event, cx| {
-        if let Some(action) = keystroke_event
-            .action
-            .as_ref()
-            .map(|action| action.boxed_clone())
-        {
-            Vim::update(cx, |vim, _| {
-                if vim.workspace_state.recording {
-                    vim.workspace_state
-                        .recorded_actions
-                        .push(ReplayableAction::Action(action.boxed_clone()));
-
-                    if vim.workspace_state.stop_recording_after_next_action {
-                        vim.workspace_state.recording = false;
-                        vim.workspace_state.stop_recording_after_next_action = false;
-                    }
-                }
-            });
-
-            // Keystroke is handled by the vim system, so continue forward
-            if action.name().starts_with("vim::") {
-                return;
-            }
-        } else if cx.has_pending_keystrokes() {
-            return;
-        }
-
-        Vim::update(cx, |vim, cx| match vim.active_operator() {
-            Some(
-                Operator::FindForward { .. } | Operator::FindBackward { .. } | Operator::Replace,
-            ) => {}
-            Some(_) => {
-                vim.clear_operator(cx);
-            }
-            _ => {}
-        });
-    })
-    .detach()
-}
-
-#[derive(Default)]
-pub struct Vim {
-    active_editor: Option<WeakView<Editor>>,
-    editor_subscription: Option<Subscription>,
-    enabled: bool,
-    editor_states: HashMap<EntityId, EditorState>,
-    workspace_state: WorkspaceState,
-    default_state: EditorState,
-}
-
-impl Vim {
-    fn read(cx: &mut AppContext) -> &Self {
-        cx.global::<Self>()
-    }
-
-    fn update<F, S>(cx: &mut WindowContext, update: F) -> S
-    where
-        F: FnOnce(&mut Self, &mut WindowContext) -> S,
-    {
-        cx.update_global(update)
-    }
-
-    fn set_active_editor(&mut self, editor: View<Editor>, cx: &mut WindowContext) {
-        self.active_editor = Some(editor.clone().downgrade());
-        self.editor_subscription = Some(cx.subscribe(&editor, |editor, event, cx| match event {
-            EditorEvent::SelectionsChanged { local: true } => {
-                let editor = editor.read(cx);
-                if editor.leader_peer_id().is_none() {
-                    let newest = editor.selections.newest::<usize>(cx);
-                    local_selections_changed(newest, cx);
-                }
-            }
-            EditorEvent::InputIgnored { text } => {
-                Vim::active_editor_input_ignored(text.clone(), cx);
-                Vim::record_insertion(text, None, cx)
-            }
-            EditorEvent::InputHandled {
-                text,
-                utf16_range_to_replace: range_to_replace,
-            } => Vim::record_insertion(text, range_to_replace.clone(), cx),
-            _ => {}
-        }));
-
-        if self.enabled {
-            let editor = editor.read(cx);
-            let editor_mode = editor.mode();
-            let newest_selection_empty = editor.selections.newest::<usize>(cx).is_empty();
-
-            if editor_mode == EditorMode::Full
-                && !newest_selection_empty
-                && self.state().mode == Mode::Normal
-                // When following someone, don't switch vim mode.
-                && editor.leader_peer_id().is_none()
-            {
-                self.switch_mode(Mode::Visual, true, cx);
-            }
-        }
-
-        self.sync_vim_settings(cx);
-    }
-
-    fn record_insertion(
-        text: &Arc<str>,
-        range_to_replace: Option<Range<isize>>,
-        cx: &mut WindowContext,
-    ) {
-        Vim::update(cx, |vim, _| {
-            if vim.workspace_state.recording {
-                vim.workspace_state
-                    .recorded_actions
-                    .push(ReplayableAction::Insertion {
-                        text: text.clone(),
-                        utf16_range_to_replace: range_to_replace,
-                    });
-                if vim.workspace_state.stop_recording_after_next_action {
-                    vim.workspace_state.recording = false;
-                    vim.workspace_state.stop_recording_after_next_action = false;
-                }
-            }
-        });
-    }
-
-    fn update_active_editor<S>(
-        &self,
-        cx: &mut WindowContext,
-        update: impl FnOnce(&mut Editor, &mut ViewContext<Editor>) -> S,
-    ) -> Option<S> {
-        let editor = self.active_editor.clone()?.upgrade()?;
-        Some(editor.update(cx, update))
-    }
-
-    pub fn start_recording(&mut self, cx: &mut WindowContext) {
-        if !self.workspace_state.replaying {
-            self.workspace_state.recording = true;
-            self.workspace_state.recorded_actions = Default::default();
-            self.workspace_state.recorded_count = None;
-
-            let selections = self
-                .active_editor
-                .as_ref()
-                .and_then(|editor| editor.upgrade())
-                .map(|editor| {
-                    let editor = editor.read(cx);
-                    (
-                        editor.selections.oldest::<Point>(cx),
-                        editor.selections.newest::<Point>(cx),
-                    )
-                });
-
-            if let Some((oldest, newest)) = selections {
-                self.workspace_state.recorded_selection = match self.state().mode {
-                    Mode::Visual if newest.end.row == newest.start.row => {
-                        RecordedSelection::SingleLine {
-                            cols: newest.end.column - newest.start.column,
-                        }
-                    }
-                    Mode::Visual => RecordedSelection::Visual {
-                        rows: newest.end.row - newest.start.row,
-                        cols: newest.end.column,
-                    },
-                    Mode::VisualLine => RecordedSelection::VisualLine {
-                        rows: newest.end.row - newest.start.row,
-                    },
-                    Mode::VisualBlock => RecordedSelection::VisualBlock {
-                        rows: newest.end.row.abs_diff(oldest.start.row),
-                        cols: newest.end.column.abs_diff(oldest.start.column),
-                    },
-                    _ => RecordedSelection::None,
-                }
-            } else {
-                self.workspace_state.recorded_selection = RecordedSelection::None;
-            }
-        }
-    }
-
-    pub fn stop_recording(&mut self) {
-        if self.workspace_state.recording {
-            self.workspace_state.stop_recording_after_next_action = true;
-        }
-    }
-
-    pub fn stop_recording_immediately(&mut self, action: Box<dyn Action>) {
-        if self.workspace_state.recording {
-            self.workspace_state
-                .recorded_actions
-                .push(ReplayableAction::Action(action.boxed_clone()));
-            self.workspace_state.recording = false;
-            self.workspace_state.stop_recording_after_next_action = false;
-        }
-    }
-
-    pub fn record_current_action(&mut self, cx: &mut WindowContext) {
-        self.start_recording(cx);
-        self.stop_recording();
-    }
-
-    fn switch_mode(&mut self, mode: Mode, leave_selections: bool, cx: &mut WindowContext) {
-        let state = self.state();
-        let last_mode = state.mode;
-        let prior_mode = state.last_mode;
-        self.update_state(|state| {
-            state.last_mode = last_mode;
-            state.mode = mode;
-            state.operator_stack.clear();
-        });
-        if mode != Mode::Insert {
-            self.take_count(cx);
-        }
-
-        // Sync editor settings like clip mode
-        self.sync_vim_settings(cx);
-
-        if leave_selections {
-            return;
-        }
-
-        // Adjust selections
-        self.update_active_editor(cx, |editor, cx| {
-            if last_mode != Mode::VisualBlock && last_mode.is_visual() && mode == Mode::VisualBlock
-            {
-                visual_block_motion(true, editor, cx, |_, point, goal| Some((point, goal)))
-            }
-
-            editor.change_selections(None, cx, |s| {
-                // we cheat with visual block mode and use multiple cursors.
-                // the cost of this cheat is we need to convert back to a single
-                // cursor whenever vim would.
-                if last_mode == Mode::VisualBlock
-                    && (mode != Mode::VisualBlock && mode != Mode::Insert)
-                {
-                    let tail = s.oldest_anchor().tail();
-                    let head = s.newest_anchor().head();
-                    s.select_anchor_ranges(vec![tail..head]);
-                } else if last_mode == Mode::Insert
-                    && prior_mode == Mode::VisualBlock
-                    && mode != Mode::VisualBlock
-                {
-                    let pos = s.first_anchor().head();
-                    s.select_anchor_ranges(vec![pos..pos])
-                }
-
-                s.move_with(|map, selection| {
-                    if last_mode.is_visual() && !mode.is_visual() {
-                        let mut point = selection.head();
-                        if !selection.reversed && !selection.is_empty() {
-                            point = movement::left(map, selection.head());
-                        }
-                        selection.collapse_to(point, selection.goal)
-                    } else if !last_mode.is_visual() && mode.is_visual() {
-                        if selection.is_empty() {
-                            selection.end = movement::right(map, selection.start);
-                        }
-                    }
-                });
-            })
-        });
-    }
-
-    fn push_count_digit(&mut self, number: usize, cx: &mut WindowContext) {
-        if self.active_operator().is_some() {
-            self.update_state(|state| {
-                state.post_count = Some(state.post_count.unwrap_or(0) * 10 + number)
-            })
-        } else {
-            self.update_state(|state| {
-                state.pre_count = Some(state.pre_count.unwrap_or(0) * 10 + number)
-            })
-        }
-        // update the keymap so that 0 works
-        self.sync_vim_settings(cx)
-    }
-
-    fn take_count(&mut self, cx: &mut WindowContext) -> Option<usize> {
-        if self.workspace_state.replaying {
-            return self.workspace_state.recorded_count;
-        }
-
-        let count = if self.state().post_count == None && self.state().pre_count == None {
-            return None;
-        } else {
-            Some(self.update_state(|state| {
-                state.post_count.take().unwrap_or(1) * state.pre_count.take().unwrap_or(1)
-            }))
-        };
-        if self.workspace_state.recording {
-            self.workspace_state.recorded_count = count;
-        }
-        self.sync_vim_settings(cx);
-        count
-    }
-
-    fn push_operator(&mut self, operator: Operator, cx: &mut WindowContext) {
-        if matches!(
-            operator,
-            Operator::Change | Operator::Delete | Operator::Replace
-        ) {
-            self.start_recording(cx)
-        };
-        self.update_state(|state| state.operator_stack.push(operator));
-        self.sync_vim_settings(cx);
-    }
-
-    fn maybe_pop_operator(&mut self) -> Option<Operator> {
-        self.update_state(|state| state.operator_stack.pop())
-    }
-
-    fn pop_operator(&mut self, cx: &mut WindowContext) -> Operator {
-        let popped_operator = self.update_state( |state| state.operator_stack.pop()
-        )            .expect("Operator popped when no operator was on the stack. This likely means there is an invalid keymap config");
-        self.sync_vim_settings(cx);
-        popped_operator
-    }
-    fn clear_operator(&mut self, cx: &mut WindowContext) {
-        self.take_count(cx);
-        self.update_state(|state| state.operator_stack.clear());
-        self.sync_vim_settings(cx);
-    }
-
-    fn active_operator(&self) -> Option<Operator> {
-        self.state().operator_stack.last().copied()
-    }
-
-    fn active_editor_input_ignored(text: Arc<str>, cx: &mut WindowContext) {
-        if text.is_empty() {
-            return;
-        }
-
-        match Vim::read(cx).active_operator() {
-            Some(Operator::FindForward { before }) => {
-                let find = Motion::FindForward {
-                    before,
-                    char: text.chars().next().unwrap(),
-                };
-                Vim::update(cx, |vim, _| {
-                    vim.workspace_state.last_find = Some(find.clone())
-                });
-                motion::motion(find, cx)
-            }
-            Some(Operator::FindBackward { after }) => {
-                let find = Motion::FindBackward {
-                    after,
-                    char: text.chars().next().unwrap(),
-                };
-                Vim::update(cx, |vim, _| {
-                    vim.workspace_state.last_find = Some(find.clone())
-                });
-                motion::motion(find, cx)
-            }
-            Some(Operator::Replace) => match Vim::read(cx).state().mode {
-                Mode::Normal => normal_replace(text, cx),
-                Mode::Visual | Mode::VisualLine | Mode::VisualBlock => visual_replace(text, cx),
-                _ => Vim::update(cx, |vim, cx| vim.clear_operator(cx)),
-            },
-            _ => {}
-        }
-    }
-
-    fn set_enabled(&mut self, enabled: bool, cx: &mut AppContext) {
-        if self.enabled != enabled {
-            self.enabled = enabled;
-
-            cx.update_global::<CommandPaletteFilter, _>(|filter, _| {
-                if self.enabled {
-                    filter.hidden_namespaces.remove("vim");
-                } else {
-                    filter.hidden_namespaces.insert("vim");
-                }
-            });
-
-            if self.enabled {
-                cx.set_global::<CommandPaletteInterceptor>(Box::new(command::command_interceptor));
-            } else if cx.has_global::<CommandPaletteInterceptor>() {
-                let _ = cx.remove_global::<CommandPaletteInterceptor>();
-            }
-
-            if let Some(active_window) = cx.active_window() {
-                active_window
-                    .update(cx, |root_view, cx| {
-                        if self.enabled {
-                            let active_editor = root_view
-                                .downcast::<Workspace>()
-                                .ok()
-                                .and_then(|workspace| workspace.read(cx).active_item(cx))
-                                .and_then(|item| item.downcast::<Editor>());
-                            if let Some(active_editor) = active_editor {
-                                self.set_active_editor(active_editor, cx);
-                            }
-                            self.switch_mode(Mode::Normal, false, cx);
-                        }
-                        self.sync_vim_settings(cx);
-                    })
-                    .ok();
-            }
-        }
-    }
-
-    pub fn state(&self) -> &EditorState {
-        if let Some(active_editor) = self.active_editor.as_ref() {
-            if let Some(state) = self.editor_states.get(&active_editor.entity_id()) {
-                return state;
-            }
-        }
-
-        &self.default_state
-    }
-
-    pub fn update_state<T>(&mut self, func: impl FnOnce(&mut EditorState) -> T) -> T {
-        let mut state = self.state().clone();
-        let ret = func(&mut state);
-
-        if let Some(active_editor) = self.active_editor.as_ref() {
-            self.editor_states.insert(active_editor.entity_id(), state);
-        }
-
-        ret
-    }
-
-    fn sync_vim_settings(&self, cx: &mut WindowContext) {
-        let state = self.state();
-        let cursor_shape = state.cursor_shape();
-
-        self.update_active_editor(cx, |editor, cx| {
-            if self.enabled && editor.mode() == EditorMode::Full {
-                editor.set_cursor_shape(cursor_shape, cx);
-                editor.set_clip_at_line_ends(state.clip_at_line_ends(), cx);
-                editor.set_collapse_matches(true);
-                editor.set_input_enabled(!state.vim_controlled());
-                editor.set_autoindent(state.should_autoindent());
-                editor.selections.line_mode = matches!(state.mode, Mode::VisualLine);
-                let context_layer = state.keymap_context_layer();
-                editor.set_keymap_context_layer::<Self>(context_layer, cx);
-            } else {
-                // Note: set_collapse_matches is not in unhook_vim_settings, as that method is called on blur,
-                // but we need collapse_matches to persist when the search bar is focused.
-                editor.set_collapse_matches(false);
-                self.unhook_vim_settings(editor, cx);
-            }
-        });
-    }
-
-    fn unhook_vim_settings(&self, editor: &mut Editor, cx: &mut ViewContext<Editor>) {
-        editor.set_cursor_shape(CursorShape::Bar, cx);
-        editor.set_clip_at_line_ends(false, cx);
-        editor.set_input_enabled(true);
-        editor.set_autoindent(true);
-        editor.selections.line_mode = false;
-
-        // we set the VimEnabled context on all editors so that we
-        // can distinguish between vim mode and non-vim mode in the BufferSearchBar.
-        // This is a bit of a hack, but currently the search crate does not depend on vim,
-        // and it seems nice to keep it that way.
-        if self.enabled {
-            let mut context = KeyContext::default();
-            context.add("VimEnabled");
-            editor.set_keymap_context_layer::<Self>(context, cx)
-        } else {
-            editor.remove_keymap_context_layer::<Self>(cx);
-        }
-    }
-}
-
-impl Settings for VimModeSetting {
-    const KEY: Option<&'static str> = Some("vim_mode");
-
-    type FileContent = Option<bool>;
-
-    fn load(
-        default_value: &Self::FileContent,
-        user_values: &[&Self::FileContent],
-        _: &mut AppContext,
-    ) -> Result<Self> {
-        Ok(Self(user_values.iter().rev().find_map(|v| **v).unwrap_or(
-            default_value.ok_or_else(Self::missing_default)?,
-        )))
-    }
-}
-
-fn local_selections_changed(newest: Selection<usize>, cx: &mut WindowContext) {
-    Vim::update(cx, |vim, cx| {
-        if vim.enabled && vim.state().mode == Mode::Normal && !newest.is_empty() {
-            if matches!(newest.goal, SelectionGoal::HorizontalRange { .. }) {
-                vim.switch_mode(Mode::VisualBlock, false, cx);
-            } else {
-                vim.switch_mode(Mode::Visual, false, cx)
-            }
-        }
-    })
-}

crates/vim2/src/visual.rs 🔗

@@ -1,1034 +0,0 @@
-use anyhow::Result;
-use std::sync::Arc;
-
-use collections::HashMap;
-use editor::{
-    display_map::{DisplaySnapshot, ToDisplayPoint},
-    movement,
-    scroll::autoscroll::Autoscroll,
-    Bias, DisplayPoint, Editor,
-};
-use gpui::{actions, ViewContext, WindowContext};
-use language::{Selection, SelectionGoal};
-use workspace::Workspace;
-
-use crate::{
-    motion::{start_of_line, Motion},
-    object::Object,
-    state::{Mode, Operator},
-    utils::copy_selections_content,
-    Vim,
-};
-
-actions!(
-    vim,
-    [
-        ToggleVisual,
-        ToggleVisualLine,
-        ToggleVisualBlock,
-        VisualDelete,
-        VisualYank,
-        OtherEnd,
-        SelectNext,
-        SelectPrevious,
-    ]
-);
-
-pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
-    workspace.register_action(|_, _: &ToggleVisual, cx: &mut ViewContext<Workspace>| {
-        toggle_mode(Mode::Visual, cx)
-    });
-    workspace.register_action(|_, _: &ToggleVisualLine, cx: &mut ViewContext<Workspace>| {
-        toggle_mode(Mode::VisualLine, cx)
-    });
-    workspace.register_action(
-        |_, _: &ToggleVisualBlock, cx: &mut ViewContext<Workspace>| {
-            toggle_mode(Mode::VisualBlock, cx)
-        },
-    );
-    workspace.register_action(other_end);
-    workspace.register_action(delete);
-    workspace.register_action(yank);
-
-    workspace.register_action(|workspace, action, cx| {
-        select_next(workspace, action, cx).ok();
-    });
-    workspace.register_action(|workspace, action, cx| {
-        select_previous(workspace, action, cx).ok();
-    });
-}
-
-pub fn visual_motion(motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
-    Vim::update(cx, |vim, cx| {
-        vim.update_active_editor(cx, |editor, cx| {
-            let text_layout_details = editor.text_layout_details(cx);
-            if vim.state().mode == Mode::VisualBlock
-                && !matches!(
-                    motion,
-                    Motion::EndOfLine {
-                        display_lines: false
-                    }
-                )
-            {
-                let is_up_or_down = matches!(motion, Motion::Up { .. } | Motion::Down { .. });
-                visual_block_motion(is_up_or_down, editor, cx, |map, point, goal| {
-                    motion.move_point(map, point, goal, times, &text_layout_details)
-                })
-            } else {
-                editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                    s.move_with(|map, selection| {
-                        let was_reversed = selection.reversed;
-                        let mut current_head = selection.head();
-
-                        // our motions assume the current character is after the cursor,
-                        // but in (forward) visual mode the current character is just
-                        // before the end of the selection.
-
-                        // If the file ends with a newline (which is common) we don't do this.
-                        // so that if you go to the end of such a file you can use "up" to go
-                        // to the previous line and have it work somewhat as expected.
-                        if !selection.reversed
-                            && !selection.is_empty()
-                            && !(selection.end.column() == 0 && selection.end == map.max_point())
-                        {
-                            current_head = movement::left(map, selection.end)
-                        }
-
-                        let Some((new_head, goal)) = motion.move_point(
-                            map,
-                            current_head,
-                            selection.goal,
-                            times,
-                            &text_layout_details,
-                        ) else {
-                            return;
-                        };
-
-                        selection.set_head(new_head, goal);
-
-                        // ensure the current character is included in the selection.
-                        if !selection.reversed {
-                            let next_point = if vim.state().mode == Mode::VisualBlock {
-                                movement::saturating_right(map, selection.end)
-                            } else {
-                                movement::right(map, selection.end)
-                            };
-
-                            if !(next_point.column() == 0 && next_point == map.max_point()) {
-                                selection.end = next_point;
-                            }
-                        }
-
-                        // vim always ensures the anchor character stays selected.
-                        // if our selection has reversed, we need to move the opposite end
-                        // to ensure the anchor is still selected.
-                        if was_reversed && !selection.reversed {
-                            selection.start = movement::left(map, selection.start);
-                        } else if !was_reversed && selection.reversed {
-                            selection.end = movement::right(map, selection.end);
-                        }
-                    })
-                });
-            }
-        });
-    });
-}
-
-pub fn visual_block_motion(
-    preserve_goal: bool,
-    editor: &mut Editor,
-    cx: &mut ViewContext<Editor>,
-    mut move_selection: impl FnMut(
-        &DisplaySnapshot,
-        DisplayPoint,
-        SelectionGoal,
-    ) -> Option<(DisplayPoint, SelectionGoal)>,
-) {
-    let text_layout_details = editor.text_layout_details(cx);
-    editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-        let map = &s.display_map();
-        let mut head = s.newest_anchor().head().to_display_point(map);
-        let mut tail = s.oldest_anchor().tail().to_display_point(map);
-
-        let mut head_x = map.x_for_display_point(head, &text_layout_details);
-        let mut tail_x = map.x_for_display_point(tail, &text_layout_details);
-
-        let (start, end) = match s.newest_anchor().goal {
-            SelectionGoal::HorizontalRange { start, end } if preserve_goal => (start, end),
-            SelectionGoal::HorizontalPosition(start) if preserve_goal => (start, start),
-            _ => (tail_x.0, head_x.0),
-        };
-        let mut goal = SelectionGoal::HorizontalRange { start, end };
-
-        let was_reversed = tail_x > head_x;
-        if !was_reversed && !preserve_goal {
-            head = movement::saturating_left(map, head);
-        }
-
-        let Some((new_head, _)) = move_selection(&map, head, goal) else {
-            return;
-        };
-        head = new_head;
-        head_x = map.x_for_display_point(head, &text_layout_details);
-
-        let is_reversed = tail_x > head_x;
-        if was_reversed && !is_reversed {
-            tail = movement::saturating_left(map, tail);
-            tail_x = map.x_for_display_point(tail, &text_layout_details);
-        } else if !was_reversed && is_reversed {
-            tail = movement::saturating_right(map, tail);
-            tail_x = map.x_for_display_point(tail, &text_layout_details);
-        }
-        if !is_reversed && !preserve_goal {
-            head = movement::saturating_right(map, head);
-            head_x = map.x_for_display_point(head, &text_layout_details);
-        }
-
-        let positions = if is_reversed {
-            head_x..tail_x
-        } else {
-            tail_x..head_x
-        };
-
-        if !preserve_goal {
-            goal = SelectionGoal::HorizontalRange {
-                start: positions.start.0,
-                end: positions.end.0,
-            };
-        }
-
-        let mut selections = Vec::new();
-        let mut row = tail.row();
-
-        loop {
-            let layed_out_line = map.layout_row(row, &text_layout_details);
-            let start = DisplayPoint::new(
-                row,
-                layed_out_line.closest_index_for_x(positions.start) as u32,
-            );
-            let mut end = DisplayPoint::new(
-                row,
-                layed_out_line.closest_index_for_x(positions.end) as u32,
-            );
-            if end <= start {
-                if start.column() == map.line_len(start.row()) {
-                    end = start;
-                } else {
-                    end = movement::saturating_right(map, start);
-                }
-            }
-
-            if positions.start <= layed_out_line.width {
-                let selection = Selection {
-                    id: s.new_selection_id(),
-                    start: start.to_point(map),
-                    end: end.to_point(map),
-                    reversed: is_reversed,
-                    goal: goal.clone(),
-                };
-
-                selections.push(selection);
-            }
-            if row == head.row() {
-                break;
-            }
-            if tail.row() > head.row() {
-                row -= 1
-            } else {
-                row += 1
-            }
-        }
-
-        s.select(selections);
-    })
-}
-
-pub fn visual_object(object: Object, cx: &mut WindowContext) {
-    Vim::update(cx, |vim, cx| {
-        if let Some(Operator::Object { around }) = vim.active_operator() {
-            vim.pop_operator(cx);
-            let current_mode = vim.state().mode;
-            let target_mode = object.target_visual_mode(current_mode);
-            if target_mode != current_mode {
-                vim.switch_mode(target_mode, true, cx);
-            }
-
-            vim.update_active_editor(cx, |editor, cx| {
-                editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                    s.move_with(|map, selection| {
-                        let mut head = selection.head();
-
-                        // all our motions assume that the current character is
-                        // after the cursor; however in the case of a visual selection
-                        // the current character is before the cursor.
-                        if !selection.reversed {
-                            head = movement::left(map, head);
-                        }
-
-                        if let Some(range) = object.range(map, head, around) {
-                            if !range.is_empty() {
-                                let expand_both_ways = object.always_expands_both_ways()
-                                    || selection.is_empty()
-                                    || movement::right(map, selection.start) == selection.end;
-
-                                if expand_both_ways {
-                                    selection.start = range.start;
-                                    selection.end = range.end;
-                                } else if selection.reversed {
-                                    selection.start = range.start;
-                                } else {
-                                    selection.end = range.end;
-                                }
-                            }
-                        }
-                    });
-                });
-            });
-        }
-    });
-}
-
-fn toggle_mode(mode: Mode, cx: &mut ViewContext<Workspace>) {
-    Vim::update(cx, |vim, cx| {
-        if vim.state().mode == mode {
-            vim.switch_mode(Mode::Normal, false, cx);
-        } else {
-            vim.switch_mode(mode, false, cx);
-        }
-    })
-}
-
-pub fn other_end(_: &mut Workspace, _: &OtherEnd, cx: &mut ViewContext<Workspace>) {
-    Vim::update(cx, |vim, cx| {
-        vim.update_active_editor(cx, |editor, cx| {
-            editor.change_selections(None, cx, |s| {
-                s.move_with(|_, selection| {
-                    selection.reversed = !selection.reversed;
-                })
-            })
-        })
-    });
-}
-
-pub fn delete(_: &mut Workspace, _: &VisualDelete, cx: &mut ViewContext<Workspace>) {
-    Vim::update(cx, |vim, cx| {
-        vim.record_current_action(cx);
-        vim.update_active_editor(cx, |editor, cx| {
-            let mut original_columns: HashMap<_, _> = Default::default();
-            let line_mode = editor.selections.line_mode;
-
-            editor.transact(cx, |editor, cx| {
-                editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                    s.move_with(|map, selection| {
-                        if line_mode {
-                            let mut position = selection.head();
-                            if !selection.reversed {
-                                position = movement::left(map, position);
-                            }
-                            original_columns.insert(selection.id, position.to_point(map).column);
-                        }
-                        selection.goal = SelectionGoal::None;
-                    });
-                });
-                copy_selections_content(editor, line_mode, cx);
-                editor.insert("", cx);
-
-                // Fixup cursor position after the deletion
-                editor.set_clip_at_line_ends(true, cx);
-                editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                    s.move_with(|map, selection| {
-                        let mut cursor = selection.head().to_point(map);
-
-                        if let Some(column) = original_columns.get(&selection.id) {
-                            cursor.column = *column
-                        }
-                        let cursor = map.clip_point(cursor.to_display_point(map), Bias::Left);
-                        selection.collapse_to(cursor, selection.goal)
-                    });
-                    if vim.state().mode == Mode::VisualBlock {
-                        s.select_anchors(vec![s.first_anchor()])
-                    }
-                });
-            })
-        });
-        vim.switch_mode(Mode::Normal, true, cx);
-    });
-}
-
-pub fn yank(_: &mut Workspace, _: &VisualYank, cx: &mut ViewContext<Workspace>) {
-    Vim::update(cx, |vim, cx| {
-        vim.update_active_editor(cx, |editor, cx| {
-            let line_mode = editor.selections.line_mode;
-            copy_selections_content(editor, line_mode, cx);
-            editor.change_selections(None, cx, |s| {
-                s.move_with(|map, selection| {
-                    if line_mode {
-                        selection.start = start_of_line(map, false, selection.start);
-                    };
-                    selection.collapse_to(selection.start, SelectionGoal::None)
-                });
-                if vim.state().mode == Mode::VisualBlock {
-                    s.select_anchors(vec![s.first_anchor()])
-                }
-            });
-        });
-        vim.switch_mode(Mode::Normal, true, cx);
-    });
-}
-
-pub(crate) fn visual_replace(text: Arc<str>, cx: &mut WindowContext) {
-    Vim::update(cx, |vim, cx| {
-        vim.stop_recording();
-        vim.update_active_editor(cx, |editor, cx| {
-            editor.transact(cx, |editor, cx| {
-                let (display_map, selections) = editor.selections.all_adjusted_display(cx);
-
-                // Selections are biased right at the start. So we need to store
-                // anchors that are biased left so that we can restore the selections
-                // after the change
-                let stable_anchors = editor
-                    .selections
-                    .disjoint_anchors()
-                    .into_iter()
-                    .map(|selection| {
-                        let start = selection.start.bias_left(&display_map.buffer_snapshot);
-                        start..start
-                    })
-                    .collect::<Vec<_>>();
-
-                let mut edits = Vec::new();
-                for selection in selections.iter() {
-                    let selection = selection.clone();
-                    for row_range in
-                        movement::split_display_range_by_lines(&display_map, selection.range())
-                    {
-                        let range = row_range.start.to_offset(&display_map, Bias::Right)
-                            ..row_range.end.to_offset(&display_map, Bias::Right);
-                        let text = text.repeat(range.len());
-                        edits.push((range, text));
-                    }
-                }
-
-                editor.buffer().update(cx, |buffer, cx| {
-                    buffer.edit(edits, None, cx);
-                });
-                editor.change_selections(None, cx, |s| s.select_ranges(stable_anchors));
-            });
-        });
-        vim.switch_mode(Mode::Normal, false, cx);
-    });
-}
-
-pub fn select_next(
-    _: &mut Workspace,
-    _: &SelectNext,
-    cx: &mut ViewContext<Workspace>,
-) -> Result<()> {
-    Vim::update(cx, |vim, cx| {
-        let count =
-            vim.take_count(cx)
-                .unwrap_or_else(|| if vim.state().mode.is_visual() { 1 } else { 2 });
-        vim.update_active_editor(cx, |editor, cx| {
-            for _ in 0..count {
-                match editor.select_next(&Default::default(), cx) {
-                    Err(a) => return Err(a),
-                    _ => {}
-                }
-            }
-            Ok(())
-        })
-    })
-    .unwrap_or(Ok(()))
-}
-
-pub fn select_previous(
-    _: &mut Workspace,
-    _: &SelectPrevious,
-    cx: &mut ViewContext<Workspace>,
-) -> Result<()> {
-    Vim::update(cx, |vim, cx| {
-        let count =
-            vim.take_count(cx)
-                .unwrap_or_else(|| if vim.state().mode.is_visual() { 1 } else { 2 });
-        vim.update_active_editor(cx, |editor, cx| {
-            for _ in 0..count {
-                match editor.select_previous(&Default::default(), cx) {
-                    Err(a) => return Err(a),
-                    _ => {}
-                }
-            }
-            Ok(())
-        })
-    })
-    .unwrap_or(Ok(()))
-}
-
-#[cfg(test)]
-mod test {
-    use indoc::indoc;
-    use workspace::item::Item;
-
-    use crate::{
-        state::Mode,
-        test::{NeovimBackedTestContext, VimTestContext},
-    };
-
-    #[gpui::test]
-    async fn test_enter_visual_mode(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.set_shared_state(indoc! {
-            "The ˇquick brown
-            fox jumps over
-            the lazy dog"
-        })
-        .await;
-        let cursor = cx.update_editor(|editor, cx| editor.pixel_position_of_cursor(cx));
-
-        // entering visual mode should select the character
-        // under cursor
-        cx.simulate_shared_keystrokes(["v"]).await;
-        cx.assert_shared_state(indoc! { "The «qˇ»uick brown
-            fox jumps over
-            the lazy dog"})
-            .await;
-        cx.update_editor(|editor, cx| assert_eq!(cursor, editor.pixel_position_of_cursor(cx)));
-
-        // forwards motions should extend the selection
-        cx.simulate_shared_keystrokes(["w", "j"]).await;
-        cx.assert_shared_state(indoc! { "The «quick brown
-            fox jumps oˇ»ver
-            the lazy dog"})
-            .await;
-
-        cx.simulate_shared_keystrokes(["escape"]).await;
-        assert_eq!(Mode::Normal, cx.neovim_mode().await);
-        cx.assert_shared_state(indoc! { "The quick brown
-            fox jumps ˇover
-            the lazy dog"})
-            .await;
-
-        // motions work backwards
-        cx.simulate_shared_keystrokes(["v", "k", "b"]).await;
-        cx.assert_shared_state(indoc! { "The «ˇquick brown
-            fox jumps o»ver
-            the lazy dog"})
-            .await;
-
-        // works on empty lines
-        cx.set_shared_state(indoc! {"
-            a
-            ˇ
-            b
-            "})
-            .await;
-        let cursor = cx.update_editor(|editor, cx| editor.pixel_position_of_cursor(cx));
-        cx.simulate_shared_keystrokes(["v"]).await;
-        cx.assert_shared_state(indoc! {"
-            a
-            «
-            ˇ»b
-        "})
-            .await;
-        cx.update_editor(|editor, cx| assert_eq!(cursor, editor.pixel_position_of_cursor(cx)));
-
-        // toggles off again
-        cx.simulate_shared_keystrokes(["v"]).await;
-        cx.assert_shared_state(indoc! {"
-            a
-            ˇ
-            b
-            "})
-            .await;
-
-        // works at the end of a document
-        cx.set_shared_state(indoc! {"
-            a
-            b
-            ˇ"})
-            .await;
-
-        cx.simulate_shared_keystrokes(["v"]).await;
-        cx.assert_shared_state(indoc! {"
-            a
-            b
-            ˇ"})
-            .await;
-        assert_eq!(cx.mode(), cx.neovim_mode().await);
-    }
-
-    #[gpui::test]
-    async fn test_enter_visual_line_mode(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.set_shared_state(indoc! {
-            "The ˇquick brown
-            fox jumps over
-            the lazy dog"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["shift-v"]).await;
-        cx.assert_shared_state(indoc! { "The «qˇ»uick brown
-            fox jumps over
-            the lazy dog"})
-            .await;
-        assert_eq!(cx.mode(), cx.neovim_mode().await);
-        cx.simulate_shared_keystrokes(["x"]).await;
-        cx.assert_shared_state(indoc! { "fox ˇjumps over
-        the lazy dog"})
-            .await;
-
-        // it should work on empty lines
-        cx.set_shared_state(indoc! {"
-            a
-            ˇ
-            b"})
-            .await;
-        cx.simulate_shared_keystrokes(["shift-v"]).await;
-        cx.assert_shared_state(indoc! { "
-            a
-            «
-            ˇ»b"})
-            .await;
-        cx.simulate_shared_keystrokes(["x"]).await;
-        cx.assert_shared_state(indoc! { "
-            a
-            ˇb"})
-            .await;
-
-        // it should work at the end of the document
-        cx.set_shared_state(indoc! {"
-            a
-            b
-            ˇ"})
-            .await;
-        let cursor = cx.update_editor(|editor, cx| editor.pixel_position_of_cursor(cx));
-        cx.simulate_shared_keystrokes(["shift-v"]).await;
-        cx.assert_shared_state(indoc! {"
-            a
-            b
-            ˇ"})
-            .await;
-        assert_eq!(cx.mode(), cx.neovim_mode().await);
-        cx.update_editor(|editor, cx| assert_eq!(cursor, editor.pixel_position_of_cursor(cx)));
-        cx.simulate_shared_keystrokes(["x"]).await;
-        cx.assert_shared_state(indoc! {"
-            a
-            ˇb"})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_visual_delete(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.assert_binding_matches(["v", "w"], "The quick ˇbrown")
-            .await;
-
-        cx.assert_binding_matches(["v", "w", "x"], "The quick ˇbrown")
-            .await;
-        cx.assert_binding_matches(
-            ["v", "w", "j", "x"],
-            indoc! {"
-                The ˇquick brown
-                fox jumps over
-                the lazy dog"},
-        )
-        .await;
-        // Test pasting code copied on delete
-        cx.simulate_shared_keystrokes(["j", "p"]).await;
-        cx.assert_state_matches().await;
-
-        let mut cx = cx.binding(["v", "w", "j", "x"]);
-        cx.assert_all(indoc! {"
-                The ˇquick brown
-                fox jumps over
-                the ˇlazy dog"})
-            .await;
-        let mut cx = cx.binding(["v", "b", "k", "x"]);
-        cx.assert_all(indoc! {"
-                The ˇquick brown
-                fox jumps ˇover
-                the ˇlazy dog"})
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_visual_line_delete(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.set_shared_state(indoc! {"
-                The quˇick brown
-                fox jumps over
-                the lazy dog"})
-            .await;
-        cx.simulate_shared_keystrokes(["shift-v", "x"]).await;
-        cx.assert_state_matches().await;
-
-        // Test pasting code copied on delete
-        cx.simulate_shared_keystroke("p").await;
-        cx.assert_state_matches().await;
-
-        cx.set_shared_state(indoc! {"
-                The quick brown
-                fox jumps over
-                the laˇzy dog"})
-            .await;
-        cx.simulate_shared_keystrokes(["shift-v", "x"]).await;
-        cx.assert_state_matches().await;
-        cx.assert_shared_clipboard("the lazy dog\n").await;
-
-        for marked_text in cx.each_marked_position(indoc! {"
-                        The quˇick brown
-                        fox jumps over
-                        the lazy dog"})
-        {
-            cx.set_shared_state(&marked_text).await;
-            cx.simulate_shared_keystrokes(["shift-v", "j", "x"]).await;
-            cx.assert_state_matches().await;
-            // Test pasting code copied on delete
-            cx.simulate_shared_keystroke("p").await;
-            cx.assert_state_matches().await;
-        }
-
-        cx.set_shared_state(indoc! {"
-            The ˇlong line
-            should not
-            crash
-            "})
-            .await;
-        cx.simulate_shared_keystrokes(["shift-v", "$", "x"]).await;
-        cx.assert_state_matches().await;
-    }
-
-    #[gpui::test]
-    async fn test_visual_yank(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.set_shared_state("The quick ˇbrown").await;
-        cx.simulate_shared_keystrokes(["v", "w", "y"]).await;
-        cx.assert_shared_state("The quick ˇbrown").await;
-        cx.assert_shared_clipboard("brown").await;
-
-        cx.set_shared_state(indoc! {"
-                The ˇquick brown
-                fox jumps over
-                the lazy dog"})
-            .await;
-        cx.simulate_shared_keystrokes(["v", "w", "j", "y"]).await;
-        cx.assert_shared_state(indoc! {"
-                    The ˇquick brown
-                    fox jumps over
-                    the lazy dog"})
-            .await;
-        cx.assert_shared_clipboard(indoc! {"
-                quick brown
-                fox jumps o"})
-            .await;
-
-        cx.set_shared_state(indoc! {"
-                    The quick brown
-                    fox jumps over
-                    the ˇlazy dog"})
-            .await;
-        cx.simulate_shared_keystrokes(["v", "w", "j", "y"]).await;
-        cx.assert_shared_state(indoc! {"
-                    The quick brown
-                    fox jumps over
-                    the ˇlazy dog"})
-            .await;
-        cx.assert_shared_clipboard("lazy d").await;
-        cx.simulate_shared_keystrokes(["shift-v", "y"]).await;
-        cx.assert_shared_clipboard("the lazy dog\n").await;
-
-        let mut cx = cx.binding(["v", "b", "k", "y"]);
-        cx.set_shared_state(indoc! {"
-                    The ˇquick brown
-                    fox jumps over
-                    the lazy dog"})
-            .await;
-        cx.simulate_shared_keystrokes(["v", "b", "k", "y"]).await;
-        cx.assert_shared_state(indoc! {"
-                    ˇThe quick brown
-                    fox jumps over
-                    the lazy dog"})
-            .await;
-        assert_eq!(
-            cx.read_from_clipboard()
-                .map(|item| item.text().clone())
-                .unwrap(),
-            "The q"
-        );
-
-        cx.set_shared_state(indoc! {"
-                    The quick brown
-                    fox ˇjumps over
-                    the lazy dog"})
-            .await;
-        cx.simulate_shared_keystrokes(["shift-v", "shift-g", "shift-y"])
-            .await;
-        cx.assert_shared_state(indoc! {"
-                    The quick brown
-                    ˇfox jumps over
-                    the lazy dog"})
-            .await;
-        cx.assert_shared_clipboard("fox jumps over\nthe lazy dog\n")
-            .await;
-    }
-
-    #[gpui::test]
-    async fn test_visual_block_mode(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.set_shared_state(indoc! {
-            "The ˇquick brown
-             fox jumps over
-             the lazy dog"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["ctrl-v"]).await;
-        cx.assert_shared_state(indoc! {
-            "The «qˇ»uick brown
-            fox jumps over
-            the lazy dog"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["2", "down"]).await;
-        cx.assert_shared_state(indoc! {
-            "The «qˇ»uick brown
-            fox «jˇ»umps over
-            the «lˇ»azy dog"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["e"]).await;
-        cx.assert_shared_state(indoc! {
-            "The «quicˇ»k brown
-            fox «jumpˇ»s over
-            the «lazyˇ» dog"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["^"]).await;
-        cx.assert_shared_state(indoc! {
-            "«ˇThe q»uick brown
-            «ˇfox j»umps over
-            «ˇthe l»azy dog"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["$"]).await;
-        cx.assert_shared_state(indoc! {
-            "The «quick brownˇ»
-            fox «jumps overˇ»
-            the «lazy dogˇ»"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["shift-f", " "]).await;
-        cx.assert_shared_state(indoc! {
-            "The «quickˇ» brown
-            fox «jumpsˇ» over
-            the «lazy ˇ»dog"
-        })
-        .await;
-
-        // toggling through visual mode works as expected
-        cx.simulate_shared_keystrokes(["v"]).await;
-        cx.assert_shared_state(indoc! {
-            "The «quick brown
-            fox jumps over
-            the lazy ˇ»dog"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["ctrl-v"]).await;
-        cx.assert_shared_state(indoc! {
-            "The «quickˇ» brown
-            fox «jumpsˇ» over
-            the «lazy ˇ»dog"
-        })
-        .await;
-
-        cx.set_shared_state(indoc! {
-            "The ˇquick
-             brown
-             fox
-             jumps over the
-
-             lazy dog
-            "
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["ctrl-v", "down", "down"])
-            .await;
-        cx.assert_shared_state(indoc! {
-            "The«ˇ q»uick
-            bro«ˇwn»
-            foxˇ
-            jumps over the
-
-            lazy dog
-            "
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["down"]).await;
-        cx.assert_shared_state(indoc! {
-            "The «qˇ»uick
-            brow«nˇ»
-            fox
-            jump«sˇ» over the
-
-            lazy dog
-            "
-        })
-        .await;
-        cx.simulate_shared_keystroke("left").await;
-        cx.assert_shared_state(indoc! {
-            "The«ˇ q»uick
-            bro«ˇwn»
-            foxˇ
-            jum«ˇps» over the
-
-            lazy dog
-            "
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["s", "o", "escape"]).await;
-        cx.assert_shared_state(indoc! {
-            "Theˇouick
-            broo
-            foxo
-            jumo over the
-
-            lazy dog
-            "
-        })
-        .await;
-
-        //https://github.com/zed-industries/community/issues/1950
-        cx.set_shared_state(indoc! {
-            "Theˇ quick brown
-
-            fox jumps over
-            the lazy dog
-            "
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["l", "ctrl-v", "j", "j"])
-            .await;
-        cx.assert_shared_state(indoc! {
-            "The «qˇ»uick brown
-
-            fox «jˇ»umps over
-            the lazy dog
-            "
-        })
-        .await;
-    }
-
-    #[gpui::test]
-    async fn test_visual_block_issue_2123(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.set_shared_state(indoc! {
-            "The ˇquick brown
-            fox jumps over
-            the lazy dog
-            "
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["ctrl-v", "right", "down"])
-            .await;
-        cx.assert_shared_state(indoc! {
-            "The «quˇ»ick brown
-            fox «juˇ»mps over
-            the lazy dog
-            "
-        })
-        .await;
-    }
-
-    #[gpui::test]
-    async fn test_visual_block_insert(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.set_shared_state(indoc! {
-            "ˇThe quick brown
-            fox jumps over
-            the lazy dog
-            "
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["ctrl-v", "9", "down"]).await;
-        cx.assert_shared_state(indoc! {
-            "«Tˇ»he quick brown
-            «fˇ»ox jumps over
-            «tˇ»he lazy dog
-            ˇ"
-        })
-        .await;
-
-        cx.simulate_shared_keystrokes(["shift-i", "k", "escape"])
-            .await;
-        cx.assert_shared_state(indoc! {
-            "ˇkThe quick brown
-            kfox jumps over
-            kthe lazy dog
-            k"
-        })
-        .await;
-
-        cx.set_shared_state(indoc! {
-            "ˇThe quick brown
-            fox jumps over
-            the lazy dog
-            "
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["ctrl-v", "9", "down"]).await;
-        cx.assert_shared_state(indoc! {
-            "«Tˇ»he quick brown
-            «fˇ»ox jumps over
-            «tˇ»he lazy dog
-            ˇ"
-        })
-        .await;
-        cx.simulate_shared_keystrokes(["c", "k", "escape"]).await;
-        cx.assert_shared_state(indoc! {
-            "ˇkhe quick brown
-            kox jumps over
-            khe lazy dog
-            k"
-        })
-        .await;
-    }
-
-    #[gpui::test]
-    async fn test_visual_object(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx).await;
-
-        cx.set_shared_state("hello (in [parˇens] o)").await;
-        cx.simulate_shared_keystrokes(["ctrl-v", "l"]).await;
-        cx.simulate_shared_keystrokes(["a", "]"]).await;
-        cx.assert_shared_state("hello (in «[parens]ˇ» o)").await;
-        assert_eq!(cx.mode(), Mode::Visual);
-        cx.simulate_shared_keystrokes(["i", "("]).await;
-        cx.assert_shared_state("hello («in [parens] oˇ»)").await;
-
-        cx.set_shared_state("hello in a wˇord again.").await;
-        cx.simulate_shared_keystrokes(["ctrl-v", "l", "i", "w"])
-            .await;
-        cx.assert_shared_state("hello in a w«ordˇ» again.").await;
-        assert_eq!(cx.mode(), Mode::VisualBlock);
-        cx.simulate_shared_keystrokes(["o", "a", "s"]).await;
-        cx.assert_shared_state("«ˇhello in a word» again.").await;
-        assert_eq!(cx.mode(), Mode::Visual);
-    }
-
-    #[gpui::test]
-    async fn test_mode_across_command(cx: &mut gpui::TestAppContext) {
-        let mut cx = VimTestContext::new(cx, true).await;
-
-        cx.set_state("aˇbc", Mode::Normal);
-        cx.simulate_keystrokes(["ctrl-v"]);
-        assert_eq!(cx.mode(), Mode::VisualBlock);
-        cx.simulate_keystrokes(["cmd-shift-p", "escape"]);
-        assert_eq!(cx.mode(), Mode::VisualBlock);
-    }
-}

crates/vim2/test_data/test_a.json 🔗

@@ -1,6 +0,0 @@
-{"Put":{"state":"The qˇuick"}}
-{"Key":"a"}
-{"Get":{"state":"The quˇick","mode":"Insert"}}
-{"Put":{"state":"The quicˇk"}}
-{"Key":"a"}
-{"Get":{"state":"The quickˇ","mode":"Insert"}}

crates/vim2/test_data/test_b.json 🔗

@@ -1,54 +0,0 @@
-{"Put":{"state":"ˇThe quick-brown\n\n\nfox_jumps over\nthe"}}
-{"Key":"b"}
-{"Get":{"state":"ˇThe quick-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The ˇquick-brown\n\n\nfox_jumps over\nthe"}}
-{"Key":"b"}
-{"Get":{"state":"ˇThe quick-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quickˇ-brown\n\n\nfox_jumps over\nthe"}}
-{"Key":"b"}
-{"Get":{"state":"The ˇquick-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quick-ˇbrown\n\n\nfox_jumps over\nthe"}}
-{"Key":"b"}
-{"Get":{"state":"The quickˇ-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quick-brown\nˇ\n\nfox_jumps over\nthe"}}
-{"Key":"b"}
-{"Get":{"state":"The quick-ˇbrown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quick-brown\n\nˇ\nfox_jumps over\nthe"}}
-{"Key":"b"}
-{"Get":{"state":"The quick-brown\nˇ\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quick-brown\n\n\nˇfox_jumps over\nthe"}}
-{"Key":"b"}
-{"Get":{"state":"The quick-brown\n\nˇ\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quick-brown\n\n\nfox_jumps ˇover\nthe"}}
-{"Key":"b"}
-{"Get":{"state":"The quick-brown\n\n\nˇfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quick-brown\n\n\nfox_jumps over\nˇthe"}}
-{"Key":"b"}
-{"Get":{"state":"The quick-brown\n\n\nfox_jumps ˇover\nthe","mode":"Normal"}}
-{"Put":{"state":"ˇThe quick-brown\n\n\nfox_jumps over\nthe"}}
-{"Key":"shift-b"}
-{"Get":{"state":"ˇThe quick-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The ˇquick-brown\n\n\nfox_jumps over\nthe"}}
-{"Key":"shift-b"}
-{"Get":{"state":"ˇThe quick-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quickˇ-brown\n\n\nfox_jumps over\nthe"}}
-{"Key":"shift-b"}
-{"Get":{"state":"The ˇquick-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quick-ˇbrown\n\n\nfox_jumps over\nthe"}}
-{"Key":"shift-b"}
-{"Get":{"state":"The ˇquick-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quick-brown\nˇ\n\nfox_jumps over\nthe"}}
-{"Key":"shift-b"}
-{"Get":{"state":"The ˇquick-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quick-brown\n\nˇ\nfox_jumps over\nthe"}}
-{"Key":"shift-b"}
-{"Get":{"state":"The quick-brown\nˇ\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quick-brown\n\n\nˇfox_jumps over\nthe"}}
-{"Key":"shift-b"}
-{"Get":{"state":"The quick-brown\n\nˇ\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quick-brown\n\n\nfox_jumps ˇover\nthe"}}
-{"Key":"shift-b"}
-{"Get":{"state":"The quick-brown\n\n\nˇfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quick-brown\n\n\nfox_jumps over\nˇthe"}}
-{"Key":"shift-b"}
-{"Get":{"state":"The quick-brown\n\n\nfox_jumps ˇover\nthe","mode":"Normal"}}

crates/vim2/test_data/test_backspace.json 🔗

@@ -1,9 +0,0 @@
-{"Put":{"state":"ˇThe quick\nbrown"}}
-{"Key":"backspace"}
-{"Get":{"state":"ˇThe quick\nbrown","mode":"Normal"}}
-{"Put":{"state":"The qˇuick\nbrown"}}
-{"Key":"backspace"}
-{"Get":{"state":"The ˇquick\nbrown","mode":"Normal"}}
-{"Put":{"state":"The quick\nˇbrown"}}
-{"Key":"backspace"}
-{"Get":{"state":"The quicˇk\nbrown","mode":"Normal"}}

crates/vim2/test_data/test_capital_f_and_capital_t.json 🔗

@@ -1,570 +0,0 @@
-{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n","mode":"Normal"}}
-{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n","mode":"Normal"}}
-{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n","mode":"Normal"}}
-{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n","mode":"Normal"}}
-{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n","mode":"Normal"}}
-{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n","mode":"Normal"}}

crates/vim2/test_data/test_cc.json 🔗

@@ -1,24 +0,0 @@
-{"Put":{"state":"ˇ"}}
-{"Key":"c"}
-{"Key":"c"}
-{"Get":{"state":"ˇ","mode":"Insert"}}
-{"Put":{"state":"The ˇquick"}}
-{"Key":"c"}
-{"Key":"c"}
-{"Get":{"state":"ˇ","mode":"Insert"}}
-{"Put":{"state":"The quˇick\nbrown fox\njumps over"}}
-{"Key":"c"}
-{"Key":"c"}
-{"Get":{"state":"ˇ\nbrown fox\njumps over","mode":"Insert"}}
-{"Put":{"state":"The quick\nbrown ˇfox\njumps over"}}
-{"Key":"c"}
-{"Key":"c"}
-{"Get":{"state":"The quick\nˇ\njumps over","mode":"Insert"}}
-{"Put":{"state":"The quick\nbrown fox\njumps ˇover"}}
-{"Key":"c"}
-{"Key":"c"}
-{"Get":{"state":"The quick\nbrown fox\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick\nˇ\nbrown fox"}}
-{"Key":"c"}
-{"Key":"c"}
-{"Get":{"state":"The quick\nˇ\nbrown fox","mode":"Insert"}}

crates/vim2/test_data/test_change_0.json 🔗

@@ -1,8 +0,0 @@
-{"Put":{"state":"The qˇuick\nbrown fox"}}
-{"Key":"c"}
-{"Key":"0"}
-{"Get":{"state":"ˇuick\nbrown fox","mode":"Insert"}}
-{"Put":{"state":"The quick\nˇ\nbrown fox"}}
-{"Key":"c"}
-{"Key":"0"}
-{"Get":{"state":"The quick\nˇ\nbrown fox","mode":"Insert"}}

crates/vim2/test_data/test_change_b.json 🔗

@@ -1,24 +0,0 @@
-{"Put":{"state":"Teˇst Test"}}
-{"Key":"c"}
-{"Key":"b"}
-{"Get":{"state":"ˇst Test","mode":"Insert"}}
-{"Put":{"state":"Test ˇtest"}}
-{"Key":"c"}
-{"Key":"b"}
-{"Get":{"state":"ˇtest","mode":"Insert"}}
-{"Put":{"state":"Test1 test2 ˇtest3"}}
-{"Key":"c"}
-{"Key":"b"}
-{"Get":{"state":"Test1 ˇtest3","mode":"Insert"}}
-{"Put":{"state":"Test test\nˇtest"}}
-{"Key":"c"}
-{"Key":"b"}
-{"Get":{"state":"Test ˇ\ntest","mode":"Insert"}}
-{"Put":{"state":"Test test\nˇ\ntest"}}
-{"Key":"c"}
-{"Key":"b"}
-{"Get":{"state":"Test ˇ\n\ntest","mode":"Insert"}}
-{"Put":{"state":"Test test-test ˇtest"}}
-{"Key":"c"}
-{"Key":"shift-b"}
-{"Get":{"state":"Test ˇtest","mode":"Insert"}}

crates/vim2/test_data/test_change_backspace.json 🔗

@@ -1,16 +0,0 @@
-{"Put":{"state":"Teˇst"}}
-{"Key":"c"}
-{"Key":"backspace"}
-{"Get":{"state":"Tˇst","mode":"Insert"}}
-{"Put":{"state":"Tˇest"}}
-{"Key":"c"}
-{"Key":"backspace"}
-{"Get":{"state":"ˇest","mode":"Insert"}}
-{"Put":{"state":"ˇTest"}}
-{"Key":"c"}
-{"Key":"backspace"}
-{"Get":{"state":"ˇTest","mode":"Insert"}}
-{"Put":{"state":"Test\nˇtest"}}
-{"Key":"c"}
-{"Key":"backspace"}
-{"Get":{"state":"Testˇtest","mode":"Insert"}}

crates/vim2/test_data/test_change_case.json 🔗

@@ -1,23 +0,0 @@
-{"Put":{"state":"ˇabC\n"}}
-{"Key":"~"}
-{"Get":{"state":"AˇbC\n","mode":"Normal"}}
-{"Key":"2"}
-{"Key":"~"}
-{"Get":{"state":"ABˇc\n","mode":"Normal"}}
-{"Put":{"state":"a😀C«dÉ1*fˇ»\n"}}
-{"Key":"~"}
-{"Get":{"state":"a😀CˇDé1*F\n","mode":"Normal"}}
-{"Key":"~"}
-{"Put":{"state":"aˇC😀é1*F\n"}}
-{"Key":"4"}
-{"Key":"~"}
-{"Get":{"state":"ac😀É1ˇ*F\n","mode":"Normal"}}
-{"Put":{"state":"abˇC\n"}}
-{"Key":"shift-v"}
-{"Key":"~"}
-{"Get":{"state":"ˇABc\n","mode":"Normal"}}
-{"Put":{"state":"ˇaa\nbb\ncc"}}
-{"Key":"ctrl-v"}
-{"Key":"j"}
-{"Key":"~"}
-{"Get":{"state":"ˇAa\nBb\ncc","mode":"Normal"}}

crates/vim2/test_data/test_change_e.json 🔗

@@ -1,24 +0,0 @@
-{"Put":{"state":"Teˇst Test"}}
-{"Key":"c"}
-{"Key":"e"}
-{"Get":{"state":"Teˇ Test","mode":"Insert"}}
-{"Put":{"state":"Tˇest test"}}
-{"Key":"c"}
-{"Key":"e"}
-{"Get":{"state":"Tˇ test","mode":"Insert"}}
-{"Put":{"state":"Test teˇst\ntest"}}
-{"Key":"c"}
-{"Key":"e"}
-{"Get":{"state":"Test teˇ\ntest","mode":"Insert"}}
-{"Put":{"state":"Test tesˇt\ntest"}}
-{"Key":"c"}
-{"Key":"e"}
-{"Get":{"state":"Test tesˇ","mode":"Insert"}}
-{"Put":{"state":"Test test\nˇ\ntest"}}
-{"Key":"c"}
-{"Key":"e"}
-{"Get":{"state":"Test test\nˇ","mode":"Insert"}}
-{"Put":{"state":"Test teˇst-test test"}}
-{"Key":"c"}
-{"Key":"shift-e"}
-{"Get":{"state":"Test teˇ test","mode":"Insert"}}

crates/vim2/test_data/test_change_end_of_document.json 🔗

@@ -1,16 +0,0 @@
-{"Put":{"state":"The quick\nbrownˇ fox\njumps over\nthe lazy"}}
-{"Key":"c"}
-{"Key":"shift-g"}
-{"Get":{"state":"The quick\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick\nbrownˇ fox\njumps over\nthe lazy"}}
-{"Key":"c"}
-{"Key":"shift-g"}
-{"Get":{"state":"The quick\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick\nbrown fox\njumps over\nthe lˇazy"}}
-{"Key":"c"}
-{"Key":"shift-g"}
-{"Get":{"state":"The quick\nbrown fox\njumps over\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick\nbrown fox\njumps over\nˇ"}}
-{"Key":"c"}
-{"Key":"shift-g"}
-{"Get":{"state":"The quick\nbrown fox\njumps over\nˇ","mode":"Insert"}}

crates/vim2/test_data/test_change_end_of_line.json 🔗

@@ -1,8 +0,0 @@
-{"Put":{"state":"The qˇuick\nbrown fox"}}
-{"Key":"c"}
-{"Key":"$"}
-{"Get":{"state":"The qˇ\nbrown fox","mode":"Insert"}}
-{"Put":{"state":"The quick\nˇ\nbrown fox"}}
-{"Key":"c"}
-{"Key":"$"}
-{"Get":{"state":"The quick\nˇ\nbrown fox","mode":"Insert"}}

crates/vim2/test_data/test_change_gg.json 🔗

@@ -1,20 +0,0 @@
-{"Put":{"state":"The quick\nbrownˇ fox\njumps over\nthe lazy"}}
-{"Key":"c"}
-{"Key":"g"}
-{"Key":"g"}
-{"Get":{"state":"ˇ\njumps over\nthe lazy","mode":"Insert"}}
-{"Put":{"state":"The quick\nbrown fox\njumps over\nthe lˇazy"}}
-{"Key":"c"}
-{"Key":"g"}
-{"Key":"g"}
-{"Get":{"state":"ˇ","mode":"Insert"}}
-{"Put":{"state":"The qˇuick\nbrown fox\njumps over\nthe lazy"}}
-{"Key":"c"}
-{"Key":"g"}
-{"Key":"g"}
-{"Get":{"state":"ˇ\nbrown fox\njumps over\nthe lazy","mode":"Insert"}}
-{"Put":{"state":"ˇ\nbrown fox\njumps over\nthe lazy"}}
-{"Key":"c"}
-{"Key":"g"}
-{"Key":"g"}
-{"Get":{"state":"ˇ\nbrown fox\njumps over\nthe lazy","mode":"Insert"}}